Instructables

Reverse Engineering to Emulate Ink Cartridges for a Epson Printer

For the past two years, I’ve been planning to build myself a 3D printer from some old Inkjet printers that I had collected over the years. But not until two weeks ago had I actually started to work on it.

The 3D printer I want to made uses a ink jets to print a chemical onto a building platform. The building platform goes down as a new layer of powder is spread onto it. Then a chemical is sprayed from the print head that will cause powder to bind. If you want more information on these kinds of printers look here

My printer of choice was an old Epson Stylus C63 for these reasons:
1. The ink cartridges only supply ink, they do not have ink jet nozzles themselves
2. I can attach rubber hoses or some other form of tubing that allows me to print another chemical of my choosing
3. The print head nozzles uses a piezoelectric material to produce droplets of ink (lasts much longer than thermal inkjets)
4. Most service manuals for Epson Printers come with a detailed schematic of the main board.

Note: Due to certain circumstances, this printer had already been pulled apart.

The first thing I needed to do was get my printer running so I could plug my logic analyser in and look at the signals, the problem was that I was missing one ink cartridge so the printer refused to print, or do anything.
Due to my budget (jobless and about to start university) I decided to try and Emulate the missing cartridge, or all of them using one microcontroller, thus allowing me to move forward and using water for the print head instead of ink that stains.

Video of the finished result:


New updated video: (HD)

 
Remove these adsRemove these ads by Signing Up

Step 1: Getting familiar with it

So in order to emulate the ink cartridges, I need to know how they work first. Luckily the ink cartridges in my printer have simple EEPROMs that hold the ink usage and other important information about the ink cartridge such as the expiration date, colour, serial number and so on.

Inside the print head, there is a circuit board with pads mapped out that are an exact mirror of the pads on the ink cartridges and a ribbon cable that connects this board up to the main board on pins 1 to 6.

A quick look at the schematic for the main board reveals that pins 1 – 6 are named CVDD, CSDA, CRST, GND, CSCK and COI.

With these signal names I decided it would be best to draw out a schematic of the connecting board as it was not present in the service manual. This revealed that CSDA, CSCK, CRST have 100KOhm Pull-Down resistors, a bit unusual in my opinion as it’s not like I2C (uses Pull-Up Resistors typically 1.8KOhms to 47KOhms). The COI signal connects serially to the ink cartridges but on the end is shorted to GND. Curiously I tested the continuity of the pads on the ink cartridges and found that they are shorted, this means that when COI is grounded, all ink cartridges are present.

Step 2: Signals

Now that I know the signal names, I wanted to see what the signals themselves looked like.
I got out my soldering iron, a ribbon cable (used to be an IDE cable from an old computer) and a craft knife. I cut 6 wire wide ribbon cable about 30cm long, stripped the ends carefully using the craft knife (a lot easier to use a craft knife to strip a ribbon cable than a pair of clippers or pliers), and soldered them onto the circuit board that mates with the ink cartridges.
I also stripped the other ends and soldered on some pins so I could plug them into my logic analyser.

After plugging in the logic analyser into the circuit board and having a look at the signals, I found that immediately the SCK, SDA look nothing like I2C, I’m pretty sure that Epson must have invented their own serial protocol for talking to the EEPROMs inside the ink cartridges.

What I found is that CVDD gets powered up and shortly after, CRST and CSDA go up at the exact same time shortly followed by CSCK which pulses. CSDA changes on the Rising Edge of the CSCK and I’ll assume it gets read on the Falling Edge.
CSCK is Serial ClocK
CSDA is Serial Data
CRST is Reset
and CVDD is the power
Looks easy enough but I wanted more information about this protocol, were there any addresses being sent?

I turned up the sample rate on my logic analyser and found that there are 4 clock cycles, a pause, then 8 cycles, pause, 8 more and so on. The 4 clock cycles at the beginning gave me the idea that the first four could possibly be a 4 bit address of the ink cartridge. Still a lot of information was unknown about this protocol. So I did some research!

