实现这种USB HID复合设备有两种方法,在《USB HID协议入门》一节已经讲到其中一种方法,说一个USB HID设备可以包含多种功能的报告描述符合集,这样可以实现复合设备,如带鼠标功能的USB键盘,这种复合键盘可以通过在报告描述里包含键盘和鼠标两种报告来实现,两个报告用报告ID来区分。这节我们就用这种方法来实现同时带鼠标和键盘功能的USB HID复合设备,有关另外一种方法的详细教程和实例可以参考本工作室推出的USB学习板。

  既然可以用“在报告描述里包含键盘和鼠标两种报告来实现”,那么我们就把上两节的键盘和鼠标实例的报告描述符放在一起,再加上报告ID就是了,修改后的报告描述符如下:

//该报告描述符号由HID Descriptor tool生成
code ] = {

//-------------键盘部分报告描述符----------------
    //表示用途页为通用桌面设备
    0x05, 0x01,                    // USAGE_PAGE (Generic Desktop)

    //表示用途为键盘
    0x09, 0x06,                    // USAGE (Keyboard)

    //表示应用集合,必须要以END_COLLECTION来结束它,见最后的END_COLLECTION
    0xa1, 0x01,                    // COLLECTION (Application)

    //报告ID(报告ID 0是保留的)
    0x85, 0x01, //Report ID (1)

    //表示用途页为按键
    0x05, 0x07,                    //   USAGE_PAGE (Keyboard)

    //用途最小值,这里为左ctrl键
    0x19, 0xe0,                    //   USAGE_MINIMUM (Keyboard LeftControl)
    //用途最大值,这里为右GUI键,即window键
    0x29, 0xe7,                    //   USAGE_MAXIMUM (Keyboard Right GUI)
    //逻辑最小值为0
    0x15, 0x00,                    //   LOGICAL_MINIMUM (0)
    //逻辑最大值为1
    0x25, 0x01,                    //   LOGICAL_MAXIMUM (1)
    //报告大小(即这个字段的宽度)为1bit,所以前面的逻辑最小值为0,逻辑最大值为1
    0x75, 0x01,                    //   REPORT_SIZE (1)
    //报告的个数为8,即总共有8个bits
    0x95, 0x08,                    //   REPORT_COUNT (8)
    //输入用,变量,值,绝对值。像键盘这类一般报告绝对值,
    //而鼠标移动这样的则报告相对值,表示鼠标移动多少
    0x81, 0x02,                    //   INPUT (Data,Var,Abs)
    //上面这这几项描述了一个输入用的字段,总共为8个bits,每个bit表示一个按键
    //分别从左ctrl键到右GUI键。这8个bits刚好构成一个字节,它位于报告的第一个字节。
    //它的最低位,即bit-0对应着左ctrl键,如果返回的数据该位为1,则表示左ctrl键被按下,
    //否则,左ctrl键没有按下。最高位,即bit-7表示右GUI键的按下情况。中间的几个位,
    //需要根据HID协议中规定的用途页表(HID Usage Tables)来确定。这里通常用来表示
    //特殊键,例如ctrl,shift,del键等 

    //这样的数据段个数为1
    0x95, 0x01,                    //   REPORT_COUNT (1)
    //每个段长度为8bits
    0x75, 0x08,                    //   REPORT_SIZE (8)
    //输入用,常量,值,绝对值
    0x81, 0x03,                    //   INPUT (Cnst,Var,Abs)

    //上面这8个bit是常量,设备必须返回0

    //这样的数据段个数为5
    0x95, 0x05,                    //   REPORT_COUNT (5)
    //每个段大小为1bit
    0x75, 0x01,                    //   REPORT_SIZE (1)
    //用途是LED,即用来控制键盘上的LED用的,因此下面会说明它是输出用
    0x05, 0x08,                    //   USAGE_PAGE (LEDs)
    //用途最小值是Num Lock,即数字键锁定灯
    0x19, 0x01,                    //   USAGE_MINIMUM (Num Lock)
    //用途最大值是Kana,这个是什么灯我也不清楚^_^
    0x29, 0x05,                    //   USAGE_MAXIMUM (Kana)
    //如前面所说,这个字段是输出用的,用来控制LED。变量,值,绝对值。
    //1表示灯亮,0表示灯灭
    0x91, 0x02,                    //   OUTPUT (Data,Var,Abs)

    //这样的数据段个数为1
    0x95, 0x01,                    //   REPORT_COUNT (1)
    //每个段大小为3bits
    0x75, 0x03,                    //   REPORT_SIZE (3)
    //输出用,常量,值,绝对
    0x91, 0x03,                    //   OUTPUT (Cnst,Var,Abs)
    //由于要按字节对齐,而前面控制LED的只用了5个bit,
    //所以后面需要附加3个不用bit,设置为常量。

    //报告个数为6
    0x95, 0x06,                    //   REPORT_COUNT (6)
    //每个段大小为8bits
    0x75, 0x08,                    //   REPORT_SIZE (8)
    //逻辑最小值0
    0x15, 0x00,                    //   LOGICAL_MINIMUM (0)
    //逻辑最大值255
    0x25, 0xFF,                    //   LOGICAL_MAXIMUM (255)
    //用途页为按键
    0x05, 0x07,                    //   USAGE_PAGE (Keyboard)
    //使用最小值为0
    0x19, 0x00,                    //   USAGE_MINIMUM (Reserved (no event indicated))
    //使用最大值为0x65
    0x29, 0x65,                    //   USAGE_MAXIMUM (Keyboard Application)
    //输入用,变量,数组,绝对值
    0x81, 0x00,                    //   INPUT (Data,Ary,Abs)
    //以上定义了6个8bit宽的数组,每个8bit(即一个字节)用来表示一个按键,所以可以同时
    //有6个按键按下。没有按键按下时,全部返回0。如果按下的键太多,导致键盘扫描系统
    //无法区分按键时,则全部返回0x01,即6个0x01。如果有一个键按下,则这6个字节中的第一
    //个字节为相应的键值(具体的值参看HID Usage Tables),如果两个键按下,则第1、2两个
    //字节分别为相应的键值,以次类推。

    //关集合,跟上面的对应
    0xc0 ,                          // END_COLLECTION

    //-----------------------鼠标部分报告描述符----------------------------
    0x05, 0x01,                    // USAGE_PAGE (Generic Desktop)
    0x09, 0x02,                    // USAGE (Mouse)
    0xa1, 0x01,                    // COLLECTION (Application)
    0x85, 0x02,               // 报告ID (2)
    0x09, 0x01,                    //   USAGE (Pointer)
    0xa1, 0x00,                    //   COLLECTION (Physical)
    0x05, 0x09,                    //     USAGE_PAGE (Button)
    0x19, 0x01,                    //     USAGE_MINIMUM (Button 1)
    0x29, 0x03,                    //     USAGE_MAXIMUM (Button 3)
    0x15, 0x00,                    //     LOGICAL_MINIMUM (0)
    0x25, 0x01,                    //     LOGICAL_MAXIMUM (1)
    0x95, 0x03,                    //     REPORT_COUNT (3)
    0x75, 0x01,                    //     REPORT_SIZE (1)
    0x81, 0x02,                    //     INPUT (Data,Var,Abs)
    0x95, 0x01,                    //     REPORT_COUNT (1)
    0x75, 0x05,                    //     REPORT_SIZE (5)
    0x81, 0x03,                    //     INPUT (Cnst,Var,Abs)
    0x05, 0x01,                    //     USAGE_PAGE (Generic Desktop)
    0x09, 0x30,                    //     USAGE (X)
    0x09, 0x31,                    //     USAGE (Y)
    0x09, 0x38,                    //     USAGE (Wheel)
    0x15, 0x81,                    //     LOGICAL_MINIMUM (-127)
    0x25, 0x7f,                    //     LOGICAL_MAXIMUM (127)
    0x75, 0x08,                    //     REPORT_SIZE (8)
    0x95, 0x03,                    //     REPORT_COUNT (3)
    0x81, 0x06,                    //     INPUT (Data,Var,Rel)
    0xc0,                          //   END_COLLECTION
    0xc0                           // END_COLLECTION
};

  这个报告描述符定义了两个报告输入报告(即数据包)和一个输出报告,两个输入报告中,一个是键盘,一个是鼠标,输出报告是用于指示LED状态的,两个输出报告所定义的数据包格式可以参考上两节内容。

  由于电路板上只有两个按键,所有用K1模拟鼠标左移,K2 模拟键盘上的Windows图标键(按下K2后会弹出开始菜单),其代码如下:

