Controlling the TI SensorTag with techBASIC
Controlling the TI SensorTag with techBASIC
Controlling the TI SensorTag with techBASIC
The Internet of Things has a new addition, and it's a great way to learn more about Bluetooth low energy. Most Bluetooth low energy devices are designed for a very specific use, but the new SensorTag from Texas Instruments is a general-purpose device with a whopping six sensors that can be put to use in a wide variety of ways. It's intended as a reference platform—a device that lets hardware and software engineers see how to use TI's CC2541 chip by example. That's exactly how we will use it, writing software to access and display the sensor data. Once the software complete, though, you'll have a nice general-purpose sensor device that can be used for all sorts of things. We flew it in a model rocket to collect acceleration, rotation and pressure during the flight.
This blog is the first in a series of seven blogs that explore this new device. This one is the starting place, where we'll explore setting up the SensorTag and writing the basic software used to access any Bluetooth low energy device. The other six blogs cover the six sensors and how to use them in detail. The software can be written entirely on your Bluetooth low energy equipped iPhone, iPad or iPod using techBASIC, the technical computing language for iOS. You don't need a Mac, XCode, knowledge of Objective C, or membership in Apple's developer program to make all of this work.
Before we dive in, it's also important to realize that the basics of writing software to access a Bluetooth low energy device are the same for all Bluetooth low energy devices, and we've covered these basics once before. If you've read my blog on the TI Key Fob, a predecessor to the SensorTag, you really don't need to read this blog, too. In fact, some sections of this blog, and even some parts of the software, are identical to the material for the Key Fob. That's good—it shows you that you can take this program and quickly modify it to access any Bluetooth low energy device. So, if you've already read the Key Fob blog, skip to the end, grab the software, and move on to any of the six sensor blogs that interest you.
What Will it Do?
Bluetooth low energy, also known as Bluetooth 4.0, BLE, Bluetooth LE, and Bluetooth Smart, is a new twist on the old Bluetooth standard. It's designed for ultra-low energy sensors that can run off of coin cell batteries. Some of the Bluetooth low energy devices that are already available include sports fitness sensors like bicycle speedometers and pedometers built into Nike running shoes; health sensors like thermometers, blood pressure cuffs and heart rate monitors; and physics sensors like accelerometers and thermometers.
The SensorTag is a peripheral from Texas Instruments designed to help hardware and software engineers learn about, and begin building, Bluetooth low energy peripherals and software using the CC2451 chip. It has these six sensors:
• A three-axis accelerometer.
• A three-axis magnetometer.
• A three-axis gyroscope.
• A dual-mode thermometer that measures ambient and remote temperatures.
• A humidity sensor.
• A barometer.
Parts List
Wednesday, October 31, 2012
iPhone 4s or later, or iPad 3 or later
iBluetooth Low Energy is physically different from the older Bluetooth devices. It requires completely new hardware that is not available in older model devices, so you will need an iPhone 4s or later, an iPad 3 or later, or the iPod Touch 5th generation (the one with the 4 inch retina display) or later to access Bluetooth low energy devices.
Writing Bluetooth low energy Programs
Before jumping into the code, though, let's stop and get an overview of how Bluetooth low energy devices are designed. Bluetooth low energy devices package information in services. In our program, we will use six of these services, one for each sensor. This article will show how to connect to one of them, the accelerometer, but the software download handles all six. The sensors themselves, and the details on how to use them, are covered in the six follow-on blogs.
Other devices might offer a heart rate service or a servo control service. Each service has characteristics, which work more or less like variables. Some can only be read, some can only be written, and some can be both read and written. There can be more than one characteristic for a service. For example, the accelerometer has three characteristics, one to start or stop the accelerometer, one to set the rate at which it will return data, and one to read the actual data. Services can also have other sub-services imbedded in them; these are called included services. Characteristics can have descriptors with additional information about the characteristic. The figure shows the services and characteristics we'll use on the SensorTag. This is a simplification of the complete specification for the communication protocol for the SensorTag. The full specification is called the GATT profile, available for download from the TI web site here. There is also a lot of supplemental information on each sensor in the wiki. You don't need the GATT profile or wiki to follow along, but learning to read these documents will help if you have to figure out another Bluetooth low energy device.
Almost all Bluetooth low energy calls are asynchronous, since it may take some time for the operating system to communicate with the device to carry out an operation. Your program can use that time to make new requests, handle information passed back from old requests, or simply to do something else while waiting for the results of a call. From the program's perspective, the program begins by making a call, then moves on. At some point in the future the operating system will call a subroutine in the program to report the results. You will see this pattern over and over in the SensorTag program.
Let's get going with the program. The first step in connecting to a Bluetooth low energy device is to start the Bluetooth low energy service with the call
BLE.startBLE
Next we begin scanning for devices. This allows our program to look for any Bluetooth low energy device in the area and connect to the one—or ones—that have the information or capabilities we want. In general, you should already know the kind of service you are looking for. Each service has a 16 or 128 bit identifier for the service, called the service UUID. The shorter 16 bit identifiers are supposed to be assigned by the Bluetooth standards committee. You can find a list of the standard services at http://developer.bluetooth.org/gatt/services/Pages/ServicesHome.aspx. Anyone is free to create a service using a 128 bit UUID.
The values shown in the figure are actually only part of the 128 bit UUID for the characteristics and services. These values make up the second and third byte of a much longer UUID. The complete UUID for the accelerometer service looks like this:
F000AA00-0451-4000-B000-000000000000
The UUID string for other services and characteristics are formed by replacing the fourth through eighth digits with the appropriate value form the table, so the calibration UUID for the barometer looks like this:
F000AA43-0451-4000-B000-000000000000
In our case, we're going to scan for any Bluetooth low energy peripheral in the area by passing an empty array of UUIDs to the startScan method.
DIM uuid(0) AS STRING
BLE.startScan(uuid)
At this point, the iPhone or iPad starts looking around for any Bluetooth low energy device in the area. Bluetooth low energy devices advertise their presence with short, infrequent radio signals that our program is scanning for. As the iOS device finds Bluetooth low energy devices, it calls a subroutine in your program called BLEDiscoveredPeripheral. Here's the implementation from our program.
! Set up variables to hold the peripheral and the characteristics
! for the battery and buzzer.
DIM sensorTag AS BLEPeripheral
! Start the BLE service and begin scanning for devices.
debug = 1
BLE.startBLE
DIM uuid(0) AS STRING
BLE.startScan(uuid)
! Called when a peripheral is found. If it is a Sensor Tag, we
! initiate a connection to it and stop scanning for peripherals.
!
! Parameters:
! time - The time when the peripheral was discovered.
! peripheral - The peripheral that was discovered.
! services - List of services offered by the device.
! advertisements - Advertisements (information provided by the
! device without the need to read a service/characteristic)
! rssi - Received Signal Strength Indicator
!
SUB BLEDiscoveredPeripheral (time AS DOUBLE, peripheral AS BLEPeripheral, services() AS STRING, advertisements(,) AS STRING, rssi)
IF peripheral.bleName = "TI BLE Sensor Tag" THEN
sensorTag = peripheral
BLE.connect(sensorTag)
BLE.stopScan
IF debug THEN PRINT "Discovered SensorTag."
END IF
END SUB
Since we're more interested in a kind of device rather than a kind of service, we check here to see if we've found the device we're looking for by looking at the name of the peripheral the iPhone or iPad found. If it matches TI BLE Sensor Tag, we found what we are looking for. Ideally, things like the name of the peripheral and the peripheral UUID will be in the documentation, but in practice, you may have to write just this much of the program and print the name of any peripheral you find to figure out the name to use.
Once we find the device, we save the peripheral in a global variable. This is important—it keeps the memory manager from disposing of the peripheral's object when the subroutine ends, which would tell the operating system we're not interested in this peripheral. Next we attempt to connect to the peripheral using BLE.connect. The last step is to stop scanning for other Bluetooth low energy devices, which can drain the battery of the iOS device and the Bluetooth low energy devices. We do this with BLE.stopScan.
Discovered SensorTag.
printed to the console.
Connecting to the device does not happen right away. The operating system asks for access and, once it gets a response, calls another subroutine called BLEPeripheralInfo.
! Called to report information about the connection status of the
! peripheral or to report that services have been discovered.
!
! Parameters:
! time - The time when the information was received.
! peripheral - The peripheral.
! kind - The kind of call. One of
! 1 - Connection completed
! 2 - Connection failed
! 3 - Connection lost
! 4 - Services discovered
! message - For errors, a human-readable error message.
! err - If there was an error, the Apple error number. If there
! was no error, this value is 0.
!
SUB BLEPeripheralInfo (time AS DOUBLE, peripheral AS BLEPeripheral, kind AS INTEGER, message AS STRING, err AS LONG)
IF kind = 1 THEN
! The connection was established. Look for available services.
IF debug THEN PRINT "Connection made."
peripheral.discoverServices(uuid)
ELSE IF kind = 2 OR kind = 3 THEN
IF debug THEN PRINT "Connection lost: "; kind
BLE.connect(sensorTag)
ELSE IF kind = 4 THEN
! Services were found. If it is one of the ones we are interested
! in, begin discovery of its characteristics.
DIM availableServices(1) AS BLEService
availableServices = peripheral.services
FOR s = 1 to UBOUND(services, 1)
FOR a = 1 TO UBOUND(availableServices, 1)
IF services(s) = availableServices(a).uuid THEN
IF debug THEN PRINT "Discovering characteristics for "; services(s)
peripheral.discoverCharacteristics(uuid, availableServices(a))
END IF
NEXT
NEXT
END IF
END SUB
We also need to add some lines at the start of the program. The new lines are shown in blue.
! Set up variables to hold the peripheral and the characteristics
! for the battery and buzzer.
DIM sensorTag AS BLEPeripheral
! We will look for these services.
DIM servicesHeader AS STRING, services(1) AS STRING
servicesHeader = "-0451-4000-B000-000000000000"
services(1) = "F000AA10" & servicesHeader : ! Accelerometer
accel% = 1
! Start the BLE service and begin scanning for devices.
debug = 0
BLE.startBLE
DIM uuid(0) AS STRING
BLE.startScan(uuid)
This subroutine can get called for a variety of reasons. After a BLE.connect call, we can get back the "connection complete" response or be told that the connection failed. Later, the connection might be lost—perhaps we wondered too far away from the peripheral. The kind parameter tells us why the call was made. Our program asks the peripheral for a list of any services it provides by calling the peripheral's discoverServices method when a connection is made, and tries to reconnect if a connection is lost. As the peripheral reports back on any services, the subroutine is called again with kind set to 4. Since we've enabled the debug output, the program will print a list of the available services.
Eventually we'll expand the array to hold all six services, but for now we'll restrict our attention to the accelerometer, so the array only has one element.
The code in BLEPeripheralInfo checks to see if the service reported by the device is the accelerometer and, if so, asks the service for a list of the available characteristics using the peripheral's discoverCharacteristics method.
By this time, you probably have a good idea what will happen next. As with the services, the characteristics are reported to the program by calling a subroutine. In this case, the subroutine is BLEServiceInfo. The name might seem odd, but the information is about a service, not the characteristic. The operating system is telling us the service has information. Here's the implementation.
! Called to report information about a characteristic or included
! services for a service. If it is one we are interested in, start
! handling it.
!
! Parameters:
! time - The time when the information was received.
! peripheral - The peripheral.
! service - The service whose characteristic or included
! service was found.
! kind - The kind of call. One of
! 1 - Characteristics found
! 2 - Included services found
! message - For errors, a human-readable error message.
! err - If there was an error, the Apple error number. If there
! was no error, this value is 0.
!
SUB BLEServiceInfo (time AS DOUBLE, peripheral AS BLEPeripheral, service AS BLEService, kind AS INTEGER, message AS STRING, err AS LONG)
IF kind = 1 THEN
! Get the characteristics.
DIM characteristics(1) AS BLECharacteristic
characteristics = service.characteristics
FOR i = 1 TO UBOUND(characteristics, 1)
IF service.uuid = services(accel%) THEN
! Found the accelerometer.
SELECT CASE characteristics(i).uuid
CASE "F000AA11" & servicesHeader
! Tell the accelerometer to begin sending data.
IF debug THEN PRINT "Start accelerometer."
DIM value(2) as INTEGER
value = [0, 1]
peripheral.writeCharacteristic(characteristics(i), value, 0)
peripheral.setNotify(characteristics(i), 1)
CASE "F000AA12" & servicesHeader
! Turn the accelerometer sensor on.
IF debug THEN PRINT "Accelerometer on."
DIM value(1) as INTEGER
value(1) = 1
peripheral.writeCharacteristic(characteristics(i), value, 1)
CASE "F000AA13" & servicesHeader
! Set the sample rate to 100ms.
DIM value(1) as INTEGER
value(1) = 100
IF debug THEN PRINT "Setting accelerometer sample rate to "; value(1)
peripheral.writeCharacteristic(characteristics(i), value, 1)
END SELECT
END IF
NEXT
END IF
END SUB
We're only interested in the first kind of call, where the operating system is telling us the service has updated its list of characteristics. If kind is 1, we get the characteristics for the service with a call to the service's characteristics method and loop over them, looking for characteristics we're interested in. There's only one so far, but the list will grow in the final program.
But wait: It looks like the characteristics are part of the service, which we knew after the call to BLEPeripheralInfo. Why go to all this trouble? The reason is that the operating system doesn't ask the device for a list of characteristics until the discoverCharacteristics call, since it doesn't want to waste battery power asking for information unless it is really needed. You can call the service's characteristics method in BLEPeripheralInfo, but it will return an empty array.
There is more than one way to read a value from the device. The accelerometer uses a method called notification, where the device notifies the iPhone each time a new value is available. We turn notifications on and start them with the calls
DIM value(2) as INTEGER
value = [0, 1]
peripheral.writeCharacteristic(characteristics(i), value, 0)
peripheral.setNotify(characteristics(i), 1)
The first two lines set up the value to write to the peripheral. The accelerometer expects two bytes, a 0 and a 1, to turn on. The next line writes these values to the characteristic, telling it to report information using notifications. The last line actually starts the notifications.
The other method would look similar. If the accelerometer supported individual read commands, you could use the readCharacteristics call, like this:
peripheral.readCharacteristic(characteristics(i))
The BLECharacteristicInfo subroutine gets called with either notifications or reads, the difference being that it will only be called once after a read, and will be called every time a new value is available for notifications.
The next two characteristics are used to turn the accelerometer on and set the sampling rate. They work pretty much like the write used earlier to tell the device to report information using notifications. It may seem odd to send information to the command using an array with only one element, but that's because the software making the call to the device really has no idea how many bytes to send, so it sends all of the bytes in any array you pass. In these cases, the device only expects a single byte. For the AA12 characteristic, a 1 turns the accelerometer on and a 0 turns it off. For the AA13 characteristic, the value is the number of tens of milliseconds between samples, so passing 100 tells the device to send back an acceleration once a second.
The operating system calls the BLECharacteristicInfo subroutine when the device reports a change to a characteristic. Here's a simplified version of the subroutine that appears in the complete program.
! Called to return information from a characteristic.
!
! Parameters:
! time - The time when the information was received.
! peripheral - The peripheral.
! characteristic - The characteristic whose information
! changed.
! kind - The kind of call. One of
! 1 - Called after a discoverDescriptors call.
! 2 - Called after a readCharacteristics call.
! 3 - Called to report status after a writeCharacteristics
! call.
! message - For errors, a human-readable error message.
! err - If there was an error, the Apple error number. If there
! was no error, this value is 0.
!
SUB BLECharacteristicInfo (time AS DOUBLE, peripheral AS BLEPeripheral, characteristic AS BLECharacteristic, kind AS INTEGER, message AS STRING, err AS LONG)
IF kind = 2 THEN
DIM value(1) AS INTEGER
value = characteristic.value
SELECT CASE characteristic.uuid
CASE "F000AA11" & servicesHeader
! Update the accelerometer.
c = 64.0
p% = value(1)
IF p% BITAND $0080 THEN p% = p% BITOR $FF00
lastAccelX = p%/c
p% = value(2)
IF p% BITAND $0080 THEN p% = p% BITOR $FF00
lastAccelY = p%/c
p% = value(3)
IF p% BITAND $0080 THEN p% = p% BITOR $FF00
lastAccelZ = p%/c
PRINT lastAccelX, lastAccelY, lastAccelZ
END SELECT
ELSE IF kind = 3 AND err <> 0 THEN
PRINT "Error writing "; characteristic.uuid; ": ("; err; ") "; message
END IF
END SUB
This subroutine is called with a kind of 2 when the accelerometer reports back with a new value. The value it returns is packed into a three-byte array, where each byte is the acceleration value along one axis. These are signed values with a range of -128 to 127, so we can't just use them as-is. Instead, the program checks to see if the value is negative and, if so, extends the sign bits to form a two-byte signed integer.
Run the program. It may connect to the peripheral right away if you have already run an earlier version of the program. If not, push the pairing button on the SensorTag to establish a connection. After a second or two, you should start to see a spew of accelerometer data on the console telling you the G forces experienced by the SensorTag. Twist it around slowly to verify that the value changes.
The Complete SensorTag Program
The complete program handles all six sensors, not just the accelerometer, and shows them with a spiffy graphical user interface. Handling user interfaces and plots are covered elsewhere, so we won't go through this part of the program line by line. See the blog Learn How to Use Controls in Your techBASIC iPhone and iPad Programs to learn more about setting up user interfaces. See techBASIC: The Power-User's Graphing Calculator for more about setting up plots like the accelerometer plot. Feel free to get in touch at support@byteworks.us if you have questions or issues.
There are six other short blogs that cover each of the other sensors. You may be able to figure them out on your own by now. The only really tricky one is the barometer, which passes back a series of calibration values that are used for the final calculation. The sensor blogs give a few details about the sensors themselves and step through the math needed to access each one.
Further Explorations
This program shows how to access Bluetooth low energy devices and covers the major commands and techniques used for all Bluetooth low energy devices. There's more to learn, though. See the techBASIC Reference Manual for a host of other Bluetooth low energy commands, as well as several complete sample programs. Check the index or do a search of the PDF for Bluetooth low energy.
Here is the source code for the demo program developed in this blog, as well as the more advanced program that created the GUI display shown at the start of the blog.
TI SensorTag
The SensorTag is a Bluetooth low energy device with six sensors. It's physically rather small, with the slightly irregular shaped soft plastic case measuring about 2.75x1.5x0.5 inches. The circuit board is 1x2.25x0.25 inches. It runs on a single CR 2032 coin cell battery, and mine has been chugging along quite nicely for several weeks on the same battery. It's available directly from Texas Instruments at this link. A great place for additional information is the SensorTag wiki.
CC Debugger
The CC Debugger is used to load new firmware onto the SensorTag, as well as for connecting diagnostic software. The SensorTag comes with a basic set of firmware preinstalled, so technically this is optional, but you may need to upgrade the firmware eventually. For example, we changed the firmware to switch the accelerometer from ±2G to ±8G for our rocket flights. You may already have a CC Debugger from the older Bluetooth Low Energy CC2540 Mini Development Kit, which included the Key Fob. If not, I'd recommend getting one when you order the SensorTag. Like the SensorTag, it's available directly from Texas Instruments.
The software for the CC Debugger runs on a Windows operating system. You only need it when you install the firmware, but if you're a Mac person, plan ahead so you have access to a Windows computer when you need it.
Since you don't need to install firmware for this blog, I'm not going to cover it here. You can find information on the TI web site or in my older Key Fob blog.
techBASIC
Bluetooth low energy support is built into techBASIC starting with version 2.3. This is available as a free update to people who own older versions of techBASIC. New copies can be purchased on the App Store.
Programs in this blog use some features from techBASIC 2.4. We had expected Apple to release it by now. Once they do, it will be a free upgrade to the current version, techBASIC 2.3.