欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

CAN总线笔记

程序员文章站 2024-02-23 22:05:34
...

起源

在没有CAN总线之前,所有外设和控制器都是单线连接,导致线布局和数量巨大,错综复杂。
总线的含义就是所有信息都在这根线上传输,当然,CAN总线是两根。

CAN协议概念

CAN是Controller Area Network 的缩写(以下称为CAN),是ISO国际标准化的串行通信协议。由德国电气商博世公司在1986 年率先提出。此后,CAN 通过ISO11898 及ISO11519 进行了标准化。现在在欧洲已是汽车网络的标准协议。

CAN协议经过ISO标准化后有两个标准:ISO11898标准和ISO11519-2标准。其中ISO11898是针对通信速率为125Kbps~1Mbps的高速通信标准,而ISO11519-2是针对通信速率为125Kbps以下的低速通信标准。

CAN具有很高的可靠性,广泛应用于:汽车电子、工业自动化、船舶、医疗设备、工业设备等方面。

CAN协议的特点

①,多主控制。总线空闲时,所有单元都可发送消息,而两个以上的单元同时开始发送消息时,根据标识符(ID,非地址)决定优先级。两个以上的单元同时开始发送消息时,对各消息ID 的每个位进行逐个仲裁比较。仲裁获胜(优先级最高)的单元可继续发送消息,仲裁失利的单元则立刻停止发送而进行接收工作。
②,系统柔软性。连接总线的单元,没有类似“地址”的信息,因此,在总线上添加单元时,已连接的其他单元的软硬件和应用层都不需要做改变。
③,速度快,距离远。最高1Mbps(距离<40M),最远可达10KM(速率<5Kbps)。
④ ,具有错误检测、错误通知和错误恢复功能。所有单元都可以检测错误(错误检测功能),检测出错误的单元会立即同时通知其他所有单元(错误通知功能),正在发送消息的单元一旦检测出错误,会强制结束当前的发送。强制结束发送的单元会不断反复地重新发送此消息直到成功发送为止(错误恢复功能)。
⑤,故障封闭功能。CAN 可以判断出错误的类型是总线上暂时的数据错误(如外部噪声等)还是持续的数据错误(如单元内部故障、驱动器故障、断线等)。由此功能,当总线上发生持续数据错误时,可将引起此故障的单元从总线上隔离出去。
⑥,连接节点多。CAN 总线是可同时连接多个单元的总线。可连接的单元总数理论上是没有限制的。但实际上可连接的单元数受总线上的时间延迟及电气负载的限制。降低通信速度,可连接的单元数增加;提高通信速度,则可连接的单元数减少。

正是因为CAN协议的这些特点,使得CAN特别适合工业过程监控设备的互连,因此,越来越受到工业界的重视,并已公认为最有前途的现场总线之一。

bxCAN

STM32自带了基本扩展CAN外设,又称bxCAN,bxCAN的特点如下:
-支持CAN协议2.0A和2.0B主动模式
-波特率最高达1Mbps
-支持时间触发通信
-具有3个发送邮箱
-具有3级深度的2个接收FIFO
-可变的筛选器组(也称过滤器组,最多28个)

接线方式

以下是STM32的F103和F4的接线方式,具体接线方式需要参考开发板原理图。
同一块板上的接线
PA11 -> CRX
PA12 -> CTX

两块板上的接线
CANL -- CANL
CANH -- CANH

API

工程源码

源码需使用keilIDE编译。工程源码

初始化函数

①配置相关引脚的复用功能,使能CAN时钟。
要用CAN,先要使能CAN的时钟,CAN的时钟通过APB1ENR的第25位来设置。其次要设置CAN的相关引脚为复用输出,这里我们需要设置PA11为上拉输入(CAN_RX引脚)PA12为复用输出(CAN_TX引脚),并使能PA口的时钟
②设置CAN工作模式及波特率等。
通过先设置CAN_MCR寄存器的INRQ位,让CAN进入初始化模式,然后设置CAN_MCR的其他相关控制位。再通过CAN_BTR设置波特率和工作模式(正常模式/环回模式)等信息。 最后设置INRQ为0,退出初始化模式。
③设置滤波器。
本例程,我们将使用筛选器组0,并工作在32位标识符屏蔽位模式下。先设置CAN_FMR的FINIT位,进入初始化模式,然后设置筛选器组0的工作模式以及标识符ID和屏蔽位。最后**筛选器,并退出初始化模式。

//CAN初始化
//tsjw:重新同步跳跃时间单元.范围:CAN_SJW_1tq~ CAN_SJW_4tq
//tbs2:时间段2的时间单元.   范围:CAN_BS2_1tq~CAN_BS2_8tq;
//tbs1:时间段1的时间单元.   范围:CAN_BS1_1tq ~CAN_BS1_16tq
//brp :波特率分频器.范围:1~1024;  tq=(brp)*tpclk1
//波特率=Fpclk1/((tbs1+1+tbs2+1+1)*brp);
//mode:CAN_Mode_Normal,普通模式;CAN_Mode_LoopBack,回环模式;
//Fpclk1的时钟在初始化的时候设置为36M(APB1?),如果设置CAN_Mode_Init(CAN_SJW_1tq,CAN_BS2_8tq,CAN_BS1_9tq,4,CAN_Mode_LoopBack);
//则波特率为:36M/((8+9+1)*4)=500Kbps
//返回值:0,初始化OK;
//    其他,初始化失败;
u8 CAN_Mode_Init(u8 tsjw,u8 tbs2,u8 tbs1,u16 brp,u8 mode)
{
  GPIO_InitTypeDef GPIO_InitStructure; 
  CAN_InitTypeDef        CAN_InitStructure;
  CAN_FilterInitTypeDef  CAN_FilterInitStructure;
//使能中断选项
#if CAN_RX0_INT_ENABLE 
  NVIC_InitTypeDef  NVIC_InitStructure;
#endif

  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);//使能PORTA时钟	                   											 
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE);//使能CAN1时钟	

  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;	//复用推挽
  GPIO_Init(GPIOA, &GPIO_InitStructure);		//初始化IO
  
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//上拉输入
  GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化IO
  
  //CAN单元设置
  CAN_InitStructure.CAN_TTCM=DISABLE;						 //非时间触发通信模式  //
  CAN_InitStructure.CAN_ABOM=DISABLE;						 //软件自动离线管理	 //
  CAN_InitStructure.CAN_AWUM=DISABLE;						 //睡眠模式通过软件唤醒(清除CAN->MCR的SLEEP位)//
  CAN_InitStructure.CAN_NART=ENABLE;						 	//禁止报文自动传送 //
  CAN_InitStructure.CAN_RFLM=DISABLE;						 //报文不锁定,新的覆盖旧的 // 
  CAN_InitStructure.CAN_TXFP=DISABLE;						 //优先级由报文标识符决定 //
  CAN_InitStructure.CAN_Mode= mode;	         //模式设置: mode:0,普通模式;1,回环模式; //
  //设置波特率
  CAN_InitStructure.CAN_SJW=tsjw;				//重新同步跳跃宽度(Tsjw)为tsjw+1个时间单位  CAN_SJW_1tq	 CAN_SJW_2tq 		 				CAN_SJW_3tq CAN_SJW_4tq
  CAN_InitStructure.CAN_BS1=tbs1; //Tbs1=tbs1+1个时间单位CAN_BS1_1tq ~CAN_BS1_16tq
  CAN_InitStructure.CAN_BS2=tbs2;//Tbs2=tbs2+1个时间单位CAN_BS2_1tq ~	CAN_BS2_8tq
  CAN_InitStructure.CAN_Prescaler=brp;            //分频系数(Fdiv)为brp+1	//
  CAN_Init(CAN1, &CAN_InitStructure);            // 初始化CAN1 

  CAN_FilterInitStructure.CAN_FilterNumber=0;	  //过滤器0
  CAN_FilterInitStructure.CAN_FilterMode=CAN_FilterMode_IdMask; 
  CAN_FilterInitStructure.CAN_FilterScale=CAN_FilterScale_32bit; //32位 
  CAN_FilterInitStructure.CAN_FilterIdHigh=0x0000;////32位ID
  CAN_FilterInitStructure.CAN_FilterIdLow=0x0000;
  CAN_FilterInitStructure.CAN_FilterMaskIdHigh=0x0000;//32位MASK
  CAN_FilterInitStructure.CAN_FilterMaskIdLow=0x0000;
  CAN_FilterInitStructure.CAN_FilterFIFOAssignment=CAN_Filter_FIFO0;//过滤器0关联到FIFO0
  CAN_FilterInitStructure.CAN_FilterActivation=ENABLE; //**过滤器0

  CAN_FilterInit(&CAN_FilterInitStructure);//滤波器初始化
#if CAN_RX0_INT_ENABLE

  CAN_ITConfig(CAN1,CAN_IT_FMP0,ENABLE);//FIFO0消息挂号中断允许.		    

  NVIC_InitStructure.NVIC_IRQChannel = USB_LP_CAN1_RX0_IRQn;
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;     // 主优先级为1
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;            // 次优先级为0
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  NVIC_Init(&NVIC_InitStructure);
#endif
	return 0;
}

发送函数

