历史上的今天
今天是:2025年05月31日(星期六)
2019年05月31日 | STM32掌机教程8,背景音乐
2019-05-31 来源:eefocus
再用一个定时器
在上一节,我们使用了一个定时器来计算频率。需要某个音符持续一定的时间的话,仍然使用的是延时函数delay_ms,这会导致CPU阻塞,程序运行到这里,CPU只会去数数字,你按下按键,他也检测不到——忙着数数字呢。接下来把这个延时也改成定时器,让定时器像个闹钟一样工作,让CPU该干什么干什么,时间到了以后,让定时器来提醒CPU。换句话说,播放的是背景音乐。
//改进此函数中的延时
void musicPlay(int length,unsigned char volume_level)
{
u8 i=0;
while(i buzzerSound(AllBGM[i].mName,volume_level); delay_ms(AllBGM[i].mTime); i++; } } 定时器5的应用比定时器3还简单点——不需要输出PWM,只做计时用途。 //beep.c void TIM5_Int_Init(u16 arr,u16 psc) { TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; NVIC_InitTypeDef NVIC_InitStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5, ENABLE); //时钟使能 //定时器TIM5初始化 TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值 TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值 TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割:TDTS = Tck_tim TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式 TIM_TimeBaseInit(TIM5, &TIM_TimeBaseStructure); //根据指定的参数初始化TIMx的时间基数单位 TIM_ClearITPendingBit(TIM5, TIM_IT_Update ); //清除TIMx更新中断标志 TIM_ITConfig(TIM5,TIM_IT_Update,ENABLE ); //使能指定的TIMx中断,允许更新中断 //中断优先级NVIC设置 NVIC_InitStructure.NVIC_IRQChannel = TIM5_IRQn; //TIMx中断 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3; //先占优先级3级 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2; //从优先级2级 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能 NVIC_Init(&NVIC_InitStructure); //初始化NVIC寄存器 TIM_Cmd(TIM5, ENABLE); //使能TIMx } 我们把定时器5的初始化放在主函数的最前边,并且把音量也初始化,让掌机一上电就唱歌。死循环暂时什么都不写。 int main(void) { BGM_volum = 6;//音量 TIM5_Int_Init(9,7199); //上电先播放背景音乐 ... } 定时器5的中断服务 定时器的中断服务函数无需调用,只要我们设定的时间到了以后,就会自动跳转到此函数中来执行。在分频系数为7199时,(自动重装值+1)/10=ms,所以,如果知道了需要延时的时间ms以后,ms * 10-1=自动重装值。 我们可以把每一个包含了音调与时间的音符都看作一个珍珠,整个乐曲就像是珍珠项链。前一个音符根据频率与音量算出定时器3需要的自动重装值与比较值(定时器3的分频系数是确定的,9),根据延时的时间设置好定时器5的自动重装值(定时器5的分频系数也是确定的,7200,且无须比较值),然后开启定时器。 CPU该干嘛干嘛。 等到定时器5的时间到了,播放下一个音符,周而复始。思路与之前一样。显而易见,我们需要一个变量来记录当前播放到哪一个音符。我用了一个局部的静态变量,即便中断服务函数执行完了,静态变量的值也不会丢失。只要没有把乐谱播放完,就播放下一个音符。 //beep.c //定时器5中断服务程序 void TIM5_IRQHandler(void) { static u16 i = 0; if (TIM_GetITStatus(TIM5, TIM_IT_Update) != RESET) //检查TIM5更新中断发生与否 { TIM_ClearITPendingBit(TIM5, TIM_IT_Update ); //清除TIMx更新中断标志 if(i buzzerSound(AllBGM[i].mName,BGM_volum); TIM_SetAutoreload(TIM5,AllBGM[i].mTime*10-1); TIM_SetCounter(TIM5,0); i++; } else i = 0; } } 完成这些代码以后,即便主函数的死循环为空,歌曲也能后台播放了。 更多的BGM 考虑到我要用的音效有好几个,所以我需要更多的BGM。其中,打中地鼠与生成地鼠的音乐还是我原创的,哈哈哈。这是我的乐谱: //beep.c const tNote AllBGM[]= { //击中 5 BAD_BGM {CM1,TT1/4},{CL4,TT1/4},{CL3,TT1/8},{CL1,TT1/8},{0,TT1/8}, // 生成 4 GOOD_BGM {CH1,TT1/4},{CH2,TT1/4},{CH3,TT1/4},{0,TT1/8}, //两只老虎 36 BEGIN_BGM {CM1,TT/4},{CM2,TT/4},{CM3,TT/4},{CM1,TT/4}, {CM1,TT/4},{CM2,TT/4},{CM3,TT/4},{CM1,TT/4}, {CM3,TT/4},{CM4,TT/4},{CM5,TT/4},{0,TT/4}, {CM3,TT/4},{CM4,TT/4},{CM5,TT/4},{0,TT/4}, {CM5,TT/8},{CM6,TT/8},{CM5,TT/8},{CM4,TT/8},{CM3,TT/4},{CM1,TT/4}, {CM5,TT/8},{CM6,TT/8},{CM5,TT/8},{CM4,TT/8},{CM3,TT/4},{CM1,TT/4}, {CM1,TT/4},{CL5,TT/4},{CM1,TT/4},{0,TT/4}, {CM1,TT/4},{CL5,TT/4},{CM1,TT/4},{0,TT/4}, //小猪佩奇 12 LIFE_BGM {CH5,TTS/4},{CH3,TTS/8},{CH1,TTS/8},{CH2,TTS/4},{CM5,TTS/4}, {CM5,TTS/8},{CM7,TTS/8},{CH2,TTS/8},{CH4,TTS/8},{CH3,TTS/4},{CH1,TTS/4},{0,TT/4}, //坦克大战 33 LEVEL_BGM {CM1,TT1/4},{CM2,TT1/4},{0,TT1/4},{CM3,TTS/4}, {CM1,TT1/4},{CM2,TT1/4},{0,TT1/4},{CM3,TTS/4}, {CM3,TT1/4},{CM4,TT1/4},{0,TT1/4},{CM5,TTS/4}, {CM3,TT1/4},{CM4,TT1/4},{0,TT1/4},{CM5,TTS/4}, {CM4,TT1/4},{CM5,TT1/4},{0,TT1/4},{CM6,TTS/4}, {CM4,TT1/4},{CM5,TT1/4},{0,TT1/4},{CM6,TTS/4}, {CM5,TT1/4},{CM7,TT1/4},{0,TT1/4},{CH1,TTS/4}, {CM5,TT1/4},{CM7,TT1/4},{0,TT1/4},{CH1,TTS/4},{0,TTS/4}, }; 如何切换歌曲呢?可以每首歌都是不同的结构体数组,然后用判断语句切换歌曲。我选择把所有的歌都放在一个结构体数组内,记录歌曲的开头与结尾的位置,并且为每首歌都按顺序编号,所以我还需要一些变量: //beep.c u8 BGM = 0; u8 BGM_LENGTH[6] = {0,5,4,36,12,33}; u8 BGM_change_flg = 0; u8 BGM_volum; 通过遍历BGM_LENGTH的数组,就可以找到每一首歌的开头和结尾的坐标了。而BGM_change_flg可用于标记背景音乐有没有改变。 按键切换BGM 首先要修改定时器5的中断服务,来判断有没有换歌,如果没有,接着播放下一个音符;如果换了,遍历LENGTH数组来寻找新的数组的开头与结尾。 当歌曲播放完成以后,我通过把BGM_change_flg与old_BGM清零,来实现这个需求:按下按键1可以播放音乐1,音乐1 播放完以后,再次按下按键1,仍然可以播放音乐1。 //beep.c //定时器5中断服务程序 void TIM5_IRQHandler(void) //TIM3中断 { static u16 i = 0; static u8 old_BGM = 0; static u16 END = 0; if (TIM_GetITStatus(TIM5, TIM_IT_Update) != RESET) //检查TIM3更新中断发生与否 { TIM_ClearITPendingBit(TIM5, TIM_IT_Update ); //清除TIMx更新中断标志 if(old_BGM != BGM)//改变了BGM { u16 temp; i = 0; for(temp = 0;temp i += BGM_LENGTH[temp]; } END = i+BGM_LENGTH[temp];//计算音乐的开头 old_BGM = BGM; BGM_change_flg = 0;//此处为1可以设为单曲循环。 } if(i buzzerSound(AllBGM[i].mName,BGM_volum); TIM_SetAutoreload(TIM5,AllBGM[i].mTime*10-1); TIM_SetCounter(TIM5,0); i++; } else { buzzerSound(0,BGM_volum);//停止。 if(BGM_change_flg) { BGM_change_flg = 0; old_BGM = 0; } } } } 主函数先写一段测试代码,按下不同的按键,可以切换不同的BGM。 //main.c int main(void) { BGM_volum = 6;//音量 TIM5_Int_Init(9,7199); //上电先播放背景音乐 LED_Init(); KEY_Init(); delay_init(); initIIC(); initOLED(); TIM3_PWM_Init(0xfffe,8); //蜂鸣器频率定时器初始化 while(1) { key = KEY_Scan(0); if(key) //如果按下按键 { BGM_change_flg = 1; if(key == KEY1_PRES)//正确打中地鼠 加分,生成下一个地鼠 { BGM = BAD_BGM; } else if(key == KEY2_PRES) { BGM = GOOD_BGM; } else if(key == KEY3_PRES) { BGM = BEGIN_BGM; } else if(key == KEY4_PRES) { BGM = LIFE_BGM; } else if(key == KEY5_PRES) { BGM = LEVEL_BGM; } else { BGM = 0; } } } } 到目前为止,背景音乐功能都写好了,最起码,掌机可以实现点唱机的功能了。应付下本科生的毕设,应该是没问题了。

