The topic of UART combined with DMA keeps coming back like a boomerang. Especially receiving data on a microcontroller causes the most problems. Why? Because we don’t know how much data will arrive. That’s why receiving via DMA is a somewhat poor idea, because we have to specify in advance the amount of data being received. But not always…

UART combined with DMA

Some time ago I wrote about a great use of DMA reception combined with the UART interrupt for the idle state, i.e. IDLE. On my blog you’ll find two articles on how to write such a mechanism for F4 (here) and F1 (here) microcontrollers. Let me briefly remind you what such a setup is about.

  1. We configure the UART to work with DMA so that it can send transfer requests to the DMA.
  2. We enable the UART idle interrupt, i.e. IDLE.
  3. We configure the DMA to read from the UART DR register and write somewhere into a prepared array in RAM (with increment).
  4. We enable reception from the UART via DMA.
  5. Reception can end at two moments
    1. We received a predetermined number of characters – we get a DMA Transfer Complete interrupt
    2. We received fewer characters, but the UART entered the idle state on the RX line – we get a UART IDLE interrupt

In the end, we get an interrupt after reception finishes regardless of how many characters we received. So we know the moment when we should deal with that data. Great, isn’t it?

Register-level implementation

In the previous posts about STM32F4 and STM32F1 I did it manually using registers. Why? Well, the HAL libraries didn’t support the IDLE interrupt at all. You had to rely on your own implementation and add IDLE interrupt handling on the UART.

Such an approach came with a lot of drawbacks and imperfections.

First, it isn’t a universal solution, as my course participants found out. People working on STM32L4 or STM32F7 ran into the problem of registers looking slightly different. For each microcontroller you basically need a separate approach and a separate implementation, because there were small nuances in how DMA or the UART worked. That can be a pain.

Second, my implementation was not resistant to the fact that IDLE also occurred after receiving exactly the number of characters requested via DMA. We were already handling the received data in the DMA interrupt, and then IDLE came in and de facto I did the same thing a second time…

We could use something more universal and working, right? Something that will work for every STM32 family. It’s already here!!!

UART IDLE in HAL libraries

At the end of 2020, ST Microelectronics finally added support for the UART IDLE interrupt! For all families. This means for us – programmers – consistent usage of this interrupt and its handling regardless of the microcontroller family.

All of this, as usual for HAL, is done “for us”. In the sense that we request reception with a special function and we have a special Callback available. It is called in two situations: DMA Transfer Complete and UART Idle.

Requesting reception is very similar to the usual way of starting DMA reception. Simply, after enabling reception on DMA, the IDLE interrupt is enabled. Along the way, information is also set that it was a reception mode with IDLE, so that HAL knows what to do in the interrupt handler.

What does the interrupt code look like? First, HAL checks in the UART structure whether reception was requested with IDLE. If yes, it will also check and clear that flag. Then it checks whether this was reception using DMA, because that also matters, and it clears the appropriate flags. At the end it calls the special Callback.

Let’s set up DMA reception with IDLE

First, we need to play with CubeMX. I’ve been using STM32CubeIDE for a long time, so CubeMX is built into it.

What will I base my work on today? I have a Nucleo-F411RE on hand, which I use among other things in my STM32 Course for Beginners (the course content is conducted in Polish). STM32CubeIDE version 1.6.0 and HAL F4 libraries v1.26.1.

First, we need to have the UART configured. If you create a project by default by selecting Nucleo, you already have UART2 configured at 11520. You need to add two things to it.

Of course, you need to set DMA for the USART2_RX receive line. Do exactly as I do, i.e. with auto-increment on the memory side.

The stream/channel number doesn’t matter much. We only want to receive something on UART, so nothing else will interfere with us.

Next, you need to enable UART interrupts. After all, we want to use the UART IDLE interrupt. The DMA interrupt is enabled by default.

Finally, move enabling interrupts to after all peripheral initializations. It’s not necessary, but it improves code readability and sometimes behavior. I follow the rule that peripheral initialization always comes first, and only later do I think about enabling interrupts.

We have everything we need. Let’s move on to the code.

How to use DMA with the UART IDLE interrupt

There’s nothing left to do but simply use DMA and IDLE to receive some message. Let’s do it.

HAL makes reception as easy as possible. We need an array into which we will receive characters. I usually create it in the global variables area in the appropriate place in main.c marked by comments.

/* USER CODE BEGIN PV */
uint8_t ReceiveBuffer[32]; // Receive from UART Buffer
/* USER CODE END PV */

Now we need to set reception via DMA. The function HAL_UART_Receive_DMA(…) handles receiving using DMA, however it won’t help us here. It sets a fixed number of characters to receive and that’s it. We need to additionally enable the IDLE interrupt.

That’s why we have to use a completely different function. It has the “Ex” suffix, i.e. extended functions from the HAL API. It will be HAL_UARTEx_ReceiveToIdle_DMA(…), which takes exactly the same arguments as the regular DMA reception function.

So let’s use this special function in main, right before starting the main loop. The right place for this is user section number 2.

/* USER CODE BEGIN 2 */

// Start to listening on UART with DMA + Idle interrupt detection
// the Callback is in USER CODE 4 section
HAL_UARTEx_ReceiveToIdle_DMA(&huart2, ReceiveBuffer, 32);
/* USER CODE END 2 */

Remember that we’re setting up “listening” and we don’t need to wait for reception to finish. We’ll get an interrupt in due time. Right – an interrupt. In HAL, we remember that interrupt handling is done by the library. We are given callbacks to react to the appropriate interrupts.

For UART reception on DMA using the IDLE interrupt there is one common callback – HAL_UARTEx_RxEventCallback(…). In it we of course get information about which UART invoked the callback, but not only that. In the second argument we get the number of bytes that managed to be received on the UART.

This is incredibly valuable information, because the interrupt could occur after a different number of characters! That’s exactly why we use IDLE. With that information we have everything we need for parsing.

How to write our callback handler? Just check if it was our UART that triggered it, then do your work. For example parsing or putting it into some buffer (e.g. circular) for later processing outside the interrupt.

At the very end you must re-enable listening. In the same way as in main. You must set up DMA and UART reception with IDLE again from scratch. The whole callback will therefore look like this.

/* USER CODE BEGIN 4 */
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
    // Check if UART2 trigger the Callback
    if(huart->Instance == USART2)
    {
        //
        // Do something
        //
        
        // Start to listening again - IMPORTANT!
        HAL_UARTEx_ReceiveToIdle_DMA(&huart2, ReceiveBuffer, 32);
    }
}
/* USER CODE END 4 */

And that’s it. Let’s check if it works.

Reception behavior

We have 32 bytes set to be received via DMA. Let’s send fewer. For example the string “Ukulele”. It has definitely fewer characters than the amount planned for DMA. In that case we should get a UART IDLE interrupt.

I set a trap in our callback, ran the program, and sent the text.

And… voilà! The program stopped at the breakpoint. On the right side I put into Live Expression what interests us: the size of the received string – 7 bytes. That’s what the callback passes to us and it should be used later.

I also added the array itself that I wanted to receive into on UART, and my Ukulele is there 🙂

It works as it should!

Parsing strings

Next, we should parse what we received, i.e. react to what came in. This is a much more complex operation and can be divided into simple and complex parsing.

I covered the topic of receiving and parsing UART strings in great detail in my STM32 Course for Beginners (the course content is conducted in Polish).

Registration for the third edition is now open. The course covers a very broad range of STM32 programming. You learn not only the microcontroller itself, but also various programming techniques and how to handle embedded challenges. For example, receiving characters on UART, buffering them in a circular buffer, and later processing strings.

Check the full curriculum at https://kursstm32.pl/ (the course content is conducted in Polish). You can join the third edition until Friday 04.06.2021 until 20:00. After that, it will not be possible to join the course!

Summary

We finally got support for UART with DMA and the IDLE interrupt. ST implemented it in such a way that using this reception method is ridiculously simple.

The only thing you need to do is set up DMA and UART interrupts, and use the reception start function and the callback.

This method is the same for all STM32 families, so go ahead and try it on your microcontroller!

If you noticed any mistake, disagree with something, would like to add something important, or simply feel like discussing this topic, write a comment. Remember that the discussion should be polite and in accordance with the rules of the Polish language.

Podobne artykuły

.

0 Comments

Leave a Reply

Avatar placeholder

Your email address will not be published. Required fields are marked *