Category: Control

Storing configuration options on the RFduino.

One of the common requirements for an IoT device is to be able to make configuration changes to the device without having to modify its source code.   Some of the changes we might make could be changing the device name, advertising data or interval, or perhaps some application specific startup options.  Any kind of change like this will require some kind of non-volatile storage to preserve the changes between power cycles.  On the RFduino we have the ability to store changes in an unused flash page (assuming our code doesn’t fill up the 128k we are allocated).

Lets get started. Here is the code. See the included comments for more information.

/*
Based on RFduino example FlashStructue.ino
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
*/
#include <RFduinoBLE.h>


//For flash access 
//Notice that the page is 251? User space is 124 to 251, inclusive. 
//This gives us our 128k for sketches. If you look at the RFduino 
//flash examples, you can see how to use the first unused page. 
//I like using the last page in user space, however, as I know it will not change.
//Loading the sketch will not overwrite our storage. 
#define FLASH_STORAGE 251 //Use the last page 
// double level of indirection required to get gcc
// to apply the stringizing operator correctly
#define  str(x)   xstr(x)
#define  xstr(x)  #x
//This is the data structure we will use. 
//Since the max transfer is <20 bytes over the radio, 
//the lazy way is to make all the arrays 20 bytes long. 
struct data_t
{
 char name[20];
 char advertisement[20];
 int interval;
 int update_delay;
 char config[20];
 char valid[6];

};

//A method to display the data structure. Force is used to 
//have the in_memory config display its contents. 
void dump_data(struct data_t *p, bool force = false)
{

 if (String(p->valid) == "valid" || force)
 {
        Serial.print("Name = ");
        Serial.println(p->name);
        Serial.print("Advertisement  = ");
        Serial.println(p->advertisement);
        Serial.print("Interval  = ");
        Serial.println(p->interval);
        Serial.print("Update Delay  = ");
        Serial.println(p->update_delay);
        Serial.print("Config  = ");
        Serial.println(p->config);
        if (force){
            Serial.print("Valid = "); 
            Serial.println(p->valid); 
        }
    }
    else {
        Serial.println("No valid data in flash");
    }

}




//This is a pointer to the data stored in flash. This is read only.
data_t *in_flash = (data_t*)ADDRESS_OF_PAGE(FLASH_STORAGE);
//This is our config struct. We will use this to store our config options in memory for normal operations. 
//When writing, the contents of this struct will overwrite the in_flash data. 
data_t config = { { "Probe" }, { "Probe_adv" }, 500, 1, { "Sample config data" }, { "" } }; //default data

void setup()   {
    Serial.begin(9600);

    //If we have config data in memory, use it instead of default.
    //This will be false initially, or when the flash page is erased. 
    if (String(in_flash->valid) == "valid")
    {
        //Since the struct in flash contains char arrays we need
        //to do a quick array copy to move the in_flash config to 
        //our config struct. 
        //This could be probably done better. 
        for (int i = 0; i < 20; i++)         {             
              config.name[i] = in_flash->name[i];
        }

        for (int i = 0; i < 20; i++)         {             
              config.advertisement[i] = in_flash->advertisement[i];
        }

        for (int i = 0; i < 20; i++)         {             
              config.config[i] = in_flash->config[i];
        }

        for (int i = 0; i < 6; i++)         {             
              config.valid[i] = in_flash->valid[i];
        }


        config.update_delay = in_flash->update_delay;
        config.interval = in_flash->interval;

    }


    //RFDuino Specific. 
    // this is the data we want to appear in the advertisement
    // (if the deviceName and advertisementData are too long to fix into the 31 byte
    // ble advertisement packet, then the advertisementData is truncated first down to
    // a single byte, then it will truncate the deviceName)
    RFduinoBLE.advertisementData = config.advertisement;
    RFduinoBLE.advertisementInterval = config.interval;
    RFduinoBLE.deviceName = config.name;

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

}

void loop() {
//Nothing going on here. 
}


