1.CAN消息
一帧CAN消息的通信协议如下:
其中,
id: CAN消息的标识符,通常是11bits
rtr: 0–消息帧,普通消息;1–远程帧,远程传输请求消息,这种类型的消息不能包含数据帧;
dn: 数据帧,一帧普通CAN消息包括0~8bytes数据
在CANOpen中需要自己实现CAN发送消息接口,类似如下代码:
/********CAN接口函数 ********/
/*****************************
函数名称 : canSend
功 能 : CAN发送一帧消息
参 数 : notused --- 未使用
m ----------- 消息内容
返 回 值 : 0:失败 1:成功
*******************************/
unsigned char canSend(CAN_PORT notused, Message *m)
{
uint8_t i;
static CanTxMsg TxMsg;
TxMsg.StdId = m->cob_id;
if(m->rtr)
TxMsg.RTR = CAN_RTR_REMOTE;//远程帧
else
TxMsg.RTR = CAN_RTR_DATA;//消息帧
TxMsg.IDE = CAN_ID_STD;//标准消息类型
TxMsg.DLC = m->len;//数据帧长度,byte
for(i=0; i<m->len; i++)
TxMsg.Data[i] = m->data[i];//数据帧
if(xQueueSend(xCANSendQueue, &TxMsg, 100) != pdPASS)
{
return 1;
}
return 0;
}
2.CANOpen中的CAN消息
在CANOpen中的CAN消息形式如下:
在CAN帧中数据都是以低位优先(小端模式)的方式存放。例如,0x01020304应该按如下方式存放在CAN帧中:
其中,cobId用于表示CANOpen中消息类型和nodeID,4bits表示消息类型,7bits表示nodeID,如下:
3.NMT协议
NMT用于管理和监控网络中的各个节点,包括Boot-up消息、Heartbeat消息、节点保护消息、NMT控制消息。
3.1NMT控制消息
将节点置于operational模式:
将节点置于stop模式:
将节点置于pre-operational模式:
将节点置于reset-application模式:
将节点置于reset-communication模式:
如果要控制所有节点,则nodeId=0x00。
例如:
- 将节点0x6置于operational模式,000 01 06;
- 将所有节点置于pre-operational模式,000 80 00;
在CanFestival中的代码如下:
/*!
**
**
** @param d
** @param nodeId
** @param cs
**
** @return
**/
UNS8 masterSendNMTstateChange(CO_Data* d, UNS8 nodeId, UNS8 cs)
{
Message m;
MSG_WAR(0x3501, "Send_NMT cs : ", cs);
MSG_WAR(0x3502, " to node : ", nodeId);
/* message configuration */
m.cob_id = 0x0000; /*(NMT) << 7*/
m.rtr = NOT_A_REQUEST;
m.len = 2;
m.data[0] = cs;
m.data[1] = nodeId;
return canSend(d->canHandle,&m);
}
其中,cs的定义如下:
/* NMT Command Specifier, sent by master to change a slave state */
/* ------------------------------------------------------------- */
/* Should not be modified */
#define NMT_Start_Node 0x01
#define NMT_Stop_Node 0x02
#define NMT_Enter_PreOperational 0x80
#define NMT_Reset_Node 0x81
#define NMT_Reset_Comunication 0x82
3.2 节点监控消息
主节点通过发送以下消息询问从节点的状态:
从节点收到后回复:
例如:1.为了询问节点5的状态,主节点需要发送一条CAN远程帧(rtr=1):705;
2.节点5处于停止状态,所以回复:705 04
3.主节点通过NMT控制消息使它进入pre-operational模式:000 80 05
4.主节点发送新的request请求:705
5.节点5现在处于pre-operational模式,所以它回复:705 FF(注意:state的最高位t翻转到了1,因为7F的二进制是:01111111,最高位翻转到1就成了FF)
在CanFestival中的节点监控代码如下:
/*!
**
**
** @param d
** @param nodeId
**
** @return
**/
UNS8 masterSendNMTnodeguard(CO_Data* d, UNS8 nodeId)
{
Message m;
/* message configuration */
UNS16 tmp = nodeId | (NODE_GUARD << 7);
m.cob_id = UNS16_LE(tmp);
m.rtr = REQUEST;
m.len = 0;
MSG_WAR(0x3503, "Send_NODE_GUARD to node : ", nodeId);
return canSend(d->canHandle,&m);
}
3.3 节点心跳消息
在不需要主节点request的情况下,节点周期性的发送它的状态:
注意:这里没有需要翻转的位了。
例如:节点5处于pre-operational模式,那么它就会定时发送:705 7F
在CanFestival中的节点心跳代码如下:
/*! The Producer Timer Callback
**
**
** @param d
** @param id
* @ingroup heartbeato
**/
void ProducerHeartbeatAlarm(CO_Data* d, UNS32 id)
{
(void)id;
if(*d->ProducerHeartBeatTime)
{
Message msg;
/* Time expired, the heartbeat must be sent immediately
** generate the correct node-id: this is done by the offset 1792
** (decimal) and additionaly
** the node-id of this device.
*/
UNS16 tmp = *d->bDeviceNodeId + 0x700;
msg.cob_id = UNS16_LE(tmp);
msg.len = (UNS8)0x01;
msg.rtr = 0;
msg.data[0] = d->nodeState; /* No toggle for heartbeat !*/
/* send the heartbeat */
MSG_WAR(0x3130, "Producing heartbeat: ", d->nodeState);
canSend(d->canHandle,&msg );
}else{
d->ProducerHeartBeatTimer = DelAlarm(d->ProducerHeartBeatTimer);
}
}
这个函数在定时器中断中调用。
3.4 节点启动消息
当节点经过initializingm模式进入pre-operational模式时,会发送节点启动消息:
/*!
**
**
** @param d
**
** @return
**/
UNS8 slaveSendBootUp(CO_Data* d)
{
Message m;
#ifdef CO_ENABLE_LSS
if(*d->bDeviceNodeId==0xFF)return 0;
#endif
MSG_WAR(0x3407, "Send a Boot-Up msg ", 0);
/* message configuration */
{
UNS16 tmp = NODE_GUARD << 7 | *d->bDeviceNodeId;
m.cob_id = UNS16_LE(tmp);
}
m.rtr = NOT_A_REQUEST;
m.len = 1;
m.data[0] = 0x00;
return canSend(d->canHandle,&m);
}
4.SDO协议
SDO协议用于读取或写入节点的对象字典。读或写的请求(request)由客户端节点发起,数据被读取或被写入的节点是服务器节点。如果是读取或写入小于4bytes的数据,那么最简单的方式是使用SDO快速读写协议。所有的SDO协议都与一帧CAN消息长度相同,包括8bytes数据和rtr=0。
4.1 SDO快速写协议
如果要将数据0xd0写入服务器节点的对象字典中,那么客户端需要发送如下请求:
如果要将数据0xd0d1写入服务器节点的对象字典中,那么客户端需要发送如下请求:
如果要将数据0xd0d1d2写入服务器节点的对象字典中,那么客户端需要发送如下请求:
如果要将数据0xd0d1d2d3写入服务器节点的对象字典中,那么客户端需要发送如下请求:
如果服务器端成功接收到数据,则服务器发送回复(response):
如果服务器端接收失败,则服务器发送响应:
例如:
1.将1byte的数据0xFD写入到服务器节点5的对象字典,索引0x1400,子索引2,那么客户端节点发送:605 2F 00 14 02 FD 00 00 00
如果接收成功,节点5回复:585 60 00 14 02 00 00 00 00
2.将4byte数据0x60120208写入到服务器节点5的对象字典,索引0x1603,子索引1,那么客户端节点发送:605 23 03 16 01 08 02 12 60
如果接收成功,节点5回复:585 60 03 16 01 00 00 00 00
4.2 SDO快速读协议
如果要从服务器节点的对象字典中读取数据0xd0d1…,那么客户端需要发送如下请求:
如果读取的数据是1byte,那么如果读取成功的话服务器返回:
如果读取的数据是2byte,那么如果读取成功的话服务器返回:
如果读取的数据是3byte,那么如果读取成功的话服务器返回:
如果读取的数据是4byte,那么如果读取成功的话服务器返回:
如果读取失败,则服务器返回:
例如:
1.从节点5的对象字典中读取1byte数据0xFD,索引0x1400,子索引2,需要发送:605 40 00 14 02 00 00 00 00
如果读取成功,节点5返回:585 4F00 14 02 FD 00 00 00
2.从节点5的对象字典中读取4byte数据0x60120208,索引0x1603,子索引1,需要发送:605 40 03 16 01 00 00 00 00
如果读取成功,节点5返回:585 43 03 16 01 08 02 12 60
4.3 SDO中止协议
如果SDO读写错误,会发送SDO中止错误码,错误码的含义如下: