SODAQ ExpLoRer and Bluetooth

SODAQ ExpLoRer board includes a Microchip RN4871 Bluetooth module. This post explains how to start using it.

Reference documentation

Getting started with ExpLoRer

Check this post.

First test – get firmware version

Once the Microchip_RN487x library is installed, several sample applications are available (File / Examples / Microchip_RN487x-master), and can be used as references for some test code.

Let’s try to write our first application, that will request and display RN4871 firmware version (that’s always a good idea to check versions of various software building blocks you are about to use).

Source code is below:

#include <RN487x_BLE.h>

#define debugSerial SerialUSB
#define bleSerial Serial1

#define SERIAL_TIMEOUT 10000  // 10 s

const char* fwVersion;

void setRgbColor(uint8_t red, uint8_t green, uint8_t blue)
{
  red = 255 - red ;
  green = 255 - green ;
  blue = 255 - blue ;

  analogWrite(LED_RED, red) ;
  analogWrite(LED_GREEN, green) ;
  analogWrite(LED_BLUE, blue) ;
}

void setup() {

  // Wait for monitor window to be used, up to SERIAL_TIMEOUT.
  while ((!debugSerial) && (millis() < SERIAL_TIMEOUT));
  debugSerial.begin(115200);  

  // Set the optional debug stream
  rn487xBle.setDiag(debugSerial) ;
  // Initialize the BLE hardware
  rn487xBle.hwInit() ;
  // Open the communication pipe with the BLE module
  bleSerial.begin(rn487xBle.getDefaultBaudRate()) ;
  // Assign the BLE serial port to the BLE library
  rn487xBle.initBleStream(&bleSerial) ;
  // Finalize the init. process
  if (rn487xBle.swInit())
  {
    setRgbColor(0, 255, 0) ;
    debugSerial.println("Init. procedure done!") ;
  }
  else
  {
    setRgbColor(255, 0, 0) ;
    debugSerial.println("Init. procedure failed!") ;
    while(1) ;
  }

}

void loop() {

  // BT module must be put in command mode first.
  rn487xBle.enterCommandMode() ;
  // Request firmware version.
  bool res = rn487xBle.getFirmwareVersion();
  if (!res) {
    debugSerial.println("Could not get firmware version. Stopping...");
    while(1);
  }
  // At this stage, firmware version was returned by RN4871 module. Get it.
  fwVersion = rn487xBle.getLastResponse();
  if (fwVersion == NULL) {
    debugSerial.println("Firmware version can't be read. Stopping...");
    while(1);
  }
  // At this stage, we should have the firmware version.
  debugSerial.print("Firmware version: "); 
  debugSerial.println(fwVersion);
  // Now, stop.
  while(1);

}

Following result is displayed in the monitor window:

Init. procedure done!
Firmware version: RN4871 V1.18.3 4/14/2016 (c)Microchip Technology Inc

According to Microchip website, that’s the latest version. Good!

Before going farther, one good practice is to reset the configurations into factory default, to be sure the module will behave according to the documentation. That’s exactly what I had to do: I’m using a second-hand board, and it rapidly appeared that the RN4871 was reacting in an unexpected way to the commands I was sending it. A factory reset put things back in order. The source code is below:

#include <RN487x_BLE.h>

#define debugSerial SerialUSB
#define bleSerial Serial1
#define SERIAL_TIMEOUT  10000

void initLed()
{
  pinMode(LED_RED, OUTPUT) ;
  pinMode(LED_GREEN, OUTPUT) ;
  pinMode(LED_BLUE, OUTPUT) ;  
}

void setRgbColor(uint8_t red, uint8_t green, uint8_t blue)
{
  red = 255 - red ;
  green = 255 - green ;
  blue = 255 - blue ;

  analogWrite(LED_RED, red) ;
  analogWrite(LED_GREEN, green) ;
  analogWrite(LED_BLUE, blue) ;
}

void setup()
{
  while ((!debugSerial) && (millis() < SERIAL_TIMEOUT)) ;
  
  debugSerial.begin(115200) ;

  initLed() ;
  setRgbColor(127, 127, 127) ;

  // Set the optional debug stream
  rn487xBle.setDiag(debugSerial) ;
  // Initialize the BLE hardware
  rn487xBle.hwInit() ;
  // Open the communication pipe with the BLE module
  bleSerial.begin(rn487xBle.getDefaultBaudRate()) ;
  // Assign the BLE serial port to the BLE library
  rn487xBle.initBleStream(&bleSerial) ;
  // Finalize the init. process
  if (!rn487xBle.swInit()) {
    setRgbColor(127, 0, 0) ;
    debugSerial.println("Init. procedure failed!") ;
    while(1) ;    
  }
  // At this stage, init. succeeded.
  setRgbColor(0, 127, 0) ;
  debugSerial.println("Init. procedure done!") ;

  // Perform factory reset.
  rn487xBle.enterCommandMode();
  if (!rn487xBle.factoryReset()) {
    debugSerial.println("Couldn't perform factory reset");
    setRgbColor(127, 0, 0);
    while(1);
  }
  setRgbColor(127, 127, 0);
  debugSerial.println("Factory reset done") ;

}

void loop()
{

}

Second test – GATT service

In this test, we configure the RN4871 so that it can be discovered by an Android smartphone, and that it provides one BLE GATT service, with one read characteristic and one write characteristic.

First, install the Microchip Bluetooth Smart Discover application on the smartphone.

Then, load following program into the board:

// Derived from BLE_Peripheral.ino

/*
    (c) 2016 Microchip Technology Inc. and its subsidiaries. You may use this
    software and any derivatives exclusively with Microchip products.

    THIS SOFTWARE IS SUPPLIED BY MICROCHIP "AS IS". NO WARRANTIES, WHETHER
    EXPRESS, IMPLIED OR STATUTORY, APPLY TO THIS SOFTWARE, INCLUDING ANY IMPLIED
    WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY, AND FITNESS FOR A
    PARTICULAR PURPOSE, OR ITS INTERACTION WITH MICROCHIP PRODUCTS, COMBINATION
    WITH ANY OTHER PRODUCTS, OR USE IN ANY APPLICATION.

    IN NO EVENT WILL MICROCHIP BE LIABLE FOR ANY INDIRECT, SPECIAL, PUNITIVE,
    INCIDENTAL OR CONSEQUENTIAL LOSS, DAMAGE, COST OR EXPENSE OF ANY KIND
    WHATSOEVER RELATED TO THE SOFTWARE, HOWEVER CAUSED, EVEN IF MICROCHIP HAS
    BEEN ADVISED OF THE POSSIBILITY OR THE DAMAGES ARE FORESEEABLE. TO THE
    FULLEST EXTENT ALLOWED BY LAW, MICROCHIP'S TOTAL LIABILITY ON ALL CLAIMS IN
    ANY WAY RELATED TO THIS SOFTWARE WILL NOT EXCEED THE AMOUNT OF FEES, IF ANY,
    THAT YOU HAVE PAID DIRECTLY TO MICROCHIP FOR THIS SOFTWARE.

    MICROCHIP PROVIDES THIS SOFTWARE CONDITIONALLY UPON YOUR ACCEPTANCE OF THESE
    TERMS.
*/

#include <RN487x_BLE.h>

#define debugSerial SerialUSB
#define bleSerial Serial1
#define SERIAL_TIMEOUT  10000

const char* myDeviceName = "testPeriph" ;  // Custom Device name
const char* myPrivateServiceUUID = "AD11CF40063F11E5BE3E0002A5D5C51B" ; // Custom private service UUID
const char* temperatureCharacteristicUUID = "BF3FBD80063F11E59E690002A5D5C501" ;  // custom characteristic GATT
const uint8_t temperatureCharacteristicLen = 2 ;  // data length (in bytes)
const uint16_t temperatureHandle = 0x72 ;
char temperaturePayload[temperatureCharacteristicLen*2] ;
float temperature_s ;
uint8_t newTempValue_u8 = 0 ;
uint8_t prevTempValue_u8 = 0 ;
const char* ledCharacteristicUUID = "BF3FBD80063F11E59E690002A5D5C503" ;  // custom characteristic GATT
const uint8_t ledCharacteristicLen = 6 ;
uint16_t ledHandle = 0x75 ;
const char* ledPayload ;

