嵌入式入门教学——C51(中)

  • 嵌入式入门教学汇总:
    • 嵌入式入门教学——C51(上)
    • 嵌入式入门教学——C51(中)
    • 嵌入式入门教学——C51(下)

目录

七、矩阵键盘

八、定时器和中断

九、串口通信 

十、LED点阵屏

十一、DS1302实时时钟

十二、蜂鸣器


七、矩阵键盘

  • 在键盘中按键数量较多时,为了减少I/0口的用,通常将按键排列成矩阵形式。
  • 采用逐行或逐列的“扫描”,就可以读出任何位置按键的状态。

1、扫描

  • 数码管扫描(输出扫描)
    • 原理:显示第1位 -> 显示第2位 -> 显示第3位 -> ...,然后快速循环这个过程,最终实现所有数码管同时显示的效果(动态显示)。
  • 矩阵键盘扫描(输入扫描)
    • 原理:读取第1行(列) -> 读取第2行(列) -> 读取第3行(列) -> ...,然后快速循环这个过程,最终实现所有按键同时检测的效果。
  • 以上两种扫描方式的共性:节省I/O口。

2、矩阵按键原理图

  • I/O口如何知道哪个按键被按下:
    • 按行扫描:
      • ,将P17、P16、P15、P14依次循环为0(看作接地),其余为1。
      • 如果此时P17为0,当S1按下,P13为0;当S2按下,P12为0。
      • 【注】按行扫描时蜂鸣器会响,是由于引脚冲突造成的。
    • 按列扫描:
      • ,将P13、P12、P11、P10依次循环为0(看作接地),其余为1。
      • 如果此时P13为0,当S1按下,P17为0;当S5按下,P16为0。

3、矩阵键盘键码显示(LCD1602显示)

  • 内容:按下矩阵键盘的按键,在LCD1602上显示对应的键码值。
  • 新建工程,在工程目录下新建Functions、Objects、Listings文件夹。将LCD1602的驱动代码和延时函数的代码复制到Functions中。(代码上面有)
  • 设置.hex文件和.lst文件存放位置。
  • 新建main.c文件,将Delay.c和LCD1602.c添加到工程中,并且设置引入路径。
  • 编写矩阵键盘模块,新建MatrixKey.c和MatrixKey.h用来存放矩阵键盘模块。
  • 编写MatrixKey.h文件。
  • #ifndef __MATRIXKEY_H__
    #define __MATRIXKEY_H__
    unsigned char MatrixKey();
    #endif
  • 【小技巧】快速生成头文件的模板
    • 添加后,双击即可使用。
  • 编写MatrixKey.c文件。
  • #include 
    #include "Delay.h"
    /**
    	*	@brief	矩阵键盘读取按键键码
    	* @param	无
    	*	@retval	KeyNumber 按下按键的键码值
    		如果按键按下不放,程序会停留在此函数,松手的瞬间,返回键码,没有按键按下时,返回零。
    	*/
    unsigned char MatrixKey(){
    	unsigned char KeyNumber=0;
    	// 按列扫描
    	// 第一列
    	P1=0xFF;
    	P1_3=0;
    	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_5==0);Delay(20);KeyNumber=9;}
    	if(P1_4==0){ Delay(20);while(P1_4==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_5==0);Delay(20);KeyNumber=10;}
    	if(P1_4==0){ Delay(20);while(P1_4==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_5==0);Delay(20);KeyNumber=11;}
    	if(P1_4==0){ Delay(20);while(P1_4==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_5==0);Delay(20);KeyNumber=12;}
    	if(P1_4==0){ Delay(20);while(P1_4==0);Delay(20);KeyNumber=16;}
    
    	return KeyNumber;
    }
  • 编写main.c文件。
  • #include 
    #include "Delay.h"
    #include "LCD1602.h"
    #include "MatrixKey.h"
    unsigned char KeyNum;
    void main(){
    	LCD_Init();
    	LCD_ShowString(1,1,"Which One?");
    	while(1){
    		KeyNum=MatrixKey();
    		if(KeyNum){ // 不加if,KeyNum会马上刷新为0
    			LCD_ShowNum(2,1,KeyNum,2);
    		}
    	}
    }
  •  编译下载程序到单片机,按键后LCD1602显示对应键码值。

4、矩阵键盘密码锁(LCD1602显示)

  • 内容:s1~s10为1~9和0,s11为确定,s12为取消。输入密码正确,显示TRUE,输入密码错误,显示ERR。
  • 复制上一个工程的文件夹并修改名称(快速创建),将其中的MatrixKey.c和MatrixKey.h放入Functions中。
  • 打开工程,将原先的MatrixKey.c和MatrixKey.h从工程中删除,重新添加MatrixKey.c文件,并设置引入路径。(将矩阵键盘模块化)
  • 修改main.c文件。
  • #include 
    #include "Delay.h"
    #include "LCD1602.h"
    #include "MatrixKey.h"
    unsigned char KeyNum;
    unsigned int Password,Count;
    void main(){
    	LCD_Init();
    	LCD_ShowString(1,1,"Password:");
    	while(1){
    		KeyNum=MatrixKey();
    		if(KeyNum){ // 不加if,KeyNum会马上刷新为0
    			if(KeyNum<=10){ // 如果是s1~s10按下,输入密码
    				if(Count<4){ // 限制密码位数
    					Password*=10; // 密码左移一位
    					Password+=KeyNum%10; // 将10转化为0,获得一位密码
    					Count++; // 记录密码位数
    				}
    			}
    			LCD_ShowNum(2,1,Password,4); // 更新显示
    		}
    		if(KeyNum==11){ // s11按下,确认
    			if(Password==1234){ // 判断密码
    				LCD_ShowString(1,11,"TRUE"); // 密码正确
    				Password=0; // 密码清零
    				Count=0;	// 计次清零
    				LCD_ShowNum(2,1,Password,4); // 更新显示
    			}else{
    				LCD_ShowString(1,11,"ERR"); // 密码错误
    				Password=0; // 密码清零
    				Count=0;	// 计次清零
    				LCD_ShowNum(2,1,Password,4); // 更新显示
    			}
    		}
    		if(KeyNum==12){ // s12按下,取消
    				Password=0; // 密码清零
    				Count=0;	// 计次清零
    				LCD_ShowNum(2,1,Password,4); // 更新显示
    		}
    	}
    }
  • 编译下载程序到单片机,调试显示正常。 

