STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
main.cpp
Go to the documentation of this file.
1#include <Arduino.h>
2#include <Ethernet.h>
3#include <EthernetUdp.h>
4#include <SPI.h>
5#include <daq-protocol.h>
6#include <esp_mac.h>
7
8#include <cstring>
9
10#include "actuator_board_pins.h"
11
12using namespace actuator_board_pins;
13
14// Configuration
15const unsigned long ADC_READ_INTERVAL_MS =
16 1000; // Read ADC every 100ms (adjust as needed)
17
18// Ethernet configuration
19byte mac[6]; // Will be populated with unique MAC from ESP32 eFuse
20IPAddress staticIP(192, 168, 2, 202);
21IPAddress gateway(0, 0, 0, 0);
22IPAddress subnet(255, 255, 255, 0);
23IPAddress dns(192, 168, 2, 1);
24const int udpPort = 5005; // Port to listen for actuator commands
25IPAddress receiverIP(192, 168, 2, 20); // IP address to send sensor data to
26const int receiverPort = 5006; // Port to send sensor data to
27EthernetUDP udp;
28
29// Actuator state tracking
30// Store the current state of each actuator (0 = OFF, 1 = ON)
32
33// PWM state tracking
34struct PWMState {
35 bool active;
36 unsigned long start_time;
37 uint32_t duration;
38 uint8_t channel;
39};
40
42
43// Sensor data collection
44const uint8_t NUM_SENSORS = NUM_ACTUATORS; // 10 current sense pins
45unsigned long lastAdcReadTime = 0;
46
47// Forward declarations
49void processActuatorCommand(const std::vector<daq::ActuatorCommand>& commands);
51 const std::vector<daq::PWMActuatorCommand>& commands);
52void updatePWM();
55
56void setup() {
57 Serial.begin(115200);
58
59 // while (!Serial) {a
60 // delay(10); // Wait for native USB serial to connect
61 // }
62
63 Serial.println("Starting Actuator Testing with Ethernet...");
64
65 // Initialize all actuators to OFF state
67
68 // Initialize current sense pins as analog inputs
69 Serial.println("Initializing current sense pins...");
70 for (uint8_t i = 0; i < NUM_SENSORS; i++) {
71 int pin = getCurrentSensePin(i);
72 if (pin >= 0) {
73 pinMode(pin, INPUT);
74 Serial.print("Current sense sensor ");
75 Serial.print(i);
76 Serial.print(" (GPIO ");
77 Serial.print(pin);
78 Serial.println(") initialized");
79 }
80 }
81
82 // Generate unique MAC address from ESP32 eFuse (derived for Ethernet)
83 ESP_ERROR_CHECK(
84 esp_read_mac(mac, ESP_MAC_ETH)); // Derived from base eFuse MAC
85
86 Serial.print("Generated unique MAC address: ");
87 for (int i = 0; i < 6; i++) {
88 if (i > 0)
89 Serial.print(":");
90 if (mac[i] < 0x10)
91 Serial.print("0");
92 Serial.print(mac[i], HEX);
93 }
94 Serial.println();
95
96 // Setup Ethernet SPI
97 Serial.println("Initializing Ethernet...");
98 SPI.begin(Actuator_Board.ETH_SCLK, Actuator_Board.ETH_MISO,
99 Actuator_Board.ETH_MOSI, Actuator_Board.ETH_CS);
100 delay(1000);
101
102 // Initialize Ethernet with CS pin
103 Ethernet.init(Actuator_Board.ETH_CS);
104 delay(1000);
105
106 // Start Ethernet with static IP
108 delay(1000);
109
110 // Start UDP
111 udp.begin(udpPort);
112
113 // Print Ethernet status
114 Serial.print("Ethernet initialized. IP: ");
115 Serial.println(Ethernet.localIP());
116 Serial.print("Link Status: ");
117 if (Ethernet.linkStatus() == LinkON) {
118 Serial.println("Connected");
119 } else if (Ethernet.linkStatus() == LinkOFF) {
120 Serial.println("Disconnected");
121 } else {
122 Serial.println("Unknown");
123 }
124
125 Serial.print("Listening for actuator commands on UDP port: ");
126 Serial.println(udpPort);
127 Serial.println("Setup complete!");
128}
129
130void loop() {
131 // Check for incoming UDP packets
132 int packetSize = udp.parsePacket();
133 if (packetSize > 0) {
134 // Read the packet into a buffer
136 int bytesRead = udp.read(packetBuffer, MAX_PACKET_SIZE);
137
138 if (bytesRead > 0) {
139 // Check the packet type from the header
140 daq::PacketHeader* header = (daq::PacketHeader*)packetBuffer;
141
142 if (header->packet_type == daq::PacketType::ACTUATOR_COMMAND) {
143 // Parse the packet as an actuator command
144 daq::PacketHeader parsedHeader;
145 std::vector<daq::ActuatorCommand> commands;
146
148 packetBuffer, bytesRead, parsedHeader, commands)) {
149 Serial.print("Received actuator command packet with ");
150 Serial.print(commands.size());
151 Serial.println(" commands");
152
154 } else {
155 Serial.println(
156 "Error: Failed to parse actuator command packet");
157 }
158 } else if (header->packet_type ==
160 // Parse the packet as a PWM actuator command
161 daq::PacketHeader parsedHeader;
162 std::vector<daq::PWMActuatorCommand> commands;
163
165 parsedHeader, commands)) {
166 Serial.print("Received PWM actuator command packet with ");
167 Serial.print(commands.size());
168 Serial.println(" commands");
169
171 } else {
172 Serial.println(
173 "Error: Failed to parse PWM actuator command packet");
174 }
175 } else {
176 // Unknown packet type
177 // Serial.print("Received unknown packet type: ");
178 // Serial.println((int)header->packet_type);
179 }
180 }
181 }
182
183 // Check for expired PWM commands
184 updatePWM();
185
186 // Read current sense pins and send sensor data at regular intervals
187 unsigned long currentTime = millis();
188 if (currentTime - lastAdcReadTime >= ADC_READ_INTERVAL_MS) {
189 lastAdcReadTime = currentTime;
191 }
192}
193
195 Serial.println("Initializing actuators...");
196
197 // Set all actuator pins as outputs and write LOW (OFF state)
198 // Actuators are 1-indexed (1-10)
199 for (int i = 1; i <= NUM_ACTUATORS; i++) {
200 int pin = getActuatorPin(i);
201 if (pin < 0) {
202 Serial.print("Warning: Invalid actuator ID ");
203 Serial.println(i);
204 continue;
205 }
206
207 pinMode(pin, OUTPUT);
208 digitalWrite(pin, LOW);
209 actuator_states[i - 1] = 0; // Store OFF state (0-indexed array)
210 Serial.print("Actuator ");
211 Serial.print(i);
212 Serial.print(" (GPIO ");
213 Serial.print(pin);
214 Serial.println(") set to OFF");
215 }
216
217 Serial.println("All actuators initialized to OFF state");
218
219 // Initialize PWM states
220 for (int i = 0; i < NUM_ACTUATORS; i++) {
221 pwm_states[i].active = false;
222 pwm_states[i].start_time = 0;
223 pwm_states[i].duration = 0;
224 // Map actuator index 0-9 to channel 0-9
225 pwm_states[i].channel = i;
226 }
227}
228
229void processActuatorCommand(const std::vector<daq::ActuatorCommand>& commands) {
230 for (const auto& cmd : commands) {
231 // Actuator IDs are 1-indexed (1-10) from the server
232 // Validate actuator ID
233 if (cmd.actuator_id < 1 || cmd.actuator_id > NUM_ACTUATORS) {
234 Serial.print("Warning: Invalid actuator ID: ");
235 Serial.println(cmd.actuator_id);
236 continue;
237 }
238
239 // Get the GPIO pin for this actuator (1-indexed)
240 int pin = getActuatorPin(cmd.actuator_id);
241 if (pin < 0) {
242 Serial.print("Warning: Failed to get pin for actuator ID: ");
243 Serial.println(cmd.actuator_id);
244 continue;
245 }
246
247 // Determine the state (0 = OFF, non-zero = ON)
248 uint8_t new_state = (cmd.actuator_state != 0) ? 1 : 0;
249
250 // Convert 1-indexed actuator ID to 0-indexed array index
251 uint8_t array_index = cmd.actuator_id - 1;
252
253 // Check if PWM is active on this actuator
255 // Stop PWM
256 ledcDetachPin(pin);
257 pwm_states[array_index].active = false;
258 Serial.print("Actuator ");
259 Serial.print(cmd.actuator_id);
260 Serial.println(" PWM interrupted by digital command");
261 }
262
263 // Update the actuator if the state has changed
264 if (actuator_states[array_index] != new_state) {
265 digitalWrite(pin, new_state ? HIGH : LOW);
266 actuator_states[array_index] = new_state;
267
268 Serial.print("Actuator ");
269 Serial.print(cmd.actuator_id);
270 Serial.print(" (GPIO ");
271 Serial.print(pin);
272 Serial.print(") set to ");
273 Serial.println(new_state ? "ON" : "OFF");
274 } else {
275 Serial.print("Actuator ");
276 Serial.print(cmd.actuator_id);
277 Serial.println(" already in requested state");
278 }
279 }
280}
281
283 const std::vector<daq::PWMActuatorCommand>& commands) {
284 for (const auto& cmd : commands) {
285 // Actuator IDs are 1-indexed (1-10) from the server
286 if (cmd.actuator_id < 1 || cmd.actuator_id > NUM_ACTUATORS) {
287 Serial.print("Warning: Invalid actuator ID for PWM: ");
288 Serial.println(cmd.actuator_id);
289 continue;
290 }
291
292 int pin = getActuatorPin(cmd.actuator_id);
293 if (pin < 0) {
294 Serial.print("Warning: Failed to get pin for actuator ID: ");
295 Serial.println(cmd.actuator_id);
296 continue;
297 }
298
299 uint8_t array_index = cmd.actuator_id - 1;
300 uint8_t channel = pwm_states[array_index].channel;
301
302 // ESP32-S3 only has 8 LEDC channels (0-7)
303 if (channel > 7) {
304 Serial.print("Error: Actuator ");
305 Serial.print(cmd.actuator_id);
306 Serial.println(" cannot use PWM (Channel > 7)");
307 continue;
308 }
309
310 // Configure LEDC channel
311 // Frequency in Hz, resolution in bits (14 bits = 0-16383 for better
312 // low-frequency support) 10Hz with 14-bit resolution -> div = 80M / (10
313 // * 16384) = ~488 (fits in divider)
314 ledcSetup(channel, cmd.frequency, 14);
315 ledcAttachPin(pin, channel);
316
317 // Calculate duty cycle value (0-16383)
318 // cmd.duty_cycle is 0.0 - 1.0
319 uint32_t max_duty = 16383; // 2^14 - 1
320 uint32_t duty = (uint32_t)(cmd.duty_cycle * (float)max_duty);
321 if (duty > max_duty)
322 duty = max_duty;
323
324 ledcWrite(channel, duty);
325
326 // Update state
327 pwm_states[array_index].active = true;
328 pwm_states[array_index].start_time = millis();
329 pwm_states[array_index].duration = cmd.duration;
330
331 // Update effective state (non-zero duty = ON for state tracking
332 // purposes, though it's PWM)
333 actuator_states[array_index] = (duty > 0) ? 1 : 0;
334
335 Serial.print("Actuator ");
336 Serial.print(cmd.actuator_id);
337 Serial.print(" PWM started: Freq=");
338 Serial.print(cmd.frequency);
339 Serial.print("Hz, Duty=");
340 Serial.print(cmd.duty_cycle * 100.0f);
341 Serial.print("%, Duration=");
342 Serial.print(cmd.duration);
343 Serial.println("ms");
344 }
345}
346
347void updatePWM() {
348 unsigned long current_time = millis();
349 for (int i = 0; i < NUM_ACTUATORS; i++) {
350 if (pwm_states[i].active) {
351 if (current_time - pwm_states[i].start_time >=
352 pwm_states[i].duration) {
353 // PWM duration expired
354 int pin = getActuatorPin(i + 1); // 1-indexed ID
355
356 // Stop PWM
357 ledcWrite(pwm_states[i].channel, 0);
358 ledcDetachPin(pin);
359 digitalWrite(pin, LOW); // Ensure explicit LOW
360
361 pwm_states[i].active = false;
362 actuator_states[i] = 0; // Update state to OFF
363
364 Serial.print("Actuator ");
365 Serial.print(i + 1);
366 Serial.println(" PWM finished");
367 }
368 }
369 }
370}
371
373 // Create a sensor data chunk with timestamp
375
376 // Read all current sense pins
377 for (uint8_t actuator_id = 1; actuator_id <= NUM_SENSORS; actuator_id++) {
378 int pin = getCurrentSensePin(actuator_id);
379 if (pin < 0) {
380 Serial.print("Warning: Invalid current sense actuator ID: ");
381 Serial.println(actuator_id);
382 continue;
383 }
384
385 // Read ADC value (ESP32 returns 12-bit value: 0-4095)
386 int adcValue = analogRead(pin);
387
388 // Convert ADC value to voltage (ESP32 ADC reference is 3.3V)
389 // Voltage = (ADC_value / 4095.0) * 3.3V
390 float voltage = (static_cast<float>(adcValue) / 4095.0f) * 3.3f;
391
392 // Store voltage as uint32_t (interpreted as float in packet)
393 // We need to send the float as a uint32_t by bit-casting
394 uint32_t voltage_bits;
395 memcpy(&voltage_bits, &voltage, sizeof(float));
396 // Sensor ID in packet is 1-indexed and matches actuator ID
397 chunk.add_datapoint(actuator_id, voltage_bits);
398 }
399
400 // Send the sensor data packet if we have data
401 if (!chunk.empty()) {
403 }
404}
405
407 // Create packet buffer
409
410 // Create a vector with a single chunk for the packet
411 std::vector<daq::SensorDataChunkCollection> chunks;
412 chunks.push_back(chunk);
413
414 // Create the sensor data packet using DAQv2-Comms library
415 size_t packetSize = daq::create_sensor_data_packet(
417
418 if (packetSize == 0) {
419 Serial.println("Error: Failed to create sensor data packet");
420 return;
421 }
422
423 // Send the packet via UDP Ethernet
424 udp.beginPacket(receiverIP, receiverPort);
425 udp.write(packetBuffer, packetSize);
426 udp.endPacket();
427
428 Serial.print("Sent sensor data packet: ");
429 Serial.print(packetSize);
430 Serial.print(" bytes, ");
431 Serial.print(NUM_SENSORS);
432 Serial.println(" sensors");
433}
static SerialStub Serial
Definition Arduino.h:128
#define INPUT
Definition Arduino.h:21
#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 HEX
Definition Arduino.h:24
int analogRead(int)
Definition Arduino.h:53
#define LOW
Definition Arduino.h:20
void digitalWrite(int, int)
Definition Arduino.h:48
#define ADC_READ_INTERVAL_MS
static EthernetClass Ethernet
Definition Ethernet.h:116
#define MAX_PACKET_SIZE
Definition main.h:6
void delay(int ms)
static uint8_t chunk[64]
Definition board_uart9.c:12
void readCurrentSensePins()
Definition main.cpp:372
void initializeActuators()
Definition main.cpp:194
unsigned long lastAdcReadTime
Definition main.cpp:45
const int udpPort
Definition main.cpp:24
const int receiverPort
Definition main.cpp:26
PWMState pwm_states[NUM_ACTUATORS]
Definition main.cpp:41
IPAddress staticIP(192, 168, 2, 202)
void setup()
Definition main.cpp:56
void processActuatorCommand(const std::vector< daq::ActuatorCommand > &commands)
Definition main.cpp:229
IPAddress dns(192, 168, 2, 1)
IPAddress subnet(255, 255, 255, 0)
IPAddress receiverIP(192, 168, 2, 20)
void processPWMActuatorCommand(const std::vector< daq::PWMActuatorCommand > &commands)
Definition main.cpp:282
void updatePWM()
Definition main.cpp:347
IPAddress gateway(0, 0, 0, 0)
void loop()
Definition main.cpp:130
void sendSensorDataPacket()
Definition main.cpp:210
std::vector< daq::SensorDataChunkCollection > chunks
Definition main.cpp:55
#define NUM_SENSORS
Definition main.cpp:46
IPAddress dns(192, 168, 2, 1)
byte mac[6]
Definition main.cpp:31
IPAddress subnet(255, 255, 255, 0)
IPAddress staticIP(192, 168, 2, BOARD_ID_DEFAULT)
static uint8_t actuator_states[NUM_ACTUATORS]
Definition main.cpp:80
EthernetUDP udp
Definition main.cpp:40
IPAddress gateway(0, 0, 0, 0)
char packetBuffer[UDP_TX_PACKET_MAX_SIZE]
Definition main.cpp:15
static osMessageQueueId_t commands
Definition idle_task.c:28
static BasicJsonType::size_type array_index(const string_t &s)
Definition json.hpp:14019
int getActuatorPin(uint8_t actuator_id)
int getCurrentSensePin(uint8_t actuator_id)
constexpr Layout Actuator_Board
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.
bool parse_pwm_actuator_packet(const uint8_t *buffer, size_t buffer_size, PacketHeader &header_out, std::vector< PWMActuatorCommand > &commands_out)
Parses a PWM Actuator Command packet from buffer.
bool parse_actuator_command_packet(const uint8_t *buffer, size_t buffer_size, PacketHeader &header_out, std::vector< ActuatorCommand > &commands_out)
Parses an Actuator Command packet from buffer.
uint32_t duration
Definition main.cpp:37
unsigned long start_time
Definition main.cpp:36
bool active
Definition main.cpp:35
uint8_t channel
Definition main.cpp:38
Represents a single data chunk with timestamp and sensor datapoints.