Teensy 4.0 PCM1865 Audio Shield - 4 channel ADC with balanced/single ended + preamp

Status
Not open for further replies.

JayShoe

Well-known member
Hello Friends,

I'm now posting regarding my Teensy 4.0 PCM1865 Audio Shield. This is part of a larger project to create a many input, many output digital mixer. But I believe the cards could be very useful for many of the projects I'm seeing posted here on the Teensy forum. My PCM5242 DAC card is currently being built at the fab house, and this one will go to fab shortly. If you have any feedback, please feel free to chime in.

This board features a PCM1865 with 4 ADC channels @ 110-dB SNR, and a front end that incorporates a preamp that provides –12 dB to +32 dB of gain. Each input channel can have either a balanced or single ended input. It's another "lean and mean" design that is supposed to be small and efficient.

TEENSY4_PCM1865_AUDIO_SHIELD_front1.jpg
TEENSY4_PCM1865_AUDIO_SHIELD_back1.jpg
TEENSY4_PCM1865_AUDIO_SHIELD_side1.jpg

The board exposes pins for connections to your "IO Board". I know that some users don't like this but for my project this is imperative. It makes it much more flexible. The boards will stack on top of eachother to provide the shortest possible distance for the Clock signals to travel to each card. Two of these cards stacked up create 8 inputs, and with all the I2S data lines on the T4 there is a theoretical input of many... Then the Analog Inputs are exposed to headers that will lead to the input boards. These input boards will be mounted to the chassis - so having them separated from the boards makes it easier to put them where they make sense physically outside of the case while the actual device doesn't take up much space. These input boards can have pretty much any input jack you want. Here is one example that provides 2 single ended RCA inputs and 2 Combo Jack inputs...

TEENSY4_PCM1865_AUDIO_SHIELD_io_board_example1.jpg

Possible input boards include 3.5mm, XLR, 1/4 inch, RCA, and pretty much any other input type you prefer for your project. The PCM1865 has 8 input lines that can also be selectable. So technically you could have up to 8 single ended inputs - on 4 ADCs. This would be useful for someone looking to MUX or MIX inputs. In the use-case above I'm using 2 single ended, and 2 balanced lines. This gives me a simple "aux in" and 2 balanced microphone inputs. The type of equipment I'm using in this project are powered speakers, dynamic microphones, CD-players, etc.

Resources:
PCM1865 Datasheet - http://www.ti.com/lit/ds/symlink/pcm1865.pdf
Teensy 4.0 PCM5242 Audio Shield Forum Post - https://forum.pjrc.com/threads/6038...hield-Stereo-Balanced-Single-Ended-DAC-Module
Teensy 4.0 Audio Toolkit Shield - https://forum.pjrc.com/threads/6041...ield-An-Open-Source-Audio-IO-Project-in-Kicad

Schematic
View attachment TEENSY4_PCM1865_AUDIO_SHIELD_Schematic.pdf

Questions:
- I just realized that some people will want phantom inputs. I might be able to expose some header pins to provide that functionality (on a second board). Before going to fab, I might research that just a bit to see how that would fit. Any input is appreciated. It might be possible to daisy chain a phantom insertion after the inputs of this card... I will study up on this and report back.
- I have spent a lot of time making sure my input filters are correct. But a second set of eyes on the input filters would be appreciated.
 
Hello,

Updating the fact that I have in hand my first prototypes. I wired them up, and I can send and receive I2C data. However, no audio yet. I'm currently talking with TI to see which register configurations I might be missing. If anyone is a strong coder and wants to help get the driver working, I might have some modules I can send out...

PCM5242_PROTOTYPE_2020-05-19.jpg
PCM5242_PROTOTYPE_2020-05-19_BACK.jpg

I do realize that I will need to write some drivers for the PCM5242. To show what I'm using and hopefully contribute some helpful stuff, I'm going to paste in my code below.

Code:
#include <Wire.h>
#include "PCM5242_.h"
#include <Audio.h>
#include <SPI.h>
#include <SerialFlash.h>

AudioInputUSB            usb1;           //xy=200,69  (must set Tools > USB Type to Audio)
AudioOutputI2S           i2s1;           //xy=365,94
AudioConnection          patchCord1(usb1, 0, i2s1, 0);
AudioConnection          patchCord2(usb1, 1, i2s1, 1);
AudioControlSGTL5000     sgtl5000_1;     //xy=302,184


uint8_t currentI2C_addr = 0x4f;
uint8_t PCM5242_I2C_addr = 0x4f;

int currentPage = 0;

void setup() {
    AudioMemory(12);
  Wire.begin();
  delay(5); // wait for steady I2C line
  Serial.begin(115200);
  delay(1000);
}