八、定时器和中断

1、定时器

  • 定时器在单片机内部就像一个小闹钟一样,根据时钟的输出信号每隔“一秒”,计数单元的数值就增加一,当计数单元数值增加到“设定的闹钟提醒时间”时,计数单元就会向中断系统发出中断申请产生“响铃提醒”,使程序跳转到中断服务函数中执行。
  • 定时器作用:
    • 用于计时系统,可实现软件计时,或者使程序每隔一固定时间完成一项操作。
    • 替代长时间的Delay,提高CPU的运行效率和处理速度。
  • 定时器个数:3个(TO、T1、T2),TO和T1与传统的51单片机兼容T2是此型号单片机增加的资源(不同的单片机可能有不同数量的定时器)。
1.1、定时器工作模式
  • STC89C52的TO和T1均有四种工作模式:
    • 模式0:13位定时器/计数器
    • 模式1:16位定时器/计数器 (常用)
    • 模式2:8位自动重装模式
    • 模式3:两个8位计数器
  • 模式1框图:
    • 由SYSclk(系统时钟)或T0 Pin(外部脉冲)提供脉冲。
    • SYSclk:系统时钟,即晶振周期,本开发板上的晶振为12MHZ。
    • TL0和TH0(两个8位,范围是0~65535)用于计数,每隔1us加一,直到溢出时,产生中断,总共定时时间为65535us。
      • 例如,需要计时1ms,只需要给其赋值64535,还有1000到65535,刚好为1ms。
1.2、定时器相关寄存器
  • 寄存器是连接软硬件的媒介,在单片机中寄存器就是一段特殊的RAM存储器一方面,寄存器可以存储和读取数据;另一方面,每一个寄存器背后都连接了一根导线,控制着电路的连接方式寄存器相当于一个复杂机器的“操作按钮”。
  • TCON是控制寄存器,控制T0、T1的启动和停止及设置溢出标志,即控制定时器启动和中断申请。(具体每一位的作用看参考手册)
    • TF1:定时器/计数器T1溢出标志。
    • TR1:定时器T1的运行控制位。
    • TF0:定时器/计数器T0溢出中断标志。
    • TR0:定时器T0的运行控制位。
    • IE1:外部中断1请求源标志。
    • IT1:外部中断1触发方式控制位。
    • IE0:外部中断0请求源标志。
    • IT0:外部中断0触发方式控制位。
  • TMOD是定时/计数器的工作方式寄存器,确定工作方式和功能。(具体每一位的作用看参考手册)
    • GATE:定时器开关。
    • C/T:选择定时器。
    • M1、M0:定时器/计数器模式选择。

2、中断

  • CPU在处理某一事件A时,发生了另一事件B请求CPU迅速去处理(中断发生);CPU暂时中断当前的工作,转去处理事件B(中断响应和中断服务);待CPU将事件B处理完毕后,再回到原来事件A被中断的地方继续处理事件A(中断返回),这一过程称为中断。
  • 引起CPU中断的根源,称为中断源
  • 被中断的地方,称为断点
  • 中断功能的部件,称为中断系统
2.1、中断系统的结构
  • STC89C52中断资源:
    • 中断源个数:8个(外部中断0、定时器0中断、外部中断1、定时器1中断、串口中断、外部中断2、外部中断3)
    • 中断优先级个数:4个
    • 中断号:
  • 中断结构(部分)
  • 各中断源响应优先级:
    • 0 外部中断0
    • 1 定时/计数器0
    • 2 外部中断1 
    • 3 定时/计数器1 
    • 4 串行口
  • 中断优先级的三个原则:
    • CPU同时接收到几个中断时,首先响应优先级别最高的中断请求。
    • 正在进行的中断过程不能被新的同级或低优先级的中断请求所中断。
    • 正在进行的低优先级中断服务,能被高优先级中断请求所中断。
  • 中断系统内部设有两个用户不能寻址的优先级状态触发器。
    • 其中一个置1,表示正在响应高优先级的中断,它将阻断后来所有的中断请求。
    • 另一个置1,表示正在响应低优先级中断,它将阻断后来所有的低优先级中断请求。
  • 中断响应的条件:
    • 中断源有中断请求。
    • 此中断源的中断允许位为1。
    • CPU开中断(即EA=1)。
2.2、中断寄存器
  • IE是中断允许寄存器,控制中断的开启。
    • EA:CPU的总中断允许控制位。
    • ET2:定时/计数器T2的溢出中断允许位。
    • ES:串行口1中断允许位。
    • ET1:定时/计数器T1的溢出中断允许位。
    • EX1:外部中断1中断允许位。
    • ET0:T0的溢出中断允许位。
    • EX0:外部中断0中断允许位。
  • IPH是中断优先级控制寄存器高。(具体每一位的作用看参考手册)
  • IP是中断优先级控制寄存器低。
    • PT0H,PT0:定时器0中断优先级控制位。(其他的看参考手册)
      • 当PT0H=0且PT0=0时,定时器0中断为最低优先级中断(优先级0)
      • 当PT0H=0且PT0=1时,定时器0中断为较低优先级中断(优先级1)
      • 当PT0H=1且PT0=0时,定时器0中断为较高优先级中断(优先级2)
      • 当PT0H=1且PT0=1时,定时器0中断为最高优先级中断(优先级3)
  • 【注】不可位寻址的寄存器只能整体赋值;可位寻址的寄存器可以按位赋值。

