news 2026/9/11 8:28:10

瑞萨RA6M5 DTC+UART:低CPU占用的串口数据搬运方案

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
瑞萨RA6M5 DTC+UART:低CPU占用的串口数据搬运方案

简介:开发者可直接基于这份FSP库驱动工程在瑞萨RA6M5/RA6系列上实现DTC+UART串口收发数据,工程支持导入e2 studio或Keil,代码可直接编译运行。资源共19个文件,以C源程序、scat链接脚本和uvprojx/uvoptx工程配置为主,另有xml/gpdsc/pincfg等FSP外设配置文件,压缩包仅31KB,结构紧凑便于定位代码。已有194人学习浏览。工程内含LED控制和调试串口模块,hal_entry.c、memory_regions.scat等文件清晰展示了DTC数据传送控制器与UART协同收发的初始化、中断和回调处理流程,以及RA6M5的存储器布局;通过FSP配置界面生成的外设句柄和引脚定义,能帮助理解DTC传输链、串口波特率/中断优先级等关键设置。适合需要掌握FSP库外设驱动、DTC机制或正在做串口通信开发的中级嵌入式工程师参考。

1. 瑞萨RA6M5的DTC+UART:不是DMA,却比DMA更适合串口搬运

做串口调试时,很多人习惯在循环里直接读UART的RDR寄存器,收一帧没问题,连续收几千字节时CPU占用立刻上来。改成中断接收后,每来一字节都要进一次ISR,高频数据下任务调度很容易被打乱。把瑞萨RA6M5的DTC挂到UART传输链路上,让DTC硬件完成RDR到内存、内存到TDR的搬运,是比DMA更轻量、比纯中断占用更少的做法。这个工程包把SCI_UART和DTC用FSP库驱动串成一条完整链路,既有现成代码,也方便在此基础上改协议帧收发。适合刚接触RA6M5的人快速跑通串口,也适合需要低CPU占用收发逻辑的老手参考。

2. FSP里配置SCI_UART与DTC:先把触发链搭对

2.1 为什么是DTC而不是DMA

RA6M5的DMA通道资源有限,每个通道都要配请求源、传输宽度和搬运方向,做串口收发不是不行,但对硬件理解要求高。DTC同样做数据搬运,但它依赖外设主动发出的事件触发,配置重心从“通道管理”变成“事件绑定”。切片收发时,SCI在发送数据寄存器为空时自动产生一次事件,DTC收到事件就把内存里的下一个字节放进TDR;接收方向同理,RDR收到数据后DTC把字节搬进用户缓冲区。这种事件驱动模型和UART的字节节拍完全对得上,只要看过中断,理解DTC几乎没有额外门槛。

还有一个实际原因:RA6M5的DMA和DTC都能搬数据,但DMA在连续小字节传输时初始化开销更大。UART每字节触发一次,DTC的响应延迟比DMA更可预测,因为DTC由CPU总线直接完成搬运,没有分离的通道描述符需要维护。很多从STM32切过来的工程师习惯找DMA,其实在瑞萨RA6M5上,DTC才是串口低占用收发的默认选择。配置DTC时,要把它想成一个“由事件激活的自动指针搬运器”,而不是降级版DMA。

2.2 RASC里三步搭出UART+DTC

2.2.1 新建工程与堆栈添加

在e2 studio中基于RASC插件创建FSP工程,芯片选R7FA6M5BH3CFC。先添加Stack:r_sci_uart,再分别添加两个r_dtc实例:一个给TX,一个给RX。很多人只添加一个DTC实例,收发共用一个通道。不是不行,但要反复Reset并修改源/目的地址,收发方向不同、计数也不同,调试时容易互相踩。分成TX/RX两个实例,参数隔离更清楚,回调函数也能分开写。

添加顺序建议UART在前,DTC在后。DTC配置界面里需要选择Activation Source,也就是触发源。如果UART堆栈还没创建,下拉列表里就不会出现对应的SCI事件,这个顺序坑在FSP 4.x里很常见。

2.2.2 UART参数与DTC触发绑定

UART部分配置:115200、8位数据、无校验、1停止位,回调函数命名如uart0_callback。打开DTC相关的接收中断或传输完成中断,优先级设置为5以上。RA中断优先级数值越低优先级越高,但不要把串口中断设成0,否则容易和系统时钟互锁。DTC实例的Activation Source下拉框选择SCI_UART TX事件或SCI_UART RX事件。源地址和目的地址在这时候也要选好,发送DTC的源地址指针递增、目的地址固定指向UART_TDR;接收DTC则反过来。

RASC里各版本对DTC属性名称有细微差异,下面是我常用的参数基线,抄作业时对照着看就行。

