Как читать значение дополнения 2 из двух регистров в int
Я пытаюсь считывать значения с монитора батареи 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 Cn", temperature);
}
Что я делаю не так? Разве не так я должен скопировать значение дополнения 2 в int?
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); }
Для ответа на вопросы из комментариев:
Функция
Интерпретация 16-битного значения как (возможно, отрицательного) значения дополнения двух обрабатываетсяi2c_smbus_read_word_data()
возвращает 16 бит данных, полученных из шины i2c в виде 16-битного значения без знака, если ошибки нет. 16-битное беззнаковое значение может быть легко представлено в 32-битном int, возвращаемом функцией, поэтому по определению 16-битные данные не могут быть отрицательными.res
будет отрицательным тогда и только тогда, когда есть ошибка.(__s16)
приведениемres
. Это принимает то значение, которое находится вres
и преобразует его в знаковое 16-битное представлениеint
. Строго говоря, это реализация, определенная относительно того, как отрицательные числа будут обработаны этим броском. Я считаю, что в реализациях Linux это всегда будет просто относиться к нижним 16 битамres
как к дополнительному числу двойки.Если вас беспокоит определенный аспект реализации приведения
(__s16)
, вы можете избежать его, используя арифметику вместо приведения, как в ответе caf:temp_value = (res > 0x7fff) ? res - (0xffff + 1) : res;
Который выполнит правильное преобразование к отрицательному значению, даже если вы работаете на своей машине дополнения (Linux вообще поддерживает запуск на такой вещи?).
Также обратите внимание, что приведенный выше код предполагает, что вы работаете на машине с малым концом - вам нужно будет соответствующим образом поменять байты на машине с большим концом, прежде чем преобразовывать данные в отрицательное значение, следующее должно сделать трюк, однако целевой процессор представляет целочисленные значения (big/little, one' или two):
__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;
Из Вашего сообщения не ясно, что такое тип данных i2c_smbus_read_word_data, но если возможно возвращать отрицательные значения, то это не могут быть просто неподписанные байты. Я хотел бы использовать res_l & значение 0xFF и res_h & 0xff просто как упражнение в паранойе, так как они не должны содержать ничего интересного.
В вашем коде, если
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_value
A__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));
Потому что какой-нибудь незадачливый сопровождающий может позже подумать, что это ошибка. и попытайтесь это "исправить"!