This tutorial is part 3 of the series covering the awesome iTracker module from RAK Wireless. Before we proceed please go through the first two parts to understand the development environment and a little bit about BLE protocol.
- PART 1: https://www.hackster.io/naresh-krish/getting-started-with-rak-itracker-module-and-arduino-ide-b78c0f
This part will cover the support for the Sensor module and how to obtain sensor reading from them.
The hardware:The iTracker module is a very unique board. It hosts a plethora of connectivity options like:
- Nb-IoT provided by the Quectel M35 chipset
- Bluetooth provided by the nordic nrf52832 chipset
- GPS provided by the Quectel L70-R chipset
Not just that a host of on board sensors are available as well:
- Opt 3001 light sensor
- LIS3dh Triple axis accelerometer
- Lis2mdl Triple axis magnetic field sensor
- Bme280 Barometric pressure and temperature sensor
Phew !! That is a lot of connectivity and a lot of sensors for such a small module. Some of the use cases for this module are:
- Vehicle Tracker and fleet management
- Personnel positioning
- Home security
- Safety monitoring for children/elders
- Animal husbandry and protection
The module supports the Arduino IDE and development using the IDE is one of the most simplest ways to access the wide array of sensors on the Board.
I will also cover the Espruino Port that is being worked upon by myself in my spare time. It uses Javascript to interface with the board peripherals and also supports wireless firmware programming. The port is still under development and once I test it out, i will start the tutorial on how to get the board up and running
Ok Down to business: The C code for all the drivers can be got here: https://github.com/narioinc/iTrackerSensorDrivers
Format of the libraries:Each of the project consists:
- Its own ser of driver.c an driver.h
- A readme.md file containing a sample implementation
- The itracker I2C drivers (I2C driver based on the NRF I2C drivers)
Lets look at one of the libraries in detail. The BME280 barometric pressure sensor.
BME280 integration:According to Bosch which manufactures the sensor:
"The BME280 is an integrated environmental sensor developed specifically for mobile applications where size and low power consumption are key design constraints. The unit combines individual high linearity, high accuracy sensors for pressure, humidity and temperature in an 8-pin metal-lid 2.5 x 2.5 x 0.93 mm³ LGA package, designed for low current consumption (3.6 μA @1Hz), long term stability and high EMC robustness.
The humidity sensor features an extremely fast response time which supports performance requirements for emerging applications such as context awareness, and high accuracy over a wide temperature range. The pressure sensor is an absolute barometric pressure sensor with features exceptionally high accuracy and resolution at very low noise. The integrated temperature sensor has been optimized for very low noise and high resolution. It is primarily used for temperature compensation of the pressure and humidity sensors, and can also be used for estimating ambient temperature."
On the itracker module, here is the connection schematics:
We can see the P0.0 0.1 0.2 and 0.3 are being used for the 4 wire SPI connection. Lets see the code in action:
#define BME280_SPI_CS_PIN 2
#define BME280_SPI_SDI_PIN 3
#define BME280_SPI_SCK_PIN 4
#define BME280_SPI_SDO_PIN 5
#define I2C_TIMEOUT 100000
uint8_t SPI_Tx_Buf[SPI_BUFSIZE];
uint8_t SPI_Rx_Buf[SPI_BUFSIZE];
volatile uint8_t SPIReadLength, SPIWriteLength;
bool BME280_INIT = false;
static volatile bool spi_xfer_done;
static const nrf_drv_spi_t spi = NRF_DRV_SPI_INSTANCE(SPI_INSTANCE);
void spi_event_handler(nrf_drv_spi_evt_t const * p_event)
{
spi_xfer_done = true;
}
static uint32_t bme280_spi_init(void)
{
uint32_t err_code;
nrf_drv_spi_config_t spi_bme_config = NRF_DRV_SPI_DEFAULT_CONFIG;
spi_bme_config.ss_pin = BME280_SPI_CS_PIN;
spi_bme_config.miso_pin = BME280_SPI_SDO_PIN;
spi_bme_config.mosi_pin = BME280_SPI_SDI_PIN;
spi_bme_config.sck_pin = BME280_SPI_SCK_PIN;
//set SPI transfer mode as blocking
if(!BME280_INIT) err_code = nrf_drv_spi_init(&spi, &spi_bme_config, spi_event_handler);
if(err_code != NRF_SUCCESS)
{
err("BME280: Error while SPI Init");
return err_code;
}else{
BME280_INIT = TRUE;
}
return NRF_SUCCESS;
}
void user_delay_ms(uint32_t period)
{
nrf_delay_ms(period);
}
int8_t user_spi_read(uint8_t dev_id, uint8_t reg_addr, uint8_t *reg_data, uint16_t len)
{
int8_t rslt = 0;
spi_xfer_done = false;
nrf_gpio_pin_write ( BME280_SPI_CS_PIN, 0 );
SPI_Tx_Buf[0] = reg_addr;
APP_ERROR_CHECK(nrf_drv_spi_transfer(&spi, SPI_Tx_Buf, 1, SPI_Rx_Buf, len+1));
while(spi_xfer_done == false);
memcpy(reg_data, &SPI_Rx_Buf[1], len);
nrf_gpio_pin_write ( BME280_SPI_CS_PIN, 1 );
return rslt;
}
int8_t user_spi_write(uint8_t dev_id, uint8_t reg_addr, uint8_t *reg_data, uint16_t len)
{
int8_t rslt = 0;
spi_xfer_done = false;
SPI_Tx_Buf[0] = reg_addr;
memcpy(&SPI_Tx_Buf[1], reg_data, len);
nrf_gpio_pin_write ( BME280_SPI_CS_PIN, 0 );
APP_ERROR_CHECK(nrf_drv_spi_transfer(&spi, SPI_Tx_Buf, len+1, NULL, 0));
while(spi_xfer_done == false);
nrf_gpio_pin_write ( BME280_SPI_CS_PIN, 1 );
return rslt;
}
void itracker_bme280data(){
struct bme280_dev dev;
int8_t rslt = BME280_OK;
bme280_spi_init();
dev.dev_id = 0;
dev.intf = BME280_SPI_INTF;
dev.read = user_spi_read;
dev.write = user_spi_write;
dev.delay_ms = user_delay_ms;
rslt = bme280_init(&dev);
if(rslt != BME280_OK) {
err("BME280 problem during device init");
}
uint8_t settings_sel;
struct bme280_data comp_data;
dev.settings.osr_h = BME280_OVERSAMPLING_1X;
dev.settings.osr_p = BME280_OVERSAMPLING_16X;
dev.settings.osr_t = BME280_OVERSAMPLING_2X;
dev.settings.filter = BME280_FILTER_COEFF_16;
dev.settings.standby_time = BME280_STANDBY_TIME_62_5_MS;
settings_sel = BME280_OSR_PRESS_SEL;
settings_sel |= BME280_OSR_TEMP_SEL;
settings_sel |= BME280_OSR_HUM_SEL;
settings_sel |= BME280_STANDBY_SEL;
settings_sel |= BME280_FILTER_SEL;
rslt = bme280_set_sensor_settings(settings_sel, &dev);
if(rslt != BME280_OK){
err("BME280: issue while applying sensor settings");
}
rslt = bme280_set_sensor_mode(BME280_NORMAL_MODE, &dev);
if(rslt != BME280_OK){
err("BME280: issue while setting sensor mode");
}
rslt = bme280_get_sensor_data(BME280_ALL, &comp_data, &dev);
if(rslt != BME280_OK){
err("BME280: issue while getting sensor data");
}
}
First we define the constants for the Pin connection of the BME280 sensor:
#define BME280_SPI_CS_PIN 2
#define BME280_SPI_SDI_PIN 3
#define BME280_SPI_SCK_PIN 4
#define BME280_SPI_SDO_PIN 5
Next we setup the SPI configuration for the TX and RX characteristics and the set the BME_init variable which keeps track if the module is already intialized to false:
uint8_t SPI_Tx_Buf[SPI_BUFSIZE];
uint8_t SPI_Rx_Buf[SPI_BUFSIZE];
volatile uint8_t SPIReadLength, SPIWriteLength;
bool BME280_INIT = false;
static volatile bool spi_xfer_done;
static const nrf_drv_spi_t spi = NRF_DRV_SPI_INSTANCE(SPI_INSTANCE);
Next we setup the call back function which is called when a SPI transfer is successful. This is recommended as the SPI comm can now be made non blocking.
void spi_event_handler(nrf_drv_spi_evt_t const * p_event)
{
spi_xfer_done = true;
}
Next we specify the BME280 init function and initialize the sensor to start reading data. Next comes the interesting function: the itracker_bme280data(). This is sample implementation on how to read the pressure, barometric and temperature data from the sensor.
This code snippet can now be reused in any of your Arduino projects and you can implement you sensor reading function similar to the itracker_bme280data function. Similarly please see the sensor connections for the other sensors as below.
LIS2MDL:
LIS3DH
OPT3001
Remember in our last tutorial, we saw that we can expose any kind of data via the characteristics in a BLE peripheral. For this purpose we used the venerable BLE peripheral library from here: https://github.com/sandeepmistry/arduino-BLEPeripheral
We can see that creating a characteristics is so easy;
BLEService service = BLEService("fff0");
// create one or more characteristics
BLECharCharacteristic characteristic = BLECharCharacteristic("fff1", BLERead | BLEWrite);
// create one or more descriptors (optional)
BLEDescriptor descriptor = BLEDescriptor("2901", "value");
void setup() {
Serial.begin(115200);
#if defined (__AVR_ATmega32U4__)
delay(5000); //5 seconds delay for enabling to see the start up comments on the serial board
#endif
blePeripheral.setLocalName("local-name"); // optional
blePeripheral.setAdvertisedServiceUuid(service.uuid()); // optional
// add attributes (services, characteristics, descriptors) to peripheral
blePeripheral.addAttribute(service);
blePeripheral.addAttribute(characteristic);
blePeripheral.addAttribute(descriptor);
// set initial value
characteristic.setValue(0);
// begin initialization
blePeripheral.begin();
}
Here we can now change the line:
characteristic.setValue(0);
to:
characteristic.setValue(<sensor daty from itracker_bme280data()>);
This lets you set the data of the charcateristics to the value read from the sensor. Similarly you can do for all other sensors as well.
These values can now be ready by an iOS or and Android application and act upon the data or send it to the internet web services for further processing.
Comments