Return from an ISR to a function different from where it is called. Possible ?

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

Hi everyone,

 

My weird programming style has led me to a problem. I don't know if it's possible or not but here it is. As far as I know normally when an ISR fires, upon it's completion it goes back to the area of code where the interrupt occurred.

Here is my case it goes back to task_A function (which is just main() while loop), all good till here. But there is a condition, which is that on completion of every 15th interrupt it must return ( or go ) to task_B function before going back to task_A and while this is happening, the interrupts must go on, even in task_B.

Following is the dummy code to create the scenario.

#include <avr/io.h>
#include <avr/interrupt.h>

inline void task_A(void)
{
    __asm__ __volatile__ ("nop");
}

void task_B(void)
{
    __builtin_avr_delay_cycles(2000);	// Simulating some work to do
}

ISR(TIMER1_COMPA_vect)
{
    static uint8_t count = 0;

    __asm__ __volatile__ ("nop");	// Pretending to do some critical work here

    if(++count == 15)
    {
        count = 0;			// On every 15th interrupt, count resets to zero
        return (task_B());              // I know this is a bad idea. Just so that you get the picture.
    }
}

int main(void)
{
    OCR1A = 1600;
    TIMSK1 |= (1 << OCIE1A);
    TCCR1B |= (1 << WGM12) | (1 << CS10);
    sei();

    while (1)
    {
        task_A();
    }
}

I can't really put the contents of the task_B in the ISR itself, because the interrupt is occurring in every 1600 clock cycles and task_B will take more than 1600 clks. I tried doing return (task_B()); with this interrupts don't happen in task_B and more PUSH/POP's happen for the interrupt. In simple words how to conditionally insert the task_B function in between the path of returning from ISR to task_A at the point where it left off, without ever stopping or missing an interrupt.

I hope I cleared everything in detail. If there is a another way to implement this logic, please let me know.

Thank you.

 

 

 

This topic has a solution.

“Everyone knows that debugging is twice as hard as writing a program in the first place. So if you're as clever as you can be when you write it, how will you ever debug it?” - Brian W. Kernighan

Last Edited: Tue. Mar 23, 2021 - 02:31 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Freertos will do this for you.

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

Kartman wrote:
Freertos will do this for you.

 

I don't want to go that path. crying I already don't have enough resources on the chip (SRAM,FLASH) nor I am knowledgeable enough to handle that thing.

I'd like to keep it as simple as possible.

“Everyone knows that debugging is twice as hard as writing a program in the first place. So if you're as clever as you can be when you write it, how will you ever debug it?” - Brian W. Kernighan

Last Edited: Tue. Mar 23, 2021 - 06:19 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

If You write interrupt in assembler You can manipulate with stack. You may put false return frame on it and go to any place in program. You must change original return frame from interrupt to correctly end additional task and return to maim loop. If You write it in C You can manipulate stack too, but in this case You don't have full control of it's content thus in this case it is very hard to make this.

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

make yourself a state machine.

 

 

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

Heisen wrote:
My weird programming style has led me to a problem

So why not adopt a more conventional style which does not cause that problem?

 

Heisen wrote:
normally when an ISR fires, upon it's completion it goes back to the area of code where the interrupt occurred.

Yes, that is the whole point of "interrupts": they interrupt the code to do something, then let it continue with what it was doing.

 

Normally, the "conventional" approach is for the ISR to be as short and simple as possible - any decision making would be done in the "mainline" code.

 

meslomp wrote:
make yourself a state machine

+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...
This reply has been marked as the solution. 
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

In:

ISR(TIMER1_COMPA_vect)
{
    static uint8_t count = 0;

    __asm__ __volatile__ ("nop");	// Pretending to do some critical work here

    if(++count == 15)
    {
        count = 0;			// On every 15th interrupt, count resets to zero
        return (task_B());              // I know this is a bad idea. Just so that you get the picture.
    }
}

what is the role of the word "return" in this? Why is it not simply:

ISR(TIMER1_COMPA_vect)
{
    static uint8_t count = 0;

    __asm__ __volatile__ ("nop");	// Pretending to do some critical work here

    if(++count == 15)
    {
        count = 0;			// On every 15th interrupt, count resets to zero
        task_B();              // I know this is a bad idea. Just so that you get the picture.
    }
}

Anyway, while this is a dangerous practice you could just do:

ISR(TIMER1_COMPA_vect)
{
    static uint8_t count = 0;

    __asm__ __volatile__ ("nop");	// Pretending to do some critical work here

    if(++count == 15)
    {
        count = 0;			// On every 15th interrupt, count resets to zero
        sei();
        task_B();              // I know this is a bad idea. Just so that you get the picture.
    }
}

Sure the execution of task_B can now be interrupted by COMPA but if, as you say :

