This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

[参考译文] AM2434:EtherNet/IP 组装设置在空闲状态下未调用 CB

Guru**** 2794735 points
请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

https://e2e.ti.com/support/microcontrollers/arm-based-microcontrollers-group/arm-based-microcontrollers/f/arm-based-microcontrollers-forum/1620333/am2434-ethernet-ip-assembly-set-cb-not-called-during-idle-state

器件型号: AM2434

您好:

我在使用汇编集回调时遇到问题。 汇编语言配置为具有 32 位运行/空闲标头、以检测空闲状态。 在运行状态期间、无需发出回调、我可以处理从 PLC 获得的数据 但在 IDLE 状态下、即使切换到运行并返回 IDLE、也会调用一次回调、绝不会再次调用。 当我尝试了解离散 IO 示例的代码时、我没有看到任何要清除的标志或调用空闲处理的特殊方法。 您能向我指出正确的方向、以便在空闲状态下调用回调吗?  

下面是我的装配体创建:

static uint32_t createAsmOutput(EI_API_CIP_NODE_T* pCipNode) {
	uint32_t errCode = EI_API_CIP_eERR_OK;
	uint16_t len;
	EI_API_CIP_SAssemCustomMap_t assembCustomMapCfg = {0};

	errCode = EI_API_CIP_createAssembly(pCipNode, DEVICE_asmCfgParam.asmOutputInst, EI_API_CIP_eAR_GET_AND_SET);
	if (errCode != EI_API_CIP_eERR_OK) {
		OSAL_printf("Error: %s:%d ErrCode:0x%08X\r\n", __FILE__, __LINE__, errCode);
		return errCode;
	}
	errCode = EI_API_CIP_setAssemblyFormat(pCipNode, DEVICE_asmCfgParam.asmOutputInst, ASSEMB_FORMAT_32BITHEADER);
	if (errCode != EI_API_CIP_eERR_OK) {
		OSAL_printf("Error: %s:%d ErrCode:0x%08X\r\n", __FILE__, __LINE__, errCode);
		return errCode;
	}

	assembCustomMapCfg.assemblyMemberLength =DEVICE_asmCfgParam.asmOutputLen;
	assembCustomMapCfg.mappedClassId = DEVICE_PROFILE_CIP_CLASS_ID_SOLENOID;
	assembCustomMapCfg.mappedInstanceId = DEVICE_PROFILE_CIP_INSTANCE_ID_01;
	assembCustomMapCfg.mappedAttributeId = k_INST_ATTR_ID_SOLENOID_VALUE;
	assembCustomMapCfg.fuCustomGet = cbGetCustomMapAsmOut;
	assembCustomMapCfg.fuCustomSet = cbSetCustomMapAsmOut;
	errCode = EI_API_CIP_addAssemblyMemberCustomMapped(pCipNode, DEVICE_asmCfgParam.asmOutputInst, &assembCustomMapCfg);
	if (errCode != EI_API_CIP_eERR_OK) {
		OSAL_printf("Error: %s:%d ErrCode:0x%08X\r\n", __FILE__, __LINE__, errCode);
		return errCode;
	}
	EI_API_CIP_getAssemblySize(pCipNode, DEVICE_asmCfgParam.asmOutputInst, &len);
	if (len != DEVICE_asmCfgParam.asmOutputLen) {
		OSAL_printf("Wrong assembly output length!\r\n");
		return -1;
	}

	return errCode;
}

这里是对 set 回调的处理:

