Intro

task list graphic

From this point on, there are various tasks that need to occur deterministically, but not necessarily via an interrupt.  We are going to implement a task manager for this purpose.  For readers, this may be familiar.  We already implemented the task manager for the MSP430 a few weeks ago in a much simpler application.  In this article, we are going to be putting several 'tasks' which will function independently, deterministically, and at different intervals.

Timing

There are several types of applications that require processing, but infrequent processing at fixed intervals.  A PID loop is a perfect example.  When the number of tasks is very low, then this type of processing is usually done using a dedicated timer interrupt.  When the number escalates, then we find that we run out of timers quickly on most processors.  This is the problem that the task manager was created to solve.

I typically only use the task manager for tasks that need to execute less frequently than 1kHz.  This is my desired 'sytem tick'.  Faster tasks should get their own timer interrupt.

Task Manager

To execute timing to 1ms precision, we are going to use the embedded task manager.  Specifically, we are going to copy the 'task.h' and 'task.c' files from the github repository and modify them somewhat to use the PIC24 timer peripheral instead of the MSP430.

We are using timer 1 already for sine wave generation and CCP timers 1 and 2 for PWM generation, so we will be forced to use CCT3 as the time base for our timer interrupt.  Note that there are multiple interrupts associated with the CCP/CCT module, you want the CCT interrupt!  Also note, neither of these is documented, I found it by guessing and checking.  In the below code, the green indicates where code has been added:

Change the include from 'lib430.h' to

1
2
#include <xc.h>
#include "task.c"

Adding a 'TMR_init()' function specific to the PIC24 which takes a function pointer as a parameter.  We are going to simply add this function at the bottom of the 'task.c'

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
void TMR_init(void (*functPtr)()){
    TMR_timedFunctPtr = functPtr;

    /* period registers */
    CCP3PRH = 0;
    CCP3PRL = 16000;

    CCP3CON1L = 0x0000; // timer mode
    CCP3CON1H = 0x0000;
    CCP3CON2L = 0x0000;
    CCP3CON2H = 0x0000;
    CCP3CON3L = 0;
    CCP3CON3H = 0x0000;

    IFS1bits.CCT3IF = 0;
    IEC1bits.CCT3IE = 1;

    CCP3CON1Lbits.CCPON = 1;
}

Adding the 'TMR_disableInterrupt()' function specific to the PIC24.  Again, we will simply include this at the bottom of the 'task.c'

1
2
3
void TMR_disableInterrupt(){
    IEC1bits.CCT3IE = 0;
}

Adding the PIC24-specific interrupt

1
2
3
4
5
6
void _ISR _CCT3Interrupt(){
    if(TMR_timedFunctPtr != 0)
        (*TMR_timedFunctPtr)();

    IFS1bits.CCT3IF = 0;
}

Adding a ClrWdt() to the 'TASK_manage()' function since it never returns.  This clears the watchdog timer, which is something we have been doing in 'main()' of our current code base.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
void TASK_manage(){
    while(1){
        uint16_t i;
        for(i = 0; i < MAX_NUM_OF_TASKS; i++){
            uint32_t time = TASK_getTime();
            if(task[i].taskFunctPtr != 0){
                if(time >= task[i].nextExecutionTime){
                    task[i].nextExecutionTime = task[i].period + time;
                    (task[i].taskFunctPtr)();
                }
            }
        }

        ClrWdt();
    }
}

Now that we have modified 'task.c' as required, we need to modify our 'main()' function so that it properly starts the task manager.  Again, green indicates additions to the code, blue indicates changes:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
int main(void) {
    initOsc();
    initLowZAnalogOut();
    initInterrupts();
    initPwm();
    initAdc();
    initUart();

    TASK_init();

    /* set the initial duty cycles */
    setDutyCycleHZ1(16384);
    setDutyCycleHZ2(8192);

    TASK_manage();

    return 0;
}

The task manager setup is complete!  Now, all we have to do is create a function and add it to the task manager!

Adding a Task

Adding a task is almost as simple as adding a function.  We are going to add two tasks:

  1. One task to control the HZ1 voltage, and
  2. Another task to control the HZ2 voltage

We could place both of these into a single task, but the time may come when we wish to change the frequency of each of them independently.  Besides, doing it this way allows us to focus on one task at a time.

Declare the function like any other function.

1
2
void hz1Control(void);
void hz2Control(void);

Write the function implementation(s):

1
2
3
4
5
6
7
8
9
void hz1Control(void){

    return;
}

void hz2Control(void){

    return;
}

Add the function in 'main()' with the proper timing specified, 20ms in this case.  The tasks must be added after 'TASK_init()' and can be added or removed at any time after that.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
int main(void) {
    initOsc();
    initLowZAnalogOut();
    initInterrupts();
    initPwm();
    initAdc();
    initUart();

    TASK_init();

    /* set the initial duty cycles */
    setDutyCycleHZ1(16384);
    setDutyCycleHZ2(8192);

    /* add tasks */
    TASK_add(&hz1Control, 20);
    TASK_add(&hz2Control, 20);

    TASK_manage();

    return 0;
}

At this point, we have two tasks that are doing nothing.  We will go just a bit further to verify the functionality of our task manager.  We will toggle a pin within 'hz1Control()':

1
2
3
4
void hz1Control(void){
    LATBbits.LATB7 ^ 1;
    return;
}

Resulting in the scope capture:

pin toggle every 20ms capture

We measure a near-perfect 20ms between pin toggles, which is exactly what we were expecting.



© by Jason R. Jones 2016
My thanks to the Pelican and Python Communities.