This is a follow-up to my previous question, Need to write driver for USB peripheral device?
I'm working on designing a USB peripheral using an STM32 microcontroller (bare metal / no OS). The device will occasionally be connected to a Windows PC, and transfer a few KB of data in each direction. There will be a custom PC application that controls the data transfers, using a proprietary protocol (i.e. for the USB payloads).
The PC will always be the master (initiator) - it'll send commands, and the device will issue responses, with up to a few hundred bytes of data going in either direction in a single command or response. I think I'll want to use USB Bulk Transfer mode.
From what I understand, one option is I could use the USB Communications Device Class (CDC). On the device side, I could use sample code from ST for the USB CDC, e.g. from STM32Cube. On the PC side, the device would present as a Virtual COM Port (VCP). Then in software I'd basically have a raw, bidirectional stream, on top of which I'd have to define my messsage formats, commands, etc.
I'm having trouble wrapping my mind around exactly what this is, and how to work with it.
What is the relationship of WinUSB to USB device classes? It seems to function as a "generic" USB class, but I can't find any documentation that spells it out as such.
Does WinUSB provide any built-in message delimiters? e.g. Does WinUsb_WritePipe send the contents of the buffer down to the device as an atomic unit? Or do I just get raw streams like a VCP/UART?
How does one implement WinUSB on the device? Is there sample code available? (Preferably for STM32.)
Launch WinUSB either from Unity or Menu. Usage is very simple and straight forward. Insert the USB disk, select the source image either ISO or real CD/DVD disks, and click Install button. That's it.
Use the free version to install WinUSB on free builds of Windows, including all retail versions.
WinUSB consists of two parts:
To use the WinUSB API in an application:
For more advanced solutions - use functions:
For debugging purposes You probably need: winusbtrace_tool https://blogs.msdn.microsoft.com/usbcoreblog/2010/02/05/how-to-generate-and-view-a-winusb-debug-trace-log/; Wireshark https://www.wireshark.org with USBPcap plugin.
Other Example: http://searchingforbit.blogspot.com/2012/04/winusb-communication-with-stm32-part-1.html. Sample template comes with Visual Studio.
You need also have knowledge of writing .inf files.
Another easy way to communicate with USB - libusb-win32 https://sourceforge.net/projects/libusb-win32/
My simple example console application sends small (for keep-alive) chunks of data to device:
#include "stdafx.h"
#include <SetupAPI.h>
#include <Hidsdi.h>
#include <devguid.h>
#include <winusb.h>
#include <usb.h>
#pragma comment(lib, "hid.lib")
#pragma comment(lib, "setupapi.lib")
#pragma comment(lib, "winusb.lib")
#include <iUString.h>
iString<char> DevicePath;
bool WinusbHandle_Open=false;
bool DeviceHandle_Open = false;
WINUSB_INTERFACE_HANDLE WinusbHandle;
HANDLE DeviceHandle;
UCHAR usb_out_buffer[64];
DEFINE_GUID(GUID_DEVCLASS_WINUSB, 0x88bae032L, 0x5a81, 0x49f0, 0xbc, 0x3d, 0xa4, 0xff, 0x13, 0x82, 0x16, 0xd6);
DEFINE_GUID(GUID_DEVCLASS_STL, 0xf177724dL, 0x74d3, 0x430e, 0x86, 0xb5, 0xf0, 0x36, 0x89, 0x10, 0xeb, 0x23);
GUID winusb_guid;
GUID stl_guid;
bool connectusb();
void disconnectusb();
int main()
{
DWORD n;
DWORD numEvents;
HANDLE rHnd;
WinusbHandle_Open = false;
DeviceHandle_Open = false;
winusb_guid = GUID_DEVCLASS_WINUSB;
stl_guid = GUID_DEVCLASS_STL;
usb_out_buffer[0] = 0;
usb_out_buffer[1] = 1;
usb_out_buffer[2] = 2;
usb_out_buffer[3] = 3;
ULONG bytesWritten;
ULONG timeout;
timeout = 100;
rHnd = GetStdHandle(STD_INPUT_HANDLE);
WinUsb_SetPipePolicy(WinusbHandle, 0x01, PIPE_TRANSFER_TIMEOUT, sizeof(ULONG), &timeout);
timeout = TRUE;
WinUsb_SetPipePolicy(WinusbHandle, 0x01, AUTO_CLEAR_STALL, sizeof(ULONG), &timeout);
timeout = TRUE;
WinUsb_SetPipePolicy(WinusbHandle, 0x01, RAW_IO, sizeof(ULONG), &timeout);//Bypasses queuing and error handling to boost performance for multiple read requests.
while (true)
{
if ((!WinusbHandle_Open) || (!WinusbHandle_Open)) { if (!connectusb())Sleep(2000); }
if ((!WinusbHandle_Open) || (!WinusbHandle_Open))continue;
bytesWritten = 0;
if (!WinUsb_WritePipe(WinusbHandle, 0x01, &usb_out_buffer[0], 2, &bytesWritten, NULL))
{
n = GetLastError();
disconnectusb();
}
Sleep(2000);
}
disconnectusb();
return 0;
}
bool connectusb()
{
BOOL bResult = FALSE;
HDEVINFO deviceInfo;
SP_DEVICE_INTERFACE_DATA interfaceData;
PSP_DEVICE_INTERFACE_DETAIL_DATA detailData = NULL;
DWORD n;
SP_DEVINFO_DATA devinfo;
BYTE devdetailbuffer[4096];
bool found;
deviceInfo = SetupDiGetClassDevs(&stl_guid, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
if (deviceInfo == INVALID_HANDLE_VALUE) { return false; }
found = false;
for (n = 0;; n++)
{
interfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
if (!SetupDiEnumDeviceInterfaces(deviceInfo, NULL, &stl_guid, n, &interfaceData))
{
n = GetLastError();
break;
}
detailData = (PSP_DEVICE_INTERFACE_DETAIL_DATA)devdetailbuffer;
detailData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
devinfo.cbSize = sizeof(devinfo);
if (!SetupDiGetDeviceInterfaceDetail(deviceInfo, &interfaceData, detailData, sizeof(devdetailbuffer), NULL, &devinfo)) { printf("SetupDiGetDeviceInterfaceDetail: %u\n", GetLastError()); break; }
if (IsEqualGUID(devinfo.ClassGuid, winusb_guid))
{
if ((-1 != iStrPos(detailData->DevicePath, "VID_0483")) || (-1 != iStrPos(detailData->DevicePath, "vid_0483")))
{
if ((-1 != iStrPos(detailData->DevicePath, "PID_576B")) || (-1 != iStrPos(detailData->DevicePath, "pid_576b")))
{
DevicePath = detailData->DevicePath;
found = true;
break;
}
}
}
}
SetupDiDestroyDeviceInfoList(deviceInfo);
if (!found)return false;
DeviceHandle = CreateFile(DevicePath.Buffer() ,
GENERIC_WRITE | GENERIC_READ,
FILE_SHARE_WRITE | FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,
NULL);
if (INVALID_HANDLE_VALUE == DeviceHandle) {
n = GetLastError();
}
if (INVALID_HANDLE_VALUE == DeviceHandle) return false;
DeviceHandle_Open = true;
if (!WinUsb_Initialize(DeviceHandle, &WinusbHandle))
{
n = GetLastError();
CloseHandle(DeviceHandle); DeviceHandle_Open = false;
return false;
}
WinusbHandle_Open = true;
return true;
}
void disconnectusb()
{
if (WinusbHandle_Open) { WinUsb_Free(WinusbHandle); WinusbHandle_Open = false; }
if (DeviceHandle_Open) { CloseHandle(DeviceHandle); DeviceHandle_Open = false; }
}
WinUSB_WritePipe
sends the data as a single USB transfer. A transfer has a specific definition in the USB specification. You can tell when a transfer ends because you will get a short packet at the end of the transfer. A short packet is a packet that is smaller than the maximum packet size of the endpoint, and it could possibly be zero-length. You should double check whether that is actually true though; try sending a transfer that is a multiple of the maximum packet size and make sure that Windows sends a zero-length packet at the end of that. By the way, you should consider sending your data as a control transfer or series of control transfers on endpoint 0. Control transfers have a built-in concept of a request and a response to the request. Despite the name, control transfers can be used to transfer large amounts of data; they are commonly used in the USB bootloaders (see the DFU class). Another advantage of using control transfers instead of non-zero endpoints is that you don't have to add extra endpoint descriptors and initialize the endpoints in your firmware. Your USB stack should have the machinery for handling custom control transfers and you should be able to make custom control transfers just by writing a few callback functions.If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With