51单片机之路__基础部分完结(学习笔记)

普中科技A3开发板
吐槽一下客服给的代码是真的辣鸡
都没备份他们的代码 直接覆盖了

导航:

一、LED
二、蜂鸣器
三、动态数码管
四、独立按键
五、矩阵按键
六、I/O拓展–74HC595
七、LED点阵
八、直流电机
九、外部中断
十、定时器
十一、EEPROM-IIC总线


LED:

单灯点亮:P2^n ,n∈[0,7]。分别对应8个灯

#include "reg52.h"
sbit led1=P2^0;
sbit led2=P2^1;
sbit led8=P2^7;
void main()
{
	led1=0;
	led2=0;//0011 1110
	led8=0;//第1、2、8灯亮
	while(1){}
}

多灯点亮:给P2赋值,将其转换为8位二进制

#include "reg52.h"
#define led P2
void main()
{	
	led=17;    //第1、5灯熄灭  
	while(1){} //17对应二进制的      0001 0001
}          	   //对应led灯位置  即为 1000 1000
               //1、5位为高电平

在这里插入图片描述

Q1:如果led=0x11,哪个灯点亮?
A1:当然效果一样。十进制是17,十六进制是0x11。

Q2:对于单灯点亮的例子,用多灯点亮的方式,给P2赋值什么?
A2:第1、2、8灯亮,即0011 1110,倒推回去,0111 1100的十六进制为0x7c,效果一样。

一开始我是转化成二进制,然后从后看,这样1就对应了熄灭的灯(0对应点亮)的位置。
其实在正面 从右往左看,不就是从后面 由左向右吗?

当然也可以把板子倒过来,自己能分清是哪个灯就可以。

也就是:
P2.0的数值(第一个灯)对应二进制的最低(右)位;
P2.7的数值(第八个灯)对应二进制的最高(左)位。


闪烁:定义并使用delay(延迟)函数,delay只是个习惯命名,完全可以取别的,这里延迟时间可以自定义。

#include "reg52.h"	
sbit led=P2^0;   //选择第1个灯
void delay(int i)
{
	while(i--);	
}
void main()
{
	while(1)
	{
		led=0;
		delay(50000); //大约延时450ms
		led=1;
		delay(50000); //大约延时450ms	
	}		
}

这里每次输入时间参数有些繁琐,如果你多次要用延迟1s、2s,可以这样定义并调用。其他时间以此类推,时间延迟的间隔只是大概,并不精准。

void delay_1s()
{
	while(100000--);	
}
void delay_2s()
{
	while(200000--);	
}
delay_1s();  //延迟一秒
delay_2s();  //延迟两秒

流水灯

#include "reg52.h"
#include "intrins.h"
#define led P2	   
void delay(int i)
{
	while(i--);	
}
void main()
{
	int i;
	led=~0x01;
	delay(50000); 	
	while(1)
	{	
		for(i=0;i<8;i++)
		{
			led=~(0x01<<i); //效果等同于led=_crol_(led,1);
			delay(50000);   //注意:该方法(_crol_)要用intrins库
		}
	}		
}

注意:

led=~0x01;     //      1111 1110
_crol_(led,1); //结果为 1111 1101
led=~0x01;     //      1111 1110
led<<1;        //结果为 1111 1100

可以理解为:
第一种移动后高位补低位
第二种移动后只补0

而我用<<实现同样的效果,是这样一个过程。
0000 0001→→→→0000 0010→→→→1111 1101
       <<1(左移1)     ~(取反)
      
_cror_(led,2) 即为右移2位 移动几位由第二个形参决定

返回顶部


蜂鸣器:

#include "reg52.h"
sbit beep=P2^5;	   
void delay(int i)
{
	while(i--);	
}
void main()
{	
	while(1)
	{	
		beep=~beep;
		delay(100); //通过修改此延时时间达到不同的发声效果	
	}
}

可以演奏音乐,并不难,自行探索。

返回顶部


动态数码管:

