Tag Archives: microcontroller

Timed 1 millisecond interrupt routine for Arduino.

With Arduino (or AVR in general for that matter) you sometimes have the need to execute some code at regular intervals. A timed interrupt routine is the proper solution, but the Arduino IDE doesn’t come with standard code for this and it can be quite complicated for those who are unfamiliar with directly programming the hardware.

I created a simple proof of concept code that runs on AVR-type Arduino’s and uses a timed interrupt to time exact 1 ms, based on the 16MHz system clock. Thus 1000 counts equals 1 second with the same accuracy as the system clock. As proof the standard LED on pin 13 will start to blink in a 0.5Hz rhythm, one second on, one second off.

Timer0 and Timer1 are commonly used for the Arduino and third party libraries, so that leaves us with Timer2. Arduino’s system clock runs at 16MHz. Timer2 is clocked through a prescaler, this basically means that the system clock is first divided before it is fed to the Timer/Counter. I selected a 128 prescaler by configuring TCCR2B. This means the Timer/Counter is clocked at 16MHz / 128 = 125kHz.

Next I decided to make Timer/Counter2 compare to a set value 125 by setting OCR2A to 125 and configuring ‘Clear Timer on Compare’-bit in TCCR2A. This results in an exact 1kHz interrupt. TIMSK2 is configured to enable the Timer/Counter2 CompareA interrupt. Then all that is left is to attach the interrupt to some code, which is done with the ISR( TIMER2_COMPA_vect ) command. The Interrupt Service Routine counts to 1000 and so we arrive at 1 second ticks which can be used in the main loop to make the LED blink.

udev file for Texas Instruments LaunchPads

To be able to use the various devices on Linux as an unprivileged (non-root) user, it is required to configure udev in such a way that appropriate authorizations are assigned to it upon plugging in the device.

For both the MSP430 and the Stellaris LaunchPad I’ve developed a simple but safe udev rules file, based on its configured serial number. This means that even when more than one LaunchPad is attached to the system, it will get a unique identifier that survives reboots. Every LaunchPad supplied by TI has a unique serial number configured upon delivery.

Retrieving the serial number

To be able to create a unique device name that survives a reboot, it is essential to retrieve the serial number of the device. This can easily be done by following these instructions:

  1. Detach the LaunchPad from the system;
  2. On the command line type (this requires the expect-dev package being installed): unbuffer udevadm monitor --environment | grep 'ID_SERIAL='
  3. Plug in the LaunchPad. Notice that MSP430 takes about 10 seconds to fully register with the system.
  4. Output similar like this is displayed for MSP430:ID_SERIAL=Texas_Instruments_Texas_Instruments_MSP-FET430UIF_36FF49ABB1D22050
  5. Output similar like this is displayed for Stellaris:ID_SERIAL=Texas_Instruments_In-Circuit_Debug_Interface_0E10A714

Creating the udev rules file

With this knowledge we can build the the udev rules file:

ACTION!="add", GOTO="persistent_serial_end"
SUBSYSTEM!="tty", GOTO="persistent_serial_end"
KERNEL!="ttyACM[0−9]*", GOTO="persistent_serial_end"

IMPORT{builtin}="usb_id"
ENV{ID_SERIAL}=="Texas_Instruments_In−Circuit_Debug_Interface_0E10A714" , SYMLINK="stellaris−001" , OWNER="jhendrix" , MODE:="0600"
ENV{ID_SERIAL}=="Texas_Instruments_Texas_Instruments_MSP−FET430UIF_36FF49ABB1D22050" , SYMLINK="msp430−001" , OWNER="jhendrix" , MODE:="0600"
LABEL="persistent_serial_end"

  1. Edit lines 6 and/or 7 to contain the serial number found in the previous paragraph. Notice that only the part between double quotes needs to be edited. Of course you need as many lines as you own LaunchPads. I currently own two, so I have two lines.
  2. Where it says “jhendrix“, change that to your own username under which you will be working with the device.
  3. Save the file as /etc/udev/rules.d/60-ttyACM.rules

