Forum: Compiler & IDEs Problem mit Interrupts


von Nachtaktiver (Gast)


Lesenswert?

Hallo zusammen,

ich bin gerade dabei eine Firmware zu schreiben und jetzt habe ich ein 
kleines Detailproblem wofür ich nun ein bisschen Hilfe benötige.
In meiner "Problemhardware" gibts es einen Mikrocontroller welcher über 
I2C ein Display ansteuert und gleichzeitig müssen über I2C diverse 
Eingabeelemente ausgewertet werden. (Taster etc.)

Nun ist es so das ich mir eine Auswerteroutine für die Taster 
geschrieben habe, welche über einen Timer-Interrupt aufgerufen wird.
Das eigentliche Problem hierbei ist das es sein kann, das in der 
Hauptschleife das Display mit Daten über I2C versorgt wird, der Timer 
Interrupts kommt und den aktuellen Datentransfer zum Display beendet und 
die Eingabelemente über I2C abfragt. Dabei ist mir aufgefallen das es zu 
allerlei Fehlern am Display kommt da das Display dadurch inkonsitente 
Daten erhält aber die Displayroutinen "planmäßig" im Programm 
weiterlaufen.

Ich habe mir vor einigen Monaten ein C Buch gekauft und habe jede menge 
dazugelernt, nur leider findet man in einen solchen Buch keine 
Strategien für ein Cleveres Programmkonzept (Um zum Beispiel solche 
Dinge zu vermeiden)

Hat irgendjemand von euch eine Idee wie man dieses Problem lösen kann?
Ich hatte schon an eine Art "I2C-Busy-Flag" oder einen anderen Interrupt 
gedacht in den das Display in einen Zug beschrieben wird (Soweit ich 
weiß können Interrupts nicht durch andere Interrupts unterbrochen 
werden, sondern Reihen sich ein). -Nur irgendwie bin ich mir nicht 
sicher ob das auch das wahre ist.

Mir sind die CLI/SEI bekannt nur habe ich im Tutorial gelesen das dies 
auch nicht die wahre Lösung ist, da man sich dafür andere Probleme 
einheimsen kann. Da das Programm größer und größer wird würde ich gerne 
eine "Bulletproof" Variante in Betracht ziehen.

Würde mich freuen wenn Ihr mir ein wenig auf die Sprünge helfen könntet.

von Karl H. (kbuchegg)


Lesenswert?

Während der Ausgabe die Interrupts mittels CLI disablen und danach 
mittels SEI wieder enablen.

Dabei strebst du eine möglichst feine Granulierung an. D.h. wenn das bei 
dir möglich ist, dann sperrst du die Interrupts nur während der Ausgabe 
eines einzelnen Zeichens. Gerade soviel, wie du brauchst, dass das 
Display nicht durcheinanderkommt.

von Karl H. (kbuchegg)


Lesenswert?

> Mir sind die CLI/SEI bekannt nur habe ich im Tutorial gelesen das dies
> auch nicht die wahre Lösung ist

Keine Regel ohne Ausnahme.
Natürlich sollst du nicht eine halbe Stunde lang die Interrupts sperren. 
Aber wenn es in deinem Programm Abschnitte gibt, die von einem Interrupt 
nicht unterbrochen werden dürfen UND die kurz und schnell abgearbeitet 
sind, dann spricht da nichts dagegen.

von Oliver (Gast)


Lesenswert?

Sinnvollerweise wirst du auf I²C immer erst ein Befehlszyklus komplett 
abarbeiten müssen, bevor du auf einen anderen Teilnehmer umschalten 
kannst. Man kann eine Übertragung zwar auch abbrechen, aber bei das ist 
in deinem Fall nicht sinnvoll. Insofern wird das mit Unterbrechungen 
durch ISR's, die dann eine eigene I²C-Kommunikation starten, nicht 
funktionieren.

Die allereinfachste Lösung dürfte sein, mehrere getrennte I²C-Busse 
einzusetzen. Die Abfrage der Eingabeelemente dürfte ja nicht 
zeitkritisch sein, da reicht Software-I²C aus.

Ansonsten tatsächlich die entsprechenden Interrupts sperren (es muß ja 
nicht gleich ein komplettes cli() sein), oder der Einsatz eines 
Echtzeit-Betriebssystems, bei dem du allerdings dann genauso, wie bei 
den Interrupts, während eines I²C-Zyklus Unterbrechungen verbieten 
musst.

Oliver

von 2ter Gast (Gast)


Lesenswert?

Die Idee mit dem I2C-Busy-Flag ist ein guter Ansatz. Über das Flag 
könnte dein Hauptprogramm feststellen,ob deine Eingabeelemente gelesen 
werden sollen und dies initieren. Ich würde das zusammen mit einer 
State-Machine als Ablaufkontrolle umsetzen.

