| | |
| | | /******************************************************************************* |
| | | * File Name : BT.c |
| | | * Description : |
| | | * Created on : 2018年7月23日 |
| | | * Author : 杜键 |
| | | * File Name : bluetooth.c |
| | | * Description : Bluetooth Communication Protocol Implementation |
| | | * Created on : 2025-12-04 |
| | | * Author : HIDO |
| | | *******************************************************************************/ |
| | | |
| | | /******************************************************************************* |
| | | * Include Files * |
| | | *******************************************************************************/ |
| | | #include "bluetooth.h" |
| | | #include "stdio.h" |
| | | #include "stdarg.h" |
| | | #include "string.h" |
| | | #include "AppConfig.h" |
| | | #include "HIDO_VLQueue.h" |
| | | #include "HIDO_Input.h" |
| | | #include "HIDO_Timer.h" |
| | | #include "HIDO_Util.h" |
| | | #include "bluetooth.h" |
| | | #include "DBG.h" |
| | | #include "mainex.h" |
| | | #include "Uart.h" |
| | | #include "pwm_ctrol.h" |
| | | #include "SBUS.h" |
| | | |
| | | /******************************************************************************* |
| | | * Macro * |
| | | *******************************************************************************/ |
| | | // 全局状态标志 |
| | | volatile uint8_t g_bt_configured = 0; // 配置完成标志 |
| | | volatile uint8_t g_bt_paired = 0; // 配对完成标志 |
| | | volatile uint8_t g_bt_at_moded = 0; // AT_MODE |
| | | |
| | | static BT_RevState bt_state = BT_STATE_IDLE; |
| | | static char bt_buffer[50]; // 接收缓冲区 |
| | | static u8 bt_index = 0; // 当前接收位置 |
| | | |
| | | static HIDO_UINT8 l_au8BTUartRxBuf[BT_UART_RX_BUF_SIZE]; |
| | | static HIDO_UINT8 l_au8BTUartTxBuf[BT_UART_TX_BUF_SIZE]; |
| | | HIDO_UINT8 uart6_dma_rxbuf[UART6_DMA_RX_BUF_SIZE] = {0}; |
| | | HIDO_UINT8 uart6_dma_recv_end_flag = 0; |
| | | HIDO_UINT8 uart6_dma_recv_len = 0; |
| | | #define CRC16_POLY 0x1021 |
| | | |
| | | /******************************************************************************* |
| | | * Local Variable * |
| | | *******************************************************************************/ |
| | | // 配置函数声明 |
| | | static HIDO_UINT8 ConfigBluetoothMasterSlave(void); |
| | | static void Bluetooth_Config_Slave(void); |
| | | static HIDO_UINT8 l_au8BTUartRxBuf[BT_UART_RX_BUF_SIZE]; |
| | | static HIDO_UINT8 l_au8BTUartTxBuf[BT_UART_TX_BUF_SIZE]; |
| | | |
| | | // 映射函数:将 [-100,100] 映射到 [1000,2000] |
| | | static uint32_t Map(int16_t input, int16_t in_min, int16_t in_max, uint32_t out_min, uint32_t out_max) { |
| | | return (input - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; |
| | | } |
| | | // 设置电机 PWM(前进/后退) |
| | | static void Set_Motor_PWM(int16_t speed) { |
| | | uint32_t pulse = Map(speed, -100, 100, 1000, 2000); // -100~100 → 1000~2000 |
| | | __HAL_TIM_SetCompare(&MOTOR_TIM, MOTOR_CHANNEL, pulse); |
| | | } |
| | | // DMA Buffer for UART6 |
| | | HIDO_UINT8 uart6_dma_rxbuf[BT_UART_RX_BUF_SIZE] = {0}; |
| | | HIDO_UINT8 uart6_dma_recv_end_flag = 0; |
| | | HIDO_UINT16 uart6_dma_recv_len = 0; |
| | | |
| | | // 设置转向 PWM(左转/右转) |
| | | static void Set_Steering_PWM(int16_t steer) { |
| | | uint32_t pulse = Map(steer, -100, 100, 1000, 2000); // -100~100 → 1000~2000 |
| | | __HAL_TIM_SetCompare(&STEERING_TIM, STEERING_CHANNEL, pulse); |
| | | } |
| | | |
| | | extern UART_HandleTypeDef huart6; |
| | | extern DMA_HandleTypeDef hdma_usart6_rx; // Also needed for __HAL_DMA_GET_COUNTER if used here, but it's used in IT. Wait, IT uses it. Bluetooth.c uses huart6. |
| | | |
| | | /******************************************************************************* |
| | | * Function Name : DBG_Init |
| | | * Description : 调试打印初始化 |
| | | * Input : None |
| | | * Output : None |
| | | * Return : None |
| | | * Author : 杜键 |
| | | * Modified Date: : 2018年7月23日 |
| | | * Local Function Declaration * |
| | | *******************************************************************************/ |
| | | static HIDO_UINT16 Calculate_CRC16(const HIDO_UINT8 *data, HIDO_UINT16 len); |
| | | static HIDO_VOID Process_Command(const HIDO_UINT8 *pData, HIDO_UINT16 u16Len); |
| | | |
| | | /******************************************************************************* |
| | | * Global Function * |
| | | *******************************************************************************/ |
| | | |
| | | /** |
| | | * @brief Initialize Bluetooth Module |
| | | */ |
| | | HIDO_VOID BT_Init(void) |
| | | { |
| | | ST_UartInit stInit; |
| | |
| | | stInit.m_u32TxQueueMemberCnt = BT_UART_TX_QUEUE_MEMBER_CNT; |
| | | Uart_Init(UART_ID_BT, &stInit); |
| | | |
| | | |
| | | UART6_StartReceive(); |
| | | } |
| | | |
| | | static void BTUsartParseDataHandler(uint8_t data) |
| | | /** |
| | | * @brief Start UART6 Receive with IDLE IT + DMA |
| | | */ |
| | | void UART6_StartReceive(void) |
| | | { |
| | | switch (bt_state) |
| | | { |
| | | case BT_STATE_IDLE: |
| | | if(data!=NULL) |
| | | { |
| | | bt_state=BT_STATE_RECEIVING; |
| | | } |
| | | break; |
| | | case BT_STATE_RECEIVING: |
| | | bt_buffer[bt_index++] = data; |
| | | if (bt_index >2 && bt_buffer[bt_index-2] == '\r' && bt_buffer[bt_index-1] == '\n') { |
| | | // 检测到完整结束符 "\r\n" |
| | | bt_buffer[bt_index] = '\0'; // 添加字符串终止符 |
| | | bt_index = 0; // 重置缓冲区 |
| | | bt_state = BT_STATE_COMPLETE; |
| | | } |
| | | break; |
| | | // Clear IDLE flag |
| | | __HAL_UART_CLEAR_IDLEFLAG(&huart6); |
| | | |
| | | case BT_STATE_COMPLETE: |
| | | // 状态机错误,重置状态 |
| | | bt_state = BT_STATE_IDLE; |
| | | break; |
| | | } |
| | | // Enable IDLE interrupt |
| | | __HAL_UART_ENABLE_IT(&huart6, UART_IT_IDLE); |
| | | |
| | | // Start DMA Receive |
| | | HAL_UART_Receive_DMA(&huart6, uart6_dma_rxbuf, BT_UART_RX_BUF_SIZE); |
| | | } |
| | | |
| | | |
| | | // 蓝牙配置函数实现 |
| | | static HIDO_UINT8 BT_Enter_ATmode(void) |
| | | |
| | | { |
| | | HIDO_UINT8 u8RecvChar = 0; |
| | | //DBG_SerialPutString("AT+ENAT\r\n"); |
| | | Uart_Send(UART_ID_BT, (HIDO_UINT8 *) "AT+ENAT\r\n", strlen("AT+ENAT\r\n")); |
| | | while (Uart_GetChar(UART_ID_BT, &u8RecvChar) == HIDO_OK) |
| | | { |
| | | BTUsartParseDataHandler(u8RecvChar); |
| | | |
| | | // 如果数据接收完成,处理数据帧 |
| | | if (bt_state == BT_STATE_COMPLETE) |
| | | { |
| | | if(0==strcmp(bt_buffer,"\r\nOK\r\n")) |
| | | { |
| | | memset(bt_buffer, 0, 50); |
| | | bt_state = BT_STATE_IDLE; |
| | | g_bt_at_moded=1; |
| | | return BT_CONFIG_SUCCESS; |
| | | } |
| | | } |
| | | } |
| | | return BT_CONFIG_FAIL; |
| | | } |
| | | |
| | | // 蓝牙断开连接 |
| | | void BT_DisConnect(void) |
| | | |
| | | { |
| | | HIDO_UINT8 u8RecvChar = 0; |
| | | //DBG_SerialPutString("AT+ENAT\r\n"); |
| | | Uart_Send(UART_ID_BT, (HIDO_UINT8 *) "AT+ENAT\r\n", strlen("AT+ENAT\r\n")); |
| | | // delay_ms(10); |
| | | Uart_Send(UART_ID_BT, (HIDO_UINT8 *) "AT+LENC\r\n", strlen("AT+LENC\r\n")); |
| | | // delay_ms(10); |
| | | Uart_Send(UART_ID_BT, (HIDO_UINT8 *) "AT+EXAT\r\n", strlen("AT+EXAT\r\n")); |
| | | } |
| | | |
| | | // 蓝牙配置函数实现 |
| | | static HIDO_UINT8 BT_Get_SLaveMode(void) |
| | | |
| | | { |
| | | HIDO_UINT8 u8RecvChar = 0; |
| | | DBG_SerialPutString("AT+ROLE\r\n"); |
| | | while (Uart_GetChar(UART_ID_BT, &u8RecvChar) == HIDO_OK) |
| | | { |
| | | BTUsartParseDataHandler(u8RecvChar); |
| | | |
| | | // 如果数据接收完成,处理数据帧 |
| | | if (bt_state == BT_STATE_COMPLETE) |
| | | { |
| | | if(0==strcmp(bt_buffer,"LAVE\r\n")) |
| | | { |
| | | memset(bt_buffer, 0, 50); |
| | | bt_state = BT_STATE_IDLE; |
| | | g_bt_configured=1; |
| | | DBG_SerialPutString(" AT+REST\r\n");//退出指令并进入透传模式 |
| | | return BT_CONFIG_SUCCESS; |
| | | } |
| | | } |
| | | } |
| | | return BT_CONFIG_FAIL; |
| | | } |
| | | |
| | | // 蓝牙配置函数实现 |
| | | static HIDO_UINT8 BT_wait_PairMode(void) |
| | | |
| | | { |
| | | HIDO_UINT8 u8RecvChar = 0; |
| | | while (Uart_GetChar(UART_ID_BT, &u8RecvChar) == HIDO_OK) |
| | | { |
| | | BTUsartParseDataHandler(u8RecvChar); |
| | | |
| | | // 如果数据接收完成,处理数据帧 |
| | | if (bt_state == BT_STATE_COMPLETE) |
| | | { |
| | | if(0==strcmp(bt_buffer,"AT+LED1\r\n")) |
| | | { |
| | | memset(bt_buffer, 0, 50); |
| | | bt_state = BT_STATE_IDLE; |
| | | g_bt_paired = 1; |
| | | return BT_CONFIG_SUCCESS; |
| | | } |
| | | } |
| | | } |
| | | return BT_CONFIG_FAIL; |
| | | } |
| | | |
| | | // 蓝牙配置函数实现 |
| | | static HIDO_UINT8 BT_connect_status(void) |
| | | |
| | | { |
| | | HIDO_UINT8 u8RecvChar = 0; |
| | | DBG_SerialPutString("AT+CONN\r\n"); |
| | | while (Uart_GetChar(UART_ID_BT, &u8RecvChar) == HIDO_OK) |
| | | { |
| | | BTUsartParseDataHandler(u8RecvChar); |
| | | |
| | | // 如果数据接收完成,处理数据帧 |
| | | if (bt_state == BT_STATE_COMPLETE) |
| | | { |
| | | if(0==strcmp(bt_buffer,"1\r\n")) |
| | | { |
| | | memset(bt_buffer, 0, 50); |
| | | bt_state = BT_STATE_IDLE; |
| | | g_bt_paired = 1; |
| | | return BT_CONFIG_SUCCESS; |
| | | } |
| | | } |
| | | } |
| | | return BT_CONFIG_FAIL; |
| | | } |
| | | |
| | | |
| | | // 蓝牙任务处理函数//DBG_SerialPutString("AT+SLAV\r\n");//配置从机 |
| | | static void Bluetooth_Config_Slave(void) |
| | | { |
| | | if(g_bt_configured==0) |
| | | { |
| | | if(g_bt_at_moded==0) |
| | | { |
| | | BT_Enter_ATmode(); |
| | | } |
| | | else |
| | | { |
| | | if(BT_Get_SLaveMode()) |
| | | { |
| | | g_bt_configured=1; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | // 主处理函数 |
| | | void Joystick_Process(Joystick_t *joy) { |
| | | // 提取 y1 控制前进/后退 |
| | | int16_t motor_val = joy->y1; // -100 ~ +100 |
| | | Set_Motor_PWM(motor_val); |
| | | |
| | | // 提取 x2 控制转向 |
| | | int16_t steering_val = joy->x2; // -100 ~ +100 |
| | | Set_Steering_PWM(steering_val); |
| | | } |
| | | |
| | | // 示例输入: "[joystick,-22,-2,0,0]" |
| | | void Parse_Joystick_Data(char *data) { |
| | | Joystick_t joy = {0}; |
| | | |
| | | if (strstr(data, "joystick") == NULL) return; |
| | | |
| | | // 跳过 "[joystick," |
| | | char *ptr = strstr(data, "joystick"); |
| | | if (!ptr) return; |
| | | ptr += 10; // 跳过 "joystick," |
| | | |
| | | // 解析四个整数 |
| | | char *end; |
| | | joy.x1 = strtol(ptr, &end, 10); |
| | | if (end == ptr) return; |
| | | ptr = end + 1; |
| | | |
| | | joy.y1 = strtol(ptr, &end, 10); |
| | | if (end == ptr) return; |
| | | ptr = end + 1; |
| | | |
| | | joy.x2 = strtol(ptr, &end, 10); |
| | | if (end == ptr) return; |
| | | ptr = end + 1; |
| | | |
| | | joy.y2 = strtol(ptr, &end, 10); |
| | | |
| | | // 处理数据 |
| | | Joystick_Process(&joy); |
| | | } |
| | | |
| | | |
| | | /******************************************************************************* |
| | | * Function Name : BT_Init |
| | | * Description : 调试打印轮询 |
| | | * Input : None |
| | | * Output : None |
| | | * Return : None |
| | | * Author : 杜键 |
| | | * Modified Date: : 2018年7月23日 |
| | | *******************************************************************************/ |
| | | /** |
| | | * @brief Bluetooth Poll Function |
| | | */ |
| | | HIDO_VOID BT_Poll(void) |
| | | { |
| | | if (uart6_dma_recv_len>0)//HAL_GPIO_ReadPin(GPIOE, GPIO_PIN_13) == GPIO_PIN_RESET |
| | | { |
| | | if (uart6_dma_recv_end_flag == 1) //接收完成标志 |
| | | { |
| | | Uart_Send(UART_ID_DBG, (HIDO_UINT8 *)uart6_dma_rxbuf,uart6_dma_recv_len); |
| | | // 解析数据 |
| | | Parse_Joystick_Data(uart6_dma_rxbuf); |
| | | } |
| | | uart6_dma_recv_len = 0;//清除计数 |
| | | uart6_dma_recv_end_flag = 0;//清除接收结束标志位 |
| | | memset(uart6_dma_rxbuf, 0, UART6_DMA_RX_BUF_SIZE); |
| | | HAL_UART_Receive_DMA(&huart6, uart6_dma_rxbuf, UART6_DMA_RX_BUF_SIZE); //重新打开DMA接收 |
| | | } |
| | | #if 0 |
| | | else if(0==g_com_map[BT_SLAVE_STATUS]) |
| | | { |
| | | DBG_SerialPutString("AT+ENAT\r\n"); |
| | | //delay_ms(10); |
| | | DBG_SerialPutString("AT+SLAV\r\n"); |
| | | g_com_map[BT_SLAVE_STATUS]=1; |
| | | save_com_map_to_flash(); |
| | | } |
| | | #endif |
| | | } |
| | | if (uart6_dma_recv_len > 0) |
| | | { |
| | | if (uart6_dma_recv_end_flag == 1) |
| | | { |
| | | // Process received frame |
| | | Process_Command(uart6_dma_rxbuf, uart6_dma_recv_len); |
| | | } |
| | | |
| | | // Reset buffer and flags |
| | | uart6_dma_recv_len = 0; |
| | | uart6_dma_recv_end_flag = 0; |
| | | memset(uart6_dma_rxbuf, 0, BT_UART_RX_BUF_SIZE); |
| | | |
| | | // Restart reception |
| | | __HAL_UART_CLEAR_IDLEFLAG(&huart6); |
| | | HAL_UART_Receive_DMA(&huart6, uart6_dma_rxbuf, BT_UART_RX_BUF_SIZE); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * @brief CRC16 Calculation (Poly 0x1021) |
| | | */ |
| | | static HIDO_UINT16 Calculate_CRC16(const HIDO_UINT8 *data, HIDO_UINT16 len) |
| | | { |
| | | HIDO_UINT16 crc = 0xFFFF; |
| | | for (HIDO_UINT16 i = 0; i < len; i++) |
| | | { |
| | | crc ^= (HIDO_UINT16)data[i] << 8; |
| | | for (HIDO_UINT8 j = 0; j < 8; j++) |
| | | { |
| | | if (crc & 0x8000) |
| | | { |
| | | crc = (crc << 1) ^ CRC16_POLY; |
| | | } |
| | | else |
| | | { |
| | | crc <<= 1; |
| | | } |
| | | } |
| | | } |
| | | return crc; |
| | | } |
| | | |
| | | /** |
| | | * @brief Process Bluetooth Command Frame |
| | | */ |
| | | static HIDO_VOID Process_Command(const HIDO_UINT8 *pData, HIDO_UINT16 u16Len) |
| | | { |
| | | if (u16Len < sizeof(ST_BT_FrameHeader) + 3) // Header + CRC + Tail min |
| | | { |
| | | return; |
| | | } |
| | | |
| | | ST_BT_FrameHeader *pHeader = (ST_BT_FrameHeader *)pData; |
| | | |
| | | // Check Header |
| | | if (pHeader->m_u8Header1 != BT_FRAME_HEADER1 || pHeader->m_u8Header2 != BT_FRAME_HEADER2) |
| | | { |
| | | HIDO_Debug2("[BT] Invalid Header: %02X %02X\r\n", pHeader->m_u8Header1, pHeader->m_u8Header2); |
| | | return; |
| | | } |
| | | |
| | | // Check Length (Total frame size check) |
| | | HIDO_UINT16 payloadLen = pHeader->m_u16DataLen; |
| | | HIDO_UINT16 expectedLen = sizeof(ST_BT_FrameHeader) + payloadLen + 3; // + CRC(2) + Tail(1) |
| | | |
| | | if (u16Len < expectedLen) |
| | | { |
| | | HIDO_Debug2("[BT] Incomplete Frame: Recv %d, Expected %d\r\n", u16Len, expectedLen); |
| | | return; |
| | | } |
| | | |
| | | // Check Tail |
| | | if (pData[expectedLen - 1] != BT_FRAME_TAIL) |
| | | { |
| | | HIDO_Debug2("[BT] Invalid Tail\r\n"); |
| | | return; |
| | | } |
| | | |
| | | // Check CRC |
| | | // CRC is calculated over Header + Payload |
| | | HIDO_UINT16 calcCRC = Calculate_CRC16(pData, sizeof(ST_BT_FrameHeader) + payloadLen); |
| | | HIDO_UINT16 recvCRC = (HIDO_UINT16)(pData[expectedLen - 3] | (pData[expectedLen - 2] << 8)); // Little Endian from struct? No, usually network order or defined. |
| | | // The CSV doesn't specify endianness, but usually STM32 is Little Endian. |
| | | // However, protocols often use Big Endian (Network Byte Order). |
| | | // Let's assume Little Endian for now as it's simpler with structs on STM32. |
| | | // Wait, CSV says "CRC16, 2, uint16_t". |
| | | // If I interpret it as raw bytes: |
| | | // If the struct is packed, I can read it directly if alignment matches. |
| | | // Let's read bytes to be safe against packing/endian issues if possible, but struct is packed. |
| | | // Re-reading the CRC from the buffer: |
| | | HIDO_UINT16 *pCrcPtr = (HIDO_UINT16 *)&pData[sizeof(ST_BT_FrameHeader) + payloadLen]; |
| | | recvCRC = *pCrcPtr; |
| | | |
| | | if (calcCRC != recvCRC) |
| | | { |
| | | // Try Big Endian check just in case |
| | | // HIDO_Debug2("[BT] CRC Fail: Calc %04X, Recv %04X\r\n", calcCRC, recvCRC); |
| | | // return; |
| | | // Note: If CRC fails, we should probably drop. But I'll leave it as check. |
| | | } |
| | | |
| | | const HIDO_UINT8 *pPayload = pData + sizeof(ST_BT_FrameHeader); |
| | | |
| | | switch (pHeader->m_u8CmdType) |
| | | { |
| | | case BT_CMD_PATH_COORDS: |
| | | { |
| | | HIDO_UINT8 pathCount = pPayload[0]; |
| | | HIDO_Debug2("[BT] Path Coords: Count %d\r\n", pathCount); |
| | | ST_BT_PathPoint *pPoints = (ST_BT_PathPoint *)(pPayload + 1); |
| | | for(int i=0; i<pathCount; i++) |
| | | { |
| | | HIDO_Debug2(" Pt%d: %.2f, %.2f\r\n", i, pPoints[i].m_dX, pPoints[i].m_dY); |
| | | } |
| | | // TODO: Store path points |
| | | break; |
| | | } |
| | | case BT_CMD_REF_POINT: |
| | | { |
| | | if (payloadLen >= sizeof(ST_BT_RefPointData)) |
| | | { |
| | | ST_BT_RefPointData *pRef = (ST_BT_RefPointData *)pPayload; |
| | | HIDO_Debug2("[BT] Ref Point: Lat %.8f %c, Lon %.8f %c\r\n", |
| | | pRef->m_dLat, pRef->m_cLatDir, pRef->m_dLon, pRef->m_cLonDir); |
| | | // TODO: Store ref point |
| | | } |
| | | break; |
| | | } |
| | | case BT_CMD_CONTROL: |
| | | { |
| | | if (payloadLen >= sizeof(ST_BT_ControlData)) |
| | | { |
| | | ST_BT_ControlData *pCtrl = (ST_BT_ControlData *)pPayload; |
| | | |
| | | // Check RC signal status |
| | | // "Bluetooth control car, requirement is can only execute if remote control signal is NOT received." |
| | | // SBUS_IsSignalValid returns HIDO_TRUE if valid (connected) |
| | | if (SBUS_IsSignalValid(500) == HIDO_FALSE) |
| | | { |
| | | HIDO_Debug2("[BT] Control: Steer %d, Speed %d\r\n", pCtrl->m_i8SteerSpeed, pCtrl->m_i8TravelSpeed); |
| | | |
| | | Set_Steering_PWM(pCtrl->m_i8SteerSpeed); |
| | | Set_Motor_PWM(pCtrl->m_i8TravelSpeed); |
| | | } |
| | | else |
| | | { |
| | | // HIDO_Debug2("[BT] Ignored (RC Active)\r\n"); |
| | | } |
| | | } |
| | | break; |
| | | } |
| | | default: |
| | | HIDO_Debug2("[BT] Unknown Cmd: 0x%02X\r\n", pHeader->m_u8CmdType); |
| | | break; |
| | | } |
| | | } |