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)
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With