How to turn a WaveShare nRF51-BLE400 into a discoverable beacon using mbed

Dependencies:   mbed BLE_API nRF51822

Port for WaveShare BLE400 (chip nRF51 Aliexpress devboard)
Android Evothings good example for Nordic nRF51822-DK

As target for mbed-online used <Nordic nRF51822>
- (Large green board Nordic nRF51822-mKIT, actually deprecated on 01/03/2019)

Briefly: Handle via Evothings BLE Application 4 LEDs and 2 Buttons(via notify messages).

Figure 1: Evothings Android application screenshot

Evothings application screenshot

Android Evothings application sources

Android application APK

Happy coding!
maxxir 02/03/19

Revision:
16:a84185b72bae
Parent:
15:7e06fce6e4f8
Child:
17:9071edee0b13
--- a/main.cpp	Tue Jan 12 12:00:16 2016 +0000
+++ b/main.cpp	Fri Mar 01 19:41:55 2019 +0000
@@ -1,98 +1,277 @@
-/* mbed Microcontroller Library
- * Copyright (c) 2006-2015 ARM Limited
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+/*
+Port for WaveShare BLE400 Evothings good example for Nordic nRF51822-DK
+As target for mbed-online used <Nordic nRF51822> (Large green board Nordic nRF51822-mKIT, actually deprecated on ~01/03/2019)
+Briefly: Handle via Evothings BLE Application 4 LEDs and 2 Buttons(via notify messages).
+
+This example original:
+https://os.mbed.com/users/jensstruemper/code/Evothings-Updated/
+Android application:
+http://evothings.com/2.2/doc/examples/nordic-nRF51-ble.html
+This application explanation:
+How to turn a Nordic Semiconductor nRF51-DK into a discoverable beacon using mbed
+https://evothings.com/how-to-turn-a-nordic-semiconductor-nrf51-dk-into-a-discoverable-beacon-using-mbed/
+
+Author porting:
+Ibragimov Maksim aka maxxir
+Russia Togliatty
+01/03/2019
+*/
+
+/*
+PS.
+My Win7 CMD script string example to flash BLE400 via STLINK-V2 && OpenOCD
+content from flash_ble400.bat:
+E:\stm32_eclipse_neon\OpenOCD\bin\openocd.exe -d2 -f interface/stlink-v2.cfg ; -f target/nrf51_stlink.tcl -c "program %1 verify reset exit"; shutdown;
+
+Flash example via console:
+>flash_ble400.bat BLE_Evothings_NRF51822-BLE400.NRF51822.hex
+*/
+
+/*
+ * nRF51-DK BLEDevice service/characteristic (read/write) using mbed.org
  */
- 
+
+// uncomment if not interested in a console log
+#define CONSOLE_LOG 
+
 #include "mbed.h"
 #include "ble/BLE.h"
 
-/* Optional: Device Name, add for human read-ability */
-const static char     DEVICE_NAME[] = "ChangeMe!!";
+//-------------------------------------------------------------------------
+
+#ifdef CONSOLE_LOG
+#define INFO(x, ...)    printf(x,        ##__VA_ARGS__);
+#define INFO_NL(x, ...) printf(x "\r\n", ##__VA_ARGS__);
+#else
+#define INFO(x, ...)    
+#define INFO_NL(x, ...) 
+#endif
+
+// a little routine to print a 128-bit UUID nicely
+void INFO_UUID(const char *prefix, UUID uuid)
+{
+   uint8_t *p = (uint8_t *)uuid.getBaseUUID();
+   INFO("%s: ", prefix);
+   for (int i=0; i<16; i++)
+   {
+     INFO("%02x", p[i]);
+     if ((i == 3) || (i == 5) || (i == 7) || (i == 9)) INFO("-");
+   }
+   INFO_NL("");
+}
+
+//-------------------------------------------------------------------------
+
+// name of the device 
+const static char DEVICE_NAME[] = "nRF51-BLE400";
+
+// GATT service and characteristic UUIDs
+const UUID nRF51_GATT_SERVICE     = UUID((uint8_t *)"nRF51-DK        ");
+const UUID nRF51_GATT_CHAR_BUTTON = UUID((uint8_t *)"nRF51-DK button ");
+const UUID nRF51_GATT_CHAR_LED    = UUID((uint8_t *)"nRF51-DK led    ");
+
+#define CHARACTERISTIC_BUTTON 0
+#define CHARACTERISTIC_LED    1
+#define CHARACTERISTIC_COUNT  2
+
+// our bluetooth smart objects
+BLE                ble;
+GattService        *gatt_service;
+GattCharacteristic *gatt_characteristics[CHARACTERISTIC_COUNT];
+uint8_t             gatt_char_value[CHARACTERISTIC_COUNT];
+
+#ifdef CONSOLE_LOG
+Serial pc(USBTX,USBRX);
+#endif
+
+//-------------------------------------------------------------------------
+// button handling
+//-------------------------------------------------------------------------
+
+// define our digital in values we will be using for the characteristic
+//WaveShare BLE400 edited
+DigitalIn button1(P0_16);
+DigitalIn button2(P0_17);
+//DigitalIn button3(P0_14); //Just substitute for check, actually not present on WaveShare BLE400
+//DigitalIn button4(P0_15); //Just substitute for check, actually not present on WaveShare BLE400
+
+uint8_t button_new_value = 0;
+uint8_t button_old_value = button_new_value;
 
-/* You have up to 26 bytes of advertising data to use. */
-const static uint8_t AdvData[] = {0x01,0x02,0x03,0x04,0x05};   /* Example of hex data */
-//const static uint8_t AdvData[] = {"ChangeThisData"};         /* Example of character data */
+void monitorButtons() 
+{
+    // read in the buttons, mapped into nibble (0000 = all off, 1111 = all on)
+    button_new_value = 0;
+    button_new_value |= (button1.read() != 1); button_new_value <<= 1;
+    button_new_value |= (button2.read() != 1); button_new_value <<= 1;
+    //Should not used for WaveShare BLE400
+    /*
+    button_new_value |= (button3.read() != 1); button_new_value <<= 1;
+    button_new_value |= (button4.read() != 1); 
+    */  
+    // set the updated value of the characteristic if data has changed
+    if (button_new_value != button_old_value)
+    {
+        ble.updateCharacteristicValue(
+              gatt_characteristics[CHARACTERISTIC_BUTTON] -> getValueHandle(),
+              &button_new_value, sizeof(button_new_value));
+        button_old_value = button_new_value;
+
+        INFO_NL("  button state: [0x%02x]", button_new_value);
+    }
+}
+
+//-------------------------------------------------------------------------
+// LED handling
+//-------------------------------------------------------------------------
+//WaveShare BLE400 edited
+DigitalOut led1(P0_18);
+DigitalOut led2(P0_19);
+DigitalOut led3(P0_20);
+DigitalOut led4(P0_21);
+DigitalOut led5(P0_22); //Actually not used here
+
+uint8_t led_value = 0;
+
+/*
+void onLedDataWritten(const uint8_t* value, uint8_t length) 
+{
+    // we only care about a single byte
+    led_value = value[0];
 
-/* Optional: Restart advertising when peer disconnects */
+    // depending on the value coming through; set/unset LED's
+    if ((led_value & 0x01) != 0) led1.write(0); else led1.write(1);
+    if ((led_value & 0x02) != 0) led2.write(0); else led2.write(1);
+    if ((led_value & 0x04) != 0) led3.write(0); else led3.write(1);
+    if ((led_value & 0x08) != 0) led4.write(0); else led4.write(1);
+
+    INFO_NL("     led state: [0x%02x]", led_value);
+}
+*/
+//Adapted for WaveShare BLE400, LED_ON = HIGH (NRF51822-DK vice versa LED_ON = LOW)
+void onLedDataWritten(const uint8_t* value, uint8_t length) 
+{
+    // we only care about a single byte
+    led_value = value[0];
+
+    // depending on the value coming through; set/unset LED's
+    if ((led_value & 0x01) != 0) led1.write(1); else led1.write(0);
+    if ((led_value & 0x02) != 0) led2.write(1); else led2.write(0);
+    if ((led_value & 0x04) != 0) led3.write(1); else led3.write(0);
+    if ((led_value & 0x08) != 0) led4.write(1); else led4.write(0);
+
+    INFO_NL("     led state: [0x%02x]", led_value);
+}
+
+//-------------------------------------------------------------------------
+
+void onConnection(const Gap::ConnectionCallbackParams_t *params)
+{
+  INFO_NL(">> connected");
+
+  // set the initial values of the characteristics (for every session)
+  //led_value = 0;
+  onLedDataWritten(&led_value, 1); // force LED's to be in off state
+  //LED5=ON on connection (for BLE400)
+  led5.write(1);
+}
+
+void onDataWritten(const GattWriteCallbackParams *context) 
+{
+   // was the characteristic being written to nRF51_GATT_CHAR_LED? 
+  if (context -> handle == 
+       gatt_characteristics[CHARACTERISTIC_LED] -> getValueHandle())
+   {
+     onLedDataWritten(context -> data, context -> len);
+   } 
+}
+
 void disconnectionCallback(const Gap::DisconnectionCallbackParams_t *params)
 {
-    BLE::Instance().gap().startAdvertising();
-}
-/**
- * This function is called when the ble initialization process has failed
- */
-void onBleInitError(BLE &ble, ble_error_t error)
-{
-    /* Avoid compiler warnings */
-    (void) ble;
-    (void) error;
-    
-    /* Initialization error handling should go here */
-}    
-
-/**
- * Callback triggered when the ble initialization process has finished
- */
-void bleInitComplete(BLE::InitializationCompleteCallbackContext *params)
-{
-    BLE&        ble   = params->ble;
-    ble_error_t error = params->error;
-
-    if (error != BLE_ERROR_NONE) {
-        /* In case of error, forward the error handling to onBleInitError */
-        onBleInitError(ble, error);
-        return;
-    }
-
-    /* Ensure that it is the default instance of BLE */
-    if(ble.getInstanceID() != BLE::DEFAULT_INSTANCE) {
-        return;
-    }
-    
-    /* Set device name characteristic data */
-    ble.gap().setDeviceName((const uint8_t *) DEVICE_NAME);
-
-    /* Optional: add callback for disconnection */
-    ble.gap().onDisconnection(disconnectionCallback);
-
-    /* Sacrifice 3B of 31B to Advertising Flags */
-    ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::BREDR_NOT_SUPPORTED | GapAdvertisingData::LE_GENERAL_DISCOVERABLE );
-    ble.gap().setAdvertisingType(GapAdvertisingParams::ADV_CONNECTABLE_UNDIRECTED);
-
-    /* Sacrifice 2B of 31B to AdvType overhead, rest goes to AdvData array you define */
-    ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::MANUFACTURER_SPECIFIC_DATA, AdvData, sizeof(AdvData));
-
-    /* Optional: Add name to device */
-    //ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LOCAL_NAME, (uint8_t *)DEVICE_NAME, sizeof(DEVICE_NAME));
-
-    /* Set advertising interval. Longer interval == longer battery life */
-    ble.gap().setAdvertisingInterval(100); /* 100ms */
-
-    /* Start advertising */
-    ble.gap().startAdvertising();
+    INFO_NL(">> disconnected");
+    ble.gap().startAdvertising(); // restart advertising
+    INFO_NL(">> device advertising");
+    //LED5=OFF on connection (for BLE400)
+    led5.write(0);
 }
 
