Using MESS as the system’s printer

What was the main reason I started MESSing with the ActionPrinter 2000? To extract the characters’ bitmaps so that I could optimize image->ASCII conversion to the printer’s specific font.

As a fun side-project I decided to configure MESS with CUPS as a normal printer on my system to see how well it would work.

I started off by implementing the Centronics communication used by the printer in MESS. This way, it was possible to boot any device with a Centronics interface connected to the printer, as if I was actually running a device and the printer itself. For this, I booted a simple 486 with an MS-DOS 6.22 floppy disk inside MESS.

486

The parallel port communication with the printer was a little bit tricky.

ActionPrinter 2000 Centronics Interface Overview

When a strobe pulse is detected, the E05A30 gate array sets the BUSY signal. When the CPU is done reading the data and it’s time to unset the BUSY signal, the firmware does the following:

  1. Read the control register (which contains the ACK and BUSY signal) to a CPU register;
  2. Unsets the ACK signal in the CPU register and outputs everything to the control register;
  3. Unsets the BUSY signal in the CPU register and outputs everything to the control register;
  4. Sets the ACK signal in the CPU register and outputs everything to the control register.

The printer’s manual had conflicting information about which component was supposed to take care of setting/resetting the BUSY signal. Another printer’s manual (the LX-1050+, which uses the exact same gate array), had even more conflicting information about the Interface overview.

Besides that, the 486 I was using with the printer would ignore the ACK signal from the printer. The result was that between steps 3 and 4, the BUSY signal was unset, so the PC would ignore the ACK signal and send a new character. But step 4 would reset the BUSY signal again, since it had read the control register to a CPU register, and did not detect that the BUSY signal had changed! The PC would then send another character, so the printer had just “lost” a character somewhere in the middle of all that.

After quite some time trying to find the problem and discussing this issue with smf on IRC, he told me some very wise words:

<@smf> never assume that the programmer knew what he was doing

Indeed, the firmware would make much more sense if we assumed that the programmer who wrote it made a few mistakes. I had already seen a couple of nasty bugs in the firmware, such as these ones:

/* 7851 */
int cr_not_full_step(void)
{
    /* there is a bug in this function, it searches 5 items
     * into an array which only contains 4 items. The last
     * item (0x34) is part of the first instruction in the
     * next function:
     * 7864: 34 4A 10           LXI     HL,$104A
     */
    uint8_t full_steps[5] = { 0x0c, 0x0d, 0x0e, 0x0f, 0x34 };
    uint8_t cur_step = [0xC008];
    for (int i = 0; i < 5; i++)
        if (cur_step == full_steps[i])
            return 0;
    return 1;
}

 

/* 00CA */
uint16_t mul16(uint16_t EA, uint8_t A)
{
    /* uses EA, BC, and A */
    uint8_t h = in16 >> 8;
    uint8_t l = in16 & 0xff;
    uint16_t mh = (uint16_t) in8 * h;
    uint16_t ml = (uint16_t) in8 * l;
    uint16_t r = 0;
    if (mh & 0xff00) {
        /* check is wrong, we don't care */
        carry = 1;
    } else {
        r =   mh;
        r <<= 8;
        r +=  ml;
    }
    return r; /* EA */
}

 

And so I added a little check to the gate array that made it impossible to reset the BUSY signal before the character had been read. The PC inside MESS was now capable of talking to the printer. I got this as a result of “dir > LPT1“:

Did you notice that the lines are not very well aligned vertically? This happens because, up until that point, I would assume that the stepper motor positions would be exactly what the last step was set to by the firmware. This is not really the case, since the motor is not being used one step at a time. It does two steps at a time, and the printhead fires twice at different moments while the motor is still moving. The firmware is very well designed, in such a way that all this movement is taken into consideration when the printhead is moving either from left to right or from right to left. It even considers the 300 microseconds it takes for the printhead to hit the paper! I implemented all this dynamics into MESS and got perfect vertical alignment (funnily enough, even better than the actual printer, which is 20+ years old by now).

Anyways, now I knew how to make the printer receive data inside MESS. But what about receiving data from outside of MESS?

I created a Dummy Centronics driver in MESS that would just read from a named pipe and feed that to the printer. At the other end of the named pipe I could print whatever I wanted from my host system with “cat file > /dev/lp0“, such as this Lorem Ipsum:

