Skip to content

Commit d2bd0fc

Browse files
committed
✨ feat(yfr4esc): add character match interrupt for efficient COBS parsing
1 parent 187b26c commit d2bd0fc

File tree

2 files changed

+48
-38
lines changed

2 files changed

+48
-38
lines changed

src/drivers/motor/yfr4esc/YFR4escDriver.cpp

Lines changed: 38 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -16,22 +16,26 @@ namespace xbot::driver::motor {
1616

1717
using namespace yfr4esc;
1818

19-
// Event when the RX DMA buffer wrapped (full) and was re-armed.
20-
static constexpr uint32_t EVT_RX_DMA_WRAP = 1;
19+
// Events signaled to the processing thread.
20+
static constexpr eventmask_t EVT_RX_DMA_WRAP = (1U << 0); // DMA buffer full/wrap
21+
static constexpr eventmask_t EVT_RX_CHAR_MATCH = (1U << 1); // Character match (0x0) received
2122

22-
bool YFR4escDriver::SetUART(UARTDriver *uart, uint32_t baudrate) {
23+
bool YFR4escDriver::SetUART(UARTDriver* uart, uint32_t baudrate) {
2324
chDbgAssert(!IsStarted(), "Only set UART when the driver is stopped");
2425
chDbgAssert(uart != nullptr, "need to provide a driver");
2526
if (IsStarted()) return false;
2627

2728
uart_ = uart;
2829
uart_config_.speed = baudrate;
2930
uart_config_.context = this;
31+
// Character-Match interrupt to trigger on COBS end marker (0x0).
32+
uart_config_.cr2 = ('\0' << USART_CR2_ADD_Pos); // Just for intent character match
33+
uart_config_.cr1 |= USART_CR1_CMIE;
3034

3135
return true;
3236
}
3337

34-
void YFR4escDriver::ProcessDecodedPacket(uint8_t *packet, size_t len) {
38+
void YFR4escDriver::ProcessDecodedPacket(uint8_t* packet, size_t len) {
3539
// Packages have to be at least 1 byte of type + 1 byte of data + 2 bytes of CRC
3640
if (len < 4) {
3741
ULOGT_EVERY_MS(WARNING, 500, "Decoded packet too short (%zu bytes). Dropping!", len);
@@ -89,10 +93,10 @@ void YFR4escDriver::ProcessDecodedPacket(uint8_t *packet, size_t len) {
8993
}
9094
}
9195

92-
void YFR4escDriver::ProcessRxBytes(const volatile uint8_t *data, size_t len) {
96+
void YFR4escDriver::ProcessRxBytes(const volatile uint8_t* data, size_t len) {
9397
if (len == 0) return;
9498
if (IsRawMode()) {
95-
RawDataOutput(const_cast<uint8_t *>(data), len);
99+
RawDataOutput(const_cast<uint8_t*>(data), len);
96100
return;
97101
}
98102
for (size_t i = 0; i < len; ++i) {
@@ -123,11 +127,11 @@ void YFR4escDriver::SetDuty(float duty) {
123127

124128
void YFR4escDriver::SendControl(float duty) {
125129
ControlPacket cp{.message_type = MessageType::CONTROL, .duty_cycle = static_cast<double>(duty), .crc = 0};
126-
const uint8_t *payload = reinterpret_cast<const uint8_t *>(&cp);
130+
const uint8_t* payload = reinterpret_cast<const uint8_t*>(&cp);
127131
cp.crc = yfr4esc_crc::crc16_ccitt_false(payload, sizeof(ControlPacket) - sizeof(cp.crc));
128132

129133
chMtxLock(&mutex_); // protect shared tx_buffer_ and send
130-
size_t len = cobs_encode(reinterpret_cast<const uint8_t *>(&cp), sizeof(cp), tx_buffer_);
134+
size_t len = cobs_encode(reinterpret_cast<const uint8_t*>(&cp), sizeof(cp), tx_buffer_);
131135
if (len + 1 > TX_BUFFER_SIZE) {
132136
ULOGT_EVERY_MS(WARNING, 1000, "TX control frame too large: len=%u", (unsigned)len);
133137
chMtxUnlock(&mutex_);
@@ -147,11 +151,11 @@ void YFR4escDriver::SendSettings() {
147151
.crc = 0};
148152

149153
// Compute CRC over message_type + data only (exclude crc), store as-is (LE on wire)
150-
const uint8_t *payload = reinterpret_cast<const uint8_t *>(&sp);
154+
const uint8_t* payload = reinterpret_cast<const uint8_t*>(&sp);
151155
sp.crc = yfr4esc_crc::crc16_ccitt_false(payload, sizeof(SettingsPacket) - sizeof(sp.crc));
152156

153157
chMtxLock(&mutex_); // protect shared tx_buffer_ and send
154-
size_t len = cobs_encode(reinterpret_cast<const uint8_t *>(&sp), sizeof(SettingsPacket), tx_buffer_);
158+
size_t len = cobs_encode(reinterpret_cast<const uint8_t*>(&sp), sizeof(SettingsPacket), tx_buffer_);
155159
if (len + 1 > TX_BUFFER_SIZE) {
156160
ULOGT_EVERY_MS(WARNING, 1000, "TX settings frame too large: len=%u", (unsigned)len);
157161
chMtxUnlock(&mutex_);
@@ -162,7 +166,7 @@ void YFR4escDriver::SendSettings() {
162166
chMtxUnlock(&mutex_);
163167
}
164168

165-
void YFR4escDriver::RawDataInput(uint8_t *data, size_t size) {
169+
void YFR4escDriver::RawDataInput(uint8_t* data, size_t size) {
166170
if (!IsRawMode() || !IsStarted()) return;
167171
chMtxLock(&mutex_);
168172
size_t len = size > TX_BUFFER_SIZE ? TX_BUFFER_SIZE : size;
@@ -175,17 +179,24 @@ bool YFR4escDriver::Start() {
175179
chDbgAssert(!IsStarted(), "don't start the driver twice");
176180
if (IsStarted()) return false;
177181

178-
// Configure RX callback
179-
uart_config_.rxend_cb = [](UARTDriver *uartp) {
182+
// Configure RX buffer-full (DMA wrap) callback
183+
uart_config_.rxend_cb = [](UARTDriver* uartp) {
180184
chSysLockFromISR();
181-
YFR4escDriver *instance = reinterpret_cast<const UARTConfigEx *>(uartp->config)->context;
185+
YFR4escDriver* instance = reinterpret_cast<const UARTConfigEx*>(uartp->config)->context;
182186
chDbgAssert(instance != nullptr, "instance cannot be null!");
183187
// Buffer reached full length. Re-arm DMA immediately to minimize RX gap, then signal the thread.
184-
uartStartReceiveI(uartp, DMA_RX_BUFFER_SIZE, const_cast<uint8_t *>(instance->dma_rx_buffer_));
185-
// Tell the thread a wrap occurred; it will handle the final tail and reset rx_seen_len_.
186-
if (instance->processing_thread_) {
187-
chEvtSignalI(instance->processing_thread_, EVT_RX_DMA_WRAP);
188-
}
188+
uartStartReceiveI(uartp, DMA_RX_BUFFER_SIZE, const_cast<uint8_t*>(instance->dma_rx_buffer_));
189+
if (instance->processing_thread_) chEvtSignalI(instance->processing_thread_, EVT_RX_DMA_WRAP);
190+
chSysUnlockFromISR();
191+
};
192+
193+
// Configure Character-Match callback for 0x00 delimiter. This ISR should
194+
// NOT process bytes; it just wakes the thread so it reads NDTR deltas.
195+
uart_config_.rx_cm_cb = [](UARTDriver* uartp) {
196+
chSysLockFromISR();
197+
YFR4escDriver* instance = reinterpret_cast<const UARTConfigEx*>(uartp->config)->context;
198+
chDbgAssert(instance != nullptr, "instance cannot be null!");
199+
if (instance->processing_thread_) chEvtSignalI(instance->processing_thread_, EVT_RX_CHAR_MATCH);
189200
chSysUnlockFromISR();
190201
};
191202

@@ -202,7 +213,7 @@ bool YFR4escDriver::Start() {
202213

203214
// Arm RX
204215
rx_seen_len_ = 0;
205-
uartStartReceive(uart_, DMA_RX_BUFFER_SIZE, const_cast<uint8_t *>(dma_rx_buffer_));
216+
uartStartReceive(uart_, DMA_RX_BUFFER_SIZE, const_cast<uint8_t*>(dma_rx_buffer_));
206217

207218
// Send an initial settings packet
208219
SendSettings();
@@ -211,17 +222,16 @@ bool YFR4escDriver::Start() {
211222
}
212223

213224
void YFR4escDriver::threadFunc() {
214-
systime_t last_heartbeat = 0;
225+
systime_t last_heartbeat = chVTGetSystemTimeX();
215226

216227
while (IsStarted()) {
217-
uint32_t events;
228+
eventmask_t events;
218229

219-
// YFRev4-ESC always send a status packet every 20ms.
220-
// So, read with timeout, instead of waiting for a buffer full ISR call.
221-
events = chEvtWaitOneTimeout(EVT_RX_DMA_WRAP, TIME_MS2I(10));
230+
// Wait for either DMA wrap or char-match; timeout keeps heartbeat running.
231+
events = chEvtWaitAnyTimeout(EVT_RX_DMA_WRAP | EVT_RX_CHAR_MATCH, HEARTBEAT_INTERVAL);
222232
(void)events;
223233

224-
// Process newly arrived bytes immediately using NDTR deltas; handle wrap if the buffer was refilled.
234+
// Process newly arrived bytes using NDTR deltas; handle wrap if the buffer was refilled.
225235
uint32_t ndtr_now = uart_->dmarx->stream->NDTR;
226236
if (ndtr_now <= DMA_RX_BUFFER_SIZE) {
227237
size_t received_so_far = DMA_RX_BUFFER_SIZE - ndtr_now;
@@ -239,8 +249,8 @@ void YFR4escDriver::threadFunc() {
239249

240250
// Heartbeat: send control periodically even if unchanged, to satisfy ESC watchdog
241251
systime_t now = chVTGetSystemTimeX();
242-
if ((systime_t)(now - last_heartbeat) >= HEARTBEAT_INTERVAL) {
243-
last_heartbeat = now;
252+
if ((now - last_heartbeat) >= HEARTBEAT_INTERVAL) {
253+
last_heartbeat += HEARTBEAT_INTERVAL;
244254
SendControl(last_duty_);
245255
}
246256
}

src/drivers/motor/yfr4esc/YFR4escDriver.h

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,13 @@ class YFR4escDriver : public DebuggableDriver, public MotorDriver {
2121
};
2222
~YFR4escDriver() override = default;
2323

24-
bool SetUART(UARTDriver *uart, uint32_t baudrate);
24+
bool SetUART(UARTDriver* uart, uint32_t baudrate);
2525

2626
void RequestStatus() override{}; // No-op, ESC streams status periodically
2727
void SetDuty(float duty) override;
2828
bool Start() override;
2929

30-
void RawDataInput(uint8_t *data, size_t size) override;
30+
void RawDataInput(uint8_t* data, size_t size) override;
3131

3232
private:
3333
// Heartbeat: resend control regularly to satisfy ESC watchdog
@@ -36,7 +36,7 @@ class YFR4escDriver : public DebuggableDriver, public MotorDriver {
3636
float last_duty_ = 0.0f;
3737

3838
struct UARTConfigEx : UARTConfig {
39-
YFR4escDriver *context;
39+
YFR4escDriver* context;
4040
};
4141

4242
// RX buffer size calc: We only receive Status packets
@@ -65,19 +65,19 @@ class YFR4escDriver : public DebuggableDriver, public MotorDriver {
6565
size_t cobs_rx_len_ = 0;
6666
size_t rx_seen_len_ = 0; // Track how many bytes we already processed in the receiving DMA buffer
6767

68-
THD_WORKING_AREA(thd_wa_, 512){}; // AH20250819: Measured stack usage of 144 bytes (without ULOG errors)
69-
thread_t *processing_thread_ = nullptr;
68+
THD_WORKING_AREA(thd_wa_, 512){}; // AH20250922: Measured stack usage of 208 bytes (without ULOG errors)
69+
thread_t* processing_thread_ = nullptr;
7070

71-
UARTDriver *uart_{};
71+
UARTDriver* uart_{};
7272
UARTConfigEx uart_config_{};
7373

7474
void threadFunc();
75-
static void threadHelper(void *instance) {
76-
static_cast<YFR4escDriver *>(instance)->threadFunc();
75+
static void threadHelper(void* instance) {
76+
static_cast<YFR4escDriver*>(instance)->threadFunc();
7777
};
7878

79-
void ProcessRxBytes(const volatile uint8_t *data, size_t len);
80-
void ProcessDecodedPacket(uint8_t *packet, size_t len);
79+
void ProcessRxBytes(const volatile uint8_t* data, size_t len);
80+
void ProcessDecodedPacket(uint8_t* packet, size_t len);
8181

8282
void SendControl(float duty);
8383
void SendSettings();

0 commit comments

Comments
 (0)