Saturday, September 1, 2012

MSP430 Fading and other long running activities using TwoMsTimer instead of delay()

This video shows a simple test TI Launchpad test bed with an RGB LED tied to three PWM pins and the red LED that comes standard. The color fading and red LED flashing are independently controlled  with the code described below.

Source Code

Some folks just can't wait to open the source.  Here is the TwoMsTimer source along with the demo program used in this blog.

Overview

Micro-controller projects often include a mix of parallel tasks.  Handling those tasks with the right priority or interleaving can be a challenge when some of the tasks happen over long periods of time. Long is relative to the execution speed of the CPU in this case.

The simplest way to handle long running activities in a micro-controller is through the use of a loop that contains some type of delay to limit the loop's speed. One of the main problems with this type of code is that the  main loop() or central routines are blocked from doing anything else while in the delay block. The following code, written using the Energia development system for the MSP430 processors shows a system fading 3 LEDs on and off in sequence.

void loop()  { 
  while (true){
    for ( int i = 0; i < 3; i++){
      // fade in from min to max in increments of 5 points:
      // fade out from max to min in increments of 5 points:
      for(int currentBrightness = 255 ; currentBrightness >= 0; currentBrightness -=5) { 
        // sets the value (range from 0 to 255):
        analogWrite(pwmPins[i], currentBrightness);         
        // wait for 30 milliseconds to see the dimming effect    
        delay(30);                            
      } 
    
      for(int currentBrightness = 0 ; currentBrightness <= 255; currentBrightness +=5) { 
        // sets the value (range from 0 to 255):
        analogWrite(pwmPins[i], currentBrightness);         
        // wait for 30 milliseconds to see the dimming effect    
        delay(30);                            
      } 
    } // end pin loop
  } // end while
}
Lets say we want to do something besides fade the LEDs. We could insert other statements inside the for() or while() loop but we'd still end up with 60msec of "no activity" while we wait for the delay to finish.  Alternatives for handling this include
  1. Create some spin-loop-activity-delay method that can be called instead of the standard delay().  
  2. Create a timed interrupt that handles the long running task, fading, leaving the main loop for other activities. 
  3. Create a timed interrupt for other activities leaving the fading in the main loop.
For the purposes of this demo, I'm going to pick option 3 and flash an LED outside of the main loop using the TwoMsTimer library. Timed interrupts provide a way of taking action on relatively precise intervals without having to write complex loop timing / adjustment code. The code flashes a 4th LED one time per second without changing the main code loop. This code interrupt handler based on the TwoMsTimer library written for the MSP430. Notice that the TwoMsTimer function hides all the interrupt setup and interrupt vector code.  TwoMsTimer executes any function at up to 500 times a second. The maximum execution rate depends on the length of time it takes to execute the function and what percentage of the cpu needs to be reserved for other tasks that execute in the main run loop(). This code causes TI Launchpad LED1  to flash once per second independently the loop() code shown above.


void setup()  { 
  // open the hardware serial port
  Serial.begin(9600);
  pinMode(heartbeatPin, OUTPUT);
  TwoMsTimer::set(500, flash); // 500ms period
  TwoMsTimer::start();
} 

// interrupt handler passed to TwoMsTimer
void flash(){
  // log every time the handler is called should be every 500msec
  Serial.println("flash");
  heartbeatPinOn = !heartbeatPinOn;
  digitalWrite(heartbeatPin,heartbeatPinOn);
} 

Hardware

This project uses the TI Launchpad, an RGB LED, three 100 Ohm resistors and a USB cable to power the device. It is mounted in a Apple Ipod Nano shipping box.  The video shows the LED wearing it's Ping-Pong ball diffuser without the case cover on.


The sample code sends the word "flash" to the Serial console every 500 msec. You can see that my demo board is a version 1.4 Launchpad with the MSP430G2553 processor which requires the TX/RX lines be crossed on the jumper block.  I used 4 shorting blocks and stuffed wires in the blocks before pushing them onto the header.

Another Example

