Hallo Programmierer,
ich habe ein kleines Problemchen und möchte hierzu mal ein paar
Lösungsansätze sammeln.
Konkret geht es um folgendes:
Ich habe ein Transpondersystem per RS485 mit meinem Mega128 verbunden.
Die Kommunikation steht und funktioniert soweit.
Das Ganze ist ein Master/Slave System --> Master Mega128.
Nun habe ich die Abläufe bis jetzt in meinen Programmen immer mittels
hochzählen einer Variablen erledigt jedoch ist mir das nun
etwas zu unübersichtlich.
Ich habe es immer wie folgt gemacht, nennen wir die Variable mal var.
1. var = 1
2. suche nach einem Transponder --> var = 2
3. Transponder gefunden --> var = 3, nicht gefunden var = 1
4. erstes Byte auslesen senden var = 4
5. ACK mit Byte empfangen, Byte speichern var = 5
6 zweites Byte auslesen senden var = 6
7. ACK mit Byte empfangen,Byte speichern var = 7
...
18.achtes Byte auslesen senden var = 16
19. ACK mit Byte empfangen,Byte speichern var = 17
20. Werte vergleichen var = 17
21 wenn Wert bekannt var = 18 ansonsten var 30
22. grüne LED am Leser einschalten var = 19
usw.
Doch man sieht schon es wird schon unübersichtlich.
Die ganze Abfragen der Variable habe ich immer mit einem switch case
gemacht und das ganze in eine Funktion gepackt.
Wie steuert Ihr solche Abläufe?
Gebt mir bitte ein Paar Ansätz wie Ihr das macht.
Danke schon mal für die Anregungen.
Thomas
Das was Du andeutest nennst sich Statusmaschine oder state machine.
Googel mal danach.
Ach ja: Keine Zahlenangaben, also besser
#define DEF_STATE 0
#define MAIN_STATE 1
#define NOCHN_STATE 2
...
Besserwisserle wrote:
> Ach ja: Keine Zahlenangaben, also besser>> #define DEF_STATE 0> #define MAIN_STATE 1> #define NOCHN_STATE 2> ...
...oder, wenn es wirklich C sein soll (was im OP leider nirgends
Erwähnung findet) ne enum nehmen...
@ Besserwisserle,
OK damit vergebe ich eben indirekt auch Zahlen die eben per define als
ein Statusnamen vergeben sind. Wenn ich nun aber 100 Statuse habe ist es
auch unübersichtlich.
Es ist ja fast das gleiche ob mit einer variablen oder mit einem
definiertem Namen zu arbeiten.
@Johannes,
ja ich programmiere in C.
Thomas
> Doch man sieht schon es wird schon unübersichtlich.> Die ganze Abfragen der Variable habe ich immer mit einem
Man kann sich das ganze aber auch vor dem Programmieren
auf einem grossen Blatt Papier aufmalen.
Jeder Zustand ist ein Kreis. Pfeile symbolisieren Zustandsübergänge.
Neben den Pfeil schreibst du die Bedingung die gelten muss, damit
dieser Übergang genommen werden kann. Und du schreibst neben
den Pfeil die Aktion die ausgeführt werden muss, wenn dieser
Übergang genommen werden muss.
Und schon hast du ein schönes Schaubild, welches deine Statemaschine
beschreibt.
@ Karl-Heinz,
Danke für die Deine Beschreibung/Visualisierung.
Wie es scheind gibt es nur diesen einen Weg einer Statemashine.
Werde mal den Vorschlag mir define zu arbeiten anstatt mir Zahlen
versuchen.
Dies scheind dann doch übersichtlicher zu sein.
Ich wollte nur mal hören wie Ihr das so macht und einige Anregungen
sammeln.
DANKE an ALLE !!!!!!!!!!!!!!!!!!!!!!!!
Thomas
@all,
nun hätte ich noch eine Unklarheit zu beseitigen.
Wie empfiehlt es sich mit der Statemashine zu zählen.
Bsp 1.
0x0001 --> 0x0002 --> 0x0004 --> 0x0008 --> 0x0010 --> usw.
Bsp 2.
0x0001 --> 0x0002 --> 0x0003 --> 0x0004 --> 0x0005 --> usw.
Ich denke Bsp 1 hat einige Vorteile da man die einzelnen Bits eine
Bedeutung haben und man sie auch auskommentieren kann mit einer &
Verknüpfung.
Wie ist Eure Meinung?
Thomas
Ich arbeite auch bevorzugt mit Statusmaschinen und benutze Zahlen, die
ich eins incrementiert hochzaehle. Der Vorteil ist, dass man eine
Sprungtabelle einsetzen kann, was sich ab einer Handvoll Zustaende
lohnt.
Eher Beispiel zwei. ABer da ja per #define den Schritten namen
zugewiesen werden, spielt das keine Rolle.
Ich verwende immer Zahlen, keine Bitbedeutungen.
Ich programmiere states immer so: Hier hab ich eine regelmäßige Aktion,
Transitionen, Zeitüberwachungen und Eingangsaktionen...
Funktioniert sehr gut ;-)
Jeder Schritt sieht bei mir identisch aus. Zumindest vom Aufbau her. Die
ACTION bleibt meistens leer, da in den meisten Schritten nur ein Ausgang
gesetzt werden muss und auf einen Eingang gewartet wird. zB.
Wenn es schneller gehen soll, benutze ich als Speicher für den Zustand
einen Pointer auf eine Funktion.
void (*sf)(void);
Die möglichen Zustände werden erst vordefiniert:
extern void sf_start(void);
extern void sf_zustand2(void);
Dieser Pointer wird beim Start mit der adresse der Funktion
initalisiert, die als erstes laufen soll:
sf = sf_start ;
In jeder der Funktionen wird bei der Entscheidung, dass dieser Zustend
verlassen werden soll die Zieladresse des nachfolgenden Zustandes in den
Pointer geschrieben:
if (...) sf = sf_zustand2 ;
In der Hauptschleife wird die richtige Funktion für den aktuellen
Zustand aufgerufen:
for(;;)
{
(*sf)();
}
Die einzelnen Zustandsfunktionen werden als einzelne Unterprogramme
geschrieben:
void sf_start( void )
{
...
if (...) sf = sf_zustand2 ;
}
Das spart die vielen Abfragen in switch-case, kostet aber einen
indirekten Funktionsaufruf mit entry und return.
@Matthias Lipinsky wrote:
Hallo Mathias,
ich habe mich nun immer wieder mit meinem Problem beschäftigt und habe
nun Deinen Vorschlag nochmals genauer angeschaut.
Nur einige Sachen sind mir nicht so klar und bevor ich mein Programm
komplett zerlege möchte ich alles zumindest richtig verstanden wissen.
Du schreibst in Deinem Beispiel
1
#define u8SmSchritt0 0
2
#define u8SmSchritt4 1
3
#define u8SmSchritt5 2
4
//...
weiter unten machst Du dann div. Zuweisungen
z.B. _u8SmAblauf = c_u8SmSchritt5
Also definiert hast Du u8SmSchritt4 und in der SwitchCase Anweisung
verwendest Du c_u8SmSchritt4.
Ist dieses "c_" ein Schreibfehler oder bedeutet es etwas was ich noch
nicht verstehe?
Sorry fast Anfänger
Thomas
Thomas S. wrote:
> Also definiert hast Du u8SmSchritt4 und in der SwitchCase Anweisung> verwendest Du c_u8SmSchritt4.> Ist dieses "c_" ein Schreibfehler oder bedeutet es etwas was ich noch> nicht verstehe?
Falls Matthias momentan nicht online ist und du auch mit einer Antwort
von mir Vorlieb nimmst:
Das sieht in der Tat nach einem Tippfehler aus, der entstanden ist, als
er ein Beispiel hier für Forum zusammenkopiert hat. Geht man von einem
Tippfehler aus, macht alles Sinn. Unterstellt man, dass mit dem c_
(welche wahrscheinlich eine const-ante kennzeichnen soll) ein anderer
Mechanismus ins Spiel kommt, ergibt sich so gut wie kein Sinn mehr.
-> wahrscheinlichste Lösung des Rätsels: Tippfehler beim Zusammenstellen
des Postings. Kommt immer wieder mal vor, egal wie sorgfältig man
versucht solche Dinge zu vermeiden.
@Karl Heinz,
danke für die schnelle Antwort deinerseits.
Ich hatte mir das schon gedacht war mir nur nicht sicher.
Nun hätte ich noch eine Frage bzw. ich brauche einen Denkanstoß.
Ich habe alles so programmiert das alles zeitabhängig abläuft per Timer
im 1ms Takt.
Ich habe insgesamt 16 zeitgeteuerte Abläufe in der ISR wir ein Integer
hochgezählt im 1ms Takt.
Nun muss ich in der ISR bestimmen welcher Timer abgelaufen ist und dies
irgendwie speichern damit es in main also in dieser Schrittkette
bearbeitet werden kann und das ist momentan mein Problem.
ich habe gedacht in 2 byte Werte bitweise speichern also jedes Bit steht
für einen Timer.
In der SwitchCase Schrittkette prüfen welcher bearbeitet werden muss,
ich habe mir den Code ungefähr so vorgestellt.
1
switch_u8SmAblauf
2
{
3
//...
4
casec_u8SmSchritt4:
5
//-- ENTRY ------------------------------------
6
if(_u8SmAblaufLast<>_u8SmAblauf)
7
{
8
_u8SmAblaufLast=_u8SmAblauf;
9
_u16SmAblaufTime=TCNT0;// zB 1kHz Timertakt
10
// einmalige actionen hier, zB ausgang setzen
11
PORTD|=0x01;
12
}
13
//-- ACTION------------------------------------
14
// regelmäßige actionen im schritt hier
15
//-- TRANSITION -------------------------------
16
//-- irgendeine weiterschaltbed. ---
17
if(Variable>=0x80)
18
{
19
_u8SmAblauf=u8SmSchritt2;
20
Variable-=0x80;
21
break;
22
}
23
if(Variable>=0x40)
24
{
25
_u8SmAblauf=u8SmSchritt2;
26
Variable-=0x40;
27
break;
28
}
29
if(Variable>=0x20)
30
{
31
_u8SmAblauf=u8SmSchritt2;
32
Variable-=0x20;
33
break;
34
}
35
if(Variable>=0x10)
36
{
37
_u8SmAblauf=u8SmSchritt2;
38
Variable-=0x10;
39
break;
40
}
41
if(Variable>=0x08)
42
{
43
_u8SmAblauf=u8SmSchritt2;
44
Variable-=0x08;
45
break;
46
}
47
//...
48
casec_u8SmSchritt5:
49
//...
50
}
So würde ich immer nach dem höchsten Bit suchen und eins nach dem
anderen abarbeiten.
Würde das funktionieren ?!? :-)
Thomas
Hallo nochmal,
ich habe vergessen zu erwähnen das meine Timer zwischen 20ms und 10
Sekunden liegen.
Es kann natürlich vorkommen das einmal gleichzeitig mehrer Timer aktiv
sind und bearbeitet werden müssen.
Jedoch zeitlich sollte dies jedoch kein Problem darstellen ich möchte
eben durch die Umstellung erreichen das Abläufe sicherer ablaufen.
Da ich zur Zeit nur Integer und Longs verwende und diese bitweise &= und
|= verknüpfen dauert das immer und dabei können auch Interrupt aktiviert
werden daher wird ca.30 Mal pro Tag ein Timer nicht wieder aktiviert.
Das habe ich nun zwar im Griff jedoch möchte ich die
Programmierung/Abläufe vereinfachen und störungsfreier machen.
Gruß
Thomas
Thomas S. wrote:
> Ich habe insgesamt 16 zeitgeteuerte Abläufe in der ISR wir ein Integer> hochgezählt im 1ms Takt.> Nun muss ich in der ISR bestimmen welcher Timer abgelaufen ist und dies> irgendwie speichern damit es in main also in dieser Schrittkette> bearbeitet werden kann und das ist momentan mein Problem.
Das würde ich ehrlich gesagt nicht in der Satetmaschine machen, sondern
in die ISR verlagern. Ein bischen was darf in einer ISR schon passieren.
Und 16 Software-Timer überprüfen, ob sie auf 0 runtergezählt haben, ist
noch nicht die Welt. Wenn der nächste ISR sowieso erst 1ms später kommt,
bleibt noch massig Zeit.
Bist du knapp an Speicher?
Wenn nein: Würde da nicht lange mit Speicher knausern.
1
structTimer
2
{
3
uint16_tValue;// zählt von einer vorgegebenen Zeit runter
und in der Statemachine setzt du dann einfach den Zeitbedarf beim
richtigen Timer in dessen Value ein. Die ISR tickt diesen Wert mit
Millisekunden-Auflösung runter. Ist die Zeit abgelaufen, dann wird der
Value Wert des Timers zu 0 und bleibt auch 0. Deine Statemachine muss
also nur (per Zustand) warten, dass Value 0 geworden ist - dann ist
seine Zeit abgelaufen.
Thomas S. wrote:
> Das habe ich nun zwar im Griff jedoch möchte ich die> Programmierung/Abläufe vereinfachen und störungsfreier machen.
Hmm. Darf ich dir eine Idee unterbreiten?
Das Multitasking des kleinen Mannes.
Jeder Task ist eine Statemachine und wird Round Robin massig
durchgeschaltet. Mit ein klein wenig Selbstdisziplin (Warteschleifen
sind verboten) ist sowas ganz einfach realisierbar und sehr zuverlässig.
Ich stell mal was zusammen und poste wieder, wenn ich was habe
Berti hats erfasst.
Erstaunlich, wie sich unsere Tasks gleichen :-)
Hier mit der zugehörigen Infrastruktur. Das eignet sich dann auch
wunderbar dafür, dass jeder Task in eine eigene *.c Datei ausgelagert
wird, sodass man in einem File immer nur einen Themenkreis (eine
Aufgabe) behandeln muss ohne sich um die anderen kümmern zu müssen.
Wie gesagt: Die einzige Einschränkung ist: Warteschleifen sind verboten.
Statt dessen gibt es in der Statemaschine einen Zustand, der die
Ablaufbedingung (Timer runtergezählt, Eingangspin hat erwarteten Wert,
...) überprüft und gegebenenfalls in einen neuen Zustand wechselt. Ist
die Bedingung nicht erfüllt, bleibts beim Wartezustand und die
Statemachine gibt die Kontrolle wieder an den Scheduler ab.
1
#include<avr/io.h>
2
#include<avr/interrupt.h>
3
4
//
5
// Timer Verwaltung
6
//
7
structTimer
8
{
9
uint16_tValue;// zählt von einer vorgegebenen Zeit runter
returnWAIT_9S;// beim nächsten Aufruf soll die Statemachine im Zustand WAIT_9S sein
137
break;
138
139
caseWAIT_9S:
140
if(!IsTimerStopped(1))// Timer abgelaufen?
141
returnWAIT_9S;// nein: Task bleibt im Zustans WAIT_9S
142
143
returnWORK_9S;// ja: nächster Zustand ist: 9 Sek sind vorbei, jetzt wird gearbeitet!
144
break;
145
146
caseWORK_9S:
147
PORTA=PORTA^0x02;// na ja, soviel gibts nicht zu tun
148
149
StartTimer(TASK_9_TIMER,WAIT_TIME_9);
150
returnWAIT_9S;
151
break;
152
}
153
154
returnstate;
155
}
156
157
intmain()
158
{
159
//
160
// Timer initialisieren und ISR anhängen
161
//
162
163
//
164
// Tasks registrieren
165
//
166
RegisterTask(0,Blink_2S,INIT);
167
RegisterTask(1,Blink_9S,INIT);
168
169
//
170
// hui - und los gehts
171
// ab jetzt arbeiten alle Tasks (fast) gleichzeitig
172
//
173
while(1){
174
RunScheduler();
175
}
176
}
PS: Ich hab jetzt nicht auf maximale Performance geachtet. Ich wollte
dir das Prinzip zeigen ohne da jetzt in kryptischen Code abzugleiten.
Edit: Wenn du globale Variablen verwendest, kannst du aus allen Tasks
auf sie zugreifen und so zb. Tasks miteinander kommunizieren lassen.
Diese Zugriffe brauchen dann auch nicht speziell abgesichert werden,
durch die Taskstruktur ist gewährleistet, dass es keine Probleme mit
atomarem Zugriff geben kann.
Das gibts auch als fertigen Code:
Beitrag "Wartezeiten effektiv (Scheduler)"
Mit dem besonderen Trick, daß die Ereignisse sortiert werden. Dadurch
müssen nicht bei jedem Timertick sämtliche Timerslots verglichen werden.
Periodische Ereignisse erreicht man ganz einfach, indem sie sich selber
wieder reinstellen.
Peter
kennt ihr dieses framework:
http://www.state-machine.com/products/index.htm
hervorragendes statemachine-framework.
- hierarchische sm
- event-delivery/queues
- time-events
- rtos optional
- tracer/logger
kostenlos für private anwendung,
sehr guter kostenloser support im forum
Ich habe nun mal Deinen Code durchgearbeitet damit ich alles verstehe
wie es so funktioniert da ich nicht so bewandert bin mit Strukturen
musse ich mir noch einiges erarbeiten.
Nun habe ich gesehen das Du beim setzen/stopen eines Timers immer die
Interript's global abschaltest und danach wieder einschaltest.
Ich habe das bei mir im Code nicht gemacht eventuell würde das schon
mein Problem lösen.
Meine generelle Frage ist sollte man dies immer so handhaben oder ist es
nur Zufall das es hier so ist.
Wenn der Interrupt wieder eingeschaltet wird werden dann alle Interrupts
die in der Zwischenzeit aufgelaufen sind bearbeitet?
Ich verwende einen MEGA128 also eigentlich kein Speicherproblem zur Zeit
ist noch 40%Flash frei da mir die Displaysteuerung und die dazugehörigen
Strings sehr viel Flash gefressen haben.
Gruß und Danke schon mal
Thomas
Thomas S. wrote:
> Meine generelle Frage ist sollte man dies immer so handhaben oder ist es> nur Zufall das es hier so ist.
value ist eine 16 Bit Variable, muss also mittels 2 8-Bit Zugriffe
gelesen/gesetzt werden.
Ich will verhindern, dass mir ein Interrupt dazwischen knallt, wenn das
Low-Byte schon geholt ist und das High-Byte noch nicht.
Stichwort: atomarer Zugriff.
> Wenn der Interrupt wieder eingeschaltet wird werden dann alle Interrupts> die in der Zwischenzeit aufgelaufen sind bearbeitet?
ja
Karl-Heinz,
Danke für Deine schnelle Antwort.
Ich verwende ja zur Zeit ein Integer bei dem jedes Bit für einen Timer
steht und mittels
1
if(TimerStatus&0x08)
2
{
3
//... CODE
4
}
schaue ich ob der Timer abgelaufen ist (BIT = 1)
Nun habe ich im AVR-GCC-Tutorial etwas über Bitfelder gelesen.
Könnte ich nun ein Bitfeld wie folgt anlegen
1
struct{
2
unsignedTimer1:1;// 1 Bit für Timer1
3
unsignedTimer2:1;// 1 Bit für Timer2
4
unsignedTimer3:1;// 1 Bit für Timer3
5
unsignedTimer4:1;// 1 Bit für Timer4
6
unsignedTimer5:1;// 1 Bit für Timer5
7
unsignedTimer6:1;// 1 Bit für Timer6
8
unsignedTimer7:1;// 1 Bit für Timer7
9
unsignedTimer8:1;// 1 Bit für Timer8
10
//...
11
unsignedTimer16:1;// 1 Bit für Timer16
12
}TimerStatus;
Auslesen könnte ich es dann mit
1
if(TimerStatus.Timer1)
2
{
3
//... CODE
4
}
Würde das eine Verbesserung bringen im Bezug auf Schnelligkeit gegenüber
dem ersten Beispiel?
Gruß
Thomas
Hallo!!
Erfahrungsgemäss weiss Herr Buchegger ja was er tut. Ich verstehe es nur
mal wieder nicht :-).
Karl heinz Buchegger wrote:
> Thomas S. wrote:>>
1
>structTimer
2
>{
3
>uint16_tValue;// zählt von einer vorgegebenen Zeit runter
schreiben können?
Sicher wird beides zur Compilezeit evaluiert und es sollte doch auch der
gleiche Code entstehen?
Aber warum macht man das dann so?
Viele Grüße,
Klaus
@ Karl Heinz,
ich habe Deinen Code nun endlich durchgearbeitet hatte in den letzten
Monaten teilweise keine Zeit/Lust aber das hat sich in den letzten
Wochen geändert.
Ich habe die funktionsweise Deines Codes verstanden und will es nun auch
so probieren.
Um nun alle Abläufe gleich zu machen will ich auch die UART
Kommunikation dazu umstellen.
Zur Zeit läuft die UART Verbindung recht problemlos das ganze ist per
ISR gesteuert ohne WAIT etc.
Ich habe mir nun überlegt auch Strukturen für die UART zu erstellen
diese sollten der besseren Übersichtlichkeit halber in eine UART
Struktur aufgeteilt werden und danach im RunScheduler() nacheinander
abgearbeitet werden?
Hier mal so wie ich es mir vorstelle.
> schreiben können?>> Sicher wird beides zur Compilezeit evaluiert und es sollte doch auch der> gleiche Code entstehen?
Ja, hätte man auch machen können.
> Aber warum macht man das dann so?
Gewohnheit :-)
Im Ernst: Die sizeof Variante hat den Vorteil, auch dann korrekt zu
funktionieren, wenn man so etwas macht ....
...., wenn also der Compiler anhand der Initialisierungen die Arraygröße
festlegt.
Spielt in diesem konkreten Fall keine Rolle. Allerdings habe ich die
Erfahrung gemacht, dass es gut ist, wenn man Dinge möglichst immer auf
die gleiche Art und Weise löst.
Thomas S. schrieb:
> Ist dies empfehlenswert oder wäre es besser nur eine Struktur zu> erstellen und alles und diese zu packen?
Wieso willst du für die UART eine eigene Taskstruktur aufbauen? UART ist
ja nicht besonders zeitkritisch, so dass es m.E. nicht unbedingt
notwendig ist, jeweils abwechselnd einen 'normalen' Task und einen
'UART' Task abzuarbeiten. Falls doch, könnte man immer noch in der
Haupttaskliste abwechselnd einen 'normalen' Task und einen 'UART' Task
einstellen.
Kurz und gut: Ich seh eigentlich keinen Grund, dass der Scheduler wissen
muss, das es spezielle Tasks, nämlich die UART Tasks, gibt.
Karl heinz Buchegger schrieb:
> Im Ernst: Die sizeof Variante hat den Vorteil, auch dann korrekt zu> funktionieren, wenn man so etwas macht ....>> .....> Spielt in diesem konkreten Fall keine Rolle. Allerdings habe ich die> Erfahrung gemacht, dass es gut ist, wenn man Dinge möglichst immer auf> die gleiche Art und Weise löst.
Ahja... wieder was gelernt :-).