延时的概念
前面的程序中我们只是点一下灯,熄一下灯,或者点亮另几个灯,再熄灭,有这编程的功夫还不如装个开关吧。你还想咋地?“宝宝想看那种五颜六色还能一闪一闪亮晶晶的”。如果你的女朋友(或男朋友)看到你学单片机只会点灯时这么对你说,你该咋办?毫无疑问,咱无论如何也要满足她(他)呀,还不止只给她看一闪一闪亮晶晶呢,你应该要给她看星星,看月亮,看整个宇宙的。毕竟现在这社会竞争这么大,你不给她,另一个朋友能给她,到时你该怎么办?都到谈恋爱这份上了,为了这点小事难道你要“善罢甘休”,真不想以后交换戒指的是你和她?
想清楚了吧,为了未来的那一大幕,我们现在是不是要先来研究研究LED怎么一闪一闪这一小步呢?
我们要让单片机控制的LED一闪一闪要怎么做呢,是不是让它亮一会再灭一会?那要怎么让它亮了再灭呢?难道写一个点亮程序,再写一个熄灭程序不停交替下载这两程序?要是这样,你女朋友早跟楼下小王跑了。
前面的内容中我们说了数字芯片大多都是需要时序来控制的吧,单片机是数字芯片的集合体,它的时钟源就是我们上节课说的晶振电路,晶振电路产生的时钟信号输入到其内部,然后其内部电路就根据这个信号按照一定的规律进行运行,如果给到的程序有什么功能它内部就随着这个时钟一步一步实现我们的功能,如果我们给了一个"空白"程序(空白不是什么都没有,用无意义来形容可能更贴切,就是不处理正经事单纯消耗系统时钟的命令,实现这一点的原理是因为单片机中任何命令执行都会消耗时间,类比人休息但时间也在流失道理一样),那它内部在这一段时钟信号内就保持原样什么都不变动。就像我们人一样,当我们在走路时,大脑控制我们的神经,带动肌肉往复运动,我们休息时就让肌肉放松下来。那我们要怎么控制芯片让它暂停一下呢,看完上面内容是不是就有答案了?给它一段“空白"命令,不行就2段,或者N段。
那在程序中要怎么实现让单片机执行一段空白命令呢?
前面接收C语言基础时我们是不是讲到了循环语句?我们在循环语句中不执行任何功能,这样就可以解决这个问题了吧。一个“暂停”功能的程序在脑海中应该有了吧。这种情况我们一般称为延时,和暂停是一个意思,但不这不是意味着单片机就真的是停下来的吧,就像我们休息的时候心跳也要照样跳动一样,“暂停”都是假象。
下面来看一下我们平时常用的延时代码:
while(n<100)
{
n--;
}
我们也可以改写成for循环的方式吧。
for(n=0;n<100;n++)
{
;
}
当然,以上程序还可以进行嵌套,如果你需要设置的延时时间很久的话。另外,熟练编程之后我们也可以把大括号对{}省略不写,至于为什么?自己思考一下。
这里我们在“暂停”时用一个变量n来控制“空白”命令的次数,如果短了就把n加大,如果长了就减小。但到底多少合适呢,也就是我们要怎样实现可控的延时时间呢,这里我们需要来了解几个关于51单片机的新概念,即时钟周期,机器周期,指令周期。
时钟周期就是指晶振振荡的周期,它是晶振频率的倒数,我们仿真电路中使用的是12MHz的晶振那它就是(1/12M)秒。
机器周期在51单片机中机器周期就是12个时钟周期,即(1/1M)秒,也就是1微秒(us)。
指令周期就是单片机执行一条指令的时间,它由若干个机器周期组成,在单片机中一些简单指令是1个机器周期,一部分复杂指令需要2个或多个机器周期。
当然我们项目开发时这些内容需要参考芯片数据手册,因为这不是固定的定义,现在很多增强型51单片机的时钟都不是这样的了,其他更高级的芯片就不用说了。
那我们到底要怎么确定我们延时程序中的n,能延时多久呢?我们可以通过程序的调试功能进行仿真确定(毕竟是仿真与实际会有一些出入)。最死板的办法是我们可以把程序下载到单片机中进行测试分析呀,用示波器测量单片机引脚电平变化周期就不出来了,然后大概记住一些典型时间,如0.1s,0.5s,1s等延时n的值,以后直接使用也可以。但我们肯定还有更简单又准确的延时办法,因为设计到定时器的使用,所以今天就先不讲解,后面学习定时器是在做具体介绍。
写了这么多内容,终于把基础写完了,老司机们是不是早就迫不及待的发车了?那我们先上车开起来吧
闪灯程序
看了以上原理,现在我们想来实现让一个灯闪起来吧。我们来看看程序内容,跟着注释看一遍是不是就很明了了。
#include //这是52单片机的头文件
#include //这也是编译器的一个头文件后面会用到
typedef unsigned char u8; //typedef 是别名关键字
typedef unsigned int u16;
void main()
{
u8 i,j; //定义变量
P1 = 0xfe;//点亮第一个灯
for(i=0;i< 200;i++) //第一段延时
{
for(j=0;j< 200;j++)
{
;
}
}
P1 = 0xff;//熄灭第一个灯
for(i=0;i< 200;i++)//第二段延时
{
for(j=0;j< 200;j++)
{
;
}
}
}
这个代码里我新引入一个头文件#include ,它也是编译软件自带的一个头文件,里面包含对寄存器的循环操作接下来的程序中我们会使用到。
typedef这个关键字就是取别名的意思,就是把一长串名称给一个简短的名字,编程时可以巧用它来简化某些程序。
使用上一节内容中对P1端口整体赋值的方式进行点灯。
点完灯后延时一会,接下来讲灯熄灭,熄灭后再进行一个延时,这样就实现了我们的闪灯效果。
创建函数,做配件
如果我们把延时程序打包成一个函数以后就可以重复使用了,这是不是爽歪歪?
再来看看修改好的代码:
#include //这是52单片机的头文件
#include //这也是编译器的一个头文件
typedef unsigned char u8;
typedef unsigned int u16;
//void delay();//声明函数
void delay(u8 ms);
void main()
{
P1 = 0xfe;//将LED1点亮
delay(50);//调用函数
P1 = 0xff; //将LED1关闭
delay(50);
}
void delay(u8 ms) //定义函数
{
u8 i,j;//定义这个延时函数中的i,j变量
for(i=0;i< ms;i++)
{
for(j=0;j< 200;j++)
{
;
}
}
}
还是同样的意思,这么一对比是不是可以看出程序有层次感就出来了,代码瞬间就好看了很多吧。当然把延时函数打包成void delay()还是void delay(u8 ms)就看个人了,要想使用更灵活那当然是后者咯。
循环流水灯
只是知道闪灯未免太单调,也打动不了人心吧,接下来我们再整点复杂的,使用循环位移操作实现流水灯变化,现在先来看看程序。
#include
#include
typedef unsigned char u8;
typedef unsigned int u16;
void delay(u8 ms);
void main()
{
u8 rol = 0xfe;
u8 i;
for(i=0;i< 8;i++)
{
//先将P1端口初始化,先设定P1.0点亮
P1 = rol;
//延时一会
delay(100);
//将变量移位
rol = _cror_(rol,1);
//移位完成在下一个循环,rol的值将重新赋值给P1寄存器
}
}
void delay(u8 ms) //定义函数
{
u8 i,j;//定义这个延时函数中的i,j变量
for(i=0;i< ms;i++)
{
for(j=0;j< 200;j++)
{
;
}
}
}
这个程序对着视频仿真效果一起看是不是还好理解呢?
_cror_函数相当于一个环形位移 11111110 向右移动一位就变成 01111111,其高位是将末位补充过来。
如果换成_iror_结果就不一样了。
_iror_是单向位移,11111110 向右移动一位变成01111111,其高位是补0。
当然左移就和右移刚好相反了。
_crol_函数是环形移动,11111110 移动一位就变成 11111101,其末尾是将最高位补充过来。
_irol_中11111110 向左移动一位变成11111100,其末尾是补0。
这两种循环反方式在编程时各有用途,所以要区分得开来,如果觉得不好掌握就多编程尝试一下。
评论
查看更多