工程师调试便携键盘 (传感器改键盘)

“生命不止,折腾不息。”对工程师来说,没有任何一件电子产品可以称作废品。

功能不够丰富?自己焊一块板子。使用不够方便?自己加一个物理外挂。有线键盘太麻烦?自己改装成无线的。EEwolrd论坛的工程师们就自己动手,把小小一块键盘改装得五花八门。

键盘侠终结者之自动反击键盘

作者:彭丙浩 原帖地址:http://www.eeworld.com.cn/avLy9WD

我比较喜欢上网,网络世界是个没有硝烟的江湖,行走江湖,必须有件趁手的兵器,传统的机械键盘火力已经无法满足时代需要,于是我造了这个自动反击键盘,MCU+继电器+键盘电路板 ;当然最节约成本的方案是搞个ST带USB接口的MCU芯片,直接输入字符,我对USB协议不太熟,于是就弄个成品键盘直接拿来用。当前这个姥爷方案简单易用,效果还不错。

工程师设计键盘,键盘改装程序员键盘

工程师设计键盘,键盘改装程序员键盘

工程师设计键盘,键盘改装程序员键盘

工程师设计键盘,键盘改装程序员键盘

工程师设计键盘,键盘改装程序员键盘

工程师设计键盘,键盘改装程序员键盘

机器视觉打造全自动老板键智能键盘

作者:eew_dy9f48 原帖地址:http://www.eeworld.com.cn/afXzvHO

一、作品简介

自带键盘的树莓派Pi400,其实可以看作一块强大的智能键盘,作为电脑的辅助;按照项目内容层层递进,首先,先配置树莓派p400,让它作为一块普通电脑键盘;接下来,我想尝试结合一些人工智能实现键盘命令的自动执行,比如全自动老板键,通过ESP32-CAM作为树莓派的网络摄像头,这样可以脱离连线限制,可以部署在任何地方。然后一旦识别到老板人脸,就自动发送Alt+Tab等老板键,瞬间切换至工作界面。除此之外,由于在上述任务中我们已经实现了树莓派HID键盘的配置,因此我们还可以使用它来作为游戏助手,实现键盘宏,按一个按键打出一整套combo。由于宏是内建在键盘中的,在pc端并没有任何相关进程,所以不会被游戏判定为使用辅助工具。这就可以实现很多功能上的延申,大家可以自行玩耍。

二、系统框图

工程师设计键盘,键盘改装程序员键盘

功能模块一共三部分:

  1. Pi400 HID键盘功能实现;
  2. Pi400 键盘动作的捕捉与独占;
  3. 人脸识别在Pi400上的实现。

三、各部分功能说明

首先,先介绍下项目中包含的硬件设计。

这个项目包括了无线网络摄像头的制作。虽然网上有现成的ESP32CAM模块售卖,且价格非常便宜。但是由于做工良莠不齐,导致经常翻车。而且,ESP32性能较弱,且不支持USB,如果未来想做一些其他的开发也可能会力不从心。因此,趁这个项目的机会,我打算直接制作一块ESP32S2 CAM开发板出来。

这个摄像头开发板其实我后面还重新绘制了第二个版本,增加了tf卡槽,同时修复了飞线的问题,并由于板子面积有限将所有封装换成了0402。但由于新版的板子打样回来焊接难度有点大,还一直迟迟没有动手,因此先使用老版本的板子完成项目。

工程师设计键盘,键盘改装程序员键盘

工程师设计键盘,键盘改装程序员键盘

工程师设计键盘,键盘改装程序员键盘

工程师设计键盘,键盘改装程序员键盘

工程师设计键盘,键盘改装程序员键盘

接下来说说软件方面,我们具体介绍一下每一个功能模块的实现方法。

1. Pi400 HID 键盘功能的实现。

在github上有一个zero_hid的库,可以实现使用树莓派zero模拟hid键盘。但这个库有一些问题,直接使用在组合键上会出很多的问题,因此我参考这个项目,重写了一下这个库。

首先科普一下HID协议,HID键盘协议是一种基于报文的协议,通过在USB总线上进行通信。当用户按下键盘上的按键时,键盘将生成一个HID报文,并将其发送到计算机。计算机收到报文后,根据报文的内容来模拟相应的键盘操作,例如在文本编辑器中输入字符或执行特定的功能。

HID键盘报文包含多个字段,其中最重要的是按键码(Keycode)。按键码表示按下的键的唯一标识符,例如“A”键的按键码是0x04。除了按键码外,报文还可以包含其他信息,如修饰键(如Shift、Ctrl和Alt键)的状态和组合键的状态。

因此,在合成报文前,我们先要知道我们想输入的按键哪些是修饰键,而哪些是按键,他们要分开进行处理。

在进入代码部分前,我们需要先安装一下驱动。首先先新建一个文件,命名为isticktoit_usb,添加可执行权限,并填入以下内容:

···
#!/bin/bash
cd /sys/kernel/config/usb_gadget/
mkdir -p isticktoit
cd isticktoit
echo 0x1d6b > idVendor # Linux Foundation
echo 0x0104 > idProduct # Multifunction Composite Gadget
echo 0x0100 > bcdDevice # v1.0.0
echo 0x0200 > bcdUSB # USB2
mkdir -p strings/0x409
echo "fedcba9876543210" > strings/0x409/serialnumber
echo "Tobias Girstmair" > strings/0x409/manufacturer
echo "iSticktoit.net USB Device" > strings/0x409/product
mkdir -p configs/c.1/strings/0x409
echo "Config 1: ECM network" > configs/c.1/strings/0x409/configuration
echo 250 > configs/c.1/MaxPower
# Add functions here
mkdir -p functions/hid.usb0
echo 1 > functions/hid.usb0/protocol
echo 1 > functions/hid.usb0/subclass
echo 8 > functions/hid.usb0/report_length
echo -ne \\x05\\x01\\x09\\x06\\xa1\\x01\\x05\\x07\\x19\\xe0\\x29\\xe7\\x15\\x00\\x25\\x01\\x75\\x01\\x95\\x08\\x81\\x02\\x95\\x01\\x75\\x08\\x81\\x03\\x95\\x05\\x75\\x01\\x05\\x08\\x19\\x01\\x29\\x05\\x91\\x02\\x95\\x01\\x75\\x03\\x91\\x03\\x95\\x06\\x75\\x08\\x15\\x00\\x25\\x65\\x05\\x07\\x19\\x00\\x29\\x65\\x81\\x00\\xc0 > functions/hid.usb0/report_desc
ln -s functions/hid.usb0 configs/c.1/
# End functions
ls /sys/class/udc > UDC
···

