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.
The parallel port communication with the printer was a little bit tricky.
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:
- Read the control register (which contains the ACK and BUSY signal) to a CPU register;
- Unsets the ACK signal in the CPU register and outputs everything to the control register;
- Unsets the BUSY signal in the CPU register and outputs everything to the control register;
- 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.