Forum Rule: Always post complete source code & details to reproduce any issue!
Results 1 to 11 of 11

Thread: Fastest way to calculate I2S FRACT & DIVIDE values?

  1. #1
    Senior Member+ Frank B's Avatar
    Join Date
    Apr 2014
    Location
    Germany NRW
    Posts
    4,546

    Fastest way to calculate I2S FRACT & DIVIDE values?

    What is the fastest way to calculate I2S FRACT & DIVIDE values for a given F_PLL and Samplefreq?

    For a new project, I need to calculate them "live", at runtime.

    Edit:
    For debugging, i needed the opposite way.
    Perhaps it's useful for others, when fiddling with I2S - this codes calculates the samplefrequency from the I2S settings (works with all existing I2s outputs form the audiolib):
    Code:
      if ((SIM_SCGC6 & SIM_SCGC6_I2S == SIM_SCGC6_I2S)) {
        Serial.println("I2S enabled");
        int fract = (I2S0_MDR >> 12) & 0xff;
        int divide = I2S0_MDR & 0xfff;
        int src = (I2S0_MCR >> 24) & 0x03; 
        double mclk = ((src == 3)? F_PLL : F_BUS) * (double)(fract + 1) / (double)(divide + 1);    
        int bitclockdiv = I2S0_TCR2 & 0xff;    
        int framesize = ((I2S0_TCR4 >> 16) & 0x0f) + 1;
        int word0width = ((I2S0_TCR5 >> 24) & 0x1f) + 1;
        int wordnwidth = ((I2S0_TCR5 >> 16) & 0x1f) + 1;
        double samplerate = (mclk /  ((bitclockdiv + 1) * 2)) /  (word0width + wordnwidth * (framesize - 1 ));
    #if 1    
        Serial.print("MCLK: ");
        Serial.println(mclk);    
        Serial.print("FRAMESIZE: ");
        Serial.println(framesize);
        Serial.print("WORDWIDTH: ");
        Serial.println(word0width);
        Serial.println(wordnwidth);
        Serial.print("FRAMELENGTH: ");
        Serial.println(  (word0width + wordnwidth * (framesize - 1 )));
        Serial.print("SAMPLERATE: ");
        Serial.println( samplerate, 4 );
    #endif    
      }
    .. and now i need a fast way for the other direction

  2. #2
    Senior Member+ Theremingenieur's Avatar
    Join Date
    Feb 2014
    Location
    Colmar, France
    Posts
    1,908
    I’m actually thinking about the same question. In theory, I’d decompose F_PLL or F_BUS on the left side and the desired system clock (samplefreq x bits/frame x 2) on the right side into prime factors and eliminate as much as possible common factors on both, so that I’d get the smallest possible values on both sides which would then be the multiplicator right and the divider left.

  3. #3
    Senior Member+ Theremingenieur's Avatar
    Join Date
    Feb 2014
    Location
    Colmar, France
    Posts
    1,908
    Click image for larger version. 

