引言
51单片机是对兼容英特尔8051指令系统的单片机的统称。51单片机广泛应用于家用电器、汽车、工业测控、通信设备中。因为51单片机的指令系统、内部结构相对简单,所以国内许多高校用其进行单片机入门教学。以下是我对51单片机的学习笔记。
51单片机的构造
我拿到的51单片机长这样:
以及一些外设:
熟悉单片机的最好办法,就是尝试控制右边的LED灯模块。不过,想要控制这些灯,还需要一个USB接口,以及两个软件。
必要的软件
这两个软件分别是Keil uVision5和stc-isp。前者负责给单片机编程,后者负责把程序下载到单片机里。
玩转LED
初探LED
亮起来了!
点亮一个LED,需要在Keil 5中输入以下指令:
#include <REGX52.H>//头文件,有它才能控制LED灯。
void main()
{
P2=0xFE;//P2是LED模块中电流的“出口”,0xFE=1111 1110。“0”为亮,“1”为灭。最右边的位数表示最左边的LED灯的状态。
}
然后把指令输进stc中:
实际效果:
一闪一闪亮晶晶
要想看到灯亮一会,灭一会,就要用到延时函数,以下是500ms的版本:
#include <REGX52.H>
#include <INTRINS.H>//头文件,可以用来控制延时
void Delay500ms() //@11.0592MHz
//延时函数,让单片机在规定时间内什么都不做,这样可以保持灯亮(灭)的状态
{
unsigned char i, j, k;
_nop_();
i = 4;
j = 129;
k = 119;
do
{
do
{
while (--k);
} while (--j);
} while (--i);
}
void main()
{
P2=0xFE;
Delay500ms();
P2=0xFF;//0xFF=1111 1111 此时灯全灭
}
实际效果如下:
LED闪烁,500ms
走马灯
既然LED灯排成一排,那是不是可以来个走马灯呢?
当然可以,只要灯的状态过一段时间变化一次即可,程序如下:
#include <REGX52.H>
#include <INTRINS.H>
void Delay200ms() //@11.0592MHz
{
unsigned char i, j, k;
_nop_();
i = 2;
j = 103;
k = 147;
do
{
do
{
while (--k);
} while (--j);
} while (--i);
}
void main()
{
while(1)
{
P2=0xFE;//1111 1110
Delay200ms();
P2=0xFD;//1111 1101
Delay200ms();
P2=0xFB;//1111 1011
Delay200ms();
P2=0xF7;//1111 0111
Delay200ms();
P2=0xEF;//1110 1111
Delay200ms();
P2=0xDF;//1101 1111
Delay200ms();
P2=0xBF;//1011 1111
Delay200ms();
P2=0x7F;//0111 1111
Delay200ms();
}
}
效果如下:
LED流水灯 匀速
这样虽然能实现走马灯,但如果想设置不同的延时,就要引入不同的函数,很麻烦,可不可以用一个函数表达不同的延时呢?
当然可以!思路是让1ms执行n次。代码如下:
void Delay1ms(unsigned int xms) //此处xms=n,xms>0 //@11.0592MHz
{
unsigned char i, j;
while(xms)//
{
i = 2;
j = 199;
do
{
while (--j);
} while (--i);
xms--;
}
}
再加上这段代码:
void main()
{
while(1)
{
P2=0xFE;//1111 1110
Delay1ms(500);
P2=0xFD;
Delay1ms(450);
P2=0xFB;
Delay1ms(400);
P2=0xF7;
Delay1ms(350);
P2=0xEF;
Delay1ms(300);
P2=0xDF;
Delay1ms(250);
P2=0xBF;
Delay1ms(200);
P2=0x7F;
Delay1ms(150);
}
}
就可以这样:
WeChat_20221115001750
独立按键控制LED
引言
现在通过编程可以控制LED灯的亮灭,然而,这似乎不够灵活。如果想让亮着的灯灭(灭着的灯亮)的话,就要重新编程并下载好程序,颇费时间。还好单片机配有独立按键,它们就像开关一样,可以控制灯的亮灭,控制LED灯变得更方便了。
独立按键控制LED的亮灭
基本操作
一般单片机有4个独立按键,分别命名为K1、K2、K3、K4,整个独立按键模块在程序中用P3表示。
要想通过是否按下独立按键来控制灯的亮灭,就需要让程序明白:当某个按键(就比如K1吧)被按下时,让某个灯(就比如最左边的那个吧)亮,否则,让某个灯灭。
那么,怎么让程序知道我们要控制哪个按键,哪个灯呢?
很简单,在模块后加个“_”,然后跟上合适的数字即可
,如“P3_1”表示独立按键K1,“P2_0”表示从左往右数第一个LED灯。
再加上C语言中让程序做判断的if语句,就能让独立按键控制LED的亮灭了。
代码如下所示,
#include <REGX52.H>
void main()
{
while(1)
{
if(P3_1==0)//0表示使电流通过K1,只有按下时才能出现这种状况,可简单理解为按下。
{
P2_0=0;//与上个注释类似,可简单理解为让第一个LED灯亮
}else
{
P2_0=1;//灯灭
}
}
}
实际效果如下:
独立按键控制LED亮灭
玩点花的
除了以上的简单操作,我们还能运用运算,实现更“精致”的控制。常用的运算如下,控制LED灯主要用逻辑运算:
独立按键控制LED的状态
按键控制的硬伤
用按键控制LED灯确实更方便,但是按键之所以能这么做,是因为按下按键时,按键会压住下方的金属弹片,使电路闭合,让电流通过。但是,由于金属弹片在被压下后松开时会发生抖动,从而导致程序崩溃,
因而,我们需要做点什么。
解决办法
解决抖动问题的方法很直接:惹不起难道躲不起吗?既然它抖,那等它抖完之后再开灯不就行了吗?
那我们再加个延时就可以了,一般是20ms。
代码如下:
#include <REGX52.H>
void Delay(unsigned int xms) //@11.0592MHz
{
unsigned char i, j;
while(xms){
i = 2;
j = 199;
do
{
while (--j);
} while (--i);
xms--;
}
}
void main()
{
while(1){
if(P3_1==0){
Delay(20);//延时20ms
while(P3_1==0);
Delay(20);
P2_0=~P2_0;//“~”即“非”,让亮灯灭,让灭灯亮
}
}
}
实际效果与上个视频太相似,因此实际效果参考上个视频(20ms的延迟正常人哪看得出来?)
独立按键控制LED显示二进制
实现二进制的显示并不难,由于LED灯受一条总线控制,之前让灯亮起来的时候,我们正是通过输入一个十六进制数做到的。再配合防抖用代码,即可实现独立按键控制LED显示二进制。
代码如下:
#include <REGX52.H>
void Delay(unsigned int xms) //@11.0592MHz
{
unsigned char i, j;
while(xms--){
i = 2;
j = 199;
do
{
while (--j);
} while (--i);
}
}
void main()
{
while(1){
if(P3_1==0){
Delay(20);
while(P3_1==0);
Delay(20);
P2++;
}
}
}
实际效果如下:
独立按键控制LED显示二进制
(单片机构造原因,二进制数得从右向左数,见谅)
通过视频我们能发现,LED灯的亮不表示1,而表示0;灭不表示0,反而表示1。解决方法很简单,用逻辑符号“~”即可。
代码如下:
#include <REGX52.H>
void Delay(unsigned int xms) //@11.0592MHz
{
unsigned char i, j;
while(xms--){
i = 2;
j = 199;
do
{
while (--j);
} while (--i);
}
}
void main()
{
while(1){
if(P3_1==0){
Delay(20);
while(P3_1==0);
Delay(20);
P2++;
P2=~P2;
}
}
}
但仔细一想就会发现问题:初始状态的P2是1111 1111,那按照代码,1111 1111加上1会溢出,变成0000 0000,取反又变回了1111 1111,这不是死循环吗?
所以,我们要定义一个变量叫做num,类型最好是unsigned char,范围与LED可显示的相同,值设为0。之后再让num取反,值赋给P2。代码如下:
#include <REGX52.H>
void Delay(unsigned int xms) //@11.0592MHz
{
unsigned char i, j;
while(xms--){
i = 2;
j = 199;
do
{
while (--j);
} while (--i);
}
}
void main()
{
unsigned char num = 0;
while(1){
if(P3_1==0){
Delay(20);
while(P3_1==0);
Delay(20);
num++;
P2=~num;
}
}
}
实际效果如下:
独立按键控制LED显示二进制plus
独立按键控制LED移位
想让LED灯移位,就要用到“<<”,如“0x11<<3”就是把二进制数0001 0001中所有的1右移3位,变成1000 1000,结合之前设置变量的经验,可以得出如下代码:
#include <REGX52.H>
void Delay(unsigned int xms);
void main(){
unsigned char num = 0;
while(1){
if(P3_1==0){
Delay(20);
while(P3_1==0);
Delay(20);
num++;
if(num>=8){
num=0;
}
P2=~(0x01<<num);//记得取反,让亮代表1,灭代表0
}
}
}
}
void Delay(unsigned int xms) //@11.0592MHz
{
unsigned char i, j;
while(xms--){
i = 2;
j = 199;
do
{
while (--j);
} while (--i);
}
}
实际效果如下:
可以发现,第一个灯在按下时不亮,我们可以稍微改进一下:
#include <REGX52.H>
void Delay(unsigned int xms);
void main(){
unsigned char num = 0;
P2=0x01;//唯一改变:先将P2设为0000 0001
while(1){
if(P3_1==0){
Delay(20);
while(P3_1==0);
Delay(20);
num++;
if(num>=8){
num=0;
}
P2=~(0x01<<num);
}
}
}
}
实际效果如下:
(单片机结构原因,程序中是左移,实际上是右移)
如果我们再玩得花一点,可以另外设置一个按键K2,实现按下K1,灯右移,按下K2,灯左移。
代码如下:
#include <REGX52.H>
void Delay(unsigned int xms);
void main(){
unsigned char num = 0;
P2=0x01;
while(1){
if(P3_1==0){
Delay(20);
while(P3_1==0);
Delay(20);
num++;
if(num>=8){
num=0;
}
P2=~(0x01<<num);
}if(P3_0==0){
Delay(20);
while(P3_0==0);
Delay(20);
if(num==0){
num=7;
}else{
num--;
}
if(num<=0){
num=8;
}
P2=~(0x01<<num);
}
}
}
void Delay(unsigned int xms) //@11.0592MHz
{
unsigned char i, j;
while(xms--){
i = 2;
j = 199;
do
{
while (--j);
} while (--i);
}
}
实际效果如下:
数码管
数码管是怎么工作的
数码管一般长这样:
它由8个灯管组成,每个灯管都被编上号,如图所示:
其亮灭由对应的电路控制,电路有两种接法——共阴极接法和共阳极接法:
(上方的为共阴极接法,下方的为共阳极接法,该单片机使用前者)
在共阴极接法中,输入1(高电平),使得电路中有电流通过,让灯管亮起,输入0(低电平)则反之。如要让数码管显示“3”,就要让A,B,C,D,G亮,其它的灭,那么从右到左应该输入的值应0100 1111,转化成十六进制就是0x4F,该十六进制数就是3的段码。
数码管段码如下所示:
如果是四个数码管并排连接,电路一般是这么连的:
在共阴极接法中,要让某个数码管亮,就要给其赋予0(低电平)如让第三个数码管亮就要输入1101。
一般单片机都有8个数码管,如果输入这么多0和1不是很麻烦?不用担心,有个叫138译码器的系统可以帮我们做到只输入3个数字就能控制亮哪个数码管,其结构如图所示:
总之,要想让数码管亮起来,需要先指定亮哪个数码管,再指定亮数码管的哪些部位。下一部分将在程序中实现数码管的静态显示。
静态数码管显示
上一部分提到,要想让数码管亮起来,需要先指定亮哪个数码管,再指定亮数码管的哪些部位。
若要编程实现这些,需要先输入P2_4,P2_3,P2_2的值,以表明亮哪个数码管,如分别输入1,1,0,是让从左往右数第六个数码管亮。再输入想显示数字的段码作为P0的值(P0是数码管的灯管的显示线路),如输入0x66表示让数码管显示“4”。具体代码如下:
void main(){
P2_4=1;
P2_3=1;
P2_2=0;
P0=0x66;
}
当然,这样的代码太冗杂,可以建立一个数组决定亮哪个数码管,再用一个switch函数决定亮哪个数字,代码如下:
#include <REGX52.H>
unsigned char nixietable[] ={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F};
void nixie(unsigned char location,number){
switch(location)
{
case 1:P2_4=1;P2_3=1;P2_2=1;break;
case 2:P2_4=1;P2_3=1;P2_2=0;break;
case 3:P2_4=1;P2_3=0;P2_2=1;break;
case 4:P2_4=1;P2_3=0;P2_2=0;break;
case 5:P2_4=0;P2_3=1;P2_2=1;break;
case 6:P2_4=0;P2_3=1;P2_2=0;break;
case 7:P2_4=0;P2_3=0;P2_2=1;break;
case 8:P2_4=0;P2_3=0;P2_2=0;break;
}
P0=nixietable[number];
}
void main(){
nixie(7,2);//第七个数码管,显示“2”
while(1){
}
}
实际效果如下:
动态数码管显示
有了nixie函数,就能方便地显示多个数字了,由于单片机一次只能显示一个数字,所以要想显示多个数字,就必须建立一个循环,快速地轮番显示不同的数字,以此达到目的。
如要显示“123”,具体代码如下:
#include <REGX52.H>
unsigned char nixietable[] ={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F};
void nixie(unsigned char location,number){
switch(location)
{
case 1:P2_4=1;P2_3=1;P2_2=1;break;
case 2:P2_4=1;P2_3=1;P2_2=0;break;
case 3:P2_4=1;P2_3=0;P2_2=1;break;
case 4:P2_4=1;P2_3=0;P2_2=0;break;
case 5:P2_4=0;P2_3=1;P2_2=1;break;
case 6:P2_4=0;P2_3=1;P2_2=0;break;
case 7:P2_4=0;P2_3=0;P2_2=1;break;
case 8:P2_4=0;P2_3=0;P2_2=0;break;
}
P0=nixietable[number];
Delay(1);
P0=0x00;
}
void main(){
while(1){
nixie(1,1);
nixie(2,2);
nixie(3,3);
}
}
但效果并不好:
这是因为单片机运行速度太快,导致要显示的数字跑到别的数码管里去,这叫做串行。这时候我们需要消影,即在nixie函数中加上一点点延时,并在显示完成后将P0的值清零。
新的nixie代码如下:
void nixie(unsigned char location,number){
switch(location)
{
case 1:P2_4=1;P2_3=1;P2_2=1;break;
case 2:P2_4=1;P2_3=1;P2_2=0;break;
case 3:P2_4=1;P2_3=0;P2_2=1;break;
case 4:P2_4=1;P2_3=0;P2_2=0;break;
case 5:P2_4=0;P2_3=1;P2_2=1;break;
case 6:P2_4=0;P2_3=1;P2_2=0;break;
case 7:P2_4=0;P2_3=0;P2_2=1;break;
case 8:P2_4=0;P2_3=0;P2_2=0;break;
}
P0=nixietable[number];
Delay(1);
P0=0x00;
}
改进后的实际效果如下:
总结
在这次的单片机学习中,我明白了控制51单片机所需软件和基本流程,学会了如何控制LED灯和数码管发光,使用独立按键,以及如何解决按键抖动、数码管显示串行等问题,收获了不少知识与经验。