Forum: Mikrocontroller und Digitale Elektronik Programm bzw. Ablaeufe steuern


von Thomas S. (thomass)


Lesenswert?

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

von Besserwisserle (Gast)


Lesenswert?

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

von Johannes M. (johnny-m)


Lesenswert?

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

von Thomas S. (thomass)


Lesenswert?

@ 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

von Karl H. (kbuchegg)


Lesenswert?

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

von Thomas S. (thomass)


Lesenswert?

@ 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

von Thomas S. (thomass)


Lesenswert?

@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

von 6632 (Gast)


Lesenswert?

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.

von Matthias L. (Gast)


Lesenswert?

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 ;-)
1
#define  u8SmSchritt0   0
2
#define  u8SmSchritt4   1
3
#define  u8SmSchritt5   2
4
//...
5
uint8_t  _u8SmAblauf     = u8SmSchritt0;
6
uint8_t  _u8SmAblaufLast = 255;
7
//..
8
switch _u8SmAblauf
9
{
10
//...
11
case c_u8SmSchritt4:
12
  //-- ENTRY ------------------------------------
13
  if ( _u8SmAblaufLast <> _u8SmAblauf )
14
     {
15
       _u8SmAblaufLast = _u8SmAblauf;
16
       _u16SmAblaufTime = TCNT0; // zB 1kHz Timertakt
17
       // einmalige actionen hier, zB ausgang setzen
18
       PORTD |= 0x01;
19
     }
20
  //-- ACTION------------------------------------
21
  // regelmäßige actionen im schritt hier
22
  //-- TRANSITION -------------------------------
23
  //-- irgendeine weiterschaltbed. ---
24
  if ( Variable == 0x4543 ) _u8SmAblauf = c_u8SmSchritt5
25
  //-- andere weiterschaltbed. -------
26
  if ( PINC & (1<<PINC3) )  _u8SmAblauf = c_u8SmSchritt1
27
  //-- Zeit rum 16,332sek ------------
28
  if ( (TCNT0-u16SmAblaufTime ) > 16332 ) _u8SmAblauf = c_u8SmSchritt7
29
//...
30
case c_u8SmSchritt5:
31
}

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.

von Hans-jürgen H. (hjherbert) Benutzerseite


Lesenswert?

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.

von Thomas S. (thomass)


Lesenswert?

@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

