User Interface Prototypes -

User Interface Prototypes


A wealth of resources are available to simulate desktop interfaces. Niall shares a low-cost way to leverage them for embedded systems.

Building a prototype of a user interface will help you understand its requirements and usability issues early in the design cycle. This month, we are going to look at an approach to prototyping user interfaces on a PC, long before the target hardware is available.

These prototypes have two main purposes. One is to enable other members of a design team to see how the device works. When an interactive device is designed on paper, it takes a certain amount of imagination to judge whether the interactions described will work in practice. A working prototype is less ambiguous and allows a wider audience to comment on the planned interface. In many cases, experimenting with the prototype will help you decide how many buttons, LEDs, and numeric or text displays the real thing needs.

The second objective is to use the prototype to write the software for the user interface in the absence of working hardware. To meet this objective, the interface presented on the PC display must be controllable from C, C++, or whatever language you use for embedded development. For the remainder of this piece, let's assume C is the language for the final target hardware.

Consider briefly which part of the software we hope to simulate. At the lowest level, the software turns an LED on or off, or maybe outputs a string to a small text display. Being able to write that software on a PC is only a minor advantage since controlling the physical components on a user interface is usually trivial. Toggling an LED might be one line of code, and displaying a text string on an LCD text display might take a function 10 or 20 lines long.

The hard part is writing the software that decides whether the LED should be on or off, and what string to display. When a measured sensor value exceeds an alarm limit for some period of time, and a set of conditions that make the alarm valid are met, we might choose to turn on the LED. When a user presses a button to select the next item on a menu, a table of strings and actions that describes the menu may be consulted to determine which item should next be displayed. The size of the software controlling the menu will exceed the size of the low-level software.

Our goal is to write simulated versions of the text display and LED toggling routines that show the changes on the screen of a PC. This enables us to write the alarm checking and menu control code so that it runs on a PC and the target.

This approach is not novel. Writing software for targets, such as PDAs and game machines, that do not support their own development environment often requires this approach.

The tools

Presenting a few buttons and a two-line text display on a PC is trivial with Visual Basic. Interfacing C code to that prototype, however, gets pretty messy.

There are a number of prototyping tools targeted at the embedded market. I have avoided these since, in my opinion, they encroach too much on the design by forcing you to comply with their event model. These tools can generate code if you conform to their style of interface, but I am not convinced they're flexible enough for every platform-and the code they generate may not suit small microcontrollers.

The tool I use is Borland's C++ Builder (CPB from here on). Though it isn't geared at the embedded market, I find it flexible for my needs, and it doesn't tie me to one particular processor or software architecture.

CPB has a set of predefined graphical components. Most of them are for desktop applications (things like pull-down menus) rather than embedded projects, but a small subset is effective for the purposes described here. UI elements such as LEDs can be simulated with pictures.

CPB comes in three versions: Standard, Professional, and Enterprise. The standard version is sufficient for the interface we're going to look at in this column.

Buttons, sliders, labels, and other UI elements can be inserted into a form (a simple dialog window) using the drag-and-drop environment. Creating a form leads to the generation of the skeleton of a C++ class.

Each element in the form has a set of events that are generated when a user clicks on an image or moves a slider, for example. The programmer selects which events require responses. Those responses are written as member functions of the class that was generated for the form.

If the front panel has been drawn by an industrial design team, a picture of the entire display will be available. If a physical prototype exists, then a digital photograph provides an image that can be used as the background.

Figure 1: A five-button UI simulation

I use the image object (also called Timage within CPB) for most physical components. The image object lets you import a bitmap, which you can then display. For a simple application, I imported an image of a lit LED. The application displays a picture of a crude interface with five buttons and four LEDs, as shown in Figure 1. The background image has the LEDs in an off state. Whenever the software decides that one of the LEDs should be turned on, an image of a lit LED has its Visible property set to true. The image of the lit LED now covers the unlit LED.

This simple trick of overlapping multiple images lets you simulate numerous parts of a physical display. Assume, for example, that we used the CPB IDE to create a label containing the word “ALARM” and we named that element AlarmIndicator . We can now write a function to control it:

void setAlarmState(Boolean state){  PanelForm->AlarmIndicator	->Visible = state;}

PanelForm is the name of the form that contains all of the graphical objects for our simulation. Alarm-Indicator is the name we assigned to the label after we placed one on the form. When it is added to the form-by dragging it on to the form window-it becomes a data member of the form.

