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.
Wireless SNES controllers
- nicksen782
- Posts: 714
- Joined: Wed Feb 01, 2012 8:23 pm
- Location: Detroit, United States
- Contact:
Re: Wireless SNES controllers
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.
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.
- nicksen782
- Posts: 714
- Joined: Wed Feb 01, 2012 8:23 pm
- Location: Detroit, United States
- Contact:
Re: Wireless SNES controllers
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.
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.
Re: Wireless SNES controllers
The wiki page describes the protocol on a high level. The demo program that reads in key scan codes is like this:
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:
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.
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;
}
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
- nicksen782
- Posts: 714
- Joined: Wed Feb 01, 2012 8:23 pm
- Location: Detroit, United States
- Contact:
Re: Wireless SNES controllers
Hey! It's been a while on this one. I'm finally going to publish the code and circuit schematics for my dongle. It corrects the timing for gamepads and allows wireless gamepads to work even on games that were compiled with older kernels. I imagine that this dongle could also be used for keyboards and mice. There is already a micro in it (An ATMEGA328P, not Arduino.) Would that be enough?
Could you do some sort of auto switching thing where the micro presents a game pad for a couple frames, then the keyboard, then the mouse? It could buffer the inputs from the device so there will always be data available. However, this would effectively be like switching devices all the time. Still, if that could be handled somehow you may be able to put 3 devices in one gamepad port and allow for 2 player games.
I know that the kernel has been updated to not need this hardware dongle but it still serves a purpose. First, no guess-work about games working with my wireless game pad. Second, it could allow for an actual PS/2 port. Perhaps the keyboard/mouse dongle and my wireless gamepad dongle could be combined?
Just some thoughts! I'll post here again when I have the project ready for Github.
Could you do some sort of auto switching thing where the micro presents a game pad for a couple frames, then the keyboard, then the mouse? It could buffer the inputs from the device so there will always be data available. However, this would effectively be like switching devices all the time. Still, if that could be handled somehow you may be able to put 3 devices in one gamepad port and allow for 2 player games.
I know that the kernel has been updated to not need this hardware dongle but it still serves a purpose. First, no guess-work about games working with my wireless game pad. Second, it could allow for an actual PS/2 port. Perhaps the keyboard/mouse dongle and my wireless gamepad dongle could be combined?
Just some thoughts! I'll post here again when I have the project ready for Github.
Re: Wireless SNES controllers
You sure can multiplex data from as many devices you want on joypad ports, it's just a question of serialization protocol. The most important part is how you indicate a start and end of transmission. You can see the keyboard firmware for how I did it. After that you can decide that the host pulls one byte with some flags indicating what's available next. I had that grandiose megalomaniac idea of a Uzebus once . It allowed to pull data from keyboard, mouse, UART, and up to 127 other devices using the I2C bus.