3、独立按键控制LED流水灯状态

  • 内容:按下独立键盘的按键,改变LED流水灯的流转方向。
  • 新建工程,在工程目录下新建Objects、Listings文件夹,并设置.hex文件和.lst文件存放位置。
3.1、定时器0初始化
3.1.1、手写
  • 使用定时器0需要对其进行初始化,包括选择定时器和其工作模式。
  • 对TMOD寄存器操作,选择定时器和其工作模式。(不可按位赋值)
    • 高4位控制定时器1,不使用,故赋全0,即TMOD=0000 0001。
  • 对TCON寄存器操作,控制寄存器开启。(可按位赋值)
    • TF=0;TR0=1;
  • 配置中断
    • ET0=1; EA=1; PT0=0;
  • 编写代码
    • //定时器0初始化
      void Timer0Init(){
      	//TMOD=0x01; // 0000 0001 这样赋值在同时使用定时器1和定时器0时可能会产生冲突
      	TMOD&=0xF0; // 把TMOD低四位清零,高四位不变
      	TMOD|=TMOD|0x01; // 把TMOD最低位置1,高四位不变
      	TF0=0; // 中断溢出标志位
      	TR0=1; // 开启定时器
      	// 初始化计数器
      	TH0=64535/256; // 取高位
      	TL0=64535%256+1; // 取低位
      	// 配置中断
      	ET0=1; // 允许中断
      	EA=1;  // 允许总中断
      	PT0=0; // 中断优先级
      }
3.1.2、自动生成
  • 也可以使用STC-ISP生成初始化函数。(建议使用)
  • 但是没有配置中断,需要另外添加。
  • void Timer0Init(void)		//1毫秒@12.000MHz
    {
    	TMOD &= 0xF0;		//设置定时器模式
    	TMOD |= 0x01;		//设置定时器模式
    	TL0 = 0x18;		//设置定时初值
    	TH0 = 0xFC;		//设置定时初值
    	TF0 = 0;		//清除TF0标志
    	TR0 = 1;		//定时器0开始计时
    	// 配置中断
    	ET0=1; // 允许中断
    	EA=1;  // 允许总中断
    	PT0=0; // 中断优先级
    }
3.1.3、定时器模块化
  • 在工程目录下新建Functions文件夹,将Timer0.c和Timer0.h放入其中。将Timer.c加入工程中,并设置其引入路径。
  • Timer0.c
  • #include 
    /**
    	*	@brief	定时器0初始化,1毫秒@12.000MHz
    	* 	@param	无
    	*	@retval	无
    	*/
    void Timer0Init(void)		//1毫秒@12.000MHz
    {
    	TMOD &= 0xF0;		//设置定时器模式
    	TMOD |= 0x01;		//设置定时器模式
    	TL0 = 0x18;		//设置定时初值
    	TH0 = 0xFC;		//设置定时初值
    	TF0 = 0;		//清除TF0标志
    	TR0 = 1;		//定时器0开始计时
    	// 配置中断
    	ET0=1;
    	EA=1; 
    	PT0=0;
    }
    /* 定时器中断函数模板
    void Timer0_Routine() interrupt 1{
    	static unsigned int T0Count;
    	TL0 = 0x18;		//设置定时初值
    	TH0 = 0xFC;		//设置定时初值
    	T0Count++;
    	if(T0Count>=1000){
    		T0Count=0;
    		P2_0=~P2_0;
    	}
    }
    */
  • Timer0.h
  • #ifndef __TIMER0_H__
    #define __TIMER0_H__
    void Timer0Init(void);
    #endif
3.2、独立按键模块化
  • 将延时函数模块和独立按键模块放入Functions文件夹。将Delay.c和Key.c加入工程中,并设置其引入路径。
  • Key.c
  • #include 
    #include "Delay.h"
    /**
    	*	@brief	获取独立按键键码
    	* 	@param	无
    	*	@retval	按下按键的键码,范围:0~4,无按键按下时,返回0
    	*/
    unsigned char Key(){
    	unsigned char KeyNumber=0;
    	if(P3_1==0){Delay(20);while(P3_1==0);Delay(20);KeyNumber=1;}
    	if(P3_0==0){Delay(20);while(P3_0==0);Delay(20);KeyNumber=2;}
    	if(P3_2==0){Delay(20);while(P3_2==0);Delay(20);KeyNumber=3;}
    	if(P3_3==0){Delay(20);while(P3_3==0);Delay(20);KeyNumber=4;}
    	return KeyNumber;
    }
  • Key.h
  • #ifndef __KEY_H__
    #define __KEY_H__
    unsigned char Key();
    #endif
    
3.3、编写main.c文件
  • 定时器0每隔1微秒会执行中断函数,而中断函数中每隔500次会执行真正的操作,也就是每隔0.5ms会移动一次LED。
  • #include 
    #include "Timer0.h"
    #include "Key.h"
    #include  // 引入_crol_()和_cror_()循环移位函数
    unsigned char KeyNum,LEDMode;
    void main(){
    	Timer0Init(); // 定时器0初始化
    	P2=0xFE; // 初始化LED
    	while(1){ // 每当定时器溢出,就跳转执行中断函数
    		KeyNum=Key();
    		if(KeyNum){
    			if(KeyNum==1){
    				LEDMode++;
    				if(LEDMode>=2) LEDMode=0; // LEDMode只在0和1变化
    			}
    		}
    	}
    }
    // 中断函数
    void Timer0_Routine() interrupt 1{
    	static unsigned int T0Count;
    	TL0 = 0x18;		//设置定时初值
    	TH0 = 0xFC;		//设置定时初值
    	T0Count++;
    	if(T0Count>=500){ // 每隔0.5ms让LED移动
    		T0Count=0;
    		if(LEDMode==0)
    			P2=_crol_(P2,1); // P2左移1位
    		else P2=_cror_(P2,1); // P2右移1位
    	}
    }

