Forum: Mikrocontroller und Digitale Elektronik Anfänger braucht Hilfe - Taste wird nicht eingelesen


von engine (Gast)


Angehängte Dateien:

Lesenswert?

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...

von dachs (Gast)


Lesenswert?

1. Wie sind denn die Tasten an welche Pins beschaltet?
2. was genau klappt nicht?


Matth

von Ralf (Gast)


Lesenswert?

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

von Karl H. (kbuchegg)


Lesenswert?

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.

von Ralf (Gast)


Lesenswert?

> 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

von engine (Gast)


Lesenswert?

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

von engine (Gast)


Lesenswert?

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!

von Ralf (Gast)


Lesenswert?

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

von Ralf (Gast)


Lesenswert?

Korrektur, du hast es ja schon in INIT umbenannt... Moment, ich grübel 
mal kurz...

Ralf

von engine (Gast)


Lesenswert?

Hallo Ralf,

ok, bin auch noch dran! Bin aber grad noch so schlau wie davor, bin ja 
auch Anfänger :) ...

von engine (Gast)


Lesenswert?

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!!! :)

von Karl H. (kbuchegg)


Lesenswert?

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.

von Ralf (Gast)


Lesenswert?

@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

von engine (Gast)


Lesenswert?

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!!!

von Karl H. (kbuchegg)


Lesenswert?

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.

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.