Article Index

The most basic task when working with the micro:bit is controlling the I/O lines. This isn't difficult if you use the framework provided but there some subtle points to watch out for. This chapter looks a the basics of using the GPIO.

Now On Sale!

You can now buy a print edition of micro:bit IoT in C.

You can buy it from:

USA and World  Amazon.com
Canada              Amazon.ca
UK                      Amazon.co.uk
France                Amazon.fr
Germany            Amazon.de
Spain                  Amazon.es
Brazil                  Amazon.br
Italy                    Amazon.it
Japan                 Amazon.co.jp
Mexico               Amazon.com.mx 

 

 The full contents can be seen below. 

Chapter List

  1. Getting Started With C/C++
    Anyone who wants to use the BBC micro:bit to its full potential as an IoT device needs to look outside the coding environments provided by its own website. As an mbed device, however, the micro:bit  is capable of being programmed in C/C++. Here we look at how to use the mbed online compiler for a simple demo program.

  2. Offline C/C++ Development  
    We have already discovered how to use the online editor to create a C/C++ program. Now we are going to move to the desktop with an offline approach. This has the advantage that we can use any tools we care to select and no Internet connection is needed.
  3. First Steps With The GPIO 
    The most basic task when working with the micro:bit is controlling the I/O lines. This isn't difficult if you use the framework provided but there some subtle points to watch out for. This chapter looks a the basics of using the GPIO.

  4. Working Directly With The Hardware - Memory Mapping. 
    The framework makes working with the GPIO and other devices as easy as it can be but there are many layers of software to go through before you get to the hardware. Writing directly to the hardware can make things up to ten times faster and give you access to things that their framework doesn't. It is also an educational experience to deal with the raw hardware directly.

  5. Pulse Width Modulation, Servos And More
    In this chapter we take a close look at pulse width modulation PWM including, sound, driving LEDs and servos.

  6. I2C
    The I2C bus is one of the most useful ways of connecting moderately sophisticated sensors and peripherals to the any processor. The only problem is that it can seem like a nightmare confusion of hardware, low level interaction and high level software. There are few general introductions to the subject because at first sight every I2C device is different, but here we present one.

  7. I2C Temperature Measurement
    Using I2C devices is fairly easy once you have successfully used one - and hence know what information you need and what to look for in a working system. In this chapter we use the HTU21D temperature and humidity sensor as a case study of I2C in action. It also happens to be a useful sensor.

  8. A Custom Protocol - The DHT11/22

  9. The DS18B20 - One Wire Bus

  10. The SPI Bus
    The SPI bus can be something of a problem because it doesn't have a well defined standard that every device conforms to. Even so if you only want to work with one specific device it is usually easy to find a configuration that works - as long as you understand what the possibilities are. 

  11. SPI MCP3008/4 AtoD   
    The SPI bus can be difficult to make work at first but once you know what to look for about how the slave claims to work it gets easier. To demonstrate how its done let's add eight channels of 12 bit AtoD using the MCP3008.

  12. Serial Connections
    The serial port is one of the oldest of ways of connecting devices together but it is still very, very useful. The micro:bit has a single serial interface but it can be directed to use any of the GPIO piins as Rx and Tx. 

  13. WiFi 
    The micro:bit has a radio that works in Bluetooth LE and point-to-point ad-hoc mode, but at the moment it lacks WiFi connectivity. The solution is to use the low cost ESP8266 to make the connection via the micro:bit's serial port. 

  14. LED Display 
    The micro:bit's LED display may only be 5x5 but it is very versatile. If you want to make use of it directly then you are going to have to master some lower level functions.

 

The micro:bit has 32 General Purpose I/O GPIO lines, although not all of them are available for use. Finding out how to use them is an important first step and you have to master both the hardware and the software aspects. 

Warning

Despite the ease of making connections to the micro:bit using ad-hoc cables and clips the GPIO lines are very low power and sensitive to voltages and currents outside of their normal range.
Do not connect anything to the micro:bit, especially for output, without considering the voltage and current applied to the GPIO line.

The voltage should always be 3.3 or less and the current should be 0.5mA or less.  

Pin Outs

The big irritation with working with any device that has GPIO lines is what do you call them. The manufacture of the electronic will have given them names like GPIO 01, GPIO 02 and so on but when the processor is used in a finished product like the micro:bit these lines are brought out to physical pins or connections on the printed circuit board and these are then often called Pin01, Pin02 and so on. The point is that there is no need for Pin01 to correspond to GPIO 01 and indeed in most cases pin numbers and GPIO numbers are fairly independent.

Why does this matter?

The main reason is that the low level documentation provided by the chip manufacturer generally uses GPIO numbers and the documentation provided by the device manufacture generally uses Pin number.

You can see the potential for confusion and mistakes. 

The framework functions all expect you to specify the GPIO number of the line you want to use but it provides constants that map Pin number to GPIO number. 

The definition of the constants can be found in MicroBitPin.h and as you can see there is no numerical logic in the assignments even more so when you remember that the pins on the micro:bit's edge connector are not sequential!

Pin                                GPIO
​MICROBIT_PIN_P0                    P0_3
MICROBIT_PIN_P1                    P0_2
MICROBIT_PIN_P2                    P0_1
MICROBIT_PIN_P3                    P0_4
MICROBIT_PIN_P4                    P0_5        
MICROBIT_PIN_P5                    P0_17
MICROBIT_PIN_P6                    P0_12
MICROBIT_PIN_P7                    P0_11
MICROBIT_PIN_P8                    P0_18
MICROBIT_PIN_P9                    P0_10
MICROBIT_PIN_P10                   P0_6
MICROBIT_PIN_P11                   P0_26
MICROBIT_PIN_P12                   P0_20
MICROBIT_PIN_P13                   P0_23
MICROBIT_PIN_P14                   P0_22
MICROBIT_PIN_P15                   P0_21
MICROBIT_PIN_P16                   P0_16
MICROBIT_PIN_P19                   P0_0 
MICROBIT_PIN_P20                   P0_30

The P0_n constants are the actual GPIO numbers and these are defined in PinNames.h as an enumeration with P0_0=0, P0_1=1 and so on. 

This would be the end of the story if it wasn't for the fact that some of the GPIO lines are used for specific jobs within the micro:bit. The standard pin out diagram is:

 

As you can see most of the GPIO lines already have jobs. If you want to use them for your own purposes then what ever they are doing for the micro:bit has to be turned off. For example if you use any of the GPIO lines that are assigned to the LED display then you cannot use the LED display and if you do then this will take over the GPIO lines that you are nominally using. 

The only GPIO lines that are completely free to use are

Free GPOIO Pins 
MICROBIT_PIN_P0
MICROBIT_PIN_P1
MICROBIT_PIN_P2
MICROBIT_PIN_P8
MICROBIT_PIN_P16 

If you are prepared not to use the LED display then you can add to this list:

LED Display Pins
MICROBIT_PIN_P3
MICROBIT_PIN_P4
​MICROBIT_PIN_P6
MICROBIT_PIN_P7
MICROBIT_PIN_P9
MICROBIT_PIN_P10

If you are prepared not to use Button A and B you can add:

Button Pins
MICROBIT_PIN_P5
MICROBIT_PIN_P11

In most cases you probably don't want to make use of the SPI bus so you can to the list:

SPI Bus Pins
MICROBIT_PIN_P13
MICROBIT_PIN_P14
MICROBIT_PIN_P15

Finally we have three pins dedicated to the I2C bus which you can use but you need to know that these are connected to the accelerometer and magnetometer which you can use if you take them over. Notice that you can use them for your devices on the I2C bus without disabling the on board devices:

I2C Bus Pins
MICROBIT_PIN_P19
MICROBIT_PIN_P20

There are also some GPIO lines used internally and not brought out onto the edge connector. 

For example as well as six pins on the edge connector the LED display makes  use of:

LED MATRIX COLS
    COL1 = p4,
    COL2 = p5,
    COL3 = p6,
    COL4 = p7,
    COL5 = p8,
    COL6 = p9,
    COL7 = p10,
    COL8 = p11,
    COL9 = p12,

LED MATRIX ROWS
    ROW1 = p13,
    ROW2 = p14,
    ROW3 = p15,

The USB serial interface uses:

   RX AND TX PINS
    TGT_TX = p24,
    TGT_RX = p25,

Finally three GPIO lines are used as interrupt lines from the accelerometer and magnetometer:

    ACCEL INTERRUPT PINS (MMA8653FC)
    ACCEL_INT2 = p27,
    ACCEL_INT1 = p28,

    MAGNETOMETER INTERRUPT PIN (MAG3110)
    MAG_INT1 = p29,

Notice that while you can't use any of these internal GPIO lines for your own devices knowing what they control can still be useful. 


GPIO Capabilities

Another complication is that GPIO lines can be assigned to other internal devices within the processor. For example, the serial hardware (UART) can make use of any two GPIO lines for its RX and TX pins.

There is also a single AtoD converter device and this can be connected to any of a subset of GPIO lines that then act as analog inputs. To create analog outputs the Pulse Width Modulation (PWM) capabilities of some of the GPIO lines is used - see the chapter on PWM. By pairing the AtoD with the DtoA provided by the PWM some of the pins are designated as analog capable. 

Analog GPOIO Pins 
MICROBIT_PIN_P0
MICROBIT_PIN_P1 
MICROBIT_PIN_P2
MICROBIT_PIN_P3
MICROBIT_PIN_P4
MICROBIT_PIN_P10

Notice that pins 3, 4 and 10 are involved in driving the LED display and this allows for some interesting possibilities such as using the array as a light sensor. 

In the case of the micro:bit there is also a GPIO mode that allows you to use the pin as a debounced high impedance input on pins P0, P1 and P2. This makes it easy to use things like fruit and other objects as creative input devices.

Digital GPIO

With all of these possiblities using the GPIO lines can seem quite complicated. For simplicity, and because it is the most common way they are used, we will concentrating on their use as simple digital I/O lines and forget, for the moment analog, PWM and touch outputs and their use as I2C and SPI buses all of which are covered in later chapters.  

The basic interface to the GPIO lines is the MicroBitPin object.  You can create and use MicroBitPin objects as needed or you can use the MicroBitPin objects created on the uBit object. If you create your own MicroBitPin objects create them as global objects and do not create a uBit object. 

For example you can use to access Pin 0 you can either use

#include "MicroBit.h"  
MicroBit uBit;

main(){ 
    uBit.init();
    uBit.io.P0.setDigitalValue(1);
}

or

#include "MicroBit.h"
main(){
 MicroBitPin P0(MICROBIT_ID_IO_P0, MICROBIT_PIN_P0, PIN_CAPABILITY_ALL);
 P0.setDigitalValue(1);
}

Don't worry about the MicroBitPin constructor or the setDigitalValue function. All that matters at the moment is that you see the two ways of getting at a GPIO line.

If you are not troubled by resource shortages then use the uBit object approach because it frees you from having to instantiate a MicroBitPin object for each pin. If you are having resource problems then only instantiating the objects you are going to use is a better strategy. 

Notice that when you work with the uBit object the GPIO lines are just referred to as P0, P1 and so on. When you work with the MicroBitPin object directly the lines are referred to as MICROBIT_PIN_P0 etc. Be careful not to confuse the two naming systems.

If you want to create MicroBitPin objects then the constructor allows you to specify an event id, pin name and pin capability - i.e. what you are going to use the pin for. 

MicroBitPin(int id,PinName name, PinCapability capability)

In most cases you use the message bus id provided for you. PinCapability is one of:

(PIN_CAPABILITY_DIGITAL, PIN_CAPABILITY_ANALOG, PIN_CAPABILITY_AD, PIN_CAPABILITY_ALL)

Compared to other GPIO libraries the strangest thing about the micro:bit's framework is that it doesn't have functions that set the direction of a GPIO line. Instead the direction is set the first time you try to read or write a line. To write a 0 or a 1 to the line you would use:

int setDigitalValue(int value)

and to read a line:

int getDigitalValue()

This approach has the advantage of not needing much in the way of initialization but if the line isn't already set up correctly for reading or writing then it takes extra time to configure it. What this means is that the first time you read or write a line it can take longer than the second and subsequent use. As a result it is sometimes necessary to do an extra read or write to the line before you start using it as a way of setting things up.

Apart from these two main function there are a few useful utilities. You can test to see if a GPIO line is currently a digital line and if it is  an input or an output:

int isDigital()
int isInput()
int isOutput()

 

Drive Type

The GPIO output can be configured into one of a number of modes but the most important is pull-up/down. 

Before we get to the code to do the job it is worth spending a moment explaining the three basic output modes.

In pushpull mode two transistors of opposite polarity are used, for example:

The circuit behaves like the two-switch equivalent shown on the right. Only one of the transistors, or switches is "closed" at any time. If the input is high then Q1 is saturated and the output is connected to ground - exactly as if S1 was closed. If the input is low then Q2 is saturated and it is as if S2 was closed and the output is connected to +V.

You can see that this pushes the output line high with the same "force" as it pulls it low. 

This is the standard configuration for a GPIO output.

The pullup mode replaces one of the transistors by a resistor:

 

In this case the circuit is equivalent to having a single switch. When the switch is closed the output line is connected to ground and hence driven low. When the switch is open the output line is pulled high by the resistor. 

You can see that in this case the degree of pulldown is greater than the pullup, where the current is limited by the resistor. The advantage of this mode is that it can be used in an AND configuration. If multiple gpio or other lines are connected to the output, then any one of them being low will pull the output line low. Only when all of them are off does the resistor succeed in pulling the line high.

This is used, for example, in a serial bus configuration like the I2C bus. 

Finally the pulldown mode is exactly the same as the pullup only now the resistor is used to pull the output line low:

In the case of the pulldown mode the line is held high by the transistor but pulled low by the resistor only when all the switches are open. Putting this the other way round - the line is high if any one switch is closed. 

