C
Implementing basic functions
The concept of platform context
Qt Quick Ultralite core communicates with the platform through Qul::Platform::PlatformContext virtual class. This class contains set of virtual methods which have to be overridden to provide access to the hardware resources. Each platform creates an 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 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 in general. 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 mentioned locations.
Platform context implementation
A platform has to implement Qul::Platform::PlatformContext virtual class by inheriting from it. Minimal implementation requires definition of only pure virtual methods. The other functions return default values and are optional to implement.
struct ExamplePlatform : PlatformContext { // 1. Basics double rand() QUL_DECL_OVERRIDE; uint64_t update() QUL_DECL_OVERRIDE; uint64_t currentTimestamp() QUL_DECL_OVERRIDE; void exec() QUL_DECL_OVERRIDE; void initializeHardware() QUL_DECL_OVERRIDE; void initializePlatform() QUL_DECL_OVERRIDE; void scheduleEngineUpdate(uint64_t timestamp) QUL_DECL_OVERRIDE; // 2. Log printing void consoleWrite(char character) QUL_DECL_OVERRIDE; // 3. Graphics FrameBufferingType frameBufferingType(const PlatformInterface::LayerEngine::ItemLayer *) const QUL_DECL_OVERRIDE; FrameStatistics presentFrame(const PlatformInterface::Screen *screen, const PlatformInterface::Rect &rect) QUL_DECL_OVERRIDE; Qul::PlatformInterface::Screen *availableScreens(size_t *screenCount) const QUL_DECL_OVERRIDE; PlatformInterface::DrawingDevice *beginFrame(const PlatformInterface::LayerEngine::ItemLayer *layer, const PlatformInterface::Rect &rect, int refreshInterval) QUL_DECL_OVERRIDE; void endFrame(const PlatformInterface::LayerEngine::ItemLayer *layer) QUL_DECL_OVERRIDE; void initializeDisplay(const PlatformInterface::Screen *screen) QUL_DECL_OVERRIDE; void waitUntilAsyncReadFinished(const void *begin, const void *end) QUL_DECL_OVERRIDE; void flushCachesForAsyncRead(const void *addr, size_t length) QUL_DECL_OVERRIDE; // 4. Layers PlatformInterface::LayerEngine *layerEngine() QUL_DECL_OVERRIDE; // 5. Memory PlatformInterface::MemoryAllocator *memoryAllocator(PlatformInterface::MemoryAllocator::AllocationType type) QUL_DECL_OVERRIDE; // 6. Performance metrics Platform::PerformanceMetrics *performanceMetrics(void) QUL_DECL_OVERRIDE; };
Methods and example implementations are explained in different chapters.
- Basics - Implementing basic functions (this page)
- Log printing - Implementing logging support
- Graphics - Getting graphics on the screen
- Layers - Implementing hardware layer support
- Memory - Memory allocation in Qt Quick Ultralite platform abstraction
- Performance metrics - Implementing performance metrics platform APIs
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
Initialization of hardware components can be done in the PlatformContext::initializeHardware method.
This may include setting up clocks, pins, peripherals, buses and memories.
Developer may also decide not implement it and perform its own hardware initialization 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
Qt Quick Ultralite platform components initialization is called before any graphics rendering.
Depending on the color-depth of the framebuffer, you should call Qul::PlatformInterface::initializeRgb332CpuFallbackDrawingEngine(), Qul::PlatformInterface::initializeRgb16CpuFallbackDrawingEngine(), Qul::PlatformInterface::initializeArgb4444CpuFallbackDrawingEngine(), Qul::PlatformInterface::initializeRgb888CpuFallbackDrawingEngine(), Qul::PlatformInterface::initializeBgr888CpuFallbackDrawingEngine(), Qul::PlatformInterface::initializeRgb32CpuFallbackDrawingEngine(), or Qul::PlatformInterface::initializeArgb32CpuFallbackDrawingEngine() here. This initializes the CPU-based fallback drawing engine in Qt Quick Ultralite core. It is necessary unless the platform itself provides the entire virtual DrawingEngine API without relying on the default implementations, or explicitly uses the DrawingEngine::fallbackDrawingEngine()
.
void ExamplePlatform::initializePlatform() { Qul::PlatformInterface::initializeRgb32CpuFallbackDrawingEngine(); ...
Preloading Qt Quick Ultralite internal resources
Qt Quick Ultralite internal resources contain the image data for example for the dials and buttons that are linked to the application from Qul::Controls
library.
Note: If the preloading is not used, following part can be left unimplemented.
The resource system of Qt Quick Ultralite expects internal resources marked as preloadable to be preloaded before starting the main loop. (See the Preloading Qt Quick Ultralite internal resources on how to configure the internal resource preloading.) For this purpose the linker script defines symbols containing the source and destination addresses of the preloadable internal resources. The copy can be performed using DMA or as shown in the following example, using memcpy
:
... extern unsigned char __ModuleResourceDataStart; extern unsigned char __ModuleResourceDataCacheStart; extern unsigned char __ModuleResourceDataCacheEnd; memcpy(&__ModuleResourceDataCacheStart, &__ModuleResourceDataStart, &__ModuleResourceDataCacheEnd - &__ModuleResourceDataCacheStart);
... #pragma section = "QulModuleResourceData" #pragma section = "QulModuleResourceData_init" char *__ModuleResourceDataStart = (char *) (__section_begin("QulModuleResourceData_init")); char *__ModuleResourceDataCacheStart = (char *) (__section_begin("QulModuleResourceData")); char *__ModuleResourceDataCacheEnd = (char *) (__section_end("QulModuleResourceData")); memcpy(__ModuleResourceDataCacheStart, __ModuleResourceDataStart, (unsigned) __ModuleResourceDataCacheEnd - (unsigned) __ModuleResourceDataCacheStart);
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); }
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; }
Performance metrics
Refer to Implementing performance metrics platform APIs to create a custom implementation for the performance metrics platform APIs regarding heap, stack, and CPU load.
Define the member function ExamplePlatform::performanceMetrics()
in the PlatformContext
class to provide the ExamplePerformanceMetrics
structure access to the Qt Quick Ultralite Core.
Platform::PerformanceMetrics *ExamplePlatform::performanceMetrics(void) { static ExamplePerformanceMetrics metrics; return &metrics; }
Renderloop callback schedule
The render loop and event processing of Qt Quick Ultralite must be called at certain times. The Qt Quick Ultralite core will notify the platform about the next timestamp it requires to be called back to perform tasks by calling PlatformContext::scheduleEngineUpdate.
void ExamplePlatform::scheduleEngineUpdate(uint64_t timestamp) { nextUpdate = timestamp; }
The timestamp for the next required update is stored in a single variable. You may implement scheduling timeouts using hardware timers or OS-provided timers, in case your platform provides these.
Note: The implementation of PlatformContext::scheduleEngineUpdate can get called from an interrupt and it must be safe for running within an interrupt context.
void ExamplePlatform::scheduleEngineUpdate(uint64_t timestamp) { nextUpdate = timestamp; }
Main loop
Qt Quick Ultralite does not provide a main loop, but requires a custom main loop that calls the core engine at a given time.
The PlatformContext::update method takes care of a single update of the core engine by calling Qul::PlatformInterface::updateEngine to update its timers and showing animations. It uses the timestamp set by the Qt Quick Ultralite core engine, as described in the previous section.
uint64_t ExamplePlatform::update() { const uint64_t timestamp = this->currentTimestamp(); if (timestamp >= nextUpdate) { // Handle deadline or pending events Qul::PlatformInterface::updateEngine(timestamp); } return nextUpdate; }
The PlatformContext::exec contain the exec loop and runs forever, or at least for as long as the application is running, and is responsible for calling PlatformContext::update at appropriate times. When no update calls are required by the core engine, the device may enter a sleep state if possible.
If you plan to drive the main loop from your application instead of the Qt Quick Ultralite platform, you can skip its implementation and instead have a similar code in your application main loop without ever calling PlatformContext::exec.
PlatformContext::update returns the timestamp when it is expected to be called the next time. A timestamp lesser than the current timestamp, or even 0, should result in calling PlatformContext::update as soon as possible. A greater timestamp value than the current timestamp means that the platform implementation should call PlatformContext::update at that given time. Until the scheduled time, the device may yield or enter a sleep mode.
void ExamplePlatform::exec() { while (true) { logFlush(); // Flush partially filled log buffer const uint64_t timestamp = this->update(); if (timestamp > Platform::getPlatformInstance()->currentTimestamp()) { // The device may yield or go into sleep mode } } }
The used function for yielding or sleeping must be able to return in case an event, like a touch event, was delivered to the Qt Quick Ultralite core from an interrupt, or the time for the next scheduled update is reached.
Note: In case a sleep or powersave mode is not implemented, the CPU will always run at full workload.
Note: The function body can be left empty if you are not going to use any demos, examples, tests, calling Application::exec() or app_common
framework.
Additional functions for IAR
In order to make the time infrastructure work, the IAR libraries require additional functions to be implemented. For more information, see the IAR developer guide.
// 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); }
See also Running Qt Quick Ultralite in applications.
Available under certain Qt licenses.
Find out more.