This is the same TI Launchpad board mounted in an old IPOD shipping box that has been painted. The RGB LED's diffuser is a ping pong ball.  In this case the main loop() is running a command processor that waits for serial commands.  The TwoMsTimer based interrupt handler drives the LEDs through a table based blinking pattern.  The interrupt handler also flashes LED2 on one second intervals as a heartbeat to help debug when pattern 0 (all off) is active. The firmware source is available on github.


Source code to a TFS Build watcher that uses this devices as a build status display can be found on github

The next two pictures show a similar build in a GameStop PS2 Gift Card tin.  The tin is actually three boxes folded together so Mr Dremel had to get involved to create a cavity large enough for the Launchpad.  I mounted the LED to the bottom so I could pull the lid on and off.  The big blob of hot glue under the LED is because I didn't think through that a spacer would have been a better way to raise u the LED.

Recap

Common micro control issues with a single run-loop include the following.
  1. Code uses Delay() or Wait() calls to space out or schedule different actions.  The problem is that these delays block anything else from happening during the pause periods.
  2. Activities need to occur on relatively precise intervals irrespective of how long the the task takes to process. Delay() and Wait() calls provide a fixed delay after a task has finished when we really want the spacing to be from task-start to task-start.  Developers have to adjust their pause or wait based on how long the intermediate code took to run.
  3. Some task or activity has higher priority than running on main-line or loop() and it is hard to time call-outs from the main loop to these tasks..
Timed or event driven interrupt handles can help break the code into independent pieces that are run "on demand" rather than as part of a loop. The TwoMsTimer package for the MSP430 environment makes coding for timed events simple.

The Energia wiki describes how to download the dev environment and compile the application. They also make available the USB drivers required to interface with a TI Launchpad if you haven't installed the native TI development tools.

3 comments:

  1. Hi Joe,

    I'm using energia in Mac OS and when I build the project I'm having this problem:

    woMsTimer.cpp: In function 'void TwoMsTimer::start()':
    TwoMsTimer.cpp:68:3: error: 'TA1CCR0' was not declared in this scope
    TwoMsTimer.cpp:69:3: error: 'TA1CTL' was not declared in this scope
    TwoMsTimer.cpp:70:3: error: 'TA1CCTL0' was not declared in this scope
    TwoMsTimer.cpp: In function 'void TwoMsTimer::stop()':
    TwoMsTimer.cpp:77:3: error: 'TA1CCTL0' was not declared in this scope
    TwoMsTimer.cpp: At global scope:
    TwoMsTimer.cpp:96:26: error: 'TIMER1_A0_VECTOR' was not declared in this scope
    TwoMsTimer.cpp: In function 'void Timer_A_int()':
    TwoMsTimer.cpp:97:6: error: interrupt vector offset must be an even non-negative integer constant

    It seems that original Timer0 Registers, such as TA1CCR0 are not recognized!!!!

    Any ideas to solve this issue?

    Best Regards

    ReplyDelete
    Replies
    1. Did you set the cpu type in energia preferences? That determines which timers are available. I wrote this for the 2553 processor.

      Delete
  2. hello Joe,

    thanks for the great library.

    consider me as a noob in MSP430 programming (2533 mcu), so I have some questions. your timer works great for considerable time delay, for example 10 or 15 seconds.

    1. is there any max limit (in seconds)? because in some programs I can run it at 20 s, but with others (which are larger in size), 20 s doesn't work anymore?

    2. it looks that your library uses some free space, is that correct? like, with smaller projects bigger delays work and vice versa ...

    3. I need the delay for example 30 s or 60 s, so I tried one more thing ... I'm running the interrupt at constant delay, lets says 10 s, which works for sure. and then I have one internal counter, which counts how many times interrupt occured - so if it occured once or twice, nothing happens, and when it occurs for the third time (if I want 30 s delay), some code is executed and that counter resets.

    4. but this also works for 30 s, also for 31, 32 and 33 s, but no more for 34 s ... and I'm not sure why?! since the interrupt is happening at constant delay ... any idea?

    I put my example code here: https://www.dropbox.com/s/yg0ykcfjjxy45zh/twomstimer_test.ino?dl=0

    thank you for help and best regards

    ReplyDelete