2017年7月30日 星期日

MicroPython on ESP8266 (十六) : 蜂鳴器測試

之前的 MicroPython 測試都是以 LED 為輸出對象, 局限於光訊號之呈現, 本篇要複製之前的 Arduino 聲音測試, 利用蜂鳴器 (Buzzer) 來測試 ESP8266 GPIO 的 PWM 聲音輸出功能, 參考 :

# Arduino 的聲音測試 (一)

本系列之前的測試紀錄參考 :

MicroPython on ESP8266 (二) : 數值型別測試
MicroPython on ESP8266 (三) : 序列型別測試
MicroPython on ESP8266 (四) : 字典與集合型別測試
MicroPython on ESP8266 (五) : WiFi 連線與 WebREPL 測試
MicroPython on ESP8266 (六) : 檔案系統測試
MicroPython on ESP8266 (七) : 時間日期測試
MicroPython on ESP8266 (八) : GPIO 測試
MicroPython on ESP8266 (九) : PIR 紅外線移動偵測
MicroPython on ESP8266 (十) : socket 模組測試
MicroPython on ESP8266 (十一) : urllib.urequest 模組測試
MicroPython on ESP8266 (十二) : urequests 模組測試
MicroPython on ESP8266 (十三) : DHT11 溫溼度感測器測試
MicroPython on ESP8266 (十四) : 網頁伺服器測試
# MicroPython on ESP8266 (十五) : 光敏電阻與 ADC 測試

MicroPython 文件參考 :

http://docs.micropython.org/en/latest/micropython-esp8266.pdf
http://docs.micropython.org/en/latest/pyboard/library/usocket.html#class-socket
http://docs.micropython.org/en/v1.8.7/esp8266/library/usocket.html#module-usocket

以下我採用了 WeMOS D1 Mini (ESP-12 模組) 進行測試, 因為這塊板子有內建 Micro USB 插槽,具備 4MB Flash 記憶體, 同時 D4 針腳 (GPIO 2) 有板上內建 LED, 隨插即可進行物聯網專案測試開發. 關於 D1 Mini 參考 :

# WeMOS D1 Mini 開發板測試

其針腳編號 D0~D8 與 ESP8266 GPIO 之對應如下 :

 板子腳位 ESP8266 腳位 功能
 D0 GPIO16 IO
 D1 GPIO5 IO, SCL 
 D2 GPIO4 IO, SDA 
 D3 GPIO0 IO, 內建 10K 上拉電阻
 D4 GPIO2 IO, 內建 10K 上拉電阻與 LED
 D5 GPIO14 IO, SCK
 D6 GPIO12 IO, MISO
 D7 GPIO13 IO, MOSI
 D8 GPIO15 IO, SS, 內建 10K 上拉電阻
 TX TXD UART 送端
 RX RXD UART 收端
 A0 ADC 類比輸入 (0~3.3V)

ESP8266 GPIO 的 PWM 功能測試已紀錄在上述第八篇文章中, 簡言之, 就是利用 MicroPython 的 machine 模組之 PWM 類別, 傳入指定 GPIO 腳之 Pin 物件來建立 PWM 物件, 再呼叫 PWM 物件之 freq() 與 duty() 方法來調變輸出之方波, 例如 :

from machine import Pin, PWM
pwm2=PWM(Pin(2))   #建立 GPIO 2 的 PWM 物件
pwm2.freq(300)           #設定 PWM 物件之頻率  
pwm2.duty(512)           #設定 PWM 物件之工作週期

其中 freq() 與 duty() 若未傳入參數則為 getter, 即讀取最近之設定值. 若要停止 PWM 功能, 則呼叫 deinit() 方法即可, 相當於 Arduino 的 noTone() 函數功能. PWM 物件之方法表列如下 :

 PWM 方法 說明
 freq([Hz]) 傳入 0~1000 設定方波頻率, 否則為讀取
 duty([cycle]) 傳入 0~1023 設定 Duty cycle (512 為 50%), 否則為讀取
 deinit() 關閉 PWM 功能

參考官網文件 :

# PWM (pulse width modulation)

注意, ESP8266 的 GPIO 中, 只有 GPIO 16 (即 D1 Mini 的 D0) 不具 PWM 功能 :

