打开上一章的工程,首先在HARDWARE文件夹下新建一个CAN的文件夹,然后新建一个can.c和can.h的文件保存在CAN文件夹下,并将CAN文件夹加入头文件包含路径。 打开can.c文件,输入如下代码: #include "can.h" #include "led.h" #include "delay.h" #include "usart.h" //CAN初始化 //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); //mode:0,普通模式;1,回环模式; //Fpclk1的时钟在初始化的时候设置为36M,如果设置CAN_Normal_Init(1,8,7,5,1); //则波特率为:36M/((8+7+1)*5)=450Kbps //返回值:0,初始化OK; // 其他,初始化失败; u8 CAN_Mode_Init(u8 tsjw,u8 tbs2,u8 tbs1,u16 brp,u8 mode) { u16 i=0; if(tsjw==0||tbs2==0||tbs1==0||brp==0)return 1; tsjw-=1;//先减去1.再用于设置 tbs2-=1; tbs1-=1; brp-=1; RCC->APB2ENR|=1<<2; //使能PORTA时钟 GPIOA->CRH&=0XFFF00FFF; GPIOA->CRH|=0X000B8000;//PA11 RX,PA12 TX推挽输出 GPIOA->ODR|=3<<11; RCC->APB1ENR|=1<<25;//使能CAN时钟 CAN使用的是APB1的时钟(max:36M) CAN->MCR=0x0000; //退出睡眠模式(同时设置所有位为0) CAN->MCR|=1<<0; //请求CAN进入初始化模式 while((CAN->MSR&1<<0)==0) { i++; if(i>100)return 2;//进入初始化模式失败 } CAN->MCR|=0<<7; //非时间触发通信模式 CAN->MCR|=0<<6; //软件自动离线管理 CAN->MCR|=0<<5; //睡眠模式通过软件唤醒(清除CAN->MCR的SLEEP位) CAN->MCR|=1<<4; //禁止报文自动传送 CAN->MCR|=0<<3; //报文不锁定,新的覆盖旧的 CAN->MCR|=0<<2; //优先级由报文标识符决定 CAN->BTR=0x00000000;//清除原来的设置. CAN->BTR|=mode<<30; //模式设置 0,普通模式;1,回环模式; CAN->BTR|=tsjw<<24; //重新同步跳跃宽度(Tsjw)为tsjw+1个时间单位 CAN->BTR|=tbs2<<20; //Tbs2=tbs2+1个时间单位 CAN->BTR|=tbs1<<16; //Tbs1=tbs1+1个时间单位 CAN->BTR|=brp<<0; //分频系数(Fdiv)为brp+1 //波特率:Fpclk1/((Tbs1+Tbs2+1)*Fdiv) CAN->MCR&=~(1<<0); //请求CAN退出初始化模式 while((CAN->MSR&1<<0)==1) { i++; if(i>0XFFF0)return 3;//退出初始化模式失败 } //过滤器初始化 CAN->FMR|=1<<0; //过滤器组工作在初始化模式 CAN->FA1R&=~(1<<0); //过滤器0不激活 CAN->FS1R|=1<<0; //过滤器位宽为32位. CAN->FM1R|=0<<0; //过滤器0工作在标识符屏蔽位模式 CAN->FFA1R|=0<<0; //过滤器0关联到FIFO0 CAN->sFilterRegister[0].FR1=0X00000000;//32位ID CAN->sFilterRegister[0].FR2=0X00000000;//32位MASK CAN->FA1R|=1<<0; //激活过滤器0 CAN->FMR&=0<<0; //过滤器组进入正常模式 #if CAN_RX0_INT_ENABLE //使用中断接收 CAN->IER|=1<<1; //FIFO0消息挂号中断允许. MY_NVIC_Init(1,0,USB_LP_CAN_RX0_IRQChannel,2);//组2 #endif return 0; } //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屏幕上。 在代码编译成功之后,我们通过下载代码到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个数据。 |