void main()
{
    unsigned ;
    signed ];
    ;        //键按下标志,防止重入
    ;        //键按下标志,防止重入

    )                    //初始化D12
        return;                            //如果初始化不成功,返回

    IT0    = ;                            //外部中断0为电平触发方式

    EX0 = ;                            //开外部中断0
    PX0 = ;                            //设置外部中断0中断优先级
    EA     = ;                                //开80C51总中断

    P0    = ;

    )
    {
        usbserve();                        //处理USB事件
        if(bEPPflags.bits.configuration)
        {
            //在这里添加端点操作代码
            if(bEPPflags.bits.ep2_rxdone )    //主端点接收到数据(从主机发往设备的数据)
            {
                bEPPflags.bits.ep2_rxdone        = ;

                //判断NumLock状态
                ] & 0x01)    //EpBuf为接收缓冲
                {
                    P0    = 0x01;
                }
                else
                {
                    P0    = 0x00;
                }                

            }

            K1    = ;        //P3.5
            K2    = ;        //P3.6

            ;i<;i++);    //延时 

            if(~K1 )    //K1按下(模拟鼠标左移)
            {
                cKeyIn[]=;        //报告ID,第一个字节为报告ID(报告描述符里定义了鼠标ID为2)

                cKeyIn[]=;
                cKeyIn[]=-;            //鼠标左移
                cKeyIn[]=;
                cKeyIn[]=;        

                D12_WriteEndpoint(,,cKeyIn);            //发5个字节到PC机,第一个字节为报告ID(报告描述符里定义了鼠标ID为2)
            }

            if(~K2 & !bKey2Pressed)    //K2按下(模拟左Windows键)
            {

                bKey2Pressed    = ;    //防止重入

                cKeyIn[]=;        //报告ID,第一个字节为报告ID(报告描述符里定义了键盘ID为1)
                cKeyIn[]=0x08;        //特殊键
                cKeyIn[]=;        //保留
                cKeyIn[]=;
                cKeyIn[]=;
                cKeyIn[]=;
                cKeyIn[]=;
                cKeyIn[]=;
                cKeyIn[]=;        

                D12_WriteEndpoint(,,cKeyIn);            //发9个字节到PC机,第一个字节为报告ID(报告描述符里定义了键盘ID为1)
            }
            else if(K2 & bKey2Pressed)    //K2弹起
            {
                bKey2Pressed    = ;    //防止重入

                cKeyIn[]=;        //报告ID,第一个字节为报告ID(报告描述符里定义了键盘ID为1)
                cKeyIn[]=;
                cKeyIn[]=;        //保留
                cKeyIn[]=;
                cKeyIn[]=;
                cKeyIn[]=;
                cKeyIn[]=;
                cKeyIn[]=;
                cKeyIn[]=;        

                D12_WriteEndpoint(,,cKeyIn);            //发9个字节到PC机,第一个字节为报告ID(报告描述符里定义了键盘ID为1)
            }

        }
    }
}

  实例中为了演示方便,没有加入按键消抖功能,实际应用中应加上。

  从实例中可以看出,所有输入输出数据包都在最低字节位置插入了一个报告ID,有用数据都从第二个字节开始。

  编译、烧录程序,插上设备,实际测试看看效果,再打开设备管理器,发现人体学输入设备里只多出了一个人体学输入设备(图中另一个是我本来的USB鼠标),在键盘和鼠标里都多了一个HID类型的键盘和鼠标,再分别查看它们的PID和VID,发现都是一样的。

       
 

