Simple Multitasking doesn't work.

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

Hello. I want to handle a button and blinking LED in main function with software timer in the interrupt. It working at one side: or blinking or button and doesn't work properly together.

 

Could someone explain where I have a brake. I'm looking at code and...

 

I have looked at "state machine" thread, but should I apply this principle here in simple case?

https://www.avrfreaks.net/forum/t...

 

Thanks.

 

#define F_CPU 1200000UL
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>

volatile uint16_t timer_counter=0;

#define BLINK_LED 500

ISR (TIM0_COMPA_vect) {
	
	timer_counter++;
	
	if (timer_counter >= BLINK_LED)
	{
		timer_counter=0;
	}
	
}

void setup () {
	
	// LEDs on PB2, PB3.
	DDRB =  0b00011101;
	PORTB = 0b00100010;
	
	// Analog comparator OFF.
	ACSR |= (1 << ACD);
	
	// Start timer T0 with prescaler 8.
	TCCR0B |= (1<<CS01);
	
	// Enable time T0 overflow interrupt.
	TIMSK0 |= (1<<OCIE0A);
	
	// Enable CTC Mode.
	TCCR0A |= (1<<WGM01);
	
	// T0 will overflow each 1 ms.
	OCR0A = 150;
	
	// Reset timer T0 flags.
	TIFR0 = 0;
	
}

int main(void)
{
	setup ();
	sei();
	
    while(1)
    {
        if (timer_counter == 0) 
	{
	     PORTB ^= (1<<PB3);
	}
		
		
	if (!(PINB & (1<<PB1)))
	{
	     PORTB |= (1<<PB2);
	}
	else
	{
	     PORTB &=~(1<<PB2);
	}
		
    }
}

 

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

Set a flag in the interrupt and check the flag instead of "timer_counter == 0".

NOTE: I no longer actively read this forum. Please ask your question on www.eevblog.com/forum if you want my answer.

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

alexru wrote:

Set a flag in the interrupt and check the flag instead of "timer_counter == 0".

 

Thank you Alexandr! Now everything is working. However I should find out for myself why my approach didn't work.

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

Because there is no guarantee that ISR won't be called between two consecutive execution "if (timer_counter == 0)", so it will almost never read 0.

 

MCU running while (1) loop at 1.2 MHz with branches in the body will be able to execute less than 1000 instructions in 1 ms. Depending on what compiler flags you have used (especially for optimization), this body may be bigger than that.

NOTE: I no longer actively read this forum. Please ask your question on www.eevblog.com/forum if you want my answer.

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

Regarding the posted code alexru wrote:

so it will almost never read 0.

this body may be bigger than that.

I think approximately the opposite happens:

 

With the 1,2MHz clock, the prescaler of 8 and OCRA0 = 150, the interrupt frequency is 1,2MHz/8/151 = 993Hz.

The complete while loop will only take a few clock cycles. Let's assume 11. Then there are 90 times, where timer_counter is read 0, but 499*90 times where a different value is read.

I *guess* it is is an even number, that timer_counter is read as 0. So the LED toggles 90times and stays off. But in relation to the 499*90 times you will not see it (0,1% brightness).

 

Another problem with the original code is uint16_t variable, which is accessed non-atomically from the main context and ISR. The interrupts should be disabled temporarily when reading timer_counter.

In the beginning was the Word, and the Word was with God, and the Word was God.

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

Out of interest I've compiled this for attiny44a without optimizations (avr-gcc -mmcu=attiny44a t.c). The body of the loop is 90 bytes (45 two-byte instructions). Some of them a jumps, but still there is no way this takes 1 ms to execute.

NOTE: I no longer actively read this forum. Please ask your question on www.eevblog.com/forum if you want my answer.

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

Thanks for your effort. I thought the while loop would be shorter. But only half of the second if/else must be executed.

However, it still can be an even number, that timer_counter was read 0 in a row.

 

@Aleksey_ua:

What is your target device?

In the beginning was the Word, and the Word was with God, and the Word was God.

Last Edited: Thu. Jul 23, 2015 - 12:18 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

With -Os, the loop in question is 12 instructions long.

 

It will be one of the tinys, only they have ISR named TIM0_COMPA_vect.

NOTE: I no longer actively read this forum. Please ask your question on www.eevblog.com/forum if you want my answer.

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

Interesting thread, but I bet EVERYONE that reads 'multitasking' in the title thinks this has something to do with a real time operating system that switches tasks at a periodic rate to 'simulate' two or more tasks running simultaneously. Most microcontroller programs use the 'main loop with interrupts' model. If a task is a program executing with return addresses on a stack, then this model is just one task. A multitasker has a stack for each task.

 

Imagecraft compiler user

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

Set a flag in the interrupt and check the flag instead of "timer_counter == 0".

Why not just toggle the LED in the interrupt when timer_counter is set to 0? 

Regards,
Steve A.

The Board helps those that help themselves.

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

I'm assuming this is just an example and the actual application must do more than simple LED blinking.

NOTE: I no longer actively read this forum. Please ask your question on www.eevblog.com/forum if you want my answer.

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

