USB 键盘整蛊专家 - 嘉立创EDA开源硬件平台

编辑器版本 ×
标准版 Standard

1、简单易用,可快速上手

2、流畅支持300个器件或1000个焊盘以下的设计规模

3、支持简单的电路仿真

4、面向学生、老师、创客

专业版 professional

1、全新的交互和界面

2、流畅支持超过3w器件或10w焊盘的设计规模,支持面板和外壳设计

3、更严谨的设计约束,更规范的流程

4、面向企业、更专业的用户

专业版 USB 键盘整蛊专家

简介:这是一个基于ESP32 S3 和 WCH 的 CH9329 实现的整蛊设备。ESP32 S3 负责 USB Host 解析USB 键盘数据,CH9329 负责将设备报告为一个键盘设备给上位机。

开源协议: Public Domain

(未经作者授权,禁止转载)

已参加:电子”愚“ 乐设计征集令

创建时间: 2024-02-20 13:25:29
更新时间: 2024-03-05 10:59:05
描述

这是一个能够让你整蛊别人的设备,将它串联到对方的USB 键盘和主机之间后,你可以用过手机上的 Blinker蓝牙连接到这个设备,然后在 Blinker中输出的信息就会出现在对方的电脑上。

硬件设计如下:

  1. 左上角是预留的调试烧录接口,通过这个接口可以进行烧录,同时 Debug信息也可以通过这个接口发送到 PC端;
  2. 左下角是这个的设计核心,它是一个 ESP32-S3 芯片,通过它实现USB Host 和蓝牙通讯的功能;
  3. ESP32-S3工作电压是 3.3V,这里使用 TLV1117 来实现,这个芯片外围只需要2个 1uf电容
  4. 右下角是 Ch9329 芯片,它是一个 HID 转串口芯片,在这个设计中用于实现USB键盘的功能。

CH9326是一款HID转串口免驱芯片。CH9326支持双向数据传输,用于接收串口数据,并按照HID类设备规范,将数据打包通过USB口上传给计算机,或者从计算机接收符合HID类设备的USB数据包,并从串口进行发送。通过提供的上位机软件,用户也可自行配置芯片的VID、PID,以及各种字符串描述符。芯片是 SOP16 封装,容易焊接。

设计的基本思路是:ESP32-S3 负责解析USB键盘数据,用这种方法来获得按键信息。之后,将获得的信息通过串口发送给CH9326, 然后 Ch9326会实现PC端的模拟按键。可以看到,这个设备对于PC端来说是透明的。之后,可以使用 Blinker 的蓝牙功能连接手机和这个设备,之后就可以从手机端发送字符给PC。

PCB 设计如下:

A computer screen shot of a circuit board

Description automatically generated

成品如下(彩色丝印,镀金工艺,背面是设计的一个二维码):

编写 Arduino 代码如下:

#include <elapsedMillis.h>

#include <usb/usb_host.h>

#include "show_desc.hpp"

#include "usbhhelp.hpp"

#define BLINKER_PRINT Serial

#define BLINKER_BLE

#include <Blinker.h>

//键盘数据

char keypress[] = {0x57, 0xAB, 0x00, 0x02, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10};

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;

// 将 Buffer 指向的内容,size 长度,计算 checksum 之后发送到Serial2

void SendData(byte *Buffer, byte size) {

byte sum = 0;

for (int i = 0; i < size - 1; i++) {

Serial2.write(*Buffer);

sum = sum + *Buffer;

Buffer++;

}

*Buffer = sum;

Serial2.write(sum);

}

// 将ASCII 字符转化为 HID Scancode值

byte Asc2Scancode(byte Asc, boolean *shift) {

if ((Asc >= 'a') && (Asc <= 'z')) {

*shift = false;

return (Asc - 'a' + 0x04);

}

if ((Asc >= 'A') && (Asc <= 'Z')) {

*shift = true;

return (Asc - 'A' + 0x04);

}

if ((Asc >= '1') && (Asc <= '0')) {

*shift = false;

return (Asc - '0' + 0x1E);

}

if (Asc == '>') {

*shift = true;

return (0x37);

}

if (Asc == '.') {

*shift = false;

return (0x37);

}

if (Asc == '_') {

*shift = true;

return (0x2D);

}

if (Asc == '-') {

*shift = false;

return (0x2D);

}

return 0;

}

// 如果未绑定的组件被触发,则会执行其中内容

// 输入框输入都会在这里处理

void dataRead(const String & data)

{

BLINKER_LOG("Blinker readString: ", data);

boolean shift;

byte scanCode;

for (int i = 0; i < data.length(); i++) {

BLINKER_LOG("Key In", data.charAt(1));

// 将收到的 ASCII 转为 ScanCode

scanCode = Asc2Scancode(data.charAt(i), &shift);

// 一些按键当有 Shift 按下时会发生转义

if (scanCode != 0) {

if (shift == true) {

keypress[5] = 0x02;

}

BLINKER_LOG("Scancode", scanCode);

// 填写要发送的 ScanCode

keypress[7] = scanCode;

SendData((byte*)keypress, sizeof(keypress));

delay(10);

keypress[5] = 0x00; keypress[7] = 0;

SendData((byte*)keypress, sizeof(keypress));

delay(10);

}

}

}

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]);

// USB Host 解析得到的数据,传输给PC

//

memcpy(&keypress[5],p,transfer->actual_num_bytes);

SendData((byte*)keypress, sizeof(keypress));

}

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()

{

// 初始化调试串口

Serial.begin(115200);

// 初始 CH9329 串口

Serial2.begin(9600, SERIAL_8N1, 14, 13, false, 1000, 112);

//Serial2.begin(9600);

#if defined(BLINKER_PRINT)

BLINKER_DEBUG.stream(BLINKER_PRINT);

#endif

// 初始化blinker

Blinker.begin();

Blinker.attachData(dataRead);

usbh_setup(show_config_desc_full);

}

void loop()

{

usbh_task();

Blinker.run();

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;

}

while (Serial.available()) {

char c = Serial.read();

if (c == 'q') {

boolean shift = false;

// 填写要发送的 ScanCode

keypress[5] = 0x08;

SendData((byte*)keypress, sizeof(keypress));

delay(20);

keypress[5] = 0;

SendData((byte*)keypress, sizeof(keypress));

}

Serial.print(c);

}

}

将板卡装入外壳后的照片:

设计图
原理图
1 /
PCB
1 /
未生成预览图,请在编辑器重新保存一次
工程视频/附件
序号 文件名称 下载次数
1

USB 键盘整蛊专家~1.mp4

1
2

USBKBChaos.ino

4
工程成员
侵权投诉
相关工程
换一批
加载中...
添加到专辑 ×

加载中...

温馨提示 ×

是否需要添加此工程到专辑?

温馨提示
动态内容涉嫌违规
内容:
  • 153 6159 2675

服务时间

周一至周五 9:00~18:00
  • 技术支持

support
  • 开源平台公众号

MP