Back in the Amiga 500 glory days, the printer was one of those essential add-ons, that allowed you to produce something physical from your word processor or drawing program. It was still a time, where physical prints were the only universally acceptable form of non-verbal communication.
In this post we are going to take a look at some assembly code, that prints a string to the printer, using the parallel port. The code is from Letter XI of the AMIGA Programming in Machine Code course, and the code can be found on Disk1. As always, remember to read the letters, since I won’t repeat all the details here 😉.
Amiga printers came in two flavors, one using the serial port, and the other, using the parallel port. Printers where much less general purpose, then they are today. Some printers were good at writting a lot of text and numbers quickly, while others also were good at printing graphics. Some printers could only do black and white, while others printed in colors.
Especially laser printers were very expensive, and some could also do PostScript printing. PostScript is a scripting language for graphics, that required a lot of computer power to interpret and rasterize. Hence, some early PostScript printers also came with their own CPU, like the Motorola 68K. There are even examples of printers having microprocessors that ran faster, than the computers that printed to them.
Back in the day, only professionals could afford laser PostScript printers. We, mere mortals, had to use cheaper alternatives for printing graphics, like the Okimate 20 Colour printer, which was part of my setup, when I was a kid 😃
The Okimate 20 was a thermal printer that used a 24 x 24 pin head to transfer three color dots from a three color wax ribbon, to the paper. The small dots would then be mixed by the eye to create a wealth of rich colors, with a glossy finish. Here’s a review of the Okimate 20 from 1986.
And here’s how the printer looked like. Image from Pintrest.
You didn’t actually need the ribbon, since this thermal printer could print black and white directly to thermal paper. I didn’t like the thermal paper, which where continous feed, delivered on a roll, since it was thin and aged horribly, so I usually photocopied it to standard paper afterwards.
Since I don’t have access to the physical hardware anymore, I have to rely on WinUAE for printing. That constrains my options somewhat to what the windows printers can understand.
I really would have liked to print som graphics in this post, but I doubt that there are any PostScript drivers out there for Workbench 1.3.
Printing from WinUAE
Before we start looking at the actual code, we need to make sure that our setup is configured correctly to do the most basic printing.
Fire up WinUAE and go into the IO ports settings and make sure that the parallel port is configured for a Microsoft XPS Document Writer and the type is Epson Matrix Printer Emulation. I have used the 48 pin version.
This configuration is about the most basic you can get. This will not get you further than raw text printing. Forget about printing stylish fonts or graphics. The gist of this is that the Epson matrix printer emulation provide a very simple printer control language, that is universally supported by almost all windows printers. My guess is that it’s PCL 1 or ESC/P, but I haven’t verified this.
A simple test of the print setup can be done through CLI. Go into the CLI and redirect the output of the list command to the parallel port.
By doing this, we are bypassing the printer driver, in the AmigaOS, by directly outputting to the parallel port. Outside WinUAE, this will trigger the windows printer.
Here’s some of my output from the list command run in the C folder of Workbench 1.3.
If this works, then try redirecting the output to PRT: which is the printer. This redirection will go through the printer driver. In Workbench 1.3 I have used the generic printer driver, and I was able to get an output that matches the one above.
Printing via the Parallel Port
The print subroutine, described later, uses the Amiga’s 8520 Complex Interface Adapter (CIA) chips, called CIAA and CIAB.
The CIA chips handle I/O activities from the keyboard, serial and parallel port, among others. We are going to use the parallel port to communicate with the printer.
The parallel port is connected to the CIAA chip, and it can be used for both input and output. In our case, we are going to send characters to the printer, so we need to output data.
The de facto standard for printers, in the late eighties, was to use a Centronics connector on the printer side, and various connectors on the host side. The Amiga used the DB25 female connector.
Here’s a pinout, showing the DB25 connector, with the 8 pins of prb highlighted.
Printer status codes
The printer can tell the host system about it’s status, by setting separate pins on the parallel port. The status of those pins can be fetched via the pra register on the CIA-B chip ($BFD100).
The pra register is 8 bits wide, but only the first three bits are related to status codes for the printer. It’s SEL, POUT and BUSY, which stands for selected, paper out, and busy. Here’s the pinout.
Printers of the late eighties, where operated a bit differently than todays printers. There are some hints of this in the printer manual for the Centronics Model 101 printer (PDF) that was written in 1978.
The printer has to be online, or selected, before it can receive data. When the printer is deselected, it also becomes busy. In the deselected state, the oprator can load the paper by using the form feed and line feed buttons or do other adjustments.
Printers often used continous feed paper, and the Form Feed button would advance the paper to the next sheet. Line feed works similarly, but just advances a line at the time. Both Form Feed and Line Feed are control codes in the ASCII table.
Here’s a video that shows an old Epson printer in action.
Notice how the Line Feed and Form Feed buttons can’t be operated, while the printer is selected.
Printer buffers also have a great impact on print speed. E.g. the previosly mentioned Centronics 101 printer, contains a buffer that only holds 132 characters. This is just enough to hold a line of text as defined by most line printers. This enabled the printer to write a whole line at a time. If the buffer contained e.g. a Carriage Return code (also in the ASCII table), the printer would become busy in the duration of time it would take to move the paper.
I couldn’t recreate this behaviour on a modern printer, probably due to a mix of their large buffers, and modern operating systems having a print queue.
Let’s take a look at the mc1102 program from Disk1. The program prints a zero terminated buffer of characters to the printer, by using the parallel port.
The signaure for the print subroutine looks like this.
Subroutine: print Description: Prints characters stored in a buffer Syntax: status = print(buffer) ML: d0 = print(a0) Arguments: buffer = zero terminated array of characters Result: status code 0 = OK 1 = POWER OFF 2 = OFFLINE 3 = PAPER OUT
The code starts by disabling all the interrupts for the duration of the call to the print subrutine.
If the printer is ready to print, i.e. it’s selected, the print subroutine continues with printing all characters in the input buffer, until the null terminator is reached.
The print subroutine also checks the status codes of the printer, and exits if the printer is out of paper or not online, or powered off. The status codes are the three least significant bits of pra ($BFD000): SEL, POUT, BUSY. This is done on the CIAB chip.
lea.l buffer,a0 ; store address of buffer into a0 move.w #$4000,$dff09a ; INTENA clear master interrupt bsr print ; branch to subroutine print move.w #$c000,$dff09a ; INTENA set master interrupt rts ; return from subroutine buffer: ; label for the buffer dc.b "Dette er en test av en printer-rutine.",10 ; text to be printed with added linefeed dc.b 0 ; null termination of the string print: ; label for the print subroutine move.b #$ff,$bfe301 ; ddrb set all pins to output for the parallel port prb wait: move.b $bfd000,d0 ; move data in pra into d0 andi.b #%111,d0 ; only keep 3 first bits in d0 - control lines SEL, POUT, BUSY cmp.b #%100,d0 ; compare 4 with d0 - SEL=1, POUT=0, BUSY=0 beq.s ready ; if equal, goto label ready cmp.b #%001,d0 ; compare 1 with d0 - SEL=0, POUT=0, BUSY=1 beq.s offline ; if equal, goto label offline cmp.b #%111,d0 ; compare 7 with d0 - SEL=1, POUT=1, BUSY=1 beq.s poweroff ; if all bits are set high, goto subroutine poweroff cmp.b #%001,d0 ; compare 1 with d0 - SEL=0, POUT=0, BUSY=1 beq.s wait ; if equal, goto label wait cmp.b #%011,d0 ; compare 3 with d0 - SEL=0, POUT=1, BUSY=1 beq.s paperout ; if equal, goto label paperout bra.s wait ; branch always to wait ready: ; label move.b (a0)+,d0 ; move value a0 points to into d0 and then increment a0 by a byte cmp.b #0,d0 ; compare 0 with d0 - we could use tst here beq.s stop ; if the zero termination of the string is reached then goto stop move.b d0,$bfe101 ; move value in d0 into the parallel port prb bra.s wait ; goto wait stop: ; label moveq #0,d0 ; move quick 0 into d0 rts ; return from subroutine poweroff: ; label moveq #1,d0 ; move quick 1 into d0 rts ; return from subroutine offline: ; label moveq #2,d0 ; move quick 2 into d0 rts ; return from subroutine paperout: ; label moveq #3,d0 ; move quick 3 into d0 rts ; return from subroutine
Even though it’s a simple program, it can be a bit hard to follow. So I made a diagram of it.
After running the program in Seka, I got this “nice” printout 😃.
Multitasking - what about it?
The mc1102 program starts by disabling all interrupts, which would be a “not so good thing” to do on a multitasking system like the AmigaOS. I guess that the authors of the Amiga Machine Code course wanted to keep things simple, but the consequence is that the whole system will freeze while our program is running.
What they should have done, was to get a handle on the ressource, in this case the printer. Then all other tasks that requested the printer, would have to wait their turn, until we were done with it.
I was wondering why, sending data to the printer and fetching it’s control codes, required both CIAA and CIAB.
To answer that question, I found the hardware schematics of the Amiga 500 over at amigawiki (PDF), and it also contains the schematic for the serial and parallel ports. 😃
I was also wondering about how the data direction, ddrb ($BFE301), on CIAA worked. I found the specs (PDF) for the CIA 6526 / 8520, but decided against going into that rabbit hole. This post is already long enough 🚀.
While I was reading the schematics, I saw the name B52/ROCK LOBSTER. It turns out that this mysterious text is written on all Amiga 500 boards. According to the Amiga history guide this was introduced by George Robbins, who was responsible for most of the low end Amiga systems. The Amiga 500 was developed under the working title of B52 🎸 🎵 👍.
Previous post: Amiga Machine Code Letter X - The Mouse.