void loop() {
  menu();
}

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

  Serial.println("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);
    delay(5);
    error = Wire.endTransmission();

    if (error == 0)
    {
      Serial.print("\nI2C device found at address 0x");
      if (address < 16)
        Serial.print("0");
      Serial.print(address, HEX);
      Serial.println("!");

      nDevices++;
    }
    else if (error == 4)
    {
      Serial.print("\nUnknown error at address 0x");
      if (address < 16)
        Serial.print("0");
      Serial.println(address, HEX);
    }
    else {
      if (address == 32 || address == 64 || address == 96) {
        Serial.print(".\n");
      } else {
        Serial.print(".");
      }
    }
  }
  if (nDevices == 0)
    Serial.println("\nNo I2C devices found\n");
  else
    Serial.println("done\n");
}

void moveI2C_addr() {
  byte error = 1;
  Serial.println("\n\nInput the I2C adddress you wish to move to,");
  Serial.println("i.e. 0x4C");
  Serial.readStringUntil('\n');
  Serial.setTimeout(999999);
  Serial.print("\nInput: ");
  String param = Serial.readStringUntil('\n');
  char buf[5];
  param.toCharArray(buf, 5);
  uint8_t xx;
  sscanf(buf, " %x", &xx);
  Serial.println();
  Serial.print("Received input: ");
  PrintHex8(&xx, 1);
  Serial.println("\Received input in dec: " + String(xx));
  Serial.print("\nAttempting to switch current I2C address to ");
  PrintHex8(&xx, 1);
  Serial.println();
  Wire.beginTransmission(xx);
  delay(5);
  error = Wire.endTransmission();

  if (error == 0)
  {
    Serial.print("I2C device found at address ");
    PrintHex8(&xx, 1);
    currentI2C_addr = xx;
    Serial.println("!");
  } else {
    Serial.print("Unable to connect to I2C device at address ");
    PrintHex8(&xx, 1);
    Serial.println("!");
    Serial.print("Current I2C address not changed!\n\n\n");
  }

}

void readI2C_command() {
  Serial.println("\nInput the page and register (in hex) you wish to read,");
  Serial.println("i.e. 0x01 0x03");
  Serial.readStringUntil('\n');
  Serial.setTimeout(999999);
  Serial.print("\nInput: ");
  String param = Serial.readStringUntil('\n');
  char buf[7];
  param.toCharArray(buf, 7);
  uint8_t xx;
  uint8_t yy;
  sscanf(buf, "%x %x", &xx, &yy);
  readValue(xx, yy);
  Serial.println();
}

void writeI2C_command() {
  Serial.println("\nInput the page, register, and value (in hex) you wish to write,");
  Serial.println("i.e. 0x01 0x3F 0x00");
  Serial.readStringUntil('\n');
  Serial.setTimeout(999999);
  Serial.print("\nInput: ");
  String param = Serial.readStringUntil('\n');
  char buf[7];
  param.toCharArray(buf, 7);
  uint8_t xx;
  uint8_t yy;
  uint8_t zz;
  sscanf(buf, "%x %x %x", &xx, &yy, &zz);
  writePage(xx, yy, zz);
  Serial.println();
}

void dumpAllRegisters() {
  Serial.println("\nDumping all registers...");
  currentPage = 0;
  for (int reg = 1; reg <= 121; reg++) {
    readValue(currentPage, reg);
    delay(2);
  }
  currentPage = 1;
  for (int reg = 1; reg <= 9; reg++) {
    readValue(currentPage, reg);
    delay(2);
  }
}

unsigned int readValue(uint8_t page, uint8_t reg) {
  unsigned int val;
  if (goToPage(page)) {
    Wire.beginTransmission(currentI2C_addr);
    Wire.write(reg);
    unsigned int result = Wire.endTransmission();
    if (result != 0) {
      Serial.print("controlPCM5242: ERROR: Read Page.  Page: "); Serial.print(page);
      Serial.print(" Reg: "); Serial.print(reg);
      Serial.print(".  Received Error During Read Page: ");
      Serial.println(result);
      val = 300 + result;
      return val;
    }
    if (Wire.requestFrom(currentI2C_addr, 1) < 1) {
      Serial.print("controlPCM5242: ERROR: Read Page.  Page: "); Serial.print(page);
      Serial.print(" Reg: "); Serial.print(reg);
      Serial.println(".  Nothing to return");
      val = 400;
      return val;
    }
    if (Wire.available() >= 1) {
      uint8_t val = Wire.read();
      Serial.print("controlPCM5242: Read Page.  Page: "); Serial.print(page);
      Serial.print(" Reg: "); Serial.print(reg);
      Serial.print(".  Received: ");
      PrintHex8(&val, 1);
      Serial.print(val);
      Serial.print(val,BIN);
            Serial.print("\n");
      return val;
    }
  } else {
    Serial.print("controlPCM5242: INFO: Read Page.  Page: "); Serial.print(page);
    Serial.print(" Reg: "); Serial.print(reg);
    Serial.println(".  Failed to go to read page.  Could not go there.");
    val = 500;
    return val;
  }
  val = 600;
  return val;
}

