英创信息技术工控主板USB通信方案介绍

描述

英创AMR9系列工控主板可以使用USB与PC连接并进行通信。在主板上,我们将USB引到了COM1,使得我们可以通过操作串口的方式来操作USB口的连接与收发。在PC端,我们提供一个使用WDK提供的驱动,来进行USB通信的解决方案。

驱动的安装及说明请参考文章《英创工控主板USB驱动安装说明》。

工控主板端USB收发程序说明

英创AMR9系列工控主板已将USB引导COM1,这里使用开发光盘中的串口例程SPT_HEX做USB收发调试例程。

嵌入式主板

代码中需要注意的地方:

BOOL ret = OpenPort(portName, baud, databit, stopbit, parity); /* 打开串口*/

在界面中选择COM1打开,调用函数OpenPort,参数portName值为_T('COM1:')。

波特率,停止位,数据位,校验参数不产生实际作用,这里可以使用默认值。

GetCommTimeouts(m_hComm, &CommTimeOuts);
        CommTimeOuts.ReadIntervalTimeout = 100; /* 接收字符间最大时间间隔*/
        CommTimeOuts.ReadTotalTimeoutMultiplier = 1;
        CommTimeOuts.ReadTotalTimeoutConstant = 0; /* 读数据总超时常量*/
        CommTimeOuts.WriteTotalTimeoutMultiplier = 1; /* 设置写超时,避免阻塞*/
        CommTimeOuts.WriteTotalTimeoutConstant = 2; /* 设置写超时,避免阻塞*/
        SetCommTimeouts(m_hComm, &CommTimeOuts) ;

在函数OpenPort中,调用SetCommTimeouts设置超时函数时最好设置有写超时,否则在发数据时,在PC端接收完数据前,WriteFile函数会一直阻塞。

PC端USB收发程序说明

在WDK提供的驱动中,创建了2个pipe来进行USB的收发,通过对这两个pipe的ReadFile和WriteFile操作进行USB通信。

嵌入式主板

1、检测设备

USB设备支持即插即用,当设备连接或断开时,都可以收到系统将发出的消息。 

使用该消息需要先引用系统头文件

#include 'dbt.h'

并在在MESSAGEMAP宏中添加设备检测消息WM_DEVICECHANGE

BEGIN_MESSAGE_MAP(Cusb_connDlg, CDialog)
                ON_WM_PAINT()
                ON_WM_QUERYDRAGICON()
                //}}AFX_MSG_MAP
                …
                ON_WM_DEVICECHANGE() 
        END_MESSAGE_MAP() 

在对话框头文件中添加该消息的响应函数

afx_msg BOOL OnDeviceChange(UINT nEventType,DWORD_PTR dwData); 

当设备发生变化(USB接上或断开)时,OnDeviceChange函数会得到响应。
        BOOL Cusb_connDlg::OnDeviceChange( UINT nEventType, DWORD_PTR dwData )
        {
                switch(nEventType)
                {
                        case DBT_DEVICEARRIVAL:
                                // UpdateUsbDeviceList(); 
                        break; 
                        case DBT_DEVICEREMOVECOMPLETE: 
                                //UpdateUsbDeviceList();
                                //UpdateWindow(); 
                        break;
                }
                return TRUE;
        }

获取USB读写管道路径 

首先,获取设备路径需要使用到setupapi.lib库中的相关函数,所以需要在工程中添加setupapi.lib库

嵌入式主板

添加setupapi.lib库的头文件

#include  

添加驱动代码的public.h头文件,该头文件定义了USB设备的GUID

// {6068EB61-98E7-4c98-9E20-1F068295909A}

DEFINE_GUID(GUID_CLASS_USBSAMP_USB, 
        0x873fdf, 0x61a8, 0x11d1, 0xaa, 0x5e, 0x0, 0xc0, 0x4f, 0xb1, 0x72, 0x8b); 

添加一个GetUsbDeviceFileName函数通过GUID获取设备地址

BOOL Cusb_connDlg::GetUsbDeviceFileName(LPGUID pGuid, LPWSTR devName)
        {
                ULONG NumberDevices;
                HDEVINFO hardwareDeviceInfo;
                SP_DEVICE_INTERFACE_DATA deviceInfoData;
                ULONG i; 
                BOOLEAN done;
                PSP_DEVICE_INTERFACE_DETAIL_DATA functionClassDeviceData = NULL;
                ULONG predictedLength = 0;
                ULONG requiredLength = 0;
                hardwareDeviceInfo =
                        SetupDiGetClassDevs ( pGuid,
                        NULL, // Define no enumerator (global)
                        NULL, // Define no 
                        (DIGCF_PRESENT | // Only Devices present
                        DIGCF_DEVICEINTERFACE)); // Function class devices. 
                if (hardwareDeviceInfo == INVALID_HANDLE_VALUE) {
                        return FALSE ;
                }
                NumberDevices = 4; 
                done = FALSE;
                deviceInfoData.cbSize = sizeof (SP_DEVICE_INTERFACE_DATA); 
                i=0;
                while (!done)
                {
                        NumberDevices *= 2;
                        for (; i < NumberDevices; i++)
                        {
                                if (SetupDiEnumDeviceInterfaces (hardwareDeviceInfo, 
                                        0, // We don't care about specific PDOs
                                        pGuid,
                                        i,
                                        &deviceInfoData)) 
                                { 
                                SetupDiGetDeviceInterfaceDetail (
                                        hardwareDeviceInfo,
                                        &deviceInfoData,
                                        NULL, // probing so no output buffer yet
                                        0, // probing so output buffer length of zero
                                        &requiredLength, 
                                        NULL); // not interested in the specific dev-node
                                predictedLength = requiredLength;
                                // sizeof (SP_FNCLASS_DEVICE_DATA) + 512;
                                functionClassDeviceData = (PSP_DEVICE_INTERFACE_DETAIL_DATA) malloc (predictedLength); 
                                if(NULL == functionClassDeviceData) 
                                {
                                        break;
                                }
                                functionClassDeviceData->cbSize = sizeof (SP_DEVICE_INTERFACE_DETAIL_DATA);
                                if (! SetupDiGetDeviceInterfaceDetail (
                                        hardwareDeviceInfo,
                                        &deviceInfoData,
                                        functionClassDeviceData,
                                        predictedLength, 
                                        &requiredLength, 
                                        NULL)) 
                               {
                                        free( functionClassDeviceData );
                                        break;;
                               }
 
                               StringCchCopy(devName, MAX_LENGTH, functionClassDeviceData->DevicePath) ;
                               free( functionClassDeviceData ); 
                               done = TRUE;
                               break; 
                       } 
                       else
                       {
                               if (ERROR_NO_MORE_ITEMS == GetLastError())
                               {
                                       done = TRUE;
                                       i = -1;
                                       break;
                               }
                       } 
               }
        }
        NumberDevices = i;
        SetupDiDestroyDeviceInfoList (hardwareDeviceInfo);
        if (i >= 0)
        {
                return TRUE;
        }
        else
        {
                return FALSE;
        } 
}

