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

Committer:
wkleunen
Date:
Mon Jan 08 12:32:21 2018 +0000
Revision:
0:23c0b182cbe6
Child:
1:33f443eaa422
Initial import saxion nioc BLE example application

Who changed what in which revision?

UserRevisionLine numberNew contents of line
wkleunen 0:23c0b182cbe6 1 #include "mbed.h"
wkleunen 0:23c0b182cbe6 2 #include "rtos.h"
wkleunen 0:23c0b182cbe6 3 #include "ble/BLE.h"
wkleunen 0:23c0b182cbe6 4
wkleunen 0:23c0b182cbe6 5 // Define the interface to the leds, they are digital outputs when can be
wkleunen 0:23c0b182cbe6 6 // enabled or disabled
wkleunen 0:23c0b182cbe6 7 enum { total_leds = 4 };
wkleunen 0:23c0b182cbe6 8 DigitalOut led[total_leds] = { D6, D7, D8, D9 };
wkleunen 0:23c0b182cbe6 9
wkleunen 0:23c0b182cbe6 10 // Function to enable led with index 0..3
wkleunen 0:23c0b182cbe6 11 void EnableLed(uint8_t index)
wkleunen 0:23c0b182cbe6 12 {
wkleunen 0:23c0b182cbe6 13 if(index < total_leds)
wkleunen 0:23c0b182cbe6 14 led[index] = 0;
wkleunen 0:23c0b182cbe6 15 }
wkleunen 0:23c0b182cbe6 16
wkleunen 0:23c0b182cbe6 17 // Function to disable led with index 0..3
wkleunen 0:23c0b182cbe6 18 void DisableLed(uint8_t index)
wkleunen 0:23c0b182cbe6 19 {
wkleunen 0:23c0b182cbe6 20 if(index < total_leds)
wkleunen 0:23c0b182cbe6 21 led[index] = 1;
wkleunen 0:23c0b182cbe6 22 }
wkleunen 0:23c0b182cbe6 23
wkleunen 0:23c0b182cbe6 24 // Define the interface to the buttons, they are configured to
wkleunen 0:23c0b182cbe6 25 // generate an interrupt and call the ButtonPress function
wkleunen 0:23c0b182cbe6 26 enum { total_buttons = 4 };
wkleunen 0:23c0b182cbe6 27 InterruptIn buttons[total_buttons] = { D2, D3, D4, D5 };
wkleunen 0:23c0b182cbe6 28
wkleunen 0:23c0b182cbe6 29 // The name of this device
wkleunen 0:23c0b182cbe6 30 // TODO .. Change the name of this device
wkleunen 0:23c0b182cbe6 31 const static char DEVICE_NAME[] = "Nioc_BLE";
wkleunen 0:23c0b182cbe6 32
wkleunen 0:23c0b182cbe6 33 // The UUID (Unique Universal Identifier) of our Led service and Led characteristic
wkleunen 0:23c0b182cbe6 34 enum { LED_SERVICE_UUID = 0xA000 };
wkleunen 0:23c0b182cbe6 35 enum { LED_VALUE_UUID = 0xA001 };
wkleunen 0:23c0b182cbe6 36
wkleunen 0:23c0b182cbe6 37 // Definition our led characteristic
wkleunen 0:23c0b182cbe6 38 ReadWriteGattCharacteristic<uint8_t> led_characteristic(LED_VALUE_UUID, NULL,
wkleunen 0:23c0b182cbe6 39 GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_NOTIFY);
wkleunen 0:23c0b182cbe6 40
wkleunen 0:23c0b182cbe6 41 // This function is called when data is written to a characteristic
wkleunen 0:23c0b182cbe6 42 void onDataWrittenCallback(const GattWriteCallbackParams *params)
wkleunen 0:23c0b182cbe6 43 {
wkleunen 0:23c0b182cbe6 44 // Check if it really was the led characteristic that was written by checking the handle
wkleunen 0:23c0b182cbe6 45 if(params->handle == led_characteristic.getValueHandle() && params->len == sizeof(uint8_t))
wkleunen 0:23c0b182cbe6 46 {
wkleunen 0:23c0b182cbe6 47 // We got a new value for the led characteristic, do something useful with this
wkleunen 0:23c0b182cbe6 48 uint8_t led_value = *(uint8_t const *)(params->data);
wkleunen 0:23c0b182cbe6 49 // TODO: .. do something useful with this value
wkleunen 0:23c0b182cbe6 50 }
wkleunen 0:23c0b182cbe6 51 }
wkleunen 0:23c0b182cbe6 52
wkleunen 0:23c0b182cbe6 53 // This function is called when the gatt connection is disconnected
wkleunen 0:23c0b182cbe6 54 // we have to manually re-enable the advertisements again when this occurs
wkleunen 0:23c0b182cbe6 55 void disconnectionCallback(const Gap::DisconnectionCallbackParams_t *)
wkleunen 0:23c0b182cbe6 56 {
wkleunen 0:23c0b182cbe6 57 BLE::Instance().gap().startAdvertising();
wkleunen 0:23c0b182cbe6 58 }
wkleunen 0:23c0b182cbe6 59
wkleunen 0:23c0b182cbe6 60 // This function is called when the BLE stack is initialized
wkleunen 0:23c0b182cbe6 61 // Setup the GATT Service and Advertisement here for the example
wkleunen 0:23c0b182cbe6 62 void bleInitComplete(BLE::InitializationCompleteCallbackContext *params)
wkleunen 0:23c0b182cbe6 63 {
wkleunen 0:23c0b182cbe6 64 if(params->error != BLE_ERROR_NONE)
wkleunen 0:23c0b182cbe6 65 return;
wkleunen 0:23c0b182cbe6 66
wkleunen 0:23c0b182cbe6 67 BLE &ble = BLE::Instance();
wkleunen 0:23c0b182cbe6 68
wkleunen 0:23c0b182cbe6 69 // Initialize the default of our led characteristic
wkleunen 0:23c0b182cbe6 70 uint8_t init_led_value = 0;
wkleunen 0:23c0b182cbe6 71 BLE::Instance().gattServer().write(led_characteristic.getValueHandle(), &init_led_value, sizeof(uint8_t));
wkleunen 0:23c0b182cbe6 72
wkleunen 0:23c0b182cbe6 73 // First create our GATT Service (basicly a list of characteristics )
wkleunen 0:23c0b182cbe6 74 enum { TOTAL_SERVICE_CHARACTERTERISTICS = 1 };
wkleunen 0:23c0b182cbe6 75 GattCharacteristic *characteristics_table[TOTAL_SERVICE_CHARACTERTERISTICS] = { &led_characteristic };
wkleunen 0:23c0b182cbe6 76 GattService led_service(LED_SERVICE_UUID, characteristics_table, TOTAL_SERVICE_CHARACTERTERISTICS);
wkleunen 0:23c0b182cbe6 77 ble.gattServer().addService(led_service);
wkleunen 0:23c0b182cbe6 78
wkleunen 0:23c0b182cbe6 79 // Setup the BLE callbacks
wkleunen 0:23c0b182cbe6 80 ble.gap().onDisconnection(disconnectionCallback);
wkleunen 0:23c0b182cbe6 81 ble.gattServer().onDataWritten(onDataWrittenCallback);
wkleunen 0:23c0b182cbe6 82
wkleunen 0:23c0b182cbe6 83 // Configure the advertisement, we add the name of the device to the advertisement and some service data
wkleunen 0:23c0b182cbe6 84 ble.gap().setDeviceName((uint8_t const *)DEVICE_NAME);
wkleunen 0:23c0b182cbe6 85 ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::BREDR_NOT_SUPPORTED | GapAdvertisingData::LE_GENERAL_DISCOVERABLE);
wkleunen 0:23c0b182cbe6 86 ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::SHORTENED_LOCAL_NAME, (uint8_t const *)DEVICE_NAME, sizeof(DEVICE_NAME));
wkleunen 0:23c0b182cbe6 87
wkleunen 0:23c0b182cbe6 88 uint8_t service_data[] = { 0x11, 0x18, 0x00 };
wkleunen 0:23c0b182cbe6 89 ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::SERVICE_DATA, service_data, sizeof(service_data));
wkleunen 0:23c0b182cbe6 90
wkleunen 0:23c0b182cbe6 91 // Start advertising our device information now
wkleunen 0:23c0b182cbe6 92 ble.gap().setAdvertisingType(GapAdvertisingParams::ADV_CONNECTABLE_UNDIRECTED);
wkleunen 0:23c0b182cbe6 93 ble.gap().setAdvertisingInterval(250);
wkleunen 0:23c0b182cbe6 94 ble.gap().startAdvertising();
wkleunen 0:23c0b182cbe6 95 }
wkleunen 0:23c0b182cbe6 96
wkleunen 0:23c0b182cbe6 97 // This function is called when a button is pressed
wkleunen 0:23c0b182cbe6 98 void ButtonPressed(uint8_t index)
wkleunen 0:23c0b182cbe6 99 {
wkleunen 0:23c0b182cbe6 100 // TODO: ... do something useful here when a button is pressed
wkleunen 0:23c0b182cbe6 101 }
wkleunen 0:23c0b182cbe6 102
wkleunen 0:23c0b182cbe6 103 // Eventqueue to handle incoming events
wkleunen 0:23c0b182cbe6 104 EventQueue eventQueue(16 * EVENTS_EVENT_SIZE);
wkleunen 0:23c0b182cbe6 105
wkleunen 0:23c0b182cbe6 106 // Function required to process events from the BLE Stack
wkleunen 0:23c0b182cbe6 107 void scheduleBleEventsProcessing(BLE::OnEventsToProcessCallbackContext *context)
wkleunen 0:23c0b182cbe6 108 {
wkleunen 0:23c0b182cbe6 109 eventQueue.call(Callback<void()>(&BLE::Instance(), &BLE::processEvents));
wkleunen 0:23c0b182cbe6 110 }
wkleunen 0:23c0b182cbe6 111
wkleunen 0:23c0b182cbe6 112 // Let's setup the BLE stack
wkleunen 0:23c0b182cbe6 113 int main(void)
wkleunen 0:23c0b182cbe6 114 {
wkleunen 0:23c0b182cbe6 115 // Configure the led to the default state
wkleunen 0:23c0b182cbe6 116 EnableLed(0);
wkleunen 0:23c0b182cbe6 117 DisableLed(1);
wkleunen 0:23c0b182cbe6 118 DisableLed(2);
wkleunen 0:23c0b182cbe6 119 DisableLed(3);
wkleunen 0:23c0b182cbe6 120
wkleunen 0:23c0b182cbe6 121 // Configure the button callbacks
wkleunen 0:23c0b182cbe6 122 buttons[0].mode(PullUp);
wkleunen 0:23c0b182cbe6 123 buttons[0].fall(eventQueue.event(ButtonPressed, 0));
wkleunen 0:23c0b182cbe6 124 buttons[1].mode(PullUp);
wkleunen 0:23c0b182cbe6 125 buttons[1].fall(eventQueue.event(ButtonPressed, 1));
wkleunen 0:23c0b182cbe6 126 buttons[2].mode(PullUp);
wkleunen 0:23c0b182cbe6 127 buttons[2].fall(eventQueue.event(ButtonPressed, 2));
wkleunen 0:23c0b182cbe6 128 buttons[3].mode(PullUp);
wkleunen 0:23c0b182cbe6 129 buttons[3].fall(eventQueue.event(ButtonPressed, 3));
wkleunen 0:23c0b182cbe6 130
wkleunen 0:23c0b182cbe6 131 // Start the BLE Stack
wkleunen 0:23c0b182cbe6 132 BLE::Instance().onEventsToProcess(scheduleBleEventsProcessing);
wkleunen 0:23c0b182cbe6 133 BLE::Instance().init(bleInitComplete);
wkleunen 0:23c0b182cbe6 134 eventQueue.dispatch_forever();
wkleunen 0:23c0b182cbe6 135 return 0;
wkleunen 0:23c0b182cbe6 136 }