feat: 添加协议处理和校验和计算功能,更新 UART 相关代码

This commit is contained in:
2026-04-02 01:52:28 +08:00
parent 8d2a0ea0c8
commit a53aa38ed3
11 changed files with 390 additions and 40 deletions

View File

@@ -1,5 +1,9 @@
#include "bsp_uart.h"
#include "checksum.h"
#include "elog.h"
#include "protocol.h"
#include <stdint.h>
#include <string.h>
/**
* @brief 使用 UART1 和 DMA 发送数据到 ESP12F 模块(用于 TCP 透传)
@@ -7,28 +11,27 @@
* @return HAL_StatusTypeDef: HAL_OK 表示成功,其他值表示失败
*/
HAL_StatusTypeDef ESP12F_TCP_SendMessage(const char *data) {
if (data == NULL) {
return HAL_ERROR; // 参数无效
}
if (data == NULL) {
return HAL_ERROR; // 参数无效
}
// 计算数据大小
uint16_t size = strlen(data);
if (size == 0) {
return HAL_ERROR; // 数据为空
}
// 计算数据大小
uint16_t size = strlen(data);
if (size == 0) {
return HAL_ERROR; // 数据为空
}
// 启动 DMA 传输
return HAL_UART_Transmit_DMA(&huart1, (uint8_t *)data, size);
// 启动 DMA 传输
return HAL_UART_Transmit_DMA(&huart1, (uint8_t *)data, size);
}
/* External DMA handle */
extern DMA_HandleTypeDef hdma_usart1_rx;
/* DMA 接收缓冲区及状态变量 */
uint8_t uart1_rx_buf[UART1_RX_BUF_SIZE];
volatile uint16_t uart1_rx_len = 0; /* 记录最近一次接收到的数据包长度 */
volatile uint8_t uart1_rx_flag = 0; /* 接收完成标志位1表示有一帧新数据 */
uint8_t uart1_rx_buf[UART1_RX_BUF_SIZE];
volatile uint16_t uart1_rx_len = 0; /* 记录最近一次接收到的数据包长度 */
volatile uint8_t uart1_rx_flag = 0; /* 接收完成标志位1表示有一帧新数据 */
/**
* @brief 初始化 UART1 并启动 DMA 接收
@@ -38,14 +41,13 @@ volatile uint8_t uart1_rx_flag = 0; /* 接收完成标志位1表示
* @return 无
* @note 需要在调用此函数前确保 UART1 和 DMA 已经正确初始化
*/
void BSP_UART1_Init(void)
{
void BSP_UART1_Init(void) {
/* 停止并重置 DMA 状态 */
HAL_UART_DMAStop(&huart1);
HAL_UART_DMAStop(&huart1);
uart1_rx_len = 0;
uart1_rx_flag = 0;
memset(uart1_rx_buf, 0, sizeof(uart1_rx_buf));
/* 启动 DMA 接收,将接收到的数据存储到 uart1_rx_buf 缓冲区 */
HAL_UART_Receive_DMA(&huart1, uart1_rx_buf, UART1_RX_BUF_SIZE);
@@ -66,7 +68,7 @@ void UART_IDLE_Callback(UART_HandleTypeDef *huart) {
/* 清除空闲中断标志位HAL库宏 */
__HAL_UART_CLEAR_IDLEFLAG(huart);
/* 停止 DMA 以便安全读取计数器并更新状态 */
HAL_UART_DMAStop(huart);
@@ -75,13 +77,13 @@ void UART_IDLE_Callback(UART_HandleTypeDef *huart) {
if (recv_len > 0 && recv_len < UART1_RX_BUF_SIZE) {
uart1_rx_len = recv_len;
uart1_rx_buf[recv_len] = '\0'; /* 添加字符串结束符,方便后续字符串处理 */
uart1_rx_flag = 1; /* 置位标志,通知应用层新消息到达 */
/* 仅供调试在中断中打印接收到的数据注意printf 可能会影响实时性) */
elog_raw("UART1 Received: %s\r\n", (char *)uart1_rx_buf);
uart1_rx_buf[recv_len] = '\0'; /* 添加字符串结束符,方便后续字符串处理 */
uart1_rx_flag = 1; /* 置位标志,通知应用层新消息到达 */
// 调用协议处理函数,通过消息队列发送接收到的数据
Protocol_HandleMessage(uart1_rx_buf, recv_len);
}
/* 重新启动新一轮的 DMA 接收 */
HAL_UART_Receive_DMA(&huart1, uart1_rx_buf, UART1_RX_BUF_SIZE);
}
}

24
Core/Bsp/checksum.c Normal file
View File

@@ -0,0 +1,24 @@
#include <stdint.h>
/**
* @brief 计算校验和
* @param buf: 数据缓冲区指针 (即你的 uart1_rx_buf)
* @param start_pos: 校验数据的起始下标 (通常跳过帧头 "LOGI:")
* @param length: 需要校验的数据长度 (不包含校验位本身和帧尾)
* @return uint8_t: 计算得出的校验和
*
* 算法说明: 将所有字节相加取低8位 (相当于 % 256)
*/
uint8_t Calculate_CheckSum(uint8_t *buf, uint16_t start_pos, uint16_t length)
{
uint32_t sum = 0; // 使用32位防止累加溢出虽然uint8累加也不会溢出单片机寄存器
uint16_t i;
for (i = 0; i < length; i++)
{
sum += buf[start_pos + i];
}
// 返回低8位相当于 sum % 256
return (uint8_t)(sum & 0xFF);
}

17
Core/Bsp/checksum.h Normal file
View File

@@ -0,0 +1,17 @@
#ifndef CHECKSUM_H
#define CHECKSUM_H
#include <stdint.h>
/**
* @brief 计算校验和
* @param buf: 数据缓冲区指针 (即你的 uart1_rx_buf)
* @param start_pos: 校验数据的起始下标 (通常跳过帧头 "LOGI:")
* @param length: 需要校验的数据长度 (不包含校验位本身和帧尾)
* @return uint8_t: 计算得出的校验和
*
* 算法说明: 将所有字节相加取低8位 (相当于 % 256)
*/
uint8_t Calculate_CheckSum(uint8_t *buf, uint16_t start_pos, uint16_t length);
#endif // CHECKSUM_H

126
Core/Bsp/protocol.c Normal file
View File

@@ -0,0 +1,126 @@
/**
* @file protocol.c
* @brief 协议处理函数实现
* @details 该文件包含了协议解析和处理的相关函数,主要用于处理从 ESP12F
* 模块接收到的数据。
* @author Beihong Wang
* @date 2026-04-01
*/
#include "protocol.h"
#include "bsp_uart.h"
#include "checksum.h"
#include "cmsis_os.h"
#include "elog.h"
#include <stdio.h>
#include <string.h>
/* 定义日志 TAG */
#define Protocol_TAG "Protocol"
/* 引用在 freertos.c 中定义的消息队列句柄 */
extern osMessageQueueId_t CmdQueueHandle;
/**
* @brief 协议处理函数
* @details 严格按照协议文档:校验范围 = 帧头 + 命令 + 数据
* 即从下标 0 开始,一直加到最后一个冒号之前
*/
void Protocol_HandleMessage(uint8_t *data, uint16_t len) {
if (data == NULL)
return;
// 1. 基础检查:长度必须足够,且必须以 '#' 结尾
if (len < 10 || data[len - 1] != '#') {
elog_w(Protocol_TAG, "协议错误:长度不足或帧尾错误 (len: %d)", len);
return;
}
// 2. 寻找校验位前的分隔符
// 协议格式LOGI:CMD:DATA:CS#
// 我们需要找到最后一个冒号 ':' 的位置,它前面是数据,后面是校验位
int last_colon_pos = -1;
for (int i = 0; i < len; i++) {
if (data[i] == ':') {
last_colon_pos = i;
}
}
// 如果找不到冒号,说明格式错误
if (last_colon_pos == -1 || last_colon_pos < 5) {
elog_w(Protocol_TAG, "协议错误:找不到分隔符 ':' 或位置非法");
return;
}
// 3. 提取接收到的校验位 (从 ASCII 转为 Hex 数值)
// 校验位紧跟在 last_colon_pos 之后,长度为 2 字节
char recv_cs_hex_str[3] = {0};
// 防止越界
if (last_colon_pos + 3 >= len) {
elog_w(Protocol_TAG, "协议错误:校验位数据越界");
return;
}
recv_cs_hex_str[0] = data[last_colon_pos + 1];
recv_cs_hex_str[1] = data[last_colon_pos + 2];
unsigned int received_checksum = 0;
sscanf(recv_cs_hex_str, "%02X", &received_checksum);
// 4. 计算本地校验和
// 【核心修改点】
// 严格按照协议文档:从下标 0 开始,长度为 last_colon_pos
// 也就是计算 "LOGI:SP:080" 的累加和
uint8_t calculated_checksum =
Calculate_CheckSum(data, 0, (uint16_t)last_colon_pos);
// 5. 对比校验和
if (calculated_checksum == (uint8_t)received_checksum) {
elog_i(Protocol_TAG, "✅ 校验通过!执行指令: %s", (char *)data);
/* 提取有效载荷发送到消息队列 */
char cmd_payload[16] = {0};
// 将 "LOGI:" 之后到最后一个冒号之前的内容作为指令
uint16_t payload_len = last_colon_pos - 5;
uint16_t copy_len = (payload_len > 15) ? 15 : payload_len;
if (copy_len > 0) {
memcpy(cmd_payload, &data[5], copy_len);
}
osStatus_t status = osMessageQueuePut(CmdQueueHandle, cmd_payload, 0, 0);
if (status != osOK) {
elog_e(Protocol_TAG, "Protocol: Queue put failed: %d", status);
}
} else {
elog_w(Protocol_TAG, "❌ 校验失败!计算值: 0x%02X, 接收值: 0x%02X",
calculated_checksum, (uint8_t)received_checksum);
// 辅助调试:打印实际参与计算的数据段
char debug_buf[32] = {0};
if (last_colon_pos < 32) {
memcpy(debug_buf, data, last_colon_pos);
elog_i(Protocol_TAG, " -> 单片机正在计算这段数据的校验和: [%s]",
debug_buf);
elog_i(Protocol_TAG, " -> 请检查上位机是否也是按照此范围计算累加和");
}
}
}
#define CarCtrlTask_TAG "CarCtrlTask"
void CarCtrl_Task(void *argument) {
/* USER CODE BEGIN CarCtrl_Task */
char cmd_payload[16];
/* Infinite loop */
for (;;) {
/* 从消息队列中获取数据,阻塞等待 */
if (osMessageQueueGet(CmdQueueHandle, cmd_payload, NULL, osWaitForever) ==
osOK) {
elog_i(CarCtrlTask_TAG, "CarCtrl: Received command (ASCII: %s) from queue",
cmd_payload);
/* 可以在这里添加根据 cmd_payload 控制小车的逻辑 */
}
// osDelay(1); // osMessageQueueGet 已经是阻塞的,不需要额外的 osDelay
}
/* USER CODE END CarCtrl_Task */
}

16
Core/Bsp/protocol.h Normal file
View File

@@ -0,0 +1,16 @@
#ifndef PROTOCOL_H
#define PROTOCOL_H
#include "main.h"
/**
* @brief 协议处理函数,解析接收到的数据并通过消息队列发送命令
* @param data: 接收到的原始数据指针
* @param len: 数据长度
*/
void Protocol_HandleMessage(uint8_t *data, uint16_t len);
void CarCtrl_Task(void *argument);
#endif /* PROTOCOL_H */

View File

@@ -29,6 +29,7 @@
#include "bsp_beep.h"
#include "usart.h"
#include "elog.h"
#include "protocol.h"
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
@@ -57,6 +58,18 @@ const osThreadAttr_t initTask_attributes = {
.stack_size = 128 * 4,
.priority = (osPriority_t) osPriorityNormal,
};
/* Definitions for CarCtrlTask */
osThreadId_t CarCtrlTaskHandle;
const osThreadAttr_t CarCtrlTask_attributes = {
.name = "CarCtrlTask",
.stack_size = 256 * 4,
.priority = (osPriority_t) osPriorityNormal,
};
/* Definitions for CmdQueue */
osMessageQueueId_t CmdQueueHandle;
const osMessageQueueAttr_t CmdQueue_attributes = {
.name = "CmdQueue"
};
/* Private function prototypes -----------------------------------------------*/
/* USER CODE BEGIN FunctionPrototypes */
@@ -73,6 +86,7 @@ PUTCHAR_PROTOTYPE {
/* USER CODE END FunctionPrototypes */
void StartDefaultTask(void *argument);
void CarCtrl_Task(void *argument);
void MX_FREERTOS_Init(void); /* (MISRA C 2004 rule 8.1) */
@@ -103,6 +117,10 @@ void MX_FREERTOS_Init(void) {
/* start timers, add new ones, ... */
/* USER CODE END RTOS_TIMERS */
/* Create the queue(s) */
/* creation of CmdQueue */
CmdQueueHandle = osMessageQueueNew (16, 16, &CmdQueue_attributes);
/* USER CODE BEGIN RTOS_QUEUES */
/* add queues, ... */
/* USER CODE END RTOS_QUEUES */
@@ -111,6 +129,9 @@ void MX_FREERTOS_Init(void) {
/* creation of initTask */
initTaskHandle = osThreadNew(StartDefaultTask, NULL, &initTask_attributes);
/* creation of CarCtrlTask */
CarCtrlTaskHandle = osThreadNew(CarCtrl_Task, NULL, &CarCtrlTask_attributes);
/* USER CODE BEGIN RTOS_THREADS */
/* add threads, ... */
/* USER CODE END RTOS_THREADS */
@@ -134,24 +155,35 @@ void StartDefaultTask(void *argument)
/* Infinite loop */
for(;;)
{
const char *message = "Hello, ESP12F! This is a test message.";
HAL_StatusTypeDef status = ESP12F_TCP_SendMessage(message);
if (status == HAL_OK) {
HAL_GPIO_WritePin(RUN_LED_GPIO_Port, RUN_LED_Pin, GPIO_PIN_SET);
BEEP_On();
osDelay(50);
BEEP_Off();
osDelay(20);
} else {
HAL_GPIO_WritePin(RUN_LED_GPIO_Port, RUN_LED_Pin, GPIO_PIN_RESET);
}
HAL_GPIO_TogglePin(RUN_LED_GPIO_Port, RUN_LED_Pin);
osDelay(1000);
}
/* USER CODE END StartDefaultTask */
}
/* USER CODE BEGIN Header_CarCtrl_Task */
/**
* @brief Function implementing the CarCtrlTask thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_CarCtrl_Task */
__weak void CarCtrl_Task(void *argument)
{
/* USER CODE BEGIN CarCtrl_Task */
/* Infinite loop */
for(;;)
{
osDelay(1); // osMessageQueueGet 已经是阻塞的,不需要额外的 osDelay
}
/* USER CODE END CarCtrl_Task */
}
/* Private application code --------------------------------------------------*/
/* USER CODE BEGIN Application */