asynchronous board synching

Status
Not open for further replies.
He wants to add decimals != zero
i.e. 44100.3456789

I don't how how that can help (Edit: Even if he changes the code not to use an int). Esp. with the temperature sesivitiy of crystals. two crystals.
And how exactly he transfers all the fast i2s signals over the air.
synchronously.

all too magically for me :) I'm out.

have fun :) i hope you can get it to work. pls. report back!
 
I see Frank's point. Not sure what I did wrong when I tested it and got a better match between my PC and teensy rate. Perhaps a misleading random change in one of the clocks.

Looks like it would be easy to change the code to use the decimal portion.
 
It *can* work, if you just don't send I2s over the air.
Maybe raw data. With sync signals added. Maybe WLAN. Or, best, bluetooth. Edit: something that is fast enough and involves error-correction.
Even then, changing the #define does'nt work and something more dynamic, and buffering will be needed.
So, reading the manual makes sense, too, to find a way to keep the dropouts minimal when changing the pll. maybe it works without? I did not read the manual from this point of view
, and don't plan to, and I can not answer this.
 
Last edited:
The problem is, as Pauls said, you can't modify that at runtime, as the PLL needs to be restarted. This will cause short breaks.

Has anyone tried anyway? Does the PLL actually ignore writes to its registers while it's turned on, or is that just what we're not supposed to do?

Or can the change turning the PLL off and back on again be done so quickly that the short breaks are imperceptible to human ears?
 
No, - had no reason to try it :)

Maybe damo1976 can tell us wether it works or not, as he works on that anyway.
I'd be interested to hear the result.
 
Has anyone tried anyway? Does the PLL actually ignore writes to its registers while it's turned on, or is that just what we're not supposed to do?

Or can the change turning the PLL off and back on again be done so quickly that the short breaks are imperceptible to human ears?

Technically I did that when testing SGTL5000 at different sampling speeds
I first configured the I2S to AUDIO_SAMPLE_RATE_EXACT (44100 Hz) and then I changed the sampling frequency.
Code:
 void I2S_start(void)
 {
  I2S1_RCSR |= (I2S_RCSR_RE | I2S_RCSR_BCE); 
  I2S1_TCSR |= (I2S_TCSR_TE | I2S_TCSR_BCE); 
 }
 void I2S_stop(void)
 {
   // stop I2S 
  I2S1_RCSR &= ~(I2S_RCSR_RE | I2S_RCSR_BCE); 
  I2S1_TCSR &= ~(I2S_TCSR_TE | I2S_TCSR_BCE); 
 }

 void setAudioFrequency(int fs)
 {
   	// PLL between 27*24 = 648MHz und 54*24=1296MHz
	int n1 = 4; //SAI prescaler 4 => (n1*n2) = multiple of 4
	int n2 = 1 + (24000000 * 27) / (fs * 256 * n1);

	double C = ((double)fs * 256 * n1 * n2) / 24000000;
	int c0 = C;
	int c2 = 10000;
	int c1 = C * c2 - (c0 * c2);
	set_audioClock(c0, c1, c2, 1);

  	// clear SAI1_CLK register locations
	CCM_CSCMR1 = (CCM_CSCMR1 & ~(CCM_CSCMR1_SAI1_CLK_SEL_MASK))
		   | CCM_CSCMR1_SAI1_CLK_SEL(2); // &0x03 // (0,1,2): PLL3PFD0, PLL5, PLL4
	CCM_CS1CDR = (CCM_CS1CDR & ~(CCM_CS1CDR_SAI1_CLK_PRED_MASK | CCM_CS1CDR_SAI1_CLK_PODF_MASK))
		   | CCM_CS1CDR_SAI1_CLK_PRED(n1-1) // &0x07
		   | CCM_CS1CDR_SAI1_CLK_PODF(n2-1); // &0x3f
	// Select MCLK
	IOMUXC_GPR_GPR1 = (IOMUXC_GPR_GPR1
		& ~(IOMUXC_GPR_GPR1_SAI1_MCLK1_SEL_MASK))
		| (IOMUXC_GPR_GPR1_SAI1_MCLK_DIR | IOMUXC_GPR_GPR1_SAI1_MCLK1_SEL(0));

 }

 void I2S_modification(uint32_t fsamp, uint16_t nbits) 
{
  I2S_stop();
  // modify sampling frequency 
  CCM_CCGR5 |= CCM_CCGR5_SAI1(CCM_CCGR_ON);

  int fs = fsamp;
  setAudioFrequency(fs);

  // restart I2S 
  I2S_start();
}

