本来はMEMS MICを正しく実装したいのだが、デバッグ等でI2Sバスで音を確認できるといろいろ助かる。また、I2Sで扱うデータ仕様の理解も深まる。
そこで、I2S接続のSpeakerを波形データを作って鳴らしてみる。使ったパーツは、M5 STACK社のSpeaker2 Hat (MAX98357)
作るデータ:440HzのSin波
データ仕様は、16bit、44.1KHzサンプリング、Monoral
高速化等を無視してmath.sin関数でただただ計算する。符号付きint16変換がちょっと怪しいがプログラムは以下
バッファサイズとして、 buffer_size = 44100 * 2 としたので、1秒間鳴るはず (I2Sバスに対して、44.1KHzサンプリング指定) 。
# # make sin wave # import math from machine import I2S, Pin def dump2Bls(buf, len=128, onHex=False): for i in range(len): data = buf[i*2+1] << 8 data += buf[i*2] if onHex: print(f'{data:02x} ',end='') if data >= 0x8000: print(data - 0x10000) else: print(data) def toI16(data): if data == 0: lb = 0 ub = 0 elif (data > 0) and (data <= 0x7fff): lb = data & 0xff ub = (data >> 8) & 0xff elif (data < 0) and (data >= (0x8000 * -1)): data = 0x10000 + data lb = data & 0xff ub = (data >> 8) & 0xff else: lb = None ub = None return (ub, lb) buffer_size = 44100 * 2 # *2 means 2byte(16bit) buf = bytearray(buffer_size) max = 0x7fff for i in range(int(len(buf)/2)): rad = math.pi * 2 * i / 100 data = max * math.sin(rad) i16 = toI16(int(data)) #print(data, i16) buf[i * 2] = i16[1] buf[i * 2 + 1] = i16[0] i2s = I2S(0, sck=Pin(18), ws=Pin(19), sd=Pin(20), mode=I2S.TX, bits=16, format=I2S.MONO, rate=44100, ibuf=20000) i2s.write(buf)
波形データが正しいか、グラフ化して確認
プログラムを実行すると、M5のSpeakerから音は鳴った。ただ、、Sin波のようなこもった音ではなく、矩形波のような尖ったBEEP音であった。これはスピーカが小さいせいか?
■追記
I2Sのバス仕様、波形の信号はMSBから送信される仕様のようだ。
https://www.nxp.com/docs/en/user-manual/UM11732.pdf
音声データのファイルフォーマットとして、マルチバイトの表現は下位バイトから並べるらしい。
WAVファイルのフォーマットについて | Ingenious
■バイトオーダを確認する
I2Sに渡すバッファ内はL_ch→R_ch→L_ch→Rの順番で並べて(モノラルの場合は、L_ch→L_ch→L_ch→L_ch)、各Channnelは下位バイト→上位バイトの順番で並べると理解した。本当にそれで合っているのかを確認
以下のような固定波形を繰り返し出力するプログラムを作成
# # byte order check # import math from machine import I2S, Pin def dump(buf,len=4): for i in range(len): print(f'{buf[i]:02x} ',end='') if (i+1) % 16 == 0: print('') buf = bytes(( 0x45, 0xA1, 0x09, 0xED,0x45, 0xA1, 0x09, 0xED, 0x45, 0xA1, 0x09, 0xED,0x45, 0xA1, 0x09, 0xED, 0x45, 0xA1, 0x09, 0xED,0x45, 0xA1, 0x09, 0xED, 0x45, 0xA1, 0x09, 0xED,0x45, 0xA1, 0x09, 0xED, 0x45, 0xA1, 0x09, 0xED,0x45, 0xA1, 0x09, 0xED, 0x45, 0xA1, 0x09, 0xED,0x45, 0xA1, 0x09, 0xED, 0x45, 0xA1, 0x09, 0xED,0x45, 0xA1, 0x09, 0xED, 0x45, 0xA1, 0x09, 0xED,0x45, 0xA1, 0x09, 0xED, 0x45, 0xA1, 0x09, 0xED,0x45, 0xA1, 0x09, 0xED, 0x45, 0xA1, 0x09, 0xED,0x45, 0xA1, 0x09, 0xED, 0x45, 0xA1, 0x09, 0xED,0x45, 0xA1, 0x09, 0xED, 0x45, 0xA1, 0x09, 0xED,0x45, 0xA1, 0x09, 0xED, 0x45, 0xA1, 0x09, 0xED,0x45, 0xA1, 0x09, 0xED, 0x45, 0xA1, 0x09, 0xED,0x45, 0xA1, 0x09, 0xED, 0x45, 0xA1, 0x09, 0xED,0x45, 0xA1, 0x09, 0xED, 0x45, 0xA1, 0x09, 0xED,0x45, 0xA1, 0x09, 0xED, 0x45, 0xA1, 0x09, 0xED,0x45, 0xA1, 0x09, 0xED, 0x45, 0xA1, 0x09, 0xED,0x45, 0xA1, 0x09, 0xED, 0x45, 0xA1, 0x09, 0xED,0x45, 0xA1, 0x09, 0xED, 0x45, 0xA1, 0x09, 0xED,0x45, 0xA1, 0x09, 0xED, 0x45, 0xA1, 0x09, 0xED,0x45, 0xA1, 0x09, 0xED, 0x45, 0xA1, 0x09, 0xED,0x45, 0xA1, 0x09, 0xED, 0x45, 0xA1, 0x09, 0xED,0x45, 0xA1, 0x09, 0xED, )) i2s = I2S(0, sck=Pin(18), ws=Pin(19), sd=Pin(20), mode=I2S.TX, bits=16, format=I2S.MONO, rate=44100, ibuf=20000) while True: i2s.write(buf) print('.')
バッファ内が、0x45,0xA1なので、I2Sのバスを観察すると、0xA145のデータがMSBから送信されるはず。オシロで確認するとそのようになってる。
L_ch -> R_chの確認もすべきかもだけど、片側のChannelしか使わないので、省略(出力バスを見ていると、モノラル出力指定したら、L_chの値と同じ値がR_chに出力されているようだ、。)
■追記
MicroPythonのDocumentにバッファのバイトオーダはLittle Endianであると書かれていた。なるほど。
クラス I2S -- IC間サウンド(Inter-IC Sound)バスプロトコル — MicroPython latest ドキュメント