Aus RN-Wissen.de
Wechseln zu: Navigation, Suche


Ein Supernintendo Controller

SNES bedeutet Super Nintendo Entertainment System. Dieser Artikel erklärt die Verwendung (Schaltung und Programmierung in C) eines Supernintendo Controllers mit einem AVR.

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

Snes controller pinout.jpg

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