• Build logs, Tutorials and Hacks

    Saturday, January 24, 2015

    Writing a protocol on Arduino on Serial Interrupts - The Tree Controller Code

    Preface


    My project for the Internet of Holoday lights was based around our living room which was not very livable. It was a mess and with the upcoming holidays I wanted to set the living room in such a way that it would be suitable for entertaining guests. Additionally I wanted to accomplish the above mentioned in such a way that the holiday lighting becomes part of the living room and I don't need to remove it after the holidays. Hence the concept of Dynamic Living-room Lighting.


    In this post while I wait for my video to upload, I explain the code for the tree.

    The problem


    The basic issue that I was facing is the ability to connect two pieces of my project which is the YUN and the Tree together. I initially though of using a wireless route but in the end, since I needed to send a direct audio line to the tree anyways, hence I chose to use the wired approach and an old school way of doing things on it. There are a couple of methods of doing what I am doing like libraries and stuff and any suggestions are welcome.



    The Approach


    My approach was to build in two steps.
    Step 1. Build the communication
    Step 2. Build the activity functions around the communication.
    I will briefly explain the code for both in the next few sections.



    The communications code


    I built the communications part on top of the serial interface of the arduino. I generally use the interrupts system in any microcontroller since it makes sure I 'don't wanna miss a thinngg'(-Aerosmith). Sorry about that pun.
    Anyways I start with the example code on the arduino and I use the Serial Event example




    [code language="cpp"]
    String inputString = ""; // a string to hold incoming data
    boolean stringComplete = false; // whether the string is complete
    + expand sourceview plain
    void setup() {
    // initialize serial:
    Serial.begin(9600);
    // reserve 200 bytes for the inputString:
    inputString.reserve(200);
    }


    void loop() {
    // print the string when a newline arrives:
    if (stringComplete) {
    Serial.println(inputString);
    // clear the string:
    inputString = "";
    stringComplete = false;
    }
    }


    /*
    SerialEvent occurs whenever a new data comes in the
    hardware serial RX. This routine is run between each
    time loop() runs, so using delay inside loop can delay
    response. Multiple bytes of data may be available.
    */
    void serialEvent() {
    while (Serial.available()) {
    // get the new byte:
    char inChar = (char)Serial.read();
    // add it to the inputString:
    inputString += inChar;
    // if the incoming character is a newline, set a flag
    // so the main loop can do something about it:
    if (inChar == '\n') {
    stringComplete = true;
    }
    }
    }
    [/code]

    Here you can see that there is a function called serialEvent() which executes automatically every time the arduino receives a character. There are two global variables that can be accessed from anywhere in the arduino program where we save the received string and a notification flag that a string was completely received. BUT I don't need that so I start by deleting way the code in the functions and write my own.


    The serialEvent is supposed to be triggered everytime I get a single byte over the UART and hence the code becomes something like...

    [code language="cpp"]
    void serialEvent() {
    while (Serial.available()) {
    // Read a char
    char inChar = (char)Serial.read();
    if(inChar==':'){ // Start New Frame
    memset(ptr, 0, FRAMESIZE);
    inFrame=true;
    myIndex=0;
    }
    if(inFrame==true && myIndex<FRAMESIZE){ // Add a byte
    *(ptr+myIndex)=inChar;
    myIndex++;
    }
    if(myIndex==FRAMESIZE){
    inFrame=false;
    if(inChar=='#')
    memcpy(&myFrame, ptr, FRAMESIZE);
    }
    }
    }
    [/code]

    In my code, I have a global flag inFrame which tells me if I am in between frame receives. The reason why I have it as a global and not a static is because I later want to implement a timer and a time out which means that if it takes too long to receive a complete frame then prolly something is wrong and I need to discard the data because something went wrong.
    In this code I simply wait for ':' which is my start of frame and then use memset to initialize the space with 0s and set some flags. In the next iteration, the function will be 'inFrame' and hence it will copy the data to the memory. I understand pointers so I just use that instead of the dozen other methods. I also check for my maxFrameSize since my protocol has a fixed frame size. I can use a variable frame size too but maybe some other day.
    In the end if the received data matches the frame size, we check for the endofframe which is a '#'. Everything in between can be a ascii character or anthing else but our code does not care about that. The question is what is that pointer pointing to?



    The protocol


    I made a structure for my protocol and it's quite simple.




    [code language="cpp"]
    struct sFrame{
    char cStart; // Expect :
    char cMode; // A-Audio, M-Manual with RGB, P-Pattern
    char cRed;
    char cGreen;
    char cBlue;
    char cEnd;
    };
    [/code]

    As you can see that there are char type variables for everything. These can be replaced but again not our motive right now. In the global space, I do the following.


    struct sFrame myFrame, buffer; // Object to store the frame
    char *ptr = (char*)&buffer; // Point to the object


    This creates an object without using the heap (malloc) since I will need a fixed size throughout the lifetime of my system. Next I create a pointer to this buffer BUT cast it into a char type. This makes sure when I ptr++, I jump to the next char in my structure. Easy! But how do I use this?



    The Loop


    The code for the loop is given below.




    [code language="cpp"]
    void loop() {
    // print the string when a newline arrives:

    if(myFrame.cMode=='A'){
    //Serial.println("Audio Mode");
    hue0 = constrain(map(analogRead(channel), 0, 400, 0, 255), 0, 255); // Read Audio
    uint32_t color0 = Wheel(hue0);
    //Shift the current values.
    for (i = 0; i<NUMPIXELS; i++){
    data_array[i+1] = data_array[i];
    }
    data_array[0] = color0;
    for(i=0; i<NUMPIXELS; i++){
    pixels.setPixelColor(i, data_array[i]);
    pixels.show(); // This sends the updated pixel color to the hardware.
    }
    }else if(myFrame.cMode=='M'){ // Manual mode
    for(i=0; i<NUMPIXELS; i++){
    pixels.setPixelColor(i, pixels.Color(myFrame.cGreen, myFrame.cRed, myFrame.cBlue));
    pixels.show(); // This sends the updated pixel color to the hardware.
    }
    }else if(myFrame.cMode=='P' && myFrame.cRed==0x00){
    rainbowCycle(20);
    }else if(myFrame.cMode=='P' && myFrame.cRed==0x01){
    colorWipe(pixels.Color(240, 0, 0), 200); // Red
    colorWipe(pixels.Color(0, 240, 0), 200); // Green
    colorWipe(pixels.Color(0, 0, 240), 200); // Blue
    colorWipe(pixels.Color(240, 240, 0), 200); // Red
    colorWipe(pixels.Color(0, 240, 240), 200); // Green
    colorWipe(pixels.Color(240, 0, 240), 200); // Blue
    }
    }
    [/code]

    In this example, I use the functions from Adafruit's NeoPixel Library to implement the patterns. When in the loop, I check for the mode settings. If its A then so something and if M then do something and if P then do something else. The serial interrupt takes care of serial communication in the background. Since I have created simple objects for the structure hence I can access the member functions with the . operator. Nothing to it.



    Conclusion


    I wanted to write a short article to explain the working of asynchronous events on a microcontroller and here it is. This is the simplest possible implementation and you can expand it as you like. There is no library code size since there is no library and I hope you got something while reading this. Any suggestions are most welcome and I if you do a project and use any snippet, just say Hi in the comments.


    Thanks,
    IP

    No comments:

    Post a Comment