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