C/C++ coding tutorial - bitty game controller

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 game controller app. What it does in response to the game 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 Kitronik Buggy.

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 Kitronik buggy

The Kitronik buggy 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 buggy's wheels to be switch on and running either forwards or backwards, or stopped completely. The different combinations of pin state and the actions they cause are summarised in the following table:

PIN 0PIN 8PIN 12PIN 16EFFECT
OFFOFFOFFOFFBuggy is stopped
OFFOFFONONBuggy drives forwards in a straight line
ONONOFFOFFBuggy drives backwards in a straight line
OFFOFFONOFFBuggy turns left in the forwards direction
OFFOFFOFFONBuggy turns right in the forwards direction
OFFONOFFOFFBuggy turns left in the backwards direction
ONOFFOFFOFFBuggy turns right in the backwards direction

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.

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.

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 buggy 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 switching on or off pins 0, 8, 12 or 16 according to the table above.

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 game controller application on your phone so you can use it for testing whilst you develop the micro:bit code. Check the bitty game 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 BuggyCtrlr.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 buggy.

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 BuggyCtrlr.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 "BUGGY" now and save your text file.

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

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 "BUGGY" 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 game controller uses Bluetooth for communication between the mobile app and the micro:bit which in our case is controlling the Kitronik buggy'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 buggy 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 game 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,8, 12, and 16 to either 0 (off) or 1 (on) to control the buggy's motors. Having a variable for each of the 4 pins to store their current state in, will be useful. It will also make life easier if we document the different pin value combinations and what they mean, in our code as a set of comments. In addition, we need to know whether the buggy 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. So let's take care of each of these issues now. Add the following lines of code under "MicroBit uBit;" which should already be there.

MicroBit uBit;
    
//                    Pin0   Pin8   Pin12  Pin16
// Stopped          : 0      0      0      0
// Forwards Straight: 0      0      1      1
// Forwards & Left  : 0      0      1      0
// Forwards & Right : 0      0      0      1
// Reverse Straight : 1      1      0      0
// Reverse & Left   : 0      0      0      0
// Reverse & Right  : 0      0      0      0

int pin0, pin8, pin12, pin16 = 0;

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

Making the buggy 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;
  pin8 = 0;
  pin12 = 0;
  pin16 = 0;    
}

void forwards() {
  pin0 = 0;
  pin8 = 0;
  pin12 = 1;
  pin16 = 1;
  drive = 1;
}

void backwards() {
  pin0 = 1;
  pin8 = 1;
  pin12 = 0;
  pin16 = 0;
  drive = 2;
}

void stop() {
  resetPinsValues();
  drive = 0;
}

void left() {
  resetPinsValues();
  if (drive == 1) {
      pin12 = 1;
      pin16 = 0;
  } else {
      pin0 = 0;
      pin8 = 1;
  }
}

void right () {
  resetPinsValues();
  if (drive == 1) {
      pin12 = 0;
      pin16 = 1;
  } else {
      pin0 = 1;
      pin8 = 0;
  }    
}

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).

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,8, 12 and 16 to the values contained within our pin variables. Add the following code just above the main() function.

void writeToPins() {
  uBit.io.P0.setDigitalValue(pin0);
  uBit.io.P8.setDigitalValue(pin8);
  uBit.io.P12.setDigitalValue(pin12);
  uBit.io.P16.setDigitalValue(pin16);
}

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 game controller app) then we lose the ability to control our buggy! We don't want the buggy 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 buggy immediately. Change your onBluetoothDisconnected function so that it looks like this:

void onDisconnected(MicroBitEvent)
{
    stop();
    writeToPins();
    uBit.display.print("D");
}

step 10 - handling bitty game controller events

When pads on the bitty game 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 game 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 game controller app to be sent to our code, resulting in a new function "onControllerEvent()" we'll write next being executed.

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

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

Add the following code above your main() function:

