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, gcspi = 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 = 0buffer = bytearray(2048)  # 小缓冲区减少内存压力while bytes_written < total_bytes:read = i2s.readinto(buffer)if read == 0:print("警告:未读取到数据")continuef.write(buffer[:read])bytes_written += readgc.collect()# 实时进度elapsed = time.ticks_diff(time.ticks_ms(), start_time) / 1000print(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 DACwhile True:try:num_read = f.readinto(wav_samples_mv)# WAV文件结束if num_read == 0: break# 直到所有样本都写入I2S外围设备num_written = 0while 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, gcspi = 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 headerdef 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 = 0buffer = bytearray(1024)  # 小缓冲区减少内存压力while bytes_written < total_bytes:read = i2s.readinto(buffer)if read == 0:print("警告:未读取到数据")continuef.write(buffer[:read])bytes_written += readgc.collect()# 实时进度elapsed = time.ticks_diff(time.ticks_ms(), start_time) / 1000print(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 DACwhile True:try:num_read = f.readinto(wav_samples_mv)# WAV文件结束if num_read == 0: break# 直到所有样本都写入I2S外围设备num_written = 0while 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 = 0for 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("检测到持续静默")# 触发后续处理
'''

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/901482.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

UE5 物理模拟 与 触发检测

文章目录 碰撞条件开启模拟关闭模拟 多层级的MeshUE的BUG 触发触发条件 碰撞 条件 1必须有网格体组件 2网格体组件必须有网格&#xff0c;没有网格虽然可以开启物理模拟&#xff0c;但是不会有任何效果 注意开启的模拟的网格体组件会计算自己和所有子网格的mesh范围 3只有网格…

微信小程序 - swiper轮播图

官方文档&#xff1a;https://developers.weixin.qq.com/miniprogram/dev/component/swiper.html <swiper indicator-color"ivory" indicator-active-color"#d43c33" indicator-dots autoplay><swiper-item><image src"/images/banner…

深入探究C#官方MCP:开启AI集成新时代

一、引言 在当今数字化时代&#xff0c;.NET 开发领域不断演进&#xff0c;而 C# 官方 MCP&#xff08;Model Context Protocol&#xff0c;模型上下文协议&#xff09;的出现&#xff0c;无疑为开发者们带来了全新的机遇与挑战。随着人工智能技术的迅猛发展&#xff0c;将 AI…

二分查找法

使用二分查找法的前提&#xff1a;&#xff08;1&#xff09;数组为有序数组. &#xff08;2&#xff09;数组中无重复元素. 二分的两种写法&#xff1a; 方法一&#xff1a;[left&#xff0c;right] class Solution { public:int search(vector<int>& nums, int …

HarmonyOS:页面滚动时标题悬浮、背景渐变

一、需求场景 进入到app首页或者分页列表首页时&#xff0c;随着页面滚动&#xff0c;分类tab要求固定悬浮在顶部。进入到app首页、者分页列表首页、商品详情页时&#xff0c;页面滚动时&#xff0c;顶部导航栏&#xff08;菜单、标题&#xff09;背景渐变。 二、相关技术知识点…

鲲鹏+昇腾部署集群管理软件GPUStack,两台服务器搭建双节点集群【实战详细踩坑篇】

前期说明 配置&#xff1a;2台鲲鹏32C2 2Atlas300I duo&#xff0c;之前看网上文档&#xff0c;目前GPUstack只支持910B芯片&#xff0c;想尝试一下能不能310P也部署试试&#xff0c;毕竟华为的集群软件要收费。 系统&#xff1a;openEuler22.03-LTS 驱动&#xff1a;24.1.rc…

React中 点击事件写法 的注意(this、箭头函数)

目录 ‌1、错误写法‌&#xff1a;onClick{this.acceptAlls()} ‌2、正确写法‌&#xff1a;onClick{this.acceptAlls}&#xff08;不带括号&#xff09; 总结 方案1&#xff1a;构造函数绑定 方案2&#xff1a;箭头函数包装方法&#xff08;更简洁&#xff09; 方案3&am…

【路由交换方向IE认证】BGP选路原则之Weight属性

文章目录 一、路由器BGP路由的处理过程控制平面和转发平面选路工具 二、BGP的选路顺序选路的前提选路顺序 三、Wight属性选路原则规则9与规则11的潜移默化使用Weight值进行选路直接更改Weight值进行选路配合使用route-map进行选路 四、BGP邻居建立配置 一、路由器BGP路由的处理…

Missashe考研日记-day20

Missashe考研日记-day20 1 高数 学习时间&#xff1a;2h30min学习内容&#xff1a; 今天当然是刷题啦&#xff0c;做不等式的证明板块的真题&#xff0c;证明题懂的都懂&#xff0c;难起来是真的一点思路都没有&#xff0c;这个板块还没做完&#xff0c;做完再总结题型。 2…

了解JVM

一.JVM概述 1.JVM的作用 把字节码编译为机器码去执行,负责把字节码装载到虚拟机中 现在的 JVM 不仅可以执行 java 字节码文件,还可以执行其他语言编译后的字节码文件,是一个跨语言平台 2.JVM的组成部分 类加载器&#xff08;ClassLoader&#xff09;运行时数据区&#x…

LeetCode LCR157 套餐内商品的排列顺序

生成字符串的全部排列&#xff08;去重&#xff09;&#xff1a;从问题到解决方案的完整解析 问题背景 在编程和算法设计中&#xff0c;生成字符串的所有排列是一个经典问题。它不仅出现在算法竞赛中&#xff0c;也在实际开发中有着广泛的应用&#xff0c;比如生成所有可能的…

pgsql:关联查询union(并集)、except(差集)、intersect(交集)

pgsql:关联查询union(并集)、except(差集)、intersect(交集)_pgsql except-CSDN博客

微信小程序中使用ECharts 并且动态设置数据

项目下载地址 GitHub 地址 https://github.com/ecomfe/echarts-for-weixin 将当前文件夹里的内容拷贝到项目中 目录&#xff1a; json: {"usingComponents": {"ec-canvas": "../components/ec-canvas/ec-canvas"} }wxml&#xff1a; <ec…

RV1126 人脸识别门禁系统解决方案

1. 方案简介 本方案为类人脸门禁机的产品级解决方案,已为用户构建一个带调度框架的UI应用工程;准备好我司的easyeai-api链接调用;准备好UI的开发环境。具备低模块耦合度的特点。其目的在于方便用户快速拓展自定义的业务功能模块,以及快速更换UI皮肤。 2. 快速上手 2.1 开…

深度学习ResNet模型提取影响特征

大家好&#xff0c;我是带我去滑雪&#xff01; 影像组学作为近年来医学影像分析领域的重要研究方向&#xff0c;致力于通过从医学图像中高通量提取大量定量特征&#xff0c;以辅助疾病诊断、分型、预后评估及治疗反应预测。这些影像特征涵盖了形状、纹理、灰度统计及波形变换等…

DeepSeek 接入 Word 完整教程

一、前期准备 1.1 注册并获取 API 密钥 访问 DeepSeek 平台&#xff1a; 打开浏览器&#xff0c;访问 DeepSeek 官方网站&#xff08;或您使用的相应平台&#xff09;。注册并登录您的账户。 创建 API 密钥&#xff1a; 在用户控制面板中&#xff0c;找到“API Keys”或“API…

驱动开发硬核特训 · Day 7:深入掌握 Linux 驱动资源管理机制(Resource Management)

&#x1f50d; B站相应的视屏教程&#xff1a; &#x1f4cc; 内核&#xff1a;博文视频 - 总线驱动模型实战全解析 —— 以 PCA9450 PMIC 为例 敬请关注&#xff0c;记得标为原始粉丝。 &#x1f6a9; 在 Linux 驱动开发中&#xff0c;资源管理机制决定了驱动的稳定性与可靠性…

什么是TensorFlow?

TensorFlow 是由 Google Brain 团队开发的开源机器学习框架&#xff0c;被广泛应用于深度学习和人工智能领域。它的基本概念包括&#xff1a; 1. 张量&#xff08;Tensor&#xff09;&#xff1a;在 TensorFlow 中&#xff0c;数据以张量的形式进行处理。张量是多维数组的泛化…

【ChCore Lab 01】Bomb Lab 拆炸弹实验(ARM汇编逆向工程)

文章目录 1. 前言2. 实验代码版本问题3. 关于使用问题4. 宏观分析5. read_line 函数介绍6. phase_0 函数6.1. read_int 函数6.2. 回到 phase_0 函数继续分析6.3. 验证结果 7. phase_1 函数7.2. 验证结果 8. phase_2 函数8.1. read_8_numbers 函数8.2. 回到 phase_2 函数继续分析…

《Vue Router实战教程》20.路由懒加载

欢迎观看《Vue Router 实战&#xff08;第4版&#xff09;》视频课程 路由懒加载 当打包构建应用时&#xff0c;JavaScript 包会变得非常大&#xff0c;影响页面加载。如果我们能把不同路由对应的组件分割成不同的代码块&#xff0c;然后当路由被访问的时候才加载对应组件&am…