1. 项目概述
搞嵌入式开发,串口通信(UART)绝对是绕不开的“老朋友”。无论是早期的单片机调试,还是现在各种传感器、模块的通信,UART都扮演着至关重要的角色。但说实话,很多开发者对UART的认知可能还停留在“配置波特率,然后 read / write ”的层面,一旦遇到数据量大、实时性要求高或者需要低功耗的场景,简单的轮询方式就捉襟见肘了。
我最近在基于NXP的Kinetis系列MCU做项目,深度使用了其SDK v2.0中的UART驱动。这套驱动设计得相当精妙,它不仅仅提供了基础的阻塞式收发,更重要的是完整封装了中断和DMA(直接内存访问)这两种高效传输模式,并且通过 uart_handle_t 等数据结构,将复杂的底层硬件操作抽象成了清晰、易用的API。这让我在实现一个需要同时与GPS模块、无线模块通信,并且还要处理大量调试日志输出的系统时,游刃有余。
这篇文章,我就结合Kinetis SDK的UART驱动源码,带大家从最基础的配置结构体 uart_config_t 开始,一步步深入到中断传输的环形缓冲区管理,再到如何利用DMA彻底解放CPU。我会重点拆解那些数据手册里不会写的“坑”,比如中断服务程序(ISR)里状态机怎么设计才不会丢数据,DMA传输完成回调里要注意什么,以及环形缓冲区那个“预留一字节”的玄机。无论你是刚接触嵌入式的新手,还是想优化现有串口通信的老鸟,相信都能从中找到可以直接“抄作业”的干货。
2. UART驱动核心数据结构深度解析
驱动好不好用,一半看API设计,另一半就得看数据结构是否合理。Kinetis SDK的UART驱动核心围绕几个结构体展开,理解它们是灵活运用的前提。
2.1 配置结构体:uart_config_t
这个结构体定义了UART模块的静态工作参数,相当于设备的“身份证”和“基础设置”。每次初始化或重新配置UART时都需要用到它。
typedef struct _uart_config
{
uint32_t baudRate_Bps; /*!< UART波特率,单位:比特每秒 */
uart_parity_mode_t parityMode; /*!< 校验模式:禁用、奇校验、偶校验 */
uart_stop_bit_count_t stopBitCount; /*!< 停止位数量:1位或2位 */
uart_bit_count_per_char_t bitCountPerChar; /*!< 数据位长度:通常为8位 */
uint8_t txFifoWatermark; /*!< TX FIFO水位线(触发中断的阈值) */
uint8_t rxFifoWatermark; /*!< RX FIFO水位线(触发中断的阈值) */
bool enableTx; /*!< 使能发送器 */
bool enableRx; /*!< 使能接收器 */
} uart_config_t;
关键字段解读与避坑指南:
-
baudRate_Bps :这是最容易出错的地方之一。SDK提供的
UART_SetBaudRate函数需要传入模块的源时钟频率srcClock_Hz。 务必确认你传入的时钟频率是UART模块实际使用的时钟,而不是系统主频 。例如,你的芯片UART时钟可能来自一个分频后的总线时钟(Bus Clock),而不是核心时钟(Core Clock)。算错时钟,波特率就对不上,通信必然失败。 -
txFifoWatermark / rxFifoWatermark :这是配置FIFO(先进先出缓冲区)中断触发点的。例如,设置
rxFifoWatermark = 4,意味着当RX FIFO中累积的数据达到或超过4字节时,才会触发“RX数据寄存器满”中断。 这个值需要根据你的数据包大小和实时性要求来权衡 。如果设置太小(比如1),每收到一个字节就进一次中断,CPU中断开销巨大;如果设置太大,对于小数据包或需要快速响应的场景,延迟又会过高。我的经验是,对于交互式调试(如AT指令),设置为1;对于流式数据传输(如传感器持续上报),可以设置为FIFO深度的一半或更高。 -
enableTx / enableRx :这是一个非常实用的设计。你可以在初始化时只使能接收,暂时关闭发送,或者反之。 在低功耗应用中,当串口暂时不用时,可以通过
UART_EnableTx和UART_EnableRx函数动态开关收发器,而不是反复初始化和反初始化,能有效降低功耗和状态管理的复杂度。
2.2 传输句柄:uart_handle_t
如果说 uart_config_t 是静态配置,那么 uart_handle_t 就是动态运行时的“大脑”和“状态记录仪”。它是实现非阻塞(中断/DMA)传输的核心。
struct _uart_handle {
uint8_t *volatile txData; /* 待发送数据的剩余缓冲区地址 */
volatile size_t txDataSize; /* 待发送数据的剩余字节数 */
size_t txDataSizeAll; /* 本次发送任务的总字节数 */
uint8_t *volatile rxData; /* 待接收数据的剩余缓冲区地址 */
volatile size_t rxDataSize; /* 待接收数据的剩余字节数 */
size_t rxDataSizeAll; /* 本次接收任务期望的总字节数 */
uint8_t *rxRingBuffer; /* 环形缓冲区起始地址 */
size_t rxRingBufferSize; /* 环形缓冲区总大小 */
volatile uint16_t rxRingBufferHead; /* 驱动写入数据的位置索引 */
volatile uint16_t rxRingBufferTail; /* 用户读取数据的位置索引 */
uart_transfer_callback_t callback; /* 传输完成回调函数 */
void *userData; /* 回调函数用户参数 */
volatile uint8_t txState; /* 发送状态机状态 */
volatile uint8_t rxState; /* 接收状态机状态 */
};
这个结构体的设计精髓在于“状态分离”和“环形缓冲区”:
-
双指针管理发送/接收 :
txData/rxData和txDataSize/rxDataSize是volatile的,因为它


291

被折叠的 条评论
为什么被折叠?



