Notifications
Clear all

REVISIT: Using SD Cards with Arduino - Record Servo Motor Movements

58 Posts
6 Users
7 Likes
1,834 Views
HinoBot
(@hinobot)
Active Member
Joined: 4 months ago
Posts: 7
Topic starter  

Hello everyone, I am working on an animatronic prop with my daughters to encourage science and engineering.  We have 3D printed all the parts and have been tinkering with the Arduino, servos, and now the code.  We closely followed Bill’s YouTube video “Using SD Cards with Arduino - Record Servo Motor Movements”.  We went step by step and learned significantly, especially since we have no coding experience.  We interpolated a lot of information and used his code, along with bits from other code and got to the point where we can connect four (4) pots, and control four (4) servos, but only record 1 channel.  We worked out the errors, but we are not getting to record all channels, and in the playback, the servos are sped up and do not follow the timing we established. We have spent hours trying to resolve these kinks, but no luck, moral is low.

We followed a similar post on this forum to compose a code, but it included LED inputs we do not need (and do not clearly understand), and we also get errors.  Therefore, we need assistance with a code that will do the following:

  • Servos set to an arbitrary start position.
  • Move pots that in turn move servos.
  • Press a physical button (or “R” in the serial monitor) to start recording the pots and servos.
  • Record those movements for a period of time (8GB card should store a lot of input).
  • Press a physical button (or “P” in the serial monitor) to stop the recording and store it on the SD card and play it back.
  • We need to be able to power everything down, transport, and when plugged back it, the servos begin to play the pre-recorded movements indefinitely.
  • BONUS: We would like to add more servos to the code, but we can hopefully figure out the pattern in the code (which we have for some other things), and expand on it.

I have attached the code to record and playback which is a combination of things I have put together, the code recognizes the pots and servos, and lets me move them, but it only records one, and the playback is fast.  We omitted a SERVO controller for now to simplify things and go stragiht off the arduino.  Thank you again so much! 

This is my attempt at the record code and the playback code.

RECORD Code from DroneBot Workshop 2019:

/*

  Servo position recorder

  servo-record.ino

  Records servo movements on SD card

  Displays results on Serial Monitor

  DroneBot Workshop 2019

   https://dronebotworkshop.com

*/

 

// Include libraries

 

#include <SPI.h>

#include <SD.h>

#include <Servo.h>

 

// CS pin for SD Card Module

const int chipSelect = 4;

// Analog pin for potentiometer

int analogPin = A0;

int analogPin1 = A1;

int analogPin2 = A2;

int analogPin3 = A3;

 

// Integer to hold potentiometer value

int val1 = 0;

int val2 = 0;

int val3 = 0;

int val4 = 0;

 

 

// Create a Servo object

Servo myservo;

Servo myservo2;

Servo myservo3;

Servo myservo4;

 

 

void setup() {

 

  // Open serial communications and wait for port to open:

  Serial.begin(9600);

  while (!Serial) {

    ; // wait for serial port to connect. Needed for native USB port only

  }

 

  Serial.print("Initializing SD card...");

 

  // see if the card is present and can be initialized:

  if (!SD.begin(chipSelect)) {

    Serial.println("Card failed, or not present");

    // don't do anything more:

    while (1);

  }

  Serial.println("card initialized.");

 

  // Attach servos to the servo object

  myservo.attach(9); 

  myservo2.attach(8); 

  myservo3.attach(7); 

  myservo4.attach(6);

}

 

void loop() {

  // make a string for assembling the data to log:

 

String dataString = "";

  String dataString2 = "";

  String dataString3 = "";

  String dataString4 = "";

 

 

  // Read pot value and append to the string

  // Map to range of 0-180 for servo

    for (int analogPin = 0; analogPin < 4; analogPin++) {

      int sensor = analogRead(analogPin); 

      dataString += String(sensor);

      if (analogPin < 4) {

        dataString += ",";

      }

    }

     

    val1 = map(analogRead(analogPin), 0, 1023, 0, 180);

    dataString += String(val1);

    myservo.write(val1);

    val2 = map(analogRead(analogPin1), 0, 1023, 0, 180);

    dataString2 += String(val2);

    myservo2.write(val2);

    val3 = map(analogRead(analogPin2), 0, 1023, 0, 180);

    dataString3 += String(val3);

    myservo3.write(val3);

    val4 = map(analogRead(analogPin3), 0, 1023, 0, 180);

    dataString4 += String(val4);

    myservo4.write(val4);

   

  // Write to the servo

  // Delay to allow servo to settle in position

  myservo.write(val1);

  myservo2.write(val2);

  myservo3.write(val3);

  myservo4.write(val4);

  delay(15);

 

  // open the file. note that only one file can be open at a time,

  // so you have to close this one before opening another.

  File dataFile = SD.open("servopo1.txt", FILE_WRITE);

 

  // if the file is available, write to it:

  if (dataFile) {

    dataFile.println(dataString);

    dataFile.close();

    // print to the serial port too:

    Serial.println(dataString);

  }

  // if the file isn't open, pop up an error:

  else {

    Serial.println("error opening servopo1.txt");

  }

}

 

