Wireless SNES controllers

Topics regarding the Uzebox hardware/AVCore/BaseBoard (i.e: PCB, resistors, connectors, part list, schematics, hardware issues, etc.) should go here.
User avatar
nicksen782
Posts: 667
Joined: Wed Feb 01, 2012 8:23 pm
Location: Detroit, United States
Contact:

Re: Wireless SNES controllers

Post by nicksen782 » Sun Apr 15, 2018 6:35 pm

I don't know how keyboard and mice work with Uzebox. I assumed that PS/2 was chosen because it is a simple serial protocol.

Why does each peripheral need its own micro?

With this setup you could just use the one micro. You only get one device per game port anyway. I was just thinking of having a switch or auto-detection for the keyboard and mouse. One adapter could be used for any PS/2 keyboard/mouse you find as well as clean up the signal polling for any game pad.

User avatar
D3thAdd3r
Posts: 2422
Joined: Wed Apr 29, 2009 10:00 am
Location: Minneapolis, United States

Re: Wireless SNES controllers

Post by D3thAdd3r » Sun Apr 15, 2018 7:37 pm

Yes because it is easier to implement than a Human Input Device via USB. USB maybe could be done though, and the ATtiny can bitbang it for some things..not sure about HID though.

With PS/2 the device runs the clock and it is very slow to wait for versus a shift register, but the protocol isn't something that a bare SR can handle. Alec made the MCU version which doesn't miss keys and also doesn't have huge idle waits (the ATtiny does the waiting, and offers it to the '644 faster, later on). My initial version did it all in software, but it uses most of the free cycles to not miss keys and ended up mostly useless.

I think the ATtiny still has plenty of idle time, so perhaps 1 MCU with more pins could handle more than that. Alec also made a design for a multitap in a similar vein.

User avatar
nicksen782
Posts: 667
Joined: Wed Feb 01, 2012 8:23 pm
Location: Detroit, United States
Contact:

Re: Wireless SNES controllers

Post by nicksen782 » Mon Apr 16, 2018 12:53 am

How is the current solution read from? A MCU provides "something" to the Uzebox and polls/reads data from the device however the device should be written.

I am offering a solution that could (with a couple switches) adapt to any of those exist solutions and with any keyboard/mouse. No mods needed, just this adapter. Set the correct switch and this "chameleon adapter" does it all.

User avatar
D3thAdd3r
Posts: 2422
Joined: Wed Apr 29, 2009 10:00 am
Location: Minneapolis, United States

Re: Wireless SNES controllers

Post by D3thAdd3r » Mon Apr 16, 2018 4:52 am

The wiki page describes the protocol on a high level. The demo program that reads in key scan codes is like this:

Code: Select all

void VsyncHandler(){

	if(readKeys){
		ReadButtons2();		//read the snes controllers

		u8 key=GetKey(KB_SEND_END);
		if(key!=0){			
			decode(key);
		}
	}
}


int main(){

	SetUserPreVsyncCallback(&VsyncHandler);

	//Set the font and tiles to use.
	//Always invoke before any ClearVram()
	SetTileTable(font);

	//Clear the screen (fills the vram with tile zero)
	ClearVram();

	debug_str_p(PSTR("GW-BASIC 3.23\r\n"));
	debug_str_p(PSTR("(C) Copyright Microsoft 1983,1984,1985,1986,1987,19883\r\n"));
	debug_int(freeRam());
	debug_str_p(PSTR(" Bytes free\r\n"));
	debug_str_p(PSTR("Ok\r\n\r\n"));

	u8 curDelay=0,curState=0;
	while(1){


		WaitVsync(1);
		//animate cusrsor
		curDelay++;
		if(curDelay==20){
			curState^=1;
			if(curState){
				PrintChar(_x,_y,'_');
			}else{
				PrintChar(_x,_y,' ');
			}

			curDelay=0;
		}
	}

} 

