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

M0 串口驱动设计文档

1. 特点

  • 基于 TI MSPM0 DriverLib 实现
  • 不定长接收方案:通过 DMA + 环形缓冲区实现,无需依赖硬件空闲中断或超时中断
  • 工作原理
    • DMA 循环模式持续将 UART 接收数据写入环形缓冲区
    • 软件通过读取 DMA 传输计数器获取当前写入位置(tail)
    • 计算 (tail - head) 得到缓冲区中未处理的数据量
    • 协议解析层从缓冲区中查找帧头帧尾,提取完整数据包后移动 head 指针释放空间
  • 发送采用阻塞方式:格式化数据后追加帧尾,通过 TX FIFO 逐字节发送

2. 整体架构

硬件层 (无中断)

UART RX  ──>  DMA RX Trigger ──> DMA (Repeat Single模式)  ──>  环形缓冲区 data[]

软件层 (轮询解析)

SysTick or 主循环│├── HMI_Get_Count()  →  计算 (tail - head) 获取有效数据量│├── HMI_Peek()       →  直接在环形缓冲区中查找帧头帧尾│├── 提取数据区         →  调用处理函数│└── HMI_Delete()     →  移动 head 指针释放已处理数据

发送路径

HMI_Printf() 格式化  →  追加 0xFF 0xFF 0xFF  →  阻塞发送至 TX FIFO

3. 环形缓冲区数据结构

#define RINGBUFF_LEN 64
#define TX_TEMP_LEN  64typedef struct {volatile uint16_t head;           // 软件读指针 (由软件主动移动)uint8_t data[RINGBUFF_LEN];       // 实际缓冲区
} HMI_Buffer_t;__attribute__((aligned(4))) HMI_Buffer_t tjc_rb;  // 四字节对齐,优化总线访问
UART_Regs *tjc_huart = UART_0_INST;               // 绑定具体外设实例
static uint8_t tjc_tx_buf[TX_TEMP_LEN];           // 格式化发送缓冲
  • head 为 volatile:确保软件层在轮询时,每次都从内存重新读取最新的头指针状态。
  • tail 指针:因为写指针的维护全权交给了 DMA 硬件内部计数器。

4. 核心函数设计与代码深度解析

4.1 HMI_Init()

void HMI_Init(void) {tjc_rb.head = 0;// 设置 DMA 源地址 (UART RX 寄存器) 和 目标地址 (Ring Buffer)DL_DMA_setSrcAddr(DMA, DMA_CH0_CHAN_ID, (uint32_t)(&tjc_huart->RXDATA));DL_DMA_setDestAddr(DMA, DMA_CH0_CHAN_ID, (uint32_t)tjc_rb.data);// 设置传输大小为环形缓冲区总长DL_DMA_setTransferSize(DMA, DMA_CH0_CHAN_ID, RINGBUFF_LEN);DL_DMA_enableChannel(DMA, DMA_CH0_CHAN_ID);// 允许 UART RX 事件触发 DMADL_UART_Main_enableDMAReceiveEvent(tjc_huart, DL_UART_MAIN_DMA_INTERRUPT_RX);
}
  • 无 NVIC_EnableIRQ:不使用外设中断。
  • 需在 SysConfig 中将 DMA 配置为 Repeat Single 模式,这样 DMA 在传输完 RINGBUFF_LEN 后,会自动将目标地址复位到 tjc_rb.data 的开头,循环搬运。

4.2 HMI_Get_Count() —— 长度计算

uint16_t HMI_Get_Count(HMI_Buffer_t *rb, UART_Regs *uart) {// DL_DMA_getTransferSize 返回的是“剩余未传输”的字节数uint16_t remaining = DL_DMA_getTransferSize(DMA, DMA_CH0_CHAN_ID);// 总长 - 剩余 = 当前 DMA 写到了哪里 (等价于 tail 指针)uint16_t tail = RINGBUFF_LEN - remaining;// 计算头尾指针的差值 (考虑数组回绕)return (tail - rb->head + RINGBUFF_LEN) % RINGBUFF_LEN;
}
  • 直接读取 DMA 硬件的计数器作为写指针(Tail),绝对精确且不会因为软件延时而错漏。无论是一个字节一个字节来,还是由于高波特率瞬间涌入大量字节,该函数都能精准计算出当前可读数据量。

4.3 HMI_Delete() 与 HMI_Peek()

uint8_t HMI_Peek(HMI_Buffer_t *rb, uint16_t index) {return rb->data[(rb->head + index) % RINGBUFF_LEN];
}void HMI_Delete(HMI_Buffer_t *rb, uint16_t size) {rb->head = (rb->head + size) % RINGBUFF_LEN;
}
  • HMI_Peek:允许上层在不破坏缓冲区结构的前提下,查看任意字节。这对于协议状态机(寻找包头/包尾)极其有用。
  • HMI_Delete:移动读指针释放已处理数据,无需拷贝,实现逻辑删除而非物理删除。

4.4 HMI_Printf() —— 阻塞式发送

void HMI_Printf(UART_Regs *uart, const char *fmt, ...) {va_list args;va_start(args, fmt);int len = vsnprintf((char *)tjc_tx_buf, TX_TEMP_LEN - 3, fmt, args);va_end(args);if (len <= 0) return;// 追加 0xFF 0xFF 0xFF 结束符tjc_tx_buf[len++] = 0xFF;tjc_tx_buf[len++] = 0xFF;tjc_tx_buf[len++] = 0xFF;for (int i = 0; i < len; i++) {DL_UART_Main_transmitDataBlocking(uart, tjc_tx_buf[i]);}
}

5. 接收处理机制 (软硬件完全分离模式)

MSPM0 串口没有空闲中断功能,为了实现不定长的数据解析,这里采用的方法是软硬件完全分离,串口以及 DMA 只负责搬运,不产生任何中断,将数据解析任务放在 SysTick 定时器中断 中循环执行。

// 示例:放在 SysTick_Handler 或主循环中周期性调用
void TJC_Receive_Process(void) {// 只要缓冲区内的数据量大于等于最小协议帧长度while (HMI_Get_Count(&tjc_rb, tjc_huart) >= MIN_FRAME_LEN) {// 1. 使用 HMI_Peek() 嗅探包头以及包尾// 2. 如果符合协议,提取数据执行动作// 3. 执行完毕后,调用 HMI_Delete() 释放已处理的数据空间}
}

关于 MSPM0 RX Timeout 中断的说明

MSPM0 的 RX Timeout 中断触发条件是:RX FIFO 非空且在一定时间内未收到新数据

但在 DMA 循环接收模式下,会出现以下问题:

  • DMA 将 FIFO 中的数据抽空,导致 FIFO 变为空状态
  • 此时即使总线已空闲,由于 FIFO 为空,超时计数器不会启动
  • 结果就是:数据已接收完成,但 RX Timeout 中断永远不会触发
    当然可以通过设置 FIFO

本方案规避方式

  • 完全关闭 RX Timeout 中断,不依赖硬件判断帧结束
  • 改用定时器轮询 DMA 计数器,通过软件计算缓冲区有效数据量
  • 由协议解析层自行查找帧头帧尾,实现帧同步

6. 注意事项

为了保证这套架构正常运行,Sysconfig应按照下面配置

  1. DMA 配置
    • Transfer Mode 设置为 Repeat Single
    • Enable Channel Interrupt 为 False(关闭 DMA 完成中断)。
  2. UART 配置
    • 开启 FIFO,且 RX FIFO Threshold 设为 >= 1 entry(保证数据立刻交给 DMA)。
    • 中断配置中,取消所有勾选,不开启任何中断。
  3. SYSTICK 配置
    • 开启中断并设置周期为 1ms(酌情设置)

7. 完整底层驱动代码

7.1 tjc_usart_hmi.h

#ifndef TJC_USART_HMI_H
#define TJC_USART_HMI_H#include "ti_msp_dl_config.h"#define RINGBUFF_LEN 64
#define TX_TEMP_LEN  64typedef struct {volatile uint16_t head;uint8_t data[RINGBUFF_LEN];
} HMI_Buffer_t;extern HMI_Buffer_t tjc_rb;
extern UART_Regs *tjc_huart;// 函数声明
void HMI_Init(void);uint16_t HMI_Get_Count(HMI_Buffer_t *rb, UART_Regs *uart);void HMI_Delete(HMI_Buffer_t *rb, uint16_t size);uint8_t HMI_Peek(HMI_Buffer_t *rb, uint16_t index);void HMI_Printf(UART_Regs *uart, const char *fmt, ...);#endif

7.2 tjc_usart_hmi.c

#include "tjc_usart_hmi.h"
#include <stdio.h>
#include <stdarg.h>__attribute__((aligned(4))) HMI_Buffer_t tjc_rb;
UART_Regs *tjc_huart = UART_0_INST;
static uint8_t tjc_tx_buf[TX_TEMP_LEN];void HMI_Init(void) {tjc_rb.head = 0;DL_DMA_setSrcAddr(DMA, DMA_CH0_CHAN_ID, (uint32_t)(&tjc_huart->RXDATA));DL_DMA_setDestAddr(DMA, DMA_CH0_CHAN_ID, (uint32_t)tjc_rb.data);DL_DMA_setTransferSize(DMA, DMA_CH0_CHAN_ID, RINGBUFF_LEN);DL_DMA_enableChannel(DMA, DMA_CH0_CHAN_ID);DL_UART_Main_enableDMAReceiveEvent(tjc_huart, DL_UART_MAIN_DMA_INTERRUPT_RX);
}uint16_t HMI_Get_Count(HMI_Buffer_t *rb, UART_Regs *uart) {uint16_t remaining = DL_DMA_getTransferSize(DMA, DMA_CH0_CHAN_ID);uint16_t tail = RINGBUFF_LEN - remaining;return (tail - rb->head + RINGBUFF_LEN) % RINGBUFF_LEN;
}void HMI_Delete(HMI_Buffer_t *rb, uint16_t size) {rb->head = (rb->head + size) % RINGBUFF_LEN;
}uint8_t HMI_Peek(HMI_Buffer_t *rb, uint16_t index) {return rb->data[(rb->head + index) % RINGBUFF_LEN];
}void HMI_Printf(UART_Regs *uart, const char *fmt, ...) {va_list args;va_start(args, fmt);int len = vsnprintf((char *)tjc_tx_buf, TX_TEMP_LEN - 3, fmt, args);va_end(args);if (len <= 0) return;tjc_tx_buf[len++] = 0xFF;tjc_tx_buf[len++] = 0xFF;tjc_tx_buf[len++] = 0xFF;for (int i = 0; i < len; i++) {DL_UART_Main_transmitDataBlocking(uart, tjc_tx_buf[i]);}
}
http://www.jsqmd.com/news/561781/

相关文章:

  • CocosCreator 3.x 实战:用Button组件做个带反馈的UI按钮(附完整代码)
  • 城域网终局:城市超级计算机
  • springboot+vue基于web的医院预约管理系统护士
  • SillyTavern角色卡片系统:技术原理与实践指南
  • UI-TARS-desktop效果实测:内置Qwen3-4B模型响应速度有多快
  • Excel转置数据不用VBA!用Kettle8.2列转行组件5分钟搞定周报统计
  • OpenClaw自动化测试:Qwen3-32B-Chat镜像驱动Python脚本全流程
  • 兰亭妙微安卓UI设计适配体系:分辨率、密度、dp/sp换算与资源管理全解析 - ui设计公司兰亭妙微
  • 别再手动建节点了!用Neo4j Desktop批量导入CSV数据,5分钟搞定知识图谱
  • springboot+vue基于web的学生健康饮食与运动管理系统
  • 4步掌握开源工具:研究者的数据获取与合规应用指南
  • GD32 USB从机硬件设计避坑指南:F303/E503与F4xx/F350系列上拉电阻到底怎么接?
  • 【深度解析】从规划到执行:用多智能体 + MCP 打造可落地的 AI 工程团队
  • 大模型如此火爆,可观测性会被重写吗?
  • AudioLDM-S影视制作应用:C++高性能音效渲染
  • 【java入门到放弃】术语
  • 2026指纹浏览器故障排查与性能优化实战:从异常定位到环境稳定落地
  • 10分钟彻底告别Windows字体审美疲劳:No!! MeiryoUI个性化字体定制全攻略
  • YOLOv5 7.0 骨干网络替换实战:从ResNet到自定义Backbone的完整指南
  • 从离线到实时:UE5体积渲染技术如何用OpenVDB与NanoVDB重塑影视级特效工作流
  • 营销短信接口调用实务:编写健壮的代码处理营销短信API反馈与失败重试
  • 2026年ROSS双联阀实力厂家盘点,哪些品牌值得关注?ROSS单联阀/TWSNS过滤器,ROSS双联阀厂商推荐 - 品牌推荐师
  • Video-subtitle-extractor:免费高效的视频硬字幕提取终极指南
  • 别再纠结XML还是CAPL了!手把手教你用CANoe搭建UDS Bootloader自动化测试环境(附节点选择避坑指南)
  • DanKoe 视频笔记:创作者经济:是庞氏骗局还是未来机遇?[特殊字符]
  • ChatGLM-6B实战教程:使用curl/postman调用REST API实现程序集成
  • Gemma-3 Pixel Studio惊艳效果展示:JPG/PNG/WebP图像深度解析作品集
  • RMBG-2.0异常处理指南:解决常见部署与运行问题
  • dp 小记
  • 快速掌握3D重建新工具:从入门到实践的完整路径