cancel
Showing results for 
Search instead for 
Did you mean: 

USART Receive Interrupt

gerrysweeney
Associate III

Hopefully this is more of a sensible non-noob question, it relates to interrupt handling, in my case a simple USART receive interrupt. 

I can see how the CubeMX tool is managing the .c file, its created an interrupt handler, for me to insert my code, thats easy. I can detect the interrupt and spit out a short string in response, so its working - all good. 

The question I have is, what can I do inside that ISR safely.  Do I need to preserve any registers. My aim is to throw anything I receive into a ring buffer of some form, with a view to building a received packet of data.  At some point I will need to pick up each completed packet from a list. In order to do that I will possibly need to allocate some memory, and it would be useful if I do not have to put the whole implementation of that ISR in the file that is being auto-generated, so would be handy to also be able to call a function where I can contain the implementation of the ISR in its own compilation unit. 

One other question is that of controlling interrupts.  At some point, I would have a packet of data in a buffer somewhere that I would access from the main loop of the code. In order for me to safely access that packet from the receive buffer, I would need to first, stop/pause/prevent the next interrupt from interrupting me. On x86 architecture this is quite tricky as you have to rely on the atomic behaviour of specific instructions, and prevent the interrupt in a way that, should an interrupt occur while getting the data safely from the buffer, the interrupt controller will hold/queue that interrupt until the interrupts are re-enabled. 

What is the right semantics for doing this on the STM32 platform?

As ever, any help at all much appreciated. 
Gerry

31 REPLIES 31
gerrysweeney
Associate III

And on the above, is it a correct statement to say the following about this code:-

 

  • The code in the green box, is non-renentrant, that is to say, if an interrupt fires here from usart3, while its doing work but before it calls the read again on line 213, it is gaurenteed that another interrupt from the same source will not re-enter here?

  • Is it safe, required, advisable, or non-advisable to disable the USART3 interrupt in this block while processing the interrupt, and then re-enabled it before calling HAL_UART_IRQHandler()?

Hope this makes sense...
Gerry 

gerrysweeney_0-1706036415454.png

 

Bob S
Principal

Do as little as possible in the interrupt handler (including HAL callback functions if you use HAL).  You COULD have the ISR put the new byte into a buffer and check for whatever "end of message" indicator you might have, then set a global variable (declared "volatile") to signal that data is ready.  But the ISR cannot overwrite that buffer with additional data until it has been processed - so you either need ping-pong buffers (2 or more buffers that the ISR cycles between), or the one buffer needs to be big enough to hold multiple "messages".

With the STM architecture, an arguably better solution is it use DMA into a (relatively) large circular buffer.  You non-interrupt code then periodically checks that buffer and processes any data that it contains.  See https://github.com/MaJerle/stm32-usart-uart-dma-rx-tx

Either way - be aware that the HAL UART RX code (both interrupt and DMA) will abort the receive if it gets any UART error, like framing errors, parity (if you have it enabled), or overrun (which should NOT happen when using DMA).

Didn't see this post before my 1st response

DON'T DO THAT!!!!  Never call any blocking function from an interrupt handler.  It may/will delay the interrupt handler long enough to miss subsequent interrupts (i.e. miss incoming data).

TO answer your question - once the UART3 interrupt handler starts it will not get called again until the handler exits.  It may immediately start that same handler again if there is still a pending interrupt.  A different interrupt can interrupt this handler if it has a higher priority.

Do not "disable" the interrupt.  It is effectively disabled by the NVIC interrupt priority structure that only allow higher priority interrupts (lower priority values) to interrupt an active interrupt handler.

> The code in the green box, is non-renentrant, that is to say, if an interrupt fires here from usart3, while its doing work but before it calls the read again on line 213, it is gaurenteed that another interrupt from the same source will not re-enter here?

Correct, the interrupt cannot pre-empt itself and will not fire again until it completes.

> Is it safe, required, advisable, or non-advisable to disable the USART3 interrupt in this block while processing the interrupt, and then re-enabled it before calling HAL_UART_IRQHandler()?

Disabling it during the interrupt will have no effect. In that respect, doing so would be inadvisable/useless.

In this specific case, you are likely using the interrupt to receive characters. In order to avoid an overrun, you should make sure it returns before the next character comes in. It's impossible to satisfy this if you call HAL_UART_Transmit, so instead save the data to a buffer, or set a flag, or do some other action and act upon that in the main thread.

 

