【51单片机学习】定时器、串口、LED点阵屏、DS1302实时时钟、蜂鸣器

发布于:2025-08-15 ⋅ 阅读:(14) ⋅ 点赞:(0)

一、定时器

1.定时器介绍

2.STC89C52定时器资源

3.定时器框图

4.定时器工作模式

定时器分为三部分:时钟、计数系统、中断

5.定时器时钟

单片机的时钟可以由系统时钟来提供,也可以由外部引脚来提供,当由外部引脚来提供时钟的时候,定时器就是一个计数器,外部引脚每来一个脉冲就会加一,相当于计脉冲的一个计数器。
该开发板上使用的为12MHz的晶振,即系统时钟为12MHz,将12MHz的脉冲进行12分频(此处默认为12T模式),分频之后变为1MHz,1个周期=1us,此时计数单元每隔1us就要计数一次,当其计到最大值时会产生中断。
开关处,Counter计数器,Timer计时器,C/T=0时是定时方式,C/T=1是计数方式。

6.中断系统

中断系统主要是用来提醒主程序。高优先级的中断可以打断低优先级的中断。

7.中断程序流程

8.STC89C52中断资源

传统的8051单片机只有5个中断源(外部中断0、定时器0中断、外部中断1、定时器1中断、串口中断)、2个中断优先级。

9.定时器和中断系统

单独的定时器也可以工作,只不过没有中断主程序就不能响应了。

10.定时器相关寄存器

单片机通过配置寄存器来控制内部线路的连接,通过内部线路的不同连接方式来实现不同电路完成不同功能。

GATE是门控端,不常用。TF一般只读不写。

11.代码示例

不可位寻址的寄存器只能整体赋值,可位寻址的寄存器可以对其每一位单独赋值。
TF是中断溢出标志位,将其置零是为了防止其刚配置好就产生中断。
TR负责控制定时器是否开启。
IE和IT是控制外部中断引脚,GATE=0时,这一部分可以不用配置。

PT0通常默认为0

上电时需要调用该函数进行初始化。
上述方式进行TMOD赋值时,若只使用一个定时器则无影响,使用两个定时器时会产生冲突。所以采用“与或式赋值法”。

TH0和TL0计算的值比生成的值小1us。

注意需要在中断函数中赋初值。
static是静态变量,如果不加 static 则局部变量在函数结束之后会销毁,下次再调用局部变量时就不是上次的局部变量了,为了保证函数结束后静态变量的值仍保留,需要设置为静态变量。
定时器一般不容易模块化,常写在主函数中。

这个头文件中包含许多函数,此处要用到的是循环左移和循环右移

(1)按键控制LED流水灯模式

#include <REGX52.H>
#include "Timer0.h"
#include "Key.h"
#include <INTRINS.H>

unsigned char KeyNum,LEDMode;

void main()
{
	P2=0xFE;
	Timer0Init();
	while(1)
	{
		KeyNum=Key();		//获取独立按键键码
		if(KeyNum)			//如果按键按下
		{
			if(KeyNum==1)	//如果K1按键按下
			{
				LEDMode++;	//模式切换
				if(LEDMode>=2)LEDMode=0;
			}
		}
	}
}

void Timer0_Routine() interrupt 1
{
	static unsigned int T0Count;
	TL0 = 0x18;		//设置定时初值
	TH0 = 0xFC;		//设置定时初值
	T0Count++;		//T0Count计次,对中断频率进行分频
	if(T0Count>=500)//分频500次,500ms
	{
		T0Count=0;
		if(LEDMode==0)			//模式判断
			P2=_crol_(P2,1);	//LED输出
		if(LEDMode==1)
			P2=_cror_(P2,1);
	}
}

(2)定时器时钟

注意:中断函数中不能执行过长的任务。

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

unsigned char Sec=55,Min=59,Hour=23;

void main()
{
	LCD_Init();
	Timer0Init();
	
	LCD_ShowString(1,1,"Clock:");	//上电显示静态字符串
	LCD_ShowString(2,1,"  :  :");
	
	while(1)
	{
		LCD_ShowNum(2,1,Hour,2);	//显示时分秒
		LCD_ShowNum(2,4,Min,2);
		LCD_ShowNum(2,7,Sec,2);
	}
}

void Timer0_Routine() interrupt 1
{
	static unsigned int T0Count;
	TL0 = 0x18;		//设置定时初值
	TH0 = 0xFC;		//设置定时初值
	T0Count++;
	if(T0Count>=1000)	//定时器分频,1s
	{
		T0Count=0;
		Sec++;			//1秒到,Sec自增
		if(Sec>=60)
		{
			Sec=0;		//60秒到,Sec清0,Min自增
			Min++;
			if(Min>=60)
			{
				Min=0;	//60分钟到,Min清0,Hour自增
				Hour++;
				if(Hour>=24)
				{
					Hour=0;	//24小时到,Hour清0
				}
			}
		}
	}
}

二、串口通信

1.串口介绍

2.硬件电路

TXD:Tansmit Exchange Data   发送端
RXD:Receive Exchange Data  接收端
VCC可以不接,GND一定要连接。当二者都有独立电源进行供电时,VCC可以不接。

3.电平标准