u8 GetKey(u8 command){
	static u8 state=0;
	u8 data=0;

	unsigned char i;

	if(state==0){

		//ready to transmit condition 
		JOYPAD_OUT_PORT&=~(_BV(JOYPAD_CLOCK_PIN));
		JOYPAD_OUT_PORT|=_BV(JOYPAD_LATCH_PIN);
		Wait200ns();
		Wait200ns();
		Wait200ns();
		Wait200ns();
		Wait200ns();
		JOYPAD_OUT_PORT&=~(_BV(JOYPAD_LATCH_PIN));
		JOYPAD_OUT_PORT|=_BV(JOYPAD_CLOCK_PIN);
		Wait200ns();
		Wait200ns();
		Wait200ns();
		Wait200ns();
		Wait200ns();

		if(command==KB_SEND_END){
			state=0;
		}else{
			state=1;
		}		
	}

	//read button states
	for(i=0;i<8;i++){

		data<<=1;
	
		//data out		
		if(command&0x80){
			JOYPAD_OUT_PORT|=_BV(JOYPAD_LATCH_PIN);		
		}else{
			JOYPAD_OUT_PORT&=~(_BV(JOYPAD_LATCH_PIN));
		}

		//pulse clock pin		
		JOYPAD_OUT_PORT&=~(_BV(JOYPAD_CLOCK_PIN));
		
		command<<=1;
		Wait100ns();
				
		if((JOYPAD_IN_PORT&(1<<JOYPAD_DATA2_PIN))!=0) data|=1;

		JOYPAD_OUT_PORT|=_BV(JOYPAD_CLOCK_PIN);		
	
	}

	JOYPAD_OUT_PORT&=~(_BV(JOYPAD_LATCH_PIN));	
	
	return data;

}


void ReadButtons2(){
	unsigned int p1ButtonsLo=0,p2ButtonsLo=0;
	unsigned char i;

	//latch controllers
	JOYPAD_OUT_PORT|=_BV(JOYPAD_LATCH_PIN);
	Wait200ns();
	Wait200ns();
	JOYPAD_OUT_PORT&=~(_BV(JOYPAD_LATCH_PIN));


	//read button states
	for(i=0;i<16;i++){

		p1ButtonsLo>>=1;
		p2ButtonsLo>>=1;

		Wait200ns();
		Wait200ns();
			
		//pulse clock pin		
		JOYPAD_OUT_PORT&=~(_BV(JOYPAD_CLOCK_PIN));
		
		if((JOYPAD_IN_PORT&(1<<JOYPAD_DATA1_PIN))==0) p1ButtonsLo|=(1<<15);
		if((JOYPAD_IN_PORT&(1<<JOYPAD_DATA2_PIN))==0) p2ButtonsLo|=(1<<15);
		
		JOYPAD_OUT_PORT|=_BV(JOYPAD_CLOCK_PIN);
		
		Wait200ns();
		Wait200ns();
	}

	joypad1_status_lo=p1ButtonsLo;
	joypad2_status_lo=p2ButtonsLo;

}
So it is not too bad to interface with the device, and that I saw it is 100% reliable, even having buffered input on the ATtiny so that you do not need to read all the keys every frame. The most important part is the asm for the ATtiny itself:

Code: Select all

 *  Firmware to interface a PS/2 AT keyboard to the Uzebox
 *  via a SNES port. The interface requires a single 8-pin AtTiny25 
 *  with no external components. The 16Mhz PLL clock fuses should be
 *  set in order to run full speed.
 *
 *  The firmware returns keyboard scan codes as-is. To start a 
 *  new "transaction" to transfer one or more bytes, the host must send
 *  the begin signal by lowering the SNES clock line while raising the 
 *  latch line and holding for at least 600ns. Then lower the last and 
 *  raise the clock lines and wait another 1uS before clocking the data.
 *  The host *must* send 0x01 (CMD_SEND_END) to receive its last byte
 *  and close the transaction. 
 *
 *  Timer1 is used as a mechanism to recover lost sync with the keyboard. 
 *  If after 4ms the 11 bits are not received, the pending character is discarded 
 *  and state is restored to be ready to receive the next character.
 *
 * 
 *  Schematic: http://belogic.com/uzebox/downloads.htm
 *
 *  Target: AtTiny24/45/85
 *
 */

.INCLUDE "tn25def.inc"

.equ DEVICE_ID=0xCC
.equ FIRWARE_REV=0x10

.equ BUFFER_SIZE=32