USB HID复合设备实例—键盘+鼠标的更多相关文章

  1. linux 读取 USB HID鼠标坐标和点击 在 LCD上显示

    首先要,编译内核时启用了 USB HID 设备.启用了 鼠标 . 在开发板上插入usb 时会有如下提示. 可以看到,多了一个 mouse0 和 eventX 打出来的是我的 联想鼠标. 1, 在 终端 ...

  2. USB HID 协议入门

    转载请注明来源:cuixiaolei的技术博客 USB HID设备类的应用场合 USB HID类是USB设备的一个标准设备类,包括的设备非常多.HID类设备定义它属于人机交互操作的设备,用于控制计算机 ...

  3. C#进阶——记一次USB HID的各种坑(x86,x64,win10,win7)

    一.简叙 写工控上位机的搬砖人,难免会遇到USB通讯,在一个项目中,我写的上位机使用USB HID协议和STM32通讯传输数据,从零大概花了几天找例程,找资料,最后是各种搬砖修补,终于出来了一个出版D ...

  4. USB HID介绍【转】

    本文转载自:http://blog.csdn.net/leo_wonty/article/details/6721214 HID是一种USB通信协议,无需安装驱动就能进行交互,在学习HID之前,先来复 ...

  5. USB HID描述符【转】

    本文转载自: USB是个通用的总线,端口都是统一的.但是USB设备却各种各样,例如USB鼠标,USB键盘,U盘等等,那么USB主机是如何识别出不同的设备的呢?这就要依赖于描述符了.USB的描述符主要有 ...

  6. C# 访问USB(HID)设备

    原文:C# 访问USB(HID)设备 二话不说,直接给代码,如果您真想做这方面的东西,还是稍微研究下,没有现成的好类用,就需要自己了解其原理 //引用空间 using System; using Sy ...

  7. USB HID报告及报告描述符简介

    在USB中,USB HOST是通过各种描述符来识别设备的,有设备描述符,配置描述符,接口描述符,端点描述符,字符串描述符,报告描述符等等.USB报告描述符(Report Descriptor)是HID ...

  8. Windows与自定义USB HID设备通信说明.

    1 .   所使用的典型 Windows API CreateFile ReadFile WriteFile 以下函数是 DDK 的内容: HidD_SetFeature HidD_GetFeatur ...

  9. USB HID Report Descriptor 报告描述符详解

    Report descriptors are composed of pieces of information. Each piece of information is called an Ite ...

