• 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
    
  • 相关阅读:
    【PAT甲级】1043 Is It a Binary Search Tree (25 分)(判断是否为BST的先序遍历并输出后序遍历)
    Educational Codeforces Round 73 (Rated for Div. 2)F(线段树,扫描线)
    【PAT甲级】1042 Shuffling Machine (20 分)
    【PAT甲级】1041 Be Unique (20 分)(多重集)
    【PAT甲级】1040 Longest Symmetric String (25 分)(cin.getline(s,1007))
    【PAT甲级】1039 Course List for Student (25 分)(vector嵌套于map,段错误原因未知)
    Codeforces Round #588 (Div. 2)E(DFS,思维,__gcd,树)
    2017-3-9 SQL server 数据库
    2017-3-8 学生信息展示习题
    2017-3-5 C#基础 函数--递归
  • 原文地址:https://www.cnblogs.com/sirius-swu/p/6823882.html
Copyright © 2020-2023  润新知