Adventures with Flippy the Flip-dot Display

Considering my next project, I wanted to make an electromechanical display using magnets. I turned to the internet for inspiration and quickly came across Flip-dot displays; solenoid driven pixels. A good starting point for what I wanted to do, I looked further.

I found a 900mm, 56×7 display on eBay from a bus salvager (who know such a thing existed!). The displays used to be common on public transport – prior to being replaced my dot matrix LEDs – to display the route number and destination. It cost me £170, which may seem expensive to some, but for 392 individually mechanically actuated pixels that are quite a feat of engineering, I thought it cheap.

Manufactured by Hanover Displays and with a basic datasheet to hand, I took the plunge.


Upon arriving and having acquired a USB-RS485 dongle, I tried out a Python module someone had written for their own Hanover display. It demonstrated my display worked but I quickly decided rolling my own driver would be better for my needs – and part of the fun of getting the display in the first place! Most importantly, the module developer had reverse engineered (or somehow knew) the messaging protocol for the displays:

The Hanover Flip-Dot display expects ascii chars representing the hexadecimal bytes; bytes being every 8 rows of dots. For example, an eight row column:

Along with a header (containing display resolution and address) and footer (containing CRC). I can only imagine the designers went designers went down this route due to hardware limitations at the time; perhaps they only had access serial controllers that would send ascii characters for use in terminals. Or perhaps there is a reason I am missing?

Message for single dot in second column: B0 ‘2’ fixed, B1 ‘1’ fixed, B2 ‘5’ display addr, B3/4 resolution, B5/6 col 0, B7/8 col 1

Having a logic analyser to scope the hardware output become invaluable when the driver didn’t work at first. Despite apparently unloading the full buffer, the node-serialport was clipping the data. A bit of debugging and a pull-request later and that hitch was solved.

This conversion from hex bytes to ascii characters can quickly become confusing, so I designed my driver to work a 2d matrix of rows and columns, only being encoded to the display format when buffering to the serial port. The matrix means one can intuitively flip a bit at the [x][y] and flip that dot on the display.

Secondly, the Python module used constant pre-defined character arrays encoded for the display. It meant that they didn’t scale well to different size displays. With my matrix implementation, I quickly realised that ascii art would be the perfect font renderer, as one can quickly parse the text strings and set any non-space character as on.  Here’s the debug output of my driver sending “hello”:

Finally, I added a queue and automatic frame/string scrolling. Once I had all this functionality, it was begging for a GUI!

The GUI was quite quick to develop as the driver was written in node.js – which is primarily a web technology – creating a web app was not a problem. By adding a HTML *canvas* element to emulate the flip-dot display, one can quickly toggle dots on the display to draw any shapes. One can send text with any Figlet font, fill the display, enable the clock and show Twitter streams.


I go into the hardware more in my video, but I’ll briefly discuss the electromechanical pixel operation. On my Hanover display, the dots have a magnet inset on one side and an exposed crescent on on the other. Two posts either side are the two poles of a solenoid, hidden below. On my diagram and up close, one can see that the magnet is attracted/repelled from each pole by alternating the current direction in the solenoid coil – this allows control of the exposed dot surface.

Diagram showing how a flip-dot display can toggle the dot with a single solenoid by alternating the current direction.

Residual magnetism in the high remanence core, holds the dot in the last position without the solenoid energised. The un-powered, luminous stable state of the display is what made them great as low-power daylight displays.

View post on

The solenoids are arranged in a multiplexed grid pattern as you’d expect. To raster a frame, the driver appears to enable a row, followed by each column, then the next row… There does not seem to be any frame optimisation – only firing dots that have changed since the last frame for example – causing the display to raster a full frame each time. This fixes the maximum refresh rate to around 2 Hz – slow but enough to display a HH:MM:SS clock!

Considering the electronic area is so large and accessible, one could relatively easily upgrade the driving hardware by hoping directly onto the driver ICs to potentially increase this refresh rate, increase the buffer, better message structure etc. As an electromechanical pixel rather than electrochemical like modern displays, the upper refresh rate would probably be limited by the physical movement of the dot however, along with the solenoid losses.

The display had flying leads for the RS485 and 24 V power that I used for development, but I’ve since been able to install a AC/DC 24 V supply within the enclosure and a Raspberry Pi hosting the node.js web controller. I can now simply plug in an IEC lead, then control the display from any web browser.

Wrap Up

Writing the software and understanding the electromechanical operation of this display has been well worth it. My packages are open-source with links below. They should work for any Hanover Flip-dot display by changing the row and column values at initiation. Even if you don’t have a display, the web controller is setup to emulate by default so you can still have a play!


NPM Package
Package Github
Web controller


13 thoughts on “Adventures with Flippy the Flip-dot Display”

  1. I was surprised to see an article about flipdot displays on hackaday ( /). After reading your article, I was even more surprised to see that your development was inspired by my dirty python library. Your driver is really well done! Good work.

    For my part, after spending nearly two weeks of my free time deboning the protocol, and another week to do something ‘usable’, the fun was gone, and I gave it up.

    Nice to see that it was not totally useless 😀

    1. Thanks! You certainly gave the project a running start and avoided me having to obtain a bus controller with it. I’m curious as to how you reverse engineered it yourself – did you have a controller to sniff?

      I got the feeling that might have been the case with the Python driver, but at least you got the hard work done! I was going to start from your base and merge it back in but decided it was going to be easiest to start from a fresh.


      1. I didn’t had a controller neither, but my chance is that someone has sent me a binary dump of transactions sent by a controller. The only thing I knew at that time is that it was a transaction for a system with three displays of different sizes.

        I first tried to split the transactions to isolate the displays. Once I was convinced of my splits, I easily understood that the address and the size was present in a kind of header. I also suspected a kind of CRC for the last byte.

        It took me roughly two hours from the dump to display the content. (fortunatly, my display had the same size than one of the three display used to dump the communication).
        The checksum was the tricky part. First, I thought that it was a CRC. So I made a script to retrieve the polynomial value of the CRC. As you can imagine, the script ran during hours and hours, without success. But in the meantime, I understood how the pixel where coded.

        The fact that the pixels are sent as an ASCII representation of their hexadecimal value troubled me. After hours of brainstorming to understand why they coded it that way, I finally understood that the development of these display were made with an old technology, and using a CRC with this technology was then improbable. So, I tried simpler methods, and finally find it. The conclusion is that the checksum couldn’t be simpler.

        Once the header, checksum and data format is understood, creating a library to use it is very simple. But this is not my favorite part. That’s why I abandoned very quickly my library, and switch to another project 😀

        1. Yes I feel like the ASCII byte representation was due to the fact they only had access to serial terminal drivers. I think that part would have had me stumped for a while!

  2. Hi John, I tried your app and it’s not working for some reason. I have a 7×84 Hanover flipdot and am using Windows 7 to drive the display via a USB RS485 adapter via COM12. The display address is 1.

    Flipdot-clock -p COM12 -a 1 -r 7 -c 84 produces just :

    FlipDot port open on COM12 @: 4800

    And nothing else on the display.

    The python script from ks156 works on my display works, so for sure the display works.

    I have no experience with node.js and am wondering if you can provide some input as to why it is not working for me?


    1. My knowledge in nodejs is almost zero, so I can’t help neither, but …
      Looking at these lines , I’m wondering if there’s not a problem with the resolution.

      The resolution parameter for a 84*7 is 0x35 0x34 (hanover format), representing 0x54 (84)

      The result of the nodejs code is 73, which is not correct.
      rows = 84 || 8;
      columns = 7 || 56;
      data = ((rows * columns) / 8);
      col_bytes = rows / 8;
      ldata = (columns * col_bytes * 2);
      res = (data & 0xFF);

      You can try to replace this line by = ((rows * 8) / 8); for debugging.

      Hope this help

      1. The comment required approval by the spam filter so didn’t appear at first. Thanks for the reply but I think you’re getting the rows and columns mixed up here. Alan is issuing a command for a display with 7 rows and 84 columns – I think you’ve written something above for 84 rows and 7 columns.

        The code rounds the number of rows to the nearest 8 to get the correct number of bytes and expected Hanover resolution format. So an input of -r 7 will round to 8 rows at this line before the following code above.

        This ensures that it is always ((columns * [8 multiple of rows]) as in your suggested fix. I’m confident this works as others have used the module with other sizes and my display is in fact a 7 row display too.

        I’m not sure what the problem is Alan without looking into it more. Are you sure you have the latest version of the code? There were some constants in some files that others have fixed since I released it.

  3. So I can confirm the following works for me :

    flipdot-clock -p COM12 -a 2 -r 8 -c 84
    So it looks like the address AND the rows need to be incremented by 1 each for it to work. FYI I also had to increment the display address by 1 in the python script by Ks156 for my display to work.

    John, what is the correct command to issue to run term.js? I tried :

    flipdot -p COM12 -a 2 -r 8 -c 84
    Windows Script Host pops up with an error message

    npm run bin/term.js / npm run bin/term.js -p COM12 -a 2 -r 8 -c 84
    npm ERR! missing script: bin/term.js

    Total newbie in node.js for sure! Any help appreciated. TIA

    1. Are you sure it doesn’t work with (7 rows but with address changed)

      flipdot-clock -p COM12 -a 2 -r 7 -c 84

      The address is set in with hardware on within the display so that being wrong would make sense for it not working.

      The script not working is because the symbolic link hasn’t been created properly. It is Windows problem though. You can run it from the code folder instead here

      1. You are right – it works with -r 7. I was sure I tried that earlier. But the address setting in the display is “1”, so an increment of 1 is still required without changing the code.

        term.js is also working now … thanks for all the help and the code. Much appreciated.

Leave a Reply