一步步实现stm32cube的usb之CUSTOM_HID当串口使用 (stm32F407discoery板)

每次用串口调试感觉麻烦死了,尤其是电脑上没有串口,usb转串口线一大坨
为什么用usb 的 hid 功能?
hid 是免驱的,任何电脑上都内置的有他的驱动.
插上去就可以用.
对于调试来说,hid的最大速度64k/s完全够用了,但是,这个速度是理论的,实际中根本不可能达到!!
但是缩减下,能达到串口的115200bit/s就比串口好用了.
 
记录开始:
首先上配置:
我的板卡是stm32f407discovery板,晶振8M
led是PD12/PD13
按键是PA0
首先选中外部时钟,然后去时钟界面输入外部时钟为8M

QQ截图20160808163431.png

 
选中usb 的fs模式, 选择custom hid接口功能

QQ截图20160808163508.png


QQ截图20160808163731.png

然后去打开usb_fs配置 ,其实就是默认的

QQ截图20160808163758.png

中断号配置界面

QQ截图20160808164003.png

 
usb device配置图

QQ截图20160808164033.png

usb 端口描述图

QQ截图20160808164051.png

还有led的IO口设置,这个就不再描述了.
 
这样配置结束了,然后生成工程,打开,编译!
 
已邀请:

admin

赞同来自:

另外, 这个完整的工程幸好以前都放到百度网盘了, 我刚把它移动到了论坛的专用百度网盘里了, 

现在可以去下载测试了 , 这个工程的名字: Hid_USB(实现hid当串口使用程序).7z

需要的去 http://pan.baidu.com/s/1gdhcja7 里面找

admin

赞同来自: zzt_car 热血熊

编译之后,直接烧录到板卡,插入otg口连接电脑,

QQ截图20160808165615.jpg


看到设备管理器里面有个叹号,这个不用着急,因为咱们还没有配置端点造成的.

QQ截图20160808165904.jpg


就是上面这个地方,
我是准备让他弄成串口类似的功能,所以不想定义什么乱七八糟的id
直接用数组写和读,然后接收后用自己定的协议来处理
所以,我参考网上的帖子对这个描述的例子,代码如下:
__ALIGN_BEGIN static uint8_t CUSTOM_HID_ReportDesc_FS[USBD_CUSTOM_HID_REPORT_DESC_SIZE] __ALIGN_END =
{
/* USER CODE BEGIN 0 */
0x05, 0x8c, /* USAGE_PAGE (ST Page) */
0x09, 0x01, /* USAGE (Demo Kit) */
0xa1, 0x01, /* COLLECTION (Application) */

// The Input report
0x09,0x03, // USAGE ID - Vendor defined
0x15,0x00, // LOGICAL_MINIMUM (0)
0x26,0x00, 0xFF, // LOGICAL_MAXIMUM (255)
0x75,0x08, // REPORT_SIZE (8bit)
0x95,0x40, // REPORT_COUNT (64Byte)
0x81,0x02, // INPUT (Data,Var,Abs)

// The Output report
0x09,0x04, // USAGE ID - Vendor defined
0x15,0x00, // LOGICAL_MINIMUM (0)
0x26,0x00,0xFF, // LOGICAL_MAXIMUM (255)
0x75,0x08, // REPORT_SIZE (8bit)
0x95,0x40, // REPORT_COUNT (64Byte)
0x91,0x02, // OUTPUT (Data,Var,Abs)

/* USER CODE END 0 */
0xC0 /* END_COLLECTION */

};

我也不知道会不会又被编辑器吞代码,所以,上图

QQ截图20160808170243.jpg


查看这个函数的数组长度定义,改为33
#define USBD_CUSTOM_HID_REPORT_DESC_SIZE 33

如果你不知道怎么查看,就去看usbd_conf.h里面修改吧.
这个时候再进行编译,烧录到板子.
这个时候可以看到设备管理器里面的hid已经多了两个
HID-compliant device 和 usb 输入设备

QQ截图20160808170659.jpg



 

admin

赞同来自:

