MicroPython 开辟ESP32应用教程 之 I2S、INMP441音频录制、MAX98357A音频播 ...

打印 上一主题 下一主题

主题 1607|帖子 1607|积分 4821

本课程我们讲解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等主流微控制器平台,通过统一接口简化跨硬件开辟‌
三)、核心功能实现


  • ‌音频输入/输出‌

    • 灌音‌:从麦克风模块获取PCM音频数据
    • 播放‌:向DAC或音频解码器发送音频流‌27。

  • 参数机动设置‌
    初始化时可设置关键参数:
    1. i2s = I2S(id,  # 硬件实例编号(如I2S.NUM0)
    2.           sck=Pin(11), ws=Pin(12), sd=Pin(13),  # 引脚映射
    3.           mode=I2S.RX,  # 模式(RX/TX)
    4.           bits=16,      # 采样位深
    5.           format=I2S.MONO,  # 声道格式 MONO为单声道,STEREO为立体声
    6.           rate=16000,   # 采样率
    7.           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设置示例:
  1. from sdcard import SDCard
  2. import os, time, gc
  3. spi = SPI(2,
  4.                   baudrate=80000000,
  5.                   polarity=0,
  6.                   phase=0,
  7.                   sck=Pin(4),
  8.                   mosi=Pin(5),
  9.                   miso=Pin(16))
  10. sd = SDCard(spi,Pin(17,Pin.OUT))
复制代码
二)、文件利用API

        基础文件读写
        使用标准文件利用接口:
 
  1. def test_sd():
  2.     os.mount(sd,'/sd')
  3.     # 重新查询系统文件目录
  4.     print('挂载SD后的系统目录:{}'.format(os.listdir()))
  5.     with open("/sd/test.txt", "w") as f:
  6.             f.write(str("Hello MicroPython!"))
  7.     # 从sd卡目录下读取hello.txt文件内容
  8.     with open("/sd/test.txt", "r") as f:
  9.         # 打印读取的内容
  10.         data = f.read()
  11.         print (data)
复制代码
四、inmp4411录制音频

通过前面的讲解,这一末节的内容需要掌握的知识点我们都已经掌握,直接上代码:
  1. audiofilename = '/sd/rec.pcm'
  2. def record_audio(filename=audiofilename, duration=5, sample_rate=16000):
  3. #     # 硬件诊断
  4.     print("初始化I2S...")
  5.     try:
  6.         i2s = I2S(
  7.             0,
  8.             sck=Pin(11), ws=Pin(12), sd=Pin(13),
  9.             mode=I2S.RX,
  10.             bits=16,
  11.             format=I2S.MONO,
  12.             rate=sample_rate,
  13.             ibuf=4096
  14.         )
  15.     except Exception as e:
  16.         print("I2S初始化失败:", e)
  17.         return
  18.     # 计算数据量
  19.     bytes_per_second = sample_rate * 2  # 16bit=2字节
  20.     total_bytes = bytes_per_second * duration
  21. #    header = createWavHeader(sample_rate, 16, 1, total_bytes)
  22.    
  23.     # 录音循环
  24.     try:
  25.         with open(audiofilename, 'wb') as f:
  26. #            f.write(header)
  27.             start_time = time.ticks_ms()
  28.             bytes_written = 0
  29.             buffer = bytearray(2048)  # 小缓冲区减少内存压力
  30.             
  31.             while bytes_written < total_bytes:
  32.                 read = i2s.readinto(buffer)
  33.                 if read == 0:
  34.                     print("警告:未读取到数据")
  35.                     continue
  36.                
  37.                 f.write(buffer[:read])
  38.                 bytes_written += read
  39.                 gc.collect()
  40.                
  41.                 # 实时进度
  42.                 elapsed = time.ticks_diff(time.ticks_ms(), start_time) / 1000
  43.                 print(f"进度: {bytes_written/total_bytes*100:.1f}%, 时间: {elapsed:.1f}s")
  44.                
  45.     except OSError as e:
  46.         print("文件写入错误:", e)
  47.     finally:
  48.         i2s.deinit()
  49. #        print("录音结束,文件大小:", os.stat(audiofilename)[6], "字节")
  50.         print("录音结束,文件大小:", bytes_written, "字节")
