Most if not all major devices (including medical devices) throughout the world have some form of a bootscreen. This often flashy but sometimes simple animation performs two purposes. One is simply that it looks good, plus companies can personalize and add their branding to it. But the second reason is arguably more important; it lets the user know that the device is working and currently still in the start up phase.
This blog will get very technical as it describes how to design and create a bootscreen.
In particular, the blog shares tips to address the pitfalls and complications that come with designing a bootscreen for the POSIX-compliant Operating System (OS), QNX. QNX is a micro-kernel OS designed to be run on embedded systems and more specifically, safety critical hardware. To help with the technical details, references to QNX documentation is included throughout for further clarification.
Defining QNX the OS Build File
The first step to designing a boot screen is to set up QNX to ensure the bootscreen will be displayed at the earliest possible time in the boot sequence. To use QNX, a developer will need to define an OS build file that will essentially describe which drivers, applications, and other extraneous files need to be included in the OS image. This OS image will then be flashed to the target system and control which applications and drivers are started on boot. QNX has a graphics system known as the Screen subsystem. It will be used to render the image onto a specific display attached to the hardware. This should be started as soon as possible during the boot sequence. The boot sequence is defined in the build file as a script tag which will look like:
[+script] .script={}
with any defined lines inside of the braces acting like a shell script. This is where the Screen subsystem should be started.
The command to start the Screen subsystem will look like:
screen -c {path_to_config_file}.
More information can be found here. Once the Screen subsystem has been started, the bootscreen binary can subsequently be started.
Working With the Screen System
The next step is to develop the bootscreen itself. QNX has no native way to show an image or animation as part of the boot sequence. This will need to be developed on a by-device basis. Since the Screen API is written in C, the bootscreen should be written in C as well. Additionally, using C will ensure that the bootscreen can be started much faster and thus reducing the time to inform the user of the device’s operation. The bootscreen needs to setup some boilerplate code to communicate with the Screen API. Specifics can be found here but to list them out, the bootscreen will need to create a context object, a render target object (in this case, the render target needed is a window target), and finally a screen buffer object. Technically, since C is not object-oriented, the concept of objects doesn’t exist in the language. But for ease of explanation, the term objects will be used to describe the types of structs used.
Here are clarification and pointers regarding some specific parameters for the objects that were just defined. When creating the screen context object for the bootscreen, the SCREEN_APPLICATION_CONTEXT type is insufficient. Instead, the bootscreen needs the SCREEN_WINDOW_MANAGER_CONTEXT. The reason will be fully explained later, but it essentially has to do with knowing when the bootscreen needs to terminate. More information is here.
Next, when defining the usage property of the render target, in this case the window, this should at least be set to SCREEN_USAGE_WRITE as the bootscreen intends to write to the render buffer but doesn’t necessarily need to read from it. The default is a combination of the write and read flags. More information is here.
Finally, the ideal number of buffers to be used by the render target can be set to one or two depending on the type of bootscreen used. If the device is going to have a static bootscreen, which will constitute a single image, then 1 buffer is all that is needed. However, if it will be showing an animation, two is recommended. Using two in combination with a render target of a window will allow the bootscreen to be double buffered. In other words, while one frame of the animation is being loaded into one buffer the Screen subsystem can be showing the other buffer. Then when the buffer is finished being loaded into, the Screen subsystem will swap the active buffer to the new one.
Working With the Image Library
Now, a second QNX library needs to be used to parse and load specific frames of the bootscreen image or animation. The Screen subsystem does not handle this. Instead, the Image library is used. Depending on the type of image file that will be used for the bootscreen, different codec Shared Object (.so) files will be needed. These would be included in the OS image build file and a list of available ones is here. A sequence of steps is needed to be done in order to render a frame of an animation to an attached screen.
These steps are defined here with a few caveats. One important caveat is the possibility that depending on the hardware being used, the entire set of frames of an animation will not fit into memory. If this is the case, the frames would need to be loaded on the fly and rendered as they are loaded. The second caveat is that both img_load_file() and img_load() (both referenced in the link above) will only load the first frame. For a still image, this is sufficient, but not for an animation. Using these functions in a loop to read in each frame will not work either because there is no way to specify the frame number to retrieve. It will always return the first frame.
Instead, to address both caveats, the code will load and decode the frame, and then in the decode callback write the animation frame into the Screen subsystem buffer. The img_decode_frame() function allows for callbacks to be defined and it is specifically in the frame_f() callback (see here) that the code to load the image into the buffer should be put.
The steps to load the data are as follows: Extract the render target (screen in this case) which is passed in as the data parameter (this will need to be type cast from a uintptr_t to a screen_window_t). Then the buffer’s SCREEN_PROPERTY_POINTER and SCREEN_PROPERTY_STRIDE (see here) should be set to the image’s data and stride respectively (the img_t parameter of the callback). The final step is to use screen_post_window() (due to the render target being a window) to render the newly loaded image to the screen.
Working With the QNX Event System
Finally, because the bootscreen is essentially an infinite loop which may display an animation over and over, the bootscreen needs to know when to terminate. This is where setting the context type to SCREEN_WINDOW_MANAGER_CONTEXT becomes important. QNX has an Event system where events are generated if new windows are created, destroyed, given focus, etc. A full list of events is here. Using the SCREEN_WINDOW_MANAGER_CONTEXT allows the bootscreen to listen for window creation and window focusing events which are generated by other applications.
Two important events for the bootscreen are the SCREEN_EVENT_CREATE and the SCREEN_EVENT_PROPERTY events. The SCREEN_EVENT_CREATE event is used to listen for when the main application (that would show the main user interface of the device) creates the window and therefore when the bootscreen should start its shutdown sequence. Further properties can be queried about this event by the use of the screen_get_event_property_X() set of functions. X is used to denote the type of the queried property. If the main application defines the SCREEN_PROPERTY_GROUP, it can be queried to find out if the specific application fired the event.
The SCREEN_EVENT_PROPERTY event can be used in conjunction with the SCREEN_PROPERTY_FOCUS property which is set when a different window takes focus (read more information here). Using the event system instead of using an animation that is X seconds long (where X is the length of the device’s boot process) means that the animation can be looped if the main application hasn’t been started yet. This allows for much better portability between different hardware since the timings will most likely be different.
On the topic of timing, because the events cannot be listened for continuously (if they were it would lock up the main thread causing the animation to not show subsequent frames), a different tactic needs to be employed. If the bootscreen is a still frame this will not be an issue. However, depending on the length of the animation, these events may need to be listened for approximately every quarter set of frames. That is, every time a quarter of the animation’s frames are loaded, before the next quarter starts being loaded, check for any possible events.
Conclusion
In conclusion, this blog explains why bootscreens are important, provides details on how to setup a medical device bootscreen for QNX, and offers caveats and design suggestions that may be useful. However, every device’s bootscreen has different requirements. As a result, this blog provides suggestions instead of a step-by-step process. However, these suggestions and details should enable developers to set up a medical device QNX bootscreen that meets their specific needs.
Dendy Addison is a Software Engineer at Starfish Medical who helps clients develop secure, efficient, and effective medical device software. He has a passion for making a difference in people’s lives through the software he develops. Dendy enjoys working on all facets of medical devices from firmware to UI.