
1.5 有符号与无符号数应用、数位分解、位操作
Keil C51的数据类型如表1-3所示。
表1-3 Keil C51的数据类型

本书大多数案例使用的都是无符号数,对于255以内的整数,本书全部定义为uchar类型(相当于字节类型byte),对于0~65535范围内的整数,本书定义为uint类型(相当于字类型word)。有关案例因为涉及正负数的处理,例如,温度控制程序中有零上温度与零下温度,由于温度管实际上可处理的范围为-55℃~125℃,为了使程序对温度值进行正确比较,程序中将温度类型定义为char类型(即signed char类型,其取值范围为-128~127)。
另外,本书大量用到的延时程序中既有uchar类型,也有uint类型,如果读者在编写代码时,给某变量赋值为2000,但该变量类型为uchar,编译程序并不会报错,但变量实际上并没得到所期望的值。
本书大量案例要将整数或浮点数显示在数码管上,这需要对待显示数据各数位进行分解,例如:
uchar d = 124; uchar c[3]; c[0] = d / 100; c[1] = d / 10 % 10; c[2] = d % 10;
又如:
float x = 123.45;
如果要得到x的各个数位,可以先将x乘以100,然后再分解各数位:
uint y = x*100; uchar c[5]; uchar i; for (i = 4 ; i!=0xFF; i--) { c[i] = y % 10; y /= 10; }
上面for循环中的循环条件本来要写成i>=0的,但由于当i = 0时,如果将i再减1,i变为0xFF,这个无符号数仍被认为≥0,这样就不能保证5次循环了,因此要改写成i!=0xFF,如果将i定义成char类型而不是uchar类型,使用i>=0时才能得到正确结果。
上述分解常用于数码管数字显示,因为显示时需要根据各数位提取数码管段码。
在本书案例中会大量出现位操作,例如,在有关LED流水灯、数码管位控制、串行收发、键盘扫描等大量案例中,位的各操作符和有关函数都会频繁使用,例如,字节位循环左移函数(_crol_)、循环右移函数(_cror_),位左移(<<)、右移(>>)、与(&)、或(|)、取反(~)、异或(^)、非(!)等,读者都要熟练掌握。
下面是有关位操作的几个简单应用。
例如,要P1端口P1.7~P1.0逐位循环轮流置1,可先设字节变量c = 0x01,然后在循环语句中执行c = _cror_(c,1),并使P1 = c,这样即可使P1端口出现10000000,01000000,00100000,…,00000001,如此重复。如果要P1.7至P1.0逐位循环轮流置0,可先设c = 0xFE,然后使用同样的循环移位操作即可。这样的位操作在LED流水灯或集成式数码管位码控制时常会用到。
又如,已知P2.3连接外部LED或蜂鸣器,如果需要LED闪烁或蜂鸣器发声,可先定义sbit LED = P2^3或sbit BEEP = P2^3,然后在循环中执行语句LED = ~LED或BEEP = ~BEEP即可,注意,对于独立的位定义,执行取反(~)与非(!)操作效果是一样的,但对于字节变量则是不同的,这一点要注意。
另外,在本书4×4键盘矩阵扫描程序中,假设P1高4位连接矩阵行,低4位连接矩阵列,为判断整个键盘中是否有键按下,通常会先在行上发送4位扫描码0000,即P1 = 0x0F,然后检查列上是否出现0,这时使用的位操作语句是:
if ( (P1 & 0x0F) != 0x0F) {//有键按下}
因为!=的优先级高于&,该语句中的与操作表达式要加上括号。
本书多个案例使用了字符液晶,向连接在P0端口的液晶屏发送显示数据时,需要先判断液晶是否忙,其忙标志在读取字节的最高位,因此又有类似语句:
if ( (P0 & 0x80) == 0x80) {//液晶忙}