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

0 Members and 1 Guest are viewing this topic.

Offline petemateTopic starter

  • Regular Contributor
  • *
  • Posts: 184
How to plan/structure interrupt-based code?
« on: September 18, 2020, 07:10:53 pm »
Hi guys, I have done simple microcontroller code before(think "everthing in one main loop), but for this new project I'm doing, I need to handle a "control loop" as well as LED indicators, ADC sampling and serial communication. As far as I understand, you use interrupts and/or timers to make sure that your relevant code is executed at the right time(or rather, at a certain repetition). I am looking for guides or principles regarding how you structure this principle of controlling "several things at once".

The project is about making an analog(+/- 10V programming voltrage) PSU controlable from a digital(USB/serial) input. I would need to be able to send commands via a virtual com port to set voltage and read back information in a SCPI kinda way. E.g. "set:volt 10" would then set the output voltage of the PSU to 10V. "output on/off", etc. Sending these commands should then set the analog programming voltage to whatever and turn on the PSU through the analog on/off pin. The ADC should then read the voltage(there is a 0..10V monitor voltage as well) and adjust the outptut to that it is a close as possible to the actual voltage desired. In addition, are also a number of open collector outputs from the PSU that can inform about over-temperature, CC, CV, etc. I'd like to "scan" these as well.

I produced a small PCB with the required hardware, based on an STM32F103 mcu. Now i need to get going with the structure of the code. How do I plan the "interrupt layout" to have all these functions running "simultaneously"? Its kind of a "structure" or "principles of programming" kind of question, but I hope someone can give me some pointers.

Thanks!

 

Offline GromBeestje

  • Frequent Contributor
  • **
  • Posts: 310
  • Country: nl
Re: How to plan/structure interrupt-based code?
« Reply #1 on: September 18, 2020, 07:28:39 pm »
My approach would be something like this: do as little as possible in the interrupt. rather set a flag that triggers something in the main loop. Have timers set a flag it is time to do something, have the ADC values written to a variable and set a flag the data is ready.
Make sure you declare the variables you set inside an interrupt as volatile.
 
The following users thanked this post: petemate

Offline petemateTopic starter

  • Regular Contributor
  • *
  • Posts: 184
Re: How to plan/structure interrupt-based code?
« Reply #2 on: September 18, 2020, 08:42:15 pm »
My approach would be something like this: do as little as possible in the interrupt. rather set a flag that triggers something in the main loop. Have timers set a flag it is time to do something, have the ADC values written to a variable and set a flag the data is ready.
Make sure you declare the variables you set inside an interrupt as volatile.

Thanks for your input! I wasn't even aware of the volatile declaration: I read this blog post and its clear that I need to take this into account. https://barrgroup.com/embedded-systems/how-to/c-volatile-keyword

I'll try to keep the interrupts as short as possible and use flags instead.
 

Offline NorthGuy

  • Super Contributor
  • ***
  • Posts: 3567
  • Country: ca
Re: How to plan/structure interrupt-based code?
« Reply #3 on: September 18, 2020, 08:47:47 pm »
I would put the control loop inside a timer interrupt - read ADC, decide on the adjustments, do them, exit ISR.

Everything else doesn't seem to be time-critical and can wait, so do it all in the main loop in a round-robin fashion.
 
The following users thanked this post: petemate

Offline TomS_

  • Frequent Contributor
  • **
  • Posts: 861
  • Country: gb
Re: How to plan/structure interrupt-based code?
« Reply #4 on: September 18, 2020, 09:08:52 pm »
If jitter is a concern, you may need to do your work in the ISR. Using the ISR to set a flag and then have an action performed in the main loop will not be deterministic, and jitter will vary depending on where are are in the main loop when the flash is set.

Or you have to go to a lot of effort to have your main loop tasks yield when various flags have been set to allow a higher priority task to run, but there will still be an amount of jitter involved.

So you might split the work between the ISR and the main loop, e.g.: ISR starts an ADC conversion and sets a flag, main loop can then wait for the conversion and process the result in time - but at least you captured the result with absolutely minimal jitter.
 
The following users thanked this post: petemate

Offline ggchab

  • Frequent Contributor
  • **
  • Posts: 292
  • Country: be
Re: How to plan/structure interrupt-based code?
« Reply #5 on: September 18, 2020, 10:28:37 pm »
I have done something very similar with an 8 bit PIC (18F25K22).The only thing that is easier via interrupts is sending/receiving characters on the serial port via buffers that can be read/written from the application. If you need delays, you may also use a timer that set flags, decrements counters, ... via interrupts. This can be seen as "services" offered to the application.
Time critical operations may also be handled via interrupts (ex.: detecting power cut to save parameters in eeprom).

Your main loop should be as simple as possible (ex.: calling a function per task). Each task should be well disciplined and keep CPU a minimum of time. Each task may divide itself in small sub-tasks and only one of them  is executed when the function is called.

 
The following users thanked this post: petemate

Offline Nominal Animal

  • Super Contributor
  • ***
  • Posts: 8349
  • Country: fi
    • My home page and email address
Re: How to plan/structure interrupt-based code?
« Reply #6 on: September 18, 2020, 10:31:51 pm »
petemate, the approach or pattern or paradigm is called event-driven programming.

If you've done ordinary Arduino sketches, that one is imperative programming.

The difference is that in imperative programming, you the programmer are in complete control of what the microcontroller does at any point in time; in event-driven programming, you write code that reacts to different events.  With hardware interrupts (and e.g. POSIX signals in Linux/Mac/BSD OSes), those events are usually different functions, and while the event (function) is being executed, nothing else happens.  The event can occur anywhere except in critical sections (where interrupts, or POSIX signals in Linux/Mac/BSD OSes, are blocked).  Because of this, it is crucial that the event handler does not sleep or dilly-dally.

Exactly how to plan or structure your code, depends on whether you have BOTH interrupt (event-based) actions AND continuously-running "work".

Let's say you have a simple sketch that generates a pulse stream, say for LEDs or some such, and some buttons that start, stop, and change the stream being output.
For the three buttons, you could install interrupt functions (with software debouncing), that simply change static volatile bool or static volatile int flags.  (Remember, Arduino uses GNU C++.  static in the file scope means the variable or function it is only visible in this file scope; you can omit it if it weirds you out.)
The loop() function would generate the next pulse, except that first it would check the flags, and change its operating mode accordingly.  (The loop() function would basically be the iterative part of a simple finite state machine.)

Okay, but why would anyone use an interrupt for that, instead of just checking the button states at the beginning?

Because with the interrupt approach, you can detect keypresses much shorter than the duration of a single iteration of the loop.
Essentially, this sketch would have a continuously running pulse generator, which regularly checks certain volatile flags that modify its operation.  Those flags are updated by the interrupt functions.

There is one nasty detail: Interrupts can be interrupted by higher-priority ones, too – you can usually change the interrupt priorities, though – and "atomicity" is an issue.  What "atomicity" means, is that when changing some variable, it may actually require more than one underlying machine instruction, and if interrupted in mid-modification, with the interrupt handling function seeing a partially modified value.  If that also modifies the value, and execution returns to the earlier interrupt which only modifies the rest of the variable value, the value becomes corrupted.

If the interrupt only examines the flags, and does not change their values, there can be no problem.  In the case where interrupt functions only set a flag, and normal (loop()) code only reads the flags, you do not need to worry about atomicity.  The atomicity issues only occur when both normal code and interrupt code can modify the same variable.

Different environments provide different tools to work with this.  The Arduino environment provides ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { critical operations } macro when <util/atomic.h> is included.  The GCC compiler provides __atomic_ built-in functions (functions provided by the GCC compiler, not by any library or environment, for a given hardware architecture).  In pseudocode, the typical sequence is
Code: [Select]
    Let  intsEnabled = (state of interrupts enabled status flag)
    If intsEnabled:
        Disable_Interrupts
    End If

    Critical operations

    If intsEnabled:
        Enable_Interrupts
    End If
(This is what the Arduino macro on AVRs does, expanding into a single-iteration for loop.)
The idea is that interrupts are disabled for the duration of the atomic or critical operations, and if enabled before, re-enabled afterwards.  (If these critical operations happen to be part of a larger set of critical operations, interrupts could be already disabled – even if we are in an interrupt handler.  That's the reason why simply disabling and re-enabling interrupts may not always work the way intended!)
The GCC atomic operations use simple hardware instructions for variable sizes that are known to be atomic on that hardware, and some other code for larger sizes.

Regardless of which environment – or even bare-metal – you use, the above concepts do apply.  Only the details vary between environments (hardware and libraries).
« Last Edit: September 18, 2020, 10:34:24 pm by Nominal Animal »
 
The following users thanked this post: petemate, I wanted a rude username

Offline Fixpoint

  • Regular Contributor
  • *
  • Posts: 105
  • Country: de
Re: How to plan/structure interrupt-based code?
« Reply #7 on: September 18, 2020, 10:40:59 pm »
To OP: There are several ways to solve this, so it depends on the details of your application. For a clean software design, you would make use of knowledge from distributed and event-oriented systems.

The following two things are especially important:

  • You should separate the different layers of abstraction from each other. In this instance, you have two layers: the technical MCU stuff (interrupt handling) and the control logic layer (your actual event handling). Don't mix those two but decouple interrupt handling from event handling. That's important for maintainability, understandability, and also correctness.
  • Do your event handlers modify shared system state that is prone to race conditions? If yes, you have to care for mutual exclusion, either via queues, temporary interrupt disablement or via a specific mutex algorithm. Interrupt disablement is not necessarily a good idea because the system can become unresponsive.
    I personally lean towards a queue. This would entail not to call the event handler directly from your interrupt handler but to put the event into a queue. This is an extremely fast operation, and the interrupt handler can exit very quickly. Then, your main loop executes the events that are stored in the queue. This way, it cannot happen that multiple events are executed simultaneously, the system is automatically free from race conditions provided that you have correct resource management w.r.t. your MCU state and you have cared for mutual exclusion on the queue itself. However, this approach increases LATENCY and it's not necessarily ok for you. I don't know your particular parameters, so it depends on your situation.
« Last Edit: September 18, 2020, 10:44:44 pm by Fixpoint »
 
The following users thanked this post: petemate

Offline petemateTopic starter

  • Regular Contributor
  • *
  • Posts: 184
Re: How to plan/structure interrupt-based code?
« Reply #8 on: September 18, 2020, 11:32:46 pm »
I would put the control loop inside a timer interrupt - read ADC, decide on the adjustments, do them, exit ISR.

Everything else doesn't seem to be time-critical and can wait, so do it all in the main loop in a round-robin fashion.


Thank you for your reply! A agree that the control loop is to be controlled at a specific instance, so that should definitely be interrupt-based. As pointed out elsewhere, jitter could become an issue if it is not done this way.

I would like some LEDs to do various flashing patterns(e.g. a fast blinking red LED as an alarm indication. How would I handle the blinking of the LED? Should that also be interrupt based?

I don't know yet how the USB com port thingie works, bud i'd assume there'd be some functions for this. Maybe an interrupt every time a buffer is full?

If jitter is a concern, you may need to do your work in the ISR. Using the ISR to set a flag and then have an action performed in the main loop will not be deterministic, and jitter will vary depending on where are are in the main loop when the flash is set.

Or you have to go to a lot of effort to have your main loop tasks yield when various flags have been set to allow a higher priority task to run, but there will still be an amount of jitter involved.

So you might split the work between the ISR and the main loop, e.g.: ISR starts an ADC conversion and sets a flag, main loop can then wait for the conversion and process the result in time - but at least you captured the result with absolutely minimal jitter.


Thanks for replying! Good point on jitter if the control loop is not run in an ISR.

I have done something very similar with an 8 bit PIC (18F25K22).The only thing that is easier via interrupts is sending/receiving characters on the serial port via buffers that can be read/written from the application. If you need delays, you may also use a timer that set flags, decrements counters, ... via interrupts. This can be seen as "services" offered to the application.
Time critical operations may also be handled via interrupts (ex.: detecting power cut to save parameters in eeprom).

Your main loop should be as simple as possible (ex.: calling a function per task). Each task should be well disciplined and keep CPU a minimum of time. Each task may divide itself in small sub-tasks and only one of them  is executed when the function is called.




Thank you for your input. I will definitely try to do a highly structured code and avoid a messy main loop.

petemate, the approach or pattern or paradigm is called event-driven programming.

If you've done ordinary Arduino sketches, that one is imperative programming.

The difference is that in imperative programming, you the programmer are in complete control of what the microcontroller does at any point in time; in event-driven programming, you write code that reacts to different events.  With hardware interrupts (and e.g. POSIX signals in Linux/Mac/BSD OSes), those events are usually different functions, and while the event (function) is being executed, nothing else happens.  The event can occur anywhere except in critical sections (where interrupts, or POSIX signals in Linux/Mac/BSD OSes, are blocked).  Because of this, it is crucial that the event handler does not sleep or dilly-dally.

Exactly how to plan or structure your code, depends on whether you have BOTH interrupt (event-based) actions AND continuously-running "work".

Let's say you have a simple sketch that generates a pulse stream, say for LEDs or some such, and some buttons that start, stop, and change the stream being output.
For the three buttons, you could install interrupt functions (with software debouncing), that simply change static volatile bool or static volatile int flags.  (Remember, Arduino uses GNU C++.  static in the file scope means the variable or function it is only visible in this file scope; you can omit it if it weirds you out.)
The loop() function would generate the next pulse, except that first it would check the flags, and change its operating mode accordingly.  (The loop() function would basically be the iterative part of a simple finite state machine.)

Okay, but why would anyone use an interrupt for that, instead of just checking the button states at the beginning?

Because with the interrupt approach, you can detect keypresses much shorter than the duration of a single iteration of the loop.
Essentially, this sketch would have a continuously running pulse generator, which regularly checks certain volatile flags that modify its operation.  Those flags are updated by the interrupt functions.

There is one nasty detail: Interrupts can be interrupted by higher-priority ones, too – you can usually change the interrupt priorities, though – and "atomicity" is an issue.  What "atomicity" means, is that when changing some variable, it may actually require more than one underlying machine instruction, and if interrupted in mid-modification, with the interrupt handling function seeing a partially modified value.  If that also modifies the value, and execution returns to the earlier interrupt which only modifies the rest of the variable value, the value becomes corrupted.

If the interrupt only examines the flags, and does not change their values, there can be no problem.  In the case where interrupt functions only set a flag, and normal (loop()) code only reads the flags, you do not need to worry about atomicity.  The atomicity issues only occur when both normal code and interrupt code can modify the same variable.

Different environments provide different tools to work with this.  The Arduino environment provides ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { critical operations } macro when <util/atomic.h> is included.  The GCC compiler provides __atomic_ built-in functions (functions provided by the GCC compiler, not by any library or environment, for a given hardware architecture).  In pseudocode, the typical sequence is
Code: [Select]
    Let  intsEnabled = (state of interrupts enabled status flag)
    If intsEnabled:
        Disable_Interrupts
    End If

    Critical operations

    If intsEnabled:
        Enable_Interrupts
    End If
(This is what the Arduino macro on AVRs does, expanding into a single-iteration for loop.)
The idea is that interrupts are disabled for the duration of the atomic or critical operations, and if enabled before, re-enabled afterwards.  (If these critical operations happen to be part of a larger set of critical operations, interrupts could be already disabled – even if we are in an interrupt handler.  That's the reason why simply disabling and re-enabling interrupts may not always work the way intended!)
The GCC atomic operations use simple hardware instructions for variable sizes that are known to be atomic on that hardware, and some other code for larger sizes.

Regardless of which environment – or even bare-metal – you use, the above concepts do apply.  Only the details vary between environments (hardware and libraries).


Thank you very much for your writeup. Getting the lingo right is important to me and its good to know what I'm actually asking about :) I read a little bit about atomic operations and static variables and while i think I have it covered, its good to know about potential issues. Thanks again!

To OP: There are several ways to solve this, so it depends on the details of your application. For a clean software design, you would make use of knowledge from distributed and event-oriented systems.

The following two things are especially important:

  • You should separate the different layers of abstraction from each other. In this instance, you have two layers: the technical MCU stuff (interrupt handling) and the control logic layer (your actual event handling). Don't mix those two but decouple interrupt handling from event handling. That's important for maintainability, understandability, and also correctness.
  • Do your event handlers modify shared system state that is prone to race conditions? If yes, you have to care for mutual exclusion, either via queues, temporary interrupt disablement or via a specific mutex algorithm. Interrupt disablement is not necessarily a good idea because the system can become unresponsive.
    I personally lean towards a queue. This would entail not to call the event handler directly from your interrupt handler but to put the event into a queue. This is an extremely fast operation, and the interrupt handler can exit very quickly. Then, your main loop executes the events that are stored in the queue. This way, it cannot happen that multiple events are executed simultaneously, the system is automatically free from race conditions provided that you have correct resource management w.r.t. your MCU state and you have cared for mutual exclusion on the queue itself. However, this approach increases LATENCY and it's not necessarily ok for you. I don't know your particular parameters, so it depends on your situation.


Thanks for your suggestion! I like the idea about a queue, but I'm afraid the overhead and work involved would drag me away from the actual task of programming this thing :) I'd like to keep it as simple as possible, as I'm already in over my head :)
 

Offline Renate

  • Super Contributor
  • ***
  • Posts: 1789
  • Country: de
    • Renate's Android Page
Re: How to plan/structure interrupt-based code?
« Reply #9 on: September 18, 2020, 11:38:05 pm »
If what you're doing isn't rocket science, one approach is to have your main loop be:
Code: [Select]
for(;;) sleep();Just handle everything in your interrupt routines.
 

Offline Fixpoint

  • Regular Contributor
  • *
  • Posts: 105
  • Country: de
Re: How to plan/structure interrupt-based code?
« Reply #10 on: September 19, 2020, 12:03:16 am »
Thanks for your suggestion! I like the idea about a queue, but I'm afraid the overhead and work involved would drag me away from the actual task of programming this thing :) I'd like to keep it as simple as possible, as I'm already in over my head :)

The overhead is almost non-existent, that's exactly one of the advantages of this approach. There is only potentially higher latency, but overhead is basically 0. The latency could be increased because an incoming event has to wait until its predecessor has finished, but that is probably anyway what you want.

In its simplest implementation, the queue would just be an array together with an index into the array, so you don't have to implement complicated things.
 
The following users thanked this post: petemate

Offline Nominal Animal

  • Super Contributor
  • ***
  • Posts: 8349
  • Country: fi
    • My home page and email address
Re: How to plan/structure interrupt-based code?
« Reply #11 on: September 19, 2020, 12:51:03 am »
I would like some LEDs to do various flashing patterns(e.g. a fast blinking red LED as an alarm indication. How would I handle the blinking of the LED? Should that also be interrupt based?

Sure, why not.  Consider the following almost-standard C (it needs usleep() or Sleep() to sleep for a fraction of a second between "interrupts", but one should be provided by your OS):
Code: [Select]
#define _POSIX_C_SOURCE  200112L
#define _DEFAULT_SOURCE
#include <stdlib.h>
#include <stdint.h>  /* This is available even in freestanding environments! */
#include <stdio.h>

/* Hack to implement sleep_ms(milliseconds) on different OSes */
#if defined(_WIN32) || defined(_WIN64)
#include <synchapi.h>
static void sleep_ms(unsigned long ms) { Sleep(ms); }
#else
#include <unistd.h>
static void sleep_ms(unsigned long ms) { usleep(ms * 1000L); }
#endif

/* Pulse train macros */
#define  PULSE_END()             0
#define  PULSE(ontime, offtime)  (((offtime) & 15) | (((ontime) & 15) << 4))
#define  PULSE_OFFVAL(value)     ((value) & 15)
#define  PULSE_ONVAL(value)      (((value) >> 4) & 15)

/* Pulse train variables */
static volatile uint_fast8_t        pulse_mode = 0;  /* 0 = stopped, 1 = on, 2 = off */
static volatile uint_fast8_t        pulse_left = 0;  /* Ticks left in current state */
static volatile const uint_fast8_t *pulse_next = NULL;

static void pulse_interrupt(void)
{
    /* No pulse train? */
    if (!pulse_mode || !pulse_next)
        return;

    /* Current state still active? */
    if (pulse_left--)
        return;

    /* Which transition? */
    if (pulse_mode == 1) {
        /* On -> Off */
        fprintf(stderr, "\r- ");
        pulse_left = PULSE_OFFVAL(*pulse_next);
        pulse_mode = 2;
        pulse_next++;

    } else
    if (*pulse_next) {
        /* Off -> On */
        fprintf(stderr, "\rX ");
        pulse_left = PULSE_ONVAL(*pulse_next);
        pulse_mode = 1;

    } else {
        /* End of pulse train. */
        fprintf(stderr, "\r \n");
        pulse_left = 0;
        pulse_mode = 0;
        pulse_next = NULL;
    }
}

static const uint_fast8_t  pwm[] = {
    /* X----     XX---        XXX--        XXXX-        XXX--        XX---        XXXX-        */
    PULSE(1, 4), PULSE(2, 3), PULSE(3, 2), PULSE(4, 1), PULSE(3, 2), PULSE(2, 3), PULSE(4, 1), PULSE_END()
};


int main(void)
{
    /* Set up pulse train */
    pulse_next = NULL;
    pulse_mode = 2;
    pulse_left = 0;
    pulse_next = pwm;

    /* Simulate interrupts every 200ms = 0.2s until pulse train is complete. */
    while (pulse_mode) {
        pulse_interrupt();
        sleep_ms(200);
    }

    return EXIT_SUCCESS;
}
The pwm[] array specifies the pulse train, one byte per pulse.  A zero byte ends the pulse train. High nibble defines the duration of the ON phase, and the low nibble the duration of the OFF phase.  Thus, you can also specify pulse trains as strings, like "\xGH\xIJ\xKL\0", where G, I, and K are hexadecimal digits specifying the durations of the three ON pulses, H and J the intervals between the pulses, and L the duration after the last pulse before the pulse train is complete.

To stop an ongoing pulse train, do pulse_next = NULL; pulse_mode = 0; pulse_left = 0;
To start, restart, or switch to a new pulse train, do pulse_next = NULL; pulse_mode = 2; pulse_left = 0; pulse_next = pwm; where pwm is an array of pulse definitions, or a pointer to the next pulse.
If you look at the function, you'll see that clearing pulse_next ensures the interrupt does nothing.  Do note that you can only do this safely in your "main" code (non-interrupt code), or in an interrupt that cannot interrupt the pulse interrupt; you may need to check the interrupt priorities to make sure.

Here, the event is a timer "tick", a regularly occurring event.  In the handler, we use the pulse_left variable to tell us how many ticks we have left until next pulse edge.  When it is zero when the handler is called, we handle the next edge.  There are three such edges:  off to on, on to off, and end of pulse train.
As a finite state machine, it really just has five different states.

The main() just simulates the interrupt.  Feel free to change the state change fprintf(stderr, ) prints (if you switch to standard output, remember to add fflush(stdout); so that your C library outputs the data immediately, instead of caching until the next newline).

For those wondering why I posted this, I'm hoping the real-world example gives the OP an intuitive "feel" of event-driven programming, in the form of a simple C text-mode application; at the same time providing a simple "test bench pattern" of "simulating" interrupt-driven C programming in easy standard/hosted C environment.
 
The following users thanked this post: petemate

Offline T3sl4co1l

  • Super Contributor
  • ***
  • Posts: 22435
  • Country: us
  • Expert, Analog Electronics, PCB Layout, EMC
    • Seven Transistor Labs
Re: How to plan/structure interrupt-based code?
« Reply #12 on: September 19, 2020, 02:31:29 am »
Note that a power supply doesn't need much, or a very reliable, sample rate, so you could very well do all of this in the main() loop -- no interrupts needed:

Code: [Select]
sample_rate_timer_reset();
while (1) {
adc_start_acquisition_on_channel(current_index);

command_buffer(serial_getc());
if (command_received()) {
process_command();
}

if (adc_done() && sample_rate_timer()) {
sample_rate_timer_reset();
adc_readings[current_index] = adc_new_value();
current_index++;
if (current_index >= (sizeof(adc_readings) / sizeof(adc_readings[0])) {
current_index = 0;
update_display();
}
}
}

This loop has a worst case execution time of [command processing time].  If this is always shorter than the time to receive a serial character, you won't miss anything.  Or if it's hardware buffered (like a 16550 UART), and you're guaranteed to never have a command so long that that buffer overflows, same idea.

Presumably, the command processing will be very brief in the average case (receiving single characters?), and only a little longer when actually doing something.  So the sample rate and jitter won't be that nasty, and you are unlikely (or guaranteed not) to miss serial characters.

Missing characters during command execution might still be acceptable behavior, anyway.  Or with flow control (Xon/Xoff or hardware), this is intended behavior!  (Flow control means the host machine stops sending characters until the client is ready to receive more.  Xon/Xoff uses special ASCII characters inline, so can't be used with a binary control set.)

Now, if you want to use it as an arb gen (setting a new setpoint on potentially every sample time), you'll want a more stable sample rate, and it's probably a good idea to trigger the DAC update from an interrupt (maybe from a timer).


Note that I went right to discussing the execution time of the loop.  That's the real-time dependency here.  If you use interrupts, you can have all the slow and noncritical stuff running in main(), and not really care about this; but you still need to do your due diligence -- and in different and sometimes surprising ways.  Namely, thread safety: shared variables need to be tagged volatile (which tells the compiler to be proactive when reading/writing that memory location), and access needs to be atomic so that the interrupt is never reading or writing a value when main() is using it.

In that case, a typical application would be using buffered serial I/O, and probably a timer or ADC interrupt to do the signal stuff.  With buffering, atomic access is handled by the driver interface, and you can use non-blocking functions to read and write (that is, they execute faster than it takes to twiddle the actual serial characters).  Don't put command execution into the interrupts: the interrupt should return quickly.  Instead, set a flag and let main() deal with it.  (This can even be expanded into an event system, though that would be complete overkill for a project like this!)

Tim
Seven Transistor Labs, LLC
Electronic design, from concept to prototype.
Bringing a project to life?  Send me a message!
 
The following users thanked this post: petemate

Offline emece67

  • Frequent Contributor
  • **
  • !
  • Posts: 614
  • Country: 00
Re: How to plan/structure interrupt-based code?
« Reply #13 on: September 19, 2020, 11:35:19 am »
.
« Last Edit: August 19, 2022, 03:55:24 pm by emece67 »
 
The following users thanked this post: petemate

Offline Renate

  • Super Contributor
  • ***
  • Posts: 1789
  • Country: de
    • Renate's Android Page
Re: How to plan/structure interrupt-based code?
« Reply #14 on: September 19, 2020, 12:04:46 pm »
You will want to use neither wait() nor sleep()...
Just to clarify, I agree, one should never use a non-interruptable sleep.
When I mentioned sleep above, I meant specifically the AVR8 instruction SLEEP which (in one mode) means, go to idle and wait for interrupt.
 
The following users thanked this post: petemate

Offline snarkysparky

  • Frequent Contributor
  • **
  • Posts: 441
  • Country: us
Re: How to plan/structure interrupt-based code?
« Reply #15 on: September 19, 2020, 01:37:10 pm »
I always make a 1ms interrupt for program timers.

Timer_X_handler  // 1 ms timouts
{
    if(LedTimer)LedTimer--;
    if(CheckSerialTimer)CheckSerialTimer--;
    *
    *
    *
    DoOneMsLoop = 1;

}

To use a timer just set it to the desired value in ms.

Then in code

If(LedTimer)LedTimerTimingFlag = 1;
   else if(LedTimerTimingFlag)
   {
         LedTimerTimingFlag = 0;
         // do whatever you want to do when timer expires
    }

use a que for serial communications with RX interrupt.

If any task can't be interrupted give its ISR higher priority.

 
The following users thanked this post: petemate

Offline NorthGuy

  • Super Contributor
  • ***
  • Posts: 3567
  • Country: ca
Re: How to plan/structure interrupt-based code?
« Reply #16 on: September 19, 2020, 02:22:22 pm »
I would like some LEDs to do various flashing patterns(e.g. a fast blinking red LED as an alarm indication. How would I handle the blinking of the LED? Should that also be interrupt based?

I don't know yet how the USB com port thingie works, bud i'd assume there'd be some functions for this. Maybe an interrupt every time a buffer is full?

Planning MCU timing is very much like planning your day.

For example, you need to give your mother medicine every second hour. You set an alarm. When it goes off, you quit doing what you were doing and go give the medicine to your mother. That's the same as interrupt - an event triggers the interrupt, CPU stops execution, enters ISR (interrupt service routine) and runs it ahead of other code. This ensures that the routine gets executed as close to the event as possible. The same as you quitting all affairs and running to give the medicine.

But not all actions are urgent. For example, you need to pay electric bill, which is important, but not urgent. Imagine you set an alarm which rings when the electric bill arrives, and as soon as it goes off you quit everything, disable your medicine alarm (so that it wouldn't interfere with paying the electric bill) and go pay your bill. You probably wouldn't do it this, would you? Similarly, you don't need interrupts to handle slow events, you just check them in a loop:

Code: [Select]
while (1) {
  if (time_to_switch_LED()) {
    switch_LED();
  }

  if (USB_packed_arrived()) {
    process_USB_packet();
  }

  ...
}

It takes time to get through the loop, but since your actions don't require urgency, it doesn't matter.

Things are different when you want to save power and sleep. Just as you must use alarm clock to wake you up in the morning, you must use interrupts to wake CPU from sleep.
 
The following users thanked this post: petemate

Offline Renate

  • Super Contributor
  • ***
  • Posts: 1789
  • Country: de
    • Renate's Android Page
Re: How to plan/structure interrupt-based code?
« Reply #17 on: September 19, 2020, 03:32:02 pm »
You don't need interrupts to handle slow events...
True, but why wouldn't you use interrupts?

If your application isn't rocket science, handle everything in ISRs, go into idle in the main loop.
If you don't re-enable interrupts in your ISRs then you get builtin mutual exclusion.
What's in most ISRs? Mostly push a byte on a queue, decide if something needs to be done and push some bytes on someone else's queue.
This can all be done quickly.
 

Offline NorthGuy

  • Super Contributor
  • ***
  • Posts: 3567
  • Country: ca
Re: How to plan/structure interrupt-based code?
« Reply #18 on: September 19, 2020, 04:16:07 pm »
You don't need interrupts to handle slow events...
True, but why wouldn't you use interrupts?

Simpler structure. Less work. No data travelling across threads. Often better latency for the interrupts you do need.
 

Offline Fixpoint

  • Regular Contributor
  • *
  • Posts: 105
  • Country: de
Re: How to plan/structure interrupt-based code?
« Reply #19 on: September 19, 2020, 05:15:48 pm »
True, but why wouldn't you use interrupts?

Simpler structure. Less work. No data travelling across threads. Often better latency for the interrupts you do need.

Hm? Interrupts have nothing to do with threads.
 

Offline NorthGuy

  • Super Contributor
  • ***
  • Posts: 3567
  • Country: ca
Re: How to plan/structure interrupt-based code?
« Reply #20 on: September 19, 2020, 06:54:29 pm »
Hm? Interrupts have nothing to do with threads.

ISR runs as a different thread. It preemts the execution, saves context, executes, restores context and exits.

Hence, if you want to share data between the "main" and the ISR, you may need to take measures to preserve data integrity as the data travel back and forth. If you put the same code in the "main", everything is in the same thread and you don't need to do anything special.
 
The following users thanked this post: Howardlong

Offline Kleinstein

  • Super Contributor
  • ***
  • Posts: 17277
  • Country: de
Re: How to plan/structure interrupt-based code?
« Reply #21 on: September 19, 2020, 07:29:12 pm »
Interrupts are a bit similar to multiple threads, though they are not exactly the same. So many things from multi threaded programs also apply the ISR programming.

The main point for an event driven system is that every ISR/job is executed in time. So the maximum delay for each ISR is an important parameter to look at when planing. Even if not critical an ISR should usually be finished at least before the next interrupt of the same type comes.

One can often leave one job to the main program. If power consumption is critical this job is often going into power saving mode when there is idle time.

There is little use of only setting flags in an ISR - quite often the HW does this anyway to signal an ISR - so in this case one could use the HW flags directly. Anyway putting the job to an extra SW handled scheduler only makes things more complicated. With interrupt priorities one can usually do all the jobs in the ISRs. In rare cases one may have to alow nested interrupts. Moving the actual work to the main program does not get the jobs done faster - so it usually does not help.

An important point is usually to avoid any longer time active waiting more than a few cycles. If there is a delay needed don't be shy using another timer and interrupt to call for the next interrupt in a few µs if needed. This way one may also split longer ISRs into 2 smaller ones if needed. Though the interrupts give some kind of preemptive multitasking, one should program like having cooperative multi-tasking. So be nice to the other ISRs and don't waste CPU time.

The data flow and interactions are an important point - so make sure to not use things like half updated data entries. This can be simple if there are no nested interrupts, but it can be quite complicated with nested interrupts.
 

Offline Renate

  • Super Contributor
  • ***
  • Posts: 1789
  • Country: de
    • Renate's Android Page
Re: How to plan/structure interrupt-based code?
« Reply #22 on: September 19, 2020, 07:32:54 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.

This can be simple if there are no nested interrupts, but it can be quite complicated with nested interrupts.
This ^.
 

Offline Fixpoint

  • Regular Contributor
  • *
  • Posts: 105
  • Country: de
Re: How to plan/structure interrupt-based code?
« Reply #23 on: September 19, 2020, 07:35:59 pm »
ISR runs as a different thread. It preemts the execution, saves context, executes, restores context and exits.

Hence, if you want to share data between the "main" and the ISR, you may need to take measures to preserve data integrity as the data travel back and forth.

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.

A thread is a completely different thing, and many widely available microcontrollers don't support them. And even if the hardware supports them, you still need operating-system like software support (which, by the way, would be overkill for 99% of embedded applications).

Quote
If you put the same code in the "main", everything is in the same thread and you don't need to do anything special.

Please don't write stuff like this, in this generality it is wrong on a very fundamental level and people (beginners) could actually believe you and do that in their code.

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. However, then there is no guarantee anymore that hardware events are handled in time because the software has to do polling. Even a little change in system behavior would then destroy all the implicit timing invariants. So, from a design perspective polling is unsafe and impairs maintainability and correctness.

By the way, The OP wanted to know how to do a GOOD design, not a bad one.

Quote from: Kleinstein
Interrupts are a bit similar to multiple threads, though they are not exactly the same.

Please, let's be correct and precise here and let us not confuse things. Beginners will not learn anything by that. After all, this is an engineering forum.
« Last Edit: September 19, 2020, 07:50:28 pm by Fixpoint »
 

Offline Renate

  • Super Contributor
  • ***
  • Posts: 1789
  • Country: de
    • Renate's Android Page
Re: How to plan/structure interrupt-based code?
« Reply #24 on: September 19, 2020, 08:00:58 pm »
Erm, I didn't mean "bare", I meant "naked". :-[
Code: [Select]
ISR(USB_COM_vect, ISR_NAKED)
{
   // blah, blah, blah
   reti();
}
If you look at your ISRs you'll see that most of it is push/pop.

Edit: I didn't mention reti() before.
« Last Edit: September 19, 2020, 11:18:40 pm by Renate »
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf

 

- - -
-->