Random chars while using a two-dimensional array

Hi there

I’m trying to read a text file, hosted in an SD card and which contains tab separated ints, and then store its values in a two-dimensional array.

I’m getting unexpected results which are probably linked to my very basic command of the language (this is for a scheduled irrigation system, my first Arduino project ever).

  • If I check the array only once, at the start of the loop, timings[6][4] equals 30 (right), then freezes for a while and then prints out of large mutating list of numbers - wrong
  • If I check the array once, at the end of the loop, timings[6][4] equals 12592 - wrong
    • If I check the array twice, one at the start of the loop and once (or more) at the en of the loop, timings[6][4] equals 30 - right

Within the file (attached), I’m using a double tab to separate the columns and the first row is trivial, as I’m only using it for visual reference.

And here goes the full code as of now, reproducing the second error:


//Incluir librerías
#include <SD.h>
#include <Wire.h>
#include <TimeLib.h>
#include <DS1307RTC.h>
#include <LiquidCrystal_I2C.h>
#include <AT24CX.h>

//Inicializar pantalla
LiquidCrystal_I2C lcd(0x3F, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);


//Constantes y variables del control de riego
const int valvenumber = 4; //Número de válvulas de riego
const int shiftnumber = 6; //Número de turnos de riego
const char suncalDir[8] = "suncal/"; //Directorio del archivo de datos de calendario
const char extdata[5] = ".txt"; //Extensión de los archivos de datos
char calPath[17]; //Ruta del calendario + Array para controlar instrucciones de riego
bool scheduled = 1; //Flag que indica si el riego programado está o no activo

//Declarar el array de timings de riego:
//Rows = shiftnumber / Cols = valvenumber
int timings[shiftnumber][valvenumber]{
  {0,0,0,0},
  {0,0,0,0},
  {0,0,0,0},
  {0,0,0,0},
  {0,0,0,0},
  {0,0,0,0}
};

//Declarar el array de tipos de válvula
char valves[6];

//Constantes y variables del control del RTC
AT24C32 EepromRTC;
time_t utc = now();
time_t local;
byte DST; //Flag del cambio de hora europeo

//Constantes y variables del control manual de la bomba
int pumpState = LOW; 
int lastButtonState = LOW;
int buttonState; 
int reading;
long lastDebounceTime = 0;
long debounceDelay = 50;

//Pins de control
const int pumpButton = 15; //Pin que controla el botón de override de la bomba
const int pumprelaypin = 3; //Pin que controla el relé de la bomba


void setup() {
  Serial.begin(9600);

  // Inicializar la tarjeta
  Serial.println("Iniciando SD ...");
  Serial.println();
  pinMode(10, OUTPUT);
  digitalWrite(10,HIGH);
  if (!SD.begin(10))
  {
    lcd.begin(16,2);
    lcd.clear();
    lcd.print("SD Module ERROR");
  }
  else{
    //Éxito: Arrancar pantalla
    lcd.begin(16,2);
    lcd.clear();
    lcd.print("SD Module OK");
  }

  //Inicializar el reloj
  setSyncProvider(RTC.get);

  //Arrancar el pin que controla el botón de la bomba
  pinMode(pumpButton, INPUT_PULLUP);

  //Arrancar el pin del relé que controla la bomba
  pinMode(pumprelaypin, OUTPUT);

  /*
  -----------------------------------------------------
  Definir las variables de riego
  ----------------------------------------------------- 
  */

  //Definir los tipos de válvulas
  valves[0] = '#'; //Dígito de control
  valves[1] = 'G'; //Válvula 1
  valves[2] = 'P'; //Válvula 2
  valves[3] = 'G'; //Válvula 3
  valves[4] = 'G'; //Válvula 4
  valves[5] = '\0'; //Null termination

  /*
  -----------------------------------------------------
  Parsing de las instrucciones de riego
  ----------------------------------------------------- 
  */

  //Abrir y manipular el archivo de configuración de riego
  File configFile;
  configFile = SD.open("config/timings.txt");
  
  byte readchar;
  int charcounter = 0;
  int shiftcounter = 0;
  int valvecounter = 0;
  int tens = 0;
  int units = 0; 

  while (configFile.available()) {
      readchar = configFile.read();
      //Ignorar line feeds [10] y retornos de carro [13]
      if(readchar != 10 && readchar != 13 && readchar != 9){
          //Ignorar la primera línea
          if(charcounter > (4 * valvenumber)-1){
            //Parsing del número
            if(charcounter % 2 == 0){ //Decenas
              tens = ((char)readchar - '0') * 10;
            }
            else{ //Unidades
              units = ((char)readchar - '0');
              int total = tens + units;
              valvecounter++;
              timings[shiftcounter][valvecounter] = total;
            }
          }
      }
      if((charcounter > (4 * valvenumber)-1) && (charcounter % (4 * valvenumber) == 0)){shiftcounter++; valvecounter = 0;}
      charcounter++;
    }
 
}

void loop() {


//  First check (Uncomment this block to get rid of the error)

//  Serial.print(timings[6][4]); //Cuarta válvula del sexto turno
//  Serial.print(" | ");
//  Serial.print(valves[2]); //Segunda válvula
//  Serial.println(" | ");


  reading = digitalRead(pumpButton);
   if (reading != lastButtonState) {
      lastDebounceTime = millis();
      lastButtonState = reading;
   } 

   if ((millis() - lastDebounceTime) > debounceDelay) {
       if (buttonState != lastButtonState) {
           buttonState = lastButtonState;
           if (buttonState == LOW) {
                 pumpState = !pumpState;
                 digitalWrite(pumprelaypin, pumpState);
                 
                 //Desactivar el riego programado si el relé está activo
                 if(pumpState == 1){
                    scheduled = 0;
                 }
                 //Reactivar el riego programado si el relé está inactivo
                 else{
                    scheduled = 1;
                 }
           }
       }
   }

  //Aplicar la programación si NO hay control manual
  if(scheduled == 1){

  local = SetDateTime(2017, 11, 6, 0, 0, 0);
  
  int currdowint = weekday(local);
  int curryearint = year(local);
  int currmonthint = month(local);
  int currdayint = day(local);
  int currhourint = hour(local);
  int currminint = minute(local);
  int currsecint = second(local);

  //A las 12 de la noche, cargar la configuración para todo el día
  if (currhourint == 0 && currminint == 0 && currsecint == 0) {

    //Si es uno de enero, calcular el año y reescribirlo en la EEPROM
    if(currdayint == 1 && currmonthint == 1){
      
        //Casting curryear
        char curryear[5];
        curryear[0] = '0' + (curryearint/1000);      // Miles
        curryear[1] = '0' + ((curryearint/100)%10);  // Cientos
        curryear[2] = '0' + ((curryearint/10)%10);   // Decenas
        curryear[3] = '0' + (curryearint%10);        // Unidades
        curryear[4] = '\0'; // Terminación

        //Escribir el año en la EEPROM
        EepromRTC.writeChars(2, curryear, 5);    
       
    }

    //Recuperar el año grabado en la EEPROM
    char yearROM[5];
    EepromRTC.readChars(2,yearROM,5);

    //Calcular la programación de riego del día a partir de los datos de la tarjeta
    File calFile;
    strcat(calPath, suncalDir);
    strcat(calPath, yearROM);
    strcat(calPath, extdata);
    calFile = SD.open(calPath);
    calPath[0] = (char)0;//Destruir ruta
    
    
  }
  
//  Second check

  Serial.print(timings[6][4]); //Cuarta válvula del sexto turno
  Serial.print(" | ");
  Serial.print(valves[2]); //Segunda válvula
  Serial.println(" | "); 
  
  }//endif flag scheduled


}


time_t SetDateTime(int y, int m, int d, int h, int mi, int s)
{
  tmElements_t Fecha;
  Fecha.Second = s;
  Fecha.Minute = mi;
  Fecha.Hour = h;
  Fecha.Day = d ;
  Fecha.Month = m ;
  Fecha.Year = y - 1970 ;
  return makeTime(Fecha);
}

Thanks in advance

z.

P.S. Several other things happen in the code (interaction with an LCD screen, an RTC unit and a button which manually activates a relay). Some or most of them might be unrelated to the issue, but I’m leaving them in just in case I am missing something (I wouldn’t be surprised if I did). On the other hand, any suggestions for achieving the same functionality with alternative and more efficient methods are more than welcome, but please bear in mind that I’m still a bit of a toddler.

timings.txt (110 Bytes)

zugunruhes:
I’m getting unexpected results which are probably linked to my very basic command of the language (this is for a scheduled irrigation system, my first Arduino project ever).

I’d parse the file with a much more structured approach, since you absolutely know what the file contains…

