[TUT][C] Multi-tasking tutorial part 1

Go To Last Post
141 posts / 0 new

Pages

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

Thank you for your comment Cliff, apologize me if you think i am asking a silly question, but is any "time slicing operation also a RTOS" ?
I think yes! since I can myself define a RTOS as an operating system which has a consistent timing and high degree of task priority.

so it seems like my original question was whether a RTOS is a software or hardware or if we can we it in have both parties,

(again my apologies if you think i am asking a silly question)

Thanks.

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

Quote:

but is any "time slicing operation also a RTOS" ?

It's an "OS" but whether it is "T" is another matter. For example Linux is a time slicing OS but even when you apply the real-time patches to the kernel it's still very difficult to guarantee true real time operation. The latency on interrupt handling can be quite a problem!
Quote:

so it seems like my original question was whether a RTOS is a software or hardware or if we can we it in have both parties,

There is no question it's simply a piece of software however it would be difficult to envisage a time slicing OS that does not use some form of timer. In Linux and Windos this means a 10ms or 1ms timer that switches between anything up to about 10,000 threads of execution at any one time.

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

Great tutorial Kartman, as always!

This is supposed to be a question, but I am not really sure how to ask it, but here goes:

In the code example, task6 and task7 will end up running in roud-robin style after the first execution of each task, because of the higher priority of task6, right? But sometimes you would want two tasks running syncronously, not one time slot apart.
So what would the solution be then? I guess there is only one, do a task with two sub-tasks in one time slot?

I hope someone understand what I am talking about.

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

Say you wanted task 6 & 7 to run every second but half a second apart. Task6 would load the task timer for task7 with half a second. Similarly, task7 would load the timer for task6 with half a second. These two tasks would happily ping pong.

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

Hehe :) I actually meant the other way, two tasks beeing executed in the SAME time slot.

What it all comes down to is probably that I don't see how the prioritization actually is thought to work.

I see the tasks running in round-robin style after all tasks have been executed once and everything has settled. With this the highest priority task will simply be the first one in the "round-robin schedule".

Ok, last thought/question, why would one want to use prioritization? I obviously fail, big time, to see the reason :-(

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

Quote:

Ok, last thought/question, why would one want to use prioritization?

Windows (these days) does it for example. Things like the mouse and keyboard service have a higher priority than when, for example, Excel is recalculating a spreadsheet. In days gone by you spent a lot of time looking at an "hour glass" mouse cursor (often that would not move) while Excel calculated.

Even when you aren't using an OS you do the same in the design of your main/interrupt mixed programs where you do things like updating LCDs in the "spare time" while things like reading the spindle rotation or operating the control valves is done as a matter of urgency.

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

At first I wanna thank the author for writing this great tutorial!
I have been searching a long time for a easy to use and understand "RTOS" or something that could handle tasks. After I never got DuinOS running and FreeRTOS and others were just too complicated to get it running with the arduino IDE I thought I would never find something, untill I found this tutorial.
So I just wanted to say that it works perfect with the arduino IDE, just copy-> paste and rename the "main" into "loop" and you can have fun with the arduino libraries and create tasks.

THANKS!!! you saved me a lot of time!!!

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

Another happy customer! I'm glad you grasped the concept and changed the code to suit your requirements.

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

He is not the only one Mate.
I myself really have found this tutorial extremely useful I dont know if it was because i am actually new into RTOS & Multitasking, But I very much appreciated all the effort you put into this tutorial man. except that i forgot to say Thank you :wink:
I really wanted to thank you Kartman, Cliff, Johan, and many other moderators here
at avrfreaks who really put a lot of effort into helping us.

Cheers Guys.

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

Hey Kartman,

Obviously, this post is old so my thanks on your wonderful tutorial are a bit behind the times, but I figured I'd put them here anyways. I've not yet had time to read (and digest) Dean's timer tutorial fully, so the values you use to setup the timer interrupt are kind of a mystery. Perhaps you might consider commenting those, or lines like it, in future tutorials for the slowpokes in the pack like me :).

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0
 TCCR0B = 0x00; //stop
 TCNT0 = 0x00; //set count
 TCCR0A = 0x02;   //CTC mode
 OCR0A = 0x9C;
 TCCR0B = 0x05; //start timer

Are the comments not enough then? That's almost as simple a timer setup as you can get and the key comment is "// CTC mode". A quick look at any AVR datasheet will show you that is the mode where the timer counts up to the value in a compare register (OCR0A in this case) and then resets to 0 (possibly generating an interrupt as it does so). So this timer is going to count 0x00, 0x01, 0x02... 0x93, 0x94, 0x95 (interrupt), 0x00, 0x01, 0x02..

As the count of 0x00 is included in that it's really counting 150 (0x95+1) timer "ticks" between overflows.

Also the "// start timer" comment shows 0b101 being loaded in to the B register. That is going to be /1024 so the timer will be running at 1/1024th of the CPU speed. It will therefore count 150*1024 = 153,600 CPU cycles between compare match interrupts. The comment at the top of the code say 16MHz so it executes 16,000,000 in 1 second therefore the interrupts will be 153600/16000000 apart that is 0.96ms - so close to 1ms.

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

After finally getting through the Timers document in this forum they all make perfect sense. I think the names of the registers and such were more confusing than anything, but I see now that the comments are appropriate if you reference any AVR datasheet you have handy.

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

Hi, Kartman, this is an awesome tutorial! Many many thanks!

I have two questions.
1) I noticed this portion of code:

