Getting started with C++ on the micro:bit v2 in Linux
Last updated: Nov 5, 2022
Aims
This post is about getting started with programming the micro:bit v2 using C++. I use the example code from the Lancaster University GitHub for this.
We will:
- Download the GitHub repository with the sample code.
- Build and load the default HelloWorld example to the micro:bit, which scrolls ‘HELLO WORLD!’ on the LEDs.
- Modify and build one of the other examples to use one of the micro:bit pins as an analog input. The LEDs display the voltage on this pin.
I tried to load one of the MICROBIT.hex files onto a v1 of the micro:bit with no success. This post is aimed at v2 of the micro:bit only.
Download the samples
Download the GitHub repository with the sample files produced by Lancaster University from their GitHub site here.
There are a couple of ways to do this. You can click on the green ‘Code’ button to download a zip file and unzip it, or you can use the ‘git clone’ command if you have git installed: git clone https://github.com/lancaster-university/microbit-v2-samples
Setting up for coding
The README.md file in the repository is pretty good. There are several packages that need to be installed on your Linux installation to be able to build the example code with. The maintainers of the repository use Ubuntu, so the package installation commands use the ‘apt’ package manager:
sudo apt install gcc
sudo apt install git
sudo apt install cmake
sudo apt install gcc-arm-none-eabi binutils-arm-none-eabi
gcc is the Gnu C compiler. This is used to compile the example C++ code with.
cmake is used for creating files that tell the system how to build the project.
gcc-arm-none-eabi is a version of gcc designed to run with the target hardware we are using on the micro:bit.
binutils-arm-none-eabi contains tools that build the .hex file that goes onto the micro:bit - the assembler and linker amongst other things.
If you have a different Linux distro, you may have a different command to install these packages with. I use Debian with the ‘aptitude’ package manager.
On the GitHub site and in the README.md file, there is some guidance on how to use a Dockerfile for installing the dependencies. I didn’t use this, so can’t comment.
You also need Python 3 installed, but odds on you already have this set up.
Directory structure layout
Like all GitHub repositories, the file layout seems really obvious to whoever created it, but might not be as clear to the rest of us.
In the top level directory, we have a file called build.py. This is the file that builds (clue is in the name) the MICROBIT.hex file that we will copy to our micro:bit. We will come back to this file in a moment.
In the source directory you find the file main.cpp. This is the file that we will work with. The samples directory contains - you guessed it! - the sample files that we will work with.
Confusingly, there is an empty directory also called samples in the top level directory. Ignore or delete this.
Hello World
The main.cpp file that comes with the GitHub repository is all set up with the classic Hello World example. All we have to do to get this running on our micro:bit is:
- Build the code to create a MICROBIT.hex file.
- Copy the MICROBIT.hex file to an attached micro:bit v2.
Building the code
Go to the top directory in the repository and type the command:
python build.py
After some seconds and a wall of text, the MICROBIT.hex file is created in the same directory as our build.py file.
This is the file that needs to be copied to our micro:bit.
Copying the MICROBIT.hex file to the micro:bit
One simple way to deploy the newly created MICROBIT.hex file to the micro:bit is to use a file browser, such as Nautilus. Connect the micro:bit to your PC and it should appear as a drive called MICROBIT. Double click on this to connect the micro:bit to your PC. Then drag and drop the MICROBIT.hex file onto this folder. The LED on the back of the micro:bit should flash for a few seconds and then the code will run. You will see ‘HELLO WORLD’ scroll across the LEDs on the front of the micro:bit.
There are slicker ways to transfer the hex file across though. Each time that the micro:bit has new code flashed to it, the connection to the PC is lost. This means that you need to double click on the MICROBIT drive each time you want to load a new hex file to connect the board, then drag over the newly created hex file. Wouldn’t it be nice to automate this, so that each time you create a new hex file, it is automagically loaded to the micro:bit?
Automating loading hex files to the micro:bit
I wrote a blog post about a script I created to find and mount a micro:bit after it is connected to my laptop’s USB port. Please find this post here. I aliased the script needed to find and mount an attached micro:bit in my .bashrc file as ‘mm’. In LInux, we can use the ‘inotifywait’ command to watch the MICROBIT.hex file we create when we build the C++ project for an update. When the hex file is updated, we can tell the system to mount the attached micro:bit and write the new hex file to the micro:bit. Here’s the command that I use:
while inotifywait -e close_write MICROBIT.hex ; do mm && cp ./MICROBIT.hex /media/myusername/MICROBIT ; done
You need to change ‘myusername’ to your user-name. You could, of course, put this into your .bashrc file with a suitable alias.
Analog input test example
The next test I did was to edit one of the sample files supplied in the ‘source/samples’ directory to display the voltage applied to pin 1 of the micro:bit. I am looking to interface a sensor with the micro:bit, so this is the first step in the project.
If we look in the samples directory, there are a plethora of example files. The one that we will work with is called GPIOTest.cpp. If you look through the example files, you may notice something. None of them have a main function. The main function is needed for a c++ file to execute - this is the first function that runs. Why is this?
When we run python build.py
from the root directory of the repository, it looks for a main function throughout the source directory. If there is more than one, the build crashes. I found this out the hard way. So we need to add a main function to our example file. Here’s what I did:
Move the sample files to a new directory under the root directory of the repository. mv ./source/samples samples_all
You need to move the samples directory the /source directory. We are going to copy, modify and rename one of these examples in the /source directory. When this modified file is built, the compiler or linker looks through all the sub-directories and will throw an error if the original file is still present in a sub-directory. This is as there will be duplicated subroutine names in the two files. I found this out the hard way.
I moved the file ./source/main.cpp to the samples_all directory and renamed it HelloWorld.cpp, to preserve a copy of the file for future reference. mv ./source/main.cpp ./samples_all/HelloWorld.cpp
Then I copied and renamed the GPIOTest.cpp file to the source directory: cp ./samples_all/GPIOTest.cpp ./source/main.cpp
Now we need to add a main function to the main.cpp file that runs the relevant analog testing function in the file. Looking through the file listing, there is a function called ‘analog_test()’ starting at line 57 which looks a good bet. Line 66 is:
int px = analogPins[0]->getAnalogValue() / 40;
The line numbers given here may change as the examples are occassionally updated.
Looks like analogPins[0] is the pin that is set up to read an analog input. So which pin is analogPins[0]?
Line 29 is:
static Pin *analogPins[] = {&uBit.io.P1};
So analogPins[0] is uBit.io.P1, which looks a lot like pin 1 on the edge connector. Worth a shot. I dug through the firmware files on the relevant repository to confirm this.
Add the following few lines to the end of your new main.cpp file:
int main() {
uBit.init();
analog_test();
}
This runs the analog_test() function.
Now we have to build the project to generate a new MICROBIT.hex file. Go to the root directory and type python build.py
.
We get an error!
undefined reference to `uBit'
Add this line after your #include statements, somewhere around line 6:
MicroBit uBit;
Also, line 2 of the file is:
#include "Tests.h"
We need to copy the file Tests.h from our samples_all directory to the source directory, alongside our main.cpp file. The other #include ...
files are found in the libraries folder in the repository.
After a wall of text, we should have a new MICROBIT.hex file, ready to load on to the micro:bit. After copying this file over, the LED on the back of the micro:bit will flash for a few seconds, then we should see about half of the LEDs on. This shows that the analog input is ‘floating’ at about half of the voltage of the power supply.
Common problems
Often, bits and pieces from previous builds interfere with a new build, throwing a variety of not-too-helpful error messages. This detritus can be cleaned by running the command: make clean
From the sub-directory: \build
Testing the analog input
So, how do we test the analog input? We can test the 0 and maximum input by connecting the ground pin to pin 1. I used a pair of tweezers to do this. Probably more sensible to use some alligator clips connected by a wire. With the ground connected to pin1, no LEDs are on. With the 3V connected to pin 1, all of the LEDs are on. So far so good.
I then tested the input using a 1/2Hz 3V sinusoidal input signal.I have an Analog Discovery 2 board made by Digilent. I programmed this to create the sinusoid and also to power the board. I connected this to the micro:bit using a Kitronik edge connector. If you are powering the micro:bit through the edge connector pins, be sure to only apply 3V. The micro:bit is only designed to take 5V through the USB connector. The little board starts to get quite hot quite quickly if you inadvertantly put 5V into the 3V pin on the edge connector!
To see a short YouTube video showing testing the analog input with the sinusoidal input please click on the picture below: