THREAD_END() = 6 PIC instructions
THREAD_START(0)
read_adc(0);
THREAD_BREAK
read_adc(1);
THREAD_BREAK
read_adc(2);
THREAD_BREAK
read_adc(3);
THREAD_END(0)
So in the above thread the overhead is 8+6+5+6+6 or a total of 31 PIC
instructions for one pass through the thread to run the read_adc(2)
function. This is not an unreasonable amount of overhead for a
multi-tasking system, especially if the functions like read_adc()
contain any sampling delays or 16bit math that might easily be 100 PIC
instructions or more.
This overhead is less on the PIC 18F series that have an improved instruction
set and of course with much faster PICs like 18F, dsp24 and PIC32 etc these PIC-thread
automatic macros become more and more attractive and viable.
There may be some way of reducing the overhead and making the macros jump
directly to the correct place in the code, but for the moment these macros
are working and form a very elegant and easy to use system that forms a
powerful multi-tasking engine using just 3 simple macros that can be added
to or removed from code at will!
NOTE! Section below is my original PIC-thread work from 2nd Jan 2010
(The first examples are very simple, the more usable code is near the page bottom.)
The simplest example? - 2nd Jan 2010
In this example an automatic sequencing engine is used to select a new thread
number after a predetermined period. Each thread is responsible to end
its task when it's number is no longer selected. Then the next thread
gets a time period to do it's task.
This way the available time can be divided up between threads, either
equally or unequally (with some type of prioritising).
Here is the most simple "PIC-thread" multitasking system I could come up with;
void main()
{
// setup PIC 12F675, 4MHz xtal
CMCON = 0x07; // comparators OFF
T1CON = 0b00100001; // TMR1 on, 1:4 prescale
TRISIO = 0b00000010; //
// loop here and do each thread for 1024 instructions (1mS)
while(1)
{
// THREAD 0 ===========================================
while(TMR1H == 0)
{
if(!GPIO.F1) GPIO.F0 = 1; // LED on if button pressed
}
// THREAD 1 ===========================================
while(TMR1H == 1)
{
if(GPIO.F1) GPIO.F0 = 0; // LED off if button not pressed
}
// THREAD 2 ===========================================
while(TMR1H == 2)
{
}
// THREAD END =========================================
TMR1L = 0;
TMR1H = 0;
}
}
The automatic sequencing engine is done by the PIC timer; TMR1H, which allocates
a timeslice of 1024 instructions (1mS) to each of the 3 threads. There could
be as many as 255 threads.
Each of the 3 threads operates in turn, and takes 1/3 of the total time.
Thread 0 checks a button, and lights a LED. Thread 1 is responsible to
turn the LED off again. And thread 2 does nothing but use up 1/3 of the
timeslice, but of course it could do another simultaneous task.
This system is very crude but does satisfy the requirement of sharing
timeslice between tasks, and all tasks run "at the same time" provided
that a few mS delay between tasks will not be noticed by the application.
It is also capable of allocating uneven timeslice;
// THREAD 0 ===========================================
while(TMR1H == 0)
{
if(!GPIO.F1) GPIO.F0 = 1; // LED on if button pressed
}
// THREAD 1 ===========================================
while(TMR1H <= 3) // gets 1, 2 and 3
{
if(GPIO.F1) GPIO.F0 = 0; // LED off if button not pressed
}
// THREAD 4 ===========================================
while(TMR1H == 4)
{
}
// THREAD END =========================================
See above that thread 1 has been given 3 timeslice units, so it gets
3mS and the other 2 threads still get 1mS each. Total timeslice is now 5mS.
Adding an interrupt
If a timed interrupt is used as the thread sequencing engine the
system becomes a lot more powerful and useful. The interrupt engine can
now generate a precise timed period, and automatically sequence the
threads based on that exact timeslice.
Imagine if the thread sequencing engine sets a "low_priority" flag
every 10 loops;
// THREAD 0 ===========================================
while(thread == 0)
{
if(!GPIO.F1) GPIO.F0 = 1; // LED on if button pressed
}
// THREAD 1 ===========================================
while(thread == 1)
{
if(GPIO.F1) GPIO.F0 = 0; // LED off if button not pressed
}
// THREAD 2 ===========================================
while(thread == 2)
{
}
// THREAD 3 ===========================================
while(thread == 3 && low_priority)
{
}
// THREAD END =========================================
Any threads at the bottom of the sequence can now be controlled by the
low_priority flag, so they will only be given timeslice when the
low_priority flag is set.
An obvious extension of this would be to allow priority ranking
using multiple x_priority flags, with the lowest ranked priority threads
at the bottom of the sequence. The interrupt engine could easily control
how often each priority rank is enabled, and each rank could apply to
any number of threads.
Interrupt engine example
Another BIG advantage is that the interrupt engine can also update
a number of independent timers, which can be shared by threads or
allocated exclusively to threads.
This is a sequencing engine using the TMR0 interrupt;
void interrupt(void)
{
// TMR0 interrupt, gets here every 200 instructions (100uS)
// this sequences the threads, and also sets a flag every second.
TMR0 += ((256-200)+3); // adjust TMR0 so it rolls every 200 ticks
intcount++;
if(intcount >= 10) // if 1mS
{
intcount = 0;
thread++; // sequence new thread every 1000uS (1mS)
mscount++;
if(mscount >= 1000)
{
mscount = 0;
newsecond = 1; // set this flag every second
}
thread2timer++; // counts up in mS
}
INTCON.T0IF = 0; // clear int flag
}
TMR0 is used so the other PIC timers are left available. TMR0 is set to make an
interrupt every 200 instructions which is exactly 100uS so it can be used for
multiple timing tasks.
The variable mscount is incremented exactly every mS,
and exactly every second the flag newsecond is set. One of the threads can
be controlled by this flag to operate as a real time clock thread.
The variable thread2timer is also incremented automatically every
1mS, and it is exclusively used by thread 2 which can now do its own
fully independant tasks. Any of the threads can have their own timers.
A real PIC-thread application!
The application below was tested in hardware and the TMR0 int
occurs exactly every 100uS, and the clock keeps perfect time;
/******************************************************************************
multi-thread3.c Example of PIC multi-thread - making a 12:00:00 clock
Open-source 22nd Dec 2009 - www.RomanBlack.com
(PIC16F887 8MHz EasyPIC6 2x16 LCD; HSOSC WDTOFF LVPOFF)
TMR0 interrupt is the thread control engine and timer generator.
Uses 4 threads; each is 1mS duration.
******************************************************************************/
unsigned int mscount; // mS counters
unsigned int thread2timer;
unsigned char newsecond;
unsigned char intcount;
unsigned char thread;
unsigned char refresh_display;
unsigned char hours; // clock vars
unsigned char mins;
unsigned char secs;
unsigned char txt[4];
#include "RomanLCD.c" // my library to drive 2x16 LCD
//-----------------------------------------------------------------------------
void interrupt(void)
{
// TMR0 interrupt, gets here every 200 instructions (100uS)
// this sequences the threads, and also sets a flag every second.
TMR0 += ((256-200)+3); // adjust TMR0 so it rolls every 200 ticks
intcount++;
if(intcount >= 10) // if 1mS
{
intcount = 0;
thread++; // sequence new thread every 1000uS (1mS)
mscount++;
if(mscount >= 1000)
{
mscount = 0;
newsecond = 1; // set this flag every second
}
thread2timer++; // counts up in mS
}
INTCON.T0IF = 0; // clear int flag
}
//-----------------------------------------------------------------------------
void main()
{
// setup PIC 16F887, 8MHz xtal (EasyPIC6)
ANSEL = 0; // Configure AN pins as digital
ANSELH = 0;
CM1CON0 = 0;
CM2CON0 = 0;
PORTB = 0b00111111; // SET LCD pins HI
TRISB = 0b00000000; // PORTB all outs, drives LCD
TRISC = 0b00000011; // RC0,1 are buttons
OPTION_REG = 0b00001000; // TMR0 1:1 prescale (2MHz)
thread = 0;
mscount = 0;
thread2timer = 0;
hours = 12;
mins = 0;
secs = 0;
RomanLCD_Init(); // init 2x16 text LCD
INTCON = 0b10100000; // TMR0 interrupt on
// loop here and do each thread for 2000 instructions (1mS)
while(1)
{
// THREAD 0 ===========================================
while(thread == 0)
{
// this thread looks for a new second and updates the seconds
if(newsecond)
{
newsecond = 0;
secs++;
refresh_display = 1; // tell other thread to redraw LCD
}
}
// THREAD 1 ===========================================
while(thread == 1)
{
// this thread updates the minutes and hours
if(secs >= 60)
{
secs = 0;
mins++;
}
if(mins >= 60)
{
mins = 0;
hours++;
}
if(hours > 12) hours = 1;
}
// THREAD 2 ===========================================
while(thread == 2)
{
// this thread checks the 2 buttons to set the clock
// test buttons 4 times per sec
if(thread2timer > 250) // 250mS
{
thread2timer = 0;
if(PORTC.F0) // set mins button
{
mins++;
mscount = 0;
secs = 0;
newsecond = 1;
}
if(PORTC.F1) // set hours button
{
hours++;
mscount = 0;
secs = 0;
newsecond = 1;
}
}
}
// THREAD 3 ===========================================
while(thread == 3)
{
// this thread draws the clock on LCD if needed
// this thread is last in case it runs >1mS
if(refresh_display)
{
ByteToStr(secs,txt); // format and display secs
txt[0] = ':';
if(txt[1] == ' ') txt[1] = '0';
RomanLCD_Out(0,6,txt);
ByteToStr(mins,txt); // format and display mins
txt[0] = ':';
if(txt[1] == ' ') txt[1] = '0';
RomanLCD_Out(0,3,txt);
ByteToStr(hours,txt); // format and display hours
RomanLCD_Out(0,0,txt);
refresh_display = 0; // refresh is done
}
}
// THREAD END =========================================
thread = 0; // double clear in case of interrupt
thread = 0;
}
}
This uses the same interrupt as the thread control engine. Also take a look at
thread 2. Thread 2 tests the 2 buttons used to set the clock time. It has
it's own independent 1mS timer variable; thread2timer. So thread 2 operates
as an independent process that checks the buttons every 250mS regardless of
what the other threads are doing.
The threads communicate between themselves in a crude fashion. The newsecond
flag is used by the interrupt engine to add a second to the clock. Thread 0
sets the refresh_display flag that tells thread 3 that the display must be
updated.
Thread 1 is the actual clock. It's only task is to check the hours, mins
and secs variables, and if any variable rolls over it fixes the clock time.
Thread 2 uses a bit of a cheat because when the user changes the time by
pressing a button it cheats and sets the newsecond flag. This tells thread 0
to add a bogus second, so it then tells thread 3 to display the new time
and before that happens thread 1 gets a chance to fix any time errors.
Thread 3 is placed at the bottom of the sequence because writing to the
LCD takes time, and it is the only thread that risks running overtime.
If thread 3 does run >1mS then the next thread executed (thread 0) will
get a timeslice that is less than 1mS. Which may or may not matter,
in this clock application it won't matter.
Polite threads?
It would be nice to have the option of "polite threads" that can
choose to give up their timeslice if they have nothing to do.
This should be easy enough. The interrupt engine needs to separate
the thread timing task from any real world timers. Then any thread can
force its own timeslice to end prematurely which allows the next thread
to take over.
void interrupt(void)
{
// TMR0 interrupt, gets here every 200 instructions (100uS)
// Sequence the threads first
TMR0 += ((256-200)+3); // adjust TMR0 so it rolls every 200 ticks
intcount++;
if(intcount >= 10) // if 1mS
{
intcount = 0;
thread++; // sequence new thread every 1000uS (1mS)
}
// update real world timers
timerintcount++;
if(timeintrcount >= 10) // if 1mS
{
timerintcount = 0;
mscount++;
if(mscount >= 1000) // if 1 second
{
mscount = 0;
newsecond = 1; // set this flag every second
}
thread2timer++; // counts up in mS
}
INTCON.T0IF = 0; // clear int flag
}
The new interrupt (see above)
needed one more variable; timerintcount, but now it
has completely separated the thread control from the timer control.
Now a thread can politely give away it's timeslice. All that it needs
to do is;
3. set intcount to 255 (so the next thread gets a full timeslice)
// THREAD 1 ===========================================
while(thread == 1)
{
if(PORTB.F1) LED = 1;
else give_up_timeslice();
}
// THREAD 2 ===========================================
while(thread == 2)
{
}
// THREAD END =========================================
give_up_timeslice();
thread = 0;
}
}
// this function can be called by any thread
void give_up_timeslice(void)
{
while(TMR0 > 240); // WAIT HERE until sure that int cant happen
thread++; // make next thread active
intcount = 255; // will roll to zero, so next thread gets full timeslice
}
Above you can see above, thread 1 politely decided to give up its timeslice
when it discovered it had nothing left to do.
The little function give_up_timeslice() can also be used to clean up
after all the threads at the bottom of the sequence. So if a thread has
gone into overtime the give_up_timeslice() function will re-sync so the
next thread (which will be thread 0) will get a full timeslice.
multi-threading by frequency
The systems above are all timeslice multi-thread systems, they operate
by allocating a percentage of the available time to each thread.
This next system is a totally different approach. It is a "frequency
multi-thread system" (for want of a better name). Threads are executed
based on frequency so a thread might be executed on every loop, or every
2nd loop, or every 20th loop etc.
This might be a better multi-threading system where there is a very
high priority thread which can be called every loop and other threads called
much less frequently. It may also be a good system if there are a
large amount of very fast tasks, which are of varying priorities.
while(1)
{
// LOOP SYNCHRONISE ===================================
while(!loop_time_sync); // WAIT HERE until loop time
loop_time_sync--;
loopnum++;
// THREAD 0 ===========================================
// this is very high priority, it is done every loop
// THREAD 1 ===========================================
// this thread is also very high priority
// THREAD 2 ===========================================
if((loopnum % 2) == 0) // do this thread every 2nd loop
{
}
// THREAD 3 ===========================================
if((loopnum % 20) == 0) // do this thread every 20th loop
{
}
// THREAD 4 ===========================================
if(loopnum == 0) // do this thread every 256th loop
{
}
// THREAD 5 ===========================================
if(loopnum == 1) // do this thread every 256th loop
{
}
// THREAD END =========================================
}
The variable loopnum increments for every loop done. It just rolls over
so it is always 0-255 range. So threads can be executed on loop frequency
like every X loops.
This is a fairly common programming practice; to de-prioritise a section
of code that does not need to be run as often as other tasks. But I think
if this technique is refined to a new level it may qualify as a form of
multi-threading.
The obvious improvement would be to use a timer interrupt again. So
any thread could use a shared real world timer or have its own dedicated
timer. Also I have added a very simple but sophisticated loop synchronisation
system. Now it is looking like a useful form of multi-threading.
The loop sync system (see above) works in tandem with the interrupt timer
engine. Imagine that the loop needs to be executed 100 times a second (10mS).
The interrupt increments the variable loop_time_sync every 10mS. If a loop
takes less than 10mS, the next loop will wait and sync (restart) when the
variable is incremented to 1 by the interrupt.
Now some loops may take longer than 10mS, even a lot longer. Or there may
be a number of consecutive loops each one being longer than 10mS.
The loop_time_sync system will increment for every 10mS, so it keeps
perfect accumulated time. If there is a very long overtime loop, or many
overtime loops, then the loop_time_sync variable just increments in value and
keeps track of the lost time.
Then when there are shorter loops (ie <10mS) they will decrement the
loop_time_sync variable every loop until the loop is perfectly back
in sync with the interrupt engine! It is a self-correcting self-synchronising
loop timer that will allow quite a large total of overtime situations
and then just fix itself up later.
Loop frequency PIC-thread application!
This is the same clock project with the same 4 threads. But this time
it has been re-structured in a loop frequency multi-thread format and
another "time waster" thread has been added to test it. Yes it has
been tested in hardware and it keeps great time.
/******************************************************************************
multi-thread4.c Example of PIC multi-thread - making a 12:00:00 clock
Open-source 22nd Dec 2009 - www.RomanBlack.com
(PIC16F887 8MHz EasyPIC6 2x16 LCD; HSOSC WDTOFF LVPOFF)
This uses loop frequency multi-threading.
TMR0 interrupt is the loop timing generator.
Uses 5 threads; varying priorities.
******************************************************************************/
unsigned char intcount;
unsigned char loop_time_sync;
unsigned char loopnum;
unsigned char loop10ms;
unsigned char refresh_display;
unsigned char thread1timer;
unsigned char hours; // clock vars
unsigned char mins;
unsigned char secs;
unsigned char txt[4];
#include "RomanLCD.c" // my library to drive 2x16 LCD
//-----------------------------------------------------------------------------
void interrupt(void)
{
// TMR0 interrupt, gets here every 200 instructions (100uS)
// this incs the loop sync flag every 10mS.
TMR0 += ((256-200)+3); // adjust TMR0 so it rolls every 200 ticks
intcount++;
if(intcount >= 100) // if reached 10mS
{
intcount = 0;
loop_time_sync++;
thread2timer++; // every 10mS
}
INTCON.T0IF = 0; // clear int flag
}
//-----------------------------------------------------------------------------
void main()
{
// setup PIC 16F887 (EasyPIC6)
ANSEL = 0; // Configure AN pins as digital
ANSELH = 0;
CM1CON0 = 0;
CM2CON0 = 0;
PORTB = 0b00111111; // SET LCD pins HI
TRISB = 0b00000000; // PORTB all outs, drives LCD
TRISC = 0b00000011; // RC0,1 are buttons
OPTION_REG = 0b00001000; // TMR0 1:1 prescale (2MHz)
loopnum = 0;
hours = 12;
mins = 0;
secs = 0;
RomanLCD_Init(); // init 2x16 text LCD
INTCON = 0b10100000; // TMR0 interrupt on
// loop here and do the multi-threading loop.
while(1)
{
// LOOP SYNCHONISE ====================================
while(!loop_time_sync); // WAIT HERE until loop time (10mS)
loop_time_sync--;
loopnum++;
// THREAD 0 ===========================================
// HIGH PRIORITY! this thread generates the 1 second event
loop10ms++;
if(loop10ms >= 100) // if reached 1 second!
{
loop10ms = 0;
secs++;
refresh_display = 1; // tell other thread to redraw LCD
}
// THREAD 1 ===========================================
// medium priority
if((loopnum % 5) == 0) // do this thread every 5th loop
{
// this thread checks the 2 buttons to set the clock
// test buttons 4 times per sec
if(thread1timer > 25) // 25x10mS = 250mS
{
thread1timer = 0;
if(PORTC.F0) // set mins button
{
mins++;
secs = 0;
refresh_display = 1; // tell other thread to redraw LCD
}
if(PORTC.F1) // set hours button
{
hours++;
secs = 0;
refresh_display = 1; // tell other thread to redraw LCD
}
}
}
// THREAD 2 ===========================================
// HIGH PRIORITY! this thread updates the minutes and hours
if(secs >= 60)
{
secs = 0;
mins++;
}
if(mins >= 60)
{
mins = 0;
hours++;
}
if(hours > 12) hours = 1;
// THREAD 3 ===========================================
// medium priority
if((loopnum % 5) == 1) // do this thread every 5th loop
{
// this thread draws the clock on LCD if needed
// this thread is last in case it runs >1mS
if(refresh_display)
{
ByteToStr(secs,txt); // format and display secs
txt[0] = ':';
if(txt[1] == ' ') txt[1] = '0';
RomanLCD_Out(0,6,txt);
ByteToStr(mins,txt); // format and display mins
txt[0] = ':';
if(txt[1] == ' ') txt[1] = '0';
RomanLCD_Out(0,3,txt);
ByteToStr(hours,txt); // format and display hours
RomanLCD_Out(0,0,txt);
refresh_display = 0; // refresh is done
}
}
// THREAD 4 ===========================================
// low priority
if((loopnum % 20) == 2) // do this thread every 20th loop
{
// this thread is just a nasty big delay every 20 loops
// to test the self correcting loop sync system.
Delay_ms(60);
}
// THREAD END =========================================
}
}
As the sync loop can be relied on to make exactly 100 loops per second I
generated the 1 second event by counting loops, mainly to test the
reliability of the loop sync. However it could have used
a dedicated 1 second timer like the last project, which would have been
a better choice. Thread 1 still uses a dedicated timer that is controlled in
the interrupt to make the 250mS period for checking the buttons.
The loop sync works fine, it swallows up the large 60mS "task" in thread 4
that occurs every 20 loops and resyncs within a few loops and there is no
noticable difference on the display, and obviously it keeps perfect time.
I'm definiely NOT an expert in multi-thread software! But having independant
threads that can have independant resources (real world timers etc) and
being able to just slot in a large clumsy task by simply adding a thread, all
without any noticable effect on operation, seems like a decent definition.
At least it's a starting point for working with multi-thread systems on
a PIC.
Where to go from here?
The code examples on this page are all pretty crude. Even the 2 clock
applications have no real NEED to be multi-threaded apps.
But when I look back at some of the larger PIC projects I have made in the
past (especially ones that grew until they became clumsy) I can see they
would have been much better built with some type of multi-thread
structure.
For the future it would be good to explore systems for controlling the
threads communication with each other. Using better variable names and a
system for controlling whether data flows in or out of a thread, so
threads can pass data and commands to each other with no conflict.
Also to look at ways of making threads more independent, like ways of
removing any need for threads to be sequenced in a particular order.
And also to explore ways of making thread execution much more parallel
and less sequential. As an example if a thread is writing graphics
data to a GLCD (which can take a while) it might only write one byte
every time that thread is executed, so the GLCD write occurs in
parallel to other tasks. This could be done with intra-thread scheduling
so each thread can queue up tasks and and perform a little piece of each
task every time that thread is executed.
A proper PIC-thread project; Baudrate converter 1
This uses the PIC-thread loop frequency system and 4 threads to
operate as a high speed asynchronous bidirectional baudrate converter.
The 4 threads are displayed in colour;
// THREAD 0 ===========================================
// check if usart1 received a byte, put it in buffer
// THREAD 1 ===========================================
// check if usart2 received a byte, send it out to usart1
// THREAD 2 ===========================================
// send usart1 byte if any bytes are in buffer
// THREAD 3 ===========================================
// display the buffer1 load as a 7 LED bargraph on PORTB
The loop frequency system gives high priority to threads 0, 1 and 2.
These are checked and executed every loop to keep data transfer high.
Thread 3 is a lower priority (because it just displays the bargraph)
and is only executed every 16th loop.
Here is the actual thread code;
while(1)
{
// LOOP SYNCHONISE ====================================
// don't sync, just run loop at full speed
loopnum++;
// THREAD 0 ===========================================
// HIGH PRIORITY!
// check if usart1 received a byte, put it in buffer
if(PIR1.RC1IF)
{
buffer1[inpointer1] = RCREG1; // store the byte
inpointer1++;
if(inpointer1 >= MAXBUFFER1) inpointer1 = 0;
}
// THREAD 1 ===========================================
// HIGH PRIORITY!
// check if usart2 received a byte, send it out to usart1
if(PIR3.RC2IF)
{
if(TXSTA1.TRMT) TXREG1 = RCREG2; // if usart1 TX free, send byte
}
// THREAD 2 ===========================================
// HIGH PRIORITY!
// send usart1 byte if any bytes are in buffer
bufferload1 = (inpointer1 - outpointer1);
if(bufferload1)
{
if(bufferload1 >= MAXBUFFER1) bufferload1 += MAXBUFFER1; // fix buffer roll
th3display = (bufferload1 >> 8); // 1792->7 ready for bar display
if(TXSTA2.TRMT) // if usart2 free, send another byte
{
TXREG2 = buffer1[outpointer1];
outpointer1++;
if(outpointer1 >= MAXBUFFER1) outpointer1 = 0; // fix buffer roll
}
}
else th3display = 0;
// THREAD 3 ===========================================
// medium priority
// display the buffer1 load as a 7 LED bargraph on PORTB
if((loopnum & 0x0F) == 0) // do this thread every 16th loop
{
LATB = 0;
if(bufferload1) LATB.F7 = 1; // 7 LED bargraph
if(th3display) LATB.F6 = 1;
if(th3display > 1) LATB.F5 = 1;
if(th3display > 2) LATB.F4 = 1;
if(th3display > 3) LATB.F3 = 1;
if(th3display > 4) LATB.F2 = 1;
if(th3display > 5) LATB.F1 = 1;
}
// THREAD END =========================================
}
The 7 LED bargraph will light LED 7 if there are any bytes in the buffer
and then LEDs 6 to 1 show the buffer usage in a linear fashion.
This project is tested in hardware and can be seen complete on my
BIGPIC6 Projects Page
along with a more sophisticated PIC-thread project which is
a Baudrate converter with GLCD display and a 62 bar bargraph
to show the buffer usage.
I think this project is a pretty fair demonstration of a multi-threading
application, for these reasons;