Heisen wrote:
because the interrupt is occurring in every 1600 clock cycles and task_B will take more than 1600 clks
then as long as task_B does not take more than 15 * 1600 it should be complete before there is a chance of it being called again. But really wouldn't you usually just do something like:

volatile uint8_t run_B_now = 0;

ISR(TIMER1_COMPA_vect)
{
    static uint8_t count = 0;

    __asm__ __volatile__ ("nop");	// Pretending to do some critical work here

    if(++count == 15)
    {
        count = 0;			// On every 15th interrupt, count resets to zero
        run_B_now = 1;
    }
}

int main(void){
    // stuff
    while(1) {
        task_A();
        if (run_B_now) {
            run_B_now = 0;
            task_B();
        }
    }
}

What is to be gained by invoking task_B() directly from the ISR (in an interrupt blocked context) compared to simply running it from main() where the COMPA can continue to interrupt anyway?

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

I'm on a phone so can't try it but I wonder if you could abuse setjmp() and friends to achieve this?

#1 Hardware Problem? https://www.avrfreaks.net/forum/...

#2 Hardware Problem? Read AVR042.

#3 All grounds are not created equal

#4 Have you proved your chip is running at xxMHz?

#5 "If you think you need floating point to solve the problem then you don't understand the problem. If you really do need floating point then you have a problem you do not understand."

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

The question comes up frequently, and generally turns out to be an X-Y Problem.

 

setjmp()  is, indeed, often suggested as an answer to Y.

 

Maybe time to step back to the actual goal (X):

 

http://www.catb.org/esr/faqs/smart-questions.html#goal

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

awneil wrote:
The question comes up frequently, and generally turns out to be an X-Y Problem.

My bad, seeing the replies made me realize the info I provided is not enough. I'll try to give more at the end but for now I got it working.

 

Brian Fairchild wrote:
I'm on a phone so can't try it but I wonder if you could abuse setjmp() and friends to achieve this?

Looks interesting never used it before, I'll read up on that.

 

awneil wrote:
So why not adopt a more conventional style which does not cause that problem?

I might be wrong, but conventional style wasn't giving me the results that I wanted. Hence, trying this weird thing.

 

clawson wrote:
what is the role of the word "return" in this?

My brain fart.

 

clawson wrote:

ISR(TIMER1_COMPA_vect)
{
    static uint8_t count = 0;

    __asm__ __volatile__ ("nop");	// Pretending to do some critical work here

    if(++count == 15)
    {
        count = 0;			// On every 15th interrupt, count resets to zero
        task_B();              // I know this is a bad idea. Just so that you get the picture.
    }
}

Anyway, while this is a dangerous practice you could just do:

ISR(TIMER1_COMPA_vect)
{
    static uint8_t count = 0;

    __asm__ __volatile__ ("nop");	// Pretending to do some critical work here

    if(++count == 15)
    {
        count = 0;			// On every 15th interrupt, count resets to zero
        sei();
        task_B();              // I know this is a bad idea. Just so that you get the picture.
    }
}

Sure the execution of task_B can now be interrupted by COMPA but if, as you say :

Heisen wrote:

because the interrupt is occurring in every 1600 clock cycles and task_B will take more than 1600 clks

then as long as task_B does not take more than 15 * 1600 it should be complete before there is a chance of it being called again.

 

This right here did the trick, now I get it, Yes task_B does not take more than 15*1600 clks to complete, therefore it should be fine to call it in the interrupt itself, sei() enables the interrupts in task_B which I wanted. Back of my mind I knew calling a function inside an interrupt is not a good idea, everywhere I read it's not recommended. But I guess if you do it carefully it should be fine.

 

clawson wrote:
What is to be gained by invoking task_B() directly from the ISR (in an interrupt blocked context) compared to simply running it from main() where the COMPA can continue to interrupt anyway?

 

Sorry, I didn't mention this, the task_A() function is very very long, you can say by the time task_A()function is completed, task_B() function would have been called many times (around 14, see diagram below). So the ISR, task_B and task A all run parallel, I know it can't run in parallel but you get the idea by interrupting each other it can. So priority wise ISR is the highest, then task_B() and then task_A().

 

Like you said here is what worked in dummy code (working on actual code right now to see it works),

 

#include <avr/io.h>
#include <avr/interrupt.h>

uint8_t stepCount = 15;
uint8_t run_A_now = 0;

inline void task_A(void)
{
	if(run_A_now)
	{
		run_A_now = 0;
		__asm__ __volatile__ ("nop");
	}
}

void task_B(void)
{
	__builtin_avr_delay_cycles(2000);	// Simulating some work to do
}

ISR(TIMER1_COMPA_vect)
{
	static uint8_t count = 0;

	__asm__ __volatile__ ("nop");	// Pretending to do some critical work here

	if(++count == 15)
	{
		count = 0;					// On every 15th interrupt, count resets to zero
		if(++stepCount == 16)
		{
			stepCount = 0;
			run_A_now = 1;
		}

		sei();
		task_B();
	}
}

int main(void)
{
    OCR1A = 1600;
    TIMSK1 |= (1 << OCIE1A);
    TCCR1B |= (1 << WGM12) | (1 << CS10);
    sei();

    while (1)
    {
		task_A();
    }
}

 

To make things more clear, Here is a timing diagram of each function, how much time it would really take, from here you can see why I was trying to do things this way. :- 

 

 

Apologies for still not explaining the problem properly. 

“Everyone knows that debugging is twice as hard as writing a program in the first place. So if you're as clever as you can be when you write it, how will you ever debug it?” - Brian W. Kernighan

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

Heisen wrote:
In simple words how to conditionally insert the task_B function in between the path of returning from ISR to task_A at the point where it left off, without ever stopping or missing an interrupt.
Schedule task_B before exiting the ISR though task_A must have synchronization points scattered "about" (scheduler is invoked given a latency requirement)

 

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

https://github.com/QuantumLeaps/qpn/blob/master/src/qkn/qkn.c#L235

https://github.com/QuantumLeaps/qpn/blob/master/src/qkn/qkn.c#L283

 

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

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

Heisen wrote:
... nor I am knowledgeable enough to handle that thing.
Early in an operating systems course are schedulers.

Heisen wrote:
I'd like to keep it as simple as possible.
Concur

 

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

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

Heisen wrote:
Sorry, I didn't mention this, the task_A() function is very very long,
Well an approach to that is to do waht others said above - make A a "state machine". Don't have one call do ALL the work, split it into chunks (states). The first time you call task_A() it does part1, next time it does part 2 and so on. This way you can interleave the task_B() work into it when the flag says it is due.

 

I've worked with co-operative muti-taking systems where we simply imposed a limit "no task should spend more than 5ms executing before it relinquishes control". So if A takes 45ms you break it into 9 pieces etc.

 

The other approach is pre-emptive (rather than co-operative) where a scheduler simply snatches back control and passes it to the next guy at something like 10ms or whatever. In this care one exection of A's 45ms would be interrupted 4.5 times to allow parts of B to run if it needed it. Of course if B was in the "idle, waiting" state not the "need to run now" state then the scheduler might interrupt A only to find that no one else wanted to run at the time so immediately hand control back to A.

 

FreeRTOS is a good example of the latter (actually it can do cooperative too).

Last Edited: Wed. Mar 24, 2021 - 11:50 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Yes though that usually is in the context of exceptions [handle the fault then restart main()]

Cooperative multitasking | C Programming/setjmp.h - Wikibooks, open books for an open world

 

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

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

Interrupts should be very short.  They should set/clear global volatile boolean flags to the main program.   I suggest naming these booleans in the format: is(activity that reflects the value).  Here I would name one of these boolean variables as:  isItTimeForTaskB.   The variable would be initialized to FALSE and would be set to TRUE inside the TIMER1_COMPA interrupt.  The main() while(1) loop would test this boolean before TaskA.

  

volatile boolean isItTimeForTaskB = FALSE;

uint_8  volatile  count = 0;

 

main() {

 while(1) {

     if  (isItTimeForTaskB == TRUE) {isItTimeForTaskB = FALSE; TaskB(); }

     TaskA();

}}

 

ISR(TIMER1_COMPA_vect) {

    // do critical work in main() while(1)

    count++;

    if (count == 15) {count=0; isItTimeForTaskB = TRUE;}

}

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

engineering is full of compromises - you want it simple and there's a baked solution available that would do precisely what you want but you'd need to get a bigger chip. Or do it in a more complicated fashion but squeeze it into the current hardware. Choose your poison.

 

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

Simonetta wrote:
 while(1) {

     if  (isItTimeForTaskB == TRUE) {isItTimeForTaskB = FALSE; TaskB(); }

     TaskA();

}}

If I'm reading #10 correctly, I don't think that's what the OP wants?

 

I think the Task-A is a long but low-priority task, and Task-B is a short but high-priority Task - so Task-B needs to interrupt Task-A

 

Something like this:

                     Active
Task-B              |---------|

          Active    [suspended] Active
Task-A:  |----------           ----------------------------|

 

As clawson said, an easy way would be to break Task-A up into "sub tasks", and manage them in a state machine

 

Could be something like:

void task_a( void )
{
   static uint8_t step;

   switch( step )
   {
      case 0: /* do 1st sub task */ ++step; break;
      case 1: /* do 2nd sub task */ ++step; break;
      case 2: /* do 3rd sub task */ ++step; break;
      case 3: /* do 4th sub task */ ++step; break;
      :
      :
      case N: /* do final sub task */ step=0; break;
   }
}

 

EDIT

 

