If people need another sampling frequency than the usual 44.117 kHz. I wrote a little function, that allows for other frequencies.
Eg. 8, 11.025, 12, 16, 22.05, 24, 32, 44.1, 48 kHz or 96 kHz, or anything in between.
It does not have to be used in conjunction with the Audio stuff. The MCLK pin can just be used standalone as another clock generator.
It should work for all CPU frequencies > 16 MHz.
Although outside of spec, generating the master clock for 96 kHz is no problem for my device.
As an example, With a CPU clock of 96 MHz
Desired: 12345 Hz
Reality: 12345.02 Hz
Delta: 0.02 Hz
Since the Audiostream objects assume a fixed frequency of 44117, to set the frequency of e.g. a sine generator, compensate by the frequency ration between the 44117 and the sample clock, as shown in the example.
Edit: Only tested it on two of my Teensy 3.1 devices. Do not know about Teensy 3.0 or even LC.
Eg. 8, 11.025, 12, 16, 22.05, 24, 32, 44.1, 48 kHz or 96 kHz, or anything in between.
It does not have to be used in conjunction with the Audio stuff. The MCLK pin can just be used standalone as another clock generator.
It should work for all CPU frequencies > 16 MHz.
Although outside of spec, generating the master clock for 96 kHz is no problem for my device.
As an example, With a CPU clock of 96 MHz
Desired: 12345 Hz
Reality: 12345.02 Hz
Delta: 0.02 Hz
Since the Audiostream objects assume a fixed frequency of 44117, to set the frequency of e.g. a sine generator, compensate by the frequency ration between the 44117 and the sample clock, as shown in the example.
Edit: Only tested it on two of my Teensy 3.1 devices. Do not know about Teensy 3.0 or even LC.
Code:
/* This code is free to use */
/* Kire Pudsje, Feb. 2015 */
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
AudioControlSGTL5000 sgtl5000;
AudioSynthWaveformSine sine;
AudioOutputI2S i2sout;
AudioConnection pc(sine, i2sout);
// function prototype
bool setI2SmasterClock(uint32_t freq, uint32_t mult = 256);
float getI2SmasterClock(uint32_t mult = 256);
#define FSAMPLE 12345
void setup() {
Serial.begin(9600);
AudioMemory(12);
setI2SmasterClock(FSAMPLE);
sgtl5000.enable();
// AudioStream objects assume a given sample rate of 44117 Hz, correct for this.
sine.frequency(1e3 * AUDIO_SAMPLE_RATE_EXACT / FSAMPLE);
sine.amplitude(1);
}
void loop() {
Serial.print("Desired: ");
Serial.print(FSAMPLE);
Serial.print(" Hz, Reality: ");
Serial.print(getI2SmasterClock());
Serial.print(" Hz, Delta: ");
Serial.print(getI2SmasterClock() - FSAMPLE);
Serial.println(" Hz");
}
/* Generate I2S master clock
The MCLK output frequencies should be in the range from F_pll/4096 to 12.5 MHz.
For convenience the frequency has been split into a sample frequency and
a sample clock multiplication factor.
For direct control of the master clock frequency, set the multiplier to 1.
For almost all combinations of sample clocks (8, 11.025, 12, 16, 22.05, 24,
32, 44.1 and 48 kHz) and cpu clock frequencies it will find a ratio, that
generates the exact frequency. For the other few, the output frequency
lies within 0.5 Hz distance from the desired frequency.
freq: sample frequency in Hz
mult: sample clock multiplication factor, (default 256)
return value: true if the frequency is within the valid range
*/
bool setI2SmasterClock(uint32_t freq, uint32_t mult)
{
// determine fractional ratio for: p / q = freq * mult / f_pll
// f_pll = 16 MHz * ((MCG_C6 & 0x1f) + 24) / ((MCG_C5 & 0x1f) + 1)
uint32_t p = freq * mult * ((MCG_C5 & 0x1f) + 1);
uint32_t q = 16000000ul * ((MCG_C6 & 0x1f) + 24);
uint32_t fract = 0, divide = 1; // I2Sx_MDR register values
uint32_t fract1, fract2 = 1; // values of iterations n-1 and n-2.
uint32_t divide1, divide2 = 0; // idem.
uint8_t src = 3; // clock source selection: 3 = pll
// Master clock should be slower than reference clock
if (p > q)
return false;
else if (p == q)
fract = divide = 1;
else {
// Approximate p/q by a continued fraction expansion.
// (typically takes only 4 to 8 iterations)
while (p) {
// find next element of continued fraction
uint32_t a = q / p;
uint32_t oldp = p;
p = q - a * p;
q = oldp;
// update fract/divide by Wallis method
fract1 = fract;
divide1 = divide;
fract = fract1 * a + fract2;
divide = divide1 * a + divide2;
fract2 = fract1;
divide2 = divide1;
// Exit loop if fraction gets too big for the registers
if (fract > 256 || divide > 4096) {
// Revert back to last approximation that still fitted
fract = fract2;
divide = divide2;
break;
}
}
// Check if frequency too low or too high
if (!fract)
return false;
}
// Actually set registers and enable MCLK.
SIM_SCGC6 |= SIM_SCGC6_I2S;
I2S0_MDR = I2S_MDR_FRACT((fract - 1)) | I2S_MDR_DIVIDE((divide - 1));
I2S0_MCR = I2S_MCR_MICS(src) | I2S_MCR_MOE;
// 'Connect' MCLK output to an actual output pin
CORE_PIN11_CONFIG = PORT_PCR_MUX(6);
//CORE_PIN28_CONFIG = PORT_PCR_MUX(4); // Pin on bottom
return true;
}
float getI2SmasterClock(uint32_t mult)
{
float fpll = 16.0e6f * ((MCG_C6 & 0x1f) + 24) / ((MCG_C5 & 0x1f) + 1);
uint32_t fract = ((I2S0_MDR >> 12) & 0xff) + 1;
uint32_t divide = (I2S0_MDR & 0xfff) + 1;
return fpll * fract / (divide * mult);
}
Last edited: