基于STM32的USB键盘制作(保姆级)(二)

 

系列文章目录
第一节  USB协议及建立USB-HID工程

第二节  配置描述符及HID报文格式

第三节  PCB按键映射(基于稚晖君开源)


 

 


前言

本文主要在上一节的基础上,对相关的设备以及报文描述符配置,实现USB键盘。


 

一、配置函数的定位汇总

这里只是汇总各个配置在哪个文件中,方便笔者后续复习时,方便查找。可先看下一部分,再回来看这。

生成后的工程文件夹如下图:(划斜线的为笔者自己创建的)

fc1a731d65e4421fa2c05e25b198eb34.png

在USB_DEVICE/App路径下的usbd_desc.c及其头文件包含了设备描述符。

即配置VID信息,USB版本以及速度等信息,保持默认即可(如下图),还有一些其他描述符,具体有什么用,需了解USB协议发送的具体过程。这里笔者没深究。

 0c4119dc540149fa99d56e74339c5595.png

usb_device.c主要写的USB初始化的相关操作,其中定义了USB的句柄,后续在main函数以及其他函数要用时需要声明(extern USBD_HandleTypeDef hUsbDeviceFS;)

usbd_custom.hid_if.c文件则是配置后续要发送报文的格式(即要送的报文代表什么含义,要发送多少个字节)。(需要修改的文件)。

在Middlewares文件夹下的usbd_customhid.c文件则包含了配置描述符(PC要对该USB口配置成什么类型,输出电流大小等信息,是否支持3.0,配置为HS、FS、或其他速度)。其头文件还定义各种报文以及缓冲区的大小。(需要修改的文件)。

72b95ff84df34f7983ea6f45d3bd9118.png

a46bf0a7de8c4d548cb76fc8039dd913.png

二、具体配置

1.设备配置报文的修改

打开usbd_customhid.c文件,找到如下代码,每一行配置的什么,后面都有注释详解的。

因为笔者这款芯片只支持FS模式,且在cubemx中也选择的FS模式,故修改FS这个数组,若为HS或其他速度模式,则在C文件下翻,找到对应的修改即可。

__ALIGN_BEGIN static uint8_t USBD_CUSTOM_HID_CfgFSDesc[USB_CUSTOM_HID_CONFIG_DESC_SIZ] __ALIGN_END =
{
  0x09, /* bLength: Configuration Descriptor size */
  USB_DESC_TYPE_CONFIGURATION, /* bDescriptorType: Configuration */
  USB_CUSTOM_HID_CONFIG_DESC_SIZ,
  /* wTotalLength: Bytes returned */
  0x00,
  0x01,         /*bNumInterfaces: 1 interface*/
  0x01,         /*bConfigurationValue: Configuration value*/
  0x00,         /*iConfiguration: Index of string descriptor describing
  the configuration*/
  0xC0,         /*bmAttributes: bus powered */
  0x32,         /*MaxPower 100 mA: this current is used for detecting Vbus*/

  /************** Descriptor of CUSTOM HID interface ****************/
  /* 09 */
  0x09,         /*bLength: Interface Descriptor size*/
  USB_DESC_TYPE_INTERFACE,/*bDescriptorType: Interface descriptor type*/
  0x00,         /*bInterfaceNumber: Number of Interface*/
  0x00,         /*bAlternateSetting: Alternate setting*/
  0x02,         /*bNumEndpoints*/
  0x03,         /*bInterfaceClass: CUSTOM_HID*/
  0x00,         /*bInterfaceSubClass : 1=BOOT, 0=no boot*/
  0x00,         /*nInterfaceProtocol : 0=none, 1=keyboard, 2=mouse*/
  0,            /*iInterface: Index of string descriptor*/
  /******************** Descriptor of CUSTOM_HID *************************/
  /* 18 */
  0x09,         /*bLength: CUSTOM_HID Descriptor size*/
  CUSTOM_HID_DESCRIPTOR_TYPE, /*bDescriptorType: CUSTOM_HID*/
  0x11,         /*bCUSTOM_HIDUSTOM_HID: CUSTOM_HID Class Spec release number*/
  0x01,
  0x00,         /*bCountryCode: Hardware target country*/
  0x01,         /*bNumDescriptors: Number of CUSTOM_HID class descriptors to follow*/
  0x22,         /*bDescriptorType*/
  USBD_CUSTOM_HID_REPORT_DESC_SIZE,/*wItemLength: Total length of Report descriptor*/
  0x00,
  /******************** Descriptor of Custom HID endpoints ********************/
  /* 27 */
  0x07,          /*bLength: Endpoint Descriptor size*/
  USB_DESC_TYPE_ENDPOINT, /*bDescriptorType:*/

  CUSTOM_HID_EPIN_ADDR,     /*bEndpointAddress: Endpoint Address (IN)*/
  0x03,          /*bmAttributes: Interrupt endpoint*/
  CUSTOM_HID_EPIN_SIZE, /*wMaxPacketSize: 2 Byte max */
  0x00,
  CUSTOM_HID_FS_BINTERVAL,          /*bInterval: Polling Interval */
  /* 34 */

  0x07,          /* bLength: Endpoint Descriptor size */
  USB_DESC_TYPE_ENDPOINT, /* bDescriptorType: */
  CUSTOM_HID_EPOUT_ADDR,  /*bEndpointAddress: Endpoint Address (OUT)*/
  0x03, /* bmAttributes: Interrupt endpoint */
  CUSTOM_HID_EPOUT_SIZE,  /* wMaxPacketSize: 2 Bytes max  */
  0x00,
  CUSTOM_HID_FS_BINTERVAL,  /* bInterval: Polling Interval */
  /* 41 */
};

修改此三行

  0x32, /*MaxPower 100 mA: this current is used for detecting Vbus*/

  0x00,         /*bInterfaceSubClass : 1=BOOT, 0=no boot*/

  0x00,         /*nInterfaceProtocol : 0=none, 1=keyboard, 2=mouse*/

0x32表示为该口提供的最大输出电流。(0x32换算十进制即50,再×2 即100)即这里提供最大100ma输出电流。USB2.0最大提供输出500ma电流,即最大设置为0xFA;

这里使用的开发板且只有一个按键,0x32绰绰有余。若单独画PCB制作键盘,设置0xFA

0x00,         /*bInterfaceSubClass : 1=BOOT, 0=no boot*/这里设置有无BOOT

笔者还不太了解这块,故给的0x00

0x00,         /*nInterfaceProtocol : 0=none, 1=keyboard, 2=mouse*/这里设置0x01表示键盘。

其他不变

 

2.键盘报文描述符设置

在usbd_custom.hid_if.c这个文件中。

题外话:具体为什么这样配置,参考这篇文章:HID报文讲解_skdev的博客-CSDN博客

想做其他操作,可以配合手册和生成软件,实现其他HID类设备

7266561689d9467ba89011f874cc121d.png

具体配置:找到   __ALIGN_BEGIN static uint8_t CUSTOM_HID_ReportDesc_FS[USBD_CUSTOM_HID_REPORT_DESC_SIZE] __ALIGN_END =  这个数组

将其中内容修改为:

__ALIGN_BEGIN static uint8_t CUSTOM_HID_ReportDesc_FS[USBD_CUSTOM_HID_REPORT_DESC_SIZE] __ALIGN_END =
{
        0x05, 0x01,         //   Usage Page (Generic Desktop),
        0x09, 0x06,         //   Usage (Keyboard),
        0xA1, 0x01,         //   Collection (Application),
        0x85, 0x01,                    //     REPORT_ID (1)
        // bitmap of modifiers
        0x75, 0x01,         //   Report Size (1),
        0x95, 0x08,         //   Report Count (8),
        0x05, 0x07,       //   Usage Page (Key Codes),
        0x19, 0xE0,       //   Usage Minimum (224),
        0x29, 0xE7,       //   Usage Maximum (231),
        0x15, 0x00,       //   Logical Minimum (0),
        0x25, 0x01,       //   Logical Maximum (1),
        0x81, 0x02,       //   Input (Data, Variable, Absolute), ;Modifier byte
        // bitmap of keys
        0x95, 0x78,       //   Report Count (120),
        0x75, 0x01,       //   Report Size (1),
        0x15, 0x00,       //   Logical Minimum (0),
        0x25, 0x01,       //   Logical Maximum(1),
        0x05, 0x07,       //   Usage Page (Key Codes),
        0x19, 0x00,       //   Usage Minimum (0),
        0x29, 0x77,       //   Usage Maximum (),
        0x81, 0x02,       //   Input (Data, Variable, Absolute),
  0xC0    /*     END_COLLECTION	             */      
};

 细数一共有41个字节,故USBD_CUSTOM_HID_REPORT_DESC_SIZE这个宏定义要修改为41

即这个描述数组有多少个字节,USBD_CUSTOM_HID_REPORT_DESC_SIZE这个宏定义就必须是多少,有一点误差都会导致识别失败。

在usbd_conf.h这个头文件里面修改USBD_CUSTOM_HID_REPORT_DESC_SIZE的大小

c634af851b74454bab596518b892c6d0.png

修改为41,数字后面的U表示无符号数。

此时,我们保存编译下载到单片机。

b703fc29053e405d95e89e674991ea07.png

会发现,识别到键盘设备,到此,成功一大半了。接下来进行按键发送报文。

上述报文描述符格式对应下图这种表。(已打包到资源文件)

792890e94eea4fd7a6cfccce0cc5b426.png

3.按键发送报文数据

首先单独新建一个C文件和一个头文件,写报文发送函数。

新建一个枚举体,存放要放入的HID报文数据。

(枚举体小知识点:未给初始值时,默认值由0开始,之后自加1赋值

即:下列枚举体中的RESERVED=0,则ERROR_ROLL_OVER=1,POST_FAIL=2...以此类推。)

  typedef enum 
    {
        /*------------------------- HID report data -------------------------*/
        LEFT_CTRL = -8,LEFT_SHIFT = -7,LEFT_ALT = -6,LEFT_GUI = -5,
        RIGHT_CTRL = -4,RIGHT_SHIFT = -3,RIGHT_ALT = -2,RIGHT_GUI = -1,

        RESERVED = 0,ERROR_ROLL_OVER,POST_FAIL,ERROR_UNDEFINED,
        A,B,C,D,E,F,G,H,I,J,K,L,M,
        N,O,P,Q,R,S,T,U,V,W,X,Y,Z,
        NUM_1/*1!*/,NUM_2/*2@*/,NUM_3/*3#*/,NUM_4/*4$*/,NUM_5/*5%*/,
        NUM_6/*6^*/,NUM_7/*7&*/,NUM_8/*8**/,NUM_9/*9(*/,NUM_0/*0)*/,
        ENTER,ESC,BACKSPACE,TAB,SPACE,
        MINUS/*-_*/,EQUAL/*=+*/,LEFT_U_BRACE/*[{*/,RIGHT_U_BRACE/*]}*/,
        BACKSLASH/*\|*/,NONE_US/**/,SEMI_COLON/*;:*/,QUOTE/*'"*/,
        GRAVE_ACCENT/*`~*/,COMMA/*,<*/,PERIOD/*.>*/,SLASH/*/?*/,
        CAP_LOCK,F1,F2,F3,F4,F5,F6,F7,F8,F9,F10,F11,F12,
        PRINT,SCROLL_LOCK,PAUSE,INSERT,HOME,PAGE_UP,DELETE,END,PAGE_DOWN,
        RIGHT_ARROW,LEFT_ARROW,DOWN_ARROW,UP_ARROW,PAD_NUM_LOCK,
        PAD_SLASH,PAD_ASTERISK,PAD_MINUS,PAD_PLUS,PAD_ENTER,
        PAD_NUM_1,PAD_NUM_2,PAD_NUM_3,PAD_NUM_4,PAD_NUM_5,
        PAD_NUM_6,PAD_NUM_7,PAD_NUM_8,PAD_NUM_9,PAD_NUM_0,
        PAD_PERIOD , NONUS_BACKSLASH,APPLICATION,POWER,PAD_EQUAL,
        F13,F14,F15,F16,F17,F18,F19,F20,F21,F22,F23,F24, EXECUTE,
        HELP,MENU,SELECT,STOP,AGAIN,UNDO,CUT,COPY,PASTE,FIND,MUTE,VOLUME_UP,VOLUME_DOWN,
        FN = 1000
        /*------------------------- HID report data -------------------------*/
    }KeyCode_t;

