Sonntag, 28. November 2010

protothread and arduino, a first easy example

I wrote a little example to demonstrate the possibilities of protothread and an avr µc. I decided to use some arduino specific functions like pinMode(), digitalWrite() and digitalRead() because i think this way it is very easy to understand what's happening in the code. But you should always keep in mind that these functions are up to 50 times slower than direct port access. It's easy to change the read and write functions to direct port access later on in a project if more I/O power is needed. For me arduino is a rapid prototyping platform...


What the code does, is that it toggles an LED every n ms using two independent protothreads for it. One pt toggles every 1000ms, the other one every 900ms. The result is an erratic blinking pattern. Download the protothread library and unpack it into your library directory, This example is already included as pde file. Restart your arduino IDE after unpacking and you will find it listed under "examples -> pt" in the "File" menue of the IDE.

#include <pt.h>   // include protothread library

#define LEDPIN 13  // LEDPIN is a constant 

static struct pt pt1, pt2; // each protothread needs one of these

void setup() {
  pinMode(LEDPIN, OUTPUT); // LED init
  PT_INIT(&pt1);  // initialise the two
  PT_INIT(&pt2);  // protothread variables
}

void toggleLED() {
  boolean ledstate = digitalRead(LEDPIN); // get LED state
  ledstate ^= 1;   // toggle LED state using xor
  digitalWrite(LEDPIN, ledstate); // write inversed state back
}

/* This function toggles the LED after 'interval' ms passed */
static int protothread1(struct pt *pt, int interval) {
  static unsigned long timestamp = 0;
  PT_BEGIN(pt);
  while(1) { // never stop 
    /* each time the function is called the second boolean
    *  argument "millis() - timestamp > interval" is re-evaluated
    *  and if false the function exits after that. */
    PT_WAIT_UNTIL(pt, millis() - timestamp > interval );
    timestamp = millis(); // take a new timestamp
    toggleLED();
  }
  PT_END(pt);
}
/* exactly the same as the protothread1 function */
static int protothread2(struct pt *pt, int interval) {
  static unsigned long timestamp = 0;
  PT_BEGIN(pt);
  while(1) {
    PT_WAIT_UNTIL(pt, millis() - timestamp > interval );
    timestamp = millis();
    toggleLED();
  }
  PT_END(pt);
}

void loop() {
  protothread1(&pt1, 900); // schedule the two protothreads
  protothread2(&pt2, 1000); // by calling them infinitely
}

I hope this example is easy to understand. I will post a more complex one the next days, dealing with input and output via the serial connection and some sort of calculation, all taking place in 'quasi'parallel. Maybe some sort of clock with the possibility to set it and some periodic actions like blinking a led and printing the actual time on the console.

7 Kommentare:

  1. Hi,

    first, very thanks for this tutorial,
    I am looking for a code which I can blink diference leds and start which one to blink by a pushbutton. I have two leds and two pushbuttons, When I push the first button, I want to blinking the first led and when a push again the first button I want to turnoff the blinking of first led. I want to control the two leds in the same way. Do you know how to do this?

    Thanks

    AntwortenLöschen
  2. Doing the following seems to be both clearer and easier to understand:

    #define LEDPIN 13

    void setup() {
    pinMode(LEDPIN, OUTPUT); // LED init
    }

    void toggleLED() {
    boolean ledstate = digitalRead(LEDPIN); // get LED state
    ledstate ^= 1; // toggle LED state using xor
    digitalWrite(LEDPIN, ledstate); // write inversed state back
    }

    void thread1(int interval) {
    static long next_time = 0; // remember the next_time for next action (note static)
    if(millis()<next_time) return; // if time has not arrived, return
    next_time += interval; // else update next_time by period
    toggleLED(); // and do the action
    }

    void thread2(int interval) {
    static long next_time = 0;
    if(millis()<next_time) return;
    next_time += interval;
    toggleLED();
    }

    void loop() {
    thread1(900);
    thread2(1000);
    }

    AntwortenLöschen
    Antworten
    1. Exactly! Frankly I don't see protothreading how is similar to multi-threading?! I hope someone explains to me please.

      Löschen
  3. Thanks!
    Nice and simple example for a complex issue :-)
    Can we avoid the doubling of code?
    I have similiar ideas where I want to control multiple identical hardware items (eg servos) concurrently.
    I see the requirement for keeping the persistent variables apart.
    Proposals:
    - instantiate them in the caller and hand a pointer
    - declare them as arrays and hand over an index
    Will this work?

    AntwortenLöschen
  4. ah, the array approach seems to work:

    static int protothread1(struct pt *pt, int interval, int cpy ) {
    static unsigned long timestamp[2] = { 0 } ;
    PT_BEGIN(pt);
    while(1) { // never stop

    PT_WAIT_UNTIL(pt, (int)(millis() - timestamp[cpy]) > interval );
    timestamp[cpy] = millis(); // take a new timestamp
    toggleLED();
    }
    PT_END(pt);
    }

    ....

    void loop() {
    protothread1(&pt1, 900, 0); // schedule the two protothreads
    protothread1(&pt2, 1000, 1); // by calling them infinitely
    }

    AntwortenLöschen
  5. alternatively with global array and reference - works, too:

    static unsigned long timestamp[2] = { 0 } ;
    ....
    static int protothread(struct pt *pt, int interval, unsigned long *tstp ) {
    ....
    PT_WAIT_UNTIL(pt, millis() - *tstp > interval );
    *tstp = millis(); // take a new timestamp
    ....
    void loop() {
    protothread(&pt1, 900, &timestamp[0] ); // schedule the two protothreads
    protothread(&pt2, 1000, &timestamp[1] ); // by calling them infinitely
    }

    AntwortenLöschen