2016-08-03 28 views
1

Ich bin in einer logischen catch-22 gefangen. Lassen Sie mich klarstellen, was ich zu tun versuche: Ein Knopfdruck löst einen Motor aus, bis ein Sensor gefühlt wird (sendet 3,3 V an mein Rpi GPIO), an diesem Punkt wird die Richtung umgekehrt. Das alles funktioniert gut; Das Problem ist, dass es in einer Endlosschleife stecken bleibt, also wenn ich einen anderen Knopf drücken möchte, um zum Beispiel die Geschwindigkeit zu erhöhen oder mitten in einem Lauf zu stoppen, kann ich nicht. Ich habe versucht, "wiringPiISR()" als Interrupt zu implementieren, aber das scheint auch innerhalb einer Schleife zu reagieren.Vermeiden Sie eine unbestimmte Schleife während Sie auf Benutzereingaben warten?

Denken Sie daran, das folgende ist nur ein Test, um etwas zu arbeiten, um an ein viel größeres Stück Code angepasst zu werden.

#include <libraries> 


using namespace std; 


MainWindow::MainWindow(QWidget *parent) : 
    QMainWindow(parent), 
    ui(new Ui::MainWindow) 
{ 
    ui->setupUi(this); 
    ui->label->setText("Nothing"); 
} 

MainWindow::~MainWindow() 
{ 
    delete ui; 

} 

void myInterruptLEFT(void) 
{ 
    qDebug() << "SENSOR HIT"; 
} 


void mainip(void)//this doesn't currently do anything. 
{ 
    wiringPiISR(24,INT_EDGE_RISING,&myInterruptLEFT); 

} 
void MainWindow::on_pushButton_clicked() 
{ 

qDebug() << "Loop Exited"; 

} 

void MainWindow::on_checkBox_clicked(bool checked) 
{ int i = 0; 
    wiringPiSetupGpio(); 
    while((checked =! 0)) 
    { 
     ui->label->setNum(i); 
     i++; 
    } 

} 

Also noch einmal, ich möchte nur eine Möglichkeit, dieses Programm ständig zur Kontrolle haben „24, INT_EDGE_RISING“ ... die für diejenigen von Ihnen nicht vertraut bedeutet, dass es einige Spannung an den 24. GPIO Pin geliefert werden (Steigend von Low-High-Volt) ... ohne dabei völlig begeistert zu sein. Eine Hintergrundschleife, oder ich weiß wirklich nicht, weshalb ich hier bin. Irgendwelche Ideen würden sehr geschätzt werden!

Antwort

0

Es ist nicht erforderlich, explizite Schleifen durchzuführen. Die Ereignisschleife erledigt das schon für Sie. Sie führen Aktionen aus, wenn bestimmte Ereignisse eintreten, z. wenn eine Schaltfläche aktiviert oder deaktiviert ist.

Es würde helfen, den Controller aus der Benutzeroberfläche herauszufiltern und sein Verhalten mit einem UML-Statechart zu spezifizieren. Der folgende Code entspricht 1: 1 dem Statechart.

Statechart of the Controller

Der s_moving Verbund Zustand hat keinen Anfangszustand, da sie nie direkt eingegeben hat, nur implizit, wenn seine substates eingeben.

// https://github.com/KubaO/stackoverflown/tree/master/questions/wiringpi-isr-38740702 
#include <QtWidgets> 
#include <wiringpi.h> 

class Controller : public QObject { 
    Q_OBJECT 
    QStateMachine m_mach{this}; 
    QState s_stopped{&m_mach}; 
    QState s_moving {&m_mach}; 
    QState s_forward{&s_moving}; 
    QState s_reverse{&s_moving}; 
    static QPointer<Controller> m_instance; 
    enum { sensorPin = 24, motorPinA = 10, motorPinB = 11 }; 
    // These methods use digitalWrite() to control the motor 
    static void motorForward() { 
     digitalWrite(motorPinA, HIGH); 
     digitalWrite(motorPinB, LOW); 
    } 
    static void motorReverse() { /*...*/ } 
    static void motorStop() { /*...*/ } 
    // 
    Q_SIGNAL void toStopped(); 
    Q_SIGNAL void toForward(); 
    Q_SIGNAL void toReverse(); 
    void setupIO() { 
     wiringPiSetupSys(); 
     pinMode(sensorPin, INPUT); 
     wiringPiISR(sensorPin, INT_EDGE_RISING, &Controller::sensorHit); 
    } 

sensorHit() Die Interrupt-Handler durch die wiringPi Bibliothek aus einem hochprioren Arbeiter-Thread aufgerufen wird, die für die Übergänge GPIO wartet, wie es durch den Kernel berichtet. Um die Latenz beim Umkehren des Motors zu minimieren, setzen wir dieses Thema ein. Da sensorHit() bereits in einem Thread mit hoher Priorität läuft und so nahe wie möglich am GPIO-Übergang ist, setzen wir sofort die umgekehrte Motorrichtung und geben ein Signal aus, um den Zustandsautomaten anzuweisen, in den Zustand s_reverse überzugehen. Da dieses Signal von einem anderen Thread als dem Hauptthread ausgegeben wird, in dem sich die Instanz Controller befindet, wird der Slot-Aufruf in die Ereigniswarteschlange des Hauptthreads eingereiht.

/// This method is safe to be called from any thread. 
    static void sensorHit() { 
     motorReverse(); // do it right away in the high-priority thread 
     emit m_instance->toReverse(); 
    } 
public: 
    Controller(QObject * parent = nullptr) : QObject{parent} { 
     Q_ASSERT(!m_instance); 
     // State Machine Definition 
     m_mach.setInitialState(&s_stopped); 
     s_stopped.addTransition(this, &Controller::toForward, &s_forward); 
     s_moving.addTransition (this, &Controller::toStopped, &s_stopped); 
     s_forward.addTransition(this, &Controller::toReverse, &s_reverse); 
     s_reverse.addTransition(this, &Controller::toForward, &s_forward); 
     connect(&s_stopped, &QState::entered, this, [this]{ 
     motorStop(); 
     emit isStopped(); 
     }); 
     connect(&s_forward, &QState::entered, this, [this]{ 
     motorForward(); 
     emit isForward(); 
     }); 
     connect(&s_reverse, &QState::entered, this, [this]{ 
     motorReverse(); 
     emit isReverse(); 
     }); 
     m_mach.start(); 
     // 
     m_instance = this; 
     setupIO(); 
    } 
    Q_SLOT void forward() { emit toForward(); } 
    Q_SLOT void stop() { 
     motorStop(); // do it right away to ensure we stop ASAP 
     emit toStopped(); 
    } 
    Q_SIGNAL void isStopped(); 
    Q_SIGNAL void isForward(); 
    Q_SIGNAL void isReverse(); 
}; 
QPointer<Controller> Controller::m_instance; 

Die UI von der Steuerung abgekoppelt ist: weder UI noch Controller-Objekte voneinander direkt bewusst sind, bis Sie sie connect mit verlinken:

int main(int argc, char ** argv) { 
    using Q = QObject; 
    QApplication app{argc, argv}; 
    Controller ctl; 
    QWidget ui; 
    QVBoxLayout layout{&ui}; 
    QLabel state; 
    QPushButton move{"Move Forward"}; 
    QPushButton stop{"Stop"}; 
    layout.addWidget(&state); 
    layout.addWidget(&move); 
    layout.addWidget(&stop); 
    Q::connect(&ctl, &Controller::isStopped, &state, [&]{ state.setText("Stopped"); }); 
    Q::connect(&ctl, &Controller::isForward, &state, [&]{ state.setText("Forward"); }); 
    Q::connect(&ctl, &Controller::isReverse, &state, [&]{ state.setText("Reverse"); }); 
    Q::connect(&move, &QPushButton::clicked, &ctl, &Controller::forward); 
    Q::connect(&stop, &QPushButton::clicked, &ctl, &Controller::stop); 
    ui.show(); 
    return app.exec(); 
} 

#include "main.moc" 

Zur Prüfung auf Desktop-Plattformen zu erleichtern, können wir hinzufügen ein triviales WiringPi-Modell, um alles in sich geschlossen zu machen:

// A rather silly WiringPi mockup 
std::function<void()> isr; 
int wiringPiSetupSys() { return 0; } 
void pinMode(int, int) {} 
void digitalWrite(int pin, int value) { 
    if (pin == 10 && value == HIGH) 
     QTimer::singleShot(1000, isr); 
} 
int wiringPiISR(int, int, void (*function)()) { 
    isr = function; 
    return 0; 
} 
+0

Geringfügige Anmerkung: Da diese Methode im Controller statisch ist, können Sie Controller :: sensorHit als Callback zu WiringPiISR verwenden, aber IMHO fügen Sie weitere Motoren hinzu wird auf diese Weise Ihren Code wirklich komplizieren. (Wie der Autor erwähnt: "Denken Sie daran, dass das Folgende nur ein Test ist, um etwas zur Arbeit zu bringen, um an ein viel größeres Stück Code angepasst zu werden"). Vielleicht möchten Sie in diesem Fall eine statische Klassenmethode überdenken. – Elijan9

+0

@ Elijan9 Leider ist die 'wiringPiISR' API kaputt. Callbacks sind nicht besonders nützlich, wenn Sie keine Benutzerdaten an den Rückruf weitergeben können. Sie benötigen also für jeden Pin einen separaten Callback. Das ist keine große Sache. Es gibt keine Möglichkeit, benutzerdefinierte Daten an den Rückruf zu übergeben, und da der Rückruf in einem anderen Thread ausgeführt wird, können Sie nicht direkt auf die Methoden des Controllers zugreifen, da sie nicht threadsicher sind. Nur die Signale sind (von Design). Und das machen wir. Es ist alles in Ordnung. Es gibt nichts zu überdenken, da der Callback ** statisch sein muss: Er muss in eine C-Funktion umgewandelt werden können. –

+0

@ Elijan9 Ich wünschte, Sie könnten 'std :: bind (& Class :: method, instance)' an 'wiringPiISR' übergeben , oder dass man dem Rückruf ein Argument liefern könnte, wie es in jeder anderen nicht gebrochenen Callback-API auf dem Planeten üblich ist, aber das können wir nicht tun. Ich werde diesen Mangel wahrscheinlich bald in 'wiringPi' beheben; Der Patch ist fast kürzer als jede Prosa, die ihn beschreibt. –

1

Ich bin mir nicht sicher, ob ich Ihr Problem vollständig verstehe, aber vielleicht könnten Sie eine Zustandsmaschine mit nur einer Schleife modellieren, die alle Tastendrücke überprüft und alle notwendigen Aktionen ausführt, dh Ihre on_checkBox_clicked würde einfach eine Markierung setzen dann in der Hauptschleife überprüft. Etwas wie folgt aus (in pseude Code):

void MainWindow::on_checkBox_clicked(bool checked) { 
    checkBox_wasClicked = true; 
} 

for (;;) { 
    if (checkBox_wasClicked) { 
      state = move_motor; 
      checkBox_wasClicked = false; 
    } else if (motor_reached_end) { 
      state = move_motor_reverse; 
      motor_reched_end = false; 
    } else if (/*... more transitions ... */){ 
    } 

    if (state == motor_move) { 
     i++; 
    } 
    /* .... more stuff ... */ 
} 

Auf diese Weise keine Zustand blockiert die Ankunft von neuen Tasten drückt oder andere Übergänge.

+0

Dies ist keine schlechte Idee. Ich würde herausfinden müssen, wie genau man in qt-creator nach anderen Tastendrücken sucht, aber ja, das ist mehr oder weniger die Idee. Also halte die Endlosschleife am Leben, aber achte auf andere Aktionen und priorisiere sie. Hoffentlich ist das möglich! – jhizzla

+0

@jhizzla Qt Creator ist eine IDE. Du meintest "nur" Qt :) –

1

IMHO sollten Sie immer einen Interrupt verwenden, wenn es verfügbar ist, anstatt ständig den aktuellen Zustand von etwas abzufragen. In diesem Fall muss ein Qt-Signal innerhalb des Interrupt-Handlers ausgegeben werden (und Sie müssen offensichtlich etwas mit diesem Qt-Signal verbunden haben). So konstruiert, brauchen Sie keine Schleife.

Es gibt jedoch eine geringfügige Catch, für Qt können Sie nur eine nicht statische Funktion aus einer QObject abgeleiteten Klasse ausgeben, während der Interrupt-Handler nicht Teil einer Klasse sein kann. Sie könnten dies leicht lösen, indem ein Zeiger auf den aktuellen Mainwindow mit (nur eine in Qt sein kann) und ein Signal zu dieser Klasse hinzufügen, zum Beispiel:

static MainWindow* mainWindow = nullptr; 

void ISRSensorDetected() 
{ 
    if (mainWindow) 
     emit mainWindow->SensorDetected(); 
} 

MainWindow::MainWindow(QWidget *parent) 
    : QMainWindow(parent), ui(new Ui::MainWindow) 
{ 
    ui->setupUi(this); 
    mainWindow = this; 
    wiringPiISR(24, INT_EDGE_RISING,&ISRSensorDetected); 
    connect(this, SIGNAL(SensorDetected()), this, SLOT(ReverseMotor())); 
} 

MainWindow::~MainWindow() 
{ 
    mainWindow = nullptr; 
} 

Da Sie nur unter Verwendung von Signalen und Slots eine nicht Blockierschleife, können Sie leicht mit allem anderen umgehen.

Hinweis: WiringPi eigentlich nicht Interrupts, sondern schafft einen separaten Thread für jeden Rückruf. Wenn Sie echte Interrupts verwenden, sollten Sie dies wahrscheinlich verbessern, indem Sie stattdessen QMetaObject::invokeMethod(..., Qt::QueuedConnection) verwenden, damit der Interrupt-Handler so schnell wie möglich zurückgibt und nur kritische Operationen sofort ausführen. Die SensorDetected-Slots werden nicht sofort im Interrupt-Handler aufgerufen, sondern später von der Qt-Hauptschleife aufgerufen. Zum Beispiel:

void ISRSensor1Detected() 
{ 
    StopMotor1(); 
    if (mainWindow) 
     QMetaObject::invokeMethod(mainWindow, "Sensor1Detected", Qt::QueuedConnection); 
} 

Falls Sie wirklich brauchen, um eine Schleife in Qt verwenden, die Sie regelmäßig die GUI-Änderungen durch den Aufruf QCoreApplication::instance()->processEvents(QEventLoop::ExcludeUserInputEvents) aktualisieren konnte. Aber das sollte in diesem Fall nicht nötig sein.

+0

Dies ist im Allgemeinen der richtige Ansatz, außer dass Sie wirklich die Motorrichtung direkt im Interrupt-Callback umkehren wollen, bevor irgendein anderer Code ausgeführt wird. –

+0

Beachten Sie, dass es keinen Grund gibt, eine explizit in die Warteschlange gestellte Verbindung zu verwenden. Die automatische Verbindung macht das Richtige: wird direkt, wenn das Signal im Thread des Ziels gesendet wird, oder wird in die Warteschlange gestellt, wenn das Signal von einem anderen Thread ausgegeben wird - wie es beim Pi ISR-Rückruf der Verdrahtung der Fall sein wird. –

+0

In diesem Fall 'emit mainWindows-> SensorDetected();' wird die Motorrichtung im Interrupt-Callback umgekehrt. Allerdings sollten Sie IMHO immer so wenig Zeit in Interrupt-Handler wie möglich verbringen, da andere Interrupts während der Behandlung auftreten können. Deshalb denke ich, dass Warteschlangen besser sind ... – Elijan9