4、定时器时钟(LCD1602显示)

  • 新建工程,在工程目录下新建Functions、Objects、Listings文件夹,将延时函数、定时器和LCD1602模块放入Functions文件夹中。设置生成.hex文件并将其存放到Objects中,将.lst文件存放到Listings中。新建main.c文件,添加Delay.c、LCD1602.c、Timer0.c到工程中,并设置其引入路径。
  • 编写main.c文件。
  • #include 
    #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){
    		T0Count=0;
    		Sec++;
    		if(Sec>=60){
    			Sec=0;
    			Min++;
    			if(Min>=60){
    				Min=0;
    				Hour++;
    				if(Hour>=24) Hour=0;
    			}
    		}
    	}
    }

九、串口通信 

1、串口

  • 串口是一种应用十分广泛的通讯接口,可以实现两个设备的互相通信。例如,单片机与单片机、单片机与电脑、单片机与名式各样的模块互相通信。
  • 51单片机内部自带UART (Universal Asynchronous ReceiverTransmitter,通用异步收发器),可实现单片机的串口通信。
1.1、硬件电路
  • 简单双向串口通信有两根通信线(发送端TXD和接收端RXD)。
  • TXD与RXD要交叉连接。
  • 当只需单向的数据传输时,可以直接一根通信线。
  • 当电平标准不一致时,需要加电平转换芯片。
  • 嵌入式入门教学——C51(中)_第1张图片
1.2、电平标准
  • 电平标准是数据1和数据0的表达方式,是传输线缆中人为规定的电压,串口常用的电平标准有如下三种:
    • TTL电平:+5V表示1,0V表示0
    • RS232电平:-3~-15V表示1,+3~+15V表示0
    • RS485电平:两线压差+2~+6V表示1,-2~-6V表示0(差分信号)
1.3、相关术语
  • 全双工:通信双方可以在同一时刻互相传输数据。
  • 半双工:通信双方可以互相传输数据,但必须分时复用一根数据线。
  • 单工:通信只能有一方发送到另一方,不能反向传输。
  • 异步:通信双方各自约定通信速率。
  • 同步:通信双方靠一根时钟线来约定通信速率。
  • 总线:连接各个设备的数据传输线路 (类似于一条马路,把路边各连接起来,使住户可以相互交流)。
1.4、UATR(通用异步收发器)
  • STC89C52有1个UART,有四种工作模式:
    • 模式0:同步移位寄存器
    • 模式1:8位UART,波特率可变 (常用)
    • 模式2:9位UART,波特率固定
    • 模式3:9位UART,波特率可变
1.5、USB原理图(使用USB进行串口通信)
  • 嵌入式入门教学——C51(中)_第2张图片
  • 嵌入式入门教学——C51(中)_第3张图片
1.6、串口的参数及时序图
  • 波特率:串口通信的速率(发送和接收各数据位的间隔时间)。
  • 检验位:用于数据验证。
  • 停止位:用于数据帧间隔。
  • 8位数据格式:
    • 嵌入式入门教学——C51(中)_第4张图片
  • 9位数据格式(多一位校验位):
    • 嵌入式入门教学——C51(中)_第5张图片
1.7、串口相关寄存器
  • 嵌入式入门教学——C51(中)_第6张图片
  • SCON是串口控制寄存器。(具体每一位的作用看参考手册)
    • SM0:检测帧错误或指定工作模式。SM0=0,SM1=1时,工作在模式1。
    • SM2:允许模式2或模式3多机通信。
    • REN:接收使能。
    • T1:发送中断请求标志位。
    • R1:接收中断请求标志位。
  • PCON是电源控制寄存器。
    • SMOD:波特率选择。
    • SMOD0:帧错误检测有效控制位。
1.8、串口模式图
  • 嵌入式入门教学——C51(中)_第7张图片

2、串口向电脑发送数据

  • 内容:单片机通过串口,每秒向计算机发送一个逐步加一的数字。
  • 新建工程,在工程目录下新建Functions、Objects、Listings文件夹,将延时函数模块放入Functions文件夹中。设置生成.hex文件并将其存放到Objects中,将.lst文件存放到Listings中。新建main.c文件,添加Delay.c到工程中,并设置其引入路径。
2.1、串口初始化
  • 配置SCON,让串口在模式1下工作。
  • 由于模式1下只允许使用定时器1,所以需要对定时器1初始化。
    • 嵌入式入门教学——C51(中)_第8张图片
  • 配置PCON需要计算波特率,可以使用STC-ISP中的波特率计算器自动生成串口初始化代码,再稍作修改。(TL1和TH1决定了波特率,结合上述串口模式图理解)
  • 嵌入式入门教学——C51(中)_第9张图片
  • // 串口初始化
    void UART_Init(){ // [email protected]
    	PCON |= 0x80;
    	SCON = 0x40;
    	// 配置定时器1(只允许使用定时器1)
    	TMOD &= 0x0F;		//设置定时器模式
    	TMOD |= 0x20;		//设置定时器模式
    	TL1 = 0xF3;		//设置定时初值
    	TH1 = 0xF3;		//设置定时初值
    	ET1 = 0;		//禁止定时器1中断
    	TR1 = 1;		//启动定时器1
    }
 2.2、串口模块化
  • UART.c
  • #include 
    /**
    	*	@brief	串口初始化,[email protected]
    	* 	@param	无
    	*	@retval	无
    	*/
    void UART_Init(){
    	PCON |= 0x80;
    	SCON = 0x50;
    	// 配置定时器1(只允许使用定时器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); // 检测是否完成。机械控制为1,即TI为1时发送完成。
    	TI=0; // 软件复位
    }
  • UART.h
  • #ifndef __UART_H__
    #define __UART_H__
    void UART_Init();
    void UART_SendByte(unsigned char Byte);
    #endif
  • 将 UART.c和UART.h放入Functions中,在工程中添加UART.c文件,并设置其引入路径。