No, I did not listen to audio, as this code is executed during setup after standard I2S configuration and I only use ADC
 
Code:
void setI2SFreq(int freq) {
#if defined(T4)
  // PLL between 27*24 = 648MHz und 54*24=1296MHz
  int n1 = 4; //SAI prescaler 4 => (n1*n2) = multiple of 4
  int n2 = 1 + (24000000 * 27) / (freq * 256 * n1);
  double C = ((double)freq * 256 * n1 * n2) / 24000000;
  int c0 = C;
  int c2 = 10000;
  int c1 = C * c2 - (c0 * c2);
  set_audioClock(c0, c1, c2, true);
  CCM_CS1CDR = (CCM_CS1CDR & ~(CCM_CS1CDR_SAI1_CLK_PRED_MASK | CCM_CS1CDR_SAI1_CLK_PODF_MASK))
       | CCM_CS1CDR_SAI1_CLK_PRED(n1-1) // &0x07
       | CCM_CS1CDR_SAI1_CLK_PODF(n2-1); // &0x3f 
#else

  unsigned tcr5 = I2S0_TCR5;
  unsigned word0width = ((tcr5 >> 24) & 0x1f) + 1;
  unsigned wordnwidth = ((tcr5 >> 16) & 0x1f) + 1;
  unsigned framesize = ((I2S0_TCR4 >> 16) & 0x0f) + 1;
  unsigned nbits = word0width + wordnwidth * (framesize - 1 );
  unsigned tcr2div = I2S0_TCR2 & 0xff; //bitclockdiv
  uint32_t MDR = I2S_dividers(freq, nbits, tcr2div);
  if (MDR > 0) {
    while (I2S0_MCR & I2S_MCR_DUF) {
      ;
    }
    I2S0_MDR = MDR;
  }

////////////////////////////////////////////////////////////////////////////////  
/*  typedef struct {
    uint8_t mult;
    uint16_t div;
  } tmclk;

  const int numfreqs = 20;
  //  const int samplefreqs[numfreqs] = { 8000, 11025, 16000, 22050, 32000, 44100, (int)44117.64706 , 48000, 88200, (int)44117.64706 * 2, 96000, 100000, 176400, (int)44117.64706 * 4, 192000};
  const int samplefreqs[numfreqs] = { 8000, 11025, 16000, 22050, 32000, 44100, (int)44117.64706 , 48000, 50223, 88200, (int)44117.64706 * 2,
                                      96000, 100000, 100466, 176400, (int)44117.64706 * 4, 192000, 234375, 281000, 352800
                                    };

#if (F_PLL==180000000)
  //{ 8000, 11025, 16000, 22050, 32000, 44100, (int)44117.64706 , 48000,
  const tmclk clkArr[numfreqs] = {{46, 4043}, {49, 3125}, {73, 3208}, {98, 3125}, {183, 4021}, {196, 3125}, {16, 255}, {128, 1875},
    // 50223, 88200, (int)44117.64706 * 2, 96000, 100466, 176400, (int)44117.64706 * 4, 192000, 234375, 281000, 352800};
    {1, 14}, {107, 853}, {32, 255}, {219, 1604}, {224, 1575}, {1, 7}, {214, 853}, {64, 255}, {219, 802}, {1, 3}, {2, 5} , {1, 2}
  };
/*#elif (F_PLL==192000000)
  const tmclk clkArr[numfreqs] = {{4, 375}, {37, 2517}, {8, 375}, {73, 2483}, {16, 375}, {147, 2500}, {1, 17}, {8, 125},
                              // { 8000,        11025,    16000,    22050,      32000,      44100,    44117.64706 , 48000,
                                  {147, 1250}, {2, 17}, {16, 125}, {147, 625}, {4, 17}, {32, 125} };
                              //     88200, 44117.64706 * 2, 96000, 176400, 44117.64706 * 4, 192000};
#elif (F_PLL==216000000)
  const tmclk clkArr[numfreqs] = {{32, 3375}, {49, 3750}, {64, 3375}, {49, 1875}, {128, 3375}, {98, 1875}, {8, 153}, {64, 1125},
                                  {196, 1875}, {16, 153}, {128, 1125}, {226, 1081}, {32, 153}, {147, 646} };
#elif (F_PLL==240000000)
  const tmclk clkArr[numfreqs] = {{16, 1875}, {29, 2466}, {32, 1875}, {89, 3784}, {64, 1875}, {147, 3125}, {4, 85}, {32, 625},
                                  {205, 2179}, {8, 85}, {64, 625}, {89, 473}, {16, 85}, {128, 625} };
                                  */
/*#endif


  for (int f = 0; f < numfreqs; f++) {
    if ( freq == samplefreqs[f] ) {
      while (I2S0_MCR & I2S_MCR_DUF) ;
      I2S0_MDR = I2S_MDR_FRACT((clkArr[f].mult - 1)) | I2S_MDR_DIVIDE((clkArr[f].div - 1));
      return;
    }
  }
////////////////////////////////////////////////////////////////////////////////////
*/

  
#endif

Just taken from dd4wh's SDR project.
 
NXP lied to us!! You can change the PLL while it's running!

Ok, I tried it just now, using this:

Code:
#include <Audio.h>

AudioSynthWaveform       waveform1;      //xy=171,84
AudioOutputI2S           i2s1;           //xy=360,98
AudioConnection          patchCord1(waveform1, 0, i2s1, 0);
AudioConnection          patchCord3(waveform1, 0, i2s1, 1);
AudioControlSGTL5000     sgtl5000_1;     //xy=239,232

uint32_t num, denom;

void setup() {
  AudioMemory(10);

  sgtl5000_1.enable();
  sgtl5000_1.volume(0.8); // caution: very loud - use oscilloscope only!

  waveform1.frequency(440);
  waveform1.amplitude(1.0);
  waveform1.begin(WAVEFORM_SINE);

  while (!Serial) ; // wait for Arduino Serial Monitor
  num = CCM_ANALOG_PLL_AUDIO_NUM;
  denom = CCM_ANALOG_PLL_AUDIO_DENOM;
  Serial.printf("PLL: num=%u, denom=%u\n", num, denom);
}

void loop() {
  CCM_ANALOG_PLL_AUDIO_NUM = num;
  delay(1000);
  CCM_ANALOG_PLL_AUDIO_NUM = num + 4000;
  delay(1000);
}

Here's what my oscilloscope sees on the LRCLK pin. The sample rate does change from 44100 to 44725, just be writing to CCM_ANALOG_PLL_AUDIO_NUM.

 
Nice! Kudos for not trusting the manual.

Add something like PTP and you can get sync, even over wireless.
 
I did some tests to check for how fine we can adjust the frequency. My scope's frequency counter isn't sensitive enough, so I had to connect it to my real frequency counter (BK Precision 1823A) and change the delays to 5 seconds.

Using the settings the audio library configures, changing the numerator by 1 alters the LRCLK frequency by 0.157 Hz.

I tried mutliplying both numerator and denominator by 1000, since the audio library is using only such a tiny fraction of the numerical range. I could not measure the change in frequency by changing the numerator by only 1 step. Changing the numerator by 20 was smallest increment I could measure, and even then it's a change approximately as much as the short term fluctuations.

This is the smallest sort of effect I could see. Indeed the frequency can be adjusted in extremely fine increments with small changes in the numerator when a larger denominator is used.

DSC_1115_web.jpg

DSC_1123_web.jpg

I want to emphasize the frequency naturally changes almost this much every few readings, and over longer time is drifts much more. This really is the smallest change which can be reliably observed (at least with my test gear) with quite a lot of careful observation to pick apart changes due to the code versus the nature amount of noise / variation.

This is the code I ran for that test.

Code:
#include <Audio.h>

AudioSynthWaveform       waveform1;      //xy=171,84
AudioOutputI2S           i2s1;           //xy=360,98
AudioConnection          patchCord1(waveform1, 0, i2s1, 0);
AudioConnection          patchCord3(waveform1, 0, i2s1, 1);
AudioControlSGTL5000     sgtl5000_1;     //xy=239,232

uint32_t num, denom;

void setup() {
  pinMode(13, OUTPUT);
  AudioMemory(10);

  sgtl5000_1.enable();
  sgtl5000_1.volume(0.8); // caution: very loud - use oscilloscope only!

  waveform1.frequency(440);
  waveform1.amplitude(1.0);
  waveform1.begin(WAVEFORM_SINE);

  while (!Serial) ; // wait for Arduino Serial Monitor
  num = CCM_ANALOG_PLL_AUDIO_NUM;
  denom = CCM_ANALOG_PLL_AUDIO_DENOM;
  Serial.printf("PLL: num=%u, denom=%u\n", num, denom);

  num = num * 1000;
  denom = denom * 1000;
  num = num + 3302; // tweak it up to close to 44100 (according to my frequency counter)
  CCM_ANALOG_PLL_AUDIO_NUM = num;
  CCM_ANALOG_PLL_AUDIO_DENOM = denom;
}

void loop() {
  digitalWrite(13, LOW);
  CCM_ANALOG_PLL_AUDIO_NUM = num;
  delay(10000);
  digitalWrite(13, HIGH);
  CCM_ANALOG_PLL_AUDIO_NUM = num + 20;
  delay(10000);
}
 
Last edited:
There are no audible artifacts?

I did a quick listening test. Sine waves aren't nice for listening.

On the first test, changing the numerator from 2240 to 6240 while the denominator is 10000, indeed there is a strange noise that seems like some sort of artifact rather than just the sine wave changing frequency.

So maybe NXP was right, maybe we're not meant to alter the PLL. More testing and listening needed....

I'm going to stop fiddling / abusing PLL4. I want to get back to fixing some bugs with the serial monitor, in prep for beta6. I might also play with adding larger flash chips support in LittleFS today. Hopefully you & others can explore what realistically can & can't be done by tweaking PLL4.
 
I toggled CCM_ANALOG_PLL_AUDIO_NUM between 2240 and 2241 every second with no audible effect. This is with SPDIF output to an external DAC.

For most use cases, only small changes are needed - like < 50 ppm. I assume that what I did is a large 446 ppm change. Reducing this with a larger denominator makes sense. Maybe also with dithering.

I haven't measured yet. Previously I would compare, over a longish period, the PC USB clock to what the teensy was using for audio rate. But as I now know, this is flawed :). But not 446 ppm flawed.
 
Sounds good. Change the clock setting code to use a large denominator and floating point. Then anyone wanting to tweak rate can a) change AUDIO_SAMPLE_RATE_EXACT at compile time or b) change CCM_ANALOG_PLL_AUDIO_NUM during run-time.