while (task <= NUM_TASKS ) 
    { 
     if ((task_bits & pgm_read_byte(&bit_mask[task]))) 
           { 
           break; /* if activate task found..*/ 
         } 
      task++;         /* else try the next one */ 
    }

Shouldn't it be "<" in stead of "<="? Because, after task=7 and task++, it'll be task=8 and program will enter the loop and try to find bit_mask[8], but bit_mask is only 0:7.

2) When you're writing "set_task(6)", it's only writing 64 to task_bits, right? The "task6()" function will actually start (I mean, the timer for task 6 and its LED blinking) in the "task_dispatch()" function, right? The reason I'm asking this is this portion of code:

set_task(7);   //task7 runs 
set_task(6);   //task6 runs

What I've understood is happening here is "task_bits" is assigned 128 first, then without actually starting "task7()" (I mean, the timer for task 7 and its LED blinking), "task_bits" is assigned 64. And then the main loop starts and "task_dispatch()" is called which starts "task6()". And I didn't find "task_bits" being assigned 128 afterwards which would start "task7()" in ""task_dispatch()".

It's probably my mistake in understanding. But I'll appreciate your or anyone else's help very much in helping me with this.

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

Quote:

Shouldn't it be "<" in stead of "<="?

If you read/search through the whole thread you will find that there has been two such remarks prior to yours. (And unless I am missing something, you are correct.)

Quote:

What I've understood is happening here is "task_bits" is assigned 128 first, then without actually starting "task7()" (I mean, the timer for task 7 and its LED blinking), "task_bits" is assigned 64.

No. The set_task function uses the |= operator, not the = operator. Thus, first call ( set_task(7) ) sets bit 7 (the task_bits gets the value 128), Next call ( set_task(6) ) sets bit 6 without setting or clearing any other bits, so now the task_bits has the value 192.

If you are unfamiliar with bit manipulating operators then there is an excellent tutorial here at AVRfreaks. Mandatory read.

"He used to carry his guitar in a gunny sack, or sit beneath the tree by the railroad track. Oh the engineers would see him sitting in the shade, Strumming with the rhythm that the drivers made. People passing by, they would stop and say, "Oh, my, what that little country boy could play!" [Chuck Berry]

 

"Some questions have no answers."[C Baird] "There comes a point where the spoon-feeding has to stop and the independent thinking has to start." [C Lawson] "There are always ways to disagree, without being disagreeable."[E Weddington] "Words represent concepts. Use the wrong words, communicate the wrong concept." [J Morin] "Persistence only goes so far if you set yourself up for failure." [Kartman]

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

Thanks, Johan!! I've read the bit manipulation tutorial recently, and haven't got very used to it. So, I misunderstood that part. Thanks for clearing it up!

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

JohanEkdahl wrote:
Quote:

Shouldn't it be "<" in stead of "<="?

If you read/search through the whole thread you will find that there has been two such remarks prior to yours. (And unless I am missing something, you are correct.)

I agree, although Kartman's response to the first remark was:
Kartman wrote:
Why? What benefit would the change make?

"When you arise in the morning think of what a privilege it is to be alive: to breathe, to think, to enjoy, to love." - Marcus Aurelius               

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

That portion of code is not ideal, but the later switch() relies on the value 8 being 'no task'. Therefore even though i go over the end of the bit_mask table, nothing comes of it. In retrospect i should've coded things clearer. Pascal would've given a runtime error! The challenge is to rewrite that section of code so it doesn't overflow the table and doesn't rely on a side-effect. The change is not simply using < as opposed to <=

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

This method saves some bytes as well as avoiding the abovementioned problems.

if (task_bits & 0x01)
   {
   task0();
   }
  else if (task_bits & 0x02)
   {
   task1();
   }
  else if (task_bits & 0x04)
   {
   task2();
   }
  else if (task_bits & 0x08)
   {
   task3();
   }
  else if (task_bits & 0x10)
   {
   task4();
   }
  else if(task_bits & 0x20)
   {
   task5();
   }
  else if (task_bits & 0x40)
   {
   task6();
   }
  else if(task_bits & 0x80)
   {
   task7();
   }

No one seemed to answer my question:

Quote:
I agree, although Kartman's response to the first remark was:

Kartman wrote:
Why? What benefit would the change make?


The answer I was looking for was, well, it would break the code as I described in my previous post.

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

I saw your previous post last night but was too tired to reply. Good explanation. I also like the very simple fix above.

Thanks Kartman.

"When you arise in the morning think of what a privilege it is to be alive: to breathe, to think, to enjoy, to love." - Marcus Aurelius               

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

