3

This might sound like a weird question at first and I understand why. My problem is a little hard to explain but hopefully I can explain it well.

EDIT: Padding may already be done in the CPU, but this padding is lost when being saved to the HMI local memory registers therefore the alignment is off and the HMI does not display the data correctly.

I have an HMI that I am programming. The software to create the pages on the HMI is mainly drag-and-drop. This is important to note because a user can't access variables in the macros, only once the data is save to local memory can they enter the data into these drag-and-drop objects.

I have a sensor that is connected to the HMI and the sensor has "blocks" of memory that can only be accessed by a multi-register read. This means I need a macro to read this data and temporarily save it in the HMI so that you can edit it before sending it back after clicking "send"

The drag-and-drop elements can only access 16-bit registers. The data that is coming in has single bytes that are thrown in the middle.

I have created a struct and received the data into the struct and written it to the HMI registers. Here is the code in C:

struct configStruct { // 72 Bytes or 36 words/registers long
    char description[32]; // char string
    float c1CE_A; // coefficient 1_A
    float c1CE_B; // coefficient 1_B
    float c1CE_C; // coefficient 1_C
    char c1useLogFit; // Single byte that is 1 if "Log Fit" is enabled, and 0 if disabled.
    float c2CE_A;
    float c2CE_B;
    float c2CE_C;
    char c2useLogFit;
    float c3CE_A;
    float c3CE_B;
    float c3CE_C;
    char c3useLogFit;
    char dummyByte;
};

int MacroEntry()
{

    struct configStruct *dataPtr, data1;

    ReadData(ProductConfig_PLC,1,2305,36,(void*)&data1); // read data from sensor and save in struct

    // Something needs to happen here that converts the bytes into words so the data lines up with the 16-bit registers in the HMI

    WriteData(ProductConfig_HMI,0,2305,36,(void*)&data1); // write temporary data to HMI from struct

    return 0;
}

(ProductConfig_PLC/HMI are defined in a tab in the software. It has options for the read/write address and whatnot)

The ReadData function is working properly, as well as WriteData. The problem is that when the macro saves the struct data to the HMI's 16-bit registers, the bytes throw off the alignment. I end up with my floats split between registers. I know this is the case because the first three floats display fine. The other floats after the single byte dont.

(Also, I've fixed this using arrays and shifting bytes but it's extremely tedious and I have many more to do)

Is there a way to take the single byte variables and convert them to words so that when they are written to the HMI the alignment is proper? I can't access the bytes if there is other data in those same registers.

I also know that I could just create another struct with the proper size variables and copy everything over. However, this becomes a problem because some "blocks" in the sensor contain hundreds of variables and doing it by hand for both send and receive would be tedious, and mind numbing.

Here is the data definition:

The Product Code Calibration Database starts at modbus address 0x900
repeats for each Product Code (currently 0 – 49). Each product code
calibration table is 72 bytes/36 words long. To calculate the start address for each
product code: address = 0x900 + (36 * Product Code number)
Data Definition:
Product Code Description – character string (32 bytes)
Constituent 1 Coefficient A – IEEE float (4 bytes)
Constituent 1 Coefficient B – IEEE float (4 bytes)
Constituent 1 Coefficient C – IEEE float (4 bytes)
Constituent 1 Use Log Fit - 8 bit integer (1 byte) 1= use log fit
Constituent 2 Coefficient A – IEEE float (4 bytes)
Constituent 2 Coefficient B – IEEE float (4 bytes)
Constituent 2 Coefficient C – IEEE float (4 bytes)
Constituent 2 Use Log Fit - 8 bit integer (1 byte) 1= use log fit
Constituent 3 Coefficient A – IEEE float (4 bytes)
Constituent 3 Coefficient B – IEEE float (4 bytes)
Constituent 3 Coefficient C – IEEE float (4 bytes)
Constituent 3 Use Log Fit - 8 bit integer (1 byte) 1= use log fit

Link to HMI datasheet here

