Activator read and write

For anything related to IO (joypad, serial, XE...)

Moderator: BigEvilCorporation

Post Reply
Sik
Very interested
Posts: 901
Joined: Thu Apr 10, 2008 3:03 pm
Contact:

Activator read and write

Post by Sik » Thu Dec 13, 2018 8:05 am

So, it turns out you can write back to the Activator to change the heights it's looking for. Although it seems only the demo program may be doing it, no idea if the released device supports this (especially seeing as it looks up if light bounces at a certain angle or not, it'd require either tilting the sensors or having a bunch of them for each height).

Warning: the stuff below is mostly a wild guess from examining code, I need confirmation from people who can test on a real Activator

When idling (i.e. when not accessing the Activator), TH and DATA0 should be outputs, and they should be high and low, respectively. The rest of the pins should be inputs (in other words, ctrl port = $41, data port = $40). I have no idea what kicks the Activator into compatibility mode (i.e. pretending to be a controller), but I guess it kicks in if this doesn't happen for a while.

The Activator loves to change the directions of the pins (sometimes it drives them, sometimes it doesn't). The "driven/not driven" stuff below is a wild guess on my end and could be entirely wrong, especially since I don't know what it does in compatibility mode.

To read the Activator state:
  1. Write $00 to data port
  2. Wait a while (code does 28 dummy DBF iterations)
  3. Read data port, check that it's $04
  4. Write $01 to data port and wait until bit 1 = 1
  5. Read data port, bits 5-2 are L3-L0
  6. Write $00 to data port and wait until bit 1 = 0
  7. Read data port, bits 5-2 are L7-L4
  8. Write $01 to data port and wait until bit 1 = 1
  9. Read data port, bits 5-2 are H3-H0
  10. Write $00 to data port and wait until bit 1 = 0
  11. Read data port, bits 5-2 are H7-H4
  12. Write $40 to data port
Image

To send a command to the Activator:
  1. Write $00 to data port
  2. Wait a while (code does 28 dummy DBF iterations)
  3. Read data port, check that it's $04
  4. Write $7D to ctrl port and wait a couple of NOP
  5. Write $01 to data port and wait until bit 1 = 1
  6. Read data port, check that it's $01
  7. Write (command<<2) to data port and wait until bit 1 = 0
  8. Write $40 to data port and wait a couple of NOP
  9. Write $41 to ctrl port
Image

This is what made me suspect that TR/TL/DATA3/DATA2 are not driven and the value caused by pull up/down instead: otherwise the only way the Mega Drive could signal a write is by causing a bus fight. It seems that the difference between starting a read and starting a write is whether they get driven low before DATA0 gets toggled again.

No idea what values can be written, but it seems 0-7 change the heights the Activator looks for (in steps of 2 inches, according to the demo program). It's possible the fourth bit isn't used at all.


EDIT: thinking about it further, since there's a significant delay needed after you do the initial $00 write, I wonder if that's what the Activator uses to determine whether to go into compatibility mode or not (i.e. Activator packet would start if TH and DATA0 are held low for long enough, while reading a controller would not try to drive low DATA0). In which case the initial "not driven" values in those charts are wrong :​P (again, I really need tests on the real thing to know for sure)
Sik is pronounced as "seek", not as "sick".

Sik
Very interested
Posts: 901
Joined: Thu Apr 10, 2008 3:03 pm
Contact:

Re: Activator read and write

Post by Sik » Thu Dec 13, 2018 10:11 am

Here are the routines in question (cleaned up for easier reading). The ROM in question is "Activator Command Demo 60 Hz (W) [c][!].bin".

At $001FC0:

Code: Select all

;****************************************************************************
; ReadActivator
; Reads the sensors from the Activator.
;----------------------------------------------------------------------------
; input d2.b .... port number
;----------------------------------------------------------------------------
; output d0.b ... 0 if success, 2 if failure
;****************************************************************************

ReadActivator:
    moveq   #0, d0                      ; Set up initial wait (d0) and
    moveq   #0, d3                      ; timeout timer (d3)
    move.b  #$FF-1, d3
    move.b  #28-1, d0
    
    add.w   d2, d2                      ; Turn port number into an offset
    lea     ($A10003), a0               ; Pointer to I/O ports
    
    move.b  #$41, 6(a0,d2.w)            ; Set up pin direction
    nop
    nop
    nop
    nop
    
    move.b  #0, (a0,d2.w)               ; Start Activator packet
@FirstWait:
    dbf     d0, @FirstWait
    btst    #1, (a0,d2.w)
    beq     @ActivatorOk
    bra     @Error
@ActivatorOk:
    
    move.b  (a0,d2.w), (ActivatorId)    ; Store returned ID
    
    move.b  #1, (a0,d2.w)               ; Get 1st data nibble
    nop                                 ; xx L3 L2 L1 L0 xx xx
    nop
@Retry1:
    btst    #1, (a0,d2.w)
    bne     @DataOk1
    dbf     d3, @Retry1
    bra     @Error
@DataOk1:
    move.b  (a0,d2.w), (ActivatorData1)
    
    move.b  #0, (a0,d2.w)               ; Get 2nd data nibble
    nop                                 ; xx L7 L6 L5 L4 xx xx
    nop
@Retry2:
    btst    #1, (a0,d2.w)
    beq     @DataOk2
    dbf     d3, @Retry2
    bra     @Error
@DataOk2:
    move.b  (a0,d2.w), (ActivatorData2)
    
    move.b  #1, (a0,d2.w)               ; Get 3rd data nibble
    nop                                 ; xx H3 H2 H1 H0 xx xx
    nop
@Retry3:
    btst    #1, (a0,d2.w)
    bne     @DataOk3
    dbf     d3, @Retry3
    bra     @Error
@DataOk3:
    move.b  (a0,d2.w), (ActivatorData3)
    
    move.b  #0, (a0,d2.w)               ; Get 4th data nibble
    nop                                 ; xx H7 H6 H5 H4 xx xx
    nop
@Retry4:
    btst    #1, (a0,d2.w)
    beq     @DataOk4
    dbf     d3, @Retry4
    bra     @Error
@DataOk4:
    move.b  (a0,d2.w), (ActivatorData4)
    
    move.b  #$40, (a0,d2.w)             ; Leave Activator alone
    moveq   #0, d0                      ; Return success
    rts                                 ; End of subroutine

;----------------------------------------------------------------------------

@Error:
    move.b  #2, d0                      ; Return failure
    
    move.b  #$00, (a0,d2.w)             ; Abort Activator packet
    rept    8
    nop
    endr
    move.b  #$40, (a0,d2.w)
    
    rts                                 ; End of subroutine
At $0020B4 (right after the above):

Code: Select all

;****************************************************************************
; WriteActivator
; Sends a command to the Activator.
;----------------------------------------------------------------------------
; input d2.b .... port number
;----------------------------------------------------------------------------
; output d0.b ... 0 if success, 2 if failure
;****************************************************************************

WriteActivator:
    move.w  #$2700, sr                  ; Disable interrupts
    move.l  d1, -(sp)                   ; Save register
    
    move.b  #28-1, d0                   ; Number of iterations for timeout
    
    add.w   d2, d2                      ; Turn port number into an offset
    lea     ($A10003), a0               ; Pointer to I/O ports
    
    move.b  #$41, 6(a0,d2.w)            ; Set up initial pin direction
    nop                                 ; TH and DATA0 are outputs
    nop
    nop
    nop
    
    move.b  #0, (a0,d2.w)               ; Start talking to the Activator
@FirstWait:                             ; and check if it's there
    dbf     d0, @FirstWait
    move.b  (a0,d2.w), d1
    and.b   #$0F, d1
    cmp.b   #$04, d1
    bne     @Error
    
    move.b  #$7D, 6(a0,d2.w)            ; Start a command write (this causes
    nop                                 ; the lines to be driven low)
    nop
    
    move.b  #$01, d2                    ; Send the 0000 nibble
@Retry1:
    btst    #1, (a0,d2.w)
    bne     @DataOk1
    bra     @Retry1
@DataOk1:
    
    move.b  (ActivatorCmd), (a0,d2.w)   ; Send the command
@Retry2:                                ; The format of the command is that
    btst    #1, (a0,d2.w)               ; the value is in bits 5-2, and the
    beq     @DataOk2                    ; rest are all zeroes
    bra     @Retry2
@DataOk2:
    
    move.b  #$40, (a0,d2.w)             ; Done with the Activator
    nop
    nop
    move.b  #$41, (a0,d2.w)
    
    moveq   #0, d0                      ; Return success
    move.l  (sp)+, d1                   ; Restore register
    move.w  #$2000, sr                  ; Enable interrupts
    rts                                 ; End of subroutine

;----------------------------------------------------------------------------

@Error:
    move.b  #2, d0                      ; Return failure
    
    move.b  #$00, (a0,d2.w)             ; Abort Activator packet
    rept    8
    nop
    endr
    move.b  #$40, (a0,d2.w)
    
    move.l  (sp)+, d1                   ; Restore register
    move.w  #$2000, sr                  ; Enable interrupts
    rts                                 ; End of subroutine
Sik is pronounced as "seek", not as "sick".

Post Reply