Analysing the Flip Dot Display Command

20 March 2014

On the back of my previous post I've modified the LINQPad script to allow me to define a conversation with the display and set it up with the observed conversation from last time.

static int baudRate = 9600;
static int dataBits = 8;
void Main()
{
    using (var port = new SerialPort("COM3", baudRate, Parity.None, dataBits, StopBits.One))
    {
        try {
            port.Open();

            port.Send(2, 49, 3, 225, 213, 19);
            port.Expect(2, 49, 4, 224, 0, 144, 26);
            port.Send(2, 49, 39, 229, 229, 32, 156, 170, 57, 228, 32, 40, 166, 76, 97, 110, 103, 115, 116, 111, 110, 101, 32, 66, 117, 115, 105, 110, 101, 115, 115, 228, 40, 78, 166, 80, 97, 114, 107, 254, 152, 188);
            port.Expect(2, 49, 3, 232, 68, 1);

            port.Close();
        }
        catch (Exception e) {
            e.Dump();
        }
    }
}

static class Extensions {
    public static string ToDebugString(this IEnumerable<byte> bytes) {
        return string.Join(", ", bytes.Select(x=>x.ToString()).ToArray());
    }

    public static void Send(this SerialPort port, params byte[] data) {
        port.Write(data, 0, data.Length);
        data.ToDebugString().Dump("Sent");
    }

    public static void Expect(this SerialPort port, params byte[] expectedData) {
        var timeout = TimeSpan.FromMilliseconds(500);

        var received = new List<byte>();
        var dataReceived = new SerialDataReceivedEventHandler((sender, args) => {
            while (port.BytesToRead != 0) {
                received.Add((byte)port.ReadByte());
            }
        });

        try {
            port.DataReceived += dataReceived;
            Thread.Sleep(timeout);
            if (received.SequenceEqual(expectedData)) {
                received.ToDebugString().Dump("Received");
            }
            else {
                throw new DataException("Expected { " + expectedData.ToDebugString() + " } but got { " + received.ToDebugString() + " }");
            }
        }
        finally {
            port.DataReceived -= dataReceived;
        }
    }
}

This will allow me to quickly test ideas about the protocol to see how the display responds.

Idea 1 - Addressing Dots/Pixels

I'm assuming that the protocol sends data about individual pixels and that all the brains behind converting text into pixels sits in the controller, which my code is replacing. In order to address each pixel I would expect some form of xy coordinates to be sent so I'm going to think about how that might be encoded in the command.

This display is 8 dots tall so encoding a y-coordinate could use 3 bits. It's also 90 dots wide, to encode the the x-coordinate would take 7 bits. This makes 10 bits, perhaps an 11th bit to indicate whether the dot should be on or off, meaning each pixel would be 2 bytes long. Turning the pixel at (42,3) could look something like this:

0 0 1 0 1 0 0 1 | 0 0 0 0 0 1 0 1
  ------x------ |         --y-- on/off

Not only is that lot of wasted bits, it also means that just the first L on the screen would take 34 bytes to communicate yet we only have 40 bytes in the command after the initial 2 display address bytes. Interestingly the 3rd byte is 39, could this be representing the size of the message that follows? I'm going to assume yes for now, I can always backtrack.

Another possibility for addressing individual pixels is for it to send a full sweep of data for the whole display. So one byte for every column, with the 8 bits representing each row. This would be more efficient with no wasted bits, however it would still require 90 bytes in the message.

Idea 2 - Hack, Prod, Hack

I don't have another idea, so I'm going to try changing numbers in the command to see what the outcome is.

I choose 101, I think because it's palindromic, I like palindromes. I'll change it to 121. Sending this modified command results in no response from the display and no change in the dots on show, this is not what I expected or wanted. I was hoping for some form of error code if I sent a bad command. Reverting my change and running the original to make sure I've not broken something elsewhere results in no data being returned after the first polling command. But wait...

Display showing "Langstony"

The display has updated and a character has changed!

This is interesting, I'm assuming that 2, 49, 3, 225, 213, 19 somehow made the modified command I sent take effect. Let's change some more characters and send that extra message afterwards.

port.Send(2, 49, 3, 225, 213, 19);
port.Expect(2, 49, 4, 224, 0, 144, 26);
port.Send(2, 49, 39, 229, 229, 32, 156, 170, 57, 228, 32, 40, 166, 78, 98, 110, 103, 115, 116, 141, 110, 121, 32, 66, 117, 115, 105, 110, 101, 115, 115, 228, 40, 78, 166, 80, 97, 114, 107, 254, 152, 188);
port.Send(2, 49, 3, 225, 213, 19);
port.Expect(2, 49, 3, 232, 68, 1);

Display showing random characters

ASCII and ye shall receive

It's hit me. These are ASCII codes.

I'm running the original command through a bit of conversion like so:

data.Select(b => new {
    dec = b,
    hex = b.ToString("X2"),
    bin = Convert.ToString(b, 2).PadLeft(8, '0'),
    ascii = ASCIIEncoding.ASCII.GetString(new []{ b }),
}).Dump();
dec hex bin ascii
2 02 00000010
49 31 00110001 1
39 27 00100111 '
229 E5 11100101 ?
229 E5 11100101 ?
32 20 00100000  
156 9C 10011100 ?
170 AA 10101010 ?
57 39 00111001 9
228 E4 11100100 ?
32 20 00100000  
40 28 00101000 (
166 A6 10100110 ?
76 4C 01001100 L
dec hex bin ascii
97 61 01100001 a
110 6E 01101110 n
103 67 01100111 g
115 73 01110011 s
116 74 01110100 t
111 6F 01101111 o
110 6E 01101110 n
101 65 01100101 e
32 20 00100000  
66 42 01000010 B
117 75 01110101 u
115 73 01110011 s
105 69 01101001 i
110 6E 01101110 n
dec hex bin ascii
101 65 01100101 e
115 73 01110011 s
115 73 01110011 s
228 E4 11100100 ?
40 28 00101000 (
78 4E 01001110 N
166 A6 10100110 ?
80 50 01010000 P
97 61 01100001 a
114 72 01110010 r
107 6B 01101011 k
254 FE 11111110 ?
152 98 10011000 ?
188 BC 10111100 ?

The text is quite clear in there, 9 Langstone Business Park. There are spaces between the first two words but some sort of control characters between the rest. Before both the number 9 and the text there is a 32 followed by 2 characters. I think these 2 characters indicate the beginning of some segment of the route and the 229, 229, 32 is the type of command, which is displaying a bus route on the display.

Modifying the Message

I want to display mattscode.com on this, my current thinking is summarised as:

  • 2 bytes - display address
  • 1 byte - command size
  • 3 bytes - type of command
  • 2 bytes - control characters indicating start of some command section
  • 13 bytes - ASCII string
  • 3 bytes - control characters indicating end of command

Putting this together I have:

port.Send(
    2, 49,            // display address
    21,                // command size
    229, 229, 32,    // type of command
    40, 166,        // start of command section
    109, 97, 116, 116, 115, 99, 111, 100, 101, 46, 99, 111, 109,    // ASCII string
    254, 152, 188    // end of command
);

Display showing "mattscode.com"

Excellent!

Next Step

I've now got the ability to write my own text and I know the make-up of a command. Next I need to work out what the different control characters mean for different command sections and why the display resets after 50 seconds.