TTL:Transistor Transistor Logic
单片机中使用的就是TTL电平。
TTL电平和RS232电平的数据传输距离有限,仅十几米时3错误率就会很高,RS485电平数据传输的距离在一千米以上。
RS232电平一般用于电脑等高电压之间的传输,所容忍的电压变化范围比较大,稳定性比TTL电平更好。但是在弟弟淡雅单片机系统中使用TTL电平足矣。

 4.接口及引脚定义

VGA接口与串口外观类似,但是它有3排15个针,主要用于传输视频,电脑和投影仪可以连接VGA接口、电脑和主机显示屏也可以连接VGA接口。
下图所示是标准的9针接口的串口,串口只能用来传送数据。

5.常见通信接口比较

6.相关术语

同步的通常有时钟线SCL,异步则没有。

7.51单片机的UART

8.串口参数及时序图

因为串口通信时异步通信,没有时钟线来确定什么时候采样,所以通信双方要约定一个通信率。
比特率与波特率不同,比特率描述的是传输多少个位,波特率描述的是传输多少个数据。
奇偶校验是一种比较简单、比较常用的一种校验方法,但是排错率不高。
串口是串行通信,数据一位一位的发送。

9.串口模式图

图示的所有电路结构都在MCU中。串口通过定时器1的溢出率来约定速率,控制收发器的采样时间。
SBUF在等号左边,就是把数据写入SBUF,SBUF在等号右边,就是将SBUF中的数据读出。
TI:发送中断。
RI:接收中断。

10.串口和中断系统

TI和RI占用同一个中断通道,即发送完成和接收完成之后都会进入同一个中断函数,在中断中判断是TI还是RI。

11.串口相关寄存器

需要注意的是,电源控制寄存器的前两位是和串口相关的。
使用串口是主要配置SCON和PCON两个寄存器即可,直接在SBUF中写入数据。
若需要配置中断,则还需要打开EA和ES。
REN=1时允许接收,REN=0时禁止接收。只做单工即数据单向传输时,不需要启动接收使能(REN)。
TI和RI需要重点关注。发送完成后TI会通过硬件置1,需要软件进行复位。RI同理。

12.数据显示模式

HEX模式是底层传输的实际数据,文本模式是将原始数据按照ASCII码表进行编码后的字符。


原始数据只能发送0~255这些数字。

发送字符时也可以用引号引起来。

13.代码示例

(1)串口向电脑发送数据

串口使用定时器1,8位自动重装载模式。
定时器/计数器T1在模式2时一般作为串行通讯时波特率发生器
【补】16位自动重装载,用两个8位的计数器当做一个大的16为计数器,计数范围是0~65535,缺点是每次进入中断时都需要给计数器赋初值,如果不赋初值就会从0开始计数,而且赋初值的语句会占用一定的时间,精度不高。而串口中的传输速率较快,需要精准的时间,使用双8位,8位自动重装载定时器,当溢出时将TH1存放的值自动重装入TL1。
就是16位记的数多,但每次都需要自己写的代码赋初值,浪费时间。双8位就是将16位分开,一个计数,另一个存放初值,每次计数完成后AR会自动将值赋给CNT,不用代码处理,比较快,但只有8位所以记的数少了。

当系统频率选择12MHz时,若波特率太高会产生很大的误差,所以设置为4800,波特率倍速。
波特率加倍是为了分频,如果不加倍时钟会变慢,变慢后就不能匹配了,会形成比较大的误差。
REN=0,SCON=0x40时,接收不使能,REN=1,SCON=0x50时,接收使能。

注意在高系列的单片机中才有AUXR,此处使用的单片机没有AUXR,需要删除相关代码。

上电时需要进行初始化。

当数据接收有误差时,大概率是波特率的问题,可以适当增加延时。波特率越低,通信越稳定,误差影响越小。

main.c文件
//每个1秒发送一个递增的数

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

unsigned char Sec;

void main()
{
	UART_Init();			//串口初始化
	while(1)
	{
		UART_SendByte(Sec);	//串口发送一个字节
		Sec++;				//Sec自增
		Delay(1000);		//延时1秒
	}
}
UART.c文件
#include <REGX52.H>

/**
  * @brief  串口初始化,4800bps@12.000MHz
  * @param  无
  * @retval 无
  */
void UART_Init()
{
	SCON=0x40;
	PCON |= 0x80;		//最高位置1,波特率加倍
	TMOD &= 0x0F;		//设置定时器模式
	TMOD |= 0x20;		//设置定时器模式
	TL1 = 0xF3;		//设定定时初值
	TH1 = 0xF3;		//设定定时器重装值
	ET1 = 0;		//禁止定时器1中断
	TR1 = 1;		//启动定时器1
}

/**
  * @brief  串口发送一个字节数据
  * @param  Byte 要发送的一个字节数据
  * @retval 无
  */
void UART_SendByte(unsigned char Byte)
{
	SBUF=Byte;
	while(TI==0);		//检测是否完成,TI=0时一直循环,直到TI=1时跳出循环
	TI=0;				//手动复位
}
UART.h文件
#ifndef __UART_H__
#define __UART_H__

void UART_Init();
void UART_SendByte(unsigned char Byte);

#endif
Delay.c文件
void Delay(unsigned int xms)
{
	unsigned char i, j;
	while(xms--)
	{
		i = 2;
		j = 239;
		do
		{
			while (--j);
		} while (--i);
	}
}

Delay.h文件
#ifndef __DELAY_H__
#define __DELAY_H__

void Delay(unsigned int xms);

#endif

(2)电脑通过串口控制LED

之前串口发送数据并没有开启中断,只是发完数据后检测TI位是否变为1,然后再置0。
串口接收数据需要一个中断系统是因为,不知道电脑什么时候发送数据,不能一直进行检测,所以利用中断,在电脑发送数据时触发中断,在中断函数中进行数据处理,将数据取出。

【理解】因为发送是你决定什么时候发送,是主动决定的;而接收是被动的,你总不能一直要求单片机时时刻刻准备接收吧。所以把接收当作一种中断请求,它来了我就处理它,没来我就继续干我自己的活。

此时需要将REN接收使能位置1,SCON=0x50。
由于收发数据都会产生中断,所以需要进一步判断。

 main.c文件
#include <REGX52.H>
#include "Delay.h"
#include "UART.h"

void main()
{
	UART_Init();		//串口初始化
	while(1)
	{
		
	}
}

void UART_Routine() interrupt 4		//中断服务函数
{
	if(RI==1)					//如果接收标志位为1,接收到了数据
								//接收的同时也在发送,防止发送时也进入中断,所以需要进一步判断
	{
		P2=~SBUF;				//读取数据,取反后输出到LED
		UART_SendByte(SBUF);	//将受到的数据发回串口
		RI=0;					//接收标志位清0	//软件进行复位
	}
}
UART.c文件
#include <REGX52.H>

/**
  * @brief  串口初始化,4800bps@12.000MHz
  * @param  无
  * @retval 无
  */
void UART_Init()
{
	SCON=0x50;			//REN=1,接收使能位置1
	PCON |= 0x80;
	TMOD &= 0x0F;		//设置定时器模式
	TMOD |= 0x20;		//设置定时器模式
	TL1 = 0xF3;		//设定定时初值
	TH1 = 0xF3;		//设定定时器重装值
	ET1 = 0;		//禁止定时器1中断
	TR1 = 1;		//启动定时器1
	EA=1;			//打开总中断
	ES=1;			//打开串口中断
}

/**
  * @brief  串口发送一个字节数据
  * @param  Byte 要发送的一个字节数据
  * @retval 无
  */
void UART_SendByte(unsigned char Byte)
{
	SBUF=Byte;
	while(TI==0);
	TI=0;
}

/*串口中断函数模板						//使用时需要移动到主函数中
void UART_Routine() interrupt 4		//中断服务函数
{
	if(RI==1)   //接收的同时也在发送,防止发送时也进入中断,所以需要进一步判断
	{
		
		RI=0;	//软件进行复位
	}
}
*/
UART.h文件
#ifndef __UART_H__
#define __UART_H__

void UART_Init();
void UART_SendByte(unsigned char Byte);

#endif

Delay.c文件和Delay.h文件同上。

【总结】串口使用过程

初始化→发送→接收(进入中断)

波特率 = 系统时钟频率/(12 * (256 - TH1))

三、LED点阵屏

1.LED点阵屏介绍

2.显示原理

LED点阵屏也有共阴极和共阳极两种接法,如果是单色点阵,共阴和共阳的区别不大,二对于多色点阵,共阴和共阳就有明显区别。

3.74HC595

即使开发板上的64个灯按照行列的扫描方式连接,仍需要16个IO口,由于开发板的IO口很紧张,所以需要对IO口进行扩展。
(不同点阵的排列不同,需要查看数据手册进行对应)
下图中,左侧是移位寄存器,右侧是输出缓存。
SER是串行数据。
上升沿移位的默认状态应该是低电平,但是单片机IO口上电时为高电平,需要手动设置对其进行初始化。
上升沿RCLK为高电平时,左侧的8位数据会直接发送给右侧。
QH'用于级联。 扩展后,输出IO口的速度会被减慢。
先写入高位,再写入低位。

4.开发板引脚对应关系

单片机的IO口输出是弱上拉类型的,弱上拉的特性是输出低电平可以接受很大的电流,输出高电平的电流比较小。低电平强,高电平弱。可以增加一个三极管增强驱动能力,此时IO口不再进行直接驱动,而是作为一个控制信号,驱动VCC是否给LED施加。

5.C51的sfr、sbit

TCON是可位寻址,TMOD是不可位寻址。
不能直接给1和0的使用“&=”,一般用于对某一位进行清零。“|=”一般用于对某一位数据与1相或。“^=”一般用于把某一位数据与1进行异或,不一样则置1,一样则置0,常用于取反。

6.代码示例

(1)LED点阵屏显示图形

要取第八位的数,那么如果Byte也是0x80,& 0x80的话,相当于1&1=1,那么Byte就可以取出第八位,其他为0。
为了保证位对齐,一般对位赋值时,直接给1或0,而对整个寄存器赋值时,一般给0x什么什么。图中所示代码就没有进行位对齐,因为等号后面是个数据,对于位进行赋值时,如果后面给的是数据,那么就相当于给1,“非零即一”,也可以用if语句写。

LED点阵屏同数码管一样,也需要进行消影。

main.c文件
#include <REGX52.H>
#include "Delay.h"

sbit RCK=P3^5;		//RCLK
sbit SCK=P3^6;		//SRCLK
sbit SER=P3^4;		//SER

#define MATRIX_LED_PORT		P0

/**
  * @brief  74HC595写入一个字节
  * @param  Byte 要写入的字节
  * @retval 无
  */
  void _74HC595_WriteByte(unsigned char Byte)	//函数名不能以数字开头
{
	unsigned char i;
	for(i=0;i<8;i++)
	{
		SER=Byte&(0x80>>i);	//0x80,0x40,0x20......
		SCK=1;				//设置为高电平,上升沿
		SCK=0;				//设置为低电平,为下一次移位做准备
	}
	RCK=1;
	RCK=0;
}

/**
  * @brief  LED点阵屏显示一列数据
  * @param  Column 要选择的列,范围:0~7,0在最左边
  * @param  Data 选择列显示的数据,高位在上,1为亮,0为灭
  * @retval 无
  */
void MatrixLED_ShowColumn(unsigned char Column,Data)
{
	_74HC595_WriteByte(Data);
	MATRIX_LED_PORT=~(0x80>>Column);	//0选中,1不选中
	Delay(1);							//延时
	MATRIX_LED_PORT=0xFF;				//位清零
}

void main()
{
	SCK=0;	//初始化后单片机所有的IO口均为1,需要手动设置进行初始化
	RCK=0;
	while(1)
	{
		MatrixLED_ShowColumn(0,0x3C);
		MatrixLED_ShowColumn(1,0x42);
		MatrixLED_ShowColumn(2,0xA9);
		MatrixLED_ShowColumn(3,0x85);
		MatrixLED_ShowColumn(4,0x85);
		MatrixLED_ShowColumn(5,0xA9);
		MatrixLED_ShowColumn(6,0x42);
		MatrixLED_ShowColumn(7,0x3C);
	}
}

Delay.c文件和Delay.h文件同上。

(2)LED点阵屏显示动画

单片机中有两种存储数据的,一种是程序运行时的暂存器RAM,另一种是放在Flash里的程序存储器。Flash中的存储空间更大。
动画数组中的数据一般不需要改动,放在RAM中可能会很占用内存,所以一般放在Flash中,在数组前加入关键字 code 即可。缺点是数组中的数据不能进行更改。

main.c文件
#include <REGX52.H>
#include "Delay.h"
#include "MatrixLED.h"

//动画数据
unsigned char code Animation[]={
	0x3C,0x42,0xA9,0x85,0x85,0xA9,0x42,0x3C,
	0x3C,0x42,0xA1,0x85,0x85,0xA1,0x42,0x3C,
	0x3C,0x42,0xA5,0x89,0x89,0xA5,0x42,0x3C,
};

void main()
{
	unsigned char i,Offset=0,Count=0;	//Offset偏移量
	MatrixLED_Init();
	while(1)
	{
		for(i=0;i<8;i++)	//循环8次,显示8列数据
		{
			MatrixLED_ShowColumn(i,Animation[i+Offset]);
		}
		Count++;			//计次延时
		if(Count>15)		//每一帧扫描15次
		{
			Count=0;
			Offset+=8;		//偏移+8,切换下一帧画面
			if(Offset>16)	//防止数组溢出
			{
				Offset=0;
			}
		}
	}
}
MatrixLED.c文件
#include <REGX52.H>
#include "Delay.h"

sbit RCK=P3^5;		//RCLK
sbit SCK=P3^6;		//SRCLK
sbit SER=P3^4;		//SER

#define MATRIX_LED_PORT		P0

/**
  * @brief  74HC595写入一个字节
  * @param  Byte 要写入的字节
  * @retval 无
  */
void _74HC595_WriteByte(unsigned char Byte)
{
	unsigned char i;
	for(i=0;i<8;i++)
	{
		SER=Byte&(0x80>>i);
		SCK=1;
		SCK=0;
	}
	RCK=1;
	RCK=0;
}

/**
  * @brief  点阵屏初始化
  * @param  无
  * @retval 无
  */
void MatrixLED_Init()
{
	SCK=0;
	RCK=0;
}

/**
  * @brief  LED点阵屏显示一列数据
  * @param  Column 要选择的列,范围:0~7,0在最左边
  * @param  Data 选择列显示的数据,高位在上,1为亮,0为灭
  * @retval 无
  */
void MatrixLED_ShowColumn(unsigned char Column,Data)
{
	_74HC595_WriteByte(Data);
	MATRIX_LED_PORT=~(0x80>>Column);
	Delay(1);
	MATRIX_LED_PORT=0xFF;
}
MatrixLED.h
#ifndef __MATRIX_LED_H__
#define __MATRIX_LED_H__

void MatrixLED_Init();
void MatrixLED_ShowColumn(unsigned char Column,Data);


#endif

Delay.c文件和Delay.h文件同上。

四、DS1302实时时钟

1.DS1302介绍

