本课程我们讲解Micropython for ESP32 的i2s及其应用,比如INMP441音频录制、MAX98357A音频播放等,还有SD卡的读写。
一、硬件预备
1、支持micropython的ESP32S3开辟板
2、INMP441数字全向麦克风模块
3、MAX98357A音频播放模块
4、SD卡模块
5、面包板及连接线若干
连接方式:
inmp441 | MAX98357A | ESP32S3 | SD | | IO13 | WS | | IO12 | SCK | | IO11 | L/R接地 | | | | SD接VCC | | | GAIN接地 | | | DIN | IO37 | | BCLK | IO38 | | LRC | IO39 | SD卡模块 | ESP32S3 | SCK | IO4 | MOSI | IO5 | MISO | IO16 | CS | IO17 |
二、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)[6], "字节")
- 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[num_written:num_read])
-
- 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)[6], "字节")
- 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[num_written:num_read])
-
- 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[i:i+2], '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企服之家,中国第一个企服评测及商务社交产业平台。 |