引言:CAN(Controller AreaNetwork)是目前使用非常广泛的一种通信方式,被大量应用于汽车、船泊、数控加工设备、机器人以及各种自动化设备。与之相关的工作人员——工程师、设备师、维修师、开发人员都将频繁与其打交道,不管是开发人员的初期调试,还是维修师检测设备的状况,对CAN总线的数据量都有监视的渴望,对两根差分的信号线,相通过示波器来监视总线上的数据,恐怕让人神精。当然,市面上也有商用CAN监视器,本人也用过,使用也还算方便,但不知道为啥,已经坏掉了好几个(与我无关),因为本人现在跳到一家资金捉襟见肘的小公司,咖啡都不给买,工资也发不起的扣老板,自制一台CAN监视器的想法诞生了。
1. 框架图
PC <<---->>USB接口<<---->>MCP2200<<---->>PIC32<<---->>CAN Transceiver<<---->>CAN 总线
关于USB接口,在这里就是与上位机PC通信的方式,是笔者惯用方式,一根USB线就可以,并且在PIC32里只需要做UART的驱动就可以了,十分简单。读者也可自行选择其他方式,比如使用PIC32自带的USB功能,HID或CDC都可以,用232芯片转换也行吧。
2. PIC32 关于 CAN 模块的配置
请将CAN模块配置为监听模式(Listen-Onlymode)。
以下引自Microchip公司手册DS61154C_CN:
监听模式是正常工作模式的一种变化形式。如果监听模式被激活,则CAN模块会出现在CAN总线上,但处于被动状态。它会接收报文,但不会发送报文。CAN模块不会产生错误标志,也不会应答信号。在该状态下,错误计数器不再工作。监听模式可用来检测CAN总线上的波特率。要使用此功能,必须至少有另外两个互相通信的节点。波特率可以通过测试一些不同值以实证方式检测。该模式也可用作总线监视器,因为CAN总线不会影响数据通信。
配置代码:
void CAN1Init(void)
{
CAN_BIT_CONFIGcanBitConfig;
UINT baudPrescalar;
CANEnableModule(CAN1,TRUE);
CANSetOperatingMode(CAN1,CAN_CONFIGURATION);
while(CANGetOperatingMode(CAN1)!= CAN_CONFIGURATION);
CANSetTimeStampPrescalar(CAN1, 80 );
CANSetCap(CAN1, TRUE );
canBitConfig.phaseSeg2Tq = CAN_BIT_2TQ;
canBitConfig.phaseSeg1Tq = CAN_BIT_3TQ;
canBitConfig.propagationSegTq = CAN_BIT_4TQ;
canBitConfig.phaseSeg2TimeSelect = TRUE;
canBitConfig.sample3Time = TRUE;
canBitConfig.syncJumpWidth = CAN_BIT_2TQ;
CANSetSpeed( CAN1, &canBitConfig,SYSTEM_FREQ, CAN_SPD_1MHZ );
CANAssignMemoryBuffer(CAN1,CAN1MessageFifoArea,(24* 16) );
CANConfigureChannelForRx(CAN1,CAN_CHANNEL1, 24, CAN_RX_FULL_RECEIVE);
CANConfigureFilterMask (CAN1, CAN_FILTER_MASK0, 0, CAN_SID,CAN_FILTER_MASK_IDE_TYPE);
CANConfigureFilter (CAN1, CAN_FILTER1, 0x00, CAN_SID);
CANLinkFilterToChannel (CAN1, CAN_FILTER1, CAN_FILTER_MASK0,CAN_CHANNEL1);
CANEnableFilter (CAN1, CAN_FILTER1, TRUE);
CANEnableChannelEvent(CAN1, CAN_CHANNEL1,CAN_RX_CHANNEL_NOT_EMPTY, TRUE);
CANEnableModuleEvent (CAN1, CAN_RX_EVENT,TRUE);
/* These functions are from interruptperipheral
* library. */
/*Step 7: Switch the CAN mode
* to normal mode. */
CANSetOperatingMode(CAN1,CAN_LISTEN_ONLY);
while(CANGetOperatingMode(CAN1)!= CAN_LISTEN_ONLY);
}
代码说明:以上代码为笔者自制CAN监视器正在使用的源代码,使用通道1来监听总线的消息。此代码参考了MPLAB X自带的关于CAN模块的示例代码,如果有相关函数不理解,请自行参考MPLAB X自带的示例代码。
3. 应用层代码的准备
CAN自定义数据结构
typedef struct _CAN_MSG_TYPE
{
struct
{
unsigned short sid; // 标准ID
unsigned short timer; // 接收时间
} ;
long size; //数据个数
unsigned char data[8]; // 数据buffer
} CAN_MSG_TYPE;
CAN消息检测函数
long can1_rx_chk( long channel )
{
long ret;
if ( !C1INTbits.RBIF )
{
return 0;
}
if((CANGetModuleEvent(CAN1)& CAN_RX_EVENT) == 0)
{
return 0;
}
if(CANGetChannelEvent(CAN1, channel) & CAN_RX_CHANNEL_NOT_EMPTY )
{
ret = 1;
}
else
{
ret = 0;
}
return ret;
}
CAN消息读取函数
void can1_rx_read( long channel, CAN_MSG_TYPE*msg )
{
CANRxMessageBuffer* message;
longi;
message = CANGetRxMessage(CAN1,channel);
msg->sid = message->msgSID.SID;
msg->size = message->msgEID.DLC;
msg->timer =message->msgSID.CMSGTS;
for( i = 0; i < 8; i++ )
{
msg->data[i]= message->data[i];
}
CANUpdateChannel(CAN1,channel);
}
USB打印函数
void usb_printf( char*str, long dat );
此函数稍有复杂,并不在这里贴出代码,请读者自行编写或是采用更为简单的方法。
此函数的功能为与标准C语言里的printf函数类似,目的是通过UART模块将字符打印到PC的终端应用程序。
4. 应用层代码
for ( ;; )
{
if ( can1_rx_chk( CAN_CHANNEL1 ) )
{
can1_rx_read( CAN_CHANNEL1, &can_msg );
usb_printf( "\r\nID:%h\r\n", can_msg.sid);
usb_printf( "Timer:%dus\r\n", can_msg.timer);
usb_printf( "Size:%d\r\n", can_msg.size);
usb_printf( "Data:", 0 );
for ( index = 0; index < 8; index++ )
{
usb_printf( "%d", can_msg.data[index] );
}
}
}
5.敲完代码编译,小伙伴们就可以了在PC的超级终端上看到CAN总线上的数据,当然也可以使用其它能够接收串口数据的其他应用软件。有个地方要请小伙伴们引起注意,就是CAN总线上的数据量与UART的通信速率的问题,请通过计算总线上的数据量来设置合理的UART速率,否则会得不到正确的总线数据。希望能够帮到一些正在为CAN通信纠结的小伙伴们,但也请小伙伴们尊重原创。