Analysing the Flip Dot Display Command
20 March 2014On 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...
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);
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
);
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.