CAN总线学习
目录
CAN总线简介
硬件电路
CAN电平标准
CAN收发器的框图
输入部分
输出部分
CAN物理层特性编辑
CAN总线帧格式
数据帧构成
遥控帧
错误帧
过载帧
帧间隔
位填充
波形实例
接收方数据采样
接收方数据采样遇到的问题
第一个问题
第二个问题
位时序
硬同步
再同步
波特率计算
多设备同时发送遇到的问题
资源分配规则(1)-------先占先得
资源分配规则(2)-------非破坏性仲裁
非破坏性仲裁过程编辑
问题:位填充会不会影响仲裁
问题:为什么叫非破坏性仲裁
数据帧和遥控帧的优先级
标准格式和扩展格式的优先级
错误处理
错误类型编辑
错误状态
错误计数器
波形示例
STM32CAN外设
CAN外设讲解
CAN网络拓扑结构
编辑CAN收发器电路
CAN外设框图
CAN基本结构
写入
读取
问题:为什么要两个队伍
发送过程
接收过程
发送和接收配置位
标识符过滤器
怎么写目标ID号?
写入标准ID
写入扩展ID
RTR是遥控帧标志
怎么实现过滤过滤一组的ID?
过滤器配置示例
测试模式
工作模式
位时间特性
中断
时间触发通信
错误处理和离线恢复
程序
初始化
发送过程
接收过程
代码:
CAN总线简介
用于超过两个单片机主控系统进行通信
硬件电路
高速CAN两端添加终端电阻的原因,防止信号波形在信号终端反射,干扰原始信号
终端电阻的作用,就是将两根线收紧,至电压一致的状态,代表默认的一状态
当某个设备想要发送0时,就会操作总线将总线拉开,使其呈现0状态
当某个设备想要发送1时,就不去碰总线,总线在终端电阻的收缩下,自动归位默认状态1
低速CAN的一端添加终端电阻的作用,防止回波反射,电阻没有连接两根线,所以这两个电阻并没有收紧作用
CAN电平标准
在CAN总线里,实际传输的是差分电平,STM32的引脚以及时序图通常用逻辑电平
总线收紧称为隐形电平,总线张开称为显性电平
两线收紧,没有电压差,是默认状态,所以叫隐性
两线张开,产生电压差,是需要设备干预的状态,所以叫显性
当显性电平和隐性电平同时出现时,总线会表现出显性电平状态,这样对应电路中,常用“0强于1”的规定
CAN收发器的框图
输入部分
当接收器,可以时刻检测总线电压差,并输出到左边的线
总线有电压差就输出1,总线没有电压差就输出0
1和0通过两个场效应管,输出到RXD引脚,两个场效应管,相当于电子开关
当右边的电压差为1时,上管断开,下官导通,输出为0
当右边的电压差为0时,上管导通,下官断开,输出为1
所以这两个管还有电平反向的功能
整体来看,当CAN总线有电压差时,RXD输出低电平,表示显性电平, 当CAN总线没有电压差时,RXD输出高电平,表示隐性电平
输出部分
当TXD给1时,后面的驱动器,会让两个场效应管,都断开,相当于不对总线进行任何操作,总线在外部的终端电阻的收紧下,呈现默认的隐形电平
内部两个电阻,起名为“中拉电阻”,通过这两个电阻,可以把CANH和CANL两跟线,都拉到0.5倍的VCC的中间电平
当TXD给0时,后面的驱动器,会让两个场效应管,都导通,使CANH总线拉高,CANL总线拉低,这样两线会分开,产生电压差,总线呈现显性电平0的状态
电流源上拉,类似上拉电阻,当TXD悬空时,这里会默认保持输入1状态
CAN物理层特性
CAN总线帧格式
数据帧构成
灰色部分必须发显性电平0
紫色部分D/R,根据发送数据的不同,可以发送D也可以发送R
白色部分,必须是隐形电平1
灰色和白色各一半部分,是应答位特有的,发送方发送隐形电平1,接收方发送显性电平0
标准格式:逻辑表示
在发送数据之前,总线必须是空闲状态,空闲状态时,总线时隐形电平,为高电平1状态
1)帧起始:数据帧开始,第一位为灰色低电平,此位为显性电平0,SOF帧起始,作用是打破宁静。因为空闲是,隐性1,所有设备都不去碰总线,那么想要开始发送数据帧,第一位必须是张开总线,发送显性数据0。如果发送隐形数据1,那么1就和前面的数据融为一体了,没有人知道发送数据了。
所以数据帧,必须以显性数据0开头,作用是打破总线空闲,开始一帧的数据。
2)ID:首先发送的是报文ID,11位,作用区分数据,还用于区分优先级,当多个设备同时发送时,根据仲裁规则,ID小的报文优先发送,ID大的报文等待下一次总线空闲再次发送,不同数据的数据帧ID都不同
3)RTR:意思是远程请求标志位,用于区分是数据帧,还是遥控帧
数据帧必须是显性0,遥控帧必须是隐形1
这里是数据帧,所以必须为显性0
报文ID和RTR合在一起称为仲裁段。RTR加进来的作用,相同ID的数据帧和遥控帧,数据帧的优先级大于遥控帧
4)IDE:为ID扩展标志位,用于区分是标准格式还是扩展格式。
标准格式固定为显性0,扩展格式固定为隐形1,
5)r0:保留位
6)DLC:表示数据段的长度,CAN总线数据一帧可以有,1~8个字节有效载荷,可以灵活指定,通过DLC进行指定,当想要发送四个字节时,为0100,想要发送八个字节就为1000,
IDE和r0和DLC统称为控制段
7)数据段Data:最大64位,即8个字节
8)CRC段,15位,CRC是高效的校验算法,会对前面的所有数据进行CRC算法计算,从SOF到Data,这些数据位,计算得到一个校验码,附在后面,接收方收到数据和校验码后,也会调用CRC算法进行计算,看看计算的校验码是否一致,判断传输是否有误。
9)CRC界定符:一位必须是隐性电平
10)ACK段,里面有ACK槽和ACK界定符,都是一位
当发送方,发送一帧数据,到底有没有收到,就通过ACK位来实现。基本思想,当发送发送一帧数据主要内容后,在应答这一位时,发送方释放总线,总线回归默认状态,隐性1;如果接收方接收到数据,就会在,ACK糟这里主动出击,把总线拉开,使总线呈现显性0的状态;
发送方发送数据后,释放总线,但是读回来,总线却是显性0,这说明一定有接收方
当读取数据为隐性1时,说明没有接收的,发送失败,发送方可以配置自动重发
11)ACK界定符:一位必须是隐性电平
遥控帧
首先,遥控帧和数据帧通过RTR进行区分,数据帧RTR为显性0遥控帧RTR为隐性1,当接收方接收到遥控帧的根式解析后,遥控帧的DLC后,没有数据段,直接跟CRC校验码。
当某个设备发送遥控帧时,代表他想请求这个数据,请求自然不用数据段。一次完整的请求,需要遥控帧和数据帧配合。请求方发出遥控帧,遥控帧的ID代表要请求的数据。响应请求的一方,通过相同ID的数据帧反馈数据。当请求和反馈同时发生时,数据帧拥有更高的优先级。
错误帧
当某个设备发现总线上的数据波形不对,产生错误了,它就会发出错误帧,错误帧可以叠加在数据帧上,可以破坏数据帧的数据,就是数据错误,进行破坏,不再使用这个数据。
错误分为主动错误和被动错误。
设备默认处于主动错误模式,处于主动错误的设备检测出错误时,会连续发个6显性位,发显性位就是拉开总线,只要一个设备拉开,就必然处于显性状态。即0和1相遇时,总是处于0状态,这是线与特性,所以主动错误标志的6个显性位,必然会破坏正常传输的数据。其他设备检测到错误标志,就会抛弃这个数据,主动错误产生太频繁,说明这个设备不可靠,设备就会进入被动错误状态,处于被动错误状态时,检测出错误,会连续发送6个隐性位,发送隐性位就是不去碰总线,就不会破坏别人发送的数据,但会破坏自己的数据,自己的数据有问题,自己破坏掉,不会影响破坏别的设备。
过载帧
就是接收设备通知其尚未做好接收准备。过载帧不是在错误时产生,而是在当接收大量数据而无法处理时,由接收方产生。因为数据是发送方主动发送的,接收方无法直接调整发送方的发送频率,那怎么办,就只能产生和错误帧类似的过载帧,将数据破坏掉,发送方发不出去,就会重试,在破坏和重试的过程中,数据就会被延误了,就会告诉发送方,接收不了。如果发送方有相应的处理逻辑就会,降低一点频率。
帧间隔
主动错误状态的帧间隔是三位
被动错误状态的帧间隔是三位加上8位的延迟传输,被动状态表示设备不可靠,不可靠就不用着急发送数据,延迟传输,会将设备置于仲裁不利的处境,尽量减少此设备干扰总线。
位填充
错误帧和过载帧,是连续6个相同的电平,而正常的数据流,经过位填充后,不可能出现连续5个以上的相同电平。
波形实例
由于ID位为11位,换成16进制时,划分3/4/4的划分101 0101 0101
RTR为0数据位,IDE为0表示标准数据帧
这个1是因为前面已经有了5位的0,所以进行数据填充1,虽然DLC检测到5位数据,但是实际使用的只有四位,把填充的1,消去后进行,读取0001 为0x01
CRC界定符作用,在ACK槽之前,发送方释放总线,如果没有CRC界定符,那么CRC校验完成后,最后一位为显性0,如果直接跟ACK槽,你发送方还拽着总线,让接收方怎么发送应答位,所以CRC与ACK之间加一个CRC界定符,界定符为隐性1,这样发送方在ACK槽之前,肯定会释放总线。之后总线的控制权,短暂的交给接收方,发送方在ACK槽期间不会碰总线,如果有接收方,接收方会在ACK槽拉开总线,只要一个设备拉开总线,就可以看到ACK槽是显性,说明有接收方给应答了,这一位不是发送方拉开的,而是接收方拉开的,表示接收方对发送方的应答。应答结束是ACK界定符,接收方会在这一期间释放总线,交出总线控制权,当然发送方也是释放状态。最后7位EOF都是隐性1。
拓展ID的范围
0x0000 0000 ~0x1FFF FFFF
标准号的ID
0x000 ~0x7FF
接收方数据采样
CAN总线是,异步通信,异步通信就是没有时钟线,数据位的传输速率,也就是波特率,所有设备必须由编程者在程序中指定,并且保存一致。
接收方数据采样遇到的问题
采样点应该处于两次的跳变沿之间
第一个问题
没有对其数据位中心,在其上升沿时采样,那么就读取不出,有效的电平。
解决方法,在第一个条跳变沿时,进行延时半个数据位左右的时间,进行第一次数据采样,后续再按固定采样间隔进行采样,这样就解决了采样位置没有对齐的问题。(硬同步)
第二个问题
就是时钟有误差,采样点进行偏离
解决方法,如果采样点已经可以观察到偏离,某个采样点距离前面的数据跳变沿,时间过于久了,可以把下个的采样间隔给缩小一点,这样所有采样点的位置都会往前提一提,就能弥补接收方采样太慢的问题。相反接收方接收太快,就可以在快到一定程度时,进行延长一次数据采样间隔,这样所有采样点的位置都会往后拖一拖,就能弥补接收方采样太快的问题。(再同步)
位时序
如果跳变沿出现在SS段,那就说明当前设备与波形达成同步,如果不在SS段,那就调整跳变沿使其正好处于SS同步段
PTS(传播时间段)可补偿信号传输延迟,适应不同网络拓扑与节点距离,还能配合采样点,确保在信号稳定区采样,保障数据传输准确,是实现 CAN 总线可靠通信的关键。
PBS1与PBS2相位缓冲段,作用确定采样点的位置,采样点会在其中间,如果PBS1给长点,PBS2给短点,那采样点就会靠后;相反,采样点靠前。
所以,通过调整PBS1和PBS2的时长,就可以灵活的调整采样点的位置。
硬同步
解决问题:使接收方第一个采样点与波形第一位对齐。参考波形的第一个下降沿,对齐初始位置。
再同步
发送方时钟跑慢了,或者接收方时钟跑快了
由于波形是发射方发送的,不可能调整发射方时钟,只能调整接收方的时钟
第一个是接收方的时钟快了,快了2个2个SJW 指定的宽度,边沿出现在SS后2个SJW 指定的宽度。由于是接收方的时钟快了,所以在PBS1和PBS2中间添加了2个SJW 指定的宽度,就相当于让接收方的时钟延迟了2个SJW 指定的宽度使其和发送方一样。
第二个是接收方的时钟慢了,慢了2个SJW 指定的宽度,边沿出现在SS前2个SJW 指定的宽度。由于是接收方的时钟慢了,所以接收方会将PBS2段进行减少2个SJW 指定的宽度,就相当于让接收方的时钟提前了2个SJW 指定的宽度使其和发送方一样。
快了加长,慢了剪短
如果误差是1个Tq,而SJW是2个Tq,只会补偿一个
如果误差值小于或者等于SJW的值,则误差几个Tq,就补偿几个Tq
如果误差是2个Tq,而SJW是1个Tq,只会补偿SJW指定1个Tq
SJW根据实际情况选择
波特率计算
多设备同时发送遇到的问题
两个波形会按照“线与”进行叠加,有一个设备是拉开总线,总线就呈现显性0状态,只有当所有的设备都释放总线时,才会呈现隐性1状态。按照线与特性进行叠加后,显然两个波形都必然产生数据破坏,谁的数据都发送不出去,所以CAN总线的设计,必须要考虑多设备同时发送,遇到的资源冲突问题。
资源分配规则(1)-------先占先得
一开始是设备A在发送数据,A发送一段时间后,B想要发送数据,但是B之前没有检测到11个隐性电平,总线不空闲,所以B不能发送,只能进行排队。B一直等到当A总线的ACK界定符为隐性电平,后面是EOF7个隐性电平,然后再等待3个隐性位的帧间隔,一共11个隐性电平,认为总线空闲,设备B开始尝试发送数据,因为可能不止B在等待发送数据,可能还有C,D。此时B,C,D就是同时到来了,这就需要使用资源分配规则2.,进行仲裁,判断谁先谁后。
资源分配规则(2)-------非破坏性仲裁
针对的是多个设备同时开始
RTR+ID号是仲裁的依据
当发出数据1时,回读的数据为0,说明有总线上有其他设备,并且与其发生冲突,所以就进行退出,不再进行抢占资源。
非破坏性仲裁过程
单元1发送0,单元2也发送0,线与特性,总线电平为0,单元1回读为0,单元2回读为0都正常没有问题。
当单元1发送1,单元2返送0时,线与特性,总线电平为0,单元1进行回读为0,发送的数据1被别的数据给破坏了,所以单元1仲裁失利,转换为接收状态,等待总线再次空闲。而单元2回读为0,没有影响。
从数据上看,单元1的数据已经被破坏掉,继续发也没有意义
从设备感知上看,单元1发送隐性1却读回显性0,感知到有别的设备存在,所以进行退出。而单元2每次发送和回读的数据都是一样的,感知不到有别的设备存在,接着继续。
多个设备也是一样,发送1位,读取1位,的流程,当发送的与回读的数据不一样就进行退出
ID号越小,其二进制的1出现的就越晚,ID号越大,1出现的越早,ID出现差异,且发出数据1的仲裁失利,所以ID号出现的越早,越容易仲裁失利。
问题:位填充会不会影响仲裁
位填充的作用范围:帧起始,仲裁场,控制场,数据场,以及CRC序列
会在仲裁场进行位填充
就是A和B
没有填充A的优先级比B的优先级高
通过填充A的优先级比B的优先级低
这种情况是不可能的
因为如果填充位会改变A和B的优先级,那么填充位,A和B前面的位就必须一样,而A和B前面的位都一样,那么A和B都执行填充,那么填充位就不会影响优先级
所以填充位,不会影响仲裁,不会影响优先级
问题:为什么叫非破坏性仲裁
从胜利者的角度,发送的数据,和回读的数据一样,没有感受到竞争者,它的发送过程没有因为仲裁而产生影响,所以是非破坏性。
数据帧和遥控帧的优先级
RTR遥控帧为1,数据帧为0
单元1和单元2前面的都一样,当到RTR时,单元1发送1,单元2发送0,总线电平为0,单元1回读为0,导致破坏了原来的数据,仲裁失利,单元2回读为,没有问题。所以说相同ID的数据帧的优先级大于遥控帧。
标准格式和扩展格式的优先级
同理
如果这里单元2换为标准遥控帧
当单元1到SRR时为1,此时单元2到RTR也是为1,此时单元1和单元2没有分出胜负,但是标准遥控帧要结束了,也就是标准遥控帧直到仲裁结束都没有和扩展数据帧分出胜负。
这种情况不会出错,标准遥控帧虽然结束了,但是扩展数据帧仲裁段仍然没有结束,标准遥控帧RTR之后,跟的是IDE扩展标志位,因为是标准帧,IDE为0;而上面扩展数据帧SRR之后跟的也是IDE,因为是扩展帧,IDE为1。在这一位,虽然标准遥控帧,不再进行仲裁,但是扩展遥控帧还在进行仲裁,并且扩展数据帧会出现“发1读0”的情况,所以扩展数据帧仲裁失利,转入接收状态。
错误处理
错误类型
错误状态
初始化时,为主动错误,当出现错误时TEC增加,当TEC>127时,说明设备不靠谱,切换为被动模式,当被动模式下TEC>255,说明设备出现问题,进行总线关闭态。当在总线上检测到128次连续的11个位的隐性位时,进入主动错误状态。
错误计数器
一次错误需要8次正常才能弥补出来
波形示例
ACK槽为显性0,代表接收方接收到数据,
一位ACK界加上七位EOF加上三位的帧间隔,一共是11位的隐性电平1,总线进入空闲,开启下一帧的传输。
ACK槽为隐性1,代表接收方没有接收到数据,就是没有设备应答
发送方检测到一个应答错误,所以发送方会在ACK槽的下一位,发出一个错误帧,由于目前还是主动错误状态,所以发送方发出主动错误帧,6个显性0主动错误标志,再跟8个隐性1,错误界定符。
错误帧是特殊的,连续的6个0
8位的错误界定符+3位的帧间隔=11位的隐性1,总线进入空闲模式,开启下一帧
刚开始时,处于主动错误状态,会发送主动错误帧,由于发送单元在输出错误标志时,TEC会进行加8,当连续16次没有设备应答时,TEC=128,这时发送方进入被动错误状态 。
ACK槽为隐性1,没有设备应答
由于处于被动错误状态,所以在ACK槽后面接着6位的被动错误标志,8位的错误界定符,3位的帧间隔。帧间隔结束后,不能立刻进入下一帧,因为是被动错误状态,不太可靠,所以它的帧间隔后还要加8位的延迟传送。
如果一个主动错误状态的设备和一个隐性错误状态的设备同时想要发送数据,他们同时检测到了11位隐性位,那么主动错误状态直接提取总线控制权,不会和被动错误的设备进行仲裁。因为被动错误的设备有8位的延迟传送,所以它赶不上主动错误设备的脚步,
STM32CAN外设
CAN外设讲解
CAN网络拓扑结构
CAN收发器电路
CAN外设框图
只有互联型设备才有CAN2
程序通过读写寄存器来操纵电路运行
主发送邮箱,发送邮箱有三个,一个邮箱可以存入一个CAN报文,如果想要发送一个报文,只需要将报文存入一个空字邮箱,之后设置寄存器请求发送就完事了。
接收滤波器,包括接收过滤器和2个FIFO,当CAN总线上出现 一个数据帧或者遥控帧时,CAN硬件电路都会把这个报文缓存下来,至于是不是要保留这个报文,要看能不能通过过滤器。
过滤器内,我们可以自己设置过滤规则,告诉硬件什么ID的报文才可以通过。硬件收到这些报文就存入FIFO。如果报文无法通过任何过滤器,说明这个报文不需要。
FIFO 是先进先出寄存器
CAN基本结构
PA12推完输出
PA11上拉输入
写入
发送部分也有三个邮箱,也是要排队
可以配置先来后到的方案,也可以配置按照ID优先级,决定处理顺序
读取
存入数据的前后,先存入邮箱0,再存入邮箱1,最后存入邮箱2
如果接收FIFO 0 邮箱都存满了,将进行FIFO锁定,新的报文将直接丢弃
如果接收FIFO 0 邮箱都存满了,不进行FIFO锁定,新的报文将直接将邮箱2的数据踢出去,自己占据邮箱2的位置
队伍排满之后,必然有数据丢失,要么新来的数据丢失,要么队伍末尾的丢失
如果CPU读取了邮箱0,那么邮箱1的报文就会上前,存入邮箱0,邮箱2的报文也会往前上存入邮箱1.那么末尾的邮箱2就会空出来,可以存入新的报文
问题:为什么要两个队伍
对报文进行分流,重要的报文一个队伍,不重要的报文一个队伍
这样重要报文丢失的概率就大大降低了
也可以按照报文的种类,报文的接收频率进行分流两个队伍
自己可以配置FIFO 0 和FIFO 1 的优先级
排队:发送部分更灵活,接收部分只能进行先来后到的方案
发送过程
ABRQ终止发送,终止发送和发送失败一样的
NART禁止自动重发, 0开启自动重发,1是进制
空置:RQCP(请求完成位)、TXOK(发送成功位)状态不定(X表示任意),TME(发送邮箱空标志)为1,表示邮箱为空,可写入待发送数据。
挂号:当设置TXRQ=1(发送请求置位),邮箱从 “空置” 进入 “挂号” 状态。此时RQCP=0(请求未完成)、TXOK=0(发送未成功)、TME=0(邮箱非空,有数据待发送)。
预定:若邮箱成为最高优先级待发送邮箱,从 “挂号” 进入 “预定” 状态;若未成为最高优先级,仍保持 “挂号” 状态。“预定” 状态下RQCP=0、TXOK=0、TME=0。
发送:当 CAN 总线处于IDLE(空闲)状态时,“预定” 状态的邮箱进入 “发送” 状态,开始发送数据。
发送成功进入空置,发送失败后,根据NART判断进入哪边,如果NART=0,开启自动重传,那么发送失败的报文就回到了预定状态。如果NART=1,禁止自动重传,那么发送失败报文,就进入发送失败的空置状态。
当邮箱处于预定或者挂号状态时,当前的报文还没有发送出去,这时如果ABRQ置1的话,就终止发送,终止发送后,状态和发送失败的状态是一样的。
接收过程
空:FIFO 中没有待处理的报文。FMP(FIFO 消息挂起数量)为0x00,表示无挂起报文;FOVR(FIFO 溢出标志)为0,表示未发生溢出。
挂号_1、挂号_2、挂号_3:分别表示 FIFO 中有 1、2、3 个挂起报文的状态,FMP对应为0x01、10b、11b,FOVR=0(无溢出)。
溢出:FIFO 已存满报文,又收到新的有效报文时进入该状态。FMP=0x11(FIFO 最多可挂起 3 个报文,已达上限),FOVR=1(溢出标志置位)。
FIFO存满之后FULL寄存器会置1
挂号3和溢出状态其实是一个状态,他们的队伍长度都是3,释放一次,队伍长度都变成2,进入挂号2状态
发送和接收配置位
标识符过滤器
- FSCx(位宽设置):
- 置0:过滤器位宽为 16 位,可处理 16 位长度的标识符等数据。
- 置1:过滤器位宽为 32 位,能处理更长(32 位)的标识符等数据,适用更复杂的报文筛选场景。
- FBMx(模式设置):
- 置0:为屏蔽模式。此时需结合屏蔽码(存储在寄存器中),只有报文标识符与过滤器标识符在屏蔽码指定的有效位上匹配时,报文才会被接收。
- 置1:为列表模式。过滤器直接存储需要匹配的标识符列表,只有报文标识符与列表中某一标识符完全一致时,报文才会被接收。
- FFAx(关联设置):
- 置0:匹配通过的报文将被送入 FIFO 0(接收先进先出队列 0)。
- 置1:匹配通过的报文将被送入 FIFO 1(接收先进先出队列 1)。
- FACTx(激活设置):
- 置0:禁用该过滤器,即使有符合条件的报文,也不会经过该过滤器筛选。
- 置1:启用该过滤器,开始执行报文筛选功能。
FSCx 与FBMx组合:
FSC=1,FBM=0 32位屏蔽模式
FSC=1,FBM=1 32位列表模式
FSC=0,FBM=0 16位屏蔽模式
FSC=0,FBM=0 16位列表模式
R1和R2两个寄存器,都写入的是目标ID,可以把想要的ID号写入到R1,R2中, R1和R2可以写两个ID,当管理员收到 一个报文时,它就和R1和R2 寄存器的目标ID对比,如果有一样的通过过滤器,如果都不一样就不同过,这种把我们想要过滤的ID直接写入R1和R2寄存器的模式,就是列表模式,过滤器对比每个收到的报文,在列表中就让过去,否则不让过去。
怎么写目标ID号?
下面指明了存储映像
写入标准ID
只用前面的STID
32位寄存器的高11位,需要存入的是,标准格式的ID号STID,后面跟着的18位是拓展格式的ID号EXID,(拓展ID一共29位),如果想要完整的存入一个扩展ID,则需要占用前面的11位标准ID的空间,再外加后面的18位的扩展ID的空间。
写入扩展ID
怎么知道写入的是标准ID和拓展ID,IDE是拓展标准位,
如果想要过滤一个拓展ID,那么在前面29位写入一个目标扩展ID,然后IDE位写1
如果想要过滤一个标准ID,那么在前面高11位写入一个目标标准ID,然后IDE位写0
ID类型一定要和IDE保持一致
RTR是遥控帧标志
如果想要过滤数据帧,RTR写0
如果想要过滤遥控帧,RTR写1
多出一位没有用,默认保持为0
R1和R2可以各写入一个标准ID和拓展ID,所以这种模式下,一个过滤器最多只能过滤两个ID号
如果只使用这个标准ID,那么后面的18位的拓展空间,就浪费了。进行优化得到
4个16位过滤器,标识符列表的设计思路
R1和R2都会被拆开,拆成4个16位的寄存器,每个寄存器写入一个标准格式的目标ID。这样一个过滤器就可以写入4个目标ID,最大化利用资源。
高11位都是标准ID,后面是RTR,需要过滤遥控帧,RTR就写1,需要过滤数据帧就写0,IDE一般写0,也就是指定过滤标准格式ID。
所以16位位宽下,IDE最好只写0,后面多出了3位拓展ID,因为不适用拓展ID,一般都写0即可
这种,只能写入标准ID,一个寄存器拆成2个使用,原来只能写入2个过滤ID,现在能写入4个过滤ID名单,资源利用率更大了。
怎么实现过滤过滤一组的ID?
有个传感器,有100个温度探头,和100个湿度探头,这些数据都在CAN总线上传输,于是可以定义ID号,使用标准格式,0x100是温度探头0的温度 ,0x101是温度探头1的温度,0x102是温度探头2的温度。依次类推,ID号以0x1开头的,都是温度数据。0x200是湿度探头0的湿度,0x201是湿度探头1的湿度,0x202是湿度探头2的湿度。依次类推,ID号以0x2开头的,都是湿度数据。
有个设备需要接收所有温度探头的温度,其他所有数据都过滤掉。
按照刚才使用的列表模式的设计,是不是就搞不定这个需求,因为温度的数据太多了,有100个,即使使用16位列表模式,用光14个过滤器,也写不出100个目标ID序列。所以继续改进。设计一种部分匹配ID的机制
R1写ID号,R2决定ID的哪些位必须一样,哪些位又是任意的,这样我们就能过滤出一组的ID号了。
假设需求是过滤出0x1开头的所有标准ID号11位的二进制表示
001 xxxx xxxx这样的
STD:001 0000 0000 EXID不需要给0,IDE一定要给0,RTR根据需求来
如果是列表模式,就只能过滤一个ID就是0x100,但是现在是屏蔽模式,可以在R2寄存器里表明,过滤时,哪些位是必须匹配的,哪些是无关的。
在R2里写1,表示R1里的ID对应位必须匹配一致,写0,表示R1里的ID对应位1和0均可。现在我们需要标准ID位的高3位必须匹配一致,剩下的位0,1均可。
所以R2寄存器可以这么填,R2寄存器高3位必须都给1,告诉过滤器ID的高3位必须为001才给过,后面都给0,告诉过滤器,后面的这些位,不需要和目标ID一样,0和1都可以过,这样通过过滤器的ID就为001 xxxx xxxx的需求。EXID的屏蔽位,没有使用都给0,IDE的屏蔽位,必须给1,表示上面的R1的IDE必须匹配,如果IDE的屏蔽位给了0,那就表示标准格式和拓展格式是无所谓的,这样会导致某些拓展ID误入进来,RTR看情况。RTR,上面给1,下面给1必须匹配遥控帧,如果上面给0,下面给1,表示必须匹配数据帧,如果是遥控帧,数据帧无所谓,那么上面给什么都可以,下面必须给0。
2个16位过滤器-标识符屏蔽
如果只需要过滤标准ID的话,也可以一个寄存器拆成两个使用,R1的低位写一个目标ID,R1的高位,是对应的屏蔽位。R2的低位写一个目标ID,R2的高位,是对应的屏蔽位。
过滤器配置示例
必须左移5位,进行左对其
后面的5位默认给0,如果想要接收遥控帧就或上0x10,把RTR置1
接收的是0x200~0x2FF的ID,数据帧,标准格式
010 0000 0000 00
111 0000 0000 11
0x700 接收0x2的数据
0x10:十六进制 0x10 对应二进制 00010000(8 位视角),如果参与到 32 位寄存器的 [31:16] 区间,会在特定位置贡献一个 “1” 的位。
0x8:十六进制 0x8 对应二进制 00001000(8 位视角),同理,会在另一位置贡献一个 “1” 的位。
从左边进行,RTR TDE EXID
0 0 000
000 1 0 000 这就是0x10
000 0 1 000 这就是0x8
测试模式
工作模式
SLAK 睡眠模式标志位
INAK 初始化模式标志位
SLEEP:请求 bxCAN 进入睡眠模式
INRQ:请求 bxCAN 进入初始化模式
SYNC: bxCAN唤醒进入正常模式时
ACK:确认信号
位时间特性
与CAN协议的区别
PTS和PBS1是合在一起的叫做PBS1段
中断
时间触发通信
错误处理和离线恢复
程序
-
初始化
- 开启RCC的CAN引脚的GPIO时钟 和CAN1的时钟
- GPIO初始化,TX推完输出,RX上拉输入
- 整个CAN外设初始化
- 单独对过滤器初始化
- 中断
CAN初始化加过滤器初始化
-
发送过程
发送,获取发送邮箱状态,取消发送
-
接收过程
读取FIFO的数据,释放FIFO,获取指定FIFO队列里排队的报文数目
CAN初始化
高速CAN的波特率
125Kbps~1Mbps
36M/48/(1+2+3)=125k
为什么没有减1?
因为在调用的函数里面自动减一
CAN滤波器初始化
发送
接收
接收状态
接收数据
可以在PA12处接个LED,LED正接正,负极接PA12
当发送时,会产生小波形,发送时,LED闪烁。
如果OLED屏幕无法接收数据,可以通过LED进行检测
如果LED闪烁,说明发送没有问题,检查接收函数
如果LED没闪烁,说明发送有问题,检查发送函数
中断接收:
要在CAN初始化之前才可以
代码:
#include "stm32f10x.h" // Device headervoid MyCAN_Init(void)
{RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1,ENABLE);GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;GPIO_InitStructure.GPIO_Pin=GPIO_Pin_12;GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;GPIO_Init(GPIOA,&GPIO_InitStructure);GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;GPIO_InitStructure.GPIO_Pin=GPIO_Pin_11;GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;GPIO_Init(GPIOA,&GPIO_InitStructure);CAN_InitTypeDef CAN_InitStructure;CAN_InitStructure.CAN_Mode=CAN_Mode_LoopBack;CAN_InitStructure.CAN_Prescaler=48;CAN_InitStructure.CAN_BS1=CAN_BS1_2tq;CAN_InitStructure.CAN_BS2=CAN_BS2_3tq;CAN_InitStructure.CAN_SJW=CAN_SJW_2tq;CAN_InitStructure.CAN_NART=DISABLE;// 禁止自动重传CAN_InitStructure.CAN_TXFP=DISABLE; // 禁止发送FIFO优先级CAN_InitStructure.CAN_RFLM=DISABLE;// 禁止接收FIFO锁定CAN_InitStructure.CAN_AWUM=DISABLE;// 禁止自动唤醒CAN_InitStructure.CAN_TTCM=DISABLE;// 禁止时间触发通信模式CAN_InitStructure.CAN_ABOM=DISABLE; // 禁止自动总线关闭管理CAN_Init(CAN1,&CAN_InitStructure);CAN_FilterInitTypeDef CAN_FilterInitStructure;CAN_FilterInitStructure.CAN_FilterNumber=0;CAN_FilterInitStructure.CAN_FilterIdHigh=0x0000;CAN_FilterInitStructure.CAN_FilterIdLow=0x0000;CAN_FilterInitStructure.CAN_FilterMaskIdHigh=0x0000;CAN_FilterInitStructure.CAN_FilterMaskIdLow=0x0000;CAN_FilterInitStructure.CAN_FilterScale=CAN_FilterScale_32bit;CAN_FilterInitStructure.CAN_FilterMode=CAN_FilterMode_IdMask;CAN_FilterInitStructure.CAN_FilterFIFOAssignment=CAN_Filter_FIFO0;CAN_FilterInitStructure.CAN_FilterActivation=ENABLE;CAN_FilterInit(&CAN_FilterInitStructure);
}void MyCAN_Transmit(uint32_t ID,uint8_t Length,uint8_t *Data)
{CanTxMsg TxMessage;TxMessage.StdId=ID;TxMessage.ExtId=ID;TxMessage.IDE=CAN_Id_Standard;TxMessage.RTR=CAN_RTR_Data;TxMessage.DLC=Length;for (uint8_t i=0;i<Length;i++){TxMessage.Data[i]=Data[i];}uint8_t TransmitMailbox=CAN_Transmit(CAN1,&TxMessage);//TransmitMailbox发送邮箱的状态uint32_t Timeout=0;while(CAN_TransmitStatus(CAN1,TransmitMailbox)!=CAN_TxStatus_Ok){Timeout++;if(Timeout>10000){break;}}CAN_Transmit(CAN1,&TxMessage);
}uint8_t MyCAN_ReceiveFlag(void)
{if(CAN_MessagePending(CAN1,CAN_FIFO0)>0)//检测查询CAN 接收 FIFO 中未处理报文数量的函数{return 1;}return 0;
} void MyCAN_Receive(uint32_t *ID,uint8_t *Length,uint8_t *Data)
{CanRxMsg RxMessage;CAN_Receive(CAN1,CAN_FIFO0,&RxMessage);if(RxMessage.IDE == CAN_Id_Standard){*ID=RxMessage.StdId;}else{*ID=RxMessage.ExtId;}if(RxMessage.RTR==CAN_RTR_Data){*Length=RxMessage.DLC;for(uint8_t i=0;i<*Length;i++){Data[i]=RxMessage.Data[i];}}
}