Using the the rules file

  1. Reload udev:
    sudo udevadm control --reload
  2. Unplug the LaunchPad;
  3. Plug the LaunchPad and wait a couple of seconds;
  4. Check the device file:ls -l /dev | grep ttyACM
    lrwxrwxrwx 1 root root 7 Feb 2 21:57 msp430-001 -> ttyACM1
    lrwxrwxrwx 1 root root 7 Feb 2 21:23 stellaris-001 -> ttyACM0
    crw------- 1 jhendrix dialout 166, 0 Feb 2 21:23 ttyACM0
    crw------- 1 jhendrix plugdev 166, 1 Feb 2 21:57 ttyACM1
  5. From this moment onward you can use the Launchpad under any of the devices listed:
    1. msp430-001 and stellaris-001 will survive a reboot;
    2. ttyACM0 and ttyACM1 are dynamic, they change according of attaching the devices.
  6. If the demo program is loaded that Stellaris comes with, you can access it from now on by typing:screen /dev/stellaris-001 115200

TI MSP430 LaunchPad Temperature Demo Application

 

When you receive a new Texas Instruments MSP430 LaunchPad, it comes with a small demo application installed so you can easily check its functionality. The interesting thing is that it also interfaces with the PC through a virtual serial interface, but reading the data from a Linux PC appears to be non-trivial. This is partially caused by the fact that the user’s guide states that the demo application can be read with any serial console application, which in practice just doesn’t always work.

Attaching the LaunchPad

When an MSP430 LaunchPad is attached to the system, dmesg logs a few lines similar like these:

[19:52:40] usb 2-2.3: new full-speed USB device number 85 using ehci_hcd
[19:52:40] usb 2-2.3: New USB device found, idVendor=0451, idProduct=f432
[19:52:40] usb 2-2.3: New USB device strings: Mfr=1, Product=2, SerialNumber=3
[19:52:40] usb 2-2.3: Product: Texas Instruments MSP-FET430UIF
[19:52:40] usb 2-2.3: Manufacturer: Texas Instruments
[19:52:40] usb 2-2.3: SerialNumber: 36FF49ABB1D22050
[19:52:40] cdc_acm 2-2.3:1.0: This device cannot do calls on its own. It is not a modem.
[19:52:40] cdc_acm 2-2.3:1.0: No union descriptor, testing for castrated device
[19:52:40] cdc_acm 2-2.3:1.0: ttyACM0: USB ACM device
[19:52:50] hid-generic 0003:0451:F432.003E: usb_submit_urb(ctrl) failed: -1
[19:52:50] hid-generic 0003:0451:F432.003E: timeout initializing reports
[19:52:50] hid-generic 0003:0451:F432.003E: hiddev0,hidraw3: USB HID v1.01 Device [Texas Instruments Texas Instruments MSP-FET430UIF] on usb-0000:00:1d.7-2.3/input1

According to line 9 of this logging, in this case the LaunchPad is attached to the system as ‘/dev/ttyACM0′.

Reading the data from MSP430 LaunchPad

For me only minicom (2400 8N1) was showing some data, but in contrast to what I understood from the user’s guide the received data is binary, not in ASCII.  I wrote a quick and dirty Perl script that easily allows for reading the data.

After attaching the LaunchPad and pressing button P1.3 on it, it starts to send data several times per second. Running the script below from the command line, outputs the current temperature.

Receiving serial data ...
22.2C 22.2C 22.8C 22.8C 22.8C 22.2C 22.8C 22.8C
22.2C 22.8C 22.8C 22.8C 22.2C 22.8C 22.8C 22.8C
22.8C 22.8C 22.8C 22.8C 22.8C 22.2C 22.2C 22.8C

The script is no rocket science, its main goal is to prove the LaunchPad is working properly and to act as a starting point for others.

#!/usr/bin/perl

use warnings;
use strict;

use Device::SerialPort;
use Time::HiRes qw( usleep );

my $PortName = '/dev/ttyACM0';
my $PortObj;
$| = 1;

print STDERR "Waiting for serial port ...\n";
while ( not( $PortObj = new Device::SerialPort ($PortName, 'false' ) ) ) {
print STDERR "Waiting: Cannot open serial port: $!\n";
sleep( 1 );
}

$PortObj->databits( 8 );
$PortObj->baudrate( 2400 );
$PortObj->parity( "none" );
$PortObj->stopbits( 1 );
$PortObj->handshake( "none" );

print STDERR "Receiving serial data ...\n";
my $newline = 8;
while ( 1 ) {
if ( not $newline ) {
# print 8 values per line
print "\n";
$newline = 8;
}
# try to read a byte of data
my ( $count, $data ) = $PortObj->read( 1 );
if ( ( defined( $count ) ) and ( $count != 0 ) ) {
# if data available, then convert from Fahrenheit to Celcius
printf( "%3.1fC " , ( ( ord( $data ) - 32 ) * 5 / 9 ) );
$newline--;
}
usleep( 100000 ); # wait 100ms
}

TI MSP430 LaunchPad from the Linux Command Line

Installing required packages

$ sudo apt-get install binutils-msp430 gcc-msp430 gdb-msp430 msp430-libc msp430mcu mspdebug

Debugger

mspdebug home page

$ mspdebug rf2500
MSPDebug version 0.19 - debugging tool for MSP430 MCUs
Copyright (C) 2009-2012 Daniel Beer This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

Trying to open interface 1 on 050
rf2500: warning: can't detach kernel driver: No data available
Initializing FET...
FET protocol version is 30394216
Configured for Spy-Bi-Wire
Set Vcc: 3000 mV
fet: FET returned error code 4 (Could not find device (or device not supported))
fet: command C_IDENT1 failed
fet: identify failed
Trying again...
Initializing FET...
FET protocol version is 30394216
Configured for Spy-Bi-Wire
Sending reset...
Set Vcc: 3000 mV
Device ID: 0x2553
Device: MSP430G2553
Code memory starts at 0xc000
Number of breakpoints: 2

Available commands:
= delbreak gdb load opt reset simio
alias dis help locka prog run step
break erase hexout md read set sym
cgraph exit isearch mw regs setbreak

Available options:
color gdb_loop iradix
fet_block_size gdbc_xfer_size quiet

Type "help " for more information.
Press Ctrl+D to quit.

(mspdebug) step
( PC: 0fcc6) ( R4: 09b66) ( R8: 0ffdf) (R12: 00000)
( SP: 0027e) ( R5: 0c7ff) ( R9: 0ff65) (R13: 0fd90)
( SR: 00000) ( R6: 0defc) (R10: 0e7ff) (R14: 00000)
( R3: 00000) ( R7: 0e157) (R11: 00298) (R15: 0ffff)
0xfcc6:
0fcc6: b2 40 6e fd 36 02 MOV #0xfd6e, &0x0236
0fccc: b2 40 6e fd 38 02 MOV #0xfd6e, &0x0238
0fcd2: b0 12 66 fd CALL #0xfd66

Simple rotary encoder controlled PWM dimmer

This post is basically a proof of concept for how to use a rotary (quadrature) encoder with an Arduino. Rotary encoders look a bit like classic potentiometers, but instead of changing resistance between its pins, it has two outputs with digital signals 90 degrees shifted with respect to each other. Because of this 90° phase difference between its outputs, it is relatively simple to determine in which direction it was turned as at any given time only one of its outputs will change. An example of the output sequence is given in the table below.

       Left   Right
Step   B A    B A
 a     1 1    1 1
 b     1 0    0 1 
 c     0 0    0 0
 d     0 1    1 0
 a     1 1    1 1
 ...

There are two ways of implementing a rotary encoder in an Arduino sketch. The first one simply polls the state of the input pins and checks the current state with the previous state. The other option is to use one encoder output as interrupt trigger and the other output to indicate direction.

Using pin state polling

In this example Arduino’s inputs are continuously polled and stored in currentState. When currentState is different from the previousState polled, then the rotary encoder has changed its position and the PWM value has to be updated. The if-then construct (lines 37-41 and 45-49) is used to decide whether the change implies an increase or a decrease. Then a second if-then (lines 42 and 50) prevents PWM from changing from 100% to 0% (and the other way around) by passing end of scale.

The code:

/*
This sketch implements a simple dimmer using a rotary encoder. The
encoder is connected to pins 22 and 23.
The standard led on pin 13 will be dimmed using pwm.
More on: https://blog.linformatronics.nl/58/electronics/simple-rotary-encoder-controlled-pwm-dimmer
*/

// Arduino Mega1280

const uint8_t pwmPin = 13;
const uint8_t encoderPinA = 22; // encoder input channel A
const uint8_t encoderPinB = 23; // encoder input channel B
uint8_t previousState = 0;
uint8_t analogValue = 128; // initialize at 50% PWM
const uint8_t stepSize = 2; // step size to increase PWM setting

void setup(){
analogWrite( pwmPin , analogValue );
pinMode( pwmPin , OUTPUT );
pinMode( encoderPinA , INPUT_PULLUP );
pinMode( encoderPinB , INPUT_PULLUP );
}

void loop() {
uint8_t currentState = ( digitalRead( encoderPinA ) << 0 ) | ( digitalRead( encoderPinB ) << 1 );

/*
State
B A
red yellow Digital
1 1 3
1 0 2
0 0 0
0 1 1
*/

if (
( ( previousState == 3 ) & ( currentState == 2 ) ) |
( ( previousState == 2 ) & ( currentState == 0 ) ) |
( ( previousState == 0 ) & ( currentState == 1 ) ) |
( ( previousState == 1 ) & ( currentState == 3 ) ) ) {
if ( analogValue <= ( 255 - stepSize ) ) {
analogValue += stepSize;
}
} else if (
( ( previousState == 1 ) & ( currentState == 0 ) ) |
( ( previousState == 0 ) & ( currentState == 2 ) ) |
( ( previousState == 2 ) & ( currentState == 3 ) ) |
( ( previousState == 3 ) & ( currentState == 1 ) ) ) {
if ( analogValue >= stepSize ) {
analogValue -= stepSize;
}
}

if ( previousState != currentState ) {
analogWrite( pwmPin , analogValue );
previousState = currentState;
}
}

It is fairly easy to adapt this code for any other Arduino board types. It doesn’t rely on any specific hardware, just requires two digital input pins and one pin that can drive PWM.  Just change encoderPinA and encoderPinB. Most Arduino boards have an on board LED on pin 13 which is used for PWM here.

Using interrupts

The second option to implement a rotary encoder is using interrupts. An interrupt can be used to temporarily stop execution of the main program to service an event (interrupt), after which the main program is being resumed. The trick used here is to use one of the encoder outputs as a clock event (both rising and falling edges) attached to the interrupt input and the other input as direction indicator.

All the main loop does in this example is setting PWM value from analogValue, where the interrupt routine increases/decreases this value. This only works because of the fact that both edges (rising and falling) can be used to trigger the routine. Whenever the interrupt routine is entered, both encoder inputs are read. When both inputs are different (LOW, HIGH) the analogValue is increased, if both inputs are identical the analogValue is decreased.

The interrupt driven version for an Arduino Mega:

/*
This sketch implements a simple dimmer using a rotary encoder. The
encoder is connected to pins 21 and 22.
The standard led on pin 13 will be dimmed using pwm.
More on: https://blog.linformatronics.nl/58/electronics/simple-rotary-encoder-controlled-pwm-dimmer
*/

// Arduino Mega1280

/*
Encoder state
B A
red yellow Numeric
1 1 3
1 0 2
0 0 0
0 1 1
*/

const uint8_t pwmPin = 13;
const uint8_t encoderPinA = 21; // encoder input channel A
const uint8_t encoderPinB = 22; // encoder input channel B
const uint8_t interruptChannel = 2; // pin 21, encoder input channel A
const uint8_t stepSize = 1; // step size to increase PWM setting

volatile uint8_t analogValue = 128; // initialize at 50% PWM

static void interruptHandler(){
delay( 10 ); // debounce
if ( digitalRead( encoderPinA ) != digitalRead( encoderPinB ) ) {
if ( analogValue <= ( 255 - stepSize ) ) {
analogValue += stepSize;
}
} else {
if ( analogValue >= stepSize ) {
analogValue -= stepSize;
}
}
}

void setup(){
analogWrite( pwmPin , analogValue );
pinMode( pwmPin , OUTPUT );
pinMode( encoderPinA , INPUT_PULLUP );
pinMode( encoderPinB , INPUT_PULLUP );

attachInterrupt( interruptChannel , interruptHandler , CHANGE );
}

void loop() {
analogWrite( pwmPin , analogValue );
delay( 100 );
}

Notice that the interrupt pins vary with Arduino board type, check its product page which exact pins you have to use and don’t forget to update the settings for interruptChannel and encoderPinA.