我有一个16位变量data
,即:
volatile uint16_t data;
我需要根据外部传感器上两个8位寄存器的内容填充此值。这些是通过I2C/TWI访问的。
我的TWI例程是async*,签名是:
bool twi_read_register(uint8_t sla, uint8_t reg, uint8_t *data, void (*callback)(void));
这将sla
上的reg
的值读入*data
,然后调用callback()
。
如果我知道uint16_t
在内存中的排列方式,比如MSB LSB
,那么我可以:
twi_read_register(SLA, REG_MSB, (uint8_t *)&data, NULL);
twi_read_register(SLA, REG_LSB, (uint8_t *)&data + 1, NULL);
但是,我不喜欢在我的代码中添加端序依赖。有没有一种独立于端部的方式来实现这一点?
(旁注:我目前的实际解决方案涉及使用结构体,即:
)typedef struct {
uint8_t msb;
uint8_t lsb;
} SensorReading;
但我很好奇我是否可以用一个简单的uint16_t
)
(*异步我指的是分阶段,即*data
将在将来的某个时候设置,此时如果请求,将通过callback
函数通知被调用者)
下面的操作不可行吗?
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;
或者data
如此易失性以至于twi_read_register
需要写它。在这种情况下,我认为你必须使用依赖于端序的代码。
正如您在下面指出的那样,data
确实是易失性的,因为还有另一个设备正在读取它。因此,在两个端序不同的设备之间建立了一个内存映射连接。这意味着您必须使用依赖于端序的代码。
你提到结构体是一种变通方法,但这是一种处理这个问题的标准方法。
#ifdef BIGENDIAN
typedef struct
{ uint8_t msb, lsb;
} uint16_as_uint8_t;
#else
typedef struct
{ uint8_t lsb, msb;
} uint16_as_uint8_t;
#endif
在上面可以加上union
union
{ uint16_as_uint8_t as8;
uint16_t as16;
};
请注意,后者违反了C89标准,因为您的明确意图是写入union
的一个字段并从另一个字段读取,这将导致未指定的值。从C99开始(幸运的是)支持这一点。在C89中,可以通过(char*)
使用指针转换,以一种可移植的方式完成此操作。
请注意,上面可能看起来以一种可移植的方式隐藏了端序,结构包装也可能因目标而异,并且仍然可能在某些目标上中断。对于上面的例子来说,这不太可能,但是周围有一些奇怪的目标。我想说的是,在这个设备级别上进行可移植编程可能是不可能的,最好接受这一点,并努力将所有细节隐藏在紧凑的目标接口中,因此为目标更改一个头文件就足以支持它。然后代码的其余部分可以看起来与目标无关。
这样怎么样?
uint16_t myValue = ...;
uint8_t LSB = (uint8_t)(myValue % 256);
uint8_t MSB = (uint8_t)(myValue / 256);
volatile uint16_t data;
uint16_t v = 1;
twi_read_register(SLA, REG_MSB, (uint8_t *)&data + *((uint8_t *)&v), NULL);
twi_read_register(SLA, REG_LSB, (uint8_t *)&data + *((uint8_t *)&v + 1), NULL);