PLAYBACK Code from DroneBot Workshop 2019:

/*

  Servo position playback

  servo-playback.ino

  Plays back servo movements from SD card

  Used with servo-record.ino

  Displays results on Serial Monitor

  DroneBot Workshop 2019

   https://dronebotworkshop.com

*/

 

// Include libraries

 

#include <SPI.h>

#include <SD.h>

#include <Servo.h>

 

// CS pin for SD Card Module

const int chipSelect = 4;

 

// String to hold one line of text

String buffer;

 

// Create a Servo object

Servo myservo;

Servo myservo2;

Servo myservo3;

Servo myservo4;

 

 

void setup() {

 

  // Open serial communications and wait for port to open:

  Serial.begin(9600);

  while (!Serial) {

    ; // wait for serial port to connect. Needed for native USB port only

  }

 

  Serial.print("Initializing SD card...");

 

  // see if the card is present and can be initialized:

  if (!SD.begin(chipSelect)) {

    Serial.println("Card failed, or not present");

    // don't do anything more:

    while (1);

  }

  Serial.println("card initialized.");

 

  // Attach servo on pin 9 to the servo object

  myservo.attach(9); 

  myservo2.attach(8); 

  myservo3.attach(7);

  myservo4.attach(6);

 

  // open the file. note that only one file can be open at a time,

  // so you have to close this one before opening another.

  File dataFile = SD.open("servopo1.txt");

 

 

  // If the file is available, read it

  if (dataFile) {

    while (dataFile.available()) {

    // Write one line to buffer

    buffer = dataFile.readStringUntil('\n');

    // Print to serial monitor

      Serial.println(buffer);

    // Convert string to integer and position servo

      myservo.write(buffer.toInt());

      myservo2.write(buffer.toInt());

      myservo3.write(buffer.toInt());

      myservo4.write(buffer.toInt());

    delay(15);

     

    }

    dataFile.close();

  }

  // if the file isn't open, pop up an error:

  else {

    Serial.println("error opening servopo1.txt");

  }

}

 

void loop() {

}

 


   
Quote
Topic Tags
Inq
 Inq
(@inq)
Noble Member
Joined: 8 months ago
Posts: 962
 

Please place your code using the following button.  I tried to just copy/paste your code in an editor and it was double spaced.  I'd like to help.  Sounds like a cool challenge, but I'm not willing to spend 30 minutes simply so I can read your code.  

Thanks,

Inq

image

3 lines of code = InqPortal = Complete IoT, App, Web Server w/ GUI Admin Client, Access Point Manager, Drag & Drop File Manager, OTA, Performance Metrics, Web Socket Comms, Easy App API, All running on ESP8266...
Even usable on ESP-01S - Quickest Start Guide


   
ReplyQuote
Ron
 Ron
(@zander)
Famed Member
Joined: 2 years ago
Posts: 3432
 

@hinobot Lot's of hep available under the Help Menu including a practice forum where you can try something like posting code to see what it looks like.

Image 2022 07 31 at 12.35

 

"Don't tell people how to do things. Tell them what to do and let them surprise you with their results.” - G.S. Patton, Gen. USA
"Never wrestle with a pig....the pig loves it and you end up covered in mud..." anon


   
ReplyQuote
Will
 Will
(@will)
Noble Member
Joined: 1 year ago
Posts: 2124
 

@hinobot

First off, you should open the file in the setup() section. If it doesn't open, print a message and hang there until the user stops the process. By creating and reopening it each time through the loop, you're slowing the loop() considerably. Once the file is open you are free to continue writing to it from loop() as long as you want.

