If you bought the breakout board linked in the previous post from Jeff Rowberg, congrats! It is already pre-flashed with the gatt.xml, hardware.xml and everything you'll need to get it work immediately with the Arduino sketch to follow. If you didn't, refer to this to learn how to flash your BLE112.
Now we're ready to begin, awesome. You need only 4 wires to wire the BLE112:
Power
Ground
RX (Receiving pin)
TX (Transmitting pin)
The RX and TX pins communicate with the Arduino via UART - the Arduino's serial. Problem is, the Arduino's serial is hardwired to pins 0 and 1 for the Arduino Uno. Therefore, you cannot simultaneously use the hardware serial on the Arduino and receive debug messages via the software serial on the Arduino's serial monitor. To get around this, you may use a software serial library to mimic a UART interface between the BLE112 and Arduino Uno.
If you go the software serial route, I strongly recommend the AltSoftSerial library. The default Software serial library SoftwareSerial is quite frankly poor. It is unstable,and you cannot simultaneously read and write which is something you may encounter; if you're subscribed to notifications and you want to write to a characteristic for example.
The AltSoftSerial library supports simultaneous reading and writing, but it has 1 major caveat: for the Uno it must be used with pins 3 and 4, since it makes use of the 16 bit timer present in the ATMEGA 328 for its function. A small price to pay for fantastic quality in my opinion.
Now let's look at some Arduino code. The following code lets you read from characteristics, and lets you subscribe to notifications from those that support it. I haven't written it to support writing or indications, but if you browse the API guide, it should be fairly easy to implement. The code has a lot of comments to help clarify things.
// Accelerometer demo sketch for TinyDuino // Based onBluegiga BGLib Arduino interface library slave device stub sketch // and accelerometer demo from TinyCitcuits // 2013-06-30 by Ken Burns, TinyCircuits http://Tiny-Circuits.com // 2014-02-12 by Jeff Rowberg <[email protected]> // 2014-07-13 modified by Adetunji Dahunsi <tunjid.com> // Updates should (hopefully) always be available at https://github.com/jrowberg/bglib // // Changelog: // 2014-06-13 - Initial release /\* ============================================ !!!!!!!!!!!!!!!!! !!! IMPORTANT !!! !!!!!!!!!!!!!!!!! THIS SCRIPT WILL NOT COMMUNICATE PROPERLY IF YOU DO NOT ENSURE ONE OF THE FOLLOWING IS TRUE: 1\. You enable the <wakeup\_pin> functionality in your firmware 2\. You COMMENT OUT two lines below which depend on wake-up funcitonality to work properly (they will BLOCK otherwise): ble112.onBeforeTXCommand = onBeforeTXCommand; ble112.onTXCommandComplete = onTXCommandComplete; /\* ============================================ BGLib Arduino interface library code is placed under the MIT license Copyright (c) 2014 Jeff Rowberg Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. =============================================== \*/ //#include <SoftwareSerial.h> // software serial library for input and output to the serial mnitor. #include <Wire.h> #include <Math.h> #include "BGLib.h" // BGLib C library for BGAPI communication. #include <AltSoftSerial.h> // software serial library for input and output to the serial mnitor. //#include <SimpleTimer.h> // I experienced increased overhead using the timer library, decided to ditch it. You may fare better. Will be trying it again soon. // uncomment the following line for debug serial output #define DEBUG int x = 0; boolean notifier = false; // variable to manage notification settings uint8\_t A\[6\] = {1, 2, 3, 4, 5, 6}; // integer array to carry accelerometer values int count = 0; //Initially used to set notification frequency. //SimpleTimer timer; // ================================================================ // BLE STATE TRACKING (UNIVERSAL TO JUST ABOUT ANY BLE PROJECT) // ================================================================ // BLE state machine definitions #define BLE\_STATE\_STANDBY 0 #define BLE\_STATE\_SCANNING 1 #define BLE\_STATE\_ADVERTISING 2 #define BLE\_STATE\_CONNECTING 3 #define BLE\_STATE\_CONNECTED\_MASTER 4 #define BLE\_STATE\_CONNECTED\_SLAVE 5 // BLE state/link status tracker uint8\_t ble\_state = BLE\_STATE\_STANDBY; uint8\_t ble\_encrypted = 0; // 0 = not encrypted, otherwise = encrypted uint8\_t ble\_bonding = 0xFF; // 0xFF = no bonding, otherwise = bonding handle // ================================================================ // HARDWARE CONNECTIONS AND GATT STRUCTURE SETUP // ================================================================ // NOTE: this assumes you are using one of the following firmwares: // - BGLib\_U1A1P\_38400\_noflow \*\* I'm using this firmware. // - BGLib\_U1A1P\_38400\_noflow\_wake16 // - BGLib\_U1A1P\_38400\_noflow\_wake16\_hwake15 // If not, then you may need to change the pin assignments and/or // GATT handles to match your firmware. #define LED\_PIN 13 // Arduino Uno LED pin #define GATT\_HANDLE\_C\_RX\_DATA 17 // 0x11, supports "write" operation #define GATT\_HANDLE\_C\_TX\_DATA 20 // 0x14, supports "read" and "indicate" operations //#define BLE\_WAKEUP\_PIN 5 // BLE Wake up pin // use SoftwareSerial on pins D3/D4 for RX/TX (Arduino side) AltSoftSerial bleSerialPort(8, 9); // Note the use of AltSoftware Serial. // Took 3 weeks of debugging to identify the non simultaneous SoftwareSerial as terrible for BLE. // create BGLib object: // - use AltSoftSerial for for module comms // - use nothing for passthrough comms (0 = null pointer) // - enable packet mode on API protocol since flow control is unavailable BGLib ble112((HardwareSerial \*)&bleSerialPort, 0, 1); #define BGAPI\_GET\_RESPONSE(v, dType) dType \*v = (dType \*)ble112.getLastRXPayload() // ================================================================ // ARDUINO APPLICATION SETUP AND LOOP FUNCTIONS // ================================================================ // initialization sequence void setup() { //Initialize accelerometer Wire.begin(); Serial.begin(38400); // initialize status LED pinMode(LED\_PIN, OUTPUT); digitalWrite(LED\_PIN, LOW); // set up internal status handlers (these are technically optional) ble112.onBusy = onBusy; ble112.onIdle = onIdle; ble112.onTimeout = onTimeout; // ONLY enable these if you are using the <wakeup\_pin> parameter in your firmware's hardware.xml file // BLE module must be woken up before sending any UART data //ble112.onBeforeTXCommand = onBeforeTXCommand; //ble112.onTXCommandComplete = onTXCommandComplete; // set up BGLib event handlers ble112.ble\_evt\_system\_boot = my\_ble\_evt\_system\_boot; ble112.ble\_evt\_connection\_status = my\_ble\_evt\_connection\_status; ble112.ble\_evt\_connection\_disconnected = my\_ble\_evt\_connection\_disconnect; ble112.ble\_evt\_attributes\_value = my\_ble\_evt\_attributes\_value; ble112.ble\_evt\_attclient\_indicated = my\_ble\_evt\_attclient\_indicated; ble112.ble\_evt\_attributes\_status = my\_ble\_evt\_attributes\_status; // open Arduino USB serial (and wait, if we're using Leonardo) // use 38400 since it works at 8MHz as well as 16MHz Serial.begin(38400); while (!Serial); // open BLE software serial port bleSerialPort.begin(38400); my\_ble\_evt\_system\_boot( NULL); } // ================================================================ // MAIN APPLICATION LOOP // ================================================================ void loop() { // keep polling for new data from BLE count++; ble112.checkActivity(); // check for input from the user, allows you communicate to the // BLE module via the serial monitor for debugging if (Serial.available()) { uint8\_t ch = Serial.read(); uint8\_t status; if (ch == '0') { // Reset BLE112 module Serial.println("-->\\tsystem\_reset: { boot\_in\_dfu: 0 }"); ble112.ble\_cmd\_system\_reset(0); while ((status = ble112.checkActivity(1000))); // system\_reset doesn't have a response, but this BGLib // implementation allows the system\_boot event specially to // set the "busy" flag to false for this particular case } } // Check if GATT Client (Smartphone) is subscribed to notifications. if (notifier == true) { // Simple way of changing frequency of notifications. see documentation on WuMRC Github or // tunji.com/blog for more details on this. if (count > 150) { x = x + 1; if(x < 180) { A\[0\] = (100 \* sin((x\*3.14)/180)); A\[1\] = (100 \* cos((x\*3.14)/180)); A\[2\] = (0); A\[3\] = fabs((x \* 0.66)); } else{ x = -180; } //Write notification to characteristic on ble112. Causes notification to be sent. ble112.ble\_cmd\_attributes\_write(GATT\_HANDLE\_C\_TX\_DATA, 0, 6 , A); // Reset count to zero count = 0; Serial.println(millis()); } } else { // Do zilch, zip, nada, nothing if notifications are not enabled. } // blink Arduino LED based on state: // - solid = STANDBY // - 1 pulse per second = ADVERTISING // - 2 pulses per second = CONNECTED\_SLAVE // - 3 pulses per second = CONNECTED\_SLAVE with encryption uint16\_t slice = millis() % 1000; if (ble\_state == BLE\_STATE\_STANDBY) { digitalWrite(LED\_PIN, HIGH); } if (ble\_state == BLE\_STATE\_ADVERTISING) { digitalWrite(LED\_PIN, slice < 100); } if (ble\_state == BLE\_STATE\_CONNECTED\_SLAVE) { if (!ble\_encrypted) { digitalWrite(LED\_PIN, slice < 100 || (slice > 200 && slice < 300)); } else { digitalWrite(LED\_PIN, slice < 100 || (slice > 200 && slice < 300) || (slice > 400 && slice < 500)); } } } // ================================================================ // INTERNAL BGLIB CLASS CALLBACK FUNCTIONS // ================================================================ // called when the module begins sending a command void onBusy() { // turn LED on when we're busy //digitalWrite(LED\_PIN, HIGH); } // called when the module receives a complete response or "system\_boot" event void onIdle() { // turn LED off when we're no longer busy // digitalWrite(LED\_PIN, LOW); } // called when the parser does not read the expected response in the specified time limit void onTimeout() { // reset module (might be a bit drastic for a timeout condition though) Serial.println("Timed out."); } // ================================================================ // APPLICATION EVENT HANDLER FUNCTIONS // ================================================================ void my\_ble\_evt\_system\_boot(const ble\_msg\_system\_boot\_evt\_t \*msg) { #ifdef DEBUG Serial.print("###\\tsystem\_boot: { "); Serial.print("major: "); Serial.print(msg -> major, HEX); Serial.print(", minor: "); Serial.print(msg -> minor, HEX); Serial.print(", patch: "); Serial.print(msg -> patch, HEX); Serial.print(", build: "); Serial.print(msg -> build, HEX); Serial.print(", ll\_version: "); Serial.print(msg -> ll\_version, HEX); Serial.print(", protocol\_version: "); Serial.print(msg -> protocol\_version, HEX); Serial.print(", hw: "); Serial.print(msg -> hw, HEX); Serial.println(" }"); #endif // system boot means module is in standby state //ble\_state = BLE\_STATE\_STANDBY; // ^^^ skip above since we're going right back into advertising below // set advertisement interval to 200-300ms, use all advertisement channels // (note min/max parameters are in units of 625 uSec) ble112.ble\_cmd\_gap\_set\_adv\_parameters(320, 480, 7); while (ble112.checkActivity(1000)); // USE THE FOLLOWING TO LET THE BLE STACK HANDLE YOUR ADVERTISEMENT PACKETS // ======================================================================== // start advertising general discoverable / undirected connectable //ble112.ble\_cmd\_gap\_set\_mode(BGLIB\_GAP\_GENERAL\_DISCOVERABLE, BGLIB\_GAP\_UNDIRECTED\_CONNECTABLE); //while (ble112.checkActivity(1000)); // USE THE FOLLOWING TO HANDLE YOUR OWN CUSTOM ADVERTISEMENT PACKETS // ================================================================= // build custom advertisement data // default BLE stack value: 0201061107e4ba94c3c9b7cdb09b487a438ae55a19 uint8 adv\_data\[\] = { 0x02, // field length BGLIB\_GAP\_AD\_TYPE\_FLAGS, // field type (0x01) 0x06, // data (0x02 | 0x04 = 0x06, general discoverable + BLE only, no BR+EDR) 0x11, // field length BGLIB\_GAP\_AD\_TYPE\_SERVICES\_128BIT\_ALL, // field type (0x07) 0xe4, 0xba, 0x94, 0xc3, 0xc9, 0xb7, 0xcd, 0xb0, 0x9b, 0x48, 0x7a, 0x43, 0x8a, 0xe5, 0x5a, 0x19 }; // set custom advertisement data ble112.ble\_cmd\_gap\_set\_adv\_data(0, 0x15, adv\_data); while (ble112.checkActivity(1000)); // build custom scan response data (i.e. the Device Name value) // default BLE stack value: 140942474c69622055314131502033382e344e4657 uint8 sr\_data\[\] = { 0x14, // field length BGLIB\_GAP\_AD\_TYPE\_LOCALNAME\_COMPLETE, // field type 'C', 'h', 'i', 'p', ' ', 'D', 'e', 'b', 'u', 'g',' ', '0', '0', ':', '0', '0', ':', '0', '0' }; // get BLE MAC address ble112.ble\_cmd\_system\_address\_get(); while (ble112.checkActivity(1000)); BGAPI\_GET\_RESPONSE(r0, ble\_msg\_system\_address\_get\_rsp\_t); // assign last three bytes of MAC address to ad packet friendly name (instead of 00:00:00 above) sr\_data\[13\] = (r0 -> address.addr\[2\] / 0x10) + 48 + ((r0 -> address.addr\[2\] / 0x10) / 10 \* 7); // MAC byte 4 10's digit sr\_data\[14\] = (r0 -> address.addr\[2\] & 0xF) + 48 + ((r0 -> address.addr\[2\] & 0xF ) / 10 \* 7); // MAC byte 4 1's digit sr\_data\[16\] = (r0 -> address.addr\[1\] / 0x10) + 48 + ((r0 -> address.addr\[1\] / 0x10) / 10 \* 7); // MAC byte 5 10's digit sr\_data\[17\] = (r0 -> address.addr\[1\] & 0xF) + 48 + ((r0 -> address.addr\[1\] & 0xF ) / 10 \* 7); // MAC byte 5 1's digit sr\_data\[19\] = (r0 -> address.addr\[0\] / 0x10) + 48 + ((r0 -> address.addr\[0\] / 0x10) / 10 \* 7); // MAC byte 6 10's digit sr\_data\[20\] = (r0 -> address.addr\[0\] & 0xF) + 48 + ((r0 -> address.addr\[0\] & 0xF ) / 10 \* 7); // MAC byte 6 1's digit // set custom scan response data (i.e. the Device Name value) ble112.ble\_cmd\_gap\_set\_adv\_data(1, 0x15, sr\_data); while (ble112.checkActivity(1000)); // put module into discoverable/connectable mode (with user-defined advertisement data) ble112.ble\_cmd\_gap\_set\_mode(BGLIB\_GAP\_USER\_DATA, BGLIB\_GAP\_UNDIRECTED\_CONNECTABLE); while (ble112.checkActivity(1000)); // set state to ADVERTISING ble\_state = BLE\_STATE\_ADVERTISING; } void my\_ble\_evt\_connection\_status(const ble\_msg\_connection\_status\_evt\_t \*msg) { #ifdef DEBUG Serial.print("###\\tconnection\_status: { "); Serial.print("connection: "); Serial.print(msg -> connection, HEX); Serial.print(", flags: "); Serial.print(msg -> flags, HEX); Serial.print(", address: "); // this is a "bd\_addr" data type, which is a 6-byte uint8\_t array for (uint8\_t i = 0; i < 6; i++) { if (msg -> address.addr\[i\] < 16) Serial.write('0'); Serial.print(msg -> address.addr\[i\], HEX); } Serial.print(", address\_type: "); Serial.print(msg -> address\_type, HEX); Serial.print(", conn\_interval: "); Serial.print(msg -> conn\_interval, HEX); Serial.print(", timeout: "); Serial.print(msg -> timeout, HEX); Serial.print(", latency: "); Serial.print(msg -> latency, HEX); Serial.print(", bonding: "); Serial.print(msg -> bonding, HEX); Serial.println(" }"); #endif // "flags" bit description: // - bit 0: connection\_connected // Indicates the connection exists to a remote device. // - bit 1: connection\_encrypted // Indicates the connection is encrypted. // - bit 2: connection\_completed // Indicates that a new connection has been created. // - bit 3; connection\_parameters\_change // Indicates that connection parameters have changed, and is set // when parameters change due to a link layer operation. // check for new connection established if ((msg -> flags & 0x05) == 0x05) { // track state change based on last known state, since we can connect two ways if (ble\_state == BLE\_STATE\_ADVERTISING) { ble\_state = BLE\_STATE\_CONNECTED\_SLAVE; } else { ble\_state = BLE\_STATE\_CONNECTED\_MASTER; } } // update "encrypted" status ble\_encrypted = msg -> flags & 0x02; // update "bonded" status ble\_bonding = msg -> bonding; } void my\_ble\_evt\_connection\_disconnect(const struct ble\_msg\_connection\_disconnected\_evt\_t \*msg) { #ifdef DEBUG Serial.print("###\\tconnection\_disconnect: { "); Serial.print("connection: "); Serial.print(msg -> connection, HEX); Serial.print(", reason: "); Serial.print(msg -> reason, HEX); Serial.println(" }"); #endif // set state to DISCONNECTED //ble\_state = BLE\_STATE\_DISCONNECTED; // ^^^ skip above since we're going right back into advertising below // after disconnection, resume advertising as discoverable/connectable //ble112.ble\_cmd\_gap\_set\_mode(BGLIB\_GAP\_GENERAL\_DISCOVERABLE, BGLIB\_GAP\_UNDIRECTED\_CONNECTABLE); //while (ble112.checkActivity(1000)); // after disconnection, resume advertising as discoverable/connectable (with user-defined advertisement data) ble112.ble\_cmd\_gap\_set\_mode(BGLIB\_GAP\_USER\_DATA, BGLIB\_GAP\_UNDIRECTED\_CONNECTABLE); while (ble112.checkActivity(1000)); // set state to ADVERTISING ble\_state = BLE\_STATE\_ADVERTISING; // clear "encrypted" and "bonding" info ble\_encrypted = 0; ble\_bonding = 0xFF; } void my\_ble\_evt\_attributes\_value(const struct ble\_msg\_attributes\_value\_evt\_t \*msg) { #ifdef DEBUG Serial.print("###\\tattributes\_value: { "); Serial.print("connection: "); Serial.print(msg -> connection, HEX); Serial.print(", reason: "); Serial.print(msg -> reason, HEX); Serial.print(", handle: "); Serial.print(msg -> handle, HEX); Serial.print(", offset: "); Serial.print(msg -> offset, HEX); Serial.print(", value\_len: "); Serial.print(msg -> value.len, HEX); Serial.print(", value\_data: "); // this is a "uint8array" data type, which is a length byte and a uint8\_t\* pointer for (uint8\_t i = 0; i < msg -> value.len; i++) { if (msg -> value.data\[i\] < 16) Serial.write('0'); Serial.print(msg -> value.data\[i\], HEX); } Serial.println(" }"); #endif // check for data written to "c\_rx\_data" handle if (msg -> handle == GATT\_HANDLE\_C\_RX\_DATA && msg -> value.len > 0) { // set ping 8, 9, and 10 to three lower-most bits of first byte of RX data // (nice for controlling RGB LED or something) digitalWrite(8, msg -> value.data\[0\] & 0x01); digitalWrite(9, msg -> value.data\[0\] & 0x02); digitalWrite(10, msg -> value.data\[0\] & 0x04); } } void my\_ble\_evt\_attclient\_indicated(const struct ble\_msg\_attclient\_indicated\_evt\_t \*msg) { #ifdef DEBUG Serial.print("###\\tattclient\_indicate: { "); Serial.print("Indication received."); Serial.println(" }"); #endif } void my\_ble\_evt\_attributes\_status (const struct ble\_msg\_attributes\_status\_evt\_t \*msg) { #ifdef DEBUG Serial.print("###\\tattributes\_status: { "); Serial.print("nSubscription changed"); Serial.print(", flags: "); Serial.print(msg -> flags, HEX); Serial.println(" }"); #endif if (msg -> flags == 1) { notifier = true; } else { notifier = false; } }
I'll like to highlight a couple of things. In this debug sketch, the unsigned integer array (Uint_*t) has a size of six, although I only set 3 values. This is because in my actual project I needed to carry 4 values, where the 4th value was a double that had 3 decimal places. To carry it I wrote a function that returned the nth digit of the number, split it up into 3 integers and sent them in array positions 4, 5 and 6. The C function is shown below for your edification:
long getNthDigit(long number, int base, int n) { long answer = 0; answer = (long) (number / pow(base, n - 1)); answer = answer % base; return answer; }
To put the numbers back together after getting the nth digit simply involved multiplying the first number by 10 and then adding the unit digit to it to concatenate them. To modify the values of an array in C, you use pointers and point it as a reference. Code below:
void changeVal(long val, uint8_t *values) { values[3] = ((getNthDigit(val, 10, 6) * 10) + getNthDigit(val, 10, 5)); values[4] = ((getNthDigit(val, 10, 4) * 10) + getNthDigit(val, 10, 3)); values[5] = ((getNthDigit(val, 10, 2) * 10) + getNthDigit(val, 10, 1)); }
There you go! You have the code for your own Arduino controlled GATT Server!