Generally speaking the pushpull mode is best for driving general loads, motors, LEDs, etc.

The pullup/down modes are used where you need to create a serial bus of some sort or when the load needs this sort of drive. 

To set the mode for a GPIO line the function to use is:

int setPull ( PinMode pull)

where pull is any of:

PullUp
PullDown
PullNone

PullNone is the default and the value of the pull up/down resistor is in the range 10 to 16K. What this means is that in most cases you will probably have to add an external pull up/down resistor to bring the resistance down/ 

GPIO Drive Characteristics

There is an important question to be answered - what can be safely connected to a GPIO line?

Of course there is no problem connecting things to GPIO lines set to input, which is the default. The only thing you have to check is that the input doesn't go above 3.3V - if it does you could damage the GPIO line and the micro:bit.

When it comes to output you have to be much more cautious to avoid damage to the micro:bit.

It is all a matter of how much current the line can source or sink. Obviously the output varies between 0V and 3.3V which is the working voltage of the micro:bit. So you can only connect devices which can operate at 3.3V but there is also the issue of how much current the device needs to operate.

One of the most difficult specifications to find is how much current the GPIO lines will provide. The reason is partly that the situation is complicated. There are a number of ways that the GPIO line can be set up and there is a limit on the total current in all of the lines as well as a per-line limit. 

What is very clear however is that the current handling ability of the micro:bit is very low. 

This is a problem because of the way that the GPIO lines are made very easy to access using clips and plugs makes it all too easy to connect a device directly to the line without thinking about the possibilities of damaging it. For example you cannot safely conned an LED to the micro:bit - the current draw is simply too much. 

In normal operation the GPIO lines are all configured into standard drive mode which limits the current to 0.5mA. 

This is a very small current - by comparison the Raspberry Pi can work with 3mA on each GPIO line and a single line can work up to 16mA.

There are high current drive modes which can be selected, see the next chapter. With high current drive on a GPIO line can work with a maximum of 5mA however the total current on all the GPIO pins cannot exceed 15mA. So you could have three GPIO lines drawing 5mA each or two drawing 5mA and five more drawing 1mA each.  Notice also that you can set high current drive on a per line basis. That is one line can be high current drive and the next standard. Each line is subject to a 5mA or a 0.5mA maximum current according to its mode. 

This is complicated so a summary will help:

  • In input mode you only have to keep the voltage on a GPIO line between 0 and 3.3V. The GPIO line in input mode is high impedance and draws little current from whatever is connected to it.
  • In output mode with the default standard drive the voltage is between 0 and 3.3V and the current has to be less than 0.5mA
  • In output mode a GPIO line with high drive turned on the current has to be less than 5mA.
  • The total current in all of the GPIO lines has to be less than  15mA.

Notice that you cannot set high drive mode on using the framework. To do this you have to access the hardware directly - see the next chapter. In addition to setting the drive strength you can also opt for open collector or open drain mode.

The bottom line is that in nearly all cases if you want to drive something from a GPIO line you will need to use a transistor or an FET to increase the current and perhaps change the voltage.  If you have seen or tried an LED or other device from a GPIO line without a transistor or FET as a buffer and it worked then either you have been lucky or the drive specifications are pessimistic - however users have destroyed their micro:bit with the same set up.


An FET Buffer

Constructing a buffer is very easy and cheap. You can use a Bipolar Junction Transistor BJT if you want to but in this case the simplest solution is to use an FET.

The reason is that an FET hardly draws any current from the GPIO line.  Not only does the FET increase the current in the load it also allows you to change the drive voltage. Although the voltage is shown as 3.3V it can be 5V or anything needed to drive the load. The FET isolates the micro:bit from the higher voltage.

The 100K Ohm resistor is there to ensure that the FET and the load is off when the micro:bit first powers up and the GPIO line defaults to an input. Without it the FET would pick up noise and switch on and off randomly.

The FET shown, 2N7000 is cheap and can work with voltages up to 60V and currents up to 100mA. If you need a higher voltage or a larger current then you need to use a different FET. Power FETs are cheap, a few dollars, and can handle currents of tens of amps. 

GPIO Output

When you are generating GPIO output signals the key questions are often when the signal goes high and when it goes low. In some applications this is a very non-time critical event. As long as the line goes high or low in response to something then all it well. In other situations the response has to be after a given amount of time. If that time interval is longer than 100ms then there is generally no problem. If the interval is less than 100ms then you have to work out the best way to ensure the delay taking account of how accurate it has to be. 

In this part of the chapter we take a look at how fast GPIO lines can be changed and what sort of accuracy is possible. 

How Fast?

A fundamental question that you have to answer for any processor intended for use in embedded or IoT projects is  - how fast can the GPIO lines work? 

Some times the answer isn't of too much concern because what you want to do only works relatively slowly. Any application that is happy with response times in the tens of millisecond region will generally work with almost any processor. However if you want to implement custom protocols or anything that needs microsecond responses the question is much more important. 

It is fairly easy to find out how fast a single GPIO line can be used if you have a logic analyzer or oscilloscope. All you have to do is run the program that we used as a first test in chapter one:

   uBit.init();
    while(1) {
        uBit.io.P0.setDigitalValue(1);
        uBit.io.P0.setDigitalValue(0);
    }

The results is a pulse train with pulses ranging from 3.5 to 3.8 microseconds and a 0.1ms pause every 6ms due to the system timer interrupting. There isn't anything that can be done about this 6ms interrupt apart from switching the system timer off. 

Sleep

To generate pulses of a known duration we need to pause the program between state changes. 

The simplest way of sleeping a fiber is to use the sleep command. Unfortunately this uses the system timer and only works in steps of 6ms. 

To try this, include a call to sleep(9) to delay the pulse:

    while(1) {
       uBit.io.P0.setDigitalValue(1);
       uBit.sleep(9);
       uBit.io.P0.setDigitalValue(0);
      uBit.sleep(9);
    }

You will discover that you get 12ms pulses. This is only useful if you want pulses that are multiples of 6ms or so long that a 6ms error doesn't matter. 

There is a microsecond delay function wait_us() which is part of the mbed software that the framework is built on and we can call it from a micro:bit program. Unfortunately there is an overhead in calling the function.

So for example:

    while(1) {
        uBit.io.P0.setDigitalValue(1);
        wait_us(20);
        uBit.io.P0.setDigitalValue(0);
        wait_us(20);     
    }

produces pulses that last just over 32 micro seconds. 

If you look at how the delay time relates to the average pulse length things seem fairly simple and we have 

pulse length = delay+12

What this means is that if you want to set a delay less than 12 microseconds don't use wait_us. For delays bigger than this it works reasonably well. 

You can try various modifications to the basic wait_us approach but the best you can do is to get the overheads down to about 7 microseconds. 

Busy Wait

For pulses of less than about 50 microseconds it is better to use a busy wait i.e. a loop that does nothing.  You have to be a little careful about how you insert a loop that does nothing because optimizing compiler have a tendency to take a null loop out in an effort to make your program run faster.

To stop an optimizing from removing busy wait loops make sure you always declare loop variables as volatile.

To generate a pulse of a given length you can use 

   volatile int i;
        while (1) {
            for (i = 0; i < n; i++) {
            };
            uBit.io.P0.setDigitalValue(1);
            for (i = 0; i < n; i++) {
            };
            uBit.io.P0.setDigitalValue(0);          
        }

Where n is set to a value that depends on the delay you want to generate.

The relationship between n and t the time delay is very linear:

n=1.333t - 5.83

Notice that you can't generate pulses less than about 6 microseconds.

For example if you want 10 microsecond pulses then n=7.5 and using 7 the result can be seen below:

 

 

Notice that the pulses are around 9.75 microsecond.

We will revisit the busy wait option and using timers in the next chapter.


Phased Pulses

As a simple example of using the output functions lets try to write a short program that pulses two lines - one high and one low and then one low and one high i.e. two pulse trains out of phase by 180 degrees.

The simplest program to do this job is: 

      while (1) {
             uBit.io.P0.setDigitalValue(1);
             uBit.io.P1.setDigitalValue(0);
             uBit.io.P0.setDigitalValue(0);
             uBit.io.P1.setDigitalValue(1);
        }

Notice that there is no delay in the loop so the pulses are produced at the fastest possible speed. 

Using a logic analyzer reveals that the result isn't what you might expect:

 

You can also see that the pulse trains are not 180 degrees out of phase. The top train switches on and the bottom train takes about half a pulse before it switches on - the intent is for both actions to occur at the same time. You should also notice that now the pulse time is about 7.75 microseconds which is about double the time for a pulse train on a single GPIO line. 

The point is that it does take quite a long time to access and change the state of an output line. 

Of course if we include a delay to increase the pulse width then the delay caused by accessing the GPIO lines in two separate actions isn't so obvious:

 

 

In this case the loop now n=100 busy wait loops:

 volatile int i;
        while (1) {
           for (i = 0; i < 100; i++) {
           };
            uBit.io.P0.setDigitalValue(1);
            uBit.io.P1.setDigitalValue(0);
            for (i = 0; i < 100; i++) {
            };
            uBit.io.P0.setDigitalValue(0);
             uBit.io.P1.setDigitalValue(1);
        }
     

You will notice that the pulses are now roughly 80 microseconds wide and they are changing at what looks like nearer to being the same time - of course they aren't.

There is still a lag, but in many applications it might not be important. In other applications it could be crucial.

For example, if the two pulse trains were driving different halves of a motor controller bridge there would be a significant time when both were high - so shorting the power supply. It might only be for 10 microseconds but over time it could well damage the power supply.  Of course, any sensible, cautious, engineer wouldn't feed a motor control bridge from two independently generated pulse trains unless they were guaranteed not to switch both sides of the bridge on at the same time. 

A better way to generate multiple pulses is to write directly to the hardware which is what we do in the next chapter. 

Summary 

If you want to create accurate pulses then:

  • for delays greater than 6ms to an accuracy of 6ms you can use sleep().
  • for delays from around 12 microseconds you can use wait_us accurate to around 1 microsecond.
  • for delays greater than 7 microseconds you can use a busy wait accurate to around 1 microsecond. 
  • switching GPIO lines takes time and achieving synchronized switching better than ten microseconds is not possible using the framework.

GPIO Input

GPIO input can be a much more difficult problem than output. At least for output you can see the change in the signal on a logic analyzer and know the exact time that it occurred. This makes if possible to track down timing problems and fine tune things with good accuracy. 

Input on the other hand is "silent" and unobservable. When did you read in the status of the line. Usually the timing of the read is with respect to some other action that the micro:bit has taken. For example, read the input line 20 microseconds after setting the output line high. The usual rule of thumb is to assume that it takes as long to read a GPIO line as it does to set it. This means we can use the delay mechanisms that we looked at with output in mind for input. 

In some applications the times are long and/or unimportant but in some they are critical.

Basic Input Circuit - The Switch

One of the most common input circuits is the switch or button - the micro:bit has two built in buttons A and B.  If you want another external button you can use any GPIO line and the following circuit:

The 10K resistor isn't critical in value. It simply pulls the GPIO line high when the switch isn't pressed. When it is pressed a current of a little more than 0.3 flows in the resistor. If this is too much increase the resistance to 100K or even more - but notice that the higher the resistor value the noisier the input to the GPIO and the more it is susceptible to RF interference.  

If you want a switch that pulls the line high instead of low, to reverse the logic just swap the positions of the resistor and the switch in the diagram. 

Although the switch is the simples input device it is also very difficult to get right. When a user clicks a switch of any sort the action isn't clean - the switch bounces. What this means is that the logic level on the GPIO line goes high then low and high and bounces between the two until it settles down. There are electronic ways of debouncing switches but software does the job much better. All you have to do is but a delay of a millisecond or so after detecting a switch press and read the line again - if it is still low then record a switch press. Similarly when the switch is released read the state twice with a delay. You can very the delay to modify the perceived characteristics of the switch.

The Potential Divider

If you have an input that is outside of the range of 0 to 3.3V then you can reduce it using a simple potential divider. 

 V is the input from the external logic and Vout it the connection to the GPIO input line:

voltageDiv

 

You can spend a lot of time on working out good values of R1 and R2. For loads that take a lot of current you need R1+R2 to be small and divided in the same ratio as the voltages.

For example for a 5V device R1=18K and R2=33K work well to drop the voltage to 3.3V.

The problem with a resistive divider is that it can round off fast pulses due to the small capacitive effects. This usually isn't a problem, but if it is then the solution is to use an FET buffer again. 

hilodrive

Notice that this is an inverting buffer but you can usually ignore this and simply correct in software i.e. read a 1 as a low and a 0 as a high state. The role of R1 is to make sure the FET is off when the 5V signal is absent and R2 limits the current in the FET to about 0.3mA.

In most case you should try the simple voltage divider and only move to an active buffer if it doesn't work. 


How Fast Can We Measure?

The simplest way to find out how quickly we can take a measurement using the micro:bit is to perform a pulse width measurement using a busy wait. Apply in square wave to P0 we can measure the time that the pulse is high using:

   uBit.init();
   uint32_t start;
   volatile int i;
   while (1) {
      while(1== uBit.io.P0.getDigitalValue());
      while(0== uBit.io.P0.getDigitalValue());
      for(i=0;i<1000;i++){
       if(0==uBit.io.P0.getDigitalValue()) break;
      }
      printf("%d\n\r",i);
   }

This might look a little strange at first. The inner while loops are responsible for getting us to the right point in the waveform. First we loop until the line goes low, then we loop until it goes high again and finally measure how long before it goes low. You might think that we simply have to wait for it to go high and then measure how long till it goes low but this misses the possibility that the signal might be part way though a high period when we first measure it.

If you run this program with different pulse widths the result are very regular:

