There comes a time in the life of every electronics nerd when they get into vintage electromechanical displays. Now, I don’t blame you for objecting to my tactless generalization, or to being called a nerd. But I take only mild tautological pleasure in pointing out that you are, well, reading this article.
I myself hopped on this bus a couple years ago, and have been on a wildly passive hunt for old flip-dot displays on eBay since. All this low effort finally paid off last February, when I found a seller flipping dots for profit on kleinanzeigen.de. Through a network of collaborators (one of my oldest friends and the Avis rent-a-car company), one made its way to my desk.
The Single Dot Flip Trick
There is a tiny permanent magnet inside each plastic disc. Pulse current through a coil under it and the generated field repels the magnet and flips the disc.
Each coil is wrapped around two prongs of a U-shaped iron core. When current flows, one side of the iron core pushes and the other side pulls the magnet inside the disc. Reverse the current pulse and the disc flips back.
I found it surprising that the core remains magnetized enough to keep the disc stable after the power is cut off:
The Matrix Multiplication Miracle
We may be tempted to wire up a bunch of dots in a LED-matrix style:
Try to flip coil A by connecting Col1 to Vcc and Row1 to GND. Sike!! Current also flows through coils C, D and B:
Unlike LEDs, coils allow for bidirectional current. That’s what allows dots to flip both ways. This issue is commonly solved by sprinkling some diodes across the matrix and splitting the rows into RowX+ and RowX-:
In this setup, switching Col1 to Vcc and Row1- to GND only powers coil A. On the downside, RowX+ and RowX- must be coordinated independently, and it’s a bad day at the office when both RowX+ and RowX- are driven at the same time. If you’re hacking on these, remember to power the whole thing with a current-limiting power supply.
The BROSE Module
The module I received is 16x28 dots, made by German company BROSE. I think the “18.KW 02” sticker in the back means it was made in the 18th week of 2002. For niche 20-year-old tech, this is a sort of common piece of hardware. In repos like 545ch4/flippie you even find custom controllers for it.
The chunky boy labeled ALCATEL 2840 at the top does most of the heavy lifting. It’s apparently a rebranded FP2800A. It takes a 5-bit address as input (pins A0 through B1) and connects one of its 28 outputs (pins 0A to 3G) to either +24V (if DATA = 1) or GND (if DATA = 0). In each module, these outputs are connected to the 28 columns of the matrix.
Each module contains one of these column drivers. The rows are expected to be driven by an external controller, through the 60-pin connectors at the top. This is so that multiple modules can be daisy-chained: each with its own column driver, but sharing a single, external row driver.
The 60-pin connector additionally carries the following signals, provided by an external controller:
MODx: 8-bit address of a module in the daisy chainCOLA0toCOLB1: the 5-bit address of a column within the selected module, from 0 to 27COLDAT:0if the column should be connected toGND,1if it should be connected to+24V
The last piece of the puzzle is the little red DIP switch on the top left. This is how each module sets its own address, and if this address matches the 8-bit MODx signal from the controller, the FP2800A driver on that particular module will be enabled with the COL_EN signal.
MODx signal
The BROSE Controller
Two PCF8574 I²C IO expanders set the module (MODx) and column addresses (COLA0 to COLB1 + COLDAT). These signals are buffered by SN74LS07s into the 60-pin connector and passed along the daisy chain of modules.
A third PCF8574 outputs the signals for the pair of FP2800A row drivers. These drivers then output the RowX+ and RowX- signals into the 60-pin connector.
There’s the secret sauce. The controller receives data through I²C to set the addresses for the drivers via PCF8574 IO expanders.
Let’s now trace back the I²C signals from the IO expanders to the screw-in input terminal. In the middle of the path, a pair of MAX487s snitch under the multimeter probes:
MAX487 RS485 transceiversLeoDJ/Brose_VM-IIC, who did some of the same reverse engineering years ago, aptly describes the setup as “cursed”. The I²C clock and data come directly from the input, except RS485-encoded into the differential pairs SCL_A/B and SDA_A/B. Presumably for better noise immunity over longer wires.
If the goal is to steer this controller with our very own signals, a tempting proposition is to generate these differential I²C SCL_A/B and SDA_A/B ourselves, as BROSE intended. A pair of external TTL-to-RS485 transceivers 1 in front of the input should let us inject I²C data in.
Kind of.
I²C in a Single Breath
When a transmitter sends a single byte to a receiver, a typical I²C transaction looks like this on the wire:
ACK from I²C receiver, borrowed from the delightful Philips data handbook, 1989In a healthy, compliant transmission, the receiver sends back an ACK bit by pulling SDA low on the last clock cycle. Even though the logical data flow is from sender to receiver, the physical medium must support bidirectional communication, lest the ability to ACK is lost.
Fight Back The Lack of ACK
Maybe you already spotted a couple of hints along the way. A controller section annotated with I²C direction switch and a suspicious SDA_OUT signal on the MAX487 transceiver:
SDA_OUT on the MAX487 transceiverThe MAX487 on the controller does, in fact, support bidirectional communication. The DE/nRE pins set the direction with the MAX_SDA_W_nR signal:
MAX_SDA_W_nR |
Direction | Description |
|---|---|---|
0 |
SDA_A/B -> SDA |
Controller receives data from the outside world |
1 |
SDA_OUT -> SDA_A/B |
Controller sends data back to the outside world |
So, where does this MAX_SDA_W_nR signal come from? I’m sorry you asked.
MAX487 direction selector for the SDA signalA pair of SN74HCT00 quad-2-input NAND gates compute the MAX_SDA_W_nR signal from SDA and N_CTRL_SEL_Y.
N_CTRL_SEL_Y is the “controller select” signal. It’s generated by comparing the MODx address (from one of the IO expanders) with the DIP switch address on the controller:
Things are not looking so hot. If (big, huge “if”) I traced all of this correctly, it seems the controller is capable of sending back an ACK. But only if the MODx from the IO expanders match the controller DIP switch setting. This felt odd to me, because during operation, MODx is supposed to select each module in the daisy chain, not the controller. So most of the time there will be no ACK anyway.
Even if that worked all the time, our hypothetical external RS485 transceiver would need to tap into this direction setting logic to know when to listen for the ACK bit. Madness. I am very curious to learn how the BROSE computer actually handles this, but I don’t have one. I suspect it simply ignores the ACK , and this direction-setting capability is only used to talk to the controller in specific cases. For example, to enumerate all controllers on the bus.
Either way, a reasonable solution is to ignore the ACK ourselves as well. There is just a tiny annoyance: I²C peripherals will wait for the ACK until a timeout error and that gets frustrating fast. LeoDJ/Brose_VM-IIC solved this by bit-banging I²C in software and explicitly ignoring the missing ACK.
A Redemptive Yoink
Luckily, my wife overheard me crying. “Do you really need to do this?”, she asks. Immediately, I knew exactly what she meant.
The problem RS485 solves doesn’t creep up here so much. The ESP32 I’m using to steer the BROSE controller is just a few short centimeters away.
Enlightened in a fit of blind joy, I yanked both MAX487s out of their sockets and tapped wires into the pristine, TTL-level I²C signals, ignoring the input terminal block and all RS485 stuff altogether and speaking directly to the PCF8574 IO expanders.
We are so bACK.
MAX487s removedThe Software
With most of the hardware mapped out, I wrote an ESPHome driver for the BROSE controller. It’s available in rbaron/esphome/components/brose_flipdot.
external_components:
- source:
type: git
url: https://github.com/rbaron/esphome
ref: brose-flipdot
components: [brose_flipdot]
display:
- platform: brose_flipdot
num_chips: 1
lambda: |-
it.printf(0, 1, id(my_font), TextAlign::TOP_LEFT, "Hello");
All the driver does is compute the I²C commands to set the appropriate IO expander pins.
And a VFD on the Side, Please
I wanted to make a beautiful housing for the display. Looking for inspiration, elbows deep in my bucket of maybe-someday parts, I fished out something I’d forgotten about.
In the haze of my recent vacuum fluorescent displays hyperfocus, I had also managed to snag a second, smaller display for around $15 on AliExpress. Validating hoarders everywhere, I thought it would look great as a secondary, tiny display.
Conveniently, there’s already a driver for it in trip5/EspHome-VFD-Clock. The end-of-life, built-in ESP12 from the clock ended up being promoted to not only drive the VFD, but the flip-dot display as well. And the LED strip. And the RTC and front panel inputs.
I shamelessly soldered the BROSE I²C lines directly into its pads:
Power Supply
100mA fuse, USB-PD trigger boardAfter that uptight I²C spec-chasing in previous sections, I concede it is ironic that I should freestyle the USB Power Delivery standard.
Yes, that is essentially a 🚩 USB-C extension cord 🚩 feeding into a USB PD trigger board. Yes, because the USB-C male connector I have only breaks out one CC line, the cable only works in one orientation.
I’m powering the whole thing with 20V over USB-C, cowboy style. The future is already here, just not properly implemented.
Front Panel
Armed with some experience from my earlier sheet metal fabrication experiment, I designed this front panel in Fusion. For $27 + shipping, JLCCNC cut, anodized and laser engraved it out of 2mm aluminum.
Housing
I’ve heard that the best way to get into woodworking is to get an IT job, burn out and set up a shop in the garage. I don’t have a garage, but I am feeling the pull toward this shared workshop.
It’s the first time I ever cut a piece of wood. Weeks of planning and overthinking ahead of it. Hours of battle-hardened woodworking YouTube celebrities warning me of all the ways I could hurt myself. How to use a miter saw. How to make bevel cuts. Rip cuts? Table saw, but watch out for kickbacks. Or track saw. Cross cuts? How to cut the lip for the front panel? What the hell, is wood glue really that strong?
The second the saw hit the board, I wondered why I hadn’t done this sooner. What an absolute joy.
On the clamped box, you can see how off my 45° joints are. I think I didn’t zero the miter well enough before tilting it. I expected to be upset about it, but it didn’t matter. I leaned into the liberating feeling of sucking at something new.
A trick I learned from MatthiasWandel masks at least part of the mistakes: wood glue and sawdust.
I gave it a coat of 50/50 tung oil and thinner with a rag. It looks great on the white oak and should protect it from the harsh environment of my office shelf.
Demo
Calendar, clock, weather and test pattern
Pong on a temporary front panel
-
For example on AliExpress ↩