Skip to content

Commit

Permalink
Precalculate common PWMAudio dividers, avoid noise
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
earlephilhower committed Dec 22, 2024
1 parent 5fb5e16 commit 79596ec
Show file tree
Hide file tree
Showing 3 changed files with 132 additions and 0 deletions.
14 changes: 14 additions & 0 deletions libraries/PWMAudio/src/PWMAudio.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
*/
#include <Arduino.h>
#include "PWMAudio.h"
#include "PWMAudioPrecalc.h"
#include <hardware/pwm.h>


Expand Down Expand Up @@ -281,6 +282,19 @@ void PWMAudio::find_pacer_fraction(int target, uint16_t *numerator, uint16_t *de
return;
}

// See if it's one of the precalculated values
for (size_t i = 0; i < sizeof(__PWMAudio_pacer) / sizeof(__PWMAudio_pacer[0]); i++) {
if (target == (int)__PWMAudio_pacer[i].freq) {
last_target = target;
bestNum = __PWMAudio_pacer[i].n;
bestDenom = __PWMAudio_pacer[i].d;
*numerator = bestNum;
*denominator = bestDenom;
return;
}
}

// Nope, do exhaustive search. This is gonna be slooooow
float targetRatio = (float)F_CPU / target;
float lowestError = HUGE_VALF;

Expand Down
37 changes: 37 additions & 0 deletions libraries/PWMAudio/src/PWMAudioPrecalc.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Generated by tools/makepacer.cpp, do not edit
typedef struct {
uint32_t freq;
uint16_t n;
uint16_t d;
} PWMPacerPrecalc;
#if F_CPU == 50000000
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}};
#elif F_CPU == 100000000
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}};
#elif F_CPU == 120000000
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}};
#elif F_CPU == 125000000
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}};
#elif F_CPU == 128000000
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}};
#elif F_CPU == 133000000
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}};
#elif F_CPU == 150000000
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}};
#elif F_CPU == 175000000
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}};
#elif F_CPU == 200000000
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}};
#elif F_CPU == 225000000
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}};
#elif F_CPU == 240000000
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}};
#elif F_CPU == 250000000
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}};
#elif F_CPU == 275000000
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}};
#elif F_CPU == 300000000
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}};
#else
const PWMPacerPrecalc __PWMAudio_pacer[] = {{1, 1, 1}}; // Invalid, should never match
#endif
81 changes: 81 additions & 0 deletions tools/makepacer.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// Generates a header with precalculated best dividers for PWMAudio at
// standard clock frequencies and audio rates.
//

#include <stdint.h>
#include <stdio.h>
#include <math.h>

void find_pacer_fraction(int F_CPU, int target, uint16_t *numerator, uint16_t *denominator) {
const uint16_t max = 0xFFFF;

/*Cache last results so we dont have to recalculate*/
static int last_target;
static uint16_t bestNum;
static uint16_t bestDenom;
/*Check if we can load the previous values*/
if (target == last_target) {
*numerator = bestNum;
*denominator = bestDenom;
return;
}

float targetRatio = (float)F_CPU / target;
float lowestError = 10000000;

for (uint16_t denom = 1; denom < max; denom++) {
uint16_t num = (int)((targetRatio * denom) + 0.5f); /*Calculate numerator, rounding to nearest integer*/

/*Check if numerator is within bounds*/
if (num > 0 && num < max) {
float actualRatio = (float)num / denom;
float error = fabsf(actualRatio - targetRatio);

if (error < lowestError) {
bestNum = num;
bestDenom = denom;
lowestError = error;
if (error == 0) {
break;
}
}
}
}

last_target = target;
*numerator = bestNum;
*denominator = bestDenom;
}

int main(int argc, char **argv) {
(void) argc;
(void) argv;
int M = 1000000;
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};
int freq[] = {8000, 11025, 16000, 22050, 32000, 44100, 48000, 88200, 96000, 176400, 192000};
FILE *f = fopen("../libraries/PWMAudio/src/PWMAudioPrecalc.h", "w");
fprintf(f, "// Generated by tools/makepacer.cpp, do not edit\n");
fprintf(f, "typedef struct {\n");
fprintf(f, " uint32_t freq;\n");
fprintf(f, " uint16_t n;\n");
fprintf(f, " uint16_t d;\n");
fprintf(f, "} PWMPacerPrecalc;\n");
for (int i = 0; i < sizeof(fsys)/sizeof(fsys[0]); i++) {
fprintf(f, "#%s F_CPU == %d\n", i == 0 ? "if" : "elif", fsys[i]);
fprintf(f, "static const PWMPacerPrecalc __PWMAudio_pacer[] = {");
for (int j = 0; j < sizeof(freq)/sizeof(freq[0]); j++) {
uint16_t n, d;
find_pacer_fraction(fsys[i], freq[j], &n, &d);
fprintf(f, "{%d, %d, %d}", freq[j], n, d);
if (j < sizeof(freq)/sizeof(freq[0]) - 1) {
fprintf(f, ", ");
}
}
fprintf(f, "};\n");
}
fprintf(f, "#else\n");
fprintf(f, "const PWMPacerPrecalc __PWMAudio_pacer[] = {{1, 1, 1}}; // Invalid, should never match\n");
fprintf(f, "#endif\n");
fclose(f);
}

0 comments on commit 79596ec

Please sign in to comment.