一、位选:选择你要第几个数码管点亮

对P2^4、3、2的二进制
取反后+1即为对应第n个灯

#include "reg52.h"
sbit LSA=P2^4;
sbit LSB=P2^3;
sbit LSC=P2^2;
LSA=1;LSB=0;LSC=0; //显示第4位

当时按普中的程序走了个小弯路。。。
首先将8个数码管看作0-7个
LSA=1;LSB=1;LSC=1; //显示第0个
LSA=0;LSB=1;LSC=1; //显示第1个
LSA=1;LSB=1;LSC=0; //显示第4个
如何知道是第几个数码管?
旧方法:对110 取反得001 从后看 得对应二进制数值 100(十进制4) 则显示第4位。

其实完全可以先定义LSC=0 ,也可以把sbit LSA=P2^4。
无论如何,怎么方便怎么来。这样只需取反,不必从后看(从右向左看)了

二、段选:
如果我要表示sdau四个字母。
以s为例子(其实s也就相当于5)
由于数码管表示的"8"是由7段 以及右下角一个小数点组成
在这里插入图片描述
我要点亮AFGCD,则
.(dp) G F E D C B A
对应 0 1 1 0 1 1 0 1 二进制表示
十六进制为6d
位选后将P0赋值为6d即可

#include "reg52.h"
sbit LSA=P2^4;
sbit LSB=P2^3;
sbit LSC=P2^2;
void delay(int i)
{
	while(i--);	
}
void main()
{	while(1){
	LSA=1;LSB=1;LSC=1; //显示第1位
	P0=0x6d;     	   //赋值段码
	delay(100);        //延迟
	P0=0x00;           //消隐

	LSA=1;LSB=1;LSC=0; //显示第2位
	P0=0x5e;    
	delay(100);       
	P0=0x00;           	
	
	LSA=1;LSB=0;LSC=1; //显示第3位
	P0=0x77;
	delay(100);
	P0=0x00;

	LSA=1;LSB=0;LSC=0; //显示第4位
	P0=0x3e;
	delay(100);
	P0=0x00;
	}
}

三、延迟与消隐:
应用如上图,缺一不可,否则造成问题。
1、不消隐位选灯重叠或混乱
2、不delay延迟导致亮度暗

PS:要写入while循环,否则或许第一个灯全段点亮。(至少我的板子如此)

代码重复的地方可以写入for循环,将不同的内容依次放入数组,如下依次显示0-7

#include "reg52.h"
sbit LSA=P2^4;
sbit LSB=P2^3;
sbit LSC=P2^2;
int code smgduan[17]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,
0x77,0x7c,0x39,0x5e,0x79,0x71};//显示0~9和A~F的值
void delay(int i)
{
	while(i--);	
}
void main()
{	
	while(1)
	{	
		int i;
		for(i=0;i<8;i++)
		{
			switch(i)	 //位选,选择点亮的数码管,
			{
			   case(0):
					LSA=1;LSB=1;LSC=1; break;//显示第1位
				case(1):
					LSA=1;LSB=1;LSC=0; break;//显示第2位
				case(2):
					LSA=1;LSB=0;LSC=1; break;//显示第3位
				case(3):	
					LSA=1;LSB=0;LSC=0; break;
				case(4):
					LSA=0;LSB=1;LSC=1; break;
				case(5):
					LSA=0;LSB=1;LSC=0; break;
				case(6):
					LSA=0;LSB=0;LSC=1; break;
				case(7):
					LSA=0;LSB=0;LSC=0; break;//显示第7位	
			}
			P0=smgduan[i];//发送段码
			delay(100); //间隔一段时间扫描	
			P0=0x00;//消隐
		}	
	}		
}

返回顶部


独立按键:

纯手工画图,略low
按键未按下为高电平1,按下后不松开即为低电平0。

delay延时是为了消除抖动的影响,如图,可以理解为直接跳过抖动部分(10ms~20ms)。否则可能0的瞬间又到了1,最后稳定又到了0,进行了两次取反,由于太快看不到,最终好像没有效果。

