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

Last post
133 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The subject of today's tutorial is: Writing multi-tasking applications.

What is a 'multi-tasking' app? We could answer that by saying 'an application that does many things at once'. This is a common requirement for embedded systems.For a small embedded application like we'd write for the AVR, we would probably like to do all or some of the following tasks:

1. display via leds or a lcd
2. get inputs from sensors
3. perform some logic or control
4. output to relays or some control device
5. Communicate with a PC, maybe via a multi-drop network.
there's a few more we could think of.

So how can we get the processor to do all these things at once? The answer is: we can't. The processor only executes one instruction at a time but the saving grace is that is does it quite quickly. So fast in fact that we can appear to be doing many things at once, but in reality we're executing a number of tasks, but splitting them up into little chunks and running them one at a time.Luckily for us, most things in the real world are quite slow compared to the speed of the processor. Some examples are:

1. Relays - being a mechanical device these can take many 10's of milliseconds to release or operate.
2. Character LCDs - depending on temperature these can take around 100milliseconds to change. Also the human watching it is none too fast either, so updating 10 times a second is usually fast for a human to actually read and follow.
3. Mechanical switches - like relays these can be slow and have a side effect called 'contact bounce' - this causes the switch to open/close rapidly over a number of milliseconds before settling to a known state. Also, if operated by a human, the speed is measured in 10's of milliseconds.
4. A machine operating at 12000rpm - each revolution takes 5 milliseconds.

For an AVR operating at a conservative speed of 8MHz, each instruction takes around 125ns which is about 8million per second. With the above examples where we measure the speed in milliseconds, you can see the AVR can execute around 8000 instructions every millisecond - this allows us to do a bit of work.

How do we go about creating a multitasking application?
First up we need to think about how we can split all the operation we want our application to do into 'tasks'. Most likely these tasks we need to talk to each other so we need to think about how we can keep the lines of communication between these tasks as simple as possible. This has the computer science term of 'coupling'. Tightly coupled means each task relies heavily on each other to operate. Conversely we have 'loosely coupled' which means each task has little reliance on other tasks. To make life easier for ourselves we want to lean towards the loose end of the scale. For an example, I'll detail an industrial controller - the sort of stuff I do everyday. The tasks might be something like this:

1. analog input task. reads the analog inputs and performs so math to scale the inputs to usuable units. Maybe a temperature sensor to read in Celcius.
2. logic task. Maybe we want some alarms if the temperature gets too high so we'll do some comparisons with the temperature value against some alarm settings and flash a light and honk a horn if the temperature gets too high.
3. bus communications. A PC might request information from us as well as set alarm levels etc. We might be one of a number of devices.
4. User interface. We might want to display the temperature and alarm status on a LCD. We might also have pushbuttons that allow us to interact with our device.
5. Setup menu. This is where the user can set various parameters for our application.

This gives us five tasks, not too many to deal with. Now we have to think about how we want these tasks to run and how often. We can also think about the priorities of these tasks - what task is more important or time critical? Some tasks might only run on demand - maybe in response an external event, some might run regularly - for instance the user interface and some tasks might run at the request of another task. Since I want to cover a straightforward system, I won't delve into tasks with variable priority etc and complicate this tutorial. I'll leave this for the reader to investigate further.

Lets assign some numbers to our tasks:

1.analog input task. Since we're measuring temperature which moves quite slowly, we'll sample this regularly at 10 times per second.
2. logic task. Since we'll be flashing lights and operating relays, 10 times a second would be a nice number.
3.bus communications. Since we'll be a slave device to a PC master, we're at the beck and call to him. We only need to speak when spoken to and to keep this fast, we want to respond quickly.
4. User interface. 10 times a second would be a good number here also. Because the human operator won't always be looking, we can have a fairly low priority since the user won't notive a slippage in the display update here or there.
5. Setup menu. Since we have only one display, either the user interface is running or the setup menu is running. So we activate on demand and run 10 times a second.

So we end up with three tasks that run regularly and one that runs on demand. If we were to choose a nice unit of time to dole out to the tasks that was neither too fast or too slow, we'd probably come up with a time chunk of 10ms - this gives us 100 time chunks per second, thus 100 tasks per second. To do a quick check of performance, we have four tasks to run every 100ms, thus we have 6 time chunks free every 100ms. Room to add more tasks or to cope with faster communications. The absolute worst case would be having communications every 10ms - there would be no time for the other tasks to run, so this is something we need to avoid. Next comes priority:

From highest to lowest:
1. bus communications
2.analog input task
3. logic task
4. user interface
5. setup menu

So far, we've defined our tasks, priorities, how often they execute and defined a nice time unit to base our system on. Now, how do we switch between tasks?

Task Switching:

There's two main techniques of task switching:
1. Preemptive
2. Co-operative.

Pre-emptive is exactly what is says: at any point in your task, you can be suspended and another task run. This involves saving and restoring the 'processor state' for each of the tasks as well as the task that manages this. The down side of this technique is that it consumes a lot of memory - each task has its own stack. For a memory constrained processor like the AVR (especially the smaller ones) you want to be as frugal as possible with memory. The other downside is that you need to provide mechanisms to share variables between tasks and that adds complexity and code size. However, this technique is very common in many operating systems like Windows and Linux.

Co-operative, again, the name says it all. Tasks must 'co-operate' with each other in order for the system to run. Each task is alloted an amount of time to do its work and return. Upside is this technique is great for small systems and avoids many potential issues with sharing of variables between tasks. Downside is that your code must be written in a way that you do the job and finish within a given time. Also, co-operative doesn't scale too well, but for our app of just five tasks, it fits well. I'll be discussing a framework for a simple co-operative task switcher that I've used on many projects and that can be used to solve a number of common embedded problems.

Co-operative Task Switcher.

First up we need to keep track of time - in our case 10ms chunks. To do this we will use the AVR timer in CTC mode to interrupt every 10ms and set a timer tick flag to say that 10ms has elapsed. Virtually all our timing will be based on this.

Next we need to figure out a way to dole this time out to the tasks and to decide what task to run next. For this we have a flag for each task, if this flag is a '1', the task wants to run. To sort out who should run first if there are more than one flag set, then we have priority. We'll set a fixed priority to makes things simple. Therefore we search the flags in order f highest priority to lowest to find the first flag set. If no flags are set, we just wait for another time tick. We'll also arrange the task flags in one byte - this gives us 8 tasks.

The pseudo code is like this:

1.while timer_tick_flag == 0 loop here
2. for task = 0 to 7 do
3. if task_flags & bit(task) goto 6 //exit if we've found an active task
4. next task
5 if no task found, goto 1
6. call the task function #task
7. goto 1

That's our basic framework. Simple eh? But it doesn't do too much for us. We need to add some timers so we can run tasks at regular intervals. If we give each task a timer that when it expires, it will set its task flag so that the task is flagged for execution. This gives us two features: to run tasks at regular intervals and to delay the running of a task. So task1 might want task2 to run in 1 seconds time. To do this we add some extra code to take care of the timers right between items 1 & 2 of the pseudo code above - just right after we've got a timer_tick.

1. for timers=0 to 7 do
2. if timer[timers] >0, decrement timer[timers]. if timer[timers] = 0 then set_task timers.
3. next timers

We implemented downcounters as its easier to test for zero or non zero vs testing for a given number. Thus if we want a task to run in 1 second, we load its timer with the value 100 (times 10ms = 1 second).
In summary, each task has a task bit and a timer associated with it. To set a task bit, we have a function called set_task(task), to clear a task bit clear_task(task) and to load a timer we access the timer array directly, task_timers[task] = time.

Here's how it plays out for a mega 168 using WinAVR (avr-gcc):

//
//	Co-operative multitasking framework tutorial code
//	(c)Russell Bull 2010. Free for any use.
//	Code built for a Mega168 @ 16MHz
//
//	flash two outputs (PORTB.4,PORTB.5) at different speeds - blinky#2.
//

#include  
#include  
#include 

void init_devices(void);
void timer0_init(void);
void reset_task(char tsk);
void set_task(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);

#define NUM_TASKS 8
char task_bits = 0;  /* lsb is hi priority task */
volatile char tick_flag = 0;    /* if non-zero, a tick has elapsed */
unsigned int task_timers[NUM_TASKS]={0,0,0,0,0,0,0,0};                  /* init the timers to 0 on startup */
static const PROGMEM char bit_mask[]={1,2,4,8,16,32,64,128};            /* value -> bit mask xlate table */

int main(void) 
{ 

  init_devices();
//
//	start at least one task here
//
set_task(7);	//task7 runs
set_task(6);	//task6 runs

//      main loop 

  while(1)
    {
    if (tick_flag)
      {
      tick_flag = 0;
	  task_dispatch();              // well.... 
	  }
	}
  return 0;
} 
//
//	a task gets dispatched on every tick_flag tick (10ms)
//
void task_dispatch(void)
{
  /* scan the task bits for an active task and execute it */

  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 )
    {
    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;
    default:
      break;                  /* no task was active!! */
    }                       
}

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

void task0(void)
{
  reset_task(0);
}
void task1(void)
{
  reset_task(1);
}
void task2(void)
{
  reset_task(2);
}
void task3(void)
{
  reset_task(3);
}
void task4(void)
{
  reset_task(4);
}
void task5(void)
{
  reset_task(5);
}
//
//	flash PORTB.4 at 2hz
//
void task6(void)
{
PORTB ^= (1<<4);
task_timers[6] = 25;		//every 250ms
reset_task(6);
}
//
//	flash PORTB.5 at 1hz
//
void task7(void)
{
PORTB ^= (1<<5);
task_timers[7] = 50;		//every 500ms  
reset_task(7);
}
//call this routine to initialize all peripherals
void init_devices(void)
{
 //stop errant interrupts until set up
 cli(); //disable all interrupts


 DDRB = 0x30;	//port 4 & 5 as outputs

 timer0_init();
 
 MCUCR = 0x00;
 EICRA = 0x00; //extended ext ints
 EIMSK = 0x00;
 
 TIMSK0 = 0x02; //timer 0 interrupt sources
 
 PRR = 0x00; //power controller
 sei(); //re-enable interrupts
 //all peripherals are now initialized
}
//TIMER0 initialize - prescale:1024
// WGM: CTC
// desired value: 10mSec
// actual value: 10.048mSec (-0.5%)
void timer0_init(void)
{
 TCCR0B = 0x00; //stop
 TCNT0 = 0x00; //set count
 TCCR0A = 0x02;	//CTC mode
 OCR0A = 0x9C; 
 TCCR0B = 0x05; //start timer
}

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

This code be easily adapted for other AVRs and other processors quite easily. The hardware initialisation, ports and timers will need attention.

This concludes part 1 of my introduction to multitasking!
Subsequent parts will expand upon this framework and will add modbus slave comms and a few other treats.

Last Edited: Fri. Jul 16, 2010 - 02:37 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Just to note that the switch() for the dispatch could be done with f_ptr's:

typedef void(*f_ptr_t)(void);
f_ptr_t tasks[] PROGMEM = {
 (f_ptr_t) task0,
 (f_ptr_t) task1,
 (f_ptr_t) task2,
 (f_ptr_t) task3,
 (f_ptr_t) task4,
 (f_ptr_t) task5,
 (f_ptr_t) task6,
 (f_ptr_t) task7
};

then

f_ptr_t fptr;
fptr = pgm_read_word(&tasks[task]);
// dispatch..
fptr();

The advantage being that it'd be much easier to add a few entries to the array than to extend the switch().

You could also make NUM_TASKS something like:

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

so it varies as array elements are added/removed.

 

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

Cliff - fully aware of this, MISRA tells me I shouldn't use function pointers! In this instance it would be in flash so the potential danger wouldn't be there. The code started out in assembler, so the C is a fairly literal adaption of it.

Nevertheless your comments gives the readers something extra to ponder on their embedded journey.

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

over 256 hits and no comments (apart from Cliff's). How 'bout some feedback?

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

Quote:

How 'bout some feedback?

OK.

Indentation inconsistent, and

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

"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]

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

Thanks Johan,

I fixed up the comment - the old 'cut and paste' problem!

The indentation I'll have to attend to at some stage.

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

Hi,
i'm new to programming embedded systems, so the tutorial was very useful. Nice to read and easy and gave me an idea about the topic.

Thanks Kartman

This is MADNESS!

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

Hi Kartman,
i'm currently building a simple robot. This is what i've got so far:

http://www.youtube.com/watch?v=VGDQnQYLEKg

Your simple system of multitasking would be ideal for controlling such a robot, to read information form IR distance sensors, to execute controlling commands, etc..
So far there is a Attiny2313 microcontroller ( control through radio module over usart ), but i'm going to use atmega128, because i need two usart's ( one for controlling the robot and the other to receive data from a special sensor and send this data through the other usart module to the PC ). It doesn't matter what the sensor is, but it gives 15 bytes with frequency of 20Hz.

So the idea is to receive this 15 bytes every 50ms (1/20Hz) with one usart and then send them through the other to the PC (this other usart also is used to control the robot ).

And here i have got couple of questions. Both usart's are 9600 baud/s, 8 data bits, no parity and one stop bit.

1) If there is 15 bytes, i have got 120 bits of data. Adding all start and stop bits it gives me 120 + 15*2 = 150 bits. So the time to send this over usart would be: 150/9600=0.015625s=16ms. So far everything is correct ?

2)When i start receiving stuff with interrupts and sending i can't do anything else. Would it be a good idea to use interrupts ? In one usart RX interrupt send the one byte immediately to the other usart ? Or to put it in some kind of a buffer and send the data through the other usart in some other part of embedded system ?

3) Is it possible to do that without interrupts ? When i receive data in usart i have to check the exact time the UDR, what if i don't do that. The data in UDR is lost when usart receives next byte ?

