Written by Elecia White.
In Episode 102, we spoke with Charles Lohr about the extremely cheap ESP8266 WiFi chip. He has a great write up on getting started. I haven’t gotten my hands on one yet but I sure want to.
In the meantime, everyone seems to want to do a BLE project. We talked about getting started with BLE in Episode 62 but that was before I’d gotten to play with it extensively. I can’t talk about the specific projects I’ve worked on with BLE yet but I can tell you about the chips I’ve used and what I’ve learned. I’ve worked in three configurations.
Processor | Processor core | Board | Compiler | Debugger |
Nordic NRF51822 | Cortex-M0 | Custom, based on Nordic reference | Keil limited license (free) | Segger with on-chip debugging |
Nordic NRF51822 | Cortex-M0 | RedBearLab BLENano | mbed online (free) | Built-in, USB MSC, no on-chip debugging |
Texas InstrumentsTexas Instruments | Cortex-M3 | TI reference (SmartRF) | IAR (purchased ARM license) | Built-in with on-chip debugging |
Those are all system-on-chips (SoCs) so the BLE module and the processor where I run my code are in the same package. Each SoC can speak BLE as well as other things (Zigbee, ANT, etc), depending on a software stack. There are plenty of other BLE SoCs out there.
While the Nordic chip has been around for a while, it has been getting significant updates to their SDK. The TI CC2640 is new (came out Q2Y15). It is lower power and more flexible than their previous CC2541 8051-core processors.
I’ve also used systems that had the application processor separate from the BLE module. In those cases, I had limited interaction with the BLE, sometimes treating it as a peculiarly unreliable serial port or as an incomprehensible peripheral (“You want me to do what with XML? And then compile it? Maybe you should do it yourself.”)
Funnily enough, it was that step I balked at (XML attribute tables) that I’m most interested in sharing with you (sans XML). However, it is going to take a bit to get there.
Getting started with BLE
Any new communication protocol requires a vocabulary lesson. BLE is even worse than most, as there is a lot of terminology. The first hurdle is the name. Like standards, there are so many to choose from.
Bluetooth by itself means Bluetooth Classic. This is higher power and higher bandwidth. Most Bluetooth headphones are Bluetooth. The connection between your phone and car is Bluetooth. Right now, we don’t care about Bluetooth, this post isn’t going to talk about it. Starting now.
I lied. Bluetooth Smart Ready means it is a dual-mode device that is compatible with both Bluetooth (Classic) and BLE devices. A smartphone is Bluetooth Smart Ready. It can communicate via Bluetooth to your car to play music or to your Fitbit device to get steps.
Bluetooth Smart is also Bluetooth Low Energy or BLE. That’s the one I’ve been playing with. The names do sort of indicate versions but I haven’t unraveled which is which and I’ve stopped wondering.
All of my development has been on the peripheral, running a server. My devices have required some battery awareness and have mostly talked to smartphones.
Starting out, I got pretty hung up on profiles, trying to fit my projects into one of the many standard profiles and services. However, despite an overlap in functionality with some, they never quite work. While I’ve used the standard device info service and the battery service, for the actual data from a device, I’ve had to come up with my own service, with a set of characteristics organized into an attribute table. Whoops, more terminology.
Say you want to make an accelerometer which sends data via BLE. Further, say you want to be able to set the sample rate. I say these things because it is one of the things I wanted to do for a side project. There are many different ways to implement this data. I’ll give specific examples as I go but I want to frame the problem now so you can think about it before we get an answer.
Data | Variables in my code | Settings |
Firmware version | uint8_t version[3] | Readable, constant |
Accelerometer data | uint16_t x, y, z | Readable, changes |
Sample rate | uint8_t sampleRate | Writeable by the client application |
So many examples
I am a reasonably lazy programmer. I’d rather use existing code than write my own, as long as I can understand it. The SoC vendors have learned this and provide examples. Now the problem is reading through the mountain of examples to find the one that is most like my intended project, then figuring out what should be changed, what might be changed with some benefit, and what should never be touched even though they give you the source file.
TI CC2640
In the TI system (CC2640), you get an operating system (TI-RTOS, based on SYS/BIOS kernel). While you may find a project file specializing in a particular profile that works for you (BloodPressure, CyclingSensor, KeyFob, etc.), the rest of us will be starting with the generic SimplePeripheral example as the basis for an embedded device.
SimplePeripheral has a task that handles BLE messages (simpleBLEPeripheral.c). On initialization, we register callbacks for GAP actions like successful pairing or loss of connection. We set up our advertising packet, just modifying the one TI provided. Somewhere in there, we also register some services. The device info is a standard one (DevInfo_AddService) but our data will go into a vendor service. This example supports a simple one (SimpleProfile_AddService , in simpleGATTprofile.c).
It starts out with five characteristics. Clearly, the clever naming department did not extend to characteristics.
Data | Variable in the code | Settings (aka properties) |
Example characteristic 1 | uint8_t simpleProfileChar1 | Read or write Has a UUID and a descriptor string "Characteristic 1" |
Example characteristic 2 | uint8_t simpleProfileChar2 | Readable only Has a UUID and a descriptor string "Characteristic 2" |
Example characteristic 3 | uint8_t simpleProfileChar3 | Writeable only Has a UUID and a descriptor string "Characteristic 3" |
Example characteristic 4 | uint8_t simpleProfileChar4 | Notify Has a UUID, characteristic configuration, and a descriptor string "Characteristic 4" |
Example characteristic 5 | uint8_t simpleProfileChar5 | Readable only Has a UUID and a descriptor string "Characteristic 5" |
This looks familiar, right? The UUIDs are 16-bit identifiers that let us access the data in the table. The descriptor strings are for the humans who have to figure out which is which. They are optional in BLE but helpful for the example.
The most interesting part of this example is the fourth characteristic. First, its property is notify. This implicitly implies that the property is also readable but it is better for data that can change on the device. See, the central (e.g. your phone) has to initiate all transactions with the peripheral (e.g. your device). If a characteristic is readable, the central will query the peripheral which will respond with the value.
However, for notify characteristics, the central won’t have to poll the data; instead BLE will let the app know when the characteristic has changed. You can change the configuration later, from the central (look for CCC in the code to see how that works).
These variables are all set up in the attribute table. This is the data that the GATT protocol deals with. You can put in callbacks so you get notified when the characteristics are read or written.
NRF51822 in mbed
Switching from TI to Nordic and from IAR to mbed should be a big change and it is. Still, the BLE interface is reasonably consistent. First, there are several RedBearLabs demos including Heart Rate, Thermometer and BatteryLevel services and an iBeacon. I started with the nrf51822_SimpleControls example, though I made a few variable name changes in the snippet of code.
We have some UUIDs, not defined in the snippet shown, and these UUIDs get attached to variables which have properties. In this case, it is two variables: txCharacteristic for input and rxCharacteristic for output (these are tx and rx from the client application perspective, not the device). These are just chunks of memory (20 bytes each, the maximum data payload).
Data | Variable in the code | Settings (aka properties) |
Input data from the client | uint8_t txCharacteristic[20] | Notify (readable, will notify the client when a change occurs) Has a UUID |
Output data to the client | uint8_t rxCharacteristic[20] | Writeable only Has a UUID |
Thinking back to my accelerometer data, I could put my XYZ into three different characteristics or I could byte pack a structure and send them over all at one time. BLE doesn’t care.
Note that mbed has plenty of examples. I’m not clear about the licensing for all of them but if you want to try out BLE and see all sorts of code, getting an mbed BLE board would be a great place to start. It was (by far) the easiest to get going. While both TI/IAR and Nordic/Keil required installing a bunch of packages in addition to the compiler, the mbed environment worked for me after following less than a page of instructions.
NRF51822 in Keil
I used the free, space limited Keil compiler with a client’s custom board. I did not expect this to work but by the time I used the Nordic BLE stack’s dual banking system to update my firmware, allocated some flash to do logging, and to store code for another processor, I couldn’t have made a much larger application anyway.
Nordic/Keil have a ton of examples, overwhelming though organized reasonably in the Pack Installer. They seem to have an example for each BLE standard profile as well as for each processor feature (SPI, UART, pin interrupts, etc.). This leads to the problem of where to start. Having looked through them again with an interest in giving you a hint, I’m still not sure. The one called experimental_ble_app_uart_s110_pca100028 allows functionality similar to the mbed example above: a 20 byte characteristic.
There is another similar sd_ble_gatts_characteristic_add call for the transmit characteristic.
I didn’t start this project from scratch, my goal was to fix some bugs and port the client’s code from SDK 6.x to 7.x. The SDK is constantly being updated and the ports should be easy. And yet, it took me a bit, mostly messing around with paths and tools though I was also changing between the BLE-only S110 and the BLE+ANT capable S310 softdevice (aka Bluetooth stack). My bias against the complexity of the Nordic SDK switch may be conflated with all the other changes.
The Nordic with the Keil compiler does allow for fairly direct and obvious interactions with the BLE state machine. The on_ble_evt function shows all of the events that happen and their usual handlers. If you want to change something, that makes it easy to find and change. On the other hand, you can drown in details before getting a proof of concept done.
The Nordic/Keil system is also the only one that I’ve used for over the air firmware update (DFU in Nordic parlance, OAD in TI). The example worked quickly though, again, finding the part I needed to modify was tricky, buried in the example code (I wanted to update a display with percent complete).
Debugging Using a Sniffer
I mainly used the Nordic PCA10031 BLE dongle for debugging. I know that this dongle can be configured to work with Wireshark. I kept meaning to do that but never did. Instead I used it with the Nordic Master Control Panel (MCP). This let me discover devices and connect with devices around me. Upon connecting, I could read the GATT information (via discover services).
TI has BTool that serves a similar purpose (and a few more). However, once I got used to MCP, I didn’t try to switch.
The other way to debug is on a smart phone, Android or iOS. There are many apps available to sniff BLE packets. At various times, I used the Nordic nrfToolbox, TI BLE Multitool, and the generic LightBlue. They will show you the devices in range, their advertising messages, and allow you to connect and interact with the data.
GATT: Attribute table
Now we have the methods to add characteristics to our different processors and ways to look at the output. I want to get back to that BLE accelerometer.
Data | Variables in my code | Settings |
Firmware version | uint8_t version[3] | Readable, constant |
Accelerometer data | uint16_t x, y, z | Readable, changes |
Sample rate | uint8_t sampleRate | Writeable by the client application |
This looks like at least three characteristics. The firmware version is easy. If I look in the device info, I can simply reuse the existing service, filling in my device’s information to the standard output.
For the accelerometer data, I could make it three more characteristics or I can pack it into a buffer and let the client app unpack it. BLE doesn’t care which you do, that protocol is between your device and the app used to read its information. I can make text descriptors to make it easy for people to see what is what in my data or I can forgo that, again BLE enables whichever I choose.
The accelerometer data is an output of my device, I probably want to it to have a notify characteristic configuration so that the client app doesn’t poll. The device can save its batteries if it only has to send a packet when information has changed.
Finally, sample rate is also straightforward though it has to be a separate characteristic because it is writeable by the client app. It controls how often the accelerometer data is updated but the app doesn’t need to remember this all poll, the notify property of the characteristic will let it know when the values change.
UUID generation
What about the UUID? That creation has always happened off screen.
Looking back at the MCP output of the AppleTV, the first entry for the device name is short; there isn’t a lot of hex cruft. It is part of the BLE-defined device info service which uses 16-bit UUIDs. However, looking at the last entry, the UUID is a 128-bit beast. If we pass that back and forth, our 20 byte packet will disappear to nothing (and we consume additional power passing data no one cares about).
Instead, our app will most likely use a handle instead of the UUID to access the custom data. This is simply an offset into the attribute table. It is easy to use though if you change the attribute table, your handles may need to be reshuffled (and that is why we need version in the standard device info, so we can change the data organization in the future).
You can make your own UUID (vendor specific) as long as it is less than the base UUID (00000000-0000-1000-8000-00805F9B34FB, source: Bluetooth Core Specification Vol 3 Part F Section 3.2.1). Choose whatever you want. It may collide with someone else’s but only if the advertising data makes you connect to their device.
As for advertising configuration, I have to admit I don’t want to cover it here, partially because I ran out of space about a thousand words ago but mostly because the examples were really good. You don’t need to me make that information more palatable.
Summary
If I were doing a project to learn about BLE for my own purposes, I’d start with RedBearLabs BLENano. The board is more expensive but the mbed integration means you have a wide variety of examples that make sense.
The CC2640 is about twice as expensive as the NRF51822 but you get a lot more processing for that and much lower power usage. There isn’t a clear “use this, always” winner: the product circumstances will drive the hardware selection.
Though, I admit, I’m looking at these and wondering if I really want to do BLE when that super cheap WiFi chip exists.
Additional Resources
- Bluetooth organization (requires a free registration): https://www.bluetooth.org/en-us
- Good UUID info on Stack Overflow: http://stackoverflow.com/questions/10243769/what-range-of-bluetooth-uuids-can-be-used-for-vendor-defined-profiles
- The Adafruit intro to BLE is a very good introduction: https://learn.adafruit.com/introduction-to-bluetooth-low-energy
- I’ve heard that Robin Heydon’s Bluetooth Low Energy: The Developer's Handbook is a good BLE book. Perhaps if I’d read that, I wouldn’t have felt the need for this post.