巧用补码

假设一个 ADC 转换芯片的转化数据为补码形式,24 位精度。最大电压值为 0x07 fffff,最小电压值为 0x80 0000,转化为十进制如下表:

十六进制    十进制
0x7fffff    8388607
0x800000    -8388608
先思考简单的,了解一下什么是补码:

二进制    十六进制    十进制
0111 1111    0x7f    127
0000 0000    0x00    0
1111 1111    0xff    -1
1000 0001    0x81    -127
1000 0000    0x80    -128
看 +127 的二进制 0111 1111b,再加 1 为 1000 0000b,马上变成负数最小值 -128。如果把 1000 0000b 看成无符号常数,那么这个数就是+128。再这个数的基础上加 1,就是1000 0001b,看成无符号常数就是 129,但是看成有符号常数就是 -127,因为最高位为 1 ,那么必然是负数,不可能是 129。通过分析这些数据,自己也能发现规律。

那么如何将补码转化为我们需要的数据呢?网上很多方法对负数求原码是采用补码取反 + 1 的形式进行转化,但是真的需要这么麻烦吗?

先来验证一般方法的准确性:

1000 0001b 取反为 0111 1110b 再加 1 就是 0111 1111b 十进制为 +127,转换正确。因为已经判断过是负数(负数才需要转化,正数的补码就是原码)然后通过打印函数printf打印出来。

来看看作这些处理需要多少步:

1、首先判断正负数
2、如果是正数,不转化,如果是负数,取反 + 1。
3、当你显示出来的时候就需要在显示前加负号才对。这样才能显示正确。

但是你有没有发现,干嘛要这么麻烦,既然计算机存储是用补码形式,你接收的数据也是补码形式,直接用不就行啦。把它当成有符号的数据直接使用就行了,不管做什么语句处理也是应该是没问题的:

    if(AD_Value > -125)
    {
    
    }
    else if(AD_Value > 125)
    {
    
    }

当你接收的AD转化数据直接放到有符号的 AD_Value 变量里面,难道这些判断就会有问题,难道就必须要转化才能进行其他处理?这不是多此一举嘛?直接把它看成有符号变量使用就行了。

你可能会问,机器里面是知道这是补码,我想打印显示出来的时候总的进行转化吧?好像挺有道理。那你直接用printf函数打印不行嘛,这个函数又不是说只能输出正数,负数也是能显示出来的,而且还可以格式化输出,比你自己写的函数好用多了吧!但是有些有项目经验的又会问:我有多个输出位置需要进行输出,比如我要在LCD上显示温度,我还要在串口上打出AD值,而printf函数只能重定向一个位置,这样不是还得自己写一个打印函数吗?真的是这样吗?我们分析以下问题:

1、什么时候往什么地方进行输出我们知道吧?
2、打印的时候可以不同时打印是吧?
3、在一个位置需要打印的时候可以稍微等一等是吧?

如果这些问题的答案是肯定的,那么就有办法。在需要往串口打印的时候,重定向到串口;当需要打印到LCD的时候,可以重定向到LCD,怎么做,一个函数指针就能搞定的事。

设置一个函数指针,当需要打印到LCD的时候,将该指针指向LCD字符输出函数,当需要打印到串口的时候,指针指向串口字符输出函数,那么就能正确打印到相应的位置。

需要重定向的函数如下:

int fputc(int ch, FILE *f)
{  
    PutChar(ch); // 打印字符的函数指针
    return ch;
}

那么为什么要满足上面的条件呢?只有知道什么时候往什么地方输出才能修改函数指针。而后两个条件就是 printf 函数本身的限制了,它是一个不可重入函数,在往串口打印的时候你就别打断它,让它又往 LCD 打印,因为这样会破坏函数,导致打印出来的东西不伦不类。因此打印的时候只能往一个位置进行打印,在打印完之后才能再切换到下一个打印位置,这势必引出第三个问题的思考。这里可以采用锁的方式进行处理,正在打印的时候就上锁,不打印的时候就释放锁,让别人使用。

题外话说的好像比较多,继续说补码。

既然你都说不用进行转化了,那么就没什么好说的了,但是我所说的不用转化是在数据刚好是 8bit,16bit,32bit,64bit 的情况下,这样机器就可以直接使用,但是如果AD转化的数据是 10bit,12bit,20bit,24bit,又该怎么办,是不是又得走上老路,按部就班的进行转化呢?如果真是这样,我就不会专门写一篇了,前面写了那么多,就是为了引出这个啦!

十六进制    十进制
0x7f ffff    8388607
0x7f fffe    8388606
0xff ffff    -1
0x80 0001    -8388607
0x80 0000    -8388608
上面的是 24 位的情况,好像和 8bit,16bit 这些数据的补码类似,都是全为 1 的时候为 -1,在最大数加 1 的时候变成最小值。

怎么处理呢?一条语句就OK!

int  AD_Value;  //这条不能算哈,但很关键
AD_Value= ((AD_Value<< 8) >> 8);

看到这一条语句是不是觉得这个人有病啊,左移完8位又右移8位,这不是闲着没事干吗?在思考出来之前,我也上网找过方法,因为我感觉应该存在一种简单的方法进行转化的,所以想上网看看能不能找到,如果能找到最好,实际上我没找到,网上可能也是有这个方法的,只是可能是我运气不好,没找到罢了。就算作是我的原创好啦!手动纯洁微笑脸。另外使用 printf 函数进行多方打印(重点是往 LCD 打印)也是我自己突发奇想的(事实上早就有人这么干过了),再加一个手动微笑脸。

好了,不扯了,再扯就晚了!

说重点,为什么这样处理就能达到我们想要的效果呢?原来位移操作有一个特性,利用这个特性就能将24位的补码转化正32位的补码形式,不对啊,怎么还是补码,不是说好了转化成原码啊,如果你还在纠结这个,你还没理解我前面所写的东西,再去前面看看吧,少年!真不扯了,继续这个特性:这个特性就是当有符号变量进行位移操作的时候,如果高位为 1,进行右移时,高位补 1;如果高位为零,右移时高位补 0,这就是和无符号变量处理的不同!亲测哦亲!也就是说在进行右移的时候它已经进行了符号位的判断了,首先通过左移8位,让最高位为1或0,然后再右移8位,根据右移的特性就完成了将24位有符号补码值转化为32位有符号变量,并且这种转化是不会影响数据的大小的。其它位数的转化同理。另外当前测试条件为 KEIL ARM 环境,其他环境不敢保证这条语句的正确性哦。

手机碎片化阅读,欢迎关注公众号:鱼鹰谈单片机
 ———————————————— 
版权声明:本文为CSDN博主「EmbeddedOsprey」的原创文章,遵循CC 4.0 by-sa版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_42876465/article/details/84501802

posted @ 2019-08-20 14:27  wdliming  阅读(543)  评论(0编辑  收藏  举报