9 years, 5 months ago.

How to convert struct value to/from char array?

I'm having difficulty finding the correct syntax for converting a struct instance to/from a char array. I want to persist the value of a struct to flash, then later read it back into a new struct instance.

So I have methods which read/write a char* to flash:

bool FRamIO::WriteByte(int address, char* data) {
    fram.start();
    fram.write(ramAddr);
    fram.write(address >> 8);
    fram.write(address & 0xFF);
    int len = (int)sizeof(data);
    for (int i=0; i<len; i++) {
        fram.write(data[i]);
    }
    fram.stop();
    return true;
}

bool FRamIO::ReadByte(int address, char* data) {
    fram.start();
    fram.write(ramAddr);
    fram.write(address >> 8);
    fram.write(address & 0xFF);
    fram.start();
    fram.write(ramAddr | 0x01);
    int len = (int)sizeof(data);
    for (int i=0; i<len; i++) {
        int s = 1;
        if (i == len - 1) {s = 0;}
        data[i] = fram.read(s);
    }
    fram.stop();
    return true;
}   

These are defined in a custom FRamIO wrapper for a FRAM (basically a fast EEPROM). Then in my custom Interpreter class (an instance of Interpreter is created in the Main.cpp code file and the Main() sub starts a Run() loop on that Interpreter instance) I have a 64-byte structure defined as:

struct ProductionItem {
public:
    char Number[54];
    short Cavities;
    int Required;
    int Count; 
        
    void add(){
        Count += Cavities;
    }
    
    bool completed() {
        return (Count >= Required);
    }
};

The Number field is essentially a string with a max character length of 53. The other fields are simple numeric values as they appear.

In the Interpreter class I've created a couple worker routines for reading and writing ProductionItems to/from FRAM:

void Interpreter::GetProdItem(int index, ProductionItem* item) {
    char* data = new char[64];
    fRam.ReadByte(addProdItem0 + (prodItemSize * index), data);
    memcpy(item, data, 64);
    delete data;
    //item = (ProductionItem *)data;
}

void Interpreter::SetProdItem(int index, ProductionItem* item) {
    char* data = new char[64];
    memcpy(data, &item, 64);
    fRam.WriteByte(addProdItem0 + (prodItemSize * index), data);
    delete data;
}

Finally I have two interpreter commands to test the functionality:

    else if(command == "testitemsave") {
        int idx;
        sscanf(params[0].data(), "%i", &idx);
        ProductionItem item;
        strcpy(item.Number, "12SWS256");
        item.Cavities = 12;
        item.Required = 5000;
        item.Count = 0;
        SetProdItem(idx, &item);
        result = "Attempted to save item...";
    }
    else if(command == "testitemload") {
        int idx;
        sscanf(params[0].data(), "%i", &idx);
        ProductionItem item;
        GetProdItem(idx, &item);
        char msg[128];
        sprintf(msg, "Item: %s, Cav: %i, Req: %i, Cnt: %i", item.Number, item.Cavities, item.Required, item.Count);
        result = msg;
    }

These are the blocks of code executed in the interpreter when the user enters the "testitemsave/load" commands into a console.

I have tried many different variations of code within the interpreter commands as well as the worker methods. I thought I should be able to simply assign the struct instance to the char array but couldn't get that to work. Copying the data isn't working either - I typically get corrupted results. At one point I had the item number working, but not the numeric values. At another point the numeric values worked if I saved them twice or more, but then the item number was always truncated.

I'm pretty good in managed code, but there's still a lot about basic unmanaged code that eludes me... it maybe that the issue I'm seeing stems from some other underlying problem; but I do have a lot of seemingly working functionality so I suspect the issue lies somewhere near the symptom.

If someone could help me understand how to properly pass the struct instances around and serialize them to/from the flash, I would be greatly appreciative.

5 Answers

9 years, 5 months ago.

And a third one, what I generally use is something like:

structName Mystruct;

char *charpointer;
charpointer = (char*) &Mystruct;


structName *Mystruct2;
Mystruct2 = (structName*) charpointer;

