2

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?

HansPeterLoft
  • 489
  • 1
  • 9
  • 28
  • 1
    I'm afraid you messed up uint16_t and uint32_t between your writeup and your example (text says there's uint16_t's in the struct, code says otherwise). Like it is now, it is impossible to answer. – tofro Jul 05 '23 at 08:09
  • Looks to me as if you'd have to split up data manually, i.e. have a separate array and copy low/high byte to there independently. – Aconcagua Jul 05 '23 at 08:18
  • It is not entirely clear what you want to achieve. Does your SPI implementation require to get the data in `uint16_t` array but only sends the low 8 bits of each entry? – Gerhardh Jul 05 '23 at 09:10

3 Answers3

2

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);
Aconcagua
  • 24,880
  • 4
  • 34
  • 59
  • huh, i don't quite get this part in `deserialize(char buffer[])` where you do `uint32_t value = highestByte = nextByte = nextByte = lowestByte;` did you mean to OR (`|`) them or do `|=` because as it is, wouldn't it just reassign it 4 times? Or has my C gotten really rusty? – Shark Jul 05 '23 at 12:47
  • @Shark Copy-paste error... *Of course* must be `|`, thanks for the hint. – Aconcagua Jul 05 '23 at 13:37
  • ah, no problemo, you're welcome. I just thought that maybe it was some weird trick i know nothing about :) – Shark Jul 05 '23 at 16:42
0

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.

Diram_T
  • 21
  • 3
  • *'simple cast to `char` type pointer'* – most likely not in this case: No `uint8_t` data type being available is a strong indicator for `CHAR_BIT` being 16 instead of 8 (and I know there are such TI devices out there...), and thus `sizeof(char) == sizeof(int16_t)`... – Aconcagua Jul 05 '23 at 09:57
  • Which is why I prefaced with a preposition 'If the microcontroller can handle bytewise' :) – Diram_T Jul 05 '23 at 10:00
  • Side note: Why do you place parentheses around hexadecimal *literals*? Just makes the code harder to read without any benefits at all. Please don't do. Actually you don't have to mask out most significant bits after a leftshift on *unsigned* types at all, they get always 0 anyway (but is implementation defined on *signed*, if negative). – Aconcagua Jul 05 '23 at 10:07
  • Actually in *given* case you wouldn't even have to mask out the high bits for the low part either, they'd be ignored anyway... And if API changes? Well, we'd add in surplus bytes (zero or seemingly garbage) anyway, so implementation breaks with or without ;) – Aconcagua Jul 05 '23 at 10:12
  • I agree that they offer no benefit, but it makes it easier to notice parts of expressions that could later on be substituted by a macro definition. I also agree with the unsigned shift part. – Diram_T Jul 05 '23 at 10:14
  • *'makes it easier'* – well, that *would* be a benefit, wouldn't it? On the other hand that's highly subjective, I wouldn't agree on, so (from my point of view?) only worsened readability remains… – Aconcagua Jul 05 '23 at 10:46
0

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
dalfaB
  • 436
  • 1
  • 7