Problem storing text in array

Hello,

I’m still working on my CarDuino, and I have run into a problem that I’m a bit perplexed about.

The CarDuino trip computer will display warning messages on a 128x128 Adafruit TFT display which are loaded from MicroSD card onto the screen. It’s connected to a number of different sensors which will pick up faults from broken light bulbs to low engine coolant level etc. I’ve created a two dimensional array table which contains information about when the error occurred, how long to display the error message, how many times a sensor has to report a fault before a warning is given out, etc.

Here’s my array:

// Error Table - this is central to the system's error reporting!
unsigned long errorTable[13][11] = {

  // Warning screen file name | Timestamp  | error counter | max. error counter | tone pitch (Hz) | beep duration (ms) | small PROGMEM icon name | alt text | reminder interval (ms)| beep once (0) or repeatedly (1) | beeped once

  {"sdcard",      0,  0,  1,  1200, 10000,  0,                "CARD FAIL",  0,        0,  0},   // data set # 0
  {"fuel.raw",    0,  0,  5,  1000, 5000,   "fuel_small",     "low fuel",   600000,   0,  0},   // 1
  {"oiltemp.raw", 0,  0,  3,  1200, 10000,  "oiltemp_small",  "oil temp",   300000,   1,  0},   // 2
  {"cool.raw",    0,  0,  3,  1200, 10000,  "cool_small",     "cool temp",  300000,   1,  0},   // 3
  {"lowcool.raw", 0,  0,  3,  1200, 10000,  "cool_small",     "cool levl",  120000,   1,  0},   // 4
  {"frost.raw",   0,  0,  3,  1000, 5000,   "frost_small",    "frost",      0,        0,  0},   // 5
  {"wash.raw",    0,  0,  5,  1000, 5000,   0,                "scrn wash",  600000,   0,  0},   // 6
  {"head.raw",    0,  0,  3,  1000, 5000,   "light_small",    "headlamps",  600000,   0,  0},   // 7
  {"doorlid.raw", 0,  0,  2,  1000, 5000,   0,                "open lids",  300000,   0,  0},   // 8
  {"tail.raw",    0,  0,  3,  1000, 5000,   "light_small",    "tail lite",  1200000,  0,  0},   // 9
  {"brake.raw",   0,  0,  2,  1000, 5000,   "light_small",    "brake lite", 1200000,  0,  0},   // 10
  {"top.raw",     0,  0,  2,  1000, 5000,   0,                "check top",  600000,   1,  0},   // 11
  {"volt.raw",    0,  0,  2,  1000, 5000,   "volt_small",     "low voltg",  600000,   1,  0},   // 12

};

This is working well mostly. The following code is used in the loop() section of the sketch to process errors and faults:

// Displaying errors one by one...

  for (byte count = 0; count < 13; count ++) {

    if (errorTable[count][2] >= errorTable[count][3] ) {

      unsigned long errorTimestamp = millis();
      unsigned long errorTimeDifference = errorTimestamp - errorTable[count][1];

      if (!errorTable[count][1] || errorTimeDifference >= errorTable[count][8]) {

        errorTable[count][1] = errorTimestamp;
        tft.fillScreen(ST7735_WHITE);

        //Display image from MicroSD card
        drawRaw(errorTable[count][0], 0, 0, 128, 128);

        // If MicroSD card fails
        if (readError) {

          // Display Alt Warnings
          drawProgmemIcon(warning, 44, 30, 40, 37);

          tft.setTextColor(ST7735_RED);
          tft.setTextSize(2);
          tft.setCursor(12, 100);
          tft.print(errorTable[count][7]);

          readError = false;

        }

        tone(tonePin, errorTable[count][4]);
        delay(errorTable[count][5]);
        noTone(tonePin);
 
      }
    }
  }

Using this bit of code, I can without a problem load .raw images from the MicroSD card which represent different kinds of error warnings. But somehow, I can’t use the text stored in errorTable[count][7] to display alternative text warnings. All I get on the screen is some kind of numerical integer value. The “small icons” are the names of miniature 20x20px icons stored in PROGMEM which will flash at the bottom of the screen when a fault still persists after the display has returned to one of the main screens where fuel comsumption data and other things are displayed. I haven’t tried those yet, not sure if I will run into the same problem there.

