Sunday, February 8, 2015

Reading encoders with Arduino

I know that the encoders topic has been done to the death already (it seems it's next in line with the blink example) but I've nowhere seen it done the way I'll present here.

1. We know that 0b10 == 2, 0b11 == 3, 0b00 == 0 and 0b01 == 1 - those are just basic 2-bit values
2. We can put 2 values like that in a nibble (half-byte) so a uint8_t is more than able to house that value
3. We'll assume pins 2&3 (PC1 and PC0)

So the reading could be like so:

volatile uint8_t state = 0;
volatile int32_t counter = 0, oldCounter = 0;
// for full-stop encoder reads only use this state changes
volatile int8_t QEM[16]  = {
 0, 0,  0, 0, -1, 0, 0, 1, 1, 0, 0, -1, 0,  0, 0, 0
};
// for full quadrature decoding use this state changes
// volatile int8_t QEM[16]  = {
// 0, 1, -1, 0, -1, 0, 0, 1, 1, 0, 0, -1, 0, -1, 1, 0
// };

void setup() {
 // configure pin direction
 DDRC &= ~(1<<PC1);
 DDRC &= ~(1<<PC0);

 // enable pullups
 PORTC |= (1<<PC1) || (1<<PC0);

 // load initial encoder values
 if ((PINC & (1 << PC1)) != 0) state |= 0b00000010;
 if ((PINC & (1 << PC0)) != 0) state |= 0b00000001;
}

void loop() {
 // make space for current state; we just want the lower nibble
 // mask everything else out
 state = (state << 2) & 0x0F;

  // read the current state into the lower part of the nibble
 // is half a nibble a nib? ;)
 if ((PINC & (1 << PC1)) != 0) state |= 0b00000010;
 if ((PINC & (1 << PC0)) != 0) state |= 0b00000001;

 // At this stage the state variable contains 4 bits of information
 // containing the previous state and the new state that can be
 // directly used as an index - just the array needs to be a little
 // bit different.
 // 0b0010 = -1
 // 0b0001 =  1
 // 0b1000 =  1
 // 0b1011 = -1
 // 0b1110 =  1
 // 0b1101 = -1
 // 0b0100 = -1
 // 0b0111 =  1
 // which results in the array defined above

  // next we use the state value as an index to the array
 // we do this only on full encoder stops
 counter += QEM[state];

  // react on counter change
 if (oldCounter != counter) {
  oldCounter = counter;

   // counter changed - process
 }
}

Now this is all nice if your loops are quick but if they aren't you're going to loose precision. To get it back we need to change to interrupts - it's not that difficult!

volatile uint8_t state = 0;
volatile int32_t counter = 0, oldCounter = 0;
// for full-stop encoder reads only use this state changes
volatile int8_t QEM[16]  = {
 0, 0,  0, 0, -1, 0, 0, 1, 1, 0, 0, -1, 0,  0, 0, 0
};
// for full quadrature decoding use this state changes
// volatile int8_t QEM[16]  = {
// 0, 1, -1, 0, -1, 0, 0, 1, 1, 0, 0, -1, 0, -1, 1, 0
// };

ISR(PCINT1_vect) {
 // make space for current state; we just want the lower nibble
 // mask everything else out
 state = (state << 2) & 0x0F;

 // read the current state into the lower part of the nibble
 // is half a nibble a nib? ;)
 if ((PINC & (1 << PC1)) != 0) state |= 0b00000010;
 if ((PINC & (1 << PC0)) != 0) state |= 0b00000001;

 // At this stage the state variable contains 4 bits of information
 // containing the previous state and the new state that can be
 // directly used as an index - just the array needs to be a little
 // bit different.
 // 0b0010 = -1
 // 0b0001 =  1
 // 0b1000 =  1
 // 0b1011 = -1
 // 0b1110 =  1
 // 0b1101 = -1
 // 0b0100 = -1
 // 0b0111 =  1
 // which results in the array defined above

 // next we use the state value as an index to the array
 // we do this only on full encoder stops
 counter += QEM[state];
}

void setup() {
 // configure pin direction
 DDRD &= ~(1<<PC1);
 DDRD &= ~(1<<PC0);

 // enable pullups
 PORTC |= (1<<PC1) || (1<<PC0);

 // enable pin-change interrupts for PC1 and PC0
 PCICR |= (1<<PCIE2);
 PCMSK2 |= (1<<PCINT18) | (1<<PCINT19);

 // load initial encoder values
 if ((PINC & (1<<PC1)) != 0) state |= 0b00000010;
 if ((PINC & (1<<PC0)) != 0) state |= 0b00000001;

 // enable interrupts
 sei();
}

void loop() {
 // react on counter change
 if (oldCounter != counter) {
  oldCounter = counter;

  // counter changed
 }
}

As you can see the ISR-driven method isn't all that different from the polling one and gives a significant advantage in terms of flexibility and reliability.

Many thanks for dr Robert Paz for his series of lectures on Arduino programming!

Have fun!

Using the Arduino environment with Eclipse

I've recently fallen in love with the AVR MCUs especially because of the Arduino and it's hugely successful Arduino IDE. It seems that if there is a piece of hardware, a sensor perhaps, then Arduino has a library for it that you can use. It's just great!

There's however one small problem: you need the IDE to build it, the IDE is very small in capabilities (there's no code insight help like in Eclipse for example) and everything seems to be a bitt ... I don't know how to call it.. "amateur" is the word I guess... There's nothing wrong with doing things this way - unless you're used to using something more effective than the notepad for coding - like I am.

So.. You have the Arduino IDE, you have UNO board (possibly a cheep chinese clone) and you have done the Blink example to the death. Now it's time to do some serious damage!

We're going to need the following:

- a couple of packages (sudo apt-get install avrdude avr-libc gcc-avr make openjdk-7-jdk)
- Eclipse for C/C++ developers
- AVR plugin for Eclipse CDT
- Arduino 1.0.6 IDE (may work for a newer one, haven't checked it yet)

I'm going to assume you have downloaded Eclipse, installed the AVR plugin as instructed on the
AVR plugin page and extracted the Arduino IDE into the /tmp folder so you have the /tmp/arduino-1.0.6 location with all the things in it).

First we need to create a programmer to make Eclipse happy. To do so click Window -> Preferences, expand the AVR node and select AVRDude. In the list of programmers click Add, enter "Arduino" as the name of the programmer, select Arduino from the Programmer Hardware list, optionally if you're using a Nano or Mini enter the proper port (I for example have had to enter /dev/ttyUSB0 and I'm using an Arduino Nano with Optiboot) and click OK.

Next we need a project that will use the Arduino environment. Let's create one:

File -> New -> C++ Project
Enter project name, select "Makefile project" and then "AVR GCC Toolchain" and click "Finish".

Next bit is a bit tricky so you need to follow it to the letter. In your freshly created project create a folder called "arduino" and copy there the entire content of the following directories:

/tmp/arduino-1.0.6/hardware/cores/arduino
/tmp/arduino-1.0.6/hardware/variants/standard (that's actually just one file)
/tmp/arduino-1.0.6/libraries/Wire
/tmp/arduino-1.0.6/libraries/Wire/utility
(and any other library you'd like to use)

That'll give you a mini version of the environment to use. Next we need a way to build it. That's quite easy - you just drop in this Makefile into the arduino folder and type "make clean all" and you're done. When you want to use more libraries just drop their files in there and make clean all again.

One last thing to enable Eclipse to understand Arduino libraries. We need to define the symbol ARDUINO with value 106. To do this select Project -> Properties, then C/C++ Build -> Build Variables, click Add, enter "ARDUINO" into "Variable name" and "106" into "Value", click OK, then OK the properties dialog and you're all set!

Now for the fun part - let's create a blinker!

For that we need to create a new file, let's call it sketch.cpp (File -> New -> File, enter the name, press enter, done). In that file we'll enter the following:

#include

void setup() {
pinMode(13, OUTPUT);
}

void loop() {
delay(500);
digitalWrite(13, LOW);
delay(500);
digitalWrite(13, HIGH);
}

This is basic Arduino thing with the addition of one extra include file at the beginning of the file. Nothing more. Now to build that we'll need this Makefile in your project's folder.

Now all you need to do is select "Project -> Make target -> Build..." which will open a list of known targets. Click "Add" and enter "clean load" (that's the set of commands to build and upload the sketch to your board). Unfortunately due to a bug you'll have to close this window and re-open it to see the target you just added - do that and then select it and press "Build". And presto! your Arduino blinks!

There's an added bonus: you can now build your project without any IDE, for example using some sort of continuous integration or something - and ride like a pro! :)