Hallo Leute,
bin absolut blutiger Anfänger in Sachen Assembler und möchte euch um Rat
fragen. Vielleicht erhört mich einer von euch :) ...
Ich möchte ganz schlicht und einfach bei einem Atmega8 auf den Portpins
D0 und D1 zwei Schalter einlesen (ein Schalter pro PortPin). An für sich
eigentlich sehr einfach, aber: ES KLAPPT EINFACH NICHT! Habe zum Test
andere Programme ausprobiert, die Taster funktionieren einwandfrei.
Anbei meine .asm Datei, hoffe jemand von euch schaut sich das mal kurz
an. Ist sicher ein einfacher Kniff, den ich noch nicht als Anfänger
kenne...
Danke im Voraus...
Hallo,
ich hab mir deinen Code mal angesehen. Folgende Sachen sind mir
aufgefallen:
> ; Ausmaskieren der Taste 1> ldi r17,0b11111110> out DDRD,r17> in r17,PORTD> ldi r22,0b00000001> eor r17,r22
Die ersten beiden Befehle konfigurieren deinen Port. Ein Pin wird als
Eingang konfiguriert, die anderen als Ausgang. Das ist gefährlich, weil
du damit einen Kurzschluss generieren könntest. Selbst wenn du momentan
nichts an den anderen sieben Pins angeschlossen hast, empfehle ich dir,
nicht benötige Pins IMMER als Eingang zu schalten.
Konkret riskierst du sowieso, deinen Port zu schrotten, weil du ja ZWEI
Taster angeschlossen hast, und durch die Konfiguration als Ausgang
würdest du bei gedrücktem Taster an D1 riskieren, dass es raucht.
Das heisst, dass du alle Ports, die lesen sollen, auch so konfigurierst
(und zwar immer, nicht schrittweise), und die, die du nicht belegt hast,
auch als Eingang lässt (in deinem Fall also DDRD = 00000000). Nur die
Ports, die wirklich Ausgang sein müssen, werden so konfiguriert. Ist
einfach sicherer :-)
Dein IN-Befehl ist nicht ganz richtig, du musst vom PIND-Register lesen
(P_ort__IN), nicht vom Port D Register. Hierzu empfehle ich das
Studium des Datenblattes, Abschnitt IO Ports. Das PIN-Register liefert
die logischen Zustände direkt an den Pins. Das Lesen des PORT-Registers
sagt dir nur, ob bei Eingangskonfiguration die Pull-Ups aktiv sind und
bei Ausgangskonfiguration, ob High oder Low angesteuert wird.
Die Exklusiv-Veroderung (also die letzten beiden Befehle) bringen dir
nichts, weil erstens deine Portkonfiguration falsch ist, und zweitens,
weil du ja den kompletten Port vergleichen würdest. Was würde
passieren, wenn beide Taster aktiv sind? Dann würde der Vergleich in die
Hose gehen.Besser wäre es denke ich, wenn du die einzelnen Bits
abfragst. Das hätte den Vorteil, dass du erstens weniger Code hast (auch
in Zusammenhang mit dem oben genannten) und somit wirds verständlicher,
und dass du nur das prüfst, was du prüfen musst.
Also eher sowas:
; Konfigurieren des Ports
LDI R17,0b00000000
OUT DDRD,R17
; Lesen des Ports
IN r17,PIND
; Prüfen der Tasten
SBRC R17,0 ;Wenn Bit 0 = 0, dann nächsten Befehl überspringen
RJMP PKT1
SBRC R17,1
RJMP PKT2
Die Zeitschleifen sind für deine Anwendung soweit ich das sehen kann,
nicht nötig, also verzichten wir mal darauf, dann wirds wieder etwas
einfacher.
Der Rest deines Programms ist soweit okay, denke ich. Wenn du jetzt z.B.
noch anstatt PKT1, PKT2 LED_ON und LED_OFF schreibst, wirds nochmal
verständlicher. Ausserdem könntest du noch folgendes machen. Du hast
drei Labels. MAIN, MAINL und MAINE.
Von MAIN bis MAINL schreibst du deinen Initialisierungscode, also z.B.
die Portkonfiguration. Dieser ist nur einmal notwendig, also führen wir
ihn auch nur einmal aus.
Ab MAINL beginnt das eigentliche Programm, welches in der Schleife
ausgeführt werden soll (Das L in MAINL steht für LOOP = Schleife).
MAINE ist einfach das Ende der Schleife (E = END). Dein Programm springt
an PKT4 und lässt nochmal eine Zeitschleife laufen, die ja eigentlich
nur ausgeführt werden sollte (obwohl nicht nötig), wenn Taste 2 gedrückt
ist. Daher wäre ein Sprung direkt auf RJMP MAIN geschickter. Du könntest
natürlich anstatt an PKT4 zu springen auch direkt auf MAIN springen,
aber das ist unübersichtlich. Besser ist es, ein Ende zu definieren, und
das Ende anzuspringen. Wenn du mehr Erfahrung hast, wirst du
Unterprogramme schreiben, dort bringt diese Vorgehensweise viel
Übersichtlichkeit und die Fehleranfälligkeit wird vermindert, weil du
das Ende nur einmal definieren musst.
Somit ergibt sich also folgendes Gesamtprogramm:
MAIN:
; Konfigurieren der Ports
LDI R17,0b00000000
OUT DDRD,R17
LDI R17,0b00000001
OUT DDRB,R17
LDI R17,0b00000000 ;LED ausschalten
OUT PORTB,R17
MAINL:
; Lesen des Ports
IN R17,PIND
; Prüfen der Tasten
SBRC R17,0 ;Wenn Bit 0 = 0, dann nächsten Befehl überspringen
RJMP LED_ON
SBRC R17,1 ;Wenn Bit 1 = 0, dann nächsten Befehl überspringen
RJMP LED_OFF
RJMP MAINE
LED_ON:
IN R17,PORTB
ORI R17,0b00000001
OUT PORTB,R17
RJMP MAINE
LED_OFF:
IN R17,PORTB
ANDI R17,0b11111110
OUT PORTB,R17
RJMP MAINE
MAINE:
RJMP MAIN
Die Programmteile fürs Ein- und Ausschalten der LEDs habe ich so
geschrieben, dass wirklich nur die LED beeinflusst wird. Warum? Das ist
vielleicht jetzt noch nicht nötig für dich, aber später wirst du auch
andere Sachen am Port dran haben, und hättest mit der alten Variante
diese anderen Sache mit beeinflusst, und dich dann gewundert :-)
Nicht vergessen, immer nur dass beeinflussen, was beeinflusst werden
soll.
Die Veroderung (ORI-Befehl) bei LED_ON sorgt dafür, dass nur das Bit 0
gesetzt wird, alles andere bleibt wie es ist (vorher wurden die anderen
Bits immer gelöscht).
Das gleiche bei LED_OFF, die Verundung (ANDI) löscht nur das Bit 0,
alles andere bleibt, wie es ist.
Ich weiss, es ist ziemlich viel, was ich geschrieben habe, und mancher
wird vielleicht denken, dass die ganzen Erklärungen nicht nötig sind
oder dass ich völligen Schwachsinn schreibe. Was ich geschrieben habe,
ist z.T. schon fortgeschrittenes Programmieren, aber ich wollte dir
einen Weg zeigen, der immer funktioniert und dich vor manchen
unliebsamen Überraschungen fernhält. Ich betreue unsere Azubis beim
Programmieren, und wenn man nicht von Anfang an einige Grundzüge
anwendet, wird man es später schwer und somit keinen Spass dran haben.
Ich hoffe, ich konnte dir helfen. Leider kann ich das Programm momentan
nicht testen, also halte mich bitte auf dem Laufenden.
Ralf
Ralf wrote:
> Von MAIN bis MAINL schreibst du deinen Initialisierungscode, also z.B.> die Portkonfiguration. Dieser ist nur einmal notwendig, also führen wir> ihn auch nur einmal aus.> MAINE:> RJMP MAIN
Hier sollte es, wie du auch richtigerweise in deiner exzellenten
Einführung geschrieben hast,
RJMP MAINL
heissen.
@engine
Aus genau diesem Grund möchte ich auch noch einen Punkt
hinzufügen: Labelnamen noch konsequenter auf Sinnhaftigkeit
prüfen.
Wenn ein Programmteil eine Initialisierung macht und daher nur
einmal ausgeführt werden soll, dann nenn den Abschnitt auch so
INIT:
; Konfigurieren der Ports
LDI R17,0b00000000
OUT DDRD,R17
LDI R17,0b00000001
OUT DDRB,R17
LDI R17,0b00000000 ;LED ausschalten
OUT PORTB,R17
Nennn es INIT oder CONFIG aber nicht unbedingt MAIN. Dann fällt
dir auch in der Hauptschleife auf, dass da irgendwas nicht
stimmen kann.
MAINE:
RJMP INIT
sieht nun mal seltsam aus, weil eine Initialisierung nur beim
Programmstart ausgeführt werden soll (drum heist sie ja auch
Initialisierung) und nicht ständig bei jedem Durchlauf der
Hauptschleife.
> Hier sollte es, wie du auch richtigerweise in deiner exzellenten> Einführung geschrieben hast,> RJMP MAINL> heissen.argl :-)
Man kanns noch so oft korrekturlesen, einer ist immer drin :-)
Sorry. Wenn ichs im AVR-Studio geschrieben und kopiert hätte, wärs mir
aufgefallen.
Ralf
Hallo Ralf, Hallo Karl-Heinz,
ersteinmal muss und kann ich nur sagen, es gibt noch Hoffnung für diese
Welt!
Wenn ich so sehe, wie ihr mir bei meinem Problem geholfen habt, kann ich
mich nur verbeugen und mich demütig bedanken... DANKE!!! Danke daß ihr
euch Zeit genommen habt...
@ Ralf:
Dadurch, daß du alles sehr penibel und eingehend beschrieben hast, ist
mir alles sofort einleuchtend gewesen! Eigentlich ein absoluter
Anfängerfehler, PORTD anstatt PIND zu nehmen! Auch der Befehl SBRC ist
ein Einfaches.. wenn man ihn kennt :) ! Was hab ich mit EOR gegrübelt,
Port-Zustand sichern, ausmaskieren... manchmal ist ein tieferer Blick in
die Befehlsliste doch sehr wertvoll.
Also, dein Quellcode ist für mich nun verständlich und klar (warum ORI /
ANDI, PORTD anstatt PIND bei Befehl IN bei den Ein-/Ausschaltroutinen
der LED usw). Ich habe ihn direkt übernommen und den Vorschlag von
Karl-Heinz implementiert. Ausserdem die Tastereingang-Ports angepasst
(nur Bit bei SBRC Abfrage geändert). Hier nochmal der Code:
INIT:
; Konfigurieren der Ports
; Port D
LDI R17,0b00000000
OUT DDRD,R17
; Port B
LDI R17,0b00000001
OUT DDRB,R17
LDI R17,0b00000000 ;LED ausschalten
OUT PORTB,R17
MAIN:
; Lesen des Ports
IN R17,PIND
; Prüfen der Tasten
SBRC R17,1 ;Wenn Bit 0 = 0, dann nächsten Befehl überspringen
RJMP LED_ON
SBRC R17,0 ;Wenn Bit 1 = 0, dann nächsten Befehl überspringen
RJMP LED_OFF
RJMP MAINE
LED_ON:
IN R17,PORTB
ORI R17,0b00000001
OUT PORTB,R17
RJMP MAINE
LED_OFF:
IN R17,PORTB
ANDI R17,0b11111110
OUT PORTB,R17
RJMP MAINE
MAINE:
RJMP MAIN
.include "m8def.inc" ; Definitionen für den ATmega8
Nun zum Ergebnis, auf was ihr sicher schon wartet:
Es funktioniert, aber nur solange, wie ich Taste "Einschalten" drücke!
Taste "Ausschalten" bewirkt nichts, auch gleichzeitig gedrückt (da auch
ausgeschlossen im Quellcode). Ich werde den Abend weiter probieren und
grübeln. Vielleicht bekomme ich ja noch einen Tipp von euch oder finde
etwas, poste dann auch gleich wieder!
Viele Grüsse, Engin
Zusatz:
Mit "es funktioniert nur solange wie ich Taster "Einschalten" drücke"
meine ich, die LED leuchtet solange, wie ich die Taste "Einschalten"
drücke!
Das liegt an meinem (von Karlheinz) erkannten Fehler mit dem RJMP MAIN
--> RJMP MAINL!!! Weil in der Initialisierung PortB gelöscht wird, und
die Initialisierung durch den Fehler immer wieder aufgerufen wird.
Ralf
So! Es ist vollbracht! Ich habe des Rätsels Lösung:
INIT:
; Konfigurieren der Ports
; Port D
LDI R17,0b00000000
OUT DDRD,R17
; Port B
LDI R17,0b00000001
OUT DDRB,R17
LDI R17,0b00000001 ;LED ausschalten
OUT PORTB,R17
MAIN:
; Lesen des Ports
IN R17,PIND
; Prüfen der Tasten
SBRS R17,1 ;Wenn Bit 1 = 0, dann nächsten Befehl überspringen
RJMP LED_ON
SBRS R17,0 ;Wenn Bit 0 = 0, dann nächsten Befehl überspringen
RJMP LED_OFF
RJMP MAINE
LED_ON:
IN R17,PORTB
ANDI R17,0b11111110
OUT PORTB,R17
RJMP MAINE
LED_OFF:
IN R17,PORTB
ORI R17,0b00000001
OUT PORTB,R17
RJMP MAINE
MAINE:
RJMP MAIN
.include "m8def.inc" ; Definitionen für den ATmega8
Es lag ganz einfach daran, daß die LED einen LOW-Pegel benötigt, um "on"
zu sein! Sprich, mit dem LOW-Pegel wird eine Masse geschalten. Das war
das Problem! Dadurch haben die ANDI und ORI Befehle auch nichtmehr
gepasst bzw. die Initialisierung des PORT B am Anfang. Habe nun alles
angepasst... Was lerne ich daraus? Erst lesen (einstudieren), dann
programmieren!!! :)
engine wrote:
> Es lag ganz einfach daran, daß die LED einen LOW-Pegel benötigt, um "on"> zu sein! Sprich, mit dem LOW-Pegel wird eine Masse geschalten. Das war> das Problem!
Das ist sicherlich auch ein Problem.
Dass aber der Zustand nicht gehalten wurde, liegt aber eher
an dieser Korrektur
; Prüfen der Tasten
SBRS R17,1 ;Wenn Bit 1 = 0, dann nächsten Befehl überspringen
RJMP LED_ON
SBRS R17,0 ;Wenn Bit 0 = 0, dann nächsten Befehl überspringen
RJMP LED_OFF
Du solltest hier aber den Kommentar noch anpassen. Der passt
jetzt nicht mehr zu den Befehlen.
@Engine:
Freut mich, dass es nun funktioniert. Wollte eh mal nachfragen, ob du
die Schaltung posten kannst, dann wären wir dann auch draufgekommen.
Also als kleinen Tip: Bei der nächsten Frage bitte Schaltplan dazu, oder
wenigstens beschreiben, wie was angeschlossen ist, dann wirds einfacher
für alle :-)
Ralf
Hallo Ralf, Hallo Karl-Heinz,
danke nochmal für die Tipps. Habe soweit alles angepasst und das
Programmchen abgespeichert :)
Hat mich jetzt in den Bann gezogen das Ganze! Ausserdem muss ichs eh für
mein Studium pauken, passt ja! Jetzt bin ich dabei, mal den Lautsprecher
zu testen. Will einen Ton ausgeben über den Lautsprecher, z.B. 500Hz.
Werde versuchen, die Frequenz des 4MHz Oszillators (extern) mittels
einem Counter runterzuteilen... was meinst ihr, bin ich richtig, oder
auf dem Holzweg? Wäre für Tipps dankbar!
Grüsse und weiterhin gutes Gelingen!!!
engine wrote:
> mein Studium pauken, passt ja! Jetzt bin ich dabei, mal den Lautsprecher> zu testen. Will einen Ton ausgeben über den Lautsprecher, z.B. 500Hz.> Werde versuchen, die Frequenz des 4MHz Oszillators (extern) mittels> einem Counter runterzuteilen... was meinst ihr, bin ich richtig, oder> auf dem Holzweg? Wäre für Tipps dankbar!
Timer ist schon ok.
Das kann man so machen.
Schau ins AVR-Tutorial. Im Prinzip kannst du dafür den Timer-
Beitrag hernehmen. Auch der Uhren-Beitrag ist im Grunde nichts
anderes.