Quadrature Decode MX340 Standby

I’ve got an Arduino Nano connected to the internals of a Canon Pixma MX340 multi-function inkjet, specifically the quadrature encoder reporting on rotational motion of the paper feed motor gear assembly. After capturing the power-up sequence, I pressed the button again to capture the power-down standby sequence.

This standby sequence took twice as long as the power-up sequence, 18 seconds versus 9. Almost 11 seconds were spent rolling backwards relative to printing feed direction, traversing about 248,000 encoder counts. Eight times more than the backwards roll during power-up, which traversed ~31,000 encoder counts. That long rearward roll was followed by a forward roll for ~25,000 encoder counts.

During this roll, there were no paper motion and the print carriage sits in the parked position. What effect could this roll have? At the moment, my only guess is something preparing the ink cartridges to sit for a while. It would make sense for the power-up sequence to prepare the ink cartridges for use, and then for the standby sequence to prepare them for storage. Trying to figure out the details of this preparatory work is on the to-do list for later this teardown.

Besides that very long motion, there are several small movements back and forth. Looking at the data, I can see the times and duration differ between them, but each of those smaller movements traversed 1800 encoder counts. Reviewing the startup sequence, most of those small movements were also 1800 encoder counts. This is interesting. I will have to determine how many degrees of rotation corresponds to 1800 encoder counts, then look for a mechanism that corresponds to such a rotation during later disassembly. This little detail should be easier to understand than the actual printing process.


This teardown ran far longer than I originally thought it would. Click here to rewind back to where this adventure started.

Captured CSV and Excel worksheets are included in the companion GitHub repository.

Quadrature Decode MX340 Startup

Whenever I turn on my Canon Pixma MX340 multi-function inkjet, I immediately hear motors whirring and I had always been curious: I haven’t even tried to print anything yet. What is it doing? Now that I’ve retired the machine and slowly taking it apart, I want to get some information hopefully leading to some answers. I have attached an Arduino Nano to two photo interrupter sensors and the quadrature encoder reporting position of the paper feed motor gearbox assembly. The Nano watches those sensors and report on their status every 10 milliseconds to a serial port as a list of comma-separated values. Thanks to the “everything is a file” nature of Linux, I could run “cat /dev/ttyUSB0 > capture.csv” and I had a file I could import into Excel.

Here is the startup sequence that has long intrigued me, in the form of an Excel scatter chart. Horizontal axis is the time stamp. This graph covered about 9 seconds with a data point every 10ms, resulting in dots densely packed into a line. Vertical axis is the encoder counter value, with the encoder as a gray line maxing out at almost 32000 counts. (Getting a number for count per rotation is on the to-do list.)

The two photo interrupter sensors did not change state throughout startup.

Due to the way I wired up the quadrature decoder, positive values on on this counter correspond to rotating the paper feed backwards. I usually think of printing as advancing forwards on the Y-axis, so this is reversed from that I would have chosen if I had known. But now that it is connected, I don’t care enough to fix this minor issue. If it starts causing problems I can flip the two wires and reverse their direction.

The fact this plot appears to be a series of straight lines tells me the motor does not vary its speed (significantly) once it is in motion.

Zooming on the initial set of movements, I see an acceleration curve before it reaches that speed, and there’s a deceleration curve at the end of each movement. My eyes did not perceive acceleration and deceleration, so this exercise is already telling me things I couldn’t pick up with my own eyes. I can also see the first two movements here are both backwards, but the second movement has a gentler slope reflecting a slower speed. Another difference I could not see with eyeballs.

Zooming in on a different part of the graph, this is the point of maximum backwards travel. On the big graph I saw a small bump on either side of the peak, now I see the motor decelerated to a stop before immediately accelerating back up in the same direction, only to decelerate to a stop again before going in the opposite direction. It then slowed down again to a stop before resuming in the same direction at a different speed. Both of these pauses are extremely short, only a few milliseconds if that.

What did that motion accomplish? In fact, what did any of these motions accomplish? I don’t have answers yet, but I’m excited I’m peeling back at least the first layer of this onion. Next: power down standby sequence.


This teardown ran far longer than I originally thought it would. Click here to rewind back to where this adventure started.

Captured CSV and Excel worksheets are included in the companion GitHub repository.

Quadrature Decoding With Periodic Output

I had forgotten (then was reminded) that I already had Arduino Nano-based quadrature decoding capability on hand. After a quick check to verify it should be fast enough, I connected it up to my retired Canon MX340 multi-function inkjet to see what I can glean from its paper feed motor assembly. The initial test used the basic example included with Paul Stoffregen’s quadrature decoder library. It polls the encoder count in a tight loop and, whenever it sees a change, it prints the new value to serial port. I started it, turned on the MX340, and got a stream of numbers on Arduino IDE’s Serial Monitor. A good start.

As the motor started spinning, the change in encoder values came fast and furious. A backlog quickly developed, which resulted in data display lagging behind actual motor movement. This was easily resolved by kicking up the serial transmission baud rate above the slow-and-reliable 9600 baud. Looking on the drop-down list of baud rates supported by Arduino IDE serial monitor, I chose 250000 because it’s easier for me to remember right now as it’s what this MX340 itself uses.