//We will use this built in event to handle our received data.
//This is a way to accept commands, but not the only way. 
//This will let anyone that connects change options. 
void RFduinoBLE_onReceive(char *data, int len)
{
    //The first byte is cast to int for use in our switch
    int command = data[0];

    String string = "";
    //Get the string from the rest of the array, if there is one. 
    if (len > 1){
        for (int i = 1; i < len; i++){
            string += String(data[i]);
        }
        string.trim(); //Remove excess spaces. 
    }

    Serial.println("Received data over BLE");
    //We will print our command and string to the console
    Serial.println(command); 
    Serial.println(string);


    switch (command)
    {
        char buf[20]; // this is used when setting string variables.
    case 0:
        //Don't do any thing. 
        break;
    case 1:
        read();
        break;
    case 2:
        erase();
        break;
    case 3:
        write();
        break;
    case 10:
        erase(); 
        break;
    case 11: 
        //rename device -- save to flash 
        string.toCharArray(buf, string.length() + 1, 0);
        RFduinoBLE.deviceName = buf;
        for (int i = 0; i < string.length(); i++){
            config.name[i] = buf[i];
        }
        //Set the rest of the array to null 
        for (int j = string.length(); j < 20; j++){
             config.name[j] = 0;
        }
        write();
        break;
    case 9:
        //change config -- save to flash 
        string.toCharArray(buf, string.length() + 1, 0);
        for (int i = 0; i < string.length(); i++){
            config.config[i] = buf[i];
        }
        //Set the rest of the array to null 
        for (int j = string.length(); j < 20; j++){
            config.name[j] = 0;
        }
        write();
        //TODO -- apply config settings to running sketch
        break;
    case 32:
        //Used to clear the receive buffer. The buffer 
        //will contain the contents of the last received message
        //unless the current message overwrites it. This can add 
        //characters from the previous message to the current message 
        //and pretty much jack up what you are trying to accomplish. To 
        //prevent this I send 20 spaces after a message to clear the 
        //receive buffer and then trim them from the current message. 
        break;
    case 255:
        //Allows reset of the system.
        RFduino_systemReset(); 
        break; 

    default:
        Serial.println("Nothing to do"); 
    }

}


//This is where the work is done. 
void erase(){
    //erase  
    int rc;
    Serial.print("Attempting to erase flash page " str(FLASH_STORAGE) ": ");
    while (!RFduinoBLE.radioActive);
    while (RFduinoBLE.radioActive);
    RFduinoBLE.end();
    rc = flashPageErase(PAGE_FROM_ADDRESS(in_flash));
    if (rc == 0)
        Serial.println("Success");
    else if (rc == 1)
        Serial.println("Error - the flash page is reserved");
    else if (rc == 2)
        Serial.println("Error - the flash page is used by the sketch");
    RFduinoBLE.begin();
    in_flash = (data_t*)ADDRESS_OF_PAGE(FLASH_STORAGE);
    Serial.println("The data stored in flash page " str(FLASH_STORAGE) " contains: ");
    dump_data(in_flash);

}

void write(){
    //If we don't have valid in the config object - add it here. 
    //This lets us determine if the in_memory struct has valid 
    //data in it or not. 
    if (String(config.valid) != "valid"){
        char valid[6] = { "valid" };
        for (int i = 0; i < 6; i++){
            config.valid[i] = valid[i];
        }
    }

    int rc;
    Serial.print("Attempting to write data to flash page " str(FLASH_STORAGE) ": ");
    while (!RFduinoBLE.radioActive);
    while (RFduinoBLE.radioActive);
    RFduinoBLE.end();
    flashPageErase(PAGE_FROM_ADDRESS(in_flash));
    rc = flashWriteBlock(in_flash, &config, sizeof(config));
    if (rc == 0)
        Serial.println("Success");
    else if (rc == 1)
        Serial.println("Error - the flash page is reserved");
    else if (rc == 2)
        Serial.println("Error - the flash page is used by the sketch");
    RFduinoBLE.begin();
    in_flash = (data_t*)ADDRESS_OF_PAGE(FLASH_STORAGE);
    Serial.println("The data stored in flash page " str(FLASH_STORAGE) " contains: ");
    dump_data(in_flash);

}

void read(){   
    Serial.println("Data in flash");
    dump_data(in_flash);
    Serial.println("Data in memory");
    dump_data(&config, true);
}

As you can see, most of the work is done in the last three functions — read(), write() and erase(). The write function erases before it writes, but an erase function is needed in case we mess up our saved data and need to clean house.

To test this we can use a program similar to Punch Through’s Light Blue (for IOS and Mac ) or any BLE app of your choosing. We simply flash the code to our RFduino the normal way (using the Arduino IDE) and open up the serial monitor.

LightBlue
Here is Punch Through LightBlue connected to our RFduino.

Probe, in this case, is the RFduino running our sketch. The write characteristic is 2220 for our device. Be sure to send hex, it makes it easier to get the correct command. As noted in the code above, messages received are character codes that are cast to ints. So of we use ASCII 1 when trying to get command 1 – read flash – we end up sending character code 49. Sending 0x01 in hex will get us 1.

Sending 0x01 hex will trigger the read command.
Sending 0x01 hex will trigger the read command.

My experience here is that when we try to erase or write the flash with the radio on the RFDuino will reboot — likely due to interrupts from the radio not being handled while erase and write operations are under way.  So what we need to do is turn off the radio before the write or erase and turn it back on after. This causes our application to lose the connection to the device causing us to have to reconnect. This can be handled silently in any app we create, but using LightBlue we will need to reconnect. Write (command 3) will write the contents of the config array to flash.

The result of a write command. The contents of the config array is written to flash.
The result of a write command. The contents of the config array is written to flash.

If we wanted to change the device name, for example, we would use command 11 (0x0B) and provide the name  in a character codes. If we wanted to change the name to “new” it would look like this  — 0x0B4E6577.  This also causes us to reconnect.

We can change the device name by send the new name in character codes
We can change the device name by send the new name in character codes

Other options can be changed in a similar fashion.

The only gotcha to look out for is ensuring strings sent to the device are long enough to clear the receive buffer. For example: If we change the name of the device by sending the string “NewDevice” (in character codes) and then send a string to change the advertising name of “Hello”, the resulting string will be “Hellovice” .  This is because the receive buffer does not flush between messages. We can get around this by sending a string of 20 0x32 (spaces) after a string to flush text out of the buffer. Or we can add an end of line character and check for it upon receipt.

So there you have it. This is a fairly simple way to allow over the air configuration changes to persist on your RFduino device. The code supplied here can certainly be refactored and enhanced to allow more functionality. Some things I plan on adding are password access to config changes and end of line checking.

Please feel free to contact me if you have questions.

Intel Edison and I2C sensors with XDK

The Intel Edison is becoming a popular system to use for IOT devices. Despite its small form factor it is a surprisingly capable platform. This makes the Edison a good choice for interfacing with sensors.

I like the Edison mini breakout board over the Edison kit for Arduino because of the form factor. The  mini breakout board provides USB connectivity, power input and a battery charging circuit to the Edison that covers most the of my requirements for devices.

The drawback of the mini breakout is that you need to either solder in some wires or add a header to the break out board to access i/o for the Edison. Also, the Edison I/o on this board operates at 1.8 volts while most sensors operate at 3.3 volts or higher so a level converter is needed.

The Intel Edison, on the mini breakout, supports various types of i/o but the one we are interested in today is I2C  Inter-Integrated circuit is a two wire serial protocol that is used by components to transfer data between one-another. On the mini-break out board we will be using I2C bus number 1.

Here the Edison is connected to a breadboard containing the MCP9808. There are other devices on the board.
Here the Edison is connected to a breadboard containing the MCP9808. There are other devices on the board.
Here is one way to make the i/o needed for connection available.
Here is one way to make the i/o needed for connection available.

Parts list:

  1. Intel Edison mini breakout board kit.
  2. Sparkfun bi-directional level shift converter.
  3. Adafruit MCP9808 temperature sensor board.
  4. Mini bread board.
  5. Solderless bread board jumpers. 
  6. Dupont male to female cable. 
  7. 90 degree dual row header. 

Items 6 and 7 are optional – you could just solder some wire onto the Edison if you prefer, or use some other type of pin headers.

Connection:

Edison                     Level Shifter                     MCP9808

J17  – 8                          LV1

NC                                 HV1                                  SDA

J18 – 6                           LV2

NC                                HV2                                   SCL

JP19 – 2                         LV — 1.8 volt

JP20 – 2                         HV — 3.3 volt                   VDD

JP19 – 3                        Both grounds                  Ground

The connection looks something like this. The level shifter and MCP9808 are in the center of the yellow board.
The connection looks something like this. The level shifter and MCP9808 are in the center of the yellow board.

With this connection we are ready to code.

I will be using the Intel  XDK IOT edition  to read values from our temp sensor. If you have not used the XDK you can learn how to get started here. Just create a blank project and paste the following code in. Running the code will display the temperature in the console every second.

function char(x) { return parseInt(x, 16)}; // helper for writing registers

var mraa = require('mraa'); //require mraa
console.log('MRAA Version: ' + mraa.getVersion()); //write the mraa version to the Intel XDK console

var x = new mraa.I2c(1); //We will use a device in I2C bus number 1
x.address(0x18); //Default for MCP9808 is 0x10

//x.writeWordReg(char('0x01'), char('0x0100')); // Controls sleep mode for the temp sensor.

periodicActivity();

