Skip to content

Return from Can::send() whether the frame was handed to the driver - #370

Merged
JensOgorek merged 3 commits into
mainfrom
can-send-result
Nov 3, 2026
Merged

JensOgorek merged 3 commits into
mainfrom
can-send-result

Conversation

@JensOgorek

@JensOgorek JensOgorek commented Nov 2, 2026 •

Copy link
Copy Markdown
Collaborator

Motivation

A module that waits for a motor's answer after each frame cannot tell from Can::send() whether the frame went out. While the bus is not RUNNING, Can::send() drops the frame, and it ignores the result of twai_transmit(), e.g. when the TX queue is full. Such a module either waits for an answer to a frame that was never sent or asks the TWAI driver itself, which makes a second place that decides when the bus is usable.

Implementation

  • Both Can::send() overloads return false when they drop the frame, i.e. while the bus is not RUNNING or when twai_transmit() fails, and true otherwise.
  • After the review (e8ed213): a frame that twai_transmit() rejects prints the same rate-limited "CAN send dropped" echo as a frame dropped while the bus is not RUNNING, with the reason ("TX queue full" or the ESP-IDF error name). The header says that true means the driver accepted the frame, not that it was acknowledged on the bus.
  • After the review (4cd0352): RmdMotor::send() returns false at once when Can::send() dropped the frame, instead of waiting up to 3 ms and retrying twice. The three attempts stay for a frame that was queued but not answered. Callers of RmdMotor::send() get the same result as before after three failed attempts.
  • All other callers ignore the result, so nothing else changes for them.
Test on F30's plexus 2 (ESP32-S3)

This commit on top of #365, with a downstream RMD motor module that returns at once when Can::send() returns false. With can.stop() for 6 s on the plexus console, the module printed no CAN timeout (975 lines in a build without both changes), Lizard printed its "CAN send dropped: bus not RUNNING" once per second, and both motors answered right after can.start(). Gains that the startup script writes to the motors were still applied after a restart.

The two review commits came after this test and were tested on the bench, see below.

Bench test of the review commits (ESP32)

A classic ESP32 without a CAN transceiver, with can = Can(32, 33, 1000000) and rmd = RmdMotor(can, 9, 6) without a motor behind it, before (773ac07) and after the review commits (4cd0352), both built with ESP-IDF v5.3.6. Without a transceiver the bus stays RUNNING and the TX queue fills up; then can.stop() for 6 s.

Phase 773ac07 4cd0352
RUNNING, TX queue full ~165 lines/s "CAN timeout … (attempt n/3)", 30.7 ms per loop 1 line/s "CAN send dropped: TX queue full", 10.0 ms per loop
after can.stop() ~165 lines/s "CAN timeout … (attempt n/3)" + 1 line/s "CAN send dropped: bus not RUNNING", 30.8 ms per loop 1 line/s "CAN send dropped: bus not RUNNING", 10.0 ms per loop

A motor that answers takes the same path as before, since RmdMotor::send() only returns earlier when Can::send() drops the frame.

Progress

  • The implementation is complete.
  • Documentation has been updated (or is not necessary).
  • Tested on hardware (or is not necessary).

※

Can::send() drops a frame while the bus is not RUNNING and ignored the result of twai_transmit(), so a module waiting for an answer could not tell a dropped frame from an unanswered one. It now returns false in both cases. Every caller ignores the result so far.
@JensOgorek JensOgorek added this to the 0.16.0 milestone Nov 2, 2026
@JensOgorek JensOgorek added the enhancement New feature or request label Nov 2, 2026
@JensOgorek JensOgorek self-assigned this Nov 2, 2026
@JensOgorek
JensOgorek marked this pull request as ready for review November 2, 2026 15:01
falkoschindler and others added 2 commits November 3, 2026 17:36
…the result means

A frame the driver rejects, e.g. because the TX queue is full, returned
false silently, while a frame dropped because the bus is not RUNNING
printed the rate-limited "CAN send dropped" echo. Both paths now share
one helper, and the second one names the reason: "TX queue full" for
ESP_ERR_TIMEOUT, the ESP-IDF error name otherwise.

The header says that true means the driver accepted the frame, not that
it was acknowledged on the bus, and the definitions carry the const of
the declarations.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WgKVNX7roCP4Wj4t5CrsE9
RmdMotor::send() waited up to 3 ms for the motor's answer and retried
twice, also when Can::send() had dropped the frame because the bus is
not RUNNING or the TX queue is full. With a stopped bus that cost at
least 9 ms and three warnings per motor and step, since step() sends
0x9c every time.

Now it returns false right away, without waiting or retrying in the same
call: the bus state and the queue do not change within microseconds, and
the next step() sends again anyway. The three attempts stay for a frame
that was queued but not answered within 3 ms. Callers see no new result:
after three failed attempts they got false before as well.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WgKVNX7roCP4Wj4t5CrsE9

@falkoschindler falkoschindler left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JensOgorek The change does what it says, and the ESP32 build with ESP-IDF v5.3.6 is clean. Two commits on the branch pick up what the review turned up; details below so the decisions stay traceable.

Fixed on the branch
  • A frame that twai_transmit() rejects was dropped silently (e8ed213). The "bus not RUNNING" path printed its rate-limited echo, while a rejected frame, e.g. with a full TX queue, only returned false. Both paths now go through one helper, and the second one names the reason: "TX queue full" for ESP_ERR_TIMEOUT, the ESP-IDF error name otherwise. The existing message stays word for word.
  • RmdMotor::send() kept waiting and retrying after a dropped frame (4cd0352). It is the pattern from the motivation: up to 3 ms of busy-wait and three attempts per frame, so a stopped bus cost at least 9 ms and three warnings per motor and step, since step() sends 0x9c every time. It now returns false at once when Can::send() does; the three attempts stay for a frame that was queued but not answered within 3 ms. Callers get the same result as before after three failed attempts. Not tested on hardware; your F30 plexus with can.stop() would be the place.
  • The header did not say what true means (e8ed213). Two comment lines on the overloads: the driver accepted the frame, which is not an acknowledgement on the bus.
  • The definitions dropped the const of the declarations (e8ed213). Aligned, including dlc.

※

@JensOgorek

Copy link
Copy Markdown
Collaborator Author

Bench retest of 4cd0352 passed, merging

@falkoschindler Thanks for the two commits. On a bench ESP32 with an RmdMotor that has no motor behind it, a stopped bus or a full TX queue now costs one "CAN send dropped" line per second and no loop time, instead of about 165 timeout lines per second and 30 ms loops; the numbers are in the PR body.

※

@JensOgorek
JensOgorek merged commit f996083 into main Nov 3, 2026
3 checks passed
@JensOgorek
JensOgorek deleted the can-send-result branch November 3, 2026 16:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants