Difference between two C code reading register - c

I would like to know the difference between these 2 codes. The first
int32_t lsm6dso32_temperature_raw_get(stmdev_ctx_t *ctx, int16_t *val)
uint8_t buff[2];
int32_t ret;
ret = lsm6dso32_read_reg(ctx, LSM6DSO32_OUT_TEMP_L, buff, 2);
val[0] = (int16_t)buff[1];
val[0] = (val[0] * 256) + (int16_t)buff[0];
return ret;
}
And this one
//some code here
uint8_t buffer[14];
data_reg.read(buffer, 14);
rawTemp = buffer[1] << 8 | buffer[0];
//some other code here
The first code comes from ST and the second comes from Adafruit, all these code are made for LSM6DSO32 6 dof captor. I'am only interested in reading byte. what is the difference between
val[0] = (int16_t)buff[1];
val[0] = (val[0] * 256) + (int16_t)buff[0];
And this
rawTemp = buffer[1] << 8 | buffer[0];
Is one solution better than the other ? All of these code read raw temperature from the captor.
Edit rawTemp is int16_t

The two methods may be identical in their defined behavior, depending on the type of rawTemp, which we are not shown. However, neither is a generally correct way to decode an int16_t value from two eight-bit bytes.
Considering this code first:
val[0] = (int16_t)buff[1];
val[0] = (val[0] * 256) + (int16_t)buff[0];
buff[1] is uint8_t, so its values range from to 255, so val[0]*256 can range from 0 to 255•256 = 65,280. val[0] is int16_t, so its values range from −32,768 to +32,767. So this assignment may exceed what can be stored in val[0]. In that case, the result of the conversion is implementation-defined.
Further, the type of val[0]*256 is int (due to the usual arithmetic conversions). So, if int is 16 bit, then val[0]*256 may overflow what is representable in int, in which case the behavior is undefined.
Considering this code:
int16_t rawTemp;
rawTemp = buffer[1] << 8 | buffer[0];
buffer[1] << 8 may also overflow int. Since the behavior is undefined, an overflow is not required to produce the same result as the above code, particularly since the operation is different (shift versus multiplication) and it is combined with the other byte in a different way (bitwise OR instead of addition).
As with the first code, an implementation-defined conversion may occur in the assignment. If so, and the right side of the assignment does not overflow (perhaps because int is 32 bits), then the conversion will produce the same result as in the first code, since it is implementation-defined.
In the absence of assurance that the first byte is less than 128 or that int is more than 16 bits and the implementation-defined conversion wraps modulo 216, neither of these is a correct way to decode an int16_t from two eight-bit bytes.
C is deficient in good ways to reassemble a signed integer from bits. The various arithmetic and bit operators get hung up on the sign bit and overflows. One method is to assemble an unsigned integer and copy its bits into a signed integer:
uint16_t t = (uint16_t) buffer[1] << 8 | buffer[0];
int16_t ret;
memcpy(&ret, &t, sizeof ret);
A good compiler will generate code that eliminates the memcpy.
(Note all of the above assumes a little-endian representation—the earlier byte, buffer[0] goes into the low bits (“little end”) of the value.)

Related

How to combine two hex value(High Value & Low Value) at two different array positions?