Name:	45911198-13C3-4CDE-86B0-2DC926D4675F.jpg 
Views:	12 
Size:	145.2 KB 
ID:	14847
    An example...
    Last edited by Theremingenieur; 09-28-2018 at 07:02 AM.

  4. #4
    Senior Member PaulStoffregen's Avatar
    Join Date
    Nov 2012
    Posts
    18,367
    When all the inputs are compile-time constants, the compiler is amazingly good at optimizing away code, even large nested loops.

  5. #5
    Senior Member
    Join Date
    Jul 2014
    Posts
    1,878
    The code I'm using in microSoundRecorder (https://github.com/WMXZ-EU/microSoun...r/audio_mods.h) is
    Code:
    // attempt to generate dividers programmatically 
    128  // always better to check 
    129  void I2S_dividers(uint32_t *iscl, uint32_t fsamp, uint32_t nbits) 
    130  { // nbits is number of bits / frame 
    131      int64_t i1 = 1; 
    132      int64_t i2 = 1; 
    133      int64_t i3 = iscl[2]+1; 
    134      float A=F_CPU/2.0f/i3/((float)nbits*fsamp); 
    135      float mn=1.0;  
    136      for(int ii=1;ii<=32;ii++)  
    137      { float xx; 
    138        xx=A*ii-(int32_t)(A*ii); 
    139        if(xx<mn && A*ii<256.0) { mn=xx; i1=ii; i2=A*ii;} //select first candidate 
    140      } 
    141      iscl[0] = (int) (i1-1); 
    142      iscl[1] = (int) (i2-1); 
    143      iscl[2] = (int) (i3-1); 
    144  }
    it is called like this
    Code:
    void I2S_modification(uint32_t fsamp, uint16_t nbits, int nch) 
    157  { uint32_t iscl[3]; 
    158  
     
    159    if(nch==8) 
    160      iscl[2]=0; 
    161    else 
    162      iscl[2]=1; 
    163     
    164    I2S_dividers(iscl, fsamp ,nch*nbits); 
    165    float fs = (F_CPU * (iscl[0]+1.0f)) / (iscl[1]+1l) / 2 / (iscl[2]+1l) / ((float)nch*nbits); 
    166  #if DO_DEBUG>0 
    167    Serial.printf("%d %d %d %d %d %d %d\n\r", 
    168                  F_CPU, fsamp, (int)fs, nbits,iscl[0]+1,iscl[1]+1,iscl[2]+1); 
    169  #endif 
    170    // stop I2S 
    171    I2S0_RCSR &= ~(I2S_RCSR_RE | I2S_RCSR_BCE); 
    172  
     
    173    // modify sampling frequency 
    174    I2S0_MDR = I2S_MDR_FRACT(iscl[0]) | I2S_MDR_DIVIDE(iscl[1]); 
    175  
     
    176    // configure transmitter 
    177    I2S0_TCR2 = I2S_TCR2_SYNC(0) | I2S_TCR2_BCP | I2S_TCR2_MSEL(1) 
    178      | I2S_TCR2_BCD | I2S_TCR2_DIV(iscl[2]); 
    179  //  I2S0_TCR4 = I2S_TCR4_FRSZ(nch-1) | I2S_TCR4_SYWD(0) | I2S_TCR4_MF 
    180  //    | I2S_TCR4_FSE | I2S_TCR4_FSD; 
    181  
     
    182    // configure receiver (sync'd to transmitter clocks) 
    183    I2S0_RCR2 = I2S_RCR2_SYNC(1) | I2S_TCR2_BCP | I2S_RCR2_MSEL(1) 
    184      | I2S_RCR2_BCD | I2S_RCR2_DIV(iscl[2]); 
    185  //  I2S0_RCR4 = I2S_RCR4_FRSZ(nch-1) | I2S_RCR4_SYWD(0) | I2S_RCR4_MF 
    186  //    | I2S_RCR4_FSE | I2S_RCR4_FSD; 
    187  
     
    188    //restart I2S 
    189    I2S0_RCSR |= I2S_RCSR_RE | I2S_RCSR_BCE; 
    190  }
    which modifies standard output_i2s.cpp setup

    caveat: the selection of iscl[2] used in I2S_RCR2_DIV is not arbitrary and may depend on if MCLK is use or not!
    that is why I use it as input.

  6. #6
    Senior Member+ Frank B's Avatar
    Join Date
    Apr 2014
    Location
    Germany NRW
    Posts
    4,546
    Thank you both!
    @WMXZ, your code returns wrong numbers for certain values. Some lead to slightly off frequencies.
    @Theremingenieur, your idea works - I wrote some code for it:
    Code:
    void I2S_dividers2( int fsamp, int nbits, int TCR_DIV2)
    { // nbits is number of bits / frame
      const uint8_t primes[] = {2, 3, 5, 7, 11, 13};
    
      unsigned x = FCPU;
      unsigned y = fsamp * nbits * (TCR_DIV2 + 1) * 2;
      unsigned ip = 0;
      unsigned p;
      unsigned divi = 1;
      unsigned mult = 1;
      do {
        p = primes[ip];
        unsigned xx = 0;
        while (x % p == 0) {
            x /= p;
            xx++;
          }
        unsigned yy = 0;
        while (y % p == 0 ) {
            y /= p;
            yy++;
          }
        if (xx > 0 || yy > 0) {
          int z = min(xx, yy);
          divi *= powf(p, xx - z);
          mult *= powf(p, yy - z);
          //   Serial.printf("L:%d^%d=%d  R:%d^%d=%d\n", p,xx-z, (int)powf(p,xx-z), p, yy-z, (int)powf(p,yy-z) );
        }
        ip++;
      } while ((x > 1 || y > 1) && (ip < sizeof(primes)));
    
      Serial.printf("\nMULT: %d DIV: %d\n", mult, divi);
    }
    (call with I2S_dividers2(44100, 32, 3) for example)

    Perhaps there are ways to optimize it more, but it is fast enough - ~0.15ms on a T3.2/98MHz
    (edit: if someone looks for a very fast integer pow(): https://gist.github.com/orlp/3551590)
    Last edited by Frank B; 10-04-2018 at 07:03 PM.

  7. #7
    Senior Member+ Frank B's Avatar
    Join Date
    Apr 2014
    Location
    Germany NRW
    Posts
    4,546
    oOPS... that works great for 44100 or 22050 or other vlaues..but not for 44117 !"$%&/
    Should have tested it with 44117..

  8. #8
    Senior Member+ Frank B's Avatar
    Join Date
    Apr 2014
    Location
    Germany NRW
    Posts
    4,546
    @Theremingenieur: We have to take into account that MULT is 1..256 (-1) and DIV is 1..4096 (-1), and there may be no exact solution.

    edit:
    MCLK Fraction
    Sets the MCLK divide ratio such that: MCLK output = MCLK input * ( (FRACT + 1) / (DIVIDE + 1) ).
    FRACT must be set equal or less than the value in the DIVIDE field.
    Last edited by Frank B; 10-04-2018 at 08:43 PM.

  9. #9
    Senior Member+ Theremingenieur's Avatar
    Join Date
    Feb 2014
    Location
    Colmar, France
    Posts
    1,908
    I did not explicitly mention the need to subtract 1 on both, multiplier and divider, because it seemed self-understanding to me. Using primes from 2 to 13 as you do, will only allow for perfect factorising 3 significants up to 169. 44117 has 5 significants, so you would have to check all primes up to sqrt(44117). But I see that 44117 is 157 x 281 and thus has no common factors with F_BUS or F_PLL, so no algorithm would be suitable to find integer multipliers and dividers.
    But wasn’t your effort about eliminating that ugly non-standard sampling frequency???

  10. #10
    Senior Member+ Frank B's Avatar
    Join Date
    Apr 2014
    Location
    Germany NRW
    Posts
    4,546
    Yes, the -1 was self-understanding. No Problem with that
    No, I have values for std frequencies (see https://forum.pjrc.com/threads/38753...l=1#post120899) - calculated "the hard way" with slow nested loops.
    So - there is an algorithm, and values exist, but the one i used to calculate the values is slow.
    I don't have problems with the "ugly" frequency - it's ok for most cases. My goal here was to adjust the freq. as smooth as possible at runtime. For constants, I don't care how fast that is - but not at runtime, for any freq...

    I've fixed WMXZ's function now, that works for me

  11. #11
    Senior Member
    Join Date
    Jul 2014
    Posts
    1,878
    Quote Originally Posted by Frank B View Post

    I've fixed WMXZ's function now, that works for me
    thanks Frank,
    (for the interested, I had limited the range of possible multiplier and dividers to maybe un-necessary low values, consequently algorithm accepted values that resulted in sometimes ugly sampling frequencies)

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •