我在嵌入式C中做了一些工作,加速度计将数据作为14位2的补码数返回。我将此结果直接存储到uint16_t
中。稍后在我的代码中,我试图将这种“原始”形式的数据转换为有符号整数,以表示/使用我的其余代码。
我无法让编译器了解我想要做什么。在下面的代码中,我正在检查第14位是否已设置(意味着数字为负),然后我想反转这些位并加1以得到数字的大小。
int16_t fxls8471qr1_convert_raw_accel_to_mag(uint16_t raw, enum fxls8471qr1_fs_range range) {
int16_t raw_signed;
if(raw & _14BIT_SIGN_MASK) {
// Convert 14 bit 2's complement to 16 bit 2's complement
raw |= (1 << 15) | (1 << 14); // 2's complement extension
raw_signed = -(~raw + 1);
}
else {
raw_signed = raw;
}
uint16_t divisor;
if(range == FXLS8471QR1_FS_RANGE_2G) {
divisor = FS_DIV_2G;
}
else if(range == FXLS8471QR1_FS_RANGE_4G) {
divisor = FS_DIV_4G;
}
else {
divisor = FS_DIV_8G;
}
return ((int32_t)raw_signed * RAW_SCALE_FACTOR) / divisor;
}
遗憾的是,此代码不起作用。反汇编告诉我,由于某种原因,编译器正在优化我的语句raw_signed = -(~raw + 1);
我如何实现我想要的结果?
数学在纸上运行,但我觉得编译器因为某种原因与我抗争:(。
答案 0 :(得分:12)
将14位2的补码值转换为16位有符号,同时保持该值只是一个符号:
int16_t accel = (int16_t)(raw << 2) / 4 ;
左移将符号位推入16位符号位位置,除以4会恢复幅度但保持其符号。除法避免了右移的实现定义行为,但通常会在允许的指令集上产生单个算术移位权。强制转换是必要的,因为raw << 2
是int
表达式,除非int
是16位,否则除法将简单地恢复原始值。
然而,将加速度计数据左移两位并将其视为传感器首先是16位就更简单了。将所有内容归一化为16位的好处是,如果使用任意数量的位数达到16的传感器,代码不需要更改。幅度将简单地增加四倍,最不重要的两位将为零 - 无信息获得或失去,任何情况下缩放都是任意的。
int16_t accel = raw << 2 ;
在这两种情况下,如果你想要无符号幅度,那么就是:
int32_t mag = (int32_t)labs( (int)accel ) ;
答案 1 :(得分:6)
我会做简单的算术。结果是14位有符号,表示为从0到2 ^ 14的数字.1。测试数字是否为2 ^ 13或更高(表示否定)然后减去2 ^ 14。
int16_t fxls8471qr1_convert_raw_accel_to_mag(uint16_t raw, enum fxls8471qr1_fs_range range)
{
int16_t raw_signed = raw;
if(raw_signed >= 1 << 13) {
raw_signed -= 1 << 14;
}
uint16_t divisor;
if(range == FXLS8471QR1_FS_RANGE_2G) {
divisor = FS_DIV_2G;
}
else if(range == FXLS8471QR1_FS_RANGE_4G) {
divisor = FS_DIV_4G;
}
else {
divisor = FS_DIV_8G;
}
return ((int32_t)raw_signed * RAW_SCALE_FACTOR) / divisor;
}
请检查我的算术。 (我有13和14正确吗?)
答案 2 :(得分:1)
假设您的特定C实现中的int
是16位宽,则在修改(1 << 15)
时使用的表达式raw
会产生未定义的行为。在这种情况下,如果采用条件的分支来评估表达式,编译器可以自由地生成代码以执行任何操作 - 或者不执行任何操作。
如果int
为16位宽,则表达式-(~raw + 1)
和所有中间值将具有unsigned int
== uint16_t
类型。这是“通常的算术转换”的结果,因为(16位)int
不能表示类型uint16_t
的所有值。结果将设置为高位,因此超出类型int
可表示的范围,因此将其分配给类型int
的左值会产生实现定义的行为。您必须查阅您的文档,以确定它定义的行为是否符合您的预期和期望。
如果您改为执行14位符号转换,强制关闭高阶位((~raw + 1) & 0x3fff
),则结果 - 所需负值的倒数 - 可由16位签名表示int
,因此明确转换为int16_t
的定义明确,并保留(正)值。你想要的结果是相反的结果,你可以通过否定它来获得。总体:
raw_signed = -(int16_t)((~raw + 1) & 0x3fff);
当然,如果int
在您的环境中超过16位,那么我认为您的原始代码无法按预期工作的原因。但是,这不会使上面的表达式无效,无论默认值int
的大小如何,都会产生一致定义的行为。
答案 3 :(得分:1)
假设代码到达return ((int32_t)raw_signed ...
时,其值为[-8192 ... +8191]
范围:
如果RAW_SCALE_FACTOR
是4的倍数,那么可以节省一点钱。
所以而不是
int16_t raw_signed = raw << 2;
raw_signed >>= 2;
代替
int16_t fxls8471qr1_convert_raw_accel_to_mag(uint16_t raw,enum fxls8471qr1_fs_range range){
int16_t raw_signed = raw << 2;
uint16_t divisor;
...
// return ((int32_t)raw_signed * RAW_SCALE_FACTOR) / divisor;
return ((int32_t)raw_signed * (RAW_SCALE_FACTOR/4)) / divisor;
}
答案 4 :(得分:0)
要将14位二进制补码转换为有符号值,可以翻转符号位并减去偏移量:
int16_t raw_signed = (raw ^ 1 << 13) - (1 << 13);