Embedded systems engineers need to write small, fast code that touches real hardware. Most information available about the Microsoft Windows CE operating system tells you how to leverage this or that high-end graphic or multimedia feature. This information won’t help the software engineer in Milwaukee designing an industrial controller. That engineer wants to know if he can respond to an interrupting peripheral and shoot a control message out his CAN port quick enough to avoid getting somebody hurt. This is not the time to talk about ActiveSync and DRM. For these scenarios, it is necessary to strip a Windows CE system down to its bare knuckles and write an application that touches real hardware, right now.
The first challenge is getting some control over your build. Platform Builder comes with a “New Target Wizard” complete with several design templates engineers can follow to make their lives easier. These templates are great if you don’t mind having the kitchen sink included in your final image. If you really want to get down to brass tacks however, there are only two things that you need to know about: “Tiny Kernel” and “Custom Device.” The Tiny Kernel design template produces the smallest possible Windows CE image. It includes nothing but the kernel by default. No display, no audio, no Web browser, etc. In short, it is perfect for evaluating Windows CE in a resource-constrained environment. You can achieve the same effect by using the Custom Device design template and removing items as you see fit. I just prefer to add things rather than take them away.
Make a Trimmed OS Image
After you select a Tiny Kernel image, you will notice that the wizard stops almost immediately. You will also notice that Platform Builder’s platform window is basically empty. You can actually build a kernel like this, but it won’t do anything. Since we want to actually write and run an application, we need to add a couple of features.
Without a file system (filesys.exe) included in the image, the WinCE kernel will just hang after booting. To prevent this, add the catalog component found under “Core OS->Windows CE devices->File Systems and Data Store->File System – Internal (Choose 1)-> ROM-only File System.” This component will also drag in a couple of dependencies like the “Full C Runtime” and “String Safe Utility Functions.” That is fine as we will actually use those later. To get some output from our test program via calls to “printf” and similar functions, add the “Core OS->Windows CE devices->Applications and Services Development->C Libraries and Runtimes->Standard I/O (STDIO)” component to the build as well. Once these components have been added, the platform window should look similar to Figure 1. Note the items prefixed with “LoCE” are unique to Logic Product Development’s Windows CE BSP and may not appear on your system.
Now that we have added the bare minimum components to our operating system image, let’s adjust the build settings to cook the smallest image possible. Pull up the “Platform->Settings” dialog box and head to the “Locale” tab. According to the Platform Builder Help topic on decreasing run-time image size, not localizing a build yields a smaller “nk.bin.” Before you hit the “OK” button, stop by the “Build Options” tab and uncheck everything except for “Enable Ship Build.” Checking that box will set the “WINCESHIP” environment variable. When “WINCESHIP” is set, the OS does not generate debug messages (Figure 2).
Building this platform will create an operating system image that is only 600 Kbytes. That sounds suspiciously like an embedded operating system to me. Just as important as the size of this image is what it includes. The final “nk.bin” file only contains:
• nk.exe (the kernel)
• ceconfig.h (information about the modules included in this image)
• initobj.dat (initial directories, files, shortcuts, etc.)
• default.fdf (initial system registry)
When configured as above, there can be no “surprise” applications running on the end system consuming resources and potentially crashing the device.
Write an Application
Now that our basic operating system image has been trimmed down nice and tight, let’s write a quick C-language application to crank on some actual hardware. One of the nicest things about Windows CE is the simple device driver model. Essentially, a WinCE device driver is no different than a regular user-space application or library. Drivers do not reside inside the kernel, rather, they are typically packaged inside a dynamically linked library (DLL) and loaded by a process called “device.exe.” Because of this model, WinCE must provide specific functions for mapping a physical address into a process’s virtual address space. There are several ways to do this, but my favorite involves a call to the function “MmMapIoSpace().” This function is part of the CE Device Driver Kit (CEDDK). Since the device manager has not been included in our image, “ceddk.dll” will not actually be built. However, as long as the application can link to ddk_map.lib and ddk_bus.lib, the compiler will be able to find “MmMapIoSpace()” just fine. These libraries can typically be found at:
If your BSP doesn’t build these libraries, you can always use a combination call to “VirtualAlloc()” and “VirtualCopy().” However, “MmMapIoSpace()” is cleaner because it will transparently take care of offsets from page boundaries.
Use Platform Builder’s “New Project Wizard” to get a start on your application by going to “File->New Project or File” and then selecting “WCE Console Application.” Choose to create “An empty project” and you will be able to get rid of some of the MS-vagaries of C-coding. When you are finished with the Wizard, right-click on the new project and choose the “Settings” option. In the “General” tab, changing “Ship Build” to “yes” will result in a smaller application. You also need to define the environment variable “WINCEOEM.” When “WINCEOEM” is set to “1,” Platform Builder allows an application, DLL, or library to link to special system libraries and header files. Because we want to use “MmMapIoSpace(),” we need to set “WINCEOEM” or the compiler won’t be able to resolve that function. Select the “Custom Variables” tab and use the “New…” button to add this variable.
In the “C/C++” tab, changing “WinMainCRTStartup” to “mainACRTStartup” will allow you to code with a standard main function to keep your application portable across tools. By standard, I mean “int main(int argc, char **argv)” as opposed to “WinMain()” and other MS-style functions. In the “Include Directories” entry box, add “$(_WINCEROOT)publiccommonddkinc” so the compiler can find “ceddk.h” and the system headers it includes.
Finally, in the “Link” tab, make sure to include the paths above to ddk_bus.lib and ddk_map.lib. If you followed everything this far, you now have an empty project that you can use to write standard C-code and access hardware.
Code Block 1 shows a small program to access and toggle an LED on a Logic Product Development PXA270 Zoom Development Kit. This Platform Builder project may be downloaded from Logic’s Web site (http://www.logicpd.com). Note the call to “MmMapIoSpace()” before accessing the CPLD’s GPIO register. If the application had just attempted to write to the physical address 0x08000030, the kernel would have killed it for trying to leave its protected, virtual address space. When the application runs, it reports that it has mapped the CPLD GPIO register from physical address 0x08000030 to virtual address 0x60030. After a good look at the Windows CE virtual memory model for a process, this should sound reasonable to you.
Measure the Results
How “real time” is this little application? I hooked up the development kit to a Tektronix TLA5204 Logic Analyzer and captured the waveforms shown in Figure 3 at a 2 nano-second resolution.
To get a further measure of reliability, I set the logic analyzer to trigger on any pulse that was less than or equal to 4.75 ms or greater than 5.25 ms long. With the exception of a few hardware glitches, this trigger never realized. That tells me that the WinCE kernel can reliably wake and run my application every 5 ms as configured. After this test, I changed the call to “Sleep()” to toggle the LED every 2 ms and achieved the same results. As shown in Figure 4, the actual pulse is slightly longer than 2 ms. Again, the application proved to stay well within the plus or minus .25 ms range allowed.
This application is relying on the “Sleep()” system call and the WinCE kernel’s scheduler for its timing control. With a little more effort, we could add “device.exe” into our operating system image and write a small device driver to trigger on an interrupt and remove the polling altogether.
What we have shown is that Windows CE can be stripped down to a very small footprint. Furthermore, we have shown how to take advantage of the Windows CE user-space device driver model to easily write an application that works on actual hardware. Finally, we have demonstrated that the Windows CE kernel will reliably provide real-time response to an application.
Logic Product Development