基于STM32F103单片机的CAN通信代码实现
本文章对CAN的理论只做简单介绍,重点介绍怎么使用stm32f103单片机实现CAN通信。
文章实验使用stm32f103c8t6单片机,TJA1042T/3 CAN收发器,广成CAN分析仪,GCANTOOLS上位机软件(CAN分析仪官网下载)实现CAN报文的发送和接受。
CAN通信物理层简单介绍
更为详细的CAN基础资料可以参考瑞萨电子的《CAN入门书》。
CAN通信就是实现多设备之间的数据交换通信方式,没有主从结构,也没有固定的设备地址,通过标识符来确定信息是不是给自己的。使用
特点如下:
1、没有主从之分,所有单元都可以发送接收报文
2、每个设备没有固定的地址,通信双方(或多方)使用标识符来判断总线上的数据是不是给自己的
3、可以连接很多单元,理论上是无限个,但受总线的物理时间延迟等影响不能连接太多。
4、使用双绞线通信,抗干扰能力强,两根线一起干扰就约等于没有干扰,因为一个时刻的电平标准取决于两根线之间的压差,同时增加或减少电压时两线压差变化不大。
5、等等其他特点,这里不做赘述。
CAN协议层简单介绍
数据以帧来分类,包括数据帧,遥控帧等,遥控帧就是告诉别人我要接收标识符ID为XXX设备的数据,数据帧就是一个设备告诉别的设备我的这串数据是这些,标识符ID是XXX,大家看看我的ID,想要的就接收。错误帧、过载帧、帧间隔这里就不再赘述。
一个帧包含很多段,通过这些段来分辨这个是什么帧,什么标识符ID,什么数据,数据有多长,标识符是标准的(11位)还是扩展的(共29位)等等信息。拿数据帧的标准格式和扩展格式举例子:
标准格式中,起始就是这个帧从这里开始,仲裁段就是标识符ID段,同一时刻多个设备发送数据的话就根据这个标识符ID选出哪个先发送,从左到右看谁的0最多谁先发(就是所有ID一起与门运算),RTR位就是这个帧是数据帧还是远程帧,IDE位就是这个帧的ID是扩展ID还是标准ID,r0只作填充用,DLC段表示后面的Data段有多少个字节(最多8个字节),CRC校验段就是校验段,界定符就是界定CRC校验数据结束,ACK就是应答之前接受的数据没问题,结束段就是表示这个帧到这里结束。
扩展格式中,标识符后面的是SRR位,作填充用,无实际意义,就是为了和标准格式的位置对齐,IDE位为显形电平,表示这个帧的标识符是扩展标识符,扩展标识符右边的RTR右边位R1,也是作填充作用,为了对齐用。
stm32f103c8t6 CAN控制器介绍
以下详细信息参考stm32中文参考手册V10。
不同的单片机具备的CAN控制器功能不同,stm32中文参考手册V10的介绍比较全面,注意区分。
bxCAN特点:
1、3个发送邮箱
2、3级深度的2个接收FIFO
3、可变的过滤器组
4、初始化、正常和睡眠模式
5、调试有静默模式、环回模式、环回静默模式
6、标识符过滤
硬件连接
程序代码实现
CAN.h文件就是几个函数声明,不作展示。
CAN.c文件
#include "stm32f10x.h"
#include "CAN.h"
#define CAN_Rx_GPIO GPIOA
#define CAN_Rx_Pin GPIO_Pin_11
#define CAN_Tx_GPIO GPIOA
#define CAN_Tx_Pin GPIO_Pin_12
//CAN模式初始化函数
void CAN_Mode_Init(u8 tsjw,u8 tbs2,u8 tbs1,u16 brp,u8 mode)
{
GPIO_InitTypeDef GPIO_InitStructure;
CAN_InitTypeDef CAN_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
CAN_FilterInitTypeDef CAN_FilterInitStructure;
//GPIO复用功能时钟开启,CAN1外设时钟开启
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO | RCC_APB2Periph_GPIOA,ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1,ENABLE );
GPIO_InitStructure.GPIO_Pin = CAN_Tx_Pin;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽
GPIO_Init(CAN_Tx_GPIO, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = CAN_Rx_Pin;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //上拉输入
GPIO_Init(CAN_Rx_GPIO, &GPIO_InitStructure); //初始化 IO
CAN_DeInit(CAN1);
CAN_InitStructure.CAN_ABOM = ENABLE; //禁用自动总线关闭模式
CAN_InitStructure.CAN_AWUM = DISABLE; //禁用自动唤醒模式
CAN_InitStructure.CAN_Mode = mode; //模式选择
CAN_InitStructure.CAN_NART = DISABLE; //非自动重传模式
CAN_InitStructure.CAN_RFLM = DISABLE; //禁用接收FIFO锁定模式
CAN_InitStructure.CAN_SJW = tsjw; //同步跳转宽度
CAN_InitStructure.CAN_BS1 = tbs1; //位时间段1的长度
CAN_InitStructure.CAN_BS2 = tbs2; //位时间段2的长度
CAN_InitStructure.CAN_Prescaler = brp; //分频系数
CAN_InitStructure.CAN_TTCM = DISABLE; //关闭时间触发通信模式
CAN_InitStructure.CAN_TXFP = DISABLE; //关闭发送FIFO优先级模式
CAN_Init(CAN1, &CAN_InitStructure);
CAN_FilterInitStructure.CAN_FilterNumber = 0; //配置CAN过滤器组中的第一个过滤器
CAN_FilterInitStructure.CAN_FilterMode = CAN_FilterMode_IdMask; //使用标识符屏蔽位来过滤CAN消息
CAN_FilterInitStructure.CAN_FilterScale = CAN_FilterScale_32bit; //使用32位的模式和掩码来过滤CAN消息
CAN_FilterInitStructure.CAN_FilterIdHigh = 0x0000; //下面四个全是0表示接收所有标识符消息
CAN_FilterInitStructure.CAN_FilterIdLow = 0x0000;
CAN_FilterInitStructure.CAN_FilterMaskIdHigh = 0x0000;
CAN_FilterInitStructure.CAN_FilterMaskIdLow = 0x0000;
CAN_FilterInitStructure.CAN_FilterFIFOAssignment = CAN_Filter_FIFO0; //过滤器匹配结果应该被存储在FIFO0缓冲区中
CAN_FilterInitStructure.CAN_FilterActivation = ENABLE; //启用CAN过滤器
CAN_FilterInit(&CAN_FilterInitStructure);
CAN_ITConfig(CAN1,CAN_IT_FMP0,ENABLE);//开启中断
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitStructure.NVIC_IRQChannel=USB_LP_CAN1_RX0_IRQn;//中断通道选择
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
//复制接受CAN结构体数据到发送结构体数据中,并发送数据
void SendMessage(void)
{
u8 i ;
CanTxMsg bufCAN;
bufCAN.ExtId = RxMessage.ExtId;
bufCAN.StdId = RxMessage.StdId;
bufCAN.IDE = RxMessage.IDE;
bufCAN.RTR = RxMessage.RTR;
bufCAN.DLC = RxMessage.DLC;
for(i = 0;i < 8;i++)
{
bufCAN.Data[i] = RxMessage.Data[i];
}
CAN_Transmit(CAN1,&bufCAN);
}
//CAN接受中断处理函数
void USB_LP_CAN1_RX0_IRQHandler(void)
{
CAN_Receive(CAN1, CAN_FIFO0, &RxMessage); //接收数据到RxMessage中
SendMessage(); 发送接收到的数据
CAN_FIFORelease(CAN1,CAN_FIFO0); //释放接收邮箱FIFO0
CAN_ClearITPendingBit(CAN1,CAN_IT_FF0); //清除FIFO0接收中断标志位
}
main.c文件
#include "stm32f10x.h"
#include "CAN.h"
//演示代码,实现上电发送报文
int main(void)
{
CAN_Mode_Init(CAN_SJW_1tq,CAN_BS2_2tq,CAN_BS1_3tq,24,CAN_Mode_Normal);
/*
波特率=(72M/2)/((3+2+1)*12)=500K
*60=100K
*24 = 250K
*/
while(1)
{
}
}
实现效果
标准帧的发送和接受
扩展帧的发送和接受
通过上述代码,实现基本的CAN数据传输,本文完。