One of the most fascinating pieces of electronic artwork I’ve seen is Mark Hansen and Ben Rubin’s Listening Post when it was on display at the Science Museum, London.
Designed in the early 2000s it was on display from around 2008 for a number of years, but unfortunately it has been packed away in storage amongst the museum’s collections now for some time.
You can read about it on the following links:
- https://www.earstudio.com/projects/project-page/listening-post
- https://archive.org/details/markhansenbenrub1624hans
- https://en.wikipedia.org/wiki/Listening_Post_(artwork)
- https://collection.sciencemuseumgroup.org.uk/objects/co8091687/listening-post
The original has 3 PCs, a Mac and 231 (11 rows of 21) embedded modules each with a four-line text display. As well as the visuals there is a complex surround audio experience of sound and synthesized speech coming out of 10 or more speakers. Each embedded module also has a clicking relay which can create the impression of an older flip display working.
The videos that can be found online really don’t do it justice. There is nothing quite like sitting in front of it with the sound coming at you from all sides.
By the way, it is called Listening Post as it taps into live forums and IRC and pulls out text for the display. It is literally showing a snapshot of the global conversations as they happen – swearing and all. In fact, I recall a “parental controls” warning prior to going into the display, to that effect.
I’ve often wondered if a small part of it could be reproduced in a simpler form, so have started a series of blog posts listing some experiments working towards that idea.
The original is pretty amazing and a serious implementation, with a huge does of imagination and creativity. It would take an awful lot of effort to get anywhere close to something similar, so I’m not aiming for anything even close to that. But I’m going to keep fiddling around on and off to see if I can get something a lot more limited going at a smaller scale that might at least be considered to be in the spirit of the original somehow.
Warning! I strongly recommend using old or second hand equipment for your experiments. I am not responsible for any damage to expensive instruments!
If you are new to microcontrollers, see the Getting Started pages.
The Display Modules
My starting point is my Waveshare Zero Multi Display PCB Design and Expander Board.
The original has 231 small embedded modules, each with a Vacuum Fluorescent Display (VFD) that displays the text. There are two modes for the text: a four-line smaller display, and a large character scrolling display. Each of these options seems to be a function of the type of display in use, which I believe to be a VFD 420.
There is a data sheet and programmers guide for the VFD 420 here:
The VFD 420’s normal display mode looks like this:

The programmers guide describes the “big character” mode as follows:
“VFD-420’s “Big-character” mode can generate full-screen-height numbers and letters (uppercase only)—up to four letters/digits per screen. Big-mode symbols—consisting of dot/decimal point (.), hyphen/minus sign (-), colon (:) and space—take up one-half the width of other “big” characters.”

It does seem possible to buy similar displays today, but what I have seen so far are very, very expensive! I did temporarily wonder about the 4-line version of the HD44780 20×2 character displays. I might still come back to that at some point.
But for now, the trick for me is to attempt to reproduce the look and feel of the VFD big character display with my ST7789 SPI displays used with my Waveshare Zero Multi Display.
I spent a bit of time studying various videos of the Listening Post in action and started to sketch out the main characters that I’d require to rebuild the big character font.
It looks like the originals have a 4×4 matrix of small characters per big character. And each small character is a 8×5 grid and of course there are 20 of these for each of the four lines of the display.

I decided I could probably get away with 10 characters in total.

The Code
I’ve opted for a class that implements the “large text LCD” that effectively drives the TFT display. There are functions for each of the 10 character patterns shown above. Each of these uses the primitives from the Adafruit GFX library to construct the 10 glyphs using filled rectangles or sequences of lines. For example:
// "bottom left triangle"
void CLCD2004::botLTri (uint16_t x, uint16_t y, uint16_t colour) {
uint16_t xlen = 0;
for (int i=1; i<CHAR_H; i++) {
m_pdisplay->drawFastHLine(1+x*CHAR_W, i+y*CHAR_H, xlen/10, colour);
xlen = xlen + (CHAR_W*10-1)/CHAR_H;
}
}
From there, I’ve built a set of functions that look up which characters are required for each bt character and gets them on the display one row at a time.
void CLCD2004::printbigchar (char c, uint16_t x, uint16_t colour) {
c = c - LCD2004FONT_START;
printglyph (lcd2004font[c][0], x*BIGCHAR_X, 0, colour);
printglyph (lcd2004font[c][1], x*BIGCHAR_X, 1, colour);
printglyph (lcd2004font[c][2], x*BIGCHAR_X, 2, colour);
printglyph (lcd2004font[c][3], x*BIGCHAR_X, 3, colour);
}
void CLCD2004::printglyph (uint16_t g, uint16_t x, uint16_t y, uint16_t colour) {
if (g & 0xF000) {
printchar('0' + (g>>12 & 0xF), x, y, colour);
}
if (g & 0x0F00) {
printchar('0' + (g>>8 & 0xF), x+1, y, colour);
}
if (g & 0x00F0) {
printchar('0' + (g>>4 & 0xF), x+2, y, colour);
}
if (g & 0x000F) {
printchar('0' + (g & 0xF), x+3, y, colour);
}
}
void CLCD2004::printchar (char c, uint16_t x, uint16_t y, uint16_t colour) {
switch (c) {
... case '0' to '5' ...
case '6':
botLTri(x, y, colour);
break;
... case '7' to '9' ...
}
}
There is a font definition that has each block of four characters stored as a 32-bit value for all ASCII characters from 0 through to Z.
Everywhere a 6 occurs in the table, that will trigger the above botLTri() function to draw a “bottom, left, triangle”.
const uint16_t lcd2004font[LCD2004FONT_SIZE][4] = {
{0x7446,0x1001,0x1001,0x9558}, // 0
{0x0720,0x0320,0x0320,0x0110}, // 1
{0x7446,0x0078,0x0780,0x7111}, // 2
...
{0x0760,0x7896,0x1441,0x1001}, // A
{0x1446,0x1558,0x1446,0x1558}, // B
{0x7446,0x1000,0x1000,0x9558}, // C
...
{0x4441,0x0078,0x7800,0x1555}, // Z
};
Finally I can now wrap this all up in a general print routine.
void CLCD2004::print (char *c) {
uint16_t xc = 0;
while ((c[xc] != 0) && (xc < MAX_BIGCHAR_X)) {
printbigchar (' ', xc, m_bg);
printbigchar (c[xc], xc, m_fg);
xc++;
}
}
Using this class is now a case of setting up the TFT display and LCD objects for each screen.
SPIClass MySPI(FSPI);
Adafruit_ST7735 tft1 = Adafruit_ST7735(&MySPI, TFT_CS1, TFT_DC, TFT_RST);
CLCD2004 lcd((Adafruit_ST77xx*)&tft1, ST_WHITE, ST_BLACK);
void setup() {
MySPI.begin(SPI_SCLK, SPI_MISO, SPI_MOSI, SPI_SS);
tft1.initR(TFT_TYPE);
tft1.setSPISpeed(SPI_SPEED);
tft1.setRotation(3);
tft1.fillScreen(ST_BLACK);
}
void loop() {
lcd.setColour(ST_CYAN, ST_BLACK);
lcd.printclr();
lcd.print("ABCD");
}

To create the scrolling is a bit more complex. To do that I decided to add the following:
- A “scrolled” parameter to the display.
- Use a GFX canvas to maintain a pixel bitmap in memory and write to that.
- Add an update() function to draw the bitmap over to the display.
- Expand the print functions to accept a longer string that is stored in a buffer ready to be printed to the canvas.
- Add a scroll function that determines where in the string the updating/printing starts.
There were a few things I had to sort out to get this working.
- The writing of an RGB bitmap to the display was really slow. This is because the provided function has additional SPI writes per pixel that could actually be done just one. This resulting in my writing my own more optimised version of drawRGBBitmap.
- It turns out that setting the SPI speed has to be performed after the initialisation of the display object (i.e. call setSPISpeed() after calling initR()).
- Getting the correct “windowing” to allow the scrolling effect was quite tricky.
- As the display shows four big characters, I always print five on the canvas and let the scroll work, via the windowing, for a whole character. Then a new set of five characters gets printed, and the scroll position resets again.
- The fastest supported SPI speed for the ESP32S3 is 80MHz and it often works at that speed. But occasionally one of the displays will be a bit glitchy, so a slower speed is advisable. I’ve been using 60MHz. I don’t know if that translates to a legitimate SPI speed or not, so the driver may be rounding that to something more sensible – I don’t know!
The basic operation is now:
Set up SPI, TFT and the LCD.
Print a character string to the LCD.
Calling update() will put the first part of the string on the display.
Calling scroll() will shift it as required.
The scroll() function has an option to wrap back to the first character in the string if required.
The Results so far
It has taken a bit of tweaking but I believe I can get useful performance out of a single ESP32S3 driving eight SPI displays.
The original has many display modes, so I went with starting to model one of the simpler modes – it searches for the phrase “I am” and then displays what follows. This mode can be seen in action in the video at the end of this post.
For now, to show the display as potential, I’m just printing the two words “I AM”. This video shows the hard-coded “I AM” display in action with a randomised start time and scroll speed for each display.
Closing Thoughts
As I say, the original has many display modes, but there is something quite hypnotic about the “I AM” mode that I like. It would be nice to find a way to hook this up to Mastodon searching for uses of the phrase.
So this is starting small, but getting the font in an acceptable format is quite a key part of this project doing anything useful for me. I think this is a really useful start.
Kevin