But that still left a lot of data flying by as the motor spun. The next change to further reduce output was to change from “every time encoder changes” to “once every 10 milliseconds”. This seems to have reduced the output to a manageable flow, but I didn’t know what kind of processing to try next. Ideally I would take advantage of characteristics of the system to filter interesting data from extraneous noise, but I don’t know its characteristics yet.

So… I will learn its characteristics! To meet my objectives for this decoder project, I connected two more Arduino Nano digital input wires to photo interrupter sensors in this system. One likely reports paper status, and the other watching something inside a gearbox I plan to dissect later. Both their states are polled at the same 10ms interval and output to serial port. I also changed the serial output to be a set of comma-separated values. After my earlier success using Microsoft Excel to make sense of raw captured data, I will use it again now to get a basic outline of what this motor system is doing.


This teardown ran far longer than I originally thought it would. Click here to rewind back to where this adventure started.

Arduino sketches are included in the companion GitHub repository.

Quadrature Decoding with Arduino

I want to understand the internal workings of a Canon Pixma MX340 multi-function inkjet. Right now my focus is on its paper feed motor assembly, and I want to record data reported by a quadrature rotation encoder inside that assembly. I want to track behavior over several seconds, possibly a minute of two, which gets a little unwieldy with a logic analyzer timeline interface. So I thought I should create a tool tailored to my project and I found a promising lead using an ESP32’s pulse counter (PCNT) peripheral.

As as I started preparing for the project, thinking through and writing down what I’d need to do, a lot of details felt very familiar in a “wait… I’ve done this before” way. I had forgotten I’ve played with quadrature encoders before! A search for “quadrature” on my project notebook (this blog site) found entries on reading the knob on a Toyota audio head unit, an inexpensive knob from Amazon, and investigative detour during Honda audio head unit adventures.

Following my earlier footsteps would be an easier way to go, because the Arduino IDE and Paul Stoffregen’s quadrature decoder library are already installed on my machine. But this will be the first time I apply it to something turned by a motor instead of by a human hand. Is it fast enough to keep up? Decoder library documentation says 100-127kHz sampling rate is possible on a Teensy 3, which was the library’s original target hardware. Running on an ATmega328 would be slower.

Aside: I found this Gammon forum thread listing technical detail on ATmega328 interrupt service routines, which laid out work just for ISR overhead that would take 5.125us before any ISR code actually runs. This puts a hard upper bound of ~200 kHz on response rate of an ISR that does nothing.

In the spirit of “try the easy thing first” I’ll start with ATmega328 Arduino. If it proves too slow, I have a Teensy LC somewhere, and I definitely have ESP8266 boards. In the unlikely case they all fail to meet my need, I can resume my examination of ESP32’s pulse counter (PCNT) peripheral.


This teardown ran far longer than I originally thought it would. Click here to rewind back to where this adventure started.

Window Shopping ESP32 Pulse Counter (PCNT)

To help me understand internal workings of a Canon Pixma MX340 multi-function inkjet, I would like some signal analysis tools. Specifically for recording data coming from a quadrature encoder attached to the paper feed motor gear train. After I found out Saleae Logic could not do this, I started reading about sigrok. Thanks to a documented protocol, sigrok could run with not-officially-supported data acquisition hardware such as an ATmega328 Arduino or ESP32.

It started to look like too much of a distraction, though, so I refocused on my specific problem at hand: I just need quadrature decoding. And for that specific purpose, ESP32 has a hardware peripheral for the job. Pulse Counter (PCNT) can certainly do what its name says and count pulses on a single input, but it can be more general than that. Espressif designers had added provision for PCNT to act in response to multiple inputs and configure their interaction. One specific configuration, demonstrated in an official example, turns PCNT into a quadrature decoder.

For my purpose I need something that can keep up with quadrature phase changes of this encoder, roughly on the order of 10-20 kHz. I found a forum thread ESP32 pulse counter speed (max frequency) which says PCNT can keep up with signals up to 40MHz. That’s plenty fast for my needs!

In fact, that might be too fast. At that high rate of sensitivity, small changes — like the little dip visible in one phase when the other phase is pulled to ground — may register with PCNT and that would spell trouble. Fortunately Espressif engineers thought of that too: PCNT includes an optional glitch filter to reject signals changing outside of its configured speed range. This may be an useful tool in my toolbox if I see spurious data.

ESP32 PCNT looks like a promising approach for me to build the tool I want. I would have to install ESP-IDF (probably in VSCode Extension form) before I could compile the official sample and start modifying it for my needs. Seems pretty easy on paper, but then I realized I had an even easier option I should try first.


This teardown ran far longer than I originally thought it would. Click here to rewind back to where this adventure started.

Notes on Arduino and sigrok

I want to better understand the motions made by motors in a Canon Pixma MX340 multi-function inkjet, and thought I might be able to gain insight by recording data output by a quadrature encoder attached to its paper feed motor. Finding my Saleae Logic analyzer’s software isn’t up for the job, I searched for alternatives and found sigrok. The open source signal analysis software.

In addition to quadrature decoding capability, sigrok has another advantage: lack of hardware tie-in. I looked for names I recognized on the list of supported hardware and found a few. Curiously the list continues onward to “Work in progress/planned” hardware, and that list included an entry for Arduino. Wow, really? A humble hobbyist-accessible microcontroller can act as data acquisition hardware for sigrok?

Reading that page, I believe the answer is “kinda… well, actually… no.” The basic idea is that (1) sigrok supports any signal acquisition hardware that can report data via SUMP protocol and (2) people hare written Arduino sketches that tell the ATmega328 chip to sample its IO pins and report their state that way. Unfortunately, based on information on that page, the situation is a mess. Sounds like an Arduino could work for certain scenarios but not others. A user would need to understand implementation details in order to know its limitations, and would need to understand ATmega328 to know workarounds. It’s not a plug-and-play solution and the Wiki page has not been edited since September 2020. I don’t think this will ever graduate to “supported” status.

Still, I was glad to see this work targeted the ATmega328. The original chip at the heart of original Arduino boards. I had half expected it to require a much newer processor with Arduino core support. Speaking of which, I searched for ESP32 + SUMP and found the esp32_sigrok project by GitHub user Ebiroll, along with a corresponding thread on ESP32 forums. This project also has known problems and the last commit was over three years ago.

Based on these findings, I got the distinct feeling building signal acquisition system from general-purpose hardware is really hard. Fortunately, for my purpose today I do not need general purpose capability. I can focus on just quadrature decoding and it turns out ESP32 was designed with a peripheral ideally suited for the task: pulse counter (PCNT).


This teardown ran far longer than I originally thought it would. Click here to rewind back to where this adventure started.

Logic Analyzer Quadrature Decoder

I would like to record internal behavior of a Canon Pixma MX340 multi-function inkjet, specifically the paper feed roller position encoder and two photo interrupter sensors. The sensors are binary on/off affairs that change relatively infrequently, so they should not be a challenge. The interesting element would be the quadrature encoder, reporting how far and how fast the paper feed roller motor is turning that shaft.

A rough calculation told me I need something that can sample two encoder outputs at a rate of at least 20 kHz. My Saleae Logic 8 hardware is advertised to sample at speeds up to 100 MHz. Maximum sampling rate drops if it needs to cover more channels, but 2 channels should be good for 50MHz for plenty of headroom. Unfortunately Saleae’s Logic 2 software does not perform quadrature decoding. I don’t know why I had assumed it would be part of the analysis toolkit, but I did, and so I was surprised to find it absent. A quick online search confirmed quadrature decoding is still on their “user requested feature” list for some undefined time in the future. And as I established earlier, Saleae’s analyzer extension framework doesn’t support writing my own decoder across multiple channels.

During this search for quadrature decoding support in logic analyzers, I came across mentions it was a capability in sigrok, forwarding to a terse page in sigrok documentation. I understand sigrok to be (very) roughly analogous to Saleae’s Logic 2 software, with support for lots of different hardware performing a wide variety of tasks. Perusing sigrok hardware support page I saw two names I recognized. Bus Pirate is listed as supported, but very limited due to its basic hardware. GreatFET One is also on the list, and it has a much more robust list of capabilities. I consider this a good reason to add GreatFET One to my investigation list for future purchase. If quadrature decoding analysis becomes a recurring need, it would be enough to motivate me to switch from Saleae’s proprietary solution to an open-source alternative.

Another advantage of sigrok’s open source nature is that, unlike Saleae’s proprietary solution, the software is not tied to the hardware. Not even officially supported hardware. It has support for documented interface protocols like SUMP, so any hardware can theoretically act as signal acquisition hardware for sigrok. However, this is apparently easier said than done.


This teardown ran far longer than I originally thought it would. Click here to rewind back to where this adventure started.

Canon Pixma MX340 Paper Feed Motion Recording Objectives

I have my old Canon Pixma MX340 multi-function inkjet in several pieces, but still linked with wires in running condition. I know that as I take this thing further apart, I will eventually reach a point where it no longer runs. Before that happens I would like to record motion of the paper feed motor, which actuates multiple different mechanisms (not just feeding paper) via a series of mechanical systems I want to understand. For this exercise, I want to gather enough data so I could plot behavior of several sensors on the same timeline.

The first and most obvious data source is the quadrature encoder attached to one of the shafts in this system.

On my first pass, I looked at encoder waveform during system startup under an oscilloscope, which told me it is a device operating at 3.3V DC logic level. During its startup sequence, quadrature state changes can occur in less than 100 microseconds. This is probably close to the maximum speed of this system, seeing how the signal took roughly 20 microseconds just to stabilize.

As a ballpark guess, this tells me I want to sample at least once every 50 microseconds (20 kHz sampling rate) just to ensure I don’t miss any pulses. And obviously if I want to calculate rotational speed from time between pulses, I would need a far higher sampling rate. I don’t think that’ll be necessary, though, given I didn’t notice many speed changes in this system. It’s probably good enough (and much easier) to calculate speed by counting number of pulses within a much longer time period.

On the same timeline, I want to plot the state of the photo interrupter sensor under this small circuit board. It sits above a geared mechanism driven by the same motor, and one of the gears has a partial disc that blocks this beam in certain positions. I’m sure it provides feedback into operating… whatever that is.

Less important is another photo interrupter sensor sharing the same wiring harness as above. I’m pretty sure it tells the printer when a sheet of paper has been successfully fed into the print path, but I thought it’s worth getting confirmation. The incremental work to add this data point shouldn’t be much, but I’m willing to abandon it if complications arise.

