Программирование PC FTDI: события подключения и отключения USB-устройства Mon, February 27 2017  

Поделиться

нашли опечатку?

Пожалуйста, сообщите об этом - просто выделите ошибочное слово или фразу и нажмите Shift Enter.

FTDI: события подключения и отключения USB-устройства Печать
Добавил(а) microsin   

Многие программы, работающие с устройством USB, теряют работоспособность при случайной потере связи. Например, если некачественное подключение (плохой контакт в разъеме miniUSB или microUSB, что довольно часто случается), или когда устройство было случайно переподключено в порт USB, приходится перезапускать программу ПО хоста. Чтобы корректно обрабатывать такие ситуации в программе, необходимо программно детектировать события подключения и отключения устройства USB, чтобы можно было осуществить адекватные действия - повторная инициализация связи с устройством, настройка параметров и т. п. Здесь приведен перевод апноута FTDI AN_152 [1], как раз посвященного этой важной теме.

В этой статье показано, как программно детектировать подключение устройств USB в порт хоста и их отключение, когда ПО хоста работает под управлением Windows. Пример ПО хоста называется D2XXNotify, и его можно свободно скачать с сайта FTDI в разделе примеров программ на C++ [2]. Приложение ПО хоста прослушивает сообщение системы WM_DEVICECHANGE, которое активизируется, обрабатывается и отправляется окну приложения, когда пользователь что-то подключает к компьютеру или отключает, например вставляет в порт USB какое-то устройство (в частности устройство USB на чипе FTDI). Любое программное обеспечение, описываемое в этом документе, приведено только для информации, и никак не поддерживается компанией FTDI.

[Как детектировать события установки и извлечения устройства USB]

Операционная система Windows рассылает широковещательные оповещения (broadcasts basic notifications message) любому приложению, создавшему окно верхнего уровня (сейчас нас интересует приложение ПО хоста USB, которое управляет устройствами USB на чипах FTDI, хотя это может быть любое приложение, работающее с аппаратурой), для этого приложение должно обработать сообщение WM_DEVICECHANGE. Под окном верхнего уровня приложения (top-level window) подразумевается окно, которое может быть обработано стандартным менеджером окон Windows: к примеру окно может быть независимо от других окон перемещаться пользователем по рабочему столу, и у окна пользователь может менять размеры.

Сообщение WM_DEVICECHANGE оповещает приложение о событии изменения конфигурации аппаратуры среди устройств, подключенных и установленных на компьютере. Если приложение не имеет окна верхнего уровня, или если оно требует оповещения о других изменениях среди устройств, то приложение может использовать функцию Win32 Application Programming interface (API, интерфейс программирования системы) RegisterDeviceNotification, которая регистрирует приложение в системе для приема событий оповещения, связанных с устройствами (device notification events). Для этой функции будет предоставлен хендл (handle), который указывает на структуру DEV_BROADCAST_DEVICEINTERFACE (в ней содержится вся информация о классе устройства) и флаг, который может быть DEVICE_NOTIFY_WINDOW_HANDLE (если это оконное приложение) или DEVICE_NOTIFY_SERVICE_HANDLE (если это сервис системы). Функция Win32 API function UnRegisterDeviceNotification закрывает указанный хендл, который был возвращен функцией RegisterDeviceNotification. Подробности см. на сайте Microsoft [3].

[Функция RegisterDeviceNotification]

Следующий пример кода на языке C показывает, как функция RegisterDeviceNotification регистрирует приложение в системе, чтобы оно получало оповещение об устройствах.

DEV_BROADCAST_DEVICEINTERFACE dbch;
dbch.dbcc_size = sizeof(dbch);
dbch.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;

for (int i = 0; i < sizeof(GuidInterfaceList); i++) { dbch.dbcc_classguid = GuidInterfaceList[i]; dbch.dbcc_name[0] = '\0'; NotificationHandle = RegisterDeviceNotification( GetSafeHwnd(), &dbch, DEVICE_NOTIFY_WINDOW_HANDLE); }

