Wednesday, August 17, 2016

Using A Receipt Printer With The CoCo/MC-10

In Episode 9 of The CoCo Crew Podcast, Neil and I discussed the role of printers in the retrocomputer hobby. For some reason this became one of our most popular segments! We continue to get comments and requests for information about printers and how to make use of them with the CoCo and MC-10...

Upon Receipt

Around that same time, I developed an interest in receipt printers. These thermal printers are a popular target for various microcontroller and "Maker"-style hacking projects, partly because they are readily available through eBay and other surplus vendors. The continued use of such devices in retail also ensures a ready supply of paper to feed them. The fact that many such printers feature an RS-232 serial interface is just icing on the cake! The Epson TM-T88III M129C is just such a device...but not all of them have RS-232 serial ports!

Having acquired a receipt printer with a serial port, I set about to connecting it to my MC-10. This is somewhat trickier that it sounds, since the MC-10 (and the CoCo) redefine the usage of the pins on the serial port depending on whether one is talking to a printer or to a modem, as explained here. With the proper cable in place, there is still the matter of setting the communications parameters, particularly the baud rate. The settings for the printer are handled by setting DIP switches internal to the device, while setting the baud rate used by the internal ROM routines on the CoCo and MC-10 involved setting a value in memory (i.e. "POKE 16932,10" for 4800 baud on the MC-10, or "POKE 150,7" for the same speed on the CoCo).

With the correct cabling and speed settings, printing almost works! Unfortunately, the printer and the CoCo/MC-10 don't agree on what constitutes the end of a line of output. This not only causes delays when printing small amounts of text, it also causes the output itself to be formatted incorrectly.

Hook Me Up

In the distant(?) past, there was some disagreement about how to indicate the start of a new line of text (a.k.a. "newline") in an ASCII-encoded file. In the days of the teletype, "carriage return" (CR) represented physical movement of the print mechanism to the left edge of the paper, while "line feed" (LF) represented moving the paper vertically down to the next line of text. These two functions generally go together, making some folks feel that having two characters to represent one action was redundant. Therefore, some systems used just the CR, some used just the LF, and others continued to use both. The CoCo and MC-10 are in the "CR-only" category, while the receipt printer requires both CR and LF to process a newline.

What needs to happen is to change Color BASIC to output an LF immediately after any CR is sent to the printer. But Color BASIC is in the ROM! How is this possible? Well, it turns-out that the writers of Color BASIC were smart enough to identify several places in the ROM where it seemed potentially useful to let other software take control and modify Color BASIC's behavior. These are called RAM hooks, and there are a number of them. One such hook gets called whenever a byte is output by Color BASIC. We just need a little assembly language program to insert an LF after a CR is sent to the printer, and a way to hook it into Color BASIC...

The CoCo assembly language program looks like this:

DEVNUM  equ        $006f

        pshs       b            save B reg
        ldb        DEVNUM       load device number
        incb                    increment for testing purposes
        puls       b            restore B reg
        beq        exit         if device number == -1, then exit
        bpl        exit         if device number >= 0, then exit

        cmpa       #$0d         is output a CR?
        bne        exit         if not, then exit
        jsr        $a2bf        send CR to line printer
        lda        #$0a         change original CR output to LF

exit    rts                     return from RAM hook

While the MC-10 version looks like this:

DEVNUM  equ        $00e8

        tst        DEVNUM       test device number (only 0 or -2)
        beq        exit         if device number == 0, then exit
        cmpa       #$0d         is output a CR?
        bne        exit         if not, then exit
        jsr        $f9c9        send CR to line printer
        ldaa       #$0a         change original CR output to LF

exit    rts                     return from RAM hook

The assembly language program itself is fairly trivial. It checks the output  device number for a value of "-2" (which represents the printer). If the device number is "-2" then it checks for a CR. For an output value other than CR or for any output device value other than "-2" it simply returns having done nothing. For a value of "-2", the program proceeds to output the CR to the printer, then modifies the output value to LF and allows the program to proceed to output an LF as well.

BASICly Done

The BASIC program that installs the assembly code is a bit more complicated. It starts by determining the highest address in use by the BASIC interpreter. It then uses the CLEAR command to adjust that address downward just enough to leave space for the assembly language program described above. The CLEAR command wipes-out any existing variables, so the program then proceeds to again determine the highest address used by BASIC and saves that information for later. At this point, the BASIC program takes the opportunity to set the printer port baud rate as required by the printer, and to send a reset command sequence to the printer.

At this point the program POKEs the assembly code into memory just above BASIC. Then it pokes the address of the start of the assembly program into the 2nd and 3rd bytes of the character output RAM hook. Finally, the value for a JMP instruction is POKEd into the 1st byte of the character output RAM hook, activating the character output hook program. Now the printer will work with Color BASIC!

Here is the CoCo version of the BASIC code as described above:

10 MT=256*PEEK(35)+PEEK(36)
20 SB=256*PEEK(33)+PEEK(34)
30 SS=MT-SB : NT=MT-21+1
50 MT=256*PEEK(35)+PEEK(36)
60 EX=MT+1
70 POKE 150,7
80 PRINT #-2,CHR$(27)+"@"
90 PRINT #-2,CHR$(10)
100 FOR OF=1 TO 21
140 EH=INT(EX/256):EL=EX-256*EH
150 POKE 360,EH
160 POKE 361,EL
170 POKE 359,126
180 DATA 52,4,214,111,92,53,4,39
190 DATA 11,42,9,129,13,38,5,189
200 DATA 162,191,134,10,57

And, here is the MC-10 version:

10 MT=256*PEEK(161)+PEEK(162)
20 SB=256*PEEK(155)+PEEK(156)
30 SS=MT-SB : NT=MT-15+1
50 MT=256*PEEK(161)+PEEK(162)
60 EX=MT+1
70 POKE 16932,10

80 LPRINT CHR$(27)+"@"+CHR$(10)
90 FOR OF=1 to 15
130 EH=INT(EX/256):EL=EX-256*EH
140 POKE 17033,EH
150 POKE 17034,EL
160 POKE 17032,189
170 DATA 125,0,232,39,9,129,13
180 DATA 38,5,189,249,201,134
190 DATA 10,57 

Those little programs may not look like much, but they sure are handy for giving the CoCo or MC-10 a modern-ish printer that can be fed with off the shelf paper. Plus, they allow for lots of interesting printer output to be driven by Color BASIC rather than having to do it all with assembly language. Not bad for some eBay shopping and a little bit of BASIC code!

This little project has been fun, and I think it could lead to more. These little printers have some cool capabilities, including various graphics capabilities and some point-of-sale features. Maybe we can explore those in the future? If you want to see what comes next then I hope that you will continue to stay tuned!