复制代码
但这里需要分析一下的是,我们刚开始开辟的时候,录制的音频文件中的数据满是0,也就是说没有声音,噪音都没有,检查连接线、换IO口等等,各种折腾,但题目依然存在,后来因为出了别的的错误,就暂停了,具体可以参考:MicroPython 开辟ESP32应用教程 之 WIFI、BLE共用常见题目处置惩罚及中断处置惩罚函数留意事项
上文中提到的题目处置惩罚完后,我们继续折腾音频录制及播放的功能,奇怪的事情发生了,连接好各功能模块后,测试,居然好了,怀疑是上文中提到的电源的题目,但把外接电源移除,测试没有题目。
也就是说,到现在,我们照旧不知道之前为什么有题目?现在为什么好了?只能怀疑电源不稳?
五、MAX98357A音频播放

这个也没什么好讲,直接上代码吧
  1. audiofilename = '/sd/rec.pcm'
  2. 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)
  3. def play_audio(filename='/sd/rec.wav', duration=5, sample_rate=16000):        
  4. #    audio_out.volume(80)
  5.     with open(audiofilename,'rb') as f:
  6.         # 跳过文件的开头的44个字节,直到数据段的第1个字节
  7. #        pos = f.seek(44)
  8.      
  9.         # 用于减少while循环中堆分配的内存视图
  10.         wav_samples = bytearray(1024)
  11.         wav_samples_mv = memoryview(wav_samples)
  12.          
  13.         print("开始播放音频...")
  14.         
  15.         #并将其写入I2S DAC
  16.         while True:
  17.             try:
  18.                 num_read = f.readinto(wav_samples_mv)
  19.                
  20.                 # WAV文件结束
  21.                 if num_read == 0:
  22.                     break
  23.      
  24.                 # 直到所有样本都写入I2S外围设备
  25.                 num_written = 0
  26.                 while num_written < num_read:
  27.                     num_written += audio_out.write(wav_samples_mv[num_written:num_read])
  28.                     
  29.             except Exception as ret:
  30.                 print("产生异常...", ret)
复制代码
六、完整代码
 


