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:

    move.b  #$07, (IoCtrl2)
    move.b  #$07, (IoData2)

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
    ; 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
    ; 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)
    ; Then set bit 1 again...
    or.b    #$FE, d1
    move.b  d1, (a0)
    ; 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)
    move.b  #$FF, (a0)
    ; Z80 can continue now

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:

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:

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
   print 0D 0A
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:

Printer color values

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):

  1. Select yellow ink (1B 72 04)
  2. Print yellow dots for the slice
  3. Send byte 0D (carriage return)
  4. Select cyan ink (1B 72 02)
  5. Print cyan dots for the slice
  6. Send byte 0D (carriage return)
  7. Select magenta ink (1B 72 01)
  8. Print magenta dots for the slice
  9. Send byte 0D (carriage return)
  10. Select black ink (1B 72 00)
  11. Print black dots for the slice
  12. Send byte 0D (carriage return)
  13. 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:

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:

  1. Convert from RGB to CYM as earlier
  2. Find the smallest value from cyan, magenta or yellow
  3. Substract that value from all three
  4. 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%).