/* * jetson_uart.c — USART6 command interface for Jetson Orin * * Mirrors the USB CDC command protocol over UART so the Jetson can * arm, disarm, heartbeat, drive, and e-stop via hardware UART. * This fixes the CDC TX bug (Issue USB_CDC_BUG.md) by providing * a reliable non-USB path. * * USART6: PC6=TX, PC7=RX @ 921600 baud (matches Orin ttyTHS1) * * Command protocol (same as CDC): * A — arm request * D — disarm request * E — emergency stop * Z — clear e-stop * H — heartbeat (refresh timeout) * C, — drive command: speed,steer (also refreshes heartbeat) * G — gyro recalibration */ #include "jetson_uart.h" #include "jetson_cmd.h" #include "config.h" #include "safety.h" #include "mpu6000.h" #include "stm32f7xx_hal.h" #include /* Shared flags — same ones CDC sets, main loop consumes */ extern volatile uint8_t cdc_arm_request; extern volatile uint8_t cdc_disarm_request; extern volatile uint8_t cdc_recal_request; extern volatile uint8_t cdc_estop_request; extern volatile uint8_t cdc_estop_clear_request; extern volatile uint8_t cdc_streaming; extern volatile char cdc_cmd_buf[32]; extern volatile uint8_t cdc_cmd_ready; /* From jetson_cmd.h */ extern volatile uint8_t jetson_cmd_ready; extern volatile char jetson_cmd_buf[32]; extern volatile uint32_t jetson_hb_tick; static UART_HandleTypeDef huart6; static uint8_t rx_byte; static char line_buf[64]; static uint8_t line_idx = 0; static void process_line(const char *buf, uint8_t len); /* Keep FC awake on Jetson activity */ extern void power_mgmt_activity(void); void jetson_uart_init(void) { __HAL_RCC_USART6_CLK_ENABLE(); __HAL_RCC_GPIOC_CLK_ENABLE(); /* PC6=TX, PC7=RX, AF8 for USART6 */ GPIO_InitTypeDef gpio = {0}; gpio.Pin = GPIO_PIN_6 | GPIO_PIN_7; gpio.Mode = GPIO_MODE_AF_PP; gpio.Pull = GPIO_PULLUP; gpio.Speed = GPIO_SPEED_FREQ_HIGH; gpio.Alternate = GPIO_AF8_USART6; HAL_GPIO_Init(GPIOC, &gpio); huart6.Instance = USART6; huart6.Init.BaudRate = 921600; huart6.Init.WordLength = UART_WORDLENGTH_8B; huart6.Init.StopBits = UART_STOPBITS_1; huart6.Init.Parity = UART_PARITY_NONE; huart6.Init.Mode = UART_MODE_TX_RX; huart6.Init.HwFlowCtl = UART_HWCONTROL_NONE; huart6.Init.OverSampling = UART_OVERSAMPLING_16; HAL_UART_Init(&huart6); HAL_NVIC_SetPriority(USART6_IRQn, 4, 0); HAL_NVIC_EnableIRQ(USART6_IRQn); /* Start interrupt-driven receive */ HAL_UART_Receive_IT(&huart6, &rx_byte, 1); /* Boot banner — confirms USART6 is alive */ const char *banner = "SALTYLAB USART6 OK\n"; HAL_UART_Transmit(&huart6, (const uint8_t *)banner, 19, 50); } /* Send telemetry/status back to Jetson */ void jetson_uart_send(const uint8_t *data, uint16_t len) { HAL_UART_Transmit(&huart6, (uint8_t *)data, len, 10); } /* ISR callback — accumulate bytes into line buffer, process on \n */ void jetson_uart_rx_callback(UART_HandleTypeDef *huart) { if (huart != &huart6) return; /* Any UART activity keeps the FC awake (prevents STOP mode) */ power_mgmt_activity(); if (rx_byte == '\n' || rx_byte == '\r') { if (line_idx > 0) { line_buf[line_idx] = '\0'; process_line(line_buf, line_idx); line_idx = 0; } } else if (line_idx < sizeof(line_buf) - 1) { line_buf[line_idx++] = (char)rx_byte; } else { line_idx = 0; /* overflow — reset */ } /* Re-arm receive */ HAL_UART_Receive_IT(&huart6, &rx_byte, 1); } /* * Process a complete line. Same command set as CDC_Receive. * Single-char commands work without newline too (for compatibility). */ static void process_line(const char *buf, uint8_t len) { if (len < 1) return; switch (buf[0]) { case 'A': cdc_arm_request = 1; /* Send arm attempt status for debugging */ { extern bool mpu6000_is_calibrated(void); extern float bal_pitch_deg_get(void); /* we'll add this */ char dbg[80]; int n = snprintf(dbg, sizeof(dbg), "ARM_REQ cal=%d estop=%d\n", mpu6000_is_calibrated() ? 1 : 0, safety_remote_estop_active() ? 1 : 0); HAL_UART_Transmit(&huart6, (uint8_t*)dbg, n, 10); } break; case 'D': cdc_disarm_request = 1; break; case 'G': cdc_recal_request = 1; break; case 'E': cdc_estop_request = 1; break; case 'F': cdc_estop_request = 2; break; case 'Z': cdc_estop_clear_request = 1; break; case 'S': cdc_streaming = !cdc_streaming; break; case 'H': jetson_hb_tick = HAL_GetTick(); break; case 'C': { uint8_t copy_len = len < 31 ? len : 31; for (uint8_t i = 0; i < copy_len; i++) jetson_cmd_buf[i] = buf[i]; jetson_cmd_buf[copy_len] = '\0'; jetson_hb_tick = HAL_GetTick(); jetson_cmd_ready = 1; break; } /* Direct motor test: W, — bypasses balance PID, * sends directly to ESC. For bench testing and diagnostics only. * Does NOT require arming. ESC watchdog stops motors if commands stop. */ case 'W': { int spd = 0, str = 0; if (len > 1 && sscanf(buf + 1, "%d,%d", &spd, &str) >= 1) { /* Clamp to safe bench test range */ if (spd > 100) spd = 100; if (spd < -100) spd = -100; if (str > 100) str = 100; if (str < -100) str = -100; /* Set persistent direct test globals — main loop sends at 50Hz */ extern volatile int16_t direct_test_speed; extern volatile int16_t direct_test_steer; direct_test_speed = (int16_t)spd; direct_test_steer = (int16_t)str; char ack[32]; int n = snprintf(ack, sizeof(ack), "W:%d,%d\n", spd, str); HAL_UART_Transmit(&huart6, (uint8_t*)ack, n, 10); } break; } #ifdef DEBUG_MOTOR_TEST /* GPIO test: reconfigure PC12 as plain GPIO output, set HIGH for 10s, then restore UART */ case 'X': { GPIO_InitTypeDef gpio = {0}; gpio.Pin = GPIO_PIN_12; gpio.Mode = GPIO_MODE_OUTPUT_PP; gpio.Pull = GPIO_NOPULL; gpio.Speed = GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOC, &gpio); HAL_GPIO_WritePin(GPIOC, GPIO_PIN_12, GPIO_PIN_SET); char msg[] = "PC12=HIGH 10s, measure T5 pad\n"; HAL_UART_Transmit(&huart6, (uint8_t*)msg, sizeof(msg)-1, 10); HAL_Delay(10000); /* Restore to UART AF */ gpio.Mode = GPIO_MODE_AF_PP; gpio.Pull = GPIO_PULLUP; gpio.Alternate = GPIO_AF8_UART5; HAL_GPIO_Init(GPIOC, &gpio); char done[] = "PC12 restored to UART5\n"; HAL_UART_Transmit(&huart6, (uint8_t*)done, sizeof(done)-1, 10); break; } /* Baud rate sweep test: try multiple baud rates on UART5, 3s each */ case 'R': { extern UART_HandleTypeDef huart2; uint16_t start = 0xABCD; int16_t steer = 0, speed = 50; uint16_t checksum = start ^ (uint16_t)steer ^ (uint16_t)speed; uint8_t pkt[8]; pkt[0] = start & 0xFF; pkt[1] = (start >> 8) & 0xFF; pkt[2] = steer & 0xFF; pkt[3] = (steer >> 8) & 0xFF; pkt[4] = speed & 0xFF; pkt[5] = (speed >> 8) & 0xFF; pkt[6] = checksum & 0xFF; pkt[7] = (checksum >> 8) & 0xFF; /* Also report current BRR and APB1 freq */ uint32_t brr = UART5->BRR; uint32_t apb1 = HAL_RCC_GetPCLK1Freq(); char info[80]; int n = snprintf(info, sizeof(info), "BRR=%lu APB1=%lu\n", (unsigned long)brr, (unsigned long)apb1); HAL_UART_Transmit(&huart6, (uint8_t*)info, n, 20); const uint32_t bauds[] = {115200, 38400, 9600, 19200, 57600, 230400}; for (int b = 0; b < 6; b++) { /* Reconfigure baud */ huart2.Init.BaudRate = bauds[b]; HAL_UART_Init(&huart2); char msg[40]; int mn = snprintf(msg, sizeof(msg), "BAUD %lu...\n", (unsigned long)bauds[b]); HAL_UART_Transmit(&huart6, (uint8_t*)msg, mn, 20); /* Send for 3 sec */ for (int i = 0; i < 150; i++) { HAL_UART_Transmit(&huart2, pkt, 8, 5); HAL_Delay(20); } /* Brief stop between bauds */ HAL_Delay(500); } /* Stop motors, restore 115200 */ speed = 0; checksum = start; pkt[4] = 0; pkt[5] = 0; pkt[6] = checksum & 0xFF; pkt[7] = (checksum >> 8) & 0xFF; huart2.Init.BaudRate = 115200; HAL_UART_Init(&huart2); for (int i = 0; i < 50; i++) { HAL_UART_Transmit(&huart2, pkt, 8, 5); HAL_Delay(20); } char ack[] = "R:done\n"; HAL_UART_Transmit(&huart6, (uint8_t*)ack, 7, 10); break; } #endif /* DEBUG_MOTOR_TEST */ case '?': { /* Status dump for debugging */ char st[128]; int n = snprintf(st, sizeof(st), "ST cal=%d estop=%d pitch=? hb=%lu\n", mpu6000_is_calibrated() ? 1 : 0, safety_remote_estop_active() ? 1 : 0, (unsigned long)jetson_hb_tick); HAL_UART_Transmit(&huart6, (uint8_t*)st, n, 20); break; } /* PID tuning commands */ case 'P': case 'I': case 'K': case 'T': case 'M': { uint8_t copy_len = len < 31 ? len : 31; for (uint8_t i = 0; i < copy_len; i++) cdc_cmd_buf[i] = buf[i]; cdc_cmd_buf[copy_len] = '\0'; cdc_cmd_ready = 1; break; } default: break; } } /* IRQ handler — call from stm32 interrupt vector */ void USART6_IRQHandler(void) { HAL_UART_IRQHandler(&huart6); }