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:
Michael F. schrieb:> Ich weiß noch nicht (abgesehen vom ewigen NOC-wiederholen), wie ich> meinen 4MHz μController runterregeln kann
Mit Verzögerungsschleifen.
Gruß Oliver
Michael F. schrieb:> Aber ist es nicht eigentlich egal, wie schnell die Clock-Takte kommen?
Nein, weil dein SNES-Controller dann nicht mehr hinterherkommt.
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.
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...
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
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.
@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.
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).