cancel
Showing results for 
Search instead for 
Did you mean: 

STM32 Good Practices: Interrupts

KMew
Senior III

Hello,

I am preparing a much larger code that handles a slew of tasks (gathers CAN data from up to 5 different nodes, reads ADCs, sends GPIO PWM signals and ON/OFF signals, LCD display screen via TouchGFX).

I have interrupts for specific tasks (when a CAN message is received, timer interrupt, etc.). Additionally, I will want to periodically send information to different CAN nodes. I understand that it is considered a good practice to minimize the amount of code in the interrupt functions, so I picture the code sequence being as follows (for the CAN receive portion, for example):

  1. CAN Message is received
  2. Interrupt is enabled (enter interrupt function)
  3. Read RxData and store it in the the variables relevant to the data sent
  4. Do arithmetic or logic based on data received <------------------------------

What I want to know is the best practice to do step 4. The arithmetic and/or logic could be a fair bit of code, so I know I shouldn't do it in the interrupt function, but the TouchGFX display portion will cause it to be in a specific task. Should I write the logic in the TouchGFX task function, or create a different task/function? If so, how will it be entered without another interrupt?

10 REPLIES 10
MM..1
Chief III

RTOS ?

AScha.3
Chief III

in INT you set a global var. CAN_RX = 1 ; int finish. (keep INT always as short as possible.)

in main-loop, wherever it is, check if(CAN_RX ) -> function to logic.....and set CAN_RX = 0;

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

And ensure INT doesn't overwrite RX till CAN_RX=0 (so you aren't processing half old packet and half new packet).

Possibly have more than one Rx buffer, that way can receive into different buffer while waiting for previous to be processed. Each buffer can have its own "Full" flag.

-Paul

gbm
Lead III

Select some unused interrupt, set it to some low priority, lower than CAN interrupt. Define a routine for handling the incoming data, giving it the name of the interrupt service routine selected. Enable the interrupt when you are ready for packet data processing.

In the CAN interrupt routine, copy the frame or the needed data into some buffer, then activate the data processing interrupt with NVIC_SetPending().

In general, there is nothing wrong with spending a long time in an ISR, as long as it doesn't spoil other interrupts' processing. You may, for example, put a slow interface service with polling in a lowest-priority interrupt service, getting rid of a terrible concept of the "main loop" and simply putting the processor to sleep after initializing the peripherals. Cortex-M multilevel interrupt system allows for designing a hierarchical event system which is typically much faster than any RTOS-based solution. The only drawback is that it requires some change in programmer's mentality, something similar to switching from procedural to object-oriented programming.

If the only function of an ISRT is to set the flag, then you don't need to use interupts - the interface module sets the flag upon receiving a message and you may test the flag in your software.

My STM32 stuff on github - compact USB device stack and more: https://github.com/gbm-ii/gbmUSBdevice
S.Ma
Principal

Interrupt purpose is to do by sw (and time) what is missing in hw. The interrupt should be brief, releive the pending interrupt after checking for errors or overrun conditions. Things that are not time critical should be done elsewhere. The interrupt can fifo/queue the data for another task(s). You need to use the hw as much as possible to relax the max latency, then relax the interrupt frequency.

S.Ma
Principal

Sometime, a more intelligent macro peripheral can be emulated by using multiple interrupt events to orchestrate an interrupt based state machine, as well.

Karl Yamashita
Lead III

Create a ring buffer to hold a couple of CAN messages from interrupt. Then with a new Task, you can poll to see if there is a new CAN message and do your arithmetic/logic with that data. If there isn't any time critical stuff that needs to be done and there is a lot of arithmetic/logic stuff going on, then make the Task a lower priority with more than one osDelay(x).

Tips and Tricks with TimerCallback https://www.youtube.com/@eebykarl
If you find my solution useful, please click the Accept as Solution so others see the solution.
gbm
Lead III

Let me disagree on the matter of interrupts vs. "elsewhere". There is no real need for any "elsewhere" if you design the whole firmware as purely event-driven - everything might be done in ISRs only. Slow actions may easily be implemented in low priority ISRs, triggered by high-priority ones. That's how most of my simple applications are designed. Having no "main loop" also allows for aggressive energy saving.

Most of simple controller firmware (simple lighting, heating etc.) may be implemented with a single timer interrupt service. I have an application with a composite USB device (3 functions) and hard real-tme control without an event loop, servicing >500k interrupts per second on STM32L4 - that's the power of a purely event-driven firmware design.

My STM32 stuff on github - compact USB device stack and more: https://github.com/gbm-ii/gbmUSBdevice
S.Ma
Principal

One challenge is to meet the WCET if saving power requires minimizing core frequency, which multilevel interrupts causes WCET increases (register stack saving vs nested). It is case by case. The interrupt max allowed latency depends on peripheral specs. This area is critical, yet rarely get mentionned.