查看: 2056|回复: 10

USB 转蓝牙键盘的装置

[复制链接]

该用户从未签到

发表于 2022-7-20 20:09 | 显示全部楼层 |阅读模式
LABZ作品0716-封面 (1).jpg
无线能够给用户带来极大的便利,对于我这样工作台很乱的人来说,无线大大降低了线路绊倒水杯之类物品的可能行。现在的计算机特别是笔记本电脑都随着 WIFI 模块自带了蓝牙功能,这次介绍的作品就是使用ESP32S3 将有线的 USB 键盘转化为一个蓝牙键盘,这样就可以直接连接到工作的计算机上。
具体的使用方法可以通过下面的视频了解:
接下来就开始介绍如何实现。这次设计的核心是ESP32-S3,它是一款2020年底推出的,集成2.4 GHz Wi-Fi Bluetooth 5 (LE) MCU 芯片。这款搭载Xtensa® 32 LX7 双核处理器,主频高达240 MHz,内置 512KB SRAM,具有 45 个可编程GPIO 管脚和丰富的通信接口。从名称上也可以看出,它是ESP32-S2的继承者,相比 S2 芯片,S3 仍然有 USB OTG 功能,同时增加了对于蓝牙的支持。这次的作品就是基于这两个功能实现的:OTG将自身设定为 USB Host,负责解析键盘数据;蓝牙负责和主机进行通讯。这样USB键盘的操作就会以蓝牙键盘操作的形式出现在主机上。
和之前的设计一样,首先介绍硬件。整体有7部分:
ub1.png
左上角是5V3.3V的电路,核心是TI生产的SOT-223装的TLV1117LV线性稳压器,这款器件功耗极低,与传统1117 稳压器相比低 500倍,适用于需要超低静态电流的应用。TLV1117LV系列 LDO还可在 0mA负载电流下保持稳定,不存在最低负载要求,因此适用于必须在待机模式为超小型负载供电的稳压器,在正常运行状态下需要 1A 大电流时同样如此。TLV1117LV可提供出色的线路与负载瞬态性能,从而可在负载电流要求由不足 1mA变为超过500mA 时产生幅值极低的下冲与过冲输出电压。特别的,和之前AMS1117 相比外部只需要2个1uf的电容即可,无需钽电容。

ub2.png
接下来是用于从外部取电的USB公头,用于给整体提供电力:
ub3.png
接下来是一个负载消耗设计,前面提到我们会用USB公头从外部取电,供电的可能是充电宝之类,这种移动电源为了节省电力当外部负载小于100ma时,会切断电源。为了避免这种情况,这里预留负载消耗的电路设计。如果有需要,可以间隔一定时间拉出一个负载避免电源自动关闭(实际上根据经验大部分USB键盘功耗就会超过100ma,只是预留不上件也没有关系)。
ub4.png
接下来是USB母头,用于连接USB键盘
ub5.png
板子上还有预留的烧写接口,这个接口是自定义的,可以看到是串口通讯,此外还有 IO0EN_AUTO配合DTR/RTS能够实现自动烧写,无需手工按键。响应的,我还设计了一个基于CH343P芯片的的串口小板,在【参考1 可以看到。
ub6.png
串口测试小卡上自带了5V3.3V供电,在调试时,焊接完成 ESP32 S3最小系统后,即可通过上面串口和3.3V供电进行调试,这样方便调试可以即使发现问题。
ub7.png
最后,就是整个设计的核心 ESP32-S3 ,下图也是这个 MCU 的最小系统,外部通过有一个 0.1uf ,一个22uf电容,以及一个10K电阻即可让 ESP32-S3 工作起来:
ub8.png
PCB 设计如下,因为元件不多,设计并不复杂:
ub9.png
3D预览如下:
uba.png
焊接后的照片:
ubb.jpg
硬件完成后即可着手软件设计。
首先,需要完成的是USBHost 部分。使用 esp32-usb-host-demos-main 库【参考2】,这个库能够在 ESP32 S2 S3 上实现 USB Host ,经过我的测试功能正常。其中的usbhhidboot是解析支持 BOOT_PROTCOL 键盘的例子。所谓 BOOT_PROTCOL是一种USB键盘的协议,主机通知USB键盘使用这个协议之后,键盘就会按照固定的格式发送数据,BOOT_PROTCOL最初的设计是为了处理计算机进入 BIOS Setup后的按键。因为键盘发送固定格式的数据,所以主机无需再根据描述符拆解数据,因此可以大大降低固件设计的复杂度。
在下面的函数中完成按键信息的解析:
  1. void keyboard_transfer_cb(usb_transfer_t *transfer)

  2. {

  3.   if (Device_Handle == transfer->device_handle) {

  4.     isKeyboardPolling = false;

  5.     if (transfer->status == 0) {

  6.       if (transfer->actual_num_bytes == 8) {

  7.         uint8_t *const p = transfer->data_buffer;

  8.         ESP_LOGI("", "HID report: %02x %02x %02x %02x %02x %02x %02x %02x",

  9.             p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7]);

  10.       }

  11.       else {

  12.         ESP_LOGI("", "Keyboard boot hid transfer too short or long");

  13.       }

  14.     }

  15.     else {

  16.       ESP_LOGI("", "transfer->status %d", transfer->status);

  17.     }

  18.   }

  19. }
