- 嵌入式入门教学汇总:
- 嵌入式入门教学——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;} }
-