Difference between revisions of "STM32 UART DMA Idle Detection"
(7 intermediate revisions by the same user not shown) | |||
Line 1: | Line 1: | ||
− | [[Category:STM32]][[Category:STM32 Development]][[Category:STM32 HAL]][[Category:C]]{{metadesc|UART DMA with idle detection}} | + | [[Category:STM32]][[Category:STM32 Development]][[Category:STM32 HAL]][[Category:C]]{{metadesc|UART DMA receive with idle detection}} |
− | Using an [[UART]] to send and receive data is a very common way to communicate between two devices. | + | [[File:UART to UART test setup.jpg|thumb|300px]]Using an [[UART]] to send and receive data is a very common way to communicate between two devices. |
+ | |||
+ | A common approach to receiving data from an [[UART]] is to have an interrupt generated after each character has been received. The problem with this approach is that a lot of interrupts will be generated leaving very few CPU cycles to process the received data without losing characters. A better approach is to use [[DMA]] for this, but this leaves another challenge since [[DMA]] in [[STM32]] generally generate an interrupt when the buffer is half full and then another one when the buffer is full. | ||
+ | {{clear}} | ||
+ | == Video == | ||
+ | |||
+ | We have created a tutorial video describing this approach. The video can be viewed here: [https://www.youtube.com/watch?v=Eh7Szh-K-u8 https://www.youtube.com/watch?v=Eh7Szh-K-u8]. | ||
+ | |||
+ | {{#ev:youtube|Eh7Szh-K-u8}} | ||
+ | |||
+ | == The Code == | ||
+ | |||
+ | The callback (interrupt handler) is actually quite simple. It is important to understand when it is triggered. Using the 'ReceiveToIdle' it will be executed in 3 different situations: | ||
+ | |||
+ | # When the buffer is half full (exactly) | ||
+ | # When the buffer is full and wrap around | ||
+ | # When the RX is idle (1 character pause) | ||
+ | |||
+ | The callback is executed with a parameter indicating the current offset of the last character received ([[ST]] call it 'Size' but we will use 'offset'). If we keep track of the last (starting with 0) it becomes really easy to determine which part of the buffer needs processing. | ||
+ | |||
+ | <pre> | ||
+ | /** | ||
+ | * @brief UART Event Callback. Fired on idle or if dma buffer half full or full. | ||
+ | * @param huart Pointer to a UART_HandleTypeDef structure that contains | ||
+ | * the configuration information for the specified UART module. | ||
+ | * @param offset A offset counter pointing to last valid character in DMA buffer. | ||
+ | * @retval None | ||
+ | */ | ||
+ | void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t offset) { | ||
+ | |||
+ | static uint16_t last_offset = 0; | ||
+ | |||
+ | // Ignore if called twice (which will happen on every half buffer) | ||
+ | if (offset != last_offset) { | ||
+ | |||
+ | // If wrap around reset last_size | ||
+ | if (offset < last_offset) | ||
+ | last_offset = 0; | ||
+ | |||
+ | while (last_offset < offset) { | ||
+ | process_character((char) dmabuf[last_offset]); | ||
+ | ++last_offset; | ||
+ | } | ||
+ | |||
+ | } | ||
+ | |||
+ | } | ||
+ | </pre> | ||
+ | |||
+ | We can now fire up the receiver: | ||
+ | |||
+ | <pre> | ||
+ | HAL_UARTEx_ReceiveToIdle_DMA(&huart4, (uint8_t*) &dmabuf, DMA_BUFFER_SIZE); | ||
+ | </pre> | ||
== Miscellaneous Links == | == Miscellaneous Links == |
Latest revision as of 13:41, 13 November 2024
Using an UART to send and receive data is a very common way to communicate between two devices.
A common approach to receiving data from an UART is to have an interrupt generated after each character has been received. The problem with this approach is that a lot of interrupts will be generated leaving very few CPU cycles to process the received data without losing characters. A better approach is to use DMA for this, but this leaves another challenge since DMA in STM32 generally generate an interrupt when the buffer is half full and then another one when the buffer is full.
Video
We have created a tutorial video describing this approach. The video can be viewed here: https://www.youtube.com/watch?v=Eh7Szh-K-u8.
The Code
The callback (interrupt handler) is actually quite simple. It is important to understand when it is triggered. Using the 'ReceiveToIdle' it will be executed in 3 different situations:
- When the buffer is half full (exactly)
- When the buffer is full and wrap around
- When the RX is idle (1 character pause)
The callback is executed with a parameter indicating the current offset of the last character received (ST call it 'Size' but we will use 'offset'). If we keep track of the last (starting with 0) it becomes really easy to determine which part of the buffer needs processing.
/** * @brief UART Event Callback. Fired on idle or if dma buffer half full or full. * @param huart Pointer to a UART_HandleTypeDef structure that contains * the configuration information for the specified UART module. * @param offset A offset counter pointing to last valid character in DMA buffer. * @retval None */ void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t offset) { static uint16_t last_offset = 0; // Ignore if called twice (which will happen on every half buffer) if (offset != last_offset) { // If wrap around reset last_size if (offset < last_offset) last_offset = 0; while (last_offset < offset) { process_character((char) dmabuf[last_offset]); ++last_offset; } } }
We can now fire up the receiver:
HAL_UARTEx_ReceiveToIdle_DMA(&huart4, (uint8_t*) &dmabuf, DMA_BUFFER_SIZE);