TDM with AK4619

Ah, sorry, you're correct, I missed the clock settings. So many registers to check :rolleyes:

It seems you are very close to getting it all working so well done and definitely get it all documented as I'm sure there are others who have been struggling with it. The AK4619 is a very good codec but darned complicated!
 
Right now it looks like I've run into a Teensy Audio bug, or I am using it wrong somehow @GuitarPhil .

I've currently got some code that outputs clean waveforms on all four output channels. The channels are set up as follows:

Code:
// GUItool: begin automatically generated code
AudioInputTDM            tdm1;           //xy=326,427
AudioOutputTDM           tdm2;           //xy=843,411
AudioSynthWaveform       waveform1;      //xy=347,179
AudioSynthWaveform       waveform2;      //xy=347,179
AudioSynthWaveform       waveform3;      //xy=347,179
AudioSynthWaveformDc     dc1;            //xy=575,252
AudioAmplifier           amp1;           //xy=540,355

AudioConnection          patchCord5(waveform1, 0, tdm2, 0);
AudioConnection          patchCord6(waveform2, 0, tdm2, 2);
AudioConnection          patchCord7(waveform3, 0, tdm2, 4);
AudioConnection          patchCord8(waveform1, 0, tdm2, 6);

MCLK timing (20kHz sample rate):
1720551804326.png


BICK Timing (20kHz sample rate):
1720551718188.png


Full data frame with scope:

1720553218857.png
(left to right, top to bottom DAC1L, DAC1R, DAC2L, DAC2R)

1720551970066.png


If I modify that code by adding the following connections, I get dropouts on DAC1 R and DAC2 L.

Code:
// GUItool: begin automatically generated code
AudioInputTDM            tdm1;           //xy=326,427
AudioOutputTDM           tdm2;           //xy=843,411
AudioSynthWaveform       waveform1;      //xy=347,179
AudioSynthWaveform       waveform2;      //xy=347,179
AudioSynthWaveform       waveform3;      //xy=347,179
AudioSynthWaveformDc     dc1;            //xy=575,252
AudioAmplifier           amp1;           //xy=540,355

AudioConnection          patchCord5(waveform1, 0, tdm2, 0);
AudioConnection          patchCord6(waveform2, 0, tdm2, 2);
AudioConnection          patchCord7(waveform3, 0, tdm2, 4);
AudioConnection          patchCord8(waveform1, 0, tdm2, 6);

AudioConnection          patchCord1(tdm1, 0, amp1, 0);
AudioConnection          patchCord2(amp1, 0, tdm2, 8);

Different configurations of connections seem to result in different strange behavior, but it's consistently this weird choppy waveform where I see zero data intermittently. Clock timing is consistent with what is displayed in the earlier screenshots, but you can see that the middle channel data is now missing from the Teensy output.

I realize I'm sending to a channel that the AK4619 should ignore- the reason is to demonstrate that the synthesized data on the prior channels seems to randomly go missing when I use the AudioInputTDM. Also the amp is set to gain 0, so it should result in all 0s on slot 8 and have no effect anyway. None of the problematic signals here are coming from the AK4619 (well, other than the choppy output, but that's explained by the TX output of the Teensy). This seems to be an issue with the Teensy, either how I'm using it or the TDM objects themselves?

1720552799807.png
(left to right, top to bottom DAC1L, DAC1R, DAC2L, DAC2R)

1720552546774.png


Here is the code I'm using:

C++:
#include <Wire.h>
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>
#include <utility/imxrt_hw.h>

class AK4619 {
public:
  TwoWire* wire;
  uint8_t addr;
  void Init(uint8_t i2cAddress=0x10) {
    Wire.begin();
    addr = i2cAddress;
  }
  uint8_t writeReg(uint8_t deviceReg, uint8_t regVal) {
    Wire.beginTransmission(addr);
    Wire.write(deviceReg);
    Wire.write(regVal);
    return(Wire.endTransmission(true));
  }