Add missing semicolons

 

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...
Last Edited: Wed. Mar 24, 2021 - 11:52 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Kartman wrote:
engineering is full of compromises - you want it simple and there's a baked solution available that would do precisely what you want but you'd need to get a bigger chip. Or do it in a more complicated fashion but squeeze it into the current hardware. Choose your poison.

 

Agreed. All this just because the microcontroller is too slow for job or I am a bad coder. Got myself too familiar with the megaAVR that now going to another microcontroller is feeling like a big task. 

 

gchapman wrote:

Schedule task_B before exiting the ISR though task_A must have synchronization points scattered "about" (scheduler is invoked given a latency requirement)

I guess it's time for me to read more about the scheduler.

 

clawson wrote:

Well an approach to that is to do waht others said above - make A a "state machine". Don't have one call do ALL the work, split it into chunks (states). The first time you call task_A() it does part1, next time it does part 2 and so on. This way you can interleave the task_B() work into it when the flag says it is due.

 

awneil wrote:
As clawson said, an easy way would be to break Task-A up into "sub tasks", and manage them in a state machine

 

I wish I could do that, but I can't. task_A is very ambiguous and such a hot mess cause it's written by me, can not split into chunks. Well that's a matter for another day.

 

awneil wrote:

I think the Task-A is a long but low-priority task, and Task-B is a short but high-priority Task - so Task-B needs to interrupt Task-A

 

Something like this:

                     Active
Task-B              |---------|

          Active    [suspended] Active
Task-A:  |----------           ----------------------------|

 

Yes, exactly, more like :-

 

ISR:        |-|   |-|   |-|   |-|   |-|   |-|   |-|   |-|   |-|   |-|   |-|   |-|

Task-B:              |--   ---   --|

Task-A:   |-   ---                     ---   ---   ---   ---   ---   ---   --| 

 

