Communicating with an odd Serial device and a Teensy

Rezo

Well-known member
I have a set of old Pioneer CDJ 1000 MK1s that I am working on converting to run off a Teensy 4.x
I want to use the CDJ's original buttons and LEDs and control them though the main display board (I am not going to use the VFD displays)

Looking at the service manuals, I was able to determine that the display board (using a uPD780204 microcontroller), which controls the entire front panel, is connected to the main assy via SPI or SPI like form.

There are 5 signals (pins 1-5 on the MFLB left connector below):
  • Serial Clock - which is generated by the main assy and fed to the display assy
  • Serial Data Out - from the main assy to the display assy
  • Serial Data In - from the display assy to the main assy
  • Reset - from the main assy to the display assy
  • Busy (Key1) - from the display assy to the main assy
There is no CS/SS line here, as this is the only device on the bus

1732829016767.png


I've hooked up a logic analyzer to try figure out how they are speaking to each other, but having a hard time figuring the message sent to the display assy
1732828470740.png

While most lines here are active-low, for some reason, the Serial Data In is active-high - is this common for SPI?
I also noticed that each "frame" contains up to 12 bytes, sent in 2.3ms windows apart


I would like to eventually use a T3.2 or T4.0 to control the display assy and read button inputs, but am unsure how to go at this.
First, I would like to read the Serial Data In line to catch button clicks etc, then be able to send payloads back to the display assy to control some of the LEDs


As this does not look like a standard SPI implementation, wondering if someone can guide me on how to get started here? Perhaps FlexIO shifters and timers might be a direction here for this "custom" serial protocol?

Also attached the LA log file
 

Attachments

  • cdj1000mk1_2.sal.zip
    184 KB · Views: 23
@KurtE @mjs513 @Paul would any of you have some suggestions on how to go at this here?
Can SPI transmit data and receive data at there same, or would I need to setup two DMA channels for this?
 
Yep, I read the SPI page on the PJRC site and found that mentioned there eventually
 
As @joepasquariello mentioned - SPI works full duplex...

And there are lots of examples of code that uses DMA. For example, most of our display drivers have DMA output. Most of them don't do
much if any DMA input.

