zoukankan      html  css  js  c++  java
  • Raspberry Pi开发之旅-控制蜂鸣器演奏乐曲

    一、无源蜂鸣器和有源蜂鸣器

    步进电机以及无源蜂鸣器这些都需要脉冲信号才能够驱动,这次尝试用GPIO的PWM接口驱动无源蜂鸣器弹奏一曲《一闪一闪亮晶晶》。
    无源蜂鸣器:

    无源内部没有震荡源,直流信号无法让它鸣叫。必须用去震荡的电流驱动它,2K-5KHZ的方波PWM (Pulse Width Modulation脉冲宽度调制)。 5KHZ的电流方波是啥意思?那就是每秒震动5K次,每一个完整的周期占用200us的时间,高点平占一部分时间,低电平占一部分时间。

    声音频率可控,可以做出不同的音效。
    有源蜂鸣器:
    内部带震荡电路,一通电就鸣叫,所以可以跟前面LED一样,给个高电平就能响,编程比无源的方便。
    二、PWM和输出模式

    单纯和上次一样操作设置GPIO口的高低是没法实现输出PWM的。好在树莓派的某些PIN口有这种模式,那就是PIN12口。可以通过控制PIN12口的PWM模式来实现。就理解为方波把。由于pygpio暂不支持操作硬件的PWM。这里我们用wiringpi库。
    wiringPi中的pinMode (1,PWM_OUT),可以设置模式。PIN12是wiringpi的1号。

    图中t(pwm)就是一个周期的时间长度。对于2K频率来说,那么周期就是1S/2K=500us。图中的D叫做占空比。指的是高电平的时间占用整个周期时间的百分比。第一个周期D=50%,那么就是高电平低电平的时间各占一半。接下来的D为33%,那就是通电时间为33%,剩余的不通电时间占用67%。
    占空比的确会影响频率,但是我没有具体去探究会如何影响频率。我测试的时候使用的占空比是50%,也就是高低电平各占用一半的时间。
    由于可以参考的例子是在太少了。只能自己翻芯片手册查找相关资料。具体的相关资料在BCM2835芯片手册的第九章(具体翻阅手册查看,真是最好的办法)。阅读这一章以后我得出的关键点有如下几点:
    1 PWM的频率是受时钟管理器控制的,(树莓派的基础时钟频率是19.2MHZ)。
    2 PWM的输出占空比模式有两种,一种是平衡模式,一种是MS模式。
    先看占空比中的平衡模式和MS模式,假设我们希望输出的占空比为 N/M。
    平衡模式是指的按照某一种算法计算何时发送低电平,何时发送高电平,该算法力求任意一段时间占空比都最接近N/M,下图是(4/8的时候的几种发送方式),很显然good的算法任意取得一段时间都更加接近4/8。

    M/S模式就是整个S周期内,先发送M时间的高电平,剩余的S-M时间为低电平。

    因此如果是4/8的占空比。
    M/S模式8个时间长度内发送的就是 11110000 (周期为8个时间长度)。   而平衡模式则是 10101010(可以说最小周期为2个时间长度,大的周期为 8个时间长度)。
    可能看不懂没关系。用图来解释更有说服力。
    假设我们需要的频率为5KHZ,那么周期时间就是1s/5000hz=200us。设定占空比为 0.5 也就是高低电平各占一半,那么需要高电平占100us,低电平占100us。
    如果是平衡模式。一个大周期内(200us)波形图看起来如下:

    也就是这个大周期内,任意取一段时间占空比都接近0.5,其实实际频率比5K要大几倍。
    如果是MS模式。则看起来如下:

    显然这个才是我们需要的标准的5K频率。因为这个模式最小频率就是200us了。
    wiringPi中的pwmSetMode (PWM_MODE_MS) 可以设置为ms模式。
    前面说到树莓派基础时钟频率是19.2MHZ。pwm也受这个基础频率的控制,也就是最小的基础周期是1/19200000 S。这个周期太小了,我们控制蜂鸣器需要2-5K的频率。我们先将基础频率调大一些。通过pwmsetClock(int clock)可以将时钟基础频率设置为 19.2M/clock的大小。然后我们再基于这个频率通过pwmsetRasnge(int range)设置最终的频率,range的范围是2-4095。
    通过pwmsetClock(clock)以及pwmsetRasnge(range)将最终的频率控制在 19.2MHz/clock/range的大小。
    这里我设置clock为32 将时钟基础频率设置为19.2MHZ/32=600khz。
    这样我们只要设置range从300到120就能得到2k-5k的频率。
    那如何设置占空比呢?还有一个函数pwmWrite(value)。value指定了range指定的时间内发送高电平的基础周期个数(以时钟基础频率计算)。因此value/range就是占空比。pwmWrite(range/2)就能得到50%的占空比。range/5 就得到20%占空比。如果设置value为0,那么就是这段时期内一直是低电平,没有任何高电平,蜂鸣器就不发声了。
    验证一下如下图。

    50% (range/2)

    20% (range/5)

    三、代码设计

    初始化

    void init()
    {
      if (wiringPiSetup () == -1)
           exit (1) ;
      //设置针脚为pwm输出模式
      pinMode (1, PWM_OUTPUT) ;
    //设置pwm 信号模式为ms模式
      pwmSetMode(PWM_MODE_MS);
     //设置时钟基础频率为19.2M/32=600KHZ
      pwmSetClock(32);
    }
    

     封装beep函数以及beep的持续时间

    void beep(int freq,int t_ms)
    {
          int range;
          if(freq<2000||freq>5000)
          {
        printf("invalid freq");
        return;
          }
          //设置range为 600KHZ/freq。也就是由range个1/600KHZ组成了freq频率的周期。
          range=600000/freq。
          pwmSetRange(range);
          //设置占空比为50%。
          pwmWrite(1,range/2);
          if(t_ms>0)
          {
        delay(t_ms);
          }
    }
    

    通过delay来控制延时。
    通过 pwmWrite(1,0)来关闭输出。

    创建乐谱pwm.c

    #include <wiringPi.h>
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <stdint.h>
    
    
    
    typedef struct _TONE{
      int freq;
      int t_ms;
    } TONE,*PTONE;
    
    #define ONESEC 1000/2
    
    #define DO 2093
    #define RE  2349
    #define MI 2637
    #define FA 2794
    #define SO 3136
    #define LA 3520
    #define XI 3951
    #define DO1 4186
    #define RI1 4698
    
    TONE star_notation[]=
    {
    {DO,ONESEC},
    {DO,ONESEC},
    {SO,ONESEC},
    {SO,ONESEC},
    {LA,ONESEC},
    {LA,ONESEC},
    {SO,ONESEC*2},
    
    {FA,ONESEC},
    {FA,ONESEC},
    {MI,ONESEC},
    {MI,ONESEC},
    {RE,ONESEC},
    {RE,ONESEC},
    {DO,ONESEC*2},
    
    {SO,ONESEC},
    {SO,ONESEC},
    {FA,ONESEC},
    {FA,ONESEC},
    {MI,ONESEC},
    {MI,ONESEC},
    {RE,ONESEC*2},
    
    {SO,ONESEC},
    {SO,ONESEC},
    {FA,ONESEC},
    {FA,ONESEC},
    {MI,ONESEC},
    {MI,ONESEC},
    {RE,ONESEC*2},
    
    {DO,ONESEC},
    {DO,ONESEC},
    {SO,ONESEC},
    {SO,ONESEC},
    {LA,ONESEC},
    {LA,ONESEC},
    {SO,ONESEC*2},
    
    
    {FA,ONESEC},
    {FA,ONESEC},
    {MI,ONESEC},
    {MI,ONESEC},
    {RE,ONESEC},
    {RE,ONESEC},
    {DO,ONESEC*2},
    
    
    };
    
    
    void beep(int freq,int t_ms)
    {
          int range;
          if(freq<2000||freq>5000)
          {
    	printf("invalid freq");
    	return;
          }
          range=600000/freq;
          pwmSetRange(range);
          pwmWrite(1,range/2);
          if(t_ms>0)
          {
    	delay(t_ms);
          }
    }
    
    void init()
    {
      if (wiringPiSetup () == -1)
           exit (1) ;
      pinMode (1, PWM_OUTPUT) ;
      pwmSetMode(PWM_MODE_MS);
      pwmSetClock(32);
    }
    
    
    
    int main (void)
    {
      int index=0 ;
    
      init();
    
      for (;index<sizeof(star_notation)/sizeof(TONE);index++) 
      {
        beep(star_notation[index].freq,star_notation[index].t_ms);
        pwmWrite(1,0);
        delay(100);
      }
    
      pwmWrite(1,0);
      
     
      return 0 ;
    }
    

    BCM标号1(PIN12 )接无源蜂鸣器的正极,负极接GND。

    编译执行

    gcc -o pwm pwm.c -lwiringPi
    sudo ./pwm
    
  • 相关阅读:
    类图class的依赖关系
    ASP.NET MVC 5
    单例模式
    facebook .net sdk 应用
    跟我一起云计算(1)——storm
    C# 求精简用一行代码完成的多项判断 重复赋值
    语音播报实时天气
    滚动监听
    10277
    第十届蓝桥杯JavaB组省赛真题
  • 原文地址:https://www.cnblogs.com/sirius-swu/p/6823882.html
Copyright © 2011-2022 走看看