我们现在开始看生成的工程代码,哪些是需要自己写的,哪些是系统写好的可以直接用的.
void MX_USB_DEVICE_Init(void)
{
/* Init Device Library,Add Supported Class and Start the library*/
USBD_Init(&hUsbDeviceFS, &FS_Desc, DEVICE_FS);

USBD_RegisterClass(&hUsbDeviceFS, &USBD_CUSTOM_HID);

USBD_CUSTOM_HID_RegisterInterface(&hUsbDeviceFS, &USBD_CustomHID_fops_FS);

USBD_Start(&hUsbDeviceFS);

}
系统完成了这么多,
只有这个事用户可以操作的,其他的就是系统完成usb的对接的
所以去查看下这个定义
USBD_CustomHID_fops_FS
USBD_CUSTOM_HID_ItfTypeDef USBD_CustomHID_fops_FS = 
{
CUSTOM_HID_ReportDesc_FS,
CUSTOM_HID_Init_FS,
CUSTOM_HID_DeInit_FS,
CUSTOM_HID_OutEvent_FS,
};
里面有刚才写的报告描述符函数
有通过hid实现其他工程用的初始化函数
有一个通过hid实现其他工程用的失能函数
还有通过hid控制一个貌似叫输出事件的函数
 
一步一步来,先来看看这个输出事件是怎么回事?
于是直接在工程中查找名字叫CUSTOM_HID_OutEvent_FS 的位置,

QQ截图20160808172104.jpg

发现貌似没有什么值得注意的被调用的地方,所以初步判断这个应该是利用了其他方法调用的,比如:指针
但是尽管事其他类似指针的调用,也要找出来,要不然不知道这个东西怎么用啊,
 
回到MX_USB_DEVICE_Init这个函数,
我们查看下USBD_CUSTOM_HID这个定义
于是我找到了它
USBD_ClassTypeDef  USBD_CUSTOM_HID = 
{
USBD_CUSTOM_HID_Init,
USBD_CUSTOM_HID_DeInit,
USBD_CUSTOM_HID_Setup,
NULL, /*EP0_TxSent*/
USBD_CUSTOM_HID_EP0_RxReady, /*EP0_RxReady*/ /* STATUS STAGE IN */
USBD_CUSTOM_HID_DataIn, /*DataIn*/
USBD_CUSTOM_HID_DataOut,
NULL, /*SOF */
NULL,
NULL,
USBD_CUSTOM_HID_GetCfgDesc,
USBD_CUSTOM_HID_GetCfgDesc,
USBD_CUSTOM_HID_GetCfgDesc,
USBD_CUSTOM_HID_GetDeviceQualifierDesc,
};
看到里面的
USBD_CUSTOM_HID_DataOut

USBD_CUSTOM_HID_DataIn
,从名称可以看出来,这个就是数据处理的地方了.
去查看OUT的函数
static uint8_t  USBD_CUSTOM_HID_DataOut (USBD_HandleTypeDef *pdev, 
uint8_t epnum)
{

USBD_CUSTOM_HID_HandleTypeDef *hhid = (USBD_CUSTOM_HID_HandleTypeDef*)pdev->pClassData;

((USBD_CUSTOM_HID_ItfTypeDef *)pdev->pUserData)->OutEvent(hhid->Report_buf[0],
hhid->Report_buf[1]);

USBD_LL_PrepareReceive(pdev, CUSTOM_HID_EPOUT_ADDR , hhid->Report_buf,
USBD_CUSTOMHID_OUTREPORT_BUF_SIZE);

return USBD_OK;
}
可以看到,从usb获取的数据,利用
USBD_CUSTOM_HID_ItfTypeDef
这个函数指针进行了一系列的处理
其中的这句话
  ((USBD_CUSTOM_HID_ItfTypeDef *)pdev->pUserData)->OutEvent(hhid->Report_buf[0], 
hhid->Report_buf[1]);
说明我们准备用的这个函数
CUSTOM_HID_OutEvent_FS(uint8_t event_idx, uint8_t state)
这个里面的事件和状态都是来自此处的buf[0]和buf[1]
 
于是,我可以这样测试一下,我写个代码在
CUSTOM_HID_OutEvent_FS这个函数里
static int8_t CUSTOM_HID_OutEvent_FS  (uint8_t event_idx, uint8_t state)
{
/* USER CODE BEGIN 6 */
switch(event_idx)
{
case 0:
if(state == 0x05)
HAL_GPIO_TogglePin(GPIOD,GPIO_PIN_12);
else if(state == 0x06)
HAL_GPIO_TogglePin(GPIOD,GPIO_PIN_13);
break;
default:
break;

}

return (0);
/* USER CODE END 6 */
}
上面的意思是如果PC端的hid上位机发送0x00 0x05 那么PD12的led进行反转
如果发送的是0x00 0x06 那么PD13的led进行反转
 
这样就可以测试.hid是不是可用了.
于是我进行了烧录,和测试
 

QQ截图20160808173106.jpg

结果表明确实可以控制板子上面的led的亮灭,不过,感觉不是实时的,总有些延时在里面,
判断是usb的hid方面利用的是poll查询发送模式,所以,可以修改这个查询的时间,
 
我们打开,工程中的 usbd_customhid.c 文件
找到控制时间的变量,改为 0x01 即1ms

QQ截图20160808173445.jpg

再次烧录,现在基本感觉不出有任何动作延时了..
 
 
未完待续!
 
 
 
 

admin

赞同来自: icebin 热血熊

大晚上,睡不着,再更新一点吧
透过上面的
int8_t CUSTOM_HID_OutEvent_FS  (uint8_t event_idx, uint8_t state);
这个函数的构造,
我们只能得到两个字节的数据使用,那么我们有方式把更多的字节取出来用吗?而不用对底层的usb打动干戈?
 
猜想可以定义一个类似下面的代码函数
static int8_t CUSTOM_HID_OutDulBuf_FS (uint8_t* DulBuf)
{
return (0);
}
利用指针把需要的数据都取出来,然后处理啥的都可以自己随便弄了
 
我们要是准备这么用肯定要把这个函数注册到包含 
CUSTOM_HID_OutEvent_FS()这个函数的结构体里面去.
由上面我们知道这个函数是在
USBD_CustomHID_fops_FS()
里面注册的,所以我们要再这个函数结构体里面进行添加
USBD_CUSTOM_HID_ItfTypeDef USBD_CustomHID_fops_FS =
{
CUSTOM_HID_ReportDesc_FS,
CUSTOM_HID_Init_FS,
CUSTOM_HID_DeInit_FS,
CUSTOM_HID_OutEvent_FS,
CUSTOM_HID_OutDulBuf_FS,
};
这样我们初始化usb的时候,这个函数就注册到usb里面了,
但是它是由
USBD_CUSTOM_HID_ItfTypeDef来定义的结构体,所以我们也需要在这个定义里添加

typedef struct _USBD_CUSTOM_HID_Itf
{
uint8_t *pReport;
int8_t (* Init) (void);
int8_t (* DeInit) (void);
int8_t (* OutEvent) (uint8_t, uint8_t );
//
int8_t (* OutDulBuf) (uint8_t*);
//
}USBD_CUSTOM_HID_ItfTypeDef;
 
但是还不够,我们并没有在usb的底层程序里调用,
我们找到上面楼层里提到的USBD_CUSTOM_HID_DataOut()这个函数
这个函数在usbd_customhid.c 文件里
函数的原型是:
static uint8_t  USBD_CUSTOM_HID_DataOut (USBD_HandleTypeDef *pdev,
uint8_t epnum)
{

USBD_CUSTOM_HID_HandleTypeDef *hhid = (USBD_CUSTOM_HID_HandleTypeDef*)pdev->pClassData;

((USBD_CUSTOM_HID_ItfTypeDef *)pdev->pUserData)->OutEvent(hhid->Report_buf[0],
hhid->Report_buf[1]);
USBD_LL_PrepareReceive(pdev, CUSTOM_HID_EPOUT_ADDR , hhid->Report_buf,
USBD_CUSTOMHID_OUTREPORT_BUF_SIZE);

return USBD_OK;
}
我们准备再这个里进行调用,就在那些buf[0]和buf[1]之后一行.添加
//-------------------------------
((USBD_CUSTOM_HID_ItfTypeDef *)pdev->pUserData)->OutDulBuf(hhid->Report_buf);
//-------------------------------
这样当usb接收到数据,就会利用函数指针调用,我们添加的函数进行各种操作了
例如下面的测试代码:
static int8_t CUSTOM_HID_OutDulBuf_FS (uint8_t* DulBuf)
{
uint8_t outdata[64];
outdata[0] = *(DulBuf++);
outdata[1] = *(DulBuf++);
outdata[2] = *(DulBuf++);
outdata[3] = *(DulBuf++);
if(outdata[0] != 3)
HAL_Delay(100);
return (0);
}
我们取出usb的数据传递给了一个数组,利用数组可以进行一系列的数据处理了.
 
这个楼层只是我的猜想,还没有进行验证,明天去验证一下.

admin

赞同来自:

根据昨晚的猜想,稍微的猜想代码为可见效果如下
static int8_t CUSTOM_HID_OutBuffEvent_FS  (uint8_t *Buff)
{
switch(*(Buff++))
{
case 1:
if(*(Buff++)==0x14)
HAL_GPIO_TogglePin(GPIOD,GPIO_PIN_14);
if(*(Buff++)==0x15) HAL_GPIO_TogglePin(GPIOD,GPIO_PIN_15);
break;
default:
break;

}
return (0);
}
假定我们发送一个64字节数据通过hid,
QQ截图20160809100350.png

通过keil查看Buff这个内存地址,可以看到内存的数据和发送完全一致!
那么hid的数据接收方面就测试完毕!
 

admin

赞同来自: zhouzhi666 秦时明月wqq hant_9

至于hid的发送,已经集成到了里面,不过被官方给注释掉了.所以,取消掉这个注释
在文件 usbd_custom_hid_if.c中最后一行
/**
* @brief USBD_CUSTOM_HID_SendReport_FS
* Send the report to the Host
* @param report: the report to be sent
* @param len: the report length
* @retval Result of the operation: USBD_OK if all operations are OK else USBD_FAIL
*/

static int8_t USBD_CUSTOM_HID_SendReport_FS ( uint8_t *report,uint16_t len)
{
return USBD_CUSTOM_HID_SendReport(&hUsbDeviceFS, report, len);
}
我们就可以调用这个发送字节到pc了.
为了测试方便,同时懒省事,我将楼上CUSTOM_HID_OutBuffEvent_FS 这个刚刚添加的修改下
static int8_t CUSTOM_HID_OutBuffEvent_FS  (uint8_t *Buff)
{

USBD_CUSTOM_HID_SendReport_FS(Buff,3);

return (0);
}
意思是接收到数据之后返回接收到数据的前三个字节.
当然实际使用中,你可以定义一个协议,这样就知道需要返回多少长度的字节了.
 
ok,hid的初步使用介绍完了.
希望能帮到看到此贴的人,
同时希望大家踊跃分享自己的心得和体会.

无色旋律

赞同来自:

看了你的教程好像不是相当于虚拟出一个串口?我的上位机用的是串口写的,好像还是得修改成HID模式?

一毛

赞同来自:

USB 有 虚拟串口为何还用 HID

cnnblike

赞同来自:

速度可以随便就弄到64kB/s,你自己去把两个EP_SIZE都改成64就行了
还有polling interval那个修改成1ms就行了

twlkengi

赞同来自:

此贴太好,解决我燃眉之急,自己不熟悉USB,而且项目周期紧,根据楼主的一步步操作很快就能正常通信了,上位机用了HIDAPI;

赵振林 - 90后,,,

赞同来自: icebin

老板交给一个USB任务,以前也没接触过,各种资料各种看,最后搞得晕晕乎乎的,跟着大神走了一遍,看见被电脑识别的那一刻,好吧,我承认自己激动了。blob.png

icebin

赞同来自:

讲的非常好,太实用了

admin

赞同来自:

总结:

共修改了四个文件,如果你害怕每次生成的时候把修改好的程序给弄没了,那么,你可以直接复制并替换这四个文件即可.

blob.png

yujunmolu

赞同来自:

好东西,学习

latera

赞同来自:

太好了,学习中

fearless

赞同来自:

如果是自己写上位机实现读取数据,该怎么处理呢?

在网上找了很多winform的程序都没成功

乌龟也会飞

赞同来自:

打什么 为什么我照着你的流程走了一遍然后使用USBHIDdemonstrator来通信不成功啊

Playnetics

赞同来自:

能不能附加上完整代码?我这边照做,发现后面几步多字节通讯的代码就完全没有了注释,或者解释的就不详细了,比如:

static int8_t CUSTOM_HID_OutBuffEvent_FS  (uint8_t *Buff)

