@@ -84,20 +84,29 @@ static const nrfx_timer_t feedback_timer_instance =
8484 * Full-Speed isochronous feedback is Q10.10 unsigned integer left-justified in
8585 * the 24-bits so it has Q10.14 format. This sample application puts zeroes to
8686 * the 4 least significant bits (does not use the bits for extra precision).
87+ *
88+ * High-Speed isochronous feedback is Q12.13 unsigned integer aligned in the
89+ * 32-bits so the binary point is located between second and third byte so it
90+ * has Q16.16 format. This sample applications puts zeroes to the 3 least
91+ * significant bits (does not use the bits for extra precision).
8792 */
88- #define FEEDBACK_K 10
93+ #define FEEDBACK_FS_K 10
94+ #define FEEDBACK_HS_K 13
8995#if defined(CONFIG_APP_USE_I2S_LRCLK_EDGES_COUNTER )
9096#define FEEDBACK_P 1
9197#else
9298#define FEEDBACK_P 5
9399#endif
94100
95101#define FEEDBACK_FS_SHIFT 4
102+ #define FEEDBACK_HS_SHIFT 3
96103
97104static struct feedback_ctx {
98105 uint32_t fb_value ;
99106 int32_t rel_sof_offset ;
100107 int32_t base_sof_offset ;
108+ uint32_t counts_per_sof ;
109+ bool high_speed ;
101110 union {
102111 /* For edge counting */
103112 struct {
@@ -190,7 +199,7 @@ struct feedback_ctx *feedback_init(void)
190199
191200 feedback_target_init ();
192201
193- feedback_reset_ctx (& fb_ctx );
202+ feedback_reset_ctx (& fb_ctx , false );
194203
195204 if (IS_ENABLED (CONFIG_APP_USE_I2S_LRCLK_EDGES_COUNTER )) {
196205 err = feedback_edge_counter_setup ();
@@ -239,6 +248,24 @@ struct feedback_ctx *feedback_init(void)
239248 return & fb_ctx ;
240249}
241250
251+ static uint32_t nominal_feedback_value (struct feedback_ctx * ctx )
252+ {
253+ if (ctx -> high_speed ) {
254+ return (SAMPLE_RATE / 8000 ) << (FEEDBACK_HS_K + FEEDBACK_HS_SHIFT );
255+ }
256+
257+ return (SAMPLE_RATE / 1000 ) << (FEEDBACK_FS_K + FEEDBACK_FS_SHIFT );
258+ }
259+
260+ static uint32_t feedback_period (struct feedback_ctx * ctx )
261+ {
262+ if (ctx -> high_speed ) {
263+ return BIT (FEEDBACK_HS_K - FEEDBACK_P );
264+ }
265+
266+ return BIT (FEEDBACK_FS_K - FEEDBACK_P );
267+ }
268+
242269static void update_sof_offset (struct feedback_ctx * ctx , uint32_t sof_cc ,
243270 uint32_t framestart_cc )
244271{
@@ -255,16 +282,16 @@ static void update_sof_offset(struct feedback_ctx *ctx, uint32_t sof_cc,
255282 * when regulated and therefore the relative clock frequency
256283 * discrepancies are essentially negligible.
257284 */
258- clks_per_edge = sof_cc / ( SAMPLES_PER_SOF << FEEDBACK_P ) ;
285+ clks_per_edge = sof_cc / ctx -> counts_per_sof ;
259286 sof_cc /= MAX (clks_per_edge , 1 );
260287 framestart_cc /= MAX (clks_per_edge , 1 );
261288 }
262289
263290 /* /2 because we treat the middle as a turning point from being
264291 * "too late" to "too early".
265292 */
266- if (framestart_cc > ( SAMPLES_PER_SOF << FEEDBACK_P ) /2 ) {
267- sof_offset = framestart_cc - ( SAMPLES_PER_SOF << FEEDBACK_P ) ;
293+ if (framestart_cc > ctx -> counts_per_sof /2 ) {
294+ sof_offset = framestart_cc - ctx -> counts_per_sof ;
268295 } else {
269296 sof_offset = framestart_cc ;
270297 }
@@ -279,26 +306,30 @@ static void update_sof_offset(struct feedback_ctx *ctx, uint32_t sof_cc,
279306
280307 if (sof_offset >= 0 ) {
281308 abs_diff = sof_offset - ctx -> rel_sof_offset ;
282- base_change = - ( SAMPLES_PER_SOF << FEEDBACK_P ) ;
309+ base_change = - ctx -> counts_per_sof ;
283310 } else {
284311 abs_diff = ctx -> rel_sof_offset - sof_offset ;
285- base_change = SAMPLES_PER_SOF << FEEDBACK_P ;
312+ base_change = ctx -> counts_per_sof ;
286313 }
287314
288315 /* Adjust base offset only if the change happened through the
289316 * outer bound. The actual changes should be significantly lower
290317 * than the threshold here.
291318 */
292- if (abs_diff > ( SAMPLES_PER_SOF << FEEDBACK_P ) /2 ) {
319+ if (abs_diff > ctx -> counts_per_sof /2 ) {
293320 ctx -> base_sof_offset += base_change ;
294321 }
295322 }
296323
297324 ctx -> rel_sof_offset = sof_offset ;
298325}
299326
300- static inline int32_t offset_to_correction (int32_t offset )
327+ static inline int32_t offset_to_correction (struct feedback_ctx * ctx , int32_t offset )
301328{
329+ if (ctx -> high_speed ) {
330+ return - (offset / BIT (FEEDBACK_P )) * BIT (FEEDBACK_HS_SHIFT );
331+ }
332+
302333 return - (offset / BIT (FEEDBACK_P )) * BIT (FEEDBACK_FS_SHIFT );
303334}
304335
@@ -320,39 +351,66 @@ static int32_t pi_update(struct feedback_ctx *ctx)
320351 int32_t error = SP - PV ;
321352
322353 /*
323- * With above normalization at Full-Speed, when data received during
324- * SOF n appears on I2S during SOF n+3, the Ziegler Nichols Ultimate
325- * Gain is around 1.15 and the oscillation period is around 90 SOF.
326- * (much nicer oscillations with 204.8 SOF period can be observed with
327- * gain 0.5 when the delay is not n+3, but n+33 - surprisingly the
328- * resulting PI coefficients after power of two rounding are the same).
354+ * With above normalization, when data received during SOF n appears on
355+ * I2S during SOF n+3, the Ziegler Nichols Ultimate Gain and oscillation
356+ * periods are as follows:
329357 *
330- * Ziegler-Nichols rule with applied stability margin of 2 results in:
331- * Kc = 0.22 * Ku = 0.22 * 1.15 = 0.253
332- * Ti = 0.83 * tu = 0.83 * 80 = 66.4
358+ * Full-Speed Linux: Ku = 1.34 tu=77 [FS SOFs]
359+ * Full-Speed Mac OS: Ku = 0.173 tu=580 [FS SOFs]
360+ * High-Speed Mac OS: Ku = 0.895 tu=4516 [HS SOFs]
361+ * High-Speed Windows: Ku = 0.515 tu=819 [HS SOFs]
333362 *
334- * Converting the rules above to parallel PI gives:
335- * Kp = Kc = 0.253
336- * Ki = Kc/Ti = 0.254/66.4 ~= 0.0038253
363+ * Linux and Mac OS oscillations were very neat, while Windows seems to
364+ * be averaging feedback value and therefore it is hard to get steady
365+ * oscillations without getting buffer uderrun.
337366 *
338- * Because we want fixed-point optimized non-tunable implementation,
339- * the parameters can be conveniently expressed with power of two:
340- * Kp ~= pow(2, -2) = 0.25 (divide by 4)
341- * Ki ~= pow(2, -8) = 0.0039 (divide by 256)
367+ * Ziegler-Nichols rule with applied stability margin of 2 results in:
368+ * [FS Linux] [FS Mac] [HS Mac] [HS Windows]
369+ * Kc = 0.22 * Ku 0.2948 0.0381 0.1969 0.1133
370+ * Ti = 0.83 * tu 63.91 481.4 3748 647.9
342371 *
343- * This can be implemented as :
372+ * Converting the rules to work with following simple regulator :
344373 * ctx->integrator += error;
345- * return (error + (ctx->integrator / 64)) / 4;
346- * but unfortunately such regulator is pretty aggressive and keeps
347- * oscillating rather quickly around the setpoint (within +-1 sample).
374+ * return (error + (ctx->integrator / Ti)) / invKc;
375+ *
376+ * gives following parameters:
377+ * [FS Linux] [FS Mac] [HS Mac] [HS Windows]
378+ * invKc = 1/Kc 3 26 5 8
379+ * Ti 64 482 3748 648
380+ *
381+ * The respective regulators seem to give quarter-amplitude-damping on
382+ * respective hosts, but tuning from one host can get into oscillations
383+ * on another host. The regulation goal is to achieve a single set of
384+ * parameters to be used with all hosts, the only parameter difference
385+ * can be based on operating speed.
386+ *
387+ * After a number of tests with all the hosts, following parameters
388+ * were determined to result in nice no-overshoot response:
389+ * [Full-Speed] [High-Speed]
390+ * invKc 128 128
391+ * Ti 2048 16384
392+ *
393+ * The power-of-two parameters were arbitrarily chosen for rounding.
394+ * The 16384 = 2048 * 8 can be considered as unifying integration time.
395+ *
396+ * While the no-overshoot is also present with invKc as low as 32, such
397+ * regulator is pretty aggressive and keeps oscillating rather quickly
398+ * around the setpoint (within +-1 sample). Lowering the controller gain
399+ * (increasing invKc value) yields really good results (the outcome is
400+ * similar to using I2S LRCLK edge counting directly).
348401 *
349- * Manually tweaking the constants so the regulator output is shifted
350- * down by 4 bits (i.e. change /64 to /2048 and /4 to /128) yields
351- * really good results (the outcome is similar, even slightly better,
352- * than using I2S LRCLK edge counting directly).
402+ * The most challenging scenario is for the regulator to stabilize right
403+ * after startup when I2S consumes data faster than nominal sample rate
404+ * (48 kHz = 6 samples per SOF at High-Speed, 48 samples at Full-Speed)
405+ * according to host (I2S consuming data slower slower than nominal
406+ * sample rate is not problematic at all because buffer overrun does not
407+ * stop I2S streaming). This regulator should be able to stabilize for
408+ * any frequency that is within required USB SOF accuracy of 500 ppm,
409+ * i.e. when nominal sample rate is 48 kHz the real sample rate can be
410+ * anywhere in [47.976 kHz; 48.024 kHz] range.
353411 */
354412 ctx -> integrator += error ;
355- return (error + (ctx -> integrator / 2048 )) / 128 ;
413+ return (error + (ctx -> integrator / ( ctx -> high_speed ? 16384 : 2048 ) )) / 128 ;
356414}
357415
358416void feedback_process (struct feedback_ctx * ctx )
@@ -374,40 +432,47 @@ void feedback_process(struct feedback_ctx *ctx)
374432 ctx -> fb_counter += sof_cc ;
375433 ctx -> fb_periods ++ ;
376434
377- if (ctx -> fb_periods == BIT ( FEEDBACK_K - FEEDBACK_P )) {
435+ if (ctx -> fb_periods == feedback_period ( ctx )) {
378436
379- /* fb_counter holds Q10.10 value, left-justify it */
380- fb = ctx -> fb_counter << FEEDBACK_FS_SHIFT ;
437+ if (ctx -> high_speed ) {
438+ fb = ctx -> fb_counter << FEEDBACK_HS_SHIFT ;
439+ } else {
440+ /* fb_counter holds Q10.10 value, left-justify it */
441+ fb = ctx -> fb_counter << FEEDBACK_FS_SHIFT ;
442+ }
381443
382444 /* Align I2S FRAMESTART to USB SOF by adjusting reported
383445 * feedback value. This is endpoint specific correction
384446 * mentioned but not specified in USB 2.0 Specification.
385447 */
386448 if (abs (offset ) > BIT (FEEDBACK_P )) {
387- fb += offset_to_correction (offset );
449+ fb += offset_to_correction (ctx , offset );
388450 }
389451
390452 ctx -> fb_value = fb ;
391453 ctx -> fb_counter = 0 ;
392454 ctx -> fb_periods = 0 ;
393455 }
394456 } else {
457+ const uint32_t zero_lsb_mask = ctx -> high_speed ? 0x7 : 0xF ;
458+
395459 /* Use PI controller to generate required feedback deviation
396460 * from nominal feedback value.
397461 */
398- fb = SAMPLES_PER_SOF << ( FEEDBACK_K + FEEDBACK_FS_SHIFT );
462+ fb = nominal_feedback_value ( ctx );
399463 /* Clear the additional LSB bits in feedback value, i.e. do not
400464 * use the optional extra resolution.
401465 */
402- fb += pi_update (ctx ) & ~0xF ;
466+ fb += pi_update (ctx ) & ~zero_lsb_mask ;
403467 ctx -> fb_value = fb ;
404468 }
405469}
406470
407- void feedback_reset_ctx (struct feedback_ctx * ctx )
471+ void feedback_reset_ctx (struct feedback_ctx * ctx , bool microframes )
408472{
409473 /* Reset feedback to nominal value */
410- ctx -> fb_value = SAMPLES_PER_SOF << (FEEDBACK_K + FEEDBACK_FS_SHIFT );
474+ ctx -> high_speed = microframes ;
475+ ctx -> fb_value = nominal_feedback_value (ctx );
411476 if (IS_ENABLED (CONFIG_APP_USE_I2S_LRCLK_EDGES_COUNTER )) {
412477 ctx -> fb_counter = 0 ;
413478 ctx -> fb_periods = 0 ;
@@ -416,19 +481,28 @@ void feedback_reset_ctx(struct feedback_ctx *ctx)
416481 }
417482}
418483
419- void feedback_start (struct feedback_ctx * ctx , int i2s_blocks_queued )
484+ void feedback_start (struct feedback_ctx * ctx , int i2s_blocks_queued ,
485+ bool microframes )
420486{
487+ ctx -> high_speed = microframes ;
488+ ctx -> fb_value = nominal_feedback_value (ctx );
489+
490+ if (microframes ) {
491+ ctx -> counts_per_sof = (SAMPLE_RATE / 8000 ) << FEEDBACK_P ;
492+ } else {
493+ ctx -> counts_per_sof = (SAMPLE_RATE / 1000 ) << FEEDBACK_P ;
494+ }
495+
421496 /* I2S data was supposed to go out at SOF, but it is inevitably
422497 * delayed due to triggering I2S start by software. Set relative
423498 * SOF offset value in a way that ensures that values past "half
424499 * frame" are treated as "too late" instead of "too early"
425500 */
426- ctx -> rel_sof_offset = ( SAMPLES_PER_SOF << FEEDBACK_P ) / 2 ;
501+ ctx -> rel_sof_offset = ctx -> counts_per_sof / 2 ;
427502 /* If there are more than 2 I2S blocks queued, use feedback regulator
428503 * to correct the situation.
429504 */
430- ctx -> base_sof_offset = (i2s_blocks_queued - 2 ) *
431- (SAMPLES_PER_SOF << FEEDBACK_P );
505+ ctx -> base_sof_offset = (i2s_blocks_queued - 2 ) * ctx -> counts_per_sof ;
432506}
433507
434508uint32_t feedback_value (struct feedback_ctx * ctx )
0 commit comments