一共128个位,即16个字节(对应上文的报文描述数组)。一个位表示一个按键。接下来定义uint8_t hidbuffer[17];一个17位的数组。有17位的原因:第一个字节表示是ID号,后16个字节表示按键的状态,即128位,一位对应一个按键。(比如hidbuffer[1]=0x01,其他均为0,就表示LEFT_RIGHT这个按键被按下了。)(发送的报文: 0表示没按下,1表示没按下)

现在就可以写按键检测并发送函数了:

uint8_t hidbuffer[17];
extern USBD_HandleTypeDef hUsbDeviceFS;

void keyboard()
{
    memset(hidbuffer,0,17);
    hidbuffer[0] = 1;
    if((HAL_GPIO_ReadPin(GPIOC,GPIO_PIN_5))==0)
    {
        DelayUs(100);
       if((HAL_GPIO_ReadPin(GPIOC,GPIO_PIN_5))==0)
       {
         hidbuffer[3] = 0x01;
       }
    }
    USBD_CUSTOM_HID_SendReport(&hUsbDeviceFS,hidbuffer,17);
}

memset函数在"string.h"这个头文件中,这个函数的作用是将hidbuffer这个数组全部请0。

hidbuffer【0】=1;即ID号等于1,即是键盘的ID号(在上面报文描述数组中0x85,0x01这一句设置的)。这里因为我用的开发板只用了一个按键,故我把这个按键表示为E,"E"键在枚举体中是第17个,即在hidbuffer数组中就是hidbuffer【3】的第0位。

DelayUs(100);是为了按键消抖,让hidbuffer【3】=0x01,即表示按键按下时,让对应"E"这位等于1.然后发送报文即可(利USBD_CUSTOM_HID_SendReport(&hUsbDeviceFS,hidbuffer,17);这个函数,该函数在 "usbd_customhid.h"中)

注:需要hUsbDeviceFS这个结构体不是在main中定义的,而是在USB_DEVICE.H中,故需要写 extern USBD_HandleTypeDef hUsbDeviceFS;

main函数调用即可,延时1ms表示1ms扫描一次按键。

5ce58c8a29a043ccabdf5ed073b39e18.png

 然后编译烧录进单片机,开始测试

4d1320ae7214498b97122825ce42f8f9.png

测试正常。至此实现完成。

4.值得注意的几个点

发送报文数据需要及时发送。否则PC机一直读的上一次报文,导致按键一直在被打印。

将HAL_DELAY(1)改为1000,相当于1秒扫描一次,并发送报文。此时烧录进单片机,按下按键一次会发现"e"键在一直被打印,因为没有即使更新报文(即以及松开了,没发送过去)。

消抖若过久,会导致有时候按下,并没有反应。因为有可能按下这个时间,程序还在上一次消抖那里延时。

笔者曾想,按下一个按键实现给主机保送多个不同的数据,比如按下一个按键就输入了密码这种操作,但在多次发送过程中,要么乱序,要么一直发送,目前未解决。(想了下,如果一个按键就输入了全部密码,那还要密码干嘛,就没继续研究了)

附录:工程代码下载链接:https://download.csdn.net/download/qq_21566881/87078754

HID手册及配置工具下载链接:https://download.csdn.net/download/qq_21566881/87073789?spm=1001.2014.3001.5503

(审核完成后上传)

 

2022.11.21补充:

0ec299d7cfbd42d7960cd3317d38fbd2.png

推荐下载这个软件 查看电脑接受到的报文数据,根据接受的数据来调试

软件叫BUS HOUND 几MB大小,上手很快 

 

 


总结

本文实现了STM32的USB-HID键盘的应用,下一篇文章将介绍基于稚晖君开源DIY的瀚文键盘的按键映射源码分析,看完下一篇后,便可动手画PCB板,DIY自己的一个USB键盘。

 

 

 

评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大颜u

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值