Printing graphics from a Cambridge Z88 on a Serial 8056 via the BASIC patch

Saturday, 29th November 2025

Photo of a Serial 8056 printer next to a Cambridge Z88 computer

I've got a number of older computers that can print, but no printer for them. Quite often these computers require a serial printer, and so when a Serial 8056 printer popped up on eBay for around a tenner I picked it up. This is a thermal printer that takes fax paper rolls, so it seemed like a safe bet as far as consumables go (no need to source awkward cartridges, ink ribbons or spark paper) and the listing claimed it was intended for the Sinclair QL.

When it turned up I was a bit surprised by the plug on the end of the cable – two rows of eight pins, similar to a 16-way IDC connector, and not the phone jack style connector the QL needed. Fortunately the data (RD) and CTS pins were marked on the circuit board inside the printer and I was able to trace them out to the plug and bodge together a cable to plug it into my PC. Between articles from Format magazine, QL World and Popular Computing Weekly about the printer I was able to find the baud rate (1200), a few control codes for formatting and how to output graphics. Still puzzled by the non-QL plug I asked Reddit and that's when it was pointed out that the Serial 8056 is really a rebadged IBM PC Compact Printer originally sold for use with the PCjr. If my particular printer had been intended for use with the Sinclair QL then it would have included the appropriate adaptor in the box.

Knowing this, however, it made it easier to find information about the printer, including a reference manual, confirming the information I'd gleaned from the magazine articles about the Serial 8056.

One of the computers I had planned to use the printer with was my Cambridge Z88. Setting this up as a text printer was easy enough, but I'd been intrigued by a feature of the Z88 BASIC Patch, as described by the notes:

STOP PRESS Version 2.1 includes a graphics dump for Epson-compatible printers. The statement CALL 11011 will dump the graphics window.
The Serial 8056 is not Epson-compatible, and so the printer just outputs nonsense instead of the promised graphics. However, the Z88 Patch has source code available, so I thought it might be possible to modify this to replace the Epson control sequences with Serial 8056 ones.

Unfortunately, the Z88 BASIC Patch source code release appears to be missing the printer code. I turned to Ghidra to disassemble the patch, and found the pertinent routines.

As the routines send a dump of the graphics window (the "map" in Z88 parlance) to the printer, I named the main routine DUMPMAP. One of the first things it does is to reset the printer via a routine I named DUMPRESET. This sends ESC @ to reset the printer (the Epson ESC/P reference may be useful here), then sends two line feeds. It falls through to the routine that is used to send bytes to the printer, which I've named DUMPWRCH:

                    *************************************************************************
                    * Resets the printer to its initial settings and outputs two line feeds *
                    *************************************************************************
                    DUMPRESET
ram:2bb5 3e 1b             LD           A,0x1b       ; ESC
ram:2bb7 cd c6 2b          CALL         DUMPWRCH
ram:2bba 3e 40             LD           A,'@'        ; ESC @ = Initialize printer
ram:2bbc cd c6 2b          CALL         DUMPWRCH
ram:2bbf 3e 0a             LD           A,'\n'       ; Line feed
ram:2bc1 cd c6 2b          CALL         DUMPWRCH
ram:2bc4 3e 0a             LD           A,'\n'       ; Line feed
                    *************************************************************************
                    * Write a byte to the serial port with a 1 second timeout               *
                    *************************************************************************
                    DUMPWRCH
ram:2bc6 f5                PUSH         AF
ram:2bc7 01 64 00          LD           BC,100       ; 100cs timeout
ram:2bca e7                RST          SYS
ram:2bcb 42                db           OS_Pbt       ; Write the byte to the serial port
ram:2bcc f1                POP          AF
ram:2bcd c9                RET

The DUMPRESET routine is also used at the very end of printing to reset the printer and ensure two line feeds appear after the graphics dump. Graphics data are sent as 8 pixel high rows with condensed line spacing, one byte per column. The relevant code that starts this process of each row is as follows: first the line spacing is set to 1/9-inch using ESC 3, a line feed is sent, there's a one second delay to give the mechanism time to advance and then graphics mode is entered with ESC L and a request to send 768 bytes:

ram:2b33 3e 1b             LD           A,0x1b       ; ESC
ram:2b35 cd c6 2b          CALL         DUMPWRCH
ram:2b38 3e 33             LD           A,'3'        ; ESC 3 = Set n/216-inch line spacing 
ram:2b3a cd c6 2b          CALL         DUMPWRCH
ram:2b3d 3e 18             LD           A,24         ; 24/216 = 1/9-inch line spacing
ram:2b3f cd c6 2b          CALL         DUMPWRCH
ram:2b42 3e 0a             LD           A,'\n'       ; Line feed
ram:2b44 cd c6 2b          CALL         DUMPWRCH

