INMP441是一款高性能、低功耗的微型电容式MEMS麦克风,采用数字输出,广泛应用于智能手机、平板电脑、智能家居、可穿戴设备等场景。其核心特点包括:
INMP441模块的引脚配置如下(不同厂商可能略有差异):
引脚名称 |
符号 |
功能描述 |
电源输入 |
VDD |
供电电压范围1.8V至3.3V,需严格匹配以保障性能。 |
接地 |
GND |
公共地线,确保电路参考点稳定。 |
字选择信号 |
WS |
I²S协议中的声道选择信号(左/右),由主机控制时序。 |
串行时钟 |
SCK |
I²S时钟信号,由主机生成,控制数据传输速率。 |
数据输出 |
SD |
数字音频数据输出,按I²S协议在时钟边沿传输。 |
声道选择 |
L/R |
配置麦克风为左声道(低电平)或右声道(高电平),部分模块可能固定此引脚。 |
这里根据声道选择引脚可以分为单声道采集和双声道采集两种模式:
INMP441基于MEMS电容传感技术:
INMP441采用标准I²S协议,关键参数如下:
既然模块采用标准的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 文档
为什么我们使用UDP传输麦克风采集到的数据流?
#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 < len; i += 3)
{
*real_data = (data_buff[i] << 16) | (data_buff[i + 1] << 8) | (data_buff[i + 2]); // 拼接 3 个字节
if (*real_data & 0x00800000)
{
*real_data |= 0xFF000000; // 如果是负数,补全符号位
}
}
}
void inmp441_free(uint8_t *data_buff)
{
free(data_buff);
}
/*
* 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.
*/
#include
#include "securec.h"
#include "cmsis_os2.h"
#include "ohos_run.h"
#include "lwip/sockets.h"
#include "lwip/ip_addr.h"
#include "wifi_device.h"
#include "esp_system.h"
#include "nvs_flash.h"
#include "esp_log.h"
#include "driver/gpio.h"
#define OPEN_WIFI_NAME "test"
#define SERVER_IP "192.168.31.238"
#define SERVER_PORT 8080
#define OD_DELAY_1000 1000
#define OD_DELAY_100 100
#define RECV_LEN 511
#define STACK_SIZE 4096
#define PRIORITY 25
osThreadId_t wifi_test_id = NULL;
/*
* 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.
*/
#define SYS_DELAY_TICKS 200
#define TASK_PRIO 25
void udp_connect_wifi(int *sock)
{
struct sockaddr_in client_addr;
char recv_data[512] = {0};
int recv_data_len;
WifiConnect(OPEN_WIFI_NAME);
printf("start wifi_test test\r\n");
while (1)
{
// 创建UDP socket
*sock = socket(AF_INET, SOCK_DGRAM, 0);
if (*sock < 0)
{
printf("Socket error\n");
osDelay(OD_DELAY_100);
continue;
}
// 设置服务器地址结构
memset_s(&client_addr, sizeof(client_addr), 0, sizeof(client_addr));
client_addr.sin_family = AF_INET;
client_addr.sin_port = htons(SERVER_PORT);
client_addr.sin_addr.s_addr = inet_addr(SERVER_IP);
printf("try connect to server " SERVER_IP ":%d\n", SERVER_PORT);
if (connect(*sock, (struct sockaddr *)&client_addr, sizeof(client_addr)) == -1)
{
printf("Connect error\n");
closesocket(*sock);
osDelay(OD_DELAY_1000);
continue;
}
else
{
printf("UDP connected! \n");
break;
}
}
}
void udp_send_mes(int sock, char *message, int message_len)
{
send(sock, message, message_len, 0);
}
void udp_close(int sock)
{
closesocket(sock);
}
/*
* 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.
*/
#include
#include
#include
#include "securec.h"
#include "cmsis_os2.h"
#include "ohos_init.h"
#include "wifi_device.h"
#include "lwip/ip4_addr.h"
#include "lwip/netif.h"
#include "lwip/netifapi.h"
#include "wifi_error_code.h"
#define DEF_TIMEOUT 15
#define ONE_SECOND 1
#define SELECT_WLAN_PORT "wlan0"
#define SELECT_WIFI_SECURITYTYPE WIFI_SEC_TYPE_OPEN
#define STD_TIMEZONE_OFFSET (+8)
#define OD_DELAY_100 100
#define OD_DELAY_200 200
static int g_staScanSuccess = 0;
static int g_ConnectSuccess = 0;
static int ssid_count = 0;
static WifiErrorCode wifi_error;
static WifiEvent g_wifiEventHandler = {0};
static int wifi_sta_init_state = 0;
int sock_fd;
int addr_length;
const int timeZone = 8;
static void WiFiInit(void);
static void WaitScanResult(void);
static int WaitConnectResult(void);
static void OnWifiScanStateChangedHandler(int state, int size);
static void OnWifiConnectionChangedHandler(int state, WifiLinkedInfo *info);
static void OnHotspotStaJoinHandler(StationInfo *info);
static void OnHotspotStateChangedHandler(int state);
static void OnHotspotStaLeaveHandler(StationInfo *info);
void DisableWIFI(void)
{
DisableWifi();
}
static void OnHotspotStaJoinHandler(StationInfo *info)
{
(void)info;
printf("STA join AP\n");
return;
}
static void OnHotspotStaLeaveHandler(StationInfo *info)
{
(void)info;
printf("HotspotStaLeave:info is null.\n");
return;
}
static void OnHotspotStateChangedHandler(int state)
{
printf("HotspotStateChanged:state is %d.\n", state);
return;
}
static void WiFiInit(void)
{
printf("<--Wifi Init-->\r\n");
g_wifiEventHandler.OnWifiScanStateChanged = OnWifiScanStateChangedHandler;
g_wifiEventHandler.OnWifiConnectionChanged = OnWifiConnectionChangedHandler;
g_wifiEventHandler.OnHotspotStaJoin = OnHotspotStaJoinHandler;
g_wifiEventHandler.OnHotspotStaLeave = OnHotspotStaLeaveHandler;
g_wifiEventHandler.OnHotspotStateChanged = OnHotspotStateChangedHandler;
wifi_error = RegisterWifiEvent(&g_wifiEventHandler);
if (wifi_error != WIFI_SUCCESS)
{
printf("register wifi event fail!\r\n");
}
else
{
printf("register wifi event succeed!\r\n");
}
}
static void OnWifiScanStateChangedHandler(int state, int size)
{
(void)state;
if (size > 0)
{
ssid_count = size;
g_staScanSuccess = 1;
}
return;
}
static int result;
int WifiConnect(const char *ssid, const char *psk)
{
WifiScanInfo *info = NULL;
unsigned int size = WIFI_SCAN_HOTSPOT_LIMIT;
static struct netif *g_lwip_netif = NULL;
WifiDeviceConfig select_ap_config = {0};
osDelay(OD_DELAY_200);
printf("<--System Init-->\r\n");
WiFiInit();
if (EnableWifi() != WIFI_SUCCESS)
{
printf("EnableWifi failed, wifi_error = %d\n", wifi_error);
return -1;
}
if (IsWifiActive() == 0)
{
printf("Wifi station is not actived.\n");
return -1;
}
info = malloc(sizeof(WifiScanInfo) * WIFI_SCAN_HOTSPOT_LIMIT);
if (info == NULL)
{
printf("faild to create wifiscanInfo.\n");
return -1;
}
do
{
ssid_count = 0;
g_staScanSuccess = 0;
Scan();
WaitScanResult();
wifi_error = GetScanInfoList(info, &size);
} while (g_staScanSuccess != 1);
strcpy_s(select_ap_config.ssid, sizeof(select_ap_config.ssid), ssid);
printf("[%s][%s] \r\n", select_ap_config.ssid, select_ap_config.preSharedKey);
select_ap_config.securityType = SELECT_WIFI_SECURITYTYPE;
if (AddDeviceConfig(&select_ap_config, &result) == WIFI_SUCCESS)
{
if (ConnectTo(result) == WIFI_SUCCESS && WaitConnectResult() == 1)
{
printf("WiFi connect succeed!\r\n");
wifi_sta_init_state = 1;
}
}
osDelay(OD_DELAY_100);
return 0;
}
static int WaitConnectResult(void)
{
int ConnectTimeout = DEF_TIMEOUT;
while (ConnectTimeout > 0)
{
sleep(1);
ConnectTimeout--;
if (g_ConnectSuccess == 1)
{
printf("WaitConnectResult:wait success[%d]s\n", (DEF_TIMEOUT - ConnectTimeout));
break;
}
}
if (ConnectTimeout <= 0)
{
printf("WaitConnectResult:timeout!\n");
return 0;
}
return 1;
}
static void OnWifiConnectionChangedHandler(int state, WifiLinkedInfo *info)
{
(void)info;
if (state > 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)
{
printf("WaitScanResult:timeout!\n");
}
}
#include
#include "cmsis_os2.h"
#include "ohos_run.h"
#include "esp_system.h"
#include "nvs_flash.h"
#include "esp_log.h"
#include "esp_rom_sys.h"
// 每次读取20ms的数据
#define DATA_LEN 1024 // 1500-28(IP/UDP头)
int socket = -1;
void LLM_Init()
{
I2S_Init();
udp_connect_wifi(&socket);
}
void LLM_Test()
{
uint8_t *read_data_buff = (uint8_t *)malloc(sizeof(uint8_t) * DATA_LEN);
size_t len;
uint16_t packet_counter = 0;
LLM_Init();
while (1)
{
int ret = inmp441_read_data(read_data_buff, DATA_LEN, &len);
if (ret == ESP_OK && len > 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中是否将移植的外设库进行了包含
发现已经包含了外设库的路径,那么再看看外设库的BUILD.gn文件是否将i2s的驱动进行了编译.
使用Ctrl+F搜索关键字i2s,果然在BUILD.gn文件中发现driver/i2s.c这行被注释掉,我们尝试去掉注释再编译。
发现还是会有报错:
我们跳转过去后,尝试取消esp_pm.h的依赖,注释掉之后又出现了一个新的报错:
在Ubuntu中grep 搜索一下这个变量,发现它是用来打印提示信息的,应该删掉这一段代码也不影响。
我们这里直接将其注释掉,发现程序能成功运行了!
我们可以通过网络助手NetAssist进行UDP协议的网络调试
这相当于监听本机上的所有1234端口的数据(这里需要与ESP32代码中udp的端口设置一致)
数据传输这边没问题,我们还可以编写一个python的上位机用来将数据流保存成wav文件实现录音功能!