fdivitto / FabGL

ESP32 Display Controller (VGA, PAL/NTSC Color Composite, SSD1306, ST7789, ILI9341), PS/2 Mouse and Keyboard Controller, Graphics Library, Sound Engine, Game Engine and ANSI/VT Terminal

Home Page:http://www.fabglib.org

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

simplify APLLCalcParams function

kov-serg opened this issue · comments

void APLLCalcParams(double freq, APLLParams * params, uint8_t * a, uint8_t * b, double * out_freq, double * error)

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.