接着运行以下命令,完成驱动配置:

···
#!/bin/bash
echo "" | sudo tee -a /boot/config.txt
echo "# BEGIN HID Keyboard Simulation" | sudo tee -a /boot/config.txt
echo "dtoverlay=dwc2" | sudo tee -a /boot/config.txt
echo "# END HID Keyboard Simulation" | sudo tee -a /boot/config.txt
echo "" | sudo tee -a /etc/modules
echo "# BEGIN HID Keyboard Simulation" | sudo tee -a /etc/modules
echo "dwc2" | sudo tee -a /etc/modules
echo "libcomposite" | sudo tee -a /etc/modules
echo "# END HID Keyboard Simulation" | sudo tee -a /etc/modules
# Move to before exit 0
echo "" | sudo tee -a /etc/rc.local
echo "# BEGIN HID Keyboard Simulation" | sudo tee -a /etc/rc.local
echo "sudo ./isticktoit_usb" | sudo tee -a /etc/rc.local
echo "# END HID Keyboard Simulation" | sudo tee -a /etc/rc.local
···

完成后,以后每次重启完成,只需要运行一下isticktoit_usb即可。

处理报文部分的代码如下:

···
from typing import List
from .hid import hidwrite
from .hid.keycodes import KeyCodes
from time import sleep
import json
import pkgutil
import os
import pathlib
class Keyboard:
def __init__(self, dev='/dev/hidg0') -> None:
self.dev = dev
self.set_layout()
self.control_pressed = []
self.key_pressed = []
def list_layout(self):
keymaps_dir = pathlib.Path(__file__).parent.absolute() / 'keymaps'
keymaps = os.listdir(keymaps_dir)
files = [f for f in keymaps if f.endswith('.json')]
for count, fname in enumerate(files, 1):
with open(keymaps_dir / fname , encoding='UTF-8') as f:
content = json.load(f)
name, desc = content['Name'], content['Description']
print(f'{count}. {name}: {desc}')
def set_layout(self, language='US'):
self.layout = json.loads( pkgutil.get_data(__name__, f"keymaps/{language}.json").decode() )
def gen_list(self, keys = []):
_control_pressed = []
_key_pressed = []
for key in keys:
if key[:3] == "MOD":
_control_pressed.append(KeyCodes[key])
else:
_key_pressed.append(KeyCodes[key])
return _control_pressed, _key_pressed
def gen_buf(self):
self.buf = [sum(self.control_pressed),0] + self.key_pressed
self.buf += [0] * (8 - len(self.buf)) # fill to lenth 8
##########################################################################
# For user
def press(self, keys = [], additive=False, hold=False):
_control_pressed, _key_pressed = self.gen_list(keys)
if not additive:
self.control_pressed = []
self.key_pressed = []
self.control_pressed.extend(_control_pressed)
self.control_pressed = list(set(self.control_pressed)) # remove repeated items
self.key_pressed.extend(_key_pressed)
self.key_pressed = list(set(self.key_pressed))[:6] # remove repeated items and cut until 6 items
self.gen_buf()
hidwrite.write_to_hid_interface(self.dev, self.buf)
if not hold:
self.release(keys)
def release(self, keys = []):
_control_pressed, _key_pressed = self.gen_list(keys)
try:
self.control_pressed = list(set(self.control_pressed) - set(_control_pressed))
except:
pass
try:
self.key_pressed = list(set(self.key_pressed) - set(_key_pressed))
except:
pass
self.gen_buf()
hidwrite.write_to_hid_interface(self.dev, self.buf)
def release_all(self):
self.control_pressed = []
self.key_pressed = []
self.gen_buf()
hidwrite.write_to_hid_interface(self.dev, self.buf)
def text(self, string, delay=0):
for c in string:
key_map = self.layout['Mapping'][c]
key_map = key_map[0]
mods = key_map['Modifiers']
keys = key_map['Keys']
self.press(mods + keys)
sleep(delay)
···

上面这段代码把想要输出的按键分为control(修饰按键)和key(普通按键)两块,再组合形成报文列表。使用的逻辑是输入当前想要按下的按键状态,然后程序发送对应的报文。

测试一下:

···
import os
import zero_hid
if os.geteuid() != 0:
raise ImportError('You must be root to use this library on linux.')
k = zero_hid.Keyboard()
k.press(["KEY_H"], additive=False, hold=False)
k.press(["KEY_E"], additive=False, hold=False)
k.press(["KEY_L"], additive=False, hold=False)
k.press(["KEY_L"], additive=False, hold=False)
k.press(["KEY_O"], additive=False, hold=False)
···

press方法中填入的是一个list,表示当前按下的所有按键。具体的键值列表在zero_hid/keymaps/US.json中。

如果电脑成功打印,表示功能正常。

工程师设计键盘,键盘改装程序员键盘

2. Pi400 键盘动作的捕捉与独占。

