Skip to content

Commit ff50dfb

Browse files
tmon-nordicjhedberg
authored andcommitted
samples: usb: uac2: Support High-Speed operation
Allow the samples to work both at Full-Speed and High-Speed exposing the same capabilities at both speeds. Signed-off-by: Tomasz Moń <[email protected]>
1 parent ba8b36c commit ff50dfb

File tree

12 files changed

+226
-109
lines changed

12 files changed

+226
-109
lines changed

samples/subsys/usb/uac2_explicit_feedback/app.overlay

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
compatible = "zephyr,uac2";
1212
status = "okay";
1313
full-speed;
14+
high-speed;
1415
audio-function = <AUDIO_FUNCTION_OTHER>;
1516

1617
uac_aclk: aclk {

samples/subsys/usb/uac2_explicit_feedback/boards/nrf54h20dk_nrf54h20_cpuapp.conf

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,3 @@
22
CONFIG_NRFX_GPPI=y
33
CONFIG_NRFX_TIMER131=y
44
CONFIG_NRFX_GPIOTE130=y
5-
6-
# Sample is Full-Speed only, prevent High-Speed enumeration
7-
CONFIG_UDC_DRIVER_HIGH_SPEED_SUPPORT_ENABLED=n
8-
CONFIG_USBD_MAX_SPEED_FULL=y

samples/subsys/usb/uac2_explicit_feedback/src/feedback.h

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,14 @@
99

1010
#include <stdint.h>
1111

12-
/* Nominal number of samples received on each SOF. This sample is currently
13-
* supporting only 48 kHz sample rate.
14-
*/
15-
#define SAMPLES_PER_SOF 48
12+
/* This sample is currently supporting only 48 kHz sample rate. */
13+
#define SAMPLE_RATE 48000
1614

1715
struct feedback_ctx *feedback_init(void);
18-
void feedback_reset_ctx(struct feedback_ctx *ctx);
16+
void feedback_reset_ctx(struct feedback_ctx *ctx, bool microframes);
1917
void feedback_process(struct feedback_ctx *ctx);
20-
void feedback_start(struct feedback_ctx *ctx, int i2s_blocks_queued);
18+
void feedback_start(struct feedback_ctx *ctx, int i2s_blocks_queued,
19+
bool microframes);
2120
uint32_t feedback_value(struct feedback_ctx *ctx);
22-
21+
#define SAMPLES_PER_SOF 48
2322
#endif /* FEEDBACK_H_ */

samples/subsys/usb/uac2_explicit_feedback/src/feedback_dummy.c

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,35 +5,49 @@
55
*/
66

77
#include <zephyr/kernel.h>
8+
#include <zephyr/usb/usbd.h>
89
#include "feedback.h"
910

1011
#warning "No target specific feedback code, overruns/underruns will occur"
1112

12-
#define FEEDBACK_K 10
13+
#define FEEDBACK_FS_K 10
14+
#define FEEDBACK_FS_SHIFT 4
15+
#define FEEDBACK_HS_K 13
16+
#define FEEDBACK_HS_SHIFT 3
17+
18+
static struct feedback_ctx {
19+
bool high_speed;
20+
} fb_ctx;
1321

1422
struct feedback_ctx *feedback_init(void)
1523
{
16-
return NULL;
24+
return &fb_ctx;
1725
}
1826

1927
void feedback_process(struct feedback_ctx *ctx)
2028
{
2129
ARG_UNUSED(ctx);
2230
}
2331

24-
void feedback_reset_ctx(struct feedback_ctx *ctx)
32+
void feedback_reset_ctx(struct feedback_ctx *ctx, bool microframes)
2533
{
26-
ARG_UNUSED(ctx);
34+
ctx->high_speed = microframes;
2735
}
2836

29-
void feedback_start(struct feedback_ctx *ctx, int i2s_blocks_queued)
37+
void feedback_start(struct feedback_ctx *ctx, int i2s_blocks_queued,
38+
bool microframes)
3039
{
31-
ARG_UNUSED(ctx);
3240
ARG_UNUSED(i2s_blocks_queued);
41+
42+
ctx->high_speed = microframes;
3343
}
3444

3545
uint32_t feedback_value(struct feedback_ctx *ctx)
3646
{
3747
/* Always request nominal number of samples */
38-
return SAMPLES_PER_SOF << FEEDBACK_K;
48+
if (USBD_SUPPORTS_HIGH_SPEED && ctx->high_speed) {
49+
return (SAMPLE_RATE / 8000) << (FEEDBACK_HS_K + FEEDBACK_HS_SHIFT);
50+
}
51+
52+
return (SAMPLE_RATE / 1000) << (FEEDBACK_FS_K + FEEDBACK_FS_SHIFT);
3953
}

samples/subsys/usb/uac2_explicit_feedback/src/feedback_nrf.c

Lines changed: 120 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -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

97104
static 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+
242269
static 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

358416
void 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

434508
uint32_t feedback_value(struct feedback_ctx *ctx)

0 commit comments

Comments
 (0)