fix: CO_EM_RPDO_TIME_OUT incorrectly cleared when multiple RPDOs monitored
CO_EM_RPDO_TIME_OUT is a single shared error bit for all RPDO instances. CO_RPDO_process() called CO_errorReset() for this bit the moment any one RPDO recovered from timeout, even if other RPDOs were still timed out. Scenario that exposes the bug: node-2 and node-3 are RPDO producers with deadline monitoring. Both go pre-operational -> CO_EM_RPDO_TIME_OUT set -> error register 0x10. node-2 comes back -> CO_RPDO_process() calls CO_errorReset() -> CO_EM_RPDO_TIME_OUT cleared -> error register 0x00. node-3 is still timed out: the error register is now incorrect. Fix: remove CO_errorReset() from CO_RPDO_process(). Instead, add a post-loop check in CO_process_RPDO() (CANopen.c) that iterates all RPDO instances. CO_errorReset() is only called when none of them remains in timeout (timeoutTimer > timeoutTime_us). CO_process_RPDO() has access to the full co->RPDO[] array and is therefore the correct place to make this cross-RPDO decision. CO_errorReset() is a no-op when the error bit is already clear, so calling it every cycle when no timeout is active is safe and efficient.
This commit is contained in:
parent
bc79a5c87d
commit
a36ab57e4e
2 changed files with 22 additions and 3 deletions
|
|
@ -890,9 +890,11 @@ CO_RPDO_process(CO_RPDO_t* RPDO,
|
|||
#if ((CO_CONFIG_PDO)&CO_CONFIG_RPDO_TIMERS_ENABLE) != 0
|
||||
if (RPDO->timeoutTime_us > 0U) {
|
||||
if (rpdoReceived) {
|
||||
if (RPDO->timeoutTimer > RPDO->timeoutTime_us) {
|
||||
CO_errorReset(PDO->em, CO_EM_RPDO_TIME_OUT, RPDO->timeoutTimer);
|
||||
}
|
||||
/* Do NOT call CO_errorReset here. CO_EM_RPDO_TIME_OUT is a
|
||||
* single shared bit for all RPDOs. Resetting it when one RPDO
|
||||
* recovers would clear the error even if other RPDOs are still
|
||||
* timed out. The reset is handled in CO_process_RPDO() after
|
||||
* all RPDOs are processed, only when none remain in timeout. */
|
||||
/* enable monitoring */
|
||||
RPDO->timeoutTimer = 1;
|
||||
} else if ((RPDO->timeoutTimer > 0U) && (RPDO->timeoutTimer < RPDO->timeoutTime_us)) {
|
||||
|
|
|
|||
17
CANopen.c
17
CANopen.c
|
|
@ -1447,6 +1447,23 @@ CO_process_RPDO(CO_t* co, bool_t syncWas, uint32_t timeDifference_us, uint32_t*
|
|||
#endif
|
||||
NMTisOperational, syncWas);
|
||||
}
|
||||
|
||||
#if ((CO_CONFIG_PDO)&CO_CONFIG_RPDO_TIMERS_ENABLE) != 0
|
||||
/* CO_EM_RPDO_TIME_OUT is a single shared bit for all RPDOs. Only reset it
|
||||
* after processing all RPDOs, and only when none remain in timeout.
|
||||
* CO_errorReset() is a no-op when the error bit is already clear. */
|
||||
bool_t anyTimeout = false;
|
||||
for (uint16_t i = 0; i < CO_GET_CNT(RPDO); i++) {
|
||||
CO_RPDO_t* rpdo = &co->RPDO[i];
|
||||
if (rpdo->timeoutTime_us > 0U && rpdo->timeoutTimer > rpdo->timeoutTime_us) {
|
||||
anyTimeout = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!anyTimeout) {
|
||||
CO_errorReset(co->em, CO_EM_RPDO_TIME_OUT, 0);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue