Forum: Mikrocontroller und Digitale Elektronik "Multitasking des kleinen Mannes" Verständnisfrage


von Jan K. (jan_k)


Lesenswert?

Hi Leute,

ich habe eine kleine Frage zu dem "Multitasking des kleinen Mannes": 
http://www.mikrocontroller.net/attachment/111803/MdkM.c

Der Sinn der State Machines erschließt sich mir, und ich bin gerade 
dabei, sie sinnvoll einzusetzen, bin aber auf ein Problem gestoßen, als 
ich das Programm erweitern wollte:

Ich benötige einige weitere Zustände, um auf Ereignisse von außen warten 
zu können. Ist z.B. eine bestimmte Flag gesetzt (z.B. DMA läuft gerade 
(nicht)) UND ist der timer dran, kann die eigentliche Arbeit begonnen 
werden.

Dafür habe ich einen IDLE task, der nur auf den timer wartet und einen 
WAIT task eingebaut, der die flag pollt.

Jetzt habe ich aber z.B. 5 tasks, die in unregelmäßigen Zeitabständen 
laufen. Die werden in der main einfach im round robin Verfahren 
durchgeschaltet.

Angenommen der timertick eines tasks kam. Wir befinden uns im Zustand 
WAIT. Dann wird die DMA flag gesetzt (oder der mutex ist nicht mehr 
gelockt oder so), und es wird weiter auf den Zustand WORK geschaltet. 
Aaaaber: dieser Zustand tritt ja erst beim nächsten Aufruf wieder ein. 
Es kann aber sein, dass in der Zeit ein anderer Task läuft, der ein paar 
ms benötigt. Dann könnte aber der DMA schon wieder an sein und der 
Zustand stimmt nicht mehr.

Ich könnte das Dilemma lösen, indem ich warte, bis der DMA nicht mehr 
läuft, aber das würde ja alles wieder kaputt machen.

Hintergrund der ganzen Geschichte: Ich zähle in der DMA ISR einen Zähler 
hoch, der mir die Anzahl der Durchläufe angibt. In einem Task frage ich 
die Anzahl ab. Ist die Anzahl >= einer Schwelle, hole ich die Daten ab. 
Dann setze ich den counter zurück. Das ist zwar atomar, aber die 
Inkrementierung in der ISR nicht. Es kann also sein, dass ich den 
Counter zurücksetze, das in der ISR aber wegen read-modify-write gar 
nicht ankommt. Daher möchte ich sichergehen, dass der DMA gerade nicht 
läuft.

Oder ist das total kacke und ich sollte auf eine Inkrementierung 
(zumindest als "public" Variable) verzichten, nur lokal in der ISR 
mitzählen und bei z.B. 10 vorhandenen samples eine binäre Flag setzen, 
deren Zugriff ja wirklich atomar ist?

Vielen Dank für eure Antworten!

von Karl H. (kbuchegg)


Lesenswert?

UM beim verlinkten Beispiel zu bleiben.

Du musst hier (original]
1
uint8_t Blink_2S( uint8_t state )
2
{
3
  switch( state ) {
4
  
5
    case INIT:
6
      StartTimer( 0, 2000 );   // Timer starten, 2 Sekunden
7
      return WAIT_2S;        // beim nächsten Aufruf soll die Statemachine im Zustand WAIT_2S sein
8
      break;
9
      
10
    case WAIT_2S:
11
      if( !IsTimerStopped( 0 ) )   // Timer abgelaufen?
12
        return WAIT_2S;   // nein: Task bleibt im Zustans WAIT_2S
13
      
14
      return WORK_2S;     // ja: nächster Zustand ist: 2 Sek sind vorbei, jetzt wird gearbeitet!
15
      break;
16
      
17
    case WORK_2S:
18
      PORTA = PORTA ^ 0x01;   // na ja, soviel gibts nicht zu tun
19
      
20
      StartTimer( 0, 2000 );
21
      return WAIT_2S;
22
      break;
23
  }
24
  
25
  return state;
26
}
keineswegs einen Statuswechsel zwischen WAIT_2S und WORK_2S machen. Das 
war nur im Beispiel so und ist keineswegs in Stein gemeisselt.

Genausogut könnte man da auch mit nur 2 Zuständen arbeiten
1
    case WAIT_2S:
2
      if( !IsTimerStopped( 0 ) )   // Timer abgelaufen?
3
        return WAIT_2S;   // nein: Task bleibt im Zustans WAIT_2S
4
      
5
      PORTA = PORTA ^ 0x01;   // na ja, soviel gibts nicht zu tun
6
      
7
      StartTimer( 0, 2000 );
8
      return WAIT_2S;
9
      break;

