嵌入式入门教学——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 <REGX52.H>
    #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 <REGX52.H>
    #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会马上刷新为0LCD_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 <REGX52.H>
    #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会马上刷新为0if(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 <REGX52.H>
    /***	@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 <REGX52.H>
    #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 <REGX52.H>
    #include "Timer0.h"
    #include "Key.h"
    #include <intrins.h> // 引入_crol_()和_cror_()循环移位函数
    unsigned char KeyNum,LEDMode;
    void main(){Timer0Init(); // 定时器0初始化P2=0xFE; // 初始化LEDwhile(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 <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){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要交叉连接。
  • 当只需单向的数据传输时,可以直接一根通信线。
  • 当电平标准不一致时,需要加电平转换芯片。
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进行串口通信)
1.6、串口的参数及时序图
  • 波特率:串口通信的速率(发送和接收各数据位的间隔时间)。
  • 检验位:用于数据验证。
  • 停止位:用于数据帧间隔。
  • 8位数据格式:
  • 9位数据格式(多一位校验位):
1.7、串口相关寄存器
  • SCON是串口控制寄存器。(具体每一位的作用看参考手册)
    • SM0:检测帧错误或指定工作模式。SM0=0,SM1=1时,工作在模式1。
    • SM2:允许模式2或模式3多机通信。
    • REN:接收使能。
    • T1:发送中断请求标志位。
    • R1:接收中断请求标志位。
  • PCON是电源控制寄存器。
    • SMOD:波特率选择。
    • SMOD0:帧错误检测有效控制位。
1.8、串口模式图

2、串口向电脑发送数据

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

3、 电脑通过串口控制LED

  • 内容:计算机通过串口向单片机发送数据,让单片机亮起对于的LED灯,并且单片机向计算机发送接收到的数据。
  • 将上一个工程文件夹复制一份,并修改名称。这次需要接收数据,所以需要重新修改串口初始化函数。只需要在UART.c中修改SCON的REN位,即SCON=0x50。
  • 因为要使用到中断,所以还要对中断进行配置。
  • 修改后的串口初始化函数:
    • 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 <REGX52.H>
      #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灯,并且向计算机发送接收到的数据。
  •  

十、LED点阵屏

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

1、显示原理

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

2、LED点阵屏原理图

  • 使用了74HC595扩展引脚,使用3个输入控制8个输出。

 3、74HC595

  • 74HC595是串行输入并行输出的移位寄存器,可用3根线输入串行数据,8根线输出并行数据,多片级联后,可输出16位、24位、32位等,常用于IO口扩展。(结合上面原理图理解)
  • OE:输出使能(上面加根横线代表低电平有效),所以需要将OE和GND短接,74HC595才能工作。(下图接的VCC,要改)
  • SRCLR:串行清零端。接了VCC,表示不清空。
  • P35(RCLK):寄存器时钟。(获得8位数据后,将8位数据同时输出)
  • P36(SRCLK):串行时钟。(将获得的数据下移一位)
  • P34(SER):串行数据。(一次只输入一位数据)
  • 再经过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 <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
    void MatrixLED_Init(){ // 初始化74HC595SCK=0;RCK=0;
    }
    void _74HC595_WriteByte(unsigned char Byte){ // 控制行unsigned char i;for(i=0;i<8;i++){ // 将8位数据放入移位寄存器SER=Byte&(0x80>>i); // 非0赋1SCK=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);}
    }
  • 编译下载程序到单片机,显示如下。
  •  
4.3、将LED点阵屏显示模块化
  • 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	LED点阵屏初始化* 	@param	无*	@retval	无*/
    void MatrixLED_Init(){ // 初始化74HC595SCK=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赋1SCK=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格式,将生成的字符串新建为数组。
  • 编写main.c文件。
    • #include <REGX52.H>
      #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、硬件电路

  • 【注】VCC1(备用电池)的作用是保证时钟在断电后依然能够计时。但是本开发板中并未接VCC1,所以没有断电后继续计时的功能。
  • 内部结构框图:

2、DS1302原理图

  • SCLK:串行时钟,用来控制IO口每一位的输入/读取。
  • IO:每次输入/读取一位数据。
  • CE:芯片使能。

3、DS1302相关寄存器

  • 【注】寄存器中的数据是以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)
  • 命令字(启动每一次数据传输,控制输入/读取哪个寄存器的值,上图前两列即为命令字):
    • 第7位:固定为1。
    • 第6位:0操作时钟,1操作RAM。
    • 第1~5位:要操作的地址。
    • 第0位:0写,1读。

4、DS1302时序图

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

 5、DS1302时钟(LCD1602显示)

  • 内容:使用DS1302控制时钟,并在LCD1602显示屏上显示。
  • 新建工程,在工程目录下新建Functions、Objects、Listings文件夹,将LCD1602模块放入Functions文件夹中。设置生成.hex文件并将其存放到Objects中,将.lst文件存放到Listings中。新建main.c文件,添加LCD1602.c到工程中,并设置其引入路径。
