以前からRaspberry Pi picoで音を鳴らす作品を作ってみたいと思っていたのですが、今回、電子工作で音楽を再生するための定番デバイス「DFPlayer」を入手したので、早速サンプルを作ってみました。
DFPlayerの制御についてググってみると、Arduinoでの実装例は様々な記事が出てくるのでとても参考になるのですが、RaspberryPi picoでの実装例はまだ少なく感じたので、勉強がてら自分もチャレンジしてみました。
ちなみに、DFPlayerは単独でもサウンドの再生が可能なので、今回やる程度のことなら、わざわざ別途マイコンを用意する必要はないのですが、単に音楽プレーヤーを作りたかったというよりも、他プロジェクトでも使用できるようなDFPlayer制御用の独自のPythonライブラリを作ってみたかったというのが主な目的です。
では早速、本論に入っていきたいと思います。
目次
- 必要な機材を入手
- サンプル回路の組み立て
- 今回作ったシンプルなライブラリとコードについて
- まとめ
1. 必要な機材を入手
まずはこの記事で紹介する機材から。
電子工作をされる方ならすでにお持ちかと思いますが、上記以外にも下記のものが必要になります。
- ブレッドボード
- タクトスイッチ
- ジャンパーワイヤー
- microSDカード(mp3ファイル格納用なので容量は小さいものでもOK)
2. サンプル回路の組み立て
さてお次は、回路の組み立てです。Raspberry pi picoとDFPlayerをブレッドボードに挿して、以下のように配線します。
DFPlayerは電子工作用の小さなモノラルスピーカーと、ステレオミニプラグを介してステレオイヤホンやアンプ付ステレオスピーカーへ同時出力することが可能です。肝心の音質ですが、電子工作レベルで音楽を再生する程度の目的なら、個人的にはこれで全く問題ないと感じています。想像していたよりも音質はよかったです。
DFPlayerへはUART経由で接続が必要になるので、対応するピンに接続します。今回はGP8(TX)とGP9(RX)に接続しました。電源ですが、DFPlayerの仕様では3.2v〜5.0vの範囲で動作するようですが、今回はUSBから給電される5vを使用するためVBUSに接続しました。
間違いやすい注意点ですが「TX – RX」「RX – TX」と繋ぎますので注意してください。当初自分もこの繋ぎ方を間違えてしまい戸惑ってしまいました。
回路の準備ができたら、今度は再生するmp3ファイルを用意してください。mp3ファイルはすべてmicroSD直下の「mp3」というフォルダ内に格納しますが、ファイル名は「0001.mp3」のような4桁の数字のファイル名にする必要があります。
ちなみに、この4桁のファイル名ですが「9999.mp3」までいけるだろうと思ってしまいがちですが、仕様書によると「0000.mp3〜2999.mp3」までしか認識しないようです。自分の場合は「0000.mp3」は使わず「0001.mp3」からの連番ファイル名としました。
3. 今回作ったシンプルなライブラリとコードについて
まずは、DFPlayer制御用のライブラリからです。この程度の実装だとコード量も少なく、できることも少ないので、わざわざライブラリを作る必要もないかと思いますが、他プロジェクトで使う時に、コードをスッキリさせるために、あえて独自のライブラリを用意してみました。
import machine
import utime
# --- DFPlayerの使い方 ---
# microSDカード直下に"mp3"という名称のフォルダを作成し"0001.mp3"のような連番でファイルを格納しておく。
# 第1引数でTXピン番号、第2引数でRXピン番号を指定する。
# 第3引数(オプション)でボリューム値を指定をする場合は、0〜30までの数値で指定する。デフォルト値は15。
THIS_VERSION = 1.0
class DFPlayer:
def __init__(self, tx:int, rx:int, volume:int=15):
self.uart = machine.UART(1, baudrate=9600, tx=machine.Pin(tx), rx=machine.Pin(rx))
# sd初期化
self._command(0x3F, 0x02)
utime.sleep_ms(500)
# volume設定
self.set_volume(volume)
print("DFPlayer initialized (ver.{})".format(THIS_VERSION))
def _command(self, command, parameter=0x00):
query = bytes([0x7e, 0xFF, 0x06, command, 0x00, 0x00, parameter, 0xEF])
self.uart.write(query)
# 以下、制御メソッド
def set_volume(self, volume:int):
self._command(0x06, volume)
print("Volume: {}".format(volume))
utime.sleep_ms(500)
def play(self, num:int):
self._command(0x12, num)
print("Play: {}".format(num))
utime.sleep_ms(500)
def repeat(self):
self._command(0x08)
print("Repeat ON")
utime.sleep_ms(500)
def pause(self):
self._command(0x0E)
print("Pause")
utime.sleep_ms(500)
def resume(self):
self._command(0x0D)
print("Resume")
utime.sleep_ms(500)
def stop(self):
self._command(0x16)
print("Stop / Repeat OFF")
utime.sleep_ms(500)
13行目:
まず初期化メソッドですが、第1引数と第2引数でUARTのTXピン及びRXピンの番号を受け取り、第3引数のボリューム設定値はデフォルト値15とし、オプションという扱いにしました。
25行目:
_command関数はDFPlayer側に16進数でコマンドとパラメーターを渡すための内部メソッドですが、基本的に制御に必要なのは、コマンドとパラメーターのみで、あとは固定でも特に問題ないため、ここではシンプルに実装しています。
31行目以降:
ここからは外部コードからも使用する制御系メソッドが続きます。幾つか注意点をメモしておきます。DFPlayerの仕様書によると、トラック番号を指定して再生するためのコマンドとして「0x03」が用意されているのですが、どうしてもうまく動かなくて「0x12」という別のコマンドを使用しています。しかし、この「0x12」というコマンドが仕様書内に存在せず(?)これでなぜ動くのかはちょっと謎でした。
また、仕様書には次のトラックを再生するための「0x01」、前のトラックを再生するための「0x02」というコマンドがあるのですが、なぜか期待通りに動いてくれませんでした。再生されたり、されなかったりのような動きをするのです。また順番もランダムになる?といった感じでした。というわけで、曲送りと曲戻しのメソッドは今回は実装を見送り、同機能はライブラリを使用するコード側で実装する方向で妥協することにしました。
というわけで、今回実装した機能は、ボリューム制御(set_volume)、再生(play)、リピート設定(repeat)、一時停止(pause)、再生再開(resume)、停止/リピート解除(stop)というメソッドを用意しています。
DFPlayerの仕様についてはこちらを参照してみてください。
お次は「main.py」です。今回はタクトスイッチを5個繋げてみたので、それぞれに以下のように機能を割り当ててみました。
import machine
import utime
from DFPModule import DFPlayer
player = DFPlayer(8, 9, 20)
btn1 = machine.Pin(0, machine.Pin.IN, machine.Pin.PULL_UP)
btn2 = machine.Pin(4, machine.Pin.IN, machine.Pin.PULL_UP)
btn3 = machine.Pin(16, machine.Pin.IN, machine.Pin.PULL_UP)
btn4 = machine.Pin(20, machine.Pin.IN, machine.Pin.PULL_UP)
btn5 = machine.Pin(26, machine.Pin.IN, machine.Pin.PULL_UP)
ON = 0
OFF = 1
btn1_prev = OFF
btn2_prev = OFF
btn3_prev = OFF
btn4_prev = OFF
btn5_prev = OFF
pause = False
repeat = False
track = 1
max = 5
while True:
btn1_state = btn1.value()
btn2_state = btn2.value()
btn3_state = btn3.value()
btn4_state = btn4.value()
btn5_state = btn5.value()
# 次のトラックを再生
if btn1_prev == OFF and btn1_state == ON:
track += 1
if track > max:
track = 1
player.play(track)
# 前のトラックを再生
if btn2_prev == OFF and btn2_state == ON:
track -= 1
if track < 1:
track = max
player.play(track)
# 現在のトラックを再生。一時停止時は途中から再生
if btn3_prev == OFF and btn3_state == ON:
if pause == False:
player.play(track)
else:
player.resume()
pause = False
# 一時停止
if btn4_prev == OFF and btn4_state == ON:
pause = True
player.pause()
# リピートON/リピートOFF(Stop)
if btn5_prev == OFF and btn5_state == ON:
if repeat == False:
player.repeat()
repeat = True
else:
player.stop()
repeat = False
btn1_prev = btn1_state
btn2_prev = btn2_state
btn3_prev = btn3_state
btn4_prev = btn4_state
btn5_prev = btn5_state
utime.sleep_ms(1)
以上となります。3行目ですが、今回用意したオリジナルのライブラリから「DFPlayer」クラスをインポートしています。
その他は、簡単なコードなので特に解説の必要もないかと思います。タクトスイッチの誤動作を防ぐため、やや冗長な記述になっているようにも思いますが、ひとまずこれで動きました。
なお、今回の記事を書くにあたり、下記のブログを参照させていただきました。大変参考になりました。ありがとうございました!
参考URL:
https://make-muda.net/2022/04/7990/
4. まとめ
というわけで、今回はRaspberry Pi picoでDFPlayerを制御するためのシンプルなライブラリを実装してみました。実はpico wが登場してからというもの、picoを使った作品作りの意欲がまた湧いてきて、あれこれ作ってみたいと考えています。ひとまず今回は初代picoを使ったプロジェクトでしたが、今度はpico wを応用したプロジェクトに取り組んでみたい思います!