Reading SD card data 1 row per loop.

A little info on the basic project.

I have been getting an Arduino based data recorder set up, now it’s time to work on the reader. These are 2 separate systems that will take an input data set from one articulated arm with sensors, and then an SD card will be transferred to the second system that will use the data that was recorded to reproduce the associated movement on the powered arm. Essentially, puppeteering VIA SD card file.

I am sampling the positions at 1/2 second intervals, and I need to import the data at the same rate. Currently there are only 6 values, 0-1023(separated by a comma) per line. The number of values will increase greatly as the project grows , but I can add to it after I get things working .

I’m hoping for some advice on importing the data. Possibly into an array like this?

How to read numbers from a txt file on an SD card into an array!

What would be the best way to import and separate the data to be used in my code?

Will I be better off setting this up with millis instead of delays? I don’t know which one will give me more precise intervals of reading vs using data.

Hopefully I got my idea across. I am definitely not a coder, but I’m doing my best to learn and work this out myself. I just need some tips to point me in the right direction.

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


int actual1;
int demand1;
int actual2;
int demand2;
int actual3;
int demand3;
int actual4;
int demand4;
int actual5;
int demand5;
int actual6;
int demand6;
File myFile;
void data(int);

double Pk = 10; //speed it gets there
double Ik = 0;  // smaller number, more quickly controller reacts to load changes but the greater the risk of oscillations. 0 = off
double Dk = 1;  // bigger number, more the controller dampens oscillations higher can harm performance
double Setpoint1, Input1, Output1;
double Setpoint2, Input2, Output2;
double Setpoint3, Input3, Output3;
double Setpoint4, Input4, Output4;
double Setpoint5, Input5, Output5;
double Setpoint6, Input6, Output6;

PID PID1(&Input1, &Output1, &Setpoint1, Pk, Ik, Dk, DIRECT);
PID PID2(&Input2, &Output2, &Setpoint2, Pk, Ik, Dk, DIRECT);
PID PID3(&Input3, &Output3, &Setpoint3, Pk, Ik, Dk, DIRECT);
PID PID4(&Input4, &Output4, &Setpoint4, Pk, Ik, Dk, DIRECT);
PID PID5(&Input5, &Output5, &Setpoint5, Pk, Ik, Dk, DIRECT);
PID PID6(&Input6, &Output6, &Setpoint6, Pk, Ik, Dk, DIRECT);

void setup() {
  pinMode (A0, INPUT);  // Section-1 up down
  pinMode (A1, INPUT);  // Section-2 up down
  pinMode (A2, INPUT);  // Head up down
  pinMode (A3, INPUT);  // Section-1 left right
  pinMode (A4, INPUT);  // Section-2 left right
  pinMode (A5, INPUT);  // Head left right
  pinMode (2, OUTPUT);  // L298N-1 section 1 up down
  pinMode (3, OUTPUT);
  pinMode (4, OUTPUT);  // L298N-2 section 2 up down
  pinMode (5, OUTPUT);
  pinMode (6, OUTPUT);  // L298N-3 head up down
  pinMode (7, OUTPUT);
  pinMode (8, OUTPUT); // BTS7960-1 section 1 right left
  pinMode (9, OUTPUT);
  pinMode (10, OUTPUT); // BTS7960-2 section 2 right left
  pinMode (11, OUTPUT);
  pinMode (12, OUTPUT);  // BTS7960-3 head right left
  pinMode (13, OUTPUT); 
 

  
  Serial.begin(9600);
  
  PID1.SetMode(AUTOMATIC);
  PID1.SetOutputLimits(-255,255);
  PID1.SetSampleTime(10);
  PID2.SetMode(AUTOMATIC);
  PID2.SetOutputLimits(-255,255);
  PID2.SetSampleTime(10);
  PID3.SetMode(AUTOMATIC);
  PID3.SetOutputLimits(-255,255);
  PID3.SetSampleTime(10);
  PID4.SetMode(AUTOMATIC);
  PID4.SetOutputLimits(-255,255);
  PID4.SetSampleTime(10);
  PID5.SetMode(AUTOMATIC);
  PID5.SetOutputLimits(-255,255);
  PID5.SetSampleTime(10);
  PID6.SetMode(AUTOMATIC);
  PID6.SetOutputLimits(-255,255);
  PID6.SetSampleTime(10);
data();  
}