ram:2b47 01 64 00          LD           BC,100       ; 100cs
ram:2b4a e7                RST          SYS
ram:2b4b 2d                db           OS_Tin       ; Wait for a key for 100cs

ram:2b4c 3e 1b             LD           A,0x1b       ; ESC
ram:2b4e cd c6 2b          CALL         DUMPWRCH
ram:2b51 3e 4c             LD           A,'L'        ; ESC L = Select 120-dpi graphics
ram:2b53 cd c6 2b          CALL         DUMPWRCH
ram:2b56 3e 00             LD           A,0          ; nL = 0
ram:2b58 cd c6 2b          CALL         DUMPWRCH
ram:2b5b 3e 03             LD           A,3          ; nH = 3: 768 bytes
ram:2b5d cd c6 2b          CALL         DUMPWRCH

The graphics window (map) is only 256 pixels wide, though, so why 768 bytes? Well, the printing code actually scales the image up before printing: it doubles the height and triples the width of each pixel. When outputting a row of graphics data, each column byte is sent three times:

ram:2b7c cd c6 2b          CALL         DUMPWRCH
ram:2b7f cd c6 2b          CALL         DUMPWRCH
ram:2b82 cd c6 2b          CALL         DUMPWRCH

This is all of the Epson-specific printer code, and fortunately it maps pretty well to the Serial 8056:

ActionEpson ESC/PSerial 8056
Initialise printerESC @CAN
Set 1/9-inch line spacingESC 3 n=24ESC 1
Output bitmapped graphicsESC L n=768 [768 bytes]ESC K n=512 [512 bytes]

Ideally, the Epson codes could simply be patched with the equivalent Serial 8056 codes but there is one slight spanner in the works: the Serial 8056 needs a carriage return to be sent after each line and the code doesn't do that and there's no easy way to insert it at the end of the relevant printing routines.

However, it is possible to insert a carriage return at the start of each line, which means that each line will start by ending the preceding one. This does still leave the final line, but fortunately the code calls DUMPRESET after printing the last line and so an additional carriage return can be inserted at the start of that routine to terminate that line.

It's not quite as elegant a patch, as the order of some code needs to be adjusted rather than just patching the Epson codes with the equivalent Serial 8056 codes, but it's not too bad overall. The full list of code changes are as follows:

                    DUMPRESET
ram:2bb5 3e 1b             LD           A,0x1b       ; Change to CR: ?&2BB6=13
ram:2bb7 cd c6 2b          CALL         DUMPWRCH
ram:2bba 3e 40             LD           A,'@'        ; Change to ESC: ?&2BBB=27
ram:2bbc cd c6 2b          CALL         DUMPWRCH
ram:2bbf 3e 0a             LD           A,'\n'       ; Change to '2': ?&2BC0=50
ram:2bc1 cd c6 2b          CALL         DUMPWRCH
ram:2bc4 3e 0a             LD           A,'\n'

The need to insert an extra carriage return at the start of the reset routine means we can only output a single line feed after resetting the printer instead of the original two. You may also be wondering why the printer is "reset" with ESC 2 instead of CAN, as that would save a byte – in my case it doesn't appear that resetting the printer that way resets the line spacing, which means that the printer gets left in the 1/9-inch line spacing mode. ESC 2 explicitly restores the 1/6-inch (default) line spacing mode.

The code that runs at the start of each line of output is a bit more awkward to change, unfortunately. The original code currently works like this:

  • Send ESC
  • Send '3'
  • Send 24
  • Send LF
  • Wait 100cs

However, our new code needs to do this instead:

  • Send CR
  • Send LF
  • Wait 200cs*
  • Send ESC
  • Send '1'*

Three of the five operations line up, however two of them (sending a byte of data and introducing a delay, marked with an asterisk) are swapped, which means that two code blocks in the code need to be swapped. Very fortunately, the code for each operation is the same size (five bytes) which at least means that the code between them can be left in the same place.

ram:2b33 3e 1b             LD           A,0x1b      ; Change to CR: ?&2B34=13
ram:2b35 cd c6 2b          CALL         DUMPWRCH
ram:2b38 3e 33             LD           A,'3'       ; Change to LF: ?&2B39=10
ram:2b3a cd c6 2b          CALL         DUMPWRCH 

