Accessing the Bluetooth low energy Thermometer on the TI SensorTag
Accessing the Bluetooth low energy Thermometer on the TI SensorTag
Accessing the Bluetooth low energy Thermometer on the TI SensorTag
This blog explores how to read temperatures 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 thermometer. 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.
The TMP006 Thermometer
The SensorTag uses the TMP006 chip to read temperature. The amazing thing about this chip is not that it can sense temperature. Heck, a thermocouple can do that. The amazing thing is that it can sense temperature remotely by examining the infra-red heat signature from an object. The thermometer actually returns two temperatures. One is the temperature on the circuit board, called the die temperature. The other is the temperature of the object the thermometer is looking at through the window on the front of the SensorTag. This is called the target temperature.
Accessing the Thermometer
Here's the Generic Attribute Profile (GATT) for the thermometer service. It's pretty simple, with just two attributes.
Attribute 0xAA02 turns the sensor on or off. Our program turns it on by writing a 1 to this attribute. You can turn it off by writing a zero, but this simple program doesn't turn the sensor off.
CASE "F000AA02" & servicesHeader
DIM value(1) as INTEGER
value(1) = 1
peripheral.writeCharacteristic(characteristics(i), value, 1)
0xAA01 is used to read the thermometer. There are two ways to read sensors on the SensorTag, either reading it when the program wants the temperature or asking the thermometer to notify the program whenever a new sensor reading is available. The first step is to tell the thermometer which way we want to read values. Write a 1 and 0 to the characteristic to receive notifications when a value is available, then ask it to start sending notifications.
CASE "F000AA01" & 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 returned value has two different temperatures stored in four bytes. Bytes 3 and 4 are the die temperature, while bytes 1 and 2 contain the raw data used to calculate the target temperature. Both are unsigned values stores least-significant byte first. The conversion to degrees Celsius is
where t1,2 is the raw value returned in the first two bytes, and t3,4 is the raw value returned in the second pair of bytes. Here's what we get after converting the equations to BASIC.
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 "F000AA01" & servicesHeader
! Update the thermometer.
temp& = (value(3) BITOR (value(4) << 8)) BITAND $00FFFF
temp = temp&/128.0
target& = (value(1) BITOR (value(2) << 8)) BITAND $00FFFF
target = target&*0.00000015625
die2 = 273.15 + temp
s0 = 6.4e-14
a1 = 1.75e-3
a2 = -1.678e-5
b0 = -2.9e-5
b1 = -5.7e-7
b2 = 4.63e-9
c2 = 13.4
tref = 298.15
dt2 = (die2 - tref)*(dies2 - tref)
S = s0*(1 + a1*(die2 - tref) + a2*dt2)
Vos = b0 + b1*(die2 - tref) + b2*dt2
fObj = (target - Vos) + c2*((target - Vos)*(target - Vos))
tObj = (die2^4 + fObj/S)^0.25
tObj = tObj - 273.15
if Math.isNaN(tObj) then
tObj = 1
end if
PRINT "Die temperature: "; temp; " Target temperature: "; tObj
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 Thermometer
The target temperature tends to jump around a bit as the chip sees various temperatures. I'd suggest keeping a running average of the last few values returned to keep this stable. It will make the thermometer slower to respond to a change, but it will also give a steadier reading that is less inclined to jump around due to noise.
Of course, if you're after the temperature at the SensorTag, you can just use the die temperature. This would be fine for a weather station. The SensorTag doesn't use much power, so it won't heat the thermometer much.
The Source
Here's the source for the thermometer program. It dumps both the die and remote temperature to the console as they are returned from the SensorTag. See the source code from, Controlling the TI SensorTag with techBASIC for a program that displays the target temperature using a nice GUI.
! Simple program to access the theromoeter 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) = "F000AA00" & servicesHeader : ! Thermometer
therm% = 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(therm%) THEN
! Found the thermometer.
SELECT CASE characteristics(i).uuid
CASE "F000AA01" & servicesHeader
! Tell the thermometer to begin sending data.
IF debug THEN PRINT "Start thermometer."
DIM value(2) as INTEGER
value = [0, 1]
peripheral.writeCharacteristic(characteristics(i), value, 0)
peripheral.setNotify(characteristics(i), 1)
CASE "F000AA02" & servicesHeader
! Turn the thermometer sensor on.
IF debug THEN PRINT "Thermometer on."
DIM value(1) as INTEGER
value(1) = 1
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 "F000AA01" & servicesHeader
! Update the thermometer.
temp = value(3) BITOR (value(4) << 8)
temp = temp/128.0
target = value(1) BITOR (value(2) << 8)
target = target*0.00000015625
die2 = 273.15 + temp
s0 = 6.4e-14
a1 = 1.75e-3
a2 = -1.678e-5
b0 = -2.9e-5
b1 = -5.7e-7
b2 = 4.63e-9
c2 = 13.4
tref = 298.15
dt2 = (die2 - tref)*(dies2 - tref)
S = s0*(1 + a1*(die2 - tref) + a2*dt2)
Vos = b0 + b1*(die2 - tref) + b2*dt2
fObj = (target - Vos) + c2*((target - Vos)*(target - Vos))
tObj = (die2^4 + fObj/S)^0.25
tObj = tObj - 273.15
if Math.isNaN(tObj) then
tObj = 1
end if
PRINT "Die temperature: "; temp; " Target temperature: "; tObj
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
Wednesday, October 31, 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.