尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

RT-Thread SAM E54 BSP 的 USART 异步驱动(HAL USART Async)完整解析:环形缓冲接收、零拷贝发送与回调机制

发布时间:2026/9/24 13:41:38

资讯中心
01
ARTICLE

RT-Thread SAM E54 BSP 的 USART 异步驱动(HAL USART Async)完整解析:环形缓冲接收、零拷贝发送与回调机制

RT-Thread SAM E54 BSP 的 USART 异步驱动(HAL USART Async)完整解析:环形缓冲接收、零拷贝发送与回调机制
RT-Thread SAM E54 BSP 的 USART 异步驱动HAL USART Async完整解析环形缓冲接收、零拷贝发送与回调机制【免费下载链接】rt-threadRT-Thread is an open source IoT Real-Time Operating System (RTOS). https://rt-thread.github.io/rt-thread/项目地址: https://gitcode.com/gh_mirrors/rt/rt-thread本篇技术指南围绕 RT-Thread 仓库中 bsp/microchip/same54 板级支持包所携带的 Microchip HALAtmel StartUSART 异步驱动展开以官方文档 usart_async.rst 为主体逐层剖析其接收走环形缓冲、发送零拷贝直用用户缓冲的核心数据通路、完整 API 集合、回调机制与硬件抽象层HPL实现。读完本文你将掌握如何在 SAM E54 系列基于 SERCOM 外设上正确初始化、配置、收发数据并处理流控与异常同时理解该驱动与同步驱动的本质区别及使用时的各类限制。1. 驱动定位异步模式下的 USART 通用收发框架USART通用同步/异步收发器是最常用的设备间数据传输外设之一常用于终端调试串口或低速设备间通信。本文所讨论的驱动位于上层驱动接口HALhal_usart_async.h 与 hal_usart_async.c硬件抽象层HPLhpl_usart_async.h 与 hpl_sercom.c通用枚举/联合定义hpl_usart.h环形缓冲实现utils_ringbuffer.c该驱动以异步为设计核心数据的发送与接收由中断驱动应用层无需阻塞等待而是在传输完成或出错时通过回调获得通知。整个驱动在 driver_init.c 中被实例化为TARGET_IO基于 SERCOM2可作为理解该驱动完整生命周期的现成范例。2. 数据通路设计接收用环形缓冲发送零拷贝原文档对驱动内部数据流做了两点关键约定这也是理解整个驱动行为的前提。2.1 接收方向中断驱动 环形缓冲驱动使用环形缓冲ring buffer暂存接收数据当 USART 产生数据接收完成中断时该字节会被写入环形缓冲的下一个空闲位置当缓冲写满时新数据会覆盖最老的旧数据overwrite 策略。每个被使用的硬件实例都有独立的缓冲大小宏例如 SERCOM0 对应SERCOM0_USART_BUFFER_SIZE。在源码层面这一行为由 utils_ringbuffer.c 的ringbuffer_put()体现int32_t ringbuffer_put(struct ringbuffer *const rb, uint8_t data) { rb-buf[rb-write_index rb-size] data; /* buffer full strategy: new data will overwrite the oldest data */ if ((rb-write_index - rb-read_index) rb-size) { rb-read_index rb-write_index - rb-size; } rb-write_index; return ERR_NONE; }两点实现细节值得注意缓冲大小必须为 2 的幂ringbuffer_init()会校验(size (size - 1)) ! 0并返回ERR_INVALID_ARG因为环形索引依赖write_index (size - 1)的位掩码运算见 utils_ringbuffer.c。中断服务程序收到字节后先入队再回调在 hal_usart_async.c 中usart_fill_rx_buffer()将数据写入环形缓冲后若应用注册了rx_done回调则立即调用。在本 BSP 的生成代码中该宏实际体现为TARGET_IO_BUFFER_SIZE值为 16见 driver_init.c#define TARGET_IO_BUFFER_SIZE 16 struct usart_async_descriptor TARGET_IO; static uint8_t TARGET_IO_buffer[TARGET_IO_BUFFER_SIZE];2.2 发送方向直用用户缓冲区无内部拷贝与接收不同发送时驱动不会把数据复制进内部缓冲而是直接使用调用方提供的缓冲。随之而来的一个重要行为是回调只在整段缓冲发送结束时产生一次而不是每个字节触发一次。从 hal_usart_async.c 的usart_async_write()可见其设计static int32_t usart_async_write(struct io_descriptor *const io_descr, const uint8_t *const buf, const uint16_t length) { struct usart_async_descriptor *descr CONTAINER_OF(io_descr, struct usart_async_descriptor, io); ASSERT(descr buf length); if (descr-tx_por ! descr-tx_buffer_length) { return ERR_NO_RESOURCE; /* 上一次传输未完成拒绝新请求 */ } descr-tx_buffer (uint8_t *)buf; /* 直接持有用户缓冲指针 */ descr-tx_buffer_length length; descr-tx_por 0; descr-stat USART_ASYNC_STATUS_BUSY; _usart_async_enable_byte_sent_irq(descr-device); return (int32_t)length; }随后中断逐字节推进usart_process_byte_sent()每发送一个字节即从tx_buffer[tx_por]取出下一字节直到全部发完才使能传输完成中断usart_transmission_complete()清除USART_ASYNC_STATUS_BUSY标志并调用tx_done回调见 hal_usart_async.c。2.3 发送缓冲的生命周期约定并发注意由于发送采用用户缓冲直用原文档明确给出并发约束数据发送期间不得修改发送缓冲。同样地如果上一次传输尚未结束tx_por ! tx_buffer_length再次调用写接口会直接返回ERR_NO_RESOURCE。因此异步场景下用于发送的字符串/数据缓冲必须使用静态分配或生命周期足够长的存储这正是 driver_examples.c 中示例使用static uint8_t example_TARGET_IO[12]的原因。3. 完整 API 集合与参数详解驱动对外提供的 API 全部声明于 hal_usart_async.h核心函数可归纳为下表功能分类函数说明初始化/反初始化usart_async_init初始化描述符、环形缓冲与底层硬件参数无效或接口已初始化返回 -1成功返回 0usart_async_deinit反初始化并清空 I/O 描述符读写指针使能/禁用usart_async_enable/usart_async_disable使能/禁用 USART 模块I/O 抽象usart_async_get_io_descriptor取出struct io_descriptor *供io_read/io_write使用回调注册usart_async_register_callback注册 RX 完成、TX 完成、错误三类回调参数设置usart_async_set_baud_rate设置波特率usart_async_set_data_order设置数据位顺序MSB/LSB 先行usart_async_set_mode设置异步/同步模式usart_async_set_parity设置校验位usart_async_set_stopbits设置停止位usart_async_set_character_size设置字符位宽流控usart_async_set_flow_control设置流控引脚动作/状态usart_async_flow_control_status读取流控引脚状态状态查询usart_async_is_tx_empty发送器是否空闲空返回 1非空返回 0usart_async_is_rx_not_empty接收器是否有数据实质是判断环形缓冲元素数 0usart_async_get_status获取usart_async_status含 busy 标志与收发计数忙时返回ERR_BUSY缓冲操作usart_async_flush_rx_buffer清空 RX 环形缓冲版本usart_async_get_version返回驱动版本号当前为0x000000013.1 关键数据结构驱动描述符struct usart_async_descriptor见 hal_usart_async.h同时内嵌了 I/O 描述符、底层设备句柄、回调集合、状态字以及 RX 环形缓冲和 TX 传输进度字段struct usart_async_descriptor { struct io_descriptor io; struct _usart_async_device device; struct usart_async_callbacks usart_cb; uint32_t stat; struct ringbuffer rx; uint16_t tx_por; uint8_t * tx_buffer; uint16_t tx_buffer_length; };状态结构usart_async_status包含flags状态标志、txcnt已发送字符数与rxcnt已接收字符数其中USART_ASYNC_STATUS_BUSY (0x0001)表示发送忙。usart_async_get_status()在忙时会额外返回ERR_BUSY以便应用区分见 hal_usart_async.c。3.2 回调注册的副作用usart_async_register_callback()有一个容易被忽略的行为传入 NULL 回调会顺带关闭对应中断传入非 NULL 则会开启对应中断见 hal_usart_async.ccase USART_ASYNC_TXC_CB: descr-usart_cb.tx_done cb; _usart_async_set_irq_state(descr-device, USART_ASYNC_TX_DONE, NULL ! cb); break;回调类型枚举为USART_ASYNC_RXC_CB接收、USART_ASYNC_TXC_CB发送完成、USART_ASYNC_ERROR_CB错误回调函数原型统一为void (*usart_cb_t)(const struct usart_async_descriptor *const descr)。4. 可配置参数枚举与取值范围驱动支持波特率、通信模式、字符宽度、数据顺序、流控五类配置其合法取值定义于 hpl_usart.h。4.1 波特率模式enum usart_baud_rate_mode { USART_BAUDRATE_ASYNCH_ARITHMETIC, USART_BAUDRATE_ASYNCH_FRACTIONAL, USART_BAUDRATE_SYNCH };即异步算术、异步分数分数波特率与同步三种模式底层通过_usart_async_calculate_baud_rate()结合时钟频率、采样数与分数值计算波特率寄存器值见 hpl_sercom.c。4.2 通信模式与数据顺序enum usart_mode { USART_MODE_ASYNCHRONOUS 0, USART_MODE_SYNCHRONOUS 1 }; enum usart_data_order { USART_DATA_ORDER_MSB 0, USART_DATA_ORDER_LSB 1 };4.3 校验位enum usart_parity { USART_PARITY_EVEN 0, USART_PARITY_ODD 1, USART_PARITY_NONE 2, USART_PARITY_SPACE 3, USART_PARITY_MARK 4 };4.4 停止位enum usart_stop_bits { USART_STOP_BITS_ONE 0, USART_STOP_BITS_TWO 1, USART_STOP_BITS_ONE_P_FIVE 2 };4.5 字符位宽enum usart_character_size { USART_CHARACTER_SIZE_8BITS 0, USART_CHARACTER_SIZE_9BITS 1, USART_CHARACTER_SIZE_5BITS 5, USART_CHARACTER_SIZE_6BITS 6, USART_CHARACTER_SIZE_7BITS 7 };注意虽然枚举中定义了9BITS但该异步驱动不支持 9 位字符宽度见第 7 节限制说明。4.6 流控引脚状态联合体流控引脚状态由联合体usart_flow_control_state表达可用位域或整数值两种方式操作union usart_flow_control_state { struct { uint8_t cts : 1; /* CTS 引脚状态 */ uint8_t rts : 1; /* RTS 引脚状态 */ uint8_t unavailable : 1;/* 硬件自动流控时置 1表示引脚状态不可读/不可设 */ uint8_t reserved : 5; } bit; uint8_t value; };原文档强调只有硬件不支持自动流控时应用才能通过usart_async_set_flow_control()手动设置流控引脚状态usart_async_flow_control_status()在硬件接管流控时返回USART_FLOW_CONTROL_STATE_UNAVAILABLEHPL 层_usart_async_get_flow_control_state()对不支持自动流控读回的实例也恒定返回该值见 hpl_usart_async.h。5. 实战在 SAM E54 BSP 中完成一次异步发送下面以本 BSP 生成的TARGET_IOSERCOM2引脚 PB24/PB25为例串联驱动完整用法。初始化由 driver_init.c 自动完成void TARGET_IO_init(void) { TARGET_IO_CLOCK_init(); /* GCLK 时钟使能 */ usart_async_init(TARGET_IO, SERCOM2, TARGET_IO_buffer, TARGET_IO_BUFFER_SIZE, (void *)NULL); TARGET_IO_PORT_init(); /* 引脚复用 PINMUX */ }TARGET_IO_CLOCK_init()负责使能 SERCOM2 的 Core/Slow GCLK 通道与 MCLK 时钟掩码TARGET_IO_PORT_init()将 PB25 配置为PINMUX_PB25D_SERCOM2_PAD0TX、PB24 配置为PINMUX_PB24D_SERCOM2_PAD1RX见 driver_init.c。发送示例取自 driver_examples.c完整展示了注册回调 → 取 I/O 描述符 → 使能 → 写入的标准流程static uint8_t example_TARGET_IO[12] Hello World!; static void tx_cb_TARGET_IO(const struct usart_async_descriptor *const io_descr) { /* Transfer completed */ } void TARGET_IO_example(void) { struct io_descriptor *io; usart_async_register_callback(TARGET_IO, USART_ASYNC_TXC_CB, tx_cb_TARGET_IO); /*usart_async_register_callback(TARGET_IO, USART_ASYNC_RXC_CB, rx_cb); usart_async_register_callback(TARGET_IO, USART_ASYNC_ERROR_CB, err_cb);*/ usart_async_get_io_descriptor(TARGET_IO, io); usart_async_enable(TARGET_IO); io_write(io, example_TARGET_IO, 12); }示例代码注释中的提示非常关键由于驱动是异步的io_write()在传输真正完成前就会返回因此必须使用静态分配的字符串缓冲否则发送中断还没取完数据栈上缓冲已被复用。接收方向则可通过io_read()从环形缓冲读取或在USART_ASYNC_RXC_CB回调中即时处理错误场景注册USART_ASYNC_ERROR_CB即可感知usart_error()触发的异常见 hal_usart_async.c。6. 底层实现HPL 层如何落地异步机制异步驱动的硬件落地在 hpl_sercom.c 中。_usart_async_init()在完成基础寄存器配置后会一次性使能该 SERCOM 实例连续 4 个中断向量如 SERCOM2 的 4 个 IRQn并绑定中断参数见 hpl_sercom.cint32_t _usart_async_init(struct _usart_async_device *const device, void *const hw) { init_status _usart_init(hw); device-hw hw; _sercom_init_irq_param(hw, (void *)device); uint8_t irq _sercom_get_irq_num(hw); for (uint32_t i 0; i 4; i) { NVIC_DisableIRQ((IRQn_Type)irq); NVIC_ClearPendingIRQ((IRQn_Type)irq); NVIC_EnableIRQ((IRQn_Type)irq); irq; } return ERR_NONE; }这印证了原文档依赖每个字符收发均产生中断的 USART 硬件的依赖说明异步驱动本质上是在逐字节中断DRE 数据寄存器空、RXC 接收完成、TXC 传输完成之间推进状态机。HPL 层相应提供了_usart_async_write_byte()、_usart_async_enable_byte_sent_irq()、_usart_async_enable_tx_done_irq()、_usart_async_set_irq_state()等原语见 hpl_usart_async.hHAL 层的收发状态机正是搭建在这些原语之上。7. 功能特性、应用场景与限制7.1 功能特性清单对照原文档 Features 一节该驱动支持初始化与反初始化使能与禁用波特率、UART/USRT 通信模式、字符宽度、数据顺序、流控等参数控制发送与接收数据传送通过回调提供传输完成或错误通知携带忙状态与传输计数的状态信息。7.2 典型应用场景如原文档所述USART 异步驱动常见于终端类应用调试串口、命令行交互以及设备间的低速通信。相比同步驱动异步方式在长数据收发时不会阻塞 CPU尤其适合与 RTOS 任务调度配合——发送在后台由中断推进应用任务可继续执行其他工作。7.3 依赖硬件前提具备 USART 能力、且每个字符发送/接收都能产生中断的硬件软件前提环形缓冲utils_ringbuffer、原子操作hal_atomic、断言utils_assert等基础组件均由 HAL 层自动引入。7.4 限制与局限不支持 9 位字符宽度尽管枚举中存在USART_CHARACTER_SIZE_9BITSUSART with ISO7816 模式仅可用于具备 ISO7816 能力的器件且 SCK 引脚不能直接设置应用需使用GCLK 输出引脚产生 SCK。原文档给出具体算例与 ISO7816 智能卡通信F 372, D 1且波特率 9600 时SCK 引脚输出频率应配置为372 × 9600 3571200 Hz更多细节参考 ISO7816 规范环形缓冲大小必须是 2 的幂源码级约束发送期间不可修改发送缓冲并发约束。7.5 已知问题原文档注明当前驱动暂无已知问题与规避方案Known issues and workarounds: N/A。8. 小结RT-Thread SAM E54 BSP 中的 USART 异步驱动是一套典型的HAL 面向应用、HPL 面向寄存器两层架构应用层只需管理usart_async_descriptor并注册回调即可获得中断驱动的无阻塞收发接收侧环形缓冲天然缓冲突发数据发送侧零拷贝设计则把缓冲生命周期责任明确交给调用方。理解其数据通路、API 参数与底层中断推进逻辑是正确使用 SERCOM USART 异步通信、规避缓冲被提前复用与发送未完成即再次写入两类常见问题的关键。进一步探索可参考usart_async.rst官方文档、hal_usart_async.c驱动实现、driver_init.cBSP 实例化与 driver_examples.c使用示例。【免费下载链接】rt-threadRT-Thread is an open source IoT Real-Time Operating System (RTOS). https://rt-thread.github.io/rt-thread/项目地址: https://gitcode.com/gh_mirrors/rt/rt-thread创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。