在单片机编程中,我们经常会用到一些无符号数与有符号数的混合运算,另外我们所用的单片机很有可能是16位或者8位的,这样,编程时所用的一些变量的取值范围会对我们的 运算有所限制.比如说8位的单片机无符号数最大值为255,有符号最大数为127;16位单片机无符号数最大值为65535,有符号数最大值为32767.对于32的单片机来说,因为我们一般所处理的值很少能超过有符号数的最大取值,所以比较少遇到下面出现的问题.
在一些运算中,我们希望有些数能表示正负,这就得用有符号数,而有些数的取值会超过有符号数的最大值,这时我们就得用无符数来表示.下面是我编程时遇到的两个问题(用的是MC9S12XS128处理器,16位的单片机).
变量的声明如下:
int iError;
unsigned int uiExpectSpeed;
unsigned int uiCurrentSpeed;
语句如下:
iError = (uiExpectSpeed - uiCurrentSpeed)/3; //(1) 第一个语句
在调试的过程中发现这个iError的值有时候会特别大,最后才发现是上面的这句语句出错了!然后修改成下面两句结果就对了:
iError = uiExpectSpeed - uiCurrentSpeed; //(2)第二个语句
iError = iError/3; //(3)第三个语句
不同类型的数据在进行混合运算时会有一个隐试的类型转换过程,有符号数与无符号数混合运算,有符号数会被转换成无符号数后再参加运算.
在上面的第一个语句中,如果uiExpectSpeed 比uiCurrentSpeed的值大,也就是uiExpectSpeed - uiCurrentSpeed结果为一正值,那不会出现啥问题,但当uiExpectSpeed 比uiCurrentSpeed的值小时就出现问题了,此时uiExpectSpeed - uiCurrentSpeed的临时结果存放在16位的寄存器中,且最高位1,对于有符号数来说会把这一个位解释为符号位,1表示负数,而对于无符号来说这个位就表示数值,接着这个临时的结果除以3后,所得到的结果的最高位变为了0此时该结果会转换为一个有符合数(不管是有符号数,还是无符号数,最高位为0时,所表示的数值就是一样的),赋给iError.本应该得到一个负数的,但最终却得到了一个比较大的正数!在第一个语句中,如果没有除以3,而是两个数作差后直接赋给iError则是不会出错的,虽然uiExpectSpeed - uiCurrentSpeed运算的结果是一个很大的正数(寄存器的最高位为1),但在这个临时结果赋给iError这个变量时,会先把这个值转换为一个有符号数赋给iError.其实,在把uiExpectSpeed - uiCurrentSpeed运算的结果赋给iError时是把所有的位原封不动的复制到iErrorr所表示的内存单元中的,只是我们是以有符号数来解释这个内存单元中的内容,所以这个很大的正数就变成了一个负数!(数据在处理器内是以补码表示的,对于数据是正还是负只是人们的解释不同而已).所以我就用后面的两句替换了第一句,这样不管uiExpectSpeed - uiCurrentSpeed的差值是正还是负都能得到正确的结果了.
下面是我在做超声波测距时遇到的又一个很隐蔽的问题:
unsigned int start; //表示计时开始时计数器的值
unsigned int end; //表示计时结束时计数器的值
unsigned int error;
unsigned int distance; //表示距离
unsigned int time; //表示从计时开始到结束所用的时间
unsigned int remainder;//余数
start = TCNT;// 计时开始, TCNT为16位的计时器寄存器
..............一段时间后(这段时间小于计时器TCNT从0计数到最大值65535所表示的时间)...........
end = TCNT; //计时结束
error = end - start; //注意,end有可能比start小,但由于都是无符号数,所以最后得到的差值就是这段时间内计数器TCNT的增量.
time = error/625; //单位为ms TCNT每1ms内数值增加625(这个数与TCNT所用的时钟有关)
distance = 17*time; //单位为cm, 距离为速度乘以时间再除以2就是声波所传波的距离
这块由于是分步计算的,所以会有比较大的误差(主要是由于error/625后的余数被丢弃了) 于是我改成如下语句:
start = TCNT;// 计时开始, TCNT为16位的计时器寄存器
..............一段时间后(这段时间小于计时器TCNT从0计数到最大值65535所表示的时间)...........
end = TCNT; //计时结束
error = end - start; //注意,end有可能比start小,但由于都是无符号数,所以最后得到的差值就是这段时间内计数器TCNT的增量.
distance = (17*error)/625; //单位为cm, 将上面的最后两句结合成一句,先乘后除就会减小误差
但改后上面distance = (17*error)/625; 这句就错了,因为error的值可能很大,最大可以达到65535,所以17*error结果很有可能会超过65535,但这个处理器是16位的,也就是说这个处理器的数据寄存器为16位,最大的表示数值也就65535,所以17*error大于65535后就会被截断存入寄存器中.也就是说存入寄存器中的值为(17*error)%65536,当再用这个值除以625时得到的很有可能就是0或者个位数的值,不管怎样,此时得到的结果都是错误的值了!!
结合上面两种情况,最后我改成如下:
start = TCNT;// 计时开始, TCNT为16位的计时器寄存器
..............一段时间后(这段时间小于计时器TCNT从0计数到最大值65535所表示的时间)...........
end = TCNT; //计时结束
error = end - start; //注意,end有可能比start小,但由于都是无符号数,所以最后得到的差值就是这段时间内计数器TCNT的增量.
time = error/625; //单位为ms
remainder = error - time*625;//计算上一句中丢弃的余数,没有用remainder = error%625,是因为除法很耗时!!
distance = 17*time + (17*remainder + 312)/625; //单位为cm,此处的312(625/2)是考虑到四舍五入的.