void loop() {

  unsigned long currentMillis = millis();
  
  // **  start PID 1 **
  actual1 = analogRead(A0);

  actual1 = map(actual1,0,1023,-255,255);

  Input1 = actual1;
  Setpoint1 = 0;

  PID1.Compute();
  Serial.print(Output1); 
  Serial.print(",");

if (Output1 < 0) {
  Output1 = abs(Output1);
  analogWrite(2,Output1);
  analogWrite(3,0);
}
else if (Output1 >= 0) {
  Output1 = abs(Output1);
  analogWrite(3,Output1);
  analogWrite(2,0);
}
else {
  analogWrite(2,0);
  analogWrite(3,0);
}

 // **  start PID 2 **
  actual2 = analogRead(A1);

  actual2 = map(actual2,0,1023,-255,255);

  Input2 = actual2;
  Setpoint2 = 0;

  PID2.Compute();
  Serial.print(Output2);
  Serial.print(","); 

if (Output2 < 0) {
  Output5 = abs(Output2);
  analogWrite(4,Output2);
  analogWrite(5,0);
}
else if (Output2 >= 0) {
  Output5 = abs(Output2);
  analogWrite(5,Output2);
  analogWrite(4,0);
}
else {
  analogWrite(4,0);
  analogWrite(5,0);
}

// **  start PID 3 **
  actual3 = analogRead(A2);

  actual3 = map(actual3,0,1023,-255,255);

  Input3 = actual3;
  Setpoint3 = 0;

  PID3.Compute();
  Serial.print(Output3);
  Serial.print(","); 
  
if (Output3 < 0) {
  Output3 = abs(Output3);
  analogWrite(6,Output3);
  analogWrite(7,0);
}
else if (Output3 >= 0) {
  Output3 = abs(Output3);
  analogWrite(7,Output3);
  analogWrite(6,0);
}
else {
  analogWrite(6,0);
  analogWrite(7,0);
}

// **  start PID 4 **
  actual4 = analogRead(A3);

  actual4 = map(actual5,0,1023,-255,255);

  Input4 = actual4;
  Setpoint4 = 0;

  PID4.Compute();
  Serial.print(Output4);
  Serial.print(","); 

if (Output4 < 0) {
  Output5 = abs(Output4);
  analogWrite(8,Output4);
  analogWrite(9,0);
}
else if (Output5 >= 0) {
  Output4 = abs(Output4);
  analogWrite(9,Output4);
  analogWrite(8,0);
}
else {
  analogWrite(8,0);
  analogWrite(9,0);
}
// **  start PID 5 **
  actual5 = analogRead(A4);

  actual5 = map(actual5,0,1023,-255,255);

  Input5 = actual5;
  Setpoint5 = 0;

  PID5.Compute();
  Serial.print(Output5); 
  Serial.print(",");

if (Output5 < 0) {
  Output5 = abs(Output5);
  analogWrite(10,Output5);
  analogWrite(11,0);
}
else if (Output5 >= 0) {
  Output5 = abs(Output5);
  analogWrite(11,Output5);
  analogWrite(10,0);
}
else {
  analogWrite(10,0);
  analogWrite(11,0);
}
// **  start PID 6 **
  actual6 = analogRead(A5);

  actual6 = map(actual6,0,1023,-255,255);

  Input6 = actual6;
  Setpoint6 = 0;

  PID6.Compute();
  Serial.println(Output6);

if (Output6 < 0) {
  Output6 = abs(Output6);
  analogWrite(12,Output6);
  analogWrite(13,0);
}
else if (Output6 >= 0) {
  Output6 = abs(Output6);
  analogWrite(13,Output6);
  analogWrite(12,0);
}
else {
  analogWrite(12,0);
  analogWrite(13,0);
}  
delay(50);
}

Why not store data as integers and get rid of the decoding during reading? 6 integers and one EOL, end of line.

So, are you talking about storing the integers on the SD card? Hmm.

I found a couple threads that sounds like what you suggest. I'll look into these closer over the next couple days.

Integers would be great, and make importing the values simple. Wondering how it would read line by line, and use the same integer tag, if I store it as integers?

Given a comment in one of those threads, it doesn't look like I would be able to easily modify the values in the file. That could be a problem, but I'd like to see how it works either way!

I don't catch Your question.
I wrote "line" but saying "record" or "structure", that contains the 6 integers, would be the same. The disadvantage is that You can only read the SD-file as integers maybe bytes and do some binary math. Normal text is written in ASCII.

Ok, that makes sense.

In that case, I'll have to go back to trying to read lines from a text file.

Will I be better off setting this up with millis instead of delays? I don't know which one will give me more precise intervals of reading vs using data.

Forget about using delay(), that will trash your PID loops since the delay functions blocks everything else from occuring while it is timing.

What I think you failed to take into account is that the default PID update rate is 100ms unless you over-ride it by calling the PID member function SetSampleTime() which I do not see in your code. What you need to understand from that statement is that if you desire to reproduce the actual movement that occurs, you'll need to sample at least every 100ms to keep up with the actual PID updates and your stated 500ms rate will not achieve that.

Fixed records store great in binary format.

Your 6 values of two bytes each makes for 12-byte records, always the same length. So to find the correct record is a matter of counting bytes.

