//id:标准ID(11位)/扩展ID(11位+18位) //ide:0,标准帧;1,扩展帧
//rtr:0,数据帧;1,远程帧
//len:要发送的数据长度(固定为8个字节,在时间触发模式下,有效数据为6个字节)
//*dat:数据指针.
//返回值:0~3,邮箱编号.0XFF,无有效邮箱.
u8 Can_Tx_Msg(u32 id,u8 ide,u8 rtr,u8 len,u8 *dat)
{
u8 mbox;
if(CAN->TSR&(1<<26))mbox=0; //邮箱0为空
else if(CAN->TSR&(1<<27))mbox=1; //邮箱1为空
else if(CAN->TSR&(1<<28))mbox=2; //邮箱2为空
else return 0XFF; //无空邮箱,无法发送
CAN->sTxMailBox[mbox].TIR=0; //清除之前的设置
if(ide==0) //标准帧
{
id&=0x7ff;//取低11位stdid
id<<=21;
}else //扩展帧
{
id&=0X1FFFFFFF;//取低32位extid
id<<=3;
}
CAN->sTxMailBox[mbox].TIR|=id;
CAN->sTxMailBox[mbox].TIR|=ide<<2;
CAN->sTxMailBox[mbox].TIR|=rtr<<1;
len&=0X0F;//得到低四位
CAN->sTxMailBox[mbox].TDTR&=~(0X0000000F);
CAN->sTxMailBox[mbox].TDTR|=len; //设置DLC.
//待发送数据存入邮箱.
CAN->sTxMailBox[mbox].TDHR=(((u32)dat[7]<<24)|
((u32)dat[6]<<16)|
((u32)dat[5]<<8)|
((u32)dat[4]));
CAN->sTxMailBox[mbox].TDLR=(((u32)dat[3]<<24)|
((u32)dat[2]<<16)|
((u32)dat[1]<<8)|
((u32)dat[0]));
CAN->sTxMailBox[mbox].TIR|=1<<0; //请求发送邮箱数据
return mbox;
}
//获得发送状态.
//mbox:邮箱编号;
//返回值:发送状态. 0,挂起;0X05,发送失败;0X07,发送成功.
u8 Can_Tx_Staus(u8 mbox)
{
u8 sta=0;
switch (mbox)
{
case 0:
sta |= CAN->TSR&(1<<0); //RQCP0
sta |= CAN->TSR&(1<<1); //TXOK0
sta |=((CAN->TSR&(1<<26))>>24); //TME0
break;
case 1:
sta |= CAN->TSR&(1<<8)>>8; //RQCP1
sta |= CAN->TSR&(1<<9)>>8; //TXOK1
sta |=((CAN->TSR&(1<<27))>>25); //TME1
break;
case 2:
sta |= CAN->TSR&(1<<16)>>16; //RQCP2
sta |= CAN->TSR&(1<<17)>>16; //TXOK2
sta |=((CAN->TSR&(1<<28))>>26); //TME2
break;
default:
sta=0X05;//邮箱号不对,肯定失败.
break;
}
return sta;
}
//得到在FIFO0/FIFO1中接收到的报文个数.
//fifox:0/1.FIFO编号;
//返回值:FIFO0/FIFO1中的报文个数.
u8 Can_Msg_Pend(u8 fifox)
{
if(fifox==0)return CAN->RF0R&0x03;
else if(fifox==1)return CAN->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(u8 fifox,u32 *id,u8 *ide,u8 *rtr,u8 *len,u8 *dat)
{
*ide=CAN->sFIFOMailBox[fifox].RIR&0x04;//得到标识符选择位的值
if(*ide==0)//标准标识符
{
*id=CAN->sFIFOMailBox[fifox].RIR>>21;
}else //扩展标识符
{
*id=CAN->sFIFOMailBox[fifox].RIR>>3;
}
*rtr=CAN->sFIFOMailBox[fifox].RIR&0x02; //得到远程发送请求值.
*len=CAN->sFIFOMailBox[fifox].RDTR&0x0F;//得到DLC
//*fmi=(CAN->sFIFOMailBox[FIFONumber].RDTR>>8)&0xFF;//得到FMI
//接收数据
dat[0]=CAN->sFIFOMailBox[fifox].RDLR&0XFF;
dat[1]=(CAN->sFIFOMailBox[fifox].RDLR>>8)&0XFF;
dat[2]=(CAN->sFIFOMailBox[fifox].RDLR>>16)&0XFF;
dat[3]=(CAN->sFIFOMailBox[fifox].RDLR>>24)&0XFF;
dat[4]=CAN->sFIFOMailBox[fifox].RDHR&0XFF;
dat[5]=(CAN->sFIFOMailBox[fifox].RDHR>>8)&0XFF;
dat[6]=(CAN->sFIFOMailBox[fifox].RDHR>>16)&0XFF;
dat[7]=(CAN->sFIFOMailBox[fifox].RDHR>>24)&0XFF;
if(fifox==0)CAN->RF0R|=0X20;//释放FIFO0邮箱
else if(fifox==1)CAN->RF1R|=0X20;//释放FIFO1邮箱
}
#if CAN_RX0_INT_ENABLE //使能RX0中断
//中断服务函数
void USB_LP_CAN1_RX0_IRQHandler(void)
{
u8 rxbuf[8];
u32 id;
u8 ide,rtr,len;
Can_Rx_Msg(0,&id,&ide,&rtr,&len,rxbuf);
printf("id:%d\r\n",id);
printf("ide:%d\r\n",ide);
printf("rtr:%d\r\n",rtr);
printf("len:%d\r\n",len);
printf("rxbuf[0]:%d\r\n",rxbuf[0]);
printf("rxbuf[1]:%d\r\n",rxbuf[1]);
printf("rxbuf[2]:%d\r\n",rxbuf[2]);
printf("rxbuf[3]:%d\r\n",rxbuf[3]);
printf("rxbuf[4]:%d\r\n",rxbuf[4]);
printf("rxbuf[5]:%d\r\n",rxbuf[5]);
printf("rxbuf[6]:%d\r\n",rxbuf[6]);
printf("rxbuf[7]:%d\r\n",rxbuf[7]);
}
#endif
//can发送一组数据(固定格式:ID为0X12,标准帧,数据帧)
//len:数据长度(最大为8)
//msg:数据指针,最大为8个字节.
//返回值:0,成功;
// 其他,失败;
u8 Can_Send_Msg(u8* msg,u8 len)
{
u8 mbox;
u16 i=0;
mbox=Can_Tx_Msg(0X12,0,0,len,msg);
while((Can_Tx_Staus(mbox)!=0X07)&&(i<0XFFF))i++;//等待发送结束
if(i>=0XFFF)return 1; //发送失败?
return 0; //发送成功;
}
//can口接收数据查询
//buf:数据缓存区;
//返回值:0,无数据被收到;
// 其他,接收的数据长度;
u8 Can_Receive_Msg(u8 *buf)
{
u32 id;
u8 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;
}
此部分代码总共8个函数,我们挑其中几个比较重要的函数简单介绍下,首先是:CAN_Mode_Init函数。该函数用于CAN的初始化,该函数带有5个参数,可以设置CAN通信的波特率和工作模式等,在该函数中,我们就是按30.1节末尾的介绍来初始化的,本章中,我们设计滤波器组0工作在32位标识符屏蔽模式,从设计值可以看出,该滤波器是不会对任何标识符进行过滤的,因为所有的标识符位都被设置成不需要关心,这样设计,主要是方便大家实验。
第二个函数,Can_Tx_Msg函数。该函数用于CAN报文的发送,该函数先查找空的发送邮箱,然后设置标识符ID等信息,最后写入数据长度和数据,并请求发送,实现一次报文的发送。
第三个函数,Can_Msg_Pend函数。该函数用于查询接收FIFOx(x=0/1)是否为空,如果返回0,则表示FIFOx空,如果为其他值,则表示FIFOx有数据。
第四个函数,Can_Rx_Msg函数。该函数用于CAN报文的接收,该函数先读取标识符,然后读取数据长度,并读取接收到的数据,最后释放邮箱数据。
can.c里面,还包含了中断接收的配置,通过can.h的CAN_RX0_INT_ENABLE宏定义,来配置是否使能中断接收,本章我们不开启中断接收的。其他函数我们就不一一介绍了,都比较简单,大家自行理解即可。保存can.c,并把该文件加入HARDWARE组下面,然后我们打开can.h在里面输入如下代码:
#ifndef __CAN_H
#define __CAN_H
#include "sys.h"
//CAN接收RX0中断使能
#define CAN_RX0_INT_ENABLE 0 //0,不使能;1,使能.
u8 CAN_Mode_Init(u8 tsjw,u8 tbs2,u8 tbs1,u16 brp,u8 mode); //CAN初始化
u8 Can_Tx_Msg(u32 id,u8 ide,u8 rtr,u8 len,u8 *dat); //发送数据
u8 Can_Msg_Pend(u8 fifox); //查询邮箱报文
void Can_Rx_Msg(u8 fifox,u32 *id,u8 *ide,u8 *rtr,u8 *len,u8 *dat);//接收数据
u8 Can_Tx_Staus(u8 mbox); //返回发送状态
u8 Can_Send_Msg(u8* msg,u8 len); //发送数据
u8 Can_Receive_Msg(u8 *buf); //接收数据
#endif
其中CAN_RX0_INT_ENABLE用于设置是否使能中断接收,本章我们不用中断接收,故设置为0。保存can.h。最后,我们在test.c里面,修改main函数如下:
int main(void)
{
u8 key;
u8 i=0,t=0;
u8 cnt=0;
u8 canbuf[8];
u8 res;
u8 mode=1;//CAN工作模式;0,普通模式;1,环回模式
Stm32_Clock_Init(9); //系统时钟设置
uart_init(72,9600); //串口初始化为9600
delay_init(72); //延时初始化
LED_Init(); //初始化与LED连接的硬件接口
LCD_Init(); //初始化LCD
usmart_dev.init(72); //初始化USMART
KEY_Init(); //按键初始化
CAN_Mode_Init(1,8,7,5,mode);//CAN初始化,波特率450Kbps
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,"ATOM@ALIENTEK");
LCD_ShowString(60,110,200,16,16,"2012/9/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==KEY_RIGHT)//KEY0按下,发送一次数据
{
for(i=0;i<8;i++)
{
canbuf=cnt+i;//填充发送缓冲区
if(i<4)LCD_ShowxNum(60+i*32,210,canbuf,3,16,0X80); //显示数据
else LCD_ShowxNum(60+(i-4)*32,230,canbuf,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==KEY_UP)//WK_UP按下,改变CAN的工作模式
{
mode=!mode;
CAN_Mode_Init(1,8,7,5,mode);//CAN模式初始化,波特率450Kbps
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,3,16,0X80); //显示数据
else LCD_ShowxNum(60+(i-4)*32,290,canbuf,3,16,0X80); //显示数据
}
}
t++;
delay_ms(10);
if(t==20)
{
LED0=!LED0;//提示系统正在运行
t=0;
cnt++;
LCD_ShowxNum(60+48,170,cnt,3,16,0X80); //显示数据
}
}
}
此部分代码,我们主要关注下CAN_Mode_Init(1,8,7,5,mode);该函数用于设置波特率和CAN的模式,根据前面的波特率计算公式,我们知道这里的波特率被初始化为450Kbps。mode参数用于设置CAN的工作模式(普通模式/环回模式),通过WK_UP按键,可以随时切换模式。cnt是一个累加数,一旦KEY_RIGHT(KEY0)按下,就以这个数位基准连续发送5个数据。当CAN总线收到数据的时候,就将收到的数据直接显示在LCD屏幕上。
30.4 下载验证 在代码编译成功之后,我们通过下载代码到ALIENTEK战舰STM32开发板上,得到如图30.4.1所示:
图30.4.1 程序运行效果图
伴随DS0的不停闪烁,提示程序在运行。默认我们是设置的环回模式,此时,我们按下KEY0就可以在LCD模块上面看到自发自收的数据(如上图所示),如果我们选择普通模式(通过WK_UP按键切换),就必须连接两个开发板的CAN接口,然后就可以互发数据了。如图30.4.2所示:
图30.4.2 CAN普通模式数据收发测试
上图中,左侧的图片来自开发板A,发送了8个数据,右侧来自开发板B,收到了来自开发板A的8个数据。