CL84209 Base Station LCD Segment Map

I have generated a printable lookup chart for the character set of an unknown LCD controller. This chip is embedded inside a black blob along the top edge of an LCD I salvaged from the base station of an AT&T CL84209 cordless phone system. The character set dictates what is rendered in two lines of 15-character alphanumeric text, each character is a dot matrix 5 pixels wide and 7 pixels tall. Below these two lines is a set of custom segmented LCD that can be controlled with 16 bytes, but there appear to be far fewer than 16*8 = 128 segments to control. A segment map will tell us which segments correspond to which bits.

I don’t have the datasheet for this chip, so I don’t know how it numbered its segments. I decided to mostly follow the precedence set by Sanyo LC75853 by numbering both bits and bytes in least-significant-first order. The difference for this map is that while LC75853 started counting from one, I’m going to start counting from zero.

My first attempt at mapping out these segments toggled them on/off alongside the character set data. When displaying characters starting with 0x00, I turned on all the segments whose number has zeroth bit set. For the character set starting with 0x10, I turned on all the segments with the next bit set, etc. In theory I could look at the on/off pattern as it cycled through 0x00 to 0x70 and determine its binary number. I also printed out the bit pattern to Arduino serial console, which looks like this:

0 1 10101010 10101010 10101010 10101010 10101010 10101010 10101010 10101010 10101010 10101010 10101010 10101010 10101010 10101010 10101010 10101010 
1 10 11001100 11001100 11001100 11001100 11001100 11001100 11001100 11001100 11001100 11001100 11001100 11001100 11001100 11001100 11001100 11001100 
2 100 11110000 11110000 11110000 11110000 11110000 11110000 11110000 11110000 11110000 11110000 11110000 11110000 11110000 11110000 11110000 11110000 
3 1000 0 11111111 0 11111111 0 11111111 0 11111111 0 11111111 0 11111111 0 11111111 0 11111111 
4 10000 0 0 11111111 11111111 0 0 11111111 11111111 0 0 11111111 11111111 0 0 11111111 11111111 
5 100000 0 0 0 0 11111111 11111111 11111111 11111111 0 0 0 0 11111111 11111111 11111111 11111111 
6 1000000 0 0 0 0 0 0 0 0 11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111 
7 10000000 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 

In theory this can work, in practice I quickly got tired flipping through images and decoding binary numbers by hand. That was a lot more work than having a quadrature encoder knob to interactively select segments to activate. I had avoided wiring in extra components out of laziness, but between manual binary decoding and soldering, I decided a bit of soldering was the easier path towards this segment map:

This map clearly showed this particular segment allocation used less than 5/8 of available segments. Starting from position zero, we would have five bits for five segments (0, 1, 2, 3, 4) then three bits that were unused. (5, 6, 7) Then five more segments (8, 9, 10, 11, 12) and three unused bits (13, 14, 15). This repeats until the final set of five (120, 121, 122, 123, 124) which were also unused.

I was surprised that segment 9 (“MSG#” title for message count) was a separate segment from 4, the rectangular border around message count. I had expected them to be a single segment.

I was also surprised and annoyed at segment 27, which lights up all three horizontal lines of the hour tens digit for the clock plus the lower left vertical segment. For a clock it made sense to restrict the tens digit to 1 or 2. But if so, why did they bother with segment 26 which isn’t useful for either of those digits? I had hoped maybe I could use it as a generic numeral display and not a clock, by leaving 65 and 66 (AM/PM) inactive. But segment 27 means I could only display 1, 2, 6, and 8. I have not yet thought of an interesting non-clock usage under those restrictions.

Obtaining a full segment map marks the end of investigating this base station LCD, I will now try to do the same for the handset LCD starting with its disassembly.

Source code for this investigation is publicly available on GitHub.

CL84209 Base Station LCD Character Set

After a look at the similarities and differences between two LCDs salvaged from an AT&T CL84209 cordless phone set, I was ready to start controlling one myself to get more data. Earlier probing indicated that I2C communication on the handset was 3.3V, so I’m going to use an ESP8266 because it is also a 3.3V part. The Arduino platform should be a good (enough) starting point, using its Wire library to recreate the I2C control messages I saw sent to the handset LCD.

After a quick test program to verify things worked as expected, I wanted to dump out the character set built inside this LCD controller. Since the character data is sent one byte per character, there are potentially 256 different characters in the font. Dealing with these powers-of-two numbers, I like to keep such information lined up with their hexadecimal values. The LCD can only print up to 15 characters per line, so I couldn’t print these in 16 characters batches. As a next best thing, I dropped down to 8 characters per line along with their starting hexadecimal value. A for() loop cycled through 16 such screens once per second, so I could see the entire character set cycle through.

This is one of the sixteen steps in the cycle. The first line shows characters from 0x70 to 0x77, and the second line from 0x78 to 0x7F. Using workbench lighting, I had trouble trying to photograph the screen without glare. So, I switched to backlighting the LCD using one of my salvaged LED backlights. I took a picture for each of the 16 sections with the intent to edit them together, but the slightly undersized backlight made it difficult. The center section of the screen is noticeably brighter than the edges, which makes it difficult to set a threshold to generate a nice monochrome reference chart. I had to consult Emily Velasco for some photo-editing tips. (I have now added “burn” & “dodge” to my photo editing toolbox.) After some very informative experimentation in GNU Image Manipulation Program I have a reference sheet suitable for printing. Using this chart, I could see that 0x7F is a solid block that activates all pixels of a 5×7 dot matrix, which I will use to as part of an “turn everything on” program to help map out its adjacent segmented region.

Source code for this investigation is publicly available on GitHub.

Handset LCD versus Base Station LCD (CL84209)

I have two LCD units salvaged from an AT&T CL84209 cordless phone system. One from the base station, and one still attached to a handset circuit board. The handset could still be powered up and ran, which helped me figure out how to interact with the base station LCD’s digital logic and analog voltage supply pins. Connecting them in parallel was a bad idea in many ways, but the risky experiment paid off telling me they both responded to the same I2C address and displayed similar but not identical things. Now that I have them side by side again, with some confidence I’m not damaging them while doing so, I have time to take a closer look at the similarities and differences.

Both of these LCDs have two lines of dot-matrix display on top, and a custom segmented LCD area below that. Each of the two lines consist of 15 characters, each character a dot matrix 5 pixels wide and 7 pixels tall. Its I2C control data indicates pixel-level control is not possible, since we only send a byte of data for each character. This is apparently tailored for alphanumeric information and not a full graphics display.

Curiously, given that both LCDs have two lines of 15 characters each, they seem to be one space off from each other when given identical control messages as they are here. For the base station LCD, we see two spaces in front of “CONNECTING…” and no space behind. In contrast, the handset LCD has that text centered on screen with one space in front and behind. If one of these screens had 15 characters and the other had 16, I could understand rendering one space off. But they both have 15 characters, so why would they do this?

The segmented area appears to be completely different between these two LCDs. Looking at the I2C traffic, 16 bytes were sent for 16*8 bits = up to 128 potential segments for control. Some of those bits go unused, as neither of these LCDs have 128 segments. For the handset LCD, I count 10 segments used for “–:– –/–” and an 11th for the battery icon. The same control bits just result in a nonsensical jumble of segments on the base station LCD, but I count 11 active segments in that jumble. It’s obvious they don’t have the same segments, but perhaps they use a similar subset that would be easiest to wire up in a LCD. I expect to get a better understanding once I start controlling this LCD with my own code.

LCD Behavior Between 5V Power Supply Candidates

There were nine pins on an LCD salvaged from an AT&T CL84209 cordless phone system. I found two candidates for its +5V power supply pin, but an experiment found that they both seemed to work. I doubt these are both +5V supply pins, as I expect the manufacturer would have removed unnecessary redundancy in the interest of cost. I hypothesize that one of them is the actual +5V supply pin, and the other one allowed +5V to leak through the circuit as an accidental side effect. Trying to guess which is which, I returned to recording analog behavior with my Saleae Logic 8.

For reference, here’s the behavior of handset LCD start-up that I want to replicate for pins 6-9. (Channels 4-7.)

The first experiment was to turn on the system without connecting any of the four mystery pins.

Channel 5 picked up 3.3V from elsewhere in the circuit. Channel 4 less so at 3V. The other two pins would sporadically spike up to one of those two voltages. This is definitely wrong, but I wanted to rule out the possibility this mystery LCD controller had a built-in voltage booster.

The next experiment was to put +5V (well, 4.8V) on Channel 4.

This looks much better. Channel 5 and 6 behavior looks correct, or at least close enough to my eyes. I don’t see anything that makes me think “Hmm, maybe this pin needs an external inductor, resistor, or capacitor.” Such as channel 7, who is trying and failing to hold 5V so I think it needs an external capacitor.

Then I switched +5V over to channel 7:

Channels 5 and 6 are again working roughly as expected, which explains why LCD segments look good no matter where +5V comes in. However, I see a weird multi-step behavior on channel 4 that is not ideal. Out of my options, I think I should put +5V on channel 4 and put a little capacitor (100nF ceramic) on channel 7:

Yeah, I think that’s my best approximation to the ideal behavior. The only difference I see is that, on the original data, channel 4 started with 3.3V then jumped up to 5V upon startup. It seems to be under LCD startup control, but I don’t see how. Maybe there’s something contributing elsewhere on the circuit board? I know there are still mysteries on this circuit buried underneath the handset LCD, something I intend to investigate later. For now, I hope putting +5V on pin 6 (channel 4) before initialization would not damage the LCD, because I plan on doing that for further exploration. Starting with a comparison between these two LCDs both responding to I2C 0x3E.

LCD in Parallel Test Round 2: 5V Power

There were nine pins on this LCD unit salvaged from an AT&T CL84209 cordless phone system. I think I’ve figured out five of them: 3.3V supply, ground, enable, I2C clock, and I2C data. The remaining four are still unknown but I noticed a correlation between their start-up behavior and the first control message sent to its I2C address of 0x3E. Perhaps those pins are not all input pins?

To get more data, I’m going to repeat the experiment of connecting two LCDs in parallel. One still mounted on remnant of a CL84209 handset circuit board, and the other was a bare module from the base station. The first time I tried this harebrained scheme, I connected all nine pins. Both displays ran, but at much lower contrast than usual.

This time, I won’t connect all the pins. Power supply, ground, and enable pins should be safe to connect in parallel. I2C data and clock lines are designed to connect to multiple devices in parallel, so electrically speaking it should be fine as well. But I2C is not designed to have two devices responding to the same address connected together. The protocol has no provision for such conflict resolution and usually I2C address conflict leads to chaos. The only reason we can get away with it this time is because these two screens respond identically (or close enough) so there aren’t conflicts to resolve.

With five out of nine pins connected in parallel, I will experiment to see what happens when I selectively connect the rest. For reference, here is the behavior recorded from the handset running standalone: Channels 4 and 7 should rise to 5V, channel 5 should be an 8kHz square wave between 3.3V and 5V, and channel 6 should be another 8kHz square wave from 0V to 3.3V in phase with channel 5.

Looking at this plot, I think I need to connect at least a pin to serve as a 5V power supply. Pin 6 (channel 4) and pin 9 (channel 7) are both candidates for 5V supply, since they both rise to 5V once things started up. Before the startup procedure, pin 6 (channel 4) received 3.3V as soon as the system received power, while pin 9 (channel 7) stayed at 0V until the startup procedure. Based on this difference, I think pin 6 (channel 4) is the better candidate to try, so I added a jumper wire to connect that to the handset circuit board.

It’s alive! And better yet, display contrast on both screens appears normal. Not faded-out as I saw in the previous parallel test with all nine pins connected.

Normal display contrast supports the hypothesis that the 8kHz voltage square waves were used for LCD segments, and furthermore this test implies those square waves were generated by the onboard controller. When these two LCDs were connected in parallel, their 8kHz voltage waves interfered with each other and lowered contrast on both. But if this is true, why do these signals need to be brought out as externally accessible pins? Why not just leave them internal? I don’t have a good guess for that.

As an additional data point, I disconnected pin 6 and connected pin 9. If my hypothesis is correct that pin 6 is the 5V power supply, disconnecting it and putting 5V on pin 9 should mean the display stays blank. Nope! My hypothesis was wrong or at least incomplete. When I disconnected pin 6, the display went blank as expected. But when I connected pin 9, the display came back to life. Both pin 6 and pin 9 could apparently serve as 5V power supply. Which one should I use? Perhaps taking another set of analog traces could provide some insight.

LCD Analog Activity Started By Digital Signal

I feel I have a working understanding of the I2C control messages necessary for controlling an LCD salvaged from an AT&T CL84209 landline phone system. But that is only part of the puzzle, because this LCD had input/output pins beyond I2C clock and data lines: there were four more pins to figure out. Represented by channel 4-7 in this logic analyzer trace:

Upon system power-up, one started at 3.3V and the others at 0V. Roughly two seconds after power-up, they come alive with two of them going up to 5.2V and the other two generating ~8kHz square waves. One between 0V and 3.3V, the other between 3.3V and 5.2V. I had assumed I would have to build circuitry to mimic these voltage values, but maybe I wouldn’t.

When zoomed in to when these pins became active, I could see the analog activity was immediately preceded by activity on the I2C bus. Comparing the timestamp against the list of decoded I2C messages, I found the I2C message visible on this graph is actually the very first set of messages sent to the LCD at address 0x3E.

This implies the LCD is not just a passive receiver of these signals, but an active participant who started something in response to the first initialization command sent over I2C bus. What we see here is some combination of functionality onboard the embedded chip, and some contribution from the circuit board. But I have no guesses at the division of labor between them. I assume the circuit board must contribute something to the process, otherwise the embedded controller could have just kept things internal and not incur the manufacturing cost of bringing those pins out.

I looked to the Sanyo LC75853N LCD controller datasheet I previously referenced for a different device to see if it had a counterpart to these pins. I note the OSC pin that required an external resistor and capacitor, and the VDD1 and VDD2 pins that required external voltage supply. None of these explanations fit the two pins that rose to 5.2V. The square wave between 0V and 3.3V might be an external oscillator, but I don’t know what that means for the other square wave between 3.3V and 5.2V.

On a quest for more data, I am going to try wiring these two LCDs in parallel again. The previous time was a risky experiment done in ignorance armed only with the fact I had very little to lose. This time I’m a little better informed which I hoped should translate to lower risk on the second try.

Examining Control Data for LCD at I2C Address 0x3E

My Saleae Logic 8 listening to the communication traffic on the I2C data bus of an AT&T CL84209 cordless phone headset heard thousands of messages within ten seconds of startup, addressed to ten different device addresses. I only cared about the messages related to its LCD screen and, fortunately, that data stood out and now I know this LCD is an I2C device with address of 0x3E.

Curious if knowing the I2C address will help me find more information on this LCD, I went online and… didn’t have much luck. Adafruit organizes a list of their I2C product addresses, but as of writing there’s nothing listed for 0x3E. I found the JHD1313 LCD module online, whose AiP31068 controller is an I2C device on address 0x3E. This chip is controlled with a series of command words, which is a control byte followed by a data byte. Different from the pattern I see on my Saleae decoded data, where character data is sent consecutively without command bytes in between. (Also, the datasheet annoyingly doesn’t give its I2C device address in hexadecimal or decimal form, only in binary form “011 1110” in the serial interface protocol chart.)

Empty-handed from this detour, I returned to my Python program parsing Saleae-decoded output. Out of 2569 captured messages, only 143 of them (5.57%) were addressed to 0x3E. This is a much more manageable number. Looking at that set, I found there were many two-byte messages and a few of the longer 18/19 byte messages.

The sequence of two-byte messages is almost identical throughout the capture, only difference is that the very first instance is one message shorter. (It omitted the final 0x00,0x02.) After that, the whole sequence repeats once every second. Lacking datasheet, I’m calling this the configuration sequence.

write to 0x3E ack data: 0x00 0x39 
write to 0x3E ack data: 0x00 0x39 
write to 0x3E ack data: 0x00 0x1C 
write to 0x3E ack data: 0x00 0x70 
write to 0x3E ack data: 0x00 0x5F 
write to 0x3E ack data: 0x00 0x6C 
write to 0x3E ack data: 0x00 0x0C 
write to 0x3E ack data: 0x00 0x02 (Omitted from initial startup)

Shortly after this is sent, three lines of display data are sent. The capture included three variations. The first one is sent only once, immediately after the initial configuration sequence is sent:

write to 0x3E ack data: 0x80 0x81 0x40 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 
write to 0x3E ack data: 0x80 0xC1 0x40 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 
write to 0x3E ack data: 0x80 0x40 0x40 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 

The first two lines are all spaces, and the third line is all zeros. I interpret this as the “clear screen” for this LCD.

Afterwards, we have two variations of display data with “CONNECTING…” text. The first one:

write to 0x3E ack data: 0x80 0x81 0x40 0x20 0x43 0x4F 0x4E 0x4E 0x45 0x43 0x54 0x49 0x4E 0x47 0x2E 0x2E 0x2E 0x20 
write to 0x3E ack data: 0x80 0xC1 0x40 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 
write to 0x3E ack data: 0x80 0x40 0x40 0x00 0x00 0x10 0x02 0x01 0x04 0x00 0x10 0x00 0x04 0x01 0x00 0x02 0x10 0x04 0x00 

Differs from the second one by only a single bit in the third line, 0x00 vs. 0x10:

write to 0x3E ack data: 0x80 0x81 0x40 0x20 0x43 0x4F 0x4E 0x4E 0x45 0x43 0x54 0x49 0x4E 0x47 0x2E 0x2E 0x2E 0x20 
write to 0x3E ack data: 0x80 0xC1 0x40 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 
write to 0x3E ack data: 0x80 0x40 0x40 0x00 0x10 0x10 0x02 0x01 0x04 0x00 0x10 0x00 0x04 0x01 0x00 0x02 0x10 0x04 0x00 

This is consistent with handset LCD behavior blinking the “battery low” icon in the custom segmented LCD area. From here we can tell the first two lines are alphanumeric text, and the third line are bits for segmented LCD control where 0 is clear and 1 is active.

Looking at the screen, I see the battery icon blinking once a second. (Half second on, half second off.) Given this, I had expected to see the two variations of display data to alternate. Instead, it’s usually two of one style followed by one of the other. This mystery was solved once I looked at the timestamps: once every second, the configuration sequence is sent immediately followed by the current display state. Even if the display state hadn’t changed from the previous display update. In between these configuration sequences, the battery icon state is toggled every half second without sending the configuration sequence.

Thanks to Saleae Logic software and a bit of Python, I feel I’ve gained a working grasp of digital control communication for this LCD. I don’t know what any of those control flags individually mean, but I wouldn’t need to fully understand them if I just wanted to send alphanumeric data and toggle segments. Outside of these digital signal pins, there’s still the unknown of analog voltage pins. But looking at the timeline, I noticed an interesting correlation.

Cordless Handset LCD is I2C Device

I’m working to understand how to control the LCD on the handset of an AT&T CL84209 landline phone system, and I started by observing the behavior of its eight IO pins (plus one pin already established to be ground) using the analog mode of my Saleae Logic 8 analyzer. Two pins were immediate candidates for a serial data pin and its corresponding clock pin, so I switched my analyzer to digital mode observing those two pins specifically.

Mouse hovering over the clock line, the analyzer software told me it started at a frequency of 7.2kHz and then sped up to 100kHz during the startup sequence. 100kHz is very promising, because it matches “Standard Mode” speed of I2C. The previous LCD controller I played with used a proprietary Sanyo protocol called CCB, but I’m hopeful this unknown chip uses Philips I2C. So in the spirit of “be sure to check the easy thing first” I activated I2C protocol analyzer built in to Saleae’s software. This presented a stream of decoded bytes and no sign of error.

Good news: it is indeed I2C!

Bad news: within less than ten seconds of powerup, there are 2569 captured and decoded messages. That is a lot of raw data to wade through when I don’t datasheets to make sense of it all. But the situation isn’t quite as bad as it initially looked. While I’ve tapped pins directly connected to the LCD, that doesn’t mean all the data we see on those lines are relevant. I2C is a data bus for multiple devices to communicate, and I see multiple addresses listed in the I2C decoder output.

I wanted to filter messages by address, but I failed to find such an option in Saleae software. Instead, I copied the data output into a Jupyter notebook and wrote a bit of quick-and-dirty Python to parse them. There were ten unique I2C addresses in this output:

{'0x52', '0x51', '0x50', '0x56', '0x3E', '0x53', '0x54', '0x55', '0x4B', '0x57'}

Scrolling through the decoder output, traffic to address 0x3E stood out because they were the longest. Many of these I2C messages had only one or two bytes, but some messages to 0x3E were much longer. The long messages have a pattern, they come in sets of three: two 18-byte messages followed by one with 19 bytes.

write to 0x3E ack data: 0x80 0x81 0x40 0x20 0x43 0x4F 0x4E 0x4E 0x45 0x43 0x54 0x49 0x4E 0x47 0x2E 0x2E 0x2E 0x20 
write to 0x3E ack data: 0x80 0xC1 0x40 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 
write to 0x3E ack data: 0x80 0x40 0x40 0x00 0x00 0x10 0x02 0x01 0x04 0x00 0x10 0x00 0x04 0x01 0x00 0x02 0x10 0x04 0x00 

These messages won’t have enough bits to control individual dots on the dot-matrix display. However, they might contain alphanumeric data for the LCD controller to use a font lookup table. Again, in the spirit of trying the easy thing first, I compared the first line of data against an ASCII chart.


“CONNECTING…” is the first line displayed on the LCD! We have a winner, time to dig into traffic addressed 0x3E for this I2C LCD.

Cordless Handset LCD Power-Up in Saleae Analog Mode

I have the remnants of a cordless handset from a AT&T CL84209 landline phone system. It can still power up enough to put “CONNECTING…” on its LCD and I want to learn how to talk to that LCD myself. To do this I’ve attached wires to that LCD so I could probe its control signals with my Saleae Logic 8 analyzer.

Before I get to the digital signal analysis, though, I need to be sure I’m listening on the correct lines. This is why I connected all eight potential data lines (plus the one known ground I probed earlier) to watch them all at once with my Saleae running in analog mode. This is a secondary capability for the device and while it cannot replace a real oscilloscope it is nevertheless very useful to me as I only have a cheapo single channel oscilloscope with unreliable voltage levels. While I might buy a real multi-channel oscilloscope in the future, it is unlikely to be an eight-channel unit.

Here is the handset power-up sequence, and roughly eight seconds after power is supplied:

When I probed with a voltmeter, pin 1 (channel 0) and 5 (channel 3) were candidates for 3.3V input power. With this graph, I see square wave dips in channel 0 that indicate an enable signal, so pin 5 is +3.3V. Which makes some sense as it is adjacent to ground on pin 4.

After pin 1 enable signal stayed high, right around the 2 second mark, something significantly happens where we saw activity on channels 4-7. Here is a zoomed in view of that time period:

Pin 3 (channel 2) looks like data bits clocked by pin 2 (channel 1). I’ll look at them more closely later in digital analysis mode.

We also see the beginnings of startup on remaining four pins of unknown:

  • Pin 6 (channel 4) had 3.3V as soon as power is applied to the system, and later it jumps up to 5.2V and stays there.
  • Pin 7 (channel 5) likewise had ~3.3V immediately upon power up, and when pin 6 jumps up to 5.2V it oscillates between 3.3V and 5.2V.
  • Pin 8 (channel 6) started out flat at 0V, but would oscillate between 0V and 3.3V.
  • Pin 9 (channel 7) started out flat at 0V, but would jump up to 5.2V and stay there.

The two oscillating pins are both square waves and they move in phase. (Rise and fall at the same time.) The period from one rising edge to the next is about 125us, translating to 8kHz.

Zooming out on the timescale, I see no signs of data modulation on these pins:

I think these are voltages applied to LCD segments. It would be easy for me to replicate pin 8 square wave of 0V to 3.3V with a 3.3V microcontroller like ESP8266, but it’s not clear how I would replicate pin 7 square wave between 3.3V and 5.2V. A headache I will set aside for later.

The relationship between pins 6 and 9 is puzzling. Since pin 6 had 3.3V immediately upon powerup, and its voltage started rising before the other three pins, I thought it was the best candidate for a 5.2V power supply rail. My hypothesis was that it is connected to a boost converter external to the LCD that would pump the 3.3V up to 5.2V. The counterargument is the fact that pin 9 reached 5.2V before pin 6 did, so I’m not sure what’s going on.

While I think over the analog voltage startup behavior puzzle, I switched my logic analyzer to digital mode (what it’s designed for) for a look at the digital data and clock lines.

Preparing Cordless Handset LCD for Logic Analysis

I’ve decided to take another look at a pair of LCD units salvaged from an AT&T CL84209 landline phone system. I have a bare LCD from the base station, and an LCD still attached to the remnants of a cordless handset. I had wired those two LCDs in parallel, a crazy roll of the dice that paid off in knowledge that those two LCDs use the same communication protocol. Now I am armed with a logic analyzer (Saleae Logic 8) and intend to get more details.

The first step is to attach wires to the handset LCD so I could probe communication traffic, similar to how I attached the bare LCD earlier but with less risk and higher chance of doing something useful. I recycled the bundle of wires I had used to perform the same task on a Toyota tape deck faceplate, which had more wires that I folded out of the way for this project. Having done similar tasks a few times, I’m starting to think about buying PCBite probes that promise to let me skip this soldering task. But I’m not yet at the point where I expect to use them enough to justify the expense.

There are nine pins on this LCD, which given my lack of official datasheet I’ve arbitrarily numbered from left to right as shown in the picture above. Earlier I had probed these connection points with a voltmeter while the handset circuit board was running. All of the pins appear to be safely within Saleae logic analyzer 25V limit relative to ground, which I determined to be pin 4. That left 8 pins I don’t understand and conveniently, my Saleae has 8 inputs so I could connect them all and not have to make decisions on which pins to omit. Once connected, I could take my first look with the Saleae in analog mode.

LCD Exploration Unwinds Back to CL84209

In my electronics teardown and salvaging adventures, I kept a few segmented LCD units with the intent to learn about them later. After I watched Joey Castillo’s Remoticon presentation on LCDs, I decided it was time to take some LCDs out of my pile and start learning how to use them.

In hindsight, I undertook this adventure in backwards order. I couldn’t make heads or tails of my first attempt, and the second didn’t fare much better though it gave me enough information to infer those two LCDs both came from an AT&T CL84209 wireless landline system. The third attempt was a car tape deck I managed to power up, but it was too advanced of a system and wouldn’t help me learn LCDs basics. The fourth item was an LCD salvaged from a food thermometer, but I had problems trying to attach wires to this display. I was finally successful on the fifth try, obtaining full control over a simple segmented LCD salvaged from an electric blanket controller.

After that success, I started unwinding my stack of failures. A bit of hackery and a lot of patience allowed me to map out the food thermometer LCD on my second try.

With these two examples of low-level LCD control, I returned to the tape deck and learned how to work at a higher level with its LCD controller. I even learned a new communication protocol (Sanyo’s CCB) and programmed an Arduino Nano to speak its language.

Obtaining a full understanding of how to interface with the faceplate was very satisfying. Two key factors made it possible: the LCD controller’s datasheet, and a Saleae logic analyzer.

Armed with this experience, I will now return to where I started: to the mystery LCD units salvaged from an AT&T CL84209 landline phone system. I found nothing to identify their LCD controller, so I would have to go without a datasheet. However, I do have one valuable datapoint: a risky experiment proved that while those two LCDs were different, they do communicate with the same protocol. This is enough of a starting point for me to dive in with my logic analyzer.

Home Assistant OS in KVM Hypervisor

I encountered some problems running Home Assistant Operating System (HAOS) as a virtual machine on a TrueNAS CORE server, which is based on FreeBSD and its bhyve hypervisor. I wanted to solve these problems and, given my good experience with Home Assistant, I was willing to give it dedicated hardware. A lot of people use a Raspberry Pi, but in these times of hardware scarcity a Raspberry Pi is rarer and more valuable than an old laptop. I pulled out a refurbished Dell Latitude E6230 I had originally intended to use as robot brain. Now it shall be my Home Assistant server, which is a robot brain of sorts. This laptop’s Core i5-3320M CPU launched ten years ago, but as a x86_64 capable CPU designed for power-saving laptop usage, it should suit Home Assistant well.

Using Ubuntu KVM Because Direct Installation Failed Boot

I was willing to run HAOS directly on the machine, but the UEFI boot process failed for reasons I can’t decipher. I couldn’t even copy down an error message due to scrambled text on screen. HAOS 8.0 moved to a new boot procedure as per its release announcement, and the comments thread on that page had lots of users reporting boot problems. [UPDATE: A few days later, HAOS 8.1 was released with several boot fixes.] Undeterred, I tried a different tack: install Ubuntu Desktop 22.04 LTS and run HAOS as a virtual machine under KVM Hypervisor. This is the hypervisor used by the Linux-based TrueNAS SCALE, to which I might migrate in the future. Whether it works with HAOS would be an important data point in that decision.

Even though I expect this computer to run as an unattended server most of the time, I installed Ubuntu Desktop instead of Ubuntu Server for two reasons:

  1. Ubuntu Server has no knowledge of laptop components, so I’d be stuck with default hardware behavior that are problematic. First is that the screen will always stay on, which wastes power. Second is that closing the lid will put the machine to sleep, which defeats the point of a server. With Ubuntu Desktop I’ve found how to solve both problems: edit /etc/systemd/logind.conf and change lid switch behavior to lock, which turns off the screen but leaves the computer running. I don’t know how to do this with Ubuntu Server or Home Assistant OS direct installation.
  2. KVM Hypervisor is a huge piece of software with many settings. Given enough time I’m sure I could learn all of the command line tools I need to get things up and running, but I have a faster option with Ubuntu Desktop: Use Virtual Machine Manager to help me make sense of KVM.

KVM Network Bridge

Home Assistant instructions for installing HAOS as a KVM virtual machine was fairly straightforward except for lack of details on how to set up a network bridge. This is required so HAOS is a peer on my home network, capable of communicating with ESPHome devices. (Equivalent to the network_mode: host option when running Home Assistant Docker container.) HAOS instruction page merely says “Select your bridge” so I had to search elsewhere for details.

A promising search hit was How to use bridged networking with libvirt and KVM on It gave a lot of good background information, but I didn’t care for the actual procedure due to this excerpt: “Notice that you can’t use your main ethernet interface […] we will use an additional interface […] provided by an ethernet to usb adapter attached to my machine.” I don’t want to add another Ethernet adapter to my machine. I know network bridging is possible on the existing adapter, because Docker does it with network_mode:host.

My next stop was Configuring Guest Networking page of KVM documentation. It offered several options corresponding to different scenarios, helping me confirm I wanted “Public Bridge”. This page had a few Linux distribution-specific scripts, including one for Debian. Unfortunately, it wanted me to edit a file /etc/network/interfaces which doesn’t exist on Ubuntu 22.04. Fortunately, that page gave me enough relevant keywords for me to find Network Configuration page of Ubuntu documentation which has a section “Bridging” pointing me to /etc/netplan. I had to change their example to match Ethernet hardware names on my computer, but once done I had a public network bridge upon my existing network adapter.

USB Device Redirection

Even though I’m still running HAOS under a virtual machine hypervisor, ESPHome could access USB hardware thanks to KVM device redirection.

First I plug in my ESP32 development board. Then, I open the Home Assistant virtual machine instance and select “Redirect USB device” under “Virtual Machine” menu.

That will bring up a list of plugged-in USB devices, where I could select the USB to UART bridge device on my ESP32 development board. Once selected, the ESPHome add-on running within this instance of HAOS could see the ESP32 board and flash its firmware via USB. This process is not as direct as it would have been for HAOS running directly on the computer, but it’s far better than what I had to do before.

At the moment, surfacing KVM capability for USB device redirection is not available on TrueNAS SCALE but it is a requested feature. Now that I see the feature working, it has become a must-have for me. Until this is done, I probably won’t bother migrating my TrueNAS server from CORE (FreeBSD/bhyve) to SCALE (Linux/KVM) because I want this feature when I consolidate HAOS back onto my TrueNAS hardware. (And probably send this Dell Latitude E6230 back into the storage closet.)

Start on Boot

And finally, I had to tell KVM to launch Home Assistant automatically upon boot. By checking “Start virtual machine on host boot up” under “Boot Options” setting.

In time I expect that I’ll learn the KVM command lines to accomplish what I’m doing today with Virtual Machine Manager, but today I’m glad VMM helps me get everything up and running quickly.

[UPDATE: virsh autostart is the command line tool to launch a virtual machine upon system startup. Haven’t yet figured out command line procedure for USB redirection.]

Home Assistant OS in TrueNAS CORE Virtual Machine

I started playing with Home Assistant in the form of Home Assistant Core, a docker container, for its low commitment. Once I decided Home Assistant was worthwhile, I moved up to running Home Assistant Operating System as a virtual machine on my TrueNAS CORE server. This move gained features of Home Assistant Supervisor and I found the following subset quite useful:

  • Easy upgrade and rollback for failed upgrades.
  • Add-on integration features, especially for ESPHome.
  • Ability to backup critical data in a compact *.tar file.

The backup situation is a tradeoff. When I ran Home Assistant Core docker container, I could map its data directory to my TrueNAS storage pool. It was a more robust data retention system, as I had configured it for nightly snapshots and regular backups to external media. However, this would take upwards of hundreds of megabytes especially when I’m flooding the Home Assistant database. In contrast, the data backup archive generated by Home Assistant Supervisor is tiny at only a few megabytes. I have ambition to eventually get the best of both worlds: a Home Assistant automation that triggers Supervisor backups, and then store those backup files on my TrueNAS storage pool. This should be possible, as people have created addons to perform automatic backups and upload to Google Drive. But right now I have to do it manually.

Unrelated to the backup situation, there were two significant downsides to running Home Assistant OS on a TrueNAS CORE virtual machine.

  1. The virtual machine does not have access to hardware. If ESPHome add-on could access USB, it could perform first-time firmware upload on ESP32/ESP8266 devices. Without hardware access, I have to perform initial upload some other way which is cumbersome. (Following uploads could be done via WiFi, a huge benefit of ESPHome.)
  2. There are various problems with the FreeBSD bhyve hypervisor running Linux-based operating systems. A category of them (apparently there are more than one) interferes with the ability for a Linux operating system to reboot itself. In practice, this means every time the Home Assistant OS updates itself and reboots, it would shut down but not restart. At one point, I could not perform manual shutdown from TrueNAS interface, so the horrible workaround was to reboot my TrueNAS server. After a few TrueNAS updates, I can now manually shut down and restart the VM. But it is still a big hassle to do this on every Home Assistant OS update.

Due to these problems, I would NOT recommend running Home Assistant OS as a TrueNAS CORE virtual machine. Issue #2 became quite annoying and, when my Home Assistant got stuck trying to reboot for an upgrade to Home Assistant OS 8.0, I decided it was time to try a different hypervisor.

Salvage Tape Deck Faceplate Connector (Toyota 86120-08010)

I’ve successfully made a simple Larson Scanner demo from the faceplate module of a Toyota 86120-08010, the stock tape deck audio head unit of a 1998 Toyota Camry LE. It gave me confidence that I wouldn’t need the original mainboard anymore.

Nor would I expect to want to probe communication between the original mainboard and the faceplate, both separately and alone. Time for a little cleanup work, undo the tangle of wires and get to something more streamlined.

I started by detaching the wires I attached earlier.

The excellent Engineer SS-02 solder sucker helped me remove all the solder attaching the mainboard connector, freeing it for reuse.

Good news: This connector has two rows of easy-to-solder through-hole pins, with the 0.1″ pitch common to perforated prototype boards. The two rows are 0.1″ apart as well.

Bad news: The two rows are staggered, which won’t fit on a 0.1″ perforated prototype board. Reusing this connector properly will mean a custom circuit board.

In the meantime, I can still make use of that connector even if “improperly”. I had soldered wires directly to the faceplate for probing and experimentation, but I can move all of those wires to that connector even without a circuit board.

Soldering wires directly to the pins isn’t best practice, but it allows me to move and store this assembly with less worry I’ll accidentally yank something off the faceplate.

Larson Scanner Demo for Tape Deck LCD

I am happy with a sense of accomplishment after I deciphered all the information necessary to utilize this circuit board, formerly the faceplate for a salvaged car tape deck. I started this investigation when I found I could power it up under control of the original mainboard. Now, I can work with the LCD and read all knobs and buttons with an Arduino, independent of the original mainboard. My original intent was just to see if I could get to this point. I thought I would learn a lot whether I succeeded or failed trying to control this faceplate. I have gained knowledge and experience I didn’t have before, and a faceplate I can control.

Now what?

It feels like I should be able to build something nifty with this faceplate, there’s room to be creative in repurposing it. At the moment I don’t have any ideas that would creatively utilize the display and button/knob input, but I could build a simple demo. This LCD is wide and not very tall, so I thought I would make it into a simple Larson Scanner. (a.k.a. Cylon lights a.k.a. K.I.T.T. lights.)

First, I divided the LCD segments into 16 groups of roughly similar size. I started using my segment map to generate the corresponding bit patterns by hand but then decided I should make a software tool to do it instead. I’ve already written code to light up one segment at a time for generating the segment map, it took only a bit of hacking to make it into a drawing tool. I use the knob to move segments as I did before, but now I could press the knob to toggle the selected segment. Every time I pressed the knob, I print the corresponding bit pattern out to serial terminal in a format that I could copy into C source code.

I then added a different operating mode to my Arduino test program. Pressing the Volume knob would toggle between drawing mode and Larson Scanner mode. While in Larson Scanner mode, I would select two of those 16 groups based on scanner position, and bitwise OR them together into my display. This gives me a nice little demo that is completely unrelated to this LCD’s original purpose, and confidence I no longer need this tape deck’s original mainboard.

Source code for this demo is publicly available on GitHub.

Pinout of Tape Deck Faceplate (Toyota 86120-08010)

It is time to wrap up investigation into the workings of a tape deck faceplate, salvaged from the stock audio head unit of a 1998 Toyota Camry LE. I believe I’ve deciphered all the information necessary to reuse this faceplate independently from the rest of the tape deck. Summarized in this pinout report with links to more details.

The faceplate circuit board is largely built around a Sanyo LC75853N chip, which communicates via a Sanyo proprietary protocol called CCB (Computer Control Bus). An external microcontroller (I used an Arduino Nano in experiments to date) can dictate what is displayed on the LCD (see segment map here) and scan pressed/not-pressed state of buttons (see button map here).

Some faceplate components are independent of LC75853N:

From right-to-left, functionality I observed on these pins are:

ACC5VPower supply for digital logic.
+5V relative to GND
LCD-DOCCB digital data out.
LC75853N address 0x43
Single 4-byte transmission to microcontroller.
LCD-DICCB digital data in.
Microcontroller to LC75853N address 0x42.
Three 7-byte transmissions to LC75853N.
LCD-CLKCCB clock signal.
Active-low generated by microcontroller.
LCD-CECCB enable.
Active-high generated by microcontroller.
CD-EJEEject button.
Normally open, shorts to GND when “Eject” button is pressed.
ILLIllumination power supply (positive).
+5V to +14.4V (~60mA) relative to ILL- for variable button backlight brightness.
LCD-BLLCD backlight power supply (positive).
+6V (~60mA) relative to BL-
VOL.CONVolume control potentiometer.
Voltage between ACC5V (full clockwise) and GND (full counterclockwise)
PULS-AAudio mode quadrature encoder knob – A
ACC5V or GND, will be opposite of B when at a detent.
A and B briefly identical during transition between detents.
PULS-BAudio mode quadrature encoder knob – B
ACC5V or GND, will be opposite of A when at a detent.
A and B briefly identical during transition between detents.
GNDDigital logic power (negative).
Relative to ACC5V
ILL-Illumination power supply (negative)
Relative to ILL
BL-LCD backlight power supply (negative)
Relative to LCD-BL
RESETUnknown. Observed 0V relative to GND.
LC75853N has no reset pin.
Seems OK to leave it unconnected/floating.
Unknown. Observed 0V relative to GND.
Seems OK to leave it unconnected/floating.

Source code for this investigation (and accompanying demo) is publicly available on GitHub.

Button Presses on Tape Deck Faceplate (Toyota 86120-08010)

I’ve mapped out all LCD segments on a Toyota 86120-08010 tape deck faceplate, allowing me to control what is displayed from an Arduino. Constrained by the nature of a segmented LCD, of course. These segments were customized for a tape deck, doing what it needs and no more. Any projects trying to repurpose it would have to get creative. This will be even more difficult than abstract Nyan Cat on a VCR VFD!

But I have more than just the LCD, I have the entire faceplate which gives me option for user interactivity. Two knobs which I still have, and buttons which are now gone but their electrical traces are still present. I can find something conductive to bridge these traces like the buttons used to do, or I can solder wires connecting to switches elsewhere. Either way, I needed to write code for my Arduino to read key scan data from the Sanyo LC75853N chip. Just like LCD segment control data, my goal is to emulate the original mainboard as closely as I can. I will be guided by the datasheet and what my logic analyzer captured.

Thanks to lessons learned from doing LCD segment control CCB communication, reading keyscan data over CCB was relatively straightforward and I could dump control data out to Arduino serial monitor.

I will follow the KD1-KD30 numbering system used in the datasheet timing diagram.

Button map for Toyota 86120-08010 faceplate. (Click to view full size)

This key map shows the KD number for almost all of the buttons on the faceplate, including the push action on both knobs. The lone exception is the “Eject” button, which has its own dedicated wire and is not part of LC75853N key matrix. These numbers may look odd at first glance, but they make sense once we look at how they would be wired to the LC75853N:

These fifteen buttons make use of KS3-6 and KI2-5, learning only KD14 unused in its matrix. If I wanted to add in a single button, I will try to find KS3 and KI4 traces on the faceplate to wire in KD14. If I want to add more buttons, I might need to solder directly to the unused IC pins KI1, KS1, and KS2 as I wouldn’t expect any traces for those unused pins on the faceplate circuit board.

Feeling good that I’ve figured out the input & output of this faceplate, it’s time to wrap it all up.

Source code for this investigation is publicly available on GitHub.

Segmented LCD on Tape Deck Faceplate (Toyota 86120-08010)

Thanks to well-labeled connectors and an online datasheet, I can write Arduino code to control faceplate LCD of the stock tape deck audio unit from a 1998 Toyota Camry LE. (86120-08010). However, knowledge of the digital wiring doesn’t tell me anything about the physical location and shape of each segment in the LCD. I will build a segment map with the help of a knob already on the faceplate. The Sanyo LC75853N chip could control up to 126 segments. I edited my Arduino program to turn on all of them, so I could take this picture to see where segments even existed.

It reflected the custom nature of a segmented LCD. Some of these digits would only ever display numbers, so they had the standard 7 segments for numeric display. Others have a secondary use to display letters for a few audio settings, and those digits had more than 7 segments. But they can’t display arbitrary letters, only exactly what was needed and no more.

With the full set of available segments in hand, I changed the program to turn on just one segment at a time interactively selected via the “Audio Mode” knob. Since each segment is a single bit, I could print out my control bits to Arduino serial monitor to see current active segment’s programmatic address.

I’m using the same numbering system as used in the datasheet, D1 to D126. From there, I generated this segment map:

LCD segment map for 86120-08010. Click to view full size.

When I first started my Arduino program, I saw nothing for the first few turns of the dial. I thought perhaps my program was faulty, but a few turns later I saw my first result for D13. From there on it was relatively straightforward, working from bottom-to-top and right-to-left. Some notes:

  • D24 is missing, a mysterious gap.
  • There were a few segments that I had thought were separate but were actually the same segment. For example, two visually distinct segments were actually just D54. This was disappointing, because it restricted the number of letters we could show. (Example: a good looking “M” would be possible, but a good looking “N” wouldn’t.)
  • Along the same lines, some numeric segments looked like separate segments purely for the sake of preserving the 7-segment aesthetic. The three segments D101, D102, and D105 together could display either “1” or “2” but visually looked like 6 segments instead of 3.
  • The leftmost large numeric digit puzzled me. It could only display 1, which is fine. But given that, why are the two segments D91 and D92 individually addressable? I can’t think of a reason why we’d want to display only the top or bottom half of a “1”.

Here is a table from the LC75853N datasheet mapping segment numbers to pins. I colored in the segments that were seen and a clear pattern emerged: This LCD allocation avoided using the first four pins (S1-S4) leaving the option of using them as general-purpose output wires. (P1-P4) At the end, stopping at D105 meant they didn’t have to wire up the final seven output pins. (The final two S41 and S42 would have had to been reallocated from key scan duty KS1 and KS2, if used.)

None of this explains why D24 is missing. The answer to this mystery must lie elsewhere.

Now that I know what is on this LCD available to display, maybe I can think of a creative way to reuse it. While I think that over, I’ll proceed to work through how to read input from this faceplate.

Source code for this investigation is publicly available on GitHub.

Reading Faceplate “Audio Mode” Knob

Once I got a retired faceplate’s LCD up and running, I realized I was wrong about its backlight circuitry. Now that it’s been sorted out, attention returns to the LCD. I want to map out all the segments, which means I need to get some way to interactive select individual segments. Recently I’ve used ESPHome’s network capabilities for interactivity, but this project uses an Arduino Nano for its 5V operating voltage. I can wire up my own physical control, but the faceplate already had some on board. Earlier probing established Power/Volume knob is a potentiometer, and Audio Mode knob is a quadrature encoder with detent.

The encoder is perfect for selecting individual segments. I can write code to activate one segment at a time. When the knob is turned one way, I can move to adjacent segments in one direction. When the knob is turned the other way, segment selection can follow suit. However, this knob does have one twist relative to my prior experience: Each detent on this encoder is actually two steps. When the knob is at rest (at a detent) the A and B pins are always different. (High/low or low/high). Intermediate values where A and B pins are the same (high/high or low/low) occur between detents.

This places timing demands for reading that knob. For encoders where each detent is a single step change and turned by human hands, polling once every hundred millisecond or so would be fast enough. However, since this knob can flash through a step very quickly between detents, I need to make sure those steps do not get lost.

There are many quadrature encoder libraries available for the Arduino platform. I selected this one by Paul Stoffregen, who I know as the brains behind the Teensy line of products. When working with interrupt-capable pins, this library will set up hardware monitoring of any changes in encoder state. This makes it very unlikely for encoder steps to get lost. According to Arduino documentation for attachInterrupt(), all ATmega328-based Arduino boards (including the Arduino Nano I’m using) have two interrupt-capable pins: 2 and 3. Using those pins resulted in reliable reading of knob position for mapping out segments of this LCD.

Source code for this investigation is publicly available on GitHub.

Two Separate Sets of Faceplate Illumination (Toyota 86120-08010)

I’ve successfully programmed an Arduino to interface with LCD controller on the faceplate of a retired car tape deck. When under the control of its original mainboard, the LCD segments and the LCD backlight always turned on and off in lockstep, leading me to think LCD backlight was under control of the LCD segment controller. But now that I have active LCD segments but no LCD backlight, I know my assumption was wrong. I’ll need to look elsewhere.

Looking at my notes from measuring voltage of each pin relative to data ground, two candidates stood out because their voltage levels changed when the LCD was active vs. inactive. When the LCD is on, there is ~6V difference between them. And when the LCD is off, there is ~0.5V between them. One candidate’s label was cropped at the bottom. Earlier I guessed maybe it said “CD-BL” but now that I look closer, I think the vertical bar to the left is part of the text: a “L” with its bottom cropped. That label is possibly “LCD-BL” which I optimistically expand to “Liquid Crystal Display Back Light”. The other candidate pin is labeled “BL-” which would be the negative terminal. Notably, “BL-” fluctuated relative to “GND” so these two pins, if they are our targets, have their own voltage planes.

The first test was with my backlight LED tester, which quickly shot up to the preset current limit of 20mA at less than 1V while the backlight stayed dark. I then measured with an Ohm meter, which indicated just 10 Ohms between these two pins. If that is a LED current-limiting resistor, it is the smallest I’ve ever measured. Might this backlight be some technology other than LEDs? I don’t know how a compact fluorescent light would look like under an Ohm meter. (I do know a filament bulb usually shows up as a short a.k.a. direct connection with no resistance.) I can’t see the actual light source, as it is buried within the metal case of the LCD assembly.

Well, this is a piece of salvaged electronics and I have little to lose. Especially since I believe the backlight is on its own circuit. So if I accidently destroy the backlight, I still have the rest of the faceplate to play with. I soldered wires to “LCD-BL” and “BL-” and connected it to my bench power supply. Gradually turning up the voltage, I start seeing a dull glow at just under 5V and got brighter as it reached 6V.

Illumination of this backlight isn’t great, but consistent with how it looked when I powered it up with the original mainboard.

The diffuser behind this LCD isn’t nearly as good as what we see behind, say, the screen of an Amazon Fire tablet. But it works well enough for us to see the LCD segments in the dark, and that’s all it needed to do in a car. These LEDs do not need to be daylight visible, as the LCD is readable in the presence of ambient daylight without backlight help.

At 6V this backlight drew almost 70mA, which is LED territory after all. If this was a piece of cheap consumer electronics, I would say 70mA indicates three heavily-driven LEDs. But this is from a Toyota, a brand with reputation for engineering for longevity. So I’m going to assume it actually represents four (or more) lightly driven LEDs. I increased the voltage until 9V, at which point it drew 80mA and was still pretty dim and uneven. I guess “bright enough to be legible at 6V” is all we’re going to get out of this backlight.

Since I had the bench power supply set up for testing LEDs anyway, I connected wires to two other pins “ILL” and “ILL-” which I had already determined to be on their own independent voltage plane as well. These wires are connected to the in-car dashboard illumination circuit, which has a dimmer knob. Being on a separate power plane explains the behavior I remember from using this tape deck in the car: lights illuminating faceplate buttons would change in brightness as I moved the dimmer knob, but the LCD backlight LED stayed at constant illumination. When I powered them directly on ILL/ILL-, I start seeing a tiny bit of glow at just over 5V (<10mA) and brighter as the voltage is increased.

I stopped at 14.4V (80mA) which is the maximum recommended voltage level for automotive lead-acid batteries, and thus likely to be the ceiling for automotive electrical systems like this one.

Between these two sets of lights, it means any project to repurpose them will need to generate multiple voltages. Up to 14.4V for button backlights (variable if we want dimming), 6V for LCD backlight, and 5V for digital logic including physical controls like the Audio Mode knob.