简体   繁体   English

在等待用户输入时避免无限循环?

[英]Avoid an infinite loop while waiting for user input?

I'm caught in a logical catch-22. 我陷入了逻辑上的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. 让我澄清一下我要做什么:按下按钮将触发电动机运行,直到感觉到传感器为止(向Rpi GPIO发送3.3V电压),这时它将反转方向。 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. 我试图将“ wiringPiISR()”实现为中断,但是这似乎也在循环内做出反应。

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. 再说一遍,我只想以某种方式使该程序不断检查“ 24,INT_EDGE_RISING” ...对于您不熟悉的人来说,意味着有一些电压正在输送至第24 GPIO引脚(从高低压升起) ...而不会因此而完全着迷。 A background loop, or I really don't know, which is why I'm here. 背景循环,或者我真的不知道,这就是为什么我在这里。 Any ideas would be much appreciated! 任何想法将不胜感激!

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, ie your on_checkBox_clicked would just set a flag that is then checked in the main loop. 我不确定我是否完全理解您的问题,但是也许您可以对状态机进行建模,而只有一个循环来检查所有按钮的按下并执行所有必要的操作,即您的on_checkBox_clicked只会设置一个标志,然后在主循环。 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. 这样,任何状态都不会阻止新按钮的按下或其他过渡的到来。

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). 在这种情况下,需要在中断处理程序内部发出Qt信号(显然,您需要将某些东西连接到该Qt信号上)。 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. 不过有一个小问题,对于Qt,您只能从QObject派生类发出非静态函数,而中断处理程序不能属于任何类。 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: 您可以通过使用指向当前MainWindow的指针(在Qt中只能有一个)并向该类添加信号来轻松解决此问题,例如:

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. 注意: WiringPi实际上并不使用中断,而是为每个回调创建一个单独的线程。 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. 使用实际中断时,您可能应该使用QMetaObject::invokeMethod(..., Qt::QueuedConnection)对此进行改进,以使中断处理程序尽快返回并仅立即执行关键操作。 The SensorDetected slots will not be called immediately inside the interrupt handler, but will be called later by the Qt main loop. 不会在中断处理程序内部立即调用SensorDetected插槽,但稍后将由Qt主循环调用。 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) . 如果确实需要在Qt中使用循环,则可以通过调用QCoreApplication::instance()->processEvents(QEventLoop::ExcludeUserInputEvents)定期更新GUI更改。 But you shouldn't need to in this case. 但是在这种情况下,您不需要。

There's no need to do any explicit looping. 无需进行任何显式循环。 The event loop already does it for you. 事件循环已经为您完成了。 You execute actions when certain events happen, eg 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. 这有助于从UI中剔除控制器,并使用UML状态图正式指定其行为。 The code below corresponds 1:1 to the statechart. 下面的代码与状态图1:1相对应。

控制器状态图

The s_moving composite state has no initial state since it's never entered directly, only implicitly when entering its substates. s_moving复合状态没有初始状态,因为它从未直接进入过,只有在进入其子状态时才隐式进入。

// 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. sensorHit() Pi库从高优先级工作线程调用sensorHit()中断处理程序,该工作线程等待内核报告的GPIO转换。 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. 由于sensorHit()已在高优先级线程中运行,并且尽可能接近GPIO转换,因此我们立即设置电动机反向,并发出信号以指示状态机转换为s_reverse状态。 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. 由于此信号是从与Controller实例所在的主线程不同的线程发出的,因此插槽调用将在主线程的事件队列中排队。

   /// 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 : UI与控制器分离:在您使用connect链接它们之前,UI和控制器对象都无法直接相互了解。

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: 为了方便在台式机平台上进行测试,我们可以添加一个简单的WiringPi样机以使其完全独立:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM