Create the DPP Qt Widgets project
The reference application creates the QP Qt library first. But on my system, one Qt GUI is the only application (I am an embedded SW engineer, not a desktop SW engineer), so I will not bother with a separate library, and just put all code in 1 Qt widgets application, in the qpcpp/example/qt/arm/buildroot folder.
~/work/Dorking/QP/qpcpp/examples/qt$ mkdir -p arm/buildroot
Then in Qt Creator (the previous blog entry discussed how to get and install the Qt Creator FROM qt.io rather than as a Debian package)
- Click "New Project" button, and then choose the "Qt Widgets Application" template.
- Following the reference application example, I create a project called "dpp-gui" in the /mnt/work/Dorking/QP/qpcpp/examples/qt/arm/buildroot folder just created.
- Next, I choose the zedbr2 kit I created in the previous blog entry.
- In a departure from the example, I create my GUI as a QMainWindow (vs. QDialog). Also unlike the example, I WILL use the form. But I will still call the main class "Gui", to follow the example.
Qt Creator can ready build this empty main class, which is always a good first step.
Preprocessor include path and defines in qmake project file
At minimum, the project must include the QP include/, qep/source/, qf/source/, and the QP port folders. Unlike other IDEs, the build variables like include paths are NOT a project property; I write these are directly into the project (.pro) file in a text editor, using a qmake variable, like this:
QP_ROOT = ../../../../..
INCLUDEPATH += $$QP_ROOT/include $$QP_ROOT/qep/source \$$QP_ROOT/qep/source \ $$QP_ROOT/qf/source \$$QP_ROOT/qf/source \ $$QP_ROOT/ports/qt
INCLUDEPATH += $$QP_ROOT/include $$QP_ROOT/qep/source \$$QP_ROOT/qep/source \ $$QP_ROOT/qf/source \$$QP_ROOT/qf/source \ $$QP_ROOT/ports/qt
Qt itself has a state machine infrastructure, which is redundant for a QP state machine application, so I turn off the Qt's state machine feature in the qmake .pro file:
DEFINES += QT_NO_STATEMACHINE
Add sources to the project and tailor to my needs
QP platform independent sources
In Qt Creator, right click on Sources --> Add Existing Directory --> Browse to the qpcpp/qep/source/ folder --> Start Parsing, to expand the folder and unselect the unnecessary files, as shown below (I do not use FSM, only HSM):
I later learned that you can also include the header files, and Qt Creator will correctly pull them into the HEADERS variable, so qep_pkg.h should have been checked in the above screenshot.
I add qpcpp/qf/source folder similarly, without leaving out any files this time.
Note on updating to the QP 5 API
When copying examples written for QP API 4.5 or earlier, the following changes are required:
- Delete the deprecated call to QS_RESET()
- QTimeEvt ctor now takes the owning active object as the 1st argument. In C++, that would show up as the "this" pointer if the timer belongs to an active object. In exchange, the armX method of the QTimerEvt--which should be used instead of postIn() method--now does NOT take an active object.
- Q_NEW now takes ctor arguments, to call the PLACEMENT new operator (i.e. unlike the new does NOT hit the heap) of the type being created. While this is great for a single process usage of the memory pool, the virtual table you get with the new operator is dangerous when the memory pool spans multiple processes (through shared memory)--as will be the case for me. The danger lies in the possibility for different compiler versions laying out the virtual table differently (C++ compilers are notorious for this, even among different versions). I decide to play it safe here, turn off QEvent's CTOR and VIRTUAL features in qep_port.h, as shown below (and pay the price of having to initialize the memory pool objects myself):
// don't define QEvent to avoid conflict with Qt
#define Q_NQEVENT 1
// provide QEvt constructors
#undef Q_EVT_CTOR
// provide QEvt virtual destructor
#undef Q_EVT_VIRTUAL
QP Qt port sources
Because Qt is a multi-platform code, the example QP port to mingw Qt still works for embedded ARM. I just have to include the qpcpp/ports/qt/ folder, like I have done for the qep/ and qf/ folders above. But since the PixelLabel is only necessary for the fly-and-shoot example, I excluded them.
SOURCES += \...
$$QP_ROOT/ports/qt/guiapp.cpp \
$$QP_ROOT/ports/qt/qf_port.cpp
HEADERS += gui.h \
$$QP_ROOT/ports/qt/qep_port.h \
$$QP_ROOT/ports/qt/qf_port.h \
$$QP_ROOT/ports/qt/tickerthread.h \
$$QP_ROOT/ports/qt/aothread.h \
$$QP_ROOT/ports/qt/guiapp.h \
$$QP_ROOT/ports/qt/guiactive.h
$$QP_ROOT/ports/qt/guiapp.cpp \
$$QP_ROOT/ports/qt/qf_port.cpp
HEADERS += gui.h \
$$QP_ROOT/ports/qt/qep_port.h \
$$QP_ROOT/ports/qt/qf_port.h \
$$QP_ROOT/ports/qt/tickerthread.h \
$$QP_ROOT/ports/qt/aothread.h \
$$QP_ROOT/ports/qt/guiapp.h \
$$QP_ROOT/ports/qt/guiactive.h
Unlike the example Qt integration on mingw, setting a stack size to 4 KB is preventing QThread start, so I commented them out and let QThread use the default thread stack size for now.
//thread->setStackSize(stkSize);
Application support files
The final step in mating QP to an application is to specify functions that QP calls for certain events (startup, onClockTick, onAssert, etc) and the application state machine calls (like updating the philosopher stats from the Table state machine). Unlike the port files, which can theoretically be shared between different QP-Qt projects (again, I will only have 1), the application specific files are coupled to the application logic. For the DPP application, dpp.h and the bsp header/source files are such files, so I add them to the first lines of SOURCES and HEADERS in the qmake pro file:
SOURCES += main.cpp gui.cpp bsp.cpp philo.cpp table.cpp \
...
HEADERS += gui.h bsp.h dpp.h \
...
dpp.h contains the application specific event class TableEvt. To turn off the event polymorphism feature, I take in only the signal number in the TableEvt constructor.
When I examine bsp.cpp, I see that the philosopher states (THINKING/HUNGRY/EATING) are displayed with QPixmaps showing 3 different PNG files, and the table state (PAUSED/SERVING) is displayed with a text on a button. The images for the philosopher states are in res folder, pointed to by the gui.qrc (Qt resource) file. So I add this file to the project (Add Existing File). I also copied the entire res/ folder from the mingw example folder, so that when I click on one of the PNG files in the resource, I see the image in the Qt Creator, like this:
In the qmake pro file, the resource shows up like this:
To update the files to the latest QP API, I make the changes discussed above, in "Note on updating to the QP 5 API" section.
SOURCES += main.cpp gui.cpp bsp.cpp philo.cpp table.cpp \
...
HEADERS += gui.h bsp.h dpp.h \
...
dpp.h contains the application specific event class TableEvt. To turn off the event polymorphism feature, I take in only the signal number in the TableEvt constructor.
When I examine bsp.cpp, I see that the philosopher states (THINKING/HUNGRY/EATING) are displayed with QPixmaps showing 3 different PNG files, and the table state (PAUSED/SERVING) is displayed with a text on a button. The images for the philosopher states are in res folder, pointed to by the gui.qrc (Qt resource) file. So I add this file to the project (Add Existing File). I also copied the entire res/ folder from the mingw example folder, so that when I click on one of the PNG files in the resource, I see the image in the Qt Creator, like this:
In the qmake pro file, the resource shows up like this:
RESOURCES += gui.qrc
To update the files to the latest QP API, I make the changes discussed above, in "Note on updating to the QP 5 API" section.
UI
Instead of just blindly copying the QDialog based UI from the example, I went through the trouble of copying the buttons and labels from the example UI to the QMainWindow based UI, all to preserve the possibility of using the top menu and the bottom status bars in the future. In Qt Creator's Designer View, the UI looks like this:
Note that all widgets I copied are in the central widget; that is, the north, south, east, west widget areas do not exist.
I wire the signals emitted from the widgets to the 3 slots defined in gui.cpp constructor:
...
QObject::connect(m_quitButton, SIGNAL(clicked()), this, SLOT(onQuit()));
QObject::connect(m_pauseButton, SIGNAL(pressed()), this, SLOT(onPausePressed()));
QObject::connect(m_pauseButton, SIGNAL(released()), this, SLOT(onPauseReleased()));
QObject::connect(this, SIGNAL(finished(int)), this, SLOT(onQuit()));
} // setupUi
The UI designer just lays out the widgets (and possibly statically connects signals to slots). The code behind the UI is in gui.cpp, which I copied from the example. After this step, my gui.cpp code is the same as the example, except for Gui parent being QMainWindow instead of QDialog.
State machines
The philosopher and the table state machines drive the application logic. The Qt integration example has the 2 state machine implementations generated by the QM state charting tool, but I do NOT want to generate my code, so I copy philo.cpp and table.cpp from another example (examples/arm/vanilla/gnu/dpp-at91sam7s-ek) that does not yet use the new style of coding the state transition. I also added these 2 files to the project. But I later found out that weird crash can occur if I update the GUI in a non-GUI thread. Examples of the crash:
This is why in the Qt integration example, the table active object it the ONLY active object that derives from GuiQActive class, which is supplied in the port.
class Table : public QP::GuiQActive {
QObject::startTimer: Timers cannot be started from another thread
QBasicTimer::stop: Failed. Possibly trying to stop from a different thread
QObject::connect: Cannot queue arguments of type 'QTextBlock'
(Make sure 'QTextBlock' is registered using qRegisterMetaType().)
valgrind --undef-value-errors=no --leak-check=yes dpp-gui > dpp_valgrind.txt 2>&1
I added the Desktop kit to the project, in the Projects toolbar icon, and reproduced the problem even on Ubuntu. More errors:
QApplication: Object event filter cannot be in a different thread.
QWidget::repaint: Recursive repaint detected
This is why in the Qt integration example, the table active object it the ONLY active object that derives from GuiQActive class, which is supplied in the port.
class Table : public QP::GuiQActive {
...
Application main
I copied main.cpp verbatim from the example, which gives the table GuiQActive object NO event queue (because events to the GUI go through the Qt event delivery mechanism). So the following code snippet is correct:
DPP::AO_Table->start((uint_fast8_t)(N_PHILO + 1),
//GuiQActive does not need event queue
//&l_tableQueueSto[0], Q_DIM(l_tableQueueSto),
(QP::QEvt const **)0, (uint32_t)0,
(void *)0, (uint_fast16_t)0);
DPP::AO_Table->start((uint_fast8_t)(N_PHILO + 1),
//GuiQActive does not need event queue
//&l_tableQueueSto[0], Q_DIM(l_tableQueueSto),
(QP::QEvt const **)0, (uint32_t)0,
(void *)0, (uint_fast16_t)0);
Build and debug on the target
- Leveraging the hard work of setting up the cross-compile in the previous blog entry, I build the target ELF file easily by clicking on the build icon (the hammer). The debug target is still only 2.3 MB on the disk.
- Following the workaround for the cross-debug not working, I copy the ELF file to the target's /root folder.
- I start the gdbserver on the copied app, specifying the mouse device (note that this application does NOT use the keyboard, but the keyboard device is event1)
gdbserver localhost:1234 /root/dpp-gui -plugin evdevmouse:/dev/input/event0 - In Qt Creator, attach to the remote gdbserver (menu --> Debug --> Start Debugging --> Attach to Remote Debug Server), specifying the port and the ELF file, as you can see in this example:
I see 5 Homer icons happily taking turns eating, thinking, being hungry!