  uint8_t readReg(int8_t deviceReg, uint8_t * regVal) {
    Wire.beginTransmission(addr);
    Wire.write(deviceReg);
    Wire.endTransmission(true);
    uint8_t numbytes = 0;
    numbytes = Wire.requestFrom(addr, (uint8_t)1, (uint8_t)false);
    if((bool)numbytes){
      Wire.readBytes(regVal, numbytes);
    }
    return(Wire.endTransmission(true)); //Send STOP
  }
};
 
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);
}

// GUItool: begin automatically generated code
AudioInputTDM            tdm1;           //xy=326,427
AudioOutputTDM           tdm2;           //xy=843,411
AudioSynthWaveform       waveform1;      //xy=347,179
AudioSynthWaveform       waveform2;      //xy=347,179
AudioSynthWaveform       waveform3;      //xy=347,179
AudioSynthWaveformDc     dc1;            //xy=575,252
AudioAmplifier           amp1;           //xy=540,355

AudioConnection          patchCord5(waveform1, 0, tdm2, 0);
AudioConnection          patchCord6(waveform2, 0, tdm2, 2);
AudioConnection          patchCord7(waveform3, 0, tdm2, 4);
AudioConnection          patchCord8(waveform1, 0, tdm2, 6);

// -------------------------------------------------------
// UNCOMMENT ME TO CAUSE ISSUES WITH DAC1R and DAC2L!!!! -
// -------------------------------------------------------
// AudioConnection          patchCord1(tdm1, 0, amp1, 0);
// AudioConnection          patchCord2(amp1, 0, tdm2, 8);

AK4619 codec;

uint8_t regval = 0;
uint8_t error = 0;
int count = 0;

void setup() {

  Serial.begin(9600);

  Serial.println("hi");

  AudioMemory(20);

  setI2SFreq(20000);

  Serial.println("begin");

  // AK4619VN startup sequence
  pinMode(41, OUTPUT);
  digitalWrite(41, LOW);
  delay(200);
  digitalWrite(41, HIGH);
  delay(200);
  codec.Init();
  delay(200);

  //if(codec.writeReg(0x00, 0b00110111)) Serial.println("Error."); // set last but repeated here for address consistency
  if(codec.writeReg(0x01, 0b11111110)) Serial.println("Error."); // set AK4619 regs
  if(codec.writeReg(0x02, 0b00011100)) Serial.println("Error.");
  if(codec.writeReg(0x03, 0b00000011)) Serial.println("Error.");
  if(codec.writeReg(0x04, 0b00100010)) Serial.println("Error.");
  if(codec.writeReg(0x05, 0b00100010)) Serial.println("Error.");
  if(codec.writeReg(0x06, 0b00110000)) Serial.println("Error.");
  if(codec.writeReg(0x07, 0b00110000)) Serial.println("Error.");
  if(codec.writeReg(0x08, 0b00110000)) Serial.println("Error.");
  if(codec.writeReg(0x09, 0b00110000)) Serial.println("Error.");
  if(codec.writeReg(0x0A, 0b00000000)) Serial.println("Error.");
  if(codec.writeReg(0x0B, 0b01010101)) Serial.println("Error.");
  //if(codec.writeReg(0x0C, 0b00000000)) Serial.println("Error."); //reserved
  if(codec.writeReg(0x0D, 0b00000000)) Serial.println("Error.");
  if(codec.writeReg(0x0E, 0b00011000)) Serial.println("Error.");
  if(codec.writeReg(0x0F, 0b00011000)) Serial.println("Error.");
  if(codec.writeReg(0x10, 0b00011000)) Serial.println("Error.");
  if(codec.writeReg(0x11, 0b00011000)) Serial.println("Error.");
  if(codec.writeReg(0x12, 0b00000100)) Serial.println("Error.");
  if(codec.writeReg(0x13, 0b00000101)) Serial.println("Error.");
  if(codec.writeReg(0x14, 0b00001010)) Serial.println("Error.");
  if(codec.writeReg(0x00, 0b00110111)) Serial.println("Error."); // turn AK4619 on

  // read back AK4619 register settings
  for(int i=0;i<=0x14;i++) {
    if(codec.readReg(i, &regval)) {
      Serial.println("READ ERROR!");
    }
    Serial.print(i, HEX);
    Serial.print(": \t");
 
    for (int8_t i = 7; i >= 0; i--) {
      uint8_t bit = (regval >> i) & 1;
      Serial.print(bit);
      if (i % 4 == 0)
        Serial.print(" ");
    }
    Serial.print("\t");
    Serial.print(regval, HEX);
    Serial.println();
  }
  Serial.println("Setup done");

  // set up waveforms
  waveform1.begin(WAVEFORM_SINE);
  waveform1.frequency(30);
  waveform1.amplitude(1.0);

  waveform2.begin(WAVEFORM_SAWTOOTH);
  waveform2.frequency(30);
  waveform2.amplitude(1.0);

  waveform3.begin(WAVEFORM_TRIANGLE);
  waveform3.frequency(30);
  waveform3.amplitude(1.0);

  amp1.gain(0.0);
}

void loop() {
  // sample-and-hold DC test signal
  //dc1.amplitude(random(0,100)/50.0 - 1.0);
  dc1.amplitude(-1.0/2.0);
  // print a counter so we know we're running
  Serial.println(count++);
  delay(100);
}
 
Last edited:
I am a fool. The issue was that I'd run out of AudioMemory. It didn't even occur to me that this could be possible, since I don't feel like I'm actually using very much, but I took a look at the TDM passthrough example and it's using AudioMemory(50) while I was using AudioMemory(20). Setting my AudioMemory to 50 solved the problem. I have input and output working now. Wow. Sweet.
 
The working code, for posterity:

C++:
#include <Wire.h>
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>
#include <utility/imxrt_hw.h>

class AK4619 {
public:
  TwoWire* wire;
  uint8_t addr;
  void Init(uint8_t i2cAddress=0x10) {
    Wire.begin();
    addr = i2cAddress;
  }
  uint8_t writeReg(uint8_t deviceReg, uint8_t regVal) {
    Wire.beginTransmission(addr);
    Wire.write(deviceReg);
    Wire.write(regVal);
    return(Wire.endTransmission(true));
  }

  uint8_t readReg(int8_t deviceReg, uint8_t * regVal) {
    Wire.beginTransmission(addr);
    Wire.write(deviceReg);
    Wire.endTransmission(true);
    uint8_t numbytes = 0;
    numbytes = Wire.requestFrom(addr, (uint8_t)1, (uint8_t)false);
    if((bool)numbytes){
      Wire.readBytes(regVal, numbytes);
    }
    return(Wire.endTransmission(true)); //Send STOP
  }
};
 
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);
}

// GUItool: begin automatically generated code
AudioInputTDM            tdm1;           //xy=326,427
AudioOutputTDM           tdm2;           //xy=843,411
AudioSynthWaveform       waveform1;      //xy=347,179
AudioSynthWaveform       waveform2;      //xy=347,179
AudioSynthWaveform       waveform3;      //xy=347,179
AudioSynthWaveformDc     dc1;            //xy=575,252
AudioAmplifier           amp1;           //xy=540,355

AudioConnection          patchCord5(waveform1, 0, tdm2, 0);
//AudioConnection          patchCord6(waveform2, 0, tdm2, 2);
AudioConnection          patchCord7(waveform3, 0, tdm2, 4);
AudioConnection          patchCord8(waveform1, 0, tdm2, 6);

// -------------------------------------------------------
// UNCOMMENT ME TO CAUSE ISSUES WITH DAC1R and DAC2L!!!! -
// -------------------------------------------------------
AudioConnection          patchCord1(tdm1, 0, amp1, 0);
AudioConnection          patchCord2(amp1, 0, tdm2, 2);

AK4619 codec;

uint8_t regval = 0;
uint8_t error = 0;
int count = 0;

void setup() {

  Serial.begin(9600);

  Serial.println("hi");

  AudioMemory(50);

  //setI2SFreq(20000);

  Serial.println("begin");

  // AK4619VN startup sequence
  pinMode(41, OUTPUT);
  digitalWrite(41, LOW);
  delay(200);
  digitalWrite(41, HIGH);
  delay(200);
  codec.Init();
  delay(200);

  //if(codec.writeReg(0x00, 0b00110111)) Serial.println("Error."); // set last but repeated here for address consistency
  if(codec.writeReg(0x01, 0b11111110)) Serial.println("Error."); // set AK4619 regs
  if(codec.writeReg(0x02, 0b00011100)) Serial.println("Error.");
  if(codec.writeReg(0x03, 0b00000011)) Serial.println("Error.");
  if(codec.writeReg(0x04, 0b00100010)) Serial.println("Error.");
  if(codec.writeReg(0x05, 0b00100010)) Serial.println("Error.");
  if(codec.writeReg(0x06, 0b00110000)) Serial.println("Error.");
  if(codec.writeReg(0x07, 0b00110000)) Serial.println("Error.");
  if(codec.writeReg(0x08, 0b00110000)) Serial.println("Error.");
  if(codec.writeReg(0x09, 0b00110000)) Serial.println("Error.");
  if(codec.writeReg(0x0A, 0b00000000)) Serial.println("Error.");
  if(codec.writeReg(0x0B, 0b01010101)) Serial.println("Error.");
  //if(codec.writeReg(0x0C, 0b00000000)) Serial.println("Error."); //reserved
  if(codec.writeReg(0x0D, 0b00000000)) Serial.println("Error.");
  if(codec.writeReg(0x0E, 0b00011000)) Serial.println("Error.");
  if(codec.writeReg(0x0F, 0b00011000)) Serial.println("Error.");
  if(codec.writeReg(0x10, 0b00011000)) Serial.println("Error.");
  if(codec.writeReg(0x11, 0b00011000)) Serial.println("Error.");
  if(codec.writeReg(0x12, 0b00000100)) Serial.println("Error.");
  if(codec.writeReg(0x13, 0b00000101)) Serial.println("Error.");
  if(codec.writeReg(0x14, 0b00001010)) Serial.println("Error.");
  if(codec.writeReg(0x00, 0b00110111)) Serial.println("Error."); // turn AK4619 on

  // read back AK4619 register settings
  for(int i=0;i<=0x14;i++) {
    if(codec.readReg(i, &regval)) {
      Serial.println("READ ERROR!");
    }
    Serial.print(i, HEX);
    Serial.print(": \t");
    
    for (int8_t i = 7; i >= 0; i--) {
      uint8_t bit = (regval >> i) & 1;
      Serial.print(bit);
      if (i % 4 == 0)
        Serial.print(" ");
    }
    Serial.print("\t");
    Serial.print(regval, HEX);
    Serial.println();
  }
  Serial.println("Setup done");

  // set up waveforms
  waveform1.begin(WAVEFORM_SINE);
  waveform1.frequency(30);
  waveform1.amplitude(1.0);

  waveform2.begin(WAVEFORM_SAWTOOTH);
  waveform2.frequency(30);
  waveform2.amplitude(1.0);

  waveform3.begin(WAVEFORM_TRIANGLE);
  waveform3.frequency(30);
  waveform3.amplitude(1.0);

  amp1.gain(1.0);
}

void loop() {
  // sample-and-hold DC test signal
  //dc1.amplitude(random(0,100)/50.0 - 1.0);
  dc1.amplitude(-1.0/2.0);
  // print a counter so we know we're running
  Serial.println(count++);
  delay(100);
}
 
Um ... yes. See post #2 :giggle:

Would I be right in thinking the only difference in the hardware settings resulting from your setI2SFreq() is the omission of
n1 = n1 / 2; //Double Speed for TDM
before CCM_CS1CDR is set? That could probably be put into an AK4619::enable() function along with all your setup register writes for a more typical audio hardware control class.
 
@john-mike - I just noticed you're using the odd-numbered TDM inputs. This tends not to work, as the slots are 32 bits wide and the original implementation shoehorns 16x 16-bit channels into 8x 32-bit slots. Paul had a plan to build an adaptor to shift the odd-numbered channels up to the even-numbered ones and send the result to a second codec (https://hackaday.io/project/176368-dual-interleaved-cs42448-chip), but that effort fizzled out about 3 years ago, and isn't helped by the CS42448 going obsolete.

TL;DR: if you use even channels and the code @eris has posted, you might have a result :)
 
I am a fool. The issue was that I'd run out of AudioMemory. It didn't even occur to me that this could be possible, since I don't feel like I'm actually using very much, but I took a look at the TDM passthrough example and it's using AudioMemory(50) while I was using AudioMemory(20). Setting my AudioMemory to 50 solved the problem. I have input and output working now. Wow. Sweet.
Sweet (y)
 
Um ... yes. See post #2 :giggle:

Would I be right in thinking the only difference in the hardware settings resulting from your setI2SFreq() is the omission of
n1 = n1 / 2; //Double Speed for TDM
before CCM_CS1CDR is set? That could probably be put into an AK4619::enable() function along with all your setup register writes for a more typical audio hardware control class.
Oh well now I just feel silly. Thank you lol.

Also, the setI2SFreq() function is now unnecessary. I had nabbed that from some other forum thread and was using it to slow things down a smidge so that the Pi Pico I was using as a logic analyzer could actually see the data reliably, but it works at full rate now so I'll probably just remove that code.

I do wonder if it might be possible to eventually get this running at 96kHz. I can't honestly say that I understand exactly what's happening in the setI2SFreq() function, but my eventual goal is use two of these AK4619 chips and try to get 32bit audio by creating a custom audio block that handles all the processing. 8i8o 32bit 96kHz audio would be phenomenal- I guess we'll see. c:
 
Hi Fellow AK4619 coders!

Great to find this post! Hopefully we can work together on this.

Last week I got a passthrough working on 24bit 192khz using a Teensy 4.1 TDM Audio setup by using an existing ESP32 codec AK4619 library which I modified to work with a Teensy and modified the AudioStream.cpp audio block for example to handle 32bit audio.
You can check out the code here, simply download it and put it in your library folder and run the passthrough example: Teensy4-4Chn-24bit-TDM-Audio

I am using an Eurorack module from apfelaudio.com which is equipped with an AK4619 which works now.
I also got a barebone version with an example of adding a sine wave to an input: https://github.com/Lytrix/Teensy4-i2s-TDM

Please let me know if this is also working for you or if you find any bugs/issues.

I am now working on playing a 24bit Wav file from SD card by modifying the existing Audio example, I can get something on my scope, but it is mostly noise :) I think it is due to how the release logic of a block is working which I don't 100% understand yet. Let me know if you have some experience on this.
 
Last edited:
Ok, I've managed to create an example recording a mono 32-bit wav file on channel 1 using the stripped down version which is not using the audio library: https://github.com/Lytrix/Teensy4-i2s-TDM
I initially tried to make the audio stream record queue example work, but I did not really grasp the bitwise operations and masking logic yet. I did got some output, but is was not signed for example.
 
I finally managed to get a proper passthrough up and running using AudioStream. I also got the Record RAW to SD example up and running using the Teensy Audio library by changing the audio_block data type to 32 bit. Yey!

In the end I modified the I2SQuad example and used some TDM simplifications to properly iterate over the 4 channels in each of the 128 samples.

 
Back
Top