Accessing the Bluetooth low energy Magnetometer on the TI SensorTag
Accessing the Bluetooth low energy Magnetometer on the TI SensorTag
Accessing the Bluetooth low energy Magnetometer on the TI SensorTag
This blog explores how to read the strength of a magnetic field 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 Magnetometer?
A magnetometer measures the strength of a magnetic field. The most common use is an electronic compass, but they can also be used as simple metal detectors or even current detectors, since electrons flowing through a wire also set up a magnetic field.
Magnetic fields are measured in Teslas, named after the famed inventor Nicola Tesla. The units are

or one Volt-second per meter squared. By comparison, the Earth's magnetic field varies from about 0.025 to 0.065 Teslas, so it's very common to see magnetic field strength given in µT, or micro-Teslas. It's just easier to deal with 25 to 65 µT.
The SensorTag uses the MAG3110 chip, which reports magnetic field strength from -1T to 1T along each of three axis. The chip claims a sensitivity of 0.1 µT, and the SensorTag reports the values to a precision of about 0.03 µT. Of course, that means any digits beyond one tenth of a micro-Tesla should be ignored.
Accessing the Magnetometer
Using the magnetometer on the SensorTag is almost a mirror image of using the accelerometer. Looking at the Generic Attribute Profile (GATT) you see three characteristics that exactly parallel the characteristics for the accelerometer.
0xAA32 characteristic is used to turn the magnetometer on and off. Our program just turns it on.
CASE "F000AA32" & 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 magnetometer off.
0xAA31 is used to read the magnetometer. There are two ways to read the magnetometer, either reading it when the program wants a magnetic field value or asking the magnetometer to notify the program whenever a new sensor reading is available. The first step is to tell the magnetometer 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 "F000AA31" & 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)
The magnetometer sends samples about once every two seconds unless you tell it otherwise. Use 0xAA33 to set the sample rate. The value is expressed in tens of milliseconds, so passing 100 gives the default sample rate of once per second. Keep in mind that the hardware uses the sample rate as a suggestion, not a firm value! If the time between samples is really important, record the time with the sample value.
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 one difference between the GATT for the accelerometer and magnetometer is that the magnetometer returns a two-byte value for each axis rather than the single-byte value returned by the accelerometer. Each value is a two-byte signed integer with the least significant byte first. The integers must be divided by 65536 and multiplied by 2000 to yield the final range of -1000 to 1000 µT. 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 "F000AA31" & servicesHeader
! Update the magnetometer.
c = 65536.0/2000.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 Magnetometer
Calibrating a magnetometer can be pretty tricky. We could use the Earth itself to calibrate the magnetometer, but that doesn't work so well for two reasons. First, the magnetic field for the Earth is far from constant. In fact, there are places on the Earth where a compass points south, not north, and others where a compass won't work at all! The Earth's magnetic field is also not parallel to the surface of the Earth. The angle between horizontal and the actual direction of the magnetic field is called the inclination, and is measured with a device called a dip circle—essentially a compass turned on its side. Since compasses are so ubiquitous for navigation, these values have been mapped pretty carefully. You can get rough values from maps like the ones in this Wikipedia article.
With the inclination, declination (the difference between magnetic north and true north) and field strength in hand, you have a pretty good source for calibrating your magnetometer.
Of course, you're going to move the SensorTag well away from refrigerator magnets, electric motors, extension cords, metal tables and the like before calibration, right? What about the SensorTag itself? Yes, the SensorTag is also generating magnetic fields, and they will even vary depending on which sensors you turn on or off! That really makes the calibration job difficult.
Fortunately, you may not need accurate calibration. If you want to measure the precise magnetic field from an electric motor, by all means, go to the trouble of calibrating the device accurately. If you want to know the rough direction of North, it's good enough to map your magnetometer readings to the values returned by a compass. For some applications, like detecting a current in a wire running behind a wall, you don't need to calibrate the device at all, since you're really just watching for the magnetic field to change.
The Source
Here's the source for the magnetometer program. It prints the time when the sample was returned and the magnetic field strength 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. The magnetometer is one of the rare sensors where I get more from the plot of the strength than from the text values, so be sure and try both programs.
! Simple program to access the humidity sensor 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) = "F000AA30" & servicesHeader : ! Magnetometer
mag% = 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(mag%) THEN
! Found the magnetometer.
SELECT CASE characteristics(i).uuid
CASE "F000AA31" & servicesHeader
! Tell the magnetometer to begin sending data.
IF debug THEN PRINT "Start magnetometer."
DIM value(2) as INTEGER
value = [0, 1]
peripheral.writeCharacteristic(characteristics(i), value, 0)
peripheral.setNotify(characteristics(i), 1)
CASE "F000AA32" & servicesHeader
! Turn the magnetometer sensor on.
IF debug THEN PRINT "Magnetometer on."
DIM value(1) as INTEGER
value(1) = 1
peripheral.writeCharacteristic(characteristics(i), value, 1)
CASE "F000AA33" & servicesHeader
! Set the sample rate to 100ms.
DIM value(1) as INTEGER
value(1) = 100
IF debug THEN PRINT "Setting magnetometer sample rate to "; value(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 "F000AA31" & servicesHeader
! Update the magnetometer.
c = 65536.0/2000.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
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.