STM32F103 CAN总线配置与通信小记
总线概念在此只做非常简要的介绍
CAN 是Controller Area Network 的缩写(CAN BUS),具有布线简单、典型的总线型结构、可最大限度的节约布线与维护成本、稳定可靠、实时、抗干扰能力强、传输距离远等特点,是一种成功的总线。不仅在汽车行业得到推广与应用,在航天、电力、石化、冶金、纺织、造纸等领域也得到广泛应用。在自动化仪表、工业生产现场、数控机床等系统中也越来越多的使用了CAN总线。
CAN总线采用差分信号传输,通常情况下只需要两根信号线(CAN-H和CAN-L)就可以进行正常的通信。在干扰比较强的场合,还需要用到屏蔽地即CAN-G(主要功能是屏蔽干扰信号),CAN协议推荐用户使用屏蔽双绞线作为CAN总线的传输线。在隐性状态下,CAN-H与CAN-L的输入差分电压为0V(最大不超过0.5V),共模输入电压为2.5V。在显性状态下,CAN-H与CAN-L的输入差分电压为2V(最小不小于0.9V)……..
反正就是一堆看着非常让人懵逼的协议啊之类咱们现在暂时就先不去折腾了,遵照基本的电气使用规范来应用即可,这里重点就是说下用stm32f1系列单片机来实现CAN总线通信的基本软件配置。
单片机基本的通信配置好之后,这种通信方式还是非常实用高效的,主要是通信稳定可靠,传输距离远。使用的前提是单片机要带硬件CAN功能,协议还是蛮复杂的,所以需要处理器自带CAN协议功能模块,使用前对相关参数进行配置,如通信速率、数据格式等,再搭配好外围电路,如TJA1050CAN转换器之后,(根据实际的总线网络配好总线的阻抗(也就是总线两端的匹配电阻),避免反射造成通信混乱,硬件部分后面再做补充),就可以接入网络进行通信,CAN通信没有主机从机之分。说的再直白一点,CAN中实际参与通信的只有两条线,CAN_H和CAN_L(当然啦,地线少不了),他们是差分信号传输,不能和串行口混为一谈,不是一个概念,STM32的CAN只是实现了协议控制器功能,接口还不能直接接入总线,需要一个转换器来实现与总线的物理连接,单片机需要通过CAN转换器,才能接入CAN网络实现双向数据交互,CAN转换器非常多,如TJA1050之类,反正,接入CAN需要两样东西,①单片机带CAN功能(牛逼的你也可以自己软实现一个) ②CAN转换器,集齐这两样装备后,就可以吃ji了。
//——————————————————————————————————
以上非常非常简单的对一些概念脑补之后,开始详细的软件配置,我们的最终目标是两个甚至多个设备,通过CAN进行通信,交换数据,从而实现多点联机操作。
基本步骤如下(寄存器操作):
1.配置通信端口与CAN基本寄存器配置
2.配置接收中断
3.数据发送处理
4.中断数据接收处理
本次配置的单片机型号是STM32F103VCT6(LQFP100,256K FLASH,48K RAM)
//—————————————————————————————————-
1.配置通信端口与CAN基本寄存器配置(CAN_TX PA12 (71pin) CAN_RX_PA11(70pin))
返回值:初始化状态
参数列表:
//tsjw:重新同步跳跃时间单元.范围:1~3;
//tbs2:时间段2的时间单元.范围:1~8;
//tbs1:时间段1的时间单元.范围:1~16;
//brp :波特率分频器.范围:1~1024;(实际要加1,也就是1~1024) tq=(brp)*tpclk1
//注意以上参数任何一个都不能设为0,否则会乱.
//波特率=Fpclk1/((tbs1+tbs2+1)*brp);
//Fpclk1的时钟在初始化的时候设置为36M,如果设置CAN_init(1,4,7,6);
//则波特率为:36M/((4+7+1)*6)= 0.5M(500K)
//返回值:0,初始化OK;
unsigned char CAN_init(unsigned char tsjw,unsigned char tbs2,unsigned char tbs1,unsigned short brp)
{
unsigned short i=0;
if(tsjw==0||tbs2==0||tbs1==0||brp==0)
return 1;
--tsjw; //减一之后再设置
--tbs2;
--tbs1;
--brp;
//------------端口配置
RCC->APB2ENR |= (1<<2); //使能PORTA时钟
GPIOA->CRH &= 0XFFF00FFF; //清0 PA11 PA12端口寄存器状态
GPIOA->CRH |= 0X000B8000; //配置端口CAN_TX PA12(71pin)复用推挽输出 CAN_RX_PA11(70pin)上拉输入
GPIOA->ODR |= (3<<11); //初始化端口高电平
//------------寄存器配置
RCC->APB1ENR |= (1<<25); //使能CAN时钟 CAN使用的是APB1的时钟(max:36M)
CAN1->MCR=0x0000; //退出睡眠模式(同时清0所有位)
CAN1->MCR |= (1<<0); //请求CAN进入初始化模式
while((CAN1->(MSR&1<<0))==0)
{
i++;
if(i>100) return 2; //超时,进入初始化模式失败
}
CAN1->MCR |= (0<<7); //禁止时间触发通信模式
CAN1->MCR |= (0<<6); //软件自动离线管理
CAN1->MCR |= (0<<5); //睡眠模式通过软件唤醒(清除CAN->MCR的SLEEP位)
CAN1->MCR |= (1<<4); //禁止报文自动传送
CAN1->MCR |= (0<<3); //报文不锁定,新的覆盖旧的
CAN1->MCR |= (0<<2); //优先级由报文标识符决定
CAN1->BTR = 0x00000000; //清0设置.
CAN1->BTR |= (0<<30); //模式设置 0,普通模式;1,回环模式;
CAN1->BTR |= (tsjw<<24); //重新同步跳跃宽度(Tsjw)为tsjw+1个时间单位
CAN1->BTR |= (tbs2<<20); //Tbs2=tbs2+1个时间单位
CAN1->BTR |= (tbs1<<16); //Tbs1=tbs1+1个时间单位
CAN1->BTR |= (brp<<0); //分频系数(Fdiv)为brp+1
//波特率:Fpclk1/((Tbs1+Tbs2+1)*Fdiv)
CAN1->MCR &=~ (1<<0); //请求CAN退出初始化模式
while((CAN1->MSR & (1<<0))==1)
{
i++;
if(i>0XFFF0)return 3;//超时,退出初始化模式失败
}
//过滤器初始化
CAN1->FMR |= (1<<0); //过滤器组工作在初始化模式
CAN1->FA1R &= ~(1<<0); //过滤器0不激活
CAN1->FS1R |= (1<<0); //过滤器位宽为32位.
CAN1->FM1R |= (0<<0); //过滤器0工作在标识符屏蔽位模式
CAN1->FFA1R |= (0<<0); //过滤器0关联到FIFO0
CAN1->sFilterRegister[0].FR1=0X36000000;//32位ID
CAN1->sFilterRegister[0].FR2=0X11000006;//32位MASK
CAN1->FA1R |= (1<<0); //激活过滤器0
CAN1->FMR &= (0<<0); //过滤器组进入正常模式
//使用中断接收
CAN1->IER |= (1<<1); //FIFO0消息挂号中断允许.
MY_NVIC_Init(1,1,USB_LP_CAN1_RX0_IRQn,1); //中断组设置
return 0; //初始化成功
}
//—-CAN接收中断服务函数 ———————————————————————
void USB_LP_CAN1_RX0_IRQHandler(void)
{
unsigned char rxbuf[8];
unsigned long id;
unsigned char ide,rtr,len;
Can_Rx_Msg(0,&id,&ide,&rtr,&len,rxbuf);
can_rx_data_deal(rxbuf); //进入接收处理
}
//------------------------------------------------------------------------------------
void can_rx_data_deal(unsigned char *buf) //CAN接收处理
{
//接收处理部分,处理的内容存在于rxbuf缓冲区内,根据实际自行添加处理
}
//------------------------------------------------------------------------------------
void can_tx_data_pack(void) //can数据发送处理函数
{
unsigned char buffer[10];
buffer[0]=can_id;
//.....根据实际添加数据
//---------------------------------------------------------------------------------
Can_Send_Msg(buffer,8);//发送
}
//------------------------------------------------------------------------------------
//id:标准ID(11位)/扩展ID(11位+18位)
//ide:0,标准帧;1,扩展帧
//rtr:0,数据帧;1,远程帧
//len:要发送的数据长度(固定为8个字节,在时间触发模式下,有效数据为6个字节)
//*dat:数据指针.
//返回值:0~3,邮箱编号.0XFF,无有效邮箱.
unsigned char Can_Tx_Msg(unsigned long id,unsigned char ide,unsigned char rtr,unsigned char len,unsigned char *dat)
{
unsigned char mbox;
if(CAN1->TSR&(1<<26))mbox=0; //邮箱0为空
else if(CAN1->TSR&(1<<27))mbox=1; //邮箱1为空
else if(CAN1->TSR&(1<<28))mbox=2; //邮箱2为空
else return 0XFF; //无空邮箱,无法发送
CAN1->sTxMailBox[mbox].TIR=0; //清除之前的设置
if(ide==0) //标准帧
{
id&=0x7ff;//取低11位stdid
id<<=21;
}else //扩展帧
{
id&=0X1FFFFFFF;//取低32位extid
id<<=3;
}
CAN1->sTxMailBox[mbox].TIR|=id;
CAN1->sTxMailBox[mbox].TIR|=ide<<2;
CAN1->sTxMailBox[mbox].TIR|=rtr<<1;
len&=0X0F;//得到低四位
CAN1->sTxMailBox[mbox].TDTR&=~(0X0000000F);
CAN1->sTxMailBox[mbox].TDTR|=len; //设置DLC.
//待发送数据存入邮箱.
CAN1->sTxMailBox[mbox].TDHR=(((unsigned long)dat[7]<<24)|
((unsigned long)dat[6]<<16)|
((unsigned long)dat[5]<<8)|
((unsigned long)dat[4]));
CAN1->sTxMailBox[mbox].TDLR=(((unsigned long)dat[3]<<24)|
((unsigned long)dat[2]<<16)|
((unsigned long)dat[1]<<8)|
((unsigned long)dat[0]));
CAN1->sTxMailBox[mbox].TIR|=1<<0; //请求发送邮箱数据
return mbox;
}
//-------------------------------------------------------------------------
//获得发送状态.
//mbox:邮箱编号;
//返回值:发送状态. 0,挂起;0X05,发送失败;0X07,发送成功.
unsigned char Can_Tx_Staus(unsigned char mbox)
{
unsigned char sta=0;
switch (mbox)
{
case 0:
sta |= CAN1->TSR&(1<<0); //RQCP0
sta |= CAN1->TSR&(1<<1); //TXOK0
sta |=((CAN1->TSR&(1<<26))>>24); //TME0
break;
case 1:
sta |= CAN1->TSR&(1<<8)>>8; //RQCP1
sta |= CAN1->TSR&(1<<9)>>8; //TXOK1
sta |=((CAN1->TSR&(1<<27))>>25); //TME1
break;
case 2:
sta |= CAN1->TSR&(1<<16)>>16; //RQCP2
sta |= CAN1->TSR&(1<<17)>>16; //TXOK2
sta |=((CAN1->TSR&(1<<28))>>26); //TME2
break;
default:
sta=0X05;//邮箱号不对,肯定失败.
break;
}
return sta;
}
//-------------------------------------------------------------------------
//得到在FIFO0/FIFO1中接收到的报文个数.
//fifox:0/1.FIFO编号;
//返回值:FIFO0/FIFO1中的报文个数.
unsigned char Can_Msg_Pend(unsigned char fifox)
{
if(fifox==0)return CAN1->RF0R&0x03;
else if(fifox==1)return CAN1->RF1R&0x03;
else return 0;
}
//-------------------------------------------------------------------------
//接收数据
//fifox:邮箱号
//id:标准ID(11位)/扩展ID(11位+18位)
//ide:0,标准帧;1,扩展帧
//rtr:0,数据帧;1,远程帧
//len:接收到的数据长度(固定为8个字节,在时间触发模式下,有效数据为6个字节)
//dat:数据缓存区
void Can_Rx_Msg(unsigned char fifox,unsigned long *id,unsigned char *ide,unsigned char *rtr,unsigned char *len,unsigned char *dat)
{
*ide=CAN1->sFIFOMailBox[fifox].RIR&0x04;//得到标识符选择位的值
if(*ide==0)//标准标识符
{
*id=CAN1->sFIFOMailBox[fifox].RIR>>21;
}else //扩展标识符
{
*id=CAN1->sFIFOMailBox[fifox].RIR>>3;
}
*rtr=CAN1->sFIFOMailBox[fifox].RIR&0x02; //得到远程发送请求值.
*len=CAN1->sFIFOMailBox[fifox].RDTR&0x0F;//得到DLC
//fmi=(CAN->sFIFOMailBox[FIFONumber].RDTR>>8)&0xFF;//得到FMI
//接收数据
dat[0]=CAN1->sFIFOMailBox[fifox].RDLR&0XFF;
dat[1]=(CAN1->sFIFOMailBox[fifox].RDLR>>8)&0XFF;
dat[2]=(CAN1->sFIFOMailBox[fifox].RDLR>>16)&0XFF;
dat[3]=(CAN1->sFIFOMailBox[fifox].RDLR>>24)&0XFF;
dat[4]=CAN1->sFIFOMailBox[fifox].RDHR&0XFF;
dat[5]=(CAN1->sFIFOMailBox[fifox].RDHR>>8)&0XFF;
dat[6]=(CAN1->sFIFOMailBox[fifox].RDHR>>16)&0XFF;
dat[7]=(CAN1->sFIFOMailBox[fifox].RDHR>>24)&0XFF;
if(fifox==0)
CAN1->RF0R|=0X20; //释放FIFO0邮箱
else if(fifox==1)
CAN1->RF1R|=0X20; //释放FIFO1邮箱
}
//-------------------------------------------------------------------------
//can发送一组数据(固定格式:ID为can_tx_id,标准帧,数据帧)
//len:数据长度(最大为8)
//msg:数据指针,最大为8个字节.
//返回值:0,成功;
// 其他,失败;
unsigned char Can_Send_Msg(unsigned char * msg,unsigned char len)
{
unsigned char mbox;
unsigned short i=0;
mbox=Can_Tx_Msg(can_tx_id,0,0,len,msg);
while((Can_Tx_Staus(mbox)!=0X07)&&(i<0XFFF))i++;//等待发送结束
if(i>=0XFFF)return 1; //发送失败
return 0; //发送成功
}
//-------------------------------------------------------------------------
//can口接收数据查询
//buf:数据缓存区;
//返回值:0,无数据被收到;
//其他,接收的数据长度;
unsigned char Can_Receive_Msg(unsigned char *buf)
{
unsigned long id;
unsigned char ide,rtr,len;
if(Can_Msg_Pend(0)==0)return 0; //没有接收到数据,直接退出
Can_Rx_Msg(0,&id,&ide,&rtr,&len,buf); //读取数据
if(id!=0x12||ide!=0||rtr!=0)len=0; //接收错误
return len;
}
//-------------------------------------------------------------------------
以上是基于寄存器的应用,重点关注以下4个函数即可:
1.CAN初始化,涉及邮箱、过滤器等设置(重点需要注意好过滤器)
unsigned char CAN_init(unsigned char tsjw,unsigned char tbs2,unsigned char tbs1,unsigned short brp);
2.CAN接收中断函数
void USB_LP_CAN1_RX0_IRQHandler(void);
3.数据接收处理函数
void can_rx_data_deal(unsigned char *buf) //CAN接收处理
4.发送函数
u8 Can_Send_Msg(u8* msg,u8 len);
以上函数组织完后,按实际需求添加处理即可完成总线通信功能,已测试过使用。
对编辑器不是很熟悉,编辑清晰度可能不是很好,望见谅。