PSoCをUSB(HID)デバイスとして接続しデータを受信する

psoc_usb_rx_01.gif
PSoC Designerを利用すると簡単にHIDに対応したUSBデバイスを作れる。前回の「PSoCでUSBマウスを作る」ではPSoCでHIDに対応したUSBマウスを作成した。作成したマウスはWindows標準のドライバーが利用されるため、USBポートに挿せば即利用できるものだった。

今回はPSoC CY8C24794-24LFXIを利用してUSB経由でPSoCからPCへデータを送信する。利用するドライバーはWindows標準のものなのでPC側では動作確認用のEXEファイルが1つあればいい。ほかにsysファイルやdllファイルなどは必要ない。つまりDDKなどがなくともPC側のアプリケーションを開発できる。

psoc_usb_rx_02.gif
プロジェクトにUSB用のモジュールを追加する。今回も「USBFS」を利用した。「Protocols」にある「USBFS」をダブルクリックする。

psoc_usb_rx_03.gif
するとUSBデバイスクラスを問う画面が開く。ここで「Human Interface Device(HID)」を選択する。

psoc_usb_rx_04.gif
これでプロジェクトに「USBFS_1」が追加された。USBに関する詳細設定をするため「Config」メニューの「Interconnect」を選択して画面を切り替える。

psoc_usb_rx_05.gif
そして「USBFS_1」を右クリックして現れたメニューから「Place」を選択して配置する。

psoc_usb_rx_06.gif
配置したらもう一度「USBFS_1」を右クリックして今度は「USB Setup Wizard」を選択する。

psoc_usb_rx_07.gif
これでUSBデバイスの設定画面「USB Setup Wizard」が開いた。
「HID Report Descriptor Root」の右側にある「Import HID Report Template」をクリックする。

psoc_usb_rx_08.gif
そして「3 Button Mouse」を選択して「Apply」をクリックする。今回はマウスを作成するわけではないが、Report Descriptorを手動で作るのは面倒なのでマウス用の設定を流用することにした。

psoc_usb_rx_09.gif
これでUSBマウス用のレポートデスクリプターが追加された。このまま利用するとUSBマウスとしてPCに認識されてしまう。そのためマウスとして認識されないようにする。
「Usage Page」の設定「Usage Page 05 01」をクリックする。

psoc_usb_rx_10.gif
すると値の設定画面が開く。ここで「Size」を「2」に、「Data[0]」と「Data[1]」をそれぞれ「00」と「FF」に変更する。これでUSBマウスとして認識されなくなる。

psoc_usb_rx_11.gif
さらに「Interface Attributes」の「Class」を「HID」に、「HID Class Descriptor」の「HID Report」を「3 Button Mouse」に変更する。これでUSBに関する設定は終わりだ。

psoc_usb_rx_12.gif
「View」メニューの「Application Editor」を選択してソースコードを編集する画面に切り替える。

psoc_usb_rx_13.gif
そして「main.c」に処理を書く。

今回は処理を単純にするため、データ送信準備が整うと変数「i」の値を送信、そして「i」の値を増やす...というループにした。つまりどんどん値が変化するデータがPCに向けて送信される。


//----------------------------------------------------------------------------
// C main line
//----------------------------------------------------------------------------

#include <m8c.h> // part specific constants and macros
#include "PSoCAPI.h" // PSoC API definitions for all User Modules

void main()
{
int i;
BYTE pData[] = {0,0,0,0};

M8C_EnableGInt;
USBFS_1_Start(0,USB_5V_OPERATION);

while(!USBFS_1_bGetConfiguration());
USBFS_1_LoadInEP(1,pData,3,USB_NO_TOGGLE);

i = 0;
while(1)
{
if(USBFS_1_bGetEPAckState(1))
{
*(int*)&(pData[2]) = i++;
USBFS_1_LoadInEP(1,pData,3,USB_TOGGLE);
}
}
}





psoc_usb_rx_14.gif
これでPSoCへ書き込み、USBケーブルでPSoCとPCとを接続する。するとPC上では「HID準拠デバイス」として自動的に認識される。

psoc_usb_rx_15.gif
ここではテストのため簡単なソフトを用意した。PSoC側のベンダーIDとデバイスIDは両方とも標準値の「FFFF」なのでその値を入力して「接続」する。

psoc_usb_rx_16.gif
そして「受信」ボタンを押すとデータを受信できた。





今回作成したPC側の「実行ファイルおよびソースファイル」はVisual Studio 2005(Visual C++)+WTL/ATLで作成した。ReadFileでレポートサイズ1つ分だけ読み込むなどテスト用に簡易的に処理している。しかし「HIDデバイス」に対応するソフトなので、PSoCだけでなくPICや市販のHID機器のテストにも利用できるだろう。



処理は以下のようになっている。





// MainDlg.h : CMainDlg クラスのインターフェイス
//
/////////////////////////////////////////////////////////////////////////////

#pragma once

#include "atlstr.h"
#include "setupapi.h"
#pragma comment(lib,"setupapi.lib")


bool GetHIDDevice(UINT nVendorID,UINT nDeviceID,CAtlString& strDevicePath)
{
GUID guidHID;
CAtlString strFindID;

strDevicePath = _T("");
strFindID.Format(_T("vid_%04x&pid_%04x"),nVendorID,nDeviceID);

////////////////////////////////
// HIDを示すGUID取得
//
{
HMODULE hDLL;
VOID (WINAPI* pfnHidD_GetHidGuid)(OUT LPGUID HidGuid);

hDLL = ::LoadLibrary(_T("hid.dll"));
if(hDLL == NULL)
return false;

(FARPROC&)pfnHidD_GetHidGuid = ::GetProcAddress(hDLL,"HidD_GetHidGuid");
if(pfnHidD_GetHidGuid == NULL)
{
::FreeLibrary(hDLL);
return false;
}

pfnHidD_GetHidGuid(&guidHID);

::FreeLibrary(hDLL);
}


////////////////////////////////
// HIDデバイスを検索
//
{
HDEVINFO hDevInfo;

hDevInfo = ::SetupDiGetClassDevs(&guidHID,NULL,NULL,DIGCF_PRESENT | DIGCF_INTERFACEDEVICE);
if(hDevInfo == INVALID_HANDLE_VALUE)
return false;

BOOL bRet;
DWORD dwSize;
DWORD dwRequiredSize;
DWORD dwIndex;

SP_DEVICE_INTERFACE_DATA sDeviceInterfaceData;
PSP_DEVICE_INTERFACE_DETAIL_DATA pDeviceInterfaceDetailData;

sDeviceInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);

dwIndex = 0;
while(::SetupDiEnumDeviceInterfaces(hDevInfo,NULL,&guidHID,dwIndex,&sDeviceInterfaceData))
{
dwIndex++;

dwSize = 0;
::SetupDiGetDeviceInterfaceDetail(hDevInfo,&sDeviceInterfaceData,NULL,0,&dwSize,NULL);
pDeviceInterfaceDetailData = (PSP_DEVICE_INTERFACE_DETAIL_DATA)new BYTE[dwSize];
pDeviceInterfaceDetailData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);

bRet = ::SetupDiGetDeviceInterfaceDetail(hDevInfo,&sDeviceInterfaceData,pDeviceInterfaceDetailData,dwSize,&dwRequiredSize,NULL);
if(bRet)
{
//探しているデバイスかどうかチェック
if(_tcsstr(pDeviceInterfaceDetailData->DevicePath,strFindID) != NULL)
{
//見つかった!
strDevicePath = pDeviceInterfaceDetailData->DevicePath;
delete pDeviceInterfaceDetailData;
::SetupDiDestroyDeviceInfoList(hDevInfo);
return true;
};
}

delete pDeviceInterfaceDetailData;
}

::SetupDiDestroyDeviceInfoList(hDevInfo);
}

return false;
}

//////////////////////////////
//HIDUSAGE.H
//
#ifndef USAGE
typedef USHORT USAGE, *PUSAGE;
#endif


//////////////////////////////
//HIDPI.H
//
#ifndef PHIDP_PREPARSED_DATA
typedef struct _HIDP_PREPARSED_DATA * PHIDP_PREPARSED_DATA;
#endif

#ifndef _HIDP_CAPS
typedef struct _HIDP_CAPS
{
USAGE Usage;
USAGE UsagePage;
USHORT InputReportByteLength;
USHORT OutputReportByteLength;
USHORT FeatureReportByteLength;
USHORT Reserved[17];

USHORT NumberLinkCollectionNodes;

USHORT NumberInputButtonCaps;
USHORT NumberInputValueCaps;
USHORT NumberInputDataIndices;

USHORT NumberOutputButtonCaps;
USHORT NumberOutputValueCaps;
USHORT NumberOutputDataIndices;

USHORT NumberFeatureButtonCaps;
USHORT NumberFeatureValueCaps;
USHORT NumberFeatureDataIndices;
} HIDP_CAPS, *PHIDP_CAPS;
#endif

#ifndef FACILITY_HID_ERROR_CODE
#define FACILITY_HID_ERROR_CODE 0x11
#endif

#ifndef HIDP_ERROR_CODES
#define HIDP_ERROR_CODES(SEV, CODE) ((NTSTATUS) (((SEV) << 28) | (FACILITY_HID_ERROR_CODE << 16) | (CODE)))
#endif

#ifndef HIDP_STATUS_SUCCESS
#define HIDP_STATUS_SUCCESS (HIDP_ERROR_CODES(0x0,0))
#endif

//////////////////////////////

bool GetHIDReportSize(HANDLE hDevice,USHORT* pnInputReportByteLength,USHORT* pnOutputReportByteLength)
{
if(pnInputReportByteLength == NULL && pnOutputReportByteLength == NULL)
return false;
if(pnInputReportByteLength)
*pnInputReportByteLength = 0;
if(pnOutputReportByteLength)
*pnOutputReportByteLength = 0;
if(hDevice == INVALID_HANDLE_VALUE)
return false;

HMODULE hDLL;
BOOLEAN (WINAPI* pfnHidD_GetPreparsedData)(IN HANDLE HidDeviceObject, OUT PHIDP_PREPARSED_DATA *PreparsedData);
BOOLEAN (WINAPI* pfnHidD_FreePreparsedData)(IN PHIDP_PREPARSED_DATA PreparsedData);
NTSTATUS (WINAPI* pfnHidP_GetCaps)(IN PHIDP_PREPARSED_DATA PreparsedData,OUT PHIDP_CAPS Capabilities);

hDLL = ::LoadLibrary(_T("hid.dll"));
if(hDLL == NULL)
return false;

(FARPROC&)pfnHidD_GetPreparsedData = ::GetProcAddress(hDLL,"HidD_GetPreparsedData");
(FARPROC&)pfnHidD_FreePreparsedData = ::GetProcAddress(hDLL,"HidD_FreePreparsedData");
(FARPROC&)pfnHidP_GetCaps = ::GetProcAddress(hDLL,"HidP_GetCaps");

if(pfnHidD_GetPreparsedData == NULL || pfnHidD_FreePreparsedData == NULL || pfnHidP_GetCaps == NULL)
{
::FreeLibrary(hDLL);
return false;
}

BOOLEAN ret;
HIDP_CAPS sHidpCaps;
PHIDP_PREPARSED_DATA pPreparsedData;
NTSTATUS status;

ret = pfnHidD_GetPreparsedData(hDevice,&pPreparsedData);
if(!ret)
{
::FreeLibrary(hDLL);
return false;
}
status = pfnHidP_GetCaps(pPreparsedData,&sHidpCaps);
pfnHidD_FreePreparsedData(pPreparsedData);

::FreeLibrary(hDLL);

if(status != HIDP_STATUS_SUCCESS)
return false;

if(pnInputReportByteLength)
*pnInputReportByteLength = sHidpCaps.InputReportByteLength;
if(pnOutputReportByteLength)
*pnOutputReportByteLength = sHidpCaps.OutputReportByteLength;

return true;
}

