首页 > 学院 > 开发设计 > 正文

STM32F4应用笔记(二)利用蜂鸣器播放天空之城

2019-11-06 07:19:20
字体:
来源:转载
供稿:网友

音阶频率对照表 百度就可以查到,我对照的是下面网址中的: http://blog.csdn.net/u012266559/article/details/51512616

单片机产生音乐的原理 音乐的产生主要是通过单片机的I/O口输出高低不同的脉冲信号来控制蜂鸣器发音,要想产生音频脉冲信号,需要算出某音频的周期(1/频率),然后将此周期除以2,即为半周期的时间。利用单片机定时器计时这个半周期的时间,每当计时到后就输出脉冲的I/O口反相,这样就在此I/O口上得到此脉冲的频率。 我是直接利用stm32的PWM端口输出PWM波(设置成占空比为50%)就可以实现,关键是每个音阶对应频率的方波如何求。 从下图看,只要理解原理,那么不管单片机的时钟频率多大,我们都可以自己求出相应的音阶所需要的arr值。 这里写图片描述

程序用到的单片机引脚 我用的是stm32开发板,利用PF9端口输出PWM波,PF8端口连着的是蜂鸣器,所以只需要用杜邦线把PF9和PF8端口连在一起就可以了。

程序中定义的的宏定义 10000的来历上图中有计算过程,然后音阶频率对照表中可以查到 低1 DO 的频率是262,所以我们得到 低1 DO 对应的arr值是 (R/262)-1 ,其余音符对应arr的计算方式同理。 #define R 10000 //84MHz/(psc+1)=10000 #define L1 (R/262)-1 //低1 DO

关于音符0的处理以及改进 在我写完的程序中,我是这样处理的,一旦遇到音符0,让PWM停止输出。有想过关闭TIM14和PORTF时钟,但是没有用——因为用同样的方法,在一首歌结束之后就是一段相同频率的杂音。所以这种方法是不可以让PWM输出停止的。最后换了个方法,把PF9设置成普通IO口而且是输入模式就可以了,就不会有噪声了。

但是这种方法很麻烦,后来想到其实在遇到音符0的时候,只需要将PWM输出频率变大,让蜂鸣器发出一个人耳听不到的超声波就可以了——程序已经写完了,懒得改了。

主要程序代码

