课程特点
- 无需开发板
- 0基础教学
- 软件硬件双修
- 辅助入门
本课程面对纯小白,因此会对各个新出现的知识点在实例基础上进行详细讲解,有相关知识的可以直接跳过。课程涉及protues基本操作、原理图设计、数电模电、kell使用、C语言基本内容,所有涉及知识都将建立在实例的基础上放心食用。
食用教程
- 详细目录,随时跳过已知部分;
- 全小白按步骤全程跟学;
- 有开发板的交叉理解开发板的原理图与我们设计的原理图
本次实验内容
设计4个LED等,通过编程控制实现流水灯,流水灯间隔时间为500ms;进一步熟悉protues和keil软件,初步了解单片机的机械周期,理解自定义定时函数的设计思路。
前言
该篇内容在硬件设计上没有太多特别,具体设计思路在protues仿真C51入门教程,本篇文章主要内容在其C语言的设计上:自定义函数以及如何用函数粗略实现定时。
流水灯是多个小灯泡依次点亮,视觉上像流水一样。
protues原理图设计
我们此次设计4个LED,调出3个LED,3个电阻设置电阻阻值为280,并按图连线

编程
51单片机的IO口默认是高电平,因此上电时LED均会亮,所以在程序的编写上,第一步是先将LED灯均熄灭。然后将D1~D4依次点亮,再全部熄灭,再依次点亮,如此反复。

C语言知识讲解
自定义函数
自定义函数顾名思义自己定义设计的函数,其形式为:
cs
[储存类型符] [返回值类型符] 函数名([形参说明表])
{
函数语句体
}
储存类型符:一共有四种,一般情况下不写,这里不详细讲解
返回值类型符:值函数运行完需要返回的东西,例如我们主函数main的返回值为void,表示空,不返回任何东西
函数名:自定义函数名字,大致要求是由大小写字母或下划线组成,不能太长,第一个字符不能是数字,具体要求自行搜索。
形参说明表:一系列逗号分开的形参变量数据类型声明。如"int a,int b"表示有两个整数变量x,y,int表示整数。
for循环语句
for循环语句形式为:
cs
for (初始化; 条件; 迭代) {
// 循环体
}
例如:
cs
for (i = 0; i < 5; i++) {
}
在这个例子中,for
循环从 i = 0
开始执行大括号中的内容(这里为空),每次执行完后迭代 i
增加 1
,直到 i
不再小于5,循环结束。
延时函数设计
如果执行一条指令要1ms,那么我执行1000条指令就是1s。所以该函数设计的重点是执行一条指令的实际时间及其执行次数。
8051的机器周期
在C51单片机中,要实现1秒的延时,我们需要知道晶振频率和机器周期的关系。C51单片机的机器周期 是晶振频率的12分频(既除以12)。我们的晶振频率是11.0592MHz,那么机器周期大约为:

将晶振频率代入计算得到:

现在我们知道每个机器周期大约是1.085微秒。为了实现1毫秒的延时,我们需要执行多少个机械周期:

