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() override;
    uint64_t update() override;
    uint64_t currentTimestamp() override;
    void exec() override;
    void initializeHardware() override;
    void initializePlatform() override;
    void scheduleEngineUpdate(uint64_t timestamp) override;

    // 2. Log printing
    void consoleWrite(char character) override;

    // 3. Graphics
    FrameBufferingType frameBufferingType(const PlatformInterface::LayerEngine::ItemLayer *) const override;
    FrameStatistics presentFrame(const PlatformInterface::Screen *screen, const PlatformInterface::Rect &rect) override;
    Qul::PlatformInterface::Screen *availableScreens(size_t *screenCount) const override;
    PlatformInterface::DrawingDevice *beginFrame(const PlatformInterface::LayerEngine::ItemLayer *layer,
                                                 const PlatformInterface::Rect &rect,
                                                 int refreshInterval) override;
    void endFrame(const PlatformInterface::LayerEngine::ItemLayer *layer) override;
    void initializeDisplay(const PlatformInterface::Screen *screen) override;
    void waitUntilAsyncReadFinished(const void *begin, const void *end) override;
    void flushCachesForAsyncRead(const void *addr, size_t length) override;

    // 4. Layers
    PlatformInterface::LayerEngine *layerEngine() override;

    // 5. Memory
    PlatformInterface::MemoryAllocator *memoryAllocator(PlatformInterface::MemoryAllocator::AllocationType type) override;
};

Methods and example implementations are explained in different chapters.

  1. Basics - Implementing basic functions (this page)
  2. Log printing - Implementing logging support
  3. Graphics - Getting graphics on the screen
  4. Layers - Implementing hardware layer support
  5. Memory - Memory allocation in Qt Quick Ultralite platform abstraction

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::init8bppRendering(), Qul::PlatformInterface::init16bppRendering(), Qul::PlatformInterface::init24bppRendering(), or Qul::PlatformInterface::init32bppRendering() 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::init32bppRendering();
    ...

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;
}

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.

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 Entry point to Qt Quick Ultralite application.

Available under certain Qt licenses.
Find out more.