1
0
Fork 0

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:
Tristen Pierson 2026-05-31 16:34:31 -04:00 committed by Janez
parent bc79a5c87d
commit a36ab57e4e
2 changed files with 22 additions and 3 deletions

View file

@ -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)) {

View file

@ -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