From 4f4d11021f5a3f9fc05cde491678cab8a161f0fa Mon Sep 17 00:00:00 2001 From: John Eric Date: Sun, 19 Apr 2026 02:56:30 +0200 Subject: [PATCH 1/5] feat(audiodriver): add 02-stage-audiodriver-2michat-v2 for V2.0 HAT MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- .../01-driver/01-packages | 6 ++ .../01-driver/03-run-chroot.sh | 44 ++++++++++ .../02-set-audio-volume/01-run.sh | 10 +++ .../files/configure_audio.service | 14 ++++ .../files/configure_audio.sh | 80 +++++++++++++++++++ 02-stage-audiodriver-2michat-v2/prerun.sh | 5 ++ 6 files changed, 159 insertions(+) create mode 100644 02-stage-audiodriver-2michat-v2/01-driver/01-packages create mode 100755 02-stage-audiodriver-2michat-v2/01-driver/03-run-chroot.sh create mode 100755 02-stage-audiodriver-2michat-v2/02-set-audio-volume/01-run.sh create mode 100644 02-stage-audiodriver-2michat-v2/02-set-audio-volume/files/configure_audio.service create mode 100755 02-stage-audiodriver-2michat-v2/02-set-audio-volume/files/configure_audio.sh create mode 100755 02-stage-audiodriver-2michat-v2/prerun.sh diff --git a/02-stage-audiodriver-2michat-v2/01-driver/01-packages b/02-stage-audiodriver-2michat-v2/01-driver/01-packages new file mode 100644 index 0000000..90cb5c2 --- /dev/null +++ b/02-stage-audiodriver-2michat-v2/01-driver/01-packages @@ -0,0 +1,6 @@ +git +build-essential +device-tree-compiler +i2c-tools +libasound2-plugins +alsa-utils diff --git a/02-stage-audiodriver-2michat-v2/01-driver/03-run-chroot.sh b/02-stage-audiodriver-2michat-v2/01-driver/03-run-chroot.sh new file mode 100755 index 0000000..a9160c6 --- /dev/null +++ b/02-stage-audiodriver-2michat-v2/01-driver/03-run-chroot.sh @@ -0,0 +1,44 @@ +#!/bin/bash -e + +# ReSpeaker 2-Mics Pi HAT **V2.0** uses a TLV320AIC3104 codec on I2C 0x18 — +# NOT the WM8960 codec (0x1a) used on V1. No out-of-tree DKMS module is +# needed: the mainline `snd-soc-tlv320aic3x` driver already ships in the +# kernel. We just need the device-tree overlay that wires it up to the Pi's +# I2S pins. The overlay source is maintained by Seeed in their +# seeed-linux-dtoverlays repo. + +echo "Building ReSpeaker 2-Mic HAT V2.0 device-tree overlay..." + +SRC=/tmp/seeed-linux-dtoverlays +rm -rf "$SRC" +git clone --depth 1 https://github.com/Seeed-Studio/seeed-linux-dtoverlays.git "$SRC" + +cd "$SRC" +make overlays/rpi/respeaker-2mic-v2_0-overlay.dtbo + +# detect boot config + overlays location +OVERLAYS_DIR=/boot/overlays +CONFIG=/boot/config.txt +if [ -d /boot/firmware/overlays ]; then + OVERLAYS_DIR=/boot/firmware/overlays +fi +if [ -f /boot/firmware/config.txt ]; then + CONFIG=/boot/firmware/config.txt +fi +if [ -f /boot/firmware/usercfg.txt ]; then + CONFIG=/boot/firmware/usercfg.txt +fi + +install -v -m 644 overlays/rpi/respeaker-2mic-v2_0-overlay.dtbo \ + "${OVERLAYS_DIR}/respeaker-2mic-v2_0.dtbo" + +# set boot params: enable I2C and load the v2 overlay +sed -i -e 's:#dtparam=i2c_arm=on:dtparam=i2c_arm=on:g' "$CONFIG" || true +grep -q "^dtparam=i2c_arm=on$" "$CONFIG" || echo "dtparam=i2c_arm=on" >> "$CONFIG" +grep -q "^dtoverlay=respeaker-2mic-v2_0$" "$CONFIG" || \ + echo "dtoverlay=respeaker-2mic-v2_0" >> "$CONFIG" + +cd / +rm -rf "$SRC" + +echo "Done. After boot the card appears as 'seeed2micvoicec' (bcm2835-i2s-tlv320aic3x-hifi)." diff --git a/02-stage-audiodriver-2michat-v2/02-set-audio-volume/01-run.sh b/02-stage-audiodriver-2michat-v2/02-set-audio-volume/01-run.sh new file mode 100755 index 0000000..fc50a65 --- /dev/null +++ b/02-stage-audiodriver-2michat-v2/02-set-audio-volume/01-run.sh @@ -0,0 +1,10 @@ +#!/bin/bash -e + +install -v -m 755 files/configure_audio.sh "${ROOTFS_DIR}/usr/bin/configure_audio.sh" +install -v -m 644 files/configure_audio.service "${ROOTFS_DIR}/etc/systemd/system/configure_audio.service" + +on_chroot << EOF +echo "Enable audio services" +systemctl daemon-reload +systemctl enable configure_audio.service +EOF diff --git a/02-stage-audiodriver-2michat-v2/02-set-audio-volume/files/configure_audio.service b/02-stage-audiodriver-2michat-v2/02-set-audio-volume/files/configure_audio.service new file mode 100644 index 0000000..f27353e --- /dev/null +++ b/02-stage-audiodriver-2michat-v2/02-set-audio-volume/files/configure_audio.service @@ -0,0 +1,14 @@ +[Unit] +Description=PiCompose - Configure Audio volume setting (ReSpeaker 2-Mic HAT V2.0) +After=sound.target alsa-restore.service +Wants=sound.target alsa-restore.service + +[Service] +Type=oneshot +ExecStart=/usr/bin/configure_audio.sh +RemainAfterExit=yes +Restart=on-failure +RestartSec=30s + +[Install] +WantedBy=multi-user.target diff --git a/02-stage-audiodriver-2michat-v2/02-set-audio-volume/files/configure_audio.sh b/02-stage-audiodriver-2michat-v2/02-set-audio-volume/files/configure_audio.sh new file mode 100755 index 0000000..be9460a --- /dev/null +++ b/02-stage-audiodriver-2michat-v2/02-set-audio-volume/files/configure_audio.sh @@ -0,0 +1,80 @@ +#!/bin/bash +set -u + +# TLV320AIC3104 mixer defaults on the V2.0 HAT are extremely quiet: the +# HP DAC attenuates by -23.5 dB and the HP analog amp sits at ~89%. Without +# tuning these three separate gain stages, end users report "silent card" +# even with HA / LVA output at maximum. +# +# Values below are the safe defaults calibrated against JST-connected +# speakers (peaks clip at 100% PCM). Tune further in-field with `amixer` +# and persist via `alsactl store`. + +wait_for_card_and_control() { + local card="$1" + local control="$2" + local max_tries=30 + local count=0 + + while [ "$count" -lt "$max_tries" ]; do + count=$((count + 1)) + + if amixer -c "$card" info >/dev/null 2>&1; then + if amixer -c "$card" scontrols | grep -Fq "'$control'"; then + echo "Card $card with control '$control' is ready ($count/$max_tries)" + return 0 + fi + echo "Card $card found, but control '$control' not ready yet ($count/$max_tries)" + else + echo "Card $card not ready yet ($count/$max_tries)" + fi + + sleep 1 + done + + echo "Card $card with control '$control' did not become ready" + return 1 +} + +set_control_if_exists() { + local card="$1" + local control="$2" + shift 2 + + if amixer -c "$card" scontrols | grep -Fq "'$control'"; then + echo "Setting $control on $card to $*" + amixer -c "$card" set "$control" "$@" + return 0 + fi + + echo "Control '$control' not found on $card, skipping" + return 0 +} + +# The kernel alias is the same for V1 and V2: `seeed2micvoicec`. The V2 HAT +# is distinguished by the presence of `PCM`/`HP DAC`/`Line DAC` controls +# (from tlv320aic3x) rather than V1's `Headphone`/`Speaker` (wm8960). +if ! wait_for_card_and_control seeed2micvoicec PCM; then + echo "No TLV320AIC3104-based card became ready; is the V2.0 overlay loaded?" + exit 1 +fi +CARD="seeed2micvoicec" + +# Digital pre-DAC attenuator. 100% clips on typical speakers; 85% keeps +# headroom while still being clearly audible. +set_control_if_exists "$CARD" "PCM" 85% + +# DAC output gains (ship at -23.5 dB by default — main source of quietness). +set_control_if_exists "$CARD" "HP DAC" 100% +set_control_if_exists "$CARD" "Line DAC" 100% + +# Analog output amps (ship slightly below max and muted on some units). +set_control_if_exists "$CARD" "HP" 100% unmute +set_control_if_exists "$CARD" "Line" 100% unmute + +# Default-route PipeWire sink to max; application-level volume still applies. +if command -v wpctl >/dev/null 2>&1; then + wpctl set-volume @DEFAULT_AUDIO_SINK@ 1.0 || true +fi + +alsactl store diff --git a/02-stage-audiodriver-2michat-v2/prerun.sh b/02-stage-audiodriver-2michat-v2/prerun.sh new file mode 100755 index 0000000..9acd13c --- /dev/null +++ b/02-stage-audiodriver-2michat-v2/prerun.sh @@ -0,0 +1,5 @@ +#!/bin/bash -e + +if [ ! -d "${ROOTFS_DIR}" ]; then + copy_previous +fi From dbbf003389e9635338cac71c5fcf7f7f582a821c Mon Sep 17 00:00:00 2001 From: John Eric Date: Sun, 19 Apr 2026 03:16:18 +0200 Subject: [PATCH 2/5] fix(2michat-v2): wire CI, guard mixer service, drop dead code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- .github/workflows/build-all.yml | 21 +++++++++- .../01-driver/01-packages | 2 - .../files/configure_audio.service | 3 ++ .../files/configure_audio.sh | 5 --- README.md | 2 + docs/hardware_2mic_v2.md | 41 +++++++++++++++++++ 6 files changed, 66 insertions(+), 8 deletions(-) create mode 100644 docs/hardware_2mic_v2.md diff --git a/.github/workflows/build-all.yml b/.github/workflows/build-all.yml index 42b33ea..501c967 100644 --- a/.github/workflows/build-all.yml +++ b/.github/workflows/build-all.yml @@ -41,6 +41,25 @@ jobs: compression-level: 6 custom-hostname: picompose + # 2MICHAT-v2 + build-2michat-v2: + uses: ./.github/workflows/build-image-template.yml + with: + image-name: PiCompose-2MicHat-v2 + stage-list: stage0 stage1 stage2 ./01-stage-picompose ./02-stage-audiodriver-2michat-v2 ./04-stage-finish + compression: xz + compression-level: 6 + custom-hostname: picompose + + build-2michat-v2-lva: + uses: ./.github/workflows/build-image-template.yml + with: + image-name: PiCompose_2MicHat-v2_Linux-Voice-Assistant + stage-list: stage0 stage1 stage2 ./01-stage-picompose ./02-stage-audiodriver-2michat-v2 ./03-stage-linux-voice-assistant ./04-stage-finish + compression: xz + compression-level: 6 + custom-hostname: picompose + # RESPEAKER-LITE build-respeaker_lite: uses: ./.github/workflows/build-image-template.yml @@ -62,5 +81,5 @@ jobs: # RPI IMAGER JSON generate-rpi-imager-json: - needs: [build, build-2michat, build-respeaker_lite, build-2michat-lva, build-respeaker_lite-lva] + needs: [build, build-2michat, build-2michat-v2, build-respeaker_lite, build-2michat-lva, build-2michat-v2-lva, build-respeaker_lite-lva] uses: ./.github/workflows/create-rpi-image-json.yml diff --git a/02-stage-audiodriver-2michat-v2/01-driver/01-packages b/02-stage-audiodriver-2michat-v2/01-driver/01-packages index 90cb5c2..775d501 100644 --- a/02-stage-audiodriver-2michat-v2/01-driver/01-packages +++ b/02-stage-audiodriver-2michat-v2/01-driver/01-packages @@ -1,6 +1,4 @@ git build-essential device-tree-compiler -i2c-tools -libasound2-plugins alsa-utils diff --git a/02-stage-audiodriver-2michat-v2/02-set-audio-volume/files/configure_audio.service b/02-stage-audiodriver-2michat-v2/02-set-audio-volume/files/configure_audio.service index f27353e..dfb9b10 100644 --- a/02-stage-audiodriver-2michat-v2/02-set-audio-volume/files/configure_audio.service +++ b/02-stage-audiodriver-2michat-v2/02-set-audio-volume/files/configure_audio.service @@ -2,10 +2,13 @@ Description=PiCompose - Configure Audio volume setting (ReSpeaker 2-Mic HAT V2.0) After=sound.target alsa-restore.service Wants=sound.target alsa-restore.service +ConditionPathExists=!/var/lib/configure_audio/success [Service] Type=oneshot ExecStart=/usr/bin/configure_audio.sh +ExecStartPost=/usr/bin/mkdir -p /var/lib/configure_audio +ExecStartPost=/usr/bin/touch /var/lib/configure_audio/success RemainAfterExit=yes Restart=on-failure RestartSec=30s diff --git a/02-stage-audiodriver-2michat-v2/02-set-audio-volume/files/configure_audio.sh b/02-stage-audiodriver-2michat-v2/02-set-audio-volume/files/configure_audio.sh index be9460a..7e15266 100755 --- a/02-stage-audiodriver-2michat-v2/02-set-audio-volume/files/configure_audio.sh +++ b/02-stage-audiodriver-2michat-v2/02-set-audio-volume/files/configure_audio.sh @@ -72,9 +72,4 @@ set_control_if_exists "$CARD" "Line DAC" 100% set_control_if_exists "$CARD" "HP" 100% unmute set_control_if_exists "$CARD" "Line" 100% unmute -# Default-route PipeWire sink to max; application-level volume still applies. -if command -v wpctl >/dev/null 2>&1; then - wpctl set-volume @DEFAULT_AUDIO_SINK@ 1.0 || true -fi - alsactl store diff --git a/README.md b/README.md index 58935f9..546ed08 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,8 @@ Here is a Overview for the specific images of each hardware: | **[Sattelite1](docs/hardware_sattelite1.md)**
**+Linux-Voice-Assistant**
**+Snapcast** | ReSpeaker Lite Board | • Satellite1 Hat Image
• Linux-Voice-Assistant (OpenHomeFoundation)
• Snapcast MultiRoom Audio Client
• Pre-configured for Home Assistant