Not having a frequency counter, I might have the PC (over longish periods, conditioned to 0.000 ppm of GPS time via NTP) send something via serial/USB to the teensy for comparison.
 
Brilliant. I'll try that myself. But I did notice that changing AUDIO_SAMPLE_RATE_EXACT to something like 48000.1 had an effect but 48000.05 did not so... I'm still trying to work out how that floating point is used in the audio code when the clock function strips those decimals..
 
I'd like to suggest to add a function _setI2Sfreq(double or float freq) to utility/imxrt_hw.cpp.
Then, we can remove all the calculations from all the I2S-using codes and just call the new function: _setI2Sfreq(AUDIO_SAMPLE_RATE_EXACT). It's centralized then, and can be used by the user, too.
 
There is no magic going on .. decimals are ignored :)
They are just not needed here, because it was intended for std sampling-freqs.
 
Just a wild guess.. maybe 1073739000 (0x3FFF F4F8) is a good value. It's a multiple of 24 and 1000, thus allows 0%error for all std frequencies and maybe the PLL can lock a little bit faster due to the multiple of 24(?!)
Does this make any sense? I have no Idea about the inner workings of digital PLLs...
 
NXP doesn't tell us how they implemented the PLL, but it's a pretty safe bet the phase comparator is a digital circuit like a R-S latch, which drives an analog amplifier / accumulator, which controls an analog oscillator. The feedback path is almost certainly digital counters. How they implement a 30 bit resolution ratio is a trick I would like to understand someday...

It would take extra design work for lock detection, which is probably built into the phase comparator, to block writes to the registers. The tools used to design these sort of modern chips automate very complex design when everything is synchronous to the same clock domain, but things are often not so easy when asynchronous signals have to be synchronized. That's why I've long suspected their documentation about not being able to write to the registers while it's running was just a usage guideline rather than a hard technical restriction.
 
Status
Not open for further replies.
Back
Top