Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Serial ports. Enumeration and FIFO control

0.00/5 (No votes)
21 May 2003 1  
Article explains how to enumerate serial ports and manage FIFO using Device Manager and Configuration Manager.

"Johnny Bowlegs, pack your kit and trek!"

Device enumeration in the application, as a rule, means that enumeration should do the way as Windows do it in the Device Manager. For example, Windows "sees" Com1, Com2, Com3 and Com27 (it's very possible for Windows 2000/XP). Application also should "see" the same serial ports.

Where does Windows keep information about hardware (and software also) configuration? In the Registry. It is for Windows 98/Me, it is for Windows NT (at least 4.0 as I know) and for Windows 2000/XP also. Information is collected by PnP (Plug'n'Play) Manager and it is supported by Device Manager (setupapi.dll, SDK) and Configuration Manager (cfgmgr32.dll, DDK).

Configuration and Device managers are very tightly connected together via common data structure and some similar functions. Both kits works with Registry when they access to device configuration - they use device classes, create special handles for devices, open device keys for access to device resources (IRQ, etc).

Important Note: Sometimes (for example when you install some multiport cards) device keys for new ports will not be created in the Registry. It can be solved via open property dialog (Device Manager) for each port.

Functions

Following are the functions from Device manager:

  • WINSETUPAPI BOOL WINAPI SetupDiClassGuidsFromNameA (IN  PCSTR  ClassName, 
     OUT LPGUID ClassGuidList,IN DWORD ClassGuidListSize,OUT PDWORD RequiredSize);

    Device class represents group of devices. For serial ports, parallel ports device class is "Ports". For representing device class in Registry, class name is not suitable enough. In this case GUID will be preferable and every device class has also GUID for the reference in the Registry. SetupDiClassGuidsFromNameA "transforms" class name in GUID.

  • WINSETUPAPI HDEVINFO WINAPI SetupDiGetClassDevsA (IN CONST GUID *ClassGuid,  
        OPTIONAL IN PCSTR Enumerator, OPTIONAL IN HWND hwndParent, 
        OPTIONAL IN DWORD Flags);

    SetupDiEnumDeviceInfo returns a context structure for a device information element of a device information set. Each call returns information about one device; the function can be called repeatedly to get information about several devices.

  • WINSETUPAPI BOOL WINAPI SetupDiDestroyDeviceInfoList(IN HDEVINFO DeviceInfoSet);

    The SetupDiDestroyDeviceInfoList function destroys a device information set (SetupDiGetClassDevsA) and frees all associated memory.

Following function is from Configuration Manager:

  • CMAPI CONFIGRET WINAPI CM_Open_DevNode_Key(IN DEVINST dnDevNode,
       IN REGSAM samDesired, IN ULONG ulHardwareProfile, 
       IN  REGDISPOSITION Disposition, OUT PHKEY phkDevice, IN ULONG ulFlags);

    CM_Open_DevNode_Key opens a registry storage key associated with a device instance.

Next figure demonstrates the way of using these functions.

Image 1

Enumerating serial ports

So any device Registry has a device key. For this key exist some names that hold information about configuration parameters. Value of name "portname" is name of port. This value can be "LPN" for parallel ports or "COMN" for serial ports (where N is number of port).

On the next figure, you can see how system do enumeration:

Image 2

I created following functions for enumerations: BeginEnumeratePorts, EnumeratePortsNext, EndEnumeratePorts.

  • HANDLE  BeginEnumeratePorts(VOID);

    The BeginEnumeratePorts function opens an enumeration handle for serial ports and returns this handle.

  • BOOL EnumeratePortsNext (HANDLE DeviceInfoSet, LPTSTR lpBuffer);

    The EnumeratePortsNext function continues enumeration from a previous call to the BeginEnumeratePorts function. You need to prepare buffer (lpBuffer) before calling this function. If the call is success, the function returns true and sends name of port in buffer. Call this function till it returns false.

  • BOOL  EndEnumeratePorts(HANDLE DeviceInfoSet);

    The EndEnumeratePorts function closes the specified enumeration handle. Below you can see source code of these functions:

#include <setupapi.h>
#define MAX_NAME_PORTS 7
#define RegDisposition_OpenExisting (0x00000001) // open key only if exists
#define CM_REGISTRY_HARDWARE        (0x00000000)

typedef DWORD WINAPI
  (* CM_Open_DevNode_Key)(DWORD, DWORD, DWORD, DWORD, ::PHKEY, DWORD);

HANDLE  BeginEnumeratePorts(VOID)
{
  BOOL guidTest=FALSE;
  DWORD RequiredSize=0;
  int j;
  HDEVINFO DeviceInfoSet;
  char* buf;

 guidTest=SetupDiClassGuidsFromNameA(
       "Ports",0,0,&RequiredSize);
 if(RequiredSize < 1)return -1;

 buf=malloc(RequiredSize*sizeof(GUID));

 guidTest=SetupDiClassGuidsFromNameA(
      "Ports",buf,RequiredSize*sizeof(GUID),&RequiredSize);

 if(!guidTest)return -1;


 DeviceInfoSet=SetupDiGetClassDevs(
      buf,NULL,NULL,DIGCF_PRESENT);
 if(DeviceInfoSet == INVALID_HANDLE_VALUE)return -1;

 free(buf);

 return DeviceInfoSet;
}

BOOL EnumeratePortsNext(HANDLE DeviceInfoSet, LPTSTR lpBuffer)
{
  static CM_Open_DevNode_Key OpenDevNodeKey=NULL;
  static HINSTANCE CfgMan;

  int res1;
  char DevName[MAX_NAME_PORTS]={0};
  static int numDev=0;
  int numport;

  SP_DEVINFO_DATA DeviceInfoData={0};
  DeviceInfoData.cbSize=sizeof(SP_DEVINFO_DATA);


  if(!DeviceInfoSet || !lpBuffer)return -1;
  if(!OpenDevNodeKey)
  {
    CfgMan=LoadLibrary("cfgmgr32");
    if(!CfgMan)return FALSE;
    OpenDevNodeKey=
       (CM_Open_DevNode_Key)GetProcAddress(CfgMan,"CM_Open_DevNode_Key");
    if(!OpenDevNodeKey)
    {
      FreeLibrary(CfgMan);
      return FALSE;
    }
  }

  while(TRUE)
  {

    HKEY KeyDevice;
    DWORD len;
    res1=SetupDiEnumDeviceInfo(
        DeviceInfoSet,numDev,&DeviceInfoData);

    if(!res1)
    {
      SetupDiDestroyDeviceInfoList(DeviceInfoSet);
      FreeLibrary(CfgMan);
      OpenDevNodeKey=NULL;
      return FALSE;
    }


    res1=OpenDevNodeKey(DeviceInfoData.DevInst,KEY_QUERY_VALUE,0,
      RegDisposition_OpenExisting,&KeyDevice,CM_REGISTRY_HARDWARE);
    if(res1 != ERROR_SUCCESS)return NULL;
    len=MAX_NAME_PORTS;

    res1=RegQueryValueEx(
      KeyDevice,    // handle of key to query
      "portname",    // address of name of value to query
      NULL,    // reserved
      NULL,    // address of buffer for value type
      DevName,    // address of data buffer
      &len     // address of data buffer size
    );

    RegCloseKey(KeyDevice);
    if(res1 != ERROR_SUCCESS)return NULL;
    numDev++;
    if(memicmp(DevName, "com", 3))continue;
    numport=atoi(DevName+3);
    if(numport > 0 && numport <= 256)
    {
      strcpy(lpBuffer,DevName);
      return TRUE;
    }

    FreeLibrary(CfgMan);
    OpenDevNodeKey=NULL;
    return FALSE;
  }
}

BOOL  EndEnumeratePorts(HANDLE DeviceInfoSet)
{
  if(SetupDiDestroyDeviceInfoList(DeviceInfoSet))return TRUE;
  else return FALSE;
}

FIFO Control

To open serial port properties in the Device Manager on the second page, you can find button Additional (Advanced) that opens FIFO dialog:

Image 3

The main idea of next part of article is the demonstration of control of this page from application. There is a little difference for various operating systems, but main idea is the same. Set/get FIFO control enable/disable and size are available via access to some values in the Registry (for serial port device key).

  • Windows 2000/XP

    To enable/disable FIFO the application, we should use the name "ForceFifoEnable". If value is equal to 0, FIFO is disabled. If 1, enabled.

    To set/get FIFO size the application should use names:

    • To receive buffer - RxFIFO

      Possible values: 1,4,8,14 (that is mean size of buffer)

    • for transmit buffer - TxFIFO

      Possible values: from 1 to 16 (that is mean size of buffer)

  • Windows 98/Me

    Enable/disable FIFO and FIFO size are controlled via name "settings". Value of name "settings" consists of four bytes.

    To enable/disable FIFO the application should use first byte of "settings". If value is equal to 0x00, FIFO is disabled, if 0x02, FIFO enabled.

    To set/get FIFO size for receiving buffer, the application should use fourth byte of "settings". Possible values: 0x00,0x40,0x80,0xC0 (that is means 1, 4, 8, 16 for size of buffer).

    To set/get FIFO size for transmitting buffer, the application should use second byte of "settings". Possible values: 0x01, 0x06, 0x0B, 0x10 (that means 1, 4, 8, 14 for size of buffer).

Function GetDeviceKey returns device key using port name as input parameter:

HKEY GetDeviceKey(LPTSTR portName)
{
  static HINSTANCE CfgMan;        
  static CM_Open_DevNode_Key OpenDevNodeKey=NULL;        
  static int numDev=0;
  int res1;
  BOOL guidTest=FALSE;
  DWORD RequiredSize=0;
  char* buf;
  HDEVINFO DeviceInfoSet;
  SP_DEVINFO_DATA DeviceInfoData={0};
  char DevName[MAX_NAME_PORTS]={0};

  guidTest=SetupDiClassGuidsFromNameA(
    "Ports",0,0,&RequiredSize);
  if(RequiredSize < 1)return -1;

  buf=malloc(RequiredSize*sizeof(GUID));

  guidTest=SetupDiClassGuidsFromNameA(
    "Ports",buf,RequiredSize*sizeof(GUID),&RequiredSize);

  if(!guidTest)return -1;


  DeviceInfoSet=SetupDiGetClassDevs(
    buf,NULL,NULL,DIGCF_PRESENT);
  if(DeviceInfoSet == INVALID_HANDLE_VALUE)return -1;

  free(buf);

  if(!OpenDevNodeKey)
  {
    CfgMan=LoadLibrary("cfgmgr32");
    if(!CfgMan)return -1;
    OpenDevNodeKey=
     (CM_Open_DevNode_Key)GetProcAddress(CfgMan,"CM_Open_DevNode_Key");
    if(!OpenDevNodeKey)
    {
      FreeLibrary(CfgMan);
      return -1;
    }
  }

  DeviceInfoData.cbSize=sizeof(SP_DEVINFO_DATA);

  while(TRUE)
  {
    HKEY KeyDevice;
    DWORD len;
    res1=SetupDiEnumDeviceInfo(
      DeviceInfoSet,numDev,&DeviceInfoData);

    if(!res1)
    {
      SetupDiDestroyDeviceInfoList(DeviceInfoSet);
      FreeLibrary(CfgMan);
      OpenDevNodeKey=NULL;
      return -1;
    }

    res1=OpenDevNodeKey(DeviceInfoData.DevInst,KEY_QUERY_VALUE | KEY_WRITE,0,
      RegDisposition_OpenExisting,&KeyDevice,CM_REGISTRY_HARDWARE);
    if(res1 != ERROR_SUCCESS)
    {
      SetupDiDestroyDeviceInfoList(DeviceInfoSet);
      FreeLibrary(CfgMan);
      OpenDevNodeKey=NULL;
      return -1;
    }
    len=MAX_NAME_PORTS;

    res1=RegQueryValueEx(
      KeyDevice,    // handle of key to query
      "portname",    // address of name of value to query
      NULL,    // reserved
      NULL,    // address of buffer for value type
      DevName,    // address of data buffer
      &len     // address of data buffer size
      );
    if(res1 != ERROR_SUCCESS)
    {
      RegCloseKey(ERROR_SUCCESS);
      FreeLibrary(CfgMan);
      OpenDevNodeKey=NULL;
      return -1;
    }
    if(!stricmp(DevName,portName))
    {
      FreeLibrary(CfgMan);
      OpenDevNodeKey=NULL;
      return KeyDevice;
    }
    numDev++;
  }

}

When device key is known, Registry functions (RegQueryValueEx, RegSetValueEx) are used to access FIFO parameters. And last function, RegCloseKey, to close device key in Registry.

Demo application

Image 4

After running push button "Enumerate", names of serial ports will appear in list box. Clicking on name, you will obtain information about current FIFO status. Change this status via controls and pushing button "Set". You can download source code (C++ Builder) and exe file that is attached to article.

Additional Links

TPortControl v.2.00 for synchronous RS232 serial communication (C++ Builder component, registration required only)

Vladimir Afanasyev.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here