; CD32 Serial Shift Register, software implementation for PIC16F88x
; Copyright (C) 2011 by Alastair M. Robinson
processor 16f886
include <P16F886.INC>
__CONFIG _CONFIG1, _INTOSCIO & _DEBUG_OFF & _LVP_OFF & _FCMEN_OFF & _IESO_OFF & _BOR_OFF & _CPD_OFF & _CP_OFF & _MCLRE_OFF & _PWRTE_ON & _WDT_ON
__CONFIG _CONFIG2, _WRT_OFF & _BOR21V
#DEFINE BANK1 bsf STATUS,RP0
#DEFINE BANK0 bcf STATUS,RP0
RAMSTART equ 0x20
COMMONRAMSTART equ 0xf0
CD32_SEL equ 0 ; In port B, to make use of edge-sensing interrupt.
CD32_CLK equ 4 ; The rest of the CD32 lines occupy port A
CD32_DATA equ 5
CD32_UP equ 0
CD32_DOWN equ 1
CD32_LEFT equ 2
CD32_RIGHT equ 3
CD32_DIRECTIONMASK equ 0x0f ; 00001111
CD32_BUTTONMASK equ 0x30 ; 00110000
Sega_SEL equ 4 ; The Sega pad is wired to port B
Sega_D0 equ 7 ; Up
Sega_D1 equ 5 ; Down
Sega_D2 equ 3 ; Left
Sega_D3 equ 2 ; Btn 2
Sega_D4 equ 6 ; Btn 1
Sega_D5 equ 1 ; Right
; Sega bit allocations, mapping to CD32 serial shift register
Sega_A equ 4 ; Green
Sega_B equ 6 ; Red
Sega_C equ 7 ; Blue
Sega_Start equ 1 ; Play/Pause
Sega_Down equ 5 ; Yellow
Sega_Up equ 3 ; Right Shoulder
Sega_Left equ 2 ; Left Shoulder
Sega_Right equ 0 ; Zero
Sega_DIRECTIONMASK equ 0x2d ; 00101101
Sega_BUTTONMASK equ 0xd2 ; 11010010
tmpval equ RAMSTART+0
shiftcounter equ RAMSTART+1
count1 equ RAMSTART+2
count2 equ RAMSTART+3
segastatus equ RAMSTART+4
segatmp equ RAMSTART+5
portalatch equ RAMSTART+6
portatmp equ RAMSTART+7
int_status equ COMMONRAMSTART+1
int_w equ COMMONRAMSTART+2
shiftreg equ COMMONRAMSTART+3
org 0x00
goto start
org 0x04 ; Interrupt routine - very time critical since we're
; simulating a serial shift register.
; Save regs
movwf int_w
swapf STATUS,w
movwf int_status
BANK1
bsf TRISA,CD32_CLK
BANK0
; The first bit should already be present since the
; first bit represents the Blue button, which is the
; data line's normal function. FIXME - can we improve compatibility by not putting
; the Blue button on the line until this point, or would it be too slow?
; We don't bother to watch the Select line, instead we
; let the watchdog timer rescue us if the comms break down.
waitclkhigh
btfss PORTA,CD32_CLK
goto waitclkhigh
; Put next bit on the data line - inverted by transistor
btfss shiftreg,6
bsf PORTA,CD32_DATA
btfsc shiftreg,6
bcf PORTA,CD32_DATA
waitclklow
btfsc PORTA,CD32_CLK
goto waitclklow
; Shift 1 bit left
rlf shiftreg,f
clrwdt
decfsz shiftcounter,f
goto waitclkhigh
waitselhigh
btfss PORTB,CD32_SEL
goto waitselhigh
bcf INTCON,INTF
; Restore port A
BANK1
bcf TRISA,CD32_CLK
BANK0
movf portalatch,w
movwf PORTA
; Reset shift counter for next time
movlw 0x07
movwf shiftcounter
; Restore regs
swapf int_status,w
movwf STATUS
swapf int_w,f
swapf int_w,w
retfie
; **************************************
start:
; Set internal oscillator to 8MHz
movlw 0x70
banksel OSCCON
movwf OSCCON
; Initialise port B for digital input, except for SEL pin
; We also set port A to high Z
banksel PORTA
clrf PORTA
clrf PORTB
clrf PORTC
; Get rid of any interference from ADCs
banksel ANSEL
clrf ANSEL
clrf ANSELH
banksel TRISA
movlw 0xff
movwf TRISB
bcf TRISB,Sega_SEL
clrf TRISA
clrf TRISC
; Enable weak pull-ups on Port B
banksel OPTION_REG
bcf OPTION_REG,NOT_RBPU
bcf OPTION_REG,INTEDG
banksel WPUB
movlw 0xfe
movwf WPUB
banksel PORTA
movlw 0x07
movwf shiftcounter
banksel WDTCON
movlw 0x1 ; Shortest WDT delay possible (without touching prescalers?)
movwf WDTCON
banksel INTCON
bcf INTCON,INTF
bsf INTCON,INTE
bsf INTCON,GIE
banksel PORTA
clrf PORTA
bsf PORTB,Sega_SEL
call shortdelay2
loop:
BANK0
call getsega
; Decode directions
movlw CD32_DIRECTIONMASK
movwf portatmp
btfss segastatus,Sega_Up
bcf portatmp,CD32_UP
btfss segastatus,Sega_Down
bcf portatmp,CD32_DOWN
btfss segastatus,Sega_Left
bcf portatmp,CD32_LEFT
btfss segastatus,Sega_Right
bcf portatmp,CD32_RIGHT
movf segastatus,w
andlw Sega_BUTTONMASK ; On the first read, only A, B, C and Start are readable
movwf tmpval
call getsega
call getsega
call getsega
movf segastatus,w
andlw Sega_DIRECTIONMASK ; Now we have X, Y and Z on the direction lines
iorwf tmpval,w
iorlw 0x01 ; Set lowest bit of shift register, which is constant
movwf shiftreg
; Mirror Red and Blue status directly to PORTA
btfss shiftreg,Sega_B ; Red button on clock line
bcf portatmp,CD32_CLK
btfsc shiftreg,Sega_B
bsf portatmp,CD32_CLK
btfss shiftreg,Sega_C ; Blue button on data line - inverted by transistor
bsf portatmp,CD32_DATA
btfsc shiftreg,Sega_C
bcf portatmp,CD32_DATA
movf portatmp,w
movwf portalatch
movwf PORTA
clrwdt
call longdelay
goto loop
getsega ; Return button status in segastatus
movlw 0xff
movwf segastatus
movf PORTB,w
movwf segatmp
btfss segatmp,Sega_D4 ; B button, map to Red
bcf segastatus,Sega_B
btfss segatmp,Sega_D5 ; C button, map to Blue
bcf segastatus,Sega_C
btfss segatmp,Sega_D0 ; Up
bcf segastatus,Sega_Up
btfss segatmp,Sega_D1 ; Down
bcf segastatus,Sega_Down
btfss segatmp,Sega_D2 ; Left
bcf segastatus,Sega_Left
btfss segatmp,Sega_D3 ; Right
bcf segastatus,Sega_Right
bcf PORTB,Sega_SEL ; Drop select line
call shortdelay2
movf PORTB,w
movwf segatmp
btfss segatmp,Sega_D4 ; A button, map to Green
bcf segastatus,Sega_A
btfss segatmp,Sega_D5 ; Start button, map to Play / Pause
bcf segastatus,Sega_Start
bsf PORTB,Sega_SEL ; Raise select line again
call shortdelay2
return
shortdelay2 ; Preserves W
movwf count2
movlw 0x10
movwf count1
clrwdt
shortdelay2loop
decfsz count1
goto shortdelay2loop
movf count2,w
return
shortdelay ; Preserves W
movwf count2
movlw 0x3f
movwf count1
clrwdt
shortdelayloop
decfsz count1
goto shortdelayloop
movf count2,w
return
longdelay ; Doesn't preserve W
movlw 0x18
movwf count2
longouterloop
movlw 0xff
movwf count1
clrwdt
longinnerloop
decfsz count1
goto longinnerloop
decfsz count2
goto longouterloop
return
END