AutoLeaders控制组——51单片机学习笔记(二)

目录

写在最前面的话

模块化编程

模块化编程概述

模块化编程注意事项

C语言的预编译

 LCD1602调试工具

简介

使用前必备小知识~

尝试&注记

矩阵键盘

矩阵键盘简介

实例演示

定时器

定时器介绍

STC89C52定时器资源

定时器框图

定时器工作模式

计时器时钟

中断系统

相关概念~

STC89C52的中断资源

定时器相关寄存器

实例演示

按键控制LED流水灯模式

定时器时钟

串口通讯

串口介绍

硬件电路

电平标准

接口及引脚定义

常见通信接口比较

51单片机的UART

串口参数及时序图

串口模式图

串口和中断系统

串口相关寄存器

实例演示

LED点阵屏

简介

显示原理

74HC595  

C51的sfr、sbit

实例演示

DS1302实时时钟

内部结构框图

引脚定义和应用电路

​编辑

寄存器定义

时序定义 

实例演示

写在最前面的话

    本篇笔记整理自江科大自化协的51单片机教程~

    很感谢AL的师兄师姐和小伙伴们,以及zzh师兄和zzl师兄在这个阶段的学习给予过我的帮助~

模块化编程

模块化编程概述

  • 模块化编程是什么?有什么好处?

传统方式编程:所有的函数放在main.c里,当使用模块较多时,文件内的代码十分杂乱,不利于代码的组织和管理,也很影响编程者的思路。

模块化编程:把各个模块的代码放在不同的.c文件里,在.h文件里提供外部可调用的函数声明,其他.c文件想使用其中代码时,只需要include“xxx.h”文件即可。使用模块化编程可以极大提高代码的可阅读性、可维护性、可移植性

  • 为什么我们可以选择使用模块化代码?

单片机的编程过程中,关于如何显示、如何控制的代码才是我们真正需要重点关注、不断优化和修改的;而除此之外,还有很多模块是为我们程序目标做准备而存在的(驱动代码),在写好之后,几乎不需要更改了(比如上一篇笔记中设计的Delay函数)。所以我们可以采取模块化结构来设计代码的编写,使其更加井井有条,思路清晰。

模块化编程注意事项

  • .c文件:函数、变量的定义
  • .h文件:可被外部调用的函数、声明和变量的声明
  • 任何自定义的变量、函数在调用前必须有定义或声明(在同一个.c内)。
  • 使用到的自定义函数的.c文件必须添加到工程参与编译(所以在局部调试时,不需要调用的函数应当注释掉)。
  • 使用到的.h文件必须放在编译器可寻找到的地方(建议放在与main.c同一个目录里)。

C语言的预编译

  • 特征:是以“#include”开头。
  • 实现方式为:预编译,实际上就是帮助我们将代码“复制粘贴”到main里面。先预处理,再编译。

  • 作用:可以对程序中某些部分是否编译做出选择,从而达到程序代码的精简化。

例一:将Delay函数模块化,实现第一个LED以1s为周期闪烁。

 main.c文件中的内容如下:

#include <REGX52.H> //<> :在安装目录中寻找文件
#include "Delay.h"  //"" : 在自己程序目录中寻找文件

void main()
{
		while(1)
		{
				P2_0=1;
				Delay(500);
			    P2_0=1;
				Delay(500);
		}
}

 Delay.c文件中的内容如下:

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

Delay.h文件中的内容如下:

#ifndef _DELAY_H_ //每次新建.h文件都要做这一步:#ifndef _(填名字,不需要括号)_H_
#define _DELAY_H_

void Delay(unsigned int xms);//对函数进行声明。复制函数的第一行,并加上分号(一定不要忘记打分号)

#endif //与#ifndef配套使用,与其一起相当于一个括号的作用

例二:将Delay函数和nixie函数模块化,实现对数码管的控制(静止显示123)

 main.c文件中的内容如下:

(相比于上一篇笔记实现类似功能的代码,运用模块化编程后整个mian.c文件真的精简了很多,而且都只呈现体现逻辑的核心代码)

#include <REGX52.H>
#include "Delay.h"
#include "nixie.h"

void main()
{
		while(1)
		{		
			  nixie(1,1);
			  nixie(2,2);
			  nixie(3,3);
		}
}

 Delay.c文件中的内容如下:

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

 Delay.h文件中的内容如下:

#ifndef _DELAY_H_
#define _DELAY_H_

void Delay(unsigned int xms);//(一定不要忘记打分号)

#endif 

 nixie.c文件中的内容如下:

#include <REGX52.H>
#include "Delay.h"//在数码管函数中要用到Delay函数,所以也要给个头文件!
unsigned char nixietable[]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F};

void nixie(unsigned char location,unsigned char 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;
 }

nixie.h文件中的内容如下:

#ifndef _NIXIE_H_ 
#define _NIXIE_H_


void nixie(unsigned char location,unsigned char number);//分号!!

#endif

再次强调一下,函数声明必须加分号。如果忘了加,有可能会报错,显示下一个文件的开头有错误。

 LCD1602调试工具

简介

  • 基于模块化编程的一种工具,既是模块化的一个实例,也为我们代码的调试提供了极大的方便。
  • LCD1602液晶屏作为调试窗口时,能够提供类似printf函数的功能,能够实时单片机内部数据的变换情况,便于调试和演示。
  • 视频提供的LCD1602代码属于模块化代码。具体函数及其说明如下图所示:  

使用前必备小知识~

①组装:将LCD1602插到液晶屏右侧的排孔中(对准,装紧)。

②使用LCD1602后,LCD1602的引脚与数码管(P0口)以及左边三个LED灯发生了冲突,也就是说数码管和边三个LED灯就不能正常使用了,数码管就算有显示也是乱码。

虽然会发生冲突,但我们仍然使用LCD1602进行调试:因为LCD1602只与这几个的引脚产生冲突,相对于整块开发板而言,调试的效率还是很高的。

③可以用螺丝刀调节LCD1602屏幕的对比度。

尝试&注记

下面做一些尝试~

①使用LCD_ShowChar()显示“6-111 FOREVER”:

#include <REGX52.H>
#include "LCD1602.h"