配置项TX DTC实例RX DTC实例
Activation SourceSCI_UART TX事件SCI_UART RX事件
ModeNormalNormal
Transfer Size1 Byte1 Byte
Source Address内存缓冲区,递增固定为UART_RDR
Destination Address固定为UART_TDR内存缓冲区,递增
DTC Interrupt传输完成使能传输完成使能
Callbackdtc_tx_callbackdtc_rx_callback

这个表格的核心在“固定”和“递增”这两个动作上。发送时,源地址每搬运一字节就加1,目的地址始终指向TDR;接收时,源地址始终指向RDR,目的地址每搬运一字节就加1。搞清楚哪边动、哪边不动,DTC配置就成功了一大半。

2.3 生成代码后先确认句柄

点击Generate Project Content后,hal_data.c会生成g_uart0_ctrl、g_dtc_tx_ctrl、g_dtc_rx_ctrl等句柄。在hal_entry.c里先做Open,再跑一个空回显验证配置。

#include "hal_data.h" void dtc_uart_init(void) { fsp_err_t err = R_SCI_UART_Open(&g_uart0_ctrl, &g_uart0_cfg); if (FSP_SUCCESS != err) { __BKPT(0); } err = R_DTC_Open(&g_dtc_tx_ctrl, &g_dtc_tx_cfg); if (FSP_SUCCESS != err) { __BKPT(0); } err = R_DTC_Open(&g_dtc_rx_ctrl, &g_dtc_rx_cfg); if (FSP_SUCCESS != err) { __BKPT(0); } }

代码逻辑是先把SCI_UART初始化好,再打开DTC。DTC依赖事件源,UART必须先Open,否则DTC的触发源还没有建立,Open时可能返回FSP_ERR_INVALID_MODE。__BKPT(0)在仿真时会停在断点,Release版建议换成错误状态变量,方便定位哪一步失败。

配置完成之后,不要急着写业务逻辑。先确认hal_data.h里确实生成了R_DTC_Open的原型,再确认configuration.xml里DTC事件源没有被其他外设占用。RA6M5的DTC事件源是共享的,比如GPT定时器的比较匹配和UART接收事件如果指向同一个DTC通道,后面启动时会互相抢事件。

3. DTC收发实现:Normal模式下的缓冲搬移与完成判定

3.1 三种DTC模式的选择边界

RA6M5的DTC支持Normal、Repeat、Block三种传输模式,差异在传输结束后的地址和计数值行为。

模式传输结束行为适合场景
Normal计数值到0后停止,地址不复位定长帧收发
Repeat地址回到初值,计数值重载固定缓冲区持续搬运
Block块传输,配合额外计数多块连续数据、复杂结构体搬运

串口收发用Normal就够了。Repeat模式的地址自动回到初值,看起来很适合做环形缓冲区,但遇到半包和粘包时很难判断当前写到了哪个位置,反而增加协议层负担。Block模式需要额外计数寄存器,UART这种单字节触发的外设根本用不上。遇到有人一上来就配Repeat、再加链式配置,多半是把DMA的环形思想硬套到DTC上,结果在中断回调里数不清长度。

Normal模式还有一层好处:一次传输结束后,DTC不会自动重启,需要显式调用R_DTC_Enable。这样上层协议天然获得了“一帧搬运结束”的边界,不用再额外判断。短帧收发、命令回显、Modbus帧处理,全部可以按这个边界来设计。

3.2 发送路径:把内存缓冲区交给DTC

先定义状态和回调。发送DTC用bit状态锁避免上层重复写入缓冲区。

static volatile bool tx_busy = false; void dtc_tx_callback(dtc_callback_args_t * p_args) { if (DTC_EVENT_TRANSFER_END == p_args->event) { tx_busy = false; } } bool dtc_uart_send(uint8_t * buf, uint16_t len) { if (tx_busy) { return false; } tx_busy = true; fsp_err_t err = R_SCI_UART_Write(&g_uart0_ctrl, buf, len); if (FSP_SUCCESS != err) { tx_busy = false; return false; } return true; }

R_SCI_UART_Write在FSP内部检测到配置了传输用DTC后,会把缓冲区首地址交给DTC并启动传输。这样做的好处是不需要直接操作DTC寄存器,FSP会根据len更新DTC的传输计数。tx_busy用来挡住上层重复调用,防止前一次搬运还没完成,后一次就把同一个缓冲区的数据改写掉。

如果读者想完全走DTC API,可以这样写:

fsp_err_t err = R_DTC_Reset(&g_dtc_tx_ctrl, (void *)buf, (void *)g_uart0_ctrl.p_reg->TDR, len); err = R_DTC_Enable(&g_dtc_tx_ctrl);

这个直接控制DTC的方式,第二个参数是源地址内存,第三个参数是SCI的TDR寄存器地址。但注意:如果FSP已经帮UART接好传输DTC,就不要再手动Reset同一个通道,否则会出现重复启动。日常项目我更推荐R_SCI_UART_Write,它会统一处理UART的TE和TCI逻辑,代码量也更少。

3.3 接收路径:把RDR里的字节搬进缓冲区

接收方向和发送方向相反,但DTC的Event也从SCI来。接收启动函数如下:

#define RX_BUFFER_LEN 64 static volatile uint8_t rx_buffer[RX_BUFFER_LEN]; static volatile bool rx_complete = false; void dtc_rx_callback(dtc_callback_args_t * p_args) { if (DTC_EVENT_TRANSFER_END == p_args->event) { rx_complete = true; } } void dtc_uart_receive_start(void) { rx_complete = false; fsp_err_t err = R_DTC_Reset(&g_dtc_rx_ctrl, (void *)g_uart0_ctrl.p_reg->RDR, (void *)rx_buffer, RX_BUFFER_LEN); if (FSP_SUCCESS != err) { return; } err = R_DTC_Enable(&g_dtc_rx_ctrl); if (FSP_SUCCESS != err) { return; } }

R_DTC_Reset的第二个参数是UART接收寄存器RDR的地址,第三个参数是内存缓冲区rx_buffer,第四个参数是传输长度。R_DTC_Enable之后,只要UART收到一个字节,硬件就会自动把RDR内容搬到rx_buffer,DTC内部计数减1,计数到达0时触发DTC_EVENT_TRANSFER_END,rx_complete置true。

要注意rx_buffer是volatile数组,因为DTC在硬件层面写它,编译器必须重新读取。如果业务层在中断关闭状态下读rx_buffer,建议先关IRQ再读取,读完恢复状态,否则可能读到半个帧的数据。

接收方向也可以使用R_SCI_UART_Read配合DTC,但FSP中接收DTC启动后,R_SCI_UART_Read的语义和发送略有不同。直接使用R_DTC_Reset/R_DTC_Enable更直观,也能看到DTC计数器的变化。

3.4 DTC状态掩码与传输完成判定

调试时最怕“好像没发完”和“到底收了多少”。FSP提供R_DTC_StatusGet,可以读到DTCSTS寄存器状态:

dtc_status_t status; R_DTC_StatusGet(&g_dtc_tx_ctrl, &status); if (status.sts) { /* 有未完成或溢出的传输状态 */ }

DTCSTS寄存器通常包含ACT和STS两个状态位。调试时可以把这两个位当成DTC状态掩码来用,取值含义如下表所示。

名称读取时通常含义
bit0ACT当前DTC正在响应传输请求
bit1STS传输计数溢出或存在未清除的请求

最常用的完成判定还是回调里的DTC_EVENT_TRANSFER_END,状态掩码用来排查异常。收到完整一帧后STS仍然挂起,说明还有一次传输请求没有被清掉,需要先禁止DTC,再读数据,最后重新Enable。如果只清标志不查状态,下一次传输可能带着脏数据启动。

4. Keil工程移植:RASC生成代码与uvprojx的衔接

4.1 工程包里到底哪些文件要用

资源里同时能看到e2 studio风格的配置文件,也有EBF_RA6M5.uvprojx,说明这个包是双工程格式。直接用Keil打开uvprojx是最快路径。如果Keil缺少RASC插件,打开configuration.xml时会提醒找不到解析器,但代码仍然可以编译。我一般先用e2 studio生成好FSP代码,再用Keil打开uvprojx;每次改配置都回到e2 studio做,避免两边同时手工改,否则对比版本时很难说清楚谁覆盖了谁。

文件/目录作用移植时是否必须
configuration.xmlFSP配置源文件必须,Keil插件识别用
.pincfg引脚配置关联生成
src/hal_entry.c用户入口必须,修改业务代码的位置
ra_cfg.h / ra_cfg.txtFSP生成宏定义必须加入Include路径
memory_regions.scat链接脚本Keil链接必须
EBF_RA6M5.uvprojxKeil工程描述直接打开

4.2 Keil里编译要确认的编译器选项

RA6M5推荐AC6,不要用AC5。FSP库很多宏是按ARMCC6写的,AC5下会报未定义的类型。如果打开工程后编译报错大半是“Unsupported compiler”,先在Options for Target里把Compiler换成AC6,再把Optimization降到-O1甚至-O0,避免FSP库关键寄存器访问被优化掉。

可以在hal_entry.h里加一个编译期检查,拿到新电脑编译时第一时间看到工具链问题:

#if !defined(__ARMCC_VERSION) #error "This FSP project requires ARM Compiler 6 (AC6)" #endif

__ARMCC_VERSION是AC6预定义宏,AC5也有但版本号范围不同,实际使用时要按版本段判断。这个错误提示比Keil那一大串warning直观得多。另一个容易忽略的点是C99/C11标准,FSP源码用了stdint.h、匿名结构体等特性,在AC6默认标准下没问题,如果手动改了项目标准到C90,会冒出大量声明位置错误。

4.3 链接脚本和RAM/Flash边界

RA6M5不同型号Flash/RAM差异大,资源里的memory_regions.scat是按型号生成的。如果只换芯片不改脚本,最常见错误是链接器报Region RW_IRAM1 overflowed by ...。不要直接改scat里的地址,去configuration.xml里确认芯片型号是R7FA6M5BH3CFC,再重新Generate Project Content,让RASC输出匹配的scat。

移植到Keil后如果出现hard fault,优先查DTC中断向量表是否被Keil的启动文件覆盖。RA6M5的DTC中断入口在FSP的bsp启动文件里定义,Keil启动文件一般只配置系统时钟,外设中断向量由FSP生成。若重复定义中断处理函数,链接器会选一个,选错就会出现进不了回调的情况。

检查办法很简单:在dtc_tx_callback里打断点,如果跑不到,再在DTC_IRQHandler里打断点。能用调试器看到DTC_IRQHandler触发但回调没触发,说明FSP生成的中断映射和Keil启动文件不一致。

5. 验证技巧:用状态寄存器和逻辑分析仪确认DTC时序

5.1 回环测试

把TX与RX短接,或者连接USB-TTL模块,调用接收启动后发送一帧固定数据,在回调里把收到的内容原样发出去:

void hal_entry_loop(void) { while (1) { if (rx_complete) { rx_complete = false; dtc_uart_send((uint8_t *)rx_buffer, RX_BUFFER_LEN); dtc_uart_receive_start(); } } }

注意RX_DTC一次只收RX_BUFFER_LEN字节,收满才会置位rx_complete,所以这个回环是定长回环。如果想处理不定长帧,通常的做法是用UART空闲中断配合DTC:DTC负责把字节搬进缓冲区,空闲中断到达时读取当前DTC计数,用“预设计数减当前剩余计数”算出实际收到多少字节。

5.2 三步定位法

第一步,DTC TX发送时观察TX引脚波形。逻辑分析仪采样率至少2Mbps,触发沿设为下降沿,抓发送起始位,确认波形是连续帧而不是断断续续的字节。第二步,配置RX_DTC后从外部发短帧,观察RX引脚波形,同时在调试器里看rx_buffer内容是否逐字节更新。第三步,用DTC状态掩码确认传输结束:调用R_DTC_StatusGet读取sts位,如果发送完毕后sts仍为1,说明DTC还有未响应的请求,多半是UART的TCI事件又触发了一次,需要先停止传输再读取。

处理接收中途停止时有个实用技巧:先调用R_DTC_Disable,再读DTC内部剩余传输计数,用“总长度减剩余计数”得到已收字节数。这样即使还没等到DTC_EVENT_TRANSFER_END,也能精确知道缓冲区里有多少有效数据,配合空闲中断就能把不定长串口帧处理得干干净净。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/11 8:27:55

LlamaIndex RemoteReader 实战:用 URL 直接加载远程网页与文件

LlamaIndex RemoteReader 实战:用 URL 直接加载远程网页与文件 【免费下载链接】llama_index LlamaIndex is the leading document agent and OCR platform 项目地址: https://gitcode.com/GitHub_Trending/ll/llama_index 导读 RemoteReader 是 LlamaIndex…

作者头像 李华
网站建设 2026/9/11 8:27:02

基于FPGA的DDS信号发生器:Verilog实现相位累加器与正弦查找表

简介:面向FPGA学习者和数字信号处理工程师,这份实战训练资料围绕DDS信号输出展开,以FPGA工程为载体,完整演示了从相位累加器、查找表(LUT)到DAC接口的实现流程,并涉及频率精度、相位噪声、实时性…

作者头像 李华
网站建设 2026/9/11 8:25:39

智能汽车软件可控感设计与工程实践

1. 汽车软件品牌升级的核心挑战与破局点在智能汽车快速迭代的今天,软件定义汽车已成为行业共识。去年某头部车企的OTA升级事故导致大规模车辆停摆,直接暴露出软件可控性的致命短板——当车机系统崩溃时,连最基本的车窗控制都失效。这个典型案…

作者头像 李华
网站建设 2026/9/11 8:21:45

ARM嵌入式AI实战:ML-KWS-for-MCU源码深度解析与内存优化

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/11 8:20:08

昇思MindSpore大模型对齐实战:从RLHF到DPO的偏好优化指南

做昇思MindSpore大模型训练的朋友,可能都有过这种时刻:基座模型辛辛苦苦预训练完,loss低得漂亮,给它一句提示词也能生成一大段通顺的文字,可一旦让它"按给定格式输出"或者"只回答要点"&#xff0c…

作者头像 李华