Issue #680 — IMU mount angle calibration: - imu_cal_flash.h/.c: store pitch/roll offsets in flash sector 7 (0x0807FF00, 64 bytes; preserves PID records across sector erase) - mpu6000_set_mount_offset(): subtracts offsets from pitch/roll output - mpu6000_has_mount_offset(): reports cal_status=2 to Orin - 'O' CDC command: capture current pitch/roll → save to flash → ACK JSON - Load offsets on boot; report in printf log CAN telemetry correction (Tee: production has no USB to Orin): - FC_IMU (0x402): pitch/roll/yaw/cal_status/balance_state at 50 Hz - orin_can_broadcast_imu() rate-limited to ORIN_IMU_TLM_HZ (50 Hz) - FC_BARO (0x403): pressure_pa/temp_x10/alt_cm at 1 Hz (Issue #672) - orin_can_broadcast_baro() rate-limited to ORIN_BARO_TLM_HZ (1 Hz) Issue #685 — LED CAN override: - ORIN_CAN_ID_LED_CMD (0x304): pattern/brightness/duration_ms from Orin - orin_can_led_override volatile state + orin_can_led_updated flag - main.c: apply pattern to LED state machine on each LED_CMD received Orin side: - saltybot_can_node.py: production SocketCAN bridge — reads 0x400-0x403, publishes /saltybot/imu, /saltybot/balance_state, /saltybot/barometer; subscribes /cmd_vel → 0x301 DRIVE; /saltybot/leds → 0x304 LED_CMD; sends 0x300 HEARTBEAT at 5 Hz; sends 0x303 ESTOP on shutdown - setup.py: register saltybot_can_node entry point + uart_bridge launch Fix: re-apply --defsym __stack_end=_estack-0x1000 linker fix to branch Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
212 lines
7.7 KiB
C
212 lines
7.7 KiB
C
#include "usbd_cdc_if.h"
|
||
#include "stm32f7xx_hal.h"
|
||
#include "ota.h"
|
||
|
||
extern USBD_HandleTypeDef hUsbDevice;
|
||
volatile uint8_t cdc_streaming = 1; /* auto-stream */
|
||
static volatile uint8_t cdc_port_open = 0; /* set when host asserts DTR */
|
||
volatile uint8_t cdc_arm_request = 0; /* set by A command */
|
||
volatile uint8_t cdc_disarm_request = 0; /* set by D command */
|
||
volatile uint8_t cdc_recal_request = 0; /* set by G command — gyro recalibration */
|
||
volatile uint8_t cdc_imu_cal_request = 0; /* set by O command — mount offset calibration (Issue #680) */
|
||
volatile uint32_t cdc_rx_count = 0; /* total CDC packets received from host */
|
||
|
||
volatile uint8_t cdc_estop_request = 0;
|
||
volatile uint8_t cdc_estop_clear_request = 0;
|
||
|
||
/*
|
||
* PID tuning command buffer.
|
||
* CDC_Receive (USB IRQ) copies multi-char commands here.
|
||
* Main loop polls cdc_cmd_ready, parses, and clears.
|
||
* Commands: P<kp> I<ki> D<kd> T<setpoint> M<max_speed> ?
|
||
*/
|
||
volatile uint8_t cdc_cmd_ready = 0;
|
||
volatile char cdc_cmd_buf[32];
|
||
|
||
/*
|
||
* Jetson command buffer (bidirectional protocol).
|
||
* 'H'\n — heartbeat, ISR updates jetson_hb_tick only (no buf copy needed).
|
||
* 'C'<s>,<t>\n — drive command: ISR copies to buf, main loop parses with sscanf.
|
||
* jetson_hb_tick is also refreshed on every C command.
|
||
*/
|
||
volatile uint8_t jetson_cmd_ready = 0;
|
||
volatile char jetson_cmd_buf[32];
|
||
volatile uint32_t jetson_hb_tick = 0; /* HAL_GetTick() of last H or C */
|
||
|
||
/*
|
||
* USB TX/RX buffers grouped into a single 512-byte aligned struct so that
|
||
* one MPU region (configured in usbd_conf.c) can mark them non-cacheable.
|
||
* Size must be a power-of-2 >= total size for MPU RASR SIZE encoding.
|
||
*/
|
||
static struct {
|
||
uint8_t tx[256];
|
||
uint8_t rx[256];
|
||
} __attribute__((aligned(512))) usb_nc_buf;
|
||
|
||
#define UserTxBuffer usb_nc_buf.tx
|
||
#define UserRxBuffer usb_nc_buf.rx
|
||
|
||
/* Exported so usbd_conf.c USB_NC_MPU_Config() can set the region base */
|
||
void * const usb_nc_buf_base = &usb_nc_buf;
|
||
|
||
/*
|
||
* Betaflight-proven DFU reboot:
|
||
* 1. Write magic to RTC backup register (persists across soft reset)
|
||
* 2. NVIC_SystemReset() — clean hardware reset
|
||
* 3. Early startup checks magic, clears it, jumps to system bootloader
|
||
*
|
||
* Magic is written to BKP15R (OTA_DFU_BKP_IDX) — not BKP0R — so that
|
||
* BKP0R–BKP6R are available for BNO055 calibration offsets (PR #150).
|
||
* The magic check in checkForBootloader() reads the same BKP15R register.
|
||
*/
|
||
static void request_bootloader(void) {
|
||
/* Betaflight-proven: write magic, disable IRQs, reset.
|
||
* checkForBootloader() runs on next boot before anything else. */
|
||
__HAL_RCC_PWR_CLK_ENABLE();
|
||
HAL_PWR_EnableBkUpAccess();
|
||
__HAL_RCC_RTC_ENABLE();
|
||
|
||
/* Write magic to BKP15R via OTA module constants (avoids BNO055 BKP0–6) */
|
||
(&RTC->BKP0R)[OTA_DFU_BKP_IDX] = OTA_DFU_MAGIC;
|
||
|
||
__disable_irq();
|
||
NVIC_SystemReset();
|
||
}
|
||
|
||
/*
|
||
* Call this VERY early in main(), before HAL_Init().
|
||
* Checks RTC backup register for magic value left by request_bootloader().
|
||
* If found: clear magic, jump to STM32F7 system bootloader at 0x1FF00000.
|
||
*/
|
||
void checkForBootloader(void) {
|
||
/*
|
||
* Betaflight-proven bootloader jump for STM32F7.
|
||
* Called VERY early, before HAL_Init/caches/clocks.
|
||
* At this point only RCC PWR is needed to read RTC backup regs.
|
||
*/
|
||
|
||
/* Enable backup domain access to read RTC backup register */
|
||
__HAL_RCC_PWR_CLK_ENABLE();
|
||
HAL_PWR_EnableBkUpAccess();
|
||
__HAL_RCC_RTC_ENABLE();
|
||
|
||
uint32_t magic = (&RTC->BKP0R)[OTA_DFU_BKP_IDX]; /* read BKP15R */
|
||
|
||
if (magic != OTA_DFU_MAGIC) {
|
||
return; /* Normal boot */
|
||
}
|
||
|
||
/* Clear magic so next boot is normal */
|
||
(&RTC->BKP0R)[OTA_DFU_BKP_IDX] = 0;
|
||
|
||
/* Jump to STM32F7 system bootloader at 0x1FF00000.
|
||
* Exactly as Betaflight does it — no cache/VTOR/MEMRMP games needed
|
||
* because we run before any of that is configured. */
|
||
|
||
__HAL_RCC_SYSCFG_CLK_ENABLE();
|
||
|
||
__set_MSP(*(uint32_t *)0x1FF00000);
|
||
((void (*)(void))(*(uint32_t *)0x1FF00004))();
|
||
|
||
while (1);
|
||
}
|
||
|
||
static int8_t CDC_Init(void) {
|
||
USBD_CDC_SetTxBuffer(&hUsbDevice, UserTxBuffer, 0);
|
||
USBD_CDC_SetRxBuffer(&hUsbDevice, UserRxBuffer);
|
||
USBD_CDC_ReceivePacket(&hUsbDevice);
|
||
|
||
/* Reset TxState so CDC_Transmit works after host (re)connects.
|
||
* Without this, if transmits happen before host opens port,
|
||
* TxState stays BUSY forever since host never ACKs. */
|
||
USBD_CDC_HandleTypeDef *hcdc = (USBD_CDC_HandleTypeDef *)hUsbDevice.pClassData;
|
||
if (hcdc) hcdc->TxState = 0;
|
||
|
||
return USBD_OK;
|
||
}
|
||
|
||
static int8_t CDC_DeInit(void) { return USBD_OK; }
|
||
|
||
static int8_t CDC_Control(uint8_t cmd, uint8_t *pbuf, uint16_t length) {
|
||
(void)pbuf; (void)length;
|
||
if (cmd == 0x22) { /* CDC_SET_CONTROL_LINE_STATE — host opened port */
|
||
cdc_port_open = 1;
|
||
cdc_streaming = 1;
|
||
}
|
||
return USBD_OK;
|
||
}
|
||
|
||
static int8_t CDC_Receive(uint8_t *buf, uint32_t *len) {
|
||
if (*len < 1) goto done;
|
||
|
||
switch (buf[0]) {
|
||
case 'S': cdc_streaming = !cdc_streaming; break;
|
||
case 'A': cdc_arm_request = 1; break;
|
||
case 'D': cdc_disarm_request = 1; break;
|
||
case 'G': cdc_recal_request = 1; break; /* gyro recalibration */
|
||
case 'O': cdc_imu_cal_request = 1; break; /* mount offset cal (Issue #680) */
|
||
case 'R': request_bootloader(); break; /* never returns */
|
||
|
||
case 'E': cdc_estop_request = 1; break;
|
||
case 'F': cdc_estop_request = 2; break;
|
||
case 'Z': cdc_estop_clear_request = 1; break;
|
||
|
||
/*
|
||
* PID tuning: P<kp> I<ki> D<kd> T<setpoint> M<max_speed> ?
|
||
* Copy to cmd buffer; main loop parses float (avoids sscanf in IRQ).
|
||
*/
|
||
case 'P': case 'I': case 'K': case 'T': case 'M': case '?': {
|
||
uint32_t copy_len = *len < 31 ? *len : 31;
|
||
for (uint32_t i = 0; i < copy_len; i++) cdc_cmd_buf[i] = (char)buf[i];
|
||
cdc_cmd_buf[copy_len] = '\0';
|
||
cdc_cmd_ready = 1;
|
||
break;
|
||
}
|
||
|
||
/* Jetson heartbeat — just refresh the tick, no buffer copy needed */
|
||
case 'H':
|
||
jetson_hb_tick = HAL_GetTick();
|
||
break;
|
||
|
||
/* Jetson drive command: C<speed>,<steer>\n
|
||
* Copy to buffer; main loop parses ints (keeps sscanf out of ISR). */
|
||
case 'C': {
|
||
uint32_t copy_len = *len < 31 ? *len : 31;
|
||
for (uint32_t i = 0; i < copy_len; i++) jetson_cmd_buf[i] = (char)buf[i];
|
||
jetson_cmd_buf[copy_len] = '\0';
|
||
jetson_hb_tick = HAL_GetTick(); /* C command also refreshes heartbeat */
|
||
jetson_cmd_ready = 1;
|
||
break;
|
||
}
|
||
|
||
default: break;
|
||
}
|
||
|
||
done:
|
||
cdc_rx_count++;
|
||
USBD_CDC_SetRxBuffer(&hUsbDevice, UserRxBuffer);
|
||
USBD_CDC_ReceivePacket(&hUsbDevice);
|
||
return USBD_OK;
|
||
}
|
||
|
||
USBD_CDC_ItfTypeDef USBD_CDC_fops = { CDC_Init, CDC_DeInit, CDC_Control, CDC_Receive };
|
||
|
||
uint8_t CDC_Transmit(uint8_t *buf, uint16_t len) {
|
||
USBD_CDC_HandleTypeDef *hcdc = (USBD_CDC_HandleTypeDef *)hUsbDevice.pClassData;
|
||
if (hcdc == NULL) return USBD_FAIL;
|
||
if (hcdc->TxState != 0) {
|
||
/* If stuck busy (no host ACK), force reset after a while */
|
||
static uint32_t busy_count = 0;
|
||
if (++busy_count > 100) { hcdc->TxState = 0; busy_count = 0; }
|
||
return USBD_BUSY;
|
||
}
|
||
/* Always copy into the static UserTxBuffer so the USB hardware reads
|
||
* from a known fixed SRAM address — never from the caller's stack.
|
||
* The USB TXFE IRQ fires asynchronously; a stack buffer could be
|
||
* overwritten by the time the FIFO is loaded. */
|
||
if (len > sizeof(UserTxBuffer)) len = sizeof(UserTxBuffer);
|
||
memcpy(UserTxBuffer, buf, len);
|
||
USBD_CDC_SetTxBuffer(&hUsbDevice, UserTxBuffer, len);
|
||
return USBD_CDC_TransmitPacket(&hUsbDevice);
|
||
}
|