Changing the array type to uint16_t (and dividing all the millisecond values by 1000 to make them fit into that number space) had no effect on the problem. I might still do that again to save SRAM space as the project progresses, but for now, I see no harm in leaving it as “unsigned long”.

You can't mix data types in the one array like that. If you declare it with

unsigned long arrayName

then every element should be an unsigned long.

There are ways to do what you want: maybe an array of structs or use separate arrays for the different columns in your table. eg you could have a 1-D array for altText etc

well it did seem counterintuitive to begin with to me that the same array could both hold the alphanumeric file names of the .raw files and also purely numerical values; but it does work for the .raw file name.

Btw, I just tried drawing the PROGMEM icons using this table, and it also didn't work.

So maybe for simplicity's sake, it's best to just put all the text in a separate two-dimensional array table. And put the .raw file names in that table as well.

What's the best data type to use for that... to have errorTable[count][0], errorTable[count][6] and errorTable[count][7] all in one array?

An array of struct’s

http://playground.arduino.cc/Code/Struct

If you do use an array of structures, you might want to calculate the resulting size and I think it's going to be fairly large. I'm not sure you'll have enough memory on an Uno.

Yes, sorry, forgot to mention that this code wil run on a 1284P-PU. It's got plenty more memory to go around than the Uno, so it should be fine.

Is there no way I could just put all the "text" colums into a simple (second) array table? I've spent tonight reading up on structs, but I haven't been able to really get my head around the concept.

#define NUM_ENTRIES(ARRAY)      (sizeof(ARRAY) / sizeof(ARRAY[0]))

struct error_entry_t
{
    char*           pszFilename;
    unsigned long   time_stamp;
    size_t          countErrors;
    size_t          countErrorsMax;
    unsigned long   tone_pitch;
    unsigned long   tmsBeep;
    char*           pszFilenameIcon;
    char*           pszAltText;
    unsigned long   tmsReminder;
    uint8_t         beep_type;
    uint8_t         did_beep;
};

error_entry_t errorTable[] =
{
    { "sdcard",      0,  0,  1,  1200, 10000,  0,                "CARD FAIL",  0,        0,  0 }   // data set # 0
  , { "fuel.raw",    0,  0,  5,  1000, 5000,   "fuel_small",     "low fuel",   600000,   0,  0 }   // 1
  , { "oiltemp.raw", 0,  0,  3,  1200, 10000,  "oiltemp_small",  "oil temp",   300000,   1,  0 }   // 2
  , { "cool.raw",    0,  0,  3,  1200, 10000,  "cool_small",     "cool temp",  300000,   1,  0 }   // 3
  , { "lowcool.raw", 0,  0,  3,  1200, 10000,  "cool_small",     "cool levl",  120000,   1,  0 }   // 4
  , { "frost.raw",   0,  0,  3,  1000, 5000,   "frost_small",    "frost",      0,        0,  0 }   // 5
  , { "wash.raw",    0,  0,  5,  1000, 5000,   0,                "scrn wash",  600000,   0,  0 }   // 6
  , { "head.raw",    0,  0,  3,  1000, 5000,   "light_small",    "headlamps",  600000,   0,  0 }   // 7
  , { "doorlid.raw", 0,  0,  2,  1000, 5000,   0,                "open lids",  300000,   0,  0 }   // 8
  , { "tail.raw",    0,  0,  3,  1000, 5000,   "light_small",    "tail lite",  1200000,  0,  0 }   // 9
  , { "brake.raw",   0,  0,  2,  1000, 5000,   "light_small",    "brake lite", 1200000,  0,  0 }   // 10
  , { "top.raw",     0,  0,  2,  1000, 5000,   0,                "check top",  600000,   1,  0 }   // 11
  , { "volt.raw",    0,  0,  2,  1000, 5000,   "volt_small",     "low voltg",  600000,   1,  0 }   // 12

};

void loop()
{    }

