Forum: Mikrocontroller und Digitale Elektronik PIC: Problem mit state-maschine und Reihenfolge der Portabfrage


von musicmiles (Gast)


Lesenswert?

Hallo zusammen,

ich schreibe derzeit ein Programm für einen Mid-Range 
PIC-Mikrocontroller (PIC16F873) in C und benutze dafür MPLABX und den 
kostenfreien Compiler XC8.
Der Mikrocontroller wird extern mit 4MHz getaktet und läuft zuverlässig 
in der Schaltung. Am I2C-Bus hängen ein paar IO-Expander mit denen ich 
verschiedene Stati visualisiere (die Expander steuern LEDs). Am PortB 
sind acht Taster angeschlossen. Alle Taster sind Hardware-Entprellt 
(RC-Glieder und Schmitt-Trigger). Wenn ich die verschiedenen Eingänge 
mit dem Oszilloskop betrachte und einen Taster betätige, sehe ich 
saubere Rechteckimpulse (ca.50ms breit, wenn sehr kurz betätigt). Auch 
im Programmcode lassen sich beliebige Aufgaben mithilfe der externen 
Taster lösen. Soweit funktioniert alles einwandfrei.

Nun habe ich eine kleine State-Maschine programmiert und wundere mich 
über das verhalten des Microcontrollers. In der main-Funktion des 
Programms wird die state-maschine aufgerufen. Die state-maschine liest 
mithilfe der Funktion key_Handler() zuerst alle Eingänge und ein 
generiert aus dem aktuellen Zustand einen Status und arbeitet darauf hin 
die notwendigen (vom Status abhängigen) Aktionen ab.

Nun zu meiner Frage: Warum macht es einen Unterschied, wo ich 
(PORTBbits.RB7==1) Abfrage? Wird der Zustand als erstes Abgefragt, 
registriert das Programm jeden Tastendruck. Wird er als letztes 
abgefragt, wird nur unregelmäßig die zugehörige Aktion ausgeführt. Kann 
mir das jemand beantworten?

Vielen Dank und freundlichen Gruß

musicmiles

Anhang:
Funktion key_handler() und Funktion key_statemaschine()
1
void key_handler()
2
{
3
    if(PORTBbits.RB1==1)
4
    {
5
        state=1;
6
        for(db_cnt = 0; db_cnt <= 10; db_cnt++)
7
        {
8
            __delay_ms(1);
9
            if(PORTBbits.RB1==1)
10
            {
11
                db_cnt = 0;
12
            }
13
        }
14
    }
15
16
    if(PORTBbits.RB2==1)
17
    {
18
        state=2;
19
        for(db_cnt = 0; db_cnt <= 10; db_cnt++)
20
        {
21
            __delay_ms(1);
22
            if(PORTBbits.RB2==1)
23
            {
24
                db_cnt = 0;
25
            }
26
        }
27
    }
28
29
    if(PORTBbits.RB3==1)
30
    {
31
        state=3;
32
        for(db_cnt = 0; db_cnt <= 10; db_cnt++)
33
        {
34
            __delay_ms(1);
35
            if(PORTBbits.RB3==1)
36
            {
37
                db_cnt = 0;
38
            }
39
        }
40
    }
41
42
    if(PORTBbits.RB4==1)
43
    {
44
        state=4;
45
        for(db_cnt = 0; db_cnt <= 10; db_cnt++)
46
        {
47
            __delay_ms(1);
48
            if(PORTBbits.RB4==1)
49
            {
50
                db_cnt = 0;
51
            }
52
        }
53
    }
54
55
    if(PORTBbits.RB5==1)
56
    {
57
        state=5;
58
        for(db_cnt = 0; db_cnt <= 10; db_cnt++)
59
        {
60
            __delay_ms(1);
61
            if(PORTBbits.RB5==1)
62
            {
63
                db_cnt = 0;
64
            }
65
        }
66
    }
67
68
    if(PORTBbits.RB6==1)
69
    {
70
        state=6;
71
        for(db_cnt = 0; db_cnt <= 10; db_cnt++)
72
        {
73
            __delay_ms(1);
74
            if(PORTBbits.RB6==1)
75
            {
76
                db_cnt = 0;
77
            }
78
        }
79
    }
80
81
    if(PORTBbits.RB7==1)
82
    {
83
        state=7;
84
        for(db_cnt = 0; db_cnt <= 10; db_cnt++)
85
        {
86
            __delay_ms(1);
87
            if(PORTBbits.RB7==1)
88
            {
89
                db_cnt = 0;
90
            }
91
        }
92
    }
93
}
94
95
void key_statemaschine()
96
{
97
    key_handler();
98
    switch(state)
99
    {
100
        case 0:break;
101
102
        case 1: Anweisung 1;
103
        break;
104
105
        case 2: Anweisung 2;
106
        break;
107
108
        case 3: Anweisung 3;
109
        break;
110
111
        case 4: Anweisung 4;
112
        break;
113
114
        case 5: Anweisung 5;
115
        break;
116
117
        case 6: Anweisung 6;
118
        break;
119
120
        case 7: Anweisung 7;
121
        break;
122
123
        default:;
124
        break;
125
     }
126
}

