Forum: Mikrocontroller und Digitale Elektronik ATmega8 Fehler in 2 Sekundenzähler?


von Olli R. (downunderthunder42)


Angehängte Dateien:

Lesenswert?

Hallo,

ich habe ein Problem mit einer Subroutine in meinem Programm für eine 
Uhr

Also:

Ich wollte meine Uhr manuell stellen. --> Das funktioniert!


Ich wollte, dass das Stellen der Uhr erst aktiviert wird, wenn ich
zuerst mindestens 2mal Taster 2
und danach 2 Taster 1 gedrückt habe. --> Das funktioniert auch1


Zu letzterem wollte ich nun eine Erweiterung machen, die quasi dafür 
sorgt, dass die Abfolge "2mal Taste2 dann 2mal Taste1" innerhalb von 
2Sekunden geschehen muss.

Dazu habe ich die angefügte Subroutine geschrieben.

Der Beginn der 2 Sekunden wird durch das erste Drücken von Taster 2 
markiert.
--> Bit 3 in "ind_flag2" wird gesetzt (habe ich überprüft wird auch 
tatsächlich gesetzt)

Dann soll wenn Bit 2 von "ind_flag2" nicht gesetzt ist (wenn es gesetzt 
ist, ist die Uhr bereits im "Stellmodus")

und Bit 1 in "ind_flag" gesetzt ist. (wird von einer ISR alle 10ms 
gesetzt und später von einer folgenden Subroutine zurückgesetzt)

ein Zähler zu laufen beginnen, der nach 2 Sekunden:

- Bit3 von "ind_flag2" zurücksetzt (damit der zähler nicht nochmal 
zählt)
- 2 Zustandszähler zurücksetzt (für die Subroutine mit dem 2mal Taste2 
dann 2mal taste1)

- und die Zustandszähler dieser Subroutine zurücksetzt 
("reg100_2sec_reset" wird mit 100 initialisiert und "reg2_2sec_reset" 
wird mit 2 initialisiert)


Leider funktioniert das nicht?

Ich kann leider keine Fehler in der Routine entdecken. Stimmt da was mit 
den Zustandszählern nicht?

von Peter D. (peda)


Lesenswert?

Olli R. schrieb:
> Ich wollte, dass das Stellen der Uhr erst aktiviert wird, wenn ich
> zuerst mindestens 2mal Taster 2
> und danach 2 Taster 1 gedrückt habe.

Das ist ja ne höllisch komplizierte Bedienung. Damit stellst Du sicher, 
daß kein anderer außer Dir das Gerät bedienen kann.

Ich mach das so, daß man eine Taste lange drücken muß, um in den 
Stellmodus zu kommen. Das reicht völlig, um eine Fehlbedienung zu 
vermeiden. Außerdem kann man ja den Stellmodus verlassen, ohne eine 
Ziffer zu ändern.


Ansonsten würde ich Dir raten, erstmal in Worten einen 
Programmablaufplan zu schreiben und nicht so drauflos zu programmieren.
Du hast zwar jede Zeile kommentiert, aber ich kann nirgends einen roten 
Faden erkennen. Ich sehe da jedenfalls nicht durch.


Peter

von Klaus 2. (klaus2m5)


Lesenswert?

Olli R. schrieb:
> Zu letzterem wollte ich nun eine Erweiterung machen, die quasi dafür
> sorgt, dass die Abfolge "2mal Taste2 dann 2mal Taste1" innerhalb von
> 2Sekunden geschehen muss.

Also 120 Anschläge pro Sekunde? Dafür musst Du nicht unbedingt 
Sekretärin sein, aber doch ne ordentliche Tastatur haben.

Lass ne LED die Wartezeit anzeigen und schau, ob Du die letzte Taste 
noch rechtzeitig drückst.

von Klaus2m5 (Gast)


Lesenswert?

Sorry, 120 Anschläge pro Minute natürlich

von Olli R. (downunderthunder42)


Lesenswert?

Peter Dannegger schrieb:
> Ich mach das so, daß man eine Taste lange drücken muß, um in den
> Stellmodus zu kommen. Das reicht völlig, um eine Fehlbedienung zu
> vermeiden. Außerdem kann man ja den Stellmodus verlassen, ohne eine
> Ziffer zu ändern.

