今天重温了下51单片机,先从流水灯做起。用的是proteus8.0来仿真。
首先是硬件电路:
左边的部分,是51的最小系统电路。包括时钟电路和复位电路。芯片的电源引脚和地引脚隐藏了,默认电源接VCC,地接GND。
在器件的属性,隐藏管脚中可以看到。
在design->configure power rails菜单中,可以设置电源和地引脚的默认电压值。一般VCC,VDD设置为5V,GND设置为0V。
在proteus中仿真,复位引脚和时钟引脚是没有用的。对于51,当RST复位引脚为高的时候,系统复位,程序重新从开头执行。时钟引脚接的晶振是决定51单片机运行时的时钟。但是在protues中,这两个引脚是没有用的。输入什么都不影响,即使时钟不接晶振,电路也正常工作。复位信号给高,电路也正常工作。所以这就是用软件模拟硬件仿真和实际的硬件仿真区别。不过对于学习51单片机的程序设计,就不要在乎这些细节。等要自己设计51单片机,要注意这些东西。
下面开始写程序:实现流水灯循环下移。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #include"reg52.h" sbit key = P1^7; void delay_ms(int t) { char i; while(t--) { for(i=0; i<100; i++); } } int main() { char temp=0x01; P0 = ~temp ; while(1) { delay_ms(500); temp = (temp<<1)|((temp>>7)); P0=~temp; } }
使用一个temp,变量来进行充当中间变量进行移位。
temp = (temp<<1)|((temp>>7)); 将temp8位数据向左移一位,然后在将temp 8位数据向右移7位,这样最高位就到最低位了,在用位或|操作,将两个数据拼接,这样就实现了循环左移功能。
感觉是没错,载入到proteus中仿真。
发现,最开始移位是正确的,流水灯循环向下移。但是当移到最后一个流水灯的时候,在移一次的时候,灯就全亮了。然后就没有移位了。这和设计的功能不一样啊。
然后找原因,使用keil的软件仿真,每次循环看temp的值。当移位到最后一位的时候,即temp=0x80的时候。查看temp<<1和temp>>7的值
发现,temp的值为0x80的时候,temp>>7的值,竟然变为0xff。而不是0x01。想了半天,想起,定义的temp为char型,而char型,这里51应该是认为有符号型数据(我认为),有符号移位,当向右移位时,最高位补符号位。所以temp为0x80,这时,最高位为1,则向右移位7位的话,补符号位,那么移位结果就是0xff了。
将char temp=0x01 改为unsigned char temp=0x01
然后在protues中仿真,发现结果对了。
发现这程序,多用了一个temp变量,何不把这变量去掉,直接就用P0移位操作了。
改改代码:
1 2 3 4 5 6 P0 = ~0x01; while(1) { delay_ms(500); P0 = (P0<<1)|((P0>>7)); }
似乎也没有什么问题。将生成的hex文件在入到proteus中,运行。发现,就只有最开始亮了一次灯,下次的时候,登就全灭了。
这不科学啊,这又是什么问题了。在来软件仿真,看结果:
第一次,结果是正确的,但是到第二次的时候,P0就全部变为全零了。这又是为什么了。查找下资料,原来P0是开漏输出的,外不接上拉电阻的话,输出都是低电平,软件仿真的时候,是认为P0是没有接上拉电阻,所以P0的输出都是0.所以在下一次移位,输出就全是0了。
将P0口改为P2口,输出就正确了。因为P2口是推免输出。可以输出高电平。
1 2 3 4 5 6 P2 = ~0x01; while(1) { delay_ms(500); P2 = (P2<<1)|((P2>>7)); }
就一个简单的流水灯,也发现了这么多的问题。看来基础还是比较重要啊。不过,能发现问题是好事啊,最重要的是,能将这些问题都给解决。