这个函数的定义,创建和调用我不知道是怎么样的关系,另外我看到64个字节的保存,我不清楚是不是漏写了?

static int8_t CUSTOM_HID_OutDulBuf_FS (uint8_t* DulBuf)
{
    uint8_t outdata[64];
    outdata[0] = *(DulBuf++);
    outdata[1] = *(DulBuf++);
    outdata[2] = *(DulBuf++);
    outdata[3] = *(DulBuf++);

//请问这里是不是还要一直做到outdata[63]? 另外下面这个判断延时有什么作用吗?

    if(outdata[0] != 3)
        HAL_Delay(100);
    return (0);
}

我之前调过USB_CDC的通讯,但是因为安装驱动程序的问题,搞的头大,因为客户的机器各不相同,特别是win64位的程序,安装驱动要强制签名,然后有的版本USB驱动内核又不完整,很头疼;

一直想搞这个HID的,一直搞不懂,这篇写的比较详细,但是缺点是后面的说明比较省了,不如前面的讲的那么详细,希望能把以上的问题帮我说一下,感谢!

admin

赞同来自:

@Playnetics

函数的创建和调用你要去看 [大晚上,睡不着,再更新一点吧] 这层楼, 

大致意思是:

首先要去usb库函数的USBD_CUSTOM_HID_ItfTypeDef 里面定义一个outdulBuf接收事件函数指针, 

然后去USBD_CustomHID_fops_FS函数里将这个指针实体化 , 

这些都做完之后, 就去usb接收函数里注册这个事件,使这个事件和usb数据接收联系起来, 于是需要在USBD_CUSTOM_HID_DataOut()这个函数里注册, 

这个函数里原来就有一个注册事件

((USBD_CUSTOM_HID_ItfTypeDef *)pdev->pUserData)->OutEvent(hhid->Report_buf[0],

hhid->Report_buf[1]); 

这个事件对应的是int8_t (* OutEvent) (uint8_t, uint8_t ); 

,实体函数是CUSTOM_HID_OutEvent_FS, 

那么我就复制这个事件, 让它重新注册到我刚刚自定义的

((USBD_CUSTOM_HID_ItfTypeDef *)pdev->pUserData)->OutDulBuf(hhid->Report_buf); 

, 这样接收到数据后,我自定义的函数就也能接收到数据, 然后我在自定义的函数里就可以随便怎么去处理数据了

你看下面的箭头的顺序捋捋吧

blob.png

而, 注册事件的函数里, 你注意下面

我是参照原来的写的,原来的只是返回[0]和[1]的数据,我直接自定义返回所有接收到的数组指针...

blob.png



admin

赞同来自:

@Playnetics

至于你说的://请问这里是不是还要一直做到outdata[63]? 另外下面这个判断延时有什么作用吗?

我定义这个outdata[64]只是为了验证是不是真的接收到我pc机发送下来的数据, 通过这个变量在keil里就可以直观看到接收到的数据......还有那个延时没有用, 应该是我之前测试其他的时候留下的忘了删......

Playnetics

赞同来自:

非常感谢,收到,今天按照这个做了一下,发现有时候电脑能识别到 HID设备,但是有时候又无法正确识别,感觉怎么不可靠呢?

无法正确识别的情况会在电脑设备管理器下面的<通用串行总线控制器>下面显示一个 unkonow USB device;

不清楚这个会是啥问题呢 ?

另外我看了上面回复的,我还是没看到使用这个

Playnetics

赞同来自:

static int8_t CUSTOM_HID_OutBuffEvent_FS  (uint8_t *Buff)

但我想是不是版主有误写啊?上面这个函数和下面这个函数有什么关系吗?是怎么样定义调用关系的呢 ?

static int8_t CUSTOM_HID_OutDulBuf_FS (uint8_t* DulBuf)

另外我已经定义了结构体

USBD_CUSTOM_HID_ItfTypeDef USBD_CustomHID_fops_FS =
{
    CUSTOM_HID_ReportDesc_FS,
    CUSTOM_HID_Init_FS,
    CUSTOM_HID_DeInit_FS,
    CUSTOM_HID_OutEvent_FS,
    CUSTOM_HID_OutDulBuf_FS,
};

和添加代码到

