bitty controller micro:bit code

Using C/C++ for 4tronix products

micro:bit code

Your micro:bit needs the right code on it to be able to respond correctly to the commands sent to it by the bitty controller app. What it does in response to the controller commands (e.g. Up, Left, Right etc) is entirely up to you. You could connect your micro:bit to all manner of things. But for our purposes, we will program the micro:bit to control a 4tronix Bit:Bot.

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!


controlling the 4tronix Bit:Bot

The 4tronix Bit:Bot has a BBC micro:bit connected to it via its edge connector. Switching specific edge connector "pins" on or off causes either or both of the motors which drive the Bit:Bot's wheels to be switch on and running either forwards or backwards, or stopped completely. This is a digital approach to controlling the motors. Alternatively, you can use analog writes to control the speed and direction of motors, giving fine-grained control rather than just on or off states.

Bit:Bot uses pins as follows:

PIN Purpose Digital Values Analog (PWM) Values
0 Left motor speed 0=stopped, 1=full speed 0-1023 where 0=always off, 1023=always and (e.g.) 511=50% on
1 Right motor speed 0=stopped, 1=full speed 0-1023 where 0=always off, 1023=always and (e.g.) 511=50% on
8 Left motor direction 0=forwards, 1=backwards N/A
12 Right motor direction 0=forwards, 1=backwards N/A

choices

There are at least a couple of different ways to create a system where a smartphone application is able to control something attached to the edge connector of a BBC micro:bit.

  1. The micro:bit's Bluetooth 'profile' includes something called the IO Pin Service and this allows a device like a smartphone to directly set the state of specific pins, just by sending the right types of message over Bluetooth. The micro:bit needs very little code for this approach to be used, other than to ensure the Bluetooth IO Pin Service is enabled. So in this option, the smartphone does nearly all of the work.
  2. We could alternatively send commands over Bluetooth in the form of special messages known as "events" from the smartphone to the micro:bit. The code on the micro:bit would then switch pins on or off according to the commands it receives and this way control the motors.
  3. 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.
  4. We have one special requirement which leads option (2) to be preferred. If the Bluetooth connection between the smartphone and the micro:bit "breaks" then we want the Bit:Bot to stop moving. So it's easier to give our micro:bit code the responsibility for controlling the pins on the edge connector. We also have the opportunity to do other things, like display certain messages in response to each type of command, if we want to. So option 2 it is then!

goals

Our micro:bit code needs to:

  1. Keep track of whether or not something is connected to the micro:bit over Bluetooth
  2. Respond to commands sent to it using the Bluetooth Event Service by writing appropriate values to pins according to the table above.

Note that in this tutorial, we'll focus on the code required to respond to the dual d-pad controller interface in bitty controller. The final solution will include support for the analog touchbad interface as well and hopefully you'll have learned enough by the time you see it to understand how it works. The source code solution is available for download too and it handles both of these user interface types. In addition, version 2.0 of bitty controller introduced support for sensor data, acquired by the micro:bit and transmitted over Bluetooth to the smartphone. You'll see how that works in the final solution too.


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 either install the bitty controller application on your phone or log in to BittyWeb so you have something you can use for testing whilst you develop the micro:bit code. Check the bitty controller 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 BitBotCtrlr.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 take control of your Bit:Bot.

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:

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 BitBotCtrlr.cpp in a text editor.


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 "Bit:Bot" now and save your text file.

                // Display a start-up message
                uBit.display.scroll("Bit:Bot");
                

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 "Bit:Bot" 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 controller uses Bluetooth for communication between the mobile app and the micro:bit which in our case is controlling the 4tronix Bit:Bot's motors. 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. For example if the connection is lost, we need to switch off both motors so that the Bit:Bot stops moving. Read all about keeping track of Bluetooth connections now and then come back here to resume the tutorial.


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

void onDisconnected(MicroBitEvent)
{
    uBit.display.print("D");
}   
and in the main() function you should 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);   

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 right here in your code.


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 controller 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 - pin state

