super, ganz tolle Erklärung, habe jetzt eine Menge Neues kapiert. Ganz herzlichen Dank !
Wenn du mit Zeigern arbeitest musst du dir im Klaren darüber sein, dass es immer zwei Speicherbereiche zu unterscheiden gibt. Der eine ist der Speicherbereich in dem die eigentlichen Daten stehen, die andere Speicherzelle ist der Zeiger, dessen Wert die Adresse des anderen enthält.
Wenn du jetzt eine Variable von einem einfachen Basistypen wie int, char oder double etc. hast und über den Zeiger den Wert ändern willst, muss der Compiler unterscheiden können ob du den Wert des Zeigers oder den Wert auf den den er zeigt ändern möchtest.
Für einfache Typen ist das schlicht. "ptr = &aval" weisst dem Zeiger die Adresse von aval zu, während " *ptr = 7 " den Zeiger dereferenziert und in den Speicher dieser Adresse den Wert 7 hineinschreibt. Zwei gänzlich unterschiedliche Dinge.Code:int aval = 5; int main(void) { int *ptr; ptr = &aval; printf("Der Wert an der Adresse %p ist %d\n", ptr, *ptr); *ptr = 7; printf("Der Wert an der Adresse %p ist %d\n", ptr, *ptr); return 0; }
Bei Strukturen kommt jetzt hinzu, dass der Compiler Offsets für die Elemente der Struktur berechnen muss, damit er den korrekten Speicherbereich liesst bzw. beschreibt.
Bei einer Variablen weiss der Compiler zum Übersetzungszeitpunkt wo der Start der Variablen ist und kann diese Offsets absolut oder relativ berechnen.
Verwende ich einen Zeiger auf eine Variable einer Struktur, dann kennt der Compiler diesen Startpunkt nicht und ich brauche einen Mechanismus ihm diesen mitteilen zu können. Der "->" sagt also aus: in der Zeigervariable findest du die Startadresse, nimm die und berechne von da an. Während bei Variablen der Compiler die Verantwortung für den Start hat, habe ich als Programmierer die Verantwortung bei Zeigern den richtigen Startpunkt dem Compiler mitzuteilen.Code:#include <stdio.h> struct vec2d { double x, y; }; struct vec2d ein_vec; int main(void) { ein_vec.x = 3.0; ein_vec.y = 7.0; printf("Adressen von ein_vec=%p, ein_vec.x=%p, ein_vec.y%p\n", &ein_vec, &ein_vec.x, &ein_vec.y); return 0; }
An die Adresse 0x00000000 darf man nichts schreiben.Code:#include <stdio.h> struct vec2d { double x, y; }; struct vec2d ein_vec; int main(void) { struct vec2d *vptr; vptr = &ein_vec; vptr->x = 3.0; vptr->y = 7.0; printf("Adressen von vptr=%p, ein_vec=%p\n", vptr, &ein_vec); /* Fehler gibt SEGFAULT */ vptr = 0; vptr->x = 3.0; vptr->y = 7.0; return 0; }
Falls dir die Erklärungen nicht so ganz einleuchten, solltest du dann vielleicht doch nochmal in ein gutes C-Buch reinschauen und dort die Erklärungen über Zeiger durchgehen. Mir fehlt im Erklären die Übung. Nichts desto trotzt sind Zeiger ein wichtiges und mächtiges Werkzeug in der C-Programmierung.
Warum ich die überhaupt benutzen wollte hatte einen bestimmten Grund:
Bei Threads muss man immer aufpassen, dass man sich keine Wettbewerbsbedingungen einfängt, sprich ein Thread in Daten eines anderen hineinschreibt. Obwohl ich erstmal globale Variablen als Speicherort gewählt hatte, werden die einzelnen PIDs dadurch isoliert, dass in den Threads immer nur auf eine Variable über den zugehörigen Zeiger zugegriffen wird (das wäre selbst so, wenn ich alle in einem Feld anordnen würde.). Diese Zeiger liegen auf dem Thread lokalem Stack, sind nach Aussen nicht sichtbar.
Das war die Idee.
Gruss
botty
Geändert von botty (27.09.2016 um 16:12 Uhr)
super, ganz tolle Erklärung, habe jetzt eine Menge Neues kapiert. Ganz herzlichen Dank !
@botty:
habe mir jetzt die letzten Tage deinen Vorschlag noch ein paar mal genauer angesehen und überdacht, etwas herumprobiert und die bereits in meiner "LegoPi" bestehende Raspi- Motorstuktur durch die PID-Parameter aufgebohrt:
3 Fragen dazu:Code://************************************************************* // motor API //************************************************************* #define MAXMOTORS 10 // maximum number of motors #define MAXMOTORSLOCAL 2 // maximum number of local motors #define MAXPWMRANGE 255 // maximum software-pwm range (0-255) // motor control structure typedef struct { // electrical motor pins uint8_t pind1, pind2, pinpwm; // dir + pwm L293 H-Bridge type uint8_t pinQa, pinQb; // rotary enc pins Qa,Qb // pwm and encoder values int16_t dirpwm; int32_t motenc, oldenc; // rotary encoder values // PID pthread_t tid; int16_t motorID; // PID custom target values int32_t target; // set target int16_t tarpwm; // motor target speed // PID custom regulation parameters double P; // P: basic propotional to error double I; // I: integral: avoid perish double D; // D: derivative: avoid oscillating double precis; // error precision to target int16_t regtime; // PID loop time double damp; // damp the integral memory int8_t cont; // target: continue or hit once // PID internal control variables int16_t runstate; // monitors runstate int16_t outp; // PID control output value int16_t maxout; // max output (max motor pwr) int32_t read; // current sensor reading double err; // current error double integr; // integral of errors double speed; // current speed } tEncMotor; tEncMotor motor[MAXMOTORS];
1) da ich jetzt alles statisch in einem Array deklariert habe: käme man jetzt überwiegend ohne die Pointerpfeile -> aus ?
2) da jede einzelne Motorstruktur über den Platz im Array definiert ist (motor[0] für motorID=0) : braucht man dann noch die Strukturvariable motorID?
3) kann man eine zusätzliche Variable im pthread-Aufruf übergeben, indem man die Motornummer mit übergibt?
bisher läuft mein pthread Aufruf immer per
pthread_create(&threadID, NULL, threadname, NULL);
wofür man die 2 NULLs braucht, ist mir z.B. auch noch unklar, vlt kann man die ein oder andere verwenden - und würde das die Sache einfacher machen?
- - - Aktualisiert - - -
ps,
der API Aufruf
RotatePID(port, Target, RotatSpeed, false);
würde dann die folgende Funktion aufrufen, die wieder nach Setten der Variablen einen pthread task starten müsste..... (????) :
Code:void RotatePID(char port, long Target, float RotatSpeed, char cont) { motor[port].runstate=1; // set runstate: PID active // custom init PID [port] motor[port].target =Target; // assign target motor[port].tarpwm =RotatSpeed; // assign rotation speed motor[port].cont=cont; // cont vs. hit once // Reset PID control defaults motor[port].outp =0; // PID control output value motor[port].maxout =100; // absolute max possible output (max pwr) motor[port].read =0; // current reading motor[port].err =0; // current error motor[port].integr =0; // integral of errors motor[port].speed =0; // current speed // <<<<<< // nur wie wäre das hier dann richtig zu implementieren ???? // threadname ist ja für jeden Motor immer ein anderer ???? // wäre hierfür eine tEncMotor Hilfsvariable char[12] tEncMotor.thrnamebuf hilfreich ???? // ("PIDthread00"... "PIDthread99", könnte man beim Initialisieren setten ????) // aber schreiben will ich ja für alle nur 1x ein einziges PID-Funktionstask-Muster ???? // >>>>>> pthread_create(& motor[port].tid, NULL??, motor[port].thrnamebuf, NULL??); }
Geändert von HaWe (28.09.2016 um 18:26 Uhr)
Hi,
die erste Frage hast du dir schon selbst beantwortet. Den Dereferenzierungsoperator "->" brauchst du nicht mehr, wenn du mit dem Array von Strukturen arbeitest. Du musst nur den Index für einen Motor als Parameter in die Thread-Main bekommen, indem deine PID-Kalkulationen sind.
Zu deiner zweiten Frage: das Element kannst du weglassen denn wir werden diese id als index übergeben.
Zu deiner letzten Frage:
schau mal die Signatur der pthread_create genau an (mit "$> man pthread_create" in 'nem Terminal unter Linux gibt's die Erklärungsseite zu sehen):
Parameter eins ist ein Zeiger auf das pthread Handle. So wie in deinem RotatePID-Beispiel passt das schon mal.Code:int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
Parameter zwei ist ein unveränderlicher Zeiger auf eine Attributstruktur. Es gibt paar, das wichtigste ist das deine Threads mit pthread_cancel sich unterbrechen liessen. Da das ein Default-Wert ist und die anderen Attribute erstmal nicht wichtig sind, kannst du hier eine 0 oder NULL reingeben, was heist, das die Defaults verwendet werden.
Parameter drei ist jetzt der Zeiger auf die Funktion, welche die Thread-Main sein soll. In deinem Fall gibst du da einfach den Namen der PID-Kalkulationsroutine an (die ist ja wohl für alle gleich?).
Parameter vier ist ein Zeiger ohne konkreten Datentypen, also void. Da Zeiger in C immer vorzeichenlose, integrale Datentypen sind können wir hier ausnahmsweise den Wert des Zeigers als unsigned int für den Index in das Array missbrauchen und so sicherstellen, das ein Thread immer nur auf ein bestimmtest Element des Arrays zurgreifen wird.
Der Erzeugungsaufruf in RotatePID sähe also so aus:
in der "pid_calc" müssen wir dann den Wert des Zeigers einmal am Anfang in einen "unsigned int" casten und diese Variable verwendest du bei den Arrayzugriffen und in deinen Unterfunktionen als Wert für den port Parameter.Code:pthread_create( &motor[port].tid, // id speichern in dem dazugehörigen Array-Element 0, // Default Attribute (threads cancelbar) pid_calc, // Name der PID-Kalkulationsroutinge (void*)port); // der Index des Array-Elements für eine PID Struktur, die mit dem Motorindex gleich ist. // Der cast ist notwendig, sonst meckert der Compiler.
Die Signatur ist bindend!:
Wenn du einen anderen Namen verwenden willst, musst du nur darauf achten das die Routine "void*" als Rückgabewert hat und es genau einen Parameter gibt mit "void*" als Typ.Code:void *pid_calc(void *arg) { // Was immer du für Variablen brauchst // Zeigerwert in Arrayindex wandeln. unsigned port = (unsigned)arg; // Nur als sinnloses Bsp. motor[port].P = 0.4; // Was immer du rechnen magst. pthread_exit(0); }
Das war's.Code:void* welcher_name_auch_immer(void *arg) { ... }
Gruss
botty
P.S. leider sind die Formatierungen in den code-Tags verrutscht, hoffe es ist trotzdem lesbar.
ok, noch immer ein paar Verständnisprobleme:
zu pid_calc....:
bisher verwende ich ja diese Task-Funktion hier (in 3 Varianten, ab jetzt natürlich einheitlich 1x für alle):
(ich HASSE ES, dass hier nie Codetags im Antwort-Editor dabei sind!!! )
Code:task task_PID_A() { float aspeed, damp, PWMpwr, readold, errorold, tprop; long readstart, cmax, cmin; // for monitoring long starttime, runtime, clock, dtime; // timer char regloop; PID_A.runstate = 0x10; // reg state: RAMPUP PID_A.read = (MotorRotationCount(OUT_A)); // get current encoder reading PID_A.err = PID_A.target - PID_A.read; // error to target readstart = PID_A.read; regloop = 1; #ifdef debug_PID_A //............................................................................ // init variables for graph output ClearScreen(); DisplayMask(); int timex, oldtx, oldy=15, pwm0=15; // values for graphic screen float scrXratio, scrYratio; scrXratio=abs(PID_A.err)/8; if(PID_A.target<50) scrXratio=abs(PID_A.err)/4; scrYratio=abs(PID_A.err)/40; //............................................................................ #endif damp=0; // damp the integral memory starttime= CurrentTick(); // appoach target _Astart: PID_A.runstate = 0x10; // run state: RUNNING do { dtime = CurrentTick() - clock; clock = CurrentTick(); runtime = clock - starttime; tprop = dtime/20.0; if ((PID_A.err==errorold)&& (abs(PID_A.err)>PID_A.precis)) damp=1; // stalling else damp=PID_A.damp; PID_A.integr = (damp * PID_A.integr) + PID_A.err; if((PID_A.integr) > 3*PID_A.maxout) PID_A.integr = 3*PID_A.maxout; // cut away else if((PID_A.integr) <-3*PID_A.maxout) PID_A.integr =-3*PID_A.maxout; PWMpwr= (PID_A.P*PID_A.err) + (PID_A.I*PID_A.integr)*tprop + (PID_A.D*(PID_A.err-errorold))/tprop; if(PWMpwr > PID_A.maxout) PWMpwr= PID_A.maxout; // forward maxout else if(PWMpwr < -PID_A.maxout) PWMpwr= -PID_A.maxout; // reverse maxout PID_A.speed= (PID_A.read-readold)*100/dtime; // rotat speed [degrees/100ms] aspeed = abs(PID_A.speed) ; if (aspeed > PID_A.tarpwm) { PWMpwr = sign(PWMpwr)*PID_A.tarpwm; } PID_A.outp = round(PWMpwr); #ifdef debug_PID_A //.......................................................................... // for graph output timex= runtime/scrXratio; PointOut(timex,(PID_A.read-readstart)/scrYratio); LineOut(oldtx, oldy, timex, pwm0+PID_A.speed*0.3); oldtx=timex; oldy=pwm0+PID_A.speed*0.3; //.......................................................................... #endif //************************************************************************** // PID regulation ! OnFwd(OUT_A, (PID_A.outp)); // action ! Wait(PID_A.regtime); // wait regulation time //************************************************************************** readold = PID_A.read; // save old sensor errorold = PID_A.err; // save old error PID_A.read = (MotorRotationCount(OUT_A)); // get new encoder value PID_A.err = PID_A.target-PID_A.read; // new error to target if (PID_A.read>cmax) cmax=PID_A.read; // monitor overshooting else if (PID_A.read<cmin) cmin=PID_A.read; // monitor overshooting if ((PID_A.cont)&& (abs(PID_A.err)<=PID_A.precis)) PID_A.runstate = 0x60; else PID_A.runstate = 0x20; #ifdef debug_PID_A //.......................................................................... printf1(68, 48,"%-5d" , cmax); printf1(68, 40,"%-5d" , cmin); printf1(68, 32,"%-5d" , PID_A.read); //.......................................................................... #endif if (PID_A.cont) continue; if (abs(PID_A.err)<=PID_A.precis) { regloop +=1 ; PID_A.runstate = 0x40; } } while ((abs(PID_A.err)>=PID_A.precis) && (regloop<=5)); // target reached Off(OUT_A); // finished - brake motor PID_A.runstate = 0x40; // run state: RAMPDOWN PID_A.outp=0; Wait(50); PID_A.read = MotorRotationCount(OUT_A); regloop=1; if (PID_A.read>cmax) cmax=PID_A.read; // detect overshooting if (PID_A.read<cmin) cmin=PID_A.read; // detect overshooting PID_A.err = PID_A.target-PID_A.read; #ifdef debug_PID_A //............................................................................ printf1(68, 56,"%-5d" , PID_A.target); printf1(68, 48,"%-5d" , cmax); printf1(68, 40,"%-5d" , cmin); printf1(68, 32,"%-5d" , PID_A.read); printf1(56, 24,"P %5.2f" , PID_A.P); printf1(56, 16,"I %5.2f" , PID_A.I); printf1(56, 8,"D %5.2f" , PID_A.D); //............................................................................ #endif if ((abs(PID_A.err)>PID_A.precis)) {goto _Astart;} #ifdef debug_PID_A //............................................................................ PointOut(timex,PID_A.read/scrYratio); LineOut(oldtx, oldy, timex, pwm0); LineOut(timex+2,PID_A.target/scrYratio, timex+10, PID_A.target/scrYratio); LineOut(timex+2, pwm0, timex+10, pwm0); //............................................................................ #endif PID_A.runstate=0; Wait(1); //runstate = IDLE } //==============================================================================
a) statt
task task_PID_A()
stünde dann hier also
void * pid_calc( void *)
und der Rest bliebe gleich?
(die ..._A am Ende fallen ntl dann auch überall weg)
und b)
auch wenn ich jetzt kurz hinterenander mehrfach diesen Task für mehrere Motoren starte, wobei die Laufzeiten sich mehrfach überlappen
RotatePID(0, 3000, 15, false);
RotatePID(2, -4000, 90, false);
delay(1);
RotatePID(8, 10000, 50, true );
delay(10);
RotatePID(1, -5000, 60, false);
RotatePID(3, -3000, 20, true);
und also in diesem Falle 5 verschiedene Threads parallel asynchron laufen müssten, mit Starten eines neuen Tasks ohne auf die Beendigung des vorherigen warten zu müssen, die sich dann alle auch unabhängig zu verschiedenen Zeiten beenden oder (in 2 Fällen) "unendlich" weiter laufen -
das funktioniert dann
Geändert von HaWe (28.09.2016 um 21:01 Uhr)
zu a)
ja, task_PID_[ABC] wird durch die allgemeine form pid_calc ersetzt.
Sinngemäss wie du auf die Elemente des Arrays zugreifst sähe so aus (ohne das ich jetzt verstehe was dein Algo macht):
zu b)Code:void *pid_calc(void *arg) { float aspeed, damp, PWMpwr, readold, errorold, tprop; long readstart, cmax, cmin; // for monitoring long starttime, runtime, clock, dtime; // timer char regloop; // arg nach index casten unsigned port = (unsigned)arg; motor[port].runstate = 0x10; // reg state: RAMPUP motor[port].read = (MotorRotationCount(port)); // get current encoder reading motor[port].err = motor[port].target - motor[port].read; // error to target readstart = motor[port].read; regloop = 1; damp=0; // damp the integral memory starttime= CurrentTick(); // appoach target _Astart: motor[port].runstate = 0x10; // run state: RUNNING do { pthread_testcancel(); dtime = CurrentTick() - clock; clock = CurrentTick(); runtime = clock - starttime; tprop = dtime/20.0; if ((motor[port].err==errorold)&& (abs(motor[port].err)>motor[port].precis)) damp=1; // stalling else damp=motor[port].damp; motor[port].integr = (damp * motor[port].integr) + motor[port].err; if((motor[port].integr) > 3*motor[port].maxout) motor[port].integr = 3*motor[port].maxout; // cut away else if((motor[port].integr) <-3*motor[port].maxout) motor[port].integr =-3*motor[port].maxout; PWMpwr= (motor[port].P*motor[port].err) + (motor[port].I*motor[port].integr)*tprop + (motor[port].D*(motor[port].err-errorold))/tprop; if(PWMpwr > motor[port].maxout) PWMpwr= motor[port].maxout; // forward maxout else if(PWMpwr < -motor[port].maxout) PWMpwr= -motor[port].maxout; // reverse maxout motor[port].speed= (motor[port].read-readold)*100/dtime; // rotat speed [degrees/100ms] aspeed = abs(motor[port].speed) ; if (aspeed > motor[port].tarpwm) { PWMpwr = sign(PWMpwr)*motor[port].tarpwm; } motor[port].outp = round(PWMpwr); //************************************************************************** // PID regulation ! OnFwd(port, (motor[port].outp)); // action ! Wait(motor[port].regtime); // wait regulation time //************************************************************************** readold = motor[port].read; // save old sensor errorold = motor[port].err; // save old error motor[port].read = (MotorRotationCount(port)); // get new encoder value motor[port].err = motor[port].target-motor[port].read; // new error to target if (motor[port].read>cmax) cmax=motor[port].read; // monitor overshooting else if (motor[port].read<cmin) cmin=motor[port].read; // monitor overshooting if ((motor[port].cont)&& (abs(motor[port].err)<=motor[port].precis)) motor[port].runstate = 0x60; else motor[port].runstate = 0x20; if (motor[port].cont) continue; if (abs(motor[port].err)<=motor[port].precis) { regloop +=1 ; motor[port].runstate = 0x40; } } while ((abs(motor[port].err)>=motor[port].precis) && (regloop<=5)); // target reached Off(port); // finished - brake motor motor[port].runstate = 0x40; // run state: RAMPDOWN motor[port].outp=0; Wait(50); motor[port].read = MotorRotationCount(port); regloop=1; if (motor[port].read>cmax) cmax=motor[port].read; // detect overshooting if (motor[port].read<cmin) cmin=motor[port].read; // detect overshooting motor[port].err = motor[port].target-motor[port].read; if ((abs(motor[port].err)>motor[port].precis)) { goto _Astart; } motor[port].runstate=0; Wait(1); //runstate = IDLE }
klar geht das, in deinem Beispiel legst du einfach fünf Threads an, die vom OS ausgeführt werden.
Was du nicht machen darfst ist zweimal ein RotatePID mit der identischen Portnummer aufzurufen ohne vorher ein StopPIDcontrol aufzurufen. Würdest du das machen hättest du zwei Threads die dann doch wieder auf einem PID rumrechnen.
Ausserdem würde ich statt Nummern für die Ports eine enum erstellen, Namen sind einfacher zu lesen, als mit 0,4,7 einen Motor zu assoziieren.
toll, danke, auch für deine Mühe mit dem ganzen Code
das war auch bisher der Punkt, den ich nicht verstanden hatte mit der Vielzahl der pthread-Tasks:
Ich dachte immer, um 2 (3,4,5) tasks unabhängig zu starten, müsse man ihnen außer eigene pthreadIDs auch 2 (3,4,5) verschiedene eindeutige unterscheidbare Namen geben
- dass es ausreicht, einen identischen Namen zu verwenden und zusätzlich zur eindeutigen pthreadID nur noch ein 4.Parameter damit sie verschiedene Variablen ansprechen, war mir nicht klar.
Es scheint also vor allem der eindeutige Handle (die pthread-ID im 1. Parameter) zu sein, die den task auf einer eigenen unabhängigen Timeslice starten lässt.
Perfekt.
Wenn ich das jetzt richtig verstanden habe, dann wird es funktionieren.
Tausend Dank, ich setz mich dran!
ps,
das mit den Namen werde ich später auch so machen, wschl allerdings als #defines:
#define LEFTW 0
#define RIGHTW 1
#define TURNTABLE 2
#define SHOULDER 3
oder so ähnlich...
Geändert von HaWe (28.09.2016 um 23:34 Uhr)
update:
hab meinen ursprünglichen NXC Code jetzt soweit fü gpp portiert und entlaust, dass er (bis auf ein paar Warnings) fehlerfrei compiliert.
Ich bn mir noch unklar darüber, wo das pthread_join hinmuss, wenn man es den Tasks überlässt, dass sie bis zur Selbstterminierung laufen, um hier keine Karteileichen im Scheduler zu hinterlassen.
Ist das richtig: direkt hinter dem Aufruf von pthread im RotatePID Wrapper?
oder muss hier evtl auch noch eine zusätzliche while Abfrage dazwischen, denn der Task soll ja nicht unmittelbar nachdem er gestartet wurde schon wieder beendt/gejoint werden?Code:void RotatePID(uint8_t port, long Target, float RotatSpeed, int8_t cont) { motor[port].runstate=1; // set runstate: PID active // custom init PID [port] motor[port].target =Target; // assign target motor[port].tarpwm =RotatSpeed; // assign rotation speed motor[port].cont=cont; // cont vs. hit once // Reset PID control defaults motor[port].outp =0; // PID control output value motor[port].maxout =100; // absolute max possible output (max pwr) motor[port].read =0; // current reading motor[port].err =0; // current error motor[port].integr =0; // integral of errors motor[port].speed =0; // current speed pthread_create( & motor[port].tid, // id speichern in dem dazugehörigen Array-Element 0, // Default Attribute (threads cancelbar) PID_calc, // Name der PID-Kalkulationsroutinge (void*)port); // der Index des Array-Elements für eine PID Struktur, // die mit dem Motorindex gleich ist. pthread_join(motor[port].tid, NULL); // <<<<<<<<<<<<<<<<<< ???? }
z.B.:
while(motor[port].tid);
Nein, das join kommt woanders hin.
Aber erstmal musst du zwei Typen von Threads unterscheiden:
1) Ein Thread auf den du nicht warten willst und der einfach durchlaufen soll ist ein "Detached" Thread. Ist dieser durch seine Routine gelaufen, dann räumt das OS von alleine auf. Damit ein Thread "Detached" markiert ist, rufst du nach dem pthread_create mit der ThreadId pthread_detach auf:
Zu beachten gilt hier noch, dass du so einen Thread nach dem detach nie wieder joinen kannst. Sinvollerweise markierst du das Ende des Threads in der Routine zum Schluss im runstate und setzt die tid wieder auf 0.Code:#include <stdbool.h> void RotatePID(uint8_t port, long Target, float RotatSpeed, int8_t cont, bool detach) { motor[port].runstate=1; // set runstate: PID active // custom init PID [port] motor[port].target =Target; // assign target motor[port].tarpwm =RotatSpeed; // assign rotation speed motor[port].cont=cont; // cont vs. hit once // Reset PID control defaults motor[port].outp =0; // PID control output value motor[port].maxout =100; // absolute max possible output (max pwr) motor[port].read =0; // current reading motor[port].err =0; // current error motor[port].integr =0; // integral of errors motor[port].speed =0; // current speed pthread_create( & motor[port].tid, // id speichern in dem dazugehörigen Array-Element 0, // Default Attribute (threads cancelbar) PID_calc, // Name der PID-Kalkulationsroutinge (void*)port); // der Index des Array-Elements für eine PID Struktur, // die mit dem Motorindex gleich ist. // Zur Unterscheidung Parameterliste von RotatePID und High-Level-Funktionen erweitern. if(detach) pthread_detach(& motor[port].tid); }
2) Bei den anderen Threads die du joinen möchtest mußt du unterscheiden ob du auf das natürliche Ende warten willst oder ob du sie abbrechen (pthread_cancel) möchtest.
In deinem API hattest du eine StopPIDcontroler Funktion, in diese würde ich das optionale cancel und das join implementieren. In etwa so:
Dann hast du eine klare Aufteilung: StopPIDcontrol hält joinable Threads (gewaltsam) an und alle anderen High-Level-Routinen starten einen Thread.Code:#include <stdbool.h> void StopPIDcontrol(char port, bool cancel) { if(cancel) pthread_cancel(motor[port].tid) pthread_join(motor[port].tid, 0); motor[port].tid = 0; motor[port].runstate = 0; // und andere Variablen nach dem Ende des Threads setzen }
Gruss
botty
P.S. Sinnvollerweise guckst du dir in den man Pages die Rückgabewerte der pthread_ Funktionen an und behandelst noch die möglichen Fehler, das habe ich hier weg gelassen.
Geändert von botty (29.09.2016 um 13:22 Uhr)
meine PID threads müssen sowohl durchlaufen können (sich selbst überlassen) als auch von außen beendet werden können, falls nötig.
Wie das gehen soll, ist im Prinzip auch "Verhandlungssache" (gewaltsam oder über einen Semaphor, s.u.).
Wer dann hinterher aufräumt, ist mir wurscht, am praktischsten ntl, wenn es automatisch geht
A-Bär: wenn er einmal beendet ist, muss er (bzw. sein Nachfolger mit derselben port-Nr) ntl jederzeit wieder neu gestartet werden können, für die nächste PID-Annäherung. Ob er dann vom OS eine neue pthread-ID zugewiesen bekommt, ist aber ntl auch wieder egal.
Was das Joinen bewirkt, ist mir nicht ganz klar, ich hatte es so verstanden, dass ihre Timeslice wieder mit der von main() vereinigt wird.
Alle PID-Tasks sollen so gleichartig behandelt werden, während meine Tasks, die von main() am Anfang gestartet werden, so bleiben können wie bisher:
hier muss ich nichts unbedingt gewaltsam beenden, und wenn, dann funktioniert das dort über eine globale Variable (Semaphor) _TASKS_ACTIVE_ :
sobald die iwo auf false gesetzt wird, beenden sich alle Haupt-Threads dann selbsttätig und werden dann in main() gejoint:
Code:// ***SNIP*** void* thread3Go (void* ) { // highest priority: 100µs encoder timer while(_TASKS_ACTIVE_) { updateEncoders(); usleep(100); } return NULL; } void* thread4Go (void* ) { // medium priority: UART while(_TASKS_ACTIVE_) { UARTcomm(); } return NULL; } // ***SNIP*** int main() { char sbuf[128]; int err; // wiringPi err = wiringPiSetup(); if( err == -1 ) return 1; // UART Serial com port fserialRemote = serialOpen (usb0, UARTclock); // UART baud rate initarrays(); setupDpins(); setupI2C(); pthread_t thread0, thread1, thread2, thread3, thread4, thread5; struct sched_param param; pthread_create(&thread0, NULL, thread0Go, NULL); // lowest priority task: screen output param.sched_priority = 10; pthread_setschedparam(thread0, SCHED_RR, ¶m); pthread_create(&thread1, NULL, thread1Go, NULL); // low priority: keyboard monitoring (stop program) param.sched_priority = 20; pthread_setschedparam(thread1, SCHED_RR, ¶m); pthread_create(&thread2, NULL, thread2Go, NULL); // medium priority: motor control param.sched_priority = 40; pthread_setschedparam(thread2, SCHED_RR, ¶m); pthread_create(&thread3, NULL, thread3Go, NULL); // highest priority: encoder reading param.sched_priority = 80; pthread_setschedparam(thread3, SCHED_FIFO, ¶m); pthread_create(&thread4, NULL, thread4Go, NULL); // medium priority: UART comm <<< test !! param.sched_priority = 40; pthread_setschedparam(thread4, SCHED_FIFO, ¶m); pthread_create(&thread5, NULL, thread5Go, NULL); // medium priority: navigation param.sched_priority = 40; pthread_setschedparam(thread5, SCHED_FIFO, ¶m); while(_TASKS_ACTIVE_) { delay(1); } // wait for threads to join before exiting pthread_join(thread0, NULL); pthread_join(thread1, NULL); pthread_join(thread2, NULL); pthread_join(thread3, NULL); pthread_join(thread4, NULL); pthread_join(thread5, NULL); serialClose( fserialRemote); exit(0); }
falls es das irgendwie einfacher macht, könnte man auch die PID-threads auf diese Weise mit einer zusätzlichen Abbruchbedingung stoppen!
Lesezeichen