以下代码效果为:按下时,第一个灯取相反状态,松开时,第二个灯取相反状态。(两个等初始状态为高电平1熄灭)

#include "reg52.h"		
sbit key1=P3^1;	 //定义第一个独立按键为key1
sbit led1=P2^0;	 //定义第1个LED灯
sbit led2=P2^1;	 //定义第2个LED灯
void delay(int i)
{
	while(i--);	
}
void keypress()
	{
		if(key1==0)		  //检测按键key1是否按下
		{	
			delay(1000);   //消除抖动 一般大约10ms
			if(key1==0)	   //再次判断按键是否按下
			{
				led1=~led1;	  //led1状态取反
			}
			while(!key1);	 //检测按键是否松开
			if(key1==1)		  //检测按键key1是否松开
			{	
				delay(1000);   
				if(key1==1)	 
				{
					led2=~led2;	 
				}
		 	}
		}	
	}
void main()
{	
	while(1)         //一直进行检测
	{	
		keypress();  //按键处理函数	
	}		
}

至于不消抖的后果,本人亲测,疯狂按键测试。会有很大几率误判,总之,消抖就对了。

返回顶部


矩阵按键:

在这里插入图片描述

先给P1口赋值0x0f(0000 1111)
按下按键变成0x0b(0000 1101)
我们把0x0b定义为第二列
这样就可以知道按键在第二列
在这里插入图片描述
先给P1口赋值0xf0 (1111 0000)
按下按键变成0xb0 (1011 0000)
我们把0xb0定义为第二行
这样就可以知道按键在第二行

导线有一端为0则点位为0。所以不会变成1111 0100

#include "reg52.h"			 
#define GPIO_DIG P0
#define MATRIX_KEY P1
int KeyValue;	//用来存放读取到的键值
int code smgduan[17]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90,
0x88,0x83,0xc6,0xa1,0x86,0x8e};//显示0~9和A~F的值(共阳极)
void delay(int i)
{
	while(i--);	
}
void KeyDown(void)
{
	char a=0;
	MATRIX_KEY=0x0f;
	if(MATRIX_KEY!=0x0f)   //检测按键是否按下
	{
		delay(1000);        //延时10ms进行消抖
		if(MATRIX_KEY!=0x0f)//再次检测键盘是否按下
		{	
			//检测列
			MATRIX_KEY=0X0F;
			switch(MATRIX_KEY)
			{
				case(0X07):	KeyValue=0;break;
				case(0X0b):	KeyValue=1;break;
				case(0X0d): KeyValue=2;break;
				case(0X0e):	KeyValue=3;break;
			}
			//检测行
			MATRIX_KEY=0XF0;
			switch(MATRIX_KEY)
			{
				case(0X70):	KeyValue=KeyValue;break;
				case(0Xb0):	KeyValue=KeyValue+4;break;
				case(0Xd0): KeyValue=KeyValue+8;break;
				case(0Xe0):	KeyValue=KeyValue+12;break;
			}
		}
	}
	while((a<50)&&(MATRIX_KEY!=0xf0)) //检测按键松手检测
	{
		delay(100);
		a++;
	}
}
void main()
{	
	while(1)
	{	
		KeyDown();		   //按键判断函数
		GPIO_DIG=~smgduan[KeyValue]; //按位取反 变为共阴极对应代码
	}		
}

返回顶部


I/O拓展–74HC595:

学习过程中看到的每篇文章都提到过它的特点:串行输入,并行输出。

究竟是什么意思?

有一篇文章将其比作手枪弹夹和霰弹枪
上膛是弹夹串行输入,而霰弹枪发射则是并行输出
比喻很生动,但又缺少一些解释,所以再加一些我自己的理解。

在这里插入图片描述
首先,看到这有4个引脚需要我们控制。

切记,OE要接GND才有效。

剩下的3个脚可以看作…

