Author Topic: How to plan/structure interrupt-based code?  (Read 44437 times)

0 Members and 1 Guest are viewing this topic.

Offline NorthGuy

  • Super Contributor
  • ***
  • Posts: 3567
  • Country: ca
Re: How to plan/structure interrupt-based code?
« Reply #25 on: September 19, 2020, 08:39:04 pm »
Hence, if you want to share data between the "main" and the ISR...
But that's the point, I don't want to share any data with main because main is a zero.
I can even flag my ISRs with "bare" so that they don't save any registers.

You don't need to save registers only if you're not preemting anything useful. This means that all of your interrupts  are on the same level and do not preemt each other. You certainly can do this, but then your fast interrupts (such as ADC in the OP's case) would have to wait until you're finished with the slow stuff.

It's better to use interrupts for fast things (such as ADC in OP's case) and the main thread is doing the slow stuff without affecting the latency of the fast interrupts.

If you really want interrupts for everything, you need a CPU with multi-level interrupts and you need to assign priorities to ensure that your fast interrupts preempt the slow ones. This will yield about the same performance as if you had all the stuff in the main loop, but you need to do extra work setting all the interrupts and priorities without any benefits.
 

Offline NorthGuy

  • Super Contributor
  • ***
  • Posts: 3567
  • Country: ca
Re: How to plan/structure interrupt-based code?
« Reply #26 on: September 19, 2020, 09:09:37 pm »
No, this is just nonsense. Please! When a CPU receives an interrupt signal on one of its pins, it reacts by pushing the instruction pointer to the stack and setting the instruction pointer to the address of the respective interrupt handler routine. After the routine has finished, the instruction pointer is restored from the stack. That's all.

Basically. The context needs to be saved or everything blows. But these are details. What is important is that the interrupt can happen at any time and can preempt your execution. For example, in the ISR you have:

Code: [Select]
a = 1;

and in the main thread:

Code: [Select]
// cannot have both "a" and "b" set to one or the bridge blows
if (a == 0) { 
  b = 1;
}

There may be interference between these like this:

- a is fetched, compared to 0, and since it is zero, the "if" is taken.
- interrupt happens and ISR is invoked
- ISR executes "a = 1"
- ISR returns
- The main thread excutes "b = 1". Now both a and b are set to 1 which causes an explosion.

This is certainly very silly example, but nonetheless it shows that you need to watch for such situation.

In contrast, if both of these statements are in the main thread:

Code: [Select]
// one place:
a = 0;
...
// another place:
// cannot have both "a" and "b" set to one or the bridge blows
if (a == 0) { 
  b = 1;
}

there's no danger for such interference.

When your "main" function is running, an interrupt handler could be called which could invalidate many state invariants that are assumed by "main". The consequence would be a crash or undefined behavior of your application.

The only situation where this cannot happen is when ALL interrupts have been disabled.

Interrupts only operate on certain variables, and you need to understand the consequences of using such variables in the main thread (see an example above). Other variables (which are not used by the interrupts) will not suddenly change just because an interrupt happens.

This is basically the point I'm trying to make. If you have less interrupts which affect less variables, it is less work for you looking for possible interference.
 

Offline Renate

  • Super Contributor
  • ***
  • Posts: 1789
  • Country: de
    • Renate's Android Page
Re: How to plan/structure interrupt-based code?
« Reply #27 on: September 19, 2020, 09:15:26 pm »
If you have less interrupts which affect less variables, it is less work for you looking for possible interference.
If you don't have a main which affects variables, it is less work for you looking for possible interference.
 

Offline Fixpoint

  • Regular Contributor
  • *
  • Posts: 105
  • Country: de
Re: How to plan/structure interrupt-based code?
« Reply #28 on: September 19, 2020, 09:33:06 pm »
Interrupts only operate on certain variables, and you need to understand the consequences of using such variables in the main thread (see an example above). Other variables (which are not used by the interrupts) will not suddenly change just because an interrupt happens.

This is basically the point I'm trying to make. If you have less interrupts which affect less variables, it is less work for you looking for possible interference.