-int main(void)
+
+int main() 
 {
-    BLE& ble = BLE::Instance(BLE::DEFAULT_INSTANCE);
- 
-    /* Initialize BLE baselayer, always do this first! */
-    ble.init(bleInitComplete);
+#ifdef CONSOLE_LOG
+    // wait a second before trying to write something to console 
+    wait(1);
+#endif
+    INFO_NL(">> nRF51-BLE400 start");
+
+    // create our button characteristic (read, notify)
+    gatt_characteristics[CHARACTERISTIC_BUTTON] = 
+      new GattCharacteristic(
+            nRF51_GATT_CHAR_BUTTON, 
+            &gatt_char_value[CHARACTERISTIC_BUTTON], 1, 1,
+            GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ | 
+            GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_NOTIFY);
+
+    // create our LED characteristic (read, write)
+    gatt_characteristics[CHARACTERISTIC_LED] = 
+      new GattCharacteristic(
+            nRF51_GATT_CHAR_LED, 
+            &gatt_char_value[CHARACTERISTIC_LED], 1, 1,
+            GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ | 
+            GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE | 
+            GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE_WITHOUT_RESPONSE);
+
+    // create our service, with both characteristics
+    gatt_service = 
+      new GattService(nRF51_GATT_SERVICE, 
+                      gatt_characteristics, CHARACTERISTIC_COUNT);
+
+    // initialize our ble device
+    ble.init();
+    ble.gap().setDeviceName((uint8_t *)DEVICE_NAME);
+    INFO_NL(">> initialized device '%s'", DEVICE_NAME);
 
-    /* Infinite loop waiting for BLE events */
-    while (true) {
-        /* Save power while waiting for callback events */
-        ble.waitForEvent();
+    // configure our advertising type, payload and interval
+    ble.gap().accumulateAdvertisingPayload(
+          GapAdvertisingData::BREDR_NOT_SUPPORTED | 
+          GapAdvertisingData::LE_GENERAL_DISCOVERABLE);
+    ble.gap().accumulateAdvertisingPayload(
+          GapAdvertisingData::COMPLETE_LOCAL_NAME, 
+          (uint8_t *)DEVICE_NAME, sizeof(DEVICE_NAME));
+    ble.gap().setAdvertisingType(GapAdvertisingParams::ADV_CONNECTABLE_UNDIRECTED);
+    ble.gap().setAdvertisingInterval(160); // 100ms
+    INFO_NL(">> configured advertising type, payload and interval");
+
+    // configure our callbacks
+    ble.gap().onDisconnection(disconnectionCallback);
+    ble.gap().onConnection(onConnection);
+    ble.onDataWritten(onDataWritten);
+    INFO_NL(">> registered for callbacks");
+
+    // add our gatt service with two characteristics
+    ble.addService(*gatt_service);
+    INFO_NL(">> added GATT service with two characteristics");
+
+    // show some debugging information about service/characteristics
+    INFO_UUID(" ", nRF51_GATT_SERVICE);
+    INFO_UUID("  :", nRF51_GATT_CHAR_BUTTON);
+    INFO_UUID("  :", nRF51_GATT_CHAR_LED);
+
+    // start advertising
+    ble.gap().startAdvertising();
+    INFO_NL(">> device advertising");
+
+    // start monitoring the buttons and posting new values
+    Ticker ticker;
+    ticker.attach(monitorButtons, 0.1);  // every 10th of a second
+    INFO_NL(">> monitoring button state");
+
+    // let the device do its thing
+    INFO_NL(">> waiting for events ");
+    for (;;)
+    {
+      ble.waitForEvent();
     }
 }
+
+//-------------------------------------------------------------------------
\ No newline at end of file