前面已经记录过can协议,后面开始CanOpen协议栈学习。其实协议栈代码已经看过了,而且已经在开发板上跑过了。这里回过头来,重新看下之前遇到的坑,记录下学习笔记。下面均以标准帧为例
1.CanOpen帧格式
下面是CanOpen协议帧格式,数据大小上和标准Can帧没什么区别,只不过对头部进行了划分。
-
1.头部变化:
- 标准Can帧有11位的ID位,这个ID并不一定是设备地址,它可以表示一类通讯命令。什么样的设备接收处理什么样的命令,设备自己最清楚。这样算的话,最多大概有2048种命令了。
- CanOpen在这个11位的基础上拿出4位,当做功能码,用来表示这个报文的意图。那么每个功能码可以表示128种报文,这里的NodeId也可以理解为设备地址。
-
2.功能码
这个和ModBus中的功能码很类似。每一类功能码表示一类功能集合。 下面简单介绍一下各个功能码的作用
-
EMCY:设备上发生错误时,发出一类报文。设备上每一类报文只会发送一次。当设备恢复后不会在发送报文,不管之前的报文有没有被其它设备接收。
-
PDO:(Process Data Object)过程数据对象,用于处理实时的数据,比如项目开发过程中,需要实时读取某一设备的传感器数据,就可以使用PDO报文。协议栈允许同时能处理4个的PDO消息,这好比CAN发送时的发送邮箱。用于缓存临时来不及发送的数据。
-
SDOtx/SDOrx:service data object,服务处理对象,用来对数据字典的访问。这里就是说的发送SDO对象,用于存放需要访问数据字典的相关信息。
-
NMT:(Network Management Objects) 网络管理对象,用来控制器节点的工作状态,这个工作状态是有一个标准的状态机的。
-
SYNC:(Synchronisation Object (SYNC))同步对象,一个系统中只能有一个sync站点,发送sync的未生产者(producer),其它接收站点为消费者(consumer)。它提供一中同步机制,同步网络上的时钟节拍。防止硬件差异导致各个设备之间的时钟相位偏差,从而导致数据发送接收出现问题。
-
TIME STAMP:(Time Stamp)高精端时间报文,用来同步时间。目前Canopen中还没有实现这部分的处理。如果你们项目需要的话话可以自己添加。stage.c/canDispatch()方法。
-
-
3.功能码对应的cobid
从下图中可以看到PDOtx,PDOrx的cob-id可以达512个,这也就是说可以处理512个不同的消息。
2.SYNC
2.1 sync报文介绍
sync报文相当简答,其报文如下所示:
- cobid:0x80
- rtr:0
- data:没有数据
关于sync报文的格式,可以从下面代码中也能看到数据结构。
UNS8 sendSYNCMessage(CO_Data* d)
{
Message m;
MSG_WAR(0x3001, "sendSYNC ", 0);
m.cob_id = (UNS16)UNS16_LE(*d->COB_ID_Sync); //注意这了取低16位
m.rtr = NOT_A_REQUEST; //RTR=0
m.len = 0;//数据长度为0
return canSend(d->canHandle,&m); //通过can控制器发送出去
}
下面是定义sync cob-id的地方,可以看到SYNC COB ID 为0x40000080,为什么不是0x80.因为30bit是用来确定是否使能sync.此外控制sync是否使能还有别的地方可以修改。所以在控制是否使能sync的时候,可以单独写个函数来控制这一位。
/* index 0x1005 : SYNC COB ID. */
UNS32 TestMaster_obj1005 = 0x40000080; /* 1073741952 */
subindex TestMaster_Index1005[] =
{
{ RW, uint32, sizeof (UNS32), (void*)&TestMaster_obj1005, NULL }
};
2.2从哪里开始触发sync
当状态切换到Pre_operational时,就会触发sync函数,以及sync定时器,并且周期性的发送sync报文。
UNS8 setState(CO_Data* d, e_nodeState newState)
{
if(newState != d->nodeState){
switch( newState ){
case Initialisation:
{
s_state_communication newCommunicationState = {1, 0, 0, 0, 0, 0, 0};
d->nodeState = Initialisation;
switchCommunicationState(d, &newCommunicationState);
/* call user app init callback now. */
/* d->initialisation MUST NOT CALL SetState */
(*d->initialisation)(d);//如果注册了nitialisation回调方法,这里会调用。
}
/* Automatic transition - No break statement ! */
/* Transition from Initialisation to Pre_operational */
/* is automatic as defined in DS301. */
/* App don't have to call SetState(d, Pre_operational) */
case Pre_operational:
{
s_state_communication newCommunicationState = {0, 1, 1, 1, 1, 0, 1};
d->nodeState = Pre_operational;
switchCommunicationState(d, &newCommunicationState);
(*d->preOperational)(d);
}
break;
2.2 如何关闭sync功能
关闭sync功能,有2中方法。目前代码中用的方法1
- 将数据字典中的COB_ID_Sync 数据域30bit位置位0即可。
- Sync_Cycle_Period:将时间周期设置为0。即没有sync功能
void startSYNC(CO_Data* d)
{
if(d->syncTimer != TIMER_NONE){
stopSYNC(d);
}
RegisterSetODentryCallBack(d, 0x1005, 0, &OnCOB_ID_SyncUpdate);
RegisterSetODentryCallBack(d, 0x1006, 0, &OnCOB_ID_SyncUpdate);
if(*d->COB_ID_Sync & 0x40000000ul && *d->Sync_Cycle_Period)
{
d->syncTimer = SetAlarm( //设置定时器,如果开启了定时器,而且循环周期也设置了,这了会设置一个定时器。每隔一段时间触发一次
d,
0 /*No id needed*/,
&SyncAlarm,
US_TO_TIMEVAL(*d->Sync_Cycle_Period),
US_TO_TIMEVAL(*d->Sync_Cycle_Period));
}
}
2.3 sync实验
根据实验可以看到,当master设备使能sync功能后,从设备能源源不断的收到master的sync报文。具体收到的频率要看主设备设置的发射频率。下图左为Master,右为Slave。
3.NMT
3.1 NMT报文
NMT报文很简单,具有下面特点
- 没有数据域
- 功能码为0
- 为数据帧,RTR=0
上面是从官方文档中截取的,可以发现上面的operational对应0x01.这和协议栈中的代码是有出入的。具体以协议栈代码为准。
enum enum_nodeState {
Initialisation = 0x00,
Disconnected = 0x01,
Connecting = 0x02,
Preparing = 0x02,
Stopped = 0x04,
Operational = 0x05,
Pre_operational = 0x7F,
Unknown_state = 0x0F
}
3.2 NMT实验
NMT功能,要注意下面几点。
- CAN网络中,只允许有一个NMT管理员。Master在实验前一定要先设置为master,默认为Master.
- 其它设备在进行NMT控制期间,必须设置为从设备对象.
下面为主设备的代码,可以看到主设备关闭了sync功能,然后才将从设备的网络开启。
int main(int argc,char **argv)
{
usart_config(2, 115200);
delay_init();
led_init();
SYS_DEBUG(("welcome come to weiduwulian!!!"));
if (canOpen(&MasterBoard,&TestMaster_Data)) { //打开CAN协议栈,初始化can硬件设备
setNodeId(&TestMaster_Data, MASTER_NODE_ID); //定义主设备的ID为0x01
set_sync(1); //关闭sync功能,总线就看不到那么多报文了
setState(&TestMaster_Data, Initialisation); //设置站点为初始化状态
setState(&TestMaster_Data, Operational); //设置站点为可操作性状态
masterSendNMTstateChange (&TestMaster_Data, SLAVE_NODE_ID, NMT_Start_Node); //设置从设备进入可操作性状态
SYS_DEBUG(("Starting node %d (%xh) ...",(int)SLAVE_NODE_ID,(int)SLAVE_NODE_ID));
}
下图左侧为Master,右侧为Slave。可以看到右侧Slave接收到NMT报文后才将设备状态设置为operational.