0

I am looking to write a firmware that writes a structure to and reads it back from EEPROM memory. Converting the structure into a byte stream is working, however when I abstract the problem by putting the structure into a namespace, or call functions that pass pointers to the structure, the byte offsets seem to change by multiples of 2.

To create the memory map of the structure I am using the methods:

template <typename U>
void StoreStruct(U *structure, unsigned int location) {
  byte values[sizeof(U)];
  memcpy(values, structure, sizeof(U));
  ...
}


template <typename U>
void RetrieveStruct(U *result, unsigned int location) {
  byte readValues[sizeof(U)];
  ...
  memcpy(result, readValues, sizeof(U));
}

When calling these from a common namespace, they function as expected. When called from an architected set of namespaces, the byte offsets appear to move around. Is this expected?

Update:

Test Code:

eeprom.h:

#include <Wire.h>
#include <Arduino.h>

#ifndef EEPROM_H
#define EEPROM_H

struct ExampleStructure {
  const byte magic[4] = {192,43,67,181};
  byte settingsVersion[4] = {0, 0, 0, 1};
};

namespace EEPROM {

  const int memoryI2CAddress = 0x50;
  
  bool CompareArrays(byte *array1, byte *array2, unsigned int count) {
    bool result = true;
    for (unsigned int i = 0; i < count; i++) { result = result && (array1[i] == array2[i]); }
    return result;
  }
  
  void ReadBytes(byte *result, unsigned int count, unsigned int baseAddress) {

    while (Wire.available()) { Wire.read(); } //Clear buffer
        
    Wire.beginTransmission(memoryI2CAddress);
    Wire.write((byte)(baseAddress >> 8));
    Wire.write((byte)(baseAddress & 0xFF));
    Wire.endTransmission();

    unsigned int returnCount = Wire.requestFrom(memoryI2CAddress, count);
    for (unsigned int i = 0; i < returnCount; i++) { result[i] = Wire.read(); }
  }
  
  void WriteBytes(byte *values, unsigned int count, unsigned int address) {
    Wire.beginTransmission(memoryI2CAddress);
    Wire.write((byte)(address >> 8));
    Wire.write((byte)(address & 0xFF));
    Wire.write(values, count);
    Wire.endTransmission();
    
    delay(5);

    byte checkBytes[count];
    ReadBytes(checkBytes, count, address);
    if (!CompareArrays(values, checkBytes, count)) { Serial.println("Written bytes not written correctly."); }
    else { Serial.println("Everything went fine"); }
  }
  
  template <typename U>
  void StoreStruct(U *structure, unsigned int location) {
    byte values[sizeof(U)];
    memcpy(values, structure, sizeof(U));
    WriteBytes(values, sizeof(U), location);
  }

  void Initialise() {
    Wire.begin();
    ExampleStructure a;
    a.settingsVersion[0] = 0;
    a.settingsVersion[1] = 0;
    a.settingsVersion[2] = 0;
    a.settingsVersion[3] = 1;

    StoreStruct(&a, 896);    
  }
}

#endif

settings.h

#include "eeprom.h"

#ifndef SETTINGS_H
#define SETTINGS_H

namespace Settings {
 
  void Initialise() {

    ExampleStructure a;
    a.settingsVersion[0] = 0;
    a.settingsVersion[1] = 0;
    a.settingsVersion[2] = 0;
    a.settingsVersion[3] = 1;
    
    EEPROM::StoreStruct(&a, 896);
  }
}

#endif

Main file:

#include "eeprom.h"
#include "settings.h"

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

  Settings::Initialise();
  EEPROM::Initialise();
}

void loop() {}

Output:

Writing:
Written bytes not written correctly.
Writing:
Everything went fine
J Collins
  • 2,106
  • 1
  • 23
  • 30
  • C and C++ are two dfifferent languages, please precise which one you are coding with – Guillaume Petitjean Feb 11 '22 at 13:59
  • in C a structure fields are not necessarily contiguous. There might be "holes" in between, because the compiler aligns some of the fields. So you cannot assume a structure to be an array of bytes, and the structure length will be different on different platforms and compilers – Guillaume Petitjean Feb 11 '22 at 14:00
  • 1
    No, it's not expected. What is an "architected set" of namespaces, and how do you observe this phenomenon? Please post a [mcve]. – molbdnilo Feb 11 '22 at 14:02
  • There is nothing inherently wrong with the code you show. There may be issues with the code you are describing. A [mre] showing the behavior you describe may be necessary. – Drew Dormann Feb 11 '22 at 14:04
  • `memcpy` is only compatible with types that are trivially copyable. What types are you trying to serialize? – François Andrieux Feb 11 '22 at 14:09
  • @GuillaumePetitjean the code has not got a need for portability, but the idea that it might have holes or be non-contiguous is worrying. – J Collins Feb 11 '22 at 14:10
  • In `RetrieveStruct` it isn't clear if `readValues` is loaded with meaningful bytes or if it is left uninitialized. Consider showing a [MCVE]. – François Andrieux Feb 11 '22 at 14:11
  • @JCollins The "holes" should not be a concern, if the type is trivially copyable. It's normal padding bytes between members. They will be accounted for by `sizeof`, and everything should work out automatically. As long as the type being "Stored" is the same as the type being "Retreived". – François Andrieux Feb 11 '22 at 14:12
  • @FrançoisAndrieux you are right, however it might not be the cleanest way to do it, especially because you may lose a significant amount of memory (EEPROM are usually small) – Guillaume Petitjean Feb 11 '22 at 14:15
  • @molbdnilo I simply mean that it is possibly to test something in isolation, then put it into production where it belongs, and expect nothing to change in its behaviour. Further, I've added the requested minimal code. – J Collins Feb 11 '22 at 15:13

1 Answers1

0

The answer to this problem was obscure and entirely unrelated to structures and namespaces. Essentially I was using an uninitialised library. (Wire.h)

J Collins
  • 2,106
  • 1
  • 23
  • 30