Pages: [1] 2   Go Down
Author Topic: Servo control  (Read 1244 times)
0 Members and 1 Guest are viewing this topic.
Colorado
Offline Offline
Edison Member
*
Karma: 47
Posts: 1562
Reviving dead brain cells with Arduinos.
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

I posted this in the Storage section a few days ago because at the time I was trying to figure out if what I wanted to do would work with an SD Card.  The answer was yes.  So now I'm moving on to the actual programming aspect of the same thing.  The idea is as follows:

I have two servos that will function as a pan and tilt system.  I'm going to cue them with music.  I have several cue points during the audio piece where I need the servos to do something.  Taking a page from a similar broadcast system, I thought I could have a single text file with commands listed in it like so:
Code:
angle_pan   speed_pan   angle_tilt   speed_tilt   pause
       90           2           90            2       5
       45           4           90            0       2
      135           4           90            0       5
Which I would read in as:
- move the pan servo to 90 degrees with speed = 2, move the tilt servo to 90 degrees with speed = 2, wait for 5 seconds.
- after 5 seconds, move the pan servo to 45 degrees with speed = 4, leave the tilt servo at 90 degrees, wait for 2 seconds.
- after 2 seconds, move the pan servo to 135 degrees with speed = 4, leave the tilt servo at 90 degrees, wait 5 seconds.
Etc., etc.  (speed will be somewhere between 2 (fast) and 10 (slow))

The controller's code will then open the SD card, and read the file in bit by bit as it needs to and move the servos as needed.

I choose this method primarily because then I don't have to do anything with the actual code on the controller, just the text file that's on the SD card.  The controller simply reads it in as needed.  When I need to change what the servos do, I simply update the SD card with a new file (for a different piece of music), and off I go.

My question is, is this the best way of doing this.  Will I have to content with a lot of lag between the time it takes for the data to be read from the SD card each time?  I don't plan on reading the whole thing in at the beginning of the program, it will be too big to fit in memory.  So I'll constantly be reading from the card and pass instructions to the servos.

Does anyone have suggestions on how to improve this system, or even come up with a different design perhaps?  I suppose I can always add a DataFlash chip on the controller board.  The controller then reads the whole file from SD card into memory for faster access.  I don't know if that would really make much of a difference though.

Ideas/suggestions anyone?
Logged

0
Offline Offline
Edison Member
*
Karma: 7
Posts: 1235
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

sub'ing to keep up with the thread.  (cool idea)..

much like creating a web app.. and then just editing the .xml file to make changes..etc


anyways..  maybe to get rid of the overhead of accessing the SD card..reading, parsing date.....etc..

maybe read in a 'few' line/variables at a time.. maybe the first 10 to give you a bit of a 'buffer' for smoother/continuous movement..?
Logged


Seattle, WA USA
Offline Offline
Brattain Member
*****
Karma: 601
Posts: 48567
Seattle, WA USA
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

How many records will there be?

You can't alter how the SD read is done. It will read 512 byte chunks from the file, or less if there is not that much data. If all the values are byte sized, you could create a binary file, with 5 bytes per record (no cr/lf needed). That would allow for 102 records before another disk read is performed.

The proper thing to do,though is perform a timing test. See if the read will be fast enough.
Logged

Colorado
Offline Offline
Edison Member
*
Karma: 47
Posts: 1562
Reviving dead brain cells with Arduinos.
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

anyways..  maybe to get rid of the overhead of accessing the SD card..reading, parsing date.....etc..

maybe read in a 'few' line/variables at a time.. maybe the first 10 to give you a bit of a 'buffer' for smoother/continuous movement..?

Where would you be reading those 'few' lines/variables at a time from?  Thin air?  They're coming off of the SD card.

How many records will there be?

You can't alter how the SD read is done. It will read 512 byte chunks from the file, or less if there is not that much data. If all the values are byte sized, you could create a binary file, with 5 bytes per record (no cr/lf needed). That would allow for 102 records before another disk read is performed.

