嵌入式入门教学——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.rhkb.cn/news/100671.html

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

相关文章

【LeetCode】224. 基本计算器

224. 基本计算器&#xff08;困难&#xff09; 方法&#xff1a;双栈解法 思路 我们可以使用两个栈 nums 和 ops 。 nums &#xff1a; 存放所有的数字ops &#xff1a;存放所有的数字以外的操作&#xff0c;/- 也看做是一种操作 然后从前往后做&#xff0c;对遍历到的字符做…

如何仿写简易tomcat 实现思路+代码详细讲解

仿写之前&#xff0c;我们要搞清楚都要用到哪些技术 自定义注解&#xff0c;比如Tomcat使用的是Servlet&#xff0c;我们可以定义一个自己的MyServlet构造请求体和返回体&#xff0c;比如tomcat使用HttpRequest&#xff0c;我们可以自己定义myHttpRequestjava去遍历一个指定目…

用pytorch实现AlexNet

AlexNet经典网络由Alex Krizhevsky、Hinton等人在2012年提出&#xff0c;发表在NIPS&#xff0c;论文名为《ImageNet Classification with Deep Convolutional Neural Networks》&#xff0c;论文见&#xff1a;http://www.cs.toronto.edu/~hinton/absps/imagenet.pdf &#xf…

[Mac软件]AutoCAD 2024 for Mac(cad2024) v2024.3.61.182中文版支持M1/M2/intel

下载地址&#xff1a;前往黑果魏叔官网 AutoCAD是一款计算机辅助设计&#xff08;CAD&#xff09;软件&#xff0c;目前已经成为全球最受欢迎的CAD软件之一。它可以在二维和三维空间中创建精确的技术绘图&#xff0c;并且可以应用于各种行业&#xff0c;如建筑、土木工程、机械…

NLP的tokenization

GPT3.5的tokenization流程如上图所示&#xff0c;以下是chatGPT对BPE算法的解释&#xff1a; BPE&#xff08;Byte Pair Encoding&#xff09;编码算法是一种基于统计的无监督分词方法&#xff0c;用于将文本分解为子词单元。它的原理如下&#xff1a; 1. 初始化&#xff1a;将…

C++信息学奥赛1121:计算矩阵边缘元素之和

题解&#xff1a;i0 or j0 or in-1 or jm-1 or in-1 or jm-1 代码&#xff1a; #include<iostream> // 包含输入输出流库 #include<cmath> // 包含数学函数库 using namespace std; // 使用标准命名空间int main() {int n,m;cin>>n>>m; // 输入…

【STM32】串口通信乱码(认识系统时钟来源)

使用 stm32f407 与电脑主机进行串口通信时&#xff0c;串口助手打印乱码&#xff0c;主要从以下方面进行排查&#xff1a; 检查传输协议设置是否一致&#xff08;波特率、数据位、停止位、校验位&#xff09;检查MCU外部晶振频率是否和库函数设置的一致 最终发现是外部晶振频…

因果推断(五)基于谷歌框架Causal Impact的因果推断

因果推断&#xff08;五&#xff09;基于谷歌框架Causal Impact的因果推断 除了传统的因果推断外&#xff0c;还有一些机器学习框架可以使用&#xff0c;本文介绍来自谷歌框架的Causal Impact。该方法基于合成控制法的原理&#xff0c;利用多个对照组数据来构建贝叶斯结构时间…

docker 安装 redis

目录 1、下载镜像文件 2、创建实例并启动 3、使用 redis 镜像执行 redis-cli 命令连接 4、redis持久化操作 5、然后按照第3点&#xff0c;再试一试&#xff0c;看看redis持久化是否配置成功。 6、最后与redis可视化工具测试连接 大家先 su root&#xff0c;这让输入命令就…

Redis系列(四):哨兵机制详解

首发博客地址 https://blog.zysicyj.top/ 前面我们说过&#xff0c;redis采用了读写分离的方式实现高可靠。后面我们说了&#xff0c;为了防止主节点压力过大&#xff0c;优化成了主-从-从模式 思考一个问题&#xff0c;主节点此时挂了怎么办 这里主从模式下涉及到的几个问题&a…