随机推荐

  1. linux 添加自定义环境变量

    1. vi /etc/profile ,在文件末尾加上要定义的环境变量,语法如下: export 变量名=变量值

  2. Keil C51汉字显示的bug问题(0xFD问题)

    一.缘起 这两天改进MCU的液晶显示方法,采用“即编即显”的思路,编写了一个可以直接显示字符串的程序.如程序调用disstr("我是你老爸");液晶屏上就会显示“我是你老爸”. 二 ...

  3. Silverlight 结合ArcGis 在地图画点

    原文 http://www.dotblogs.com.tw/justforgood/archive/2012/05/10/72076.aspx 我们直接来看完成后的画面读者会比较有兴趣 如上,小马在地 ...

  4. case then 的用法 貌似case then不支持别名

    set ANSI_NULLS ONset QUOTED_IDENTIFIER ONgo ALTER PROC [dbo].[usp_SRV_GetALLRelativeProject]@Service ...

  5. 程序猿必备的10款web前端开发插件一

    1.CSS3实现的火柴燃烧Loading加载动画 这次我们要给大家分享一款非常特别的CSS3 Loading加载动画,整个Loading加载动画就好像是火柴在燃烧一样,不足的是火苗并没有那么真实,比较 ...

  6. js跳转页面代码用法

    一:window.location.href='https://www.baidu.com';  需要加上http或者https,否则会查找项目内htm打开. 二:window.history.bac ...

  7. IOS ViewController 生命周期

    加载过程 第一步 -(id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil 第二步 -(void) ...

  8. wpf msdn在线地址http://msdn.microsoft.com/zh-cn/library/ms752324(v=vs.110).aspx

    http://msdn.microsoft.com/zh-cn/library/ms752324(v=vs.110).aspx

  9. Android应用程序组件Content Provider应用实例

    文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/6950440 文简要介绍了Android应用程序 ...

  10. 【剑指offer】Q40:数组中出现一次的数

    书里面关于分类的推断有些麻烦,通过某一位为0为1来对数组元素进行分类.假如第3位为1.那么也就是元素x & 8 等于或不等于0,所以不是必需非的用第几位去推断. def once(array) ...