Peripheral ID

Many of the things you can plug into the Mega Drive have an "ID", which is a four bit number you can use to identify a peripheral before you use it. Most devices Sega released on Mega Drive and Saturn can be identified this way.

The peripheral ID is a side effect of the protocol used to talk with the device, so not everything has an ID. Peripherals without an ID need to be detected in their own unique way.

How to get the ID

To get the peripheral ID, first configure the I/O control port so that bit 6 is output and the rest are input (in other words, the same way as for setting up a controller). Then read the peripheral twice, once with bit 6 = 1 and once with bit 6 = 0. The values of bits 3-0 are used to determine the ID.

Remember to pause the Z80 while accessing the I/O port.

The peripheral ID consists of four bits (where each bit is obtained by ORing two bits read back from the data port). They're as follows:

Peripheral IDWrite thisRead back from port
ID bit 3$40bit 3 OR bit 2
ID bit 2$40bit 1 OR bit 0
ID bit 1$00bit 3 OR bit 2
ID bit 0$00bit 1 OR bit 0

The following routine shows how you could do it (using look-up tables to handle both ORs of each read, since it honestly makes the code simpler):

; Subroutine to get peripheral ID
; Pointer to data port in a0, result in d0
; d1 and a1 get trashed

GetPeripheralId:
    ; Make sure pin direction is
    ; set correctly for this
    FastPauseZ80
    move.b  #$40, 6(a0)
    
    ; Get bits 3-2 of peripheral ID
    move.b  #$40, (a0)
    nop
    nop
    lea     @Table1(pc), a1
    moveq   #$0F, d1
    and.b   (a0), d1
    move.b  (a1,d1.w), d1
    
    ; Get bits 1-0 of peripheral ID
    move.b  #$00, (a0)
    nop
    nop
    lea     @Table2(pc), a1
    moveq   #$0F, d0
    and.b   (a0), d0
    move.b  (a1,d0.w), d0
    
    ; Leave peripheral alone
    move.b  #$40, (a0)
    ResumeZ80
    
    ; Put bits together
    or.b    d1, d0
    
    ; Result is in d0
    rts
    
    ; Look-up table to extract ID
    ; bits from the first read
@Table1:
    dc.b  %0000,%0100,%0100,%0100
    dc.b  %1000,%1100,%1100,%1100
    dc.b  %1000,%1100,%1100,%1100
    dc.b  %1000,%1100,%1100,%1100
    
    ; Look-up table to extract ID
    ; bits from the second read
@Table2:
    dc.b  %0000,%0001,%0001,%0001
    dc.b  %0010,%0011,%0011,%0011
    dc.b  %0010,%0011,%0011,%0011
    dc.b  %0010,%0011,%0011,%0011

Beware! Since we messed with the data port the peripheral now may need some time before returning correct data. This is true even for controllers (as this interferes with the 6-button controller sequence). If you retrieved the ID you'll want to save it and wait for the next frame before starting to use the peripheral. You'll know if it got unplugged when you try to use it and it returns incorrect data, anyway.

Known peripheral IDs

The following values are known to match existing peripherals:

List of known peripheral IDs
IDPeripheral
1111 ($0F)(undetectable)
1101 ($0D)Mega Drive controller
1100 ($0C)Mega Drive controller (see note)
1011 ($0B)Saturn controller
1010 ($0A)Printer
0111 ($07)Sega multitap
0101 ($05)Other Saturn peripherals
0011 ($03)Mouse
0001 ($01)Justifier
0000 ($00)Menacer

The following peripherals can't be detected this way, and you need to try interacting with them directly and see if they return the expected data to tell if they're there:

Iwis says

The ID 1100 can happen if the console is reset in the middle of trying to read a 6-button controller (in which case it may end up returning the extra buttons and trip the check).