Image is currently work in progress! | | **[ReSpeaker 2-Mic HAT v1](docs/hardware_2mic_v1.md)** | ReSpeaker 2-Mics Pi HAT | • Base Image
• Seeed Voicecard Driver | | **[ReSpeaker 2-Mic HAT v1](docs/hardware_2mic_v1.md)**
**+Linux-Voice-Assistant**
**+Snapcast** | ReSpeaker 2-Mics Pi HAT | • 2-Mic HAT Image
• Linux-Voice-Assistant (OpenHomeFoundation)
• 2-Mic HAT GPIO LED Control
• Snapcast MultiRoom Audio Client
• Pre-configured for Home Assistant | +| **[ReSpeaker 2-Mic HAT v2.0](docs/hardware_2mic_v2.md)** | ReSpeaker 2-Mics Pi HAT | • Base Image
• TLV320AIC3104 device-tree overlay
• First-boot mixer tuning for the V2.0 codec | +| **[ReSpeaker 2-Mic HAT v2.0](docs/hardware_2mic_v2.md)**
**+Linux-Voice-Assistant**
**+Snapcast** | ReSpeaker 2-Mics Pi HAT | • 2-Mic HAT v2.0 Image
• Linux-Voice-Assistant (OpenHomeFoundation)
• Snapcast MultiRoom Audio Client
• Pre-configured for Home Assistant | | **[ReSpeaker Lite](docs/hardware_respeaker_lite.md)** | ReSpeaker Lite Board | • Base Image
• Audio keep-alive service
• Workaround for connectivity issues in combination with the Pi Zero 2W.

There is a USB connectivity issue with the Pi Zero 2W. I cannot recommend this board if you want to use it with that. Use Pi3 or higher. | | **[ReSpeaker Lite](docs/hardware_respeaker_lite.md)**
**+Linux-Voice-Assistant**
**+Snapcast** | ReSpeaker Lite Board | • ReSpeaker Lite Image
• Linux-Voice-Assistant (OpenHomeFoundation)
• Snapcast MultiRoom Audio Client
• Pre-configured for Home Assistant
• Workaround for connectivity issues in combination with the Pi Zero 2W.

There is a USB connectivity issue with the Pi Zero 2W. If you want to use it with that, you need to use Pi3 or higher. | diff --git a/docs/hardware_2mic_v2.md b/docs/hardware_2mic_v2.md new file mode 100644 index 0000000..19296b2 --- /dev/null +++ b/docs/hardware_2mic_v2.md @@ -0,0 +1,41 @@ +# Hardware - ReSpeaker 2-Mics Pi HAT v2.0 + +ReSpeaker 2-Mics Pi HAT + +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. + +Features (same as v1): +- Support the Raspberry Pi 3B / 4B / Zero 2 W +- Two microphones (Mic L and Mic R) +- Two Grove connectors +- One User-defined button +- 3.5 mm audio interface +- XH2.54-2P audio output interface + +## Order + +### Base: + +- [Raspberry Pi Zero 2 W](https://amzn.to/3M0G4hC) +- [SD-Card](https://amzn.to/4qfx06l) +- [US MicroUSB Power Supply](https://amzn.to/4c52mt3) +- [Cable for Speaker](https://amzn.to/3ZvU0Dz) + +### ReSpeaker 2-Mics Pi HAT v2.0 + +- [Seeed Studio ReSpeaker 2-Mics Pi HAT V2.0](https://wiki.seeedstudio.com/respeaker_2_mics_pi_hat_v2/) + +## Technical notes + +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: + +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`. +2. `dtparam=i2c_arm=on` in `/boot/firmware/config.txt`. +3. Mixer tuning on first boot — the TLV320 ships with three separate attenuators all 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. + +The PiCompose `02-stage-audiodriver-2michat-v2` stage performs all three steps automatically. The mixer tuning runs once on first boot (guarded by `/var/lib/configure_audio/success`) so that subsequent manual `amixer` adjustments via `alsactl store` are preserved across reboots. + +## Additional information + +- [Seeed Studio Wiki — ReSpeaker 2-Mics Pi HAT v2.0](https://wiki.seeedstudio.com/respeaker_2_mics_pi_hat_v2/) +- [seeed-linux-dtoverlays (overlay source)](https://github.com/Seeed-Studio/seeed-linux-dtoverlays) From a1191300929ee1320a1ae272fb79e92262cfe475 Mon Sep 17 00:00:00 2001 From: John Eric Date: Sun, 19 Apr 2026 09:13:06 +0200 Subject: [PATCH 3/5] 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). --- .../files/configure_audio.service | 3 -- .../files/configure_audio.sh | 32 +++++++++++++++---- docs/hardware_2mic_v2.md | 2 +- 3 files changed, 26 insertions(+), 11 deletions(-) diff --git a/02-stage-audiodriver-2michat-v2/02-set-audio-volume/files/configure_audio.service b/02-stage-audiodriver-2michat-v2/02-set-audio-volume/files/configure_audio.service index dfb9b10..f27353e 100644 --- a/02-stage-audiodriver-2michat-v2/02-set-audio-volume/files/configure_audio.service +++ b/02-stage-audiodriver-2michat-v2/02-set-audio-volume/files/configure_audio.service @@ -2,13 +2,10 @@ Description=PiCompose - Configure Audio volume setting (ReSpeaker 2-Mic HAT V2.0) After=sound.target alsa-restore.service Wants=sound.target alsa-restore.service -ConditionPathExists=!/var/lib/configure_audio/success [Service] Type=oneshot ExecStart=/usr/bin/configure_audio.sh -ExecStartPost=/usr/bin/mkdir -p /var/lib/configure_audio -ExecStartPost=/usr/bin/touch /var/lib/configure_audio/success RemainAfterExit=yes Restart=on-failure RestartSec=30s diff --git a/02-stage-audiodriver-2michat-v2/02-set-audio-volume/files/configure_audio.sh b/02-stage-audiodriver-2michat-v2/02-set-audio-volume/files/configure_audio.sh index 7e15266..619e560 100755 --- a/02-stage-audiodriver-2michat-v2/02-set-audio-volume/files/configure_audio.sh +++ b/02-stage-audiodriver-2michat-v2/02-set-audio-volume/files/configure_audio.sh @@ -6,6 +6,10 @@ set -u # tuning these three separate gain stages, end users report "silent card" # even with HA / LVA output at maximum. # +# Runs on every boot via configure_audio.service — PipeWire / WirePlumber +# manages ALSA mixer state per session and can reset controls to 0% between +# reboots, so a first-boot-only guard would let users end up stuck silent. +# # Values below are the safe defaults calibrated against JST-connected # speakers (peaks clip at 100% PCM). Tune further in-field with `amixer` # and persist via `alsactl store`. @@ -36,6 +40,10 @@ wait_for_card_and_control() { return 1 } +# Returns 0 on success OR when the control is absent (expected on hardware +# variants without that stage). Returns 1 only when `amixer set` actually +# failed for a control that exists — so a broken tuning surfaces as a +# non-zero exit from the script and triggers the systemd retry. set_control_if_exists() { local card="$1" local control="$2" @@ -43,8 +51,11 @@ set_control_if_exists() { if amixer -c "$card" scontrols | grep -Fq "'$control'"; then echo "Setting $control on $card to $*" - amixer -c "$card" set "$control" "$@" - return 0 + if amixer -c "$card" set "$control" "$@"; then + return 0 + fi + echo "amixer set failed for '$control' on $card" >&2 + return 1 fi echo "Control '$control' not found on $card, skipping" @@ -60,16 +71,23 @@ if ! wait_for_card_and_control seeed2micvoicec PCM; then fi CARD="seeed2micvoicec" +FAIL=0 + # Digital pre-DAC attenuator. 100% clips on typical speakers; 85% keeps # headroom while still being clearly audible. -set_control_if_exists "$CARD" "PCM" 85% +set_control_if_exists "$CARD" "PCM" 85% || FAIL=1 # DAC output gains (ship at -23.5 dB by default — main source of quietness). -set_control_if_exists "$CARD" "HP DAC" 100% -set_control_if_exists "$CARD" "Line DAC" 100% +set_control_if_exists "$CARD" "HP DAC" 100% || FAIL=1 +set_control_if_exists "$CARD" "Line DAC" 100% || FAIL=1 # Analog output amps (ship slightly below max and muted on some units). -set_control_if_exists "$CARD" "HP" 100% unmute -set_control_if_exists "$CARD" "Line" 100% unmute +set_control_if_exists "$CARD" "HP" 100% unmute || FAIL=1 +set_control_if_exists "$CARD" "Line" 100% unmute || FAIL=1 + +if [ "$FAIL" -ne 0 ]; then + echo "One or more amixer set calls failed; not storing state." >&2 + exit 1 +fi alsactl store diff --git a/docs/hardware_2mic_v2.md b/docs/hardware_2mic_v2.md index 19296b2..c8c9ae7 100644 --- a/docs/hardware_2mic_v2.md +++ b/docs/hardware_2mic_v2.md @@ -33,7 +33,7 @@ Unlike v1 — which needs an out-of-tree DKMS kernel module — v2 uses the main 2. `dtparam=i2c_arm=on` in `/boot/firmware/config.txt`. 3. Mixer tuning on first boot — the TLV320 ships with three separate attenuators all 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. -The PiCompose `02-stage-audiodriver-2michat-v2` stage performs all three steps automatically. The mixer tuning runs once on first boot (guarded by `/var/lib/configure_audio/success`) so that subsequent manual `amixer` adjustments via `alsactl store` are preserved across reboots. +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: 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. ## Additional information From 9121dd684dca5922295f6c9b6f5b5d1cd468b9e9 Mon Sep 17 00:00:00 2001 From: John Eric Date: Sun, 19 Apr 2026 09:31:34 +0200 Subject: [PATCH 4/5] =?UTF-8?q?fix(2michat-v2):=20align=20with=20#42=20?= =?UTF-8?q?=E2=80=94=20add=20wpctl=20unity=20+=20user@1000=20dep?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- .../files/configure_audio.service | 5 ++- .../files/configure_audio.sh | 39 ++++++------------- README.md | 2 +- docs/hardware_2mic_v2.md | 4 +- 4 files changed, 18 insertions(+), 32 deletions(-) diff --git a/02-stage-audiodriver-2michat-v2/02-set-audio-volume/files/configure_audio.service b/02-stage-audiodriver-2michat-v2/02-set-audio-volume/files/configure_audio.service index f27353e..945f217 100644 --- a/02-stage-audiodriver-2michat-v2/02-set-audio-volume/files/configure_audio.service +++ b/02-stage-audiodriver-2michat-v2/02-set-audio-volume/files/configure_audio.service @@ -1,10 +1,11 @@ [Unit] Description=PiCompose - Configure Audio volume setting (ReSpeaker 2-Mic HAT V2.0) -After=sound.target alsa-restore.service -Wants=sound.target alsa-restore.service +After=user@1000.service sound.target alsa-restore.service +Wants=user@1000.service sound.target alsa-restore.service [Service] Type=oneshot +Environment=XDG_RUNTIME_DIR=/run/user/1000 ExecStart=/usr/bin/configure_audio.sh RemainAfterExit=yes Restart=on-failure diff --git a/02-stage-audiodriver-2michat-v2/02-set-audio-volume/files/configure_audio.sh b/02-stage-audiodriver-2michat-v2/02-set-audio-volume/files/configure_audio.sh index 619e560..305b4df 100755 --- a/02-stage-audiodriver-2michat-v2/02-set-audio-volume/files/configure_audio.sh +++ b/02-stage-audiodriver-2michat-v2/02-set-audio-volume/files/configure_audio.sh @@ -1,18 +1,10 @@ #!/bin/bash set -u -# TLV320AIC3104 mixer defaults on the V2.0 HAT are extremely quiet: the -# HP DAC attenuates by -23.5 dB and the HP analog amp sits at ~89%. Without -# tuning these three separate gain stages, end users report "silent card" -# even with HA / LVA output at maximum. -# -# Runs on every boot via configure_audio.service — PipeWire / WirePlumber -# manages ALSA mixer state per session and can reset controls to 0% between -# reboots, so a first-boot-only guard would let users end up stuck silent. -# -# Values below are the safe defaults calibrated against JST-connected -# speakers (peaks clip at 100% PCM). Tune further in-field with `amixer` -# and persist via `alsactl store`. +# TLV320AIC3104 on V2.0 ships quiet: HP DAC at -23.5 dB, HP/Line amps at +# ~89% and muted on some units. PCM is driven by wpctl as hardware-volume +# passthrough, so only the downstream stages need tuning. Runs every boot +# because WirePlumber can reset ALSA state between sessions. wait_for_card_and_control() { local card="$1" @@ -40,10 +32,8 @@ wait_for_card_and_control() { return 1 } -# Returns 0 on success OR when the control is absent (expected on hardware -# variants without that stage). Returns 1 only when `amixer set` actually -# failed for a control that exists — so a broken tuning surfaces as a -# non-zero exit from the script and triggers the systemd retry. +# Returns 0 on set-success or missing control; 1 on set-failure. +# Non-zero exit triggers the systemd retry. set_control_if_exists() { local card="$1" local control="$2" @@ -62,9 +52,8 @@ set_control_if_exists() { return 0 } -# The kernel alias is the same for V1 and V2: `seeed2micvoicec`. The V2 HAT -# is distinguished by the presence of `PCM`/`HP DAC`/`Line DAC` controls -# (from tlv320aic3x) rather than V1's `Headphone`/`Speaker` (wm8960). +# Same kernel alias as V1 (`seeed2micvoicec`); the PCM control distinguishes +# V2's tlv320aic3x from V1's wm8960. if ! wait_for_card_and_control seeed2micvoicec PCM; then echo "No TLV320AIC3104-based card became ready; is the V2.0 overlay loaded?" exit 1 @@ -72,16 +61,8 @@ fi CARD="seeed2micvoicec" FAIL=0 - -# Digital pre-DAC attenuator. 100% clips on typical speakers; 85% keeps -# headroom while still being clearly audible. -set_control_if_exists "$CARD" "PCM" 85% || FAIL=1 - -# DAC output gains (ship at -23.5 dB by default — main source of quietness). set_control_if_exists "$CARD" "HP DAC" 100% || FAIL=1 set_control_if_exists "$CARD" "Line DAC" 100% || FAIL=1 - -# Analog output amps (ship slightly below max and muted on some units). set_control_if_exists "$CARD" "HP" 100% unmute || FAIL=1 set_control_if_exists "$CARD" "Line" 100% unmute || FAIL=1 @@ -90,4 +71,8 @@ if [ "$FAIL" -ne 0 ]; then exit 1 fi +# Keep PipeWire sink at unity so HA/LVA volume reaches the ALSA stages +# above. Matches the 2michat-v1 pattern from #42. +wpctl set-volume @DEFAULT_AUDIO_SINK@ 1.0 + alsactl store diff --git a/README.md b/README.md index 546ed08..9016706 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ Here is a Overview for the specific images of each hardware: | **[Sattelite1](docs/hardware_sattelite1.md)**
**+Linux-Voice-Assistant**
**+Snapcast** | ReSpeaker Lite Board | • Satellite1 Hat Image
• Linux-Voice-Assistant (OpenHomeFoundation)
• Snapcast MultiRoom Audio Client
• Pre-configured for Home Assistant

