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

七、基于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占用,是嵌入式系统串口通信的理想解决方案。

http://www.xdnf.cn/news/5535.html

相关文章:

  • 国产化Excel处理控件Spire.XLS系列教程:如何通过 C# 删除 Excel 工作表中的筛选器
  • HTML简单语法标签(后续实操:云备份项目)
  • 《Spring Boot 4.0新特性深度解析》
  • 企业即时通讯软件,私有化安全防泄密
  • 图灵爬虫练习平台第十九题js逆向
  • 使用基于ARM的低功耗微型单板计算机打造智能家居管理系统中枢
  • 开发环境(Development Environment)
  • 前端面试每日三题 - Day 32
  • Kubernetes控制平面组件:Kubelet详解(二):核心功能层
  • Go语言:json 作用和语法
  • 【WPF】Opacity 属性的使用
  • 【Redis 进阶】哨兵模式
  • 降低60.6%碰撞率!复旦大学地平线CorDriver:首次引入「走廊」增强端到端自动驾驶安全性
  • 芯显10.4寸工业液晶屏XB104S01-200-10.4寸工业显示屏
  • 了解神经网络声音定制,实现多情绪、多语言演绎
  • Java—— 集合 Set
  • Android HttpAPI通信问题(待解决)
  • 【人工智能-agent】--Dify中自然语言生成SQL查询数据库
  • Java学习手册:客户端负载均衡
  • 基于LNMP架构的个人博客系统部署
  • DDD领域驱动开发
  • 基于 51 单片机的 PWM 电机调速系统实现
  • https的发展历程
  • 区块链钱包开发全解析:从架构设计到安全生态构建
  • 【c++】异常详解
  • 计网学习笔记———通信知识(计算机网络通信单独讲)
  • Python 处理图像并生成 JSONL 元数据文件 - 灵活text版本
  • 亚川科技YCS-7000 建筑设备一体化监控系统选型说明与配置原理
  • NVIDIA Isaac™ AI 机器人开发平台笔记
  • 从经典力扣题发掘DFS与记忆化搜索的本质 -从矩阵最长递增路径入手 一步步探究dfs思维优化与编程深度思考