Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

simplify APLLCalcParams function #380

Open
kov-serg opened this issue Mar 25, 2024 · 0 comments
Open

simplify APLLCalcParams function #380

kov-serg opened this issue Mar 25, 2024 · 0 comments

Comments

@kov-serg
Copy link

kov-serg commented Mar 25, 2024

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant