当前位置: 首页 > news >正文

七、基于HAL库,实现串口+DMA+状态机通信实现

基于HAL库的串口+DMA+状态机通信实现

在嵌入式系统中,实现可靠的串口通信至关重要。结合STM32 HAL库的串口、DMA功能和状态机设计模式,可以高效处理上位机发送的指令帧。下面我将详细介绍如何实现这个功能。

一、系统架构设计

我们将构建一个基于以下组件的通信系统:

  • UART:负责物理层数据传输
  • DMA:实现数据的零拷贝接收,减少CPU干预
  • 状态机:解析和验证指令帧格式
  • 环形缓冲区:存储接收到的数据,提供线程安全访问

系统工作流程:

  1. 配置UART和DMA,启动连续接收
  2. 当接收到数据时,DMA自动将数据存入缓冲区
  3. 状态机在后台解析缓冲区数据,识别完整指令帧
  4. 应用层处理解析后的指令
二、代码实现

以下是完整的实现代码:

#include "stm32f4xx_hal.h"
#include <string.h>/* 定义指令帧相关常量 */
#define FRAME_HEADER1       0xA5
#define FRAME_HEADER2       0x5A
#define FRAME_TAIL1         0x0D
#define FRAME_TAIL2         0x0A
#define MAX_FRAME_LENGTH    64
#define RX_BUFFER_SIZE      128/* 帧状态机枚举 */
typedef enum {STATE_HEADER1,    // 等待帧头1 (0xA5)STATE_HEADER2,    // 等待帧头2 (0x5A)STATE_LENGTH,     // 等待长度字节STATE_DATA,       // 接收数据STATE_TAIL1,      // 等待帧尾1 (0x0D)STATE_TAIL2       // 等待帧尾2 (0x0A)
} FrameState;/* 全局变量 */
UART_HandleTypeDef huart1;
DMA_HandleTypeDef hdma_usart1_rx;uint8_t rx_dma_buffer[RX_BUFFER_SIZE];  // DMA接收缓冲区
uint8_t rx_frame_buffer[MAX_FRAME_LENGTH]; // 帧解析缓冲区uint32_t rx_dma_index = 0;             // DMA缓冲区当前索引
uint32_t rx_dma_prev_index = 0;        // 上次处理的DMA缓冲区索引
uint8_t frame_received = 0;            // 帧接收完成标志
uint8_t frame_length = 0;              // 当前帧长度
FrameState current_state = STATE_HEADER1; // 当前状态机状态/* 函数声明 */
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_DMA_Init(void);
static void MX_USART1_UART_Init(void);
void process_received_frame(void);/* 初始化函数 */
int main(void)
{HAL_Init();SystemClock_Config();MX_GPIO_Init();MX_DMA_Init();MX_USART1_UART_Init();/* 启动DMA接收 */HAL_UART_Receive_DMA(&huart1, rx_dma_buffer, RX_BUFFER_SIZE);while (1){/* 处理接收到的帧 */if (frame_received) {process_received_frame();frame_received = 0;}/* 其他主循环任务 */// ...}
}/* 处理接收到的完整帧 */
void process_received_frame(void)
{/* 验证帧长度 */if (frame_length < 4) {  // 至少包含帧头、长度和帧尾return;}/* 解析指令类型 */uint8_t command = rx_frame_buffer[0];uint8_t param = rx_frame_buffer[1];/* 根据指令类型执行相应操作 */switch (command) {case 0x02:// 处理指令0x02break;// 其他指令处理...default:// 未知指令break;}
}/* 串口接收回调函数 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{if (huart->Instance == USART1) {/* 获取当前DMA缓冲区索引 */uint32_t current_index = RX_BUFFER_SIZE - __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);/* 处理新接收到的数据 */while (rx_dma_prev_index != current_index) {uint8_t data = rx_dma_buffer[rx_dma_prev_index];/* 状态机处理接收到的字节 */switch (current_state) {case STATE_HEADER1:if (data == FRAME_HEADER1) {current_state = STATE_HEADER2;frame_length = 0;}break;case STATE_HEADER2:if (data == FRAME_HEADER2) {current_state = STATE_LENGTH;} else {current_state = STATE_HEADER1;  // 帧头错误,重新开始}break;case STATE_LENGTH:if (data <= MAX_FRAME_LENGTH - 4) {  // 确保帧长度合理frame_length = data;current_state = STATE_DATA;} else {current_state = STATE_HEADER1;  // 长度错误,重新开始}break;case STATE_DATA:if (frame_length > 0) {rx_frame_buffer[frame_length - data] = data;  // 存储数据frame_length--;if (frame_length == 0) {  // 数据接收完毕,等待帧尾current_state = STATE_TAIL1;}} else {current_state = STATE_HEADER1;  // 数据长度异常,重新开始}break;case STATE_TAIL1:if (data == FRAME_TAIL1) {current_state = STATE_TAIL2;} else {current_state = STATE_HEADER1;  // 帧尾错误,重新开始}break;case STATE_TAIL2:if (data == FRAME_TAIL2) {/* 完整帧接收成功 */frame_received = 1;}current_state = STATE_HEADER1;  // 无论是否成功,都重新开始break;}/* 更新索引 */rx_dma_prev_index = (rx_dma_prev_index + 1) % RX_BUFFER_SIZE;}/* 重新启动DMA接收 */HAL_UART_Receive_DMA(&huart1, rx_dma_buffer, RX_BUFFER_SIZE);}
}/* 错误处理回调 */
void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart)
{if (huart->Instance == USART1) {/* 处理UART错误 */uint32_t error = HAL_UART_GetError(huart);/* 清除错误并重新启动DMA接收 */__HAL_UART_CLEAR_OREFLAG(huart);HAL_UART_Receive_DMA(&huart1, rx_dma_buffer, RX_BUFFER_SIZE);}
}/* 初始化函数实现 (由CubeMX生成) */
static void MX_USART1_UART_Init(void)
{huart1.Instance = USART1;huart1.Init.BaudRate = 115200;huart1.Init.WordLength = UART_WORDLENGTH_8B;huart1.Init.StopBits = UART_STOPBITS_1;huart1.Init.Parity = UART_PARITY_NONE;huart1.Init.Mode = UART_MODE_RX;huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;huart1.Init.OverSampling = UART_OVERSAMPLING_16;if (HAL_UART_Init(&huart1) != HAL_OK){Error_Handler();}
}static void MX_DMA_Init(void)
{__HAL_RCC_DMA2_CLK_ENABLE();HAL_NVIC_SetPriority(DMA2_Stream2_IRQn, 0, 0);HAL_NVIC_EnableIRQ(DMA2_Stream2_IRQn);hdma_usart1_rx.Instance = DMA2_Stream2;hdma_usart1_rx.Init.Channel = DMA_CHANNEL_4;hdma_usart1_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;hdma_usart1_rx.Init.PeriphInc = DMA_PINC_DISABLE;hdma_usart1_rx.Init.MemInc = DMA_MINC_ENABLE;hdma_usart1_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;hdma_usart1_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;hdma_usart1_rx.Init.Mode = DMA_CIRCULAR;hdma_usart1_rx.Init.Priority = DMA_PRIORITY_LOW;hdma_usart1_rx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;if (HAL_DMA_Init(&hdma_usart1_rx) != HAL_OK){Error_Handler();}__HAL_LINKDMA(&huart1, hdmatx, hdma_usart1_rx);
}
三、实现要点解析
  1. DMA环形缓冲区配置

    • 使用DMA_CIRCULAR模式实现无限接收
    • 通过比较当前DMA索引和上次处理索引来确定新数据
  2. 状态机设计

    • 6个状态:等待帧头、接收长度、接收数据、验证帧尾
    • 每个状态处理特定字节并转移到下一状态
    • 任何错误都会导致状态机重置
  3. 帧解析逻辑

    • 帧头验证:连续接收到0xA5和0x5A
    • 长度验证:确保数据长度在合理范围内
    • 帧尾验证:确保以0x0D和0x0A结束
  4. 错误处理

    • 实现UART错误回调函数处理溢出等错误
    • 重置DMA接收以恢复通信
四、优化建议
  1. 线程安全

    • 如果在中断和主循环中访问共享资源,建议添加临界区保护
    • 例如在处理frame_received标志时禁用中断
  2. 扩展功能

    • 添加CRC校验增强数据可靠性
    • 实现指令超时检测
    • 支持多指令并发处理
  3. 性能优化

    • 使用双缓冲区减少数据处理延迟
    • 优化状态机转移逻辑提高解析效率

这种设计结合了DMA的高效数据传输能力和状态机的灵活解析能力,能够可靠地接收和解析上位机发送的指令帧,同时最大限度减少CPU占用,是嵌入式系统串口通信的理想解决方案。

相关文章:

  • 居逸JY02A无刷电机驱动芯片介绍
  • 获取高德地图JS API的安全密钥和Key的方法
  • dfs第二次加训 详细题解 下
  • k8s监控方案实践(三):部署与配置Grafana可视化平台
  • Spring Boot整合Kafka实战指南:从环境搭建到消息处理全解析
  • 【运维】MacOS蓝牙故障排查与修复指南
  • FreeRTOS的学习记录(基础知识)
  • STM32F103_LL库+寄存器学习笔记12.1 - 串口DMA高效收发实战:引入ringbuffer结构
  • [计算机网络]网络层
  • Matlab 模糊控制节水洗衣机模型
  • 中科院无人机导航物流配送的智能变革!LogisticsVLN:基于无人机视觉语言导航的低空终端配送系统
  • 9.渐入佳境 -- 套接字的多种可选项
  • iptables实现DDos
  • 计算频谱的方法
  • [Java实战]Spring Boot 整合 Session 共享(十七)
  • 什么是 NoSQL 数据库?它与关系型数据库 (RDBMS) 的主要区别是什么?
  • ros1+docker环境快速搭建
  • postgresql主从集群一键搭建脚本分享
  • 人工智能领域1-4区所有SCI汇总!
  • springCloud/Alibaba常用中间件之Setinel实现熔断降级
  • 以开放促发展,以发展促开放,浙江加快建设高能级开放强省
  • 英国知名歌手批政府:让AI公司免费使用艺术家作品是盗窃
  • 上海公办小学验证今起开始,下周一和周二分区进行民办摇号
  • 奥运“四朝元老”华天回国参赛,伤势未愈谨慎出战全国锦标赛
  • 中央宣传部、全国妇联联合发布2025年“最美家庭”
  • 万科再获深铁集团借款,今年已累计获股东借款近120亿元