void initLed()
{
  pinMode(LED_BUILTIN, OUTPUT) ;
  pinMode(LED_RED, OUTPUT) ;
  pinMode(LED_GREEN, OUTPUT) ;
  pinMode(LED_BLUE, OUTPUT) ;  
  pinMode(BUTTON, INPUT_PULLUP) ;
}

void turnBlueLedOn()
{
  digitalWrite(LED_BUILTIN, HIGH) ;
}

void turnBlueLedOff()
{
  digitalWrite(LED_BUILTIN, LOW) ;
}

void setRgbColor(uint8_t red, uint8_t green, uint8_t blue)
{
  red = 255 - red ;
  green = 255 - green ;
  blue = 255 - blue ;

  analogWrite(LED_RED, red) ;
  analogWrite(LED_GREEN, green) ;
  analogWrite(LED_BLUE, blue) ;
}

void initTemperature()
{
  pinMode(TEMP_SENSOR, INPUT) ;
  //Set ADC resolution to 12 bits
  analogReadResolution(12) ;  
}

float getTemperature()
{
  float mVolts = (float)analogRead(TEMP_SENSOR) * 3300.0 / 1023.0 ;
  float temp = (mVolts - 500.0) / 100.0 ;
  return temp ;
}

void setup()
{
  while ((!debugSerial) && (millis() < SERIAL_TIMEOUT)) ;
  
    debugSerial.begin(115200) ;

  initLed() ;
  initTemperature() ;

  // Set the optional debug stream
  rn487xBle.setDiag(debugSerial) ;
  // Initialize the BLE hardware
  rn487xBle.hwInit() ;
  // Open the communication pipe with the BLE module
  bleSerial.begin(rn487xBle.getDefaultBaudRate()) ;
  // Assign the BLE serial port to the BLE library
  rn487xBle.initBleStream(&bleSerial) ;
  // Finalize the init. process
  if (rn487xBle.swInit())
  {
    setRgbColor(0, 255, 0) ;
    debugSerial.println("Init. procedure done!") ;
  }
  else
  {
    setRgbColor(255, 0, 0) ;
    debugSerial.println("Init. procedure failed!") ;
    while(1) ;
  }

  // Fist, enter into command mode
  rn487xBle.enterCommandMode() ;
  // Stop advertising before starting the demo
  rn487xBle.stopAdvertising() ;
  // Set the advertising output power (range: min = 5, max = 0)
  rn487xBle.setAdvPower(3) ;
  // Set the serialized device name, i.e. device n,ame + 2 last bytes from MAC address.
  rn487xBle.setSerializedName(myDeviceName) ;
  rn487xBle.clearAllServices() ;
  rn487xBle.reboot() ;
  rn487xBle.enterCommandMode() ;  
  // Set a private service ...
  rn487xBle.setServiceUUID(myPrivateServiceUUID) ;
  // which contains ...
  // ...a temperature characteristic; readable and can perform notification, 2-octets size
  rn487xBle.setCharactUUID(temperatureCharacteristicUUID, READ_PROPERTY | NOTIFY_PROPERTY, temperatureCharacteristicLen) ;
  // ...an LED characteristic; properties: writable
  rn487xBle.setCharactUUID(ledCharacteristicUUID, WRITE_PROPERTY, ledCharacteristicLen) ;       
  // take into account the settings by issuing a reboot
  rn487xBle.reboot() ;
  rn487xBle.enterCommandMode() ;
  // Clear adv. packet
  rn487xBle.clearImmediateAdvertising() ;
  // Start adv.
  // The line below is required, to let an Android device discover the board. 
  rn487xBle.startImmediateAdvertising(AD_TYPE_FLAGS, "06");
  rn487xBle.startImmediateAdvertising(AD_TYPE_MANUFACTURE_SPECIFIC_DATA, "CD00FE14AD11CF40063F11E5BE3E0002A5D5C51B") ;

  debugSerial.println("Starter Kit as a Peripheral with private service") ;
  debugSerial.println("================================================") ;
  debugSerial.print("Private service: ") ;
  debugSerial.println(myPrivateServiceUUID) ;
  debugSerial.print("Private characteristic used for the Temperature: ") ;
  debugSerial.println(temperatureCharacteristicUUID) ;
  debugSerial.print("Private characteristic used for the LED: ") ;
  debugSerial.println(ledCharacteristicUUID) ;
  debugSerial.println("You can now establish a connection from the Microchip SmartDiscovery App") ;
  debugSerial.print("with the starter kit: ") ;
  debugSerial.println(rn487xBle.getDeviceName()) ;

}

