SNES bedeutet Super Nintendo Entertainment System. Dieser Artikel erklärt die Verwendung (Schaltung und Programmierung in C) eines Supernintendo Controllers mit einem AVR.
Inhaltsverzeichnis
Elektronik
Der Controller beherbergt ein 16 Bit Schieberegister. Jede Taste entspricht einem Bit in diesem Register. Wird eine Taste gedrückt, wechselt das entsprechende Bit von 0 auf 1 und bleibt so lange auf 1, bis die Taste losgelassen wird. (In der Datei snes.h wird dieses Register snes_keys genannt)
High Byte (snes_keys & 0xff00):
Bit: | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
Name: | n.c | n.c. | n.c. | n.c. | R | L | X | A |
Low Byte (snes_keys & 0x00ff):
Bit: | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
Name: | B | Y | SELECT | START | RAUF | RUNTER | LINKS | RECHTS |
Anschluss
1 | +5v |
2 | Clock |
3 | Latch |
4 | Data |
5 | nicht angeschlossen |
6 | nicht angeschlossen |
7 | Masse |
Stromversorgung anschließen und Clock, Latch und Data mit dem AVR verbinden. Um möglichst unkompliziert eine sichere Verbindung mit den einzelnen Leitungen herzustellen, empfehle ich, den originalen Anschluss aus einem defekten SNES zu verwenden. Defekte Supernintendos gibts es bei Ebay günstig zu ersteigern.
Auslesen des Registers
Um das Register auszulesen muss der SNES Controller zunächst initialisiert werden:
- Clock und Latch müssen am AVR als Output konfiguriert werden
- Data als Input mit Pullup Widerstand (Bit in DDRx auf 0, Bit in PORTx auf 1)
- Clock auf 1
- Latch auf 0
Anschließend kann die Datenübertragung initialisiert werden. Dieser Schritt muss bei jedem Auslesen des Registers durchgeführt werden.
- Latch auf 1
- 12 Mikrosekunden (im Folgenden "us") warten
- Latch auf 0
Nun können die 16 Bit des Registers wie folgt ausgelesen werden (folgendes also 16 Mal wiederholen):
- 6 us warten
- Clock auf 0
- Data auslesen und zwischenspeichern
- 6us warten
- Clock auf 1
Software
Ich habe einen SNES Controller erfolgreich an einen Arduino (Mega168) angeschlossen und in C (avr-gcc, auf Linux) programmiert. Neben der Ansteuerung enthält mein Code auch eine Routine, welche die Tasten "verlangsamt", damit die Verwendung in Programmen einfacher wird. Soll zum Beispiel die rote A Taste eine LED an- und ausschalten, würde diese ohne Verlangsamung mehrfach ein- und ausgeschaltet werden, bevor ein Mensch die Taste loslassen kann.
snes.h
/* * Copyright (C) 2009 by Tom Vincent Peters * code@ofenrohr.net * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include <avr/io.h> #include <util/delay.h> #include <inttypes.h> /* snes Gamepad Einstellungen */ /* arduino: analog: 0:Clock, 1:Latch, 2:Data */ #define SNES_LATCH_DDR DDRC #define SNES_LATCH_PORT PORTC #define SNES_LATCH_BIT 0x02 #define SNES_CLOCK_DDR DDRC #define SNES_CLOCK_PORT PORTC #define SNES_CLOCK_BIT 0x01 #define SNES_DATA_PORT PORTC #define SNES_DATA_DDR DDRC #define SNES_DATA_PIN PINC #define SNES_DATA_BIT 0x04 #define SNES_REPEAT_DELAY 0x0fff; /* snes Tasten */ #define SNES_KEY_CNT 12 #define SNES_KEY_A 8 #define SNES_KEY_B 7 #define SNES_KEY_X 9 #define SNES_KEY_Y 6 #define SNES_KEY_L 10 #define SNES_KEY_R 11 #define SNES_KEY_UP 3 #define SNES_KEY_DOWN 2 #define SNES_KEY_LEFT 1 #define SNES_KEY_RIGHT 0 #define SNES_KEY_START 4 #define SNES_KEY_SELECT 5 #define SNES_LATCH_LOW() { SNES_LATCH_PORT &= ~(SNES_LATCH_BIT); } #define SNES_LATCH_HIGH() { SNES_LATCH_PORT |= SNES_LATCH_BIT; } #define SNES_CLOCK_LOW() { SNES_CLOCK_PORT &= ~(SNES_CLOCK_BIT); } #define SNES_CLOCK_HIGH() { SNES_CLOCK_PORT |= SNES_CLOCK_BIT; } #define SNES_GET_DATA() (SNES_DATA_PIN & SNES_DATA_BIT) static uint16_t snes_keys; static uint16_t snes_key_repeat_delay[SNES_KEY_CNT]; void snes_init(void); void snes_update(void); uint8_t snes_key_pressed(uint8_t key); void snes_init(void) { SNES_LATCH_DDR |= SNES_LATCH_BIT; SNES_CLOCK_DDR |= SNES_CLOCK_BIT; SNES_DATA_DDR &= ~(SNES_DATA_BIT); SNES_DATA_PORT |= SNES_DATA_BIT; SNES_CLOCK_PORT |= SNES_CLOCK_BIT; SNES_LATCH_PORT &= ~(SNES_LATCH_PORT); } void snes_update(void) { uint8_t i; uint8_t tmp=0; uint16_t ret=0; SNES_LATCH_HIGH(); _delay_us(12); SNES_LATCH_LOW(); for (i=0;i<8;i++) { _delay_us(6); SNES_CLOCK_LOW(); tmp <<= 1; if (!SNES_GET_DATA()) { tmp|=1; } _delay_us(6); SNES_CLOCK_HIGH(); } snes_keys = tmp; for (i=0;i<8;i++) { _delay_us(6); SNES_CLOCK_LOW(); tmp >>= 1; if (!SNES_GET_DATA()) { tmp|=0x80; } _delay_us(6); SNES_CLOCK_HIGH(); } snes_keys |= tmp<<8; for (i=0;i<SNES_KEY_CNT;i++) { if (snes_key_repeat_delay[i]>0) snes_key_repeat_delay[i]--; } } uint8_t snes_key_pressed(uint8_t key) { if (key>SNES_KEY_CNT) { return 0; } if (snes_keys&_BV(key)) { if (snes_key_repeat_delay[key]==0) { snes_key_repeat_delay[key]=SNES_REPEAT_DELAY; return 1; } } return 0; }
main.c
Dieses Beispiel wechselt den Zustand der LED (an / aus) bei Tastendruck auf A oder B des SNES Controllers. Taste B ist verlangsamt, während bei A das Register (snes_keys) direkt ausgelesen wird.
#define F_CPU 16000000L #include <avr/io.h> #include "snes.h" /* Arduino Belegung: * 0-7: PORTD * 8-13: PORTB (LED auf 13) * Analog: PORTC */ /* LED an- bzw. ausschalten */ void toggle_led() { if (PORTB & 0x20) { PORTB &= ~(0x20); } else { PORTB |= 0x20; } } int main(void) { /* init led */ DDRB = 0x20; /* init snes gamepad */ snes_init(); while (1) { /* Register auslesen */ snes_update(); /* Register (snes_keys) auswerten */ /* Tastendruck mit Verzoegerung */ if (snes_key_pressed(SNES_KEY_B)) { toggle_led(); } /* Tastendruck ohne Verzoegerung */ if (snes_keys & _BV(SNES_KEY_A)) { toggle_led(); } } }
Makefile
Da ich auf Linuxumgebungen programmiere, habe ich mir ein Makefile für oft verwendete Befehle geschrieben. Dieses Makefile ist auf ein USB Arduino Board eingestellt. Bei anderen Boards sollten MMCU (AVR Typ) und PROGRAMMER im Makefile angepasst werden. Wenn weitere Header Dateien (*.h) hinzukommen sollten diese als Abhängigkeit in der folgenden Zeile hinzugefügt werden:
$(SRC).o : $(SRC).c snes.h
Eine Erklärung der einzelnen Befehle:
make = Compilieren
make clean = Aufräumen
make upload = Programm auf Arduino laden
make link = Nur notwendig auf Gentoo Systemen (einmal in jedem Projekt Ordner)
make dump = Programm in Assembler ausgeben lassen
make size = Speicherverbrauch des Programms anzeigen
make screen = Verbindung mit Arduino herstellen (USART)
make hexdump = USART Daten in Hex ausgeben
CC = avr-gcc OCP = avr-objcopy MMCU = atmega168 CFLAGS = -Os -g -mmcu=$(MMCU) PORT = /dev/ttyUSB0 BAUD = 19200 PROGRAMMER = stk500v1 TARGET = m168 SRC = main $(SRC).hex : $(SRC).elf $(OCP) -j .text -O ihex $(SRC).elf $(SRC).hex $(SRC).elf : $(SRC).o $(CC) $(SRC).o -o $(SRC).elf -mmcu=$(MMCU) $(SRC).o : $(SRC).c snes.h $(CC) $(SRC).c $(CFLAGS) -c -o $(SRC).o clean : rm *.o *.elf *.hex upload : $(SRC).hex avrdude -C /etc/avrdude.conf -p$(TARGET) -c$(PROGRAMMER) -P$(PORT) -b$(BAUD) -D -Uflash:w:$(SRC).hex -F link : ln -s /usr/lib/binutils/avr/2.19.1/ldscripts/ ldscripts dump : $(SRC).elf avr-objdump -S -d -j .text -j .data $(SRC).elf size : $(SRC).o avr-size -x -A --target=ihex $(SRC).o avr-nm --size-sort -S $(SRC).elf screen : screen /dev/ttyUSB0 9600 hexdump : cat /dev/ttyUSB0 | hexdump