//can发送一组数据(固定格式:ID为0X12,标准帧,数据帧)	
//len:数据长度(最大为8)				     
//msg:数据指针,最大为8个字节.
//返回值:0,成功;
//		 其他,失败;
u8 Can_Send_Msg(u8* msg,u8 len)
{	
  u8 mbox;
  u16 i=0;
  CanTxMsg TxMessage;
  TxMessage.StdId=0x12;					 // 标准标识符 
  TxMessage.ExtId=0x12;				   // 设置扩展标示符 
  TxMessage.IDE=CAN_Id_Standard; // 标准帧
  TxMessage.RTR=CAN_RTR_Data;		 // 数据帧
  TxMessage.DLC=len;						// 要发送的数据长度
  for(i=0;i<len;i++)
  TxMessage.Data[i]=msg[i];			          
  mbox= CAN_Transmit(CAN1, &TxMessage);   
  i=0;
  while((CAN_TransmitStatus(CAN1, mbox)==CAN_TxStatus_Failed)&&(i<0XFFF))i++;	//等待发送结束
  if(i>=0XFFF)return 1;
  return 0;		
}

接收函数

//can口接收数据查询
//buf:数据缓存区;	 
//返回值:0,无数据被收到;
//		 其他,接收的数据长度;
u8 Can_Receive_Msg(u8 *buf)
{		   		   
 	u32 i;
	CanRxMsg RxMessage;
  if( CAN_MessagePending(CAN1,CAN_FIFO0)==0)return 0;		//没有接收到数据,直接退出 
  CAN_Receive(CAN1, CAN_FIFO0, &RxMessage);//读取数据	
  for(i=0;i<8;i++)
  buf[i]=RxMessage.Data[i];  
  
	return RxMessage.DLC;	
}

主函数

#include "led.h"
#include "delay.h"
#include "key.h"
#include "sys.h"
#include "lcd.h"
#include "usart.h"	 
#include "can.h" 
 
int main(void)
{	 
	u8 key;
	u8 i=0,t=0;
	u8 cnt=0;
	u8 canbuf[8];
	u8 res;
	u8 mode=CAN_Mode_LoopBack;//CAN工作模式;CAN_Mode_Normal(0):普通模式,CAN_Mode_LoopBack(1):环回模式

	delay_init();	    	 //延时函数初始化	  
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置中断优先级分组为组2:2位抢占优先级,2位响应优先级
	uart_init(115200);	 	//串口初始化为115200
	LED_Init();		  		//初始化与LED连接的硬件接口
	LCD_Init();			   	//初始化LCD	
	KEY_Init();				//按键初始化		 	
   
	CAN_Mode_Init(CAN_SJW_1tq,CAN_BS2_8tq,CAN_BS1_9tq,4,CAN_Mode_LoopBack);//CAN初始化环回模式,波特率500Kbps    

 	POINT_COLOR=RED;//设置字体为红色 
	LCD_ShowString(60,50,200,16,16,"WarShip STM32");	
	LCD_ShowString(60,70,200,16,16,"CAN TEST");	
	LCD_ShowString(60,90,200,16,16,"aaa@qq.com");
	LCD_ShowString(60,110,200,16,16,"2015/1/11");
	LCD_ShowString(60,130,200,16,16,"LoopBack Mode");	 
	LCD_ShowString(60,150,200,16,16,"KEY0:Send WK_UP:Mode");//显示提示信息		
  POINT_COLOR=BLUE;//设置字体为蓝色	  
	LCD_ShowString(60,170,200,16,16,"Count:");			//显示当前计数值	
	LCD_ShowString(60,190,200,16,16,"Send Data:");		//提示发送的数据	
	LCD_ShowString(60,250,200,16,16,"Receive Data:");	//提示接收到的数据		

 	while(1)
	{
		key=KEY_Scan(0);
		if(key==KEY0_PRES)//KEY0按下,发送一次数据
		{
			for(i=0;i<8;i++)
			{
				canbuf[i]=cnt+i;//填充发送缓冲区
				if(i<4)LCD_ShowxNum(60+i*32,210,canbuf[i],3,16,0X80);	//显示数据
				else LCD_ShowxNum(60+(i-4)*32,230,canbuf[i],3,16,0X80);	//显示数据
 			}
			res=Can_Send_Msg(canbuf,8);//发送8个字节 
			if(res)LCD_ShowString(60+80,190,200,16,16,"Failed");		//提示发送失败
			else LCD_ShowString(60+80,190,200,16,16,"OK    ");	 		//提示发送成功								   
		}else if(key==WKUP_PRES)//WK_UP按下,改变CAN的工作模式
		{	   
			mode=!mode;
  			CAN_Mode_Init(CAN_SJW_1tq,CAN_BS2_8tq,CAN_BS1_9tq,4,mode);//CAN普通模式初始化, 波特率500Kbps 
			POINT_COLOR=RED;//设置字体为红色 
			if(mode==0)//普通模式,需要2个开发板
			{
				LCD_ShowString(60,130,200,16,16,"Nnormal Mode ");	    
			}else //回环模式,一个开发板就可以测试了.
			{
 				LCD_ShowString(60,130,200,16,16,"LoopBack Mode");
			}
 			POINT_COLOR=BLUE;//设置字体为蓝色 
		}		 
		key=Can_Receive_Msg(canbuf);
		if(key)//接收到有数据
		{			
			LCD_Fill(60,270,130,310,WHITE);//清除之前的显示
 			for(i=0;i<key;i++)
			{									    
				if(i<4)LCD_ShowxNum(60+i*32,270,canbuf[i],3,16,0X80);	//显示数据
				else LCD_ShowxNum(60+(i-4)*32,290,canbuf[i],3,16,0X80);	//显示数据
 			}
		}
		t++; 
		delay_ms(10);
		if(t==20)
		{
			LED0=!LED0;//提示系统正在运行	
			t=0;
			cnt++;
			LCD_ShowxNum(60+48,170,cnt,3,16,0X80);	//显示数据
		}		   
	}
}