Функция в параметрах принимает хендл GetSafeHwnd(), флаг DEVICE_NOTIFY_WINDOW_HANDLE и указатель на структуру данных DEV_BROADCAST_DEVICEINTERFACE. Флаг DEVICE_NOTIFY_WINDOW_HANDLE указывает, что вызывающий код относится к окну приложения, и структура данных DEV_BROADCAST_DEVICEINTERFACE задает размер этой структуры, тип устройства DBT_DEVTYP_DEVICEINTERFACE, имя устройств и уникальный идентификатор интерфейса устройства (device Globally Unique Identifier, GUID). Список (массив) GuidInterfaceList будет расшифрован в следующей секции.

Примечание: значения структуры данных DEV_BROADCAST_DEVICEINTERFACE должны быть отредактированы в соответствии с требованиями Вашего приложения. Чтобы использовать приведенный выше код, убедитесь, что заголовок Dbt.h подключен к приложению. Дополнительную информацию см. на сайте Microsoft [3].

[Device Globally Unique Identifier (GUID)]

Устройства, поддерживающие технологию Plug and Play (PnP), обычно связаны с двупя разными GUID-ами: device interface GUID и device class GUID. Идентификатор device class GUID определяет широкую категорию устройств. Когда Вы просматриваете дерево устройств в Менеджере Устройств (Windows Device Manager), то они там рассортированы по типам устройств. Каждое устройство принадлежит к классу, и каждый класс идентифицируется по классу устройства device class GUID.

Идентификатор device interface GUID указывает частный интерфейс ввода/вывода (particular input/output interface). Подразумевается, что каждый экземпляр device interface GUID поддерживает один и тот же базовый набор входов/выходов. Идентификатор device interface GUID - это то, что драйвер зарегистрирует и будет разрешать или запрещать, базируясь на состоянии PnP. В предыдущей секции пример кода показывает, как в цикле for регистрируется несколько оповещений для каждого GUID.

Следующий код дает список GUID для классов интерфейса устройств.

static const GUID GuidInterfaceList[] =
{
   // USB Raw Device Interface Class GUID
   {
      0xa5dcbf10, 0x6530, 0x11d2,
      {0x90, 0x1f, 0x00, 0xc0, 0x4f, 0xb9, 0x51, 0xed}
   },
   // Disk Device Interface Class GUID
   {
      0x53f56307, 0xb6bf, 0x11d0,
      {0x94, 0xf2, 0x00, 0xa0, 0xc9, 0x1e, 0xfb, 0x8b}
   },
   //Human Interface Device Class GUID
   {
      0x4d1e55b2, 0xf16f, 0x11Cf,
      {0x88, 0xcb, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30}
   },
   // FTDI_D2XX_Device Class GUID
   {
      0x219d0508, 0x57a8, 0x4ff5,
      {0x97, 0xa1, 0xbd, 0x86, 0x58, 0x7c, 0x6c, 0x7e}
   },
   // FTDI_VCP_Device Class GUID
   {
      0x86e0d1e0L, 0x8089, 0x11d0, 
      {0x9c, 0xe4, 0x08, 0x00, 0x3e, 0x30, 0x1f, 0x73}
   },
};

[Обработка сообщения WM_DEVICECHANGE на C++]

При любом изменении с состоянии устройства (например, устройство было подключено или было извлечено) это событие будет детектироваться и приложению будет отправлено сообщение WM_DEVICECHANGE, и структура сообщения передаст значение произошедшего события в параметре сообщения WParam. Поле wParam может получить различные значения из файла заголовка Dbt.h. Например, DBT_DEVICEARRIVAL и DBT_DEVICEREMOVALCOMPLETE показывают соответственно подключение и извлечение устройства. Подробности см. на сайте Microsoft [4].

Processing WM DeviceChange message

Рис. 2.1. Алгоритм обработки сообщения WM_DeviceChange.

Пример кода ниже показывает, как в приложении может быть обработано сообщение WM_DEVICECHANGE, когда это сообщение принято приложением.

BOOL CD2XXNotifyDlg::OnDeviceChange(UINT EventType, DWORD dwData)
{
   char Word [50];
   char Word1 [57];
   char Word2 [50];
   char Word3 [50];
   char ch = ' ';
   CString Msg = "duh";
   
   switch( EventType)
   {
   case DBT_CONFIGCHANGECANCELED:
      Msg.Format("DBT_CONFIGCHANGECANCELED");
      break;
   case DBT_CONFIGCHANGED:
      Msg.Format("DBT_CONFIGCHANGED");
      break;
   case DBT_CUSTOMEVENT:
      Msg.Format("DBT_CUSTOMEVENT");
      break;
   case DBT_DEVICEARRIVAL:
      Msg.Format("DBT_DEVICEARRIVAL");
      break;
   case DBT_DEVICEQUERYREMOVE:
      Msg.Format("DBT_DEVICEQUERYREMOVE");
      break;
   case DBT_DEVICEQUERYREMOVEFAILED:
      Msg.Format("DBT_DEVICEQUERYREMOVEFAILED");
      break;
   case DBT_DEVICEREMOVEPENDING:
      Msg.Format("DBT_DEVICEREMOVEPENDING");
      break;
   case DBT_DEVICEREMOVECOMPLETE:
      Msg.Format("DBT_DEVICEREMOVECOMPLETE");
      break;
   case DBT_DEVICETYPESPECIFIC:
      Msg.Format("DBT_DEVICETYPESPECIFIC");
      break;
   case DBT_QUERYCHANGECONFIG:
      Msg.Format("DBT_QUERYCHANGECONFIG");
      break;
   case DBT_DEVNODES_CHANGED:
      Msg.Format("DBT_DEVNODES_CHANGED");
      break;
   case DBT_USERDEFINED:
      Msg.Format("DBT_USERDEFINED");
      break;
   default:
      Msg.Format("Event type %d",EventType);
   }
  
   PDEV_BROADCAST_DEVICEINTERFACE pdbch = (PDEV_BROADCAST_DEVICEINTERFACE)dwData;
   if( pdbch!=NULL && pdbch->dbcc_devicetype==DBT_DEVTYP_DEVICEINTERFACE)
   {
      CString Msg2;
      Msg2.Format("%s: %s",Msg,pdbch->dbcc_name);
      Msg = Msg2;
   }
   if (Msg == "DBT_DEVNODES_CHANGED")
   {
      CListBox* EventList = (CListBox*)GetDlgItem(IDC_EVENT_LIST);
      EventList->AddString(Msg);
   }
   else
   {
      strncpy(Word,Msg,17);
      Word[17] = '\0';
      if ( strcmp ( Word, "DBT_DEVICEARRIVAL" )== 0 )
      {
         strncpy(Word1,Msg,44);
         Word1[44] = '\0';
         strncpy(Word2, Word1, 17);
         Word2[17] = '\0';
         strncpy(Word3, &Word1[27], 17);
         Word3[17] = '\0';
      }
      else
      {
         strncpy(Word,Msg,24);
         Word[24] = '\0';
         if ( strcmp ( Word, "DBT_DEVICEREMOVECOMPLETE" )== 0 )
         {
            strncpy(Word1,Msg,51);
            Word1[51] = '\0';
            strncpy(Word2, Word1, 24);
            Word2[24] = '\0';
            strncpy(Word3, &Word1[34], 17);
            Word3[17] = '\0'; }
         }
         if (Word3[0]== 'V')
         {
            CListBox* EventList = (CListBox*)GetDlgItem(IDC_EVENT_LIST);
            EventList->AddString(Word2);
            EventList->AddString(Word3);
         }
      }
   }
   return TRUE;
}

Программа D2XXNotify предоставляет пользователю интерфейс, в котором детектируются установка или извлечение устройств USB на компьютере. Исходный код этого приложения доступен на сайте FTDI [2].

[Запуск исполняемого файла]

Готовый исполняемый файл приложения D2XXNotify.exe можно загрузить с сайта FTDI [2] в составе архива D2XXNotify.zip (в этом же архиве содержится также и исходный код приложения). Распакуйте архив с помощью любого архиватора, например WinZIP или WinRAR. Двойным щелчком в Проводнике запустите приложение D2XXNotify.exe. Появится следующее окно:

D2XXNotify run

Рис. 3.1. Запуск приложения D2XXNotify из двоичного исполняемого файла.

