RH850 CSIH+DMA驱动开发:基于GHS的汽车电子外设配置与调试
2026/9/16 5:26:02 网站建设 项目流程

简介:基于瑞萨RH850/F1K汽车级32位MCU的CSIH(SPI)结合DMA数据收发示例工程,面向需要快速上手该系列芯片通信外设的嵌入式工程师与单片机学习者。压缩包共68个文件,以C/H源文件与GHS工程文件为主,辅以链接脚本、map/hex映射与烧录文件及ReadMe说明文档,整体仅869KB,结构紧凑且可直接导入GHS编译环境查看。源码按功能拆分为时钟、复位、中断、CSIH FIFO与DMA等模块,并给出了初始化及收发流程的参考实现,有助于理解汽车级MCU在SPI通信中如何通过DMA降低CPU负载、提升传输效率。工程还提供了详细的项目配置与调试相关文件,便于对照实际硬件进行验证。目前已吸引971人学习/下载,适合项目移植或学习借鉴。

1. 从压缩包命名解码一个嵌入式项目:F1K_GHS_20_R7F701587_CSIH_DMA.7z

拿到F1K_GHS_20_R7F701587_CSIH_DMA.7z这个压缩包,第一眼以为是个普通固件备份,但实际上它是一类很典型的汽车电子控制器软件交付物。F1K 通常是整车厂或 Tier1 内部的平台代号,GHS 指 Green Hills Software 的编译器与集成开发环境,20 多半是版本年号或内部基线号,R7F701587 是瑞萨 RH850/F1K 系列 MCU 的具体型号,CSIH 是瑞萨的串行通信接口(Clock Synchronous Serial Interface with Hardware Chip Select),支持 SPI 模式、I2C 模式和 UART 模式,DMA 则说明这套外设驱动里用了 DMA 通道来搬数据。把这些拼起来,这套代码对应的场景就很清晰了:在 GHS 工程里,为 R7F701587 配置 CSIH 外设,使其作为 SPI/I2C 主站,并通过 DMA 完成大批量数据收发,从而把 CPU 从逐字节搬移中解放出来。

这类项目的读者主要是做汽车电子 BSW(基础软件)和 MCAL(微控制器抽象层)开发的工程师,也包括负责 Bootloader、通信矩阵和传感器驱动集成的同事。解决的痛点很直接:CSIH 本身有发送/接收中断,但中断服务里逐字搬运 16 位数据对 CPU 占用极高,尤其在多路 CAN、多路串口同时工作的控制器里,中断延迟会直接冲击调度。用 DMA 把 CSIH 的收发缓冲区和 RAM 缓冲区对接,是 MCAL 层最常用的优化手段。这篇文章就按“先懂硬件行为,再写配置代码,最后讲调试方法”的顺序,把 CSIH 加 DMA 的完整落地路径讲清楚。

2. GHS 工程里 R7F701587 的时钟树与引脚复用初始化

2.1 为什么 CSIH 加 DMA 的初始化顺序不能乱

CSIH 是挂在 APB 总线上的外设,DMA 控制器则是独立于 CPU 的数据搬移引擎。两点之间的通路依赖三个条件:外设时钟已经使能、引脚复用功能已经切到 CSIH 信号、DMA 控制器的触发源路由表里已经登记了 CSIH 的请求号。任何一个没就位,表现出的故障现象都很迷惑,比如 CSIH 寄存器读写正常但 SCK 引脚无波形,或者 DMA 使能后始终不响应 CSIH 的请求。R7F701587 的外设时钟门控和引脚复用都集中在系统寄存器区,而这些寄存器的地址映射由 GHS 工程的链接脚本决定。瑞萨的 MCAL 一般以R_CSIH_InitR_DMA_Init两个驱动函数对外提供服务,但直接操作寄存器的方式在调试阶段反而更容易定位问题。

2.2 用 GHS 链接脚本定位外设寄存器地址

GHS 的.ld文件(或.ldr链接命令文件)里会有一段类似下面的描述,把芯片外设区域的虚拟地址和物理地址映射到运行时内存视图。R7F701587 的存储映射中,CSIH0 到 CSIH3 的寄存器基址通常在0xFFF300000xFFF4FFFF区间内,DMA 控制器的基址在0xFFF50000附近。实际数值必须对照瑞萨提供的r7f701587.h头文件和硬件手册,不同封装和不同子型号会留有不同的保留区。GHS 的链接脚本里一般只定义程序区和数据区,外设地址不需要声明,直接用__attribute__((section()))或者指针常量访问即可。常见做法是写一个memory map头文件,用宏定义把外设寄存器基址固定下来。

#define CSIH0_BASE (0xFFF30000u) #define CSIH1_BASE (0xFFF31000u) #define DMA0_BASE (0xFFF50000u)

这里用宏定义而不是结构体映射,是因为 CSIH 的寄存器在 R7F701587 上既有 8 位访问也有 16 位访问,不同寄存器宽度不同。宏定义配合读改写函数比结构体更直观,调试时可以直接在 GHS 的调试器里输入地址查看。如果项目里已经有瑞萨的r_rh850_csih_r7f701xxxMCAL 驱动,寄存器定义可以直接复用,但要注意 GHS 的#pragma pack处理方式,避免结构体对齐导致寄存器偏移错误。

2.3 时钟使能与引脚复用配置的代码实现

R7F701587 的时钟树里,外设时钟分为 PLL 时钟和直接来自振荡器的时钟。CSIH 挂在PCLKPCLKH上,DMA 控制器挂在与 CPU 同频的ICLK上。初始化时要先确认PLLPCLK分频系数已经配置完成,否则 CSIH 位速率和 DMA 请求触发时序都会偏移。下面这段代码演示了最基础的使能操作。

/* 使能 CSIH0 和 DMA 的时钟门控 */ SYSC->PERCLKEN |= (1u << 7); /* bit7 对应 CSIH0/CSIH1 的时钟 */ SYSC->PERCLKEN |= (1u << 5); /* bit5 对应 DMA 控制器的时钟 */ /* 等待时钟稳定 */ while ((SYSC->PERCLKEN & (1u << 7)) == 0u) { /* 自旋等待 */ }

逻辑说明:PERCLKEN寄存器控制外设时钟的开关,置位后外设寄存器才可访问。SYSC是系统控制寄存器组的起始地址,具体位号以 R7F701587 参考手册的Clock Generator章节为准。多数情况下,MCAL 驱动已经被 S32K 或 EB tresos 配置工具生成过了,手工写这个汇编级别的驱动主要用在 Bootloader 或者自定义通信场景。参数调整点在两个地方:一个是分频系数,它决定了 CSIH 的波特率;另一个是PERCLKEN的位定义,芯片手册里有一个表逐位列出外设与时钟的对应关系,务必先查表再操作。

引脚复用配置属于PORT控制器的范畴。R7F701587 的引脚功能选择由PMSCRPMC寄存器完成。比如要让P20引脚作为 CSIH0 的 SCK0,需要把PMSCR设置为功能 2,把PMC置位为外设模式。这个操作必须在 CSIH 使能之前完成,否则引脚保持 GPIO 输入状态,SCK 不会产生任何信号。写码时建议直接在R_CSIH_Init中一次性完成引脚设置,不要在应用层再去调整。

/* P20 作为 SCK0,P21 作为 CS0,P22 作为 SO0,P23 作为 SI0 */ PORT->PMC20 = 1u; /* 外设模式 */ PORT->PMSCR20 = 2u; /* 功能选择 */

参数说明:PMC1代表非 GPIO 模式,PMSCR的具体数值由芯片手册的引脚功能分配表定义,不是所有引脚都支持同一组功能编号。配置完成后,建议第一次调试时先不加 DMA,直接用寄存器轮询方式让 CSIH 发送一帧数据,确认 SCK/CS/SO 波形正确,再引入 DMA。这个习惯可以排除一半以上的“DMA 不工作”问题,因为很多情况其实是引脚复用或时钟门控没打开。

提示:GHS 工程中如果使用瑞萨的 MCAL,一定要在R_CG_*R_Config_*初始化函数里检查SystemInit的分频比,有些配置工具生成的代码会把PCLK设置成 120MHz,而 CSIH 和 DMA 的频率上限并不相同,超频时寄存器配置值看似正确,实际物理层时序已经跑飞了。

3. CSIH 的帧格式、片选逻辑与 DMA 触发源配置

3.1 R7F701587 的 CSIH 与 DMA 通道映射关系

