51单片机入门教程(二极管、按键、数码管、138译码器、矩阵键盘、定时器、串口、LED、DS1302、蜂鸣器)

一、 STC89C52

单片机:Micro Controller  Unit。

51内核:

C6、C5相当于蓄水池,保持水流稳定。

X1晶振电路,程序一条条执行,晶振产生单片机所必须的时钟频率,让程序一步步往下走。晶振的提供的时钟频率越高,那单片机的运行速度也就越快。32.768K晶振通常用于时间显示,16MHZ、26MHZ等用于传输信号。C2、C3起振电容作用:稳定频率。R2是为了电路稳定工作。

R1复位电路。高电平复位,因为刚接高电平电容没有电,电容相当于短路,电流直接过去。充满之后电容相当于断路,9线变为低电平。

s1-s16:矩阵按键。

k1-4:独立按键。

DS1302:时钟芯片(闹钟、定时控制)。

AD/DA:模数转换器,将0、5v转为0-5v,数字信号转为模拟信号(单片机LED可以渐进亮度)。

单片机电平是TTL 电平,高电平+5v,低电平0v。

GR1:光敏电阻,103电位器(滑动变阻器)。

NTC1:热敏电阻

U15:触摸屏芯片。

U14:步进电机:精确控制角度、速度。

138译码器:驱动数码管的。

24C02:E方PROM,掉电不丢失。

74HC245:驱动数码管。

PR9和PR10:限流电阻,102=10 00=1K电阻,473=47 000=47K.

二、LED发光二极管

LED发光二极管:Light Emitting Diode

CPU通过控制寄存器来控制硬件电路。

D1-D8电路图:

P20接高电平LED灯不亮,接低电平才亮,控制引脚输出高低电平,P2是从STC搭线过来的。

跑马灯程序:

 

Delay函数延迟1ms:

#include <REGX52.H>
void Delay500ms(unsigned int num)		//stc-isp软件延时计算器长度1ms、指令集为STC-Y1
{
	unsigned char i, j;
	while(num)
	{
			i = 2;
			j = 239;
	do
		{
			while (--j);
		} while (--i);
		num--;
	} 
	}

void main()
{
	while(1)
	{
		P2=0xFE;
		Delay500ms(600);
		P2=0xFD;
		Delay500ms(500);
		P2=0xFB;
		Delay500ms(400);
		P2=0xF7;
		Delay500ms(300);
		P2=0xEF;
		Delay500ms(200);
		P2=0xDF;
		Delay500ms(100);
		P2=0xBF;
		Delay500ms(100);
		P2=0x7F;
		Delay500ms(100);
	}
}

三、独立按键 

独立按键内部结构:

 

在<REGX52.H>头文件中,sfr P0  = 0x80;表示字节寄存器,8位为一组的。 sbit P0_0=0x80;表示位寄存器。

独立按键电路图:

单片机上电,所有IO口默认都是高电平,在开漏输出下,默认输出电平被上拉电阻拉成高电平。

对于机械开关,机械触点断开、闭合时,一个开关在闭合时不会马上接通、断开也不立即断开而是会有抖动。 

#include<REGX52.H>
void Delay(unsigned int x)
{
	unsigned char i,j;

	while(x)
	{
		i=2;
		j=239;
		do
		{
			while(j--);
		}while(i--);
		x--;
	}

}
//单独按键控制LED
void main(){
	      while(1)
				{
					if(P3_1==0)
					{
						Delay(10);
						while(P3_1==0);
						Delay(10);
						P2_0=~P2_0;
					}
				}
}

//独立按键控制LED显示二进制
void main(){
				unsigned char num=0;
	      while(1)
				{
					if(P3_1==0)
					{
						while(P3_1==0);

                        //unsigned int 类型超出最大值时他会从0开始
						num++;
               
						P2=~num;
					}
				}
}
//独立按键控制LED移位
void main(){
				unsigned char num=0;
				P2=~0x01;
	      while(1)
				{
					if(P3_1==0)
					{
						Delay(10);
						while(P3_1==0);
						Delay(10);
						num++;
						if(num>=8)
						{
							num=0;
						}
							P2=~(0x01<<num);
					}
				}
}

每更新一次文件,都要重新导入文件然后烧录。

四、LED数码管 

LED数码管电路图:

COM:接地线。

74HC245:双向数据缓冲器。作用是提高驱动能力。如果没有缓冲器,那么P的数据要直接驱动LED显示,有了缓冲器,数据就是信号了,因为缓冲器接了VCC,驱动能力就强。

读二进制数是从高位P07开始读。

OE其实是CE(chip enable),低电平有效,不接地就不工作。

DIR是方向,是送数据还是读数据。如果接高电平,就把左边数据送到右边。

J21是跳线帽,两个插槽,让他们短路。如果跳线帽在LE和VCC上,就是把LE接在VCC,那么左边一直是高电平。

单片机高电平驱动能力有限,输出的最大电流不能太大,低电平驱动能力强。

104是电容,100K pF=100nF。pF,nF,uF,mF,F,用来稳定电源,电源滤波。

RP4是排阻,100R=100欧,限流电阻,4位一体。

五、138译码器

G1、G2A、G2B是使能端,G1要接高电平1,G2A、G2B接低电平0才能工作。

3位ABC控制8个LED。

共阴极连接,3、8接Gnd。

共阳极连接,A段阳极连接到3、8引脚,阴极连接到7引脚。

位选:7、6、4、2、1、9、10、5端。

定义段,正好8位一一对应(51单片机是8位单片机)。

就近原则:段离哪个引脚近,就从哪个引脚引出。

//静态数码管显示
#include<REGX52.H>
	unsigned char numarr[]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F,0x77,0x7C,0x39,0x5E,0x79,0x71,0x00};
