将二进制补码、左对齐的整数转换为常规二进制。

4
我正在使用C++和Qt编程Raspberry Pi 3。我正在使用wiringPi库与I2C加速度计进行接口交互(我用它来比较倾斜角度值但不测量角度度数)。
我需要确定一个加速度计读数是否大于或小于先前的读数或任意预设值。(我有一个机器上的臂,我想将其设置为任意角度,然后让臂移动到该角度并从该角度移动。)
加速度计输出两个字节的二进制补码数据。数据左对齐12位。(我认为这意味着加速度计值的4个最高有效位位于上字节的左侧?)加速度计是LIS3DH。
我将两个字节读取到我的程序中作为2个整数值,但我在将数据转换为有用信息方面遇到了困难。
wiringPi I2C库仅返回整数。
我可能需要将十进制读数转换为二进制,将两个字节的二进制补码左对齐数据转换为右对齐的12位常规二进制数据。
我正在寻找如何完成此操作以及是否有比我上面列出的路径更简单的方法的建议。

编辑:

void main(void)       
{
    int acc_l = 0, acc_h = 0;
    acc_l = read_output_register_lo;
    acc_h = read_output_register_hi;

/*
How do I convert acc_l and acc_h to binary?

Do I need to shift acc_h 4 bits to the right?

How do I concatenate acc_h and acc_l into a 12 bit binary number?

How do I convert 12 bit twos compliment binary into regular binary?

Is this the correct process to follow?

Is there an easier way to do this?
*/
}

编辑: 非常感谢Roman。我想到了同样的基本概念,但似乎我把两个寄存器反了。再次感谢。


你有一些可以发布的代码吗? - markwalker_
2个回答

8

我也在使用LIS3DH,经过几个小时的阅读和尝试,我终于弄清楚了。虽然我的代码是针对AVR微控制器的,但方法仍然相同。

加速度计将其读数表示为左对齐的16位值,每个轴分别划分为2字节寄存器:OUT_X_LOUT_X_HOUT_Y_LOUT_Y_HOUT_Z_LOUT_Z_H。左对齐意味着:所有值位都向左移动。有多少位?这取决于您的精度。

假设您使用10位精度(默认值),并且想要读取X轴的值,则OUT_X_HOUT_X_L寄存器如下所示:

╔═════════╦═══════════╦═══════════╗
║         ║  OUT_X_H  ║  OUT_X_L  ║
╠═════════╬═══════════╬═══════════╣
║ Content ║ xxxx xxxx ║ xx00 0000 ║
╚═════════╩═══════════╩═══════════╝

x是你想要的值,0只是一个零。由于精度为10位,6个不太重要的位将始终被清零。

为什么有人要发送这样的数据?原因很简单——如果您不关心精度,您可以仅读取高寄存器并将其存储为8位整数(int8_t)。由于左对齐,保留了更多的最高有效位,您仅失去了来自低寄存器的2位。另一方面,右对齐更容易操作,但如果您不读取整个16位值,则会丢失大量信息。

现在,如果您想将此值转换为“正常”值(右对齐),则需要将其存储为16位整数(int16_t)。计算机将带符号整数保存为二进制补码形式,LIS3DH也是如此,这意味着存储在此int16_t变量中的值将具有正确的符号。您必须执行的最后一个操作是将此值向右移动6个位置(对于10位精度),然后翻译就完成了!

我编写了两种转换方法,一种基于典型的位操作,另一种是指针类型转换。

位操作

int16_t x = out_x_h; // 1.
x <<= 8;             // 1.
x |= out_x_l;        // 2.
x >>= 6;             // 3.

int16_t y = out_y_h;
y <<= 8;
y |= out_y_l;
y >>= 6;

int16_t z = out_z_h;
z <<= 8;
z |= out_z_l;
z >>= 6;

对于每个轴:

  1. 将高寄存器存储在16位变量的上半部分。
  2. 附加一个低寄存器。现在您的变量包含左对齐的值。
  3. 将整个变量向右移动6位。

指针转换自杀

uint8_t x_a[2] = {out_x_l, out_x_h}; // 1.
int16_t x = (*(int16_t*) x_a) >> 6;  // 2.

uint8_t y_a[2] = {out_y_l, out_y_h};
int16_t y = (*(int16_t*) y_a) >> 6;

uint8_t z_a[2] = {out_z_l, out_z_h};
int16_t z = (*(int16_t*) z_a) >> 6;

对于每个轴:

  1. 将寄存器存储在字节数组中。元素的顺序仅适用于小端架构。对于大端架构,高位寄存器应该放在第一位。
  2. uint8_t指针转换为int16_t指针,这样字节数组的指针就被视为指向一个16位整数的指针,解引用它,并右移6位。

1
有符号整数的右移操作是实现定义的。确保您的编译器对负数执行“算术右移”以使此解决方案能够正常工作。 - markrages
需要解决的问题是:如何以可移植的方式实现它,因为它确实是实现定义的。问题在于,您首先需要将其转换为int类型,然后删除LSB零位,并且符号位必须保留存在。 - Jan

0
根据LIS3DH的描述(ADC具有10位),您应该执行以下操作:
unsigned int result = ((acc_h & 0x03)<<8) + (acc_l & 0xFF);

对于12位,您需要使用以下代码:

unsigned int result = ((acc_h & 0x0F)<<8) + (acc_l & 0xFF);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接