static EI_API_CIP_EAssemb_Return_Code_t cbSetCustomMapAsmOut(EI_API_CIP_SAssemMapData_t* pAttrMapData, const EI_API_CIP_STransferBuffer_t* pConsumeBuffer) {
	EI_API_CIP_EAssemb_Return_Code_t retValue = ASSEMB_SERVICE_NO_RESPONSE;
	uint16_t dataLen;

	OSAL_printf("ASM data set\r\n");
	if (DEVICE_PROFILE_CIP_CLASS_ID_SOLENOID != pAttrMapData->classId) {
		return retValue;
	}

	if (pAttrMapData->instanceId > 0) {
		if (k_INST_ATTR_ID_SOLENOID_VALUE == pAttrMapData->attributeId)
		{
			OSAL_printf("ASM data set\r\n");
			if(pConsumeBuffer->p8uDataBuf[0] == 0) //Idle state
			{
				OSAL_printf("Idle Detected\r\n");
				if(ConnectionIdle == false)
				{
					// change state from run to idle
					DEVICE_receiveConnectionEvent(pAttrMapData->attributeId, DEVICE_CONNECTION_DELETED);
				}
				ConnectionIdle = true;
				retValue = ASSEMB_SERVICE_RESPONSE_OK;
			}
			else
			{
				if(ConnectionIdle == true)
				{
					// change state from idle to run
					DEVICE_receiveConnectionEvent(pAttrMapData->attributeId, DEVICE_CONNECTION_ESTABLISHED_IO);
				}
				ConnectionIdle = false;

				dataLen = pConsumeBuffer->u16uMaxData;
				if (dataLen > MAX_SOLENOID_BYTES) {
					dataLen = MAX_SOLENOID_BYTES;
				}
				OSAL_MEMORY_memcpy(ab_BitMapSolenoidValue, pConsumeBuffer->p8uDataBuf + 4, dataLen); // +4 for header
				retValue = ASSEMB_SERVICE_RESPONSE_OK;
			}
		}
	}
	return retValue;
}

device_receiveConnectionEvent 仅更改状态机箱、以确定应该将哪些值用于输出到运行或空闲。   

感谢你的帮助。

此致、  

1 月

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    您好、Jan、

    初始化函数“createAsmOutput"似乎“似乎已正确实现。 您设置的回调实现也基本正确、尽管有一个小问题:您应该从“pConsumeBuffer->u16uActData"成员“成员读取数据长度、而不是从“pConsumeBuffer->u16uMaxData"读取“读取数据长度、它只指示 pConsumeBuffer->p8uDataBuf 中的最大可用大小。

    我希望在数据包到达后、您可以正确接收 Idle 和 Run 标头。 我无法重现您在分立式 IO 示例中描述的场景。

    由于 Idle 命令有一条 printf 消息、因此我建议为 Run 命令添加一条类似的 printf 语句、以验证这两条命令都是否正确接收。

    该问题可能出在您的状态机实现中、特别是使用全局标志 ConnectionIdle 或函数 device_receiveConnectionEvent 时、您能否验证该情况?
    此致、
    Pourya

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    您好 Pourya、

    未收到 RUN 模式。 对于空闲、我的观察结果是、只有在 PLC 处于空闲状态时从可避免的故障切换到建立的连接时才会调用回调。  

    对于我的状态机、我只能进行简单的输出切换。 我是否应该调用任何函数来让 API 了解当前状态?  

    这是我实现的状态机:

    static IMI_DEVICE_SmStates_t processState(IMI_DEVICE_SmStates_t state, IMI_DEVICE_ConnectionEvent_t event) {
    	if (event == IMI_DEVICE_CONNECTION_ESTABLISHED_IO) {
    		return IMI_DEVICE_SM_RUN_IO;
    	}
    
    	if (event == IMI_DEVICE_CONNECTION_ESTABLISHED_EXP) {
    		if (state == IMI_DEVICE_SM_RUN_IO) {
    			return IMI_DEVICE_SM_IDLE;
    		}
    		return IMI_DEVICE_SM_RUN_EXP;
    	}
    
    	if (event == IMI_DEVICE_CONNECTION_DELETED) {
    		return IMI_DEVICE_SM_IDLE;
    	}
    
    	if (event == IMI_DEVICE_CONNECTION_TIMEDOUT) {
    		return IMI_DEVICE_SM_RECOVERABLEFAULT;
    	}
    
    	return IMI_DEVICE_SM_UNRECOVERABLEFAULT;
    }
    
    DEVICE_SmStates_t IMI_DEVICE_getDeviceState(void) {
    	return connPathIDstate[0];
    }
    
    void DEVICE_receiveConnectionEvent(uint16_t connPathID, IMI_DEVICE_ConnectionEvent_t connectionEvent) {
    	switch (connPathID) {
    		case 1:
    			connPathIDstate[0] = processState(connPathIDstate[0], connectionEvent);
    			break;
    		case 2:
    			connPathIDstate[1] = processState(connPathIDstate[1], connectionEvent);
    			break;
    		case 3:
    			connPathIDstate[2] = processState(connPathIDstate[2], connectionEvent);
    			break;
    		case 4:
    			connPathIDstate[3] = processState(connPathIDstate[3], connectionEvent);
    			break;
    		default:
    			break;
    	}
    }
    
    void DEVICE_taskRun(EI_API_CIP_NODE_T* pCipNode) {
    	stateNow = IMI_DEVICE_getDeviceState();
    	switch (stateNow) {
    		case DEVICE_SM_RECOVERABLEFAULT:
    			for (i = 0; i < MAX_SOLENOID_BYTES * 8; i++) {
    				DEVICE_getABBitMapBit(i, (bool*)&val, ab_BitMapFaultAction);
    				if (val) {
    					DEVICE_getABBitMapBit(i, (bool*)&val, ab_BitMapFaultValue);
    					DEVICE_setABBitMapBit(i, val, ab_BitMapSolenoidValue);
    				}
    			}
    			WriteDataToValves(ab_BitMapSolenoidValue, MAX_SOLENOID_BYTES);
    			break;
    		case DEVICE_SM_IDLE:
    			for (i = 0; i < MAX_SOLENOID_BYTES * 8; i++) {
    				DEVICE_getABBitMapBit(i, (bool*)&val, ab_BitMapIdleAction);
    				if (val) {
    					DEVICE_getABBitMapBit(i, (bool*)&val, ab_BitMapIdleValue);
    					DEVICE_setABBitMapBit(i, val, ab_BitMapSolenoidValue);
    				}
    			}
    			WriteDataToValves(ab_BitMapSolenoidValue, MAX_SOLENOID_BYTES);
    			break;
    		case DEVICE_SM_READY:
    			WriteDataToValves(ab_BitMapSolenoidValue, MAX_SOLENOID_BYTES);
    			break;
    		case DEVICE_SM_RUN_IO:
    			WriteDataToValves(ab_BitMapSolenoidValue, MAX_SOLENOID_BYTES);
    			break;
    		case DEVICE_SM_RUN_EXP:
    			WriteDataToValves(ab_BitMapSolenoidValue, MAX_SOLENOID_BYTES);
    			break;
    		default:
    			for (i = 0; i < MAX_SOLENOID_BYTES * 8; i++) {
    				DEVICE_getABBitMapBit(i, (bool*)&val, ab_BitMapFaultAction);
    				if (val) {
    					DEVICE_getABBitMapBit(i, (bool*)&val, ab_BitMapFaultValue);
    					DEVICE_setABBitMapBit(i, val, ab_BitMapSolenoidValue);
    				}
    			}
    			WriteDataToValves(ab_BitMapSolenoidValue, MAX_SOLENOID_BYTES);
    			break;
    	}
    }

    感谢你的帮助。

    此致、  

    1 月

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    您好、Jan、

    在检查状态机之前、让我们首先关注回调功能。

    来自回调的 printf 消息是否确认已正确接收空闲和运行状态?

    关于您的状态机实现、我注意到在空闲状态期间、您调用的是:
    “device_receiveConnectionEvent (pAttrMapData->attributeId、device_connection_deleted);“

    但是、我想问“pAttrMapData->attributeId"是否“是否是此处要使用的正确字段。 观察您的状态机设计、您似乎应该改为通过实例 ID。 此外、在这种情况下、只有实例 ID:1 才有效、因为状态机 在运行时仅检查 connPathIDstate[0](基于 IMI_DEVICE_getDeviceState () 的返回值)。

    在继续之前、请确认运行和空闲状态的 printf 语句是否按照我的预期在回调中正确显示。

    此致、
    Pourya
  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    您好 Pourya、

    我的 prinf 显示在回调中没有收到 Idle 状态(在检查任何条件之前,它永远不会根据 printf 进入回调)  

    接收到运行状态、我可以读取 PLC 发送的成功读取数据

    感谢你的帮助。

    此致、  

    1 月

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    您好、Jan、

    在这种情况下、我需要其他信息来正确诊断问题。 请提供:

    1. Wireshark 日志捕获 PLC 与设备之间的通信(使用网络分路器捕获正在交换的数据包)

    2. O→T 和 T→O 连接的装配体实例 ID

    3. 如果可用、您的设备的 EDS 文件

    利用这些信息、我们将更好地了解和解决潜在问题。

    此致、
    Pourya
  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    您好 Pourya、

    以下是 Wireshark 日志的屏幕截图

    运行模式:  

    空闲模式:

     

    我的新 printf 独立于状态:  

    static EI_API_CIP_EAssemb_Return_Code_t cbSetCustomMapAsmOut(EI_API_CIP_SAssemMapData_t* pAttrMapData, const EI_API_CIP_STransferBuffer_t* pConsumeBuffer) {
    	EI_API_CIP_EAssemb_Return_Code_t retValue = ASSEMB_SERVICE_NO_RESPONSE;
    	uint16_t dataLen;
    
    	OSAL_printf("ASM data set\r\n");
    	
    	if (DEVICE_PROFILE_CIP_CLASS_ID_SOLENOID != pAttrMapData->classId) {
    		return retValue;
    	}
    
    	if (pAttrMapData->instanceId > 0) {
    		if (k_INST_ATTR_ID_SOLENOID_VALUE == pAttrMapData->attributeId)
    		{
    			OSAL_printf("ASM data set ID = 0x%X, Idle data 0x%X\r\n", pAttrMapData->instanceId, pConsumeBuffer->p8uDataBuf[0]);
    			if(pConsumeBuffer->p8uDataBuf[0] == 0) //Idle state
    			{
    				OSAL_printf("Idle Detected\r\n");
    				if(ConnectionIdle == false)
    				{
    					// change state from run to idle
    					IMI_DEVICE_receiveConnectionEvent(pAttrMapData->attributeId, IMI_DEVICE_CONNECTION_DELETED);
    				}
    				ConnectionIdle = true;
    				retValue = ASSEMB_SERVICE_RESPONSE_OK;
    			}
    			else
    			{
    				if(ConnectionIdle == true)
    				{
    					// change state from idle to run
    					IMI_DEVICE_receiveConnectionEvent(pAttrMapData->attributeId, IMI_DEVICE_CONNECTION_ESTABLISHED_IO);
    				}
    				ConnectionIdle = false;
    
    				dataLen = pConsumeBuffer->u16uActData;
    				if (dataLen > MAX_SOLENOID_BYTES) {
    					dataLen = MAX_SOLENOID_BYTES;
    				}
    				OSAL_MEMORY_memcpy(ab_BitMapSolenoidValue, pConsumeBuffer->p8uDataBuf + 4, dataLen); // +4 for header
    				retValue = ASSEMB_SERVICE_RESPONSE_OK;
    			}
    		}
    	}
    	return retValue;
    }
    e2e.ti.com/.../1157.IdleMode.zip

    感谢你的帮助。

    此致、  

    1 月

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    您好 Pourya、

    以下是 Wireshark 日志的屏幕截图

    运行模式:  

    空闲模式:

     

    我的新 printf 独立于状态:  

    static EI_API_CIP_EAssemb_Return_Code_t cbSetCustomMapAsmOut(EI_API_CIP_SAssemMapData_t* pAttrMapData, const EI_API_CIP_STransferBuffer_t* pConsumeBuffer) {
    	EI_API_CIP_EAssemb_Return_Code_t retValue = ASSEMB_SERVICE_NO_RESPONSE;
    	uint16_t dataLen;
    
    	OSAL_printf("ASM data set\r\n");
    	
    	if (DEVICE_PROFILE_CIP_CLASS_ID_SOLENOID != pAttrMapData->classId) {
    		return retValue;
    	}
    
    	if (pAttrMapData->instanceId > 0) {
    		if (k_INST_ATTR_ID_SOLENOID_VALUE == pAttrMapData->attributeId)
    		{
    			OSAL_printf("ASM data set ID = 0x%X, Idle data 0x%X\r\n", pAttrMapData->instanceId, pConsumeBuffer->p8uDataBuf[0]);
    			if(pConsumeBuffer->p8uDataBuf[0] == 0) //Idle state
    			{
    				OSAL_printf("Idle Detected\r\n");
    				if(ConnectionIdle == false)
    				{
    					// change state from run to idle
    					IMI_DEVICE_receiveConnectionEvent(pAttrMapData->attributeId, IMI_DEVICE_CONNECTION_DELETED);
    				}
    				ConnectionIdle = true;
    				retValue = ASSEMB_SERVICE_RESPONSE_OK;
    			}
    			else
    			{
    				if(ConnectionIdle == true)
    				{
    					// change state from idle to run
    					IMI_DEVICE_receiveConnectionEvent(pAttrMapData->attributeId, IMI_DEVICE_CONNECTION_ESTABLISHED_IO);
    				}
    				ConnectionIdle = false;
    
    				dataLen = pConsumeBuffer->u16uActData;
    				if (dataLen > MAX_SOLENOID_BYTES) {
    					dataLen = MAX_SOLENOID_BYTES;
    				}
    				OSAL_MEMORY_memcpy(ab_BitMapSolenoidValue, pConsumeBuffer->p8uDataBuf + 4, dataLen); // +4 for header
    				retValue = ASSEMB_SERVICE_RESPONSE_OK;
    			}
    		}
    	}
    	return retValue;
    }
    e2e.ti.com/.../6177.IdleMode.zip

    编辑:已添加 EDS 文件

    感谢你的帮助。

    此致、  

    1 月

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    您好、Jan、

    请确保在 Wireshark 中捕获 ForwardOpen 的连接请求(可能先开始 Wireshark 捕获,然后重置设备以确保捕获所有内容)、因为这包含我诊断问题所需的关键信息。

    您能否重新上传 Wireshark 日志?

    此致、
    Pourya

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    您好 Pourya、

    抱歉、数据不完整、这里有新日志。

     e2e.ti.com/.../RunMode.zip 

    感谢你的帮助。

    此致、  

    1 月

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    您好、Jan、

    我相信我已经找到了问题。 您的 PLC 似乎没有增加数据包之间的 CIP 序列计数、这就是为什么数据包不被视为新数据包并因此被丢弃的原因。

    PLC 中必须有一些设置、以便将其配置为在数据包之间增加 CIP 序列计数。 您也可以使用 Hilscher 工具发送您的 IO 数据、以确认回调已正确触发。

    此致、
    Pourya

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    您好 Pourya、

    你是对的, 在我使用 Hilsher 工具后,我成功地测试了它。 但是、是否有办法从堆栈中获取有关已丢弃数据包的信息以检测 PLC 的这种状态?

    感谢你的帮助。

    此致、  

    1 月

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    您好、Jan、

    由于堆栈中的数据重复、因此不会保留有关丢弃的数据包的记录。

    此致、
    Pourya

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    您好 Pourya、

    感谢您提供信息。

    此致、  

    1 月