Recovery and restoration service:frontea online,corp.

Recovery and restoration service:frontea online,corp.

Deep Abyss Audio (12) - Core’3 Guidance & Navigation Control PopoDAC(ProAudio‑Spec USB DAC) DIY

‹ 2026/01/07 ›

Hello again. Episode 12 marks the beginning of Core’3 — the Guidance & Navigation Control (GNC) phase, formerly known as the Timer phase.

This is where we finally reach the true heartbeat of PopoDAC.


“Been waiting for this one?”


If you’ve made it this far, I’m sure this is the moment you’ve been looking forward to.



A sudden leap in PopoDAC’s completion level

Before diving into GNC, there has been a major breakthrough.

PopoDAC has now reached:

  • 200% completion on Windows / macOS
  • 100% completion on iPhone

I know — I’m shocked and moved myself.


What does “over 100% completion” mean?

It means:

  • PopoDAC not only rides perfectly on the MASTER TIMELINE,but can further extract each device’s individuality and apply “flavor tuning” on top.

That is the 100% zone.


And the missing 0.05% on iPhone?

It wasn’t the iPhone at all…


The culprit was the Lightning Camera Adapter (third‑party clone).

After replacing it, the iPhone now dances cleanly on the PopoDAC MASTER TIMELINE.

I’m relieved to confirm that iPhone playback is now fully locked and synchronized.


GNC Phase

In Paxa‑terms, GNC is:

  • The active subsystem that performs ultra‑precise sensing and corrective feedback to control the probe’s output and attitude.

I2S handles the final output, but unless GNC keeps both DDC and UAC under proper control, no matter how much thrust you apply, the probe will never move in the correct direction or speed.


And remember:

PopoDAC’s I2S output is quantum‑level thrust, not a simple bucket‑relay of packets.


A thought experiment

Suppose the desired audio resolution is 96 kHz.

Where should a single quantum land on the TIMELINE?


On a 1 ms window, that’s:

  • 1 / (96000 / 1000 ) ≈ 1 / 96 ms

Of course, with MCLK_MULTI, BCLK resolution increases, so the landing point becomes even finer than 1/96 ms.

This is why PopoDAC’s DDC requires ultra‑high‑precision TIMELINE resolution.


And we already proved

that ESP32‑S3/IDF’s new PLL dividers (XYZ division) can tick time with extreme precision, and PCNT can measure it in real time.


GNC dives deeper than 96 kHz —

into the micro‑intervals between those divisions —

to obtain sub‑96 resolution and build a cooperative TIMELINE with the USB host.


Module Structure

GNC consists of the following three modules:

  • Module 2: Real‑Time Sensor
  • Module 3: Timeline Adjuster
  • Module 4: Feedback Uplinker

Together, these modules form the following story:

The Story of GNC

  • Measure SOF jitter arriving from the UAC side
  • Absorb and mitigate it within the DDC‑GNC phase
  • Use the true MCLK generated by the PLL
  • Notify I2S of the correct landing point on the TIMELINE

If these three modules perform properly, PopoDAC’s DDC acquires the true MASTER TIMELINE (MTL)  and becomes a manipulator of time, controlling both UAC and I2S.


Target SCK (MCLK)

Let’s return to the PCM5102A datasheet and re‑evaluate the MCLK (SCK) targets for PopoDAC.


For PCM5102A:

  • 48 kHz: 256fS–1024fS
  • 96 kHz: 256fS–512fS
  • 192 kHz: 256fS

This gives us a wide range to pull out the chip’s full potential.

If we can run it at full capacity, that would be more than satisfying.


PCNT → BCLK Counter

“Wait, isn’t 96 kHz × 512fS (49.152 MHz) impossible for PCNT?”


Correct — if you try to count MCLK directly, PCNT tops out in the 20‑something MHz range.

But we overcome this by switching the counting target from MCLK to BCLK (after division).


As long as we know the actual MCLK and MCLK_MULTI values,

we can safely count BCLK instead of MCLK and avoid the limitation entirely.


