MicroPython 开辟ESP32应用教程 之 I2S、INMP441音频录制、MAX98357A音频播放、SD卡读写
本课程我们讲解Micropython for ESP32 的i2s及其应用,比如INMP441音频录制、MAX98357A音频播放等,还有SD卡的读写。一、硬件预备
1、支持micropython的ESP32S3开辟板
2、INMP441数字全向麦克风模块
3、MAX98357A音频播放模块
4、SD卡模块
5、面包板及连接线若干
连接方式:
https://i-blog.csdnimg.cn/direct/b79b22ca4d0a48b8806429be478fb8ac.png https://i-blog.csdnimg.cn/direct/cc855d5fe6574572b4c8498699db75a1.png
inmp441MAX98357AESP32S3SDIO13WSIO12SCKIO11L/R接地SD接VCCGAIN接地DINIO37BCLKIO38LRCIO39 SD卡模块ESP32S3SCKIO4MOSIIO5MISOIO16CSIO17
二、i2s介绍
一)、I2S协议基础
I2S(Inter-IC Sound)是一种同步串行通讯协议,专为数字音频设备设计,支持单向/双向音频数据传输。其物理层包含三条信号线:
[*]SCK(串行时钟):同步数据传输速率
[*]WS(字选择):区分左右声道或定义采样率
[*]SD(串行数据):传输现实音频数据流
二)、MicroPython I2S类特性
A. 仅支持主设备利用模式,可控制SCK和WS信号的天生,适用于连接麦克风、
DAC等从设备
B. 支持ESP32、STM32、RP2等主流微控制器平台,通过统一接口简化跨硬件开辟
三)、核心功能实现
[*] 音频输入/输出
[*]灌音:从麦克风模块获取PCM音频数据
[*]播放:向DAC或音频解码器发送音频流27。
[*] 参数机动设置
初始化时可设置关键参数:
i2s = I2S(id,# 硬件实例编号(如I2S.NUM0)
sck=Pin(11), ws=Pin(12), sd=Pin(13),# 引脚映射
mode=I2S.RX,# 模式(RX/TX)
bits=16, # 采样位深
format=I2S.MONO,# 声道格式 MONO为单声道,STEREO为立体声
rate=16000, # 采样率
ibuf=8092) # 输入缓冲区大小:ml-citation{ref="4,7" data="citationList"}
[*] 中断与DMA支持
支持异步数据读写,通过DMA减少CPU占用率,提拔实时性
四)、范例应用场景
[*] 音频播放器
播放WAV/MP3文件(需解码库支持)。
[*] 语音采集系统
连接INMP441等数字麦克风实现情况音录制。
[*] 实时语音处置惩罚
联合神经网络进行关键词辨认或声纹分析
三、MicroPython SD卡介绍
一)、SD卡初始化与挂载
硬件接口设置
使用SPI模式连接SD卡(需4线:CLK/MOSI/MISO/CS),范例ESP32设置示例:
from sdcard import SDCard
import os, time, gc
spi = SPI(2,
baudrate=80000000,
polarity=0,
phase=0,
sck=Pin(4),
mosi=Pin(5),
miso=Pin(16))
sd = SDCard(spi,Pin(17,Pin.OUT))
二)、文件利用API
基础文件读写
使用标准文件利用接口:
def test_sd():
os.mount(sd,'/sd')
# 重新查询系统文件目录
print('挂载SD后的系统目录:{}'.format(os.listdir()))
with open("/sd/test.txt", "w") as f:
f.write(str("Hello MicroPython!"))
# 从sd卡目录下读取hello.txt文件内容
with open("/sd/test.txt", "r") as f:
# 打印读取的内容
data = f.read()
print (data)
四、inmp4411录制音频
通过前面的讲解,这一末节的内容需要掌握的知识点我们都已经掌握,直接上代码:
audiofilename = '/sd/rec.pcm'
def record_audio(filename=audiofilename, duration=5, sample_rate=16000):
# # 硬件诊断
print("初始化I2S...")
try:
i2s = I2S(
0,
sck=Pin(11), ws=Pin(12), sd=Pin(13),
mode=I2S.RX,
bits=16,
format=I2S.MONO,
rate=sample_rate,
ibuf=4096
)
except Exception as e:
print("I2S初始化失败:", e)
return
# 计算数据量
bytes_per_second = sample_rate * 2# 16bit=2字节
total_bytes = bytes_per_second * duration
# header = createWavHeader(sample_rate, 16, 1, total_bytes)
# 录音循环
try:
with open(audiofilename, 'wb') as f:
# f.write(header)
start_time = time.ticks_ms()
bytes_written = 0
buffer = bytearray(2048)# 小缓冲区减少内存压力
while bytes_written < total_bytes:
read = i2s.readinto(buffer)
if read == 0:
print("警告:未读取到数据")
continue
f.write(buffer[:read])
bytes_written += read
gc.collect()
# 实时进度
elapsed = time.ticks_diff(time.ticks_ms(), start_time) / 1000
print(f"进度: {bytes_written/total_bytes*100:.1f}%, 时间: {elapsed:.1f}s")
except OSError as e:
print("文件写入错误:", e)
finally:
i2s.deinit()
# print("录音结束,文件大小:", os.stat(audiofilename), "字节")
print("录音结束,文件大小:", bytes_written, "字节") 但这里需要分析一下的是,我们刚开始开辟的时候,录制的音频文件中的数据满是0,也就是说没有声音,噪音都没有,检查连接线、换IO口等等,各种折腾,但题目依然存在,后来因为出了别的的错误,就暂停了,具体可以参考:MicroPython 开辟ESP32应用教程 之 WIFI、BLE共用常见题目处置惩罚及中断处置惩罚函数留意事项
上文中提到的题目处置惩罚完后,我们继续折腾音频录制及播放的功能,奇怪的事情发生了,连接好各功能模块后,测试,居然好了,怀疑是上文中提到的电源的题目,但把外接电源移除,测试没有题目。
也就是说,到现在,我们照旧不知道之前为什么有题目?现在为什么好了?只能怀疑电源不稳?
五、MAX98357A音频播放
这个也没什么好讲,直接上代码吧
audiofilename = '/sd/rec.pcm'
audio_out = I2S(1, sck=Pin(38), ws=Pin(39), sd=Pin(37), mode=I2S.TX, bits=16, format=I2S.MONO, rate=16000, ibuf=20000)
def play_audio(filename='/sd/rec.wav', duration=5, sample_rate=16000):
# audio_out.volume(80)
with open(audiofilename,'rb') as f:
# 跳过文件的开头的44个字节,直到数据段的第1个字节
# pos = f.seek(44)
# 用于减少while循环中堆分配的内存视图
wav_samples = bytearray(1024)
wav_samples_mv = memoryview(wav_samples)
print("开始播放音频...")
#并将其写入I2S DAC
while True:
try:
num_read = f.readinto(wav_samples_mv)
# WAV文件结束
if num_read == 0:
break
# 直到所有样本都写入I2S外围设备
num_written = 0
while num_written < num_read:
num_written += audio_out.write(wav_samples_mv)
except Exception as ret:
print("产生异常...", ret)
六、完整代码
该代码简单修改可生存为WAV格式文件,可以用我们常见的音频播放软件播放。
from machine import I2S, Pin,SPI
from sdcard import SDCard
import os, time, gc
spi = SPI(2,
baudrate=20000000,
polarity=0,
phase=0,
sck=Pin(4),
mosi=Pin(5),
miso=Pin(16))
sd = SDCard(spi,Pin(17,Pin.OUT))
audiofilename = '/sd/rec.pcm'
def createWavHeader(sampleRate, bitsPerSample, num_channels, datasize):
riff_size = datasize + 36 - 8# 修正RIFF块大小
header = bytes("RIFF", 'ascii')
header += riff_size.to_bytes(4, 'little')
header += bytes("WAVE", 'ascii')
header += bytes("fmt ", 'ascii')
header += (16).to_bytes(4, 'little') # fmt块大小
header += (1).to_bytes(2, 'little') # PCM格式
header += num_channels.to_bytes(2, 'little') # 声道数
header += sampleRate.to_bytes(4, 'little') # 采样率
header += (sampleRate * num_channels * bitsPerSample // 8).to_bytes(4, 'little')# 字节率
header += (num_channels * bitsPerSample // 8).to_bytes(2, 'little')# 块对齐
header += bitsPerSample.to_bytes(2, 'little')# 位深
header += bytes("data", 'ascii')
header += datasize.to_bytes(4, 'little') # 数据块大小
return header
def record_audio(filename=audiofilename, duration=5, sample_rate=16000):
# # 硬件诊断
print("初始化I2S...")
try:
i2s = I2S(
0,
sck=Pin(11), ws=Pin(12), sd=Pin(13),
mode=I2S.RX,
bits=16,
format=I2S.MONO,
rate=sample_rate,
ibuf=4096
)
except Exception as e:
print("I2S初始化失败:", e)
return
# 计算数据量
bytes_per_second = sample_rate * 2# 16bit=2字节
total_bytes = bytes_per_second * duration
# header = createWavHeader(sample_rate, 16, 1, total_bytes)
# 录音循环
try:
with open(audiofilename, 'wb') as f:
# f.write(header)
start_time = time.ticks_ms()
bytes_written = 0
buffer = bytearray(1024)# 小缓冲区减少内存压力
while bytes_written < total_bytes:
read = i2s.readinto(buffer)
if read == 0:
print("警告:未读取到数据")
continue
f.write(buffer[:read])
bytes_written += read
gc.collect()
# 实时进度
elapsed = time.ticks_diff(time.ticks_ms(), start_time) / 1000
print(f"进度: {bytes_written/total_bytes*100:.1f}%, 时间: {elapsed:.1f}s")
except OSError as e:
print("文件写入错误:", e)
finally:
i2s.deinit()
# print("录音结束,文件大小:", os.stat(audiofilename), "字节")
print("录音结束,文件大小:", bytes_written, "字节")
audio_out = I2S(1, sck=Pin(38), ws=Pin(39), sd=Pin(37), mode=I2S.TX, bits=16, format=I2S.MONO, rate=16000, ibuf=20000)
def play_audio(filename='/sd/rec.wav', duration=5, sample_rate=16000):
# audio_out.volume(80)
with open(audiofilename,'rb') as f:
# 跳过文件的开头的44个字节,直到数据段的第1个字节
# pos = f.seek(44)
# 用于减少while循环中堆分配的内存视图
wav_samples = bytearray(1024)
wav_samples_mv = memoryview(wav_samples)
print("开始播放音频...")
#并将其写入I2S DAC
while True:
try:
num_read = f.readinto(wav_samples_mv)
# WAV文件结束
if num_read == 0:
break
# 直到所有样本都写入I2S外围设备
num_written = 0
while num_written < num_read:
num_written += audio_out.write(wav_samples_mv)
except Exception as ret:
print("产生异常...", ret)
if __name__ == "__main__":
try:
os.mount(sd,'/sd')
record_audio(duration=5)
play_audio()
except Exception as e:
print("异常:",e)
# 测试
'''
import time
from machine import I2S, Pin
import math
# I2S配置
i2s = I2S(0,
sck=Pin(22), ws=Pin(23), sd=Pin(21),
mode=I2S.RX,
bits=16,
rate=16000,
channel_format=I2S.ONLY_LEFT)
# 参数配置
SILENCE_THRESHOLD = 0.02# 需根据环境噪声校准
CHECK_INTERVAL = 0.1 # 检测间隔(秒)
SILENCE_DURATION = 1.0 # 目标静默时长
buffer = bytearray(1024)# 512个16位样本
last_sound_time = time.time()
while True:
i2s.readinto(buffer)# 读取I2S数据:ml-citation{ref="6" data="citationList"}
# 计算当前块RMS值
sum_sq = 0
for i in range(0, len(buffer), 2):
sample = int.from_bytes(buffer, 'little', True)
sum_sq += (sample / 32768) ** 2# 16位有符号转浮点:ml-citation{ref="6" data="citationList"}
rms = math.sqrt(sum_sq / 512)
# 更新最后有声时间戳
if rms > SILENCE_THRESHOLD:
last_sound_time = time.time()
# 判断静默持续时间
if (time.time() - last_sound_time) >= SILENCE_DURATION:
print("检测到持续静默")
# 触发后续处理
'''
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页:
[1]