CSIH 接口全称是 Clock Synchronous Serial Interface with Hardware Chip Select,它在 R7F701587 上支持三种模式:SPI、I2C 和 UART。最常用的是 SPI 主模式,因为硬件片选引脚可以自动控制 CS 线的拉低和释放,不需要 GPIO 模拟时序。DMA 和 CSIH 的绑定关系在芯片内部是固定的,CSIH0 的发送请求对应 DMA 通道 0,接收请求对应 DMA 通道 1;CSIH1 的则对应通道 2 和通道 3。这个映射在 DMA 控制器的DMA trigger source表里可以查到,GHS 的调试器里也能直接看到DMADSAR(源地址寄存器)和DMADTCR(传输计数寄存器)的实时值。

理解这个映射关系对调试 DMA 至关重要。函数R_DMA_Init接收的配置结构里有一个通道编号参数,如果通道编号和 CSIH 实例不匹配,DMA 即使配置正确也不会被触发。一般芯片厂商提供的 MCAL 驱动会封装好这层映射,但直接阅读寄存器手册后你会发现,这种设计本质上就是为 DMA 请求线与外设中断请求线共享一套外设互连矩阵。调试时看到DMATRGR里的触发源值始终为 0,第一反应就应该是去查通道绑定,而不是怀疑外设没发出请求。

3.2 SPI 主模式单帧传输的寄存器配置

直接操作寄存器时,CSIH 的配置核心集中在CSIH0_CTR(控制寄存器)、CSIH0_STR(状态寄存器)、CSIH0_TDR(发送数据寄存器)和CSIH0_RDR(接收数据寄存器)。其中CTR的位字段要设置数据长度(8 位或 16 位)、CPOL/CPHA 相位、以及片选极性。下面这段代码展示了将 CSIH0 配置成 SPI 主模式、8 位数据、模式 0 的过程。

/* CSIH0 控制寄存器配置 */ CSIH0->CTR = (0u << 22) /* 主模式 */ | (0u << 20) /* CPOL=0 */ | (0u << 19) /* CPHA=0 */ | (1u << 15) /* 使能片选信号 */ | (0u << 11) /* 片选极性低有效 */ | (0u << 10) /* MSB first */ | (7u << 4); /* 8位数据长度 */

逻辑说明:CTR寄存器的位定义在不同子型号上略有差异,R7F701587 上bit22MSS(主从选择),bit20-19是时钟极性和相位,bit11CSLP(片选极性),bit10MSBF(位序),bit4-6DHL(数据长度)。写这段代码时最好打开 GHS 的寄存器窗口实时观察各个位的变化,因为某些 OEM 的芯片版本里CTR的保留位写 1 会导致配置被静默忽略。

发送一帧数据的方法有两种。轮询模式检查STRTEND(发送结束标志),置位后写TDR。DMA 模式则不直接操作TDR,而是让 DMA 把数据从内存缓冲区搬到TDR地址。接收数据正好相反,DMA 把RDR里的内容搬到内存缓冲区。需要注意的坑是:CSIH 的发送和接收共用同一个移位寄存器,主模式下发送一字节的同时必定接收一字节。如果你的通信对象是只写设备(比如 DAC),接收缓冲区里的数据必须丢弃,否则 DMA 接收通道会一直累积垃圾数据。

3.3 DMA 通道的传输类型与触发请求配置

R7F701587 的 DMA 控制器支持两种传输类型:正常传输和块传输。块传输模式下,一次触发可以搬移多字节直到计数归零,适合 CSIH 这种有固定帧长的通信协议。DMA 的相关寄存器包括DMAn_DSAR(源地址)、DMAn_DDAR(目的地址)、DMAn_DTCR(传输计数)、DMAn_DMDR(模式寄存器)和DMAn_DTFR(触发源选择)。

/* DMA 通道 0:内存到 CSIH0_TDR 的 32 字节搬移 */ DMA0_CH0->DSAR = (uint32_t)tx_buffer; /* 源地址指向内存 */ DMA0_CH0->DDAR = (uint32_t)&CSIH0->TDR; /* 目的地址指向寄存器 */ DMA0_CH0->DTCR = 32u; /* 传输计数 */ DMA0_CH0->DMDR = (1u << 16) | (1u << 4) | (1u << 1); /* 触发源为CSIH0发送请求,地址递增 */ DMA0_CH0->DTFR = (1u << 31) | (0u << 24) | (3u << 16); /* 使能通道,普通传输,触发源编号 */

逻辑说明:DMDRbit1是地址更新模式,bit4是传输方向位,bit16-19是触发请求使能。DTFRbit31是软件触发使能位,bit24-27是触发源编号,bit16-17是传输模式。这里的关键参数是触发源编号,它必须与 CSIH0 的 DMA 请求线编号一致。R7F701587 的 DMA 触发源表中,CSIH0 发送请求是 3,CSIH0 接收请求是 4,具体值要查芯片手册。写触发源编号时,不要把外设中断号混进来,这是初学者最常见的错误。

连续传输场景下,DMA 的DTCR计数归零后会产生一个中断请求,可以在中断服务函数里重装载地址和计数,实现循环发送。另一种做法是开启 DMA 的连续模式continuous mode,但这种模式在 CSIH 上有时会导致片选信号无法在帧间精确拉高,必须根据通信对象决定是否开启。如果从设备要求严格的 CS 时序,建议使用中断服务重装载方式,不要依赖连续模式。

提示:初始化 DMA 时如果DSARDDAR的地址没有按照 4 字节对齐,部分 R7F701587 芯片会触发地址对齐错误,这个错误不会在 DMA 寄存器里留下明确标志,而是直接进入Unhandled Exception。把缓冲区声明成__attribute__((aligned(4)))可以彻底规避这个问题。

4. 连续批量数据搬移的应用层设计与调试步骤

4.1 DMA 中断标志与轮询位的配合使用

实际项目里,CSIH 用于传感器数据采集时通常要连续读回多字节结果,发送的多是命令帧或配置帧,所以发送用 DMA、接收用 DMA 加中断是最常见的组合。R7F701587 的 DMA 在传输计数归零后会置位DMAn_ST寄存器里的DTIF标志位,并把中断请求发给 CPU。中断服务函数里要判断是哪个 DMA 通道完成的,还要清标志位,否则同一中断会反复触发。轮询方式则直接读ST寄存器里的TENDDACT字段,适用于发送方向。

下面给出一段发送完成中断的服务函数原型,它对应“发送 4 字节命令,接收 16 字节传感器数据”的典型场景。

void dma_ch0_isr(void) { if (DMA0_CH0->ST & (1u << 8)) /* 传输完成标志 */ { DMA0_CH0->ST = (1u << 8); /* 写1清标志 */ tx_busy = 0u; /* 标记发送空闲 */ } }

参数说明:ST寄存器是中断状态寄存器,读出来之后写 1 清位,写 0 无效,这种“写 1 清零”的行为模式在瑞萨外设里非常常见。tx_busy是全局标志,应用层主循环在发下一帧之前必须确认它为 0。这里没有用volatile修饰标志位,在 -O2 优化下可能会出现逻辑错误,所以实际代码里要声明为volatile uint8_t tx_busy

4.2 批量数据搬移的完整代码结构

批量搬移的场景以“发送命令,等待从设备响应,DMA 接收响应”为主轴。代码结构上,发送和接收各占一条 DMA 通道,两条通道可以同时工作,但接收通道要在发送完成后再使能,否则从设备还没开始输出,接收缓冲区就已经进入等待状态了。代码的主流程如下。

uint8_t tx_buffer[8] __attribute__((aligned(4))); uint8_t rx_buffer[64] __attribute__((aligned(4))); void csih_transfer(uint8_t *cmd, uint8_t len) { /* 准备发送数据 */ memcpy(tx_buffer, cmd, len); /* 配置发送通道 */ DMA0_CH0->DSAR = (uint32_t)tx_buffer; DMA0_CH0->DDAR = (uint32_t)&CSIH0->TDR; DMA0_CH0->DTCR = len; DMA0_CH0->DMDR |= (1u << 16); /* 使能触发请求 */ /* 配置接收通道,等发送完成后手动启动 */ DMA0_CH1->DSAR = (uint32_t)&CSIH0->RDR; DMA0_CH1->DDAR = (uint32_t)rx_buffer; DMA0_CH1->DTCR = 16u; DMA0_CH1->DMDR |= (1u << 16); /* 等待发送完成中断 */ tx_busy = 1u; while (tx_busy == 1u) { /* 等待ISR置零 */ } /* 启动接收 */ DMA0_CH1->DTFR |= (1u << 31); }

逻辑说明:这个流程里接收通道的DTFR使能位在发送完成后才置位,目的是避免接收通道提前进入等待而丢失从设备的前导字节。DSARDDAR都是重新配置的,因为每次传输的缓冲区地址和长度可能不同。有一个细节值得注意:中断方式下DTCR计数归零后,DMA 通道会自动失能,下次使用必须重复配置DTFR使能位,不能只在初始化时配一次。

4.3 调试时必看的寄存器与常见故障定位

拿到一套不工作的 CSIH 加 DMA 代码,我不会先去翻逻辑代码,而是直接看三个地方:DMA 通道的ST寄存器有没有报错、DTCR有没有在递减、CSIH 的STRTDRE有没有置位。TDRE表示发送数据寄存器空,如果它一直为 0,说明 DMA 根本没往TDR写数据,问题在 DMA 配置;如果它一直为 1 但DTCR不递减,说明 DMA 响应了请求但源地址读不出数据,检查缓冲区地址和内存保护。

CSIH 侧的问题则大多表现为STRUDR(下溢)或OVR(上溢)标志置位。发送方向出现UDR说明 DMA 数据供不上,时序要求严格的场景下需要降低 SPI 波特率或改用块传输方式;接收方向出现OVR说明 CPU 读取接收缓冲太慢,应该加大 DMA 接收缓冲或提高中断优先级。这些标志位用调试器直接在 GHS 的寄存器视图中观察最直接,不需要额外打印日志。

# GHS MULTI 调试器命令行查看寄存器值(示例) rd CSIH0_STR rd DMA0_CH0_ST rd DMA0_CH0_DTCR

参数说明:rd是 GHS 调试器的寄存器读取命令,执行后输出对应寄存器的十六进制值。查看DTCR如果发现计数从 16 减到 0,说明 DMA 正常搬完了,问题大概率在 CSIH 的波特率或从设备响应时序上。这组命令在 GHS 的 Debug 控制台直接输入即可,不需要额外配置脚本。如果把断点下在 DMA 中断里,断点命中时DTCR的值通常已经为 0,要观察递减过程需要把断点下在DMDR使能位置之后的几行代码上。

提示:CSIH 的发送和接收缓冲区大小实际是 4 字节深度的 FIFO,不是单字节缓冲。DMA 连续发送时如果帧长小于 4 字节,会出现“FIFO 里还有数据但 DMA 已经完成”的现象,导致 CS 线提前释放。解决方法是把最小传输长度设置为 4 字节,或者关闭 FIFO 功能,具体看CTR寄存器的FIFO位域设置。

5. 这套方案里值得单独跑一遍的时序与压力验证

CSIH 加 DMA 的方案在收尾阶段,我一般会单独写一个测试用例,把 NAND Flash 或外部 SRAM 作为从设备,连续读写大块数据来验证时序余量和 DMA 通道协同能力。这个用例的目标不是功能正确,而是测出在哪个波特率下UDROVR开始出现,从而为量产留出安全余量。

测试代码的思路很简单:发送通道的缓冲区填充随机数,接收通道收到后做异或校验。如果连续跑 10000 帧没有校验错误,说明当前波特率下时序余量充足。如果出现错误,把波特率降一档再跑。这个测试套件在 Bootloader 开发阶段尤其重要,因为 Bootloader 升级时对通信可靠性要求最高,而且中断上下文和应用层不一样,DMA 中断里不能调用蓝牙或网络协议栈的函数,这些阻塞操作全部要放到主循环。

另外一个值得关注的点是DMA continuous requests这种网络热词里反复出现的需求。很多从业者在实际调试中希望 DMA 通道在传输完成后自动重新装载、不产生中断、持续搬移,用来构建某种数据流管道。但 CSIH 场景下这个模式容易引入片选信号的毛刺。我一般做法是关闭连续模式,改为在 DMA 完成中断里重新装载DSARDTCR,并把 CSIH 配置成多帧模式下片选只在整批数据结束后拉高。这样既保留了 DMA 的低 CPU 占用,又让硬件片选时序完全可控。

最终的验证手法可以落在一个很小的观察上:在 GHS 调试器里同时开启 CSIH 的STR与 DMA 的DTCR窗口,把传输长度设成 64 字节,连续触发 100 次,观察DTCR是否每次都精确回到 64。如果出现个别次数变成 63 或 62,说明 DMA 触发出错或源地址边界有问题,这种偶发错误在功能测试中不会暴露,但会在整车电磁兼容测试时突然冒出来。把这个计数器校验写进测试用例,比任何代码走查都能更快定位到真实问题。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询