The proper thing to do,though is perform a timing test. See if the read will be fast enough.

How many records, not entirely sure yet.  It's a 6 minute show, and the servos will be doing "stuff" the whole time.  At the most, they'll be receiving a new instruction every second.  For the moment, the instruction that needs to get parsed is what I've shown above, 5 parameters per record.  Two of them will have a number between 0 and 180, two of them will have a number between 0 and 10, and the last one is 0 to 30 ... maybe larger, but no more than 3 digits.

Can I fit it all in 512 bytes?  Just a random set of instructions, sending both servos into sweep mode and changing every 2-5 seconds, and I can get about 35 instructions, one per line (cr/lf at the end), in a TEXT file.  I want to stay away from having to create a binary file.  The idea is that anyone can enter their instructions in an Excel file and export the sheet as a CSV file.

I'm going to need more than 35 instructions, that's for sure. smiley
Logged

Seattle, WA USA
Offline Offline
Brattain Member
*****
Karma: 601
Posts: 48567
Seattle, WA USA
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Quote
I want to stay away from having to create a binary file.  The idea is that anyone can enter their instructions in an Excel file and export the sheet as a CSV file.
Like I said, you should run a test. Create a file that contains more than 512 bytes. Call micros() before and after each read(). Compute the difference, and write it to the serial port. The 1st and the 513th read should take significantly longer than the 2nd, 3rd, etc. reads. Whether that actual time is significant, or not, only you can decide.

One thing you will notice is that parsing and using the text data is going to take significantly longer than reading and using binary data would.

It might be worth the effort to develop a csv to bin converter to create the binary file. Only testing will tell.
Logged

Colorado
Offline Offline
Edison Member
*
Karma: 47
Posts: 1562
Reviving dead brain cells with Arduinos.
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Agreed, testing is definitely needed.

While futzing around last night, learning the SD library and what not, I came up with this little snippet:
Code:
#include <SD.h>

File dataFile;
int done = 0;
char c;
String data;

void setup() {
  Serial.begin(9600);
  Serial.println("Initializing SD card...");
  pinMode(10, OUTPUT);
   
  if (!SD.begin(4)) {
    Serial.println("initialization failed!");
    return;
  }
  Serial.println("initialization done.");
}

void loop() {
  dataFile = SD.open("data.txt", FILE_READ);
 
  if (!done) {
    if (dataFile) {
      while (dataFile.available()) {
        c = dataFile.read();
        switch(c) {
          case ',':
            Serial.print(data);
            Serial.print(" - ");
            data = "";
            break;
          case '\n':
            Serial.println(data);
            /*
              Process instructions here ...
              Break up into {angle_pan (INT), speed_pan (INT), angle_tilt (INT), speed_tilt (INT), pause (INT)}
            */
            Serial.println("new line detected!");
            data = "";
            break;
          default:
            data = data + String(c);
            break;
        }
      }
      done = 1;
      dataFile.close();
      Serial.println();
      Serial.println("Done reading file.");
    } else {
      Serial.println("error opening data.txt");
    }
  }
}

The data.txt file looks like this:
Code:
90,2,90,2,5
45,4,135,4,2
135,4,45,4,2
90,2,90,2,5

The sketch does what I'm expecting it to do, it prints out (I'm not at my workbench, so I'm writing from memory here)
Code:
90 - 2 - 90 - 5
new line detected!
45 - 4 - 135 - 4 - 2
new line detected!
etc., etc.

Now, I did that as an exercise, see if I could figure out the parsing and all of that.  Great.  However, what I need are INTs, not Strings.  Because I need to pass that data on to the Servo library.  And this is where I wish I had paid more attention, converting from one to the other.

If you notice, in my sketch I wrote myself a little comment of where I need to parse the full instruction line.  I don't know how to actually do that.  Ideally it's just an array with the various values in it which I will then pass on to the servo function to do its thing.

Some help will be much appreciated here.
Logged