"PWM can be enabled on all pins except Pin(16). There is a single frequency for all channels, with range between 1 and 1000 (measured in Hz). The duty cycle is between 0 and 1023 inclusive."

如果傳入 Pin(16) 給 PWM() 建構式無法建立 PWM 物件 :

>>> pwm=PWM(Pin(16))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: PWM not supported on pin 16  

接下來我想複製 Arduino 聲音測試的範例, 改編為 MicroPython on ESP8266 版. 首先是讓蜂鳴器發出 1KHz 的 Bee Bee 聲, 硬體接線上只要將無源蜂鳴器的 + 端接在 D1 Mini 的 D2 腳 (GPIO 4), 另一端接地即可 (無源蜂鳴器是有極性的). 雖然蜂鳴器上有標示 5V 規格, 其實 3.3V 也是可以用的.

測試 1 : 使蜂鳴器發出 1KHz 的 Bee Bee 告警聲

#main.py or myapp.py
from machine import Pin,PWM
import time

def alarmBeep(pwm):
   pwm.freq(1000)     #設定頻率為 1KHz    
   pwm.duty(512)      #設定工作週期為 50%
   time.sleep(1)          #持續時間 1 秒
   pwm.deinit()          #停止發聲
   time.sleep(2)          #持續時間 2 秒

pwm=PWM(Pin(4))

while True:
    alarmBeep(pwm)


上面的程式裡, 我將發聲的程式碼寫成一個函數 alarmBeep(), 只要在無窮迴圈中呼叫此函數, 並將 PWM 物件傳給它即可.

測試 2 : 模擬鬧鐘鈴聲

from machine import Pin,PWM
import time

def alarmClockBeep(pwm):
   for i in range(1,5):        #4 次迴圈 (1~4)
       pwm.freq(1000)       #設定頻率為 1KHz  
       pwm.duty(512)         #設定工作週期為 50%
       time.sleep_ms(100)  #持續時間 0.1 秒
       pwm.deinit()             #停止發聲
       time.sleep_ms(200)   #持續時間 0.2 秒
   time.sleep_ms(800)

pwm=PWM(Pin(4))

while True:
    alarmClockBeep(pwm)

這裡要注意的是 range(a, b) 是傳回 a ~ b-1 的整數, 所以 4 次迴圈需用 range(1, 5).

測試 3 : 模擬電話鈴聲

from machine import Pin,PWM
import time

def ringTone(pwm):
   for i in range(1,11):         #10 次迴圈 (1~10)
       pwm.freq(1000)          #設定頻率為 1KHz
       pwm.duty(512)           #設定工作週期為 50%
       time.sleep_ms(50)      #持續時間 50 毫秒
       pwm.freq(500)            #設定頻率為 500Hz
       time.sleep_ms(50)      #持續時間 50 毫秒
   pwm.deinit()                   #停止發聲
   time.sleep(2)                   #持續時間 2 秒

pwm=PWM(Pin(4))

while True:
    ringTone(pwm)

這電話聲模擬得還真像.

測試 4 : 模擬警車鈴聲

from machine import Pin,PWM
import time

def policeSiren(pwm):
   for i in range(150,1800):
       pwm.freq(i)          
       pwm.duty(512)
       time.sleep_ms(10)
       pwm.deinit()
       time.sleep_ms(1)
   for i in range(1800,150):
       pwm.freq(i)          
       pwm.duty(512)
       time.sleep_ms(10)
       pwm.deinit()
       time.sleep_ms(1)

pwm=PWM(Pin(4))

while True:
    policeSiren(pwm)

有點怪怪的, 原因是 ESP8266 GPIO PWM 的最高頻率只到 1000 Hz 而已, 而這程式卻要用到 1800 Hz.

測試 5 : 救護車的歐伊歐伊

from machine import Pin,PWM
import time

def ambulenceSiren(pwm):
    pwm.freq(400)          
    pwm.duty(512)
    time.sleep_ms(500)
    pwm.freq(800)
    time.sleep_ms(500)
    pwm.deinit()

pwm=PWM(Pin(4))

while True:
    ambulenceSiren(pwm)

