Forum: Mikrocontroller und Digitale Elektronik Nicht blockierende Funktionen - wie löst ihr das?


von Miu (Gast)


Lesenswert?

Hallo zusammen,

ich würde gerne wissen wir ihr folgendes Problem lösen würdet.

Mir geht es nicht unbedingt um den unten dargestellten Code, sondern wie 
man das im allgemeinen am besten löst...

Pseudo-Code:
1
void doX()
2
{
3
    TransmitCanMessage(x,y,z); // Beginn der CAN-Übertragung
4
    while (GetCanTransmitStatus() == Pending); // Warten bis CAN-Nachricht gesendet. Kann lange dauern, da nur 10 kBit/s...
5
6
    doA();
7
}
8
9
int main(void)
10
{
11
    for(;;)
12
    {
13
         doX();
14
         doY();
15
         doZ();
16
    }
17
18
    return 0;
19
}

Die Funktion doA soll erst ausgeführt werden, wenn die CAN-NAchricht 
erfolgreich gesendet wurde. Gleichzeitig soll die Funktion doY und doZ 
regelmäßig aufgerufen werden, d.h. es kann nicht jedes mal die lange 
Zeit gewartet werde, bis die CAN-Nachricht gesendet wurde.

Folgende Lösung ist mir eingefallen, welche aber nicht besonders wartbar 
finde und sich schwierig gestaltet, wenn doX() in irgendeiner 
Unterfunktion auftritt und mehrfach verschachtelt ist:

Pseudo-Code:
1
void doX()
2
{
3
    static int sendFinished = 1;
4
    if (sendFinished == 1)
5
    {
6
        TransmitCanMessage(x,y,z);
7
        sendFinished = 0;
8
    }
9
    
10
    if (sendFinished == 0)
11
    {
12
         if (GetCanTransmitStatus() == Finished)
13
         {
14
             sendFinished = 1;
15
         }
16
    }
17
18
    if (sendFinished == 1)
19
    {
20
        doA();
21
    }
22
}
23
24
int main(void)
25
{
26
    for(;;)
27
    {
28
         doX();
29
         doY();
30
         doZ();
31
    }
32
33
    return 0;
34
}

Welche Lösung fällt euch für diese Art von Problem ein?

von holger (Gast)


Lesenswert?

>Welche Lösung fällt euch für diese Art von Problem ein?

Deine Lösung ist doch ok. Oder denk mal über ein RTOS nach;)

von Miu (Gast)


Lesenswert?

RTOS kommt in meinem speziellen Fall aus lizenzrechtlichen Gründen und 
Platzgründen nicht in Frage (nur 4Kb Codespeicher)...

von Kein Name (Gast)


Lesenswert?

Falls es wie RTOS aussehen soll, aber in 4k  passen muss...
http://dunkels.com/adam/pt/index.html

von Miu (Gast)


Lesenswert?

Das sieht sehr interessant aus. Das muss ich mir mal anschauen. Gibt es 
noch Alternativvorschläge, die das ganze von Hand umsetzen?

von Franz F (Gast)


Lesenswert?

Das in der Funktion doX () nennt sich Zustansautomat. Wie ein RTOS
 dazu eine Alternative sein kann verstehe ich aber nicht. ?

von Hans (Gast)


Lesenswert?

Vom Prinzip her ist das schon genau richtig: Nicht auf Dinge warten, 
sondern auf Ereignisse reagieren. Ereignisse können Interrupts sein oder 
per Polling festgestellt werden. Pro Durchgang der Hauptschleife kann 
man jeder Ereignisquelle einmal pollen.

Den konkreten Code könnte man vielleicht folgendermaßen vereinfachen, 
das hängt aber natürlich von der genauen Semantik der Funktionen ab:
1
void doX()
2
{
3
  if (GetCanTransmitStatus() == Finished)
4
  {
5
    doA();
6
    TransmitCanMessage(x,y,z);
7
  }
8
}

von Miu (Gast)


Lesenswert?

Danke Hans. Ja die Funktion hätte man deutlich einfacher gestalten 
können. Mir geht es weniger um die spezielle Lösung, sondern alternative 
Lösungsansätze...

