Change sample rate for Teensy 4 (vs Teensy 3)

Status
Not open for further replies.

chipaudette

Well-known member
Hi All,

Previously, I made an extension of the audio library to enable float32 audio processing for the Teensy 3.5/3.6 (see Tympan library here). With Teensy 4, a bunch of stuff has changed, which I'm working through.

One troublesome area that I've hit is how to enable the user (me!) to change the sample rate of the audio system. Does anyone have suggestions on how to change the I2S bus rates for Teensy 4?

For Teensy 3, I used the many helpful posts from @FrankB, including:

https://forum.pjrc.com/threads/3875...he-sample-rate?p=121365&viewfull=1#post121365

and

https://forum.pjrc.com/threads/3875...he-sample-rate?p=188812&viewfull=1#post188812

I shamelessly copied the examples above (with very modest additions) and turned it into the code below (again, for Teensy 3):

Code:
float AudioOutputI2S_F32::setI2SFreq(const float freq_Hz) {
#if defined(KINETISK)
	int freq = (int)freq_Hz;
  typedef struct {
    uint8_t mult;
    uint16_t div;
  } __attribute__((__packed__)) tmclk;

  const int numfreqs = 16;
  const int samplefreqs[numfreqs] = { 2000, 8000, 11025, 16000, 22050, 24000, 32000, 44100, (int)44117.64706 , 48000, 88200, (int)(44117.64706 * 2), 96000, 176400, (int)(44117.64706 * 4), 192000};

#if (F_PLL==16000000)
  const tmclk clkArr[numfreqs] = {{4, 125}, {16, 125}, {148, 839}, {32, 125}, {145, 411}, {48, 125}, {64, 125}, {151, 214}, {12, 17}, {96, 125}, {151, 107}, {24, 17}, {192, 125}, {127, 45}, {48, 17}, {255, 83} };
#elif (F_PLL==72000000)
  const tmclk clkArr[numfreqs] = {{832, 1125}, {32, 1125}, {49, 1250}, {64, 1125}, {49, 625}, {32, 375}, {128, 1125}, {98, 625}, {8, 51}, {64, 375}, {196, 625}, {16, 51}, {128, 375}, {249, 397}, {32, 51}, {185, 271} };
#elif (F_PLL==96000000)
  const tmclk clkArr[numfreqs] = {{2, 375},{8, 375}, {73, 2483}, {16, 375}, {147, 2500}, {8, 125},  {32, 375}, {147, 1250}, {2, 17}, {16, 125}, {147, 625}, {4, 17}, {32, 125}, {151, 321}, {8, 17}, {64, 125} };
#elif (F_PLL==120000000)
  const tmclk clkArr[numfreqs] = {{8, 1875},{32, 1875}, {89, 3784}, {64, 1875}, {147, 3125}, {32, 625},  {128, 1875}, {205, 2179}, {8, 85}, {64, 625}, {89, 473}, {16, 85}, {128, 625}, {178, 473}, {32, 85}, {145, 354} };
#elif (F_PLL==144000000)
  const tmclk clkArr[numfreqs] = {{4, 1125},{16, 1125}, {49, 2500}, {32, 1125}, {49, 1250}, {16, 375},  {64, 1125}, {49, 625}, {4, 51}, {32, 375}, {98, 625}, {8, 51}, {64, 375}, {196, 625}, {16, 51}, {128, 375} };
#elif (F_PLL==180000000)
  const tmclk clkArr[numfreqs] = {{23, 8086}, {46, 4043}, {49, 3125}, {73, 3208}, {98, 3125}, {37, 1084},  {183, 4021}, {196, 3125}, {16, 255}, {128, 1875}, {107, 853}, {32, 255}, {219, 1604}, {214, 853}, {64, 255}, {219, 802} };
#elif (F_PLL==192000000)
  const tmclk clkArr[numfreqs] = {{1, 375}, {4, 375}, {37, 2517}, {8, 375}, {73, 2483}, {4, 125}, {16, 375}, {147, 2500}, {1, 17}, {8, 125}, {147, 1250}, {2, 17}, {16, 125}, {147, 625}, {4, 17}, {32, 125} };
#elif (F_PLL==216000000)
  const tmclk clkArr[numfreqs] = {{8, 3375}, {32, 3375}, {49, 3750}, {64, 3375}, {49, 1875}, {32, 1125},  {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] = {{4, 1875}, {16, 1875}, {29, 2466}, {32, 1875}, {89, 3784}, {16, 625}, {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 (float)(F_PLL / 256 * clkArr[f].mult / clkArr[f].div);
    }
  }
#endif
  return 0.0f;
}

Looking at Teensy 4, I'm not even quite sure where to begin. I looks like the Teensy 4 setup has some important differences...for example, the Teensy 4 compatible AudioOutputI2S class uses a function called set_audioClock(). If I want to change the sample rate, do I only need to call this function, or is this an unrelated function?

Any guidance would be greatly appreciated!

Chip
 
This works for me on T4B2. It's a newer version from Frank B.

Pete
P.S. I've only used it for 12000Hz but it should be good for other rates.

Code:
#include <Audio.h>
#include <utility/imxrt_hw.h>
  
void setI2SFreq(int freq) {
  // 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 
//Serial.printf("SetI2SFreq(%d)\n",freq);
}
 
The basic difference is that the T3.x has a different way of generating the I2S master clock: as seen in post #1, the single T3 I2S has its own fractional divider to generate the MCLK, while the three SAIs in the T4 can each have a different clock source or use a common one. The preferred (and configured by the audio lib) clock source is PLL4, so the code in #2 might well be used to set it.
 
Pete and FrankB, you saved my evening! :)

Thanks a lot for the code, works fine for the Teensy audio board and T4!
 
Presumably, for this function: void setI2SFreq(int freq), the "freq" is the sample rate (in Hz) that I want?

So, if I want 44100 (ie, the default), I would call setI2SFreq(44100)? But, if I wanted 48 kHz, I would call setI2SFreq(48000)?

Do I have it right?

Many thanks!

Chip
 
This works for me on T4B2. It's a newer version from Frank B.

Pete
P.S. I've only used it for 12000Hz but it should be good for other rates.

Code:
#include <Audio.h>
#include <utility/imxrt_hw.h>
 
void setI2SFreq(int freq) {
  // 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
//Serial.printf("SetI2SFreq(%d)\n",freq);
}
This works for me on T4B2. It's a newer version from Frank B.

Pete
P.S. I've only used it for 12000Hz but it should be good for other rates.

Code:
#include <Audio.h>
#include <utility/imxrt_hw.h>
 
void setI2SFreq(int freq) {
  // 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
//Serial.printf("SetI2SFreq(%d)\n",freq);
}
Hello, I think I have a similar question. I am using Digital MEMS Mic SPH0641LU4H-1 (https://www.elecrow.com/digital-mems-microphone.html) to apply PdM in bearings. I am using Arduino Framework and, according to the datasheet of the microphone, it is capable of "hearing" a range between 100Hz to 80kHz. To make it operate in ultrasonic mode, I need to change the clock to 3.072 MHz ≤ fCLOCK ≤ 4.8 MHz. I have done this code, but the FFT is not detecting any frequencies, even in the first bins. It works for 96000hz, but to 176400hz does not detect anything. Can anyone help?

#include <Audio.h>
#include "setI2SFreq.h"

// GUItool: begin automatically generated code
AudioInputPDM pdm1; //xy=180,111
AudioFilterStateVariable filter1; //xy=325,101
AudioOutputI2S i2s1; //I2S audio output
AudioAmplifier amp1; //xy=470,93
AudioAnalyzeFFT1024 fft1024_1; //xy=616,102
AudioConnection patchCord1(pdm1, 0, filter1, 0);
AudioConnection patchCord2(filter1, 2, amp1, 0);
AudioConnection patchCord3(amp1, fft1024_1);
// GUItool: end automatically generated code
void setup() {
AudioMemory(50);
filter1.frequency(30); // filter out DC & extremely low frequencies
amp1.gain(8.5); // amplify sign to useful range
setI2SFreq(192000);
}

void loop() {
if (fft1024_1.available()) {
// each time new FFT data is available
// print 20 bins to the Arduino Serial Monitor
//512 bins, 187.5 hz each bin
Serial.print(millis());
Serial.print("FFT: ");
for (int i = 0; i < 20; i++) {
float n = fft1024_1.read(i);
if (n >= 0.001) {
Serial.print(n, 3);
Serial.print(" ");
} else {
Serial.print(" -- "); // don't print "0.000"
}
}
Serial.println();
}
}
 
Hello, I think I have a similar question. I am using Digital MEMS Mic SPH0641LU4H-1 (https://www.elecrow.com/digital-mems-microphone.html) to apply PdM in bearings. I am using Arduino Framework and, according to the datasheet of the microphone, it is capable of "hearing" a range between 100Hz to 80kHz. To make it operate in ultrasonic mode, I need to change the clock to 3.072 MHz ≤ fCLOCK ≤ 4.8 MHz. I have done this code, but the FFT is not detecting any frequencies, even in the first bins. It works for 96000hz, but to 176400hz does not detect anything. Can anyone help?

@SamLoureiro: You've posted essentially the same question in three different places (this post, <here>, and <here>). Please avoid doing that in the future as it only clutters the forum, and will not likely result in any faster response. One post is sufficient. Unfortunately, I don't personally have any experience with MEMS microphones, but someone else may chime in who has more experience to offer.

Mark J Culross
KD5RXT
 
Status
Not open for further replies.
Back
Top