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

Last post
104 posts / 0 new

Pages

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.

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

  • 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 :)

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

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

 

Pages