2.3、编写main.c文件
  • #include 
    #include "Delay.h"
    #include "UART.h"
    unsigned char Sec;
    void main(){
    	UART_Init();
    	while(1){
    		UART_SendByte(Sec);
    		Sec++;
    		Delay(1000);
    	}
    }
  • 编译下载到单片机。打开STC-ISP的串口助手,设置波特率等,打开串口。按复位键,每隔一秒会收到加一的数字。
  • 嵌入式入门教学——C51(中)_第10张图片

3、 电脑通过串口控制LED

  • 内容:计算机通过串口向单片机发送数据,让单片机亮起对于的LED灯,并且单片机向计算机发送接收到的数据。
  • 将上一个工程文件夹复制一份,并修改名称。这次需要接收数据,所以需要重新修改串口初始化函数。只需要在UART.c中修改SCON的REN位,即SCON=0x50。
  • 因为要使用到中断,所以还要对中断进行配置。
  • 嵌入式入门教学——C51(中)_第11张图片
  • 修改后的串口初始化函数:
    • void UART_Init(){
      	PCON |= 0x80;
      	SCON = 0x50;
      	// 配置定时器1(只允许使用定时器1)
      	TMOD &= 0x0F;		//设置定时器模式
      	TMOD |= 0x20;		//设置定时器模式
      	TL1 = 0xF3;		//设置定时初值
      	TH1 = 0xF3;		//设置定时初值
      	ET1 = 0;		//禁止定时器1中断
      	TR1 = 1;		//启动定时器1
      	// 配置中断
      	EA=1; // 启动所有中断
      	ES=1// 启动串口中断
      }
  • 在main.c文件中添加串口中断函数。
    • #include 
      #include "Delay.h"
      #include "UART.h"
      unsigned char Sec;
      void main(){
      	UART_Init();
      	while(1){
      		
      	}
      }
      void UART_Rountine() interrupt 4{ // 当单片机串口接收到数据时,该中断函数会执行
      	if(RI==1){ // 接收中断请求标志位,将发送和接收区分开
      		P2=~SBUF;
      		UART_SendByte(SBUF); // 将单片机收到的数据发送到电脑显示
      		RI=0; // 软件复位
      	}
      }
  • 编译下载程序到单片机,使用串口助手发送数据到单片机(十六进制),单片机亮起对于的LED灯,并且向计算机发送接收到的数据。
  •  嵌入式入门教学——C51(中)_第12张图片

十、LED点阵屏

  • 嵌入式入门教学——C51(中)_第13张图片
  •  LED点阵屏由若干个独立的LED组成,LED以矩阵的形式排列,以灯珠亮灭来显示文字、图片、视频等。LED点阵屏广泛应用于各种公共场合,如汽车报站器、广告屏以及公告牌等。
  • LED点阵屏分类
    • 按颜色:单色、双色、全彩。
    • 按像素:8*8、16*16等 (大规模的LED点阵通常由很多个小点阵拼接而成)

1、显示原理

  • 嵌入式入门教学——C51(中)_第14张图片
  • LED点阵屏的结构类似于数码管,只不过是数码管把每一列的像素以“8”字型排列而已。
  • LED点阵屏与数码管一样,有共阴和共阳两种接法,不同的接法对应的电路结构不同。
  • LED点阵屏需要进行逐行或逐列扫描,才能使所有LED同时显示。
  • 开发板对应引脚关系
    • 嵌入式入门教学——C51(中)_第15张图片

2、LED点阵屏原理图

  • 嵌入式入门教学——C51(中)_第16张图片
  • 嵌入式入门教学——C51(中)_第17张图片
  • 使用了74HC595扩展引脚,使用3个输入控制8个输出。
  • 嵌入式入门教学——C51(中)_第18张图片

 3、74HC595

  • 74HC595是串行输入并行输出的移位寄存器,可用3根线输入串行数据,8根线输出并行数据,多片级联后,可输出16位、24位、32位等,常用于IO口扩展。(结合上面原理图理解)
  • OE:输出使能(上面加根横线代表低电平有效),所以需要将OE和GND短接,74HC595才能工作。(下图接的VCC,要改)
    • 嵌入式入门教学——C51(中)_第19张图片
  • SRCLR:串行清零端。接了VCC,表示不清空。
  • P35(RCLK):寄存器时钟。(获得8位数据后,将8位数据同时输出)
  • P36(SRCLK):串行时钟。(将获得的数据下移一位)
  • P34(SER):串行数据。(一次只输入一位数据)
  • 嵌入式入门教学——C51(中)_第20张图片
  • 再经过8位的输入,就能刷新数据。

 4、LED点阵屏显示图形

  • 内容:控制LED点阵屏显示一个笑脸。
  • 新建工程,在工程目录下新建Functions、Objects、Listings文件夹,将延时函数模块放入Functions文件夹中。设置生成.hex文件并将其存放到Objects中,将.lst文件存放到Listings中。新建main.c文件,添加Delay.c到工程中,并设置其引入路径。
