4、CAN驱动开发(裸机):寄存器操作、初始化流程、发送与接收中断处理
好,咱们今天来聊聊CAN驱动的裸机开发。说实话,很多工程师一上来就怼着HAL库或者SDK猛啃,结果出了问题连寄存器都找不到在哪。我个人习惯,做底层通信,一定要先把寄存器摸透。你想想看,连硬件怎么工作的都不清楚,出了问题你怎么定位?
4.1 寄存器操作:别怕,就那么几个
CAN控制器内部,说白了就是一堆寄存器。我当年第一次接触CAN模块,看着几百页的数据手册也头大。但后来发现,真正要你亲手配的,其实就那么几个关键寄存器。
以常见的SJA1000或者STM32的bxCAN为例,核心寄存器包括:
- 模式寄存器(MOD):控制CAN控制器的工作模式,比如复位模式、正常模式、监听模式。
- 时钟分频寄存器(CDR):配置CAN时钟源和分频系数。
- 位时序寄存器(BTR):这个最关键,决定了你的CAN总线波特率。
- 中断使能寄存器(IER):打开发送完成、接收有效、错误等中断。
- 状态寄存器(SR):读取当前控制器状态,比如是否正在发送、接收缓冲区是否有数据。
- 发送缓冲区(TXB):你要发出去的数据,填到这里。
- 接收缓冲区(RXB):收到的数据,从这里取走。
嗯,这里要注意,操作寄存器时,一定要先看数据手册里关于“访问权限”的描述。有些寄存器只能在复位模式下修改,有些则可以在正常模式下直接读写。我在项目中遇到过,有同事在正常模式下写位时序寄存器,结果CAN总线直接罢工,查了半天才发现是寄存器被写保护了。
核心原则: 操作寄存器前,先确认当前模式是否允许写操作。如果不确定,先切到复位模式再配置。
4.2 初始化流程:一步一步来,别跳步
CAN控制器的初始化,我习惯把它分成三步走。每一步都有它的道理,跳步容易出问题。
第一步:进入复位模式,锁定配置权限
说白了,就是告诉CAN控制器:“我要开始配置你了,你先别干活。” 通常是通过设置模式寄存器的复位位来实现。配置完成后,再清除该位,让控制器进入正常模式。
// 伪代码示例:进入复位模式
CAN_MOD = 0x01; // 设置复位位
while (CAN_MOD & 0x01 == 0); // 等待确认进入复位模式
第二步:配置核心参数
这一步是重头戏。你需要配置:
- 波特率:通过位时序寄存器设置同步段、传播段、相位缓冲段1、相位缓冲段2和重新同步跳跃宽度。我一般会先算好目标波特率对应的分频系数和时序参数,再写进去。
- 验收滤波:设置哪些ID的报文你能收,哪些直接过滤掉。这个后面会细讲。
- 中断使能:打开你需要的那些中断源。我个人习惯,开发阶段把所有中断都打开,方便调试;量产时再根据需求裁剪。
小技巧: 配置波特率时,建议先用示波器或者CAN分析仪抓一下CAN_H和CAN_L的波形,看看位时间是否准确。我曾经因为晶振精度问题,算出来的波特率和实际差了2%,导致通信时好时坏。
第三步:退出复位模式,进入正常模式
配置完成后,清除复位位,让控制器开始工作。此时,CAN控制器就会按照你配置的参数,开始在总线上监听和发送了。
// 伪代码示例:进入正常模式
CAN_MOD = 0x00; // 清除复位位
while (CAN_MOD & 0x01 != 0); // 等待确认退出复位模式
注意: 退出复位模式后,不要立即发送大量报文。给控制器一点时间,让它先同步总线上的信号。我一般会加一个10ms左右的延时,确保总线状态稳定。
4.3 发送处理:把数据扔出去
发送流程其实挺直接的。你把报文ID、数据长度、数据内容填到发送缓冲区,然后触发发送命令。控制器会自动把数据打包成CAN帧,发送到总线上。
关键点在于:发送完成中断。你发完一帧数据后,控制器会产生一个中断,告诉你“我发完了,你可以准备下一帧了”。
我个人的做法是:
- 在发送中断服务函数里,清除发送完成标志位。
- 检查发送队列里还有没有待发送的报文。如果有,继续填缓冲区并触发发送;如果没有,就啥也不干。
这样做的好处是,你不会因为连续发送而把发送缓冲区撑爆。你想想看,如果你在主循环里疯狂调用发送函数,而控制器还没发完上一帧,新的数据就会把旧的覆盖掉,导致丢帧。
避坑指南: 我曾经在项目里遇到过一个诡异的问题:发送中断来了,我清除了标志位,但下一次发送却迟迟不触发。后来发现,是因为我在中断里又调用了发送函数,而发送函数里又去操作了同一个寄存器,导致中断标志位被意外再次置位。所以,中断服务函数里,只做最核心的事:清标志、取数据、填数据。别干多余的事。
4.4 接收处理:别让数据丢了
接收比发送稍微复杂一点。因为发送是你主动的,而接收是被动的。你不知道总线上的报文什么时候来,所以必须靠中断来通知你。
接收中断的处理流程:
- 中断来了,先读取接收缓冲区里的数据。
- 把数据拷贝到你的应用层缓冲区里。
- 释放接收缓冲区(有些控制器需要手动释放,有些是自动的)。
- 设置一个标志位,告诉主循环:“我有新数据了,快来处理。”
这里有个很容易踩的坑:接收缓冲区溢出。如果中断来得太频繁,或者你的中断服务函数处理得太慢,新的报文来了,但旧的还没取走,就会覆盖掉旧数据。这就是所谓的“丢帧”。
我一般会这样做:
- 在中断里,只做最轻量的操作:把数据从硬件缓冲区搬到内存里的环形缓冲区。
- 在主循环里,再从环形缓冲区里取出数据,进行解析和处理。
- 环形缓冲区的大小,要根据你的总线负载来估算。负载越高,缓冲区就要越大。
经验之谈: 如果你发现接收中断频繁触发,但主循环处理不过来,可以考虑用DMA(直接存储器访问)来搬运数据。这样CPU就不用每次中断都去读寄存器了,效率会高很多。不过裸机下用DMA,配置起来会稍微复杂一些。
4.5 中断处理:别让中断嵌套搞死你
CAN控制器的中断,通常包括:发送完成中断、接收有效中断、错误中断、总线关闭中断等。我建议你,在开发阶段,把所有中断都打开。这样一旦出问题,你能第一时间知道是哪里出了状况。
中断处理函数里,我习惯这样做:
- 读取中断状态寄存器,判断是哪个中断源触发了。
- 如果是接收中断,调用接收处理函数。
- 如果是发送完成中断,调用发送处理函数。
- 如果是错误中断,记录错误码,必要时复位CAN控制器。
- 清除对应的中断标志位。
嗯,这里要特别说一下错误中断。CAN总线上的错误,比如位错误、填充错误、CRC错误等,都会触发错误中断。我建议你在中断里记录下错误类型和发生次数,方便后期排查。我曾经在一个项目里,发现CAN总线偶尔会丢帧,查了三天没找到原因。后来把错误中断打开,才发现是总线上的终端电阻接触不良,导致信号反射,产生了位错误。
警告: 不要在主循环里轮询中断状态寄存器来判断是否有新数据。这样做不仅浪费CPU资源,而且可能因为轮询间隔太长而错过数据。一定要用中断方式。
4.6 总结:裸机开发的核心思想
CAN驱动裸机开发,说白了就是:你直接跟硬件对话。没有操作系统帮你管理中断、调度任务,所有事情都得你自己来。
我给你的建议是:
- 寄存器操作要谨慎:写之前先读,确认当前模式允许写。
- 初始化流程要规范:先复位,再配置,最后进入正常模式。
- 中断处理要高效:只做最核心的事,别在中断里做复杂运算。
- 缓冲区管理要合理:用环形缓冲区来解耦中断和主循环。
好了,这一章的内容就到这。下一章我们会聊聊CAN的验收滤波机制,以及如何用裸机实现一个简单的CANopen协议栈。到时候见。