.equ CMD_SEND_KEY=0x00
.equ CMD_SEND_END=0x01
.equ CMD_SEND_DEVICE_ID=0x02
.equ CMD_SEND_FIRMWARE_REV=0x03
.equ CMD_RESET=0x7f


.def rTemp			=r16	;temp register for main, do not use in interrupts.
.def rTxData		=r17	;data for next transfer to host
.def rHostCmd		=r18	;command byte received from the host
.def rIntTemp		=r19	;temp register for interrupts
.def rIntSregSave	=r20	;holds SREG in interrupts
.def rState			=r21	;receiver state (0=waiting for host RTS, 1=tranfer mode)
.def rEdge 			=r22	;current keyboard clock edge (0=falling,1=rising)
.def rBitCount		=r23	;bits left to transfer from the keyboard
.def rData			=r24	;incoming data from the keyboard


;********************************************************************
; S R A M   D E F I N I T I O N S
;********************************************************************
.DSEG
.ORG  0X0060

buffer: .byte BUFFER_SIZE;*must* be at ORG 0x60


;********************************************************************
;   R E S E T   A N D   I N T   V E C T O R S
;********************************************************************

.CSEG
.ORG $0000
	rjmp Main			;0x0000 RESET 			Reset vector
	reti 				;0x0001 INT0			External Interrupt 0
	rjmp PCINT0_vect	;0x0002 PCINT0 			Change Interrupt Request 0
	rjmp TIMER1_COMPA_vect;0x0003 TIMER1_COMPA 	Compare Match A
	reti 				;0x0004 TIMER1_OVF 		Timer/Counter1 Overflow
	reti 				;0x0005 TIMER0_OVF 		Timer/Counter0 Overflow
	reti 				;0x0006 EE_RDY 			EEPROM Ready
	reti 				;0x0007 ANA_COMP 		Analog Comparator
	reti 				;0x0008 ADC ADC 		Conversion Complete
	reti 				;0x0009 TIMER1_COMPB 	Timer/Counter1 Compare Match B
	reti 				;0x000A TIMER0_COMPA 	Timer/Counter0 Compare Match A
	reti 				;0x000B TIMER0_COMPB 	Timer/Counter0 Compare Match B
	reti 				;0x000C WDT 			Watchdog Time-out
	reti 				;0x000D USI_START 		USI START
	rjmp USI_OVF_vect	;0x000E USI_OVF 		USI Overflow


;********************************************************************
;  M A I N    P R O G R A M    I N I T
;********************************************************************
Main:
	;***Initialize***

    ;set stack
.ifdef SPH
	ldi rTemp, HIGH(RAMEND) 	;for AtTiny45/85
    out SPH,rTemp 			
.endif
    ldi rTemp, LOW(RAMEND) 	
    out SPL,rTemp 

	ldi rTemp,(1<<PCIE)		;enable PIN change interrupts
	out GIMSK,rTemp
	
	ldi rTemp,(1<<PCINT4)		;turn on pin change int on PB4 (keyboard clock)
	out PCMSK,rTemp			

	ldi rTemp,(1<<PCIF)		;clear any pending interrupts
	out GIFR,rTemp

	ldi rTemp,(1<<PB1)		;Set MISO as output
	out DDRB,rTemp			

	ldi rTemp,(1<<OCIE1A)		;activate int on compare match A
	out TIMSK,rTemp	

	ldi rTemp,4
	out OCR1A,rTemp			;delay about 2ms

	out TCNT1,r0			;clear timer

	ldi rBitCount,11
	clr rEdge
	clr rData
	clr rState
	clr rTxData

	clr r0				;always zero in all code
	mov r1,r0
	inc r1				;always one in all code
	mov r2,r1
	inc r2				;always two in all code
	mov r3,r2
	inc r3				;always three in all code

	ldi XL,low(buffer)	;set tail pointer
	ldi XH,high(buffer)
	movw YL,XL			;set head pointer
	
	in rTemp,SREG
	ori rTemp,0x80
	out SREG,rTemp		;enable global interrupts
	

	;*************************
	; Main loop             
	;*************************
loop:
	cp rState,r1
	breq loop

ready:
	;detect start condition
	in rTemp,PINB
	andi rTemp,0b00000101
	cpi rTemp, 0b00000001
	brne loop

	;wait for end of start condition
