The object of this tutorial is to show you how to create a Bluetooth Low Energy (BLE) Thermometer using an M0 Feather Bluefruit LE board from Adafruit and a non-contact Infrared Temperature sensor from SeeedStudio.
We will use a momentary push button, or touch sensor, to trigger a temperature reading and a buzzer will be used to indicate that a reading has been taken and has been sent via Nordic Bluetooth LE module using the standard BLE Health Thermometer GATT Service (0x1809).
The tutorial will also show you how to create your own Android app, using MIT App Inventor, to read the temperature measurement data received and to display the correct temperature result on the screen. The app will also check to see if any of the optional data is included, such as the "Temperature Type" information. This optional BLE characteristic tells us the location of the temperature reading. In this tutorial, the location is set to "Body (general)".
The Infrared Temperature Sensor in ActionThe beauty about using standard Bluetooth GATT Services to transmit data is that it allows for easy interoperability with different apps etc. This will be demonstrated in the video.
What is also demonstrated, to a degree, is the sensitivity to how far the temperature sensor is away from the object (note temperature of bottle of juice showed a 3 degree temperature difference). The ideal distance for more accurate measurements is around 7 to 9 cm.
The Components needed for the project1. The Adafruit M0 Feather Bluefruit LE board
The Adafruit Feather M0 contains an ATSAMD21G18 ARM Cortex M0 microprocessor, which is clocked at 48 MHz and includes 256k of FLASH and 32k of RAM. This is the same MCU as an Arduino Zero.
The Bluefruit Low Energy (LE) module on the board is an nRF51822 chipset from Nordic Semiconductor.
There are many great things about this board, but one handy feature, which we need for the project, is that it has a connector for a 3.7V Lithium polymer battery and there is also built in battery charging via the micro USB connector.
Adafruit have an awesome learning guide about this product. This can be viewed by clicking here. Here you will find details about the pinouts etc. They also provide a very handy pinout guide, which I've included here:
The following pins are used in this project:
- Nordic Bluetooth LE embedded module - this uses the hardware SPI settings (this cannot be changed).
- Grove Infrared Temperature Sensor module - this uses pins A0 and A1. Any analogue pin could be used here.
- Touch Sensor (or Push Button) - this uses pin 12, although any digital pin could be used.
- Buzzer - this uses pin 17, although any digital pin could be used.
2. The Grove Infrared Temperature Sensor module
The Grove Infrared Temperature Sensor module, from SeeedStudio, is a non-contact temperature sensor, which uses an OTP-538U thermopile sensor. You would typically find, for example, thermopile sensors being used in ear thermometers.
For further details on this sensor, please refer to the SeeedStudio wiki page.
The sensor module has two output pins, namely:
- The first pin, labelled SUR, provides the surrounding temperature value via an analogue voltage signal. In the demo code this is connected to pin A0 of an Arduino board / M0 Feather.
- The second pin, labelled OBJ, provides the object temperature value as an analogue voltage signal. In the demo code this is connected to pin A1 of an Arduino board / M0 Feather.
The SeeedStudio page indicate that the sensor accuracy is plus minus 2 degrees. As calibration is also involved (see firmware code), accuracy is not assured from the outset. In my demo video, shown below, I have made no attempt to validate the accuracy of the sensor.
3. Touch Sensor and Buzzer
For this project, I used a capacitive touch sensor, as the small metallic plate that detects touch can be place directly under the enclosure so no holes are required. It also just happened that I had a touch sensor on my desk, but a momentary push-button would work just as well too.
A buzzer is also used in the project to alert the user that a temperature reading has been taken. In this case, I used a buzzer which included an oscillating circuit and therefore I did not need to use PWM to generate the required frequency to create a tone. To drive the buzzer you will need to use an NPN transistor as a switch - see schematic at bottom of page. For prototyping, I could have used a grove buzzer, from SeeedStudio, as that already includes the transistor, but I did not have one available.
4. Enclosures
I used an old Arduino Project Enclosure to house all the breadboard, all the electronics and the lithium battery. The Ethernet opening just happened to be the perfect size for the smaller enclosure, which housed the Infrared temperature sensor.
The firmware for the deviceThis tutorial makes use of the infrared temperature sensor demo code provided in the SeeedStudio wiki page, which has been modified to make more readable for the tutorial and to allow the code work on a Feather M0 board.
This temperature measurement code is then merged with the health thermometer example, which can be found with the Adafruit Bluefruit LE nRF51 library, with some modifications made to allow for optional Health Thermometer GATT Service (0x1809) Characteristic data to be included.
So, to get started you will need to grab this library using the Arduino IDE Library Manager, as shown here:
Now, let's walk through some of the code, starting with the header files.
#include <Arduino.h>
#include <SPI.h>
#include "Adafruit_BLE.h"
#include "Adafruit_BluefruitLE_SPI.h"
#include "Adafruit_BLEGatt.h"
#include "IEEE11073float.h"
#include "BluefruitConfig.h"
The key header file for this health thermometer project is "IEEEE11073float.h", as it is used to convert the temperature data, which is represented as a float data type, into the correct 5 byte data representation used to transmit the value via BLE.
The 5 byte data representation is made up of one byte for the coefficient and 4 bytes (or 32 bit value) for the mantissa.
// Health thermometer location definitions
const uint8_t
BLE_HTS_TEMP_TYPE_ARMPIT = 1,
BLE_HTS_TEMP_TYPE_BODY = 2,
BLE_HTS_TEMP_TYPE_EAR = 3,
BLE_HTS_TEMP_TYPE_FINGER = 4,
BLE_HTS_TEMP_TYPE_GI_TRACT = 5,
BLE_HTS_TEMP_TYPE_MOUTH = 6,
BLE_HTS_TEMP_TYPE_RECTUM = 7,
BLE_HTS_TEMP_TYPE_TOE = 8,
BLE_HTS_TEMP_TYPE_EAR_DRUM = 9;
These are the standard health thermometer GATT service Temperature Type locations, which are defined here.
const long RT[110] = {
531958,504588,478788,454457,431504,409843,389394,370082,351839,334598,
318300,302903,288329,274533,261471,249100,237381,226276,215750,205768,
196300,187316,178788,170691,163002,155700,148766,142183,135936,130012,
124400,119038,113928,109059,104420,100000,95788,91775,87950,84305,
80830,77517,74357,71342,68466,65720,63098,60595,58202,55916,
53730,51645,49652,47746,45924,44180,42511,40912,39380,37910,
36500,35155,33866,32631,31446,30311,29222,28177,27175,26213,
25290,24403,23554,22738,21955,21202,20479,19783,19115,18472,
17854,17260,16688,16138,15608,15098,14608,14135,13680,13242,
12819,12412,12020,11642,11278,10926,10587,10260,9945,9641,
9347,9063,8789,8525,8270,8023,7785,7555,7333,7118
};
These are the resistance values for different ambient temperatures, which are given to us by the sensor manufacturer. These values are also found in the SeeedStudio code example, although the SeeedStudio code missed a value, hence you may notice that the last value shown here is different.
Graphically, these values look like this:
To determine the temperature we need to calculate the resistance value, which is done by taking a reading from the analog pin A0 (SUR). This gives us a voltage which is then plugged into this formula (comes from the SeeedStudio code example) to give us a resistance value:
Rval = long(2000000*tmpVolt/(2.50-tmpVolt));
And where does the 2, 000, 000 and the 2.50 values come from? Well, the 2 million is the ohms of a series resistor in the circuit and the 2.50 is the reference voltage used (SIG2). These can be seen in the schematic:
Then for the thermopile infrared sensor, we have a two dimensional array of values, as given by the sensor manufacturer, which will be used to calculate the temperature reading.
const float OBJ [13][12]={
{ 0,-0.274,-0.58,-0.922,-1.301,-1.721,-2.183,-2.691,-3.247,-3.854,-4.516,-5.236}, //
{ 0.271,0,-0.303,-0.642,-1.018,-1.434,-1.894,-2.398,-2.951,-3.556,-4.215,-4.931}, //→surrounding temperature,from -10,0,10,...100
{ 0.567,0.3,0,-0.335,-0.708,-1.121,-1.577,-2.078,-2.628,-3.229,-3.884,-4.597}, // ↓object temperature,from -10,0,10,...110
{ 0.891,0.628,0.331,0,-0.369,-0.778,-1.23,-1.728,-2.274,-2.871,-3.523,-4.232},
{ 1.244,0.985,0.692,0.365,0,-0.405,-0.853,-1.347,-1.889,-2.482,-3.13,-3.835},
{ 1.628,1.372,1.084,0.761,0.401,0,-0.444,-0.933,-1.47,-2.059,-2.702,-3.403},
{ 2.043,1.792,1.509,1.191,0.835,0.439,0,-0.484,-1.017,-1.601,-2.24,-2.936},
{ 2.491,2.246,1.968,1.655,1.304,0.913,0.479,0,-0.528,-1.107,-1.74,-2.431},
{ 2.975,2.735,2.462,2.155,1.809,1.424,0.996,0.522,0,-0.573,-1.201,-1.887},
{ 3.495,3.261,2.994,2.692,2.353,1.974,1.552,1.084,0.568,0,-0.622,-1.301},
{ 4.053,3.825,3.565,3.27,2.937,2.564,2.148,1.687,1.177,0.616,0,-0.673},
{ 4.651,4.43,4.177,3.888,3.562,3.196,2.787,2.332,1.829,1.275,0.666,0},
{ 5.29,5.076,4.83,4.549,4.231,3.872,3.47,3.023,2.527,1.98,1.379,0.72}
};
These values are the millivolt values of a test object's temperature at different ambient temperatures. Graphically it looks like this:
The millivolt readings also have to be adjusted due to an voltage offset of 0.5Volts.
const float HW_OFFSETVOLTAGE = 0.500; // This is the hardware defined offset voltage - don't change
According to the SeeedStudio code example, sensor calibration is required to achieve equilibrium between thermistor and thermopile readings.
const float CALIB_OFFSETVOLTAGE = 0.0175; // This is a calibrated offset factor.
The calibration factor should initially be set to 0.014 (as per SeeedStudio code instructions). We then uncomment the CALIB define setting and we will get readings from a 30 second calibration test using this routine (a longer time is recommended by SeeedStudio):
void calibSensorVoltage() {
float cumVoltages = 0.0;
int objValue = 0;
for (byte xx = 0; xx < 200; xx++) {
// read the values from the sensor:
objValue = analogRead(OBJ_TEMP_PIN); // discard first reading
delay(20);
objValue = analogRead(OBJ_TEMP_PIN);
for (byte i = 0; i< 4; i++) {
delay(20);
objValue += analogRead(OBJ_TEMP_PIN);
}
objValue /= 5;
// convert to voltage and add
cumVoltages += (objValue*ANAREF_VALS[anaRef_ind]/1023.0);
delay(100);
}
// Now save the average less the offset
calibVolt = (cumVoltages/200.0) - (HW_OFFSETVOLTAGE + CALIB_OFFSETVOLTAGE);
if (calibVolt*100 > 0.5) {
Serial.print(F("Add this to Calibrated Offset Voltage: "));
Serial.println(calibVolt, 4);
}
else Serial.println(F("Sensor voltage calibration fine"));
}
For more fine temperature tuning you will need to add in your own calibration factors.
A key setting in getting good analog readings is setting the right Analog Reference voltage. The M0 boards have different settings to the Arduino UNO, for example. This is handled here in the setup routine:
analogReference(AR_INTERNAL1V0); //set the refenrence voltage 1.0V,the distinguishability can up to 1mV.
Now onto the Bluetooth Low Energy settings.
All Gatt Service settings are handled in this library:
#include "Adafruit_BLEGatt.h"
Adafruit_BLEGatt gatt(ble);
To add a new Gatt Service we use:
htsServiceId = gatt.addService(0x1809);
Then to add our Characteristics for this Gatt Service, we use:
htsMeasureCharId = gatt.addCharacteristic(0x2A1C, GATT_CHARS_PROPERTIES_INDICATE, 5, 13, BLE_DATATYPE_BYTEARRAY);
Now notice the above.
This is different from the standard health termometer example. In the standard healthermometer example, they set the min and max byte array size to be 5. This makes no allowances for any of the optional data fields. As I wanted to include temperature type in the measurement data and have an option for date-time to be included too, the max size is increased to 13.
Note here that the "INDICATE" option is used as per specification.
There is also another option for Temperature Type data, which can be sent using its own characteristic.
htsBodyLocId = gatt.addCharacteristic(0x2A1D, GATT_CHARS_PROPERTIES_READ, 1, 1, BLE_DATATYPE_BYTEARRAY);
Here the Characteristic property is set to "READ" as per specification. This allows the app to read this value at any time once it has been set.
To set this value we use the following:
// Set the body location - needs to be within an array
uint8_t TempLocation[1] = {BLE_HTS_TEMP_TYPE_BODY};
gatt.setChar(htsBodyLocId, TempLocation, sizeof(TempLocation));
Finally we set up the advertising data fields - these define all the GATT services available by the device:
uint8_t advdata[] { 0x02, 0x01, 0x06, 0x05, 0x02, 0x09, 0x18, 0x0a, 0x18 };
ble.setAdvData( advdata, sizeof(advdata) );
The rest of the code should be reasonable to understand. The main function outside of the main loop function is simply called measureTemp(). This routine handles both thermistor and thermopile readings (in the SeeedStudio code there are two routines).
Then there are two other functions. One to handle thermistor data extrapolation for the resistance values and the other that deals with extrapolation on two axis's.
Finally, what is on the TODO list:
Just to note that the code provided does not include any deep sleep function, so power consumption has not been optimised for battery usage.
But as this prototype device uses a rechargeable battery the next obvious "todo" will be to include the Bluetooth Battery Service (0x180F) so that I will know the current battery level as a percentage.
The MIT App Inventor Project for the Android appThis is a single screen app (required when using Bluetooth to maintain connectivity), which hides or displays different horizontal and vertical arrangements, depending on user action. The app main components within screen1 are as follows:
Some of the extension names have been shortened (my preference):
- BLE1 = BluetoothLE extension
- BTC1 = BluetoothClient component
- AST1 = Activity Starter
Note that the Web1 component is included as a future option for online connectivity. It is not used here.
A walk through the key parts of the app:
- When opening the app it will check to see if the phone's Bluetooth is enabled. If it is not enabled then a request is made to turn Bluetooth on.
- When the user clicks on the scan button it will continue to scan for Bluetooth devices until the user clicks on the Stop Scan button or selects a Bluetooth device that is shown in the device list. The device list is updated every time a new device is discovered.
- When the user clicks on a device shown in the device list, the "VA12_connectbar" section is then displayed, providing the user with an option to connect to that device.
- When the user clicks on the connect button, the "VA_BLEscan" section is then hidden from view and the "VA_TEMPdisplay" section is displayed. In the block code a BLE connection request is made.
- When the BLE device is shown to be connected the "when BLE connected" event will be triggered. Here the GATT services are checked to see if it contains the 0x1809 Health Thermometer GATT service.
- If the Health Service does not exist in the BLE device's list of services then the app will disconnect with the device and return the user back to the device scan routine.
- If the Health Service does exist, it will check to see what Charactertic UUID's are linked to this service. It makes sure that the Temperature Measurement UUID exists, which is mandatory, and whether any of the optional Characteristics are available, such as Temperature Type.
- If the Temperature Type option is available it will read the data and display the temperature location on the screen.
- As the Temperature Measurement uses the INDICATE property a "RegisterForBytes" function is used, so that this will trigger a "when BytesReceived" BLE event when new data arrives.
- When temperature data arrives a special temperature parse function is used (as shown below) to display the correct temperature value in the correct units.
- If temperature_type and/or date&time information is also included in the payload it will also parse this out and display this information above the temperature value.
Comments