Cheers Larry. That code had remained unchanged for 15 years or so. My new attempt just felt 'clunky' as there was no looping but it does seem to be much simpler and more obvious. I was modifying some code at the office today and thought I'd try it. Imagecraft reported around 20 bytes less code for the new method and one less byte of ram. So win-win. I wasn't motivated enough to measure the execution time, but I'd expect it is probably a few cycles less worst case especially since the compiler should be able to cache the task_bits variable in a register. I have had part two written for the past two years but never got around to proof reading it to make it suitable for publication. Not enough time since children and home construction projects eat up my time.

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

Hi Kartman,

First of all, thanks for the inspiring code.

If I understand your code correctly, when two task timers run to zero at the same time, only the task with the lowest number will be executed in the current time slice. The other task will be executed 10 ms later. This would also imply that the task timer of this second task will be re-initialized 10 ms later. It will never make up for the lost time will it? So basically only task0 would be really deterministic...

By the way, same here with kids and construction,
Cheers.

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

RedDuck, you understand correctly. In many cases hard real time is not necessary. If you're just controlling relays and a lcd, such anomalies are not apparent or cause a problem. Obviously, if you really needed hard timing, then you would have that done via an interrupt or hardware (timer etc).

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

thanks, is part 2 completed?

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

Can you find it?... No. Maybe soon.

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

Kartman

Informative tutorial... thanks.

sundownr


All of us can help make a better world simply by taking better care of children

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

Thanks a lot Kartman. You made my work very easy and
I learned other things aside from multitasking.
Please find a free time to complete second part. Many of us are waiting.

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

instead of

pgm_read_byte(&bit_mask[task])

can and it's just a simple

(1<<task)
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Yeah that'd work but one would have to study the generated Asm to see which is more space/time efficient.

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

There's little doubt that (1<<task) is probably more obvious. As for efficiency, I agree with Cliff. Nevertheless, the net result is not going to be much of an efficiency gain methinks.
The source of that code goes back to the early days of an IAR 8051 compiler where I would look carefully at the generated code to coax the compiler into doing its best. If I was doing a code review and came across this, the questions I would ask:
1. does it work? No potential side effects?
2. is the intention reasonably obvious?
3. what would be gained by changing it?
4. Is the use consistant?
5. Is it MISRA compliant?

Granted it's a lot more verbose, but again, the pgm_read_byte() is a avr-gcc - ism. given another compiler, it would probably read a lot simpler. Does less source == less object?? (that was a rhetorical question). Less chance for typing errors though!

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

Hi,

I have a problem in task_timers array in which it does not get set in tasks functions. On line 129 of the log file, the tasktimers array stops getting value in the tasks function. What could be the problem?
Here is the code.

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include "uart.c"

// PD0 and PD1 are allocated for serial communication
#define POMPdasti 			PD2
#define DARBAND 			PD3
#define POMPDutomat  		PD4
#define PORKONDASTI 		PD5
#define PORKONATOMAT 		PD6
//---------------------------------------------------------------
//OITOUT variables
#define PORCONout 			PB2
#define POMPout 			PB6
#define Dargozarout 		PB7
//----------------------------------------------------------------
//The rest of input variables( continued from PORTD)
#define Dargozardasti 		PC0
#define Dargozarautomat 	PC1
#define uswitchdargozar		PC2
#define usw1 				PC3
#define pressure 			PC4
//----------------------------------------------------------------
//Some definitions regarding RTOS
#define NUM_TASKS 10
char task_bits = 0;  /* lsb is hi priority task */
volatile char tick_flag = 0;    /* if non-zero, a tick has elapsed */
volatile unsigned int task_timers[NUM_TASKS]={};                  /* init the timers to 0 on startup */
static const PROGMEM unsigned int bit_mask[]={1,2,4,8,16,32,64,128,256,512};            /* value -> bit mask xlate table */
static const char PROGMEM tasklegend[10] = {"0123456789"};	
volatile unsigned char FLAG=255;
volatile unsigned char outputtask=0;
char buf;
//-------------------------------------------------------------------------------
//--------------------------------------------------------------------
//Prototype  definitions
void init_devices(void);
void timer0_init(void);
void reset_task(unsigned char tsk);
void set_task(unsigned char tsk);
void task_dispatch(void);
void task0(void);
void task1(void);
void task2(void);
void task3(void);
void task4(void);
void task5(void);
void task6(void);
void task7(void);
void task8(void);
void task9(void);
//---------------------------------------------------------------------------