von Nachtaktiver (Gast)


Lesenswert?

Gerade bin ich mir nicht sicher was nun wirklich "kurz" oder "lang" ist.
Was ist wenn ich die Interrupts sperre aber Zeitlich gesehen schon 2 
Timer Interrupts ausgelöst wurden. - Dann geht doch einer verloren oder?
(Sollte bei Tastern  und langsamen Eingabelementen nicht so schlimm 
sein)

Den Timerinterrupt hatte ich so organisiert das es eine Art "Event" 
Zähler gibt und abhängig vom Zählerstand unterschiedliche 
Eingabeelemente abfragt.
Dadurch ist es mir möglich das ich innerhalb einer gewissen Anzahl von 
Interrupts unterschiedliche Prioritäten setzen kann. (Das zum Beispiel 
Inkrementalgeber öfters abgefragt werden als Taster etc.)

Ich kann leider noch nichts genaueres zu den Hauptteil sagen da dieser 
erst Stück für Stück anwächst. Hinterher kommen noch eine Menge 
Zustands/-Kontroll abfragen und Displaymenüs hinzu.

von Nachtaktiver (Gast)


Lesenswert?

Entschuldigung für das Doppelpost aber ich hatte den Post erst jetzt 
gesehen.

@Oliver:
Du musst wissen das es sich hierbei um fertige Hardware handelt wo nur 
noch die Software geschickt implementiert werden muss. Es ist zwar 
vieles beachtet ( z.B. Inkrementalgeber hängen direkt an IO-Pins und 
müssen nicht über I2C abgefragt werden), hätte aber nicht gedacht das 
ein Temperatursensor ein IO-Expander(+Bedienelemente) sowie ein Display 
auf dem I2C-Bus solche "Probleme" machen kann.

@2ter Gast:
Wie habe ich mir das genau vorzustellen?
In Strukturierten Programmieren habe ich noch nicht so viel Erfahrung da 
vor etwa 6 Monaten mit C angefangen habe und vorher nur Miniprogramme in 
Assembler geschrieben habe. Entwicklung von Hardware liegt mir eher.
(Aber das eine geht nicht ohne den anderen)

von Karl H. (kbuchegg)


Lesenswert?

Nachtaktiver schrieb:
> Gerade bin ich mir nicht sicher was nun wirklich "kurz" oder "lang" ist.
> Was ist wenn ich die Interrupts sperre aber Zeitlich gesehen schon 2
> Timer Interrupts ausgelöst wurden. - Dann geht doch einer verloren oder?
> (Sollte bei Tastern  und langsamen Eingabelementen nicht so schlimm
> sein)

Also Faustregel:
Alles was mit Benutzerinteraktion zu tun hat, geschieht für den µC in 
extremer Zeitlupe. Selbst wenn dein µC mal 100ms nicht auf die Tasten 
schaut, ist das für einen Benutzer praktisch noch nicht bemerkbar.

Und 100ms sind für einen µC schon eine lange Zeit.

Du könntest ja zb deine Display Ausgabe auch als ein Event auffassen. 
Bei jedem Display-Event werden ein paar Zeichen aufs Display ausgegeben. 
Danach geht es reihum alle Eingabeelemente abfragen und beim nächsten 
Display-Event werden wieder ein paar Zeichen ausgegeben, solange bis der 
auszugebende String komplett am Display ist.

von Falk B. (falk)


Lesenswert?

Siehe Interrupt

von Falk B. (falk)


Lesenswert?


von Heiko B. (heiko_b)


Lesenswert?

Du solltest die gesamte i2c Geschichte nicht in den Interrupts 
ausführen. Glieder sie in dein Hauptprogramm oder in ein Unterprogramm 
ein. Deine Interupt-Routine setzt nur ein Flag und das Hauptprogramm 
verzweigt in das entsprechende Unterprogramm. Dann kommen sich deine 
Abfragen und Ausgaben auch nicht mehr ins Gehege, da sie sich nicht 
gegenseitig unterbrechen können. Du hast leider nicht beschrieben, auf 
welcher Plattform du arbeitest.

von Nachtaktiver (Gast)


Lesenswert?

@Falk:
Ich kannte die Artikel (Halbherzig) und habe diese nochmal durchgelesen.
Dabei muss ich mir aber eingestehen das es sich stellenweise um große
Häppchen handelt welche ich schwer auf Mikrocontroller exthrahieren 
kann.
Die meiste Zeit ist ja von einen "vollwertigen" Betriebssystem auf PC 
Basis die Rede.

Der Multitasking Prozess gibt mir aber die Inspiration das ich die 
komplette Hauptschleife in kleinen Teilen Sturkturiere. Und bei jeden 
durchlauf einen Aktion durchführe. (z.B)

-Messwert A erfassen
-Messwert A in ASCII umwandeln
-Messwert B erfassen
-Messwert B in ASCII umwandeln
-Messwert C erfassen
-Messwert C kontrolieren
-Kontrolzustände abfragen
-Kontrolzustände auswerten
-Eingabeelemente: auswerten  (Werden durch Timer-Interrupt aktualisiert)
-Eingabeelemente: Aktionen ausführen
-Display aktualisieren
- (+Unterpunkte welche mir gerade nicht in den Sinn kamen)
-zurück zum Anfang

@Heiko Bendt:
Als erstes bevor ich es wieder vergesse: Es handelt sich um einen 
ATMEGA16 welcher wahrscheinlich auf einen MEGA64 aufgerüstet werden 
muss.

Ich selber sehe da kein Problem einige Bytes über I2C in einen Interrupt 
abzufragen. Zumal das auch nur gemacht wird wenn z.B ein Taster 
abgefragt wird. Wenn ein Inkrementalgeber ausgewertet wird, wird sofort 
von den IO-Pins gelesen.Irgendwie müssen diese Eingabeelemente auf 
"aktuellen Stand" gehalten werden.

Die Idee die dahinter steckt ist ja, das der Timerinterrupt dafür sorgt 
das für die Taster entsprechende Flags wie "BUTTON_A_LONG_CLICK" oder 
"BUTTON_A_SHORT_CLICK" gesetzt werden können und das Hauptprogramm 
reagierend auf diese Zustände, Aktionen durchführen kann.

von Peter D. (peda)


Lesenswert?

Heiko Bendt schrieb:
> Du solltest die gesamte i2c Geschichte nicht in den Interrupts
> ausführen.

Stimmt!

I2C kostet schon einiges an CPU-Zeit, daher sollte man damit keine 
Interrupts blockieren.

Es könnte sogar sinnvoll sein, das I2C selber als Interrupt zu 
programmieren. Dazu legt man einen Puffer an, wo das Main die Daten 
ablegt und der I2C-Interrupt gibt sie dann aus.


Peter

von 2ter Gast (Gast)


Lesenswert?

Nachtaktiver schrieb:
> Wie habe ich mir das genau vorzustellen?
> In Strukturierten Programmieren habe ich noch nicht so viel Erfahrung da
> vor etwa 6 Monaten mit C angefangen habe und vorher nur Miniprogramme in
> Assembler geschrieben habe. Entwicklung von Hardware liegt mir eher.
> (Aber das eine geht nicht ohne den anderen)

http://de.wikipedia.org/wiki/Endlicher_Automat

Im Prinzip hast Du in deiner main-Funktion eine while-Schleife und eine 
Variable, mit der du dir den aktuellen Zustand Programms merkst. Den 
Zustand wertet Du  z.B. mit switch-case ab. In Abhängigkeit von dem 
aktuellen Zustand führt dein Programm aktionen aus.

Z.B.

Du hast den Zustand "Display-Ausgabe". Dort machst Du deine 
Displayausgabe.
Wenn ein Fehler auftritt könnte es den Zustand "Display-Ausgabe-Fehler" 
geben, in dem Du den Fehler behandelt; D.h. du setzt den Zustand auf 
"Display-Ausgabe-Fehler" oder aber du bist fertig setzt den Zustand 
Display-Ausgabe-fertig. Im Zustand Display-Ausgabe-fertig prüfts dann ob 
dein Interrupt das flag "Eingabe-Elemente-Prüfen-Flag" gesetzt hat und 
fährst mit dem Zustand "Eingabe-Elemente-Prüfen"   fort

von Falk B. (falk)


Lesenswert?

@  Nachtaktiver (Gast)

>Ich kannte die Artikel (Halbherzig) und habe diese nochmal durchgelesen.
>Dabei muss ich mir aber eingestehen das es sich stellenweise um große
>Häppchen handelt

Für Anfänger schon, ja.

>welche ich schwer auf Mikrocontroller exthrahieren kann.

Bitte? Das ist direkt am Beispiel AVR erklärt, mit Beispielprogrammen.

>Die meiste Zeit ist ja von einen "vollwertigen" Betriebssystem auf PC
>Basis die Rede.

Nö, keine Sekunde.

>Der Multitasking Prozess gibt mir aber die Inspiration das ich die
>komplette Hauptschleife in kleinen Teilen Sturkturiere. Und bei jeden
>durchlauf einen Aktion durchführe. (z.B)

Genau so.

>Als erstes bevor ich es wieder vergesse: Es handelt sich um einen
>ATMEGA16 welcher wahrscheinlich auf einen MEGA64 aufgerüstet werden
>muss.

Und? Der Speicher ist nicht entscheiden, aber das Konzept!

>Ich selber sehe da kein Problem einige Bytes über I2C in einen Interrupt
>abzufragen.

Weil du Änfänger bist ;-)

> Zumal das auch nur gemacht wird wenn z.B ein Taster
>abgefragt wird. Wenn ein Inkrementalgeber ausgewertet wird, wird sofort
>von den IO-Pins gelesen.Irgendwie müssen diese Eingabeelemente auf
>"aktuellen Stand" gehalten werden.

Das geht auch anders. Kurze, knackige Interrupts, der Rest in der 
Hauptschleife.

>"BUTTON_A_SHORT_CLICK" gesetzt werden können und das Hauptprogramm
>reagierend auf diese Zustände, Aktionen durchführen kann.

Eben.

MFG
Falk

von Nacktaktiver (Gast)


Lesenswert?

Ihr habt mir viele Tipps gegeben wie man das Programm zumindest 
sinnvoll/optimal strukturieren könnt. Aber ich muss ganz ehrlich zugeben 
das ich immer noch nicht verstehe wie ich die Eingabedaten der Taster 
aktuell halte.

Fest steht aufjedenfall, das eine I2C Kommunikation im Interrupts Tabu 
ist, da dies viel wertvolle Zeit verschwendet. Für mich ist das nicht 
ganz nachvollziehbar (Wie Falk schon Sachte - Ich bin ein Anfänger) und 
dementsprechend kann ich das auch akzeptieren.

Soll ich das ganze so interpretieren das ich in meiner "Unendlichen 
Automatentafel" (Wie 2ter Gast 2-Post hier darüber) so schön erklärte, 
mehrere Unterpunkte "Daten: Eingabeelemente aktualisieren" aufführe?

Dadurch kann der Interrupt schön kurz und knackig ausfallen und ich muss 
mir keine Sorgen machen das es zu abgebrochenen Fehlübertragungen kommt. 
In diesen Interrupt werden dann nur die Taster ausgewertet und 
entsprechende Flags für die weitere Auswertung gesetzt.

Also(?):
1
while(1)
2
{
3
4
switch (Event) 
5
{
6
case 0:
7
Eingabeelemente_aktualisieren(); // Hier werden die Eingabeelemente neu abgefragt/Aktualisiert
8
Event = Event+1
9
10
case 1:
11
Instruktion_1(); // z.B Messwerte erfassen
12
Event = Event+1
13
14
case 2:
15
Instruktion_2(); // z.B Zustände abfragen
16
Event = Event+1
17
18
case 3:
19
Instruktion_3(); // z.B Zustände auswerten& Error Flags setzen
20
Event = Event+1
21
22
case 4:
23
Eingabeelemente_aktualisieren(); // Hier werden die Eingabeelemente neu abgefragt/Aktualisiert
24
Event = Event+1
25
26
case 5:
27
Instruktion_5(); // z.B Zustände der Eingabeelemente auswerten, Aktionen durchführen - Werden per Interrupt aktuell gehalten
28
Event = Event+1
29
30
case n:
31
Instruktion_4(); // z.B Wenn vorhanden: Fehlermeldungen ausgeben
32
Event = Event+1
33
34
case n+1:
35
Instruktion_5(); // z.B Display aktualisieren
36
Event = 0
37
38
39
default: // Wenn ungültig - Event Zähler zurücksetzen
40
Event = 0
41
}
42
43
}
44
return 0;
45
}
46
47
48
ISR(TIMER0_OVF_vect) // Super-Duper-Kleiner-Interrupt
49
50
{
51
Taster_Auswerten(); // Daten werden per Mainloop auf aktuellen Stand gehalten
52
}

Anstatt den Zahlen kann man auch gute Namen nehmen, aber zur 
verdeutlichung denke ich sollte dies reichen.

von Heiko B. (heiko_b)


Lesenswert?

Deine Beschreibung bleibt aber trotzdem vage. Wie wärs mit dem 
Schaltplan und dem Programm?

Grundsätzlich gilt, daß in den Interrupts möglichst wenig gearbeitet 
werden sollte. Ein paar Register auslesen, Flags setzen, Daten in/aus 
Sende/Empfangsregister transportieren. Die eigentliche Verarbeitung 
geschieht im Haupt/Unterprogramm. Schon garnicht wird in der 
Interruptroutine gewartet bis ein bestimmtes Bit z.B durch die Hardware 
gesetzt wird.Wenn du nur ein Flag setzt, das etwas erledigt werden muß, 
kannst du in Ruhe die momentane Aufgabe zuendebringen bevor du die 
nächste anfängst. In deinem Einganspost erscheint es mir, als würdest du 
versuchen auf das display zu schreiben und im Interrupt über die gleiche 
Schnittstelle zu versuchen etwas einzulesen. Das muß schief gehen. 
Übertrage die paar Zeichen zuende zum Display und frage dann die Taster 
ab. So schnell wird dein Finger nicht sein, das du den Tastendruck 
verpaßt. Könnte dein Inkrementalgeber nicht einen Pinchange Interrupt 
auslösen?
Dann hättest du eine regelmäßige Abfrage weniger.
Benutzt du eigentlich Hardware TWI?