void main()	
{
	LCD_Init();//初始化,每次使用LCD1602之前都必须调用这个初始化函数。一般来说初始化一次就好了。

	LCD_ShowChar(1,1,'');//三个参数,分别是行、列、要显示的字符。字符要用单引号括起来
	LCD_ShowChar(1,2,'6');
	LCD_ShowChar(1,3,'-');
	LCD_ShowChar(1,4,'1');
	LCD_ShowChar(1,5,'1');
	LCD_ShowChar(1,6,'1');
	LCD_ShowChar(1,7,' ');
	LCD_ShowChar(1,8,'F');
	LCD_ShowChar(1,9,'O');
	LCD_ShowChar(1,10,'R');
	LCD_ShowChar(1,11,'E');
	LCD_ShowChar(1,12,'V');
	LCD_ShowChar(1,13,'E');
	LCD_ShowChar(1,14,'R');

	while(1)
	{
	
	}
}

②当需要显示较多字符时,用LCD_ShowChar()函数会方便很多~

注意字符个数不能超出边界(不然直接被吃掉or显示乱码)

#include <REGX52.H>
#include "LCD1602.h"

void main()	
{
	LCD_Init();
	LCD_ShowString(1,3,"jyjj is cute!!");//三个参数分别对应:起始行,起始列,字符串
	while(1)
	{
	
	}
}

 ③和Delay函数结合,实现在屏幕上显示读秒:

#include <REGX52.H>
#include "LCD1602.h"
#include "Delay.h"

int Result=0;

void main()	
{
	LCD_Init();
	while(1)
	{
	  Result++;
	  Delay(1000);
	  LCD_ShowNum(1,1,Result,3) ;

	}
}

其他注记:

①LCD_ShowNum()

三个参数分别是起始行,起始列,给定数字,显示位数

如果显示的位数多于给定数的位数,则高位补零

②LCD_ShowSignedNum()

三个参数分别是起始行,起始列,给定数字,显示位数(正负号不是第一位!

Signed,可正可负

③LCD_ShowHexNum()

三个参数分别是起始行,起始列,给定十六进制数,显示位数

可以用来验证数据的正确性

④LCD_ShowBinNum()

三个参数分别是起始行,起始列,给定二进制数(不能直接输入二进制数!!通常用十六进制表示),显示位数

可以用来验证数据的正确性

矩阵键盘

(小tips:视频中矩阵键盘的调试都基于LCD1602)

矩阵键盘简介

  • 优势:减少l/O口的占用(由16个减少为8个)。矩阵应用得越多,在减少l/O口的占用上越明显。几乎所有的显示器都会采用矩阵方式进行扫描。
  • 特点:采用逐行或逐列“扫描”,就可以读出任意位置按键的状态。
  • 与数码管的比较: 

 ①扫描方式:

        数码管扫描是一种“输出扫描”(显示)

        原理:显示第一位 ->显示第二位  ->显示第三位->......,并且快速循环这整个过程,实现所有数码管“同时显示”的效果。

        矩阵键盘扫描是一种“输入扫描”(检测)

        原理:读取第一行(列)->读取第二行(列)->读取第三行(列)->......,并且快速循环这整个过程,实现所有按键“同时检测”的效果。

        值得一提的是,由于内部电路的设计,在本块开发板中如果采用逐行扫描,由于外设的互相干扰,蜂鸣器会自己响。所以我们采用逐列扫描。

        这两种扫描的共性:减少l/O口的占用(由16个减少为8个)

②连接方式:

相同之处:其中一端都连在GND上,另一端都连在四个不同的接口上 

实例演示

(注意事项及原因说明请看注释)

示例一:读取矩阵键盘的数码值,并显示在LCD1602显示屏上

main.c文件内代码如下所示:

#include <REGX52.H>
#include "Delay.h"
#include "LCD1602.h"
#include "Matrixkey.h"

unsigned char KeyNumber;
void main()
{
	LCD_Init();
	LCD_ShowString(1,1,"Matrixkey");
	while(1)
	{
	
		    KeyNumber=Matrixkey();

		    if(KeyNumber)//如果if语句的条件是一个变量,那么非零即真
		    {
			    LCD_ShowNum(2,1,KeyNumber,2);
		    }
	}
}

 Matrixkey.c文件内代码如下所示:

#include <REGX52.H>
#include "Delay.h"


/**
  * @brief 矩阵键盘读取按键键码
  * @param  无
  * @retval KeyNumber 按下按键的数码值
                      如果按住不放,程序会停留在子函数
                      松手的一瞬间,返回按键键码
                      没有按键按下时,返回0
  */


unsigned char Matrixkey()/*这个函数用于处理对按键的操作
                            根据按键判断按键是否按下
                            这样能够实现按键的操作和按键的扫描独立开来
                            从而降低代码的耦合性*/
{
	unsigned char KeyNumber=0;
	
	P1=0xFF;//先将P1全部置高
	P1_3=0;//要扫描第一列,所以单独把P1_3置零
	if (P1_7==0) {Delay(20);while(P1_7==0);Delay(20);KeyNumber=1;}/*如果按下按键
                                                                    消抖松开后,返回键数*/
	if (P1_6==0) {Delay(20);while(P1_6==0);Delay(20);KeyNumber=5;}
	if (P1_5==0) {Delay(20);while(P1_7==0);Delay(20);KeyNumber=9;}
	if (P1_4==0) {Delay(20);while(P1_7==0);Delay(20);KeyNumber=13;}
	
	
	P1=0xFF;
	P1_2=0;
	if (P1_7==0) {Delay(20);while(P1_7==0);Delay(20);KeyNumber=2;}
	if (P1_6==0) {Delay(20);while(P1_6==0);Delay(20);KeyNumber=6;}
	if (P1_5==0) {Delay(20);while(P1_7==0);Delay(20);KeyNumber=10;}
	if (P1_4==0) {Delay(20);while(P1_7==0);Delay(20);KeyNumber=14;}
	
	P1=0xFF;
	P1_1=0;
	if (P1_7==0) {Delay(20);while(P1_7==0);Delay(20);KeyNumber=3;}
	if (P1_6==0) {Delay(20);while(P1_6==0);Delay(20);KeyNumber=7;}
	if (P1_5==0) {Delay(20);while(P1_7==0);Delay(20);KeyNumber=11;}
	if (P1_4==0) {Delay(20);while(P1_7==0);Delay(20);KeyNumber=15;}
	
	P1=0xFF;
	P1_0=0;
	if (P1_7==0) {Delay(20);while(P1_7==0);Delay(20);KeyNumber=4;}
	if (P1_6==0) {Delay(20);while(P1_6==0);Delay(20);KeyNumber=8;}
	if (P1_5==0) {Delay(20);while(P1_7==0);Delay(20);KeyNumber=12;}
	if (P1_4==0) {Delay(20);while(P1_7==0);Delay(20);KeyNumber=16;}
	
	
	return KeyNumber;
}	

 Matrixkey.c文件内代码如下所示:

#ifndef _MATRIXKEY_H_
#define _MATRIXKEY_H_

unsigned char Matrixkey();

#endif

最终实现的效果是:按下哪个按键,LCD_1602显示屏会在第二行一、二列显示键码

示例二:①矩阵键盘的应用——电子密码锁

main.c文件内代码如下所示:

#include <REGX52.H>
#include "Delay.h"
#include "LCD1602.h"
#include "Matrixkey.h"

unsigned char KeyNumber;
unsigned int Password,Count;/*LCD1602能显示数字的范围是0~65535 
							 所以我们确定四位密码就能保证不溢出
                             所以用unsigned int就可以了*/

void main()
{
	LCD_Init();
	LCD_ShowString(1,1,"Password:");
	while(1)
	{
	
		KeyNumber=Matrixkey();
		if(KeyNumber)//如果if语句的条件填写一个变量,那么非零即真
		{
			if(KeyNumber<=10)//如果s1~s10按键按下,输入密码
			{
				  if(Count<4)//引入计数器,防止输入超出范围,出现乱码
						{
							Password*=10;/密码左移一位
							Password+=KeyNumber%10;/*获取一位密码
													 能够直接解决“用s10表示数字0”的表达					                
                                                     而无需专门写一个if条件判断
                                                     很巧妙的设计!*/

							Count++;
						}
					  	
							LCD_ShowNum(2,1,Password,4);/*更新显示
						                                 最后一个参数4
                                                         在这里表示“显示四位密码”*/
			}

			if(KeyNumber==11) //如果s11按下,确认
			{				
					if(Password==2345)
					{
						LCD_ShowString(1,14,"OK ");
						Delay(100);//如果按太用力容易直接跳转显示“ERR”,所以加了个延迟~
						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(KeyNumber==12)	//如果按键s12被按下,取消
				{
					Password=0;		//密码清零
					Count=0;		//计数器清零
					LCD_ShowNum(2,1,Password,4);	//更新显示
				}
		}
	}
}

定时器

定时器介绍

①51单片机的定时器属于单片机的内部资源,其电路的连接和运转均在单片机内部完成(按键/数码管/LCD1602是I/O口控制的外设)。

②作用:

        (1)用于计时系统,可实现软件计时,或者使程序每隔一定固定时间就完成一项操作(比如采样)

        (2)替代长时间的Dealy,提高CPU的运行效率和处理速度

        (3)切换任务的执行

        (4)......

STC89C52定时器资源

①定时器个数:3个(T0、T1、T2),T0和T1与传统的单片机兼容,T2是此型号单片机增加的资源

②需要注意的是,定时器资源和单片机型号是关联在一起的,不同型号会有不同的定时器和操作方式,但一般来说,T0和T1的操作方式是所有51单片机共有的

定时器框图

定时器在单片机内部根据时钟的输出信号,每隔一定的时间就输出相应的信号,计数单元也增加1。当计数单元增加到设定的“终点”时,计数单元会向中断系统发出中断申请,产生“响铃提醒”,使程序跳转到中断服务函数中执行。具体如下图所示:

定时器工作模式

STC89C52的T0和T1均有四种工作模式:

  • 模式0:13位定时器/计数器
  • 模式1:16位定时器/计数器(最常用)
  • 模式2:8位自动重装模式
  • 模式4:两个8位计数器

计时器时钟

  • 计时器实质上可以视为一个计数器,数值加到溢出即中断,“闹钟响”
  • 计数系统能存2个字节(高字节TH,低字节TL),能够数0到65535(有点像沙漏,可以类比unsigned int)
  • 溢出后,计数器会从65535回到0的位置,会置一个标志位(TF0),申请中断
  • TR0:控制定时器启动和暂停
  • 时钟有两个来源:①系统时钟(SYSclk),由晶振提供脉冲,晶振周期为(12MHz)。当把SYSclk接到12分频器时(如上图所示),周期就是1μs,即每隔1μs记一次数。 ②外部引脚(用T0口)提供的时钟。时钟提供“脉冲”,每检测到一个脉冲,计数器就+1。
  • C/T0:定时器    C/T1:计数器(选择C/T1还是C/T0的过程其实是配置寄存器的过程)

中断系统

相关概念~

中断系统是为CPU具有对外界紧急时间的实时处理能力而设置的。很多外设都需要中断系统,比如定时器,I/O口,串口等等,都需要。

中断:当中央处理器CPU正在处理某件事的时候外界发生了紧急事件请求,要求CPU暂停当时的工作,转而去处理这个紧急时间,处理完以后,再回到原来被中断的地方,继续原来的工作,这样的过程称为中断。

中断源:请示CPU中断的请求源成为中断源(比如定时器溢出就是一个中断源)。微型机中断系统一般允许多个中断源,当几个中断源同时向CPU请求中断时,CPU总是先相应优先级别高的中断请求。

中断程序流程:

注记:当定时器溢出,即发出中断请求时,那么执行就会跳转到中断处理程序。执行完后,再返回断点,继续执行主函数。这就相当于同时完成两项任务,能够大大提高效率。

STC89C52的中断资源

  • 中断源个数:外部中断0,定时器0中断,外部中断1,定时器1中断,串口中断,外部中断2,外部中断3(共8个)。
  • 中断优先级个数:4个
  • 中断号:如果使用C语言编程,查询次序号就是中断号(程序的定义与普通的子函数的区别:名字后面多了interrup+数字,数字表示中断入口)。
  •  中断的资源和单片机的型号是关联在一起的,不同型号可能会有不同的中断系资源,例如中断源个数不同、中断优先级个数不同等等。

定时器相关寄存器

  • 简介

寄存器是连接软硬件的媒介,相当于一个复杂机器的“操作按钮”

在单片机中,寄存器就是一段特殊的RAM存储器。

一方面,寄存器可以存储和读取数据;另一方面,每一个寄存器背后都连接了一根导线,控制着电路的连接方式(单片机通过配置寄存器来控制内部线路的连接,从而使不同电路完成不同的功能)。

可位寻址&不可位寻址:

①可位寻址寄存器可以整体赋值,也可以单独对某一位赋值;②不可位寻址寄存器只能整体赋值

  • 定时器/计数器控制寄存器TCON(timer control)

 SFR name  Address  bit       B7      B6      B5       B4     B3     B2     B1     B0

  TCON          88H    name    TF1    TR1    TF0    TR0    IE1    ITI     IE0    IT0  

 
TF1:

定时器/计数器T1溢出标志。T1被允许计数以后,从初值开始加1计数。当最高位产生溢
出时由硬件置“1”TF1,向CPU请求中断,一直保持到CPU响应中断时,才由硬件清“0”TF1  (TFI也可由程序查询清“0”)。

TR1:

定时器T1的运行控制位。该位由软件置位和清零。当GATE(TMOD7)=0,TR1=1时就
允许T1开始计数,TR1=0时禁止T1计数。当GATE(TMOD.7)=1,TR1=1且INTT输入高电平时,才允许T1计数。

TF0:

定时器/计数器T0溢出中断标志。T0被允许计数以后,从初值开始加1计数,当最高位产
生溢出时,由硬件置“1”TF0,向CPU请求中断,一直保持CPU响应该中断时,才由硬件清“0”TF0(TF0也可由程序查询清“0”)。这一位一般只读不写。

TR0:

定时器T0的运行控制位。该位由软件置位和清零。当GATE(TMOD.3)=0,TR0=1时就
允许T0开始计数,TR0=0时禁止T0计数。当GATE(TMOD.3)=1,TR0=0且INT0输入高电平时,才允许T0计数。

IE1:

外部中断1请求源(INTI/P3.3)标志。IE1=1,外部中断向CPU请求中断,当CPU响应该
中断时由硬件清“0”IE1。

IT1:

外部中断1触发方式控制位。IT1=0时,外部中断1为低电平触发方式,当INTI(P3.3)
输入低电平时,置位IE1,采用低电平触发方式时,外部中断源(输入到INTI)必须保持低电平有效,直到该中断被CPU响应,同时在该中断服务程序执行完之前,外部中断源必须被清除(P3.3要变高),否则将产生另一次中断。当IT1=1时,则外部中断1(INTI)端口由“1”→“0”下降沿跳变,激活中断请求标志位IE1,向主机请求中断处理。
 

  • 定时器/计数器工作模式寄存器TMOD(timer mode)

 

M0,M1:

控制定时器/计数器的工作模式

TMOD.1/TMOD.0 M1、M0    定时器/计数器0模式选择    
                               0      0     13位定时器/计数器,兼容8048定时模式,TL1只用低5位参与    
                                               分频,TH1整个8位全用。
                               0      1     16位定时器/计数器,TL1、TH1全用    
                               1      0      8位自动重装载定时器,当溢出时将TH1存放的值自动重装入TL1    
                               1      1      定时器0此时作为双8位定时器/计数器。  
                                             

  • C/T~置0:定时器;C/T~置1:计数器

  • GATE(门控端):

启动or暂停可以由TR0单独控制,也可以由TR0和外部中断联合控制,使用哪种控制方式由GATE的置位控制。

当GATE置0,TR0为1时,定时器启动

当GATE置1,只有外部引脚(NT0:外部中断引脚)为高且TR0为1时,定时器启动

  • 中断允许寄存器IE和XICON

EA(interrupt all):

CPU的总中断允许控制位,EA=1,CPU开放中断,EA=0,CPU屏蔽所有的中断申请。
EA的作用是使中断允许形成两级控制。即各中断源首先受EA控制;其次还受各中断源自己的中断允许控制位控制。

ET2:

定时/计数器T2的溢出中断允许位。ET2=1,允许T2中断:ET2=0,禁止T2中断。    

ES:

串行口1中断允许位。ES=1,允许串行口1中断;ES=0,禁止串行口1中断。

ET1:

定时/计数器T1的溢出中断允许位。ET1=1,允许T1中断;ET1=0,禁止T1中断。    

EX1:

外部中断1中断允许位。EX1=1,允许外部中断1中断;EX1=0,禁止外部中断1中断。  

ET0:

T0的溢出中断允许位。ET0=1,允许T0中断:ET0=0禁止T0中断    

EX0:

外部中断0中断允许位。EX0=1,允许中断;EX0=0禁止中断。

  • 中断优先级控制寄存器高(不可位寻址)

① PX3H,PX3:外部中断3优先级控制位

当PX3H=0且PX3=0时,外部中断3为最低优先级中断(优先级0)

当PX3H=0且PX3=1时,外部中断3为较低优先级中断(优先级1)

当PX3H=1且PX3=0时,外部中断3为较高优先级中断(优先级2)

当PX3H=1且PX3=1时,外部中断3为最高优先级中断(优先级3)

②PX2H,PX2:外部中断2优先级控制位

当PX2H=0且PX2=0时,外部中断2为最低优先级中断(优先级0)

当PX2H=0且PX2=1时,外部中断2为较低优先级中断(优先级1)

当PX2H=1且PX2=0时,外部中断2为较高优先级中断(优先级2)

当PX2H=1且PX2=1时,外部中断2为最高优先级中断(优先级3)

  • 中断优先级控制寄存器低(可位寻址)

①PT1:定时器1中断优先级控制位

PT1=1,定时器1中断为最高优先级

PT1=0,定时器1中断为最低优先级

PX1:外部中断1优先级控制位

PX1=1,外部中断1为最高优先级

PX1=0,外部中断1为最低优先级

②PT0:定时器1中断优先级控制位

PT0=1,定时器0中断为最高优先级

PT0=0,定时器0中断为最低优先级

③PX0:外部中断0优先级控制位

PX0=1,外部中断0为最高优先级

PX0=0,外部中断0为最低优先级

实例演示

按键控制LED流水灯模式

效果:在LED模块呈现以500ms(由计时器控制)为周期的流水灯;每次按下按键K1,流水灯反向

//Timer0.c文件:
#include <REGX52.H>
#include "Timer0.h"
/**
  * @brief 定时器初始化,周期为1ms 12MHz
  * @param  无
  * @retval 无
  */
void Timer0_Init()//1ms
{
	TMOD &= 0xF0;		//设置定时器模式
	TMOD |= 0x01;		//设置定时器模式
	TL0 = 0x18;		//设置定时初值		
	TH0 = 0xFC;		//设置定时初值	
	TF0 = 0;			//清除TF0标志
	TR0 = 1;			//定时器0开始计时
	ET0=1;//允许T0中断
	EA=1;//CPU开放中断
	PT0=0;//定时器0为最低优先级
}


//Timer0.c
#ifndef _TIMER0_H_
#define _TIMER0_H_

void Timer0_Init();
#endif

//Key.c
#include <REGX52.H>
#include "Delay.h"

/**
  * @brief 获取独立按键键码
  * @param  无
  * @retval 键码,范围是0~4  */
	
unsigned char Key()
{
	unsigned char KeyNumber=0;
	
	if(P3_1==0){Delay(20);while(P3_1==0);KeyNumber=1;}
	if(P3_0==0){Delay(20);while(P3_0==0);KeyNumber=2;}
	if(P3_2==0){Delay(20);while(P3_2==0);KeyNumber=3;}
	if(P3_3==0){Delay(20);while(P3_3==0);KeyNumber=4;}
	
	return KeyNumber;
}

//Key.h
#ifndef _KEY_H_
#define _KEY_H_

unsigned char Key();

#endif

//main.c
#include < REGX52.H>
#include "Time.h"
#include "Key.h"
#include <INTRINS.H>
unsigned char KeyNum,LEDMode;

void main()
{
	P2=0xFE;//点亮第一个LED
	Timer0_Init();
	while(1)
	{
		KeyNum=Key();
		if(KeyNum)
		{
			if(KeyNum==1)
			{
				LEDMode++;
				if(LEDMode>=2)LEDMode=0;
			}

		}
	}
}
void Time0_Routine() interrupt 1
{
	static unsigned int T0Count;//设置静态变量,在子函数结束后仍然占位
	TL0 = 0x18;		//设置定时初值	
	TH0 = 0xFC;		//设置定时初值	
	T0Count++;
	if(T0Count>=500)//数500ms
	{
		T0Count=0;
		if(LEDMode==0)
			P2=_crol_(P2,1);
		if(LEDMode==1)
			P2=_cror_(P2,1);
	}
	
}

注记:小tips~

①实现每隔一秒产生中断的思路::

    赋初值 0~65535,每隔1us计数加一,总共能记录的时间为65535us

   记满之后才溢出——赋初值为64535,离计算器起初还差10个数,所以计时时间为1ms

②采用“与或赋值法”就能达到单独操作某些位的目的!

   TMOD=TMOD&0xF0(TMOD&=0xF0):把TMOD低四位清零,高四位保持不变

   原因:1&任何数->任何数,0&任何数->0

   TMOD=TMOD|0x01(TMOD|=0x01):把TMOD最低位置1,高四位保持不变

   原因:0|任何数->任何数,1|任何数->1

③可以直接用STC—ISP里面“定时器计算器”的功能生成计时器代码!不过要记得配置相关参数(以下以定时1毫秒为例)

④实现流水灯新方法:

调用INTRINS函数库,使用“crol”(循环左移)和“cror”(循环右移)这两个函数

这两个函数分别都需要两个参数:数值,移动的位数

好处:溢出后能够重新从零开始不需要做越界判断

定时器时钟

//main.c(需要调用之前写过的模块)
#include <REGX52.H>
#include "Delay.h"
#include "LCD1602.h"
#include "Timer0.h"

unsigned int Sec=50;
unsigned int Min=59;
unsigned int Hour=23;   //实际应用中可以设置一个准确的时间~这里这样写只是基于验证进位的需要
void main()
{
	LCD_Init();
	Timer0_Init();
	while(1)
	{
		LCD_ShowString(1,1,"Clock:");
		LCD_ShowNum(2,1,Hour,2);
	    LCD_ShowString(2,3,":");
		LCD_ShowNum(2,4,Min,2);
		LCD_ShowString(2,6,":");
		LCD_ShowNum(2,7,Sec,2);
	}
}
void Timer0_Routine() interrupt 1
{
	static unsigned int T0Count;	
    TL0 = 0x18;		
	TH0 = 0xFC;			
    T0Count++;
	if(T0Count>=1000)	  {
      Sec++;
			T0Count=0;
				if(Sec>=60)
			{
				Min++;
				Sec=0;
					if(Min>=60)
					{
						Hour++;
						Min=0;
							if(Hour>=24)
							{
								Hour=0;
							}
					}
			}
	  }
}

串口通讯

串口介绍

  • 串口是一种应用十分广泛的通讯接口,串口成本低、容易使用、通信线路简单,可实现两个或多个设备的互相通信。
  • 单片机的串口可以使单片机与单片机、单片机与电脑、单片机与各式各样的模块互相通信,极大的扩展了单片机的应用范围,增强了。单片机系统的硬件实力。
  • 51单片机内部自带UART (Universal Asynchronous Receiver Transmitter,通用异步收发器),可实现单片机的串口通信。

硬件电路

  • 简单双向串口通信有两根通信线(发送端TXD和接收端RXD)
  • 发送端TXD和接收端RXD交叉连接(一个发送,另一个接收。其实很好理解,同一时间一个设备不能既发送又接收)
  • 当只需单向的数据传输时,可以只接一根通信线
  • 搭建串口通信前要了解电压、电平协议。如果协议不同可以则需要电平转化芯片(不然会烧坏)。
  • 单片机和电脑进行串口通讯时,需要用到串口助手。蓝牙通讯往往会有一个app充当“串口助手” 
  • 普遍来说,通信至少需要三根线(多了一根供电线)。如果设备一、设备二都有独立供电,那么不需要VCC;如果其中一个是模块,需要另一个设备供电,则需要VCC,使得具有独立供电能力的设备向模块供电

电平标准

  • 传输线缆中认为规定的电压与数据的对应关系,串口常用的电平标准有以下几种

①TTL电平:+5V表示1,0V表示0

②RS232电平:-3~-15V表示1,+3~+15V表示0

③RS485电平:两线压差+2~+6V表示1,-2~-6V表示0(差分信号)

  • 差异

RS232是反逻辑电平,数据传输最多十多米;

RS485最多能达到千米级别,这有赖于差分传输的高稳定性(USB就是)

接口及引脚定义

DSR,RTS属于数据流控制(流控)

可以用于调整发送和接受的速率,但是C51不支持

常见通信接口比较

  • UART TXD RXD:属于点对点通信 ,严格上的用法只能一对一
  • I方C:单片机与其他设备进行串口通信时,数据的写入读入都依赖I方C
  • CAN传输稳定性好,运用了差分传输 ,在汽车上有应用
  • 全双工:通信双方可以同一时刻互相传输数据
  • 半双工:通信双方可以互相传输数据,但必须分时复用一根数据线
  • 单工:只能一方发给另一方,单线传播,比如遥控器
  • 同步:对时间的要求很严格,通信双方靠一根时钟线来约定通信速率
  • 异步:通信双方各自约定通信速率,对时间要求那么严格,没有时钟线
  • 总线:连接各个设备数据的传输线路,可以说USB/SPI/I方C/......总线,但没有“串口总线”这一提法

51单片机的UART

  • STC89C52有1个UART
  • STC89C52的UART有四种模式,其中最常用的是模式1(八位UART,波特率可变)
  • P3口  两份线  I/O口与串口复用  通过寄存器来配置
  • 两个单片机一般可以直接进行串口通信
  • 串口是内部资源,不需要再额外写入

串口参数及时序图

  • 波特率:可以被理解为一个设备在单位时间内发送(或接收)了多少码元的数据。它是对符号传输速率的一种度量,表示单位时间内传输符号的个数。当串口通讯异步时,就需要约定一个 波特率
  • 比特率:与上类似,表示数据采样等的速率,保证数据同步
  • 检验位:用于数据验证。9位,多一位可以用来校验。奇偶校验最常用,但是排错率不高。如果双方都翻转了,就检测不到了。使用校验之前双方要约定好、配置好 
  • 停止位:用于数据帧间隔
  • 串口 串型 一位一位收发 每次一个字节

串口模式图

  • 都是单片机内部的东西。由TXD RXD连成的一整片都是总线
  • 靠定时器溢出率来控制数据采样的收发速率
  • 写入 读入:先把8位数据写入缓存 再控制发送 接受时也是一位一位移到寄存器中,需要数据时再读出 发的时候只要写入 就能自动发送。发完后会产生发送中断;读的时候 当外部有数据时自动写入寄存器,并且生接收中断。发送中断可以不用管 接受中断能够提醒我们有数据进来

串口和中断系统

  • 串口通讯可以概括为:收发过程+中断系统
  • 申请中断 去中断 发送完成or接受完成 后在同一个线路产生中断(通过R1还是I1区分两者)
  • 使用单片机进行串口通讯时,中断优先级不是必须配置的,当中断优先级较多时才需要

串口相关寄存器

  • 属于特殊功能寄存器
  • 每一位下面都有一根连线
  • 电源控制寄存器的前两位和串口有关
  • STC89C52有四个优先级,所以需要两个寄存器来控制

实例演示

实现效果:电脑通过发送数据控制LED,并且单片机会把数据返回给电脑

部分代码如下所示:

//main.c:
#include <REGX52.H>
#include "Delay.h"
#include "UART.h"
void main()
{
	UART_Init();//初始化
	
	while(1)
	{

	}
	
}

void UART_Routine() interrupt 4
{
	if(R1==1)
	{
	  P2=SBUF;
		UART_SendByte(SBUF);
		RI=0;//硬件只能置1,要用软件复位
}

//UART.c
void UART_Init()//2400bps@12.000MHz
{
	SCON=0x50;//ÔÊÐí½ÓÊÕ
	PCON |=0x80;
	TMOD &= 0x0F;
	TMOD |= 0x20;   	
    TL1 = 0xF3;				
    TH1 = 0xF3;				
    ET1 = 0;			  
	TR1 = 1;			
	EA=1;
	ES=1;
}

void UART_SendByte(unsigned char Byte)
{
	SBUF=Byte;
	while(TI==0);//检测
	TI=0;//复位
}

相关注记:

  • 配置16位计时器两个语句会占用时间,影响精度,所以在串口中使用双八位(0~255),自动存装
  • 波特率加倍的原因:后面会分频
  • 输入不需要配置中断。只要计时器有溢出,就能形成波特率
  • 如果误差较大,可以设置发送速度慢一些。波特率越低,发送越稳定。如果在发送时有问题,可以写个延迟
  • 同一个函数不能既在主函数里调用又在中断函数里调用,函数的重录可能导致错误
  • 串口助手:HEX模式/十六进制模式——以原始数据形式显示;文本模式——以编码模式显示

LED点阵屏

简介

  • LED点阵屏由若干个独立的LED组成,LED以矩阵的形式排列,以灯珠亮灭来显示文字、图片、视频等。
  • LED点阵屏广泛应用于各种公共场合,如汽车报站器、广告屏以及公告牌等
  • LED点阵屏分类:

按颜色:单色、双色、全彩
按像素:8*8、16*16等(大规模的LED点阵通常由很多个小点阵拼接而成) 一般是八的倍数,可以充分利用内存

显示原理

  • 结构类似于数码管,只不过是数码管把每一列的像素以"8"字型排列而已,所以操作方式可以相互借鉴~
  • LED点阵屏与数码管一样,有共阴和共阳两种接法(使用之前要了解清楚,特别是多色点阵屏)。不同的接法对应的电路结构不同,引脚是乱序排列的,使用之前要验证一下
  • LED点阵屏需要进行逐行或逐列扫描(由于I/O的有限),才能使所有LED同时显示

 

74HC595  

  • 74HC595是串行输入并行输出的移位寄存器,可用3根线输入串行数据,由八根线输出并行数据,多片级联后,可输出16位、24位、32位等,常用于IO口扩展。 
  • 上升沿移位&上升沿锁

上升沿移位用于“排队”默认是低电平,写入数据后记得清零

上升沿锁存用于“搬运”,默认高电平

  • 输入会有延时

C51的sfr、sbit

  • sfr (special function register)

特殊功能寄存器声明
例: sfr P0 =0x80;

声明PO口寄存器,物理地址为0x80

  • sbit(special bit)

特殊位声明
例: sbit P0_1=0x81;或sbit p0_1=P0^1;

声明PO寄存器的第1位
可位寻址/不可位寻址:在单片机系统中,操作任意寄存器或者某一位的数据时,必须给出其物理地址

又因为一个寄存器里有8位,所以位的数量是寄存器数量的8倍,单片机无法对所有位进行编码,故每8个寄存器中,只有一个是可以位寻址的。

对不可位寻址的寄存器,若要只操作其中一位而不影响其它位时,可用“&=”、“|=”、"^="(对某一位进行取反)的方法进行位操作

实例演示

效果:在LED点阵屏上显示弹幕“ZL!!!”

核心代码如下所示:

#include <REGX52.H>
#include "Delay.h"
#include "MATRIX.h"

unsigned char code Animation[]={0x81,0x83,0x85,0x89,0x91,0xA1,0xC1,0x81,
													 0x00,0x00,0x00,0xFF,0x01,0x01,0x01,0x01,
													 0x00,0x00,0x00,0xFD,0x00,0x00,0x00,0xFD,
													 0x00,0x00,0x00,0xFD,0x00,0x00,0x00,0x00,};//由字模提取插件生成 存在flash中
void main()
{
	  unsigned char offset=0;//偏移量
	  unsigned char i;
	  unsigned char count=0;//用于控制滚动的速度
	  MatrixLED_Init();//初始化
	  while(1)
	{
			for(i=0;i<8;i++)
			{
				MatrixLED_ShowColumn(i,Animation[i+offset]);
			}
		  count++;
			if(count>10)			{
				count=0;
				offset++;
				if(offset>24)//防止数组溢出				
                {
					offset=0;
				}
			}
		
	}
}

注记:

  • 和数码管扫描相类似,也要对LED点阵屏进行消影处理 。段选 位选 段选->段选 位选 延时 位清零
  • 将数组存储在flash里面(不能更改),不会占据RAM的空间

DS1302实时时钟

内部结构框图

引脚定义和应用电路

  • 一般都是用这个频率的晶振(石英),稳定性最高,精度最高
  • 任务:读取和写入,不需要配置寄存器 

寄存器定义

时序定义 

  • 在时钟的上升沿写入数据
  • 在时钟的下降沿读出数据

实例演示

时钟~显示年月日时分秒

//main.c
#include <REGX52.H>
#include "LCD1602.h"
#include "DS1302.h"

void main()
{
	LCD_Init();
	DS1302_Init();
	LCD_ShowString(1,1,"  -  -  ");
	LCD_ShowString(2,1,"  :  :  ");
	
	DS1302_SetTime();//设置时间
	
	while(1)
	{
		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);//日
		LCD_ShowNum(2,1,DS1302_Time[3],2);//时
		LCD_ShowNum(2,4,DS1302_Time[4],2);//分
		LCD_ShowNum(2,7,DS1302_Time[5],2);//秒
	}
}



//DS1302.C
#include <REGX52.H>


sbit DS1302_SCLK=P3^6;
sbit DS1302_IO=P3^4;
sbit DS1302_CE=P3^5;


#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[]={23,6,6,23,59,50,2};//年、月、日、时、分、秒、星期

/**
  * @brief  DS1302初始化
  * @param  无
  * @retval 无
  */
void DS1302_Init(void)
{
	DS1302_CE=0;
	DS1302_SCLK=0;
}

/**
  * @brief  DS1302写一个字节
  * @param  Command 命令字 地址
  * @param  Data 要写入的数据
  * @retval 无
  */
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;
}

/**
  * @brief  DS1302读一个字节
  * @param  Command 命令字 地址
  * @retval 读出的数据
  */
unsigned char DS1302_ReadByte(unsigned char Command)
{
	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;
	}
	for(i=0;i<8;i++)
	{
		DS1302_SCLK=1;
		DS1302_SCLK=0;
		if(DS1302_IO){Data|=(0x01<<i);}
	}
	DS1302_CE=0;
	DS1302_IO=0;	
	return Data;
}

/**
  * @brief  DS1302设置时间
  * @param  无
  * @retval 无
  */
void DS1302_SetTime(void)
{
	DS1302_WriteByte(DS1302_WP,0x00);
	DS1302_WriteByte(DS1302_YEAR,DS1302_Time[0]/10*16+DS1302_Time[0]%10);
	DS1302_WriteByte(DS1302_MONTH,DS1302_Time[1]/10*16+DS1302_Time[1]%10);
	DS1302_WriteByte(DS1302_DATE,DS1302_Time[2]/10*16+DS1302_Time[2]%10);
	DS1302_WriteByte(DS1302_HOUR,DS1302_Time[3]/10*16+DS1302_Time[3]%10);
	DS1302_WriteByte(DS1302_MINUTE,DS1302_Time[4]/10*16+DS1302_Time[4]%10);
	DS1302_WriteByte(DS1302_SECOND,DS1302_Time[5]/10*16+DS1302_Time[5]%10);
	DS1302_WriteByte(DS1302_DAY,DS1302_Time[6]/10*16+DS1302_Time[6]%10);
	DS1302_WriteByte(DS1302_WP,0x80);
}

/**
  * @brief  DS1302读取时间
  * @param  无
  * @retval 无
  */
void DS1302_ReadTime(void)
{
	unsigned char Temp;
	Temp=DS1302_ReadByte(DS1302_YEAR);
	DS1302_Time[0]=Temp/16*10+Temp%16;
	Temp=DS1302_ReadByte(DS1302_MONTH);
	DS1302_Time[1]=Temp/16*10+Temp%16;
	Temp=DS1302_ReadByte(DS1302_DATE);
	DS1302_Time[2]=Temp/16*10+Temp%16;
	Temp=DS1302_ReadByte(DS1302_HOUR);
	DS1302_Time[3]=Temp/16*10+Temp%16;
	Temp=DS1302_ReadByte(DS1302_MINUTE);
	DS1302_Time[4]=Temp/16*10+Temp%16;
	Temp=DS1302_ReadByte(DS1302_SECOND);
	DS1302_Time[5]=Temp/16*10+Temp%16;
	Temp=DS1302_ReadByte(DS1302_DAY);
	DS1302_Time[6]=Temp/16*10+Temp%16;
}

可调时钟

//main.c
#include <REGX52.H>
#include "LCD1602.h"
#include "DS1302.h"
#include "Matrixkey.h"
#include "Timer0.h"

unsigned char KeyNum,MODE,TimeSetSelect,TimeSetFlashFlag;

void TimeShow(void)
{
	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);
	LCD_ShowNum(2,1,DS1302_Time[3],2);
	LCD_ShowNum(2,4,DS1302_Time[4],2);
	LCD_ShowNum(2,7,DS1302_Time[5],2);
}


