Chapter 78: MEMS microphones¶
What: digital MEMS microphones, the chips that replaced analog electret mics in everything from phones to wearables. We focus on I²S mics (TDK InvenSense INMP441, TDK ICS-43434) and contrast with PDM mics. Plus the ASoC machine-driver pattern needed to wire one to the i.MX6ULL SAI, since this is one case where you really do write a small “machine driver” but not a chip driver. ASoC: ALSA System-on-Chip, the embedded audio layer that connects CPU audio ports, codecs, and board wiring.
Why: every smart speaker, voice-assistant, voice-controlled IoT device, dashcam, drone for FAA-broadcast, they all have one or more digital microphones. The mic outputs already-digitized PCM (or PDM). No separate ADC or codec required. The driver structure is unusual: there’s no codec chip with registers, just a simple I²S DAI. The ASoC
simple-cardmachine driver handles this exact pattern.Focus: A digital MEMS mic is, from Linux’s view, an I²S DAI without any control interface, clocks in, samples out. It samples audio internally, outputs PCM on SD when WS/LR-clock + BCLK are running. To the SAI driver, the mic is just an I²S slave. Wire SAI to the mic in DT via
simple-audio-card, thenarecordcaptures the audio. That is the whole audio-input pipeline.Tooling. This chapter uses
alsa-utils(arecord,aplay),i2c-tools. ALSA: Linux’s kernel and user-space audio stack.
Ubuntu-base (target):
apt install alsa-utils i2c-toolsBuildroot:
BR2_PACKAGE_ALSA_UTILS=y BR2_PACKAGE_I2C_TOOLS=yBuildroot: a configuration-driven build system that produces a complete root filesystem and related images.Full per-tool reference: Userspace tooling appendix. MCU bridge: Think of the rootfs as the firmware image’s file-backed runtime environment. On an MCU you link everything into flash. On Linux, programs and config live in this mounted tree. rootfs: root filesystem, the directory tree mounted at / that contains /bin, /etc, /dev, and libraries.
78.1 Sensor comparison¶
TDK InvenSense INMP441 |
TDK ICS-43434 |
TDK ICS-41350 (PDM) |
|
|---|---|---|---|
Interface |
I²S |
I²S |
PDM (1-bit pulse-density) |
Sample rate |
7.19, 52.7 kHz |
7.19, 51.6 kHz |
1.024, 3.072 MHz PDM (decimates to ~48 kHz) |
Resolution |
24-bit |
24-bit |
1-bit out, 16-bit after decimation |
SNR |
61 dBA |
65 dBA |
64 dBA |
Sensitivity |
-26 dBFS @ 94 dB SPL |
-26 dBFS |
-26 dBFS |
Acoustic overload (AOP) |
120 dB SPL |
116 dB SPL |
130 dB SPL |
Idle current |
0.5 mA |
0.6 mA |
0.6 mA |
Stereo (L/R select) |
LR pin selects L or R channel |
LR pin |
LR pin |
Package |
6-pin LGA |
6-pin LGA |
5-pin LGA |
Volume price |
$1.50–2.50 |
$2–3.50 |
$1.50–2.50 |
Pick guide:
INMP441: cheap, common, fine for voice. Default choice.
ICS-43434: lower noise. Better for music or low-volume signal.
PDM: more compact wiring (1-bit data). But needs the SoC’s PDM-decoder hardware. I.MX6ULL’s SAI has only I²S, no native PDM. So PDM mics are awkward on i.MX6ULL, skip.
78.2 I²S protocol primer¶
I²S is a 3-wire serial audio standard:
BCLK (bit clock): one transition per bit.
LRCLK (word/frame clock): toggles once per sample, marking left vs right channel.
SD (serial data): the audio bits.
For a 48 kHz / 24-bit stereo stream:
LRCLK = 48 kHz square wave (low = L word, high = R word).
BCLK = 48 kHz × 64 = 3.072 MHz (64 bits per stereo sample, conventional even for 24-bit data).
SD = MSB-first, 24 bits per word, padded with zeros to fill the 32-bit slot.
The “master” generates BCLK + LRCLK. The “slave” follows. For a microphone, the SoC is master, mic is slave. The mic drives SD whenever LRCLK matches its programmed channel.
LRCLK: ──────────┐ ┌─────────────┐
│ L word │ R word ...
└─────────┘
BCLK: ┐_┌─┐_┌─┐_┌─┐_┌─┐_┌─┐ ... (free-running)
SD: ────── 24 bits of L sample ──────── 24 bits of R sample ────
The mic outputs its sample MSB-first during its assigned LRCLK phase. Single mic → mono. Two mics with LR-select pin strapped opposite → stereo.
78.3 How the data flows in Linux¶
Microphone (INMP441) ──[I²S]──► SAI (CPU DAI on i.MX6ULL)
↓ DMA
DDR ring buffer
↓
ALSA core
↓
/dev/snd/pcmC0D0c ← user-space arecord reads
There’s no I²C control, the mic has no registers. The wires alone (BCLK, LRCLK, SD, LR-select strap) determine its behavior.
What the kernel needs¶
The kernel needs an ASoC sound card consisting of:
CPU DAI: the i.MX SAI driver (mainline, no work).
Codec DAI: a stub representing the mic. The mainline driver for this is called
dmic(digital mic) or, for I²S mics specifically, there’s no chip driver because the mic has no registers. The ASoC machine driver uses a fake codec.Machine driver: wires the two DAIs together. Use
simple-audio-cardfor this.
simple-audio-card (in sound/soc/generic/simple-card.c) is a generic ASoC machine driver. You describe the audio topology in DT. The driver builds a working sound card from the description. No coding required.
78.4 Device tree for INMP441 → SAI2¶
Template warning: This block contains placeholder values. Replace compatible strings, GPIO numbers, addresses, and paths with values from your board before using it.
&sai2 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_sai2>;
assigned-clocks = <&clks IMX6UL_CLK_SAI2_SEL>,
<&clks IMX6UL_CLK_SAI2>;
assigned-clock-parents = <&clks IMX6UL_CLK_PLL4_AUDIO_DIV>;
assigned-clock-rates = <0>, <24576000>;
status = "okay";
};
/* The "codec" is a fake — INMP441 has no control interface */
dmic_codec: dmic-codec {
compatible = "dmic-codec";
#sound-dai-cells = <0>;
};
sound {
compatible = "simple-audio-card";
simple-audio-card,name = "imx-inmp441";
simple-audio-card,format = "i2s";
simple-audio-card,bitclock-master = <&cpu_dai>;
simple-audio-card,frame-master = <&cpu_dai>;
cpu_dai: simple-audio-card,cpu {
sound-dai = <&sai2>;
};
simple-audio-card,codec {
sound-dai = <&dmic_codec>;
};
};
Three nodes:
&sai2: enable the i.MX SAI2 peripheral. The mainlinefsl_sai.chandles it.dmic_codec: a fake codec node. The mainlinesound/soc/codecs/dmic.cdriver (compatible = "dmic-codec") is a generic “digital microphone” placeholder, no registers, no control, just claims to be an ASoC codec DAI.sound: the machine.simple-audio-cardreads this and builds the sound card by wiring sai2’s DAI to dmic_codec’s DAI.bitclock-master = <&cpu_dai>tells the framework that the SoC generates BCLK.
After boot:
[root@pa-mini:~]# arecord -l
**** List of CAPTURE Hardware Devices ****
card 0: imxinmp441 [imx-inmp441], device 0: 308b000.sai-dmic-hifi dmic-hifi-0 []
The mic appears as a capture device. Now record:
[root@pa-mini:~]# arecord -D plughw:0,0 -f S32_LE -r 48000 -c 1 -d 5 test.wav
Recording WAVE 'test.wav' : Signed 32 bit Little Endian, Rate 48000 Hz, Mono
The S32_LE format is required because INMP441 outputs 24 bits in a 32-bit slot. ALSA can downconvert to 16-bit via plughw (the plug prefix). Writing directly to hw:0,0 requires S32_LE.
Listen back on the host machine: it’ll be quiet (single mic, low input) but speech should be audible.
78.5 How the SAI + DMA + ALSA pipeline works¶
Worth understanding even when you do not write the drivers yourself:
SAI is the i.MX’s I²S peripheral. The mainline
fsl_sai.cdriver:Configures BCLK and LRCLK from a parent clock (the PLL4_AUDIO_DIV in our DT, at 24.576 MHz, exactly 512 × 48 kHz, a “perfect” rate for audio).
Programs its TX/RX FIFO settings.
Sets up an SDMA channel to copy RX FIFO → DDR (Ch 51).
DDR: external DRAM that must be configured and trained before most software can run from it.
Implements the ASoC
snd_soc_dai_ops:set_fmt(I²S vs left-justified),hw_params(sample rate, channels),trigger(start/stop).
ASoC core binds the SAI DAI (CPU side) to the
dmic-codecDAI (codec side). Athw_paramstime, both DAIs negotiate format and sample rate.PCM substream: ALSA creates a substream backed by a DMA-coherent ring buffer in DDR. The SAI’s SDMA channel writes into it cyclically (Ch 51.5).
MCU bridge: Think of DMA like the MCU DMA controller you used for UART or SPI, but with cache coherency, scatter-gather descriptors, and kernel ownership rules added. DMA: Direct Memory Access. Hardware moves data to or from memory without the CPU copying each byte.
User-space: opens
/dev/snd/pcmC0D0c, configures format/rate via ioctl, reads samples. Reads block until enough data is available. ALSA copies from the ring buffer.
In the data path: mic → BCLK timing → SD bits → SAI’s RX FIFO → SDMA → DDR ring buffer → memcpy → user-space buffer. Zero CPU between mic and SDMA. One memcpy per read().
78.6 A “machine driver” you might write¶
simple-audio-card handles most use cases. But for unusual topologies (multiple mics with different formats. On-the-fly clock-rate changes. DAPM widgets representing mute relays), you may write a custom machine driver. The shape:
/* sound/soc/fsl/my-mic-machine.c — sketch */
static struct snd_soc_dai_link my_dai = {
.name = "mic-link",
.stream_name = "mic-capture",
.cpus = SND_SOC_DAILINK_REGn(cpu, "30030000.sai"),
.codecs = SND_SOC_DAILINK_REGn(codec, "dmic-codec"),
.platforms = SND_SOC_DAILINK_REGn(platform, "30030000.sai"),
.dai_fmt = SND_SOC_DAIFMT_I2S
| SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBC_CFC,
.ops = &my_dai_ops,
};
static struct snd_soc_card my_card = {
.name = "imx-inmp441-custom",
.owner = THIS_MODULE,
.dai_link = &my_dai,
.num_links = 1,
};
static int my_probe(struct platform_device *pdev)
{
my_card.dev = &pdev->dev;
return devm_snd_soc_register_card(&pdev->dev, &my_card);
}
Roughly 70 lines. Same shape as Ch 53’s WM8960 machine driver, only without the codec controls, DAPM widgets, and jack-detection.
For most cases: Most projects do not need a custom machine driver. Use simple-audio-card in DT.
78.7 Stereo with two mics¶
Two INMP441s on the same bus, one strapped LR=GND (Left), one strapped LR=VDD (Right):
┌── SD ─────────────────► both mics share SD
├── BCLK ───────────────► both
├── LRCLK ──────────────► both
┌── INMP441 #1, LR → GND (drives SD during left phase)
└── INMP441 #2, LR → VDD (drives SD during right phase)
Both mics monitor LRCLK. Each drives SD only during its assigned phase. The SoC sees stereo without any extra wires.
DT: change simple-audio-card,routing to declare two channels. The kernel doesn’t need to know how many mics, that’s a wiring choice.
[root@pa-mini:~]# arecord -D plughw:0,0 -f S32_LE -r 48000 -c 2 -d 5 stereo.wav
The captured file plays back in stereo on the host.
78.8 Lab¶
Wire INMP441 to SAI2 on the i.MX6ULL: BCLK, LRCLK (=WS), SD, VDD, GND, LR-strap.
Add DT. Both
&sai2andsimple-audio-cardas in §78.4.Verify enumeration.
arecord -lshould show the mic.cat /proc/asound/cards.Record.
arecord -D plughw:0,0 -f S32_LE -r 48000 -c 1 -d 5 voice.wav. Speak. Copy file to host. Play back. Speech should be clear though quiet.Check volume. Run
arecord ... | aplay(loopback on the same i.MX, with speakers via Ch 53). Hear yourself.Stereo. Add a second INMP441, strap LR opposite. Record 2-channel. Check L and R are different (cover one mic while recording. That channel goes quiet).
Sample-rate variation. Try 16 kHz, 32 kHz, 48 kHz. Verify the chip + SAI cooperate.
FFT in user-space. Pipe
arecordinto a small program that does FFT over 8192-sample windows. Plot spectrum live with gnuplot. Watch frequencies appear as you whistle.
78.9 Pitfalls¶
Wrong format. INMP441 outputs 24-bit MSB-first I²S. Configure for S32_LE in ALSA. The 24 audio bits sit in the high 24 bits of the 32-bit slot. The bottom 8 bits are zero. S24_LE may work depending on ASoC version.
MCLK not provided. Some I²S mics need an MCLK in addition to BCLK + LRCLK. INMP441 does not. ICS-43434 also doesn’t. But other I²S codecs do. Verify against datasheet.
Master/slave mismatch. Both SoC and mic configured as slaves → no clock generated. INMP441 is always a slave (chip can’t generate clocks).
WS polarity wrong. Some chips expect LRCLK = HIGH for left. Others use LOW.
simple-audio-card,format = "i2s"defaults to “left = LOW” (LJ vs I²S subtly differ).LR-strap floating. INMP441 reads middle state. Outputs nothing or noisy data. Strap explicitly.
Single mic, stereo requested. ALSA gives you a stereo stream with the second channel duplicated (or zero, depending on plug setup). For real stereo, wire two physical mics.
DC-offset / wind noise. MEMS mics pick up low-frequency rumble. Apply a high-pass filter in user-space (
soxhas ahighpass 100effect).Self-noise floor not what’s specified. A 61 dBA SNR INMP441 in a quiet room measures ~33 dB SPL noise floor, anything quieter is masked. To capture whispers, choose ICS-43434 (4 dB lower noise).
MCU bridge: Think of SPL like the tiny early startup code that runs from internal SRAM before DDR is usable. SPL: Secondary Program Loader, a tiny first U-Boot stage that fits in OCRAM and initializes DDR.
dmic-codec compatible string. Must be exactly
"dmic-codec". If the kernel was built withoutCONFIG_SND_SOC_DMIC=y, the placeholder driver is missing and the card fails to probe.
78.10 Going deeper¶
sound/soc/generic/simple-card.c: read the parsing logic to understand whatsimple-audio-cardaccepts.sound/soc/codecs/dmic.c: the dmic placeholder driver (~150 lines).sound/soc/fsl/fsl_sai.c: the i.MX SAI driver.Documentation/devicetree/bindings/sound/simple-card.yaml: DT binding reference.INMP441 datasheet (TDK InvenSense): timing diagram, LR-strap behavior.
ICS-43434 datasheet: similar, with the SNR improvement.
ALSA documentation at https://www.alsa-project.org/ for advanced topics (ringbuffer tuning, sample-rate conversion).
Next chapter: Chapter 79: Health sensors (MAX30100 / MAX30102). PPG-based heart-rate and SpO₂ measurement. The I²C interface with FIFO + the user-space DSP to extract HR/SpO₂.