@@ -84,20 +84,29 @@ static const nrfx_timer_t feedback_timer_instance =
84
84
* Full-Speed isochronous feedback is Q10.10 unsigned integer left-justified in
85
85
* the 24-bits so it has Q10.14 format. This sample application puts zeroes to
86
86
* 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).
87
92
*/
88
- #define FEEDBACK_K 10
93
+ #define FEEDBACK_FS_K 10
94
+ #define FEEDBACK_HS_K 13
89
95
#if defined(CONFIG_APP_USE_I2S_LRCLK_EDGES_COUNTER )
90
96
#define FEEDBACK_P 1
91
97
#else
92
98
#define FEEDBACK_P 5
93
99
#endif
94
100
95
101
#define FEEDBACK_FS_SHIFT 4
102
+ #define FEEDBACK_HS_SHIFT 3
96
103
97
104
static struct feedback_ctx {
98
105
uint32_t fb_value ;
99
106
int32_t rel_sof_offset ;
100
107
int32_t base_sof_offset ;
108
+ uint32_t counts_per_sof ;
109
+ bool high_speed ;
101
110
union {
102
111
/* For edge counting */
103
112
struct {
@@ -190,7 +199,7 @@ struct feedback_ctx *feedback_init(void)
190
199
191
200
feedback_target_init ();
192
201
193
- feedback_reset_ctx (& fb_ctx );
202
+ feedback_reset_ctx (& fb_ctx , false );
194
203
195
204
if (IS_ENABLED (CONFIG_APP_USE_I2S_LRCLK_EDGES_COUNTER )) {
196
205
err = feedback_edge_counter_setup ();
@@ -239,6 +248,24 @@ struct feedback_ctx *feedback_init(void)
239
248
return & fb_ctx ;
240
249
}
241
250
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
+
242
269
static void update_sof_offset (struct feedback_ctx * ctx , uint32_t sof_cc ,
243
270
uint32_t framestart_cc )
244
271
{
@@ -255,16 +282,16 @@ static void update_sof_offset(struct feedback_ctx *ctx, uint32_t sof_cc,
255
282
* when regulated and therefore the relative clock frequency
256
283
* discrepancies are essentially negligible.
257
284
*/
258
- clks_per_edge = sof_cc / ( SAMPLES_PER_SOF << FEEDBACK_P ) ;
285
+ clks_per_edge = sof_cc / ctx -> counts_per_sof ;
259
286
sof_cc /= MAX (clks_per_edge , 1 );
260
287
framestart_cc /= MAX (clks_per_edge , 1 );
261
288
}
262
289
263
290
/* /2 because we treat the middle as a turning point from being
264
291
* "too late" to "too early".
265
292
*/
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 ;
268
295
} else {
269
296
sof_offset = framestart_cc ;
270
297
}
@@ -279,26 +306,30 @@ static void update_sof_offset(struct feedback_ctx *ctx, uint32_t sof_cc,
279
306
280
307
if (sof_offset >= 0 ) {
281
308
abs_diff = sof_offset - ctx -> rel_sof_offset ;
282
- base_change = - ( SAMPLES_PER_SOF << FEEDBACK_P ) ;
309
+ base_change = - ctx -> counts_per_sof ;
283
310
} else {
284
311
abs_diff = ctx -> rel_sof_offset - sof_offset ;
285
- base_change = SAMPLES_PER_SOF << FEEDBACK_P ;
312
+ base_change = ctx -> counts_per_sof ;
286
313
}
287
314
288
315
/* Adjust base offset only if the change happened through the
289
316
* outer bound. The actual changes should be significantly lower
290
317
* than the threshold here.
291
318
*/
292
- if (abs_diff > ( SAMPLES_PER_SOF << FEEDBACK_P ) /2 ) {
319
+ if (abs_diff > ctx -> counts_per_sof /2 ) {
293
320
ctx -> base_sof_offset += base_change ;
294
321
}
295
322
}
296
323
297
324
ctx -> rel_sof_offset = sof_offset ;
298
325
}
299
326
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 )
301
328
{
329
+ if (ctx -> high_speed ) {
330
+ return - (offset / BIT (FEEDBACK_P )) * BIT (FEEDBACK_HS_SHIFT );
331
+ }
332
+
302
333
return - (offset / BIT (FEEDBACK_P )) * BIT (FEEDBACK_FS_SHIFT );
303
334
}
304
335
@@ -320,39 +351,66 @@ static int32_t pi_update(struct feedback_ctx *ctx)
320
351
int32_t error = SP - PV ;
321
352
322
353
/*
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:
329
357
*
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]
333
362
*
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.
337
366
*
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
342
371
*
343
- * This can be implemented as :
372
+ * Converting the rules to work with following simple regulator :
344
373
* 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).
348
401
*
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.
353
411
*/
354
412
ctx -> integrator += error ;
355
- return (error + (ctx -> integrator / 2048 )) / 128 ;
413
+ return (error + (ctx -> integrator / ( ctx -> high_speed ? 16384 : 2048 ) )) / 128 ;
356
414
}
357
415
358
416
void feedback_process (struct feedback_ctx * ctx )
@@ -374,40 +432,47 @@ void feedback_process(struct feedback_ctx *ctx)
374
432
ctx -> fb_counter += sof_cc ;
375
433
ctx -> fb_periods ++ ;
376
434
377
- if (ctx -> fb_periods == BIT ( FEEDBACK_K - FEEDBACK_P )) {
435
+ if (ctx -> fb_periods == feedback_period ( ctx )) {
378
436
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
+ }
381
443
382
444
/* Align I2S FRAMESTART to USB SOF by adjusting reported
383
445
* feedback value. This is endpoint specific correction
384
446
* mentioned but not specified in USB 2.0 Specification.
385
447
*/
386
448
if (abs (offset ) > BIT (FEEDBACK_P )) {
387
- fb += offset_to_correction (offset );
449
+ fb += offset_to_correction (ctx , offset );
388
450
}
389
451
390
452
ctx -> fb_value = fb ;
391
453
ctx -> fb_counter = 0 ;
392
454
ctx -> fb_periods = 0 ;
393
455
}
394
456
} else {
457
+ const uint32_t zero_lsb_mask = ctx -> high_speed ? 0x7 : 0xF ;
458
+
395
459
/* Use PI controller to generate required feedback deviation
396
460
* from nominal feedback value.
397
461
*/
398
- fb = SAMPLES_PER_SOF << ( FEEDBACK_K + FEEDBACK_FS_SHIFT );
462
+ fb = nominal_feedback_value ( ctx );
399
463
/* Clear the additional LSB bits in feedback value, i.e. do not
400
464
* use the optional extra resolution.
401
465
*/
402
- fb += pi_update (ctx ) & ~0xF ;
466
+ fb += pi_update (ctx ) & ~zero_lsb_mask ;
403
467
ctx -> fb_value = fb ;
404
468
}
405
469
}
406
470
407
- void feedback_reset_ctx (struct feedback_ctx * ctx )
471
+ void feedback_reset_ctx (struct feedback_ctx * ctx , bool microframes )
408
472
{
409
473
/* 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 );
411
476
if (IS_ENABLED (CONFIG_APP_USE_I2S_LRCLK_EDGES_COUNTER )) {
412
477
ctx -> fb_counter = 0 ;
413
478
ctx -> fb_periods = 0 ;
@@ -416,19 +481,28 @@ void feedback_reset_ctx(struct feedback_ctx *ctx)
416
481
}
417
482
}
418
483
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 )
420
486
{
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
+
421
496
/* I2S data was supposed to go out at SOF, but it is inevitably
422
497
* delayed due to triggering I2S start by software. Set relative
423
498
* SOF offset value in a way that ensures that values past "half
424
499
* frame" are treated as "too late" instead of "too early"
425
500
*/
426
- ctx -> rel_sof_offset = ( SAMPLES_PER_SOF << FEEDBACK_P ) / 2 ;
501
+ ctx -> rel_sof_offset = ctx -> counts_per_sof / 2 ;
427
502
/* If there are more than 2 I2S blocks queued, use feedback regulator
428
503
* to correct the situation.
429
504
*/
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 ;
432
506
}
433
507
434
508
uint32_t feedback_value (struct feedback_ctx * ctx )
0 commit comments