Avoid an infinite loop while waiting for user input?

爱⌒轻易说出口 提交于 2019-12-11 06:38:06

问题


I'm caught in a logical catch-22. Let me clarify what I am trying to do: A button press will trigger a motor to move until a sensor is felt (sends 3.3V to my Rpi GPIO), at which point it will reverse direction. This all works fine; the problem is, it is stuck within an infinite loop, so if I want to press another button, for example to increase the speed, or stop it in the middle of a run, well, I can't. I have tried to implement "wiringPiISR()", as an interrupt, but that seems to also be within a loop to react.

Keep in mind, the following is just a test to get something to work, to be adapted to a much larger piece of code.

#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++;
    }

}

So again, I just want some way to have this program constantly checking for "24,INT_EDGE_RISING" ...which for those of you unfamiliar means that there is some voltage being delivered to the 24th GPIO Pin (Rising from low-high volt)...without being completely enthralled by doing so. A background loop, or I really don't know, which is why I'm here. Any ideas would be much appreciated!


回答1:


There's no need to do any explicit looping. The event loop already does it for you. You execute actions when certain events happen, e.g. when a button is checked or unchecked.

It'd help to factor out the controller from the UI, and formally specify its behavior using a UML statechart. The code below corresponds 1:1 to the statechart.

The s_moving composite state has no initial state since it's never entered directly, only implicitly when entering its substates.

// 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);
   }

The sensorHit() interrupt handler is called by the wiringPi library from a high-priority worker thread that waits for GPIO transitions as reported by the kernel. To minimize the latency of reversing the motor, we leverage this thread. Since sensorHit() already runs in a high-priority thread and is as close to the GPIO transition as possible, we immediately set the reverse motor direction, and emit a signal to instruct the state machine to transition to the s_reverse state. Since this signal is emitted from a thread different than the one the main thread the Controller instance lives in, the slot call is queued in the main thread's event queue.

   /// 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;

The UI is decoupled from the controller: neither UI nor controller objects are directly aware of each other until you link them using connect:

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"

To facilitate testing on desktop platforms, we can add a trivial WiringPi mockup to make it all self-contained:

// 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;
}



回答2:


I am not sure if I fully understand your problem, but maybe you could model a state machine whith only a single loop that checks all button presses and does all the necessary actions, i.e. your on_checkBox_clicked would just set a flag that is then checked in the main loop. Something like this (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 ... */
}

In this way no state blocks the arrival of new buttons presses or other transitions.




回答3:


IMHO you should always use an interrupt when it's available, rather than continuously poll the current state of anything. In that case, a Qt signal needs to be emitted inside the interrupt handler (and you obviously need to have something connected to that Qt signal). Constructed like that you won't need a loop.

There is a minor catch though, for Qt you can only emit a non-static function from a QObject derived class whereas the interrupt handler cannot be part of any class. You could easily solve this by using a pointer to the current MainWindow (there can only be one in Qt) and add a signal to that class, for example:

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;
}

Since you are only using signals and slots an no blocking loop, you can easily handle anything else.

Note: WiringPi does not actually use interrupts but creates a separate thread for every callback. When using real interrupts you probably should improve on this by using QMetaObject::invokeMethod(..., Qt::QueuedConnection) instead to have the interrupt handler return as soon as possible and only perform critical operations immediately. The SensorDetected slots will not be called immediately inside the interrupt handler, but will be called later by the Qt main loop. For example:

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

In case you really need to use a loop in Qt, you could regularly update the GUI changes by calling QCoreApplication::instance()->processEvents(QEventLoop::ExcludeUserInputEvents). But you shouldn't need to in this case.



来源:https://stackoverflow.com/questions/38740702/avoid-an-infinite-loop-while-waiting-for-user-input

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!