An alternative to the obsolescent CS42448: the PCM3168

Thanks for that. I've been going through your schematics and the 3168a datasheet and it has been very educational.
I have one remaining question: What was the motivation for 12v into the op-amps? Was that to improve the SNR?
If I went with a slightly lower power opamp, like the OPA2134, would I reasonably be able to stick with 5v?
 
No, it was because the NE5532 can only get to within about 2V of its supply rails, and that was the single simplest change I could make! Just a -5V supply to ANA_VMM might do it. I used the NE5532 because it was on the data sheet examples and JLCPCB had it in stock.

A quick glance at the OPA2134 suggests it’s similarly not rail-to-rail. Op-amp selection is tricky…
 
If two PCM3168As were connected to TDM and TDM2 of a Teensy 4.1, will it be able to receive a total of 12 audio inputs?

When using two CS42448, which were connected to TDM and TDM2 of a Teensy 4.1.
TDM was "7:OUT1A, 8:IN1, 18:SDA, 19:SCL, 20:LRCLK1, 21:BCLK1, 23:MCLK1".
TDM2 was "2:OUT2, 3:LRCLK2, 4:BCLK2, 5:IN2, 18:SDA, 19:SCL, 33:MCLK2".
 
Yes, that’s right, you can use both TDM channels at once. Can’t claim to have checked the pin numbers you gave though :) Doesn’t matter whether they’re CS42448 or PCM3168.

There’s also this thread and linked pull request which suggests that it should be possible to squeeze another chip on to the spare SAI1 pins, so you could have 18i24o. I’ve started testing it and unfortunately (a) it’s not working for me and (b) the author hasn’t responded my reports of this, either on that thread or the PR conversation.
 
How to set 96kHz sampling rate on PCM3168?

former CS42448 was set via
<AudioStream.h>
#define AUDIO_SAMPLE_RATE_EXACT 96000.0f

But, PCM3168 doesn't work with above sampling rate. even though same MCLK 24.576MHz.

When I looked at the data sheet, it said that 96kHz should be set to Dual rate mode, but I wasn't sure what that meant exactly. how to connect it with Teensy 4.1 for sampling rate 96kHz?
I wondered if I could pull up MD1 (pin. 44) and MD0 (pin. 45), but that didn't work either.

Could you give me an idea?
 
Hmmm ... I've not tried 96kHz. As you say, the data sheet requires Dual rate mode for 96kHz, which needs an extra DIN and DOUT pin to be used, and will require some fairly deep diving into the TDM hardware drivers to enable those and route the audio data appropriately. It looks as if the PCM3168 can't cope with 8 channels at 96kHz on one data pin, so uses half the bit clock rate on two data pins. There's a high-speed mode, but only for the DACs; and an I²S mode which needs 7 data pins (Teensy has 5); so essentially those modes are useless for us.

This may relate in some way to this thread, but unfortunately @caleb seems to have disappeared since 19th January, leaving a non-working PR (as far as I can tell ... I could easily be wrong!).

I'll put it on my list of stuff to think about, but it's a long list...
 
I've just tested my revision to the TDM library with a PCM3168 at 96kHz, and it seems to be working :)

You need to wire two data pins in each direction (OUT1A and OUT1B out, IN1 and OUT1D in), and use the AudioInputTDMAB and AudioOutputTDMAB objects. My test topology is thus:
1723397259337.png
 
I've just tested my revision to the TDM library with a PCM3168 at 96kHz, and it seems to be working :)

You need to wire two data pins in each direction (OUT1A and OUT1B out, IN1 and OUT1D in), and use the AudioInputTDMAB and AudioOutputTDMAB objects. My test topology is thus:
View attachment 35405

1754305800557.png
1754305939453.png

above audio systems work at 96kHz with PCM3168A dual rate.
and If connecting tdmAB1 and tdmAB2 directly, pin 32 (TX2) outputs a waveform and it works.
however,

1754306083151.png

When AudioPlayQueues are applied to tdmAB1 at 96kHz, Port In 0, 1, 2 and 3 works, but In 4, 5, 6, and 7 do not. Upon checking, found that pin 32 (TX2) of Teensy 4.1 doesn't output a waveform.
Please check.
 
Last edited:
I built a board using the schematic placed here and I can't make the teensy to recognize the pcm. Ran an I2C scanner and got a Unknown error at 0xYY (all the addresses that is scanning). Set Mode directly to digital ground for I2C as the datasheet states, 2K2 pullups on SDA and SCL and ADDR0/ADDR1 both to ground (by 0 ohm resistors) but no luck at all.

