C - convert a struct in a u16_array, where each uint16_t represents only a byte

227 Views Asked by At

I have a pretty nasty case on a texas instruments C2000 microcontroller, that is only available to represent uin16_t and not uint8_t. Now I have a struct like the following, where each uint16_t represents only a byte of data, that will be written to an external EEPROM:

typedef struct
{
    uint32_t a;
    uint32_t b;
    uint32_t c;
    uint32_t d;
} MCL_Struct_t;

MCL_Struct_t struc_write = {0};
MCL_Struct_t struc_read = {0};
uint16_t *struc_write_handle = ((uint16_t *)&struc_write);
uint16_t *struc_read_handle = ((uint16_t *)&struc_read);

struc_write.a = 0x11112222;
struc_write.b = 0x11113333;
struc_write.c = 0x11114444;
struc_write.d = 0x11115555;

//call eeprom write
EEPROM_writeTransaction(eepromAddr, struc_write_handle, sizeof(MCL_Struct_t));

//read back eeprom
EEPROM_readTransaction(eepromAddr, sizeof(MCL_Struct_t));

//copy to struc_read
memcpy(struc_read_handle, &SPI_DMA_Handle.pSPIRXDMA->pbuffer[0], sizeof(MCL_Struct_t));

Since the SPI is only capable of transfering bytewise, the uint16_t data is handle as a byte and so I only write something like struc_write.a = 0x00110022 etc.

How can I make, that my handles point to the data bytewise?

3

There are 3 best solutions below

6
Diram_T On

If the underlying architecture cannot physically do bytewise addressing, then you are out of luck to do it natively, though it is not clear from your question whether or not that's case.

If your microcontroller can handle bytewise (which I believe should be the case, as far as my knowledge goes), it would have been a simple cast to char type pointer:

char* addr = (char*) &struct_write.a;
char lower_byte = *addr; 
char upper_byte = *(addr + 1); 

Otherwise, you could do it with shift operations, but keep in mind this would take twice the memory:

uint6_t lower_byte = struct_write.a & (0x00FF);
uint6_t upper_byte = (struct_write.a >> 16)  & (0x00FF);

I would suggest you look further into the architecture of your microcontroller and check the Instruction Set Architecture.

3
Aconcagua On

It appears as if the EEPROM implementation is implemented to treat individual characters as 8-byte values even though CHAR_BIT on your system apparently is 16 (function signature then most likely is based on char or unsigned char directly).

This being the case you then cannot write entire structs directly, though doing so is questionable anyway because of potential padding bytes existing (generally, not in concrete case) and byte order issues. So I recommend serialising explicitly, you get well defined behaviour no matter on which architecture you are:

void serialize(uint32_t value, char buffer[])
{
// for little endian byte order:
    buffer[0] = value >>  0 & 0xff;
    buffer[1] = value >>  8 & 0xff;
    buffer[2] = value >> 16 & 0xff;
    buffer[3] = value >> 24 & 0xff;
//                       ^^
// revert order of shifts for big endian byte order
}

char* serialize_s(uint32_t value, size_t length, char buffer[])
{
    return length < 4 ? NULL : (serialize(value, buffer), buffer + 4);
}

uint32_t deserialize(char buffer[])
{
    uint32_t value
        = (uint32_t)(buffer[0] & 0xff) <<  0
        | (uint32_t)(buffer[1] & 0xff) <<  8
        | (uint32_t)(buffer[2] & 0xff) << 16
        | (uint32_t)(buffer[3] & 0xff) << 24;
    return value;
}

char* deserialize_s(uint32_t* value, size_t length, char buffer[])
{
    return !value || length < 4
        ? NULL : (*value = deserialize(buffer), buffer + 4);
}

char buffer[16];
serialize(struct_write.a, buffer +  0);
serialize(struct_write.b, buffer +  4);
serialize(struct_write.c, buffer +  8);
serialize(struct_write.d, buffer + 12);

// send buffer
// receive buffer

struct_write.a = deserialize(buffer +  0);
struct_write.b = deserialize(buffer +  4);
struct_write.c = deserialize(buffer +  8);
struct_write.d = deserialize(buffer + 12);
0
dalfaB On

Here we have sizeof(char)=sizeof(uint16_t)=sizeof(int)=1.
A char is a byte, but a byte is 2 octets.
It is not possible to have pointers on octets. If you have to transmit/receive octets, you must do it through conversion functions.

typedef union BiOctets {
    uint16_t  u16;    // 2 octets
    unsigned char uc; // also 2 octets
    struct {          // also 2 octets
        unsigned char  high : 8;
        unsigned char  low  : 8;  // cannot take address of low
    //Note: depending en endianess of C2000, may be high and low must be swapped
    };
} BiOctets;

// access to an octet in an array of bytes
unsigned char  BiOctetToOctet( BiOctets bi[], int index ) {
    if ( index & 1 )
        return  bi[index>>1].low;     // odd values is low part
    else
        return  bi[index>>1].high;    // even values is high part
} // notice that returns a byte where high octet is null, it's an octet

// insert an octet in array of bytes
void  OctetToBiOctet( BiOctets bi[], int index, unsigned char value ) {
    if ( index & 1 )
        bi[index>>1].low = value;
    else
        bi[index>>1].high = value;
} // notice that 'value' is supposed to have null high octet, it's an octet

// then these function must be used each time you have to deal with octets