zoukankan      html  css  js  c++  java
  • 巧用补码

    假设一个 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

  • 相关阅读:
    WCF步步为营(三):使用配置文件改变使用服务的方式
    WCF步步为营(五):数据契约
    弹性工作制下的IT项目管理
    C#拾遗系列(8):异常
    WCF步步为营(一):简单示例
    敏捷的 "道"
    从中国男足看项目管理
    WCF步步为营(二):使用配置文件改变发布服务的方式
    WCF步步为营(四):客户端使用代理类使用服务
    C#拾遗系列(9):继承、接口、扩展方法、分部类、类操作、Ref and Out、可空类型
  • 原文地址:https://www.cnblogs.com/CodeWorkerLiMing/p/12007397.html
Copyright © 2011-2022 走看看