Does the .enable method on the control code you sent sets true when the pcm is correctly initialized?

Any thoughts? Attached is the schematic I used
 

Attachments

  • pcm.png
    pcm.png
    116.4 KB · Views: 13
Yes, I did reset the codec, placed AudioInputTDM / AudioOutputTDM objects. I get noise on the outputs, if I change the address with setAddress the output varies, wrong address, no output, right address noise (a lot). Here's a snippet of the code I'm running:

Code:
AudioControlPCM3168 codec;
AudioInputTDM Input;
AudioOutputTDM Output;

AudioConnection patchCord1(Input, 0, Output, 0);
AudioConnection patchCord2(Input, 1, Output, 1);
AudioConnection patchCord3(Input, 2, Output, 2);
AudioConnection patchCord4(Input, 3, Output, 3);

void setup() {
  Serial.begin(115200);
  Wire.begin();
 
  // CODEC
  AudioMemory(100);
 
  codec.setAddress(0);
  codec.setWire(Wire);
  codec.reset(CODEC_RESET);
  codec.enable();
  codec.volume(0.5f);    // tried with 0 to see if that affected the output but no
  codec.inputLevel(0.5f);
  }
 
  loop() {
 
  }

Included the control object from h4yn0nnym0u5e repo. I can see the clocks arriving from the Teensy.
 
That all looks OK. Can you post your layout? I seem to recall issues for some people running I²C near the I²S bus - it go so noisy the I²C stopped working.