Seattle, WA USA
Offline Offline
Brattain Member
*****
Karma: 601
Posts: 48567
Seattle, WA USA
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Quote
Now, I did that as an exercise, see if I could figure out the parsing and all of that.  Great.  However, what I need are INTs, not Strings.
A couple corrections. What you need are ints, not strings. Ditch the String class. You will run out of memory that way.

A fixed array of chars that you store c in (and increment an index and add a NULL terminator to) will, overall, use less memory. Each time you encounter a comma, call atoi() to convert the value to an int, and reset index (and put a NULL in the 0th position of the array).
Logged

Colorado
Offline Offline
Edison Member
*
Karma: 47
Posts: 1562
Reviving dead brain cells with Arduinos.
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

A couple corrections. What you need are ints, not strings. Ditch the String class. You will run out of memory that way.

A fixed array of chars that you store c in (and increment an index and add a NULL terminator to) will, overall, use less memory. Each time you encounter a comma, call atoi() to convert the value to an int, and reset index (and put a NULL in the 0th position of the array).

While I understand what you're explaining to me, the whole process of converting from one to the other is elusive to me. The fixed array, would that be for the 5 elements I will eventually end up with, or is that for each character read to be stored in prior to converting to INT?
Logged

Seattle, WA USA
Offline Offline
Brattain Member
*****
Karma: 601
Posts: 48567
Seattle, WA USA
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Quote
The fixed array, would that be for the 5 elements I will eventually end up with, or is that for each character read to be stored in prior to converting to INT?
Could be either one. Depends on whether you want to read a whole record, then parse it, and convert each token to an int, or whether you want to read and parse in one step, as you are doing now.
Logged

Colorado
Offline Offline
Edison Member
*
Karma: 47
Posts: 1562
Reviving dead brain cells with Arduinos.
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Could be either one. Depends on whether you want to read a whole record, then parse it, and convert each token to an int, or whether you want to read and parse in one step, as you are doing now.

Probably the first method, read till I get a new line, then parse the whole thing.  Unless you can think of a reason why I should be doing it one at a time.  In the end, I'd like to just pass all the arguments on to the servo function as one piece (at least, I think I can.)  So something like myServos(arg1, arg2, arg3, arg4).  The pause argument is being used by the main loop itself, not the servo function.
Logged

Seattle, WA USA
Offline Offline
Brattain Member
*****
Karma: 601
Posts: 48567
Seattle, WA USA
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Quote
Probably the first method, read till I get a new line, then parse the whole thing.
Something like this, then:
Code:
char buffer[40]; // Hold a whole record (may be larger than needed
byte index;

void loop()
{
   dataFile = SD.open("data.txt", FILE_READ);
   if (dataFile)
   {
      index = 0;
      buffer[index] = '\0';

      while (dataFile.available())
      {
         c = dataFile.read();
         if(c == '\n' || c == '\r')
         {
            parseBuffer();
            index = 0;
            buffer[index] = '\0';
         }
         else if(index < 39)
         {
            buffer[index++} = c;
            buffer[index] = '\0';
         }
      }
   }
}

void parseBuffer()
{
   if(strlen(buffer) > 0)
   {
       int vals[] = {0,0,0,0,0};
       byte index = 0;
       char *token = strtok(buffer, ",");
       while(token)
       {
          vals[index] = atoi(token);
          token = strtok(NULL, ",");
       }

       // Use the values here...
   }
}
Logged

Colorado
Offline Offline
Edison Member
*
Karma: 47
Posts: 1562
Reviving dead brain cells with Arduinos.
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Hrm, getting something odd here
Code:
#include <SD.h>

File dataFile;
int done = 0;
char c;
char buffer[4];
byte index;

void setup() {
  Serial.begin(9600);
  Serial.println("Initializing SD card...");
  pinMode(10, OUTPUT);
   
  if (!SD.begin(4)) {
    Serial.println("initialization failed!");
    return;
  }
  Serial.println("initialization done.");
}