复制代码

为了实现 ESP32-S3 BLE键盘,我们使用 ESP32-BLE-Keyboard库【参考2】,同样的这个库提供了按键的示例。对于我们来说只需要用下面的方法声明一个 BLE 键盘:
BleKeyboard bleKeyboard;
接下来判断如果当前蓝牙已经连接,即可将敲击的键盘数据发送出去
  1.         if(bleKeyboard.isConnected()) {

  2.             bleKeyboard.sendReport((KeyReport*) p);

  3.           }
复制代码

从上面可以看到 Arduino 设计的魅力就在于无需深入了解原理,简单拼接即可实现功能。因为对于大多数人来说,功能是他们更关心的而不是如何实现 ,有兴趣的朋友可以在 Arduino 中文社区上看到完整的代码。
参考:
1.     https://www.lab-z.com/esp32testbd/ 做一个 ESP32 S2 模块测试板子

该用户从未签到

 楼主| 发表于 2022-7-20 20:13 | 显示全部楼层
电路图和PCB    USBKB2BLEKB.zip (52.5 KB, 下载次数: 48)

源代码   USBKB2BTKB.zip (1.83 KB, 下载次数: 53)

该用户从未签到

发表于 2022-7-20 20:39 | 显示全部楼层
不错不错,赞一个

该用户从未签到

发表于 2022-8-16 22:02 来自手机 | 显示全部楼层
楼主我用esp32s2,试了好几个usb键盘想读出键值都没有成功

该用户从未签到

 楼主| 发表于 2022-8-17 08:35 | 显示全部楼层
hztwb 发表于 2022-8-16 22:02
楼主我用esp32s2,试了好几个usb键盘想读出键值都没有成功

你跑的什么代码?打开 Debug 了么

该用户从未签到

发表于 2022-8-23 07:16 来自手机 | 显示全部楼层
用的就是这个库的示例代码https://github.com/touchgadget/esp32-usb-host-demos,我没有用蓝牙,我就想当我按下键盘按键,然后在esp32s2的下载程序的串口中能输出键值信息,但是换了好几个键盘都没反应,拔下键盘的一瞬间串口能输出一条提示

该用户从未签到

发表于 2022-8-23 07:39 | 显示全部楼层
本帖最后由 hztwb 于 2022-8-23 07:53 编辑

