nRF UART In Detail

To better understand the BLE UART interface, lets take a look at the basic echo demo. This version is designed to make the BLE breakout be as effortless to use as Serial.

Behind the scenes, the library does much of the heavy lifting of managing the connection, sending and receiving data as well as buffering incoming data so you can grab it when the Arduino has time.

The following sketch should allow you to start bi-directional communication on BLE-enabled Android devices (4.3 or higher) or recent iOS devices. It waits for incoming data, and then echoes it back to the transmitting device.
Download: file
// This version uses the internal data queing so you can treat it like Serial (kinda)!

#include <SPI.h>
#include "Adafruit_BLE_UART.h"

// Connect CLK/MISO/MOSI to hardware SPI
// e.g. On UNO & compatible: CLK = 13, MISO = 12, MOSI = 11
#define ADAFRUITBLE_REQ 10
#define ADAFRUITBLE_RDY 2     // This should be an interrupt pin, on Uno thats #2 or #3
#define ADAFRUITBLE_RST 9

Adafruit_BLE_UART BTLEserial = Adafruit_BLE_UART(ADAFRUITBLE_REQ, ADAFRUITBLE_RDY, ADAFRUITBLE_RST);
/**************************************************************************/
/*!
    Configure the Arduino and start advertising with the radio
*/
/**************************************************************************/
void setup(void)
{ 
  Serial.begin(9600);
  Serial.println(F("Adafruit Bluefruit Low Energy nRF8001 Print echo demo"));

  BTLEserial.begin();
}

/**************************************************************************/
/*!
    Constantly checks for new events on the nRF8001
*/
/**************************************************************************/
aci_evt_opcode_t laststatus = ACI_EVT_DISCONNECTED;

void loop()
{
  // Tell the nRF8001 to do whatever it should be working on.
  BTLEserial.pollACI();
  
  // Ask what is our current status
  aci_evt_opcode_t status = BTLEserial.getState();
  // If the status changed....
  if (status != laststatus) {
    // print it out!
    if (status == ACI_EVT_DEVICE_STARTED) {
        Serial.println(F("* Advertising started"));
    }
    if (status == ACI_EVT_CONNECTED) {
        Serial.println(F("* Connected!"));
    }
    if (status == ACI_EVT_DISCONNECTED) {
        Serial.println(F("* Disconnected or advertising timed out"));
    }
    // OK set the last status change to this one
    laststatus = status;
  }
  
  if (status == ACI_EVT_CONNECTED) {
    // Lets see if there's any data for us!
    if (BTLEserial.available()) {
      Serial.print("* "); Serial.print(BTLEserial.available()); Serial.println(F(" bytes available from BTLE"));
    }
    // OK while we still have something to read, get a character and print it out
    while (BTLEserial.available()) {
      char c = BTLEserial.read();
      Serial.print(c);
    }
    
    // Next up, see if we have any data to get from the Serial console

    if (Serial.available()) {
      // Read a line from Serial
      Serial.setTimeout(100); // 100 millisecond timeout
      String s = Serial.readString();

      // We need to convert the line to bytes, no more than 20 at this time
      uint8_t sendbuffer[20];
      s.getBytes(sendbuffer, 20);
      char sendbuffersize = min(20, s.length());
      
      Serial.print(F("\n* Sending -> \"")); Serial.print((char *)sendbuffer); Serial.println("\"");
      
      // write the data
      BTLEserial.write(sendbuffer, sendbuffersize);
    }
  }
}

Initialization


Lets look at it section by section. Starting with initialization. You'll need to include the header files and define the pins used. Since we're using hardware SPI, the CLK/MOSI and MISO pins are fixed (see the hookup guide)

the RDY pin is the only pin that must be an interrupt pin. We'll use 2, most Arduino's can use 2 or 3.

Then create the Adafruit_BLE_UART object at the top.
Download: file
#include <SPI.h>
#include "Adafruit_BLE_UART.h"