void loop() {
  dataFile = SD.open("data.txt", FILE_READ);
 
  if (!done) {
    if (dataFile) {
      index = 0;
      buffer[index] = '\0';
     
      while (dataFile.available()) {
        c = dataFile.read();
        if (c == '\n' || c == '\r') {
          parseBuffer();
          index = 0;
          buffer[index] = '\0';
        } else if (index < 39) {
          buffer[index++] = c;
          buffer[index] = '\0';
        }
      }
      done = 1;
      dataFile.close();
      Serial.println();
      Serial.println("Done reading file.");
    } else {
      Serial.println("error opening data.txt");
    }
  }
}

void parseBuffer() {
  if (strlen(buffer) > 0) {
    int vals[] = {0, 0, 0, 0, 0};
    byte index = 0;
    char *token = strtok(buffer, ",");
    while (token) {
      vals[index] = atoi(token);
      token = strtok(NULL, ",");
    }
    // print vals
    for (int i = 0; i < 5; i++) {
      Serial.print(vals[i]);
      Serial.print(" - ");
    }
    Serial.println();
  }
}

Data file contains:
Code:
90,2,90,2,5
45,4,135,4,2
135,4,45,4,2
90,2,90,2,5


The ouputs the following to the serial monitor:
Quote
Initializing SD card...
initialization done.
5 - 0 - 0 - 0 - 0 -
5 - 0 - 0 - 0 - 0 -
5 - 0 - 0 - 0 - 0 -
5 - 0 - 0 - 0 - 0 -

Done reading file.

On a side note, why can't I use 'i < sizeof(vals)' ?  When I try that, I get a different output:
Quote
Initializing SD card...
initialization done.
5 - 0 - 0 - 0 - 0 - 2268 - 0 - 1297 - -1534 - 397 -
5 - 0 - 0 - 0 - 0 - 2268 - 0 - 1297 - -1534 - 397 -
5 - 0 - 0 - 0 - 0 - 2268 - 0 - 1297 - -1534 - 397 -
5 - 0 - 0 - 0 - 0 - 2268 - 0 - 1297 - -1534 - 397 -

Done reading file.
Logged

Offline Offline
Jr. Member
**
Karma: 0
Posts: 58
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

This probably isn't right      

 token = strtok(NULL, ",");

edit, try this:

Code:


void parseBuffer() {
  if (strlen(buffer) > 0) {
    int vals[] = {0, 0, 0, 0, 0};
    byte index = 0;
    char *token = strtok(buffer, ",");
    while (token) {
      vals[index++] = atoi(token);
      token = strtok(buffer, ",");
    }
    // print vals
    for (int i = 0; i < 5; i++) {
      Serial.print(vals[i]);
      Serial.print(" - ");
    }
    Serial.println();
  }
}
« Last Edit: March 18, 2012, 05:03:27 am by remiss » Logged

Seattle, WA USA
Offline Offline
Brattain Member
*****
Karma: 601
Posts: 48567
Seattle, WA USA
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

@remiss
Yes, it is. Using NULL as the first argument tells strtok() to keep parsing the same string. Your modification will have strtok() start parsing the original string again, which will result in an endless loop.

Quote
On a side note, why can't I use 'i < sizeof(vals)' ?  When I try that, I get a different output:
vals is an array of ints. There are 5 ints in the array, so that is a total of 10 bytes, so sizeof(vals) returns 10.

sizeof(vals)/sizeof(vals[0]) would return the number of elements in the array.

As to your overall problem, there could be an issue with the reading of the file, or there could be an issue with the parsing. Add:
Code:
Serial.print("String to parse: [");
Serial.print(buffer);
Serial.println("]");
to the top of parseBuffer(). This will isolate the error to either the reading or the parsing.
Logged

Seattle, WA USA
Offline Offline
Brattain Member
*****
Karma: 601
Posts: 48567
Seattle, WA USA
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Code:
char c;
char buffer[4];
byte index;
buffer is WAY too small. I suggested a size of 40, not 4!
Logged

Pages: [1] 2   Go Up
Jump to: