Skip to content

Commit 62ec6f2

Browse files
Add 02-stage-audiodriver-2michat-v2 pi-gen stage for ReSpeaker V2.0 HAT (#41)
* feat(audiodriver): add 02-stage-audiodriver-2michat-v2 for V2.0 HAT The ReSpeaker 2-Mics Pi HAT V2.0 replaces the WM8960 codec (V1, I2C 0x1a) with a TLV320AIC3104 codec (I2C 0x18). The mainline kernel already ships the `snd-soc-tlv320aic3x` driver, so no DKMS module is needed — but a device-tree overlay and a separate set of mixer defaults are required. Without these two bits the card either never appears or appears silent. The new stage mirrors the v1 layout: - prerun.sh — copy_previous guard, same as v1 - 01-driver/ - 01-packages — build deps only (no dkms, no kernel headers needed) - 03-run-chroot.sh — clone seeed-linux-dtoverlays, compile respeaker-2mic-v2_0-overlay.dtbo with dtc, install into /boot/overlays, enable I2C + dtoverlay=respeaker-2mic-v2_0 in /boot/config.txt - 02-set-audio-volume/ - 01-run.sh — install script + oneshot unit (same pattern as v1) - files/configure_audio.sh — TLV320-aware defaults: PCM 85% (digital pre-DAC, 100% clips on typical JST speakers), HP DAC and Line DAC to 100% (defaults sit at -23.5 dB), HP and Line output amps unmuted at 100%, then alsactl store + wpctl sink to 1.0 - files/configure_audio.service — After=sound.target alsa-restore.service, no dependency on a seeed-voicecard.service (none needed for v2) The v2 stage is additive; the existing v1 stage is untouched. A build can pick v1 or v2 by including the matching directory in stage selection. * fix(2michat-v2): wire CI, guard mixer service, drop dead code Follow-up to the initial 02-stage-audiodriver-2michat-v2 commit: - build-all.yml: add build-2michat-v2 and build-2michat-v2-lva jobs and include them in generate-rpi-imager-json.needs. Without this the new stage exists but no image is ever produced. - configure_audio.service: add ConditionPathExists=!/var/lib/configure_audio/success + ExecStartPost touch marker so mixer tuning runs once on first boot and subsequent manual amixer + alsactl store customisations survive reboots. Without the guard every boot reset the mixer to the stage's canned defaults. - configure_audio.sh: drop the wpctl block. The service runs as root under systemd; wpctl requires the PipeWire user session (uid 1000) and the existing `|| true` made it a silent no-op. - 01-driver/01-packages: drop i2c-tools (no runtime hardware detection on v2 — the overlay is fixed) and libasound2-plugins (v2 does not install a custom asound.conf that references resample plugins). - docs/hardware_2mic_v2.md + README.md: add a v2 hardware page and two README rows (base + LVA) mirroring the v1 entries. * fix(2michat-v2): run mixer tune every boot, propagate amixer failures Address review feedback on #41: drop the first-boot guard so the mixer tuning runs on every boot. PipeWire / WirePlumber manage ALSA state per session and can reset controls to 0% between reboots, which would leave users stuck silent if the service only ran once. Also make configure_audio.sh surface amixer set failures (previously swallowed by unconditional return 0), so that a broken tuning exits non-zero and the systemd Restart=on-failure path actually retries instead of falsely claiming success and persisting the broken state via alsactl store. Verified with a shim-based test harness (5 cases: happy path, single amixer set failure, missing control, multiple failures, card timeout). * fix(2michat-v2): align with #42 — add wpctl unity + user@1000 dep Match the post-#42 pattern already used by 02-stage-audiodriver-2michat-v1: call `wpctl set-volume @DEFAULT_AUDIO_SINK@ 1.0` after the amixer tune so the PipeWire default sink lands at unity, and add `After=/Wants=user@1000.service` + `Environment=XDG_RUNTIME_DIR=/run/user/1000` to the unit so wpctl can reach the user PipeWire socket. Drop the `PCM 85%` amixer set: PipeWire drives PCM as hardware volume passthrough, so wpctl unity immediately overwrites whatever we set PCM to. Verified on satellite-bedroom: `amixer set PCM 50%` then `wpctl set-volume @DEFAULT_AUDIO_SINK@ 1.0` leaves PCM at 100%. Keeping the line made the intent misleading ("85% for headroom") while having zero effect on the final state. The rest of the amixer tuning stays — unlike WM8960 on v1 or the DSP-driven lite board, the TLV320AIC3104 on the V2.0 HAT ships with `HP DAC` at -23.5 dB and `HP` at ~89%. Those stages are downstream of the PipeWire-managed PCM, so wpctl unity on its own still yields a stuck-quiet card. * fix(2michat-v2): boost capture PGA for wake-word detection TLV320AIC3104 ships with the input PGA at 27% (16 dB), which is too quiet for reliable microWakeWord triggering on a Pi Zero 2 W at typical speaking distance. Verified on satellite-bedroom: at 27% 'Hey Jarvis' never fires; at 80% (47.5 dB) it does. The output-side tune handled audibility; this handles detectability. --------- Co-authored-by: Florian Asche <github@florian-asche.de>
1 parent a71b63f commit 62ec6f2

8 files changed

Lines changed: 218 additions & 0 deletions

File tree

.github/workflows/build-all.yml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,25 @@ jobs:
5151
compression-level: 6
5252
custom-hostname: picompose
5353

54+
# 2MICHAT-v2
55+
build-2michat-v2:
56+
uses: ./.github/workflows/build-image-template.yml
57+
with:
58+
image-name: PiCompose-2MicHat-v2
59+
stage-list: stage0 stage1 stage2 ./01-stage-picompose ./02-stage-audiodriver-2michat-v2 ./04-stage-finish
60+
compression: xz
61+
compression-level: 6
62+
custom-hostname: picompose
63+
64+
build-2michat-v2-lva:
65+
uses: ./.github/workflows/build-image-template.yml
66+
with:
67+
image-name: PiCompose_2MicHat-v2_Linux-Voice-Assistant
68+
stage-list: stage0 stage1 stage2 ./01-stage-picompose ./02-stage-audiodriver-2michat-v2 ./03-stage-linux-voice-assistant ./04-stage-finish
69+
compression: xz
70+
compression-level: 6
71+
custom-hostname: picompose
72+
5473
# RESPEAKER-LITE
5574
build-respeaker_lite:
5675
uses: ./.github/workflows/build-image-template.yml
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
git
2+
build-essential
3+
device-tree-compiler
4+
alsa-utils
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
#!/bin/bash -e
2+
3+
# ReSpeaker 2-Mics Pi HAT **V2.0** uses a TLV320AIC3104 codec on I2C 0x18 —
4+
# NOT the WM8960 codec (0x1a) used on V1. No out-of-tree DKMS module is
5+
# needed: the mainline `snd-soc-tlv320aic3x` driver already ships in the
6+
# kernel. We just need the device-tree overlay that wires it up to the Pi's
7+
# I2S pins. The overlay source is maintained by Seeed in their
8+
# seeed-linux-dtoverlays repo.
9+
10+
echo "Building ReSpeaker 2-Mic HAT V2.0 device-tree overlay..."
11+
12+
SRC=/tmp/seeed-linux-dtoverlays
13+
rm -rf "$SRC"
14+
git clone --depth 1 https://github.com/Seeed-Studio/seeed-linux-dtoverlays.git "$SRC"
15+
16+
cd "$SRC"
17+
make overlays/rpi/respeaker-2mic-v2_0-overlay.dtbo
18+
19+
# detect boot config + overlays location
20+
OVERLAYS_DIR=/boot/overlays
21+
CONFIG=/boot/config.txt
22+
if [ -d /boot/firmware/overlays ]; then
23+
OVERLAYS_DIR=/boot/firmware/overlays
24+
fi
25+
if [ -f /boot/firmware/config.txt ]; then
26+
CONFIG=/boot/firmware/config.txt
27+
fi
28+
if [ -f /boot/firmware/usercfg.txt ]; then
29+
CONFIG=/boot/firmware/usercfg.txt
30+
fi
31+
32+
install -v -m 644 overlays/rpi/respeaker-2mic-v2_0-overlay.dtbo \
33+
"${OVERLAYS_DIR}/respeaker-2mic-v2_0.dtbo"
34+
35+
# set boot params: enable I2C and load the v2 overlay
36+
sed -i -e 's:#dtparam=i2c_arm=on:dtparam=i2c_arm=on:g' "$CONFIG" || true
37+
grep -q "^dtparam=i2c_arm=on$" "$CONFIG" || echo "dtparam=i2c_arm=on" >> "$CONFIG"
38+
grep -q "^dtoverlay=respeaker-2mic-v2_0$" "$CONFIG" || \
39+
echo "dtoverlay=respeaker-2mic-v2_0" >> "$CONFIG"
40+
41+
cd /
42+
rm -rf "$SRC"
43+
44+
echo "Done. After boot the card appears as 'seeed2micvoicec' (bcm2835-i2s-tlv320aic3x-hifi)."
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#!/bin/bash -e
2+
3+
install -v -m 755 files/configure_audio.sh "${ROOTFS_DIR}/usr/bin/configure_audio.sh"
4+
install -v -m 644 files/configure_audio.service "${ROOTFS_DIR}/etc/systemd/system/configure_audio.service"
5+
6+
on_chroot << EOF
7+
echo "Enable audio services"
8+
systemctl daemon-reload
9+
systemctl enable configure_audio.service
10+
EOF
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
[Unit]
2+
Description=PiCompose - Configure Audio volume setting (ReSpeaker 2-Mic HAT V2.0)
3+
After=user@1000.service sound.target alsa-restore.service
4+
Wants=user@1000.service sound.target alsa-restore.service
5+
6+
[Service]
7+
Type=oneshot
8+
Environment=XDG_RUNTIME_DIR=/run/user/1000
9+
ExecStart=/usr/bin/configure_audio.sh
10+
RemainAfterExit=yes
11+
Restart=on-failure
12+
RestartSec=30s
13+
14+
[Install]
15+
WantedBy=multi-user.target
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
#!/bin/bash
2+
set -u
3+
4+
# TLV320AIC3104 on V2.0 ships quiet on both ends: HP DAC at -23.5 dB,
5+
# HP/Line amps at ~89% and muted on some units, and the capture PGA at 27%
6+
# (16 dB) — too low for microWakeWord detection. PCM is driven by wpctl as
7+
# hardware-volume passthrough, so only the downstream stages need tuning.
8+
# Runs every boot because WirePlumber can reset ALSA state between sessions.
9+
10+
wait_for_card_and_control() {
11+
local card="$1"
12+
local control="$2"
13+
local max_tries=30
14+
local count=0
15+
16+
while [ "$count" -lt "$max_tries" ]; do
17+
count=$((count + 1))
18+
19+
if amixer -c "$card" info >/dev/null 2>&1; then
20+
if amixer -c "$card" scontrols | grep -Fq "'$control'"; then
21+
echo "Card $card with control '$control' is ready ($count/$max_tries)"
22+
return 0
23+
fi
24+
echo "Card $card found, but control '$control' not ready yet ($count/$max_tries)"
25+
else
26+
echo "Card $card not ready yet ($count/$max_tries)"
27+
fi
28+
29+
sleep 1
30+
done
31+
32+
echo "Card $card with control '$control' did not become ready"
33+
return 1
34+
}
35+
36+
# Returns 0 on set-success or missing control; 1 on set-failure.
37+
# Non-zero exit triggers the systemd retry.
38+
set_control_if_exists() {
39+
local card="$1"
40+
local control="$2"
41+
shift 2
42+
43+
if amixer -c "$card" scontrols | grep -Fq "'$control'"; then
44+
echo "Setting $control on $card to $*"
45+
if amixer -c "$card" set "$control" "$@"; then
46+
return 0
47+
fi
48+
echo "amixer set failed for '$control' on $card" >&2
49+
return 1
50+
fi
51+
52+
echo "Control '$control' not found on $card, skipping"
53+
return 0
54+
}
55+
56+
# Same kernel alias as V1 (`seeed2micvoicec`); the PCM control distinguishes
57+
# V2's tlv320aic3x from V1's wm8960.
58+
if ! wait_for_card_and_control seeed2micvoicec PCM; then
59+
echo "No TLV320AIC3104-based card became ready; is the V2.0 overlay loaded?"
60+
exit 1
61+
fi
62+
CARD="seeed2micvoicec"
63+
64+
FAIL=0
65+
set_control_if_exists "$CARD" "HP DAC" 100% || FAIL=1
66+
set_control_if_exists "$CARD" "Line DAC" 100% || FAIL=1
67+
set_control_if_exists "$CARD" "HP" 100% unmute || FAIL=1
68+
set_control_if_exists "$CARD" "Line" 100% unmute || FAIL=1
69+
set_control_if_exists "$CARD" "PGA" 80% cap || FAIL=1
70+
71+
if [ "$FAIL" -ne 0 ]; then
72+
echo "One or more amixer set calls failed; not storing state." >&2
73+
exit 1
74+
fi
75+
76+
# Keep PipeWire sink at unity so HA/LVA volume reaches the ALSA stages
77+
# above. Matches the 2michat-v1 pattern from #42.
78+
wpctl set-volume @DEFAULT_AUDIO_SINK@ 1.0
79+
80+
alsactl store
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#!/bin/bash -e
2+
3+
if [ ! -d "${ROOTFS_DIR}" ]; then
4+
copy_previous
5+
fi

docs/hardware_2mic_v2.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Hardware - ReSpeaker 2-Mics Pi HAT v2.0
2+
3+
<img src="respeaker_2michats.webp" alt="ReSpeaker 2-Mics Pi HAT" style="width: 300px; height: auto;"></img>
4+
5+
The V2.0 is a silent hardware revision of the 2-Mics Pi HAT. Visually identical, electrically different: the WM8960 codec used on v1 (I2C `0x1a`) is replaced with a **TLV320AIC3104** codec (I2C `0x18`). This matters because the v1 driver stage does not produce a working sound card on v2 hardware, and because the mixer defaults differ.
6+
7+
Features (same as v1):
8+
- Support the Raspberry Pi 3B / 4B / Zero 2 W
9+
- Two microphones (Mic L and Mic R)
10+
- Two Grove connectors
11+
- One User-defined button
12+
- 3.5 mm audio interface
13+
- XH2.54-2P audio output interface
14+
15+
## Order
16+
17+
### Base:
18+
19+
- [Raspberry Pi Zero 2 W](https://amzn.to/3M0G4hC)
20+
- [SD-Card](https://amzn.to/4qfx06l)
21+
- [US MicroUSB Power Supply](https://amzn.to/4c52mt3)
22+
- [Cable for Speaker](https://amzn.to/3ZvU0Dz)
23+
24+
### ReSpeaker 2-Mics Pi HAT v2.0
25+
26+
- [Seeed Studio ReSpeaker 2-Mics Pi HAT V2.0](https://wiki.seeedstudio.com/respeaker_2_mics_pi_hat_v2/)
27+
28+
## Technical notes
29+
30+
Unlike v1 — which needs an out-of-tree DKMS kernel module — v2 uses the mainline `snd-soc-tlv320aic3x` driver that already ships with the Raspberry Pi OS kernel. The image only needs:
31+
32+
1. The device-tree overlay `respeaker-2mic-v2_0.dtbo` (built from [Seeed-Studio/seeed-linux-dtoverlays](https://github.com/Seeed-Studio/seeed-linux-dtoverlays)), enabled via `dtoverlay=respeaker-2mic-v2_0` in `/boot/firmware/config.txt`.
33+
2. `dtparam=i2c_arm=on` in `/boot/firmware/config.txt`.
34+
3. Per-boot mixer tuning — the TLV320 ships with several attenuators well below 100 % (`HP DAC` at -23.5 dB in particular), producing a card that appears to work but is inaudible at typical application volumes.
35+
36+
The PiCompose `02-stage-audiodriver-2michat-v2` stage performs all three steps automatically. The mixer tuning is applied by `configure_audio.service` on every boot, the same pattern other audiodriver stages follow after #42: amixer on the codec-specific controls, then `wpctl set-volume @DEFAULT_AUDIO_SINK@ 1.0` to keep the PipeWire sink at unity. PipeWire / WirePlumber manage ALSA state per session and can reset the mixer between reboots, so a first-boot-only guard would let users end up stuck at 0%. Customization via `amixer` is still possible at runtime — it just won't survive a reboot.
37+
38+
## Additional information
39+
40+
- [Seeed Studio Wiki — ReSpeaker 2-Mics Pi HAT v2.0](https://wiki.seeedstudio.com/respeaker_2_mics_pi_hat_v2/)
41+
- [seeed-linux-dtoverlays (overlay source)](https://github.com/Seeed-Studio/seeed-linux-dtoverlays)

0 commit comments

Comments
 (0)