Adding 12kHz sample rate to setI2SFreq

Status
Not open for further replies.

el_supremo

Well-known member
I've been working on a program which plays audio at a sampling rate of 12kHz and I just couldn't get it to work. Then I had a look at the code for setI2SFreq and (finally) realized that it doesn't even have an entry for 12kHz - no wonder nothing happened! :). After a lot of high-powered thinking and hammering away on my calculator, I figured out how to calculate the {mult,div} entries for 12kHz at F_CPU=180MHz. It worked very well, so I then worked out the {mult,div} for the rest of the F_CPU entries and added them to the code. This was rather tedious work so I wrote a sketch which helps with the calculations and I have now also checked every mult,div entry for CPU frequencies of 180, 192 and 240MHz. Some of the entries in the existing code could be replaced by exact values and others that I found were closer than the existing table entry.
For example, at 180MHz the entry {62, 1977} for 22050Hz will give a frequency of 22050.455Hz which is really close. However, changing the entry to {98, 3125} will give exactly 22050.
I have also changed the setI2SFreq function so that it returns an integer value indicating whether or not it succeeded. It returns zero if it found the specified frequency in the table and it had a valid {mult,div} entry (mult was not zero) which allowed the requested sampling rate to be set. Otherwise it returns 1 if the mult entry was zero (which I used when I hadn't yet calculated a {mult,div} for an entry), and it returns 2 if it couldn't find the specified frequency.

I've also added the setDACFreq function - it works well at 12kHz.

The code is below. It contains my version of the {mult,div} table with some new entries as mentioned above. I have left the original {mult,div} table in the code, inside an #ifdef/#endif, so that if necessary it will be easy to revert to it for testing.

Although I figured out how the {mult,div} entries must be calculated, I couldn't figure out WHY it was that way, and reading the reference manual didn't help.
If anyone knows or has a reference, I'd appreciate an explanation of how these numbers are actually derived.
From perusing the tables in setI2SFreq, the way I've got it sort-of figured out is that for a given CPU frequency in MHz (FCPU), there is a constant
Code:
C = 3906.25 * FCPU
Then, for a given sampling frequency of srate (Hz), you must find integers div and mult such that
Code:
div/mult = C/srate
and having found such integers, they give the {mult,div} entry for that sampling rate and FCPU.
This would be a real pain to do in general, but the problem is simplified somewhat because the multiplier can only have a value from 1 to 256 (i.e. it is an 8-bit quantity). The div quantity is 12 bits and therefore can't exceed 4096. These limitations make life a bit easier when writing a program to calculate values for {mult,div} because only 256 values of mult need be tried and values for div greater than 4096 can be ignored.

Pete

setI2SFreq.cpp (also has setDACFreq)
Code:
#include <Arduino.h>

// See: #5 in https://forum.pjrc.com/threads/38753
// From FrankB - brilliant!
int setI2SFreq(int freq)
{
  typedef struct {
    uint8_t mult;
    uint16_t div;
  } __attribute__((__packed__)) tmclk;


// 12000Hz sampling rate added by Pete (El_Supremo)
  const int samplefreqs[] = { 8000, 11025, 12000, 16000, 22050, 32000, 44100, 44117 , 48000 };

#ifdef USE_ORIGINAL_TABLE
#if (F_PLL==16000000)
  const tmclk clkArr[] = {{16, 125}, {148, 839}, {48, 250}, {32, 125}, {145, 411}, {64, 125}, {151, 214}, {12, 17}, {96, 125} };
#elif (F_PLL==72000000)
  const tmclk clkArr[] = {{32, 1125}, {49, 1250}, {64, 1500}, {64, 1125}, {49, 625}, {128, 1125}, {98, 625}, {8, 51}, {64, 375} };
#elif (F_PLL==96000000)
  const tmclk clkArr[] = {{8, 375}, {60, 2041}, {64, 2000}, {16, 375}, {120, 2041}, {32, 375}, {147, 1250}, {2, 17}, {16, 125} };
#elif (F_PLL==120000000)
  const tmclk clkArr[] = {{32, 1875}, {29, 1233}, {64, 2500}, {64, 1875}, {89, 1892}, {128, 1875}, {89, 946}, {8, 85}, {64, 625} };
#elif (F_PLL==144000000)
  const tmclk clkArr[] = {{16, 1125}, {40, 2041}, {64, 3000}, {32, 1125}, {49, 1250}, {64, 1125}, {49, 625}, {4, 51}, {32, 375} };
#elif (F_PLL==180000000)
// {64, 3750} will also work for 12000Hz
// {49, 3125} is exact for 11025
  const tmclk clkArr[] = {{9, 791}, {31, 1977}, {32, 1875}, {37, 1626}, {62, 1977}, {73, 1604}, {107, 1706}, {16, 255}, {128, 1875} };
#elif (F_PLL==192000000)
  const tmclk clkArr[] = {{4, 375}, {30, 2041}, {20, 1250}, {8, 375}, {60, 2041}, {16, 375}, {120, 2041}, {1, 17}, {8, 125} };
#elif (F_PLL==216000000)
  const tmclk clkArr[] = {{17, 1793}, {17, 1301}, {16, 1125}, {34, 1793}, {49, 1875}, {49, 1292}, {98, 1875}, {8, 153}, {64, 1125} };
#elif (F_PLL==240000000)
  const tmclk clkArr[] = {{16, 1875}, {24, 2041}, {16, 1250}, {32, 1875}, {29, 1233}, {64, 1875}, {89, 1892}, {4, 85}, {32, 625} };
#endif
#else
// Some values are changed by Pete (El_Supremo) to give
// either an exact sampling rate or a closer one
#if (F_PLL==16000000)
  const tmclk clkArr[] = {{16, 125}, {148, 839}, {48, 250}, {32, 125}, {145, 411}, {64, 125}, {151, 214}, {12, 17}, {96, 125} };
#elif (F_PLL==72000000)
  const tmclk clkArr[] = {{32, 1125}, {49, 1250}, {64, 1500}, {64, 1125}, {49, 625}, {128, 1125}, {98, 625}, {8, 51}, {64, 375} };
#elif (F_PLL==96000000)
  const tmclk clkArr[] = {{8, 375}, {60, 2041}, {64, 2000}, {16, 375}, {120, 2041}, {32, 375}, {147, 1250}, {2, 17}, {16, 125} };
#elif (F_PLL==120000000)
  const tmclk clkArr[] = {{32, 1875}, {29, 1233}, {64, 2500}, {64, 1875}, {89, 1892}, {128, 1875}, {89, 946}, {8, 85}, {64, 625} };
#elif (F_PLL==144000000)
  const tmclk clkArr[] = {{16, 1125}, {40, 2041}, {64, 3000}, {32, 1125}, {49, 1250}, {64, 1125}, {49, 625}, {4, 51}, {32, 375} };
#elif (F_PLL==180000000)
// {49, 3125} is exact for 11025
// {64, 3750} will also work for 12000Hz
// {73, 3208} is a closer fit for 16000
// {98, 3125} is exact for 22050
// {196, 3125} is exact for 44100
  const tmclk clkArr[] = {{9, 791}, {49, 3125}, {32, 1875}, {73, 3208}, {98, 3125}, {73, 1604}, {196, 3125}, {16, 255}, {128, 1875} };
#elif (F_PLL==192000000)

  const tmclk clkArr[] = {{4, 375}, {37, 2517}, {20, 1250}, {8, 375}, {74, 2517}, {16, 375}, {147,2500}, {1, 17}, {8, 125} };
#elif (F_PLL==216000000)
  const tmclk clkArr[] = {{17, 1793}, {17, 1301}, {16, 1125}, {34, 1793}, {49, 1875}, {49, 1292}, {98, 1875}, {8, 153}, {64, 1125} };
#elif (F_PLL==240000000)
// {30, 2551} is best for 11025
// {89, 3784} is best for 22050
// {147,3125} is exact for 44100
  const tmclk clkArr[] = {{16, 1875}, {30, 2551}, {16, 1250}, {32, 1875}, {89, 3784}, {64, 1875}, {147,3125}, {4, 85}, {32, 625} };
#endif
#endif

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


// See: #10 in https://forum.pjrc.com/threads/38753
// See also: https://forum.pjrc.com/threads/45610
void setDACFreq(int freq)
{
const unsigned config = PDB_SC_TRGSEL(15) | PDB_SC_PDBEN | PDB_SC_CONT | PDB_SC_PDBIE | PDB_SC_DMAEN;
    PDB0_SC = 0;
    PDB0_IDLY = 1;
    PDB0_MOD = round((float)F_BUS / freq ) - 1;    
    PDB0_SC = config | PDB_SC_LDOK;
    PDB0_SC = config | PDB_SC_SWTRIG;
    PDB0_CH0C1 = 0x0101;    
}

sgtl_mult.ino code to aid in choosing {mult,div} pairs.
Code:
// Figure out multipliers for different sampling rates
// by Pete (El_Supremo)

// Sample rates
uint32_t srate[] = { 8000, 11025, 12000, 16000, 22050, 32000, 44100, 44117 , 48000};

void setup(void)
{
  Serial.begin(9600);
  while(!Serial);
  delay(1000);

/*
For a reason I have yet to determine, the
constant is related to the cpu frequency
in MHz (FCPU) by the equation:
Constant = FCPU*3906.25
*/

//>>> Choose a CPU frequency
  int FCPU = 180;
  float Constant = 3906.25 * FCPU;
  Serial.printf("Table of mult/div for %dMHz",FCPU);

  for(int j = 0; j < sizeof(srate)/sizeof(srate[0]); j++) {
    float epsilon = 1;
    int i_epsilon;
    Serial.printf("\nsrate[%d] = %d\n",j,srate[j]);
    // Generate entries for every value of mult
    // from 1 to 256
    for(int i = 1; i <= 256; i++) {
      // Find a pair of integers, Div and mult (i=1,256),
      // such that Div/mult = Constant/Sampling_rate
      // Div = Constant/sample_rate
      float Div = (Constant/srate[j])*i;
      if(Div > 4096)break;
      float rem = Div - (int)Div;
      if(rem < epsilon) {
        epsilon = rem;
        i_epsilon = i;
      }
      Serial.printf("%3d  %10.3f %8.6f",i,Div,rem);
      // Indicate an exact result
      if(rem == 0)Serial.println(" ***");
      // or indicate the closest one
      else if(rem < .001)Serial.println(" < ");
      else Serial.println();
    }
    if(epsilon > 0.0) {
      Serial.printf("epsilon = %8.6f at %d\n\n",epsilon,i_epsilon);
    }
  }
}

void loop(void)
{
}
 
Status
Not open for further replies.
Back
Top