Blog

FlexCAN usage example

Since we included our iMX FlexCAN driver in the iMX25 Topaz image binaries we have received a couple of requests for sample code, so here it is:

#include "DebugZones.h"
#include "FlexCANSDK.h"

#define CAN_BITRATE         (125*1000)  // 125 kHz
#define CAN_READ_INTERVAL   1000        // Set to INFINITE to wait forever for a CAN Frame
#define CAN_WRITE_INTERVAL   500

CRITICAL_SECTION gCS;
LPCWSTR DataToHexString(const PCAN_FRAME pCANFrame)
{
    static WCHAR szHexString[100];
    WCHAR szHexByte[7];
    EnterCriticalSection(&gCS);
    {
        szHexString[0] = 0;
        if (pCANFrame->btDataLen)
        {
            wsprintf(szHexByte, L"0x%02X", pCANFrame->btData[0]);
            wcscat_s(szHexString, _countof(szHexString), szHexByte);
            for (int i=1; i<pCANFrame->btDataLen; i++)
            {
                wsprintf(szHexByte, L", 0x%02X", pCANFrame->btData[i]);
                wcscat_s(szHexString, _countof(szHexString), szHexByte);
            }
        }
    } LeaveCriticalSection(&gCS);
    return szHexString;
}

DWORD WINAPI CANReadThread(LPVOID lpParameter)
{
    HANDLE hFinishEvent = (HANDLE)lpParameter;
    CAN_FRAME canFrame = {0};
    canFrame.bExtended = CAN_EXTENDED;  // Receive any extended frame
    LOGMSG(ZONE_INFO, L"CAN Read Thread initialized; waiting for %s CAN frames...\r\n", (CAN_EXTENDED == canFrame.bExtended) ? L"Extended" : L"Standard");
    do
    {
        if (ERROR_SUCCESS == FlexCANReadFrame(1, &canFrame, CAN_READ_INTERVAL, 0))
        {   // Convert data to string, copy the string and show received frame
            LPWSTR pszData = wcsdup(DataToHexString(&canFrame));
            if (canFrame.bExtended == CAN_EXTENDED)
                LOGMSG(ZONE_INFO, L"Read CAN frame with ID 0x%08X, Data: %s\r\n", canFrame.ui32ID, pszData);
            else
                LOGMSG(ZONE_INFO, L"Read CAN frame with ID 0x%03X, Data: %s\r\n", canFrame.ui32ID, pszData);
            // Clean up
            free(pszData);
        }
    } while (WAIT_TIMEOUT == WaitForSingleObject(hFinishEvent, 0));

    return 0;
}

int _tmain(int argc, _TCHAR* argv[])
{
    // Register with debug subsystem
    RETAILREGISTERZONES(NULL);

    InitializeCriticalSection(&gCS);

    HANDLE hFinishEvent = NULL;
    HANDLE hReadThread = NULL;
    for (;;)
    {   // Use loop for easy break-out error handling
        if (!FlexCANInit(1))
            break;

        DWORD dwBitrate = CAN_BITRATE;
        if (!FlexCANSetBitrate(1, &dwBitrate))
            break;
        LOGMSG(ZONE_INFO, L"Bitrate set to %d (requested %d)\r\n", dwBitrate, CAN_BITRATE);

        hFinishEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
        if (!hFinishEvent)
            break;

        // Create the read thread that will receive all CAN Frames
        hReadThread = CreateThread(NULL, 0, CANReadThread, (LPVOID)hFinishEvent, 0, NULL);
        if (!hReadThread)
            break;

        // Some time to see CAN Frames coming in
        Sleep(10000);

        CAN_FRAME canFrame = {0};
        for (int i=0; i<10; i++)
        {   // Send a couple of frames at highest priority (0)
            canFrame.bExtended = CAN_EXTENDED;
            canFrame.btData[0] = 0x10;
            canFrame.btData[1] = 0x11;
            canFrame.btData[2] = 0x12;
            canFrame.btDataLen = 3;
            canFrame.ui32ID = 0x12345678;
            DWORD dwRet = FlexCANWriteFrame(1, CAN_DATA, CAN_TXNOW, 0, &canFrame, 500);
            LOGMSG(ZONE_INFO, L"Frame with ID 0x%08X and data %s sent. Result: %d\r\n", canFrame.ui32ID, DataToHexString(&canFrame), dwRet);
            Sleep(CAN_WRITE_INTERVAL);
        }

        // Signal read thread to finish
        SetEvent(hFinishEvent);
        if (WAIT_OBJECT_0 != WaitForSingleObject(hReadThread, CAN_READ_INTERVAL*2))
            TerminateThread(hReadThread, 0);

        // And wait for a frame with ID 0x12345678 for 10 seconds (no other CAN frames are accepted during this time)
        canFrame.bExtended = CAN_EXTENDED;
        canFrame.ui32ID = 0x12345678;
        LOGMSG(ZONE_INFO, L"Waiting for extended frame with ID 0x%08X...\r\n", canFrame.ui32ID);
        DWORD dwRet = FlexCANReadFrame(1, &canFrame, 10000, 0x1FFFFFFF);
        LOGMSG(ZONE_INFO, L"Specific FlexCANReadFrame result: %d\r\n", dwRet);
        if (ERROR_SUCCESS == dwRet)
            LOGMSG(ZONE_INFO, L"Read CAN frame with ID 0x%08X, Data: %s\r\n", canFrame.ui32ID, DataToHexString(&canFrame));

        // Now read any frame (ui32IDMask set to 0)
        LOGMSG(ZONE_INFO, L"Waiting for extended frame with any ID...\r\n");
        dwRet = FlexCANReadFrame(1, &canFrame, 10000, 0);
        LOGMSG(ZONE_INFO, L"Unspecific FlexCANReadFrame result: %d\r\n", dwRet);
        if (ERROR_SUCCESS == dwRet)
        {   // Show received frame
            if (canFrame.bExtended == CAN_EXTENDED)
                LOGMSG(ZONE_INFO, L"Read CAN frame with ID 0x%08X, Data: %s\r\n", canFrame.ui32ID, DataToHexString(&canFrame));
            else
                LOGMSG(ZONE_INFO, L"Read CAN frame with ID 0x%03X, Data: %s\r\n", canFrame.ui32ID, DataToHexString(&canFrame));
        }

        // Now transfer a whole bunch of messages in one go
        CAN_TRANSFER_BLOCK canBlock;
        canBlock.dwNumPackets = 32;
        canBlock.pCANPackets = new CAN_PACKET[canBlock.dwNumPackets];
        if (canBlock.pCANPackets)
        {
            for (DWORD i=0; i<canBlock.dwNumPackets; i++)
            {   // Init packets; data from max to 0
                canBlock.pCANPackets[i].bRemote = CAN_DATA;             // Data frame
                canBlock.pCANPackets[i].btIndex = 0xFF;                 // Let driver find a free message buffer
                canBlock.pCANPackets[i].btPriority = 0;                 // Highest priority
                canBlock.pCANPackets[i].bTxOnRemoteRx = CAN_TXNOW;      // Transfer immediately
                canBlock.pCANPackets[i].bWrite = CAN_WRITE;             // We want to write this frame
                canBlock.pCANPackets[i].canFrame.bExtended = CAN_STANDARD;  // Standard address (11 bit)
                canBlock.pCANPackets[i].canFrame.btData[0] = (BYTE)(0xFF - (i*(256/canBlock.dwNumPackets)));
                canBlock.pCANPackets[i].canFrame.btDataLen = 1;         // 1 byte data
                canBlock.pCANPackets[i].canFrame.ui32ID = 0x123;        // Address
                canBlock.pCANPackets[i].dwTimeout = 100;                // Write timeout
            }
            BOOL bRet = FlexCANTransfer(1, &canBlock);
            LOGMSG(ZONE_INFO, L"FlexCANTransfer result: %s\r\n", bRet ? L"TRUE" : L"FALSE");
            for (DWORD i=0; i<canBlock.dwNumPackets; i++)
                LOGMSG(ZONE_INFO, L"Individual transmit %d transmit result: %d\r\n", i, canBlock.pCANPackets[i].dwError);
        }

        // Now write a frame after receiving a remote request with the same ID
        canFrame.bExtended = CAN_EXTENDED;
        canFrame.btData[0] = 0x12;
        canFrame.btData[1] = 0x11;
        canFrame.btData[2] = 0x10;
        canFrame.btDataLen = 3;
        canFrame.ui32ID = 0x7654321;
        LOGMSG(ZONE_INFO, L"Waiting for remote request with ID 0x%08X...\r\n", canFrame.ui32ID);
        dwRet = FlexCANWriteFrame(1, CAN_DATA, CAN_TXONREMOTERX, 0, &canFrame, 10000);
        LOGMSG(ZONE_INFO, L"Frame with ID 0x%08X and data %s sent. Result: %d\r\n", canFrame.ui32ID, DataToHexString(&canFrame), dwRet);

        // Always exit out of loop
        break;
    }

    //Clean up
    FlexCANDeinit(1);

    if (hFinishEvent)
        CloseHandle(hFinishEvent);
    if (hReadThread)
        CloseHandle(hReadThread);

    DeleteCriticalSection(&gCS);

    return 0;
}

The code above is using our standard FlexCAN driver functionality and uses our (free downloadable) debug message templates for debug logging. Our High Performance FlexCAN driver supports complex ID filtering setups and a high speed (virtually unlimited) receive FIFO, among many more features.

Load, unload or reload a driver at runtime

