C++ and BBC micro:bit - part 2

Making it lean

Now it’s time to review the code that made the Hello World example spinning.

Let’s open source/main.cpp file from our working copy of microbit-sample repository:

// some license info on top ...
#include "MicroBit.h"

MicroBit uBit;

int main()
{
    // Initialise the micro:bit runtime.
    uBit.init();

    // Insert your code here!
    uBit.display.scroll("HELLO WORLD! :)");

    // If main exits, there may still be other fibers running or registered event handlers etc.
    // Simply release this fiber, which will mean we enter the scheduler. Worse case, we then
    // sit in the idle task forever, in a power efficient sleep.
    release_fiber();
}

Looks simple and friendly.

Simple quick check with modification of the string in the .scroll method, rebuild, re-program - indeed does change the text displayed on the device’s LED matrix. Fine.

Let’s look at the files that are produced during the build. the content of the build/bbc-microbit-classic-gcc/source directory reveals:

total 3516
drwxrwxr-x 3 adam adam    4096 Jul 31 01:24 .
drwxrwxr-x 6 adam adam    4096 Jul 31 01:24 ..
drwxrwxr-x 3 adam adam    4096 Jul 31 00:10 CMakeFiles
-rw-rw-r-- 1 adam adam     909 Jul 31 00:10 cmake_install.cmake
-rw-rw-r-- 1 adam adam     826 Jul 31 00:10 CMakeLists.txt
-rw-rw-r-- 1 adam adam     371 Jul 31 00:10 CTestTestfile.cmake
-rwxrwxr-x 1 adam adam 2182996 Jul 31 01:24 microbit-samples
-rwxrwxr-x 1 adam adam   75048 Jul 31 01:24 microbit-samples.bin
-rw-rw-r-- 1 adam adam  494768 Jul 31 01:24 microbit-samples-combined.hex
-rw-rw-r-- 1 adam adam  211160 Jul 31 01:24 microbit-samples.hex
-rw-rw-r-- 1 adam adam  660782 Jul 31 01:24 microbit-samples.map

The microbit-samples.bin seem to be the complete program binary wee need to flash, and there is a microbit-samples.hex version of it. However, what we’ve been actually copying to the device is the -combined.hex counterpart. Inspection of toolchain.cmake file reveals what is the difference between the two: the -combined.hex one contains also NRF51822 bootloader prepended to it.

But wait. All what our program is doing is scrolling some text over LED matrix, and we’ve ended up with 75k binary?

By looking to build instuctions I can see that the sources are compiled with -Os option, so there is nothing like non-optimized build accidentally took place.

Let’s look to the microbit-samples.map file where we can see everything that landed in the final program image. When examining it, it becomes clear that entire microbit framework is linked-in.

Let’s take some shortcut and modify the main.cpp to get rid of useful but relatively big MicrBit object and have some selective includes:

#include "MicroBitMessageBus.h"
#include "MicroBitDisplay.h"
#include "MicroBitFiber.h"

MicroBitMessageBus messageBus;
MicroBitDisplay display;

int main()
{
    scheduler_init(messageBus);

    // Insert your code here!
    display.scroll("HELLO WORLD! :)");

    // If main exits, there may still be other fibers running or registered event handlers etc.
    // Simply release this fiber, which will mean we enter the scheduler. Worse case, we then
    // sit in the idle task forever, in a power efficient sleep.
    release_fiber();
}

Building it with yotta build goes without problems and we can successfully see the result in the binary directory:

total 1044
drwxrwxr-x 4 adam adam   4096 Jul 31 16:13 .
drwxrwxr-x 7 adam adam   4096 Jul 31 16:13 ..
drwxrwxr-x 3 adam adam   4096 Jul 31 00:10 CMakeFiles
-rw-rw-r-- 1 adam adam    909 Jul 31 00:10 cmake_install.cmake
-rw-rw-r-- 1 adam adam    826 Jul 31 00:10 CMakeLists.txt
-rw-rw-r-- 1 adam adam    371 Jul 31 00:10 CTestTestfile.cmake
-rwxrwxr-x 1 adam adam 449544 Jul 31 16:13 microbit-samples
-rwxrwxr-x 1 adam adam  12780 Jul 31 16:13 microbit-samples.bin
-rw-rw-r-- 1 adam adam 323512 Jul 31 16:13 microbit-samples-combined.hex
-rw-rw-r-- 1 adam adam  36011 Jul 31 16:13 microbit-samples.hex
-rw-rw-r-- 1 adam adam 264889 Jul 31 16:13 microbit-samples.map

Yeah, that’s far better now. The microbit-samples.bin went down to less than 13kB. Inspection of microbit-samples.map file shows that it’s hard to find any linked-in functions that are not directly related to the handling of scheduler or LED display in there.

Good, we can be selecting then. Just in case some precious Flash memory is required for a program.

Hey, C++11 is here

Inspection of compile flags settings in the CMake’s toolchain file, reveals:

...
set(CMAKE_CXX_FLAGS_INIT "${CMAKE_CXX_FLAGS_INIT} ${_CPU_COMPILATION_OPTIONS} -std=c++11 -fwrapv")
...

which means all C++ sources are compiled with GCC having C++11 features on.

Sweet!

Let’s modify our example so that the Hello World message is produced with some of C++11 syntax:

#include <array>
#include <algorithm>
#include "MicroBitMessageBus.h"
#include "MicroBitDisplay.h"
#include "MicroBitFiber.h"

MicroBitMessageBus messageBus;
MicroBitDisplay display;

// we're using C++11 initialiser list here
const std::array<ManagedString,3> words { "HELLO", "WORLD!", ":)"};
const std::array<ManagedString,3> wordsLowercase { "hello", "world!", ":)"};

int main()
{
    scheduler_init(messageBus);

    // using C++11 for syntax with auto
    for (auto& word: words)
    {
        display.scroll(word);
    }

    // and second pass using lambda
    std::for_each(wordsLowercase.begin(), wordsLowercase.end(), 
    			 [](const ManagedString& word) { display.scroll(word); });

    // If main exits, there may still be other fibers running or registered event handlers etc.
    // Simply release this fiber, which will mean we enter the scheduler. Worse case, we then
    // sit in the idle task forever, in a power efficient sleep.
    release_fiber();
}

Building goes without any problems and indeed the program executes as expected.

Being even leaner

Now, just for the sake of exercising, let’s try to go real bare-metal and exclude embed layer as well as micro:bit runtime, and let’s control the board using as little as needed to get access to built-in buttons and LED.

Our example will light one of the display’s LED on the button A key press and will turn it off after button is released. What can be more classic than that?

By looking into the concept page, it looks like we could be using stand alone nrf51-sdk library, and get a low-level access to available peripherals.

To handle LEDs as well as continuously poll state of the button, we’ll be using GPIO access. We will need to have some HW information collected first.

After peeking to schematics and the display documentation it looks like we could control the D1 LED, we need to put PO.13 o HIGH state and .04 to LOW state.

To read state of the button A, wee need to probe pin PO.17.

Lets write some code that does that (and only that) in the infinite loop. We’ll be including onlu nef_giop.h header and have few low-level calls made:

#include "nrf_gpio.h"

const uint32_t ROW1 = 13;       // PO.13
const uint32_t COL1 = 4;        // PO.04
const uint32_t BUTTON_A = 17;   // PO.17

int main()
{
    nrf_gpio_cfg_output(COL1);
    nrf_gpio_cfg_output(ROW1);

    // we need no pull-up, since there is one soldered to the board
    nrf_gpio_cfg_input(BUTTON_A, NRF_GPIO_PIN_NOPULL);

    // this pin will be always low, we'll control the ROW1 only
    nrf_gpio_pin_write(COL1, 0);

    // now, let's light the LED if the button A is pressed, clearing the LED otherwise
    while (true)
    {
        uint32_t isButtonUp = nrf_gpio_pin_read(BUTTON_A);
        nrf_gpio_pin_write(ROW1, !isButtonUp);
    }
}

Compiling with yotta produces a really tiny program image:

...
-rwxrwxr-x 1 adam adam   2064 Jul 31 18:54 microbit-samples.bin
...

Well done. We have now a tiny, truly bare-metal program, getting low-level control of the stuff.

Conclusion

I was pretty pleased with the experience I got with micro:bit. I’ve found it an inspiring little board. I quite like the fact it can be programmed ‘bold’ and high-level, e.g. using online visual block programming languages, as well as relatively high-level C++ with embed and MicroBit runtime, including C++11 feature set. Finally - low-level access is pretty convenient too, giving memory footprint squeeze down to a per-byte-level.

comments powered by Disqus