void display(unsigned char led,unsigned char num)
{
	switch(led)
	{
		case 1:P2_2=1;P2_3=1;P2_4=1;break;
		case 2:P2_2=1;P2_3=1;P2_4=0;break;
		case 3:P2_2=1;P2_3=0;P2_4=1;break;
		case 4:P2_2=1;P2_3=0;P2_4=0;break;
		case 5:P2_2=0;P2_3=1;P2_4=1;break;
		case 6:P2_2=0;P2_3=1;P2_4=0;break;
		case 7:P2_2=0;P2_3=0;P2_4=1;break;
		case 8:P2_2=0;P2_3=0;P2_4=0;break;	
	}
	P0=numarr[num];
}
void main()
{
	display(7,5);
}

六、模块化编程

函数模块化编程:.c(函数、变量的定义)+.h文件(函数、变量的声明)。

LCD1602液晶屏调试窗口:

LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)。

LCD_ShowString(unsigned char Line,unsigned char Column,char *String)。

LCDShowChar ( unsigned char Line, unsigned char Column, char Char)。

LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length)。

LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)。

LCD1602原理图:

当RS和R/W共同为低电平时可以写入指令或显示地址;当RS为低电平,R/W为高电平时,可以读忙信号;当 RS为高电平,R/W为低电平时,可以写入数据。 

E端为使能端,当E端由高电平跳变为低电平时(下降沿有效),液晶模块执行命令。

#include <REGX52.H>
#include "LCD1602.h"
void main()
{
	LCD_Init();
	LCD_ShowChar(1,1,'A');
	while(1){};
}

七、矩阵键盘

矩阵按键模块原理图:

检测逻辑:逐行扫描是把上面4个赋值,比如扫描s1行,那就P17为0(引脚默认为高电平),P16、P15、P14为1,如果P13为0那就是S1按下。逐列扫描是把下面4个赋值。

单片机I/O口工作模式:弱上拉强下拉(准双向口):输出1能力比较弱,输出0能力比较强。

上拉电阻是把一个信号通过一个电阻接到电源(Vcc),下拉电阻是一个信号通过一个电阻接到地(GND)。高电平容易被外部信号拉低。

芯片的管脚有三个类型,输出(Output,简称O)、输入(Input,简称I)和输入输出(Input/Output,简称I/O)。

芯片的输入管脚,输入的状态有三个:高电平、低电平和高阻状态。高阻状态,即管脚悬空,很可能造成输入的结果是不定状态,引起输出震荡。有些应用场合不希望出现高阻状态,可以通过上拉电阻或下拉电阻使管脚稳定状态。

高阻输入:没有上拉也没有下拉,仅做输入,减少上拉电阻对输入的影响。 

推挽输出:没有上拉电阻,高电平直接接Vcc,低电平直接接Gnd。

数码管扫描(输出扫描):显示第1位->显示第2位->显示第3位->······,快速循环,同时显示。

矩阵键盘(输入扫描):读取第1行(列)->读取第2行(列)->······快速循环,同时显示。

按下矩阵键盘显示相应数字:

MatrixKey.c

#include <REGX52.H>
#include "Delay.h"
unsigned int MatrixKey(){
	unsigned int num=0;
	P1=0xff;
	P1_3=0;
	if(P1_7==0){Delay(20);while(P1_7==0);Delay(20);num=1;}
		if(P1_6==0){Delay(20);while(P1_6==0);Delay(20);num=5;}
			if(P1_5==0){Delay(20);while(P1_5==0);Delay(20);num=9;}
				if(P1_4==0){Delay(20);while(P1_4==0);Delay(20);num=13;}
					P1_2=0;
	if(P1_7==0){Delay(20);while(P1_7==0);Delay(20);num=2;}
		if(P1_6==0){Delay(20);while(P1_6==0);Delay(20);num=6;}
			if(P1_5==0){Delay(20);while(P1_5==0);Delay(20);num=10;}
				if(P1_4==0){Delay(20);while(P1_4==0);Delay(20);num=14;}
					P1_1=0;
	if(P1_7==0){Delay(20);while(P1_7==0);Delay(20);num=3;}
		if(P1_6==0){Delay(20);while(P1_6==0);Delay(20);num=7;}
			if(P1_5==0){Delay(20);while(P1_5==0);Delay(20);num=11;}
				if(P1_4==0){Delay(20);while(P1_4==0);Delay(20);num=15;}
					P1_0=0;
	if(P1_7==0){Delay(20);while(P1_7==0);Delay(20);num=4;}
		if(P1_6==0){Delay(20);while(P1_6==0);Delay(20);num=8;}
			if(P1_5==0){Delay(20);while(P1_5==0);Delay(20);num=12;}
				if(P1_4==0){Delay(20);while(P1_4==0);Delay(20);num=16;}
		return num;
				
	
	
}

MatrixKey.h

#ifndef _MATRIXKEY_H_
#define _MATRIXKEY_H_
unsigned int MatrixKey();
#endif
#include <REGX52.H>
#include "Delay.h"
#include "LCD1602.h"
#include "MatrixKey.h"
	unsigned int number;
void main(){
	LCD_Init();
	LCD_ShowString(1,1,"hello world");
	while(1){
		number= MatrixKey();
		if(number)
		{
			LCD_ShowNum(2,1,number,2);//最后是显示数据长度2
		}
	}
}

矩阵键盘密码锁:

Delay.h

#ifndef __DELAY_H__
#define __DELAY_H__
void Delay(unsigned int num);
#endif

Delay.c

void Delay(unsigned int num)
{
  unsigned char i, j;
	while(num)
	{
			i = 2;
			j = 239;
	do
		{
			while (--j);
		} while (--i);
		num--;
	} 
}

LCD1602.C

#include <REGX52.H>