Googling around found nothing about this protocol, the next best place to look was in patents.
Yes, patents, I searched for "Epson SDA SCK RST memory eeprom" on http://www.freepatentsonline.com/   and one of the patents I found (US7660008 ) contained exactly what I was looking for. It outlined the SCK, SDA, RST and VDD of the protocol.
I’ll save you the hassle and point out what I found:
1. Flow chart diagrams describing what the Host and Slave does
2. A timing diagram of the protocol
3. The first 3 bits are a 3bit address followed by a Read/Write bit (Read = 0, Write = 1)
4. The address counter always starts at 0x00 and increments by 1 every clock cycle (This must mean it writes in bits, not bytes)
5. The moment RST goes low, the EEPROM stops everything and resets itself
6. There are 252 bits to read, the last 3 bits (actually they’re at the start) is the address of the ink cartridge.

Using what I had found, I decided to start writing code

Step 3: Basic Code Routines

What I wanted to do was read out the ROMs from the ink carts and then use those to try and emulate the missing ink cartridge I don’t have. This means creating a library that will act as a host to read the data but can also be used to emulate an ink cartridge.
Using previous knowledge from creating software based protocols, I created a header file with these functions:
• extern void epsnCartInit(void);
• extern void epsnCartStart(unsigned char addr, unsigned char write);
• extern void epsnCartStop(void);
• extern void epsnCartWrite(unsigned char data);
• extern unsigned char epsnCartRead(void);

I also created the following definitions that help with remembering which pin was what:
For you microcontroller, you will need to modify these values to suit your application

#define _ecSCKTRIS TRISCbits.TRISC0
#define _ecSDATRIS TRISCbits.TRISC1
#define _ecRSTTRIS TRISCbits.TRISC2
#define _ecVDDTRIS TRISBbits.TRISB1

#define _ecSCKLat LATCbits.LATC0
#define _ecSDALat LATCbits.LATC1
#define _ecRSTLat LATCbits.LATC2
#define _ecVDDLat LATBbits.LATB0

#define _ecSCK PORTCbits.RC0
#define _ecSDA PORTCbits.RC1
#define _ecRST PORTCbits.RC2
#define _ecVDD PORTBbits.RB1

#define wait() DelayUS(50)


Note: I used a PIC18F and used Latches for setting the outputs, it’s just my preference in choice. You’re welcome to use any microcontroller you’re familiar with.

void epsnCartInit(){
    _epsnCartSCKTRIS = 0;
    _epsnCartSDATRIS = 0;
    _epsnCartRSTTRIS = 0;
    _epsnCartVDDTRIS = 0;

    _epsnCartSCK = 0;
    _epsnCartSDA = 0;
    _epsnCartRST = 0;
    _epsnCartVDD = 0;

    _epsnCartSCKLat = 0;
    _epsnCartSCKLat = 0;
    _epsnCartSCKLat = 0;
    _epsnCartSCKLat = 0;
}


The start function pulls VDD high, waits, then sets SDA according to the address bit as well as SCK. Waits, pulls SCK low, waits yet again and then pulls SDA high accordingly to the address. This repeats 2 more times except on the 4th time, SDA is set Low because we want to read the data in the EEPROMs not write.

void epsnCartStart(unsigned char addr,unsigned char write){
    char i = 0; // Used for counting

     char tmp = 0; // Temp storage variable

    _ecVDDLat = 1; // Set VDD High
     DelayMS(1); // Wait for the EEPROMs to get ready
     _ecRSTLat = 1; // Enable the EEPROMs for communication

     tmp = addr; // Copy the address into the temp variable
     if(write){ // If we want to write
         temp |= 0x08; //set the 4th bit high ( ‘|’ means OR )
     }  

     while(i<4){
         _ecSDALat = (tmp&0x01); // Set SDA High or Low according to the LSB

         wait(); // Wait roughly 50uS
         _ecSCKLat = 1; // Set Clock High
         wait(); // Wait another 50uS
         _ecSCKLat = 0; //Set Clock Low
         wait(); // Wait another 50uS
         tmp>>=1; //Right shift all the bits in tmp by one
         i++; // increment the counter by one
     }

}

The next function to implement is stop:
void epsnCartStop(){
     _ecRSTLat = 0; //Set RST low
    DelayMS(1); //Wait a bit
     _ecVDDLat = 0; //Turn off the EEPROMS
}


And finally the read function:
unsigned char epsnCartRead(){
     char i=0;
    char temp = 0x00;

    while(i<8){
        temp>>=1; // Right shift the temp variable by one
        _ecSCKLat = 1; //set SCK High
        wait(); //Wait roughly 50uS
        if(_ecSDA){ // If SDA is high
            temp |= 0x80; // Then set bit 7 high
        }
        _ecSCKLat = 0; // Bring SCK low again
        wait(); //wait another 50uS

        i++; // Increment the counter by 1
    }
    return temp; // Return what we have read
}


We now have the basic functions that we need to read data from the ink cartridges, lets set up our microcontroller and set the results.

Step 4: Setting Up Our Microcontroller!

Schematic Used.png
Start up your IDE, in my case it's MPLab, create a new project for your chip and create a basic hello world program to make sure your chip is going.

Note: My hello world program sends "Hello World!\r\n" over Serial to my computer at 38400bps

#include <p18f2550.h> // Include file specific for this chip
#include <usart.h> // USART Functions
#include <stdio.h> // printf(format,...) , sprintf(string, format, ...), etc...

#include "delay.h"

void main(){

    PORTA = 0x00;

    PORTB = 0x00;
    PORTC = 0x00;

    TRISA = 0x00;
    TRISB = 0x00;
    TRISC = 0x00;

    ADCON1 = 0x0F; // This is such a trouble maker, never forget to turn the Analogue pins back to digital! :P

    TRISCbits.TRISC7 = 1; // Turns the RX pin into an input (look it up in your datasheet for your chip)

    OpenUSART(USART_TX_INT_OFF & USART_RX_INT_OFF & USART_ADDEN_OFF & USART_BRGH_HIGH & USART_CONT_RX & USART_EIGHT_BIT & USART_ASYNCH_MODE,78);

    while(1){
        putrsUSART("Hello World!\r\n"); // Send a message via Serial

         LATAbits.LATA0 = ~LATAbits.LATA0; //Flash an LED by toggling it on and off

         DelayMS(500); // A delay function I've created
     }
}


This was set up with a 3.3V power supply because the ink cartridges run on 3.3Volts

Why a hello world program? Well, every time I set up a new project on a bread board, I always want to make sure I've got the configureation bits correct and that I'm not getting garbage in the terminal.

I think this is good practice and everyone should do it! :P

If your chip is doing something simple like mine, Great!! on to the next step

Step 5: Implementing Our Library

Now we want to include the library we created for interfacing the microcontroller to the ink cartridge.

At the top of the file add:
#include "epsnCart.h" // the name of the header file we created earlier

Since this microcontroller is pretending to be the host, it's controlling the SCK, SDA and RST signals. so make sure they're outputs, add this with the other TRIS registers inside main():

epsnCartInit();