It is difficult to understand the flow of control in the recording section (I just haven't gotten to the playback side yet). You have a variable called dataString to which you append the (comma separated) values of the pots each time through the loop. However, you don't use anything to delimit the end of the line (of 4 readings) and you pollute this data by subsequently appending another value (mapped to a servo position val1 and converted to a string) without any delimiter,

Inside loop() first, you have a for loop which reads each of the pots, converts the raw value to a string and appends that onto a string (called dataString) containing all 4 results separated by commas. So far, so good, but you don't then distinguish the "end of line" in any way.

You then REREAD each pin (why, you already have a value from above), you then map it from the 0-1023 range of the pot to a 0-180 range for the servo. You then write the mapped value to the servo. This time, you save the first (and only the first) value again to dataString without any delimiters and save the other values (val2, val3, val4) to dataString2, dataString3 and dataString4.

If that's not confusing enough, you then rewrite val1 to val4 to their respective servos again.

I think your first task here is to decide what you want in the data strings. Maybe create a dataString1 to correspond to the dataString2, ...3, ...4 if you think you still need them. It's hard to tell because you do append values to them but you never save them to the file anywhere.

You should also consider whether you need to store the raw values (i.e. directly from the pots as you do now) or the values as mapped for servo positions. Which will you really need for playback ?

After you've got that debugged, post the updated file again and note any problems observed, but only for the "record" part; the "playback" part needs to be treated separately (and can only be done once you decide what information you'll be storing and how you'll store it).

Experience is what you get when you don't get what you want.


   
Ron reacted
ReplyQuote
Inq
 Inq
(@inq)
Noble Member
Joined: 8 months ago
Posts: 962
 

Industrial robots were programmed using a very similar kind of action... move to, then record point and then play them back.  It took huge amounts of time to say... paint a door panel.  I'd bet, they still do, but maybe they've advanced since the last time I was near one mid '80s.  

From just your description (haven't looked at your code yet) it sounds like you have two separate problems:

  1. Only recording or only playing back one channel.
  2. Not recording or not playing back the time component of the data.

How are you adding the time component?

I imagine you want to move the body around until you get it "just right" then you want to take a snapshot.  Then you move it some more and take another snapshot.  Kind of like they do with Claymation videos.  When you play yours back the servos move to each position, "one snapshot" at a time.  I imagine it takes far more time to "stage" some snapshots than others, so you have to add the time manually (I'm guessing).

Does this sound like what you want to do?

3 lines of code = InqPortal = Complete IoT, App, Web Server w/ GUI Admin Client, Access Point Manager, Drag & Drop File Manager, OTA, Performance Metrics, Web Socket Comms, Easy App API, All running on ESP8266...
Even usable on ESP-01S - Quickest Start Guide


   
ReplyQuote
Ron
 Ron
(@zander)
Famed Member
Joined: 2 years ago
Posts: 3432
 

@inq My IBM account back in the early 70's was Ford in Oakville Ont. They had huge car painting robots and I was told they 'programmed' them by an expert human painter grasping the paint gun and actually painting a door for example. The robot would then perform the exact same motions mistakes and all. I don't know but am guessing there were a small group of these expert human painters and maybe they held a competition to see who could paint the door the most efficiently, least time, least material but still get full coverage. I wonder if that is how it is still done, or is artificial vision used now?

"Don't tell people how to do things. Tell them what to do and let them surprise you with their results.” - G.S. Patton, Gen. USA
"Never wrestle with a pig....the pig loves it and you end up covered in mud..." anon


   
ReplyQuote
Inq
 Inq
(@inq)
Noble Member
Joined: 8 months ago
Posts: 962
 
Posted by: @zander

@inq My IBM account back in the early 70's was Ford in Oakville Ont. They had huge car painting robots and I was told they 'programmed' them by an expert human painter grasping the paint gun and actually painting a door for example. The robot would then perform the exact same motions mistakes and all. I don't know but am guessing there were a small group of these expert human painters and maybe they held a competition to see who could paint the door the most efficiently, least time, least material but still get full coverage. I wonder if that is how it is still done, or is artificial vision used now?

I'd bet Tesla factory are doing something like that with vision and AI.

3 lines of code = InqPortal = Complete IoT, App, Web Server w/ GUI Admin Client, Access Point Manager, Drag & Drop File Manager, OTA, Performance Metrics, Web Socket Comms, Easy App API, All running on ESP8266...
Even usable on ESP-01S - Quickest Start Guide


   
Ron reacted
ReplyQuote
robotBuilder
(@robotbuilder)
Noble Member
Joined: 3 years ago
Posts: 1556
 

This is easier to read and why does the other method add another CRLF to the one already there.

 

// RECORD Code from DroneBot Workshop 2019
/*
  Servo position recorder
  servo-record.ino
  Records servo movements on SD card
  Displays results on Serial Monitor
  DroneBot Workshop 2019
    https://dronebotworkshop.com 
*/


// Include libraries

#include <SPI.h>
#include <SD.h>
#include <Servo.h>

// CS pin for SD Card Module
const int chipSelect = 4;

// Analog pin for potentiometer
int analogPin = A0;
int analogPin1 = A1;
int analogPin2 = A2;
int analogPin3 = A3;

// Integer to hold potentiometer value
int val1 = 0;
int val2 = 0;
int val3 = 0;
int val4 = 0;

// Create a Servo object
Servo myservo;
Servo myservo2;
Servo myservo3;
Servo myservo4;

void setup() {
  // Open serial communications and wait for port to open:
  Serial.begin(9600);
  while (!Serial) {
    ; // wait for serial port to connect. Needed for native USB port only
  }
  Serial.print("Initializing SD card...");
  // see if the card is present and can be initialized:
  if (!SD.begin(chipSelect)) {
    Serial.println("Card failed, or not present");
    // don't do anything more:
    while (1);
  }

  Serial.println("card initialized.");

  // Attach servos to the servo object
  myservo.attach(9); 
  myservo2.attach(8); 
  myservo3.attach(7); 
  myservo4.attach(6);
}

 

void loop() {
  // make a string for assembling the data to log:
  String dataString = "";
  String dataString2 = "";
  String dataString3 = "";
  String dataString4 = "";

  // Read pot value and append to the string
  // Map to range of 0-180 for servo
    for (int analogPin = 0; analogPin < 4; analogPin++) {
      int sensor = analogRead(analogPin); 
      dataString += String(sensor);
      if (analogPin < 4) {
        dataString += ",";
      }
    }

    val1 = map(analogRead(analogPin), 0, 1023, 0, 180);
    dataString += String(val1);
    myservo.write(val1);
    val2 = map(analogRead(analogPin1), 0, 1023, 0, 180);
    dataString2 += String(val2);
    myservo2.write(val2);
    val3 = map(analogRead(analogPin2), 0, 1023, 0, 180);
    dataString3 += String(val3);
    myservo3.write(val3);
    val4 = map(analogRead(analogPin3), 0, 1023, 0, 180);
    dataString4 += String(val4);
    myservo4.write(val4);

  // Write to the servo
  // Delay to allow servo to settle in position
  myservo.write(val1);
  myservo2.write(val2);
  myservo3.write(val3);
  myservo4.write(val4);
  delay(15);

  // open the file. note that only one file can be open at a time,
  // so you have to close this one before opening another.
  File dataFile = SD.open("servopo1.txt", FILE_WRITE);

  // if the file is available, write to it:
  if (dataFile) {
    dataFile.println(dataString);
    dataFile.close();
    // print to the serial port too:
    Serial.println(dataString);
  }

  // if the file isn't open, pop up an error:
  else {
    Serial.println("error opening servopo1.txt");
  }
}


 

 

 

// PLAYBACK Code from DroneBot Workshop 2019:
/*
  Servo position playback
  servo-playback.ino
  Plays back servo movements from SD card
  Used with servo-record.ino
  Displays results on Serial Monitor
  DroneBot Workshop 2019
    https://dronebotworkshop.com 
*/

// Include libraries
#include <SPI.h>
#include <SD.h>
#include <Servo.h>
// CS pin for SD Card Module
const int chipSelect = 4;
// String to hold one line of text
String buffer;
// Create a Servo object
Servo myservo;
Servo myservo2;
Servo myservo3;
Servo myservo4;

void setup() {
  // Open serial communications and wait for port to open:
  Serial.begin(9600);
  while (!Serial) {
    ; // wait for serial port to connect. Needed for native USB port only
  }
  Serial.print("Initializing SD card...");

  // see if the card is present and can be initialized:
  if (!SD.begin(chipSelect)) {
    Serial.println("Card failed, or not present");
    // don't do anything more:
    while (1);
  }

  Serial.println("card initialized.");
  // Attach servo on pin 9 to the servo object
  myservo.attach(9); 
  myservo2.attach(8); 
  myservo3.attach(7);
  myservo4.attach(6);
  // open the file. note that only one file can be open at a time,
  // so you have to close this one before opening another.
  File dataFile = SD.open("servopo1.txt");
  // If the file is available, read it
  if (dataFile) {
    while (dataFile.available()) {
      // Write one line to buffer
      buffer = dataFile.readStringUntil('\n');
      // Print to serial monitor
      Serial.println(buffer);
      // Convert string to integer and position servo
      myservo.write(buffer.toInt());
      myservo2.write(buffer.toInt());
      myservo3.write(buffer.toInt());
      myservo4.write(buffer.toInt());
    delay(15);
    }
    dataFile.close();
  }

  // if the file isn't open, pop up an error:
  else {
    Serial.println("error opening servopo1.txt");
  }
}

void loop() {
}


 

 

 

 

 


   
ReplyQuote
Ron
 Ron
(@zander)
Famed Member
Joined: 2 years ago
Posts: 3432
 

@inq No doubt.

"Don't tell people how to do things. Tell them what to do and let them surprise you with their results.” - G.S. Patton, Gen. USA
"Never wrestle with a pig....the pig loves it and you end up covered in mud..." anon


   
ReplyQuote
Ron
 Ron
(@zander)
Famed Member
Joined: 2 years ago
Posts: 3432
 

@robotbuilder In the very deep dark recesses of my mind I seem to recall that conversion between windows and *nix causes that.

"Don't tell people how to do things. Tell them what to do and let them surprise you with their results.” - G.S. Patton, Gen. USA
"Never wrestle with a pig....the pig loves it and you end up covered in mud..." anon


   
ReplyQuote
HinoBot
(@hinobot)
Active Member
Joined: 4 months ago
Posts: 7
Topic starter  

@inq Thank you, this was our first post, I'll get it right the next time.  The reason I am using the two (2) separate codes is because we don't know any better 😆.  We are working off the original codes created by DroneBot Workshop 2019 by adding the extra servos to the best of our knowledge.  Using the code for one servo works great, but as we expand the code. it gets jumbled.  As far as timing, we just need to control the servos in real time, and then play them back in the same pattern.  We are building a pair of eyes that go in a painting for our haunted house.  The eyes remain closed for some time, open, move around for a bit, stare off for a moment, and then close.  The idea is that the pattern repeat over and over again.  Think of a tape recorder that captures information in real time and then plays it back again.

We attached a sketch of the setup.

4 servos

 

@zander Got it, thanks!

@robotbuilder Thanks for the repost!

 


   
ReplyQuote
Ron
 Ron
(@zander)
Famed Member
Joined: 2 years ago
Posts: 3432
 

@hinobot Ok, so what is the purpose of the SD card?

"Don't tell people how to do things. Tell them what to do and let them surprise you with their results.” - G.S. Patton, Gen. USA
"Never wrestle with a pig....the pig loves it and you end up covered in mud..." anon


   
ReplyQuote
Will
 Will
(@will)
Noble Member
Joined: 1 year ago
Posts: 2124
 

@hinobot 

You won't achieve the synchronicity you desire unless you specifically add timing marks to your sketch and write them to the SD card as well as the values because the length of time the processor will take for each "line" that it has to collect, adapt and store to the SD card is different from the length of time required to read and process the line  from the SD card.

If you want replay and display to match, then you need to add code to set and utilize those markers.

Experience is what you get when you don't get what you want.


   
ReplyQuote
Ron
 Ron
(@zander)
Famed Member
Joined: 2 years ago
Posts: 3432
 

@will @hinobot I don't understand the reason for the card, just write the sketch and it loops forever, no need to store it on a card unless I missed something.

"Don't tell people how to do things. Tell them what to do and let them surprise you with their results.” - G.S. Patton, Gen. USA
"Never wrestle with a pig....the pig loves it and you end up covered in mud..." anon


   
ReplyQuote
Will
 Will
(@will)
Noble Member
Joined: 1 year ago
Posts: 2124
 

@zander 

As I understand it, they want to record a series of 4-tuples corresponding to the position of 4 pots. These pot values are then printed off to the SD and also to the serial display and the corresponding angle is sent off to the 4 servos.

At some time(s) later, the file will be read and the 4-tuples restored to numeric values and again be used to set the angles on the servos.

So the first sketch captures and stores the servo positions to the SD card and the second sketch reads the SD card contents and replays the servo movements.

At least, that's my take on it 🙂

Experience is what you get when you don't get what you want.


   
ReplyQuote
Page 1 / 4