解决usbhost 无法使用hid设备的问题,

发布于 2020-11-28 11:48:10

由于我自己需要用到USBhost接口访问HID设备,所以掉进HID坑里了,官网的hid.c文件估
计有快10年没更新了,与现有代码存在不兼容和错误,然后把这些坑填了填,希望能帮助使
用HID时遇到问题的同学,能将host的hid用起来.
HID设备使用标准的HID协议,用户需要关心的主要是下面这两个接口:

rt_err_t rt_usbh_hid_set_report(struct uhintf* intf, rt_uint8_t type, rt_uint8_t id, rt_uint8_t *buffer, rt_size_t size);
rt_err_t rt_usbh_hid_get_report(struct uhintf* intf, rt_uint8_t type, rt_uint8_t id, rt_uint8_t *buffer, rt_size_t size);

rt_usbh_hid_set_report()接口用于向HID设备发送一条命令,
rt_usbh_hid_get_report()接口用于从HID设备读取返回数据,
所以在应用中这两个函数应该配合使用,如果没有返回数据,则只需set_report接口.
发送指令的格式为:
1) 先发送控制字协议包;对应rt_usb_hcd_setup_xfer()接口;
2) 接着发送用户命令包;对应rt_usb_hcd_pipe_xfer()接口;
3) rt_usb_hcd_pipe_xfer()接口如果传入的是pipe_ep0_out,就是向设备写数据;
rt_usb_hcd_pipe_xfer()接口如果传入的是pipe_ep0_in,就是从设备读数据;
总体来说rtthread的usb驱动架构,使用了线程异步处理,用户应用中数据流就类似于线性的,处理数据很方便;作为对比原子的usb驱动接口使用的状态机模式,在操作系统下,处理usb数据就很麻烦,反而非RTOS下处理起来更容易.
下面贴出hid源码,我们可以一起完善它
hid.c

/*
 * Copyright (c) 2006-2018, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2011-12-12     Yi Qiu      first version
 */

#include <rtthread.h>
#include <drivers/usb_host.h>
#include "hid.h"

#ifdef RT_USBH_HID

static struct uclass_driver hid_driver;
static rt_list_t _protocal_list;

/**
 * This function will do USB_REQ_SET_IDLE request to set idle period to the usb hid device
 *
 * @param intf the interface instance.
 * @duration the idle period of requesting data.
 * @report_id the report id
 * 
 * @return the error code, RT_EOK on successfully.
*/
rt_err_t rt_usbh_hid_set_idle(struct uhintf* intf, int duration, int report_id)
{
    struct urequest setup;
    struct uinstance* device;    
    int timeout = USB_TIMEOUT_BASIC;
        
    /* parameter check */
    RT_ASSERT(intf != RT_NULL);
    RT_ASSERT(intf->device != RT_NULL);

    device = intf->device;

    setup.request_type = USB_REQ_TYPE_DIR_OUT | USB_REQ_TYPE_CLASS | 
        USB_REQ_TYPE_INTERFACE;
    setup.bRequest = USB_REQ_SET_IDLE;
    setup.wIndex = 0;
    setup.wLength = 0;
    setup.wValue = (duration << 8 )| report_id;

    if(rt_usb_hcd_setup_xfer(device->hcd, device->pipe_ep0_out, &setup,
        timeout) == 0) return RT_EOK;
    else return -RT_ERROR;    
}

/**
 * This function will do USB_REQ_GET_REPORT request to get report from the usb hid device
 *
 * @param intf the interface instance.
 * @buffer the data buffer to save usb report descriptor.
 * @param nbytes the size of buffer
 * 
 * @return the error code, RT_EOK on successfully.
*/
rt_err_t rt_usbh_hid_get_report(struct uhintf* intf, rt_uint8_t type, rt_uint8_t id, rt_uint8_t *buffer, rt_size_t size)
{
    struct urequest setup;
    struct uinstance* device;
    int timeout = USB_TIMEOUT_BASIC;
        
    /* parameter check */
    RT_ASSERT(intf != RT_NULL);
    RT_ASSERT(intf->device != RT_NULL);

    device = intf->device;

    setup.request_type = USB_REQ_TYPE_DIR_IN | USB_REQ_TYPE_CLASS | USB_REQ_TYPE_INTERFACE; //0xA1
    setup.bRequest = USB_REQ_GET_REPORT; //0x01
    setup.wIndex = intf->intf_desc->bInterfaceNumber;
    setup.wLength = size;
    setup.wValue = (type << 8 ) + id;

    if(rt_usb_hcd_setup_xfer(device->hcd, device->pipe_ep0_out, &setup, timeout) != 8) {
        return -RT_ERROR;
    }

    if (rt_usb_hcd_pipe_xfer(device->hcd, device->pipe_ep0_in, buffer, size, timeout) != size) {
        return -RT_ERROR;
    }

    return RT_EOK;
}

