查看: 2517|回復: 15
打印 上一主題 下一主題

[項目] 復古留聲機式 arduino 音樂盒

[復制鏈接]
  • TA的每日心情
    奮斗
    2020-1-18 20:55
  • 簽到天數: 928 天

    [LV.10]以壇為家III

    跳轉到指定樓層
    樓主
    發表于 2019-4-30 17:42 | 只看該作者 |只看大圖 回帖獎勵 |倒序瀏覽 |閱讀模式


    前言

    無論是 arduino 還是 51 還是啥單片機或 STEAM 教育,很多的音樂方面的練習就是生硬的機械的刺耳的嗶嗶聲,如果讓單片機發出悅耳一點的聲音多好啊。比如清脆美妙的音樂盒的聲音。
    經過多番搜索,功夫不負有心人,找到了一些相關的內容能夠讓 arduino 發出漂亮的聲音。于是打算動手做一個音樂盒。
    電子的音樂盒自然想讓功能多一點。想到最早的留聲機那美麗的外觀,就連現在很多音樂盒也用的留聲機的外觀呢,那么我們的電子音樂盒也做成留聲機外觀吧。
    既然都做成留聲機外觀了,那不如也做出可以更換唱片的功能唄。其實就是機械的音樂盒也有可以更換唱片的,看上去非常的昂貴哦,也有用紙帶的,這兩種方式都是通過打孔來制作音樂音符。那么我們發揮電子的優勢,把打孔的音符簡化為用筆涂黑,使用反射式紅外傳感器(就是循跡小車的循跡傳感器)來檢測音符,從而播放自己畫出來的唱片的音樂。

    材料
    1.     arduino pro mini 一塊
    2.     360度云臺一個
    3.     360度舵機一個
    4.     喇叭一個,最好帶功放
    5.     電位器模塊一個
    6.     洞洞板 2 片
    7.     反射式紅外傳感器8個
    8.     2K可調電阻8個
    9.     支架一套
    10.  銅柱、螺絲若干
    11.  導線等焊接材料
    12.  排針及排母若干
    13.  做裝飾喇叭的牛皮紙一張
    14.  做唱片的白紙若干


    制作過程
    • 音符傳感器的制作
      硬件小白,在制作的時候一直打退堂鼓,為了使做出來的外觀像個留聲機/唱片機,檢測輸入音符的這個“唱臂”也要做的細一些,不能直接用整塊板,切這洞洞板就費了九牛之力,還要切成三片。這也算是平生第一次做的三層板了,還是立體的呢!把走線藏在里面,最后做出來的成品還算過得去,對于非專業的自己,對這個成果已經很滿意了。

    • 底座的制作
      機械小白,看到論壇有云臺硬件申請,看起來剛好像是一個唱片的機構,直接拿來當底座用,裝個電位器用來控制轉盤的旋轉速度,以根據不同的曲風控制樂曲的播放快慢,加一個支架用來裝“唱臂”,裝喇叭。測試的時候用的 arduino UNO,為了把所有部件都塞進底座,最后組裝的時候使用 arduino pro mini。為了連線,焊接了個洞洞板 IO 擴展板,這個其實非常簡單,就是把排針往洞洞板上焊上就行了。
    • 大喇叭的制作
      留聲機最大的特點就是有一個大喇叭,3D 小白,不會建模,就用紙張做一個嘍。那天在垃圾桶發現丟棄的一個文件袋,這顏色還真是和留聲機的銅喇叭有幾分相似,就從垃圾桶里撿出了這個袋子,經過嘗試,剪剪粘粘做出了一個大喇叭。當然,這僅僅是一個外觀的裝飾用的,如果真正的那個喇叭可以調整安裝位置的話,這個裝飾的喇叭倒是可以用來做個擴音的功能。
    • 組裝成品及繪制唱片
      唱片根據云臺的大小切個圓紙片,根據傳感器的大小距離畫出八個同心圓,然后用量角器等分圓。分的太細的話,對傳感器來說精度和靈敏度要求比較高,最后還是用一個不那么細分的唱片來測試,上面畫的是《小星星》的音符,才兩句半的樂曲,將就一下吧……這里沒有體現出可以同時發出多個音符的特點,實際上可以同時涂三個音哦。把大喇叭裝上,拍一張唯美的照片,感覺還過得去啊。
      注意傳感器陣列那里每兩個之間用了個黑紙隔開,以避免鄰近的紅外光的干擾。
      在弄這個傳感器的時候突然有一個大膽的想法,直接拿一個條碼掃碼器來掃描應該也是個好主意,精度可以更好吶!


    程序流程
    • 參考大神們的實現,調整和封裝了一下音符播放的功能
    • 讀取傳感器狀態,播放對應的音符
    • 讀取電位器數據,控制轉速

    代碼
    這里附上兩個代碼,
    一個是不需要唱片輸入的程序內置音樂的程序,簡單起見只固定一首樂曲,大家可以參考格式自己輸入喜歡的音樂哦。這里的示例音樂是《千與千尋》的《與我同在》,挑的簡單版本,沒有很多的伴奏音,只有偶爾的雙音,就這樣已經讓我手工輸入了很久,保守估計40分鐘吧,需要做一個工具來簡化輸入啊。網上參考來源的大神用的是 midi 文件導出的方式,我是為了做唱片輸入,所以將格式做了調整,兩份代碼的音樂格式不通用哦。
    另一個是使用手繪唱片來輸入播放樂曲的代碼,不過由于電機聲挺大的,其實最終效果不怎么理想,僅僅是實現它,驗證了一下想法。
    大家可以使用第一個程序來做實驗玩,只需要拿個喇叭一頭接地一頭接 UNO 的 11 引腳就可以開始了。
    完整程序大家下載附件,下面展示主程序,頭文件比較長影響版面就略過。
    ArduinoMusicBoxFixed.zip (9.13 KB, 下載次數: 19)
    ArduinoMusicBox.zip (8.11 KB, 下載次數: 10)


    • 內置音樂版本的代碼

      kittenblock中小學創客名師推薦的圖形化編程軟件

      // ArduinoMusicBoxFixed.ino
      // 實現一個 Arduino 音樂盒,內置固定音樂版本
      // By seesea, 2019-03-20
      //
      // 很多的 arduino 示例做音樂播放或電子琴什么的,就只是一個 tone() 嗶嗶嗶的聲音,就想著怎樣才能讓 arduino 播放好聽一點的聲音呢。
      // 在逛一些論壇的時候有人在討論日本的一個大神做的 avr 程序播放的很好聽,但是沒有源碼。
      // 后來多番搜索有了一些收獲,下面是幾個比較相關的鏈接可以參考,按相關程度排序:
      // 1. arduino 音樂盒播放(頁面末有代碼下載鏈接),本程序參考來源:
      //     https://www.nutsvolts.com/magazine/article/april2012_Lindley
      // 2. arduino wav 聲音播放器,arduino 聯合創始人的文章,值得一看,上一鏈接作者的代碼我看應該很大程度參考自這里:
      //     https://www.elektormagazine.com/files/attachment/331
      // 3. makezine 的教學,淺顯易懂,推薦學習:
      //     https://makezine.com/projects/ma ... no-sound-synthesis/
      // 4. 日本大神的音樂盒,主要是原理介紹值得學習:
      //     http://elm-chan.org/works/mxb/report.html
      // 5. 直接數字合成器,理論講解很詳細:
      //     http://www.cs.nott.ac.uk/~pszjm2/?p=674
      // 6. 聲音采樣知識介紹:
      //     http://www.quintadicopertina.com ... ng-digitized-audio/
      // 7. arduino 聲音合成庫,功能強大:
      //     http://sensorium.github.io/Mozzi
      
      #include <stdint.h>
      #include <avr/interrupt.h>
      #include <avr/io.h>
      #include <avr/pgmspace.h>
      #include <util/atomic.h>
      #include "wavetableData.h"
      #include "pitchs.h"
      #include "songData.h"
      
      #define AUDIO_OUTPUT_PIN        11
      #define GENERATOR_COUNT         2
      #define SAMPLE_RATE             32000
      #define SUSTAIN_LENGTH          128
      #define SHIFT_BIT_NUM           8
      #define SENSOR_COUNT            8
      #define POTENTIOMETER_PIN       A0
      #define MOTOR_PWM_PIN           6
      
      const uint16_t WAVETABLE_LENGTH    = sizeof(waveTableData) / sizeof(waveTableData[0]);
      const uint16_t ENVELOPTABLE_LENGTH = sizeof(envelopeData) / sizeof(envelopeData[0]);
      
      // 注意,使用8個傳感器,如果不是,需要調整相關代碼
      // 功能按順序為:高八度標記,音符 7 6 5 4 3 2 1
      const uint8_t sensorPins[SENSOR_COUNT] = { 2, 3, 4, 5, 7, 8, 9, 10 };
      
      // 聲音合成器結構
      // 包括:相位步進,相位累計,包絡數據表的索引
      // 后續如需擴展多樂器什么的,可以在這里增加樂器波表指針
      struct
      {
          uint16_t phaseStep;
          uint32_t phaseAccumulator;
          uint16_t envelopeIndex;
      } generators[GENERATOR_COUNT];
      
      // 初始化
      void systemInit()
      {
          // cli();
      
          pinMode(AUDIO_OUTPUT_PIN, OUTPUT);
          digitalWrite(AUDIO_OUTPUT_PIN, LOW);
      
          initSensorPins();
      
          for (int i = 0; i < GENERATOR_COUNT; i++)
          {
              generators.phaseStep        = 0;
              generators.phaseAccumulator = 0;
              generators.envelopeIndex    = 0;
          }
      
          initTimers();
      
          // sei();
      }
      
      // 配置定時器
      // 注意此函數調用前關中斷
      // 比較底層,直接照搬大神的
      // 參考:
      //      https://www.nutsvolts.com/magazine/article/april2012_Lindley
      //      https://www.elektormagazine.com/files/attachment/331
      void initTimers()
      {
          /*
          * Timer 1 Configuration
          * Set up Timer 1 to generate interrupt at SAMPLE_RATE.
          */
          // Set CTC mode (Clear Timer on Compare Match)
          // Have to set OCR1A *after*, otherwise it gets reset to 0!
          TCCR1B = (TCCR1B & ~_BV(WGM13)) | _BV(WGM12);
          TCCR1A = TCCR1A & ~(_BV(WGM11) | _BV(WGM10));
      
          // No prescaler
          TCCR1B = (TCCR1B & ~(_BV(CS12) | _BV(CS11))) | _BV(CS10);
      
          // Set the compare register (OCR1A).
          // OCR1A is a 16-bit register, so we have to do this with
          // interrupts disabled to be safe.
          OCR1A = F_CPU / SAMPLE_RATE;    // 16e6 / 32000 = 500
      
          // Enable interrupt when TCNT1 == OCR1A
          TIMSK1 |= _BV(OCIE1A);
      
          /*
          * Timer 2 Configuration
          * Set up Timer 2 to do pulse width modulation at frequency of 62,500 Hz
          */
          // Set fast PWM mode
          TCCR2A |= _BV(WGM21) | _BV(WGM20);
          TCCR2B &= ~_BV(WGM22);
      
          // Do non-inverting PWM on pin OC2A. Arduino pin 11
          TCCR2A = (TCCR2A | _BV(COM2A1)) & ~_BV(COM2A0);
          TCCR2A &= ~(_BV(COM2B1) | _BV(COM2B0));
      
          // No prescaler so PWM frequency is 16000000 / 256 or 62,500 Hz
          TCCR2B = (TCCR2B & ~(_BV(CS12) | _BV(CS11))) | _BV(CS10);
      
          // Set initial pulse width to zero.
          OCR2A = 0;
      }
      
      // 定時器 1 的中斷處理函數
      // 參考:https://www.nutsvolts.com/magazine/article/april2012_Lindley
      ISR(TIMER1_COMPA_vect)
      {
      
          int32_t  pwmSample = 0;
          uint32_t phase;
          uint32_t waveTableIndex;
          int32_t  sampleValue;
          uint8_t  envlopeValue;
      
          for (uint8_t i = 0; i < GENERATOR_COUNT; i++)
          {
              phase          = generators.phaseAccumulator;        // 取出步進累計值
              waveTableIndex = (phase >> SHIFT_BIT_NUM);              // 去除低位做為波表的索引
              sampleValue    = (int8_t) pgm_read_byte(waveTableData + waveTableIndex);
              envlopeValue   = pgm_read_byte(envelopeData + generators.envelopeIndex);
              pwmSample     += sampleValue * envlopeValue;            // 把波形值和包絡相乘做為 pwm 的參數(需去除低位),注意的是這是所有的聲音合成器的綜合
      
              // 累加相位步進值
              // 如果到波表的結尾了,則開始增加包絡的索引,進行聲音大小的改變
              generators.phaseAccumulator += generators.phaseStep;
              if (generators.phaseAccumulator >= ((uint32_t) WAVETABLE_LENGTH) << SHIFT_BIT_NUM)
              {
                  generators.phaseAccumulator -= (((uint32_t) SUSTAIN_LENGTH) << SHIFT_BIT_NUM);
      
                  if (generators.envelopeIndex < ENVELOPTABLE_LENGTH - 1)
                  {
                      generators.envelopeIndex++;
                  }
              }
          }
      
          // 去除低位,留高位,縮小數值到波表的長度內
          pwmSample >>= (SHIFT_BIT_NUM + 2);
      
          // 加上偏移量避免負數后,設置給定時器 2
          OCR2A = (uint8_t)(127 + pwmSample);
      }
      
      // 將音符頻率換算為相位步進值
      // 計算公式為:2^32 * 頻率 / 采樣率
      // 2^32 來源是相位累加 generator_t.phaseAccumulator 是 32 位的
      inline unsigned int hzToPhaseStep(float hz)
      {
          return (unsigned int) (hz * 4.096);
      }
      
      // 傳入音符對應的頻率來播放音符
      void playPitch(uint16_t pitchFreq)
      {
          static int8_t generatorIndex = 0;
          uint16_t step = hzToPhaseStep(pitchFreq);
      
          ++generatorIndex;
          if (generatorIndex >= GENERATOR_COUNT) {
              generatorIndex = 0;
          }
      
          // 注意關中斷進行設置
          cli();
          generators[generatorIndex].phaseStep        = step;
          generators[generatorIndex].phaseAccumulator = 0;
          generators[generatorIndex].envelopeIndex    = 0;
          sei();
      }
      
      // 初始化傳感器引腳,設置為內部上拉的輸入
      void initSensorPins()
      {
          for (int i = 0; i < SENSOR_COUNT; ++i)
          {
              pinMode(sensorPins, INPUT_PULLUP);
          }
      }
      
      // 目前限制在8個傳感器,所以用8位的變量返回值
      // 返回值的每一位表示一個傳感器的狀態
      uint8_t getAllSensorsInput()
      {
          uint8_t sensorsStatus = 0;
      
          for (int i = 0; i < SENSOR_COUNT; ++i)
          {
              sensorsStatus <<= 1;
      
              if (digitalRead(sensorPins) == LOW)
              {
                  sensorsStatus |= 0x01;
              }
          }
      
          return sensorsStatus;
      }
      
      void readSensorAndPlayNote()
      {
          // 存放至少 16 個音符,這里為兩個八度 C 大調音階,可根據自己的樂曲定制相應的音符
          static const uint16_t noteBox[] = {
              NOTE_C3, NOTE_D3, NOTE_E3, NOTE_F3, NOTE_G3, NOTE_A3, NOTE_B3,
              NOTE_C4, NOTE_D4, NOTE_E4, NOTE_F4, NOTE_G4, NOTE_A4, NOTE_B4
          };
      
          static uint8_t lastSensorStatus = 0;
          uint8_t sensorStatus = getAllSensorsInput();
          uint8_t pitchOffset  = 0;
      
          // 除了高八度標記位外的其它音符位沒有變化的時候,認為還沒掃描到下一次音符輸入,不做任何操作
          if ((0xEF & sensorStatus) == lastSensorStatus)
              return;
      
          // 高八度
          if ((0x80 & sensorStatus) != 0)
              pitchOffset = 7;
      
          // 從低到高檢查每一位標志來進行音符播放
          sensorStatus &= 0xEF;
          for (uint8_t i = 0; i < SENSOR_COUNT - 1; ++i)
          {
              if (sensorStatus & 0x01)
              {
                  playPitch(noteBox[i + pitchOffset]);
              }
              sensorStatus >>= 1;
          }
      
          // 記錄舊值用于下次比較
          // 注意:sensorStatus 已經是 sensorStatus & 0xEF 了
          lastSensorStatus = sensorStatus;
      }
      
      void playSong(const uint16_t *songAddr)
      {    
          int index = 0;
          uint16_t note = 0;
          uint16_t beatTime = 0;
          unsigned long lastTime = millis();
      
          while (true)
          {
              delay(beatTime);
              /*
              Serial.println(millis());
              Serial.println("-----------");
              if (millis() - lastTime < beatTime)
                  continue;
              */
              
              note     = pgm_read_word_near(songAddr + index);
              beatTime = pgm_read_word_near(songAddr + index + 1);
      
              if (note < 0)
                  break;
      
              playPitch(note);
              index += 2;
              lastTime = millis();
              /*
              Sefrial.println(note);
              Serial.println(beatTime);
              Serial.println(lastTime);
              */
          }
      }
      void setup()
      {
          Serial.begin(115200);
          delay(1000);
          systemInit();
          playSong(songAlwaysWithMe);
      }
      
      void loop()
      {
      
      }
      
    • 唱片式音樂盒代碼

      kittenblock中小學創客名師推薦的圖形化編程軟件

      // ArduinoMusicBox.ino
      // 實現一個 Arduino 音樂盒
      // By seesea, 2019-03-20
      //
      // 很多的 arduino 示例做音樂播放或電子琴什么的,就只是一個 tone() 嗶嗶嗶的聲音,就想著怎樣才能讓 arduino 播放好聽一點的聲音呢。
      // 在逛一些論壇的時候有人在討論日本的一個大神做的 avr 程序播放的很好聽,但是沒有源碼。
      // 后來多番搜索有了一些收獲,下面是幾個比較相關的鏈接可以參考,按相關程度排序:
      // 1. arduino 音樂盒播放(頁面末有代碼下載鏈接),本程序參考來源:
      //     https://www.nutsvolts.com/magazine/article/april2012_Lindley
      // 2. arduino wav 聲音播放器,arduino 聯合創始人的文章,值得一看,上一鏈接作者的代碼我看應該很大程度參考自這里:
      //     https://www.elektormagazine.com/files/attachment/331
      // 3. makezine 的教學,淺顯易懂,推薦學習:
      //     https://makezine.com/projects/ma ... no-sound-synthesis/
      // 4. 日本大神的音樂盒,主要是原理介紹值得學習:
      //     http://elm-chan.org/works/mxb/report.html
      // 5. 直接數字合成器,理論講解很詳細:
      //     http://www.cs.nott.ac.uk/~pszjm2/?p=674
      // 6. 聲音采樣知識介紹:
      //     http://www.quintadicopertina.com ... ng-digitized-audio/
      // 7. arduino 聲音合成庫,功能強大:
      //     http://sensorium.github.io/Mozzi
      
      #include <stdint.h>
      #include <avr/interrupt.h>
      #include <avr/io.h>
      #include <avr/pgmspace.h>
      #include <util/atomic.h>
      #include "wavetableData.h"
      #include "pitchs.h"
      
      #define AUDIO_OUTPUT_PIN        11
      #define GENERATOR_COUNT         3
      #define SAMPLE_RATE             32000
      #define SUSTAIN_LENGTH          128
      #define SHIFT_BIT_NUM           8
      #define SENSOR_COUNT            8
      #define POTENTIOMETER_PIN       A0
      #define MOTOR_PWM_PIN           6
      
      const uint16_t WAVETABLE_LENGTH    = sizeof(waveTableData) / sizeof(waveTableData[0]);
      const uint16_t ENVELOPTABLE_LENGTH = sizeof(envelopeData) / sizeof(envelopeData[0]);
      
      // 注意,使用8個傳感器,如果不是,需要調整相關代碼
      // 功能按順序為:高八度標記,音符 7 6 5 4 3 2 1
      const uint8_t sensorPins[SENSOR_COUNT] = { 2, 3, 4, 5, 7, 8, 9, 10 };
      
      // 聲音合成器結構
      // 包括:相位步進,相位累計,包絡數據表的索引
      // 后續如需擴展多樂器什么的,可以在這里增加樂器波表指針
      struct
      {
          uint16_t phaseStep;
          uint32_t phaseAccumulator;
          uint16_t envelopeIndex;
      } generators[GENERATOR_COUNT];
      
      // 初始化
      void systemInit()
      {
          cli();
      
          pinMode(AUDIO_OUTPUT_PIN, OUTPUT);
          digitalWrite(AUDIO_OUTPUT_PIN, LOW);
      
          initSensorPins();
      
          for (int i = 0; i < GENERATOR_COUNT; i++)
          {
              generators.phaseStep        = 0;
              generators.phaseAccumulator = 0;
              generators.envelopeIndex    = 0;
          }
      
          initTimers();
      
          sei();
      }
      
      // 配置定時器
      // 注意此函數調用前關中斷
      // 比較底層,直接照搬大神的
      // 參考:
      //      https://www.nutsvolts.com/magazine/article/april2012_Lindley
      //      https://www.elektormagazine.com/files/attachment/331
      void initTimers()
      {
          /*
          * Timer 1 Configuration
          * Set up Timer 1 to generate interrupt at SAMPLE_RATE.
          */
          // Set CTC mode (Clear Timer on Compare Match)
          // Have to set OCR1A *after*, otherwise it gets reset to 0!
          TCCR1B = (TCCR1B & ~_BV(WGM13)) | _BV(WGM12);
          TCCR1A = TCCR1A & ~(_BV(WGM11) | _BV(WGM10));
      
          // No prescaler
          TCCR1B = (TCCR1B & ~(_BV(CS12) | _BV(CS11))) | _BV(CS10);
      
          // Set the compare register (OCR1A).
          // OCR1A is a 16-bit register, so we have to do this with
          // interrupts disabled to be safe.
          OCR1A = F_CPU / SAMPLE_RATE;    // 16e6 / 32000 = 500
      
          // Enable interrupt when TCNT1 == OCR1A
          TIMSK1 |= _BV(OCIE1A);
      
          /*
          * Timer 2 Configuration
          * Set up Timer 2 to do pulse width modulation at frequency of 62,500 Hz
          */
          // Set fast PWM mode
          TCCR2A |= _BV(WGM21) | _BV(WGM20);
          TCCR2B &= ~_BV(WGM22);
      
          // Do non-inverting PWM on pin OC2A. Arduino pin 11
          TCCR2A = (TCCR2A | _BV(COM2A1)) & ~_BV(COM2A0);
          TCCR2A &= ~(_BV(COM2B1) | _BV(COM2B0));
      
          // No prescaler so PWM frequency is 16000000 / 256 or 62,500 Hz
          TCCR2B = (TCCR2B & ~(_BV(CS12) | _BV(CS11))) | _BV(CS10);
      
          // Set initial pulse width to zero.
          OCR2A = 0;
      }
      
      // 定時器 1 的中斷處理函數
      // 參考:https://www.nutsvolts.com/magazine/article/april2012_Lindley
      ISR(TIMER1_COMPA_vect)
      {
      
          int32_t  pwmSample = 0;
          uint32_t phase;
          uint32_t waveTableIndex;
          int32_t  sampleValue;
          uint8_t  envlopeValue;
      
          for (uint8_t i = 0; i < GENERATOR_COUNT; i++)
          {
              phase          = generators.phaseAccumulator;        // 取出步進累計值
              waveTableIndex = (phase >> SHIFT_BIT_NUM);              // 去除低位做為波表的索引
              sampleValue    = (int8_t) pgm_read_byte(waveTableData + waveTableIndex);
              envlopeValue   = pgm_read_byte(envelopeData + generators.envelopeIndex);
              pwmSample     += sampleValue * envlopeValue;            // 把波形值和包絡相乘做為 pwm 的參數(需去除低位),注意的是這是所有的聲音合成器的綜合
      
              // 累加相位步進值
              // 如果到波表的結尾了,則開始增加包絡的索引,進行聲音大小的改變
              generators.phaseAccumulator += generators.phaseStep;
              if (generators.phaseAccumulator >= ((uint32_t) WAVETABLE_LENGTH) << SHIFT_BIT_NUM)
              {
                  generators.phaseAccumulator -= (((uint32_t) SUSTAIN_LENGTH) << SHIFT_BIT_NUM);
      
                  if (generators.envelopeIndex < ENVELOPTABLE_LENGTH - 1)
                  {
                      generators.envelopeIndex++;
                  }
              }
          }
      
          // 去除低位,留高位,縮小數值到波表的長度內
          pwmSample >>= (SHIFT_BIT_NUM + 2);
      
          // 加上偏移量避免負數后,設置給定時器 2
          OCR2A = (uint8_t)(127 + pwmSample);
      }
      
      // 將音符頻率換算為相位步進值
      // 計算公式為:2^32 * 頻率 / 采樣率
      // 2^32 來源是相位累加 generator_t.phaseAccumulator 是 32 位的
      inline unsigned int hzToPhaseStep(float hz)
      {
          return (unsigned int) (hz * 4.096);
      }
      
      // 傳入音符對應的頻率來播放音符
      void playPitch(uint16_t pitchFreq)
      {
          static int8_t generatorIndex = 0;
          uint16_t step = hzToPhaseStep(pitchFreq);
      
          ++generatorIndex;
          if (generatorIndex >= GENERATOR_COUNT) {
              generatorIndex = 0;
          }
      
          // 注意關中斷進行設置
          cli();
          generators[generatorIndex].phaseStep        = step;
          generators[generatorIndex].phaseAccumulator = 0;
          generators[generatorIndex].envelopeIndex    = 0;
          sei();
      }
      
      // 初始化傳感器引腳,設置為內部上拉的輸入
      void initSensorPins()
      {
          for (int i = 0; i < SENSOR_COUNT; ++i)
          {
              pinMode(sensorPins, INPUT_PULLUP);
          }
      }
      
      // 目前限制在8個傳感器,所以用8位的變量返回值
      // 返回值的每一位表示一個傳感器的狀態
      uint8_t getAllSensorsInput()
      {
          uint8_t sensorsStatus = 0;
      
          for (int i = 0; i < SENSOR_COUNT; ++i)
          {
              sensorsStatus <<= 1;
      
              if (digitalRead(sensorPins) == LOW)
              {
                  sensorsStatus |= 0x01;
              }
          }
      
          return sensorsStatus;
      }
      
      void readSensorAndPlayNote()
      {
          // 存放至少 16 個音符,這里為兩個八度 C 大調音階,可根據自己的樂曲定制相應的音符
          static const uint16_t noteBox[] = {
              NOTE_C3, NOTE_D3, NOTE_E3, NOTE_F3, NOTE_G3, NOTE_A3, NOTE_B3,
              NOTE_C4, NOTE_D4, NOTE_E4, NOTE_F4, NOTE_G4, NOTE_A4, NOTE_B4
          };
      
          static uint8_t lastSensorStatus = 0;
          uint8_t sensorStatus = getAllSensorsInput();
          uint8_t pitchOffset  = 0;
      
          // 除了高八度標記位外的其它音符位沒有變化的時候,認為還沒掃描到下一次音符輸入,不做任何操作
          if ((0xEF & sensorStatus) == lastSensorStatus)
              return;
      
          // 高八度
          if ((0x80 & sensorStatus) != 0)
              pitchOffset = 7;
      
          // 從低到高檢查每一位標志來進行音符播放
          sensorStatus &= 0xEF;
          for (uint8_t i = 0; i < SENSOR_COUNT - 1; ++i)
          {
              if (sensorStatus & 0x01)
              {
                  playPitch(noteBox[i + pitchOffset]);
              }
              sensorStatus >>= 1;
          }
      
          // 記錄舊值用于下次比較
          // 注意:sensorStatus 已經是 sensorStatus & 0xEF 了
          lastSensorStatus = sensorStatus;
      }
      
      void setup()
      {
          systemInit();
          Serial.begin(9600);
      }
      
      void loop()
      {
          readSensorAndPlayNote();
          analogWrite(MOTOR_PWM_PIN, map(analogRead(POTENTIOMETER_PIN), 0, 1024, 0, 255));
      }
      



    演示視頻
    http://player.youku.com/player.php/sid/XNDE3MjMyODE0NA==/v.swf

    參考文檔
    按與本制作相關程度排序


  • TA的每日心情
    開心
    2019-4-30 18:23
  • 簽到天數: 1 天

    [LV.1]初來乍到

    沙發
    發表于 2019-4-30 18:29 | 只看該作者
    哇哦,不錯哦,感謝樓主分享

    該用戶從未簽到

    板凳
    發表于 2019-4-30 20:39 | 只看該作者
    很有意思的設計啊
  • TA的每日心情
    奮斗
    2020-1-18 20:55
  • 簽到天數: 928 天

    [LV.10]以壇為家III

    地板
     樓主| 發表于 2019-4-30 21:27 | 只看該作者
    小明來了 發表于 2019-4-30 18:29
    哇哦,不錯哦,感謝樓主分享

    謝謝支持,很有趣,也試試吧
  • TA的每日心情
    奮斗
    2020-1-18 20:55
  • 簽到天數: 928 天

    [LV.10]以壇為家III

    5#
     樓主| 發表于 2019-4-30 21:27 | 只看該作者
    Zoologist 發表于 2019-4-30 20:39
    很有意思的設計啊

    :D 謝謝支持,咱就搞點小玩意兒,各位大神才是玩大的
  • TA的每日心情
    奮斗
    2019-5-5 21:49
  • 簽到天數: 208 天

    [LV.7]常住居民III

    6#
    發表于 2019-5-3 04:23 | 只看該作者
    大佬的代碼總是那么牛逼閃閃!
  • TA的每日心情
    奮斗
    2020-1-18 20:55
  • 簽到天數: 928 天

    [LV.10]以壇為家III

    7#
     樓主| 發表于 2019-5-3 13:29 來自手機 | 只看該作者
    mostblack 發表于 2019-5-3 04:23
    大佬的代碼總是那么牛逼閃閃!

    不敢裝逼不敢閃
  • TA的每日心情
    奮斗
    2020-1-18 20:55
  • 簽到天數: 928 天

    [LV.10]以壇為家III

    9#
     樓主| 發表于 2019-5-3 22:13 | 只看該作者

    過獎過獎,都是大神們做好的,改造一下
  • TA的每日心情
    郁悶
    2019-10-11 17:27
  • 簽到天數: 1 天

    [LV.1]初來乍到

    10#
    發表于 2019-10-11 17:34 | 只看該作者
    謝謝樓主分享!
    您需要登錄后才可以回帖 登錄 | 立即注冊

    本版積分規則

    熱門推薦

    [限時福利]5分鐘帶你快速了解新一代開發板:M5STACK
    [限時福利]5分鐘帶你快速
    一、什么是M5Stack M5Stack是一種模塊化、可堆疊擴展的開發板,每個模塊
    Arduino使用電阻分壓測量電池電壓問題
    Arduino使用電阻分壓測量
    請教,下圖中的連接有什么問題,如何調整? 讓只有一塊電池的條件下,解決測量電壓不
    上傳一個藍牙串口助手,安卓版的
    上傳一個藍牙串口助手,安
    /* 本軟件為藍牙串口通訊工具,可與藍牙模塊(如:HC-05)建立連接,進行串口通訊,可
    藍牙串口助手 1.3 beta 1測試版
    藍牙串口助手 1.3 beta 1
    在之前版本的基礎上增加了視覺相關功能: 顏色跟蹤模式及人臉跟蹤模式, 增加了發
    萌新跪求arduinoUNO板對接無線模塊(如何接和程序)
    萌新跪求arduinoUNO板對接
    哪位dalao能幫幫我?????急?。?! (提供有償服務可加我QQ3285396460)
    Copyright   ©2015-2016  Arduino中文社區  Powered by©Discuz!   
    快速回復 返回頂部 返回列表
    北京快乐8论坛