ESP32移植Openharmony外设篇(10)inmp441麦克风

06-01 1713阅读

inmp441麦克风模块

模块简介

INMP441是一款高性能、低功耗的微型电容式MEMS麦克风,采用数字输出,广泛应用于智能手机、平板电脑、智能家居、可穿戴设备等场景。其核心特点包括:

  • 高信噪比(SNR):61 dBA,适合远场和近场语音采集。
  • 数字接口:支持24位I²S输出,可直接连接微控制器或DSP,无需额外编解码器。
  • 低功耗:工作电流仅1.4 mA,适用于电池供电设备。
  • 宽频率响应:60 Hz至15 kHz,覆盖人耳可听范围,音质自然清晰。
  • 小型封装:4.72 mm × 3.76 mm × 1 mm,适合高密度集成。

    ESP32移植Openharmony外设篇(10)inmp441麦克风

    引脚介绍

    INMP441模块的引脚配置如下(不同厂商可能略有差异):

    引脚名称

    符号

    功能描述

    电源输入

    VDD

    供电电压范围1.8V至3.3V,需严格匹配以保障性能。

    接地

    GND

    公共地线,确保电路参考点稳定。

    字选择信号

    WS

    I²S协议中的声道选择信号(左/右),由主机控制时序。

    串行时钟

    SCK

    I²S时钟信号,由主机生成,控制数据传输速率。

    数据输出

    SD

    数字音频数据输出,按I²S协议在时钟边沿传输。

    声道选择

    L/R

    配置麦克风为左声道(低电平)或右声道(高电平),部分模块可能固定此引脚。

    这里根据声道选择引脚可以分为单声道采集和双声道采集两种模式:

    • 单声道采集:连接单个INMP441,L/R引脚固定为左声道,仅使用WS信号的左声道周期。
    • 双声道系统:使用两个模块,分别配置为左/右声道,共享SCK和WS信号,通过分时复用SD线传输数据。

      工作原理

      INMP441基于MEMS电容传感技术:

      1. 声波转换:声波振动导致麦克风膜片与背极板间电容变化。
      2. 信号处理:内置ADC将模拟信号转换为24位数字数据,并通过抗混叠滤波器优化。
      3. 数字输出:通过I²S接口输出数据,主机按协议时序读取。

      通信协议(I²S)

      INMP441采用标准I²S协议,关键参数如下:

      • 数据格式:24位有符号整数,大端模式传输36。
      • 时序要求:
          • WS(LRCLK):周期为64个SCK时钟,低电平传输左声道,高电平传输右声道。
          • SCK(BCLK):主机生成的时钟信号,数据在SCK的上升沿或下降沿被采样(具体取决于配置)。
          • 数据传输:从WS下降沿后的第2个SCK上升沿开始,连续传输24位数据。
            • 主从模式:INMP441为从机,需由微控制器(如STM32、ESP32)作为主机驱动时序

              ESP32移植Openharmony外设篇(10)inmp441麦克风

              既然模块采用标准的I²S协议通信那就好办了,我们只需要使用ESPIDF官方封装好的I²S库就行!

              tips:

              这里要注意,我们移植到ESP32上的是ESPIDF V4.3.1,最新版的I²S API有所不同,读者可以根据自己使用的ESPIDF版本修改代码。

              这里我们将V4.3.1和最新版的I²S文档都提供给大家:

              I2S - ESP32 - — ESP-IDF Programming Guide v4.3.1 documentation

              I2S - ESP32 - — ESP-IDF 编程指南 latest 文档

              注意事项

              • 电气参数:供电电压需在1.8V–3.3V之间,避免超压损坏模块。
              • 信号完整性:
                  • SD线下拉:建议在SD引脚接10 kΩ下拉电阻,防止高阻态时误读高电平。
                  • 时钟匹配:SCK频率需与主机配置一致,避免数据错位。
                    • 声道配置:若使用双麦克风,需通过L/R引脚区分声道,并确保WS信号同步切换。
                    • 声学环境:避免强噪声干扰,必要时添加物理隔音或软件滤波。
                    • 开发调试:
                        • 使用示波器验证I²S时序,确保数据对齐。
                        • 在代码中处理24位符号扩展,避免数据解析错误。

                          UDP通信

                          UDP简介
                          1. 无连接通信:UDP在传输数据时不需要建立连接,直接将数据包发送出去。这使得UDP的传输效率比TCP更高,因为省去了建立连接所需的时间和资源。
                          2. 不可靠传输:UDP不提供可靠性保证,在传输过程中可能会出现数据包丢失、重复、乱序等问题。然而,由于UDP的无连接特点,应用层可以自行处理这些问题,如通过重传机制来恢复丢失的数据包。
                          3. 简单轻量:UDP的协议头部较小,只包含源端口、目的端口、长度、校验和以及数据等字段,这使得UDP的数据包结构相对简单且轻量。此外,UDP不进行流量控制和拥塞控制,进一步减少了协议开销。
                          4. 低延迟:UDP不需要等待确认,因此可以实现较低的传输延迟。这使得UDP非常适合实时应用场景,如视频、音频、游戏等。

                          为什么我们使用UDP传输麦克风采集到的数据流?

                          1. 实时性要求高:麦克风声音流属于实时音频数据,对延迟的要求非常高。UDP的低延迟特性使其成为传输实时音频数据的理想选择。相比之下,TCP虽然可靠但传输延迟较高,不适合实时音频传输。
                          2. 容错性强:对于麦克风声音流来说,偶尔的数据包丢失或乱序并不会对整体音质造成太大影响。因为人耳对音频数据的容错性较强,即使丢失一些数据包也不会导致音频完全中断或失真。这使得UDP的不可靠传输特性在音频传输中变得可接受。
                          3. 资源占用少:UDP的协议开销较小,传输效率高。在资源受限的环境中(如嵌入式设备或移动网络),使用UDP可以减少资源占用并提高传输效率。这对于实时音频传输来说非常重要,因为资源占用少意味着可以更快地处理和传输音频数据。

                          参考代码

                          inmp441.c
                          #include 
                          #include "cmsis_os2.h"
                          #include "ohos_run.h"
                          #include "esp_system.h"
                          #include "nvs_flash.h"
                          #include "esp_log.h"
                          #include "driver/i2s.h"
                          #include "driver/gpio.h"
                          #define INMP_SD GPIO_NUM_16  // 数据引脚
                          #define INMP_SCK GPIO_NUM_17 // 时钟引脚
                          #define INMP_WS GPIO_NUM_18  // 字选择引脚
                          void I2S_Init(void)
                          {
                            // I2S 配置
                            i2s_config_t i2s_config = {
                                .mode = I2S_MODE_MASTER | I2S_MODE_RX,             // 主模式,接收模式
                                .sample_rate = 16000,                              // 采样率 44.1kHz
                                .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,      // 24 位数据
                                .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT,       // 单声道(左声道)
                                .communication_format = I2S_COMM_FORMAT_STAND_I2S, // 标准 I2S 格式
                                .dma_buf_count = 8,                                // DMA 缓冲区数量
                                .dma_buf_len = 1024,                               // 每个 DMA 缓冲区长度
                                .use_apll = false,                                 // 不使用 APLL
                            };
                            // I2S 引脚配置
                            i2s_pin_config_t pin_config = {
                                .bck_io_num = INMP_SCK, // 时钟引脚
                                .ws_io_num = INMP_WS,   // 字选择引脚
                                .data_out_num = -1,     // 不使用输出引脚
                                .data_in_num = INMP_SD, // 数据输入引脚
                            };
                            // 初始化 I2S 驱动
                            i2s_driver_install(I2S_NUM_0, &i2s_config, 0, NULL);
                            i2s_set_pin(I2S_NUM_0, &pin_config);
                          }
                          esp_err_t inmp441_read_data(void *read_data_buff, size_t size, size_t *bytes_read)
                          {
                            // 读取 I2S 数据
                            return i2s_read(I2S_NUM_0, read_data_buff, size, bytes_read, portMAX_DELAY);
                          }
                          void inmp441_data_process(uint8_t *data_buff, uint32_t len, int32_t *real_data)
                          {
                            // 处理读取的数据
                            for (uint16_t i = 0; i  0)
                            {
                              sleep(1);
                              ConnectTimeout--;
                              if (g_ConnectSuccess == 1)
                              {
                                printf("WaitConnectResult:wait success[%d]s\n", (DEF_TIMEOUT - ConnectTimeout));
                                break;
                              }
                            }
                            if (ConnectTimeout  0)
                            {
                              g_ConnectSuccess = 1;
                              printf("callback function for wifi connect\r\n");
                            }
                            else
                            {
                              g_ConnectSuccess = 0;
                              printf("connect wifi_error, please check password, state:%d, try connect again\r\n", state);
                              esp_wifi_connect();
                            }
                            return;
                          }
                          static void WaitScanResult(void)
                          {
                            int scanTimeout = DEF_TIMEOUT;
                            while (scanTimeout > 0)
                            {
                              sleep(ONE_SECOND);
                              scanTimeout--;
                              if (g_staScanSuccess == 1)
                              {
                                printf("WaitScanResult:wait success[%d]s\n", (DEF_TIMEOUT - scanTimeout));
                                break;
                              }
                            }
                            if (scanTimeout  0)
                              {
                                // 发送音频数据
                                udp_send_mes(socket, read_data_buff, len);
                              }
                            }
                            udp_close();
                          }
                          void LLM_Task(void)
                          {
                            osThreadAttr_t attr;
                            attr.name = "llm_task";
                            attr.attr_bits = 0U;
                            attr.cb_mem = NULL;
                            attr.cb_size = 0U;
                            attr.stack_mem = NULL;
                            attr.stack_size = 4096;
                            attr.priority = osPriorityNormal;
                            if (osThreadNew(LLM_Test, NULL, &attr) == NULL)
                            {
                              printf("[Inmp441Test] Failed to create LLM_Test!\n");
                            }
                          }
                          OHOS_APP_RUN(LLM_Task);

                          BUILD.gn

                          # Copyright (c) 2022 Hunan OpenValley Digital Industry Development Co., Ltd.
                          # Licensed under the Apache License, Version 2.0 (the "License");
                          # you may not use this file except in compliance with the License.
                          # You may obtain a copy of the License at
                          #
                          #     http://www.apache.org/licenses/LICENSE-2.0
                          #
                          # Unless required by applicable law or agreed to in writing, software
                          # distributed under the License is distributed on an "AS IS" BASIS,
                          # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
                          # See the License for the specific language governing permissions and
                          # limitations under the License.
                          import("//kernel/liteos_m/liteos.gni")
                          module_name = get_path_info(rebase_path("."), "name")
                          kernel_module(module_name){
                              sources = [
                                  "udp_test.c",
                                  "wifi_connect.c",
                                  "inmp441.c",
                                  "main.c"
                              ]
                              include_dirs = [
                                  "//drivers/hdf_core/framework/include/platform/",
                                  "//drivers/hdf_core/framework/include/utils/",
                                  "//drivers/hdf_core/framework/support/platform/include/adc",
                                  "//drivers/hdf_core/adapter/khdf/liteos_m/osal/include/",
                                  "//drivers/hdf_core/framework/include/core/",
                                  "//drivers/hdf_core/framework/include/osal/",
                                  "//drivers/hdf_core/interfaces/inner_api/utils",
                                  "//device/soc/esp/esp32/components/driver/include",
                                  "//device/soc/esp/esp32/components/esp_adc_cal/include",
                                  "//drivers/hdf_core/framework/support/platform/include/gpio",   
                                  "//device/soc/esp/esp32/components/driver/esp32/include",
                                  "//foundation/communication/wifi_lite/interfaces/wifiservice",
                                  "//device/board/esp/esp32/liteos_m/hals/driver/wifi_lite",      
                                  "//device/soc/esp/esp32/components/esp_wifi/include",
                                  "//device/soc/esp/esp32/components/esp_event/include",
                                  "//device/soc/esp/esp32/components/esp_netif/include",
                                  "//device/soc/esp/esp32/components/tcpip_adapter/include",
                                  "//device/soc/esp/esp32/components/spi_flash/sim/stubs/freertos/include",
                                  "//device/soc/esp/esp32/components/osal/include/esp_osal",
                                  "//device/soc/esp/esp32/components/driver/include/driver",
                                  "//device/soc/esp/esp32/components/hal/include/hal"
                              ]
                          }

                          代码调试

                          运行代码发现缺少i2s.h中定义的api函数,但是可以通过右键跳转找到函数的定义,而且能在components中找到i2s.h这个库,说明已经将其移植过来了,那到底是为什么编译提示函数未定义呢?这就跟openharmony的编译过程有关了,简单来说,移植来的库需要在BUILD.gn文件中参与编译才能生效。

                          我的思路是先检查工程文件的BUILD.gn中是否将移植的外设库进行了包含

                          ESP32移植Openharmony外设篇(10)inmp441麦克风

                          发现已经包含了外设库的路径,那么再看看外设库的BUILD.gn文件是否将i2s的驱动进行了编译.

                          使用Ctrl+F搜索关键字i2s,果然在BUILD.gn文件中发现driver/i2s.c这行被注释掉,我们尝试去掉注释再编译。

                          ESP32移植Openharmony外设篇(10)inmp441麦克风

                          发现还是会有报错:

                          ESP32移植Openharmony外设篇(10)inmp441麦克风

                          我们跳转过去后,尝试取消esp_pm.h的依赖,注释掉之后又出现了一个新的报错:

                          ESP32移植Openharmony外设篇(10)inmp441麦克风

                          在Ubuntu中grep 搜索一下这个变量,发现它是用来打印提示信息的,应该删掉这一段代码也不影响。

                          我们这里直接将其注释掉,发现程序能成功运行了!

                          我们可以通过网络助手NetAssist进行UDP协议的网络调试

                          ESP32移植Openharmony外设篇(10)inmp441麦克风

                          这相当于监听本机上的所有1234端口的数据(这里需要与ESP32代码中udp的端口设置一致)

                          数据传输这边没问题,我们还可以编写一个python的上位机用来将数据流保存成wav文件实现录音功能!

免责声明:我们致力于保护作者版权,注重分享,被刊用文章因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理! 部分文章是来自自研大数据AI进行生成,内容摘自(百度百科,百度知道,头条百科,中国民法典,刑法,牛津词典,新华词典,汉语词典,国家院校,科普平台)等数据,内容仅供学习参考,不准确地方联系删除处理! 图片声明:本站部分配图来自人工智能系统AI生成,觅知网授权图片,PxHere摄影无版权图库和百度,360,搜狗等多加搜索引擎自动关键词搜索配图,如有侵权的图片,请第一时间联系我们。

相关阅读

目录[+]

取消
微信二维码
微信二维码
支付宝二维码