Here's an adapted version of my initial test scan code, which I've just runs and is working (apart from apparently picking up extra addresses that aren't really active):
C++:
// --------------------------------------
// i2c_scanner
// http://playground.arduino.cc/Main/I2cScanner
//
// Version 1
//    This program (or code that looks like it)
//    can be found in many places.
//    For example on the Arduino.cc forum.
//    The original author is not know.
// Version 2, Juni 2012, Using Arduino 1.0.1
//     Adapted to be as simple as possible by Arduino.cc user Krodal
// Version 3, Feb 26  2013
//    V3 by louarnold
// Version 4, March 3, 2013, Using Arduino 1.0.3
//    by Arduino.cc user Krodal.
//    Changes by louarnold removed.
//    Scanning addresses changed from 0...127 to 1...119,
//    according to the i2c scanner by Nick Gammon
//    http://www.gammon.com.au/forum/?id=10896
// Version 5, March 28, 2013
//    As version 4, but address scans now to 127.
//    A sensor seems to use address 120.
// Version 6, November 27, 2015.
//    Added waiting for the Leonardo serial communication.
//
//
// This sketch tests the standard 7-bit addresses
// Devices with higher bit address might not be seen properly.
//
#include "Audio.h"

AudioOutputTDM  tdmO_1; // PCM3168 doesn't respond without clocks
AudioOutputTDM2 tdmO_2; // PCM3168 doesn't respond without clocks
AudioControlPCM3168 codec;

#define PCM3168_RST 17

void setup() {
  AudioMemory(20);

  pinMode(LED_BUILTIN,OUTPUT);

  codec.reset(PCM3168_RST);
  codec.enable();
  
  Serial.begin(115200);
  while (!Serial)  // Leonardo: wait for serial monitor
    ;
  Serial.println(F("\nI2C Scanner"));
}

void loop()
{
  scanWire();
  //dumpPCM3168();
}

#define PCM3186_ADDR 0x44
#define PCM3186_BASE 0x40

void dumpPCM3168(void)
{
  Serial.println("PCM3168 dump");
  Wire.beginTransmission(PCM3186_ADDR);
  Wire.write(PCM3186_BASE); // start of registers
  Wire.endTransmission();

  Wire.requestFrom(PCM3186_ADDR, 0x5E - 0x40); // all registers
  int addr = PCM3186_BASE;
  while (Wire.available())
  {
    uint8_t v = Wire.read();
    Serial.printf("%02X: %02X\n",addr++,v);
  }
  Serial.println("complete");
  delay(2000);
}

void scanWire() {
  byte error, address;
  int nDevices;

  digitalToggleFast(LED_BUILTIN);
  Serial.println(F("Scanning..."));

  nDevices = 0;
  for (address = 1; address < 127; address++) {
    // The i2c_scanner uses the return value of
    // the Write.endTransmisstion to see if
    // a device did acknowledge to the address.
    Wire.beginTransmission(address);
    error = Wire.endTransmission();

    if (error == 0) {
      Serial.print(F("Device found at address 0x"));
      if (address < 16) {
        Serial.print("0");
      }
      Serial.print(address,HEX);
      Serial.print("  (");
      printKnownChips(address);
      Serial.println(")");

      nDevices++;
    } else if (error==4) {
      Serial.print(F("Unknown error at address 0x"));
      if (address < 16) {
        Serial.print("0");
      }
      Serial.println(address,HEX);
    }
  }
  if (nDevices == 0) {
    Serial.println(F("No I2C devices found\n"));
  } else {
    Serial.println(F("done\n"));
  }
  delay(2000);           // wait 5 seconds for next scan
}


void printKnownChips(byte address)
{
  // Is this list missing part numbers for chips you use?
  // Please suggest additions here:
  // https://github.com/PaulStoffregen/Wire/issues/new
  switch (address) {
    case 0x00: Serial.print(F("AS3935")); break;
    case 0x01: Serial.print(F("AS3935")); break;
    case 0x02: Serial.print(F("AS3935")); break;
    case 0x03: Serial.print(F("AS3935")); break;
    case 0x04: Serial.print(F("ADAU1966")); break;
    case 0x0A: Serial.print(F("SGTL5000")); break; // MCLK required
    case 0x0B: Serial.print(F("SMBusBattery?")); break;
    case 0x0C: Serial.print(F("AK8963")); break;
    case 0x10: Serial.print(F("CS4272")); break;
    case 0x11: Serial.print(F("Si4713")); break;
    case 0x13: Serial.print(F("VCNL4000,AK4558")); break;
    case 0x18: Serial.print(F("LIS331DLH")); break;
    case 0x19: Serial.print(F("LSM303,LIS331DLH")); break;
    case 0x1A: Serial.print(F("WM8731")); break;
    case 0x1C: Serial.print(F("LIS3MDL")); break;
    case 0x1D: Serial.print(F("LSM303D,LSM9DS0,ADXL345,MMA7455L,LSM9DS1,LIS3DSH")); break;
    case 0x1E: Serial.print(F("LSM303D,HMC5883L,FXOS8700,LIS3DSH")); break;
    case 0x20: Serial.print(F("MCP23017,MCP23008,PCF8574,FXAS21002,SoilMoisture")); break;
    case 0x21: Serial.print(F("MCP23017,MCP23008,PCF8574")); break;
    case 0x22: Serial.print(F("MCP23017,MCP23008,PCF8574")); break;
    case 0x23: Serial.print(F("MCP23017,MCP23008,PCF8574")); break;
    case 0x24: Serial.print(F("MCP23017,MCP23008,PCF8574,ADAU1966,HM01B0")); break;
    case 0x25: Serial.print(F("MCP23017,MCP23008,PCF8574")); break;
    case 0x26: Serial.print(F("MCP23017,MCP23008,PCF8574")); break;
    case 0x27: Serial.print(F("MCP23017,MCP23008,PCF8574,LCD16x2,DigoleDisplay")); break;
    case 0x28: Serial.print(F("BNO055,EM7180,CAP1188")); break;
    case 0x29: Serial.print(F("TSL2561,VL6180,TSL2561,TSL2591,BNO055,CAP1188")); break;
    case 0x2A: Serial.print(F("SGTL5000,CAP1188")); break;
    case 0x2B: Serial.print(F("CAP1188")); break;
    case 0x2C: Serial.print(F("MCP44XX ePot")); break;
    case 0x2D: Serial.print(F("MCP44XX ePot")); break;
    case 0x2E: Serial.print(F("MCP44XX ePot")); break;
    case 0x2F: Serial.print(F("MCP44XX ePot")); break;
    case 0x30: Serial.print(F("Si7210")); break;
    case 0x31: Serial.print(F("Si7210")); break;
    case 0x32: Serial.print(F("Si7210")); break;
    case 0x33: Serial.print(F("MAX11614,MAX11615,Si7210")); break;
    case 0x34: Serial.print(F("MAX11612,MAX11613")); break;
    case 0x35: Serial.print(F("MAX11616,MAX11617")); break;
    case 0x38: Serial.print(F("RA8875,FT6206,MAX98390")); break;
    case 0x39: Serial.print(F("TSL2561, APDS9960")); break;
    case 0x3C: Serial.print(F("SSD1306,DigisparkOLED")); break;
    case 0x3D: Serial.print(F("SSD1306")); break;
    case 0x40: Serial.print(F("PCA9685,Si7021,MS8607")); break;
    case 0x41: Serial.print(F("STMPE610,PCA9685")); break;
    case 0x42: Serial.print(F("PCA9685")); break;
    case 0x43: Serial.print(F("PCA9685")); break;
    case 0x44: Serial.print(F("PCA9685, PCM3168, SHT3X, ADAU1966")); break; // PCM3168 needs clocks
    case 0x45: Serial.print(F("PCA9685, PCM3168, SHT3X")); break;
    case 0x46: Serial.print(F("PCA9685, PCM3168")); break;
    case 0x47: Serial.print(F("PCA9685, PCM3168")); break;
    case 0x48: Serial.print(F("ADS1115,CS42448,PCF8591,PN532,TMP102,LM75")); break;
    case 0x49: Serial.print(F("ADS1115,CS42448,PCF8591,TSL2561,TC74A1")); break;
    case 0x4A: Serial.print(F("ADS1115,CS42448,Qwiic Keypad")); break;
    case 0x4B: Serial.print(F("ADS1115,CS42448,Qwiic Keypad,TMP102,BNO080")); break;
    case 0x50: Serial.print(F("EEPROM,FRAM")); break;
    case 0x51: Serial.print(F("EEPROM")); break;
    case 0x52: Serial.print(F("Nunchuk,EEPROM")); break;
    case 0x53: Serial.print(F("ADXL345,EEPROM")); break;
    case 0x54: Serial.print(F("EEPROM")); break;
    case 0x55: Serial.print(F("EEPROM")); break;
    case 0x56: Serial.print(F("EEPROM")); break;
    case 0x57: Serial.print(F("EEPROM")); break;
    case 0x58: Serial.print(F("TPA2016,MAX21100")); break;
    case 0x5A: Serial.print(F("MPR121")); break;
    case 0x60: Serial.print(F("MPL3115,MCP4725,MCP4728,TEA5767,Si5351")); break;
    case 0x61: Serial.print(F("MCP4725,AtlasEzoDO")); break;
    case 0x62: Serial.print(F("LidarLite,MCP4725,AtlasEzoORP")); break;
    case 0x63: Serial.print(F("MCP4725,AtlasEzoPH")); break;
    case 0x64: Serial.print(F("AtlasEzoEC, ADAU1966")); break;
    case 0x66: Serial.print(F("AtlasEzoRTD")); break;
    case 0x68: Serial.print(F("DS1307,DS3231,MPU6050,MPU9050,MPU9250,ITG3200,ITG3701,LSM9DS0,L3G4200D")); break;
    case 0x69: Serial.print(F("MPU6050,MPU9050,MPU9250,ITG3701,L3G4200D")); break;
    case 0x6A: Serial.print(F("LSM9DS1")); break;
    case 0x6B: Serial.print(F("LSM9DS0")); break;
    case 0x6F: Serial.print(F("Qwiic Button")); break;
    case 0x70: Serial.print(F("HT16K33,TCA9548A")); break;
    case 0x71: Serial.print(F("SFE7SEG,HT16K33")); break;
    case 0x72: Serial.print(F("HT16K33")); break;
    case 0x73: Serial.print(F("HT16K33")); break;
    case 0x76: Serial.print(F("MS5607,MS5611,MS5637,BMP280")); break;
    case 0x77: Serial.print(F("BMP085,BMA180,BMP280,MS5611")); break;
    case 0x7C: Serial.print(F("FRAM_ID")); break;
    default: Serial.print(F("unknown chip"));
  }
}
 
Well, there's something that's pulling the SDA/SCL lines down on the PCM. I got low voltage readings on those pins, on a board that have the PCM removed voltages are normal so no issues with the routing of those lines. Even without the Teensy attached to the board those lines are down in voltage.

Pull down resistors are ok, 3v3 line is ok, no shortcuts to ground or any other power plane/trace.

Is too much to ask to get some voltage readings of the reference pins?
 
I'm getting about 1.84V on both VCOMAD and VCOMDA; that's lower than I'd expect, as VCC is about 4.5V. I can't get to VREFAD1 and VREFAD2 to check those voltages.
 
I'm getting about 1.84V on both VCOMAD and VCOMDA; that's lower than I'd expect, as VCC is about 4.5V. I can't get to VREFAD1 and VREFAD2 to check those voltages.
VThanks, I'm getting 5v on AREFAD1, 0v on VREFAD2, 2.5V on VCOMAD and 0V on VCOMDA. The 2.5v comes from the reference of the analog part on the input, but the out is decoupled so 0V reference I think.
 
Last edited:
Last update, got to the point the Teensy can see the PCM. I had an external analog reference, took it out and used the VREFAD as reference and got the SDA/SCL lines up to the right voltages. After that I figured out I had another I2C device on the same address, changed the address and finally can see the PCM on the scanner and the initialization method returns true.

Now I have to debug the audio part. I need 4 inputs (Vin1, Vin2, Vin3 and Vin4) and only two outputs (Vout1 and Vout2) but got a bit scrambled with all the Dins and Douts that the PCM have, all of them must be connected to the teensy?
 
Good news! At least you didn't need to do too many hardware mods :)

Looking at the data sheet (January 2016 edition) to remind myself, Table 11 on page 33 and Figure 52 on page 34 are pretty useful. Essentially for "normal" TDM use you just need DIN1 and DOUT1 connected (their I²S / left-justified TDM Single mode). If you want 96kHz, you need to use Dual mode, and wire DIN2 and DOUT2. The first option is what's supported by the standard Audio library; the second requires a revised library - see post #33 above.

In my proof-of-principle design, I routed all the pins via 100R resistors so I could explore all the possibilities, which was probably confusing of me.
 
Yeah, at least that was not that much of a deal.

I'm connecting DIN1 of the PCM to OUT1A (pin 7) of the teensy and DOUT1 to IN1 (pin 8) of the teensy, using AudioInputTDM and AudioOutputTDM objects but cannot see anything on the output with a passthru connection:
Code:
AudioControlPCM3168 codec;
AudioInput TDM Input;
AudioOutputTDM Output;

AudioConnection patchCord1(Input, 0, Output, 0);
AudioConnection patchCord2(Input, 1, Output, 1);
 
Does it work if you try to output a waveform?

Don't forget that for most purposes the odd-numbered TDM I/O ports don't do anything useful, so you should have AudioConnection patchCord2(Input, 2, Output, 2); in order to pass through the second channel.

Please post complete sketch code - if there's a coding issue then it's almost certainly in the lines you didn't post. Although there's at least one more error in your posted fragment, which I spotted but didn't mention...
 
Well, for the Input going on even numbers got to identify my inputs having them outputted to the USB. The outputs couldn't get any sound out, I have data comming out from the OUT1A but nothing comes out from the analog outputs of the PCM. Here's the code I'm using:


Code:
#include <Arduino.h>
#include <SPI.h>
#include <Wire.h>
#include "Settings.h"  // Macro definitions for pins and states

#include <ResponsiveAnalogRead.h>
#include <Adafruit_NeoPixel.h>
#include <Audio.h>
#include <EasyButton.h>
#include "control_pcm3168.h"

/ ============================================================================
//                         AUDIO
// ============================================================================
AudioControlPCM3168 codec;
AudioInputTDM Input;
AudioOutputTDM Output;

// USB OUT
AudioOutputUSB USB_Output;

// OSCILLATORS (DEBUG)
AudioSynthWaveformSine osc1;

AudioConnection patchCord1(Input, 6, USB_Output, 0);
// AudioConnection patchCord2(osc1, 0, USB_Output, 1);


AudioConnection patchCord3(osc1, 0, Output, 0);
AudioConnection patchCord4(osc1, 0, Output, 2);
AudioConnection patchCord5(osc1, 0, Output, 4);
AudioConnection patchCord6(osc1, 0, Output, 6);
AudioConnection patchCord7(osc1, 0, Output, 8);
AudioConnection patchCord8(osc1, 0, Output, 10);
AudioConnection patchCord9(osc1, 0, Output, 12);
AudioConnection patchCord10(osc1, 0, Output, 14);

void setup() {
  Serial.begin(115200);
  Wire.begin();
  // CODEC
  AudioMemory(100);

  codec.setAddress(0);
  codec.setWire(Wire);
  codec.reset(CODEC_RESET);

  if (codec.enable()) {
    Serial.println("CODEC OK");
  } else {
    Serial.println("CODEC FAIL");
  }

  codec.volume(0.0f);
  codec.inputLevel(0.5f);

  // Debug oscillators
  osc1.amplitude(0.9f); osc1.frequency(220.0f);
  }
 
  void loop{
  }
 
You haven’t set the waveform (not sure if it defaults to something sensible…), and you’ve set the volume to 0.0.

EDIT: just spotted you used AudioSynthWaveformSine, so of course no need to set the waveform! I never use it myself...
 
Last edited:
Back
Top