Accessing the Bluetooth low energy Gyroscope on the TI SensorTag
Accessing the Bluetooth low energy Gyroscope on the TI SensorTag
Accessing the Bluetooth low energy Gyroscope on the TI SensorTag
This blog explores how to read the rate of rotation using new TI SensorTag, a Bluetooth low energy peripheral designed to be a hardware and software reference program for the CC2541 chip. It shows a simple, text-based program that's great for experimenting with the sensor. The complete listing appears at the end of the blog. A companion blog, Controlling the TI SensorTag with techBASIC, gives background information about Bluetooth low energy devices, the TI SensorTag, and accessing Bluetooth devices using an iPhone or iPad. It also gives a complete program that accesses all six sensors on the TI SensorTag, showing the values using a GUI interface.
What's a Gyroscope?
A gyroscope measures how fast something rotates. The IMU-3000 Triple Axis MotionProcessor™ Gyroscope used on the TI SensorTag returns rotation about three axis in degrees per second. The chip can return rotation rates across several ranges; the SensorTag preselects a range of ±250 degrees/second, giving a maximum precision in exchange for a smaller maximum rotation rate. The precision is about 0.007 degrees per second, although based on observations of a SensorTag that is stationary on my desk, I don't think it's really that accurate. Or perhaps my world really is spinning around randomly. It certainly feels that way on occasion. Based on casual observation, I'd say the true accuracy is about 0.2 degrees per second, and there is a definite need for calibration.
Accessing the Gyroscope
Looking at the Generic Attribute Profile (GATT) you see two characteristics for the gyroscope.
0xAA52 characteristic is used to turn the gyroscope on and off. Our program just turns it on.
CASE "F000AA52" & servicesHeader
DIM value(1) as INTEGER
value(1) = 1
peripheral.writeCharacteristic(characteristics(i), value, 1)
Pass a 0 instead of a 1 to turn the gyroscope off.
0xAA51 is used to read the gyroscope. There are two ways to read the gyroscope, either reading it when the program wants a rotation rate or asking the gyroscope to notify the program whenever a new sensor reading is available. The first step is to tell the gyroscope which way we want to read values. To receive notifications when a value is available, write a 1 and 0 to the characteristic, then ask it to start sending notifications.
SELECT CASE characteristics(i).uuid
CASE "F000AA51" & servicesHeader
DIM value(2) as INTEGER
value = [0, 1]
peripheral.writeCharacteristic(characteristics(i), value, 0)
peripheral.setNotify(characteristics(i), 1)
Use readCharacteristic to read a single value.
peripheral.readCharacteristic(characteristics(i), 1)
Whether you read the data once using readCharacteristic or ask for notifications with setNotify, the values are always returned using a call to BLECharacteristicInfo. The value reported consists of three two-byte signed integers, one each for the rate of rotation about the x, y and z axis, in that order. Each value is least significant byte first. The integers must be divided by 65536 and multiplied by 500 to yield the final range of -250 to 250 degrees per second. Here's the BASIC code to take care of reading and printing the values.
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 "F000AA51" & servicesHeader
! Update the gyroscope.
c = 65536.0/500.0
x = ((value(2) << 8) BITOR value(1))/c
y = ((value(4) << 8) BITOR value(3))/c
z = ((value(6) << 8) BITOR value(5))/c
PRINT time, x, y, z
CASE ELSE
PRINT "Read from "; characteristic.uuid
END SELECT
ELSE IF kind = 3 AND err <> 0 THEN
PRINT "Error writing "; characteristic.uuid; ": ("; err; ") "; message
END IF
END SUB
Using the Gyroscope
Calibrating the gyroscope proceeds in two stages. The first is to set the zero point. That's actually rather easy. Lay the SensorTag on the table and record a few dozen to a few hundred values. The average should be zero, since the device is not rotating. Subtract the actual average from each reading to force the mean to zero. Take the standard deviation of the samples if you need a good handle on the accuracy of the device.
Of course, that doesn't mean that a rotation rate of 100 degrees per second is really rotating at 100 degrees per second. If you would like to calibrate the scale, and not just the zero point, place the device on a slow motor from a toy and start it spinning. A motor that turns about twice a second should work well. You can count the rotations over a minute and use the value to calculate the actual rotation, comparing that to the reported rotation. If you really need this kind of calibration, test the device separately for each axis, and spin it in both directions. You might even want to spin it at several speeds.
Gyroscopes are used for all sorts of applications, from physics experiments to flying aircraft. They work really well when coupled with an accelerometer and magnetometer. The combination of inputs from all three sensors helps correct errors from any one of them alone. For example, a gyroscope might have a systematic error that, over time, would indicate it is turning slowly. Couple it with a compass, though, and it's easy to see there is no drift. At the same time, the compass is not terribly accurate for short, quick movements that occur in augmented reality applications or flight. Couple it with the gyroscope, and the rapid changes from a heading can be handled smoothly. The amount of work you put into using the sensors together really depends a lot on the application.
The Source
Here's the source for the gyroscope program. It prints the time when the sample was returned and the rotation rate along each axis to the console, updating the values about once a second. This is a great program to experiment a bit with the magnetometer. For another, see the source code for the main article, Controlling the TI SensorTag with techBASIC. That article displays all six SensorTag sensors in a nice GUI environment.
! Simple program to access the gyroscope on the TI SensorTag.
! 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) = "F000AA50" & servicesHeader : ! Gyroscope
gyro% = 1
! Start the BLE service and begin scanning for devices.
debug = 0
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
END IF
END SUB
! 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
! 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(gyro%) THEN
! Found the gyroscope.
SELECT CASE characteristics(i).uuid
CASE "F000AA51" & servicesHeader
! Tell the gyroscope to begin sending data.
IF debug THEN PRINT "Start gyroscope."
DIM value(2) as INTEGER
value = [0, 1]
peripheral.writeCharacteristic(characteristics(i), value, 0)
peripheral.setNotify(characteristics(i), 1)
CASE "F000AA52" & servicesHeader
! Turn the gyroscope on.
IF debug THEN PRINT "Gyroscope on."
DIM value(1) as INTEGER
value(1) = 7
peripheral.writeCharacteristic(characteristics(i), value, 1)
END SELECT
END IF
NEXT
END IF
END SUB
! 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 "F000AA51" & servicesHeader
! Update the gyroscope.
c = 65536.0/500.0
x = ((value(2) << 8) BITOR value(1))/c
y = ((value(4) << 8) BITOR value(3))/c
z = ((value(6) << 8) BITOR value(5))/c
PRINT time, x, y, z
CASE ELSE
PRINT "Read from "; characteristic.uuid
END SELECT
ELSE IF kind = 3 AND err <> 0 THEN
PRINT "Error writing "; characteristic.uuid; ": ("; err; ") "; message
END IF
END SUB
Tuesday, October 30, 2012
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. techBASIC 2.3 is available now in the App Store.