[Architecture] - Work In Interrupts -v- Main Loop and sync issues.

Go To Last Post
32 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Previously I've tended to let peripherals do as much work as possible, then I do work in interrupts (which reduces reaction times of further interrupts) and little if anything in the main loop. I know there's no hard/fast rule on this.

Sometimes however peripherals can't do everything and one such example is a UVLO on a Tiny1616 which is interrupt based. Switching to interrupts setting flags and exiting quick, I have a main loop which looks like this -

 

nt main(void) {

    SYSTEM_Initialize();
    MOTION_Init();
    LIGHTDETECT_Start();

    while (1) {

        if (work.ops.tasks) {
            MOTION_Task();
            TUNING_Task();

            if (work.tasks.lightdetect) {
                LIGHTDETECT_Task();
            }

           USB_Task();
           work.ops.tasks = 0;
        }

        if (! * (uint32_t*) &work.ops) {
           SLEEP();
        }
    }
}

Any interrupt will restart the work cycle, which is fine. Let's say a timer has done just that and the CPU is part way through the USB_Task which is -

 

static void active (void) {

    handleCurrentState();
    if (motion.stable && ! lightdetect.found &&  DAC_VALUE < USB_CURRENT_STATE_ABOVE_500MA && *motion.ticks > lastTicksBlipFail && *motion.ticks > MOTION_TICKS_500MA_AVAILABLE) {
        blipTicksPoint = *motion.ticks;
        LED_FlashFast();
        LED_RED_DISABLE;
        LED_GREEN_DISABLE;
        USB_DISABLE;
        USB_Task = delay;
        return;
    }
    setCurrentLeds();
}

Let's say the CPU stack is inside that if statement, the UVLO triggers and in that interrupt code immediately disables USB (the cause of the UVLO) and switches the USB_Task to 'inactive'. The interrupt exits and the state is out of sync, so the USB_Task gets switched to 'delay', led's flash etc.

 

And the question, is how to go about handling it? It's not practical to set a flag within the interrupt and have an if check on every statement inside this loop, plus the stack might already be inside one of these checks anyway.

 

When I think more about it, some tasks in main aren't affected by the UVLO. What would work is before the main loop enters/exits the USB_Task it sets a 'reset' flag to indicate the interrupt (if fired) shouldn't return to the stack. The UVLO interrupt checks the reset flag and if set it resets the CPU stack to restart the main loop?

 

Is there another way, and if not, how to reset the stack without a reset?
 

Last Edited: Sun. Sep 6, 2020 - 09:45 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Your use of the term ‘stack’ is unclear. Why would you want to reset the stack? How do you ‘return to the stack’?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I think I described it well, I'd just be repeating myself.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

snoopy33 wrote:
I think I described it well, I'd just be repeating myself.

I'll start the repeating:

snoopy33 wrote:
Let's say the CPU stack is inside that if statement

That wasn't covered in my old Computer Science courses.

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Ok, example time.

The CPU is at statement ->

blipTicksPoint = *motion.ticks;

An interrupt fires, is serviced, and the CPU returns to the aforementioned statement.

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

snoopy33 wrote:

Ok, example time.

The CPU is at statement ->

blipTicksPoint = *motion.ticks;

An interrupt fires, is serviced, and the CPU returns to the aforementioned statement.

 

Yes, but I don't understand about the stack being in an if statement.

 

snoopy33 wrote:
the UVLO triggers and in that interrupt code immediately disables USB (the cause of the UVLO) and switches the USB_Task to 'inactive'.

I'm lost on this.  No UVLO in the datasheet.  UnderVoltageLockOut?  Aaaah -- probably VLM?  I didn't know this model had USB.  I didn't know this model had an inherent tasking system.

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

It doesn't have USB or a tasking system, this is just a general question about how an interrupt comes back to the main loop to find the state has changed but it is non the wiser and continues that way. I may have used the wrong terminology with stack, but by it I mean where the CPU is at at the point the interrupt fires.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

Program flow returns to where it was when the interrupt occurred - by some magic means, the address of the next instruction and (depends on the processor) the status register are shoved on the stack before the program counter transfers to the interrupt vector. The interrupt handler does its stuff, carefully preserving any registers it clobbers in the course of its processing, and returns with a return-from-interrupt instruction which differs from a normal return only in that it also restores the status register.

 

As far as the main program execution is concerned, nothing has happened except that a tiny delay has occurred while the interrupt handler ran. Since it's restored the status and any clobbered registers, it's impossible to tell that an interrupt has occurred except by secondary effects - perhaps a global flag has been set to indicate that, say, a new character is available in the serial buffer.  Any use of the stack - the return address of a subroutine, or any data used, is unchanged by the interrupt - it used the stack, but it has cleaned up after itself and left all the stack pointers where they were.

 

(That said - there *are* cases when it is necessary to inhibit interrupts during stack manipulation. If you are directly manipulating the stack pointer - perhaps to create a local stack frame on a processor without a base pointer - then you need to ensure that it cannot be changed by the interrupt event at the same time as you are changing it - that way lies (a) madness and (b) difficult to locate bugs. In the vast majority of cases, though, the stack pointer will be at the end of any active data and so the interrupt return address will not affect anything.)

 

Neil

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi Neil, sounds like I was roughly right with the stack terminology.

 

So let's break down my problem. The task inside main doesn't know that a UVLO interrupt has occurred, so it's busy doing all the things it shouldn't after one does occur. Even if a flag was set inside the interrupt it wouldn't be practical to set a check on every statement inside the task and even then the address of the next instruction might not be the check but what's inside it, so this approach doesn't work.

 

What would work is if before entering the task inside main a flag was set, let's call it "criticalFlag" to tell the interrupt code, should it fire, that it should switch the pointer on the stack that will be returned to after the interrupt completes to point to main().

 

If this is not possible then as far as I know only a RESET would do the same, and that's not an option.

 

This problem has only manifested because I moved to setting flags inside interrupts and doing non time-sensitive work within the main loop.

Last Edited: Sun. Sep 6, 2020 - 06:24 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

That smacks of self-modifying code, which is generally considered a no-no.

 

You could reduce time-to-react by making your main loop code as granular as possible, so as to minimise the amount of code that is executed before you realise a critical event has occurred. e.g. (incomplete, psuedocode) :

 

volatile bool critical_event_received = false;

void isr(void) {
    critical_event_received = true;
}

void main(void) {
    
    int counter = 0;
    
    while (!critical_event_received) {
        switch (counter++) {
            case 0:
              // do granular work
              break;
            case 1:
              // do granular work
              break;
            // etc, etc
            default:
              break;
        }
    }
}

 

I recall from the dim, distant past setjmp() and longjmp() as a means of implementing a non-local goto, but I don't think I ever used them. They are present in avr-libc: https://www.nongnu.org/avr-libc/...

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

That's not much different from a check on every statement.

 

It looks like I can't set state in the main loop meaning doing non-critical work in the interrupts. I'd have thought the problem I'm having would be common though.

 

For example this

if (work.ops.tasks) {
            MOTION_Task();
            TUNING_Task();

            if (work.tasks.lightdetect) {
                LIGHTDETECT_Task();
            }

           USB_Task();
           work.ops.tasks = 0;
        }

Is based on a flag set by a timer interrupt. Instead I'll have to move the block back into the timer interrupt. While the timer interrupt is serviced the UVLO interrupt is delayed which could, worst case, delay the USB turn-off and cause a brown-out.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

Sounds like you want the isr to cause a task switch. Ie interrupt occurs and is processed, but rather return to where it left off, it will return to another point in the code.
For that, one would normally use something like freeRtos. You can fiddle the values on the stack but that might cause you unintended side effects - the isr might hit at any point in your code so the outcome might be random.

I’d suggest you carefully rethink your strategy and consider how quickly you need to react to UVLO.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

I commonly change the NextState in an ISR, but then let StateManager() (which is called inside the main loop) recognize that change and do all of the little sub-tasks necessary to do that change. Strategy: Keep Those ISRs LEAN!

 

Jim

 

Until Black Lives Matter, we do not have "All Lives Matter"!

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

obdevel wrote:
I recall from the dim, distant past setjmp() and longjmp() as a means of implementing a non-local goto, but I don't think I ever used them. They are present in avr-libc: https://www.nongnu.org/avr-libc/...

 

Sounds like https://www.avrfreaks.net/forum/...

 

The question is can you longjmp from ISR? Maybe OK, not sure, can't say I have ever tried it.

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

If you longjump out of an ISR, won't the return address (and any save & restore values) still be sitting on the stack. And, interrupts would still be disabled. ISRs are really intended to exit with reti that is used by a standard ISR return process.

 

In the somewhat distant past, there have been discussions about mucking with the stack to put a new return address there. Its been some time since that lunacy, however.

 

Jim

 

 

 

Until Black Lives Matter, we do not have "All Lives Matter"!

 

 

Last Edited: Sun. Sep 6, 2020 - 10:23 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

ka7ehk wrote:

If you longjump out of an ISR, won't the return address (and any save & restore values) still be sitting on the stack. And, interrupts would still be disabled. ISRs are really intended to exit with reti that is used by a standard ISR return process.

 

The SPL, SPH and SREG will be restored to the point where the setjmp was called.  I believe a longjmp from an ISR would work.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Monkeying with the stack and its pointer is trivial in assembler, but I very rarely do so, because as pointed out, that way lies (a) madness.

 

S.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