von Nacktaktiver (Gast)


Lesenswert?

Ich finde es wahnsinnig toll das Ihr mir hier so fleißig hilft aber aus 
diversen Gründen möchte ich keine Software+Hardware Details hier 
veröffentlichen. Der erste Grund ist das es sich um mehrere Platinen 
handelt und es sich hier um viele Seiten Schaltplan dreht - Ich kann 
schlecht erwarten das Ihr euch darin zurechtfinden wird und gleichzeitig 
schrecke ich damit doch nur Leute ab. Die Software möchte ich nicht 
veröffentlichen, da allein die wichtigen Grundfunktionen so viel Umfang 
einnehmen das Ihr euch darin genauso wenig zurechtfinden werdet. (Ist 
nicht "böse" gemeint)

Auch ist es für die Fragestellung eigentlich irrelevant wie genau die 
Hardware aussieht, da ja auf den von mir Oben auf die Schnelle 
geschrieben C Schnippsel um ein Beispiel handelt. Es sollte eigentlich 
um das Prinzip gehen aber scheinbar ist das nach hinten losgegangen.

>In deinem Einganspost erscheint es mir, als würdest du
>versuchen auf das display zu schreiben und im Interrupt über die gleiche
>Schnittstelle zu versuchen etwas einzulesen. Das muß schief gehen.

Bevor ich es vergessse: Natürlich verwende ich Hardware I2C (200kbit/s)
Genau das ist ja der Sinn meiner Frage!Der Kernproblematik ist 
eigentlich:

Es gibt eine Displayroutine welche das Display, welches ein I2C 
Interface besitzt, beschreibt (senden), nun kommt der Timerinterrupt, 
"killt" diesen I2C Transfer, und fragt eigenständig über I2C (emfpangen) 
den Zustand einiger Taster über einen IO-Expander ab. Nach beenden des 
Interruptes läuft die Displayroutine weiter und das Display macht mist 
da die Daten korrupt sind.

Die eigentliche Fragestellung lautet somit:
Wie kann ich eine solche Unterbrechnung geschickt vermeiden?
Ich weiß halt nicht wie ich sowas geschickt Programmiere. Da "breche" 
mir halt die Beine das alles sinnvoll zusammen zubauen. Mein C Buch 
konnte mir an dieser Stelle nicht weiterhelfen, sonst hätte ich hier 
auch nicht gefragt.

Bis jetzt sind wir so weit gekommen, das dass Anfragen der 
Tasterzustände über I2C im Interrupt Fehl am Platz ist und das im 
Interrupt selbst, nur die reine Tasterauswertung kommt (Welche kurz 
ist).Nur konnte ich bis jetzt nicht nachvollziehen wo eine solche 
Aktualisierung sonst hin soll.

Die Inkrementalgeber sind erstmal unwichtig. Wenn der 
Taster-Programmteil flüssig läuft sind die Inkrementalgeber ein 
leichtest Spiel. (Da mir dann das Prinzip klar sein wird)

von Karl H. (kbuchegg)


Lesenswert?

Nacktaktiver schrieb:

> Die eigentliche Fragestellung lautet somit:
> Wie kann ich eine solche Unterbrechnung geschickt vermeiden?
> Ich weiß halt nicht wie ich sowas geschickt Programmiere. Da "breche"
> mir halt die Beine das alles sinnvoll zusammen zubauen. Mein C Buch
> konnte mir an dieser Stelle nicht weiterhelfen, sonst hätte ich hier
> auch nicht gefragt.

Bei solchen Fragestellungen ist es oft hilfreich (zumindest für mich) 
mich der Fragestellung zu widmen (nicht lachen): Wie mach ich das im 
realen Leben?


Wenn ein Klo gerade besetzt ist und jemand anderer kommt und will 
dieselbe Resource (Schüssel) benutzen ... wie verhinderst du im realen 
Leben, dass da Blödsinn passiert? Welche Möglichkeiten gibt es, welche 
Voraussetzungen müssen für jede der Möglichkeiten gegeben sein.

Abstrahiere das Problem in dein tägliches Leben. Du wirst überrascht 
sein, wo und wie du derartige Probleme jeden Tage ohne mit der Wimper zu 
zucken und ohne groß nachdenken zu müssen lösen kannst!

Das was du weiter oben als "Strategien für ein Cleveres Programmkonzept" 
schmerzlich vermisst, sind in Wirklichkeit zu über 80% ganz alltägliche 
Strategien aus deinem täglichen Leben, transferiert in ein Programm.

von Oliver (Gast)


Lesenswert?

Nacktaktiver schrieb:
> Es gibt eine Displayroutine welche das Display, welches ein I2C
> Interface besitzt, beschreibt (senden), nun kommt der Timerinterrupt,
> "killt" diesen I2C Transfer, und fragt eigenständig über I2C (emfpangen)
> den Zustand einiger Taster über einen IO-Expander ab. Nach beenden des
> Interruptes läuft die Displayroutine weiter und das Display macht mist
> da die Daten korrupt sind.
>
> Die eigentliche Fragestellung lautet somit:
> Wie kann ich eine solche Unterbrechnung geschickt vermeiden?

Wurde ja schon gesagt: Am einfachsten während der LCD-Ausgabe die 
Interrupts sperren. wenn das zu nicht akzeptablen Tastenverzögerungen 
führt, wird es komplizierter. Dann braucht es eine zentrale Instanz, die 
die I²C-Kommunikation koordiniert (auf Betriebssysteme nennt man so 
etwas Treiber).

Oliver

von MWS (Gast)


Lesenswert?

Nacktaktiver schrieb:
> Es gibt eine Displayroutine welche das Display, welches ein I2C
> Interface besitzt, beschreibt (senden), nun kommt der Timerinterrupt,
> "killt" diesen I2C Transfer, und fragt eigenständig über I2C (emfpangen)
> den Zustand einiger Taster über einen IO-Expander ab. Nach beenden des
> Interruptes läuft die Displayroutine weiter und das Display macht mist
> da die Daten korrupt sind.

Der Ansatz ist Murks. Einfach ein Flag im Timer setzen, dieses Flag im 
Hauptcode auswerten und dann ggf. die Tastenabfrage durchführen.

von Oliver (Gast)


Lesenswert?

Mal ein paar Zahlen:

Wenn das ein 4*16-Display ist, das komplett vollgeschrieben wird, sind 
das mit Protokolloverhead vielleicht 600 Bit, die ans Dispaly geschickt 
werden. Bei 100kHz Takt benötigt das 6ms, bei 400kHz 1,5ms.

Ob sich eine Tastenabfrage mal um 6ms verzögert, ist nun wirklich völlig 
egal. Das merkt kein Mensch.

Eine eventgesteuerte Zustandmaschine ist allerdings trotzdem sehr zu 
empfehlen.

Oliver

von Nacktaktiver (Gast)


Lesenswert?

Es sind hier ziemlich gute Tipps gefallen und langsam wird es Zeit das 
ein oder andere mal in der Tat umzusetzen. Falls es bedarf gibt werde 
ich hier mich einfach wieder melden.

@Karl Heinz Buchegger:
Ich finde das es wirklich ein sehr guter Tipp ist! Es ist schon 
irgendwie zum lachen, zeigt aber das wenn man verkrampft an etwas 
herangeht sich eigentlich nur unnötige Probleme macht.

von 2ter Gast (Gast)


Lesenswert?

Nacktaktiver schrieb:
> Soll ich das ganze so interpretieren das ich in meiner "Unendlichen
> Automatentafel" (Wie 2ter Gast 2-Post hier darüber) so schön erklärte,
> mehrere Unterpunkte "Daten: Eingabeelemente aktualisieren" aufführe?

Genauso so etwas in der Art hatte ich im Sinn. Vergess' bei deinen cases 
jeweils das break nicht

von Nacktaktiver (Gast)


Lesenswert?

Danke! Das Break hatte ich wirklich übersehen. Irgendwie komme ich 
gerade richtig gut vorran und aufeinmal kommen mir immer wieder bessere 
Ideen.

Gerade hatte ich die ersten Anfänge geschrieben und hatte mir zum Test 
einige Werte auf das Display anzeigen lassen. Dabei musste ich 
feststellen das der Wert so schnell beschrieben wird, das man es nicht 
mitlesen kann.
Also zähle ich einfach nebenbei in der Tasterauswertig in einer Variable 
hoch löse irgendwann ein bestimmtes Flag aus und erst dann wird dann 1x 
das Display beschrieben. Ist diesen Flag nicht gesetzt wird in der State 
Maschine der Display Aktualisieren Teil einfach weggelassen.

Vorhin einen kleinen Zettel beschrieben in der ich die wichtigsten 
Sachen chronologisch aufgeführt habe und schreibe ich das ganze einfach 
Stück für Stück runter.Auch habe ich schon erste Ideen für eine Art 
Error Handler.

Hier mal ein kleiner Teil:
1
  unsigned char MAIN_EVENT_COUNTER=0;  