class CMainDlg : public CDialogImpl<CMainDlg>, public CUpdateUI<CMainDlg>,
public CMessageFilter, public CIdleHandler
{
public:
enum { IDD = IDD_MAINDLG };

virtual BOOL PreTranslateMessage(MSG* pMsg)
{
return CWindow::IsDialogMessage(pMsg);
}

virtual BOOL OnIdle()
{
return FALSE;
}

BEGIN_UPDATE_UI_MAP(CMainDlg)
END_UPDATE_UI_MAP()

BEGIN_MSG_MAP(CMainDlg)
MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)
COMMAND_ID_HANDLER(ID_APP_ABOUT, OnAppAbout)
COMMAND_ID_HANDLER(IDOK, OnOK)
COMMAND_ID_HANDLER(IDCANCEL, OnCancel)
COMMAND_ID_HANDLER(IDC_BUTTON_CONNECT, OnConnect)
COMMAND_ID_HANDLER(IDC_BUTTON_SEND, OnSend)
COMMAND_ID_HANDLER(IDC_BUTTON_RECV, OnRecv)
END_MSG_MAP()

// ハンドラーのプロトタイプ (引数が必要な場合はコメントを外してください):
//LRESULT MessageHandler(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
//LRESULT CommandHandler(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
//LRESULT NotifyHandler(int /*idCtrl*/, LPNMHDR /*pnmh*/, BOOL& /*bHandled*/)

HANDLE _hDevice;
USHORT _nInputReportByteLength;
USHORT _nOutputReportByteLength;

void Destroy(void)
{
_nInputReportByteLength = 0;
_nOutputReportByteLength = 0;

if(_hDevice != INVALID_HANDLE_VALUE || _hDevice != NULL)
::CloseHandle(_hDevice);
_hDevice = INVALID_HANDLE_VALUE;

GetDlgItem(IDC_BUTTON_SEND).EnableWindow(FALSE);
GetDlgItem(IDC_BUTTON_RECV).EnableWindow(FALSE);
}

LRESULT OnConnect(WORD /*wNotifyCode*/, WORD wID, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
{
CAtlString strVendorID;
CAtlString strDeviceID;

GetDlgItemText(IDC_EDIT_VENDORID,strVendorID);
GetDlgItemText(IDC_EDIT_DEVICEID,strDeviceID);

Destroy();

strVendorID.MakeLower();
strDeviceID.MakeLower();
strVendorID.Replace(_T(" "),_T(""));
strDeviceID.Replace(_T(" "),_T(""));
strVendorID.Replace(_T("0x"),_T(""));
strDeviceID.Replace(_T("0x"),_T(""));

if(strVendorID == _T("") || strDeviceID == _T(""))
{
Message(_T("接続したいHIDデバイスのベンダーIDとデバイスIDを16進数4桁で指定してください。\n入力例:「FFFF」"));
return 0;
}


bool ret;
int nVendorID;
int nDeviceID;
TCHAR* pszEnd;
CAtlString strDevicePath;

pszEnd = NULL;
nVendorID = ::_tcstol(strVendorID,&pszEnd,16);
nDeviceID = ::_tcstol(strDeviceID,&pszEnd,16);
ret = GetHIDDevice(nVendorID,nDeviceID,strDevicePath);
if(ret == false)
{
Message(_T("指定されたベンダーID、デバイスIDのUSB機器が見つかりませんでした。"));
return 0;
}


_hDevice = ::CreateFile(strDevicePath,GENERIC_READ | GENERIC_WRITE,FILE_SHARE_READ | FILE_SHARE_WRITE,NULL,OPEN_EXISTING,FILE_FLAG_OVERLAPPED,NULL);
if(_hDevice == INVALID_HANDLE_VALUE)
{
Message(_T("HIDデバイスは見つかりましたが接続に失敗しました(CreateFileに失敗)"));
return 0;
}


ret = GetHIDReportSize(_hDevice,&_nInputReportByteLength,&_nOutputReportByteLength);
if(ret == false)
{
Message(_T("HIDデバイスに接続しましたが、レポートサイズの取得に失敗しました(GetHIDReportSizeに失敗)"));
return 0;
}

CAtlString strMessage;

strMessage.Format(_T("HIDデバイスに接続しました。\nデバイス->PCのデータサイズは%dバイト\nPC->デバイスのデータサイズは%dバイトです。"),_nInputReportByteLength,_nOutputReportByteLength);
Message(strMessage);

GetDlgItem(IDC_BUTTON_SEND).EnableWindow(_nOutputReportByteLength != 0);
GetDlgItem(IDC_BUTTON_RECV).EnableWindow(_nInputReportByteLength != 0);

{
int i;
CAtlString strBuff;

for(i = 0; i < _nOutputReportByteLength; i++)
strBuff += _T("00 ");
SetDlgItemText(IDC_EDIT_SEND,strBuff);
}


return 0;
}

LRESULT OnSend(WORD /*wNotifyCode*/, WORD wID, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
{
BYTE* pBuff;
DWORD dwWritten;

pBuff = new BYTE[_nOutputReportByteLength];
if(pBuff == NULL)
{
Message(_T("送信に失敗しました(newに失敗)"));
return 0;
}
::ZeroMemory(pBuff,_nOutputReportByteLength);


bool bInvalid;
{
CAtlString strText;

GetDlgItemText(IDC_EDIT_SEND,strText);
strText.MakeLower();
strText.Replace(_T(" "),_T(""));
strText.Replace(_T("0x"),_T(""));

int i;
int nSize;
BYTE* pData;

nSize = strText.GetLength();
if(nSize % 2)
nSize--;

bInvalid = false;
pData = pBuff;
for(i = 0; i < nSize; i += 2)
{
if(strText[i] >= _T('0') && strText[i] <= _T('9'))
*pData = (BYTE)strText[i] - _T('0');
else if(strText[i] >= _T('a') && strText[i] <= _T('f'))
*pData = (BYTE)strText[i] - _T('a') + 10;
else
{
bInvalid = true;
break;
}

*pData <<= 4;
if(strText[i+1] >= _T('0') && strText[i+1] <= _T('9'))
*pData += (BYTE)strText[i+1] - _T('0');
else if(strText[i+1] >= _T('a') && strText[i+1] <= _T('f'))
*pData += (BYTE)strText[i+1] - _T('a') + 10;
else
{
*pData = 0x00;
bInvalid = true;
break;
}

pData++;
if(pData >= pBuff + _nOutputReportByteLength)
break;
}
}


BOOL bRet;
OVERLAPPED sOverlapped;

::ZeroMemory(&sOverlapped,sizeof(OVERLAPPED));
sOverlapped.hEvent = ::CreateEvent(NULL,FALSE,TRUE,_T(""));

dwWritten = 0;
pBuff[0] = 0;
bRet = ::WriteFile(_hDevice,pBuff,_nOutputReportByteLength,&dwWritten,&sOverlapped);

DWORD dwWait;

bRet = FALSE;
dwWait = ::WaitForSingleObject(sOverlapped.hEvent,1000);
if(dwWait == WAIT_OBJECT_0)
{
bRet = ::GetOverlappedResult(_hDevice,&sOverlapped,&dwWritten,TRUE);
if(bRet)
{
DWORD i;
CAtlString strBuff;

for(i = 0; i < dwWritten; i++)
strBuff.AppendFormat(_T("%02X "),pBuff[i]);

// SetDlgItemText(IDC_EDIT_SEND,strBuff);
strBuff = _T("送信に成功しました。\n") + strBuff;
if(bInvalid)
strBuff += _T("\nただし不正な文字が検出されました。入力した送信文字列を確認してください。");
Message(strBuff);
}
}

if(bRet == FALSE)
{
Message(_T("送信に失敗しました(WriteFileに失敗)"));
delete pBuff;
return 0;
}

delete pBuff;

return 0;
}

LRESULT OnRecv(WORD /*wNotifyCode*/, WORD wID, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
{
BYTE* pBuff;
DWORD dwRead;

pBuff = new BYTE[_nInputReportByteLength];
if(pBuff == NULL)
{
Message(_T("受信に失敗しました(newに失敗)"));
return 0;
}


BOOL bRet;
OVERLAPPED sOverlapped;

::ZeroMemory(&sOverlapped,sizeof(OVERLAPPED));
sOverlapped.hEvent = ::CreateEvent(NULL,FALSE,TRUE,_T(""));

dwRead = 0;
pBuff[0] = 0;
bRet = ::ReadFile(_hDevice,pBuff,_nInputReportByteLength,&dwRead,&sOverlapped);
if(bRet == FALSE)
{
Message(_T("受信に失敗しました(ReadFileに失敗)"));
delete pBuff;
return 0;
}


DWORD dwWait;

bRet = FALSE;
dwWait = ::WaitForSingleObject(sOverlapped.hEvent,1000);
if(dwWait == WAIT_OBJECT_0)
{
bRet = ::GetOverlappedResult(_hDevice,&sOverlapped,&dwRead,TRUE);
if(bRet)
{
DWORD i;
CAtlString strBuff;

for(i = 0; i < dwRead; i++)
strBuff.AppendFormat(_T("%02X "),pBuff[i]);

SetDlgItemText(IDC_EDIT_RECV,strBuff);
Message(_T("受信に成功しました。\nHIDデバイスの場合、レポートIDを示す1バイト目は「00」固定です。"));
}
}

delete pBuff;

if(bRet == FALSE)
Message(_T("受信に失敗しました(WaitForSingleObjectもしくはGetOverlappedResultに失敗)"));

return 0;
}


void Message(LPCTSTR pszMessage)
{
SetDlgItemText(IDC_STATIC_STATUS,pszMessage);
}


LRESULT OnInitDialog(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
// 画面の中心へダイアログを移動
CenterWindow();

// アイコンの設定
HICON hIcon = (HICON)::LoadImage(_Module.GetResourceInstance(), MAKEINTRESOURCE(IDR_MAINFRAME),
IMAGE_ICON, ::GetSystemMetrics(SM_CXICON), ::GetSystemMetrics(SM_CYICON), LR_DEFAULTCOLOR);
SetIcon(hIcon, TRUE);
HICON hIconSmall = (HICON)::LoadImage(_Module.GetResourceInstance(), MAKEINTRESOURCE(IDR_MAINFRAME),
IMAGE_ICON, ::GetSystemMetrics(SM_CXSMICON), ::GetSystemMetrics(SM_CYSMICON), LR_DEFAULTCOLOR);
SetIcon(hIconSmall, FALSE);

// メッセージ フィルターおよび画面更新用のオブジェクト登録
CMessageLoop* pLoop = _Module.GetMessageLoop();
ATLASSERT(pLoop != NULL);
pLoop->AddMessageFilter(this);
pLoop->AddIdleHandler(this);

UIAddChildWindowContainer(m_hWnd);


_hDevice = INVALID_HANDLE_VALUE;
Destroy();

return TRUE;
}

LRESULT OnAppAbout(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
{
CAboutDlg dlg;
dlg.DoModal();
return 0;
}

LRESULT OnOK(WORD /*wNotifyCode*/, WORD wID, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
{
// TODO: チェックコードの追加
CloseDialog(wID);
return 0;
}

LRESULT OnCancel(WORD /*wNotifyCode*/, WORD wID, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
{
CloseDialog(wID);
return 0;
}

void CloseDialog(int nVal)
{
Destroy();
DestroyWindow();
::PostQuitMessage(nVal);
}
};

プロジェクトファイルをダウンロード


トラックバック

この一覧は、次のエントリーを参照しています: PSoCをUSB(HID)デバイスとして接続しデータを受信する:

» PSoCをExcelのマクロ機能(VBA)だけでUSB制御する 送信元 dinop.com
前回と前々回はUSBを内蔵したPSoC「CY8C24794-24FLXI」をヒ... [詳しくはこちら]


カテゴリー「電子工作」 のエントリー