ich nutze ja im wesentlichen deine Routine zum Einlesen der Taster!

Beitrag "Taster entprellen funktion "get8key" ?"

deswegen ist es ja nicht so ohne weiteres mögliche in den Stellmodus zu 
wechseln, indem man nur eine Taste lange (z.B. 3s lang) gedrückt hält!

Denn diese Routine gibt mir ja ein byte "key_press" und das zur Taste 
gehörige Bit wird in diesem Byte gesetzt, wenn die Taste "einmal" 
gedrückt wurde.
Ein Halten der Taste kann ich also gar nicht so ohne weiteres Erkennen?
oder doch?

von oldmax (Gast)


Lesenswert?

Hi
Klar kannst du erkennen, wie lange eine Taste edrückt ist! Wie ich sehe, 
programmierst du in asm. also versuch ich mal, dir auf die Sprünge zu 
helfen... Deklariere eine Variable In_Byte ( z.B.) und lese diese als 
erstes in deiner Programmschleife ein, getreu nach EVA.
("E"inlesen, "V"erarbeiten, "A"usgeben)
Dann baust du dir eine Timer-ISR und in dieser prüfst du, ob dein 
Eingang "gesetzt" ist. Ist dies der FAll, zälst du eine Variable hoch. 
Wenn du das alle 100mSek. tust, hast du bei 20 die 2 Sek. Haltezeit. Ist 
das Signal "nicht gesetzt", setzt du den Zähler einfach auf 0
1
Timer_ISR: 
2
   LDS  Reg_A, msek_0        ; Auflösung Millisekunden
3
   INC  Reg_A
4
   STS  mSek_0, Reg_A        ; zurückspeichern
5
   CPI  Reg_A, 10
6
   BRLO End_ISR              ; prüfen ob kleiner 10
7
; ------- 10 mSek
8
   CLR  Reg_A                ; wenn nicht , zurücksetzen
9
   STS  mSek_0, Reg_A        ; und zurückspeichern
10
   LDS  Reg_A, msek_1        ; nun Millisekunden x 10 
11
   INC  Reg_A
12
   STS  mSek_1, Reg_A        ; zurückspeichern
13
   CPI  Reg_A, 10
14
   BRLO End_ISR              ; prüfen ob kleiner 10
15
; ------- 100 mSek
16
   CLR  Reg_A                ; wenn nicht , zurücksetzen
17
   STS  mSek_1, Reg_A        ; und zurückspeichern
18
   
19
   RCALL Press_Event_Time    ; hier rufst du deine Routine auf, die 
20
                             ; die Länge des Tastendrucks prüft
21
                         
22
   LDS  Reg_A, msek_2        ; nun Millisekunden x 100 
23
   INC  Reg_A
24
   STS  mSek_2, Reg_A        ; zurückspeichern
25
   CPI  Reg_A, 10
26
   BRLO End_ISR     
27
; ------- Sekunden
28
    etc.......
29
30
31
RETI
32
33
Press_Event_Time :
34
    Push Reg_A               ; Register sichern, weil Aufruf aus ISR
35
    LDS  Reg_A, In_Byte      ; Eingelesenen Wert  laden
36
    ANDI Reg_A, 0b00010000   ; Relevanten Eingang ausmaskieren
37
    BREQ Time_Cnt_Res        ; wenn nicht, Zeitzähler rücksetzen
38
    LDS  Reg_A, Time_Cnt_x   ; zugehörigen Zeitzähler laden
39
    Inc  Reg_A
40
    CPI  Reg_A, 20           ; 2 Sek. erreicht
41
    BRLO Noch_Warten         ;
42
    LDI  Reg_A, 20 
43
    STS  Time_Cnt_x, Reg_A   ; auf Wert 20 halten
44
    LDS  Reg_A, Prg_Ctrl     ; Ein Controllbyte laden
45
    ORI  Reg_A, 0b00000001   ; Bearbeitungsbit setzen
46
                             ; Wird im Programm abgefragt und bearbeitet
47
    STS  Prg_Ctrl, Reg_A
