目录
系列文章目录
前言
你还在为送什么生日礼物给女神而烦恼吗?不用多想了,快试试这个用51单片机制作的有音乐播放功能的流水灯吧,相信一定能打动她的芳心。爱她就TMD(甜蜜地)给她一个TMD(甜蜜的)惊喜!
本作品所用单片机芯片是:STC89C52RC,无源蜂鸣器播放音乐有:《生日快乐》、《祝寿歌》、《生日祝福歌》、《小幸运》
播放效果:B站搜索“甘腾胜”或“gantengsheng”查看。
源代码下载:B站对应视频的简介有下载链接。
一、效果展示
二、制作背景
时隔十年,突然又想玩一下单片机。
七月份,通过看B站江科大(江协科技)的51单片机入门教程复习单片机知识和C语言。
八月份,想着老婆的生日快到了(十一月中旬),为什么不用单片机做一个特别的礼物给她一个甜蜜的惊喜呢?决定做一个心形流水灯。
有了这个想法就开干!用几十个LED在一块较大的洞洞板上摆出心形,内部再摆一个字。后来想了一下,这样会比较单调,通过江科大的视频学习了如何用蜂鸣器播放音乐,能不能播放流水灯的同时播放音乐呢?我简单理论分析了一下,是可行的,并且用开发板测试了一下,发现没问题,就再焊接上无源蜂鸣器、三极管和电阻。
因为要给她一个惊喜,所以不能在家里做,只能在宿舍牺牲休息时间进行设计、焊接、编程、调试等。经过一个多月的时间完成了(其实不用那么久,用这么长的时间是因为每天制作的时间有限,有时隔几天才继续制作),提前两个多月做好了。
大家想制作,如果没有C语言和单片机的基础的话,建议在B站看江协科技的51单片机入门教程学习一遍,可以不用全部学习,学习这几方面就差不多了:能随心所欲地实现8个LED流水灯(心形流水灯只是要控制的灯变多了而已)、数码管的扫描显示原理、定时器的功能和使用、蜂鸣器播放音乐。如果想做呼吸灯效果,还要了解PWM。没有烧录器的话,还要上网买一个烧录器。
三、接线图
1、总体布局
2、单片机最小系统
3、LED驱动
4、蜂鸣器驱动
5、电池接线
四、原理分析
1、LED的控制
总共有56个灯,如果一个IO口控制一个LED的话,IO口的数量还差很多,如果通过74HC595芯片级联扩展IO口的话,虽然占用IO口很少,但要焊接很多个595芯片,很占空间,也需要焊接很多线,也影响美观。
数码管一位有8个LED,驱动八位数码管是用了扫描的方法,即某一时刻让其中一位数码管导通,按顺序一个一个循环导通显示,当扫描速度比较快的时候,由于人眼的视觉暂留(当物体的影像消失后,人眼仍然会保留该影像一段时间),我们感觉不到LED在闪烁,一般是2ms切换到下一位数码管导通。
LED的显示也可以用扫描的方法,这样,只需要几个三极管和十几个电阻就可以了。由于单片机是弱上拉和强下拉,所以一般用共阳接法,一般用PNP三极管(低电平导通)。用定时器定时,每隔2ms切换到下一组LED导通。定义一个全局数组变量,定时器中断的时候,中断函数中控制P1切换到下一组导通,同时将数组中对应数据赋值给P2口,我们显示流水灯的时候改变数组中的数据就行了,剩下的交给定时器扫描。跟数码管扫描显示一样,要进行消影处理。
由于用扫描的方法点亮LED,一个扫描周期只有1/7的时间是通电的,所以为了提高亮度,LED的限流电阻要小一点,用100欧姆。
2、音乐的播放
用无源蜂鸣器播放音乐,无非是要控制两个方面,一个是音符的频率,一个是音符的时长。
江协科技的教程中,例程是用一个定时器通过改变重装值来控制音符的频率,然后在主循环中控制音符的时长。如果只是用蜂鸣器播放音乐的话是没有问题的,我们如果在播放音乐的同时还要控制流水灯,就会出现问题,因为播放音乐的延时会造成主函数出现阻塞,影响LED的控制,LED的控制也会影响播放音乐的延时,相互影响。
解决方案是用两个定时器,定时器0(Timer0)负责LED的扫描、控制音符的时长、设置定时器1的重装值(即设定音符的频率)、控制音符之间短暂的间隔(5ms),定时器1(Timer1)负责控制翻转无源蜂鸣器IO口的电平,使蜂鸣器按设置的频率响。
要注意的是,定时器1的优先级要比定时器0的高(即要设置为PT0=0,PT1=1),否则蜂鸣器发声会受到影响。
五、各模块代码
1、延时函数
h文件
#ifndef __DELAY_H__
#define __DELAY_H__
void Delay(unsigned int xms);
#endif
c文件
/**
* @brief 延时函数,晶振@12.0000MHz时,输入参数延时的单位是1ms
* @param xms 延时的时间,范围:0~65535
* @retval 无
*/
void Delay(unsigned int xms)
{
unsigned char i, j;
while(xms)
{
i = 2;
j = 239;
do
{
while (--j);
} while (--i);
xms--;
}
}
2、乐谱头文件
h文件
#ifndef __MUSIC_H__
#define __MUSIC_H__
//播放速度,值为十六分音符的时长(ms)
unsigned char code MusicSpeed[]={ //“code”:不占RAM空间
150, //《生日快乐》速度,一分钟100个四分音符
125, //《祝寿歌》速度,一分钟120个四分音符
125, //《生日祝福歌》速度,一分钟120个四分音符
188, //《小幸运》速度,一分钟80个四分音符
};
//音符与索引对应表,P:休止符,L:低音,M:中音,H:高音,下划线:升半音符号#
#define P 0
#define L1 1
#define L1_ 2
#define L2 3
#define L2_ 4
#define L3 5
#define L4 6
#define L4_ 7
#define L5 8
#define L5_ 9
#define L6 10
#define L6_ 11
#define L7 12
#define M1 13
#define M1_ 14
#define M2 15
#define M2_ 16
#define M3 17
#define M4 18
#define M4_ 19
#define M5 20
#define M5_ 21
#define M6 22
#define M6_ 23
#define M7 24
#define H1 25
#define H1_ 26
#define H2 27
#define H2_ 28
#define H3 29
#define H4 30
#define H4_ 31
#define H5 32
#define H5_ 33
#define H6 34
#define H6_ 35
#define H7 36
//索引与频率对照表
unsigned int code FreqTable[]={
0, //休止符,即进入定时器1中断程序后之后不赋初值,则从TH=0和TL=0开始计数,并且蜂鸣器端口不翻转
63628,63731,63835,63928,64021,64103,64185,64260,64331,64400,64463,64524,
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 code Music[]={
//音符,时值,
//即频率,十六分音符时长的倍数
0, //第0个数据为0,目的是主函数中开启中断后,能立刻进入定时器0的中断函数进行频率和时值的赋值
//生日快乐歌
P,2, //第1个数据和第2个数据,加多一个休止符,用来控制让心形先亮
M2,2, //原本简谱上是G调,我自己改了调号,升高点音调
M2,2,
M3,4,
M2,4,
M5,4,
M4_,4+4, //祝你生日快乐
M2,2,
M2,2,
M3,4,
M2,4,
M6,4,
M5,4+4, //祝你生日快乐
M2,2,
M2,2,
H2,4,
M7,4,
M5,4,
M4_,4,
M3,4, //祝你生日快乐
H1,2,
H1,2,
M7,4,
M5,4,
M6,4,
M5,4+4, //祝你生日快乐
0xF0,0xF0, //切换播放速度标记(也可以设置为其他的值,不要跟音符索引和时值重复就行了)
P,2,
//祝寿歌
M6,2,
M6,4,
M5,2,
M6,2,
M5,2,
M3,4, //恭祝你福寿
H1,4,
H2,2,
H1,2,
M5,4+4, //与天齐
M5,2,
M3,4,
M5,2,
M6,2,
M2,4,
M4,2, //庆贺你生辰快
M3,12, //乐
M2,2,
M2,4,
M6,2,
M5,2,
M6,2,
M3,4, //年年都有今日
M5,2,
M5,4,
M7,2,
M6,2,
M7,2,
M6,4, //岁岁都有今朝
M6,4,
M5,2,
M6,2,
M5,2,
M3,8, //恭喜你
H3,4,
H2,2,
H3,2,
H1,16, //恭喜你
P,8, //停顿一下再播放下一首
0xF0,0xF0, //切换播放速度标记
//生日祝福歌(海底捞)
M3,2,
M5,2,
M5,2,
M6,2,
M5,4,
P,4, //今天你生日
M2,2,
M5,2,
M5,2,
M6,2,
M5,4,
P,4, //送上我祝福
M1,2,
M1,2,
M1,2,
M1,2,
M1,4,
H1,4, //特别的日子有
M7,2,
M5,2,
M5,2,
M6,2,
M5,4+4, //灿烂的笑容
M6,2,
H1,2,
H1,2,
H2,2,
H1,4,
H1,2,
M6,2, //我们来相聚,带着
M5,2,
M6,2,
M6,2,
M5,2,
M3,8, //满满的关爱
M4,2,
M4,4,
M3,2,
M4,2,
M3,4,
M1,2, //祝福你好运常
M2,8,
P,8, //伴
M3,2,
M5,2,
M5,2,
M6,2,
M5,4,
P,4, //点燃了蜡烛
M2,2,
M5,2,
M5,2,
M6,2,
M5,4,
P,4, //许下你心愿
M1,2,
M1,2,
M1,2,
M1,2,
M1,4,
H1,2,
H1,2, //未来的日子每个
M7,2,
M5,2,
M5,2,
M6,2,
M5,4+4, //梦想都实现
M6,2,
H1,2,
H1,2,
H2,2,
H1,4,
H1,2,
M6,2, //唱首祝福歌,我们
M5,2,
H3,2,
H3,2,
H2,2,
H1,4+4, //相亲又相爱
M6,2,
M6,4,
M5,2,
M6,2,
M6,4,
H1,2, //祝福你健康平
H2,16+8, //安
P,4,
P,2,
M5,2, //对
H3,4,
H1,3,
H2,1,
H3,4,
H1,4, //所有的烦恼
H2,2,
P,2,
M5,2,
P,2,
M5,4,
P,2,
M3,2, //说bye bye,对
H1,4,
M6,3,
M7,1,
H1,2,
M6,2,
M6,4, //所有的快乐
M7,2,
P,2,
H5,2,
P,2,
H5,4+4, //说hi hi
H4,4,
H4,3,
H3,1,
H2,4,
H3,3,
H4,1, //亲爱的亲爱的
H3,2,
P,2,
H2,2,
P,2,
H1,2,
M6,2,
M6,4, //生日快乐
H1,4,
H1,3,
M6,1,
H1,4,
H2,2,
H3,2, //每一天都精
H2,4+4+4, //彩
P,2,
M5,2, //看
H3,4,
H1,3,
H2,1,
H3,4,
H1,2,
H1,2, //幸福的花儿为
H2,2,
P,2,
M5,2,
P,2,
M5,4,
P,2,
M3,2, //你盛开,听
H1,4,
M6,3,
M7,1,
H1,4,
M6,3,
M6,1, //美妙的音乐为
M7,2,
P,2,
H5,2,
P,2,
H5,4+4, //你喝彩
H4,4,
H4,3,
H3,1,
H2,4,
H3,3,
H4,1, //亲爱的亲爱的
H3,2,
P,2,
H2,2,
P,2,
H1,2,
M6,2,
M6,4, //生日快乐
H1,4,
H1,2,
M6,2,
H1,4,
H2,2,
H3,3, //祝福你幸福永
H2,4,
P,2,
M7,2,
H1,4,
H1,2,
H2,2, //远,幸福永
H1,16+4, //远
P,8,
0xF0,0xF0, //切换播放速度标记
//小幸运
P,0,
M3,2,
M3,2,
M5,2,
M5,2,
H1,2,
H1,2,
M7,2, //我听见雨滴落在
M7,2,
M6,2,
M3,2,
M6,10, //青青草地
P,2,
M6,2,
M6,2,
M7,2,
M7,2,
H3,2,
H3,2,
M7,2, //我听见远方下课
M7,2,
M5,2,
M3,2,
M5,10, //钟声响起
P,2,
M3,2,
M3,2,
M5,2,
M5,2,
H1,2,
H1,2,
M7,2, //可是我没有听见
M7,2,
M6,2,
M3,2,
M6,6,
M6,2,
M7,2+2,
M6,2,
M7,2,
H3,4,
H2,4,
H1,2+16, //你的声音,认真呼唤我姓名
P,0,
M3,2,
M3,2,
M5,2,
M5,2,
H1,2,
H1,2,
M7,2, //爱上你的时候还
M7,2,
M6,2,
M3,2,
M6,10, //不懂感情
P,2,
M6,2,
M6,2,
M7,2,
M7,2,
H3,2,
H3,2,
M7,2, //离别后才觉得
M7,2,
M5,2,
M3,2,
M5,10, //刻骨铭心
P,2,
M3,2,
M3,2,
M5,2,
M5,2,
H1,2,
H1,2,
M7,2, //为什么没有发现
M7,2,
M6,2,
M3,2,
M6,6,
M6,2,
M7,2+2, //遇见了你,是生
M6,2,
M7,2,
H3,2,
H3,2,
H2,4,
H1,2+8, //命最好的事
H3,2,
H2,1,
H1,3,
M7,2, //也许当时
M6,2,
M6,2,
M6,2,
M6,2,
M6,2,
H3,2,
H2,2,
H2,10, //忙着微笑和哭泣
H2,2,
H1,1,
M7,3,
M6,2, //忙着追逐
M5,2,
M5,2,
M5,2,
M3,2,
M5,2,
H2,2,
H1,2,
H1,2+4, //天空中的流星
H1,2,
H1,2,
M5,2,
M5,2,
M1,2, //人理所当然
M3,4,
M2,2,
M6,8,
M6,2+2, //的忘记,是
M6,2,
M6,2,
M6,1,
H1,3,
M6,2,
H1,2,
M6,2, //谁风里雨里一直
H1,2,
H1,2,
H1,2,
H1,2,
H3,2,
H2,4,
H2,2+6, //默默守护在原地
M5,2,
H3,2,
H2,1,
H1,3,
H2,2, //原来你是我
H3,2,
M5,2,
H2,2,
H3,4,
M5,2,
H2,2,
H3,2+2, //最想留住的幸运
H2,2,
H2,2,
H3,1,
H4,3,
H3,2,
H2,2,
M7,2, //原来我们和爱情
H1,2,
M3,2,
M6,2,
H1,4,
M3,2,
M6,2,
M7,2+2, //曾经靠得那么近
M7,2,
M7,2,
H3,1,
H5,3,
H3,2,
H1,2,
M7,2, //那为我对抗世界
M6,2,
H4,1,
H4,5,
P,2,
H5,2,
H4,2,
H3,2, //的决定,那陪我
M5,2,
H3,1,
H3,5,
P,2,
H4,2,
H3,2,
H1,2, //淋的雨,一幕幕
// H4_,2, //疑似谱有问题,改成中音4升半调
M4_,2,
H2,1,
H2,5,
P,2,
H2,2,
H1,2,
H3,2+2, //都是你,一尘不
H2,2,
H1,2,
H3,4,
H2,2,
H1,2,
H1,2, //染的真心
H3,2,
M5,2,
H2,2,
H3,4,
M5,2,
H2,2,
H3,2+2, //与你相遇好幸运
H2,2,
H2,2,
H3,1,
H4,3,
H3,2,
H2,2,
M7,2, //可我已失去为你
H1,2,
M3,2,
M6,2,
H1,4,
M3,2,
M6,2,
M7,2+2, //泪流满面的权利
M7,2,
M7,2,
H3,1,
H5,3,
H3,2,
H1,2,
M7,2, //但愿在我看不到
M6,2,
H4,1,
H4,5,
P,2,
H5,2,
H4,2,
H3,2, //的天际,你张开
M5,2,
H3,1,
H3,5,
P,2,
H4,2,
H3,2,
H1,2, //了双翼,遇见你
// H4_,2, //疑似谱有问题,改成中音4升半调
M4_,2,
H2,1,
H2,13, //的注定
P,2,
H3,2,
H1,2,
H1,2,
H3,3,
H2,3,
H1,18, //他会有多幸运
0xF0,0xF0, //最后再加两个切换播放速度标记,在定时器中断函数中让MusicSpeedSelect自增并清零
P,4, //短暂停顿一下再停止播放
0xFF,0xFF, //终止标记
};
#endif
3、定时器0
h文件
#ifndef __TIMER0_H__
#define __TIMER0_H__
void Timer0_Init(void);
#endif
c文件
#include <REGX52.H>
/**
* @brief 定时器0初始化,1毫秒@12.000MHz
* @param 无
* @retval 无
*/
void Timer0_Init(void)
{
TMOD&=0xF0; //设置定时器模式(高四位不变,低四位清零)
TMOD|=0x01; //设置定时器模式(通过低四位设为“定时器0工作方式1”的模式)
TL0=0x18; //设置定时初值,定时1ms
TH0=0xFC; //设置定时初值,定时1ms
TF0=0; //清除TF0标志
TR0=1; //定时器0开始计时
ET0=1; //打开定时器0中断允许
EA=1; //打开总中断
PT0=0; //当PT0=0时,定时器0为低优先级,当PT0=1时,定时器0为高优先级
}
/*定时器中断函数模板
void Timer0_Routine() interrupt 1 //定时器0中断函数
{
static unsigned int T0Count; //定义静态变量
TL0=0x18; //设置定时初值,定时1ms
TH0=0xFC; //设置定时初值,定时1ms
T0Count++;
if(T0Count>=1000)
{
T0Count=0;
}
}
*/
4、定时器1
h文件
#ifndef __TIMER1_H__
#define __TIMER1_H__
void Timer1_Init(void);
#endif
c文件
#include <REGX52.H>
/**
* @brief 定时器1初始化,1毫秒@12.000MHz
* @param 无
* @retval 无
*/
void Timer1_Init(void)
{
TMOD&=0x0F; //设置定时器模式(低四位不变,高四位清零)
TMOD|=0x10; //设置定时器模式(通过高四位设为“定时器1工作方式1”的模式)
TL1=0x18; //设置定时初值,定时1ms
TH1=0xFC; //设置定时初值,定时1ms
TF1=0; //清除TF1标志
TR1=1; //定时器1开始计时
ET1=1; //打开定时器1中断允许
EA=1; //打开总中断
PT1=1; //当PT1=0时,定时器1为低优先级,当PT1=1时,定时器1为高优先级
//定时器1的优先级要比定时器0的高,否则发声不正常
//即要设置成:PT0=0;PT1=1;
}
/*定时器中断函数模板
void Timer1_Routine() interrupt 3 //定时器1中断函数
{
static unsigned int T1Count; //定义静态变量
TL1=0x18; //设置定时初值,定时1ms
TH1=0xFC; //设置定时初值,定时1ms
T1Count++;
if(T1Count>=1000)
{
T1Count=0;
}
}
*/
六、主函数
main.c
/*
by甘腾胜@20240915
效果:B站搜索“甘腾胜”或“gantengsheng”查看
单片机:STC89C52RC
晶振:12T@12.0000MHz
*/
#include <REGX52.H> //包含头文件(安装目录下)
#include <INTRINS.H> //用来控制循环左移或右移
#include "Delay.h" //包含头文件(工程目录下)
#include "Timer0.h" //包含头文件相当于把头文件中的内容插入此处
#include "Timer1.h"
#include "Music.h" //将乐谱放在头文件,防止其在main.c文件中占太多空间
//蜂鸣器端口定义
sbit Buzzer=P3^7; //随便用一个端口控制蜂鸣器
//宏定义
#define LEDSpeed 125 //流水灯速度,值越小,速度越快
//定义全局变量
unsigned char MusicSpeedSelect; //音乐速度选择变量
unsigned char MusicOffFlag; //停止播放音乐的标志,1:停止,0:不停止
unsigned char FreqSelect; //频率选择变量
unsigned char TimeIntervalFlag; //相邻音符之间短暂间隔的标志,1:到了短暂间隔的时刻,0:未到短暂间隔的时刻
unsigned char WordFlashFlag; //心形中间的字闪烁的标志
unsigned int MusicSelect; //音乐选择变量,用来选择音符的频率和时长,因为音符较多,所以用整型
unsigned char code Bit[]={0x7F,0xBF,0xDF,0xEF,0xF7,0xFB,0xFD,}; //扫描LED的位码,按顺序扫描LED,0接通,1断开
unsigned char LED[]={0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,}; //LED显示的缓存数据,56个灯共7个字节,0点亮,1熄灭
//显示动画直接操作缓存数据就行了
//阴码(1为点亮),从上往下或从下往上熄灭或点亮(要取反)
unsigned char code LEDTable1[][7]={ //字从上往下点亮或从上往下熄灭或从下往上点亮或从下往上熄灭,都可以
0xFF,0xFF,0xFF,0xFF,0x00,0x20,0x00, //点亮的话要取反
0xFF,0xFF,0xFF,0xFF,0xE0,0x37,0x00, //查表显示
0xFF,0xFF,0xFF,0xFF,0xF1,0x3F,0xC0,
0xFF,0xFF,0xFF,0xFF,0xF9,0x3F,0xC0,
0xFF,0xFF,0xFF,0xFF,0xFD,0xBF,0xE4,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xF6,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,};
//阴码(1为点亮),全体LED从上往下熄灭或点亮(要取反)
unsigned char code LEDTable2[][7]={
0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x30,0x30,0x00,0x00,0x00,0x00,
0x00,0x78,0x78,0x00,0x00,0x00,0x00,
0x00,0xF8,0xF8,0x00,0x00,0x00,0x00,
0x00,0xFC,0xFC,0x00,0x00,0x20,0x00,
0x00,0xFE,0xFE,0x00,0xE0,0x37,0x00,
0x00,0xFE,0xFE,0x00,0xF1,0x3F,0xC0,
0x00,0xFF,0xFF,0x00,0xF9,0x3F,0xC0,
0x80,0xFF,0xFF,0x80,0xFD,0xBF,0xE4,
0xC0,0xFF,0xFF,0xC0,0xFF,0xFF,0xF6,
0xE0,0xFF,0xFF,0xE0,0xFF,0xFF,0xFF,
0xF0,0xFF,0xFF,0xF0,0xFF,0xFF,0xFF,
0xF8,0xFF,0xFF,0xF8,0xFF,0xFF,0xFF,
0xFC,0xFF,0xFF,0xFC,0xFF,0xFF,0xFF,
0xFE,0xFF,0xFF,0xFE,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
};
//阴码(1为点亮),全体LED从左往右熄灭或点亮(要取反)
unsigned char code LEDTable3[][7]={
0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x03,0x00,0x00,0x00,0x00,0x00,
0x80,0x07,0x00,0x00,0x00,0x00,0x00,
0xC0,0x0F,0x00,0x00,0x00,0x00,0x00,
0xE0,0x0F,0x00,0x00,0x00,0x00,0x00,
0xF0,0x1F,0x00,0x00,0x00,0x00,0x00,
0xF8,0x1F,0x00,0x00,0x82,0x00,0x00,
0xFC,0x3F,0x00,0x00,0xCF,0x00,0x00,
0xFE,0x7F,0x00,0x00,0xDF,0xC0,0x00,
0xFF,0xFF,0x00,0x00,0xFF,0xC8,0x08,
0xFF,0xFF,0x80,0x01,0xFF,0xF8,0x18,
0xFF,0xFF,0xC0,0x03,0xFF,0xFC,0x7C,
0xFF,0xFF,0xE0,0x07,0xFF,0xFE,0x7E,
0xFF,0xFF,0xE0,0x0F,0xFF,0xFF,0xFF,
0xFF,0xFF,0xF0,0x1F,0xFF,0xFF,0xFF,
0xFF,0xFF,0xF0,0x3F,0xFF,0xFF,0xFF,
0xFF,0xFF,0xF8,0x7F,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFC,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
};
//阴码(1为点亮),全体LED旋转(要取反)
unsigned char code LEDTable4[][7]={
0x01,0x80,0x80,0x01,0x00,0x08,0x08,
0x06,0x00,0x60,0x00,0x00,0x78,0x08,
0x18,0x00,0x18,0x00,0x00,0xC7,0x60,
0x60,0x00,0x06,0x00,0x06,0xC1,0xE4,
0x80,0x01,0x01,0x80,0x0C,0x80,0x24,
0x00,0x06,0x00,0x60,0x09,0x80,0x26,
0x00,0x18,0x00,0x18,0xD9,0x00,0x33,
0x00,0x60,0x00,0x06,0x70,0xC8,0x18,
};
void DelayUs(unsigned int t) //大约延时2t+4 us
{ //用来控制呼吸灯
while(t--);
}
void LED1(void) //LED全部点亮
{
unsigned char i;
for(i=0;i<7;i++)
{
LED[i]=0x00; //设置缓存数组的数据,交给定时器扫描
}
}
void LED0(void) //LED全部熄灭
{
unsigned char i;
for(i=0;i<7;i++)
{
LED[i]=0xFF;
}
}
void LED40(void) //心形LED全部熄灭
{
unsigned char i;
for(i=0;i<4;i++)
{
LED[i]=0xFF;
}
}
void LED31(void) //字的LED全部点亮
{
unsigned char i;
for(i=4;i<7;i++)
{
LED[i]=0x00;
}
}
void main()
{
unsigned char LEDPWM; //控制每个周期LED亮的时长
unsigned char LEDCycle=100; //控制呼吸灯亮度的档位,共100档
unsigned char LEDDelay=20; //控制呼吸灯每个状态的时长,即控制呼吸灯的快慢
unsigned char i,j,k; //控制流水灯的循环
unsigned int MusicFlash; //用来控制LED随音乐节奏闪烁
Timer0_Init(); //定时器0初始化
Timer1_Init(); //定时器1初始化,注意定时器1的优先级要设置比定时器0高,否则会影响蜂鸣器发声
//即要设置为:PT0=0;PT1=1;
EA=0; //关闭所有中断
//其实不关闭也行,可以再设多一个标志,用来控制显示呼吸灯的时候不扫描LED和不播放音乐就行了,我懒得弄了
//同时也可以利用定时器,通过比较的方法实现呼吸灯,我懒得弄了
P1=0xFF;
Delay(1000); //复位后过1s再显示呼吸灯,不需要可以删掉
//三次呼吸灯
P1=0x00; //选中所有位
for(j=0;j<3;j++)
{
for(LEDPWM=0;LEDPWM<LEDCycle;LEDPWM++) //8个LED由暗到亮
{
for(i=0;i<LEDDelay;i++) //计次延时,通过LEDDelay控制每个档位的时间,以此控制呼吸快慢
{
P2=0x00; //LED亮
DelayUs(LEDPWM); //延时LEDPWM
P2=0xFF; //LED灭
DelayUs(LEDCycle-LEDPWM); //延时LEDCycle-LEDPWM
}
}
for(LEDPWM=LEDCycle;LEDPWM>0;LEDPWM--) //8个LED由亮到暗
{
for(i=0;i<LEDDelay;i++) //计次延时,通过LEDDelay控制每个档位的时间,以此控制呼吸快慢
{
P2=0x00; //LED亮
DelayUs(LEDPWM); //延时LEDPWM
P2=0xFF; //LED灭
DelayUs(LEDCycle-LEDPWM); //延时LEDCycle-LEDPWM
}
}
}
LED0(); //确保全部LED熄灭
Delay(250); //延时一小段时间
//全部LED闪烁3次
P1=0x00;
for(i=0;i<3;i++)
{
P2=0x00; //LED亮
Delay(500); //延时500ms,即0.5s
P2=0xFF; //LED灭
Delay(500);
}
EA=1; //开启所有中断,开始播放音乐
while(1)
{
MusicSelect=0; //音乐和流水灯播放完了就从这里开始重新播放,所以音乐选择变量要清零
TR1=1; //音乐播放完之后定时器1暂停了,所以要重新启动
MusicOffFlag=0; //音乐停止标志位清零
//第一首音乐《生日快乐》播放完之后再跳出循环
while(MusicSpeedSelect==0)
{
if(MusicSelect>2) //防止没播放音乐就开始点亮LED
{
for(i=0;i<7;i++)
{
if(i<4){LED[i]=0xFF;}
else{LED[i]=0x00;}
}
}
MusicFlash=MusicSelect;
while(MusicFlash==MusicSelect); //播放下一个音符前,一直等待
MusicFlash=MusicSelect;
for(i=0;i<7;i++) //心形的LED先亮
{
if(i<4){LED[i]=0x00;}
else{LED[i]=0xFF;}
}
MusicFlash=MusicSelect;
while(MusicFlash==MusicSelect); //播放下一个音符前,一直等待
MusicFlash=MusicSelect;
}
LED0(); //每个动画结束后确保所有LED都熄灭
Delay(LEDSpeed); //延时一会
//一个LED按顺序移动
for(j=0;j<7;j++)
{
for(i=0;i<8;i++)
{
if(j>0){LED[j-1]=0xFF;}
if(j==5 && i==0){LED[4]=0xF7;Delay(LEDSpeed);LED[4]=0xFF;}
if(j<2){LED[j]=~(0x01<<i);}
else{LED[j]=~(0x80>>i);}
Delay(LEDSpeed);
}
}
LED0();
//按顺序点亮所有LED
for(j=0;j<7;j++)
{
for(i=0;i<8;i++)
{
if(j==5 && i==0){Delay(LEDSpeed);} //“又”字有个交叉的地方,需要停顿一下
if(j==0 || j==1){LED[j]=(0xFE<<i);}
else{LED[j]=(0x7F>>i);}
Delay(LEDSpeed);
}
}
LED1();
//按顺序全部熄灭所有LED
for(j=0;j<7;j++)
{
for(i=0;i<8;i++)
{
if(j==0 || j==1){LED[j]=~(0xFE<<i);}
else{LED[j]=~(0x7F>>i);}
Delay(LEDSpeed);
}
}
LED0();
//心形和字依次按顺序从上往下亮
for(i=0;i<8;i++)
{
LED[1]=0x7F>>i;
LED[2]=0x7F>>i;
Delay(LEDSpeed);
}
for(i=0;i<8;i++)
{
LED[0]=0x7F>>i;
LED[3]=0x7F>>i;
Delay(LEDSpeed);
}
for(j=0;j<7;j++)
{
for(i=0;i<7;i++)
{
LED[i]=~LEDTable1[j][i]; //通过查表的方法,可以实现任意流水灯
}
Delay(LEDSpeed);
}
LED1();
//心形和字依次闪烁5次
for(j=0;j<5;j++)
{
for(i=0;i<4;i++)
{
LED[i]=0xFF;
}
Delay(LEDSpeed);
for(i=0;i<4;i++)
{
LED[i]=0x00;
}
Delay(LEDSpeed);
}
for(j=0;j<5;j++)
{
for(i=4;i<7;i++)
{
LED[i]=0xFF;
}
Delay(LEDSpeed);
for(i=4;i<7;i++)
{
LED[i]=0x00;
}
Delay(LEDSpeed);
}
LED1();
//心形两边分别有2个熄灭的LED从上到下移动
LED[1]=0x80;
LED[2]=0x80;
Delay(LEDSpeed);
LED[1]=0xC0;
LED[2]=0xC0;
Delay(LEDSpeed);
for(i=0;i<6;i++)
{
LED[1]=_cror_(LED[1],1);
LED[2]=_cror_(LED[2],1);
Delay(LEDSpeed);
}
LED[1]=0x01;
LED[2]=0x01;
LED[0]=0x80;
LED[3]=0x80;
Delay(LEDSpeed);
LED[1]=0x00;
LED[2]=0x00;
LED[0]=0xC0;
LED[3]=0xC0;
Delay(LEDSpeed);
for(i=0;i<6;i++)
{
LED[0]=_cror_(LED[0],1);
LED[3]=_cror_(LED[3],1);
Delay(LEDSpeed);
}
LED[0]=0x01;
LED[3]=0x01;
Delay(LEDSpeed);
LED[0]=0x00;
LED[3]=0x00;
Delay(LEDSpeed);
//从上往下熄灭所有LED
for(j=0;j<16;j++)
{
for(i=0;i<7;i++)
{
LED[i]=LEDTable2[j][i]; //查表显示LED
}
Delay(LEDSpeed);
}
LED0();
//8个LED按出现,顺时针移动,再消失,循环2次
for(k=0;k<2;k++)
{
for(j=0;j<5;j++)
{
for(i=0;i<8;i++)
{
if(j<2)
{
LED[j]=(0xFE<<i);
if(j>0){LED[j-1]=~(0xFE<<i);}
}
else
{
if(j<4){LED[j]=(0x7F>>i);}
if(j==2){LED[j-1]=~(0xFE<<i);}
else {LED[j-1]=~(0x7F>>i);}
}
Delay(LEDSpeed/2);
}
}
LED0();
Delay(LEDSpeed);
}
//8个LED出现,逆时针移动,再消失,循环2次
for(k=0;k<2;k++)
{
for(j=4;j<5;j--)
{
for(i=7;i<8;i--)
{
if(j<2)
{
LED[j]=(0xFE<<i);
if(j>0){LED[j-1]=~(0xFE<<i);}
}
else
{
if(j<4){LED[j]=(0x7F>>i);}
if(j==2){LED[j-1]=~(0xFE<<i);}
else {LED[j-1]=~(0x7F>>i);}
}
if((j==2 || j==1 || j==0) && i==7){LED[j+1]=0xFF;}
Delay(LEDSpeed/2);
}
}
LED0();
Delay(LEDSpeed);
}
//心形两边分别有8个LED从上到下移动,循环2次
for(k=0;k<2;k++)
{
for(i=0;i<8;i++)
{
LED[1]=(0x7F>>i);
LED[2]=(0x7F>>i);
Delay(LEDSpeed/2);
}
for(i=0;i<8;i++)
{
LED[0]=(0x7F>>i);
LED[3]=(0x7F>>i);
LED[1]=~(0x7F>>i);
LED[2]=~(0x7F>>i);
Delay(LEDSpeed/2);
}
for(i=0;i<8;i++)
{
LED[0]=~(0x7F>>i);
LED[3]=~(0x7F>>i);
Delay(LEDSpeed/2);
}
LED0();
Delay(LEDSpeed);
}
//心形两边分别有8个LED从下到上移动,循环2次
for(k=0;k<2;k++)
{
for(i=0;i<8;i++)
{
LED[0]=(0xFE<<i);
LED[3]=(0xFE<<i);
Delay(LEDSpeed/2);
}
for(i=0;i<8;i++)
{
LED[1]=(0xFE<<i);
LED[2]=(0xFE<<i);
LED[0]=~(0xFE<<i);
LED[3]=~(0xFE<<i);
Delay(LEDSpeed/2);
}
for(i=0;i<8;i++)
{
LED[1]=~(0xFE<<i);
LED[2]=~(0xFE<<i);
Delay(LEDSpeed/2);
}
LED0();
}
//从左往右点亮,再熄灭,循环4次,速度逐渐加快
for(k=2;k<6;k++)
{
for(j=0;j<19;j++)
{
for(i=0;i<7;i++)
{
LED[i]=~LEDTable3[j][i];
}
Delay(LEDSpeed/k); //k增大,则延时减小,速度变快
}
for(j=0;j<19;j++)
{
for(i=0;i<7;i++)
{
LED[i]=LEDTable3[j][i];
}
Delay(LEDSpeed/k);
}
}
//从上往下点亮,再熄灭,循环4次,速度逐渐加快
for(k=2;k<6;k++)
{
for(j=0;j<16;j++)
{
for(i=0;i<7;i++)
{
LED[i]=~LEDTable2[j][i];
}
Delay(LEDSpeed/k);
}
for(j=0;j<16;j++)
{
for(i=0;i<7;i++)
{
LED[i]=LEDTable2[j][i];
}
Delay(LEDSpeed/k);
}
}
//光棒顺时针旋转,每转一圈,速度逐渐加快
for(k=3;k<25;k++)
{
for(j=0;j<8;j++)
{
for(i=0;i<7;i++)
{
LED[i]=~LEDTable4[j][i];
}
Delay(LEDSpeed*2/k);
}
}
LED40(); //熄灭心形的LED
LED31(); //点亮中间的字
//中间的字整体闪烁,心形4扇形顺时针变化,循环4次
WordFlashFlag=1; //字闪烁标志位置1,字开始闪烁
for(k=0;k<4;k++)
{
for(j=0;j<8;j++)
{
for(i=0;i<4;i++)
{
if(i==0 || i==1){LED[i]=(0xFE<<j);}
else {LED[i]=(0x7F>>j);}
}
Delay(LEDSpeed/2);
}
for(j=0;j<8;j++)
{
for(i=0;i<4;i++)
{
if(i==0 || i==1){LED[i]=~(0xFE<<j);}
else {LED[i]=~(0x7F>>j);}
}
Delay(LEDSpeed/2);
}
}
WordFlashFlag=0; //字闪烁标志位清0,暂停闪烁
LED0();
//光棒顺时针和逆时针旋转,速度逐渐加快
for(k=3;k<25;k++)
{
for(j=0;j<8;j++)
{
for(i=0;i<7;i++)
{
LED[i]=~LEDTable4[7-j][i];
}
Delay(LEDSpeed*2/k);
}
}
LED40();
LED31();
//中间的字整体闪烁,心形扇形逆时针变化,循环4次
WordFlashFlag=1;
for(k=0;k<4;k++)
{
for(j=0;j<8;j++)
{
for(i=0;i<4;i++)
{
if(i==0 || i==1){LED[i]=(0x7F>>j);}
else {LED[i]=(0xFE<<j);}
}
Delay(LEDSpeed/2);
}
for(j=0;j<8;j++)
{
for(i=0;i<4;i++)
{
if(i==0 || i==1){LED[i]=~(0x7F>>j);}
else {LED[i]=~(0xFE<<j);}
}
Delay(LEDSpeed/2);
}
}
LED0();
//中间的字整体闪烁,心形顺时针依次全部点亮,再顺时针依次全部熄灭,速度加快
for(j=3;j<8;j++)
{
for(i=0;i<8;i++)
{
LED[2]=(0x7F>>i);
Delay(LEDSpeed/j);
}
for(i=0;i<8;i++)
{
LED[3]=(0x7F>>i);
Delay(LEDSpeed/j);
}
for(i=0;i<8;i++)
{
LED[0]=(0xFE<<i);
Delay(LEDSpeed/j);
}
for(i=0;i<8;i++)
{
LED[1]=(0xFE<<i);
Delay(LEDSpeed/j);
}
for(i=0;i<8;i++)
{
LED[2]=~(0x7F>>i);
Delay(LEDSpeed/j);
}
for(i=0;i<8;i++)
{
LED[2]=0xFF;
LED[3]=~(0x7F>>i);
Delay(LEDSpeed/j);
}
for(i=0;i<8;i++)
{
LED[3]=0xFF;
LED[0]=~(0xFE<<i);
Delay(LEDSpeed/j);
}
for(i=0;i<8;i++)
{
LED[0]=0xFF;
LED[1]=~(0xFE<<i);
Delay(LEDSpeed/j);
}
LED[1]=0xFF;
}
LED40();
//中间的字整体闪烁,心形从两侧由上向下点亮,再由上往下熄灭,循环3次
for(k=0;k<3;k++)
{
for(i=0;i<8;i++)
{
LED[1]=0x7F>>i;
LED[2]=0x7F>>i;
Delay(LEDSpeed/2);
}
for(i=0;i<8;i++)
{
LED[0]=0x7F>>i;
LED[3]=0x7F>>i;
Delay(LEDSpeed/2);
}
for(i=0;i<8;i++)
{
LED[1]=~(0x7F>>i);
LED[2]=~(0x7F>>i);
Delay(LEDSpeed/2);
}
for(i=0;i<8;i++)
{
LED[0]=~(0x7F>>i);
LED[3]=~(0x7F>>i);
Delay(LEDSpeed/2);
}
}
LED40();
//中间的字整体闪烁,8个LED顺时针循环移动,速度加快
for(i=0;i<8;i++) //8个LED依次出现
{
LED[2]=(0x7F>>i);
Delay(LEDSpeed*2/7);
}
for(j=7;j<128;j++)
{
for(i=0;i<8;i++)
{
if(j!=7)LED[2]=(0x7F>>i);
if(j!=7)LED[1]=~(0xFE<<i);
if(j!=7)Delay(LEDSpeed*2/j); //随着j增大,延时时间变短
}
LED[1]=0xFF;
for(i=0;i<8;i++)
{
LED[3]=(0x7F>>i);
LED[2]=~(0x7F>>i);
Delay(LEDSpeed*2/j);
}
LED[2]=0xFF;
for(i=0;i<8;i++)
{
LED[0]=(0xFE<<i);
LED[3]=~(0x7F>>i);
Delay(LEDSpeed*2/j);
}
LED[3]=0xFF;
for(i=0;i<8;i++)
{
LED[1]=(0xFE<<i);
LED[0]=~(0xFE<<i);
Delay(LEDSpeed*2/j);
}
LED[0]=0xFF;
}
//中间的字整体闪烁,4*4个等间隔的LED顺时针循环移动
LED[0]=0xF0;
LED[1]=0xF0;
LED[2]=0x0F;
LED[3]=0x0F;
Delay(LEDSpeed);
for(i=0;i<32;i++)
{
LED[0]=_crol_(LED[0],1);
LED[1]=_crol_(LED[1],1);
LED[2]=_cror_(LED[2],1);
LED[3]=_cror_(LED[3],1);
Delay(LEDSpeed);
}
LED40();
//中间的字整体闪烁,8个LED逆时针循环移动,速度加快
for(i=0;i<8;i++)
{
LED[1]=(0x7F>>i);
Delay(LEDSpeed*2/7);
}
for(j=7;j<128;j++)
{
for(i=0;i<8;i++)
{
if(j!=7)LED[1]=(0x7F>>i);
if(j!=7)LED[2]=~(0xFE<<i);
if(j!=7)Delay(LEDSpeed*2/j);
}
LED[2]=0xFF;
for(i=0;i<8;i++)
{
LED[0]=(0x7F>>i);
LED[1]=~(0x7F>>i);
Delay(LEDSpeed*2/j);
}
LED[1]=0xFF;
for(i=0;i<8;i++)
{
LED[3]=(0xFE<<i);
LED[0]=~(0x7F>>i);
Delay(LEDSpeed*2/j);
}
LED[0]=0xFF;
for(i=0;i<8;i++)
{
LED[2]=(0xFE<<i);
LED[3]=~(0xFE<<i);
Delay(LEDSpeed*2/j);
}
LED[3]=0xFF;
}
LED0();
//中间的字整体闪烁,4*4个等间隔的LED逆时针循环移动
LED[0]=0xF0;
LED[1]=0xF0;
LED[2]=0x0F;
LED[3]=0x0F;
Delay(LEDSpeed);
for(i=0;i<32;i++)
{
LED[0]=_cror_(LED[0],1);
LED[1]=_cror_(LED[1],1);
LED[2]=_crol_(LED[2],1);
LED[3]=_crol_(LED[3],1);
Delay(LEDSpeed);
}
LED40();
//中间的字整体闪烁,心形按音乐节奏左右闪烁
WordFlashFlag=1;
for(i=0;i<16;i++)
{
LED[0]=0x00;
LED[1]=0x00;
LED[2]=0xFF;
LED[3]=0xFF;
MusicFlash=MusicSelect;
while(MusicFlash==MusicSelect);
MusicFlash=MusicSelect;
LED[0]=0xFF;
LED[1]=0xFF;
LED[2]=0x00;
LED[3]=0x00;
MusicFlash=MusicSelect;
while(MusicFlash==MusicSelect);
MusicFlash=MusicSelect;
}
LED40();
//中间的字整体闪烁,每两个灯为一组,相邻组交替闪烁,循环16次
for(j=0;j<16;j++)
{
for(i=0;i<4;i++)
{
LED[i]=0x99;
}
Delay(LEDSpeed*2);
for(i=0;i<4;i++)
{
{LED[i]=0x66;}
}
Delay(LEDSpeed*2);
}
//中间的字整体闪烁,相邻的灯交替闪烁,直到音乐播放结束
while(MusicSpeedSelect==3)
{
for(i=0;i<4;i++)
{
if(i==0 || i==1){LED[i]=0x55;}
else {LED[i]=0xAA;}
}
if(MusicSelect>100){Delay(LEDSpeed*2);}
for(i=0;i<4;i++)
{
if(i==0 || i==1){LED[i]=0xAA;}
else {LED[i]=0x55;}
}
if(MusicSelect>100){Delay(LEDSpeed*2);}
}
WordFlashFlag=0; //字闪烁的标志清零
LED0(); //所有LED熄灭
Delay(1000); //播放结束后延时1s再重新开始播放和显示
}
}
void Timer0_Routine() interrupt 1 //定时器0中断函数
{
static unsigned int T0Count1,T0Count2,T0Count3,T0Count4; //定义静态变量
static unsigned char i,j; //定义静态变量
TL0=0x18; //设置定时初值,定时1ms,晶振@12.0000MHz
TH0=0xFC; //设置定时初值,定时1ms,晶振@12.0000MHz
T0Count1++;
T0Count2++;
T0Count3++;
if(WordFlashFlag){T0Count4++;} //用来控制中间的字闪烁,字闪烁的标志WordFlashFlag为0则暂停闪烁
else {j=0;T0Count4=249;} //用来使标志WordFlashFlag设为1之后,字能尽快闪烁,并且字闪烁时先亮再灭
if(T0Count1>=2) //扫描LED,类似数码管的扫描,2ms切换一次显示
{
T0Count1=0; //静态计数变量清零
P2=0xFF; //消影
P1=Bit[i]; //切换“位”,即切换到下一组LED导通
P2=LED[i]; //切换“段”
i++; //通过静态变量i来进行切换
if(i>6){i=0;} //总共有7位,即7个字节的LED,7*8=56个
}
if(T0Count2>=Music[MusicSelect]*MusicSpeed[MusicSpeedSelect]) //控制每个音符响的时长
{ //如果上一个音符的时长到了(即如果时间=倍数*十六分音符的时长),则进入此函数,重新赋值
T0Count2=0;
//如果是终止标志0xFF,则音乐选择变量清零,定时器1暂停,音乐停止标志置1
if(Music[MusicSelect+1]==0xFF){MusicSelect=0;TR1=0;MusicOffFlag=1;}
if(MusicOffFlag==0) //如果音乐停止标志为0,则播放音乐
{
//如果下一个数据是切换播放速度的标志0xF0,则音乐速度选择的变量MusicSpeedSelect自增,并跳过标志0xF0(两个)
if(Music[MusicSelect+1]==0xF0){MusicSpeedSelect++;MusicSpeedSelect%=4;MusicSelect=MusicSelect+2;}
MusicSelect++; //音乐选择变量自增,此时对应Music数组里音符的频率
FreqSelect=Music[MusicSelect]; //将下一个音符的频率赋值给频率选择变量FreqSelect
//音乐选择变量自增,此时对应Music数组里↑上面频率的时长数据,即时长为十六分音符的倍数
MusicSelect++; //此时对应的Music数组的数据用来控制从现在到下次进入此函数的时间
TimeIntervalFlag=1; //控制音符之间短暂间隔(5ms)的标志置1
TR1=0; //定时器1暂停计时,蜂鸣器不发声
T0Count3=0; //T0Count3清零
}
}
if(T0Count3>=5 && TimeIntervalFlag==1) //5ms
{
T0Count3=0;
TimeIntervalFlag=0; //控制音符之间短暂间隔(5ms)的标志清零,在置1前不会进入此函数
TR1=1; //定时器1继续计时,蜂鸣器继续发声
}
if(T0Count4>=250) //控制心形中间的字的闪烁,250ms进入一次此函数,即闪烁周期为500ms
{
T0Count4=0;
if(j%2){LED[4]=0x00;LED[5]=0x00;LED[6]=0x00;} //点亮
else {LED[4]=0xFF;LED[5]=0xFF;LED[6]=0xFF;} //熄灭
j++; //通过静态变量j控制字的亮灭
}
}
void Timer1_Routine() interrupt 3 //定时器1中断函数
{
if(FreqTable[FreqSelect]) //如果不是休止符
{
/*取对应频率值的重装载值到定时器*/
TL1 = FreqTable[FreqSelect]%256; //设置定时初值
TH1 = FreqTable[FreqSelect]/256; //设置定时初值
Buzzer=!Buzzer; //翻转蜂鸣器IO口
}
}
七、制作过程
附录A:所用到的音乐的简谱
(图片来源:百度图片)
1、生日快乐
2、祝寿歌
3、生日祝福歌
4、小幸运
附录B:不同频率的定时器重装值的计算
图片来源:江科大51单片机入门教程