loop2:
	in rTemp,PINB
	andi rTemp,0b00000101
	cpi rTemp, 0b00000100
	brne loop2

	mov rState,r1

	ldi rTemp, (1<<USIWM0)+(1<<USICS1)+(1<<USICS0)+(1<<USIOIE)
	out USICR,rTemp		;enable SPI 3-wire slave mode, negative/falling clock, tx complete interrupt
		
	ldi rTemp,(1<<USIOIF)
	out USISR,rTemp

	out USIDR,r0

	cp XL,YL		;data in the ring buffer?
	breq loop

	ld rTemp,X+		;read from tail
	andi XL,0x7f	;wrap pointer to 32
	ori XL,0x60
	out USIDR,rTemp
	rjmp loop


;********************************************************************
; Keyboard clock interrupt
; -------------------------------------------------------------------
; This interrupt is executed when the keyboard pulses its clock
; line to sends data. 
;********************************************************************
PCINT0_vect:
	in rIntSregSave,SREG

	cpi rEdge,0
	brne rising_edge	
	
	;falling edge
	ldi rIntTemp,(1<<CS13)+(1<<CS12)+(1<<CS11)+(1<<CS10) ;set timer 1 to clk/16384 (1ms)
	out TCCR1,rIntTemp

	cpi rBitCount,11
	brsh skip_bits		;skip start bit
	cpi rBitCount,3
	brlo skip_bits		;skip parity and stop bit

	lsr rData
	sbic PINB,3			;data bit set?
	ori rData,0x80	
skip_bits:	
	ldi rEdge,1			;set interrupt on rising edge 
	rjmp pcint0_end
	
rising_edge:	
	ldi rEdge,0			;set interrupt on falling edge 
	dec rBitCount
	brne pcint0_end
		
	st Y+,rData			;all bits received, store in ring buffer
	andi YL,0x7f		;wrap pointer to 32
	ori YL,0x60

	ldi rBitCount,11

	out TCCR1,r0		;stop timer1
	out TCNT1,r0		;clear timer1
	out GTCCR,r2		;clear prescaler
		
pcint0_end:
	out SREG,rIntSregSave
	reti

;********************************************************************
; SPI transfer complete interrupt
; -------------------------------------------------------------------
; This interrupt is executed when the host has completed a SPI
; transfer. Commands are read from the host and available scan codes 
; data will be put on the SPI data register for the next transfer.
; SPI bus is turned off to avoid having the SNES polling 
; pulsing the clock line sending out/loosing the data.
;********************************************************************
USI_OVF_vect:
	in rIntSregSave,SREG

	in rHostCmd,USIDR
	sbi USISR,USIOIF ;Clear the overflow flag
		
	clr rTxData

	cpi rHostCmd,CMD_SEND_KEY
	brne next_cmd

	cp XL,YL		;data in the ring buffer?
	breq usi_end

	ld rTxData,X+	;read from tail
	andi XL,0x7f	;wrap pointer to 32
	ori XL,0x60	
	rjmp usi_end

next_cmd:
	cpi rHostCmd,CMD_SEND_END
	brne usi_end
	
	;end transmisison
	clr rTxData
	out USICR,r0	;turn off spi
	clr rState

usi_end:
	out USIDR,rTxData
	out SREG,rIntSregSave
	reti


;********************************************************************
; Keyboard read transfer reset
; -------------------------------------------------------------------
; This interrupt is executed if a full transfer (11 bits) of 
; the keyboard is not received within 3ms.
;********************************************************************
TIMER1_COMPA_vect:

	mov rEdge,r0
	ldi rBitCount,11
	out TCCR1,r0		;stop timer1
	out TCNT1,r0		;clear timer1
	out GTCCR,r2		;clear prescaler

	reti
It should be possible to extend upon that to handle other things as well. Like on Uzebox128 there never was a final answer how a mouse would work, or how we could use a keyboard and controller in port 2(I didn't want to have to add switches, or unplug the controller to use the keyboard there, nor drop 2 player games outright). I am all for it, just not sure how to do it.

Post Reply

Who is online

Users browsing this forum: No registered users and 18 guests