void loop()
{
  // Check the connection status
  if (rn487xBle.getConnectionStatus())
  {
    // Connected to a peer
    debugSerial.print("Connected to a peer central ") ;
    debugSerial.println(rn487xBle.getLastResponse()) ;

    // Temperature
    prevTempValue_u8 = newTempValue_u8 ;
    temperature_s = getTemperature() ;
    newTempValue_u8 = (int)temperature_s ;
    // Update the local characteristic only if value has changed
    if (newTempValue_u8 != prevTempValue_u8)
    {
      uint8_t data = newTempValue_u8 ;
      temperaturePayload[3] = '0' + (data % 10) ; // LSB
      data /= 10 ;
      temperaturePayload[2] = '0' + (data % 10) ;
      data /= 10 ;
      temperaturePayload[1] = '0' + (data % 10) ;
      if (temperature_s > 0)  temperaturePayload[0] = '0' ; // MSB = 0, positive temp.
      else                    temperaturePayload[0] = '8' ; // MSB = 1, negative temp.  
         
      if (rn487xBle.writeLocalCharacteristic(temperatureHandle, temperaturePayload))
      {
        debugSerial.print("Temperature characteristic has been updated with the value = ") ; debugSerial.println(newTempValue_u8) ;
      }
    }

    // LED
    if (rn487xBle.readLocalCharacteristic(ledHandle))
    {
      // Read the local characteristic written by the client: smartphone/tablet
      // The payload must follow the 6-bit RGB coded format:
      // [0] Red MSB
      // [1] Red LSB
      // [2] Green MSB
      // [3] Green LSB
      // [4] Blue MSB
      // [5] Blue LSB
      // Each color can be coded from range 0x00 to 0xFF
      ledPayload = rn487xBle.getLastResponse() ;
      if (ledPayload != NULL)
      {
        if (ledPayload[0] != 'N') // != "N/A" response
        {
          debugSerial.println(ledPayload) ;
          if (strlen(ledPayload) == ledCharacteristicLen)
          {
            // Filter only the 6-digit len
            // Convert ASCII to integer values
            uint8_t r = (ledPayload[0] - '0') * 10 + (ledPayload[1] - '0') ;
            uint8_t g = (ledPayload[2] - '0') * 10 + (ledPayload[3] - '0') ;
            uint8_t b = (ledPayload[4] - '0') * 10 + (ledPayload[5] - '0') ;
            setRgbColor(r, g, b) ;
          }
        }
      }
    }        
  }
  else
  {
    // Not connected to a peer device
    debugSerial.println("Not connected to a peer device") ;
  }
  // Delay inter connection polling
  delay(3000) ;
}

This code is derived from one of the samples provided with the library (that’s why I kept Microchip copyright notice). But there is one important difference: I added following line, for the advertisement configuration:

rn487xBle.startImmediateAdvertising(AD_TYPE_FLAGS, "06");

Without it, an Android device would not discover the board (source of information: section 4.3 of the v1.18.3 release notes. BTW, there is an error in this section, but quite easy to spot).

On the smartphone, start the Bluetooth Smart Discover application. It must be granted following permissions:

  • pair with Bluetooth devices
  • access Bluetooth settings
  • precise location (or approximate location)

Thanks to the application, you should be able to discover the board (named testPeriph_XXXX), connect to it and get the list of services it provides. The service created by the above sample code, the one with AD11CF40063F11E5BE3E0002A5D5C51B UUID, lets you read the temperature value generated by the board sensor, thanks to the read characteristic (BF3FBD80063F11E59E690002A5D5C501 UUID). And it lets you modify LED color, thanks to the write characteristic (BF3FBD80063F11E59E690002A5D5C503 UUID): send three bytes, e.g. 00FF00 to set green color at maximum intensity.

Your own Android app?

What now if you want to develop your own Android app, that you would be able to use with this Arduino code?

The easiest way it to start from an existing sample code, the one of the BluetoothLeGatt application. Do not forget to grant above three permissions to your application, otherwise it won’t work…