I received two hex values where at array[1] = lowbyte and at array[2] = highbyte where for my example lowbyte = 0xF4 and highbyte = 0x01 so the value will be in my example 1F4(500). So I want to combine these two values and compare but how do I do that without any library function?
Please help and sorry for my bad English.
I did some research and I found this as my solution and it seems to be working fine:
int temp = (short)(((HIGHBYTE) & 0xFF) << 8 | (LOWBYTE) & 0xFF);
Just a basic example showing how to combine values of two different variables into one:
#include <stdio.h>
int main (void)
{
char highbyte = 0x01;
unsigned char lowbyte = 0xF4; //Edited as per comments from #Fe2O3,
short int val = 0;
val = (highbyte << 8) | lowbyte; // If lowbyte declared as signed, then masking is required `lowbyte & 0xFF`
printf("0x%hx\n", val);
return 0;
}
Tested this on Linux PC.
Based on the answer where you converted to short, it seems you may want to combine the two bytes to produce a 16-bit two’s complement integer. This answer shows how to do that in three ways for which the behavior is fully defined by the C standard, as well as a fourth way that requires knowledge of the C implementation being used. Methods 1 and 3 are also defined in C++.
Given two eight-bit unsigned bytes with the more significant byte in highbyte and the less significant byte in lowbyte, four options for constructing the 16-bit two’s complement value they represent are:
Assemble the bytes in the desired order and copy them into an int16_t: uint16_t t = (uint16_t) highbyte << 8 | lowbyte; int16_t result; memcpy(&result, &t, sizeof result);.
Assemble the bytes in the desired order and use a union to reinterpret them: int16_t result = (union { uint16_t u; int16_t i; }) { (uint16_t) highbyte << 8 | lowbyte } .i;.
Construct the result arithmetically: int16_t result = ((highbyte ^ 128) - 128) * 256 + lowbyte;.
If it is given that the code will be used only with C implementations that define conversion to a signed integer to wrap, then a conversion may be used: int16_t result = (int16_t) ((uint16_t) highbyte << 8 | lowbyte);.
(In the last, the conversion to int16_t is implicit in the initialization, but a cast is used because, without it, some compilers will produce a warning or error, depending on switches.)
Note: int16_t and uint16_t are defined by including <stdint.h>. Alternatively, if it is given that short is 16 bits, then short and unsigned short may be used in place of int16_t and uint16_t.
Here is more information about the first three of these.
1. Assemble the bytes and copy
(uint16_t) highbyte << 8 | lowbyte converts to a type suitable for shifting without sign-bit issues, moves the more significant byte into the upper 8 bits of 16, and puts the less significant byte into the lower 8 bits.
Then uint16_t = …; puts those bits into a uint16_t.
memcpy(&result, &t, sizeof result); copies those bits into an int16_t. C 2018 7.20.1.1 1 guarantees that int16_t uses two’s complement. C 2018 6.2.6.2 2 guarantees that the value bits in int16_t have the same position values as their counterparts in uint16_t, so the copy produces the desired arrangement in result.
2. Assemble the bytes and use a union
(type) { initial value } is a compound literal. (union { uint16_t u; int16_t i; }) { (uint16_t) highbyte << 8 | lowbyte } makes a compound literal that is a union and initializes its u member to have the value described above. Then .i reads the i member of the union, which reinterprets the bits using the type int16_t, which is two’s complement as describe above. Then int16_t result = …; initializes result to this value.
3. Construct the result arithmetically
Here we start with the more significant byte separately, interpreting the eight bits of highbyte as two’s complement. In eight-bit two’s complement, the sign bit represents 0 if it is off and −128 if it is on. (For example, 111111002 as unsigned binary represents 128+64+32+16+8+4 =252, but, in two’s complement, it is −128+64+32+16+8+4 = −4.)
Consider highbyte ^ 128) - 128. If the first bit is off, ^ 128 turns it on, which adds 128 to its unsigned binary meaning. Then - 128 subtracts 128, producing a net effect of zero. If the first bit is on, ^ 128 turns it off, which cancels its unsigned binary meaning. Then - 128 gives the desired value. Thus (highbyte ^ 128) - 128 reinterprets the first bit to have a value of 0 if it is off and −128 if it is on.
Then ((highbyte ^ 128) - 128) * 256 moves this to the more significant byte of 16 bits (in an int type at this point), and + lowbyte puts the less significant byte in the less significant position. And of course int16_t result = …; initializes result to this computed value.

Convert 8 bit signed integer to unsigned and then convert to int32

I have a signed 8-bit integer (int8_t) -- which can be any value from -5 to 5 -- and need to convert it to an unsigned 8-bit integer (uint8_t).
This uint8_t value then gets passed to another piece of hardware (which can only handle 32-bit types) and needs to be converted to a int32_t.
How can I do this?
Example code:
#include <stdio.h>
#include <stdint.h>
void main() {
int8_t input;
uint8_t package;
int32_t output;
input = -5;
package = (uint8_t)input;
output = (int32_t)package;
printf("output = %d",output);
}
In this example, I start with -5. It temporarily gets cast to 251 so it can be packaged as a uint8_t. This data then gets sent to another piece of hardware where I can't use (int8_t) to cast the 8-bit unsigned integer back to signed before casting to int32_t. Ultimately, I want to be able to obtain the original -5 value.
For more info, the receiving hardware is a SHARC processor which doesn't allow int8_t - see https://ez.analog.com/dsp/sharc-processors/f/q-a/118470/error-using-stdint-h-types
The smallest addressable memory unit on the SHARC processor is 32 bits, which means that the minimum size of any data type is 32 bits. This applies to the native C types like char and short. Because the types "int8_t", "uint16_t" specify that the size of the type must be 8 bits and 16 bits respectively, they cannot be supported for SHARC.
Here is one possible branch-free conversion:
output = package; // range 0 to 255
output -= (output & 0x80) << 1;
The second line will subtract 256 if bit 7 is set, e.g.:
251 has bit 7 set, 251 - 256 = -5
5 has bit 7 clear, 5 - 0 = 5
If you want to get the negative sign back using 32-bit operations, you could do something like this:
output = (int32_t)package;
if (output & 0x80) { /* char sign bit set */
output |= 0xffffff00;
}
printf("output = %d",output);
Since your receiver platform does not have types that are less than 32 bits wide, your simplest option is to solve this problem on the sender:
int8_t input = -5;
int32_t input_extended = input;
uint8_t buffer[4];
memcpy(buffer, &input_extended, 4);
send_data(buffer, 4);
Then on the receiving end you can simply treat the data as a single int32_t:
int32_t received_data;
receive_data(&received_data, 4);
All of this is assuming that your sender and receiver share the same endianness. If not, you will have to flip the endianness in the sender before sending:
int8_t input = -5;
int32_t input_extended = input;
uint32_t tmp = (uint32_t)input_extended;
tmp = ((tmp >> 24) & 0x000000ff)
| ((tmp >> 8) & 0x0000ff00)
| ((tmp << 8) & 0x00ff0000)
| ((tmp << 24) & 0xff000000);
uint8_t buffer[4];
memcpy(buffer, &tmp, 4);
send_data(buffer, 4);
Just subtract 256 from the value, because in 2's complement an n-bit negative value v is stored as 2n - v
input = -5;
package = (uint8_t)input;
output = package > 127 ? (int32_t)package - 256 : package;
EDIT:
If the issue is that your code has if statements for values of -5 to 5, than the simplest solution might be to test for result + 5 and change the if statements to values between 0 and 10.
This is probably what the compiler will do when optimizing (since values of 0-10 can be converted to a map, avoiding if statements and minimizing predictive CPU flushing).
Original:
Type casting will work if first cast to uint8_t and then uint32_t...
output = (int32_t)(uint32_t)(uint8_t)input;
Of course, if the 8th bit is set it will remain set, but the sign won't be extended since the type casting operation is telling the compiler to treat the 8th bit as a regular bit (it is unsigned).
Of course, you can always have fun with bit masking if you want to be even more strict, but that's essentially a waste or CPU cycles.
The code:
#include <stdint.h>
#include <stdio.h>
void main() {
int8_t input;
int32_t output;
input = -5;
output = (int32_t)(uint32_t)(uint8_t)input;
printf("output = %d\n", output);
}
Results in "output = 251".

Bitwise operation in C language (0x80, 0xFF, << )