The stretch goal is to also include print carriage horizontal motion encoder into the same data stream. If successful, it would give me full information on print engine motion. However, this encoder is buried within the print carriage behind ink cartridge interfaces. I haven’t figured out how to tap into its signal yet. Getting to that circuit board may damage something beyond repair, so it is definitely not part of the first draft plan.

I think that’s a fair outline of what I want to accomplish. The obvious next question is: how might I accomplish this?


This teardown ran far longer than I originally thought it would. Click here to rewind back to where this adventure started.

Canon Pixma MX340 Paper Feed Motor Round 2

I’m slowly taking apart my retired Canon Pixma MX340 multi-function inkjet, and I’ve wrapped up my second pass examination of control panel electronics. Now I will switch attention to its paper feed motor assembly. First of all, calling it the paper feed motor is an understatement, I just don’t have a better name. Looking at gears and shafts on my first pass, I knew it is linked to several other mechanisms beyond feeding paper. I got a close look at how it can open the paper tray door at the front of the machine, but everything else is still buried out of sight at the moment.

They are still buried because I paused physical disassembly in order to ensure the machine remain in a running state. This was very useful as I probed electronic control and communication signals while it was running live. I gained far more insight this way than I could have if I couldn’t run the machine. I learned a lot about the document feeder, the scanner, and how the main board communicates with the control panel. After all that, though, I’m close to exhausting everything within my current skill level to understand. But I think there’s one or two more things I can do before I pick up the screwdriver again.

I want to record paper feed motor mechanism’s behavior. I can see with my eyes it turn forward and backwards multiple times at different speeds. There’s a sequence that runs upon power up, another one when it goes into standby, and naturally there is much more when the machine prepares to actually put ink on paper. I want to quantify that motion more precisely than what I can see with my own eyes. This way, in the likely event the machine stops running as a result of future disassembly, I have something to reference. Correlating them with mechanisms that probably won’t run by the time I disassemble the machine far enough to get to them. I need to quantify exactly what I want to record, then go look for tools that can help me do it.


This teardown ran far longer than I originally thought it would. Click here to rewind back to where this adventure started.

Canon Pixma MX340 Control Panel Round 2 Summary

I’ve been poking around the control panel assembly of a Canon Pixma MX340 multi-function inkjet. An examination of data sent to its LCD module was the final piece of information I had expected to extract when I started “Control Panel Round 2. This post will summarize what I found, with links to specific posts for more detailed reference.

Electrical

The control panel is connected to the main board with a long flat cable with twelve wires. Five of those wires are related to power supply: One 5.5V DC feed a single LED, a 3.3V DC supply for everything else, and three ground wires. Four wires enable direct main board control of four control panel elements: two buttons [Power] and [Stop], and two LEDs [Power] and [Alarm]. That leaves three wires for communication with the NEC K13988 chip on the control panel. See connector pinout post.

The NEC K13988 chip controls the remaining 2 LEDs on the control panel, has a matrix of wires to scan through the remaining 27 buttons, and passes display data onward to the LCD screen. Details at NEC K13988 chip pinout post and LCD connector pinout post.

Communication

Three wires for communication between main board and NEC K13988 chip consists of a single wire for [Chip Enable] and two wires for bidirectional communication. The protocol is 250000-8-E-1 asynchronous serial for both wires.

  • Main board commands are always two-byte sequences, each command acknowledged by the NEC K13988 with a single byte 0x20.
  • Main board bulk data transfers are announced with 0x06 for first byte of the two-byte command and the second byte indicates length of data transfer in number of bytes. This has only been observed for updating LCD screen bitmap, with five transfers of 196 (0xC4) bytes. After 0x06 0xC4 is acknowledged by a 0x20, 196 bytes are sent and another 0x20 acknowledgement at its conclusion. This post decoded a single frame using Excel.
  • Main board commands with 0x04 as the first byte are LCD module commands. NEC K13988 will forward the second byte to the LCD.
  • Main board commands where 0x0E is the first byte are LED commands. NEC K13988 will update illumination of [In Use/Memory] and [WiFi] LEDs based on two of the bits in the second byte.

In addition to 0x20 acknowledgements, the NEC K13988 sends a single byte reporting button matrix status every ~9.2ms. 0x80 means no buttons are pressed. Any value other than 0x20 or 0x80 will be one of the button matrix status reporting scan code listed in this post.

Commands

Lacking any reference material on these components, I don’t know the full meaning of most of the commands decoded and recorded by my Saleae Logic 8 analyzer. Some were correlated with observable behavior and listed above, but the rest remain opaque unknowns.

List of posts that go into more detail on commands associated with each machine state, along with some notes on timing pauses seen in logic analyzer captures:

Future

Sometime after this MX340 is completely torn down, I may try to repurpose this control panel assembly for a future project. I will start by playing back all of the commands recorded above and see if the control panel responds as observed. If it doesn’t, then I have to go back to my Saleae Logic captures and try to replicate the pause timing between commands. If that still doesn’t work, I have failed to capture something important.

If it works, I can try to gain more insight into those opaque commands. I can omit a command and look for changes in system behavior, then repeat for each opaque command. Or I may decide such investigation is not worth the trouble, and just play back those commands as-is while I focus on other aspects of the project. We’ll see what the future holds.

