CanOpen协议栈学习笔记2-EMCY、guard&Heartbeat

1.Emergency Object

当设备内部出现错误,设备就会上报且** Error Event**.且每一类Error event只会上报一次。当设备没哟错误产生时,就不会在上报Error Event. 最近几次的error状态会被记录下来,新的error会覆盖旧的error.
在这里插入图片描述
当我们想让设备支持Emergency,就需要至少支持0x00xx和0x10xx,体现在数据字典中就是必须支持这两个数据域。否则协议栈运行时极有可能出现空指针,一去不复返。

  • Emergency报文
cob-idErrorcode highErrorcode Lowerror registerbyte3~byte7
81~FFbit15~bit8bit7~bit0errorRegister值客户自己定义

上面能看到Emergency报文的cobid为81~FF,这个和sync报文的cobid(80)刚看到时,我以为报文会被认错。后来发现sync报文的的数据长度为0。

  • Emergency报文发送
    发送该报文一般通过EMCY_setError方法,其中errRegMask,字面是bitmask,我的理解是用来按位与获取确切的error code。应为error code有很多种。addInfo为额外的信息,这个会被同error code一起存放到error_first_element(第一个error元素)。
    *(d->error_first_element) = errCode | ((UNS32)addInfo << 16)。什么都不必说,精华都在代码中
/*! Sets a new error with code errCode. Also sets corresponding bits in Error register (1001h)
 **                                                                                                 
 **  
 ** @param d
 ** @param errCode Code of the error                                                                                        
 ** @param errRegMask
 ** @param addInfo
 ** @return 1 if error, 0 if successful
 */
UNS8 EMCY_setError(CO_Data* d, UNS16 errCode, UNS8 errRegMask, UNS16 addInfo)
{
	UNS8 index;
	UNS8 errRegister_tmp;
	//设备能记录错误总数,这里我的EMCY_MAX_ERRORS定义为8.
	for (index = 0; index < EMCY_MAX_ERRORS; ++index)
	{
		if (d->error_data[index].errCode == errCode)		/* error already registered *///这个错误之前已经注册过
		{
			if (d->error_data[index].active)//错误还是有效的,设备还没恢复,给他点时间
			{
				MSG_WAR(0x3052, "EMCY message already sent", 0);
				return 0;
			} else d->error_data[index].active = 1;		/* set as active error *///如果没有这个error,激活当前error
			break;
		}
	}
	//检查indexs是否达到上限,如果已经达到,查找是否有error已经解除,如果解除了,则index就不是EMCY_MAX_ERRORS
	if (index == EMCY_MAX_ERRORS)		/* if errCode not already registered */
		for (index = 0; index < EMCY_MAX_ERRORS; ++index) if (d->error_data[index].active == 0) break;	/* find first inactive error */
	//经过上面的判断,如果index还是等于EMCY_MAX_ERRORS,则说明系统错误处理已经满仓了。直接返回,当前消息不处理了。
	if (index == EMCY_MAX_ERRORS)		/* error_data full */
	{
		MSG_ERR(0x3053, "error_data full", 0);
		return 1;
	}
	//下面将error信息,记录到数据字典的error域,
	d->error_data[index].errCode = errCode;
	d->error_data[index].errRegMask = errRegMask;
	d->error_data[index].active = 1;
	
	/* set the new state in the error state machine *///设置当前设备错误状态
	d->error_state = Error_occurred;

	/* set Error Register (1001h) */
	//这里会遍历所有错误,并将所有errRegMask按位或,其实这个操作,也算是记录所有错误标记位。
	for (index = 0, errRegister_tmp = 0; index < EMCY_MAX_ERRORS; ++index)
		if (d->error_data[index].active == 1) errRegister_tmp |= d->error_data[index].errRegMask;
	*d->error_register = errRegister_tmp;//将所有错误保存到error_register准备发送
	
	/* set Pre-defined Error Field (1003h) */
	//这里记录当前error,覆盖之前旧的。开始往前一个回滚
	for (index = d->error_history_size - 1; index > 0; --index)
		*(d->error_first_element + index) = *(d->error_first_element + index - 1);
	*(d->error_first_element) = errCode | ((UNS32)addInfo << 16); //将当前错误放在第一个错误位置
	if(*d->error_number < d->error_history_size) ++(*d->error_number); //增加error数量
	
	/* send EMCY message */
	if (d->CurrentCommunicationState.csEmergency) //如果使能了emcy,下面会放emcy
		return sendEMCY(d, errCode, *d->error_register, NULL);
	else return 1;
}
  • EMCY报文处理(proceedEMCY)
    处理就比较简单了,直接看代码。下面能够看到,这里直接调用post_emcy回调,如果没有实现该方法,那么就直接返回了。所以这里能够看到所有节点都能收到一个节点发送的的emcy报文
