学习单片机有将近20天了,有一点感触,刚开始用proteus仿真,总是出现各种错误,无奈之下买了一块板子.要真正学习单片机必须有开发板,这句话一点没错.另外如果之前有数模和C语言的基础,学习起来是很快的, 单片机是座桥,ARM近在咫尺拉!
.
一、根据简谱写程序数据
二、程序实现
三、其他
四、附录(五线谱对应表)
.
一、根据简谱写程序数据 (必须有简单的简谱知识,为此我也专门去网上找了几个视频学了下 )
1、音符变量对应表(F大调)
2、根据乐谱确定变量
这里简单说一点,<兰花花>是2/4拍,也就是以四分音符为一拍,每小结两拍, "T"代表一拍,"T/2"代表半拍,"T/4"代表四分之一拍
3、程序数组:
//《兰花花》
code CNote MusicTab[] =
{ // {音名,时值}
{d2,T/2},{e2,T/2},{d2,T/2},{d2,T/4},{c2,T/4},{d2,T/2},{e2,T/2},{d2,T},
{c2,T/2},{d2,T/2},{a1,T/2},{g1,T/4},{g1,T/4},{f1,T/2},{d1,T+T/2},
{g1,T/2},{a1,T/2},{c2,T/2},{c2,T/2},{a1,T/2},{d2,T/2},{c2,T/2},{a1,T/2},
{g1,T/4},{a1,T/4},{g1,T/4},{f1,T/4},{d1,T/2},{c1,T/2},{d1,T*2},
{0,T}, {0,T},
{0,0} //结束标志
};
二、程序实现
beep.h
//#define 0xF922 // 262Hz 低1
//#define 0xF9E2 // 294Hz 低2
//#define 0xFA8C // 330Hz 低3
#define as 0xFAD9 // 349Hz 低4
#define c1 0xFB69 // 392Hz 低5
#define d1 0xFBE9 // 440Hz 低6
#define e1 0xFC5C // 494Hz 低7
#define f1 0xFC8F // 523Hz 中1
#define g1 0xFCF0 // 587Hz 中2
#define a1 0xFD46 // 659Hz 中3
#define a1s 0xFD6D // 698Hz 中4
#define c2 0xFDB5 // 784Hz 中5
#define d2 0xFDF5 // 880Hz 中6
#define e2 0xFE2E // 988Hz 中7
#define f2 0xFE48 // 1046Hz 高1
#define g2 0xFE79 // 1175Hz 高2
#define a2 0xFEA3 // 1318Hz 高3
//#define 0xFEB7 // 1397Hz 高4
//#define 0xFEDB // 1568Hz 高5
//#define 0xFEFB // 1760Hz 高6
//#define 0xFF16 // 1967Hz 高7
//定义发声时值
//T*4全音符,T*2二分音符,T四分音符,T/2八分音符,T/4十六分音符
#define T 1000
//《兰花花》
code CNote MusicTab[] =
{ // {音名,时值}
{d2,T/2},{e2,T/2},{d2,T/2},{d2,T/4},{c2,T/4},{d2,T/2},{e2,T/2},{d2,T},
{c2,T/2},{d2,T/2},{a1,T/2},{g1,T/4},{g1,T/4},{f1,T/2},{d1,T+T/2},
{g1,T/2},{a1,T/2},{c2,T/2},{c2,T/2},{a1,T/2},{d2,T/2},{c2,T/2},{a1,T/2},
{g1,T/4},{a1,T/4},{g1,T/4},{f1,T/4},{d1,T/2},{c1,T/2},{d1,T*2},
{0,T}, {0,T},
{0,0} //结束标志
};
beep.c
#include <REG52.H> //包含所选单片机的定义头文件
sbit SPEAKER = P2^3; //定义音乐输出口
typedef struct //定义音符结构
{ unsigned int mFreq; //发声频率(对应的定时器初值)
unsigned int mDelay; //发声时值
}CNote;
unsigned char ReloadH; //定义定时器T1重装值
unsigned char ReloadL;
/************************************************************
函数:T1_ISR()
功能:定时器T1中断服务函数,产生音乐振荡频率
*************************************************************/
void T1_ISR() interrupt 3
{ TR1 = 0;
TH1 = ReloadH;
TL1 = ReloadL;
TR1 = 1;
SPEAKER = !SPEAKER; //音乐声频的半个波
}
/*************************************************************
函数:Delay()
功能:延时0.001~65.536s
参数:t>0时,延时(t*0.001)s
t=0时,延时65.536s
*************************************************************/
void Delay(unsigned int t)
{ do
{ TH0 = 0xFC; //定时器赋初值,定时1ms
TL0 = 0x66+17;
TR0 = 1; //启动定时器
while ( !TF0 ); //等待定时器溢出
TR0 = 0; //关闭定时器
TF0 = 0; //清除溢出标志
} while ( --t != 0 ); //循环t次
}
/***********************************************************
函数:Sound()
功能:演奏一个音符
参数:*note,音符指针,指向要演奏的音符
***********************************************************/
void Sound(CNote *note)
{ //利用定时器T1发出音符的频率
if ( note->mFreq != 0 )
{ ReloadH = (unsigned char)(note->mFreq >> 8);
ReloadL = (unsigned char)(note->mFreq);
TH1 = 0xFF;
TL1 = 0xF0;
TR1 = 1;
}
Delay(note->mDelay); //发声延时
TR1 = 0; //停止发声
TF1 = 0;
SPEAKER = 1;
Delay(10);
}
/************************************************************
函数:Play()
功能:演奏一段乐曲
参数:music[],要演奏的乐曲
*************************************************************/
void Play(CNote music[])
{ unsigned int n = 0;
for (;;)
{ if ( music[n].mDelay == 0 ) break;
Sound(&(music[n]));
n++;
}
}
#include "beep.h" //包含乐曲头文件
void main() //主程序
{
TMOD = 0x11; //设定时器
ET1 = 1;
EA = 1;
for (;;)
{
Play(MusicTab); //演奏第一首乐曲
Delay(500); //等待数秒
}
}
我测试代码后,觉得有那么点意思了,但是还不是很理想,个别音很颤...
三 、其他
1、计时器初值的计算
细心的朋友注意到了beep.h中的代码有点乱,这里解释一下,这个头文件里定义的是每个音符所对应的计时器初值,在串口通信里通常用T1作为波特率发生器,这里我们用T1作为音符频率发生器,踏实这样算出来的:
假设音乐频率为 X,晶振为11.0592MHz 一、先求出一个定时周期的时间 1.求机器周期:1/11.0592*12=1.085 us (一个记时周期为12个晶振周期即1.085微秒) 2.音乐频率周期 1/X 1/(2X) 一个音频脉冲为二个周期, 二、计算所需定时周期数 记时周期数=音乐频率周期/记时周期 三、得到定时器初值 定时器初值=65536-记时周期数 四、例子 如440HZ标准音 音乐频率周期=1/(2X)=1/(2*440)=1136.36 us 记时周期数=1136.36us/1.085=1047.34个 定时器初值=65536-1047.34~=64489 十六进制为(0xFBE9) |
在beep.h中已经有了21个音符的频率,下面就要根据公式把计时器初值算出来,如果手算的话工作量太大,直接用程序代替吧
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
using namespace std;
typedef struct
{
//char tone[2]; //音调
int audio_fre; //音符频率
int audio_cycle; //频率周期
int time_num; //记时数
int init; //定时器初值
}ress;
ress res[21];
int hz[] = {262,294,330,349,392,440,494,
523,587,659,698,784,880,988,
1046,1175,1318,1397,1568,1760,1967};
int t = sizeof(hz) / sizeof(hz[0]);
int main()
{
int i;
for (i = 0;i < 21; i++)
{
res[i].audio_fre = hz[i];
res[i].audio_cycle = (((double)(1000000) / (2*res[i].audio_fre)));
res[i].time_num = (res[i].audio_cycle) / (1.085);
res[i].init = 65536 - res[i].time_num;
}
printf("音符频率 频率周期 记时数 初值\n");
for (i = 0;i < 21; i++)
{
printf("%6d %6d %8d %#7X\n",res[i].audio_fre,res[i].audio_cycle,res[i].time_num,res[i].init);
}
system("pause");
}
2、蜂鸣器如何发声
无源蜂鸣器根据输入方波的频率来决定发声音调, 周期是指一个波形完整变化的过程,对于方波包括一半低电平和一半高电平,所以前面计算的时候,才出现 1/(2X),我们让它在一个周期内 (1/X)变化一次(由高到低或由低到高变动,这才是方波嘛)
四、附录(五线谱对应表)