static uint8_t  USBD_CUSTOM_HID_DataOut (USBD_HandleTypeDef *pdev,
        uint8_t epnum)
{

    USBD_CUSTOM_HID_HandleTypeDef     *hhid = (USBD_CUSTOM_HID_HandleTypeDef*)pdev->pClassData;

    ((USBD_CUSTOM_HID_ItfTypeDef *)pdev->pUserData)->OutEvent(hhid->Report_buf[0],
            hhid->Report_buf[1]);
//-------------------------------
    ((USBD_CUSTOM_HID_ItfTypeDef *)pdev->pUserData)->OutDulBuf(hhid->Report_buf);
//-------------------------------            
           
    USBD_LL_PrepareReceive(pdev, CUSTOM_HID_EPOUT_ADDR , hhid->Report_buf,
                           USBD_CUSTOMHID_OUTREPORT_BUF_SIZE);

    return USBD_OK;
}

并添加了函数 :

static int8_t CUSTOM_HID_OutDulBuf_FS (uint8_t* DulBuf)
{
    uint8_t outdata[64];
    outdata[0] = *(DulBuf++);
    outdata[1] = *(DulBuf++);
    outdata[2] = *(DulBuf++);
    outdata[3] = *(DulBuf++);

    if(outdata[0] != 3)
        HAL_Delay(100);
    return (0);
}

但是现在就是不知道最上面说的那个函数该怎么添加?

如果可以的话,希望能对下面这个函数说一下?

static int8_t CUSTOM_HID_OutBuffEvent_FS  (uint8_t *Buff)
{
   //这里面的功能是能明白是干什么的 ,但是这个函数该放在哪里,什么时候能调用到这个函数,我不是很清楚
switch(*(Buff++))
{
case 1:
if(*(Buff++)==0x14)
HAL_GPIO_TogglePin(GPIOD,GPIO_PIN_14);
if(*(Buff++)==0x15) HAL_GPIO_TogglePin(GPIOD,GPIO_PIN_15);
break;
default:
break;

}
    return (0);
}


Playnetics

赞同来自:

经过我的初步实验,我发现

static int8_t CUSTOM_HID_OutBuffEvent_FS  (uint8_t *Buff)
{
switch(*(Buff++))
{
case 1:
if(*(Buff++)==0x14)
HAL_GPIO_TogglePin(GPIOD,GPIO_PIN_14);
if(*(Buff++)==0x15) HAL_GPIO_TogglePin(GPIOD,GPIO_PIN_15);
break;
default:
break;

}
    return (0);
}
这个函数是不需要的,目前我做了一个类似USB_CDC的环回接收和发送程序,是在如下的程序:
static int8_t CUSTOM_HID_OutDulBuf_FS (uint8_t* DulBuf)
{
    uint8_t *outdata;
    outdata = DulBuf;    //这个新定义的outdata是不需要的,不过为了调试方便,可以打断点在这里观察实际收到的数据

    USBD_CUSTOM_HID_SendReport_FS ( outdata, 64); //这个就是HID的发送函数,按照楼主的方式打开这个函数的注释之后就可以直接使用了
    return (0);
}
以上接收和发送程序在STM32F103C8T6的芯片实验了可以,既然知道了发送和接收程序,那就好办了,可以和CDC的方式一样使用了 ;谢谢楼主!


有点累

赞同来自:

你好,在网盘中用的历程,显示“未知设备”,系统时钟配置的是168M,USB时钟48M

gx_9988

赞同来自:

梦回庄周

赞同来自:

请问按照步骤烧入后出现未知USB设备怎么回事?图片.png

hklevejkl

赞同来自:

网上的stm32 hid的内容 很多都是只实现了个鼠标或键盘的输入 根本没讲输出的事 好不容易找到这篇文章 解决了我的问题 太感谢了

dgq

赞同来自:

写得很好,能提供合作吗?帮我做一个小系统。用stm32f105来实现PC通信与U盘读写。麻烦联系我1035026591@qq.com

dgq

赞同来自:

根据本文的指导,本人顺利完成了项目。在此表示感谢!

吉米

赞同来自:

感谢分享,很有帮助。

ImpulseHu

赞同来自:

stm32f103c8t6按楼主的方法试了下,可以使用上位机向USB发送数据来控制我的PC13口LED。感谢!!!

沉成林

赞同来自:

f407zg按方法操作,发送数据之后卡死了图片.png

要回复问题请先登录注册