//引脚配置:
sbit LCD_RS=P2^6;
sbit LCD_RW=P2^5;
sbit LCD_EN=P2^7;
#define LCD_DataPort P0

//函数定义:
/**
  * @brief  LCD1602延时函数,12MHz调用可延时1ms
  * @param  无
  * @retval 无
  */
void LCD_Delay()
{
	unsigned char i, j;

	i = 2;
	j = 239;
	do
	{
		while (--j);
	} while (--i);
}

/**
  * @brief  LCD1602写命令
  * @param  Command 要写入的命令
  * @retval 无
  */
void LCD_WriteCommand(unsigned char Command)
{
	LCD_RS=0;
	LCD_RW=0;
	LCD_DataPort=Command;
	LCD_EN=1;
	LCD_Delay();
	LCD_EN=0;
	LCD_Delay();
}

/**
  * @brief  LCD1602写数据
  * @param  Data 要写入的数据
  * @retval 无
  */
void LCD_WriteData(unsigned char Data)
{
	LCD_RS=1;
	LCD_RW=0;
	LCD_DataPort=Data;
	LCD_EN=1;
	LCD_Delay();
	LCD_EN=0;
	LCD_Delay();
}

/**
  * @brief  LCD1602设置光标位置
  * @param  Line 行位置,范围:1~2
  * @param  Column 列位置,范围:1~16
  * @retval 无
  */
void LCD_SetCursor(unsigned char Line,unsigned char Column)
{
	if(Line==1)
	{
		LCD_WriteCommand(0x80|(Column-1));
	}
	else if(Line==2)
	{
		LCD_WriteCommand(0x80|(Column-1+0x40));
	}
}

/**
  * @brief  LCD1602初始化函数
  * @param  无
  * @retval 无
  */
void LCD_Init()
{
	LCD_WriteCommand(0x38);//八位数据接口,两行显示,5*7点阵
	LCD_WriteCommand(0x0c);//显示开,光标关,闪烁关
	LCD_WriteCommand(0x06);//数据读写操作后,光标自动加一,画面不动
	LCD_WriteCommand(0x01);//光标复位,清屏
}

/**
  * @brief  在LCD1602指定位置上显示一个字符
  * @param  Line 行位置,范围:1~2
  * @param  Column 列位置,范围:1~16
  * @param  Char 要显示的字符
  * @retval 无
  */
void LCD_ShowChar(unsigned char Line,unsigned char Column,char Char)
{
	LCD_SetCursor(Line,Column);
	LCD_WriteData(Char);
}

/**
  * @brief  在LCD1602指定位置开始显示所给字符串
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  String 要显示的字符串
  * @retval 无
  */
void LCD_ShowString(unsigned char Line,unsigned char Column,char *String)
{
	unsigned char i;
	LCD_SetCursor(Line,Column);
	for(i=0;String[i]!='\0';i++)
	{
		LCD_WriteData(String[i]);
	}
}

/**
  * @brief  返回值=X的Y次方
  */
int LCD_Pow(int X,int Y)
{
	unsigned char i;
	int Result=1;
	for(i=0;i<Y;i++)
	{
		Result*=X;
	}
	return Result;
}

/**
  * @brief  在LCD1602指定位置开始显示所给数字
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:0~65535
  * @param  Length 要显示数字的长度,范围:1~5
  * @retval 无
  */
void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
	unsigned char i;
	LCD_SetCursor(Line,Column);
	for(i=Length;i>0;i--)
	{
		LCD_WriteData(Number/LCD_Pow(10,i-1)%10+'0');
	}
}

/**
  * @brief  在LCD1602指定位置开始以有符号十进制显示所给数字
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:-32768~32767
  * @param  Length 要显示数字的长度,范围:1~5
  * @retval 无
  */
void LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length)
{
	unsigned char i;
	unsigned int Number1;
	LCD_SetCursor(Line,Column);
	if(Number>=0)
	{
		LCD_WriteData('+');
		Number1=Number;
	}
	else
	{
		LCD_WriteData('-');
		Number1=-Number;
	}
	for(i=Length;i>0;i--)
	{
		LCD_WriteData(Number1/LCD_Pow(10,i-1)%10+'0');
	}
}

/**
  * @brief  在LCD1602指定位置开始以十六进制显示所给数字
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:0~0xFFFF
  * @param  Length 要显示数字的长度,范围:1~4
  * @retval 无
  */
void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
	unsigned char i,SingleNumber;
	LCD_SetCursor(Line,Column);
	for(i=Length;i>0;i--)
	{
		SingleNumber=Number/LCD_Pow(16,i-1)%16;
		if(SingleNumber<10)
		{
			LCD_WriteData(SingleNumber+'0');
		}
		else
		{
			LCD_WriteData(SingleNumber-10+'A');
		}
	}
}

/**
  * @brief  在LCD1602指定位置开始以二进制显示所给数字
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:0~1111 1111 1111 1111
  * @param  Length 要显示数字的长度,范围:1~16
  * @retval 无
  */
void LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
	unsigned char i;
	LCD_SetCursor(Line,Column);
	for(i=Length;i>0;i--)
	{
		LCD_WriteData(Number/LCD_Pow(2,i-1)%2+'0');
	}
}

LCD1602. H

#ifndef __LCD1602_H__
#define __LCD1602_H__

//用户调用函数:
void LCD_Init();
void LCD_ShowChar(unsigned char Line,unsigned char Column,char Char);
void LCD_ShowString(unsigned char Line,unsigned char Column,char *String);
void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);
void LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length);
void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);
void LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);

#endif
#include <REGX52.H>
#include "Delay.h"
#include "LCD1602.h"
#include "MatrixKey.h"
	unsigned int number;
	unsigned int password;
	unsigned int count=0;
