关于 KEIL提供的JoystickMouse例程 转化为 USB虚拟键盘

2019-07-21 07:18发布

我只是USB新手,我只是想叙述一下我如何 将JoystickMouse例程 改为 USB虚拟键盘的过程,以及一些仍未解决的疑惑。
我昨天补了1天的USB知识,对USB的枚举,描述符间的关系,控制传输有个大致清晰了解,但是并不深刻。(为此,我决定随后开始从PDIUSB12芯片开始学习起走)

首先,我们先打开JoystickMouse的例程,先下载进单片机看看,以确保单片机和USB之间的通信是正常(即硬件无问题),如果是正常的,驱动程序安装后,鼠标会跑
先看主函数.
int main(void)
{ #ifdef DEBUG
  debug();
#endif   Set_System();   USB_Interrupts_Config();   Set_USBClock();   USB_Init();   while (1)
  {
    Delay(10000);
    if ((JoyState() != 0) & (bDeviceState == CONFIGURED))
    {
      Joystick_Send(JoyState());         //这里便是发送信息至PC机的函数,这个信息可以代表很多设备,主要是看描述符的内容。  
    }
  }
}
要想让Joystick_Send发送控制键盘的信息,我们需要更改描述符.
打开usb_desc.c  usb_desc.h此处是用于编写USB描述符的.c 以及 .h文件
Joystick_DeviceDescriptor 是设备描述符,需要修改一下VID(厂商ID) PID(产品ID)
即更改
0x83,  0x04,  /*idVendor (0x0483)*/
0x10,  0x57,  /*idProduct = 0x5710*/
随意更改下数字即可,我是更改为
0x34, 0x12,/*idVendor (0x1234)*/
0x21, 0x43,/*idProduct = 0x4321*/
其实该处影响并不大,如果ID号不变的话,就会加载之前JoystickMouse的驱动程序(如果成功运行JoystickMouse例程),但是该驱动程序并不满足我们的需求,也就是说和虚拟键盘的描述符之间存在矛盾 .(加载的驱动程序不是我们想要的嘛,肯定就会有问题,换一个ID的话,会重新检测并加载新的驱动程序)


