Saturday, August 30, 2014

RFduino and BLED112, Putting It All Together

This tutorial is a very simple example to get started with setting data on the RFduino and reading it via a BLED112 on a PC running windows.

The code for this example can be found on GitHub in the dhRFduino repository under digitalhack.

This tutorial includes a sketch to run on the RFduino that will set various data values to be read from the RFduino and a program for Windows that uses the BLED112 to connect to the RFduino and read back the data.  To set different values the RFduino sketch has to have comments changed and then be recompiled.  I wasn’t looking for elegance that will come later.  I just wanted something simple.

This tutorial makes use of the following:

  1. A Windows PC
  2. Code:: Blocks 12.11 w/ MINGW
  3. Bluegiga’s BGLib SDK
  4. The Arduino IDE version 1.5.7 BETA
  5. The RFduino RFD22102 with an RFD22121 USB Shield for RFduino or another way to code to the RFduino.  In this post I describe a method using a Sparkfun FTDI 3.3V board.
  6. The RFduino library for the Arduino IDE.  This library can be found by starting at http://www.rfduino.com/product/rfd22102-rfduino-dip/ and then scrolling halfway down the page to DOWNLOAD, than selecting DOWNLOAD and finally selecting Download RFduino Zip.

Prior to starting you should review these documents:

  1. RFduino Quick  Start Guide for step by step instructions on how to install the RFduino library in the Arduino IDE.
  2. My Bluegiga BLED112 Setup on Windows 7 blog post.
  3. My Bluegiga’s BLED112, BGLib and Code::Blocks blog post.
  4. My It Worked For Me – RFduino blog post for information on downloading code to the RFduino using a Sparkfun FTDI 3.3V board.  Reviewing this document is optional and is only needed if you are using method described in the post.

RFduino Sketch

Below is the code for the RFduino to test initial connectivity between the RFduino and the BLED112.  This code should be copied into the Arduino IDE, compiled and downloaded to the RFduino.

#include <RFduinoBLE.h>

// This function is defined in the RFduino BLE library so it gets
// called anytime a connect is completed to the RFduino.
void RFduinoBLE_onConnect(){
  Serial.println("Connect");
}

// This function is defined in the RFduino BLE library so it gets
// called any time the RFduino is disconnected from.
void RFduinoBLE_onDisconnect(){
  Serial.println("Disconnect"); 
}

void setup() {
  Serial.begin(9600);
  Serial.println("Starting...");
  // this is the data we want to appear in the advertisement
  RFduinoBLE.advertisementData = "test01";

  // start the BLE stack
  RFduinoBLE.begin();
}

// In loop we send either a byte, float of integer by uncommenting the appropriate line.
// As the code is setup now the RFduino will send an uppercase A when it is queried for data

void loop() {
  uint8_t temp = 'A';
  Serial.println("Setting value");
  RFduinoBLE.sendByte(temp);
  //RFduinoBLE.sendFloat(99.1);
  //RFduinoBLE.sendInt(32766);
  while (true) {
  }
}

Windows Program

Below is the code that is used in main.c.  To compile this code you will need to have the following files in the source directory and you will need to make sure they are all included in your Code::Blocks project definition.  This is most easily done using the process described in the Bluegiga’s BLED112, BGLib and Code::Blocks blog post.

  1. cmd_def.c
  2. commands.c – this file will need to be modified to comment out the stubs of the ble functions that are setup in main.c.  This is
    1. ble_evt_connection_status
    2. ble_evt_attclient_attribute_value
    3. ble_evt_connection_disconnected
    4. ble_rsp_gap_connect_direct
  3. apitypes.h – this file will need to be modified as described in the Bluegiga’s BLED112, BGLib and Code::Blocks blog post
  4. cmd_def.h – this file will need to be modified as described in the Bluegiga’s BLED112, BGLib and Code::Blocks blog post

In main.c there are two defines that will need to be updated. 

  1. BLEADDRESS which needs to be the address of your RFduino.
  2. COMPORT needs to be the COM port that is defined for your BLED112

If you uncomment the #define DEBUG the program will print out a number of status messages that should be useful for debugging.

Once you have the Code::Blocks project setup, and have made all the necessary changes you should build the project.

#include <stdio.h>
#include <windows.h>
#include "cmd_def.h"

//#define DEBUG
#define BLEADDRESS "d6:3e:7a:bd:b5:7d" // substitute your RFduinos address
#define COMPORT "COM8" // substitute the COM port for your BLED112
//#define COMPORT "\\\\.\\COM10" // use this notation for com ports > 9.

typedef enum {
  state_disconnected,
  state_connecting,
  state_connected,
  state_requesting_value,
  state_value_returned,
  state_finish,
} states;
states state = state_disconnected;

bd_addr btle_dev;
uint8 btle_connection;
volatile HANDLE comm_port_h;

void dump_packet(char *rxtx, int len0, unsigned char *data0, int len1, unsigned char *data1) {
  printf("%s Packet: ", rxtx);
  int i;
  for (i = 0; i < len0; i++) {
    printf("%02x ", data0[i]);
  }

  for (i = 0; i < len1; i++) {
    printf("%02x ", data1[i]);
  }
  printf("\n");
}

void change_state(states new_state) {
  state = new_state;
}

void btle_send(uint8 len1, uint8* data1, uint16 len2, uint8* data2) {
  DWORD bytes_sent;

#ifdef DEBUG
  dump_packet("TX", len1, data1, len2, data2);
#endif

  if(!WriteFile (comm_port_h, data1, len1, &bytes_sent, NULL)) {
    printf("ERROR: Writing data. %d\n",(int)GetLastError());
    exit(-1);
  }

  if(!WriteFile (comm_port_h, data2, len2, &bytes_sent, NULL)) {
    printf("ERROR: Writing data. %d\n",(int)GetLastError());
    exit(-1);
  }
}

int btle_read() {
  DWORD bytes_read;
  const struct ble_msg *btle_msg;
  struct ble_header btle_msg_hdr;
  unsigned char buf[128];

#ifdef DEBUG
  printf("above first read\n");
#endif

  if(!ReadFile(comm_port_h, (unsigned char*)&btle_msg_hdr, 4, &bytes_read, NULL)) {
    return GetLastError();
  }
  // Error if 4 bytes were not read.  This means a timeout occurred.
  if (bytes_read != 4) return -1;

  if(btle_msg_hdr.lolen) {

#ifdef DEBUG
    printf("above second read\n");
#endif

    if(!ReadFile(comm_port_h, buf, btle_msg_hdr.lolen, &bytes_read, NULL)) {
      return GetLastError();
    }
  }

#ifdef DEBUG
  dump_packet("RX", sizeof(btle_msg_hdr), (unsigned char *)&btle_msg_hdr, btle_msg_hdr.lolen, buf);
#endif

  btle_msg=ble_get_msg_hdr(btle_msg_hdr);
  if(!btle_msg) {
    printf("ERROR: Message not found:%d:%d\n",(int)btle_msg_hdr.cls,(int)btle_msg_hdr.command);
    return -1;
  }

#ifdef DEBUG
  printf("dispatching event / response\n");
#endif

  btle_msg->handler(buf);

#ifdef DEBUG
  printf("below dispatch\n");
#endif

  return 0;
}

void ble_evt_connection_status(const struct ble_msg_connection_status_evt_t *msg) {
  if (msg->flags &connection_connected) {
    change_state(state_connected);

#ifdef DEBUG
    printf("Connected\n");
#endif
  }
}

void ble_evt_attclient_attribute_value(const struct ble_msg_attclient_attribute_value_evt_t *msg) {
  union {
    float f;
    int i;
    uint8 b[4];
  } value;

  value.b[0] = msg->value.data[0];
  value.b[1] = msg->value.data[1];
  value.b[2] = msg->value.data[2];
  value.b[3] = msg->value.data[3];

  printf("----> value: (int) %d (float) %f (byte) %c\n", value.i, value.f, value.b[0]);

  if (state == state_requesting_value) change_state(state_value_returned);
}

void ble_evt_connection_disconnected(const struct ble_msg_connection_disconnected_evt_t *msg) {
  change_state(state_disconnected);

#ifdef DEBUG
  printf("Connection terminated\n");
#endif
}

void ble_rsp_gap_connect_direct(const struct ble_msg_gap_connect_direct_rsp_t *msg) {

#ifdef DEBUG
  printf("from ble_rsp_gap_connect_direct - result: %d, connection_handle: %d\n",
         msg->result, msg->connection_handle);
#endif

  btle_connection = msg->connection_handle;
}

