从事嵌入式方面,会点上位机会事半功倍,总体而言,一个串口,网口,usb通讯用到的比较多,这方面的资料网上也很多,但是总体而言零零碎碎,不算太齐全。
本问讲解的是usb hid类的封装,该例程的上位机可以和圈圈的开发板配套使用。在这里,我们用到的库是hidapi.lib,这个网上有下载,到时本人也提供一份。圈圈上位机的usb hid通讯接口看起来有些零碎杂乱,对于新手而言,看起来很费劲。下面罗列一下该库的提供的接口函数。
int HID_API_EXPORT HID_API_CALL hid_init(void);
int HID_API_EXPORT HID_API_CALL hid_exit(void);
HID_API_EXPORT hid_device * HID_API_CALL hid_open(unsigned short vendor_id, unsigned short product_id, wchar_t *serial_number);
int HID_API_EXPORT HID_API_CALL hid_write(hid_device *device, const unsigned char *data, size_t length);
int HID_API_EXPORT HID_API_CALL hid_read_timeout(hid_device *dev, unsigned char *data, size_t length, int milliseconds);
int HID_API_EXPORT HID_API_CALL hid_read(hid_device *device, unsigned char *data, size_t length);
void HID_API_EXPORT HID_API_CALL hid_close(hid_device *device);
相信这些接口很多人一开就明白是什么意思,在这里我也简单的讲解下。
hid_init该函数相当于初始化,是初始化HIDAPI库的,用在usb通讯最前端。
hid_exit是用来释放HIDAPI一些资源,防止内存泄露,用在usb通讯关闭后。
hid_open该函数是用来打开usb设备,需要提供版本号和产品号,序列号可以提供为NULL,如果该usb设备存在,此时该函数会返回一个指针,指向hid通讯对象。如果返回值为NULL,那么表示无法建立通讯。
hid_write此函数用是用来往hid端口写数据,第一个参数是hid_oepn函数的指针,第二个参数是你要写数据的首地址,第三个表示你写的长度,返回值为已经写入多少字节。
hid_read_timeout是用来读取usb hid接口中的数据,第一个参数是hid_open函数返回的指针,第二个参数存储读取数据存放的首地址,第三个表示需要的长度,肯定不能小于你要读取内容的长度,第四个参数表示超时时间,如果多少毫秒内没读取到内容,该函数立即返回为0,如果读取发生错误,返回值为-1,读取正常,返回值为读取内容的长度。
hid_read次函数也表示读取hid中的数据,和hid_read_timeout函数唯一的区别是该函数没有超时机制,如果没有数据,此函数一直会处于阻塞状态,无法返回,当然读取内的函数一般是放在线程中去处理的,否则界面会出现卡死的现象。
继续这些接口都有了,那我又做了什么,试想你有了这写接口,你用在你程序中因为你无法确认你什么时候会收到数据,所以接受的部分需要单独一个线程处理,在这里,我就是前面的接口经过封装成类,把收到的数据通过消息发送出去,到时候使用者就只要在响应函数中处理你所需的函数。
在这里我使用的是vs2010,先在工程中把hidapi.dll hidapi.lib, hidapi.h三个文件添加到工程文件夹中,并且把头文件添加到工程中。
新建USBClass类,里面的代码如下:
#pragma once
#include "hidapi.h" //hidapi库的头文件
#pragma comment(lib, "hidapi.lib") //导入的库
#define USB_RECV WM_USER + 1 //自定义的消息
#define VEN_ID 0x8888 //版本号
#define PRO_ID 0x0001 //产品号
class USBClass
{
private:
hid_device *usbDev; //指向通讯接口对象的指针
HANDLE thread; //线程
HANDLE hEvent; //事件
HWND curHwnd; //用来存放需要接受数据的窗口句柄
UINT8 buf[256]; //接受函数的缓冲区
UINT16 venID, proID; //接口的版本号,产品号
public:
/**
构造函数是只是把一些参数初始化
*/
USBClass(void)
{
usbDev = NULL;
hEvent = NULL;
venID = VEN_ID;
proID = PRO_ID;
}
/**
析构函数主要是判断线程是否运行,如果运行设置hEvent为有信号状态,并且设置一个等待时间,方便接受线程退出。
*/
~USBClass(void)
{
if (hEvent)
{
SetEvent(hEvent);
Sleep(50);
}
}
/**
接受线程,如果hEvent为无信号状态,那么一直处于读取内容状态,如果为有信号状态,此时线程退出
*/
static DWORD WINAPI RecvThread(LPVOID para)
{
int ret;
USBClass *p = (USBClass *)para;
while (WaitForSingleObject(p->hEvent, 5) != WAIT_OBJECT_0)
{
ret = p->Read();
if (ret) //收到数据,此时发送消息
{
PostMessage(p->curHwnd, USB_RECV, (WPARAM)p->buf, ret);
}
}
if (p->usbDev) //线程退出时,关闭usb hid通讯接口
{
hid_close(p->usbDev);
p->usbDev = NULL;
}
hid_exit();
return 0;
}
/**
为打开usb接口,hwnd为传入的句柄,为了把收到的数据传给该串口句柄。
同时建立线程,事件
*/
BOOL Open(HWND hwnd)
{
hid_init();
usbDev = hid_open(venID, proID, NULL);
if (usbDev == NULL)
{
return FALSE;
}
curHwnd = hwnd;
thread = CreateThread(NULL, 0, RecvThread, this, 0, 0);
CloseHandle(thread);
hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
return TRUE;
}
/**
判断接口是否打开
*/
BOOL IsOpen()
{
if (usbDev)
{
return TRUE;
}
return FALSE;
}
/**
设置有信号,方便接受线程退出,并且要设置等待停留时间
*/
BOOL Close()
{
SetEvent(hEvent);
Sleep(50);
return TRUE;
}
/**
往usb接口中写入数据,这里要注意写入数据的内容长度必须多加一个字节空间,需要存放
报告ID号,在这里我一门设置为0,如果你要传送64字节,此时你要提供65个字节空间大小,
而且你的有效数据从第一个字节开始存放。该函数返回实际写入的数据长度。
*/
int Write(UINT8 *data, UINT32 len)
{
int ret = 0;
if (usbDev)
{
data[0] = 0;
ret = hid_write(usbDev, data, len);
}
return ret;
}
/**
读取接口中的内容,采用的是读取超时函数,使用阻塞的不太好,因为不方便线程的退出。
*/
int Read()
{
int ret = 0;
if (usbDev)
{
ret = hid_read_timeout(usbDev, buf, 256, 10);
}
return ret;
}
};
建立一个对话框USBExample,界面如下:
有10个按钮,ID号分别为IDC_BUTTON_OPEN,IDC_BUTTON_CLOSE,IDC_BUTTON_LED1~IDC_BUTTON_LED8.
在Resource.h文件添加对应的ID号
#define IDC_BUTTON_OPEN 1000
#define IDC_BUTTON_CLOSE 1001
#define IDC_BUTTON_LED1 1002
#define IDC_BUTTON_LEN2 1003
#define IDC_BUTTON_LED3 1004
#define IDC_BUTTON_LED4 1005
#define IDC_BUTTON_LED5 1006
#define IDC_BUTTON_LED6 1007
#define IDC_BUTTON_LED7 1008
#define IDC_BUTTON_LED8 1009
为了打印接受的数据,这里,需要在stdafx.h文件中添加一下宏定义:
#define MY_DEBUG 1
#if MY_DEBUG
#pragma comment( linker, "/subsystem:console /entry:wWinMainCRTStartup" )
#endif
在USBExmapleDlg.h文件中内容如下:
// USBExampleDlg.h : 头文件
//
#pragma once
#include "USBClass.h"
// CUSBExampleDlg 对话框
class CUSBExampleDlg : public CDialogEx
{
// 构造
public:
CUSBExampleDlg(CWnd* pParent = NULL); // 标准构造函数
// 对话框数据
enum { IDD = IDD_USBEXAMPLE_DIALOG };
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV 支持
// 实现
protected:
HICON m_hIcon;
USBClass usbCls; //定义使用到的类对象
// 生成的消息映射函数
virtual BOOL OnInitDialog();
afx_msg void OnPaint();
afx_msg HCURSOR OnQueryDragIcon();
DECLARE_MESSAGE_MAP()
public:
afx_msg void OnBnClickedButtonLed(UINT nID);//LED1~LED8 8个按钮的处理函数
afx_msg LRESULT OnRecvAct(WPARAM wParam, LPARAM lParam);//USB接受数据的处理函数
afx_msg void OnBnClickedButtonOpen(); //打开按钮的处理函数
afx_msg void OnBnClickedButtonClose(); //关闭按钮的处理函数
};
// USBExampleDlg.cpp : 实现文件
//
#include "stdafx.h"
#include "USBExample.h"
#include "USBExampleDlg.h"
#include "afxdialogex.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
// CUSBExampleDlg 对话框
CUSBExampleDlg::CUSBExampleDlg(CWnd* pParent /*=NULL*/)
: CDialogEx(CUSBExampleDlg::IDD, pParent)
{
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}
void CUSBExampleDlg::DoDataExchange(CDataExchange* pDX)
{
CDialogEx::DoDataExchange(pDX);
}
BEGIN_MESSAGE_MAP(CUSBExampleDlg, CDialogEx)
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_MESSAGE(USB_RECV, &CUSBExampleDlg::OnRecvAct) //消息绑定,就是用来处理usb收到的数据
ON_CONTROL_RANGE(BN_CLICKED, IDC_BUTTON_LED1, IDC_BUTTON_LED8, OnBnClickedButtonLed)
ON_BN_CLICKED(IDC_BUTTON_OPEN, &CUSBExampleDlg::OnBnClickedButtonOpen) //打开按钮的绑定
ON_BN_CLICKED(IDC_BUTTON_CLOSE, &CUSBExampleDlg::OnBnClickedButtonClose)//关闭按钮的绑定
END_MESSAGE_MAP()
// CUSBExampleDlg 消息处理程序
BOOL CUSBExampleDlg::OnInitDialog()
{
CDialogEx::OnInitDialog();
// 设置此对话框的图标。当应用程序主窗口不是对话框时,框架将自动
// 执行此操作
SetIcon(m_hIcon, TRUE); // 设置大图标
SetIcon(m_hIcon, FALSE); // 设置小图标
// TODO: 在此添加额外的初始化代码
GetDlgItem(IDC_BUTTON_OPEN)->EnableWindow(true);
GetDlgItem(IDC_BUTTON_CLOSE)->EnableWindow(false);
return TRUE; // 除非将焦点设置到控件,否则返回 TRUE
}
// 如果向对话框添加最小化按钮,则需要下面的代码
// 来绘制该图标。对于使用文档/视图模型的 MFC 应用程序,
// 这将由框架自动完成。
void CUSBExampleDlg::OnPaint()
{
if (IsIconic())
{
CPaintDC dc(this); // 用于绘制的设备上下文
SendMessage(WM_ICONERASEBKGND, reinterpret_cast(dc.GetSafeHdc()), 0);
// 使图标在工作区矩形中居中
int cxIcon = GetSystemMetrics(SM_CXICON);
int cyIcon = GetSystemMetrics(SM_CYICON);
CRect rect;
GetClientRect(&rect);
int x = (rect.Width() - cxIcon + 1) / 2;
int y = (rect.Height() - cyIcon + 1) / 2;
// 绘制图标
dc.DrawIcon(x, y, m_hIcon);
}
else
{
CDialogEx::OnPaint();
}
}
//当用户拖动最小化窗口时系统调用此函数取得光标
//显示。
HCURSOR CUSBExampleDlg::OnQueryDragIcon()
{
return static_cast(m_hIcon);
}
/**
这里只是打印接受的数据
*/
LRESULT CUSBExampleDlg::OnRecvAct(WPARAM wParam, LPARAM lParam)
{
UINT8 *str = (UINT8 *)wParam;
int len = (int)lParam;
printf("***USB Recv\r\n");
for (int i = 0; i < len; i++)
{
printf("%d=%02x\r\n", i, str[i]);
}
return 0;
}
void CUSBExampleDlg::OnBnClickedButtonLed(UINT nID)
{
// TODO: 在此添加控件通知处理程序代码
if (usbCls.IsOpen())
{
UINT8 data[9]; //本来发送的数据长度只为0,需要多加一个字节空间用来存放报告ID号
memset(data, 0, 9);
data[1] = (nID - IDC_BUTTON_LED1 + 1);
usbCls.Write(data, 9);
printf("###USB Send\r\n");
for (int i = 0; i < 9; i++)
{
printf("%d=%02x\r\n", i, data[i]);
}
}
}
void CUSBExampleDlg::OnBnClickedButtonOpen()
{
// TODO: 在此添加控件通知处理程序代码
if (usbCls.Open(this->m_hWnd))
{
GetDlgItem(IDC_BUTTON_OPEN)->EnableWindow(false);
GetDlgItem(IDC_BUTTON_CLOSE)->EnableWindow(true);
}
}
void CUSBExampleDlg::OnBnClickedButtonClose()
{
// TODO: 在此添加控件通知处理程序代码
if (usbCls.IsOpen())
{
usbCls.Close();
GetDlgItem(IDC_BUTTON_OPEN)->EnableWindow(true);
GetDlgItem(IDC_BUTTON_CLOSE)->EnableWindow(false);
}
}
程序下载链接为: USBExample.rar