C/C++ coding tutorial - bitty mood_lighting

micro:bit code

Your micro:bit needs the right code on it to be able to respond to buttons being pressed and it being tilted to change the current colour on your tablet's screen.

As with all our apps you can simply download and install a hex file which we've prepared for you or you can code it yourself, following the Bitty Software coding tutorial below. Naturally, we recommend you code it yourself!


requirements

From the user's point of view, and without worrying yet how this will be accomplished, here's what we want:

  1. Button B - The user must be able to unlock the currently displayed colour, so that they can change it, by pressing micro:bit button B. Pressing button B while the colour is unlocked must return it to a locked state so that it can no longer be changed. When locking, the system must also save the newly selected colour.
  2. Button A - All colours on the screen are a mixture of 0 - 255 parts of red, green and blue. When the system has been unlocked by pressing button B, the user must be able to increase or decrease the red, green or blue part of the current colour. Pressing button A will switch the current colour component from red to green to blue and back to red again. The micro:bit display should indicate the current colour part which is selected by displaying "R", "G" or "B".
  3. Button A+B together - When buttons A and B are both pressed together, the system must go into "standby mode" and display a black screen. If it is already in standby mode when A+B are pressed, it should come out of standby mode and enter "active mode", displaying the last colour to have been selected.
  4. Left and right tilting - with the colour unlocked (pressing button B), tilting the micro:bit to the right (with the display facing up) should increase the selected colour part of red, green or blue while tilting to the left should reduce it. Tilting to a greater angle from the horizontal should make the rate of change proceed faster. Tilting less should make the change proceed more slowly to allow fine tuning.

choices

There are at least a couple of different ways to create a system which responds to the micro:bit buttons and to movement or tilting.

  1. The micro:bit's Bluetooth 'profile' includes "services" (like components or modules) called the Button Service and the Accelerometer Service. The Button Service can send messages over Bluetooth to a connected device whenever a button is pressed, held down for more than 2 seconds or released. The Accelerometer Service can send a stream of X,Y and Z values to describe the raw motion of the micro:bit. We could use these services and have the smartphone code do all the hard work to process that data and decide how to respond to it. The micro:bit would need very little code for this approach to be used, other than to ensure these two Bluetooth services are enabled.
  2. We could alternatively send information over Bluetooth in the form of special messages known as "events" from the micro:bit to the smartphone. The code on the micro:bit would indicate actions it wants the smartphone to take instead of sending it raw data about button presses or movement. To do this, the micro:bit code would need to process button and motion data and make decisions about what this should mean to the connected phone or tablet. The micro:bit does more of the work and the smartphone a bit less in this approach.

Alternative system designs like this, where we have to decide which part of our system will be responsible for which aspect of the logic, are known as the System Architecture.

There are pros and cons in each of these two approaches but option 2 lets you learn more about micro:bit coding. So we'll proceed with option 2!


goals

Our micro:bit code needs to:

  1. Keep track of whether or not something is connected to the micro:bit over Bluetooth
  2. Keep track of its own state, whether "locked" or "unlocked" and to change this state when button B is pressed.
  3. Cycle through the selected colour component of red/green/blue when button A is pressed and in the unlocked state (because button B was pressed).
  4. Send the connected smartphone data values which indicate which colour component should be changed and by how much, when in an unlocked state and the micro:bit is tilted more than, say +/- 5% from the horizontal
  5. Send the smartphone data which means "go into standby mode" or "go into active mode" when buttons A and B are pressed together.

before you start

To program your micro:bit using C/C++ you need the Yotta tools and the micro:bit 'DAL' source code on a PC or Mac. If you haven't yet set this environment up, read the information on our tools page and make sure you can build and install the Hello World sample code onto your micro:bit before going any further. If you've accomplished this then you have a working C/C++ environment for micro:bit development and can move on.

You should also install the bitty mood lighting application on your phone so you can use it for testing whilst you develop the micro:bit code. Check the bitty mood_lighting page for details of where to find the app for your type of phone or tablet.


step 1 - set up initial source code files

Download our project starter source code file to your microbit-samples\source folder. Delete any ".cpp" files you already had there. Rename the file to MoodLighting.cpp so it has a name that reflects what it does.

During the course of the tutorial, you'll create a micro:bit hex file which does not require Bluetooth pairing. At the end of the tutorial, when everything is working, we'll do one final build that does need your phone or tablet to pair with your micro:bit so that nobody else can connect to your micro:bit and stop you from controlling your tablet's colour!

Download this config.json file and save it in your microbit-samples folder. Rename it to config.json.

Your microbit-samples folder should now look like this:

And your microbit-samples source folder should look like this:

config.json includes various build properties that affects certain aspects of your micro:bit hex file once you have created it:

The property "open" which has a value of "1" means that your micro:bit will not need to be paired with your phone to be usable. This makes life easier during development but before you finish your project, remember to review this and if you decide you do want security then change the property to "0" and build again. You'll now need to pair your micro:bit with your phone.

Open MoodLighting.cpp in a text editor. It should look like this:

The lines which start with "//" are comments which are there to help you understand the code. Review them now. Don't worry if the final comment block about fibers makes no sense to you at this stage. The key point here is that when programming a micro:bit, finishing with a call to the release_fiber() function is good practice.


step 2 - change the start-up message

To get things started let's just change the start-up message which is displayed on the micro:bit to something more fitting. Change it to "LIGHTS" now and save your text file.

// Display a start-up message
uBit.display.scroll("LIGHTS");

step 3 - build and test

If you haven't already done so, find and launch the "Run Yotta" shortcut that should have been created for you when you installed Yotta. This will result in a window you can type commands into appearing. Use 'cd' to navigate to your microbit-samples folder.

Your micro:bit does not process C/C++ code directly. We have to translate it into a binary format which the micro:bit understands using a process called "building". Before we go any further, let's build and install your application now to make sure everything is working as expected. Follow the steps for building and installing your code and then return here and continue.

Tip: It's a good idea to progress by making small changes, building and testing at each step rather than typing huge amounts of code and then trying to build and test all in one go. It's much easier to solve problems when we're only making small changes each time.

Press the reset button on the back of your micro:bit. Your code will execute and you should see the text "LIGHTS" scroll across the LED display.


step 4 - micro:bit events

We're going to make extensive use of 'events' in this application so if you don't know what that means, read all about micro:bit events now and then come back here to resume the tutorial.


step 5 - keeping track of the bluetooth connection state

bitty mood lighting uses Bluetooth for communication between the micro:bit and mobile app. Before any communication can take place, the mobile device must connect to the micro:bit. Our micro:bit code needs to keep track of whether or not something is connected by Bluetooth to it and use this information in some of the logic we need to program. There's no point, for example, in trying to send the smartphone an event message meaning "go into standby mode" if it is not currently connected to the micro:bit. So change your code to include a variable with which to track connection state and event handlers for responding to changes in the Bluetooth connection state as shown below.

int connected = 0;

void onConnected(MicroBitEvent)
{
    connected = 1;
    uBit.display.print("C");
}

void onDisconnected(MicroBitEvent)
{
    connected = 0;
    uBit.display.print("D");
}
In the main() function you should also have:
// Application code will go here and in functions outside of main()
uBit.messageBus.listen(MICROBIT_ID_BLE, MICROBIT_BLE_EVT_CONNECTED, onConnected);
uBit.messageBus.listen(MICROBIT_ID_BLE, MICROBIT_BLE_EVT_DISCONNECTED, onDisconnected);   

This just says "if a Bluetooth connection is established or lost then please run the function onConnected or onDisconnected".

Apply what you learned about Bluetooth connection tracking to your code now. Listen for connect and disconnect events and use functions to display "C" or "D" for each of the two connection states. And use an int variable to record the current state too. In other words use *exactly* the code you just reviewed.


step 6 - checkpoint

Let's check that what we have at this point is working. Build your code and copy the hex file it created onto your micro:bit.

On your phone or tablet, launch the bitty mood lighting app. Press the Scan button. You should see your micro:bit appear, listed on the screen. Tap it and this will cause your phone to connect to it. The micro:bit should now display a letter 'C'. Press the Back button on the app and your phone will disconnect and the micro:bit show a letter 'D'.


