@@ -16,22 +16,26 @@ namespace xbot::driver::motor {
1616
1717using 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
124128void 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
213224void 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 }
0 commit comments