MicroPython 开发ESP32应用教程 之 I2S、INMP441音频录制、MAX98357A音频播放、SD卡读写

本课程我们讲解Micropython for ESP32 的i2s及其应用,比如INMP441音频录制、MAX98357A音频播放等,还有SD卡的读写。

一、硬件准备

1、支持micropython的ESP32S3开发板
2、INMP441数字全向麦克风模块
3、MAX98357A音频播放模块
4、SD卡模块
5、面包板及连接线若干

连接方式:
                 

inmp441MAX98357AESP32S3
SDIO13
WSIO12
SCKIO11
L/R接地
SD接VCC
GAIN接地
DINIO37
BCLKIO38
LRCIO39
SD卡模块ESP32S3
SCKIO4
MOSIIO5
MISOIO16
CSIO17

       

二、i2s介绍 

一)、I2S协议基础

I2S(Inter-IC Sound)是一种同步串行通信协议,专为数字音频设备设计,支持单向/双向音频数据传输。其物理层包含三条信号线:

  • SCK‌(串行时钟):同步数据传输速率
  • WS‌(字选择):区分左右声道或定义采样率
  • SD‌(串行数据):传输实际音频数据流‌
二)、MicroPython I2S类特性

        A.  仅支持主设备操作模式,可控制SCK和WS信号的生成,适用于连接麦克风、
             DAC等从设备‌

        B. 支持ESP32、STM32、RP2等主流微控制器平台,通过统一接口简化跨硬件开发‌

三)、核心功能实现
  1. ‌音频输入/输出‌

    • 录音‌:从麦克风模块获取PCM音频数据
    • 播放‌:向DAC或音频解码器发送音频流‌27。
  2. 参数灵活配置‌
    初始化时可设置关键参数:

    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"}
    

  3. ‌中断与DMA支持‌
    支持异步数据读写,通过DMA减少CPU占用率,提升实时性‌

 

 四)、典型应用场景
  1. 音频播放器
    播放WAV/MP3文件(需解码库支持)‌。

  2. 语音采集系统
    连接INMP441等数字麦克风实现环境音录制‌。

  3. 实时语音处理
    结合神经网络进行关键词识别或声纹分析‌

三、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("检测到持续静默")
        # 触发后续处理
'''

### 实现ESP32-S3通过MicroPython读取INMP441麦克风数据并使用MAX98357进行实时音频播放 为了实现这一目标,需要配置I2S接口用于连接INMP441麦克风和MAX98357扬声器模块。下面提供了一个具体的代码实例,展示了如何利用MicroPython完成上述任务。 #### 初始化硬件设置 首先定义所需的引脚分配以及初始化I2S对象: ```python from machine import I2S, Pin # 定义I2S总线参数 bclk = Pin(26) # BCLK pin for INMP441 wsi = Pin(25) # WS/IWS pin for INMP441 din = Pin(22) # DIN/SDATA pin from INMP441 to MAX98357A dout = Pin(21) # DOUT pin of INMP441 (not used here) audio_out = I2S( I2S.NUM0, bck=bclk, ws=wsi, sd=din, mode=I2S.TX, bits=16, format=I2S.MONO, rate=16000, ibuf=20000 ) ``` 这段代码设置了I2S通信模式为传输(TX),指定了采样率为16kHz,并创建了大小为20KB的内部缓冲区[^1]。 #### 数据处理与回放逻辑 接下来编写循环结构来持续获取来自INMP441的数据流并通过MAX98357输出声音信号: ```python import time while True: try: buf = bytearray(1024) num_read = audio_out.readinto(buf) if not num_read: continue # 将接收到的数据立即发送出去形成回音效果 audio_out.write(buf[:num_read]) except KeyboardInterrupt: break time.sleep_ms(10) ``` 此部分实现了简单的回声功能,即捕获到的声音会几乎同步地被重播出来。`readinto()`方法用来填充字节数组,而`write()`则负责将这些数据推送到DAC端口以便于发声设备发出声响[^2]。 请注意,在实际应用中可能还需要考虑更多细节,比如噪声抑制、增益调整等高级特性;此外,由于这里采用的是最基础的方式来进行即时录音与播放,因此可能会存在轻微延迟现象。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

永远的元子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值