; 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