一般在python中捕获键盘动作,大家使用的都是keyboard库,简单好用。但keyboard库有个致命的问题,就是无法独占键盘。这在我们当前的应用中是无法接受的。试想一下,当我们想发送ctrl+alt+del时,一旦按下,树莓派和电脑都进入了安全模式。你无法预期在键盘上的操作会在树莓派系统中整出什么幺蛾子。因此,我们需要在捕捉键盘动作的同时,对键盘资源进行独占,以此避免按键被其他的进程捕获。在这里我们使用evdev库来实现。

···
import os
import evdev
if os.geteuid() != 0:
raise ImportError('You must be root to use this library on linux.')
dev = evdev.InputDevice('/dev/input/event0')
dev.grab() # grab 是为了独占,保证此设备不会被别的进程捕获
for event in dev.read_loop():
key = evdev.categorize(event)
if isinstance(key, evdev.KeyEvent) and key.keystate != 2:
print(key.keycode)
···

按下按键,我们就可以看到对应的键值被打印在终端里。

工程师设计键盘,键盘改装程序员键盘

接下来只需要把抓取到的键值组合成列表,发送到我们上一步实现的hid中即可。

细心的同学可能会意识到,evdev抓取到的的键值如果和hid的键值不匹配怎么办?这里我们就需要人工进行匹配,创建一个文件,将他们一一对应起来。

在项目文件夹下创建一个codemap.csv文件,写入以下对应:

···
KEY_LEFTCTRL,MOD_LEFT_CONTROL
KEY_RIGHTCTRL,MOD_RIGHT_CONTROL
KEY_LEFTALT,MOD_LEFT_ALT
KEY_RIGHTALT,MOD_RIGHT_ALT
KEY_LEFTSHIFT,MOD_LEFT_SHIFT
KEY_RIGHTSHIFT,MOD_RIGHT_SHIFT
,
KEY_LEFTMETA,MOD_LEFT_GUI
,
,
KEY_ESC,KEY_ESC
KEY_TAB,KEY_TAB
KEY_CAPSLOCK,KEY_CAPSLOCK
,
KEY_NUMLOCK,KEY_NUMLOCK
KEY_SYSRQ,KEY_SYSRQ
KEY_DELETE,KEY_DELETE
KEY_INSERT,KEY_INSERT
KEY_BACKSPACE,KEY_BACKSPACE
KEY_ENTER,KEY_ENTER
,
KEY_SPACE,KEY_SPACE
,
KEY_UP,KEY_UP
KEY_DOWN,KEY_DOWN
KEY_LEFT,KEY_LEFT
KEY_RIGHT,KEY_RIGHT
,
KEY_PAGEUP,KEY_PAGEUP
KEY_PAGEDOWN,KEY_PAGEDOWN
KEY_HOME,KEY_HOME
KEY_END,KEY_END
,
KEY_F1,KEY_F1
KEY_F2,KEY_F2
KEY_F3,KEY_F3
KEY_F4,KEY_F4
KEY_F5,KEY_F5
KEY_F6,KEY_F6
KEY_F7,KEY_F7
KEY_F8,KEY_F8
KEY_F9,KEY_F9
KEY_F10,KEY_F10
KEY_F11,KEY_F11
KEY_F12,KEY_F12
,
KEY_GRAVE,KEY_GRAVE
KEY_1,KEY_1
KEY_2,KEY_2
KEY_3,KEY_3
KEY_4,KEY_4
KEY_5,KEY_5
KEY_6,KEY_6
KEY_7,KEY_7
KEY_8,KEY_8
KEY_9,KEY_9
KEY_0,KEY_0
KEY_MINUS,KEY_MINUS
KEY_EQUAL,KEY_EQUAL
,
KEY_Q,KEY_Q
KEY_W,KEY_W
KEY_E,KEY_E
KEY_R,KEY_R
KEY_T,KEY_T
KEY_Y,KEY_Y
KEY_U,KEY_U
KEY_I,KEY_I
KEY_O,KEY_O
KEY_P,KEY_P
KEY_A,KEY_A
KEY_S,KEY_S
KEY_D,KEY_D
KEY_F,KEY_F
KEY_G,KEY_G
KEY_H,KEY_H
KEY_J,KEY_J
KEY_K,KEY_K
KEY_L,KEY_L
KEY_Z,KEY_Z
KEY_X,KEY_X
KEY_C,KEY_C
KEY_V,KEY_V
KEY_B,KEY_B
KEY_N,KEY_N
KEY_M,KEY_M
,
KEY_LEFTBRACE,KEY_LEFTBRACE
KEY_RIGHTBRACE,KEY_RIGHTBRACE
KEY_BACKSLASH,KEY_BACKSLASH
KEY_SEMICOLON,KEY_SEMICOLON
KEY_APOSTROPHE,KEY_APOSTROPHE
KEY_COMMA,KEY_COMMA
KEY_DOT,KEY_DOT
KEY_SLASH,KEY_SLASH
,
KEY_KP0,KEY_KP0
KEY_KP1,KEY_KP1
KEY_KP2,KEY_KP2
KEY_KP3,KEY_KP3
KEY_KP4,KEY_KP4
KEY_KP5,KEY_KP5
KEY_KP6,KEY_KP6
KEY_KP7,KEY_KP7
KEY_KP8,KEY_KP8
KEY_KP9,KEY_KP9
KEY_KPASTERISK,KEY_KPASTERISK
KEY_KPMINUS,KEY_KPMINUS
KEY_KPPLUS,KEY_KPPLUS
KEY_KPDOT,KEY_KPDOT
KEY_KPSLASH,KEY_KPSLASH
···

接着在代码中,我们只需要打开该文件,转换为字典,删除空白项,即可制作好对应的字典。每次捕捉到按键后,利用字典翻译一下即可。

···
with open('./codemap.csv', 'r') as file:
reader = csv.reader(file)
codemap = {rows[0]:rows[1] for rows in reader}
del codemap[""]
···