5.1、DS1302模块化
  • 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
    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<<i);DS1302_SCLK=1; // 时序,一个上升下降沿读取IO口的一位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	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<<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}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); // 十进制转BCDDS1302_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 <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(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 <REGX52.H>
      #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){ // 5msT0Count=0;TimeSetFlashFlag=!TimeSetFlashFlag;}
      }

十二、蜂鸣器

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

1、蜂鸣器原理图

  • 因为单片机的IO口不能直接驱动蜂鸣器,所以通过ULN2003D辅助控制。

2、蜂鸣器播放提示音

  • 内容:按下独立按键,数码管显示对应的按键键码,蜂鸣器在松开按键时发声。
  • 新建工程,在工程目录下新建Functions、Objects、Listings文件夹,将延时函数、独立按键和数码管模块放入Functions文件夹中。设置生成.hex文件并将其存放到Objects中,将.lst文件存放到Listings中。新建main.c文件,添加Delay.c、Key.c和Nixie.c到工程中,并设置其引入路径。
  • 编写蜂鸣器模块。
    • Buzzer.c
    • #include <REGX52.H>
      #include <INTRINS.H>
      // 蜂鸣器端口
      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;i<ms*2;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 <REGX52.H>
      #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); // 蜂鸣器发声100msNixie_Static(1,KeyNum); // 显示对应按键的键码}}
      }
  • 按下独立按键松开时,蜂鸣器以1000HZ频率发声100毫秒。

3、蜂鸣器播放音乐

  • 内容:蜂鸣器播放小星星。
  • 新建工程,在工程目录下新建Functions、Objects、Listings文件夹,将延时函数、定时器0模块放入Functions文件夹中。设置生成.hex文件并将其存放到Objects中,将.lst文件存放到Listings中。新建main.c文件,添加Delay.c和Timer0.c到工程中,并设置其引入路径。
  • 乐谱:
  • 编写main.c文件夹
    • #include <REGX52.H>
      #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执行一次,500HZif(FreqTable[FreqSelect]){TL0 = FreqTable[FreqSelect]%256;		//设置定时初值TH0 = FreqTable[FreqSelect]/256;		//设置定时初值Buzzer=!Buzzer;}
      }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/44474.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Spring 6.0官方文档示例(24): replace-method的用法