In CPB, all of the properties of an element on the display are available as public data members of the class representing that element. This way, the property Visible can be altered with a simple assignment. Public data members can be altered by assignment from any place in the program. Within CPB, properties also have special status, which allows them to be altered from the IDE. The developer can click on a label and set the Visible property in the properties window. Similarly the developer can change the color and font.

We looked at a version of setAlarmState() that drives the CPB-based simulation. This code is CPB specific and it won't run on the final target. Later on, we'll have to write a version of this function for the target that might look something like:

void setAlarmState(Boolean state){  if (state)  {	ledRegister |= 0x02;  }  else  {	ledRegister &= ~0x02;  }}

Sometimes this style of programming leads to a set of small functions that create some function call overhead. On small systems where this is a concern, some of these functions can be written as macros or inline functions. I usually defer those sorts of optimizations until fairly late in the project.

Code organization

If we've got two versions of setAlarm-State(), we must ensure that we only compile one at a time. One option is to use the CPB code until the target hardware is available, and then replace all of the CPB-specific code with target-specific code. If we do this, we won't be able to run the simulation after we move to the target. You might not think this is a problem, but the simulation is useful even when hardware is available.

The simulation's PC-based debugging environment, for example, is often superior to the target's environment. The target may have a slow download time or you may have to blow a one-time-programmable chip for every software change. A debugger that allows single-stepping and breakpoints may not be available. Even if we assume a good debugging environment on the target, the PC simulation has other advantages. You can e-mail an .exe file to colleagues working at other sites to get feedback.

Once you've decided to keep both versions functional through the life of the project, it's easy to separate them. In CPB, under Project/Options, you can define macros, and I always define USING_CPB. Then, in my source code, I can use an #ifdef to distinguish versions. Another approach is to keep the target code and the simulation code in separate files, but share a single header file to ensure that the same set of function signatures is used for both.

The CPB environment is based on C++, but many embedded targets barely support C. It's important in those cases to use only the C++ subset that your cross-compiler supports in the shared code. This is not all that difficult. One way to do it is to compile the code for the embedded target, even if you have no hardware to run it on. This will highlight features used on the PC that may not be legal on the target. Some smaller processors will not allow recursion. Checking the software on the embedded compiler will quickly flag any CPB-specific code that has accidentally been included in the build for the target. I also find this a useful way to keep track of how large the software is, because compiling for the PC does not give realistic results. The CPB libraries are so large that they distort the size completely.

Three types of code are in use here. Some code is CPB specific and only compiles on the PC. Some code is target specific and only compiles on the target. The rest of the code is common, and should run on both platforms. Ideally, each source file should only contain one type of code. Your IDE or makefile should allow you to choose which files you want to include in each build.

I recommend naming any files that are CPB specific as .cpp files. Any files that are target specific or shared are named with a .c extension. For the target environment, I then compile all of the .c files, but not the .cpp files.

If you follow this style, you'll run into one problem in the CPB environment. It assumes that .c files are C code and .cpp files are C++. Calls from one file to the other cause link-time problems related to the way C++ generates mangled function names. You can get around this by using the “extern C” construct. But I find that a bit messy, especially when calls are going from C to C++ and vice versa. I prefer to set a flag for the Borland compiler that indicates all files are to be compiled as C++ irrespective of the filename extension. Unfortunately no flag in the IDE does this. The hack is to edit the project configuration file by hand.

Sample code

At, you'll find an executable called five.exe that contains a row of five buttons and a set of LEDs. Pressing any of the first four buttons turns on the corresponding LED. A RESET button turns off all of the LEDs. Of course, if you were really building such a project, no simulation would be necessary. But the example shows that the simulation can be made to look quite similar to a real device with little effort, once an initial picture is available. It also demonstrates that the module key.c contains code that runs on the target or the simulation, and this module does not require any conditional code to allow for the differences between the two platforms. All of the source code and the initial bitmaps used to build the application are also available for download.

Building simulations like this requires some knowledge of C++, and the CPB environment has a learning curve, especially if you have not used an event-driven, object-oriented environment. The good news is that once you've built one simulation, others follow the same format. Learning CPB also pays off if you ever write a PC-based program that requires a GUI. I've used it in the past for simple download utilities that perform serial communications with the embedded target.

Niall Murphy has been writing software for user interfaces and medical systems for ten years. He is the author of Front Panel: Designing Software for Embedded User Interfaces. Murphy's training and consulting business is based in Galway, Ireland. He can be reached at .

Return to the September 2002 Table of Contents

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.