We need to set pins 0,1, 8, and 12 to either 0 (off) or 1 (on) to control the Bit:Bot's motors. Having a variable for each of the 4 pins to store their current state in, will be useful. In addition, we need to know whether the Bit:Bot should be stopped, moving forwards or moving backwards. We'll use a variable called 'drive' which will take values 0, 1 and 2 for each of these states and we'll use variables fwd,bwd,lft and rgt to track the individual dimensions of motion and direction.

MicroBit uBit;

int connected = 0;

int pin0, pin8, pin1, pin12 = 0;
int fwd, bwd, lft, rgt = 0;

// 0=stopped, 1=forwards, 2=backwards
int drive = 0;

Making the Bit:Bot behave in a certain way, such as driving forwards or backwards, really just involves setting our variables and then writing those variable values to the micro:bit pins. To keep our code easy to read and to avoid repeating ourselves, we're going to write a number of functions which will set our variables for each of the important cases. We'll use these functions later on in our main control logic. Add the following functions under the variables you just created:

Add the following code somewhere outside of the main() function but make sure it is all above the onDisconnected functrion:

void resetPinsValues() {
  pin0 = 0;
  pin1 = 0;
  pin8 = 0;
  pin12 = 0;    
}

void dpadForwards()
{
    uBit.serial.printf("fwd\r\n");
    // left motor
    pin0 = 1;
    pin8 = 0;
    // right motor
    pin1 = 1;
    pin12 = 0;

    drive = 1;
}

void dpadBackwards()
{
    uBit.serial.printf("bwd\r\n");
    // left motor
    pin0 = 0;
    pin8 = 1;
    // right motor
    pin1 = 0;
    pin12 = 1;

    drive = 2;
}

void stop()
{
    fwd = 0;
    bwd = 0;
    resetPinValues();
    drive = 0;
}

void dpadLeft()
{
    lft = 1;
    rgt = 0;
    resetPinValues();
    if (drive == 1)
    {
        // left motor off
        pin0 = 0;
        pin8 = 0;
        // right motor on, fwd
        pin1 = 1;
        pin12 = 0;
    }
    else
    {
        // left motor off
        pin0 = 0;
        pin8 = 0;
        // right motor on, bwd
        pin1 = 0;
        pin12 = 1;
    }
}

void dpadRight()
{
    lft = 0;
    rgt = 1;
    resetPinValues();
    if (drive == 1)
    {
        // right motor off
        pin1 = 0;
        pin12 = 0;
        // left motor on, fwd
        pin0 = 1;
        pin8 = 0;
    }
    else
    {
        // right motor off
        pin1 = 0;
        pin12 = 0;
        // left motor on, bwd
        pin0 = 0;
        pin8 = 8;
    }
}

Study this code and compare it with the table of pin values above. Remember that the "drive" variable tracks whether we're moving forwards (1), backwards (2) or are stopped (0).

You probably noticed the use of the uBit.serial.printf function. This writes a string, possibly with a variable or two embedded inside it to the serial port. If you plug your micro:bit into a computer using USB and connect with a tool like Putty you can watch these messages. This is a really useful debugging technique.

Build your code with "yt build" just to make sure there are no compilation errors. Fix any errors that are reported before moving on. Errors are most probably caused by simple typos or by putting your code in the wrong place in the source code file. Most of the code we've added here should *not* be inside the main() function.


step 8 - setting pin values

The micro:bit software, known as the micro:bit "runtime" provides lots of useful functions. One set allow us to set the state of pins on the micro:bit's edge connector. Our next job is to write a function which will set pins 0, 1, 8 and 12 to the values contained within our pin variables. Add the following code just above the main() function.

                void writePinsDigital()
                {
                    uBit.serial.printf("P0:%d P1:%d P8:%d P12:%d\r\n", pin0, pin1, pin8, pin12);
                    uBit.io.P0.setDigitalValue(pin0);
                    uBit.io.P8.setDigitalValue(pin8);
                    uBit.io.P1.setDigitalValue(pin1);
                    uBit.io.P12.setDigitalValue(pin12);
                }
                

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


step 9 - stop when disconnected

If we lose our Bluetooth connection (e.g. if you close the bitty controller app) then we lose the ability to control our Bit:Bot! We don't want the Bit:Bot driving off on its own, out of control! So our next job is to ensure that this cannot happen. If Bluetooth disconnects, we'll use two of our new functions to stop the Bit:Bot immediately. Change your onBluetoothDisconnected function so that it looks like this:

                void onDisconnected(MicroBitEvent)
                {
                    connected = 0;
                    setConnectedIndicator();
                    stop();
                    writePinsAnalog();
                }
                

And add the setConnectedIndicator function somewhere above onConnected and onDisconnected:

                void setConnectedIndicator()
                {
                    if (connected == 1)
                    {
                        uBit.display.print("C");
                    }
                    else
                    {
                        uBit.display.print("D");
                    }
                }
                                

step 10 - handling bitty controller events

When pads on the bitty controller app screen are touched, the app will send "events messages" to the micro:bit it is connected to. We now need to program the micro:bit to listen for the right family of event messages and to respond to them.

micro:bit already has an event family (Event ID) set aside for controllers. Add the following code inside the main function after the code which listens for Bluetooth connected/disconnected events. This will cause events from the bitty controller app to be sent to our code, resulting in a new function "onDpadControllerEvent()" we'll write next being executed.

uBit.messageBus.listen(MES_DPAD_CONTROLLER_ID, 0, onDpadControllerEvent); 

Now let's make a start in the onDpadControllerEvent() function. This is where all the actions happens!

Add the following code above your main() function:

void onDpadControllerEvent(MicroBitEvent e)
{
    
  // MES_DPAD_BUTTON_1_DOWN = right hand pad, up
  // MES_DPAD_BUTTON_2_DOWN = right hand pad, down
  // MES_DPAD_BUTTON_C_DOWN = left hand pad, left
  // MES_DPAD_BUTTON_D_DOWN = left hand pad, right

}

Note the comments. The micro:bit runtime already defines variables (actually "constants" since the values do not vary) for specific controller actions like pressing the UP button on the right hand pad. We'll use these constants.


step 11 - forwards/backwards/stop

The buttons on the d-pad controller generate events, each with a distinct value for button pressed vs button releases. We can use these values to implement the logic required to determine which direction and speed each motor should be set to and then make this happen by using digital writes to the appropriate micro:bit pins. Here's the code required to accomplish this:

                void onDpadControllerEvent(MicroBitEvent e)
                {
                
                    // MES_DPAD_BUTTON_1_DOWN = right hand pad, up
                    // MES_DPAD_BUTTON_2_DOWN = right hand pad, down
                    // MES_DPAD_BUTTON_C_DOWN = left hand pad, left
                    // MES_DPAD_BUTTON_D_DOWN = left hand pad, right
                
                    uBit.serial.printf("dpad event %d\r\n", e.value);
                
                    if (e.value == MES_DPAD_BUTTON_1_DOWN)
                    {
                        fwd = 1;
                        bwd_fwd_lvl = 10;
                    }
                    if (e.value == MES_DPAD_BUTTON_1_UP)
                    {
                        fwd = 0;
                        bwd_fwd_lvl = 0;
                    }
                    if (e.value == MES_DPAD_BUTTON_2_DOWN)
                    {
                        bwd = 1;
                        bwd_fwd_lvl = -10;
                    }
                    if (e.value == MES_DPAD_BUTTON_2_UP)
                    {
                        bwd = 0;
                        bwd_fwd_lvl = 0;
                    }
                    if (e.value == MES_DPAD_BUTTON_C_DOWN)
                    {
                        lft = 1;
                        lft_rgt_lvl = -10;
                    }
                    if (e.value == MES_DPAD_BUTTON_C_UP)
                    {
                        lft = 0;
                        lft_rgt_lvl = 0;
                    }
                    if (e.value == MES_DPAD_BUTTON_D_DOWN)
                    {
                        rgt = 1;
                        lft_rgt_lvl = 10;
                    }
                    if (e.value == MES_DPAD_BUTTON_D_UP)
                    {
                        rgt = 0;
                        lft_rgt_lvl = 0;
                    }
                
                    if (e.value == MES_DPAD_BUTTON_1_DOWN)
                    {
                        dpadForwards();
                    }
                    else if (e.value == MES_DPAD_BUTTON_1_UP || e.value == MES_DPAD_BUTTON_2_UP)
                    {
                        stop();
                        setDirectionIndicators();
                        writePinsDigital();
                        return;
                    }
                    else if (e.value == MES_DPAD_BUTTON_2_DOWN)
                    {
                        dpadBackwards();
                    }
                
                    if (drive > 0)
                    {
                        if (e.value == MES_DPAD_BUTTON_C_DOWN)
                        {
                            dpadLeft();
                        }
                        else
                        {
                            if (e.value == MES_DPAD_BUTTON_D_DOWN)
                            {
                                dpadRight();
                            }
                            else
                            {
                                if (e.value == MES_DPAD_BUTTON_C_UP || e.value == MES_DPAD_BUTTON_D_UP)
                                {
                                    if (drive == 1)
                                    {
                                        dpadForwards();
                                    }
                                    else
                                    {
                                        dpadBackwards();
                                    }
                                }
                            }
                        }
                    }
                    setDirectionIndicators();
                    writePinsDigital();
                }
                