Although thread is already marked solved, here is something I am experimenting with now (it's mostly the same with what clawson mentioned), it also does the job well, let me know is this better or worse (I mean less chance of something going horribly wrong)

 

#include <avr/io.h>
#include <avr/interrupt.h>

ISR(TIMER1_COMPA_vect)
{
	static uint8_t count = 0;

	// Critical ISR

	if(++count == 15)
	{
		count = 0;
		TIMSK1 |= (1 << OCIE1B);	// Enable run_task_b interrupt
	}
}

ISR(TIMER1_COMPB_vect, ISR_NOBLOCK)
{
	TIMSK1 &= ~(1 << OCIE1B);		// Disable run_task_b interrupt

	// Run task_B here
}

int main(void)
{
	TCCR1B |= (1 << WGM12) | (1 << CS10);
	OCR1A = 1600;
	TIMSK1 |= (1 << OCIE1A);
	sei();

    while (1)
    {
		// Run task_A here
    }
}

I am using TIMER1_COMPB_vect with ISR_NOBLOCK for task_B, on every 15th interrupt the execution from task_A goes to ISR then goes to task_B and returns to task_A (interrupts also works in task_B), on other cases it just goes back and fourth between task_A and ISR. No more calling function in an ISR.

“Everyone knows that debugging is twice as hard as writing a program in the first place. So if you're as clever as you can be when you write it, how will you ever debug it?” - Brian W. Kernighan

Last Edited: Wed. Mar 24, 2021 - 06:09 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Heisen wrote:
Although thread is already marked solved

You can un-mark it, if you wish

 

 here is something I am experimenting with now (it's mostly the same with what clawson mentioned), it also does the job

So what you have there is:

ISR (including Task-B):        |-|   |-||-------||-|   |-|   |-|   |-|   |-|   |-|   |-|   |-|   |-|   |-|

Task-A:                      |-   ---               ---   ---   ---   ---   ---   ---   --| 

 

So you need to be sure that the combined execution time of (ISR + Task-B) doesn't  block for too long.

 

EDIT: From your diagram in #10, Task-B is going to block for too long!

 

Maybe you can break Task-B down into a state machine ... ?

 

EDIT 2

 

As it stands, your code in #18 executes the whole of Task-B on every single interrupt.

 

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...
Last Edited: Wed. Mar 24, 2021 - 05:30 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

Heisen wrote:

Yes, exactly, more like :-

 

ISR:        |-|   |-|   |-|   |-|   |-|   |-|   |-|   |-|   |-|   |-|   |-|   |-|

Task-B:              |--   ---   --|

Task-A:   |-   ---                     ---   ---   ---   ---   ---   ---   --| 

Is it just me or would this lend itself beautifully to a priority level interrupt controller in say an xmega device?
Task B would be the ISR of a low or medium priority interrupt where the existing timer interrupt would have medium or high priority respectively.

Steve

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

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

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

Unless more needs to be added extra ISR levels will only slow things down! 

 

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

I've been working low level on a task switching "library".   It's currently about 250 words of flash and 50 bytes of RAM, plus task stacks.   I'm still struggling with getting the overhead down for interrupts.  But in any case using this type of system requires a stack for every task that can support context switch (~ 40 bytes) plus room for max interrupt.   Here is a stripped down demo that I was running on the atmega4809 to blink leds at different brightness, if you will.   It has a main task and two real-time tasks.   When the timer interrupt fires it enables both RT tasks.  When the higher priority task completes, the lower one takes over.  When the lower one completes, the control goes back to "main".   See this thread.

 

#define F_CPU 5000000

#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include "octos.h"

void init_tca() {
  TCA0.SINGLE.CTRLA = TCA_SINGLE_CLKSEL_DIV256_gc | TCA_SINGLE_ENABLE_bm;
  TCA0.SINGLE.CTRLB = TCA_SINGLE_WGMODE_NORMAL_gc;
  TCA0.SINGLE.INTCTRL = TCA_SINGLE_OVF_bm;
  TCA0.SINGLE.PER = 2*9745;
}

volatile uint8_t flag = 0;

#define STKSIZ2 0x40
uint8_t task2_stk[STKSIZ2];

void task2(void) {
  sei();
  while (1) {
    oct_idle_task(OCT_TASK2);
    for (int i = 0; i < 1500; i++) {
      PORTF.OUTCLR = PIN5_bm;
      _delay_us(90);
      PORTF.OUTSET = PIN5_bm;
      _delay_us(10);
    }
  }
}

#define STKSIZ3 0x40
uint8_t task3_stk[STKSIZ3];

void task3(void) {
  sei();
  while (1) {
    oct_idle_task(OCT_TASK3);
    for (int i = 0; i < 5000; i++) {
      PORTF.OUTCLR = PIN5_bm;
      _delay_us(20);
      PORTF.OUTSET = PIN5_bm;
      _delay_us(80);
    }
  }
}

ISR(TCA0_OVF_vect) {
  TCA0.SINGLE.INTFLAGS = TCA_SINGLE_ENABLE_bm;
  oct_wake_task(OCT_TASK2 | OCT_TASK3);
}


void main(void) {
  init_tca();

  oct_os_init(OCT_TASK6);
  oct_attach_task(OCT_TASK2, task2, task2_stk, STKSIZ2);
  oct_attach_task(OCT_TASK3, task3, task3_stk, STKSIZ3);

  PORTF.DIRSET = PIN5_bm;		/* LED output */
  PORTF.OUTSET = PIN5_bm;		/* LED off */

  sei();
  while (1) {
    wdt_reset();

    PORTF.OUTCLR = PIN5_bm;
    _delay_us(5);
    PORTF.OUTSET = PIN5_bm;
    _delay_us(95);
  }
}

 

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

MattRW wrote:
When the lower one completes .

But he doesn't want the long, low-priority task to run to completions - he wants it to be pre-empted.

 

EDIT

 

Use correct quote

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...
Last Edited: Thu. Mar 25, 2021 - 08:41 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

MattRW wrote:
When the lower one completes, the control goes back to "main".   See this thread.
Neither that thread, nor this says where your Octos is actually located? Have you posted it to Github? The other thread shows the start of your task switching code but the all important (and interesting) bit beyond "brne task_swap" is not shown??

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

I guess that the "correct" solution depends of why the two tasks has to run as described, and what are the dependencies between them and the outside world. 

what happens if taskA don't get done before next taskB, is that an error or not etc. 

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

sparrow2 wrote:
extra ISR levels will only slow things down! 

Indeed.

 

But then, the overhead of a pre-emptive task switcher would also slow things down.

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

I misstated.  On idle/wake restarts the highest priority task, which is run to completion, or idle call, or pre-empted by a higher priority task.

 

The code will be posted in github when I have fixed the issues.  I did some analysis using the model checker SPIN and found issues.   I was trying to save context switch time by not saving/restoring caller-save registers.  But that does not work with interrupt handlers, unless you use function calls in the interrupt handlers.  In that case, now your interrupt handler has to save/restore caller-save registers.  I want to do that only if needed.  In any case, without stable code, it'll be a waste of time to release.

 

I have updated the license:

;;; Permission is hereby granted, free of charge, to any person obtaining a copy
;;; of this software and associated documentation files (the "Software"), to
;;; deal in the Software without restriction, including without limitation the
;;; rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
;;; sell copies of the Software, and to permit persons to whom the Software is
;;; furnished to do so, subject to the following conditions:
;;;
;;; The above copyright notice and this permission notice shall be included in
;;; all copies or substantial portions of the Software.
;;;
;;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
;;; OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
;;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
;;; THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
;;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
;;; FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
;;; DEALINGS IN THE SOFTWARE.

 

Last Edited: Thu. Mar 25, 2021 - 12:44 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Heisen wrote:

I wish I could do that, but I can't. task_A is very ambiguous and such a hot mess cause it's written by me, can not split into chunks. Well that's a matter for another day.

 

Sometimes a hack for a specific problem can be a good solution. You may not need to write a complete generic pre=emptive task scheduler.

 

A quick and dirty hack may be to call a Yield() function before the lines of long execution time in your Task-A.

I.e Task-A co-operatively yields control to a supervising scheduler via a Yield() function.

That scheduler assumes the High Priority Task-B runs to completion in a very short time making it acceptable to call Task-B or indeed any other short duration task on a round robin basis. Control then returns to Task-A.

 

 

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

... a priority level interrupt controller ...

That is also what I was thinking.

 

XMegas have a 3 level priority interrupt controller.

AVR_DA, DB, ... have a two level interrupt controller.

 

The Xmegas also can run at 32 MHz, likely double the clock speed of the current version, which might also help with the timing issues.

 

JC 

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

Why slow everything down with extra level of interrupts, when it's not needed ? (and then suggest a bigger hammer because now it's needed). 

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

sparrow2 wrote:

what happens if taskA don't get done before next taskB, is that an error or not etc. 

 

If taskA don't get done before the next taskB and taskB doesn't get done in between time of 15 ISR's, that is definitely a catastrophic failure in my application.

I am fighting with clock cycles since the day one (learned a lot), now I got the hang of it, all tasks finish long before the deadline, So I am 100% sure that will not happen.

 

sparrow2 wrote:
I guess that the "correct" solution depends of why the two tasks has to run as described, and what are the dependencies between them and the outside world.

 

I see where this thread will lead to if I answer that. I try to keep the discussion in the thread about the problem mentioned, let's not deviate from that, of course If I tell the dependencies, someone might tell me why are you doing this X through Y when there is Z already available. In other words I have created this pyramid after many iterations of code, so if someone tell me to change something at bottom because that is the main cause of the problem, I won't be able to do that, the pyramid will fall, all the progress will be lost. It's okay if I fail in the end, but I can't go back right now. I already know I am not doing things the traditional way (be it better code, faster chip or multiple IC solution), before I hop on that train, I'll keep digging. With traditional way's (that I am aware of) the application is not possible "yet". Yes you might have a better solution, since you guys are the expert, I am just a newbie. If I fail hard, then I will create the thread about the underlaying workings and ask questions then, it will be more relevant, but until then Imma keep it on my own.

 

The application is 32 channel software PWM (capable upto 40) with individual channel control from outside, with high refresh rate and high bit depth ( with gamma correction for all channels ), plus still enough headroom to run led animations ( with individual animation speed control ) and decoding of animation data.  (megaAVR) Single Chip solution, baby let's gooooo!!

 

sparrow2 wrote:
extra ISR levels will only slow things down! 

 

I don't think that is happening.

There are just two ISR's, one is very critical and the other I am using just as a function (task_B), task_A is running in main while loop. All is good right now, unless I find something else.

 

#include <avr/io.h>
#include <avr/interrupt.h>

ISR(TIMER1_COMPA_vect)
{
	static uint8_t count = 0;

	// Critical ISR

	if(++count == 15)
	{
		count = 0;
		TIMSK1 |= (1 << OCIE1B);	// Enable run_task_b interrupt
	}
}

ISR(TIMER1_COMPB_vect, ISR_NOBLOCK)
{
	TIMSK1 &= ~(1 << OCIE1B);		// Disable run_task_b interrupt

	// Run task_B here
}

int main(void)
{
	TCCR1B |= (1 << WGM12) | (1 << CS10);
	OCR1A = 1600;
	TIMSK1 |= (1 << OCIE1A);
	sei();

    while (1)
    {
		// Run task_A here
    }
}

 

No scheduler needed as of now.

 

Why an extra ISR for task_B you might ask?

 

Because as you see in clawson's reply (or marked solution), calling task_B function in the critical ISR push's all the registers to stack, because the complier thinks there is a possible of task_B happing in every interrupt. This hinders with main ISR execution time, due to all the pushing registers, what I did I put the task_b into another ISR with which has interrupts enabled by the way (also executes right after the main ISR) so all the registers all still getting pushed to stack for the task_B ISR, not for the main ISR. This insures that the critical ISR's execution and code is neat and clean and quick. So when task_B is not running, the switching time between the critical ISR and task_A is quick.

“Everyone knows that debugging is twice as hard as writing a program in the first place. So if you're as clever as you can be when you write it, how will you ever debug it?” - Brian W. Kernighan

Last Edited: Fri. Mar 26, 2021 - 08:42 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

Make task_B "static inline" and it should be inlined in to the ISR. This may be helped along a bit with an __attribute__((always_inline)). That is
 

__attribute__((always_inline)) static inline void task_B(void)
{
    __builtin_avr_delay_cycles(2000);	// Simulating some work to do
}

If it's inlined it won't be CALL'd and there will not be the huge register bank PUSH/POPs

Last Edited: Fri. Mar 26, 2021 - 09:05 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Yes indeed, there won't be a call to task_B therefore huge register PUSH/POPs will not happen for it but they gotta happen somewhere, wouldn't they take place when there will be a call to ISR(TIMER1_COMPA_vect), in this case ?

 

__attribute__((always_inline)) static inline void task_B(void)
{
    // Run task_B
}

ISR(TIMER1_COMPA_vect)
{
    static uint8_t count = 0;

    // critical ISR

    if(++count == 15)
    {
        count = 0;			// On every 15th interrupt, count resets to zero
        task_B();
    }
}

there are many instances in code where task_B is not happening and just switching between task_A and ISR.

I'd rather have huge push/pop's happen for task_B but not for the main ISR.

 

ISR(TIMER1_COMPA_vect)
{
	static uint8_t count = 0;

	// Critical ISR

	if(++count == 15)
	{
		count = 0;
		TIMSK1 |= (1 << OCIE1B);	// Enable run_task_b interrupt
	}
}

ISR(TIMER1_COMPB_vect, ISR_NOBLOCK)
{
	TIMSK1 &= ~(1 << OCIE1B);		// Disable run_task_b interrupt

	// Run task_B here
}

 

“Everyone knows that debugging is twice as hard as writing a program in the first place. So if you're as clever as you can be when you write it, how will you ever debug it?” - Brian W. Kernighan

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

Ok now I'm confused.

The code in #34 will not have count counting while taskB is running! (as shown in #18).

 

Just general if 8 bit PWM done in ASM  and constant time between ISR's: 

4 5 clk for each channel in each ISR.

 

So for 40 channels and 10KHz counter update, (about 40Hz) take about 25%1/3 of the time

 

Add:

Ups wrong numbers.   

 

 

 

 

 

 

Last Edited: Fri. Mar 26, 2021 - 11:42 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

I like clawson's solution w/ static inline in the case taskB does not call functions.   If it does, I offer this hack: code up taskB as an ISR at an unused vector (say 12) and asm("jmp 12") in your TIMER ISR at the 15th iteration.   This way the compiler will push/pop caller-saves onto the stack inside ISR(12) but not in ISR(TIMER).

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

sparrow2 wrote:
Ok now I'm confused.

 

Me too. indecision

 

MattRW wrote:
code up taskB as an ISR at an unused vector (say 12) and asm("jmp 12") in your TIMER ISR at the 15th iteration.   This way the compiler will push/pop caller-saves onto the stack inside ISR(12) but not in ISR(TIMER).

 

This looks interesting. I wanna try. How to do ISR at an unused vector? Do we do that in assembly? Any example?

“Everyone knows that debugging is twice as hard as writing a program in the first place. So if you're as clever as you can be when you write it, how will you ever debug it?” - Brian W. Kernighan

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

Say you are NOT using ADC interrupt.  That is, you don't enable it.  Then, given that the vector table offset for ADC_vect is 42

ISR(ADC_vect) {
  taskB();
}

ISR(TIMER1_COMPA_vect) {
 ...
 if (++count == 15) {
     count = 0;
     asm volatile("jmp 42"); // verify this is right for ADC_vect
 }
}

I'm sure there may be better ways to do this.   You should play with it and dump the assembly.   You might be able to bypass the interrupt vector table by writing a function with "signal" attribute.

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

 

MattRW wrote:
I'm sure there may be better ways to do this.  
Well what most AVR datasheets say is:

 

 

So it seems if you want a "software interrupt" an unused INTn might be the way to do it.

 

(personally I've always wondered "why would you want to do that?" both from such datasheet entry and various threads that have discussed it here in the past - perhaps this is finally the reason for doing it?)

 

Presumably what happens is that when count==15 you set the condition to trigger the ext-INT interrupt then RETI from the COMPA handler, execution returns to main() - presumably deep in the bowels of task_A? - then after executing one opcode it notices the condition flag for the ext-INT is set and vectors to its handler that actually contains the task_B work.

 

Last Edited: Fri. Mar 26, 2021 - 04:14 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

What ever you force a new ISR or not, don't make counter interrupts come thru while taskB are running! 

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

Yea.   I think using INTn as a software interrupt is the right way to go.   My asm("jmp 42")  solution could fail as registers may not be popped in the TIMER1 interrupt.

In the case of the INTn approach, unless taskB explicitly enables interrupts it should run to completion uninterrupted.

Last Edited: Sat. Mar 27, 2021 - 12:10 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Heisen wrote:
But there is a condition, which is that on completion of every 15th interrupt it must return ( or go ) to task_B function before going back to task_A and while this is happening, the interrupts must go on, even in task_B.

 

So the two interrupt solution is not good, unless you relax the last requirement.

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

I can't help thinking that with 20 minutes, a good cup of coffee, a big sheet of paper, and some coloured pens, that a bit of careful thought would restructure this into a more logical flow which didn't require programming horrors.

 

It might be as simple as using a processor with more horsepower.

#1 Hardware Problem? https://www.avrfreaks.net/forum/...

#2 Hardware Problem? Read AVR042.

#3 All grounds are not created equal

#4 Have you proved your chip is running at xxMHz?

#5 "If you think you need floating point to solve the problem then you don't understand the problem. If you really do need floating point then you have a problem you do not understand."

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

Well, the requirement of ‘simple’ can be interpreted many ways!
I had a similar real time requirement many years ago - the isr had to do a bit of work, then there was some housekeeping to be done as a result and tasks to run in the background. I used a RTOS. Solved the problem. Its not quite he magic bullet for all problems and there is a cost involved. This was back in the days of assembler.

Even if we gave you a magic solution, would you be able to debug it. Ie don’t write code you don’t how to debug. Similarly, don’t design hardware you can’t debug

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

What actually the reason for not just make the timer interrupt 15 times slower, and do taskB in the interrupt and taskA as main ?

 

And again how does a SW interrupt solve the problem with updating the counter in a interrupt. 

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

sparrow2 wrote:
What ever you force a new ISR or not, don't make counter interrupts come thru while taskB are running! 

 

See, this is what the application really needs. Usually people use ISR's for setting a flag and do the work in the main while loop. I am here doing real work in ISR's, it drives the output pins. That's the difference.

 

sparrow2 wrote:
What actually the reason for not just make the timer interrupt 15 times slower, and do taskB in the interrupt and taskA as main ?

 

Because it's complicated, the main while loop where Task_A is running, it's preparing data for the Task_B, and based on that data, for the next 16 times task_B is called, it's further manipulating the data, making it ready for the next 15 critical ISR's. All works like a clockwork. You might say why use task_B at all, cut the middle man, prepare the data in Task_A feed straight to critical ISR's. That won't be possible in my case, That's too much work for Task_A. You can say I am offloading some of the work of Task_A and turning into Task_B. Attempt to run them somewhat parallel, so the critical ISR can get what it wants in time. 

 

I will reduce the number of clock cycles going to waste(Idling) and turn them into higher refresh for led's. 

 

I see based on many people suggestions of RTOS is the way to go, or faster microcontroller for this kind of stuff, but before going that way I want to try this with megaAVR.

 

Even clawson back in 2008, suggested the idea of two ISR's in this thread.

 

clawson wrote:

Michael,

I fear you've missed the point of the very clever idea Colin was suggesting to someone.

He's exchanging:

ISR() {
 //lots of pushes because of CALL
 invoke_function();
 //lot of pops
}

for

ISR() {
 // few pushes
 invoke ISR2() soft int
 // few pops
}

ISR2() {
 //few pushes
 code of "invoke_function()"
 //few pops
}

I thought this sounded like a really clever idea to call a function from an ISR without the huge push/pop overhead.

 

If I knew that the one interrupt can interrupt itself with the use of sei() in interrupt beginning, or with ISR_NOBLOCK attribute, this thread wouldn't exist, that is what marked solution showed me, but that came with one penalty that was huge push/pop of the interrupt in my case, so I went with two interrupts, that way critical one stays out of huge push/pop's. The critical one can also interrupt the second interrupt. It's a win win.

 

“Everyone knows that debugging is twice as hard as writing a program in the first place. So if you're as clever as you can be when you write it, how will you ever debug it?” - Brian W. Kernighan

Last Edited: Sat. Mar 27, 2021 - 02:27 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

Guy's everything is working as I wanted it be. No problems now.

“Everyone knows that debugging is twice as hard as writing a program in the first place. So if you're as clever as you can be when you write it, how will you ever debug it?” - Brian W. Kernighan

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

Heisen wrote:

Guy's everything is working as I wanted it be. No problems now.

 

And the solution?

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

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

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

Heisen wrote:
I see based on many people suggestions of RTOS is the way to go, ...
or an event framework where the objects are state machines.

Heisen wrote:
... but before going that way I want to try this with megaAVR.
fyi, the follow-on to megaAVR (AVRxt) add prioritized interrupts.

 


Modern Embedded Programming: Beyond the RTOS « State Space

Key concept: event-driven programming (Quantum Leaps)

 

CPUINT - CPU Interrupt Controller | ATmega4808/4809 Data Sheet

Interrupt System in tinyAVR® 0- and 1-series, and megaAVR® 0-series

Interrupts | Migration from the megaAVR® to AVR® Dx Microcontroller Families (last paragraph)

Instruction Set Summary | AVR® Instruction Set Manual (Table 1)

 

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