diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..6e42899 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,22 @@ +cmake_minimum_required(VERSION 3.7.0) + +project(qwt-example-app VERSION 1.0.0 LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) +set(CMAKE_AUTOUIC ON) + +find_package(Qt5 COMPONENTS Widgets REQUIRED) + +add_executable(qwt-example + window.cpp + main.cpp +) + +target_link_libraries(qwt-example Qt5::Widgets) +target_link_libraries(qwt-example qwt-qt5) + +install(TARGETS qwt-example) diff --git a/CppTimer.h b/CppTimer.h new file mode 100644 index 0000000..deaa6bf --- /dev/null +++ b/CppTimer.h @@ -0,0 +1,162 @@ +#ifndef __CPP_TIMER_H_ +#define __CPP_TIMER_H_ + +/** + * GNU GENERAL PUBLIC LICENSE + * Version 3, 29 June 2007 + * + * (C) 2020-2024, Bernd Porr + * + * This is inspired by the timer_create man page. + **/ + +#include +#include +#include +#include +#include +#include + +/** + * Enumeration of CppTimer types + **/ +enum cppTimerType_t +{ + PERIODIC, + ONESHOT +}; + +/** + * Timer class which repeatedly fires. It's wrapper around the + * POSIX per-process timer. + **/ +class CppTimer +{ + +public: + /** + * Starts the timer. The timer fires first after + * the specified time in nanoseconds and then at + * that interval in PERIODIC mode. In ONESHOT mode + * the timer fires once after the specified time in + * nanoseconds. + * @param nanosecs Time in nanoseconds + * @param type Either PERIODIC or ONESHOT + **/ + virtual void startns(long nanosecs, cppTimerType_t type = PERIODIC) { + if (running) return; + fd = timerfd_create(CLOCK_MONOTONIC, 0); + if (fd < 0) + throw("Could not start timer"); + switch (type) + { + case (PERIODIC): + //starts after specified period of nanoseconds + its.it_value.tv_sec = nanosecs / 1000000000; + its.it_value.tv_nsec = nanosecs % 1000000000; + its.it_interval.tv_sec = nanosecs / 1000000000; + its.it_interval.tv_nsec = nanosecs % 1000000000; + break; + case (ONESHOT): + //fires once after specified period of nanoseconds + its.it_value.tv_sec = nanosecs / 1000000000; + its.it_value.tv_nsec = nanosecs % 1000000000; + its.it_interval.tv_sec = 0; + its.it_interval.tv_nsec = 0; + break; + } + if (timerfd_settime(fd, 0, &its, NULL) == -1) + throw("Could not start timer"); + uthread = std::thread(&CppTimer::worker,this); + } + + /** + * Starts the timer. The timer fires first after + * the specified time in milliseconds and then at + * that interval in PERIODIC mode. In ONESHOT mode + * the timer fires once after the specified time in + * milliseconds. + * @param millisecs Time in milliseconds + * @param type Either PERIODIC or ONESHOT + **/ + virtual void startms(long millisecs, cppTimerType_t type = PERIODIC) { + if (running) return; + fd = timerfd_create(CLOCK_MONOTONIC, 0); + if (fd < 0) + throw("Could not start timer"); + switch (type) + { + case (PERIODIC): + //starts after specified period of milliseconds + its.it_value.tv_sec = millisecs / 1000; + its.it_value.tv_nsec = (millisecs % 1000) * 1000000; + its.it_interval.tv_sec = millisecs / 1000; + its.it_interval.tv_nsec = (millisecs % 1000) * 1000000; + break; + case (ONESHOT): + //fires once after specified period of milliseconds + its.it_value.tv_sec = millisecs / 1000; + its.it_value.tv_nsec = (millisecs % 1000) * 1000000; + its.it_interval.tv_sec = 0; + its.it_interval.tv_nsec = 0; + break; + } + if (timerfd_settime(fd, 0, &its, NULL) == -1) + throw("Could not start timer"); + uthread = std::thread(&CppTimer::worker,this); + } + + /** + * Stops the timer by disarming it. It can be re-started + * with start(). + **/ + virtual void stop() { + if (!running) return; + running = false; + uthread.join(); + } + + /** + * Destructor disarms the timer, deletes it and + * disconnect the signal handler. + **/ + virtual ~CppTimer() { + stop(); + } + +protected: + /** + * Abstract function which needs to be implemented by the children. + * This is called every time the timer fires. + **/ + virtual void timerEvent() = 0; + +private: + int fd = 0; + struct itimerspec its; + bool running = false; + std::thread uthread; + void worker() { + running = true; + while (running) { + uint64_t exp; + const long int s = read(fd, &exp, sizeof(uint64_t)); + if (s != sizeof(uint64_t) ) { + running = false; + return; + } + timerEvent(); + } + // disarm + struct itimerspec itsnew; + itsnew.it_value.tv_sec = 0; + itsnew.it_value.tv_nsec = 0; + itsnew.it_interval.tv_sec = 0; + itsnew.it_interval.tv_nsec = 0; + timerfd_settime(fd, 0, &itsnew, &its); + close(fd); + fd = -1; + } +}; + +#endif diff --git a/QwtExample.pro b/QwtExample.pro deleted file mode 100644 index 8302b83..0000000 --- a/QwtExample.pro +++ /dev/null @@ -1,13 +0,0 @@ -# Qt project file - qmake uses his to generate a Makefile - -QT += core gui - -greaterThan(QT_MAJOR_VERSION, 4): QT += widgets - -TARGET = QwtExample - -LIBS += -lqwt -lm - -HEADERS += window.h - -SOURCES += main.cpp window.cpp diff --git a/README.md b/README.md index 8d67282..9df6036 100644 --- a/README.md +++ b/README.md @@ -1,31 +1,27 @@ -QwtExample -=========== +# QwtExample -A simple example program using Qt/Qwt widgets to be used as a base for students doing Raspberry Pi data acquisition. +A simple example program using QT and Qwt widgets. -Qt is a cross platform framework for developing graphical applications, for more information please visit the links below: -* [Qt Homepage](http://qt-project.org/) -* [Qt 4.8 Class List](http://qt-project.org/doc/qt-4.8/classes.html) -* [Wikpedia](http://en.wikipedia.org/wiki/Qt_%28framework%29) -* [Signals and Slots](http://qt-project.org/doc/qt-4.8/signalsandslots.html) +![alt tag](screenshot.png) -Qwt is a technical widget library based on Qt, please see: -* [Qwt Hompage](http://qwt.sourceforge.net/) +## Required packages +Install the QT5 and Qwt development packages: -Making it work --------------- +``` + apt-get install qtdeclarative5-dev-tools qt5-qmake qt5-qmake-bin qtbase5-dev qtbase5-dev-tools + apt-get install libqwt-qt5-dev +``` -To clone the git repository: +## Build it - git clone https://github.com/glasgow-bio/qwt-example - -To build: - - cd qwt-example - qmake +``` + cmake . make +``` -To run (assuming you are logged into the RPi over ssh and no X-server is running): +## Run it - startx ./QwtExample +``` + ./qwt-example +``` diff --git a/fakesensor.h b/fakesensor.h new file mode 100644 index 0000000..db06419 --- /dev/null +++ b/fakesensor.h @@ -0,0 +1,20 @@ +#ifndef __FAKESENSOR_H +#define __FAKESENSOR_H + +#include "CppTimer.h" +#include + +class FakeSensor : public CppTimer { + public: + virtual void fakeSensorHasData(double inVal) = 0; + void timerEvent() { + const double inVal = gain * sin( M_PI * (float)(count++) / 50.0 ); + fakeSensorHasData(inVal); + } + private: + int count = 0; + static constexpr double gain = 7.5; +}; + + +#endif diff --git a/main.cpp b/main.cpp index cd0103a..21fa4e9 100644 --- a/main.cpp +++ b/main.cpp @@ -1,17 +1,15 @@ -#include +#include "window.h" #include +// Main program int main(int argc, char *argv[]) { QApplication app(argc, argv); // create the window Window window; - window.showMaximized(); - - // call the window.timerEvent function every 40 ms - window.startTimer(40); + window.show(); // execute the application return app.exec(); diff --git a/screenshot.png b/screenshot.png new file mode 100644 index 0000000..e2f7b55 Binary files /dev/null and b/screenshot.png differ diff --git a/window.cpp b/window.cpp index 2cfdad7..e8d857d 100644 --- a/window.cpp +++ b/window.cpp @@ -3,68 +3,83 @@ #include // for sine stuff -Window::Window() : plot( QString("Example Plot") ), gain(5), count(0) // <-- 'c++ initialisation list' - google it! -{ - // set up the gain knob - knob.setValue(gain); - - // use the Qt signals/slots framework to update the gain - - // every time the knob is moved, the setGain function will be called - connect( &knob, SIGNAL(valueChanged(double)), SLOT(setGain(double)) ); - - // set up the thermometer - thermo.setFillBrush( QBrush(Qt::red) ); - thermo.setRange(0, 20); - thermo.show(); - - - // set up the initial plot data - for( int index=0; indexsetFillBrush( QBrush(Qt::red) ); + thermo->setScale(0, 10); + thermo->show(); + + + // set up the initial plot data + for( int index=0; indexsetSamples(xData, yData, plotDataSize); + curve->attach(plot); + + plot->setAxisScale(QwtPlot::yLeft,-10,10); + plot->replot(); + plot->show(); + + button = new QPushButton("Reset"); + // see https://doc.qt.io/qt-5/signalsandslots-syntaxes.html + connect(button,&QPushButton::clicked,[this](){reset();}); + + // set up the layout - button above thermometer + vLayout = new QVBoxLayout(); + vLayout->addWidget(button); + vLayout->addWidget(thermo); + + // plot to the left of button and thermometer + hLayout = new QHBoxLayout(); + hLayout->addLayout(vLayout); + hLayout->addWidget(plot); + + setLayout(hLayout); + + // a fake data sample every 10ms + FakeSensor::startms(10); + // Screen refresh every 40ms + startTimer(40); } +Window::~Window() { + FakeSensor::stop(); +} -void Window::timerEvent( QTimerEvent * ) -{ - // generate an sine wave input for example purposes - you must get yours from the A/D! - double inVal = gain * sin( M_PI * count/50.0 ); - ++count; +void Window::reset() { + // set up the initial plot data + for( int index=0; indexgain = gain; + mtx.lock(); + curve->setSamples(xData, yData, plotDataSize); + thermo->setValue( fabs(yData[0]) ); + mtx.unlock(); + plot->replot(); + update(); } diff --git a/window.h b/window.h index e511d40..6a46018 100644 --- a/window.h +++ b/window.h @@ -2,46 +2,53 @@ #define WINDOW_H #include -#include #include #include #include +#include + +#include + +#include "fakesensor.h" // class definition 'Window' -class Window : public QWidget +class Window : public QWidget, FakeSensor { - // must include the Q_OBJECT macro for for the Qt signals/slots framework to work with this class - Q_OBJECT - + // must include the Q_OBJECT macro for for the Qt signals/slots framework to work with this class + Q_OBJECT + public: - Window(); // default constructor - called when a Window is declared without arguments - - void timerEvent( QTimerEvent * ); + Window(); // default constructor - called when a Window is declared without arguments + ~Window(); -public slots: - void setGain(double gain); + void timerEvent( QTimerEvent * ); // internal variables for the window class private: - // graphical elements from the Qwt library - http://qwt.sourceforge.net/annotated.html - QwtKnob knob; - QwtThermo thermo; - QwtPlot plot; - QwtPlotCurve curve; + static constexpr int plotDataSize = 100; + + QPushButton *button; + QwtThermo *thermo; + QwtPlot *plot; + QwtPlotCurve *curve; + + + + // layout elements from Qt itself http://qt-project.org/doc/qt-4.8/classes.html + QVBoxLayout *vLayout; // vertical layout + QHBoxLayout *hLayout; // horizontal layout - // layout elements from Qt itself http://qt-project.org/doc/qt-4.8/classes.html - QVBoxLayout vLayout; // vertical layout - QHBoxLayout hLayout; // horizontal layout + // data arrays for the plot + double xData[plotDataSize]; + double yData[plotDataSize]; - static const int plotDataSize = 100; + long count = 0; - // data arrays for the plot - double xData[plotDataSize]; - double yData[plotDataSize]; + void reset(); + virtual void fakeSensorHasData(double v); - double gain; - int count; + std::mutex mtx; }; #endif // WINDOW_H