一、原始bean定义 package cn.edu.tju.study.service.anno.domain;public class MyValueCalculator {public String computeValue(String input) {return "you inputted: " input;}// some other methods... }二、replace bean定义 package cn.edu.tju.study.serv…

LED驱动型IC芯片的原理介绍

一、LED驱动器是什么 LED驱动器&#xff08;LED Driver&#xff09;&#xff0c;是指驱动LED发光或LED模块组件正常工作的电源调整电子器件。由于LED PN结的导通特性决定&#xff0c;它能适应的电源电压和电流变动范围十分狭窄&#xff0c;稍许偏离就可能无法点亮LED或者发光效…

设计模式十七:迭代器模式(Iterator Pattern)

迭代器模式&#xff08;Iterator Pattern&#xff09;是一种行为型设计模式&#xff0c;它提供了一种访问聚合对象&#xff08;例如列表、集合、数组等&#xff09;中各个元素的方法&#xff0c;而无需暴露其内部表示。迭代器模式将遍历元素和访问元素的责任分离开来&#xff0…

04_Redis与mysql数据双写一致性案例

04——redis与mysql数据双写一致性 一、canal 是什么 canal[ka’nel,中文翻译为水道/管道/沟渠/运河&#xff0c;主要用途是用于MySQL数据库增量日志数据的订阅、消费和解析&#xff0c;是阿里巴巴开发并开源的,采用Java语言开发&#xff1b; 历史背景是早期阿里巴巴因为杭州和…

无涯教程-TensorFlow - XOR实现

在本章中&#xff0c;无涯教程将学习使用TensorFlow的XOR实现&#xff0c;在TensorFlow中开始XOR实施之前&#xff0c;看一下XOR表值。这将帮助了解加密和解密过程。 A B A XOR B 0 0 0 0 1 1 1 0 1 1 1 0 XOR密码加密方法基本上用于加密&#xff0c;即通过生成与适当密钥匹配…

W6100-EVB-PICO 做UDP Client 进行数据回环测试(八)

前言 上一章我们用开发板作为UDP Server进行数据回环测试&#xff0c;本章我们让我们的开发板作为UDP Client进行数据回环测试。 连接方式 使开发板和我们的电脑处于同一网段&#xff1a; 开发板通过交叉线直连主机开发板和主机都接在路由器LAN口 测试工具 网路调试工具&a…

对容器、虚拟机和 Docker 的初学者友好介绍

一、说明 如果你是一个程序员或技术人员&#xff0c;你可能至少听说过Docker&#xff1a;一个有用的工具&#xff0c;用于在“容器”中打包&#xff0c;运输和运行应用程序。很难不这样做&#xff0c;这些天它得到了所有的关注 - 来自开发人员和系统管理员。即使是像谷歌、VMwa…

leetcode 125.验证回文串

⭐️ 题目描述 &#x1f31f; leetcode链接&#xff1a;https://leetcode.cn/problems/valid-palindrome/ 思路&#xff1a; 这道题只判断字符串中的字母与数字是否是回文。虽然小写大写字母可以互相转换&#xff0c;但是里面是含有数字字符的&#xff0c;所以先统一&#xff…

数据结构——B-树、B+树、B*树

一、B-树 1. B-树概念 B树是一种适合外查找的、平衡的多叉树。一棵m阶&#xff08;m>2&#xff09;的B树&#xff0c;是一棵平衡的M路平衡搜索树&#xff0c;它可以是空树或满足以下性质&#xff1a; &#xff08;1&#xff09;根节点至少有两个孩子。 &#xff08;2&#…

LVS+Keepalived

Keepalived概述&#xff1a; keepalived软件 就是通过vrrp协议实现高可用功能 vrrp通信原理&#xff1a; vrrp就是虚拟路由冗余协议&#xff0c;它的出现就是为了解决静态路由的单点故障vrrp是通过一种竞选的一种协议机制将路由交给某台vrrp路由器vrrp用ip多播的方式【多播地…

亿级短视频,如何架构?

说在前面 在尼恩的&#xff08;50&#xff09;读者社群中&#xff0c;经常指导大家面试架构&#xff0c;拿高端offer。 前几天&#xff0c;指导一个年薪100W小伙伴&#xff0c;拿到字节面试邀请。 遇到一个 非常、非常高频的一个面试题&#xff0c;但是很不好回答&#xff0…

Java 日期格式(yyyy-MM-dd HH:mm:ss SSS)

常用格式为&#xff1a;yyyy-MM-dd HH:mm:ss 以 2019-12-31 06:07:59:666 时间为例&#xff1a; 字符 含义 Example y&#xff08;小写的y&#xff09; 年 yyyy---->2019 M&#xff08;大写的M&#xff09; 月 MM---->12 d&#xff08;小写的d&#xff09; 一…

速通蓝桥杯嵌入式省一教程:(五)用按键和屏幕实现嵌入式交互系统

一个完整的嵌入式系统&#xff0c;包括任务执行部分和人机交互部分。在前四节中&#xff0c;我们已经讲解了LED、LCD和按键&#xff0c;用这三者就能够实现一个人机交互系统&#xff0c;也即搭建整个嵌入式系统的框架。在后续&#xff0c;只要将各个功能加入到这个交互系统中&a…

单体版ruoyi表格绑定按钮

先需要在表格中添加一个按钮&#xff0c;可以快速操作这条数据的某个0/1状态 表格的列 editFlag是检验用户是否有操作的权限 var editFlag [[${permission.hasPermi(pipayshop:itemCommoidtyInfoCategoryTop:edit)}]]; 绑定状态条 /* 用户状态显示 */function statusTools(…

分布式图数据库 NebulaGraph v3.6.0 正式发布,强化全文索引能力

本次 v3.6.0 版本&#xff0c;主要强化全文索引能力&#xff0c;以及优化部分场景下的 MATCH 性能。 强化 强化增强全文索引功能&#xff0c;具体 pr 参见&#xff1a;#5567、#5575、#5577、#5580、#5584、#5587 优化 支持使用 MATCH 子句检索 VID 或属性索引时使用变量&am…

3d max省时插件CG MAGIC功能中的材质参数可一键优化!

渲染的最终结果就是为了让渲染效果更加真实的体现。 对于一些操作上&#xff0c;可能还是费些时间&#xff0c;VRay可以说是在给材质做加法的路上越走越远&#xff0c;透明度、凹凸、反射等等参数细节越做越多。 对于材质参数调节的重要性大家都心里有数的。 VRay材质系统的每…

IDEA源码下载失败问题

1.IDEA下载源码报 java.lang.RuntimeException: Cannot reconnect java.lang.RuntimeException: Cannot reconnect 异常通常表示无法重新连接到资源或服务。这种情况可能出现在尝试重新连接到数据库、网络套接字或任何需要连接的资源时。 以下是解决此异常的几种可能方法&…

语言基础2 矩阵和数组

语言基础2 矩阵和数组 矩阵和数组是matlab中信息和数据的基本表示形式 可以创建常用的数组和网格 合并现有的数组 操作数组的形状和内容 以及使用索引访问数组元素 用到的函数列表如下 一 创建 串联和扩展矩阵 矩阵时按行和列排列的数据元素的二维数据元素的二维矩…

Pytorch的torch.utils.data中Dataset以及DataLoader等详解

在我们进行深度学习的过程中&#xff0c;不免要用到数据集&#xff0c;那么数据集是如何加载到我们的模型中进行训练的呢&#xff1f;以往我们大多数初学者肯定都是拿网上的代码直接用&#xff0c;但是它底层的原理到底是什么还是不太清楚。所以今天就从内置的Dataset函数和自定…

c++通过gsop调用基于https的webservice接口总结

ww哦步骤&#xff1a; 第一步&#xff1a;生成头文件 webservice接口一般会有一个对外接口文档。比如&#xff1a;http://www.webxml.com.cn/WebServices/WeatherWebService.asmx?WSDL 问号后面的参数表示WSDL文档&#xff0c;是一个XML文档&#xff0c;看不懂配置没关系&a…