void TimeSet(void)
{
	if(KeyNum==2)
	{
		TimeSetSelect++;
		TimeSetSelect%=6;
	}
	if(KeyNum==3)
	{
		DS1302_Time[TimeSetSelect]++;
		if(DS1302_Time[0]>99){DS1302_Time[0]=0;}
		if(DS1302_Time[1]>12){DS1302_Time[1]=1;}
		if( DS1302_Time[1]==1 
            || DS1302_Time[1]==3 
            || DS1302_Time[1]==5 
            || DS1302_Time[1]==7    
            || DS1302_Time[1]==8 
            || DS1302_Time[1]==10     
            || DS1302_Time[1]==12)
		{
			if(DS1302_Time[2]>31){DS1302_Time[2]=1;}
		}
		    else if(DS1302_Time[1]==4 
                || DS1302_Time[1]==6 
                || DS1302_Time[1]==9 
                || DS1302_Time[1]==11)
		{
			if(DS1302_Time[2]>30){DS1302_Time[2]=1;}
		}
		    else if(DS1302_Time[1]==2)
		{
			if(DS1302_Time[0]%4==0)
			{
				if(DS1302_Time[2]>29){DS1302_Time[2]=1;}
			}
			else
			{
				if(DS1302_Time[2]>28){DS1302_Time[2]=1;}
			}
		}
		if(DS1302_Time[3]>23){DS1302_Time[3]=0;}
		if(DS1302_Time[4]>59){DS1302_Time[4]=0;}
		if(DS1302_Time[5]>59){DS1302_Time[5]=0;}
	}
	if(KeyNum==4)
	{
		DS1302_Time[TimeSetSelect]--;
		if(DS1302_Time[0]<0){DS1302_Time[0]=99;}
		if(DS1302_Time[1]<1){DS1302_Time[1]=12;}
		if( DS1302_Time[1]==1 
            || DS1302_Time[1]==3 
            || DS1302_Time[1]==5 
            || DS1302_Time[1]==7 
            || DS1302_Time[1]==8 
            || DS1302_Time[1]==10 
            || DS1302_Time[1]==12)
		{
			if(DS1302_Time[2]<1){DS1302_Time[2]=31;}
			if(DS1302_Time[2]>31){DS1302_Time[2]=1;}
		}
		else if(DS1302_Time[1]==4 
            || DS1302_Time[1]==6 
            || DS1302_Time[1]==9 
            || DS1302_Time[1]==11)
		{
			if(DS1302_Time[2]<1){DS1302_Time[2]=30;}
			if(DS1302_Time[2]>30){DS1302_Time[2]=1;}
		}
		else if(DS1302_Time[1]==2)
		{
			if(DS1302_Time[0]%4==0)
			{
				if(DS1302_Time[2]<1){DS1302_Time[2]=29;}
				if(DS1302_Time[2]>29){DS1302_Time[2]=1;}
			}
			else
			{
				if(DS1302_Time[2]<1){DS1302_Time[2]=28;}
				if(DS1302_Time[2]>28){DS1302_Time[2]=1;}
			}
		}
		if(DS1302_Time[3]<0){DS1302_Time[3]=23;}
		if(DS1302_Time[4]<0){DS1302_Time[4]=59;}
		if(DS1302_Time[5]<0){DS1302_Time[5]=59;}
	}
	if(TimeSetSelect==0 && TimeSetFlashFlag==1){LCD_ShowString(1,1,"  ");}
	else {LCD_ShowNum(1,1,DS1302_Time[0],2);}
	if(TimeSetSelect==1 && TimeSetFlashFlag==1){LCD_ShowString(1,4,"  ");}
	else {LCD_ShowNum(1,4,DS1302_Time[1],2);}
	if(TimeSetSelect==2 && TimeSetFlashFlag==1){LCD_ShowString(1,7,"  ");}
	else {LCD_ShowNum(1,7,DS1302_Time[2],2);}
	if(TimeSetSelect==3 && TimeSetFlashFlag==1){LCD_ShowString(2,1,"  ");}
	else {LCD_ShowNum(2,1,DS1302_Time[3],2);}
	if(TimeSetSelect==4 && TimeSetFlashFlag==1){LCD_ShowString(2,4,"  ");}
	else {LCD_ShowNum(2,4,DS1302_Time[4],2);}
	if(TimeSetSelect==5 && TimeSetFlashFlag==1){LCD_ShowString(2,7,"  ");}
	else {LCD_ShowNum(2,7,DS1302_Time[5],2);}
}

void main()
{
	LCD_Init();
	DS1302_Init();
	Timer0_Init();
	LCD_ShowString(1,1,"  -  -  ");
	LCD_ShowString(2,1,"  :  :  ");
	
	DS1302_SetTime();
	
	while(1)
	{
		KeyNum=Matrixkey();
		if(KeyNum==1)
		{
			if(MODE==0){MODE=1;TimeSetSelect=0;}
			else if(MODE==1){MODE=0;DS1302_SetTime();}
		}
		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;
		TimeSetFlashFlag=!TimeSetFlashFlag;
	}
}

  • 17
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值