Windows CE: C# Application to Format TFAT


Windows CE: C# Application to Format TFAT
In Windows CE: Formatting TFAT I show how to format a disk with TFAT using C/C++ code. The customer that I wrote that for develops using C#, so I wrote a C# application to test the formatting code. To me, most formatting tools are just too difficult for the average user. In this case, the user will be someone working in the factory or field service. They just need to select a disk and request that it be formatted.
The application dialog look like:
It has a drop down list which I populated with a list of disks, a format button and an exit button. It is simple, and easy to use.
The hard part of this application is populating the drop down list. I did some searching and found suggestion of using System.IO.DirectoryInfo to find the root folders and check the attributes for temporary folders. The following code demonstrates this approach, were DiskSelect is the drop down list:
            System.IO.DirectoryInfo DI = new System.IO.DirectoryInfo(@"\");
 
            System.IO.DirectoryInfo[] Folders = DI.GetDirectories();
            foreach (System.IO.DirectoryInfo FDI in Folders)
            {
                if (FDI.Attributes.CompareTo(System.IO.FileAttributes.Temporary) == 1)
                    DiskSelect.Items.Add(FDI.Name);
            }
            if( DiskSelect.Items.Count > 0 )
               DiskSelect.SelectedIndex = 0;
This code works and can be effective on some systems. But it has a flaw which is that not all temprary folders are disks. If your OS includes the network redirector component, there will be a Network folder which is temporary but is not a disk. The Release folder which is used in debugging the OS using Platform Builder is also temporary, but not a disk.
The only alternative that I could find is to use FindFirstStore()/FindNextStore() and FindFirstPartition()/FindNextPartition() which are included in StoreAPI. The downside of this approach is that StoreAPI is not generaly included in the SDK and worse, it is not a DLL in the system so it cannot be P/Invoked. So if you don’t have access to the StoreAPI, you might as well stop reading now – the rest of this assumes that you do have access to StoreAPI. If you don’t have it, you might want to ask your OEM for it because it has some valuable functions for accessing disk information.
I could have simply wrapped the StoreAPI with a DLL, which would have been as simple as just linking StoreAPI.lib with a DllEntry() function. But I am not a good enough C# programmer to define the structures needed for C# and I felt that for a C# program the functions are just too clunky. Instead I made some assumptions before writing the code. The assumptions are:
1.       The functions that I will be writing will be used for populating a drop down list and nothing more sophisticated
2.       The disks have only one partition, if you are working with systems that have multiple partitions you can extend the code to handle that but I didn’t need it
I realized that I needed to have some data that I could let the application track and pass into function calls. So I started by defining the structure that I needed:
typedef struct __FindDiskInfo__
{
                STOREINFO StoreInfo;
                PARTINFO PartInfo;
} FindDiskInfoT;
STOREINFO and PARTINFO are defined in storemgr.h which is included in the SDK. I plan to create functions that populate the structure and then some functions to get the strings that I need. This should make the C# fairly easy to write. The first step is to initalize the structure and get a pointer/reference from the C/C++ code to the C# code. NewStoreInfo() will allocate the storage and initialize it and DeleteStoreInfo() will release the structure:
__declspec(dllexport) FindDiskInfoT * APIENTRY NewStoreInfo()
{
                FindDiskInfoT *FindDiskInfo;
                RETAILMSG( 1, (TEXT("NewStoreInfo\n")));
                FindDiskInfo = (FindDiskInfoT *)malloc( sizeof(FindDiskInfoT) );
                if( FindDiskInfo == NULL )
                {
                                RETAILMSG( 1, (TEXT("StartFindStore: malloc failed\n")));
                                return NULL;
                }
                memset(FindDiskInfo, 0, sizeof(FindDiskInfoT));
                FindDiskInfo->StoreInfo.cbSize = sizeof(STOREINFO);
                FindDiskInfo->PartInfo.cbSize = sizeof(PARTINFO);
                                wcscpy(FindDiskInfo->PartInfo.szVolumeName, TEXT("TEST VolName"));
 
                RETAILMSG( 1, (TEXT("NewStoreInfo return %X\n"), FindDiskInfo));
                return FindDiskInfo;
}
 
__declspec(dllexport) BOOL APIENTRY DeleteStoreInfo(HANDLE hFind, FindDiskInfoT *FindDiskInfo)
{
                if( FindDiskInfo != NULL )
                {
                                free( FindDiskInfo );
                }
                if( hFind )
                {
                                FindCloseStore( hFind );
                }
                return TRUE;
}
And the C# call to these function is:
IntPtr StoreInfo;
 
StoreInfo = NewStoreInfo();
DeleteStoreInfo(hStore, StoreInfo);
Note that DeleteStoreInfo() has an hStore passed into into it. hStore is HANDLE used for finding the disks. hStore is returned from StartFindDisk() which is called to start finding available disks. StartFindDisk() receives a pointer to the previously allocated FindDiskInfoT:
__declspec(dllexport) HANDLE APIENTRY StartFindDisk( FindDiskInfoT *FindDiskInfo )
{
HANDLE hFind = INVALID_HANDLE_VALUE;
 
                hFind = FindFirstStore(&(FindDiskInfo->StoreInfo));
               
                if( hFind != INVALID_HANDLE_VALUE )
                {
                                HANDLE hStore;
                                hStore = OpenStore( FindDiskInfo->StoreInfo.szDeviceName );               
 
                                if( hStore )
                                {
                                                HANDLE hFind = INVALID_HANDLE_VALUE;
                                                hFind = FindFirstPartition(hStore, &(FindDiskInfo->PartInfo));
 
                                                if(INVALID_HANDLE_VALUE != hFind)
                                                {
                                                                FindClose(hFind);
                                                }
                                                else
                                                                RETAILMSG( 1, (TEXT("No Partitions Found\n")));
                                                CloseHandle(hStore);
                                }
                                else
                                                RETAILMSG(1, (TEXT("OpenSelectedStore failed\n")));
                }
                else
                {
                                RETAILMSG( 1, (TEXT("StartFindStore: FindFirstStore failed %d\n"), GetLastError()));
                }
               
                return hFind;
}
And the C# call is:
            hStore = StartFindDisk(StoreInfo);
StartFindDisk() finds the first available disk and the first partition on the disk and populates the FindDiskInfoT structure with the available information for use later. After calling this, to get the name of the folder, the device name and the partition name, the following functions can be used:
__declspec(dllexport) TCHAR * APIENTRY GetStoreDeviceName( FindDiskInfoT *FindDiskInfo )
{
                TCHAR * RetVal = NULL;
                if( FindDiskInfo )
                {
                                RetVal = FindDiskInfo->StoreInfo.szDeviceName;
                }
                return RetVal;
}
 
__declspec(dllexport) TCHAR * APIENTRY GetStoreFolderName( FindDiskInfoT *FindDiskInfo )
{
                TCHAR * RetVal = NULL;
                if( FindDiskInfo )
                {
                                RetVal = FindDiskInfo->PartInfo.szVolumeName;
                }
                return RetVal;
}
 
__declspec(dllexport) TCHAR * APIENTRY GetStorePartitionName( FindDiskInfoT *FindDiskInfo )
{
                TCHAR * RetVal = NULL;
                if( FindDiskInfo )
                {
                                RetVal = FindDiskInfo->PartInfo.szPartitionName;
                }
                return RetVal;
}
Then to get the rest of the disks, FindNextDisk() is used:
__declspec(dllexport) BOOL APIENTRY FindNextDisk( HANDLE hFind, FindDiskInfoT *FindDiskInfo )
{
BOOL FindResult = FALSE;
 
                if( FindDiskInfo == NULL || hFind == INVALID_HANDLE_VALUE )
                {
                                                RETAILMSG( 1, (TEXT("FindNextDisk: invalid parameters\n")));
                                                return FALSE;
                }
                FindResult = FindNextStore(hFind, &(FindDiskInfo->StoreInfo));
               
                if( FindResult != FALSE )
                {
                                HANDLE hStore;
                                hStore = OpenStore( FindDiskInfo->StoreInfo.szDeviceName );               
 
                                if( hStore )
                                {
                                                HANDLE hFind = INVALID_HANDLE_VALUE;
                                                memset( &(FindDiskInfo->PartInfo), 0, sizeof(PARTINFO));
                                                FindDiskInfo->PartInfo.cbSize = sizeof(PARTINFO);
 
                                                hFind = FindFirstPartition(hStore, &(FindDiskInfo->PartInfo));
 
                                                if(INVALID_HANDLE_VALUE != hFind)
                                                {
                                                                FindClose(hFind);
                                                }
                                                else
                                                {
                                                                RETAILMSG( 1, (TEXT("FindNextDisk: No Partitions Found %d\n"), GetLastError()));
                                                                FindResult = FALSE;
                                                }
                                                CloseHandle(hStore);
                                }
                                else
                                {
                                                RETAILMSG(1, (TEXT("FindNextDisk: OpenSelectedStore failed\n")));
                                                FindResult = FALSE;
                                }
                }
                else
                {
                                RETAILMSG( 1, (TEXT("FindNextDisk: FindNextStore failed %d\n"), GetLastError()));
                                FindResult = FALSE;
                }
               
                return FindResult;
}
Similar to StartFindDisk() this finds the next disk and its first partition and populates the FindDiskInfoT with the available data, so that the strings can be retreived later.
The code for setting up the C# dialog looks like:
        [DllImport("FormatDLL.dll", CharSet = CharSet.Auto, SetLastError = true)]
        static extern bool FormatDiskTFAT(String StoreName, String PartitionName);
        [DllImport("FormatDLL.dll", CharSet = CharSet.Auto, SetLastError = true)]
        static extern bool FormatDiskTFATEx(String FolderName);
        [DllImport("FormatDLL.dll", CharSet = CharSet.Auto, SetLastError = true)]
        static extern IntPtr StartFindDisk(IntPtr pStoreInfo);
        [DllImport("FormatDLL.dll", CharSet = CharSet.Auto, SetLastError = true)]
        static extern bool DeleteStoreInfo(IntPtr hStore, IntPtr FindDiskInfo);
        [DllImport("FormatDLL.dll", CharSet = CharSet.Auto, SetLastError = true)]
        static extern bool FindNextDisk(IntPtr hFind, IntPtr pStoreInfo);
        [DllImport("FormatDLL.dll", CharSet = CharSet.Auto, SetLastError = true)]
        static extern IntPtr GetStoreDeviceName(IntPtr pStoreInfo);
        [DllImport("FormatDLL.dll", CharSet = CharSet.Auto, SetLastError = true)]
        static extern IntPtr GetStoreFolderName(IntPtr pStoreInfo);
        [DllImport("FormatDLL.dll", CharSet = CharSet.Auto, SetLastError = true)]
        static extern IntPtr GetStorePartitionName(IntPtr pStoreInfo);
        [DllImport("FormatDLL.dll", CharSet = CharSet.Auto, SetLastError = true)]
        static extern IntPtr NewStoreInfo();
 
        DiskInfo[] Disks;
 
        public FormatTool()
        {
            IntPtr StoreInfo;
            IntPtr hStore;
            IntPtr ipName;
            int DiskCount = 0;
 
            InitializeComponent();
          
            StoreInfo = NewStoreInfo();
            hStore = StartFindDisk(StoreInfo);
 
            if (hStore != null)
            {
                Disks = new DiskInfo[1];
                Disks[DiskCount] = new DiskInfo();
 
                ipName = GetStoreDeviceName(StoreInfo);
                Disks[DiskCount].DeviceName = Marshal.PtrToStringUni(ipName).ToString();
 
                ipName = GetStoreFolderName(StoreInfo);
                Disks[DiskCount].FolderName = Marshal.PtrToStringUni(ipName).ToString();
 
                ipName = GetStorePartitionName(StoreInfo);
                Disks[DiskCount].PartitionName = Marshal.PtrToStringUni(ipName).ToString();
 
                DiskSelect.Items.Add(Disks[DiskCount].FolderName);
                DiskSelect.SelectedIndex = 0;
                DiskCount++;
 
                while (FindNextDisk(hStore, StoreInfo))
                {
                    DiskInfo[] NewDisks = new DiskInfo[DiskCount+1];
                    Disks.CopyTo(NewDisks, 0);
                    NewDisks[DiskCount] = new DiskInfo();
 
                    ipName = GetStoreDeviceName(StoreInfo);
                    NewDisks[DiskCount].DeviceName = Marshal.PtrToStringUni(ipName).ToString();
 
                    ipName = GetStoreFolderName(StoreInfo);
                    NewDisks[DiskCount].FolderName = Marshal.PtrToStringUni(ipName).ToString();
 
                    ipName = GetStorePartitionName(StoreInfo);
                    NewDisks[DiskCount].PartitionName = Marshal.PtrToStringUni(ipName).ToString();
 
                    DiskSelect.Items.Add(NewDisks[DiskCount].FolderName);
                    DiskCount++;
                    Disks = NewDisks;
                }
                DeleteStoreInfo(hStore, StoreInfo);
                if (DiskSelect.Items.Count > 0)
                    DiskSelect.SelectedIndex = 0;
            }
            
        }
I probably could have simplified this code, and if it were a long term project I would spend time on that.   This cod uses a class for keeping track of the disk information for use in the call to FormatDiskTFAT:
    public class DiskInfo
    {
        public String FolderName;
        public String DeviceName;
        public String PartitionName;
    }
But in hindsight I wrote FormatDiskTFATEx() which doesn’t need all of that information. So the code can be simplified to:
            String DiskName;
            StoreInfo = NewStoreInfo();
            hStore = StartFindDisk(StoreInfo);
 
            if (hStore != null)
            {
                ipName = GetStorePartitionName(StoreInfo);
                DiskName = Marshal.PtrToStringUni(ipName).ToString();
 
                DiskSelect.Items.Add(DiskName);
                DiskSelect.SelectedIndex = 0;
 
                while (FindNextDisk(hStore, StoreInfo))
                {
                    ipName = GetStorePartitionName(StoreInfo);
                    DiskName = Marshal.PtrToStringUni(ipName).ToString();
 
                    DiskSelect.Items.Add(DiskName);
                }
                DeleteStoreInfo(hStore, StoreInfo);
                if (DiskSelect.Items.Count > 0)
                    DiskSelect.SelectedIndex = 0;
            }
Finally, when the Format button is pressed we need to format the disk. The button handler is:
        private void FormatButton_Click(object sender, EventArgs e)
        {
            int Index = DiskSelect.SelectedIndex;
 
            Exitbutton.Visible = false;
            FormatButton.Visible = false;
            Formatinglabel.Text = "Formatting, Please Wait";
            Formatinglabel.Visible = true;
            this.Refresh();
 
            System.Threading.Thread.Sleep(2000); 
            FormatDiskTFAT(Disks[Index].DeviceName, Disks[Index].PartitionName);
 
            Exitbutton.Visible = true;
            FormatButton.Visible = true;
            Formatinglabel.Text = "Formatting Complete";
            Formatinglabel.Visible = true;
            this.Refresh();
        }
Which uses FormatDiskTFAT(), but FormatDiskTFATEx() can be used as:
            FormatDiskTFATEx(DiskSelect.SelectedItem.ToString());
FormatButton_Click() hides the buttons and puts a message in a static text box to tell the user to wait then formats the disk. After the disk is formatted, it changes the text to Format Complete and puts the buttons back in. The problem that I found is that the format can be very quick, too quick so that the message isn’t displayed and the buttons aren’t removed. So I added a 2 second delay just to let the user know that something happened.
 
Copyright © 2010 – Bruce Eitman
All Rights Reserved
  • Share This Post:
  • Share on Twitter
  • Share on Facebook
  • Share on Technorati

author: Bruce Eitman | Posted On Monday, August 30, 2010 10:34 PM | Feedback (2)

Windows CE: Formatting TFAT


A customer came to me for assistance in formatting an SD card with TFAT. I did a quick look in the control panel code to find how it formats disks as TFAT and told the customer to use FormatVolume() and set the FORMAT_OPTIONS dwFlags FATUTIL_FORMAT_TFAT flag.   
I assumed that it was that easy and left the customer to write the code. A few weeks later the customer came back to me and said that they just couldn’t get FormatVolume() to work, let alone format the disk as TFAT. So this time I dug deeper and write some code to test it out before getting back to the customer. The following is what I figured out including the code to format a disk as TFAT.
What went wrong? When I first looked at how to format TFAT, I looked to see what happens when the Format button is selected. What I didn’t look for is what has to happen before the Format button is selectable. It turns out that before the Format button is selectable, the partition must first be dismounted.
__declspec(dllexport) BOOL APIENTRY FormatDiskTFAT( TCHAR *StoreName, TCHAR *PartitionName )
{
                HANDLE hStore;
                HANDLE hPartition;
 
                hStore = OpenStore(StoreName);          
 
                if( INVALID_HANDLE_VALUE != hStore )
                {
                                hPartition = OpenPartition( hStore, PartitionName );
                                if( INVALID_HANDLE_VALUE != hPartition )
                                {
                                                if( DismountPartition(hPartition) )
                                                {   
                                                                FORMAT_OPTIONS fo;
 
                                                                // Get formating options
                                                                fo.dwClusSize = 4 * 1024;
                                                                fo.dwFatVersion = 32;
                                                                fo.dwNumFats = 2;
                                                                fo.dwRootEntries = 512;
 
                                                                fo.dwFlags = 0;
                                                                //fo.dwFlags |= FATUTIL_FULL_FORMAT;
                                                                fo.dwFlags |= FATUTIL_FORMAT_TFAT;
 
                                                                if( ERROR_SUCCESS != pfnFormatVolume( hPartition, NULL, &fo, HandleProgress, HandleMessage ))
                                                                                RETAILMSG( 1, (TEXT("Format Failed\n")));
                                                                MountPartition(hPartition);
                                                }
                                                else
                                                                RETAILMSG( 1, (TEXT("Failed to dismount Partition\n")));
 
                                                CloseHandle( hPartition );
                                }
                                else
                                                RETAILMSG(1, (TEXT("OpenPartition failed\n")));
 
                                CloseHandle(hStore);
                }
                else
                                RETAILMSG(1, (TEXT("OpenSelectedStore failed\n")));
 
                return TRUE;
}
FormatDiskTFAT() takes two strings as arguments:
·         StoreName which is the szDeviceName in the STOREINFO structure returned from FindFirstStore()/FindNextStore() or the szStoreName in the CE_VOLUME_INFO structure returned from CeGetVolumeInfo(). The value of the string will be somethings like “DSK1:”.
·         PartitionName which is the szPartionName in PARTINFO and CE_VOLUME_INFO returned by FindFirstPartition()/FindNextPartition() and CeGetVolumeInfo(). The value of the string will be something like “PART00”.
FormatDiskTFAT() uses the strings to open the store and the partition. Then it dismounts the partition, formats the disk and re-mounts the partition.
I choose the function signature because I was thinking about using FindFirstStore()/FindNextStore(), so the device name and the partition name would be readily available. As an afterthought, I decided that it might be handy to have a function that simply takes the folder name of the disk, so I came up with FormatDiskTFATEx():
__declspec(dllexport) BOOL APIENTRY FormatDiskTFATEx( TCHAR *FolderName )
{
                CE_VOLUME_INFO VolInfo;
                VolInfo.cbSize = sizeof( CE_VOLUME_INFO );
 
                CeGetVolumeInfo(FolderName, CeVolumeInfoLevelStandard, &VolInfo );
                return FormatDiskTFAT( VolInfo.szStoreName, VolInfo.szPartitionName );
}
FormatDiskTFATEx() takes one string as an argument which is the name of the disk folder. This will be something like “Storage Card” or “\Storage Card” depending on how you obtain the string.
One of the interesting problems that I ran into is that building this for Windows CE 5.0 (I have not tested yet with CE 6.0) the call to CeGetVolumeInfo() causes a linker error because CeGetVolumeInfoW() does not exist. The solution to this problem is to undefine CeGetVolumeInfo(). So I added:
#ifdef CeGetVolumeInfo
#undef CeGetVolumeInfo
#endif
Then next problem that I needed to solve is that while I can easily compile this code using Platform Builder, my customer who is using an SDK that I provided cannot. The problem is that FATUTIL.DLL is included in the OS, but FATUTIL.LIB is not included in the SDK and FormatVolume() is in FATUTIL. It would be easy enough to create a new SDK and release it, but that may be overkill for one function. So the solution that I came up with is to use LoadLibrary() and GetProcAddress().   I am including these functions in a DLL, so I did the mapping in DllEntry():
HMODULE hFatUtil = NULL;
DWORD (*pfnFormatVolume)(HANDLE hVolume,void *pdi,FORMAT_OPTIONS *pfo,void *pfnProgress,void *pfnMessage);
 
static void *GetFormatVolume(HMODULE *hFatUtil);
 
 
BOOL APIENTRY DllMain( HANDLE hModule,
                       DWORD ul_reason_for_call,
                       LPVOID lpReserved
                                                                                 )
{
                switch( ul_reason_for_call )
                {
                                case DLL_PROCESS_ATTACH:
                                                pfnFormatVolume = GetFormatVolume(&hFatUtil);
                                                break;
                                case DLL_PROCESS_DETACH:
                                                FreeLibrary(hFatUtil);
                                                break;
                }
    return TRUE;
}
The last problem is that FORMAT_OPTIONS needed by FormatVolume() is also not available in the SDK, or more accuratly the header file that they are included in doesn’t compile cleanly, so I included them in the C code:
#define FATUTIL_FULL_FORMAT                 0x1
#define FATUTIL_FORMAT_TFAT                 0x2
 
typedef struct _FORMAT_OPTIONS {
    DWORD   dwClusSize;
    DWORD   dwRootEntries;
    DWORD   dwFatVersion;
    DWORD   dwNumFats;
    DWORD   dwFlags;
} FORMAT_OPTIONS;
In my next article (see Windows CE: C# Application to Format TFAT), I will show how to use these functions from C#. As for using them from C/C++, you may want to review Windows CE: Displaying Disk Information for example code that uses FindFirstStore()/FindNextStore() and FindFirstPartition()/FindNextPartition() to enumerate the mounted disks.
NOTE 1: In FormatDiskTFAT() I have commented out the line “//fo.dwFlags |= FATUTIL_FULL_FORMAT;” because in my testing with both this code and the Storage Manager control panel applet, the full format would not complete.
NOTE 2: I made a conscious choice to hardcode the formatting options in FormatDiskTFAT() because while these options are interesting, they just shouldn’t be user options in an embedded system. I could have pushed them up to the caller function, but that opens the door for a confusing user interface.
Copyright © 2010 – Bruce Eitman
All Rights Reserved
  • Share This Post:
  • Share on Twitter
  • Share on Facebook
  • Share on Technorati

author: Bruce Eitman | Posted On Sunday, August 29, 2010 10:42 PM | Feedback (0)

Windows Compact: Discussion Forums


I monitor some Windows Compact (the new name for Windows CE) Forums for a few reasons:
  • I learn a lot from others working on Windows CE, so many of you are running into the same issues that I run into.  Sometimes you run into them before I do, other times I have already run into the issue.
  • I learn a lot from answering questions.  You might be surprised to find that sometimes I don't already know the answer, so I do some investigation to get the answer.  Sometimes I am wrong, or there is a better way, with my answer, and there is always someone to point that out.
  • I get satisfaction from helping others by answering their questions
I monitor the following Forums:
  1. Windows Embedded Compact Managed Application Development which discusses C# and VB .NET managed application development
  2. Windows Embedded Compact Native Application Development is a forum for discussing C/C++ application development
  3. Windows Embedded Compact Platform Development a forum for discussing issues related to use of Platform Builder for Windows Compact OS development

 

I use Internet Explorer to monitor and answer questions. Windows Embedded Compact Category is a page which lists all three of the Windows Compact forums.

Copyright © 2010 – Bruce Eitman
All Rights Reserved
  • Share This Post:
  • Share on Twitter
  • Share on Facebook
  • Share on Technorati

author: Bruce Eitman | Posted On Wednesday, August 18, 2010 7:53 PM | Feedback (14)

Platform Builder: Write, Debug, Fix, Debug…


The question came up again about how to quickly test an application or driver using Platform Builder and KITL. This article will show how this test cycle can be done quickly without building a new OS and downloading it.
Of course Platform Builder is a tool for developing an operating system, so if you use its default behavior it will build an operating system. So that means that a small change to source code means building the operating system and deploying it to your device. But we will look at how we can do things a little differently and speed up the process. 
The basic steps are:
1.       Add KITL support
2.       Add Target Control Support (Shell.exe)
3.       Leave your app out of the OS, but leave in the registry settings
4.       Build your OS
5.       Start the OS and connect KITL
6.       When your run the app, it will load over your KITL connection
7.       Update your app
8.       Build your app
9.       Repeat starting at step 6
For this article I am not going to show you everything, but I will describe what I did to write an application and test it using KITL without rebuilding the OS. I am writing this while sitting in an airport, so I am using the emulator but these techniques work just as well with a real device, maybe better.
I started by creating a new project, not that this is important but I didn’t have one to work with. I wanted to keep it simple so I chose the Emulator BSP and the Thin Client template. I left out anything that I didn’t need for this article, like the .NET CompactFramework. I double checked that KITL was enabled and that Target Control Support was included. Target Control Support is important to this because it adds support for the \Release folder which links back to the _FLATRELEASEDIR on the development PC. I change the shell from the Thin Client shell to the standard shell, honestly I am not familiar with the Thin Client shell and felt more comfortable with the standard shell for a quick test.
Next I added a subproject. I am adding it now, before building the OS, so that it more closely simulates a real world project. The project is a WCE Application and I selected a simple Hello World template. This template doesn’t provide a menu with an option to Exit, so using code from Windows CE: Setting an Application to Display Full Screen in C/C++ I added an exit button. Being able to exit the application being tested will be very important for speeding up the test cycle.
We are almost ready to build and test, but first, and this is important for speeding up the test cycle, remove the subproject application from the OS image.   If you are testing a driver, the same thing applies. There are a couple of ways to remove the application from the image:
1.       In the OSDesign Properties, select Configuration Properties\Subproject Image Settings. Double click on the subproject that you are working on, and then select Exclude from Image.
 
 


It took me a long time to find this property. In prior versions of Platform Builder it was much easier to exclude a file from the OS. For that reason, my first test I used option 2.
2.       Comment out the file in the bib file.
It is finally time to build the OS and run the first test. For this, run Sysgen and attach to the device.
Once the OS is running, you can start your application by using Start\Run. Looking at the debug output you will see that the application was loaded by RelFSD, which is the \Release folder.
That means that the application was downloaded from your _FLATRELEASEDIR and was not in the OS image. If it isn’t in the OS image, which means that we can change it without changing the OS image, or for that matter restarting the device and downloading the image. The application looks like this when I run it:
The next step is to modify the application. In this case, I want to app to say “The App is running” instead of “Hello World!” So I changed the string table to modify the string that is displayed.  I also fixed the location of the button so that it doesn't sit on top of the text.
Now we need to build the application, not the whole OS. I am a command line kind of engineer, so I would open a build window, change directories to the subproject folder and run build. If you like to use Visual Studio, then start by unchecking Build\Targeted Build Settings\Make Run-Time Image After Build, which is important because we don’t need to make the run-time image and the process takes considerable time.   Now you can right click on your subproject in the Solution Explorer and select Build (for this to work well, set the environment variable WINCEREL to 1).
Finally, run the application again, now it looks like:
To use this same technique to test a stream interface driver, leave the driver and its registry settings out of the OS. Then call ActivateDevice() and DeactivateDevice() from your application as shown in Windows CE: Loading a Driver with ActivateDeviceEx.
 
Copyright © 2010 – Bruce Eitman
All Rights Reserved
  • Share This Post:
  • Share on Twitter
  • Share on Facebook
  • Share on Technorati

author: Bruce Eitman | Posted On Tuesday, August 17, 2010 8:28 PM | Feedback (0)

Windows CE: Disable the Taskbar for Full Screen Applications


In the article Windows CE: Setting an Application to Display Full Screen in C/C++ I discussed how to show an application full screen using C++. I discussed that for a kiosk application that the easiest way to disable the Explorer shell taskbar is to simply not have the Explorer shell launch when the system starts up. But there are cases where having the Explorer shell run underneath the kiosk app has value in some cases. The trouble is that the taskbar will still respond to special keys like the Windows key and suddenly the user has access to the full system. In this article, we will explore disabling the taskbar.
The first step to disable the taskbar is to get a handle to the taskbar window. To do this we will use FindWindow() and the class name of the taskbar which is “HHTaskBar”. We know that the name is HHTaskBar because the code for the taskbar is available in Platform Builder and a review of the code tells us the name. So to get a handle to the taskbar window:
                HWND TaskBar = FindWindow(TEXT("HHTaskBar"), NULL );
Now that we have a handle to the taskbar window, we can send it messages. We could send it a WM_CLOSE message:
                SendMessage(TaskBar, WM_CLOSE, NULL, NULL );
But, that will close the Explore shell, which defeats the purpose which is to keep the Explorer shell running and have access to the APIs that it provides.
Better is to hide the taskbar, not in the sense of setting its auto hide feature which leaves it usable but minimized. Instead we will disable it with a call to EnableWindow():
                EnableWindow( TaskBar, FALSE );
Which also allows us to reenable the taskbar if and when we need to do so.
In the modified full screen application from Windows CE: Setting an Application to Display Full Screen in C/C++, the WinMain() is now:
int WINAPI WinMain(HINSTANCE hInstance,
                   HINSTANCE hPrevInstance,
                   LPTSTR    lpCmdLine,
                   int       nCmdShow)
{
                MSG msg;
 
                if( lpCmdLine )
                                SignalStarted( _wtol(lpCmdLine) );
                HWND TaskBar = FindWindow(TEXT("HHTaskBar"), NULL );
                if( TaskBar != NULL )
                {
                                EnableWindow( TaskBar, FALSE );
                }
 
                // Perform application initialization:
                if (!InitInstance(hInstance, nCmdShow))
                {
                                if( TaskBar != NULL )
                                {
                                                EnableWindow( TaskBar, TRUE );
                                }
                                return FALSE;
                }
 
                HACCEL hAccelTable;
                hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_DELETEMENEXT));
 
                // Main message loop:
                while (GetMessage(&msg, NULL, 0, 0))
                {
                                if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
                                {
                                                TranslateMessage(&msg);
                                                DispatchMessage(&msg);
                                }
                }
                TaskBar = FindWindow(TEXT("HHTaskBar"), NULL );
                if( TaskBar != NULL )
                {
                                EnableWindow( TaskBar, TRUE );
                }
                return (int) msg.wParam;
}
Which not only disables the taskbar when the application starts, but reenables it when the application exits.
Copyright © 2010 – Bruce Eitman
All Rights Reserved
  • Share This Post:
  • Share on Twitter
  • Share on Facebook
  • Share on Technorati

author: Bruce Eitman | Posted On Sunday, August 15, 2010 10:59 PM | Feedback (0)

Windows CE: Setting an Application to Display Full Screen in C/C++


Many, maybe even most, Windows CE devices are single purpose devices that have an application that fills the entire screen. So of course setting the application to entirely fill the display is important. In this article we will explore setting an application to display full screen using C/C++.
My plan is to create a simple application that fills the screen with white and displays two buttons, exit and explore. Those may be odd buttons for a kiosk application, but they will come in handy while working on this application. The exit button will exit the application and the explore button will start the Explorer shell and then exit the application.
Working in Visual Studio 2005, I started by creating a new C++ Win32 Smart Device Application and let the wizard create the starting source code. I then deleted the code for the menu and the about dialog since I don’t need them. If we run the application now we will find two things about it; we can’t exit (now you know why I am going to add the exit button) and the Explorer shell’s task bar is filling part of the screen.
The application almost fills the screen. This is because the CreateWindow() call that was added by Visual Studio set the location and size using CW_USEDEFAULT. This causes the application size to be full screen, minus the task bar area.
Next let’s add the exit button. To do that, I added a global HWND and create the button with CreateWindow() in InitInstance():
 
HWND ExitButton;
 …
      ExitButton = CreateWindow(TEXT("Button"),TEXT("Exit"),
            BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE ,
            20,20,100,40,hWnd,(HMENU)IDM_FILE_EXIT,g_hInst,0);   
      if( ExitButton == NULL )
      {
            DWORD Error = GetLastError();
            RETAILMSG( 1, (TEXT("ExitButton failed %d\r\n"), Error));
            return FALSE;
                }
                              DestroyWindow(ExitButton);
 
