As I mentioned in a previous post, I am not a big a fan of the Arduino IDE. Since writing that, I’ve discovered I like it even less, because it does some ad-hoc mangling of what you write to turn it from a nearly-C language into genuine C. As a result, it is possible to write some C++ and get away with it, but whether C++ stuff works or not seems entirely random.
It may sound nuts to want to write C++ for a processor so small. But read on – it turns out you can do some nice things at essentially zero cost. But first you need a proper C++ toolchain. One way to get hold of it would be to install the Arduino IDE, in fact, since it uses it under the hood. But I did it from scratch on my FreeBSD system. This turns out to be mostly easy, but there were a couple of wrinkles worth writing down for the greater good.
Firstly, on FreeBSD the compiler is available as a port, so I just installed it with my favourite ports tool
portmaster devel/avr-gcc
(note that there are several variants based on different versions of the compiler available – this is the default variant which, at the time of writing, is based on gcc 4.2.4).
Sadly, although the libc part of the toolchain is available as a port, too, at the time of writing both versions (devel/avr-libc
and devel/avr-libc-devel
) are broken because they depend on defunct source code. So, I had to build this one by hand, starting with avr-libc-1.6.8.tar.bz2
from http://download.savannah.gnu.org/releases/avr-libc/. This is not too hard, just a slightly customised configuration followed by the usual make commands:
./configure --prefix=/usr/local/avr --host=avr
make
make install
Setting the prefix to /usr/local/avr
is advisable as some things get installed immediately below the prefix and so could conflict with native compilers and libraries. However, it does cause some things to end up in /usr/local/avr/avr
. Oh, well.
Next up, a test program is a good idea. avr-glibc comes with demo code, which can be found in /usr/local/avr/share/doc/avr-libc-1.6.8/examples/
, but none of it is particularly well suited to an Arduino. So, I stole the Makefile
from the demo
sample and used this code instead of demo.c
#include <avr/interrupt.h> #include <avr/io.h> #define FLASH PB5 // "Pin 13" (Arduino pin) - ATmega168 pin 19 #define CONTROL_PORT PORTB #define CONTROL_DDR DDRB static void ioinit(void) { CONTROL_DDR = _BV(FLASH); } int main(void) { long n; ioinit(); for( ; ; ) { CONTROL_PORT &= ~_BV(FLASH); for(n=0; n < 300000; ++n) ; CONTROL_PORT |= _BV(FLASH); for(n=0; n < 300000; ++n) ; } }
and modified the Makefile to remove optimisation (essential, otherwise the delay loops get optimised away), select the right CPU (atmega168
) and to modify these two lines
DEFS = -I /usr/local/avr/avr/include
LIBS = -B /usr/local/avr/avr/lib
-B
is a new flag to me: it specifies where binaries and the crt0
files are found. The last ingredient is a way to upload to the Arduino. The utility avrdude
can do this for you
avrdude -p m168 -P /dev/cuaU0 -c arduino -b 19200 -U flash:w:yourstuff.hex
Of course, delay loops are horrible, so my second attempt does this properly, using a timer interrupt. And this is where the C++ comes in: the “standard” way to set up the CPU is to write code like
#define FLASH PB5 // "Pin 13" (Arduino pin) - ATmega168 pin 19 #define CONTROL_PORT PORTB #define CONTROL_DDR DDRB static void ioinit(void) { // PWM, 10-bit, phase-correct TCCR1A = _BV(WGM10) | _BV(WGM11); // Pre-scaler set to 1024 TCCR1B = _BV(CS12) | _BV(CS10); // Set flash pin to output CONTROL_DDR = _BV(FLASH); // Enable timer 1 overflow interrupt TIMSK1 = _BV(TOIE1); sei(); }
which is pretty revolting and involves a lot of manual-reading to understand. So, as is my habit when dealing with hardware, I tried wrapping it up in nice C++ classes to see what the run-time cost is. I won’t show the C++ classes here as they’re quite verbose and are a work in progress, but the net effect on the setup code is that it now looks like this
#define FLASH PortB5 // "Pin 13" (Arduino pin) - ATmega168 pin 19 static void ioinit(void) { Control c; c.tc1.OC1ADisconnected(); c.tc1.OC1BDisconnected(); c.tc1.PWMPhaseCorrect10Bit(); c.tc1.Prescaler1024(); c.tc1.OverflowInterruptEnable(); FLASH::Out(&c); c.Set(); sei(); }
which I hope you’ll agree is much more readable. The amazing thing is that, despite the increased verbosity, there’s no cost at all: this produces almost exactly the same assembler as the original code (it is in a slightly different order, though even that could be fixed if needed). The wonders of optimisation.
Note, by the way, the use of a PWM mode is simply because the demo code I borrowed from actually did use PWM – but pin 19 (where the LED is on a standard Ardunio/Freeduino) isn’t a PWM pin, so my code just uses the timer interrupt to time when to turn the LED on or off. The PWM is not really needed but the timer has to be in some mode, so I haven’t yet bothered to figure out a more appropriate one.
When I’ve got more stuff encapsulated in C++ I’ll start sharing the code.