FormLabs Form 1+ OLED Screen Updates

It looks like the OLED dot matrix display in my broken FormLabs Form 1+ laser resin 3D printer uses a SSD1305 controller, based on communication traffic captured during its initial power-up sequence. Walking through that data while cross-referencing with the SSD1305 datasheet taught me a lot, and now I can apply this knowledge to examine logic analyzer traces from OLED reacting to other printer activities.

The Form 1+ doesn’t really have a power switch. When the 24VDC power supply is plugged in, it immediately starts running which includes the OLED power-up sequence I examined. But the user doesn’t see anything, because OLED display frame buffer has been filled with all black pixels. It’s not until they press the front panel button does the OLED start displaying visible pixels, and I have a logic analyzer trace of this startup sequence.

Visually, the startup sequence is a short animation of FormLabs text and butterfly logo. From off the bottom of the screen, it translates upwards until the text and logo is centered on screen for a brief second marking the end. From that point on, the screen is used to display up to four lines of text. Before this animation started, I see an initialization sequence identical to the power-up sequence: set all parameters and clear all eight memory pages to zero. After that, the animation starts running. When reading the SSD1305 datasheet, I saw it had a vertical scroll mode where bitmap in memory can be scrolled by changing the rendering start address. I thought that’s what the FormLabs animation used, but it wasn’t. Each frame of the animation is a full screen update sending four blocks of data for pages 0-3.

It appears memory pages 4-7 are not actively used for this application, which makes sense as the SSD1305 is designed for up to 132×64 pixels and we only have 128×32 on this OLED. However, those four pages of update data are transmitted in reverse order. Page 3 first, then 2, then 1, then 0. I wonder why? Hypothesis: This is to minimize visual artifacts. Imagine what happens if we update a memory page at the exact same time SSD1305 is displaying data from that page. We’d see a part of the old image mixed in with the updated image. Assuming the SSD1305 renders in increasing page order, sending data in the same increasing order means worst case unlucky timing will mess up all four pages. But if we update page in decreasing order, even the unluckiest timing scenario means only one out of four pages would be messed up.

Guesses or not, I feel like I have a pretty grasp of this OLED display module. Enough to try controlling it with my own code.

FormLabs Form 1+ OLED Control Consistent with SSD1305

I’ve been looking at various components of a broken FormLabs Form 1+ laser resin 3D printer, right now the focus is its front panel dot matrix OLED display. My first attempt at using a logic analyzer on its control signals told me which wires were active, but the actual data were gibberish. Fortunately, a second attempt retrieved sensical data waveforms. Someone on the FormLabs forums speculated this was an OLED display built around a SSD1305 controller, so now I will see if my captured data matches commands listed in SSD1305 documentation.

This stream of data represents system powerup, a set of commands (channel 0 white line is low) sent just before the first batch of data (channel 1 white line is high). If I interpret these bytes in context of SSD1305 datasheet, I get the following:

  • 0xAE: Display OFF.
  • 0xD5 0x10: Set display clock divide ratio to 1:1 and oscillator frequency to 300kHz.
  • 0xA8 0x1F: Set multiplex ratio to 31. (0x1F)
  • 0xD3 0x00: Set display offset to zero.
  • 0x40: Set display start line to zero.
  • 0xAD: Master Configuration for external Vcc power supply.
  • 0x8E: ???
  • 0x20 0x02: Set memory addressing mode to 0x02 (Page Addressing Mode.)
  • 0xA0: Set segment remap to 0 (Column address 0 is SEG0)
  • 0xC8: Set COM output scan direction to 1 (Remapped mode. Scan from COM[N~1] to COM0)
  • 0xDA 0x12: Set COM pins hardware configuration. (Disable COM Left/Right remap, Alternative COM pin configuration.)
  • 0x91 0x3F 0x3F 0x3F 0x3F: Set current drive pulse width of BANK0, color A, B, and C all to maximum valid value of 0x3F (63 clocks).
  • 0x81 0xFF: Set contrast control for BANK0 to 0xFF. (256, which is maximum contrast.)
  • 0x82 0xFF: Set brightness for area color banks to 0xFF. (256, which is maximum brightness.)
  • 0xD9 0xD2: Set precharge period to 0xD2. (Phase 1 period of 0x2 clocks, phase 2 period of 0xD or 13)
  • 0xDB 0x08: Set VCOMH Deselect Level to 0x08, but 0x08 is not in the list of valid values?
  • 0xA4: Entire display ON to display RAM content.
  • 0xA6: Set Normal Display. (Instead of 0xA7 for Inverted.)

This looks like an entirely sensible chain of commands for initial startup, aside from the two gaps: command 0x8E and parameter 0x08 for command 0xDB. The datasheet I have is rev 1.9 dated May 2008, so it’s possible those commands were added later. Even though they didn’t quite line up with the datasheet, these matches are too close to have been a coincidence. I’m now convinced there is a SSD1305 (or very closely related derivative) controller inside this OLED module.

Three more commands round out the end of the startup sequence:

  • 0xB0: Set Page Start Address to 0.
  • 0x00: Set Lower Column Start address to 0.
  • 0x10: Set Higher Column Start address to 0.

After these are sent, the Command/Data line was raised signifying data transmission. A large number of zeros followed, then the C/D line was lowered in for another trio of commands:

  • 0xB1: Set Page Start Address to 1.
  • 0x00: Set Lower Column Start address to 0.
  • 0x10: Set Higher Column Start address to 0.

Followed by another large chunk of zeroes, and this repeated for all eight pages of memory. Documentation section 8.7 Graphic Display Data RAM (GDDRAM) gave a size of 132 x 64 bits divided into eight pages. By that math, there should be 132 bytes in each block of zeros, but I’m not going to count that by hand. There’s probably a way to count inside Saleae Logic software, but I went with a low-tech approach:

  1. Zoom into the trace so one large block of zeros span majority of my computer monitor.
  2. Using a ruler, I measured the physical width on screen of the first eight decoded bytes of 0x00: they are 33mm wide.
  3. 132 bytes / 8 bytes = 16. So if there are 132 bytes in the block of zeros, they should be (16 * 33mm =) 528mm wide.
  4. I measured the entire block, 525mm wide. Close enough!

Having learned this information about initial startup including clearing the screen memory, I can better interpret the data captured by the logic analyzer during my other test activities.

Second Try with FormLabs Form 1+ Display Board Signals

One lasting memory I have from the movie Apollo 13 is the line “is this an instrumentation problem or are we looking at real power loss?” When an instrument tells us something is wrong, it’s possible the problem is in the instrument and not in the system it is measuring. I thought of this when I looked over the initial set of logic analyzer traces of data sent to an OLED display module. The traces superficially resembled SPI, but with many traits inconsistent with SPI. Before I dive into a rabbit hole of trying to figure out strange data, I wanted to make sure it isn’t an instrumentation problem.

The first thought was sampling rate. I gathered information for 8 channels because I had an 8-channel logic analyzer. But there’s a tradeoff. Sampling frequency drops as number of channels go up. For the base model Saleae Logic 8 that I have, polling all 8 channels drops it down to 25MS/s (25 million samples per second.) This might not be fast enough, because SPI peripherals could go all the way up to a clock rate of 50MHz. My first round of probing found only three wires with interesting activity, so dropping sampling down to 3 channels let me increase sampling rate to 100MS/s. Which is the minimum requirement to capture a 50MHz signal, but I doubt this OLED is running that fast. If this is a SSD1305 controller, its datasheet (Table 13-4 Serial Interface Timing Characteristics) lists a clock cycle time minimum of 250ns which translates to a maximum clock speed of 4 MHz. I figured even if it isn’t a SSD1305, it likely operates at similar speeds.

The next step was to redo all physical connections. I disconnected all eight probes and reconnected to reseat just the three channels I care about. I switched to a different USB cable for the Saleae, and I plugged it into a different computer that had two advantages: (1) it had a faster processor, and (2) I could connect to an onboard USB port. (I didn’t need a USB hub.)

This second set of traces look more like the SPI signal I expected, so my problem was indeed instrumentation. But the white line (channel 0, display input pin 3) is still clearly not an SPI chip select signal, as data transmission occurs both at high and low levels. What might it be? Looking into the SSD1305 datasheet, I saw its SPI mode required an extra pin labeled D/C#. This is the Data/Command control pin telling the OLED how to interpret incoming SPI traffic. If this line is low, SPI traffic will be interpreted as commands. If this line is high, SPI traffic will be interpreted as data. This could explain what I see, but for final confirmation I will examine the data to see if it’s consistent with SSD1305 communication.

First Look at FormLabs Form 1+ Display Board Signals

I’m working to understand the OLED dot matrix display from a broken FormLabs Form 1+ laser resin printer. It is hosted on a FormLabs custom circuit board and, after tracing through copper traces of that board, I have a candidate list of five wires for further investigation. When I went to attach my Saleae logic analyzer, I decided to attach probes to all eight unknown wires. (Out of ten wires total and I have identified two: the ground and 3.3VDC Vcc wires.) It wasn’t much extra effort and I was curious if anything was going on. I then captured traces for four activities:

  1. Power-on: when I plugged the 24VDC power supply into the printer.
  2. Startup: when I pressed the front panel button to start its logo animation, ending at the “lid is open” warning.
  3. Steady: Several seconds when the display stays at “lid is open” warning, with no updates.
  4. Ready/Open/Ready: Using a magnet, I toggled the display from “ready” to “cover open” and back to “ready” again.

Trace for “Steady” showed no activity at all. I had expected the system to refresh the display periodically regardless of update activity, but I just captured five seconds of silence. This is quite a contrast from the super chatty display from an AT&T CL84209 handset where I had 2569 messages within 10 seconds! Here I have nothing. Well, at least that was out of the way.

Trace for “Power-on” and “Startup” was interesting because it captured activity on two of the three wires that were unused. One looked like clock and another looked like data, so I asked Saleae Logic to treat them as I2C. They came back as valid I2C messages.

write to 0x48 ack data: 0x01 0x00 
write to 0x48 ack data: 0x00

Hypothesis: printer mainboard has an I2C peripheral bus and it’s been routed all the way to the OLED display module circuit board. I2C is not used by this particular display module, but the design gives FormLabs an option to switch to an I2C display module without changing the rest of printer hardware. In the meantime, a logic analyzer connected to the display module would see traffic on the I2C bus. By this hypothesis, such traffic is intended for other components instead of this display, so I’ll ignore it until/unless I discover a reason to revisit. [UPDATE: I found a NXP LM75B on the mainboard, an I2C temperature sensor that could answer to address 0x48.]

Back to the five wires of interest: three wires showed activity correlating with screen updates. I didn’t see any activity independent of screen update, so these wires might be a dedicated peripheral data line. If it is a peripheral bus, every other peripheral on the bus stayed quiet during my test set of activities. At first glance I thought this was SPI, but a closer look revealed behavior inconsistent with SPI.

  • Mainboard cable pin 3 — connected to logic analyzer channel 0 (white line) — showed infrequent level changes near the start of every activity. A good candidate for SPI “Chip Select” or Enable, except data transfers seem to happen both when it is low and when it is high. Which shouldn’t happen if it is indeed Enable.
  • Mainboard cable pin 4 — connected to logic analyzer channel 4 (yellow line) — shows regular level changes during every activity. A good candidate for SPI Clock, except the candidate data line changes within each “clock” pulse” which shouldn’t happen if it is indeed clock.
  • Mainboard cable pin 6 — connected to logic analyzer channel 5 (green line) — shows level changes at irregular bursts. A good candidate for SPI Data, except it pulses out of sync with “clock”.

If this is SPI, why does it look weird? If this is not SPI, what is it? I’ll have to check over my setup and try again.

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 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.

Successful Arduino Test of LC75853N Control

Using a Saleae Logic 8 Analyzer, I’ve examined the communication protocol between the mainboard and faceplate of a car tape deck. These signals match expectations of a Sanyo LC75853N LCD controller which uses Sanyo’s proprietary CCB (Computer Control Bus) protocol. CCB has some resemblance to SPI and I2C but is neither, though close enough to SPI for me to use the SPI analyzer mode on a Saleae analyzer.

But “close enough” won’t be good enough for the next step: take an Arduino Nano and write code to talk to the LCD controller via CCB, copying the data waveform behavior I saw as closely as I can. Arduino has a library for SPI that assumes control of the enable pin, which has different behavior under CCB so that would not work here. I investigated using the shiftIn() and shiftOut() routines, which is part of the standard Arduino library. They are software implementations of a clocked serial data transfer routine, but unfortunately their clock signal behavior is different from what I saw of CCB under the logic analyzer. (Active-low vs. active-high.) In order to emulate behavior of the tape deck mainboard, I would have to write my own software implementation of CCB serial data transfer.

On the hardware side, I could no longer avoid soldering to small surface-mount connector pins on the back of the faceplate. I started simple by soldering the four data communication wires: LCD-DO, LCD-DI, LCD-CLK, and LCD-CE. Probing the circuit board with my meter, the only alternative soldering points were directly to the LC75853N, and those pins are even smaller. However, I found alternatives for ACC5V and GND: those were directly connected to the volume control potentiometer, which has nice big through-hole pins for me to solder to. I soldered these wires to a small prototype board with header pins, which then plugged into a breadboard alongside my Arduino Nano.

As a “Hello World” for CCB, I wrote code to replicate the control signals as closely as I could. I won’t try to replicate the exact timing of every pulse captured by my logic analyzer because (1) Arduino doesn’t make that level of control easy and (2) the CBB spec has no explicit requirement for precise timing anyway. However, I aim to make sure relationship between every clock, data, and enable pin high/low transition is preserved. I can verify this by capturing my Arduino output and compared the output to what I captured from the tape deck mainboard, look for where they are different, and fix differences over several iterations. Finally, I was satisfied the data waveforms look the same (minus the timing caveat above) and connected the faceplate.

These are almost the same LCD segments that are visible when I captured the data communication between mainboard and faceplate.

The only difference I see is “ST” in the upper right, which lights up when the FM tuner has a good enough signal to obtain stereo audio. Since this tape deck didn’t have an antenna attached, “ST” blinks on and off. Apparently, I had taken this picture when “ST” was on, and the recorded control signal I played back on an Arduino was when it was off. This is close enough to call my first test a success.

The other visible difference was the backlight: illuminated when I captured the data control message, but dark when I played it back. I had hoped the backlight was under LC75853N control somehow, but it looks like those LEDs are actually separately controlled.

Source code for this investigation is publicly available on GitHub.

Logic Analyzer on Sanyo LC75853N Key Scan Input

I’m learning to understand control communications for a Sanyo LC75853N, which drives the LCD in the stock tape deck head unit of a 1998 Toyota Camry LE. My goal is to control the faceplate independent of the rest of the head unit, which means learning about Sanyo’s proprietary CCB (computer control bus) protocol. CCB resembles some parts of SPI and some parts of I2C but is neither of them. I was primarily focused on the LCD output, but in setting up my logic analyzer to look at LCD output control, I also have everything to examine keypress input.

Here is the Saleae logic analyzer output for the chip reporting that the volume knob was pressed, which is designed as the on/off toggle for this tape deck. The controller toggled LCD-CLK 32 times during this data transmission. Datasheet says the first 30 bits represent state for each of the scanned keys, followed by one bit representing sleep state, and one bit of padding whose state is irrelevant. Here the 0x08 represents the single scanned key that corresponds to the knob being pressed.

Before that happened, though, were several events of note. When a CCB peripheral wants to report data to the controller, it pulls the DO line low. (Spec sheet says DO has a 1k – 10k pull-up resistor.) It then waits for the controller to emit a peripheral address and then raise CE.

  • If the controller emitted the peripheral’s address, then it can start using DO to report bits upon each CLK pulse.
  • If the controller emitted another peripheral’s address, then it releases DO (letting it be pulled high again) and wait until CE is low, at which point it pulls DO low again.

When I read this part of the spec, I was confused at how the LC75853N would know whether to transmit or to receive when its address was called. Eventually I found the answer: it has two different addresses! Address 0x42 receives LCD data from the controller, and address 0x43 reports key scan data to the controller. I had missed the difference when I looked at the logic analyzer output earlier, 0x42 looked a lot like 0x43 when I was skimming through output. But it’s plainly there once I was paying attention. Address is transmitted on DI from the least significant bit first: 1100 0010.

Counting buttons on this faceplate, I don’t think it wired up all 30 possible key scans. But aside from the power button (pressing volume knob) logged here, I don’t know what they are. I might map them once I can perform CCB communication to read these key press reports. And if I fail to do that… I guess I wouldn’t care anyway. Onward to the Arduino!

Logic Analyzer on Sanyo LC75853N LCD Control

My Saleae Logic 8 analyzer is now tapped into communication between the mainboard and face plate of a tape deck. A quick examination of the captured data looks reasonable, now it is time to dig into the data, compare against the LC75853 datasheet, and understand what is going on with LCD output control.

Within the ~3/4 second of the faceplate getting power, this sequence is transmitted repeatedly approximately once every 5 milliseconds. Zooming in, I see they are three consecutive CCB transmissions to address 0x42:


Guided by the datasheet, I interpret them as:

  • Most of the 0x00 bits indicate the corresponding LCD segment is to be turned off.
  • Some of the 0x00 bits in the second and third set are fixed data.
  • In the first set, the two bits in 0xC0 maps to S0 and S1, which controls sleep mode. S0 =1 and S1=1 is one of three available sleep modes.
  • Also in the first set, the single bit in 0x10 translates to SC, the segment on/off control data. SC=1 means display state is off.
  • For the second and third set, they each have a single bit that appear to be fixed within the section labeled as DD (direction data) and their values written into the timing diagram.

After that initial ~3/4 of a second, the pattern makes a minor change and repeats less frequently: once every 50 milliseconds:


The two bits associated with sleep mode are now zeroes. Sleep flags S0=0 and S1=0 means “Normal operation”, so the chip is awake. SC is still 1, though, so LCD is still off.

Then I pressed the power button. This triggered a set of events (probably related to key scan reporting) that I will investigate later. Right now, I am focused on how the LCD control message changed to display “FM 1 CH 1 87.9”


The end of first set changed from 0x10 to 0x00. This cleared the SC flag, so now LCD is on. There are other control bits in the first set of bytes and they are all zero.

  • K0 and K1 set to zero means none of the key scan pins were reassigned to segment control duty, so the chip retains ability to scan up to 30 keys.
  • P0 and P1 set to zero means none of the segment pins were reassigned to digital output duty, so the chip can control a maximum of 120 segments.
  • DR set to zero means 1/3 bias drive voltage.

The display backlight is also illuminated now, but which of these bits are responsible? For now, that is still an unknown. Until I get more information otherwise, I’ll proceed on the assumption that all remaining bits are LCD segment control.

To get more information, I pressed the “Mode” knob changing the display to “BAS 0” and its control message to this:


That’s a lot of changes!

For a smaller delta, I turned the knob one step so it now shows “BAS 1”.


This is a more manageable set of changes. A single step changes “0” to “1”, which on a 7-segment display means turning off four segments. A change from 0xC0 to 0x80 turns off one bit, and from 0x0D to 0x00 turns off three bits. This matches expectation of deactivating four segments.

I didn’t take pictures for the next experiment: While in FM radio mode, I press the tuner up button once. This moved it off the first preset so “CH 1” disappeared and now the display shows “FM 1 88.1”:


Then I pressed it again, so it now displays “FM 1 88.3”:


All changes are in the first part of the control message. Changing from 0x10 to 0xD0 turns on two bits, 0xD1 to 0xD9 turns on one bit. These added up to the three activated segments, matching expectation of going from a “1” to “3” on a seven-segment numeric display.

These observations give me confidence that we are indeed looking at control bits corresponding to individual segments on screen. Not enough information to map out all the segments to their corresponding bits, but I am postponing that task until I have individual control through code. It would be much easier to map out segments when they are under my control. And if I fail to obtain control, I wouldn’t care about the segment map anyway.

With some confidence I understand the output side, now to look at the input side.

Sanyo CCB Close Enough to SPI For Logic Analyzer

I’m examining the control signals for a Sanyo LC75853N LCD driver chip, which uses a Sanyo proprietary protocol they call CCB. (Computer Control Bus.) It’s popular enough that I could find CCB reference material online, but it’s not popular enough to be natively understood by Saleae’s Logic Analyzer software. Beyond Saleae’s set is a list of Community Shared Analyzers but Sanyo CCB didn’t make the cut there, either. Those additional analyzers were written using Saleae’s Protocol Analyzer SDK so there is the option to write one for CCB. For the purpose of initial experimentation, though, their default SPI analyzer is close enough.

Before we even try using the SPI analyzer, we can look at the raw data. CCB transmits the peripheral address before raising CE. Here I can see 0x42 hexadecimal, or 0b01000010 binary. (The white binary numbers were not part of Saleae software, I drew it in afterwards.) In an unfortunate bit of coincidence, this binary value is symmetric so it alone couldn’t tell us if CCB transfer least-significant bit first or most-significant-bit first. According to spec, it is least-significant-bit first. Seeing this gave me the confidence I’ve wired up everything correctly for further probing.

The clock pulses measured out to be in the ballpark of 400kHz, which I can probably work with. But more importantly, I was relieved to see that the clock pulse widths varied somewhat between transmitted bits. This is encouraging because it meant the protocol is graceful under irregular clock pulses, making it more likely I can successfully communicate using CCB in software. Which is great because I don’t have dedicated CCB communication peripheral hardware.

The next step was to activate SPI analyzer with the following parameters. The biggest difference between CCB and SPI is the behavior of CE line, and thankfully Saleae’s SPI analyzer can be configured to ignore CE. (“Enable” set to “None”.) I set the SPI analyzer options to the following values to decode all the values regardless of CE status:

SPI Analyzer OptionValue
Significant BitLeast Significant Bit First
Bits per Transfer8 Bits per Transfer
Clock StateClock is High when inactive
Clock PhaseData is Valid on Clock Trailing Edge
Enable Line(Doesn’t matter when “Enable” is “None”)

Now the software can decode data for us. This time, the decoded values 0x42, etc. in this image was drawn by the software.

This was the start of the very first data transmission after I applied power to the tape deck. Which is why CLK started as low even though it is normally high when inactivity. When Enable is set to None, I see all the data regardless of CE status.

First question to answer: the B in CCB is “Bus”. How many devices are on this bus? Taking advantage of the difference between CCB and SPI, I can tell the SPI analyzer to decode just the CCB address by saying CE is Active Low:

SPI Analyzer OptionValue
Enable LineEnable is Active Low

The decoded values on LCD-DI were all 0x42, which tells me the LCD control chip is the only peripheral on this bus, which makes things simpler. I won’t have to worry about reading data intended for the wrong device. And once I decided I didn’t have to worry about different addresses anymore, I can switch the SPI Analyzer over to Active High CE. This will cause the analyzer to ignore addresses (since I expect them to all be 0x42) transmitted while CE is low and decode just the data.

SPI Analyzer OptionValue
Enable LineEnable is Active High

Within the ~3/4 second of the faceplate getting power, something is transmitted repeatedly approximately once every 5 milliseconds. Zooming in, I see they are three consecutive CCB transmissions to address 0x42:


Next: compare this captured data to the LC75853N datasheet to see if they make sense.