Note that the button size and location are hardcoded, we will look at that later. When the button is pressed, the existing action for closing the application will be called, with the edition of a call to DestroyWindow() for the button. If you test the application now, the exit button will be placed in the top left corner and pressing the button will exit the application.
Let’s step back for little bit, this application is going to run in kiosk mode. That is the application will be full screen and we don’t need the Explorer shell. I discussed how to do this in a previous article, Windows CE: Starting an Application when the System Boots. My next step is to add the application to the OS and change the registry so that the Explorer shell doesn’t start and instead this applciation does start. The effect of this change is that the applciation now is full screen. We must add a call to SignalStarted():
      if( lpCmdLine )
            SignalStarted( _wtol(lpCmdLine) );
 
My work here is done. But I will continue.
Now if we test the application by pressing the Exit button, what we will find is that the application stops, but it doesn’t look like it stops because there is nothing to draw on the display. Since we don’t expect this condition in the real world, I am not going to do anything about it. But what I would like to do is start the explorer shell for testing and debugging purposes. To do that lets’s add the Explore button now. The explore button is similar to the exit button, but we will need to add more handling code to support it.
HWND ExplorerShellButton;
      ExplorerShellButton = CreateWindow(TEXT("Button"),TEXT("Explore"),
            BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE ,
            20 + 10 + 100,20,100,40,hWnd,(HMENU)IDM_EXPLORER_BUTTON,g_hInst,0);    
      if( ExplorerShellButton == NULL )
      {
            DWORD Error = GetLastError();
            RETAILMSG( 1, (TEXT("ExplorerShellButton failed %d\r\n"), Error));
            return FALSE;
      }
      case IDM_EXPLORER_BUTTON:
      {
            TCHAR *AppName = TEXT("Explorer.exe");
            TCHAR *CommandLine = NULL;
            PROCESS_INFORMATION pi;
 
            if( CreateProcess( AppName, CommandLine, NULL, NULL, FALSE, 0, NULL, NULL, NULL, &pi))
            {
                  CloseHandle(pi.hProcess);
                  CloseHandle(pi.hThread);
            }
            else
            {
                  RETAILMSG( 1, (TEXT("Failed to start app: %s error %d\n"), AppName, GetLastError() ));
            }
      }
      //Fall through and exit
      DestroyWindow(ExplorerShellButton);
 