So you just make a pointer to a char, and then you give it as value the pointer to your struct, casted to char pointer. Quite similar to the union option tbh, with both some small pros and cons.

Regarding your code, it is good you put all the relevant parts here. For debugging however it is handy to try to keep it as simple as possible. Then in SetProdItem you have: memcpy(data, &item, 64), however item is already a pointer, you now make a pointer to a pointer.

Accepted Answer
9 years, 5 months ago.

Hi Reed,

Did you try to use a union? e.g.:

struct t_any_data {

int nVal1; int nVal2; char szText[10];

};

union t_byte_data {

struct t_any_data Data; char Bytes[sizeof(t_any_data)];

} Frame;

Frame.Data.nVal1 = 5; Access nVal1, stores 5 into Frame.Bytes[0] = 7; Access nVal1, Byte 0, overwrites with 7

Now you can swap between single byte/char access and formated data access.

With greetings

Joachim

Thank you I may try this if I cannot get the simple casting to work as I thought it should.

posted by Reed Kimble 20 Aug 2013
9 years, 5 months ago.

Hi Reed, what you are looking for is serialization of the objects, where the struct is in fact a C++ object. This link may help you: http://www.functionx.com/cpp/articles/serialization.htm

Hope this helps, Henrik

Hi and thank you; serialization is the first thing I searched for but all examples (including this one) seem to deal with a file system and I do not know how to modify the samples to simply use a byte array instead of a path to a file.

posted by Reed Kimble 20 Aug 2013
9 years, 5 months ago.

Hello Reed, in your above example the FRamIO::WriteByte and FRamIO::ReadByte methods calculate the number of bytes to write/read incorrectly. You have the following line in both of these functions:

    int len = (int)sizeof(data);

The sizeof(data) will always be 4 since that is the size of a pointer on this 32-bit architecture. That means only the first 4 bytes of your structure are actually being read/written from/to FLASH.

You should make len a parameter of those methods and pass it in from the caller which actually knows the size of the data structures you are trying to write/read.

bool FRamIO::WriteByte(int address, const char* data, size_t len) {
    fram.start();
    fram.write(ramAddr);
    fram.write(address >> 8);
    fram.write(address & 0xFF);
    for (size_t i=0; i<len; i++) {
        fram.write(data[i]);
    }
    fram.stop();
    return true;
}
 
bool FRamIO::ReadByte(int address, char* data, size_t len) {
    fram.start();
    fram.write(ramAddr);
    fram.write(address >> 8);
    fram.write(address & 0xFF);
    fram.start();
    fram.write(ramAddr | 0x01);
    for (size_t i=0; i<len; i++) {
        int s = 1;
        if (i == len - 1) {s = 0;}
        data[i] = fram.read(s);
    }
    fram.stop();
    return true;
} 

...


void Interpreter::GetProdItem(int index, ProductionItem* item) {
    fRam.ReadByte(addProdItem0 + (prodItemSize * index), (char*)item, sizeof(*item));
}
 
void Interpreter::SetProdItem(int index, ProductionItem* item) {
    fRam.WriteByte(addProdItem0 + (prodItemSize * index), (const char*)item, sizeof(*item));
}

Reed Kimble
poster
9 years, 5 months ago.

Thanks for the help thus far everyone, but I'm still not getting it...

@Adam: Ah of course... at one point I was trying to pass an actual character array and then the count was accurate, when I changed to the pointer I failed to consider the change to the sizeof result. So I've corrected those two routines.

@Erik: This is what I wanted to do to begin with, and I've tried it again but I'm still not getting proper results. It probably has to do with passing the value between methods... but I don't know where I've gone wrong.

Here are the Read/Write methods after Adam's correction:

bool FRamIO::WriteByte(int address, char* data, size_t len) {
    fram.start();
    fram.write(ramAddr);
    fram.write(address >> 8);
    fram.write(address & 0xFF);
    for (int i=0; i<len; i++) {
        fram.write(data[i]);
    }
    fram.stop();
    return true;
}