bool goToPage(byte page) {
  Wire.beginTransmission(currentI2C_addr);
  Wire.write(0x00); delay(10);// page register  //was delay(10) from BPF
  Wire.write(page); delay(10);// go to page   //was delay(10) from BPF
  byte result = Wire.endTransmission();
  if (result != 0) {
    Serial.print("controlPCM5242: Received Error During goToPage(): Error = ");
    Serial.println(result);
    if (result == 2) {
      // failed to transmit address
      //return goToPage(page);
    } else if (result == 3) {
      // failed to transmit data
      //return goToPage(page);
    }
    return false;
  }
  return true;
}

void transmit_exported_registers(cfg_reg *r, int n) {
  Serial.println("Beginning to transmit exported registers...");
  int i = 0;
  while (i < n) {
    Wire.beginTransmission(PCM5242_I2C_addr);
    Serial.print("r[");
    Serial.print(i);
    Serial.print("].command: ");
    // PrintHex8(r[i].command, 1);
    Wire.write(r[i].command); delay(10);
    Serial.print("r[");
    Serial.print(i);
    Serial.print("].param: ");
    Serial.println(r[i].param, HEX);
    Wire.write(r[i].param); delay(10);
    Wire.endTransmission();
    i++;
  }
  Serial.println("Finished transmitting all dumped register values");
}

boolean writePage(uint8_t page, uint8_t reg, uint8_t val) {
  Serial.print("controlPCM5242: Write Page.  Page: "); Serial.print(page);
  Serial.print(" Reg: "); Serial.print(reg);
  Serial.print(" Val: "); Serial.println(val);
  if (goToPage(page)) {
    Wire.beginTransmission(currentI2C_addr);
    Wire.write(reg); delay(10);
    Wire.write(val); delay(10);
    uint8_t result = Wire.endTransmission();
    if (result == 0) return true;
    else {
      Serial.print("controlPCM5242: Received Error During writePage(): Error = ");
      Serial.println(result);
    }
  }
  return false;
}

void printMenu() {
  Serial.println("Test menu:");
  Serial.println("---------------------------------------");
  Serial.print("Current I2C Address: ");
  PrintHex8(&currentI2C_addr, 1);
  Serial.println("(" + String(currentI2C_addr) + ")");
  Serial.println("Available options: ");
  Serial.println("1: Move to different I2C address");
  Serial.println("2: Scan I2C address space");
  Serial.println("3: Dump all registers");
  Serial.println("4: Write all preprogrammed registers");
  Serial.println("5: Dump specific register");
  Serial.println("6: Write specific register");
  Serial.println("---------------------------------------");
  Serial.print("Input: ");
}

void menu() {
  printMenu();
  for (;;) {
    switch (Serial.read()) {

      case '1': {
          moveI2C_addr();
          printMenu();
          break;
        }

      case '2': {
          scanI2C();
          printMenu();
          break;
        }

      case '3': {
          dumpAllRegisters();
          printMenu();
          break;
        }

      case '4': {
          transmit_exported_registers(registers, sizeof(registers) / sizeof(registers[0]));
          printMenu();
          break;
        }

      case '5': {
          readI2C_command();
          printMenu();
          break;
        }

      case '6': {
          writeI2C_command();
          printMenu();
          break;
        }

      default:
        continue;  // includes the case 'no input'
    }
  }
}

void PrintHex8(uint8_t *data, uint8_t length) // prints 8-bit data in hex with leading zeroes
{
  char tmp[16];
  for (int i = 0; i < length; i++) {
    sprintf(tmp, "0x%.2X", data[i]);
    Serial.print(tmp); Serial.print(" ");
  }
}

What this code does is show a menu that allows you to read, and write registry values to the module. It also send USB passthrough. It's useful for testing, so I can determine what exactly the driver needs to do to initialize the module. I have an EVM setup, along with the custom board, and so I know sound is coming from the EVM, thus it's correct. But it unfortunately doesn't yet come out of my module. :-/

This is the setup. One of the boards in the stack is a "Teensy 4 to I2S Converter" board that I made (and plan to share eventually, on oshpark) that is making it easier to connect the EVM to the Teensy for testing purposes. It's pretty sweet! Yes it's a mess, just trying to get it going!!!

PCM5242_PROTOTYPE_2020-05-19_TEST.jpg

Edit: My support inquiry on E2E forum: https://e2e.ti.com/support/audio/f/6/t/906629
 
Status
Not open for further replies.
Back
Top