And in resource.h:
#define IDM_EXPLORER_BUTTON                   40003
Now when we test the application, the exit button works like it did before, but we can now use the Explore button to start up the Explorer shell before exiting. If you are testing with the Explorer shell already running, you will find that Windows Explorer starts.
Some of you are problably thinking that for some reason or another you need the Explorer shell running under your application. So you need the application to run full screen even when the task bar is available. To solve this, modify InitInstance() to find out the dimensions of the display and set the application to be top most and use the dimension of the display. The application can find the dimensions of the display using GetDeviceCaps(), and then use the dimensions in a call to SetWindowPosition():
      HDC hDC;
      DWORD Width;
      DWORD Height;
      hDC = GetDC(NULL);
      Width = GetDeviceCaps( hDC, HORZRES );
      Height = GetDeviceCaps( hDC, VERTRES );
 
                SetWindowPos(hWnd, HWND_TOPMOST,0, 0, Width, Height, 0 );
Now that we have the screen dimensions, I can move the buttons down to the bottom right where I think that they will be better placed:
      ExitButton = CreateWindow(TEXT("Button"),TEXT("Exit"),
            BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE ,
            Width - 200 - 20 - 10, Height - 40 - 20,100,40,hWnd,(HMENU)IDM_FILE_EXIT,g_hInst,0);
      ExplorerShellButton = CreateWindow(TEXT("Button"),TEXT("Explore"),
            BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE ,
            Width - 100 - 20, Height - 40 - 20,100,40,hWnd,(HMENU)IDM_EXPLORER_BUTTON,g_hInst,0);
     