Когда устройство USB подключается в порт USB компьютера, то будет отправлено событие оповещения интерфейса устройства USB, и приложение получит сообщение WM_DEVICECHANGE. Приложение вызовет соответствующую функцию, указанную в карте сообщений (см. ниже секцию статьи "Message Map"), и в окне появится информация о подключении, как это показано на скриншоте 3.2.

D2XXNotify receive DBT DEVICEARRIVAL

Рис. 3.2. Приложение получило сообщение DBT_DEVICEARRIVAL (было подключено устройство USB).

На рис. 3.2 видно, что приложение D2XXNotify отобразило сообщения DBT_DEVNODES_CHANGED, DBT_DEVICEARRIVAL и PID и VID устройства, когда оно было добавлено в систему(операционная система отправляет события устройства DBT_DEVNODES_CHANGED и DBT_DEVICEARRIVAL).

Когда устройство USB извлекается из компьютера, будет отправлено оповещение события интерфейса устройства USB, и приложение получит сообщение WM_DEVICECHANGE. На экране появится текст, как это видно в примере на скриншоте 3.3.

D2XXNotify receive DBT DEVICECOMPLETE

Рис. 3.3. Приложение получило сообщение DBT_DEVICECOMPLETE (устройство USB было отключено).

На рис. 3.3 видно, что приложение D2XXNotify отобразило сообщения DBT_DEVNODES_CHANGED, DBT_DEVICEREMOVECOMPLETE и PID и VID устройства, когда оно было извлечено из системы (операционная система отправляет события устройства DBT_DEVNODES_CHANGED и DBT_ DEVICEREMOVECOMPLETE).

Приложение можно также запустить из среды разработки Microsoft Visual Studio. Для этого запустите Visual Studio, откройте файл d2xxnotify.sln из распакованного архива, выполните стандартные операции по очистке проекта (Clean Solution) и сборке (Rebuild Solution). После этого можно запустить приложение (меню Debug -> Start Debugging или Start Without Debugging).

D2XXNotify clean

D2XXNotify rebuild

D2XXNotify debug

[Message Map]

Message Map - способ обработки сообщений приложения, определенный в библиотеке классов Microsoft Foundation Class (MFC). Это таблица, в которой отображена взаимосвязь между событиями и соответствующими функциями. Когда окно принимает сообщение, MFC сканирует message map окна, чтобы определить - есть ли обработчик (handler) для этого события, который нужно вызвать. Следующий пример кода на языке C++ показывает, как message map может использоваться в приложении.

class CAboutDlg : public CDialog
{
   void CD2XXNotifyDlg::OnPaint();
   BOOL CD2XXNotifyDlg::OnDeviceChange(UINT EventType, DWORD dwData);protected:
   DECLARE_MESSAGE_MAP()
};
  
void CAboutDlg::DoDataExchange(CDataExchange* pDX) { ... } BEGIN_MESSAGE_MAP(CD2XXNotifyDlg, CDialog) ON_WM_PAINT() ON_WM_DEVICECHANGE() END_MESSAGE_MAP()
void CD2XXNotifyDlg::OnPaint() { ... } BOOL CD2XXNotifyDlg::OnDeviceChange(UINT EventType, DWORD dwData) { ... }

[Обработка сообщения WM_DEVICECHANGE на C#]

Пример детектирования подключения/отключения устройства построен на основе переопределения в приложении процедуры WndProc. В процедуру передается параметр Message, поле Msg которого обрабатывается на предмет появления сообщения WM_DEVICECHANGE. Если получено такое сообщение, то поле wParam декодируется, чтобы понять к какому типу принадлежит сообщение. Для типа сообщения DBT_DEVICEARRIVAL производится дополнительное декодирование, для чего поле LParam сообщения привязывается к соответствующей информационной структуре.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;
 
namespace D2XXNotify_Csharp
{
   public partial class Form1 : Form
   {
      const int WM_DEVICECHANGE = 0x0219;
      const int DBT_DEVICEARRIVAL = 0x8000;        // система нашла новое устройство
      const int DBT_DEVICEREMOVECOMPLETE = 0x8004; // устройство было удалено
      const int DBT_DEVNODES_CHANGED = 0x0007;
      const UInt32 DBT_DEVTYP_DEVICEINTERFACE = 0x00000005;
      const UInt32 DBT_DEVTYP_PORT = 0x00000003;
 
      [StructLayout(LayoutKind.Sequential)]
      struct DEV_BROADCAST_HDR
      {
         public UInt32 dbcd_size;
         public UInt32 dbcd_devicetype;
         public UInt32 dbcd_reserved;
      }
 
      [StructLayout(LayoutKind.Sequential)]
      struct DEV_BROADCAST_PORT_Fixed
      {
         UInt32 dbcp_size;
         UInt32 dbcp_devicetype;
         UInt32 dbcp_reserved;
         //[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 255)]
         //public char[] dbcc_name;
      }
 
      [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
      public struct DEV_BROADCAST_DEVICEINTERFACE
      {
         public int dbcc_size;
         public int dbcc_devicetype;
         public int dbcc_reserved;
         public Guid dbcc_classguid;
         [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
         public char[] dbcc_name;
      }
      
      public Form1()
      {
         InitializeComponent();
      }
 
      [System.Security.Permissions.PermissionSet(System.Security.Permissions.SecurityAction.Demand,
                                                 Name = "FullTrust")]
      protected override void WndProc(ref Message m)
      {
         String Msg = "";
 
         switch (m.Msg)
         {
         case WM_DEVICECHANGE:
            if (m.WParam.ToInt64() == DBT_DEVNODES_CHANGED)
            {
               Msg = "DBT_DEVNODES_CHANGED";
               listBox1.Items.Add(Msg);
            }
            else if (m.WParam.ToInt64() == DBT_DEVICEARRIVAL)
            {
               Msg = "DBT_DEVICEARRIVAL";
               listBox1.Items.Add(Msg);
               DEV_BROADCAST_HDR DevHdr =
                  (DEV_BROADCAST_HDR)Marshal.PtrToStructure(m.LParam,
                                                            typeof(DEV_BROADCAST_HDR));
               if (DevHdr.dbcd_devicetype == DBT_DEVTYP_DEVICEINTERFACE)
               {
                  DEV_BROADCAST_DEVICEINTERFACE DisplayDevIF =
                     (DEV_BROADCAST_DEVICEINTERFACE)Marshal.PtrToStructure(m.LParam,
                                                                           typeof(DEV_BROADCAST_DEVICEINTERFACE));
                  Msg = new String(DisplayDevIF.dbcc_name);
                  listBox1.Items.Add(Msg);
               }
               if (DevHdr.dbcd_devicetype == DBT_DEVTYP_PORT)
               {
                  Msg = Marshal.PtrToStringUni((IntPtr)(m.LParam.ToInt32() +
                                                        Marshal.SizeOf(typeof(DEV_BROADCAST_PORT_Fixed))));
                  listBox1.Items.Add(Msg);
               }
            }
            else if (m.WParam.ToInt64() == DBT_DEVICEREMOVECOMPLETE)
            {
               Msg = "DBT_DEVICEREMOVECOMPLETE";
               listBox1.Items.Add(Msg);
            }
            else
            {
               Msg = String.Format("{0:X8}", m.WParam.ToInt64());
               listBox1.Items.Add(Msg);
            }
            break;
         }
         base.WndProc(ref m);
      }
   }
}

D2XXNotify csharp

Обратите внимание, что в этих примерах кода совсем не использовалась библиотека D2XX компании FTDI - обработка событий подключения и отключения устройств осуществляется на основе системы сообщений Windows API.

Исходные коды и исполняемые exe-файлы тестовых программ можно скачать по ссылке [6].

[Ссылки]

1How To Detect The Connection And Removal Of USB Devices On A System site:ftdichip.com.
2. Visual C++ Examples site:ftdichip.com.
3. RegisterDeviceNotification function site:msdn.microsoft.com.
4. WM_DEVICECHANGE message site:msdn.microsoft.com.
5. Класс-обертка для AVR-USB-MEGA16 с поддержкой событий.
6150612D2XXNotify.zip - исходный код проектов C++ и C# для Visual Studio.

 

Добавить комментарий


Защитный код
Обновить

Top of Page