【问题标题】:Arduino read 20 bit number from 3 registersArduino 从 3 个寄存器中读取 20 位数字
【发布时间】:2017-08-16 21:00:08
【问题描述】:

我有一个 ADXL355 (EVAL-ADXL355-PMDZ),我正在尝试针对非常昂贵的工业级传感器进行测试。我正在使用 I2C,并且能够读取datasheet 中所述的设备属性和设置。

我遇到的问题是如何将 3 个 ZDATA(或 XDATA、YDATA)寄存器读取为单个值。我尝试了两种方法。这是第一个:

double values[3];
Wire.beginTransmission(addr);
  Wire.write(0x08); // ACCEL_XAXIS
  Wire.endTransmission();
  Wire.requestFrom(addr, 9, true); // Read 9, 3 for each axis
  byte x1, x2, x3;
  for (int i = 0; i < 3; ++i){
    x3 = Wire.read();
    x2 = Wire.read();
    x1 = Wire.read();
    unsigned long tempV = 0;
    unsigned long value = 0;
    value = x3;
    value <<= 12;
    tempV = x2;
    tempV <<= 4;
    value |= tempV;
    tempV = x1;
    tempV >>= 4;
    value |= tempV;
    values[i] = SCALEFACTOR * value;
  }

这将产生接近 1g 负重力和 3g 正重力的值。此外,未加载的轴有时会显示超标高而不是 -0.0g。它们从 0.0 到 4.0 g 弹跳。这告诉我我有一个标志问题,我确信这来自使用unsigned long。所以我尝试将其读取为 16 位值并保留符号。

double values[3];
  Wire.beginTransmission(addr);
  Wire.write(0x08); // ACCEL_XAXIS
  Wire.endTransmission();
  Wire.requestFrom(addr, 9, true); // Read 9, 3 for each axis
  byte x1, x2, x3;
  for (int i = 0; i < 3; ++i){
    x3 = Wire.read();
    x2 = Wire.read();
    x1 = Wire.read();
    long tempV = 0;
    long value = 0;
    value = x3;
    value <<= 8;
    tempV = x2;
    value |= tempV;
    values[i] = SCALEFACTOR * value;
  }

这个产生的值在符号方面是好的,但它们(正如预期的那样)比它们应该的要低得多。我试图创建一个像 long value:20; 这样的 20 位数字,但我收到了

':' 标记之前的预期初始化程序

int 出现同样的错误。

如何正确读取 3 个寄存器以获得正确的 20 位值?

【问题讨论】:

  • 我正在尝试做同样的事情:用昂贵的加速器测试这个加速器。正如其他用户在here 中所描述的那样,由于输出值停留在所选范围内,我的日子很不好过。这个加速度计有同样的问题还是一切正常?

标签: arduino


【解决方案1】:

首先,在使用左右移位运算符时,您确实希望使用无符号类型(请参阅this question)。

查看avr-gcc type layout,我们了解到long 以 4 个字节(即 32 位)表示,因此它们足够长(没有双关语)以“保存”您的 20 位数字(XDATA、YDATA、和 ZDATA)。另一方面,int 以 2 个字节(即 16 位)表示,因此不应在您的情况下使用。

根据您在第 33 页链接的数据表,数字格式为 two's complement。您的第一个示例正确设置了 最后 20 位 您的无符号 32 位长 value(特别是左对齐处理 - 将 x1 右移四位 - 看起来已经正确)但是“新”的 12 个最高有效位始终设置为 0

要执行sign extension,如果数字是正值,则需要将“新”12 个最高有效位设置为0,如果数字是负值,则需要设置1(改编您的第一个示例) :

...
value |= tempV;
if (x3 & 0x80) /* msb is 1 so the number is a negative value */
    value |= 0xFFF00000;

从那里,您应该观察到的行为与以前大致相同:高正值而不是小的负值(但甚至比以前更高)。这是因为尽管您的value 按位正确,但它仍然解释为无符号。这可以通过强制编译器使用 value 作为签名来解决:

values[i] = SCALEFACTOR * (long)value;

现在它应该可以工作了。

请注意,此答案使用您的 C/C++ 实现使用二进制补码表示负整数这一事实。虽然在实践中非常罕见,但该标准允许其他表示形式(参见this question 示例)。

【讨论】:

    【解决方案2】:

    这是使其工作的一种方法。它确实对有符号值使用了位移。各种消息来源都说这是一个潜在的错误,因为它是由实现定义的。它在我的平台上运行。

    typedef union {
      byte bytes[3];
      long value:24;
    } accelData;
    
    double values[3];
    Wire.beginTransmission(addr);
    Wire.write(0x08); // ACCEL_XAXIS
    Wire.endTransmission();
    Wire.requestFrom(addr, 9, true); // Read 9, 3 for each axis
    accelData raw;
    for (int i = 0; i < 3; ++i){
      raw.bytes[2] = Wire.read();
      raw.bytes[1] = Wire.read();
      raw.bytes[0] = Wire.read();
      long temp = raw.value >> 4;
      values[i] = SCALEFACTOR * (double)temp;
    }
    

    我更喜欢 Alexandre Perrin 提出的解决方案。

    【讨论】:

    • 另请注意,此实现依赖于字节序。
    • @Alexandre Perrin 你也是。这是不必要的差评
    • @PeterJ 据我了解,我的解决方案仅使用按位运算,无论字节顺序如何(与联合类型双关语不同),其工作方式相同。见this question
    【解决方案3】:
    union
    {
        struct
        {
            unsigned : 4;
            unsigned long uvalue : 20;
        };
        struct
        {
            unsigned : 4;
            signed long ivalue : 20;
        };
        unsigned char rawdata[3];
    }raw;
    
    for (int i = 0; i < 3; ++i){
      raw.bytes[2] = Wire.read();  //if most significant part is transfered first
      raw.bytes[1] = Wire.read();
      raw.bytes[0] = Wire.read();
    
      values[i] = SCALEFACTOR * (double)raw.ivalue;
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-04-11
      • 2010-10-13
      • 2020-12-14
      • 2023-01-11
      • 1970-01-01
      • 1970-01-01
      • 2018-05-29
      相关资源
      最近更新 更多