von Falk B. (falk)


Lesenswert?

@ musicmiles (Gast)

>Nun habe ich eine kleine State-Maschine programmiert

Kleiner Tipp: Nutze symbolische Namen (#define oder enum) für die 
States, damit wird das Ganze deutlich besser lesbar, siehe 
Statemachine.

>über das verhalten des Microcontrollers. In der main-Funktion des
>Programms wird die state-maschine aufgerufen. Die state-maschine liest
>mithilfe der Funktion key_Handler() zuerst alle Eingänge und ein
>generiert aus dem aktuellen Zustand einen Status und arbeitet darauf hin
>die notwendigen (vom Status abhängigen) Aktionen ab.

Deine Struktur ist ungünstig. Die Aktionen gehören in die State machine. 
Ausserdem macht man eine Tasterentprtellung bzw. Flankenerkennung 
anders. Der Klassiker: Die Entprellroutin von Peter Danneger. ISt zwar 
für AVR, kann man aber leicht auf PIC anpassen.

Siehe Entprellung.

von musicmiles (Gast)


Lesenswert?

Vielen Dank für den Tipp mit den Symbolischen Namen - das werde ich noch 
in Angriff nehmen.

Falk Brunner schrieb:
> Die Aktionen gehören in die State machine.

Was gehört genau in die State maschine? Das Abfragen der Eingänge?

Falk Brunner schrieb:
> Ausserdem macht man eine Tasterentprtellung bzw. Flankenerkennung
> anders. Der Klassiker: Die Entprellroutin von Peter Danneger. ISt zwar
> für AVR, kann man aber leicht auf PIC anpassen.

Das es andere Methoden gibt, dass steht ausser Frage. Allerdings 
interessiert mich, warum es so wie oben beschrieben steht, nicht 
funktioniert. Natürlich kann ich gleich das Programm umbauen und mir 
vorgefertigten Code zunutze machen. Aber ich würde gerne nachvollziehen 
können, warum der PIC die Eingaben nicht mitbekommt.

Gruß

von Karl H. (kbuchegg)


Lesenswert?

musicmiles schrieb:

BIst du sicher, dass deine Tasten im Ruhezustand eine 0 liefern und nur 
wenn gedrückt eine 1?

Wie sind deine Taster verschaltet? Schalten die nach GND oder schalten 
die nach Vcc? Gibt es Pullup bzw. Pulldown Widerstände?


(Ich geh mal davon aus, dass eine gedrückte Taste bei dir eine 1 
liefert)
Dein Code ist insofern ungünstig, weil du grundsätzlich immer nur die 
Taste mit der höchsten Bitnummer akzeptierst. Durch Drücken von RB7 kann 
ich damit alle anderen Tasten lahm legen. Und wenn deine 
Hardware-Entprellung zb wegen offener Eingänge doch nicht so gut 
funktioniert, wie du denkst, dann kann dir ein floatender Eingang mit 
einer Nummer höher als RB1 den RB1 lahmlegen. Vor allen Dingen dann, 
wenn der toggelt wie verrückt. Denn dein Code WILL ja den Eingang 11 mal 
hintereinander (mit jeweils 10ms Wartezeit dazwischen) auf Low sehen, 
ehe der Tastendruck akzeptiert wird. Jeder Toggler an einem Eingang 
startet diese 110ms wieder erneut.

>     if(PORTBbits.RB1==1)
>     {
>         state=1;
>         for(db_cnt = 0; db_cnt <= 10; db_cnt++)
>         {
>             __delay_ms(1);
>             if(PORTBbits.RB1==1)
>             {
>                 db_cnt = 0;
>             }
>         }
>     }

Das ist ziemlich kompliziert geschrieben. Wenn deine Hardware 
Entprellung funktioniert, dann ist das alles recht unnötig. Vor allen 
Dingen die Wartezeiten. Wenn die Hardware Entprellung in Ordnung ist, 
dann ist es so
1
     if( PORTBbits.RB1 == 1 )     // gedrückt ?
2
     {
3
       while( PORTBbits.RB1 == 1 )  // ja, warte aufs loslassen
4
         ;
5
       state = 1;
6
     }

viel simpler. Alleine dadurch, dass du da scheinbar einen delay 
brauchst, lässt mich daran glauben, dass deine Entprellung eben doch 
nicht so gut funktioniert, wie du denkst.

: Bearbeitet durch User
von Karl H. (kbuchegg)


Lesenswert?

Karl Heinz schrieb:

> wenn der toggelt wie verrückt. Denn dein Code WILL ja den Eingang 11 mal
> hintereinander (mit jeweils 10ms Wartezeit dazwischen)


Entschuldigung. Es sind nur 1 ms und nicht 10. Demenstprechend dann auch 
11ms komplett und nicht 110.

Am grundsätzlichen Grundproblem ändert das allerdings immer noch nichts. 
Die Tastenabfrage ist so inakzeptabel. Durch drücken einer Taste und 
gedrückt halten der Taste kann ich den kompletten µC mit Ausnahme von 
Interrupt Routinen lahm legen.

: Bearbeitet durch User
von musicmiles (Gast)


Lesenswert?

Vielen Dank für die schnellen Antworten.
Der Mikrocontroller ist in diesem Fall ausschließlich dafür zuständig, 
die Eingaben des Benutzers auszuführen und zu visualisieren. Wenn er 
eine Taste lange drücken möchte, kann er das tun - und es passiert 
nichts.

Aber davon abgesehen verstehe ich nicht, warum es einen Unterschied 
macht, an welcher Stelle im key-handler() die einzelnen Bits der 
Eingänge abgefragt werden. Ich drücke die Taste am RB7. Steht die 
if-Anweisung if(PORTBbits.RB7==1) ganz oben, funktioniert alles (wie von 
mir zu diesem Zeitpunkt gewünscht) wunderbar. Setze ich die if-Anweisung 
ganz nach unten, bekommt der Mikrocontroller den Tastendruck nicht immer 
mit. Und alle anderen Eingänge liegen definitiv auf low (werden durch 
den Schmitt-Trigger in Verbindung mit einem Pull_Down runtergezogen).

Vielen Dank und Gruß

von Karl H. (kbuchegg)


Lesenswert?

musicmiles schrieb:

> mit. Und alle anderen Eingänge liegen definitiv auf low (werden durch
> den Schmitt-Trigger in Verbindung mit einem Pull_Down runtergezogen).

Dann musst du dir ansehen, wo der µC die Zeit vertödelt.
Ich denke immer noch, er hängt in irgendeiner anderen Tastenabfrage, 
weil die Eingänge nicht so ruhig sind, wie du glaubst das sie sind.

Mach dir dort halt mal ein paar Ausgaben zb mittels LED einschalten 
rein. Dann kommst du dem auf die Schliche

von musicmiles (Gast)


Lesenswert?

Karl Heinz schrieb:
> Dann musst du dir ansehen, wo der µC die Zeit vertödelt.
Scheinbar hing der µC in einer while-schleife fest, die auf das ACK von 
einem I2C-Slave wartete. Jetzt funktioniert es wie gewünscht. Danke.

Nun noch eine Frage: Ich möchte jetzt noch verschiedene 
Tastendruckszenarien mithilfe des integrierten Timers und einer 
zugehörigen Interrupt-Routine programmieren, wie z.B. Taste ist länger 
als drei Sekunden gedrückt, dann schalte zwei LEDs ein, anstatt nur 
eine.

Ich setze bei Tastendruck den TIMER0 und das zugehörige Interrupt-Flag 
auf 0. Zähle dann die Überläufe und nach einer gewissen Zeit (abhängig 
vom Takt und den Prescaler-Einstellungen) passiert dann etwas in der 
Interrupt-Routine. Ich möchte aber alle Zustände in der Statemaschine 
verwalten, d.h. wenn eine Taste kurz gedrückt wird, entspricht dies bsp. 
dem Zustand 11, wenn Sie aber länger als drei Sekunden gedrückt wird, 
entspricht dies dem Zustand 12.

Rufe ich aus der Interrupt-Routine die Statemaschine auf oder wie löst 
man so etwas sauber? So wie ich es verstanden habe, liest man alle 
Zustände (Taster gedrückt/nicht gedrückt/lange gedrückt) ein und dann 
entscheidet die statemaschine was passiert.

Gruß

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.