Help debugging Teensy4 & WM8904 audio codec

sweentj

New member
Hello All,

I wanted to create a project with the Teensy4 and WM8904 audio codec. I'm currently trying to get a barebones test off the ground by just sending a signal (1khz 0.5Vpp sinewave) through the bypass signal path. I've created a custom eval board for the codec and have wired it up to the Teensy4 on a breadboard. Eval board schematic included.

The WM8904 includes a Write Control Sequencer that is supposed to set the registers to a known good startup configuration. I wasn't able to get it to work so I copied the startup sequence settings to a struct array and iterated through that in order to configure the codec. I then set the required LINEOUT Bypass bits in the Analogue OUT12 reg. After these steps I would expect to see an output on LINEOUT if I send an input signal on LINEIN, but I get nothing. I can't figure out if its an issue electrically or with the configuration of the codec registers.

Any ideas for debugging would be much appreciated :)

Test code:

C++:
#include <Arduino.h>
#include "Audio.h"
#include "Wire.h"
#include "wm8904_reg.h"

#define LED_PIN 13
#define BUTTON 12
#define WM8904_I2C_ADDRESS 0x1A

//------------------------------------------------------------------------------
int wm8904_i2c_write(uint8_t reg, uint16_t value);
int wm8904_i2c_read(uint8_t reg, uint16_t *value);
int wm8904_enable();
void wm8904_dump_reg();

AudioInputI2S i2s1;         // I2S input (for future use)
AudioOutputI2S i2s2;        // I2S output

void setup(){

  Serial.begin(9600);

  Wire.begin();

  int debug = wm8904_enable();
  switch(debug){
    case 0:
        Serial.println("Success!");
        break;
    case 1:
        Serial.println("Data too long!");
        break;
    case 2:
        Serial.println("Received NACK!");
        break;
    case 3:
        Serial.println("Received other NACK!");
        break;
    case 4:
        Serial.println("Something else fucked up!");
        break;
    case 5:
        Serial.println("Timeout");
        break;
    default:
        Serial.println("Default");
        Serial.print("Debug: ");
        Serial.println(debug);
        break;
  }

  wm8904_dump_reg();

}

//------------------------------------------------------------------------------
// loop() is the main thread.  Not used in this example.
void loop() {
}

int wm8904_enable(){
  int len = sizeof(wm8904_startup_seq)/sizeof(reg_default_t);
  Serial.printf("Num of default regs: %d\n", len);

  //reset
  wm8904_i2c_write(0x00, 0x0000);

  for(int i = 0; i < len; i++){
    //iterate through all reg values defined in wm8904_reg.h
    if(wm8904_i2c_write(wm8904_startup_seq[i].addr, wm8904_startup_seq[i].val) != 0){
      Serial.print("Failed reg write: ");
      Serial.println(wm8904_startup_seq[i].addr);
      return -1;
    };
    delay(10);
  }

  return 0;
}

void wm8904_dump_reg(){
  uint16_t reg_data;
  uint16_t reg_addr;
  int len = sizeof(wm8904_reg_default)/sizeof(reg_default_t);

  for(int i = 0; i < len; i++){
    reg_addr = wm8904_reg_default[i].addr;
    wm8904_i2c_read(reg_addr, &reg_data);
    Serial.print("REG: ");
    Serial.print(reg_addr);
    Serial.print("; DAT: ");
    Serial.println(reg_data, 2);
  }

int wm8904_i2c_write(uint8_t reg, uint16_t value) {
    uint8_t data[3];
    data[0] = reg;  // Register address
    data[1] = (value >> 8) & 0xFF; // Data MSB
    data[2] = value & 0xFF;       // Data LSB

    Wire.beginTransmission(WM8904_I2C_ADDRESS);
    Wire.write(data, (uint8_t)3);
    return Wire.endTransmission();
}

int wm8904_i2c_read(uint8_t reg, uint16_t *value) {
    uint8_t data[10];

    Wire.beginTransmission(WM8904_I2C_ADDRESS);
    Wire.write(reg);
    Wire.endTransmission();

    Wire.requestFrom(WM8904_I2C_ADDRESS, 2);
    data[0] = Wire.read();
    data[1] = Wire.read();

    *value = (data[0] << 8) | data[1];
    return 0;
}

}

Configuration struct:
C++:
struct reg_default_t{
    uint8_t addr;
    uint16_t val;
};