该代码简单修改可生存为WAV格式文件,可以用我们常见的音频播放软件播放。
  1. from machine import I2S, Pin,SPI
  2. from sdcard import SDCard
  3. import os, time, gc
  4. spi = SPI(2,
  5.                   baudrate=20000000,
  6.                   polarity=0,
  7.                   phase=0,
  8.                   sck=Pin(4),
  9.                   mosi=Pin(5),
  10.                   miso=Pin(16))
  11. sd = SDCard(spi,Pin(17,Pin.OUT))
  12. audiofilename = '/sd/rec.pcm'
  13. def createWavHeader(sampleRate, bitsPerSample, num_channels, datasize):   
  14.     riff_size = datasize + 36 - 8  # 修正RIFF块大小
  15.     header = bytes("RIFF", 'ascii')
  16.     header += riff_size.to_bytes(4, 'little')
  17.     header += bytes("WAVE", 'ascii')
  18.     header += bytes("fmt ", 'ascii')
  19.     header += (16).to_bytes(4, 'little')          # fmt块大小
  20.     header += (1).to_bytes(2, 'little')            # PCM格式
  21.     header += num_channels.to_bytes(2, 'little')   # 声道数
  22.     header += sampleRate.to_bytes(4, 'little')     # 采样率
  23.     header += (sampleRate * num_channels * bitsPerSample // 8).to_bytes(4, 'little')  # 字节率
  24.     header += (num_channels * bitsPerSample // 8).to_bytes(2, 'little')  # 块对齐
  25.     header += bitsPerSample.to_bytes(2, 'little')  # 位深
  26.     header += bytes("data", 'ascii')
  27.     header += datasize.to_bytes(4, 'little')       # 数据块大小
  28.     return header
  29. def record_audio(filename=audiofilename, duration=5, sample_rate=16000):
  30. #     # 硬件诊断
  31.     print("初始化I2S...")
  32.     try:
  33.         i2s = I2S(
  34.             0,
  35.             sck=Pin(11), ws=Pin(12), sd=Pin(13),
  36.             mode=I2S.RX,
  37.             bits=16,
  38.             format=I2S.MONO,
  39.             rate=sample_rate,
  40.             ibuf=4096
  41.         )
  42.     except Exception as e:
  43.         print("I2S初始化失败:", e)
  44.         return
  45.     # 计算数据量
  46.     bytes_per_second = sample_rate * 2  # 16bit=2字节
  47.     total_bytes = bytes_per_second * duration
  48. #    header = createWavHeader(sample_rate, 16, 1, total_bytes)
  49.    
  50.     # 录音循环
  51.     try:
  52.         with open(audiofilename, 'wb') as f:
  53. #            f.write(header)
  54.             start_time = time.ticks_ms()
  55.             bytes_written = 0
  56.             buffer = bytearray(1024)  # 小缓冲区减少内存压力
  57.             
  58.             while bytes_written < total_bytes:
  59.                 read = i2s.readinto(buffer)
  60.                 if read == 0:
  61.                     print("警告:未读取到数据")
  62.                     continue
  63.                
  64.                 f.write(buffer[:read])
  65.                 bytes_written += read
  66.                 gc.collect()
  67.                
  68.                 # 实时进度
  69.                 elapsed = time.ticks_diff(time.ticks_ms(), start_time) / 1000
  70.                 print(f"进度: {bytes_written/total_bytes*100:.1f}%, 时间: {elapsed:.1f}s")
  71.                
  72.     except OSError as e:
  73.         print("文件写入错误:", e)
  74.     finally:
  75.         i2s.deinit()
  76. #        print("录音结束,文件大小:", os.stat(audiofilename)[6], "字节")
  77.         print("录音结束,文件大小:", bytes_written, "字节")
  78.         
  79. 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)
  80. def play_audio(filename='/sd/rec.wav', duration=5, sample_rate=16000):        
  81. #    audio_out.volume(80)
  82.     with open(audiofilename,'rb') as f:
  83.         # 跳过文件的开头的44个字节,直到数据段的第1个字节
  84. #        pos = f.seek(44)
  85.      
  86.         # 用于减少while循环中堆分配的内存视图
  87.         wav_samples = bytearray(1024)
  88.         wav_samples_mv = memoryview(wav_samples)
  89.          
  90.         print("开始播放音频...")
  91.         
  92.         #并将其写入I2S DAC
  93.         while True:
  94.             try:
  95.                 num_read = f.readinto(wav_samples_mv)
  96.                
  97.                 # WAV文件结束
  98.                 if num_read == 0:
  99.                     break
  100.      
  101.                 # 直到所有样本都写入I2S外围设备
  102.                 num_written = 0
  103.                 while num_written < num_read:
  104.                     num_written += audio_out.write(wav_samples_mv[num_written:num_read])
  105.                     
  106.             except Exception as ret:
  107.                 print("产生异常...", ret)
  108. if __name__ == "__main__":
  109.     try:
  110.         os.mount(sd,'/sd')   
  111.         record_audio(duration=5)
  112.         play_audio()
  113.     except Exception as e:
  114.         print("异常:",e)
  115. # 测试
  116. '''
  117. import time
  118. from machine import I2S, Pin
  119. import math
  120. # I2S配置
  121. i2s = I2S(0,
  122.           sck=Pin(22), ws=Pin(23), sd=Pin(21),
  123.           mode=I2S.RX,
  124.           bits=16,
  125.           rate=16000,
  126.           channel_format=I2S.ONLY_LEFT)
  127. # 参数配置
  128. SILENCE_THRESHOLD = 0.02  # 需根据环境噪声校准
  129. CHECK_INTERVAL = 0.1      # 检测间隔(秒)
  130. SILENCE_DURATION = 1.0    # 目标静默时长
  131. buffer = bytearray(1024)  # 512个16位样本
  132. last_sound_time = time.time()
  133. while True:
  134.     i2s.readinto(buffer)  # 读取I2S数据‌:ml-citation{ref="6" data="citationList"}
  135.    
  136.     # 计算当前块RMS值
  137.     sum_sq = 0
  138.     for i in range(0, len(buffer), 2):
  139.         sample = int.from_bytes(buffer[i:i+2], 'little', True)
  140.         sum_sq += (sample / 32768) ** 2  # 16位有符号转浮点‌:ml-citation{ref="6" data="citationList"}
  141.     rms = math.sqrt(sum_sq / 512)
  142.    
  143.     # 更新最后有声时间戳
  144.     if rms > SILENCE_THRESHOLD:
  145.         last_sound_time = time.time()
  146.    
  147.     # 判断静默持续时间
  148.     if (time.time() - last_sound_time) >= SILENCE_DURATION:
  149.         print("检测到持续静默")
  150.         # 触发后续处理
  151. '''
复制代码


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

小秦哥

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表