Skip to content

fix(UART_ReceptionToIdle_CircularDMA): wrap old_pos to fix silent data loss#204

Open
94xhn wants to merge 1 commit into
STMicroelectronics:masterfrom
94xhn:fix/uart-toidle-circular-dma-old-pos-wrap
Open

fix(UART_ReceptionToIdle_CircularDMA): wrap old_pos to fix silent data loss#204
94xhn wants to merge 1 commit into
STMicroelectronics:masterfrom
94xhn:fix/uart-toidle-circular-dma-old-pos-wrap

Conversation

@94xhn

@94xhn 94xhn commented Jul 10, 2026

Copy link
Copy Markdown

Relates to #196 (closed for inactivity, but the underlying issue is still present in current main.c).

Problem

In HAL_UARTEx_RxEventCallback(), old_pos tracks the last-processed position in the circular DMA Rx buffer, compared against the HAL-reported Size to detect new data:

if (Size != old_pos)
{
  ...
}
old_pos = Size;

On a Transfer Complete event (buffer fills up exactly to RX_BUFFER_SIZE), the HAL reports Size == huart->RxXferSize == RX_BUFFER_SIZE, not a wrapped 0. This is confirmed in Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_uart.c's UART_DMAReceiveCplt():

if (huart->ReceptionType == HAL_UART_RECEPTION_TOIDLE)
{
  ...
  HAL_UARTEx_RxEventCallback(huart, huart->RxXferSize);
}

Since old_pos = Size; stores this unwrapped, after one full-buffer cycle old_pos is left at RX_BUFFER_SIZE. On every subsequent full-buffer Transfer Complete event, Size == old_pos == RX_BUFFER_SIZE, so Size != old_pos spuriously evaluates false and an entire buffer's worth of newly received UART data is silently dropped — UserDataTreatment() is never called for it.

This matches the original report in #196: "old_pos==RX_BUFFER_SIZE Size==RX_BUFFER_SIZE ... will break condition."

Fix

Wrap old_pos with % RX_BUFFER_SIZE so it stays a valid index into aRXBufferUser, matching the actual wrapped position (this is also the fix originally suggested in #196).

Test plan

No hardware on hand to reproduce end-to-end, so I verified with a standalone C reproduction of the callback's exact state-machine logic (comparing before/after over 3 consecutive full-buffer Transfer Complete events, each reporting Size == RX_BUFFER_SIZE):

  • Before the fix: UserDataTreatment() is called only once (10/30 bytes processed) — cycles 2 and 3 are silently dropped, exactly reproducing the reported bug.
  • After the fix: UserDataTreatment() is called on all 3 cycles (30/30 bytes processed).
  • Partial (non-full-buffer) receptions are unaffected, since Size % RX_BUFFER_SIZE == Size when Size < RX_BUFFER_SIZE.

Disclosure

Generative AI (Claude) was used to help investigate this issue, implement, and verify this fix. All changes were reviewed by me before submission.

…a loss

HAL_UARTEx_RxEventCallback() tracks the last-processed position in the
circular DMA Rx buffer via old_pos, and compares it against Size to
detect whether new data has arrived (`if (Size != old_pos)`).

On a Transfer Complete event (a full circular buffer), the HAL reports
Size == huart->RxXferSize == RX_BUFFER_SIZE, not a wrapped 0 (confirmed
in stm32f4xx_hal_uart.c's UART_DMAReceiveCplt(), which calls
HAL_UARTEx_RxEventCallback(huart, huart->RxXferSize) for
HAL_UART_RECEPTION_TOIDLE mode). Since old_pos was previously set to
`old_pos = Size;` unwrapped, after one full-buffer cycle old_pos is
left at RX_BUFFER_SIZE. On every subsequent full-buffer Transfer
Complete event, Size == old_pos == RX_BUFFER_SIZE, so the `Size !=
old_pos` check spuriously evaluates false and the entire buffer's
worth of newly received data is silently dropped without calling
UserDataTreatment().

Wrap old_pos with `% RX_BUFFER_SIZE` so it stays a valid index into
aRXBufferUser, matching the actual wrapped position.

Fixes STMicroelectronics#196

Signed-off-by: 94xhn <87560781+94xhn@users.noreply.github.com>
@ALABSTM ALABSTM added bug Something isn't working hal HAL-LL driver-related issue or pull-request. uart Universal Asynchronous Receiver Transmitter labels Jul 13, 2026
@KRASTM

KRASTM commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Hello @94xhn,

Thank you for the report.

After checking the example behavior against the readme and running a few tests, the current implementation is aligned with the intended use case of this example. UART_ReceptionToIdle_CircularDMA is intended to demonstrate reception in circular DMA mode with the standard receive event notifications enabled:

HT   : Half Transfer, half of the Rx buffer is filled
TC   : Transfer Complete, the full Rx buffer is filled
IDLE : Rx line idle after reception

As described in the readme, the example handles received data through these notifications. For instance, with a 20-byte buffer and 22 received bytes, the documented callback sequence is:

HT   Size = 10
TC   Size = 20
IDLE Size = 2

Therefore, in the intended configuration of this example, repeated full-buffer TC callbacks without intervening HT or IDLE events are not the nominal flow demonstrated by the example.

The case described in the PR corresponds to a customized event sequence compared to the one documented and demonstrated by this example. For the example as provided, with HT, TC, and IDLE notifications enabled as described in the readme, no change is required.

Please allow me to close the PR. Thank you for your understanding.

With regards,

@KRASTM KRASTM moved this from To do to Analyzed in stm32cube-mcu-hal-dashboard Jul 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working hal HAL-LL driver-related issue or pull-request. uart Universal Asynchronous Receiver Transmitter

Projects

Development

Successfully merging this pull request may close these issues.

3 participants