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:
- Open the nRF Toolbox app
- Select the 'UART' mode
- Click the 'CONNECT' button
- Select the device with the correct SHORT_NAME from the 'AVAILABLE DEVICES' list
- 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':
main.cpp@0:442c7a6f1978, 2014-12-15 (annotated)
- Committer:
- Daniel Veilleux
- Date:
- Mon Dec 15 21:05:18 2014 -0800
- Revision:
- 0:442c7a6f1978
Initial commit.
Who changed what in which revision?
User | Revision | Line number | New contents of line |
---|---|---|---|
Daniel Veilleux |
0:442c7a6f1978 | 1 | /* mbed Microcontroller Library |
Daniel Veilleux |
0:442c7a6f1978 | 2 | * Copyright (c) 2006-2013 ARM Limited |
Daniel Veilleux |
0:442c7a6f1978 | 3 | * |
Daniel Veilleux |
0:442c7a6f1978 | 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
Daniel Veilleux |
0:442c7a6f1978 | 5 | * you may not use this file except in compliance with the License. |
Daniel Veilleux |
0:442c7a6f1978 | 6 | * You may obtain a copy of the License at |
Daniel Veilleux |
0:442c7a6f1978 | 7 | * |
Daniel Veilleux |
0:442c7a6f1978 | 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
Daniel Veilleux |
0:442c7a6f1978 | 9 | * |
Daniel Veilleux |
0:442c7a6f1978 | 10 | * Unless required by applicable law or agreed to in writing, software |
Daniel Veilleux |
0:442c7a6f1978 | 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
Daniel Veilleux |
0:442c7a6f1978 | 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
Daniel Veilleux |
0:442c7a6f1978 | 13 | * See the License for the specific language governing permissions and |
Daniel Veilleux |
0:442c7a6f1978 | 14 | * limitations under the License. |
Daniel Veilleux |
0:442c7a6f1978 | 15 | */ |
Daniel Veilleux |
0:442c7a6f1978 | 16 | |
Daniel Veilleux |
0:442c7a6f1978 | 17 | #include "mbed.h" |
Daniel Veilleux |
0:442c7a6f1978 | 18 | #include "BLEDevice.h" |
Daniel Veilleux |
0:442c7a6f1978 | 19 | #include "UARTService.h" |
Daniel Veilleux |
0:442c7a6f1978 | 20 | #include "nrf_temp.h" |
Daniel Veilleux |
0:442c7a6f1978 | 21 | |
Daniel Veilleux |
0:442c7a6f1978 | 22 | #define MAX_REPLY_LEN (UARTService::BLE_UART_SERVICE_MAX_DATA_LEN) |
Daniel Veilleux |
0:442c7a6f1978 | 23 | #define SENSOR_READ_INTERVAL_S (0.5F) |
Daniel Veilleux |
0:442c7a6f1978 | 24 | #define ADV_INTERVAL_MS (1000UL) |
Daniel Veilleux |
0:442c7a6f1978 | 25 | #define UART_BAUD_RATE (19200UL) |
Daniel Veilleux |
0:442c7a6f1978 | 26 | #define DEVICE_NAME ("DEMO SENSOR") // This can be read AFTER connecting to the device. |
Daniel Veilleux |
0:442c7a6f1978 | 27 | #define SHORT_NAME ("HACKDEMO") // Keep this short: max 8 chars if a 128bit UUID is also advertised. |
Daniel Veilleux |
0:442c7a6f1978 | 28 | |
Daniel Veilleux |
0:442c7a6f1978 | 29 | #define DEBUG(...) { m_serial_port.printf(__VA_ARGS__); } |
Daniel Veilleux |
0:442c7a6f1978 | 30 | |
Daniel Veilleux |
0:442c7a6f1978 | 31 | |
Daniel Veilleux |
0:442c7a6f1978 | 32 | BLEDevice m_ble; |
Daniel Veilleux |
0:442c7a6f1978 | 33 | Serial m_serial_port(p9, p11); // TX pin, RX pin |
Daniel Veilleux |
0:442c7a6f1978 | 34 | DigitalOut m_cmd_led(LED1); |
Daniel Veilleux |
0:442c7a6f1978 | 35 | DigitalOut m_error_led(LED2); |
Daniel Veilleux |
0:442c7a6f1978 | 36 | AnalogIn m_analog_in(p1); |
Daniel Veilleux |
0:442c7a6f1978 | 37 | uint16_t m_analog_in_value; |
Daniel Veilleux |
0:442c7a6f1978 | 38 | UARTService *m_uart_service_ptr; |
Daniel Veilleux |
0:442c7a6f1978 | 39 | |
Daniel Veilleux |
0:442c7a6f1978 | 40 | |
Daniel Veilleux |
0:442c7a6f1978 | 41 | /** |
Daniel Veilleux |
0:442c7a6f1978 | 42 | * This callback is used whenever a disconnection occurs. |
Daniel Veilleux |
0:442c7a6f1978 | 43 | */ |
Daniel Veilleux |
0:442c7a6f1978 | 44 | void disconnectionCallback(Gap::Handle_t handle, Gap::DisconnectionReason_t reason) |
Daniel Veilleux |
0:442c7a6f1978 | 45 | { |
Daniel Veilleux |
0:442c7a6f1978 | 46 | switch (reason) { |
Daniel Veilleux |
0:442c7a6f1978 | 47 | case Gap::REMOTE_USER_TERMINATED_CONNECTION: |
Daniel Veilleux |
0:442c7a6f1978 | 48 | DEBUG("Disconnected (REMOTE_USER_TERMINATED_CONNECTION)\n\r"); |
Daniel Veilleux |
0:442c7a6f1978 | 49 | break; |
Daniel Veilleux |
0:442c7a6f1978 | 50 | case Gap::LOCAL_HOST_TERMINATED_CONNECTION: |
Daniel Veilleux |
0:442c7a6f1978 | 51 | DEBUG("Disconnected (LOCAL_HOST_TERMINATED_CONNECTION)\n\r"); |
Daniel Veilleux |
0:442c7a6f1978 | 52 | break; |
Daniel Veilleux |
0:442c7a6f1978 | 53 | case Gap::CONN_INTERVAL_UNACCEPTABLE: |
Daniel Veilleux |
0:442c7a6f1978 | 54 | DEBUG("Disconnected (CONN_INTERVAL_UNACCEPTABLE)\n\r"); |
Daniel Veilleux |
0:442c7a6f1978 | 55 | break; |
Daniel Veilleux |
0:442c7a6f1978 | 56 | } |
Daniel Veilleux |
0:442c7a6f1978 | 57 | |
Daniel Veilleux |
0:442c7a6f1978 | 58 | DEBUG("Restarting the advertising process\n\r"); |
Daniel Veilleux |
0:442c7a6f1978 | 59 | m_ble.startAdvertising(); |
Daniel Veilleux |
0:442c7a6f1978 | 60 | } |
Daniel Veilleux |
0:442c7a6f1978 | 61 | |
Daniel Veilleux |
0:442c7a6f1978 | 62 | |
Daniel Veilleux |
0:442c7a6f1978 | 63 | /** |
Daniel Veilleux |
0:442c7a6f1978 | 64 | * This callback is used whenever the host writes data to one of our GATT characteristics. |
Daniel Veilleux |
0:442c7a6f1978 | 65 | */ |
Daniel Veilleux |
0:442c7a6f1978 | 66 | void dataWrittenCallback(const GattCharacteristicWriteCBParams *params) |
Daniel Veilleux |
0:442c7a6f1978 | 67 | { |
Daniel Veilleux |
0:442c7a6f1978 | 68 | // Ensure that initialization is finished and the host has written to the TX characteristic. |
Daniel Veilleux |
0:442c7a6f1978 | 69 | if ((m_uart_service_ptr != NULL) && (params->charHandle == m_uart_service_ptr->getTXCharacteristicHandle())) { |
Daniel Veilleux |
0:442c7a6f1978 | 70 | uint8_t buf[MAX_REPLY_LEN]; |
Daniel Veilleux |
0:442c7a6f1978 | 71 | uint32_t len = 0; |
Daniel Veilleux |
0:442c7a6f1978 | 72 | |
Daniel Veilleux |
0:442c7a6f1978 | 73 | if (1 == params->len) { |
Daniel Veilleux |
0:442c7a6f1978 | 74 | switch (params->data[0]) { |
Daniel Veilleux |
0:442c7a6f1978 | 75 | case '0': |
Daniel Veilleux |
0:442c7a6f1978 | 76 | m_cmd_led = 0; |
Daniel Veilleux |
0:442c7a6f1978 | 77 | len = snprintf((char*) buf, MAX_REPLY_LEN, "OK"); |
Daniel Veilleux |
0:442c7a6f1978 | 78 | break; |
Daniel Veilleux |
0:442c7a6f1978 | 79 | case '1': |
Daniel Veilleux |
0:442c7a6f1978 | 80 | m_cmd_led = 1; |
Daniel Veilleux |
0:442c7a6f1978 | 81 | len = snprintf((char*) buf, MAX_REPLY_LEN, "OK"); |
Daniel Veilleux |
0:442c7a6f1978 | 82 | break; |
Daniel Veilleux |
0:442c7a6f1978 | 83 | case 'a': |
Daniel Veilleux |
0:442c7a6f1978 | 84 | len = snprintf((char*) buf, MAX_REPLY_LEN, "%d", m_analog_in_value); |
Daniel Veilleux |
0:442c7a6f1978 | 85 | break; |
Daniel Veilleux |
0:442c7a6f1978 | 86 | default: |
Daniel Veilleux |
0:442c7a6f1978 | 87 | len = snprintf((char*) buf, MAX_REPLY_LEN, "ERROR: Unknown char"); |
Daniel Veilleux |
0:442c7a6f1978 | 88 | break; |
Daniel Veilleux |
0:442c7a6f1978 | 89 | } |
Daniel Veilleux |
0:442c7a6f1978 | 90 | } |
Daniel Veilleux |
0:442c7a6f1978 | 91 | else |
Daniel Veilleux |
0:442c7a6f1978 | 92 | { |
Daniel Veilleux |
0:442c7a6f1978 | 93 | len = snprintf((char*) buf, MAX_REPLY_LEN, "ERROR: Invalid len"); |
Daniel Veilleux |
0:442c7a6f1978 | 94 | } |
Daniel Veilleux |
0:442c7a6f1978 | 95 | |
Daniel Veilleux |
0:442c7a6f1978 | 96 | m_ble.updateCharacteristicValue(m_uart_service_ptr->getRXCharacteristicHandle(), buf, len); |
Daniel Veilleux |
0:442c7a6f1978 | 97 | |
Daniel Veilleux |
0:442c7a6f1978 | 98 | DEBUG("%d bytes received from host\n\r", params->len); |
Daniel Veilleux |
0:442c7a6f1978 | 99 | } |
Daniel Veilleux |
0:442c7a6f1978 | 100 | } |
Daniel Veilleux |
0:442c7a6f1978 | 101 | |
Daniel Veilleux |
0:442c7a6f1978 | 102 | |
Daniel Veilleux |
0:442c7a6f1978 | 103 | /** |
Daniel Veilleux |
0:442c7a6f1978 | 104 | * This callback is used whenever a write to a GATT characteristic causes data to be sent to the host. |
Daniel Veilleux |
0:442c7a6f1978 | 105 | */ |
Daniel Veilleux |
0:442c7a6f1978 | 106 | void dataSentCallback(unsigned count) |
Daniel Veilleux |
0:442c7a6f1978 | 107 | { |
Daniel Veilleux |
0:442c7a6f1978 | 108 | // NOTE: The count always seems to be 1 regardless of data. |
Daniel Veilleux |
0:442c7a6f1978 | 109 | DEBUG("%d bytes sent to host\n\r", count); |
Daniel Veilleux |
0:442c7a6f1978 | 110 | } |
Daniel Veilleux |
0:442c7a6f1978 | 111 | |
Daniel Veilleux |
0:442c7a6f1978 | 112 | |
Daniel Veilleux |
0:442c7a6f1978 | 113 | /** |
Daniel Veilleux |
0:442c7a6f1978 | 114 | * This callback is scheduled to be called periodically via a low-priority interrupt. |
Daniel Veilleux |
0:442c7a6f1978 | 115 | */ |
Daniel Veilleux |
0:442c7a6f1978 | 116 | void periodicCallback(void) |
Daniel Veilleux |
0:442c7a6f1978 | 117 | { |
Daniel Veilleux |
0:442c7a6f1978 | 118 | m_analog_in_value = m_analog_in.read_u16(); |
Daniel Veilleux |
0:442c7a6f1978 | 119 | } |
Daniel Veilleux |
0:442c7a6f1978 | 120 | |
Daniel Veilleux |
0:442c7a6f1978 | 121 | |
Daniel Veilleux |
0:442c7a6f1978 | 122 | void error(ble_error_t err, uint32_t line) |
Daniel Veilleux |
0:442c7a6f1978 | 123 | { |
Daniel Veilleux |
0:442c7a6f1978 | 124 | m_error_led = 1; |
Daniel Veilleux |
0:442c7a6f1978 | 125 | DEBUG("Error %d on line number %d\n\r", err, line); |
Daniel Veilleux |
0:442c7a6f1978 | 126 | } |
Daniel Veilleux |
0:442c7a6f1978 | 127 | |
Daniel Veilleux |
0:442c7a6f1978 | 128 | |
Daniel Veilleux |
0:442c7a6f1978 | 129 | int main(void) |
Daniel Veilleux |
0:442c7a6f1978 | 130 | { |
Daniel Veilleux |
0:442c7a6f1978 | 131 | ble_error_t err; |
Daniel Veilleux |
0:442c7a6f1978 | 132 | Ticker ticker; |
Daniel Veilleux |
0:442c7a6f1978 | 133 | |
Daniel Veilleux |
0:442c7a6f1978 | 134 | m_serial_port.baud(UART_BAUD_RATE); |
Daniel Veilleux |
0:442c7a6f1978 | 135 | |
Daniel Veilleux |
0:442c7a6f1978 | 136 | DEBUG("Initialising\n\r"); |
Daniel Veilleux |
0:442c7a6f1978 | 137 | |
Daniel Veilleux |
0:442c7a6f1978 | 138 | m_cmd_led = 0; |
Daniel Veilleux |
0:442c7a6f1978 | 139 | m_error_led = 0; |
Daniel Veilleux |
0:442c7a6f1978 | 140 | m_analog_in_value = 0; |
Daniel Veilleux |
0:442c7a6f1978 | 141 | |
Daniel Veilleux |
0:442c7a6f1978 | 142 | ticker.attach(periodicCallback, SENSOR_READ_INTERVAL_S); |
Daniel Veilleux |
0:442c7a6f1978 | 143 | |
Daniel Veilleux |
0:442c7a6f1978 | 144 | m_ble.init(); |
Daniel Veilleux |
0:442c7a6f1978 | 145 | m_ble.onDisconnection(disconnectionCallback); |
Daniel Veilleux |
0:442c7a6f1978 | 146 | m_ble.onDataWritten(dataWrittenCallback); |
Daniel Veilleux |
0:442c7a6f1978 | 147 | m_ble.onDataSent(dataSentCallback); |
Daniel Veilleux |
0:442c7a6f1978 | 148 | |
Daniel Veilleux |
0:442c7a6f1978 | 149 | // Set the TX power in dBm units. |
Daniel Veilleux |
0:442c7a6f1978 | 150 | // Possible values (in decreasing order): 4, 0, -4, -8, -12, -16, -20. |
Daniel Veilleux |
0:442c7a6f1978 | 151 | err = m_ble.setTxPower(4); |
Daniel Veilleux |
0:442c7a6f1978 | 152 | if (BLE_ERROR_NONE != err) { |
Daniel Veilleux |
0:442c7a6f1978 | 153 | error(err, __LINE__); |
Daniel Veilleux |
0:442c7a6f1978 | 154 | } |
Daniel Veilleux |
0:442c7a6f1978 | 155 | |
Daniel Veilleux |
0:442c7a6f1978 | 156 | // Setup advertising (GAP stuff). |
Daniel Veilleux |
0:442c7a6f1978 | 157 | err = m_ble.setDeviceName(DEVICE_NAME); |
Daniel Veilleux |
0:442c7a6f1978 | 158 | if (BLE_ERROR_NONE != err) { |
Daniel Veilleux |
0:442c7a6f1978 | 159 | error(err, __LINE__); |
Daniel Veilleux |
0:442c7a6f1978 | 160 | } |
Daniel Veilleux |
0:442c7a6f1978 | 161 | |
Daniel Veilleux |
0:442c7a6f1978 | 162 | err = m_ble.accumulateAdvertisingPayload(GapAdvertisingData::BREDR_NOT_SUPPORTED); |
Daniel Veilleux |
0:442c7a6f1978 | 163 | if (BLE_ERROR_NONE != err) { |
Daniel Veilleux |
0:442c7a6f1978 | 164 | error(err, __LINE__); |
Daniel Veilleux |
0:442c7a6f1978 | 165 | } |
Daniel Veilleux |
0:442c7a6f1978 | 166 | |
Daniel Veilleux |
0:442c7a6f1978 | 167 | m_ble.setAdvertisingType(GapAdvertisingParams::ADV_CONNECTABLE_UNDIRECTED); |
Daniel Veilleux |
0:442c7a6f1978 | 168 | |
Daniel Veilleux |
0:442c7a6f1978 | 169 | err = m_ble.accumulateAdvertisingPayload(GapAdvertisingData::SHORTENED_LOCAL_NAME, |
Daniel Veilleux |
0:442c7a6f1978 | 170 | (const uint8_t *)SHORT_NAME, |
Daniel Veilleux |
0:442c7a6f1978 | 171 | (sizeof(SHORT_NAME) - 1)); |
Daniel Veilleux |
0:442c7a6f1978 | 172 | if (BLE_ERROR_NONE != err) { |
Daniel Veilleux |
0:442c7a6f1978 | 173 | error(err, __LINE__); |
Daniel Veilleux |
0:442c7a6f1978 | 174 | } |
Daniel Veilleux |
0:442c7a6f1978 | 175 | |
Daniel Veilleux |
0:442c7a6f1978 | 176 | err = m_ble.accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LIST_128BIT_SERVICE_IDS, |
Daniel Veilleux |
0:442c7a6f1978 | 177 | (const uint8_t *)UARTServiceUUID_reversed, |
Daniel Veilleux |
0:442c7a6f1978 | 178 | sizeof(UARTServiceUUID_reversed)); |
Daniel Veilleux |
0:442c7a6f1978 | 179 | if (BLE_ERROR_NONE != err) { |
Daniel Veilleux |
0:442c7a6f1978 | 180 | error(err, __LINE__); |
Daniel Veilleux |
0:442c7a6f1978 | 181 | } |
Daniel Veilleux |
0:442c7a6f1978 | 182 | |
Daniel Veilleux |
0:442c7a6f1978 | 183 | m_ble.setAdvertisingInterval(Gap::MSEC_TO_ADVERTISEMENT_DURATION_UNITS(ADV_INTERVAL_MS)); |
Daniel Veilleux |
0:442c7a6f1978 | 184 | m_ble.startAdvertising(); |
Daniel Veilleux |
0:442c7a6f1978 | 185 | |
Daniel Veilleux |
0:442c7a6f1978 | 186 | // Create a UARTService object (GATT stuff). |
Daniel Veilleux |
0:442c7a6f1978 | 187 | UARTService uartService(m_ble); |
Daniel Veilleux |
0:442c7a6f1978 | 188 | m_uart_service_ptr = &uartService; |
Daniel Veilleux |
0:442c7a6f1978 | 189 | |
Daniel Veilleux |
0:442c7a6f1978 | 190 | while (true) { |
Daniel Veilleux |
0:442c7a6f1978 | 191 | m_ble.waitForEvent(); |
Daniel Veilleux |
0:442c7a6f1978 | 192 | } |
Daniel Veilleux |
0:442c7a6f1978 | 193 | } |