CAN是控制器局域网络(Controller Area Network, CAN)的简称,是国际上应用最广泛的现场总线之一。 在北美和西欧,CAN总线协议已经成为汽车计算机控制系统和嵌入式工业控制局域网的标准总线,并且拥有以CAN为底层协议专为大型货车和重工机械车辆设计的J1939协议。
本例通过用MC9S12XS128MAA来实现CAN标准帧的发送。
首先需要对CAN进行初始化配置,以下为本例所用到的寄存器介绍:
CANCTL0寄存器
RXFRM = 1 时,收到有效消息
= 0 时,未收到有效消息
RXACT = 1时,MSCAN正在接收消息
= 0时,MSCAN正在发送或空闲
CSWAI = 1 时,在等待模式下,模块停止计时
= 0时,等待模式期间模块不受影响
SYNCH = 1 时,MSCAN与CAN同步
= 0 时,MSCAN与CAN不同步
TIME = 1 时,启用内部 MSCAN计时器
= 0 时,禁用内部 MSCAN计时器
WUPE = 1 时,唤醒启用
= 0 时,唤醒禁用
SLPRQ = 1 时,CAN总线空闲时MSCAN进入休眠模式
= 0 时,MSCAN运行在正常模式
INITRQ = 1 时,MSCAN进入初始化模式
= 0 时,MSCAN运行在正常模式
CANCTL1寄存器
CANE = 1 时,MSCAN使能
= 0 时,MSCAN禁止
CLKSRC = 1 时,MSCAN时钟来源于总线
= 0 时,MSCAN时钟来源于晶振
LOOPB = 1 时,启用环回自检
= 0 时,禁用环回自检
LISTEN = 1 时,帧听模式开启
= 0 时,运行在正常模式
BORM = 1 时,根据用户请求总线断开恢复
= 0 时,自动总线断开恢复
MUPM = 1 时,只有当CAN总线上的主脉冲长度为t wup时,MSCAN才会唤醒
= 0 时,MSCAN任何时候都可唤醒
SLPAK = 1 时,睡眠模式激活
= 0 时,运行在正常模式
INITAK = 1 时,初始化模式激活
= 0 时,运行在正常模式
CANBTR0寄存器
SJW[1:0] :同步跳跃宽度
BRP[5:0] :波特率预分频
CANBTR1寄存器
SAMP = 1 时,每bit采样3次
= 0 时,每bit采样1次
TSEG2[2:0] :时间段2
TSEG1[3:0] :时间段1
通过以下公式计算bit time
CANIDMR0–CANIDMR7寄存器
AM[7:0]:接收屏蔽位
= 0 时,匹配接收码寄存器和标识符位
= 1 时,忽略相应接收码寄存器位
CANRIER寄存器
WUPIE = 1 时,唤醒事件导致唤醒中断请求
= 0 时,唤醒事件不生成中断请求
CSCIE = 1 时,CAN状态改变事件导致错误中断请求
= 0 时,CAN状态改变事件不生成中断请求
RSTATE[1:0] = 00 时,不生成由接收器状态变化引起的任何CSCIF中断
= 01 时,仅当接收器进入或离开“总线关闭”状态时生成CSCIF中断。丢弃其他接收器状态改变生成的CSCIF中断
= 10 时,只有当接收器进入或离开“RXERR”或“总线关闭”(2)状态时,才生成CSCIF中断。丢弃其他接收器状态改变生成的CSCIF中断
= 11 时,任何状态改变均生成CSCIF中断
TSTATE[1:0] = 00 时,不生成由发送器状态变化引起的任何CSCIF中断
= 01 时,仅当发送器进入或离开“总线关闭”状态时生成CSCIF中断。丢弃其他发送器状态改变生成的CSCIF中断
= 10 时,仅当发送器进入或离开“TXERR”或“总线关闭”状态时生成CSCIF中断。丢弃其他发送器状态改变生成的CSCIF中断
= 11 时,任何状态改变均生成CSCIF中断
OVRIE = 1 时,溢出事件导致错误中断请求
= 0 时,溢出事件不生成中断请求
RXFIE = 1 时,接收缓冲区满事件导致接收中断请求
= 0 时,接收缓冲区满事件不生成中断请求
CANTFLG寄存器
TXE[2:0] = 1 时,关联的消息缓冲区为空
= 0 时,关联的消息缓冲区已满
CANTBSEL寄存器
TX[2:0] = 1 时,如果最低编号置位,则选择相关的消息缓冲区
= 0 时,忽略选择关联的消息缓冲区
CANTXIDR:用来存放CAN报文的ID、远程帧或数据帧状态、标准帧或扩展帧
CANTXDSR:用来存放数据
CANTXDLR:用来存放数据长度
CANTXTBPR:优先级设置,具有最低本地优先级字段的传输缓冲区获得优先级
以下为完整的CAN发送报文程序(用到的寄存器意思见上)
#include
#include "derivative.h"
#define LED PORTB_PB0 //定义连接发光二级管的PORTB_PB0口数据寄存
//器为LED,写'0'亮,写'1' 灭
#define LED_dir DDRB_DDRB0 //定义连接发光二级管的PORTB_PB0口方向寄存器
//为LED_dir,写'0'做输入口,写'1'做输出口
#define ID 0x0001
#define ID1 0x0002 //发送标识符ID号
#define data_len_TX 8 //发送数据长度
unsigned char a;
unsigned char senddata[8] = {'A','d','o','k','e','n','T','o'};
unsigned char senddata1[8] = {'r','o','t','h','y','N','B','!'}; //发送的数据 AdokenTorothyNB!
struct can_msg //定义发送报文的结构体
{
unsigned int id; //ID号
Bool RTR; //远程帧或数据帧标志位
unsigned char data[8]; //存放数据
unsigned char len; //数据长度
unsigned char prty; //优先级
};
struct can_msg msg_send, msg_send1; //定义结构体变量
void INIT_PLL(void) //初始化锁相环
{
CLKSEL_PLLSEL=0; //内部总线时钟来源于晶振
PLLCTL_PLLON=0; //关闭PLL
SYNR=0x40 | 0x03;
REFDV=0x80 | 0x01;
POSTDIV=0x00; //PLL为64MHz
PLLCTL_PLLON=1; //打开PLL
_asm(nop);
_asm(nop); //等待两个机器周期
while(CRGFLG_LOCK==0); //根据CRGFLG寄存器的LOCK位,确定PLL是否稳定 LOCK==1 稳定,==0 不稳定
CLKSEL_PLLSEL =1; //内部总线时钟选择PLL作为时钟源
}
void INIT_CAN0(void) //初始化CAN0
{
if(CAN0CTL0_INITRQ==0) // 查询是否进入初始化状态
CAN0CTL0_INITRQ =1; // 进入初始化状态
while (CAN0CTL1_INITAK==0); //初始化握手标志
CAN0BTR0_SJW = 0; //设置同步
CAN0BTR0_BRP = 7; //设置波特率
CAN0BTR1 = 0x1c; //设置时段1和时段2的Tq个数 ,总线频率为250kb/s
/*
Bit Time = [(Prescaler value)*(1+TimeSegment1+TimeSegment2)]/fCANCLK
f = 1 / Bit Time
Prescaler value = 8
TimeSegment1 = 13
TimeSegment2 = 2
fcanclk = 32MHz
f = 250kb/s
*/
CAN0IDMR0 = 0xFF;
CAN0IDMR1 = 0xFF;
CAN0IDMR2 = 0xFF;
CAN0IDMR3 = 0xFF;
CAN0IDMR4 = 0xFF;
CAN0IDMR5 = 0xFF;
CAN0IDMR6 = 0xFF;
CAN0IDMR7 = 0xFF; // 关闭滤波器
CAN0CTL1 = 0xC0; //使能MSCAN模块,设置为一般运行模式、使用总线时钟源
CAN0CTL0 = 0x00; //返回一般模式运行
while(CAN0CTL1_INITAK); //等待回到一般运行模式
while(CAN0CTL0_SYNCH==0); //等待总线时钟同步
CAN0RIER_RXFIE = 0; //禁止接收中断
}
Bool MSCAN0SendMsg(struct can_msg msg) //CAN发送
{
unsigned char send_buf, sp;
if(msg.len > 8)
return(FALSE); // 检查数据长度
if(CAN0CTL0_SYNCH==0)
return(FALSE); // 检查总线时钟是否同步
send_buf = 0;
do
{
CAN0TBSEL=CAN0TFLG;
send_buf=CAN0TBSEL;
}
while(!send_buf); // 寻找空闲的缓冲器
CAN0TXIDR0 = (unsigned char)(msg.id>>3);
CAN0TXIDR1 = (unsigned char)(msg.id<<5); // 写入标识符
if(msg.RTR)
CAN0TXIDR1 |= 0x10; //检测数据帧或标准帧
for(sp = 0; sp < msg.len; sp++)
*((&CAN0TXDSR0)+sp) = msg.data[sp]; // 写入数据
CAN0TXDLR = msg.len; // 写入数据长度
CAN0TXTBPR = msg.prty; // 写入优先级
CAN0TFLG = send_buf; // 清 TXx 标志 (缓冲器准备发送)
return(TRUE);
}
void main(void) {
DisableInterrupts; //禁止打开所有中断
INIT_PLL(); //初始化PLL模块,设置busclock=32Mhz
INIT_CAN0(); //初始化can0模块
LED_dir=1; //LED接口PB0设置为输出口
LED=0; //初始化LED初始状态为亮
EnableInterrupts; //允许打开所有中断
//填写报文内容
msg_send.id = ID; //填写报头ID
for(a=0;a
**注意:**ID号我们需要通过移位处理放入CANTXIDR寄存器相应的位置,另外还需将相应的RTR和IDE数据放入相应位置
本例我们通过用ZLG-USBCAN来查看CAN发送出来的数据,具体接线实物图如下所示
然后通过CANTest可以看到循环接收到的CAN数据,如下图所示(数据是ASCII码表示的,和我们程序中所要发送的两个数组是一致的)