Now we have an application that displays full screen. 
 
As you test the application with the Explorer shell still running, you will find that there is still a problem if the user has a full keyboard and can use hot keys like the Windows key. I would strongly urge you to consider running your kiosk application without the Explorer shell, so I won’t go into solving that problem now. If your application isn’t a kiosk application, but needs to be full screen, then you should consider leaving support for the hot keys, but I do know that sometimes that isn’t practical.
 
 
Copyright © 2010 – Bruce Eitman
All Rights Reserved
  • Share This Post:
  • Share on Twitter
  • Share on Facebook
  • Share on Technorati

author: Bruce Eitman | Posted On Friday, August 06, 2010 10:47 PM | Feedback (0)

Windows CE: Launching an Application when a Network Connects


I was working on a headless system recently and wanted to run cemgrc.exe when a network connection was made. That inspired me to write a little application to do it.
I decided to make the application flexible, so I added a few requirements; be able to run from HKEY_LOCAL_MACHINE\Init, and read the application name and command line from the registry.
Working backward, I wrote a function to read the registry to get the application name and command line and then start the application using CreateProcess(). Regular readers of this blog will recognize most of this code from Windows CE: Reading a String from the Registry.
void StartApp()
{
      TCHAR *AppName = NULL;
      TCHAR *CommandLine = NULL;
      DWORD Result;
      HKEY hKey;
      DWORD NumBytes = 0;
      DWORD Type;
      PROCESS_INFORMATION pi;
 
      // Open the Registry Key
      Result = RegOpenKeyEx(HKEY_LOCAL_MACHINE, NETWORK_START_APP_KEY, 0, 0, &hKey);
 
      if( ERROR_SUCCESS == Result )
      {
            // This is a fake read, all it does is fill in NumBytes with the number of
            // bytes in the string value plus the null character.
            Result = RegQueryValueEx( hKey, NETWORK_START_APP_NAME, NULL, &Type, NULL, &NumBytes );
            if( NumBytes > 0 )
            {
                  // Now we know how big the string is allocate and read it
                  AppName = (TCHAR *)malloc( NumBytes );
                  if( AppName != NULL )
                  Result = RegQueryValueEx( hKey, NETWORK_START_APP_NAME, NULL, &Type,
                                                            (LPBYTE)AppName, &NumBytes );
            }
            // This is a fake read, all it does is fill in NumBytes with the number of
            // bytes in the string value plus the null character.
            Result = RegQueryValueEx( hKey, NETWORK_START_APP_COMMANDLINE, NULL, &Type, NULL, &NumBytes );
            if( NumBytes > 0 )
            {
                  // Now we know how big the string is allocate and read it
                  CommandLine = (TCHAR *)malloc( NumBytes );
                  if( CommandLine != NULL )
                  Result = RegQueryValueEx( hKey, NETWORK_START_APP_COMMANDLINE, NULL, &Type,
                                                            (LPBYTE)CommandLine, &NumBytes );
            }
            RegCloseKey( hKey );
 
            if( CreateProcess( AppName, CommandLine, NULL, NULL, FALSE, 0, NULL, NULL, NULL, &pi))
            {
                  CloseHandle(pi.hProcess);
                  CloseHandle(pi.hThread);
            }
            else
            {
                  RETAILMSG( 1, (TEXT("Failed to start app: %s error %d\n"), AppName, GetLastError() ));
            }
 
            free( AppName );
            free( CommandLine );
      }
}
StartApp() is really the meat of this application, now we just need to determine if we are running because of a network connection or not and we need to register for network notifications. I do all of this in WinMain():
int WINAPI WinMain( HINSTANCE hInstance,
                    HINSTANCE hPrevInstance,
                    LPTSTR    lpCmdLine,
                    int       nCmdShow)
{
    TCHAR Path[MAX_PATH];
      HANDLE hMutex;
 
    GetModuleFileName( hInstance, Path, MAX_PATH );
      hMutex = CreateMutex(NULL, TRUE, Path);
      if (GetLastError() == ERROR_ALREADY_EXISTS){
            RETAILMSG(1, (TEXT("%s is running already\r\n"), Path));
            return 0;
      }
     
      if( lpCmdLine && 0 != wcscmp(lpCmdLine, TEXT("AppRunAtNetConnect")) )
      {
            if( !CeRunAppAtEvent(Path, NOTIFICATION_EVENT_NET_CONNECT) )
                  RETAILMSG(1, (TEXT("Could not register for notifications\r\n")));
            SignalStarted( _wtol(lpCmdLine) );
      }
      else
      {
            StartApp();
            // Okay this is maybe wrong, but it prevents running again if we
            // are run more than once on a network connection
            Sleep( 5000 );
      }
 
      return 1;
}
Note that if this isn’t a network connection, then the code calls CeRunAppAtEvent() to register for network notifications and calls SignalStarted() in case the application was started from HKEY_LOCAL_MACHINE\Init.
This file includes and defines the following:
#include <windows.h>
#include <commctrl.h>
#include <notify.h>
 