Revised init_monitor

Practically speaking, we simply change the observed pin in pcnt_config_t.

(And of course, since the counter now reflects BCLK, we must compute the actual fS accordingly.)

    pcnt_config_t pcnt_config = {

        .pulse_gpio_num = PIN_MONITOR, // Input BCK; MCLK would exceed PCNT capability

        .ctrl_gpio_num = PCNT_PIN_NOT_USED,

        .channel = PCNT_CHANNEL_0,

        .unit = SYNCLK_PCNT_UNIT,

        .pos_mode = PCNT_COUNT_INC,

        .neg_mode = /*PCNT_COUNT_INC*/PCNT_COUNT_DIS, // Negative edge disabled to avoid count ambiguity

        .lctrl_mode = PCNT_MODE_KEEP,

        .hctrl_mode = PCNT_MODE_KEEP,

        .counter_h_lim = INT16_MAX,

        .counter_l_lim = 0,

    };


Ideally, we would count both positive and negative edges,

but due to the counter limit, we restrict it to positive edges only.


“So even if BCLK and PCNT are perfect, the measurement will still fluctuate ±2 counts. Is that acceptable at 1/96 scale?”

Yes — completely acceptable.


A ±2‑count fluctuation is well within tolerance for the 1/96‑second TIMELINE resolution, and meets the design requirements of PopoDAC’s MASTER TIMELINE.

Naturally, GNC must be built with this in mind.


Module 2: RTS(Real-Time Sensor)

RTS has a single, clear responsibility:

  • Under a highly accurate timer interval, count BCLK precisely and continuously.

There is only one rule:

  • Once ALT1 opens, never reset the counter until ALT1 closes.

Meaning: keep calling pcnt_get_counter_value endlessly.


“But the counter is only int16 — isn’t that impossible?”

Not impossible.

You must operate within that constraint.


“Why not reset the counter after each measurement?”

Absolutely not.

As shown in the earlier story diagram:

  • A single MTL (the true tick replacing SOF) only gains validity when it aligns with the previous MTL.

If you reset the counter each time, you lose the before/after continuity of the TIMELINE, and the time axis collapses.


Fortunately, the PCNT counter wraps cleanly:

  • After reaching max, it returns to min
  • As long as BCLK does not overflow the counter twice within one interval, measurement remains continuous and stable

This is why PopoDAC treats MCLK and MCLK_MULTI as a pair, and defines preset combinations that guarantee non‑breakage.


Module 3: TLA(TimeLine Adjuster)

TLA is the most delicate, precise, and flexible computation block in GNC.


It takes:

  • The true count from RTS
  • The historical count
  • The observed SOF/MTL fluctuations

And determines the next fS value to be fed back to the host.


This is the decisive move that helps the host align itself onto the MASTER TIMELINE.


The nature of jitter

The fluctuations are not simple:

  • Short‑term jitter
  • Long‑term drift
  • Mixed patterns influenced by OS behavior
  • And even UAC behavior unique to each application

TLA must dig deep enough to capture the origin of UAC jitter and respond appropriately.


Module 4: FUL(Feedback UpLinker)

FUL is the simplest part of GNC.


It merely takes the fS value computed by TLA and sends it back to the USB host in the appropriate UAC format:

  • UAC1.0 / 2.0 FullSpeed: 10.14 fixed‑point
  • UAC2.0 HighSpeed: 16.16 fixed‑point

Feedback interval

Normally, the interval defined in

Feedback Endpoint Descriptor → bInterval  

is sufficient.


(Though some hosts have quirks…

and when iPhone is involved, “a little trick” is often mandatory.)


Completion of the GNC Phase

If these three modules run without stress and successfully anchor SOF onto the MASTER TIMELINE, GNC’s mission is complete.


The resulting status is then elevated by CCC and passed on to the I2S phase.


Implementation Example

Let’s walk through an implementation example of the GNC phase.

In PopoDAC, the GNC phase is driven by GPTimer,so the implementation is divided into two parts:

  • Trigger Unit 
  • Execution Unit 

Trigger Unit

bool IRAM_ATTR monitor_task_gpt_isr(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_ctx)

{

    // Simply raise a “do it” flag every 1 ms

    if (g_app_started) {

        BaseType_t xHigherPriorityTaskWoken = pdFALSE;

        vTaskNotifyGiveFromISR(g_monitor_task_handle, &xHigherPriorityTaskWoken);

        if (xHigherPriorityTaskWoken) {

            portYIELD_FROM_ISR();  // Run the task immediately after ISR

        }

        //g_monitor_tick = true;

    }

    return true;

}


The trigger unit monitor_task_gpt_isr is extremely simple.

As the comment says, it only fires a single “please do it” instruction.

That’s all it needs to do.


Execution Unit

void monitor_task_gpt(void *param)

{

    ESP_LOGI(TAG, "monitor_task_gpt enter");

    // Wait until the system is fully started

    while (!g_app_started) {

        vTaskDelay(pdMS_TO_TICKS(1));

    }


    while (1) {

        // Wait for notification from GPTimer ISR (infinite wait)

        // → One notification = 1 ms of processing

        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);


        // Do nothing until all IO is active

        if (g_app_started) 

        {

            // RTS

            g_ddc.counter_warmup_rollout = get_fscount(&g_cnt_ideal);


            // TLA

            fscount_to_fb_q(&g_ddc, &g_cnt_ideal);


            // FB Lock

            if (g_ddc.uac_fb_start_pending)

            {

                g_ddc.uac_fb_started = true;

                g_ddc.uac_fb_start_pending = false;

            }


            // FUL

            uint32_t fb_to_send = g_cnt_ideal.last_fb;

            if (!g_ddc.uac_fb_locked) {

                fb_to_send = ((g_ddc.uac_quality.sample_rate * (1u << 14) 500) / 1000);   // ← VLC reset workaround

            }

            uac_send_feedback(fb_to_send, 0);

        }

    }

}


The execution unit monitor_task_gpt simply advances RTS → TLA → FUL, one step at a time, every millisecond.

It is beautifully clean and structured.


Why RTS, TLA, and FUL belong together

Technically, RTS does not require floating‑point operations, so it could be executed inside the trigger ISR.


However:

  • Doing so would introduce unnecessary timing gaps
  • between RTS → TLA → FUL,
  • causing the freshly computed fS to suffer from avoidable latency.

By placing all three steps in the execution unit, the GNC pipeline remains perfectly aligned and deterministic.


Feedback Timing

Among all implementation details, the most critical point is the timing of Feedback.


“Isn’t sending Feedback at AUDIO_SOF the correct answer?”

The answer is: both YES and NO.

And for PopoDAC, the answer is NO.


Let’s recall the purpose of GNC:

  • The primary mission of PopoDAC GNC is to construct the true MASTER TIMELINE (MTL).
  • SOF is the one being absorbed into that timeline — not the other way around.

Therefore:

  • Feedback should not be aligned to SOF.
  • Feedback should be the move that pulls SOF onto the MTL.

Sending Feedback within the MTL is what maximizes its corrective effect.



Application‑Level Variations

Once GNC is fully operational, the influence of the MASTER TIMELINE extends all the way up to the USB host’s application layer.


This reveals interesting differences between applications:

  • Winamp (Windows)  
  • Very well‑behaved.
  • Carefully handles low‑level USB driver IO.

  • VLC  
  • Does not touch USB directly.
  • Sometimes leaves garbage when ALT1 closes and the next track begins.

  • YouTube / streaming services  
  • Often do not open/close ALT1 on title changes or pause/resume events.


Each application has its own personality.


These quirks can affect PopoDAC’s next‑track playback behavior.

Therefore, PopoDAC can include mechanisms to absorb as much application‑level error as possible.


This concludes the GNC phase.

Next time, we move into the I2S phase, which is extremely important for sound quality.

Yes — the I2S Thruster Output.


You absolutely don’t want to miss this.

Stay tuned. 🎶