Knight Rider 2

The Knight Rider 2 is a port of the Knight Rider Experiment to the Arduino Due. Hence the 2 at the end of this experiment’s name. As we will see there is more to learn than one might expect.

Lets look at the pinout of the Due (thanks to Rob Gray aka Graynomad for the nice writeup). As it turns out the pins do not map 1:1 with the pins of the Arduino Uno. As a consequence the LEDs of the Blinkenlight Shield do not map 1:1 with pins of the Due.

So how do we fix this? Just replace all pin named variables in the original sketch by led named variables and add a pin mapping function in between.

//
//  www.blinkenlight.net
//
//  Copyright 2015 Udo Klein
//
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program. If not, see http://www.gnu.org/licenses/

uint8_t ledpin(const int8_t led) {
   return led<14? led: led+(54-14);
}

void setup() {
    for (uint8_t led=0; led<20; ++led) {
        pinMode(ledpin(led), OUTPUT);
    }
}

uint8_t brightness(const int8_t led, const int8_t pos) {    
    switch (abs(led-pos)) {
        case 0:     return 32;
        case 1:     return 16;
        case 2:     return 6;
        case 3:     return 2;
        default:    return 1;
    }
}

void pulse_width_modulation(const uint8_t pos) {
    for(uint8_t times=0; times<10; ++times) {
        for (uint8_t pass=0; pass<32; ++pass) {
            for (int8_t led=0; led<20; ++led) {
                digitalWrite(ledpin(led), (brightness(led, pos) > pass));
                
            }
        }
    }
}

void loop() {
    static uint8_t pos=0;

    while(pos<20) {
        pulse_width_modulation(pos);
        ++pos;
    }

    while(pos>0) {
        --pos;
        pulse_width_modulation(pos);
    }
}

Now that was easy. Till now there was not so much to learn. But hang on. The surprise will come soon. Obviously our new sketch will not work anymore with the UNO as it changes the pin mappings no matter which controller we have. We want to fix this. With the help of the c macro preprocessor this should not be to hard. First we need some macros to find out what the compile target actually is. How do we know the macros?

There are several ways. First one: ask the compiler about what it knows.


avr-gcc -dM -E -x c /dev/null

Or if we are searching for something that identifies Atmel controllers.


> avr-gcc -dM -E -x c /dev/null | grep -i avr
#define __AVR_2_BYTE_PC__ 1
#define __AVR_ARCH__ 2
#define __AVR 1
#define AVR 1
#define __AVR__ 1
#define __AVR_HAVE_16BIT_SP__ 1

In a similar way we can ask the arm compiler

> arduino-1.5.8/hardware/tools/gcc-arm-none-eabi-4.8.3-2014q1/bin/arm-none-eabi-gcc -dM -E -x c /dev/null | grep -i arm
#define __ARM_SIZEOF_WCHAR_T 32
#define __ARM_ARCH_ISA_ARM 1
#define __ARMEL__ 1
#define __ARM_FP 12
#define __ARM_NEON_FP 4
#define __ARM_SIZEOF_MINIMAL_ENUM 1
#define __ARM_PCS 1
#define __VERSION__ "4.8.3 20140228 (release) [ARM/embedded-4_8-branch revision 208322]"
#define __ARM_ARCH_ISA_THUMB 1
#define __ARM_ARCH 4
#define __arm__ 1
#define __ARM_ARCH_4T__ 1
#define __ARM_EABI__ 1

This tells about the architecture of the board. But of course there are also macros which depend on the target board. So how to find those? Well, lets look at the libraries. First lets look at the avr io libraries. I deleted a little bit of the output but you get the idea.


> grep 'defined (__AVR' /usr/lib/avr/include/avr/io.h | sed 's/^[^(]*(\([^)]*\))/\1/'
...
__AVR_ATmega644__ || defined (__AVR_ATmega644A__)
__AVR_ATmega644P__
__AVR_ATmega644PA__
__AVR_ATmega645__ || defined (__AVR_ATmega645A__) || defined (__AVR_ATmega645P__)
__AVR_ATmega6450__ || defined (__AVR_ATmega6450A__) || defined (__AVR_ATmega6450P__)
__AVR_ATmega649__ || defined (__AVR_ATmega649A__)
__AVR_ATmega6490__ || defined (__AVR_ATmega6490A__) || defined (__AVR_ATmega6490P__)
__AVR_ATmega649P__
__AVR_ATmega64HVE__
__AVR_ATmega103__
__AVR_ATmega32__
__AVR_ATmega323__
__AVR_ATmega324P__ || defined (__AVR_ATmega324A__)
__AVR_ATmega324PA__
__AVR_ATmega325__ || defined (__AVR_ATmega325A__)
__AVR_ATmega325P__
__AVR_ATmega3250__ || defined (__AVR_ATmega3250A__)
__AVR_ATmega3250P__
__AVR_ATmega328P__ || defined (__AVR_ATmega328__)
...