该函数大致流程:

1.通过SetupDiGetClassDevs函数根据GUID获得设备顶级窗口句柄。

2.然后通过这个句柄,使用SetupDiEnumDeviceInterfaces函数枚举USB设备,直到找到符合条件的USB设备或枚举完所有设备为止。

3.当找到符合条件的设备后,使用SetupDiGetDeviceInterfaceDetail函数获取设备路径。 

根据设备路径获得输入管道和输出管道的路径。

// 获得USB设备路径 deviceName '\\?\usb#vid_045e&pid_00ce#00000000-0000-0000-0427-980002d9f4b1#{00873fdf-61a8-11d1-aa5e-00c04fb1728b}'
        if(!GetUsbDeviceFileName((LPGUID)&GUID_CLASS_USBSAMP_USB, deviceName))
        {
                MessageBox(L'设备未连接');
                return;
        }
        // hRead读管道路径 inPipe '\\?\usb#vid_045e&pid_00ce#00000000-0000-0000-0427-980002d9f4b1#{00873fdf-61a8-11d1-aa5e-00c04fb1728b}\PIPE00'
        StringCchCopy(inPipe , MAX_LENGTH, deviceName);
        StringCchCat(inPipe, MAX_LENGTH, L'\\' );
        if(FAILED(StringCchCat (inPipe, MAX_LENGTH, L'PIPE00'))) {
                return;
        }
        // hWrite写管道路径 outPipe '\\?\usb#vid_045e&pid_00ce#00000000-0000-0000-0427-980002d9f4b1#{00873fdf-61a8-11d1-aa5e-00c04fb1728b}\PIPE01'
        StringCchCopy(outPipe , MAX_LENGTH, deviceName);
        StringCchCat(outPipe, MAX_LENGTH, L'\\' );
        if(FAILED(StringCchCat (outPipe, MAX_LENGTH, L'PIPE01'))) {
                return;
        }

驱动代码设定,输入管道路径为设备路径加上\PIPE00,输出管道路径为设备路径加上\PIPE01

2、USB口通信

通过CreateFile打开管道,使用WriteFile和ReadFile就可以进行USB口的输入输出了。

注意,在读取USB口数据时,ReadFile函数会阻塞。所以需要用非阻塞的方式读,在打开PIPE时使用FILE_FLAG_OVERLAPPED标记。

hRead = CreateFile(inPipe,
                GENERIC_WRITE | GENERIC_READ, 
                FILE_SHARE_WRITE | FILE_SHARE_READ,
                NULL,
                OPEN_EXISTING,
                FILE_FLAG_OVERLAPPED, 
                NULL);
        if(hRead == INVALID_HANDLE_VALUE)
        {
                MessageBox(L'输入通道打开失败');
                return;
        }

在读线程中使用异步方式读。

success = ReadFile(pDlg->hRead, pDlg->inBuf, 65536, &len, &overlap);
        // 因为是overlapped操作,ReadFile会将读文件请求放入读队列之后立即返回(false),而不会等到文件读完才返回(true)
        if (!success)
        {
                if (GetLastError() == ERROR_IO_PENDING)
                {
                        dwRes = WAIT_TIMEOUT;
                        while(dwRes != WAIT_OBJECT_0)
                        {
                                dwRes = WaitForSingleObject(pDlg->hRead, 1000);
                                // 获取读的的长度
                                success = GetOverlappedResult(pDlg->hRead, &overlap, &len, FALSE);
                                // 上面二条语句完成的功能与下面一条语句的功能等价:
                                // 一直阻塞等到得到数据才继续下面。
                                // GetOverlappedResult(pDlg->hRead, &overlap, &len, TRUE);
                                if (pDlg->killThread)
                                {
                                        // 关闭线程,直接关闭
                                        return 0;
                                } 
                        }
                 }
        else
        {
                // 出错!
                return -1;
        }
}

传输速度测试

编写程序,测试工控主板端向PC端的数据传输速度。

EM9170上传速度最高可达3.6MB/秒。

EM9160上传速度最高可达500kb/秒。

其他说明

驱动中默认的管道BUFFER大小为256字节,在读写操作时,不宜超过管道BUFFER大小,否则会返回失败。例程中提供一个修改后的驱动,只是简单将BUFFER扩大到64K,原驱动保留为usbsamp_bak.sys。

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分