CAN通信
- CAN通信的由来
- CAN通信格式
- CAN通信配置
- 实验验证
- 结束语
- 参考资料目录
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]; // 字节1RX_FRAME_CANA.x= (Uint16)(((ucRXMsgData1[1] & 0xF0) >> 4) & 0x0F); // 字节2HRX_FRAME_CANA.xx = (Uint16)ucRXMsgData1[1] & 0x0F; // 字节2LRX_FRAME_CANA.xxx= (Uint16)(((ucRXMsgData1[2] & 0xF0) >> 4) & 0x0F); // 字节3HRX_FRAME_CANA.xxxx= (Uint16)ucRXMsgData1[2] & 0x0F; // 字节3LRX_FRAME_CANA.xxxxx= ((Uint16)(ucRXMsgData1[3] & 0xFF)) * 0.2; // 字节4RX_FRAME_CANA.xxxxxx= ((Uint16)(ucRXMsgData1[4] & 0xFF)) * 196.08; // 字节5RX_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的所有例程