点亮LED和流水灯设计
参考书籍:51单片机C语言教程
视频链接:参考视频
单片机型号:巫妖王51单片机V2版
单片机概述
-
什么是单片机?
Micro Controller Unit简称MCU,即单片微型计算机,简称单片机。
用专业语言讲,单片机就是在一块硅片上集成了微处理器、存储器及各种输入/输出接口的芯片,这样一块芯片就具有了计算机的属性,因此称为单片微型机,也叫单片机。
通俗点来说,单片机就是一块集成芯片,但这块集成芯片具有一些特殊的功能,而功能的实现要靠我们使用者自己来编程完成。我们编程的目的就是控制这个芯片的各个引脚在不同时间输出不同的电平,进而控制单片机各个引脚相连接的外围电路的电气状态。
-
单片机能作什么
单片机属于控制类数字芯片,目前其应用领域非常广泛。
-
单片机如何学习
理论和实践需要相辅相成。
点亮LED
- LED介绍
- 电阻阻值看法
- 电平
- LED点亮的原理
LED原理图:
LED点亮代码如下:
#include <REGX52.H>
void main()
{
P2=0xFE; //对P2口的8个I/O口同时操作,0x代表后面的数据是以十六进制表示的,十六进制的FE //表示的是 1111 1110
//原理图上表示 P20对应的LED为D2,故D2亮,其余不亮
while(1) //用于程序的死循环
{
}
}
//书上的另外一种写法:9
/*
#include <REGX52.H>
sbit led1=P2^0; //声明单片机P2口的第一位(最低位),将单片机P2口的最低位定义为led1
void()
{
led1=0; //点亮第一个发光二极管,对P2口的最低位清0,由于没有操作其他位,所以其他位均保持原来的状态不变
}
*/
-
头文件:reg51.h和reg52.h是定义51单片机或52单片机特殊功能寄存器和位寄存器的。
-
main()函数
格式:void main()
特点:无返回值,无参数。
无返回值,表示该函数执行之后不返回任何值,上面的main前面的void表示“空”,即不返回值的意思。
无参数表示该函数不带任何参数,即main后面的括号中没有任何参数,我们只写“()”就可以,也可以在括号里写上void,表示“空”的意思,如void main(void)
任何一个单片程序有且仅有一个main函数,它是整个程序开始执行的入口。
-
电平特性:
单片机是一种数字集成芯片,数字电路中只有两种电平:高电平和低电平。常用的逻辑电平有TTL、CMOS、LVTTL、ECL、PECL、GTL等。其中TTL和CMOS的逻辑电平可分为四类:5V系列(5V TTL和5V CMOS)、3.3V系列、2.5V系列和1.8V系列。
常见的51单片机通常使用TTL电平信号,+5V等价逻辑1,0V等价逻辑0。
-
while()语句
格式:
while(表达式) { 内部语句(内部可空) }
特点:先判断表达式,后执行内部语句。
原则:若表达式不是0,即为真,那么执行语句,否则跳出while语句,执行后面的语句。
需要注意三点:
- 在C语言中我们一般把“0”认为是假,“非0”认为是真,也就是说只要不是0就为真,所以1、2、3等都为真
- 内部语句可为空,就是说while后面的大括号里可以什么也不写,如
while(1);
,注意:;
一定不可少,否则while()会把跟在它后面第一个分号前的语句认为是它的内部语句。 - 表达式可以是一个常数,也可以是一个运算或带一个返回值的函数
有了上述的介绍,就可以知道,我们往往会在单片机后加上
while(1);
这样的一条让程序停止的语句。因为该语句表达式值为1,内部语句为空,执行前先判断表达式的值,因为为真,所以什么也不执行,然后再判断表达式,仍然为真,又不执行,所以程序会一直执行这条语句,换而言之,单片机在点亮LED后将一直重复执行这条语句。
LED闪烁
由while语句生成延时函数
由while语句构成的延时子函数可通过stc-isp进行生成:
主函数代码如下:
#include <REGX52.H>
#include <INTRINS.H> //空语句需要用到的头文件
void Delay500ms() //@12.000MHz 子函数
{
unsigned char i, j, k;
_nop_(); //空语句
i = 4;
j = 205;
k = 187;
do
{
do
{
while (--k);
} while (--j);
} while (--i);
}
void main()
{
while(1) //做大循环
{
P2=0xfe;// 1111 1110 P2.0亮 也就是D2亮
Delay500ms(); //调用子函数,延时半秒
P2=0xdd;// 1101 1101 P2.1亮 P2.5亮 也就是D3和D7亮
Delay500ms();
}
}
//知识点
/*
void main()
main()函数
无返回值,无参数
*/
由for语句写出的简单延时语句
-
for语句
格式:
for(表达式1;表达式2;表达式3) { 语句(内部可为空) }
执行过程:
第一步,求解表达式1
第二步,求解表达式2,若其值为真(非0即为真),则执行for语句中的内容,然后执行第3步;否则结束for语句,直接跳出,不再执行第3步
第三步,求解表达式3
第四步,跳到第二步重复执行。
实例:
unsigned char i; for(i=2,i>0,i--); /* 首先定义一个无符号字符型变量i,然后执行for语句 -表达式1,i=2:给i赋一个初值2 -表达式2。i>0:判断i大于0是真是假 -表达式3。i--:i自减1 */ //for嵌套 unsigned char i,j; for(i=2,i>0,i--) for(j=2,j>0,j--);
显然程序在执行上述语句的时候是需要时间的,i的初值较小,所以执行的步数就少,当我们给i赋一个较大的值,它执行所花的时间也就越长,通过这个原理,我们就可以利用for语句来作为一个简单的延时语句。
需要注意的是,由于i是无符号字符型变量,它的最大值为255,所以不能通过赋给i一个尽可能大的值来写一个延时较大的语句。
这就是为什么每次给变量赋值时都要首先考虑变量类型,然后根据变量类型赋一个合理的值。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xuEEqghd-1666776958774)(https://louis-typora.oss-cn-nanjing.aliyuncs.com/img_for_typora/20221026154601.png)]
LED流水灯
带参数和不带参数函数的写法
- 不带参数函数的写法及调用
在C语言代码中,如果有一些语句不止一次用到,而且语句内容都相同,我们就可以把这样一些语句写成不带参数的子函数,当主函数需要用到这些语句时,只需要调用子函数即可。我们先观察一下由stc-isp
生成的延时函数:
//由stc-isp的软件延时计算器生成的延时函数
void Delay1ms() //@12.000MHz
{
unsigned char i, j;
i = 2;
j = 239;
do
{
while (--j);
} while (--i);
}
//课本中延时函数的写法采用for语句
-
void表示这个函数执行之后不返回任何数据,即它是一个无返回值的函数
-
Delay1ms是函数名,名字可以随便取,但是注意不要与C语言中的关键词相同,函数命名规则一般写成方便记忆或读懂的名字,也就是一看到函数名就知道函数实现的内容是什么。
显然通过这个函数名我们知道这个函数是一个延时1ms的函数,函数名后面紧跟括号(),这个括号内没有任何数据或符号(即C语言中的“参数”),因此这个函数是一个无参数的函数。
-
写法:子函数可以写在主函数的前面或者后面,一定不能写在主函数里面。
-
子函数写在主函数后面
当子函数写在主函数后面,必须在主函数之前声明子函数。
声明方法:
将返回值特性、函数名及后面的小括号完全复制。若是无参函数,则括号内为空;若是有参函数,需要在括号内依次写上参数类型,只写参数类型,无需写参数,参数类型之间用逗号隔开,最后在小括号后面必须加上分号。
如无参函数声明:
void delay1ms();
如有参函数声明:
void delay1ms(unsigned int);
-
子函数写在主函数之前
当子函数写在主函数之前可以不用声明子函数,这是因为写函数体的同时就已经相当于声明了函数本身。通俗的来说,声明子函数的目的是为了编译器在编译主程序的时候,当它遇到一个子函数的时候知道有这样一个子函数的存在,并且知道它的类型和带参情况等信息,以方便为这个子函数分配必要的存储空间。
-
- 带参数函数的写法及调用
先看一段延时子函数代码:
#include <REGX52.H>
void Delay1ms(unsigned int xms) //@12.000MHz
{
unsigned char i, j;
while(xms)
{
i = 2;
j = 239;
do
{
while (--j);
} while (--i);
xms--;
}
}
//我们注意到子函数内部“unsigned char i, j;”语句,介绍一下变量定义的方式
-
unsigned int xms
:函数所带的一个参数,xms是unsigned int类型变量,又叫这个函数的形参,在调用此函数时,我们用一个具体真实的数据代替此形参,这个真实数据称为实参,形参在被实参代替后,在子函数内部所有和形参名相同的变量将都被实参代替。 -
声明:
void delay1ms(unsigned int)
-
调用方法:
调用一个延时300ms的函数:
void delay1ms(300)
调用一个延时500ms的函数:
void delay1ms(500)
-
变量定义:
以上面子函数内部
“unsigned char i, j;”
语句为例,i, j两个变量的定义放到了子函数里,而没有写在主函数的最外面。在主函数外面定义的变量叫全局变量,像这种定义在子函数内部的变量叫局部变量,这里的i, j就是局部变量。局部变量只在当前函数中有效,程序一旦执行完当前子函数,在它内部定义的所有变量都将自动销毁,当下次再用到该子函数时,编译器重新为其分配内存空间。
每个全局变量都占据着单片机内固定的RAM,由于单片机内部RAM空间有限,所以我们要从一开始写程序时就要坚持能节省的RAM空间就要节省,能用局部变量就不用全局变量的原则。
-
移位操作
实现流水灯的办法有很多,可以用逻辑运算来实现,也可以用C51自带的函数来实现。
移位:
- 左移:
a=a<<1
- 右移:
a=a>>1
库函数:
- 循环左移函数:
_crol_
- 循环右移函数:
_cror_
#include <intrins.h> unsigned char_crol_(unsigned char c,unsigned char b)//字符C循环左移b位 //return value: 返回的是C循环左移之后的值
函数描述:以循环左移函数为例,
_crol_
这个函数的意思是将字符C循环左移b位,由于这是C51库自带的内部函数,在使用这个函数之前,需要在文件中包含它所在的头文件。 - 左移:
流水灯的实现
流水灯的代码如下:
#include <REGX52.H>
#include <intrins.h>
unsigned char aa;//定义一个变量,用来给P2口赋值
void Delay1ms(unsigned int xms) //@12.000MHz
{
unsigned char i, j;
while(xms)
{
i = 2;
j = 239;
do
{
while (--j);
} while (--i);
xms--;
}
}
void main()
{
aa=0xFE;
while(1) //大循环
{
P2=aa; //先点亮第一个发光管
Delay1ms(500); //延时
aa=_crol_(aa,1); //将aa的值左移一位后再赋给aa
}
}