Something like this (not tested).

  while (char myChar = configFile.read() != '\n');  // consumir la primera linea hasta el retorno del carro
  for (int i = 0; i < shiftnumber + 1; i++)
  {
    for (int j = 0; j < valveNumber, i++)
    {
      char linea[16];  // verificar este tamaño
      for (int k = 0; k < 16, k++)
      {
        char c = configFile.read();
        if (c == '\n')
        {
          linea[k] = '\0';
          continue;  // tememos toda la linea
        }
        else 
        {
          linea[k] = c;
        }
      }
      strtok(linea, '\t');
      for (int k = 0; k < 4; k++)
      {
        timings[i][j] = atoi(strtok(NULL, '\t'));
      }
    }
  }

Thanks a lot for your code. Your parsing method is so much better than mine (and has helped me to learn a couple of c essentials).

However, after correcting a few minor typos and tweaking it a bit, it doesn’t seem to work either. When applied to a stripped down version of my file it fills the 2-D matrix with zeros, instead of the appropriate values.

Here it goes:


//Incluir librerías
#include <SD.h>

//Constantes y variables del control de riego
const int valvenumber = 4; //Número de válvulas de riego
const int shiftnumber = 6; //Número de turnos de riego

//Declarar el array de timings de riego:
//Rows = shiftnumber / Cols = valvenumber
int timings[shiftnumber][valvenumber]{
  {0,0,0,0},
  {0,0,0,0},
  {0,0,0,0},
  {0,0,0,0},
  {0,0,0,0},
  {0,0,0,0}
};

void setup() {
  
  Serial.begin(9600);

  // Inicializar la tarjeta
  Serial.println("Iniciando SD ...");
  Serial.println();
  pinMode(10, OUTPUT);
  digitalWrite(10,HIGH);
  SD.begin(10);

  //Abrir y manipular el archivo de configuración de riego
  File configFile;
  configFile = SD.open("config/timings.txt");

  
  while (char myChar = configFile.read() != '\n');  // consumir la primera linea hasta el retorno del carro
  for (int i = 1; i < shiftnumber + 1; i++)
  { 
    for (int j = 1; j < valvenumber; j++)
    {
      char linea[16];  // verificar este tamaño
      for (int k = 0; k < 16; k++)
      {
        char c = configFile.read();
        if (c == '\n')
        {
          linea[k] = '\0';
          continue;  // tenemos toda la linea
        }
        else 
        {
          linea[k] = c;
        }
        strtok(linea, "\t");
        for (int l = 0; l < 3; l++)
        {
          timings[i][j] = atoi(strtok(NULL, "\t"));
        }
      }
    }
  }
}

void loop() {
  for (int i = 0; i < shiftnumber; i++)
  { 
    for (int j = 0; j < valvenumber; j++)
    {
      Serial.println(timings[i][j]);
    }
  }
}

P.S. A straight printout of “linea” within the parsing loop already shows that it goes berserk.

zugunruhes:
P.S. A straight printout of "linea" within the parsing loop already shows that it goes berserk.

did you try:

if (c == '\n' or c == '\r') // buscar nueva línea o retorno de carro

and also check to see if the first line is being consumed properly:

while (char myChar = configFile.read() != '\n')
{
  Serial.print(myChar);  //imprimir caracteres mientras están destruidos
}
Serial.println();

SOLVED!

There were several problems that needed tackling and that somehow I managed to debug (despite my very basic skills, that is):

  • Mixing of line feeds and carriage returns in the text file.
  • Incorrect nesting of the for loops.
  • Insufficient cols in the 2D array (I wanted to leave the first of them blank, in order to assign it to the master valve).
  • Not assigning the first item of strtok to the 2D array.

So here goes the final parsing:

File timingsFile;
  timingsFile = SD.open("config/timings.txt");

  char linea[valvenumber * 3];
  char c;
  int shiftcounter = 1;
  int charcounter = 0;
  
  while (char firstline = timingsFile.read() != '\n');  // Consumir la primera linea hasta el salto de línea
  while (timingsFile.available()) {
      c = timingsFile.read();
      if(c != '\n'){
        linea[charcounter] = c;
        charcounter++;
      }
      else{
        linea[valvenumber * 3] = '\0';
        timings[shiftcounter][1] = atoi(strtok(linea, "\t"));
        for (int k = 2; k <= valvenumber; k++)
        {
          timings[shiftcounter][k] = atoi(strtok(NULL, "\t"));
        }
        charcounter = 0;
        shiftcounter++;
      }
  }

Thanks soooo much BulldogLowell!!!