48
    RJMP End_Events
49
Noch_Warten:
50
    STS  Time_Cnt_x, Reg_A
51
End_Events: 
52
    POP  Reg_A               ; Register wieder herstellen
53
RET                          ; kein RETI, da nur Unterprogram
Natürlich mußt du in der Timer-ISR auch noch die Register sichern, die 
du brauchst, auch das Statusregister, aber das findest du hier in den 
Tutorials über den Timer. Sicherlich ist dieses noch ausbaufähig, aber 
soll dir ja auch nur einen möglichen Weg zeigen.
Gruß oldmax

von Peter D. (peda)


Lesenswert?

Olli R. schrieb:
> ich nutze ja im wesentlichen deine Routine zum Einlesen der Taster!

http://www.mikrocontroller.net/articles/AVR-Tutorial:_Tasten#Tastenentprellung.2C_Abfrage_und_Autorepeat


Ich programmiere jetzt nur noch in C.
Da kann man sich voll auf die eigentliche Aufgabe konzentrieren und muß 
sich nicht mehr Push/Pop, Parameterübergabe, Variablenverwaltung und 
Rechenroutinen abplagen.


Peter

von oldmax (Gast)


Lesenswert?

Hi
@Peter
Hast diesbezüglich  recht, dafür aber auch woanders Probleme. Ich hab, 
was C betrifft, so gut wie gar keine Ahnung und Versuche, einzusteigen, 
sind am "holperigen" Syntax gescheitert. Ich werd in meinem Alter 
vermutlich auch nicht mehr so einfach einsteigen können. Was Push und 
POP angeht, na ja, solche Routinen schreibst du einmal, danach brauchts 
auch keine großartige Überlegung mehr, weil du ja das Rad nicht dauernd 
neu erfindest. Und so oft ruf ich aus einer ISR auch nicht grad 
Unterprogramme auf.
Gruß oldmax

von Olli R. (downunderthunder42)


Lesenswert?

oldmax schrieb:
> Dann baust du dir eine Timer-ISR und in dieser prüfst du, ob dein
> Eingang "gesetzt" ist. Ist dies der FAll, zälst du eine Variable hoch.
> Wenn du das alle 100mSek. tust, hast du bei 20 die 2 Sek. Haltezeit. Ist
> das Signal "nicht gesetzt", setzt du den Zähler einfach auf 0

ja natürlich daran habe ich ja gar nicht mehr gedacht.
Ich hab mich zu sehr einfach auf die bereits implementierte Taster 
einlesen-Routine berufen, dass ich ich gar nicht daran gedacht habe, den 
"Port" der Tasten nochmal einzulesen (sollte ja sogar weniger aufwendig 
sein als eine Routine, die die Taster entprellt!)

Ich werd mal schauen (natürlich mit vorherigem, genauem Überlegen) wie 
ich das hinkriege.


Peter Dannegger schrieb:
> Ich programmiere jetzt nur noch in C.

OK an C werde ich ohnehin nicht ganz vorbei kommen. Das werde ich dann 
mal bei einem neuen Projekt in Angriff nehmen.

Zur Übersichtlichkeit:

Also zu Anfang habe ich doch recht strukturiert gearbeitet. Dann
kamen allerdings immer mehr Erweiterungen hinz zu, die ich "mal eben" 
implementieren wollte.
Ich hätte nicht gedacht, wie schnell einem dann das Ganze fast über den 
Kopf wachsen kann!

Aber ich denke, nachdem ich den aktuellen Schritt (aktivieren des 
Stellen nach 3s Taster 1 drücken) umgesetzt habe, werde ich erst mal 
Ordnung schaffen und die schlimmsten (Unübersichtlichkeiten) ausmerzen.

Fürs nächste Projekt weiss ich jetzt wenigstens, das auch hinter 
vermeintlich geringfügingen Erweiterungen (z.B. bezüglich Einlesen und 
Verarbeitung von Tastern) ne Menge hintersteht und solche Dinge gleich 
in einem Ablaufplan aufnehmen.

von oldmax (Gast)


Lesenswert?

