-
und wie geht es, wenn die Klasse über
class PID;
PID.start()
selber ihren eigenen privaten pthread Task starten möchte?
(hat sich überschnitten, ich bezog mich auf mxt's code)
- - - Aktualisiert - - -
über botty's Variante muss ich nochmal etwas nachdenken...
- - - Aktualisiert - - -
@botty:
deinen Vorschlag davor
https://www.roboternetz.de/community...l=1#post631429
hatte ich völlig überlesen - vielen Dank!
hat sich auch wohl mit einem anderen zeitlich überschnitten.
Ich blicke auch dabei noch nicht völlig durch, aber ich versuche gerade, mich da hineinzudenken... :)
-
So hier noch mal ein Beispiel, auf Raspian getestet. Die returns sind doch nötig.
Code:
#include <stdio.h>
#include <pthread.h>
class Pid
{
private:
pthread_t m_thread;
int m_a; // Nur Beispielparameter, hier kämen die richtigen Parameter des Reglers hin
int m_b;
static void* helper(void* instance)
{
return ((Pid*) instance)->run();
}
void* run()
{
printf("Die Parameter sind %d %d\n", m_a, m_b);
// Hier käme die Regelschleife rein
return 0;
}
public:
Pid(int a, int b) : m_a(a), m_b(b)
{}
void start()
{
pthread_create(&m_thread, NULL, &helper, this);
}
void stop()
{
pthread_join(m_thread, 0);
}
};
int main()
{
Pid test(10, 42);
test.start();
// ...
test.stop();
return 0;
}
Die eigentliche Regelung fände in der Funktion run statt. Man beachte, dass diese und die statische helper Funktion privat sind, also von außen nicht aufgerufen werden dürfen.
Die Parameter würden im Konstruktor gesetzt. Kann man aber auch über Parameter in start lösen.
Ich würde aber trotzdem empfehlen, entweder die Lösung von botty oder richtige C++ Threads zu nehmen.
-
Oder nochmal in C++11
Code:
#include <iostream>
#include <thread>
using namespace std;
typedef struct {
pthread_t tid;
char* name;
float p;
float i;
float d;
} PID_t;
class TestClass
{
public:
TestClass() {}
~TestClass() {}
void Start(PID_t *p)
{
t = new thread(&this->MyTask, p);
}
void Stop()
{
t->join();
}
private:
static void MyTask(PID_t *p)
{
cout << "PID: " << p->tid << endl;
cout << "Name: " << p->name << endl;
}
thread *t;
};
int main()
{
cout << "Start" << endl;
PID_t pp[3] = { {0, (char*)"MyName1", 5, 6, 7},
{1, (char*)"MyName2", 5, 6, 7},
{2, (char*)"MyName3", 5, 6, 7}};
TestClass tc1;
TestClass tc2;
TestClass tc3;
tc1.Start(&pp[0]);
tc2.Start(&pp[1]);
tc3.Start(&pp[2]);
//...
tc1.Stop();
tc2.Stop();
tc3.Stop();
return 0;
}
-
Danke für alle Tipps!
bin gerade ein paar Tage außer Haus, daher kann ich es z.Zt nicht testen.
Für MT kommen keine C++11 tasks in Frage, wie ich bereits schrieb, weil ich mich nicht damit auskenne - sondern nur pthread, das ich auch sonst verwende.
Bottys Version ganz ohne ++ hatte ich ja zunächst überlesen, das will ich mal als nächstes versuchen umzusetzen, wenn ich hier wieder einen Pi zum programmieren habe. Ich melde mich dann baldmöglichst!
hier erst einmal - nochmal - vielen Dank!
-
hallo,
2 Dinge sind mir mit der Struktur noch unklar:
warum muss am Anfang immer eine Null stehen
Code:
struct PID pids[] = {
{0, "Task A", 0.015, 0.2, 0.7 },
{0, "Task B", 0.3, 0.5, 0.80 }
};
und 2., ist es erlaubt, die Struktur statt mit einem String auch mit einem char (0, 1, 2, 3, ...) zu definieren und aufzurufen oder muss es unbedingt ein String sein?
Code:
struct PID pids[] = {
{0, 0, 0.015, 0.2, 0.7 },
{0, 1, 0.3, 0.5, 0.80 }
};
:?:
-
@botty:
je mehr ich drüber nachdenke, wird mir allerdings klar, dass ich gar nicht recht verstehe, wofür diese Struktur
https://www.roboternetz.de/community...l=1#post631429
Code:
struct PID pids[] = {
{0, "Task A", 0.015, 0.2, 0.7 },
{0, "Task B", 0.3, 0.5, 0.80 }
};
überhaupt da ist:
Das "setten" der Kp, Ki, Kd (kurz: P, I, D) soll ja nicht im Aufruf der PID-Funktion stehen.
Die PID Funktion wird nur mit der Motornummer und dem RotationTarget aufgerufen plus einem pwm-Wert für die max. Rotationsgeschwindigkeit
Code:
void RotatePIDtoTarget (char port, long Target, float RotatSpeed); // approach absolute target once
void RotatePIDdegrees (char port, long Target, float RotatSpeed); // turn relative degrees
void RotatePIDcontinue (char port, long Target, float RotatSpeed); // approach target continuously
void StopPIDcontrol (char port); // stop PID
Das Setten der PID-Konstanten geschieht automatisch bei der Initialisiering und kann dann auf Wunsch auch optional durch extra Funktionen geschehen:
// first Initialization:
void PIDinit(); // P=0.4, I=0.4, D=10.0
//
// b) simple customized PID setting:
void SetPIDparam(char port, float P,float I,float D);
//
// c) extended customized parameter setting:
void SetPIDparamEx(char port, float P,float I,float D, float prec,int rtime, float damp);
wozu also deine
:?:
-
Hallo HaWe,
sorry das ich mich jetzt erst melde aber bei dem schönen Wetter konnte mich am WE nichts vor den Bildschirm locken.
Zu Deinen Fragen:
Meine "struct pid" sollte nur ein Auszug aus Deiner "struct PIDstruct" sein. Worauf es mir ankommt ist, dass du diese um zwei Felder erweiterst:
einmal um ein Element pthread_t und
zweitens, um ein Element worüber du deine Motoren identifizieren kannst.
Dadurch kannst du deinen Code wesentlich verkürzen, schau mal deine
Code:
safecall void StopPIDcontrol (char port) {
if (port==OUT_A) {
if (PID_A.runstate!=0) { // stop PID task if already running
stop task_PID_A;
PlayTone(TONE_C7,100);
Off(OUT_A);
Wait(50);
PID_A.cont=false;
PID_A.runstate=0;
Wait(1);
return;
}
}
else
if (port==OUT_B) {
if (PID_B.runstate!=0) { // stop PID task if already running
stop task_PID_B;
PlayTone(TONE_C7,100);
Off(OUT_B);
Wait(50);
PID_B.cont=false;
PID_B.runstate=0;
Wait(1);
return;
}
}
else
if (port==OUT_C) {
if (PID_C.runstate!=0) { // stop PID task if already running
stop task_PID_C;
PlayTone(TONE_C7,100);
Off(OUT_C);
Wait(50);
PID_C.cont=false;
PID_C.runstate=0;
Wait(1);
return;
}
}
}
an.
Da steht dreimal quasi das Gleiche.
Wenn du jetzt deine Struktur erweiterst und statt dem Port einen Zeiger auf die Variable übergibst, läßt sich der Code um 2/3 kürzen.
Code:
struct PIDstruct {
/* Erweiterung */
pthread_t tid;
unsigned short motor_id;
// custom target values
long target; // set target
int tarpwm; // motor target speed
// custom regulation parameters
float P; // P: basic propotional to error
float I; // I: integral: avoid perish
float D; // D: derivative: avoid oscillating
float precis; // error precision to target
int regtime; // PID loop time
float damp; // damp the integral memory
char cont; // target: continue or hit once
// internal control variables
char runstate; // monitors runstate
int outp; // PID control output value
int maxout; // max output (max motor pwr)
long read; // current sensor reading
float err; // current error
float integr; // integral of errors
float speed; // current speed
} ;
void StopPIDcontrol (struct PIDstruct *pid) {
if (pid->tid) { // stop PID task if already running
pid_stop(pid, 1); // siehe botties zweites Beispiel
Off(pid->motor_id);
pid->cont=false;
pid->runstate=0;
return;
}
}
Wenn du jetzt eine Variable PID_A definierst, würde der Aufruf so aussehen:
Code:
struct PIDstruct PID_A;
void eine_stop_funktion(void) {
StopPIDcontrol( &PID_A );
}
Du wählst somit nicht mehr über eine port/switch Kombination deinen PID Controler aus, sondern beim Aufruf der Funktionen über den Zeiger auf die Variable, welche alle Daten für einen Controler enthält.
D.h. bei allen Funktionen ersetzt du "char port" durch "struct PIDstruct* pid" und dereferenzierst die Elemente der Struktur nicht mehr über den "." Operator sondern über den "->" Operator.
Auch bei der Erezugung der Threads können wir jetzt den Zeiger auf die Variable als Parameter in die Thread-Funktion hineingeben, denn die Signatur dieser Funktion lautet:
Code:
void *(*function)(void *arg);
Zwar ist arg vom Typ "void *" nur da wir immer nur Zeiger vom Typ "struct PIDcontrol *" hineingeben können wir am Anfang der Routine diesen Zeiger sicher auf den richtigen Typ casten:
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;
/* das ist ein ziemlich sicherer cast */
struct PIDcontrol *pid = (struct PIDcontrol *) arg;
pid->runstate = 0x10; // reg state: RAMPUP
pid->read = (MotorRotationCount(pid->motor_id)); // get current encoder reading
pid->err = pid->target - pid->read; // error to target
readstart = pid->read;
/* ... */
}
so brauchst du auch diese Routine nur einmal, denn bei der Thread Erzeugung übergibst du den Zeiger auf die Variable z.B.:
Code:
pthread_create(&PID_A.tid, 0, pid_calc, &PID_A);
ich hoffe das es jetzt klarer ist, warum ich Zeiger verwenden würde?
Noch ein Wort zu den Variablen für die einzelnen PID Controler, du definierst die so:
Code:
struct PIDcontrol PID_A;
struct PIDcontrol PID_B;
struct PIDcontrol PID_C;
bei sowas sehe ich sofort eine Aufzählung und wenn ich dann auch noch Operationen über diese machen muss, wie z.B. ein warten auf das Beenden eines Threads, dann ordne ich sowas in einem Array an:
Code:
enum motoren {
MOT_SCHULTER = 0,
MOT_ELLE,
MOT_HAND,
MOT_MAX
};
struct PIDcontrol pids [MOT_MAX];
/* Verwendung */
void ein_stop(void) {
StopPIDcontrol( &pids[MOT_SCHULTER );
}
void mehrere_stops(void) {
int idx;
for(idx = MOT_SCHULTER; idx < MOT_MAX; idx++)
StopPIDcontrol( &pids[idx] );
}
find ich bequemer, ist aber Geschmackssache.
Gruss
botty
-
vielen Dank, dass ich mehere Einzelfunktionen einsparen will ist kar, das war ja meine Absicht, dennoch sehe ich jetzt schon ein wenig klarer.
Dennoch ist das mit den doppelten Pointern un dem "casting" schon seeehr kompliziert...
Außerdem ist mir nicht klar, wie jetzt genau die Verknüpfung erfolgen muss von der aufrufenden simplen API Funktion bis hin zum Task:
der vereinfachende Wrapper
Code:
inline void RotatePIDtoTarget(char port, long Target, float RotatSpeed) {
RotatePID(port, Target, RotatSpeed, false);
Wait(1);
}
ruft auf die ein wenig komplexere Interface-Funktion inkl. dem Target-Haltemodus:
Code:
RotatePID(port, Target, RotatSpeed, false);
und hier wird nach Setzen der PID Laufzeit-Variablen der PID Task gestartet:
Code:
safecall void RotatePID(char port, long Target, float RotatSpeed, char cont) {
if (port==OUT_A) {
//...
//...
PID_A.runstate=1; // set runstate: PID active
// custom init PID_A
PID_A.target =Target; // assign target
PID_A.tarpwm =RotatSpeed; // assign rotation speed
PID_A.cont=cont; // cont vs. hit once
// Reset PID control defaults
PID_A.outp =0; // PID control output value
PID_A.maxout =100; // absolute max possible output (max pwr)
PID_A.read =0; // current reading
PID_A.err =0; // current error
PID_A.integr =0; // integral of errors
PID_A.speed =0; // current speed
start task_PID_A;
}
else
if (port==OUT_B) {
//...
statt der if-Schleifen für jeden Motor käme jetzt dein expliziter PID-Aufruf mit Motornummer als Parameter -
wie würde es aber denn heißen müssen, wenn ich den PID pthread task innerhalb deiner neuen Struktur starten will?
-
Na ja,
zuerst must du die PIDinit ergänzen:
Code:
void PIDinit(void) {
PID_A.tid = 0;
PID_A.motor_id = OUT_A;
// für alle Variablen und dann natürlich die anderen Elemente der Struktur.
}
Die Signaturen deiner High-Level Funktionen ändern sich nur im ersten Element, die anderen Parameter kannst du 1:1 übernehmen z.B.:
Code:
inline void RotatePIDtoTarget(struct PIDstruct *pid, long Target, float RotatSpeed) {
RotatePID(pid, Target, RotatSpeed, false);
}
wobei du festlegen musst ob evtl. Fehlercodes wieder hochgereicht werden? Hier spar ich mir das mal.
Deine zentrale RotatePID sähe dann so aus:
Code:
void RotatePID(struct PIDstruct *pid, long Target, float RotatSpeed, char cont) {
// Nullzeiger?
if( ! pid ) {
fprintf(stderr, "RotatePID Null Zeiger!\n");
return;
}
// Laeuft der PID in einem Thread bereits?
if( pid->tid != 0 ) {
fprintf(stderr, "RotatePID - PID schon gestartet!\n");
return;
}
// Koennte weg
PID_A.runstate=1; // set runstate: PID active
// custom init pid
pid->target =Target; // assign target
pid->tarpwm =RotatSpeed; // assign rotation speed
pid->cont=cont; // cont vs. hit once
// Reset PID control defaults
pid->outp =0; // PID control output value
pid->maxout =100; // absolute max possible output (max pwr)
pid->read =0; // current reading
pid->err =0; // current error
pid->integr =0; // integral of errors
pid->speed =0; // current speed
// Thread starten
if(pthread_create(&pid->tid, 0, pid_calc, pid)) {
perror("RotatePID");
pid->tid = 0;
return;
}
}
Stoppen ginge dann so
Code:
void StopPIDcontrol (struct PIDstruct *pid, int cancel) {
if (pid->tid) { // stop PID task if already running
Off(pid->motor_id);
if(cancel)
pthread_cancel(pid->tid);
pthread_join(pid->tid, 0);
pid->tid = 0; // wichtig!
pid->cont=false;
pid->runstate=0;
}
}
Die verwendung sähe dann etwa so aus:
Code:
struct PIDstruct PID_A;
int main(int argc, char** argv) {
PIDinit();
// Wartend
RotatePIDtoTarget(&PID_A, 25, 0.4); // Sinvolle Werte kennst du besser als ich ;)
StopPIDcontrol(&PID_A, 0);
// Abbrechend
RotatePIDtoTarget(&PID_A, 35, 0.3);
sleep(2);
StopPIDcontrol(&PID_A, 1);
return 0;
}
Ich habe diesen Code nicht durchkompiliert, hoffe aber das er trotzdem deine Fragen klärt.
Noch ein Wort zur pid_calc Funktion: In ihr solltest du alle paar Schritte, insbesondere in Schleifen, pthread_testcancel() aufrufen, damit ein cancel auch wirklich stattfinden kann.
Gruss
botty
-
hallo botty,
ganz herzlichen Dank für deine viele Mühe und deine ausführlichen Codebeispiele. Leider verstehe ich im Moment doch noch gar nichts über die ganzen neuen kryptischen Funktionen, Aufrufe und Konstruktionen, insb. noch nicht einmal, warum ich -> statt . verwenden muss.
Tatsächlich möchte ich allerdings im Aufruf auch nur eine Motornummer übergeben (0,1,2,3 oder per
#define OUT_A 0
#define OUT_B 1
etc.
und keine PID-Struktur.
Der Sinn ist, dass meine neue C Syntax an NXC-API Befehle angelehnt sein soll, denn das Ziel ist es auch, Lego Roboter für bisherige Lego NXT und NXC Nutzer mit einer Raspi API zu programmieren und zu betreiben, was bisher nicht möglich ist bei dem völlig fehlkonstruierten neuen Lego EV3-SoC: also quasi als Konkurrenz-Gegenentwurf ;) . Ich habe auch schon mit so einem Gegenprojekt angefangen, PID wäre der nächste Schritt hierfür: http://www.mindstormsforum.de/viewtopic.php?f=78&t=8851
NXC (bzw. Vorgänger NQC) ist den Lego Nutzern seit fast 20 Jahren bekannt, und hier werden ebenfalls Motor Steuerbefehle verwendet wie
motorcommand ( motorNr, rotationDegrees, pwm)
Ich bin für deine neuen Konstrukte aber einfach noch zusehr auf dem absoluten C Anfängerlevel, als dass ich das jetzt wirklich umsetzen könnte. Ich fürchte im Moment, dass meine 3 -10 Einzel Tasks wie bei NXC doch die einzige Möglichkeit sind, das ganze PID Projekt auf dem Raspi für mich zu verwirklichen.
Vlt könntest du mir aber doch quasi als minimal kleinst-möglichen Lernschritt erklären, warum ich hier oft -> statt . verwenden muss?
Bei Arduino und auch sonst nutzt man doch auch den . z.B.
Serial.print()
Serial.available()
Wire.begin()
Und Variablenwerte in Strukturen meine ich auch schon mit Punkt gesehen zu haben
mystruc.fvar=1.234;