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

注:本篇文章转载自http://www.stm32cube.com/question/527

我按照该文章配置STM32F103ZE开发板可行。

有两点补充:

一、调试的时候要注意将编译等级修改为0级;

二、CUSTOM_HID_EPIN_SIZE及CUSTOM_HID_EPOUT_SIZE宏定义修改为0X40。

【STM32】【USB】一步步实现stm32cube的usb之CUSTOM_HID当串口使用 (stm32F407discoery板)_第1张图片

以下为转载内容:

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

【STM32】【USB】一步步实现stm32cube的usb之CUSTOM_HID当串口使用 (stm32F407discoery板)_第2张图片


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

【STM32】【USB】一步步实现stm32cube的usb之CUSTOM_HID当串口使用 (stm32F407discoery板)_第3张图片


 

【STM32】【USB】一步步实现stm32cube的usb之CUSTOM_HID当串口使用 (stm32F407discoery板)_第4张图片


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

【STM32】【USB】一步步实现stm32cube的usb之CUSTOM_HID当串口使用 (stm32F407discoery板)_第5张图片


中断号配置界面
 

【STM32】【USB】一步步实现stm32cube的usb之CUSTOM_HID当串口使用 (stm32F407discoery板)_第6张图片


 
usb device配置图(备注:下图中的2可修改为33.

【STM32】【USB】一步步实现stm32cube的usb之CUSTOM_HID当串口使用 (stm32F407discoery板)_第7张图片


usb 端口描述图
 

【STM32】【USB】一步步实现stm32cube的usb之CUSTOM_HID当串口使用 (stm32F407discoery板)_第8张图片


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

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

【STM32】【USB】一步步实现stm32cube的usb之CUSTOM_HID当串口使用 (stm32F407discoery板)_第9张图片



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

【STM32】【USB】一步步实现stm32cube的usb之CUSTOM_HID当串口使用 (stm32F407discoery板)_第10张图片



就是上面这个地方,
我是准备让他弄成串口类似的功能,所以不想定义什么乱七八糟的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	             */
  
};


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

【STM32】【USB】一步步实现stm32cube的usb之CUSTOM_HID当串口使用 (stm32F407discoery板)_第11张图片



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

#define USBD_CUSTOM_HID_REPORT_DESC_SIZE 33


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

【STM32】【USB】一步步实现stm32cube的usb之CUSTOM_HID当串口使用 (stm32F407discoery板)_第12张图片

 

我们现在开始看生成的工程代码,哪些是需要自己写的,哪些是系统写好的可以直接用的.

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 的位置,
 

【STM32】【USB】一步步实现stm32cube的usb之CUSTOM_HID当串口使用 (stm32F407discoery板)_第13张图片


发现貌似没有什么值得注意的被调用的地方,所以初步判断这个应该是利用了其他方法调用的,比如:指针
但是尽管事其他类似指针的调用,也要找出来,要不然不知道这个东西怎么用啊,
 
回到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是不是可用了.
于是我进行了烧录,和测试

【STM32】【USB】一步步实现stm32cube的usb之CUSTOM_HID当串口使用 (stm32F407discoery板)_第14张图片


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

【STM32】【USB】一步步实现stm32cube的usb之CUSTOM_HID当串口使用 (stm32F407discoery板)_第15张图片


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

大晚上,睡不着,再更新一点吧
透过上面的

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的数据传递给了一个数组,利用数组可以进行一系列的数据处理了.

假定我们发送一个64字节数据通过hid,

【STM32】【USB】一步步实现stm32cube的usb之CUSTOM_HID当串口使用 (stm32F407discoery板)_第16张图片


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

至于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_OutDulBuf_FS 这个刚刚添加的修改下

static int8_t CUSTOM_HID_OutDulBuf_FS (uint8_t *Buff)
{

	USBD_CUSTOM_HID_SendReport_FS(Buff,3);
	
    return (0);
}

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

你可能感兴趣的:(STM32)