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.