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

音阶频率对照表
百度就可以查到,我对照的是下面网址中的:
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%
}  

猜你喜欢

转载自blog.csdn.net/u014751607/article/details/60465673