Developing in C for the ATmega328P: Multi-tasking the Arduino - Part 1

7 minute read

Where I develop the C version of multi-tasking, similar to the example provided by Adafruit.

Introduction

An informative and valuable lesson on the Adafruit site is Multi-tasking the Arduino - Part 1. In this explanation, the author lays out how to “Make your Arduino walk and chew gum at the same time.” While it is phenomenal instructional content, the ultimate solution is a class approach which is not allowed in C. This entry will follow the content of the original, however, it will provide C-based solution.

Bigger and Better Projects

Our goal is the same as the original, determine a method to manage multiple tasks on the Uno without the help of an operating system. The content goes on to show how blink works using a delay() as well as a similar program for a servo called sweep. If you need a refresher on blink there are several examples in the examples folder, blink, blink_avr, and blink_tog. Note: As of this moment, I don’t have a servo, so I will be implementing the examples with LED’s.

Using millis() for timing

It will be helpful to have this specific example from the site, as it provides the initial basis for a State Machine. The implementation is very similar to that of Adafruit, however, my example has the following changes:

  • removes the setup and loop constructs and replaces them with initialization statements, main() function and a while(1) loop
  • added required #include’s for the functions being used, including sys_clock.h, which is required for millis() and init_sysclock_2(), which will set up millis()
  • uses the C99 standard types of uint8_t and uint16_t, instead of the int and long int (millis() on AVR C only returns a 16 bit result)
  • replaces the cumbersome code tracking the LED state with a simple toggle, as that is part of the AVR instruction set (the state tracking code can be helpful in other instances, as we’ll see in the next example)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// Blink without Delay C version, based on Arduino tutorial:
// http://www.arduino.cc/en/Tutorial/BlinkWithoutDelay
// And the Adafruit tutorial:
// https://learn.adafruit.com/multi-tasking-the-arduino-part-1?view=all

#include "pinMode.h"
#include "digitalWrite.h"
#include "sysclock.h"

// constants won't change. Used here to set pin number and time interval:
const uint8_t ledPin =  3;          // the number of the LED pin
const uint16_t interval = 1000;     // interval at which to blink (milliseconds)

// Variables will change:
uint16_t previousMillis = 0;        // will store last time LED was updated

int main (void)
{
  // Set up a system tick of 1 millisec (1kHz)
  init_sysclock_2 ();

  // set the digital pin as output:
  pinMode(ledPin, OUTPUT);      

    while(1)
    {
        // check to see if it's time to blink the LED; that is, if the 
        // difference between the current time and last time you blinked 
        // the LED is bigger than the interval at which you want to 
        // blink the LED.
        uint16_t currentMillis = millis();

        if(currentMillis - previousMillis > interval) {
        // save the last time you blinked the LED 
        previousMillis = currentMillis;   
        // toggle the state of the LED
        digitalWrite(ledPin, TOG);
        }
    }
}

Welcome to the Machine

This is a better example of a State Machine, as in this example, we will keep track of the State. We do this because we want to have different values of our ON time and our OFF time. Similar changes to the program were made, as noted above:

Example Code (examples/flashwithoutdelay)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// FlashWithoutDelay
// Adafruit tutorial:
// https://learn.adafruit.com/multi-tasking-the-arduino-part-1?view=all

#include "pinMode.h"
#include "digitalWrite.h"
#include "sysclock.h"

// constants won't change. Used here to 
// set pin number and time interval:
const uint8_t ledPin =  3;        // the number of the LED pin
const uint16_t OnTime = 250;      // interval On (milliseconds)
const uint16_t OffTime = 750;     // interval Off (milliseconds)

// Variables will change:
uint8_t ledState = LOW;           // LED State variable, tracks state of LED
uint16_t previousMillis = 0;        // will store last time LED was updated

int main (void)
{
  // Set up a system tick of 1 millisec (1kHz)
  init_sysclock_2 ();

  // set the digital pin as output:
  pinMode(ledPin, OUTPUT);      

    while(1)
    {
        // check to see if it's time to blink the LED
        uint16_t currentMillis = millis();

        if ((ledState == HIGH) && (currentMillis - previousMillis >= OnTime)) 
        {
        // save the last time you blinked the LED and change state
        previousMillis = currentMillis;   
        ledState = LOW;
        digitalWrite(ledPin, ledState);
        }
        if ((ledState == LOW) && (currentMillis - previousMillis >= OffTime)) 
        {
        // save the last time you blinked the LED and change state
        previousMillis = currentMillis;   
        ledState = HIGH;
        digitalWrite(ledPin, ledState);
        }
    }
}

