CAN是Controller Area Network的缩写,最初是专门用于汽车网络的通信协议,与485协议相似,CAN也是一种2线制,采用两根线的电压差进行数据传输的协议,随着CAN协议的高性能与可靠性被认同,现在被广泛运用在工业自动化,船舶等当面,目前最热门的国六标准,J1939通信协议就是以CAN协议为基础设计的。
CAN协议最远通信距离可达10km,与485相似,CAN也有两种电平,分别为显性电平与隐性电平,当CAN_H与CAN_L电压一致的时候为隐性电平,反之为显性电平。实际上隐性电平代表逻辑电平1,显性电平代表逻辑电平0,CAN在通信的时候,也需要在每个设备输出端并联1个120Ω的终端匹配电阻,用于进行阻抗匹配。
CAN协议通过5种类型的帧进行数据通信:数据帧,遥控帧,错误帧,过载帧和间隔帧,其中数据帧与遥控帧具有标准格式与扩展格式两种,标准格式有11个位的标识符,扩展格式则有29个位的标识符,五种帧的功能如下所示。
(1)数据帧:用于实际数据传输
(2)遥控帧:用于接收端向具有相同ID的发送端请求数据
(3)错误帧:用于检测报错时通知其他设备
(4)过载帧:用于接收端通报尚未准备好接收准备
(5)间隔帧:用于将数据帧与遥控帧和前面的帧分割
完整的数据帧构成如下图所示。
数据帧一般由7段组成:
(1)帧起始:即数据帧开始的段,标准帧和扩展帧都是由1个位的显性电平表示帧起始
(2)仲裁段:表示该帧优先级,标准帧和扩展帧格式在这一段结构如下图所示。
其中RTR位用于标识是否是远程帧(0代表数据帧;1代表远程帧),IDE位为标识符选择位(0表示使用标准标识符;1表示使用扩展标识符),SRR位为代替远程请求位,为隐性位,代替了标准帧中的RTR位。
(3)控制段:表示数据的字节数即保留位,由6个位构成,表示数据段的字节数。标准帧和扩展帧的控制段结构如下图所示。
r0和r1为保留位,必须全部以显性电平发送,但是接收端可以接收显性、隐性及任意组合的电平。DLC段为数据长度表示段,高位在前,DLC段有效值为08,但是接收方接收到915的时候并不认为是错误。
(4)数据段:数据段内容,一段可以发送0~8个字节的数据,从最高位MSB开始输出
(5)CRC校验:CRC校验数据完整性
(6)应答段:表示正常接收
(7)帧结束:表示该帧结束
由发送单元在非同步的情况下发送的每秒钟的位数称为位速率。一个位可分为4段。
(1)同步段(SS)
(2)传播时间段(PTS)
(3)相位缓冲段1(PBS1)
(4)相位缓冲段2(PBS2)
这些段又由可称为Tq的最小时间单位构成。1位分为4个段,每个段又由若干个Tq构成,这称为位时序。1位由多少个Tq构成、每个段又由多少个Tq构成等,可以任意设定位时序。通过设定位时序,多个单元可同时采样,也可任意设定采样点。各段的作用和Tq数如下表所示。
在总线空闲态,最先开始发送消息的单元获得发送权。当多个单元同时开始发送时,各发送单元从仲裁段的第一位开始进行仲裁。连续输出显性电平最多的单元可继续发送。实现过程如下图所示。
单元1和单元2同时开始向总线发送数据,开始部分他们的数据格式是一样的,故无法区分优先级,直到T时刻,单元1输出隐性电平,而单元2输出显性电平,此时单元1仲裁失利,立刻转入接收状态工作,不再与单元2竞争,而单元2则顺利获得总线使用权,继续发送自己的数据。这就实现了仲裁,让连续发送显性电平多的单元获得总线使用权
STM32F1系列自带的是基本扩展CAN,支持CAN协议的2.0A和2.0B,支持报文发送的优先级要求可软件配置,最大通信速率1Mbps,3个发送邮箱和3级深度的2个接收缓存器FIFO,28个可变的滤波器组,STM32的CAN模组结构如下图所示。
从上面结构可以看出,两个CAN都分别具备自己的发送与接收邮箱,但是28个滤波器却是公用的,通过FMR寄存器可以设置滤波器的分配方式,STM32的每个滤波器组的位宽都可以独立配置,根据位宽的不同,每个滤波器组可以提供
(1)1个32位过滤器:包含STDID[10:0],EXTID[17:0],18位扩展ID,IDE和RTR位
(2)1个16位过滤器:包含STDID[10:0],IDE,RTR和EXTID[17:15]
此外过滤器可以配置为屏蔽位模式与标识符列表模式。
(1)在屏蔽位模式下,标识符寄存器和屏蔽寄存器一起,指定报文标识符的任何一位,应该按照“必须匹配”或“不用关心”处理
(2)标识符列表模式下,屏蔽寄存器也被当作标识符寄存器用。因此,不是采用一个标识符加一个屏蔽位的方式,而是使用2个标识符寄存器。接收报文标识符的每一位都必须跟过滤器标识符相同。
第1步:程序选择1个空置的邮箱(TME=1),设置标识符(ID),数据长度和发送数据
第2步:设置CAN_TIxR的TXRQ位为1,请求发送
第3步:邮箱挂号(等待成为最高优先级)
第4步:预定发送(等待总线空闲)
第5步:发送
第6步:邮箱空置
整个发送流程如下图所示。
CAN接收到的有效报文,被存储在3级邮箱深度的FIFO中。FIFO完全由硬件来管理,从而节省了CPU的处理负荷,简化了软件并保证了数据的一致性。应用程序只能通过读取FIFO输出邮箱,来读取FIFO中最先收到的报文。这里的有效报文是指那些正确被接收的(直到EOF都没有错误)且通过了标识符过滤的报文。前面我们知道CAN的接收有2个FIFO,我们每个滤波器组都可以设置其关联的FIFO,通过CAN_FFA1R的设置,可以将滤波器组关联到FIFO0/FIFO1。CAN接收流程为:
FIFO空->收到有效报文->挂号1(存入FIFO的一个邮箱,这个由硬件控制,我们不需要理会)->收到有效报文->挂号2->收到有效报文->挂号3->收到有效报文->溢出
这个流程里面,我们没有考虑从FIFO读出报文的情况,实际情况是:我们必须在FIFO溢出之前,读出至少1个报文,否则下个报文到来,将导致FIFO溢出,从而出现报文丢失。每读出1个报文,相应的挂号就减1,直到FIFO空,完整的接收流程图如下图所示。
FIFO接收到的报文数,我们可以通过查询CAN_RFxR的FMP寄存器来得到,只要FMP不为0,我们就可以从FIFO读出收到的报文。
根据通信参数小节我们可以知道1个位是由4个段组成,分别为同步段(SS),传播时间段(PTS),相位缓冲段1(PBS1),相位缓冲段2(PBS2),STM32内部将传播时间段与相位缓冲段1合并为时间段1,这样1个位就是有3个段组成,即同步段SS,时间段1和时间段2(即相位缓冲段),由于波特率的定义是1秒内发送二进制位的个数,所以,CAN波特率的计算公式为
(1)静默模式
通过对CAN_BTR寄存器的SILM位置1,来选择静默模式。在静默模式下,CAN可以正常地接收数据帧和远程帧,但只能发出隐性位,而不能真正发送报文。如果bxCAN需要发出显性位(确认位、过载标志、主动错误标志),那么这样的显性位在内部被接回来从而可以被CAN内核检测到,同时CAN总线不会受到影响而仍然维持在隐性位状态。因此,静默模式通常用于分析CAN总线的活动,而不会对总线造成影响-显性位(确认位、错误帧)不会真正发送到总线上,静默模式等效图如下图所示。
(2)环回模式
通过对CAN_BTR寄存器的LBKM位置1,来选择环回模式。在环回模式下,CAN把发送的报文当作接收的报文并保存(如果可以通过接收过滤)在接收邮箱里,环回模式等效图如下图所示。
(3)环回静默模式
该模式可用于“热自测试”,即可以像环回模式那样测试CAN,但却不会影响CANTX和CANRX所连接的整个CAN系统。在环回静默模式下,CANRX引脚与CAN总线断开,同时CANTX引脚被驱动到隐性位状态,环回静默模式等效图如下图所示。
31 | 30 | 29 | 28 | 27 | 26 | 25 | 24 | 23 | 22 | 21 | 20 | 19 | 18 | 17 | 16 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
- | DBF | ||||||||||||||
15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
RESET | - | TTCM | ABOM | AWUM | NART | RFLM | TXFP | SLEEP | INRQ |
Bit 16:DBF:调试冻结
0:在调试时,CAN照常工作
1:在调试时,冻结CAN的接收/发送。仍然可以正常地读写和控制接收FIFO
Bit 15:bxCAN软件复位
0:本外设正常工作
1:对bxCAN进行强行复位,复位后bxCAN进入睡眠模式。此后硬件自动对该位清0
Bit 7:时间触发通信模式
0:禁止时间触发通信模式
1:允许时间触发通信模式
Bit 6:自动离线管理
0:离线状态的退出过程是,软件对INRQ位进行置1随后清0后,一旦硬件检测到128次11位连续的隐性位,则退出离线状态
1:一旦硬件检测到128次11位连续的隐性位,则自动退出离线状态
Bit 5:自动唤醒模式
0:睡眠模式通过清除CAN_MCR寄存器的SLEEP位,由软件唤醒
1:睡眠模式通过检测CAN报文,由硬件自动唤醒。唤醒的同时,硬件自动对SLEEP和SLAK位清0
Bit 4:禁止报文自动重传
0:按照CAN标准,CAN硬件在发送报文失败时会一直自动重传直到发送成功
1:CAN报文只被发送1次,不管发送的结果如何
Bit 3:接收FIFO锁定模式
0:在接收溢出时FIFO未被锁定,当接收FIFO的报文未被读出,下一个收到的报文会覆盖原有的报文
1:在接收溢出时FIFO被锁定,当接收FIFO的报文未被读出,下一个收到的报文会被丢弃
Bit 2:发送FIFO优先级
0:优先级由报文的标识符来决定
1:优先级由发送请求的顺序来决定
Bit 1:睡眠模式请求(在复位后该位被置1)
1:可以请求CAN进入睡眠模式,一旦当前的CAN活动结束,CAN就进入睡眠
0:使CAN退出睡眠模式
Bit 0:初始化请求
0:当CAN在接收引脚检测到连续的11个隐性位后,CAN就达到同步,并为接收和发送数据作好准备了。为此,硬件相应地对INAK位清0
1:一旦当前的CAN活动结束,CAN就进入初始化模式。相应地,硬件对INAK位置1
15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
- | RX | SAMP | RXM | TXM | - | SLAKI | WKUI | ERRI | SLAK | INAK |
Bit 11:CAN接收电平,该位反映CAN接收引脚的实际电平
Bit 10:上次采样值,CAN接收引脚的上次采样值(对应于当前接收位的值)
Bit 9:接收模式,该位为1表示CAN当前为接收器
Bit 8:发送模式,该位为1表示CAN当前为发送器
Bit 4:睡眠确认中断,当SLKIE=1,一旦CAN进入睡眠模式硬件就对该位置1,紧接着相应的中断被触发软件可对该位清0,当SLAK位被清0时硬件也对该位清0
Bit 3:唤醒中断挂号,当CAN处于睡眠状态,一旦检测到帧起始位,硬件就置该位为1
Bit 2:出错中断挂号,当检测到错误时,CAN_ESR寄存器的某位被置1,如果CAN_IER寄存器的相应中断使能位也被置1时,则硬件对该位置1
Bit 1:睡眠模式确认
0:CAN退出睡眠模式
1:CAN模块正处于睡眠模式
Bit 0:初始化确认
0:CAN退出初始化模式时
1:CAN模块正处于初始化模式
31 | 30 | 29 | 28 | 27 | 26 | 25 | 24 | 23 | 22 | 21 | 20 | 19 | 18 | 17 | 16 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
SILM | LBKM | - | SJW[1:0] | - | TS2[2:0] | TS1[3:0] | |||||||||
15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
- | BRP[9:0] |
Bit 31:静默模式(用于调试)
0:正常状态
1:静默模式
Bit 30:环回模式(用于调试)
0:禁止环回模式
1:允许环回模式
Bit 25~Bit 24:重新同步跳跃宽度,该位域定义了CAN硬件在每位中可以延长或缩短多少个时间单元的上限,t RJW =t CAN ×(SJW[1:0]+1)
Bit 22~Bit 20:时间段2,该位域定义了时间段2占用了多少个时间单元,t BS2 =t CAN ×(TS2[2:0]+1)
Bit 19~Bit 16:时间段1,该位域定义了时间段1占用了多少个时间单元,t BS1 =t CAN ×(TS1[3:0]+1)
Bit 9Bit 0:波特率分频器,该位域定义了时间单元tq的时间长度,t PCLK~q =(BRP[9:0]+1)×t
31 | 30 | 29 | 28 | 27 | 26 | 25 | 24 | 23 | 22 | 21 | 20 | 19 | 18 | 17 | 16 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
LOW2 | LOW1 | LOW0 | TME2 | TME1 | TME0 | CODE[1:0] | ABRQ2 | - | TERR2 | ALST2 | TXOK2 | RQCP2 | |||
15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
ABRQ1 | - | TERR1 | ALST1 | TXOK1 | RQCP1 | ABRQ0 | - | TERR0 | ALST0 | TXOK0 | RQCP0 |
Bit 31,Bit30,Bit 29:邮箱x最低优先级标志
当多个邮箱在等待发送报文,且邮箱x的优先级最低时,硬件对该位置1
Bit 28,Bit 27,Bit 26:发送邮箱x空
Bit 25~Bit 24:邮箱号
当有至少1个发送邮箱为空时,表示下一个空的发送邮箱号。
当所有的发送邮箱都为空时,表示优先级最低的那个发送邮箱号
Bit 23,Bit 15,Bit 7:邮箱x终止发送
Bit 19,Bit 11,Bit 3:邮箱x发送失败
Bit 18,Bit 10,Bit 2:邮箱x仲裁丢失
Bit 17,Bit 9,Bit 1:邮箱x发送成功
Bit 16,Bit 8,Bit 0:邮箱x请求完成
15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
- | ROFM | FOVR | FULL | - | FMP[1:0] |
Bit 5:释放接收FIFO,输出邮箱软件通过对该位置1来释放接收FIFO的输出邮箱。如果接收FIFO为空,那么对该位置1没有任何效果,即只有当FIFO中有报文时对该位置1才有意义。如果FIFO中有2个以上的报文,由于FIFO的特点,软件需要释放输出邮箱才能访问第2个报文,当输出邮箱被释放时,硬件对该位清0
Bit 4:FIFO溢出,当FIFO0已满,又收到新的报文且报文符合过滤条件,硬件对该位置1,该位由软件清0
Bit 3:FIFO满,当FIFO0中有3个报文时,硬件对该位置1,该位由软件清0
Bit 1~Bit 0:FIFO报文数目,表示当前接收FIFO0中存放的报文数目
每当1个新的报文被存入接收FIFO0,硬件就对FMP0加1
每当软件对RFOM位写1来释放输出邮箱,FMP就被减1,直到其为0
15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
- | FINIT |
Bit 0:过滤器初始化模式
0:过滤器组工作在正常模式
1:过滤器组工作在初始化模式
31 | 30 | 29 | 28 | 27 | 26 | 25 | 24 | 23 | 22 | 21 | 20 | 19 | 18 | 17 | 16 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
- | FBM[27:16] | ||||||||||||||
15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
FBM[15:0] |
Bit 29~Bit 0:过滤器模式
0:过滤器组x的2个32位寄存器工作在标识符屏蔽位模式
1:过滤器组x的2个32位寄存器工作在标识符列表模式
31 | 30 | 29 | 28 | 27 | 26 | 25 | 24 | 23 | 22 | 21 | 20 | 19 | 18 | 17 | 16 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
- | FSC[27:16] | ||||||||||||||
15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
FSC[15:0] |
Bit 29~Bit 0:过滤器位宽设置
0:过滤器位宽为2个16位
1:过滤器位宽为单个32位
31 | 30 | 29 | 28 | 27 | 26 | 25 | 24 | 23 | 22 | 21 | 20 | 19 | 18 | 17 | 16 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
FFA[27:16] | |||||||||||||||
15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
FFA[15:0] |
Bit 29~Bit 0:过滤器关联设置
0:过滤器被关联到FIFO0
1:过滤器被关联到FIFO1
31 | 30 | 29 | 28 | 27 | 26 | 25 | 24 | 23 | 22 | 21 | 20 | 19 | 18 | 17 | 16 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
FACT[27:16] | |||||||||||||||
15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
FACT[15:0] |
Bit 29~Bit 0:过滤器激活
0:过滤器被禁用
1:过滤器被激活
31 | 30 | 29 | 28 | 27 | 26 | 25 | 24 | 23 | 22 | 21 | 20 | 19 | 18 | 17 | 16 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
STID[10:0]/EXID[28:18] | EXID[17:13] | ||||||||||||||
15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
EXID[12:0] | IDE | RTR | TXRQ |
Bit 31~Bit 21:标准标识符或扩展标识符,依据IDE位的内容,这些位或是标准标识符,或是扩展身份标识的高字节
Bit 20~Bit 3:扩展标识符,扩展身份标识的低字节
Bit 2:标识符选择
0:使用标准标识符
1:使用扩展标识符
Bit 1:远程发送请求
0:数据帧
1:远程帧
Bit 0:发送数据请求,由软件对其置1,来请求发送邮箱的数据。当数据发送完成,邮箱为空时,硬件对其清0
31 | 30 | 29 | 28 | 27 | 26 | 25 | 24 | 23 | 22 | 21 | 20 | 19 | 18 | 17 | 16 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
TIME[15:0] | |||||||||||||||
15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
- | TGT | - | DLC[3:0] |
Bit 31~Bit 16:报文时间戳,该域包含了,在发送该报文SOF的时刻,16位定时器的值
Bit 8:发送时间戳(只有在CAN处于时间触发通信模式才有效)
0:不发送时间戳TIME[15:0]
1:发送时间戳TIME[15:0]
注:在长度为8的报文中,时间戳TIME[15:0]是最后2个发送的字节:TIME[7:0]作为第7个字节,TIME[15:8]为第8个字节,替换了写入CAN_TDHxR[31:16]的数据。为了把时间戳的2个字节发送出去,DLC必须编程为8。
Bit 3~Bit 0:发送数据长度,指定数据报文的数据长度或者远程帧请求的数据长度
31 | 30 | 29 | 28 | 27 | 26 | 25 | 24 | 23 | 22 | 21 | 20 | 19 | 18 | 17 | 16 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
DATA3[7:0] | DATA2[7:0] | ||||||||||||||
15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
DATA1[7:0] | DATA0[7:0] |
Bit 31~Bit 24:数据字节3
Bit 23~Bit 16:数据字节2
Bit 15~Bit 8:数据字节1
Bit 7~Bit 0:数据字节0
31 | 30 | 29 | 28 | 27 | 26 | 25 | 24 | 23 | 22 | 21 | 20 | 19 | 18 | 17 | 16 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
DATA7[7:0] | DATA6[7:0] | ||||||||||||||
15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
DATA5[7:0] | DATA4[7:0] |
Bit 31~Bit 24:数据字节7
Bit 23~Bit 16:数据字节6
Bit 15~Bit 8:数据字节5
Bit 7~Bit 0:数据字节4
31 | 30 | 29 | 28 | 27 | 26 | 25 | 24 | 23 | 22 | 21 | 20 | 19 | 18 | 17 | 16 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
STID[10:0]/EXID[28:18] | EXID[17:13] | ||||||||||||||
15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
EXID[12:0] | IDE | RTR | - |
Bit 31~Bit 21:标准标识符或扩展标识符,依据IDE位的内容,这些位或是标准标识符,或是扩展身份标识的高字节
Bit 20~Bit 3:扩展标识符,扩展身份标识的低字节
Bit 2:标识符选择
0:使用标准标识符
1:使用扩展标识符
Bit 1:远程发送请求
0:数据帧
1:远程帧
31 | 30 | 29 | 28 | 27 | 26 | 25 | 24 | 23 | 22 | 21 | 20 | 19 | 18 | 17 | 16 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
TIME[15:0] | |||||||||||||||
15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
FMI[7:0] | - | DLC[3:0] |
Bit 31~Bit 16:报文时间戳,该域包含了,在接收该报文SOF的时刻,16位定时器的值
Bit 15~Bit 8:过滤器匹配序号
Bit 3~Bit 0:接收数据长度
31 | 30 | 29 | 28 | 27 | 26 | 25 | 24 | 23 | 22 | 21 | 20 | 19 | 18 | 17 | 16 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
DATA3[7:0] | DATA2[7:0] | ||||||||||||||
15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
DATA1[7:0] | DATA0[7:0] |
Bit 31~Bit 24:数据字节3
Bit 23~Bit 16:数据字节2
Bit 15~Bit 8:数据字节1
Bit 7~Bit 0:数据字节0
31 | 30 | 29 | 28 | 27 | 26 | 25 | 24 | 23 | 22 | 21 | 20 | 19 | 18 | 17 | 16 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
DATA7[7:0] | DATA6[7:0] | ||||||||||||||
15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
DATA5[7:0] | DATA4[7:0] |
Bit 31~Bit 24:数据字节7
Bit 23~Bit 16:数据字节6
Bit 15~Bit 8:数据字节5
Bit 7~Bit 0:数据字节4
实验内容:利用CAN的回环模式进行数据收发测试,通信速率500Kbps。
(1)创建can.h文件,并输入以下代码。
/*********************************************************************************************************
CAN 通 信 驱 动 文 件
*********************************************************************************************************/
#ifndef _CAN_H_
#define _CAN_H_
#include "sys.h"
/*********************************************************************************************************
函 数 列 表
*********************************************************************************************************/
u8 CAN_Init( u8 tsjw, u8 tbs2, u8 tbs1, u16 brp, u8 mode ) ; //CAN初始化
u8 CAN_Tx_Msg( u32 id, u8 ide, u8 rtr, u8 len, u8 *dat ) ; //CAN发送数据
void CAN_Rx_Msg( u8 fifox, u32 *id, u8 *ide, u8 *rtr, u8 *len, u8 *dat ) ; //CAN接收数据
#endif
(2)创建can.c文件,并输入以下代码。
/*********************************************************************************************************
CAN 通 信 驱 动 程 序
*********************************************************************************************************/
#include "can.h"
/*******************************************************
Name :CAN_Mode_Init
Function :CAN初始化
Paramater :
tsjw:重新同步跳跃时间单元.范围:1~3
tbs2:时间段2的时间单元.范围:1~8
tbs1:时间段1的时间单元.范围:1~16
brp:波特率分频器.范围:1~1024
mode:工作模式
0:普通模式
1:回环模式
Return :
0:成功
其他:失败
*******************************************************/
u8 CAN_Init( u8 tsjw, u8 tbs2, u8 tbs1, u16 brp, u8 mode )
{
u16 i=0;
if( ( tsjw==0 )||( tbs2==0 )||( tbs1==0 )||( brp==0 ) )
return 1 ;
//先减去1.再用于设置
tsjw -= 1 ;
tbs2 -= 1 ;
tbs1 -= 1 ;
brp -= 1 ;
RCC->APB2ENR |= 1<<2 ; //使能PA时钟
GPIOA->CRH &= 0xFFF00FFF ;
GPIOA->CRH |= 0x000B8000 ;
GPIOA->ODR |= 3<<11 ;
RCC->APB1ENR |= 1<<25 ; //使能CAN时钟
CAN1->MCR = 0x0000 ; //退出睡眠模式
CAN1->MCR |= 1<<0 ; //请求CAN进入初始化模式
while( ( CAN1->MSR&0x01 )==0 )
{
i ++ ;
if( i>100 )
return 2 ; //进入初始化模式失败
}
CAN1->MCR |= 0<<7 ; //非时间触发通信模式
CAN1->MCR |= 0<<6 ; //软件自动离线管理
CAN1->MCR |= 0<<5 ; //睡眠模式通过软件唤醒
CAN1->MCR |= 1<<4 ; //禁止报文自动传送
CAN1->MCR |= 0<<3 ; //报文不锁定,新的覆盖旧的
CAN1->MCR |= 0<<2 ; //优先级由报文标识符决定
CAN1->BTR = 0x00000000 ; //清除原来的设置
CAN1->BTR |= mode<<30 ; //模式设置
CAN1->BTR |= tsjw<<24 ; //重新同步跳跃宽度(Tsjw)为tsjw+1个时间单位
CAN1->BTR |= tbs2<<20 ; //Tbs2=tbs2+1个时间单位
CAN1->BTR |= tbs1<<16 ; //Tbs1=tbs1+1个时间单位
CAN1->BTR |= brp<<0 ; //分频系数(Fdiv)为brp+1
CAN1->MCR &= ~( 1<<0 ) ; //请求CAN退出初始化模式
while( ( CAN1->MSR&0x01 )!=0 )
{
i ++ ;
if( i>0xFFF0 )
return 3 ; //退出初始化模式失败
}
//过滤器初始化
CAN1->FMR |= 1<<0 ; //过滤器组工作在初始化模式
CAN1->FA1R &= ~( 1<<0 ) ; //过滤器0不激活
CAN1->FS1R |= 1<<0 ; //过滤器位宽为32位
CAN1->FM1R |= 0<<0 ; //过滤器0工作在标识符屏蔽位模式
CAN1->FFA1R |= 0<<0 ; //过滤器0关联到FIFO0
CAN1->sFilterRegister[ 0 ].FR1 = 0x00000000 ; //32位ID
CAN1->sFilterRegister[ 0 ].FR2 = 0x00000000 ; //32位MASK
CAN1->FA1R |= 1<<0 ; //激活过滤器0
CAN1->FMR &= 0<<0 ; //过滤器组进入正常模式
return 0 ;
}
/*******************************************************
Name :CAN_Tx_Msg
Function :CAN发送数据
Paramater :
id:标准ID(11位)/扩展ID(11位+18位)
ide:ID类型
0:标准帧
1:扩展帧
rtr:数据类型
0:数据帧
1:远程帧
len:要发送的数据长度
*dat:数据指针
Return :
0~3:邮箱编号
0xFF:无有效邮箱
*******************************************************/
u8 CAN_Tx_Msg( u32 id, u8 ide, u8 rtr, u8 len, u8 *dat )
{
u16 i;
u8 mbox, sta=0 ;
//邮箱0为空
if( CAN1->TSR&( 1<<26 ) )
mbox = 0 ;
//邮箱1为空
else if( CAN1->TSR&( 1<<27 ) )
mbox = 1 ;
//邮箱2为空
else if( CAN1->TSR&( 1<<28 ) )
mbox = 2 ;
//无空邮箱,无法发送
else
return 0xFF ;
CAN1->sTxMailBox[ mbox ].TIR = 0 ; //清除之前的设置
//标准帧
if( ide==0 )
{
id &= 0x7FF ; //取低11位stdid
id <<= 21 ;
}
//扩展帧
else
{
id &= 0x1FFFFFFF ; //取低32位extid
id <<= 3 ;
}
CAN1->sTxMailBox[ mbox ].TIR |= id ;
CAN1->sTxMailBox[ mbox ].TIR |= ide<<2 ;
CAN1->sTxMailBox[ mbox ].TIR |= rtr<<1 ;
len &= 0x0F ; //得到低四位
CAN1->sTxMailBox[ mbox ].TDTR &= 0xFFFFFFF0 ;
CAN1->sTxMailBox[ mbox ].TDTR |= len ; //设置DLC
//待发送数据存入邮箱
CAN1->sTxMailBox[ mbox ].TDHR = ( ( (u32)dat[7]<<24 )|( (u32)dat[6]<<16 )|( (u32)dat[5]<<8 )|( (u32)dat[4] ) ) ;
CAN1->sTxMailBox[ mbox ].TDLR = ( ( (u32)dat[3]<<24 )|( (u32)dat[2]<<16 )|( (u32)dat[1]<<8 )|( (u32)dat[0] ) ) ;
CAN1->sTxMailBox[ mbox ].TIR |= 1<<0 ; //请求发送邮箱数据
//获取发送状态
while( ( sta!=0x07 )&&( i<0xFFF ) )
{
i ++ ;
switch( mbox )
{
//邮箱0
case 0:
sta |= CAN1->TSR&( 1<<0 ) ; //RQCP0
sta |= CAN1->TSR&( 1<<1 ) ; //TXOK0
sta |= CAN1->TSR&( 1<<26 )>>24 ; //TME0
break;
//邮箱1
case 1:
sta |= CAN1->TSR&( 1<<8 )>>8 ; //RQCP1
sta |= CAN1->TSR&( 1<<9 )>>8 ; //TXOK1
sta |= CAN1->TSR&( 1<<27 )>>25 ; //TME1
break;
//邮箱2
case 2:
sta |= CAN1->TSR&( 1<<16 )>>16 ; //RQCP2
sta |= CAN1->TSR&( 1<<17 )>>16 ; //TXOK2
sta |= CAN1->TSR&( 1<<28 )>>26 ; //TME2
break;
//邮箱号不对
default:
sta = 0x05 ;
break ;
}
}
if( i==0xFFF )
mbox = 0xFF ;
return mbox ;
}
/*******************************************************
Name :CAN_Rx_Msg
Function :CAN接收数据
Paramater :
fifox:邮箱号
id:标准ID(11位)/扩展ID(11位+18位)
ide:ID类型
0:标准帧
1:扩展帧
rtr:数据类型
0:数据帧
1:远程帧
len:要发送的数据长度
*dat:数据指针
Return :None
*******************************************************/
void CAN_Rx_Msg( u8 fifox, u32 *id, u8 *ide, u8 *rtr, u8 *len, u8 *dat )
{
if( ( fifox==0 )&&( ( CAN1->RF0R&0x03 )==0 ) )
len = 0 ;
else if( ( fifox==1 )&&( ( CAN1->RF1R&0x03 )==0 ) )
len = 0 ;
else
{
//接收数据
*ide = CAN1->sFIFOMailBox[ fifox ].RIR&0x04 ; //得到标识符选择位的值
if( *ide==0 )
*id = CAN1->sFIFOMailBox[ fifox ].RIR>>21 ; //标准标识符
else
*id = CAN1->sFIFOMailBox[ fifox ].RIR>>3 ; //扩展标识符
*rtr = CAN1->sFIFOMailBox[ fifox ].RIR&0x02 ; //得到远程发送请求值
*len = CAN1->sFIFOMailBox[ fifox ].RDTR&0x0F ; //得到DLC
dat[ 0 ] = CAN1->sFIFOMailBox[ fifox ].RDLR&0xFF ;
dat[ 1 ] = ( CAN1->sFIFOMailBox[ fifox ].RDLR>>8 )&0xFF ;
dat[ 2 ] = ( CAN1->sFIFOMailBox[ fifox ].RDLR>>16 )&0xFF ;
dat[ 3 ] = ( CAN1->sFIFOMailBox[ fifox ].RDLR>>24 )&0xFF ;
dat[ 4 ] = CAN1->sFIFOMailBox[ fifox ].RDHR&0xFF ;
dat[ 5 ] = ( CAN1->sFIFOMailBox[ fifox ].RDHR>>8 )&0xFF ;
dat[ 6 ] = ( CAN1->sFIFOMailBox[ fifox ].RDHR>>16 )&0xFF ;
dat[ 7 ] = ( CAN1->sFIFOMailBox[ fifox ].RDHR>>24 )&0xFF ;
if( fifox==0 )
CAN1->RF0R |= 0x20 ; //释放FIFO0邮箱
else if( fifox==1 )
CAN1->RF1R |= 0x20 ; //释放FIFO1邮箱
}
}
(3)创建1.c文件,并输入以下代码。
#include "sys.h"
#include "delay.h"
#include "usart1.h"
#include "can.h"
int main()
{
u32 id;
u8 ide, rtr, len, canbuf[ 8 ] ;
STM32_Clock_Init( 9 ) ; //STM32时钟初始化
SysTick_Init( 72 ) ; //SysTick初始化
USART1_Init( 72, 115200 ) ; //初始化串口1波特率115200
CAN_Init( 1, 8, 9, 4, 1 ) ; //CAN初始化
CAN_Tx_Msg( 0x12, 0, 0, 8, "CAN Test" ) ; //发送8个字节
while( 1 )
{
CAN_Rx_Msg( 0, &id, &ide, &rtr, &len, canbuf ) ; //读取数据
}
}
全部0条评论
快来发表一下你的评论吧 !