/**
 * This function will do USB_REQ_SET_REPORT request to set report to the usb hid device
 *
 * @param intf the interface instance.
 * @buffer the data buffer to save usb report descriptor.
 * @param nbytes the size of buffer
 * 
 * @return the error code, RT_EOK on successfully.
*/
rt_err_t rt_usbh_hid_set_report(struct uhintf* intf, rt_uint8_t type, rt_uint8_t id, rt_uint8_t *buffer, rt_size_t size)
{
    struct urequest setup;
    struct uinstance* device;        
    int timeout = USB_TIMEOUT_BASIC;
        
    /* parameter check */
    RT_ASSERT(intf != RT_NULL);
    RT_ASSERT(intf->device != RT_NULL);
    
    device = intf->device;

    setup.request_type = USB_REQ_TYPE_DIR_OUT | USB_REQ_TYPE_CLASS | USB_REQ_TYPE_INTERFACE; //0x21
    setup.bRequest = USB_REQ_SET_REPORT; //0x09
    setup.wValue = type << 8 || id;
    setup.wIndex = intf->intf_desc->bInterfaceNumber;
    setup.wLength = size;

    if(rt_usb_hcd_setup_xfer(device->hcd, device->pipe_ep0_out, &setup, timeout) != 8) {
        return -RT_ERROR;
    }

    if (rt_usb_hcd_pipe_xfer(device->hcd, device->pipe_ep0_out, buffer, size, timeout) != size) {
        return -RT_ERROR;
    }

    return RT_EOK;
}

/**
 * This function will do USB_REQ_SET_PROTOCOL request to set protocal to the usb hid device.
 *
 * @param intf the interface instance.
 * @param protocol the protocol id.
 * 
 * @return the error code, RT_EOK on successfully.
 */
rt_err_t rt_usbh_hid_set_protocal(struct uhintf* intf, int protocol)
{
    struct urequest setup;
    struct uinstance* device;
    int timeout = USB_TIMEOUT_BASIC;
        
    /* parameter check */
    RT_ASSERT(intf != RT_NULL);
    RT_ASSERT(intf->device != RT_NULL);
    
    device = intf->device;

    setup.request_type = USB_REQ_TYPE_DIR_OUT | USB_REQ_TYPE_CLASS | 
        USB_REQ_TYPE_INTERFACE;
    setup.bRequest = USB_REQ_SET_PROTOCOL;
    setup.wIndex = 0;
    setup.wLength = 0;
    setup.wValue = protocol;

    if(rt_usb_hcd_setup_xfer(device->hcd, device->pipe_ep0_out, &setup,
        timeout) == 0) return RT_EOK;
    else return -RT_ERROR;    
}

/**
 * This function will do USB_REQ_GET_DESCRIPTOR request for the device instance 
 * to set feature of the hub port.
 *
 * @param intf the interface instance.
 * @buffer the data buffer to save usb report descriptor.
 * @param nbytes the size of buffer
 * 
 * @return the error code, RT_EOK on successfully.
 */
rt_err_t rt_usbh_hid_get_report_descriptor(struct uhintf* intf, rt_uint8_t *buffer, rt_size_t size)
{
    struct urequest setup;
    struct uinstance* device;    
    int timeout = USB_TIMEOUT_BASIC;
        
    /* parameter check */
    RT_ASSERT(intf != RT_NULL);
    RT_ASSERT(intf->device != RT_NULL);
    
    device = intf->device;

    setup.request_type = USB_REQ_TYPE_DIR_IN | USB_REQ_TYPE_STANDARD| 
        USB_REQ_TYPE_INTERFACE;
    setup.bRequest = USB_REQ_GET_DESCRIPTOR;
    setup.wIndex = 0;
    setup.wLength = size;
    setup.wValue = USB_DESC_TYPE_REPORT << 8;

    if(rt_usb_hcd_setup_xfer(device->hcd, device->pipe_ep0_out, &setup,
        timeout) == size) return RT_EOK;
    else return -RT_ERROR;
}

/**
 * This function will register specified hid protocal to protocal list
 *
 * @param protocal the specified protocal.
 * 
 * @return the error code, RT_EOK on successfully.
 */
rt_err_t rt_usbh_hid_protocal_register(uprotocal_t protocal)
{
    RT_ASSERT(protocal != RT_NULL);

    if (protocal == RT_NULL) return -RT_ERROR;

    /* insert class driver into driver list */
    rt_list_insert_after(&_protocal_list, &(protocal->list));
    
    return RT_EOK;    
}

/**
 * This function is the callback function of hid's int endpoint, it is invoked when data comes.
 *
 * @param context the context of the callback function.
 * 
 * @return none.
 */
static void rt_usbh_hid_callback(void* context)
{
    upipe_t pipe; 
    struct uhid* hid;
    int timeout = USB_TIMEOUT_LONG;

    /* parameter check */
    RT_ASSERT(context != RT_NULL);
    
    pipe = (upipe_t)context;
    hid = (struct uhid*)pipe->user_data;
    rt_kprintf("%s()\n", __func__);
    /* This function is not used in HID protocal?! */
    /* invoke protocal callback function */
    hid->protocal->callback((void*)hid);

    /* parameter check */
     RT_ASSERT(pipe->inst->hcd != RT_NULL);

    rt_usb_hcd_pipe_xfer(pipe->inst->hcd, pipe, hid->buffer,
        pipe->ep.wMaxPacketSize, timeout);
}

/**
 * This function will find specified hid protocal from protocal list
 *
 * @param pro_id the protocal id.
 * 
 * @return the found protocal or RT_NULL if there is no this protocal.
 */
static uprotocal_t rt_usbh_hid_protocal_find(int pro_id)
{
    struct rt_list_node *node;

    /* try to find protocal object */
    for (node = _protocal_list.next; node != &_protocal_list; node = node->next)
    {
        uprotocal_t protocal = (uprotocal_t)rt_list_entry(node, struct uprotocal, list);
        rt_kprintf("protocal->pro_id=%d\n", protocal->pro_id);
        if (protocal->pro_id == pro_id) return protocal;
    }

    /* not found */
    return RT_NULL;
}

/**
 * This function will run hid class driver when usb device is detected and identified
 *  as a hid class device, it will continue the enumulate process.
 *
 * @param arg the argument.
 * 
 * @return the error code, RT_EOK on successfully.
 */
static rt_err_t rt_usbh_hid_enable(void* arg)
{
    int i = 0, pro_id;
    uprotocal_t protocal;    
    struct uhid* hid;
    struct uhintf* intf = (struct uhintf*)arg;
    int timeout = USB_TIMEOUT_BASIC;
    upipe_t pipe;
    
    /* parameter check */
    if(intf == RT_NULL)
    {
        rt_kprintf("the interface is not available\n");
        return -RT_EIO;
    }

    pro_id = intf->intf_desc->bInterfaceProtocol;

    RT_DEBUG_LOG(RT_DEBUG_USB, ("HID device enable, protocal id %d\n", pro_id));

    protocal = rt_usbh_hid_protocal_find(pro_id);    
    if(protocal == RT_NULL)
    {
        rt_kprintf("can't find hid protocal %d\n", pro_id);
        intf->user_data = RT_NULL;        
        return -RT_ERROR;
    }
    
    hid = rt_malloc(sizeof(struct uhid));
    RT_ASSERT(hid != RT_NULL);

    /* initilize the data structure */    
    rt_memset(hid, 0, sizeof(struct uhid));
    intf->user_data = (void*)hid;
    hid->protocal = protocal;

    for(i=0; i<intf->intf_desc->bNumEndpoints; i++)
    {    
        rt_err_t ret;
        uep_desc_t ep_desc;

        /* get endpoint descriptor */        
        rt_usbh_get_endpoint_descriptor(intf->intf_desc, i, &ep_desc);
        if(ep_desc == RT_NULL)
        {
            rt_kprintf("rt_usbh_get_endpoint_descriptor error\n");
            return -RT_ERROR;
        }
                
        if(USB_EP_ATTR(ep_desc->bmAttributes) != USB_EP_ATTR_INT) 
            continue;
    
        if(!(ep_desc->bEndpointAddress & USB_DIR_IN)) continue;

        ret = rt_usb_hcd_alloc_pipe(intf->device->hcd, &hid->pipe_in, intf->device, ep_desc);

        if(ret != RT_EOK) return ret;
    }

    /* initialize hid protocal */
    hid->protocal->init((void*)intf);  
#if 0      
    pipe = hid->pipe_in;

    /* parameter check */
     RT_ASSERT(pipe->inst->hcd != RT_NULL);
    
    rt_usb_hcd_pipe_xfer(pipe->inst->hcd, pipe,
        hid->buffer, pipe->ep.wMaxPacketSize, timeout);
    /* 这里直接读取一个报告?按照HID规范,读报告都要先设定报告指令,再读取,因为
    一般HID设备都有多个报告, 所以这里直接读取的行为不合适.只有mouse类HID设备
    才可能使用这种方式,驱动应该是适应多数设备的,所以这里屏蔽掉! */
#endif        
    return RT_EOK;
}