2
  unsigned char COUNTER=0;
3
4
  while(1)
5
{ 
6
// Main State Maschine
7
  switch(MAIN_EVENT_COUNTER)  
8
  {
9
10
// Aktualisiert den Zustand der Taster 
11
    case 0:
12
    // Empfängt die neuen Daten über den IO-Expander ab      
13
      KEY_DATA = Expander_Read_PINA( PINA_MASK);
14
    // Main Event: +1      
15
      MAIN_EVENT_COUNTER++;
16
    // Beendet die Aktuellen Zustand  
17
    break;
18
  
19
20
// Aktualisiert den Temperaturwert
21
    case 1:
22
    // Fragt die neue Temperatur ab
23
      ACTUAL_TEMPERATURE = LM75_Read_Temperature();
24
    // Main Event: +1
25
      MAIN_EVENT_COUNTER++;
26
    // Beendet den Aktuellen Zustand
27
    break;
28
29
// Aktualisiert den Messwert an ADC0 ( aktueller Strommesswert)
30
// Dabei wird ein Mittelwert aus 32 Einzelwerten errechnet
31
    case 2:
32
    // Zähler vorladen
33
      COUNTER = 31;
34
        
35
    // Alten Wert löschen
36
      ACTUAL_VOLTAGE = 0;
37
38
    // Bearbeitungsschleife
39
        while(COUNTER) {
40
        
41
        // Neuen Messwert addieren
42
        ACTUAL_CURRENT += ADC_Read( ADC1);
43
44
        // Bearbeitungszähler: -1
45
        COUNTER--;
46
        }
47
    
48
    // Mittelwertbildung
49
      ACTUAL_CURRENT = ACTUAL_CURRENT/32;
50
    
51
    // Main Event: +1
52
      MAIN_EVENT_COUNTER++;
53
    // Beendet den Aktuellen Zustand
54
    break;  
55
  
56
...
57
...
58
...
59
60
  }

von 2ter Gast (Gast)


Lesenswert?

Paar Anregungen:
- Die Nummerierung würde ich durch #defines mit aussagekräftigen 
Bezeichner ersetzen.

- Anstatt die State-Machine mit einem Counter abzuarbeiten, den State 
ausdrücklich zuweisen, also anstatt
1
 ++MAIN_EVENT_COUNTER
 lieber
1
 MAIN_EVENT_COUNTER=UPDATE_TEMPERATURE_STATE

Das hätte mir beim Code-Schnipsel auffallen können.

Und sich mal alles aufschreiben ist eh ein guter Plan.

von Oliver (Gast)


Lesenswert?

Diese all-in-one-swich-case-statemachine wird dir irgendwann um die 
Ohren fliegen, da ziemlich unwartbar.

Lad dir mal hier:
http://gandalf.arubi.uni-kl.de/avr_projects/

den gcc-port des avr-butterfly herunter, und schau dir die darin 
enthaltene statemachine an.

Oliver

von Nacktaktiver (Gast)


Lesenswert?

@2ter Gast:
Dabei hatte ich es oben sogar selber geschrieben. Werde ich dann mit
übernehmen und einarbeiten.

@Oliver:
Warum unwartbar?

Ich habe mir mal den ganzen Sourcecode vom AVR Butterfly angeschaut, 
aber ich muss sagen das ich zum größten Teils nur Bahnhof verstehe.

Sofern ich das aber richtig interpretiere ist die Statemachine dort eine 
Funktion welche Daten aus den Flash liest. (Oder?)

von Oliver (Gast)


Lesenswert?

Nacktaktiver schrieb:
> @Oliver:
> Warum unwartbar?

Was schätzt du denn, wie viele Zustände und Zustandswechsel bei einem 
Gerät mit mehreren Tasten etc. zusammenkommen?

Oliver

von Nachtaktiver (Gast)


Lesenswert?

@Oliver:

So einige! (Kurzes drücken, langes drücken +welche mitgezählt werden)
Bis jetzt scheint es aber gut klappen, aber vielleicht ändert sich das 
noch.
Zum der Tasterauswertung habe ich eine Testfunktion geschrieben welche 
mir die ganze Zeit eine Variable auf dem Display anzeigt. Jeder Taster 
kann bei drücken diese Variable verändern. Wenn ich wild auf alle Tasten 
drücke und das unterschiedlich (Also ob lang oder kurz) werden wirklich 
alle Tasterdrücke erkannt. Ich bezweifel das "wahnsinnig auf alle Tasten 
drücken" ein normaler Betriebszustand entspricht.

Das Problem hier ist ja das die Taster nicht direkt an den IO-Pins 
hängen, sondern I2C gepollt werden müssen - Das hat ja nichts im 
Interrupt zu suchen.
Hat man wirklich ein Gerät wo mehrere Taster braucht dann hängt man 
diese direkt an den Mikrocontroller. Dann fällt der Zustand "Taster 
Daten Aktualisieren" weg und hat entsprechend keine Probleme mit vielen 
unterschiedlichen Zuständen der Eingabeelemente.



Was sollte ich den deiner Meinung nach konkret verändern?

von Nachtaktiver (Gast)


Lesenswert?

Ich möchte ein kleines Update hier machen. Nach ein bisschen nachdenken 
hat mir Oilvers Ansatz mit den Typ aus den AVR Butterfly weitergeholen.
Das ganze Passiert auf eine konstantes Array im Flash welches mithilfe
einer Funktion aktualisiert wird. Dadurch kann man ganz beqeum die 
Reihenfolgen, Häufigkeiten etc. umstellen, ohne das man den ganzen Code 
Zerstückeln muss.

So sieht die Grundvorbereitung für die State Machine aus:
state_machine.h
1
/* ------------------------------------------------------------------------------- */
2
3
// Zustandstabelle für die Main Statemachine
4
const unsigned char    STATE_MACHINE_ARRAY [] PROGMEM = {
5
      
6
            // Abarbeitungsreihenfolge
7
            0, 1, 2, 0, 1, 0, 1, 2, 255
8
};
9
10
// Hier listet man alle benötigten Zustände
11
#define          TU_ETWAS_A      0
12
#define          TU_ETWAS_B      1
13
#define          TU_ETWAS_N      2
14
15
// Reset Zustand
16
#define          STATE_MACHINE_ZURUECKSETZEN  255
17
18
/* ------------------------------------------------------------------------------- */



Unsere eigentliche Statemachine könnte so aussehen:
state_machine.c
1
/* ------------------------------------------------------------------------------- */
2
#include <avr/io.h>
3
#include <avr/pgmspace.h>
4
#include <inttypes.h>
5
6
#include <state_machine.h>
7
8
9
  unsigned int   STATE_MACHINE_HANDLER=0;  
10
  unsigned char  i=0;
11
12
while(1) {
13
14
  // Liest den aktuellen Zustand aus den Flash
15
  STATE_MACHINE_HANDLER = pgm_read_byte(&STATE_MACHINE_ARRAY[i]);
16
  
17
  // Inkrementiert die Array Nummer aus den Flash
18
  i++;
19
20
21
switch(STATE_MACHINE_HANDLER) {
22
23
24
/* ------------------------------------------------------------------------------- */
25
  
26
  case TU_ETWAS_A:
27
      // Tu_Etwas_A Programmteil
28
  break;
29
30
31
/* ------------------------------------------------------------------------------- */
32
33
  case TU_ETWAS_B:
34
      // Tu_Etwas_A Programmteil
35
  break;
36
37
/* ------------------------------------------------------------------------------- */
38
39
  case TU_ETWAS_N:
40
      // Tu_Etwas_A Programmteil
41
  break;
42
43
44
/* ------------------------------------------------------------------------------- */
45
46
  case STATE_MACHINE_ZURUECKSETZEN:
47
      i=0
48
  break;
49
}
50
51
}
52
return 0;
53
}

von 2ter Gast (Gast)


Lesenswert?

Ich kenne den AVR Butterfly Code nicht, aber so wie es jetzt aussieht, 
könnte man auch
1
while(1)
2
{
3
 TuEtwas_A();
4
 TuEtwas_B();
5
 TuEtwas_N();
6
TuEtwas_A();
7
 TuEtwas_B();
8
TuEtwas_A();
9
TuEtwas_N();
10
11
}

schreiben.

von Nachtaktiver (Gast)


Lesenswert?

Kann man auch so. Der Vorteil dieser Lösung ist ja das Reihenfolgen und 
Prioritäten ganz einfach festlegen kann. Wenn du diese Lösung ja jetzt 
umstellen musst musst du den Code zurechtstückeln, vorallen dann wenn 
nun ein neuer Unterpunkt eingearbeitet werden soll.

Bei mir war ja das Problem das es bei mir ein Programmteil gab welches 
in der Hauptschleife öfters ausgeführt werden musste. Jetzt kann die 
Aufrufseqeunz mit einer einer Datenarray manipulieren.
(Zumindest gefällt mir diese Lösung so noch besser.)

von 2ter Gast (Gast)


Lesenswert?

Wenn das für Dich die einfachste Lösung deines Problems ist... happy 
hacking.

von Nachtaktiver (Gast)


Lesenswert?

Kann man auch prima auf LCD Menus anwenden. Vorher hatte ich keinen 
blassen Dunst wie man das gut realisieren könnte.


Vielen Dank für eure Hilfreichen Antworten. :)

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.