Well, while we're at it, let's use the correct terminology: the thing we are talking about is called "race conditions", not "interference". Race conditions not only concern variables. Since we are talking about embedded systems, we also have to consider hardware state, not just software state. Usually, an interrupt handler will change the hardware state in some way. In the simplest case, it could be some flags (usually harmless, but sometimes dangerous), and in more complicated scenarios, some configuration of the MCU (pins, ADC, UART, USB, etc etc etc). That's why interrupts are often disabled as long as an ISR is running: the ISR has to do things that must not be interrupted, otherwise the semantics of the code would be undefined.

But as I said: even if you disable ALL interrupts and do everything from main by software polling, you have a very grave problem in your design because of the implicit temporal couplings. Your invariants can (and will) be destroyed by software changes (random errors or even crashes are guaranteed) or sometimes even temperature changes because the oscillator could be sensitive to temperature, and then the software polling is not guaranteed to work anymore because the timing is off.

Replacing interrupts by polling introduces a pretty serious design flaw because of the implicit temporal couplings. Polling may be simpler than using interrupts, but this simplicity makes the system fragile and hard to maintain.
« Last Edit: September 19, 2020, 09:41:50 pm by Fixpoint »
 

Offline petemateTopic starter

  • Regular Contributor
  • *
  • Posts: 184
Re: How to plan/structure interrupt-based code?
« Reply #29 on: September 19, 2020, 09:34:51 pm »
Thank you all for your comments and suggestions! They have given me a lot to think about. I am still waiting for the last few parts, but I think I got enough info to start experimenting and my board is already up and running(only one mistake: Forgot to pull BOOT0).

I'll report back in due time, when I have some working code. I'm sure I'll have more questions for you throughout the project :)
 

Offline uer166

  • Super Contributor
  • ***
  • Posts: 1278
  • Country: us
Re: How to plan/structure interrupt-based code?
« Reply #30 on: September 21, 2020, 04:26:28 am »
Replacing interrupts by polling introduces a pretty serious design flaw because of the implicit temporal couplings. Polling may be simpler than using interrupts, but this simplicity makes the system fragile and hard to maintain.

You have temporal couplings between different interrupts and interrupts <-> main() context anyway. A polling-only architecture (a cooperative scheduler) is deterministic and safe if the actual application is written with requisite care (again as it would be anyway with interrupts).

https://www.safetty.net/download/pont_pttes_2001.pdf

 

Offline Fixpoint

  • Regular Contributor
  • *
  • Posts: 105
  • Country: de
Re: How to plan/structure interrupt-based code?
« Reply #31 on: September 21, 2020, 09:45:00 am »
You have temporal couplings between different interrupts and interrupts <-> main() context anyway.

This depends on how many flaws there are your software design. In many cases, it can be avoided. An obvious exception is when your application requires such extremely tight timing that you just cannot decouple things because the timing would be influenced by that.

Quote
A polling-only architecture (a cooperative scheduler) is deterministic and safe if the actual application is written with requisite care (again as it would be anyway with interrupts).

That is a null statement. "Your system will work if you do everything right." Aha.

But seriously:

(a) From the viewpoint of determinism and indeterminism, a cooperative scheduler is a very problematic thing, because it only has very little control about scheduling (that's what "cooperative" means), and a scheduler that only has little control isn't a great help when it comes to achieving safety and determinism. To be exact: a cooperative scheduler only has control over the ORDER of tasks, but not over the TIMING of tasks. But timing is a fundamental and crucial property of safety-critical systems. For example, if your ESP (in a car) is not timed exactly right down to a millisecond, then the driver might experience a surprise. So, a cooperative scheduler is actually an old, outdated, primitive and deeply flawed design that only works because the rest of the system is designed with these flaws in mind.

Because systems with cooperative schedulers are extremely unreliable and prone to crashing, such schedulers were banned from non-embedded systems during the 90s. It is ironic that safety-criticial embedded systems, of all things, still use them. I am pretty sure this is purely because of the huge code base and because a redesign is not deemed feasible, but not because it has anything to do with a reliable, solid software design.

Once, I myself worked for a large corporation in a team that was building a new operating system for cars. Because it was a new system, the scheduler was built from the ground up. For starters, in order to achieve safety, it wasn't a cooperative design. (It wasn't my design, by the way. The scheduler was already there when I joined the team.)

