A simple program that uses the UARTService to transfer string data over GATT. Can be used with the nRF Toolbox app for rapid prototyping.

Dependencies:   BLE_API mbed nRF51822

ABOUT

This program is intended to make it easy to prototype a sensor that sends data to a smart device using Bluetooth Low Energy (AKA Bluetooth Smart). Nordic's UARTService is used because it allows string data to be exchanged over GATT using Nordic's nRF Toolbox app (available for download in both Apple and Google app stores).

HARDWARE

By default the program assumes that the hardware being used is Nordic's nRF51822-mKIT (PCA10024) along with an analog sensor.

SETUP

A simple light sensor is a great example of an analog sensor and requires only:

Example breadboard circuit (with the photoresistor acting as part of a voltage divider):

                   +---------------+        
                   |   XXXXXXXXXX  |        
                   |             X |        
                   | PHOTORESISTOR |        
VCC                | X             |        
 ^                 |  XXXXXXXXXX   |        
 |                 +---------------+        
 |                 |               |        
 |     +-----+     |               |        
 +-----+ 10k +-----+               +------+ 
       +-----+     |               |      | 
                   |               |      | 
P0.01+-------------+               |     -+-
                   |               |     GND
                   |               |        
                                            
             NOTE:                          

             It doesn't matter which lead of
             the sensor is connected to VCC 
             and which is connected to GND. 

The value that will be read on the analog pin will decrease if the sensor is exposed to brighter light.

VCC is about 2.9V when the board is powered via USB and 2.2V when using the 2032 coin cell battery.

SOFTWARE

The only file in the program that is not part of the normal libraries is main.cpp.

The easiest way to get started is to use the precompiled nRF Toolbox app on your smart device (iPhone4s or newer, Android 4.3 or newer).

SETUP

It is recommended to change the SHORT_NAME variable to differentiate the device from other devices in the area (default is 'HACKDEMO'). Note that the SHORT_NAME string will probably be used as the description when the device is discovered by scanners.

The nRF51822-mKIT has a UART-to-USB adapter that can be used for debugging. The default configuration is 19200 baud, 8N1, no flow control. On Linux, the serial port enumerates as /dev/ttyACM0. On Windows, the port is displayed in the Device Manager as 'mbed Serial Port (COMX)'. Note that the serial port will be temporarily disabled when new programs are loaded.

USAGE

After the program is compiled and downloaded it will advertise continuously.

To connect:

  1. Open the nRF Toolbox app
  2. Select the 'UART' mode
  3. Click the 'CONNECT' button
  4. Select the device with the correct SHORT_NAME from the 'AVAILABLE DEVICES' list
  5. Wait for the primary service to be found

All of the commands that are sent to the mKIT will be acknowledged with a string in the form 'OK' or a string in the form 'ERROR: ...'. The following commands are defined:

  • Write a '1' character to turn LED_1 on
  • Write a '0' character to turn LED_1 off
  • Write an 'a' character to read P0.01 as a 16-bit integer

If an error occurs then LED_2 will be turned on and a line number and non-zero error code will be printed to the UART.

In this screenshot the up-arrow button sends a '1', down-arrow sends a '0', and the info button sends an 'a': /media/uploads/foolsday/screenshot_2014-12-15-20-32-23.png

