From 917eb6693328aa79240d23b6044f3ff7858e9509 Mon Sep 17 00:00:00 2001 From: jacobh460 Date: Sun, 16 Aug 2026 17:24:52 -0700 Subject: [PATCH 1/8] - create stm32CubeMX .ioc file (stm32CubeMX is used to generate clock, peripheral initialization, etc using hal) - we can frankenstein the firmware to use pieces of the generated code from stm32cubemx - add generated stm32cubemx files to gitignore, since they can be regenerated using just the .ioc file and stm32CubeMX --- ..gitignore.swp | Bin 0 -> 1024 bytes UI/imgui | 1 - UI/implot | 1 - UI/implot3d | 1 - 4 files changed, 3 deletions(-) create mode 100644 ..gitignore.swp delete mode 160000 UI/imgui delete mode 160000 UI/implot delete mode 160000 UI/implot3d diff --git a/..gitignore.swp b/..gitignore.swp new file mode 100644 index 0000000000000000000000000000000000000000..15fc651df2b9258ae901d3a464a5d53b5d1540d8 GIT binary patch literal 1024 zcmYc?$V<%2SFq4CVL$*kuu-dg+-Zndy1?MX3m} RQPyY(jD`T+LLd~~CIC@14QT)X literal 0 HcmV?d00001 diff --git a/UI/imgui b/UI/imgui deleted file mode 160000 index 83f6686..0000000 --- a/UI/imgui +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 83f668625ad45364de71d385aeb6a5dd04bee02e diff --git a/UI/implot b/UI/implot deleted file mode 160000 index 1351ab2..0000000 --- a/UI/implot +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 1351ab2c46d7a05a60f3533047bfba8a953520a1 diff --git a/UI/implot3d b/UI/implot3d deleted file mode 160000 index 3a3ae28..0000000 --- a/UI/implot3d +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 3a3ae280d21cb6dbd1be2ef2daf825d6eb78d7d9 From fcf389188a25be28a84c919f62466b9a3517b202 Mon Sep 17 00:00:00 2001 From: Anand Krishnan Date: Tue, 22 Sep 2026 23:27:53 -0400 Subject: [PATCH 2/8] Control for linear actuator --- UI/imgui | 1 + UI/implot | 1 + UI/implot3d | 1 + firmware/lib/tvc_actuators/TVC_Actuators.cpp | 68 +++- firmware/lib/tvc_actuators/TVC_Actuators.h | 11 +- .../lib/tvc_actuators/UltramotionActuator.cpp | 328 ++++++++++++++++++ .../lib/tvc_actuators/UltramotionActuator.hpp | 59 ++++ 7 files changed, 462 insertions(+), 7 deletions(-) create mode 160000 UI/imgui create mode 160000 UI/implot create mode 160000 UI/implot3d create mode 100644 firmware/lib/tvc_actuators/UltramotionActuator.cpp create mode 100644 firmware/lib/tvc_actuators/UltramotionActuator.hpp diff --git a/UI/imgui b/UI/imgui new file mode 160000 index 0000000..83f6686 --- /dev/null +++ b/UI/imgui @@ -0,0 +1 @@ +Subproject commit 83f668625ad45364de71d385aeb6a5dd04bee02e diff --git a/UI/implot b/UI/implot new file mode 160000 index 0000000..1351ab2 --- /dev/null +++ b/UI/implot @@ -0,0 +1 @@ +Subproject commit 1351ab2c46d7a05a60f3533047bfba8a953520a1 diff --git a/UI/implot3d b/UI/implot3d new file mode 160000 index 0000000..3a3ae28 --- /dev/null +++ b/UI/implot3d @@ -0,0 +1 @@ +Subproject commit 3a3ae280d21cb6dbd1be2ef2daf825d6eb78d7d9 diff --git a/firmware/lib/tvc_actuators/TVC_Actuators.cpp b/firmware/lib/tvc_actuators/TVC_Actuators.cpp index 48f0c3c..a536027 100644 --- a/firmware/lib/tvc_actuators/TVC_Actuators.cpp +++ b/firmware/lib/tvc_actuators/TVC_Actuators.cpp @@ -1,17 +1,75 @@ #include "TVC_Actuators.h" #include "GimbalKinematics.h" +#include "UltramotionActuator.hpp" +#include "ec_pins.h" +#include "fdcan_toad.h" +#include "toad_can_bus.h" + +// One CAN object here, not two - CAN_ID_TVC_PITCH and CAN_ID_TVC_YAW are two +// message IDs on the SAME physical "Actuators CAN Bus" (PIN_CAN_TVC_RX/TX), +// not two separate buses. Note the constructor takes (tx_pin, rx_pin), in +// that order - matches CAN::CAN(uint32_t _tx_pin, uint32_t _rx_pin) in +// fdcan_toad.cpp. namespace TVC_Actuators { +CAN actuators_can(PIN_CAN_TVC_TX, PIN_CAN_TVC_RX); + +// TODO - PLACEHOLDER. 500 kbit/s is a common CAN default, not a confirmed +// value - needs to match whatever the Ultramotion actuators are configured +constexpr uint32_t ACTUATORS_CAN_BIT_RATE = 500000; + +// TODO - PLACEHOLDER scaling. Assumes a straight linear map from physical +// actuator length (mm) to the actuator's raw target_pos range (0-65535). +// Needs the actuator's real stroke length and pMin/pMax from its +// datasheet/CONFIG.TXT before this is trustworthy. +uint16_t length_to_target_pos(float length_mm) { + constexpr float MIN_LENGTH_MM = 0.0f; // TODO + constexpr float MAX_LENGTH_MM = 100.0f; // TODO + constexpr uint16_t POS_MAX = 65535; + + float clamped = constrain(length_mm, MIN_LENGTH_MM, MAX_LENGTH_MM); + return (uint16_t)((clamped - MIN_LENGTH_MM) / (MAX_LENGTH_MM - MIN_LENGTH_MM) * POS_MAX); +} + bool begin() { - return true; + float actual_rate = actuators_can.begin(ACTUATORS_CAN_BIT_RATE); + reset_status_state(); // from UltramotionActuator.hpp - clears "what's new" status tracking + return actual_rate > 0.0f; } -// TODO - this function void set_angles_pitch_yaw(float pitch, float yaw) { - float pitch_len; - float yaw_len; - calc_actuator_lengths(pitch, yaw, &pitch_len, &yaw_len); + float pitch_len, yaw_len; + calc_actuator_lengths(pitch, yaw, &pitch_len, &yaw_len); // already implemented + + send_target_pos(actuators_can, CAN_ID_TVC_PITCH, length_to_target_pos(pitch_len)); + send_target_pos(actuators_can, CAN_ID_TVC_YAW, length_to_target_pos(yaw_len)); +} + +// Call every flight_loop() iteration - nothing currently does. Drains +// whatever arrived in the RX FIFO since the last call and decodes anything +// addressed to the TVC actuators. +void poll() { + while (actuators_can.available() > 0) { + arduino::CanMsg msg = actuators_can.read(); + uint32_t id = msg.isStandardId() ? msg.getStandardId() : msg.getExtendedId(); + + if (id == CAN_ID_TVC_PITCH || id == CAN_ID_TVC_YAW) { + // TODO - confirm this 6-byte [status_word][position] decode_str + // against the actuator's actual configured telemetry layout - still + // an open team decision, not a confirmed spec. + char decode_str[] = {'A', 'B', 'C', 'D', 'G', 'H'}; + telem frame; + parse_CAN_frame(msg.data, msg.data_length, decode_str, sizeof(decode_str), &frame); + + // TODO - act on frame.status_word here, e.g.: + // constexpr uint32_t FOLLOWING_ERROR_BIT = 1u << 11; + // if (frame.status_word & FOLLOWING_ERROR_BIT) { kill_flag = true; } + (void)frame; + } + // CAN_ID_STEPPER_OX / CAN_ID_STEPPER_FU frames also arrive on this same + // bus - dispatch to ThrottleValves here too once it reads feedback this way. + } } } // namespace TVC_Actuators \ No newline at end of file diff --git a/firmware/lib/tvc_actuators/TVC_Actuators.h b/firmware/lib/tvc_actuators/TVC_Actuators.h index 658515c..1451f25 100644 --- a/firmware/lib/tvc_actuators/TVC_Actuators.h +++ b/firmware/lib/tvc_actuators/TVC_Actuators.h @@ -1,9 +1,16 @@ #pragma once +// Destination in repo: firmware/lib/tvc_actuators/TVC_Actuators.h +// (adds poll() - everything else unchanged from the existing file) + namespace TVC_Actuators { bool begin(); - void set_angles_pitch_yaw(float pitch, float yaw); -} \ No newline at end of file +// Drains and processes any actuator feedback waiting on the Actuators CAN +// bus. Must be called from flight_loop() (or loop()) for feedback to ever be +// seen - nothing does this automatically. +void poll(); + +} // namespace TVC_Actuators \ No newline at end of file diff --git a/firmware/lib/tvc_actuators/UltramotionActuator.cpp b/firmware/lib/tvc_actuators/UltramotionActuator.cpp new file mode 100644 index 0000000..cfb41e8 --- /dev/null +++ b/firmware/lib/tvc_actuators/UltramotionActuator.cpp @@ -0,0 +1,328 @@ +#include "UltramotionActuator.hpp" +#include "fdcan_toad.h" + +// Changes from the previous version: sends now go through the real fdcan_toad +// build an arduino::CanMsg and call bus.write() directly. Everything else +// (status_codes[], print_new_status_codes(), parse_CAN_byte(), +// print_can_data()) is unchanged. + +using namespace arduino; + +const char *status_codes[STATUS_CODE_COUNT] = { + "Position at or beyond retracted physical stop rPos", // 0 - 8 + "Position at or beyond extended physical stop ePos", + "Position beyond retracted software limit spMin", + "Position beyond extended software limit spMax", + "Supply voltage low, motor in COAST (<6.75 VDC, 1 V hysteresis)", + "Supply voltage high, motor in dynamic brake (>44.0 VDC, 2 V hysteresis)", + "Torque output greater than ovTorq limit", + "Torque command at maxTorq limit", + "Speed below \xe2\x80\x9cstop\xe2\x80\x9d threshold", // 9 - 16 + "Direction is extend", + "Position at target (position near target within posWin for posTime)", + "Following error (position error larger than fErrWin for time period fErrTime)", + "Command RX error (message not received in [rxTO * 800 \xc2\xb5s])", + "Telemetry TX error (message not sent over full telemetry interval)", + "CAN position command input capped at low limit pMin", + "CAN position command input capped at upper limit pMax", + "Trajectory move active ", // 17 - 24 + "Heating active", + "Temperature at PCB greater than ovTemp value", + "Temperature at PCB less than unTemp value", + "Relative humidity at PCB greater than ovHumi value", + "Fatal error in CONFIG.TXT or HARDWARE.TXT", + "Fault output bit of DRV8323RS (Bridge Driver)", + "Erroneous warm reset of the CPU has occurred", + "opMode (CLI = 0, CAN = 1)", // 25 - 32 + "Interpolation enabled ", + "Heating enabled ", + "CAN bus module in passive mode ", + "USB connected", + "Opto input 1", + "Opto input 2", + "Opto input 3"}; + +uint32_t current_status = 0; // limitation - don't mix latched status with normal status for the same variable +uint32_t current_status_latched_high = 0; +uint32_t current_status_latched_low = 0; + +void reset_status_state() { + current_status = 0; + current_status_latched_high = 0; + current_status_latched_low = 0; +} + +void print_new_status_codes(uint32_t new_status, uint32_t old_status) { + uint32_t status_dif = new_status ^ old_status; + + Serial.println(" New Status Messages: "); + uint32_t current_status_shift = new_status; + uint32_t status_dif_shift = status_dif; + for (int i = 0; i < STATUS_CODE_COUNT; i++) { + if (status_dif_shift & 0x1 && current_status_shift & 0x1) { // select bits, check if message is NEW and ACTIVE + Serial.print(" "); + Serial.println(status_codes[i]); + }; + status_dif_shift = status_dif_shift >> 1; + current_status_shift = current_status_shift >> 1; + } + Serial.println(" END"); + + Serial.println("\n Cleared Status: "); + current_status_shift = new_status; + status_dif_shift = status_dif; + for (int i = 0; i < STATUS_CODE_COUNT; i++) { + if (status_dif_shift & 0x1 && !(current_status_shift & 0x1)) { // select bits, check if message is NEW and INACTIVE + Serial.print(" "); + Serial.println(status_codes[i]); + }; + status_dif_shift = status_dif_shift >> 1; + current_status_shift = current_status_shift >> 1; + } + Serial.println(" END"); +} + +void parse_CAN_byte(uint8_t can_msg, char decode_str, telem *current_telem_frame) { + uint16_t can_msg_16bit = can_msg; + uint32_t can_msg_32bit = can_msg; + + if (decode_str == 'A') { + current_telem_frame->status_word += can_msg_32bit; + } else if (decode_str == 'B') { + current_telem_frame->status_word += can_msg_32bit << 8; + } else if (decode_str == 'C') { + current_telem_frame->status_word += can_msg_32bit << 16; + } else if (decode_str == 'D') { + current_telem_frame->status_word += can_msg_32bit << 24; + } else if (decode_str == 'E') { + current_telem_frame->avg_motor_current += can_msg_16bit; + } else if (decode_str == 'F') { + current_telem_frame->avg_motor_current += can_msg_16bit << 8; + } else if (decode_str == 'G') { + current_telem_frame->abs_servo_cylinder_pos += can_msg_16bit; + } else if (decode_str == 'H') { + current_telem_frame->abs_servo_cylinder_pos += can_msg_16bit << 8; + } else if (decode_str == 'I') { + current_telem_frame->rel_servo_cylinder_pos += can_msg_16bit; + } else if (decode_str == 'J') { + current_telem_frame->rel_servo_cylinder_pos += can_msg_16bit << 8; + } else if (decode_str == 'K') { + current_telem_frame->latch_high_status_word += can_msg_32bit; + } else if (decode_str == 'L') { + current_telem_frame->latch_high_status_word += can_msg_32bit << 8; + } else if (decode_str == 'M') { + current_telem_frame->latch_high_status_word += can_msg_32bit << 16; + } else if (decode_str == 'N') { + current_telem_frame->latch_high_status_word += can_msg_32bit << 24; + } else if (decode_str == 'O') { + current_telem_frame->latch_low_status_word += can_msg_32bit; + } else if (decode_str == 'P') { + current_telem_frame->latch_low_status_word += can_msg_32bit << 8; + } else if (decode_str == 'Q') { + current_telem_frame->latch_low_status_word += can_msg_32bit << 16; + } else if (decode_str == 'R') { + current_telem_frame->latch_low_status_word += can_msg_32bit << 24; + } else if (decode_str == 'S') { + current_telem_frame->phys_stop_pos = can_msg; + } else if (decode_str == 'T') { + current_telem_frame->motor_current_avg_16 = can_msg; + } else if (decode_str == 'U') { + current_telem_frame->bus_voltage = can_msg; + } else if (decode_str == 'V') { + current_telem_frame->motor_current_avg = can_msg; + } else if (decode_str == 'W') { + current_telem_frame->max_motor_current_8_bit = can_msg; + } else if (decode_str == 'X') { + current_telem_frame->signed_PCB_temp_sensor = can_msg; + } else if (decode_str == 'Y') { + current_telem_frame->unsigned_PCB_temp_sensor = can_msg; + } else if (decode_str == 'Z') { + current_telem_frame->PCB_relative_humidity = can_msg; + } else if (decode_str == 'm') { + current_telem_frame->max_motor_current_16_bit += can_msg_16bit; + } else if (decode_str == 'c') { + current_telem_frame->max_motor_current_16_bit += can_msg_16bit << 8; + } else if (decode_str == 'p') { + current_telem_frame->unitID += can_msg_32bit; + } else if (decode_str == 'q') { + current_telem_frame->unitID += can_msg_32bit << 8; + } else if (decode_str == 'r') { + current_telem_frame->unitID += can_msg_32bit << 16; + } else if (decode_str == 's') { + current_telem_frame->unitID += can_msg_32bit << 24; + } else if (decode_str == 't') { + current_telem_frame->target_pos += can_msg_16bit; + } else if (decode_str == 'u') { + current_telem_frame->target_pos += can_msg_16bit << 8; + } else { + Serial.print("FATAL ERROR - CAN frame contained non-decodable char: "); + Serial.println(decode_str); + } +} + +void print_can_data(char decode_str, telem *current_telem_frame) { + switch (decode_str) { + case 'A': + case 'B': + case 'C': + case 'D': + if (!current_telem_frame->has_printed_status) { + Serial.println("Status: "); + print_new_status_codes(current_telem_frame->status_word, current_status); + current_status = current_telem_frame->status_word; + } + current_telem_frame->has_printed_status = true; + break; + case 'E': // prevent dup + case 'G': + case 'I': + case 'm': + case 't': + break; + case 'F': // and E + Serial.print("Average motor current over telemetry interval: "); + Serial.println(current_telem_frame->avg_motor_current); + break; + case 'H': // and G + Serial.print("Servo Cylinder position, absolute encoder value: "); + Serial.println(current_telem_frame->abs_servo_cylinder_pos); + break; + case 'J': // and I + Serial.print("Position converted to input range (pMin to pMax): "); + Serial.println(current_telem_frame->rel_servo_cylinder_pos); + break; + case 'K': + case 'L': + case 'M': + case 'N': + if (!current_telem_frame->has_printed_high_status) { + Serial.println("Status (Latched High): "); + print_new_status_codes(current_telem_frame->latch_high_status_word, current_status_latched_high); + current_status_latched_high = current_telem_frame->latch_high_status_word; + } + current_telem_frame->has_printed_high_status = true; + break; + case 'O': + case 'P': + case 'Q': + case 'R': + if (!current_telem_frame->has_printed_low_status) { + Serial.println("Status (Latched Low): "); + print_new_status_codes(current_telem_frame->latch_low_status_word, current_status_latched_low); + current_status_latched_low = current_telem_frame->latch_low_status_word; + } + current_telem_frame->has_printed_low_status = true; + break; + case 'S': + Serial.print("8-bit position between physical stops (rPos to ePos): "); + Serial.println(current_telem_frame->phys_stop_pos); + break; + case 'T': + Serial.print("8-bit motor current 16-sample average of last 16 ms: "); + Serial.println(current_telem_frame->motor_current_avg_16); + break; + case 'U': { + Serial.print("8-bit bus voltage 0 VDC to +50 VDC: "); + float voltage = current_telem_frame->bus_voltage; + Serial.print(voltage * 50 / 255); + Serial.println(" VDC"); + break; + } + case 'V': + Serial.print("8-bit average motor current over telemetry interval: "); + Serial.println(current_telem_frame->motor_current_avg); + break; + case 'W': + Serial.print("8-bit max motor current over telemetry interval: "); + Serial.println(current_telem_frame->max_motor_current_8_bit); + break; + case 'X': + Serial.print("8-bit signed integer PCB temp sensor C (-50 to +127): "); + Serial.println(current_telem_frame->signed_PCB_temp_sensor); + break; + case 'Y': { + Serial.print("8-bit unsigned PCB temp sensor: "); + int pcb_temp = current_telem_frame->unsigned_PCB_temp_sensor; + Serial.print(pcb_temp - 50); + Serial.println(" deg C"); + break; + } + case 'Z': + Serial.print("8-bit PCB relative humidity: "); + Serial.print(current_telem_frame->PCB_relative_humidity); + Serial.println("%"); + break; + case 'c': // and m + Serial.print("Max motor current over telemetry interval: "); + Serial.println(current_telem_frame->max_motor_current_16_bit); + break; + case 'p': + case 'q': + case 'r': + case 's': + if (!current_telem_frame->has_printed_ID) { + Serial.print("ID: 0x"); + Serial.println(current_telem_frame->unitID, HEX); + } + current_telem_frame->has_printed_ID = true; + break; + case 'u': // and t + Serial.print("Target position, absolute encoder value: "); + Serial.println(current_telem_frame->target_pos); + break; + default: + Serial.print("FATAL ERROR - CAN frame contained non-decodable char: "); + Serial.println(decode_str); + break; + } +} + +void parse_CAN_frame(const uint8_t can_msg[], uint8_t msg_length, char decode_str[], uint8_t decode_length, + telem *out) { + if (msg_length != decode_length) { + Serial.println("FATAL ERROR - CAN frame and decode code lengths do not match!"); + } + + if (msg_length > 8) { + Serial.println("FATAL ERROR - CAN frame longer than max!"); + } + + telem current_telem_frame; // values are all set to 0 + + for (uint8_t i = 0; i < msg_length; i++) { + parse_CAN_byte(can_msg[i], decode_str[i], ¤t_telem_frame); + } + + Serial.println("CAN FRAME START ++++++++++++++++++++++++++"); + for (uint8_t i = 0; i < msg_length; i++) { + print_can_data(decode_str[i], ¤t_telem_frame); + } + Serial.println("CAN FRAME END ---------------------------\n"); + + if (out != nullptr) { + *out = current_telem_frame; + } +} + +void send_target_pos(CAN &bus, uint16_t id, uint16_t target_pos) { + // Same byte layout as the original prep_CAN_msg(id, target_pos): 2-byte + // frame, little-endian target position. toad_can_bus.h documents TOAD's + // CAN IDs as 11-bit standard, so CanStandardId (not CanExtendedId) is used. + uint8_t data[2] = { + static_cast(target_pos & 0xFF), + static_cast(target_pos >> 8), + }; + CanMsg msg(CanStandardId(id), sizeof(data), data); + bus.write(msg); +} + +void send_target_pos(CAN &bus, uint16_t id, uint16_t target_pos, uint16_t max_torque) { + uint8_t data[4] = { + static_cast(target_pos & 0xFF), + static_cast(target_pos >> 8), + static_cast(max_torque & 0xFF), + static_cast(max_torque >> 8), + }; + CanMsg msg(CanStandardId(id), sizeof(data), data); + bus.write(msg); +} \ No newline at end of file diff --git a/firmware/lib/tvc_actuators/UltramotionActuator.hpp b/firmware/lib/tvc_actuators/UltramotionActuator.hpp new file mode 100644 index 0000000..b0ae3a5 --- /dev/null +++ b/firmware/lib/tvc_actuators/UltramotionActuator.hpp @@ -0,0 +1,59 @@ +#include + +#ifndef UltramotionActuator_H +#define UltramotionActuator_H + +// Destination in repo: firmware/lib/tvc_actuators/UltramotionActuator.hpp +// +// Forward declare rather than #include "fdcan_toad.h" here - a reference is +// all this header needs, and fdcan_toad.h drags in the STM32 HAL headers, +// which callers of this file shouldn't have to pull in just to see these +// prototypes. +class CAN; + +struct telem { + uint32_t status_word = 0; // A-D: Status word byte + uint32_t latch_high_status_word = 0; // K-N: Latched high copy of status word byte + uint32_t latch_low_status_word = 0; // O-R: Latched low copy of status word byte + uint32_t unitID = 0; // p-s: unitID (CAN ID) + + uint16_t avg_motor_current = 0; // E-F: Average motor current over telemetry interval (0 to 32767) + uint16_t abs_servo_cylinder_pos = 0; // G-H: Servo Cylinder position, absolute encoder value (0 to 65535) + uint16_t rel_servo_cylinder_pos = 0; // I-J: Position converted to input range (pMin to pMax) + uint8_t phys_stop_pos = 0; // S: 8-bit position between physical stops (rPos to ePos) + uint8_t motor_current_avg_16 = 0; // T: 8-bit motor current 16-sample average of last 16 ms (0 to 255) + uint8_t bus_voltage = 0; // U: 8-bit bus voltage 0 VDC to +50 VDC (0 to 255) + uint8_t motor_current_avg = 0; // V: 8-bit average motor current over telemetry interval (0 to 255) + uint8_t max_motor_current_8_bit = 0; // W: 8-bit max motor current over telemetry interval (0 to 255) + int8_t signed_PCB_temp_sensor = 0; // X: 8-bit signed integer PCB temp sensor C (-50 to +127) + uint8_t unsigned_PCB_temp_sensor = + 0; // Y: 8-bit unsigned PCB temp sensor in deg_C where 0 = -50deg_C and 200 = +150deg_C (0 to 200) + uint8_t PCB_relative_humidity = 0; // Z: 8-bit PCB relative humidity % (0 to 100) + uint16_t max_motor_current_16_bit = 0; // mc: Max motor current over telemetry interval (0 to 32767) + uint16_t target_pos = 0; // tu: Target position, absolute encoder value + + bool has_printed_status = false; + bool has_printed_high_status = false; + bool has_printed_low_status = false; + bool has_printed_ID = false; +}; + +#define STATUS_CODE_COUNT 32 + +// treat all active messages as new +void reset_status_state(); + +// Decodes a raw CAN frame and prints its contents. +// If `out` is non-null, also copies the decoded telem struct into it so the +// caller (e.g. TVC_Actuators) can act on status/position/fault data instead +// of only logging it. +void parse_CAN_frame(const uint8_t can_msg[], uint8_t msg_length, char decode_str[], uint8_t decode_length, + telem *out = nullptr); + +// Sends a CAN frame commanding the actuator at `id` to target_pos, over `bus` - data fmt '<>' +void send_target_pos(CAN &bus, uint16_t id, uint16_t target_pos); + +// Sends a CAN frame commanding the actuator at `id` to target_pos with a max_torque limit - data fmt '<>()' +void send_target_pos(CAN &bus, uint16_t id, uint16_t target_pos, uint16_t max_torque); + +#endif \ No newline at end of file From 4e5f53d9e29300bd1fa1ab945f5a7900c07d7ae2 Mon Sep 17 00:00:00 2001 From: Anand Krishnan Date: Thu, 24 Sep 2026 10:42:28 -0400 Subject: [PATCH 3/8] Addressed PR comments. Updated CAN parser, changed constant based on data file, made begin() dependent on rx val --- firmware/lib/tvc_actuators/TVC_Actuators.cpp | 30 ++--- firmware/lib/tvc_actuators/TVC_Actuators.h | 3 - .../lib/tvc_actuators/UltramotionActuator.cpp | 108 ++++++++++-------- .../lib/tvc_actuators/UltramotionActuator.hpp | 8 +- 4 files changed, 80 insertions(+), 69 deletions(-) diff --git a/firmware/lib/tvc_actuators/TVC_Actuators.cpp b/firmware/lib/tvc_actuators/TVC_Actuators.cpp index a536027..aff8f47 100644 --- a/firmware/lib/tvc_actuators/TVC_Actuators.cpp +++ b/firmware/lib/tvc_actuators/TVC_Actuators.cpp @@ -15,9 +15,8 @@ namespace TVC_Actuators { CAN actuators_can(PIN_CAN_TVC_TX, PIN_CAN_TVC_RX); -// TODO - PLACEHOLDER. 500 kbit/s is a common CAN default, not a confirmed -// value - needs to match whatever the Ultramotion actuators are configured -constexpr uint32_t ACTUATORS_CAN_BIT_RATE = 500000; +constexpr uint32_t ACTUATORS_CAN_BIT_RATE = 1000000; +bool tvc_debug_mode = false; // TODO - PLACEHOLDER scaling. Assumes a straight linear map from physical // actuator length (mm) to the actuator's raw target_pos range (0-65535). @@ -34,8 +33,9 @@ uint16_t length_to_target_pos(float length_mm) { bool begin() { float actual_rate = actuators_can.begin(ACTUATORS_CAN_BIT_RATE); - reset_status_state(); // from UltramotionActuator.hpp - clears "what's new" status tracking - return actual_rate > 0.0f; + if (actual_rate <= 0.0f) { + return false; + } } void set_angles_pitch_yaw(float pitch, float yaw) { @@ -55,17 +55,17 @@ void poll() { uint32_t id = msg.isStandardId() ? msg.getStandardId() : msg.getExtendedId(); if (id == CAN_ID_TVC_PITCH || id == CAN_ID_TVC_YAW) { - // TODO - confirm this 6-byte [status_word][position] decode_str - // against the actuator's actual configured telemetry layout - still - // an open team decision, not a confirmed spec. - char decode_str[] = {'A', 'B', 'C', 'D', 'G', 'H'}; - telem frame; - parse_CAN_frame(msg.data, msg.data_length, decode_str, sizeof(decode_str), &frame); + // Fixed-format decode for exactly what flight code acts on - no + // letter-code dispatch, no unused fields. + tvc_actuator_telemetry_t telem = parse_tvc_telemetry(msg.data); - // TODO - act on frame.status_word here, e.g.: - // constexpr uint32_t FOLLOWING_ERROR_BIT = 1u << 11; - // if (frame.status_word & FOLLOWING_ERROR_BIT) { kill_flag = true; } - (void)frame; + if (tvc_debug_mode) { + // Full generic decode/print, bench debugging only - never feeds a + // flight decision. Matches "KLMGHEFY", the factory-default txData. + char decode_str[] = {'K', 'L', 'M', 'G', 'H', 'E', 'F', 'Y'}; + struct telem full_frame; + parse_CAN_frame(msg.data, msg.data_length, decode_str, sizeof(decode_str), &full_frame); + } } // CAN_ID_STEPPER_OX / CAN_ID_STEPPER_FU frames also arrive on this same // bus - dispatch to ThrottleValves here too once it reads feedback this way. diff --git a/firmware/lib/tvc_actuators/TVC_Actuators.h b/firmware/lib/tvc_actuators/TVC_Actuators.h index 1451f25..0506d1b 100644 --- a/firmware/lib/tvc_actuators/TVC_Actuators.h +++ b/firmware/lib/tvc_actuators/TVC_Actuators.h @@ -1,8 +1,5 @@ #pragma once -// Destination in repo: firmware/lib/tvc_actuators/TVC_Actuators.h -// (adds poll() - everything else unchanged from the existing file) - namespace TVC_Actuators { bool begin(); diff --git a/firmware/lib/tvc_actuators/UltramotionActuator.cpp b/firmware/lib/tvc_actuators/UltramotionActuator.cpp index cfb41e8..c916e44 100644 --- a/firmware/lib/tvc_actuators/UltramotionActuator.cpp +++ b/firmware/lib/tvc_actuators/UltramotionActuator.cpp @@ -1,4 +1,5 @@ #include "UltramotionActuator.hpp" +#include "CommsSerial.h" #include "fdcan_toad.h" // Changes from the previous version: sends now go through the real fdcan_toad @@ -55,31 +56,31 @@ void reset_status_state() { void print_new_status_codes(uint32_t new_status, uint32_t old_status) { uint32_t status_dif = new_status ^ old_status; - Serial.println(" New Status Messages: "); + CommsSerial.println(" New Status Messages: "); uint32_t current_status_shift = new_status; uint32_t status_dif_shift = status_dif; for (int i = 0; i < STATUS_CODE_COUNT; i++) { if (status_dif_shift & 0x1 && current_status_shift & 0x1) { // select bits, check if message is NEW and ACTIVE - Serial.print(" "); - Serial.println(status_codes[i]); + CommsSerial.print(" "); + CommsSerial.println(status_codes[i]); }; status_dif_shift = status_dif_shift >> 1; current_status_shift = current_status_shift >> 1; } - Serial.println(" END"); + CommsSerial.println(" END"); - Serial.println("\n Cleared Status: "); + CommsSerial.println("\n Cleared Status: "); current_status_shift = new_status; status_dif_shift = status_dif; for (int i = 0; i < STATUS_CODE_COUNT; i++) { if (status_dif_shift & 0x1 && !(current_status_shift & 0x1)) { // select bits, check if message is NEW and INACTIVE - Serial.print(" "); - Serial.println(status_codes[i]); + CommsSerial.print(" "); + CommsSerial.println(status_codes[i]); }; status_dif_shift = status_dif_shift >> 1; current_status_shift = current_status_shift >> 1; } - Serial.println(" END"); + CommsSerial.println(" END"); } void parse_CAN_byte(uint8_t can_msg, char decode_str, telem *current_telem_frame) { @@ -155,8 +156,8 @@ void parse_CAN_byte(uint8_t can_msg, char decode_str, telem *current_telem_frame } else if (decode_str == 'u') { current_telem_frame->target_pos += can_msg_16bit << 8; } else { - Serial.print("FATAL ERROR - CAN frame contained non-decodable char: "); - Serial.println(decode_str); + CommsSerial.print("FATAL ERROR - CAN frame contained non-decodable char: "); + CommsSerial.println(decode_str); } } @@ -167,7 +168,7 @@ void print_can_data(char decode_str, telem *current_telem_frame) { case 'C': case 'D': if (!current_telem_frame->has_printed_status) { - Serial.println("Status: "); + CommsSerial.println("Status: "); print_new_status_codes(current_telem_frame->status_word, current_status); current_status = current_telem_frame->status_word; } @@ -180,23 +181,23 @@ void print_can_data(char decode_str, telem *current_telem_frame) { case 't': break; case 'F': // and E - Serial.print("Average motor current over telemetry interval: "); - Serial.println(current_telem_frame->avg_motor_current); + CommsSerial.print("Average motor current over telemetry interval: "); + CommsSerial.println(current_telem_frame->avg_motor_current); break; case 'H': // and G - Serial.print("Servo Cylinder position, absolute encoder value: "); - Serial.println(current_telem_frame->abs_servo_cylinder_pos); + CommsSerial.print("Servo Cylinder position, absolute encoder value: "); + CommsSerial.println(current_telem_frame->abs_servo_cylinder_pos); break; case 'J': // and I - Serial.print("Position converted to input range (pMin to pMax): "); - Serial.println(current_telem_frame->rel_servo_cylinder_pos); + CommsSerial.print("Position converted to input range (pMin to pMax): "); + CommsSerial.println(current_telem_frame->rel_servo_cylinder_pos); break; case 'K': case 'L': case 'M': case 'N': if (!current_telem_frame->has_printed_high_status) { - Serial.println("Status (Latched High): "); + CommsSerial.println("Status (Latched High): "); print_new_status_codes(current_telem_frame->latch_high_status_word, current_status_latched_high); current_status_latched_high = current_telem_frame->latch_high_status_word; } @@ -207,72 +208,72 @@ void print_can_data(char decode_str, telem *current_telem_frame) { case 'Q': case 'R': if (!current_telem_frame->has_printed_low_status) { - Serial.println("Status (Latched Low): "); + CommsSerial.println("Status (Latched Low): "); print_new_status_codes(current_telem_frame->latch_low_status_word, current_status_latched_low); current_status_latched_low = current_telem_frame->latch_low_status_word; } current_telem_frame->has_printed_low_status = true; break; case 'S': - Serial.print("8-bit position between physical stops (rPos to ePos): "); - Serial.println(current_telem_frame->phys_stop_pos); + CommsSerial.print("8-bit position between physical stops (rPos to ePos): "); + CommsSerial.println(current_telem_frame->phys_stop_pos); break; case 'T': - Serial.print("8-bit motor current 16-sample average of last 16 ms: "); - Serial.println(current_telem_frame->motor_current_avg_16); + CommsSerial.print("8-bit motor current 16-sample average of last 16 ms: "); + CommsSerial.println(current_telem_frame->motor_current_avg_16); break; case 'U': { - Serial.print("8-bit bus voltage 0 VDC to +50 VDC: "); + CommsSerial.print("8-bit bus voltage 0 VDC to +50 VDC: "); float voltage = current_telem_frame->bus_voltage; - Serial.print(voltage * 50 / 255); - Serial.println(" VDC"); + CommsSerial.print(voltage * 50 / 255); + CommsSerial.println(" VDC"); break; } case 'V': - Serial.print("8-bit average motor current over telemetry interval: "); - Serial.println(current_telem_frame->motor_current_avg); + CommsSerial.print("8-bit average motor current over telemetry interval: "); + CommsSerial.println(current_telem_frame->motor_current_avg); break; case 'W': - Serial.print("8-bit max motor current over telemetry interval: "); - Serial.println(current_telem_frame->max_motor_current_8_bit); + CommsSerial.print("8-bit max motor current over telemetry interval: "); + CommsSerial.println(current_telem_frame->max_motor_current_8_bit); break; case 'X': - Serial.print("8-bit signed integer PCB temp sensor C (-50 to +127): "); - Serial.println(current_telem_frame->signed_PCB_temp_sensor); + CommsSerial.print("8-bit signed integer PCB temp sensor C (-50 to +127): "); + CommsSerial.println(current_telem_frame->signed_PCB_temp_sensor); break; case 'Y': { - Serial.print("8-bit unsigned PCB temp sensor: "); + CommsSerial.print("8-bit unsigned PCB temp sensor: "); int pcb_temp = current_telem_frame->unsigned_PCB_temp_sensor; - Serial.print(pcb_temp - 50); - Serial.println(" deg C"); + CommsSerial.print(pcb_temp - 50); + CommsSerial.println(" deg C"); break; } case 'Z': - Serial.print("8-bit PCB relative humidity: "); - Serial.print(current_telem_frame->PCB_relative_humidity); - Serial.println("%"); + CommsSerial.print("8-bit PCB relative humidity: "); + CommsSerial.print(current_telem_frame->PCB_relative_humidity); + CommsSerial.println("%"); break; case 'c': // and m - Serial.print("Max motor current over telemetry interval: "); - Serial.println(current_telem_frame->max_motor_current_16_bit); + CommsSerial.print("Max motor current over telemetry interval: "); + CommsSerial.println(current_telem_frame->max_motor_current_16_bit); break; case 'p': case 'q': case 'r': case 's': if (!current_telem_frame->has_printed_ID) { - Serial.print("ID: 0x"); - Serial.println(current_telem_frame->unitID, HEX); + CommsSerial.print("ID: 0x"); + CommsSerial.println(current_telem_frame->unitID, HEX); } current_telem_frame->has_printed_ID = true; break; case 'u': // and t - Serial.print("Target position, absolute encoder value: "); - Serial.println(current_telem_frame->target_pos); + CommsSerial.print("Target position, absolute encoder value: "); + CommsSerial.println(current_telem_frame->target_pos); break; default: - Serial.print("FATAL ERROR - CAN frame contained non-decodable char: "); - Serial.println(decode_str); + CommsSerial.print("FATAL ERROR - CAN frame contained non-decodable char: "); + CommsSerial.println(decode_str); break; } } @@ -280,11 +281,11 @@ void print_can_data(char decode_str, telem *current_telem_frame) { void parse_CAN_frame(const uint8_t can_msg[], uint8_t msg_length, char decode_str[], uint8_t decode_length, telem *out) { if (msg_length != decode_length) { - Serial.println("FATAL ERROR - CAN frame and decode code lengths do not match!"); + CommsSerial.println("FATAL ERROR - CAN frame and decode code lengths do not match!"); } if (msg_length > 8) { - Serial.println("FATAL ERROR - CAN frame longer than max!"); + CommsSerial.println("FATAL ERROR - CAN frame longer than max!"); } telem current_telem_frame; // values are all set to 0 @@ -293,17 +294,24 @@ void parse_CAN_frame(const uint8_t can_msg[], uint8_t msg_length, char decode_st parse_CAN_byte(can_msg[i], decode_str[i], ¤t_telem_frame); } - Serial.println("CAN FRAME START ++++++++++++++++++++++++++"); + CommsSerial.println("CAN FRAME START ++++++++++++++++++++++++++"); for (uint8_t i = 0; i < msg_length; i++) { print_can_data(decode_str[i], ¤t_telem_frame); } - Serial.println("CAN FRAME END ---------------------------\n"); + CommsSerial.println("CAN FRAME END ---------------------------\n"); if (out != nullptr) { *out = current_telem_frame; } } +tvc_actuator_telemetry_t parse_tvc_telemetry(const uint8_t data[8]) { + tvc_actuator_telemetry_t out; + out.status_word = (uint32_t)data[0] | ((uint32_t)data[1] << 8) | ((uint32_t)data[2] << 16); // K,L,M + out.position = (uint16_t)data[3] | ((uint16_t)data[4] << 8); // G,H + return out; +} + void send_target_pos(CAN &bus, uint16_t id, uint16_t target_pos) { // Same byte layout as the original prep_CAN_msg(id, target_pos): 2-byte // frame, little-endian target position. toad_can_bus.h documents TOAD's diff --git a/firmware/lib/tvc_actuators/UltramotionActuator.hpp b/firmware/lib/tvc_actuators/UltramotionActuator.hpp index b0ae3a5..fa1037c 100644 --- a/firmware/lib/tvc_actuators/UltramotionActuator.hpp +++ b/firmware/lib/tvc_actuators/UltramotionActuator.hpp @@ -3,7 +3,6 @@ #ifndef UltramotionActuator_H #define UltramotionActuator_H -// Destination in repo: firmware/lib/tvc_actuators/UltramotionActuator.hpp // // Forward declare rather than #include "fdcan_toad.h" here - a reference is // all this header needs, and fdcan_toad.h drags in the STM32 HAL headers, @@ -56,4 +55,11 @@ void send_target_pos(CAN &bus, uint16_t id, uint16_t target_pos); // Sends a CAN frame commanding the actuator at `id` to target_pos with a max_torque limit - data fmt '<>()' void send_target_pos(CAN &bus, uint16_t id, uint16_t target_pos, uint16_t max_torque); +struct tvc_actuator_telemetry_t { + uint32_t status_word; // latched-high status word, bits 0-23 only (byte 3/N not included by default) + uint16_t position; // absolute servo cylinder position +}; + +tvc_actuator_telemetry_t parse_tvc_telemetry(const uint8_t data[8]); + #endif \ No newline at end of file From 69e5ee7fe27f9b81900cd4a89d98cadb353a039c Mon Sep 17 00:00:00 2001 From: Anand Krishnan Date: Sat, 26 Sep 2026 13:27:12 -0400 Subject: [PATCH 4/8] Updated begin() to listen to actuators, updated parse_tvc_telemetry to get more data --- firmware/lib/tvc_actuators/TVC_Actuators.cpp | 30 ++++++++++++++++++- .../lib/tvc_actuators/UltramotionActuator.cpp | 2 ++ .../lib/tvc_actuators/UltramotionActuator.hpp | 6 ++-- 3 files changed, 35 insertions(+), 3 deletions(-) diff --git a/firmware/lib/tvc_actuators/TVC_Actuators.cpp b/firmware/lib/tvc_actuators/TVC_Actuators.cpp index aff8f47..e10e831 100644 --- a/firmware/lib/tvc_actuators/TVC_Actuators.cpp +++ b/firmware/lib/tvc_actuators/TVC_Actuators.cpp @@ -32,10 +32,38 @@ uint16_t length_to_target_pos(float length_mm) { } bool begin() { - float actual_rate = actuators_can.begin(ACTUATORS_CAN_BIT_RATE); + uint32_t ASSUMED_ACTUATOR_TELEMETRY_INTERVAL_MS = 1000; + uint32_t ACTUATOR_HANDSHAKE_TIMEOUT_MS = 2 * ASSUMED_ACTUATOR_TELEMETRY_INTERVAL_MS + 500; + + float actual_rate = CAN_TVC.begin(ACTUATORS_CAN_BIT_RATE); if (actual_rate <= 0.0f) { return false; } + + // both actuators broadcast telemetry on their own every ~ACTUATOR_HANDSHAKE_TIMEOUT_MS/2 ms. Just + // listen until we've heard from both, or give up after the timeout. + bool heard_pitch = false; + bool heard_yaw = false; + + uint32_t start_time = millis(); + while (millis() - start_time < ACTUATOR_HANDSHAKE_TIMEOUT_MS) { + while (CAN_TVC.available() > 0) { + arduino::CanMsg msg = CAN_TVC.read(); + uint32_t id = msg.isStandardId() ? msg.getStandardId() : msg.getExtendedId(); + + if (id == CAN_ID_TVC_PITCH) { + heard_pitch = true; + } else if (id == CAN_ID_TVC_YAW) { + heard_yaw = true; + } + } + + if (heard_pitch && heard_yaw) { + return true; + } + } + + return false; } void set_angles_pitch_yaw(float pitch, float yaw) { diff --git a/firmware/lib/tvc_actuators/UltramotionActuator.cpp b/firmware/lib/tvc_actuators/UltramotionActuator.cpp index c916e44..a9285fc 100644 --- a/firmware/lib/tvc_actuators/UltramotionActuator.cpp +++ b/firmware/lib/tvc_actuators/UltramotionActuator.cpp @@ -309,6 +309,8 @@ tvc_actuator_telemetry_t parse_tvc_telemetry(const uint8_t data[8]) { tvc_actuator_telemetry_t out; out.status_word = (uint32_t)data[0] | ((uint32_t)data[1] << 8) | ((uint32_t)data[2] << 16); // K,L,M out.position = (uint16_t)data[3] | ((uint16_t)data[4] << 8); // G,H + out.avg_motor_current = (uint16_t)data[5] | ((uint16_t)data[6] << 8); // E,F + out.pcb_temp_c = (int16_t)data[7] - 50; // Y return out; } diff --git a/firmware/lib/tvc_actuators/UltramotionActuator.hpp b/firmware/lib/tvc_actuators/UltramotionActuator.hpp index fa1037c..79df06d 100644 --- a/firmware/lib/tvc_actuators/UltramotionActuator.hpp +++ b/firmware/lib/tvc_actuators/UltramotionActuator.hpp @@ -56,8 +56,10 @@ void send_target_pos(CAN &bus, uint16_t id, uint16_t target_pos); void send_target_pos(CAN &bus, uint16_t id, uint16_t target_pos, uint16_t max_torque); struct tvc_actuator_telemetry_t { - uint32_t status_word; // latched-high status word, bits 0-23 only (byte 3/N not included by default) - uint16_t position; // absolute servo cylinder position + uint32_t status_word; // latched-high status word, bits 0-23 only (byte 3/N not included by default) + uint16_t position; // absolute servo cylinder position + uint16_t avg_motor_current; // raw units, 0-32767 - see UM711293 (hardware manual) for conversion to amps + int16_t pcb_temp_c; // decoded PCB temperature in degrees C (raw byte "Y" is 0-200; 0 = -50C, 200 = +150C) }; tvc_actuator_telemetry_t parse_tvc_telemetry(const uint8_t data[8]); From 445f0c83ac5650c4a1cc138acf1d55871b8c19f8 Mon Sep 17 00:00:00 2001 From: Anand Krishnan Date: Sat, 26 Sep 2026 13:37:19 -0400 Subject: [PATCH 5/8] Added messaging for actuators that did not respond --- firmware/lib/tvc_actuators/TVC_Actuators.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/firmware/lib/tvc_actuators/TVC_Actuators.cpp b/firmware/lib/tvc_actuators/TVC_Actuators.cpp index e10e831..fb3ea9d 100644 --- a/firmware/lib/tvc_actuators/TVC_Actuators.cpp +++ b/firmware/lib/tvc_actuators/TVC_Actuators.cpp @@ -4,6 +4,7 @@ #include "ec_pins.h" #include "fdcan_toad.h" #include "toad_can_bus.h" +#include // One CAN object here, not two - CAN_ID_TVC_PITCH and CAN_ID_TVC_YAW are two // message IDs on the SAME physical "Actuators CAN Bus" (PIN_CAN_TVC_RX/TX), @@ -63,6 +64,13 @@ bool begin() { } } + if (!heard_pitch) { + CommsSerial.println("No telemetry heard from Pitch Actuator"); + } + if (!heard_yaw) { + CommsSerial.println("No telemetry heard from Yaw Actuator"); + } + return false; } From d2a3f84d02dac990e2c06b4083b2315627bee2a8 Mon Sep 17 00:00:00 2001 From: Anand Krishnan Date: Thu, 1 Oct 2026 19:24:11 -0400 Subject: [PATCH 6/8] Deleted .gitignore.swp --- ..gitignore.swp | Bin 1024 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 ..gitignore.swp diff --git a/..gitignore.swp b/..gitignore.swp deleted file mode 100644 index 15fc651df2b9258ae901d3a464a5d53b5d1540d8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1024 zcmYc?$V<%2SFq4CVL$*kuu-dg+-Zndy1?MX3m} RQPyY(jD`T+LLd~~CIC@14QT)X From 95abed05c8901a909173c1c6e32ce742f8db8d4a Mon Sep 17 00:00:00 2001 From: Anand Krishnan Date: Thu, 1 Oct 2026 19:41:09 -0400 Subject: [PATCH 7/8] Fixed global CAN PR --- firmware/lib/tvc_actuators/TVC_Actuators.cpp | 32 +++++++------------- 1 file changed, 11 insertions(+), 21 deletions(-) diff --git a/firmware/lib/tvc_actuators/TVC_Actuators.cpp b/firmware/lib/tvc_actuators/TVC_Actuators.cpp index fb3ea9d..ecb744c 100644 --- a/firmware/lib/tvc_actuators/TVC_Actuators.cpp +++ b/firmware/lib/tvc_actuators/TVC_Actuators.cpp @@ -14,9 +14,9 @@ namespace TVC_Actuators { -CAN actuators_can(PIN_CAN_TVC_TX, PIN_CAN_TVC_RX); - -constexpr uint32_t ACTUATORS_CAN_BIT_RATE = 1000000; +uint32_t ASSUMED_ACTUATOR_TELEMETRY_INTERVAL_MS = 1000; +uint32_t ACTUATOR_HANDSHAKE_TIMEOUT_MS = 2 * ASSUMED_ACTUATOR_TELEMETRY_INTERVAL_MS + 500; +uint32_t ACTUATORS_CAN_BIT_RATE = 1000000; bool tvc_debug_mode = false; // TODO - PLACEHOLDER scaling. Assumes a straight linear map from physical @@ -33,23 +33,13 @@ uint16_t length_to_target_pos(float length_mm) { } bool begin() { - uint32_t ASSUMED_ACTUATOR_TELEMETRY_INTERVAL_MS = 1000; - uint32_t ACTUATOR_HANDSHAKE_TIMEOUT_MS = 2 * ASSUMED_ACTUATOR_TELEMETRY_INTERVAL_MS + 500; - - float actual_rate = CAN_TVC.begin(ACTUATORS_CAN_BIT_RATE); - if (actual_rate <= 0.0f) { - return false; - } - - // both actuators broadcast telemetry on their own every ~ACTUATOR_HANDSHAKE_TIMEOUT_MS/2 ms. Just - // listen until we've heard from both, or give up after the timeout. bool heard_pitch = false; bool heard_yaw = false; uint32_t start_time = millis(); while (millis() - start_time < ACTUATOR_HANDSHAKE_TIMEOUT_MS) { - while (CAN_TVC.available() > 0) { - arduino::CanMsg msg = CAN_TVC.read(); + while (can_tvc.available() > 0) { + arduino::CanMsg msg = can_tvc.read(); uint32_t id = msg.isStandardId() ? msg.getStandardId() : msg.getExtendedId(); if (id == CAN_ID_TVC_PITCH) { @@ -65,10 +55,10 @@ bool begin() { } if (!heard_pitch) { - CommsSerial.println("No telemetry heard from Pitch Actuator"); + CommsSerial.println("No telemetry heard from PITCH actuator"); } if (!heard_yaw) { - CommsSerial.println("No telemetry heard from Yaw Actuator"); + CommsSerial.println("No telemetry heard from YAW actuator"); } return false; @@ -78,16 +68,16 @@ void set_angles_pitch_yaw(float pitch, float yaw) { float pitch_len, yaw_len; calc_actuator_lengths(pitch, yaw, &pitch_len, &yaw_len); // already implemented - send_target_pos(actuators_can, CAN_ID_TVC_PITCH, length_to_target_pos(pitch_len)); - send_target_pos(actuators_can, CAN_ID_TVC_YAW, length_to_target_pos(yaw_len)); + send_target_pos(can_tvc, CAN_ID_TVC_PITCH, length_to_target_pos(pitch_len)); + send_target_pos(can_tvc, CAN_ID_TVC_YAW, length_to_target_pos(yaw_len)); } // Call every flight_loop() iteration - nothing currently does. Drains // whatever arrived in the RX FIFO since the last call and decodes anything // addressed to the TVC actuators. void poll() { - while (actuators_can.available() > 0) { - arduino::CanMsg msg = actuators_can.read(); + while (can_tvc.available() > 0) { + arduino::CanMsg msg = can_tvc.read(); uint32_t id = msg.isStandardId() ? msg.getStandardId() : msg.getExtendedId(); if (id == CAN_ID_TVC_PITCH || id == CAN_ID_TVC_YAW) { From dd11c465b4cea396078e3797e1d905d9b31ce87e Mon Sep 17 00:00:00 2001 From: Anand Krishnan Date: Sat, 3 Oct 2026 19:28:47 -0400 Subject: [PATCH 8/8] Addressed PR comments --- firmware/lib/tvc_actuators/TVC_Actuators.cpp | 7 ------- 1 file changed, 7 deletions(-) diff --git a/firmware/lib/tvc_actuators/TVC_Actuators.cpp b/firmware/lib/tvc_actuators/TVC_Actuators.cpp index ecb744c..b70ba8e 100644 --- a/firmware/lib/tvc_actuators/TVC_Actuators.cpp +++ b/firmware/lib/tvc_actuators/TVC_Actuators.cpp @@ -6,17 +6,10 @@ #include "toad_can_bus.h" #include -// One CAN object here, not two - CAN_ID_TVC_PITCH and CAN_ID_TVC_YAW are two -// message IDs on the SAME physical "Actuators CAN Bus" (PIN_CAN_TVC_RX/TX), -// not two separate buses. Note the constructor takes (tx_pin, rx_pin), in -// that order - matches CAN::CAN(uint32_t _tx_pin, uint32_t _rx_pin) in -// fdcan_toad.cpp. - namespace TVC_Actuators { uint32_t ASSUMED_ACTUATOR_TELEMETRY_INTERVAL_MS = 1000; uint32_t ACTUATOR_HANDSHAKE_TIMEOUT_MS = 2 * ASSUMED_ACTUATOR_TELEMETRY_INTERVAL_MS + 500; -uint32_t ACTUATORS_CAN_BIT_RATE = 1000000; bool tvc_debug_mode = false; // TODO - PLACEHOLDER scaling. Assumes a straight linear map from physical