From 85d4aa915d7600d69164fcf0972eef1cc253b448 Mon Sep 17 00:00:00 2001 From: wadhwat Date: Sat, 26 Sep 2026 12:42:14 -0400 Subject: [PATCH 1/7] Add Zucrow interface module --- firmware/lib/hardware_mapping/ec_pins.h | 1 + .../lib/zucrow_interface/ZucrowInterface.cpp | 176 ++++++++++++++++++ .../lib/zucrow_interface/ZucrowInterface.h | 26 +++ firmware/src/ec_main.cpp | 6 + 4 files changed, 209 insertions(+) create mode 100644 firmware/lib/zucrow_interface/ZucrowInterface.cpp create mode 100644 firmware/lib/zucrow_interface/ZucrowInterface.h diff --git a/firmware/lib/hardware_mapping/ec_pins.h b/firmware/lib/hardware_mapping/ec_pins.h index 9eb8cc9..4f0882b 100644 --- a/firmware/lib/hardware_mapping/ec_pins.h +++ b/firmware/lib/hardware_mapping/ec_pins.h @@ -66,6 +66,7 @@ extern Uart RS485_2; // RS485 on UART 2 #define PIN_SPARK_TRIG PC13 // Zucrow Board +#define ZUCROW_BOARD_SPI_BUS PT_TC_SPI_1 #define PIN_ZUCROW_BOARD_CS PE3 // TODO - replace with PT board definition #define PIN_ZUCROW_BOARD_DO1 PF4 diff --git a/firmware/lib/zucrow_interface/ZucrowInterface.cpp b/firmware/lib/zucrow_interface/ZucrowInterface.cpp new file mode 100644 index 0000000..d3644ab --- /dev/null +++ b/firmware/lib/zucrow_interface/ZucrowInterface.cpp @@ -0,0 +1,176 @@ +#include "ZucrowInterface.h" +#include "CommandRouter.h" +#include "CommsSerial.h" +#include "ec_pins.h" + +// Analog chain on the Zucrow board: +// MCP4822 12 bit DAC, internal 2.048 V reference, 1x gain +// -> TLV9142 non-inverting stage, gain = 1 + 15k / 5k = 4 +// -> AO_1 (ox) and AO_2 (fu) at the Zucrow connector +// Full scale is 2.048 V * 4 = 8.192 V, which is exactly 2 mV per count. +#define DAC_COUNTS 4096 +#define DAC_MAX_COUNT (DAC_COUNTS - 1) +#define VOLTS_PER_COUNT (2.048f * 4.0f / DAC_COUNTS) + +// Valve angle that maps to full scale. Zucrow configures their scaling to match. +#define FULL_SCALE_ANGLE_DEG 90.0f + +// MCP4822 write command is one 16 bit word [MCP4822 datasheet, write command register]: +// bit 15 channel (0 = A, 1 = B), bit 13 gain (1 = 1x), bit 12 output (1 = active), bits 11:0 data +// LDAC is tied to GND_ISO on this board, so each write updates the output immediately. +#define MCP4822_CHANNEL_A 0x0000 +#define MCP4822_CHANNEL_B 0x8000 +#define MCP4822_GAIN_1X 0x2000 +#define MCP4822_ACTIVE 0x1000 + +// TODO - verify clock integrity with a scope. The ISO6441 isolator is rated to 150 Mbps, so the +// limit here is the long traces to the Zucrow board. Matches the other devices on this bus. +SPISettings ZUCROW_DAC_SPI_SETTINGS(4000000, MSBFIRST, SPI_MODE0); + +// Line assignments. DI3 and DI4 are reserved for the Skipper autosequences. +// TODO - confirm DI1/DI2 and DO1/DO2 against the schematic net names. +#define PIN_ZUCROW_FAULT_IN PIN_ZUCROW_BOARD_DI1 +#define PIN_ZUCROW_SYNC_IN PIN_ZUCROW_BOARD_DI2 +#define PIN_EC_FAULT_OUT PIN_ZUCROW_BOARD_DO1 +#define PIN_EC_SYNC_OUT PIN_ZUCROW_BOARD_DO2 + +// Line polarities, mirroring TADPOLE: Zucrow pulls its lines low to assert, we drive ours high. +// TODO - verify on the bench that an unplugged Zucrow connector reads as a fault. A disconnected +// line must safe the system. +#define ZUCROW_FAULT_LEVEL LOW +#define ZUCROW_RUNNING_LEVEL LOW +#define EC_FAULT_LEVEL HIGH +#define EC_OK_LEVEL LOW +#define EC_RUNNING_LEVEL HIGH +#define EC_IDLE_LEVEL LOW + +namespace ZucrowInterface { + +uint16_t last_count_ox; +uint16_t last_count_fu; + +void write_dac(uint16_t channel, uint16_t count) { + // beginTransaction before CS so the bus is in this device's SPI mode before it is selected. + ZUCROW_BOARD_SPI_BUS.beginTransaction(ZUCROW_DAC_SPI_SETTINGS); + digitalWrite(PIN_ZUCROW_BOARD_CS, LOW); + ZUCROW_BOARD_SPI_BUS.transfer16(channel | MCP4822_GAIN_1X | MCP4822_ACTIVE | count); + digitalWrite(PIN_ZUCROW_BOARD_CS, HIGH); + ZUCROW_BOARD_SPI_BUS.endTransaction(); +} + +uint16_t angle_to_count(float angle_deg) { + // Written as !(x > 0) rather than x <= 0 so that NaN also maps to 0 instead of + // being cast to an integer, which is undefined behavior. + if (!(angle_deg > 0.0f)) { + return 0; + } + + // Full scale maps to 4096, one past what a 12 bit DAC can hold. + float count = angle_deg / FULL_SCALE_ANGLE_DEG * DAC_COUNTS; + if (count > DAC_MAX_COUNT) { + return DAC_MAX_COUNT; + } + + return (uint16_t)(count + 0.5f); +} + +void send_valve_angles(float ox_angle_deg, float fu_angle_deg) { + last_count_ox = angle_to_count(ox_angle_deg); + last_count_fu = angle_to_count(fu_angle_deg); + write_dac(MCP4822_CHANNEL_A, last_count_ox); + write_dac(MCP4822_CHANNEL_B, last_count_fu); +} + +bool check_fault() { + return digitalRead(PIN_ZUCROW_FAULT_IN) == ZUCROW_FAULT_LEVEL; +} + +bool check_sync() { + return digitalRead(PIN_ZUCROW_SYNC_IN) == ZUCROW_RUNNING_LEVEL; +} + +void send_fault() { + digitalWrite(PIN_EC_FAULT_OUT, EC_FAULT_LEVEL); +} + +void send_ok() { + digitalWrite(PIN_EC_FAULT_OUT, EC_OK_LEVEL); +} + +void send_sync(bool running) { + digitalWrite(PIN_EC_SYNC_OUT, running ? EC_RUNNING_LEVEL : EC_IDLE_LEVEL); +} + +void print_status() { + CommsSerial.printf("From Zucrow: fault %s, sync %s\n", check_fault() ? "ASSERTED" : "clear", + check_sync() ? "running" : "idle"); + CommsSerial.printf("To Zucrow: fault %s, sync %s\n", + digitalRead(PIN_EC_FAULT_OUT) == EC_FAULT_LEVEL ? "ASSERTED" : "clear", + digitalRead(PIN_EC_SYNC_OUT) == EC_RUNNING_LEVEL ? "running" : "idle"); + CommsSerial.printf("OX: %4u counts, %.3f V\n", last_count_ox, last_count_ox * VOLTS_PER_COUNT); + CommsSerial.printf("FU: %4u counts, %.3f V\n", last_count_fu, last_count_fu * VOLTS_PER_COUNT); +} + +void send_angles_cmd(const char *args) { + float ox_angle_deg; + float fu_angle_deg; + if (sscanf(args, "%f %f", &ox_angle_deg, &fu_angle_deg) != 2) { + CommsSerial.println("Usage: zi_angles "); + return; + } + send_valve_angles(ox_angle_deg, fu_angle_deg); + print_status(); +} + +// Steps both outputs through known counts, holding each long enough to read a multimeter +// at the Zucrow connector. The readings should land on 2 mV per count. +void calibration_sweep() { + const uint16_t counts[] = {0, 1024, 2048, 3072, DAC_MAX_COUNT}; + for (uint16_t count : counts) { + last_count_ox = count; + last_count_fu = count; + write_dac(MCP4822_CHANNEL_A, count); + write_dac(MCP4822_CHANNEL_B, count); + CommsSerial.printf("%4u counts, expect %.3f V\n", count, count * VOLTS_PER_COUNT); + delay(5000); + } + send_valve_angles(0.0, 0.0); + CommsSerial.println("Sweep done, outputs zeroed."); +} + +bool begin() { + // Deselect the DAC first. SPI1 is shared with PT and TC boards, and until this runs the CS pin + // floats, which could let the DAC clock in their traffic. + digitalWrite(PIN_ZUCROW_BOARD_CS, HIGH); + pinMode(PIN_ZUCROW_BOARD_CS, OUTPUT); + + pinMode(PIN_ZUCROW_FAULT_IN, INPUT); + pinMode(PIN_ZUCROW_SYNC_IN, INPUT); + + // Boot reporting a fault, and only report ok once we are actually ready. A controller that + // resets or hangs during boot then looks like a fault to Zucrow. Same as TADPOLE. + digitalWrite(PIN_EC_FAULT_OUT, EC_FAULT_LEVEL); + pinMode(PIN_EC_FAULT_OUT, OUTPUT); + digitalWrite(PIN_EC_SYNC_OUT, EC_IDLE_LEVEL); + pinMode(PIN_EC_SYNC_OUT, OUTPUT); + + // No DAC write here. The MCP4822 powers up with both outputs shut down, which already reads as + // 0 V at the connector, and the first write to each channel enables it. Skipping the write means + // begin() puts no traffic on SPI1 while other devices' CS pins may still be floating. + + /* clang-format off */ + CommandRouter::add(print_status, "zi_status", "Print Zucrow line states and analog outputs."); + CommandRouter::add(send_fault, "zi_fault", "Assert the fault line to Zucrow."); + CommandRouter::add(send_ok, "zi_ok", "Clear the fault line to Zucrow."); + CommandRouter::add([]() { send_sync(true); }, "zi_run", "Set the sync line to Zucrow to running."); + CommandRouter::add([]() { send_sync(false); }, "zi_idle", "Set the sync line to Zucrow to idle."); + CommandRouter::add(send_angles_cmd, "zi_angles", "Send valve angles to Zucrow. Args: "); + CommandRouter::add(calibration_sweep, "zi_sweep", "Step the analog outputs through known counts for calibration."); + /* clang-format on */ + + // There is no readback path on this board, so nothing here can fail. The calibration sweep + // is what proves the analog chain works. + return true; +} + +} // namespace ZucrowInterface diff --git a/firmware/lib/zucrow_interface/ZucrowInterface.h b/firmware/lib/zucrow_interface/ZucrowInterface.h new file mode 100644 index 0000000..a22771b --- /dev/null +++ b/firmware/lib/zucrow_interface/ZucrowInterface.h @@ -0,0 +1,26 @@ +#pragma once + +// Interface to the Zucrow test facility through the Zucrow board. +// Fault and sync lines run in both directions, and two analog outputs report +// valve angles to Zucrow's DAQ. + +namespace ZucrowInterface { + +bool begin(); + +// Inputs from Zucrow. Return true when the condition is asserted, so callers +// never deal with pin polarity. +bool check_fault(); +bool check_sync(); + +// Outputs to Zucrow. +void send_fault(); +void send_ok(); +void send_sync(bool running); + +// Valve angles in degrees. Values outside [0, FULL_SCALE_ANGLE_DEG] are clamped. +void send_valve_angles(float ox_angle_deg, float fu_angle_deg); + +void print_status(); + +} // namespace ZucrowInterface diff --git a/firmware/src/ec_main.cpp b/firmware/src/ec_main.cpp index c45a242..3eaecdf 100644 --- a/firmware/src/ec_main.cpp +++ b/firmware/src/ec_main.cpp @@ -10,6 +10,7 @@ #include "TemperatureSensors.h" #include "ThrottleValves.h" #include "ValveController.h" +#include "ZucrowInterface.h" #include "fdcan_toad.h" // shared interfaces @@ -53,6 +54,8 @@ void setup() { bool all_modules_ok = true; all_modules_ok &= CAN::init(); + // Zucrow before PT and TC: its begin() deselects the Zucrow DAC before those boards talk on the shared SPI1 bus. + all_modules_ok &= ZucrowInterface::begin(); all_modules_ok &= PressureSensors::begin(); all_modules_ok &= TemperatureSensors::begin(); all_modules_ok &= ThrottleValves::begin(); @@ -77,6 +80,9 @@ void loop() { while (CommsSerial.available()) { CommandRouter::receive_byte(CommsSerial.read()); } + + // TODO - send measured valve angles to Zucrow here with ZucrowInterface::send_valve_angles(), so Zucrow + // always has an angle readout. Needs ThrottleValves to expose the encoder angles. } // TODO - these? From 16ca35ad88d4176d90dc701d524a1371a6c0c86d Mon Sep 17 00:00:00 2001 From: wadhwat Date: Tue, 29 Sep 2026 21:01:47 -0400 Subject: [PATCH 2/7] Fix Zucrow output init order so pins boot at their intended levels stm32duino only enables a GPIO port's clock inside pinMode(), so digitalWrite() before pinMode() is dropped and the pin comes up LOW. That made the fault line to Zucrow boot as OK instead of FAULT, and left the DAC selected. --- firmware/lib/zucrow_interface/ZucrowInterface.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/firmware/lib/zucrow_interface/ZucrowInterface.cpp b/firmware/lib/zucrow_interface/ZucrowInterface.cpp index d3644ab..fbe2b4c 100644 --- a/firmware/lib/zucrow_interface/ZucrowInterface.cpp +++ b/firmware/lib/zucrow_interface/ZucrowInterface.cpp @@ -141,18 +141,20 @@ void calibration_sweep() { bool begin() { // Deselect the DAC first. SPI1 is shared with PT and TC boards, and until this runs the CS pin // floats, which could let the DAC clock in their traffic. - digitalWrite(PIN_ZUCROW_BOARD_CS, HIGH); + // pinMode must come before digitalWrite on every output here: stm32duino only enables the GPIO + // port clock inside pinMode, so an earlier write is silently dropped and the pin comes up LOW. pinMode(PIN_ZUCROW_BOARD_CS, OUTPUT); + digitalWrite(PIN_ZUCROW_BOARD_CS, HIGH); pinMode(PIN_ZUCROW_FAULT_IN, INPUT); pinMode(PIN_ZUCROW_SYNC_IN, INPUT); // Boot reporting a fault, and only report ok once we are actually ready. A controller that // resets or hangs during boot then looks like a fault to Zucrow. Same as TADPOLE. - digitalWrite(PIN_EC_FAULT_OUT, EC_FAULT_LEVEL); pinMode(PIN_EC_FAULT_OUT, OUTPUT); - digitalWrite(PIN_EC_SYNC_OUT, EC_IDLE_LEVEL); + digitalWrite(PIN_EC_FAULT_OUT, EC_FAULT_LEVEL); pinMode(PIN_EC_SYNC_OUT, OUTPUT); + digitalWrite(PIN_EC_SYNC_OUT, EC_IDLE_LEVEL); // No DAC write here. The MCP4822 powers up with both outputs shut down, which already reads as // 0 V at the connector, and the first write to each channel enables it. Skipping the write means From 119fd5b3f2a630b44a1068d80d2fca48feca12e5 Mon Sep 17 00:00:00 2001 From: wadhwat Date: Tue, 29 Sep 2026 21:05:38 -0400 Subject: [PATCH 3/7] Tidy Zucrow interface: expose full-scale angle in header, use float literals --- firmware/lib/zucrow_interface/ZucrowInterface.cpp | 5 +---- firmware/lib/zucrow_interface/ZucrowInterface.h | 3 +++ 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/firmware/lib/zucrow_interface/ZucrowInterface.cpp b/firmware/lib/zucrow_interface/ZucrowInterface.cpp index fbe2b4c..6549b32 100644 --- a/firmware/lib/zucrow_interface/ZucrowInterface.cpp +++ b/firmware/lib/zucrow_interface/ZucrowInterface.cpp @@ -12,9 +12,6 @@ #define DAC_MAX_COUNT (DAC_COUNTS - 1) #define VOLTS_PER_COUNT (2.048f * 4.0f / DAC_COUNTS) -// Valve angle that maps to full scale. Zucrow configures their scaling to match. -#define FULL_SCALE_ANGLE_DEG 90.0f - // MCP4822 write command is one 16 bit word [MCP4822 datasheet, write command register]: // bit 15 channel (0 = A, 1 = B), bit 13 gain (1 = 1x), bit 12 output (1 = active), bits 11:0 data // LDAC is tied to GND_ISO on this board, so each write updates the output immediately. @@ -134,7 +131,7 @@ void calibration_sweep() { CommsSerial.printf("%4u counts, expect %.3f V\n", count, count * VOLTS_PER_COUNT); delay(5000); } - send_valve_angles(0.0, 0.0); + send_valve_angles(0.0f, 0.0f); CommsSerial.println("Sweep done, outputs zeroed."); } diff --git a/firmware/lib/zucrow_interface/ZucrowInterface.h b/firmware/lib/zucrow_interface/ZucrowInterface.h index a22771b..ca2ae32 100644 --- a/firmware/lib/zucrow_interface/ZucrowInterface.h +++ b/firmware/lib/zucrow_interface/ZucrowInterface.h @@ -4,6 +4,9 @@ // Fault and sync lines run in both directions, and two analog outputs report // valve angles to Zucrow's DAQ. +// Valve angle that maps to full scale on the analog outputs. Zucrow configures their scaling to match. +#define FULL_SCALE_ANGLE_DEG 90.0f + namespace ZucrowInterface { bool begin(); From e631e6dd42e56705a2cb5e985ee5198e3f7b10c0 Mon Sep 17 00:00:00 2001 From: wadhwat Date: Sat, 3 Oct 2026 11:51:53 -0400 Subject: [PATCH 4/7] cleaned up superfluous comments and made bit numbers clearer --- .../lib/zucrow_interface/ZucrowInterface.cpp | 20 +++++++------------ 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/firmware/lib/zucrow_interface/ZucrowInterface.cpp b/firmware/lib/zucrow_interface/ZucrowInterface.cpp index 6549b32..b2a1d33 100644 --- a/firmware/lib/zucrow_interface/ZucrowInterface.cpp +++ b/firmware/lib/zucrow_interface/ZucrowInterface.cpp @@ -3,22 +3,16 @@ #include "CommsSerial.h" #include "ec_pins.h" -// Analog chain on the Zucrow board: -// MCP4822 12 bit DAC, internal 2.048 V reference, 1x gain -// -> TLV9142 non-inverting stage, gain = 1 + 15k / 5k = 4 -// -> AO_1 (ox) and AO_2 (fu) at the Zucrow connector -// Full scale is 2.048 V * 4 = 8.192 V, which is exactly 2 mV per count. -#define DAC_COUNTS 4096 +#define DAC_COUNTS (1 << 12) // 12 bit DAC, 4096 counts #define DAC_MAX_COUNT (DAC_COUNTS - 1) #define VOLTS_PER_COUNT (2.048f * 4.0f / DAC_COUNTS) -// MCP4822 write command is one 16 bit word [MCP4822 datasheet, write command register]: -// bit 15 channel (0 = A, 1 = B), bit 13 gain (1 = 1x), bit 12 output (1 = active), bits 11:0 data -// LDAC is tied to GND_ISO on this board, so each write updates the output immediately. -#define MCP4822_CHANNEL_A 0x0000 -#define MCP4822_CHANNEL_B 0x8000 -#define MCP4822_GAIN_1X 0x2000 -#define MCP4822_ACTIVE 0x1000 +// MCP4822 write command: 16 bit word: +// bit 15 channel (0 = A, 1 = B), bit 13 gain (1 = 1x), bit 12 output (1 = active), bits 11:0 data +#define MCP4822_CHANNEL_A (0 << 15) +#define MCP4822_CHANNEL_B (1 << 15) +#define MCP4822_GAIN_1X (1 << 13) +#define MCP4822_ACTIVE (1 << 12) // TODO - verify clock integrity with a scope. The ISO6441 isolator is rated to 150 Mbps, so the // limit here is the long traces to the Zucrow board. Matches the other devices on this bus. From e6a299d59fb2f952fbf145cfc74630e192712158 Mon Sep 17 00:00:00 2001 From: wadhwat Date: Sat, 3 Oct 2026 12:17:13 -0400 Subject: [PATCH 5/7] Added check fault enum to be clearer than just booleans --- firmware/lib/zucrow_interface/ZucrowInterface.cpp | 11 +++++------ firmware/lib/zucrow_interface/ZucrowInterface.h | 11 +++++++---- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/firmware/lib/zucrow_interface/ZucrowInterface.cpp b/firmware/lib/zucrow_interface/ZucrowInterface.cpp index b2a1d33..5a3a09a 100644 --- a/firmware/lib/zucrow_interface/ZucrowInterface.cpp +++ b/firmware/lib/zucrow_interface/ZucrowInterface.cpp @@ -14,11 +14,10 @@ #define MCP4822_GAIN_1X (1 << 13) #define MCP4822_ACTIVE (1 << 12) -// TODO - verify clock integrity with a scope. The ISO6441 isolator is rated to 150 Mbps, so the -// limit here is the long traces to the Zucrow board. Matches the other devices on this bus. +// TODO: verify clock integrity with a scope; The ISO6441 isolator is rated to 150 Mbps, +// and long traces to the zucrow board may cause issues. SPISettings ZUCROW_DAC_SPI_SETTINGS(4000000, MSBFIRST, SPI_MODE0); -// Line assignments. DI3 and DI4 are reserved for the Skipper autosequences. // TODO - confirm DI1/DI2 and DO1/DO2 against the schematic net names. #define PIN_ZUCROW_FAULT_IN PIN_ZUCROW_BOARD_DI1 #define PIN_ZUCROW_SYNC_IN PIN_ZUCROW_BOARD_DI2 @@ -72,8 +71,8 @@ void send_valve_angles(float ox_angle_deg, float fu_angle_deg) { write_dac(MCP4822_CHANNEL_B, last_count_fu); } -bool check_fault() { - return digitalRead(PIN_ZUCROW_FAULT_IN) == ZUCROW_FAULT_LEVEL; +fault_state_t check_fault() { + return digitalRead(PIN_ZUCROW_FAULT_IN) == ZUCROW_FAULT_LEVEL ? FAULT_ASSERTED : FAULT_CLEAR; } bool check_sync() { @@ -93,7 +92,7 @@ void send_sync(bool running) { } void print_status() { - CommsSerial.printf("From Zucrow: fault %s, sync %s\n", check_fault() ? "ASSERTED" : "clear", + CommsSerial.printf("From Zucrow: fault %s, sync %s\n", check_fault() == FAULT_ASSERTED ? "ASSERTED" : "clear", check_sync() ? "running" : "idle"); CommsSerial.printf("To Zucrow: fault %s, sync %s\n", digitalRead(PIN_EC_FAULT_OUT) == EC_FAULT_LEVEL ? "ASSERTED" : "clear", diff --git a/firmware/lib/zucrow_interface/ZucrowInterface.h b/firmware/lib/zucrow_interface/ZucrowInterface.h index ca2ae32..bb7f146 100644 --- a/firmware/lib/zucrow_interface/ZucrowInterface.h +++ b/firmware/lib/zucrow_interface/ZucrowInterface.h @@ -11,10 +11,13 @@ namespace ZucrowInterface { bool begin(); -// Inputs from Zucrow. Return true when the condition is asserted, so callers -// never deal with pin polarity. -bool check_fault(); -bool check_sync(); +// State of the fault line from Zucrow. FAULT_ASSERTED comes first so a zero-initialized value reads +// as a fault: an unknown state should safe the system, the same as a disconnected line. +enum fault_state_t { FAULT_ASSERTED, FAULT_CLEAR }; + +// Inputs from Zucrow. These hide pin polarity from callers. +fault_state_t check_fault(); +bool check_sync(); // true when Zucrow reports running // Outputs to Zucrow. void send_fault(); From 322974e8cf51b5dab8b0d11e5dff8b3f5100a4b8 Mon Sep 17 00:00:00 2001 From: wadhwat Date: Sat, 3 Oct 2026 15:22:52 -0400 Subject: [PATCH 6/7] added DI3 and DI4 checking functions --- firmware/lib/zucrow_interface/ZucrowInterface.cpp | 14 ++++++++++++++ firmware/lib/zucrow_interface/ZucrowInterface.h | 5 ++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/firmware/lib/zucrow_interface/ZucrowInterface.cpp b/firmware/lib/zucrow_interface/ZucrowInterface.cpp index 5a3a09a..5af6e5b 100644 --- a/firmware/lib/zucrow_interface/ZucrowInterface.cpp +++ b/firmware/lib/zucrow_interface/ZucrowInterface.cpp @@ -21,6 +21,8 @@ SPISettings ZUCROW_DAC_SPI_SETTINGS(4000000, MSBFIRST, SPI_MODE0); // TODO - confirm DI1/DI2 and DO1/DO2 against the schematic net names. #define PIN_ZUCROW_FAULT_IN PIN_ZUCROW_BOARD_DI1 #define PIN_ZUCROW_SYNC_IN PIN_ZUCROW_BOARD_DI2 +#define PIN_ZUCROW_DI3 PIN_ZUCROW_BOARD_DI3 +#define PIN_ZUCROW_DI4 PIN_ZUCROW_BOARD_DI4 #define PIN_EC_FAULT_OUT PIN_ZUCROW_BOARD_DO1 #define PIN_EC_SYNC_OUT PIN_ZUCROW_BOARD_DO2 @@ -79,6 +81,14 @@ bool check_sync() { return digitalRead(PIN_ZUCROW_SYNC_IN) == ZUCROW_RUNNING_LEVEL; } +PinStatus check_di3() { + return digitalRead(PIN_ZUCROW_BOARD_DI3); +} + +PinStatus check_di4() { + return digitalRead(PIN_ZUCROW_BOARD_DI4); +} + void send_fault() { digitalWrite(PIN_EC_FAULT_OUT, EC_FAULT_LEVEL); } @@ -97,6 +107,8 @@ void print_status() { CommsSerial.printf("To Zucrow: fault %s, sync %s\n", digitalRead(PIN_EC_FAULT_OUT) == EC_FAULT_LEVEL ? "ASSERTED" : "clear", digitalRead(PIN_EC_SYNC_OUT) == EC_RUNNING_LEVEL ? "running" : "idle"); + CommsSerial.printf("Spare lines from Zucrow: DI3: %s, DI4: %s\n", check_di3() == HIGH ? "high" : "low", + check_di4() == HIGH ? "high" : "low"); CommsSerial.printf("OX: %4u counts, %.3f V\n", last_count_ox, last_count_ox * VOLTS_PER_COUNT); CommsSerial.printf("FU: %4u counts, %.3f V\n", last_count_fu, last_count_fu * VOLTS_PER_COUNT); } @@ -138,6 +150,8 @@ bool begin() { pinMode(PIN_ZUCROW_FAULT_IN, INPUT); pinMode(PIN_ZUCROW_SYNC_IN, INPUT); + pinMode(PIN_ZUCROW_DI3, INPUT); + pinMode(PIN_ZUCROW_DI4, INPUT); // Boot reporting a fault, and only report ok once we are actually ready. A controller that // resets or hangs during boot then looks like a fault to Zucrow. Same as TADPOLE. diff --git a/firmware/lib/zucrow_interface/ZucrowInterface.h b/firmware/lib/zucrow_interface/ZucrowInterface.h index bb7f146..231cab0 100644 --- a/firmware/lib/zucrow_interface/ZucrowInterface.h +++ b/firmware/lib/zucrow_interface/ZucrowInterface.h @@ -1,4 +1,5 @@ #pragma once +#include // Interface to the Zucrow test facility through the Zucrow board. // Fault and sync lines run in both directions, and two analog outputs report @@ -18,7 +19,9 @@ enum fault_state_t { FAULT_ASSERTED, FAULT_CLEAR }; // Inputs from Zucrow. These hide pin polarity from callers. fault_state_t check_fault(); bool check_sync(); // true when Zucrow reports running - +// TODO: Rename when di3 and di4 have defined purposes +PinStatus check_di3(); +PinStatus check_di4(); // Outputs to Zucrow. void send_fault(); void send_ok(); From 9c049ed9e1e39c8117b11638ba59fa3ea48af0f1 Mon Sep 17 00:00:00 2001 From: wadhwat Date: Sat, 3 Oct 2026 15:29:06 -0400 Subject: [PATCH 7/7] changed pin mode/digital write order to write before setting pin mode = output --- firmware/lib/zucrow_interface/ZucrowInterface.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/firmware/lib/zucrow_interface/ZucrowInterface.cpp b/firmware/lib/zucrow_interface/ZucrowInterface.cpp index 5af6e5b..96ab841 100644 --- a/firmware/lib/zucrow_interface/ZucrowInterface.cpp +++ b/firmware/lib/zucrow_interface/ZucrowInterface.cpp @@ -143,10 +143,8 @@ void calibration_sweep() { bool begin() { // Deselect the DAC first. SPI1 is shared with PT and TC boards, and until this runs the CS pin // floats, which could let the DAC clock in their traffic. - // pinMode must come before digitalWrite on every output here: stm32duino only enables the GPIO - // port clock inside pinMode, so an earlier write is silently dropped and the pin comes up LOW. - pinMode(PIN_ZUCROW_BOARD_CS, OUTPUT); digitalWrite(PIN_ZUCROW_BOARD_CS, HIGH); + pinMode(PIN_ZUCROW_BOARD_CS, OUTPUT); pinMode(PIN_ZUCROW_FAULT_IN, INPUT); pinMode(PIN_ZUCROW_SYNC_IN, INPUT); @@ -155,10 +153,10 @@ bool begin() { // Boot reporting a fault, and only report ok once we are actually ready. A controller that // resets or hangs during boot then looks like a fault to Zucrow. Same as TADPOLE. - pinMode(PIN_EC_FAULT_OUT, OUTPUT); digitalWrite(PIN_EC_FAULT_OUT, EC_FAULT_LEVEL); - pinMode(PIN_EC_SYNC_OUT, OUTPUT); + pinMode(PIN_EC_FAULT_OUT, OUTPUT); digitalWrite(PIN_EC_SYNC_OUT, EC_IDLE_LEVEL); + pinMode(PIN_EC_SYNC_OUT, OUTPUT); // No DAC write here. The MCP4822 powers up with both outputs shut down, which already reads as // 0 V at the connector, and the first write to each channel enables it. Skipping the write means