Storing an Int Array using 'Preferences' (ESP32)

I've checked several tutorials and they seem to contradict each other.
I have an array of integers. In order to store them in NVM, can I just write the whole array in one go with putUInt and read it with getUInt or do I need to manually store each element with a loop? Some example also use 'Byte' rather that Int... but a UInt is 4 bytes..

e.g. Can I do this:

   uint32_t pulseHistory [ 93 ] = { NVM_Store.getUInt("pulseHistory", 0)};  

(and does that zero above set the default to all elements if missing?)
Yes, I know my key name is the same as the variable; I like it.

or should it be bytes:

  NVM_Store.getBytes("pulseHistory", &pulseHistory, 93);

or am I going to have to do it manually with a loop, one element at a time:

  int count;
  for (int count = 0; count < 93; count++) {
      Serial.print(NVM_Store.getUInt("pulseHistory", 0));
      // Not sure where the count would go above :-)
      Serial.print(",");
  }

thanks kindly,

See this example of handling an integer array

#include <Preferences.h>
Preferences prefs;

uint32_t dataStore[3] = {12345, 45689, 78901};
uint32_t dataRetrieve[3];

void setup() {
  Serial.begin(115200);
  prefs.begin("IntegerArray"); //namespace
  //bytes can be put/get in namespace directly see prefs2struct example
  //prefs.putBytes("IntegerArray", (byte*)(&dataStore), sizeof(dataStore));
  //SavedIntegers is Key
  prefs.putBytes("SavedIntegers", (byte*)(&dataStore), sizeof(dataStore));
  prefs.getBytes("SavedIntegers", &dataRetrieve, sizeof(dataRetrieve));
  Serial.println(dataRetrieve[0]);
  Serial.println(dataRetrieve[1]);
  Serial.println(dataRetrieve[2]);
}

void loop() {}

Preferences, good stuff using them in my cam software..
To add I also found prefs.getBytesLength("key") very useful in determining if there is in fact a value saved..

good luck.. ~q

Thank you

Thanks for this, it has helped me a lot.
If I have to store an array of Bool, can I also use putBytes? If I am not mistaken a bool is the same thing as a Byte but with only two values, 0 and 1 right?
The putBool function, in contrast to the putBytes one, does not seem to give the option of indicating a size:

size_t putBool(const char* key, bool value);

size_t putBytes(const char* key, const void* value, size_t len);

Yes.

The .putBool function only stores one boolean value at the key similar to any of the other .putXXX functions.

1 Like

Perfect! Thank you! :beers:

I have read your post over and over again trying to understand what exactly happens. I hope it's ok if I ask two further questions to understand what is happening.

  1. I do not quite understand the mixing of datatypes. uint32_t to my understand is an unsigned long or unsigned int (16 or 32 bit) , while Bytes (which is put to the preferences) is only 8 bit. Why is the definition of the variable at the beginning of the code different from the putBytes?
  2. the put feature is mostly defined by only the name, the value and for Bytes data types the size. What exactly is the (byte*)(&dataStore) (so the byte with the * and the & in front of the value)?

Unfortunately I am unable to change the code to work with my variables.

for instance, I have the variables

int LatestM[5][4] = {
  { 1, 2, 3, 4 },
  { 5, 6, 7, 8 },
  { 0, 0, 0, 0 },
  { 0, 0, 0, 0 },
  { 0, 0, 0, 0 }
};

and
unsigned long epochTimeOffline = 0;

but I cannot store the variables to preferences using this code

#include <Preferences.h>
Preferences preferences;

int LatestM[5][4] = {
  { 1, 2, 3, 4 },
  { 5, 6, 7, 8 },
  { 0, 0, 0, 0 },
  { 0, 0, 0, 0 },
  { 0, 0, 0, 0 }
};

int LatestMR[5][4];


void setup() {
  Serial.begin(115200);
  Serial.println();

  preferences.begin("Pflanzen", false);  // false = read/write, true = read only.
  // Remove all preferences under the opened namespace
  //preferences.clear();


  //WRITING
  preferences.putInt("LatestM", (byte*)(&LatestM), sizeof(LatestM));
  Serial.println("Written!");

  delay(1000);

  //READING
  preferences.getInt("LatestM", &LatestMR, sizeof(LatestMR));
  Serial.printf("Current value: %u\n", LatestMR[0][2]);
  preferences.end();
  
}

void loop() {}

I get error:

no matching function for call to 'Preferences::putInt(const char [8], byte*, unsigned int)'

There are only certain basic data types which can be stored directly without casting to the underlying bytes.
https://docs.espressif.com/projects/arduino-esp32/en/latest/tutorials/preferences.html

Formore complex data items like arrays or structures, you need to cast the data to bytes and use the putBytes( ) and getBytes( ) functions.

//preferences.putInt("LatestM", (byte*)(&LatestM), sizeof(LatestM));
preferences.putBytes("LatestM", (byte*)(&LatestM), sizeof(LatestM));

I see, so when I usually define a variable, it reserves a certain area for that variable on the MCU that has specific characteristics. But the preferences does only allow a low complexity, for instance is limitied for arrays. So the code transforms the specific variable to an unspecific data (bytes). Meaning that the array variable at the beginning of the code can be any variable type, just when putting it in preferences it needs to transform to byte first which seems to be automatically done with putBytes. Also I just understood that "Bytes" of the ESP32 (variable size) is totally different from a "byte" type variable (1 byte size) of Arduinos, which confused me a lot.
So that is the reason why only with the putBytes function the size must be defined and not with the other putX functions.

My array only contains numbers between 0 and 150, which would make "int" not ideal I guess, and uchar would actually be better. A 5 by 4 array would have 20 bytes size when using uchar type variable then instead of 100 bytes of an 5 by 4 int array. Which I understand now.
So when defining the size in the putByte function, I could use either sizeof(LatestM) or 20*sizeof(uchar) to define the size if the array variable is an uchar type, but the sizeof(LatestM) would actually reduce storage size since the putByte is variabe and the defined variable could even be long long because only the actual needed storage will be used.

Still I am wondering about the (byte*)(&LatestM) of your code. What does the (byte*) and the & in front of the variable do? I have tested it just now with just the variable, so preferences.putBytes("LatestM", LatestM, 20*sizeof(int)); and it seems to work fine too. (Still had the int instead of uchar when tested).

In this case the & is the "address of" operator.

The code is telling the processor to go to the memory address of the variable and then to do something with a number of bytes located sequentially after that address.

Arrays are special and that the name of the array is a pointer to the memory address where the array is stored, so arrays don't need the & operator.