In deinem Beispiel: sobald deine Voraussetzungen im WAIT Zustand so 
sind, dass der DMA gestartet werden kann, dann machst du das auch. Somit 
ist dieses Problem
> und es wird weiter auf den Zustand WORK geschaltet. Aaaaber:
> dieser Zustand tritt ja erst beim nächsten Aufruf wieder ein.
> Es kann aber sein, dass in der Zeit ein anderer Task läuft,
> der ein paar ms benötigt.
keines mehr. Denn dann kann zwischen feststellen der Arbeitsfähigkeit 
und dem tatsächlichen Reservieren des DMA kein anderer Task per 
Definition dazwischen funken.

Den Rest deiner Ausführungen hab ich auf die schnelle nicht verstanden.

: Bearbeitet durch User
von Uwe (Gast)


Lesenswert?

Vieleicht noch ne DMA-State-machine bzw. DMA-Sequencer, der weiß was 
geht und was nicht. Dieser reiht dann die Operationen in eine 
Warteschlage ein und kann auch einen error wie "busy" zurückgeben.

von Uwe (Gast)


Lesenswert?

bzw. die DMA-State-machine ist dann halt im State "busy" und nicht im 
State "ready".

von MarcVonWindscooting (Gast)


Lesenswert?

Hi Jan,

also vom uebersichtlicher machen deines Quellcodes - wie du da in deinem 
Thread von vor 'ner Woche oder so geschrieben hattest (STM Cortex-M3?) - 
davon bist Du ganz weit weg mittlerweile!

In einer Schleife Funktionen periodisch aufzurufen und diese Funktionen 
mit nem expliziten Zustand versehen zu m"ussen und den Code diesem 
Zustand anzupassen, das hat bei mir nicht mehr viel mit dem Multitasking 
zu tun. Das ist Bitgefummel. Da kannst Du auch Sudoku spielen, daf"ur 
ist genausoviel Gr"utze in der Birne notwendig und es wird halt 
gefummelt bis das R"atsel gel"ost ist. Eleganz? Nicht vorgesehen.
Nach der (Forums-) Definition ist fast jedes Programm 'multitasking' 
denn die meisten Programme (auf einem uC) fragen in einer Hauptschleife 
irgendwelche Hardware-Flags ab und tun irgendwas anderes, wenn die Flags 
anzeigen, dass nichts zu tun ist. Das geht ganz leicht, seit der 
Erfindung der Unterfunktion! Mensch und ich Pappnase hab in der 90er 
Jahren nicht kapiert, dass MSDOS auch schon Multitasking war!

"Multitasking des kleinen Mannes" sehe ich auf 'nem ganz kleinen uC noch 
ein (8-Bitter). Aber auf einem 32-Bitter wirst Du doch (nur zum 
Beispiel) nicht im Ernst f"ur eine Reihe von Software-Timern in einer 
ISR f"ur jeden Timer den Stand herunterz"ahlen? Nein, du wirst eine 
System-Uhr t laufen lassen (HW oder SW) und f"ur jeden Software-Timer 
nur die Zeit te, wann abgelaufen festhalten.
Wenn Du wissen willst ob der Timer abgelaufen ist, pr"ufst Du 
(int)(te-t)<0. Timer aufziehen: te = t + Dauer. Signed oder unsigned ist 
egal, bloss nicht beim Vergleich. Und (int)(te-t)<0 ist nicht gleich 
te<t, "ubrigens. Und das funktioniert auch wenn der Timer "uberl"auft, 
falls Du mit 32bit rechnest UND der Timer genau 32bit hat und bei 2^32 
"uberlauft.
Keine ISR, keine Schleife in der ISR, keine Rechenoperationen wenn 
nichts wichtiges passiert. Geht aber bloss, wenn man mal von der Denke 
von 8bit wegkommt, so dass jeder Timer schon nach 256us "uberl"auft und 
die zu messenden Zeiten viel gr"osser sind...

"Multitasking des kleinen Mannes" - Falsche Lekt"ure! Nat"urlich kannst 
Du, wenn Du Dich f"ur's Welle-Nabe-Verklebungen interessierst, dich in 
die Herstellung von Pfeilen mit Birkenpech und Feuerstein vertiefen und 
dein Wissen dann auf das Welle-Nabe-Problem anwenden. Vielleicht 'hebt' 
ein Kugellager ja auch mit was 'einfacherem' als Loctite 603, wer weiss?

Ich hab's mir schon gedacht, als gar niemand sich f"ur meinen 
Kontext-Switcher interessierte...
Der Code des "Multitasking des kleinen Mannes" ist gar nicht so klein 
f"ur das was er leistet.

MEINER ist KLEINER!  ;-)

von Jan K. (jan_k)


Lesenswert?

Hm bin etwas verwirrt.

Was wäre denn vernünftige Literatur für einen 32-Bitter?

von chris_ (Gast)


Lesenswert?

>Ich hab's mir schon gedacht, als gar niemand sich f"ur meinen
>Kontext-Switcher interessierte...

Wo findet sich denn Dein Kontext-Switcher? Link ?