Hi
>ja natürlich daran habe ich ja gar nicht mehr gedacht.
>Ich hab mich zu sehr einfach auf die bereits implementierte Taster
>einlesen-Routine berufen, dass ich ich gar nicht daran gedacht habe, den
>"Port" der Tasten nochmal einzulesen (sollte ja sogar weniger aufwendig
>sein als eine Routine, die die Taster entprellt!)
So hatte ich es eigentlich nicht gemeint....
Also, du hast eine Programmschleife, die nach "EVA" funktionieren sollte
Einlesen Verarbeiten Ausgeben
Am Anfang deiner Schleife liest du die Daten ein. Dazu baust du dir eine 
Routine Read_IO und schreibst die gelesenen Werte in ein Byte Bspw. 
"New_In".
Hier kannst du deine IO-Pins nun im gesamten Programm abfragen, denn 
genau für einen Zyklus sind sie gültig. Liest du deine IO's mehrfach 
ein, so mußt du bei einer Änderung immer im gesamten Programm nach den 
IO Aufrufen suchen. Mit diesem kleinen Unterprogramm weißt du aber, wo 
alles steht und du kannst gezielt deine Ändeungen vornehmen.
Wenn du mal ein wenig suchst, findest du "OpenEye". Ich hab das mal vor 
einiger Zeit hier im Forum veröffentlicht. Dieses Programm liest über RS 
2323 die Variablen aus und gibt deren Inhalte wieder. Ist sehr 
hilfreich, wenn man nicht anders debuggen kann.
An meinem Pollin-Board ist auf ISP der Progger und der serielle Anschluß 
auf dem Com-Port. So kann ich sehr schnell Fehler in meinem 
Programmablauf erkennen.
Also: nicht im Programm verteilt einlesen, sondern in einer 
Speicherzelle den gelesenen Wert für einen Zyklus halten. So kannst du 
dir die Bits auch zusammenstellen und dokumentieren
zB.
New_In:  .Byte 1    ; Bit 0  = PortD.4  "Taster 1"
                    ; Bit 1  = PortD.5  "Taster 2"
                    ; Bit 2  = PortC.0  "Taster 3"
                     usw.

Gruß oldmax

von Peter D. (peda)


Lesenswert?

Olli R. schrieb:
> Ich hab mich zu sehr einfach auf die bereits implementierte Taster
> einlesen-Routine berufen, dass ich ich gar nicht daran gedacht habe, den
> "Port" der Tasten nochmal einzulesen (sollte ja sogar weniger aufwendig
> sein als eine Routine, die die Taster entprellt!)

Nicht den Port. Nimm key_state, daß ist schon entprellt.

Olli R. schrieb:
> Also zu Anfang habe ich doch recht strukturiert gearbeitet. Dann
> kamen allerdings immer mehr Erweiterungen hinz zu, die ich "mal eben"
> implementieren wollte.

Das wichtigste ist, modular zu programmieren. Wenn Du etwas hinzufügen 
willst, verwebe es nicht mit dem bestehenden Code, sondern erstelle eine 
Unterfunktionen.
Der AVR ist ja schließlich kein PIC, wo der Call-Stack auf 2 Ebenen 
limitiert ist.

Für die Verwendung von Unterfunktionen mußt Du Dir aber erstmal ein paar 
Regeln festlegen. Also in welchen Registern werden Argumente übergeben, 
welche Register enthalten Returnwerte und welche Register sind 
Scratchpad (kann die Funktion zerstören ohne umständlich push/pop).

Es ist sinnvoll, sich 4..8 Sctratchpadregister zu definieren, Du wirst 
staunen, wieviel push/pop man dadurch einspart. Denn oftmals übergibt 
man Parameter an eine Funktion, die dann hinterher garnicht mehr 
gebraucht werden.

Wann immer möglich sollte man mit lokalen Variablen (Register) arbeiten, 
globale Variablen so wenig wie möglich. Es hilft Dir, die Übersicht zu 
behalten, Du weißt, daß lokale Variablen nach Funktionsende ungültig 
sind, es kann also keine Seiteneffekte mit anderen Programmteilen geben.


Ein C-Compiler übernimmt das Erstellen solcher Regeln automatisch für 
Dich.


Peter

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.