void task_dispatch(void)
{
  /* scan the task bits for an active task and execute it */
//static const PROGMEM int bit_mask[]={1,2,4,8,16,32,64,128,256,512};            /* value -> bit mask xlate table */
  unsigned char task;
   

/* take care of the task timers. if the value ==0 skip it
   else decrement it. If it decrements to zero, activate the task associated with it */

  task=0;
  while (task < NUM_TASKS )
    {
		 outputtask=pgm_read_byte(&tasklegend[task]);
		 uart_puts("\r\n");
		 uart_puts("Task Number #: \r\n");
		 uart_putc(outputtask);
		 uart_puts("\r\n");
		 uart_puts("Task Timer is:\r\n");
		 itoa(task_timers[task],buf,10);
		 uart_puts(buf);
		 uart_puts("\r\n");
		 uart_puts("\r\n");

    if (task_timers[task])
      {
        task_timers[task]--;            /* dec the timer */
      if (task_timers[task] == 0 )
            {
		set_task(task); /* if ==0 activate the task bit */
         }
      }
	 
    task++;
	}

  task = 0; /* start at the most significant task */
  while (task <= NUM_TASKS )
    {
     if ((task_bits & pgm_read_byte(&bit_mask[task])))
           {
		   		   
           break; /* if activate task found..*/
         }
      task++;         /* else try the next one */
    }
	  
	
	  
  switch(task)            /* if task bit is active..execute the task */
    {
    case 0:
      task0();
      break;
    case 1:
      task1();
      break;
    case 2:
      task2();
      break;
    case 3:
      task3();
      break;
    case 4:
      task4();
      break;
    case 5:
      task5();
      break;
    case 6:
      task6();
      break;
    case 7:
      task7();
      break;
	case 8:
      task8();
      break;
	case 9:
      task9();
      break;	  
    default:
      break;                  /* no task was active!! */
    }                       
}

// enable a task for execution
void set_task(unsigned char tsk)
{
  task_bits |= pgm_read_byte(&bit_mask[tsk]);       /* sets a task bit */
}
// disable a task from executing
void reset_task(unsigned char tsk)
{
  task_bits &= (~pgm_read_byte(&bit_mask[tsk]));  /* resets a task bit */
}


//----------------------------------------------------------------------------
//TASK 0
void task0(void)
{
task_timers[0] = 1; 
/*-------------------------------------------------------------------------
//Porkon Automat
if(PIND&(1<<pressure ))   
{  
if((PIND&(1<<PORKONATOMAT))&&(PINC&(1<<usw1))) 	PORTB|=(1<<PORCONout);
}
*///---------------------------------------------------------------------
  reset_task(0);
}
//----------------------------------------------------------------------------
//TASK 1
void task1(void)
{
task_timers[1] = 1; 
/*--------------------------------------------------------------
//PORKON DASTI
if(PINC&(1<<pressure ))  
{
	if(PIND&(1<<PORKONDASTI))
			{
			PORTB|=(1<<PORCONout );
			}
		else if(!(PIND&(1<<PORKONDASTI))&&(!(PIND&(1<<PORCONout))|(!(PIND&(1<<usw1)))))    PORTB&=~(1<<PORCONout);


}

*///----------------------------------------------------------
  reset_task(1);
}
//----------------------------------------------------------------------------
//TASK 2
void task2(void)
{
task_timers[2] = 1; 
if(PIND&(1<<PD2)) 
{
/* outputtask=pgm_read_byte(&tasklegend[2]);
 uart_puts("Task Called: ");
 uart_puts("\r\n");
 uart_putc(outputtask);
 uart_puts("\r\n");*/

}
/*------------------------------------------------------------------
//DARGOZARE DASTI
if(PIND&(1<<pressure ))  
{


}
*///------------------------------------------------------------------
  reset_task(2);
}
//----------------------------------------------------------------------------
//TASK 3
void task3(void)
{
task_timers[3] = 1; 
if(PIND&(1<<PD3)) 
{
 outputtask=pgm_read_byte(&tasklegend[3]);
 uart_puts("Task Called: ");
 uart_puts("\r\n");
 uart_putc(outputtask);
 uart_puts("\r\n");
}
/*--------------------------------------------------------------------------
//Dargozar dasti
if(PIND&(1<<pressure ))  
{
if (PINC&(1<<PC0)) 
{
PORTB|=(1<<PB7);
}
else if (!(PINC&(1<<PC0))&&((!(PINC&(1<<Dargozarautomat)))|(!(PINC&(1<<uswitchdargozar))))) 
{
PORTB&=~(1<<PB7);
}
}
*///-------------------------------------------------------------------------
  reset_task(3);
}
//----------------------------------------------------------------------------
//TASK 4
void task4(void)
{
task_timers[4] = 1; 
if(PIND&(1<<4)) 
{
 outputtask=pgm_read_byte(&tasklegend[4]);
 uart_puts("Task Called: ");
 uart_puts("\r\n");
 uart_putc(outputtask);
 uart_puts("\r\n");

}
/*------------------------------------------------------------------------
//Dargozar automat
if(PIND&(1<<pressure ))  
{

if(    (   PINC&(1<<Dargozarautomat)    ) && (  PINC&(1<<uswitchdargozar) ) )
{PORTB|=(1<<Dargozarout);}
else if(!(PINC&(1<<Dargozarautomat))&&(!(PINC&(1<<PC0)))&&(!(PINC&(1<<uswitchdargozar))))
{PORTB&=~(1<<PB7);}//Dargozarout
}
*///----------------------------------------------------------------------
  reset_task(4);
}
//----------------------------------------------------------------------------
//TASK 5
void task5(void)
{
task_timers[5] = 1; 
/*----------------------------------------------------------------------
//-Pomp Automat-
if(PIND&(1<<pressure ))  
{
if(PIND&(1<<POMPDutomat)&&((PIND&(1<<usw1)))) 	PORTB|=(1<<POMPout);

}
*///----------------------------------------------------------------------


  reset_task(5);
}
//----------------------------------------------------------------------------
//TASK 6
void task6(void)
{
task_timers[6] = 1; 
/*-------------------------------------------------------------------
//POMP DASTI
if(PIND&(1<<pressure ))  
{
	if(PIND&(1<<POMPdasti)) 
			{
			PORTB|=(1<<POMPout);
			}
		else if(!(PIND&(1<<POMPdasti))&&(!(PIND&(1<<POMPDutomat))|(!(PIND&(1<<usw1)))))    PORTB&=~(1<<POMPout);


}
*///--------------------------------------------------------------------
reset_task(6);
}
//----------------------------------------------------------------------------
//TASK 7
void task7(void)
{
task_timers[7] = 1; 
/*/---------
//DARBAND BA TAKHIR
if(PIND&(1<<pressure ))  
{
if (   (PIND&(1<<DARBAND) ) && (PIND&(1<<usw1)) && FLAG )  //automatic state-halat dasti nadarad
         { 
	   PORTB|=(1<<DARBAND);
       //  i=TCNT0;
       //  _delay_ms(i*1000); 
          PORTB&=~(1<<DARBAND); 
		  FLAG=0;
    }
	

   if(!(PIND&(1<<usw1) ) |(!(PIND&(1<<DARBAND))) ) FLAG=255;
}
*///-----------------------------------------------------


reset_task(7);
}
//----------------------------------------------------------------------------
//TASK 8
void task8(void)
{
task_timers[8] = 1; 
reset_task(8);
}
//----------------------------------------------------------------------------
//TASK 9
void task9(void)
{
task_timers[9] = 1; 
reset_task(9);
}
//----------------------------------------------------------------------------