If you develop drivers for Windows CE or Embedded Compact you know it is a major PITA to have to reload the entire kernel every time you change something in the driver code. You need to do this even in a debug kernel because the driver binaries are in use (and thus you can't re-compile & link) when the driver is loaded.

To workaround this I created a tool that allows you to unload or (re)load a driver at runtime. The way I use it is as follows:

  1. I build a small debug kernel including the driver under development
  2. I add the driver dll to the list of modules that will be loaded from the Release Directory (in VS2K5 with PB go to menu "Target"->"Release Directory Modules..." and add the driver)
  3. I add my tool (see below) as a subproject to the OS Design

Now when I download the kernel the driver will load (if the correct Drivers\BuiltIn registry values are there of course). I can set breakpoints in and step through my driver code, etc. Now when I find a bug I just press F5 to continue execution, open a "Target Control" window (menu Target->Target Control) and use my tool to unload the driver, then fix the bug, recompile & link and then (re)load the driver without the need for rebuilding or even reloading the kernel:

reloaddrv [-u] drv1: [[-u] drv2: {etc}]

In Target Control you use the 's' command to start an executable: s reloaddrv -u drv1:

The drv1: is the name of your driver (a 3 letter prefix followed by an index and a colon). Make sure to specify the -u (unload) parameter before the driver name!

To just reload the driver (so a quick unload followed immediately by a load; great for debugging initialization and clean-up) you use the tool as follows: s reloaddrv drv1:

If the driver was not loaded when you executed the above command the tool will try to find the BuiltIn registry key for the driver and load it from there.

You can specify multiple drivers on the commandline as well (-u always applies to the driver directly following it): s reloaddrv -u drv1: -u drv2: drv3: -u drv4: drv5: drv6:

I'm sure this tool is useful to a lot of you developing drivers for Windows CE/Embedded Compact, so here it is:

int WINAPI WinMain(HINSTANCE hInstance,
                   HINSTANCE hPrevInstance,
                   LPWSTR    lpCmdLine,
                   int       nCmdShow)
{
    BOOL bLoad = TRUE;
    LPWSTR pszToken = wcstok(lpCmdLine, L" ");
    while (pszToken)
    {   // Process parameters
        if (wcsicmp(pszToken, L"-u")==0)
        {   // Reset load flag if only unload requested
            bLoad = FALSE;
            pszToken = wcstok(NULL, L" ");
            continue;
        }

        // See if the driver is loaded
        DEVMGR_DEVICE_INFORMATION devInfo = {0};
        LPWSTR pszDeviceKey = NULL;
        devInfo.dwSize = sizeof(devInfo);
        HANDLE hFind = FindFirstDevice(DeviceSearchByLegacyName, pszToken, &devInfo);
        if (INVALID_HANDLE_VALUE != hFind)
        {   // Clean up find handle
            FindClose(hFind);
            // Store driver registry key
            pszDeviceKey = wcsdup(devInfo.szDeviceKey);
            // Unload driver
            RETAILMSG(1, (L"Found driver \"%s\", unloading...", devInfo.szLegacyName));
            DeactivateDevice(devInfo.hDevice);
        }
        else
            RETAILMSG(1, (L"Driver \"%s\" not loaded.", pszToken));

        if (bLoad)
        {   // Load driver if requested
            if (NULL == pszDeviceKey)
            {   // Try to find driver key in registry
                WCHAR szPrefix[4] = {0};
                DWORD dwIndex = 0;
                wcsncpy(szPrefix, pszToken, 3);
                dwIndex = _wtol(&pszToken[3]);
                HKEY hKey;
                BOOL bFound = FALSE;
                if (ERROR_SUCCESS == RegOpenKeyEx(HKEY_LOCAL_MACHINE, L"Drivers\\BuiltIn", 0, NULL, &hKey))
                {   // Opened BuiltIn drivers key
                    DWORD i = 0;
                    WCHAR szDriverKey[MAX_PATH];
                    DWORD dwCount = _countof(szDriverKey);
                    while (ERROR_NO_MORE_ITEMS != RegEnumKeyEx(hKey, i++, szDriverKey, &dwCount, NULL, NULL, NULL, NULL))
                    {   // Enumerate all driver keys
                        HKEY hDriverKey;
                        if (ERROR_SUCCESS == RegOpenKeyEx(hKey, szDriverKey, 0, NULL, &hDriverKey))
                        {   // Check if the prefix matches the specified one
                            WCHAR szDriverPrefix[MAX_PATH] = {0};
                            dwCount = sizeof(szDriverPrefix);
                            RegQueryValueEx(hDriverKey, L"Prefix", NULL, NULL, (LPBYTE)szDriverPrefix, &dwCount);
                            if (0 == wcsicmp(szPrefix, szDriverPrefix))
                            {   // And check if the index matches the specified one
                                DWORD dwDriverIndex = 0;
                                dwCount = sizeof(dwDriverIndex);
                                RegQueryValueEx(hDriverKey, L"Index", NULL, NULL, (LPBYTE)&dwDriverIndex, &dwCount);
                                if (dwDriverIndex == dwIndex)
                                {   // We found our driver key!
                                    bFound = TRUE;
                                    RegCloseKey(hDriverKey);
                                    break;
                                }
                            }
                            RegCloseKey(hDriverKey);
                        }
                        dwCount = _countof(szDriverKey);
                    }
                    if (bFound)
                    {   // Copy driver key name into pszDeviceKey
                        pszDeviceKey = (LPWSTR)malloc(MAX_PATH * sizeof(WCHAR));
                        if (pszDeviceKey)
                            wsprintf(pszDeviceKey, L"Drivers\\BuiltIn\\%s", szDriverKey);
                    }
                    else
                        RETAILMSG(1, (L"Can't find driver key for \"%s\"...", pszToken));
                    RegCloseKey(hKey);
                }
            }
            if (pszDeviceKey)
            {   // Reload driver
                RETAILMSG(1, (L"Reloading from \"%s\"...", pszDeviceKey));
                ActivateDeviceEx(pszDeviceKey, NULL, 0, NULL);
            }
        }
        // Clean up and get ready for next parameter
        if (pszDeviceKey)
            free(pszDeviceKey);
        bLoad = TRUE;
        pszToken = wcstok(NULL, L" ");
    }
    return 0;
}

And for your convenience here's the tool 7-zipped up as an OS Design subproject: reloaddrv.7z

Customizing a UI Component

In this blog post I will show you how you can customize all Windows CE dialogs without the need to change ANY file in the PUBLIC tree. As you no doubt know changing any code in the Windows CE PUBLIC (& PRIVATE) tree is a bad idea. It is a bad idea because it is very hard to maintain, your changes may be overwritten at any time by a Windows CE Update and worst of all your changes affect ALL OS Designs (not just the OS Design you need the changes for). Besides that you often need to do a "Build & Sysgen" to get your changes into your kernel image and we all know you should NEVER EVER do a "Build & Sysgen".

However, MSDN is still full of articles with instructions telling you to change code in the PUBLIC tree. An example of this is the "Customizing a UI Component" article in MSDN. It talks about modifying files in %_WINCEROOT%\Public\Common\Oak\Drivers and changing \WINCE600\PUBLIC\CEBASE\OAK\MISC\Winceos.bat... Bad, bad, bad!

Time for an update on this article:

Customizing a UI Component

To suit the requirements of your target device, Windows CE allows you to customize and replace certain user interface (UI) components. Windows CE provides these UI components in the form of libraries that you can either use as supplied or replace with your own custom UI components. The following table shows these UI components.

Component Description
Oomui Controls the appearance and behavior of your target device in out of memory conditions.
Startui Controls the appearance and behavior of system startup windows.
Calibrui Controls the appearance and behavior of your target device during calibration of the touch screen.

For detailed instructions on how to clone Calibrui please see Cloning CalibrUi in Windows CE 6.0. This article deals with STARTUI and OOMUI

To customize a UI component, we first need to clone the component into our OS Design and change it so that the build system uses our cloned binaries when linking GWES. We'll take STARTUI as an example for this exercise:

  1. Copy \WINCE600\PUBLIC\COMMON\OAK\DRIVERS\STARTUI to \WINCE600\OSDesigns\YourOSDesign\YourOSDesign\STARTUI
  2. Open the 'sources.' file in \WINCE600\OSDesigns\YourOSDesign\YourOSDesign\STARTUI and add the following to the top of the file (right under the !endif):
  3. _OEMINCPATH=$(_WINCEROOT)\public\common\ddk\inc;$(_WINCEROOT)\public\common\oak\inc;$(_WINCEROOT)\public\common\sdk\inc;
    __PROJROOT=$(_PROJECTROOT)
    _COMMONPUBROOT=$(_WINCEROOT)\public\common

    WINCEOEM=1
    PBP_PRESYSGEN=1

  4. Append '_clone' to the TARGETNAME:
  5. TARGETNAME=startui_clone

  6. Add RELEASETYPE and TARGET_PDB_NAME under TARGETTYPE=LIBRARY:
  7. RELEASETYPE=OAK
    TARGET_PDB_NAME=$(_RELEASELIBDIR)\$(TARGETNAME).pdb
  8. Remove the INCLUDES. In this case nothing is included from ..\..\inc but if it would be we would have to include the correct absolute path to the required include files.
  9. Remove any #xref lines (not used anymore since CE 2.12)
  10. The final sources file should look something like this:

    _OEMINCPATH=$(_WINCEROOT)\public\common\ddk\inc;$(_WINCEROOT)\public\common\oak\inc;$(_WINCEROOT)\public\common\sdk\inc;
    __PROJROOT=$(_PROJECTROOT)
    _COMMONPUBROOT=$(_WINCEROOT)\public\common

    WINCEOEM=1
    PBP_PRESYSGEN=1

    TARGETNAME=startui_clone
    TARGETTYPE=LIBRARY
    RELEASETYPE=OAK
    TARGET_PDB_NAME=$(_RELEASELIBDIR)\$(TARGETNAME).pdb

    MODULES=gwes
    CDEFINES= $(CDEFINES) -D__USERDECL_H__ -D__PRIVDECL_H__ -DGWE

    !IFDEF MEM_ACCOUNT
    CDEFINES=$(CDEFINES) -DMEM_ACCOUNT=1
    !ENDIF

    WINCETARGETFILES= \
      $(_COMMONOAKROOT)\lib\$(_CPUINDPATH)\$(TARGETNAME).res \
     
    SOURCES=startui.cpp                

  11. Now add this project to your OS Design's subprojects
  12. by right clicking the "SubProjects" node in the Solution Explorer and choosing "Add existing subproject...". Then browse to the STARTUI folder in your OS Design folder and be sure to select "Sources/Dirs Files (sources;dirs)" in the "Files of type" drop down list to be able to select the STARTUI sources. file.

    This action creates several files and adds several lines to our sources file that we do not need...

  13. From the Solution Explorer select all the files in the "Parameter Files" node (ProjSysgen.bat, STARTUI.bib, .dat, .db & .reg) of the STARTUI subproject, right click and "Remove and delete" them.
  14. If you want you can add "startui.rc" and "windowsce.bmp" to the "Resource Files" node in the subproject (but it is not necessary for building).
  15. Now we need to tell Platform Builder that this component needs to built before we sysgen the common tree so GWES can link our binaries instead of the default ones. We do this by editing the STARTUI.pbpxml file. Make sure the STARTUI.pbpxml file is as follows:
    <?xml version="1.0"?>
    <PBProject DisplayName="STARTUI (clone)" SysgenDeptree="common" SysgenModule="gwe2" xmlns="urn:PBProject-schema" />

    I added "(clone)" to the display name just to make clear this is a cloned component; it is not necessary for building. SysgenDeptree tells Platfom Builder this needs to be built before sysgenning the common dependency tree and SysgenModule specifies the module this component needs to be linked with.

  16. Since we named our project STARTUI_clone we need to rename the startui.rc file to startui_clone.rc (in preparation for the step below).
  17. The final step involves building the resources for all possible locales. The script used was taken from the Calibrui clone operation described in Cloning CalibrUi in Windows CE 6.0. The only thing added is setting the INCLUDE environment variable so that windows.h can be found by the resource compiler (startui_clone.rc needs windows.h). Copy the following into the prelink.bat file:
    @echo off
    @REM This is called before build to create all the RES files needed for all locales
    @REM WARNING: Do not change the names of any files because SYSGENMAKE.EXE assumes their location for cloning
    (IF NOT EXIST obj mkdir obj || goto EXIT_ERROR
    (IF NOT EXIST obj\%_TGTCPU% mkdir obj\%_TGTCPU%) || goto EXIT_ERROR
    (IF NOT EXIST obj\%_TGTCPU%\%WINCEDEBUG% mkdir obj\%_TGTCPU%\%WINCEDEBUG%) || goto EXIT_ERROR

    dir /AD /B %_PUBLICROOT%\COMMON\OAK\LIB\%_TGTCPU%\%WINCEDEBUG% > obj\%_TGTCPU%\%WINCEDEBUG%\clone_locales.txt

    set INCLUDE=%_WINCEROOT%\public\common\sdk\inc;
    set _PRELINK_LOCALE=%LOCALE%
    for /f %%L in (obj\%_TGTCPU%\%WINCEDEBUG%\clone_locales.txt) do call :COMPILE_RC %%L
    set LOCALE=%_PRELINK_LOCALE%
    goto :END_CLONING

    :COMPILE_RC
    (set LOCALE=%1 && nmake startui_clone.res) || goto EXIT_ERROR
    (IF NOT EXIST obj\%_TGTCPU%\%WINCEDEBUG%\%1 mkdir obj\%_TGTCPU%\%WINCEDEBUG%\%1) || goto EXIT_ERROR
    move /Y startui_clone.res obj\%_TGTCPU%\%WINCEDEBUG%\%1\startui_clone.res || goto EXIT_ERROR
    goto EOF

    :EXIT_ERROR
    exit /b -1

    :END_CLONING
    @REM Place any additional steps after END_CLONING but before EOF
    :EOF

That's it! You can now modify the STARTUI sources and build (sysgen) your OS Design to see your changes included in GWES, all without modifying one single file in the PUBLIC tree. The same instructions can be used to clone and modify OOMUI (the "out of memory" dialogs).

Good luck!

dwNKWatchDogThreadPriority

Windows CE supports a hardware watchdog implementation. The watchdog's properties can be set through two exported kernel variables; dwOEMWatchDogPeriod (to set the period in ms between feeding the dog) and dwNKWatchDogThreadPriority (to set the priority of the watchdog thread).

Unfortunately, dwNKWatchDogThreadPriority is not defined... Looks like somebody at Microsoft made a typo; the name of the exported variable is dwdwNKWatchDogThreadPriority (note the double 'dw').

Here's some example code to setup the watchdog:

extern DWORD dwOEMWatchDogPeriod;
extern DWORD dwdwNKWatchDogThreadPriority;

dwOEMWatchDogPeriod = 5000;
dwdwNKWatchDogThreadPriority = 100;
pfnOEMRefreshWatchDog = RefreshWatchdogTimer;

void RefreshWatchdogTimer(void)
{
    static BOOL bFirstTime = TRUE;
    OALMSG(OAL_FUNC, (L"+RefreshWatchdogTimer\r\n"));
    if (bFirstTime)
    {
        OALMSG(OAL_FUNC, (L"+RefreshWatchdogTimer: First call; init the Watchdog to timeout reset in %d secs\r\n", WD_RESET_PERIOD/1000));
        WatchdogInit(10000);
        bFirstTime = FALSE;
    }
    else
    {
        OALMSG(OAL_FUNC, (L"+RefreshWatchdogTimer: Subsequent calls; refresh the Watchdog timeout to %d secs again\r\n", WD_RESET_PERIOD/1000));
        WatchdogFeed();
    }
    OALMSG(OAL_FUNC, (L"-RefreshWatchdogTimer\r\n"));
}

Of course WatchdogInit and WatchdogFeed are functions implemented to initialize and feed your hardware watchdog.

Windows Embedded QFE's (updates)

Since the change to Silverlight/Bing on Microsoft downloads, the download search has become completely useless (for fun, try searching for "CE 6.0", then try "CE") [Update: MS seems to have fixed the search phrase problems, so "CE" and "CE 6.0" now return the correct results. Nevertheless; the remainder of this blog post is still valid]. Therefore the links in this post do not work anymore and an update is needed.

All Windows Embedded Compact 7 / Windows Embedded CE 6.0 / Windows Embedded CE 5.0 / Windows CE.NET 4.2 / Windows CE 3.0 (you can leave it to MS marketing to make product naming as confusing as possible...) updates can be found at these links:

Windows Embedded Compact 7
Windows Embedded CE 6.0
Windows Embedded CE 5.0
Windows CE .NET 4.2
Windows CE 3.0

You can still download the QFEInstaller so you don't have to click the QFE installation wizard a million times, but nowadays the msi installers support silent installation through commandline options as well. The QFEInstaller is still handy because it makes sure the QFE's are installed in the right order.

Windows Embedded Compact 7 BSP

Today GuruCE released the first version of the Topaz iMX25 Windows Embedded Compact 7 BSP and image binaries and a new release of the CE 6.0 R3 BSP and image binaries.

Release notes and the latest revision can be downloaded here.

We've also released a new version of the Topaz Flasher.

Speeding up the Windows Embedded Compact 7 build process

When you modify something small in your BSP's code and parameter files (like platform.reg) and want to build it, the right way is to right click your BSP and select "Build and Sysgen" (note that this is NOT the demonic Build and Sysgen variant; this merely results in a cebuild -qbsp).

(for a more in depth discussion of what to build when click here)

The downside of this is that if you happened to have just changed your build configuration from RELEASE to DEBUG (or vice-versa), the build system will rebuild all of the SOC folders in the platform common folder.

The default installation of Windows Embedded Compact 7 comes with 7 SOC folders and rebuilding all of these is completely useless and a waste of time (since your BSP only uses 1 SOC folder, if any at all!).

Luckily, we can let the build system know we only want to build the SOC folders we really need by utilizing "OPTIONAL_DIRS".

Here's how:

  1. Change "DIRS=*" in \WINCE700\PLATFORM\common\src\SOC\dirs. to "OPTIONAL_DIRS=*"
  2. Add "set BUILD_OPTIONS=socdir1 socdir2" to your BSP batch file in \WINCE700\PLATFORM\YourBSP\YourBSP.bat (and set the socdir's to the correct foldernames you want to build separated by a space if you want to build more than 1, eg. "set BUILD_OPTIONS=x86_ms_v1")

That's it! A very quick and easy way to speed up your targeted builds.

PS. This also works for CE 6.0 and WEC2013!

How to remove the demonic "Build and Sysgen" commands from Platform Builder for Windows Embedded Compact 7

By now, all of you reading this blog should know that you should NEVER EVER DO A BUILD AND SYSGEN (a "blddemo" without "-q") on your build tree.

The reason for this is that this command will try to rebuild the entire tree which (besides taking forever!) will overwrite binaries that may have been updated through QFE's and will result in errors because Microsoft does not supply all of the source code required to rebuild everything. Executing this command will lead to a corrupted build tree for which the only resolution is to do a complete re-install of Windows Embedded Compact 7. The "(Re)Build and sysgen" commands are only useful for people who have full source code: The Microsoft Windows Embedded Compact Development Team (even with premium shared source you don't get a full 100% of source code!).

This has always been an issue, ever since the invention of Windows CE (now known as Windows Embedded Compact).

Despite years and years of lobbying by many MVPs (including myself) with Microsoft they simply refuse to remove these demonic commands from the build menu. In previous versions of Windows CE it was very simple to remove these commands from the build menu (to prevent accidental clicking of these commands). Unfortunately, things have become worse in Platform Builder for Windows Embedded Compact 7. As in the previous version of Windows CE (6.0 R3) the "(Re)Build and Sysgen" commands are in the "Advanced Build" menu. In the previous version of CE you would simply "Customize" the toolbar and remove the "Build and Sysgen" commands from the "Advanced Build" menu. Not anymore... For some strange reason the "Advanced Build Commands" submenu now shows "(Advanced command placeholder)" when in "Customize-mode" and deleting individual commands is no longer possible:

So, what to do? Deleting the whole "Advanced Build Commands" menu seems a bit rigorous, especially since it does contain some very useful commands (like "Clean sysgen" and "Build Current BSP and Subprojects" etc.). All of these commands are also available by right clicking the correct nodes in the Solution Explorer, but still, it's nice to have them handy in the "Advanced Build Commands" menu as well.

Luckily, Platform Builder for Windows Embedded Compact 7 introduces a new feature; Custom Build Commands.

Using Custom Build Commands we can re-create the non-demonic commands from the "Advance Build Commands" menu and omit the demonic commands. That way we can safely delete the entire "Advanced Build Commands" menu, so let's start with that:

  1. Click "Customize..." in the "Tools" menu
  2. Click the "Build" menu
  3. Right click the "Advanced Build Commands" submenu
  4. Choose "Delete" from the context menu
  5. and finally close the "Customize" dialog

We can now add the useful commands back as custom build commands:

  1. Click "Options..." from the "Tools" menu
  2. Open the node "Platform Builder -> OS Design and Build -> Build Commands"
  3. Click on the "Add" button in the "Custom build commands" section
  4. Add the following commands:
  • Sysgen (blddemo -q)    blddemo -q
  • Clean Sysgen (blddemo clean -q)    blddemo clean -q
  • Build Current BSP and Subprojects (blddemo -qbsp)    blddemo -qbsp
  • Rebuild Current BSP and Subprojects (blddemo -c -qbsp)    blddemo -c -qbsp

Now click OK and see the magic in your "Build" menu:

Hosting webservices on Windows Embedded Compact (Windows CE) using gSOAP

In this blog post I'd like to discuss how to host webservices on a Windows Embedded Compact device. You can certainly consume webservices on a smart device using managed code, but hosting a webservice on the device is unfortunately not possible using managed code. So it's back to good old native code again!

I will use gSOAP, a third party framework, that will do most of the nitty gritty work for us. Sounds easy right? Let's get started!

The most important part is the WSDL (Web Services Description Language) file. The WSDL file is an XML document that describes the interface and thus which methods we want to expose and host on our device. I won't spend much time explaining the WSDL syntax as there are lots of online WSDL tutorials available that explain the syntax much better than I ever could.

For now I just created a very simple WSDL file that describes one very simple webservice method: string HelloWorld(string name)

The wsdl:

<?xml version="1.0" encoding="utf-8"?>
<definitions name="HelloService"
   targetNamespace="http://www.YourServer.com/wsdl/HelloService.wsdl"
   xmlns="http://schemas.xmlsoap.org/wsdl/"
   xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
   xmlns:tns="http://www.YourServer.com/wsdl/HelloService.wsdl"
   xmlns:xsd="http://www.w3.org/2001/XMLSchema">

  <message name="HelloWorldRequest">
    <part name="name" type="xsd:string"/>
  </message>
  <message name="HelloWorldResponse">
    <part name="answer" type="xsd:string"/>
  </message>

  <portType name="HelloWorld_PortType">
    <operation name="HelloWorldOperation">
      <input message="tns:HelloWorldRequest"/>
      <output message="tns:HelloWorldResponse"/>
    </operation>
  </portType>

  <binding name="HelloWorld_Binding" type="tns:HelloWorld_PortType">
    <soap:binding style="rpc"
       transport="http://schemas.xmlsoap.org/soap/http"/>
    <operation name="HelloWorldOperation">
      <soap:operation soapAction="HelloWorldAction"/>
      <input>
        <soap:body
           encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
           namespace="urn:examples:helloservice"
           use="encoded"/>
      </input>
      <output>
        <soap:body
           encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
           namespace="urn:examples:helloservice"
           use="encoded"/>
      </output>
    </operation>
  </binding>

  <service name="HelloWorld_Service">
    <documentation>WSDL File for HelloService</documentation>
    <port binding="tns:HelloWorld_Binding" name="HelloWorld_Port">
      <soap:address
         location="http://Topaz:8080"/>
    </port>
  </service>
</definitions>

As you can see there are 6 main tags in the WSDL within the mandatory <definition></definition> tags:

types
Provides data type(s) used for the method parameters and return values.
message
Represents an abstract definition of the data being transmitted. A message consists of logical parts, each of which is associated with a definition within some type system.
portType
A set of abstract operations. Each operation refers to an input message and output messages.
binding
Specifies concrete protocol and data format specifications for the operations and messages defined by a particular portType.
port
Specifies an address for a binding, thus defining a single communication endpoint.
service
Used to aggregate a set of related ports and to set the network location of the actual service.

Basically Type, Message and portType describe the webservice methods used and binding, port and service describe how to transfer the data over the socket.

The location contains the IP address or hostname of the target that runs the server hosting the webservice. In this example I use a Topaz device (you would normally set this to the IP address or hostname assigned to your device).

After this very brief WSDL description let's set up our Visual Studio 2008 project. This will be the biggest task.

Before we can create the actual VS2008 project we need to perform a couple of steps (described in more detail below):

  1. Import the WSDL in an emtpy project
  2. Let gSOAP generate a header file via wsdl2header.exe
  3. Let gSOAP generate the corresponding implementation *.cpp file
  4. Implement our methods

Because gSOAP generates a lot files I prefer to separate the generated files from the actual implementation files so that you only have to focus on your implementation and not what is being generated by gSOAP. So let’s begin and create an empty project in Visual Studio 2008 and add the WSDL file.

Step 1: Import the WSDL in an empty project

Open Visual Studio and create a new C++ smart device project (File menu | New | Project) and enter HelloWorldWebService as the name of the solution.

Select Ok, and click next on the "Welcome to the Win32 Smart Device Project Wizard" dialog. In the "platforms" dialog select the SDK for your target device. As you can see I chose the Topaz device (http://guruce.com/topaz). Select next:

In the "project setttings" dialog select "console application" and "empty project" and click finish.

When the solution is created, go to the solution explorer (normally this would be visible by default but if not: go to View | Solution Explorer) and add a new filter "gsoap" in the "header" section (right click "Header Files", choose Add->New Filter) and do the same in the "source" section. We'll get gSOAP to generate its files there. Now create a new file with extension .wsdl in your solution's folder. Copy the contents of the sample WSDL above and add the WSDL file to your solution (right click on the solution and select “add existing item”). I've named the file HelloWsdl.wsdl. It should look something like this:

Step 2: Let gSOAP generate the header file from our wsdl.

First download gSOAP in order to get the tools needed. You can download gSOAP from this location:
http://sourceforge.net/projects/gsoap2/files/
Make sure you save the extracted files in an appropriate folder for you to remember because we need references to this folder within our visual studio project later on.

Now go back to your solution and right click on the wsdl file and select properties. In the “Custom build step“ add the following command (note that I’ve put the gSOAP tools in the $(SolutionDir); make sure you get the path right and that the solution directory doesn't contain any spaces) in the “Command Line” section:
$(SolutionDir)\gsoap-2.8\gsoap\bin\win32\wsdl2h.exe -s $(InputPath) -o $(ProjectDir)$(InputName).h
This will run the wsdl2h.exe to generate our header file. The parameters specify the input and output file. In the “Outputs” field on the property page enter:
$(InputName).h

.

You need to specify something in the “outputs” field in order to make the wsld ‘compilable’. Note that the path here contains a version number (gsoap-2.8). This can change of course so keep that in mind. Also notice that I did this "setting" for "All Configurations" and not just for the current one.

Click “Apply” and “Ok”, and then right click the wsdl file again and choose “Compile”.
Now our header (HelloWsdl.h) file is generated in the project directory and we need to add this file to the project. In the solution explorer right click on the “gsoap” folder in the header section and choose “add existing item”. Navigate to HelloWsdl.h and add it.

Let’s do the same for generating the cpp file:

Step 2: Let gSOAP generate the cpp source file.

In the solution explorer right click on the HelloWsdl.h, which we just added in the previous step, and select “properties”. In the “Custom build step“ add the following command:
$(SolutionDir)\gsoap-2.8\gsoap\bin\win32\soapcpp2.exe -S $(InputPath)

In the “Outputs” field enter the following:
$(InputName).cpp

.

Right click on the HelloWsdl.h file and choose "compile". This will generate a bunch of files as well, but we are not yet ready to build our solution... If you would try to build the solution at this time, like I did when I first started with gSOAP, you will run into a lot of compile errors so bear with me for a few more steps.

Right click the gSOAP folder in the header section in your solution and add these header files:
- soapH.h
- soapStub.h

Right click the gSOAP folder in the source section in your solution and add these cpp files:
- soapC.cpp
- soapServer.cpp

The next step is to add stdsoap2.h and stdsoap2.cpp to your solution. You can find these 2 files in “gsoap-2.8\gsoap”. Add them together by right clicking the project in the solution explorer and select “Add existing item”. They will automatically appear under the correct “header” and “source” sections.

We’ve added the stdsoap2.h to our solution but we also need to add the directory where stdsoap2.h resides to the project's "include directory list". Add “$(SolutionDir)\gsoap-2.8\gsoap” to the include directorie list by right clicking the project in the solution explorer and click "Properties". In the “Configuration Properties" | C/C++ | General” section you will find “Additional Include Directories”.

Now that we’re done generating files we can actually start to code!

Step 4: Implement our methods

First we need to create the cpp source code containing the main definition and our methods. I will use 2 separate files for this. The first file will contain the server code and will listen to incoming requests/messages. The second file will actually implement our webservice's methods.

Right click on the “Source Files” in the Solution Explorer and select “New Item”. Choose C/C++ file and name the file HelloWsdl.cpp. Do exactly the same for a file called HelloWsdlMethods.cpp.

Your complete solution should now look like this:

.

Let’s start with an easy one; open the HelloWsdlMethods.cpp and copy and paste the following code snippet into that file:

#include "soapH.h"

int ns1__HelloWorldOperation(struct soap*,
                             char*  name,       /// Request parameter
                             char*  &answer     /// Response parameter
)
{
    printf("Hello my method\r\n");

    char* myName = {"Erwin"};
    answer = myName;
    return SOAP_OK;
}

Now if you have built the project prior to adding this piece of incredible intelligent code, you would have seen an unresolved external in the error list: this method. The above function is generated (or better; declared) by gSOAP and the code snippet above is the implementation. You can find the declaration of this method in the generated soapStub.h file:

/************************************************************************\
* Server-Side Operations                                                    
\************************************************************************/

SOAP_FMAC5 int SOAP_FMAC6 ns1__HelloWorld(struct soap*, char *name, char *&answer);

This is where you will find your method's declarations when you have added your own in the WSDL.

We’re almost there! The last thing we need to do is add our server code. Code that will wait for a request from any client. Below is the code needed. It may look complicated at first but don’t let it scare you. This code is taken from the gSOAP website (section 7.2.3 How to Create a Stand-Alone Server in the documentation section: link listed below) with some minor changes that I will describe below:

/** Include the namespaces struct */
#include "HelloWorld_USCOREBinding.nsmap"

int _tmain(int argc, char* argv[])
{
   struct soap soap;
   int m, s; // master and slave sockets
   soap_init(&soap);
   soap_set_namespaces(&soap, namespaces);      //** Set the namespaces **/
   m = soap_bind(&soap, "", 8080, 100);         //** leave the string empty and gSOAP will figure out what our "localhost" is **/
   if (m < 0)
      soap_print_fault(&soap, stderr);
   else
   {
      fprintf(stderr, "Socket connection successful: master socket = %d\n", m);
      for (int i = 1; ; i++)
      {
         s = soap_accept(&soap);
         if (s < 0)
          {
            soap_print_fault(&soap, stderr);
            break;
         }
      fprintf(stderr, "%d: accepted connection from IP=%d.%d.%d.%d socket=%d", i,
            (soap.ip >> 24)&0xFF, (soap.ip >> 16)&0xFF, (soap.ip >> 8)&0xFF, soap.ip&0xFF, s);
      if (soap_serve(&soap) != SOAP_OK) // process RPC request
      soap_print_fault(&soap, stderr); // print error
      fprintf(stderr, "request served\n");
      soap_destroy(&soap);      // clean up class instances
      soap_end(&soap)// clean up everything and close socket
      }
   }
   soap_done(&soap); // close master socket and detach context

   return 0;
}

Copy and paste the code above into HelloWsld.cpp.

I've listed my changes with some comments (/** */). What is added is that we include a namespaces struct to explicitly set the correct namespaces. gSOAP (soapcpp2.exe) will not only generate code files but also a *.nsmap file which generates a static struct containing the correct namespace to use. The soap_set_namespaces() method will set this struct to use it. That’s it!

All of this may seem like a lot of work but when you are finished setting up the project every change you make in your interface (wsdl) will automatically drill down into your implementation. After setting up the project you most likely will only need to work on the wsdl file and HelloWsdlMethods.cpp to add your own methods.

Now that we've created the server on Windows CE it is time to create a client that will consume our service:

The C# Managed Client

Create a C# .NET (desktop) application with just one Form (I'm not going through each single step of how to create a C# .NET application as this is outside of the scope of this article and assumed known). Add just one button to the form and give it a name. After that we need add our webservice reference to the solution (our wsdl file). In the solution explorer right click on "References" and select "Add Service Reference". In the address field enter the path and the name of the wsdl file on your local machine. In my case this is: C:\Data\Development\HelloWorldWebService\HelloWorldWebService\HelloWsdl.wsdl. I named the reference HelloWsdlMethods and this name will appear in the solution explorer. Click "Go":

.

On the main form Double click on the button and paste the following code into the button handler:

private void btnTest_Click(object sender, EventArgs e)
{
    try
    {
        HelloWsdlMethods.HelloWorld_PortTypeClient server = new HelloWsdlMethods.HelloWorld_PortTypeClient();
        string name = server.HelloWorldOperation("Dummy");
        MessageBox.Show(name);
    }
    catch (Exception error)
    {
        MessageBox.Show("Request failed: " + error.Message);
    }
}

The code behind the button will access our server and call the webservices method. As you can see the naming is probably not the best but for this example it will do.
Run the server program (HelloWorldWebService.exe) on the target and run the client on the desktop. If you've done everything correct you should see the following after pressing the "Test" button:
The C# Client running on the desktop:

Application console on the Windows Embedded Compact device:

Things to lookout for

As you have seen there is a bit of work involved to get gSOAP to integrate nicely with Visual Studio 2008. The process is error prone and gSOAP's logging doesn't help much in most cases. In my experience there are basically three areas where things are most likely to go wrong:

  • Location
  • This tag in the wsdl file specifies the network address on which the webservices are running. Make sure it is set right.

  • Namespaces
  • Make sure that the namespaces used in the wsdl file match the ones in the source code (use the nsmap file) and don't forget to call soap_set_namespaces().

  • Hostname
  • Make sure that you leave the hostname empty: soap_bind(&soap, "", 8080, 100). Specifying the hostname does not work on CE. Also the tools wsdl2h.exe and soapcpp2.exe have a lot of options that I did not discuss in this article. However, getting to know the different gSOAP options is definitely worth the time. The gSOAP website contains a wealth of information and the documentation is comprehensive.

Of course this article only shows a very simple example of how to use Webservices on Windows CE with gSOAP but it should be enough to get you going on much more complex webservices hosted on smart devices.

Please let us know if you'd like to see a more complex example (for instance transferring a file through webservices).

Good luck!

Cloning CalibrUi in Windows CE 6.0

If you have a need to clone the calibration application in Windows CE 6.0 you'll run into several problems when following the instructions in MSDN. The instructions on that page seem to be working fine for Windows CE 5.0 but are causing major headaches on Windows CE 6.0...

Time for an update on the documentation to match the correct procedure for Windows CE 6.0:

Cloning the CalibrUi Module

To clone the CalibrUi module

  1. Select the Catalog Items View tab in the workspace window.
  2. Expand the [OS Design Name]\Core OS node, and navigate to CEBASE\Shell and User Interface\Graphics, Windowing and Events.
  3. Right-click Minimal GDI Configuration. In the pop-up menu that appears, select Clone Catalog Item.
  4. The Clone Catalog Item - gwe2 window appears, with a list of component libraries that can be cloned. If the Calibrui module is not already selected, select it and choose OK. In the Cloning Complete window, choose OK.
  5. To make sure that the CalibrUi module has been cloned, select the Solution Explorer tab in the workspace. Expand the Subprojects node. If the cloning operation was successful, you will see CalibrUi (gwe2 clone) in the list of subprojects.
    Note: You must never change the project name CalibrUi (gwe2 clone). Making changes to the project name will affect the build system and cause your run-time image to be built incorrectly or to fail.
  6. Expand the CalibrUi (gwe2 clone) node. All of the source files that relate to the Calibrui module are now available for editing. Any changes to the source files will take effect in any run-time image or executable that you create from this point forward.

You can now try to build the CalibrUi subproject by right clicking it and choosing "Build" from the context menu. If you have sysgenned your OS Design before it should build without any problems.

The problems start when you now (re)sysgen your entire OS Design (needed to include the cloned CalibrUi into your kernel).

When the build system executes sysgen -p dcom preproc a build tool called "SysgenMake.exe" will throw an unhandled exception:

Unhandled Exception: System.ArgumentException: Illegal characters in path.
at System.IO.Path.CheckInvalidPathChars(String path)
at System.IO.Path.GetFileName(String path)
at System.IO.Path.GetFileNameWithoutExtension(String path)
at Microsoft.PlatformBuilder.MainClass.ComputeClonedLibs(String targetLibs, StringDictionary& moduleVars, StringDictionary& environmentVariables, Boolean res2res)
at Microsoft.PlatformBuilder.MainClass.Main(String[] args)
NMAKE : fatal error U1077: 'SysgenMake' : return code '0xe0434f4d'
Stop.

This error is caused by TAB characters in the makefile in \WINCE600\PUBLIC\DCOM\CESYSGEN. Remember that you should NEVER EVER change anything in the PUBLIC or PRIVATE folders? Well, this is an exception. There's simply no way around this bug without changing the TAB characters to spaces, so:

  1. Open \WINCE600\PUBLIC\DCOM\CESYSGEN\makefile. in Visual Studio 2008
  2. Press CTRL-R, CTRL-W (this will turn on "View White Space")
  3. Replace all TAB characters in the file (recognizable by the right arrow character) with spaces
  4. Save the file

If you would now sysgen your OS Design again you will see that it successfully executes the sysgen -p dcom preproc command but unfortunately it will fail a bit later in the build process with the following error:

Copying gwestubs.*
Building combined gwes res file for 0419
SysgenMake -RES2RES %GWES_RESOURCES% -fo C:\WINCE600\OSDesigns\MyOSDesign\MyOSDesign\Wince600\MyBSP_ARMV4I\cesysgen\oak\target\ARMV4I\retail\0419\gwes.res
Res2Res for Windows CE (Release) (Built on Jun 30 2006 16:52:50)
Copyright (C) Microsoft Corp. 1991-2004. All rights reserved.
Res2Res: Ignoring "dummy"
Res2Res: Using C:\WINCE600\public\common\oak\Bin\i386\R2RDUMMY.DLL for temp exe
Res2Res: Using resources from C:\WINCE600\OSDesigns\MyOSDesign\MyOSDesign\calibrui\obj\ARMV4I\retail\0419\calibrui_clone.res.
Res2Res: Adding resources from C:\WINCE600\OSDesigns\MyOSDesign\MyOSDesign\calibrui\obj\ARMV4I\retail\0419\calibrui_clone.res to C:\DOCUME~1\Michel\LOCALS~1\Temp\R2R1A00.tmp.
ERROR: Res2Res: Could not open C:\WINCE600\OSDesigns\MyOSDesign\MyOSDesign\calibrui\obj\ARMV4I\retail\0419\calibrui_clone.res.
Res2Res: Error adding resources (-1) 
 
NMAKE : fatal error U1077: 'SysgenMake' : return code '0x2'
Stop.

It is complaining it can't find the file C:\WINCE600\OSDesigns\MyOSDesign\MyOSDesign\calibrui\obj\ARMV4I\retail\0419\calibrui_clone.res. Let's check if that file exists or not...

This is the tree of the CalibrUi subproject after building it:

+---obj
    +---ARMV4I
        +---retail
            +---0404
            +---0407
            +---0409
            +---040C
            +---0410
            +---0411
            +---0412
            +---0413
            +---0416
            +---041D
            +---0804
            +---0C0A

As you can see all locale subfolders are created *except* 0419 (Russian)... Even though you may not even need Russian this is still causing a problem!

Inside the obj dir of CalibrUi a file named clone_locales.txt is generated. It looks like this file may have something to do with our problem... But who/where/when is this file created?

The magic is in prelink.bat (thank you for the tip MVP Pavel Belevsky!):

@echo off
@REM This is called before build to create all the RES files needed for all locales
@REM WARNING: Do not change the names of any files because SYSGENMAKE.EXE assumes their location for cloning
(IF NOT EXIST obj mkdir obj || goto EXIT_ERROR
(IF NOT EXIST obj\%_TGTCPU% mkdir obj\%_TGTCPU%) || goto EXIT_ERROR
(IF NOT EXIST obj\%_TGTCPU%\%WINCEDEBUG% mkdir obj\%_TGTCPU%\%WINCEDEBUG%) || goto EXIT_ERROR

dir /AD /B %_PROJECTOAKROOT%\files\INTLTRNS > obj\%_TGTCPU%\%WINCEDEBUG%\clone_locales.txt

set _PRELINK_LOCALE=%LOCALE%
for /f %%L in (obj\%_TGTCPU%\%WINCEDEBUG%\clone_locales.txt) do call :COMPILE_RC %%L
set LOCALE=%_PRELINK_LOCALE%
goto :END_CLONING

:COMPILE_RC
(set LOCALE=%1 && nmake CalibrUi_clone.res) || goto EXIT_ERROR
(IF NOT EXIST obj\%_TGTCPU%\%WINCEDEBUG%\%1 mkdir obj\%_TGTCPU%\%WINCEDEBUG%\%1) || goto EXIT_ERROR
move /Y CalibrUi_clone.res obj\%_TGTCPU%\%WINCEDEBUG%\%1\CalibrUi_clone.res || goto EXIT_ERROR
goto EOF

:EXIT_ERROR
exit /b -1

:END_CLONING
@REM Place any additional steps after END_CLONING but before EOF
:EOF

As you can see in the above batch file the folder list in clone_locales.txt is created by the line:

dir /AD /B %_PROJECTOAKROOT%\files\INTLTRNS > obj\%_TGTCPU%\%WINCEDEBUG%\clone_locales.txt

The folder %_PROJECTOAKROOT%\files\INTLTRNS (in our case C:\WINCE600\OSDesigns\MyOSDesign\MyOSDesign\Wince600\MyBSP_ARMV4I\OAK\files\INTLTRNS) apparently does not contain the full list of locales we need for a successful sysgen of the OS Design. We already saw that our %_FLATRELEASEDIR% does seem to contain more locale IDs but at the time of a clean sysgen those folders do not exist in the %_FLATRELEASEDIR% yet, so we need to get them from another location. A bit of searching leads us to \WINCE600\PUBLIC\COMMON\OAK\LIB so lets change the line creating clone_locales.txt to take that location as a source:

dir /AD /B %_PUBLICROOT%\COMMON\OAK\LIB\%_TGTCPU%\%WINCEDEBUG% > obj\%_TGTCPU%\%WINCEDEBUG%\clone_locales.txt

Now if you build the CalibrUi subproject you will end up with this tree:


+---obj
    +---ARMV4I
        +---retail
            +---0404
            +---0407
            +---0409
            +---040C
            +---0410
            +---0411
            +---0412
            +---0413
            +---0416
            +---0419
            +---041D
            +---0804
            +---0C0A

With all the above changes implemented you can finally successfully sysgen your platform with your cloned and modified CalibrUi component!

New release of Topaz BSP

We have just released a new version of the BSP (r366).

Here are the fixes/additions in this release:

  • Improved LCD autodetection routines
  • Added support for LCD autodetection in 24LC32+ EEPROMs
  • Added support for backlight configuration in LCD structures
  • Added support for LCD enable configuration in LCD structures
  • LCD autodetection is now an optional component
  • Fixed PWM2 and PWM4 pad settings
  • Added SDK headers and library for PWM
  • Added automatic prescaler selection for PWM
  • Fixed UART4 pad settings
  • Added UART5 DMA support
  • Fixed I2C pad and ALT settings
  • Fixed I2C and USB OTG duplicated pin use
  • Changed driver selection to match DevKit EXP headers
  • Fixed flash in IE

New version of the Topaz BSP and Topaz Flasher

We have just released a new version of the BSP (r298) and a new version of the Topaz Flasher.

The r298 Topaz BSP now includes a fully configurable GPIO driver and SDK libraries / headers (amongst other improvements/fixes) and the Topaz Flasher can now also be used to flash a custom flat binary file of any size to any location in flash (so you can now use the Topaz Flasher to flash Linux for instance!).

Installing CE 6.0 SDK's on systems with only VS2008 installed

If you are a device manufacturer or are in any other way involved in building Windows CE 6.0 kernels, you undoubtedly also have to generate an SDK for your device so that application developers can create applications targeting your device.

Platform Builder for Windows CE 6.0 plugs into Visual Studio 2005, but the latest and greatest version of Visual Studio that supports "smart device" development is Visual Studio 2008 (unfortunately not Visual Studio 2010, yet?!). Most application developers will use Visual Studio 2008 for smart device development, so that's what your SDK should support in most cases.

You may have noticed that some of your customers are complaining that the SDK installation aborts and rolls back completely. This only happens on systems that do not have Visual Studio 2005 installed:

The reason for these "ToolsMsmCA(Error): IHxFilters filter registration failure" and "ToolsMsmCA(Error): IHxRegisterSession transaction failure" errors is that the documentation of the SDK is trying to merge with the Visual Studio namespace "VS.VSIPCC.v80", which is the Visual Studio 2005 namespace. Visual Studio 2008 uses the namespace "VS.VSIPCC.v90", and the namespace "VS.VSIPCC.v80" is not available on systems with only Visual Studio 2008 installed.

The solution is simple:

Open your SDK.sdkcfg file and look for the lines:

<PropertyBag NAME="Help2">
        <Property NAME="VS Namespace">MS.VSIPCC.v80</Property>

If you want the SDK to install into Visual Studio 2005 then leave these lines as it is, but if you want the SDK to install into Visual Studio 2008 then change the "v80" into "v90":

<PropertyBag NAME="Help2">
        <Property NAME="VS Namespace">MS.VSIPCC.v90</Property>

and regenerate the SDK.

But what if you have an SDK from a 3rd party and you can't regenerate the SDK? Well, to this problem there are two solutions:

  1. Do a custom installation of the SDK and de-select the documentation
  2. Use Orca (or any other msi editor) to change the namespace

Of course "solution" number 1 is not a real solution. What if you really need the documentation of this SDK?

Solution number 2 is of course much better. Orca is in the Microsoft Windows SDK which should be on your machine if you've installed Visual Studio. Take a look in your "Program Files" folder and see if there's a folder "Microsoft SDKs". If there is, you'll find "Orca.msi" in the "bin" folder (if not, install Visual Studio or download the Microsoft Windows SDK here.

Once you've installed Orca you can right click the msi and choose "Edit with Orca". When Orca opens you'll have to select the "Property" table on the left and scroll to the property "VS_NAMESPACE" on the right. Now double click on the value of that property and change "MS.VSIPCC.v80" into "MS.VSIPCC.v90" if you want the documentation to install correctly into Visual Studio 2008.

Of course it would be nice to be able to build one SDK that will install without any problems in any version of Visual Studio that supports smart device development. Without an update to the SDK roller by Microsoft or some more hacking and editing of the MSI using Orca this isn't possible at the moment, but the above solutions will at least give you a way to target either VS2005 or VS2008 with your SDK.

Topaz

GuruCE is the official supplier and support center for Windows CE/Windows Embedded Compact on Device Solution's Topaz i.MX25 CPU Module and the Topaz i.MX25 Development Kit.

For more information and downloads, see the Topaz page.

Martin Welford of Device Solutions, the board manufacturer, made a cool video about what the Topaz is and can do:

VisualSVN (Subversion) and Mantis Integration

This post is a bit off-topic for this blog, but since source control and bugtracking are important for Windows CE development as well I thought I'd post about my experiences for (my own) future reference (but I'm sure this will help others as well).

The current setup

At GuruCE we use Subversion (aka SVN) for source control and Mantis (a free popular web-based bugtracking system) for tracking bugs & features.

Subversion runs on a Windows Server through VisualSVN Server (VisualSVN Server makes the Subversion server easy and convenient to install and administer on Windows).

Mantis is running on a Linux server on the other side of the world.

The goal

The goal is to automatically update the status and notes of an issue when committing changes to the SVN server through the TortoiseSVN client.

I want to be able to write comments like:

Worked on issue #64. Robot now enters low power mode when in surveillance mode.
Fixed bug #65. No more unintentional kills. Hopefully...
Implemented feature #66. Robot is now self-aware.
Implementing feature #67 failed. Robot does not allow implementation of self-destruct command.

Ok, I apologize. That was really geeky... ;)

The solution

A lot of information can be found on the net about integrating Subversion and Mantis when they both run on the same Linux server. Our situation is a bit different, being that Subversion runs on a Windows server and Mantis runs on a Linux server on the other side of the world.

Step 1: Configuring Mantis

Using MantisBT version 1.2.0
The first step is to configure Mantis to filter comments so it can determine which issue to update and what to set the state of the issue to. To do that, open config_inc.php in your Mantis installation folder on your server and add the following items:

  • $g_source_control_regexp = '/\b(?:feature|bug|issue)\s*[#]{0,1}(\d+)\b/i';
  • This regular expression filters out the feature, issue or bug number (however you'd like to call it) so it can add a note to the feature/bug/issue.

  • $g_source_control_fixed_regexp = '/\b(?:implement(?:ed|s)|fix(?:ed|es))\s+(?:feature|bug|issue)?\s*[#]{0,1}(\d+)\b/i';
  • This regular expression filters out the feature/bug/issue number if you prepend the word "fixed", "fixes", "implemented" or "implements". When this regular expression evaluates to a number Mantis will use the settings below to set the state of the issue.

  • $g_source_control_set_status_to = RESOLVED;
  • If you fix a bug or implement a feature Mantis will set the issue to status "Resolved".

  • $g_source_control_set_resolution_to = FIXED;
  • If you fix a bug or implement a feature Mantis will set the resolution to "Fixed".

  • $g_source_control_notes_view_status = VS_PUBLIC;
  • Any note added to the issue will be "Public" by default. If you omit this statement all notes will be "Private".

Mantis v1.2.0 comes with a script that you can call from your source control application when committing (or checking in) files. The script can be found in the "scripts" subfolder in your Mantis installation and is called checkin.php.

This script originally uses g_source_control_account to specify the name of the user updating the issue. I didn't want to always specify the same user, but instead specify the name of the user actually committing the files. Therefore I changed line 31 so it reads:

  1. # Check that the username is set and exists
  2. $t_username = $argv[1]; # config_get( 'source_control_account' );
  3. if( is_blank( $t_username ) || ( user_get_id_by_name( $t_username ) === false ) ) {
  4.         echo "Invalid source control account ('$t_username').\n";
  5.         exit( 1 );
  6. }

Now the script accepts a username as the first parameter to the script. For this to work you need to make sure that the usernames you have configured in VisualSVN (subversion) are the same as the ones you configure in Mantis.

To see if Mantis is configured correctly you can test the script. First create a new test issue in Mantis and note the issue number. Now login to your server (through ssh) and run the script as follows (if your host doesn't have a simple way to log in using ssh read on to see how to use PuTTY for this):

path-to/php-cli path-to/checkin.php "username" <<< "This will be added as a note to issue #nn"

In our case, the server that runs Mantis is hosted by hostmonster, so for me the actual command to run is:

/ramdisk/bin/php5-cli ~/public_html/mantis/scripts/checkin.php "Michel" <<< "This will be added as a note to issue #46"

The above should add a note to your newly created issue (remember to change #46 with the issue number you created!).

If you want to test setting the issue to "Resolved" with resolution "Fixed", run the following command:

path-to/php-cli path-to/checkin.php "username" <<< "This fixes bug #nn"

Again, in my case:

/ramdisk/bin/php5-cli ~/public_html/mantis/scripts/checkin.php "Michel" <<< "This fixes bug #46"

Once you've verified the checkin script is working properly, you have to configure the machine running the VisualSVN Server so it can remote login on the server running Mantis over ssh. I've used PuTTY/Plink for that purpose:

Step 2: Configuring PuTTY/Plink

  1. Download the PuTTY suite
  2. Generate a public/private key pair using PuTTYgen
  3. DO NOT enter a passphrase for this private key. We want to use Plink from a script so we can not enter a passphrase!

  4. Import the public key into your server running Mantis and authenticate the key (if your Mantis server is hosted by Hostmonster you can simply use the "SSH/Shell Access" icon on the HM control panel to "Manage SSH Keys" and import & authenticate the public key that way, otherwise read this)
  5. Now start PuTTY, fill in the hostname and the ssh port of your server running Mantis and point to the private key in Connection->SSH->Auth->Private key file for authentication
  6. Test the session by clicking "Open". If this is the first time you access this server using PuTTY it will display a warning:

    Click yes to store the server's fingerprint in the registry (so you don't have to do this the next time).

    Now enter the username you use for ssh access to your Mantis server (in the case of hostmonster this is your account name). If you imported and authenticated the public key you created in (2) correctly on your server you should NOT have to enter a password. The server should have authenticated you using the key you pointed to in (4).

    You should now be able to run the checkin script through PuTTY using the same commands as above.

    The next step is to use Plink, a commandline version of PuTTY that can be used to run commands on a remote server using ssh without dialogs or user input:

  7. "path-to\plink.exe" -ssh -i "path-to-private-key" -batch sshusername@server.com path-to/php-cli path-to/checkin.php username <<< \"This will be added as a note to issue #nn\""
  8. If you've filled in the correct values for the placeholders above plink should return without outputting any warning or error, and a note should be added to the specified issue in Mantis.

    If not, specify -v on the plink commandline to get verbose output.

We're almost done! What we have accomplished so far is that we are able to update Mantis remotely, from the machine running the VisualSVN (or subversion) server. Now all we have to do is write a proper "hook" script that gets called right after we commit some files.

Step 3: Configuring the VisualSVN post-commit hook script

In VisualSVN (or subversion), create a "Test" repository that you'll be using to test the post-commit hook script.

VisualSVN offers a handy IDE to edit hook scripts. Go to the VisualSVN administration console, right click on the repository you want to install the hook into ("Test"), click "Properties" and select the "Hooks" tab. Here you will find templates for the various scripts SVN supports. We want to use the post-commit script, so select that script and press the "Edit" button.

Paste the following batch script into the VisualSVN edit window (or if you're not using VisualSVN just create post-commit.cmd) and update the PLINK, PLINK_CMDLINE, PHP, CHECKIN and SVNLOOK environment variables to the correct values on your system:

@ECHO off

SET REPOS=%1
SET REV=%2
SET PLINK="%ProgramFiles(x86)%\PuTTY\plink.exe"
SET PLINK_CMDLINE=-ssh -i "path-to-private-key" -batch sshusername@server.com
SET PHP=/server_path_to/php5-cli
SET CHECKIN=/server_path_to_mantis/scripts/checkin.php
SET SVNLOOK="%VISUALSVN_SERVER%\bin\svnlook.exe"

SET PLINKLOGFILE=%REPOS%\hooks\log.txt
SET LOGFILE=%REPOS%\hooks\log%REV%.txt
SET AUTHORFILE=%REPOS%\hooks\author%REV%.txt
SET OUTPUTFILE=%REPOS%\hooks\output%REV%.txt
SET PLINKBAT=%REPOS%\hooks\plinkbat.cmd

%SVNLOOK% log -r %REV% %REPOS% > %LOGFILE%
%SVNLOOK% author -r %REV% %REPOS% > %AUTHORFILE%

TYPE %LOGFILE% > %OUTPUTFILE%
ECHO  --- >> %OUTPUTFILE%
ECHO Checked into repository >> %OUTPUTFILE%
ECHO %REPOS% (Revision %REV%) >> %OUTPUTFILE%
ECHO by >> %OUTPUTFILE%
TYPE %AUTHORFILE% >> %OUTPUTFILE%

ECHO %PLINK% %PLINK_CMDLINE% ^"%PHP% %CHECKIN% > %PLINKBAT%
ECHO ^\^">> %PLINKBAT%
TYPE %AUTHORFILE% >> %PLINKBAT%
ECHO ^\^" >> %PLINKBAT%
ECHO ^<^<^< >> %PLINKBAT%
ECHO ^\^">> %PLINKBAT%
TYPE %OUTPUTFILE% >> %PLINKBAT%
ECHO ^\^">> %PLINKBAT%
ECHO ^">> %PLINKBAT%

setlocal enabledelayedexpansion
@echo off
set FINAL=
for /f "delims=" %%a in (%PLINKBAT%) do (
set FINAL=!FINAL!%%a
)
echo %FINAL% > %PLINKBAT%
endlocal

CALL %PLINKBAT% > %PLINKLOGFILE%

rem CALL DEL %PLINKBAT%
CALL DEL %LOGFILE%
CALL DEL %AUTHORFILE%
CALL DEL %OUTPUTFILE%

Step 4: Testing it all (and some bugfixing)

Now locally check out the repository "Test" and add a text file to it. Commit the changes with a comment "worked on issue #nn", of course replacing nn with the number of the Mantis test issue you created above, and the script should be automatically called.

After committing, go to Mantis and see if your note has been added.

No? Hmmm.... Strange...

Check the "hooks" subfolder in repository "Test" and open the plinkbat.cmd file:

"C:\Program Files (x86)\PuTTY\plink.exe" -ssh -i "path-to-private-key" -batch sshusername@server.com "/server_path_to/php5-cli /server_path_to_mantis/scripts/checkin.php \"Michel\" <<< \"Worked some more on issue #50 --- Checked into repository C:\Repositories\Test (Revision 23) by Michel\""

Well, that's strange. That command looks all good. (Of course your command has the right values filled in for "path-to-private-key" etc!)

Let's try to manually call the post-commit.cmd file and see if it works then...

Open a dos prompt command window (in Windows 7 shift-rightclick the folder in Explorer and choose "Open command window here") to your repository's hook subfolder (in my case "C:\Repositories\Test\hooks") and run the following command:

post-commit.cmd C:\Repositories\Test 23 (of course replacing '23' with your revision number!)

Now check the issue again in Mantis. Has it been updated? What the... Now it works!?

If you would take a look in plinkbat.cmd you'd see it is exactly the same as before (in fact, running plinkbat.cmd from the command line results in the note being added to Mantis as well of course!), so what is going on?

Well, that question has kept me busy for some time... Remember we started PuTTY once to store the server key in the registry? Well, it so happens that VisualSVN runs as a service, and as such executes the hook scripts under a different user name than yours (because you may not be logged in at all). Second, PuTTY stores the ssh host keys under HKEY_CURRENT_USER in the registry, and what's even worse DOES NOT return 1 when it fails to connect to the server due to the server's host key not being cached in the registry. I think that's a bug in plink. Plink should return 1 in that instance (if it would we would be able to see the error in TortoiseSVN).

So, what's the solution? The solution is to simply copy the stored ssh host key to all users in the system:

  1. Start regedit.exe
  2. Browse to "HKEY_CURRENT_USER\Software\SimonTatham\PuTTY\SshHostKeys" and export that key
  3. Browse to HKEY_USERS and expand the key
  4. You should now see a list of users on your system similar to this:
    • .DEFAULT
    • S-1-5-18
    • S-1-5-19
    • ...etc...
  5. Open the exported key from (2) in notepad and duplicate the key for the list of users above
  6. Like this:

    [HKEY_USERS\.DEFAULT\Software\SimonTatham\PuTTY\SshHostKeys]
       hostkeyvalue_here

    [HKEY_USERS\S-1-5-18\Software\SimonTatham\PuTTY\SshHostKeys]
       hostkeyvalue_here

    [HKEY_USERS\S-1-5-19\Software\SimonTatham\PuTTY\SshHostKeys]
       hostkeyvalue_here

    ...etc...

    Do not duplicate the key for user keys that have more numbers behind S-1-5-XX, since that is the current user.

  7. Now save and close the registry file
  8. And finally double click it to add the hostkey registry information to the registry

If you now change a file in your Test repository again, commit it and add a comment containing "issue #nn", Mantis should update correctly now with a note added to the issue, like this:

Worked some more on issue 0000050 --- Checked into repository C:\Repositories\Test (Revision 25) by Michel

You could go on and write a more elaborate batch script, but we all know that batch files are not the most user friendly way of writing scripts. Nothing stops us from writing a simple C# application that gathers all information and updates Mantis for us though!

I've attached a simple C# application that updates Mantis using a nicely formatted note, like these ones (I committed 2 files at the same time with the comments "worked on issue #50 (file1.txt)" and "implemented feature #46 (file2.txt)":

worked on issue 0000050 (file1.txt)
Checked into repository "Test" (revision 26) by Michel on 2010-03-12 10:38:27 +1300 (Fri, 12 Mar 2010)
Changed files:
U trunk/file1.txt
U trunk/file2.txt
implemented feature 0000046 (file2.txt)
Checked into repository "Test" (revision 26) by Michel on 2010-03-12 10:38:27 +1300 (Fri, 12 Mar 2010)
Changed files:
U trunk/file1.txt
U trunk/file2.txt

As you can see my C# program supports updating multiple issues at the same commit, it gathers all information and formats it nicely.

Call the attached C# program, UpdateMantis.exe, like this from your post-commit.cmd:

"%ProgramFiles(x86)%\GuruCE Ltd\UpdateMantis\UpdateMantis.exe" "%1" %2 > "%1\hooks\UpdateMantis.log"

But, before you do you'll of course have to install UpdateMantis (a setup package is included in the attachment) and configure the UpdateMantis.exe.config file (note that in Windows 7 you'll have to configure security for the .config file so normal users can modify the file):

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <appSettings>
    <add key="plink" value="path_to_PuTTY\plink.exe"/>
    <add key="plink_commandline" value="-l username -i privkey -ssh -batch"/>
    <add key="php-cli" value="/serverpath_to/php5-cli"/>
    <add key="checkin" value="/serverpath_to_mantis/scripts/checkin.php"/>
    <add key="svnlook" value="path_to\svnlook.exe"/>
  </appSettings>
</configuration>

Note that you can use environment variables in the xml values. My config file looks something like this:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <appSettings>
    <add key="plink" value="%ProgramFiles(x86)%\PuTTY\plink.exe"/>
    <add key="plink_commandline" value="-ssh -i "C:\Data\MantisSVN\mantiskey.ppk" -batch username@guruce.com"/>
    <add key="php-cli" value="/ramdisk/bin/php5-cli"/>
    <add key="checkin" value="~/public_html/mantis/scripts/checkin.php"/>
    <add key="svnlook" value="%VISUALSVN_SERVER%bin\svnlook.exe"/>
  </appSettings>
</configuration>

Note that the attached VS2008 C# project includes source and IS NOT OF PRODUCTION QUALITY. There is no error handling whatsoever, so if you want to use this program in a production environment you should add some code and make it more robust.

I hope this blog post will help some of you out there struggling to get Mantis integrated with SVN. Good luck!

Last but not least, I'd like to thank the writer of this blog post and all people that commented on that blog post. It has been my single source of information while trying to find a solution to my specific problem. Thanks!

Offline installation of CE 6.0 R3

I already provided a way to "download once install often" for the Windows CE 6.0 R2 update, and now I've modified the sources to do the same for the R3 update (1.19 GB). Full source and executable attached!

Enjoy!

PS. If you have any trouble installing the Windows CE 6.0 R3 Update please let Brian Rogers know by providing feedback on this blogpost. Feedback will make the process better next time round!

Update: Microsoft has made a download available of the Windows CE R3 update in ISO form! Of course you can still use my tool if you want. Sometimes I just wish Microsoft would announce these kind of things...

VoIP QFE's

The fixes to CE 6.0 R2 VoIP will be included in the July 2009 QFE package which should be released this week.

These QFE's are a direct result from the support incident we opened with Microsoft regarding this issue. The support incident was of course completely free because this was a bug, so, whenever you find a bug that needs fixing: Please open a support incident with Microsoft and help make Windows CE better!

At this moment the whole Windows CE support procedure you have to go through is frustrating to say the least, but Microsoft is updating its support procedures for Windows CE so it will be streamlined in the near future. For now, just keep your cool on the phone and follow the instructions as per the leaflet inside the Windows CE 6.0 box. Do not pay or give them your credit card number if you still have a free support call left (you get 2 free support calls when you buy Platform Builder for Windows CE 6.0).

I'll keep you updated when I hear something back from Microsoft regarding the support procedures.

What does the internet think about...

The war is settled! After years of not knowing, the definitive answer is finally clear:

The internet thinks this about Windows CE and this about Linux.

;)

Communicating with your MicroFramework Application over USB

Version 3.0 of the MicroFramework SDK (finally!) supports configuring USB for other functions than just debug. This means you can now use USB as a communications medium for your applications. Microsoft ships a sample that shows one of those possibilities: Transforming your device into a mouse. In this blog post I'll show a more useful solution: How to setup a MicroFramework device so you can send and receive data over USB to and from the device.

Independent hardware vendors (IHVs) who manufacture USB devices must typically provide a way for applications to access the device’s features. Historically, this has meant using the Windows® Driver Model (WDM) to implement a function driver for the device and installing the driver in the device stack. However, drivers require a significant development effort, and some devices are simple enough that they do not require the full support of a custom function driver.
The USB functionality we'll be developing will be simple, so writing a full function driver will be overkill. Luckily Microsoft introduced the user-mode driver framework (UMDF) and part of this framework is WinUSB. WinUSB was developed concurrently with the Windows Driver Foundation (WDF) and is available for Windows XP and later versions of Windows. It includes a kernel-mode driver, WinUsb.sys, which is an integral part of the WDF-UMDF support for USB drivers. For USB devices that are accessed by only a single application, you can often install WinUsb.sys as the device’s function driver instead of implementing a custom driver. The application can then configure the device and access its endpoints by using the WinUSB API, and that's exactly what I'll do in this example!

WinUSB consists of two primary components:

  • WinUsb.sys is a kernel-mode driver that can be installed as either a filter or function driver, above the protocol drivers in a USB device’s kernel-mode device stack.
  • WinUsb.dll is a user-mode DLL that exposes the WinUSB API. Applications can use this API to communicate with WinUsb.sys when it is installed as a device’s function driver.

For devices that do not require a custom function driver, WinUsb.sys can be installed in the device’s kernel-mode stack as the function driver. User-mode processes can then communicate with WinUsb.sys through a set of device I/O control requests. The WinUSB API, exposed by WinUSB.dll, simplifies this communication process. Instead of constructing device I/O control requests to perform standard USB operations such as configuring the device, sending control requests, and transferring data to or from the device, applications call equivalent WinUSB API functions. Internally, WinUsb.dll uses the data that the application passes to the WinUSB function to construct the appropriate device I/O control request and sends the request to WinUsb.sys for processing. When the request is complete, the WinUSB function passes any information returned by WinUsb.sys such as data from a read request—back to the calling process.

Using the WinUSB API to communicate with a device is much simpler than implementing a driver, but has some corresponding limitations:

  • The WinUSB API allows only one application at a time to communicate with the device. If more than one application must be able to communicate concurrently with a device, you must implement a function driver.
  • The WinUSB API does not support streaming data to or from isochronous endpoints. Isochronous transfers require a kernel-mode function driver.
  • The WinUSB API does not support devices that already have kernel-mode support. Examples of such devices include modems and network adaptors, which are supported by the telephony API (TAPI) and NDIS, respectively.
  • For multifunction devices, you can use the device’s INF to specify either an in-box kernel-mode driver or WinUsb.sys for each USB function separately. However, you can specify only one of these options for a particular function, not both.

WinUSB is natively supported on all Windows Vista SKU's and is supported on all SKU's of the 32 bit versions of Windows XP SP2 and later service packs. WinUSB is not native to Windows XP; it must be installed with the WinUSB co-installer.

The WinUSB co-installer package can be found in the Windows Driver Kit under the WinDDK\BuildNumber\Redist\Winusb folder. The DLLs are signed and can be redistributed by IHVs. So if you want to develop an application that talks to your MicroFramework device over USB under XP, you'll have to install the WinDDK. After you've completed your development and are ready to ship your device your customers of course don't have to install the WinDDK to use your application because you'll ship your device with an installer that includes the redistributable co-installer package.

Read "How to Get the WDK and the WLK" to obtain the latest WDK version.

Before your application can use the WinUSB API to communicate with your device, you must install WinUsb.sys as the device’s function driver. To do so, you have to create a package that includes:

  • The WinUSB co installer, which installs WinUSB on the target system, if necessary. The WDK includes two versions of the co-installer, one for x86 systems and one for x64 systems. They are both named WinUSBCoInstaller.dll and are located in the WinDDK\BuildNumber\redist\winusb folder.
  • The Kernel Mode Driver Framework (KMDF) co installer, which installs the correct version of KMDF on the target system, if necessary. This co-installer is required because WinUsb.sys depends on KMDF. The x86 and x64 versions of WdfCoInstaller01005.dll are included with the WDK under the WinDDK\BuildNumber\redist\wdf folder (but you need WDK version 6001.18002 or higher).
  • An INF that installs WinUsb.sys as the device’s function driver.
  • A signed catalog file for the package. This file is required to install WinUSB on x64 versions of Windows Vista. For more information on how to create and test signed catalog files, see "Kernel-Mode Code Signing Walkthrough". Note that this document states that Inf2Cat is not currently part of the WDK tools, but in fact it is (you can find it in \WinDDK\BuildNumber\bin\SelfSign).

Before we can create the INF, let's define what we want our MicroFramework device to do, and develop the device side code!

In this example we want to keep it as simple as possible; all we want is two-way communication with our device. The best option for this is to create 2 bulk endpoints; one for reading and one for writing data. I'll be using the DeviceSolutions TahoeII board to develop the application on.

[Note: Download all code below]

Let's start coding!

  1. Start Visual Studio 2008
    I'm assuming you have Visual Studio 2008 with SP1, the .NET MicroFramework SDK v3.0 and the TahoeII SDK v3.0 installed.
  2. Create a new MicroFramework Console Application
  3. First let's add the needed references. Right click on your project in the solution explorer and select "Add reference...". Now add the following references:
    • Microsoft.SPOT.Hardware.Usb
  4. Open program.cs and add the following to the top of the file:
    using Microsoft.SPOT.Hardware.UsbClient;
  5. Add the following constants to class Program:
    private const int WRITE_EP = 1;
    private const int READ_EP = 2;

    We use these constants so we can easily change the used endpoints if needed.

  6. Delete the standard Debug.Print line from the function Main()
  7. Now we add code to function Main() to detect the number of USB Controllers supported by the hardware:
    // See if the hardware supports USB
    UsbController[] controllers = UsbController.GetControllers();

    // Bail out if USB is not supported on this hardware!
    if (0 == controllers.Length)
    {
        Debug.Print("USB is not supported on this hardware!");
        return;
    }

  8. At this moment the .NET MicroFramework 3.0 does not support changing the USB configuration on the fly so using USB for application communication and Visual Studio Debugging is not possible when configuring USB from C# code running on the device. If you update the device's USB descriptors through XML using MFDeploy it is possible to debug and communicate at the same time (over multiple USB interfaces descriptors that is! More about that in a follow up post about CDC).
    // Find a free USB controller
    UsbController usbController = null;
    foreach (UsbController controller in controllers)
    {
        if (UsbController.PortState.Stopped == controller.Status)
        {
            usbController = controller;
            break;
        }
    }

    // If no free USB controller
    if (null == usbController)
    {
        Debug.Print("All available USB controllers already in use. Set the device to use Ethernet or Serial debugging.");
        return;
    }

    Setting the TahoeII to use Ethernet or serial debugging is easy; just hold SW4 while resetting the device for debugging over Ethernet, or hold SW2 for debugging over serial. If you set it to debug over Ethernet then don't forget to configure the TahoeII's Ethernet settings using MFDeploy (see "Configuring Ethernet on the Tahoe-II" in the TahoeII SDK documentation -> press F1 in Visual Studio)

  9. If we found a free USB controller we can now start to configure that controller. We'll put the configuration code in a separate function to keep it all together. Add the following function to the Program class:
    1. private static bool ConfigureUSBController(UsbController usbController)
    2. {
    3.     bool bRet = false;
    4.  
    5.     // Create the device descriptor
    6.     Configuration.DeviceDescriptor device = new Configuration.DeviceDescriptor(0xDEAD, 0x0001, 0x0100);
    7.     device.bcdUSB           = 0x110;
    8.     device.bDeviceClass     = 0xFF;     // Vendor defined class
    9.     device.bDeviceSubClass  = 0xFF;     // Vendor defined subclass
    10.     device.bDeviceProtocol  = 0;
    11.     device.bMaxPacketSize0  = 8;        // Maximum packet size of EP0
    12.     device.iManufacturer    = 1;        // String #1 is manufacturer name (see string descriptors below)
    13.     device.iProduct         = 2;        // String #2 is product name
    14.     device.iSerialNumber    = 3;        // String #3 is the serial number
    15.  
    16.     // Create the endpoints
    17.     Configuration.Endpoint writeEP = new Configuration.Endpoint(WRITE_EP, Configuration.Endpoint.ATTRIB_Bulk | Configuration.Endpoint.ATTRIB_Write);
    18.     writeEP.wMaxPacketSize  = 64;
    19.     writeEP.bInterval       = 0;
    20.  
    21.     Configuration.Endpoint readEP = new Configuration.Endpoint(READ_EP, Configuration.Endpoint.ATTRIB_Bulk | Configuration.Endpoint.ATTRIB_Read);
    22.     readEP.wMaxPacketSize   = 64;
    23.     readEP.bInterval        = 0;
    24.  
    25.     Configuration.Endpoint[] usbEndpoints = new Configuration.Endpoint[] { writeEP, readEP };
    26.  
    27.     // Set up the USB interface
    28.     Configuration.UsbInterface usbInterface = new Configuration.UsbInterface(0, usbEndpoints);
    29.     usbInterface.bInterfaceClass    = 0xFF; // Vendor defined class
    30.     usbInterface.bInterfaceSubClass = 0xFF; // Vendor defined subclass
    31.     usbInterface.bInterfaceProtocol = 0;
    32.  
    33.     // Create array of USB interfaces
    34.     Configuration.UsbInterface[] usbInterfaces = new Configuration.UsbInterface[] { usbInterface };
    35.    
    36.     // Create configuration descriptor
    37.     Configuration.ConfigurationDescriptor config = new Configuration.ConfigurationDescriptor(180, usbInterfaces);
    38.  
    39.     // Create the string descriptors
    40.     Configuration.StringDescriptor manufacturerName = new Configuration.StringDescriptor(1, "GuruCE");
    41.     Configuration.StringDescriptor productName      = new Configuration.StringDescriptor(2, "MicroFramework WinUSB");
    42.     Configuration.StringDescriptor serialNumber     = new Configuration.StringDescriptor(3, "0000-0000-0000-0001");
    43.     Configuration.StringDescriptor displayName      = new Configuration.StringDescriptor(4, "MicroFramework WinUSB");
    44.     Configuration.StringDescriptor friendlyName     = new Configuration.StringDescriptor(5, "NetMF_WinUSB");
    45.  
    46.     // Create the final configuration
    47.     Configuration configuration = new Configuration();
    48.     configuration.descriptors = new Configuration.Descriptor[]
    49.     {
    50.         device,
    51.         config,
    52.         manufacturerName,
    53.         productName,
    54.         serialNumber,
    55.         displayName,
    56.         friendlyName
    57.     };
    58.  
    59.     try
    60.     {
    61.         // Set the configuration
    62.         usbController.Configuration = configuration;
    63.         if (UsbController.ConfigError.ConfigOK != usbController.ConfigurationError)
    64.             throw new ArgumentException();
    65.         // If all ok, start the USB controller.
    66.         bRet = usbController.Start();
    67.     }
    68.     catch (ArgumentException)
    69.     {
    70.         Debug.Print("Can't configure USB controller, error " + usbController.ConfigurationError.ToString());
    71.     }
    72.     return bRet;
    73. }

    The MicroFramework SDK v3.0 shipped without documentation on the new USB classes, but an extra download updating the documentation fixes that.

    Let's step through the code:

    • Line 6-14: We start by creating the device descriptor. The constructor of the Configuration.DeviceDescriptor Class has the following signature:
      public DeviceDescriptor(ushort Vendor, ushort Product, ushort DeviceVersion)

      Using parameter "Vendor" we supply the VID of the device. In this example I'm using a non-existing VID (0xDEAD), but in your final commercial device you'd have to use your own registered VID (check out http://usb.org for information on how to acquire a VID/PID). Note that in most countries using someone else's VID is considered theft and thus a criminal offense, not just a civil one. The parameter "Product" is used to supply the PID. In this case we'll just use a PID of 0x0001. The "DeviceVersion" parameter can be used to indicate device version and you are free to set this to anything you like (I set it to "1.0").
      The next 4 lines set the USB version in bcdUSB (USB 1.1), the device class, subclass & protocol. We set these to indicate our device is a custom vendor defined class; it doesn't fit in one of the predefined USB classes. The MicroFramework firmware already sets up control endpoint 0 for us so all we have to do is supply the maximum packet size of EP0. Even though the maximum packet size for EP0 is 32 bytes (see the iMXS datasheet; the processor the Meridian module on the TahoeII board is using) the TahoeII firmware sets it up as 8 bytes, so we have to set this field to 8. The iManufacturer, iProduct and iSerialNumber fields are indexes pointing to strings (that are defined in lines 40 - 44).

    • Line 17-25: Here we create our two endpoints: EP1 BULK IN (which means data flows from device to host, so we'll call this WRITE) and EP2 BULK OUT (which means data flows from host to device so we'll call this READ). The class constructor:
      public Endpoint(byte EndpointAddress, byte Attributes)

      . The EndpointAddress indicates the physical endpoint number. The attributes indicate the type of transfer, the type of synchronization and the usage type for the endpoint.
      After creating the two endpoints we store them in an array of endpoints so we can include them in the UsbInterface:

    • Line 28-34: The UsbInterface class constructor has the following signature:
      public UsbInterface(byte InterfaceNumber, Microsoft.SPOT.Hardware.UsbClient.Configuration.Endpoint[] Endpoints)

      The parameter InterfaceNumber allows us to define multiple interfaces with a different set of endpoints. In this case I define only 1 interface with 2 endpoints in the array. We set the InterfaceClass and InterfaceSubClass again to "vendor defined" and we put our interface in an array so we can include them in the configuration descriptor:

    • Line 37: The ConfigurationDescriptor class constructor has the following signature:
      public ConfigurationDescriptor(ushort MaxPower_mA, Microsoft.SPOT.Hardware.UsbClient.Configuration.UsbInterface[] Interfaces)

      The parameter MaxPower_mA is used to set the maximum power in mA. The TahoeII draws an absolute maximum of 180 mA, so that's what we set. The 2nd parameter holds our interfaces.

    • Line 40-44: Here we create our string descriptors that we point at in line 12 - 14.
    • Line 46-57: After creating the various descriptors we can now put them all together in the Configuration class.
    • Line 59-71: And finally we apply the configuration and start the USB controller.
  10. Now that we've finished that function, we can continue adding code to the end of the Main() function:
    UsbStream usbStream = null;
    try
    {   // Configure the USB controller
        if (ConfigureUSBController(usbController))
            usbStream = usbController.CreateUsbStream(WRITE_EP, READ_EP);
        else
            throw new Exception();
    }
    catch (Exception)
    {
        Debug.Print("USB stream could not be created, error " + usbController.ConfigurationError.ToString());
        return;
    }

    The code above calls the ConfigureUSBController function we created before and creates the USB stream.

  11. All we want our program to do is echo back whatever our application running on the PC sends to it. Add the following function to the Program class:
    static void USBLoop(UsbStream usbStream)
    {
        byte[] readData = new byte[100];
        for (; ; )
        {
            int bytesRead = usbStream.Read(readData, 0, readData.Length);
            if (bytesRead > 0)
            {   // echo back!
                usbStream.Write(readData, 0, bytesRead);
            }
        }
    }
  12. And finally we'll call this never ending loop by adding the following code to the end of the Main() function:
    // Start our communication loop
    USBLoop(usbStream);
    usbStream.Close();

    Of course the usbStream.Close() function will never be reached in our case, but we could add code to return from the loop if "Quit" is received for instance...

  13. Now that our program is finished we have to set up the correct debug transport to the TahoeII. Before switching the TahoeII to Ethernet you'll have to configure the Ethernet settings using MFDeploy:
    • Make sure the TahoeII is using USB as a debug transport by holding SW3 while resetting the TahoeII
    • Now start MFDeploy and select "USB" from the Device dropdown. The TahoeII should immediately pop up (as "Meridian_XXXXXXXX"). Ping the device to make sure you can reach it.
    • From the Target menu, click "Configuration" and select "Network".
    • Configure the TahoeII to match your network (set a static IP and subnetmask within range of your PC's IP or enable DHCP if you have a DHCP server on your network)
    • Now set the TahoeII to use TCP/IP as debug transport by holding SW4 while resetting the device.

    If the TahoeII is setup for TCP/IP we have to setup Visual Studio 2008 to use TCP/IP as well: Right click on your project in the solution explorer, choose "Properties" and click on the ".NET Micro Framework" tab. Select "All Configurations" from the "Configuration" dropdown list, set "Transport" to "TCP/IP" and you should see the device's IP and MAC address appearing in the "Device" dropdown list. Select the correct one (if you have more than one TahoeII connected) and close this window.

If all is well you can now press F5 to build and deploy the program. If you keep a close eye on your taskbar notification area you'll see the following notifications pop up:

And after that XP will ask you for the driver:

Since we'll be using WinUSB as the driver, all we need to do now is create the .inf file for our device. The document "How to Use WinUSB to Communicate with a USB Device" explains in detail how to do exactly that. If we modify the sample from that document according to the instructions we'll end up with an inf that looks like this:

[Version]
Signature   = "$Windows NT$"
Class       = MicroFrameworkWinUSB
ClassGuid   = {F80EAFB4-8422-446d-BC18-63B1BC21843E}
Provider    = %ProviderName%
CatalogFile = MFWinUSB.cat
DriverVer   = 05/30/2009,1.0.0.0

; ========== Manufacturer/Models sections ===========
[Manufacturer]
%ProviderName% = MicroFramework_WinUSB,NTx86,NTamd64,NTia64

[MicroFramework_WinUSB.NTx86]
%USB\MFWinUSB.DeviceDesc% = USB_Install, USB\VID_DEAD&PID_0001

[MicroFramework_WinUSB.NTamd64]
%USB\MFWinUSB.DeviceDesc% = USB_Install, USB\VID_DEAD&PID_0001

[MicroFramework_WinUSB.ntia64]
%USB\MFWinUSB.DeviceDesc% = USB_Install, USB\VID_DEAD&PID_0001

; =================== Installation ===================
;[0]
; If the class used in the Version section above is not a standard one
; (see http://msdn.microsoft.com/en-us/library/ms791134.aspx)
; then it  must install information about the device class so it is displayed
; and processed correctly by various tools like the device manager, WMI, etc...
[ClassInstall32]
AddReg = MicroFrameworkWinUSB_addreg
 
[MicroFrameworkWinUSB_addreg]
HKR,,,,%ClassName%
HKR,,Icon,,"18"

;[1]
[USB_Install]
Include = WinUSB.inf
Needs   = WinUSB.NT

;[2]
[USB_Install.Services]
Include    = WinUSB.inf
AddService = WinUSB,0x00000002,WinUSB_ServiceInstall

;[3]
[WinUSB_ServiceInstall]
DisplayName   = %WinUSB_SvcDesc%
ServiceType   = 1
StartType     = 3
ErrorControl  = 1
ServiceBinary = %12%\WinUSB.sys

;[4]
[USB_Install.Wdf]
KmdfService      = WinUSB, WinUSB_Install

[WinUSB_Install]
KmdfLibraryVersion = 1.7

;[5]
[USB_Install.HW]
AddReg = Dev_AddReg

[Dev_AddReg]
HKR,,DeviceInterfaceGUIDs,0x10000,"{77C99034-2428-424a-8130-DC481841429B}"

;[6]
[USB_Install.CoInstallers]
AddReg    = CoInstallers_AddReg
CopyFiles = CoInstallers_CopyFiles

[CoInstallers_AddReg]
HKR,,CoInstallers32,0x00010000,"WinUSBCoInstaller.dll","WdfCoInstaller01007.dll,WdfCoInstaller"

[CoInstallers_CopyFiles]
WinUSBCoInstaller.dll
WdfCoInstaller01007.dll

[DestinationDirs]
CoInstallers_CopyFiles = 11

; ================= Source Media Section =====================
;[7]
[SourceDisksNames]
1 = %DISK_NAME%,,,\x86
2 = %DISK_NAME%,,,\amd64
3 = %DISK_NAME%,,,\ia64

[SourceDisksFiles.x86]
WinUSBCoInstaller.dll   = 1
WdfCoInstaller01007.dll = 1

[SourceDisksFiles.amd64]
WinUSBCoInstaller.dll   = 2
WdfCoInstaller01007.dll = 2

[SourceDisksFiles.ia64]
WinUSBCoInstaller.dll   = 3
WdfCoInstaller01007.dll = 3

; =================== Strings ===================
[Strings]
ProviderName            = "MicroFramework WinUSB"
USB\MFWinUSB.DeviceDesc = "MicroFramework WinUSB Device"
WinUSB_SvcDesc          = "MicroFramework WinUSB"
DISK_NAME               = "MicroFramework WinUSB Driver Disk"
ClassName               = "MicroFramework WinUSB Devices"

If you don't use one of the System-Supplied Device Setup Classes you'll have to add a ClassInstall32 section to the inf that adds the class name to the registry. Even though our device would seem to fit the USB class we can't use that class because it's only meant for USB Host controllers and hubs (not for USB peripherals).

Note that if you want to ship a device using the above inf you'll have to change the following:

  • Class: Change to something meaningful for your device
  • ClassGuid: Generate a new GUID for the class
  • CatalogFile: Change to something meaningful for your device
  • DriverVer: Change to something meaningful for your device
  • Manufacturer/Models sections: Change to something meaningful for your device and make sure the VID/PID matches your device.
  • [Dev_AddReg]: Generate a new GUID for the device
  • [Strings]: Change to something meaningful for your device

With this inf file we can now create our driver package. The drivers and tools required are all in the Windows Driver Kit, so you'll need to install that first.

After you have installed the WDK create a new folder, let's say C:\MFWinUSBDriver, and create the inf file in that folder. Now open a build environment matching your operating system by clicking the appropriate shortcut in the "Windows Driver Kits" start menu group. Now "cd" (change directory) to C:\MFWinUSBDriver.

The next step is to create a catalog file out of our inf. The process of creating a catalog file (and signing) is described in "Kernel-Mode Code Signing Walkthrough" and the tool to use is Inf2Cat. If you are planning on shipping your driver to your customers you should properly sign your catalog. For the sake of simplicity this blogpost will not discuss official signing (but will focus on getting the driver to load asap!).

As you can see, the inf file lists a couple of dll's. These dll's are part of the WDF redistributables that can be found in the WDK at this location: C:\WinDDK\6001.18002\redist

Copy the amd64, ia64 and x86 folders from C:\WinDDK\6001.18002\redist\wdf to C:\MFWinUSBDriver so that you now have the following 3 folders (with the corresponding driver files in those folders):

C:\MFWinUSBDriver\amd64
C:\MFWinUSBDriver\ia64
C:\MFWinUSBDriver\x86

Now copy the amd64, ia64 and x86 folders from C:\WinDDK\6001.18002\redist\winusb to C:\MFWinUSBDriver and click "Yes to All" in the "Confirm Folder Replace" dialog.

You should now have the following files in those 3 folders listed above:

WdfCoInstaller01007.dll
WdfCoInstaller01007_chk.dll
WinUSBCoInstaller.dll
WinUSBCoInstaller_chk.dll
WUDFUpdate_01007.dll
WUDFUpdate_01007_chk.dll

The _chk files are for checked (debug) builds of the operating system and since we won't be using those you can delete all the _chk files from the 3 folders listed above.
You can also remove the WUDFUpdate*.dll files because we are going to be talking to the WinUSB driver directly from our application. If you want to write a UMDF driver instead you'll need to add the UMDF coinstaller and have all of the appropriate entries in the inf (like UmdfServiceOrder). The ZIP file contains an example inf file for UMDF as well.

Now that everything is in place we can finally call Inf2Cat:

inf2cat /driver:C:\MFWinUSBDriver /os:XP_X86,Server2003_X86,Vista_X86,XP_X64,Server2003_X64,Vista_X64,Server2003_IA64

If all went well the inf2cat output should show:

............................
Signability test complete.

Errors:
None

Warnings:
None

Catalog generation complete.
C:\MFWINUSBDRIVER\mfwinusb.cat

If you still have the "Found New Hardware Wizard" open you can now point the wizard to C:\MFWinUSBDriver and have it install the driver. If you don't have the wizard open anymore; go to the Device Manager, delete the unknown USB device from the "Other devices" node and re-attach (or reset) the TahoeII.





Now everything is ready and we can start the development of the PC application. Fortunately Jan Axelson already developed an application in C# that can talk to devices utilizing WinUSB. She provides full source code and it's a real good starting point for your own applications. Download the latest version from her WinUSB page.

Download and extract winusb_cs_xxx.zip to a folder of your choice and load the solution in Visual Studio 2008. Now open the frmMain.cs code and change the WINUSB_DEMO_GUID_STRING to match our inf file: "{77C99034-2428-424a-8130-DC481841429B}".

Now press F5 to run the solution. Enter some text in the "Bulk Transfers" textbox and click on the "Send" button:

Eureka! The TahoeII is echoing back whatever we send to it. You can download the MicroFramework code and the MFWinUSB Driver here.

I'll try to follow up this article with an article showing how to setup the TahoeII as a USB Communication Device Class (CDC) so that you can communicate with your device without using WinUSB (and without having to sign your "driver").


Good luck in creating your next USB connected device running the MicroFramework!

Some portions of the text in this article were taken (and modified/updated/added to where necessary) from "How to Use WinUSB to Communicate with a USB Device".

Creating an OS Design containing VoIP in CE R2

If you try to sysgen a Windows CE R2 OS Design that contains the VoIP components you'll encounter an "error in sysgen phase" when sysgenning FP_VOIP. Build.log will show the error occurred when trying to link phsettings.exe:

phsettings.exe : fatal error LNK1120: 4 unresolved externals

The reason for this is an error in the "sources." file of \WINCE600\PUBLIC\FP_VOIP\OAK\PHONE\COMMON\UTILS:

SSOURCES= \
    DisplayItem.cpp      \
    GDICache.cpp         \
    LogoScreen.cpp       \
    LineHelper.cpp       \
    NetworkUtils.cpp     \
    PaintHelper.cpp      \
    Poom.cpp             \
    RingtoneIterator.cpp \
    SecurityUtils.cpp  

\
    SystemTimeUtils.cpp  \
    Timers.cpp           \
    TimeUtilities.cpp

No files after SecurityUtils.cpp will be built because of the extra return (actually a couple of funny characters messing up the build parser). The shipped libraries were built using this faulty "sources." file, hence the unresolved external symbols when phsettings links to voip_common.lib.

What I'm going to tell you now is against all my beliefs: To (quickly) fix this, you'll have to modify some files in the PUBLIC tree (did I really say that?!)... Well, at least until Microsoft releases the QFE (I've opened a support incident with Microsoft and they are working on fixing this bug at this very moment).

First copy \WINCE600\PUBLIC\FP_VOIP to \WINCE600\ORG_PUBLIC_FP_VOIP (so that you can restore the original FP_VOIP folder when Microsoft releases the QFE).

Now change the "sources." file in \WINCE600\PUBLIC\FP_VOIP\OAK\PHONE\COMMON\UTILS to match this:

!if 0
Copyright (c) Microsoft Corporation.  All rights reserved.
!endif
!if 0
Use of this sample source code is subject to the terms of the Microsoft
license agreement under which you licensed this sample source code. If
you did not accept the terms of the license agreement, you are not
authorized to use this sample source code. For the terms of the license,
please see the license agreement between you and Microsoft or, if applicable,
see the LICENSE.RTF on your install media or the root of your tools installation.
THE SAMPLE SOURCE CODE IS PROVIDED "AS IS", WITH NO WARRANTIES.
!endif

TARGETNAME=voip_common
TARGETTYPE=LIBRARY
RELEASETYPE=OAK
WINCEOEM=1

WINCEATL80=1

INCLUDES=\
    ..\inc; \
    ..\..\inc; \
    $(_PUBLICROOT)\wceappsfe\sdk\inc; \
    ..\ResourceUtils; \
    $(_PUBLICROOT)\common\ddk\inc;

SOURCES= \
    DisplayItem.cpp      \
    GDICache.cpp         \
    LogoScreen.cpp       \
    LineHelper.cpp       \
    NetworkUtils.cpp     \
    PaintHelper.cpp      \
    Poom.cpp             \
    RingtoneIterator.cpp \
    SecurityUtils.cpp    \
    SystemTimeUtils.cpp  \
    Timers.cpp           \
    TimeUtilities.cpp    

Once you've done this, browse to PUBLIC\FP_VOIP in the Solution Explorer (in Visual Studio 2005 with your OS Design open), right click on FP_VOIP and choose "Rebuild" from the context menu. After the rebuild has completed you can sysgen your OS Design successfully.

Unfortunately when you run your kernel you will encounter some more bugs in FP_VOIP... When you add the portrait resources the settings application (phsettings.exe) won't start because it can't find the correct menu structure (breaks at networkdnssettings.cpp line 330 (function NetworkDNSSettingsDialog_t::CreateSpinnerDisplayItem) and when you add the landscape resources the settings application will run correctly, but the homescreen application won't show any text on the buttons (and the buttons don't work).

So, the out-of-the-box applications don't work as expected, but again, Microsoft will release a QFE fixing these issues soon. In the meantime you can play around with it (just make sure you select the landscape resources) by manually starting phsettings.exe so you can setup VoIP. You can even make a phone call (wow! ;), but you'll have to manually start the correct applications.

Seems like this R2 VOIP stuff was never really tested...

CE XAML

I've worn out the newsgroups...

Yes, it's official: I've posted too many answers to the newsgroups in the past 9 years.

They blacklisted me! No, just kidding (I hope). I'm not sure what is going on, but the reason for me being really quiet on the newsgroups is because my posts seem to be blocked by the NNTP server...

If I try to post using any newsreader (I use Thunderbird but also tried Agent) my posts get sent correctly but never appear on the server. If I try to post using the web front-end on microsoft.com, my posts do show up on the web front-end but again never make it to the NNTP server (so they are not being propagated to my (and your) newsreader and also don't appear in Google's Group archive/DejaNews). I've even tried the shadowing news server of my ISP provider. My posts do show up on the news server of my provider, but again don't make it to the news.microsoft.com server.

For example these posts are on the web, but not on the NNTP server:
how to use .CAB file provided by driver vendor
USB unmount in CE 6.0
QFE updates
Strange PM timeouts behaviour

And since nobody is responding to my posts I can only conclude nobody is actually using the web front-end on microsoft.com, well, except for this user (because he responded to my post, yeah!):
Parallel port interrupt

So it all points to me being blacklisted... Maybe there's a spam rule: "if [user's post count] > 10000; "it must be a spambot; block it!"

Microsoft is looking into it, so hopefully I'm back soon...

If you have seen the same behavior or may know what's going on, please leave a comment or send me an email (through the contact form on this site)

UPDATE: It indeed seems to be a spam filter thing. I tried changing my email address but that didn't seem to help. Then I tried changing my "Display Name" as well and now I can post again. If you find yourself unable to post; change both your email address (you don't want to use your real email address on NG's anyway because of email-harvester-bots) and your display name and you should be good to go again!

Programmatically instantiate the password login dialog

In Windows CE there is built in functionality that allows a user to lock and login the system, just like you are used to on "big" desktop systems. To activate this functionality you simply set the system password in Windows CE through the control panel applet. You can also do this programmatically using the SetPassword() and SetPasswordStatus() API's.

When the password is set at boot the system will show the standard Windows CE login screen:

Recently I got a question whether it is possible to programmatically bring up the password dialog. After some analysis the answer was yes. I found out that the Power Manager instantiates the password dialog when the system returns from showing a screensaver. The password dialog is instantiated by calling ShowStartupWindow(). Here's the code used by the Power Manager (from "<WINCEROOT>\PUBLIC\COMMON\OAK\DRIVERS\PM"):

hmCoreDll = (HMODULE) LoadLibrary(_T("coredll.dll"));
gpfnShowStartupWindow = (PFN_ShowStartupWindow) GetProcAddress(hmCoreDll, _T("ShowStartupWindow"));

The example below shows you how to use the exact same technique inside your applications to instantiate the password dialog:

#include <windows.h>

// We need to declare these functions here because they are defined
// in pwinbase.h (contrary to what MSDN help for "SetPassword" tells
// us!) and pwinbase.h is not included as an SDK header file.
// Therefore to use these functions without needing access to all
// Platform Builder include files, we have to define them ourselves.
extern "C"
{
    BOOL SetPassword (LPWSTR lpszOldpassword, LPWSTR lspzNewPassword);
    BOOL SetPasswordStatus(DWORD dwStatus, LPWSTR lpszPassword);
}

// Forward declarations
bool InitPassword(LPWSTR pszOldPassword, LPWSTR pszNewPassword);

// Defines
#define PASSWORD_STATUS_ACTIVE              1
#define PASSWORD_STATUS_SCREENSAVERPROTECT  2

// Typedef'ed functions
typedef BOOL (WINAPI *PFN_ShowStartupWindow)(void);

int _tmain(int argc, _TCHAR* argv[])
{
    BOOL bRet = false;
    HMODULE hCoreDll = NULL;
    PFN_ShowStartupWindow pfnShowStartupWindow = NULL;

    // Create and activate password
    // (you can supply the current ("old") password on the command line)
    LPWSTR pszOldPassword = (argc > 1) ? argv[1] : NULL;
    if (InitPassword(pszOldPassword, L"password"))
    {
        hCoreDll = (HMODULE) LoadLibrary(L"coredll.dll");
        if (hCoreDll)
        {
            pfnShowStartupWindow = (PFN_ShowStartupWindow)GetProcAddress(hCoreDll, L"ShowStartupWindow");
            if (pfnShowStartupWindow)
                bRet = pfnShowStartupWindow();
            else
                RETAILMSG(1, (L"Failed to get address of \"ShowStartupWindow\" in coredll.dll, error %d\r\n", GetLastError()));
            FreeLibrary(hCoreDll);
        }
        else
            RETAILMSG(1, (L"Failed to load coredll.dll, error %d\r\n", GetLastError()));
    }
    return (int)bRet;
}

bool InitPassword(LPWSTR pszOldPassword, LPWSTR pszNewPassword)
{
    bool bRet = false;
    // Please note that the password must be lower case when you set it!
    // The password dialog box (code in startui.cpp) always converts the
    // user entered password to lower case before the compare!
    if (!SetPassword(pszOldPassword, pszNewPassword))
        RETAILMSG(1, (L"Failed to set the password, error %d\r\n", GetLastError()));
    //Set the password status
    else if (!SetPasswordStatus(PASSWORD_STATUS_ACTIVE | PASSWORD_STATUS_SCREENSAVERPROTECT, pszNewPassword))
        RETAILMSG(1, (L"Failed to set the password status, error %d\r\n", GetLastError()));
    else
        bRet = true;
    return bRet;
}

Of course you can always create your own password dialog (because let's be honest; it's not the most beautiful dialog you've ever seen, right?!), but if you don't mind the ugly box the technique above just uses the existing functionality of CE and will save you a bit of time developing a password dialog yourself.

From the comments in the code above you can see I found a rather interesting little fact: Windows CE passwords are CASE INSENSITIVE! That's old-school, isn't it?!

The code that handles the GUI portion of the password handling code is in startui.cpp (here "<WINCEROOT>\PUBLIC\COMMON\OAK\DRIVERS\STARTUI"):

//      Get text from password window.
SendMessage(hwndPass, WM_GETTEXT, PASSWORD_LENGTH + 1, (LPARAM)szText);
_wcslwr(szText);
isAuthValid = (CheckPassword(szText) != FALSE);

The _wcslwr function converts the password string to lowercase before checking it, but SetPassword allows you to set a password using mixed case! Keep that in mind next time you locked yourself out of your Windows CE device... ;o)

PB 5.0 crash hang dead

Today all of a sudden Platform Builder 5.0 didn't want to load my workspace anymore. Just before I updated the machine using Windows Update so I figured it must have had something to do with that. Platform Builder would start loading the workspace, show "Refreshing the Catalog" in the status bar, then show "Getting variables that correspond with catalog items" and then it would just sit there consuming 50% CPU and not responding to anything else. Luckily I'm using VMWare for all my CE development work so it was easy to revert back to an earlier snapshot before I updated XP. Unfortunately, this didn't solve the problem! I continued my search and after some frustrating hours I found the cause:

A bit earlier in the day I added a whole lot of header files (121 to be precise) to one of my subprojects sources file (using the FILE_VIEW_INCLUDES_FOLDER macro), and this proved to be the problem. After I reverted the sources file back to its previous revision in SVN the project loaded again.

Another one of those Platform Builder 5.0 mysteries solved...

Manual Clone of Public Code

If sysgen_capture doesn't work for some reason you can always manually clone the component you want to modify. By following the instructions on the blog post "Cloning Public Code: An Example" you should be able to get this working, but to help you a bit more here are some step by step instructions for manually cloning public code.

In this blogpost we'll be cloning TELNETD because for some reason sysgen_capture doesn't work for this component. TELNETD is located under the WINCE project tree "servers". I know the exact name of the WINCEPROJ by opening up the makefile in \PUBLIC\SERVERS\CESYSGEN and searching for WINCEPROJ. The correct sysgen_capture command for TELNETD would be:

sysgen_capture -p servers telnetd

The -p parameter indicates the WINCEPROJ, so in this case it's of course not "common", but "servers". Unfortunately for some reason this command doesn't output a sources.telnetd file (in fact it doesn't output any file!). No stress, let's just clone manually:

  1. Copy the TELNETD folder to your OS Design (\WINCE500\PBWorkspaces\<MyOSDesign>\TELNETD or \WINCE600\OSDesigns\<MyOSDesign>\<MyOSDesign>\TELNETD)
  2. Open the sources. file in the folder you just copied
  3. Set TARGETTYPE to DYNLINK
  4. Set RELEASETYPE to LOCAL
  5. Delete WINCETARGETFILE0=$(_RELEASELIBDIR)\$(TARGETDEFNAME).def
  6. Open the makefile. in <WINCEROOT>\PUBLIC\SERVERS\CESYSGEN and search for telnetd
  7. Copy the line @set TARGETLIBS... and @set DLLENTRY...
  8. Paste into your sources. file
  9. Change @set TARGETLIBS... in your sources. file to:
    TARGETLIBS=$(_PROJECTROOT)\cesysgen\sdk\lib\$(_CPUINDPATH)\ws2.lib \
               $(_PROJECTROOT)\cesysgen\sdk\lib\$(_CPUINDPATH)\coredll.lib \
               $(_PROJECTROOT)\cesysgen\sdk\lib\$(_CPUINDPATH)\ceosutil.lib  \
               $(_PROJECTROOT)\cesysgen\sdk\lib\$(_CPUINDPATH)\authhlp.lib
  10. Change @set DLLENTRY... in your sources. file to:
    DLLENTRY=DllEntry
  11. Add the include paths:
    _OEMINCPATH=$(_WINCEROOT)\public\common\oak\inc; $(_WINCEROOT)\public\common\sdk\inc; $(_WINCEROOT)\public\common\ddk\inc

    NOTE: DO NOT PUT SPACES BETWEEN THE PATHS! I know it is confusing because I do it above, but I have to to support line breaking (otherwise this page will look ugly...).

  12. And build...

Easy as that! Now that you have the TELNETD component successfully cloned you can continue following the instructions from step 7 in the blog post "Cloning Public Code: An Example" (of course replacing "netui" with "telnetd" in the text).

Here's the complete sources. file for reference (don't forget to delete the spaces in the _ISVINCPATH, _OEMINCPATH and INCLUDES macros! I need those spaces here to format the HTML nicely, but you will get compiler error "/I" requires an argument if you leave those spaces):

WINCEOEM=1
_OEMINCPATH=$(_WINCEROOT)\public\common\oak\inc; $(_WINCEROOT)\public\common\sdk\inc; $(_WINCEROOT)\public\common\ddk\inc

TARGETNAME=telnetd
TARGETTYPE=DYNLINK
RELEASETYPE=LOCAL

TARGETDEFNAME=$(TARGETNAME)
DEFFILE=$(TARGETNAME).def
CDEFINES=$(CDEFINES) -DwinCE
DLLENTRY=DllEntry

SOURCES=telnetd.cpp telndev.cpp consemu.cpp

TARGETLIBS=$(_PROJECTROOT)\cesysgen\sdk\lib\$(_CPUINDPATH)\ws2.lib \
           $(_PROJECTROOT)\cesysgen\sdk\lib\$(_CPUINDPATH)\coredll.lib \
           $(_PROJECTROOT)\cesysgen\sdk\lib\$(_CPUINDPATH)\ceosutil.lib  \
           $(_PROJECTROOT)\cesysgen\sdk\lib\$(_CPUINDPATH)\authhlp.lib

Cloning public code: An example

For some strange reason, people are still changing public code and doing build and sysgen's on their Windows CE tree (read this to learn why this is a bad thing). Changing code in the PUBLIC and PRIVATE trees might seem like a shortcut but actually it is everything but a shortcut. When you clone the code before changing it in place you will save yourself hours and hours of build time. A targeted build of a cloned driver takes seconds, a clean build and sysgen takes hours.

Cloning is not difficult at all, so there is really no reason to change PUBLIC or PRIVATE code. To prove that it is not difficult (once you know what you are doing) I will show you how to clone PUBLIC code in this blog post. Note that there is an article by Steve Maillet and Mike Hall in MSDN about cloning public code too. They split the clone into a "LIB" part and a "DLL" part. I don't really like that method; I just merge the sources file into one that creates the DLL in one go. Have a look at the "split" method here.

I chose to clone NETUI, since this component is responsible for a lot of dialog boxes that you may want to change to match your systems specifications.

I am cloning NETUI on a Windows CE 6.0 tree, but it works exactly the same on a Windows CE 5.0 tree (apart from the OSDesign folder which is called PBWorkspaces in CE 5.0).

Prerequisites: A sysgenned OSDesign with the "Network UI" component included.

Let's start!

  1. Copy the entire \WINCE600\PUBLIC\COMMON\OAK\DRIVERS\NETUI folder to your OSDesign, for instance \WINCE600\OSDesigns\MyOS\MyOS
  2. Open a build window (in Platform Builder / VS2005 click "Open Release Directory in Build Window" from the menu "Build"
  3. In the build window, type:
    cd..
    cd..
    cd netui
  4. Now type:
    sysgen_capture -p common netui

    This command will output a set of files in the current directory.

  5. Since we only want to build netui, you can delete:
    sources.btdrt
    sources.ceddk
    sources.iphlpapi
    sources.k.iphlpapi
    sources.ws2
    sources.ws2k
  6. Now intelligently merge sources. with sources.netui (we make the changes in the sources. file):
  • Make a backup of the sources. file (copy to sources.org) so you can always go back if needed
  • We are trying to build a DLL, so change TARGETTYPE=LIBRARY to TARGETTYPE=DYNLINK
  • We are building NETUI as a project in our workspace, so set the RELEASETYPE to LOCAL (RELEASETYPE=LOCAL)
  • We are not building a LIB anymore, so there's no need to preprocess the def file. Remove the following lines:
    WINCETARGETFILE0=$(_RELEASELIBDIR)\$(TARGETDEFNAME).def
    PREPROCESSDEFFILE=1
  • And add this line to tell the build system to use the local def file for the exports:

    DEFFILE=$(TARGETDEFNAME).def
  • We don't need the resource file later (because we are not creating a lib that has to be transformed into a dll later), so remove the COPYRES=1 line and the WINCETARGETFILES line pointing to the res.
  • OSDesign SubProjects get build last, so we don't need to copy SYNCHRONIZE_DRAIN=1 from sources.netui to our sources. file
  • Since we are now building a DLL, copy the DLLENTRY line from sources.netui to sources.
  • Now move the entire SOURCELIBS and TARGETLIBS from sources.netui to sources.
  • and delete any references to netui (netui.lib/netui.res) itself (because we are building that now)
  • Change the paths to the target libraries from using the PUBLIC sysgened libraries to the libraries sysgened to your workspace, so change $(_SYSGENSDKROOT)\ to $(_PROJECTROOT)\cesysgen\sdk\ and $(_SYSGENOAKROOT)\ to $(_PROJECTROOT)\cesysgen\oak\
  • You can also delete the #xref lines, they are remains of older days (CE 2.11 era).
  • Now try to build (by typing "build" in the build window you still have open)
  • oops! Doesn't build. It can't find the standard include files... Add the following to the top of the sources file:
    _ISVINCPATH=$(_WINCEROOT)\public\common\sdk\inc;
    _OEMINCPATH=$(_WINCEROOT)\public\common\ddk\inc; $(_WINCEROOT)\public\common\oak\inc; $(_WINCEROOT)\public\common\sdk\inc;

    NOTE: DO NOT PUT SPACES BETWEEN THE PATHS! I know it is confusing because I do it above, but I have to to support line breaking (otherwise this page will look ugly...).

  • And since this project is building OEM code (a kernel component) set WINCEOEM=1 to indicate we want to use the _OEMINCPATH (so if you really want to you can delete the _ISVINCPATH since we don't use that one).
  • Try to build again:
    BUILD: [01:0000000048:ERRORE] \WINCE600\OSDesigns\MyOS\MyOS\NETUI\.\btmgmtui.cpp(39) : fatal error C1083: Cannot open include file: '../bluetooth/sample/btenum/btenum.hxx': No such file or directory

    So, one of the sources files is trying to include a header on a relative path. Since NETUI came from \WINCE600\PUBLIC\COMMON\OAK\DRIVERS\NETUI this path must be \WINCE600\PUBLIC\COMMON\OAK\DRIVERS\BLUETOOTH

    Now there are 3 ways to solving this: Either add the \WINCE600\PUBLIC\COMMON\OAK\DRIVERS\NETUI folder to the include path (add INCLUDES=$(INCLUDES);\WINCE600\PUBLIC\COMMON\OAK\DRIVERS\NETUI) so that the header file can be found off the relative path, or change the source file including the header to use a full path to the header file, or copy the btenum.hxx file into the NETUI folder and change the source file including the header to use the local header file. I think the last option is the cleanest, so let's copy \WINCE600\PUBLIC\COMMON\OAK\DRIVERS\BLUETOOTH\SAMPLE\BTENUM\btenum.hxx to \WINCE600\OSDesigns\MyOS\MyOS\NETUI\btenum.hxx and change btmgmtui.cpp to #include "btenum.hxx"

  • Build again: 0 Warnings,  0 Errors. Whuuhooo!
  • Now there is one final thing we have to add to the NETUI sources. file. We have to tell the build system we want to copy the final binary to the _FLATRELEASEDIR so our netui.dll will overwrite the default one from the PUBLIC\COMMON folder. Add WINCEREL=1 to the top of the sources. file.
  • Now add the NETUI project to your workspace. In the Platform Builder VS IDE click "Add Existing Subproject..." from the "Project" menu. Browse to the \WINCE600\OSDesigns\MyOS\MyOS\NETUI folder, select "Sources/Dirs Files" from the "Files of type" dropdown list, select the sources. file, and click "Open"
  • In the Solution Explorer window you can now find your NETUI component under the "SubProjects" node. Right click NETUI and click "Rebuild" to see if you can build the project from the IDE. It should all build fine.
  • If all builds fine, you can delete the sources.netui and the sources.org files since you no longer need them now.
  • The "Insert existing project" action created a couple of files for you. One of those files is a bib file. BIB files are used to indicate which files need to be included in the image. Since we cloned NETUI, but did not remove it from the OSDesign features, we now have 2 references to NETUI in the combined BIB files (one is in COMMON.BIB and the other in our newly generated NETUI.BIB). We don't need an extra reference in the BIB files, all we want is overwrite the default NETUI.DLL in the _FLATRELEASEDIR, so you can remove this reference from the newly generated NETUI.bib.
  • Now you can change whatever you need to change in NETUI without worrying about messing up the PUBLIC portion of the WINCE tree or having to do a "Clean Build and Sysgen".

    If you have followed all the steps above your final NETUI sources. file should look like this (except the spaces in the _ISVINCPATH, _OEMINCPATH and INCLUDES macros!):

    _ISVINCPATH=$(_WINCEROOT)\public\common\sdk\inc;
    _OEMINCPATH=$(_WINCEROOT)\public\common\ddk\inc; $(_WINCEROOT)\public\common\oak\inc; $(_WINCEROOT)\public\common\sdk\inc;

    WINCEOEM=1
    WINCEREL=1

    TARGETNAME=netui
    TARGETTYPE=DYNLINK
    RELEASETYPE=LOCAL

    TARGETDEFNAME=$(TARGETNAME)
    DEFFILE=$(TARGETDEFNAME).def

    DLLENTRY=_DllEntryCRTStartup

    CONDITIONAL_INCLUDES=prshtp.h

    SOURCELIBS=

    TARGETLIBS=\
        $(_PUBLICROOT)\common\oak\lib\$(_CPUINDPATH)\btenum.lib \
        $(_PROJECTROOT)\cesysgen\sdk\lib\$(_CPUINDPATH)\iphlpapi.lib \
        $(_PROJECTROOT)\cesysgen\oak\lib\$(_CPUINDPATH)\btdrt.lib \
        $(_PROJECTROOT)\cesysgen\sdk\lib\$(_CPUINDPATH)\ws2.lib \
        $(_PROJECTROOT)\cesysgen\oak\lib\$(_CPUINDPATH)\ceddk.lib \
        $(_PROJECTROOT)\cesysgen\sdk\lib\$(_CPUINDPATH)\coredll.lib

    SOURCES= \
        netui.c \
        getip.c \
        ipaddr.c \
        getuser.c \
        linecnfg.c \
        wnet.c \
        netcard.c \
        certui.cpp  \
        showcert.cpp \
        eaptlscfg.cpp \
        network.c \
        transdlg.c \
        reg.c \
        util.c \
        wzcui.c \
        wzcprops.c \
        btmgmtui.cpp \
        WzcQuickCfgUi.c \
        IpQuickCfgUi.c \
        QuickConfigUi.c \
        WzcLogging.c \
        WzcPopup.c \
        netui.rc  \
        wnet_wrapper.cpp \
        netui_wrapper.cpp \
        getuser_wrapper.cpp \
        gwes_wrapper.cpp \
        getip_wrapper.cpp \
        linecnfg_wrapper.cpp \
        transdlg_wrapper.cpp \
        network_wrapper.cpp \
        netcard_wrapper.cpp

    FILE_VIEW_PARAMETER_FOLDER= \
        NETUI.bib \
        NETUI.reg \
        NETUI.dat \
        NETUI.db \
        ProjSysgen.bat \

    FILE_VIEW_ROOT_FOLDER= \
        prelink.bat \
        postlink.bat \

    PRELINK_PASS_CMD=prelink.bat
    POSTLINK_PASS_CMD=postlink.bat

    Filtering dat files

    If you read this blog or the CE newsgroups you should by now know you should never change any files in the PUBLIC or PRIVATE folders of your WINCE installation tree. So what if you want to change a setting in common.reg (which is located in the PUBLIC folder)? Well, add the same entry to your platform.reg (in your PLATFORM\BSP folder) and set it to whatever you like. So, what if you want to delete a setting from common.reg? Again, add the same entry to your platform.reg but precede the key name with a "-". This works because platform.reg is merged into reginit.ini after common.reg, so any duplicate entries or deletion commands (the "-") will override the settings in common.reg.

    So, what about dat files? Unfortunately, deleting dat entries with the "-" tag is not supported. Dat files are responsible for the initial copy of files and creation of folders during boot. For instance, dat files are used to copy shortcuts from the \Windows folder to the desktop of your CE device. Now what if you don't want to have icons on your desktop? The only way that seems possible is to actually change the public dat files in place, but you know you should never change any files in the PUBLIC tree! We just have to come up with a different solution...

    The solution in this case is to filter the dat files and remove any unwanted lines before creating the final image but after the sysgen phase and without touching the original files in PUBLIC. Windows CE calls out to a number of batch files during the makeimg process to let you write your own scripts to affect the image at different points during the image building process. One of those batch files is "PostFmergeObj.bat" which, as the name suggests, is called just after fmerge merged the "Obj" files (which are in fact "Dat" files... confused already? ;o) into your FLATRELEASEDIR.

    By using a bit of scripting magic we can use this batch file to filter out unwanted lines from the final merged InitObj.dat file:

    1. REM   This batch file filters the DAT files
    2. REM   CE merges all DAT files into initobj.tmp
    3. REM   and then transforms initobj.tmp into a
    4. REM   UNICODE version initobj.dat.
    5. REM   Just before the conversion to unicode
    6. REM   the build system calls PostFmergeObj.bat
    7. REM   (this file). We filter out all the
    8. REM   strings defined in PostFmergeObj.txt
    9. REM   from initobj.tmp
    10.  
    11. @echo off
    12. echo PostFmergeObj.bat entry.
    13. pushd %_FLATRELEASEDIR%
    14. echo Deleting root:- entries from initobj.tmp
    15. del initobj.org
    16. ren initobj.tmp initobj.org
    17. findstr /i /v /g:PostFmergeObj.txt initobj.org > initobj.tmp
    18. popd
    19. echo PostFmergeObj.bat exit.
    20. @echo on

    The core of the script is from line 15 onwards. In line 15 we delete any previous initobj.org file this batch file may have left behind. Then, in line 16, we rename initobj.tmp (all dat files are merged into this file by fmerge) to initobj.org and in line 17 we call the dos utility Findstr:

    >findstr /?
    Searches for strings in files.

    FINDSTR [/B] [/E] [/L] [/R] [/S] [/I] [/X] [/V] [/N] [/M] [/O] [/P] [/F:file]
            [/C:string] [/G:file] [/D:dir list] [/A:color attributes] [/OFF[LINE]]
            strings [[drive:][path]filename[ ...]]

      /B         Matches pattern if at the beginning of a line.
      /E         Matches pattern if at the end of a line.
      /L         Uses search strings literally.
      /R         Uses search strings as regular expressions.
      /S         Searches for matching files in the current directory and all
                 subdirectories.
      /I         Specifies that the search is not to be case-sensitive.
      /X         Prints lines that match exactly.
      /V         Prints only lines that do not contain a match.
      /N         Prints the line number before each line that matches.
      /M         Prints only the filename if a file contains a match.
      /O         Prints character offset before each matching line.
      /P         Skip files with non-printable characters.
      /OFF[LINE] Do not skip files with offline attribute set.
      /A:attr    Specifies color attribute with two hex digits. See "color /?"
      /F:file    Reads file list from the specified file(/ stands for console).
      /C:string  Uses specified string as a literal search string.
      /G:file    Gets search strings from the specified file(/ stands for console).
      /D:dir     Search a semicolon delimited list of directories
      strings    Text to be searched for.
      [drive:][path]filename
                 Specifies a file or files to search.

    Use spaces to separate multiple search strings unless the argument is prefixed
    with /C.  For example, 'FINDSTR "hello there" x.y' searches for "hello" or
    "there" in file x.y.  'FINDSTR /C:"hello there" x.y' searches for
    "hello there" in file x.y.

    Regular expression quick reference:
      .        Wildcard: any character
      *        Repeat: zero or more occurances of previous character or class
      ^        Line position: beginning of line
      $        Line position: end of line
      [class]  Character class: any one character in set
      [^class] Inverse class: any one character not in set
      [x-y]    Range: any characters within the specified range
      \x       Escape: literal use of metacharacter x
      \<xyz    Word position: beginning of word
      xyz\>    Word position: end of word

    For full information on FINDSTR regular expressions refer to the online Command
    Reference.

    By calling findstr with options /I and /V we effectively filter out the lines we specify in PostFmergeObj.txt and we pipe the output of findstr into InitObj.tmp. After this batch file returns the build system will call the "Text to Unicode" tool (Txt2ucde.exe) that converts any ASCII text strings in Initobj.tmp to Unicode text strings and creates the file Initobj.dat which then contains the Unicode conversion of InitObj.tmp (see http://msdn.microsoft.com/en-us/library/ms930234.aspx).

    Note that you can also use regular expressions to find strings with findstr. Using this you have a very powerful tool to filter whatever file you want filtered and of course this trick doesn't only work on dat files, it works on any ascii text file (like bib, reg, etc).

    All you have to do for all this to start working in your build is create PostFmergeObj.bat in your PLATFORM\BSP\FILES folder and copy'n'paste the above script. The Windows CE build system will automatically search for this batch file and if it finds it execute it just after it has merged the various configuration files. Don't forget to create a "PostFmergeObj.txt" file with the lines you want to delete from the final InitObj.dat file in the same folder as the batch file, like this:

    root:-Directory("\Windows"):-Directory("www")
    Directory("\Windows\www"):-Directory("wwwpub")
    root:-Directory("Program Files")
    root:-Directory("My Documents")
    root:-File("Control Panel.lnk", "\Windows\control.lnk")
    Directory("\Windows"):-Directory("Desktop")
    Directory("\Windows"):-Directory("Programs")
    Directory("\Windows"):-Directory("Recent")
    Directory("\Program Files"):-File("Command Prompt.lnk","\Windows\cmd.lnk")
    Directory("\Windows\Programs"):-File("Windows Explorer.lnk", "\Windows\explore.lnk")
    Directory("\Windows\Programs"):-File("Command Prompt.lnk","\Windows\cmd.lnk")
    Directory("\Program Files"):-File("Command Prompt.lnk","\Windows\cmd.lnk")

    When you now perform a "make image" this batch file should automatically execute and its output (the "echo" commands in the batch file) should appear in the build output window.

    SD MMC and Windows CE

    Lately there have been a lot of questions about SD and MMC in the newsgroups, especially about what is supported by the Microsoft SD bus driver. This blog post hopefully helps clear up some things about SD/MMC support in Windows CE.

    First let’s look at an overview of the MMC and SD specifications and who supports what:

    Specification overview
    CE Version SD Spec MMC Spec
    Win CE 5.0 RTM 1.1 3.x
    Win CE 5.0 QFE (April 2007 onward) 2.0 4.3
    Win Mobile 6.0 RTM 1.1 3.x
    Win Mobile 6.x (AKU 0.2 onward) 2.0 4.3
    Win CE 6.0 RTM 1.1 3.x
    Win CE 6.0 R2 2.0 4.3

    From the table above it looks like the SD bus driver in CE 6.0 R2 supports the MMC 4.3 specification, but actually it doesn’t completely (as we’ll see a bit later in this blog post).

    The Microsoft SD bus driver (sdbus2.dll) is fully supporting the SD 2.0 specification. Because the MMC 4.3 specification is quite similar to SD 2.0, CE “somewhat” supports MMC 4.3.

    So what does this mean? Well, for example, the number of data lines (bus width) of MMC supported by the Microsoft SD bus driver differs from the MMC specification:

    8/4 or 1-bit mode

    The MMC specification tells us it supports 8-bit wide bus mode (8-data lines). However, the SD 2.0 specification does not support 8 bit bus mode. Since CE officially supports SD but not MMC, CE does not support the 8-bit mode for any MMC/SD card. Unfortunately it appears the 4-bit mode is also not supported for MMC cards by the Microsoft SD bus driver even though the SD specification does support this mode. This leaves 1-bit mode as the only supported mode for MMC.

    Here’s an overview of the supported modes by the SD bus driver (sdbus2.dll):

    Bus mode SD MMC
    1 Y Y
    4 Y N
    8 N N

    So does this mean that MMC cannot support 4 or 8 bit at all? Well no, not really... You can always CLONE the SDBUS driver and modify it according to your needs or you can develop your own driver without using the SD bus driver at all.

    High Capacity

    According to the SD 2.0 specification "High Capacity" means cards with sizes larger then 2GB. SDHC cards are supported by the SD bus driver version 2.0. However, because of a bug in the SD bus driver high capacity MMC cards don’t work. You need to fix one line of code to support HC MMC cards. Besides this bug there is also a difference in the protocol for MMC and SD 2.0 regarding High Capacity, more about that later.

    First let’s fix the obvious bug so that your High Capacity MMC card will be recognized and mounted properly:

    The first step is to clone the SD Bus driver located at <WINCEROOT>\PUBLIC\COMMON\OAK\DRIVER\SDCARD\SDBUS

    1. Open a build release window
    2. Type "cd %_targetplatroot%"
    3. Type "cd src"
    4. Type "cd drivers"
    5. Type "md sdbus2"
    6. Type "cd sdbus2"
    7. Type "sysgen_capture -p common sdbus"
    8. Now copy the files from <WINCEROOT>\PUBLIC\COMMON\OAK\DRIVER\SDCARD\SDBUS into <WINCEROOT>\PLATFORM\<YourBSP>\SRC\DRIVERS\SDBUS2
    9. And merge sources.sdbus and sources so you end up with this sources file:
    10. SYNCHRONIZE_BLOCK=1

      TARGETNAME=sdbus
      TARGETDEFNAME=SDBus2
      DEFFILE=$(TARGETDEFNAME).def

      TARGETTYPE=DYNLINK
      RELEASETYPE=PLATFORM

      DLLENTRY=_DllEntryCRTStartup

      SOURCES = sdbusreq.cpp \
                sddevice.cpp \
                sdbus.cpp \
                sdslot.cpp \
                sdclient.cpp \
                sddevinf.cpp \
                sdiofeat.cpp \
                sdworki.cpp \
                sddebug.cpp \

      TARGETLIBS=                                           \
        $(_SYSGENOAKROOT)\lib\$(_CPUINDPATH)\defbuslib.lib  \
        $(_SYSGENSDKROOT)\lib\$(_CPUINDPATH)\coredll.lib    \
        $(_SYSGENOAKROOT)\lib\$(_CPUINDPATH)\ceddk.lib    

      Note that I added SYNCHRONIZE_BLOCK=1 to make sure any other component that will link to the sdbus library will be able to find it. SYNCHRONIZE_BLOCK=1 makes sure this folder is built before any other folder in the DRIVERS folder is built.

    11. Try to build the SDBUS2 driver; it should build without errors. If it doesn’t, make sure you selected the SD Bus Driver in the catalog and performed a sysgen on your OSDesign.
    12. And add the SDBUS2 folder to the dirs file in the DRIVERS folder.
    13. Don't forget to change any BSP component that links to $(_COMMONOAKROOT)\lib\$(_CPUINDPATH)\sdbus.lib to use the cloned SDBUS library, located at $(_TARGETPLATROOT)\lib\$(_CPUINDPATH)\sdbus.lib

    Now change StringCchCopy to StringCchCat in line 1394 of sddevice.cpp (in your cloned folder of course!). This will append the "\\High_Capacity" string to the Client driver registry entry instead of replacing it (which leads to not finding the correct registry path for the profile because "HKLM\\High_Capacity" is not a valid registry path). Even with this fix it won’t find that path because Microsoft did not provide registry settings for High Capacity MMC. Let’s fix that by adding the following to platform.reg:

    ; SDHC Memory Storage class driver
    [HKEY_LOCAL_MACHINE\Drivers\SDCARD\ClientDrivers\Class\MMC_Class\High_Capacity]
       "Dll"="SDMemory.dll"
       "Prefix"="DSK"
       "BlockTransferSize"=dword:40  ; send no more than 64 blocks of data per bus transfer
       ;"SingleBlockWrites"=dword:1  ; alternatively force the driver to use single block access
       ;"IdleTimeout"=dword:7D0      ; 2000 milliseconds
       ;"IdlePowerState"=dword:2     ; 0 == D0, 1 == D1, etc.
       ;"DisablePowerManagement"=""  ; if value present, then disable (remove value to enable)

       "Profile"="MMC"
       "IClass"=multi_sz:"{A4E7EDDA-E575-4252-9D6B-4195D48BB865}",
                         "{8DD679CE-8AB4-43c8-A14A-EA4963FAA715}"

    Now you can Build and Sysgen your BSP by right clicking on your BSP node in the Solution Explorer and choosing Build and Sysgen. NEVER EVER click Build and Sysgen from the Build menu, please read the blog post http://www.guruce.com/blogpost/whattobuildwhen to understand why you should delete that option from the build menu. After the Build and Sysgen of the BSP perform a Copy files to release directory followed by a Make image and download your kernel to your device. When you now insert a high capacity MMC card it should be recognized and mounted correctly, well, almost! The size is most probably still reported wrong. This is because of a difference in the protocol. CMD 8 is handled differently in the SD 2.0 and MMC 4.3 specification:

    COMMAND 8 (CMD8)
    Command Type Response Abbreviation Command Description
    CMD8 MMC R1 SEND_EXT_CSD The card sends its EXT_CSD register as a Block of data.
    CMD8 SD R7 SEND_IF_COND Sends SD Memory Card interface condition

    In the SD protocol CMD8 is used to identify high capacity cards and it is send at the beginning of the initialization process. When CMD8 is getting a response it means that it is identified as a High Capacity card. If sending a CMD8 results in a response timeout it is not a High Capacity card.

    For MMC the CMD8 means retrieving the density of the card from the EXT_CSD register instead of the CDS register. CMD8 must be send after a CMD7 command which will place the card into "Tran" state. Since the SD bus driver doesn't do that the CMD8 will time out.

    Like I said the SD bus driver fully supports the SD 2.0 specification and it reads the card density from the CSD register. This works for SD 2.0, but not for MMC. To fully support High Capacity MMC cards you should modify the SD bus driver so it can handle CMD8 instead of CMD9 to retrieve the card density for MMC high capacity cards.

    SDBUS or SDBUS2

    Now how do you select the correct SD bus driver? First remember that you have to install the correct version of Windows CE and install all required QFEs (see specification overview above). For Windows Embedded CE 6.0 there are two catalog components for SDBUS; "SD Bus Legacy" and "SD Bus driver". The legacy one is the SD bus driver that supports SD specification 1.1 and the "SD Bus Driver" item supports the 2.0 specification.

    For all other versions of CE there is only one catalog component which automatically selects the SD bus 1.1 specification. To support the 2.0 specification in CE versions prior to CE 6.0 you have to set the IMGSDBUS2 environment variable to 1.

    Conclusion

    Windows CE fully supports the SD 2.0 specification, but as we saw this doesn’t automatically mean it also fully supports High Capacity MMC cards. Fortunately with a couple modifications here and there you can now fully support High Capacity MMC cards as well!

    Pages

    Subscribe to RSS - blogs