Search code examples
c++qtinfinite-loop

Avoid an infinite loop while waiting for user input?


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!


Solution

  • 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.

    Statechart of the Controller

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