Saturday, October 20, 2012

Reading the Fake Sensor Data

Now that we have created the fake sensor data and put it into a CSV file, we can now re-use the sensor data to help troubleshoot and debug the rest of your system. The first step is to download Processing (if using a Windows system, get the 32 bit version or you won't be able to use its Serial features).

The Processing environment is almost the same as that for the Arduino. Processing will be responsible for reading the CSV file and sending it over the serial connection to the Arduino.

While running the Processing Serial connection, you WILL NOT be able to use the Serial Monitor on the Arduino or upload updated sketches to the micro controller.

However, you can have Processing display any Serial.print() output from the Arduino.

When you need to upload a sketch to the Arduino, just make sure you press the STOP button on the Processing IDE prior to uploading.

The biggest advantage with this is that you can test your much cooler code quickly and easily with a predicable test environment. When you are ready to move to testing with real sensors (which will have to be done anyway later on) it will be trivial to get the input from the sensors themselves rather than from the serial connection.

Enjoy!

Processing Sketch

import processing.serial.*;
String myFile = "c:/mydata.csv";   //note the direction of the slash
String arduinoResponse;            //for displaying Arduino Serial.print() data
Serial myPort;
int delayTime = 25;    
//How long between each byte being transferred. If there isn't enough of a pause
//you could have some unpredictable results.
void setup() {
  //Replace COM3 with your actual serial port
  myPort = new Serial(this, "COM3", 9600);
  
  //The entire csv file will be stored in a string array.
  //One line of the csv per array element
  String lines[] = loadStrings(myFile);

  //Write each line to the serial port, one character at a time
  for (int i =0; i < lines.length; i++) {

    for (int j = 0; j < lines[i].length(); j++) {
      //print (lines[i].charAt(j));
      myPort.write (lines[i].charAt(j));
      delay(delayTime);
    }

    delay(delayTime); 

    //Read any output the Arduino sends over Serial. Great for debugging.
    arduinoResponse = myPort.readString();
    println (arduinoResponse);
  }
}

Arduino Sketch
//Global Variables and Constants

int NUMCOL = 5;    //Number of columns in the sensor data

//Input pins
const int TS = 0;       //time stamp, not really a pin, but...
const int brake = A1;   //Arduino Analog Pin 1
const int BRAKE = 1;    //Column 1 in the array
const int reverse = A2;
const int REVERSE = 2;
const int left = A3;
const int LEFT = 3;
const int right = A4;
const int RIGHT = 4;

//Array that holds sensor readings
const int HISTORY = 100;
//Length of the reading history (set to 1 if only only need the current dataset)
const int CURR = 0;      //0th row is the current reading
long int sensorReading[HISTORY][NUMCOL];    //Sensor readings stored here.

void setup() {
  //Initialize historical arrays to -1 (a reading that is always invalid)

  for (int i = 0; i < HISTORY; i++) { 
    for (int j = 0; j < NUMCOL; j++) {
      wheelSensor[i][j] = -1;
    }
  }

  //Open serial connection to Processing sketch
  Serial.begin(9600); //Make sure Processing and Arduino agree on this number
}


void loop () { 
  readSensor();
  //DoCoolStuffWithSensorReadings(). Your other code goes here.
}



void readSensor () {

  //Get the current reading from the wheel sensors, place them in
  //the current line.

  char currByte;      //Current incoming byte
  int currDigit;      //Convert char to digits
  int currColumn = 0;  //Current column we are working on
  long int currNumber = 0;  //Current number we are builing one byte at a time
  boolean readComplete = false;  //Have we read a full row yet?
  const char SEP = ',';  //Separator character
  const char END_FILE ='@'; //End of file character


  /*Shift the sensor reading array one row down to make room 
  for the current reading. Picture the most recent readings being 
  at the top of the array, and older ones at the bottom*/

  for (int i = HISTORY -1 ; i >=0; i--) { 
    for (int j = 0; j < NUMCOL; j++) {
      wheelSensor[i][j] = wheelSensor[i-1][j];
    }
  }

  //Get the next reading

  while (!readComplete) {

    if (Serial.available())  {

      currByte = Serial.read(); //Next byte on the serial line

      if (currByte >='0' && currByte <='9') {
        /*Get a number, convert it to an int, then put it in the one's place
         in the number we are building. Eg if the entry in the CSV is 654
         we will get 6, then 65, finally 654.*/
        currDigit = currByte - '0';
        currNumber = (currNumber * 10) + currDigit;
      }


      else if (currByte == SEP) {
        //Got a separater, number is complete, add it to the sensor array
        //We always add to the CURR row
        sensorReading[CURR][currColumn] = currNumber;

        //Reset the number we are building to make it ready for the next column
        currNumber = 0;

        if (currColumn == NUMCOL -1) {  
          //Last Column, reading is complete
          currColumn = 0;        
          readComplete = true;
        }

        else {
          //Move to the next column
          currColumn++;
        }

      }//End of Separator stuff

      else if (currByte == END_FILE) {

        Serial.end();
        break;  //Break out of while and close connection

      }

      else {
        //Invalid character
        //You can add your own error handling here if desired.
      }

    }// end of serial reading

  } // end of while loop

} // end of readSensor() function
Creative Commons License
Making a Better Wheelchair is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.

Faking It

The wheelchair project is progressing but one element is more difficult than I had initially anticipated; The ability of the chair to react appropriately to sensor input will require a lot of testing to get the results I want. The test procedure looks like this:
  1. Haul the wheelchair to the test site. Unlike other projects, I can't test it in my living room
  2. Hookup the Arduino, breadboard and wires.
  3. Run and videotape the test.
  4. Disassemble the chair, return home
  5. Find,  fix, and facepalm for typing if (a=b) instead of if (a==b)
  6. Repeat
Naturally, this is not feasible. I need a way to simulate all my sensor data from the computer and feed that information to the Arduino. A CSV file would be a natural way to do this. Simply send the contents of the file through the Serial point and I now have a way to run repeated tests, fiddle with my code, and make it work without having to keep running to the test site.

In other words, I have to fake the sensor inputs. This will take a few steps, and more than one post.

Building the Fake Date File (in CSV)

For my project I have 4 sensors planned at the moment and if things go well I may have 5 or 6. Each line in the CSV file will be in this format (the final comma is important):

   timestamp, brakeSensor, reverseSensor, leftSignal, rightSignal,

The Arduino Code to generate the CSV file from real sensor input is below:

//Global variables

const int NUMCOL = 5;  //How many columns in your CSV file
const char SEP = ',';  //Comma is the separator character
long int sensor[NUMCOL];  //long int because the timestamp could get long quickly
const int DELAY_TIME = 10; //How long between sensor readings

//Input pins

const int TS = 0;       //time stamp, not really a pin, but...
const int brake = A1;   //Arduino Analog Pin 1
const int BRAKE = 1;    //Column 1 in the array
const int reverse = A2;
const int REVERSE = 2;
const int left = A3;
const int LEFT = 3;
const int right = A4;
const int RIGHT = 4;

void setup() {
  Serial.begin(9600);  //Begin a serial connection
}

void loop () {
  
  //Since it's so fast, all readings can all share a single time stamp
  sensor[TS] = millis();  //get timestamp
  sensor[BRAKE] = analogRead(brake);
  sensor[REVERSE] = analogRead(reverse);
  sensor[LEFT] = analogRead(left);
  sensor[RIGHT] = analogRead(right);
  
  //Print to serial monitor
  
  for (int i = 0; i < NUMCOL; i++) {
    Serial.print(sensor[i]);
    Serial.print(SEP);
  }
  
  //Output the \n character to make the CSV easier to read
  
  Serial.print('\n'); 
  delay(DELAY_TIME);
  
} // end loop

To get the data to a CSV file, simply open up the Serial Monitor, and copy and paste to Notepad.  To make the next steps easier, you will need to do the following:

  1. At the very top, include or or two lines of "dummy data". When reading the file, this will give the serial port time to connect on both ends without risk of missing real data. I used two rows of all zeroes.
  2. Indicate the end of the file with an '@' character. I actually used @,@,@,@,@ but only one should be needed.


In the next post I will include the code needed to send the sensor data to the Arduino to allow you to "fake it" with your sensors.

Creative Commons License
Making a Better Wheelchair is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.