#include "sys.h"#include "delay.h" #include "led.h"#include "timer.h"#include "key.h" #define ZERO 3000// #define R 10000 //F_CLOCK/(psc+1)=10000 #define L1 (R/262)-1 //低1 DO #define half_L1 (R/277)-1 //#1 DO# #define L2 (R/294)-1 #define half_L2 (R/311)-1 #define L3 (R/330)-1 #define L4 (R/349)-1 #define half_L4 (R/370)-1 #define L5 (R/392)-1 #define half_L5 (R/410)-1 #define L6 (R/440)-1 #define half_L6 (R/466)-1 #define L7 (R/494)-1 #define M1 (R/523)-1 //中1 DO #define half_M1 (R/554)-1 //#1  DO# #define M2 (R/587)-1 #define half_M2 (R/622)-1 #define M3 (R/659)-1 #define M4 (R/698)-1 #define half_M4 (R/740)-1 #define M5 (R/784)-1 #define half_M5 (R/831)-1 #define M6 (R/880)-1 #define half_M6 (R/932)-1 #define M7 (R/988)-1 #define H1 (R/1046)-1 //高1 DO #define half_H1 (R/1109)-1 //#1 DO# #define H2 (R/1175)-1 #define half_H2 (R/1245)-1 #define H3 (R/1318)-1 #define H4 (R/1397)-1 #define half_H4 (R/1480)-1 #define H5 (R/1568)-1 #define half_H5 (R/1661)-1 #define H6 (R/1760)-1 #define half_H6 (R/1865)-1 #define H7 (R/1967)-1 int flag=0;//标志 int x; int tune[] = { M6,M7,H1,M7,H1,H3,M7,M7,M7,M3,M3, M6,M5,M6,H1,M5,M5,M5,M3,M4,M3,M4,H1, M3,M3,ZERO,H1,H1,H1,M7,half_M4,M4,M7,M7,M7,ZERO,M6,M7, H1,M7,H1,H3,M7,M7,M7,M3,M3,M6,M5,M6,H1, M5,M5,M5,M2,M3,M4,H1,M7,M7,H1,H1,H2,H2,H3,H1,H1,H1, H1,M7,M6,M6,M7,half_M5,M6,M6,M6,H1,H2,H3,H2,H3,H5, H2,H2,H2,M5,M5,H1,M7,H1,H3,H3,H3,H3,H3, M6,M7,H1,M7,H2,H2,H1,M5,M5,M5,H4,H3,H2,H1, H3,H3,H3,H3,H6,H6,H5,H5,H3,H2,H1,H1,ZERO,H1, H2,H1,H2,H2,H5,H3,H3,H3,H3,H6,H6,H5,H5, H3,H2,H1,H1,ZERO,H1,H2,H1,H2,H2,M7,M6,M6,M6,M6,M7};float duration[]= { 0.5,0.5, 1.5,0.5,1,1, 1,1,1,0.5,0.5, 1.5,0.5,1,1, 1,1,1,1, 1.5,0.5,1,1, 1,1,0.5,0.5,0.5,0.5, 1+0.5,0.5,1,1, 1,1,1,0.5,0.5, 1+0.5,0.5,1,1, 1,1,1,0.5,0.5, 1+0.5,0.5,1,1, 1,1,1,0.5,0.5, 1,0.5,0.25,0.25,0.25,0.5, 0.5,0.5,0.5,0.25,0.5,1, 0.5,0.5,0.5,0.5,1,1, 1,1,1,0.5,0.5, 1+0.5,0.5,1,1, 1,1,1,0.5,0.5, 1.5,0.5,1,1, 1,1,1,1, 0.5,0.5,1,1,0.5,0.5, 1.5,0.25,0.5,1, 1,1,1,1, 1,1,1,1, 1,1,1,1, 0.5,0.5,1,1,0.5,0.5, 1,0.5,0.5,1,1, 1,1,1,1, 1,1,1,1, 0.5,0.5,1,1,0.5,0.5, 1,0.5,0.25,0.5,1, 1,1,1,0.5,0.5};//这部分是整首曲子的节拍部分,也定义个序列duration,浮点(数组的个数和前面音符的个数是一样的,一一对应么)int length = sizeof(tune)/sizeof(tune[0]);//这里用了一个sizeof函数, 可以查出tone序列里有多少个音符//int length;//这里定义一个变量,后面用来表示共有多少个音符void main(void){ Stm32_Clock_Init(336,8,2,7);//设置时钟,168Mhz delay_init(168); //延时初始化 for(x=0;x<length;x++)//循环音符的次数 { if(flag==1) //上一个音符是0,在遇到下一个音符前重新使用IO口的复用功能 { //RCC->AHB1ENR|=1<<5; //使能PORTF时钟 //RCC->APB1ENR|=1<<8; //使能TIM14时钟 GPIO_Set(GPIOF,PIN9,GPIO_MODE_AF,GPIO_OTYPE_PP,GPIO_SPEED_100M,GPIO_PUPD_PU);//复用功能,上拉输出 GPIO_AF_Set(GPIOF,9,9); //PF9,AF9 flag=0;//将标志置0 } if(tune[x]==ZERO) { //RCC->APB1ENR|=0<<8; //关闭TIM14时钟 //RCC->AHB1ENR|=0<<5; //关闭PORTF时钟使PF9引脚无法输出PWM波 GPIO_Set(GPIOF,PIN9,GPIO_MODE_IN,GPIO_OTYPE_PP,GPIO_SPEED_100M,GPIO_PUPD_PU); //将PF9设置成普通IO口,输入 flag=1;//将关闭标志置1 } GPIO_Set(GPIOF,PIN9,GPIO_MODE_AF,GPIO_OTYPE_PP,GPIO_SPEED_100M,GPIO_PUPD_PU);//复用功能,上拉输出 GPIO_AF_Set(GPIOF,9,9); //PF9,AF9 //设置成复用模式(此处就是PWM端口),输出 TIM14_PWM_Init(tune[x],8400-1); //(arr,psc) if(flag==1) delay_ms(300*duration[x]);//否则延时会很长 else delay_ms(400*duration[x]); //每个音符持续的时间,即节拍duration //设置成一个全拍400ms } //RCC->APB1ENR|=0<<8; //关闭TIM14时钟 //RCC->AHB1ENR|=0<<5; //关闭PORTF时钟 GPIO_Set(GPIOF,PIN9,GPIO_MODE_IN,GPIO_OTYPE_PP,GPIO_SPEED_100M,GPIO_PUPD_PU); //把PF9设置成普通IO口输入就可以了,就不会有噪声了。 //delay_ms(10000);//还是会有声音,不知道为什么 //GPIO_AF_Set(GPIOF,9,9); //PF9,AF9 while(1);//防止程序跑飞 }//TIM14 PWM部分初始化函数//PWM输出初始化//arr:自动重装值//psc:时钟预分频数void TIM14_PWM_Init(u32 arr,u32 psc){ //此部分需手动修改IO口设置 RCC->APB1ENR|=1<<8; //TIM14时钟使能 RCC->AHB1ENR|=1<<5; //使能PORTF时钟 //GPIO_Set(GPIOF,PIN9,GPIO_MODE_AF,GPIO_OTYPE_PP,GPIO_SPEED_100M,GPIO_PUPD_PU);//复用功能,上拉输出 //GPIO_AF_Set(GPIOF,9,9); //PF9,AF9 TIM14->ARR=arr; //设定计数器自动重装值 TIM14->PSC=psc; //预分频器分频 TIM14->CCMR1|=6<<4; //CH1 PWM1模式 TIM14->CCMR1|=1<<3; //CH1 预装载使能 TIM14->CCER|=1<<0; //OC1 输出使能 TIM14->CCER|=1<<1; //OC1 低电平有效 TIM14->CR1|=1<<7; //ARPE使能 TIM14->CR1|=1<<0; //使能定时器14 TIM14->CCR1=arr*0.5; //占空比= TIM14->CCR1 / arr(单位:%) //设置占空比为50%}
发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表