void setup()
{
    Serial.begin(9600);
    
    for ( size_t i = 0; i < NUM_ENTRIES(errorTable); i++ )
    {
        error_entry_t& entry = errorTable[i];
        
        Serial.print(entry.pszFilename);        Serial.print(", ");
        Serial.print(entry.time_stamp);         Serial.print(", ");
        Serial.print(entry.countErrors);        Serial.print(", ");
        Serial.print(entry.countErrorsMax);     Serial.print(", ");
        Serial.print(entry.tone_pitch);         Serial.print(", ");
        Serial.print(entry.tmsBeep);            Serial.print(", ");
        Serial.print(entry.pszFilenameIcon);    Serial.print(", ");
        Serial.print(entry.pszAltText);         Serial.print(", ");
        Serial.print(entry.tmsReminder);        Serial.print(", ");
        Serial.print(entry.beep_type);          Serial.print(", ");
        Serial.println(entry.did_beep);
    }
}

I like to think of structure arrays as arrays for adults. A standard array is simply a collection of variables of one data type that share a common name. A structure allows you to group different types of data under a single name. Your structure might be something like:

struct carData {
   char fileName[13];            // Max 12 chars plus null
   unsigned long timeStamp;
   int errorCounter;
   int maxErrorCounter;
   int tone;
   int beepDuration;
   char iconName[11];       // Max 10 chars plus null
   char altTest[11];           //           "
   int reminderInterval;
   int howOftenToBeep;
} carData[20];

//  some code...
// Now stuff the Audi data into the structure...

   strcpy(carData[0].fileName, "Audi");
   carData[0].errorCounter = 0;
   carData[0].tone = 700;
// etc

A structure lets you take something that takes different data types to describe, but gather them together in a more cohesive manner. Personally, I would think it makes sense to keep everything together rather than trying to manage parallel arrays.

hm... I see... I'm gonna have read up a bit more on all that to work it out. Still struggling with the concept... :frowning:

My basic idea, as you probably have guessed, is to have kind of a rudimentary miniature database of sorts within my sketch code. I used to do a bit of MySQL programming, and a MySQL database itself doesn't really complain if you put different kinds of variable types into it... it is only when data is retrieved from a database that a variable type becomes relevant. So I guess I was kind of mistaken as to what can be done within an Arduino sketch to mimic some of that functionality.

Anyway, the plan is to have a similar array later on as the array I posted above, which will store fuel consumption and driving time data while the engine is running. So that's going to consist mainly of floats and integers. Then when the engine is turned off, the trip computer will write that array's data, the bits that continuously change anyway, into EEPROM, from which they will be retrieved again the next time the engine is running. The CarDuino will be connected straight to the battery for this, and will sense the ignition's status on a digital input pin (on/off).

(Much, much later, not before the entire code is running stable anyway, the plan is also to pass the data from those array tables on to my smartphone via Bluetooth BLE ... and to make a little smartphone app that will collect and store that data. So that for example you will know how much fuel is left in your tank without even having to get in your car to have a look at the fuel gauge... :smiley: )

and a MySQL database itself doesn't really complain if you put different kinds of variable types into it.

It does if you try to store "Fred" in a field defined as an int.

PaulS:
It does if you try to store "Fred" in a field defined as an int.

true; my point was, with MySQL, you can store different data types in the same table.

true; my point was, with MySQL, you can store different data types in the same table.

That's true with any relational database application. But, irrelevant. The thing you can not do is store different data bytes in the same column. A column has a type. The data stored in the column must have that type.

The same is true in C and C++. You can not store a string in a long. You can't store a float in a long.

Having looked into it a bit more, structs do not seem to be the way forward for this project.

In the interest of keeping the code simple, managable and easily referenceable, I've decided to just divide my error table information up into three different arrays:

// Error tables - these are central to the system's error reporting!