因此我们大致知道我们需要执行921个机械周期。
那么怎么让程序执行921个机械周期呢?这就涉及到汇编语言了,C语言是高级语言,主要是便于我们编程者设计,难以直接判断其机械周期。实际上,C语言在编译后会转换为汇编语言,这个才是判断其机械周期的关键。
因此设计一个可以定时的函数需要了解其汇编语言,在这里我们不去深究与探讨,有兴趣的可以自性了解,或者私信留言。这里我们只是将这种思维转达给各位。
这里我们提供一个函数是:
cs
void delay_ms(unsigned n)
{
unsigned int i,j;
for(i=n;i>0;i--)
for(j=114;j>0;j--);
}
该函数经过严格验证,其运行时间在1ms左右。
该函数的汇编指令如下:
cs
C:0x005D 7D72 MOV R5,#0x72
C:0x005F 7C00 MOV R4,#0x00
C:0x0061 ED MOV A,R5
C:0x0062 1D DEC R5
C:0x0063 7001 JNZ C:0066
C:0x0065 1C DEC R4
C:0x0066 ED MOV A,R5
C:0x0067 4C ORL A,R4
C:0x0068 70F7 JNZ C:0061
C:0x006A EF MOV A,R7
C:0x006B 1F DEC R7
C:0x006C 70E6 JNZ delay_ms(C:0054)
其主要循环部分大致8个机械周期,921/8约等于114,因此循环114次大致1ms
流水灯程序设计
cs
#include "reg51.h"
sbit LED1=P1^0;
sbit LED2=P1^1;
sbit LED3=P1^2;
sbit LED4=P1^3;
void delay_ms(unsigned n)
{
unsigned int i,j;
for(i=n;i>0;i--)
for(j=114;j>0;j--);
}
void main(){
LED1=0;
LED2=0;
LED3=0;
LED4=0;
while(1){
LED1=1;
LED2=0;
LED3=0;
LED4=0;
delay_ms(500);
LED1=0;
LED2=1;
LED3=0;
LED4=0;
delay_ms(500);
LED1=0;
LED2=0;
LED3=1;
LED4=0;
delay_ms(500);
LED1=0;
LED2=0;
LED3=0;
LED4=1;
delay_ms(500);
}
}
分别定义原理图中D1~D4的控制引脚为LED1~LED4
cs
sbit LED1=P1^0;
sbit LED2=P1^1;
sbit LED3=P1^2;
sbit LED4=P1^3;
主函数结构
cs
void main(){
//初始化区
while(1){
//循环区
}
}
初始化区一般进行一些变量的定义 ,初始状态的设置 ,在这里我们初始化让D1~D4熄灭
cs
LED1=0;
LED2=0;
LED3=0;
LED4=0;
循环区一般运行主要程序 ,单片机会不断重复执行里面内容。在这里我们依次点亮LED灯,点亮一个灯的同时关闭其它灯,经过500ms,再点亮下一个灯熄灭其它灯,如此反复。
cs
LED1=1;
LED2=0;
LED3=0;
LED4=0;
delay_ms(500);
LED1=0;
LED2=1;
LED3=0;
LED4=0;
delay_ms(500);
LED1=0;
LED2=0;
LED3=1;
LED4=0;
delay_ms(500);
LED1=0;
LED2=0;
LED3=0;
LED4=1;
delay_ms(500);
点击编译

看到生成hex文件成功即可。

报0Error即可,但警告太多还是要看一下。
protues验证
打开protues点击开始仿真(没有加载hex文件的记得双击芯片加载hex文件)

现象:点击开始D1~D4不亮->D1亮,其它不亮->D2亮,其它不亮->D3亮,其它不亮->D4亮,其它不亮->D1亮,其它不亮->...如此循环
protues仿真流水灯(C51)
代码优化
我们可以看到,我们的代码有大量的重复的内容,对这一部分我们进行优化,对于重复内容我们可以设计一个函数去执行,但又不能破坏其功能。那么我们设计一个函数。可以同时控制4个灯的状态
cs
void LEDdisplay(int L1,int L2,int L3,int L4){
LED1=L1;
LED2=L2;
LED3=L3;
LED4=L4;
}
如此我们的程序将变成:
cs
#include "reg51.h"
sbit LED1=P1^0;
sbit LED2=P1^1;
sbit LED3=P1^2;
sbit LED4=P1^3;
void delay_ms(unsigned n)
{
unsigned int i,j;
for(i=n;i>0;i--)
for(j=273;j>0;j--);
}
void LEDdisplay(int L1,int L2,int L3,int L4){
LED1=L1;
LED2=L2;
LED3=L3;
LED4=L4;
}
void main(){
LEDdisplay(0,0,0,0);
while(1){
LEDdisplay(1,0,0,0);
delay_ms(500);
LEDdisplay(0,1,0,0);
delay_ms(500);
LEDdisplay(0,0,1,0);
delay_ms(500);
LEDdisplay(0,0,0,1);
delay_ms(500);
}
}
在主函数这里是不是相对程序简单了很多呢?
总结
protuse仿真C51流水灯的硬件设计并没有特别需要讲解的,主要是需要设计一个可以粗略定时的函数,理解其设计思路,了解单片机的机械周期以及C语言函数的基本形式。