STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
main.cpp
Go to the documentation of this file.
1/**
2 * RTD (Resistance Temperature Detector) Hotfire state machine — ESP32 PCB
3 *
4 * Same state machine as PT/TC/LC: Waiting for Server → Active → Standalone
5 * Abort. Sends heartbeats, streams RTD (differential + IDAC excitation) sensor
6 * data to server or abort controller. Uses RTD_Board pin layout and
7 * BoardType::RTD. Reads connectors 1–4 (two physical ADS1263 chips). ADC1
8 * (ADC_CS_1) handles connectors 1 & 2; ADC2 (ADC_CS_2) handles connectors 3
9 * & 4. Based on RTD_Testing project for proper RTD read sequence.
10 */
11
12#include "main.h"
13
14#include <Arduino.h>
15#include <Ethernet.h>
16#include <EthernetUdp.h>
17#include <SPI.h>
18#include <daq-protocol.h>
19#include <esp_mac.h>
20
21#include <cstring>
22#include <vector>
23
24#include "STAR_ADS126X.h"
25
26// clang-format off
27// MUST be defined before sense_board_pins.h / connector_adc_map.h
28#define PINS_ACTIVE_LAYOUT sense_board_pins::RTD_Board
29#include "sense_board_pins.h"
30#include "connector_adc_map.h"
31#include "adc_mappings.h"
32#include "SensorHotfireCore.h"
33#include "SensorSelfTest.h"
34// clang-format on
35
36using namespace sense_board_pins;
37
38//-----------------------------------------------------------------------------
39// ADC config — RTD: differential (pin 1 vs pin 2) + IDAC excitation per
40// connector (from RTD_Testing)
41//-----------------------------------------------------------------------------
42// FIR @ 20 SPS: the ADS1263's quietest mode, and its FIR has fixed 50/60 Hz
43// rejection nulls (datasheet Table 9-13 / p93 — FIR is available only at
44// 2.5/5/10/20 SPS). This sensor is slow enough that 20 SPS is ample.
45#define FILTER ADS126X_FIR
46#define DATA_RATE ADS126X_RATE_20
48#define NUM_CONNECTORS 4 // Connectors 1–4 (ADC1: 1&2, ADC2: 3&4)
49
50static ADS126X ads126x; // ADC1 — connectors 1 & 2
51static ADS126X ads126x_2; // ADC2 — connectors 3 & 4
52SPIClass ADC_SPI(HSPI);
53std::vector<daq::SensorDataChunkCollection> dataChunks;
54
57
58static unsigned long g_last_sensor_packet_log_ms = 0;
59
60bool g_verbose = true;
61
62// Helper: get the correct ADS126X instance and DRDY pin for a connector
63static ADS126X& adc_for_connector(uint8_t connector_id) {
64 return (getAdcIndex(connector_id, 1) == 2) ? ads126x_2 : ads126x;
65}
66static int drdy_for_connector(uint8_t connector_id) {
67 return (getAdcIndex(connector_id, 1) == 2) ? Pins.ADC_DRDY_2
68 : Pins.ADC_DRDY_1;
69}
70
71// Set mux and IDAC for one RTD connector (differential + excitation)
72static void set_connector_rtd(uint8_t connector_id) {
73 ADS126X& adc = adc_for_connector(connector_id);
74 adc.setInputMux(getAdcChannel(connector_id, 1),
75 getAdcChannel(connector_id, 2));
76 const int idac1 = getIdacChannel(connector_id, 1);
77 const int idac2 = getIdacChannel(connector_id, 2);
78 if (idac1 >= 0) {
79 adc.setIDAC1Pin(static_cast<uint8_t>(idac1));
80 adc.setIDAC1Mag(ADS126X_IDAC_MAG_1000);
81 }
82 if (idac2 >= 0) {
83 adc.setIDAC2Pin(static_cast<uint8_t>(idac2));
84 adc.setIDAC2Mag(ADS126X_IDAC_MAG_1000);
85 }
86}
87
88static void collect_chunk_impl() {
89 const uint8_t active_count = coreState.stored_config.num_sensors;
90 if (active_count == 0)
91 return;
92 const uint8_t* active_ids = coreState.stored_config.sensor_ids;
93
95 for (uint8_t i = 0; i < active_count; i++) {
96 const uint8_t conn = active_ids[i];
98 const int drdy_pin = drdy_for_connector(conn);
101 while (digitalRead(drdy_pin) != LOW)
103 const auto reading = adc.readADC1();
104 const uint32_t value =
105 reading.checksumValid ? static_cast<uint32_t>(reading.value) : 0u;
106 chunk.add_datapoint(conn, value);
107 }
108 if (chunk.size() == active_count &&
110 dataChunks.push_back(chunk);
111}
112
113static void send_chunks_to_impl(IPAddress dest_ip, int dest_port,
114 bool also_to_abort_controller,
115 IPAddress abort_controller_ip,
116 int abort_controller_port) {
117 if (dataChunks.empty())
118 return;
120 return;
122 const uint8_t num_sensors = dataChunks[0].num_sensors;
123 size_t packetSize = daq::create_sensor_data_packet(
124 dataChunks, num_sensors, millis(), packetBuffer, sizeof(packetBuffer));
125 if (packetSize == 0)
126 return;
127 coreState.udp.beginPacket(dest_ip, dest_port);
128 coreState.udp.write(packetBuffer, packetSize);
129 coreState.udp.endPacket();
130 HF_VERBOSE("Sent: sensor_data to ");
131 HF_VERBOSE(dest_ip);
132 HF_VERBOSE(":");
133 HF_VERBOSELN(dest_port);
134
135 unsigned long now = millis();
136 if (now - g_last_sensor_packet_log_ms >= 1000) {
138 HF_VERBOSELN("SENSOR_DATA contents:");
139 for (size_t i = 0; i < dataChunks.size(); ++i) {
140 const auto& chunk = dataChunks[i];
141 HF_VERBOSE(" chunk ");
142 HF_VERBOSE(i);
143 HF_VERBOSE(" ts=");
144 HF_VERBOSE(chunk.timestamp);
145 HF_VERBOSE(" :");
146 for (const auto& dp : chunk.datapoints) {
147 HF_VERBOSE(" (id=");
148 HF_VERBOSE(static_cast<unsigned>(dp.sensor_id));
149 HF_VERBOSE(", data=");
150 HF_VERBOSE(dp.data);
151 HF_VERBOSE(")");
152 }
154 }
155 }
156
157 if (also_to_abort_controller) {
158 coreState.udp.beginPacket(abort_controller_ip, abort_controller_port);
159 coreState.udp.write(packetBuffer, packetSize);
160 coreState.udp.endPacket();
161 HF_VERBOSE("Sent: sensor_data to ");
162 HF_VERBOSE(abort_controller_ip);
163 HF_VERBOSE(":");
164 HF_VERBOSELN(abort_controller_port);
165 }
166 dataChunks.clear();
167}
168
169static void init_adc_cb(void*) {
170 // Deselect both CS pins before anything else so neither chip floats LOW
171 pinMode(Pins.ADC_CS_1, OUTPUT);
172 digitalWrite(Pins.ADC_CS_1, HIGH);
173 pinMode(Pins.ADC_CS_2, OUTPUT);
174 digitalWrite(Pins.ADC_CS_2, HIGH);
175
176 // Drive RESET and START pins for both chips
177 if (Pins.ADC_RESET_1 >= 0) {
178 pinMode(Pins.ADC_RESET_1, OUTPUT);
179 digitalWrite(Pins.ADC_RESET_1, LOW);
180 delay(10);
181 digitalWrite(Pins.ADC_RESET_1, HIGH);
182 delay(10);
183 }
184 if (Pins.ADC_START_1 >= 0) {
185 pinMode(Pins.ADC_START_1, OUTPUT);
186 digitalWrite(Pins.ADC_START_1, HIGH);
187 }
188 if (Pins.ADC_RESET_2 >= 0) {
189 pinMode(Pins.ADC_RESET_2, OUTPUT);
190 digitalWrite(Pins.ADC_RESET_2, LOW);
191 delay(10);
192 digitalWrite(Pins.ADC_RESET_2, HIGH);
193 delay(10);
194 }
195 if (Pins.ADC_START_2 >= 0) {
196 pinMode(Pins.ADC_START_2, OUTPUT);
197 digitalWrite(Pins.ADC_START_2, HIGH);
198 }
199
200 // SPI bus — use -1 for SS so hardware doesn't auto-assert any CS pin
201 ADC_SPI.begin(Pins.ADC_SCLK, Pins.ADC_MISO, Pins.ADC_MOSI, -1);
202 ADC_SPI.setDataMode(SPI_MODE1);
203
204 // --- ADC1 (connectors 1 & 2) ---
205 pinMode(Pins.ADC_DRDY_1, INPUT);
206 ads126x.begin(Pins.ADC_CS_1, &ADC_SPI);
207 ads126x.stopADC1();
209 ads126x.bypassPGA();
210 ads126x.setFilter(FILTER);
211 ads126x.setRate(DATA_RATE);
213 ads126x.startADC1();
214
215 // --- ADC2 (connectors 3 & 4) ---
216 pinMode(Pins.ADC_DRDY_2, INPUT);
217 ads126x_2.begin(Pins.ADC_CS_2, &ADC_SPI);
218 ads126x_2.stopADC1();
220 ads126x_2.bypassPGA();
221 ads126x_2.setFilter(FILTER);
222 ads126x_2.setRate(DATA_RATE);
224 ads126x_2.startADC1();
225}
226
227static void on_reference_voltage_cb(void*, uint8_t reference_voltage) {
228 uint8_t ref_pos =
229 (reference_voltage == 1) ? ADS126X_REF_POS_VDD : ADS126X_REF_POS_INT;
230 ads126x.setReference(ADS126X_REF_NEG_VSS, ref_pos);
231 ads126x_2.setReference(ADS126X_REF_NEG_VSS, ref_pos);
232}
233
234static void collect_chunk_cb(void*) {
236}
237
238static void send_chunks_to_cb(void*, IPAddress dest_ip, int dest_port,
239 bool also_to_abort_controller,
240 IPAddress abort_controller_ip,
241 int abort_controller_port) {
242 send_chunks_to_impl(dest_ip, dest_port, also_to_abort_controller,
243 abort_controller_ip, abort_controller_port);
244}
245
246static void run_self_test_cb(void*,
248 std::vector<daq::SelfTestResult>& results_out) {
249 // 1. ADC self-test (TDAC internal) for both ADCs
250 auto adc1_result = SensorSelfTest::run_adc_self_test(
252 results_out.push_back(daq::SelfTestResult{
253 0u, static_cast<uint8_t>(adc1_result.passed ? 1 : 0)});
254
255 auto adc2_result = SensorSelfTest::run_adc_self_test(
257 results_out.push_back(daq::SelfTestResult{
258 100u, static_cast<uint8_t>(adc2_result.passed ? 1 : 0)});
259
260 // 2. Sensor bias continuity test
263
264 for (uint8_t i = 0; i < cfg.num_sensors; i++) {
265 uint8_t id = cfg.sensor_ids[i];
266 int ch1 = getAdcChannel(id, 1);
267 int ch2 = getAdcChannel(id, 2);
268 if (ch1 < 0 || ch2 < 0)
269 continue;
270
272 int drdy_pin = drdy_for_connector(id);
273
274 auto bias = SensorSelfTest::read_sensor_bias(adc, drdy_pin,
275 static_cast<uint8_t>(ch1),
276 static_cast<uint8_t>(ch2));
277 uint8_t pass =
278 (bias.result == SensorSelfTest::BiasResult::CONNECTED) ? 1u : 0u;
279 results_out.push_back(daq::SelfTestResult{id, pass});
280 }
281
284}
285
286void setup() {
287 // Do NOT memset coreState -- it contains IPAddress (has vtable via
288 // Printable) and EthernetUDP. memset zeroes vtable pointers, causing
289 // LoadProhibited crashes on virtual calls like Serial.print(IPAddress).
290 coreState.gateway = IPAddress(0, 0, 0, 0);
291 coreState.subnet = IPAddress(255, 255, 255, 0);
292 coreState.dns = IPAddress(192, 168, 2, 1);
293
294 coreConfig.board_name = "RTD";
295 coreConfig.pins = &Pins;
296 coreConfig.init_adc = init_adc_cb;
297 coreConfig.collect_chunk = collect_chunk_cb;
298 coreConfig.send_chunks_to = send_chunks_to_cb;
299 coreConfig.run_self_test = run_self_test_cb;
300 coreConfig.on_reference_voltage_config = on_reference_voltage_cb;
301 coreConfig.user_data = nullptr;
302
304}
305
#define FILTER
Definition main.h:4
#define DATA_RATE
Definition main.h:5
#define INPUT
Definition Arduino.h:21
#define HSPI
Definition Arduino.h:132
int digitalRead(int)
Definition Arduino.h:50
#define HIGH
Definition Arduino.h:19
void pinMode(int, int)
Definition Arduino.h:46
#define OUTPUT
Definition Arduino.h:22
unsigned long millis()
Definition Arduino.h:29
#define SPI_MODE1
Definition Arduino.h:131
#define LOW
Definition Arduino.h:20
void delayMicroseconds(unsigned int)
Definition Arduino.h:58
void digitalWrite(int, int)
Definition Arduino.h:48
#define ADS126X_REF_POS_VDD
Definition STAR_ADS126X.h:9
#define ADS126X_REF_NEG_VSS
Definition STAR_ADS126X.h:8
#define ADS126X_REF_POS_INT
#define ADS126X_IDAC_MAG_1000
#define SENSOR_HOTFIRE_MAX_PACKET_SIZE
Shared sensor hotfire state machine core — PT / TC / LC boards.
const int IPAddress
void delay(int ms)
#define ADS126X_ASSERT_FILTER_RATE(filter, rate)
static uint8_t chunk[64]
Definition board_uart9.c:12
static ADS126X ads126x
Definition main.cpp:20
SPIClass ADC_SPI(HSPI)
std::vector< daq::SensorDataChunkCollection > dataChunks
Definition main.cpp:48
char packetBuffer[UDP_TX_PACKET_MAX_SIZE]
Definition main.cpp:15
bool g_verbose
Definition main.cpp:65
static unsigned long g_last_sensor_packet_log_ms
Definition main.cpp:62
static void run_self_test_cb(void *, const SensorHotfire::StoredSensorConfig &cfg, std::vector< daq::SelfTestResult > &results_out)
Definition main.cpp:219
static void collect_chunk_cb(void *)
Definition main.cpp:206
static SensorHotfire::Config coreConfig
Definition main.cpp:60
static void init_adc_cb(void *)
Definition main.cpp:184
static SensorHotfire::CoreState coreState
Definition main.cpp:59
static void on_reference_voltage_cb(void *, uint8_t reference_voltage)
Definition main.cpp:200
static void send_chunks_to_cb(void *, IPAddress dest_ip, int dest_port, bool also_to_abort_controller, IPAddress abort_controller_ip, int abort_controller_port)
Definition main.cpp:210
void setup()
Definition main.cpp:286
static void set_connector_rtd(uint8_t connector_id)
Definition main.cpp:72
SPIClass ADC_SPI(HSPI)
static void run_self_test_cb(void *, const SensorHotfire::StoredSensorConfig &cfg, std::vector< daq::SelfTestResult > &results_out)
Definition main.cpp:246
static ADS126X ads126x_2
Definition main.cpp:51
static int drdy_for_connector(uint8_t connector_id)
Definition main.cpp:66
static void collect_chunk_cb(void *)
Definition main.cpp:234
static void send_chunks_to_impl(IPAddress dest_ip, int dest_port, bool also_to_abort_controller, IPAddress abort_controller_ip, int abort_controller_port)
Definition main.cpp:113
static void init_adc_cb(void *)
Definition main.cpp:169
static void on_reference_voltage_cb(void *, uint8_t reference_voltage)
Definition main.cpp:227
static ADS126X & adc_for_connector(uint8_t connector_id)
Definition main.cpp:63
static void collect_chunk_impl()
Definition main.cpp:88
static void send_chunks_to_cb(void *, IPAddress dest_ip, int dest_port, bool also_to_abort_controller, IPAddress abort_controller_ip, int abort_controller_port)
Definition main.cpp:238
void loop()
Definition main.cpp:306
static STAR_MCP3201 adc(MCP3201_CS_PIN, &mcp_spi)
#define HF_VERBOSE(...)
#define HF_VERBOSELN(...)
#define HF_VERBOSELN_()
void setup(CoreState &s, const Config &cfg)
void loop(CoreState &s, const Config &cfg)
void sensor_bias_disable(ADS126X &adc)
Call once after the connector loop.
BiasReadResult read_sensor_bias(ADS126X &adc, uint8_t drdy_pin, uint8_t adc_channel, uint8_t aincom, uint8_t settle_cycles=5)
Read one connector with sensor bias already active.
void sensor_bias_enable(ADS126X &adc)
Call once before the connector loop.
AdcSelfTestResult run_adc_self_test(ADS126X &adc, uint8_t drdy_pin, uint8_t original_ref_neg, uint8_t original_ref_pos)
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.
constexpr int getAdcIndex(uint8_t connector, uint8_t pin)
constexpr int getIdacChannel(uint8_t connector, uint8_t pin)
constexpr int getAdcChannel(uint8_t connector, uint8_t pin)
constexpr const Layout & Pins
#define SENSOR_MAX_CHUNKS_BEFORE_SEND
Represents a single data chunk with timestamp and sensor datapoints.