“生命不止,折腾不息。”对工程师来说,没有任何一件电子产品可以称作废品。
功能不够丰富?自己焊一块板子。使用不够方便?自己加一个物理外挂。有线键盘太麻烦?自己改装成无线的。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端并没有任何相关进程,所以不会被游戏判定为使用辅助工具。这就可以实现很多功能上的延申,大家可以自行玩耍。
二、系统框图
功能模块一共三部分:
Pi400 HID键盘功能实现;Pi400 键盘动作的捕捉与独占;人脸识别在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/bashcd /sys/kernel/config/usb_gadget/mkdir -p isticktoitcd isticktoitecho 0x1d6b > idVendor # Linux Foundationecho 0x0104 > idProduct # Multifunction Composite Gadgetecho 0x0100 > bcdDevice # v1.0.0echo 0x0200 > bcdUSB # USB2mkdir -p strings/0x409echo "fedcba9876543210" > strings/0x409/serialnumberecho "Tobias Girstmair" > strings/0x409/manufacturerecho "iSticktoit.net USB Device" > strings/0x409/productmkdir -p configs/c.1/strings/0x409echo "Config 1: ECM network" > configs/c.1/strings/0x409/configurationecho 250 > configs/c.1/MaxPower# Add functions heremkdir -p functions/hid.usb0echo 1 > functions/hid.usb0/protocolecho 1 > functions/hid.usb0/subclassecho 8 > functions/hid.usb0/report_lengthecho -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_descln -s functions/hid.usb0 configs/c.1/# End functionsls /sys/class/udc > UDC···接着运行以下命令,完成驱动配置:
···#!/bin/bashecho "" | sudo tee -a /boot/config.txtecho "# BEGIN HID Keyboard Simulation" | sudo tee -a /boot/config.txtecho "dtoverlay=dwc2" | sudo tee -a /boot/config.txtecho "# END HID Keyboard Simulation" | sudo tee -a /boot/config.txtecho "" | sudo tee -a /etc/modulesecho "# BEGIN HID Keyboard Simulation" | sudo tee -a /etc/modulesecho "dwc2" | sudo tee -a /etc/modulesecho "libcomposite" | sudo tee -a /etc/modulesecho "# END HID Keyboard Simulation" | sudo tee -a /etc/modules# Move to before exit 0echo "" | sudo tee -a /etc/rc.localecho "# BEGIN HID Keyboard Simulation" | sudo tee -a /etc/rc.localecho "sudo ./isticktoit_usb" | sudo tee -a /etc/rc.localecho "# END HID Keyboard Simulation" | sudo tee -a /etc/rc.local···完成后,以后每次重启完成,只需要运行一下isticktoit_usb即可。
处理报文部分的代码如下:
···from typing import Listfrom .hid import hidwritefrom .hid.keycodes import KeyCodesfrom time import sleepimport jsonimport pkgutilimport osimport pathlibclass Keyboard:def __init__(self, dev='/dev/hidg0') -> None:self.dev = devself.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_presseddef gen_buf(self):self.buf = [sum(self.control_pressed),0] + self.key_pressedself.buf += [0] * (8 - len(self.buf)) # fill to lenth 8########################################################################### For userdef 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 itemsself.key_pressed.extend(_key_pressed)self.key_pressed = list(set(self.key_pressed))[:6] # remove repeated items and cut until 6 itemsself.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:passtry:self.key_pressed = list(set(self.key_pressed) - set(_key_pressed))except:passself.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 osimport zero_hidif 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 osimport evdevif 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_CONTROLKEY_RIGHTCTRL,MOD_RIGHT_CONTROLKEY_LEFTALT,MOD_LEFT_ALTKEY_RIGHTALT,MOD_RIGHT_ALTKEY_LEFTSHIFT,MOD_LEFT_SHIFTKEY_RIGHTSHIFT,MOD_RIGHT_SHIFT,KEY_LEFTMETA,MOD_LEFT_GUI,,KEY_ESC,KEY_ESCKEY_TAB,KEY_TABKEY_CAPSLOCK,KEY_CAPSLOCK,KEY_NUMLOCK,KEY_NUMLOCKKEY_SYSRQ,KEY_SYSRQKEY_DELETE,KEY_DELETEKEY_INSERT,KEY_INSERTKEY_BACKSPACE,KEY_BACKSPACEKEY_ENTER,KEY_ENTER,KEY_SPACE,KEY_SPACE,KEY_UP,KEY_UPKEY_DOWN,KEY_DOWNKEY_LEFT,KEY_LEFTKEY_RIGHT,KEY_RIGHT,KEY_PAGEUP,KEY_PAGEUPKEY_PAGEDOWN,KEY_PAGEDOWNKEY_HOME,KEY_HOMEKEY_END,KEY_END,KEY_F1,KEY_F1KEY_F2,KEY_F2KEY_F3,KEY_F3KEY_F4,KEY_F4KEY_F5,KEY_F5KEY_F6,KEY_F6KEY_F7,KEY_F7KEY_F8,KEY_F8KEY_F9,KEY_F9KEY_F10,KEY_F10KEY_F11,KEY_F11KEY_F12,KEY_F12,KEY_GRAVE,KEY_GRAVEKEY_1,KEY_1KEY_2,KEY_2KEY_3,KEY_3KEY_4,KEY_4KEY_5,KEY_5KEY_6,KEY_6KEY_7,KEY_7KEY_8,KEY_8KEY_9,KEY_9KEY_0,KEY_0KEY_MINUS,KEY_MINUSKEY_EQUAL,KEY_EQUAL,KEY_Q,KEY_QKEY_W,KEY_WKEY_E,KEY_EKEY_R,KEY_RKEY_T,KEY_TKEY_Y,KEY_YKEY_U,KEY_UKEY_I,KEY_IKEY_O,KEY_OKEY_P,KEY_PKEY_A,KEY_AKEY_S,KEY_SKEY_D,KEY_DKEY_F,KEY_FKEY_G,KEY_GKEY_H,KEY_HKEY_J,KEY_JKEY_K,KEY_KKEY_L,KEY_LKEY_Z,KEY_ZKEY_X,KEY_XKEY_C,KEY_CKEY_V,KEY_VKEY_B,KEY_BKEY_N,KEY_NKEY_M,KEY_M,KEY_LEFTBRACE,KEY_LEFTBRACEKEY_RIGHTBRACE,KEY_RIGHTBRACEKEY_BACKSLASH,KEY_BACKSLASHKEY_SEMICOLON,KEY_SEMICOLONKEY_APOSTROPHE,KEY_APOSTROPHEKEY_COMMA,KEY_COMMAKEY_DOT,KEY_DOTKEY_SLASH,KEY_SLASH,KEY_KP0,KEY_KP0KEY_KP1,KEY_KP1KEY_KP2,KEY_KP2KEY_KP3,KEY_KP3KEY_KP4,KEY_KP4KEY_KP5,KEY_KP5KEY_KP6,KEY_KP6KEY_KP7,KEY_KP7KEY_KP8,KEY_KP8KEY_KP9,KEY_KP9KEY_KPASTERISK,KEY_KPASTERISKKEY_KPMINUS,KEY_KPMINUSKEY_KPPLUS,KEY_KPPLUSKEY_KPDOT,KEY_KPDOTKEY_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 torchvisionpip 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 YOLOimport requestsimport timeurl = "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:",_id)print("Coordinates:", cords)print("Probability:", conf)print("---")···如果所安装的树莓派系统是桌面版本,我们在桌面版本上运行以上程序,就可以看到画面。如果是仅有terminal的系统,terminal中也会有相应信息打印。
最后我们只需要整合上述功能,就可以实现带有全自动老板键的智能键盘。
完整主程序代码如下:
···import zero_hidimport evdevimport csvimport signalimport osimport threadingif 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_pressedif 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+Zsignal.signal(signal.SIGINT, handler) # Ctrl+Cdef 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 YOLOimport requestsurl = "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 = 1count = 0mode = 0pre_mode = 0for 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:",_id)print("Coordinates:", cords)print("Probability:", conf)print("---")if_id == "person":mode = 1count = 0if mode != pre_mode:pre_mode = modek.press(["MOD_LEFT_ALT","KEY_TAB"], additive=False, hold=False)print("triggered!!!")count += 1if count > delay:mode = 0pre_mode = modeexcept 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 addressextern 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_CODEvoid 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键盘上修改而来。(完整代码和工程文件见原帖)