CharacteristicsBehavior: Device finely adjusts to the host’s data rateHost affinity: Works well with Windows, poorly with iPhoneQuality preservation: SRC required on the DDC side; DAC must follow the USB host clock unconditionally
Characteristics Behavior: Device becomes the clock source; host follows the feedbackHost affinity: Poor with Windows, excellent with iPhoneQuality preservation: Accurate real‑time measurements must be fed back; SRC still required on the DDC side
I (265) main_task: Started on CPU0 I (288) main_task: Calling app_main() I (298) PopoDAC21: load:uac rate=96000, ch=2, bits(bytes)=24(3) I (299) PopoDAC21: uac rate=96000, ch=2, bits(bytes)=24(3), frame=576 I (299) PopoDAC21: i2s dma count=16, size=192, priority=3 I (304) SSD1306: New i2c driver is used I (358) SSD1306: OLED configured successfully I (414) PopoDAC21: Succeeded to create monitor_task_esp I (414) PopoDAC21: Succeeded to start monitor_task_esp per=1000us I (414) PopoDAC21: i2s slotcfg data=24, ws=32, slot=32, mode=2 I (419) PopoDAC21: i2s request mclk=24576000, mclk_multi=256 blck=6144000, blck_div=4.0 I (428) PopoDAC21: i2s(1) sel=2 src=160000000 mlck=24576000.00 num=6 x=1 y=2 z=47 yn1=1 I (435) PopoDAC21: i2s(1) en=0, act=0 bits=23 bck_div=3 I (440) PopoDAC21: i2s(1) latency 0us/1ms I (444) PopoDAC21: usb_task enter
// Integral I2S TX clock divider value. f_I2S_CLK = f_I2S_CLK_S/(N b/a). // There will be (a-b) * n-div and b * (n 1)-div. // So the average combination will be: for b<= a/2, z * [x * n-div (n 1)-div] y * n-div. For b > a/2, z * [n-div x *(n 1)-div] y * (n 1)-div.int div_num;// For b <= a/2, the value of I2S_TX_CLKM_DIV_X is (a/b) - 1. For b > a/2, the value of I2S_TX_CLKM_DIV_X is (a/(a-b)) - 1.int div_x;// For b <= a/2, the value of I2S_TX_CLKM_DIV_Y is (a%b) . For b > a/2, the value of I2S_TX_CLKM_DIV_Y is (a%(a-b)).int div_y;// For b <= a/2, the value of I2S_TX_CLKM_DIV_Z is b. For b > a/2, the value of I2S_TX_CLKM_DIV_Z is (a-b).int div_z;// For b <= a/2, the value of I2S_TX_CLKM_DIV_YN1 is 0 . For b > a/2, the value of I2S_TX_CLKM_DIV_YN1 is 1.int div_yn1;
void i2s_current_clock(i2s_port_t port, clock_info_t* clk){ clk->clk_enable = REG_GET_FIELD(I2S_TX_CLKM_CONF_REG(port), I2S_CLK_EN); clk->clk_active = REG_GET_FIELD(I2S_TX_CLKM_CONF_REG(port), I2S_TX_CLK_ACTIVE); // Select I2S Tx module source clock. 0: XTAL clock. 1: APLL. 2: CLK160. 3: I2S_MCL clk->clk_sel = REG_GET_FIELD(I2S_TX_CLKM_CONF_REG(port), I2S_TX_CLK_SEL); clk->bits_mod = REG_GET_FIELD(I2S_TX_CONF1_REG(port), I2S_TX_BITS_MOD); clk->bck_div = REG_GET_FIELD(I2S_TX_CONF1_REG(port), I2S_TX_BCK_DIV_NUM); switch (clk->clk_sel) { case 0: clk->src_clk = 40000000; break; // XTAL 40 MHz case 1: clk->src_clk = 491520000; break; // APLL 491.52 MHz case 2: clk->src_clk = 160000000; break; // PLL160 MHz case 3: clk->src_clk = 0; break; // external MCLK default: clk->src_clk = 0; break; } clk->div_num = REG_GET_FIELD(I2S_TX_CLKM_CONF_REG(port), I2S_TX_CLKM_DIV_NUM); clk->div_x = REG_GET_FIELD(I2S_TX_CLKM_DIV_CONF_REG(port), I2S_TX_CLKM_DIV_X); clk->div_y = REG_GET_FIELD(I2S_TX_CLKM_DIV_CONF_REG(port), I2S_TX_CLKM_DIV_Y); clk->div_z = REG_GET_FIELD(I2S_TX_CLKM_DIV_CONF_REG(port), I2S_TX_CLKM_DIV_Z); clk->div_yn1 = REG_GET_FIELD(I2S_TX_CLKM_DIV_CONF_REG(port), I2S_TX_CLKM_DIV_YN1); double frac = 0.0; if (clk->div_x && clk->div_z) { int a,b; if (clk->div_yn1 == 0) { // b <= a/2 b = clk->div_z; a = clk->div_z * (clk->div_x 1) clk->div_y; } else { // b > a/2 int a_minus_b = clk->div_z; a = a_minus_b * (clk->div_x 1) clk->div_y; b = a - a_minus_b; } frac = (double)b / (double)a; } clk->divider = (double)clk->div_num frac; clk->mclk = (clk->src_clk && clk->divider) ? ((double)clk->src_clk / clk->divider) : 0.0;}
bool i2s_scan_div(double src_clk, double target_mclk, int div_num, int div_a, clock_info_t* clki) { double target_sub = fabs(src_clk / target_mclk - (double)div_num); int match_div_b = 0; double *candidate_subs = (double *)malloc(div_a * sizeof(double)); int div_b; double matched = 0; // collect div_b for (div_b=0; div_b candidate_subs[div_b] = (double)div_b/(double)div_a; } // scaning for (div_b=0; div_b if (fabs(target_sub - fabs(candidate_subs[div_b])) < fabs(target_sub - fabs(candidate_subs[match_div_b]))) { match_div_b = div_b; } } if (match_div_b == 0) return false; if (match_div_b <= div_a/2) { // For b <= a/2 clki->div_yn1 = 0; clki->div_x = (div_a/match_div_b) - 1; clki->div_y = (div_a%match_div_b); clki->div_z = match_div_b; } else { // For b > a/2 clki->div_yn1 = 1; clki->div_x = (div_a/(div_a-match_div_b)) - 1; clki->div_y = (div_a%(div_a-match_div_b)); clki->div_z = (div_a-match_div_b); } clki->div_num = div_num; clki->divider = (double)clki->div_num (double)match_div_b/(double)div_a; clki->mclk = src_clk / ((double)div_num (double)match_div_b/(double)div_a); matched = candidate_subs[match_div_b]; free(candidate_subs); return fabs(matched)} I2S: Reconfiguring CLKM_DIV_X / Y / Z / YN1
void i2s_set_clock_div(i2s_port_t port, clock_info_t* clk){ // param setting(clkm, bck, mclk...) REG_SET_FIELD(I2S_TX_CLKM_CONF_REG(port), I2S_TX_CLKM_DIV_NUM, clk->div_num); REG_SET_FIELD(I2S_TX_CLKM_DIV_CONF_REG(port), I2S_TX_CLKM_DIV_X, clk->div_x); REG_SET_FIELD(I2S_TX_CLKM_DIV_CONF_REG(port), I2S_TX_CLKM_DIV_Y, clk->div_y); REG_SET_FIELD(I2S_TX_CLKM_DIV_CONF_REG(port), I2S_TX_CLKM_DIV_Z, clk->div_z); REG_SET_FIELD(I2S_TX_CLKM_DIV_CONF_REG(port), I2S_TX_CLKM_DIV_YN1, clk->div_yn1); // apply REG_SET_FIELD(I2S_TX_CONF_REG(port), I2S_TX_UPDATE, 1);}