Revision:
0:442c7a6f1978
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main.cpp	Mon Dec 15 21:05:18 2014 -0800
@@ -0,0 +1,193 @@
+/* mbed Microcontroller Library
+ * Copyright (c) 2006-2013 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.
+ */
+
+#include "mbed.h"
+#include "BLEDevice.h"
+#include "UARTService.h"
+#include "nrf_temp.h"
+
+#define MAX_REPLY_LEN           (UARTService::BLE_UART_SERVICE_MAX_DATA_LEN)
+#define SENSOR_READ_INTERVAL_S  (0.5F) 
+#define ADV_INTERVAL_MS         (1000UL)
+#define UART_BAUD_RATE          (19200UL)
+#define DEVICE_NAME             ("DEMO SENSOR") // This can be read AFTER connecting to the device.
+#define SHORT_NAME              ("HACKDEMO")    // Keep this short: max 8 chars if a 128bit UUID is also advertised.
+
+#define DEBUG(...)              { m_serial_port.printf(__VA_ARGS__); }
+
+
+BLEDevice   m_ble;
+Serial      m_serial_port(p9, p11);  // TX pin, RX pin
+DigitalOut  m_cmd_led(LED1);
+DigitalOut  m_error_led(LED2);
+AnalogIn    m_analog_in(p1);
+uint16_t    m_analog_in_value;
+UARTService *m_uart_service_ptr;
+
+
+/**
+ * This callback is used whenever a disconnection occurs.
+ */
+void disconnectionCallback(Gap::Handle_t handle, Gap::DisconnectionReason_t reason)
+{
+    switch (reason) {
+    case Gap::REMOTE_USER_TERMINATED_CONNECTION:
+        DEBUG("Disconnected (REMOTE_USER_TERMINATED_CONNECTION)\n\r");
+        break;
+    case Gap::LOCAL_HOST_TERMINATED_CONNECTION:
+        DEBUG("Disconnected (LOCAL_HOST_TERMINATED_CONNECTION)\n\r");
+        break;
+    case Gap::CONN_INTERVAL_UNACCEPTABLE:
+        DEBUG("Disconnected (CONN_INTERVAL_UNACCEPTABLE)\n\r");
+        break;
+    }
+
+    DEBUG("Restarting the advertising process\n\r");
+    m_ble.startAdvertising();
+}
+
+
+/**
+ * This callback is used whenever the host writes data to one of our GATT characteristics.
+ */
+void dataWrittenCallback(const GattCharacteristicWriteCBParams *params)
+{
+    // Ensure that initialization is finished and the host has written to the TX characteristic.
+    if ((m_uart_service_ptr != NULL) && (params->charHandle == m_uart_service_ptr->getTXCharacteristicHandle())) {
+        uint8_t  buf[MAX_REPLY_LEN];
+        uint32_t len = 0;
+
+        if (1 == params->len) {
+            switch (params->data[0]) {
+            case '0':
+                m_cmd_led = 0;
+                len = snprintf((char*) buf, MAX_REPLY_LEN, "OK");
+                break;
+            case '1':
+                m_cmd_led = 1;
+                len = snprintf((char*) buf, MAX_REPLY_LEN, "OK");
+                break;
+            case 'a':
+                len = snprintf((char*) buf, MAX_REPLY_LEN, "%d", m_analog_in_value);
+                break;
+            default:
+                len = snprintf((char*) buf, MAX_REPLY_LEN, "ERROR: Unknown char");
+                break;
+            }
+        }
+        else
+        {
+            len = snprintf((char*) buf, MAX_REPLY_LEN, "ERROR: Invalid len");
+        }
+
+        m_ble.updateCharacteristicValue(m_uart_service_ptr->getRXCharacteristicHandle(), buf, len);
+
+        DEBUG("%d bytes received from host\n\r", params->len);
+    }
+}
+
+
+/**
+ * This callback is used whenever a write to a GATT characteristic causes data to be sent to the host.
+ */
+void dataSentCallback(unsigned count)
+{
+    // NOTE: The count always seems to be 1 regardless of data.
+    DEBUG("%d bytes sent to host\n\r", count);
+}
+
+
+/**
+ * This callback is scheduled to be called periodically via a low-priority interrupt.
+ */
+void periodicCallback(void)
+{
+    m_analog_in_value = m_analog_in.read_u16();
+}
+
+
+void error(ble_error_t err, uint32_t line)
+{
+    m_error_led = 1;
+    DEBUG("Error %d on line number %d\n\r", err, line);
+}
+
+
+int main(void)
+{
+    ble_error_t err;
+    Ticker      ticker;
+
+    m_serial_port.baud(UART_BAUD_RATE);
+
+    DEBUG("Initialising\n\r");
+
+    m_cmd_led      = 0;
+    m_error_led    = 0;
+    m_analog_in_value    = 0;
+
+    ticker.attach(periodicCallback, SENSOR_READ_INTERVAL_S);
+
+    m_ble.init();
+    m_ble.onDisconnection(disconnectionCallback);
+    m_ble.onDataWritten(dataWrittenCallback);
+    m_ble.onDataSent(dataSentCallback);
+
+    // Set the TX power in dBm units.
+    // Possible values (in decreasing order): 4, 0, -4, -8, -12, -16, -20.
+    err = m_ble.setTxPower(4);
+    if (BLE_ERROR_NONE != err) {
+        error(err, __LINE__);
+    }
+
+    // Setup advertising (GAP stuff).
+    err = m_ble.setDeviceName(DEVICE_NAME);
+    if (BLE_ERROR_NONE != err) {
+        error(err, __LINE__);
+    }
+
+    err = m_ble.accumulateAdvertisingPayload(GapAdvertisingData::BREDR_NOT_SUPPORTED);
+    if (BLE_ERROR_NONE != err) {
+        error(err, __LINE__);
+    }
+
+    m_ble.setAdvertisingType(GapAdvertisingParams::ADV_CONNECTABLE_UNDIRECTED);
+
+    err = m_ble.accumulateAdvertisingPayload(GapAdvertisingData::SHORTENED_LOCAL_NAME,
+                                                (const uint8_t *)SHORT_NAME,
+                                                (sizeof(SHORT_NAME) - 1));
+    if (BLE_ERROR_NONE != err) {
+        error(err, __LINE__);
+    }
+
+    err = m_ble.accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LIST_128BIT_SERVICE_IDS,
+                                                (const uint8_t *)UARTServiceUUID_reversed,
+                                                sizeof(UARTServiceUUID_reversed));
+    if (BLE_ERROR_NONE != err) {
+        error(err, __LINE__);
+    }
+
+    m_ble.setAdvertisingInterval(Gap::MSEC_TO_ADVERTISEMENT_DURATION_UNITS(ADV_INTERVAL_MS));
+    m_ble.startAdvertising();
+
+    // Create a UARTService object (GATT stuff).
+    UARTService uartService(m_ble);
+    m_uart_service_ptr = &uartService;
+
+    while (true) {
+        m_ble.waitForEvent();
+    }
+}