I have a problem understanding this code. What I know is that we have passed a code into a assembler that has converted code into "byte code". Now I have a Virtual machine that is supposed to read this code. This function is supposed to read the first byte code instruction. I don't understand what is happening in this code. I guess we are trying to read this byte code but don't understand how it is done.
static int32_t bytecode_to_int32(const uint8_t *bytecode, size_t size)
{
int32_t result;
t_bool sign;
int i;
result = 0;
sign = (t_bool)(bytecode[0] & 0x80);
i = 0;
while (size)
{
if (sign)
result += ((bytecode[size - 1] ^ 0xFF) << (i++ * 8));
else
result += bytecode[size - 1] << (i++ * 8);
size--;
}
if (sign)
result = ~(result);
return (result);
}
This code is somewhat badly written, lots of operations on a single line and therefore containing various potential bugs. It looks brittle.
bytecode[0] & 0x80 Simply reads the MSB sign bit, assuming it's 2's complement or similar, then converts it to a boolean.
The loop iterates backwards from most significant byte to least significant.
If the sign was negative, the code will perform an XOR of the data byte with 0xFF. Basically inverting all bits in the data. The result of the XOR is an int.
The data byte (or the result of the above XOR) is then bit shifted i * 8 bits to the left. The data is always implicitly promoted to int, so in case i * 8 happens to give a result larger than INT_MAX, there's a fat undefined behavior bug here. It would be much safer practice to cast to uint32_t before the shift, carry out the shift, then convert to a signed type afterwards.
The resulting int is converted to int32_t - these could be the same type or different types depending on system.
i is incremented by 1, size is decremented by 1.
If sign was negative, the int32_t is inverted to some 2's complement negative number that's sign extended and all the data bits are inverted once more. Except all zeros that got shifted in with the left shift are also replaced by ones. If this is intentional or not, I cannot tell. So for example if you started with something like 0x0081 you now have something like 0xFFFF01FF. How that format makes sense, I have no idea.
My take is that the bytecode[size - 1] ^ 0xFF (which is equivalent to ~) was made to toggle the data bits, so that they would later toggle back to their original values when ~ is called later. A programmer has to document such tricks with comments, if they are anything close to competent.
Anyway, don't use this code. If the intention was merely to swap the byte order (endianess) of a 4 byte integer, then this code must be rewritten from scratch.
That's properly done as:
static int32_t big32_to_little32 (const uint8_t* bytes)
{
uint32_t result = (uint32_t)bytes[0] << 24 |
(uint32_t)bytes[1] << 16 |
(uint32_t)bytes[2] << 8 |
(uint32_t)bytes[3] << 0 ;
return (int32_t)result;
}
Anything more complicated than the above is highly questionable code. We need not worry about signs being a special case, the above code preserves the original signedness format.
So the A^0xFF toggles the bits set in A, so if you have 10101100 xored with 11111111.. it will become 01010011. I am not sure why they didn't use ~ here. The ^ is a xor operator, so you are xoring with 0xFF.
The << is a bitshift "up" or left. In other words, A<<1 is equivalent to multiplying A by 2.
the >> moves down so is equivalent to bitshifting right, or dividing by 2.
The ~ inverts the bits in a byte.
Note it's better to initialise variables at declaration it costs no additional processing whatsoever to do it that way.
sign = (t_bool)(bytecode[0] & 0x80); the sign in the number is stored in the 8th bit (or position 7 counting from 0), which is where the 0x80 is coming from. So it's literally checking if the signed bit is set in the first byte of bytecode, and if so then it stores it in the sign variable.
Essentially if it's unsigned then it's copying the bytes from from bytecode into result one byte at a time.
If the data is signed then it flips the bits then copies the bytes, then when it's done copying, it flips the bits back.
Personally with this kind of thing i prefer to get the data, stick in htons() format (network byte order) and then memcpy it to an allocated array, store it in a endian agnostic way, then when i retrieve the data i use ntohs() to convert it back to the format used by the computer. htons() and ntohs() are standard C functions and are used in networking and platform agnostic data formatting / storage / communication all the time.
This function is a very naive version of the function which converts form the big endian to little endian.
The parameter size is not needed as it works only with the 4 bytes data.
It can be much easier archived by the union punning (and it allows compilers to optimize it - in this case to the simple instruction):
#define SWAP(a,b,t) do{t c = (a); (a) = (b); (b) = c;}while(0)
int32_t my_bytecode_to_int32(const uint8_t *bytecode)
{
union
{
int32_t i32;
uint8_t b8[4];
}i32;
uint8_t b;
i32.b8[3] = *bytecode++;
i32.b8[2] = *bytecode++;
i32.b8[1] = *bytecode++;
i32.b8[0] = *bytecode++;
return i32.i32;
}
int main()
{
union {
int32_t i32;
uint8_t b8[4];
}i32;
uint8_t b;
i32.i32 = -4567;
SWAP(i32.b8[0], i32.b8[3], uint8_t);
SWAP(i32.b8[1], i32.b8[2], uint8_t);
printf("%d\n", bytecode_to_int32(i32.b8, 4));
i32.i32 = -34;
SWAP(i32.b8[0], i32.b8[3], uint8_t);
SWAP(i32.b8[1], i32.b8[2], uint8_t);
printf("%d\n", my_bytecode_to_int32(i32.b8));
}
https://godbolt.org/z/rb6Na5
If the purpose of the code is to sign-extend a 1-, 2-, 3-, or 4-byte sequence in network/big-endian byte order to a signed 32-bit int value, it's doing things the hard way and reimplementing the wheel along the way.
This can be broken down into a three-step process: convert the proper number of bytes to a 32-bit integer value, sign-extend bytes out to 32 bits, then convert that 32-bit value from big-endian to the host's byte order.
The "wheel" being reimplemented in this case is the the POSIX-standard ntohl() function that converts a 32-bit unsigned integer value in big-endian/network byte order to the local host's native byte order.
The first step I'd do is to convert 1, 2, 3, or 4 bytes into a uint32_t:
#include <stdint.h>
#include <limits.h>
#include <arpa/inet.h>
#include <errno.h>
// convert the `size` number of bytes starting at the `bytecode` address
// to a uint32_t value
static uint32_t bytecode_to_uint32( const uint8_t *bytecode, size_t size )
{
uint32_t result = 0;
switch ( size )
{
case 4:
result = bytecode[ 0 ] << 24;
case 3:
result += bytecode[ 1 ] << 16;
case 2:
result += bytecode[ 2 ] << 8;
case 1:
result += bytecode[ 3 ];
break;
default:
// error handling here
break;
}
return( result );
}
Then, sign-extend it (borrowing from this answer):
static uint32_t sign_extend_uint32( uint32_t in, size_t size );
{
if ( size == 4 )
{
return( in );
}
// being pedantic here - the existence of `[u]int32_t` pretty
// much ensures 8 bits/byte
size_t bits = size * CHAR_BIT;
uint32_t m = 1U << ( bits - 1 );
uint32_t result = ( in ^ m ) - m;
return ( result );
}
Put it all together:
static int32_t bytecode_to_int32( const uint8_t *bytecode, size_t size )
{
uint32_t result = bytecode_to_uint32( bytecode, size );
result = sign_extend_uint32( result, size );
// set endianness from network/big-endian to
// whatever this host's endianness is
result = ntohl( result );
// converting uint32_t here to signed int32_t
// can be subject to implementation-defined
// behavior
return( result );
}
Note that the conversion from uint32_t to int32_t implicitly performed by the return statement in the above code can result in implemenation-defined behavior as there can be uint32_t values that can not be mapped to int32_t values. See this answer.
Any decent compiler should optimize that well into inline functions.
I personally think this also needs much better error handling/input validation.