void main(){
	LCD_Init();
	LCD_ShowString(1,1,"Password");
	while(1){
		number= MatrixKey();
		if(number)
		{
			if(number<=10)
			{
			if(count<4)
				{
					password=password*10;//左移一位
					password+=number%10;//10当作0
					count++;
				}
				LCD_ShowNum(2,1,password,4);
			}

			if(number==11)//11当作确认键
			{
				if(password==1234)//不是“1234”
				{
								LCD_ShowString(1,14,"OK");//一共16列从倒数第三个开始
							password=0;
				count=0;
									LCD_ShowNum(2,1,password,4);
				}
				else
				{
							LCD_ShowString(1,14,"ERR");
							password=0;
				     count=0;
									LCD_ShowNum(2,1,password,4);
				}
			}
		
			if(number==12)//12取消键
			{
					LCD_ShowString(1,14,"CANCEL");
				password=0;
				count=0;
				LCD_ShowNum(2,1,password,4);
	
			}
	
		}
	}
}

出现requires ANSI-style prototype的问题,要么是没有导入头文件,要么方法的形参或者名称引用时有错误。

八、定时器

定时器:内部资源,可实现软件计时,可替换长时间Delay。

定时器个数:T0、T1、T2 。T0、T1与传统51单片机兼容,T2是89C52新增的资源。

定时器根据时钟信号,每隔一个时间段,计数单元的值+1,增加到“设定的闹钟提醒时间”,计数单元就会向中断系统发出中断申请。

STC89C52的T0和T1均有四种工作模式:
模式0:13位定时器/计数器。
模式1:16位定时器/计数器(常用)。
模式2:8位自动重装模式。
模式3:两个8位计数器。

TL0:time or low。

TH0:time or high。 

TF0:time or flag,标志位,超出65535申请中断。

T0Pin:单片机外部的接口,外部引脚T0。

SYSclk(system clock):系统时钟,即晶振周期,89C52晶振为12MHz。

C/T头上-:如果一位配置为高电平,它就是C功能(counter),给0就是timer(定时器),给1是计数器。

➗12:进12分频是1MHz,1MHz的一个周期是1ms,每个1ms就要计一次数。

INT0:外部中断引脚。

GATE:打开定时器/计数器,由TR0单独控制或者INT0+TR0联合控制。联合控制就是两者都为高才打开定时器/计数器。后面跟的是非门、或门(有1计1)、与门(有0计0)。

STC89C52:4个外部中断,3个定时器中断,1个串口中断。

单片机通过配置寄存器控制内部电路连接->控制不同电路->完成不同功能。比如寄存器可以控制开关往哪里开。寄存器是连接软硬件的媒介。

time or control:

TR1:定时器开始。

IE1:interrupt enable,定时器1的中断使能。

TF:溢出标志位。

IE、IT和INT0有关。

可位寻址:可以对每一位单独赋值。

不可位寻址:只能整体赋值。

定时器的相关寄存器: 

中断寄存器:

EA:总开关。

ET:中断允许位。

PT:控制优先级。

初始化之后,从while中断到中断函数。

#include <REGX52.H>
void Timer0_Init(){
//	TMOD=0x01;//0000 0001
	//假如高4位配置好了,低4位要配置0001,解决配置低4位的时候覆盖高4位的问题
	TMOD&=0XF0;//高4位保持不变,低4位清0
	TMOD|=0X01;
	
	//配置TCON
	TF0=0;
	TR0=1;
	//计数脉冲在12MHz情况下每隔1us计数+1,加到最大值产生中断。
	//计数器可以从0计数到65535,总共时间65ms,可以每隔1ms产生一次中断,1000次中断就是1s,此时TF0=1;
	//123/100=1,存一位;123%100=23;存两位,寄存器8位,两个寄存器拼接的65535,所以但看寄存器0要分开。
//把高八位拿出来
	TH0=64535/256;//0xFC
//把低八位拿出来
	TL0=64535%256;//0x17,stp生成是0x18,应该是64535%256+1
	ET0=1;
	EA=1;
//PT0默认为0
	PT0=0;
	
}
void main()
{
	Timer0_Init();
while(1)
{
	
}
}
unsigned int T0count;
//中断服务程序
void Timer0_Routine() interrupt 1
{
//TH、TL执行初始化后会变成0,要修改回来
	TH0=64535/256;
	TL0=64535%256;
	T0count++;
	if(T0count>=1000)
	{
		T0count=0;
		P2_0=~P2_0;
	}
}

可以使用stp生成定时器初始化的代码。

6T和12T指的是12分频和6分频。

删除AUXR,增加ET0=1;EA=1; 

LED流水灯

#include<REGX52.H>
#include "delay.h"
#include "LCD1602.h"
#include <INTRINS.H>
void Time0_Init()
{

	TMOD &= 0xF0;		
	TMOD |= 0x01;		
	TL0 = 0x18;		
	TH0 = 0xFC;		
	TF0 = 0;		
	TR0 = 1;		
	ET0=1;
	EA=1;
	PT0=0;
}
	 unsigned  int Tcount;
	 unsigned  int cou;
void main()
{
	P2=0xFE;
	Time0_Init();
	
  while(1)
  {
  }
}

void Timer0_Routine() interrupt 1
{
	TL0 = 0x18;		
	TH0 = 0xFC;
	Tcount++;
	if(Tcount>=1000)
	{
		Tcount=0;
		cou++;
		P2=_crol_(P2,1);

	}

	
}

液晶屏显示计时:

#include<REGX52.H>
#include "delay.h"
#include "LCD1602.h"
#include <INTRINS.H>
void Time0_Init()
{

	TMOD &= 0xF0;		
	TMOD |= 0x01;		
	TL0 = 0x18;		
	TH0 = 0xFC;		
	TF0 = 0;		
	TR0 = 1;		
	ET0=1;
	EA=1;
	PT0=0;
}
	 unsigned  int Tcount,min,hour;
	 unsigned  int sec;
