Forum: Mikrocontroller und Digitale Elektronik Zeitgesteuerter Ablauf mittels einer "State Mashine"


von Thomas S. (thomass)


Lesenswert?

Hallo Zusammen,

ich habe hier ein Programm geschrieben das verschiedene Eingänge, Geräte 
nacheinander auslesen soll.
Zur hardware:
Ich habe einen Mega128 und an diesem eine Tasterturmatrix,Lesegerät per 
RS485 und noch einen Port der ausgelesen werden muss.
Das Gerät an der RS485 Schnittstelle hat eine Baud Rate von 9600 diese 
ist fix.
Nun soll dieses Gerät im 500ms Rythmus ausgelesen werden die Matrix alle 
40ms und der Port alle 40ms.
Es laufen noch andere Timer der State mashine insgesammt 15 Abläufe.
Ich habe den Timer0 so programmiert das dieser jede Millisekunde einen 
Interruppt auslöst.
Durch diesen Interrupt wird eine Variable Timer_MSek hochgezählt.

Im Programm verwende ich 2 Integer Variablen delay_MSek_01 und 
delay_MSek_02 diese zeigen mir ob ein Timer gestartet ist und welcher 
abgelaufen ist.
Jedes Bit dieser Variablen steht für einen "TIMER".
Will ich nun Timer 1 Starten setze ich das LSB auf "1" und schreibe und 
eine Variable Timer_Msek_01 den Wert von der Variable Timer_MSek + 
xxxMSek.

Nun wird im Interrupt von Timer0 jedesmal geprüft welche Timer_MSek_xx 
abgelaufen sind mittels vergleichen der Timer_MSek und Timer_MSek_xx.
Sind beide Werte gleich ist die Zeit abgelaufen und das Bit wird im 
delay_MSek_01 gelöscht und im delay_MSek_02 gesetzt.

Im Programm schaue ich nach welche Timer von der Variable delay_MSek_02 
abgelaufen sind und führe die dementsprechenden Befehle aus.

Das ganze funktioniert auch ganz gut nur die beim Auslesen der 
Tasterturmatrix wird nach einer undefinierten Zeit das Bit gelöscht.
Ich habe mich nun auf die suche gemacht wo dieses gelöscht wird konnte 
jedoch keinen fehler erkennen.
Nun habe ich mal das JTAG MK2 drangehängt und dabei ist mir folgendes 
aufgefallen.
Es sind 2 Timer am Laufen alle 30ms der eine Tasterturmatrix der andere 
auslesen eines Ports.
Wenn ich nun mir die Zustände der delay_MSek_01 und delay_MSek_02 
mitlogge schreiben in ein Array dann sehe ich folgenden Ablauf

1. Timer Tasterturmatrix gesetzt --> Zeit nicht abgelaufen also 
delay_MSek_01
2. Timer Portauslesen gesetzt --> Zeit abgelaufen also delay_MSek_02
3.Prüfen Timer Portauslesen abgelaufen (Funktion)  --> JA Bit löschen
4. Nach diesem Vorgang prüfe ich in der Funktion ob der Timer 
Tasterturmatrix noch gesetzt ist und dieser ist OK --> Per Breakpoint.
5. Nun prüfe ich in der if Schleife als erstes ob der Timer 
Tasterturmatrix noch gesetzt ist per if Abfrage und dort ist er 
gelöscht.

Nun laufen die seriellen Schnittstellen auch per Interrupt 
senden,empfangen etc.
Kann es sein das in einem ungünstigen Zeitpunt ein Interrupt ausgelöst 
wird und danach irgendwie dieses BIT gelöscht wird?

Das ganze Programm läuft mal 2Std,4Std,8Std,24Std,48Std ohne Probleme 
und dann ist dieses Bit gelöscht.
Nun gut ich könnte in der for(;;) Schleife reinschreiben das ich das Bit 
setze wenn beide nicht gesetzt sind nur somit habe ich das Problem nicht 
gelöst.

Gruß

Thomas

von Thomas S. (thomass)


Lesenswert?

Hallo Zusammen,

ich denke das ich das ganze beim letzten Versuch einfach schlecht und zu 
komplex erklären wollte.
Da jedoch das Problem immer noch vorhanden ist und mir die Ideen 
ausgehen starte ich einen neuen Versuch.
Also ich wollte mein Programm komplett zeitgesteuert ablaufen lassen 
also per Timer.
Hierzu habe ich 2 Integer verwendet bei dem jedes Bit für einen 
Zeitablauf steht z.B 0x0001 ist der Timer um die Tasterturmatrix 
auszulesen,0x0002 ist um eine Anfrage an einen Transponderleser zu 
senden usw.
Der erste Integer delay_mSek_status_1 wird verwendet um einen 
Timerablauf zu starten, beim start des Timerablaufs wir das 
entsprechenede Bit gesetzt und ein Ablaufwert in eine Variable 
gespeichert
1
void Set_delay_mSek_002(void)
2
{
3
  if(((delay_mSek_status_1 & Mask_delay_002) == 0x0000) && ((delay_mSek_status_2 & Mask_delay_002) == 0x0000))
4
  {
5
    delay_mSek_status_1 |= Mask_delay_002;
6
    delay_mSek_002 = Timercounter_mSek + Reloadwerte_Timerallgemein_mSek[1];
7
  
8
  }
9
}

Die Variable Timercounter_mSek wird ISR des Timer1 hochgezählt  danach 
wird Bitweise das delay_mSek_status_1 durchsucht ob ein Timerablauf 
aktiviert ist ist dies der Fall wird die Variable hier delay_mSek_002 
mit dem Timercounter_mSek verglichen ist dieser gleich wird das 
entsprechende Bit im delay_mSek_status_1 gelöscht und im 
delay_mSek_status_2 gesetzt.
Alle Bits die im delay_mSek_status_2 gesetzt sind stehen für einen 
abgelaufenen Timer.
Im main wird nun delay_mSek_status_2 per Funktion durchsucht ob zB. Bit 
0x0001 gesetzt ist, ist dieses gesetzt wird der entsprechende Code 
abgearbeitet.

Die seriellen Schnittstellen funktionieren auch per Interrupt.

Ich habe nun folgendes Problem.

1. Manchmal wird nicht erkannt das "delay_mSek_002 == Timercounter_mSek" 
und das entsprechende Bit nicht gelöscht/gesetzt. Dann läuft das ganze 
65.535 Sekunden lang bis Timercounter_mSek erneut den Wert von 
delay_mSek_002 hat.

2. Manchmal fehlen beide Bits also das eine was den Timer startet und 
das andere welches das erreichen des Ablaufs signalisiert.

Ich habe den Code scho zig mal durchsucht aber diese Bits werden im 
gesammten Code nur einmal gesetzt,gewechselt(beim Ablauf ISR) und 
gelöscht.

Kann es sein das in einem ungünstigen Augenblick ein ISR ausgelöst wird 
und danach eines dieser Phänomene auftritt?
Ich habe die ISR's mal komplett in den Anhang gepackt.
Die ISR von Timer1 und 0 ist zwar etwas lang jedoch habe ich das deshalb 
so gemacht damit die Variablen bei jedem Interupt vergliechen werden.

Ich habe leider nicht zu State Mashine gefunden nur das man das damit 
macht jedoch kein Beispiel(Code).
Ich beschäftige mich noch nicht so lange mit uC's und bin sozusagen 
fortgeschrittener Anfänger.

Gruß

Thomas

von Thomas S. (thomass)


Angehängte Dateien:

Lesenswert?

Verflixt Vorschau mit Absenden verwechselt ich hoffe alles stimmt hier 
noch der Anhang.

Gruß

Thomas

von Matthias L. (Gast)


Lesenswert?


von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

Du hast einen
> Mega128
und dann im Programm durchgehend longs:
1
  if(((UART_status & 0xffff0000) == 0x08010000) | ((UART_status & 0xffff0000) == 0x08020000) |
Alle Achtung, Hut ab.

Dir ist schon klar, dass du einen kleinen 8-Bitter vor dir hast? Mit 
32-Bit-Variablen und -Vergleichen und -Berechnungen. Das dauert.

Und dann solche Monster-Interrupt-Routinen...
Donnerwetter, das zwingt den stärksten Kerl in die Knie.

> Kann es sein das in einem ungünstigen Zeitpunt ein Interrupt ausgelöst
> wird und danach irgendwie dieses BIT gelöscht wird?
Ich würde sagen, du hast
1. ein Semaphoren-Problem (dein gelöschtes Bit)
2. ein Rechenleistungsproblem (deine Long-Variablen)


Ich habe mir deinen Code nicht näher angeschaut, aber ich habe den 
Eindruck, dass du etwas Einfaches kompliziert aufziehst... :-/


EDIT:
@ Matthias Lipinsky
Du hast es auf den Punkt gebracht   ;-)

von Peter D. (peda)


Lesenswert?

Du hast entweder ein volatile oder atomicity Problem bzw. beides.

Interruptvariablen > 8Bit müssen im Main atomar zugegriffen werden.
Interruptvariablen = 8Bit, die im Main gelesen, geändert und geschrieben 
werden, müssen auch atomar zugegriffen werden.

Z.B. X |= Y ist so ein Read-Modify-Write:
1
#include <util\atomic.h>
2
...
3
ATOMIC_BLOCK(ATOMIC_FORCEON)
4
{
5
  X |= Y;
6
}
7
...

Ausnahme, X ist ein IO-Port und in Y ist nur ein Bit gesetzt. Das geht 
atomar (SBI,CBI).


Peter

von Thomas S. (thomass)


Lesenswert?

@ Matthias Lipinsky,

in Deinem Betrag gehst Du davon aus das immer ein fester Ablauf gegeben 
ist.
1-2-3-4-5-...
Dies trifft bei mir jedoch nicht zu es muss alles flexibel sein das was 
gerade bearbeitet werden soll wird gemacht.
Vergebe ich nun einer Ablaufvariablen eine feste Nummer wie kann ich 
dann sagen es soll sowohl 1-3-6 bearbeitet werden.
Daher habe ich es Bitbasierend gemacht.
Somit kann das Programm erkennen was bearbeitet wewrden soll.
Meine Displaysteuerung funktioniert nach Deiner Methode für jeden 
Schritt eine Nummer dort ist es logisch.

@Lothar,
OK die Longs sind vielleicht nicht gut gewählt aber das schnellste 
Ereignis ist alle 40ms danach 60,250,1000,6000.
Ist also ein Timer abgelaufen wird er zwar wieder gestartet aber der 
Mega128 hat genügend Zeit.
Die UARD arbeitet mit 9600 und gesendet wird alle 250ms.
Ich denke das ich das Long eventuell in ein Byte oder int ändern kann.
Es kann schon sein das das lesen schreiben per UARD kompliziert 
geschrieben ist wollte UARD0 und 1 in eine Variable packen vieleicht 
sollte ich 2 byte variablen daraus machen.
Gute Denkanstoß

@Peter,

> Interruptvariablen > 8Bit müssen im Main atomar zugegriffen werden.
> Interruptvariablen = 8Bit, die im Main gelesen, geändert und geschrieben
> werden, müssen auch atomar zugegriffen werden.

Ich verstehe Deinen Beitrag nicht ganz was meinst Du mit "atomar 
zugegriffen werden"

Gruß

Thomas

von Unbekannter (Gast)


Lesenswert?

> Ich verstehe Deinen Beitrag nicht ganz was meinst Du mit "atomar
> zugegriffen werden"

Wenn Du in C schreibst:

   var |= mask;

Macht Dein Compiler (ungefähr) daraus:

  tmp1 = var;
  tmp1 = tmp | mask;
  var = tmp1;

Und überall kann ein Interrupt dazwischen funken und "var" z.b. ändern. 
Dann kommst Chaos raus.


Und es kommt noch schlimmer. Alles was länger als 8 Bit ist, ist nie 
atomar. Also aus

  unsigned long var = ...;
  unsigned long mask = ...;

  var |= mask;

macht der Compiler (ungefähr):

  char * ptr1 = (char *) &var;
  char * ptr2 = (char *) &mask;
  char tmp1, tmp2;

  tmp1 = *ptr1;
  tmp2 = *ptr2;
  tmp1 = tmp1 | tmp2;
  *ptr1 = tmp1;
  *ptr2 = tmp2;
  ptr1 = ptr1 + 1;
  ptr2 = ptr2 + 1;
  tmp1 = *ptr1;
  tmp2 = *ptr2;
  tmp1 = tmp1 | tmp2;
  *ptr1 = tmp1;
  *ptr2 = tmp2;
  ptr1 = ptr1 + 1;
  ptr2 = ptr2 + 1;
  tmp1 = *ptr1;
  tmp2 = *ptr2;
  tmp1 = tmp1 | tmp2;
  *ptr1 = tmp1;
  *ptr2 = tmp2;
  ptr1 = ptr1 + 1;
  ptr2 = ptr2 + 1;
  tmp1 = *ptr1;
  tmp2 = *ptr2;
  tmp1 = tmp1 | tmp2;
  *ptr1 = tmp1;
  *ptr2 = tmp2;


Und überall kann ein Interrupt dazwischen funken.

Un das was der Compiler wirklich erzeugt, kann auch vollkommen anders 
aussehen, weil er optimiert, umsortiert etc.

von Unbekannter (Gast)


Lesenswert?

Argl:

     *ptr2 = tmp2;

Mach der Compiler natürlich nicht.



Hier mal ein konkretes Beispiel. Aus:
1
extern volatile unsigned long var;
2
extern volatile unsigned long mask;
3
4
void test()
5
{
6
  var |= mask;
7
}

macht der Compiler (mit -O2 und bereinigt):
1
test:
2
        lds r18,var
3
        lds r19,(var)+1
4
        lds r20,(var)+2
5
        lds r21,(var)+3
6
        lds r24,mask
7
        lds r25,(mask)+1
8
        lds r26,(mask)+2
9
        lds r27,(mask)+3
10
        or r24,r18
11
        or r25,r19
12
        or r26,r20
13
        or r27,r21
14
        sts var,r24
15
        sts (var)+1,r25
16
        sts (var)+2,r26
17
        sts (var)+3,r27
18
        ret

Und wie gesagt, überall kann ein Interrupt auftreten. Dann kommt alles 
durcheinander.

von Thomas S. (thomass)


Lesenswert?

@unbekannter,

danke für die ausführliche Beschreibung hat mir viel geholfen um das 
Problem zu verstehen.

Werde versuchen das ganze etwas zu vereinfachen und wenn möglich nur mit 
8Bit Werten zu arbeiten.
Bis das läuft werde ich mal versuchen zu erkennen wann etwas 
dazwischenfunkt und dann die Werte auf einen Grundwert zu setzen damit 
das ganze dann wieder am Anfang anfängt.
Ich habe mir eine Variable vorgestellt die wärend das Programm läuft 
immer hochgezählt wird.Sollte das Programm mal wieder nicht mehr alle 
Programmteile bearbeiten wird die Variable nicht mehr verändert das wird 
erkannt und alles wieder Grundeingestellt.

Thomas

von Thomas S. (thomass)


Lesenswert?

Hallo Zusammen,

ich habe nun einiges verändert und viele Bitorientierte Abfragen gegen 
Schrittketten abgeändert.

Nur bei einen bitorientierten Ablauf komme ich irgentwie nicht weiter da 
ich diesen nicht durch eine Schrittkette ersetzen kann.
Ich habe z.B. ein Integerwert bei dem jedes Bit für einen Zeitablauf 
steht.
Es kann also sein das ein oder mehrere Abläufe bearbeitet werden müssen.
Bisher habe ich das entsprechende Bit immen in der Timer ISR 
(1ms)gesetzt.
In main habe ich die einzelnen Bits abgefragt und den/die entsprechenden 
Abläufe bearbeitet.
Die Abläufe sollten unabhängig voneinander ablaufen allso keine 
Schrittkette.
Ich könnte natürlich für jeden Ablauf einen Variable setzen und diese 
dann wieder im Main zurüchsetzen jedoch habe ich dann 16 Variablen für 
16 Abläufe plus 16 Variablen(Integer) für die Timerablaufwerte.
Das wird dann etwas unübersichtlich finde ich.
Solle mir nichts einfallen kann ich den 16 Bit Integerwert auch in zwei 
8 Bit Werte ändern muss dan nur mal nachsauen ob die auch weniger 
Schritte bedeutet.