fetch 32bit instruction from binary file in C

I need to read 32bit instructions from a binary file.
so what i have right now is:
unsigned char buffer[4];
fread(buffer,sizeof(buffer),1,file);
which will put 4 bytes in an array
how should I approach that to connect those 4 bytes together in order to process 32bit instruction later?
Or should I even start in a different way and not use fread?
my weird method right now is to create an array of ints of size 32 and the fill it with bits from buffer array
The answer depends on how the 32-bit integer is stored in the binary file. (I'll assume that the integer is unsigned, because it really is an id, and use the type uint32_t from <stdint.h>.)
Native byte order The data was written out as integer on this machine. Just read the integer with fread:
uint32_t op;
fread(&op, sizeof(op), 1, file);
Rationale: fread read the raw representation of the integer into memory. The matching fwrite does the reverse: It writes the raw representation to thze file. If you don't need to exchange the file between platforms, this is a good method to store and read data.
Little-endian byte order The data is stored as four bytes, least significant byte first:
uint32_t op = 0u;
op |= getc(file); // 0x000000AA
op |= getc(file) << 8; // 0x0000BBaa
op |= getc(file) << 16; // 0x00CCbbaa
op |= getc(file) << 24; // 0xDDccbbaa
Rationale: getc reads a char and returns an integer between 0 and 255. (The case where the stream runs out and getc returns the negative value EOF is not considered here for brevity, viz laziness.) Build your integer by shifting each byte you read by multiples of 8 and or them with the existing value. The comments sketch how it works. The capital letters are being read, the lower-case letters were already there. Zeros have not yet been assigned.
Big-endian byte order The data is stored as four bytes, least significant byte last:
uint32_t op = 0u;
op |= getc(file) << 24; // 0xAA000000
op |= getc(file) << 16; // 0xaaBB0000
op |= getc(file) << 8; // 0xaabbCC00
op |= getc(file); // 0xaabbccDD
Rationale: Pretty much the same as above, only that you shift the bytes in another order.
You can imagine little-endian and big-endian as writing the number one hundred and twenty tree (CXXIII) as either 321 or 123. The bit-shifting is similar to shifting decimal digtis when dividing by or multiplying with powers of 10, only that you shift my 8 bits to multiply with 2^8 = 256 here.
Add
unsigned int instruction;
memcpy(&instruction,buffer,4);
to your code. This will copy the 4 bytes of buffer to a single 32-bit variable. Hence you will get connected 4 bytes :)
If you know that the int in the file is the same endian as the machine the program's running on, then you can read straight into the int. No need for a char buffer.
unsigned int instruction;
fread(&instruction,sizeof(instruction),1,file);
If you know the endianness of the int in the file, but not the machine the program's running on, then you'll need to add and shift the bytes together.
unsigned char buffer[4];
unsigned int instruction;
fread(buffer,sizeof(buffer),1,file);
//big-endian
instruction = (buffer[0]<<24) + (buffer[1]<<16) + (buffer[2]<<8) + buffer[3];
//little-endian
instruction = (buffer[3]<<24) + (buffer[2]<<16) + (buffer[1]<<8) + buffer[0];
Another way to think of this is that it's a positional number system in base-256. So just like you combine digits in a base-10.
257
= 2*100 + 5*10 + 7
= 2*10^2 + 5*10^1 + 7*10^0
So you can also combine them using Horner's rule.
//big-endian
instruction = ((((buffer[0]*256) + buffer[1]*256) + buffer[2]*256) + buffer[3]);
//little-endian
instruction = ((((buffer[3]*256) + buffer[2]*256) + buffer[1]*256) + buffer[0]);
#luser droog
There are two bugs in your code.
The size of the variable "instruction" must not be 4 bytes: for example, Turbo C assumes sizeof(int) to be 2. Obviously, your program fails in this case. But, what is much more important and not so obvious: your program will also fail in case sizeof(int) be more than 4 bytes! To understand this, consider the following example:
int main()
{ const unsigned char a[4] = {0x21,0x43,0x65,0x87};
const unsigned char* p = &a;
unsigned long x = (((((p[3] << 8) + p[2]) << 8) + p[1]) << 8) + p[0];
printf("%08lX\n", x);
return 0;
}
This program prints "FFFFFFFF87654321" under amd64, because an unsigned char variable becomes SIGNED INT when it is used! So, changing the type of the variable "instruction" from "int" to "long" does not solve the problem.
The only way is to write something like:
unsigned long instruction;
instruction = 0;
for (int i = 0, unsigned char* p = buffer + 3; i < 4; i++, p--) {
instruction <<= 8;
instruction += *p;
}

