概要
- 久しぶりにM5StickC Plusで遊んだ。
- 今度はUIFlowではなくmicropythonを使用。
- 内蔵マイクで騒音計(っぽい)ものを作ってみた。
- なんとなく動いているようだが、補正とか単位は何も考えていない。
環境
- M5StickC Plus
- UIFlow2 (ファームウェア、開発環境)
- Thonny (開発環境)
はまったところ
前提
- 音声信号というのがなんなのかわかっていない
- micropythonもわかっていない
- なんならメモリの扱いもわかっていない
マイクが触れない
一番参考にしたのがこの記事。
M5StickCでdB(デシベル)表示の騒音計を作る
micropythonに触れることが主たる目的だったので、写経してからいじるだけのつもりでした。しかし、M5stickC&騒音計µpython自体は見つけられなかったので、上記のコードをmicropythonで行うのを基本方針としました。
しかし、micropythonのI2Sの使い方がよくわかりませんでした。具体的にはコンストラクタで、sckとしてどのピンを指定すれば良いのかが見つけられず断念。
公式ドキュメントにたどり着いてようやくデータを取得
どうしてすぐにこれを見なかったのかというのは置いておいて、ようやくマイクのデータにアクセスできるようになったのが、これにたどり着いてからです。
UIFlow2 Programming Guide / Hardware / Mic
M5モジュールにあるMicを使ってマイクのデータを取得できるようになったので、UIFlow2でMic周りのところ以外を作って、Micの処理を書いていくことにしました。
取得したデータがよくわからないその1
Thonnyで接続してシェルを叩いて試している時に、UIFlow2 Programming Guide / Hardware / Micの記述
Methods of the Mic Class heavily rely on M5.begin()
All calls to methods of Mic objects should be placed after M5.begin(), and M5.update()
に、気づかずおかしなデータになっていて、しばらく時間を潰しました。
最終的にはシェルでの動作確認時には、最低これで動いているようでした。
>>> import M5
>>> M5.begin()
>>> samples = bytearray(128*2*8)
>>> M5.Mic.begin()
True
>>> M5.Mic.record(samples, 16000, False)
True
>>> print(samples)
取得したデータがよくわからないその2
それっぽいデータがようやく取れるようになったので、データのフォーマット向き合う時間。参考に挙げたページの3, 8などを読むと符号付き16ビットのリトルエンディアンということがわかりました。
MicroPython ドキュメンテーション クラス I2S -- IC間サウンド(Inter-IC Sound)バスプロトコル
"buf" のバイトオーダはリトルエンディアンです。
M5StickC内蔵I2Sマイクの録音データは、符号付き16ビットですので、2バイト単位で送信されてきます。
追記
この記事(I2Sと、I2Sもどきと、 S/PDIFについて)を読むとデータフォーマットについて上記の理解では誤っているようです。より理解が進んだら改めて修正し直しますが、いまのところはこのままにします。
追記ここまで。
データの転送あたりは#23-10 PDMマイクを使って見るを読んでわかったふりをしました。
これらを踏まえてM5StickCでdB(デシベル)表示の騒音計を作るを参考に、マイクのデータ(buffer)を補正とか無視して音の強さ(っぽいの)への変換するのがこの部分です。
def putSoundLevel(buffer):
ret = 0.0
dataSize = len(buffer) // 2
for i in range(dataSize):
r = int.from_bytes(buffer[i*2:i*2+2], "little")
ret += (r if r <= 0x8000 else r - 0x10000)**2
ret = ret / dataSize
ret = math.sqrt(ret)
return ret
コード
できあがったのが次のコード。なんとなくは動くけれど、縦軸、横軸ともに適当です。バッファーの大きさとかこれで良いのかよくわかっていないから、それっぽく出るからひとまずここまで。
import M5
from M5 import *
import math
import time
line0 = None
title0 = None
label0 = None
soundLevels = None
isRecording = None
# Describe this function...
def putSoundLevel(buffer):
ret = 0.0
dataSize = len(buffer) // 2
for i in range(dataSize):
r = int.from_bytes(buffer[i*2:i*2+2], "little")
ret += (r if r <= 0x8000 else r - 0x10000)**2
ret = ret / dataSize
ret = math.sqrt(ret)
return ret
def putColor(value):
_value = value / 4096 if value <= 4096 else 1
if _value <= 0.2:
r = 0
g = int(255 * (1 - _value / 0.2))
b = 255
elif _value <= 0.8:
r = int(255 * (_value - 0.2) / 0.6)
g = 0
b = int(255 * (1 - (_value - 0.2) / 0.6))
else:
r = 255
g = 0
b = int(255 * (_value - 0.8) / 0.2)
return r << 16 | g << 8 | b
def setup():
global line0, title0, label0, soundLevels, isRecording
M5.begin()
Widgets.fillScreen(0xeeeeee)
line0 = Widgets.Line(120, 30, 120, 230, 0xffffff)
title0 = Widgets.Title("Sound level meter", 3, 0xffffff, 0x0000FF, Widgets.FONTS.DejaVu12)
label0 = Widgets.Label("Stop", 0, 16, 1.0, 0x222222, 0xeeeeee, Widgets.FONTS.DejaVu12)
soundLevels = [0.0 for i in range(200)]
isRecording = False
Mic.config(dma_buf_len=256)
Mic.config(dma_buf_count=2)
Mic.begin()
def loop():
global line0, title0, label0, soundLevels, isRecording
M5.update()
samples = bytearray(512)
if BtnA.wasClicked():
if isRecording:
isRecording = False
title0.setColor(0x000000, 0x00ff00)
label0.setText("Stop")
else:
isRecording = True
if isRecording:
Widgets.fillScreen(0xeeeeee)
title0.setColor(0xffffff, 0xff0000)
Mic.record(samples, 16000, False)
while Mic.isRecording():
time.sleep_ms(10)
soundLevels.pop(0)
soundLevels.append(putSoundLevel(samples))
label0.setText("%.2f"%soundLevels[-1])
for i in range(199):
c = putColor((soundLevels[i] + soundLevels[i+1])/2)
Widgets.Line(int(120-soundLevels[i]/4096*120), 30+i, int(120-soundLevels[i+1]/4096*120), 30+i+1, c)
time.sleep_ms(10)
if __name__ == '__main__':
try:
setup()
while True:
loop()
except (Exception, KeyboardInterrupt) as e:
try:
from utility import print_error_msg
print_error_msg(e)
except ImportError:
print("please update to latest firmware")
感想
参考に挙げたページなどを根気よく読んでいればもっとすんなりできた気がします。「こんなもんやろ」でつまみ食いしながら進めてしまったために、時間を要しました。