提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
CAN总线的学习
前言
几乎所有的嵌入式岗位的面试要求中都会含有熟悉CAN
的这个要求,特此重新学习一遍,采用的是正点原子的STM32F4和STM32F1进行双机通信测试,以及各自的回环测试。
一、涉及到CAN的基础知识介绍
1.CAN中出现的名词解释
CAN
是 Controller Area Network(控制器局域网),
2.四种工作模式
正常模式:
静默模式:在STM32的手册中可以找到具体的描述,官方解释是此时CAN可以正常的接收遥控帧和数据帧,但是不能发送数据。也就是说的可以发送隐形电平逻辑1,但不能发送显性电平逻辑0,不会干扰到总线(发送的隐形位不会影响到总线),一般应用都是用来检测和分析CAN总线的活动。
环回模式:看下面的图也可以看出来,这时候的CAN是自发自收,为了测试CAN通信是否正确。
静默环回模式:
二、CAN在STM32F4的应用
CAN的物理层是,两根差分线,对于STM32来说,有一个控制器和收发器。控制器是STM32本身带有的,而收发器是外部的,可以将自己的信号转化成差分信号。
1.进行GPIO的初始化配置
这方面就比较简单了,STM32F4标准库代码如下(示例):
void Can_Gpio_Config(void)
{
GPIO_InitTypeDef GPIO_InitTypeStruct;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE);//使能CAN1时钟
GPIO_InitTypeStruct.GPIO_Pin = GPIO_Pin_11| GPIO_Pin_12;
GPIO_InitTypeStruct.GPIO_Mode = GPIO_Mode_AF;//复用功能
GPIO_InitTypeStruct.GPIO_OType = GPIO_OType_PP;//推挽输出
GPIO_InitTypeStruct.GPIO_Speed = GPIO_Speed_100MHz;//100MHz
GPIO_InitTypeStruct.GPIO_PuPd = GPIO_PuPd_UP;//上拉 这里可以浮空输入或者上拉
GPIO_Init(GPIOA, &GPIO_InitTypeStruct);//初始化PA11,PA12
//引脚复用映射配置
GPIO_PinAFConfig(GPIOA,GPIO_PinSource11,GPIO_AF_CAN1); //GPIOA11复用为CAN1
GPIO_PinAFConfig(GPIOA,GPIO_PinSource12,GPIO_AF_CAN1); //GPIOA12复用为CAN1
}
在参考手册找到的GPIO说明,发送引脚是推挽复用,接收引脚二者其一就行
2.对CAN的结构体进行配置
关于各个模式的介绍可以在参考手册的439页找到详细的说明,强烈建议大家去读一下对应的手册,豁然开朗(英文和我一样的就去读中文的,效果一样很好)
接下来介绍的就是我们在STM32中比较重要的了,MODE配置,一共四种模式
#define IS_CAN_MODE(MODE) (((MODE) == CAN_Mode_Normal) || \
((MODE) == CAN_Mode_LoopBack)|| \
((MODE) == CAN_Mode_Silent) || \
((MODE) == CAN_Mode_Silent_LoopBack))
当我们只有一个开发板时候,就可以选择环回模式,这时候不经过收发器,而直接通过内部的控制器。
接下来就是波特率的配置 STM32F1,CAN搭载在APB1低速时钟下,最高频率为36M,STM32F4的CAN搭载在APB1下,最高频率为42M。
以代码为例进行波特率的运算(示例):
void Can_Config(void)
{
CAN_InitTypeDef CAN_InitTypeStruct;
CAN_InitTypeStruct.CAN_TTCM=DISABLE;
CAN_InitTypeStruct.CAN_ABOM=DISABLE;//手动置1然后清零进入退出离线模式
CAN_InitTypeStruct.CAN_AWUM=DISABLE;//软件唤醒
CAN_InitTypeStruct.CAN_NART=ENABLE;//只发送一次
CAN_InitTypeStruct.CAN_RFLM=ENABLE;//锁定模式,新的会被丢弃
CAN_InitTypeStruct.CAN_TXFP=DISABLE;//由标识符决定发送的优先级
CAN_InitTypeStruct.CAN_Mode= CAN_Mode_LoopBack;//回环模式
CAN_InitTypeStruct.CAN_SJW=CAN_SJW_1tq;
CAN_InitTypeStruct.CAN_BS1=CAN_BS1_7tq;
CAN_InitTypeStruct.CAN_BS2=CAN_BS2_6tq;
CAN_InitTypeStruct.CAN_Prescaler=6;
CAN_Init(CAN1, &CAN_InitTypeStruct);
}
先算1TQ的运行时间,我使用的是F4,CAN搭载在APB1上,时钟频率为42M
,这里的CAN分频系数CAN_Prescaler
为6,所以1TQ的时间为(1/42M)*6;
标准的CAN的一个数据(0或1),是有四段组成,STM32这里由三段构成,出了BS1和BS2还有个SS(固定为1TQ),所以这里一位数据就有7+6+1=14TQ
(这里的1不是SJW而是SS),所以一个数据的时间为14乘以((1/42M)*6)=2us
,一秒内的数据就是1S除以一个数据的时间即就是500Kbit。
3.筛选器配置
STM32F1有14组筛选器,每个筛选器有四种模式,主要是32位和16位,标识符列表和标识符屏蔽,所谓筛选器,就是只有你跟它配置完全一样时,才能通过。而列表和屏蔽呢,列表是相同的才能通过,屏蔽相当于多了一个条件,例如
ID 11011011
MSK 10000000
MSK是屏蔽的八位,ID也是八位,MAK对应为1表示只有首位为1的才能通过
代码讲解
#define PASS_ID ((uint32_t)0x1314)
void Can_Filter_Config(void)
{
CAN_FilterInitTypeDef CAN_FilterInitTypeStruct;
CAN_FilterInitTypeStruct.CAN_FilterNumber=0; //配置哪一个过滤器
CAN_FilterInitTypeStruct.CAN_FilterFIFOAssignment= CAN_Filter_FIFO0;//指定是哪一个FIFO接收
CAN_FilterInitTypeStruct.CAN_FilterScale=CAN_FilterScale_32bit;//32位
CAN_FilterInitTypeStruct.CAN_FilterMode=CAN_FilterMode_IdMask;//MASK和LIST;
CAN_FilterInitTypeStruct.CAN_FilterMaskIdHigh=((PASS_ID<<3 | CAN_Id_Extended |CAN_RTR_Data)&0xffff0000)>>16;
CAN_FilterInitTypeStruct.CAN_FilterIdLow=(PASS_ID<<3 | CAN_Id_Extended |CAN_RTR_Data)&0xffff;
CAN_FilterInitTypeStruct.CAN_FilterMaskIdHigh=0xffff;//所有都要完全一样
CAN_FilterInitTypeStruct.CAN_FilterMaskIdLow=0xffff;
CAN_FilterInitTypeStruct.CAN_FilterActivation=ENABLE;
CAN_FilterInit(&CAN_FilterInitTypeStruct);
}
其中的这个是配置他的模式,这里我选择的是32位掩码模式(屏蔽)
CAN_FilterInitTypeStruct.CAN_FilterScale=CAN_FilterScale_32bit;//32位
CAN_FilterInitTypeStruct.CAN_FilterMode=CAN_FilterMode_IdMask;//MASK和LIST;
第一行ID表示的就是他设置的ID号
而最终赋值给下面的映像寄存器
((PASS_ID<<3 | CAN_Id_Extended |CAN_RTR_Data)&0xffff0000)>>16
这个就是将你的ID配置给下面的32位映像寄存器,低三位 IDE是拓展ID还是普通ID(拓展的有29个,普通的有11个,如上图所示,第四位到32位),这个里面要好好体会。
最后再设置掩码的格式,1是必须和标识符一样,0是可以不一样,这里为了方便直接设置成0XFFFFFFFF。
4.发送和接收数据
邮箱
:发送报文的地方(STM32里面有三个)
FIFO
: 接收报文的地方 (两个FIFO,每个三个深度,一共六个)
我采用了中断的方式进行接收数据
void CAN1_RX0_IRQHandler(void)
{
CAN_Receive(CAN1, 0, &RxMessage_txt);//标志位不用清空
CAN_flag=1;//采集到数据后到主函数进行处理
}
//主函数中
```c
if(CAN_flag)
{
CAN_flag=0;
LCD_Fill(30,270,160,310,WHITE);//清除之前的显示
for(i=0;i<8;i++)
{
canbuf[i]=RxMessage_txt.Data[i];//将接收到的报文进行复制
if(i<4)LCD_ShowxNum(30+i*32,270,canbuf[i],3,16,0X80); //显示数据
else LCD_ShowxNum(30+(i-4)*32,290,canbuf[i],3,16,0X80); //显示数据
}
}
发送数据
u8 CAN1_Send_Msg(u8* msg,u8 len)
{
u8 mbox;
u16 i=0;
CanTxMsg TxMessage;
TxMessage.StdId=0; // 标准标识符为0
TxMessage.ExtId=PASS_ID; // 设置扩展标示符(29位)
TxMessage.IDE=CAN_Id_Extended; // 使用扩展标识符
TxMessage.RTR=CAN_RTR_Data; // 消息类型为数据帧,一帧8位
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;
}
当配置成扩展格式时候,ID号只取决于扩展的格式
总结
CAN总线很牛,不过STM32里面的操作很方便,主要是搞懂,筛选器和ID,以及波特率的计算
百度网盘链接
链接:https://pan.baidu.com/s/1ruyHxXhybqrDNm4E5k6IiQ
提取码:1234
提取码1234.