void proceedEMCY(CO_Data* d, Message* m)
{
	UNS8 nodeID;
	UNS16 errCode;
	UNS8 errReg;
	
	MSG_WAR(0x3055, "EMCY received. Proceed. ", 0);
  
	/* Test if the size of the EMCY is ok */
	if ( m->len != 8) {
		MSG_ERR(0x1056, "Error size EMCY. CobId  : ", m->cob_id);
		return;
	}
	
	/* post the received EMCY */
	nodeID = m->cob_id & 0x7F;
	errCode = m->Data[0] | ((UNS16)m->Data[1] << 8);
	errReg = m->Data[2];
	(*d->post_emcy)(d, nodeID, errCode, errReg, (const UNS8*)&m->Data[3]);
}

上面接收则最关键的就是post_emcy回调,如果接收到其它设备的EMCY话,需要做一些处理的话。则我们需要实现这个方法。

2.HeartBeat

2.1 初始化

  • ConsumerHeartbeatCount:这个表示当前设备所关心的消息或消息。比如A设备很关心B设备(如果B设备离线了会有很大影响),所以A设备就会将B设备的nodeid加入A设备的监听列表。当A设备收到B设备发送过来的心跳包后,会重新开始计时。如果在规定时间内B设备又发送了心跳包,那么就会认为B设备仍然在线。如此重复进行下去,知道B设备出现异常。当然B发送心跳包的间隔时间,不要小于A设备的等待时间。具体项目大家开发时需要调试下。
    在这里插入图片描述

  • ConsumerHeartbeatEntries:当前设备关心的nodeid列表。记录这些nodeid列表,方便在接收到心跳包时,能够确定是否是我们需要关注的设备/cobid的报文,进而确定设备是否在线。

  • ProducerHeartBeatTime:当前设备上报心跳包的时间。其不用关心谁会接收处理这个事件。

  • ProducerHeartBeatTimer:定时器handler,到了设定的时间,会触发定时器,开始发送心跳包。

下面来学习下心跳包初始化的过程。

void heartbeatInit(CO_Data* d)
{

  UNS8 index; /* Index to scan the table of heartbeat consumers */
  RegisterSetODentryCallBack(d, 0x1017, 0x00, &OnHeartbeatProducerUpdate);

  d->toggle = 0;
 //这里由于设备刚上来就开始监听,其它设备。具体数量和设备由我们来配置数据字典。如果在规定时间内,设备有响应这个心跳,则设备会被置位在网状态,反之就断开状态。
  for( index = (UNS8)0x00; index < *d->ConsumerHeartbeatCount; index++ )
    {
      TIMEVAL time = (UNS16) ( (d->ConsumerHeartbeatEntries[index]) & (UNS32)0x0000FFFF ) ;
      if ( time )
        {
          d->ConsumerHeartBeatTimers[index] = SetAlarm(d, index, &ConsumerHeartbeatAlarm, MS_TO_TIMEVAL(time), 0);
        }
    }

  if ( *d->ProducerHeartBeatTime ) //这里开始准备发送心跳包。
    {
      TIMEVAL time = *d->ProducerHeartBeatTime;
      d->ProducerHeartBeatTimer = SetAlarm(d, 0, &ProducerHeartbeatAlarm, MS_TO_TIMEVAL(time), MS_TO_TIMEVAL(time));
    }
}

2.2 响应心跳包

void proceedNODE_GUARD(CO_Data* d, Message* m )
{
  UNS8 nodeId = (UNS8) GET_NODE_ID((*m));

  if((m->rtr == 1) ) //当master访问slave设备状态时,需要发送远程帧(request)
    /*!
    ** Notice that only the master can have sent this
    ** node guarding request
    */
    {
      /*!
      ** Receiving a NMT NodeGuarding (request of the state by the
      ** master)
      ** Only answer to the NMT NodeGuarding request, the master is
      ** not checked (not implemented)
      */
      if (nodeId == *d->bDeviceNodeId ) //如果是请求当前设备,就会把当前设备的工作状态发送给请求者
        {
          Message msg;
          UNS16 tmp = *d->bDeviceNodeId + 0x700;
          msg.cob_id = UNS16_LE(tmp);
          msg.len = (UNS8)0x01;
          msg.rtr = 0;
          msg.data[0] = d->nodeState;
          if (d->toggle) //这里为了确保2次发送的toggle不一样。
            {
              msg.data[0] |= 0x80 ;
              d->toggle = 0 ;
            }
          else
            d->toggle = 1 ;
          /* send the nodeguard response. */
          MSG_WAR(0x3130, "Sending NMT Nodeguard to master, state: ", d->nodeState);
          canSend(d->canHandle,&msg );
        }

    }else{ /* Not a request CAN */
      /* The state is stored on 7 bit */
      e_nodeState newNodeState = (e_nodeState) ((*m).data[0] & 0x7F);

      MSG_WAR(0x3110, "Received NMT nodeId : ", nodeId);
      
      /*!
      ** Record node response for node guarding service
      */
      d->nodeGuardStatus[nodeId] = *d->LifeTimeFactor;
      //当前设备的记录其它设备发过来的node状态,如果node状态发生改变,则会回到设备post_SlaveStateChange的回调方法。
      if (d->NMTable[nodeId] != newNodeState)
      {
        (*d->post_SlaveStateChange)(d, nodeId, newNodeState);
        /* the slave's state receievd is stored in the NMTable */
        d->NMTable[nodeId] = newNodeState;//将新状态记录下
      }

      /* Boot-Up frame reception */
      if ( d->NMTable[nodeId] == Initialisation) //如果发送节点的状态为Initialisation,则会调用当前设备的post_SlaveBootup
      {
          /*
          ** The device send the boot-up message (Initialisation)
          ** to indicate the master that it is entered in
          ** pre_operational mode
          */
          MSG_WAR(0x3100, "The NMT is a bootup from node : ", nodeId);
          /* call post SlaveBootup with NodeId */
		  (*d->post_SlaveBootup)(d, nodeId);//有需要的话可以实现
      }

      if( d->NMTable[nodeId] != Unknown_state ) {
        UNS8 index, ConsumerHeartBeat_nodeId ;
        for( index = (UNS8)0x00; index < *d->ConsumerHeartbeatCount; index++ )
          {
            ConsumerHeartBeat_nodeId = (UNS8)( ((d->ConsumerHeartbeatEntries[index]) & (UNS32)0x00FF0000) >> (UNS8)16 );
            if ( nodeId == ConsumerHeartBeat_nodeId )//如果是当前设备关心的设备发送过来的心跳包
              {
                TIMEVAL time = ( (d->ConsumerHeartbeatEntries[index]) & (UNS32)0x0000FFFF ) ;
                /* Renew alarm for next heartbeat. */
                DelAlarm(d->ConsumerHeartBeatTimers[index]);//删除当前定时器,并重新启动一个定时器。
                d->ConsumerHeartBeatTimers[index] = SetAlarm(d, index, &ConsumerHeartbeatAlarm, MS_TO_TIMEVAL(time), 0);
              }
          }
      }
    }
}

下图是从设备中数据字典配置的ConsumerHeartbeatEntries,因为从设备起来之后,会启动一个定时器,等待监听设备的心跳包,由于监听设备没有发送心跳包(ProducerHeartBeatTime 为0),所以当前设备就没有及时删除掉定时器,超时后就会设置当前设备为离线状态
在这里插入图片描述
从机设置的等待时间,等待的过程中,如果被等待的设备没有发送心跳包,则设置设备离线。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值