Now for two at once

I won’t repeat the code as they did for adding a second LED. I believe that is something, which is worthwhile if you wish to understand this process more. Let’s move on to the meat of the solution, one which will replace “A classy solution” called A more Structured Approach

A More Structured Approach

As compared to C++, the C language does not allow for classes. I don’t find this an issue, as while classes can simplify programming, it can also introduce significant debugging issues. Let’s go forward with the C approach which will use a struct.

A struct is C language method of grouping common variables together. As we can see above, we would want to group (at a minimum) the OnTime, OffTime and ledState together. By doing so, we can easily add multiple versions of the same three variables. We able to cycle through the versions as easily as we might cycle through an array. For a more detailed discussion (and a video) of using structs.

This version will be called multi_struct, as it uses multiples (think an array) of the same struct to control the LEDs.

Example Code (examples/multi_struct)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
// multi_struct a C compatible version of the Adafruit Classy example
// https://learn.adafruit.com/multi-tasking-the-arduino-part-1?view=all
// Uses the "One line kernal for multitasking"
// https://www.embedded.com/a-multitasking-kernel-in-one-line-of-code-almost/
// Tasks are individually setup using a struct

#include <avr/io.h>
#include "delay.h"
#include "pinMode.h"
#include "sysclock.h"
#include "digitalWrite.h"

// Defaults:
//  MAX_FLASHERS: Number of flashers to be defined
//  DEFAULT_ON/DEFAULT_OFF: Default times (msec) for flasher to be on/off
//  With On/Off set to 1, three tasks run at 500Hz
#define MAX_FLASHERS 3
#define DEFAULT_ON 100
#define DEFAULT_OFF 100

// struct flasher replaces class flasher in Adafruit example
// struct contains the variables required to maintain pin, state and time
struct flasher 
{
   volatile uint8_t pin;    // Uno pin 
   uint8_t state;           // Is pin HIGH or LOW
   uint16_t on;             // Time on
   uint16_t off;            // Time off
   uint16_t elapsed;        // Time elapsed sinced last in loop
} ;

// now setup an array of structs to easily manage them, we can 
// reference each struct by a subscript just like an array
struct flasher flashers[MAX_FLASHERS];

// init is similar to the Flasher Constructor in the example
// and initializes the member variables and state, and sets LED pin to OUTPUT
void init(uint8_t index, uint8_t pin, uint8_t state, uint16_t on, uint16_t off,\
        uint16_t elapsed)
{
    flashers[index].pin = pin;
    flashers[index].state = state;
    flashers[index].on = on;
    flashers[index].off = off;
    flashers[index].elapsed = elapsed;

    pinMode(pin, OUTPUT);
    return ;
}

// update is similar to Classy update in that it checks the time and changes
// state if the time has expired, it also tracks both on-time and off-time
void update (uint8_t flasherID) 
{
    // Based on adafruit lesson on classes
    uint16_t now = millis();

    if((flashers[flasherID].state == HIGH) &&\
        (now - flashers[flasherID].elapsed >= flashers[flasherID].on))
    {
        flashers[flasherID].state = LOW;  // Turn it off
        flashers[flasherID].elapsed = now;  // Remember the time
        digitalWrite(flashers[flasherID].pin, LOW);
    }
    else if ((flashers[flasherID].state == LOW) &&\
        (now - flashers[flasherID].elapsed >= flashers[flasherID].off))
    {
        flashers[flasherID].state = HIGH;  // turn it on
        flashers[flasherID].elapsed = now;   // Remember the time
        digitalWrite(flashers[flasherID].pin, HIGH);
    }
    return;
}

int main(void)
{
    init_sysclock_2 ();

    // initialize each flasher (struct: {pin, state, on, off, elapsed})
    // in comparison to Classy example, each LED only requires 1 line of code :)
    // AND update MAX_FLASHERS to number of flashers
    init(0, 3, LOW, DEFAULT_ON, DEFAULT_OFF, 0);
    init(1, 5, LOW, DEFAULT_ON << 1, DEFAULT_OFF << 1, 0);
    init(2, 6, LOW, DEFAULT_ON << 2, DEFAULT_OFF << 2, 0);

    // same as the Adafruit example, update is very simple
    // in this case, we simply increment through our array of structs
    while (1)
    {
    for (uint8_t flasher_cntr=0; flasher_cntr < MAX_FLASHERS; ++flasher_cntr)
        {
            update(flasher_cntr);
        }
    }
    return 0; 
}

Comments powered by Talkyard.