Would it be possible to add this printer to CUPS and use it with any other program? Of course. It’s quite easy too:

$ sudo mkfifo /dev/lp0
$ sudo chmod 666 /dev/lp0 # bad idea but simple solution
$ sudo chown root.lp /dev/lp0
$ sudo lpadmin -p ap2000 -E -v parallel:/dev/lp0 -P Generic-ESC_P_Dot_Matrix_Printer-epson.ppd

The printer needs this PPD file so that CUPS knows how to convert anything to the only language the ActionPrinter 2000 understands (ESC/P). At first I had configured the device as a raw printer, so CUPS would happily send PostScript commands to the printer, which would just print them as plain text.

Now I was able to go to LibreOffice and print using the ap2000 printer:

And even GIMP!:

The printer’s resolution is 120×72 dpi. The pixels are not square: every 10 pixels horizontally are the same size as 6 pixels vertically.

MESSing with the ActionPrinter 2000

We receive all kinds of junk and old electronics at Tarrafa Hackerspace. One day, we received an Epson ActionPrinter 2000.

Epson ActionPrinter 2000

The ribbon was dry, but the printer still worked, so we bought a new ribbon and started having fun printing out ASCII-art stuff.

The ActionPrinter 2000 offers a few different kinds of fonts and modes: Draft, Near Letter Quality (Roman and Sans Serif), condensed mode, 12 characters per inch, 10 characters per inch, subscript, superscript… It occurred to me that the font used for the ASCII-art conversion programs was hardcoded, and probably didn’t relate to the one used in the ActionPrinter 2000.

So I was interested in mapping out the character bitmaps to get a better ASCII-art conversion. My first thought was: “let’s get a magnifying glass and map the characters dot by dot!”.

Mapping the dots using magnifying glass

Sure, that worked, but it was time consuming and not the smartest way… There should be some better way. To print out the dots, at some point there must be an electrical signal being sent to the printhead for each dot. So, what if I intercepted that signal and used it to map out the characters?

I opened up the printer and looked for the printhead connector. But then I found a 27c256 EPROM inside, which likely contained the firmware. Hey, the firmware has all the characters inside it somewhere, that’s better than fiddling with the printhead. I got the 27c256’s datasheet and used an Arduino Mega to dump its contents.

Dumping the 27c256 EPROM with an Arduino Mega

After reading through the dump for quite some time, I found some bitstreams that looked like characters, but they weren’t very well organized, so I couldn’t make sense of their structure. Then I remembered that the last time I had visited Garoa Hackerspace, it was Retroprogramming night, and Felipe “Juca” Sanches was talking about emulating old hardware using MAME/MESS. Well, what if I emulated the firmware and recorded the data before it was sent to the printhead?

So I started fiddling around with MESS. It already had some incomplete drivers for the Epson LX-800 and Epson EX-800 printers. I used them as a basis to get my head around the MESS codebase and add support for the ActionPrinter 2000. It shouldn’t be so hard, right? (whenever I ask “right?”, the answer is “wrong”).

So I added the ActionPrinter 2000 firmware and fired up MESS. The processor hanged. I spent some time looking for buttons or switches being read by MESS, maybe just tweaking them would get past the hang. That wasn’t the case, it would still hang and there was no way out of it. Now, if only I had a debugger to see what was wrong… Oh, MESS has a debugger.

MAME/MESS debugger

So, the ActionPrinter 2000 uses an uPD7810 processor. I needed to get its datasheet to understand the assembly. After stepping through the assembly for quite some time, I realized the uPD7810 emulation in MESS was lacking a few functions. There was no ADC support, which was necessary to read the input voltage and some switches. If the printer’s input voltage was too low, the printer would not boot.

So I implemented ADC support, and the firmware would just hang again a few instructions later. What was wrong this time? An interrupt wasn’t being set inside the processor. Who should be setting that interrupt?

I had already started reverse-engineering the printer’s hardware, but now I knew it was important to map all hardware connections in MESS. There were many other integrated circuits: a gate array (E05A30), a RAM chip (2064C), a 256-bit EEPROM (93c06), a stepper motor driver (SLA7020M), and others…