step 7 - respond to the buttons

We want to respond to micro:bit buttons A or B being pressed separately or A+B being pressed together.

Add the following code after the lines where you listen for Bluetooth connection events:

uBit.messageBus.listen(MICROBIT_ID_BUTTON_A, MICROBIT_BUTTON_EVT_CLICK, onButton);
uBit.messageBus.listen(MICROBIT_ID_BUTTON_B, MICROBIT_BUTTON_EVT_CLICK, onButton);
uBit.messageBus.listen(MICROBIT_ID_BUTTON_AB, MICROBIT_BUTTON_EVT_CLICK, onButton);

We've said we're interested in 'click events' relating to buttons A, B and A+B and that in all three cases, the function onButton must be called if any one of these events occurs. Let's make a start on the onButton function.

Add the following code somewhere outside of the main() function:

void onButton(MicroBitEvent e)
{
    if (e.source == MICROBIT_ID_BUTTON_A) {
        uBit.display.scroll("BTN A");
    }

    if (e.source == MICROBIT_ID_BUTTON_B) {
        uBit.display.scroll("BTN B");
    }

    if (e.source == MICROBIT_ID_BUTTON_AB) {
        uBit.display.scroll("BTN AB");
    }
}

To begin with, we're just concerned with making sure those buttons are working for us. As you can see, at this stage, all we do is check the type of button event which has occurred and use this to show a simple message on the LED display. Once you've added this code, build, install and test by pressing button A, then button B, then pressing buttons A and B at the same time. You should see the expected messages scroll across the display.


step 8 - ignore buttons if there's nothing connected over Bluetooth

The micro:bit buttons should do nothing if no other device is connected to the micro:bit over Bluetooth, so we need some code which will test for this situation. Add the "if" statement shown below, as the first statement inside the onButton function. If a button is pressed when we don't have a Bluetooth connection, "NC", meaning "No Connection" will be displayed on the micro:bit display and the button(s) will otherwise be ignored.

