Add hw_button driver (PC2 active-low, 20ms debounce) with gesture detection: - Single short press + 500ms quiet -> BTN_EVENT_PARK - SHORT+SHORT+LONG combo (within 3s) -> BTN_EVENT_REARM_COMBO New BALANCE_PARKED state: PID frozen, motors off, quick re-arm via button combo without the 3-second arm interlock required from DISARMED. FC_BTN (0x404) CAN frame sent to Orin on each event: event_id 1=PARKED, 2=UNPARKED, 3=UNPARK_FAILED (pitch > 20 deg) Includes 11 unit tests (1016 assertions) exercising debounce, bounce rejection, short/long classification, sequence detection, and timeout. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
168 lines
7.3 KiB
C
168 lines
7.3 KiB
C
#ifndef ORIN_CAN_H
|
||
#define ORIN_CAN_H
|
||
|
||
#include <stdint.h>
|
||
#include <stdbool.h>
|
||
|
||
/*
|
||
* orin_can — Orin↔FC CAN protocol driver (Issue #674).
|
||
*
|
||
* Standard 11-bit CAN IDs on CAN2, FIFO0.
|
||
*
|
||
* Orin → FC commands:
|
||
* 0x300 HEARTBEAT : uint32 sequence counter (4 bytes)
|
||
* 0x301 DRIVE : int16 speed (−1000..+1000), int16 steer (−1000..+1000)
|
||
* 0x302 MODE : uint8 mode (0=RC_MANUAL, 1=ASSISTED, 2=AUTONOMOUS)
|
||
* 0x303 ESTOP : uint8 action (1=ESTOP, 0=CLEAR)
|
||
*
|
||
* FC → Orin telemetry (broadcast at ORIN_TLM_HZ):
|
||
* 0x400 FC_STATUS : int16 pitch_x10, int16 motor_cmd, uint16 vbat_mv,
|
||
* uint8 balance_state, uint8 flags [bit0=estop, bit1=armed]
|
||
* 0x401 FC_VESC : int16 left_rpm_x10 (RPM/10), int16 right_rpm_x10,
|
||
* int16 left_current_x10, int16 right_current_x10
|
||
*
|
||
* Balance independence: if no Orin heartbeat for ORIN_HB_TIMEOUT_MS, the FC
|
||
* continues balancing in-place — Orin commands are simply not injected.
|
||
* The balance PID loop runs entirely on Mamba and never depends on Orin.
|
||
*/
|
||
|
||
/* ---- Orin → FC command IDs ---- */
|
||
#define ORIN_CAN_ID_HEARTBEAT 0x300u
|
||
#define ORIN_CAN_ID_DRIVE 0x301u
|
||
#define ORIN_CAN_ID_MODE 0x302u
|
||
#define ORIN_CAN_ID_ESTOP 0x303u
|
||
#define ORIN_CAN_ID_LED_CMD 0x304u /* LED pattern override (Issue #685) */
|
||
|
||
/* ---- FC → Orin telemetry IDs ---- */
|
||
#define ORIN_CAN_ID_FC_STATUS 0x400u /* balance state + pitch + vbat at 10 Hz */
|
||
#define ORIN_CAN_ID_FC_VESC 0x401u /* VESC RPM + current at 10 Hz */
|
||
#define ORIN_CAN_ID_FC_IMU 0x402u /* full IMU angles + cal status at 50 Hz (Issue #680) */
|
||
#define ORIN_CAN_ID_FC_BARO 0x403u /* barometer pressure/temp/altitude at 1 Hz (Issue #672) */
|
||
#define ORIN_CAN_ID_FC_BTN 0x404u /* button event on-demand (Issue #682) */
|
||
|
||
/* ---- Timing ---- */
|
||
#define ORIN_HB_TIMEOUT_MS 500u /* Orin offline after 500 ms without any frame */
|
||
#define ORIN_TLM_HZ 10u /* FC_STATUS + FC_VESC broadcast rate (Hz) */
|
||
#define ORIN_IMU_TLM_HZ 50u /* FC_IMU broadcast rate (Hz) */
|
||
#define ORIN_BARO_TLM_HZ 1u /* FC_BARO broadcast rate (Hz) */
|
||
|
||
/* ---- Volatile state updated by orin_can_on_frame(), read by main loop ---- */
|
||
typedef struct {
|
||
volatile int16_t speed; /* DRIVE: −1000..+1000 */
|
||
volatile int16_t steer; /* DRIVE: −1000..+1000 */
|
||
volatile uint8_t mode; /* MODE: robot_mode_t value */
|
||
volatile uint8_t drive_updated; /* set on DRIVE, cleared by main */
|
||
volatile uint8_t mode_updated; /* set on MODE, cleared by main */
|
||
volatile uint8_t estop_req; /* set on ESTOP(1), cleared by main */
|
||
volatile uint8_t estop_clear_req; /* set on ESTOP(0), cleared by main */
|
||
volatile uint32_t last_rx_ms; /* HAL_GetTick() of last received frame */
|
||
} OrinCanState;
|
||
|
||
extern volatile OrinCanState orin_can_state;
|
||
|
||
/* ---- FC → Orin broadcast payloads (packed, 8 bytes each) ---- */
|
||
|
||
typedef struct __attribute__((packed)) {
|
||
int16_t pitch_x10; /* pitch degrees × 10 */
|
||
int16_t motor_cmd; /* balance PID output −1000..+1000 */
|
||
uint16_t vbat_mv; /* battery voltage (mV) */
|
||
uint8_t balance_state; /* BalanceState: 0=DISARMED,1=ARMED,2=TILT_FAULT */
|
||
uint8_t flags; /* bit0=estop_active, bit1=armed */
|
||
} orin_can_fc_status_t; /* 8 bytes */
|
||
|
||
typedef struct __attribute__((packed)) {
|
||
int16_t left_rpm_x10; /* left wheel RPM / 10 (±32767 × 10 = ±327k RPM) */
|
||
int16_t right_rpm_x10; /* right wheel RPM / 10 */
|
||
int16_t left_current_x10; /* left phase current × 10 (A) */
|
||
int16_t right_current_x10; /* right phase current × 10 (A) */
|
||
} orin_can_fc_vesc_t; /* 8 bytes */
|
||
|
||
/* FC_IMU (0x402) — full IMU angles at 50 Hz (Issue #680) */
|
||
typedef struct __attribute__((packed)) {
|
||
int16_t pitch_x10; /* pitch degrees × 10 (mount-offset corrected) */
|
||
int16_t roll_x10; /* roll degrees × 10 (mount-offset corrected) */
|
||
int16_t yaw_x10; /* yaw degrees × 10 (gyro-integrated, drifts) */
|
||
uint8_t cal_status; /* 0=uncal, 1=gyro_cal, 2=gyro+mount_cal */
|
||
uint8_t balance_state; /* BalanceState: 0=DISARMED, 1=ARMED, 2=TILT_FAULT */
|
||
} orin_can_fc_imu_t; /* 8 bytes */
|
||
|
||
/* FC_BARO (0x403) — barometer at 1 Hz (Issue #672) */
|
||
typedef struct __attribute__((packed)) {
|
||
int32_t pressure_pa; /* barometric pressure in Pa */
|
||
int16_t temp_x10; /* temperature × 10 (°C) */
|
||
int16_t alt_cm; /* altitude in cm (reference = pressure at boot) */
|
||
} orin_can_fc_baro_t; /* 8 bytes */
|
||
|
||
/* FC_BTN (0x404) — button event, sent on demand (Issue #682)
|
||
* event_id: 1=PARKED, 2=UNPARKED, 3=UNPARK_FAILED (pitch too large) */
|
||
typedef struct __attribute__((packed)) {
|
||
uint8_t event_id; /* 1=PARKED, 2=UNPARKED, 3=UNPARK_FAILED */
|
||
uint8_t balance_state; /* balance_state_t value after the event */
|
||
} orin_can_fc_btn_t; /* 2 bytes */
|
||
|
||
/* LED_CMD (0x304) — Orin → FC LED pattern override (Issue #685)
|
||
* duration_ms = 0: hold until next state change; >0: revert after duration */
|
||
typedef struct __attribute__((packed)) {
|
||
uint8_t pattern; /* 0=state_auto, 1=solid, 2=slow_blink, 3=fast_blink, 4=pulse */
|
||
uint8_t brightness; /* 0-255 (0 = both LEDs off) */
|
||
uint16_t duration_ms; /* override duration; 0 = permanent until state change */
|
||
} orin_can_led_cmd_t; /* 4 bytes */
|
||
|
||
/* LED override state (updated by orin_can_on_frame, read by main loop) */
|
||
extern volatile orin_can_led_cmd_t orin_can_led_override;
|
||
extern volatile uint8_t orin_can_led_updated;
|
||
|
||
/* ---- API ---- */
|
||
|
||
/*
|
||
* orin_can_init() — zero state, register orin_can_on_frame as std_cb with
|
||
* can_driver. Call after can_driver_init().
|
||
*/
|
||
void orin_can_init(void);
|
||
|
||
/*
|
||
* orin_can_on_frame(std_id, data, len) — dispatched by can_driver for each
|
||
* standard-ID frame in FIFO0. Updates orin_can_state.
|
||
*/
|
||
void orin_can_on_frame(uint16_t std_id, const uint8_t *data, uint8_t len);
|
||
|
||
/*
|
||
* orin_can_is_alive(now_ms) — true if a frame from Orin arrived within
|
||
* ORIN_HB_TIMEOUT_MS of now_ms.
|
||
*/
|
||
bool orin_can_is_alive(uint32_t now_ms);
|
||
|
||
/*
|
||
* orin_can_broadcast(now_ms, status, vesc) — rate-limited broadcast of
|
||
* FC_STATUS (0x400) and FC_VESC (0x401) at ORIN_TLM_HZ (10 Hz).
|
||
* Safe to call every ms; internally rate-limited.
|
||
*/
|
||
void orin_can_broadcast(uint32_t now_ms,
|
||
const orin_can_fc_status_t *status,
|
||
const orin_can_fc_vesc_t *vesc);
|
||
|
||
/*
|
||
* orin_can_broadcast_imu(now_ms, imu_tlm) — rate-limited broadcast of
|
||
* FC_IMU (0x402) at ORIN_IMU_TLM_HZ (50 Hz).
|
||
* Safe to call every ms; internally rate-limited. Issue #680.
|
||
*/
|
||
void orin_can_broadcast_imu(uint32_t now_ms,
|
||
const orin_can_fc_imu_t *imu_tlm);
|
||
|
||
/*
|
||
* orin_can_broadcast_baro(now_ms, baro_tlm) — rate-limited broadcast of
|
||
* FC_BARO (0x403) at ORIN_BARO_TLM_HZ (1 Hz).
|
||
* Pass NULL to skip transmission. Issue #672.
|
||
*/
|
||
void orin_can_broadcast_baro(uint32_t now_ms,
|
||
const orin_can_fc_baro_t *baro_tlm);
|
||
|
||
/*
|
||
* orin_can_send_btn_event(event_id, balance_state) — send FC_BTN (0x404)
|
||
* immediately. Call on button park/unpark events. Issue #682.
|
||
* event_id: 1=PARKED, 2=UNPARKED, 3=UNPARK_FAILED.
|
||
*/
|
||
void orin_can_send_btn_event(uint8_t event_id, uint8_t balance_state);
|
||
|
||
#endif /* ORIN_CAN_H */
|