@@ -82,14 +82,15 @@ K_MEM_SLAB_DEFINE_STATIC(rx_slab, BLOCK_SIZE, BLOCK_COUNT, 32);
8282#define LOOP_IO_TIMEOUT_MS 200
8383
8484/*
85- * Acoustic-loopback capture (issue #6). The ES8311 ADC is mono and lands on one
86- * I2S slot, so AUDIO_MIC_SLOT (a slot INDEX 0 or 1, not a channel count) picks
87- * it - confirm which slot carries the ADC at HW-016. AUDIO_MIC_FULL is an
88- * empirical full-scale RMS for the PAGE_AUDIO bar, tuned at HW-016 once a real
89- * mic level is known.
85+ * Mic capture (issue #6). The ES8311 ADC is mono and lands on one I2S slot, so
86+ * AUDIO_MIC_SLOT (a slot INDEX 0 or 1, not a channel count) picks it; HW-016d
87+ * confirmed slot 0 carries the ADC. AUDIO_MIC_FULL is the empirical full-scale
88+ * RMS for the PAGE_AUDIO live bar: HW-016d measured quiet ~70-100, a normal
89+ * voice a few hundred to a few thousand, and a loud clap >= 12000, so 2000 gives
90+ * visible bars for speech without pinning the bar to 4 on every breath.
9091 */
9192#define AUDIO_MIC_SLOT 0U
92- #define AUDIO_MIC_FULL 8000U
93+ #define AUDIO_MIC_FULL 2000U
9394#define LOOP_SIL_BLOCKS 4U
9495#define LOOP_BEEP_BLOCKS 12U
9596
@@ -101,7 +102,10 @@ static const struct device *const i2s_dev = DEVICE_DT_GET(DT_NODELABEL(i2s0));
101102static const struct gpio_dt_spec amp_gpio =
102103 GPIO_DT_SPEC_GET (DT_NODELABEL (sound_amp ), amp_gpios );
103104
104- static bool ready ;
105+ /* Written by audio_init() on the main thread, read by the capture thread too, so
106+ * volatile (a plain bool could be cached in the capture loop and never seen set).
107+ */
108+ static volatile bool ready ;
105109
106110/*
107111 * Precomputed one-block 440 Hz mono sine, 16-bit signed, sampled at 16 kHz
@@ -348,11 +352,16 @@ void audio_beep(void)
348352 audio_codec_stop_output (codec_dev );
349353}
350354
351- /* Capture scratch + the latest loopback peak RMS (single-threaded use). */
355+ /* Capture scratch + the latest mic level. The capture thread (below) is the only
356+ * active user of these on hardware (audio_loopback() is a dormant bring-up
357+ * primitive); mic_rms_peak is WRITTEN by that thread and READ by the UI thread
358+ * via audio_mic_level()/_bars(), so it is volatile (a 16-bit aligned access is
359+ * atomic on this unicore SoC).
360+ */
352361static int16_t zero_block [BLOCK_FRAMES * AUDIO_CHANNELS ];
353362static int16_t rx_buf [BLOCK_FRAMES * AUDIO_CHANNELS ];
354363static int16_t mono_buf [BLOCK_FRAMES ];
355- static uint16_t mic_rms_peak ;
364+ static volatile uint16_t mic_rms_peak ;
356365
357366BUILD_ASSERT (sizeof (rx_buf ) == BLOCK_SIZE , "rx_buf must hold exactly one I2S block" );
358367
@@ -516,14 +525,108 @@ void audio_loopback(void)
516525 audio_codec_stop_output (codec_dev );
517526}
518527
528+ /*
529+ * PAGE_AUDIO live mic meter -- a dedicated CONTINUOUS-capture thread (issue #6).
530+ *
531+ * While capture_on is set (the AUDIO page is up), run ONE full-duplex session and
532+ * stream the ADC, updating mic_rms_peak with the peak RMS of each ~128 ms window
533+ * so audio_mic_bars()/audio_mic_level() track sound LIVE and fall when quiet.
534+ * This is the HW-016d-proven continuous-capture shape (one START, steady reads,
535+ * one DROP) -- NOT a per-render start/stop, which corrupted the very next SPI
536+ * display write and wedged I2S on hardware (HW-016e). The UI thread only READS
537+ * mic_rms_peak and NEVER touches I2S, so redrawing the meter can't break the
538+ * display from the UI side. Gated to the page (one START on enter, one DROP on
539+ * leave -- bounded churn, like the HW-013-proven audio_loopback). Any bounded-I/O
540+ * failure breaks to a DROP-only stop (skips DRAIN) so a wedged clock can't hang.
541+ */
542+ #define CAPTURE_STACK_SIZE 3072
543+ #define CAPTURE_PRIORITY 7
544+ #define CAPTURE_WINDOW_BLOCKS 8U /* publish the level every ~128 ms */
545+
546+ static volatile bool capture_on ;
547+
548+ void audio_capture_set (bool on )
549+ {
550+ capture_on = on ;
551+ }
552+
553+ static void audio_capture_thread (void * p1 , void * p2 , void * p3 )
554+ {
555+ ARG_UNUSED (p1 );
556+ ARG_UNUSED (p2 );
557+ ARG_UNUSED (p3 );
558+
559+ for (;;) {
560+ uint16_t wpeak = 0U ;
561+ uint32_t wc = 0U ;
562+
563+ if (!ready || !capture_on ) {
564+ mic_rms_peak = 0U ;
565+ k_msleep (50 );
566+ continue ;
567+ }
568+
569+ /* Start ONE full-duplex session (amp stays OFF for capture). */
570+ (void )i2s_trigger (i2s_dev , I2S_DIR_BOTH , I2S_TRIGGER_DROP );
571+ audio_codec_start_output (codec_dev );
572+ if (loop_tx (zero_block ) < 0 || loop_tx (zero_block ) < 0 ||
573+ i2s_trigger (i2s_dev , I2S_DIR_BOTH , I2S_TRIGGER_START ) < 0 ) {
574+ (void )i2s_trigger (i2s_dev , I2S_DIR_BOTH , I2S_TRIGGER_DROP );
575+ audio_codec_stop_output (codec_dev );
576+ k_msleep (50 );
577+ continue ;
578+ }
579+
580+ /* Stream while still on the page. TX must keep feeding silence or
581+ * the shared clock stops and RX stalls.
582+ */
583+ while (ready && capture_on ) {
584+ size_t size = sizeof (rx_buf );
585+ size_t frames ;
586+ uint16_t rms ;
587+
588+ if (loop_tx (zero_block ) < 0 ) {
589+ break ;
590+ }
591+ if (i2s_buf_read (i2s_dev , rx_buf , & size ) < 0 ) {
592+ break ;
593+ }
594+ if (size > sizeof (rx_buf )) {
595+ size = sizeof (rx_buf );
596+ }
597+ frames = size / AUDIO_FRAME_BYTES ;
598+ audio_deinterleave (rx_buf , frames , AUDIO_MIC_SLOT , mono_buf );
599+ rms = audio_rms_i16 (mono_buf , frames );
600+ if (rms > wpeak ) {
601+ wpeak = rms ;
602+ }
603+ if (++ wc >= CAPTURE_WINDOW_BLOCKS ) {
604+ mic_rms_peak = wpeak ;
605+ wpeak = 0U ;
606+ wc = 0U ;
607+ }
608+ }
609+
610+ /* Stop: DROP only (skip DRAIN so a wedged clock can't hang), mute
611+ * the DAC, and clear the level for the next visit.
612+ */
613+ (void )i2s_trigger (i2s_dev , I2S_DIR_BOTH , I2S_TRIGGER_DROP );
614+ audio_codec_stop_output (codec_dev );
615+ mic_rms_peak = 0U ;
616+ }
617+ }
618+
619+ K_THREAD_DEFINE (audio_capture_tid , CAPTURE_STACK_SIZE , audio_capture_thread ,
620+ NULL , NULL , NULL , CAPTURE_PRIORITY , 0 , 0 );
621+
519622uint16_t audio_mic_level (void )
520623{
521624 return mic_rms_peak ;
522625}
523626
524- uint8_t audio_mic_bars (void )
627+ uint8_t audio_mic_bars (uint16_t level )
525628{
526- return audio_level_bars (mic_rms_peak , AUDIO_MIC_FULL , 4U );
629+ return audio_level_bars (level , AUDIO_MIC_FULL , 4U );
527630}
528631
529632#endif /* CONFIG_APP_AUDIO */
0 commit comments