The gate array that was partly implemented in MESS was the E05A03, not the E05A30. It’s a custom-made gate array, and works pretty much like a black box. There’s no way to find out what goes on inside if you don’t have access to the datasheets (which I didn’t). I created some skeleton code for the E05A30 gate array for MESS.

I found out the interrupt that wasn’t being set should have been set by the gate array. So I added an interrupt request right after the gate array started.

I got a few more instructions being run and the firmware would hang again. At this point it was getting complicated having to read the assembly all the time, so I started manually decompiling the firmware. It is a 32 KB firmware. It shouldn’t take too long, right? (remember: the answer is “wrong”)

With a bit of decompiled code, I got this snippet:

void reset_ram(uint8_t val)
{
	memset(0x9800, val, 0x2000);
	if (*((uint8_t *)0xb7ff) != 0xff) {
		killall();
		beep(4, 2);
		while(1); /* HANG */
	}
}

void _start()
{
	[...]
	reset_ram(0xff);
	reset_ram(0x00);
	[...]
}

That didn’t make much sense to me. The RAM was only 8 KB, starting at 0x8000 and ending at 0xa000. There should be nothing at 0xB7FF. Even if there was something, it was being set to 0xFF and then to 0x00, but the data was expected to stay at 0xFF. It might be something inside the gate array, I don’t know. I just created a fake memory device that worked just as was expected for the emulator to be happy.

I got some more instructions, but it entered an infinite loop. Hey, that’s better than hanging…

After much more debugging and decompiling, I realized that the printer was outputting some commands to the gate array in a loop. There were 8 commands being sent and there would be a check for a sensor in the board. I looked at the sensor in the printer, and it turned out to be the Home sensor, i.e. whether the printhead was back at position 0. The command sequence being sent looked very familiar, like the phase signals for a stepper motor. MESS already had stepper motors implemented. I just needed to adapt the command sequence, because it passed through the SLA7020M before reaching the motor. And then I got this:

:__________@_______________________________:
:_________@________________________________:
:________@_________________________________:
:_______@__________________________________:
:______@___________________________________:
:_____@____________________________________:
:____@_____________________________________:
:___@______________________________________:
:__@_______________________________________:
:_@________________________________________:
:@_________________________________________:
:_@________________________________________:
:__@_______________________________________:
:___@______________________________________:
:____@_____________________________________:
:_____@____________________________________:
:______@___________________________________:
:_______@__________________________________:
:________@_________________________________:
:_________@________________________________:
:__________@_______________________________:
:___________@______________________________:
:____________@_____________________________:
:_____________@____________________________:
:______________@___________________________:
:_______________@__________________________:
:________________@_________________________:
:_________________@________________________:
:__________________@_______________________:
:___________________@______________________:
:____________________@_____________________:

This is a printf() of the motor seeking home and going back to the middle of the page. The printer was finally starting to work! This was awesome!

But then, the printer would get into an infinite loop again. It was waiting for the Online button to be pressed, but it wasn’t being acknowledged by the processor. That’s when I realized that the button should trigger an interrupt, which wasn’t properly implemented in MESS. So I implemented the interrupt properly and pressed the Online button.

According to the printer’s User Manual, now the printer should be pulling paper. Indeed, I noticed a different command sequence being sent (for the Paper Feed stepper motor). Now I had both motors working.

What was the next step? Entering the input loop. I should be able to send commands to the printer and have it print them out to paper.

At this moment we take a little pause. We need to realize that the work up to here has already taken me many months. It may seem simple when being read in a blog post, but it was a lot of hard work.

So, instead of going for the input loop, I decided to use the printer’s self-test function. While running the self-test function, I got more commands being sent to the gate array. I suspected they were the printhead signals (the ones I was after right when I started this). Indeed, they seemed like characters, but there was a problem: the printhead wasn’t being fired. After some debugging, I noticed that the timer which would fire the printhead was incorrectly implemented in MESS, so I had to fix that.

Finally, I had a bunch of stuff being printed. I wrote a little script to organize that stuff into an image, and then I got this:

I finally got my characters.

The code can be found in my GitHub account, inside the MAME repository and the lx810l branch. It should be merged into MAME upstreams in a few days. To run the self-test, build the dummy_centronics subtarget from the lx810l-debug branch.

By the way, the characters matched the ones I got with the magnifying glass…