上一篇:STM32掌机教程9,完成掌机
下一篇:STM32掌机教程7,演奏音乐
史海拾趣
|
使用这些设计技巧和ISE功能分析工具来控制功耗 新一代 FPGA的速度变得越来越快,密度变得越来越高,逻辑资源也越来越多。那么如何才能确保功耗不随这些一起增加呢?很多设计抉择可以影响系统的功耗,这些抉择包括从显见的器件选择到细小的 ...… 查看全部问答> |
|
【原创+示波器显示】模拟式信号发生器的设计3------锯齿波信号发生器的设计 http://blog.ednchina.com/freeeedoooom/189369/message.aspx… 查看全部问答> |
|
当 给 mc55模块 发送 短信时,模块 为什么 无法 接受到 +CMT: \"+491795289609\",,\"03/08/14,14:44:49+08\"是甚么 原因造成的呢。但用AT+CMGR=28 可以 读取到短信。… 查看全部问答> |
|
tilcon下可以在edit输入框窗口右键属性里Mask标签页的Format下输入如##.0来设置数据显示的格式,但是遇到负数只能显示正数,虽然在程序里读取的值是负数,如果在设置格式的同时也能显示负数,该怎么样做,请教这方面的高手,是在是困扰多日,谢谢 ...… 查看全部问答> |
|
在虚拟机中安装了vmtools ,在虚拟机设置里也添加了共享文件夹,而且也在虚拟机linux中运行了vmware-config-tools.pl,但是我设置的那个文件夹似乎还是没有和windows7实现直接共享,无论在哪个系统中的共享文件夹中有什么操作,另一个系统的共享文件 ...… 查看全部问答> |