How to read 2’s complement value from two registers into an int

I am trying to read values from the STC3100 battery monitor IC, but the values I am getting are not correct. What the datasheet says:
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
This is the datasheet: http://www.st.com/internet/com/TECHNICAL_RESOURCES/TECHNICAL_LITERATURE/DATASHEET/CD00219947.pdf
What I have in my code:
__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);
}
What am I doing wrong? Is this not how I should copy a 2's complement value into an int?
i2c_smbus_read_word_data() will read 16 bits starting from your specified register on the device, so a single i2c_smbus_read_word_data() will read both registers that you're interested in using a single i2c transaction.
i2c_smbus_read_word_data() returns the 16 bits read from the device as an unsigned quantity - if there's an error, the return from i2c_smbus_read_word_data() will be negative. You should be able to read the temperature sensor like so:
__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);
}
To address questions from the comments:
The i2c_smbus_read_word_data() function returns the 16 bits of data obtained from the i2c bus as an unsigned 16-bit value if there's no error. A 16-bit unsigned value can easily be represented in the 32-bit int returned by the function, so by definition the 16-bits of data cannot be negative. res will be negative if and only if there's an error.
Interpreting the 16 bit value as a (possibly negative) two's complement value is handled by the (__s16) cast of res. This takes that value that's in res and converts it to a signed 16-bit int representation. Strictly speaking, it's implementation-defined regarding how negative numbers will be dealt with by this cast. I believe that on Linux implementations, this will always simply treat the lower 16 bits of res as a two's complement number.
If you're concerned about the implementation defined aspect of the (__s16) cast, you can avoid it by using arithmetic instead of a cast as in caf's answer:
temp_value = (res > 0x7fff) ? res - (0xffff + 1) : res;
Which will perform the correct conversion to a negative value even if you happen to be running on a one's complement machine (does Linux even support running on such a thing?).
Also note that the above posted code assumes you're running on a little-endian machine - you'll need to swap the bytes appropriately on a big-endian machine before converting the data to a negative value, The following should do the trick however the target CPU represents integer values (big/little, one' or two's):
__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;
It's not clear from your post what the data type of i2c_smbus_read_word_data is, but if it's possible to return negative values, it can't be just unsigned bytes. I would use res_l & 0xff
and res_h & 0xff just as an exercise in paranoia, since they are not supposed to contain anything of interest.
In your code, if int happens to be a 32bit type, the expression temp_value = (res_h << 8) + res_l; does not generate the correct result for negative values because the concatenation is 16 bit and the sign bit does not extend.
You should probably avoid any implicit conversions and specify precisely what you want to happen. Implicit conversion rules and conversion between signed and unsigned are arcane and can produce unexpected results. Splitting the expression up into smaller parts will also aid debugging as you will be able to see exactly which type conversion or bit-wise operation is incorrect.
I would also suggest consistency in arithmetic and bitwise operations preferring either (a << 8) | b or (a * 256) + b rather than (a << 8) + b as you have.
__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);
It is not entirely necessary to be that explicit with the masking and casting or to break it down as I have with additional variables, but it does avoid having to know the intricate details of implicit conversions that occur in mixed type expressions, and makes it very clear to the reader and the compiler what you intend to happen. It also makes it simpler to debug since you can watch those intermediate values in your debugger (you use a debugger right!?).
If you do prefer terse then your original code can be corrected simply by making temp_value a __s16 or by casting the expression to __s16, but since this has already tripped you up, I would not recommend it, it may also trip up the guy who later has to maintain or reuse this code. Nontheless either of the following will work:
__s16 temp_value = (res_h << 8) | res_l ;
or
int temp_value = (__s16)((res_h << 8) | res_l);
The last one at least has the result in an int, which is both what you asked for and probably safer in terms of any subsequent arithmetic operations to be performed.
If you want to show that you really intended to cast to __s16 then assign to an int, then make it explicit:
int temp_value = (int)((__s16)((res_h << 8) | res_l));
because some hapless maintainer may later think it is an error and attempt to "correct" it!
You need to correctly handle the high bit. The easiest way to do this is:
s32 temp_value = (res_h << 8) | res_l;
if (temp_value > 32767)
temp_value -= 65536;
Don't forget to check if the res_h transaction failed, too.

Resources