/*******************************************************************************
|
* 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_Util.h"
|
#include "DBG.h"
|
#include "Uart.h"
|
#include "pwm_ctrol.h"
|
#include "SBUS.h"
|
|
/*******************************************************************************
|
* Macro *
|
*******************************************************************************/
|
#define CRC16_POLY 0x1021
|
#define BT_RX_BUF_SIZE 128 // Internal parsing buffer size
|
#define ENABLE_BT_DEBUG_LOG 0 // Set to 1 to enable debug logs
|
|
/*******************************************************************************
|
* Local Variable *
|
*******************************************************************************/
|
static HIDO_UINT8 l_au8BTUartRxBuf[BT_UART_RX_BUF_SIZE];
|
static HIDO_UINT8 l_au8BTUartTxBuf[BT_UART_TX_BUF_SIZE];
|
|
// DMA Buffer for UART6 (aligned for D-Cache operations)
|
__attribute__((aligned(32))) 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;
|
volatile HIDO_UINT32 g_u32BtIdleIntCount = 0;
|
|
extern UART_HandleTypeDef huart6;
|
extern DMA_HandleTypeDef hdma_usart6_rx;
|
|
// Receive State Machine
|
typedef enum
|
{
|
BT_FSM_IDLE = 0,
|
BT_FSM_HEADER2,
|
BT_FSM_CMD,
|
BT_FSM_LEN_LOW,
|
BT_FSM_LEN_HIGH,
|
BT_FSM_DATA,
|
BT_FSM_TAIL
|
} E_BT_FSM_State;
|
|
typedef struct
|
{
|
E_BT_FSM_State state;
|
HIDO_UINT8 cmd_type;
|
HIDO_UINT16 data_len;
|
HIDO_UINT16 data_idx;
|
HIDO_UINT8 rx_buf[BT_RX_BUF_SIZE];
|
} ST_BT_RecvFSM;
|
|
static ST_BT_RecvFSM s_bt_fsm;
|
|
/*******************************************************************************
|
* Local Function Declaration *
|
*******************************************************************************/
|
static HIDO_UINT16 Calculate_CRC16(const HIDO_UINT8 *data, HIDO_UINT16 len);
|
static HIDO_VOID BT_RecvFSM(HIDO_UINT8 byte);
|
static HIDO_VOID BT_ProcessFrame(void);
|
|
/*******************************************************************************
|
* Global Function *
|
*******************************************************************************/
|
|
/**
|
* @brief Initialize Bluetooth Module
|
*/
|
HIDO_VOID BT_Init(void)
|
{
|
ST_UartInit stInit;
|
|
memset(&stInit, 0, sizeof(stInit));
|
stInit.m_eRxMode = UART_RX_MODE_DMA;
|
stInit.m_eTxMode = UART_TX_MODE_DMA;
|
stInit.m_pu8RxBuf = l_au8BTUartRxBuf;
|
stInit.m_u32RxBufSize = BT_UART_RX_BUF_SIZE;
|
stInit.m_pu8TxBuf = l_au8BTUartTxBuf;
|
stInit.m_u32TxBufSize = BT_UART_TX_BUF_SIZE;
|
stInit.m_u32TxQueueMemberCnt = BT_UART_TX_QUEUE_MEMBER_CNT;
|
Uart_Init(UART_ID_BT, &stInit);
|
Uart_ReConfigBaudRate(UART_ID_BT, 115200);
|
UART6_StartReceive();
|
}
|
|
/**
|
* @brief Start UART6 Receive with Circular DMA
|
*/
|
void UART6_StartReceive(void)
|
{
|
// Clear IDLE flag
|
__HAL_UART_CLEAR_IDLEFLAG(&huart6);
|
|
// IDLE interrupt is optional for Circular DMA with GetChar polling
|
// Uncomment if you want IDLE interrupt as a hint (not required for functionality)
|
// __HAL_UART_ENABLE_IT(&huart6, UART_IT_IDLE);
|
|
// Start DMA Receive (Circular Mode configured in HAL_MSP)
|
HAL_UART_Receive_DMA(&huart6, uart6_dma_rxbuf, BT_UART_RX_BUF_SIZE);
|
}
|
|
/**
|
* @brief Bluetooth Poll Function (Circular DMA Mode with GetChar)
|
*/
|
HIDO_VOID BT_Poll(void)
|
{
|
static HIDO_UINT32 s_last_poll_tick = 0;
|
|
// Periodic debug log
|
if (HAL_GetTick() - s_last_poll_tick > 1000)
|
{
|
s_last_poll_tick = HAL_GetTick();
|
#if ENABLE_BT_DEBUG_LOG
|
HIDO_UINT32 dma_cnt = 0;
|
if (huart6.hdmarx != NULL)
|
{
|
dma_cnt = __HAL_DMA_GET_COUNTER(huart6.hdmarx);
|
}
|
HIDO_UINT32 cr1 = huart6.Instance->CR1;
|
HIDO_UINT32 isr = huart6.Instance->ISR;
|
HIDO_Debug2("[BT] Poll: IntCnt=%u, DMA_CNDTR=%u, CR1=0x%X, ISR=0x%X\r\n",
|
g_u32BtIdleIntCount, dma_cnt, cr1, isr);
|
#endif
|
}
|
|
// Read and process bytes from circular DMA buffer
|
HIDO_UINT8 byte = 0;
|
while (Uart_GetChar(UART_ID_BT, &byte) == HIDO_OK)
|
{
|
BT_RecvFSM(byte);
|
}
|
}
|
|
/**
|
* @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 a complete BT frame (called after FSM receives full frame)
|
*/
|
static HIDO_VOID BT_ProcessFrame(void)
|
{
|
// Frame is stored in s_bt_fsm.rx_buf
|
// Structure: Header(5) + SeqNum(2) + Payload(4) + CRC(2) + Tail(1) = 14 bytes total
|
// rx_buf[0..4]: Header (AA 55 Cmd Len_L Len_H)
|
// rx_buf[5..6]: SeqNum
|
// rx_buf[7..10]: Payload (Steer + Speed + Reserved)
|
// rx_buf[11..12]: CRC
|
// rx_buf[13]: Tail
|
|
HIDO_UINT8 *pBuf = s_bt_fsm.rx_buf;
|
HIDO_UINT16 total_len = 5 + s_bt_fsm.data_len; // Header(5) + Data(SeqNum + Payload + CRC + Tail)
|
|
if (total_len < 14 || total_len > BT_RX_BUF_SIZE)
|
{
|
#if ENABLE_BT_DEBUG_LOG
|
HIDO_Debug2("[BT] Invalid frame length: %u\r\n", total_len);
|
#endif
|
return;
|
}
|
|
#if ENABLE_BT_DEBUG_LOG
|
// Print entire frame in hex for debugging
|
HIDO_Debug2("[BT] Frame (%u bytes): ", total_len);
|
for (HIDO_UINT16 i = 0; i < total_len; i++)
|
{
|
HIDO_Debug2("%02X ", pBuf[i]);
|
}
|
HIDO_Debug2("\r\n");
|
#endif
|
|
// Check CRC - Try different ranges to find the correct one
|
HIDO_UINT16 crc_calc_len = total_len - 3; // Current: Header + SeqNum + Payload (11 bytes)
|
HIDO_UINT16 calc_crc1 = Calculate_CRC16(pBuf, crc_calc_len); // Full: AA 55 03 09 00 67 00 00 0A 00 00
|
HIDO_UINT16 calc_crc2 = Calculate_CRC16(pBuf + 3, crc_calc_len - 3); // From DataLen: 09 00 67 00 00 0A 00 00
|
HIDO_UINT16 calc_crc3 = Calculate_CRC16(pBuf + 5, crc_calc_len - 5); // From SeqNum: 67 00 00 0A 00 00
|
|
HIDO_UINT8 crc_low = pBuf[crc_calc_len];
|
HIDO_UINT8 crc_high = pBuf[crc_calc_len + 1];
|
HIDO_UINT16 recv_crc_le = (HIDO_UINT16)(crc_low | (crc_high << 8)); // Little Endian
|
HIDO_UINT16 recv_crc_be = (HIDO_UINT16)((crc_low << 8) | crc_high); // Big Endian
|
|
#if ENABLE_BT_DEBUG_LOG
|
HIDO_Debug2("[BT] CRC Test: Recv(LE)=%04X, Recv(BE)=%04X\r\n", recv_crc_le, recv_crc_be);
|
HIDO_Debug2(" Calc1(Full,11B)=%04X, Calc2(FromLen,8B)=%04X, Calc3(FromSeq,6B)=%04X\r\n",
|
calc_crc1, calc_crc2, calc_crc3);
|
#endif
|
|
// Use calc_crc1 for now (original logic)
|
if (calc_crc1 != recv_crc_le && calc_crc1 != recv_crc_be)
|
{
|
#if ENABLE_BT_DEBUG_LOG
|
HIDO_Debug2("[BT] CRC Fail (all methods)\r\n");
|
#endif
|
// Continue processing for debug
|
// return;
|
}
|
|
// Check Tail
|
HIDO_UINT8 tail = pBuf[total_len - 1];
|
if (tail != BT_FRAME_TAIL)
|
{
|
#if ENABLE_BT_DEBUG_LOG
|
HIDO_Debug2("[BT] Invalid Tail: %02X\r\n", tail);
|
#endif
|
return;
|
}
|
|
// Parse command based on type
|
switch (s_bt_fsm.cmd_type)
|
{
|
case BT_CMD_CONTROL:
|
{
|
// Payload starts at offset 7 (after Header(5) + SeqNum(2))
|
ST_BT_ControlData *pCtrl = (ST_BT_ControlData *)&pBuf[7];
|
|
// Check RC signal status
|
if (SBUS_IsSignalValid(500) == HIDO_FALSE)
|
{
|
#if ENABLE_BT_DEBUG_LOG
|
HIDO_Debug2("[BT] Control: Steer %d, Speed %d\r\n", pCtrl->m_i8SteerSpeed, pCtrl->m_i8TravelSpeed);
|
#endif
|
|
Set_Steering_PWM(pCtrl->m_i8SteerSpeed);
|
Set_Motor_PWM(pCtrl->m_i8TravelSpeed);
|
}
|
else
|
{
|
// HIDO_Debug2("[BT] Ignored (RC Active)\r\n");
|
}
|
break;
|
}
|
default:
|
#if ENABLE_BT_DEBUG_LOG
|
HIDO_Debug2("[BT] Unknown Cmd: 0x%02X\r\n", s_bt_fsm.cmd_type);
|
#endif
|
break;
|
}
|
}
|
|
/**
|
* @brief Receive State Machine (called for each byte)
|
*/
|
static HIDO_VOID BT_RecvFSM(HIDO_UINT8 byte)
|
{
|
switch (s_bt_fsm.state)
|
{
|
case BT_FSM_IDLE:
|
if (byte == BT_FRAME_HEADER1) // 0xAA
|
{
|
s_bt_fsm.rx_buf[0] = byte;
|
s_bt_fsm.data_idx = 1;
|
s_bt_fsm.state = BT_FSM_HEADER2;
|
}
|
break;
|
|
case BT_FSM_HEADER2:
|
if (byte == BT_FRAME_HEADER2) // 0x55
|
{
|
s_bt_fsm.rx_buf[1] = byte;
|
s_bt_fsm.data_idx = 2;
|
s_bt_fsm.state = BT_FSM_CMD;
|
}
|
else
|
{
|
s_bt_fsm.state = BT_FSM_IDLE;
|
}
|
break;
|
|
case BT_FSM_CMD:
|
s_bt_fsm.rx_buf[2] = byte;
|
s_bt_fsm.cmd_type = byte;
|
s_bt_fsm.data_idx = 3;
|
s_bt_fsm.state = BT_FSM_LEN_LOW;
|
break;
|
|
case BT_FSM_LEN_LOW:
|
s_bt_fsm.rx_buf[3] = byte;
|
s_bt_fsm.data_len = byte; // Low byte first
|
s_bt_fsm.data_idx = 4;
|
s_bt_fsm.state = BT_FSM_LEN_HIGH;
|
break;
|
|
case BT_FSM_LEN_HIGH:
|
s_bt_fsm.rx_buf[4] = byte;
|
s_bt_fsm.data_len |= (HIDO_UINT16)byte << 8; // High byte
|
s_bt_fsm.data_idx = 5;
|
|
// Sanity check on length
|
if (s_bt_fsm.data_len > (BT_RX_BUF_SIZE - 5))
|
{
|
#if ENABLE_BT_DEBUG_LOG
|
HIDO_Debug2("[BT] FSM: Length too large (%u), resetting\r\n", s_bt_fsm.data_len);
|
#endif
|
s_bt_fsm.state = BT_FSM_IDLE;
|
}
|
else
|
{
|
s_bt_fsm.state = BT_FSM_DATA;
|
}
|
break;
|
|
case BT_FSM_DATA:
|
s_bt_fsm.rx_buf[s_bt_fsm.data_idx++] = byte;
|
|
// Check if we've received all data bytes
|
// data_len includes: SeqNum(2) + Payload(N) + CRC(2) + Tail(1)
|
// Total frame = Header(5) + data_len
|
// We've already stored Header(5), now need data_len more bytes
|
if (s_bt_fsm.data_idx >= (5 + s_bt_fsm.data_len))
|
{
|
// Frame complete, process it
|
BT_ProcessFrame();
|
s_bt_fsm.state = BT_FSM_IDLE;
|
}
|
|
// Safety check: prevent buffer overflow
|
if (s_bt_fsm.data_idx >= BT_RX_BUF_SIZE)
|
{
|
#if ENABLE_BT_DEBUG_LOG
|
HIDO_Debug2("[BT] FSM: Buffer overflow, resetting\r\n");
|
#endif
|
s_bt_fsm.state = BT_FSM_IDLE;
|
}
|
break;
|
|
default:
|
s_bt_fsm.state = BT_FSM_IDLE;
|
break;
|
}
|
}
|
|
/**
|
* @brief Process Bluetooth Command Frame (Legacy, kept for reference)
|
*/
|
static HIDO_VOID Process_Command(const HIDO_UINT8 *pData, HIDO_UINT16 u16Len)
|
{
|
// HIDO_Debug2("[BT] Processing %u bytes\r\n", 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)
|
// Protocol Definition: DataLen (9) includes Seq(2) + Payload(4) + CRC(2) + Tail(1)
|
// Frame Structure: Header(5) + DataLen(9) = 14 bytes
|
// ST_BT_FrameHeader(7) includes SeqNum(2) at the end.
|
// So HeaderBase (AA 55 Cmd Len) is 5 bytes.
|
HIDO_UINT16 headerBaseSize = 5;
|
HIDO_UINT16 declaredLen = pHeader->m_u16DataLen;
|
HIDO_UINT16 expectedLen = headerBaseSize + declaredLen;
|
|
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: %02X\r\n", pData[expectedLen - 1]);
|
return;
|
}
|
|
// Check CRC
|
// CRC is calculated over Header + Payload (excluding CRC and Tail)
|
// Range: 0 to (TotalLen - CRC(2) - Tail(1)) = TotalLen - 3
|
HIDO_UINT16 calcLen = expectedLen - 3;
|
HIDO_UINT16 calcCRC = Calculate_CRC16(pData, calcLen);
|
|
// CRC is at offset: expectedLen - 3 (Low byte), expectedLen - 2 (High byte) - assuming Little Endian in stream
|
// User Example: ... C3 7B 0D. CRC is C3 7B. Tail 0D.
|
// 7B is at -2, C3 is at -3.
|
// If recvCRC is uint16, let's read it.
|
HIDO_UINT8 crcLow = pData[expectedLen - 3];
|
HIDO_UINT8 crcHigh = pData[expectedLen - 2];
|
HIDO_UINT16 recvCRC = (HIDO_UINT16)(crcLow | (crcHigh << 8));
|
|
if (calcCRC != recvCRC)
|
{
|
HIDO_Debug2("[BT] CRC Fail: Calc %04X, Recv %04X\r\n", calcCRC, recvCRC);
|
// Continue processing for debugging, but typically should return
|
// return;
|
}
|
|
// Payload starts after SeqNum.
|
// ST_BT_FrameHeader includes SeqNum. sizeof is 7.
|
// So pPayload points to Data after SeqNum.
|
const HIDO_UINT8 *pPayload = pData + sizeof(ST_BT_FrameHeader);
|
|
// Payload length for Command logic (excluding Seq, CRC, Tail)
|
// DataLen(9) - Seq(2) - CRC(2) - Tail(1) = 4 bytes.
|
HIDO_INT16 realPayloadLen = declaredLen - 2 - 3;
|
|
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 (realPayloadLen >= 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 (realPayloadLen >= 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;
|
}
|
}
|