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.
This commit is contained in:
parent
5fb5e16be8
commit
d732ac82ba
3 changed files with 132 additions and 0 deletions
|
|
@ -20,6 +20,7 @@
|
|||
*/
|
||||
#include <Arduino.h>
|
||||
#include "PWMAudio.h"
|
||||
#include "PWMAudioPrecalc.h"
|
||||
#include <hardware/pwm.h>
|
||||
|
||||
|
||||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
37
libraries/PWMAudio/src/PWMAudioPrecalc.h
Normal file
37
libraries/PWMAudio/src/PWMAudioPrecalc.h
Normal file
|
|
@ -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
tools/makepacer.cpp
Normal file
81
tools/makepacer.cpp
Normal file
|
|
@ -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);
|
||||
}
|
||||
|
||||
Loading…
Reference in a new issue