An important part of embedded design is portability. You never know when something changes on the platform you are developing on. The next generation might have a different processor, hardware engineers love to change things, contracts with third party suppliers come to an end or another company might want to license your code. So any software group worth their weight will tend to design modules using standard code design and keep any product specific code abstract from the main bulk of the code. Also any module designed this way can be written and simulated on a PC, which can increase development time. Also with a GUI it means a graphic designer can see results without requiring any hardware other than a PC or Mac.
This is quite easy to do with a GUI. Your first choice is to choose a language which can be compiled for most of the popular processors, my personal choice is C++, which if coded using only the standard libraries can be compiled for almost any platform you care. C# wouldn't give you much scope, but C would. Of course most embedded design tend to be C, C++ or Java. Therefore for the scope of this book, I will be referring to classes & other object oriented jargon.
GUI design tends to be object-oriented by nature. So although some good GUI's have been written in C, I personally think that you can do a better job if using C++ or Java. For instance in C each widget has it's data kept in a structure, which means a different structure has to be created for each widget or you have a base structure with generic pointers which point to more relevant structures. Where-as in C++ you create a base class and derive each widget from that or a child class.The use of polymorphism in a hierarchy of objects creates a more powerful and easily changed software solution.
So the first you need to do is make sure all of the platform specific code is kept separate form the main bulk of code. This can easily be done with just a few files and will be made up of Graphics, User input (IR Control, keyboard etc). I tend to have a header file which holds some definitions to let the rest of the code know what the platform is capable of, such as maximum screen size (TV's tend to have restricted sizes, whereas a PC has many). Also it might typedef some OS functions, such as mutex handling or thread calls.
Second is the basic graphic calls, which I call the 'graphics device' layer. Hardware differs wildly when it comes to displaying the pixels. Some platforms might have an in built blitter and MMU for hardware acceleration, some comes with some ready code for displaying rectangles and handling palettes. Whichever way, you need to define an API of methods which unify any graphic drawing and on-screen display,by creating a template / declaration which is shared by all platforms, but the actual source code will need to be rewritten for each platform. However some of these will only need a shim, which wraps each of it's existing calls with your own methods.
Input just needs some code to translate the platforms key codes into a set of common codes.
So you've got a working GUI but a single platform might make up several products. For instance I worked on a digital TV platform which could be a basic set-top box, a digital recorder with an internal hard-driver or a recorder with USB slots. Each product required different menus and additional screens. Therefore you need be able to include different code without having to change any of the basic GUI code. For instance the recorders needed to be able to play, stop and rewind; this makes up several on-screen icons which called the platforms recording code.
As mentioned previously, different products require different code. Also any product specific code requires an abstract layer between it and the GUI code. This means if you are simulating the product on a PC you can create stubs which don't do anything when the user selects an event, such as record or stop. This keep the effort on the target platform and away from the simulation. It also means the work is easier if the platform changes, although your other modules should also be such that the API stays the same even though the device drivers need to be re-written. Another thing to take into consideration is if a third party uses your GUI, they'll have yet another set of device drivers with different APIs. So you need to judge how much of your product code requires an abstraction layer.
One thing that crops up in many forms is time/date structures. One product I worked on had three different time/date structures along with an API for each, one was based on the broadcast information, one was our recording schedule module and the third was for the GUI. These required functions for conversion as each module dealt with the others.
When designing your architect, try and keep your GUI in modules (libraries, packages etc). This means if a third party is interested in licensing your GUI it'll be easy to release as libraries, enabling you to keep your source to yourself should you wish and makes updates easier. There is nothing worse than a source code update requiring you to merge in all source files even though only one part of the GUI has changed. I tend to break down the GUI into the following sections:
This groups together the look and feel of your final product. It is really important to keep this as a separate set of data from the main code. The best way is to use data files, such as XML, to describe this information.
Resources make up the position and size of screens, which widgets they use, bitmaps and icons, colours and text (including translations). Again done correctly these can be shared across products and platforms.
The Graphics Context
When you start to put the graphics device class together, you will find a lot of code can be used by any platform, such as remembering the size of graphics and fonts. This code becomes another layer on top of the graphics devices class and is called the 'graphics context'. It should also handle the colours. Regardless of the resolution of your platform it is possible to work with a 32 bit RGBA palette which gets translated on the graphics device layer.
The framework holds everything together. It's main running task keeps track of which menu or screen is being displayed, when and which part of the graphics require an update and passes on any events posted.
Simple screens like menus can be designed purely using resource data and share common code, however more complicated screens tend to be product specific and will require derived classes and a separate set of code. I tend to create a module which is product specific but will run on any platform.
The base / simple screens should be kept with the main GUI module.
Like the screens, your widgets will be made up of basic standard class,which will sit in the GUI modules. And also have further product specific widgets. Widgets that describe coloured squares or areas of text will get used by all products, whereas more complicated stuff, like a 'record' icon, will only get used by a product that records and should sit in a different module.
The Event Driver
A tidy GUI will use events to control it. Rather than have an API to update or change screens, it is much better to have the code post an event. This also allows anyone developing with your GUI to add their own events which are more specific to the product they are creating.
It's very important that the framework controls when the screen should update rather than the user. I remember working on one GUI with other coders and some screens called the update over 10 times during a redraw of widgets, rather than just once at the end of the draw. Event posting tends to stop shoddy program control and stops the need for application coders to be involved in mutexes which tend to create confusion for some developers.