第一篇blog,纯手打,无复制。
目标实验平台:stm32f4-discovery,板载STM32F407VGT6。
写的时候忘记掉可以用“报文”这个词,于是就很愚昧的都使用了一帧信息这样的表达。。意思是一样的!
这里首先说一下CAN总线。
CAN总线时一种工业总线,展开来说就是控制器局域网,Controller Area Network
常常用在汽车和工业控制的通讯中。也就是说,在汽车上,各种控制器常常是通过CAN总线进行通讯的。
具体到他是谁发明的有什么历史就不详述了,百度百科一大堆我自己看了也记不清~
只是这两天在调两个STM32通过CAN总线进行最简单的收发通信,这里就简要介绍一下其通信的建立过程
也就是简单的收发功能,至于其中更复杂的错误检测等功能我也只看过介绍没有具体实验过,也就不说了。
如果能有机会用到,也会来再记录的。
CAN总线和I2c等总线有所不同,各个节点的通信并不是通过确定发送目标机地址来确定收发方向。
他是采取一种广播的方式,也就是说一个节点向总线发送信号,所有节点都能收到。
但是如果这个信号对自己无用怎么办呢?事实上,在CAN总线上传输的信号并不仅仅只有所需要的数据、控制信号等,
更重要的是一帧信号还有一串标识符ID,每个节点可以设置自己想要接收什么样标识符的信息
而将不需要的信息通过节点中的过滤器处理掉,就不用担心无用数据的干扰了。这样比需要地址总线通讯方式的好处就是如果同样信息不需要一个一个地址重复发送,只要一次发送就行了,提高效率。
在一个CAN的节点上,除了CPU以外,还需要一个CAN控制器和一个CAN收发器,CAN控制器可以根据当前总线上的数据传输情况以及现在控制器中发送邮箱及接受缓冲器等的情况来实时调整发送和接收。而CAN总线上的电平并不是TTL电平故需要一个CAN收发器进行电平转换的功能。
使用STM32进行CAN通讯的好处之一就是他自带有CAN控制器,而在外面只需要一个CAN收发器即可。至于其他没有CAN控制器的MCU,就需要配备如SJA1000等额外的CAN控制器了。
我这里使用的CAN收发器选用TJA1050,外部接线非常简单,如下图
其中TXD、RXD引脚连接STM32的CANTX\CANRX。电源滤波电容可选取0.1uF。Vref可不接,S(RS)端可接GND或不接。至于CANH\CANL之间的120欧姆电阻,在节点为CAN总线终端时接上,否则不接。电路非常简单,可以自己制作,我自己淘宝花了10块钱买了两个TJA1050芯片,其中有6元是运费。如果直接买TJA1050的模块的话,基本上网上都是10元一个,还要运费,加起来都快30了,真是不值呀~
进行双机通信时,两个CAN收发器的CANH、CANL连接即可。
如果没有条件进行双机通信,可以在STM32进行CAN控制器初始化时设置成回环模式,也就是STM32的CAN-TX在芯片内部与CAN-RX进行直接连接,而外部的CAN-RX引脚不影响内部。同时可以在外部TX引脚上通过示波器测得输出信号波形。此种方法可以在一定程度上用于测试程序正确与否(因为他隔绝了外部电路的影响,比如因为电路不正确等,但也同时无法使用其总线错误检测等功能)。建议先通过CAN的回环测试,再进行双机通信。
下面再介绍一下stm32的CAN控制器。分为CAN1(主)和CAN2(从)。每个CAN控制器有3个发送邮箱用于存放需要发送的信息。发送顺序可以由软件设置为根据信息优先级还是根据信息的请求时间。它还有2个用于接收的缓冲器队列FIFO0和FIFO1,每个FIFO可以存放3帧信息,如果存满,新来的信息可能会丢失或者被覆盖(依据软件设置)。
他可以被CAN控制器的过滤器(共28组,也就是可以设置28个标识符ID)关联,通过该过滤器的信息就会被存入相应的FIFO中。
再说一下stm32的CAN过滤器。
他共有28组过滤器,每组过滤器有2个32位的存储器。每组过滤器可以设置为:
1、两个32位完全匹配的过滤器 (用于扩展帧)2、4个16位完全匹配的过滤器 (用于标准帧)3、一个32位有位屏蔽功能的过滤器
这里再提一下,CAN总线的数据帧有两种,一种是11位标识符的,叫标准帧,另一种除了11位以外还有18位共计29位标识符的叫扩展帧。
所谓完全匹配,即总线上一帧信息的标识符必须与28个过滤器中某一个标识符每一位都相同,才会被过滤器接收到FIFO中。所谓位屏蔽,就是只需要与设定的标识符中某几位相同即可被接收进FIFO中。
絮絮叨叨说了这么多,想必对STM32的CAN控制器也有所了解了。下面根据所写的代码进行操作步骤讲解吧:D
我用的是CAN1,使用FIFO0存放接收到的数据
</pre>首先是主函数中,首先先通过CAN_Config和NVIC_Config对CAN控制器及其中断进行初始化配置。其他的CAN_打头的函数来源于STM32的函数库,具体功能见注释。</div><div><pre name="code" class="cpp">void main()
{
uint8_t TransmitMailbox = 0;
USART1_Config();//初始化串口,与电脑进行通信,方便调试 下面的printf函数被重载到发送信息到串口
NVIC_Config();
CAN_Config();
RxReset();//将接收缓冲区清空
CAN_FIFORelease(CAN1,CAN_FIFO0 );//清空接收FIFO
PackTxMessage();//打包要发送的数据到全局结构体变量TxMessage中
TransmitMailbox = CAN_Transmit(CAN1, &TxMessage);//发送数据,如果发送成功返回存入的邮箱号
while((CAN_TransmitStatus(CAN1, TransmitMailbox) != CANTXOK))//判断是否成功发送
{
printf("\n\r\n\rStill transmitting\n\r");
}
printf("\n\rTransmit OK!\n\r");
while(1);
}
下面是CAN_Config函数
配置过程可以参照STM32F4的函数库文件stm32f4xx_can.c中前面注释中的介绍“How to use this 。。。”就可以知道要对CAN控制器进行初始化需要用到哪些函数。这也是一个很重要的学习使用STM32外设的方法,就是阅读每一个库文件的源代码及其介绍。
一个经验就是,现在网上STM32的代码和相关资料大部分是针对STM32F10x芯片的,这里使用的F407的寄存器以及库函数与他们有一些不同,如果生搬硬套,首先就可能无法通过编译,提示某些变量或者函数没有定义。即使通过了编译,也很有可能因为某些在F10x中没有的函数没有使用而导致外设无法正常运行。
void CAN_Config()
{
CAN_GPIO_Init();
CAN_ModeAndFilterInit();
}
void CAN_GPIO_Init()
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE);//打开CAN外设时钟
//RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1 | RCC_APB1Periph_CAN2, ENABLE);//
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
GPIO_PinAFConfig(GPIOB, GPIO_PinSource8, GPIO_AF_CAN1);//配置功能复用
GPIO_PinAFConfig(GPIOB, GPIO_PinSource9, GPIO_AF_CAN1);
GPIO_InitTypeDef CANIO;
CANIO.GPIO_Pin = GPIO_Pin_8;
CANIO.GPIO_Mode = GPIO_Mode_AF;
CANIO.GPIO_PuPd = GPIO_PuPd_UP;
CANIO.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&CANIO);
//Config CANTX
CANIO.GPIO_Pin = GPIO_Pin_9;
CANIO.GPIO_Mode = GPIO_Mode_AF;
CANIO.GPIO_OType = GPIO_OType_PP;
GPIO_Init(GPIOB,&CANIO);
}
void CAN_ModeAndFilterInit()
{
CAN_InitTypeDef CAN_InitStructure;
CAN_FilterInitTypeDef CAN_FilterInitStructure;
CAN_DeInit(CAN1);//首先使用默认配置,减少配置数量
/* CAN cell init */
CAN_InitStructure.CAN_TTCM = DISABLE;//关闭时间触发通信使能(我也不知道这个干嘛用的。。)
CAN_InitStructure.CAN_ABOM = ENABLE;//自动离线管理,也就是如果检测到总线上出错,本节点自动离线
CAN_InitStructure.CAN_AWUM = ENABLE;//设置当需要时自动唤醒,如果DISABLE,则需要软件唤醒
CAN_InitStructure.CAN_NART = DISABLE;//如果报文发送不成功,就自动重发
CAN_InitStructure.CAN_RFLM = DISABLE;//在接收的FIFO中如果溢出,自动覆盖原有报文
CAN_InitStructure.CAN_TXFP = DISABLE;//发送邮箱优先级取决于报文标识符
CAN_InitStructure.CAN_Mode = CAN_Mode_Normal;//正常工作模式,如果是回环模式,这里要更改为CAN_Mode_Loopback
<pre name="code" class="cpp" style="font-size: 18px;">/* CAN Baudrate = 700kbps (CAN clocked at 42 MHz) */
/*以下5行设置CAN传送速率为700kbps,CAN的最高传输速率为1Mbps*/
<pre name="code" class="cpp" style="font-size: 18px;"> CAN_InitStructure.CAN_SJW = CAN_SJW_1tq;
CAN_InitStructure.CAN_BS1 = CAN_BS1_6tq; CAN_InitStructure.CAN_BS2 = CAN_BS2_8tq; CAN_InitStructure.CAN_Prescaler = 4; CAN_Init(CAN1, &CAN_InitStructure); /* CAN filter init *///设置CAN过滤器 CAN_FilterInitStructure.CAN_FilterNumber = 1;//使用1号过滤器,(共28组,编号0~27) CAN_FilterInitStructure.CAN_FilterMode = CAN_FilterMode_IdMask;//标识符屏蔽位模式 CAN_FilterInitStructure.CAN_FilterScale = CAN_FilterScale_32bit;//32位标识符
/*以下4句分析见后文,用于设置过滤器的标识符和屏蔽位*/
CAN_FilterInitStructure.CAN_FilterIdHigh = (((u32)0x1314<<3)&0xffff0000)>>16;//0x0000;
CAN_FilterInitStructure.CAN_FilterIdLow = (((u32)0x1314<<3)|CAN_ID_EXT|CAN_RTR_DATA)&0Xffff;//0x0000;
CAN_FilterInitStructure.CAN_FilterMaskIdHigh = 0xffff;
CAN_FilterInitStructure.CAN_FilterMaskIdLow = 0xffff;
CAN_FilterInitStructure.CAN_FilterFIFOAssignment = CAN_FIFO0;//过滤器与FIFO0相关联
CAN_FilterInitStructure.CAN_FilterActivation = ENABLE;//开启过滤器!
CAN_FilterInit(&CAN_FilterInitStructure);
CAN_ITConfig(CAN1, CAN_IT_FMP0, ENABLE);//允许CAN的FMP0中断
}
由前文可知,CAN过滤器有2个32位存储器。她的结构如下(本程序使用一个32位有位屏蔽功能的过滤器):
第一个32位ID用于存放完整的标识符ID,第二个用于存放掩码。也就是说,在第二个32位存储器某一位若为1,则收到数据的相应位必须与之相同才会被接收,其他若为0的位不care 其中扩展ID存放在3~17位,标准ID存放在剩下的高位中。低位还有IDE\RTR位标明了这一帧报文的属性。IDE位标明是标准帧还是扩展帧,RTR位标明这一帧报文是数据帧还是远程遥控帧(用于请求数据)。
再看这两句:
CAN_FilterInitStructure.CAN_FilterIdHigh = (((u32)0x1314<<3)&0xffff0000)>>16;//0x0000;
CAN_FilterInitStructure.CAN_FilterIdLow = (((u32)0x1314<<3)|CAN_ID_EXT|CAN_RTR_DATA)&0Xffff;//0x0000;
我们的标准ID为0x00,扩展ID位0x1314,第一句FilterIdHigh即设置32位存储器的高16位,0x1314<<3后,&0xffff0000即可保留
第17、18、19位,并将高位设置为0,即标准ID=0x00;最后右移16位,因为FilterIdHigh是一个16位的uint16_t类型变量。如果不保存,高位就丢失了。
懂了第一句,第二句就容易理解了,将除了最高3位以外的保留下来,再配置上IDE和RTR位,设置为扩展ID,数据帧。
CAN_FilterInitStructure.CAN_FilterMaskIdHigh = 0xffff;
CAN_FilterInitStructure.CAN_FilterMaskIdLow = 0xffff;
两句设置了每一位都需要进行匹配才会被接收。如果设置成0x0000,那么任意报文都会被接收并产生接收中断。
CAN_ITConfig(CAN1, CAN_IT_FMP0, ENABLE);
使能了CAN1的FMP0中断,也就是消息接收中断。除此之外还有发送中断等,详见stm32f4的库帮助手册
再回到主函数,
还未分析的函数展开如下,关于中断控制器NVIC的配置就不多叙述了。大家都懂的XD
CanTxMsg TxMessage;
CanRxMsg RxMessage;//前面两个结构体类型用于定义发送和接收的缓冲,因为CAN_Transmit()函数的参数是此种///变量
void RxReset()
{
RxMessage.StdId = 0x00;
RxMessage.IDE = CAN_ID_STD;
RxMessage.DLC = 0;
int i;
for(i=0;i<7;i++)
{
RxMessage.Data[i] = 0x00;
}
RxMessage.ExtId = 0x00;
}
void PackTxMessage()//打包报文函数
{
TxMessage.StdId = 0x00;//标准ID0x00
TxMessage.RTR = CAN_RTR_DATA;//发送的是数据
TxMessage.IDE = CAN_ID_EXT;//扩展帧
TxMessage.ExtId = 0x1314;//扩展ID
TxMessage.DLC = 2;//数据数量(最大为8)
TxMessage.Data[0] = 0x52;//数据
TxMessage.Data[1] = 0x6E;
}
void NVIC_Config()
{
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = CAN1_RX0_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
void CAN1_RX0_IRQHandler()
{
printf("\n\rTHIS IS INTERRUPT!\n\r");
CAN_Receive(CAN1, CAN_FIFO0, &RxMessage);
printf("RxMessage:StdId=0x%x,DLC=0x%x, ExtId = 0x%x",RxMessage.StdId,RxMessage.DLC,RxMessage.ExtId);
printf("RxMessage:data[0]=0x%x,data[1]=0x%x,",RxMessage.Data[0],RxMessage.Data[1]);
}
CAN_Receive()和CAN_Transmit()都是提供的发送接收库函数,
一旦接收缓冲区FIFO有数据,就产生一个中断,打印接收到的信息。
这段代码可以放在接收的从机里。从机中只要配置完CAN_Config和NVIC_Config即可进入中断等待。
这里的中断函数名
CAN1_RX0_IRQHandler可在startup_stm32f4xx.s汇编程序中找到入口
其中还有一个CAN1_RX1_IRQHandler
两个的不同就是,当FIFO0产生中断时,进入前者,当FIFO1产生中断时进入后者。
由于是在实验室做的测试,这里就不贴运行结果了,亲测可用XD。
以上就是stm32f4的CAN最简单的收发应用,更多细节请自行开发~
打这么多字好累。。。