ram:2b3d 3e 18             LD           A,24        ; Change to 200cs delay: ?&2B3D=1 ?&2B3E=200
ram:2b3f cd c6 2b          CALL         DUMPWRCH    ; ?&2B3F=0 ?&2B40=231 ?&2B41=45

ram:2b42 3e 0a             LD           A,'\n'      ; Change to ESC: ?&2B43=27
ram:2b44 cd c6 2b          CALL         DUMPWRCH

ram:2b47 01 64 00          LD           BC,100      ; Change to DUMPWRCH '1':
ram:2b4a e7                RST          SYS         ; ?&2B47=62 ?&2B48=49
ram:2b4b 2d                db           OS_Tin      ; ?&2B49=205 ?&2B4A=198 ?&2B4B=43

The time delay is handled by calling the OS input routine with the timeout delay specified in register BC. The original code used 100cs, i.e. 1 second. When I was testing the code I ran into some issues: the first few lines printed fine, but the last couple of lines ended up failing to print, with the preceding lines showing some junk characters at the end of each line. Extending the delay to 200cs fixed the issue, but I was not sure why the first few lines printed fine and the problem only manifested itself at the end of the print until I looked at the movement of the print head more carefully.

The test image I was using was a row of Sierpinski triangles, and so the rightmost pixels were mostly white in the early rows but increasingly black as the triangles widened towards the bottom of the image. It turns out that if the end of the line is white the print head returns back home early, and so the one second delay was enough when the print head was skipping the end of the line but not quite enough when it had to travel the full distance back to the left edge. Extending the delay to two seconds provides more than enough time for the carriage to return.

When it comes to sending the actual bitmap data to the printer only a simple modification is required:

ram:2b4c 3e 1b             LD           A,0x1b
ram:2b4e cd c6 2b          CALL         DUMPWRCH
ram:2b51 3e 4c             LD           A,'L'        ; Change to 'K': ?&2B52=75
ram:2b53 cd c6 2b          CALL         DUMPWRCH
ram:2b56 3e 00             LD           A,0
ram:2b58 cd c6 2b          CALL         DUMPWRCH    
ram:2b5b 3e 03             LD           A,3          ; Change to 2: ?&2B5C=2
ram:2b5d cd c6 2b          CALL         DUMPWRCH

Instead of ESC L with an argument of 768 bytes (&0300) we need to send ESC K with an argument of 512 bytes (&0200). The code will still try to send 768 bytes by repeating each column of the 256-pixel wide image three times, so instead we need to only send each column twice:

ram:2b7c cd c6 2b          CALL         DUMPWRCH
ram:2b7f cd c6 2b          CALL         DUMPWRCH
ram:2b82 cd c6 2b          CALL         DUMPWRCH     ; Change to CALL <dummy>: ?&2B83=&B4

The final CALL could be replaced by three NOP bytes but rather than do that the address of the target is patched to &2BB4. This address contains a RET instruction as it's the final instruction of a nearby routine so effectively turns the CALL into a NOP.

This completes the patch itself; the only thing needed to do is to wrap it up into a neat installer. Here is the result of that, in BBC BASIC:

   10 REM Serial 8056 Patch for Z88 BASIC
   20 C%=0:FORA%=&2B03TO&2BF6:C%=C%+?A%:NEXT
   30 IFC%=&5BF1PRINT"Patch already applied.":END
   40 IFC%<>&5BB9PRINT"Please load Z88PATCH.BBC first.":END
   50 READA%,V%:REPEATA%?&2B00=V%:READA%,V%:UNTILA%<0
   60 PRINT"Patch applied: use CALL 11011 to print.":END
   70 DATA&B6,13,&BB,27,&C0,50
   80 DATA&34,13,&39,10,&3D,1,&3E,200,&3F,0,&40,231,&41,45
   90 DATA&43,27,&47,62,&48,49,&49,205,&4A,198,&4B,43
  100 DATA&52,75,&5C,2,&83,180,-1,0

Line 20 first calculates a checksum of the area targeted by the patch, which is then checked in lines 30 and 40 for two known states: Serial 8056 patch already applied and Z88PATCH loaded but Serial ;8056 patch not applied. Line 50 reads the patch data itself (stored in lines 70 to 100) which is made up of addresses and patch value pairs; as all bytes to patch appear in the &2Bxx address range only the least significant byte of the address is stored.

In summary, if you have a Serial 8056 and a Cambridge Z88 and wish to print graphics from BBC BASIC you may find the Serial 8056 for Z88 patch useful. You will also need the Z88 BASIC Patch as a starting point.

FirstPreviousLast RSSSearchBrowse by dateIndexTags