void onButton(MicroBitEvent e)
{
    if (connected == 0) {
        uBit.display.scroll("NC");
        return;
    }

Check your code is OK by running "yt build".


step 9 - unlock / lock colour selection

Pressing button B should unlock the current colour selection so that we can change it by tilting the micro:bit or it should lock the current colour if we were already in a locked state. Let's code this aspect now.

First we'll set up a variable that will track whether we're in the "locked" or "unlocked" state. We'll need some other variables too so we'll set them up now and explain what they do as we use them later in the tutorial.

Add the following code near the top of your source code, after the "MicroBit uBit;" line:

int EVENT_ID = 9006;
int connected = 0;
int standby = 13;
int colour_unlocked = -1;
int standby_mode = -1;
int colour = 1;
int roll = 0;
int roll_event_value = 0;

In the colour_unlocked variable, we're using -1 to mean "locked" and 1 to mean "unlocked". This may seem an odd choice of values to choose but as you'll see, it actually make switching from unlocked to locked and back to unlocked again, very easy. Muliply -1 by -1 and you get 1. Multiple 1 by -1 and you get -1. So all we ever need to do is multiple our variable by -1 and we'll get the value representing the next state. Simple!

Make sure you start off with the correct, initial values and add the following code right after where you display "LIGHTS" in your main function:

colour_unlocked = -1;
standby_mode = -1;

So, when button B is pressed, we need to change the colour_unlocked variable as described. We also want to start things off with Red as the colour part which can be changed and we want to inform the connected smartphone or tablet that we've changed colour locked/unlocked states.

There's one more thing we need to do when button B is pressed. We need to make it possible for tilting the micro:bit to generate "events" which indicate which way the micro:bit is being tilted, and whether the angle it is tilted through requires a small, medium or large change to the current colour part. In itself, this is not difficult but there is a "gotcha" if we want to do this right.

What we need is a loop which checks the angle that the micro:bit is being tilted through very frequently, say every 100ms. It will then decide what this means in terms of small, medium or large colour changes and send a Bluetooth event to the connected tablet to communicate this information, so that the tablet can change the current colour. If we put this loop in the onButton function, which is our event handler for all button presses, we might cause delays in the handling of the event or in the checking of the micro:bit tilt angle. What we need to do is make sure that our "tilt loop" is running at the same time as our button handler, without the two interfering with each other.

This is actually quite an advanced topic called "concurrency" and micro:bit has various ways of approaching this issue, through what it calls "threading models". If you're curious you can read more about this at the Lancaster University micro:bit web site... but make sure you come back and finish this tutorial!

We're going to take a simple approach and explicitly run our tilt checking code in the background so that button presses can be processed independently and without interference. Add the following function which will run our tilt checking code:

void changeColour() {
    while (connected == 1 && colour_unlocked == 1) {
        roll = uBit.accelerometer.getRoll();
        if (abs(roll) > 5) {
            roll_event_value = 256 * colour + (roll - abs(roll % 15)) / 15 + 7;
            MicroBitEvent evt(EVENT_ID, roll_event_value);
        }
        uBit.sleep(100);
    }
}

As you can see, we have a "while loop" which will keep running as long as we have a Bluetooth connection and as long as we're in the "colour unlocked" state. If the Bluetooth connection drops or button B is pressed again so that the colour is once again locked, this while loop will exit and then the changeColour function as a whole.

In the loop, every 100ms, we measure the tilt angle, known as "roll" in micro:bit functions. The function "uBit.accelerometer.getRoll()" gives us the number of degrees from the horizontal that the micro:bit is tilted to. It's a positive value if tilted to the right and a negative value if tilted to the left.

We want to ignore very small tilt angles so that it's not too sensitive. The statement "if (abs(roll) > 5)" checks that the angle is at least 6 degrees. "abs" means "absolute". It's a function from a C/C++ library which takes any number, positive or negative and returns the number without the sign. In other words a positive number is returned unchanged but a negative number passed into abs like abs(-10) will result in the corresponding positive value being returned, in that example +10. In other words we're saying "is this number bigger than 5? And by the way, we don't care whether it's negative or positive".

To be able to use the abs function, we need to include the C/C++ library which contains it. We do this with a #include at the top of our source code. Add the code to include the stdlib.h library underneath the include for MicroBit.h which you should already have:

#include "MicroBit.h"
#include 

If the roll value is bigger than 5 degrees we want to send a Bluetooth event to the tablet running the bitty mood lighting application. We'll imagine that the 90 degrees we can tilt the micro:bit through, from horizontal to vertical, is divided into 15 degree slices and use this to calculate a number to send in our Bluetooth event. The number means "tilting a very little bit", "tilting a bit" through to "tilting a lot". In fact, in both tilt directions, we'll have 6 different amounts of tilt which we need to calculate and send as an event value so that the bitty mood lighting knows what to do. That's 12 different event values in total! This is what the following two lines of code do; calculate an event value in the range 1 to 12 (1 to 6 mean tilting to the left and 7 to 12 mean tilting to the right) and then send it as a Bluetooth event. Note that we're using our EVENT_ID variable here too:

roll_event_value = 256 * colour + (roll - abs(roll % 15)) / 15 + 7;
MicroBitEvent evt(EVENT_ID, roll_event_value);

So now, it's time to respond to button B being pressed.

Replace the if statement which handles button B being pressed with the following code:

if (e.source == MICROBIT_ID_BUTTON_B) {
    if (connected == 1) {
        colour_unlocked = colour_unlocked * -1;
        MicroBitEvent evt(EVENT_ID, 14);
        if (colour_unlocked == 1) {
            colour = 1;
            uBit.display.print("R");
            create_fiber(changeColour);
        } else {
            uBit.display.clear();
        }
    }
}

Here, we check the Bluetooth connection state and then if we're connected, send an event to the tablet to indicate that the colour lock state is changing. If the colour is now unlocked, we select red, represented by a value of 1 in the colour variable, to the current colour part and we start executing our tilt check code in the changeColour function in the background using the create_fiber function. A "fiber" is a mechanism for running some code concurrently with some other code. In the Lancaster University documentation, the term "thread" is sometimes used instead. Threads and fibers are very similar from our point of view.


step 10 - cycle through the selected R/G/B colour part

Button A lets us cycle through the current colour part of Red, Green, Blue and back to Red. Replace the if statement which handles button A in your onButton function with the following:

if (e.source == MICROBIT_ID_BUTTON_A) {
    if (connected == 1 && colour_unlocked == 1) {
        colour = colour + 1;
        if (colour == 4) {
            colour = 1;
        }
        if (colour == 1) {
            uBit.display.print("R");
        } else if (colour == 2) {
            uBit.display.print("G");
        } else if (colour == 3) {
            uBit.display.print("B");
        }
    }
}

Build and test your code now. You should be able to unlock with button B and then change colours by tilting and using button A.


step 11 - standby and active modes

All that's left now is to code for buttons A+B being pressed together so that we can tell the bitty mood lighting app to enter standby mode or come out of it, back into active mode. Replace your if statement for buttons A+B with the following:

if (e.source == MICROBIT_ID_BUTTON_AB) {
    if (connected == 1 && colour_unlocked == -1) {
        standby_mode = standby_mode * -1;
        MicroBitEvent evt(EVENT_ID, 13);
    }
} 

As you can see, we're using an event value of 13 to mean "swap between standby and active modes". Build and test your code now by connecting to your micro:bit from bitty mood lighting and then pressing buttons A+B together. The screen should go black when entering standby mode and return to whatever colour you had selected when your press the buttons again.


step 12 - reset variables when connected to

Our final, final, final step is to ensure that some of the key variables we use get reset to a suitable initial value every time the micro:bit is connected to over Bluetooth. This will ensure everything works properly from that point on because the variable values reflect the state that we want to have things in when "starting again". In the onConnected function, add the variable assignments as shown at the start of the function here:

void onConnected(MicroBitEvent)
{
    colour_unlocked = -1;
    standby_mode = -1;
    colour = 1;

    connected = 1;
    uBit.display.print("C");
}

step 13 - final test

If all has gone to plan, we should now have a completed micro:bit application for use with the bitty mood lighting application. Build, install and test to make sure everything is working.


step 14 - make Bluetooth pairing required

For this application it makes sense to use Bluetooth pairing. If you pair your micro:bit with your phone then nobody else will be able to connect to your micro:bit and take control of it! We don't want someone else driving off with your Kitronic buggy, do we?

Edit the config.json file in your microbit-samples folder and change it so that the 'open' property is set to 0 and the 'pairing_mode' property is set to 1, as shown here:

{
    "microbit-dal": {
        "bluetooth": {
            "enabled": 1,
            "pairing_mode": 1,
            "private_addressing": 0,
            "open": 0,
            "whitelist": 1,
            "advertising_timeout": 0,
            "tx_power": 6,
            "dfu_service": 0,
            "event_service": 1,
            "device_info_service": 1
        },
        "gatt_table_size": "0x600"
    }
}

Run 'yt clean' and then 'yt build' to rebuild your application in full and install the hex file on your micro:bit.

you've finished!

That's it! You've finished and are all set to have fun with the bitty mood lighting application. You've also learned about micro:bit events, about Bluetooth connection tracking, about using standard C/C++ functions like abs and about running code in the background in its own fiber. Congratulations!

if you get stuck

Check the steps above carefully and see if you can spot where things have gone wrong. If necessary, start again and proceed very slowly. building and testing at each step. Take a look at the solution and compare the code with yours.

If you did not install the correct config.json file in the microbit-samples directory or put it in the wrong place (e.g. in microbit-samples/source) then the app may not work because the pairing settings on the phone compared with the micro:bit may not match. Check the location and content of config.json. Try pairing your phone with your micro:bit. See our list of common issues for more information on this.

Make sure no other app on your phone is connected to your micro:bit. If you've been experimenting with other apps like nRF Connect, for example, this can easily happen. List the running apps on your phone and kill any that use Bluetooth, just to be sure. Kill and restart the bitty mood lighting app too.