Who ever said PIC's can't do more than one thing at the same time was right, that's because they have a single instruction pointer that executes one command at a time. But what if one function took 1mS, and another took 3mS (and so on). Instead of waiting forever servicing the one function/waiting for a condition, why not allocate a time interval for each task, eg
One thing to keep in mind is that each task takes time, and you should allocate enough time for the task to complete, and every other task (as sooner or later, all the tasks will be synchronized and will need to be serviced one after the other). This is only really important if you want perfect timing, but its definitely not needed for most (almost all) applications, as each task will be serviced no matter what with this method.
OK, lets move on. My first task is to scan for a button press. The button will control a counter on an LCD, but at the same time, I want to update the current time on the LCD. I cant check the button every mS, or else the counter will almost instantly go to max, and there's no point updating the LCD display more than once a second to show current time... So lets multi-task!
To spice it up a little, I'm going to add two more tasks, frequency generators. (4 Tasks in total)
| Task | Frequency Interval | |
| Check Button & Update Counter | 500mS | |
| Update Current Time | 1000mS | |
| Frequency Generator 1 | 100mS (10Hz) | |
| Frequency Generator 2 | 5mS (200Hz) |
Sounds a little tricky, the truth of the matter is that it is. Consider the following code and the video below;
Device = 18F452 Clock = 20 #option LCD_DATA = PORTD.4 #option LCD_RS = PORTE.0 #option LCD_EN = PORTE.1 Include "LCD.bas" Include "utils.bas" Include "convert.bas" Dim TMR2_Int_Enable As PIE1.1, // TMR2 interrupt enable TMR2_Overflow As PIR1.1, // TMR2 overflow flag TMR2_On As T2CON.2, // Enables TMR2 to begin incrementing mS As Word, // Time Registers S As Byte, // M As Byte, // H As Byte, // Task_1_Timer As Word, // Task 1 = Check if button pressed Task_2_Timer As Word, // Update Time on LCD Task_3_Timer As Word, // Frequency output 1 Task_4_Timer As Word, // Frequency output 2 Task_1 As Boolean, // Task service request bits Task_2 As Boolean, // Task_3 As Boolean, // Task_4 As Boolean, // Example_Counter As Byte, // Counter register for the button Button As PORTA.0 // Assing the button Pin Const Task_1_Interval = 500, // mS interval for button check Task_2_Interval = 1000, // mS interval between LCD time display updates Task_3_Interval = 100, // mS interval for frequency generator output 1 Task_4_Interval = 5 // mS interval for frequency generator output 2 Interrupt TMR2_Interrupt() Save(0) If TMR2_Overflow = 1 And TMR2_Int_Enable = 1 Then TMR2_Overflow = 0 // Reset TMR2 Overflow flag mS = mS + 1 If mS = 1000 Then // Code for current time mS = 0 S = S + 1 If S = 60 Then S = 0 M = M + 1 If M = 60 Then M = 0 H = H + 1 If H = 24 Then H = 0 EndIf EndIf EndIf EndIf Task_1_Timer = Task_1_Timer + 1 // Increment all Task handlers Task_2_Timer = Task_2_Timer + 1 Task_3_Timer = Task_3_Timer + 1 Task_4_Timer = Task_4_Timer + 1 If Task_1_Timer = Task_1_Interval Then // Check if a task interval is Task_1_Timer = 0 // ready to be flagged Task_1 = True EndIf If Task_2_Timer = Task_2_Interval Then Task_2_Timer = 0 Task_2 = True EndIf If Task_3_Timer = Task_3_Interval Then Task_3_Timer = 0 Task_3 = True EndIf If Task_4_Timer = Task_4_Interval Then Task_4_Timer = 0 Task_4 = True EndIf EndIf Restore End Interrupt Sub TMR2_Initialize() TMR2_On = 0 // Disbale TMR2 TMR2_Int_Enable = 0 // Turn off TMR2 interrupts INTCON.6 = 1 // Peripheral Interrupts Enabled T2CON.0 = 1 // 00 = Prescaler is 1 T2CON.1 = 0 // 01 = Prescaler is 4 // 1x = Prescaler is 16 PR2 = 249 // TMR2 Period register PR2 T2CON.3 = 0 // 0000 = 1:1 postscale T2CON.4 = 0 // 0001 = 1:2 postscale T2CON.5 = 1 // 0010 = 1:3 postscale T2CON.6 = 0 // 1111 = 1:16 postscale TMR2 = 0 // Reset TMR2 Value TMR2_Int_Enable = 1 // Enable TMR2 interrupts TMR2_On = 1 // Enable TMR2 to increment Enable(TMR2_Interrupt) End Sub Sub Check_Button() If Button = 1 Then Example_Counter = Example_Counter + 1 WriteAt(1,11,DecToStr(Example_Counter,3)) EndIf End Sub Sub Update_Time() WriteAt(2,7,DecToStr(H,2), ":", DecToStr(M,2), ":", DecToStr(S,2)) End Sub Sub Freqency_Output_1() PORTB.0 = 1 PORTB.0 = 0 End Sub Sub Freqency_Output_2() PORTB.1 = 1 PORTB.1 = 0 End Sub DelayMS(200) // Allow LCD to warm up SetAllDigital // Make all pins digital I/O's Cls // Clear the LCD screen WriteAt(1,1,"Counter = 000") // Send text that only needs to be sent once WriteAt(2,1,"Time:") // to the LCD mS = 0 // Reset all registers S = 0 // M = 30 // H = 12 // Example_Counter = 0 // Task_1_Timer = 0 // Task_2_Timer = 0 // Task_3_Timer = 0 // Task_4_Timer = 0 // Task_1 = False // Task_2 = False // Task_3 = False // Task_4 = False // Input(Button) // Make the button an input Low(PORTB.0) // Make frequency 1 signal an output and low Low(PORTB.1) // Make frequency 2 signal an output and low TMR2_Initialize // Turn on TMR2 (with settings for 1mS interrupt) While 1 = 1 // Main program loop that is multi-tasked If Task_1 = True Then // Waits for each service request bit to go high Task_1 = False // then services the task as required Check_Button EndIf If Task_2 = True Then Task_2 = False Update_Time EndIf If Task_3 = True Then Task_3 = False Freqency_Output_1 EndIf If Task_4 = True Then Task_4 = False Freqency_Output_2 EndIf Wend
Notice that the interrupt contains only simple flag setting and variable increments. The actual tasks are performed outside the interrupt, so one task could be doing its thing, an interrupt occurs mid way through, "flagging" a task if its intervals elapses. This ensures that the task is serviced when the previous task is complete, easy!
The result, 4 tasks being performed at the same time at precise intervals;