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 exec() QUL_DECL_OVERRIDE; uint64_t update() QUL_DECL_OVERRIDE; double rand() QUL_DECL_OVERRIDE; void scheduleEngineUpdate(uint64_t timestamp) 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; }
Render loop callback schedule
The render loop and event processing of Qt Quick Ultralite has to be called at given times.
The Qt Quick Ultralite core will call PlatformContext::scheduleEngineUpdate to notify the platform about the next timestamp it requires to be called back to perform tasks.
void ExamplePlatform::scheduleEngineUpdate(uint64_t timestamp) { nextUpdate = timestamp; }
The timestamp argument specifies at which time the render loop needs to run next. You may implement scheduling timeouts using hardware timers or OS-provided timers if your platform provides them. An easy solution is to store this timeout in a variable to be used by the functions below.
Warning: The implementation of PlatformContext::scheduleEngineUpdate can be called from an interrupt and it must be safe for running within an interrupt context.
Render loop update
The Qt Quick Ultralite engine requires to be called a given times.
The PlatformContext::update method takes care of a single core engine update by calling 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; }
This function returns the timestamp when it is expected to be called the next time. A timestamp less than the current timestamp, or even 0, should result in calling it again 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.
This function can be called as often as possible because it checks if the timestamp that was scheduled by PlatformContext::scheduleEngineUpdate has already passed. If you want to handle the scheduled times differently, only the call to PlatformInterface::updateEngine is essential.
Main loop
The PlatformContext::exec contains 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.
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 only be left empty if you are not going to use any demos, examples, tests, call Application::exec() or use the app_common
framework.
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.