I fiddled around with a Wolfson WM8960 DAC until I got it sort of working with a Teensy 3.6 and 4.1 as Audio USB to i2s output passthrough for playing music from a (windows) pc
The code that I mangled use 16 bit and 48kHz - it is from Cirrus Logic for use with an STM32
I need to get it down to 44.1kHz but I cannot figure out a relatively simple setting for the R4 (04h) register Clocking
It seems as if I have to switch froma MCLK derived sustem clock to one that use the PLL?
Am I correct in this?
Any pointers will be appreciated (page 69 in the data sheet for Reg04 Clocking attached next post)
Also code use 25MHz/512 but from schematic use a 24MHz crustal
Therefore need divide by 544(.22) can I use integer PLL page 81 instead of fractional 24bit divide?
Fraction is 0.0018382
Is it accurate enough?
Wolfson docs
https://www.cirrus.com/products/wm8960/
Waveshare DAC
https://www.waveshare.com/wm8960-audio-hat.htm
I attach my code which plays - although it sometimes need a few tries
The code that I mangled use 16 bit and 48kHz - it is from Cirrus Logic for use with an STM32
I need to get it down to 44.1kHz but I cannot figure out a relatively simple setting for the R4 (04h) register Clocking
It seems as if I have to switch froma MCLK derived sustem clock to one that use the PLL?
Am I correct in this?
Any pointers will be appreciated (page 69 in the data sheet for Reg04 Clocking attached next post)
Also code use 25MHz/512 but from schematic use a 24MHz crustal
Therefore need divide by 544(.22) can I use integer PLL page 81 instead of fractional 24bit divide?
Fraction is 0.0018382
Is it accurate enough?
Wolfson docs
https://www.cirrus.com/products/wm8960/
Waveshare DAC
https://www.waveshare.com/wm8960-audio-hat.htm
I attach my code which plays - although it sometimes need a few tries
Code:
// --------------------------------------
// i2c_scanner
// http://playground.arduino.cc/Main/I2cScanner
//
////////////////////////////////////////////////////////////////////////////
// WM8960-DAC Teensy 4 Teensy 3.6 Audioboard 3
// 1,2 VCC Red 3V3 * 3V3
// 3,4 GND Brown GND * GND (not AGnd)
// 5,6 SDA Gray 18 SDA0 * 18 SDA0 18 SDA
// 7,8 SCL Orange 19 SCL0 * 19 SCL0 19 SCL
// 9,10 CLK Blue 21 BCLK1 * 9 BCK 9 BCLK
// 11,12 WS White 20 LRCLK1 * 23 LRCK 23 LRCLK I2S Frame clock input
// 13 RXSDA Green 7 OUT1A * 22 TX 22 TX I2S Data output
// 14 TXSDA Blue 22 TX 13 RX I2S Data input
// 15 RXMCLK I2S System Clock(Sending)
// 16 TXMCLK I2SSystem Clock(Receive)
// WM8960_ADDRESS = 0x1a
// MCLK signal transmitted (TX MCLK RX) MCLK signal transmitted
// * = usb audio working
// 11 MCLK
// 7 MOSI
// 12 MISO
// 14 SCLK
// 6,10 CS
///////////////////////////////////////////////////////////////////////////
// See this post for other type of pcm5102 board
// https://forum.pjrc.com/threads/53069-Teensy-with-PCM5102a-Module-via-I2S
// PCM5102A Teensy 3.6
//
// VCC = Vin
// 3.3v = NC
// GND = GND
// FLT = GND
// SCL = GND (also works connected to 11 /MCL)
// BCK = BCK (9)
// DIN = TX (22)
// LCK = LCRLK (23)
// FMT = GND
// XMT = 3.3V (HIGH)
// Set Tools -> USB Audio
///////////////////////////////////////////////////////
#include <Wire.h>
int led = LED_BUILTIN;
#define WM8960_ADDRESS 0x1A
//register values
static uint16_t WM8960_REG_VAL[56] =
{
0x0097, 0x0097, 0x0000, 0x0000, 0x0000, 0x0008, 0x0000, 0x000A,
0x01C0, 0x0000, 0x00FF, 0x00FF, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x007B, 0x0100, 0x0032, 0x0000, 0x00C3, 0x00C3, 0x01C0,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0100, 0x0100, 0x0050, 0x0050, 0x0050, 0x0050, 0x0000, 0x0000,
0x0000, 0x0000, 0x0040, 0x0000, 0x0000, 0x0050, 0x0050, 0x0000,
0x0000, 0x0037, 0x004D, 0x0080, 0x0008, 0x0031, 0x0026, 0x00ED
};
#include <Audio.h>
#include <SPI.h>
// GUItool: begin automatically generated code
AudioInputUSB usb1; //xy=121,162
AudioOutputI2S i2s1; //xy=559,166
AudioConnection patchCord1(usb1, 0, i2s1, 0);
AudioConnection patchCord2(usb1, 1, i2s1, 1);
// GUItool: end automatically generated code
int delay1 = 10;
int delay2 = 500;
//////////////////////////////////////////////////////////////////////////////////////////////////
// i2c master first send an address frame for searching slave and setting communication mode.
// High 7 bits = address, followed a control bit and finally the response signal
// Slave device receives the response signal, it will return a response to master device.
// Then master device could begin to send data to slave device, which is 8 bits and the high 7 bits are
// address and the lowest one is response signal. MSB first.
//
// Registers of WM8960 are 9-bit. Thus, when we send data to it,
// should split data to two bytes and add the ID of register to recognized before
// transmitting. The ID of register flagged by 7-bit.
//
// Byte0 = bit7 to bit1 = device address, bit0 = read/write
// Byte1 = bit7 to bit1 = register number 0-57, bit0 = MSB bit8 of register value
// Byte2 = bit7-bit0 register value
/////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////
// WM89060 Write RegisterNumber = Value
/////////////////////////////////////////
uint8_t WM8960_Write_Reg(uint8_t reg, uint16_t dat)
{
uint8_t res,I2C_Data[2];
I2C_Data[0] = (reg<<1)|((uint8_t)((dat>>8)&0x0001)); //RegAddr
I2C_Data[1] = (uint8_t)(dat&0x00FF); //RegValue
//digitalWrite(led, HIGH); // briefly flash the LED
Wire.beginTransmission(WM8960_ADDRESS); // transmit to device lsb=0 => write
//Wire.write(I2C_Data[0]); // buffer 1 byte reg1 in b7-b1
//Wire.write(I2C_Data[1]); // buffer 1 byte lsb of val1
Wire.write(I2C_Data, 2); // buffer 1 byte lsb of val1
res = Wire.endTransmission(); // transmit buffer and then stop
if(res == 0) { WM8960_REG_VAL[reg] = dat;
//digitalWrite(led, LOW);
}
return res;
}
/////////////////////////////////////////
// WM89060 Inititialise
/////////////////////////////////////////
uint8_t WM89060_Init(void) {
uint8_t res;
//////////////////////////////////////////////////////////
// Reset Device 0x0f, 0x0000
// #define WM8960_RESET 0xf
// reg<<1 register now in bit7-bit1
// msb=b8 (ninth bit) of val now in bit0
// uint8_t reg1 = ((reg << 1) |(uint8_t)((val >> 8) & 1))
// val = lsb b7-b0 val and with val msb b15-b18 all = 0
////////////////////////////////////////////////////////
res = WM8960_Write_Reg(0x0f, 0x0000);
//if (res == 0) Serial.println("WM8960 reset completed"); else return res;
delay(delay1);
// Set Power Source
// #define WM8960_POWER1 0x19
// #define WM8960_POWER2 0x1a
// #define WM8960_ADDCTL3 0x1b **************
// #define WM8960_POWER3 0x2f
res = WM8960_Write_Reg(0x19, 1<<8 | 1<<7 | 1<<6);
delayMicroseconds(delay2);
res += WM8960_Write_Reg(0x1A, 1<<8 | 1<<7 | 1<<6 | 1<<5 | 1<<4 | 1<<3);
delayMicroseconds(delay2);
res += WM8960_Write_Reg(0x2F, 1<<3 | 1<<2);
//if (res == 0) Serial.println("WM8960 power 1,2,3 completed"); else return res;
delay(delay1);
// Configure clock
// MCLK->div1->SYSCLK->DAC/ADC sample Freq = 25MHz(MCLK)/2*256 = 48.8kHz
res = WM8960_Write_Reg(0x04, 0x0000);
//if (res == 0) Serial.println("WM8960 Configure clock"); else return res;
delay(delay1);
// Configure ADC/DAC
res = WM8960_Write_Reg(0x05, 0x0000);
//if (res == 0) Serial.println("WM8960 Configure ADC/DAC"); else return res;
delay(delay1);
// Configure audio interface
// I2S format 16 bits word length
res = WM8960_Write_Reg(0x07, 0x0002);
//if (res == 0) Serial.println("WM8960 Configure audio interface"); else return res;
delay(delay1);
// Configure HP_L and HP_R OUTPUTS
res = WM8960_Write_Reg(0x02, 0x006F | 0x0100); //LOUT1 Volume Set
delayMicroseconds(delay2);
res += WM8960_Write_Reg(0x03, 0x006F | 0x0100); //ROUT1 Volume Set
//if (res == 0) Serial.println("WM8960 Configure HP_L and HP_R OUTPUTS"); else return res;
delay(delay1);
// Configure SPK_RP and SPK_RN
res = WM8960_Write_Reg(0x28, 0x007F | 0x0100); //Left Speaker Volume
delayMicroseconds(delay2);
res += WM8960_Write_Reg(0x29, 0x007F | 0x0100); //Right Speaker Volume
//if (res == 0) Serial.println("WM8960 Configure SPK_RP and SPK_RN"); else return res;
delay(delay1);
// Enable the OUTPUTS
res = WM8960_Write_Reg(0x31, 0x00F7); //Enable Class D Speaker Outputs
//if (res == 0) Serial.println("WM8960 Enable Class D Speaker Outputs"); else return res;
delay(delay1);
// Configure DAC volume
res = WM8960_Write_Reg(0x0a, 0x00FF | 0x0100);
delayMicroseconds(delay2);
res += WM8960_Write_Reg(0x0b, 0x00FF | 0x0100);
//if (res == 0) Serial.println("WM8960 Configure DAC volume"); else return res;
delay(delay1);
// 3D
// WM8960_Write_Reg(0x10, 0x001F);
// Configure MIXER
res = WM8960_Write_Reg(0x22, 1<<8 | 1<<7);
delayMicroseconds(delay2);
res += WM8960_Write_Reg(0x25, 1<<8 | 1<<7);
//if (res == 0) Serial.println("WM8960 Configure MIXER"); else return res;
delay(delay1);
// Jack Detect
res = WM8960_Write_Reg(0x18, 1<<6 | 0<<5);
delayMicroseconds(delay2);
res += WM8960_Write_Reg(0x17, 0x01C3);
delayMicroseconds(delay2);
res += WM8960_Write_Reg(0x30, 0x0009); //0x000D,0x0005
//if (res == 0) Serial.println("WM8960 Jack Detect"); else return res;
delay(delay1);
return 0;
}
/////////////////////////////////////////
// Setup
/////////////////////////////////////////
void setup()
{
uint8_t res;
pinMode(led, OUTPUT);
AudioMemory(12);
// uncomment these to use alternate pins
//Wire.setSCL(16);
//Wire.setSDA(17);
Wire.begin(); // join i2c bus (address optional for master)
//Serial.begin(9600);
//while (!Serial); // Leonardo: wait for serial monitor
//Serial.println(F("\nI2C Scanner"));
res = WM89060_Init();
//if (res != 0) Serial.println(res);
}
/////////////////////////////////////////
// Main
/////////////////////////////////////////
void loop()
{
delay(100); // wait 5 seconds for next scan
}
Last edited: