Forum: Mikrocontroller und Digitale Elektronik SNES Controller auslesen mit AVR Atmega88


von Michael F. (prunebutt)


Angehängte Dateien:

Lesenswert?

Hallo, Leute!

Ich bin immernoch am Einsteigen mit Assemblerprogrammierung und wollte 
ein bisschen üben, indem ich den SNES-Controller auslese.

Das Schema, wie die Daten abgegriffen werden ist folgendes (siehe: 
Anhang 1):

SNES schickt 12μs das Latch Signal auf 1. Der Controller weiß: Abfrage 
beginnt.
Während des Latch Signals wird erster Button geprüft und in ein 
Schieberegister geschoben. Wenn er gedrückt ist, geht die Verbindung auf 
Ground.
Nach dem Latchsignal kommen 6μs Pause. Danach geht für 6μs das 
Clocksignal auf 1 und dann wieder für 6μs auf 0, während der nächste 
Button geprüft wird.
die letzten Schritte werden wiederholt, bis alle Buttons ausgelesen sind 
und 8 Bits (12 für den SNES) erhalten wurden. Dann geht der Spaß von 
vorne los.

Ich weiß noch nicht (abgesehen vom ewigen NOC-wiederholen), wie ich 
meinen 4MHz μController runterregeln kann, dachte aber, dass es ja auch 
mit 250ns Schritten gehen müsste, weil der μController ja mit der 
Clockleitung das Timing selbst festlegt.

Jetzt hab ich versucht, die ersten beiden Knöpfe auszulesen, aber es 
blinkt nur die zweite LED (es war geplant, dass für jeden Knopf eine LED 
blinkt). Was mach ich falsch?

Das Schieberegister (ich hab keins) hab ich versucht mit LSL zu 
simulieren.

Mein Code:
1
.include "m88def.inc"
2
;PORTD ist LEDs
3
;PORTC ist DATA eingang
4
;PORTB0 ist Latch, PORTB1 ist CLK
5
.DEF STANDARDREGISTER = R16
6
.DEF BUTTONS = R17
7
.DEF BUTTON = R18
8
.DEF MULTIPLIKATOR = R19
9
10
ldi STANDARDREGISTER, 0xFF
11
out DDRB, STANDARDREGISTER; PORTB auf Ausgang
12
13
out DDRC, STANDARDREGISTER; PORTC auf Eingang
14
15
out DDRD, STANDARDREGISTER; PORTD auf Ausgang
16
17
schleife:
18
ldi BUTTONS, 0x00; 0,25 Buttons auf 0 setzen
19
sbi PORTB, PORTB0; 0,5 µS Latch auf 1
20
in BUTTON, PORTC; A holen
21
cbi PORTB, PORTB0; Latch auf 0
22
com BUTTON; Button Komplementär
23
add BUTTONS, BUTTON;
24
lsl BUTTONS; A geholt
25
26
sbi PORTB, PORTB1; CLK auf 1
27
in BUTTON, PORTC; B holen
28
cbi PORTB, PORTB1; CLK auf 0
29
com BUTTON
30
add BUTTONS, BUTTON;
31
lsl BUTTONS;
32
33
com BUTTONS
34
out PORTD, BUTTONS
35
36
rjmp schleife

Danke schonmal im Voraus!

Genauere Erklärung, wie der (S)NES Controller die Daten abgreift:
http://www.mit.edu/~tarvizo/nes-controller.html

von Oliver J. (skriptkiddy)


Lesenswert?

Michael F. schrieb:
> Ich weiß noch nicht (abgesehen vom ewigen NOC-wiederholen), wie ich
> meinen 4MHz μController runterregeln kann

Mit Verzögerungsschleifen.

Gruß Oliver

von Michael F. (prunebutt)


Lesenswert?

Aber ist es nicht eigentlich egal, wie schnell die Clock-Takte kommen?

von Jonathan S. (joni-st) Benutzerseite


Lesenswert?

Michael F. schrieb:
> Aber ist es nicht eigentlich egal, wie schnell die Clock-Takte kommen?

Nein, weil dein SNES-Controller dann nicht mehr hinterherkommt.

von Matthias (Gast)


Lesenswert?

Als Info:

Ich habe mal NES-Controller mit FPGA ausgelesen und hab auch nach dem 
Timing-Diagramm der oben genannten Seite angefangen zum coden... aber 
das Diagramm war irgendwie komisch. Der NES-Controller besteht 
eigentlich nur aus dem Baustein: 
http://www.zero-soft.com/HW/USB_NES_old/CD4021.pdf

Da steht wenigstens beschrieben wie das mit den Flanken ist...

ohne NOC (= nop, oder?) könntest (wenn schon etwas Erfahrung in µC) eine 
timerinterruptgesteuerte Statemachine verwenden, wo man in den States 
den nächsten Wiederaufrufzeitpunkt festlegt. Oder eben über eine 
schleife in der du bis zu einem gewissen Wert zählst...

Zum ASM-Code: Da kommt mir mehreres komisch vor: Was genau liest Du da 
am PortC ein? Immer den ganzen und nicht nur einen Pin? Das mit 
komplementär und dann addieren und dann linksshift?
Wie bereits zuvor gesagt: Das lauft so viel zu schnell.

von Michael F. (prunebutt)


Lesenswert?

Ok, um ehrlich zu sein bin ich ein bisschen überwältigt von deinem 
Datenblatt, kurz: Ich kann das meiste davon nicht lesen, weil ich noch 
zu viel Bahnhuof verstehe. Ich hätte gehofft, dass mich solche Projekte 
ein bisschen näher dazu bringen.

Wie ich eine Statemachine in Assembler umsetze hab ich auch noch keinen 
Schimmer und mit Interrupts hab ich auch noch nicht gearbeitet. Ich 
dachte, das Signal sei recht leicht einzulesen und das Tut auf der Seite 
geht mir zu schnell von einem Punkt zum nächsten ohne Übung dazwischen. 
Ich brauch aber Übung. Leider bin ich recht schlecht darin mir selbst 
Projekte zu schaffen, weil diese meist zu hoch gesteckt sind, aber was 
solls. Ich werde es wohl mit einem Zähler machen, um das Programm 
abzubremsen.

Wann sollte ich aber dann den Wert für den gedrückten Knopf auslesen? Am 
Anfang des 12µs runterlaufen lassens, in der Mitte, oder am Ende?

Zum Code:
Ich hätte ihn ein bisschen aufräumen müssen, fällt mir grad auf. Wenn 
man noch so wackelig auf den neuen Assemblerbeinen rumstolpert kommt 
leider immer ein bisschen peinlicher Codebrei raus.

Ich hatte vor, ein Register mit dem aktuellen gedrückten Knopf und nur 
diesem zu füllen (register BUTTON). Anfangs wollte ich zur Kommunikation 
mit dem Joypad den DDRB auf 0b11000000 setzen. Dass ich DDRB, DDRC und 
DDRD auf 0xFF gesetzt habe war ein dummes Versehen und ist mir gerade 
gehörig peinlich...
Jedenfalls wollte ich aber BUTTON nur mit dem einem Bit des 
Data-Anschlusses laden. Ich habe aber nicht gewusst, wie ich beim 8 Bit 
Register das least significant Bit mit nur einem Portbit legen kann (ich 
dachte erst an sbic/sbis, aber das hätte das Timing inkonsistent 
gemacht). Da hab ich einfach gedacht scheiß drauf! Nimm gleich einen 
anderen PORT her. Ist nicht sonderlich ökonomisch, der Gedanke, aber ich 
wollte einfach, dass es funktioniert und dann optimiert wird.

Da es für mich angenehmer war, mit der Vorstellung 1=an/gedrückt zu 
arbeiten kam ich auf den Gedankenschritt:
Ich habe ein Byte auf dem Register BUTTON, wobei das lsb den 
Knopfzustand widerspiegelt (1=gedrückt, 0=nicht gedrückt, deswegen das 
erste com).
Addiere BUTTON mit BUTTONS (gesamte Knöpfe).
Verschiebe BUTTONS nach links und fang von vorne an.

Wenn fertig: Komplementär bilden und ausgeben (damit wieder 0=gedrückt 
und 1=nicht gedrückt herrscht, was die LEDs ja wollen)

Mir fällt auch gerade auf, dass BUTTON mit dem msb statt dem lsb den 
Knopfzustand zugewiesen bekommt. Muss ich auch ändern.


Es ist mir unheimlich peinlich, dermassen chaotischen Code hier 
reinzustellen. Ich hoffe, es wird einem Anfänger verziehen...

von Matthias (Gast)


Lesenswert?

Ja, aller Anfang ist schwer. da braucht einem nix peinlich sein und 
Datenblätter sind zu Beginn ein bißchen kryptisch... das wird schon!

Das wichtigste aus dem Datenblatt ist meiner Meinung die Info

"When the parallel/serial control input is in the logical “0”
state, data is serially shifted into the register synchronously
with the positive transition of the clock. When the parallel/
serial control is in the logical “1” state, data is jammed into
each stage of the register asynchronously with the clock."

Die Zeichnung auf Seite 2 könnte auch noch nützlich sein: Serial_In 
dürfte auf '1' gelegt sein. Die Daten die man bekommt kommen von Ausgang 
Q8. Unten in der Tabelle siehst Du dann zB die Angabe f_CL (maximum 
input frequency) mit 3.5 MHz.

Das kennst du wahrscheinlich eh: http://www.atmel.com/Images/doc0856.pdf

Da steht zB wie man ein einzelnes Bit einliest(BST).
So wie Du es machen willst geht es, nur muss da der Rest von PORTC auch 
auf einen definierten Wert liegen. Dein Prinzip ist ja so: Wenn A 
gedrückt war solltest du zB 0b11111110 einlesen. Dann kannst du COM 
darauf anwenden damit es zu 0b00000001 wird. Zu Beginn ist BUTTONS auf 
0x00. dann addierst du und bekommst in BUTTONS den Wert 0b00000001. dann 
shiften mit LSL 0b00000010... usw. Das ist eh gut soweit, Falls PORTC an 
den anderen pins keinen definierten Wert hat, kannst ja verunden mit 
0x01. Hast Du wahrscheinlich eh passende Werte anliegen, weil sonst ja 
die ADD dir die alten Buttonwerte ändern würde.
Mit einem Zähler damit du Timings einhalten kannst, solltest Du schon 
irgendwas bekommen. Ich würde gleich alle Buttons einlesen dann kann man 
besser schauen, ob und wie die Daten verschoben empfangen wurden im 
Falle, dass man die falsche Flanke zum Einlesen benutzt.

LG, Matthias

von Christian S. (gordon--)


Lesenswert?

Falls Interesse besteht, ich habe soetwas auch mal gebaut. Allerdings 
mit einem NES-Pad und einem ATtiny 2313. Wie schon erwähnt wurde enthält 
das NES-Pad nicht viel mehr als ein 4021, wie ich gelesenhabe sollen im 
SNES-Pad zwei 4021 kaskadiert worden sein, also auf die gleiche Art und 
Weise auszulesen. Wenn du möchtest kann ich dir meinen Code mal posten 
wenn ich zu hause bin, vielleicht hilft dir das weiter. Wenn du aber 
nicht alles vorgekaut haben möchest bin ich dir gern bei einzelnen 
Fragen behilflich. Den Controller habe ich in meinem Fall nicht 
gedrosselt, er läuft auf 1 MHz mittels internem Oszillator.

von Michael F. (prunebutt)


Lesenswert?

@Matthias
Ach so! jetzt ist einiges klarer!
Ich hatte aber eigentlich vorerst nur vor, den Controller nicht 
aufzuschrauben und gemäß dem Diagramm, wie es das (S)NES macht die Datem 
vom Stecker selbst auslesen.

@Christian
Danke für das Angebot! Ich würde gerne noch selbst ein bisschen tüfteln 
und wenn ich dann immernoch auf keinen grünen Zweig komme, nehme ich das 
Angebot gerne an.^^

Ich bin übrigens auf einen Algorithmus gekommen, wie ich den aktuellen 
Knopf als MSB in ein improvisiertes Shiftregister einbringen kann (auch 
mit 0 bedeutet an):

Register Knopf holt den aktuellen Knopf, Register Buttons speichert alle 
gedrückten Knöpfe (oder zumindest acht davon)

ldi Buttons, 0xFF
and Buttons, Knopf

[circular Shift left] Buttons (der wird durchgeführt durch ein rotate 
left through carry bei jedem Knopfdrücken und zum Schluss noch ein mal)

Fertig.

Den Code hab ich mal angepasst, inklusive Timing. Es funktioniert leider 
immernoch nicht.
1
.include "m88def.inc"
2
;PORTD ist LEDs
3
;PORTC ist DATA eingang
4
;PORTB0 ist Latch, PORTB1 ist CLK
5
.DEF TEMP = R16
6
.DEF BUTTONS = R17
7
.DEF BUTTON = R18
8
.DEF COUNTER = R19
9
.MACRO Warte3
10
  nop
11
  nop
12
  nop
13
.ENDMACRO
14
15
16
ldi TEMP, HIGH(RAMEND)
17
out SPH,TEMP
18
ldi TEMP, LOW(RAMEND)
19
out SPL,TEMP; Stack für rcall vorbereiten
20
21
ldi TEMP, 0x00
22
out DDRC, TEMP; PORTC auf Eingang
23
24
ldi TEMP, 0xFF
25
out DDRB, TEMP; PORTB auf Ausgang
26
out DDRD, TEMP; PORTD auf Ausgang
27
ldi TEMP, 0b01111111
28
out PORTC, TEMP; PORTC0 auf Eingang
29
30
31
schleife:
32
ldi BUTTONS, 0xFF; Buttons auf 1 setzen    
33
ldi COUNTER, 7
34
sbi PORTB, PORTB0; Latch auf 1  
35
rcall delay12us      
36
cbi PORTB, PORTB0; Latch auf 0
37
in BUTTON, PORTC; A holen
38
and BUTTONS, BUTTON; A geholt
39
ror BUTTONS; BUTTONS weitergeschoben
40
rcall delay6us
41
warte3
42
holen:
43
sbi PORTB, PORTB1; CLK auf 1
44
Warte3
45
rcall delay6us
46
warte3
47
cbi PORTB, PORTB1; CLK auf 0
48
in BUTTON, PORTC; B holen
49
and BUTTONS, BUTTON;
50
ror BUTTONS;
51
rcall delay6us
52
dec COUNTER
53
brne holen
54
55
ror BUTTONS
56
out PORTD, BUTTONS
57
58
rjmp schleife
59
60
delay12us:
61
ldi temp, 12
62
mov R1, temp
63
count1:
64
dec R1
65
brne count1
66
ret
67
68
delay6us:
69
ldi temp, 3
70
mov R1, temp
71
count2:
72
dec R1
73
brne count2
74
nop
75
nop
76
ret

von Matthias (Gast)


Lesenswert?

Michael F. schrieb:
> @Matthias
> Ach so! jetzt ist einiges klarer!
> Ich hatte aber eigentlich vorerst nur vor, den Controller nicht
> aufzuschrauben und gemäß dem Diagramm, wie es das (S)NES macht die Datem
> vom Stecker selbst auslesen.

Brauchst nicht aufschrauben. Die Leitung geht ja zum Stecker ;-D

Zum Code würde ich  mir mal genau aufschreiben, wie die Werte sind die 
anliegen. ZB wenn Du PORTC einliest: Hast du dann am Port 0x01 anliegen? 
Dann würde ich zuerst mal Testen den Port auf einen Wert zulegen, den 
auslesen und an Leds ausgeben.

Mit LSL und ADD müsste es eh gehen, ginge aber zB auch so:

schreibe in BUTTONS 0x00 ; nur zum Init

read_and_shift:
lese PORTC ein ;Annahme dass Signal an PORTC0 anliegt. wenn gedrückt 0.
schreibe PORTC in Register BUTTON
and  BUTTON,0x01 ;falls PORTC an anderen Stellen ungewünschte Werte hat
or   BUTTONS,BUTTON
lsl  BUTTONS     ; shifte um eins nach links.

Weiters würd ich die Schleife zum Auslesen zB alle 100,200Hz machen und 
den Wert BUTTONS erst immer nachdem alle 8 (im Falle von NES) an die 
LEDS ausgeben. In deinem Beispiel rotiert er ja auch immer, möglich dass 
man da nix sieht (weil zu schnell).

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.