Up Next

This concludes “Control Panel Round 2” and I’m ready to move on. My next target is the paper feed motor assembly.


This teardown ran far longer than I originally thought it would. Click here to rewind back to where this adventure started.

Canon Pixma MX340 Control Panel LCD Data Decoded

I am examining communication between NEC K13988 chip and LCD embedded controller on board the control panel of a Canon Pixma MX340 multi-function inkjet. There are five wires between them, and I have candidate roles for all five after running the printer and looking at their behavior with a Saleae Logic 8 analyzer. Armed with this information, I can configure the Saleae Logic SPI decoder module.

Selecting channel numbers for data, clock, and enable was straightforward. I had to go back and look at the waveform some more to determine the next few options: enable is low when active and there are 8 bits per transfer. Clock is also low when active, and data is valid on clock trailing edge. (With active low clock, trailing edge is the low-to-high transition.)

That left one unknown: does this system transfer most significant bit first, or least significant bit first? Saleae SPI decoder says most significant bit (MSB) first is standard, so I tried that first. I compared decoded data against what I saw before and saw no resemblance. I then flipped the decoder over to treat least significant bits (LSB) first, and jackpot! Decoded data matches byte-for-byte.

Looking at scenarios for system power-up, LCD screen update, and LCD sleep/wake, the pattern is clear. Every time the main board sent a two-byte command to the K13988 where the first byte is 0x04, the K13988 passes the second byte on to the LCD as a command. For LCD update bulk transfers, where the first byte is 0x06 and second byte is length of transfer, the command itself is not passed along but all bytes transferred afterwards are passed on to the LCD as data.

The question this raises is: do I have the correct setting? Perhaps the asynchronous serial communication between main board and K13988 was supposed to be decoded as MSB first, and the match here is merely a “two wrongs made a right” situation. I thought about it and decided the octal number pattern visible in button matrix scan code report wouldn’t exist if decoded as MSB first, so LSB first must be the correct setting.

LCD PinK13988 PinRoleNotes
128SPI Chip SelectActive Low
226Chip EnableActive High
327Command/DataLow = command
High = data

Valid on first clock trailing edge
(low-to-high transition)
49SPI ClockActive Low

8 bits per transfer
510SPI MOSI DataLeast significant bit first

Valid on every clock trailing edge
(low-to-high transition)

It’s great when observations from different parts of the system align! It gives me confidence my notes here aren’t entirely gibberish. As a practical matter I don’t expect to put this knowledge to use, since I don’t plan to extract this LCD screen from its control panel assembly. I can only imagine one scenario where it might make sense to do so: if I want to update this LCD at a higher frame rate, I can decouple it from the K13988 and hook it up to something else with higher SPI clock speed. I doubt that will happen. I’m more likely to use another display, especially if I have its datasheet to know how much higher I can run the SPI clock. I lack such technical information for this screen. I only have information on what I can characterize based on change in measurable behavior, and it’s time to wrap it up with a summary.


This teardown ran far longer than I originally thought it would. Click here to rewind back to where this adventure started.

Canon Pixma MX340 Control Panel LCD Pin Assignment

Tracing through the control panel circuit board of a Canon Pixma MX340 multi-function inkjet, I found five communication wires between its control chip and LCD screen. While I was soldering wires to tap into that data bus, I hypothesized what they might be. My best guess was four wires for SPI plus an analog voltage wire to control display contrast. For my initial captures, I set Saleae Logic to analog mode and ran through scenarios similar to what I used to capture data between main board and control panel. The analog voltage hypothesis was quickly disproved: these are all digital signals of either 3.3V DC or ground.

Setting my capture settings back to digital, I started over from the beginning. At the moment I plugged in its power cable, four out of five wires went high.

When I pressed the power button, the first detected activity was the fifth wire going high. This pin stayed high until I pressed the power button to drop the system into standby mode. From this I inferred LCD pin 2 (red wire, logic analyzer channel 2) is passing through the “Chip Enable” signal. One mystery down, four to go.

Here’s a single-byte command sent to the LCD telling it to go to sleep. LCD pin 4 (green wire, logic analyzer channel 5) looks like an active-low clock signal, pulsing eight times to send a single byte. LCD pin 1 (yellow wire, logic analyzer channel 4) was dropped low just before the clock started pulsing, and stayed low until after it was done. This behavior is consistent with SPI “Select” wire, notifying LCD to pay attention to incoming synchronous clock and data signals. Speaking of data, that would be LCD pin 5 (black wire, logic analyzer channel 0) that held each data bit at the clock signal’s low-to-high transition.

Where does that leave the LCD pin 3? (blue wire, logic analyzer channel 6) It almost behaves like a SPI Select, except it drops low at the same time as the first leading edge of the clock. Too late to serve as notification for LCD to get ready, so it must mean something else.

This is the capture timeline for a LCD update, and it showed a behavior difference between blue and yellow lines. The blue pulses divide this transfer into five sections, matching the number of bulk transfers sent by the main board. For each section, several bytes were transferred with blue held low, followed by many bytes with the blue held high. This means the blue line indicates whether the data being transferred is to be treated as a command (low) or as data (high).

With candidate roles for all five wires, I could use them to configure Saleae Logic’s SPI decoder and look over the bytes transferred.