To expand a bit on what obdevel (#10) was up to, another thought is break up all the 'do work' code into 'small pieces', then prohibit interrupts from occurring while the 'small pieces' are being done.  That guarantees the AVR will finish one 'small piece' before processing any interrupts (and it will queue them to be executed later - but not if multiple identical interrupts occur while they're disabled).

 

Not quite what you asked for, but it's another way of looking at it.

 

But no, I don't think there's a better way than flags and testing them.

 

  S.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

It's a conceptual thing: use the state machine handler within the main routine to deal with the mechanics of the thing, and the interrupt system to generate the events that cause state changes. Trying to directly change the state using an interrupt is really going to confuse the issue (and the programmer) - precisely because it happens asynchronously.

 

The difference is that each state must complete any associated entry/exit actions before the state can change; directly changing the state using the interrupt can leave things in a potentially broken state. The interrupt might set a (global) flag, or directly call a routine to add an event to an event queue; the state machine either reads the flag and adds the event itself, or simply finds it there on the queue as if by magic. It doesn't care; it's there; it needs to handle it.

 

It's down to the programmer to define states small enough that they can complete and still meet the response time requirements...

 

Neil

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

That is what I tried to describe in #13.

 

Jim

 

Until Black Lives Matter, we do not have "All Lives Matter"!

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Incidentally, the one good use I found for this sort of thing was fatal error handling.

 

e.g. Given an AVR controlled machine with a big red Emergency Stop button (for safety) wired up to a hardware interrupt, it would, when pushed, do many things other than jump straight to RESET.  It would call the error handler, safe the machine, log the error, then smash the stack (and stack pointer) to jump to a known place in the code; because nobody could know how deep or where it was in a subroutine when pushed.  And you didn't want to 'just continue' after a fatal error.

 

RETI not included.  cheeky  S.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

Indeed you did Jim.

 

It sounds like Snoopy's problem is not the interrupt but the structure of the program around it. Perhaps a simple example?

 

A serial port generates an interrupt when a new character arrives. The interrupt handler inserts the new character into a receive buffer, updating a pointer to the next available character space, and returns having done nothing else. The main loop (or state machine, or whatever) inspects that pointer to see if it differs from its own pointer - e.g. it thinks the pointer should be 7, and finds it 8 instead. Knowing then that something has arrived, it can grab it from the buffer, and update its own pointer accordingly. Note that it doesn't matter when the character arrives or what the main program is doing when it happens; nor does it matter if multiple characters arrive between tests for new characters; each character is properly caught and buffered, and is handled in received order - provided that the main loop can handle each character on average in less time than a character takes to arrive, on average.

 

Neil

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Re-reading some of the early posts in this thread, I think the OP may have confused the Program Counter (PC) with the stack.  While the stack can contain Program Counter values in order to return from an interrupt, that's not required and certainly not all it contains.

 

In short, if the PC is down in some subroutine, the PC from before the subroutine call is pushed onto the stack, and popped off when one returns from the subroutine, so the Program Counter resumes counting where it was.  The same thing happens with an interrupt.  Thus, smashing the stack will also smash the stored value of the PC.  But you can reload the PC with anything you like - just beware what might be popped into it, overwriting what you put into it.

 

  S.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

In the #1 code:

static void active (void) {

    handleCurrentState();
    if (motion.stable && ! lightdetect.found &&  DAC_VALUE < USB_CURRENT_STATE_ABOVE_500MA && *motion.ticks > lastTicksBlipFail && *motion.ticks > MOTION_TICKS_500MA_AVAILABLE) {
        blipTicksPoint = *motion.ticks;
        LED_FlashFast();
        LED_RED_DISABLE;
        LED_GREEN_DISABLE;
        USB_DISABLE;
        USB_Task = delay;
        return;
    }
    setCurrentLeds();
}

What is wrong with letting it run to completion ?

At the next execution interval your "scheduler" would do some checks and if UVLO has occurred, call a task clean-up function and abort the task.

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

In that example not much, just the LED flashes although it would probably never be seen visually.

 

There a few other tasks that can be assigned to USB_Task, one of them is to blip it on and off. It probably would work to do a check afterwards, it just smells a bit - I don't like the idea of running code unrelated to the current state. A jump seems cleaner.

 

I agree the question is identical to the topic posted at https://www.avrfreaks.net/forum/...

 

And longjmp looks to be a solution, but there are conflicting messages on what is left on the stack, and whether the avr lib 'longjmp' is actually a function which is cleaning them up. If so, it sounds ideal.

Last Edited: Tue. Sep 8, 2020 - 03:01 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

From the avr-libc source, this is what is saved and restored by setjmp and longjmp:

jmp_buf:
	offset	size	description
	 0	16	call-saved registers (r2-r17)
	16	 2	frame pointer (r29:r28)
	18	 2	stack pointer (SPH:SPL)
	20	 1	status register (SREG)
	21	 2/3	return address (PC) (2 bytes used for <=128Kw flash)
	23/24 = total size
   All multibytes are stored as little-endian.

   int setjmp(jmp_buf __jmpb);
   void longjmp(jmp_buf __jmpb, int __val) __attribute__((noreturn));

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I'm implementing, with partial success. Problem is when it re-enters the while loop interrupts are disabled so I don't get back to the USB_Task. Shall try and set the global int flag again, perhaps the longjmp disables it?

Edit - no go on re-enabling global ints.

 


void USB_EventUnderVoltage (void) {

    USB_DISABLE;
    UVLO_DISABLE_INT;
    disableCurrentState();
    longjmp(env, 1);
}

 

 

   // on uvlo
    if (setjmp(env)) {
        
    };
    
    LIGHTDETECT_Start();
    
    while (1) {
        
        if (tasker.ops.tasks) {
            USB_Task();
        }

        if (! * (uint8_t*) &tasker.ops) {
           sleep_cpu();
        }
    }

 

Last Edited: Wed. Sep 30, 2020 - 01:20 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The preemptive instance of Quantum Platform does that.

 

P.S.

Miro Samek somewhat recently added to his embedded programming course event-driven topics that's after the RTOS portion of his series.

 


AVR :

https://github.com/QuantumLeaps/qpn/blob/master/ports/avr/qk/gnu/qfn_port.h#L57

Multiple :

QP-nano: Ports

 

What is it? | GitHub - QuantumLeaps/modern-embedded-programming-course: Companion repository to the "Modern Embedded Systems Programming" video course.

Key Concepts | Quantum Leaps

 

"Dare to be naïve." - Buckminster Fuller

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Scroungre wrote:
Re-reading some of the early posts in this thread, I think the OP may have confused the Program Counter (PC) with the stack.

+1

 

Top Tips:

  1. How to properly post source code - see: https://www.avrfreaks.net/comment... - also how to properly include images/pictures
  2. "Garbage" characters on a serial terminal are (almost?) invariably due to wrong baud rate - see: https://learn.sparkfun.com/tutorials/serial-communication
  3. Wrong baud rate is usually due to not running at the speed you thought; check by blinking a LED to see if you get the speed you expected
  4. Difference between a crystal, and a crystal oscillatorhttps://www.avrfreaks.net/comment...
  5. When your question is resolved, mark the solution: https://www.avrfreaks.net/comment...
  6. Beginner's "Getting Started" tips: https://www.avrfreaks.net/comment...
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Have I used it correctly? It appears so from what documentation I can find.

The int fires and does the stuff before state disappears into the ether. 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

snoopy33 wrote:
Have I used it correctly?

As  Scroungre  says, it appears that you meant the Program Counter (PC) when you said "the stack"; eg,

 

In #1, snoopy33 wrote:
Let's say the CPU stack (sic) is inside that if statement

which, as  theusch said in #3, doesn't really make much sense.

 

I think you meant, "the Program Counter (PC) is inside that if statement" ?

 

ie, the CPU is executing the instructions corresponding to the if statement.

Top Tips:

  1. How to properly post source code - see: https://www.avrfreaks.net/comment... - also how to properly include images/pictures
  2. "Garbage" characters on a serial terminal are (almost?) invariably due to wrong baud rate - see: https://learn.sparkfun.com/tutorials/serial-communication
  3. Wrong baud rate is usually due to not running at the speed you thought; check by blinking a LED to see if you get the speed you expected
  4. Difference between a crystal, and a crystal oscillatorhttps://www.avrfreaks.net/comment...
  5. When your question is resolved, mark the solution: https://www.avrfreaks.net/comment...
  6. Beginner's "Getting Started" tips: https://www.avrfreaks.net/comment...
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

snoopy33 wrote:

Have I used it correctly? It appears so from what documentation I can find.

The int fires and does the stuff before state disappears into the ether. 

I would try stripping it down to very basic program and check what that does.

As an example, I tried this minimal program on m328p, 2 leds  and i used UART rx complete as an interrupt.

It does seem to work as expected.

I get the long 5sec flash on led1 only at power up.

Then the 1 sec flash on led1, then led2 toggling,

Whenever I then send a charcater to uart, it goes back to the 1 sec flash on led1 (followed by back into led2 toggling).

static jmp_buf env;

ISR(USART_RX_vect)
{
    UDR0;
    longjmp(env, 1);
}

int main(void)
{
    led_init();

     /* long 5 second flash */
    led1_on();
    _delay_ms(5000);
    led1_off();
    _delay_ms(1000);

    uart_init();
    sei();

    (void)setjmp(env);

    led2_off();

    /* short 1 second flash */
    led1_on();
    _delay_ms(1000);
    led1_off();

    while (1)
    {
        led2_toggle();
        _delay_ms(500);
    }
    return 0;
}