Receiving serial data meant for a shift register (replacing a VFD display)

Hello all, I'm looking to intercept serial data sent to a custom display chip that controls a 1x22 VFD. It has 3 lines:
-Latch
-Clock
-Data
All idle high at 5V. The incoming data appears to be at about 1Mhz, in two bursts per display update (I can't tell how much data per burst due to the limitations of my scope.)
I already have the data wires in place into SPI pins on the Arduino, but I don't know much about receiving serial data.
I copied this code from Gammon.au, modified the data rate, and added the SPI mode line:

// Written by Nick Gammon
// February 2011


#include <SPI.h>

char buf [100];
volatile byte pos;
volatile boolean process_it;

void setup (void)
{
  Serial.begin 250000);   // changed by me - for 1Mhz data
  
  // turn on SPI in slave mode
  SPCR |= bit (SPE);

  // have to send on master in, *slave out*
  pinMode(MISO, OUTPUT);
  
  // get ready for an interrupt 
  pos = 0;   // buffer empty
  process_it = false;

  // now turn on interrupts
  SPI.attachInterrupt();
SPI.setDataMode (SPI_MODE2);  //added by me
}  // end of setup


// SPI interrupt routine
ISR (SPI_STC_vect)
{
byte c = SPDR;  // grab byte from SPI Data Register
  
  // add to buffer if room
  if (pos < (sizeof (buf) - 1))
    buf [pos++] = c;
    
  // example: newline means time to process buffer
  if (c == '\n')
    process_it = true;
      
}  // end of interrupt routine SPI_STC_vect

// main loop - wait for flag set in interrupt routine
void loop (void)
{
  if (process_it)
    {
    buf [pos] = 0;  
    Serial.println (buf);
    pos = 0;
    process_it = false;
    }  // end of flag set
    
}  // end of loop

I'm using PuTTY to monitor the serial since the Arduino serial monitor data rate is limited. I get a few random characters every 4th display update or so, but that's about it.
The problem is, I don't really know much about SPI, what I'm doing, or how the data is stored and pushed out to PuTTY.
Once I can reliably store the display update "bursts", I can then move on to processing the data (I have a character map from the display chip datasheet).
Can anyone help with the data capture portion? What other info do I need?
Thanks all!

It sounds like you are getting fairly reliable data. Is it not good enough to figure out the protocol?

I guess I should clarify: I don't exactly know what is being captured & stored by the Arduino with the above code, or how to reliably monitor that. I just get a few random characters out of the serial monitor. Which is a start, I guess.

It sounds to me like the protocol is binary (which is what I would expect). The sketch is assuming the data is printable text. Try this version. It will gather bytes until there is a half-second gap (up to 1000 bytes) and then dump them in hexadecimal.

#include <SPI.h>

volatile char V_buf[1000];
volatile unsigned V_pos = 0;
volatile unsigned long V_LastArrivalTime;

void setup(void)
{
  Serial.begin(250000);   // changed by me - for 1Mhz data

  // turn on SPI in slave mode
  SPCR |= bit(SPE);

  // have to send on master in, *slave out*
  pinMode(MISO, OUTPUT);

  // get ready for an interrupt
  V_pos = 0;  // buffer empty

  // now turn on interrupts
  SPI.attachInterrupt();
  SPI.setDataMode(SPI_MODE2);  //added by me
}  // end of setup


// SPI interrupt routine
ISR(SPI_STC_vect)
{
  V_LastArrivalTime = micros();
  // add to buffer if room
  if (V_pos < (sizeof V_buf) - 1)
    V_buf[V_pos++] = SPDR;
}  // end of interrupt routine SPI_STC_vect

// main loop - wait for flag set in interrupt routine
void loop(void)
{
  noInterrupts();
  if (V_pos == 0)
  {
    interrupts();
    return;  // No data
  }
  unsigned localPos = V_pos;
  unsigned long localLastArrivalTime = V_LastArrivalTime;
  interrupts();

  if (micros() - localLastArrivalTime > 500000ul)  // Half a second
  {
    // Input has paused so let's dump the buffer
    for (unsigned i = 0; i < localPos; i++)
    {
      if (V_buf[i] < 0x10)
        Serial.print('0');
      Serial.print(V_buf[i], HEX);
      Serial.print(' ');
      if ((i % 16) == 15)
        Serial.println();
    }
    noInterrupts();
    V_pos = 0; // Start over at the beginning of the buffer
    interrupts();
  }

}  // end of loop

Awesome, that appears to work! I suspected that I had some data type problems.
Here's an example of the output for this screen:
(there's a button that scrolls between messages)

04 0FFFFFF82 11 59 15 25 02 06 36 0E 59 02 08 0FFFFFFF2 0FFFFFFF2 0FFFFFFA2
0FFFFFFA2 5C 4C 0FFFFFF8C 0C 04 0FFFFFF92 04

I think that gives me a great start with processing, thanks!

Change that line to:
volatile byte V_buf[1000];
That will get rid of the extra "FFFFFF" parts.

After that change, here's the output for the Odometer screen:
Hex:

04 82 11 59 15 25 02 06 36 0E 59 02 08 F2 F2 A2
A2 5C 4C 8C 0C 04 92 04

Binary:

0100 10000010 10001 1011001 10101 100101 010 0110 110110 01110 1011001 010 0100 1111001 1111001 1010001
1010001 101110 100110 1000110 0110 010 1001001 010

Now I'm off to decode some characters. Thanks again!

Reverse-engineering a protocol tends to be easier if you have multiple different messages. That way you can look for commonalities and differences. For example, I would collect the odometer message again after moving a mile.

:blue_heart: VFDs

I can cycle through 4 different messages. So far I can't make any sense of the data vs. the character map, but I'll keep poking at it.
Processing the data into characters is going to be another challenge.
Thanks!

Doesn't the display chip datasheet tell you what the protocol is?

It's possible the data the Arduino is receiving is inverted (meaning every 1 should be a 0 and every 0 should be a 1) or that the bits in every byte are sent MSB first or LSB first (meaning the first bit of each byte received is either bit 0 or bit 7).

Well, not that I'm asking anyone to do work for me, but here's the datasheet. I haven't had much time to poke around with figuring out the data yet.
http://www.datasheet.hk/view_download.php?id=1049705&file=0032\ml9203-xx_254193.pdf

(I'm not allowed to upload attachments yet)
I don't know if this is the exact datasheet, but I can't find one for the actual OKI L9203-2. I'm assuming the protocols and character map are the same, but I could be wrong.

TripA
0100 10000010 100101 0101 1000001 010 010 010 0110 0110 1011001 010 01000 101010 10010010 0100
1011100 0100 0100 0100 1110100 0100 10010010 0100

Trip B
01001 0100 1001010 01010 1000010 0100 0100 0100 01100 01100 10110010 0100 01000 101010 10010010 0100
1011100 0100 0100 0100 1110100 0100 10010010 0100

Hours
0100 11010001 1110001 111001 010 1111001 100101 101110 010 1001110 0110 1000110 0100 111001 1001001 1010001
01001 1010101 1100101 010 100110 110110 111010 010

Sorry for the spam!

Datasheet:
OKI_ML9203_Datasheet.pdf (388.0 KB)

Each transmission starts with a command byte followed by at least one data byte.

Messages:

" ODOMETER: 201680 MI. "
" TRIP A:      0.0 MI. "
" TRIP B:      0.0 MI. "
"ENGINE HOURS:  2960.1 "

That binary doesn't match that hex. If you don't display the binary correctly you are going to have a hard time making sense of the data. I'd rather work in HEX.

You would expect these two messages to be almost identical but the binary is looking very different for the first half of the message:
TRIP A: 0100 10000010 100101 0101 1000001 010 010 010 0110 0110 1011001 010 01000 101010 10010010 0100 1011100 0100 0100 0100 1110100 0100 10010010 0100
TRIP B: 01001 0100 1001010 01010 1000010 0100 0100 0100 01100 01100 10110010 0100 01000 101010 10010010 0100 1011100 0100 0100 0100 1110100 0100 10010010 0100

Understood about Hex vs. Binary. I was changing this:
Serial.print(V_buf[i], HEX);
Serial.print(V_buf[i], BIN);
Just to compare the outputs. The character map shows the the MSB and LSB of each character as binary, so I thought I'd see if anything looked familiar on first glance.

In case anyone is still following:

trip a

09 04 4A 0A 82 04 04 04 0C 0C B2 04 08 2A 92 04
5C 04 04 04 74 04 92 04

trip b

09 04 4A 0A 42 04 04 04 0C 0C B2 04 08 2A 92 04
5C 04 04 04 74 04 92 04

So I'm seeing the "82" byte change to "42" which is a good sign (only difference in the display is A character vs. B character). If my conversion is correct:
A = 1000 0010
B = 1000 101

The character map shows (MSB, LSB)
A = 0011 0001
B = 0011 0010

So the bit moves, but I still have some deciphering to do. I wonder if building a new HEX character table would make the process simpler?

Not quite correct:
0x82 -> 1000 0010
0x42 -> 0100 0010

A -> 0011 0001 -> 0x31
B -> 0011 0010 -> 0x32

It looks like the bits are reversed and the result is off by 0x10:

0x82 -> 1000 0010 reversed -> 0100 0001 -> 0x41
0x42 -> 0100 0010 reversed -> 0100 0010 -> 0x42

0x41 - 0x10 -> 0x31 -> 'A'
0x42 - 0x10 -> 0x32 -> 'B'

The SPI settings probably default to LSBFIRST. You should configure for MSBFIRST:
`SPI.setBitOrder(MSBFIRST);'