; multitask.asm ; ; An example of implementing a task switching system. ; ; This example has two tasks, each of which does its job, and then ; calls the task scheduler. The task switcher makes a decision about what to ; run and then performs a task switch. ; ; This process is typical of embedded systems, where tasks sometimes ; ready other tasks to run; for example, you scan the buttons and that ; causes you to run a task to update a counter. In ; any case, this is different from a general purpose operating system, ; where the key to the whole operation is a task scheduler and switcher ; which runs independently of any particular ; task or its desires. ; LIST P3D16F877, R3DDEC INCLUDE "P16F877.inc" ; Definitions ; These are task names and also priorities #define DipTask 0 #define LedTask 1 #define IdleTask 2 #define UNDEFINED 0 #define BLOCKED 1 #define READY 2 ; __CONFIG _CP_OFF & _DEBUG_OFF & _WRT_ENABLE_OFF & _CPD_OFF & _LVP_OFF & _BODEN_OFF & _PWRTE_ON & _WDT_OFF & _HS_OSC20 ReadyTask macro task movlw State addlw task movwf FSR movlw READY movwf INDF endm BlockTask macro task movlw State addlw task movwf FSR movlw BLOCKED movwf INDF endm SetContext macro task ; W contains the PC task 3D task number movwf Temp movlw Context addlw task movwf FSR movf Temp,W movwf INDF endm Switch macro retlw $+1 endm ; Define start of vars ScratchPad EQU 0x20 ; Variables DipValue EQU ScratchPad+0 ; Value from dip switch Temp EQU ScratchPad+1 Counter EQU ScratchPad+2 DelayCount1 EQU ScratchPad+3 DelayCount2 EQU ScratchPad+4 SchW EQU ScratchPad+5 SchStat EQU ScratchPad+6 SchPCH EQU ScratchPad+7 Running EQU ScratchPad+8 ; Task control variables. Since there isn't much going on here, all we ; need to do is save the basics. For example, its all in one page, so ; ignore PCLATH; all three tasks make sure that all variables and ; registers are initialized, so don't worry about W, the ports, etc. In ; other words, all we have to store is a return location. For the sake ; of generality, the contexts are treated as a simple ; array, rather than as individual named contexts for each task (e.g. ; DipTaskContext). The State variables hold the state as ready or not ready. cblock ScratchPad+9 Context:3 Status:3 PCH:3 State:3 endc ; Start of the Program ORG 3 goto Start ; This code reads the dip switch and stores the bit pattern in Dippy and then outputs ; to the led_bar. ; ORG 0x50 Start ; Initialize the system. ; Set up RC3 to be output, RB3 to be input, A4-A0 to be output. bsf STATUS, RP0 clrf TRISA bsf TRISB,3 bcf TRISC,3 clrf PORTD ; to use the led_bar for output bcf STATUS, RP0 ; Set up the clocking pin for reading the dip switches from the latch. clrf SSPCON bcf PORTC,3 ; Initialize the task states and contexts ; movlw 0 movwf Counter InitLoop ; Set the task states BlockTask DipTask BlockTask LedTask ReadyTask IdleTask ; Set the context as the beginning location and ignore W, STATUS and PCH for now movlw DipTaskStart SetContext DipTask movlw LedTaskStart SetContext LedTask movlw IdleTaskStart SetContext IdleTask ; Set the variables controlling the scheduler. This includes some fakeyness ; because we have to handcraft the situation so that when the scheduler ; initializes, we want it to work properly. movlw IdleTask movwf Running movlw IdleTaskStart ; ; ===================================================================== ; Task scheduler/switcher Scheduler ; Set up a return address to get back here and establish a basic scheduling loop call $+2 goto $-1 ; Getting here implies that the "call" above has been returned, so W contains the return address ; for the task. First thing to do is store the current context. This could be really simple, just ; grabbing W, but other things are included here for demonstration purposes. movwf SchW ; get W without setting any status bits movf STATUS,W ; store STATUS movwf SchStat ; Now store the program counter low order bits permanently movf Running,W addlw Context movwf FSR movf SchW,W movwf INDF ; Store the status movf Running,W addlw Status movwf FSR movf SchStat,W movwf INDF ; Store the high order PC movf Running,W addlw PCH movwf FSR movf PCLATH,W movwf INDF ; The scheduling algorithm is simple. Start with task 0 and go up. The first ready task ; gets to run. clrw movwf Counter sched_lp movlw State addwf Counter,W movwf FSR movf INDF,W sublw READY btfsc STATUS,Z goto start_task ; nothing found, loop again. Note, since the IdleTask is always ready, there is no reason ; to test to see if the loop has gone beyond the number of tasks. incf Counter,F goto sched_lp ; Start a task. This is done by replacing the current context start_task movf Counter,W movwf Running addlw Context movwf FSR movf INDF,W movwf PCL ; End of the scheduler. We will never get back to here directly. The ; return will be to the top of the scheduler where the call instruction ; pushed an address on the stack ; ; ====================================================================== ; Idle task ; The name is a misnomer, because the task does things other than just ; idle. It implements a busy wait, and then every so often, it causes the ; dip switch read task to run. IdleTaskStart ; call Delay nop ReadyTask DipTask Switch goto IdleTaskStart ; ; ========================================================================= ; This is the task to read the dip switch. It reads the dip switch, ; puts the value in the global DipValue, readies the led write task and then ; calls the scheduler. DipTaskStart ; Read the current value of the dip switch ; Enable the 74LS165 and then disable it to create a low-going pulse. movlw 0x1E movwf PORTA movlw 0x10 movwf PORTA ; Now read each bit, shifting it into DipValue movlw 8 movwf Counter loop1 call ReadDipBit decfsz Counter,F goto loop1 ; Ready the Write task and call the task scheduler ReadyTask LedTask BlockTask DipTask Switch ; On return, do it all again goto DipTaskStart ; ; ======================================================================= ; This task, when started, gets the value from DipValue, puts the value ; to the LED bar and then starts the idle task. ; ; Put the value into the led_bar LedTaskStart comf DipValue,W call DispLedBar ; Ready the Write task and call the task scheduler ReadyTask IdleTask BlockTask LedTask Switch ; On return, do it all again goto LedTaskStart ; ; ======================================================================= ; Utility routines of various kinds ReadDipBit ; Rotate Dippy to make room for the next bit. Clear bit zero and then ; test PORTB, bit 3 to see its state. If zero, we be done. If not, set ; bit 0 of Dippy rlf DipValue,F bcf DipValue,0 btfsc PORTB,3 bsf DipValue,0 ; toggle the clock to generate a pulse that reads the next. Setting the ; bit drives it high and clearing sets it low, giving a square wave, ; high-going pulse that causes the next bit to be shifted out. bsf PORTC,3 bcf PORTC,3 return DispLedBar ; Enable the led bar and put the value in W there. movwf Temp ; save W movlw 0x12 movwf PORTA movf Temp,W movwf PORTD return Delay ; Delay for a bit (0.4 sec or so) delay_loop1 movlw 255 movwf DelayCount1 delay_loop2 decfsz DelayCount1,F goto delay_lab1 return delay_lab1 movlw 255 movwf DelayCount2 delay_loop3 fill (nop),30 decfsz DelayCount2,F goto delay_loop3 END