The next bit of code is what I used inside of the while(1) {
It requests an address starting at 0x00 then increments by one after 32 read bytes:

void main(){
    char addr = 0, i = 0;
    char string[40];
   
    ...
    epsnCartInit(); // Initialised the pins used

    while(1){ // Loop forever!

        sprintf(string, "Reading cartridge with addr: 0x%02X\r\n",addr);
        putsUSART(string); // prints a messge like: Reading cartridge with addr: 0x03


        epsnCartStart(addr,0); // Start by sending the address in read mode
       
        i = 0;
        while(i<32){ // keep looping until i is no longer less than 32
            sprintf(string,"0x%02X,",epsnCartRead());
            putsUSART(string);
            i++;
        }

        epsnCartStop(); //brings RST back low

        putrsUSART("\r\n");

        addr++
        if(addr>7){
            addr = 0;
        }
        DelayMS(500);
    }
}


You're probably looking at the line "sprintf(string,"0x%02X,",epsnCartRead());" and going "Huh?"
sprintf is a string formatting function, much like printf except saves the formatted string into the variable string.
"0x%02X," will return a string with a readable hexadecimal value eg: 0xFE and epsnCartRead() returns a value that was read from the ink cart

This was set up with a 3.3V power supply because the ink cartridges run on 3.3Volts

I programmed this to my microcontroller, disconnected the print head from the printer to prevent interference.
I then plugged in the 3 ink cartridges I had and turned it on.

Note: At this point, if you ran this code for the first time, I would expect you have problems. Like me, I've gone over the code dozens of times, changing it here and there to make it work. It's normal if it doesn't work the first time for you. It's a great learning experience figuring out what went wrong! :P

Step 6: Mwahahaha, It's alive :P

Picture of Mwahahaha, It's alive :P
Schematic Used.png

phew, after all that hard work, it's alive!!!

Here is what I got of my terminal

Addr: 0x01
57,07,02,F2,00,00,00,00,00,50,98,6A,EB,D3,54,A3,39,50,14,15,31,93,E4,B4,24,96,57,04,35,F5,E4,14,

Addr: 0x02
00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,

Addr: 0x03
03,00,02,F2,00,00,00,00,00,50,C8,1C,2A,A5,82,00,3B,50,16,17,31,93,E4,B4,24,96,57,04,35,F5,E4,34,

Addr: 0x04
00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,

Addr: 0x05

00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,

Addr: 0x06
00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,

Addr: 0x07
03,00,02,F2,00,00,00,00,00,50,30,9F,74,81,D1,40,3B,50,1A,1B,31,93,E4,B4,24,96,57,04,35,F5,E4,74,


Now, see here how only the odd address returned data except 0x05?
That's because 0x05 is the address of the Magenta ink cartridge which I don't have sadly :(

Step 7: Emulation Code Routines

Now the fun part!

I need the printer to think that there are four ink cartridges installed, to do this we need to implement some emulation functions first. Again from previous knowledge I have predefined these in the header:

• #define epsnCartReady() (_ecRST)
• extern unsigned char espnCartGetAddr(void);
• extern void epsnCartOut(unsigned char data);
• extern unsigned char epsnCartIn(unsigned char data);


epsnCartReady is a macro that is used check if the printer has RST set high.
epsnCartGetAddr returns the address that the print head is requesting.
epsnCartOut is used to send data to the Host controller
epsnCartIn is used to receive data from the Host when it’s writing

So this time, instead of controlling the SCK to go high or low, we need it to wait for it to go high or low, but if the reset goes low, then stop everything and return:

unsigned char espnCartGetAddr(void){
    char i=0;
    char temp = 0x00;

    while((i<4) && _ecRST){ // Loop while i < 4 and RST is HIGH
        temp>>=1; // Right Shift Temp by one
        while((!_ecSCK) && (_ecRST)); // Wait for the clock to go high
        if(_ecSDA){// only if SDA is high
            temp |= 0x08; // then set the 4th bit
        }
        while((_ecSCK) && (_ecRST));// wait for the clock to go low
        i++; // increment the counter
    }

    if(_ecRST == 0){
        return 0xFF;//if RST went low, means the Host killed the signal
    }
    return temp; //return the address and the read/write bit
}


And the Out function:

void epsnCartOut(unsigned char data){
    char i = 0;
    char temp = data;

    _ecSDATRIS = 0; // set the SDA to output
    _ecSDALat = 0; // set SDA low

    while((i<8) && _ecRST){ // Loop while I < 8 and RST is high
        while(!_ecSCK && _ecRST); // Wait for SCK to go high
        _ecSDALat = temp&0x01; // Set SDA high or low according to the first bit

        while(_ecSCK && _ecRST); // Wait for SCK to go low
        temp>>=1; // Right shift the data by one
        i++; // increment the counter by one
    }
    _ecSDATRIS = 1; // Set SDA to an output
    _ecSDA = 1; // not sure if this is needed
}


And finally the In function:

unsigned char epsnCartIn(void){
    unsigned char i=0;
    char temp = 0x00;

    while((i<8) && _ecRST){ // Loop while I < 8 and RST is high
        temp>>=1; // Right Shift the temp by one
        while(!_ecSCK && _ecRST); // Wait for SCK to go High
        if(_ecSDA){ //only id SDA is high
            temp |= 0x80; // Set the 8th bit
        }

        while(_ecSCK && _ecRST);// Wait for SCK to go low
        i++; // Increment the counter by one
    }
    return temp; // return what we have received
}


Step 8: Emulating Ink Cartridges

Picture of Emulating Ink Cartridges
So what happens now is that we need to set up our microcontroller to contain the data we have dumped. Then allow the printer host to read and write to it.

Now the problem with this is that I don’t want my fake ink cartridges to ever get used up, so to do that is we have a fixed dump that the microcontroller will load into ram everytime it starts up. This will allow the printer to not only read, but write to it as well. When the microcontroller is powered off then back on, the previous data is restored. (Double Bonus)

Storing data arrays in the microcontroller, place this somewhere under the include, but before void main()

Note: This may vary between different microcontroller families, but it works nicely with my PIC18F

ram unsigned char black[32] = {0x10,0x00,0x02,0xF2,0x00,0x00,0x00,0x00,0x00,0x50,0x90,0x6A,0x17,0xE3,0x54,0xA3,0x39,0x50,0x14,0x15,0x31,0x93,0xE4,0xB4,0x24,0x96,0x57,0x04,0x35,0xF5,0xE4,0x14};
ram unsigned char cyan[32] = {0x4C,0x04,0x01,0xF2,0x00,0x00,0x00,0x00,0x00,0x50,0x90,0x5A,0x19,0xF0,0x94,0x03,0x3B,0x50,0x16,0x17,0x31,0x93,0xE4,0xB4,0x24,0x96,0x57,0x04,0x35,0xF5,0xE4,0x34};
ram unsigned char yellow[32] = {0x4A,0x04,0x01,0xF2,0x00,0x00,0x00,0x00,0x00,0x50,0x90,0x6A,0x8B,0xC9,0xA4,0x41,0x3B,0x50,0x1A,0x1B,0x31,0x93,0xE4,0xB4,0x24,0x96,0x57,0x04,0x35,0xF5,0xE4,0x74};

Note: Noticed that I don’t have Magenta? That’s because that’s the ink cartridge I don’t have.

The next step is to write a routine that checks which ink cartridge is being requested, and allow the host to read and write to it.
This is how I did it:

void main(){
    unsigned char* inkPtr;
    char addr = 0x00;
    ...

    While(1){

        epsnCartStartListen();

        while(epsnCartReady()==1); // Wait for the Ready signal to go back to low

        while(epsnCartReady()==0);
        addr = espnCartGetAddr();

        if(addr&0x7 == 0x01){ // if the first 3 bits is equal to 0x01
            inkPtr = &black[0]; // Point to the black data
        }else if(addr&0x7 == 0x03){ // if the first 3 bits is equal to 0x03
            inkPtr = &cyan[0]; // Point to the cyan data
        }else if(addr[i]&0x7 == 0x05){
            inkPtr = &cyan[0]; // This is meant to be Magenta, but I don’t have that
        }else if(addr[i]&0x7 == 0x07){
            inkPtr = &yellow[0];
        }else{
            continue: // Go back to the beginning of the main loop because we don't know this address
        }

        if(addr&0x08){//WriteMode – Check to see if the Host wants to write
            while(epsnCartReady()){//keep looping until RST goes low
                *inkPtr = epsnCartIn();
                // the data the pointer is pointing to
                // gets written with new data
                inkPtr++; // Increment the Pointer
            }
        }else{//Read Mode
            while(epsnCartReady()){
               epsnCartOut(*inkPtr);
               // Output the data the Pointer is pointing to
               inkPtr++; // Inrement the pointer
           }
       }
    }
}

Success!
After programming this new code and turning on my printer, the printer accepted 4 imaginary cartridges thanks to my microcontroller. I'm also quite shocked that I accepted the cyan ink cartridge data for magenta aswell!
This is a huge achievement for me as this is my first real world reverse engineering I’ve ever done and I got promising results.
Now I can continue with turning the printer into a 3D printer

I hope you guys liked this, this is my second instructable and this took me a while to write :P

If you wan, you guys are more than welcome to download the source files

Cheers!
Roman
padbravo18 days ago

wow!!!

mma401711 months ago
R 1,R 2,R 3,R 4,R 5=?
C 1-C 2=?
Crystal=?
This is the closest thing to what I wanted. I wanted to know how to directly interface to an ink cartridge.
Also, I would like it in Wiring language so I can use it with my Arduino.
Hello , Im thinking of making a design to color plastic fillament and I thought that ink jet cartridges would do for such a thing , though I dobt know how they work and how I can communicate with them using the small pcb they have .I also thought that the cartridges should be from epson since I like their shape and it would be neat .So can you provide me with the information I need to control them?
If so please , please send me an e-mail at theohacker@hotmail.com
Thanks by the way ...
NZSmartie (author)  θφλώκος2 years ago
Hey,

I haven't had much time to work on my printer project all year so I don't know how to control the print heads just yet. What I can say though is that you will definitely need the control circuitry on the printer's main board because it provides the correct voltage levels and wave forms that are needed to drive the Piezoelectric print heads.

The good news is however that University had just ended and I'm now on my summer break meaning more time to work on my own stuff. I'm sure that during that time I would be able to figure out the control signals and have it print "Hello World" or something on a piece of paper :P

I'm sorry I can't be of much help at the moment =(
AgeingHippy3 years ago
Hey

This is interesting, although I currently don't have any need for reverse engineering... but who knows. Maybe some day in the future.

Re 3D printers - I am building on, a standard Mendel from the open source Reprap project www.reprap.org

Although reprap primarily looks a FDM (laying down layers of plastic) I am sure it would be a valuable resource for you to proceed with your build. I have recently seen a demo video of a printer building with this approach and thought it may be a simpler method over FDM. But then again, you need some method of laying a even layer of the build material down each time. Are you looking at a dropping bed or a raising head? Dust management might also be interesting :)

good luck with your build. Looking foreward for more info.
NZSmartie (author)  AgeingHippy3 years ago
Hey, Thanks for your reply :)

When I do get around to building the build platform. it will be a bed that drops for each layer printed.
I haven't really thought about dust management at this stage because I have yet to build something that prints first :P

Cheers
roman
Which logic analyzer are you using?

Thanks...
dn_nguoiban3 years ago
how do you control nozzles of printhead?
dn_nguoiban3 years ago
hi!

I'm a student . now i am begin to do my project about 3d printing.i have very much difficult about printer head.I try to search information, how to control print head .but i don't see. fortunately, to day i written your project.I found very interesting. i hope you can shared for me about your experience,tell me more than information about your project.
and please give me some document to
tranngoclinh.cdt@gmail.com.


thanks you very much.
ufosystem3 years ago
Roman,

I have some interesting auto reset ink cartridges for you.
I can get you for free.

Check the video and let me know if this can help you on this project.
http://www.ufosystem.net/

Our ink refill solution will auto reset the ink level to 100%, and you can refill the ink without removing the ink cartridge.
Anyway, let me know if you need it. It is free for you.
(email me at info@timelinedigitalic.com)

Charles
NZSmartie (author)  ufosystem3 years ago
Hey Charles,

Thanks for the offer but I will have to turn it down. My printer didn't want to work without all ink cartridges present, that's why I emulated them all so I could continue with my 3D printer project.

The print head in the end will be printing chemicals to bind powder together, not ink. So ink cartridge reset will not be of any help to me, sorry.
Roman,

No worries, if you need anything related to ink cartridges, let me know.

Cheers,

Charles
richms3 years ago
Great job on that.
mossDboss3 years ago
this is so awesome, I hate my epson, 1, it drains the ink every time i turn it on or 2. it simply does nto work, 3. will only work with all colors full (I only use black, but somehow the colors get used up too)

thank you for doing this.
NZSmartie (author)  mossDboss3 years ago
Yeah, Epson printers are trouble makers but I'm choosing it because of the technology it uses.
Not only will my hack help me with my long term project, but it will also mean no wasting expensive ink :P

Word of warning though, If you try to use this on your own Epson printer, I can not guarantee the protocol is the same or the pin out of the ink cartridges. But if you do get it to work, you will need to isolate the inks EEPROMs so that they do not interfere with the microcontroller...
Or
Analyse the dumped data and see if you can find what bytes contain the ink usage, and reset it to full :P (Might post this up later myself)
Pro

Get More Out of Instructables

Already have an Account?

close

PDF Downloads
As a Pro member, you will gain access to download any Instructable in the PDF format. You also have the ability to customize your PDF download.

Upgrade to Pro today!