bool FRamIO::ReadByte(int address, char* data, size_t len) {
    fram.start();
    fram.write(ramAddr);
    fram.write(address >> 8);
    fram.write(address & 0xFF);
    fram.start();
    fram.write(ramAddr | 0x01);
    for (int i=0; i<len; i++) {
        int s = 1;
        if (i == len - 1) {s = 0;}
        data[i] = fram.read(s);
    }
    fram.stop();
    return true;
}     

The Get/Set methods on Interpreter now look like:

void Interpreter::GetProdItem(int index, ProductionItem* item) {
    char* data = new char[64];
    fRam.ReadByte(addProdItem0 + (prodItemSize * index), data, 64);
    item = (ProductionItem*) data;
}

void Interpreter::SetProdItem(int index, ProductionItem item) {
    char* data; 
    data = (char*) &item;
    fRam.WriteByte(addProdItem0 + (prodItemSize * index), data, 64);
}

And for the test commands I've got:

    else if(command == "testitemsave") {
        int idx;
        sscanf(params[0].data(), "%i", &idx);
        ProductionItem item;
        strcpy(item.Number, "12SWS256");
        item.Cavities = 12;
        item.Required = 5000;
        item.Count = 0;
        SetProdItem(idx, item);
        result = "Attempted to save item...";
    }
    else if(command == "testitemload") {
        int idx;
        sscanf(params[0].data(), "%i", &idx);
        ProductionItem item;
        GetProdItem(idx, &item);
        char msg[128];
        sprintf(msg, "Item: %s, Cav: %i, Req: %i, Cnt: %i", item.Number, item.Cavities, item.Required, item.Count);
        result = msg;
    }

This all compiles with a warning of: "Parameter "item" was set but never used" pointing to the line:

void Interpreter::GetProdItem(int index, ProductionItem* item) {

Again I've experimented with variations of declaring values and passing pointers along with declaring pointers and passing them directly, but I mostly just get empty values in the structure instance other than the last integer which is always the same value (making me think I'm still writing/reading a pointer instead of the value it points to).

I apologize for the convolution of the code in question but I have often found that I can make a simple example work just fine and the problems only occur when I try to structure my code into a domain model and start passing parameters between methods... that's always when things get wonky on me so that's why I wanted to keep the context of the chain of method calls that I need to use.

Thanks again to everyone trying to help.

Still I think you have to split it up, then you can find out when it goes wrong exactly.

Two options I currently see: Reading/writing from/to your memory goes wrong. Did you check with a simple check if that works correctly for 64 bytes? (Just writing 1-64 for example).

Or something with your casting goes wrong. So first try to just save it in a random char array, and get it back in a new structure and see if that works.

What is going wrong now is that in the loading you give it a pointer to a fixed ProductionItem. You change that pointer in your function to point at new data, but that doesn't change the value you get outside the function, which is why you get that error. If you have it like that you should do that part with memcopy.

posted by Erik - 20 Aug 2013

"What is going wrong now is that in the loading you give it a pointer to a fixed ProductionItem. You change that pointer in your function to point at new data, but that doesn't change the value you get outside the function, which is why you get that error. If you have it like that you should do that part with memcopy."

That's the relevant part; thank you so much. I suspected something like this was necessary but have not yet found the correct way to write the memcpy statement.

I am quite sure that the read and write to the memory is correct. Those methods are nearly identical to others which are working properly in all cases. The only blunder I had was changing a parameter into a pointer and not accounting for that when measuring the size of the data buffer. But I'm sure the steps work and I'm sure that whatever I'm passing is being written properly.

I am unsure of what I'm passing. =P But I believe you've hit the main problem so I'll try to get memcpy working, unless you can suggest a better way to return the new ProductionItem instance from memory.

posted by Reed Kimble 21 Aug 2013

Ok I was being a dolt and forgetting the length parameter on the memcpy statement... that's why it I was getting a parameter error. I'm too spoiled by intellisense for this web IDE lol

At any rate, with Adam's catch and Erik's help I've got this working as expected now. Thank you so much everyone!

posted by Reed Kimble 21 Aug 2013