简介:这份资源是瑞萨RH850F1L微控制器的CAN通信驱动官方示例代码,面向从事汽车电子与嵌入式开发的工程师及学习者,帮助其快速理解并上手该芯片内置CAN控制器的编程方法。压缩包共9个文件,约1MB,包含3个C源文件、2个汇编文件、2个头文件、1份PDF说明文档及1个工程文件,覆盖初始化配置、CAN帧构造与解析、发送接收函数、中断处理、错误检测与滤波器设置等核心模块,PDF文档则提供官方寄存器与流程说明。目前已有699人学习下载。通过研读这些代码,读者可掌握波特率与工作模式配置、多通道管理及中断服务例程的编写思路,并借助示例应用理解车辆状态模拟等实际场景,为在RH850F1L平台上开发稳定可靠的CAN通信应用提供直接参考。
1. 瑞萨RH850F1L CAN通信驱动:从官方示例到量产代码的距离
拿到瑞萨RH850F1L的CAN通信驱动官方示例代码,很多人的第一反应是“编译过了,灯闪了,收发了,收工”。但真把它塞进整车网络或者工业总线节点里跑上电老化,问题就来了:总线一忙就丢帧,错误帧一多就进Bus Off,休眠唤醒后滤波器全乱。官方示例的价值不在于“能跑”,而在于它把RH850F1L这颗MCU的CAN控制器(RSCAN)寄存器操作、中断向量、引脚复用这些底层细节摊开给你看。它解决的是“从零到一”的通信打通问题,适合已经熟悉RH850架构、需要快速验证CAN物理层和底层驱动逻辑的嵌入式工程师。但示例代码到量产代码之间,隔着中断优先级配置、FIFO深度调优、Bus Off恢复策略这三座大山。这篇笔记就按“先看懂示例在干什么、再动手改出能用的驱动、最后把坑填平”的顺序,把RH850F1L的CAN驱动从官方Demo推到能上总线的程度。
2. 拆解官方示例:RSCAN控制器在RH850F1L上怎么被点亮的
官方示例代码通常包含三个核心文件:CAN初始化配置、收发中断服务函数、以及一个简单的回环或收发测试主循环。要把它变成自己的东西,得先搞清楚RH850F1L的RSCAN模块和通用CAN控制器在寄存器层面的差异。
2.1 时钟与引脚:示例里最容易忽略的“默认正确”
RH850F1L的CAN模块时钟源来自PLL或者外部晶振分频,官方示例往往直接写死一个分频系数,因为它的评估板晶振是固定的。但你的板子可能跑不同频率。RSCAN的波特率计算公式是:波特率 = CAN时钟 / (BRP × (1 + TSEG1 + TSEG2))。示例代码里通常用宏定义把BRP、TSEG1、TSEG2写死,你需要根据实际CAN时钟反推。
引脚方面,RH850F1L的CAN收发引脚通常复用在P10_0和P10_1这类端口上,示例代码里会有一句PORT.PIBC10 &= ~(1<<0)之类的寄存器操作来关闭输入缓冲,或者配置PMC寄存器切换为CAN功能。很多人移植时只改波特率不改引脚复用,结果示波器上TX有波形、CAN_H没动静,就是这里翻车。
/* 以RH850F1L的RSCAN0通道0为例,配置引脚复用为CAN功能 */ /* 假设使用P10_0作为CTXD0,P10_1作为CRXD0 */ void CanPinMuxInit(void) { /* 关闭P10_0和P10_1的输入缓冲,避免数字输入干扰 */ PORT.PIBC10 |= (1 << 0); /* 关闭P10_0输入缓冲 */ PORT.PIBC10 |= (1 << 1); /* 关闭P10_1输入缓冲 */ /* 将P10_0和P10_1切换为CAN功能 */ PORT.PMC10 &= ~(1 << 0); /* 清除PMC位,选择复用功能 */ PORT.PMC10 &= ~(1 << 1); /* 设置P10_0为输出方向,P10_1为输入方向 */ PORT.PM10 &= ~(1 << 0); /* P10_0输出 */ PORT.PM10 |= (1 << 1); /* P10_1输入 */ /* 选择复用功能为CAN0 */ PORT.PFC10 &= ~(1 << 0); PORT.PFC10 &= ~(1 << 1); }这段代码的逻辑是:先关输入缓冲防止悬空引脚引入噪声,再通过PMC寄存器把引脚从通用IO切到外设复用,最后用PM寄存器定方向。参数上,PIBC、PMC、PM、PFC这几个寄存器在RH850F1L的用户手册里都有详细位定义,不同封装的端口号可能不同,移植时对着原理图查。
2.2 邮箱与FIFO:示例用邮箱,量产得看FIFO
官方示例为了代码简洁,通常用RSCAN的“邮箱”机制——每个发送或接收邮箱独立配置ID和掩码,中断里逐个判断。但RH850F1L的RSCAN有16个发送邮箱和16个接收邮箱,如果总线上有几十个ID要过滤,邮箱就不够用了。这时候得切到FIFO模式。
RSCAN的接收FIFO有RF0到RF7共8个,每个FIFO可以配置深度(最多16级)和接收规则。示例代码里一般只初始化RF0,深度设为1,中断里读一个邮箱。要改成FIFO,需要操作RFCC0到RFCC7这些寄存器,设置RFDC(深度)、RFID(ID)、RFIDM(掩码)。
/* 配置接收FIFO RF0,深度16,接收ID范围0x100~0x1FF */ void CanRxFifoInit(void) { /* 先进入RSCAN配置模式 */ RSCAN0.CTL0 = 0x00000001; /* 设置配置模式 */ while (RSCAN0.CTL0 & 0x00000001); /* 等待模式切换完成 */ /* 配置RF0的接收规则:ID 0x100,掩码0x700,即接收0x100~0x1FF */ RSCAN0.RFID0 = 0x00000100; /* 基础ID */ RSCAN0.RFIDM0 = 0x00000700; /* 掩码,只比较高9位 */ /* 设置FIFO深度为16 */ RSCAN0.RFDC0 = 0x0000000F; /* 深度16,值=深度-1 */ /* 使能RF0接收 */ RSCAN0.RFCC0 = 0x00000001; /* 使能FIFO接收 */ /* 退出配置模式,进入通信模式 */ RSCAN0.CTL0 = 0x00000000; while (RSCAN0.CTL0 & 0x00000001); }逻辑说明:RFID0和RFIDM0配合实现ID过滤,掩码位为1表示该位参与比较,为0表示忽略。深度设置成16意味着可以缓存16帧不丢,适合总线负载高的场景。参数上,RFDC的值是深度减一,写15就是16级。注意配置FIFO必须在RSCAN的配置模式下进行,通信模式下写这些寄存器无效。
2.3 中断向量与优先级:示例里默认的“够用”在量产里不够
官方示例的中断服务函数通常只处理一个中断源,比如RSCAN0_RXF0。但实际项目里,发送完成、接收FIFO、错误警告、Bus Off这些中断都得处理。RH850F1L的中断控制器(INTC)支持优先级分组,示例代码里往往把CAN中断优先级设成默认值,结果和定时器中断抢资源,导致接收溢出。
常见做法是:接收FIFO中断优先级设最高(比如优先级3),发送完成中断设中(优先级2),错误中断设低(优先级1)。在INTC的ICxxx寄存器里配置。另外,RH850F1L的CAN中断可以映射到不同的中断通道,示例里用哪个通道,移植时得在vector table里对应上。
3. 从示例到驱动:把收发逻辑改成能上总线的状态机
官方示例的收发逻辑通常是“发一帧、等中断、收一帧、打印”。但真实总线是异步的,发送请求可能来自应用层任务,接收数据要缓存到队列。这一章把示例代码重构成一个带状态机和环形缓冲的驱动。
3.1 发送:从阻塞等待到非阻塞队列
示例里发送函数可能是这样的:填邮箱、置发送请求位、while等待发送完成标志。这在单任务裸机里勉强能用,但如果有多个任务要发不同ID的帧,就串了。改成非阻塞的方式:维护一个发送邮箱数组,每个邮箱有“空闲/占用”标志,发送函数找空闲邮箱填入,置位后立即返回。发送完成中断里清标志。
/* 发送邮箱管理结构 */ typedef struct { uint32_t id; uint8_t data[8]; uint8_t dlc; uint8_t busy; /* 0=空闲,1=占用 */ } CanTxMailbox; CanTxMailbox txMailbox[16]; /* RH850F1L有16个发送邮箱 */ /* 非阻塞发送函数,返回0成功,-1无空闲邮箱 */ int CanSendFrame(uint32_t id, uint8_t *data, uint8_t dlc) { uint8_t i; for (i = 0; i < 16; i++) { if (txMailbox[i].busy == 0) { /* 找到空闲邮箱,填入数据 */ txMailbox[i].id = id; txMailbox[i].dlc = dlc; memcpy(txMailbox[i].data, data, dlc); txMailbox[i].busy = 1; /* 写RSCAN发送邮箱寄存器 */ RSCAN0.TMIDi = id; /* 设置ID */ RSCAN0.TMDLCi = dlc; /* 设置DLC */ /* 写数据到TMDATA0和TMDATA1 */ RSCAN0.TMDATA0i = *(uint32_t*)&data[0]; RSCAN0.TMDATA1i = *(uint32_t*)&data[4]; /* 置发送请求位 */ RSCAN0.TMTRFi = 0x00000001; return 0; } } return -1; /* 无空闲邮箱 */ } /* 发送完成中断服务函数 */ void CanTxCompleteIsr(void) { uint8_t i; for (i = 0; i < 16; i++) { if (RSCAN0.TMTRFi & 0x00000001) { /* 发送完成,清标志和忙标志 */ RSCAN0.TMTRFi = 0x00000000; txMailbox[i].busy = 0; } } }逻辑说明:发送函数遍历邮箱数组找空闲项,填入后置位TMTRF触发发送。中断里检查每个邮箱的发送完成标志,清忙状态。参数上,TMIDi、TMDLCi、TMDATA0i、TMDATA1i是RSCAN的发送邮箱寄存器,i是邮箱编号0~15。注意TMDATA0和TMDATA1各存4字节,小端模式下直接memcpy即可。
3.2 接收:环形缓冲加ID过滤
示例的接收中断通常直接读数据然后置个全局变量。改成环形缓冲:定义一个CanRxBuffer结构,包含buf[64][8]、head、tail、count。接收中断里读FIFO,写入缓冲,更新head。应用层从tail读。
/* 接收环形缓冲 */ typedef struct { uint32_t id[64]; uint8_t data[64][8]; uint8_t dlc[64]; uint8_t head; uint8_t tail; uint8_t count; } CanRxRingBuffer; CanRxRingBuffer rxBuf; /* 接收FIFO中断服务函数 */ void CanRxFifoIsr(void) { uint32_t id; uint8_t dlc; uint8_t data[8]; /* 从RF0读取一帧 */ id = RSCAN0.RFID0; /* 读取ID */ dlc = RSCAN0.RFDLC0; /* 读取DLC */ *(uint32_t*)&data[0] = RSCAN0.RFDATA0; *(uint32_t*)&data[4] = RSCAN0.RFDATA1; /* 写入环形缓冲 */ if (rxBuf.count < 64) { rxBuf.id[rxBuf.head] = id; rxBuf.dlc[rxBuf.head] = dlc; memcpy(rxBuf.data[rxBuf.head], data, dlc); rxBuf.head = (rxBuf.head + 1) % 64; rxBuf.count++; } /* 如果count满,丢弃或置溢出标志 */ /* 清接收FIFO中断标志 */ RSCAN0.RFSTS0 &= ~0x00000001; } /* 应用层读取函数 */ int CanReadFrame(uint32_t *id, uint8_t *data, uint8_t *dlc) { if (rxBuf.count == 0) { return -1; /* 无数据 */ } *id = rxBuf.id[rxBuf.tail]; *dlc = rxBuf.dlc[rxBuf.tail]; memcpy(data, rxBuf.data[rxBuf.tail], *dlc); rxBuf.tail = (rxBuf.tail + 1) % 64; rxBuf.count--; return 0; }逻辑说明:中断里从RFID0、RFDLC0、RFDATA0/1读一帧,写入环形缓冲。head和tail用模运算实现循环。参数上,缓冲深度64是经验值,根据总线负载调整;RFSTS0是接收FIFO状态寄存器,清中断标志位。注意中断里不要做memcpy以外的耗时操作,否则会丢帧。
3.3 错误处理:Bus Off恢复不能只靠“重新初始化”
示例代码对Bus Off的处理通常是“检测到错误就重新初始化CAN”。但RH850F1L的RSCAN有专门的错误状态寄存器RSCAN0.ERFL和RSCAN0.STS,Bus Off时STS.BOFF置位。正确的恢复流程是:检测到Bus Off后,先进入配置模式,复位RSCAN,再重新配置波特率和FIFO,最后退出配置模式。直接重新初始化整个CAN模块会丢失所有邮箱配置。
/* Bus Off恢复函数 */ void CanBusOffRecovery(void) { /* 检查Bus Off标志 */ if (RSCAN0.STS & 0x00000004) { /* BOFF位 */ /* 进入配置模式 */ RSCAN0.CTL0 = 0x00000001; while (RSCAN0.CTL0 & 0x00000001); /* 复位RSCAN */ RSCAN0.CTL1 = 0x00000001; /* 复位 */ while (RSCAN0.CTL1 & 0x00000001); /* 重新配置波特率、FIFO、邮箱 */ CanBitrateInit(); CanRxFifoInit(); CanTxMailboxInit(); /* 退出配置模式 */ RSCAN0.CTL0 = 0x00000000; while (RSCAN0.CTL0 & 0x00000001); /* 清Bus Off标志 */ RSCAN0.STS &= ~0x00000004; } }逻辑说明:CTL0的bit0是配置模式使能,CTL1的bit0是复位请求。复位后所有寄存器恢复默认值,需要重新配置。参数上,STS寄存器的BOFF位在Bus Off时置1,写0清除。注意恢复过程中要关全局中断,防止中断里访问正在复位的寄存器。
4. 避坑与排查:RH850F1L CAN驱动最常见的5个翻车现场
这一章记录我在移植和调试RH850F1L CAN驱动时踩过的坑,每个都按“现象→原因→解决”写清楚。
4.1 现象:发送正常,接收一帧都收不到
原因:接收FIFO的ID过滤掩码配错。示例代码里RFIDM可能设成了0xFFFFFFFF,要求ID完全匹配,但实际发送方ID和配置的RFID差一位。或者RFCC寄存器没使能。
解决:先把RFIDM设成0x00000000,接收所有ID,确认硬件通路没问题。再逐步收紧掩码。用示波器看CRXD引脚有没有波形,有波形但收不到就是过滤器问题。
4.2 现象:总线一忙就丢帧,接收缓冲溢出
原因:接收中断优先级太低,被其他中断打断;或者FIFO深度设得太小(比如深度1),中断响应不及时就溢出。
解决:把接收FIFO中断优先级调到最高,FIFO深度至少设8。如果还丢,检查中断服务函数里有没有浮点运算或长循环。另外,RH850F1L的RSCAN接收FIFO有“阈值中断”功能,可以设成收到4帧才中断,减少中断频率。
4.3 现象:进入Bus Off后无法恢复,必须断电重启
原因:Bus Off恢复函数里没有复位RSCAN,只是清了标志位。或者恢复后没有重新使能FIFO和邮箱。
解决:按3.3节的流程,先复位再重新配置。注意复位后CTL0的配置模式要重新进入。另外,Bus Off恢复后建议延时100ms再发数据,等总线稳定。
4.4 现象:波特率算出来对,但总线上错误帧一堆
原因:采样点位置不对。RH850F1L的TSEG1和TSEG2决定了采样点,示例代码里可能用默认值(比如TSEG1=4,TSEG2=3),采样点在75%左右。但有些总线要求采样点在80%以上。
解决:根据总线规范调整TSEG1和TSEG2。比如波特率500kbps,CAN时钟40MHz,BRP=4,TSEG1=13,TSEG2=4,采样点=(1+13)/(1+13+4)=77.8%。要80%就TSEG1=14,TSEG2=3。
4.5 现象:休眠唤醒后CAN不工作
原因:RH850F1L进入低功耗模式时,CAN模块时钟被关断,唤醒后没有重新初始化。或者唤醒中断里没有清RSCAN的唤醒标志。
解决:在唤醒中断里调用CanBusOffRecovery类似的重新配置流程。注意检查RSCAN0.CTL0的配置模式位,唤醒后可能还在配置模式。另外,休眠前要把发送邮箱清空,否则唤醒后会发残留数据。
5. 进阶技巧:用RSCAN的FIFO阈值中断和DMA减轻CPU负载
官方示例的中断方式是一帧一中断,在500kbps总线满载时,每秒最多2000帧,每帧一次中断,CPU有20%时间在进中断。RH850F1L的RSCAN支持FIFO阈值中断——设置RFSTS的RFTS位,当FIFO里帧数达到阈值才触发中断。比如深度16,阈值设8,中断频率降一半。
更狠的做法是配合DMA。RH850F1L的DMA控制器(DMA)可以把RSCAN的接收FIFO数据直接搬到内存环形缓冲,完全不用CPU干预。配置DMA通道的源地址为RSCAN0.RFDATA0,目的地址为rxBuf.data[head],传输长度8字节,触发源选RSCAN接收FIFO中断。这样CPU只在缓冲半满或全满时处理一次。
/* 配置DMA通道0,从RSCAN RF0搬运数据到环形缓冲 */ void CanDmaInit(void) { /* 设置DMA源地址:RSCAN RF0数据寄存器 */ DMA0.SAR = (uint32_t)&RSCAN0.RFDATA0; /* 设置DMA目的地址:环形缓冲当前head位置 */ DMA0.DAR = (uint32_t)&rxBuf.data[rxBuf.head][0]; /* 传输长度:8字节 */ DMA0.TCR = 8; /* 传输模式:每次触发搬8字节,地址递增 */ DMA0.CHCR = 0x00000001; /* 使能DMA,源地址固定,目的地址递增 */ /* 触发源:RSCAN RF0接收中断 */ DMA0.CHCR |= (0x0A << 8); /* 假设RF0中断号是0x0A */ /* 使能DMA通道 */ DMA0.CHCR |= 0x00000001; }逻辑说明:DMA源地址固定为RFDATA0,目的地址指向环形缓冲的head位置,每次搬8字节。触发源选RSCAN接收中断,这样每收到一帧,DMA自动搬走,CPU只需在缓冲满时处理。参数上,CHCR的bit0是使能位,bit8~15是触发源编号,具体编号查RH850F1L的DMA手册。注意DMA搬完后要手动更新head指针,或者用DMA的传输完成中断来更新。
验证DMA是否工作,可以在DMA传输完成中断里翻转一个GPIO,用示波器看频率。如果频率和接收帧率一致,说明DMA在搬。如果CPU负载没降,检查DMA触发源有没有配错,或者RSCAN的中断有没有使能DMA请求。
我自己的习惯是:先用官方示例把物理层跑通,然后立刻把接收改成FIFO加环形缓冲,发送改成非阻塞邮箱,最后根据总线负载决定要不要上DMA。这套流程走下来,RH850F1L的CAN驱动基本能扛住整车网络的日常通信。希望帮到你。
本文还有配套的精品资源,点击获取