巧用补码(如何转化器件的补码数据?)
导读:网上关于二进制补码数据的负数都是采用取反+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
单片机中的CY与OV详细解析
CY(Carry): 用于表示加法进算中的进位和减法运算中的借位,加法运算中有进位或减法运算中有借位则CY位置1,否则为0
OV: 表示运算过程中是否发生了溢出,若运算结果超过了8位二进制数所能表示数据的范围即有符号数-128~+127,则标志位置1。
单片机中的CY与OV详细解析
对无符号数的运算,判断只需CY即可,OV无作用。
对有符号数的运算,OV位是有用的。“OV位是C6位进位与C7位进位的异或”,说法对的(对51单片机而言),但不同的计算机说法不一
CY位是累加器的进位、借位标志。下文的叙述按16位机来举例说明,如果是8位机或其它字长,则可换一个例子,但道理相似。
对于无符号数的运算,CY位就可以表示其是否溢出。但如果是有符号数,则不能按CY标志来判断了。为此,设了另一个标志OV,其含义就是“假如是有符号数运算,是否出现了溢出”。
例如对于16位运算器,65534 + 3,(即二进制的1111111111111110 + 0000000000000011),
本该得65537,(即二进制的10000000000000001),但因为寄存器只有16位,最高位的那个1丢掉了(进入了CY标志)。结果寄存器中只剩下了1,(即二进制的0000000000000001)。
此时,我们可以说,16位的无符号数加法,65534+3溢出了,溢出后的答案成了1。
但是对于有符号整数,情况就不同了。有符号整数采用补码表示法。16位有符号整数不可能表示65534,此时如果机内二进制是1111111111111110,程序中认为它是-2,故:
机内的二进制的1111111111111110 + 0000000000000011,代表的是(-2) + 3。
请注意,此时的(-2)+3和上文的无符号数65534+3,在CPU的运算器硬件上完全相同,都是得到和为1,而CY标志也为1。
但是,有符号数(-2)+3=1并无溢出。故此时的CY标志不能代表它溢出了。
另外再举一例:
无符号数32763 + 8 = 32771,没有进位,CY标志为0。此时并不溢出。
但是,如果是有符号数32763 + 8,这就是溢出了,因为32773的二进制为1000000000000011,作为有符号数会被看成负数-32765。16位有符号数不可能表示32773的。
不管是有符号数还是无符号数,CPU的二进制运算器机器加、减操作是一样的,但其“溢出”的条件不同。
现在大多数的计算机中,如果是无符号数,都可以用CY标志来判断其是否溢出;而如果是有符号数,则需要用OV标志来判断其是否溢出。
至于OV标志在逻辑上又是根据什么产生的呢?则不同的计算机上有不同的实现方法,但效果都是一样。
这里介绍一种道理比较容易懂的方法:“双符号位法”。具体是:
作加、减法前,先将两个运算数都按照有符号数的规则扩充成17位。即:符号位是0的前面添一位0,符号位是1的前面添一位1。
然后按17位的机器加、减,得出17位的结果。
如果17位结果的高两位(即双符号位)不同,就置OV标志为1,否则,OV标志为零。
相关问答
单片机 原理及应用中.补码是怎样表示 负数 的?51单片机最小内存单元是B1B=8bit所以用这8bit的最高位表示1或者0表示正数或者负数,如果是负者,该为置1,其他位做相应的补码转换51单片机最小内存单元是B...
如何用51 单片机 读取ds18b20的取负温度?求教.18b20的ram中,前...[最佳回答]判断是否是负,就是取高几位的读取值采用与的方式判断,比如(000)1111100001000,那么高5位可以这样弄,tempH&0x1f,如果这个值=1;说明是负的,否则就...
单片机 X2=-1010110反码是10101001请问补码是什么?10101010负数的补码则是符号位为“1”,数值部分按位取反后再在末位(最低位)加1。也就是“反码+1”。例:[-7]原=10000111B[-7]反=11111000B[-7]补=11111001...
单片机 的CY位有什么作用-ZOL问答单片机CY(Carry):用于表示加法进算中的进位和减法运算中的借位,加法运算中有进...例如,在进行算术运算后,检查CY的状态可以帮助判断结果是否为负数或者完成加减...
已知A=0F8H,当执行ADDA,#0A9H指令后,PSW中的OV、CY、AC、P各...[回答]OV,溢出,=1CY,有进位,=1AC,半进位,=1P,偶检验,=10xF8+0xA9为0x1A1,结果取8位,=0xA1最高位符号位为1,表示负数,补码的原码为取反加1,即0x5F,表示十...
工作当中这几种直流电压等级你可曾了解?-盖德问答-化工人互助...因此我们这些电工从业者有必要了解一下这些经常遇到的直流电压它们的作用,方便日后的工作。首先来看,如果我们面对的电气控制设备内含有单片机这类...
单片机 调用中断服务子程序和调用子程序的区别是什么?基于51单片机中断跳出指令“RETI”浅议"最近在基于51单片机编程的过程中出现了个很奇怪的问题“程序执行中在寄存器EA=1,ET0=1,TR0=1条件下,单TF...
60吨数字汽车衡 归零称重怎么没有反应?[回答]60吨数字汽车衡地磅的使用人员常常会反应一个问题,就是同一个物品,放在四个角上跟放在中间,称出来的份量不一样,其实这就是所谓的角差。那我们该怎...
电气自动化基础知识有哪些 - Fengyejiao 的回答 - 懂得电工基础、模电/数电、低压电器、PLC、单片机,我在学校学的就是这些。电力行业,电气工程及其自动化专业名词解释:1、三相交流电:由三个频率相同、...
角度传感器MMA7455模块怎么连接电路,与 单片机 I2C如何使用。...[最佳回答]INT1引脚一般作为数据准备好中断DRDY,用于提示测量数据已经准备好,同时在状态寄存器(STATUS地址0X09)中的DRDY位也会置位,中断时输出高电平,并一直...