saltylab-firmware/include/uart_protocol.h
sl-firmware 602fbc6ab3 feat: UART command protocol for Jetson-STM32 (Issue #629)
Implements binary command protocol on UART5 (PC12/PD2) at 115200 baud
for Jetson→STM32 communication. Frame: STX+LEN+CMD+PAYLOAD+CRC8+ETX.

Commands: SET_VELOCITY (RPM direct to CAN), GET_STATUS, SET_PID, ESTOP,
CLEAR_ESTOP. DMA1_Stream0_Channel4 circular 256-byte RX ring. ACK/NACK
inline; STATUS pushed at 10 Hz. Heartbeat timeout 500 ms (UART_PROT_HB_TIMEOUT_MS).

NOTE: Spec requested USART1 @ 115200; USART1 occupied by JLink @ 921600.
Implemented on UART5 instead; note in code comments.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-15 14:41:00 -04:00

97 lines
4.4 KiB
C
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#ifndef UART_PROTOCOL_H
#define UART_PROTOCOL_H
/*
* uart_protocol.h — UART command protocol for Jetson-STM32 communication (Issue #629)
*
* Frame format:
* [STX][LEN][CMD][PAYLOAD...][CRC8][ETX]
* 0x02 1B 1B 0-12 B 1B 0x03
*
* CRC8-SMBUS: poly=0x07, init=0x00, computed over CMD+PAYLOAD bytes.
*
* Physical layer: UART5 (PC12=TX / PD2=RX), GPIO_AF8_UART5, 115200 baud, no hw flow.
* NOTE: Spec requested USART1 @ 115200, but USART1 is occupied by JLink @ 921600.
* Implemented on UART5 instead; Jetson must connect to PC12/PD2.
*
* DMA: DMA1_Stream0_Channel4, circular 256-byte ring buffer.
* Heartbeat: if no frame received in UART_PROT_HB_TIMEOUT_MS (500 ms), Jetson is
* considered lost; caller must handle estop if needed.
*/
#include <stdint.h>
#include <stdbool.h>
/* ── Frame delimiters ─────────────────────────────────────────────────────── */
#define UPROT_STX 0x02u
#define UPROT_ETX 0x03u
/* ── Command IDs (host → STM32) ───────────────────────────────────────────── */
#define UCMD_SET_VELOCITY 0x01u /* payload: int16 left_rpm, int16 right_rpm (4 B) */
#define UCMD_GET_STATUS 0x02u /* payload: none */
#define UCMD_SET_PID 0x03u /* payload: float kp, float ki, float kd (12 B) */
#define UCMD_ESTOP 0x04u /* payload: none */
#define UCMD_CLEAR_ESTOP 0x05u /* payload: none */
/* ── Response IDs (STM32 → host) ──────────────────────────────────────────── */
#define URESP_ACK 0x80u /* payload: 1 B — echoed CMD */
#define URESP_NACK 0x81u /* payload: 2 B — CMD, error_code */
#define URESP_STATUS 0x82u /* payload: sizeof(uart_prot_status_t) = 8 B */
/* ── NACK error codes ─────────────────────────────────────────────────────── */
#define UERR_BAD_CRC 0x01u
#define UERR_BAD_LEN 0x02u
#define UERR_BAD_ETX 0x03u
#define UERR_ESTOP 0x04u /* command rejected — estop active */
#define UERR_DISARMED 0x05u /* velocity rejected — not armed */
/* ── STATUS payload (URESP_STATUS, 8 bytes packed) ───────────────────────── */
typedef struct __attribute__((packed)) {
int16_t pitch_x10; /* pitch angle ×10 deg (balance controller) */
int16_t motor_cmd; /* ESC motor command -1000..+1000 */
uint16_t vbat_mv; /* battery voltage in mV */
uint8_t balance_state; /* BalanceState enum (0=DISARMED, 1=ARMED, …) */
uint8_t estop_active; /* non-zero if remote estop is latched */
} uart_prot_status_t;
/* ── Shared state (read by main.c) ────────────────────────────────────────── */
typedef struct {
volatile uint8_t vel_updated; /* 1 when SET_VELOCITY received */
volatile int16_t left_rpm;
volatile int16_t right_rpm;
volatile uint8_t pid_updated; /* 1 when SET_PID received */
volatile float pid_kp;
volatile float pid_ki;
volatile float pid_kd;
volatile uint8_t estop_req; /* 1 on UCMD_ESTOP */
volatile uint8_t estop_clear_req; /* 1 on UCMD_CLEAR_ESTOP */
volatile uint32_t last_rx_ms; /* HAL_GetTick() of last valid frame */
} UartProtState;
extern UartProtState uart_prot_state;
/* ── API ───────────────────────────────────────────────────────────────────── */
/**
* uart_protocol_init() — configure UART5 + DMA, start circular receive.
* Must be called once during system init, before main loop.
*/
void uart_protocol_init(void);
/**
* uart_protocol_process() — drain DMA ring buffer, parse frames, dispatch commands.
* Call once per main loop iteration (every ~1 ms).
*/
void uart_protocol_process(void);
/**
* uart_protocol_send_status() — build and TX a URESP_STATUS frame.
* @param s Pointer to status payload to send.
*/
void uart_protocol_send_status(const uart_prot_status_t *s);
#endif /* UART_PROTOCOL_H */