引脚比喻上升沿作用
SER (P34)子弹\
SRCLK (P36)装弹子弹塞入弹夹,之前的子弹下移
RCLK (P35)扳机发射8颗子弹

装弹与扳机都是上升沿有效。

那么,究竟什么是上升沿有效?

其实就是,当电位由低到高,就会激发作用。

相反,还记得独立按键吗?从高到低的过程,就是下降沿。(不记得点我)

但它不用延迟,为什么?因为它不是按键,没有抖动(maybe),理想下这样。
在这里插入图片描述
Q:我们如何发送一个字节呢?

A:一个字节有八位,看作8个子弹。最高位为第一颗子弹,最低为为第八颗。如,0111 1111 第一个子弹为0。

先装入最高位。这个过程跟进栈类似。
在这里插入图片描述
每次要给SER赋值0或1,也就是让这颗子弹为0或1。然后SRCLK就是装弹。

如此循环,8颗装完之后,使用RCLK发射!

要注意,SRCLK和RCLK都要经历一个过程,即电平由低到高的变化,才能实现。

以下代码直接引用普中科技的, 我只修改了注释和一些变量名, 没有进行精简。


这里例程为矩阵led效果展示,请先学习下一节。

#include "reg51.h"
#include "intrins.h"

char ledNum;
//--定义使用的IO口--//
sbit SRCLK=P3^6;   //定义:扳机
sbit RCLK=P3^5;    //	  装弹
sbit SER=P3^4;     //     弹夹
sbit LED=P0^7;

void delay(int i)
{
	while(i--);	
}

void Hc595SendByte(char dat)
{
	char i;
	SRCLK = 1;				
	RCLK = 1;				
	for(i=0;i<8;i++)		 //发送8位数
	{
		SER = dat >> 7;		 //子弹准备塞入弹夹
		dat <<= 1;           //第一颗已经填入,把第一位丢掉,到最后一位,这里是位运算,如1234 5678 变为 2345 6780 

		SRCLK = 0;			 //先变为低电平
		_nop_();
		_nop_();
		SRCLK = 1;			//从低到高上升沿,填入子弹,第一颗子弹下移。
		//如此循环,填入8个子弹。
	}
	RCLK = 0;
	_nop_();
	_nop_();
	RCLK = 1;  //发射  
}


void main()
{	
	LED=0;
	ledNum = ~0x01;	    //1111 1110 最下面熄灭  

	while(1)
	{
		Hc595SendByte(ledNum);     //发送数据 点亮LED灯
		ledNum = _crol_(ledNum, 1);//变为1111 1110 以此类推
		delay(50000);
	}		
}


实验现象:矩阵LED灯从下往上熄灭,类似流水灯效果

返回顶部


LED点阵:

在这里插入图片描述
例如左下角点亮,只需让ROW8=1(高电平),ROW1到ROW7为0 。 COLUMN1=0(低电平),COLUMN2到8为0或1(二极管导电特性)。

点亮列COLUMN:由P0口控制

使第一列亮,可以P0^7=0
P0^7对应二进制最左/高位(参考点亮二极管)

或P0=0x7f(0111 1111)即可。

直观:最高位控制最左边一列

点亮行ROW:由74Hc595输出口控制
输入0xfe
输出1111 1110
二级制从左到右
对应ROW由上到下

此时左下角为熄灭

#include "reg51.h"			
#include "intrins.h"

sbit SRCLK=P3^6;
sbit RCLK=P3^5;
sbit SER=P3^4;
sbit LED_Column_1=P0^7;

void Hc595SendByte(char dat1)
{
	char a;

	SRCLK = 1;
	RCLK = 1;

	for(a=0;a<8;a++)		 
	{
		SER = dat1 >> 7;
		dat1 <<= 1;

		SRCLK = 0;			 
		_nop_();
		_nop_();
		SRCLK = 1;	
	}


	RCLK = 0;
	_nop_();
	_nop_();
	RCLK = 1;
}