function periodicActivity()
{

var t = x.readWordReg(char('0x05')); // 0x05 is the register for the current temp.
//The byte order of words is not the same between Edison and the MCP9808
//The edison stores the most significant byte first - big endian, where the
//MCP9808 stores the lowest byte first -- little endian.
//Here is a wikipedia article on endianness. 
var s = ((t & 0xFF) << 8) | ((t >> 8 ) & 0xFF); //swap the bytes.
var r = s & 0xFFF; // Mask of the control bits to get the temp value
r /= 16.0; // dividing by 16 will give us the temp in celcius as long as the temp is above 0.

s = r * 9 / 5 + 32; //get the farenheit value.

console.log(r + " C " + s + " F"); //log the values

setTimeout(periodicActivity,1000); //do it again in a second.
}

For a more thorough explanation of the MCP9808 control registers see it’s data sheet.

And there we have it. It is not a very complex thing to interface an I2C sensor with the Intel Edison. We just need to use a level shifter and connect the thing up. Using the XDK it is fairly easy to read the temp data and control the MCP9808. The complexity comes in when more complex devices are integrated. It can take some time studying the data sheet of a device to figure out how to get everything working correctly.

There are other options for interfacing with I2C and other devices, but MRAA is the easiest in this case as it is already installed. If our sensor had been in the UPM library we could have used a predefined class to operate it.

Beagle Bone Black, Relays and Bonescript.

One of the common things to do with an embedded system is to control a high voltage device with the low voltage signal from a microprocessor. The easiest way to do this is with a relay either of the electronic or mechanical type.

In my application I want to switch a 12 volt vacuum pump on and off and open a 12 volt bleed valve to cycle pressure on and off. When the system boots or there is no 5 volt power I want the pump to be off and the bleed valve to be closed. This means I will need to use the normally open connections of the relay to control the pump and  valve. This poses a couple problems for us, which I will get in to later.

First, lets hook the relay board (Sain Smart Relay Specification) to the BBB. I chose to use P8_11 and P8_12, with 12 controlling relay 1 and 11 controlling relay 2. So make these connections:

P8_11 –> IN2

P8_12 –> IN1

P9_01 –> Ground

P9_07 –> VCC

If we wire the relays directly to the BBB we have a problem.
If we wire the relays directly to the BBB we have a problem.

When we power up the system the first problem shows up. We hear the relays chatter and notice the indictor leds are dimly lit. The issue is that at power on the pin mode is not set and the P8_11 and P8_12 are floating at a value that is neither TTL high or low. This is a problem since we are controlling a pump we don’t want the relays to momentarily energize and send power to energize it.

With the pins on the BBB floating the relays are neither on or off[
With the pins on the BBB floating the relays are neither on or off.
We can exercise the relay with the following script. Just paste this in cloud9 and step through it.

var b = require('bonescript');

var relay1 = "P8_12";
var relay2 = "P8_11";
var c = 0;
//Set pinMode causes output to go to 0, which activates our relay!
//We really need the relay off when low.
b.pinMode(relay1 , b.OUTPUT);
b.pinMode(relay2 , b.OUTPUT);

//Just alternate the relays on/off every second.
setInterval(function(){
b.digitalWrite(relay1 , c % 2 == 0 ? b.HIGH : b.LOW);
b.digitalWrite(relay2 , c % 2 == 0 ? b.HIGH : b.LOW);
c++;
}, 1000);

When we set the pin mode we see the relay activating again. Pin mode sets the pin to TTL low initially, which in this case activates our relay. It is clear we need to add a small circuit between the BBB and the relay board to get this to work the way we want it to. We will add a 74LS04  hex inverter and  a pair of 500 ohm pull down resistors. The circuit is shown here (GND on pin 7, VCC pin 14):

We will add pull down resistors and a 74LS04
We will add pull down resistors and a 74LS04

So go ahead and shutdown the BBB and wire in the 74LS04.  Unused inputs on the 74LS04 should be grounded as per the manufacturers recommendation.

With the inverter and pull downs installed the relays behave as they should.
With the inverter and pull downs installed the relays behave as they should.

When we power up the BBB we will see the problem of the floating TTL is solved via the pull down resistors, and the inverter is translating that low to a high for the relay input. This keeps the relay off. Now we can run the test script above again. If you step through it you will see that setting the pin mode no longer causes the relay to activate. Writing a low to P8_11 and P8_12 will cause the relays to deactivate, a high to these pins will activate the relays. This meets our design goal.

So we see with minimal circuitry we can easily interface the BBB to a relays. The resistors and 74LS04 can be found in any electronics surplus store for pennies, or purchased form Mouser, Digi-Key or your favorite supplier.

Next time I will discuss how to control this relay setup via a web ui with bonescript.