We haven't written the setDirectionIndicators() function yet. It's job is to use the LED display to visually depict the direction selected using bitty controller. Add this function somewhere above the previous code:

                        void setDirectionIndicators()
                        {
                            //    uBit.serial.printf("fwd:%d bwd:%d lft:%d rgt:%d bwd_fwd_lvl:%d lft_rgt_lvl:%d\r\n",fwd,bwd,lft,rgt,bwd_fwd_lvl,lft_rgt_lvl);
                            pattern.clear();
                            pattern.setPixelValue(2, 0, fwd * 25 * abs(bwd_fwd_lvl));
                            pattern.setPixelValue(2, 1, fwd * 25 * abs(bwd_fwd_lvl));
                        
                            pattern.setPixelValue(2, 3, bwd * 25 * abs(bwd_fwd_lvl));
                            pattern.setPixelValue(2, 4, bwd * 25 * abs(bwd_fwd_lvl));
                        
                            pattern.setPixelValue(0, 2, lft * 25 * abs(lft_rgt_lvl));
                            pattern.setPixelValue(1, 2, lft * 25 * abs(lft_rgt_lvl));
                        
                            pattern.setPixelValue(4, 2, rgt * 25 * abs(lft_rgt_lvl));
                            pattern.setPixelValue(3, 2, rgt * 25 * abs(lft_rgt_lvl));
                        
                            uBit.display.image.paste(pattern);
                        }
                

Study these two functions and make sure you understand the logic and how it relates to the function of the 4 pins we're using, as documented in the table earlier in this tutorial. Have a look at the micro:bit API documentation if you're not sure what some of this code does.


step 12 - final test

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


step 13 - 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 4tronix Bit:Bot, 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,
                        "security_level": "SECURITY_MODE_ENCRYPTION_NO_MITM",
                        "whitelist": 1,
                        "advertising_timeout": 0,
                        "tx_power": 7,
                        "dfu_service": 1,
                        "event_service": 1,
                        "device_info_service": 1
                    },
                    "gatt_table_size": "0x700"
                }
}

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 controller application and 4tronix Bit:Bot. You've also learned about micro:bit events, about Bluetooth connection tracking and about controlling the state of the electrical pins on the BBC micro:bit's edge connector! 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. Note that this is the *full* source code solution, which includes sensor sampling and touchpad control as well as the d-pad control we've been investigating.

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 controller app too.