Skip to content

Commit d732ac8

Browse files
Precalculate common PWMAudio dividers, avoid noise (#2714)
Calculating the DMA clock divider for PWMAudio can take a very long time because there are 65K floating point divisions required. But most audio will be one of a dozen sample rates, so it is possible to precalculate the rates on the host and use a small lookup table to avoid any math at all. Removes occasional scratching in PWMAudio when the BackgroundAudio library changes sample rates.
1 parent 5fb5e16 commit d732ac8

File tree

3 files changed

+132
-0
lines changed

3 files changed

+132
-0
lines changed

libraries/PWMAudio/src/PWMAudio.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
*/
2121
#include <Arduino.h>
2222
#include "PWMAudio.h"
23+
#include "PWMAudioPrecalc.h"
2324
#include <hardware/pwm.h>
2425

2526

@@ -281,6 +282,19 @@ void PWMAudio::find_pacer_fraction(int target, uint16_t *numerator, uint16_t *de
281282
return;
282283
}
283284

285+
// See if it's one of the precalculated values
286+
for (size_t i = 0; i < sizeof(__PWMAudio_pacer) / sizeof(__PWMAudio_pacer[0]); i++) {
287+
if (target == (int)__PWMAudio_pacer[i].freq) {
288+
last_target = target;
289+
bestNum = __PWMAudio_pacer[i].n;
290+
bestDenom = __PWMAudio_pacer[i].d;
291+
*numerator = bestNum;
292+
*denominator = bestDenom;
293+
return;
294+
}
295+
}
296+
297+
// Nope, do exhaustive search. This is gonna be slooooow
284298
float targetRatio = (float)F_CPU / target;
285299
float lowestError = HUGE_VALF;
286300

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// Generated by tools/makepacer.cpp, do not edit
2+
typedef struct {
3+
uint32_t freq;
4+
uint16_t n;
5+
uint16_t d;
6+
} PWMPacerPrecalc;
7+
#if F_CPU == 50000000
8+
static const PWMPacerPrecalc __PWMAudio_pacer[] = {{8000, 6250, 1}, {11025, 31746, 7}, {16000, 3125, 1}, {22050, 15873, 7}, {32000, 3125, 2}, {44100, 53288, 47}, {48000, 3125, 3}, {88200, 42517, 75}, {96000, 3125, 6}, {176400, 55839, 197}, {192000, 3125, 12}};
9+
#elif F_CPU == 100000000
10+
static const PWMPacerPrecalc __PWMAudio_pacer[] = {{8000, 12500, 1}, {11025, 63492, 7}, {16000, 6250, 1}, {22050, 31746, 7}, {32000, 3125, 1}, {44100, 15873, 7}, {48000, 6250, 3}, {88200, 53288, 47}, {96000, 3125, 3}, {176400, 42517, 75}, {192000, 3125, 6}};
11+
#elif F_CPU == 120000000
12+
static const PWMPacerPrecalc __PWMAudio_pacer[] = {{8000, 15000, 1}, {11025, 32653, 3}, {16000, 7500, 1}, {22050, 59864, 11}, {32000, 3750, 1}, {44100, 62585, 23}, {48000, 2500, 1}, {88200, 62585, 46}, {96000, 1250, 1}, {176400, 62585, 92}, {192000, 625, 1}};
13+
#elif F_CPU == 125000000
14+
static const PWMPacerPrecalc __PWMAudio_pacer[] = {{8000, 15625, 1}, {11025, 56689, 5}, {16000, 15625, 2}, {22050, 62358, 11}, {32000, 15625, 4}, {44100, 42517, 15}, {48000, 15625, 6}, {88200, 42517, 30}, {96000, 15625, 12}, {176400, 42517, 60}, {192000, 15625, 24}};
15+
#elif F_CPU == 128000000
16+
static const PWMPacerPrecalc __PWMAudio_pacer[] = {{8000, 16000, 1}, {11025, 11610, 1}, {16000, 8000, 1}, {22050, 5805, 1}, {32000, 4000, 1}, {44100, 5805, 2}, {48000, 8000, 3}, {88200, 5805, 4}, {96000, 4000, 3}, {176400, 61678, 85}, {192000, 2000, 3}};
17+
#elif F_CPU == 133000000
18+
static const PWMPacerPrecalc __PWMAudio_pacer[] = {{8000, 16625, 1}, {11025, 24127, 2}, {16000, 16625, 2}, {22050, 24127, 4}, {32000, 16625, 4}, {44100, 24127, 8}, {48000, 16625, 6}, {88200, 24127, 16}, {96000, 16625, 12}, {176400, 47500, 63}, {192000, 16625, 24}};
19+
#elif F_CPU == 150000000
20+
static const PWMPacerPrecalc __PWMAudio_pacer[] = {{8000, 18750, 1}, {11025, 27211, 2}, {16000, 9375, 1}, {22050, 47619, 7}, {32000, 9375, 2}, {44100, 37415, 11}, {48000, 3125, 1}, {88200, 42517, 25}, {96000, 3125, 2}, {176400, 42517, 50}, {192000, 3125, 4}};
21+
#elif F_CPU == 175000000
22+
static const PWMPacerPrecalc __PWMAudio_pacer[] = {{8000, 21875, 1}, {11025, 15873, 1}, {16000, 21875, 2}, {22050, 15873, 2}, {32000, 21875, 4}, {44100, 15873, 4}, {48000, 21875, 6}, {88200, 15873, 8}, {96000, 21875, 12}, {176400, 62500, 63}, {192000, 21875, 24}};
23+
#elif F_CPU == 200000000
24+
static const PWMPacerPrecalc __PWMAudio_pacer[] = {{8000, 25000, 1}, {11025, 54422, 3}, {16000, 12500, 1}, {22050, 63492, 7}, {32000, 6250, 1}, {44100, 31746, 7}, {48000, 12500, 3}, {88200, 15873, 7}, {96000, 6250, 3}, {176400, 53288, 47}, {192000, 3125, 3}};
25+
#elif F_CPU == 225000000
26+
static const PWMPacerPrecalc __PWMAudio_pacer[] = {{8000, 28125, 1}, {11025, 20408, 1}, {16000, 28125, 2}, {22050, 10204, 1}, {32000, 28125, 4}, {44100, 5102, 1}, {48000, 9375, 2}, {88200, 63776, 25}, {96000, 9375, 4}, {176400, 62500, 49}, {192000, 9375, 8}};
27+
#elif F_CPU == 240000000
28+
static const PWMPacerPrecalc __PWMAudio_pacer[] = {{8000, 30000, 1}, {11025, 65306, 3}, {16000, 15000, 1}, {22050, 32653, 3}, {32000, 7500, 1}, {44100, 59864, 11}, {48000, 5000, 1}, {88200, 62585, 23}, {96000, 2500, 1}, {176400, 62585, 46}, {192000, 1250, 1}};
29+
#elif F_CPU == 250000000
30+
static const PWMPacerPrecalc __PWMAudio_pacer[] = {{8000, 31250, 1}, {11025, 45351, 2}, {16000, 15625, 1}, {22050, 56689, 5}, {32000, 15625, 2}, {44100, 62358, 11}, {48000, 15625, 3}, {88200, 42517, 15}, {96000, 15625, 6}, {176400, 42517, 30}, {192000, 15625, 12}};
31+
#elif F_CPU == 275000000
32+
static const PWMPacerPrecalc __PWMAudio_pacer[] = {{8000, 34375, 1}, {11025, 49887, 2}, {16000, 34375, 2}, {22050, 37415, 3}, {32000, 34375, 4}, {44100, 37415, 6}, {48000, 34375, 6}, {88200, 37415, 12}, {96000, 34375, 12}, {176400, 35856, 23}, {192000, 34375, 24}};
33+
#elif F_CPU == 300000000
34+
static const PWMPacerPrecalc __PWMAudio_pacer[] = {{8000, 37500, 1}, {11025, 27211, 1}, {16000, 18750, 1}, {22050, 27211, 2}, {32000, 9375, 1}, {44100, 47619, 7}, {48000, 6250, 1}, {88200, 37415, 11}, {96000, 3125, 1}, {176400, 42517, 25}, {192000, 3125, 2}};
35+
#else
36+
const PWMPacerPrecalc __PWMAudio_pacer[] = {{1, 1, 1}}; // Invalid, should never match
37+
#endif

tools/makepacer.cpp

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
// Generates a header with precalculated best dividers for PWMAudio at
2+
// standard clock frequencies and audio rates.
3+
//
4+
5+
#include <stdint.h>
6+
#include <stdio.h>
7+
#include <math.h>
8+
9+
void find_pacer_fraction(int F_CPU, int target, uint16_t *numerator, uint16_t *denominator) {
10+
const uint16_t max = 0xFFFF;
11+
12+
/*Cache last results so we dont have to recalculate*/
13+
static int last_target;
14+
static uint16_t bestNum;
15+
static uint16_t bestDenom;
16+
/*Check if we can load the previous values*/
17+
if (target == last_target) {
18+
*numerator = bestNum;
19+
*denominator = bestDenom;
20+
return;
21+
}
22+
23+
float targetRatio = (float)F_CPU / target;
24+
float lowestError = 10000000;
25+
26+
for (uint16_t denom = 1; denom < max; denom++) {
27+
uint16_t num = (int)((targetRatio * denom) + 0.5f); /*Calculate numerator, rounding to nearest integer*/
28+
29+
/*Check if numerator is within bounds*/
30+
if (num > 0 && num < max) {
31+
float actualRatio = (float)num / denom;
32+
float error = fabsf(actualRatio - targetRatio);
33+
34+
if (error < lowestError) {
35+
bestNum = num;
36+
bestDenom = denom;
37+
lowestError = error;
38+
if (error == 0) {
39+
break;
40+
}
41+
}
42+
}
43+
}
44+
45+
last_target = target;
46+
*numerator = bestNum;
47+
*denominator = bestDenom;
48+
}
49+
50+
int main(int argc, char **argv) {
51+
(void) argc;
52+
(void) argv;
53+
int M = 1000000;
54+
int fsys[] = {50*M, 100*M, 120*M, 125*M, 128*M, 133*M, 150*M, 175*M, 200*M, 225*M, 240*M, 250*M, 275*M, 300*M};
55+
int freq[] = {8000, 11025, 16000, 22050, 32000, 44100, 48000, 88200, 96000, 176400, 192000};
56+
FILE *f = fopen("../libraries/PWMAudio/src/PWMAudioPrecalc.h", "w");
57+
fprintf(f, "// Generated by tools/makepacer.cpp, do not edit\n");
58+
fprintf(f, "typedef struct {\n");
59+
fprintf(f, " uint32_t freq;\n");
60+
fprintf(f, " uint16_t n;\n");
61+
fprintf(f, " uint16_t d;\n");
62+
fprintf(f, "} PWMPacerPrecalc;\n");
63+
for (int i = 0; i < sizeof(fsys)/sizeof(fsys[0]); i++) {
64+
fprintf(f, "#%s F_CPU == %d\n", i == 0 ? "if" : "elif", fsys[i]);
65+
fprintf(f, "static const PWMPacerPrecalc __PWMAudio_pacer[] = {");
66+
for (int j = 0; j < sizeof(freq)/sizeof(freq[0]); j++) {
67+
uint16_t n, d;
68+
find_pacer_fraction(fsys[i], freq[j], &n, &d);
69+
fprintf(f, "{%d, %d, %d}", freq[j], n, d);
70+
if (j < sizeof(freq)/sizeof(freq[0]) - 1) {
71+
fprintf(f, ", ");
72+
}
73+
}
74+
fprintf(f, "};\n");
75+
}
76+
fprintf(f, "#else\n");
77+
fprintf(f, "const PWMPacerPrecalc __PWMAudio_pacer[] = {{1, 1, 1}}; // Invalid, should never match\n");
78+
fprintf(f, "#endif\n");
79+
fclose(f);
80+
}
81+

0 commit comments

Comments
 (0)