Danke schon mal für Eure Meinungen/Anregungen

Thomas

von StinkyWinky (Gast)


Lesenswert?

Du könntest eine Struktur bauen, welche einen 1ms Zähler und einen 
Funktionszeiger besitzt, das ganze 16 mal (Array).
In der Timer-ISR werden alle 1ms-Zähler dekrementiert.
Die main() prüft, ob einer der Zähler Null geworden ist, und führt die 
zugehörige Funktion aus (und der 1ms Zähler wieder neu setzen).

von Peter D. (peda)


Lesenswert?


von Gast (Gast)


Lesenswert?

Nimm doch einfach ein Betriebssystem. Entweder FreeRtos oder ne embOS 
Evalversion von Segger.

von Thomas S. (thomass)


Lesenswert?

@StinkyWinky

Dein Gedanke ist eigentlich ganz gut jedoch werden mehrere Funktionen 
per Timerablauf ausgefürt.
Ich denke ich werde ein Array erstelle int - char - char und das ganze 
16 mal.
Der Integer beinhaltet den Timerablaut der 2 Char Wert wird gesetzt wenn 
der Timer aktiviert ist und der letzte wenn er abgelaufen ist.
So muss ich in Main nur abfragen ob der char Wert true ist oder nicht.
Und das Rücksetzen ist auch einfacher als zuvor.

@Peter

sowas wie Du vorschlägst habe ich eigentlich schon zumindest so ähnlich.
Ich habe mir den Code mal angeschaut jedoch für mich als nucht mehr ganz 
Anfänger doch etwas hoch.


Thomas

von Matthias L. (Gast)


Lesenswert?

>in Deinem Betrag gehst Du davon aus das immer ein fester Ablauf gegeben
<ist.
>1-2-3-4-5-..

Wenn du das korrekt liest, wirst du sehen, das das nicht stimmt.

Du kannst am Ende jedes Schrittes (zB 2) entscheiden, wie es weiter 
gehen soll. Die Nummerierung ist willkürlich und kann zB auch so gehen:

0-44-1-43-912-41-12-543-...

von genau so (Gast)


Lesenswert?

>delay_mSek_002 == Timercounter_mSek

Was soll das ?

Mach da ein <= oder >= draus und gut ist.

von Thomas S. (thomass)


Lesenswert?

@ Matthias Lipinsky,

OK ich werde mir dein Beispiel nochmal anschuen, wenn es stimmt was Du 
geschrieben hast und davon gehe ich aus könnte ich es verwenden.

@genau so,

Dein Codeschnipsel ist vom setzen des Ablaufwertes.
Wenn ich schaue nach ob der Ablaufwert erreicht ist es in der ISR also 
dort kann nichts passieren.
Ich hatte mal mehrere Tage einen JTAG MKII drangehängt und dabei habe 
ich gesehen das das Bit das signalisiert das ein neuer Ablaufwert 
gesetzt wurde fehlt und das setzten ist mit den Code unten. Dieser ist 
nicht in der ISR und ich denke das es wie oben schon beschrieben wurde 
nur beim setzen des Bits ein Interrupt ab und an dazwischenfunkt.
1
void Set_delay_mSek_002(void)
2
{
3
  if(((delay_mSek_status_1 & Mask_delay_002) == 0x0000) && ((delay_mSek_status_2 & Mask_delay_002) == 0x0000))
4
  {
5
    delay_mSek_status_1 |= Mask_delay_002;
6
    delay_mSek_002 = Timercounter_mSek + Reloadwerte_Timerallgemein_mSek[1];
7
  
8
  }
9
}

Ich brauche also etwas was das setzen des neuen Werts usw. verkürzt.

Thomas

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.