ESP-ROM:esp32s2-rc4-20191025
Build:Oct 25 2019
rst:0x1 (POWERON),boot:0x8 (SPI_FAST_FLASH_BOOT)
SPIWP:0xee
mode: DIO, clock div:1
load:0x3ffe6100,len:0x524
load:0x4004c000,len:0xa50
load:0x40050000,len:0x28cc
entry 0x4004c18c


E (9899) USBH: Device 1 gone





键盘插着的时候,按任意一键都没反应,拨下键盘USB口的时候串口输出“E (9899) USBH: Device 1 gone”


我烧录的是下面这个示例代码:

/*
* MIT License
*
* Copyright (c) 2021 touchgadgetdev@gmail.com
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <elapsedMillis.h>
#include <usb/usb_host.h>
#include "show_desc.hpp"
#include "usbhhelp.hpp"

bool isKeyboard = false;
bool isKeyboardReady = false;
uint8_t KeyboardInterval;
bool isKeyboardPolling = false;
elapsedMillis KeyboardTimer;

const size_t KEYBOARD_IN_BUFFER_SIZE = 8;
usb_transfer_t *KeyboardIn = NULL;

void keyboard_transfer_cb(usb_transfer_t *transfer)
{
  if (Device_Handle == transfer->device_handle) {
    isKeyboardPolling = false;
    if (transfer->status == 0) {
      if (transfer->actual_num_bytes == 8) {
        uint8_t *const p = transfer->data_buffer;
        ESP_LOGI("", "HID report: %02x %02x %02x %02x %02x %02x %02x %02x",
            p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7]);
      }
      else {
        ESP_LOGI("", "Keyboard boot hid transfer too short or long");
      }
    }
    else {
      ESP_LOGI("", "transfer->status %d", transfer->status);
    }
  }
}

void check_interface_desc_boot_keyboard(const void *p)
{
  const usb_intf_desc_t *intf = (const usb_intf_desc_t *)p;

  if ((intf->bInterfaceClass == USB_CLASS_HID) &&
      (intf->bInterfaceSubClass == 1) &&
      (intf->bInterfaceProtocol == 1)) {
    isKeyboard = true;
    ESP_LOGI("", "Claiming a boot keyboard!");
    esp_err_t err = usb_host_interface_claim(Client_Handle, Device_Handle,
        intf->bInterfaceNumber, intf->bAlternateSetting);
    if (err != ESP_OK) ESP_LOGI("", "usb_host_interface_claim failed: %x", err);
  }
}

void prepare_endpoint(const void *p)
{
  const usb_ep_desc_t *endpoint = (const usb_ep_desc_t *)p;
  esp_err_t err;

  // must be interrupt for HID
  if ((endpoint->bmAttributes & USB_BM_ATTRIBUTES_XFERTYPE_MASK) != USB_BM_ATTRIBUTES_XFER_INT) {
    ESP_LOGI("", "Not interrupt endpoint: 0x%02x", endpoint->bmAttributes);
    return;
  }
  if (endpoint->bEndpointAddress & USB_B_ENDPOINT_ADDRESS_EP_DIR_MASK) {
    err = usb_host_transfer_alloc(KEYBOARD_IN_BUFFER_SIZE, 0, &KeyboardIn);
    if (err != ESP_OK) {
      KeyboardIn = NULL;
      ESP_LOGI("", "usb_host_transfer_alloc In fail: %x", err);
      return;
    }
    KeyboardIn->device_handle = Device_Handle;
    KeyboardIn->bEndpointAddress = endpoint->bEndpointAddress;
    KeyboardIn->callback = keyboard_transfer_cb;
    KeyboardIn->context = NULL;
    isKeyboardReady = true;
    KeyboardInterval = endpoint->bInterval;
    ESP_LOGI("", "USB boot keyboard ready");
  }
  else {
    ESP_LOGI("", "Ignoring interrupt Out endpoint");
  }
}

void show_config_desc_full(const usb_config_desc_t *config_desc)
{
  // Full decode of config desc.
  const uint8_t *p = &config_desc->val[0];
  static uint8_t USB_Class = 0;
  uint8_t bLength;
  for (int i = 0; i < config_desc->wTotalLength; i+=bLength, p+=bLength) {
    bLength = *p;
    if ((i + bLength) <= config_desc->wTotalLength) {
      const uint8_t bDescriptorType = *(p + 1);
      switch (bDescriptorType) {
        case USB_B_DESCRIPTOR_TYPE_DEVICE:
          ESP_LOGI("", "USB Device Descriptor should not appear in config");
          break;
        case USB_B_DESCRIPTOR_TYPE_CONFIGURATION:
          show_config_desc(p);
          break;
        case USB_B_DESCRIPTOR_TYPE_STRING:
          ESP_LOGI("", "USB string desc TBD");
          break;
        case USB_B_DESCRIPTOR_TYPE_INTERFACE:
          USB_Class = show_interface_desc(p);
          check_interface_desc_boot_keyboard(p);
          break;
        case USB_B_DESCRIPTOR_TYPE_ENDPOINT:
          show_endpoint_desc(p);
          if (isKeyboard && KeyboardIn == NULL) prepare_endpoint(p);
          break;
        case USB_B_DESCRIPTOR_TYPE_DEVICE_QUALIFIER:
          // Should not be config config?
          ESP_LOGI("", "USB device qual desc TBD");
          break;
        case USB_B_DESCRIPTOR_TYPE_OTHER_SPEED_CONFIGURATION:
          // Should not be config config?
          ESP_LOGI("", "USB Other Speed TBD");
          break;
        case USB_B_DESCRIPTOR_TYPE_INTERFACE_POWER:
          // Should not be config config?
          ESP_LOGI("", "USB Interface Power TBD");
          break;
        case 0x21:
          if (USB_Class == USB_CLASS_HID) {
            show_hid_desc(p);
          }
          break;
        default:
          ESP_LOGI("", "Unknown USB Descriptor Type: 0x%x", bDescriptorType);
          break;
      }
    }
    else {
      ESP_LOGI("", "USB Descriptor invalid");
      return;
    }
  }
}

void setup()
{
  usbh_setup(show_config_desc_full);
}

void loop()
{
  usbh_task();

  if (isKeyboardReady && !isKeyboardPolling && (KeyboardTimer > KeyboardInterval)) {
    KeyboardIn->num_bytes = 8;
    esp_err_t err = usb_host_transfer_submit(KeyboardIn);
    if (err != ESP_OK) {
      ESP_LOGI("", "usb_host_transfer_submit In fail: %x", err);
    }
    isKeyboardPolling = true;
    KeyboardTimer = 0;
  }
}



该用户从未签到

 楼主| 发表于 2022-8-23 08:56 | 显示全部楼层
hztwb 发表于 2022-8-23 07:39
ESP-ROM:esp32s2-rc4-20191025
Build:Oct 25 2019
rst:0x1 (POWERON),boot:0x8 (SPI_FAST_FLASH_BOOT)

打开调试输出了么?有完整的 Log 吗?

该用户从未签到

发表于 2022-8-23 17:10 | 显示全部楼层
本帖最后由 hztwb 于 2022-8-23 17:57 编辑
Zoologist 发表于 2022-8-23 08:56
打开调试输出了么?有完整的 Log 吗?

多谢楼主提醒,终于注意到库作者的这一句“To see the sketch output on the serial monitor set the Core Debug Level to Verbose.

打开后,就有键值输出了


[157518][I][usbhhidboot.ino:46] keyboard_transfer_cb(): [] HID report: 00 00 1f 00 00 00 00 00
[157662][I][usbhhidboot.ino:46] keyboard_transfer_cb(): [] HID report: 00 00 00 00 00 00 00 00
[157774][I][usbhhidboot.ino:46] keyboard_transfer_cb(): [] HID report: 00 00 20 00 00 00 00 00
[157950][I][usbhhidboot.ino:46] keyboard_transfer_cb(): [] HID report: 00 00 00 00 00 00 00 00
[158086][I][usbhhidboot.ino:46] keyboard_transfer_cb(): [] HID report: 00 00 21 00 00 00 00 00
[158245][I][usbhhidboot.ino:46] keyboard_transfer_cb(): [] HID report: 00 00 00 00 00 00 00 00
[158326][I][usbhhidboot.ino:46] keyboard_transfer_cb(): [] HID report: 00 00 22 00 00 00 00 00
[158486][I][usbhhidboot.ino:46] keyboard_transfer_cb(): [] HID report: 00 00 00 00 00 00 00 00
[158590][I][usbhhidboot.ino:46] keyboard_transfer_cb(): [] HID report: 00 00 23 00 00 00 00 00
[158758][I][usbhhidboot.ino:46] keyboard_transfer_cb(): [] HID report: 00 00 00 00 00 00 00 00
[158886][I][usbhhidboot.ino:46] keyboard_transfer_cb(): [] HID report: 00 00 24 25 00 00 00 00
[159014][I][usbhhidboot.ino:46] keyboard_transfer_cb(): [] HID report: 00 00 24 00 00 00 00 00
[159030][I][usbhhidboot.ino:46] keyboard_transfer_cb(): [] HID report: 00 00 00 00 00 00 00 00
[159214][I][usbhhidboot.ino:46] keyboard_transfer_cb(): [] HID report: 00 00 26 00 00 00 00 00
[159278][I][usbhhidboot.ino:46] keyboard_transfer_cb(): [] HID report: 00 00 26 27 00 00 00 00
[159326][I][usbhhidboot.ino:46] keyboard_transfer_cb(): [] HID report: 00 00 26 00 00 00 00 00
[159350][I][usbhhidboot.ino:46] keyboard_transfer_cb(): [] HID report: 00 00 00 00 00 00 00 00
[159566][I][usbhhidboot.ino:46] keyboard_transfer_cb(): [] HID report: 00 00 17 00 00 00 00 00
[159622][I][usbhhidboot.ino:46] keyboard_transfer_cb(): [] HID report: 00 00 17 24 18 00 00 00
[159694][I][usbhhidboot.ino:46] keyboard_transfer_cb(): [] HID report: 00 00 00 00 00 00 00 00
[159806][I][usbhhidboot.ino:46] keyboard_transfer_cb(): [] HID report: 00 00 17 24 00 00 00 00
[159902][I][usbhhidboot.ino:46] keyboard_transfer_cb(): [] HID report: 00 00 00 00 00 00 00 00

该用户从未签到

发表于 2022-8-28 14:58 | 显示全部楼层
能加一个SL2.1A弄出两个口供鼠标键盘一起用呢?
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

热门推荐

原价299元【语音开发板套件】限时免费领!
原价299元【语音开发板套
教你让OLED动起来!多重字符串版!
教你让OLED动起来!多重字
大家都知道:arduino单片机是单线程的 而上次教程中的多段字符串的运行速度必须一致
TTGO T8 1.7.1使用TFT_eSPI库驱动2.4寸ILI9341屏幕显示板载SD内图片
TTGO T8 1.7.1使用TFT_eSP
TTGO T8 1.7.1采用Espressif官方ESP32-WROVER模块制作,4MB闪存和8MB PSRAM,支持TF
TTGO TFT屏幕Arduino使用的小结
TTGO TFT屏幕Arduino使用
TTGO TFT屏幕是一款ESP32和1.44寸LCD屏幕组合的产品,屏幕由ST7789驱动。ESP32自带520
分享我的第一个点灯程序——家庭控制中心
分享我的第一个点灯程序—
2020年,在B站上初识ESP8266,被ESP8266的低门槛、高性价比深深吸引,2020年6月20日,
Copyright   ©2015-2016  Arduino中文社区  Powered by©Discuz!   
快速回复 返回顶部 返回列表