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.
81 lines
2.7 KiB
C++
81 lines
2.7 KiB
C++
// 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);
|
|
}
|
|
|