x

Dependencies:   BLE_API mbed-dev-bin nRF51822

Fork of microbit-dal by Lancaster University

Revision:
1:8aa5cdb4ab67
Child:
21:cab56b701601
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/source/bluetooth/MicroBitBLEManager.cpp	Thu Apr 07 01:33:22 2016 +0100
@@ -0,0 +1,602 @@
+/*
+The MIT License (MIT)
+
+Copyright (c) 2016 British Broadcasting Corporation.
+This software is provided by Lancaster University by arrangement with the BBC.
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
+*/
+
+#include "MicroBitConfig.h"
+#include "MicroBitBLEManager.h"
+#include "MicroBitStorage.h"
+#include "MicroBitFiber.h"
+
+
+/* The underlying Nordic libraries that support BLE do not compile cleanly with the stringent GCC settings we employ.
+ * If we're compiling under GCC, then we suppress any warnings generated from this code (but not the rest of the DAL)
+ * The ARM cc compiler is more tolerant. We don't test __GNUC__ here to detect GCC as ARMCC also typically sets this
+ * as a compatability option, but does not support the options used...
+ */
+#if !defined(__arm)
+#pragma GCC diagnostic ignored "-Wunused-function"
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-parameter"
+#endif
+
+#include "ble.h"
+
+extern "C"
+{
+#include "device_manager.h"
+uint32_t btle_set_gatt_table_size(uint32_t size);
+}
+
+/*
+ * Return to our predefined compiler settings.
+ */
+#if !defined(__arm)
+#pragma GCC diagnostic pop
+#endif
+
+#define MICROBIT_PAIRING_FADE_SPEED		4
+
+const char* MICROBIT_BLE_MANUFACTURER = NULL;
+const char* MICROBIT_BLE_MODEL = "BBC micro:bit";
+const char* MICROBIT_BLE_HARDWARE_VERSION = NULL;
+const char* MICROBIT_BLE_FIRMWARE_VERSION = MICROBIT_DAL_VERSION;
+const char* MICROBIT_BLE_SOFTWARE_VERSION = NULL;
+const int8_t MICROBIT_BLE_POWER_LEVEL[] = {-30, -20, -16, -12, -8, -4, 0, 4};
+
+/*
+ * Many of the mbed interfaces we need to use only support callbacks to plain C functions, rather than C++ methods.
+ * So, we maintain a pointer to the MicroBitBLEManager that's in use. Ths way, we can still access resources on the micro:bit
+ * whilst keeping the code modular.
+ */
+static MicroBitBLEManager *manager = NULL;                      // Singleton reference to the BLE manager. many mbed BLE API callbacks still do not support member funcions yet. :-(
+static uint8_t deviceID = 255;                                  // Unique ID for the peer that has connected to us.
+static Gap::Handle_t pairingHandle = 0;                         // The connection handle used during a pairing process. Used to ensure that connections are dropped elegantly.
+
+static void storeSystemAttributes(Gap::Handle_t handle)
+{
+    if(manager->storage != NULL && deviceID < MICROBIT_BLE_MAXIMUM_BONDS)
+    {
+        ManagedString key("bleSysAttrs");
+
+        KeyValuePair* bleSysAttrs = manager->storage->get(key);
+
+        BLESysAttribute attrib;
+        BLESysAttributeStore attribStore;
+
+        uint16_t len = sizeof(attrib.sys_attr);
+
+        sd_ble_gatts_sys_attr_get(handle, attrib.sys_attr, &len, BLE_GATTS_SYS_ATTR_FLAG_SYS_SRVCS);
+
+        //copy our stored sysAttrs
+        if(bleSysAttrs != NULL)
+        {
+            memcpy(&attribStore, bleSysAttrs->value, sizeof(BLESysAttributeStore));
+            delete bleSysAttrs;
+        }
+
+        //check if we need to update
+        if(memcmp(attribStore.sys_attrs[deviceID].sys_attr, attrib.sys_attr, len) != 0)
+        {
+            attribStore.sys_attrs[deviceID] = attrib;
+            manager->storage->put(key, (uint8_t *)&attribStore);
+        }
+    }
+}
+
+/**
+  * Callback when a BLE GATT disconnect occurs.
+  */
+static void bleDisconnectionCallback(const Gap::DisconnectionCallbackParams_t *reason)
+{
+    storeSystemAttributes(reason->handle);
+
+    if (manager)
+	    manager->advertise();
+}
+
+/**
+  * Callback when a BLE SYS_ATTR_MISSING.
+  */
+static void bleSysAttrMissingCallback(const GattSysAttrMissingCallbackParams *params)
+{
+    int complete = 0;
+    deviceID = 255;
+
+    dm_handle_t dm_handle = {0,0,0,0};
+
+    int ret = dm_handle_get(params->connHandle, &dm_handle);
+
+    if (ret == 0)
+        deviceID = dm_handle.device_id;
+
+    if(manager->storage != NULL && deviceID < MICROBIT_BLE_MAXIMUM_BONDS)
+    {
+        ManagedString key("bleSysAttrs");
+
+        KeyValuePair* bleSysAttrs = manager->storage->get(key);
+
+        BLESysAttributeStore attribStore;
+        BLESysAttribute attrib;
+
+        if(bleSysAttrs != NULL)
+        {
+            //restore our sysAttrStore
+            memcpy(&attribStore, bleSysAttrs->value, sizeof(BLESysAttributeStore));
+            delete bleSysAttrs;
+
+            attrib = attribStore.sys_attrs[deviceID];
+
+            ret = sd_ble_gatts_sys_attr_set(params->connHandle, attrib.sys_attr, sizeof(attrib.sys_attr), BLE_GATTS_SYS_ATTR_FLAG_SYS_SRVCS);
+
+            complete = 1;
+
+            if(ret == 0)
+                ret = sd_ble_gatts_service_changed(params->connHandle, 0x000c, 0xffff);
+        }
+    }
+
+    if (!complete)
+        sd_ble_gatts_sys_attr_set(params->connHandle, NULL, 0, 0);
+
+}
+
+static void passkeyDisplayCallback(Gap::Handle_t handle, const SecurityManager::Passkey_t passkey)
+{
+    (void) handle; /* -Wunused-param */
+
+	ManagedString passKey((const char *)passkey, SecurityManager::PASSKEY_LEN);
+
+    if (manager)
+	    manager->pairingRequested(passKey);
+}
+
+static void securitySetupCompletedCallback(Gap::Handle_t handle, SecurityManager::SecurityCompletionStatus_t status)
+{
+    (void) handle; /* -Wunused-param */
+
+    dm_handle_t dm_handle = {0,0,0,0};
+    int ret = dm_handle_get(handle, &dm_handle);
+
+    if (ret == 0)
+        deviceID = dm_handle.device_id;
+
+    if (manager)
+    {
+        pairingHandle = handle;
+	    manager->pairingComplete(status == SecurityManager::SEC_STATUS_SUCCESS);
+    }
+}
+
+/**
+ * Constructor.
+ *
+ * Configure and manage the micro:bit's Bluetooth Low Energy (BLE) stack.
+ *
+ * @param _storage an instance of MicroBitStorage used to persist sys attribute information. (This is required for compatability with iOS).
+ *
+ * @note The BLE stack *cannot*  be brought up in a static context (the software simply hangs or corrupts itself).
+ * Hence, the init() member function should be used to initialise the BLE stack.
+ */
+MicroBitBLEManager::MicroBitBLEManager(MicroBitStorage& _storage) :
+    storage(&_storage)
+{
+    manager = this;
+	this->ble = NULL;
+	this->pairingStatus = 0;
+}
+
+/**
+ * Constructor.
+ *
+ * Configure and manage the micro:bit's Bluetooth Low Energy (BLE) stack.
+ *
+ * @note The BLE stack *cannot*  be brought up in a static context (the software simply hangs or corrupts itself).
+ * Hence, the init() member function should be used to initialise the BLE stack.
+ */
+MicroBitBLEManager::MicroBitBLEManager() :
+    storage(NULL)
+{
+    manager = this;
+	this->ble = NULL;
+	this->pairingStatus = 0;
+}
+
+/**
+ * When called, the micro:bit will begin advertising for a predefined period,
+ * MICROBIT_BLE_ADVERTISING_TIMEOUT seconds to bonded devices.
+ */
+void MicroBitBLEManager::advertise()
+{
+    if(ble)
+        ble->gap().startAdvertising();
+}
+
+/**
+  * Post constructor initialisation method as the BLE stack cannot be brought
+  * up in a static context.
+  *
+  * @param deviceName The name used when advertising
+  * @param serialNumber The serial number exposed by the device information service
+  * @param messageBus An instance of an EventModel, used during pairing.
+  * @param enableBonding If true, the security manager enabled bonding.
+  *
+  * @code
+  * bleManager.init(uBit.getName(), uBit.getSerial(), uBit.messageBus, true);
+  * @endcode
+  */
+void MicroBitBLEManager::init(ManagedString deviceName, ManagedString serialNumber, EventModel& messageBus, bool enableBonding)
+{
+	ManagedString BLEName("BBC micro:bit");
+	this->deviceName = deviceName;
+
+#if !(CONFIG_ENABLED(MICROBIT_BLE_WHITELIST))
+	ManagedString namePrefix(" [");
+	ManagedString namePostfix("]");
+	BLEName = BLEName + namePrefix + deviceName + namePostfix;
+#endif
+
+    // Start the BLE stack.
+#if CONFIG_ENABLED(MICROBIT_HEAP_REUSE_SD)
+    btle_set_gatt_table_size(MICROBIT_SD_GATT_TABLE_SIZE);
+#endif
+
+    ble = new BLEDevice();
+    ble->init();
+
+    // automatically restart advertising after a device disconnects.
+    ble->gap().onDisconnection(bleDisconnectionCallback);
+    ble->gattServer().onSysAttrMissing(bleSysAttrMissingCallback);
+
+    // Configure the stack to hold onto the CPU during critical timing events.
+    // mbed-classic performs __disable_irq() calls in its timers that can cause
+    // MIC failures on secure BLE channels...
+    ble_common_opt_radio_cpu_mutex_t opt;
+    opt.enable = 1;
+    sd_ble_opt_set(BLE_COMMON_OPT_RADIO_CPU_MUTEX, (const ble_opt_t *)&opt);
+
+#if CONFIG_ENABLED(MICROBIT_BLE_PRIVATE_ADDRESSES)
+	// Configure for private addresses, so kids' behaviour can't be easily tracked.
+	ble->gap().setAddress(BLEProtocol::AddressType::RANDOM_PRIVATE_RESOLVABLE, {0});
+#endif
+
+    // Setup our security requirements.
+    ble->securityManager().onPasskeyDisplay(passkeyDisplayCallback);
+    ble->securityManager().onSecuritySetupCompleted(securitySetupCompletedCallback);
+    ble->securityManager().init(enableBonding, (SecurityManager::MICROBIT_BLE_SECURITY_LEVEL == SecurityManager::SECURITY_MODE_ENCRYPTION_WITH_MITM), SecurityManager::IO_CAPS_DISPLAY_ONLY);
+
+    if (enableBonding)
+    {
+        // If we're in pairing mode, review the size of the bond table.
+        int bonds = getBondCount();
+
+        // TODO: It would be much better to implement some sort of LRU/NFU policy here,
+        // but this isn't currently supported in mbed, so we'd need to layer break...
+
+        // If we're full, empty the bond table.
+        if (bonds >= MICROBIT_BLE_MAXIMUM_BONDS)
+            ble->securityManager().purgeAllBondingState();
+    }
+
+#if CONFIG_ENABLED(MICROBIT_BLE_WHITELIST)
+    // Configure a whitelist to filter all connection requetss from unbonded devices.
+    // Most BLE stacks only permit one connection at a time, so this prevents denial of service attacks.
+    BLEProtocol::Address_t bondedAddresses[MICROBIT_BLE_MAXIMUM_BONDS];
+    Gap::Whitelist_t whitelist;
+    whitelist.addresses = bondedAddresses;
+    whitelist.capacity = MICROBIT_BLE_MAXIMUM_BONDS;
+
+    ble->securityManager().getAddressesFromBondTable(whitelist);
+
+    ble->gap().setWhitelist(whitelist);
+    ble->gap().setScanningPolicyMode(Gap::SCAN_POLICY_IGNORE_WHITELIST);
+    ble->gap().setAdvertisingPolicyMode(Gap::ADV_POLICY_FILTER_CONN_REQS);
+#endif
+
+    // Configure the radio at our default power level
+    setTransmitPower(MICROBIT_BLE_DEFAULT_TX_POWER);
+
+    // Bring up core BLE services.
+    new MicroBitDFUService(*ble);
+    DeviceInformationService ble_device_information_service (*ble, MICROBIT_BLE_MANUFACTURER, MICROBIT_BLE_MODEL, serialNumber.toCharArray(), MICROBIT_BLE_HARDWARE_VERSION, MICROBIT_BLE_FIRMWARE_VERSION, MICROBIT_BLE_SOFTWARE_VERSION);
+    new MicroBitEventService(*ble, messageBus);
+
+
+    // Configure for high speed mode where possible.
+    Gap::ConnectionParams_t fast;
+    ble->getPreferredConnectionParams(&fast);
+    fast.minConnectionInterval = 8; // 10 ms
+    fast.maxConnectionInterval = 16; // 20 ms
+    fast.slaveLatency = 0;
+    ble->setPreferredConnectionParams(&fast);
+
+    // Setup advertising.
+#if CONFIG_ENABLED(MICROBIT_BLE_WHITELIST)
+    ble->accumulateAdvertisingPayload(GapAdvertisingData::BREDR_NOT_SUPPORTED);
+#else
+    ble->accumulateAdvertisingPayload(GapAdvertisingData::BREDR_NOT_SUPPORTED | GapAdvertisingData::LE_GENERAL_DISCOVERABLE);
+#endif
+
+    ble->accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LOCAL_NAME, (uint8_t *)BLEName.toCharArray(), BLEName.length());
+    ble->setAdvertisingType(GapAdvertisingParams::ADV_CONNECTABLE_UNDIRECTED);
+    ble->setAdvertisingInterval(200);
+
+#if (MICROBIT_BLE_ADVERTISING_TIMEOUT > 0)
+    ble->gap().setAdvertisingTimeout(MICROBIT_BLE_ADVERTISING_TIMEOUT);
+#endif
+
+    // If we have whitelisting enabled, then prevent only enable advertising of we have any binded devices...
+    // This is to further protect kids' privacy. If no-one initiates BLE, then the device is unreachable.
+    // If whiltelisting is disabled, then we always advertise.
+#if CONFIG_ENABLED(MICROBIT_BLE_WHITELIST)
+    if (whitelist.size > 0)
+#endif
+        ble->startAdvertising();
+}
+
+/**
+ * Change the output power level of the transmitter to the given value.
+ *
+ * @param power a value in the range 0..7, where 0 is the lowest power and 7 is the highest.
+ *
+ * @return MICROBIT_OK on success, or MICROBIT_INVALID_PARAMETER if the value is out of range.
+ *
+ * @code
+ * // maximum transmission power.
+ * bleManager.setTransmitPower(7);
+ * @endcode
+ */
+int MicroBitBLEManager::setTransmitPower(int power)
+{
+    if (power < 0 || power >= MICROBIT_BLE_POWER_LEVELS)
+        return MICROBIT_INVALID_PARAMETER;
+
+    if (ble->gap().setTxPower(MICROBIT_BLE_POWER_LEVEL[power]) != NRF_SUCCESS)
+        return MICROBIT_NOT_SUPPORTED;
+
+    return MICROBIT_OK;
+}
+
+/**
+ * Determines the number of devices currently bonded with this micro:bit.
+ * @return The number of active bonds.
+ */
+int MicroBitBLEManager::getBondCount()
+{
+    BLEProtocol::Address_t bondedAddresses[MICROBIT_BLE_MAXIMUM_BONDS];
+    Gap::Whitelist_t whitelist;
+    whitelist.addresses = bondedAddresses;
+    whitelist.capacity = MICROBIT_BLE_MAXIMUM_BONDS;
+    ble->securityManager().getAddressesFromBondTable(whitelist);
+
+    return whitelist.bonds;
+}
+
+/**
+ * A request to pair has been received from a BLE device.
+ * If we're in pairing mode, display the passkey to the user.
+ * Also, purge the bonding table if it has reached capacity.
+ *
+ * @note for internal use only.
+ */
+void MicroBitBLEManager::pairingRequested(ManagedString passKey)
+{
+    // Update our mode to display the passkey.
+	this->passKey = passKey;
+	this->pairingStatus = MICROBIT_BLE_PAIR_REQUEST;
+}
+
+/**
+ * A pairing request has been sucessfully completed.
+ * If we're in pairing mode, display a success or failure message.
+ *
+ * @note for internal use only.
+ */
+void MicroBitBLEManager::pairingComplete(bool success)
+{
+	this->pairingStatus = MICROBIT_BLE_PAIR_COMPLETE;
+
+	if(success)
+    {
+		this->pairingStatus |= MICROBIT_BLE_PAIR_SUCCESSFUL;
+        fiber_add_idle_component(this);
+    }
+}
+
+/**
+ * Periodic callback in thread context.
+ * We use this here purely to safely issue a disconnect operation after a pairing operation is complete.
+ */
+void MicroBitBLEManager::idleTick()
+{
+    if (ble)
+        ble->disconnect(pairingHandle, Gap::REMOTE_DEV_TERMINATION_DUE_TO_POWER_OFF);
+
+    fiber_remove_idle_component(this);
+}
+
+/**
+ * Enter pairing mode. This is mode is called to initiate pairing, and to enable FOTA programming
+ * of the micro:bit in cases where BLE is disabled during normal operation.
+ *
+ * @param display An instance of MicroBitDisplay used when displaying pairing information.
+ * @param authorizationButton The button to use to authorise a pairing request.
+ *
+ * @code
+ * // initiate pairing mode
+ * bleManager.pairingMode(uBit.display, uBit.buttonA);
+ * @endcode
+ */
+void MicroBitBLEManager::pairingMode(MicroBitDisplay& display, MicroBitButton& authorisationButton)
+{
+	ManagedString namePrefix("BBC micro:bit [");
+	ManagedString namePostfix("]");
+	ManagedString BLEName = namePrefix + deviceName + namePostfix;
+
+	ManagedString msg("PAIRING MODE!");
+
+	int timeInPairingMode = 0;
+	int brightness = 255;
+	int fadeDirection = 0;
+
+    ble->gap().stopAdvertising();
+
+    // Clear the whitelist (if we have one), so that we're discoverable by all BLE devices.
+#if CONFIG_ENABLED(MICROBIT_BLE_WHITELIST)
+    BLEProtocol::Address_t addresses[MICROBIT_BLE_MAXIMUM_BONDS];
+    Gap::Whitelist_t whitelist;
+    whitelist.addresses = addresses;
+    whitelist.capacity = MICROBIT_BLE_MAXIMUM_BONDS;
+    whitelist.size = 0;
+    ble->gap().setWhitelist(whitelist);
+    ble->gap().setAdvertisingPolicyMode(Gap::ADV_POLICY_IGNORE_WHITELIST);
+#endif
+
+	// Update the advertised name of this micro:bit to include the device name
+    ble->clearAdvertisingPayload();
+
+    ble->accumulateAdvertisingPayload(GapAdvertisingData::BREDR_NOT_SUPPORTED | GapAdvertisingData::LE_GENERAL_DISCOVERABLE);
+    ble->accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LOCAL_NAME, (uint8_t *)BLEName.toCharArray(), BLEName.length());
+    ble->setAdvertisingType(GapAdvertisingParams::ADV_CONNECTABLE_UNDIRECTED);
+    ble->setAdvertisingInterval(200);
+
+    ble->gap().setAdvertisingTimeout(0);
+    ble->gap().startAdvertising();
+
+	// Stop any running animations on the display
+	display.stopAnimation();
+	display.scroll(msg);
+
+	// Display our name, visualised as a histogram in the display to aid identification.
+	showNameHistogram(display);
+
+	while(1)
+	{
+		if (pairingStatus & MICROBIT_BLE_PAIR_REQUEST)
+		{
+			timeInPairingMode = 0;
+			MicroBitImage arrow("0,0,255,0,0\n0,255,0,0,0\n255,255,255,255,255\n0,255,0,0,0\n0,0,255,0,0\n");
+			display.print(arrow,0,0,0);
+
+			if (fadeDirection == 0)
+				brightness -= MICROBIT_PAIRING_FADE_SPEED;
+			else
+				brightness += MICROBIT_PAIRING_FADE_SPEED;
+
+			if (brightness <= 40)
+				display.clear();
+
+			if (brightness <= 0)
+				fadeDirection = 1;
+
+			if (brightness >= 255)
+				fadeDirection = 0;
+
+			if (authorisationButton.isPressed())
+			{
+				pairingStatus &= ~MICROBIT_BLE_PAIR_REQUEST;
+				pairingStatus |= MICROBIT_BLE_PAIR_PASSCODE;
+			}
+		}
+
+		if (pairingStatus & MICROBIT_BLE_PAIR_PASSCODE)
+		{
+			timeInPairingMode = 0;
+			display.setBrightness(255);
+			for (int i=0; i<passKey.length(); i++)
+			{
+				display.image.print(passKey.charAt(i),0,0);
+				fiber_sleep(800);
+				display.clear();
+				fiber_sleep(200);
+
+				if (pairingStatus & MICROBIT_BLE_PAIR_COMPLETE)
+					break;
+			}
+
+			fiber_sleep(1000);
+		}
+
+		if (pairingStatus & MICROBIT_BLE_PAIR_COMPLETE)
+		{
+			if (pairingStatus & MICROBIT_BLE_PAIR_SUCCESSFUL)
+			{
+				MicroBitImage tick("0,0,0,0,0\n0,0,0,0,255\n0,0,0,255,0\n255,0,255,0,0\n0,255,0,0,0\n");
+				display.print(tick,0,0,0);
+                fiber_sleep(15000);
+		        timeInPairingMode = MICROBIT_BLE_PAIRING_TIMEOUT * 30;
+
+                /*
+                 * Disabled, as the API to return the number of active bonds is not reliable at present...
+                 *
+                display.clear();
+                ManagedString c(getBondCount());
+                ManagedString c2("/");
+                ManagedString c3(MICROBIT_BLE_MAXIMUM_BONDS);
+                ManagedString c4("USED");
+
+                display.scroll(c+c2+c3+c4);
+                *
+                *
+                */
+			}
+			else
+			{
+				MicroBitImage cross("255,0,0,0,255\n0,255,0,255,0\n0,0,255,0,0\n0,255,0,255,0\n255,0,0,0,255\n");
+				display.print(cross,0,0,0);
+			}
+		}
+
+		fiber_sleep(100);
+		timeInPairingMode++;
+
+		if (timeInPairingMode >= MICROBIT_BLE_PAIRING_TIMEOUT * 30)
+			microbit_reset();
+	}
+}
+
+/**
+ * Displays the device's ID code as a histogram on the provided MicroBitDisplay instance.
+ *
+ * @param display The display instance used for displaying the histogram.
+ */
+void MicroBitBLEManager::showNameHistogram(MicroBitDisplay &display)
+{
+    uint32_t n = NRF_FICR->DEVICEID[1];
+    int ld = 1;
+    int d = MICROBIT_DFU_HISTOGRAM_HEIGHT;
+    int h;
+
+    display.clear();
+    for (int i=0; i<MICROBIT_DFU_HISTOGRAM_WIDTH;i++)
+    {
+        h = (n % d) / ld;
+
+        n -= h;
+        d *= MICROBIT_DFU_HISTOGRAM_HEIGHT;
+        ld *= MICROBIT_DFU_HISTOGRAM_HEIGHT;
+
+        for (int j=0; j<h+1; j++)
+            display.image.setPixelValue(MICROBIT_DFU_HISTOGRAM_WIDTH-i-1, MICROBIT_DFU_HISTOGRAM_HEIGHT-j-1, 255);
+    }
+}