mod Naim CD-player with PMD-200 into stand alone DAC

OK, I hope we can fix it! If we don't find any other way I can open my Naim cd-player and try to record the communication with my scope - at least that way we should be able to reverse engineer the control code! If you send me your code I can look into it and see if I can help.



Did you also try I2C instead of SPI? In my Naim cd-player only reset(29), sck(26) and sda(25) are connected to the mcu (see schematic in the first post)...



Great, thanks for that!


-----------------------------------------------------------------------------------
HI jpk73
Thank you very much for your reply. I am so touched by your dedication. I will attach the code and schematic diagram, I take up your precious time, so I apologize.
The application framework is: DIR9001-->PMD200-->PCM1704. (PMD200 works in SPI mode).
1, DIR9001 as S/PDIF decoding setting serial output IIS, 16bit, 44.1Khz 256X rate---->PMD200 input port configuration 16bit, 44.1Khz, 256X; export configuration 24bit, two's complement, 8x, 352.8K (LRCK ).

2. The manual does not introduce the details of register I2C, so I have not tried I2C.

other:
a, PMD200 requires XTAL to have a clock signal before system Reset. The SPI pulse period is greater than 127ns.
b, I have detected the signal frequency at the input of PMD200 LRCK=44.1KHZ; BCK=2.8224M hz SCK=11.2896MHZ.
c. I used the same configuration (PMD200) to replace PCM1704 with PCM1702. I can hear the sound several times, but it occasionally makes a "ch...ch" sound. After switching back to PCM1704, there is always a "huh...sand" sound like the format configuration is incorrect.

therefore:
I guess: Is the PMD200 register information correct? (According to the SPI mode of the data sheet I read, the pulse period of 0x57 is set to be about 500ns, PMD200 only has 3 registers 0x00, 0x10, 0x20)


TKS
 

Attachments

  • dir9001.PNG
    dir9001.PNG
    145.6 KB · Views: 252
  • PMD200.PNG
    PMD200.PNG
    255.6 KB · Views: 248
  • DA.PNG
    DA.PNG
    271.5 KB · Views: 251
  • pmd200 CODE.txt
    4.1 KB · Views: 63
  • SPI regisitor.PNG
    SPI regisitor.PNG
    131 KB · Views: 245
Wow, thanks, you did a lot of work! The code looks good, but I am on tour so can't do deeper tests at the moment. Only the spi transfers look a bit strange to me, maybe you would like to have a look at the spi library I wrote for a DDS chip: see here...?

Regarding I2C: configuring the same settings means writing to the same registers, regardless of the serial communication protocol chosen, I thought...? Section 5 of the data sheet says "in the case of I2C, the base address of the PMD200 is determined by the value on pins D7..D0 at reset": I think we should try I2C as the Naim schematic confirms I2C as a working solution...
 
Last edited:
Here a code snippet from my library, it includes the same Arduino SPI.h as your code (but your code seems to not really use the Arduino SPI.h library):

spi_port->beginTransaction(settings);

for (int b = 0; b < 4; b++, ftw >>= 8) {
spi_port->transfer(ftw & 0xFF);}

spi_port->transfer(phase & 0xFF);
spi_port->endTransaction();

pulse(FQ_UD);
 
Last edited:
And here a code example for SPI portable to all Arduino compatible MCUs:

Code:
#include <SPI.h>
#define CS 10  // chip select pin
#define UD 11  // latch pin
SPISettings settings(100000, MSBFIRST, SPI_MODE1);

void setup() {
  SPI.begin();
}

void loop() {
  spi_transfer(B00000000, B00011100, B01110001);
  spi_transfer(B00010000, B00001100, B01100001);
  spi_transfer(B00100000, B00000100, B00000001);
  while (1);
}

void spi_transfer(byte value1, byte value2, byte value3) {
  digitalWrite(CS, LOW);
  SPI.beginTransaction(settings);
  SPI.transfer(value1);
  SPI.transfer(value2);
  SPI.transfer(value3);
  SPI.endTransaction();
  digitalWrite(CS, HIGH);
  digitalWrite(UD, HIGH);
  digitalWrite(UD, LOW);
 }
I took the binary values from your screen shot of the configuration bits. I am not sure though if handling of the HREQ and UD pins is needed. With I2C the code would be even easier, and handling of the HREQ pin is not required:

Code:
#include <Wire.h>  // I2C: SCL = 100kHz
#define device 99  // device number

void setup() {
  Wire.begin();
}

void loop() {
  i2c_transfer(B00000000, B00011100, B01110001);
  i2c_transfer(B00010000, B00001100, B01100001);
  i2c_transfer(B00100000, B00000100, B00000001);
  while (1);
}

void i2c_transfer(byte value1, byte value2, byte value3) {
  Wire.beginTransmission(device);
  Wire.write(value1);
  Wire.write(value2);
  Wire.write(value3);
  Wire.endTransmission();
 }
I would use an attiny for that and send it to deep sleep after configuration is done.
 
Last edited:
Some more notes... Setting SPI speed to 2MHz:
Code:
SPISettings settings(2000000, MSBFIRST, SPI_MODE1);
Transfer the 24bit commands in one block:
Code:
spi_transfer(0b000000000001110001110001);
spi_transfer(0b000100000000110001100001);
spi_transfer(0b001000000000010000000001);

void spi_transfer(uint32_t i) {
  byte value[3];
  value[0] = (i >> 16) & 0xFF;
  value[1] = (i >> 8) & 0xFF;
  value[2] = i & 0xFF;
  digitalWrite(CS, LOW);
  SPI.beginTransaction(settings);
  SPI.transfer(value, 3);
  SPI.endTransaction();
  digitalWrite(CS, HIGH);
}
 
Last edited:
Dear friend

I am not familiar with Arduino syntax, I found a piece of ARDUINO UNO to upload the code, and observed that A4 and A5 did not send data with a logic analyzer. I would like to ask you to help me review those parts of the code that need to be modified. thank you very much。
----------------------------------------------------------------------
Original Code:
#include <Wire.h> // I2C: SCL = 100kHz
#define device 99 // device number

void setup() {
Wire.begin();
}

void loop() {
i2c_transfer(B00000000, B00011100, B01110001);
i2c_transfer(B00010000, B00001100, B01100001);
i2c_transfer(B00100000, B00000100, B00000001);
while (1);
}

void i2c_transfer(byte value1, byte value2, byte value3) {
Wire.beginTransmission(device);
Wire.write(value1);
Wire.write(value2);
Wire.write(value3);
Wire.endTransmission();
}

-------------------------------------------------------------------------



I tried to modify
////////////////////////////////////////////////////////////////////////////////////////

#include <Wire.h> // I2C: SCL = 100kHz
#define device 00 // device number:00

void setup()
{
Wire.begin();
}

void loop()
{
i2c_transfer(0x00,0x10,0x22); // reg00,Arbitrary register value, modified according to the target register.
i2c_transfer(0x10,0x11,0x12); //reg10
i2c_transfer(0x20,0x22,0x23); //reg20
while (1);
}

void i2c_transfer(byte value1, byte value2, byte value3)
{
Wire.beginTransmission(device);
Wire.write(byte(0x00));
Wire.write(value1);
Wire.write(byte(0x00));
Wire.write(value2);
Wire.write(byte(0x00));
Wire.write(value3);
Wire.endTransmission();
}

-------------------------------------------------------------------------



thank you very much 。
 
OK try this:
Code:
#include <Wire.h> // I2C: SCL = 100kHz
#define device 00 // device number:00

void setup() {
  Wire.begin();
  delay(3000);    // [COLOR=Red]wait in ms for mcu to be ready...[/COLOR]
  i2c_transfer(0x00, 0x10, 0x22);
  i2c_transfer(0x10, 0x11, 0x12);
  i2c_transfer(0x20, 0x22, 0x23);
}

void i2c_transfer(byte value1, byte value2, byte value3) {
  Wire.beginTransmission(device);
  Wire.write(value1 & 0xFF);
  Wire.write(value2 & 0xFF);
  Wire.write(value3 & 0xFF);
  Wire.endTransmission();
}

void loop() {}
Or better in one block - I converted your hex values into the bit stream as represented in the data sheet:
Code:
#include <Wire.h>  // I2C: SCL = 100kHz
#define device 0   // device number

void setup() {
  Wire.begin();
  delay(3000);
  i2c_transfer(0b000000000001000000100010); // 0x00, 0x10, 0x22
  i2c_transfer(0b000100000001000100010010); // 0x10, 0x11, 0x12
  i2c_transfer(0b001000000010001000100011); // 0x20, 0x22, 0x23
}

void i2c_transfer(uint32_t i) {
  byte value[3];
  value[0] = (i >> 16) & 0xFF;
  value[1] = (i >> 8) & 0xFF;
  value[2] = i & 0xFF;
  Wire.beginTransmission(device);
  Wire.write(value, 3);
  Wire.endTransmission();
}

 void loop() {}
SPI version:
Code:
#include <SPI.h>
#define CS 10  // chip select pin
SPISettings settings(2000000, MSBFIRST, SPI_MODE1);

void setup() {
  SPI.begin();
  delay(3000);
  spi_transfer(0b000000000001000000100010); // 0x00, 0x10, 0x22
  spi_transfer(0b000100000001000100010010); // 0x10, 0x11, 0x12
  spi_transfer(0b001000000010001000100011); // 0x20, 0x22, 0x23
}

void spi_transfer(uint32_t i) {
  byte value[3];
  value[0] = (i >> 16) & 0xFF;
  value[1] = (i >> 8) & 0xFF;
  value[2] = i & 0xFF;
  digitalWrite(CS, LOW);
  SPI.beginTransaction(settings);
  SPI.transfer(value, 3);
  SPI.endTransaction();
  digitalWrite(CS, HIGH);
}

 void loop() {}
You can use Serial.write() or Serial.print() instead of Wire.write and SPI.transfer to watch output on serial monitor and see if routines work as intended.
 
Last edited:
HI my friend
Thank you very much for your help.
Today I uploaded the SPI code to the Arduino UNO and observed the data sent by the SPI host with a logic analyzer. It worked perfectly. Soon I used it to test the verification registers on the PMD200 engineering board. If it succeeds, I believe this project is your credit, thank you again, and please wait for my test message..........

In addition, I also tested the work of IIC. I’m sorry, maybe I haven’t understood it yet. The result I detected with the logic analyzer is not what I want. I still need to modify it. Please help, because in the whole During the project implementation, I saw that IIC may be easier to use than SPI in some occasions.
TKS
 

Attachments

  • IIC mode.PNG
    IIC mode.PNG
    220.8 KB · Views: 67
  • SPI.PNG
    SPI.PNG
    88.1 KB · Views: 161
Seems the device address is reserved... I made a new version with a couple of changes:
Code:
#include <Wire.h>              // I2C: SCL = 100kHz (default)
const byte slave_address = 10; // addresses 0 to 7 and 120 to 127 are reserved

void setup() {
  Wire.begin();
  delay(3000);
  i2c_transfer(0b000000000001000000100010); // 0x00, 0x10, 0x22
  i2c_transfer(0b000100000001000100010010); // 0x10, 0x11, 0x12
  i2c_transfer(0b001000000010001000100011); // 0x20, 0x22, 0x23
}

void i2c_transfer(uint32_t value) {
  byte buf[3];
  buf[0] = value >> 16;
  buf[1] = value >> 8;
  buf[2] = value & 0xFF;
  Wire.beginTransmission(slave_address);
  Wire.write(buf, sizeof buf);
  Wire.endTransmission();
  delayMicroseconds(300);
}

void loop() {}
If that doesn't work, try the simplified version with serial monitor turned on:
Code:
#include <Wire.h>              // I2C: SCL = 100kHz (default)
const byte slave_address = 10; // addresses 0 to 7 and 120 to 127 are reserved

void setup() {
  Wire.begin();
  Serial.begin(115200);
  delay(3000);
  i2c_transfer_simple();
}

void i2c_transfer_simple() {
  Wire.beginTransmission(slave_address);
  Wire.write(0x00);
  Wire.write(0x10);
  Wire.write(0x22);
  Wire.write(0x10);
  Wire.write(0x11);
  Wire.write(0x12);
  Wire.write(0x20);
  Wire.write(0x22);
  Wire.write(0x23);
  if (Wire.endTransmission()) {Serial.println("error!");} else {Serial.println("success!");}
}

void loop() {}
 
Last edited:
Hi friend
The attachment is the result of running the first piece of code. :D
The second code run prompts "error" . I guess there will be data response only if I want to connect to the slave device ? Or am I doing it incorrectly ?

thank you very much。
 

Attachments

  • I2C.PNG
    I2C.PNG
    112.8 KB · Views: 134
Yes, you need a slave connected. Looking at the NACK/ACK bit (the 9th bit) SDA was high during the 9th SCL pulse which means there was no answer from the slave. Wire.endTransmission() returns that bit. Maybe you can hook up the PMD or another MCU or configure the logic analyzer as the slave?
 
Any news? With your existing setup you could try the following to make i2c ignore that no slave is connected:

In the file twi.cpp (in folder arduino_avr/libraries/Wire/utility on your hard disk) search this code:
Code:
if (twi_error == 0xFF)
    return 0;	// success
  else if (twi_error == TW_MT_SLA_NACK)
    return 2;	// error: address send, nack received
  else if (twi_error == TW_MT_DATA_NACK)
    return 3;	// error: data send, nack received
  else
    return 4;	// other twi error
And change all of the return lines to "return 0" and compile/upload. This should bypass the NACK handling of the master-sender so you can see the outgoing communication with your logic analyzer.
 
Hi my friend

I have done a lot of this test in 2 days, but still can't communicate with PMD200.
1. First is the IIC scheme. I set D0~D7 of PMD200 to LOW level. I understand it as the default address 0X00, and the register is not configured. So I used the Bitbang library function to search for I2C slave devices, but it was not found.
2. I tested the SPI communication. I used 0X00, 0XAA, 0X37 to send 3 8-bit data to the PMD200 register, but I used the LOGIC analyzer to check that only 0X00, 0XAA was monitored, and 0X37 was lost. I can't find the reason.

I think:
Is the register address information inaccurate? I don’t know if that friend has the official data sheet instead of the first version.
 
I am home for a couple of hours and will be on tour again for the next few weeks, so in an extreme hurry I opened the Naim, hooked up the scope and looked at the communication:

l8jhHP9.jpg


The PMD200 has address 0x00 and the PIC writes to it with 8bit length and MSB-first at a clock speed of only 15kHz:

d2TyVMe.jpg


The commands sent to the PMD200:
Code:
0x00    0x09    0x81
0x10    0x1F    0x65
0x20    0x00    0x00
This code block is sent 7~11 times, I don't know why, but as soon as one byte fails it is re-sent:

iXvFyaD.jpg


I think we now have all information to make the PMD200 work :cool:
 
Last edited:
hi my friend

I'm really sorry to take your precious time to help me find the problem.

Now:
The reverse decoding provided by you is very important, yes, PMD200 is already working. It is now in SPI mode, and I used the code you wrote. Thanks again.

Although let it run this time, there are still some problems.

1. As you said, why do you send it 7~11 times? I did an experiment when the SPI host sent the chip (PMD200) once and it didn't work until I put the sent data into the main loop. At this time, the configuration register became effective. I don't understand why this phenomenon?

2. I reduced the sending speed of the host because I tested that PMD200 will not receive data if it sends data at a high speed. 、According to my guess, why is the SPI communication interface so slow for a chip with DSP as the core?

3. From your reverse register value, let me compare, the PMD200 entry parameter is set to 24bit, IIS, 384xfs, 44.1K, right-justified format. Output: 32Bit, 8X, IIS format.

Among them: the filter works at LRCK=352Khz sampling, WCKO=1.44Mhz



----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
#include <SPI.h>
#define CS 10 // chip select pin
SPISettings settings(1000000, MSBFIRST, SPI_MODE1);

void setup() {
SPI.begin();
delay(3000);
spi_transfer(0b000000000000100110000001 ); // 0x00, 0x09, 0x81
spi_transfer(0b000100000001111101100101 ); // 0x10, 0x1f, 0x65
spi_transfer(0b001000000000000000000000); // 0x20, 0x22, 0x23
}

void spi_transfer(uint32_t i) {
byte value[3];
value[0] = (i >> 16) & 0xFF;
value[1] = (i >> 8) & 0xFF;
value[2] = i & 0xFF;
digitalWrite(CS, LOW);
SPI.beginTransaction(settings);
SPI.transfer(value, 3);
SPI.endTransaction();
digitalWrite(CS, HIGH);
}

void loop() {

spi_transfer(0b000000000000100110000001 ); // 0x00, 0x09, 0x81
spi_transfer(0b000100000001111101100101 ); // 0x10, 0x1f, 0x65
spi_transfer(0b001000000000000000000000); // 0x20, 0x22, 0x23


}
 
Last edited:
hi my friend
I did a test just now. First of all, I kept pressing UNO's RESET after adding the written value to the loop. After repeating it several times, I could observe that the PMD200 output waveform became more and more accurate.


The PMD200 test control code needs to be adjusted as follows:
1. The writing speed must be slow enough, SPI is about 1Mhz (the data sheet indicates that the pulse width is greater than 127us).

2. Whether the transmission is successful or not requires confirmation and repeated transmission, and multiple transmissions are required. (There is no information to confirm why this is done)

3. After the data transmission is completed and successful, the MCU enters the sleep state.
 

Attachments

  • pmd200.jpg
    pmd200.jpg
    964.5 KB · Views: 62