单片机定时器计时存在几个缺点:

  1. 精度不高
  2. 利用定时器计时会占用CPU的时间
  3. 单片机定时器的时钟不能掉电继续运行

对于DS1302这个实时时钟芯片,会带有一个备用电池,如果掉电的话,芯片内部的逻辑判断会自动把电源切换到备用电池,即使单片机不工作,备用电池也会给时钟芯片提供电能保证其能够持续计时。

其他的时钟芯片还有:
DS3231,特点是精度很高,集成度更高,价格更贵。
DS12C887,特点是该芯片内部自带电池,不需要外接。

当我们拿到一个芯片或者根据需求找到一款芯片时
第一步是要了解它的功能,将芯片的功能和需求进行对应;
第二步是找到芯片的数据

2.引脚定义和应用电路

DIP——直插封装;SO——贴片封装。
右图是应用电路。
VCC2是主电源,与单片机电源的正极连在一起。VCC1连接备用电池,备用电池的正极连接VCC1,负极连接GND。由于该芯片具有涓细电流充电能力,所以在连接VCC时,会对备用电池进行充电,一旦VCC断开,会继续利用备用电池给芯片进行供电,保证了芯片的持续工作。
一般情况下不需要配置涓细电流充电能力,因为芯片在掉电的情况下使用备用电池的功耗是很低的。
根据原理图可以看到,开发板上的时钟芯片是没有接备用电池的,所以在该开发板上单片机掉电之后时钟芯片会停止工作。

X1和X2之间接一个固定频率的晶振,晶振的作用是为了给实时时钟系统提供一个稳定的计数脉冲,晶振经过内部电路的处理,会输出一个1Hz的标准的频率,而且频率的精度很高。
一般来说,晶振和时钟稳定的脉冲有关,而且晶振的振荡器稳定性特别高,产生的时钟频率精度很高,它的全称是石英晶体振荡器。
左侧的三个引脚是通信引脚,利用这三个引脚,通过专用的通信协议,单片机可以把芯片内部的时钟读取出来,并将设置的时间进行写入。这三个引脚的操作方式和74HC595移位寄存器十分类似,也是上升沿进行移位。

3.内部结构框图

芯片内部的时间都存储在实时时钟的寄存器中,寄存器的大小为31×8 RAM,这里的RAM与单片机中的RAM相同,例如定义一个变量 unsigned char i, 就相当于在单片机内部的寄存器中,开辟了一个地址的空间,这个空间的名字叫 i 。
CE引脚负责芯片使能(即使不使能时钟也是工作的),主要是用来判断的,IO口和SCLK相当于输入移位寄存器,将数据进行串行传输交互,CE为高电平时,I/O和SCLK才有效。

4.寄存器定义

下图所示的是与时钟有关的寄存器,芯片内部有很多的变量,变量的地址和内容都是定义好的。
这些寄存器都有相应的地址,每个地址下有一个数据,数据是一个字节一个字节的存储的,一个字节有8位,地址0存储的是秒寄存器,Day寄存器代表是星期。
WP是写保护,置1时,写入操作无效,但是可以读出。最后一个寄存器是存储涓细电流充电的寄存器,在该开发板上不需要配置。

表2是地址命令字,芯片需要对寄存器进行读写,需要完成几个任务:在哪写入什么,在哪读出(什么)。其中“在哪”体现在地址上,“写入”还是“读出”,地址命令字主要负责在哪写入、在哪读出。
命令字是一个字节,有8位,最高位7固定为1,第6位操作RAM给1,操作CK给0,第5位到地1位是地址位,A4~A0,(81h是1000 0001,而1000 0001中五到一位是00000,这个就是秒的地址),第0位用来确定是读还是写,1读0写。
CH是时钟停止,时钟暂停的意思,如果BIT7置一,则整个时钟停止计时。

5.时序定义

在整个写入和读取的过程中CE始终保持高电平,SCLK负责提供固定的时钟,I/O给数据。在SCLK的时钟上升沿,I/O的数据会被写入,在下降沿,DS1302会把数据进行输出,简单来说就是,上升沿单片机向时钟芯片写入数据,下降沿时钟芯片向单片机写入数据即读出时钟芯片的数据。这个协议和串口通信那里提到的SPI协议类似。
只有读出数据的D0~D7是时钟芯片控制的,其他的都是单片机进行控制的。
I/O扣罚送两个字节,第一个字节是命令字,第二个字节是数据。这里的移位寄存器先发送最低位

6.BCD码

芯片内部寄存器的数据不是以二进制进行存储的,而是以BCD码的形式进行存储的。
BCD码在操作的时候不方便,它将十位和个位分开了,如果想要进行判断,需要单独判断,但是BCD码的好处是,给数码管译码的时候很方便。

7.代码示例

(1)DS1302时钟

因为时钟需要通过LCD1602进行显示,所以需要提前加入LCD1602.c文件LCD1602.h文件
在对SCLK操作时,先置1后又直接置0,这里的时序操作需要考虑时钟芯片能够承受的时钟最快频率,如果时钟线操作太快,时钟芯片可能反应不过来。可以在数据手册中查询操作的时序时间。因为51单片机的操作速率比较慢,所以可以不加延时,如果单片机的速度很快,超过了限定的范围,就需要加入延时。

因为DS1302_IO是位变量,与位有关的是逻辑判断,非0即真。

根据时序图可知,单字节写入的函数有16个脉冲,而单字节读取的函数有15个脉冲。在单字节读取函数中,DS1302_SCLK先给0再给1。
先0后1每次循环后就会在凸处停下,先1后0每次循环后会在凹处停下。

对于单字节读取函数,单片机在传输万第一个字节后,需要把I/O的控制权转让给DS1302,先给下降沿再给上升沿后,时序到了如图所示的位置上。

局部变量刚定义之后的初始值不一定为0,需要赋值,全局变量定义之后的默认初始值为0。
一般情况下,与&用来清零,或|用来置一。
这里重复置一的目的是去掉一个脉冲。

如果声明外部数组和变量,前面必须加 extern ,加extern表示是变量声明,否则是全局变量的定义。

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

//时间数组,索引0~6分别为年、月、日、时、分、秒、星期
unsigned char DS1302_Time[]={19,11,16,12,59,55,6};

/**
  * @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);	//取出第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;	//将指令转换为读指令,将最低位置1
	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);}	//如果端口输出为1,就把Data的值读取出来了
	}
	DS1302_CE=0;
	DS1302_IO=0;	//读取后将IO设置为0,否则读出的数据会出错
	return Data;
}

/**
  * @brief  DS1302设置时间,调用之后,DS1302_Time数组的数字会被设置到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);//十进制转BCD码后写入
	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读取时间,调用之后,DS1302中的数据会被读取到DS1302_Time数组中
  * @param  无
  * @retval 无
  */
void DS1302_ReadTime(void)
{
	unsigned char Temp;					//暂时
	Temp=DS1302_ReadByte(DS1302_YEAR);
	DS1302_Time[0]=Temp/16*10+Temp%16;//BCD码转十进制后读取
	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;
}
DS1302.h文件
#ifndef __DS1302_H__
#define __DS1302_H__

//外部可调用时间数组,索引0~6分别为年、月、日、时、分、秒、星期
extern unsigned char DS1302_Time[];

void DS1302_Init(void);
void DS1302_WriteByte(unsigned char Command,Data);
unsigned char DS1302_ReadByte(unsigned char Command);
void DS1302_SetTime(void);
void DS1302_ReadTime(void);

#endif

(2)DS1302可调时钟

因为时钟需要通过按键进行调整,所以需要提前加入Key.c文件Key.h文件以及Timer0.c文件Timer0.h文件

越界清零的几种写法

无符号的数据从0再减1,会变成255,不存在负数。所以可以改为有符号数,范围是-128~127。

main.c文件
#include <REGX52.H>
#include "LCD1602.h"
#include "DS1302.h"
#include "Key.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)//按键2按下
	{
		TimeSetSelect++;//设置选择位加1
		TimeSetSelect%=6;//越界清零
	}
	if(KeyNum==3)//按键3按下
	{
		DS1302_Time[TimeSetSelect]++;//时间设置位数值加1
		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;}//闰年2月
			}
			else
			{
				if(DS1302_Time[2]>28){DS1302_Time[2]=1;}//平年2月
			}
		}
		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)//按键3按下
	{
		DS1302_Time[TimeSetSelect]--;//时间设置位数值减1
		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;}//闰年2月
				if(DS1302_Time[2]>29){DS1302_Time[2]=1;}
			}
			else
			{
				if(DS1302_Time[2]<1){DS1302_Time[2]=28;}//平年2月
				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;}//秒越界判断
	}
	//更新显示,根据TimeSetSelect和TimeSetFlashFlag判断可完成闪烁功能
	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();
	Timer0Init();
	LCD_ShowString(1,1,"  -  -  ");//静态字符初始化显示
	LCD_ShowString(2,1,"  :  :  ");
	
	DS1302_SetTime();//设置时间
	
	while(1)
	{
		KeyNum=Key();//读取键码
		if(KeyNum==1)//按键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)//每500ms进入一次
	{
		T0Count=0;
		TimeSetFlashFlag=!TimeSetFlashFlag;//闪烁标志位取反
	}
}

五、蜂鸣器

1.蜂鸣器介绍

有源蜂鸣器和无源蜂鸣器的外观相差不大,但是驱动方式有很大区别。
有源蜂鸣器的优点是驱动简单,缺点是频率固定。

2.驱动电路

最常见的驱动电路是三极管开关驱动电路,NPN型是高电平导通的三极管开关,PNP型是低电平导通的三极管开关。在基极需要接一个限流电阻,减小控制信号的电流,相当于弱化了控制信号的驱动能力,实际提供驱动能力的是VCC。
对于数字电路来说,限流电阻只需要保证三极管能够饱和即可,1kΩ和10kΩ都可。

因为该单片机的I/O口不能直接驱动蜂鸣器,需要经过一个芯片ULN2003。

3.ULN2003

达林顿晶体管也是一种晶体管开关,达林顿管也称复合管,可以增大单片机的驱动能力。
逻辑框图中的非门实际上是由达林顿管组成的,具有很强的驱动能力。

给0的时候,虽然输出为1,但是不具有驱动能力,相当于断开。给1时才驱动。
二极管是用来测试的,或者说是继电器的续流二极管,防止反向开合时产生高压脉冲。

ULN2003常用于步进电机的驱动,该单片机中也用来驱动步进电机,不过驱动步进电机只需要四路,多出来的三路可以选一路用来驱动蜂鸣器。

【注意】无源蜂鸣器必须给交流振荡才能发声,不能一直通电,无源蜂鸣器内部的线圈电阻很小,如果一直通电很容易造成蜂鸣器的烧毁。

4.键盘与音符对照

每个组的同一个音相差八度,没两个相邻的键相差半音,两个相邻的半音相差全音。
#是升音符号,b是降音符号。

下图中依次为全音符、二分音符、四分音符、八分音符、十六分音符、三十二分音符。
我们一般以四分音符作为时间基准,这个基准可以使100ms,也可以是200ms,根据节奏的快慢定。

附点表示该音符延长原来时长的1/2。

是延音符。

5.音符与频率对照

根据频率值控制定时器,产生相应频率的计时,有了频率就有了计时的频率,有了周期就可以控制中断,再控制I/O口的翻转,进而控制频率。

我们选择低音6作为基准频率

他们之间的12个键是以等比数列进行平分的,也称十二平均律。

利用定时器中断产生频率,定时器中断周期的时间是通过定时器TL0和TH0的重装值确定的。
单片机中不容易产生频率,更容易产生周期,所以应该先将频率计算为周期。
这里使用的单片机是12T的,机器周期是振荡周期的1/12,振荡周期就是晶振,因为晶振是12MHz,所以机器周期为1MHz,每经过一个机器周期,定时器的计数器加一,耗时1us。如果晶振不是12MHz,例如为11.0592MHz,则定时的时间不是1us,机器周期的频率=11.0592/12,11.0592MHz加一的时间=1/机器周期的频率。
I/O口翻转两次为一个周期。将定时器的周期设置为我们想要的声音周期的两倍。翻转频率是自身的二倍,原来一周期内翻转两次,现在半个周期翻转两次,所以是自身的二倍。除以2就是算出接通到断开的时间,为周期的一半,不同频率接通到断开的时间不同,振荡周期不同,频率也会不同。
相当于你要得到两个1ms的高电平,首先要把IO口从0置1,维持1ms,再把IO口从1置0,维持1ms,再置1,维持1ms再置0,维持1ms,相当于做了4次IO口反转,用了4ms才有两个1ms高电平。

TH0=重装载值的高八位,TL0=重装载值的低八位,再控制定时器中断,在中的中翻转I/O口即可产生对应的频率。

6.简谱


7.代码示例

(1)蜂鸣器播放提示音

因为需要根据独立按键输入,数码管显示1、2、3、4,所以需要提前加入Delay.c文件Delay.h文件Key.c文件Key.h文件Nixie.c文件Nixie.h文件

注意这里要将数码管Nixie.c文件中的后两句注释掉,因为这里是静态显示,不需要清零。

如果延时中出现_nop_,则需要添加头文件

for循环是让时间保持1s的,延时函数改变成0.5us,那要保持时长仍为1s,肯定是要乘以2的。

main.c文件
#include <REGX52.H>
#include "Delay.h"
#include "Key.h"
#include "Nixie.h"
#include "Buzzer.h"

unsigned char KeyNum;

void main()
{
	Nixie(1,0);			//为了保证数码管上电时显示0
	while(1)
	{
		KeyNum=Key();	//获取键码值
		if(KeyNum)
		{
			Buzzer_Time(100);
			Nixie(1,KeyNum);
		}
	}
}
Buzzer.c文件
#include <REGX52.H>
#include <INTRINS.H>

//蜂鸣器端口:
sbit Buzzer=P1^5;

/**
  * @brief  蜂鸣器私有延时函数,延时500us
  * @param  无
  * @retval 无
  */
void Buzzer_Delay500us()		//@12.000MHz
{
	unsigned char i;

	_nop_();					//延时1us
	i = 247;
	while (--i);
}

/**
  * @brief  蜂鸣器发声
  * @param  ms 发声的时长,范围:0~32767
  * @retval 无
  */
void Buzzer_Time(unsigned int ms)
{
	unsigned int i;
	for(i=0;i<ms*2;i++)			//for循环是让时间保持1s的,延时函数改变成0.5us,那要保持时长仍为1s,肯定是要乘以2的
	{
		Buzzer=!Buzzer;			//翻转
		Buzzer_Delay500us();	//延时,实现500us翻转一次
	}
}
Buzzer.h文件
#ifndef __BUZZER_H__
#define __BUZZER_H__

void Buzzer_Time(unsigned int ms);

#endif

(2)蜂鸣器播放音乐

需要提前加入Delay.c文件Delay.h文件Timer0.c文件Timer0.h文件

main.c文件
#include <REGX52.H>
#include "Delay.h"
#include "Timer0.h"

//蜂鸣器端口定义
sbit Buzzer=P1^5;

//播放速度,值为四分音符的时长(ms)
#define SPEED	500

//音符与索引对应表,P:休止符,L:低音,M:中音,H:高音,下划线:升半音符号#
#define P	0
#define L1	1
#define L1_	2
#define L2	3
#define L2_	4
#define L3	5
#define L4	6
#define L4_	7
#define L5	8
#define L5_	9
#define L6	10
#define L6_	11
#define L7	12
#define M1	13
#define M1_	14
#define M2	15
#define M2_	16
#define M3	17
#define M4	18
#define M4_	19
#define M5	20
#define M5_	21
#define M6	22
#define M6_	23
#define M7	24
#define H1	25
#define H1_	26
#define H2	27
#define H2_	28
#define H3	29
#define H4	30
#define H4_	31
#define H5	32
#define H5_	33
#define H6	34
#define H6_	35
#define H7	36

//索引与频率对照表
unsigned int FreqTable[]={
	0,
	63628,63731,63835,63928,64021,64103,64185,64260,64331,64400,64463,64528,
	64580,64633,64684,64732,64777,64820,64860,64898,64934,64968,65000,65030,
	65058,65085,65110,65134,65157,65178,65198,65217,65235,65252,65268,65283,
};

//乐谱
unsigned char code Music[]={
	//音符,时值,
	
	//1
	P,	4,
	P,	4,
	P,	4,
	M6,	2,
	M7,	2,
	
	H1,	4+2,
	M7,	2,
	H1,	4,
	H3,	4,
	
	M7,	4+4+4,
	M3,	2,
	M3,	2,
	
	//2
	M6,	4+2,
	M5,	2,
	M6, 4,
	H1,	4,
	
	M5,	4+4+4,
	M3,	4,
	
	M4,	4+2,
	M3,	2,
	M4,	4,
	H1,	4,
	
	//3
	M3,	4+4,
	P,	2,
	H1,	2,
	H1,	2,
	H1,	2,
	
	M7,	4+2,
	M4_,2,
	M4_,4,
	M7,	4,
	
	M7,	8,
	P,	4,
	M6,	2,
	M7,	2,
	
	//4
	H1,	4+2,
	M7,	2,
	H1,	4,
	H3,	4,
	
	M7,	4+4+4,
	M3,	2,
	M3,	2,
	
	M6,	4+2,
	M5,	2,
	M6, 4,
	H1,	4,
	
	//5
	M5,	4+4+4,
	M2,	2,
	M3,	2,
	
	M4,	4,
	H1,	2,
	M7,	2+2,
	H1,	2+4,
	
	H2,	2,
	H2,	2,
	H3,	2,
	H1,	2+4+4,
	
	//6
	H1,	2,
	M7,	2,
	M6,	2,
	M6,	2,
	M7,	4,
	M5_,4,
	
	
	M6,	4+4+4,
	H1,	2,
	H2,	2,
	
	H3,	4+2,
	H2,	2,
	H3,	4,
	H5,	4,
	
	//7
	H2,	4+4+4,
	M5,	2,
	M5,	2,
	
	H1,	4+2,
	M7,	2,
	H1,	4,
	H3,	4,
	
	H3,	4+4+4+4,
	
	//8
	M6,	2,
	M7,	2,
	H1,	4,
	M7,	4,
	H2,	2,
	H2,	2,
	
	H1,	4+2,
	M5,	2+4+4,
	
	H4,	4,
	H3,	4,
	H3,	4,
	H1,	4,
	
	//9
	H3,	4+4+4,
	H3,	4,
	
	H6,	4+4,
	H5,	4,
	H5,	4,
	
	H3,	2,
	H2,	2,
	H1,	4+4,
	P,	2,
	H1,	2,
	
	//10
	H2,	4,
	H1,	2,
	H2,	2,
	H2,	4,
	H5,	4,
	
	H3,	4+4+4,
	H3,	4,
	
	H6,	4+4,
	H5,	4+4,
	
	//11
	H3,	2,
	H2,	2,
	H1,	4+4,
	P,	2,
	H1,	2,
	
	H2,	4,
	H1,	2,
	H2,	2+4,
	M7,	4,
	
	M6,	4+4+4,
	P,	4,
	
	0xFF	//终止标志
};

unsigned char FreqSelect,MusicSelect;

void main()
{
	Timer0Init();
	while(1)
	{
		if(Music[MusicSelect]!=0xFF)	//如果不是停止标志位
		{
			FreqSelect=Music[MusicSelect];	//选择音符对应的频率
			MusicSelect++;
			Delay(SPEED/4*Music[MusicSelect]);	//选择音符对应的时值
			MusicSelect++;
			TR0=0;
			Delay(5);	//音符间短暂停顿
			TR0=1;
		}
		else	//如果是停止标志位
		{
			TR0=0;
			while(1);
		}
	}
}

void Timer0_Routine() interrupt 1	//该函数1ms进入一次,1ms翻转一次,2ms一个周期,对应1/0.002=500Hz
{
	if(FreqTable[FreqSelect])	//如果不是休止符
	{
		/*取对应频率值的重装载值到定时器*/
		TL0 = FreqTable[FreqSelect]%256;		//设置定时初值
		TH0 = FreqTable[FreqSelect]/256;		//设置定时初值
		Buzzer=!Buzzer;	//翻转蜂鸣器IO口
	}
}

网站公告

今日签到

点亮在社区的每一天
去签到