The memcpy() function is a great help. Read the 12 bytes into a buffer, then use memcpy() to copy it to the struct or array that holds them.

WattsThat:
Forget about using delay(), that will trash your PID loops since the delay functions blocks everything else from occuring while it is timing.

Get rid of delays. Gotcha. :slight_smile:

WattsThat:
What you need to understand from that statement is that if you desire to reproduce the actual movement that occurs, you'll need to sample at least every 100ms to keep up with the actual PID updates and your stated 500ms rate will not achieve that.

I can up the sample rate easily enough, and it won't really affect anything other than the amount of stored data.

wvmarle:
Fixed records store great in binary format.

Your 6 values of two bytes each makes for 12-byte records, always the same length. So to find the correct record is a matter of counting bytes.

The memcpy() function is a great help. Read the 12 bytes into a buffer, then use memcpy() to copy it to the struct or array that holds them.

This could be a good way to go. I may try a test to see if I can work with this type of setup. Might be over my head, but never know. lol And, it may come down to whether I am able to manually edit the values in the stored file.

Either way, I'm going to push on with these suggestions (hoping for more) and keep trying to get this working!

MrDoggss:
And, it may come down to whether I am able to manually edit the values in the stored file.

You could if you really want... but it's not going to be easy... binary is not human readable, nor meant to be.

I feel that some of the advice you have been given so far would make the project difficult and over-complicated.

I would not recommend reading the data from SD card into an array. There is no benefit to this as far as I can see, and would quickly limit the number of rows of data you could use because of the limited size of ram memory on most Arduino, especially Uno and the like. You can simply have your code read the file from the SD card one line at a time every 500ms.

I would stick with text format, not binary, so that you can edit the file on pc using Excel or whatever.

You should definitely use millis(), not delay(). They are equal in terms of accuracy, but as already pointed out, delay() would prevent you from using PID and other functions. (The delay() function is literally a waste of time: it's function is to waste time.) However, I don't see a need to synchronise reading data rows from the SD card with the 100ms update rate of the PID functions. Using millis(), you can continue to have a 500ms interval for reading from the SD card while the PID functions operate at 100ms.

// learn about arrays, they let you take things like this:
int actual1;
int demand1;
int actual2;
int demand2;
int actual3;
int demand3;
int actual4;
int demand4;
int actual5;
int demand5;
int actual6;
int demand6;

// and turn them into this:
int actual[ 6 ];  // int array named actual with int elements [0],[1]...[5] that take 10 bytes total starting address is actual.
int demand[ 6 ]; // this one is named demand. The compiler sees 'demand' as a pointer to int variables at the array address.
//  so now you can access all of the actual values as actual[0], actual[1], actual[ variable ]; same with demand[x].

// Arduino double is 32-bit IEE sand&dirt FP. These could go into a 2D array, float muhFloats[6][3];
double Setpoint1, Input1, Output1;
double Setpoint2, Input2, Output2;
double Setpoint3, Input3, Output3;
double Setpoint4, Input4, Output4;
double Setpoint5, Input5, Output5;
double Setpoint6, Input6, Output6;
// if you read integers from your sensors then try and keep it integer except where it's printed in standard terms.
// if you do have a formula or part of any that can be precalculated for all reads and stored as a table in flash, 
// that can save major cpu cycles, let you up your read rate. Even just a table of sine values saves a series calculation
// done by sin(float), lots of cycles and stack ops. If you can use integers (type long are signed 32-bit integers, byte
// are unsigned 8-bit) to do the job it should run faster. 

// even PID objects can be arrayed but you might need to initialize each one in void setup()
PID PIDarray[ 6 ];
// then you could have one start PID function, one PID[ x ].compute, etc, to handle all of them.

...............................

MrDoggss:
I am sampling the positions at 1/2 second intervals, and I need to import the data at the same rate. Currently there are only 6 values, 0-1023(separated by a comma) per line. The number of values will increase greatly as the project grows , but I can add to it after I get things working .

I'm hoping for some advice on importing the data. Possibly into an array like this?

What would be the best way to import and separate the data to be used in my code?

What are you trying to do with the array once you've read the data into it? Write it back out on time to replicate motion? There will be lag and error in playback.

Will I be better off setting this up with millis instead of delays? I don't know which one will give me more precise intervals of reading vs using data.

Use micros() since millis() low 8 bits skips 6 values every 250 ms. If it matters, use micros() that counts by 4 us ticks, ms/250.
You maybe log to the nearest ms but use the micros() timer and /1000 to get ms.

Hopefully I got my idea across. I am definitely not a coder, but I'm doing my best to learn and work this out myself. I just need some tips to point me in the right direction.

You need to learn the basics of C just to save yourself uncountable hours of typing. How you look at data will change with experience and impossibles become possible once you figure out how.