static const struct reg_default_t wm8904_startup_seq[] = {
    {0x04, 0x001A},     /*R4 - ISEL=10b; BIAS_ENA=0*/
    {0x05, 0x0047},     /*R5 - VMID_BUF_ENA=0; VMID_RES[1:0]=11b; VMID_ENA=1*/
    {0x05, 0x0043},     /*VMID_RES[1:0]=01b*/
    {0x04, 0x001B},     /*R4 - BIAS_ENA=1*/
    {0x0E, 0x0003},     /*HPL_PGA_ENA=1; HPR_PGA_ENA=1*/
    {0x0F, 0x0003},     /*LINEOUTL_PGA_ENA=1; LINEOUTR_PGA_ENA=1*/
    {0x16, 0x0002},     /* CLK_DSP_ENA=1 */
    {0x12, 0x000C},     /* DACL_ENA=1; DACR_ENA=1*/
    {0xFF, 0x0000},     /* Dummy write */
    {0x04, 0x000B},     /* delay write?*/
    {0x62, 0x0001},     /* CP_ENA=1 */
    {0xFF, 0x0000},     /* Dummy write */
    {0x5A, 0x0011},     /* HPL_ENA = 1; HPR_ENA=1 */
    {0x5E, 0x0011},     /* LINEOUTL_ENA = 1; LINEOUTR_ENA=1 */
    {0x5A, 0x0033},     /* HPL_ENA_DLY=1; HPR_ENA_DLY=1*/
    {0x5E, 0x0033},     /* LINEOUTL_ENA_DLY=1; LINEOUTR_ENA_DLY=1*/
    {0x43, 0x000F},     /* R67 - DCS_ENA_CHANNEL_[3:0]=1 */
    {0x44, 0x000F},     /* R68 - DCS_DAC_WR_[3:0]=1 */
    {0xFF, 0x0000},     /* Dummy write */
    {0x5A, 0x0077},     /* HPL_ENA_OUTP=1; HPR_ENA_OUTP=1*/
    {0x5E, 0x0077},     /* LINEOUTL_ENA_OUTP=1; LINEOUTR_ENA_OUTP=1*/ 
    {0x5A, 0x00FF},     /* HPL_RMV_SHORT=1; HPR_RMV_SHORT=1*/
    {0x5E, 0x00FF},     /* LINEOUTL_RMV_SHORT=1; LINEOUTR_RMV_SHORT=1*/
    {0x16, 0x0006},     /* CLK_SYS_ENA=1*/
    {0x0C, 0x0003},     /* INL_ENA=1; INR_ENA=1*/
    {0x3D, 0x0003},     /* LINEOUTL_BYP_ENA=1; LINEOUTR_BYP_ENA=1*/
    {0x2C, 0x0005},     /* LINMUTE = 0*/
    {0x2D, 0x0005},     /* LINMUTE = 0*/
    {0x2E, 0x0040},     /* R46 - R_IP_SEL_P = 01 (IN1R)*/
    {0x2F, 0x0040}      /* R46 - L_IP_SEL_P = 01 (IN1L)*/

};
 

Attachments

  • WM8904Eval_Schematic.pdf
    86.2 KB · Views: 24
A few questions:
1. Does reading out the registers wm8904_dump_reg(); work?

2. Is the MCLK signal present?
1734037715388.png


3. What happens if you read out register R0?
1734037493497.png

The datasheet I looked at.

Paul
 
Hey Paul thanks for taking a look :)

Yes wm8904_reg_dump() is working, I've listed the output below.

2. MCLK is present. I've edited the output_i2s.cpp code to set MCLK to 12.288MHz for 256 * fs where fs=48kHz. Maybe this was wrong and it should be higher than 256*fs?

3. Reading R0 receives 0x8904.

After reviewing my schematic again, the VMID pin is unconnected while in the datasheet it calls for a 4.7u capacitor. However, this is for the ADC/DAC reference so I don't think it would prevent the Bypass signal path not working. My best guess at this point is something with the reg configuration.

1734049859648.png


Register dump:

REG: 4; DAT(bin): 1011 DAT(hex): B
REG: 5; DAT(bin): 1000011 DAT(hex): 43
REG: 6; DAT(bin): 0 DAT(hex): 0
REG: 7; DAT(bin): 0 DAT(hex): 0
REG: 10; DAT(bin): 1 DAT(hex): 1
REG: 12; DAT(bin): 11 DAT(hex): 3
REG: 14; DAT(bin): 11 DAT(hex): 3
REG: 15; DAT(bin): 11 DAT(hex): 3
REG: 18; DAT(bin): 1100 DAT(hex): C
REG: 20; DAT(bin): 1000110001011110 DAT(hex): 8C5E
REG: 21; DAT(bin): 110000000101 DAT(hex): C05
REG: 22; DAT(bin): 110 DAT(hex): 6
REG: 24; DAT(bin): 1010000 DAT(hex): 50
REG: 25; DAT(bin): 1010 DAT(hex): A
REG: 26; DAT(bin): 11100100 DAT(hex): E4
REG: 27; DAT(bin): 1000000 DAT(hex): 40
REG: 30; DAT(bin): 11000000 DAT(hex): C0
REG: 31; DAT(bin): 11000000 DAT(hex): C0
REG: 32; DAT(bin): 0 DAT(hex): 0
REG: 33; DAT(bin): 1000 DAT(hex): 8
REG: 36; DAT(bin): 11000000 DAT(hex): C0
REG: 37; DAT(bin): 11000000 DAT(hex): C0
REG: 38; DAT(bin): 10000 DAT(hex): 10
REG: 39; DAT(bin): 0 DAT(hex): 0
REG: 40; DAT(bin): 110101111 DAT(hex): 1AF
REG: 41; DAT(bin): 11001001001000 DAT(hex): 3248
REG: 42; DAT(bin): 0 DAT(hex): 0
REG: 43; DAT(bin): 0 DAT(hex): 0
REG: 44; DAT(bin): 101 DAT(hex): 5
REG: 45; DAT(bin): 101 DAT(hex): 5
REG: 46; DAT(bin): 1000000 DAT(hex): 40
REG: 47; DAT(bin): 1000000 DAT(hex): 40
REG: 57; DAT(bin): 101101 DAT(hex): 2D
REG: 58; DAT(bin): 101101 DAT(hex): 2D
REG: 59; DAT(bin): 111001 DAT(hex): 39
REG: 60; DAT(bin): 111001 DAT(hex): 39
REG: 61; DAT(bin): 11 DAT(hex): 3
REG: 67; DAT(bin): 1111 DAT(hex): F
REG: 69; DAT(bin): 101000001010 DAT(hex): A0A
REG: 71; DAT(bin): 101010 DAT(hex): 2A
REG: 72; DAT(bin): 101010 DAT(hex): 2A
REG: 90; DAT(bin): 11111111 DAT(hex): FF
REG: 94; DAT(bin): 11111111 DAT(hex): FF
REG: 98; DAT(bin): 1 DAT(hex): 1
REG: 104; DAT(bin): 100 DAT(hex): 4
REG: 108; DAT(bin): 0 DAT(hex): 0
REG: 109; DAT(bin): 0 DAT(hex): 0
REG: 110; DAT(bin): 0 DAT(hex): 0
REG: 111; DAT(bin): 0 DAT(hex): 0
REG: 112; DAT(bin): 0 DAT(hex): 0
REG: 116; DAT(bin): 0 DAT(hex): 0
REG: 117; DAT(bin): 111 DAT(hex): 7
REG: 118; DAT(bin): 0 DAT(hex): 0
REG: 119; DAT(bin): 10111011100000 DAT(hex): 2EE0
REG: 120; DAT(bin): 100 DAT(hex): 4
REG: 121; DAT(bin): 10100 DAT(hex): 14
REG: 122; DAT(bin): 10000 DAT(hex): 10
REG: 123; DAT(bin): 10000 DAT(hex): 10
REG: 124; DAT(bin): 0 DAT(hex): 0
REG: 126; DAT(bin): 0 DAT(hex): 0
REG: 128; DAT(bin): 1111111111 DAT(hex): 3FF
REG: 129; DAT(bin): 0 DAT(hex): 0
REG: 130; DAT(bin): 0 DAT(hex): 0
REG: 134; DAT(bin): 0 DAT(hex): 0
REG: 135; DAT(bin): 1100 DAT(hex): C
REG: 136; DAT(bin): 1100 DAT(hex): C
REG: 137; DAT(bin): 1100 DAT(hex): C
REG: 138; DAT(bin): 1100 DAT(hex): C
REG: 139; DAT(bin): 1100 DAT(hex): C
REG: 140; DAT(bin): 111111001010 DAT(hex): FCA
REG: 141; DAT(bin): 10000000000 DAT(hex): 400
REG: 142; DAT(bin): 11011000 DAT(hex): D8
REG: 143; DAT(bin): 1111010110101 DAT(hex): 1EB5
REG: 144; DAT(bin): 1111000101000101 DAT(hex): F145
REG: 145; DAT(bin): 101101110101 DAT(hex): B75
REG: 146; DAT(bin): 111000101 DAT(hex): 1C5
REG: 147; DAT(bin): 1110001011000 DAT(hex): 1C58
REG: 148; DAT(bin): 1111001101110011 DAT(hex): F373
REG: 149; DAT(bin): 101001010100 DAT(hex): A54
REG: 150; DAT(bin): 10101011000 DAT(hex): 558
REG: 151; DAT(bin): 1011010001110 DAT(hex): 168E
REG: 152; DAT(bin): 1111100000101001 DAT(hex): F829
REG: 153; DAT(bin): 11110101101 DAT(hex): 7AD
REG: 154; DAT(bin): 1000100000011 DAT(hex): 1103
REG: 155; DAT(bin): 10101100100 DAT(hex): 564
REG: 156; DAT(bin): 10101011001 DAT(hex): 559
REG: 157; DAT(bin): 100000000000000 DAT(hex): 4000
REG: 161; DAT(bin): 0 DAT(hex): 0
REG: 198; DAT(bin): 0 DAT(hex): 0
REG: 247; DAT(bin): 0 DAT(hex): 0
REG: 248; DAT(bin): 11001 DAT(hex): 19[/CODE]
 
After reviewing my schematic again, the VMID pin is unconnected while in the datasheet it calls for a 4.7u capacitor. However, this is for the ADC/DAC reference so I don't think it would prevent the Bypass signal path not working.
Follow the schematics given in the datasheet - don't assume anything (or else get in touch with the manufacturer and ask). an MLCC ceramic cap isn't expensive...
 
Back
Top