#define NETWORK_START_APP_KEY (TEXT("Software\\NetworkLauncher"))
#define NETWORK_START_APP_NAME      (TEXT("AppName"))
#define NETWORK_START_APP_COMMANDLINE     (TEXT("CommandLine"))
 
And now when a network connection is made, I can start cemgrc.exe.
 
Copyright © 2010 – Bruce Eitman
All Rights Reserved
  • Share This Post:
  • Share on Twitter
  • Share on Facebook
  • Share on Technorati

author: Bruce Eitman | Posted On Thursday, August 05, 2010 9:07 PM | Feedback (0)

Windows CE: Writing an Application to Test GPIO Pins


In previous articles I have developed a driver to access GPIO Pins, Windows CE: Using a Driver to Read/Write Hardware Registers, and a wrapper API for the driver, Windows CE: Developing an API to Access a Driver. Now it is time to use them from an application.
Because the real work is done in the driver, and because we have some simple to use wrapper functions, this application is very simple. It starts by calling the InitGPIOAPI() function, then it sets GPIO Pin 30 to be an output and then toggles the pin high and low.
#include <Windows.h>
#include <GPIO_API.h>
 
int WINAPI WinMain(     HINSTANCE hInstance,
                                                                                HINSTANCE hPrevInstance,
                                                                                LPTSTR    lpCmdLine,
                                                                                int       nCmdShow)
{
                HANDLE hGPIO_API;     
                DWORD count;
 
                RETAILMSG( 1, (TEXT("Testing GPIO\n")));
 
                hGPIO_API = InitGPIOAPI();
 
                if( hGPIO_API != INVALID_HANDLE_VALUE )
                {
                                SetOutputGPIOPin( hGPIO_API, 30 );
 
                                for( count = 0; count < 20; count++ )
                                {
                                                SetGPIOPin( hGPIO_API, 30 );
                                                RETAILMSG( 1, (TEXT( "set GPIO 30 is %s\r\n"), ReadGPIOPin(hGPIO_API, 30)?TEXT("Set"):TEXT("Clear")));
                                                Sleep( 1000 );
 
                                                ClearGPIOPin( hGPIO_API, 30 );
                                                RETAILMSG( 1, (TEXT( "clear GPIO 30 is %s\r\n"), ReadGPIOPin(hGPIO_API, 30)?TEXT("Set"):TEXT("Clear")));
                                                Sleep( 1000 );
                                }
                                DeinitGPIOAPI( hGPIO_API );
                }
                RETAILMSG(1,(TEXT("Done\n")));
 
                return 0;
}
Copyright © 2010 – Bruce Eitman
All Rights Reserved
  • Share This Post:
  • Share on Twitter
  • Share on Facebook
  • Share on Technorati

author: Bruce Eitman | Posted On Sunday, August 01, 2010 7:33 PM | Feedback (14)

Windows CE: Developing an API to Access a Driver


In my article on reading and writing GPIO pins from a driver (see Windows CE: Using a Driver to Read/Write Hardware Registers) I developed a driver that exposes GPIO pins through DeviceIoControl() calls to the driver’s XXX_IOControl() function.   Using DeviceIoControl() can be messy, so developing wrapper functions helps clean that up, but if the driver developer also created the wrapper and provided it as an API that would help.
An API is really just a lib file, or DLL, that exposes some functions.   In this case, we will expose the functions from a statically linked library, but it would be a very minor change to provide them as a DLL included in the OS.
To start, this API will provide a init and deinit functions that will gain access to the driver through CreateFile() and then clean up with CloseHandle():
HANDLE InitGPIOAPI()
{
                HANDLE hDriver;
 
                // Get a handle to the driver. Which causes the driver's XXX_Open function to be called
                hDriver = CreateFile( TEXT("XXX1:"), GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, 0 );
               
                return hDriver;
}
 