数据帧

数据帧由7个段组成:

①,帧起始。
表示数据帧开始的段。
②,仲裁段。
表示该帧优先级的段。
③,控制段。
表示数据的字节数及保留位的段。
④,数据段。
数据的内容,一帧可发送0~8个字节的数据。
⑤,CRC段。
检查帧的传输错误的段。
⑥,ACK段。
表示确认正常接收的段。
⑦,帧结束。
表示数据帧结束的段。
CAN总线笔记

模式

工作模式

①初始化模式(INRQ=1,SLEEP=0),即初始化过程
②正常模式(INRQ=0,SLEEP=0),即工作过程
③睡眠模式(SLEEP=1),即低功耗待机形式

测试模式

①静默模式( LBKM=0,SILM=1 ),只收不发
②环回模式( LBKM=1,SILM=0 ),只发不收及自发自收
③环回静默模式(LBKM=1,SILM=1),不发不收

调试模式

标识符筛选器

筛选器可配置为:屏蔽位模式和标识符列表模式。
标识符=ID=优先级,ID越小,优先级越高.
can的标识符是自己设定的,接收方自己筛选需要接受哪些标识符

屏蔽位模式

掩码,一种屏蔽和筛选技术
ID为期待标识符,掩码为必须匹配的位。即接收一组标识符。

标识符列表模式

ID为期待标识符,掩码和ID一样,所有位必须匹配,即接收的是一个标识符。

CAN总线笔记

栗子

例:设置筛选器组0工作在:1个32位筛选器-标识符屏蔽模式,然后设置CAN_F0R1=0XFFFF0000,CAN_F0R2=0XFF00FF00。其中存放到CAN_F0R1的值就是期望收到的ID,即(STID+EXTID+IDE+RTR)最好是:0XFFFF0000。而0XFF00FF00就是设置我们需要必须关心的ID,表示收到的映像,其位[31:24]和位[15:8]这16个位的必须和CAN_F0R1中对应的位一模一样,而另外的16个位则不关心,可以一样,也可以不一样,都认为是正确的ID,即收到的映像必须是0XFFxx00xx,才算是正确的(x表示不关心)。

收发流程

发送流程

程序选择1个空置的邮箱(TME=1)->设置标识符(ID),数据长度和发送数据->设置CAN_TIxR的TXRQ位为1,请求发送->邮箱挂号(等待成为最高优先级)->预定发送(等待总线空闲)->发送->邮箱空置。

CAN总线笔记

接收流程

CAN接收流程为:
FIFO空->收到有效报文->挂号_1(存入FIFO的一个邮箱,这个由硬件控制,我们不需要理会)->收到有效报文->挂号_2->收到有效报文->挂号_3->收到有效报文->溢出。

CAN总线笔记

位速率

位速率由发送单元在非同步的情况下发送的每秒钟的位数称为位速率。一个位一般可以分为如下四段:
-同步段(SS)
-传播时间段(PTS)
-相位缓冲段1(PBS1)
-相位缓冲段2(PBS2)

这些段又由可称为 Time Quantum(以下称为Tq)的最小时间单位构成。
1 位分为4 个段,每个段又由若干个Tq 构成,这称为位时序。
位时间=1/波特率,因此,知道位时间,我们就可以知道波特率。
1 位由多少个Tq 构成、每个段又由多少个Tq 构成等,可以任意设定位时序。通过设定位时序,多个单元可同时采样,也可任意设定采样点。

CAN总线笔记

CAN时序

STM32F103,设TS1=8、TS2=7、BRP=3,波特率=36000/[(9+8+1)*4]=500Kbps。
STM32F407,设TS1=6、TS2=5、BRP=5,波特率=42000/[(7+6+1)*6]=500Kbps。

CAN总线笔记

参考链接:
https://www.openedv.com/