接下來要演奏馬力歐一個小節, 各音符對應的頻率參考 :

Arduino 的聲音測試 (一)

測試 6 : 馬力歐 (1)

from machine import Pin,PWM
import time

def mario(pwm):
    pwm.freq(659) #E5        
    pwm.duty(512)
    time.sleep_ms(150)
    pwm.deinit()
    time.sleep_ms(150)
    pwm.freq(659) #E5
    pwm.duty(512)
    time.sleep_ms(150)
    pwm.deinit()
    time.sleep_ms(150)
    pwm.freq(659) #E5
    pwm.duty(512)
    time.sleep_ms(150)
    pwm.deinit()
    time.sleep_ms(150)
    time.sleep_ms(150)
    pwm.freq(523) #C5
    pwm.duty(512)
    time.sleep_ms(150)
    pwm.deinit()
    time.sleep_ms(150)
    pwm.freq(659) #E5
    pwm.duty(512)
    time.sleep_ms(150)
    pwm.deinit()
    time.sleep_ms(150)
    time.sleep_ms(150)
    pwm.freq(784) #G5
    pwm.duty(512)
    time.sleep_ms(150)
    pwm.deinit()
    time.sleep(3)

pwm=PWM(Pin(4))

while True:
    mario(pwm)

注意, 只要呼叫過 deinit() 方法, 頻率與工作週期都會被清掉, 下一個音要再呼叫 freq() 與 duty() 重新設定. 直接使用頻率似乎非常亂, 應該使用音名來代表頻率數字, 但由於 Python 沒有像 C 語言那樣可用 define 定義常數, 只好用變數, 上面的程式可以改寫如下 :

測試 7 : 馬力歐 (2)

from machine import Pin,PWM
import time

E5=659
C5=523
G5=784

def mario(pwm):
    pwm.freq(E5)          
    pwm.duty(512)
    time.sleep_ms(150)
    pwm.deinit()
    time.sleep_ms(150)
    pwm.freq(E5)
    pwm.duty(512)
    time.sleep_ms(150)
    pwm.deinit()
    time.sleep_ms(150)
    pwm.freq(E5)
    pwm.duty(512)
    time.sleep_ms(150)
    pwm.deinit()
    time.sleep_ms(150)
    time.sleep_ms(150)
    pwm.freq(C5)
    pwm.duty(512)
    time.sleep_ms(150)
    pwm.deinit()
    time.sleep_ms(150)
    pwm.freq(E5)
    pwm.duty(512)
    time.sleep_ms(150)
    pwm.deinit()
    time.sleep_ms(150)
    time.sleep_ms(150)
    pwm.freq(G5)
    pwm.duty(512)
    time.sleep_ms(150)
    pwm.deinit()
    time.sleep(3)

pwm=PWM(Pin(4))

while True:
    mario(pwm)

上面的 mario() 有點長, 因為重複的部分很多, 可以模仿 Arduino 的 tone() 函數來精簡如下 :

測試 8 : 馬力歐 (3)

from machine import Pin,PWM
import time

E5=659
C5=523
G5=784

def tone(pwm,note,duration):
    pwm.freq(note)            
    pwm.duty(512)
    time.sleep_ms(duration)
    pwm.deinit()

def mario(pwm):
    tone(pwm,E5,150)
    time.sleep_ms(150)
    tone(pwm,E5,150)
    time.sleep_ms(150)
    tone(pwm,E5,150)
    time.sleep_ms(150)
    time.sleep_ms(150)
    tone(pwm,C5,150)
    time.sleep_ms(150)
    tone(pwm,E5,150)
    time.sleep_ms(150)
    time.sleep_ms(150)
    tone(pwm,G5,150)
    time.sleep_ms(150)
    time.sleep(3)

pwm=PWM(Pin(4))

while True:
    mario(pwm)

上面我定義了一個 tone() 函數來模擬 Arduino 的 tone(), 這樣就能跟 Arduino 的程式一一對應了. 由於 ESP8266 的 GPIO 最高只能調變到 1000 Hz, C6 (1046 Hz) 以上的音名無法發出來, 所以要用 ESP8266 的 PWM 演奏音樂受到限制.

下列範例改編自 Arduino 的測試 9 :

