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

时间:2016-08-03 10:14:57

标签: c++ qt user-interface infinite-loop

我陷入了一个合乎逻辑的陷阱-22。让我澄清一下我要做的事情:按下按钮将触发电机移动直到感应到传感器(向我的Rpi GPIO发送3.3V电压),此时它将反转方向。这一切都很好;问题是,它被卡在一个无限循环中,所以如果我想按下另一个按钮,例如提高速度,或者在跑步过程中停止它,那么,我不能。       我试图实现" wiringPiISR()",作为一个中断,但这似乎也在一个循环中作出反应。

请记住,以下只是一个测试,可以使某些东西工作,以适应更大的代码。

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

}

所以再一次,我只想要一些方法让这个程序不断检查&#34; 24,INT_EDGE_RISING&#34; ...对于那些你不熟悉的人来说,有一些电压被传送到第24个GPIO引脚(从低 - 高电压上升)......没有被这样做完全迷住。一个背景循环,或者我真的不知道,这就是我在这里的原因。任何想法将不胜感激!

3 个答案:

答案 0 :(得分:1)

我不确定我是否完全理解你的问题,但也许你可以建模一个状态机,只有一个循环检查所有按钮按下并执行所有必要的操作,即你的onCreate只会设置一个然后在主循环中检查标志。这样的东西(用pseude代码):

null

通过这种方式,没有任何状态阻止新按钮按下或其他转换的到来。

答案 1 :(得分:1)

恕我直言,你应该在它可用的时候使用中断,而不是连续轮询任何东西的当前状态。在这种情况下,需要在中断处理程序内部发出Qt信号(显然你需要连接到该Qt信号)。这样构建你不需要循环。

但是有一个小问题,对于Qt,你只能从QObject派生类中发出非静态函数,而中断处理程序不能是任何类的一部分。您可以通过使用指向当前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;
}

由于您只使用信号和插槽而没有阻塞循环,因此您可以轻松处理其他任何操作。

注意: WiringPi实际上并不使用中断,而是为每个回调创建一个单独的线程。使用实际中断时,您可能应该通过使用QMetaObject::invokeMethod(..., Qt::QueuedConnection)来改进,以使中断处理程序尽快返回 ,并且只立即执行关键操作。 SensorDetected插槽不会立即在中断处理程序中调用,但稍后将由Qt主循环调用。例如:

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

如果你真的需要在Qt中使用循环,你可以通过调用QCoreApplication::instance()->processEvents(QEventLoop::ExcludeUserInputEvents)定期更新GUI更改。但在这种情况下你不应该这样做。

答案 2 :(得分:0)

没有必要进行任何明确的循环。事件循环已经为你做了。您可以在某些事件发生时执行操作,例如选中或取消选中按钮时。

它有助于从UI中分解出控制器,并使用UML状态图正式指定其行为。下面的代码对应于状态图1:1。

Statechart of the Controller

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

来自高优先级工作线程的wiringPi库调用sensorHit()中断处理程序,该线程等待内核报告的GPIO转换。为了最大限度地减少电机反转的延迟,我们利用这个线程。由于sensorHit()已经在高优先级线程中运行并且尽可能接近GPIO转换,我们立即设置反向电机方向,并发出信号以指示状态机转换到{{1}国家。由于此信号是从与s_reverse实例所在的主线程不同的线程发出的,因此槽调用在主线程的事件队列中排队。

Controller

UI与控制器分离:在使用 /// 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; 链接它们之前,UI和控制器对象都不会直接相互识别:

connect

为了便于在桌面平台上进行测试,我们可以添加一个简单的WiringPi模型,使其全部自包含:

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"