2012-08-12 21 views
3

我试图从STC3100电池监视器IC中读取值,但是我得到的值不正确。什么是数据表说:如何从两个寄存器中读取2的补码值为int

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?

+0

您能打印单个字节reg_l和reg_h以确保它们按预期方式出现吗? – cnicutar 2012-08-12 07:31:12

+2

你能解释你所得到的值是不正确的吗?你得到了什么(寄存器的值是'res_l'和'res_h'),你期望什么? – 2012-08-12 07:31:13

+0

该死的。删除我愚蠢的评论。绝对是一个好主意,分别打印res_l和res_h以查看它们是什么。 – 2012-08-12 07:39:15

回答

4

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

i2c_smbus_read_word_data()将从设备读取的16位作为无符号数量返回 - 如果有错误,则返回i2c_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将为负数。

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

如果你关心(__s16)投的实现定义方面,您可以通过使用算术,而不是投在咖啡厅的答复避免:

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; 
+0

哇,这个工程很好,解决方案非常简单!在最后一个问题之后,我会将此标记为正确答案:如果值为负,会发生什么情况?请记住,这些值可以是负数,并以“2的补码格式”存储。那么我的错误处理会失败?负值是否会出现在我的错误处理中,或者我应该如何处理负值? – Reto 2012-08-13 12:01:23

+0

@Reeto:我已经更新了答案,试图直接解决这些问题。 – 2012-08-13 13:24:02

2

从你的文章中不清楚i2c_smbus_read_word_data的数据类型是什么,但是如果可能返回负值,它不能只是无符号字节。我会使用res_l & 0xff 和res_h & 0xff,因为它们不应该包含任何感兴趣的内容。

2

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

您应该避免任何隐式转换并准确指定您想要发生的事情。隐式转换规则以及signed和unsigned之间的转换是神秘的,并且可能会产生意想不到的结果。将表达式拆分成更小的部分也将有助于调试,因为您将能够准确查看哪种类型的转换或按位操作不正确。

我还建议算术和按位运算的一致性优先选择(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_value一个__s16或铸造表达__s16来进行调整,但由于这已经绊倒你,我不会推荐它,它也可能绊倒后来必须维护或重用此代码的人。 Nontheless以下任何一种情况将工作:

__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)); 

因为一些不幸的维护者以后可能会认为这是一个错误,并试图“正确“吧!

1

您需要正确处理高位。要做到这一点最简单的方法是:

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

不要忘记检查,如果res_h交易失败了。

+0

我会乞求不同的“最简单”。在这个问题中,需求是分配给一个'int',但你已经把它改成了's32',这不一定是一回事。碰巧您的代码将工作,没有类型的,即使'int'是16位的变化,但测试会在这种情况下是多余的(即总是假的),所以如果你要改变的类型,为什么不使用'__s16 '完全省略测试?将'__s16'分配给任何大小的'int'都有明确定义和正确的结果,有或没有明确的转换。 – Clifford 2012-08-12 11:26:24

+0

@Clifford:如果'int'分别为16位,然后res_h << 8'分配|如果设置了“res_h”的最高位,res_l'会在技术上给出实现定义的结果。从代码中可以清楚地看到'temp_value'变量在转换为'float'之前是一个临时中间变量。 – caf 2012-08-12 22:03:01

+0

它确实是实现定义的,但不是位模式意义上的,只是在它的解释中。这是一个有效的观点,你的代码*可以移植到非2的补充体系结构,但我没有看到它们中的很多! ;-)。我把'temp_value'表示为“温度值”,而不是“临时值”,这在上下文中似乎是合理的。我认为转换为浮动只是为了显示调试文本。使用“temp”表示变量名中的临时变量总是一个坏主意,在温度传感器代码中是这样的! – Clifford 2012-08-13 14:17:37