Raspberry Pi を使って圧電スピーカーを鳴らしてみます。
以下の環境で動作確認をしています。
環境
・ Raspberry Pi 5 (bookworm)
・ 圧電スピーカー(以下参照)
・ 振動板として機能するもの、他
・ Windows パソコン/LAN ネットワーク(Raspberry Pi の設定用)
背景 ~ 圧電スピーカーを鳴らしてみる!
先日、ブラウザ上で任意のメロディーを鳴らすプログラムをまとめ、音階や周波数が自由に扱えるようになっています(下記関連リンク参照)。
たまたま手元に、圧電スピーカーのデバイス(60円/個)がいくつかあります。圧電スピーカーは Raspberry Pi の GPIO にジャンパーケーブル2本で接続するだけで、最もシンプル、安価、省電力で音を鳴らすことができます。
ということで、圧電スピーカーを Raspberry Pi につないで、音階、メロディーを鳴らしてみることにします。
なお、Raspberry Pi の GPIO を使う際は、GPIO に流れる最大電流が仕様の上限を超えていないか注意をする必要があります。そこで、これらの式などについてもまとめておくことにします。
余裕のある方は、ドビュッシーの「月の光」のメロディーなども鳴らしてみてください。
手順
圧電スピーカーの取りつけ
① Raspberry Pi の GPIO に圧電スピーカーを接続します。
上の写真の事例では、以下のものを使っています。
・ 圧電スピーカー(圧電サウンダー)
・ 2ピンソケット(圧電サウンダーの赤、黒ケーブルにはんだ付け済み)
・ オス-メスのジャンパーケーブル×2本(赤、黒)
※ 写真では、GPIO 18 を使用しています。
圧電スピーカーの赤色ケーブルを GPIO 18 に接続し、黒色ケーブルを GND に接続します。
② 振動板にするものを決め、圧電素子の面を振動板に密着させるように取りつけます。
※ 振動板は一般のスピーカーのコーンに相当するものです。面積がある程度ないと音が大きくなりません。
上記の例ではプラスチック容器を振動板として使うこととし、圧電素子部分をネオジム磁石2つで固定しています。
スクリプトの設定・実行
③ 圧電スピーカーを扱うためのフォルダ(例:”gpio1″)とファイル(例:”piezo_speaker1.py”、”piezo_speaker2.py”)を作成し、ファイルに下記のスクリプトをコピー&ペーストして保存します。
例:
・ home/pi/gpio1/piezo_speaker1.py
・ home/pi/gpio1/piezo_speaker2.py
※ スクリプトでは、GPIO 18 を使うようにしてあります。
別の GPIO の端子を使う場合は、スクリプトの記載を修正してください。pinout を実行することでピン配列を確認することができます。
④ ③のスクリプトを実行します。
例:
python home/pi/gpio1/piezo_speaker1.py
python home/pi/gpio1/piezo_speaker2.py
→ スピーカーから音が鳴ったら成功です!
うまく動いたら
振動板の調整
うまく動いたら、振動板の種類や取りつけ方を調整して、音量が最大となるよう最適化してみてください。
音量を大きくするパラメータとしては、振動板の密着のさせ方、面積、厚み、材質、形状などがあります。
また、振動板をコーン形状のものとすると、音に指向性を持たせることができます。
圧電素子の種別・周波数特性、振動板(コーン)の設定、ソフトウェアの設定(PWM制御、周波数、波形・デューティ)、人間の耳の周波数の感度、の4つの組み合わせによって、よく聞こえる周波数帯があります。
写真の事例では、雑音のない環境下であれば、5m 程度離しても聞こえる程度の音量となっています。
スクリプトの調整
スクリプト piezo_speaker1.py では、低音域(低周波数)から高音域(高周波数)まで音を出すようにしています。よく聞こえる周波数領域、上限・下限となる周波数を確認してみてください。
また、上記で最大音量の調整ができたら、音量を小さくする場合はソフトウェア側の設定で対応できます。
スクリプトでは、パルス幅制御のデューティ duty1 を 0.5 (50 % の時間を High, 残りの 50 % を Low)としています。
デューティ duty1 を 0.0 / 1.0 に近づけていくと、GPIO の出力が 0.0 V / 3.3 V のままの定常状態に近づきますので、音量が0に近づいていきます。プログラムを修正することで、音量調整の機能を入れるなど、アレンジが可能だと思います。
スクリプトを調整することで、音・音程などの実験も可能です。
たとえば、ピアノなどの楽器では、A4(ドレミファソラシドのラ)の 440 Hz を調律の基準とすることが多いです。ジャンパーケーブル2本で接続するだけで、440 Hz などの振動を簡単に生成できます。
440 Hz の物理的な振動が本当にピアノのラの音と一致するのか、周波数を2倍に上げると本当に1オクターブ上がるのかなど、容易に実験、確認ができます。
メロディーの作成
2つめのスクリプト piezo_speaker2.py では、音階を記載しておくと、メロディーを鳴らすことができるようにしてあります。
str1 = … 、str2 = … 、str3 = … としたところは、メロディーの記載例としています。
a1 = get_score1(str1) とした行で、メロディーを書いたテキストから配列(リスト)を生成しています。そこで、get_score1(str2), get_score1(str3) のように書き換えて、スクリプトを実行してみてください。
任意のメロディーを書き出して、再生することが可能です。
メロディーは、各行ごとに、下記のフォーマットで各音階を記載します。
音を流す時間(秒)+ ” ” (半角スペース) + 音階(C4, D4,E4, F4, ~, B4, C5:ドレミファソラシドに対応)
以前に、ブラウザ上で音階を書いておくとメロディーを再生するスクリプトをまとめています(下記関連リンク参照)。パソコンのブラウザ上でメロディーを作り、出来上がったものを今回のスクリプトに貼りつけると Raspberry Pi/圧電スピーカーでも再生できるよう意図しています。メロディーを作って再生させてみてください。同リンク先には、「月の光」(ドビュッシー)の出だし部分の例を記載しています(著作権が切れている曲を使った事例)。
ジャンパーケーブル2本で接続するだけで、任意のメロディーを流すことが可能になります。
仕様の確認
圧電スピーカーの仕様について確認しておきます。
主な仕様(下記の外部リンク参照)を抜粋すると、以下となっています。
・ 最大動作電圧: 30 Vpp
・ 共振周波数: 6000 Hz
・ 静電容量: 0.015 μF
なお、圧電スピーカーの内部抵抗をテスターで測定すると、測定範囲外(数10MΩ以上)となっています。
人の可聴域は、20 Hz ~ 20,000 Hz といわれています。下記のプログラムでは PWM 制御を使って圧電素子を振動させています。
音波を生成するためにデバイスに交流電圧を与えると、実効的な抵抗値が変動します。そこで、インピーダンス(抵抗値)と最大電流がどの程度となるか確認しておくことにします。
インピーダンス(抵抗値)の確認
以下のインピーダンスの式を使います。
$$ Z = \frac{1}{2\, \pi \, f \, C} … (*01)$$
インピーダンス(抵抗値)が最小となると最大電流が流れることになります。そこで、周波数fの最大値として可聴領域の上限の周波数を代入すると以下となります。
$$ Z ≒ \frac{1}{2 \times 3.14 \times 20000Hz \times 0.015 \times 10^{-6}F}=\frac{1000}{1.8} Ω ≒ 530 \, Ω … (*02)$$
PWM 制御として周波数を上げていくと、530 Ω 程度までインピーダンス(抵抗値)が下がってくることになります。
最大電流の確認
最大電流を確認しておきます。
$$ I = \frac{V}{Z}=\frac{3.3 V}{530Ω} ≒ 6.2 mA < 16 mA … (*03)$$
Raspberry Pi の GPIO に流すことのできる電流は1ピンあたり 16mA まで、トータルで 50mA までとなっています(下記外部リンク参照)。
上記の式から見積もると、約 6.2 mA となっています。可聴範囲の周波数領域を使う程度であれば、最大電流に関しては問題はないと考えられます。
なお、Raspberry Pi の PWM には、ソフトウェア PWM と、GPIO ピンに特別に割り当てられたハードウェア PWM とがあります。
下記のサンプルスクリプトは、ソフトウェア PWM を使っています。ソフトウェア PWM では 20,000 Hz まで出すことはできないようです。
もし、ハードウェア PWM や超音波領域を使う場合は、16mA を超える可能性があるので使用時には確認しておいたほうが安全です。
また、別の対策としては、330Ω 等の抵抗を直列でつないでおくのも有効です。この場合、圧電素子に加えることのできる最大電圧が下がることになり、音量がいくらか犠牲になります。しかし、確実に Raspberry Pi を保護(最大電流を規制)したい場合に有効です。
あるいは、トランジスタ等を使って、圧電素子部分を外部電源化・外部回路化してしまえば、Raspberry Pi に流れる最大電流の問題と、スピーカーの最大音量の問題を設計的に分離することができます。
その他
圧電素子について
市販されているスピーカーのデバイスとしては一般に、圧電素子のタイプのものとコイルを用いたダイナミック式のものとがあります。
圧電素子のタイプとすると、Raspberry Pi の GPIO に直結することで、もっともシンプルに動かすことが可能です。
Raspberry Pi の GPIO は 3.3V なので、電圧(音量、出力)としては不足気味ですが、特別な電気回路を作ることなく(電気回路が苦手でも)、そのまま音波や振動を扱うことができます。
圧電素子は、超音波モーターなどのアクチュエータとしても使われています。0.1μm ~ 数μm 程度の微細な振幅で振動させることで、音源以外に振動源として活用できる可能性があります。
また、圧電素子には、衝撃や振動で電気を発生させて LED を光らせるといった応用例もあります。入力デバイスとして、ギターなど音のピックアップとして使う例もあります。
振動を自由に扱えることにもなりますし、圧電材料の独特な特性を活用してみるのも面白そうです。
さらに発展させるには ~ 音量や周波数領域
上記の圧電スピーカーの仕様によれば、最大電圧は 30 Vpp となっています。しかし、今回は Raspberry Pi の GPIO に直結したので、3.3 V のかなり低電圧で動かしていることになります。
スピーカーの電源を別電源として電圧を上げると、最大音量を上げることができます。
Raspberry Pi で、外部バッテリーから電圧を与える回路の例を末尾の関連リンクに挙げておきますので、興味のある方は参照してみてください。
また、今回のスクリプトでは、周波数は Raspberry Pi のソフトウェア PWM の機能を使って設定しています。
ソフトウェア PWM では周波数の上限に限界があります。
Raspberry Pi のハードウェア PWM を使えば、最大周波数(高音域)の性能を上げられる可能性があります。
ハードウェア PWM は、対応する GPIO の端子を使って、別途、Raspberry Pi 側の設定をすることで使用できそうですが、Raspberry Pi 5 では仕様を変更中なのか、動作確認まではできていません。Raspberry Pi の制御周りに詳しい方は、ハードウェア PWM についても試してみてください。
なお、MP3 などの音楽や人の声を再生する場合は、ダイナミックスピーカーか、USB 接続の市販のスピーカーを使ったほうが適しています。この場合は、コンセントから電源を取って出力や音質を上げるなど、構成を変える必要が出てきます。
スクリプトの説明
・ スクリプト1は、”ドレミファソラシド” の音階を、オクターブ数の低い側から高い側まで順次変え、音を出すようにしたものです。低音域から高音域の範囲で、音がよく聞こえる領域、音が出せる周波数の下限、上限を確認できるようにしています。
・ スクリプトでは、まず冒頭で、PWM 制御で周波数を設定したいので、PWMOutputDevice 等をインポートしています。
・ tone1() 関数は、出力する GPIO とデューティ値、音の長さ、振動数を与えると、指定した GPIO での ON/OFF (3.3V/0.0V)信号として出力します。
・ get_octave_frequencyes1(oct1) 関数は、指定したオクターブ番号 oct1 での “ドレミファソラシド” の音階の周波数を生成します。
・ play1() 関数は、与えられた音階(a1)の内容を読み取って、各音を生成する関数です。
・ スクリプトを実行すると、gpio1 = … 以降を順次、実行します。
・ gpio1 = PWMOutputDevice(18) とした行で、使用する GPIO を設定しています。GPIO 18 以外の端子を使う場合は、この部分の数値を修正してください。pinout コマンドでピン配列を確認できます。
・ duty1 = 0.5 とした行で、PWM 制御でのデューティを設定しています。デューティ duty1 を 0.5 とすると、PWM 制御での周期(=1/周波数)の範囲で、50% を HIGH (3.3V)、残りの 50% を LOW (0.0V) とします。
この部分で、duty1 = 0、1.0 と書き換えると、出力電圧がつねに LOW、つねに HIGH となって圧電素子が振動しなくなるため、音は出なくなります。したがって、たとえば、duty1 = 0.0 ~ 0.5 の範囲とすることにより、音量を微調整することが可能です。
・ duration1 = 1.0 としたところで、音の長さ(秒)を設定しています。実際は、tone1() 関数の中で、音を鳴らした後、duration1 の期間だけ待ち時間を入れることで、音を鳴らし続けるようにしています。
音をゆっくり鳴らす場合には duration1 = 2.0 とするなど、調整してみてください。
・ for ループで、1オクターブ分のドレミファソラシドを複数回再生しています。
繰り返すたび、各オクターブ番号を上げていき、最低音域から高音域まで、音階を鳴らすようにしています。
これにより、どの音域がよく聞えるかなどを確認できるようにしています。
・ 最後に gpio1.value = 0.0 とすることで停止処理を入れるようにしています。
・ スクリプト2は、メロディーをテキストで書き出しておくと、再生できるようにしたものです。
好きな曲のサビなどをテキストで記入することで、Raspberry Pi でメロディーを奏でることが可能になります。
テキストをリターンで分割して配列を作り、各行のスペースで配列の要素をさらに分割します。
そうして得られた音の長さと音階(周波数)を順次 tone1() 関数に入れていくことで、メロディーを流すようにしています。音階は国際式の音階(C4, D4, … C5)であっても、周波数であっても、判別して機能するようにしてあります。
まとめ
Raspberry Pi で圧電スピーカーを鳴らす方法についてまとめました。
圧電スピーカーを使うことで、簡単に音や周波数を扱うことが可能になります。また、音を鳴らす以外に、圧電材料といった特徴のある材料が気軽に扱える、入力素子として使える可能性があるといった観点でも応用範囲が広がることになります。
音階や周波数などについては、下記の関連リンクなどでも扱っています。
もし興味があるようでしたら参照してみてください。
関連リンク
・ ブラウザでメロディーを鳴らす 【JavaScript】
・ Raspberry Pi で PWM制御 【Python】
・ DCモーターを動かす 【Raspberry Pi】
※ もし、電源を外部電源化する場合はこちら↑も参照してみてください。
電源を外部バッテリー化しています。圧電スピーカーを鳴らす場合はもう少し電圧を上げるとよいです。
・ ドレミファソラシドを鳴らすサンプル【Python】
・ キーボードピアノ 【本格49鍵!】
外部リンク
・ 圧電スピーカー:圧電サウンダー FGT-15T-6.0A1W40 (秋月電子)
・ Raspberry Pi hardware – Raspberry Pi Documentation
※ リンク先を確認すると、”Combined, the GPIO pins can draw 50mA safely; each pin can individually draw up to 16mA.” とあります。GPIO ピンにトータルでは 50mA まで流すことができる、そして、GPIO の1つのピンには 16mA まで流すことができる、といった記載になっています。
サンプルスクリプト
スクリプト1: piezo_speaker1.py 低~高周波数領域まで鳴らすスクリプト
from gpiozero import PWMOutputDevice
import time as tm1
def tone1(gpio1, duty1, duration1, freq1):
if freq1 > 0:
gpio1.value = duty1 # duty
gpio1.frequency = freq1
print(f"{duration1:.2f} {freq1:.3f}")
else:
print(f"{duration1:.2f}")
tm1.sleep(duration1)
gpio1.value = 0.0
def get_octave_frequencies1(oct1):
freq_A4 = 440.0
a1 = [-9, -7, -5, -4, -2, 0, 2, 3] # C, D, E, F, G, A, B, C
a2 = []
for offset1 in a1:
semitones1 = offset1 + (oct1 * 12)
freq1 = round(freq_A4 * 2 ** (semitones1 / 12), 3) # 12 equal temperament
a2.append(freq1)
return a2
def play1(gpio1, duty1, duration1, a1):
for freq1 in a1:
tone1(gpio1, duty1, duration1, freq1)
gpio1.value = 0.0
gpio1 = PWMOutputDevice(18) # GPIO18
duty1 = 0.5
# duty1 = 0.01 # duty
duration1 = 1.0
# a1 = range(2000, 7001, 100)
# a1 = [261.63, 293.66, 329.63, 349.23, 392.00, 440.00, 493.88, 523.25]
for i1 in range(6):
a1 = get_octave_frequencies1(i1-1)
play1(gpio1, duty1, duration1, a1)
print("")
tm1.sleep(1.0)
gpio1.value = 0.0
スクリプト2: piezo_speaker2.py メロディーを流すスクリプト
from gpiozero import PWMOutputDevice
import time as tm1
def isfloat1(str1):
try:
v1 = float(str1)
return v1
except ValueError:
return False
def CDEFGAB_to_semitones1(str1):
dic1 = {"C":-9, "D":-7, "E":-5, "F":-4, "G":-2, "A": 0, "B": 2}
return dic1[str1]
def pitch2semitones1(str1):
c1 = str1[0]
c2 = str1[1]
c3 = str1[-1]
n1 = CDEFGAB_to_semitones1(c1) + (int(c3)-4)*12
if c2 == "s":
n1 = n1 + 1
elif c2 == "f":
n1 = n1 -1
return n1
def pitch2frequency1(str1):
if isfloat1(str1):
v1 = float(str1)
else:
n1 = pitch2semitones1(str1)
v1 = 440.0 * 2 **(n1/12)
return v1
def get_score1(str1):
a1 = str1.strip().split("\n")
for i1 in range(len(a1)):
a1[i1] = a1[i1].strip().split(" ")
len1 = float(a1[i1][0])
if len(a1[i1]) > 1:
a1[i1] = [len1, pitch2frequency1(a1[i1][1])]
else:
a1[i1] = [len1, -1] # rest
return a1
def tone1(gpio1, duty1, duration1, freq1):
if freq1 > 0:
gpio1.value = duty1 # duty
gpio1.frequency = freq1
print(f"{duration1:.2f} {freq1:.3f}")
else:
print(f"{duration1:.2f}")
tm1.sleep(duration1)
gpio1.value = 0.0
def play1(gpio1, beat_length1, duty1, a1):
for i1 in range(len(a1)):
duration1 = a1[i1][0]*beat_length1;
freq1 = a1[i1][1]
tone1(gpio1, duty1, duration1, freq1)
gpio1.value = 0.0
str1 = '''
1.00 C4
1.00 D4
1.00 E4
1.00 F4
1.00 G4
1.00 A4
1.00 B4
1.00 C5
'''
str2 = '''
1.00 261.626
1.00 293.665
1.00 329.628
1.00 349.228
1.00 391.995
1.00 440.000
1.00 493.883
1.00 523.251
'''
str3 = '''
0.25 A4
0.75
0.25 A4
0.75
0.25 A4
0.75
2.00 A5
'''
duty1 = 0.5 # duty 0.00 to 1.00
# duty1 = 0.02
bpm1 = 60 # beats per minute
beat_length1 = 60.0/bpm1
a1 = get_score1(str1) # music score list
gpio1 = PWMOutputDevice(18) # GPIO18
play1(gpio1, beat_length1, duty1, a1)
gpio1.value = 0.0

