CAN通信的由来
为适应“减少线束的数量”、“通过多个LAN,进行大量数据的高速通信”的需要,1986 年德国电气商博世公司开发出面向汽车的CAN 通信协议。CAN属于现场总线的范畴,它是一种有效支持分布式控制或实时控制的串行通信网络。
CAN通信格式
CAN通信共有5种,分别为数据帧、遥控帧、错误帧、过载帧、帧间隔。数据帧格式由下图所示,分为标准格式和拓展格式,笔者目前仅使用到标准数据帧,使用其中的64bits数据段进行CAN节点间的数据交互,以ID号区分数据类型。注意ACK!实际波形中彼己电平高度不一
CAN通信配置
①初始化CAN的映射GPIO,使用TI封装好的函数初始化CAN,选择CAN时钟源,设置波特率,使能CAN中断触发源,开启CAN;
void InitCana(void)
{
InitCanaGpio();
//
// Initialize the CAN controller
//
CANInit(CANA_BASE);
//
// Setup CAN to be clocked off the M3/Master subsystem clock
//
CANClkSourceSelect(CANA_BASE, 0);
//
// Set up the bit rate for the CAN bus. This function sets up the CAN
// bus timing for a nominal configuration. You can achieve more control
// over the CAN bus timing by using the function CANBitTimingSet() instead
// of this one, if needed.
// In this example, the CAN bus is set to 500 kHz. In the function below,
// the call to SysCtlClockGet() is used to determine the clock rate that
// is used for clocking the CAN peripheral. This can be replaced with a
// fixed value if you know the value of the system clock, saving the extra
// function call. For some parts, the CAN peripheral is clocked by a fixed
// 8 MHz regardless of the system clock in which case the call to
// SysCtlClockGet() should be replaced with 8000000. Consult the data
// sheet for more information about CAN peripheral clocking.
//
CANBitRateSet(CANA_BASE, 200000000, 200000); // 波特率200kbps
//
// Enable interrupts on the CAN peripheral. This example uses static
// allocation of interrupt handlers which means the name of the handler
// is in the vector table of startup code. If you want to use dynamic
// allocation of the vector table, then you must also call CANIntRegister()
// here.
//
CANIntEnable(CANA_BASE, CAN_INT_MASTER | CAN_INT_ERROR | CAN_INT_STATUS);
//
// Enable the CAN for operation.
//
CANEnable(CANA_BASE);
//
// Enable CAN Global Interrupt line0
//
CANGlobalIntEnable(CANA_BASE, CAN_GLB_INT_CANINT0);
}
②确定收发缓存数组、ID号;
// CAN通信
unsigned char ucRXMsgData1[8]; // CAN接受数据
unsigned char ucTXMsgData2[8]; // CAN发送数据
unsigned char ucTXMsgData3[8]; // CAN发送数据
unsigned char ucTXMsgData4[8]; // CAN发送数据
unsigned char ucTXMsgData5[8]; // CAN发送数据
unsigned char ucTXMsgData6[8]; // CAN发送数据
tCANMsgObject sRXCANMessage1 = {RX_MSG_OBJ_ID1, 0, 2, 8, ucRXMsgData1}; // CAN接收结构体 MSG_OBJ_RX_INT_ENABLE = 2
tCANMsgObject sTXCANMessage2 = {TX_MSG_OBJ_ID2, 0, 1, 8, ucTXMsgData2}; // CAN发送结构体 MSG_OBJ_TX_INT_ENABLE = 1
tCANMsgObject sTXCANMessage3 = {TX_MSG_OBJ_ID3, 0, 1, 8, ucTXMsgData3}; // CAN发送结构体 MSG_OBJ_TX_INT_ENABLE = 1
tCANMsgObject sTXCANMessage4 = {TX_MSG_OBJ_ID4, 0, 1, 8, ucTXMsgData4}; // CAN发送结构体 MSG_OBJ_TX_INT_ENABLE = 1
tCANMsgObject sTXCANMessage5 = {TX_MSG_OBJ_ID5, 0, 1, 8, ucTXMsgData5}; // CAN发送结构体 MSG_OBJ_TX_INT_ENABLE = 1
tCANMsgObject sTXCANMessage6 = {TX_MSG_OBJ_ID6, 0, 1, 8, ucTXMsgData6}; // CAN发送结构体 MSG_OBJ_TX_INT_ENABLE = 1
③根据自己的通信机制,封装好CAN通信收发函数;
void CANA_TX(void)
{
unsigned int T_switch = 0; // 选择数据发送
CANA_TX_FRAME_CNT = CANA_TX_FRAME_CNT + 1; // 帧计数自增
// CANA_TX_FRAME_CNT = CANA_TX_FRAME_CNT & 0xFF; // 帧计数达到255后清零,循环计数
T_switch = CANA_TX_FRAME_CNT % 5;
switch(T_switch)
{
case 0:
ucTXMsgData2[0] = CAN_CNT_delta & 0xFF; // 帧计数
ucTXMsgData2[1] = 0; // ...此处略去
ucTXMsgData2[2] = 0; // ...此处略去
ucTXMsgData2[3] = 0; // ...此处略去
ucTXMsgData2[4] = 0; // ...此处略去
ucTXMsgData2[5] = 0; // ...此处略去
ucTXMsgData2[6] = 0; // ...此处略去
ucTXMsgData2[7] = 0; // ...此处略去
CanaMessageSet(TX_MSG_OBJ_ID2, &sTXCANMessage2, MSG_OBJ_TYPE_TX);
break;
case 1:
ucTXMsgData3[0] = CAN_CNT_delta & 0xFF; // 帧计数
ucTXMsgData3[1] = 0; // ...此处略去
ucTXMsgData3[2] = 0; // ...此处略去
ucTXMsgData3[3] = 0; // ...此处略去
ucTXMsgData3[4] = 0; // ...此处略去
ucTXMsgData3[5] = 0; // ...此处略去
ucTXMsgData3[6] = // ...此处略去
ucTXMsgData3[7] = // ...此处略去
CanaMessageSet(TX_MSG_OBJ_ID3, &sTXCANMessage3, MSG_OBJ_TYPE_TX);
break;
case 2:
ucTXMsgData4[0] = CAN_CNT_delta & 0xFF; // 帧计数
ucTXMsgData4[1] = // ...此处略去
ucTXMsgData4[2] = // ...此处略去
ucTXMsgData4[3] = // ...此处略去
ucTXMsgData4[4] = // ...此处略去
ucTXMsgData4[5] = // ...此处略去
ucTXMsgData4[6] = // ...此处略去
ucTXMsgData4[7] = // ...此处略去
CanaMessageSet(TX_MSG_OBJ_ID4, &sTXCANMessage4, MSG_OBJ_TYPE_TX);
break;
case 3:
ucTXMsgData5[0] = CAN_CNT_delta & 0xFF; // 帧计数
ucTXMsgData5[1] = // ...此处略去
ucTXMsgData5[2] = // ...此处略去
ucTXMsgData5[3] = // ...此处略去
ucTXMsgData5[4] = // ...此处略去
ucTXMsgData5[5] = // ...此处略去
ucTXMsgData5[6] = // ...此处略去
ucTXMsgData5[7] = // ...此处略去
CanaMessageSet(TX_MSG_OBJ_ID5, &sTXCANMessage5, MSG_OBJ_TYPE_TX);
break;
case 4:
ucTXMsgData6[0] = CAN_CNT_delta & 0xFF; // 帧计数
ucTXMsgData6[1] = // ...此处略去
ucTXMsgData6[2] = // ...此处略去
ucTXMsgData6[3] = // ...此处略去
ucTXMsgData6[4] = // ...此处略去
ucTXMsgData6[5] = // ...此处略去
ucTXMsgData6[6] = // ...此处略去
ucTXMsgData6[7] = // ...此处略去
CanaMessageSet(TX_MSG_OBJ_ID6, &sTXCANMessage6, MSG_OBJ_TYPE_TX);
CANA_TX_Active_Flag = 0;
break;
default:
break;
}
if(CANA_TX_FRAME_CNT >= 254)
{
CANA_TX_FRAME_CNT = 0;
}
}
void CANA_RX(void)
{
if((CANA_errorFlag == 0) && (CANA_RX_Flag == 1))
{
RX_FRAME_CANA.CNT = (Uint16)ucRXMsgData1[0]; // 字节1
RX_FRAME_CANA.x= (Uint16)(((ucRXMsgData1[1] & 0xF0) >> 4) & 0x0F); // 字节2H
RX_FRAME_CANA.xx = (Uint16)ucRXMsgData1[1] & 0x0F; // 字节2L
RX_FRAME_CANA.xxx= (Uint16)(((ucRXMsgData1[2] & 0xF0) >> 4) & 0x0F); // 字节3H
RX_FRAME_CANA.xxxx= (Uint16)ucRXMsgData1[2] & 0x0F; // 字节3L
RX_FRAME_CANA.xxxxx= ((Uint16)(ucRXMsgData1[3] & 0xFF)) * 0.2; // 字节4
RX_FRAME_CANA.xxxxxx= ((Uint16)(ucRXMsgData1[4] & 0xFF)) * 196.08; // 字节5
RX_FRAME_CANA.xxxxxxx= ((int)(((ucRXMsgData1[5] & 0xFF) << 8 ) | (ucRXMsgData1[6] & 0xFF))) * 0.02; // 字节6 字节7
}
}
④使用中断服务函数,传输接收发送结构体;
interrupt void CANA0_ISR(void)
{
/************************************************************
Description:CANA0中断服务程序
用于检测中断产生原因,发送接收上位机数据
************************************************************/
uint32_t status;
//
// Read the CAN-A interrupt status to find the cause of the interrupt
//
status = CANIntStatus(CANA_BASE, CAN_INT_STS_CAUSE);
//
// If the cause is a controller status interrupt, then get the status
//
if(status == CAN_INT_INT0ID_STATUS)
{
//
// Read the controller status. This will return a field of status
// error bits that can indicate various errors. Error processing
// is not done in this example for simplicity. Refer to the
// API documentation for details about the error status bits.
// The act of reading this status will clear the interrupt.
//
status = CANStatusGet(CANA_BASE, CAN_STS_CONTROL);
//
// Check to see if an error occurred.
//
if(((status & ~(CAN_ES_TXOK | CAN_ES_RXOK)) != 7) &&
((status & ~(CAN_ES_TXOK | CAN_ES_RXOK)) != 0))
{
//
// Set a flag to indicate some errors may have occurred.
//
CANA_errorFlag = 1;
}
}
//
// Check if the cause is the CAN-A receive message object 1
//
else if(status == RX_MSG_OBJ_ID1)
{
//
// Get the received message
//
CANMessageGet(CANA_BASE, RX_MSG_OBJ_ID1, &sRXCANMessage1, true);
//
// Getting to this point means that the RX interrupt occurred on
// message object 1, and the message RX is complete. Clear the
// message object interrupt.
//
CANIntClear(CANA_BASE, RX_MSG_OBJ_ID1);
//
// Since the message was received, clear any error flags.
//
CANA_errorFlag = 0;
CANA_RX_Flag = 1;
CANA_TX_Active_Flag = 1;
Timer_CANA_TX_1ms = 0;
}
//
// Check if the cause is the CAN-A send message object 1
//
else if(status == TX_MSG_OBJ_ID2)
{
//
// Getting to this point means that the TX interrupt occurred on
// message object 1, and the message TX is complete. Clear the
// message object interrupt.
//
CANIntClear(CANA_BASE, TX_MSG_OBJ_ID2);
//
// Since the message was sent, clear any error flags.
//
CANA_errorFlag = 0;
}
//
// Check if the cause is the CAN-A send message object 1
//
else if(status == TX_MSG_OBJ_ID3)
{
//
// Getting to this point means that the TX interrupt occurred on
// message object 1, and the message TX is complete. Clear the
// message object interrupt.
//
CANIntClear(CANA_BASE, TX_MSG_OBJ_ID3);
//
// Since the message was sent, clear any error flags.
//
CANA_errorFlag = 0;
}
//
// Check if the cause is the CAN-A send message object 1
//
else if(status == TX_MSG_OBJ_ID4)
{
//
// Getting to this point means that the TX interrupt occurred on
// message object 1, and the message TX is complete. Clear the
// message object interrupt.
//
CANIntClear(CANA_BASE, TX_MSG_OBJ_ID4);
//
// Since the message was sent, clear any error flags.
//
CANA_errorFlag = 0;
}
//
// Check if the cause is the CAN-A send message object 1
//
else if(status == TX_MSG_OBJ_ID5)
{
//
// Getting to this point means that the TX interrupt occurred on
// message object 1, and the message TX is complete. Clear the
// message object interrupt.
//
CANIntClear(CANA_BASE, TX_MSG_OBJ_ID5);
//
// Since the message was sent, clear any error flags.
//
CANA_errorFlag = 0;
}
//
// Check if the cause is the CAN-A send message object 1
//
else if(status == TX_MSG_OBJ_ID6)
{
//
// Getting to this point means that the TX interrupt occurred on
// message object 1, and the message TX is complete. Clear the
// message object interrupt.
//
CANIntClear(CANA_BASE, TX_MSG_OBJ_ID6);
//
// Since the message was sent, clear any error flags.
//
CANA_errorFlag = 0;
}
//
// If something unexpected caused the interrupt, this would handle it.
//
else
{
//
// Spurious interrupt handling can go here.
//
}
//
// Clear the global interrupt flag for the CAN interrupt line
//
CANGlobalIntClear(CANA_BASE, CAN_GLB_INT_CANINT0);
//
// Acknowledge this interrupt located in group 9
//
PieCtrlRegs.PIEACK.all = PIEACK_GROUP9;
}
实验验证
采用上位机(PC)发1帧,下位机(DSP)应答5帧的方式,实现遥控与遥测,要想实现更为精准的定时,则可以采取其他的方式,比如TT-CAN等。上位机发送间隔6ms,下位机应答间隔1ms。上位机数据帧ID(00000000001),下位机数据帧ID(00000000010、00000000011、00000000100、00000000101、00000000110)。使用ZLG的示波器,因为他自带CAN通信解码功能,使用起来非常方便。
上位机遥控帧:
下位机遥测帧①
下位机遥测帧⑤
结束语
笔者对于CAN的使用,仅停留在数据帧这一简单的帧种类上,以后若是有项目需要,则再补充学习其他的帧种类。TI对于CAN的支持比较到位,我们可以直接调用相应的函数,即可实现功能。当然规则越明细,开发人员对其标准化程度会越高,但使用灵活度、自由度变差。
参考资料目录
《TMS320F2837xS Delfino Microcontrollers Datasheet》Memory章节
《TMS320F2837xS Delfino Microcontrollers Technical Reference Manual》CAN章节
RENESAS应用手册《CAN入门书》
C2000Ware有关CAN的所有例程