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

本文详细介绍了STM32F103ZE开发板USB HID功能的配置过程,包括时钟配置、端点设置、宏定义修改等关键步骤,并探讨了如何自定义数据处理函数以实现LED控制和数据收发功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

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

有两点补充:

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

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

以下为转载内容:

每次用串口调试感觉麻烦死了,尤其是电脑上没有串口,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配置图(备注:下图中的2可修改为33.

QQ截图20160808164033.png


usb 端口描述图
 

QQ截图20160808164051.png


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

编译之后,直接烧录到板卡,插入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

 

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

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


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

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

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,

QQ截图20160809100350.png


通过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的初步使用介绍完了.
希望能帮到看到此贴的人,
同时希望大家踊跃分享自己的心得和体会.

STM32F103C8 Serial(UART) to USB HID Keyboard Mouse 串口 USB键盘鼠标 (1) 使用Composite Device 组合(复合)设备 (1.1) 1个Device -> 1个 Configuation -> 2个Interfance (Keyboard & Mouse) (1.2) Keyboard Interfance -> HID (boot mode) -> 2个Endpoint(IN_0x81 & OUT_0x01) -> KeyboardReportDescriptor(使用Report ID) (1.3) Mouse Interfance -> HID (boot mode) -> 1个Endpoint(IN_0x82) -> MouseReportDescriptor(使用Report ID) (1.4) 使用HID boot模式, 不使用Report ID, 以便兼容在 计算器设定BIOS模式 中的操作 (2) 串口接收 命令 (2.1) UART协议: 115200, n, 8, 1 (2.2) 1帧发送字符串格式, 以 '{'开始; '}'结束; ','分隔. 共9个10进制数字 例如: {1,2,3,4,5,6,7,8,9} (2.3) 第9位 区分 Keyboard(64) 或是 Mouse(128) 命令 例如: {0,0,0,0,0,0,0,0,64} --- 发送Keyboard命令 {0,0,0,0,0,0,0,0,128} --- 发送Keyboard命令 (3) 发送Keyboard键盘命令时 : 第1~8位 分别如下 (3.1) 第1位 : Key_Release = 0x00, Left_Control = 0x01, Left_Shift = 0x02, Left_Alt = 0x04, Left_GUI = 0x08, Right_Control = 0x10, Right_Shift = 0x20, Right_Alt = 0x40, Right_GUI = 0x80, 例如: {8,0,0,0,0,0,0,0,64} --- 发送 Win_Key键 {128,0,0,0,0,0,0,0,64} --- 发送 WinApp_Key键 {32,0,0,0,0,0,0,0,64} --- 发送 右Shift键 (3.2) 第2位 : 保留,不使用,一律填0 (3.3) 第3~8位 : 可以同时发送6个Keyboard按键 例如: {0,0,4,5,6,7,8,9,64} --- 发送 'abcdef'键 {2,0,4,5,6,7,8,9,64} --- 按住 左Shift 发送 'abcdef'键 => 'ABCDEF' {0,0,0,5,0,7,0,9,64} --- 发送 'bdf'键 (0表示 无按键) 按键码 可参阅: (HID Usage ID) http://download.microsoft.com/download/1/6/1/161ba512-40e2-4cc9-843a-923143f3456c/translate.pdf https://www.hiemalis.org/~keiji/PC/scancode-translate.pdf https://gist.github.com/MightyPork/6da26e382a7ad91b5496ee55fdc73db2 http://www.usb.org/developers/hidpage/Hut1_12v2.pdf (4) 发送Mouse鼠标命令时 : 第1~8位 分别如下 (4.1) 第1位 : Button_Release = 0x00, Left_Button = 0x01, Right_Button = 0x02, Mid_Button = 0x04, 例如: {1,0,0,0,0,0,0,0,128} --- 点击 左键 {2,0,0,0,0,0,0,0,128} --- 点击 右键 {4,0,0,0,0,0,0,0,128} --- 点击 中键 (4.2) 第2~4位 : 移动(X,Y), 滚轮(Wheel) X: -127~127:左右移动鼠标 Y: -127~127:上下移动鼠标 Wheel: -127~127:上下动滚轮 例如: {0,20,-10,0,0,0,0,0,128} --- 鼠标 右移20,上移10 {0,0,0,-30,0,0,0,0,128} --- 滚轮-30 (4.2) 第5~8位 : 保留,不使用,一律填0
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值