void main()
{	LED_Column_1=0;  //使第一列为低电平。
	
	while(1)
	{
	   Hc595SendByte(0xfe);
	}		
}

进阶:
显示数字零

#include "reg51.h"	
#include<intrins.h>

sbit SRCLK=P3^6;
sbit RCLK=P3^5;
sbit SER=P3^4;

char ledduan[]={0x00,0x00,0x3e,0x41,0x41,0x41,0x3e,0x00};
char ledwei[]={0x7f,0xbf,0xdf,0xef,0xf7,0xfb,0xfd,0xfe};

void delay(int i)
{
	while(i--);	
}


void Hc595SendByte(char dat)
{
	char a;
	SRCLK=0;
	RCLK=0;
	for(a=0;a<8;a++)
	{
		SER=dat>>7;
		dat<<=1;

		SRCLK=1;
		_nop_();
		_nop_();
		SRCLK=0;	
	}

	RCLK=1;
	_nop_();
	_nop_();
	RCLK=0;
}


void main()
{			
	char i;
	while(1)
	{
		P0=0x7f;
		for(i=0;i<8;i++)
		{
			P0=ledwei[i];		  //位选
			Hc595SendByte(ledduan[i]);	//发送段选数据
			delay(100);		   //延时
			Hc595SendByte(0x00);  //消隐
		}	
	}		
}

返回顶部


直流电机:

一端接地,一端接5V就能转。

可以接一个控制口,如下图所示。

在这里插入图片描述

#include "reg52.h"	
#include<intrins.h>		

sbit moto=P1^0;	  	 //控制步进电机模块的01口

void delay(u16 i)
{
	while(i--);	
}


void main()
{	
	while(1)
	{	
		char i;
		for(i=0;i<100;i++)	  //循环100次,也就是大约5S
		{
			moto=1;			//开启电机
			delay(5000);	//大约延时50ms
		}
	
		for(i=0;i<100;i++)	//循环100次,也就是大约5S
		{
			moto=0;			//关闭电机
			delay(5000);	//大约延时50ms
		}					
	}
}

返回顶部


外部中断:

一直没看明白普中给的例程,不是看不明白代码,而是根本感觉不到中断的作用!先放一下他们的代码。

#include "reg52.h" 

sbit k3=P3^2; //定义按键 K3
sbit led=P2^0; //定义 P20 口是 led

void delay(u16 i)
{
while(i--);
}

void Int0Init()
{
//设置 INT0
IT0=1;//跳变沿出发方式(下降沿)
EX0=1;//打开 INT0 的中断允许。
EA=1;//打开总中断
}

void main()
{
Int0Init(); // 设置外部中断 0
while(1);
}

void Int0() interrupt 0 //外部中断 0 的中断函数
{
delay(1000); //延时消抖
if(k3==0)
{
led=~led;
}
}

所以,为什么要用中断?这和我用if判断k3是否按下,然后,对led灯操作有什么区别?根本无法体现中断的优越,就这个代码困扰了我半天,让我一直觉得中断就仅仅是个函数。

Q:到底什么是中断?
A:就是让单片机暂停执行main()函数而去执行"中断函数"。

既然让单片机暂停,那必须给它一个信号。

来看看他们的PPT怎么说的。

(P3.2)可由IT0(TCON.0)选择其为低电平有效还是下降沿有效。
当CPU检测到P3.2引脚上出现有效的中断信号时,中断标志IE0(TCON.1)置1,向CPU申请中断。

好了,还是看我解释吧。

IT0=1;//跳变沿出发方式(下降沿)
EX0=1;//打开 INT0 的中断允许。
EA=1;//打开总中断

首先,EX0和EA暂且当做两个开关,都打开,才能用中断。

IT0则是,监听这个信号的方式。

又要去看独立按键那了
(点我去看)

IT0=1;
每次一个下降沿就是一个信号
IT0=0;
低电平是一个信号

不想多说了,看一下我修改的代码,感受一下什么是中断吧。

根本无需定义k3按键,而且采用IT0=1为什么还要消抖?真的无法理解。

#include "reg52.h" 

sbit led=P2^0;

void main()
{
		  //设置 INT0
	IT0=1;//跳变沿出发方式(下降沿)
	EX0=1;//打开 INT0 的中断允许。
	EA=1; //打开总中断
	while(1);
}

void Int0() interrupt 0 //外部中断 0 的中断函数
{
	led=~led;
}

回去看看普中的,这个是不是简单明了。

返回顶部


定时器/计数器:

振荡脉冲频率为12MHz时,那么它的时钟周期为1/12MHz,一个机器周期=时钟周期X12=1μs。

我的板子振荡脉冲频率11.018398MHz,那么它的时钟周期为1/11.018398MHz,一个机器周期=12/11.018398MHz≈1.08μs

所以为了方便,用12MHz举例。

这有两种方式计时:

第一种,计数位数时16位,由TH0作为高八位,TL0 作为低八位,组成16位加一计数器。

在TR0 =1后单片机开始计时,每经过一个机器周期单片机输出一个脉冲使定时器加一,加到16位全为1时会溢出,使TF0 =1,利用此性质可以去执行相应的功能。

TF0=1当做一个信号。类似于上一节P3^2中断里,将它的下降沿作为信号。

设置初值:

我们要的时间间隔都是1s、2s之类,那么,我们让开始计时到TF0 置1这个时间间隔变成1ms,然后for循环1000次,以此实现计时1s。

若 高八位 TH0 和 低八位 TL0 的初值都为0(即00000000 00000000),当16位全为1时,单片机一共经历了65536个机器周期,时间经过了65536μs。

以单次定时1ms(1000μs)为例。假设16位定时器的初值为x,由于单片机定时固定到达65536溢出,那么(65536-x)*12/12M = 1000,可计算得到x = 64536。

对应16进制为0x3cb0。那么高八位TH0 = 0x3c, 低八位TL0 = 0xb0。

#include "reg52.h"			 

sbit led=P2^0;	 //定义P20口是led

void Timer0Init()
{
	TMOD|=0X01; //选择为定时器0模式,工作方式1,仅用TR0打开启动。

	TH0=0XFC;	//给定时器赋初值,定时1ms
	TL0=0X18;	
	ET0=1;		//打开定时器0中断允许
	EA=1;		//打开总中断
	TR0=1;		//打开定时器			
}

void main()
{	
	Timer0Init();  //定时器0初始化
	while(1);		
}

void Timer0() interrupt 1
{
	static int i;
	TH0=0XFC;	//给定时器赋初值,定时1ms
	TL0=0X18;	//11111100 00011000 (64536)
				//65536-64536=1000μs
	i++;
	if(i==1000)	//循环1000次  为1000ms=1s
	{
		i=0;
		led=~led;	
	}	
}

实现效果:led灯持续点亮1s,熄灭1s.

#include "reg52.h"			

char code smgduan[17]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,
					0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71};//显示0~F的值
char n=0;

void Timer1Init()
{
	TMOD|=0X10; //选择为定时器1模式,工作方式1,仅用TR1打开启动。

	TH1=0XFC;	//给定时器赋初值,定时1ms
	TL1=0X18;	
	ET1=1;		//打开定时器1中断允许
	EA=1;		//打开总中断
	TR1=1;		//打开定时器			
}
void delay(int i)
{
	while(i--);	
}
void main()
{	
	Timer1Init();  //定时器1初始化
	while(1);		
}

void Timer1() interrupt 3
{
	int i;
	TH1=0XFC;	//给定时器赋初值,定时1ms
	TL1=0X18;
	i++;
	if(i==1000)
	{
		i=0;
		P0=smgduan[n++];
		if(n==16)n=0;	
	}	
}

实验效果:第一位数码管进行16进制计时

返回顶部


EEPROM-IIC总线:

返回顶部


  • 12
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 10
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值