cancel
Showing results for 
Search instead for 
Did you mean: 

CubeMX Feature Request: Add USART RX IDLE Handling

Steve H
Associate III
Posted on May 23, 2017 at 16:36

I'm preparing to implement an STM32 project that does high-speed async serial communications, and needs to send and receive variable-length messages. My receive messages are characterized in that they are *always* continuous byte streams on the serial line, with no inter-byte gaps; when the RX serial line goes idle, the message is complete. Because the bit rate is high, I prefer to use DMA for TX/RX message handling. I've written code to do this task before, and I know exactly how to set up the USART, DMA, and interrupt handlers to do the job.

It would be great to use the CubeMX HAL's UART routines for this project, BUT they have an essential lack here: They don't provide handling for the UART_FLAG_IDLE interrupt flag in HAL_UART_IRQHandler(). This means that DMA receive operations using the HAL routines are limited to fixed-length messages, because there is no way to handle the RX idle interrupt that is generated when the last byte of a short message comes in.

I have three equally unappealing options in this case: 1) Continue rolling my own UART support code outside the HAL framework, replacing the hal_usart module for each new target MCU I write for, and creating an integration headache; 2) Hack a local copy of the hal_usart module to add the feature (again, for each target MCU I write for), creating maintenance headaches; or 3) give up on USART DMA receive operations, creating a potentially unacceptable performance bottleneck with all that USART polling or IRQ overhead.

A quick browse of these forums and others like StackOverflow tells me that a number of other developers have faced this same issue, so there's a decent chance this feature will be generally useful.

Here's the specific feature I'd like to see added to the hal_usart drivers:

  • Add code to HAL_UART_IRQHandler() to handle the UART_FLAG_IDLE condition when receiving under DMA, with a callback hook that is invoked when the RX idle condition occurs. Based on the current organization of the hal_usart module, it probably makes best sense to add a separate callback hook, e.g., 'HAL_UART_RxIdleCallback()' or similar.
  • Provide a way for the RX idle callback to determine how much data the RX DMA has received, probably by adding or reusing a member of UART_HandleTypeDef.

With this addition, it should be possible to use the stock HAL routines to manage variable-length RX messages while preserving the efficiency advantages of DMA.

I'm going to try hacking a copy of the hal_usart driver to add this feature, and if it's wildly successful, I'll post it as an update for the curious.

#feature-request
35 REPLIES 35
MDS
Associate III
Posted on August 17, 2017 at 18:29

I would like to see the RTO feature also supported. My design uses the UART Receive Timeout (RTO) interrupt for variable length receive messages. I like RTO because you can specify the idle time in characters. I would eventually like to get RTO working with Double Buffer (DB) DMA mode to provide continuous receive, so I can do slow receive like from a keyboard or fast for receiving a byte stream. I have DB working for SPIs, so UART should be very similar.

Posted on August 17, 2017 at 20:06

I support this request. I've written this same damn code as well. 

Andrei Chichak

CBF Systems Inc

The Embedded.fm podcast and blog.

Posted on September 01, 2017 at 15:47

I like RTO before idle. It's far more flexible than a fixed idle timing.

Bob jenkins
Associate II
Posted on September 13, 2017 at 16:51

in your HAL_UART_RxIdleCallback() function, when you 'zero' huart->RxXferCount, you [probably] should also reset the uart read state to HAL_UART_STATE_READY:

huart->RxState = HAL_UART_STATE_READY;

Otherwise I was having a problem with the next call to HAL_UART_Receive_DMA, returning HAL_BUSY  and being unable to receive next chunks of data from USART.

MDS
Associate III
Posted on October 20, 2017 at 13:20

I had to change my design from Double Buffer Mode (DBM) to Circular Mode (CM). The problem with DBM is that if even one interrupt is serviced too late so the buffer swap does not occur before the 2nd buffer fills, the DMA will overwrite a buffer before it is serviced. I have to use small buffers for real-time performance in an interrupt intensive design that also uses an RTOS, USB,  and an IP stack. I could not consistently service the DMA buffer swaps fast enough to prevent overrun. By going to a circular buffer with RTO interrupts, the chance of an overrun is greatly reduced. I created a buffer large enough for a long stream of bytes with buffer half full interrupts to simulate DBM mode. I have RTO interrupts enabled so I can get notified of short bursts. The RTO ISR gets the transfer length from the DMA, not the UART. I set the RTO count to provide real-time processing and minimize RTO interrupts. I have already implemented this design for an SPI interface and it works really well. For the SPI, I use a periodic timer to trigger the processing of receive data in real-time. This is similar to using the RTO interrupt in a USART as it too would be periodic based on character times (RTO count). Using the IDL interrupt would cause too many interrupts compared to the RTO design.

Posted on October 21, 2017 at 21:18