3. 人脸识别在Pi400上的实现。

实现人脸识别我们使用的工具是ultralytics。Ultralytics安装非常简单,只需要pip install ultralytics即可。唯一需要注意的是我们需要更换一下pytorch的版本,否则会出现Segmentation fault

···

pip uninstall torch torchvision

pip install torch==2.0.1 torchvision==0.15.2 torchaudio==2.0.2

···

工程师设计键盘,键盘改装程序员键盘

完成安装后,我们要使用stream的方法,从网络推流中获取到视频流。视频流来源是我们一开始制作的ESP32 S2 CAM开发板。开发板上烧录的是arduino ide上的官方CameraWebServer例程。除了常规的选择对应开发板并修改wifi信息外,我们还需要自定义一下开发板引脚。假设我们这里选择#define CAMERA_MODEL_ESP32S2_CAM_BOARD,那么我们要把camera_pins.h中的对应部分改成:

···
#elif defined(CAMERA_MODEL_ESP32S2_CAM_BOARD)
#define PWDN_GPIO_NUM -1
#define RESET_GPIO_NUM -1
#define XCLK_GPIO_NUM 2
#define SIOD_GPIO_NUM 42
#define SIOC_GPIO_NUM 41
#define Y9_GPIO_NUM 1
#define Y8_GPIO_NUM 3
#define Y7_GPIO_NUM 4
#define Y6_GPIO_NUM 6
#define Y5_GPIO_NUM 8
#define Y4_GPIO_NUM 14
#define Y3_GPIO_NUM 9
#define Y2_GPIO_NUM 7
#define VSYNC_GPIO_NUM 16
#define HREF_GPIO_NUM 15
#define PCLK_GPIO_NUM 5
#define LED_GPIO_NUM 45
···

按照下图所示配置进行烧录即可。

工程师设计键盘,键盘改装程序员键盘

Pi400这边的代码比较简单,ultralytics已经被设计的非常易于使用。

···
from ultralytics import YOLO
import requests
import time
url = "http://192.168.8.171"
model = YOLO("yolov8n.pt")
requests.get(url+"/control?var=framesize&val=" + str(8))
results = model.predict(url+":81/stream", stream=True, show=True, conf = 0.5)
for result in results:
for box in result.boxes:
class_id = result.names[box.cls[0].item()]
cords = box.xyxy[0].tolist()
cords = [round(x) for x in cords]
conf = round(box.conf[0].item(), 2)
print("Object type:", class_id)
print("Coordinates:", cords)
print("Probability:", conf)
print("---")
···

如果所安装的树莓派系统是桌面版本,我们在桌面版本上运行以上程序,就可以看到画面。如果是仅有terminal的系统,terminal中也会有相应信息打印。

工程师设计键盘,键盘改装程序员键盘

最后我们只需要整合上述功能,就可以实现带有全自动老板键的智能键盘。

完整主程序代码如下:

···

import zero_hid

import evdev

import csv

import signal

import os

import threading

if os.geteuid() != 0:

raise ImportError('You must be root to use this library on linux.')

k = zero_hid.Keyboard()

# dev = evdev.InputDevice('/dev/input/by-id/usb-_Raspberry_Pi_Internal_Keyboard-event-kbd')

dev = evdev.InputDevice('/dev/input/event0')

dev.grab() # grab 是为了独占,保证此设备不会被别的进程捕获

with open('./codemap.csv', 'r') as file:

reader = csv.reader(file)

codemap = {rows[0]:rows[1] for rows in reader}

del codemap[""]

curr_pressed = []

def key_input(key):

global curr_pressed

if isinstance(key, evdev.KeyEvent) and key.keystate != 2:

if key.keystate == 1:

curr_pressed.append(key.keycode)

if key.keystate == 0:

curr_pressed.remove(key.keycode)

print("\r" + "CODE: " + key.keycode + " ;STAT: " + str(key.keystate) + " "*40, end="")

keys = [codemap[i] for i in curr_pressed]

k.press(keys, additive=False, hold=True)

def handler(signal, frame):

k.release_all()

dev.ungrab()

dev.close()

exit()

signal.signal(signal.SIGTSTP, handler) # Ctrl+Z

signal.signal(signal.SIGINT, handler) # Ctrl+C

def thread1():

for event in dev.read_loop():

try:

key_input(evdev.categorize(event))

except Exception as error:

print(error)

t1 = threading.Thread(target=thread1, daemon=True)

t1.start()

from ultralytics import YOLO

import requests

url = "http://192.168.8.171"

model = YOLO("yolov8n.pt")

requests.get(url+"/control?var=framesize&val=" + str(8))

results = model.predict(url+":81/stream", stream=True, show=True, conf = 0.5)

delay = 1

count = 0

mode = 0

pre_mode = 0

for result in results:

try:

for box in result.boxes:

class_id = result.names[box.cls[0].item()]

cords = box.xyxy[0].tolist()

cords = [round(x) for x in cords]

conf = round(box.conf[0].item(), 2)

print("Object type:", class_id)

print("Coordinates:", cords)

print("Probability:", conf)

print("---")

if class_id == "person":

mode = 1

count = 0

if mode != pre_mode:

pre_mode = mode

k.press(["MOD_LEFT_ALT","KEY_TAB"], additive=False, hold=False)

print("triggered!!!")

count += 1

if count > delay:

mode = 0

pre_mode = mode

except Exception as error:

print(error)

···

罗技K260键盘套装改锂电池供电及加工作指示

作者:dcexpert 原帖地址:http://www.eeworld.com.cn/a0Guf54

看到手边的罗技K260无线键盘,就想改造一下。

工程师设计键盘,键盘改装程序员键盘

K260使用了两个AAA电池供电,虽说K260省电已经做的很不错,但是为了环保,以及避免电池没电时找不到电池更换,还有电池漏液造成的腐蚀,就想改成锂电池供电。

工程师设计键盘,键盘改装程序员键盘

键盘很容易拆开,取下反面的十多颗螺丝,用撬棒稍微用力把外壳的卡扣松开,就可以轻松把后盖取下

工程师设计键盘,键盘改装程序员键盘

拆开后可以看到按键部分分三层,一层是按键部分,另外两层分别是按键的行列电路。这也是薄膜键盘的标准结构。

工程师设计键盘,键盘改装程序员键盘

工程师设计键盘,键盘改装程序员键盘

工程师设计键盘,键盘改装程序员键盘

主控芯片使用了 nordic 的 nrf31504,这是nordic公司的一颗集成增强8051内核的2.4G无线收发器,带有16K ROM和512字节RAM。在nordic的网站上已经搜索不到这个芯片,估计已经停产了。

工程师设计键盘,键盘改装程序员键盘

比较有趣的是天线部分,居然把长度标上去了。

工程师设计键盘,键盘改装程序员键盘

系统原来使用2节AAA电池供电,电压范围是2-3.2V,而主芯片 nrf31504的电源范围是 1.9-3.6V。使用锂电池供电(电压范围是3.3-4.2V),最简单方式就是加一个二极管,将电源降低0.6V,这样电源范围就是2.7-3.6V,可以满足电压要求。但是这样改造感觉缺少了一点趣味性,就想能不能换一种方式,增加一个LED根据电流指示工作状态,既能反映出工作状态,看起来也更有科技感。用万用表实测键盘的待机电流约35uA,发射时电流约5mA。电流可以通过电流传感器(如TI的INA180)转换为电压,但是春节期间无法打样,PCB打样焊接周期也较长,又不想继续等待,就想还有没有其它方式。考虑到键盘的功耗很低,如果把LED串联到电源中,也是可以满足功耗要求,但是电压就不够了,这时就需要提高供电的电压才行。一颗红色LED导通压降约1.5V,黄色和普通绿色LED约1.8V,蓝色白色LED的压降较高,约2.3-2.7V。这样算下来,使用5V左右供电在串联一个黄色LED正合适,这样的话使用移动电源或TWS耳机充电盒的主板,就可以实现充电管理和升压两个功能,既简单又能满足要求。

虽然键盘看起来很大,但是内部空间其实比较小。经过翻箱倒柜找了一圈,终于在箱底找出几个合适的元件。

电池,用了两种电池,并联以提高容量。一个140mAH,另一个200mAH。

工程师设计键盘,键盘改装程序员键盘

这个圆柱形电池不知道大家认识不

工程师设计键盘,键盘改装程序员键盘

拆开后是这样的。

工程师设计键盘,键盘改装程序员键盘

移动电源主板,这个还是几年前社区网友老杨提供的。把USB座拆掉后,正好可以放在键盘的空隙中。

工程师设计键盘,键盘改装程序员键盘

工程师设计键盘,键盘改装程序员键盘

主要元件的布局

工程师设计键盘,键盘改装程序员键盘

因为键盘是不透明的,为了让指示灯露出来,用电动螺丝刀和钻头,在适当的位置钻孔。充电指示灯处用1.8mm钻头,工作指示灯用3mm的LED,使用2.8mm的钻头。充电指示灯的空用热熔胶堵住,防止进灰进水,同时也能透光。

工程师设计键盘,键盘改装程序员键盘

工程师设计键盘,键盘改装程序员键盘

工程师设计键盘,键盘改装程序员键盘

插入LED,并用胶水固定。

工程师设计键盘,键盘改装程序员键盘

再焊接各部分导线

工程师设计键盘,键盘改装程序员键盘

工程师设计键盘,键盘改装程序员键盘

用透明胶带固定电源板和导线

工程师设计键盘,键盘改装程序员键盘

最后测试一下充电和键盘功能,没有问题就可以将后盖装回去。下面是改装后的效果:

充电指示灯效果。

工程师设计键盘,键盘改装程序员键盘

待机时指示灯状态,每秒唤醒10次左右。

按下键盘,指示灯状态,可以看到按键后不会立即休眠。

CIY64机械键盘锂电池充电改装

作者:IC爬虫 原帖地址:http://www.eeworld.com.cn/a00SefD

CIY64 这款客制化键盘我有两把,使用快两年了,几个月就要换电池,这些废弃的电池没地方回收真是有点污染环境,而且有时碰到没有备用电池的情况非常抓狂,两节南孚7号电池也要6块钱,所以早就有把键盘改成充电的想法。本来想使用无线充电的方案,但是这把键盘的脚撑是不可调节的,而且高低有限,充电时不好放在无线充电器上。又不想给这把键盘的外壳开孔,所以做个能在7号电池仓放下的充电板,1000mah的锂电池估计可以用非常的久,需要用的时候拉出来充其实也没啥问题。

方案:使用ME4054作为充电IC,这款IC最大可以800ma的电流给锂电池充电,但是发热比较大,我不需要快速给锂电池充电,而且避免电路板过热,可以使用这颗芯片的外部配置电阻就充电电流限制在300mA,这个时候芯片温度还可以接受。这款键盘使用的是两节7号电池供电,为了避免锂电池的电压过高损坏键盘的原有的电路,加了一颗PAM2305AABADJ DC-DC降压芯片,将共给键盘控制板的电压限制到2.6V左右。

电路:

工程师设计键盘,键盘改装程序员键盘

安装:

工程师设计键盘,键盘改装程序员键盘

工程师设计键盘,键盘改装程序员键盘

工程师设计键盘,键盘改装程序员键盘

工程师设计键盘,键盘改装程序员键盘

工程师设计键盘,键盘改装程序员键盘

工程师设计键盘,键盘改装程序员键盘

工程师设计键盘,键盘改装程序员键盘

充电电流,这张图是我将充电配置为100ma,有点慢,后面改成了300MA,给1000mah的电池充电,从3.7v充满,耗时一个半小时:

工程师设计键盘,键盘改装程序员键盘

CH582有线键盘转蓝牙键盘

作者:pomin 原帖地址:http://www.eeworld.com.cn/a5WjDaT

结合USB-HOST和蓝牙HID键盘的例程制作了一个有线键盘转蓝牙键盘的设备,代码如下:

/******************************************************************************/
/* 头文件包含 */
#include "CONFIG.h"
#include "HAL.h"
#include "hiddev.h"
#include "hidkbd.h"




/*********************************************************************
 * GLOBAL TYPEDEFS
 */
__attribute__((aligned(4))) uint32_t MEM_BUF[BLE_MEMHEAP_SIZE / 4];
__attribute__((aligned(4))) uint8_t RxBuffer[MAX_PACKET_SIZE]; // IN, must even address
__attribute__((aligned(4))) uint8_t TxBuffer[MAX_PACKET_SIZE]; // OUT, must even address
extern uint8_t need_send;




#if(defined(BLE_MAC)) && (BLE_MAC == TRUE)
const uint8_t MacAddr[6] = {0x84, 0xC2, 0xE4, 0x03, 0x02, 0x02};
#endif




/*********************************************************************
 * @fn      Main_Circulation
 *
 * [url=home.php?mod=space&uid=159083]@brief[/url] 主循环
 *
 * [url=home.php?mod=space&uid=784970]@return[/url] none
 */
__HIGH_CODE
void Main_Circulation()
{
    TMOS_SystemProcess();
}




/*********************************************************************
 * @fn      main
 *
 * @brief   主函数
 *
 * @return  none
 */
int main(void)
{
    uint8_t i, s, k, len, endp;
    uint16_t  loc;
#if(defined(DCDC_ENABLE)) && (DCDC_ENABLE == TRUE)
    PWR_DCDCCfg(ENABLE);
#endif
    SetSysClock(CLK_SOURCE_PLL_60MHz);
#if(defined(HAL_SLEEP)) && (HAL_SLEEP == TRUE)
    GPIOA_ModeCfg(GPIO_Pin_All, GPIO_ModeIN_PU);
    GPIOB_ModeCfg(GPIO_Pin_All, GPIO_ModeIN_PU);
#endif
    /* 开启电压监控 */
    PowerMonitor(ENABLE, HALevel_2V1);
#ifdef DEBUG
    GPIOA_SetBits(bTXD1);
    GPIOA_ModeCfg(GPIO_Pin_8, GPIO_ModeIN_PU);
    GPIOA_ModeCfg(bTXD1, GPIO_ModeOut_PP_5mA);
    UART1_DefInit();
#endif
    PRINT("%s\n", VER_LIB);
    CH58X_BLEInit();
    HAL_Init();
    GAPRole_PeripheralInit();
    HidDev_Init();
    HidEmu_Init();




    PRINT("Start @ChipID=%02X\n", R8_CHIP_ID);




    pU2HOST_RX_RAM_Addr = RxBuffer;
    pU2HOST_TX_RAM_Addr = TxBuffer;
    USB2_HostInit();
    PRINT("Wait Device In\n");
    while(1)
    {
        Main_Circulation();
        s = ERR_SUCCESS;
        if(R8_USB2_INT_FG & RB_UIF_DETECT)
        { // 如果有USB主机检测中断则处理
            R8_USB2_INT_FG = RB_UIF_DETECT;
            s = AnalyzeRootU2Hub();
            if(s == ERR_USB_CONNECT)
                FoundNewU2Dev = 1;
        }




        if(FoundNewU2Dev || s == ERR_USB_CONNECT)
        { // 有新的USB设备插入
            FoundNewU2Dev = 0;
            mDelaymS(200);          // 由于USB设备刚插入尚未稳定,故等待USB设备数百毫秒,消除插拔抖动
            s = InitRootU2Device(); // 初始化USB设备
            if(s != ERR_SUCCESS)
            {
                PRINT("EnumAllRootDev err = %02X\n", (uint16_t)s);
            }
        }




        /* 如果下端连接的是HUB,则先枚举HUB */
        s = EnumAllU2HubPort(); // 枚举所有ROOT-HUB端口下外部HUB后的二级USB设备
        if(s != ERR_SUCCESS)
        { // 可能是HUB断开了
            PRINT("EnumAllHubPort err = %02X\n", (uint16_t)s);
        }




        /* 如果设备是键盘 */
        loc = U2SearchTypeDevice(DEV_TYPE_KEYBOARD); // 在ROOT-HUB以及外部HUB各端口上搜索指定类型的设备所在的端口号
        if(loc != 0xFFFF)
        { // 找到了,如果有两个KeyBoard如何处理?
            i = (uint8_t)(loc >> 8);
            len = (uint8_t)loc;
            SelectU2HubPort(len);                                                 // 选择操作指定的ROOT-HUB端口,设置当前USB速度以及被操作设备的USB地址
            endp = len ? DevOnU2HubPort[len - 1].GpVar[0] : ThisUsb2Dev.GpVar[0]; // 中断端点的地址,位7用于同步标志位
            if(endp & USB_ENDP_ADDR_MASK)
            {                                                                                                        // 端点有效
                s = USB2HostTransact(USB_PID_IN << 4 | endp & 0x7F, endp & 0x80 ? RB_UH_R_TOG | RB_UH_T_TOG : 0, 0); // 传输事务,获取数据,NAK不重试
                if(s == ERR_SUCCESS)
                {
                    endp ^= 0x80; // 同步标志翻转
                    if(len)
                        DevOnU2HubPort[len - 1].GpVar[0] = endp; // 保存同步标志位
                    else
                        ThisUsb2Dev.GpVar[0] = endp;
                    len = R8_USB2_RX_LEN; // 接收到的数据长度
                    if(len)
                    {
                        U2SETorOFFNumLock(RxBuffer);




                        PRINT("keyboard data: ");
                        for(i = 0; i < len; i++)
                        {
                            PRINT("x%02X ", (uint16_t)(RxBuffer[i]));
                        }
                        PRINT("\n");
                        need_send = 1;
                    }
                }
                else if(s != (USB_PID_NAK | ERR_USB_TRANSFER))
                {
                    PRINT("keyboard error %02x\n", (uint16_t)s); // 可能是断开了
                }
            }
            else
            {
                PRINT("keyboard no interrupt endpoint\n");
            }
            SetUsb2Speed(1); // 默认为全速
        }
    }
}