Image is currently work in progress! | | **[ReSpeaker 2-Mic HAT v1](docs/hardware_2mic_v1.md)** | ReSpeaker 2-Mics Pi HAT | • Base Image
• Seeed Voicecard Driver | | **[ReSpeaker 2-Mic HAT v1](docs/hardware_2mic_v1.md)**
**+Linux-Voice-Assistant**
**+Snapcast** | ReSpeaker 2-Mics Pi HAT | • 2-Mic HAT Image
• Linux-Voice-Assistant (OpenHomeFoundation)
• 2-Mic HAT GPIO LED Control
• Snapcast MultiRoom Audio Client
• Pre-configured for Home Assistant | -| **[ReSpeaker 2-Mic HAT v2.0](docs/hardware_2mic_v2.md)** | ReSpeaker 2-Mics Pi HAT | • Base Image
• TLV320AIC3104 device-tree overlay
• First-boot mixer tuning for the V2.0 codec | +| **[ReSpeaker 2-Mic HAT v2.0](docs/hardware_2mic_v2.md)** | ReSpeaker 2-Mics Pi HAT | • Base Image
• TLV320AIC3104 device-tree overlay
• Per-boot codec mixer tuning for the V2.0 | | **[ReSpeaker 2-Mic HAT v2.0](docs/hardware_2mic_v2.md)**
**+Linux-Voice-Assistant**
**+Snapcast** | ReSpeaker 2-Mics Pi HAT | • 2-Mic HAT v2.0 Image
• Linux-Voice-Assistant (OpenHomeFoundation)
• Snapcast MultiRoom Audio Client
• Pre-configured for Home Assistant | | **[ReSpeaker Lite](docs/hardware_respeaker_lite.md)** | ReSpeaker Lite Board | • Base Image
• Audio keep-alive service
• Workaround for connectivity issues in combination with the Pi Zero 2W.

