4

我正在尝试从 STC3100 电池监视器 IC 读取值,但我得到的值不正确。数据表中的内容:

The temperature value is coded in 2’s complement format, and the LSB value is 0.125° C.

REG_TEMPERATURE_LOW, address 10, temperature value, bits 0-7
REG_TEMPERATURE_HIGH, address 11, temperature value, bits 8-15

这是数据表:http: //www.st.com/internet/com/TECHNICAL_RESOURCES/TECHNICAL_LITERATURE/DATASHEET/CD00219947.pdf

我的代码中有什么:

__u8 regaddr = 0x0a; /* Device register to access */
__s32 res_l, res_h;

int temp_value;
float temperature;

res_l = i2c_smbus_read_word_data(myfile, regaddr);
regaddr++;
res_h = i2c_smbus_read_word_data(myfile, regaddr);
if (res_l < 0) {
  /* ERROR HANDLING: i2c transaction failed */
} else {
  temp_value = (res_h << 8)+res_l;
  temperature = (float)temp_value * 0.125;
  printf("Temperature: %4.2f C\n", temperature);
}

我究竟做错了什么?这不是我应该如何将 2 的补码值复制到 int 中吗?

4

4 回答 4

6

i2c_smbus_read_word_data()将从设备上您指定的寄存器开始读取 16 位,因此单个i2c_smbus_read_word_data()将使用单个 i2c 事务读取您感兴趣的两个寄存器。

i2c_smbus_read_word_data()返回从设备读取的 16 位作为无符号数 - 如果有错误,则返回 fromi2c_smbus_read_word_data()将为负数。您应该能够像这样读取温度传感器:

__u8 regaddr = 0x0a; /* Device register to access */
__s32 res;

int temp_value;
float temperature;

res = i2c_smbus_read_word_data(myfile, regaddr);

if (res < 0) {
  /* ERROR HANDLING: i2c transaction failed */
} else {
  temp_value = (__s16) res;
  temperature = (float)temp_value * 0.125;
  printf("Temperature: %4.2f C\n", temperature);
}

要解决评论中的问题:

i2c_smbus_read_word_data()如果没有错误,该函数将从 i2c 总线获得的 16 位数据作为无符号 16 位值返回。16 位无符号值可以很容易地用函数返回的 32 位 int 表示,因此根据定义,16 位数据不能为负数。res当且仅当出现错误时才会为负。

(__s16)将16 位值解释为(可能为负)二进制补码值由res. 这将获取其中的值res并将其转换为有符号的 16 位int表示形式。严格来说,它是实现定义的,关于这个演员将如何处理负数。我相信在 Linux 实现上,这将始终简单地将低 16 位res视为二进制补码数。

如果您担心强制转换的实现定义方面(__s16),您可以通过使用算术而不是像 caf 的答案中的强制转换来避免它:

temp_value = (res > 0x7fff) ? res - (0xffff + 1) : res;

即使您碰巧在一个补码机器上运行,它也会正确转换为负值(Linux 甚至支持在这样的机器上运行吗?)。

另请注意,上面发布的代码假设您在小端机器上运行 - 在将数据转换为负值之前,您需要在大端机器上适当地交换字节,以下应该可以解决问题目标 CPU 表示整数值(大/小、一或二):

__u16 data = __le16_to_cpu( (__u16) res);

// convert negative two's complement values to native negative value:
int temp_value = (data > 0x7fff) ? data - (0xffff + 1) : data;
于 2012-08-12T21:23:39.810 回答
2

从您的帖子中不清楚 i2c_smbus_read_word_data 的数据类型是什么,但如果可以返回负值,它就不能只是无符号字节。我会使用 res_l & 0xff 和 res_h & 0xff 作为偏执狂的练习,因为它们不应该包含任何感兴趣的东西。

于 2012-08-12T07:49:04.123 回答
2

在您的代码中,如果int碰巧是 32 位类型,则表达式 temp_value = (res_h << 8) + res_l;不会为负值生成正确的结果,因为连接是 16 位并且符号位没有扩展。

您可能应该避免任何隐式转换并准确指定您想要发生的事情。隐式转换规则以及有符号和无符号之间的转换是晦涩难懂的,可能会产生意想不到的结果。将表达式拆分为更小的部分也有助于调试,因为您将能够准确地看到哪种类型转换或按位运算不正确。

我还建议在算术和按位运算中保持一致,(a << 8) | b(a * 256) + b不是(a << 8) + b像你一样。

  __u8 tlow = (__u8)(res_l & 0xff) ;
  __u8 thigh = (__u8)(res_h & 0xff) << 8 ;
  __s16 temp_value = (__s16)((thigh << 8) | tlow);

  temperature = (float)temp_value * 0.125f ;
  printf("Temperature: %4.2hf C\n", temperature);

并不完全有必要对掩码和强制转换进行显式处理,或者像我使用其他变量那样将其分解,但它确实避免了必须知道混合类型表达式中发生的隐式转换的复杂细节,并且使它非常让读者和编译器清楚你打算发生什么。它还使调试变得更简单,因为您可以在调试器中查看这些中间值(您使用调试器对吧!?)。

如果您确实更喜欢简洁,那么您的原始代码可以简单地通过制作temp_valuea__s16或通过将表达式转换为来纠正__s16,但由于这已经绊倒了您,我不推荐它,它也可能会绊倒后来必须维护的人或重用此代码。尽管如此,以下任何一项都将起作用:

__s16 temp_value = (res_h << 8) | res_l ;

或者

int temp_value = (__s16)((res_h << 8) | res_l);

最后一个至少有一个结果int,这既是您所要求的,而且在任何后续要执行的算术运算方面可能更安全。

如果你想表明你真的打算转换为__s16然后分配给一个int,那么让它明确:

int temp_value = (int)((__s16)((res_h << 8) | res_l));

因为一些倒霉的维护者以后可能会认为这是一个错误并试图“纠正”它!

于 2012-08-12T10:19:29.970 回答
1

您需要正确处理高位。最简单的方法是:

s32 temp_value = (res_h << 8) | res_l;
if (temp_value > 32767)
    temp_value -= 65536;

不要忘记检查res_h交易是否也失败了。

于 2012-08-12T10:36:58.150 回答