Printer
Fun fact: Mega Anser could interface with a printer connected to the second controller port (used for print screen, back when the meaning was literal). You could purchase a bundle that included a thermal printer and a cable to connect it to your Mega Drive.
Disclaimer: the information below comes from reverse engineering Mega Anser and hasn't been checked with the printer adapter. Nobody seems to have seen one of these adapters in like forever so right now it's impossible to verify edge cases. In the future we may come up with our own version of the adapter.
Detecting and setup
First you need to detect the printer. The only way to do so is to check the peripheral ID: the printer has an ID
of 10 (%1010
).
If the printer has been found, set bits 2-0 as output, and the rest as input:
FastPauseZ80
move.b #$07, (IoCtrl2)
move.b #$07, (IoData2)
ResumeZ80
Mega Anser expects the printer in the second port.
Using the printer
First, read bit 3 from the port. If it's set (bit 3 = 1), then the printer can't take in more data right now. Come back later. You may want to include the option to cancel the operation too, in case the printing got stuck for whatever reason.
; Keep Z80 out of the way
FastPauseZ80
; Check if printer is ready
btst #3, (a0)
bne @NotReady
Now comes the fun part: we need to load the byte into the shift register,
one bit at a time. To do this, first we write the next bit into bit 0,
OR'd with $FC
. Then we do it again but OR'd with
$FE
. Repeat this for all bits (highest bit comes first).
; Go through the whole byte
; Assumes byte is in d0
moveq #8-1, d7
@Loop:
; Push next bit where we need
; to write it (this pushes the
; bit 7 of d0 into bit 0 of d1)
add.b d0, d0
addx.b d1, d1
; Put bit into shift register
; Bit 1 must be cleared too
and.b #$01, d1
or.b #$FC, d1
move.b d1, (a0)
nop
nop
; Then set bit 1 again...
or.b #$FE, d1
move.b d1, (a0)
nop
nop
; Onto next bit
dbf d7, @Loop
Finally, you have to send the byte you just copied to the printer. To
do this, you have to first write $FB
, then $FF
:
; Confirm byte
move.b #$FB, (a0)
nop
nop
move.b #$FF, (a0)
nop
nop
; Z80 can continue now
ResumeZ80
Repeat this operation for every byte you need to write to the printer, and make sure to check for bit 3 for whenever the printer becomes busy (e.g. because it's still printing).
Compatible printers
Even though the printer bundle for Mega Anser came with a thermal printer, the interface itself is pretty agnostic, working with any printer with a Centronics interface… although if you remember printing in the DOS days, you probably know that just about every printer did its own thing with escape codes.
Mega Anser sends ESC/P codes to the printer, so for the sake of consistency you'll want to stick to those printers. Thankfully those were pretty common back then.
Mega Anser subset
Mega Anser actually uses a very limited subset of ESC/P (just enough to print a bitmap), and it never prints ASCII text directly. The commands used by Mega Anser are:
NUL
(00
): ignore these.CR
(0D
): returns to the beginning of the current line.LF
(0A
): advances to the beginning of the next line.ESC A
(1B 41 nn
): changes the line height (how muchLF
moves downwards). The value nn specifies height in 1/60 inches.ESC L
(1B 4C nn mm ...
): prints bitmapped graphics, with a resolution of 120×60 dpi. The width is mmnn pixels, and height is 8 pixels. After this command there's a byte for every column (bit 0 is bottom pixel, bit 7 is top pixel).
Note that it's likely that the bundled printer supports more commands
than those (e.g. it probably supports FF
(0C
)
to eject the page, but Mega Anser doesn't do it).
Printing an image
Chances are, you just want to print an image. You can use the same ESC/P commands that Mega Anser uses for the job. First of all, make sure your image meets these requirements:
- Image must be monochrome (no, not even grayscale)
- Width can be anything (but keep 640px at most)
- Height must be multiple of 8px
- Pixels are rectangular! (width is half the height)
- Mega Anser gets around this by printing every column twice.
- You could instead exploit this to get finer dither detail.
First you want to set up the page. Output 1B 41 08
to set
the correct line height (8px), then 0D 0A
to ensure you
start on a new line.
Now, "slice" the image into horizontal bars that are 8px high. For each
of these bars, use the ESC L
command: output 1B
4C
, then the width (first low byte, then high byte), then output
a byte for every 1×8 column (remember, bit 0 is the bottom row). Once
you've done that, output CR LF
(0D 0A
).
After you've printed all those bars the image is already on paper, but
optionally if you want to be nice, you can output a 0C
(form feed) to make the printer let go of the paper. Otherwise the user
will have to do it manually with the printer buttons.
In pseudocode:
print 1B 41 08 0D 0A
for every 8px slice
print 1B 4C
print (width & 0xFF)
print (width >> 8)
for every column
byte = (row[0] << 7) |
(row[1] << 6) |
(row[2] << 5) |
(row[3] << 4) |
(row[4] << 3) |
(row[5] << 2) |
(row[6] << 1) |
(row[7] << 0)
print byte
end
print 0D 0A
end
print 0C
Printing in color
Iwis says
Remember to be nice and let people pick if they want to print in color or monochrome (since we'll be dealing with old printers and it's also possible they just don't have color ink right now).
If you want to go further and support printers that can do color, you
can make use of the relevant ESC/P command to change the ink color.
The ESC r
(1B 72 nn
) command lets you pick
the ink that will be used from now on. The nn byte specifies
the color:
Value | Color |
---|---|
0 | Black |
1 | Magenta |
2 | Cyan |
3 | Violet |
4 | Yellow |
5 | Red |
6 | Green |
Assuming your image is in CYMK format, what you need to do is to print each 8px high slice four times (once for each ink) before moving onto the next one. You achieve this by doing the following (make sure to print the colors in this order or you risk smearing):
- Select yellow ink (
1B 72 04
) - Print yellow dots for the slice
- Send byte
0D
(carriage return) - Select cyan ink (
1B 72 02
) - Print cyan dots for the slice
- Send byte
0D
(carriage return) - Select magenta ink (
1B 72 01
) - Print magenta dots for the slice
- Send byte
0D
(carriage return) - Select black ink (
1B 72 00
) - Print black dots for the slice
- Send byte
0D
(carriage return) - Send byte
0A
(line feed)
Converting from RGB to CYMK
If you want to print an arbitrary image chances are that you have it in RGB format instead. You can convert from RGB to CYM (no black) easily:
- cyan = 100% - red
- magenta = 100% - green
- yellow = 100% - blue
That alone is already enough to get a full color image, but you probably also want to use black ink to improve contrast (i.e. CYMK). To convert from RGB to CYMK:
- Convert from RGB to CYM as earlier
- Find the smallest value from cyan, magenta or yellow
- Substract that value from all three
- Add that value to black
The above works fine even with dithered images (in fact, if each layer is monochrome then it's even easier since each component can only be either 0% or 100%).