von max (Gast)


Lesenswert?

mit timern arbeiten?

von holger (Gast)


Lesenswert?

>Mir geht es weniger um die spezielle Lösung, sondern alternative
>Lösungsansätze.

Deine ankommende Nachricht kann sicherlich einen CAN Interrupt
auslösen. Dann machst du doA(); halt im Interrupt.
Oder per Callback. Was aber auf das gleiche hinausläuft.

von holger (Gast)


Lesenswert?

>Deine ankommende Nachricht kann sicherlich einen CAN Interrupt

Schwachsinn, du willst ja wissen wann deine Nachricht gesendet
wurde. Löschen meinen letzten Post.

von Miu (Gast)


Lesenswert?

Mit Timer würde mir keine Lösung einfallen...

Danke Holger. Es geht nicht um die ankommende, sondern um die Abfrage, 
ob die Nachricht gesendet wurde. Prinzipiell hast du Recht, danke für 
die Alternative.

von Luhe (Gast)


Lesenswert?

Schon etwas von  thread gehört?

Eine Funktion wird gestartet und läuft dann im Hintergrund und tut was.
-> Ein Thread.
Zu geeigneten Zeitpunkten sendet diese dann die Ergebnisse an andere 
Threads.

Die einzelnen Funktionen kommunizieren über diese Nachrichten.

Ich glaube in diese Richtung ging die Frage.

Gruß

von Hans (Gast)


Lesenswert?

Falls Du es nicht schon kennst, hilft Dir vielleicht das Konzept 
"Zustandsautomat". So lassen sich komplexe Abläufe, die unterbrochen 
werden, recht übersichtlich darstellen:
1
enum {
2
  STATE_A,
3
  STATE_B,
4
  STATE_C,
5
  STATE_D,
6
};
7
8
void nextStep(void)
9
{
10
  static uint8_t state = STATE_A;
11
  switch (state)
12
  {
13
    case STATE_A:
14
      doA();
15
      state = STATE_B;
16
      break;
17
    case STATE_B:
18
      doB();
19
      state = STATE_C;
20
      break;
21
    case STATE_C:
22
      doC();
23
      state = STATE_D;
24
      break;
25
    case STATE_D:
26
      doD();
27
      state = STATE_A;
28
      break;
29
  }
30
}

Die Zustände müssen natürlich nicht linear aufeinander folgen. Sie 
können z.B. von Bedingungen wie Nachricht fertig gesendet, neues Zeichen 
empfangen, Timer abgelaufen etc. abhängen.

von Miu (Gast)


Lesenswert?

Wie gesagt, ein OS bzw. RTOS fällt raus. Ich kenne Threads. Danke...

von Miu (Gast)


Lesenswert?

