0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

M5StickC Plusのマイクをmicropythonでいじってみた

Last updated at Posted at 2024-09-23

概要

  • 久しぶりにM5StickC Plusで遊んだ。
  • 今度はUIFlowではなくmicropythonを使用。
  • 内蔵マイクで騒音計(っぽい)ものを作ってみた。
  • なんとなく動いているようだが、補正とか単位は何も考えていない。

環境

  • M5StickC Plus
  • UIFlow2 (ファームウェア、開発環境)
  • Thonny (開発環境)

はまったところ

前提

  • 音声信号というのがなんなのかわかっていない
  • micropythonもわかっていない
  • なんならメモリの扱いもわかっていない

マイクが触れない

一番参考にしたのがこの記事。
M5StickCでdB(デシベル)表示の騒音計を作る
micropythonに触れることが主たる目的だったので、写経してからいじるだけのつもりでした。しかし、M5stickC&騒音計&micropython自体は見つけられなかったので、上記のコードを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のマイクを使ってみる その3

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

コード

できあがったのが次のコード。なんとなくは動くけれど、縦軸、横軸ともに適当です。バッファーの大きさとかこれで良いのかよくわかっていないから、それっぽく出るからひとまずここまで。

soundlevelmeter.py
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")


感想

参考に挙げたページなどを根気よく読んでいればもっとすんなりできた気がします。「こんなもんやろ」でつまみ食いしながら進めてしまったために、時間を要しました。

参考

  1. M5StickCでMicroPython(本家ESP32版+Thonny編)
  2. M5StickCでdB(デシベル)表示の騒音計を作る
  3. MicroPython ドキュメンテーション クラス I2S -- IC間サウンド(Inter-IC Sound)バスプロトコル
  4. MicroPython的午睡 投稿順index
  5. #23-10 PDMマイクを使って見る
  6. M5StickCのマイクを使ってみる その1
  7. M5StickCのマイクを使ってみる その2
  8. M5StickCのマイクを使ってみる その3
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?