Microcontroller Units (MCUs) are the heart of countless embedded systems, powering devices from consumer electronics to industrial machinery. With modern applications demanding rich user interfaces, many electronic devices now require graphical UI. QT for MCU application development, using the “QT for MCUs” variant, offers a robust solution for building graphical user interfaces on resource-constrained MCUs. QT for MCU strips away the heavyweight desktop runtime to deliver only what is needed for embedded UI rendering. While QT for MCU firmware development involves navigating hardware constraints and real-time requirements, it provides a powerful framework for creating attractive, responsive UIs. Today, we'll delve into the process of QT for MCU application development, comparing approaches for both bare-metal and Real-Time Operating Systems (RTOS). Embien's expertise in digital transformation services includes QT for MCU application delivery for automotive, industrial, and consumer products.
Understanding QT for MCU Application Architecture
Before diving into QT for MCU application development, it is essential to understand the architecture of QT for MCU. With a development history of over 23 years, QT is a C++ based library for backend integration with QML frontend development. It comes with a rich set of widgets, efficient memory management, modular architecture, and portability — all of which make QT for MCU one of the preferred choices for any GUI product.
While the full QT runtime is suitable for processor-based systems, MCUs are far more limited in terms of memory and features. A heavy runtime cannot be supported, and hence QT launched the QT for MCU variant targeted for smaller MCUs. This variant is optimized for QT for MCU firmware development on microcontrollers with as little as 256 KB of RAM.
The tech stack of QT is captured along with that of QT for MCU below.

QT for MCU does not support multimedia, filesystem, Networking etc. It supports only graphical interface design.
In QT for MCU, all the design and assets are converted to cpp and compiled together to get a single elf file.

Run Time environment: Bare-Metal vs RTOS
QT for MCU can be run on embedded firmware both on bare-metal and RTOS modes. Let us understand the differences the same.
Bare-Metal: In a bare-metal environment, applications run directly on the MCU hardware without an operating system layer. Developers have direct control over hardware peripherals and system resources, allowing for maximum efficiency and minimal overhead. It will be a single threaded application with only one job running at any point in time. Bare-metal development requires careful management of application architecture to maintain the application responses to external world.
RTOS: In contrast, Real-Time Operating Systems (RTOS) provide a layer of abstraction on top of the hardware, enabling multitasking and deterministic behaviour. While there is a direct control over peripherals, RTOSes offer task scheduling, synchronization primitives, and communication mechanisms, making it easier to develop complex applications with strict timing requirements. However, RTOSes introduce overhead, and complexity compared to bare-metal development.
QT for MCU Application Development: Bare-Metal Approach
Bare metal QT for MCU firmware development involves running QT for MCU application logic directly on the MCU hardware without an OS. Understanding how QT for MCU renders content and handles UI updates at runtime is the core of bare metal QT for MCU firmware design.

In QT all the UI layouts are defined in QML language, for a project with multiple layouts and screens there will be multiple QML files. So, in QT project, we need to define one of the QML as an entrypoint for QT application by setting it as root item for QT Application. All the QML files will be converted to cpp and header files during compilation. Include necessary assets and resources in the qmlproject or cmake.
Example (Test.qml)
import QtQuick 2.15
import QtQuick.Controls 2.15
import DataModels
Rectangle{
width: 800
height: 480
visible: true
Image {
id: testImage
visible: true
source: TestModel.image1 => QT Object variables
}
}
QML widget properties(source) are updated using QT Object variables.
Example(TestModel.qml)
pragma Singleton
import QtQuick 2.15
QtObject {
id: TestModel
property string image1: '' => QT Object Variable
}
Main.cpp shall be created and define the entrypoint of qml file and timer to update the QT object variable dynamically.
Example(main.cpp)
#include 'test.h' => default entry QML file header file
#include
#include
#include
int main()
{
Qul::initHardware(); => Initialize hardware
Qul::initPlatform(); => Initialize QT
Qul::Platform::getPlatformInstance();
Qul::Application app;
static test item; => Create an instance of default QML file(test.qml)
Qul::Timer timer; => Timer instance to handle application requirement
timer.onTimeout([]() { timer_call(); }); => Register application handler for the timer
timer.start(1000); => Configure timer for 1ms
app.setRootItem(&item); => Configure QT application with root element as our default QML file.
app.exec(); => QT application idle loop to redraw UI on need basis.
return 0;
}
As an example in the below timer routine, we are updating the display image path dynamically.
Example(timer.cpp)void timer_handler ()
{
::TestModel &model = < Model URI >:: TestModel::instance(); => Fetch instance of a singleton qtobject class
/*Process business logic and update data*/
/*Update QT Object interface variables to QML accordingly*/
if (condition)
Model.image1.setValue(“background.png”);
else
Model.image1.setValue(“”);
}
This is a trivial approach which might be suitable for very small applications. But when we are working for a complex use case like an industrial HMI or automotive instrument cluster, there is a lot of business logic involved more than that can be handled over a simple timer handler. In this case the following approach can be used, where we keep the execution in application control and update the QT screens as and when necessary.
Example(main.cpp)
#include 'test.h'
#include
#include
int main()
{
Qul::initHardware();
Qul::initPlatform();
Qul::Application app;
static test item; => Create an instance of default QML file(test.qml)
app.setRootItem(&item);
while (true) {
uint64_t now = Qul::Platform::getPlatformInstance()->currentTimestamp();
//
uint64_t nextUpdate = app.update();
//Do business logic here
if (event1)
{
Model.image1.setValue(“background.png”);
}
else
{
Model.image1.setValue(“”);
}
}
return 1;
}
QT for MCU Application Development: RTOS Approach
RTOS based QT for MCU firmware separates the QT for MCU application rendering task from the business logic task, enabling true multitasking. RTOS based QT for MCU firmware is the preferred approach for complex applications such as automotive instrument clusters and industrial HMIs. The process is similar to the bare-metal approach but with added RTOS task management considerations.

RTOS Integration: Choose an RTOS that is suitable for the target and configure task scheduling, memory management, and communication mechanisms provided by the RTOS. Integrate application business case excluding UI as one or more task and configure priority of the tasks as required for the use case.
QT Integration: Create a dedicated task for LCD initialization and asset initialization. Post initialization QT EXEC will be called. QT exec is a QT idle loop routine, which repaints the UI based on dirty flag status. Dirty flag get set based on any widget property changes.
Synchronization and Communication: Utilize RTOS primitives for synchronization and communication between tasks, ensuring thread safety and deterministic behavior. QT widget properties update and repaint need to happen in the same thread.
Use case:Let's take a typical example of a firmware where the business logic runs one task and QT on another. Here the scheduler contact-switches both periodically and the business logic determines the value to be shown.
Via RTOS mechanisms these changes are to be indicated to the QT task from where the QT related API's can be called.
Example(main.cpp)
#include “qul_run.h”
Void main(){
Platform_init(); => Initialize hardware
RTOS_init(); => Initialize stack and heap for RTOS
/*Create task for QT*/ => Create QT task and call qul_run()
RTOS_Task_Start (qt_task);
/*Create task for business logic and data processing */
RTOS_Task_Start (app_task);
}
#include
#include
#include
extern RTOSEvent Event1;
int qt_task()
{
Qul::initHardware();
Qul::initPlatform();
Qul::Application app;
static test item; => Create an instance of default QML file(test.qml) app.setRootItem(&item);
while (true) {
uint64_t now = Qul::Platform::getPlatformInstance()->currentTimestamp();
//
uint64_t nextUpdate = app.update();
if (nextUpdate > now) {
::TestModel &model = < Model URI >:: TestModel::instance(); => Fetch instance of a singleton qtobject class
/*Process LIN/CAN data and do business logic*/
/*Update QT Object interface variables to QML accordingly*/
if (Rtos_IsEventSet (&Event1))
Model.image1.setValue(“background.png”); => Draw background image
else
Model.image1.setValue(“”); => Turn off background image
}
}
return 0;
}
#include 'app_task.h'
RTOSEvent Event1;
int app_task()
{
/*Process business logic and derive information*/
if (/*condition*/){
Rtos_SetEvent (&Event1);
}
else {
Rtos_ClearEvent (&Event1);
}
}
An essential aspect to consider in the design is ensuring that any modifications to UI elements via models are executed in a sequential order to prevent potential race conditions in the QT processing pipeline. This sequential execution is critical for maintaining the integrity and consistency of the user interface.
This can be achieved by ensuring the UI elements are updated only in the QT task. By adhering to this, we can mitigate the risk of concurrent access to UI elements, thereby avoiding conflicts that could arise if multiple threads attempt to modify the interface simultaneously. Qt Application Development Services delivering efficient GUI solutions for MCU applications on bare metal and RTOS.
Conclusion
QT for MCU application development provides a powerful framework for GUI applications on MCUs, offering high abstraction and cross-platform compatibility. Whether opting for bare metal QT for MCU firmware or RTOS based QT for MCU firmware, QT for MCU enables developers to create rich and responsive user interfaces for embedded systems. By understanding the differences between the two approaches, teams can choose the best fit for their QT for MCU firmware development project, balancing efficiency, real-time performance, and development complexity. Whether the QT for MCU application is a simple consumer gadget or a complex automotive cluster, QT for MCU empowers developers to unlock the full potential of MCUs across a wide range of applications.