This teardown ran far longer than I originally thought it would. Click here to rewind back to where this adventure started.

Canon Pixma MX340 Control Panel LCD Wires for Logic Analyzer

I’m taking apart my old Canon Pixma MX340 multi-function inkjet and the next step in this adventure is to tap into communication between the main chip (marked NEC K13988) and LCD screen (no markings found) of its control panel.

Earlier exploration of the circuit board indicated 5 of 24 pins on 1mm pitch LCD connector are wired to the K13988 indirectly via resistors and capacitors. I want to solder wires to connect those signals to my logic analyzer, and the easiest to solder features are five 330 ohm resistors much larger than other surrounding features. Out of habit I soldered the wire colors to match my oscilloscope channel colors, forgetting that I skipped the oscilloscope probing step and thus there would be no color mismatch concern here. Oops.

LCD PinWire colorSaleae channelK13988 pin
1Yellow428
2Red226
3Blue627
4Green59
5Black010

I have some idea of the kind of data that flows between that K13988 chip and system main board. Since this K13988 to LCD link sits downstream, I expect to see some subset of that data passed through. For me, the most interesting unknown is… why five wires? I’ve already traced out 3.3V power and ground to other pins on the connector, so they’re not involved in this set of five.

Something like asynchronous serial or I2C would need only two wires. SPI would need four wires, but that would still leave one explained wire. My best guess is the last wire controls LCD screen contrast, possibly via an analog voltage level. I recall seeing such a control scheme, designed so it’s trivial to implement a contrast adjustment knob by connecting a potentiometer to that wire. Those resistors and capacitors I see between K13988 and LCD are consistent with this idea, possibly implementing a filter to help turn a digital PWM signal into an analog voltage level.

The possibility of an analog signal was why I started my Saleae Logic session in analog mode.


This teardown ran far longer than I originally thought it would. Click here to rewind back to where this adventure started.

Canon Pixma MX340 Control Panel LCD Is Next Target

Using a Python script I wrote to match known patterns, I am now confident I have a good record of communication between main board and control panel of a Canon Pixma MX340 multi-function inkjet. My next focus is communication between control panel’s NEC K13988 chip and LCD screen.

I’ve established the LCD is a pixel-addressable display with a resolution of 196-ish pixels wide and 34 pixels tall. With just five wires between K13988 and LCD, that’s far too few to control all pixels directly, so those five wires must lead to an embedded controller on board the LCD itself. Something with a frame buffer who will scan through all the segment/common lines of the pixel array to refresh them.

Given the delicate mounting mechanism and fine-pitched surface mount connector, I don’t expect to extract this LCD from the control panel assembly. Any potential future repurpose project will use the entire control panel assembly and not just the LCD itself. If I have a project that wants a small LCD, I’m more likely to use something else. So my objective here is just for learning’s sake. See if I can gain any further insight into the recorded (but not fully understood) communication between main board and control panel by looking at the LCD which lies downstream.

When I started examining communication between main board and control panel, my first step was wiring things up to an oscilloscope. One of the reasons was because I didn’t know the voltage going over those wires and my oscilloscope can handle a far wider voltage range than the logic analyzer. Now that I know this board runs on 3.3V DC (with the lone exception of a LED powered by 5.5V DC) I’m more comfortable skipping the oscilloscope step and go straight to wiring up the board for my Saleae Logic 8.


This teardown ran far longer than I originally thought it would. Click here to rewind back to where this adventure started.

Canon Pixma MX340 Control Panel Filter App

I think I have a pretty good understanding of communication between main board and control panel of a Canon Pixma MX340 multi-function inkjet. To help me see if I’ve missed anything, I connected two serial adapters to listen to traffic in both directions, and wrote a Python script to match incoming data against patterns I’ve seen to date starting with the data burst to update what’s shown on its LCD screen.

Along with the code to recognize a LCD update, I also had code to print out any sequences it didn’t understand. I thought it was better to copy/paste that data and double-checking it against my earlier notes, eliminating the risk of data entry errors if I try to type them back in by hand. This presented a unique challenge: when does “a sequence” start and end? The most obvious answer seemed to be waiting for some set time period, but trying to find the perfect timeout value was doomed to fail. From logic analyzer traces, I knew there were pauses of up to several hundred milliseconds in these sequences, and some sequences follow each other quickly.

Another twist to the puzzle was the LED status update command, where I want to parse the parameter and check the bits corresponding to each LED instead of matching fixed values in a dictionary. This needs to be handled with a special case different from a dictionary lookup. The code could live alongside the code looking for a bulk transfer, but LED updates are buried inside several of these long sequences.

I decided the easiest thing to do was to break up long sequences like “startup” into multiple shorter patterns. So instead of a single line telling me it matched the startup sequence, I will get multiple lines “startup 1”, “startup 2”, etc. Not elegant, but sufficient for the quick-and-dirty nature of this project.

With that adaptation in place, I was able to set this script running and run through various scenarios on my MX340. Scan and copy a page, scan a document to PDF, try to send or receive a fax (which fails as I had no landline) and such. With every action I glance over to my console output. All communication traffic matched known patterns, and nothing new popped up. This gives me confidence I’ve mapped out all data traffic between main board and control panel, meeting the success criteria I set out for my data filter script project. It’s very rough, but it did the job, and that makes it version 1.0 (“good enough”) for this side quest and more than sufficient for me to move on.


