本来は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 ドキュメント