4) your proposition of doing that :P
Besides transfering this data robot would have to do:
-read adc signal from sensor (still don't know how often)
-read adc signal from powering system ( every couple of seconds - not time critical )
-read controlling commands and execute specifed action

thanks for any suggestions
pauldab

This is MADNESS!

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

Some of the pitfalls to keeping it real time is to avoid things that halt execution until some event occurs.

For example:

 delay_ms(10); // wait 10 mS
and
while(PINB & 1); // wait for PINB.0 to go low.

Where this might be simple ways of accomplishing one thing like the embedded version of "Hello World" it is not very co-operative.

Great Tutorial. You don't need an RTOS to keep it real time. Especially if you only have 2K to work with.

official AVR Consultant
www.veruslogic.com

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

Paul, I would probably buffer the data from both the usarts in send and receive. This is a more general approach. Sometimes, if you're pushed for ram, you need to do things like sending data out one usart when receiving from another as you suggest. As for not being able to do other things whilst receiving data, this should not be the case. For the receive interrupt, when fired it gets the data from the UDR and pops it into a circular buffer and exits or if you have a particular protocol, you might have a little state machine that checks for the start token, end token etc. All this takes very little time. So the impact on your other processing is probably only 1-5% at most. If using a circular buffer, you can have a task that regularly checks for incoming data and process it or if you decode the protocol, the isr sets a flag when it has a receive packet and a task can check for this.

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

Very nicely worded tutorial Kartman.

I'm now going to search for Part 2.

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

Havent got to part two yet!

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

Thanks for this tutorial. I've been looking for something like it for a while. Thanks. :)

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

My pleasure!

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

Thank you! I loved this! Works fine in AVR Studio 5 beta and runs in a Proteus simulation of a ATmega168!

I am getting an Arduino Compatible POP-BOT kit and will be using ISP to program it in C (I like AVR Studio 5 beta since I am used to MS Studio Express.)

Please put up part 2, I can't wait! :-)

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

Kartman, this is a fantastical tutorial! Than you for your great example of how to multi-task on the AVR. I have got the code to compile and it's running on a Baby Orangutan from Pololu.

I have one question. How do I modify the code to run on the Baby O that is operating at 20Mhz? I'm quite a newbie when it comes to this stuff, but I'm having lots of fun so far!

Thanks again mate.

Jay

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

Gents, thanks for the nice words. I've started part 2, but finding the time to check the code in a real micro to ensure what I post actually works is the hurdle I have at the moment.

Jay,

The difference with operating at 20MHz will be the timer compare value. The posted code assumes a 16MHz crystal, but for 20MHz, change this line:

in timer0_init()

OCR0A = 0xC3;

That's all!

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

A true champion! Thanks Kartman. Looking forward to part 2.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0
 while (task <= NUM_TASKS )
    { 
     if ((task_bits & pgm_read_byte(&bit_mask[task]))) 
           { 
           break; /* if activate task found..*/ 
         } 
      task++;         /* else try the next one */ 
    } 

Should use "<" instead of "<=" in the while (task <= NUM_TASKS )

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

Why? What benefit would the change make?

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

Hi Kartman, fantastic tutorial;
few questions: I am doing some RX related stuff (on ATmega128, XTAL freq: 14.7456 MHz) which receives frames (either only commands or data along with commands) at 115200 bps. For some reason I don't have my hardware ready yet. The UDR is read in RX ISR, the received data is then parsed in a function which runs from a non-preemptive multitasking scheduler which is running at every 1ms tick with 8 tasks.
In receive ISR I have used a ring buffer into which the data is pushed at every interrupt, this data is then popped into another byte array where it looks for start/stop, cmd, message size bytes etc which is part of scheduled function. on getting particular cmd some functionality needs to be executed. however from intuition it seems that all this activity may not be completed before timer tick (1ms).
Can you suggest me optimized method of doing this?
Shall I use state machine for frame parsing directly in ISR, OR its not recommended.
How to efficiently use ring buffers along with RX interrupts when message parsing needs to be carried out?

Please share your expert advice. Thanks

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

also waiting for Part 2... do explain some loop timeout mechanisms in blocking functions (in part 2)

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

Kulu,

If you are receiving packetised data, then it makes sense to decode the packet in the isr (assuming there is not a time consuming function such as computing a CRC). For example: NMEA data as output from GPS receivers. The isr can have a state machine to search for the start token, accumulate a packet and calc the checksum then when it gets the end token, check the checksum. You would then copy the packet to another buffer and set a flag that would be tested and a task activated if it was set. Depending on the priority of that task it may or may not get executed next.

If using a circular buffer and NMEA packets, again have a state machine to decode the packets but put a limit on the amount of chars you extract in one go and if the buffer is empty. You would then defer the task for a time period and attempt to get the rest of the packet.

In part two, I use the example of Modbus and the packet is assembled by the receive isr and sets a flag like described above. Modbus RTU uses a CRC for packet checking, so this is done by the processing function, not in the rx isr where it might consume too much time. These decisions need to be made in view of the whole system - interrupt latency being a factor.

Quote:

do explain some loop timeout mechanisms in blocking functions (in part 2)

You don't have blocking functions! If a function blocks, then the co-operative scheme fails. The whole basis is that you design your system not to block - finite state machines is one technique to achieve this.

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

Thanks Kartman for great explanation! I'll try to implement w/o ring buffer as explained in 1st part.

However blocking function I mean to say, e.g consider a scheduled task such as scanning an ADC input after every 10 ms. there we have to wait for conversion to get complete

while(ADCSRA &(1<<ADSC));

how to tackle such conditions in scheduler as these statements might hang the system for indefinate time and other task may not get execute.
In such cases can we give fixed delay for conversion to happen e.g say 2 us delay before reading ADCR OR some kind of timer timeout ANDed with the condition?

This is because inspite of having interrupts for every activity all of them cannot be used as unnecessary nesting of interrupt is not recommended (read somewhere)

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

If for some reason the ADC failed and caused an infinite loop then that is a failure. In the dispatch loop we would kick the watchdog so normal operation would keep the 'dog happy. In the instance you describe, the task would stall and the watchdog would kick in and reset the microcontroller.

In normal operation, the ADC has a known conversion time and therefore if this is less than the alotted task time then you are in the clear. If you need better performance where you might need to sample the ADC faster, then you would need to use interrupts. It is not necessary to nest interrupts unless you have tight real time constraints. Having multiple interrupt sources it not necessarily bad, but you need to ensure that the worst case latency doesn't exceed your requirements and that you don't have too much of the processor time tied up in servicing interrupts.

In my next installment, I will show an example where the ADC is run in a task. This is a low performance application where the ADC inputs are read, made available via Modbus and can be displayed on a LCD.

Every application is different, so you need to evaluate requirements on a case by case basis. One size does not fit all!

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

I've taken the liberty of copying your tutorial into PDF format - I hope the formatting is readable, I had trouble converting the tabs. :oops:

Attachment(s): 

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

Lenny, the doc could do with a spell check and the tabs on the code definitely need fixing!

Hopefully this tut has been useful to you.

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

I didn't do any spell-checking on it... I just used copy-and-paste to put your text into Open Office Writer. :)

I'm more of a hardware guy than a software/firmware type, so I have a LOT of figuring-out to do. I doubt that I'll ever want to write an RTOS on my own, but it's a good introduction to how such a system works.

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

@Kartman ... looking forward to seeing your follow ups to this tutorial.

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

I echo the comments of the others, a very usefuly tutorial.

I ran this on my STK500 with ATMega 8515. to set CTC mode and acheive the 10mS tick i used the following values for the respective registers

							//FOC 0, WGM00 0, COM01 0, COM00 1, WGM01 1, CS02 1, CS01 0, CS00 1 
	TCCR0 = 0x1D;					//CTC Mode, 1024 prescaler
	TCNT0 = 0x00;
	OCR0 = 0x27;					//0x9C from example

Regards

Ade

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

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

Array index in C starts from 0.

Therefore for an array of 8 bytes:

#define SIZE 8

unsigned char a[SIZE];

You can access each individual byte
with:
a[0].....a[7].

or in a loop using <.

When using <= it will do one final go on the loop when your index will become 8 bcz less than or equal too. And will result in index being out of bounds. So you will corrupt the memory outside the array. If nobody is using it, no problems, but...it should be avoided because it can cause bugs.

Since I am a PC programmer mainly, this would lead to application crash at that instant or at some time which would be hard to track when & reason why.

Now....

I woke up this morning to search this forum on how to do similar things and I am so glad to have found your tutorial!

I need to control 16 DI, 16 DO lines, CAN send/receive and Ethernet send/receive on one AVR device (thinking either mega128 or go with xmega). Something like an controller, CAN for motor drives and Ethernet/TCP/IP for communicating with a PC...where PC side will send status update request every 300ms and/or some command to do something which would cause change in DIO or CAN message being sent out....also for status update, I would regularly have to get data from CAN and put it in some kind of array for sending back to PC (health of CAN devices, data from them, etc).

I was thinking of using A90CAN128 device with Wiznet 5100 chip which implements TCP/IP stack and all other goodies or go with mega128 / xmega128 with MCP2515 CAN controller + same which for Ethernet. In all cases, my memory reqs are not that high, and mega can do it.

I didn't want to go for RTOS for just those 4 simple tasks.

Only thing I have to think more is to have variable DIO task sleep time, which I can implement as a "state" machine so to set a bit at time N depending on state I am in; Bcz to do one fully with DIO I would need DO 1 and DO high until DI 2 is high or 2000msec expires in which case flag an error.

Thanks for your post!

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

Nice! Helped me understand using timer ticks better. Perfect timing...(pun intended!)

-fab

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

Fjarnjak, you are indeed correct. I do execute the loop one too many times. I should've learnt to count by now!

If you want to do ethernet, can and usb just go for a lpcxpresso lpc1768 board. Sure the AVR can do it but at $29 usd it is hard to beat.

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

Kartman wrote:
If you want to do ethernet, can and usb just go for a lpcxpresso lpc1768 board. Sure the AVR can do it but at $29 usd it is hard to beat.
They are pretty hard to beat, I have three. The LPCXpresso1768 (100MHZ) was superceeded by the LPCXpresso1769 (120MHZ) late last year.

"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

Very nice tutorial, i was searching for this type of nice explanation. Waiting for part2.

Thanks
Naveen

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

Kartman,

I cannot begin to tell you how good I thought this article was. It's clear and incisive and explains this subject far better than anything I've ever read before. Just cannot wait for the next articles you write here.

In total awe.

SC

EDIT : Its great to see how people with rich experience like you share it, so that the beginners do not have to reinvent the wheel.

Why memorize anything which you can easily pen down on a piece of paper or get from a book in less than two minutes? - Albert Einstein on being asked why cant he remember his phone number!!!

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

Take a look at this if you want to examine interesting pseudo multitasking variations:
http://www.romanblack.com/PICthread.htm

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

I'm a seat of the pants hack, but I did know a bit about Windows and it's non-multitasking environment back 20 years ago.

I just wrote a simple program to control a machine, but I need to dump some of the while and delay stuff. Should I be looking at your code, or just use timers to turn 1 things off, while another is in a while loop?

thanks

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

Quote:

Should I be looking at your code, or just use timers to turn 1 things off, while another is in a while loop?

Often an MCU app needs nothing more than a central non-interrupt loop doing the "slow" stuff and a handful of ISRs (possibly just timer ISRs) doing the small amount of things that need to happen more regularly/deterministically. It kind of depends how many "concurrent" tasks you have going on as to whether you finally reach a point where more formal "multi-tasking" becomes warranted.

 

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

To get rid of the while and delay stuff, it is common to build sequential systems with a 'finite state machine'. This has been discussed in some posts recently.
http://www.avrfreaks.net/index.php?name=PNphpBB2&file=viewtopic&t=106943&postdays=0&postorder=asc&highlight=state+machine&start=40

If you're controlling things like relays, a time tick of 100ms is reasonable. In my framework, you would have a task executing every 100ms and running the state machine. You could have multiple tasks and multiple state machines running concurrently seemingly all running at the same time, but being executed in sequence.

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

Hi again Kartman,

Thank you for your contribution sharing this very
useful post here

So Does it mean we can't get interrupts in
a Co-operative Task Switcher? since you've mention that each task must run and finish at a definite amount of time?

Thank you.

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

Quote:

So Does it mean we can't get interrupts in
a Co-operative Task Switcher? since you've mention that each task must run and finish at a definite amount of time?


Just think of a co-op tasker as one big while(1) main() loop and in that sense it's no different to any other kind of program and there are no limits about also running ISRs alongside.

The usual rules about trying to keep ISRs as short as possible apply of course. If an ISR were to take 100ms for example then that would stop all the co-operating foreground tasks for all that time. So, as always, make it just a few time sensitive instructions and put any "long work" in another foreground task that usually does nothing but is triggered into action by some flag set in the ISR. The usual rules for foreground task timing apply to it too - so if it will take more than the usual task time limit then break its work up into a multi-stage state machine too.

 

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

Thank you again Cliff. it makes sense.
I am getting to know how things should really be done

If we can in some of the pseudo code that Kartman gave for ex.

1. for timers=0 to 7
we want to do
2. if timer[timers] >0, decrement timer[timers].
if timer[timers] = 0 then set_task timers.
3. next timers

i was concerned on this piece of code.


while (task < NUM_TASKS ) 
    { 
    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++; 
    } 

shouldn't on the 1st if statement have been

   if (task_timers[task]>0)   
     {  
        task_timers[task]--; 
      }  // etc..

rather than if (task_timers[task])
Since Kartman said earlier that we want to decrement
the associated timer if it's found to be greater than 0?

Cheers.

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

Since task_timers is unsigned it can never be < 0, so the test is good as Kartman gave it.

"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]

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

Thank you answer Johan

You are right unsigned int can never be < 0,
but it can be greater than zero, which is the case
i was talking about that if is ever found to be > 0.

  if (task_timers[task]>0)
     { 
       task_timers[task]--; 
      } 

then we would decrement it?

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

But testing with just

    if (task_timers[task])

for unsigned does just the same! Remember that as long as something is != 0 it is true.

So, technically it will do just as fine as testing for > 0.

Whether it would be clearer for the reader of the code to use > 0 is probably a matter of opinion.

"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]

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

Thank you Johan,

very well explained :)

