最近在写一个AD驱动的时候遇到了一个问题:
24位(bit)的数据,以补码形式传上来,如何转换为正确的补码形式存储在上位机中,从而得到正确的电压值。
为什么这是一个问题呢?
在计算机中,所有的数据类型都是2的幂次方的bit长度,比如8bit,16bit,32bit。但是如果24bit的一个数字,以补码的形式传上来,赋值给一个32bit的数据类型,这里就会有坑。
这里的具体问题是,传上来的数据以byte为单位,3个byte组成一个24bit的数据,代表了一个电压值,这里是由于AD自身的属性。
举个例子,某个电压传上来的数据为0x80 0x80 0x80,软件需要将这3个byte合并为24bit的形式,也就是0x808080,这个操作通过位运算就可以进行计算,但是这里有2个问题:
- 24bit通过移位赋值给32bit,前边的高八位是怎么处理的?
- 如果将0x808080复制给一个32bit的int类型,那么符号位如何处理?
重点来了,上边说的这两个操作,windows下和linux下编程结果是不一样的,先看一下代码。
void main(){
char a = 0x80;
char b = 0x80;
char c = 0x80;
int32_t temp;
temp = (a<<16) | (b<<8) | (c);
printf("%x\n", temp);
printf("%d\n", temp);
}
运行结果如下
808080
8421504
可以看到,16进制的输出正常,但是转换为10进制以后就不是我们要的结果了,因为很显然80开头,结果应该是一个负数啊。
我们改写一下代码,将结果逐步输出。
void main(){
char a = 0x80;
char b = 0x80;
char c = 0x80;
int32_t temp;
printf("%x\n", temp);
// temp = (a<<16) | (b<<8) | (c);
temp=a;
printf("%x\n", temp);
temp=temp<<8;
temp=(temp|(b));
printf("%x\n", temp);
temp=temp<<8;
printf("%x\n", temp);
temp=(temp|(c));
printf("%x\n", temp);
printf("%d\n", temp);
}
0
80
8080
808000
808080
8421504
可以看到,拼接移位的过程是按照我们想的来的,但是其实结果得到的是
0x00808080
也就是负数变正数了,当然如果本身就是正数,也就是24bit的数据最高位不是1,那么结果是对的,但是负数结果就不对了。这个时候可以这么操作。
void main(){
char a = 0x80;
char b = 0x80;
char c = 0x80;
int32_t temp;
if ((a&0x80)==0)
temp = (a<<16) | (b<<8) | (c);
else
temp = ((a<<16) | (b<<8) | (c)) | 0xff000000;
printf("%x\n", temp);
printf("%d\n", temp);
}
区分一下第一个byte的最高位是不是1,如果是1,需要将0x00808080变为0xFF808080,这样一个操作将符号位移位到最前边,在做补码到源码的操作的时候,不会出现错误。最终结果为
ff808080
-8355712
这个补码转换为10进制是对的。
这里我们可以看到,在linux下,不同长度数据在进行计算的时候,会给不足长度补0,这一点非常重要。
windows里边的处理方法又不一样了,直接说结论,windows下会在这种操作的时候补1,我们看一下位移过程。
先看一下windows下的代码
void main(){
char a = 0x80;
char b = 0x80;
char c = 0x80;
__int32 temdata;
temdata=a;
printf("%x\n", temdata);
temdata=temdata<<8;
printf("%x\n", temdata);
temdata=(temdata|(b));
printf("%x\n", temdata);
temdata=temdata<<8;
printf("%x\n", temdata);
temdata=(temdata|(c));
printf("%x\n", temdata);
printf("%d\n", temdata);
}
结果
可以看到,windows下虽然进行了补1的操作,但是在移位后的或运算上,后来补上的1会影响之前的数字,因此修改了代码
void main(){
char a = 0x80;
char b = 0x80;
char c = 0x80;
__int32 temdata;
temdata=a;
printf("%x\n", temdata);
temdata=temdata<<8;
printf("%x\n", temdata);
temdata=(temdata|(b&0x000000ff));
printf("%x\n", temdata);
temdata=temdata<<8;
printf("%x\n", temdata);
temdata=(temdata|(c&0x000000ff));
printf("%x\n", temdata);
printf("%d\n", temdata);
}
这样结果就是对的了。
结论
windows下和linux下在一些默认的处理上会有不同,尤其在跨平台的项目中尽量不要把一些数据有关的东西交给处理器自己去处理,比如我们定义的32bit的临时变量,就是一个我们不知道处理器会如何做的变量。