It is a tiny13 with default 1,2 MHz clock with 1ms timer interrupt calling, optimize (-O1). I have experimented with condition inspection ">=" (it doesn't work too), just my last code playground left with "==". 

 

This is a starting point after it will become into more complex task, where one LED should not only blinking, so I prefer control it in the main loop.

 

Atomicity is my small fear. I know that "volatile" solve only some optimisation issue, when code could to be compiled.

Hope, I use right that reference http://www.atmel.com/webdoc/AVRL...

 

Yes this is just a general approach for simulate multi tasking through interrupts, perhaps not quite right name of thread, sorry me for that.

 

My final working code

 

#define F_CPU 1200000UL
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include <util/atomic.h>

volatile uint16_t timer_counter=0;
int8_t flag=0;

#define BLINK_LED 500

ISR (TIM0_COMPA_vect) {
  
  timer_counter++;
  
  if (timer_counter >= BLINK_LED)
  {
    timer_counter=0;
    flag=1;
  }
  
}

void setup () {
  
  // LEDs on PB2, PB3.
  DDRB =  0b00011101;
  PORTB = 0b00100010;
  
  // Analog comparator OFF.
  ACSR |= (1 << ACD);
  
  // Start timer T0 with prescaler 8.
  TCCR0B |= (1<<CS01);
  
  // Enable time T0 overflow interrupt.
  TIMSK0 |= (1<<OCIE0A);
  
  // Enable CTC Mode.
  TCCR0A |= (1<<WGM01);
  
  // T0 will overflow each 1 ms.
  OCR0A = 150;
  
  // Reset timer T0 flags.
  TIFR0 = 0;
  
}

void Blinking () {
    
    if (flag) 
    {
      // Toggle LED
      PORTB ^= (1<<PB3);
      
      // Interrupt protection before accessing variable
      ATOMIC_BLOCK(ATOMIC_FORCEON)
      {
        flag=0;
      }
      
    }
  
}

int main(void)
{
  setup ();
  sei();
  
  while(1)
  {
        
    Blinking();
    
    if (!(PINB & (1<<PB1)))
    {
      PORTB |= (1<<PB2);
    }
    else
    {
      PORTB &=~(1<<PB2);
    }
    
  }
  
}

 

 

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0
volatile uint16_t timer_counter=0;
int8_t flag=0;

Variables used both in ISR and main() must be declared as "volatile".

"timer counter" is used only in ISR.

"flag" is used in ISR and also in main().

So it should be

uint16_t timer_counter=0;
volatile int8_t flag=0;

 

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

You are right, thanks. I forgot to change it.

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

Instead of using a (boolean) flag, I used once an 8 bit counter each in the ISR and main context:

 

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

#define TIM0_COMPA_DIVIDER 500

volatile int8_t SignalCounterISR;

ISR(TIM0_COMPA_vect) {

  static uint16_t timer_counter;
  timer_counter++;
  
  if (timer_counter >= TIM0_COMPA_DIVIDER)
  {
    timer_counter=0;
    SignalCounterISR++;
  }
  
}

void init() {
  ...  
  sei();
}

int main(void)
{
  int8_t SignalCounterMain = 0;
  init();
  
  while(1)
  {
    
    if (SignalCounterMain!=SignalCounterISR)
    {
      SignalCounterMain++;
      /* do a special task (for example clock) */
    }
  
    /* do other tasks, which may vary in execution time */	
  
  }
  
}

 

 

Only SignalCounterISR is shared by ISR and main context, so it must be "volatile". The read access of SignalCounterISR in the main context is an atomically operation, because it's is only 8 bit.

With this I have a kind of buffering, which keeps the timing better, if a certain task must exactly serviced a certain number of times in a given period.

If one other "task" / function needs longer than the period between two "signals" from the ISR, the signals get lost much later (after 256 times) regarding to a simple flag.

With this the task can catch up, if again more CPU time is available. (Depends on the task, if this behaviour is wanted.)

 

In the beginning was the Word, and the Word was with God, and the Word was God.

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

My tutorial on multi-tasking used a flag. Might be a good read for the OP

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

This is very helpful explanation, thanks for code.

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

skotti wrote:

Instead of using a (boolean) flag, I used once an 8 bit counter each in the ISR and main context:

...

Only SignalCounterISR is shared by ISR and main context, so it must be "volatile". The read access of SignalCounterISR in the main context is an atomically operation, because it's is only 8 bit.

With this I have a kind of buffering, which keeps the timing better, if a certain task must exactly serviced a certain number of times in a given period.

If one other "task" / function needs longer than the period between two "signals" from the ISR, the signals get lost much later (after 256 times) regarding to a simple flag.

With this the task can catch up, if again more CPU time is available. (Depends on the task, if this behaviour is wanted.)

 

Indeed; I sometimes use this.

 

My most common "tick" value for internal timekeeping is 10ms.  With a simple boolean flag, that leaves me up to 20ms before I "miss" a tick.

 

With a faster tick, and occasional long operations (e.g., EEPROM structure write;  SD card operation) using a counter in the timer tick ISR allows the mainline to do intelligent processing based on the number of elapsed ticks.

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.