void main()
{
	LCD_Init();
	Time0_Init();
	LCD_ShowString(1,1,"  :  :");
  while(1)
  {
		LCD_ShowNum(1,1,hour,2);
				LCD_ShowNum(1,4,min,2);
				LCD_ShowNum(1,7,sec,2);
		
  }
}

void Timer0_Routine() interrupt 1
{
	TL0 = 0x18;		
	TH0 = 0xFC;
	Tcount++;
	if(Tcount>1000)
	{
		Tcount=0;
		sec++;
		if(sec>=60)
		{
			min++;
			sec=0;
			if(min>=60)
			{
				min=0;
				hour++;
				if(hour>=24)
				{
					hour=0;
				}
			}
		}
		

	}

	
}

九、串口

串口是通讯接口,实现两个设备互相通信。

51单片机自带UART(通用异步收发器),可实现单片机串口通信。

TXD:transmit exchange data

TXD与RXD要交叉连接。
当电平标准不一致时,需要加电平转换芯片。

串口常用的电平标准有如下三种:
TTL电平▏:+5V表示1,0V表示0。单片机使用TTL电平。
RS232电平:-3~-15V表示1,+3~+15V表示0。
RS485电平:两线压差+2~+6V表示1,-2~-6V表示0(差分信号即两线压差,不相对于Gnd)。差分传输,CAN总线、USB使用该电平,传输距离一千多米,RS232和TTL只能传输十几米,其相对于Gnd。

通信接口比较:

点对点通信:只能两个设备互相通信,不能多个设备一起通信。

单片机板子上的24C02,是存储数据的芯片,数据读出写入靠I方C实现。

单片机板子上的DS1302通信方式 类似SPI协议。

单片机板子上的DS18B202 1-Wire。

STC89C52有1个UART
STC89C52的UART有四种工作模式:
模式0:同步移位寄存器。
模式1:8位UART,波特率可变(常用)。
模式2:9位UART,波特率固定。
模式3:9位UART。
 

波特率:采样的速率,串口通信的速率,发送和接收数据位的间隔时间。传送多少数据帧,比特率是传送多少位。

时序图:

奇偶校验:奇校验:1是奇数,如果原数据1是偶数再加1位。

先发低位后发高位,0011 0101,从右往左发。 

串口集成了UART收发器,所以不需要了解时序图,收发就好了。

串口模式图:内部怎么收发。

SBUF:串口数据缓存寄存器,物理上是两个独立的寄存器,但占用相同的地址。写操作时,写入的是发送寄存器,读操作时,读出的是接收寄存器。

TI:transmit interrupt,发送中段,RI:接收中断。

÷2是2分频。控制波特率,控制收发器的采样时间。

串口中断:

串行口相关寄存器: 

SMOD控制串口栏,POF、GF、PD、IDL电源控制。SM0/FE(frame error):检测帧错误,不用管。

IPH、IP是因为89C52有4个优先级。

串口工作:配置SCON,PCON,+配置中断的EA,ES+配置定时器T1。

在串口数据手册中:

REN:是否允许接收 。

T1=1,发送完了置1,然后用软件置0,还用这个判断是发送还是接收。

设置SCON=0100000

 SMOD波特率是否加倍,给1加倍。SMOD0检测帧错误。设置PCON=0

IE不需要配置,默认是不开启中断。

串口是对应定时器1的,定时器初始化函数需要修改。

十六位记的数多(0-65535),但每次都需要自己写的代码赋初值,浪费时间。双八位就是将十六位分开,一个计数,另一个存放初值,每次计数完成后AR(Auto Reload)会自动将值赋给CNT(计数器),不用代码处理,比较快,但只有八位所以记的数少了(0-255)。串口中需要使用双8位的模式(把两个定时器分开,自动重装)。

波特率加倍是为了分频,如果不加倍时钟变慢 就不能匹配了,会有较大误差 。

当我溢出的时候是用来计算波特率的,并不是判断中断的标志,在讲定时器的时候溢出后令ET0=1执行中断函数,但这里定时器是用来计算波特率的,串口这里接收中断和发送中断应该和定时器没关系(自己理解)

不开启中断,是因为需要我们手动清零。(课本有讲)

由于定时器T1的优先级高于串口中断的优先级,因此在定时器初始化的时候,不要打开定时器T1的中断允许标志,只需要打开定时器T1的计数功能即可,否则可能会出现无法进入串口中断的情况!

串口向电脑发送数据:

UART.c

#include <REGX52.H>

void UART_Init()
{
	PCON=0X80;//波特率加倍
	SCON=0X50;//0X40和0x50区别在于REN位不同
	TMOD&=0x0f;//清除定时器1模式位
	TMOD|=0X20;
	TL1=0XF4;//设定定时器1初值,0XF3打开串口调试会出现E6
	TH1=0XF4;//设定定时器1重装值
	TR1=1;//启动定时器1
	ET1=0;//关闭定时器1中断
	
}
void UART_SendByte(unsigned int byte)
{
	SBUF=byte;//串口模式图中,配置好定时器和波特率,然后byte给SBUF就可自动发
	while(TI==0);//检测是不是发送出去
	TI=0;//TI必须软件复位
}

UART.h

#ifndef _UART_H_
#define _UART_H_
void UART_Init();
void UART_SendByte(unsigned int byte);
#endif

main.c

#include<REGX52.H>
#include "delay.h"
#include "UART.h"
unsigned int num;

void main()
{
	UART_Init();
  while(1)
  {
			UART_SendByte(num);//波特率越低,误差越稳定
			num++;
			Delay(1000);
  }
}

效果:

电脑通过串口控制:
发送是只需要单片机发送,收是需要中断系统,电脑发过来就使用中断需要把REN位=1.即SCON=0X50。需要中断查询号。

注意:之前不用的中断是时钟中断,这里使用的中断是串口中断.

当你在发送区发送数据时,电脑端才会接受到中断,才会执行.

main.c

#include<REGX52.H>
#include "delay.h"
#include "UART.h"
unsigned int num;

void main()
{
	UART_Init();
  while(1)
  {

  }
}

//中断服务子函数
void UART_Routine() interrupt 4
{
	if(RI==1)
	{
		P2=~SBUF;
		RI=0;
		UART_SendByte(SBUF);
	}
}

F3=243,每隔256溢出一次,相当于计13个数溢出一次,12T的晶振在12T的模式下每隔1us计一次数,所以每隔13us溢出一次,溢出的频率就是1/13us MHz=0.07692,也就是T1溢出率。在串口模式图中,走1和÷16那么1/13us/16=0.004807MHz=4807Hz就是波特率,误差就是7/4800。

文本模式/字符模式:将原始数据编码(ASCII码)后显示。

十、LED点阵屏

引脚乱序排列。

逐行或者逐列扫描。比如9给1,其余行给0,扫描列。 

74H595:

74HC595是串行输入并行输出的移位寄存器,可用3根线输入串行数据,8根线输出并行数据,多片级联后,可输出16位、24位、32位等,常用于IO口扩展。恒压输出,如果多个灯点亮就可能亮度不均匀,当然也有恒流输出的。

如果OE(autoput enable,输出使能)上面加一个横线,代表低电平有效或者下降沿有效。

RCLK:register clock,寄存器时钟。默认低电平。

SERCLR:serial clear,串行清零端,把里面数据清空,接VCC代表不清空。默认低电平。

SER:串行数据。

QH‘:控制多片级联.QH’可能接其他的74HC595,如果寄存器数据满了没有RCLK移位,溢出的数据就给QH‘,QH’放到其他的74HC595的移位寄存器。

左边是移位寄存器,右边是输出缓存。 

RCLK用于移位,当一位数据进来,有一个上升沿进来,这位数据就下降一位,当八位数据满了,RCLK就把寄存器的8位移到输出缓存。

单片机IO口一上电是高电平,所以需要给SERCLK设置低电平初始化。而且IO口是弱上拉, 输出低电平可以接受很大电流,输出高电平电流小相当于接一个电阻再接VCC。

LED点阵原理图:

开发板引脚和对应关系: 

IO口驱动LED能力弱,可以加一个三极管驱动,这时候IO口相当于控制信号。

C51的sfr、sbit。

sfr(special function register):特殊功能寄存器声明,例:sfr P0= 0x80;声明P0口寄存器,物理地址为0x80。
sbit(special bit):特殊位声明例:sbit P0_1=0x81;或 sbit p0_1= P0_1;声明PO寄存器的第1位。
可位寻址/不可位寻址:在单片机系统中,操作任意寄存器或者某一位的数据时,必须给出其物理地址,又因为一个寄存器里有8位,所以位的数量是寄存器数量的8倍,单片机无法对所有位进行编码,故每8个寄存器中,只有一个是可以位寻址的。对不可位寻址的寄存器,若要只操作其中一位而不影响其它位时,可用“&=”、“1=”、“^=”的方法进行位操作。

74HC595电路:

 

让LED显示内容:

#include <REGX52.H>
#include "Delay.h"
	//不管前面的名称是什么,地址赋值过去就可以
sbit RCK=P3^5;//从P3_0开始,往后5位的地址
sbit SRK=P3^6;
sbit SER=P3^4;
#define LED_PORT P0//自己设计电路P0对应可能改变。P0是寄存器,不能用sbit

void WriteByte(unsigned int byte)
{
	unsigned char i;//不能在for里面用int i=0;
	for(i=0;i<8;i++)
	{
		SER=byte&(0x80>>i);//非0就是1
		SRK=1;
		SRK=0;
	}
	RCK=1;
	RCK=0;	
}

void LEDshow(unsigned char column,Data)//Data是断码,不能是data
{
	WriteByte(Data);
	LED_PORT=~(0x80>>column);
	Delay(1);
	LED_PORT=0xFF;//位清零
}
void main()
{
	SRK=0;
	RCK=0;
	while(1)
	{
		  LEDshow(1,0x80);
		 LEDshow(2,0x80);
		 LEDshow(3,0x60);
		
	}

}

 让LED实现动态效果:

Animation[]数组是存放在RAM里面,如果里面内容数据大,可以放FLASH程序存储器里面,里面的数据是不能更改的。声明:unsigned char code Animation[] = {}。

#include <REGX52.H>
#include "Delay.h"
	//不管前面的名称是什么,地址赋值过去就可以
sbit RCK=P3^5;//从P3_0开始,往后5位的地址
sbit SRK=P3^6;
sbit SER=P3^4;
#define LED_PORT P0//自己设计电路P0对应可能改变。P0是寄存器,不能用sbit
unsigned char Animation[] = {
0x30,0x78,0x7C,0x3E,0x7C,0x78,0x30,0x00,0x30,0x78,
0x7C,0x3E,0x7C,0x78,0x30,0x00,	
};

void WriteByte(unsigned int byte)
{
	unsigned char i;//不能在for里面用int i=0;
	for(i=0;i<8;i++)
	{
		SER=byte&(0x80>>i);//非0就是1
		SRK=1;
		SRK=0;
	}
	RCK=1;
	RCK=0;	
}

void LEDshow(unsigned char column,Data)//Data是断码,不能是data
{
	WriteByte(Data);
	LED_PORT=~(0x80>>column);
	Delay(1);
	LED_PORT=0xFF;//位清零
}
unsigned char i,count,offset;
void main()
{
	SRK=0;
	RCK=0;
	
	while(1)
	{
		for(i=0;i<8;i++)
		{
			LEDshow(i,Animation[i+offset]);
			if(i>=8) i=0;
			count++;
			if(count>=8)//count防止越界,灯亮8遍才到下一帧
			{count=0;offset++;if(offset>8) offset=0;} 
			}
			Delay(20);
		
	}

}

十一、DS1302实时时钟

DS1302是由美国DALLAS公司推出的具有涓细电流充电能力(Vcc有电给备用电池充电,没电使用备用电池)的低功耗实时时钟芯片。它可以对年、月、日、周、时、分、秒进行计时且具有闰年补偿等多种功能。SO。引脚有DIP和SO,DIP直插封装,SO贴片封装。
RTC(Real TimeClock):实时时钟,是一种集成电路,通常称为时钟芯片。

晶振通过内部电路处理生成稳定的频率,精度非常高。全称叫石英晶体增长器。

WP:写保护,置1写入无效。TCS、DS、RS用于涓流充电。

寄存器定义:

7位固定为1,6位置1操作RAM,置0操作CK(clock时钟)。RD是读,WR是写。

在时钟上升沿,IO口时钟将会被写入,数据写入时钟芯片 。时钟下降沿,DS1302的数据会输出。

以BCD码存储,BCD码(BinaryCoded Decimal),用4位二进制数来表示1位十进
制数。例:00010011表示13,10000101表示85,00011010不合法。在十六进制中的体现:0x13表示13,0x85表示85,0x1A不合法。

BCD码转十进制:DEC=BCD/16*10+BCD%16;(2位BCD)。
十进制转BCD码:BCD=DEC/1O*16+DEC%10;(2位BCD)。

通信接口:

时序表: 

都先要读命令字。 命令字解决了‘读写?在哪里?去哪里?’的问题。读出read用到下降沿。

read中,上升沿给IO口赋值,下降沿读出。

原理图:

LCD1602 显示时间:
DS1302.c

#include <REGX52.H>

sbit DS1302_SCLK=P3^6;
sbit DS1302_IO=P3^4;
sbit DS1302_CE=P3^5;//P3_6可行的前提是P3可位寻址,而P3^6是直接得到对应端口的地址,无论能不能位寻址都能用
#define DS1302_SECOND 0x80
#define DS1302_MINUTE 0x82
#define DS1302_HOUR 0x84
#define DS1302_DATE 0x86
#define DS1302_MONTH 0x88
#define DS1302_DAY 0x8A
#define DS1302_YEAR 0x8C
#define DS1302_WP 0x8E

unsigned char DS1302_Time[]={25,3,8,18,06,36,6};//不能是03,08

void DS1302_Init()
{
	DS1302_SCLK=0;
	DS1302_CE=0;
}
void DS1302_WriteByte(unsigned char Command,Data)
{
	unsigned char i;
	DS1302_CE=1;
	for(i=0;i<8;i++)
	{
		DS1302_IO=Command&(0x01<<i);
		DS1302_SCLK=1;
		DS1302_SCLK=0;//不需要延时也可以,
	}
		for(i=0;i<8;i++)
	{
		DS1302_IO=Data&(0x01<<i);
		DS1302_SCLK=1;
		DS1302_SCLK=0;
	}
		DS1302_CE=0;
}
unsigned DS1302_ReadByte(unsigned char Command,Data)
{

	unsigned char i;Data=0x00;
		Command|=0x01;//不要放第一句
	DS1302_CE=1;
	for(i=0;i<8;i++)
	{
		DS1302_IO=Command&(0x01<<i);
		DS1302_SCLK=0;
		DS1302_SCLK=1;//如果是10那就是在Command范围内读出data
	}
		for(i=0;i<8;i++)
	{
		DS1302_SCLK=1;
		DS1302_SCLK=0;//write是16个周期,read是15个周期,重复置1抵消1个周期
		if(DS1302_IO){Data|=(0x01<<i);}
	}
	DS1302_IO=0;
	DS1302_CE=0;
	return Data;
}
void DS1302_SetTime()
{
	DS1302_WriteByte(DS1302_WP,0x00);
	DS1302_WriteByte(DS1302_SECOND,DS1302_Time[0]/10*16+DS1302_Time[0]%10);
	DS1302_WriteByte(DS1302_MINUTE,DS1302_Time[1]/10*16+DS1302_Time[1]%10);
	DS1302_WriteByte(DS1302_HOUR,DS1302_Time[2]/10*16+DS1302_Time[2]%10);
	DS1302_WriteByte(DS1302_WP,0x80);//开启写保护
}
void DS1302_ReadTime()
{
	unsigned char temp;
	temp=DS1302_ReadByte(DS1302_SECOND);
	DS1302_Time[0]=temp/16*10+temp%16;
		temp=DS1302_ReadByte(DS1302_MINUTE);
	DS1302_Time[1]=temp/16*10+temp%16;
		temp=DS1302_ReadByte(DS1302_HOUR);
	DS1302_Time[2]=temp/16*10+temp%16;
}

DS1302.h

#ifndef _DS1302_H_
#define _DS1302_H_
unsigned char DS1302_Time[];//变量声明为外部变量,必须加上extern
  void DS1302_Init();
	void DS1302_WriteByte(unsigned char Command,Data);
	unsigned DS1302_ReadByte(unsigned char Command,Data);
	void DS1302_SetTime();	
void DS1302_ReadTime();
#endif

main.c

#include<REGX52.H>
#include "LCD1602.h"
#include "DS1302.h"
unsigned char Second;

void main()
{
	LCD_Init();
	DS1302_Init();
	DS1302_SetTime();	
	
	
  while(1)
  {
	 DS1302_ReadTime();
		LCD_ShowNum(1,1, DS1302_Time[0],2);
				LCD_ShowNum(1,3, DS1302_Time[1],2);
				LCD_ShowNum(1,5, DS1302_Time[2],2);
		
  }
}

可调时钟代码:

加入定时器、按键模块。

main.c

#include<REGX52.H>
#include "LCD1602.h"
#include "DS1302.h"
#include "key.h"
#include "Timer0.h"
unsigned char KeyNum,MODE,TimeFlash,TimeSelect;//两个模块:时钟显示、时钟设置
void TimeShow()
{
  DS1302_ReadTime();
	LCD_ShowNum(1,1, DS1302_Time[0],2);
	LCD_ShowNum(1,4, DS1302_Time[1],2);
	LCD_ShowNum(1,7, DS1302_Time[2],2);
	
}
void TimeSet()
{
	if(KeyNum==2) 
		{
			TimeSelect++;
			TimeSelect%=3;//越界清零
		}
		if(KeyNum==3) 
		{

			DS1302_Time[TimeSelect]++;
			DS1302_SetTime();
			if(DS1302_Time[0]>60){DS1302_Time[0]=0;}
			if(DS1302_Time[1]>60){DS1302_Time[1]=0;}
			if(DS1302_Time[2]>24){DS1302_Time[2]=0;}
						
		}
		if(KeyNum==4) 
		{
			DS1302_Time[TimeSelect]--;
			DS1302_SetTime();
			if(DS1302_Time[0]<0){DS1302_Time[0]=59;}
			if(DS1302_Time[1]<0){DS1302_Time[1]=59;}
			if(DS1302_Time[2]<0){DS1302_Time[2]=23;}
		}
		if(TimeSelect==0&&TimeFlash==1){	LCD_ShowString(1,1,"  ");}
			else {	LCD_ShowNum(1,1, DS1302_Time[0],2);}
			if(TimeSelect==1&&TimeFlash==1){	LCD_ShowString(1,4,"  ");}
			else {	LCD_ShowNum(1,4, DS1302_Time[1],2);}
			if(TimeSelect==2&&TimeFlash==1){	LCD_ShowString(1,7,"  ");}
			else {	LCD_ShowNum(1,7, DS1302_Time[2],2);}			
}
void main()
{
	LCD_Init();
	DS1302_Init();
	DS1302_SetTime();	
	LCD_ShowString(1,1,"  :  :  ");
	Timer0Init();

  while(1)
  {
	KeyNum=key();
	if(KeyNum==1)
	{
			if(MODE==0){MODE=1;}
			else if(MODE==1)
				{MODE=0;}
	}
	switch(MODE)
	{
		case 0:TimeShow(); break;
		case 1:TimeSet();break;
	}

		
  }
}

void Timer0_Routine() interrupt 1
{
	static unsigned int T0Count;
	TL0 = 0x18;		//设置定时初值
	TH0 = 0xFC;		//设置定时初值
	T0Count++;
	if(T0Count>=500)
	{
		T0Count=0;
		TimeFlash=!TimeFlash;//!是逻辑取反0取反是1,~是按位取反0取反是0xff
		
	}
}

十二、蜂鸣器

蜂鸣器按驱动方式可分为有源蜂鸣器和无源蜂鸣器。有显示正极的或者长脚是正极。
有源蜂鸣器:内部自带振荡源,将正负极接上直流电压即可持续发声,频率固定。
无源蜂鸣器:内部不带振荡源,需要控制器提供振荡脉冲才可发声,调整提供振荡脉冲(时钟周期)的频率,可发出不同频率的声音。

蜂鸣器样式:

蜂鸣器原理图:

单片机IO口不能直接驱动蜂鸣器。所以经过ULN2003芯片。

ULN2003是一个单片高电压、高电流的达林顿晶体管阵列集成电路。它是由7对NPN达林顿管组成的。

蜂鸣器接在其中一路。给0经非门后没有驱动能力,是一种高阻态,相当于断开。LED起到测试作用。

51单片机蜂鸣器是无源蜂鸣器,所以P15要提供固定频率。不能一直通电。默认通电是有电流的。

蜂鸣器播放提示音代码:

Buzzer.c

#include "Delay.h"
#include <REGX52.H>
#include <INTRINS.H>
sbit buzzer=P2^5;
unsigned int i;
void Buzzer_Delay500ms()
{
	unsigned char i;
	_nop_();//延时1个机器周期,12MHz的单片机机器周期为1us
	i = 247;
	while (--i);
}
void Buzzer_Time(unsigned int ms)
{
	for(i=0;i<ms*2;i++)
	{
		buzzer=!buzzer;
		Buzzer_Delay500ms();//1000HZ,
	}
}

main.c

#include <REGX52.H>
#include "Delay.h"
#include "key.h"
#include "Nixie.h"
#include "Buzzer.h"
unsigned char KeyNum;
sbit Buzzer=P2^5;//原理图上是P1^5,其实是P2^5

void main()
{
	while(1)
	{
		KeyNum=key();
		if(KeyNum)
		{
				Buzzer_Time(1000);
			}
			Nixie(1,KeyNum);

		}
	}

蜂鸣器播放音乐:

Timer0Init()中改TH0、TL0是无关紧要的,因为这是第一次中断的时间。TH0、TL0构成16位计数器,只能溢出申请中断然后归0,但不会自己清零,所以设一个初值,比如初值从中间开始计时,所以设置中断函数的TH0、TL0。把TH0从0XFC改为0XFD,重装值变大,溢出时间就变短,频率升高。

需要在半个周期翻转一次,取整是计数器计这么多就产生中断,重装载值就是中间值。

乐谱:#升音,b降音,半音是白旁边黑的,全音是白旁边的白的。 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

嵌入1024

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值