int main(int argc, char *argv[]) {
  COMMTIMEOUTS cto;
  char *comm_port = COMPORT;
  unsigned int b[5];
  int n;

     n = sscanf(BLEADDRESS, "%X:%X:%X:%X:%X:%X", &b[5], &b[4], &b[3], &b[2], &b[1], &b[0]);
     if (n == 6 && (b[0] | b[1] | b[2] | b[3] | b[4] | b[5]) < 256) {
    for(n=0; n<6; n++)
           btle_dev.addr[n] = b[n];
     } else {
         printf("bad bluetooth address");
         return -1;
     }

  bglib_output = btle_send;

  comm_port_h = CreateFile(comm_port, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE,
                              NULL, OPEN_EXISTING, 0, NULL);


  if (comm_port_h == INVALID_HANDLE_VALUE) {
    printf("Error opening %s: %d\n",comm_port,(int)GetLastError());
    return -1;
  }

  // Reset dongle to get it into known state
  ble_cmd_system_reset(0);
  CloseHandle(comm_port_h);

  do {
    Sleep (500); // 0.5s
    comm_port_h = CreateFile(comm_port, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE,
                                NULL, OPEN_EXISTING, 0, NULL);
  } while (comm_port_h == INVALID_HANDLE_VALUE);

  // Set 10 second timeout for reads.
  cto.ReadIntervalTimeout = 0;
  cto.ReadTotalTimeoutMultiplier = 0;
  cto.ReadTotalTimeoutConstant = 10000;
  cto.WriteTotalTimeoutMultiplier = 0;
  cto.WriteTotalTimeoutConstant = 0;

  if (!SetCommTimeouts(comm_port_h, &cto)) {
    printf("Error setting port timeouts: %d\n",(int)GetLastError());
    return -1;
  }

#ifdef DEBUG
  printf("ReadIntervalTimeout: %ld, ReadTotalTimeoutConstant: %ld, ReadTotalTimeoutMultiplier %ld\n",
         cto.ReadIntervalTimeout, cto.ReadTotalTimeoutConstant, cto.ReadTotalTimeoutMultiplier);
#endif

  change_state(state_connecting);
  ble_cmd_gap_connect_direct(&btle_dev, gap_address_type_random, 40, 60, 100,0);
  while (state != state_connected) {
    if (btle_read() != 0) break;
  }

  change_state(state_requesting_value);
  ble_cmd_attclient_read_by_handle(btle_connection, 0x000e);
  while (state != state_value_returned) {
    if (btle_read() != 0) break;
  }

  ble_cmd_connection_disconnect(btle_connection);
  while (state != state_disconnected) {
    if (btle_read() != 0) break;
  }

  CloseHandle(comm_port_h);
  return 0;
}

This program is based on the Bluegiga demo programs. 

The program starts by attempting to open the designated COM port.  If the open is successful it issues a reset to the BLED112 device to get it into a known state and closes the COM port.  It then attempts to open the COM port.  This open will not succeed until the BLED112 completes resetting.  When the BLED112 resets it will disconnect and reconnect from the USB.

Next the program sets up a read timeout so that it will not lock up forever if the BLED112 stops sending data.  In this example that is unlikely to happen.

After the read timeout is setup the program instructs the BLED112 to connect to the RFduino.  When the connection completes the ble_evt_connection_status callback function is called.

After the connection is established the program instructs the BLED112 to read data from the RFduino based on the data handle..  When the data is read the ble_evt_attclient_attribute_value callback function is called which prints out the data.

Finally the program instructs the BLED112 to disconnect from the RFduino.

The ble_evt_attclient_attribute_value callback function is setup to print the data out in three of the data types that the RFduino can send: byte, float, and integer.  This allows you to see on the console the value set on the RFduino.

Getting It All To Work Together

The sequence of events that I use is to get the RFduino up and running with a Serial Monitor session going so I can see the status outputs.  Then I run the windows program which takes a couple of seconds to run and prints the value.

You can test different data types by switching the comments around in the RFduino sketch.  When you do this don’t forget to compile and download the modified code to the RFduino.

If everything is working the program should print the following line after about 6 seconds:

----> value: (int) 131481153 (float) 0.000000 (byte) A

If you change the RFduino sketch to send an integer or a float or a different character your output will obviously look slightly different.

Hope you find this helpful. My thanks to Bluegiga and RFduino because without their products none of this would be possible.

If you find something that doesn’t work for you or you have questions please post them as comments and I will get back to you as soon as I can.

2 comments:

James Cannan said...

Nice posts.
Any thoughts on how to increase the transfer speed between the Rfduino and the BLED112 computer code?

xenserver said...

Hello! One kind curiosity:
have you ever thought to directly use the nRF51822 chip alone?
Have you ever experimented in that direction?
Thank you
Robert