/******************************** endfile @ main ******************************/

设置need_send全局变量作为标志位,对于蓝牙HID线程修改如下:


 if(events & START_REPORT_EVT)

    {

        if (need_send) {

            HidDev_Report(HID_RPT_ID_KEY_IN, HID_REPORT_TYPE_INPUT,

                          HID_KEYBOARD_IN_RPT_LEN, RxBuffer);

            need_send = 0;

        }

        tmos_start_task(hidEmuTaskId, START_REPORT_EVT, 1);

        return (events ^ START_REPORT_EVT);

    }

将HID线程的周期改为1ms来提高蓝牙键盘的速度。

实物:

工程师设计键盘,键盘改装程序员键盘

(工程文件可至原帖内*载下**)

  • 扫描二维码,与作者交流

老机械键盘改造USB,QWERTY/Dvorak一键切换

作者:cruelfox 原帖地址:http://www.eeworld.com.cn/aWvjfbD

这个DIY项目的想法已经有很久了,如今终于达到了的设计的初衷。要体现“任性”的特点,先介绍背景吧。

在看这个帖子的诸位一定都在用计算机键盘吧。键盘上的数字键是1到9从左至右排列,或者是右小键盘区那样三个一排有序排列,反正规律很明显。但是字母键却不是A,B,C...到Z这么按字母序有规律地排下来的。我刚接触电脑(其实还是学习机)的时候,没在意这个问题,觉得是要盲打嘛,反正对两手的手指头来说,按字母序排列并没有什么好处。于是用多了这些排列也就记住了,从来不管它为什么要这样。其实,PC的键盘键位排布上是延用了打字机的键盘,这是设备演变过程中很自然的一个延续。打字机的历史就要早很多了,我没有亲见过打字机长什么样,而且,咱们汉字是铅字打上去的,和英文打字机方式完全不同。

工程师设计键盘,键盘改装程序员键盘

上面这个照片(来自wikipedia),是"Sholes and Glidden Type-Writer.",第一个获得成功的商用打字机(1873)。请注意它的键盘字母键排列。

工程师设计键盘,键盘改装程序员键盘

为什么得到这样一个字母排列?在当时的确经过了多次的优化改进,因为打字机是机械的动作,要尽量避免连续的击键引起冲突。结果是因为商业上的成功,QWERTY这个布局也跟着被越来越多的制造商吸收采用。在非英语语言的键盘上,个别键位可能不同,属大同小异了。最早的IBM PC键盘:

工程师设计键盘,键盘改装程序员键盘

其实在电传动打字机问世之后,打字键盘的键位布局就可以自由了。但是QWERTY的流行没有被改变——习惯的力量是强大的。虽然是大众所接受,QWERTY也有被人诟病的地方,比如说左右手分配不平衡,在英语里面单独用左手能打出来的单词远比单独用右手的多。那么,除了QWERTY还能用啥?在ANSI标准里面还有另外一个键盘布局,叫做DVORAK.

Dvorak(德沃夏克)布局,是以其发明人之一: August Dvorak 的姓命名的。在20世纪30年代,Dvorak 和 Dealey 在他们多年的研究工作基础上发明了Dvorak布局,目标是减少打字出错几率、提高速度和减少手的疲劳。最初发明的布局是这个样子:

工程师设计键盘,键盘改装程序员键盘

Dvorak布局的最明显特征是让使用频率最高的键安排在中间的一排(Home row),这样手指不用移动就触得到。当然还有左右手均衡的设计等等。尽管不是所有人都同意Dvorak布局能够比QWERTY布局提高键盘输入的效率,最快打字速度的记录的确是在Dvorak键盘上创造的。

我是经常要写代码的人,对键盘要求比较高,一定要顺手。从1998年拥有电脑开始,第一块键盘用了5年,实在是塑料结构磨损严重了才换了。第二块键盘用了大概也有5年,第三块是淘宝买到的和第二块同样的。除了手感,我对键盘还有个挑剔是要大回车键(老键盘惯出来的)。到了用上笔记本电脑,键盘问题只能忍忍了。我最后买的一块Benq的”轻指飞扬"绝版键盘因为是USB,作为笔记本键盘替补一直保留到现在。

到2012年下半年,我在淘宝发现了有“机械键盘”这东东,认识了Cherry MX轴。然后到2013年农历年后,我花一百多一点买了一块老旧的国产青轴机械键盘,虽然很陈旧状态也差了,敲了一会儿我就发现:这就是我要的手感啊,一比起来用了多年的薄膜键盘简直太委屈手指了。我后来花了更多的钱买了新的轴(就是机械键盘的开关)来更换修复,使之成为上班工作用。

机械键盘用着爽,后来我发现手指别扭的地方了,跟QWERTY键盘布局有关系。了解了Dvorak布局之后,我下定决心,换用Dvorak. 这个过程很漫长,大约是一年以后才抛开了QWERTY根深蒂固的影响。到如今两年多,我也没有肯定我的输入速度是否达到自己曾经QWERTY时候最快的水平,不过可以肯定的是换了Dvorak,手指头是舒服了。借个图说明两种布局的差别:

工程师设计键盘,键盘改装程序员键盘

从QWERTY换到Dvorak,除了决心以及过程中的痛苦外,还有额外的成本。一是操作系统的支持,虽然DOS, Windows, Linux都支持Dvorak,但需要加载keymap,或者设置键盘布局,且每台机器,每个用的系统都要改。在Windows上,Dvorak和默认的En-US是平级的,但中文输入法只能用En-US也就是压根儿没考虑Dvorak. 于是我将en_us.dll直接替换掉了,但也不是完美的解决,比如Sogou拼音会从更底层调用读键盘,还是没法用(于是我一直用智能ABC咯)。二是用别人电脑的时候,比如同事要请帮忙,又不能SSH过去,我就只好盯着键盘来“一指禅”了;以及电脑安装系统的时候,应急启动时候,类似的困难。三是我的电脑夫人也就没法用,同样的道理。四是虽然内部变成Dvorak,键盘上印的还是QWERTY那样的,必须盲打,必须双手干活,不能一只手拿着食物啦。这时候我多希望它还是QWERTY,可以用用一指禅。

综上,在操作系统软件层次上修改键盘布局来使用Dvorak,问题还是多多。那么我在键盘上面改,硬件直接搞定好了。附带的好处是可以随时切换键盘布局,键盘也可以共享给夫人用。国产老机械键盘里面主控是8049 MCU,虽然不能对它编程,我换掉它还是可以的。于是就有了这次的“任性"DIY。

先是改造的对象,主角: 这已经是拆解出机械键盘中的PCB板+钢板,并且拆掉了全部的键轴之后的样子。这块键盘买来时的成色相当差,很脏,惟有键帽还不错,但原本的轴已进灰,状态差。

工程师设计键盘,键盘改装程序员键盘

轴全部拆下来之后才能将钢板和PCB分离,不然是被卡住的。原来键盘里面的灰比照片上还多得多。注意到这块DIP40的芯片,就是键盘的主控。

工程师设计键盘,键盘改装程序员键盘

特写,80C49

工程师设计键盘,键盘改装程序员键盘

LED部分,使用了一片D触发器锁存指示灯状态。

工程师设计键盘,键盘改装程序员键盘

*力暴**破坏,将80C49拆掉。

工程师设计键盘,键盘改装程序员键盘

拆掉原来的键盘主控,我用什么顶替呢?没有引脚全兼容的单片机了,而且我要制作USB键盘,所以……STM32F072,做块一样大小的PCB. 因为主要是使用原有的键盘扫描矩阵,有些引脚是不需要连的。

工程师设计键盘,键盘改装程序员键盘

焊好元件后的板子,准备替换80C49。

工程师设计键盘,键盘改装程序员键盘

用剪下的电阻腿作连接吧,对好位把引脚都焊上。STM32F0的SWD接口务必要留出来*载下**程序的。

工程师设计键盘,键盘改装程序员键盘

这是在软件开发当中调试的场景。USB线需要飞线,因为原来的键盘PCB上就没有USB。

工程师设计键盘,键盘改装程序员键盘

开始安装钢板,主键区焊上全新的Cherry MX茶轴(2.5 RMB一颗)。F区暂且空着,因为使用频率不高,换新轴就显得浪费了,等下再把部分旧轴清洗一下装回去。

工程师设计键盘,键盘改装程序员键盘

我设计的MCU PCB要在键盘PCB和钢板之间。除了SWD的引脚,把UART飞线出来供调试的不时之需。

工程师设计键盘,键盘改装程序员键盘

主键区键帽就位。

工程师设计键盘,键盘改装程序员键盘

编辑键区也安装好,确认这里替换后不会有冲突。调试用的线和针脚以后是要拆掉的。

工程师设计键盘,键盘改装程序员键盘

最后的组装,USB线,以及切换键盘布局的附加按钮。部分键轴还没有装,低优先级的。

工程师设计键盘,键盘改装程序员键盘

DIY过程直播完了,下面说硬件的设计。80C49是块MCU,貌似也就在PS/2键盘上面用。搜到其datasheet对引脚的定义:

工程师设计键盘,键盘改装程序员键盘

其实最关心的还是键盘矩阵怎么接的,这个我就靠人肉了,在的PCB背面寻着每条扫描行或列线找,记录在草稿纸上。最终整理出来的结果是这样的:(最上边和最右边铅笔写的数字是引脚编号)

工程师设计键盘,键盘改装程序员键盘

扫描矩阵是8x14的,最多可以支持112个按键,实际上只有101个键,空出了一些。对照上面那个引脚定义,可以把用到的I/O口确定了。除了电源引脚,剩下还有几个引脚使用到:PS/2的CLK和DATA占用2个,状态指示LED的电路占用1个,AT/XT开关使用了一个。我用STM32F072C8,有48个引脚刚好是够的,富余的I/O就飞线引出了。

这是我设计的电路图:

工程师设计键盘,键盘改装程序员键盘

PCB Layout:

工程师设计键盘,键盘改装程序员键盘

不从80C49引脚上走的信号包括: SWD接口,USB D+/D-,USART TX/RX,额外两个可用I/O。

软件上的工作比硬件多得多。因为想改造成USB键盘,不得不把USB HID的实现稍微看懂一下。PS/2模式硬件上也是保留的,暂时我还没去写软件。

总结一下,USB HID键盘需要使用两种HID报告:一是从设备到主机的,按键状态的报告,8字节;二是主机到设备的,指示灯状态的报告,1字节。第一个报告我使用EP1(Endpoint 1, 端点1)来发送,中断传输;第二个报告就使用默认的EP0,控制传输。USB的描述符,可以从现有的USB键盘上修改而来。 (完整代码和工程文件见原帖)