Wie du glaube ich schon geschrieben hast, habe ich schon ein 
Zustandsautomat implentiert (nur halt mit if's).

Für mich ist die Frage, ob es Sinn macht oder einfachere Lösungen gibt, 
anstatt immer einen Zustandsautomat mit einer Zustandsvariable (bei mir 
sendFinished) zu bauen.

Wenn das Problem mehrfach in einem Programm auftaucht, kann das doch 
sehr fehlerträchtig werden...

von stefanus (Gast)


Lesenswert?

Einfacher geht es nur mit einem OS, dass aktiv zwischen Tasks 
umschaltet. Geht aber nicht mit nur 4KB Speicher.

von Hans (Gast)


Lesenswert?

Naja, irgendwie muss sich das System den Zustand merken. Entweder indem 
Du ihn explizit in einer Zustandsvariable speicherst oder implizit im 
Kontext eines Prozesses/Tasks/Threads. Dort besteht der Zustand dann 
eben aus Instructionpointer, Stackpointer, Registerinhalten etc..

Auf kleinen Mikrocontrollern ist es logischerweise wesentlich 
effizienter, selber ein Byte für den Zustand zu verwalten, als ein RTOS 
damit zu beschäftigen.

Besonders praktisch ist es, wenn der Zustand in den Peripherieregistern 
des Controllers gespeichert ist. Z.B. in Form des 
CAN-Controller-Statusregisters, in dem ein Bit gesetzt ist, nachdem die 
Nachricht gesendet wurde. Dann braucht man gar keinen zusätzlichen 
Speicher für den Zustand. Geht aber natürlich nicht immer.

von Winfried J. (Firma: Nisch-Aufzüge) (winne) Benutzerseite


Lesenswert?

Ich neige dazu, in ISR die zeitkritischen Aufgaben auszuführen, ein Flag 
zu setzen und überlasse der verarbeitenden Schicht, welche von main 
aufgerufen wird die Prüfung des Flags, genau wie du es machst. das geht 
auch ohne RTOS, like OS.

Ich kann dir das Studium des 8 Betriebssystems von ATARI XL empfehlen
ist zwar angestaubt aber übersichtlich. Ausfühlich in "Mein Atari 
Computer" "ATARI 800 XL intern" allerdings muss man ohne Listings 
auskommen, da die Einsprungpunkte der Routinen sich von Version zu 
Version verschoben.
Dafür sind die Funktionsweisen der Selben gut beschrieben. Wie auch die 
Struktur des BIOS.

Mit dem Monitor habe ich mein XE ROM damals durchforscht und 
modifiziert, was durch Spiegelung des ROM ins RAM (Dank vorhandenem 
Mapping)leicht möglich war.

: Bearbeitet durch User
von W.S. (Gast)


Lesenswert?

Miu schrieb:
> TransmitCanMessage(x,y,z); // Beginn der CAN-Übertragung
>     while (GetCanTransmitStatus() == Pending); // Warten bis
> CAN-Nachricht gesendet. Kann lange dauern, da nur 10 kBit/s...

Jaja, so macht man es eben lieber nicht, wenn man sich nicht selbst 
blockieren will. Ich mach das (grob skizziert) so:

main(..)
{ int result;
.. init diverses;

immerzu:
  if (!CAN_Pending)
  { if (SomethingToDoWithCAN)
      DoWhatIsToBeDoneButDoNOTwaitForTheEnd();
  }
  else
  { result = LookOnStopwatch();
    if (result==CANwasTooLazy) KillTheCANandReInitHim();
  }

  if (somethingelse) DoWhateverIsToBeDoneWithIt()
  .. usw. z.B.
  if (EventAvailable()
  { DispatchEvent(GetEvent());
  }

  goto immerzu;

Eben einfach so schreiben, daß man NICHT wartet.

W.S.

von Falk B. (falk)


Lesenswert?

Siehe Multitasking.

von Marc P. (marcvonwindscooting)


Lesenswert?

Bitte nicht den Schwachsinn vom Dr. Dunkels (der will dir alle lokale 
Variablen wegnehmen .-)

> Falls es wie RTOS aussehen soll, aber in 4k  passen muss...
> http://dunkels.com/adam/pt/index.html

sondern:

Beitrag "Multithreading auf LPC800 - 86Byte(!) Code-Groesse"

Der Teil, der dich am ehesten interessiert ist die Umsetzung von 
sleepMs() in main.c : Wartezeit mit sinnvollerem verbringen.

Wo hat man denn nur 4K Code?
Auf dem LPC810, LPC1110, ... hoffe ich doch mal?

von Mehmet K. (mkmk)


Lesenswert?

Marc P. schrieb:
> Bitte nicht den Schwachsinn vom Dr. Dunkels (der will dir alle lokale
> Variablen wegnehmen .-)

Bin ein regelmaessiger Benutzer dieser von Dir als Schwachsinn 
bezeichneten Technik. Und kann's jedem empfehlen, der nach RTOS 
aehnlichen Möglichkeiten sucht, aber kein echtes RTOS aufsetzen möchte.

von Ulrich H. (lurchi)


Lesenswert?

Für solche Sachen haben fast alle µCs interrupts. Oft hat auch schon das 
CAN Interface einen interrupt, wenn er fertig ist.

Man muss sich nur trauen in der ISR mehr als nur ein Flag zu setzen - 
das was bis zum nächsten Interrupt erledigt sein muss, darf in aller 
Regel auch als Code in die ISR - wenn das zu viel ist, ist der µC 
einfach überfordert, denn anders wird es nur noch langsamer.

von Hmm... (Gast)


Lesenswert?

Ulrich H. schrieb:
> Man muss sich nur trauen in der ISR mehr als nur ein Flag zu setzen -
> das was bis zum nächsten Interrupt erledigt sein muss, darf in aller
> Regel auch als Code in die ISR - wenn das zu viel ist, ist der µC
> einfach überfordert, denn anders wird es nur noch langsamer.

Kommt halt darauf an, wie deterministisch es werden soll/muss. Wenn man 
einen relativ langen Interrupt hat und dieser auch noch Burst-Artig in 
kurzen Abständen kommen kann, kann es passieren das man andere Tasks 
nicht mehr in definierten Zeitabständen bedienen kann. Daher tendiere 
ich eher dazu kritische Sache (Notaustaster, Endstellungsschalter am 
Motor, Überlastabschaltungen, etc.) direkt im Interrupt zu machen und 
den Rest lieber in ein Flag zu packen und in der Hauptschleife darauf zu 
realisieren.

Einfache Dinge wie z.B. ein empfangenes Byte von der RS485 in einen 
Puffer abzulegen macht man natürlich direkt in der ISR. Aber sobald man 
auf irgendetwas warten muss (z.B. ein weiteres seriell übertragenes Byte 
oder ein Write_Done Flag in einem externen Speicher oder ...) ist es 
besser das nicht im ISR zu machen. Man muss halt vorher wissen, wie 
lange die ISR im schlimmsten Fall brauchen kann und wie oft das Ereignis 
auftritt.

von Marc P. (marcvonwindscooting)


Lesenswert?

Mehmet Kendi schrieb:
> Bin ein regelmaessiger Benutzer dieser von Dir als Schwachsinn
> bezeichneten Technik. Und kann's jedem empfehlen, der nach RTOS
> aehnlichen Möglichkeiten sucht, aber kein echtes RTOS aufsetzen möchte.

... und der am liebsten wieder in die Zeit zurueckversetzt wuerde, in 
der es noch keine 'Hoch'-Sprachen wie C gab.
0815 Fitzelprograemmchen, da kann man noch drueber reden vielleicht. 
Aber empfehlen darf man das nicht! Du nennst es in einem Atemzug mit 
'RTOS', aber auf die Einschraenkungen gehst Du ja (fahrlaessigerweise) 
gar nicht ein. Ich hoffe Du kennst sie als regelmaessiger Nutzer 
wenigstens!?

Man kann auch Kokain benutzen um das eine oder andere Leistungsproblem 
eines Menschen zu beheben und das funktioniert vermutlich sogar. Bloss 
muss man es deshalb - trotz seiner schwerwiegenden Seiteneffekte - 
empfehlen ?

von Rudolph (Gast)


Lesenswert?

Kann mich dem oben nur anschliessen.
Testen ob der CAN-Controller bereit zum verschicken ist und nur was 
machen wenn es denn so ist.

Extrem simpler aber funktionierender Test-Code für den ATMega16M1:

Beitrag "CAN beim ATMega16M1 anders als bei 90CANxx?"


Wobei das so sicherlich geschickter wäre:
1
  while(1)
2
  {
3
    _delay_ms(1);
4
5
    CANPAGE = (0<<4); // select MOB0
6
    delay++;
7
    if(delay > 49)
8
    {
9
                 if(CANSTMOB & (1<<TXOK)) // fertig mit Senden?
10
           {
11
            delay = 0
12
                  CANSTMOB &= ~(1<<TXOK); // reset flag
13
14
       CANCDMOB = (1<<DLC1); // 2 Byte
15
       CANMSG = 0x55;
16
       CANMSG = 0xaa;
17
       CANCDMOB |= (1<<CONMOB0); // Transfer einleiten
18
         }
19
    }
20
  }

Und ja, statt delay() im richtigen Code den µC schlafen legen am Ende 
der Schleife und durch nen Timer wecken lassen.

von (prx) A. K. (prx)


Lesenswert?

Marc P. schrieb:
> Bitte nicht den Schwachsinn vom Dr. Dunkels (der will dir alle lokale
> Variablen wegnehmen .-)

Dieser "Schwachsinn" ersetzt komplexe von Hand programmierte und 
unübersichtliche state machines durch jene state machine, die der 
Prozessor von Haus auf mitbringt: den program counter. Nachteile hat es 
natürlich auch, aber der entstehende Code ist wesentlich lesbarer als 
die üblichen state machines.

Der Sinn davon liegt in kleinen Prozessoren mit einigen KB Speicher, 
für die ein RTOS zu gross sein könnte.

: Bearbeitet durch User
von Badtler (Gast)


Lesenswert?

Und der "Schwachsinn" verhindert, daß ich Stackframes anlegen "muß", für 
Variablen, von denen es nur eine Instanz gibt, weil ich real keine 10 
identischen Blink-Threads brauche. (ich muß natürlich nicht, aber wer 
derentwegen heult, der macht das wohl)

Oder anders: static Variablen in der Thread-Funktion anlegen, dann stört 
das Vergessen des Stack-Frames bei protothreads nicht wirklich.
Es geht da um einfach Abläufe, die man lieber sequenziell schreibt, als 
als EventHandler-Grab. Wir befinden uns da oft auf 2Kb AVR's und nicht 
auf Multicore-ARM's.

von Marc P. (marcvonwindscooting)


Lesenswert?

Badtler schrieb:
> Oder anders: static Variablen in der Thread-Funktion anlegen, dann stört

Jede static Variable braucht ihren eigenen Platz fuer alle Zeit! Eine 
Variable auf dem Stack hat gut Chancen denselben Speicherbereich zu 
nutzen wie eine lokale Variable einer anderen Funktion des gleichen 
Threads zu einer anderen Zeit. Und nicht jede lokale Variable ist nur 1 
oder 2 Bytes gross.
Und genau da faengt das Problem an: dann wird an lokalen Variablen 
gespart, oder 2 Funktionen zu einer zusammengefasst, oder auf 
primitivere Typen zurueckgegriffen statt auf problemorientierte. Dann 
werden ploetzlich uint8_t statt int benutzt, obwohl der uC mit einem 
anderen Typ besser funktionieren wuerden, dto. mit Pointer vs. 
8/16-bit-Index. Dann bastelt man ein PTLOCAL Makro das fur lokalen 
Variablen in potentiellen Bibliotheksfunktionen verwendet wird (und 
irgendwo eine mal vergessen;) , usw. usw.

Schwupdiwupp hat sich die Vereinfachung (Sequenzialisierung) des Ablaufs 
zu einer 'Vermurksung' des Codes ueber das aktuelle Projekt hinaus 
verwandelt. Ein bekannter Effekt von erstbesten Loesungen.

Darum bin ich so ein Gegner der Protothreads. Sie provozieren geradezu 
schlechten Code. Okay ich will es nicht mehr 'Schwachsinn' nennen, 
sondern 'Leichtsinn'.

Ich spiel ebenfalls mit kleinen Prozessoren: LPC810, LPC1110 - 4kiB ROM, 
1kiB RAM. Ich spar lieber die paar Bytes fuer paar Stacks und den Code 
KontextSwitcher indem ich keine Fliesskommas, printf und sonstigen 
Standard-Rotz benutze. Aber meistens brauch ich den nicht mal, denn ich 
design meine Interfaces praktisch immer als asynchrones IO.

Fuer Anregungen zum Sparen empfehle ich google mit 'Felix von Leitner', 
der hat da paar interessante Vortraege gemacht.

A. K. schrieb:
> natürlich auch, aber der entstehende Code ist wesentlich lesbarer als
> die üblichen state machines.
ACK mit obigen Einschraenkungen.
Das eigentliche Problem ist, dass wir kein Werkzeug haben, das 
sequentiell geschriebenen Code in eine Zustandsautomaten uebersetzt und 
zurueck. Vielleicht sollte man sich da mal Gedanken machen, wie man 
vielleicht mit magischen Kommentaren den Code so mit Information 
anreichern kann, dass die Konvertierung automatisch erfolgen kann. 
Zustandsautomaten fuer Textmuster werden ja auch generiert und nicht 
handprogrammiert.

von Mehmet K. (mkmk)


Lesenswert?

Marc P. schrieb:
> Okay ich will es nicht mehr 'Schwachsinn' nennen, sondern 'Leichtsinn'.

Noch ein Hau-Ruck und ich bin mir sicher, Du findest eine noch bessere 
Umschreibung als 'Leichtsinn'.

von S. R. (svenska)


Lesenswert?

> Jede static Variable braucht ihren eigenen Platz fuer alle Zeit! Eine
> Variable auf dem Stack hat gut Chancen denselben Speicherbereich zu
> nutzen wie eine lokale Variable einer anderen Funktion des gleichen
> Threads zu einer anderen Zeit. Und nicht jede lokale Variable ist nur 1
> oder 2 Bytes gross.

Protothreads schenken dir Threads ohne lokale Variablen, *und ohne den 
Stackframe-Overhead*. Du siehst nur den Nachteil.

> Und genau da faengt das Problem an: dann wird an lokalen Variablen
> gespart, oder ... oder ....

Das Zauberwort heißt "Kompromiss".

Brauchst du viele lokale Variablen in deinen Threads, sind Protothreads 
ungeeignet. Brauchst du dagegen möglichst viel Speicher für globale 
Variablen (Buffer!), dann sind echte Threads ziemlich teuer.

Wenn du bei richtigen Threads und wirklich wenig Speicher nicht 
aufpasst, handelst du dir auch gerne mal einen Stack-Overflow ein.

> Fuer Anregungen zum Sparen empfehle ich google mit 'Felix von Leitner',
> der hat da paar interessante Vortraege gemacht.

Stimme ich zu, aber bei ihm geht es weniger um die ganz kleinen Systeme.

von chris_ (Gast)


Lesenswert?

Man kann jede der aufzurufenden Funktionen als Zustandsmaschine 
aufbauen. Diese sollten dann "nicht blockierend" ausgeführt werden.
1
void doX()
2
{
3
   stateMachineX();
4
}
5
void doY()
6
{
7
   stateMachineY();
8
}
9
void doZ()
10
{
11
   stateMachineZ();
12
}
13
14
15
int main(void)
16
{
17
    // equal time slot scheduler
18
    for(;;)
19
    {
20
         doX();
21
         doY();
22
         doZ();
23
    }
24
25
    return 0;
26
}

von W.S. (Gast)


Lesenswert?

Mehmet Kendi schrieb:
> Noch ein Hau-Ruck und ich bin mir sicher, Du findest eine noch bessere
> Umschreibung als 'Leichtsinn'.

Du beziehst dich auf den schwedischen Adam, den dunklen Adam, ja?
Für dessen Proto-Threads sollte man diesen Präprozessor-Steptänzer 
windelweich prügeln. Das ist der allergrößte Bockmist, den ich je in der 
Szene gesehen habe.

Man sollte sich eines merken: Selbst durch allergrößte 
Präprozessor-Akrobatik kann man aus einer kleinen Ressource keine große 
Ressource machen. Da wäre es weitaus besser, direkt in biederem C ohne 
Verrenkungen und ohne Mißbrauch des switch/case ein dem Problem wirklich 
angepaßtes Stück Code zu schreiben.

W.S.

von Marc P. (marcvonwindscooting)


Lesenswert?

W.S. schrieb:
> ...diesen Präprozessor-Steptänzer...
> ...der allergrößte Bockmist...

:) Das ist Balsam auf meine Seele!

Kennst Du das Framework 'ACE' ?
http://www.cs.wustl.edu/~schmidt/ACE.html

Das hat damals meinen AMD K6-300 auf den ich damals so stolz war (lach 
nicht!!) dank solcher Akrobatik soweit ausgelastet, dass ich (gefuehlt) 
ewig auf Compiliern und Linken von 'hello world' (mit select()) warten 
musste. ACE war Vorgabe des Chefs und in der folgenden Diskussion 
darueber wurde ich als 'Spalter' (der Entwicklergruppe) bezeichnet. 
Letzten Endes hab ich in dem Laden nicht angefangen und es wurde nichts 
gespalten.

Interessant ist, dass Leute sehr gute Ideen haben, aber in der Umsetzung 
haeufig so schlecht sind, dass unter'm Strich was negatives rauskommt.

Dunkels ist da ein Paradebeispiel. IP/TCP/UPD werden auf die 
kleinstmoeglichen Grenzen geprueft und Minimalanforderungen ermitteln 
-sehr gut! Aber die Ausfuehrung ..(wuerg).. na, ja,.. aehm nein! Ich hab 
selbst uIP benutzt - kurze Zeit - und hab Dunkel's Sachen gelesen, 
nachvollzogen. Aber die Umsetzung, da hat mich das blanke Entsetzen 
gepackt und am Ende hab ich lieber selber einen UDP/IP Stack gebastelt. 
Ich frag mit ob der weiss, dass es structs gibt, nicht nur char[]?
Heutzutage mach ich eine Datei in dem Stil nach spaetesten 100 Zeilen 
anschauen wieder zu und loesch das Gesamtwerk vom Rechner. Das Leben ist 
zu kurz, um es mit sowas zu vergeuden.

Gleiches gilt fuer ACE. Die Papers sind echt lesenswert, die Umsetzung 
macht man besser selber. Sonst wird aus jeden feature ein Makro FEATURE, 
das dann je nach Plattform per Praeprozessorakrobatik (bis zum Exzess) 
was anderes gibt.

Kompromisse mach ich auch, bis ich bis zu den Knie in der Sch*isse steh, 
aber nicht bis zum Hals!

von Georg (Gast)


Lesenswert?

Hallo,

eigentlich ist es ganz einfach auch selbst zu stricken: nichtblockierend 
heisst, Start einer Aufgabe (z.B. Druck einer Seite) und deren Abschluss 
werden getrennt bearbeitet. Die Startroutine schiebt das ganze an und 
kehrt sofort zurück, der Druck läuft im Hintergrund und ruft die 
Abschlussroutine auf, wenn er fertig ist. Die kann z.B. den Drucker für 
andere Zwecke wieder freigeben, oder auch garnichts tun.

Georg

von 123 (Gast)


Lesenswert?

Ich würde, wenn ich das als loop Programm realisieren müste, folgender 
Massen machen.

Das doX in 2 Funktionen zerlegen.

Eine sendCan, der alle Parameter und ggf die doA über gegeben wird. Die 
Funktion speichert die Werte nur zwüschen. Ein return value sagt dir ob 
die can Nachrichten angenommen wurde oder nicht. Die rufende Funktion 
muss sich dann nicht blokierend um das wiederholen kümmern fals 
notwendig. Ggf kann hier auch eine queue verwendet werden.

Und eine transmitCan. Die Funktion hängt in der hauptschleife, 
entsprechend der oben gespeicherten Werte kümmert die sich um die 
eigentliche Übertragung, warten und doA aufruf.

So ähnlich würde ich es auch mit threads machen. TransmitCan Wäre dann 
der thread.

Gruss

von S. R. (svenska)


Lesenswert?

Threads wurden erfunden, um gerade dieses handgefummelte, 
fehlerträchtige Zerlegen in Ausführungsblöcke zu verhindern. Ändern sich 
die Anforderungen ans Timing, muss man das alles neu bauen.

Sinnvoll ist, alles entweder von Beginn an in kleinste Einheiten zu 
zerlegen und manuell zu schedulen (Zustandsautomat), oder alles am Stück 
lassen und eine Runtime mit dem Zerlegen zu beauftragen (Threads).

Beides hat Vor- und Nachteile.

von Ulrich H. (lurchi)


Lesenswert?

Multitasking / Threads sind auf dem PC oder einen großen System OK. Bei 
einem kleinen µC ( 4 K Speicher) sind da aber eher Interrupts die Lösung 
der Wahl. Das ist nicht so flexibel, wird aber gerade auf dem µC für 
viele IO Funktionen direkt von der Hardware unterstützt. Es ist eine 
etwas andere Art zu programmieren  - halt ereignisorinetiert und nicht 
mehr linear.

Das kann so weit gehen, das fast alles in ISRs erledigt wird, und das 
Hauptprogramm nur noch den µC in den Schlafmodus schickt, wenn gerade 
nichts zu tun ist.

von Marc P. (marcvonwindscooting)


Lesenswert?

S. R. schrieb:
> Threads wurden erfunden, um gerade dieses handgefummelte,
> fehlerträchtige Zerlegen in Ausführungsblöcke zu verhindern. Ändern sich
> die Anforderungen ans Timing, muss man das alles neu bauen.

Moment: gerade beim Timing sind Threads keine grosse Hilfe, wenn's um 
die Zeit zwischen 'Hardware-fertig' und 'passenden Thread aufrufen' 
geht.
Wie macht ein Scheduler die Umsetzung von (Interrupt-)Flags ins 
Aktivieren von schlafenden Threads? Pollen oder ISR! Nur dass man ihm 
als goettliche Ueberinstanz dies zugesteht und dem Programmierer lieber 
nicht ;-) Aber dann muss der Scheduler auf ueber alle moeglichen 
Interrupt-Flags bescheid wissen und das kann doch auch schnell 
unuebersichtlich werden, oder lieg ich da falsch?

Ulrich H. schrieb:
> sind da aber eher Interrupts die Lösung
Sehe ich genauso. Vor allem in Kombination mir Priorisierung und 
verschachtelten Interrupts. Ich finde das macht die Cortex-M-* so 
interessant.
> Es ist eine etwas andere Art zu programmieren  - halt ereignisorinetiert
> und nicht mehr linear.
Und jetzt halt dich fest: genau dafuer hab ich meinen Kontext-Switcher 
so geschrieben und nicht anders. Dass man naemlich aus einer ISR raus 
(!!)  eine zuvor angefangene, lineare Funktion fortsetzen kann.
Dafuer muss man aber einen vorgefertigten Scheduler entmachten 
(eliminieren).

von chris (Gast)


Lesenswert?

Ist doch ganz einfach:

void doX()
{
    TransmitCanMessage(x,y,z); // Beginn der CAN-Übertragung
}

void doYZ()
{
        doY();
        doZ();
}

int main(void)
{
    for(;;)
    {
         doYZ();
         doX();
         while (GetCanTransmitStatus() == Pending)
                doYZ();
         doA();
    }

    return 0;
}

von S. R. (svenska)


Lesenswert?

>> Threads wurden erfunden, um gerade dieses handgefummelte,
>> fehlerträchtige Zerlegen in Ausführungsblöcke zu verhindern. Ändern sich
>> die Anforderungen ans Timing, muss man das alles neu bauen.
>
> Moment: gerade beim Timing sind Threads keine grosse Hilfe, wenn's um
> die Zeit zwischen 'Hardware-fertig' und 'passenden Thread aufrufen'
> geht.

Wir haben verschiedene Probleme im Hinterkopf. Mir geht es nicht darum, 
Interrupts durch pollende Threads zu ersetzen oder alle ISRs in Threads 
zu stopfen.

Mehrere parallel ablaufende CPU-intensive Algorithmen sind ungeeignet 
für eine ereignisorientierte Architektur. Ich denke da an Roboter, die 
gleichzeitig Kamerabilder analysieren und Wegfindung betreiben. Man kann 
die Algorithmen als nichtblockierende Zustandsautomaten entwerfen, oder 
als blockierende Threads, die vom Scheduler gewechselt werden.

von Georg (Gast)


Lesenswert?

S. R. schrieb:
> oder
> als blockierende Threads, die vom Scheduler gewechselt werden.

Was preemptives Multitasking voraussetzt, kooperativ ist das zu 
unsicher, siehe seliges 16bit-Windows.

Georg

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.