- 嵌入式入门教学汇总:
- 嵌入式入门教学——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会马上刷新为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 <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会马上刷新为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 <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; // 初始化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 <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.000MHz 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 <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(){ // 初始化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); } }
- 编译下载程序到单片机,显示如下。
-
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(){ // 初始化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格式,将生成的字符串新建为数组。
-
- 编写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); // 十进制转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 <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){ // 5ms T0Count=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); // 蜂鸣器发声100ms Nixie_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执行一次,500HZ if(FreqTable[FreqSelect]){ TL0 = FreqTable[FreqSelect]%256; //设置定时初值 TH0 = FreqTable[FreqSelect]/256; //设置定时初值 Buzzer=!Buzzer; } }
-