4.1、C51的sfr和sbit
  • sfr:特殊功能寄存器声明
    • 例:sfr P0 = 0x80; // 声明P0口寄存器,物理地址为0x80
  • sbit:特殊位声明
    • 例:sbit P0_1 = 0x81; 或 sbit P0_1 = P0^1; // 声明P0寄存器的第1位
4.2、编写main.c文件
  • 74HC595控制行显示,P0寄存器控制列显示。使用动态显示原理,循环扫描要显示的每一列。(需要消影)
  • #include 
    #include "Delay.h"
    sbit RCK=P3^5; // RCLK寄存器时钟
    sbit SCK=P3^6; // SRCLK串行时钟
    sbit SER=P3^4; // SER串行数据
    #define MATRIX_LED_PORT P0
    void MatrixLED_Init(){ // 初始化74HC595
    	SCK=0;
    	RCK=0;
    }
    void _74HC595_WriteByte(unsigned char Byte){ // 控制行
    	unsigned char i;
    	for(i=0;i<8;i++){ // 将8位数据放入移位寄存器
    		SER=Byte&(0x80>>i); // 非0赋1
    		SCK=1; // 移位
    		SCK=0;
    	}
    	RCK=1; // 将8位数据输出
    	RCK=0;
    }
    void MatrixLED_ShowColumn(unsigned char Column,unsigned char Line){
    	_74HC595_WriteByte(Line); // 第几行亮
    	MATRIX_LED_PORT=~(0x80>>Column); // 第几列亮,0选中
    	Delay(1); // 消影
    	MATRIX_LED_PORT=0xFF;
    }
    void main(){
    	MatrixLED_Init();
    	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);
    	}
    }
  • 编译下载程序到单片机,显示如下。
  •  嵌入式入门教学——C51(中)_第21张图片
4.3、将LED点阵屏显示模块化
  • MatrixLED.c
  • #include 
    #include "Delay.h"
    sbit RCK=P3^5; // RCLK寄存器时钟
    sbit SCK=P3^6; // SRCLK串行时钟
    sbit SER=P3^4; // SER串行数据
    #define MATRIX_LED_PORT P0
    /**
    	*	@brief	LED点阵屏初始化
    	* 	@param	无
    	*	@retval	无
    	*/
    void MatrixLED_Init(){ // 初始化74HC595
    	SCK=0;
    	RCK=0;
    }
    /**
    	*	@brief	74HC595写入一个字节
    	* 	@param	要写入的字节
    	*	@retval	无
    	*/
    void _74HC595_WriteByte(unsigned char Byte){ // 控制行
    	unsigned char i;
    	for(i=0;i<8;i++){ // 将8位数据放入移位寄存器
    		SER=Byte&(0x80>>i); // 非0赋1
    		SCK=1; // 移位
    		SCK=0;
    	}
    	RCK=1; // 将8位数据输出
    	RCK=0;
    }
    /**
    	*	@brief	LED点阵屏显示一列数据
    	* 	@param	Column 要选择的列,范围:0~7,0在最左边
    	* 	@param	Line 选择列显示的数据,高位在上,1为亮,0为灭
    	*	@retval	无
    	*/
    void MatrixLED_ShowColumn(unsigned char Column,unsigned char Line){
    	_74HC595_WriteByte(Line); // 第几行亮
    	MATRIX_LED_PORT=~(0x80>>Column); // 第几列亮,0选中
    	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,unsigned char Line);
    #endif

5、LED点阵屏显示动画

  • 内容:LED点阵屏流动显示字符串。
  • 复制上一个工程文件夹,将LED点阵屏模块放入Functions中,添加MatrixLED.c到工程中,并设置其引入路径。
  • 使用字模提取工具获得动画位置(网上有)
    • 点击新建,建立一个高度为8,长度自定义的画框。点击放大,在画框中点出需要显示的动画。
    • 嵌入式入门教学——C51(中)_第22张图片嵌入式入门教学——C51(中)_第23张图片
    • 点击C51格式,将生成的字符串新建为数组。
    • 嵌入式入门教学——C51(中)_第24张图片
  • 编写main.c文件。
    • #include 
      #include "Delay.h"
      #include "MatrixLED.h"
      unsigned char code Animation[]={ // code将数组放入flash中
      	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // 为了让动画流畅,开头让动画流水出现
      	0xFF,0x08,0x08,0x08,0xFF,0x00,0x0E,0x15,
      	0x15,0x0C,0x00,0x7E,0x01,0x01,0x02,0x00,
      	0x7E,0x01,0x01,0x02,0x00,0x0E,0x11,0x11,
      	0x0E,0x00,0x7D,0x00,0x7D,0x00,0x7D,0x00,
      	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // 显示完空的8列,再显示新的,才不会扰乱前面的显示
      };
      void main(){
      	unsigned char i,offset,Count;
      	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>40) offset=0;
      		}
      	}
      }
  • 编译下载程序到单片机,显示如下。

十一、DS1302实时时钟

  • DS1302是由低功耗实时时钟芯片,它可以对年、月、日、周、时、分、秒进行计时且具有闰年补偿等多种功能。
  • 工作原理:设置好初始时间,DS1302就会自动计时,只需把其中寄存器的值读出即可。

1、硬件电路

  • 嵌入式入门教学——C51(中)_第25张图片嵌入式入门教学——C51(中)_第26张图片
  • 嵌入式入门教学——C51(中)_第27张图片
  • 【注】VCC1(备用电池)的作用是保证时钟在断电后依然能够计时。但是本开发板中并未接VCC1,所以没有断电后继续计时的功能。
  • 内部结构框图:
    • 嵌入式入门教学——C51(中)_第28张图片

