Adafruit nRF8001 BLE Callback Echo Example

This example shows how to use the Adafruit nRF8001 Bluetooth Low Energy breakout in callback mode. The sketch starts advertising, reports connection events over Serial, receives incoming BLE UART data, prints it, and immediately echoes the same data back to the sender.

The nice thing about this example is that almost all the logic is event-driven. Instead of constantly checking for incoming data in the main loop, the sketch uses callbacks for both BLE state changes and received data. That makes the code cleaner and easier to understand.

What this example does

This sketch turns the nRF8001 module into a simple BLE UART echo device.

In practice it does the following:

  • Starts the BLE radio and begins advertising
  • Prints connection status messages to the Serial Monitor
  • Waits for incoming data over the BLE UART service
  • Prints the received text and hex values
  • Sends the exact same data back to the connected device

So if you send text from a phone app or another compatible BLE UART client, the board will receive it and echo it back.

Libraries used

The example uses:

  • SPI.h
  • Adafruit_BLE_UART.h

SPI.h is needed because the nRF8001 breakout communicates with the microcontroller over SPI.

Adafruit_BLE_UART.h is the Adafruit library that handles the BLE UART functionality and ACI event handling for the nRF8001.

Pin definitions

The code defines three control pins:

#define ADAFRUITBLE_REQ 10
#define ADAFRUITBLE_RDY 2
#define ADAFRUITBLE_RST 9

These are used to communicate with the nRF8001 module:

  • REQ is the request line
  • RDY is the ready line
  • RST is the reset line

Then the sketch creates the BLE UART object:

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

This object is what the rest of the sketch uses to control the module.

Why this version is different

The comment near the top explains the main idea:

This version uses callbacks on the event and RX so there’s no data handling in the main loop.

That is the key point of the whole example.

Instead of writing all BLE handling directly inside loop(), the sketch registers two callback functions:

  • one for BLE events
  • one for received data

That makes the main loop extremely small and keeps the real logic in dedicated functions.

The ACI event callback

This function handles BLE state changes:

void aciCallback(aci_evt_opcode_t event)

It is called automatically whenever important ACI events happen.

Inside the switch, the sketch handles three main cases:

  • ACI_EVT_DEVICE_STARTED
  • ACI_EVT_CONNECTED
  • ACI_EVT_DISCONNECTED

ACI_EVT_DEVICE_STARTED

When the device starts up, the sketch prints:

Serial.println(F("Advertising started"));

This tells you the module is alive and has started advertising so other BLE devices can discover it.

ACI_EVT_CONNECTED

When a BLE connection is made, it prints:

Serial.println(F("Connected!"));

That confirms a phone, tablet, or other central device has connected.

ACI_EVT_DISCONNECTED

When the connection ends, or advertising times out, it prints:

Serial.println(F("Disconnected or advertising timed out"));

This is useful because it tells you the device is no longer actively connected.

The RX callback

This function runs whenever data arrives over BLE:

void rxCallback(uint8_t *buffer, uint8_t len)

It receives:

  • a pointer to the incoming data buffer
  • the number of bytes received

Printing the incoming data

First, the sketch prints how many bytes arrived:

Serial.print(F("Received "));
Serial.print(len);
Serial.print(F(" bytes: "));

Then it loops through the buffer and prints each byte as a character:

for(int i=0; i<len; i++)
Serial.print((char)buffer[i]);

So if the remote device sends Hello, the Serial Monitor will show that text.

Printing the hex values

After that, the code prints the same bytes again in hexadecimal form:

for(int i=0; i<len; i++)
{
Serial.print(" 0x");
Serial.print((char)buffer[i], HEX);
}

That is useful for debugging because sometimes incoming data may include non-printable characters or raw binary values.

Echoing the data back

At the end of the callback, the sketch sends the same bytes back:

uart.write(buffer, len);

That is why this is called an echo demo. Whatever the remote device sends gets transmitted straight back.

What happens in setup()

The setup() function prepares Serial output and starts the BLE module.

Serial initialization

Serial.begin(9600);
while(!Serial);

The while(!Serial); line is there for boards like the Leonardo or Micro, where the sketch may wait for the USB Serial connection to become ready before continuing.

Then the sketch prints a startup message:

Serial.println(F("Adafruit Bluefruit Low Energy nRF8001 Callback Echo demo"));

Registering the callbacks

Next, the sketch attaches the two callback functions:

uart.setRXcallback(rxCallback);
uart.setACIcallback(aciCallback);

This tells the library which functions to call when data arrives or when BLE events occur.

Optional device name

There is also this commented line:

// uart.setDeviceName("NEWNAME"); /* 7 characters max! */

That means you can change the BLE device name, but the name length is limited.

Starting BLE

Finally:

uart.begin();

This initializes the module and starts the BLE process.

What happens in loop()

The loop() function is only one line:

uart.pollACI();

This is important. Even though the sketch uses callbacks, it still needs to continuously poll the ACI interface so the library can process events and trigger those callbacks.

So the loop is simple, but it is still essential.

What you should see in Serial Monitor

When everything is working, the Serial Monitor will show messages like:

  • Adafruit Bluefruit Low Energy nRF8001 Callback Echo demo
  • Advertising started
  • Connected!
  • Received 5 bytes: Hello [ 0x48 0x65 0x6C 0x6C 0x6F ]
  • Disconnected or advertising timed out

That makes this example very good for basic BLE troubleshooting.

Why this example is useful

This sketch is a good learning example because it demonstrates several important ideas at once:

  • BLE advertising
  • BLE connection events
  • receiving UART-style BLE data
  • using callbacks instead of handling everything in the main loop
  • echoing data back for quick testing

If you are trying to understand how event-driven BLE code works, this example is much cleaner than a sketch that mixes everything together in one big loop.

Important practical notes

A few things are worth keeping in mind:

  • This is an echo demo, not a full application protocol
  • The sketch depends on regular uart.pollACI() calls in the loop
  • Incoming data is printed both as text and as hex for easier debugging
  • The callback approach keeps the structure clean and scalable
  • The optional device name has a short length limit

Final thoughts

The Adafruit nRF8001 callback echo example is a simple but very effective demo. It shows how to react to BLE events properly, how to receive data without cluttering the main loop, and how to send a response back immediately.

For beginners, it is a nice introduction to BLE UART concepts. For more advanced users, it is a solid reminder that callback-based code is often much easier to maintain than constantly checking flags and buffers manually.

Share your love

Leave a Reply

Your email address will not be published. Required fields are marked *