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 shift 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