simplify APLLCalcParams function
kov-serg opened this issue · comments
kov-serg commented
Line 1753 in 1ad2713
I offer to simplify function APLLCalcParams to reduce computation time.
Here is example of possible alternative:
output:
f=25.175000MHz
APLLCalcParams.steps=160
sdm0=235 sdm1= 17 sdm2= 6 o_div= 2 a= 1 b= 0 of=25.174980MHz err= 19.8 ppm=-0.79
sdm0=236 sdm1= 17 sdm2= 6 o_div= 2 of=25.175018MHz err= 18.0 ppm= 0.71
code:
#include <stdio.h>
#include <math.h>
//---------------------------------------------------------------------------
#define FABGLIB_XTAL 40000000
#define FABGLIB_USE_APLL_AB_COEF 0
typedef unsigned char uint8_t;
//typedef long long int64_t;
struct APLLParams {
uint8_t sdm0;
uint8_t sdm1;
uint8_t sdm2;
uint8_t o_div;
};
template <typename T> const T & tmax(const T & a, const T & b) { return (a < b) ? b : a; }
template <typename T> const T & tmin(const T & a, const T & b) { return !(b < a) ? a : b; }
template <typename T> const T & tclamp(const T & v, const T & lo, const T & hi) { return (v < lo ? lo : (v > hi ? hi : v)); }
void floatToFraction(double value, int maxDen, int * num, int * den)
{
int64_t a, h[3] = { 0, 1, 0 }, k[3] = { 1, 0, 0 };
int64_t x, d, n = 1;
while (value != floor(value)) {
n <<= 1;
value *= 2;
}
d = value;
for (int i = 0; i < 64; ++i) {
a = n ? d / n : 0;
if (i && !a)
break;
x = d;
d = n;
n = x % n;
x = a;
if (k[1] * a + k[0] >= maxDen) {
x = (maxDen - k[0]) / k[1];
if (x * 2 >= a || k[1] >= maxDen)
i = 65;
else
break;
}
h[2] = x * h[1] + h[0];
h[0] = h[1];
h[1] = h[2];
k[2] = x * k[1] + k[0];
k[0] = k[1];
k[1] = k[2];
}
*den = k[1];
*num = h[1];
}
static void APLLCalcParams(double freq, APLLParams *params, uint8_t * a, uint8_t * b, double* out_freq, double* error) {
int steps=0;
double FXTAL = FABGLIB_XTAL;
*error = 999999999;
double apll_freq = freq*2;
for (int o_div=0;o_div<=31;++o_div) {
int idivisor = (2*o_div + 4);
for (int sdm2 = 4; sdm2 <= 8; ++sdm2) {
// from tables above
int minSDM1 = (sdm2 == 4 ? 192 : 0);
int maxSDM1 = (sdm2 == 8 ? 128 : 255);
// apll_freq = XTAL * (4 + sdm2 + sdm1 / 256) / divisor -> sdm1 = (apll_freq * divisor - XTAL * 4 - XTAL * sdm2) * 256 / XTAL
int startSDM1 = ((apll_freq * idivisor - FXTAL * 4.0 - FXTAL * sdm2) * 256.0 / FXTAL);
#if FABGLIB_USE_APLL_AB_COEF
for (int isdm1 = tmax(minSDM1, startSDM1); isdm1 <= maxSDM1; ++isdm1) {
#else
int isdm1 = startSDM1; {
#endif
steps++;
int sdm1 = isdm1;
sdm1 = tmax(minSDM1, sdm1);
sdm1 = tmin(maxSDM1, sdm1);
// apll_freq = XTAL * (4 + sdm2 + sdm1 / 256 + sdm0 / 65536) / divisor -> sdm0 = (apll_freq * divisor - XTAL * 4 - XTAL * sdm2 - XTAL * sdm1 / 256) * 65536 / XTAL
int sdm0 = ((apll_freq * idivisor - FXTAL * 4.0 - FXTAL * sdm2 - FXTAL * sdm1 / 256.0) * 65536.0 / FXTAL);
// from tables above
sdm0 = (sdm2 == 8 && sdm1 == 128 ? 0 : tmin(255, sdm0));
sdm0 = tmax(0, sdm0);
// dividend inside 350-500Mhz?
double dividend = FXTAL * (4.0 + sdm2 + sdm1 / 256.0 + sdm0 / 65536.0);
if (dividend >= 350000000 && dividend <= 500000000) {
// adjust output frequency using "b/a"
double oapll_freq = dividend / idivisor;
// Calculates "b/a", assuming tx_bck_div_num = 1 and clkm_div_num = 2:
// freq = apll_clk / (2 + clkm_div_b / clkm_div_a)
// abr = clkm_div_b / clkm_div_a
// freq = apll_clk / (2 + abr) => abr = apll_clk / freq - 2
uint8_t oa = 1, ob = 0;
#if FABGLIB_USE_APLL_AB_COEF
double abr = oapll_freq / freq - 2.0;
if (abr > 0 && abr < 1) {
int num, den;
floatToFraction(abr, 63, &num, &den);
ob = tclamp(num, 0, 63);
oa = tclamp(den, 0, 63);
}
#endif
// is this the best?
double ofreq = oapll_freq / (2.0 + (double)ob / oa);
double err = freq - ofreq;
if (abs(err) < abs(*error)) {
*params = (APLLParams){(uint8_t)sdm0, (uint8_t)sdm1, (uint8_t)sdm2, (uint8_t)o_div};
*a = oa;
*b = ob;
*out_freq = ofreq;
*error = err;
if (err == 0.0) goto leave;
}
}
}
}
}
leave:
printf("APLLCalcParams.steps=%d\n",steps);
}
//---------------------------------------------------------------------------
typedef struct calc_fabc_s { int sm[3],odiv,freq; } calc_fabc_t;
enum {
calc_fabc_f_min = 5645162,
calc_fabs_hole_head = 83333334,
calc_fabs_hole_tail = 87500000,
calc_fabc_f_max = 125000000
};
// fmin=5.645162MHz fmax=125MHz without range [83333334,87500000)
// f in [5645162..83333333]+[87500000..125000000] max(ppm)=0.9
void calc_fabc(unsigned f,calc_fabc_t *p) {
unsigned d,d1,d2,abc,fx,qm,q,s;
unsigned long long lq;
p->freq=0; fx=40000000;
d1=(175000000-1+f)/f; d2=250000000/f;
if (d1<2) d1=2; if (d2>31) d2=31;
for(d=d1;d<=d2;d++) {
lq=f*d; lq<<=17; abc=lq/fx; lq-=(unsigned long long)abc*fx;
q=lq; if (2*q>fx) { q=fx-q; s=1; } else s=0;
if (d==d1 || q<qm) { qm=q; abc+=s;
p->sm[2]=(abc>>16)-4; p->sm[1]=(abc>>8)&255; p->sm[0]=abc&255;
p->odiv=d-2;
q/=2*d; q+=32768; q>>=16; if (s) q+=f; else q=f-q;
p->freq=q;
}
}
}
//---------------------------------------------------------------------------
void test_apll() {
double f=25.175e6;
APLLParams prm[1]{};
calc_fabc_t p[1]{};
uint8_t a,b; double of,err;
printf("f=%.6fMHz\n",f*1e-6);
APLLCalcParams(f,prm,&a,&b,&of,&err);
printf("sdm0=%3d sdm1=%3d sdm2=%2d o_div=%2d ",
prm->sdm0,prm->sdm1,prm->sdm2,prm->o_div);
printf("a=%2d b=%2d of=%.6fMHz err=%5.1f ppm=%5.2f\n",
a,b,of*1e-6,abs(err),(of-f)/f*1e6);
int N=2; // a=1 b=0
calc_fabc(N*f,p); p->freq/=N;
printf("sdm0=%3d sdm1=%3d sdm2=%2d o_div=%2d"
" of=%.6fMHz err=%5.1f ppm=%5.2f\n",
p->sm[0],p->sm[1],p->sm[2],p->odiv,p->freq*1e-6,
abs(p->freq-f),(p->freq-f)/f*1e6);
}
//---------------------------------------------------------------------------
int main(int argc, char *argv[]) {
test_apll();
return 0;
}
ps: ESP32 has hardware floats, but double will be calculated in software. So the calculation time will be even more longer.