STM32 ——CAN协议

STM32 ——CAN协议

物理层

CAN 收发器根据两根总线(CAN_High 和 CAN_Low)的电位差来判断总线电平。

总线电平分为显性电平和隐性电平两种。总线必须处于两种电平之一。总线上执行逻辑上的线“与”时,显性电平为“0”,隐性电平为“1”。

显性电平对应逻辑 0,CAN_H 和 CAN_L 之差为 2.5V 左右。而隐性电平对应逻辑 1,CAN_H 和 CAN_L 之差为 0V

图为CAN协议的物理层,CAN有ISO11898和ISO11519-2( 两个标准,我使用的的ISO11898,最高的波特率是1Mbps
图为CAN协议的物理层,CAN有ISO11898和ISO11519-2( 两个标准,我使用的的ISO11898,**最高的波特率是1Mbps**

CAN——帧种类

CAN 协议是通过以下 5 种类型的帧进行的:
 数据帧
 要控帧
 错误帧
 过载帧
 帧间隔
另外,数据帧和遥控帧有标准格式和扩展格式两种格式。标准格式有 11 个位的标识符(ID),扩展格式有 29 个位的 ID。

各种帧的用途:
数据帧 :用于发送单元向接收单元传送数据的帧。
遥控帧 :用于接收单元向具有相同 ID 的发送单元请求数据的帧。
错误帧 :用于当检测出错误时向其它单元通知错误的帧。
过载帧 :用于接收单元通知其尚未做好接收准备的帧。
间隔帧 :用于将数据帧及遥控帧与前面的帧分离开来的帧。

其中数据帧由以下7个段构成:
(1) 帧起始。表示数据帧开始的段。
(2) 仲裁段。表示该帧优先级的段。
(3) 控制段。表示数据的字节数及保留位的段。
(4) 数据段。数据的内容,一帧可发送 0~8 个字节的数据。
(5) CRC 段。检查帧的传输错误的段。
(6) ACK 段。表示确认正常接收的段。
(7) 帧结束。表示数据帧结束的段。

接下来,我们再来看看 CAN 的位时序。
由发送单元在非同步的情况下发送的每秒钟的位数称为位速率。一个位可分为 4 段。
 同步段(SS)
 传播时间段(PTS)
 相位缓冲段 1(PBS1)
 相位缓冲段 2(PBS2)
这些段又由可称为 Time Quantum(以下称为 Tq)的最小时间单位构成。
1 位分为 4 个段,每个段又由若干个 Tq 构成,这称为位时序。
1 位由多少个 Tq 构成、每个段又由多少个 Tq 构成等,可以任意设定位时序。通过设定
位时序,多个单元可同时采样,也可任意设定采样点。
所谓采样点是读取总线电平,并将读到的电平作为位值的点。位置在 PBS1 结束处。

在这里插入图片描述除了SS段固定是1Tq以外,SJW/PBS1/PBS2需要我们进行给值。
当检测到电平的跳变不在帧起始时,就会进行BS1段合BS2段的延长和缩短,其中延长和缩短的最大值就是SJW的值。
在这里插入图片描述

其中:
波特率=APB1Clock/(1+CAN_BS1+CAN_BS2)/CAN_Prescaler
例如我使用的是 SJW:1Tq BS1:5Tq BS2:3Tq 预分频系数:4.
所以波特率是36000000/(1+5+3)/4=1Mbps
但波特率的计算并不重要,在网上有大佬将要用的波特率做成表格的形式,只要找到表,将数值一一填进就行了。

过滤器

STM32的标识符过滤是一个比较复杂的东东,它的存在减少了CPU处理CAN通信的开销。
STM32 的过滤器组最多有 28 个(互联型),但是 STM32F103ZET6 只有 14 个(增强型),每个
滤波器组 x 由 2 个 32 为寄存器,CAN_FxR1 和 CAN_FxR2 组成。
STM32 每个过滤器组的位宽都可以独立配置,以满足应用程序的不同需求。根据位宽的不
同,每个过滤器组可提供:
● 1 个 32 位过滤器,包括:STDID[10:0]、EXTID[17:0]、IDE 和 RTR 位
● 2 个 16 位过滤器,包括:STDID[10:0]、IDE、RTR 和 EXTID[17:15]位
此外过滤器可配置为,屏蔽位模式和标识符列表模式

为了过滤出一组标识符,应该设置过滤器组工作在屏蔽位模式。
为了过滤出一个标识符,应该设置过滤器组工作在标识符列表模式。
应用程序不用的过滤器组,应该保持在禁用状态。

在屏蔽位模式下,标识符寄存器和屏蔽寄存器一起,指定报文标识符的任何一位,应该按
照“必须匹配”或“不用关心”处理。

在这里插入图片描述

例如我使用32位的过滤器标识符屏蔽模式,把要接受的报文ID写成 CAN_F0R1=0xFFFF0000(STID+EXID+IDE+RTR+0),我想要我收到我报文是0xFFF****( * 号代表随意十六进制数额),也就是说 * 号是我不关心的内容,那么我就需要将屏蔽位设为CAN_F0R2=0XF0F0F0000,就代表着我收到的报文要是0xFFF****的形式,不然就过滤掉。

工作模式

can有4种工作模式——静默模式、回环(环回)模式、静默回环模式和正常模式。
其中我用的是回环模式来进行调试,当回环模式中我能收到我想要的报文和数据时,再改为正常模式就能正常的使用了。
在这里插入图片描述回环模式简单来说就是自己发送,自己接受。不需要两块板子,自己就能进行CAN的发送和接受。

代码实现

在进行初步的学习过后,我们就开始尝试写写代码。

第一步先使能CAN和引脚的时钟和配置CAN_High和CAN_LOW引脚,并配置成上拉输入和复用输出。

  	GPIO_InitTypeDef      gpio_init;
	CAN_InitTypeDef       can_init;
	CAN_FilterInitTypeDef can_filterinit; // 过滤器的结构体
	
	RCC_APB2PeriphClockCmd (RCC_APB2Periph_GPIOA,ENABLE );
	RCC_APB1PeriphClockCmd (RCC_APB1Periph_CAN1 ,ENABLE );//使能 CAN1 时钟
	
	gpio_init.GPIO_Pin=GPIO_Pin_11;
	gpio_init.GPIO_Mode=GPIO_Mode_IPU ;
	GPIO_Init(GPIOA,&gpio_init);
	
	gpio_init.GPIO_Pin=GPIO_Pin_12;
	gpio_init.GPIO_Mode=GPIO_Mode_AF_PP;
	gpio_init.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOA ,&gpio_init);

第二步配置工作模式和波特率

	//can单元设置 配置工作模式 CAN_Mode_LoopBack  CAN_Mode_Normal
	can_init.CAN_Mode=CAN_Mode_LoopBack;//回环模式,测试完后改为普通模式
	can_init.CAN_TTCM=DISABLE;			//非时间触发通信模式  
	can_init.CAN_ABOM=ENABLE;			//软件自动离线管理	 
	can_init.CAN_AWUM=ENABLE;			//睡眠模式通过软件唤醒(清除CAN->MCR的SLEEP位)
	can_init.CAN_NART=DISABLE;			//报文错误自动传送 
	can_init.CAN_RFLM=DISABLE;		 	//报文不锁定,新的覆盖旧的  
	can_init.CAN_TXFP=DISABLE;			//优先级由报文标识符决定 
	//设置波特率 1Mbps SYNC_SE段固定是1Tq
	can_init.CAN_SJW =CAN_SJW_1tq;
	can_init.CAN_BS1= CAN_BS1_5tq;
	can_init.CAN_BS2= CAN_BS2_3tq;//bs1+bs2+se段=9Tq 
	can_init.CAN_Prescaler=4;//内部会自减1  
	CAN_Init(CAN1 ,&can_init);

需要注意的是 你F12进SJW的宏定义你会发现,他内部已经帮你自减好了。Tq的限制也能在里面看到。

第三步是过滤器

/* can过滤器 */
	can_filterinit.CAN_FilterActivation=ENABLE; //是否使能过滤器
	can_filterinit.CAN_FilterFIFOAssignment=CAN_FilterFIFO0;//数据存入FIFO0
	can_filterinit.CAN_FilterNumber=0;//0到13 过滤器0
	can_filterinit.CAN_FilterScale=CAN_FilterScale_32bit;//16位 32位
	can_filterinit.CAN_FilterMode=CAN_FilterMode_IdMask;
	/* 过滤的是扩展帧、扩展数据帧 */
	can_filterinit.CAN_FilterIdHigh= ((PASS_ID<<3|CAN_ID_EXT|CAN_RTR_Data)&0xFFFF0000)>>16 ;//过滤的ID0x0000;//((PASS_ID<<3|CAN_ID_EXT|CAN_RTR_Data)&0xFFFF0000)>>16
	can_filterinit.CAN_FilterIdLow=   ((PASS_ID<<3|CAN_ID_EXT|CAN_RTR_Data)&0xFFFF) ;//0x0000;// ((PASS_ID<<3|CAN_ID_EXT|CAN_RTR_Data)&0xFFFF)
	/*扩展数据帧的低16位,不需要位移 */
	can_filterinit.CAN_FilterMaskIdHigh=0x0000;//32位MASK0x01E0;// 
	can_filterinit.CAN_FilterMaskIdLow=0x0000;//不正常的配置0x0000;//
	CAN_FilterInit(&can_filterinit);			//滤波器初始化

	nvic_cfg (USB_LP_CAN1_RX0_IRQn,0,0);//接收邮箱0
	/*中断函数错的话 会卡在启动文件*/
	CAN_ITConfig (CAN1 ,CAN_IT_FMP0,ENABLE );//CAN_IT_FMP0接受邮箱0产生的中断,使能中断

需要的报文和上边的配置大同小异,就是先将想要的报文先左移3位,给IDE和RTR腾出位置,最后一位补0。我这里用的是32位的屏蔽位模式,下面的我把屏蔽位都为0,也就是说不管发送的报文是什么,我都接受,但这样是不符合标准的,不用过滤器要把他给屏蔽了。

最后就是报文的发送和接受了
CanRxMsg CAN_Rece_Data;//接受的数据存储在这里
CanTxMsg CAN_Tran_Data;//发送的数据
这里用到的是这两个结构体。

			CAN_Tran_Data.StdId=PASS_ID ;
			CAN_Tran_Data.ExtId=0;//发送的是扩展帧
			CAN_Tran_Data.RTR=CAN_RTR_Data;//数据帧
			CAN_Tran_Data.IDE=CAN_Id_Standard;
			CAN_Tran_Data.DLC=2;//一个字节
			
			CAN_Tran_Data.Data[0]=1+m;
			CAN_Tran_Data.Data[1]=100+m;  
			/*CAN_Transmit()返回的是他的发送邮箱号 */
			box=CAN_Transmit (CAN1 ,&CAN_Tran_Data);
			while(CAN_TransmitStatus(CAN1 ,box)==CAN_TxStatus_Failed);
			/* 等待报文的发送完成*/

Stdid表示我要发送的是标准帧的报文,其实扩展帧不用为0也行,用RTR位表示要发送的是标准帧(0)还是扩展帧(1),IDE位表示我要发送的是数据帧还是遥控帧。如果是遥控帧,那么只发送报文。DLC就是要发送的字节了,最高有8个字节。而Data【】就是要发送的内容了。

void USB_LP_CAN1_RX0_IRQHandler(void)
{
	CAN_Receive (CAN1 ,CAN_FIFO0,&CAN_Rece_Data);//接受的数据位存储在这里
	if(CAN_GetITStatus(CAN1,CAN_IT_FMP0 )==RESET)
	{
			for(j=0;j<8;j++)
	{
			printf("第%d个接受的数据是:%d/r/n          ",j,CAN_Rece_Data.Data[j]);
	}
}
	}

实现的效果

在这里插入图片描述但要注意的是,回环模式不会对IO口和波特率进行检测。
所以但回环能使用而正常模式不能时,可以检查一下自己的IO是否配置正确。CAN-H是否接CAN-H,引脚是否已经被使用,波特率是否正确等等进行一步步的排除,直到最后还是没找到问题的所在的话,可以怀疑下自己的板子是否有问题,找找例程烧烧看。

还有一个问题是,我用的是正点原子的精英版,默认是p6的跳帽接的是USB的,需要把跳帽接到CAN上。
在这里插入图片描述附波特率参考图:
http://www.51hei.com/bbs/dpj-145867-1.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值