Adafruit Bluefruit LE UART Friend - Bluetooth Low Energy (BLE)
.
Adafruit Bluefruit LE UART Friend
The low cost Adafruit Bluefruit BLE board features an ARM Cortex M0 core running at 16MHz with 256KB flash memory and 32KB SRAM. Peak current draw is <20mA (radio actively transmitting/receiving) Transport is a UART @ 9600 baud with HW flow control (CTS+RTS). It has on-board 3.3V voltage regulation and a Bootloader with support for safe OTA firmware updates. A UART mode or easy AT command set can be used (note switch on board above). Free demo apps along with source code is available for iOS or Android phones or tablets. The app supports over the air firmware updates, UART text transfers, user control pads, and IMU or GPS data streaming from the phone or tablet.
Wiring for Hello World BLE UART Demo
Here are the pins to hookup for the mbed LPC1768
mbed | Adafruit BLE |
---|---|
gnd | gnd |
VU(5v) | Vin (3.3-16V) |
nc | RTS |
Gnd | CTS |
p27 (Serial RX) | TXO |
p28 (Serial TX) | RXI |
Hello World BLE UART demo
Here are the steps for the demo:
Mini slide switch on Adafruit board set to "UART" (not "CMD")
Download the free "Adafruit Bluefruit BLE" App for your iOS or Android phone or tablet from the Apple App or Google Play store. (First time only!)
Compile and Download the serial pass through program below to your mbed. It transfers data from the serial port connected to the BLE module to mbed's USB Virtual Com port. A PC terminal window connected to mbed's USB Virtual Com port then allows the user to see the data from the BLE module or send data from the PCs keyboard.
Import programSerialPassthrough_LPC1768
Serial pass through program - version for mbed LPC1768 pins 28,27 9600 baud
Reset the mbed to run the serial pass through program
Start a terminal application program on your PC at 9600 baud and connect to mbed's USB virtual com port
Red power/status LED should blink on Adafruit board
After installing, find and start the "Adafruit Bluefruit BLE" App on your phone
After scanning stops, Connect to "Adafruit Bluefruit BLE"
If prompted for a device firmware update, go ahead and update the firmware. This works over the air using the Bluetooth RF link.
Select "UART" mode on phone window that appears
When connected, blue LED on Adafruit board should turn on
Anything typed in the phone's UART text window, will be sent via bluetooth after hitting "send" and appear in the PCs terminal window as seen below.
Any characters typed in the PC's terminal window will appear in the phone's text window as seen below.
LED and Control Pad Demo
Once it is connecting and transfers text, try this demo. The program below decodes button messages from the number keys on the right side of the control pad. Hitting a button sends a text message like "!B11" (button 1 hit) or "!B20" (button 2 released). Sending both a hit and release code is common on keyboards and touch devices. It allows software to determine if a key is held down for a long period of time for features like automatic repeat, and determine exactly what to do whenever more than one key is held down at a time.
#include "mbed.h" BusOut myled(LED1,LED2,LED3,LED4); Serial blue(p28,p27); int main() { char bnum=0; while(1) { if (blue.getc()=='!') { if (blue.getc()=='B') { //button data bnum = blue.getc(); //button number if ((bnum>='1')&&(bnum<='4')) //is a number button 1..4 myled[bnum-'1']=blue.getc()-'0'; //turn on/off that num LED } } } }
Compile and download it to mbed and reset.
After connecting to the module, instead of "UART" select "Controller" then "Control Pad". Each number key will turn on one of the four built-in LEDs on the mbed LPC1768, and when the button is released it turns off the LED.
Once more keys need to be decoded for controlling something a little more complex such as a robot or game, a switch statement can be added. A template is provided in the code below.
#include "mbed.h" BusOut myled(LED1,LED2,LED3,LED4); Serial blue(p28,p27); int main() { char bnum=0; char bhit=0; while(1) { if (blue.getc()=='!') { if (blue.getc()=='B') { //button data packet bnum = blue.getc(); //button number bhit = blue.getc(); //1=hit, 0=release if (blue.getc()==char(~('!' + 'B' + bnum + bhit))) { //checksum OK? myled = bnum - '0'; //current button number will appear on LEDs switch (bnum) { case '1': //number button 1 if (bhit=='1') { //add hit code here } else { //add release code here } break; case '2': //number button 2 if (bhit=='1') { //add hit code here } else { //add release code here } break; case '3': //number button 3 if (bhit=='1') { //add hit code here } else { //add release code here } break; case '4': //number button 4 if (bhit=='1') { //add hit code here } else { //add release code here } break; case '5': //button 5 up arrow if (bhit=='1') { //add hit code here } else { //add release code here } break; case '6': //button 6 down arrow if (bhit=='1') { //add hit code here } else { //add release code here } break; case '7': //button 7 left arrow if (bhit=='1') { //add hit code here } else { //add release code here } break; case '8': //button 8 right arrow if (bhit=='1') { //add hit code here } else { //add release code here } break; default: break; } } } } } }
In this example, additional code was added to check the checksum byte sent at the end of each key event text string. In case of a checksum error, the command is ignored.
Using Serial Interrupts
Some applications may need to do additional tasks while waiting for a new button message. Getc() does not return until a new character is sent, so most of the processor time is spent waiting for new characters to arrive. Serial receive character interrupts can be used to parse each button message. The main program can then do other tasks and periodically check to see if a new message has been received.
The example code below shows how to process button messages using interrupts. Main checks for a new button message each time through the while loop. In this example, the while loop in main blinks led1 and button 4 controls led4. It is still possible to miss a button message, if main does not check for messages often enough.
#include "mbed.h" Serial Blue(p28,p27); DigitalOut myled(LED1); DigitalOut myled4(LED4); //global variables for main and interrupt routine volatile bool button_ready = 0; volatile int bnum = 0; volatile int bhit ; //state used to remember previous characters read in a button message enum statetype {start = 0, got_exclm, got_B, got_num, got_hit}; statetype state = start; //Interrupt routine to parse message with one new character per serial RX interrupt void parse_message() { switch (state) { case start: if (Blue.getc()=='!') state = got_exclm; else state = start; break; case got_exclm: if (Blue.getc() == 'B') state = got_B; else state = start; break; case got_B: bnum = Blue.getc(); state = got_num; break; case got_num: bhit = Blue.getc(); state = got_hit; break; case got_hit: if (Blue.getc() == char(~('!' + ' B' + bnum + bhit))) button_ready = 1; state = start; break; default: Blue.getc(); state = start; } } int main() { //attach interrupt function for each new Bluetooth serial character Blue.attach(&parse_message,Serial::RxIrq); while(1) { //check for a new button message ready if(button_ready && (bnum=='4')) { // button 4 changed myled4 = bhit - '0'; //turn led4 on/off button_ready = 0; //reset flag after reading button message } //do other tasks in main - interrupts will process button message characters myled = 1; wait(0.1); myled = 0; wait(0.1); } }
If the button action to be taken can be performed quickly with just a few lines of code it could be implemented directly in the interrupt routine instead of passing messages for main to check, but long execution times for interrupt routines need to be avoided since they can cause a multitude of other problems.
Color Picker RGB LED Demo
For this demo, select the color picker instead of the control pad
This demo code below reads in color packets from the app and sets the color of an RGB LED using three PWM pins. Select a color using the color wheel and hit send. The color wheel GUI is a bit different in the Android app. A Sparkfun RGB LED was used with three 100 ohm series resistors connected to PWM pins 21,22,23 (RGB) on the mbed LPC1768.
#include "mbed.h" BusOut myled(LED1,LED2,LED3,LED4); Serial bluemod(p28,p27); PwmOut red(p21); PwmOut green(p22); PwmOut blue(p23); int main() { char bred=0; char bgreen=0; char bblue=0; red = green = blue = 0; while(1) { if (bluemod.getc()=='!') { if (bluemod.getc()=='C') { //color data packet bred = bluemod.getc(); // RGB color values bgreen = bluemod.getc(); bblue = bluemod.getc(); if (bluemod.getc()==char(~('!' + 'C' + bred + bgreen + bblue))) { //checksum OK? red = bred/255.0; //send new color to RGB LED PWM outputs green = bgreen/255.0; blue = bblue/255.0; } } } } }
GPS and IMU Data Streaming
The app's controller GUI also has an option to stream IMU or GPS data from the phone. Click the Location "off" button to "on" to start streaming the GPS location values to mbed.
The float values for x,y,z are transferred as four bytes over the serial link. The four bytes need to be packed back into a 32-bit float, to get the original float value. The code below shows a way to do this and it prints out the GPS data from the phone to mbed's' USB virtual com port. This approach will also work for the IMU options.
#include "mbed.h" Serial bluemod(p28,p27); //C union can convert 4 chars to a float - puts them in same location in memory //trick to pack the 4 bytes from Bluetooth serial port back into a 32-bit float union f_or_char { float f; char c[4]; }; int main() { char bchecksum=0; char temp=0; union f_or_char x,y,z; while(1) { bchecksum=0; if (bluemod.getc()=='!') { if (bluemod.getc()=='L') { //GPS data packet for (int i=0; i<4; i++) { temp = bluemod.getc(); x.c[i] = temp; bchecksum = bchecksum + temp; } for (int i=0; i<4; i++) { temp = bluemod.getc(); y.c[i] = temp; bchecksum = bchecksum + temp; } for (int i=0; i<4; i++) { temp = bluemod.getc(); z.c[i] = temp; bchecksum = bchecksum + temp; } if (bluemod.getc()==char(~('!' + 'L' + bchecksum))) { //checksum OK? printf("Latitude = %f Longitude = %f Altitude = %f\n\r",x.f, y.f, z.f); } } } } }
If mbed starts dropping characters in packets in more complex applications, it might make sense to use the interrupt driven BufferSerial (instead of Serial) and/or make a receive serial character data thread in the RTOS. If an application is running using threads in the RTOS, don’t forget that getc() blocks until a new character arrives and this will use a lot of processor time. Prior to calling getc(), using while(!readable()) to test first for a new character available and a yield() or thread::wait() if not, will free up significant processor time for other threads.
Tilt Controller Demo
Here is a final demo using the phone or tablet as a tilt based controller. Download the demo code below, connect, select controller, and turn on Accelerometer data streaming. The code below reads accelerometer data packets. Using the X and Y acceleration values (range is -10.0 to 10.0), the code controls the four built in LEDs on the mbed LPC1768 using PWM (PWM can dim the LEDs with a 0.0 to 1.0 value).
#include "mbed.h" Serial bluemod(p28,p27); PwmOut left_led(LED1); PwmOut right_led(LED4); PwmOut forward_led(LED2); PwmOut reverse_led(LED3); //C union can convert 4 chars to a float - puts them in same location in memory //trick to pack the 4 bytes from Bluetooth serial port back into a 32-bit float union f_or_char { float f; char c[4]; }; int main() { char bchecksum=0; char temp=0; union f_or_char x,y,z; while(1) { bchecksum=0; if (bluemod.getc()=='!') { if (bluemod.getc()=='A') { //Accelerometer data packet for (int i=0; i<4; i++) { temp = bluemod.getc(); x.c[i] = temp; bchecksum = bchecksum + temp; } for (int i=0; i<4; i++) { temp = bluemod.getc(); y.c[i] = temp; bchecksum = bchecksum + temp; } for (int i=0; i<4; i++) { temp = bluemod.getc(); z.c[i] = temp; bchecksum = bchecksum + temp; } if (bluemod.getc()==char(~('!' + 'A' + bchecksum))) { //checksum OK? //printf("X = %f Y = %f Z = %f\n\r",x.f, y.f, z.f); right_led = left_led = forward_led = reverse_led = 0.0; if (x.f<=0.0) right_led = -x.f/10.0;//Scale to 0.0 to 1.0 for PWM so /10.0 else left_led = x.f/10.0; if (y.f<=0.0) forward_led = -y.f/10.0; else reverse_led = y.f/10.0; } } } } }
Hold the phone or tablet level. Note the accelerometer readings in the GUI. Most have a high Z axis reading when the device is horizontal (On my device Z was near 10.0 and X and Y should be low near zero). This is the gravity vector. If the device is not accelerating due to linear motion, the accelerometer detects the gravity vector.
When the phone or tablet is tilted, X or Y axis values increase and Z decreases (due to gravity vector). Using these values, the mbed code controls the brightness of the LEDs. LED1 indicates tilt left, LED4 is tilt right, LED2 is tilt forward, and LED3 is tilt reverse. The more the device is tilted, the brighter the corresponding tilt LED will become. If it is perfectly level, all LEDs are off.
Other OSes
A PC running Windows 10 with a Bluetooth 4.0 dongle can detect and pair with the device, but it appears additional work or firmware is needed to get the UART services working. Instructions can be found at Adafruit for Linux and OS X.
Command Mode
By placing the BLE UART Friend module in 'Command' mode (set the mode selection switch to CMD or setting the MODE pin to a high voltage) you can enter a variety of Hayes AT style commands to configure the device or retrieve basic information about the module or BLE connection. Commands include reducing RF power and turning off the MODE LED. There is documentation from Adafruit that explains the AT commands.
RPC Serial Using Bluetooth
The Remote Procedure Call (RPC) serial code examples could also be used to remotely control the mbed using the Bluetooth module, since the characters in commands wind up coming in on the serial port from the Bluetooth module. Interfacing-Using-RPC has more details. There are examples using both serial port polling and interrupt driven routines to handle remote procedure calls (RPC). An RPC tutorial is also available.
Using code in RTOS threads
When using getc() in an RTOS thread for the Bluetooth module, it seems to need the same mutex lock as any printfs (even on different serial port I/O hardware) or things can lock up. Printf() calls putc() and Scanf calls getc() as virtual functions so they are all related. Most of the functions in the C STDIO library are not reentrant (have global or static variables that need the same STDIO mutex lock). If you add the lock around getc(), don’t grab the lock until you first check if it is readable() or things could lock up until a new character shows up from the Bluetooth module. If you switch the Bluetooth device to use “RawSerial” instead of “Serial” this also seems to fix it. “RawSerial” does not use interrupt driven I/O on the serial port with buffering. The danger with “RawSerial” is that you could drop characters at high baud rates, if they are not read out fast enough. It likely is OK at just 9600 baud. A third alternative would be to use the I/O device hardware register pointers to write your own getc() routine to directly read the serial port hardware, but you would need to be careful about using other mbed APIs on the same serial port and not let it setup interrupt buffers for the serial port.
Please log in to post comments.