中文说明:README-CN.md
Four synchronized IM72D128V01 PDM microphones on the custom RP2354A board.
Zephyr, its modules, and the SDK all live in zephyrproject/ inside this
repository. The schematic and open-source hardware project are hosted on
EasyEDA Pro. The
build target is rpi_pico2/rp2350a/m33; app.overlay sets this chip's 2 MiB
flash, pin wiring, and USB functions. See RP2354A board.
One full-speed composite device, VID:PID 2fe3:0007 (legacy microphone-only firmware: 2fe3:0006), product
Pico 2 Mic Array UAC2, manufacturer Pico Array Prototype, configuration
Audio, shell and telemetry:
| Function | USB interfaces | Windows label |
|---|---|---|
| Diagnostics shell | CDC 0 / data 1 | Mic Array Shell |
| MIC/2 telemetry and controls | CDC 2 / data 3 | Mic Array Telemetry |
| Four-channel 48 kHz PCM16 capture | UAC2 microphone | Pico 2 Mic Array UAC2 |
| Stereo 48 kHz PCM16 playback | UAC2 speaker | Pico 2 Mic Array UAC2 |
COM numbers come from the PC. mic_app picks the telemetry port from the
VID, PID, and interface, then checks the MIC/2 handshake. UAC2 is independent
of both serial ports. Record the four raw channels with WASAPI exclusive; the
Windows shared mixer presents stereo. See
CDC telemetry.
With no saved audio record, the USB stream is the four microphones.
Beamforming, noise reduction, and direction estimation are off. A saved record
restores noise mode, hum filter, input channel, beam mode, azimuth, assumed
elevation, and the learned 129-bin noise spectrum before capture starts.
Direction estimation is separate and stays off until enabled. Saving is
mic_app Save & restart, or the MIC/2 command SETTINGS_SAVE, and it runs
only while UAC is idle. The last 8 KiB of flash holds two alternating 4 KiB
records with a generation count and CRC32. A normal UF2 update leaves that
partition in place; erasing the whole flash clears it. See
persistent audio settings.
The linked image in build/zephyr/zephyr_final.map uses 147,228 bytes of the
2,088,960-byte firmware partition and 513,760 of 532,480 bytes of RAM, leaving
18,720 bytes. CONFIG_DMA=n because this application owns the DMA controller.
CONFIG_ASSERT=n and CONFIG_SPIN_VALIDATE=n keep debug kernel checks out of
the realtime build. Capture sequence checks, USB health counters, configuration
bounds, and the settings CRC stay on.
Select Pico 2 Mic Array UAC2 as the Windows output device to play through
the MAX98357A and connected speaker while recording from the array. USB accepts
stereo; the amplifier module selects left, right or a mono mix for the single
speaker. Acoustic echo cancellation is not implemented. See
UAC2 microphone and speaker for playback, diagnostics
and validation results.
One PIO0 state machine clocks GP6 and samples GP7–GP10 together. Every microphone SELECT pin is grounded, so the data pins are driven while CLOCK is low. The program samples on the fifth of ten PIO cycles, late in that low half, with GPIO input synchronizers enabled. Each FIFO word holds eight chronological 4-bit samples, earliest in bits 0..3, in order U1, U2, U3, U4.
At the nominal 150 MHz system clock the PIO divider is 1250/256: 3.072 MHz PDM, then 48,000 PCM frames/s after decimation by 64. The rate follows the 12 MHz crystal. The fractional divider adds cycle-to-cycle jitter.
Two hardware-chained DMA channels fill 24 buffers. Each destination is a
1024-word, 4 KiB area, aligned to 4 KiB, and the write address wraps inside
it. A late rearm cannot run past that buffer before the fault path stops the
stream. One block is 128 PCM frames, 2.667 ms. The pool holds 64 ms. A missing
block, a missed rearm, a DMA bus error, or a stalled PIO RX FIFO stops
capture, increments faults, and invalidates an in-progress RAM recording.
The shell remains available. Reboot to capture again.
Conversion:
- CIC4, decimation 16, as the exact 61-tap convolution through byte lookup tables. Core 1 runs this on the incoming block.
- 31-tap halfband FIR, decimation 2, to 96 kHz. Core 0.
- 63-tap halfband FIR, decimation 2, to 48 kHz. Core 0.
- Three-tap CIC compensation at 48 kHz. Core 0.
Core 1 is a bare RP2350 worker started through the ROM FIFO handshake. It does not run Zephyr, take interrupts, or touch DMA or USB. Queued beamforming and GCC-PHAT windows also run there. Core 0 owns scheduling, the FIR stages, noise reduction, USB, and DMA block lifetime. The two cores overlap by one block, which adds 2.667 ms. The raw block stays owned until both cores finish. Hot filter code is in SRAM.
Capture discards the first 48 blocks, 128 ms, while the microphones and the CIC settle. Each later block is used as follows.
- Direction estimation reads the raw four-channel PCM, before the beamformer. It is off at boot. An analysis frame is four 2048-point windows, 8192 frames (170.7 ms). Manual beamforming skips the next frame. Rejected frames carry no angle, and the estimate does not move the beam. See onboard direction.
- The beamformer runs on core 1. Off passes the four channels through. Broadside sums the face-normal direction. Manual applies a 21-tap fractional delay on the 25 mm cross below, with a common delay of 14 samples and a 20 ms crossfade. Changing the beam clears the learned noise profile. See manual beamforming.
- Noise reduction, when enabled, runs on core 0 after the beamformer and copies its mono result into all four UAC slots. Off leaves the four channels distinct. Filter mode is an 80 Hz high-pass and an optional 50 or 60 Hz notch on one selected channel. Suppress mode is a 20 Hz high-pass, the same notch, and learned stationary-noise suppression on the 129 bins of a 256-point FFT. Suppress adds one 128-sample hop, another 2.667 ms. Learning takes two seconds; keep the sound you want to preserve out of that interval. See noise reduction.
UAC2 uses a 2048-frame ring and holds a 32 ms reserve (1536 frames) before streaming. Full-speed packets are 47, 48, or 49 frames so the device follows the host clock. UAC carries the beamformed and noise-reduced stream. CDC snapshots are the PCM from before the beamformer.
nr config off|filter|suppress [hum=0|50|60] [channel=1..4]
nr learn
nr stats
bf config off|broadside|manual <azimuth-deg> [elevation-deg=0]
bf stats
doa config on|off
doa stats
Close UAC before nr config or uacpattern. Beam changes are accepted during
a UAC recording. bf stats prints mode, tenth-degree azimuth and elevation,
revision, pending and fade counts, and each channel's delay in milliseconds.
Sound ports sit on a 25 mm cross. In the exposed-port view, USB-C upper right:
| Channel | Microphone | GPIO | X, Y (mm) | Position |
|---|---|---|---|---|
| 1 | U1 | GP7 | (0, 25) | top |
| 2 | U2 | GP8 | (−25, 0) | left |
| 3 | U3 | GP9 | (0, −25) | bottom |
| 4 | U4 | GP10 | (25, 0) | right |
Azimuth is 0° right, 90° top, 180° left, and 270° bottom. Elevation 0 lies in
the board plane; 90 points straight out of the port face and is the broadside
direction. Elevation is an assumed steering angle. The same frame is used by
the beamformer, the direction estimator, and mic_app. Coordinates and the
diagram are in array geometry,
array-geometry.svg, and
array-geometry.json.
python tools/extract_geometry.pyGPIO numbers are not QFN package pins. Source: src/board_pins.h and the EasyEDA Pro hardware project.
| Net | GPIO | U5 QFN-60 pin | Goes to |
|---|---|---|---|
| SCLK | GP6 | 9 | U1–U4 CLOCK |
| DATA1 | GP7 | 10 | U1, PCM channel 1 |
| DATA2 | GP8 | 12 | U2, PCM channel 2 |
| DATA3 | GP9 | 13 | U3, PCM channel 3 |
| DATA4 | GP10 | 14 | U4, PCM channel 4 |
| DIN | GP11 | 15 | U7 pin 3, external amplifier |
| BCLK | GP12 | 16 | U7 pin 4 |
| LRC | GP13 | 17 | U7 pin 5 |
U7 pins 1 and 2 are VBUS and GND for that amplifier. U6 is the ME6217C33M5G
3.3 V regulator. USB D−/D+ connect to U5 package pins 51 and 52. The crystal
is 12 MHz. GP0–GP5 are free; the overlay disables uart0, i2c0, i2c1,
and adc.
Flash is the in-package 2 MiB. Firmware occupies 0x000000–0x1FDFFF. The
audio-settings partition is 0x1FE000–0x1FFFFF.
Zephyr, its modules, and the SDK stay in zephyrproject/ inside this
repository. setup.ps1 and setup.sh do the same work: check out Zephyr
27150c9dd348690a6b41d3992dd5d3d1458fdc66, update the modules named by that
manifest, apply patches/zephyr-udc-rpi-pico.patch, and install Zephyr SDK
1.0.1 with the Arm toolchain. The patch acknowledges USB start-of-frame and
keeps a queued control transfer from being reported as an endpoint error.
zephyrproject/ is gitignored. The image is build/zephyr/zephyr.uf2, and
the CMake cache stays in build/.cache.
Install Git, Python 3, CMake, Ninja, and 7-Zip. Then install west:
python -m pip install westsetup.ps1 downloads zephyr-sdk-1.0.1_windows-x86_64_minimal.7z and runs
setup.cmd /t arm-zephyr-eabi /h /c.
.\setup.ps1
.\build.ps1
.\build.ps1 -PristineHold BOOTSEL while connecting the board, then copy the UF2 to the mass-storage
volume that appears. Copy it only to that volume. Once this firmware is
running, boot in the shell restarts into BOOTSEL with mass storage and
picoboot both enabled. The serial ports disconnect until the new image
enumerates.
python tools/flash.py <shell-port>The script checks the UF2 header, sends boot, and copies onto the single
drive whose INFO_UF2.TXT contains Board-ID: RP2350. More than one such
drive stops the script. It then waits for the same USB serial number on the
shell interface, Windows MI_00. The COM number may change.
Install the host packages, then west. ~/.local/bin has to be on PATH.
sudo apt install --no-install-recommends git cmake ninja-build gperf ccache dfu-util device-tree-compiler wget curl python3-dev python3-pip python3-setuptools python3-wheel xz-utils file make gcc g++
python3 -m pip install --user west
bash setup.sh
bash build.sh
bash build.sh --pristinesetup.sh reads uname -m and downloads the matching minimal archive,
zephyr-sdk-1.0.1_linux-x86_64_minimal.tar.xz or
zephyr-sdk-1.0.1_linux-aarch64_minimal.tar.xz. It then runs
zephyrproject/zephyr-sdk-1.0.1/setup.sh -t arm-zephyr-eabi -h -c.
Hold BOOTSEL while connecting the board and copy build/zephyr/zephyr.uf2
onto the RP2350 mass-storage volume. tools/flash.py is the Windows helper.
The shell is a CDC ACM device; host tools that take a COM port take the
corresponding /dev/ttyACM* node instead.
CDC ignores the baud rate; 115200 is a fine terminal setting. Capture starts
without a terminal. After warmup, mic stats shows ready=1, an increasing
blocks count, and dropped=0 faults=0. max_process_us stays within the
2.667 ms block. Peaks are the latest block's per-channel absolute PCM16
values, not SPL. Close the terminal before a host script opens the port.
mic stats
mic record 1000
mic status
mic dump
mic raw
uacstats
uacpattern on
doa capture
doa dump
mic record accepts 1..333 ms, up to 16,000 frames in the shared diagnostic
buffer. The host tool records, then reads the hex dump, and checks the header
and length. The text transfer has no extra checksum. A second record is
rejected while one is active. A dropped block marks that take INVALID.
python -m pip install pyserial
python tools/record.py <shell-port> capture.wav --ms 250
python tools/raw_pdm.py <shell-port> --output build-tests/raw-pdmtools/raw_pdm.py counts GP6 edges, exports 10.67 ms of all four PDM lanes
plus the firmware PCM, and rebuilds PCM with a separate bitwise CIC and
integer FIR. uacpattern replaces the microphones with a diagnostic ramp;
turn it off before treating a recording as microphone audio. doa capture
holds one raw frame for doa dump.
Continuous four-channel recording uses UAC2:
python tools/uac_wasapi.py --list
python tools/uac_wasapi.py build-tests/continuous-48k.wav --seconds 60The WAV is exclusive-mode PCM16 at 48 kHz. A JSON sidecar records Windows
packet flags and position gaps. An unfinished recording is left as
.partial.wav.
Host checks, without the board:
New-Item -ItemType Directory -Force build-tests | Out-Null
gcc -std=c11 -O2 -Wall -Wextra -Werror -Isrc tests/filter_test.c src/pdm_filter.c -lm -o build-tests/filter_test.exe
.\build-tests\filter_test.exe
python -m unittest discover -s tests -p "test_*.py" -vThe C test compares the lookup CIC with a scalar reference, including overflow and block edges, then checks channel isolation, polarity, DC gain, and a synthetic sigma-delta sine. The Python tests cover the host tools. They do not measure pin timing or on-device throughput.
mic_app is a Tauri, Vue, and TypeScript application.
Build it with npm run desktop:build inside mic_app; the resulting
mic_app/release/mic_app.exe is not part of the source tree. It opens Mic Array Telemetry only. From
there you can enable direction estimation, choose Off, Broadside, or Manual,
set azimuth and assumed elevation, apply the beam, and relearn noise.
Save & restart writes the audio record and reconnects. Simulation draws the
same interface and does not open a port. Audacity or tools/uac_wasapi.py
can record UAC while the app is connected. Leave the shell port closed while
the app is using the board.
Microphone: Infineon IM72D128. Chip: Raspberry Pi RP2350.
The firmware, host tools, desktop application, hardware drawings, and documentation in this repository are under the MIT license. See LICENSE.
Zephyr and the Zephyr SDK are Apache-2.0. The setup scripts download them
into zephyrproject/, which is not part of this repository.
patches/zephyr-udc-rpi-pico.patch modifies Zephyr's RP2350 USB device
driver; that file remains under the Apache-2.0 terms. The Pico SDK used by
sdk-benchmark is the workspace's hal_rpi_pico module (BSD-3-Clause).
Packages installed for mic_app keep their own licenses.