STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
main.cpp
Go to the documentation of this file.
1/**
2 * ProtoEncoder Hotfire — dual AS5600 encoder board
3 *
4 * Minimal state machine: Setup -> SelfTest -> Active.
5 * In Setup, sends SETUP heartbeats and waits for any SENSOR_CONFIG packet.
6 * In SelfTest, probes each encoder over I2C, sends a SELF_TEST packet, then
7 * transitions to Active. In Active, streams encoder data continuously.
8 */
9
10#include <Arduino.h>
11#include <Ethernet.h>
12#include <EthernetUdp.h>
13#include <SPI.h>
14#include <STAR_EthernetOTA.h>
15#include <Wire.h>
16#include <daq-protocol.h>
17#include <esp_mac.h>
18
19#include <cstring>
20#include <vector>
21
22#ifndef BOARD_ID
23#define BOARD_ID 61
24#endif
25#include "firmware_hash.h"
26#include "hotfire_config.h"
27#include "sense_config.h"
28
29// ---------------------------------------------------------------------------
30// Pin definitions — Encoder Board PCB
31// ---------------------------------------------------------------------------
32
33// Encoder 1 (Wire)
34static constexpr int ENC1_SDA = 4;
35static constexpr int ENC1_SCL = 5;
36
37// Encoder 2 (Wire1)
38static constexpr int ENC2_SDA = 15;
39static constexpr int ENC2_SCL = 7;
40
41// W5500 Ethernet (SPI)
42static constexpr int ETH_MOSI = 11;
43static constexpr int ETH_MISO = 13;
44static constexpr int ETH_SCLK = 12;
45static constexpr int ETH_CS = 10;
46
47// ---------------------------------------------------------------------------
48// AS5600 constants
49// ---------------------------------------------------------------------------
50static constexpr uint8_t AS5600_ADDR = 0x36;
51static constexpr uint8_t REG_STATUS = 0x0B;
52static constexpr uint8_t REG_RAW_MSB = 0x0C;
53static constexpr uint8_t STATUS_MH = 0x08; // magnet too strong
54static constexpr uint8_t STATUS_ML = 0x10; // magnet too weak
55static constexpr uint8_t STATUS_MD = 0x20; // magnet detected
56static constexpr uint16_t READ_ERROR = 0xFFFF;
57
58// Sensor IDs used in SENSOR_DATA packets
59static constexpr uint8_t SENSOR_ID_ENC1 = 1;
60static constexpr uint8_t SENSOR_ID_ENC2 = 2;
61static constexpr uint8_t NUM_SENSORS = 2;
62
63// ---------------------------------------------------------------------------
64// Network / packet config
65// ---------------------------------------------------------------------------
66static constexpr int ENCODER_PACKET_BUF_SIZE = 512;
67static constexpr int UDP_LISTEN_PORT = 5005;
68
69// ---------------------------------------------------------------------------
70// State machine
71// ---------------------------------------------------------------------------
72enum class State : uint8_t { Setup = 1, SelfTest = 10, Active = 2 };
74
75// ---------------------------------------------------------------------------
76// Global state
77// ---------------------------------------------------------------------------
78static byte mac[6];
80static IPAddress gateway(0, 0, 0, 0);
81static IPAddress subnet(255, 255, 255, 0);
82static IPAddress dns_addr(192, 168, 2, 1);
84static constexpr int serverPort = HOTFIRE_SERVER_PORT;
85
86static EthernetUDP udp;
88
89static std::vector<daq::SensorDataChunkCollection> dataChunks;
90static unsigned long lastHeartbeatMs = 0;
91
92// ---------------------------------------------------------------------------
93// AS5600 I2C helpers (parameterized by bus)
94// ---------------------------------------------------------------------------
95
96static bool i2cReadBytes(TwoWire& bus, uint8_t reg, uint8_t* buf, uint8_t len) {
97 bus.beginTransmission(AS5600_ADDR);
98 bus.write(reg);
99 if (bus.endTransmission(false) != 0)
100 return false;
101 bus.requestFrom(AS5600_ADDR, len);
102 if (bus.available() < len)
103 return false;
104 for (uint8_t i = 0; i < len; i++)
105 buf[i] = bus.read();
106 return true;
107}
108
109static uint16_t readRawAngle(TwoWire& bus) {
110 uint8_t buf[2];
111 if (!i2cReadBytes(bus, REG_RAW_MSB, buf, 2))
112 return READ_ERROR;
113 return ((uint16_t)(buf[0] << 8) | buf[1]) & 0x0FFF;
114}
115
116static bool checkMagnetPresence(TwoWire& bus, const char* label) {
117 Serial.print("[");
118 Serial.print(label);
119 Serial.println("] Checking magnet...");
120 unsigned long start = millis();
121 while (millis() - start < 3000) {
122 uint8_t status;
123 if (!i2cReadBytes(bus, REG_STATUS, &status, 1)) {
124 Serial.print("[");
125 Serial.print(label);
126 Serial.println("] ERROR: could not read STATUS — check wiring");
127 delay(500);
128 continue;
129 }
130 status &= 0x38;
131 if (status & STATUS_MD) {
132 if (status & STATUS_MH) {
133 Serial.print("[");
134 Serial.print(label);
135 Serial.println(
136 "] WARNING: magnet too strong — increase air gap");
137 return false;
138 }
139 if (status & STATUS_ML) {
140 Serial.print("[");
141 Serial.print(label);
142 Serial.println("] WARNING: magnet too weak — decrease air gap");
143 return false;
144 }
145 Serial.print("[");
146 Serial.print(label);
147 Serial.println("] Magnet detected OK");
148 return true;
149 }
150 delay(100);
151 }
152 Serial.print("[");
153 Serial.print(label);
154 Serial.println("] ERROR: no magnet detected after 3 s");
155 return false;
156}
157
158// ---------------------------------------------------------------------------
159// Encoder self-test — verify I2C communication with each AS5600
160// ---------------------------------------------------------------------------
161
162static bool testEncoderComms(TwoWire& bus) {
163 uint8_t status;
164 return i2cReadBytes(bus, REG_STATUS, &status, 1);
165}
166
167static void runSelfTest() {
168 Serial.println("=== Encoder Self-Test ===");
169
170 uint8_t adc_good = 0; // no ADC on this board
171 Serial.println(" ADC TDAC: FAIL (no ADC)");
172
173 bool enc1_ok = testEncoderComms(Wire);
174 Serial.print(" Encoder 1 I2C: ");
175 Serial.println(enc1_ok ? "PASS" : "FAIL");
176
177 bool enc2_ok = testEncoderComms(Wire1);
178 Serial.print(" Encoder 2 I2C: ");
179 Serial.println(enc2_ok ? "PASS" : "FAIL");
180
181 std::vector<daq::SelfTestResult> results;
182 results.push_back(daq::SelfTestResult{
183 SENSOR_ID_ENC1, static_cast<uint8_t>(enc1_ok ? 1 : 0)});
184 results.push_back(daq::SelfTestResult{
185 SENSOR_ID_ENC2, static_cast<uint8_t>(enc2_ok ? 1 : 0)});
186
187 uint8_t buf[ENCODER_PACKET_BUF_SIZE];
188 size_t n = daq::create_self_test_packet(adc_good, results, millis(), buf,
189 sizeof(buf));
190 if (n > 0) {
191 udp.beginPacket(serverIP, serverPort);
192 udp.write(buf, n);
193 udp.endPacket();
194 Serial.println(" Sent SELF_TEST packet to server");
195 }
196
197 Serial.println("=========================");
198 Serial.flush();
199
201 Serial.println("State -> Active");
202 Serial.flush();
203}
204
205// ---------------------------------------------------------------------------
206// Packet sending
207// ---------------------------------------------------------------------------
208
209static void sendDataChunks() {
211 return;
212
213 uint8_t buf[ENCODER_PACKET_BUF_SIZE];
215 buf, sizeof(buf));
216 if (n == 0) {
217 dataChunks.clear();
218 return;
219 }
220
221 udp.beginPacket(serverIP, serverPort);
222 udp.write(buf, n);
223 udp.endPacket();
224
225 dataChunks.clear();
226}
227
229 switch (boardState) {
230 case State::Setup:
232 case State::SelfTest:
234 case State::Active:
236 default:
238 }
239}
240
241static void sendHeartbeat() {
242 daq::BoardHeartbeatPacket hb;
243 memcpy(hb.firmware_hash, FirmwareHash::get(), 32);
244 hb.board_id = (uint8_t)BOARD_ID;
245 hb.engine_state = daq::EngineState::SAFE;
246 hb.board_state = currentBoardState();
247
248 uint8_t buf[ENCODER_PACKET_BUF_SIZE];
249 size_t n =
250 daq::create_board_heartbeat_packet(hb, millis(), buf, sizeof(buf));
251 if (n == 0)
252 return;
253
254 udp.beginPacket(serverIP, serverPort);
255 udp.write(buf, n);
256 udp.endPacket();
257}
258
259// ---------------------------------------------------------------------------
260// Arduino entry points
261// ---------------------------------------------------------------------------
262
263void setup() {
264 Serial.begin(115200);
265 delay(2000);
267 Serial.println("=== ProtoEncoder Hotfire ===");
268
269 // I2C bus 1 — Encoder 1
270 Wire.begin(ENC1_SDA, ENC1_SCL);
271 Wire.setClock(100000);
272 Serial.println("I2C bus 1 initialized (Encoder 1)");
273 checkMagnetPresence(Wire, "ENC1");
274
275 // I2C bus 2 — Encoder 2
276 Wire1.begin(ENC2_SDA, ENC2_SCL);
277 Wire1.setClock(100000);
278 Serial.println("I2C bus 2 initialized (Encoder 2)");
279 checkMagnetPresence(Wire1, "ENC2");
280
281 // Ethernet
282 ESP_ERROR_CHECK(esp_read_mac(mac, ESP_MAC_ETH));
283 staticIP = IPAddress(192, 168, 2, (uint8_t)BOARD_ID);
284 SPI.begin(ETH_SCLK, ETH_MISO, ETH_MOSI, ETH_CS);
286 Ethernet.init(ETH_CS);
290
291 udp.begin(UDP_LISTEN_PORT);
292 Serial.print("UDP listening on port ");
293 Serial.println(UDP_LISTEN_PORT);
294
295 otaServer.begin();
296 Serial.print("OTA TCP server listening on port ");
297 Serial.println(HOTFIRE_OTA_PORT);
298
299 Serial.print("Board ID: ");
300 Serial.print((unsigned)BOARD_ID);
301 Serial.print(" IP: ");
302 Serial.println(Ethernet.localIP());
303 Serial.print("Server: ");
304 Serial.print(serverIP);
305 Serial.print(":");
306 Serial.println(serverPort);
307 Serial.println("State -> Setup");
308 Serial.println("(Waiting for SENSOR_CONFIG packet to begin self-test)");
309 Serial.flush();
310}
311
312void loop() {
313 // OTA check — blocks only if a client connects; on success the board
314 // reboots into the new image and never returns here.
315 otaServer.poll();
317
318 switch (boardState) {
319 case State::Setup: {
320 int packetSize = udp.parsePacket();
321 if (packetSize > 0) {
322 uint8_t typeByte;
323 udp.read(&typeByte, 1);
324 udp.flush();
325 if (typeByte ==
326 static_cast<uint8_t>(daq::PacketType::SENSOR_CONFIG)) {
327 Serial.println(
328 "SENSOR_CONFIG received -> State -> SelfTest");
329 Serial.flush();
331 }
332 }
333 break;
334 }
335
336 case State::SelfTest:
337 runSelfTest();
338 break;
339
340 case State::Active: {
341 uint16_t raw1 = readRawAngle(Wire);
342 uint16_t raw2 = readRawAngle(Wire1);
343
344 uint32_t val1 =
345 (raw1 != READ_ERROR) ? static_cast<uint32_t>(raw1) : 0u;
346 uint32_t val2 =
347 (raw2 != READ_ERROR) ? static_cast<uint32_t>(raw2) : 0u;
348
350 chunk.add_datapoint(SENSOR_ID_ENC1, val1);
351 chunk.add_datapoint(SENSOR_ID_ENC2, val2);
352
354 dataChunks.push_back(chunk);
355
357 break;
358 }
359 }
360
361 // Heartbeat in all states
362 unsigned long now = millis();
364 lastHeartbeatMs = now;
366 }
367
369}
static SerialStub Serial
Definition Arduino.h:128
unsigned long millis()
Definition Arduino.h:29
#define ETHERNET_INIT_DELAY_MS
#define LOOP_DELAY_MS
#define ETHERNET_BEGIN_DELAY_MS
#define BOARD_HEARTBEAT_INTERVAL_MS
#define ETHERNET_SPI_DELAY_MS
static EthernetClass Ethernet
Definition Ethernet.h:116
const int IPAddress
void delay(int ms)
static uint8_t chunk[64]
Definition board_uart9.c:12
The OTA listener.
uint8_t reg
void setup()
Definition main.cpp:142
void loop()
Definition main.cpp:201
std::vector< daq::SensorDataChunkCollection > dataChunks
Definition main.cpp:48
#define NUM_SENSORS
Definition main.cpp:46
const int serverPort
Definition main.cpp:38
byte mac[6]
Definition main.cpp:31
IPAddress subnet(255, 255, 255, 0)
IPAddress staticIP(192, 168, 2, BOARD_ID_DEFAULT)
IPAddress serverIP(192, 168, 2, 20)
EthernetUDP udp
Definition main.cpp:40
IPAddress gateway(0, 0, 0, 0)
bool i2cReadBytes(uint8_t reg, uint8_t *buf, uint8_t len)
Definition main.cpp:30
#define REG_STATUS
Definition main.cpp:13
#define REG_RAW_MSB
Definition main.cpp:14
#define AS5600_ADDR
Definition main.cpp:9
#define STATUS_ML
Definition main.cpp:21
#define STATUS_MH
Definition main.cpp:20
bool checkMagnetPresence()
Definition main.cpp:120
#define READ_ERROR
Definition main.cpp:23
#define STATUS_MD
Definition main.cpp:22
uint16_t readRawAngle()
Definition main.cpp:109
static IPAddress subnet(255, 255, 255, 0)
static IPAddress gateway(192, 168, 2, 1)
StarOTA::Server otaServer(OTA_TCP_PORT)
static constexpr int ETH_CS
Ball Valve Hotfire — motorized ball valve prototype board.
Definition main.cpp:53
static constexpr int ETH_SCLK
Definition main.cpp:55
static constexpr int ETH_MISO
Definition main.cpp:56
static constexpr int ETH_MOSI
Definition main.cpp:54
static IPAddress serverIP(192, 168, 2, HOTFIRE_SERVER_IP_OCTET_4)
static bool testEncoderComms(TwoWire &bus)
Definition main.cpp:162
static void runSelfTest()
Definition main.cpp:167
static IPAddress dns_addr(192, 168, 2, 1)
static constexpr int ENC2_SDA
Definition main.cpp:38
static constexpr int ENC2_SCL
Definition main.cpp:39
static constexpr int ENC1_SCL
Definition main.cpp:35
static constexpr uint8_t SENSOR_ID_ENC2
Definition main.cpp:60
static constexpr uint8_t SENSOR_ID_ENC1
Definition main.cpp:59
static void sendHeartbeat()
Definition main.cpp:241
static void sendDataChunks()
Definition main.cpp:209
static constexpr int ENC1_SDA
Definition main.cpp:34
static State boardState
Definition main.cpp:73
static daq::BoardState currentBoardState()
Definition main.cpp:228
static constexpr int ENCODER_PACKET_BUF_SIZE
Definition main.cpp:66
static unsigned long lastHeartbeatMs
Definition main.cpp:90
static constexpr int UDP_LISTEN_PORT
Definition main.cpp:67
State
Definition main.cpp:72
@ Active
Definition main.cpp:72
@ Setup
Definition main.cpp:72
@ SelfTest
Definition main.cpp:72
static void start(gnss_frame_parser_t *p, uint8_t byte)
Definition gnss_frame.c:45
#define HOTFIRE_SERVER_PORT
#define HOTFIRE_SERVER_IP_OCTET_4
#define HOTFIRE_OTA_PORT
#define BOARD_ID
const uint8_t * get()
void printTestMessage()
Bench-test helper: prints STAR_OTA_TEST_MESSAGE on a timer as.
size_t create_sensor_data_packet(const std::vector< SensorDataChunkCollection > &chunks, const uint8_t num_sensors, uint32_t timestamp_ms, uint8_t *buffer, size_t buffer_size)
Creates a complete Sensor Data packet in the provided buffer.
size_t create_board_heartbeat_packet(const BoardHeartbeatPacket &data, uint32_t timestamp_ms, uint8_t *buffer, size_t buffer_size)
Creates a complete Board Heartbeat packet in the provided buffer.
BoardState
Defines the operational state of a board.
Definition DiabloEnums.h:31
size_t create_self_test_packet(uint8_t adc_good, const std::vector< SelfTestResult > &results, uint32_t timestamp_ms, uint8_t *buffer, size_t buffer_size)
Creates a complete Self Test packet in the provided buffer.
*brief user defined string literal for JSON values *sa std::size_t n
Definition json.hpp:24474
#define SENSOR_MAX_CHUNKS_BEFORE_SEND
Represents a single data chunk with timestamp and sensor datapoints.