Last Edited: Thu. Jun 28, 2012 - 03:25 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Shouldn't also 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 */ 
    } 

really be

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

since

unsigned int task_timers[NUM_TASKS]={0,0,0,0,0,0,0,0}; 

has 8 elements starting from 0 which makes the last element be the 7th?

thus from while (task <= NUM_TASKS ) this allows the 8th element aswell no? which might probably lead to access of the memory outside the array boundary when compiler gets to this if statement, as the last element is the 7th

if ((task_bits & pgm_read_byte(&bit_mask[task]))) 

Thanks all.

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

Hi again all,

Kartman said that for example task 1 might need task 2 to take (run) 1sec!
So i understood that creating task_timers[NUM_TASKS]={0,0,0,0,0,0,0,0}; gives us extra opportunities to specify the time we want that task to run plus to keep in track if its flag is ON.
I was concerned about initializing the task to a certain amount of time! like some task run in 250 ms? won't this affect/delay other tasks as we had 10 ms for each task to run?
If i understood right we might want some tasks to take longer than others as long as the finish and other can also follow?
(which is the matter of the facts Kartman said that the worst case scenario would be having the task which will need to be done on every single 10 ms basis)
But then if the task could take longer than others as long as it finishes and allow other also to run would be fine?

(just like Cliff suggested like in the while (1) loop, the most important thing is that every single task will be done although some may take longer than others) right?

Thank all.

Last Edited: Thu. Jun 28, 2012 - 02:24 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Just out of curiosity question, Does anyone knows What adding a RTOS in this Cooperative Multitasking would benefit?

I am just learning on Multitasking RTOS.

Cheers.

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

Quote:

Does anyone knows What adding a RTOS in this Cooperative Multitasking would benefit?

Your question makes little sense - are you saying you would run a secondary co-operative tasking system within a single thread of a pre-emptive, time-slicing tasking system? To what gain? If you have a pre-emptive scheduler then why not make all tasks separate - that's kind of the point of pre-emption isn't it?

 

  • 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.

"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]

  • 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.php?name=PNphpBB2&file=viewtopic&t=129157 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/JasonthePCMD

  • 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
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I'm happy that you've gained something from it. Using the bit_mask table is a bit of a throwback to when i first wrote the code on a 8051 processor. With the AVR you could flip a coin vs using shift operators. With an ARM processor, using a shift operator would be more efficient.

I've used the framework on a number of commercial projects spanning nearly 20 years. There's many little boxes ticking away happily around the world.

It's good to get feedback from readers, thanks.

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

Time to update to __flash and drop the pgm_read*() stuff perhaps? ;-)

 

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

Agreed. 

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

I have some useful information on realtime systems from my university;

 

Some appropriate lecture slides (this presents one way of "simultaneous" execution on a single core processor, we used AVRs in the labs).

 

http://www.sm.luth.se/csee/courses/d0003e/lectures/lecture4.pdf

 

And a light weight multi-threading kernel, tinythreads. However, some of the methods are unimplemented - that's for the students to do.

 

http://www.sm.luth.se/csee/courses/d0003e/labs/tinythreads.h

http://www.sm.luth.se/csee/courses/d0003e/labs/tinythreads.c

 

And to actually use the kernel, use "spawn" from main on methods that do not terminate. As shown here, example:

 

http://www.sm.luth.se/csee/courses/d0003e/labs/mytest.c

 

What makes the threads context switch is inserting a call to "yield", somewhere in the code of the spawned methods, or why not from an interrupt handler (ISR) written in tinythreads.

If you want to know the implementation of the yield method - which triggers the context switching - then you'll have to do some coding yourself (HINT: It's four lines of code, including turning on and off interrupts).

Yield shall enqueue the current thread and dispatch the one in the waiting queue. (PM me if you're not up for my bullshit, I'll show you)

sol i sinne - brun inne

Last Edited: Fri. Jan 2, 2015 - 05:16 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

If you yield within an isr, is that not preemption?

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

Kartman wrote:

If you yield within an isr, is that not preemption?

 

Oh, yeah sure. Right now I've hooked my Arduino up to an LED (I'm strapped for parts, can't make anything more advanced at the moment), that one blinks on/off every 300ms, and the on-board LED blinks on/off every 200ms, both using very simple endless loops with _delay(). The interrupt comes from the WDT set to timeout every 32ms, works flawlessly to the naked eye.

sol i sinne - brun inne

Last Edited: Wed. Jan 7, 2015 - 10:54 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

As a side note (nothing to do with multitasking, but interrupt handlers), depending on your system and what the requirements are, you can get away with just runnig your code in the interrupt handlers (: That way you can sleep in the main-loop, saving energy!

sol i sinne - brun inne

Last Edited: Sat. Jan 3, 2015 - 02:50 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

This tutorial was not meant to be the last word in multitasking - as i said up front, it is an example of a simple, cooperative tasker. There are a number of ways to achieve the same or similar results.

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

Of course! (:

sol i sinne - brun inne

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

@Tickstart

 

Why don't you write up your approach as a tutorial?

pragmatic - dealing with things sensibly and realistically in a way that is based on practical rather than theoretical consideration.

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

Brian Fairchild wrote:

@Tickstart

 

Why don't you write up your approach as a tutorial?

It's not an impossibility, but I'm not the original author of this program. Not that it's copyrighted or anything but since I didn't write it myself (and I'm really no C programmer) I might not be able to answer those really tricky questions. We'll see, until then all information is in those slides and in the submitted code, only thing missing is the implementation of yield which I can help you with. The mutexes I haven't looked into since the threads I've run haven't shared common resources, but I have the code for those aswell.

sol i sinne - brun inne

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

Thanks for such a wonderful tutorial....waiting eagerly for the 2nd part...

 

Saikat

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

Can I call another task from one task? Will it make create any problem?

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

sktnandy wrote:

Can I call another task from one task?

 

Why would you want to do that?

 

I'd suggest that if you find yourself needing to do it then you have partitioned your tasks wrongly.

pragmatic - dealing with things sensibly and realistically in a way that is based on practical rather than theoretical consideration.

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

I'd suggest that if you find yourself needing to do it then you have partitioned your tasks wrongly.

My thoughts to. In multi-tasking it is often the case that you have one task that "does a job for" another task but rather than CALL it you might typically have the "worker" task block on a mutex or semaphore then, when the parent task wants the job done it release the shared object that triggers the other into life. Anotther way is for the parent to post a request into a message queue of the child task and when it receives and "OK, go for it" message it springs into life. This is quite different to synchronously calling functions in one task from another. You have to start thinking about the problem in a "different way" when you try to share work across threads and tasks in a multi-tasking system.

 

At the end of the day think about it just like you get to write three or five (or however many) different main()s in a single program. Each "does it's own thing". But a bit like when in Windows your word processor sends a document to the print queue along with another document already waiting to be printed from a spredasheet, sometimes the main()'s "talk" to each other. That's at the highest level of course but really the issue is pretty similar.

 

So in what context do you see the need for one task to "call" another?

 

Of course there are things that both tasks might call - I bet they might both be tempted to call printf() but that does not "belong" to either task really - it's a shared resource hey can both use. Of course that raises another issue. If one does printf("hello") and one does printf("goodbye") at the same time how do you prevent the user seeing "hegloodlyeo" ? (or maybe that's what you want?). This is really where a mutex (or semaphore if more than 2 tasks) might come into use. A mutex (mutual exclusion) ensures that only one task can "own" a resource at a time. So both programs call get_mutex(printf); printf("hello"/"goodbye"); release_mutex(printf). The one that "gets there first" gets control and can complete his printf() then he release the lock. Meanwhile the other task is "stuck" inside get_mutex() waiting for it to become available. When the first task calls release_mutex() that allows the get_mutex() to then complete and he can then do his printing. So the user sees "hellogoodbye" or "goodbyehello" depending on which got there first.

 

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

thanks brian and clawson.....I am not willing to do semaphore or something like that because it will get more complicated. I will re-code the tasks so that there is no need to call one from another. 

And thank you clawson for such a wonderful explanation.

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

I re-coded it so that there is no need to call one task from other. But I am getting a strange problem after running it a while. It is getting stuck somewhere and not coming out. How to figure it out where it is getting stuck? 

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

How to figure it out where it is getting stuck? 

It kind of depends what "stuck" really means - you meant the task switching preemption actually stops or simply that one of the tasks goes into an infinite loop?

 

Anyway an OCD debugger (JTAG/debugWire/PDI/whatever) is usually the best for this kind of thing or failing that (if not too much external stimulation is involved) the simulator in Studio. Run the code until it is "stuck" then simply break execution and find out where it is. Maybe follow it into the task preemption (assuming that's till going) and watch it as it returns into each task. Is one (or all even?) "stuck" in a loop waiting for something to happen that never will?

 

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

It seems one of the task is going into an infinte loop. This problem is happening randomly like sometimes after 10min, sometimes after 15 min and like that. What do you mean by "Maybe follow it into the task preemption (assuming that's till going) and watch it as it returns into each task." How to do that?

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

As I say just run the code in a debugger or simulator to the point where it "locks" then break execution to find out where it is stuck (which should then tell you why). If some tasks are still going OK then just keep stepping execution until it gets into the task that is not running.

 

(BTW this is one of the downsides of multi-tasking - it can often be trickier to find out what's wrong when some part of the code stops working).

 

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

How many tasks do you have? Could you get each task to flash an LED? If each task sets an I/O bit on entry and clears it on exit you could probe with a scope to see what's toggling and what isn't.

 

Or, get each task to write its task number to multiple LEDs. When it freezes the display will indicate the last task to run.

pragmatic - dealing with things sensibly and realistically in a way that is based on practical rather than theoretical consideration.

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

Cliff, there is no preemption happening in the bit of code i wrote. Everything runs to completion.
Sktnandy - see above. Your task can't sit in a loop - when it is called it must do what is necessary and exit within its time slot. If it has to wait for something, you use a state machine, set the state and come back later.

Further to what Brian has suggested, output the task number via the uart. For many years i didn't use (or have) in circuit debug so techniques like flashing leds and uart output are powerful means of identifying what is happening. Even recently i output recovered clock and data signals on i/o bits then used a salae logic analyser to see what was happening.

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

I found the problem using LED while running it in the simulator. I saw the code is stuck at this I2C routine

  while (!(TWCR & (1 << TWINT)));  // Wait for TWINT flag set in TWCR Register

 

Below is the complete code where it is getting stuck.

 

unsigned char i2c_transmit(unsigned char type) 
{
        switch(type) 
        {
                case I2C_START:    // Send Start Condition
                                                TWCR = (1 << TWINT) | (1 << TWSTA) | (1 << TWEN);
                                                break;
                case I2C_DATA:     // Send Data
                                                TWCR = (1 << TWINT) | (1 << TWEN);
                                                break;
                case I2C_STOP:     // Send Stop Condition
                                                TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWSTO);
                                                return 0;
        }
  // Wait for TWINT flag set in TWCR Register
  while (!(TWCR & (1 << TWINT)));
  // Return TWI Status Register, mask the prescaler bits (TWPS1,TWPS0)
  return (TWSR & 0xF8);

 

The task which is doing this I2C communication is taking toatl 1ms when it is running fine.I checked it by toggling a bit. 

I dont know why it is stuck at that while() function. Also I am calling the task after every 250ms

Last Edited: Sat. Jan 17, 2015 - 07:13 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

You should implement a timeout while waiting for TWINT.

 

Also, examine TWEN when your task hangs.  Is it set?

 

"Experience is what enables you to recognise a mistake the second time you make it."

"Good judgement comes from experience. Experience comes from bad judgement."

"Fast. Cheap.  Good.  Pick any two."

Last Edited: Sat. Jan 17, 2015 - 07:26 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

how much time should I set for TWINT? Have not checked the TWEN flag. what will be the value of TWEN in this case? controller is in master transmitter mode

Last Edited: Sat. Jan 17, 2015 - 08:20 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

checked the TWEN bit. It is set. What could be the reason TWINT bit is not getting set?

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

plz help me

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

It could be a number of problems. Do you have the correct pullup resistors? What have you done to investigate the problem? What circumstances lead up to the problem? Use the sound card as an oscilloscope and capture the i2c bus. You'll be able to see where it hangs and hopefully solve the problem yourself.

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

thnks for the reply. I searched net but it could not find anything to resolve the problem. Pull up resistors are 2k2. As per the code, i2c routine get called after every 250ms. And after a while it gets stop at that line, though the code is going to interrupt after every 10ms. I checked this with a led.

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

As i said, it could be a number of things causing the problem. You need to dig for more evidence.

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

You have no default case in the below so if i2c_transmit(type) is called with type != I2C_START | I2C_DATA | I2C_STOP you won't make any change to TWCR but you will enter the while loop.  Is that what you intended?

 

If not then set a flag (LoopFlag) in cases below, clear LoopFlag in a new default case and add LoopFlag test as condition for While. 

 

David

 

unsigned char i2c_transmit(unsigned char type) 
{
        switch(type) 
        {
                case I2C_START:    // Send Start Condition
                                                TWCR = (1 << TWINT) | (1 << TWSTA) | (1 << TWEN);
                                                break;
                case I2C_DATA:     // Send Data
                                                TWCR = (1 << TWINT) | (1 << TWEN);
                                                break;
                case I2C_STOP:     // Send Stop Condition
                                                TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWSTO);
                                                return 0;
        }
  // Wait for TWINT flag set in TWCR Register
  while (!(TWCR & (1 << TWINT)));
  // Return TWI Status Register, mask the prescaler bits (TWPS1,TWPS0)
  return (TWSR & 0xF8);

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

*Tickstart's derailment of this thread has been moved to the off-topic forum. Moderator*

Ross McKenzie ValuSoft Melbourne Australia