(b) Let us assume you have a really deterministic scheduler. Keep in mind that it does not nothing more than -- well, deterministic scheduling. If you use such a scheduler, ok, then your scheduling is determinstic. But that does still NOT mean that the *system itself* is deterministic. Each of your tasks needs to be deterministic, too. And this goes even beyond the timing aspect. That's not just academic blabla, it is very real.

(c) Finally: Determinism neither means that your system works nor that it is safe nor that it is maintainable. It just means that its semantics is defined in a specific way (which I surely shouldn't go into at this point), but that does not equate to "simple" or "easy". It could still be very complex and broken at 1000 dangerous places without anybody knowing it.
 
The following users thanked this post: I wanted a rude username

Offline mac.6

  • Regular Contributor
  • *
  • Posts: 226
  • Country: fr
Re: How to plan/structure interrupt-based code?
« Reply #32 on: September 21, 2020, 10:47:51 am »
Quite frankly, unless you are in a very thigh (space/timing) environment on a puny platform, you don't want to reinvent the wheel and code a scheduler by yourself.
Go directly to a rtos, yes it has some drawback but at least your productivity will increase and you won't end up debugging non core code.
 
The following users thanked this post: newbrain

Offline newbrain

  • Super Contributor
  • ***
  • Posts: 1917
  • Country: se
Re: How to plan/structure interrupt-based code?
« Reply #33 on: September 21, 2020, 10:49:27 am »
Nobody did it (explicitly, as yet, but Fixpoint's posts are quite the hint...see mac.6's post), so I'll throw this in the arena:
Consider using a RTOS:
The overhead, if you don't ned to squeeze the last drop of performance, is quite OK.
There's no need to reinvent, write and debug a scheduler (and many RTOSes provide a choice of schedulers) or synchronization primitives.
They are quite easy to learn.
They give a greater flexibility in adding and removing tasks.

The general approach is:
An HW event happens (e.g. char received by the UART, ADC conversion ready, the user turned an encoder, a DMA has finished)
An interrupt routine just caches the data (if needed), signals the the relevant task and exits, this limits the race and data sharing issues.
The task is scheduled according to its priority (e.g. higher for input tasks, lower for presenting data on a display etc.).

Having done it both ways for my projects, I'm starting to appreciate an RTOS (FreeRTOS in my case) more and more.

I wrote the code in my PSU in the superloop plus ISR way.
It works and is readable (if I say so), but a bit inflexible and hard to modify.

For the code for a DDS based generator (AD9834+TFT LCD+encoders+DACs+UART, so quite similar in scope) I used FreeRTOS, on an STM32F072.
It's clearer, easier to maintain and much more flexible, well worth the initial learning effort and the overhead.
For an SDR I'm working on, I would never have considered going bare metal.
« Last Edit: September 21, 2020, 10:51:03 am by newbrain »
Nandemo wa shiranai wa yo, shitteru koto dake.
 

Offline greenpossum

  • Frequent Contributor
  • **
  • Posts: 408
  • Country: au
Re: How to plan/structure interrupt-based code?
« Reply #34 on: September 21, 2020, 10:52:23 am »
Well it goes like this:

1. First you have to

Just a moment, I have an incoming phone call, back in a sec...

 :-DD
 
The following users thanked this post: newbrain

Offline dietert1

  • Super Contributor
  • ***
  • Posts: 3015
  • Country: br
    • CADT Homepage
Re: How to plan/structure interrupt-based code?
« Reply #35 on: September 21, 2020, 11:10:03 am »
I agree the best recommendation to plan/structure an embedded project with real-time requirements is to use a real-time operating system. The developers of those libraries thought a lot about all problems mentioned above and learning to use a library means learning a basic, standard approach. No need to invent anything.
The STM32 is strong enough to handle the overhead of using a predefined library. Beginning a project with optimization is a really bad idea.

Regards, Dieter
« Last Edit: September 21, 2020, 11:11:44 am by dietert1 »
 

Offline Fixpoint

  • Regular Contributor
  • *
  • Posts: 105
  • Country: de
Re: How to plan/structure interrupt-based code?
« Reply #36 on: September 21, 2020, 12:08:49 pm »
The STM32 is strong enough to handle the overhead of using a predefined library. Beginning a project with optimization is a really bad idea.

That is music for my ears.
 

Offline Howardlong

  • Super Contributor
  • ***
  • Posts: 5524
  • Country: gb
Re: How to plan/structure interrupt-based code?
« Reply #37 on: September 21, 2020, 12:38:09 pm »
Regarding using ISRs just to set flags, depending on the architecture, that might already be supported without even the need for an ISR. It's very common in PICs for example to poll a peripherals' xxxIF flag without the need for an ISR.

Whether or not to use an ISR for a given task is dependent on how timely a response is required.

Whether or not to use an RTOS is dependent on how complex the application is. Most of the time, I find it's simply not justified in deeply embedded systems. But if you start adding touch screen UIs etc, with other features such as networking, that's probably a good time to consider one. There's no such thing as a free lunch though, RTOSes by their very nature force you to structure code in certain ways, and there is usually some overhead associated with RTOSes in terms of performance and memory use.

 
The following users thanked this post: I wanted a rude username

Offline Renate

  • Super Contributor
  • ***
  • Posts: 1789
  • Country: de
    • Renate's Android Page
Re: How to plan/structure interrupt-based code?
« Reply #38 on: September 21, 2020, 01:48:53 pm »
Well, the obvious solution is to throw some gigaherz at it and use Windows. :-DD

Unrelated aside: Worst case preamble/postamble for AVR8 ISR is a total of 38 instructions.
Code: [Select]
1f 92  push r1
0f 92  push r0
0f b6  in   r0, 0x3f
0f 92  push r0
11 24  eor  r1, r1
2f 93  push r18
3f 93  push r19
4f 93  push r20
5f 93  push r21
6f 93  push r22
7f 93  push r23
8f 93  push r24
9f 93  push r25
af 93  push r26
bf 93  push r27
cf 93  push r28
df 93  push r29
ef 93  push r30
ff 93  push r31

ff 91  pop  r31
ef 91  pop  r30
df 91  pop  r29
cf 91  pop  r28
bf 91  pop  r27
af 91  pop  r26
9f 91  pop  r25
8f 91  pop  r24
7f 91  pop  r23
6f 91  pop  r22
5f 91  pop  r21
4f 91  pop  r20
3f 91  pop  r19
2f 91  pop  r18
0f 90  pop  r0
0f be  out  0x3f, r0
0f 90  pop  r0
1f 90  pop  r1
18 95  reti
 

Offline dietert1

  • Super Contributor
  • ***
  • Posts: 3015
  • Country: br
    • CADT Homepage
Re: How to plan/structure interrupt-based code?
« Reply #39 on: September 21, 2020, 02:35:43 pm »
In a 32 bit arm processor there are instructions for push and pop multiple registers. The interrupt context switch in a STM32 will be a fraction of a microsecond. No need for gigahertz.

Regards, Dieter
 

Offline Renate

  • Super Contributor
  • ***
  • Posts: 1789
  • Country: de
    • Renate's Android Page
Re: How to plan/structure interrupt-based code?
« Reply #40 on: September 21, 2020, 02:50:26 pm »
In a 32 bit arm processor there are instructions for push and pop multiple registers.
In the Arm FIQ interrupt there is register bank switching too.
 

Offline NorthGuy

  • Super Contributor
  • ***
  • Posts: 3567
  • Country: ca
Re: How to plan/structure interrupt-based code?
« Reply #41 on: September 21, 2020, 03:26:37 pm »
In a 32 bit arm processor there are instructions for push and pop multiple registers. The interrupt context switch in a STM32 will be a fraction of a microsecond.

Not that it matters, but the STM/LDM instructions certainly don't execute in a single clock and their execution time increases proportionally with the number of registers being saved/loaded.
 
The following users thanked this post: Howardlong

Offline Howardlong

  • Super Contributor
  • ***
  • Posts: 5524
  • Country: gb
Re: How to plan/structure interrupt-based code?
« Reply #42 on: September 21, 2020, 03:33:46 pm »
In a 32 bit arm processor there are instructions for push and pop multiple registers. The interrupt context switch in a STM32 will be a fraction of a microsecond.

Not that it matters, but the STM/LDM instructions certainly don't execute in a single clock and their execution time increases proportionally with the number of registers being saved/loaded.

Add to that, ARM is, by design, solidly register based. That's a key point of its architecture, do as much as possible on the core without having to refer to lower speed off-core memory. Therefore, potentially you have a lot of context to save, and latency suffers as a result. The STM/LDM only saves you multiple instruction fetches, it doesn't save you the time to save/load to off-core memory.

In some MIPS implementations (and possibly some ARM implementations, I don't know), there are shadow register sets on the core, allowing instant context switching for the most demanding of scenarios.

 

Offline rstofer

  • Super Contributor
  • ***
  • Posts: 10089
  • Country: us
Re: How to plan/structure interrupt-based code?
« Reply #43 on: September 21, 2020, 04:04:58 pm »
Research the GCC function attribute 'naked'.  If a function has this attribute, there will be no prolog or epilog code generated.  It's all on you!  But if all you are doing is setting a flag, you can do a wee bit of assembly code to save a register, set the register to point to the flag and then stuff a '1' in the flag.  Restore the register and return.

Here's another idea that comes from early gaming:  If you use a superloop, you can have a loop counter, say 0..3.  Every time through the loop with a particular counter value, certain functions are executed, others are skipped.  Important functions might get called on every pass and so on.

The right way to do this stuff is with an RTOS like FreeRTOS which has ports for all popular processors.

Depending on what needs to be done and how quick, consider a PSoC 6 with dual cores - use the M4 core for the mainline stuff and use the M0 core for the interrupt driven and IO stuff.  Yes, there a FreeRTOS port for the PSoC 6.  There are a bunch of videos as well.  Just Google for 'PSoC 6 videos'


 
The following users thanked this post: I wanted a rude username

Offline emece67

  • Frequent Contributor
  • **
  • !
  • Posts: 614
  • Country: 00
Re: How to plan/structure interrupt-based code?
« Reply #44 on: September 21, 2020, 04:13:00 pm »
.
« Last Edit: August 19, 2022, 03:57:46 pm by emece67 »
 

Offline Howardlong

  • Super Contributor
  • ***
  • Posts: 5524
  • Country: gb
Re: How to plan/structure interrupt-based code?
« Reply #45 on: September 21, 2020, 05:29:20 pm »
I'm going to strongly disagree with those calling for RTOS as a blanket approach. As I mentioned, in deeply embedded systems it's rare to need one, and I'd say they usually get in the way in most of those cases.

There's definitely a time and a place for RTOSes, but, if you don't care much about resources, power consumption, BOM cost, development cost, maintenance cost, upskilling etc, then whatever floats your boat on your blinky project ;-)

I've just always found that approach disappointing and defeatist, it makes me wonder what else might be being overlooked. :-(

There's certainly absolutely nothing in the OP's specification that justifies an RTOS, unless they want something extra to have to learn. If someone came to me with that spec and said they'd be using an RTOS, I'd be asking for some justification. Edit: think over-engineering.

« Last Edit: September 21, 2020, 05:32:22 pm by Howardlong »
 

Offline rstofer

  • Super Contributor
  • ***
  • Posts: 10089
  • Country: us
Re: How to plan/structure interrupt-based code?
« Reply #46 on: September 21, 2020, 05:40:39 pm »
One thing an RTOS provides, as a side benefit, is modularizing the code.  Each task stands alone - it takes inputs from queues/semaphonres and delivers outputs to pins or other queues/semaphores.  But the entire process is a building block wrapped in a task.

There's certainly nothing wrong with avoiding the RTOS in favor of some other scheme, probably a superloop although it does seem wasteful to use a bunch of cycles to test whether some function should be called based, perhaps, on a flag.  Considering the prolog and epilog code, we don't want to call a function unless we already know it has work to do.

The first thing to get straight is the required response time to external events.
 

Offline Sal Ammoniac

  • Super Contributor
  • ***
  • Posts: 1811
  • Country: us
Re: How to plan/structure interrupt-based code?
« Reply #47 on: September 21, 2020, 05:54:22 pm »
I'm going to strongly disagree with those calling for RTOS as a blanket approach. As I mentioned, in deeply embedded systems it's rare to need one, and I'd say they usually get in the way in most of those cases.

There's definitely a time and a place for RTOSes, but, if you don't care much about resources, power consumption, BOM cost, development cost, maintenance cost, upskilling etc, then whatever floats your boat on your blinky project ;-)

I disagree. An RTOS doesn't "get in the way" if you use it correctly, nor does it necessarily consume a lot of resources/power/cost or what have you.

Sure, if your "RTOS" is something like embedded Linux, then I'd agree that can be a resource hog, but a small RTOS running on something like a Cortex-M MCU can be very small and still get the job done.

As an example, my RTOS takes up 8-15K of flash (depending on how it's configured) and about 4K of RAM (excluding task stacks). That may be a lot if you're developing for a PIC MCU with 2K of flash and 256 bytes of RAM, but on the MCU I typically use (2M flash/512K RAM), it's nothing.

An RTOS makes sense when your embedded application has multiple pieces that interact in complex ways. Yes, you can implement such a system without an RTOS, but maintaining and extending such a system becomes difficult when any change has the potential to throw off timing and synchronization between the various parts of the system.
"That's not even wrong" -- Wolfgang Pauli
 

Offline NivagSwerdna

  • Super Contributor
  • ***
  • Posts: 2571
  • Country: gb
Re: How to plan/structure interrupt-based code?
« Reply #48 on: September 21, 2020, 06:12:29 pm »
This is one of those questions where you get loads of replies since it is such a common pattern.  I see loads of wisdom earlier in the thread but I can't resist my 2c worth...

Have interrupts for things that need to happen in a timely fashion or that signal completion of a long running task...

The obvious interrupts in your case are :
  • Serial receive (so you don't saturate the receive buffer).  In the ISR just push bytes onto a ring buffer
  • Regular timer expiry for use in flashing LEDs and/or de-bouncing push buttons
  • ADC Completion (if conversions are slow relative to clock)
Everything else can stay in main in a while forever loop.

Sounds like a fun project

 

Online Kleinstein

  • Super Contributor
  • ***
  • Posts: 17277
  • Country: de
Re: How to plan/structure interrupt-based code?
« Reply #49 on: September 21, 2020, 06:41:21 pm »
How bad the overhead from an RTOS is depend on the tasks to do. If there are short but very frequently called jobs, a RTOS can be a hard no-go slowing things down up to an order of magnitude. Some tasks can be short but run quite often (e.g. a software implemented DDS that takes only a few cycles at a time).
An RTOS can help a lot if one has tasks to do that the OS already provides (e.g. buffered UART or similar).

There are different ways to program many concurrent jobs to be done with the µC: RTOS, all in ISRs, partially in main loop, separate task scheduling, all in main loop. It depends on the job which system works good and which may not work at all. If in doubt one may have to evaluate different possible solutions and than decide which path is the best match - here may be different criteria for best: lowest power, readable code, resources , estimated development time, potential for failure.  Sometimes it help to look at the problem from a different side, so the time spend on a alternative concept is not really lost.

In addition to the pure software side modern µCs also have special hardware functions like special periphery, DMAs and event systems that can do quite some jobs (e.g. buffered UART) mainly in hardware, that needed coding by hand on older systems. So it may not be just the SW, but also planing to use HW resources. DMAs can help, but they could also make data access and sometimes the needed run time tricky.
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf

 

- - -
-->