unsigned long errorTable[13][8] = {

  //Timestamp  | error counter | max. error counter | tone pitch (Hz) | beep duration (ms) | reminder interval (ms)| beep once (0) or repeatedly (1) | beeped once

  {0,  0,  1,  1200, 10000,  0,        0,  0},   // 0 - sdcard
  {0,  0,  5,  1000, 5000,   600000,   0,  0},   // 1 - fuel
  {0,  0,  3,  1200, 10000,  300000,   1,  0},   // 2 - oil temp
  {0,  0,  3,  1200, 10000,  300000,   1,  0},   // 3 - coolant temp
  {0,  0,  3,  1200, 10000,  120000,   1,  0},   // 4 - low coolant
  {0,  0,  3,  1000, 5000,   0,        0,  0},   // 5 - frost
  {0,  0,  5,  1000, 5000,   0,        0,  0},   // 6 - screen wash
  {0,  0,  3,  1000, 5000,   600000,   0,  0},   // 7 - headlamps
  {0,  0,  2,  1000, 5000,   300000,   0,  0},   // 8 - doors and lids
  {0,  0,  3,  1000, 5000,   1200000,  0,  0},   // 9 - tail lights
  {0,  0,  2,  1000, 5000,   1200000,  0,  0},   // 10 - brake lights
  {0,  0,  2,  1000, 5000,   600000,   1,  0},   // 11 - top
  {0,  0,  2,  1000, 5000,   600000,   1,  0},   // 12 - low voltage

};

char* errorTableText[13][2] = {

  // Warning screen file name | alt text

  {"sdcard",        "CARD FAIL" },    // data set # 0
  {"fuel.raw",      "low  fuel" },    // 1
  {"oiltemp.raw",   "oil temp"  },    // 2
  {"cool.raw",      "cool temp" },    // 3
  {"lowcool.raw",   "cool levl" },    // 4
  {"frost.raw",     "  frost"   },    // 5
  {"wash.raw",      "scrn wash" },    // 6
  {"head.raw",      "headlamps" },    // 7
  {"doorlid.raw",   "open lids" },    // 8
  {"tail.raw",      "tail lite" },    // 9
  {"brake.raw",     "brake lite"},    // 10
  {"top.raw",       "check top" },    // 11
  {"volt.raw",      "low voltg" },    // 12

};

const uint16_t* const errorSmallIcons[13] = {sdcard_small, fuel_small, oiltemp_small, cool_small, cool_small, 0, 0, light_small, 0, light_small, light_small, 0, volt_small};

This is working; it has fixed my initial problem of not being able to correctly display the "alt text" warnings on the screen when the SD card fails. It may not be the most elegant way of doing this, but it gets the job done. Once the CarDuino is installed in my car and displaying all the values neatly on the TFT screen, it won't matter anymore anyway if I used structs or a number of different tables.

I might slim down the errorTable a bit to integers, because unsigned long is really only needed for the reminder intervals and the time stamp; both of which could really be divided by 60,000 because with the way my code will operate, full minutes will be all that matters, not milliseconds. And maybe I could divide the tone pitch information by ten and the duration by 1000, so then I would really only need to make the table a "byte". My error processing code will then simply multiply all those byte values by the proper factors again.

carguy:
Having looked into it a bit more, structs do not seem to be the way forward for this project.

In the interest of keeping the code simple, managable and easily referenceable, I've decided to just divide my error table information up into three different arrays:
...
...

Just because you probably don't understand them, it does not mean that structs ain't the way forward. You seem to have made your code more complex than strictly necessary.

Would you create two tables in MySql, one with fields for unsigned long and one with fields for text?

More chances of bugs splitting related things apart.

sterretje:
Would you create two tables in MySql, one with fields for unsigned long and one with fields for text?

No; but Arduino ain't MySQL.

But this code gets the job done, and before I spend more time trying to get my head around structs, why not stick with it the way it is now. It will make no difference one way or the other in terms of the final product of this CarDuino. What I will be looking at when it's installed in my car is the TFT display, and not lines of code. And you are going to have to admit that the concept of structs isn't as immediately accessible to somebody who hasn't worked with them as, for example, two-dimensional arrays. shrug

There is no difference between a mysql table and a struct.

And what is easier to understand and maintain
This

errorTable[0][4]

Or

errorTable[0].beepduration

You we're given two examples, but ok, it's your code

Well, as you say in your own signature -- If you understand an example, use it.
If you don't understand an example, don't use it.

Better to employ a slightly less elegant programming concept that works for you, than one which you struggle to get your head around. No shame in that.

Maybe I'll have another go at it in due time, but at this point in time, for me it's enough to have something that works and gives me the desired results, so that for now, I can move on to other areas of this project.

carguy:
Well, as you say in your own signature -- If you understand an example, use it.
If you don't understand an example, don't use it.

You got me there :smiley: