Template for the workshop 'Ontwerp je eigen Bluetooth LE device' for the NIOC Congress 2018

Ontwikkel je eigen Bluetooth LE device

Welkom bij de workshop, tijdens deze workshop ga je aan de gang met het ontwikkelen van een Bluetooth LE device met behulp van MBED. Tijdens de workshop heb je het volgende nodig:

  • Het bluetooth LE ontwikkel bord, dat door ons beschikbaar is gesteld
  • Een laptop met internet toegang. Voor het ontwikkelen van de software gebruik je de online mbed ontwikkel omgeving
  • Een redelijke kennis van C++ programmeren, de software voor het device wordt ontwikkeld met C++.

De handleiding en presentatie zijn hier te vinden:
Presentatie
Handleiding

Opzetten van de ontwikkel omgeving

Stap 1: Registreer je op de MBED website.
Ga naar de MBED OS website en registreer je op de website om toegang te krijgen tot de online IDE: os.mbed.com

Stap 2: Voeg het NRF52-DK ontwikkelbord toe aan je ontwikkelomgeving.
Het ontwikkelbord wat beschikbaar is gesteld is het NRF52-DK ontwikkelbord, informatie over dit bord is te vinden via de volgende pagina: https://os.mbed.com/platforms/Nordic-nRF52-DK/

Bekijk deze pagina, op deze pagina is ook een knop aanwezig om het platform toe te voegen aan je online ontwikkelomgeving. Gebruik de knop om het platform toe te voegen aan de compiler.

/media/uploads/wkleunen/add_to_compiler.png

Selecteer het NRF52-Dk platform in de IDE rechtsboven in het browser scherm.

/media/uploads/wkleunen/select_compiler.png

Stap 3: Installeer de NRF-Connect app op je smartphone.
Installeer de NRF-Connect app van Nordic op je smartphone. Deze app gaan we gebruiken om via bluetooth verbinding te maken met het ontwikkelbord. Deze app is beschikbaar voor zowel Android als IOS.

/media/uploads/wkleunen/nrf_connect.png

De ontwikkelomgeving is nu juist geconfigureerd om te gaan beginnen.

Het ontwikkelen van je eigen Bluetooth LE device

In een aantal stappen gaan we nu een eigen Bluetooth LE device ontwikkelen die aangestuurd kan worden m.b.v. je smartphone met de Nordic NRFConnect LE app.

Stap 1: Opzetten van een basis applicatie.
We beginnen vanaf een basis applicatie die reeds voor geprogrammeerd is. Hiervoor moet je een bestaand project importeren / clonen in de online ontwikkel omgeving. Hiervoor gebruiken we je import van de MBED omgeving:

/media/uploads/wkleunen/mbed_import.png

  1. Klik op de knop import
  2. Selecteer het tabblad ‘Programs’
  3. Gebruik de link om een programma te importeren vanaf een URL

/media/uploads/wkleunen/mbed_import_url.png

Importeer het programma vanaf de volgende url: https://os.mbed.com/users/wkleunen/code/saxion_nioc/

Stap 2: Compileer het programma en laad het op het ontwikkelbord.
Nadat je de basis applicatie hebt geimporteerd kun je hem compileren m.b.v. de ‘Compile’ button. Druk op compile, het project wordt nu gecompileerd op de server van mbed. Als de compilatie klaar en succesvol is, wordt de gecompileerde firmware automatisch gedownload in de vorm van een ‘hex’ bestand.

/media/uploads/wkleunen/mbed_compile.png

Sluit het ontwikkelbord aan op je laptop via de USB kabel. Na installatie van de noodzakelijke drivers, verschijnt er een nieuwe drive ‘JLINK’. Sleep het ‘hex’ bestand naar de JLINK drive om de applicatie te schrijven naar het ontwikkelbord. De applicatie wordt op het bord geprogrammeerd en wordt nu gestart.

/media/uploads/wkleunen/nrf_connect_scan.png

Start nu de NRF-Connect app op je smartphone. Zodra je op ‘Scan’ drukt wordt het bord gedetecteerd met de naam ‘Nioc_BLE’. Aangezien er meer mogelijk meerdere borden worden gedetecteerd met dezelfde naam weet je dus niet of dit specifiek jouw bord is. Ga daarvoor verder naar de volgende stap.

Stap 3: Aanpassen van de device naam.
Pak de code van het basis programma er bij en pas nu de naam van het device aan in de code, succes! Na het aanpassen van de code kun je het programma opnieuw compileren en op het device programmeren door de hex file naar de USB drive te slepen. Je hoeft niet eerst de oude applicatie weg te halen, de nieuwe hex file wordt over de oude applicatie heen gezet en automatisch gestart.

Stap 4: Stuur de LEDS aan m.b.v. een Gatt Characteristic.
In de voorbeeld code is reeds een Gatt Characteristic gedefinieerd genaamd ‘Alert Level’. M.b.v. de NRF Connect app kan na het connecten met het BLE device een waarde (byte) naar dit characteristic worden geschreven.