opencv进阶07-支持向量机cv2.ml.SVM_create()简介及示例

支持向量机&#xff08;Support Vector Machine&#xff0c;SVM&#xff09;是一种二分类模型&#xff0c;目标是寻找一个标准&#xff08;称为超平面&#xff09;对样本数据进行分割&#xff0c;分割的原则是确保分类最优化&#xff08;类别之间的间隔最大&#xff09;。当数据…

opencv-手势识别

# HandTrackingModule.py import cv2 import mediapipe as mpclass HandDetector:"""使用mediapipe库查找手。导出地标像素格式。添加了额外的功能。如查找方式&#xff0c;许多手指向上或两个手指之间的距离。而且提供找到的手的边界框信息。"""…

vscode 安装勾选项解释

1、通过code 打开“操作添加到windows资源管理器文件上下文菜单 &#xff1a;把这个两个勾选上&#xff0c;可以对文件使用鼠标右键&#xff0c;选择VSCode 打开。 2、将code注册为受支持的文件类型的编辑器&#xff1a;不建议勾选&#xff0c;这样会默认使用VSCode打开支持的相…

【Java从0到1学习】10 Java常用类汇总

1. System类 System类对读者来说并不陌生&#xff0c;因为在之前所学知识中&#xff0c;需要打印结果时&#xff0c;使用的都是“System.out.println();”语句&#xff0c;这句代码中就使用了System类。System类定义了一些与系统相关的属性和方法&#xff0c;它所提供的属性和…

PyCharm PyQt5 开发环境搭建

环境 python&#xff1a;3.6.x PyCharm&#xff1a;PyCharm 2019.3.5 (Community Edition) 安装PyQT5 pip install PyQt5 -i https://pypi.douban.com/simplepip install PyQt5-tools -i https://pypi.douban.com/simple配置PyCharm PyQtUIC Program &#xff1a;D:\Pytho…

Unity解决:3D开发模式第三人称视角 WASD控制角色移动旋转 使用InputSystem

Unity版本&#xff1a;2019.2.3f1 目录 安装InputSystem 1&#xff1a;创建InputHander.cs脚本 挂载到Player物体上 获取键盘输入WADS 2.创建PlayerLocomotion.cs挂载到Player物体上&#xff0c;控制物体移动转向 安装InputSystem 菜单栏/Window/Package Manager/Input Syst…

网络编程(三次握手、四次挥手)

一、Wireshark 窗口介绍 二、 wireshark与对应的OSI七层模型 服务器和客户端的代码不能都运行在ubuntu&#xff0c;因为wireshark抓的是流经真实网卡的数据包。 若将服务器客户端都运行在ubuntu&#xff0c;数据直接经过虚拟网卡通信&#xff0c;而不会经过真实网卡。 三、以太…

监控 FTP 服务器

文件传输协议 &#xff08;FTP&#xff09; 用于在 TCP/IP 网络中的服务器和客户端之间传输文件&#xff0c;它是一种标准协议&#xff0c;广泛用于在各个垂直行业的组织之间从集中位置存储和分发数据。FTP协议的其他一些安全版本如下&#xff1a; SSH 文件传输协议 &#xff…

问道管理:股票印花税是多少?印花税降低有何影响?

股票印花税&#xff0c;是指对证券商场上买卖、承继、赠与所确立的股权转让根据&#xff0c;按买卖额纳税。那么&#xff0c;我国股票印花税是多少&#xff1f;印花税下降有何影响&#xff1f;问道管理为我们预备了相关内容&#xff0c;以供参考。 股票印花税是多少&#xff1f…

如何让智能搜索引擎更灵活、更高效?

随着互联网的发展和普及&#xff0c;搜索引擎已经成为人们获取信息、解决问题的主要工具之一。 然而&#xff0c;传统的搜索引擎在面对大数据时&#xff0c;往往存在着搜索效率低下、搜索结果精准度不够等问题。 为了解决这些问题&#xff0c;越来越多的企业开始采用智能搜索技…