想了想,还是看看这两基础哥们。
1 麦克风
1.1 总览
大概有以下这些。
| 类型 | 换能原理 | 输出信号 | 灵敏度 | SNR | 体积 | 成本 | 典型应用 |
|---|---|---|---|---|---|---|---|
| MEMS 数字麦(PDM) | 硅微振膜电容 + 片内 ΣΔ 调制 | PDM 1bit 串行 | 中等 | 60~70dB | 极小(3×4mm 级) | 低 | TWS 耳机、手机、语音阵列、远场唤醒 |
| MEMS 数字麦(I2S/TDM) | 硅振膜 + 片内 ADC + 抽取 | 多 bit PCM | 中等 | 60~70dB | 小 | 中高 | 高端多麦阵列、车载 |
| MEMS 模拟麦 | 硅振膜电容 + 片内前置放大 | 模拟电压 | 中高 | 60~65dB | 极小 | 低 | 需要外部 Codec 的低成本方案 |
| ECM 驻极体麦 | 驻极体薄膜电容 + JFET | 模拟电压 | 高 | 55~65dB | 中(6mm 圆柱) | 极低 | 玩具、低端音箱、DIY |
| 动圈麦 | 线圈切割磁感线 | 模拟电压(无源) | 低 | 70~80dB | 大 | 中高 | 舞台演出、户外 K 歌 |
| 大振膜电容麦 | 电容振膜 + 48V 幻象供电 | 模拟电压 | 极高 | 80~100dB | 大 | 高 | 录音棚、专业配音 |
| MEMS 超声麦 | MEMS 压电 / 电容超声阵列 | 数字 / 模拟 | — | — | 小 | 中高 | 手势识别、测距、探伤 |
1.2 MEMS
原理本质是微型电容结构:硅片光刻出极薄振膜 + 固定背板,声波推动振膜,电容变化;芯片内部集成前置放大、Σ-Δ 调制器,直接输出 PDM(最常见),少数型号输出 I2S / 模拟电压。
✅ 优点:尺寸极小(可以做到 1.6×1.0mm 级别)、支持 SMT 回流焊、批次一致性很好(SNR 离散小,做多麦波束成形很关键)、功耗低、抗震;数字 PDM 版本走数字信号,不怕主板射频干扰(WiFi / 蓝牙干扰是模拟麦的大坑)
✅ 接口:PDM 是最主流,两根线(CLK+DATA),一个时钟,1bit 密度调制,MCU/DSP 内部做抽取滤波转 PCM;I2S 麦直接输出 PCM,接口更重、成本更高;模拟 MEMS 输出模拟电压,后级要 PGA+ADC,和老 ECM 链路一致
✅ 典型指标:SNR 60~70dB,声学过载点 120~135dB SPL;分上进声 / 下进声(PCB 开孔);有全向为主,也有差分双振膜 MEMS(内置降噪)
✅ 用法:单麦基础拾音;2 麦 / 4 麦阵列做波束成形 BF、AEC 回声消除、语音降噪、声源定位、远场语音唤醒(小爱 / 天猫);TWS 耳机里通常 2~3 颗 MEMS,一颗主拾音、一颗环境参考做通话降噪 / AOC 堵耳消除
1.3 ECM 驻极体电容麦(老一代电容麦)
原理也是电容,薄膜预先注入永久电荷(驻极体),搭配内置 JFET 阻抗变换,输出模拟信号;没有内置 ADC,一定是模拟输出,必须外部 PGA+ADC 才能转 PCM。
✅ 优点:成本极低、电路简单;
❌ 短板:体积偏大、不能过高温回流焊、一致性差(同批次 SNR 偏差 3~5dB,做多麦阵列很难对齐相位)、模拟走线极易引入干扰,现在新手机 TWS 基本淘汰,只在低价音箱、玩具、简易 DIY 里还在用。
1.4 动圈麦克风
电磁感应:振膜带动线圈在磁铁缝隙里运动,切割磁感线产生电压;无源,不需要供电。
✅ 优势:耐大声压、结实、抗造、低频扎实;
❌ 短板:灵敏度偏低、高频上限一般、体积大,不适合小型穿戴设备,几乎不用在嵌入式语音采集。
1.5 采集例子(ECM)
用的GY-MAX4466 模块,咪头是 ECM 驻极体电容麦。输出:OUT 引脚是模拟音频信号,必须接到 MCU/ADC,采样之后才能得到 PCM,没有 PDM、没有数字输出。
- ECM + 运放(MAX4466 方案):老模拟路线。优点结构简单、便宜;缺点模拟走线极易引入电源噪声、射频干扰;想要 PCM,依赖主控 ADC。STM32F4 的内置 ADC 就可以直接接这个 OUT 脚来采集。
- PDM MEMS:把 ΣΔ 做进麦里面,直接输出数字 1bit 流,抗干扰强,适合多麦阵列。
| 模块 | 内部有没有 ΣΔ | 原始输出 | 得到 PCM 的方式 |
|---|---|---|---|
| GY-MAX4466 | ❌ 无 | 模拟电压 | 外部 ADC 采样 |
| INMP441 PDM 麦 | ✅ 片内 ΣΔ | PDM 1bit 流 | 软件抽取滤波 |
所以在使用MAX4466,需要接到ADC的引脚。我这边是用的树莓派Pico2来完成实验,使用的GP16,ADC0口。
实现思路是麦克风使用 MAX4466 模拟放大模块,模拟输出接到 Pico 2 的 GP26,也就是 ADC0。固件以 20 kHz 采样,每 512 个样本组成一帧,通过 USB CDC 串口发给电脑。每帧包含 ZAUD 帧头、样本数、帧序号和 512 个 16 位样本。电脑端可以据此同步数据并统计丢帧。
这里树莓派Pico的ADC原始能力是12bit SAR ADC,也就是12位精度。在2048的时候,表示电压在中间也就是几乎没声音,在0或者4095的时候,都是声音最大的时候。之后在处理的时候扩展到16位。
转换到PCM算法如下:
raw = 0 ~ 4095
mid = 2048
pcm16 = (raw - 2048) << 4;
整体代码如下:
Pico2和上位机的通信协议:
| 字节位置 | 长度 | 内容 |
|---|---|---|
| 0–3 | 4 字节 | 固定标识ZAUD(ASCII) |
| 4–5 | 2 字节 | 本包样本数512,小端序 |
| 6–7 | 2 字节 | 帧序号,小端序,无符号,循环计数 |
| 8 起 | 1024 字节 | 512 个 16 位小端序样本 |
采样率是 20 kHz、单声道。每包代表 512 / 20000 = 25.6 ms 音频。
核心代码main.c:
/* * Pico 2 audio capture: MAX4466 analog output on GP26 / ADC channel 0. * Streams 512-sample, 16-bit little-endian frames over USB CDC ACM. */ #include <sample_usbd.h> #include <hardware/adc.h> #include <hardware/dma.h> #include <errno.h> #include <stdint.h> #include <string.h> #include <zephyr/device.h> #include <zephyr/drivers/adc.h> #include <zephyr/drivers/uart.h> #include <zephyr/kernel.h> #include <zephyr/usb/usbd.h> #include <zephyr/sys/byteorder.h> #define SAMPLE_RATE_HZ 20000U #define ADC_CLOCK_HZ 48000000U #define ADC_DMA_CHANNEL 0U #define FRAME_SAMPLES 512U #define FRAME_HEADER_BYTES 8U #define FRAME_BYTES (FRAME_HEADER_BYTES + FRAME_SAMPLES * sizeof(uint16_t)) BUILD_ASSERT(SAMPLE_RATE_HZ == 20000U, "Update ADC clock divider if sample rate changes"); static const struct adc_dt_spec adc_spec = ADC_DT_SPEC_GET(DT_NODELABEL(audio_user)); static const struct device *const cdc_dev = DEVICE_DT_GET_ONE(zephyr_cdc_acm_uart); static struct usbd_context *usb_ctx; static struct k_sem tx_done; static uint8_t tx_frame[FRAME_BYTES]; static volatile size_t tx_offset; static void usb_message_cb(struct usbd_context *ctx, const struct usbd_msg *msg) { if (!usbd_can_detect_vbus(ctx)) { return; } if (msg->type == USBD_MSG_VBUS_READY) { (void)usbd_enable(ctx); } else if (msg->type == USBD_MSG_VBUS_REMOVED) { (void)usbd_disable(ctx); } } static int usb_device_start(void) { usb_ctx = sample_usbd_init_device(usb_message_cb); if (usb_ctx == NULL) { return -ENODEV; } if (!usbd_can_detect_vbus(usb_ctx)) { return usbd_enable(usb_ctx); } return 0; } static void cdc_tx_isr(const struct device *dev, void *user_data) { ARG_UNUSED(user_data); if (!uart_irq_update(dev)) { return; } while (uart_irq_tx_ready(dev)) { if (tx_offset >= sizeof(tx_frame)) { uart_irq_tx_disable(dev); k_sem_give(&tx_done); return; } int written = uart_fifo_fill(dev, tx_frame + tx_offset, sizeof(tx_frame) - tx_offset); if (written <= 0) { return; } tx_offset += (size_t)written; } } static int adc_channel_start(void) { struct adc_channel_cfg channel_cfg = { .gain = ADC_GAIN_1, .reference = ADC_REF_INTERNAL, .acquisition_time = ADC_ACQ_TIME_DEFAULT, .channel_id = adc_spec.channel_id, }; return adc_channel_setup(adc_spec.dev, &channel_cfg); } static void audio_stream_thread(void *arg1, void *arg2, void *arg3) { ARG_UNUSED(arg1); ARG_UNUSED(arg2); ARG_UNUSED(arg3); uint16_t samples[FRAME_SAMPLES]; uint16_t frame_number = 0; dma_channel_config_t dma_cfg = dma_channel_get_default_config(ADC_DMA_CHANNEL); channel_config_set_transfer_data_size(&dma_cfg, DMA_SIZE_16); channel_config_set_read_increment(&dma_cfg, false); channel_config_set_write_increment(&dma_cfg, true); channel_config_set_dreq(&dma_cfg, DREQ_ADC); /* Pace conversions in hardware and move each result with DMA. */ adc_run(false); /* Zephyr's ADC driver enabled the FIFO IRQ; DMA owns FIFO reads from here. */ adc_irq_set_enabled(false); adc_fifo_setup(true, true, 1, false, false); adc_set_clkdiv(((float)ADC_CLOCK_HZ / SAMPLE_RATE_HZ) - 1.0f); k_sem_init(&tx_done, 0, 1); while (1) { /* Clear a possible trailing sample before starting the next exact block. */ adc_run(false); adc_fifo_drain(); dma_channel_configure(ADC_DMA_CHANNEL, &dma_cfg, samples, &adc_hw->fifo, FRAME_SAMPLES, false); dma_start_channel_mask(BIT(ADC_DMA_CHANNEL)); adc_run(true); dma_channel_wait_for_finish_blocking(ADC_DMA_CHANNEL); adc_run(false); uint32_t dtr = 0; if (uart_line_ctrl_get(cdc_dev, UART_LINE_CTRL_DTR, &dtr) != 0 || !dtr) { continue; } memcpy(tx_frame, "ZAUD", 4); sys_put_le16(FRAME_SAMPLES, tx_frame + 4); sys_put_le16(frame_number++, tx_frame + 6); for (size_t i = 0; i < FRAME_SAMPLES; ++i) { sys_put_le16(samples[i], tx_frame + FRAME_HEADER_BYTES + i * 2U); } tx_offset = 0; uart_irq_tx_enable(cdc_dev); k_sem_take(&tx_done, K_FOREVER); } } K_THREAD_STACK_DEFINE(audio_thread_stack, 3072); static struct k_thread audio_thread; int main(void) { if (!device_is_ready(cdc_dev)) { return 0; } if (usb_device_start() != 0) { return 0; } if (uart_irq_callback_user_data_set(cdc_dev, cdc_tx_isr, NULL) != 0) { return 0; } if (!device_is_ready(adc_spec.dev) || adc_channel_start() != 0) { return 0; } k_thread_create(&audio_thread, audio_thread_stack, K_THREAD_STACK_SIZEOF(audio_thread_stack), audio_stream_thread, NULL, NULL, NULL, 5, 0, K_NO_WAIT); return 0; }用到的API来自:
- Zephyr 提供:
uart_line_ctrl_get()查询 CDC 串口的 DTR 状态;k_sem_init()/k_sem_take()控制发送同步;ARG_UNUSED()是 Zephyr 宏。- Pico SDK 提供:
adc_run()、adc_irq_set_enabled()、adc_fifo_setup()、adc_fifo_drain()、adc_set_clkdiv()操作 RP2350 的 ADC;dma_channel_*()、dma_start_channel_mask()配置和控制 DMA。- 硬件寄存器:
adc_hw->fifo是 ADC FIFO 寄存器地址。
上位机:
电脑端上位机使用 Python 和 Tkinter 编写,可以选择串口、显示实时波形和电平,并将采集数据保存为 WAV。当前录音格式为单声道、20 kHz、16 位 PCM。
确实可以转换成PCM,当时也确实噪音很大。调了几次gain,也没有太明显的效果。后面再试试软件优化吧。
2 喇叭
2.1 总览
| 类型 | 换能原理 | 驱动信号 | 特点 | 频响 | 体积 | 成本 | 典型应用 |
|---|---|---|---|---|---|---|---|
| 动圈扬声器(电动式,最通用) | 音圈在磁场里,电流驱动音圈带动振膜振动发声 | 模拟交流音频功率信号 | 技术成熟、推力足、失真可控;需要功放驱动 | 全频友好,低频好 | 跨度很大(微型~大低音单元) | 低~中 | 蓝牙音箱、TWS 耳机单元、电视、汽车喇叭、开发板蜂鸣喇叭 |
| 压电扬声器(压电陶瓷) | 压电材料加电压后形变,带动振膜振动 | 模拟交流,可高压驱动 | 结构简单、无磁、不怕电磁干扰;低频差、偏尖 | 高频为主,低频弱 | 很小 | 极低 | 蜂鸣器、便携提示音、穿戴设备提示、简易报警器,很少用来放语音音乐 |
| 静电扬声器(ESL) | 高压电场驱动超薄导电振膜,极板静电吸力推拉振膜 | 高压音频信号(几十~上千 V 偏置) | 解析极高、瞬态好;需要高压驱动、怕潮湿、低频弱 | 中高频极其顺滑,低频难做大 | 薄、大面积 | 很高 | HiFi 高端音箱,消费电子几乎不用 |
| 平板扬声器(平面振膜 / 气动式 AMT) | AMT:褶皱铝膜在磁场里;平面振膜:整块薄膜驱动 | 模拟功率音频 | 扩散好、高频细腻;低频效率偏低 | 中高频优秀 | 偏薄、面积大 | 中高 | 高端头戴耳机、高端 HiFi 音箱、车载顶棚扬声器 |
| MEMS 扬声器(硅喇叭) | 硅工艺微型振膜,静电 / 压电驱动,晶圆级封装 | 数字 / 模拟,很多自带驱动 | 极小、适合 SMT 贴片、一致性好;声压上限有限,低频弱 | 中高频为主 | 芯片级,极小 | 中(正在降价) | 智能眼镜、TWS 副单元、助听器、AR 设备 |
| 骨传导扬声器(骨传导振子) | 振动块机械振动,通过颅骨传声,不靠空气声波 | 模拟功率信号 | 开放听感、不漏音相对更好;有震感、低频弱 | 中低频偏弱 | 中等 | 中高 | 骨传导耳机、安全帽通讯设备 |
| 气传导扬声器(开放式喇叭单元) | 本质还是动圈,结构专门优化远场开放辐射 | 模拟功率音频 | 开放声场,不堵耳;容易漏音 | 全频,低频一般 | 中等 | 中 | 开放式耳机、挂耳耳机 |
因为喇叭需要前后震动发声,所以给的电流是交流电。
不过DAC/CODEC 直接输出的信号电压小、电流能力极弱,推不动喇叭,输出电流只有mA 甚至 μA 级,带载能力非常弱。一般来说喇叭想要正常响、达到额定响度,需要几百 mA 级的电流,DAC 引脚完全给不出这么大电流。所以需要一个功放,来放大电流。
功放:
| 功放类型 | 简介 | 适配喇叭 | 特点 |
|---|---|---|---|
| AB 类功放 | 线性模拟放大 | 动圈为主 | 音质好、噪声低、发热大、效率低,老方案 |
| D 类功放(数字功放 / PWM 功放) | PWM 调制,开关放大 | 动圈、压电、MEMS | 效率高、发热小,现在消费音频主流(WM8960 内部就是 D 类) |
| H 类 / G 类 | 自适应电源轨 D 类改进 | 动圈 | 兼顾效率和大动态,高端音箱 |
不过有些有线耳机,耳机32Ω、16Ω,功率只需要毫瓦级,CODEC内置的耳机驱动缓冲,电流刚好够用,此时就不需要额外外接功放芯片。
2.2 Speaker
这个是目前某宝上随处可见的一个喇叭,功率8W,阻抗3Ω。选择功放如下:
| 组合 | 后果 |
|---|---|
| 功放支持≥8Ω,功放 @8Ω 功率 ≥喇叭功率 | ✅ 理想,余量充足 |
| 功放支持≥8Ω,功放 @8Ω 功率 <喇叭功率 | ⚠️ 能用,音量上限不足,大声容易失真 |
| 喇叭阻抗<功放允许最小阻抗(比如功放最小 4Ω,接 2Ω 喇叭) | ❌ 危险,过流、烧功放 |
喇叭输入的就是经过放大的交流电。因为是交流电,方向一直变化,所以接反了影响也不会太大。
2.3 播放例子
这里给一个MAX98357进行播放的例子。
MAX98357 是一款集成 DAC 与 D 类功率放大的音频芯片,直接接收 I2S 格式的 PCM 数字音频信号,内部完成数模转换后通过 BTL 桥接方式输出差分交流功率信号,可直接驱动扬声器,无需额外模拟功放;它无需 MCLK 主时钟,外围电路简洁,支持增益配置与单声道混合,5V 供电下 8Ω 负载最大输出约 1.8W,适合 STM32、ESP32 等嵌入式设备做语音播报、音频播放,缺点是功率有限,推 3W 8Ω 喇叭无法达到满功率,大音量下容易出现削波失真。
详细的内容之前写过,这里就不再做实验了,直接看就行了。总线学习5--I2S_max98357数据手册-CSDN博客