void onControllerEvent(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 game controller actions like pressing the UP button on the right hand pad. We'll use these constants.


step 11 - forwards/backwards/stop

The up and down buttons on the right hand game controller pad control whether we drive forwards, drive backwards or do not move at all. Up makes us move forwards, doiwn makes us move backwards and if neither of these buttons is pressed, the buggy should stop. Let's code these behaviours now. Add the following code to the onControllerEvent function under the comments:

if (e.value == MES_DPAD_BUTTON_1_DOWN) {
    forwards();
} else if (e.value == MES_DPAD_BUTTON_1_UP || e.value == MES_DPAD_BUTTON_2_UP) {
    stop();
} else if (e.value == MES_DPAD_BUTTON_2_DOWN) {
    backwards();
}

Note that the onEventController function has an 'argument' in its brackets. MicroBitEvent e is an event object stored in a special variable called 'e' and this is what tells our code which type of game controller event was received over Bluetooth from the bitty game controller app. The event object has a value we can extract using 'e.value' and it's this which will guide much of our logic here. So the code we've added checks the event.value and if the right hand UP button was pressed, we get ready to make the buggy go forwards. If DOWN was pressed, we get ready to go backwards. And if the event indicates that either the UP button or the DOWN button on the right hand pad was released (i.e. the user stopped pressing) then we prepare to stop the buggy.

Why "prepare to"? Why isn't this code actually doing what its name suggests? Well, if you review the forwards(), backwards() and stop() functions, you'll see that all we do at this stage is to set our pin and drive variables to certain values. We havn't yet written these values to the micro:bit edge connector pins which control the motors. We'll do this after we've considered the left hand pad and turning left or right.


step 12 - left/right/straight

The left and right buttons on the left hand game controller pad control whether we drive to the left, to the right or go in a straight line. In any case, we go in the direction controlled by the right hand pad and in fact if neither of UP or DOWN is pressed on the right hand pad, we don't move at all. Let's add code for these behaviours now. Add the following code to the onControllerEvent under the last code you added:

if (drive > 0) {
    if (e.value == MES_DPAD_BUTTON_C_DOWN) {
        left();
    } else {
        if (e.value == MES_DPAD_BUTTON_D_DOWN) {
            right();
        } else {
            if (e.value == MES_DPAD_BUTTON_C_UP || e.value == MES_DPAD_BUTTON_D_UP) {
                if (drive == 1) {
                    forwards();
                } else {
                    backwards();
                }
            }
        }
    }
}

Note that the first thing we check is the 'drive' variable. If it is zero then the buggy will not be moving at all so we don't care about left vs right vs straight on. Stopped means stopped. If it's 1 or 2 however, we do need to check for left vs right and forwards vs backwards so that we can set our drive and pin variables correctly using those handy functions we wrote earlier.


step 13 - action!

Our pin and drive variables should now have the right values according to whatever buttons the user was pressing or releasing on the bitty game controller app's main screen. All that's left is to write those values to the micro:bit's pins and this should cause our buggy to respond in the required way. How exciting! Add this final line of code after your last code. Make sure you add it inside the onControllerEvent functions' closing bracket.

writeToPins();  

For completeness and to allow you to check for mistakes, your onControllerEvent function should now look like this:

void onControllerEvent(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
    
  if (e.value == MES_DPAD_BUTTON_1_DOWN) {
      forwards();
  } else if (e.value == MES_DPAD_BUTTON_1_UP || e.value == MES_DPAD_BUTTON_2_UP) {
      stop();
  } else if (e.value == MES_DPAD_BUTTON_2_DOWN) {
      backwards();
  }
  
  if (drive > 0) {
      if (e.value == MES_DPAD_BUTTON_C_DOWN) {
          left();
      } else {
          if (e.value == MES_DPAD_BUTTON_D_DOWN) {
              right();
          } else {
              if (e.value == MES_DPAD_BUTTON_C_UP || e.value == MES_DPAD_BUTTON_D_UP) {
                  if (drive == 1) {
                      forwards();
                  } else {
                      backwards();
                  }
              }
          }
      }
  }

  writeToPins();  
}

step 14 - final test

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


step 15 - 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 game controller application and Kitronic buggy. 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.

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