Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can you get a pointer to the lower byte in an endian-independent way?

I have a 16 bit variable data, ie:

volatile uint16_t data;

I need to populate this value based on the contents of two 8 bit registers on an external sensor. These are accessed over I2C/TWI.

My TWI routine is async*, and has the signature:

bool twi_read_register(uint8_t sla, uint8_t reg, uint8_t *data, void (*callback)(void));

This reads the value of reg on sla into *data, then calls callback().

If I knew the uint16_t was arranged in memory as, say, MSB LSB, then I could do:

twi_read_register(SLA, REG_MSB, (uint8_t *)&data, NULL);
twi_read_register(SLA, REG_LSB, (uint8_t *)&data + 1, NULL);

However, I don't like baking endian dependence into my code. Is there a way to achieve this in an endian-independent way?

(side note: my actual workaround at the moment involves using a struct, ie:

typedef struct {
    uint8_t msb;
    uint8_t lsb;
} SensorReading;

but I'm curious if I could do it with a simple uint16_t)

EDIT

(* by async I mean split-phase, ie *data will be set at some point in the future, at which point the callee will be notifed via the callback function if requested)

like image 932
sapi Avatar asked Oct 04 '22 09:10

sapi


1 Answers

Would the following not work?

uint8_t v1, v2;
twi_read_register(SLA, REG_MSB, &v1, NULL);
twi_read_register(SLA, REG_LSB, &v2, NULL);
data = ((uint16_t)v1<<8)|v2;

Or is data so volatile that the twi_read_register needs to write it. In that case I think you're stuck with endian dependent code.

As you pointed out below the data is indeed that volatile, because yet another device is reading it. So a memory mapped connection is established between two devices that may differ in endianness. This means you are stuck with endian dependent code.

You mention the struct as a workaround, but that is kind of a standard way of dealing with this.

#ifdef BIGENDIAN
typedef struct
{       uint8_t  msb, lsb;
} uint16_as_uint8_t;
#else
typedef struct
{       uint8_t  lsb, msb;
} uint16_as_uint8_t;
#endif

On top of that you could put a union

union
{       uint16_as_uint8_t  as8;
        uint16_t           as16;
};

Note that the latter is in violation of the C89 standard as it is your clear intention to write one field of the union and read from another, which results in an unspecified value. From off C99 this is (fortunately) supported. In C89 one would use pointer conversions through (char*) to do this in a portable way.

Note that the above may to seem hide the endianness in a portable way, structure packing may also differ from target to target and it might still break on some target. For the above example this is unlikely, but there are some bizarre targets around. What I'm trying to say is that it is probably not possible to program portable on this device level and it might be better to accept that and strive to hide all the details in a compact target interface, so changing one header file for the target would be enough to support it. The remainder of the code then can look target independent.

like image 84
Bryan Olivier Avatar answered Oct 13 '22 00:10

Bryan Olivier