#ifndef UART_PROTOCOL_H #define UART_PROTOCOL_H #include #include /* * uart_protocol.h -- Structured UART command protocol for Jetson-STM32 * communication (Issue #629) * * Hardware: UART5, PC12=TX / PD2=RX, 115200 baud * Note: spec references USART1 but USART1 is occupied by JLink (Issue #120) * at 921600 baud. UART5 is the designated secondary/debug UART and is used * here as the structured-command channel. * * Frame format (both directions): * [STX=0x02][LEN][CMD][PAYLOAD...][CRC8][ETX=0x03] * * STX : frame start sentinel (0x02) * LEN : CMD byte + payload bytes (1 + payload_len) * CMD : command / response type byte * PAYLOAD : 0..N bytes, CMD-dependent * CRC8 : CRC8-SMBUS (poly 0x07, init 0x00) over CMD+PAYLOAD * ETX : frame end sentinel (0x03) * * Host (Jetson) to STM32 commands: * 0x01 SET_VELOCITY - int16 left_rpm, int16 right_rpm (4 bytes) * 0x02 GET_STATUS - no payload; reply URESP_STATUS * 0x03 SET_PID - float kp, float ki, float kd (12 bytes, LE) * 0x04 ESTOP - no payload; engage emergency stop * 0x05 CLEAR_ESTOP - no payload; clear emergency stop * * STM32 to host responses: * 0x80 ACK - uint8 echoed_cmd (1 byte) * 0x81 NACK - uint8 echoed_cmd, uint8 error_code (2 bytes) * 0x82 STATUS - uart_prot_status_t (8 bytes) * * Heartbeat: if no valid frame is received within UART_PROT_HB_TIMEOUT_MS, * uart_protocol_is_active() returns false and velocity commands are ignored. * * DMA: UART5_RX uses DMA1_Stream0_Channel4 in circular mode (256-byte ring). * Parser runs in uart_protocol_process() called from the main loop. */ /* ---- Frame constants ---- */ #define UPROT_STX 0x02u #define UPROT_ETX 0x03u #define UPROT_RX_BUF_LEN 256u /* DMA ring buffer size (power of 2) */ #define UPROT_MAX_PAYLOAD 16u /* largest payload: SET_PID = 12 bytes */ /* ---- Command IDs (host to STM32) ---- */ #define UCMD_SET_VELOCITY 0x01u /* int16 left_rpm + int16 right_rpm */ #define UCMD_GET_STATUS 0x02u /* no payload */ #define UCMD_SET_PID 0x03u /* float kp, ki, kd (12 bytes LE) */ #define UCMD_ESTOP 0x04u /* no payload */ #define UCMD_CLEAR_ESTOP 0x05u /* no payload */ /* ---- Response IDs (STM32 to host) ---- */ #define URESP_ACK 0x80u /* 1-byte payload: echoed CMD */ #define URESP_NACK 0x81u /* 2-byte payload: CMD + error_code */ #define URESP_STATUS 0x82u /* 8-byte payload: uart_prot_status_t */ /* ---- NACK error codes ---- */ #define UERR_UNKNOWN_CMD 0x01u /* unrecognised CMD byte */ #define UERR_BAD_LENGTH 0x02u /* LEN does not match CMD expectation */ #define UERR_BAD_CRC 0x03u /* CRC8 mismatch */ #define UERR_REFUSED 0x04u /* command refused in current state */ /* ---- STATUS response payload (8 bytes, packed) ---- */ typedef struct __attribute__((packed)) { int16_t pitch_x10; /* pitch angle, degrees x10 (0.1° resolution) */ int16_t motor_cmd; /* balance PID ESC output, -1000..+1000 */ uint16_t vbat_mv; /* battery voltage (mV) */ uint8_t balance_state; /* BalanceState: 0=DISARMED, 1=ARMED, 2=TILT */ uint8_t estop_active; /* 1 = estop currently engaged */ } uart_prot_status_t; /* 8 bytes */ /* ---- Volatile state (read from main loop) ---- */ typedef struct { /* SET_VELOCITY: set by parser, cleared by main loop */ volatile uint8_t vel_updated; /* 1 = new velocity command pending */ volatile int16_t left_rpm; /* requested left wheel RPM */ volatile int16_t right_rpm; /* requested right wheel RPM */ /* SET_PID: set by parser, cleared by main loop */ volatile uint8_t pid_updated; /* 1 = new PID gains pending */ volatile float pid_kp; volatile float pid_ki; volatile float pid_kd; /* ESTOP / CLEAR_ESTOP: set by parser, cleared by main loop */ volatile uint8_t estop_req; volatile uint8_t estop_clear_req; /* Heartbeat: updated on every valid frame */ volatile uint32_t last_rx_ms; /* HAL_GetTick() at last good frame; 0=none */ } UartProtState; extern volatile UartProtState uart_prot_state; /* ---- API ---- */ /* * uart_protocol_init() -- configure UART5 + DMA1_Stream0 and start reception. * Call once during system init, before the main loop. */ void uart_protocol_init(void); /* * uart_protocol_process() -- parse new bytes from DMA ring buffer, dispatch * commands, and send ACK/NACK/STATUS responses. * Call every main loop tick (non-blocking, < 5 µs when idle). */ void uart_protocol_process(void); /* * uart_protocol_is_active(now_ms) -- returns true if a valid frame was * received within UART_PROT_HB_TIMEOUT_MS. Main loop uses this to gate * SET_VELOCITY commands when the host is disconnected. */ bool uart_protocol_is_active(uint32_t now_ms); /* * uart_protocol_send_status(s) -- transmit a URESP_STATUS (0x82) frame. * Called from main loop in response to GET_STATUS or periodically. */ void uart_protocol_send_status(const uart_prot_status_t *s); #endif /* UART_PROTOCOL_H */