接着改Joystick_ConfigDescriptor,这是配置描述符(注意---配置描述符,接口描述符,HID描述符,端点描述符均包含在其中)
该处参考电脑圈圈的程序,更改为
const u8 Joystick_ConfigDescriptor[JOYSTICK_SIZ_CONFIG_DESC] =
  {
{
 //以下为配置描述符
 0x09, /* bLength: Configuation Descriptor size */
 USB_CONFIGURATION_DESCRIPTOR_TYPE, /* bDescriptorType: Configuration */
 JOYSTICK_SIZ_CONFIG_DESC,
 /* wTotalLength: Bytes returned */
 0x00,
 0x01,         /*bNumInterfaces: 1 interface*/
 0x01,         /*bConfigurationValue: Configuration value*/
 0x00,         /*iConfiguration: Index of string descriptor describing
               the configuration*/
 0xC0,         /*bmAttributes: self powered */
 0x32,         /*MaxPower 100 mA: this current is used for detecting Vbus*/  //以下为接口描述符
 /************** Descriptor of Joystick Mouse interface ****************/
 /* 09 */
 0x09,         /*bLength: Interface Descriptor size*/
 USB_INTERFACE_DESCRIPTOR_TYPE,/*bDescriptorType: Interface descriptor type*/
 0x00,         /*bInterfaceNumber: Number of Interface*/
 0x00,         /*bAlternateSetting: Alternate setting*/
 0x02,         /*bNumEndpoints*/
 0x03,         /*bInterfaceClass: HID*/
 0x01,         /*bInterfaceSubClass : 1=BOOT, 0=no boot*/
 0x01,         /*bInterfaceProtocol : 0=none, 1=keyboard, 2=mouse*/
 0,            /*iInterface: Index of string descriptor*/  //以下为HID描述符
 /******************** Descriptor of Joystick Mouse HID ********************/
 /* 18 */
 0x09,         /*bLength: HID Descriptor size*/
 HID_DESCRIPTOR_TYPE, /*bDescriptorType: HID*/
 0x00,         /*bcdHID: HID Class Spec release number*/
 0x01,
 0x00,         /*bCountryCode: Hardware target country*/
 0x01,         /*bNumDescriptors: Number of HID class descriptors to follow*/
 0x22,         /*bDescriptorType*/
 JOYSTICK_SIZ_REPORT_DESC,/*wItemLength: Total length of Report descriptor*/
 0x00,
 
 //以下为输入端点1描述符
 /******************** Descriptor of Joystick Mouse endpoint ********************/
 /* 27 */
 0x07,          /*bLength: Endpoint Descriptor size*/
 USB_ENDPOINT_DESCRIPTOR_TYPE, /*bDescriptorType:*/
 0x81,          /*bEndpointAddress: Endpoint Address (IN)*/
 0x03,          /*bmAttributes: Interrupt endpoint*/
 0x08,          /*wMaxPacketSize: 8 Byte max */
 0x00,
 0x20,          /*bInterval: Polling Interval (32 ms)*/  //以下为输出端但1描述符
 /* 34 */
 0x07,          /*bLength: Endpoint Descriptor size*/
 USB_ENDPOINT_DESCRIPTOR_TYPE, /*bDescriptorType:*/
 0x01,          /*bEndpointAddress: Endpoint Address (OUT)*/
 0x03,          /*bmAttributes: Interrupt endpoint*/
 0x08,          /*wMaxPacketSize: 8 Byte max */
 0x00,
 0x20,          /*bInterval: Polling Interval (32 ms)*/
/* 41 */
};
这时候编译试试,会发现
error:   #146too many initializer values的问题,很显然给配置描述符数组设定的大小太小了
打开usb_desc.h
将数组大小改为
#define JOYSTICK_SIZ_CONFIG_DESC                41   //是41个字节嘛~


然后改usb_desc.c里面的Joystick_ReportDescriptor(报告描述符)
我们仍然参考电脑圈圈的程序
const u8 Joystick_ReportDescriptor[JOYSTICK_SIZ_REPORT_DESC] =
{
 0x05, 0x01, // USAGE_PAGE (Generic Desktop)
 0x09, 0x06, // USAGE (Keyboard)
 0xa1, 0x01, // COLLECTION (Application)
 0x05, 0x07, //     USAGE_PAGE (Keyboard/Keypad)
 0x19, 0xe0, //     USAGE_MINIMUM (Keyboard LeftControl)
 0x29, 0xe7, //     USAGE_MAXIMUM (Keyboard Right GUI)
 0x15, 0x00, //     LOGICAL_MINIMUM (0)
 0x25, 0x01, //     LOGICAL_MAXIMUM (1)
 0x95, 0x08, //     REPORT_COUNT (8)
 0x75, 0x01, //     REPORT_SIZE (1)
 0x81, 0x02, //     INPUT (Data,Var,Abs)
 0x95, 0x01, //     REPORT_COUNT (1)
 0x75, 0x08, //     REPORT_SIZE (8)
 0x81, 0x03, //     INPUT (Cnst,Var,Abs)
 0x95, 0x06, //   REPORT_COUNT (6)
 0x75, 0x08, //   REPORT_SIZE (8)
 0x25, 0xFF, //   LOGICAL_MAXIMUM (255)
 0x19, 0x00, //   USAGE_MINIMUM (Reserved (no event indicated))
 0x29, 0x65, //   USAGE_MAXIMUM (Keyboard Application)
 0x81, 0x00, //     INPUT (Data,Ary,Abs)
 0x25, 0x01, //     LOGICAL_MAXIMUM (1)
 0x95, 0x02, //   REPORT_COUNT (2)
 0x75, 0x01, //   REPORT_SIZE (1)
 0x05, 0x08, //   USAGE_PAGE (LEDs)
 0x19, 0x01, //   USAGE_MINIMUM (Num Lock)
 0x29, 0x02, //   USAGE_MAXIMUM (Caps Lock)
 0x91, 0x02, //   OUTPUT (Data,Var,Abs)
 0x95, 0x01, //   REPORT_COUNT (1)
 0x75, 0x06, //   REPORT_SIZE (6)
 0x91, 0x03, //   OUTPUT (Cnst,Var,Abs)
 0xc0        // END_COLLECTION
}; /* Joystick_ReportDescriptor */

好,尝试编译编译。
OK,编译成功,
但是千万不要认为就这样结束了!!!!!!!!!!
Joystick_ReportDescriptor[JOYSTICK_SIZ_REPORT_DESC]的数组大小还没改呢!
这个必须得改,不改的话,PC机识别不了人体输入设备。
我曾在这里花了很久的时间。。。。。。。这个大小必须改为实际大小
数数行数 1行2个,最后1行1个。
1共61个字节。
返回usb_desc.h中,
#define JOYSTICK_SIZ_REPORT_DESC                61
OK。

描述符就改完了。
现在描述符是用于处理键盘的了。

我们试试编译下载程序,打开设备管理器看看,发现是这样的

 

不用着急,还有些东西也需要改。
友情提示: 此问题已得到解决,问题已经关闭,关闭后问题禁止继续编辑,回答。
该问题目前已经被作者或者管理员关闭, 无法添加新回复
42条回答
lsj9383
1楼-- · 2019-07-21 10:11

我们先打开usb_conf.h

由于我们程序里面会用到EndPoint1的收和发,所以我们需要定义一下EP1的tx buffer地址 和 rx_buffer地址
/* EP0  */
/* rx/tx buffer base address */
#define ENDP0_RXADDR        (0x18)
#define ENDP0_TXADDR        (0x58)

/* EP1  */
/* tx buffer base address */
#define ENDP1_TXADDR        (0x100)
#define ENDP1_RXADDR  (0xD8)

该处的地址估计可以为任意值,但是不能重复,而且需要一定间隔,具体间隔多少我也不是很清楚,希望有大侠给我说下间隔如何确定(不知道是不是Joystick_Reset.c中的SetEPTxCount(ENDP1, 8);  设置端点长度来确定)

就先不管这个吧~  反正我们把地址给设置好了~

编译~ 没有问题~~


好,我们打开usb_prop.c
该文件适用于Joystick的初始化和复位等。
既然是初始化和复位 Init和Reset肯定比较重要~
首先我们找到Joystick_init

我们注释掉//  Get_SerialNum();

void Joystick_init(void)
{

  /* Update the serial number string descriptor with the data from the unique
  ID*/
//  Get_SerialNum();

  pInformation->Current_Configuration = 0;
  /* Connect the device */
  PowerOn();
  /* USB interrupts initialization */
  _SetISTR(0);               /* clear pending interrupts */
  wInterrupt_Mask = IMR_MSK;
  _SetCNTR(wInterrupt_Mask); /* set interrupts mask */

  bDeviceState = UNCONNECTED;
}
编译下载看看,下载成功后,关机再打开,我们会发现设备管理器里面


 

PC机可以将其视为键盘了。
说实话,我也不知道为什么,为啥把Get_SerialNum();注释掉,就可以成功找到键盘,取消注释的话,会发现仍然识别不了为键盘。知道的大侠能解释下吗?
lsj9383
2楼-- · 2019-07-21 13:48

就不管那个问题了,
我们接着改。
现在还没完了。
找到Joystick_Reset(void)
更改两个东西
SetEPTxCount(ENDP1, 8);    //该处将端点1的长度设为8  
// SetEPRxStatus(ENDP1, EP_RX_DIS);    此处注释掉  

做到这里,我们离成功已经很近了。

此处重新找到Joystick_Send, 该函数在hw_config.c中
最开始我们就知道,这个函数是用于发送数据给USB的。
现在该数据已是键盘数据了。

我们需要更改下Joystick_Send内部的一些结构。
由于我们报告描述符是移植的电脑圈圈的程序,所以我们需要根据报告描述符来编写Joystick_Send
我们知道端点1的长度为8字节,
所以我们一次给USB发送的字节可以达到8字节。
根据报告描述符中定义的,第一个字节8个位,分别代表功能键的点击情况(功能键即为ctrl alt)等,第二个字节不适用,保留
第3到第8个字节  每个字节代表一个按键。

于是我们知道发送一次数据,最多可以按键6个(5个普通键,1个功能键)

#define KEY_SEL    0x01
#define KEY_RIGHT  0x02
#define KEY_LEFT   0x04
#define KEY_DOWN   0x10
#define KEY_UP     0x08
#define KEY_2      0x20
#define KEY_3      0x40


/*******************************************************************************
* Function Name : Joystick_Send.
* Description   : prepares buffer to be sent containing Joystick event infos.
* Input         : Keys: keys received from terminal.
* Output        : None.
* Return value  : None.
*******************************************************************************/
void Joystick_Send(u8 Keys)
{
  u8 Buffer[8] = {0, 0, 0, 0, 0, 0, 0, 0};
  u8 i;
  i=2;
 
  //对各个按键进行处理。注意,由于这里的摇杆5个按键
  //不可能同时按下,所以返回的普通键数量不会超过6个。
  //如果你的键盘同时按下的普通键能够超过6个的话,就需要做
  //点特殊处理了,将后面6字节全部设置为0xFF,表示按键无法识别。
  if(Keys&KEY_UP)
  {
   Buffer=0x52; //Keyboard UpArrow
   i++;
  }
  if(Keys&KEY_DOWN)
  {
   Buffer=0x51; //Keyboard DownArrow
   i++;
  }
  if(Keys&KEY_LEFT)
  {
   Buffer=0x50; //Keyboard LeftArrow
   i++;
  }
  if(Keys&KEY_RIGHT)
  {
   Buffer=0x4F; //Keyboard RightArrow
   i++;
  }
  if(Keys&KEY_2)
  {
   Buffer=0x39; //Keyboard Caps Lock
   i++;
  }
  if(Keys&KEY_3)
  {
   Buffer=0x53; //Keypad Num Lock and Clear
   i++;
  }
  if(Keys&KEY_SEL)
  {
   Buffer=0x28; //Keyboard Return (ENTER)
  }
  /*copy mouse position info in ENDP1 Tx Packet Memory Area*/
  UserToPMABufferCopy(Buffer, GetEPTxAddr(ENDP1), 8);   //发8个字节
  /* enable endpoint for transmission */
  SetEPTxValid(ENDP1);
}

将该内容整体替代Joystick_Send函数

里面有些#define定义,记得将这些定义也搬到main文件里面,这样可读性更强。

好了,到这里,我们的程序也就差不多了,
回到主函数
我们将while里面的东西全去掉
int main(void)
{

#ifdef DEBUG
  debug();
#endif

  Set_System();

  USB_Interrupts_Config();

  Set_USBClock();

  USB_Init();

  while (1)
  {
       ;
  }
}

我们测试下按键,
把while里面的写成
while(1)
{
    Delay(10000);
  Joystick_Send(KEY_RIGHT);
}
会发现光标一直忘右跑。

如果有这一现象,就代表之前都做对了。

也就实现了虚拟按键

值得注意的是,

Joystick_Send(KEY_RIGHT);
仅代表按键按下,并不代表松开
Joystick_Send(0)才代表松开,
Joystick_Send(0)发送出去为8个字节均是0,这样按键才松开。



大家或许发现我没有写接收端点,恩啊,我只需要发送功能,接收功能就没有写了~  O(∩_∩)O~~

大家可以根据电脑圈圈的来修改。

我来发一个我更改的例子吧~

以及电脑圈圈的例子,我也是仿写的

lsj9383
3楼-- · 2019-07-21 19:36
希望高人帮忙看看我对VID ID需要修改的原因的理解是否正确

我也不知道我有没有理解正确~

另外还有为啥注释掉Get_SerialNum();才能成功枚举到主机,也就是上述红 {MOD}的字段。我是百思不得其解。

最后还有个问题
USB初始化成功后,我如此写

Delay(10000);
Joystick_Send(KEY_RIGHT);
while(1)

光标并没有任何动作,这是为什么呀?
只有改为在循环内部,才会有作用。。。

我最开始怀疑是延时不够,但是我设了很大的延时,仍然没能起作用。(一定运行到了Joystick_Send了的)。
lsj9383
4楼-- · 2019-07-21 23:57
 精彩回答 2  元偷偷看……
正点原子
5楼-- · 2019-07-22 04:13
帮顶。
皮皮鲁
6楼-- · 2019-07-22 08:31
顶。
还没到看懂这些的地步。