2、DS1302原理图

  • 嵌入式入门教学——C51(中)_第29张图片
  • 嵌入式入门教学——C51(中)_第30张图片
  • SCLK:串行时钟,用来控制IO口每一位的输入/读取。
  • IO:每次输入/读取一位数据。
  • CE:芯片使能。

3、DS1302相关寄存器

  • 嵌入式入门教学——C51(中)_第31张图片
  • 【注】寄存器中的数据是以BCD码进行存储的。(所以输出需要转换为十进制,输入需要转换为BCD码)
    • ​​​​​BCD码是用4位二进制数来表示1位十进制数。
    • 例:0001 0011表示13,1000 0101表示85,0001 1010不合法(第二位超出了)
      • 在十六进制中的体现:0x13表示13,0x85表示85,0x1A不合法
    • BCD码转十进制:DEC=BCD/16*10+BCD%16(2位BCD)
    • 十进制转BCD码:BCD=DEC/10*16+DEC%10(2位BCD)
  • 命令字(启动每一次数据传输,控制输入/读取哪个寄存器的值,上图前两列即为命令字):
    • 嵌入式入门教学——C51(中)_第32张图片
    • 第7位:固定为1。
    • 第6位:0操作时钟,1操作RAM。
    • 第1~5位:要操作的地址。
    • 第0位:0写,1读。

4、DS1302时序图

  • 读:先输入要读取寄存器的位置(命令字),再读取数据。
  • 写:先输入要写入寄存器的位置(命令字),再写入数据。
  • 嵌入式入门教学——C51(中)_第33张图片
  • 【注】利用时序(上升/下降沿)控制位输入/读出。因为串行输入是一次输入一位,所以应该先从低位开始输入/读出。

 5、DS1302时钟(LCD1602显示)

  • 内容:使用DS1302控制时钟,并在LCD1602显示屏上显示。
  • 新建工程,在工程目录下新建Functions、Objects、Listings文件夹,将LCD1602模块放入Functions文件夹中。设置生成.hex文件并将其存放到Objects中,将.lst文件存放到Listings中。新建main.c文件,添加LCD1602.c到工程中,并设置其引入路径。
5.1、DS1302模块化
  • DS1302.c
  • #include 
    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
    char DS1302_TIME[]={23,8,11,13,59,55,5};
    /**
    	*	@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<1; // 时序,一个上升下降沿读取IO口的一位
    		DS1302_SCLK=0;
    	}
    	// 输入数据
    	for(i=0;i<8;i++){
    		DS1302_IO=Data&(0x01<1; // 时序
    		DS1302_SCLK=0;
    	}
    	DS1302_CE=0;
    }
    /**
    	*	@brief	从DS1302读出一个字节数据
    	* 	@param	Command	要写入的命令
    	*	@retval	Data 读出的一个字节数据
    	*/
    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<0; // 时序
    		DS1302_SCLK=1;
    	}
    	// 读取数据
    	for(i=0;i<8;i++){
    		DS1302_SCLK=1;
    		DS1302_SCLK=0;
    		if(DS1302_IO){ Data|=(0x01<// 将对应位置1
    	}
    	DS1302_CE=0;
    	DS1302_IO=0;
    	return Data;
    }
    /**
    	*	@brief	使用数组设置时间
    	* 	@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	读取时间到数组
    	* 	@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__
    void DS1302_Init(void);
    void DS1302_WriteByte(unsigned char Command,Data);
    unsigned char DS1302_ReadByte(unsigned char Command);
    void DS1302_ReadTime(void);
    void DS1302_SetTime(void);
    extern char DS1302_TIME[];
    #endif
  • 将编写好的DS1302模块放入Funcitons中,并设置其引入路径。
5.2、编写main.c文件
  • #include 
    #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(1,10,DS1302_TIME[6],1); // 星期
    		LCD_ShowNum(2,1,DS1302_TIME[3],2);
    		LCD_ShowNum(2,4,DS1302_TIME[4],2);
    		LCD_ShowNum(2,7,DS1302_TIME[5],2);
    	}
    }
  •  编译下载程序到单片机,显示如下。

6、DS1302调节时钟(LCD1602显示)

  • 内容:使用DS1302控制时钟在LCD1602显示屏上显示,并且可以通过独立按键设置时间。按下按键1打开设置,按下按键2切换设置对象,按下按键3增大数值,按下按键4减小数值,再次按下按键1保存设置。
  • 复制上一个工程文件夹,将延时函数、独立按键和定时器0模块放入Functions文件夹中。添加Delay.c、Key.c和Timer0.c到工程中,并设置其引入路径。
  • 编写main.c文件
    • #include 
      #include "LCD1602.h"
      #include "DS1302.h"
      #include "Key.h"
      #include "Timer0.h"
      unsigned char KeyNum,MODE,TimeSetSelect,TimeSetFlashFlag;
      /**
      	*	@brief	显示时间
      	* 	@param	无
      	*	@retval	无
      	*/
      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(1,10,DS1302_TIME[6],1); // 星期
      	LCD_ShowNum(2,1,DS1302_TIME[3],2);
      	LCD_ShowNum(2,4,DS1302_TIME[4],2);
      	LCD_ShowNum(2,7,DS1302_TIME[5],2);
      }
      /**
      	*	@brief	调节时间
      	* 	@param	无
      	*	@retval	无
      	*/
      void TimeSet(void){
      	if(KeyNum==2){ // 按键按键2选择要更新的位置
      		TimeSetSelect++;
      		TimeSetSelect%=7; // 越界清零
      	}else if(KeyNum==3){ // 按键按键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 && DS1302_TIME[0]%100!=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(DS1302_TIME[6]>7) DS1302_TIME[6]=1; // 星期
      	}else if(KeyNum==4){ // 按键按键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 && DS1302_TIME[0]%100!=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(DS1302_TIME[6]<1) DS1302_TIME[6]=7; // 星期
      	}
      	// 更新显示
      	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);
      	if(TimeSetSelect==6 && TimeSetFlashFlag==1) LCD_ShowString(1,10," ");
      	else LCD_ShowNum(1,10,DS1302_TIME[6],1);
      	LCD_ShowNum(2,10,TimeSetSelect,1);
      }
      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;
      		}
      	}
      }
      // 定时器0
      void Timer0_Routine() interrupt 1{
      	static unsigned int T0Count;
      	TL0 = 0x18;		//设置定时初值
      	TH0 = 0xFC;		//设置定时初值
      	T0Count++;
      	if(T0Count>=500){ // 5ms
      		T0Count=0;
      		TimeSetFlashFlag=!TimeSetFlashFlag;
      	}
      }