void init_devices(void)
{
 //stop errant interrupts until set up
cli(); //disable all interrupts
uart_init(UART_BAUD_SELECT(9600,16000000));
DDRB = 0x30;   //port 4 & 5 as outputs
timer0_init();
DDRD=0;
PORTD=255;

//PD0  ---> usw1  moment
//PD1 --->  usw2 pressure moment
//PD2 --->  SHAsi porkon debounce
//PD3 --->  Shasi Pomp  debounce
//PD4 --->  Shasi Darband one of the ANd inputs of the corresdoponding output delayed by a counter


DDRB=0xff;  // all outputs
DDRD=0x00;  //Input variables
DDRC=0X00;  //rest of inout variables continued from PORTD

 
 
MCUCR = 0x00;
GICR = 0x00; //extended ext ints
MCUCR = 0x00;
 
TIMSK = 0x02; //timer 0 interrupt sources
 
sei(); //re-enable interrupts
//all peripherals are now initialized
}

void timer0_init(void)
{
//TIMER0 initialize - prescale:1024
// WGM: CTC
// desired value: 10mSec
// actual value: 10.048mSec (-0.5%)
 TCCR0 = (1<<WGM01) | (0X05); 
 TCNT0 = 0x00; 
 OCR0 = 144;
 }
  




int main(void)
{

  init_devices();
  uart_puts("hERE IS MAIN() \r\n");
  
//
//   start at least one task here
//
for(unsigned char j=0;j<=9;j++){
task_timers[j]=1;

outputtask=pgm_read_byte(&tasklegend[j]);
		 uart_puts("\r\n");
		 uart_puts("Task Number from main #: \r\n");
		 uart_putc(outputtask);
		 uart_puts("\r\n");
		 uart_puts("Task Timer from main is:\r\n");
		 itoa(task_timers[j],buf,10);
		 uart_puts(buf);
		 uart_puts("\r\n");
		 uart_puts("\r\n");
//      main loop


 set_task(j);   
 }



  while(1)
    {
    if (tick_flag)
      {
      tick_flag = 0;
     task_dispatch();              // well....
     }
   }
  return 0;
}
//
//   a task gets disPDtched on every tick_flag tick (10ms)

ISR(TIMER0_COMP_vect)
{
 //TIMER0 has overflowed
    tick_flag = 1;
}


here is the Teraterm output log.

Attachment(s): 

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

You've added more tasks but task_bits is only a char which has 8 bits - make task_bits an unsigned int. 10 does not go into 8. I think you have too many tasks. I would suggest you combine some of them into one task.for example, most of my stuff is industrial control. I have one task for modbus communications, one task for control logic, one task for a lcd. The comms task runs on demand, the control logic every 100ms ( because relays usually don't work much faster) and the display task every second. Having a lot of tasks doing a little amount of work is probably not the most efficient approach.

Setting the task_timer[x] to 1 for each task means the tasks will forever be competing to execute - this may just be part of your test.

Basically - use the minimum amount of tasks necessary.

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

Kartman,

Thanks so much for this tutorial. I definitely learned from it. I just posted my first project, that uses the framework on an ATtiny2313. It was probably overkill for what I was doing, but it was worthy learning experience to use it.

See http://www.avrfreaks.net/index.p... for more about the project.

Thanks again,
Nathan

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

Very interesting tutorial. I had often wondered how I'd take care of multiple things going on at once, looks like I may have found a way of doing it. Thanks.

My video channel on PC Repair and troubleshooting: http://www.youtube.com/user/Jaso...

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

great tutorial

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

Hi Kartman, would you please help me figuring out why task0 doesn't work when I added some decrement?

void task0(void) 
{ 
  task_timers[0] = 10;
  uint32_t i = 100;
  reset_task(0);
  if(--i==10)
  {
  PORTA ^= 1<< PA0;  // every sec 
  reset_task(0);      
  }
 reset_task(0); 
} 

?

The LED of task1 flashes properly but the task0 doesn't :(
here I wasn't trying to add some delays because it would be causing some conflicts but I don't see why why the LED in task0 doesn't flash after I decrement I until it reaches 0?
here is task1 which works fine,

void task1(void) 
{   
  PORTA ^= 1<< PA1;
  task_timers[1] = 30;      //every 300ms 
  reset_task(1);  
} 

Cheers

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

Few years ago Someone would come in my Bedroom and ask why it isn't tidy, i would reply "it's because me & my bedroom obey the 2nd Law of Thermodynamics" ;) -> Entropy :lol:

Anyways, going back to your question,
i would recommend to declare variable "i" as static first of all. this will prevent it to get destroyed everytime void task0(void) finish running :)
consider this code.

     #include

     void play (void);

     int main()
     {
       uint8_t i;
       for(i=0; i<5; i++)
        {
          play();
         }
      }
   
      void play (void)
      {
      int j=10;
      printf("#d \n",j);
       ++j;
      }

The output i believe will be:

10
10
10
10
10

However Try this one!

     #include

     void play (void);

     int main()
      {
        uint8_t i;
        for(i=0; i<5; i++)
         {
           void play (void)
          }
        }

        void play(void)
        {
           static int j=10;
           printf("#d \n",j);
           ++j;
          }

and the output will surely be!

10
11
12
13

Can you see the Advantage of the Keyword "static especially when you have an increment/ decrement from a f(x) outside main() ?
Apart from this Another good programming habit is to re-initialize your variable after if expires. since your are intending to decrement from 10 to zero then ensure that "i" will be 10 again so it can be decremented and so forth..

Try

        void task0(void)
          {
                task_timers[0] = 10;
                static uint32_t i = 10;
                reset_task(0);
                if(--i==0)
                   {
                       i = 10;
                       PORTA ^= 1<< PA0;  // every sec
                       reset_task(0);     
                   }
                 reset_task(0);
            }

And let's us know how it went :)

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

Thank you so much for ur helpful information,
yeah it is now working fine. :)

Cheers

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

Why so many reset_task(0); ?
It really only needs 1

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

A question, it looks like only one task will run each tick, the highest priority one, if lower priority tasks are ready they have to wait for the next tick and hope a higher priority task is not ready, is that the intent?
what changes would be needed to let all ready tasks run, in priority order, with each tick?

Was part 2 posted? Thanks for the easy to read/run tutorial!
JC

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

The intent is that you would have regular running tasks as lower priority and infrequent but important tasks as a higher priority. Generally i would have a serial receive comms task as high priority. When a serial packet is assembled by the rx isr, it enables a task to process it. Lower priority tasks might be updating a lcd. The human wont pick if it is delayed by a few 10's of milliseconds.

Fixed priority is simple. It can have drawbacks in that lower priority tasks can get starved. Another method is round - robin. Effectively all tasks are the same priority. Each tick you would look for the next task ready to run. You could have a counter that has the next task number to run. On the tick you would increment the counter, roll back to 0 if > 7 then test to see if that task bit is set - if it is, call the task else inc the counter. Only try 8 times per tick otherwise you'll keep spinning until another task becomes active.

There's other techniques based on priority, round robin, least recently run and combinations of these. There has been extensive research into this topic - so Google if you've brave or bored. Fixed priority can starve other tasks if you're not careful whereas round robin gives everyone a fair go. What you choose depends on the nature of the problem you want to solve.

Part 2 is still vapourware. The draft was done some time ago but i havent been suitably motivated to check the code and publish. Too many other distractions.

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

This is an interesting and informative thread, thanks to all who contributed to the discussion,
especially Kartmannn and clawson. Looking forward for tutorial part 2.
I've boiled down what I learned here and use the following (example) framework in my projects:

#include 
#include 
#include 

#define PRIO_1 0
#define PRIO_2 1
#define PRIO_3 2
#define PRIO_4 3
#define PRIO_5 4
#define PRIO_6 5
#define PRIO_7 6
#define PRIO_8 7

void tasker_init(void);
void task_dispatch(void);
void prio1_task(void);
void prio2_task(void);
void prio3_task(void);
void prio4_task(void);
void prio5_task(void);
void prio6_task(void);
void prio7_task(void);
void prio8_task(void);

typedef void(*f_ptr_t)(void);

f_ptr_t const tasks[] PROGMEM = {
 (f_ptr_t) prio1_task,
 (f_ptr_t) prio2_task,
 (f_ptr_t) prio3_task,
 (f_ptr_t) prio4_task,
 (f_ptr_t) prio5_task,
 (f_ptr_t) prio6_task,
 (f_ptr_t) prio7_task,
 (f_ptr_t) prio8_task
};

#define NUM_TASKS (sizeof(tasks)/sizeof(tasks[0]))

volatile uint8_t task_flag = 0; /* if non-zero, a tick has elapsed */
uint8_t task_timer[NUM_TASKS];

ISR(TIMER0_COMPA_vect)
{
 static uint8_t tasker_ticks = 0;
	
 if (++tasker_ticks == 10)
 {
  task_flag = 1;			/* scan tasks every 10 ms */
  tasker_ticks = 0;
 }
}

void tasker_init(void)
{
	/* port 0..5 as outputs */
 DDRB = _BV(PB5)|_BV(PB4)|_BV(PB3)|_BV(PB2)|_BV(PB1)|_BV(PB0);
 TCCR0A = _BV(WGM01);   /* CTC mode */
 OCR0A = 250;           /* 1 ms @ 16 MHz and prescaler = 64 */
 TIMSK0 = _BV(OCIE0A);	/* Enable Timer/Counter0 Compare Match A interrupt */
 TCCR0B = _BV(CS00) | _BV(CS01); /* prescaler 64, start timer */

 /* experimental attempt to spread the (initial dispatch of) tasks */
 uint8_t prime_number[]={2, 3, 5, 7, 11, 13, 17, 19};

 for(int i = 0; i< NUM_TASKS; i++)
 {
  task_timer[i] = prime_number[i & 0x07];
 }
 sei();
}

void prio1_task(void)
{
 task_timer[PRIO_1] = 10; /*(placeholder) unused in this example*/
}

void prio2_task(void)
{	
 task_timer[PRIO_2] = 10; /*(placeholder) unused in this example*/
}

void prio3_task(void)
{
 PORTB ^= _BV(PB0);
 task_timer[PRIO_3] = 10; /* every 100ms */
}

void prio4_task(void)
{
 PORTB ^= _BV(PB1);
 task_timer[PRIO_4] = 20; /* every 200ms */
}

void prio5_task(void)
{
 PORTB ^= _BV(PB2);
 task_timer[PRIO_5] = 30; /* every 300ms */
}

void prio6_task(void)
{
 PORTB ^= _BV(PB3);
 task_timer[PRIO_6] = 40; /* every 400ms */
}

void prio7_task(void)
{
 PORTB ^= _BV(PB4);
 task_timer[PRIO_7] = 50; /* every 500ms */
}

void prio8_task(void)
{
 PORTB ^= _BV(PB5);
 task_timer[PRIO_8] = 60; /* every 600ms */
}

void task_dispatch(void)
{
 f_ptr_t fptr;
 for (uint8_t task = 0; task < NUM_TASKS; task++)
 {
  if (task_timer[task]) 
  {
   if (!--task_timer[task])
   {
    fptr = (f_ptr_t)pgm_read_word(&tasks[task]);
    fptr();
   }
  }
 }
}

int main( void )
{
 tasker_init();

 while(1)
 {
  if (task_flag)
  {
   task_flag = 0;
   task_dispatch();
  }
 }
}

1. does it work? No potential side effects?
It works, no side effects discovered so far. Comments welcome.
2. is the intention reasonably obvious?
I wanted to test if I had understood the different suggestions ;)
3. what would be gained by changing it?
Number of tasks not limited to eight, compact code...
4. Is the use consistent?
I think so.
5. Is it MISRA compliant?
Probably not. But this is an AVR-forum, isn't? ;)

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

Misra used to frown on the use of function pointers. The latest standard doesn't seem to be worried about them anymore. Since your function pointers are in flash, it is highly unlikely they would get inadvertently modified which was the original issue that Misra was addressing. Misra has gained a large following in embedded systems other than automotive. Compliance makes good sense - but i would stop short of strict compliance for amateur projects. As for function pointers vs switch vs if - i think the intention is pretty clear regardless of the implementation. Execution efficiency and code size? I don't think there are great gains to be made - maybe if you were targetting a tiny. Considering we sit in a loop waiting for a timer tick - then we're really not too concerned for extracting every last cycle.

Your example is rather synthetic - i gather you want each task to not 'bump' into each other and effectively have equal priority. In my instance, i wanted and needed priority. I had a comms task that was the highest priority - comms response was important. If the lcd update task got 'bumped' - no one would notice. Similarly for tasks like reading the adc, doing some control logic and activating relays or lamps - the non-determinism isn't really an issue. Of course, its not a solution for every problem. If you wanted equal sharing for all tasks, a round- robin strategy would be the choice. In most of the stuff I've done in the last 20 or so years, the strict priority has worked well.

More tasks? Really, you want less tasks! I think if you need more than 8 tasks, then either the architecture of the code is suspect or you really want to go to a pre-emptive rtos.

Part 2 was intended to introduce a real world application with modbus comms, some control logic and data acquisition and a simple 2x16 lcd interface with three buttons and a setup menu. The sort of thing an AVR class processor would be used for as a small controller. The code is 99% along with the words. I just haven't garnered the motivation to go through the checking to get it ready for an audience. Nothing worse than a tutorial that is full of bugs!
It's only been 7years!
If your intention was to have a task, for say, each flashing an led, then i'd say that is an inefficient implementation. If you can give me an idea of what you're wanting to achieve, then i can offer suggestions.

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

Hi Kartman,

 

As everyone else has said - thanks for the great tutorial! I've learned quite a bit following along with your code but have one question regarding the task_timers.

 

You have a "system tick" setup every 10ms by using the AVR's timer which you build off of. Every 10ms, the tick_flag is set, which triggers the task_dispatcher.

 

Inside the task dispatcher, the first step is to loop through all the task timers and decrement them to zero. As the task_timer is decrementing to zero, how do you ensure one "decrement cycle" takes exactly one "system tick"?

 

For example, in your tutorial, task7() sets task_timer[7] = 50, which translates into task7() running every 500ms. So every 10ms, the tick_flag is set, then we enter into the task dispatcher. As the task dispatcher is decrementing task_timer[7] how do you ensure that the task_timer is only decremented every 10ms to give a delay of 500ms?

 

Hope this makes sense :)

 

Brian

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

Brian, i think you've misread my code. Each system tick, each timer is decremented by one.

so for each system tick:

if the timer is not already zero,

subtract one

if the timer is now zero, set task bit

 

So it takes n ticks for n times 10ms delay

 

you might ask why i decrement to 0 rather than increment and compare - it is usually easier for a cpu to test for zero or non zero. 

 

If you run the code in the atmel studio simulator, you'll see how it works .

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

Kartman,

 

Thanks for clarifying! I hadn't had a chance to simulate yet but I must have misinterpreted your code at first glance. 

 

I was under the impression that each task timer would decrement to zero inside the while loop in one system tick, instead of one decrement per task timer per system tick. 

 

Makes much more sense now :)

 

Brian

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

Kartman,

 

One follow up question - Our system is now humming along happily, ticking away in 10ms intervals and waiting for a task_timer to decrement to zero. Once the task_timer hits zero, a task_bit is set and that task is ready to execute. How do we ensure the task to be executed completes within the remaining time before the next system tick?

 

In your example, this is straightforward since there are only a handful of lines of code per task to turn the LED on/off, but what about more complex systems where in a given task a IOs can be polled, ADC conversions need to take place, and LCD needs to be driven etc? Or the case where one system tick triggers multiple tasks to be ran. 

 

In other words, how can you determine what portion of your 10ms system tick is used by one task? I'm thinking this is related to the number of assembly instructions per statement of code?

 

Thanks Again!

Brian

 

 

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

 

The simple answer is : you measure it. You can do this in the simulator or you can physically measure the time by using an oscilloscope or logic analyser on a port pin you set on the beginning of the task and cleared when you exit. You can also use a timer to measure the time from when dispatch calls it and when it returns. 10ms equates to 160000 cycles at 16MHz - that allows you to get a bit of work done. Deviceslike lcds are notoriously slow, so they can chew up a few cycles. With some experience you get a feel for how intensive a bit of code might be. The number of instruction per statement of code is extremely variable it could range from 1 to infinity, so you can't use number of source lines of code as a time metric. In assembler you can count the cycles for each instruction - but that gets labourious. In many cases the execution time is variable as the execution path varies, so measurement is really the only way.

If the task exceeds its time budget, then other tasks just get pushed out - thus the term 'co-operative'.

 

Currently, you can only dispatch one task per tick.

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

If there is a sample project out there using this code tutorial to help learn the concept i would definitely find it beneficial. 

Like the rest of my slow crawling MCU journey i will eventually understand and use it but active tutorials are a blessing, videos especially.

^^Case in point it took 4 days for my brain to click and understand setting up the timers and how they work! Iv spent 140-160 hrs on c,c++ tutorials that really helped with the coding. 

~GuitarDude

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

The sample code flashes two leds as a example. Give yourself a challenge to solve then work towards it. You'll soon learn what works and what doesn't. Also, running the code in the simulator can give you an insight as to what happens at a step by step level.

 

If you pose a problem, we can help lead you to a solution.

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

Just came back to say iv finally gotten through it!

It was a great learning exercise and workout for the brain, every time i figured out a part it felt pretty good.

New stuff for me were the PROGMEM and pgm_read_byte functions for stored in flash, read out only as needed in ROM.

I also forgot about functions with parameters " void reset_task(char tsk) ", revisiting my learning materials caught me back up.

The binary equivalent values in the bit_mask array i thought were clever and practical.

In the end the last key to open the door was " if ((task_bits & pgm_read_byte(&bit_mask[task]))) ",

and how it would '& out a task bit set from a lower priority task to run the higher priority one if multiple tasks were active.

To figure that out I hypothetically had task_timers set to zero for task 0 and 2, so the task_bits = 5 or 0b00000101 until that '&' line of code. 

 

Thank you for posting this code. It allowed me to get smarter,  learn better programming and reinforce my belief that if i work long enough

at something i can accomplish and figure it out.   

~GuitarDude

Last Edited: Wed. Nov 12, 2014 - 06:18 PM

Pages