There is a USB connectivity issue with the Pi Zero 2W. I cannot recommend this board if you want to use it with that. Use Pi3 or higher. | | **[ReSpeaker Lite](docs/hardware_respeaker_lite.md)**
**+Linux-Voice-Assistant**
**+Snapcast** | ReSpeaker Lite Board | • ReSpeaker Lite Image
• Linux-Voice-Assistant (OpenHomeFoundation)
• Snapcast MultiRoom Audio Client
• Pre-configured for Home Assistant
• Workaround for connectivity issues in combination with the Pi Zero 2W.

There is a USB connectivity issue with the Pi Zero 2W. If you want to use it with that, you need to use Pi3 or higher. | diff --git a/docs/hardware_2mic_v2.md b/docs/hardware_2mic_v2.md index c8c9ae7..dbc6c0b 100644 --- a/docs/hardware_2mic_v2.md +++ b/docs/hardware_2mic_v2.md @@ -31,9 +31,9 @@ Unlike v1 — which needs an out-of-tree DKMS kernel module — v2 uses the main 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`. 2. `dtparam=i2c_arm=on` in `/boot/firmware/config.txt`. -3. Mixer tuning on first boot — the TLV320 ships with three separate attenuators all 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. +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. -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: 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. +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. ## Additional information From 138a088d2ad0c9eb0ba50dccf3fcfa98e26a5693 Mon Sep 17 00:00:00 2001 From: John Eric Date: Sun, 19 Apr 2026 11:21:08 +0200 Subject: [PATCH 5/5] 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. --- .../02-set-audio-volume/files/configure_audio.sh | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/02-stage-audiodriver-2michat-v2/02-set-audio-volume/files/configure_audio.sh b/02-stage-audiodriver-2michat-v2/02-set-audio-volume/files/configure_audio.sh index 305b4df..2880059 100755 --- a/02-stage-audiodriver-2michat-v2/02-set-audio-volume/files/configure_audio.sh +++ b/02-stage-audiodriver-2michat-v2/02-set-audio-volume/files/configure_audio.sh @@ -1,10 +1,11 @@ #!/bin/bash set -u -# TLV320AIC3104 on V2.0 ships quiet: HP DAC at -23.5 dB, HP/Line amps at -# ~89% and muted on some units. PCM is driven by wpctl as hardware-volume -# passthrough, so only the downstream stages need tuning. Runs every boot -# because WirePlumber can reset ALSA state between sessions. +# TLV320AIC3104 on V2.0 ships quiet on both ends: HP DAC at -23.5 dB, +# HP/Line amps at ~89% and muted on some units, and the capture PGA at 27% +# (16 dB) — too low for microWakeWord detection. PCM is driven by wpctl as +# hardware-volume passthrough, so only the downstream stages need tuning. +# Runs every boot because WirePlumber can reset ALSA state between sessions. wait_for_card_and_control() { local card="$1" @@ -65,6 +66,7 @@ set_control_if_exists "$CARD" "HP DAC" 100% || FAIL=1 set_control_if_exists "$CARD" "Line DAC" 100% || FAIL=1 set_control_if_exists "$CARD" "HP" 100% unmute || FAIL=1 set_control_if_exists "$CARD" "Line" 100% unmute || FAIL=1 +set_control_if_exists "$CARD" "PGA" 80% cap || FAIL=1 if [ "$FAIL" -ne 0 ]; then echo "One or more amixer set calls failed; not storing state." >&2