十二、蜂鸣器

  • 蜂鸣器是一种将电信号转换为声音信号的器件,常用来产生设备的按键音、报警音等提示信号。蜂鸣器按驱动方式可分为有源蜂鸣器和无源蜂鸣器。
    • 有源蜂鸣器:内部自带振荡源,将正负极接上直流电压即可持续发声,频率固定。
    • 无源蜂鸣器:内部不带振荡源,需要控制器提供振荡脉冲才可发声调整提供振荡脉冲的频率,可发出不同频率的声音。
  • 本开发板使用的是无源蜂鸣器,需要不断翻转电平蜂鸣器才会发声。例如:
  • for(i=0;i<500;i++){
        Buzzer=!Buzzer; // 蜂鸣器控制引脚
        Delay(1); // 每隔一毫秒翻转一次,周期为2毫秒,频率为500HZ
    } // 以500HZ的频率响500ms

1、蜂鸣器原理图

  • 嵌入式入门教学——C51(中)_第34张图片嵌入式入门教学——C51(中)_第35张图片
  • 因为单片机的IO口不能直接驱动蜂鸣器,所以通过ULN2003D辅助控制。

2、蜂鸣器播放提示音

  • 内容:按下独立按键,数码管显示对应的按键键码,蜂鸣器在松开按键时发声。
  • 新建工程,在工程目录下新建Functions、Objects、Listings文件夹,将延时函数、独立按键和数码管模块放入Functions文件夹中。设置生成.hex文件并将其存放到Objects中,将.lst文件存放到Listings中。新建main.c文件,添加Delay.c、Key.c和Nixie.c到工程中,并设置其引入路径。
  • 编写蜂鸣器模块。
    • Buzzer.c
    • #include 
      #include 
      // 蜂鸣器端口
      sbit Buzzer=P2^5;
      /**
      	*	@brief	蜂鸣器私有延时函数,延时500微秒
      	* 	@param	无
      	*	@retval	无
      	*/
      void Buzzer_Delay500us()		//@12.000MHz
      {
      	unsigned char i;
      	_nop_(); // 延时一个机器周期
      	i = 247;
      	while (--i);
      }
      /**
      	*	@brief	让蜂鸣器以1000HZ的频率发声
      	* 	@param	ms 发声时长
      	*	@retval	无
      	*/
      void Buzzer_Time(unsigned int ms){
      	unsigned int i;
      	for(i=0;i2;i++){
      		Buzzer=!Buzzer;
      		Buzzer_Delay500us(); // 每隔500微秒翻转一次,周期为1毫秒,频率为1000HZ
      	}
      }
    • Buzzer.h
    • #ifndef __BUZZER_H__
      #define __BUZZER_H__
      void Buzzer_Time(unsigned int ms);
      #endif
    • 将蜂鸣器模块放入Functions文件夹中,并设置其引入路径。
  • 编写main.c文件。
    • #include 
      #include "Delay.h"
      #include "Key.h"
      #include "Nixie.h"
      #include "Buzzer.h"
      unsigned char KeyNum;
      void main(){
      	Nixie_Static(1,0);
      	while(1){
      		KeyNum=Key();
      		if(KeyNum){
      			Buzzer_Time(100); // 蜂鸣器发声100ms
      			Nixie_Static(1,KeyNum); // 显示对应按键的键码
      		}
      	}
      }
  • 按下独立按键松开时,蜂鸣器以1000HZ频率发声100毫秒。

3、蜂鸣器播放音乐

  • 内容:蜂鸣器播放小星星。
  • 新建工程,在工程目录下新建Functions、Objects、Listings文件夹,将延时函数、定时器0模块放入Functions文件夹中。设置生成.hex文件并将其存放到Objects中,将.lst文件存放到Listings中。新建main.c文件,添加Delay.c和Timer0.c到工程中,并设置其引入路径。
  • 乐谱:
    • 嵌入式入门教学——C51(中)_第36张图片嵌入式入门教学——C51(中)_第37张图片
  • 编写main.c文件夹
    • #include 
      #include "Delay.h"
      #include "Timer0.h"
      sbit Buzzer=P2^5;
      #define SPEED 500 //十六分音符时长
      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 Music[]={
      	13,4,
      	13,4,
      	20,4,
      	20,4,
      	22,4,
      	22,4,
      	20,8,
      	0,2,
      	18,4,
      	18,4,
      	17,4,
      	17,4,
      	15,4,
      	15,4,
      	13,8,
      	0xFF, // 终止标志
      };
      unsigned char FreqSelect=0,MusicSelect=0;
      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执行一次,500HZ
      	if(FreqTable[FreqSelect]){
      		TL0 = FreqTable[FreqSelect]%256;		//设置定时初值
      		TH0 = FreqTable[FreqSelect]/256;		//设置定时初值
      		Buzzer=!Buzzer;
      	}
      }

你可能感兴趣的:(嵌入式,51单片机)