I think this answers your main post as well. Throwing characters into a buffer in the IRQ and then handling that in the main thread is a viable and advisable approach. You will need to do so in a thread-safe manner, but this is quite doable.

If you feel a post has answered your question, please click "Accept as Solution".
gerrysweeney
Associate III

@TDK thank you for the clarifications, thats very clear, non-reneterent, thats helpful to know. The second part of my question was this.  If I use the ISR to stack my received chars into a buffer, at some point, say setting a variable, I need to indicate I have received a frame of data, and the main loop will need to come and pick up that data, which will involve copying the message out of the buffer, and moving a pointer to allow that part of the buffer to once again be free for the ISR to use.  

During the time I am messing with the buffer form the main loop, how do I ensure that the ISR does not fire again and change the buffer that I am currently copying?

And one other question, how do I know which registers its safe to mess with during my ISR, for example, if I used memcpy thats going to use some registers for src/dst/len param, that I presume for an ISR would be bad right?

Hi Bob,
Yes I generally would to be honest, what I am trying to understand though is what can I safely do.  I cannot seem to find any reference to that.  

Best I understand it, I will be receiving chars of the IUART.  The start of frame begins with a period greater than one character of all 1's which I understand the UART can/will generate an interrupt on. If that is the case, then I would have my serial stream framing, but, if not, then I need to use a timer to receieve characters until there is a period of not receiving characters any more. I am hoping the USART/Interrupt controller will do the work for me here, the timer solution sounds horrible. 

Gerry

I will not :) I was just doing it for testing, I was pretty well aware that what I was doing in that test code was bad, but it let me see that the interrupt was working :) thats all

Thank you for the clarification on the semantics of the reentry, thats what I was expecting/.hoping, but in essence I think what you are saying is, its on me to make sure that the ISR completes in enough time to be done *before* the next character arrives and fires the interrupt again, otherwise I can miss chars... 

No hardware FIFO's like there was in the 1980's 16550's UARTS then :(

Thanks,
Gerry

magene
Senior II

I have to deal with 8 or more UARTs on a regular basis.  I setup a FIFO queue for each UART and all I do in the ISR is enqueue the received message on the queue.  Then the non-interrupt part of my code looks for a queue length > 0.  I use the STM32H7A3 which has a character match function so the ISR doesn't get called until the UART detects the end of line character I've defined, almost always a LF.  I haven't tested the CM functionality with non-DMA but since the CM interrupt is in the UART, I'm guessing it will work.  When I was using a STM32 processor without CM, I set up the UART to interrupt every character.  When I got a character, I just stuck it in an array.  When the received character matched my end of line character, I enqueued the character array on the FIFO.

I know a lot of people will yell about interrupting on every character being inefficient, but these processors are so fast it isn't a problem for my moderately real time requirements.

Here's a good reference with example code for setting up a FIFO. https://www.techiedelight.com/queue-implementation-cpp/

BTW, most STM32 processors do have FIFOs attached to the UARTs that you can setup in CubeMX.

I understand your point about not wanting to modify the auto-generated code too much.  But if you put your code between the designated comments in the autogenerated code, CubeMX will preserved your code when it regenerates.  Also you should check to see if the IRQ handlers that CubeMX generates are declared as weak so you can override them.  In my case, they aren't so I just put a call to my IRQ handler in the autogenerated IRQ handler code between the "user code here" comments and I don't have to touch the autogenerated code.  I do have to declare my IRQ handler as external to make this work. 

Full disclosure, I"m not a very experienced embedded programmer so don't take anything I saw as gospel.

 

Hi Magene,
Thanks for you input.  I am very comfortable using std::queue but I am lost as to how I can do that safely.  The data I am trying to receive is some bytes (a few of them) followed by an IDLE frame.  So my plan broadly speaking was to collect the characters one by one on the ISR into a buffer, at the point I receive an IDLE state interrupt, I was going to transfer those bytes (the packet) to the queue.  Easy enough, and I expect if thats all I was doing, then it would work fine. 

However, in the application part of the system I will need to periodically check the queue, and, on finding one or more packets of data in that queue, I will need to dequeue them and process them.  

The problem I am strugllling with is I cannot see how I can access that queue and safely dequeue ensuring that tihngs do not go wrong because I receive another interrupt that puts something into that queue, while I am trying to take something out of it.   Any idea?

Gerry