/**
 * This function will be invoked when usb device plug out is detected and it would clean 
 * and release all hub class related resources.
 *
 * @param arg the argument.
 * 
 * @return the error code, RT_EOK on successfully.
 */
static rt_err_t rt_usbh_hid_disable(void* arg)
{
    struct uhid* hid;
    struct uhintf* intf = (struct uhintf*)arg;

    RT_ASSERT(intf != RT_NULL);

    RT_DEBUG_LOG(RT_DEBUG_USB, ("rt_usbh_hid_disable\n"));

    hid = (struct uhid*)intf->user_data;
    if(hid != RT_NULL)
    {
        if(hid->pipe_in != RT_NULL)
        {
            /* free the HID in pipe */
            rt_usb_hcd_free_pipe(intf->device->hcd, hid->pipe_in);
        }

        /* free the hid instance */    
        rt_free(hid);
    }
    
    /* free the instance */
    rt_free(intf);

    return RT_EOK;
}

/**
 * This function will register hid class driver to the usb class driver manager.
 * and it should be invoked in the usb system initialization.
 * 
 * @return the error code, RT_EOK on successfully.
 */
ucd_t rt_usbh_class_driver_hid(void)
{
    rt_list_init(&_protocal_list);

    hid_driver.class_code = USB_CLASS_HID;
    
    hid_driver.enable = rt_usbh_hid_enable;
    hid_driver.disable = rt_usbh_hid_disable;

    return &hid_driver;
}

#endif

hid.h

/*
 * Copyright (c) 2006-2018, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2011-12-12     Yi Qiu      first version
 */

#ifndef __HID_H__
#define __HID_H__

#include <rtthread.h>

struct uhid
{
    upipe_t pipe_in;
    rt_uint8_t buffer[8];
    uprotocal_t protocal;
};    
typedef struct uhid uhid_t;

#define USB_REQ_GET_REPORT      0x01
#define USB_REQ_GET_IDLE        0x02
#define USB_REQ_GET_PROTOCOL    0x03
#define USB_REQ_SET_REPORT      0x09
#define USB_REQ_SET_IDLE        0x0a
#define USB_REQ_SET_PROTOCOL    0x0b

#define USB_HID_TOUCH           0
#define USB_HID_KEYBOARD        1    
#define USB_HID_MOUSE           2

rt_err_t rt_usbh_hid_set_idle(struct uhintf* intf, int duration, int report_id);
rt_err_t rt_usbh_hid_get_report(struct uhintf* intf, rt_uint8_t type, rt_uint8_t id, rt_uint8_t *buffer, rt_size_t size);
rt_err_t rt_usbh_hid_set_report(struct uhintf* intf, rt_uint8_t type, rt_uint8_t id, rt_uint8_t *buffer, rt_size_t size);
rt_err_t rt_usbh_hid_set_protocal(struct uhintf* intf, int protocol);
rt_err_t rt_usbh_hid_get_report_descriptor(struct uhintf* intf, rt_uint8_t *buffer, rt_size_t size);
rt_err_t rt_usbh_hid_protocal_register(uprotocal_t protocal);
ucd_t rt_usbh_class_driver_hid(void);

#endif

举例应用方法,我这里以读取USB触摸屏info为例:

static rt_uint8_t outbuf[64];
void ilitek_read_tp_info(struct uhintf* intf)
{
    rt_uint8_t buf[64] = {0x03, 0xA3, 1, 5, 0x61}; //0x61
    /* parameter2&3 varies from device to device!! */
    if (rt_usbh_hid_set_report(intf, 0x02, 0x03, buf, 5) == RT_EOK) {
        rt_kprintf("set report 0x61 OK\n");

        if (rt_usbh_hid_get_report(intf, 0x02, 0x03, outbuf, 9) == RT_EOK) {
            rt_kprintf("get report 0x61 OK\n");
            /* In outbuf is the returned data from HID device */
            /* 03 A3 61 05 11 25 0D 00 01 */ /* my touch chip is ili2511 */
        }
    }
}

参考umouse.c实现touch的接口函数,


static rt_err_t rt_usbh_hid_touch_init(void* arg)
{
    struct uhintf* intf = (struct uhintf*)arg;

    RT_ASSERT(intf != RT_NULL);

    rt_usbh_hid_set_protocal(intf, 0); /* 指定HID设备的协议, 协议号从接口描述符中获取 */
    rt_usbh_hid_set_idle(intf, 10, 0);

#ifdef RT_USING_RTGUI
    //EVENT_MOUSE_BUTTON_INIT(&etouch); /* etouch是我的触摸屏的数据接口,因设备而异 */
#endif

    ilitek_read_tp_info(intf);

    return RT_EOK;
}

/**
 * This function will define the hid touch protocal, it will be register to the protocal list.
 *
 * @return the touch protocal structure.
 */
uprotocal_t rt_usbh_hid_protocal_touch(void)
{
    touch_protocal.pro_id = USB_HID_TOUCH;

    touch_protocal.init = rt_usbh_hid_touch_init;
    touch_protocal.callback = rt_usbh_hid_touch_callback;

    return &touch_protocal;
}

在usbhost.c中的rt_usb_host_init()接口中加入HID相关初始化

rt_err_t rt_usb_host_init(void)
{
    ucd_t drv;
    rt_device_t uhc;
    uprotocal_t protocal;

    uhc = rt_device_find(USB_HOST_CONTROLLER_NAME);
    if(uhc == RT_NULL)
    {
        rt_kprintf("can't find usb host controller %s\n", USB_HOST_CONTROLLER_NAME);
        return -RT_ERROR;
    }

    /* initialize usb hub */
    rt_usbh_hub_init((uhcd_t)uhc);

    /* initialize class driver */
    rt_usbh_class_driver_init();

#ifdef RT_USBH_MSTORAGE
    /* register mass storage class driver */
    drv = rt_usbh_class_driver_storage();
    rt_usbh_class_driver_register(drv);
#endif

    /* register hub class driver */
    drv = rt_usbh_class_driver_hub();
    rt_usbh_class_driver_register(drv);

    drv = rt_usbh_class_driver_hid();
    rt_usbh_class_driver_register(drv);
#ifdef RT_USBH_HID_MOUSE
    protocal = rt_usbh_hid_protocal_mouse();
    rt_usbh_hid_protocal_register(protocal);
#endif

#ifdef RT_USBH_HID_TOUCH
    protocal = rt_usbh_hid_protocal_touch();
    rt_usbh_hid_protocal_register(protocal);
#endif

    /* initialize usb host controller */
    rt_device_init(uhc);

    return RT_EOK;
}

查看更多

关注者
2
被浏览
385
aozima
aozima 2020-11-28
拒绝白嫖,拒绝键盘侠!

感谢楼主分享

4 个回答
iamyhw
iamyhw 2020-11-28

有个接口函数

/**
 * This function is the callback function of hid's int endpoint, it is invoked when data comes.
 *
 * @param context the context of the callback function.
 * 
 * @return none.
 */
static void rt_usbh_hid_callback(void *context);

没有搞懂应该在哪里调用它,我的理解是,在用户处理数据的线程中调用这样的一个接口,看注释该接口的设定是由中断调用的,但是中断处理部分已经由drv_usbh.c的drv_pipe_xfer()接口处理好了.所以这里很迷惑!

じ★ve萫彯彯
じ★ve萫彯彯 2020-12-28
rt_err_t rt_usbh_hid_set_report(struct uintf* intf, rt_uint8_t *buffer, rt_size_t size)

请问,为什么我这边的驱动是这个样子的,然后这边找不到uintf结构体

好的名字
好的名字 2021-03-09

想问一下楼主,初始化完成之后是怎么操作的,怎么进行连接设备,接收数据等操作。

撰写答案

请登录后再发布答案,点击登录

发布
问题

分享
好友

手机
浏览

扫码手机浏览