数字电路原码反码补码讲解(如何转化器件的补码数据)
数字电路原码反码补码讲解(如何转化器件的补码数据)1、首先判断正负数来看看作这些处理需要多少步:那么如何将补码转化为我们需要的数据呢?网上很多方法对负数求原码是采用补码取反 1 的形式进行转化,但是真的需要这么麻烦吗?先来验证一般方法的准确性:1000 0001b 取反为 0111 1110b 再加 1 就是 0111 1111b 十进制为 127,转换正确。因为已经判断过是负数(负数才需要转化,正数的补码就是原码)然后通过打印函数 printf 打印出来。
导读:网上关于二进制补码数据的负数都是采用取反 1进行转换,但是真的需要这么麻烦吗?这篇文章将让你真正理解补码!
假设一个 ADC 转换芯片的转化数据为补码形式,24 位精度。最大电压值为 0x7fffff,最小电压值为 0x800000,转化为十进制如下表:
先思考简单的,了解一下什么是补码:
看 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,又该怎么办,是不是又得走上老路,按部就班的进行转化呢?如果真是这样,我就不会专门写一篇了,前面写了那么多,就是为了引出这个啦!
上面的是 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 环境,其他环境不敢保证这条语句的正确性哦。
---------------------------------------------------------------------------------------------------------------------------------------------2018/07/30 Osprey