// Connect CLK/MISO/MOSI to hardware SPI
// e.g. On UNO & compatible: CLK = 13, MISO = 12, MOSI = 11
#define ADAFRUITBLE_REQ 10
#define ADAFRUITBLE_RDY 2     // This should be an interrupt pin, on Uno thats #2 or #3
#define ADAFRUITBLE_RST 9

Adafruit_BLE_UART BTLEserial = Adafruit_BLE_UART(ADAFRUITBLE_REQ, ADAFRUITBLE_RDY, ADAFRUITBLE_RST);

Setup


Setup is easy, just remember to call begin(); in the setup procedure to begin talking to the nrf8001

Polling


During your working loop, you have to give some time to the nRF8001 and tell it to process data. So be sure to call
// Tell the nRF8001 to do whatever it should be working on.
BTLEserial.pollACI();
as often as possible - and if you're having issues where data rates seem slow, try speeding up your loop
It's important to constantly call pollACI if you want to efficiently handle data over BLE. Be sure to include this function at the top of your 'loop' function in your sketch.

Managing Status


BLE is very asynchronous, it can connect, disconnect, time out. Part of the niceness of BTLE compared to classic BT is that this is all much more stable. Reconnecting takes less than half a second instead of up to 20 seconds! Be sure to check in with the nRF8001 often to see if the see the state has changed. We suggest keeping a global variable for the last known status so you can see if its changed
aci_evt_opcode_t laststatus = ACI_EVT_DISCONNECTED;
and then calling getState() to query the latest state. If something's changed, you can notify the user:
Download: file
  // Ask what is our current status
  aci_evt_opcode_t status = BTLEserial.getState();
  // If the status changed....
  if (status != laststatus) {
    // print it out!
    if (status == ACI_EVT_DEVICE_STARTED) {
        Serial.println(F("* Advertising started"));
    }
    if (status == ACI_EVT_CONNECTED) {
        Serial.println(F("* Connected!"));
    }
    if (status == ACI_EVT_DISCONNECTED) {
        Serial.println(F("* Disconnected or advertising timed out"));
    }
    // OK set the last status change to this one
    laststatus = status;
  }
Valid events are:
  • ACI_EVT_DEVICE_STARTED: The device has started advertising, and can be detected by other devices in listening range
  • ACI_EVT_CONNECTED: A connection has been established with another devices (meaning that advertising will now stop)
  • ACI_EVT_DISCONNECTED: The connection with the external device was closed or timed out
By detecting the event type, we can perform an action like enabling an LED when we are connected, or no longer reading sensor data when we are disconnected, etc.

Reading data


If data is available, you can query it with available() which will return the number of bytes waiting. You can then read one byte at a time with read() just like you would with Serial

Writing data


The nRF8001 sends out packets of data, 20 bytes at time. Keep this in mind if you want to send a lot of data it will be packetized into chunks of 20. You can of course send less than 20 bytes.

Much like Serial you can use the .write and .print functions allow us to send data out to the connected device:

uint16_t write ( uint8_t singlebyte)

Writes a single byte to the connected device, and returns the number of bytes successfully written.

uint16_t write ( uint8_t * buffer, uint8_t len )

Writes len bytes from buffer to the connection device, and returns the number of bytes successfully written.

uint16_t print("text here")

Prints the supplied string to the connected device, and returns the number of bytes successfully written. This is simple a helper function that points to .write, but may be easier to work with since it follows the same naming conventions as the familiar Serial class on Arduino.

uint16_t println("text here")

Similar to the print function above, but appends the string with new line characters at the end of the string, similar to the difference between Serial.print and Serial.println on Arduino.

Try to keep the buffers and strings under 20 bytes. The library will split up large messages but often times the app on the other side wants to read the whole packet at once, and it can make your job a lot easier!
This guide was first published on Mar 20, 2014. It was last updated on Mar 20, 2014. This page (nRF UART In Detail) was last updated on May 26, 2019.