/media/uploads/wkleunen/nrf_connect_write.png

Connect met je BLE device m.b.v. NRF Connect en stuur een waarde naar de ‘Alert Level’ characteristic:

  1. Druk op ‘Write value’ knop (pijltje omhoog) in NRF connect om een waarde te gaan schrijven. (N.B. Als je de knop niet ziet, dan ben je niet verbonden met het BLE bord, druk op connect om te verbinden).
  2. Kies een waarde / alert level die naar de characteristic moet worden gestuurd
  3. Druk op send om de waarde naar het device te schrijven

In de code wordt de callback functie ‘onDataWrittenCallback’ aangeroepen als een waarde geschreven wordt naar de characteristic. Pas deze functie aan, dat de waarde gebruikt wordt om de LEDS op het bord aan te sturen. Er zijn 4 leds op het bord die aangestuurd kunnen worden, m.b.v. de ‘EnableLed’ en ‘DisableLed’ functies kunnen deze aan en uit worden gezet.

Stap 5: Pas de LEDS aan m.b.v. de knoppen op het bord.
Op het bord zijn ook 4 knoppen aanwezig. In de code is er reeds voor gezorgd dat op het moment dat één van de knoppen wordt ingedrukt, de functie ‘ButtonPressed’ wordt aangeroepen.

Pas de code aan dat de LEDs worden aangepast zodra op één van de knoppen wordt gedrukt. Maak het ook zo dat de ‘alert_characteristic’ wordt aangepast zodra één van de knoppen wordt ingedrukt. De waarde van de characteristic kan als volgt worden aangepast:

Code voor aanpassen van waarde van characteristic in mbed

uint8_t alert_value = 3;
BLE::Instance().gattServer().write(alert_characteristic.getValueHandle(),   &alert_value, sizeof(uint8_t));

Aangezien je de waarde van de ‘alert_characteristic’ aanpast, is het mogelijk om de nieuwe waarde via Bluetooth draadloos op te vragen aan het device.

/media/uploads/wkleunen/nrf_connect_push.png

  • Connect weer met je device en druk op de ‘lezen’ knop (1) van de ‘Alert Level’ characteristic. De huidige waarde van het characteristic wordt nu getoond (2).
  • Het is ook mogelijk om notifications aan te zetten, doe dit met behulp van de notifications knop (3). Als notifications aan staan is het niet meer nodig om de waarde van de characteristic handmatig op te vragen. Zodra de waarde van de characteristic wordt aangepast (door op een knop te drukken), wordt de nieuwe waarde gepushed naar je smartphone en wordt de waarde getoond (2).

Nawoord

Bedankt voor het bijwonen van de workshop ‘Ontwikkel je eigen Bluetooth LE device’. Wij zijn erg enthousiast over het MBED platform en de beschikbare hardware. Je hebt nu ervaren hoe eenvoudig het is om aan de gang te gaan met embedded software ontwikkeling onderwijs door gebruik te maken van het MBED online platform. Daarnaast biedt Bluetooth LE veel interessante mogelijkheden om (Embedded) software onderwerpen te behandelen en wordt het in de praktijk steeds meer gebruikt.

Naast het online platform biedt MBED de mogelijkheid om offline de tools te gebruiken. Dit is eenvoudig te installeren m.b.v. een installer: https://os.mbed.com/docs/latest/tools/installing-with-the-windows-installer.html

Hoewel dit prettig is als het druk is op de MBED cloud server, is onze ervaring dat studenten toch eigenlijk altijd met de online omgeving blijven werken. Binnen ons onderwijs wordt het MBED platform gebruikt voor microcontroller programmeren en draadloze communicatie.

Naast het ontwikkelen van een Bluetooth LE device met MBED is het ook mogelijk om Bluetooth LE te gebruiken voor Android en IOS software ontwikkeling en web software door de Bluetooth LE stack op een Raspberry PI te combineren met web m.b.v. NodeJS door gebruik te maken van de Noble library: https://github.com/sandeepmistry/noble

Wij zijn zeer geinteresseerd in de mogelijkheden die jullie zien voor MBED en BLE in het onderwijs,

Wouter van Kleunen
Wilco Bonestroo
HBO-ICT Saxion

main.cpp

Committer:
wkleunen
Date:
2018-02-02
Revision:
1:33f443eaa422
Parent:
0:23c0b182cbe6

File content as of revision 1:33f443eaa422:

#include "mbed.h"
#include "rtos.h"
#include "ble/BLE.h"

// Define the interface to the leds, they are digital outputs when can be
// enabled or disabled
enum { total_leds = 4 };
DigitalOut led[total_leds] = { D6, D7, D8, D9 };

// Function to enable led with index 0..3
void EnableLed(uint8_t index)
{
    if(index < total_leds)
        led[index] = 0;
}

// Function to disable led with index 0..3
void DisableLed(uint8_t index)
{
    if(index < total_leds)
        led[index] = 1;
}

// Define the interface to the buttons, they are configured to 
// generate an interrupt and call the ButtonPress function
enum { total_buttons = 4 };
InterruptIn buttons[total_buttons] = { D2, D3, D4, D5 };

// The name of this device
// TODO .. Change the name of this device
const static char DEVICE_NAME[] = "Nioc_BLE";

// The UUID (Unique Universal Identifier) of our Alert service and Alert characteristic
enum { ALERT_SERVICE_UUID = 0x1811 };
enum { ALERT_VALUE_UUID = 0x2A06 };

// Definition our alert characteristic
ReadWriteGattCharacteristic<uint8_t> alert_characteristic(ALERT_VALUE_UUID, NULL, 
    GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_NOTIFY);

// This function is called when data is written to a characteristic
void onDataWrittenCallback(const GattWriteCallbackParams *params)
{
    // Check if it really was the alert characteristic that was written by checking the handle
    if(params->handle == alert_characteristic.getValueHandle() && params->len == sizeof(uint8_t))
    {
        // We got a new value for the alert characteristic, do something useful with this
        uint8_t value = *(uint8_t const *)(params->data);
        // TODO: .. do something useful with this value
    }
}

// This function is called when the gatt connection is disconnected
// we have to manually re-enable the advertisements again when this occurs
void disconnectionCallback(const Gap::DisconnectionCallbackParams_t *)
{
    BLE::Instance().gap().startAdvertising();
}

// This function is called when the BLE stack is initialized
// Setup the GATT Service and Advertisement here for the example
void bleInitComplete(BLE::InitializationCompleteCallbackContext *params)
{
    if(params->error != BLE_ERROR_NONE)
        return;
    
    BLE &ble = BLE::Instance();
    
    // Initialize the default of our alert characteristic
    uint8_t init_alert_value = 0;
    BLE::Instance().gattServer().write(alert_characteristic.getValueHandle(), &init_alert_value, sizeof(uint8_t)); 
    
    // First create our GATT Service (basicly a list of characteristics )
    enum { TOTAL_SERVICE_CHARACTERTERISTICS = 1 };
    GattCharacteristic *characteristics_table[TOTAL_SERVICE_CHARACTERTERISTICS] = { &alert_characteristic };
    GattService alert_service(ALERT_SERVICE_UUID, characteristics_table, TOTAL_SERVICE_CHARACTERTERISTICS);
    ble.gattServer().addService(alert_service);
    
    // Setup the BLE callbacks    
    ble.gap().onDisconnection(disconnectionCallback);
    ble.gattServer().onDataWritten(onDataWrittenCallback);
    
    // Configure the advertisement, we add the name of the device to the advertisement and some service data
    ble.gap().setDeviceName((uint8_t const *)DEVICE_NAME);
    ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::BREDR_NOT_SUPPORTED | GapAdvertisingData::LE_GENERAL_DISCOVERABLE);
    ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::SHORTENED_LOCAL_NAME, (uint8_t const *)DEVICE_NAME, sizeof(DEVICE_NAME) - 1);
    
    uint8_t service_data[] = { 0x11, 0x18, 0x00 };
    ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::SERVICE_DATA, service_data, sizeof(service_data));

    // Start advertising our device information now
    ble.gap().setAdvertisingType(GapAdvertisingParams::ADV_CONNECTABLE_UNDIRECTED);
    ble.gap().setAdvertisingInterval(250);
    ble.gap().startAdvertising();
}

// This function is called when a button is pressed
void ButtonPressed(uint8_t index)
{
    // TODO: ... do something useful here when a button is pressed
}

// Eventqueue to handle incoming events
EventQueue eventQueue(16 * EVENTS_EVENT_SIZE);

// Function required to process events from the BLE Stack
void scheduleBleEventsProcessing(BLE::OnEventsToProcessCallbackContext *context)
{
    eventQueue.call(Callback<void()>(&BLE::Instance(), &BLE::processEvents));
}

// Let's setup the BLE stack
int main(void)
{    
    // Configure the led to the default state
    EnableLed(0);
    DisableLed(1);
    DisableLed(2);
    DisableLed(3);
    
    // Configure the button callbacks
    buttons[0].mode(PullUp);
    buttons[0].fall(eventQueue.event(ButtonPressed, 0));
    buttons[1].mode(PullUp);
    buttons[1].fall(eventQueue.event(ButtonPressed, 1));
    buttons[2].mode(PullUp);
    buttons[2].fall(eventQueue.event(ButtonPressed, 2));
    buttons[3].mode(PullUp);
    buttons[3].fall(eventQueue.event(ButtonPressed, 3));
    
    // Start the BLE Stack 
    BLE::Instance().onEventsToProcess(scheduleBleEventsProcessing);
    BLE::Instance().init(bleInitComplete);
    eventQueue.dispatch_forever();
    return 0;
}