C
Implementing basic functions
The concept of platform context
Qt Quick Ultralite core communicates with the platform through the Qul::Platform::PlatformContext virtual class. This class contains a set of virtual methods which have to be implemented to provide access to the hardware resources. Each platform creates its own implementation of the platform context class by inheriting from Qul::Platform::PlatformContext.
The platform-specific implementation of Qul::Platform::PlatformContext is retrieved by Qt Quick Ultralite core through the Qul::Platform::getPlatformInstance function.
Implementing basic functions
This chapter lists the basic functions and class methods that must be implemented to run Qt Quick Ultralite core. You can take the contents of platform/boards/qt/example-baremetal/platform_context.cpp as a basis for your own implementation and add your code at the specified locations.
Platform context implementation
A platform has to implement the Qul::Platform::PlatformContext virtual class by inheriting from it.
struct ExamplePlatform : PlatformContext { void initializeHardware() QUL_DECL_OVERRIDE; void initializePlatform() QUL_DECL_OVERRIDE; void process() QUL_DECL_OVERRIDE; double rand() QUL_DECL_OVERRIDE; void resume() QUL_DECL_OVERRIDE; void waitUntil(uint64_t deadlineTimestampMs) QUL_DECL_OVERRIDE; uint64_t currentTimestamp() QUL_DECL_OVERRIDE; void consoleWrite(char character) QUL_DECL_OVERRIDE;
Note: These methods and their example implementations are explained in different chapters of the Platform porting guide.
An instance of the platform-specific context class has to be returned by Qul::Platform::getPlatformInstance.
PlatformContext *getPlatformInstance() { static ExamplePlatform platform; return &platform; }
Qt Quick Ultralite core uses the returned instance and calls context methods to communicate with the hardware and platform during runtime.
Initializing hardware
The initialization of the hardware components can be done in the PlatformContext::initializeHardware method.
This may include setting up clocks, pins, peripherals, buses and memories. You may also decide not to implement it and perform your own hardware initialization instead, for example in the application main() function.
void ExamplePlatform::initializeHardware() { #if 0 Replace the following code with actual code for your device. setup_system_clocks(); setup_flash_memory(); setup_sd_ram(); setup_usart(); setup_rnd_generator(); #endif } ...
Initializing platform
PlatformContext::intializePlatform is called before the Qt Quick Ultralite engine is ran and has to setup some resources that are specific for your board.
This is mostly used for graphics resources, so it can be kept empty for now.
void PlatformContext::intializePlatform() { Qul::PlatformInterface::log("Called platform initialization\n"); }
Current timestamp
An essential part for rendering and timers is getting the current timestamp of the system. The method PlatformContext::currentTimestamp has to return this system time.
uint64_t ExamplePlatform::currentTimestamp() { // Replace this line to make it return the system timestamp in milliseconds. return 0; }
Recurring platform work
PlatformContext::process gives the platform a place to perform operations on each invocation, such as polling a touch controller. This function is called by Application::update.
void ExamplePlatform::process() { // Read touch data, poll other peripherals that need regular attention, etc. logFlush(); // Flush partially filled log buffer }
Waiting for the next update
PlatformContext::waitUntil suspends until the deadline returned by Application::update passes, or until PlatformContext::resume is called, whichever happens first. By default, applications that drive their loop through Application::update and Application::waitUntil rely on this function to wait/sleep between updates.
void ExamplePlatform::waitUntil(uint64_t deadlineTimestampMs) { if (deadlineTimestampMs <= this->currentTimestamp()) return; // deadlineTimestampMs already passed - return immediately // Replace this loop with your platform's real wait primitive (WFI, xSemaphoreTake(), // k_sem_take(), etc.), checking m_pendingResume so an early resume() isn't missed. while (!m_pendingResume && this->currentTimestamp() < deadlineTimestampMs) { // The device may yield or go into sleep mode } m_pendingResume = false; }
If the deadline has already passed, this function must return immediately. A deadline of std::numeric_limits<uint64_t>::max() means there are no pending timers or animations, and the platform should wait indefinitely until resumed; guard against integer overflow when computing the remaining time from this value.
Note: Implementing PlatformContext::waitUntil is optional if the application has its own waiting or sleeping logic.
Resuming a wait
PlatformContext::resume signals the platform to stop waiting in PlatformContext::waitUntil. If resume() is called before waitUntil(), for example from an interrupt, the following waitUntil() call must still return immediately.
void ExamplePlatform::resume() { // Sticky: seen by waitUntil() even if resume() is called before it, e.g. from an // interrupt that fires while Qul::Application::update() is still running. This // function must be safe to call from an interrupt. m_pendingResume = true; }
Warning: The implementation of PlatformContext::resume can be called from an interrupt and it must be safe for running within an interrupt context.
Note: If custom wait/sleep logic is used, PlatformContext::resume must be able to resume the application from its waiting state.
Reference implementations for waiting and resuming
void BaremetalPlatform::waitUntil(uint64_t deadlineTimestampMs) { const uint64_t now = currentTimestamp(); if (deadlineTimestampMs <= now) return; // deadlineTimestampMs already passed - return immediately if (deadlineTimestampMs != std::numeric_limits<uint64_t>::max()) { const uint64_t delta = deadlineTimestampMs - now; // fire an interrupt after delta ms (or maximum supported hardware timer duration if delta ms exceeds that) programHardwareTimer(delta); } __disable_irq(); if (!m_pendingResume) __WFI(); m_pendingResume = false; __enable_irq(); } // Called from ISR or event sources; sets a flag so WFI in waitUntil() returns immediately. void BaremetalPlatform::resume() { m_pendingResume = true; // seen by waitUntil() even if called before it }
For the AUTOSAR, FreeRTOS, Linux platforms see Qt Quick Ultralite as an AUTOSAR Complex Device Driver, Using Qt Quick Ultralite with FreeRTOS and Using Qt Quick Ultralite on Linux targets for the reference implementations.
Main loop
The main loop is part of the application. A typical implementation looks like this:
Qul::initHardware(); // Optional Qul::initPlatform(); Qul::Application app; static struct Main item; app.setRootItem(&item); while (true) { const uint64_t nextUpdate = app.update(); app.waitUntil(nextUpdate); }
Each iteration calls Application::update, which calls PlatformContext::process and then updates the Qt Quick Ultralite core engine, returning the timestamp of its next expected call. The timestamp is passed to Application::waitUntil, which suspends until it passes or until PlatformContext::resume is called, whichever happens first. A timestamp less than or equal to the current timestamp means that Application::waitUntil must return immediately.
Note: In case a sleep or powersave mode is not implemented to Application::waitUntil, the CPU will always run at full workload.
Note: The application's main loop may also choose not to call Application::waitUntil and implement its own waiting or sleeping logic instead.
Random numbers
To provide random numbers to QML applications, the platform requires PlatformContext::rand to be overriden.
double ExamplePlatform::rand() { // Replace std::rand() by the proper call to the random number generator on your device, if available. const uint32_t number = std::rand(); return number / (std::numeric_limits<uint32_t>::max() + 1.0); }
Basic memory allocation
The Qt Quick Ultralite core library handles small memory allocations using the following functions:
You can forward these calls to the platform-specific functions as shown below:
void *qul_malloc(std::size_t size) { return std::malloc(size); } void qul_free(void *ptr) { std::free(ptr); } void *qul_realloc(void *ptr, size_t s) { return std::realloc(ptr, s); }
All graphics-related memory allocations will be addressed later in this guide.
Additional functions for IAR compilers
To make the time infrastructure work, the IAR libraries require additional functions to be implemented. These require to have C-linkage either by putting them in a .c file or inside an extern "C" section. For more information, see the "IAR C/C++ Developer Guide" from IAR.
// The number of times an internal timing event occurs per second int const CLOCKS_PER_SECOND = 1000; clock_t clock(void) { QUL_UNUSED(CLOCKS_PER_SECOND); // This function should return a value, which after division by CLOCKS_PER_SECOND, // is the processor time in seconds. return (clock_t) HAL_GetTick(); } // The time_t type is defined in bxarm/arm/inc/c/time{32,64}.h #if _DLIB_TIME_USES_64 time_t __time64(time_t *t) #else time_t __time32(time_t *t) #endif { uint64_t timeAtStartup = 0; // Read this from a time source like a real time clock; uint64_t currentTimestamp = timestamp(); // same timestamp as _gettimeofday time_t curtime = (time_t) (timeAtStartup + (currentTimestamp / 1000)); if (t) *t = curtime; return curtime; } char const *__getzone() { // See <IAR>/src/lib/time/getzone.c for documentation // For Germany as a default timezone return ":GMT+1:GMT+2:0100:032502+0:102502+0"; } __ATTRIBUTES char *_DstMalloc(size_t); __ATTRIBUTES void _DstFree(char *); char *_DstMalloc(size_t s) { // Return a buffer that can hold the maximum number of DST entries of // of any timezone available on the device. // Each DST entry takes up a structure of 5 bytes plus regular alignment. // Instead of a static buffer a dynamically allocated memory can be used as well. // With the two entries shown above the required buffer size would be // 2 * (5 bytes size + 3 bytes alignment) = 16 bytes static char buffert[8 * 4]; return buffert; } void _DstFree(char *p) { // Nothing required here because of static buffer in _DstMalloc QUL_UNUSED(p); }
Available under certain Qt licenses.
Find out more.