I came back to this part of the project after working on some other parts, and Ifound out that #2, continuous circular buffer reception, already works,but with one requirement on the user code that's not obvious until you read the reference manual: You need to make the size of your circular buffer equal to the transfer size. If you do, then the chip will automatically wrap new data back to the start of the buffer, and go on continuously.

I find that using the circular buffer with idle detection interrupts works great for my application, and I don't need to modify any HAL library code outside of the user-modifiable sections. The overview of the strategy is as follows:

1.Use HAL CubeMX to set up a DMA transfer into a circular buffer on your UART of choice

2. Define a circular buffer global variable and some variables for the head, tail, and length of the transmission

3. Enable the interrupt for idle mode detection and enable reception

4. In the stm32l0xx_it.c file there is an ISR that is specific to each UART, with a section for user code. Put code to ID and clear the idle mode detection interrupt here, and parse the UART transmission or set a flag to parse it somewhere else.

In more detail, with example code:

2:

uint8_t cyp_cmd_buff[CYP_BUFF_LENGTH];
uint16_t cyp_cmd_head;
uint16_t cyp_cmd_tail;
uint8_t fresh_cyp_cmd = 0;�?�?�?�?�?�?�?�?�?�?�?�?
uint8_t cyp_cmd_buff[CYP_BUFF_LENGTH];
uint16_t cyp_cmd_head;
uint16_t cyp_cmd_tail;
uint8_t fresh_cyp_cmd = 0;�?�?�?�?�?�?�?�?�?�?�?�?

3: In main:

/* USER CODE BEGIN 2 */
 __HAL_UART_ENABLE_IT(&huart1,UART_IT_IDLE);
 status = HAL_UART_Receive_DMA(&huart1, &cyp_cmd_buff, CYP_BUFF_LENGTH);�?�?�?

4:

/**
* @brief This function handles USART1 global interrupt / USART1 wake-up interrupt through EXTI line 
*/
void USART1_IRQHandler(void)
{
 /* USER CODE BEGIN USART1_IRQn 0 */
 int16_t length = 0;
// Receive time-out interrupt
 if (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE) == SET) {
 __HAL_UART_CLEAR_IDLEFLAG(&huart1);
cyp_cmd_tail = cyp_cmd_head;
 cyp_cmd_head = huart1.RxXferSize -(uint16_t)huart1.hdmarx->Instance->CNDTR;
if (cyp_cmd_tail || cyp_cmd_head) { //Ignore case where the interrupt fires before any data is received, when both are zero
 HAL_GPIO_WritePin(GPIOB, DEV_GRN_Pin, 1);
 timers[LED_OFF].timeval = 50 + HAL_GetTick();
 if(cyp_cmd_tail >= cyp_cmd_head) { //Wrapped around circular buffer
 length = (cyp_cmd_head + CYP_BUFF_LENGTH) - cyp_cmd_tail;
 } else {
 length = cyp_cmd_head - cyp_cmd_tail;
 }
 fresh_cyp_cmd = 1; //Flag to let command parser know that data is available.
 }
 }
/* USER CODE END USART1_IRQn 0 */
 HAL_UART_IRQHandler(&huart1);
 /* USER CODE BEGIN USART1_IRQn 1 */
/* USER CODE END USART1_IRQn 1 */�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?

Posted on October 30, 2017 at 08:01

Hi

DAHMEN.IMEN

In which update should we expect this functionality?

Thanks

Shamil Ibn Sagit

Posted on November 01, 2017 at 02:58

+1

Guess we are all waiting for this to implement ModBus or RS485 related communication.!

.Zt

Posted on November 01, 2017 at 15:07

Indeed. I've just implemented this again to provide half-duplex RS-485

comms for a Modbus device link using an LTC2850 RS-485 transceiver.

Fortunately, it works; after porting my RX idle mods to the updated

hal_uart source, I added code to usart.c to enable the USART TC interrupt,

so the call that starts the TX DMA first flips on the transmit enable, and

the handler for HAL_UART_TxCpltCallback() flips the transmit enable back

off when the last stop bit has gone out the door. (Hint: Make sure that the

callback is being called for the right USART, or things get weird).

ST team, are you paying attention here? There seem to be rather a few folks

who would appreciate this feature.

Steve

On Tue, Oct 31, 2017 at 9:59 PM, Zhitai Liu <st-microelectronics@jiveon.com>

Ricky Black
Associate
Posted on April 19, 2018 at 00:14

Hi! Thanks for your solution. I'm starting with programming STM32. I have some problems, maybe you can solve it.

I've used your solution and implemented it on STM32F103RBT6. It works, but I can't read rxBuffer. I'm using module GPS SIM808. I need to find a special sequence in rxBuffer received from module, like three signs '@' 'C' '1' because I want to control my equipment by SMS message. When I send sms to module, it transmit on UART sequence with date, phone number etc. and message. Message is a key, which help me to control my equipment (STM32+SIM808).