The SPI library has a DMA transfer method:
Code:
bool SPIClass::transfer(const void *buf, void *retbuf, size_t count, EventResponderRef event_responder) {

There is code setup to setup the DMA and do a single transfer... Actually the single transfer, might be more complicated in that
if your transfer count is > how many bytes that can be transferred in single setup, it will detect that, and restart the transfer to
get the next chunk...
 
I think for now I will just send some dummy loads and read the input to see what bits change when I click buttons!

Will come back (for sure) once I get that part done
 
Got the T3.2 hooked up, streamed 12 bytes at a time and printed out the values - could see byte values changing when clicking some of the buttons. Others? Nothing

C++:
#include <SPI.h>

// Define pins
#define RESET_PIN 15
#define BUSY_PIN 14

// SPI settings
const uint32_t SPI_CLOCK = 1020000; // 1 MHz
const SPISettings spiSettings(SPI_CLOCK, MSBFIRST, SPI_MODE1);

// Timing
const uint32_t SEND_INTERVAL = 3000; // 2.3ms in microseconds
uint32_t lastSendTime = 0;

// Data buffers
uint8_t txBuffer[12] = {0xFF, 0xFF,0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
uint8_t rxBuffer[12];

void setup() {
  // Initialize SPI
  SPI.begin();

  // Set pin modes
  pinMode(RESET_PIN, OUTPUT);
  pinMode(BUSY_PIN, INPUT);

  // Reset the slave
  digitalWrite(RESET_PIN, LOW);
  delay(10); // Hold reset low for 10ms
  digitalWrite(RESET_PIN, HIGH);

  // Start serial for debugging
  Serial.begin(115200);
}

void loop() {
  // Check if it's time to send data
  uint32_t currentTime = micros();
  if (currentTime - lastSendTime >= SEND_INTERVAL) {
    // Wait for the busy line to go low
    while (digitalRead(BUSY_PIN) == LOW) {
      // Optional: timeout or error handling can be added here
    }

    // Begin SPI transaction
    SPI.beginTransaction(spiSettings);

    // Send and receive 12 bytes
    for (int i = 0; i < 12; i++) {
      rxBuffer[i] = SPI.transfer(txBuffer[i]);
    }

    // End SPI transaction
    SPI.endTransaction();

    // Debug: Print received data
    Serial.print("Received: ");
    for (int i = 0; i < 12; i++) {
      Serial.print(rxBuffer[i], HEX);
      Serial.print(" ");
    }
    Serial.println();

    // Update last send time
    lastSendTime = currentTime;
  }
}


I believe I need to figure out hot the host is indicating the start of a sequence.

Now this is where I need help - can be on the T3.2 or a T4.x or MM - I need to sniff the MISO/MOSI lines via the Teensy and print them out - but how to I get it to detect the 2.3ms hold between frames in order to print the data out in the right order/grouping?

Can anyone help me with that? I know I need to set use the Teensy SPI in Slave mode., but need some help with the logic inside
 
As this does not look like a standard SPI implementation, wondering if someone can guide me on how to get started here?

First, use Teensy 4.x. It's about 11 times faster than Teensy 3.2 (which is discontinued) and it has 480 Mbit USB, which gives you far more ability to capture fast signals and transmit lots of info to your PC. So put that old Teensy 3.2 away for a less demanding project and use Teensy 4.x for this.

Can you zoom in and show a screenshot of the clock, so we can get an idea of how fast this signal is? I know you shared the raw logic analyzer data... and maybe someone with that software will dive in, but for the sake of conversation a screenshot of how fast the clock changes would give all of us a better idea of the challenge you're facing.

1733154925254.png


Also a couple more questions... I know you showed that schematic, but maybe you could explain in simple terms (or photos) how this equipment is built? I got the impression it's in 2 main parts, the user interface and a main processor board. Is that right? If so, have you figured out which side is transmitting the clock and which receive the clock? I'm guessing your initial goal is just to snoop the communication and learn the data format? But once you learn such things, are you aiming to replace the UI part or the main processor part (or other stuff, if I've misunderstood things... that schematic seems to show up to 9 parts)
 
@Paul I used the T3.2 because I had one laying around and it's 5v tolerant
I'll dig up my logic level shifters and use the T4

So your assumption is right about the modules - there is a main board that holds a processor, FPGA and some DSPs, and there is a control unit that has the uPD780240 that captures signals from push buttons and based on commands from the main unit, will light up some leds and the VFD display

My goal is to remove the main board and replace it with my SDRAM teensy (Devboard v5) with an LCD display, and then use the control panel and it's LEDs instead of having to rout that all to a multiplexer and write other complex logic

I do want to snoof to see if there is some pattern or sequence to the frames with the Teensy, as it's harder to do with the logic analyzer software.

The clock signal is generated by the main unit rated at 1Mhz

Here is a zoomed in image start of frame
1733159189600.png


Here is a zoomed in image end of frame (different frame)
1733159237572.png
 
Last edited:
Can you zoom in and show a screenshot of the clock, so we can get an idea of how fast this signal is? I know you shared the raw logic analyzer data... and maybe someone with that software will dive in, but for the sake of conversation a screenshot of how fast the clock changes would give all of us a better idea of the challenge you're facing.
For those who may want to look at the posted data, you can download the software without having to own one of their logic analyzers

 
Looks like the fastest clock speed is pretty slow 1 MHz. My initial impression is the data changes during falling edges and is valid/stable on the rising edge. So perhaps you do useful capture with attachInterrupt() on rising edge?

Here's a rough idea, with a trick to speed up response a bit by bypassing the normal attachInterrupt handler. This only works if you have a single pin you're using with attachInterrupt, and if you choose a different pin you'll need to loop up the GPIO register and bitmask.

Code:
void setup() {
        pinMode(6, INPUT); // data pin
        pinMode(5, INPUT_PULLUP); // clock pin
        while (digitalReadFast(5) == LOW) ; // wait for normally high
        attachInterrupt(5, mycapture, CHANGE);
        attachInterruptVector(IRQ_GPIO6789, capture); // respond faster
        NVIC_SET_PRIORITY(IRQ_GPIO6789, 48); // higher interrupt priority
}

void capture() {
        bool clock_pin = digitalReadFast(5);
        bool data_pin = digitalReadFast(6);
        static uint32_t prior_cycle_count;
        uint32_t cycle_count = ARM_DWT_CYCCNT;
        GPIO9_ISR = 1<<8; // clear interrupt status, pin 5 = EMC_08 = GPIO9.8

        // TODO: actually do stuff with the clock, data and cycle count...

        prior_cycle_count = cycle_count;
}

Looks like this protocol has 1 MHz clock for data, and also uses a long single clock pulse for some sort of reset or begin/end of data or other unknown circumstance. Inside this capture() function you would first check the clock read to tell if you've just captured a rising or falling edge. In the rising edge case, you'd subtract the prior_cycle_count from cycle_count and it the number if much larger than 300, then you've just detected the long pulse. Otherwise it's probably a data bit. You don't have a lot of time to waste, but with Teensy 4.x running at 600 MHz you can certainly do simple stuff like count the number of bits, shift them into a byte, and put the byte into a buffer and update volatile head & tail indexes. Then your main loop can monitor the buffer (look at HardwareSerial or similar libraries for examples) and use Serial.print() to tell you what happened. Teensy 4.x has plenty of memory, so you can use a buffer of 16 or 32 bit integers rather than just bytes, where you the other bits can give you info like detected timing.

What you'll do with all this, I'm not sure. Reverse engineering is usually a long road of incremental discoveries. But hopefully this quick recipe for capturing the clock and data and cycle count for each clock edge helps you get started on that road.
 
Thanks for the code example and guidance @Paul!

I chatGPT'd this a bit to help me put together a basic example of printing the data - would this be the right direction?


C++:
volatile uint8_t frame_buffer[12]; // Holds the current frame being built
volatile uint8_t frame_index = 0;  // Current byte index in the frame
volatile uint8_t bit_index = 0;    // Current bit index in the current byte
volatile bool frame_ready = false; // Flag to indicate a complete frame
volatile uint32_t prior_cycle_count = 0;

void capture() {
    static uint8_t current_byte = 0; // Current byte being built
    uint32_t cycle_count = ARM_DWT_CYCCNT; // Capture precise timing
    GPIO9_ISR = 1 << 8; // Clear interrupt status for GPIO9.8 (pin 5)

    // Determine clock state (rising or falling edge)
    bool clock_pin = digitalReadFast(5);

    if (clock_pin) { // Rising edge detected
        uint32_t delta_cycles = cycle_count - prior_cycle_count;
        prior_cycle_count = cycle_count;

        if (delta_cycles > 300) { // Long pulse detected (frame boundary)
            frame_index = 0;      // Reset frame
            bit_index = 0;
            frame_ready = false;  // Invalidate the previous frame
        } else { // Normal clock pulse - process data bit
            bool data_bit = digitalReadFast(6);
            current_byte = (current_byte << 1) | data_bit; // Shift in the data bit
            bit_index++;

            if (bit_index == 8) { // Byte complete
                frame_buffer[frame_index++] = current_byte;
                current_byte = 0;
                bit_index = 0;

                if (frame_index == 12) { // Frame complete
                    frame_ready = true;
                    frame_index = 0; // Reset for the next frame
                }
            }
        }
    }
}

void setup() {
        pinMode(6, INPUT); // data pin
        pinMode(5, INPUT_PULLUP); // clock pin
        while (digitalReadFast(5) == LOW) ; // wait for normally high
        attachInterrupt(5, mycapture, CHANGE);
        attachInterruptVector(IRQ_GPIO6789, capture); // respond faster
        NVIC_SET_PRIORITY(IRQ_GPIO6789, 48); // higher interrupt priority
        Serial.begin(115200);
}


void loop() {
    if (frame_ready) {
        Serial.print("Frame: ");
        for (uint8_t i = 0; i < 12; i++) {
            Serial.printf("%02X ", frame_buffer[i]); // Print each byte in hexadecimal
        }
        Serial.println();
        frame_ready = false; // Reset the flag
    }
}

I can throw the data into a larger buffer and then print that in bigger intervals to not overload serial - I guess that would be a better idea.
 
My main advice is to start as small and simple as possible, like even just increment a variable each time the interrupt triggers and occasionally print it from the loop() function. Get the smallest thing working first. What the Arduino Serial Monitor while also capturing on the logic analyzer. The idea is to get small and simple but reliable success. Then slowly build on that success little by little, watching the analyzer as you go. Even if you have a lot of experience, this is the path for reverse engineering because you will learn things about the protocol as you go.
 
Is there a suggested way to isolate the signal from the main board - maybe optocouplers? I'm getting zapped by static from the ungrounded chassis and don't want to fry my LA or my Mac
 
Well the CDJs have a 2 pin power connector on them - so no earthing there.
And really don’t want to earth through the Mac>USB3>Monitor

I can try doing it on the dining room table and put some rubber sole shoes on to isolate myself too I guess.
 
I was eventually able to get the data into the right format and order!
Used a more simplified sketch. What did the trick was writing the data LSB first

C++:
#define SCK_PIN 5  // Clock pin (SCK)
#define MOSI_PIN 6 // Data pin (MOSI)

volatile uint8_t capturedByte = 0; // Byte being constructed
volatile bool byteReady = false;  // Flag to indicate byte capture is complete

void setup() {
    Serial.begin(115200);
    pinMode(MOSI_PIN, INPUT);      // Data pin
    pinMode(SCK_PIN, INPUT_PULLUP); // Clock pin

    // Attach interrupt on FALLING edge of SCK
    attachInterrupt(digitalPinToInterrupt(SCK_PIN), mycapture, FALLING);

    Serial.println("Begin sniffing");
}

uint32_t lastMicros = 0;  // Variable to store the previous time in microseconds

void loop() {
    if (byteReady) {
        uint32_t currentMicros = micros();  // Current time in microseconds

        // Calculate the delta between the current and previous time
        uint32_t deltaMicros = currentMicros - lastMicros;
        if (deltaMicros > 2000){
          Serial.println("Delta");
          //  Serial.println("");
        }
        // Print captured byte and delta time in microseconds
        //Serial.print("Byte: 0x");
        //Serial.print(capturedByte, HEX);
        Serial.printf("0x%X ", capturedByte);
        

        // Update lastMicros with the current time for the next loop
        lastMicros = currentMicros;

        byteReady = false; // Clear the flag
    }
}


void mycapture() {
    static uint8_t bitCounter = 0; // Bit counter for current byte
    static uint8_t tempByte = 0;   // Temporary storage for byte

    // Read MOSI line and shift in the bit
    bool data = digitalReadFast(MOSI_PIN);
    //tempByte = (tempByte << 1) | data; // MSB first
    tempByte = (tempByte >> 1) | (data << 7); //LSB First

    bitCounter++;

    // If 8 bits are collected, store the byte and reset for the next
    if (bitCounter >= 8) {
        capturedByte = tempByte;
        tempByte = 0;
        bitCounter = 0;
        byteReady = true;
    }
}


And the cleaned up output:
Code:
0x1 0x88 0xD0 0x8 0x0 0x0 0x0 0x0 0x0 0x0 0xD 0x6E
0x2 0x0 0x0 0x0 0x0 0x78 0xA0 0x90 0xEB 0x71 0xD 0x13
0x3 0x2 0xA5 0x0 0x21 0x0 0x0 0x0 0x90 0x0 0xD 0x68
0x4 0x0 0x0 0x0 0xE0 0xC3 0xEE 0xCB 0x0 0xBC 0xD 0x29
0x5 0x80 0x84 0xC0 0x0 0x80 0x7B 0x80 0x84 0x0 0xD 0xD5
0x6 0x0 0xFF 0xE0 0xE7 0x87 0x38 0xF7 0xF1 0xFF 0xD 0x7F
0x7 0xFF 0xFE 0xFE 0xFF 0xFF 0xFF 0xF8 0x0 0x3 0xD 0x7
0x8 0xFF 0xBE 0xEF 0x6E 0xBE 0xBF 0xFF 0x3F 0x3F 0xD 0x29
0x9 0x7F 0x7F 0x7F 0x7F 0x7F 0x0 0x40 0xC1 0x0 0xD 0x92
0xA 0xC0 0x1 0x1C 0x0 0x42 0x1 0xFF 0xFF 0xA0 0xD 0xD5
0xB 0x1C 0x0 0x3 0xE0 0x0 0xC 0x1C 0x0 0x3 0xD 0x42
0xC 0xE0 0x0 0x0 0xC 0x0 0x0 0x0 0x0 0x0 0xD 0x5
 
0x1 0x88 0xD0 0x8 0x0 0x0 0x0 0x0 0x0 0x0 0xD 0x6E
0x2 0x0 0x0 0x0 0x0 0x78 0xA0 0x90 0xEB 0x71 0xD 0x13
0x3 0x2 0xA5 0x0 0x21 0x0 0x0 0x0 0x90 0x0 0xD 0x68
0x4 0x0 0x0 0x0 0xE0 0xC3 0xEE 0xCB 0x0 0xBC 0xD 0x29
0x5 0x80 0x84 0xC0 0x0 0x80 0x7B 0x80 0x84 0x0 0xD 0xD5
0x6 0x0 0xFF 0xE0 0xE7 0x87 0x38 0xF7 0xF1 0xFF 0xD 0x7F
0x7 0xFF 0xFE 0xFE 0xFF 0xFF 0xFF 0xF8 0x0 0x3 0xD 0x7
0x8 0xFF 0xBE 0xEF 0x6E 0xBE 0xBF 0xFF 0x3F 0x3F 0xD 0x29
0x9 0x7F 0x7F 0x7F 0x7F 0x7F 0x0 0x40 0xC1 0x0 0xD 0x92
0xA 0xC0 0x1 0x1C 0x0 0x42 0x1 0xFF 0xFF 0xA0 0xD 0xD5
0xB 0x1C 0x0 0x3 0xE0 0x0 0xC 0x1C 0x0 0x3 0xD 0x42
0xC 0xE0 0x0 0x0 0xC 0x0 0x0 0x0 0x0 0x0 0xD 0x5

0x1 0x88 0xD0 0x8 0x0 0x0 0x0 0x0 0x0 0x0 0xD 0x6E
0x2 0x0 0x0 0x0 0x0 0x78 0xA0 0x90 0xEB 0x71 0xD 0x13
0x3 0x2 0xA5 0x0 0x21 0x0 0x0 0x0 0x90 0x0 0xD 0x68
0x4 0x0 0x0 0x0 0xE0 0xC3 0xEE 0xCB 0x0 0xBC 0xD 0x29
0x5 0x80 0x84 0xC0 0x0 0x80 0x7B 0x80 0x84 0x0 0xD 0xD5
0x6 0x0 0xFF 0xE0 0xE7 0x87 0x38 0xF7 0xF1 0xFF 0xD 0x7F
0x7 0xFF 0xFE 0xFE 0xFF 0xFF 0xFF 0xF8 0x0 0x3 0xD 0x7
0x8 0xFF 0xBE 0xEF 0x6E 0xBE 0xBF 0xFF 0x3F 0x3F 0xD 0x29
0x9 0x7F 0x7F 0x7F 0x7F 0x7F 0x0 0x40 0xC1 0x0 0xD 0x92
0xA 0xC0 0x1 0x1C 0x0 0x42 0x1 0xFF 0xFF 0xA0 0xD 0xD5
0xB 0x1C 0x0 0x3 0xE0 0x0 0xC 0x1C 0x0 0x3 0xD 0x42
0xC 0xE0 0x0 0x0 0xC 0x0 0x0 0x0 0x0 0x0 0xD 0x5

0x1 0x88 0xD0 0x8 0x0 0x0 0x0 0x0 0x0 0x0 0xD 0x6E
0x2 0x0 0x0 0x0 0x0 0x78 0xA0 0x90 0xEB 0x71 0xD 0x13
0x3 0x2 0xA5 0x0 0x21 0x0 0x0 0x0 0x90 0x0 0xD 0x68
0x4 0x0 0x0 0x0 0xE0 0xC3 0xEE 0xCB 0x0 0xBC 0xD 0x29
0x5 0x80 0x84 0xC0 0x0 0x80 0x7B 0x80 0x84 0x0 0xD 0xD5
0x6 0x0 0xFF 0xE0 0xE7 0x87 0x38 0xF7 0xF1 0xFF 0xD 0x7F
0x7 0xFF 0xFE 0xFE 0xFF 0xFF 0xFF 0xF8 0x0 0x3 0xD 0x7
0x8 0xFF 0xBE 0xEF 0x6E 0xBE 0xBF 0xFF 0x3F 0x3F 0xD 0x29
0x9 0x7F 0x7F 0x7F 0x7F 0x7F 0x0 0x40 0xC1 0x4 0xD 0x96
0xA 0xC0 0x1 0x1C 0x0 0x42 0x1 0xFF 0xFF 0xA0 0xD 0xD5
0xB 0x1C 0x0 0x3 0xE0 0x0 0xC 0x1C 0x0 0x3 0xD 0x42
0xC 0xE0 0x0 0x0 0xC 0x0 0x0 0x0 0x0 0x0 0xD 0x5

0x1 0x88 0xD0 0x8 0x0 0x0 0x0 0x0 0x0 0x0 0xD 0x6E
0x2 0x0 0x0 0x0 0x0 0x78 0xA0 0x90 0xEB 0x71 0xD 0x13
0x3 0x2 0xA5 0x0 0x21 0x0 0x0 0x0 0x90 0x0 0xD 0x68
0x4 0x0 0x0 0x0 0xE0 0xC3 0xEE 0xCB 0x0 0xBC 0xD 0x29
0x5 0x80 0x84 0xC0 0x0 0x80 0x7B 0x80 0x84 0x0 0xD 0xD5
0x6 0x0 0xFF 0xE0 0xE7 0x87 0x38 0xF7 0xF1 0xFF 0xD 0x7F
0x7 0xFF 0xFE 0xFE 0xFF 0xFF 0xFF 0xF8 0x0 0x3 0xD 0x7
0x8 0xFF 0xBE 0xEF 0x6E 0xBE 0xBF 0xFF 0x3F 0x3F 0xD 0x29
0x9 0x7F 0x7F 0x7F 0x7F 0x7F 0x0 0x40 0xC1 0x4 0xD 0x96
0xA 0xC0 0x1 0x1C 0x0 0x42 0x1 0xFF 0xFF 0xA0 0xD 0xD5
0xB 0x1C 0x0 0x3 0xE0 0x0 0xC 0x1C 0x0 0x3 0xD 0x42
0xC 0xE0 0x0 0x0 0xC 0x0 0x0 0x0 0x0 0x0 0xD 0x5

0x1 0x88 0xD0 0x8 0x0 0x0 0x0 0x0 0x0 0x0 0xD 0x6E
0x2 0x0 0x0 0x0 0x0 0x78 0xA0 0x90 0xEB 0x71 0xD 0x13
0x3 0x2 0xA5 0x0 0x21 0x0 0x0 0x0 0x90 0x0 0xD 0x68
0x4 0x0 0x0 0x0 0xE0 0xC3 0xEE 0xCB 0x0 0xBC 0xD 0x29
0x5 0x80 0x84 0xC0 0x0 0x80 0x7B 0x80 0x84 0x0 0xD 0xD5
0x6 0x0 0xFF 0xE0 0xE7 0x87 0x38 0xF7 0xF1 0xFF 0xD 0x7F
0x7 0xFF 0xFE 0xFE 0xFF 0xFF 0xFF 0xF8 0x0 0x3 0xD 0x7
0x8 0xFF 0xBE 0xEF 0x6E 0xBE 0xBF 0xFF 0x3F 0x3F 0xD 0x29
0x9 0x7F 0x7F 0x7F 0x7F 0x7F 0x0 0x40 0xC1 0x4 0xD 0x96
0xA 0xC0 0x1 0x1C 0x0 0x42 0x1 0xFF 0xFF 0xA0 0xD 0xD5
0xB 0x1C 0x0 0x3 0xE0 0x0 0xC 0x1C 0x0 0x3 0xD 0x42
0xC 0xE0 0x0 0x0 0xC 0x0 0x0 0x0 0x0 0x0 0xD 0x5

0x1 0x88 0xD0 0x8 0x0 0x0 0x0 0x0 0x0 0x0 0xD 0x6E
0x2 0x0 0x0 0x0 0x0 0x78 0xA0 0x90 0xEB 0x71 0xD 0x13
0x3 0x2 0xA5 0x0 0x21 0x0 0x0 0x0 0x90 0x0 0xD 0x68
0x4 0x0 0x0 0x0 0xE0 0xC3 0xEE 0xCB 0x0 0xBC 0xD 0x29
0x5 0x80 0x84 0xC0 0x0 0x80 0x7B 0x80 0x84 0x0 0xD 0xD5
0x6 0x0 0xFF 0xE0 0xE7 0x87 0x38 0xF7 0xF1 0xFF 0xD 0x7F
0x7 0xFF 0xFE 0xFE 0xFF 0xFF 0xFF 0xF8 0x0 0x3 0xD 0x7
0x8 0xFF 0xBE 0xEF 0x6E 0xBE 0xBF 0xFF 0x3F 0x3F 0xD 0x29
0x9 0x7F 0x7F 0x7F 0x7F 0x7F 0x0 0x40 0xC1 0x4 0xD 0x96
0xA 0xC0 0x1 0x1C 0x0 0x42 0x1 0xFF 0xFF 0xA0 0xD 0xD5
0xB 0x1C 0x0 0x3 0xE0 0x0 0xC 0x1C 0x0 0x3 0xD 0x42
0xC 0xE0 0x0 0x0 0xC 0x0 0x0 0x0 0x0 0x0 0xD 0x5

0x1 0x88 0xD0 0x8 0x0 0x0 0x0 0x0 0x0 0x0 0xD 0x6E
0x2 0x0 0x0 0x0 0x0 0x78 0xA0 0x90 0xEB 0x71 0xD 0x13
0x3 0x2 0xA5 0x0 0x21 0x0 0x0 0x0 0x90 0x0 0xD 0x68
0x4 0x0 0x0 0x0 0xE0 0xC3 0xEE 0xCB 0x0 0xBC 0xD 0x29
0x5 0x80 0x84 0xC0 0x0 0x80 0x7B 0x80 0x84 0x0 0xD 0xD5
0x6 0x0 0xFF 0xE0 0xE7 0x87 0x38 0xF7 0xF1 0xFF 0xD 0x7F
0x7 0xFF 0xFE 0xFE 0xFF 0xFF 0xFF 0xF8 0x0 0x3 0xD 0x7
0x8 0xFF 0xBE 0xEF 0x6E 0xBE 0xBF 0xFF 0x3F 0x3F 0xD 0x29
0x9 0x7F 0x7F 0x7F 0x7F 0x7F 0x0 0x40 0xC1 0x4 0xD 0x96
0xA 0xC0 0x1 0x1C 0x0 0x42 0x1 0xFF 0xFF 0xA0 0xD 0xD5
0xB 0x1C 0x0 0x3 0xE0 0x0 0xC 0x1C 0x0 0x3 0xD 0x42
0xC 0xE0 0x0 0x0 0xC 0x0 0x0 0x0 0x0 0x0 0xD 0x5

0x1 0x88 0xD0 0x8 0x0 0x0 0x0 0x0 0x0 0x0 0xD 0x6E
0x2 0x0 0x0 0x0 0x0 0x78 0xA0 0x90 0xEB 0x71 0xD 0x13
0x3 0x2 0xA5 0x0 0x21 0x0 0x0 0x0 0x90 0x0 0xD 0x68
0x4 0x0 0x0 0x0 0xE0 0xC3 0xEE 0xCB 0x0 0xBC 0xD 0x29
0x5 0x80 0x84 0xC0 0x0 0x80 0x7B 0x80 0x84 0x0 0xD 0xD5
0x6 0x0 0xFF 0xE0 0xE7 0x87 0x38 0xF7 0xF1 0xFF 0xD 0x7F
0x7 0xFF 0xFE 0xFE 0xFF 0xFF 0xFF 0xF8 0x0 0x3 0xD 0x7
0x8 0xFF 0xBE 0xEF 0x6E 0xBE 0xBF 0xFF 0x3F 0x3F 0xD 0x29
0x9 0x7F 0x7F 0x7F 0x7F 0x7F 0x0 0x40 0xC1 0x4 0xD 0x96
0xA 0xC0 0x1 0x1C 0x0 0x42 0x1 0xFF 0xFF 0xA0 0xD 0xD5
0xB 0x1C 0x0 0x3 0xE0 0x0 0xC 0x1C 0x0 0x3 0xD 0x42
0xC 0xE0 0x0 0x0 0xC 0x0 0x0 0x0 0x0 0x0 0xD 0x5

0x1 0x88 0xD0 0x8 0x0 0x0 0x0 0x0 0x0 0x0 0xD 0x6E
0x2 0x0 0x0 0x0 0x0 0x78 0xA0 0x90 0xEB 0x71 0xD 0x13
0x3 0x2 0xA5 0x0 0x21 0x0 0x0 0x0 0x90 0x0 0xD 0x68
0x4 0x0 0x0 0x0 0xE0 0xC3 0xEE 0xCB 0x0 0xBC 0xD 0x29
0x5 0x80 0x84 0xC0 0x0 0x80 0x7B 0x80 0x84 0x0 0xD 0xD5
0x6 0x0 0xFF 0xE0 0xE7 0x87 0x38 0xF7 0xF1 0xFF 0xD 0x7F
0x7 0xFF 0xFE 0xFE 0xFF 0xFF 0xFF 0xF8 0x0 0x3 0xD 0x7
0x8 0xFF 0xBE 0xEF 0x6E 0xBE 0xBF 0xFF 0x3F 0x3F 0xD 0x29
0x9 0x7F 0x7F 0x7F 0x7F 0x7F 0x0 0x40 0xC1 0x4 0xD 0x96
0xA 0xC0 0x1 0x1C 0x0 0x42 0x1 0xFF 0xFF 0xA0 0xD 0xD5
0xB 0x1C 0x0 0x3 0xE0 0x0 0xC 0x1C 0x0 0x3 0xD 0x42
0xC 0xE0 0x0 0x0 0xC 0x0 0x0 0x0 0x0 0x0 0xD 0x5

0x1 0x88 0xD0 0x8 0x0 0x0 0x0 0x0 0x0 0x0 0xD 0x6E
0x2 0x0 0x0 0x0 0x0 0x78 0xA0 0x90 0xEB 0x71 0xD 0x13
0x3 0x2 0xA5 0x0 0x21 0x0 0x0 0x0 0x90 0x0 0xD 0x68
0x4 0x0 0x0 0x0 0xE0 0xC3 0xEE 0xCB 0x0 0xBC 0xD 0x29
0x5 0x80 0x84 0xC0 0x0 0x80 0x7B 0x80 0x84 0x0 0xD 0xD5
0x6 0x0 0xFF 0xE0 0xE7 0x87 0x38 0xF7 0xF1 0xFF 0xD 0x7F
0x7 0xFF 0xFE 0xFE 0xFF 0xFF 0xFF 0xF8 0x0 0x3 0xD 0x7
0x8 0xFF 0xBE 0xEF 0x6E 0xBE 0xBF 0xFF 0x3F 0x3F 0xD 0x29
0x9 0x7F 0x7F 0x7F 0x7F 0x7F 0x0 0x40 0xC1 0x2 0xD 0x94
0xA 0xC0 0x1 0x1C 0x0 0x42 0x1 0xFF 0xFF 0xA0 0xD 0xD5
0xB 0x1C 0x0 0x3 0xE0 0x0 0xC 0x1C 0x0 0x3 0xD 0x42
0xC 0xE0 0x0 0x0 0xC 0x0 0x0 0x0 0x0 0x0 0xD 0x5

0x1 0x88 0xD0 0x8 0x0 0x0 0x0 0x0 0x0 0x0 0xD 0x6E
0x2 0x0 0x0 0x0 0x0 0x78 0xA0 0x90 0xEB 0x71 0xD 0x13
0x3 0x2 0xA5 0x0 0x21 0x0 0x0 0x0 0x90 0x0 0xD 0x68
0x4 0x0 0x0 0x0 0xE0 0xC3 0xEE 0xCB 0x0 0xBC 0xD 0x29
0x5 0x80 0x84 0xC0 0x0 0x80 0x7B 0x80 0x84 0x0 0xD 0xD5
0x6 0x0 0xFF 0xE0 0xE7 0x87 0x38 0xF7 0xF1 0xFF 0xD 0x7F
0x7 0xFF 0xFE 0xFE 0xFF 0xFF 0xFF 0xF8 0x0 0x3 0xD 0x7
0x8 0xFF 0xBE 0xEF 0x6E 0xBE 0xBF 0xFF 0x3F 0x3F 0xD 0x29
0x9 0x7F 0x7F 0x7F 0x7F 0x7F 0x0 0x40 0xC1 0x2 0xD 0x94
0xA 0xC0 0x1 0x1C 0x0 0x42 0x1 0xFF 0xFF 0xA0 0xD 0xD5
0xB 0x1C 0x0 0x3 0xE0 0x0 0xC 0x1C 0x0 0x3 0xD 0x42
0xC 0xE0 0x0 0x0 0xC 0x0 0x0 0x0 0x0 0x0 0xD 0x5

Easily see there are 12 bytes per frame (as I saw in the LA). First bit indicated the frame number, last bit is a CRC (Byte0+Byte1... +Byte10%256)
Now that I know the structure, I can start to reverse engineer the values
 
Spent the last few days since posting the above trying to get my Logic analyzer to show the same data. Today I finally decided to assign the KEY1 (Slave Busy) signal to CS on Logic2 - voila! Got the same values

I then took my T3.2 (using it due to the 5v tolerance for now) wrote a small sketch to send these frame out - but was not getting any leds lighting up on the control panel.
Did a quick comparison with the analyzer between my SPI stream on the CDJs main board steam and noticed that there is a 45-50 microsecond gap between each byte, so I added a delay in and voila again - got it to light up!

Now, I just need to play around with the frame values and figure out while bytes turn the LEDs on/off

And then, the more challenging part is fine tuning the timing of everything here, as the output from the control panel is a mess - no visible structure at all, and data becomes organized the more I play with the bus timing and the timing between bytes and frames.

But so far this has been a great learning experience
 
639484bf-e8fe-4932-a4da-165af5b1fe0a.jpeg


Apologies for the wiring mess and messy desk!

Played a bit more with byte interval timing today and now I can control each led individually by toggling specific bits in specific bytes of specific frames

And I can also read out buttons clicks and potentiometer values.

Once I have it all logged out I will post up my findings on Github
 
Back
Top