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

Go To Last Post
141 posts / 0 new

Pages

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

'This forum helps those who help themselves.'

 

pragmatic  adjective 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.

'This forum helps those who help themselves.'

 

pragmatic  adjective 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.

'This forum helps those who help themselves.'

 

pragmatic  adjective 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."

"When you hear hoofbeats, think horses, not unicorns."

"Fast.  Cheap.  Good.  Pick two."

"Read a lot.  Write a lot."

"We see a lot of arses on handlebars around here." - [J Ekdahl]

 

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

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

If I understand correctly, there is a heavy dependency on task_timers. In case few task_timers are of 10 msec and their priority is also high, other tasks may not get chance to run. So we have to be careful in selecting  task_timers right? 

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

There's a fixed priority with the tasks. Thus a task can 'steal' all the cycles. The general idea is that you don't allow that to happen. Each task does what it needs to do in its 10ms slot and must yield. You can normally do quite a bit of work in that time. Thus the term 'co -operative'.

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

I'm a bit late to the party but this page is still very interesting. I was curious if there were problems with changing the section:

void set_task(char tsk)
{
  task_bits |= pgm_read_byte(&bit_mask[tsk]);       /* sets a task bit */
}

to:

void set_task(char tsk)
{
  task_bits |= (1<<tsk);       /* sets a task bit */
}

Or am I missing some information regarding the use of pgm_read_byte(), or that bit shifting is not the best solution in this scenario.

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

The AVR can only do one shift per cycle (compare this with an ARM that can do n bits in one cycle). My example takes a constant amount of time vs the bit shift taking a variable amount.

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

Ah k I see. I was also wondering if using a Timer/counter could be an option to measure the cycles a function takes. This is more of an exercise for me to understand parts of my code better.

I was also curious how small the time slices could get. When transmitting/receiving data via UART, I was wondering if your system could handle a lot higher baud rates.

I tried to do some calculations and am wondering on peoples thoughts. These calculations were done with 11bits in mind (1 start, 8 data, 1 parity, 1 stop)

Time between transmissions/receptions in ms = 1/((baud/11)/1000)

  • At 9600 baud it would take 1.146 ms between tx/rx
  • At 250000 baud it would take 0.0440 ms between tx/rx

So the time slices at 9600 baud would be 100us and for the higher baud rate would be 1us.

Does this sound correct?

Cheers.

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

The UART RX has a two-word buffer, plus the incomming shift register, so it can buffer up to three characters.  At 250kbps 8N2, that's 132 us.  At 16 MHz, that's 2112 cycles.

 

Let me guess...  DMX/RDM?  I'd suggest you handle that in an ISR, and buffer an entire DMX frame (or whichever addresses in which you're interested) in SRAM.  Your tasks would then access that buffer.

 

Although you said 8P1, so maybe not DMX...?

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

"When you hear hoofbeats, think horses, not unicorns."

"Fast.  Cheap.  Good.  Pick two."

"Read a lot.  Write a lot."

"We see a lot of arses on handlebars around here." - [J Ekdahl]

 

Last Edited: Wed. Jul 12, 2017 - 12:21 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Ah forgot about the buffer. Would it be sketchy to wait and unload the received words once the buffer and shift register are full? On an interrupt i guess it would not be.

Cheers for making me aware of that. Just went through the datasheet and found what you mentioned, YAY learning!
 

So that is 2112 cycles between each unloading of the 3 bytes? I was trying to work this out to see when having such a high baud was problematic, however every design is situation dependent. And was asking to see if my math was right, which it seems to be.

 

I'm not doing anything fancy yet, as I did had to google DMX/RDM. It was more to learn about this multitasking idea and find the limitations that I need to be aware of.

 

And now I just read the flowchart pic at footer of your post. I feel it is fairly apt considering Im fairly new to MCU's and often dig myself a hole by trying to optimize and write the initial code at the same time.

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

3 bytes is the outside limit.  The moment the next start bit arrives, a data overrun condition is flagged with the DORn bit, and the latest incoming word is dropped.

 

There is no way to determine programmatically how many words are waiting in the RX buffer, or how many bits are in the shift register.  The only determination you can make is that the receive buffer contains either:

  • 0 words (UDRE = 1)
  • 1 or more words (UDRE = 0)

 

Some models of AVR have a start frame detection bit, so you could get a warning that there is an incoming word in progress, but that is typically used to wake the device from sleep mode.

 

In practice it is sufficient to service the receive interrupt with every word, so usually the two-deep-buffer-plus-shift-register only ever gets partly full.  The extra depth is useful when your app gets excessively busy on occasion, allowing it to catch up without losing any incoming data, but this behaviour should be profiled to ensure overrun isn't possible, or that it is properly handled if it does.

 

While you can design your app 'to the edge', this must be done with care.

 

Re: flow-chart... Randall Monroe is one of my favourite humans whom I've never met.

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

"When you hear hoofbeats, think horses, not unicorns."

"Fast.  Cheap.  Good.  Pick two."

"Read a lot.  Write a lot."

"We see a lot of arses on handlebars around here." - [J Ekdahl]

 

Pages