Forum Rule: Always post complete source code & details to reproduce any issue!

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

1. ## 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. 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. An example...

4. When all the inputs are compile-time constants, the compiler is amazingly good at optimizing away code, even large nested loops.

5. 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. 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)

7. oOPS... that works great for 44100 or 22050 or other vlaues..but not for 44117 !"§\$%&/
Should have tested it with 44117..

8. @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.

9. 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.

10. 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. Originally Posted by Frank B

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
•