Source code for this quick-and-dirty data parsing project is publicly available on GitHub.

This teardown ran far longer than I originally thought it would. Click here to rewind back to where this adventure started.

Canon Pixma MX340 Control Panel Command Sequence Lookup

I’m teasing apart the stream of data sent by the main board of a Canon Pixma MX340 multi-function inkjet to its control panel. I’ve separated out its 196-byte bulk data transfers from 2-byte command sequences. The bulk data transfers are LCD screen bitmap, so I could ignore that for now and focus on functional commands.

The main problem here is that I never managed to find a data sheet or useful reference material for the main chip on the control panel, marked as NEC K13988. So these command sequences are opaque bytes picked out by my Saleae logic analyzer. A few of these immediately changed machine behavior so I could make guesses on what they mean, but the rest are just a mystery.

I thought I had a huge challenge on my hands, trying to build a state machine to parse a language without knowing the vocabulary or syntax. After drawing a few diagrams on scratch paper, I noticed they all ended up as a straightforward pattern matching exercise. Well, it would be much easier to treat the problem that way, and I should always try that easy thing first.

Logically this would be a switch statement, but since I’m working in Python, I thought I would try to be a bit more clever using existing data structure infrastructure instead of writing my own. I thought a Python dictionary could do the job. I feed it a command sequence and ask if it’s one that I’ve already seen. The minor twist is that I build up my command sequence in a list as bytes arrive on the serial port, but a list is not valid data type to use for dictionary key because they need to be immutable data types.

The first workaround, then, is to convert a list into an immutable counterpart called a tuple in Python. This mostly worked, but the tuple-to-list conversion has a subtle special case for converting lists of tuples (each two-byte command sequence is a tuple) to a tuple of tuples when the original list has only a single entry. It looks like somewhere along the line, a tuple with a single entry of another tuple is collapsed into just a tuple. I don’t fully understand what’s going on but I was able to rig up a second workaround to make the dictionary lookup happen.

Once that was up and running, I could successfully look up the LCD screen update sequence and collapse that sequence of commands, including its 5 bulk data transfers, into a single line on my console output. This is a great start! Now I can proceed to fill in the rest.


Source code for this quick-and-dirty data parsing project is publicly available on GitHub.

This teardown ran far longer than I originally thought it would. Click here to rewind back to where this adventure started.

Canon Pixma MX340 Main Board Command Versus Bulk Transfer

I’m writing a simple (hopefully) program to parse data flowing between main board and control panel of a Canon Pixma MX340 multi-function inkjet. It will listen to traffic in both directions by constantly polling two serial ports for data availability and process data as they come in on whichever port. My previous code listening for control panel button scan code was easily adapted, now I need to figure out how to handle main board commands.

Based on what I’ve seen so far, the majority (by byte count) of main board traffic updates LCD screen bitmap, with each screen refresh consisting of five bulk transfers of 196 bytes. Remaining traffic consist of two byte sequences, including the bytes leading up to each bulk transfer. So before anything else, I need to read through those two-byte commands and recognize the bulk transfer command so I know to skip ahead 196 bytes. Otherwise I’d end up trying to parse screen image bitmap as commands and that won’t end well.

Right now I don’t plan to do anything with the LCD screen image bitmap data, they’ll just be discarded. My Python project is just a command line tool with no practical way to show an image anyway. The closest thing I can do is print out an array of asterisks/spaces 196 columns wide and 40 rows tall. Which may be an interesting exercise but not very practical. I don’t plan to render the LCD screen image bitmap data until I evolve to something more advanced. Either a computer app with a graphical interface, or an ESP32 serving up HTML, something along those lines.

Once I separate main board commands from image data, I want my program to comb through those commands. Look for sequences I’ve already analyzed, and call attention to any sequences I haven’t seen yet. Articulating all these patterns in terms of Python code could be straightforward or a hidden gotcha could turn it into an interesting challenge. I decided to try the easy thing first and see how far I get.


Source code for this quick-and-dirty data parsing project is publicly available on GitHub.

This teardown ran far longer than I originally thought it would. Click here to rewind back to where this adventure started.

Simultaneously Listening to Two Serial Ports

I’m slowly understanding the data flowing between main board and control panel of a Canon Pixma MX340 multi-function inkjet. It’s small enough to be tractable for my skill level, but too complex to be practical on an oscilloscope screen or logic analyzer timeline view. Going beyond those instruments, I’ve decided to tackle this challenge by writing a data filter program on my computer, using two USB serial adapters to hear both sides of the conversation.

I started with a single USB serial adapter to listen to the traffic from control panel to main board, which was successful enough for me to quickly learn all button matrix status reports (scan codes). I quickly learned adding a second serial port will more than double the complexity of my program. When I’m only listening to one port, I could make a blocking call to read() and let it wait for the next byte of data to arrive. But if I block waiting for data to come in on one port, data might arrive on the other port and I wouldn’t know until I get around to calling read() on that other port.

One approach is to create another unit of execution. Whether it be another thread, process, etc. One per serial port and they can each block on their respective calls to read(). Whichever one gets data first gets to execute. There are a few problems with this approach. The first is Python’s historically poor support for multi-threading, leaving a legacy of tricky gotchas that I don’t want to spend time to learn right now. The second problem is when I have two independent units of execution it will take work to coordinate between them. For example, if I want to link main board commands with the matching 0x20 sent by control panel as acknowledgement. They’re solvable problems, but not the next one: I have ambition to create a microcontroller project to reuse this control panel in the future, so I want to work on logic that can conceivably be ported to a microcontroller. While FreeRTOS running on ESP32 has concept of tasks, ATmega328 Arduino has no such counterpart.

Due to those concerns, I will first try an alternate approach. Check to see if data is available before I commit to a serial read operation. If so, read only what’s already available for processing. This allows my code to rapidly cycle through all my serial ports checking for available data. And if found, process only the amount available in order to avoid blocking execution any longer than I have to. This pattern is bad for modern computers because polling prevents dropping the big CPU to a low power state, but is common for microcontrollers.

If I want to eventually port this code, though, I should at least make sure it’s theoretically possible. I found good news there. The ability to check serial data availability in a non-blocking manner seems to be pretty common across different serial data APIs.

Looks promising enough for a test drive.


This teardown ran far longer than I originally thought it would. Click here to rewind back to where this adventure started.

New USB Serial Adapter Show Minor Updates

I want to listen in on both directions of communication between main board and control panel of a Canon Pixma MX340 multi-function inkjet. I thought I had the hardware on hand to do this: one USB serial adapter that I’ve been using successfully for years, and a second adapter I bought but haven’t needed until now. The second unit turned out to be a dud, wasting some of my time before I realized it was junk. I went back to my earlier purchase and ordered another unit from the same Amazon listing. (*)

Even though I ordered from the same listing, I did not expect to receive an identical unit. It’s very common for Amazon vendors to switch products without switching the listing. This can be done maliciously, selling a listing with high star rating to another vendor who then offload junk. Sometimes this scam is obvious as the reviews discuss a completely different product, but some buyers don’t even bother to read those reviews to pick up on the fraud. Even without fraudulent intent, a vendor may switch to shipping a different but similar product or upgrade to an updated version. I think I have the latter scenario. Here’s a picture of the new arrival (top) and my veteran workhorse (bottom)

The two devices are substantially similar, with the same size, I/O locations, and number of surface-mount components laid out in the same locations. But the circuit board underneath is not identical with small differences such as the width of traces, taking turns at different angles, or small differences in printed labels.

The most visible difference upon power-up: LED colors. My veteran uses three red LEDs, the new adapter use three different colors. Yellow for power, green for receive activity, and red for transmit activity.

The product listing advertises FTDI chip. Is it a genuine FTDI chip or one of the many fakes riding on FTDI’s success? I don’t know how to tell. I do know, however, that this new adapter passed its first test making it better than the dud. I wired it to listen to traffic from MX340 main board to control panel. I pushed a button on the control panel to trigger a LCD screen update, and this new adapter reported 1020 bytes of data was transmitted. This was the expected value and good enough for me to get back to work.


This teardown ran far longer than I originally thought it would. Click here to rewind back to where this adventure started.

(*) Disclosure: As an Amazon Associate I earn from qualifying purchases

Time Wasted By Faulty USB Serial Adapter

I have deciphered how the main board of a Canon Pixma MX340 multi-function inkjet commands its control panel to toggle two onboard LEDs, one more step towards understanding how those two components work together. Now I want to incorporate that new knowledge to my serial data filter program and add a second channel of serial data.

To sit alongside my trusty and proven USB serial adapter, I dug up an inexpensive USB serial adapter I bought years ago. It was still unopened in its package. I don’t remember what I originally bought it for, but apparently I didn’t end up using it. The device is a blue USB plug with four wires coming out of it, a very generic form factor that still comes up for searches on “TTL Serial USB adapter”.

I plugged it into the USB hub and it was recognized as a USB serial device. I wired it to receive MX340 main board commands in parallel with the control panel. I wrote a few lines of Python to read from this adapter and it was a mess. A LCD screen update — which I had established to be 1020 bytes long — was received as 65 bytes. Not only did the majority of data go missing, the bytes that came through didn’t resemble the expected data at all.

I first thought I made a wiring error and doubled checked my connections. Then I thought there was a coding error. When neither turned up a plausible explanation, I flipped the channels connected to my two serial adapters. Now my trusty USB serial adapter reported the expected 1020 bytes of data in a LCD screen update, and this new-from-package adapter reported gibberish instead of proper control panel button scan codes.

This USB serial adapter sucked straight out of the box. Searching on my Amazon order history, I found my order for this adapter in September 2019. I was curious to see if this adapter caused problems for other people so I clicked on the product link, and that went to an error page. This product was so excellent its listing had been pulled! It was probably filled with negative reviews. I would have written one if I had opened the package as soon as I got it and realized it was junk. It had cost $7. Less than $12 I paid for my working adapter (purchased June 2018) but $7 is obviously too much for garbage. Since it’s far too late for me to return this device for a refund, I shrugged and chalked it up to lesson learned. I went back to my order history, found the invoice for the USB serial adapter (*) with a proven history and ordered another unit.


This teardown ran far longer than I originally thought it would. Click here to rewind back to where this adventure started.

(*) Disclosure: As an Amazon Associate I earn from qualifying purchases.