(y axis loop count x axis pulse time)

The equation relating time t and loop count n is:

t=4.88 n +3.38 microseconds

This works down to about 10 microseconds or slightly less. 

If you want to record the time in microseconds rather than using a loop count then you could use:

    uBit.init();
    uint32_t t;
    volatile int i;
    while (1) {
          while(1== uBit.io.P0.getDigitalValue());
          while(0== uBit.io.P0.getDigitalValue());
          t = us_ticker_read();
          for(i=0;i<1000;i++){
           if(0==uBit.io.P0.getDigitalValue()) break;
          }
          t= us_ticker_read()-t;
          printf("%d,%ld\n\r",i,t);
        }

This is accurate to around 10 microsecond. 

Notice that in either case if you try measuring pulse widths much shorter than the lower limit that works you will get results that look like longer pulses are being applied. The reason is simply that the micro:bit will miss the first transition to zero but will detect a second or third or later transistion. This is the digital equivalent to of the aliasing effect found in the Fourier Transform or general signal processing. 

Interrupts

There is a general feeling that realtime programming and interrupts go together and if you are not using an interrupt you are probably doing something wrong. In fact the truth is that if you are using an interrupt you probably are doing something wrong. Some organizations agree with the general idea that interrupts are dangerous so much that they are banned from being used at all - and this includes realtime software. 

In the case of the micro:bit we have a system of events that stand in as a software wrapping of the raw system interrupts that are available if you dig just a little deeper. 

As far as GPIO lines are concerned you can set an event to occur on any of the following:

MICROBIT_PIN_EVENT_ON_EDGE  fire on rising or falling edge you can specify which when you register the event.
MICROBIT_PIN_EVENT_ON_PULSE fire if pin is high or low you can set high or low to fire when you register the event. 
MICROBIT_PIN_EVENT_ON_TOUCH fire if pin touched
MICROBIT_PIN_EVENT_NONE disable events for this pin

The most commonly useful is the on-edge setting which fires and event if a GPIO line transitions from high to low or low to high. You can set the event type using the

int eventOn(int eventType)

As well as the eventOn setting you also have to register the event with the messagebus. You have to supply the, id of the pin generating the event, the event type, the function to be called when the event happens and how the event should be handled. For example

bus.listen(MICROBIT_ID_IO_P0, MICROBIT_PIN_EVT_PULSE_HI, onPulse, MESSAGE_BUS_LISTENER_IMMEDIATE)

will call onPulse when the PULSE HI event occurs on pin P0 and it will call the event handler at once.  The even types that you can specify are:

MICROBIT_PIN_EVT_RISE
MICROBIT_PIN_EVT_FALL
MICROBIT_PIN_EVT_PULSE_HI
MICROBIT_PIN_EVT_PULSE_LO

The event handler has the signature:

void eventHandler(MicroBitEvent evt)

and one of the most useful properties of the evt object is the timestamp of the event.

Events are only really useful when you have a low frequency condition that needs to be dealt with on a low priority basis. Their use can simplify the logic of your program but rarely does using an event speed things up because the overhead involved in event or interrupt handling is usually quite high. 

For example suppose you want to react to a doorbell push button. You could write a polling loop that simply checks the button status repeatedly and forever - or you could write an event to respond to the doorbell. How good a design this is depends on how much the doorbell press event has to interact with the rest of the program.

Finally before you dismiss the idea of having a micro:bit do nothing but ask repeatedly "is the doorbell pressed" - what else has it to do? 

If the answer is "not much" then a polling loop might well be your simplest option. 

Let's find out how much overhead is inherent in using events by repeating the pulse width measurement. This time we can't simply print the results as this would stop the event handling. As a compromise we save 20 readings in an array and then print them. It is also important to keep the event handling routines short as how long they take to complete. If the event handling routine takes longer then the pulse width that can be measured is longer. 

 

uint64_t t[20];
uint64_t temp=0;
int i=0;
 void onPulseEdge(MicroBitEvent evt) 
 { 

  t[i]=(evt.timestamp-temp);
  i++;
  temp = evt.timestamp; 
    if(i<20)return;
     uBit.io.P0.eventOn(MICROBIT_PIN_EVENT_NONE);
    for(i=1;i<20;i++){
        printf("%d\n\r",(int)t[i]);  
    }
 }

int main() {
    uBit.init();   
     uBit.messageBus.listen(MICROBIT_ID_IO_P0, MICROBIT_PIN_EVT_RISE , onPulseEdge, MESSAGE_BUS_LISTENER_IMMEDIATE) ;
      uBit.messageBus.listen(MICROBIT_ID_IO_P0, MICROBIT_PIN_EVT_FALL , onPulseEdge, MESSAGE_BUS_LISTENER_IMMEDIATE) ;
          uBit.io.P0.eventOn(MICROBIT_PIN_EVENT_ON_EDGE);
     

Notice we have event handler called when there is a rising edge and a falling edge. 

This records accurate times for pulses longer than 250 microseconds but starts to miss pulses as the pulse width shortens to 100 microsecond. At 100 microseconds it misses about one pulse in ten. If the event handlers took longer to do their task then the limit would be even larger. 

Events are great for handling things that happened on the scale of 100s of microseconds and preferably 1ms. 

Also notice that the documentation says that the shortest pulse that can be reliably detect, i.e. will fire a MICROBIT_PIN_EVENT_ON_PULSE event is 85 microseconds.

Events are only useful if the time that the event handler takes plus the overheads of servicing an event is less than the average repetition rate of the event.

Where Next?

Now that we have explored many of the ideas in using the GPIO lines for output and input the next question is can we do better by access the hardware directly. 

This is not necessary for most applications but when it is there really is no other way to do the job. 

 

Now On Sale!

You can now buy a print edition of micro:bit IoT in C.

You can buy it from:

USA and World  Amazon.com
Canada              Amazon.ca
UK                      Amazon.co.uk
France                Amazon.fr
Germany            Amazon.de
Spain                  Amazon.es
Brazil                  Amazon.br
Italy                    Amazon.it
Japan                 Amazon.co.jp
Mexico               Amazon.com.mx 

 

 The full contents can be seen below. 

Chapter List

  1. Getting Started With C/C++
    Anyone who wants to use the BBC micro:bit to its full potential as an IoT device needs to look outside the coding environments provided by its own website. As an mbed device, however, the micro:bit  is capable of being programmed in C/C++. Here we look at how to use the mbed online compiler for a simple demo program.

  2. Offline C/C++ Development  
    We have already discovered how to use the online editor to create a C/C++ program. Now we are going to move to the desktop with an offline approach. This has the advantage that we can use any tools we care to select and no Internet connection is needed.
  3. First Steps With The GPIO 
    The most basic task when working with the micro:bit is controlling the I/O lines. This isn't difficult if you use the framework provided but there some subtle points to watch out for. This chapter looks a the basics of using the GPIO.

  4. Working Directly With The Hardware - Memory Mapping. 
    The framework makes working with the GPIO and other devices as easy as it can be but there are many layers of software to go through before you get to the hardware. Writing directly to the hardware can make things up to ten times faster and give you access to things that their framework doesn't. It is also an educational experience to deal with the raw hardware directly.

  5. Pulse Width Modulation, Servos And More
    In this chapter we take a close look at pulse width modulation PWM including, sound, driving LEDs and servos.

  6. I2C
    The I2C bus is one of the most useful ways of connecting moderately sophisticated sensors and peripherals to the any processor. The only problem is that it can seem like a nightmare confusion of hardware, low level interaction and high level software. There are few general introductions to the subject because at first sight every I2C device is different, but here we present one.

  7. I2C Temperature Measurement
    Using I2C devices is fairly easy once you have successfully used one - and hence know what information you need and what to look for in a working system. In this chapter we use the HTU21D temperature and humidity sensor as a case study of I2C in action. It also happens to be a useful sensor.

  8. A Custom Protocol - The DHT11/22

  9. The DS18B20 - One Wire Bus

  10. The SPI Bus
    The SPI bus can be something of a problem because it doesn't have a well defined standard that every device conforms to. Even so if you only want to work with one specific device it is usually easy to find a configuration that works - as long as you understand what the possibilities are. 

  11. SPI MCP3008/4 AtoD   
    The SPI bus can be difficult to make work at first but once you know what to look for about how the slave claims to work it gets easier. To demonstrate how its done let's add eight channels of 12 bit AtoD using the MCP3008.

  12. Serial Connections
    The serial port is one of the oldest of ways of connecting devices together but it is still very, very useful. The micro:bit has a single serial interface but it can be directed to use any of the GPIO piins as Rx and Tx. 

  13. WiFi 
    The micro:bit has a radio that works in Bluetooth LE and point-to-point ad-hoc mode, but at the moment it lacks WiFi connectivity. The solution is to use the low cost ESP8266 to make the connection via the micro:bit's serial port. 

  14. LED Display 
    The micro:bit's LED display may only be 5x5 but it is very versatile. If you want to make use of it directly then you are going to have to master some lower level functions.

 

More Information

developer.mbed.org

http://lancaster-university.github.io/microbit-docs/

Related Articles

The BBC Micro:bit Is An Mbed Device In C/C++ 

Commando Jump Game For The Micro:bit In Python 

BBC Micro To micro:bit 

 

 

 

 

comments powered by Disqus