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.
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.
// 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.
#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.as often as possible - and if you're having issues where data rates seem slow, try speeding up your loop
BTLEserial.pollACI();
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:
// 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
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!
Page last edited March 20, 2014
Text editor powered by tinymce.