void DeinitGPIOAPI( HANDLE hDriver )
{
                CloseHandle( hDriver );
}
Now we wrapper each of the IOCTL calls:
BOOL SetGPIOPin( HANDLE hDriver, DWORD PinNumber )
{
                DWORD BytesHandled;
 
                return DeviceIoControl( hDriver,
                                IOCTL_SET_GPIO,
                                &PinNumber,
                                sizeof( PinNumber ),
                                NULL,
                                0,
                                &BytesHandled,
                                NULL );
}
 
BOOL ClearGPIOPin( HANDLE hDriver, DWORD PinNumber )
{
                DWORD BytesHandled;
 
                return DeviceIoControl( hDriver,
                                IOCTL_CLEAR_GPIO,
                                &PinNumber,
                                sizeof( PinNumber ),
                                NULL,
                                0,
                                &BytesHandled,
                                NULL );
}
 
BOOL SetInputGPIOPin( HANDLE hDriver, DWORD PinNumber )
{
                DWORD BytesHandled;
 
                return DeviceIoControl( hDriver,
                                IOCTL_SET_INPUT_GPIO,
                                &PinNumber,
                                sizeof( PinNumber ),
                                NULL,
                                0,
                                &BytesHandled,
                                NULL );
}
 
BOOL SetOutputGPIOPin( HANDLE hDriver, DWORD PinNumber )
{
                DWORD BytesHandled;
 
                return DeviceIoControl( hDriver,
                                IOCTL_SET_OUTPUT_GPIO,
                                &PinNumber,
                                sizeof( PinNumber ),
                                NULL,
                                0,
                                &BytesHandled,
                                NULL );
}
 
BOOL ReadGPIOPin( HANDLE hDriver, DWORD PinNumber )
{
                DWORD BytesHandled;
                DWORD GPIOValue;
 
                DeviceIoControl( hDriver,
                                IOCTL_READ_GPIO,
                                &PinNumber,
                                sizeof( PinNumber ),
                                &GPIOValue,
                                sizeof(GPIOValue),
                                &BytesHandled,
                                NULL );
 
                return GPIOValue & 1;
}
See how simple that was? And our application developers will love us for it because their code can be very simple. This code will even be easy for them to use from C# with a simple P/Invoke.
The sources file is straight forward for those of you using Platform Builder:
TARGETNAME=GPIO_API
TARGETTYPE=LIBRARY
RELEASETYPE=PLATFORM
 
SOURCES= \
                GPIO_API.c
 
INCLUDES=$(INCLUDES);..\Inc
To turn this into a DLL, simply change the TARGETTYPE to DYNLINK and add a def file.
Copyright © 2010 – Bruce Eitman
All Rights Reserved
  • Share This Post:
  • Share on Twitter
  • Share on Facebook
  • Share on Technorati

author: Bruce Eitman | Posted On Sunday, August 01, 2010 5:48 PM | Feedback (4)

Windows CE: Using a Driver to Read/Write Hardware Registers


A recent discussion thread in the Windows Embedded Compact Platform Development forum made it clear to me that for new Windows CE Software Engineers doing something as simple as accessing a hardware register can be very difficult. In the old days, Windows CE 5.0 and before, it was very easy; write an application that allocates a virtual address and then read or write a register. The problem with that is that allowing applications to access registers is a serious security and system stability problem. So in Windows CE 6.0 Microsoft fixed that and now only the kernel or kernel mode drivers can access hardware registers.
In the discussion thread, I suggested that the solution is to use my Driver Shell as a starting point to added code to access the CPU GPIO registers. In this article, I will do just that and then follow up with a series of articles that show how to use the driver to read and write GPIO pins using an set of wrapper APIs and an application.
I have discussed stream interface drivers in previous articles (see Windows CE: A Stream Interface Driver Shell, Windows CE: Stream Interface Driver, DllEntry(), Windows CE: Stream Interface Driver, XXX_Init(), and Windows CE: Stream Interface Driver Power Management). In those articles I developed a driver that doesn’t actually do anything that I call Driver Shell. Driver Shell is a handy starting point for developing a stream interface driver because it includes the necessary functions, bib and reg files for creating a driver that will successfully be loaded by the device manager. I started by downloading the Driver Shell source code from this blog’s Downloads page and modify the driver to add control of reading pin state, writing pin state, and setting pin direction.
But I am getting ahead of myself; we need some hardware to use. For this example, I will use some (almost) imaginary CPU because I don’t want this discussion to be about any specific CPU, but about the driver. Although I have tested the driver on a specific CPU.
The imaginary CPU has 32 GPIO pins. All of the GPIO pins can be used as input or output, and therefore can be read and written as well as the direction of the pin can be set. The following is a brief description of the registers:
ReadReg
Physical Address: 40E0_0008
 

 

Bits
Access
Pin Name
Description
<0..31>
R
LEVELx
GPIO Pin Level ‘x’ (where x = 0 to 31)
This read-only field indicates the current value of each GPIO.
0 = Pin state is low.
1 = Pin state is high.
 
DirectionReg
Physical Address: 40E0_0010

 

 

Bits
Access
Pin Name
Description
<0..31>
R/W
DIRx
GPIO Pin Direction ‘x’ (where x = 0 to 31)
This field defines the direction of the GPIO pins.
0 = Pin is an input.
1 = Pin is an output
 
SetReg
Physical Address: 40E0_0018

 

 

Bits
Access
Pin Name
Description
<0..31>
W
SETx
GPIO Pin SEt ‘x’ (where x = 0 to 31)
This field sets the output level of GPIO pins
0 = no change to pin state
1 = Pin state is driven high
 
ClearReg
Physical Address: 40E0_0020

 

 

Bits
Access
Pin Name
Description
<0..31>
W
CLEARx
GPIO Pin CLEAR ‘x’ (where x = 0 to 31)
This field clears the output level of GPIO pins
0 = no change to pin state
1 = Pin state is driven low
 
So the first step in developing code in the Driver Shell to access these registers is to define a data structure that matches the registers. The following GPIO_REGISTERS does that.
typedef struct _GPIO_REGISTERS_
{
    volatile DWORD ReadReg;
    DWORD Reserved2[2];
    volatile DWORD DirectionReg;
    DWORD Reserved3[2];
    volatile DWORD SetReg;
    DWORD Reserved4[2];
    volatile DWORD ClearReg;
} GPIO_REGISTERS;
Note that the registers are in the structure ordered by their physical addresses, and that some filler variables have been added to place the registers at the correct offset from the address of the first register.
To access the registers, what we need is a pointer to BPIO_REGISTERS that points to the address of the ReadReg. Well that isn’t exactly true is it? We need it to point to a virtual address that maps to the physical address of ReadReg. I started by adding the pointer to the DRIVERSHELL_CONTEXT structure. The DRIVERSHELL_CONTEXT structure is used by the driver to keep track of instance data and is returned to the device manager by XXX_Init().
typedef struct _DRIVERSHELL_CONTEXT
{
                DWORD Instance;
                CEDEVICE_POWER_STATE CurrentPowerState;
                HANDLE hDDKPower;
                GPIO_REGISTERS *pGPIO;
} DRIVERSHELL_CONTEXT;
Then the next step is to allocate a virtual address for the GPIO_REGISTERS structure. This is done in XXX_Init() using MmMapIoSpace(), the chages are in green below:
 