測試 9 : Melody

from machine import Pin,PWM
import time

C0=18
CS0=17
D0=18
DS0=19
E0=21
F0=22
FS0=23
G0=25
GS0=26
A0=28
AS0=29
B0=31
C1=33
CS1=35
D1=37
DS1=39
B0=31
C1=33
CS1=35
D1=37
DS1=39
E1=41
F1=44
FS1=46
G1=49
GS1=52
A1=55
AS1=58
B1=62
C2=65
CS2=69
D2=73
DS2=78
E2=82
F2=87
FS2=93
G2=98
GS2=104
A2=110
AS2=117
B2=123
C3=131
CS3=139
D3=147
DS3=156
E3=165
F3=175
FS3=185
G3=196
GS3=208
A3=220
AS3=233
B3=247
C4=262
CS4=277
D4=294
DS4=311
E4=330
F4=349
FS4=370
G4=392
GS4=415
A4=440
AS4=466
B4=494
C5=523
CS5=554
D5=587
DS5=622
E5=659
F5=698
FS5=740
G5=784
GS5=831
A5=880
AS5=932
B5=988

note=(C4, G3, G3, A3, G3, 0, B3, C4)
duration=(4, 8, 8, 4, 4, 4, 4, 4)

def tone(pwm,note,duration):
    pwm.freq(note)          
    pwm.duty(512)
    time.sleep_ms(duration)
    pwm.deinit()

def melody(pwm):
    for i in range(0,8):
        d=int(1000/duration[i])
        tone(pwm, note[i], d)
        p=int(d*1.3)
        time.sleep_ms(p)
    time.sleep(2)

pwm=PWM(Pin(4))

while True:
    melody(pwm)

但是我測試結果發現倒數第二個音 B3 卻沒發出來, 反覆檢查程式沒錯啊! WHY?

測試 9 : 小蜜蜂

from machine import Pin,PWM
import time

C0=18
CS0=17
D0=18
DS0=19
E0=21
F0=22
FS0=23
G0=25
GS0=26
A0=28
AS0=29
B0=31
C1=33
CS1=35
D1=37
DS1=39
B0=31
C1=33
CS1=35
D1=37
DS1=39
E1=41
F1=44
FS1=46
G1=49
GS1=52
A1=55
AS1=58
B1=62
C2=65
CS2=69
D2=73
DS2=78
E2=82
F2=87
FS2=93
G2=98
GS2=104
A2=110
AS2=117
B2=123
C3=131
CS3=139
D3=147
DS3=156
E3=165
F3=175
FS3=185
G3=196
GS3=208
A3=220
AS3=233
B3=247
C4=262
CS4=277
D4=294
DS4=311
E4=330
F4=349
FS4=370
G4=392
GS4=415
A4=440
AS4=466
B4=494
C5=523
CS5=554
D5=587
DS5=622
E5=659
F5=698
FS5=740
G5=784
GS5=831
A5=880
AS5=932
B5=988

note=(G5, E5, E5, 0, F5, D5, D5, 0, C5, D5, E5, F5, G5, G5, G5, 0,
      G5, E5, E5, 0, F5, D5, D5, 0, C5, E5, G5, G5, E5, 0, 0, 0,
      D5, D5, D5, D5, D5, E5, F5, 0, E5, E5, E5, E5, E5, F5, G5, 0,
      G5, E5, E5, 0, F5, D5, D5, 0, C5, E5, G5, G5, C5, 0, 0, 0)
duration=(4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
          4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
          4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
          4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4)

def tone(pwm,note,duration):
    pwm.freq(note)          
    pwm.duty(512)
    time.sleep_ms(duration)
    pwm.deinit()

def littleBee(pwm):
    for i in range(0,len(note)):
        d=int(1000/duration[i])
        tone(pwm, note[i], d)
        p=int(d*1.3)
        time.sleep_ms(p)
    time.sleep(2)

pwm=PWM(Pin(4))

while True:
    littleBee(pwm)

很奇怪, 還是會掉好幾個音, 不知哪裡有問題, 有空再研究.

參考 :

# ESP8266 first project: home automation with relays, switches, PWM, and an ADC (Good)

沒有留言 :