Another way is to look at some of the packaged libraries, e.g. the servo library.

#if defined(ARDUINO_ARCH_AVR)
#include "avr/ServoTimers.h"
#elif defined(ARDUINO_ARCH_SAM)
#include "sam/ServoTimers.h"
#else
#error "This library only supports boards with an AVR or SAM processor."
#endif

In the included libraries we find more information on how the compiler is told about the target board.

To not complicate matters to much lets pretend that all of the AVR based Arduinos share the pinout of the Uno (which is not true of course) and all the ARM based Arduinos share the pinout of the Due (also not true). So we make the pin mapper function platform dependend like below.

#if defined(__AVR__)
inline uint8_t ledpin(const int8_t led) {
   return led;
}
#else
uint8_t ledpin(const int8_t led) {
   return led<14? led: led+(54-14);
}
#endif

In theory this should now work on both the Uno and the Due. Also because the AVR version of the function gets inlined it will create no overhead in case we are dealing with an Uno. Nice. Except for the fact that this compiles for the Uno but fails with strange errors for the Due. To investigate this lets switch the compiler to verbose and navigate to the build directory. There we can see what the IDE does with our code. It is especially interesting to see what it does to our macro.

#if defined(__AVR__)
#include "Arduino.h"
inline uint8_t ledpin(const int8_t led);
uint8_t ledpin(const int8_t led);
void setup();
uint8_t brightness(const int8_t led, const int8_t pos);
void pulse_width_modulation(const uint8_t pos);
void loop();
#line 21
inline uint8_t ledpin(const int8_t led) {
   return led;
}
#else
uint8_t ledpin(const int8_t led) {
   return led<14? led: led+(54-14);
}
#endif

Arghh. The IDE generates stuff into the macro that would better be in front of the macro. So we need a way to prevent the IDE to generate stuff into our macro. In the past I already noticed several times that the IDE does not know about namespaces. So if we put this into a namespace everything should be fine. But I do not want to prefix all my pin mapper calls with a helper namespace. As it turns out this is not necessary. Well, an anonymous namespace can be declared by not giving a namespace name. Thus my final solution of this issue is

//
//  www.blinkenlight.net
//
//  Copyright 2015 Udo Klein
//
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program. If not, see http://www.gnu.org/licenses/

namespace {
#if defined(__AVR__)
inline uint8_t ledpin(const int8_t led) {
   return led;
}
#else
uint8_t ledpin(const int8_t led) {
   return led<14? led: led+(54-14);
}
#endif
}

void setup() {
    for (uint8_t led=0; led<20; ++led) {
        pinMode(ledpin(led), OUTPUT);
    }
}

uint8_t brightness(const int8_t led, const int8_t pos) {    
    switch (abs(led-pos)) {
        case 0:     return 32;
        case 1:     return 16;
        case 2:     return 6;
        case 3:     return 2;
        default:    return 1;
    }
}

void pulse_width_modulation(const uint8_t pos) {
    for(uint8_t times=0; times<10; ++times) {
        for (uint8_t pass=0; pass<32; ++pass) {
            for (int8_t led=0; led<20; ++led) {
                digitalWrite(ledpin(led), (brightness(led, pos) > pass));
                
            }
        }
    }
}

void loop() {
    static uint8_t pos=0;

    while(pos<20) {
        pulse_width_modulation(pos);
        ++pos;
    }

    while(pos>0) {
        --pos;
        pulse_width_modulation(pos);
    }
}

As you can easily verify this does the trick. Lets have a look at the generated cpp file to see why it works.

#include "Arduino.h"
void setup();
uint8_t brightness(const int8_t led, const int8_t pos);
void pulse_width_modulation(const uint8_t pos);
void loop();
#line 19
namespace {
#if defined(__AVR__)
inline uint8_t ledpin(const int8_t led) {
   return led;
}
#else
uint8_t ledpin(const int8_t led) {
   return led<14? led: led+(54-14);
}
#endif
}

Aha! The IDE is in fact dumb enough to not understand namespaces. So it leaves the namespace alone and generates its stuff before its definition. Exactly what we need.

Advertisement

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.