diff --git a/include/config.h b/include/config.h index d48bf90..c3bb117 100644 --- a/include/config.h +++ b/include/config.h @@ -157,6 +157,12 @@ // Jetson: USART6 (PC6=TX, PC7=RX) // Debug: UART5 (PC12=TX, PD2=RX) +// --- ESC Backend Selection (Issue #388) --- +// Pluggable ESC abstraction layer — supports multiple backends: +// HOVERBOARD: EFeru FOC (USART2 @ 115200) — current default +// VESC: FSESC 4.20 Plus (USART6 @ 921600, balance mode) — future +#define ESC_BACKEND HOVERBOARD /* HOVERBOARD or VESC */ + // --- CRSF / ExpressLRS --- // CH1[0]=steer CH2[1]=throttle CH5[4]=arm CH6[5]=mode #define CRSF_ARM_THRESHOLD 1750 /* CH5 raw value; > threshold = armed */ diff --git a/include/esc_backend.h b/include/esc_backend.h new file mode 100644 index 0000000..d1ef848 --- /dev/null +++ b/include/esc_backend.h @@ -0,0 +1,79 @@ +#ifndef ESC_BACKEND_H +#define ESC_BACKEND_H + +#include +#include + +/* + * ESC Backend Abstraction Layer + * + * Provides a pluggable interface for different ESC implementations: + * - Hoverboard (EFeru FOC firmware, UART @ 115200) + * - VESC (via UART @ 921600, with balance mode) — future + * + * Allows motor_driver.c to remain ESC-agnostic. Backend selection + * via ESC_BACKEND compile-time define in config.h. + * + * Issue #388: ESC abstraction layer + * Blocks Issue #383: VESC integration + */ + +/* Telemetry snapshot from ESC (polled on-demand) */ +typedef struct { + int16_t speed; /* Motor speed (PWM duty or RPM, backend-dependent) */ + int16_t steer; /* Steering position (0 = centered) */ + uint16_t voltage_mv; /* Battery voltage in millivolts */ + int16_t current_ma; /* Motor current in milliamps (signed: discharge/charge) */ + int16_t temperature_c; /* ESC temperature in °C */ + uint16_t fault; /* Fault code (backend-specific) */ +} esc_telemetry_t; + +/* Virtual function table for ESC backends */ +typedef struct { + /* Initialize ESC hardware and UART (called once at startup) */ + void (*init)(void); + + /* Send motor command to ESC (called at ~50Hz from motor_driver_update) + * speed: -1000..+1000 (forward/reverse) + * steer: -1000..+1000 (left/right) + */ + void (*send)(int16_t speed, int16_t steer); + + /* Emergency stop: send zero and disable output + * (called from safety or mode manager) + */ + void (*estop)(void); + + /* Query current ESC state + * Returns latest telemetry snapshot (may be cached/stale on some backends). + * Safe to call from any context (non-blocking). + */ + void (*get_telemetry)(esc_telemetry_t *out); + + /* Optional: resume from estop (not all backends use this) */ + void (*resume)(void); +} esc_backend_t; + +/* + * Register a backend implementation at runtime. + * Typically called during init sequence before motor_driver_init(). + */ +void esc_backend_register(const esc_backend_t *backend); + +/* + * Get the currently active backend. + * Returns pointer to vtable; nullptr if no backend registered. + */ +const esc_backend_t *esc_backend_get(void); + +/* + * High-level convenience wrappers (match motor_driver.c interface). + * These call through the active backend if registered. + */ +void esc_init(void); +void esc_send(int16_t speed, int16_t steer); +void esc_estop(void); +void esc_resume(void); +void esc_get_telemetry(esc_telemetry_t *out); + +#endif /* ESC_BACKEND_H */ diff --git a/src/esc_backend.c b/src/esc_backend.c new file mode 100644 index 0000000..61f4cd6 --- /dev/null +++ b/src/esc_backend.c @@ -0,0 +1,58 @@ +#include "esc_backend.h" +#include + +/* Global active backend (selected at runtime or compile-time) */ +static const esc_backend_t *g_active_backend = NULL; + +void esc_backend_register(const esc_backend_t *backend) { + g_active_backend = backend; + if (backend && backend->init) { + backend->init(); + } +} + +const esc_backend_t *esc_backend_get(void) { + return g_active_backend; +} + +/* High-level convenience wrappers — call through vtable */ + +void esc_init(void) { + if (g_active_backend && g_active_backend->init) { + g_active_backend->init(); + } +} + +void esc_send(int16_t speed, int16_t steer) { + if (g_active_backend && g_active_backend->send) { + g_active_backend->send(speed, steer); + } +} + +void esc_estop(void) { + if (g_active_backend && g_active_backend->estop) { + g_active_backend->estop(); + } +} + +void esc_resume(void) { + if (g_active_backend && g_active_backend->resume) { + g_active_backend->resume(); + } +} + +void esc_get_telemetry(esc_telemetry_t *out) { + if (out) { + /* Zero-fill by default */ + out->speed = 0; + out->steer = 0; + out->voltage_mv = 0; + out->current_ma = 0; + out->temperature_c = 0; + out->fault = 0; + + if (g_active_backend && g_active_backend->get_telemetry) { + g_active_backend->get_telemetry(out); + } + } +} diff --git a/src/esc_hoverboard.c b/src/esc_hoverboard.c new file mode 100644 index 0000000..b41ae23 --- /dev/null +++ b/src/esc_hoverboard.c @@ -0,0 +1,127 @@ +#include "esc_backend.h" +#include "config.h" +#include "stm32f7xx_hal.h" + +/* + * Hoverboard ESC Backend Implementation + * + * Adapts the Hoverboard EFeru FOC protocol to the ESC backend vtable. + * UART2: PA2=TX, PA3=RX @ 115200 baud + * + * Packet: [0xABCD] [steer:i16] [speed:i16] [checksum:u16] + * Checksum = start ^ steer ^ speed + * Speed range: -1000 to +1000 + * Must send at >=50Hz or ESC times out. + */ + +#define HOVERBOARD_START_FRAME 0xABCD +#define HOVERBOARD_BAUD 115200 + +typedef struct __attribute__((packed)) { + uint16_t start; + int16_t steer; + int16_t speed; + uint16_t checksum; +} hoverboard_cmd_t; + +static UART_HandleTypeDef huart2; + +/* Backend vtable instance */ +static const esc_backend_t hoverboard_backend; + +/* + * Initialize UART2 for hoverboard communication. + * Called once at startup via backend registration. + */ +static void hoverboard_backend_init(void) { + /* Enable clocks */ + __HAL_RCC_USART2_CLK_ENABLE(); + __HAL_RCC_GPIOA_CLK_ENABLE(); + + /* PA2=TX, PA3=RX, AF7 for USART2 */ + GPIO_InitTypeDef gpio = {0}; + gpio.Pin = GPIO_PIN_2 | GPIO_PIN_3; + gpio.Mode = GPIO_MODE_AF_PP; + gpio.Pull = GPIO_PULLUP; + gpio.Speed = GPIO_SPEED_FREQ_HIGH; + gpio.Alternate = GPIO_AF7_USART2; + HAL_GPIO_Init(GPIOA, &gpio); + + /* USART2 config */ + huart2.Instance = USART2; + huart2.Init.BaudRate = HOVERBOARD_BAUD; + huart2.Init.WordLength = UART_WORDLENGTH_8B; + huart2.Init.StopBits = UART_STOPBITS_1; + huart2.Init.Parity = UART_PARITY_NONE; + huart2.Init.Mode = UART_MODE_TX_RX; + huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE; + huart2.Init.OverSampling = UART_OVERSAMPLING_16; + HAL_UART_Init(&huart2); +} + +/* + * Send motor command via hoverboard protocol. + * Called at ~50Hz from motor_driver_update(). + */ +static void hoverboard_backend_send(int16_t speed, int16_t steer) { + hoverboard_cmd_t cmd; + cmd.start = HOVERBOARD_START_FRAME; + cmd.steer = steer; + cmd.speed = speed; + cmd.checksum = cmd.start ^ cmd.steer ^ cmd.speed; + + HAL_UART_Transmit(&huart2, (uint8_t *)&cmd, sizeof(cmd), 5); +} + +/* + * Emergency stop: send zero and disable motors. + * Hoverboard will disable outputs on repeated zero packets. + */ +static void hoverboard_backend_estop(void) { + hoverboard_backend_send(0, 0); +} + +/* + * Resume after estop (optional, hoverboard auto-resumes on non-zero command). + */ +static void hoverboard_backend_resume(void) { + /* No action needed — next non-zero send will resume */ +} + +/* + * Query telemetry from hoverboard. + * Hoverboard protocol is command-only (no RX in this implementation). + * Return zero telemetry stub; future: add RX feedback. + */ +static void hoverboard_backend_get_telemetry(esc_telemetry_t *out) { + if (out) { + out->speed = 0; + out->steer = 0; + out->voltage_mv = 0; + out->current_ma = 0; + out->temperature_c = 0; + out->fault = 0; + } +} + +/* Hoverboard backend vtable */ +static const esc_backend_t hoverboard_backend = { + .init = hoverboard_backend_init, + .send = hoverboard_backend_send, + .estop = hoverboard_backend_estop, + .resume = hoverboard_backend_resume, + .get_telemetry = hoverboard_backend_get_telemetry, +}; + +/* + * Public functions for backward compatibility. + * These remain for existing code that calls hoverboard_init/hoverboard_send directly. + */ + +void hoverboard_init(void) { + esc_backend_register(&hoverboard_backend); +} + +void hoverboard_send(int16_t speed, int16_t steer) { + esc_send(speed, steer); +} diff --git a/src/esc_vesc.c b/src/esc_vesc.c new file mode 100644 index 0000000..9d3ebfb --- /dev/null +++ b/src/esc_vesc.c @@ -0,0 +1,70 @@ +#include "esc_backend.h" + +/* + * VESC ESC Backend Stub + * + * Placeholder implementation for FSESC 4.20 Plus (VESC-based dual ESC). + * UART: ttyTHS1 (Jetson Orin) → FC USART6 @ 921600 baud + * Protocol: pyvesc with CRC16-XModem checksum + * + * Issue #383: VESC integration (fills in this stub) + * Issue #388: ESC abstraction layer (provides this interface) + * + * TODO (Issue #383): + * - Implement vesc_init() with UART/GPIO config + * - Implement vesc_send() with pyvesc packet encoding (duty/RPM control) + * - Implement vesc_estop() to disable motor controller + * - Implement vesc_get_telemetry() to parse VESC state messages + * - Add balance mode configuration if using VESC balance app + */ + +static const esc_backend_t vesc_backend; + +/* Stub implementations — no-op until #383 fills them in */ + +static void vesc_backend_init(void) { + /* TODO (Issue #383): Initialize UART6, configure VESC balance mode */ +} + +static void vesc_backend_send(int16_t speed, int16_t steer) { + /* TODO (Issue #383): Encode speed/steer to pyvesc packet and send via UART6 */ + (void)speed; + (void)steer; +} + +static void vesc_backend_estop(void) { + /* TODO (Issue #383): Send VESC shutdown command to disable motors */ +} + +static void vesc_backend_resume(void) { + /* TODO (Issue #383): Resume from estop if needed */ +} + +static void vesc_backend_get_telemetry(esc_telemetry_t *out) { + /* TODO (Issue #383): Poll/parse VESC telemetry (voltage, current, RPM, temp, fault) */ + if (out) { + out->speed = 0; + out->steer = 0; + out->voltage_mv = 0; + out->current_ma = 0; + out->temperature_c = 0; + out->fault = 0; + } +} + +/* VESC backend vtable */ +static const esc_backend_t vesc_backend = { + .init = vesc_backend_init, + .send = vesc_backend_send, + .estop = vesc_backend_estop, + .resume = vesc_backend_resume, + .get_telemetry = vesc_backend_get_telemetry, +}; + +/* + * Public function to register VESC backend. + * Called from main.c when configured with ESC_BACKEND=VESC. + */ +void vesc_backend_register_impl(void) { + esc_backend_register(&vesc_backend); +} diff --git a/src/hoverboard.c b/src/hoverboard.c deleted file mode 100644 index 572a3bc..0000000 --- a/src/hoverboard.c +++ /dev/null @@ -1,41 +0,0 @@ -#include "hoverboard.h" -#include "config.h" -#include "stm32f7xx_hal.h" - -static UART_HandleTypeDef huart2; - -void hoverboard_init(void) { - /* Enable clocks */ - __HAL_RCC_USART2_CLK_ENABLE(); - __HAL_RCC_GPIOA_CLK_ENABLE(); - - /* PA2=TX, PA3=RX, AF7 for USART2 */ - GPIO_InitTypeDef gpio = {0}; - gpio.Pin = GPIO_PIN_2 | GPIO_PIN_3; - gpio.Mode = GPIO_MODE_AF_PP; - gpio.Pull = GPIO_PULLUP; - gpio.Speed = GPIO_SPEED_FREQ_HIGH; - gpio.Alternate = GPIO_AF7_USART2; - HAL_GPIO_Init(GPIOA, &gpio); - - /* USART2 config */ - huart2.Instance = USART2; - huart2.Init.BaudRate = HOVERBOARD_BAUD; - huart2.Init.WordLength = UART_WORDLENGTH_8B; - huart2.Init.StopBits = UART_STOPBITS_1; - huart2.Init.Parity = UART_PARITY_NONE; - huart2.Init.Mode = UART_MODE_TX_RX; - huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE; - huart2.Init.OverSampling = UART_OVERSAMPLING_16; - HAL_UART_Init(&huart2); -} - -void hoverboard_send(int16_t speed, int16_t steer) { - hoverboard_cmd_t cmd; - cmd.start = HOVERBOARD_START_FRAME; - cmd.steer = steer; - cmd.speed = speed; - cmd.checksum = cmd.start ^ cmd.steer ^ cmd.speed; - - HAL_UART_Transmit(&huart2, (uint8_t *)&cmd, sizeof(cmd), 5); -} diff --git a/src/motor_driver.c b/src/motor_driver.c index 2bddc48..c4a3860 100644 --- a/src/motor_driver.c +++ b/src/motor_driver.c @@ -1,5 +1,5 @@ #include "motor_driver.h" -#include "hoverboard.h" +#include "esc_backend.h" #include "config.h" #include @@ -22,7 +22,7 @@ void motor_driver_update(motor_driver_t *m, /* Emergency stop: send zero, hold latch */ if (m->estop) { - hoverboard_send(0, 0); + esc_send(0, 0); return; } @@ -42,7 +42,7 @@ void motor_driver_update(motor_driver_t *m, if (steer > headroom) steer = headroom; if (steer < -headroom) steer = -headroom; - hoverboard_send((int16_t)speed, (int16_t)steer); + esc_send((int16_t)speed, (int16_t)steer); } void motor_driver_estop(motor_driver_t *m) {