DWORD XXX_Init(ULONG   RegistryPath)
{
                HKEY hKey;
                DRIVERSHELL_CONTEXT *pDriverContext;
                PHYSICAL_ADDRESS physicalAddress;
 
                RETAILMSG( 1, (TEXT("XXX_Init\n")));
 
                pDriverContext = LocalAlloc( LMEM_FIXED, sizeof( DRIVERSHELL_CONTEXT ));
                if( pDriverContext == NULL )
                {
                                RETAILMSG( 1, (TEXT("XXX_Init failed, unable to allocate driver context\n")));
                                return FALSE;
                }
               
                hKey = OpenDeviceKey((LPCTSTR)RegistryPath);
                if ( !hKey ) {
                                RETAILMSG(1, (TEXT("Failed to open devkeypath,\r\n")));
                }
                else
                {
                                DWORD Type = REG_DWORD;
                                DWORD Data;
                                DWORD DataSize = sizeof( DWORD );
 
                                // Read values from registry if needed
                                if( ERROR_SUCCESS == RegQueryValueEx( hKey, TEXT("DeviceArrayIndex"), NULL, &Type, (LPBYTE)&Data, &DataSize ) )
                                                pDriverContext->Instance = Data;
 
                                RETAILMSG( 1, (TEXT("pDriverContext->Instance %d\n"), pDriverContext->Instance));
                                RegCloseKey (hKey);
                }
 
                // Init Power management
                pDriverContext->CurrentPowerState = D0;
                pDriverContext->hDDKPower = DDKPwr_Initialize(XXX_SetPowerState, (DWORD)pDriverContext , TRUE, 1000 );
 
                physicalAddress.QuadPart = GPIO_REGISTERS_BASE_PHYSICAL;
                pDriverContext->pGPIO = (GPIO_REGISTERS *)MmMapIoSpace(physicalAddress, sizeof(GPIO_REGISTERS), FALSE);
 
                if (pDriverContext->pGPIO == NULL )
                {
                                RETAILMSG(TRUE, (_T("XXX_Init failed to allocate pGPIO (%d)\r\n"), GetLastError()));
                                XXX_Deinit( pDriverContext );
                                return (DWORD)NULL;
                }
 
                return (DWORD)pDriverContext;
}
 
The address used to initialize the physical address is the address of the ReadReg, which is defined to as:
#define GPIO_REGISTERS_BASE_PHYSICAL (0x40E00008)
I suppose that you probably want to jump in now and start reading and writing registers, but we have some more to do first, we need to update XXX_Deinit() to free the virtual addresses that we just allocated – no nasty memory leaks here. The changes to XXX_Deinit() are in green below:
BOOL XXX_Deinit( DWORD hDeviceContext )
{
                DRIVERSHELL_CONTEXT *pDriverContext = (DRIVERSHELL_CONTEXT *)hDeviceContext;
                if( pDriverContext )
                {
                    if( pDriverContext->hDDKPower != INVALID_HANDLE_VALUE )
                                                DDKPwr_Deinitialize(pDriverContext->hDDKPower);
                                if( pDriverContext->pGPIO )
                                                MmUnmapIoSpace( pDriverContext->pGPIO, sizeof(GPIO_REGISTERS) );
                                LocalFree( pDriverContext );
                }
                return TRUE;
}
So now we have the pointer to a data structure that is mapped to the GPIO registers. To access the registers we just use the pointer, like this:
DWORD State;
State = pDriverContext->pGPIO->ReadReg;
XXX_Read() and XXX_Write() might seem like good candidates for developing code to read and write the registers, but to me they would be awkward. Instead I am going to use XXX_IOControl(). I have also decided that having IOCTLS to handle pins makes more sense than accessing the entire registers.   One reason to handle pins is that the API can be used without change even if the number of pins increases to greater than 32 on other processors. This way transitioning from one board/CPU to another will be easier, although not pain free.
To start with, add some code to read the state of a pin. We know from the code above how to read the ReadReg, we just need to add some error handling and mask off the pin that was requested:
                                case IOCTL_READ_GPIO:
                                                if( pBufOut && pBufIn && *pBufIn < 32 )
                                                {
                                                                DWORD *pPinNumber = (DWORD *)pBufIn;
                                                                DWORD *pPinValue = (DWORD *)pBufOut;
                                                                RETAILMSG( 1, (TEXT("Read GPIO pin %d\r\n"), *pPinNumber ));
                                                                *pPinValue = (pDriverContext->pGPIO->ReadReg & ( 1 << *pPinNumber )) >> *pPinNumber;
                                                                RetVal = TRUE;
                                                }
                                                else
                                                {
                                                                SetLastError(ERROR_INVALID_PARAMETER);
                                                                RetVal = FALSE;
                                                }
                                                break;
That is very simple, read the register, mask off the pin requested, then shit the result down so that we can return zero or one. The other IOCLTS are similar:
                                case IOCTL_SET_GPIO:
                                                if( pBufIn && *pBufIn < 32 )
                                                {
                                                                DWORD *pPinNumber = (DWORD *)pBufIn;
                                                                RETAILMSG( 1, (TEXT("Set GPIO pin %d\r\n"), *pPinNumber ));
                                                                pDriverContext->pGPIO->SetReg = ( 1 << *pPinNumber );
                                                                RetVal = TRUE;
                                                }
                                                else
                                                {
                                                                SetLastError(ERROR_INVALID_PARAMETER);
                                                                RetVal = FALSE;
                                                }
                                                break;
                                case IOCTL_CLEAR_GPIO:
                                                if( pBufIn && *pBufIn < 32 )
                                                {
                                                                DWORD *pPinNumber = (DWORD *)pBufIn;
                                                                RETAILMSG( 1, (TEXT("Clear GPIO pin %d\r\n"), *pPinNumber ));
                                                                pDriverContext->pGPIO->ClearReg = ( 1 << *pPinNumber );
                                                                RetVal = TRUE;
                                                }
                                                else
                                                {
                                                                SetLastError(ERROR_INVALID_PARAMETER);
                                                                RetVal = FALSE;
                                                }
                                                break;
                                case IOCTL_SET_OUTPUT_GPIO:
                                                if( pBufIn && *pBufIn < 32 )
                                                {
                                                                DWORD *pPinNumber = (DWORD *)pBufIn;
                                                                RETAILMSG( 1, (TEXT("Set Output GPIO pin %d\r\n"), *pPinNumber ));
                                                                pDriverContext->pGPIO->DirectionReg |= ( 1 << *pPinNumber );
                                                                RetVal = TRUE;
                                                }
                                                else
                                                {
                                                                SetLastError(ERROR_INVALID_PARAMETER);
                                                                RetVal = FALSE;
                                                }
                                                break;
                                case IOCTL_SET_INPUT_GPIO:
                                                if( pBufIn && *pBufIn < 32 )
                                                {
                                                                DWORD *pPinNumber = (DWORD *)pBufIn;
                                                                RETAILMSG( 1, (TEXT("Set Input GPIO pin %d\r\n"), *pPinNumber ));
                                                                pDriverContext->pGPIO->DirectionReg &= ~( 1 << *pPinNumber );
                                                                RetVal = TRUE;
                                                }
                                                else
                                                {
                                                                SetLastError(ERROR_INVALID_PARAMETER);
                                                                RetVal = FALSE;
                                                }
                                                break;
Now we have a driver that can read, write and change direction of the GPIO pins. You could certainly go from here and write code to use this driver (see Windows CE: Accessing a Stream Interface Driver from an Application) but instead of having applications directly access the driver, the next article will develop an API to wrapper the driver and simplify application development.
Copyright © 2010 – Bruce Eitman
All Rights Reserved

 

  • Share This Post:
  • Share on Twitter
  • Share on Facebook
  • Share on Technorati

author: Bruce Eitman | Posted On Sunday, August 01, 2010 4:21 PM | Feedback (11)