von MarcVonWindscooting (Gast)


Lesenswert?

Jan K. schrieb:
> Was wäre denn vernünftige Literatur für einen 32-Bitter?

Jedenfalls keine "uber 8-Bit Prozessoren!

Wie w"ar's denn damit:
http://www.lpcware.com/content/forum/minimalist-threading-lpc800

Okay das war selbsts"uchtig, aber es ist halt auch der Grund, warum ich 
heute nochmals nach deinem Thread / Deinen Aktivit"aten geschaut habe.


Nein, Hass beiseite, ich hab nichts gegen 8-Bitter, ich hab was gegen 
die 8-Bit-Denke an Stellen, wo sie nicht angebracht ist.

Du verlierst Dich - meines Erachtens - zu schnell in Details. Du 
schreibst zuviel und l"asst Dir zuviel ratschlagen.

Du brauchst Wissen "uber Synchronisationsmechanismen auf DEINEM uC: 
LDREX, STREX, atomare Updates. Wenn Du wieder bei den 8-Bittern schaust, 
wirst Du Bit-Test-and-Set oder swap finden. Und die bringen Dir auf 
deinem STM32 genau so viel: Null.

Du brauchst Wissen "uber Scheduling, Stacks, was 'Warten' bedeutet, usw. 
Mein Link oben f"uhrt zu einem Context-Switcher und der ist f"ur 'nen 
Cortex-M0. Das kannst Du 1:1 auf dem M3 verwenden, nur zur Info.

Du brauchst Wissen, wie man generell guten Code schreibt. Es gibt einen 
Artikel "uber dietlibc von Felix von Leitner - LESEN! Schau Dir die 
normale C-lib an und du weisst wie man's nicht macht: printf/scanf, 
Namen ohne Systematik, Pr"aprozessor-Exzesse, ...
Finde Leute die guten Code schreiben und schau Dir diesen an! Die 
legend"aren Linux-Kernel-Hacker geh"oren nicht dazu, auch kein noch so 
oft ausgezeichneter Adam Furunkels mit seinem uIP/lwIP. Am besten Leute, 
die Programme schreiben, die was richtig heftiges Steuern, das gleich 
explodiert wenn das Programm falsch ist. ABS-Steuerungen, 
Frequenzumrichter, Energieanlagen, Medizintechnik, Milit"ar.
Die sind nur leider schwer zu finden und das was die schreiben i.d.R. 
nicht offentlich zug"anglich.
Immerhin, die Java API find ich ein gutes Vorbild obwohl das nat"urlich 
ein ganz anderes Gebiet ist.

Lerne Bibliotheken selbst zu erstellen und zu verwenden. Bei mir 
bestehen beispielsweise fast alle Programme aus einer main.c + 
Bibliotheken. Und die main.c hat nicht riesengross zu sein, das ist 
klar, oder?
Lerne, einer Funktion, die du gerade geschrieben hast und in der main 
ihre Arbeit tut so zu gestalten, dass sie in die Bibliothek passt und in 
der neuen Form auch f"ur zuk"unftige Projekte taugt.
Ahnliches gilt f"ur Datenstrukturen.
Richtig schwierig wird's, wenn Du auf mehreren Plattformen/Cores 
arbeitest und die Duplizierung von Quellcode vermeiden m"ochtest. 
Heftig! Aber es geht, mit Hirnschmalz und z.B. Subversion.

Und dann kommt irgendwann der Schritt, wo Du analysieren musst, was Dir 
an deinem Programm nicht gef"allt, so wie's jetzt ist und wie Du das 
gerne anders h"attest. Und da musst Du Ideen haben und absch"atzen 
k"onnen was die kosten und ob sie umsetzbar sind. Dazu braucht man 
Erfahrung und die sammelt man durch "Ubung, nicht durch 
Forumsdiskussionen.


Obiger Task-Switcher ist entstanden durch Basteln eines Modellprogramms 
aus main() und paar Variablen und Funktionsprototypen (auch ISRs) und 
herumprobieren, wie mir das Zusammenspiel von Variablen 
(Thread-Context), Initialisierung und Aufrufen gefallen w"urde. Und dann 
hab ich's erst versucht umzusetzen - zun"achst auf Papier. Das 
Modellprogramm war nat"urlich nicht compilierbar, sondern nur die Skizze 
der sp"ateren Anwendung.

So was in der Art wirst Du wohl vor dir haben. Hast Dir vermutlich sogar 
ein zu grosses Thema rausgesucht. Ich wollte fr"uher immer 
(Turbo-Pascal-Zeiten) eine GUI-Bibliothek machen und bin daran 
monumental gescheitert. Die ist bis zum heutigen Tage nicht fertig...

So, genug gelabert f"ur die 'Community' - nun will ich endlich meinen 
LPC812 per pin-Interrupt aus dem power-down (2uA, Outputs aktiv!) holen 
k"onnen.

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.