Fastest way to calculate I2S FRACT & DIVIDE values?

Status
Not open for further replies.

Frank B

Senior Member
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 :)
 
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.
 
45911198-13C3-4CDE-86B0-2DC926D4675F.jpg
An example...
 
Last edited:
When all the inputs are compile-time constants, the compiler is amazingly good at optimizing away code, even large nested loops.
 
The code I'm using in microSoundRecorder (https://github.com/WMXZ-EU/microSoundRecorder/blob/master/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.
 
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:
oOPS... that works great for 44100 or 22050 or other vlaues..but not for 44117 !"§$%&/
Should have tested it with 44117..
 
@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:
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???
 
Yes, the -1 was self-understanding. No Problem with that :)
No, I have values for std frequencies (see https://forum.pjrc.com/threads/3875...he-sample-rate?p=120899&viewfull=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 :)
 
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)
 
Status
Not open for further replies.
Back
Top