Autoleaders控制组——51单片机学习笔记(一)

引言

51单片机是对兼容英特尔8051指令系统的单片机的统称。51单片机广泛应用于家用电器、汽车、工业测控、通信设备中。因为51单片机的指令系统、内部结构相对简单,所以国内许多高校用其进行单片机入门教学。以下是我对51单片机的学习笔记。

51单片机的构造

我拿到的51单片机长这样:
在这里插入图片描述
以及一些外设:在这里插入图片描述

熟悉单片机的最好办法,就是尝试控制右边的LED灯模块。不过,想要控制这些灯,还需要一个USB接口,以及两个软件。

必要的软件

这两个软件分别是Keil uVision5stc-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灯和数码管发光,使用独立按键,以及如何解决按键抖动、数码管显示串行等问题,收获了不少知识与经验。

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值