1
> switch _u8SmAblauf
2
{
3
//...
4
case c_u8SmSchritt4:
5
//...
6
  if ( Variable == 0x4543 ) _u8SmAblauf = c_u8SmSchritt5
7
  //-- andere weiterschaltbed. -------
8
  if ( PINC & (1<<PINC3) )  _u8SmAblauf = c_u8SmSchritt1
9
  //-- Zeit rum 16,332sek ------------
10
  if ( (TCNT0-u16SmAblaufTime ) > 16332 ) _u8SmAblauf = c_u8SmSchritt7
11
//...
12
case 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

von Karl H. (kbuchegg)


Lesenswert?

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.

von Thomas S. (thomass)


Lesenswert?

@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
case c_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
case c_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

von Thomas S. (thomass)


Lesenswert?

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

von Karl H. (kbuchegg)


Lesenswert?

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
struct Timer
2
{
3
  uint16_t  Value;    // zählt von einer vorgegebenen Zeit runter
4
                      // auf 0. Zeit wird in ms angegeben
5
};
6
7
struct Timer AllTimers[ 16 ];
8
#define NR_TIMERS ( sizeof( AllTimers ) / ( sizeof( *AllTimers ) ) )
9
10
ISR( .... )
11
{
12
  MilliSekunden++;
13
14
  for( i = 0; i < NR_TIMERS; ++i ) {
15
    if( AllTimers[i].Value > 0 )
16
      AllTimers[i].Value--;
17
  }
18
}

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.

von Karl H. (kbuchegg)


Lesenswert?

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

von Berti (Gast)


Angehängte Dateien:

Lesenswert?

Ja kooperatives Multitasking mit einfachem dispatcher -> siehe Anhang!

von Karl H. (kbuchegg)


Lesenswert?

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
struct Timer
8
{
9
  uint16_t  Value;    // zählt von einer vorgegebenen Zeit runter
10
                      // auf 0. Zeit wird in ms angegeben
11
};
12
13
struct Timer AllTimers[ 16 ];
14
#define NR_TIMERS ( sizeof( AllTimers ) / ( sizeof( *AllTimers ) ) )
15
16
void StartTimer( uint8_t Nr, uint16_t Time )
17
{
18
  if( Nr < NR_TIMERS ) {
19
    cli();
20
    AllTimers[Nr].Value = Time;
21
    sei();
22
  }
23
}
24
25
uint8_t IsTimerStopped( uint8_t Nr )
26
{
27
  uint16_t curVal;
28
  
29
  cli();
30
  curVal = AllTimers[Nr].Value;
31
  sei();
32
  
33
  return curVal == 0;
34
}
35
36
ISR( .... )   // wird alle Millisekunden aufgerufen
37
{
38
  uint8_t i;
39
  
40
  for( i = 0; i < NR_TIMERS; ++i ) {
41
    if( AllTimers[i].Value > 0 )
42
      AllTimers[i].Value--;
43
  }
44
}
45
46
//
47
// Taskverwaltung. Jeder Task ist eine Statemachine für sich selbst
48
//
49
typedef uint8_t (*ExecuteFnct)( uint8_t );
50
51
struct Task {
52
  uint8_t     currentState;
53
  ExecuteFnct execute;
54
};
55
56
struct Task AllTasks[8];
57
#define MAX_TASKS ( sizeof( AllTasks ) / sizeof( *AllTasks ) )
58
59
void RegisterTask( uint8_t Nr, ExecuteFnct fnct, uint8_t StartState )
60
{
61
  if( Nr < MAX_TASKS ) {
62
    AllTasks[Nr].currentState = StartState;
63
    AllTasks[Nr].execute = fnct;
64
  }
65
}
66
67
void RunScheduler()
68
{
69
  static uint8_t NextTask = 0;
70
  
71
  if( AllTasks[NextTask].execute )
72
    AllTasks[NextTask].currentState = (*AllTasks[NextTask].execute)( AllTasks[NextTask].currentState );
73
    
74
  NextTask++;
75
  if( NextTask == MAX_TASKS )
76
    NextTask = 0;
77
}
78
79
//
80
// und jetzt die Tasks ....
81
//
82
83
// States die alle Tasks haben
84
#define INIT    0
85
86
//
87
// *****************************************************************
88
// Task: Alle 2 Sekunden eine Aktion ausführen
89
// States nur für den 2 Sekunden Task
90
#define WAIT_2S   1
91
#define WORK_2S   2
92
93
uint8_t Blink_2S( uint8_t state )
94
{
95
  switch( state ) {
96
  
97
    case INIT:
98
      StartTimer( 0, 2000 );   // Timer starten, 2 Sekunden
99
      return WAIT_2S;        // beim nächsten Aufruf soll die Statemachine im Zustand WAIT_2S sein
100
      break;
101
      
102
    case WAIT_2S:
103
      if( !IsTimerStopped( 0 ) )   // Timer abgelaufen?
104
        return WAIT_2S;   // nein: Task bleibt im Zustans WAIT_2S
105
      
106
      return WORK_2S;     // ja: nächster Zustand ist: 2 Sek sind vorbei, jetzt wird gearbeitet!
107
      break;
108
      
109
    case WORK_2S:
110
      PORTA = PORTA ^ 0x01;   // na ja, soviel gibts nicht zu tun
111
      
112
      StartTimer( 0, 2000 );
113
      return WAIT_2S;
114
      break;
115
  }
116
  
117
  return state;
118
}
119
      
120
//
121
// *****************************************************************
122
// Task: Alle 9 Sekunden eine Aktion ausführen
123
// States nur für den 9 Sekunden Task
124
#define WAIT_9S   1
125
#define WORK_9S   2
126
127
#define TASK_9_TIMER 1
128
#define WAIT_TIME_9  9000
129
130
uint8_t Blink_9S( uint8_t state )
131
{
132
  switch( state ) {
133
  
134
    case INIT:
135
      StartTimer( TASK_9_TIMER, WAIT_TIME_9 );   // Timer starten, 9 Sekunden
136
      return WAIT_9S;        // beim nächsten Aufruf soll die Statemachine im Zustand WAIT_9S sein
137
      break;
138
      
139
    case WAIT_9S:
140
      if( !IsTimerStopped( 1 ) )   // Timer abgelaufen?
141
        return WAIT_9S;   // nein: Task bleibt im Zustans WAIT_9S
142
      
143
      return WORK_9S;     // ja: nächster Zustand ist: 9 Sek sind vorbei, jetzt wird gearbeitet!
144
      break;
145
      
146
    case WORK_9S:
147
      PORTA = PORTA ^ 0x02;   // na ja, soviel gibts nicht zu tun
148
      
149
      StartTimer( TASK_9_TIMER, WAIT_TIME_9 );
150
      return WAIT_9S;
151
      break;
152
  }
153
  
154
  return state;
155
}
156
      
157
int main()
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.

von Peter D. (peda)


Lesenswert?

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

von jupp (Gast)


Lesenswert?

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

von Thomas S. (thomass)


Lesenswert?

Karl heinz Buchegger wrote:
>
>
1
> //... 
2
> void StartTimer( uint8_t Nr, uint16_t Time )
3
> {
4
>   if( Nr < NR_TIMERS ) {
5
>     cli();
6
>     AllTimers[Nr].Value = Time;
7
>     sei();
8
>   }
9
> }
10
> 
11
> uint8_t IsTimerStopped( uint8_t Nr )
12
> {
13
>   uint16_t curVal;
14
> 
15
>   cli();
16
>   curVal = AllTimers[Nr].Value;
17
>   sei();
18
> 
19
>   return curVal == 0;
20
> }
21
> 
22
> //...
23
>

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

von Karl H. (kbuchegg)


Lesenswert?

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

von Thomas S. (thomass)


Lesenswert?

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
   unsigned Timer1:1; // 1 Bit für Timer1
3
   unsigned Timer2:1; // 1 Bit für Timer2
4
   unsigned Timer3:1; // 1 Bit für Timer3
5
   unsigned Timer4:1; // 1 Bit für Timer4
6
   unsigned Timer5:1; // 1 Bit für Timer5
7
   unsigned Timer6:1; // 1 Bit für Timer6
8
   unsigned Timer7:1; // 1 Bit für Timer7
9
   unsigned Timer8:1; // 1 Bit für Timer8
10
   //...
11
   unsigned Timer16: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

von Klaus W. (Firma: privat) (texmex)


Lesenswert?

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
> struct Timer
2
> {
3
>   uint16_t  Value;    // zählt von einer vorgegebenen Zeit runter
4
>                       // auf 0. Zeit wird in ms angegeben
5
> };
6
> 
7
> struct Timer AllTimers[ 16 ];
8
> #define NR_TIMERS ( sizeof( AllTimers ) / ( sizeof( *AllTimers ) ) )
9
>

Hätte man hier nicht einfach
1
 #define NR_TIMERS 16
2
 struct Timer AllTimers[ NR_TIMERS ];
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

von Thomas S. (thomass)


Lesenswert?

@ 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.
1
void RunScheduler()
2
{
3
  static uint8_t NextTask = 0,NextUARTTask = 0;
4
  
5
  if( AllTasks[NextTask].execute )
6
    AllTasks[NextTask].currentState = (*AllTasks[NextTask].execute)( AllTasks[NextTask].currentState );
7
    
8
  NextTask++;
9
  if( NextTask == MAX_TASKS )
10
    NextTask = 0;
11
12
  if( AllUARTTasks[NextUARTTask].UARTexecute )
13
    AllUARTTasks[NextUARTTask].currentUARTState = (*AllUARTTasks[NextUARTTask].UARTexecute)( AllUARTTasks[NextUARTTask].currentUARTState );
14
    
15
  NextUARTTask++;
16
  if( NextUARTTask == MAX_UARTTASKS )
17
    NextUARTTask = 0;
18
}

Ist dies empfehlenswert oder wäre es besser nur eine Struktur zu 
erstellen und alles und diese zu packen?

Danke schon mal für die Antworten.

Thomas

von Karl H. (kbuchegg)


Lesenswert?

Klaus W. schrieb:

> Hätte man hier nicht einfach
>
1
>  #define NR_TIMERS 16
2
>  struct Timer AllTimers[ NR_TIMERS ];
3
>
> 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 ....
1
int Values[] = { 1, 3, 7, 9 };
2
#define NR_VALUES ( sizeof( Values ) / sizeof( *Values ) )

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

von Karl H. (kbuchegg)


Lesenswert?

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.

von Klaus W. (Firma: privat) (texmex)


Lesenswert?

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

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.