Thanks for the help.

  • 4
    Are you asking why structs have padding? See [Why isn't sizeof for a struct equal to the sum of sizeof of each member?](https://stackoverflow.com/questions/119123/why-isnt-sizeof-for-a-struct-equal-to-the-sum-of-sizeof-of-each-member). As for how to fix that... you can pack the data using non-standard features like `#pragma pack`, but only if your CPu can actually read data misaligned. – Lundin Mar 27 '20 at 07:38
  • Two structs and a conversion routine might be cleanest, especially cross platform (which this currently isn't; but, if it becomes so, the conversion routine could also handle endianness). Encapsulation and single purpose come to mind – Mawg says reinstate Monica Mar 27 '20 at 08:49
  • 1
    @Andrew Cline - It is uncertain whether your description contains false assumptions. How do you know that the _data from sensor_ are _72 Bytes or 36 words/registers long_? A link to the documentation of the sensor interface would be in order. How do you know that the _data to HMI_ are _72 Bytes or 36 words/registers long_? A link to the documentation of the HMI would be in order. – Armali Mar 27 '20 at 09:08
  • 1
    @Armali I wouldn't have specified that unless it was in the datasheet. It's confirmed to be 36 because the sensor only sends the entire block. If this was wrong the HMI would not authenticate the data and I would get an error. – Andrew Cline Mar 27 '20 at 09:30
  • @Lundin That's a very good thing to know. According to the link you sent, the data should be automatically aligned to a 4 or 8-byte boundary depending on machine word size. It's a 32-bit, 800 MHz RISC CPU. I'll have to pull it apart to find the exact one. Either way, the data coming in is not padded. I hooked it up to wireshark and I can see the data so can confirm. What makes this difficult to wrap my head around is I dont know at what point the data is being misaligned. Would it go into the struct properly still? Or is it getting mixed up when sent to the HMI? – Andrew Cline Mar 27 '20 at 09:56
  • 1
    @MawgsaysreinstateMonica What do you mean by cross-platform? also encapsulation seems to be a C++ thing after googling it. This is strictly C. and single-purpose is not a narrow enough search term so idk what that is. – Andrew Cline Mar 27 '20 at 09:59
  • 2
    Network data has nothing to do with padding. Padding is done by the compiler for your local system, to satisfy your local alignment requirement. Just view the struct memory map in your favourite debugger. – Lundin Mar 27 '20 at 10:57
  • @Andrew Cline - Well, you have shown that the _data from sensor_ has the structure you described, with no padding. We can assume in practice that the data is sent unchanged to the HMI. Whether or how it has to be rearranged so that the HMI correctly reads it can only be determined with knowledge of the way in which the HMI accesses and processes the data. The plain indication _16-bit_ is insufficient. – Armali Mar 27 '20 at 11:37
  • @Lundin I have debugged the whole process using the HMI to show hexadecimal values as well wireshark that the HMI, nor the sensor, is adding any padding when being sent. This is the issue. The people that wrote the firmware did so with a computer program in mind that can show variables of any size. Therefore, a byte is okay. For the HMI, the local data is read by register. I wish the sensor added padding to the byte values as this would make my life easier, but they didn't. Because of this, I need to add my own after each byte. This gets harder with other "blocks" that have 100 single bytes – Andrew Cline Mar 27 '20 at 20:00
  • Andrew Cline, can you re-order the members of the `struct`? Say typically widest first? .. or put in more dummy members near the `char` ones? – chux - Reinstate Monica Mar 27 '20 at 20:17
  • @chux-ReinstateMonica No unfortunately. I have no control over the sensor firmware. There is already a PC program that reads the data so I can't make changes to it – Andrew Cline Mar 27 '20 at 21:02
  • there's no such thing as "register alignment". Alignment is a memory thing where the address must be a multiple of some number – phuclv Mar 28 '20 at 03:39
  • @phuclv It is when I have an HMI that only reads from single word registers but I get half a word in two registers. – Andrew Cline Mar 28 '20 at 17:53
  • @AndrewCline what's HMI here? Human-machine interface? And if your register is an MMIO register then it's exactly the memory address issue I said. No one says "register alignment" – phuclv Mar 29 '20 at 02:06

2 Answers2

1

C11 does allow alignment control.

Perhaps align many members - any time a more restrictive member may follow a less restrictive one.

#include <stdalign.h>

struct configStruct {
    char description[32];
    alignas (short) float c1CE_A; // coefficient 1_A
    float c1CE_B; // coefficient 1_B
    float c1CE_C; // coefficient 1_C
    char c1useLogFit;
    alignas (short) float c2CE_A;
    float c2CE_B;
    float c2CE_C;
    char cc2useLogFit;
    alignas (short) float c3CE_A;
    float c3CE_B;
    float c3CE_C;
    char c3useLogFit;
    char dummyByte;
};
chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
  • This seems to be exactly what I need, however, the compiler that came with the HMI software doesn't recognize those functions or find stdalign.h. The HMI seems to be running an ARM linux kernel. Could I download the newer C11 compiler and replace the old one? (After backup of course) – Andrew Cline Mar 27 '20 at 20:59
  • @AndrewCline IDK. [chance favors the prepared mind](https://en.wikiquote.org/wiki/Louis_Pasteur#Quotes). – chux - Reinstate Monica Mar 27 '20 at 21:19
  • I don't know what that means but I assume it means, "try it and find out". Which I am doing right now :) – Andrew Cline Mar 27 '20 at 21:25
1

Most compilers add padding to a struct for alignment purposes. Assuming a float is 4 bytes, the actual layout of the struct would look like this:

struct configStruct { 
    char description[32];
    float c1CE_A; 
    float c1CE_B; 
    float c1CE_C; 
    char c1useLogFit; 
    char dummy[3];    // padding bytes
    float c2CE_A;
    float c2CE_B;
    float c2CE_C;
    char c2useLogFit;
    char dummy[3];    // padding bytes
    float c3CE_A;
    float c3CE_B;
    float c3CE_C;
    char c3useLogFit;
    char dummyByte;
    char dummy[2];    // padding bytes
};

So your 72 byte struct is actually 80 bytes.

If you're using gcc, you can declare the struct to be packed:

struct configStruct __attribute__((packed)) { 
    char description[32];
    float c1CE_A; 
    float c1CE_B; 
    float c1CE_C; 
    char c1useLogFit; 
    float c2CE_A;
    float c2CE_B;
    float c2CE_C;
    char c2useLogFit;
    float c3CE_A;
    float c3CE_B;
    float c3CE_C;
    char c3useLogFit;
    char dummyByte;
};

Which will eliminate the padding.

If you don't have that, you'll need to manually deserialize the data to work on it as serialize it back to a byte buffer to write it to the other end.

An example of how you would do this is as follows:

void deserialize(char *indata, struct configStruct *data)
{
    char *p = indata;
    int offset = 0;
    memcpy(data->description, p + offset, sizeof(data->description));
    offset += sizeof(data->description);
    memcpy(&data->c1CE_A, p + offset, sizeof(data->c1CE_A));
    offset += sizeof(data->c1CE_A);
    memcpy(&data->c1CE_B, p + offset, sizeof(data->c1CE_B));
    offset += sizeof(data->c1CE_B);
    memcpy(&data->c1CE_C, p + offset, sizeof(data->c1CE_C));
    offset += sizeof(data->c1CE_C);
    memcpy(&data->c1useLogFit, p + offset, sizeof(data->c1useLogFit));
    offset += sizeof(data->c1useLogFit);
    // etc.
}

void serialize(struct configStruct *data, char *outdata)
{
    char *p = outdata;
    int offset = 0;
    memcpy(p + offset, data->description, sizeof(data->description));
    offset += sizeof(data->description);
    memcpy(p + offset, &data->c1CE_A, sizeof(data->c1CE_A));
    offset += sizeof(data->c1CE_A);
    memcpy(p + offset, &data->c1CE_B, sizeof(data->c1CE_B));
    offset += sizeof(data->c1CE_B);
    memcpy(p + offset, &data->c1CE_C, sizeof(data->c1CE_C));
    offset += sizeof(data->c1CE_C);
    memcpy(p + offset, &data->c1useLogFit, sizeof(data->c1useLogFit));
    offset += sizeof(data->c1useLogFit);
    // etc.
}

int MacroEntry()
{

    char indata[72], outdata[72];
    struct configStruct data;

    ReadData(ProductConfig_PLC,1,2305,36,(void*)indata); 
    deserialize(indata, &data);

    // work with "data"

    serialize(&data, outdata);
    WriteData(ProductConfig_HMI,0,2305,36,(void*)outdata); // write temporary data to HMI from struct

    return 0;
}
dbush
  • 205,898
  • 23
  • 218
  • 273
  • Somewhere there is a trick to append the line number for the `dummy` member name to not have repeated members - as here. Also helps user code to avoid using those members. Also I think nameless members of some bit size are allowed. – chux - Reinstate Monica Mar 27 '20 at 20:32
  • @chux-ReinstateMonica I just used the dummy members as an example of what's happening. The struct as declared matches the binary data coming in, so explicitly adding padding won't help. Either the struct needs to be packed or deserialization is required. – dbush Mar 27 '20 at 20:35
  • @dbush deserialization seems to be the key term. It all comes in serialized and whatever the CPU does regarding padding, I dont know, but the WriteData function ignores any implicit padding and it goes to the local memory serialized anyways – Andrew Cline Mar 27 '20 at 21:02
  • @dbush I need the data to be "deserialized" before being sent to the HMI local registers. The macro just takes the data and saves it in temporary memory on the HMI to be edited. The macro does not send data back to the HMI as that has to be a separate macro. I have 2 buttons that Send and Recieve data. – Andrew Cline Mar 27 '20 at 21:07
  • @AndrewCline It looks like the write function takes exactly what the read function returned. If you don't touch the data you'll likely get the same data pushed, but if you attempt to modify the struct as is, that's when you hit a problem. – dbush Mar 27 '20 at 21:10
  • @dbush Yes. There is a comment in between that says something needs to happen. Of course I'm learning as I go here so maybe it needs to happen before. The subpost about C11 and the alignas feature seems to be a way to fix this but I can't use that as the MinGW that came with the HMI software doesn't support it. I'm going to download the new version and temporarily replcace it to see if it will work. If not I need a solution that sends out dummy bytes to the HMI for alignment purposes. – Andrew Cline Mar 27 '20 at 21:15
  • @AndrewCline See my edit for an example on serialization/deserialization. – dbush Mar 27 '20 at 21:16
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/210463/discussion-between-andrew-cline-and-dbush). – Andrew Cline Mar 27 '20 at 21:27