关键词: DIY CV键盘 树莓派 PICO 微控制器 TinyUSB
0 写在前面
1) CV键盘简介
顾名思义,只提供 “复制粘贴” 功能的小键盘。
他们说:高端的程序员,往往采用最朴素的编程方式。
他们说:顶尖程序员都把自己叫做CV工程师。
他们说:CV大法是一门正派武功,没几年沉淀学不来。
他们说:程序员的事不叫抄,这叫代码复用。
在他们之间,有一款键盘十分受追捧。这款神秘键盘,抛弃了那些冗余花哨的键位,只保留了最纯粹的功能,专为追求效率的CV工程师量身打造。
搬砖,只要这一个键盘就够了。
2) 主控芯片简介:
RP2040,一款树莓派mcu
https://datasheets.raspberrypi.com/rp2040/rp2040-datasheet.pdfhttps://datasheets.raspberrypi.com/rp2040/rp2040-datasheet.pdf
Pico开发板,灵活、易用的树莓派微控制器开发板,基于rp2040芯片 https://pico.org.cnhttps://pico.org.cn
3) 硬件准备
a)开发环境:树莓派4B
b)目标设备:树莓派PICO
c)PCB:CV键盘
d)电子元件:机械按键 0805 1K电阻 100nF电容
4) 术语:
HID:人机交互设备
Host/Device:主机/从机(设备)
TinyUSB:一种USB协议栈
PHY:外设
1 使用树莓派开发树莓派:
https://datasheets.raspberrypi.com/pico/getting-started-with-pico.pdfhttps://datasheets.raspberrypi.com/pico/getting-started-with-pico.pdf
1)构建开发环境
a) 下载构建脚本
$ wget https://raw.githubusercontent.com/raspberrypi/pico-setup/master/pico_setup.sh
b)获取权限
$ chmod +x pico_setup.sh
c)执行脚本
$ ./pico_setup.sh
d)重新启动
$ sudo reboot
2)获取示例代码和SDK
$ cd ~/
$ mkdir pico
$ cd pico
获取sdk
$ git clone -b master https://github.com/raspberrypi/pico-sdk.git
$ cd pico-sdk
$ git submodule update --init
$ cd ..
$ git clone -b master https://github.com/raspberrypi/pico-examples.git
3)获取编译工具
$ sudo apt update
$ sudo apt install cmake gcc-arm-none-eabi libnewlib-arm-none-eabi build-essential
更新SDK
$ cd pico-sdk
$ git pull
$ git submodule update
4)编译示例代码
$ cd pico-examples
$ mkdir build
$ cd build
导入sdk路径
$ export PICO_SDK_PATH=../../pico-sdk
$ cmake ..
编译代码
$ cd usb/device/dev_hid_composite
$ make -j4
5)下载和运行
按住booltest按键同时将pcio连接到4B上,弹出大容量设备挂在点击ok,拷贝到.uf2目标盘
$ sudo cp dev_hid_composite.uf2 /media/pi/RPI_RP2
此时按下bootest按键可以观察到键盘输入字符A,鼠标移动x和y分别移动5
2 TinyUsb协议栈
键盘的按键触发基于USB进行传输,因此引入开源的USB协议栈TinyUSB。
TinyUSB是一种用于嵌入式系统的开放源代码跨平台USB主机/设备堆栈,设计为内存安全,无动态分配和线程安全,所有中断事件都被延迟,然后在非ISR任务功能中处理。
https://docs.tinyusb.org/en/latesthttps://docs.tinyusb.org/en/latest
USB 设备通过报告形式向主机传递数据,这种报告称为USB HID报告描述符。HID是人机交互设备的缩写。
USB HID报告描述符是USB主机请求于USB设备的一种描述符。HID设备用报告的形式发送数据到主机,描述符告诉主机如何解释数据。在像键盘、鼠标等低速设备中,每10ms进行一次数据上报。通过数据上报告诉主机哪个按键被按下或者鼠标移动的位置。
3 Demo代码分析
在路径pico/pico-example/build/usb/device/dev_hid_composite/main.c中。
main调用了5个函数:
int main(void)
{
board_init(); //外设初始化
tusb_init(); //tinyUsb协议栈初始化
while (1)
{
tud_task(); // tinyusb device task
led_blinking_task();// LED运行指示灯
hid_task(); //人机交互设备任务
}
return 0;
}
关键:hid_task()其实现为:
void hid_task(void)
{
// Poll every 10ms
const uint32_t interval_ms = 10;
static uint32_t start_ms = 0;
//判断时间是否到10ms
if ( board_millis() - start_ms < interval_ms) return;
start_ms += interval_ms;
//按键是否被按下
uint32_t const btn = board_button_read();
//当挂起时远端唤醒
if ( tud_suspended() && btn )
{
// Wake up host if we are in suspend mode
// and REMOTE_WAKEUP feature is enabled by host
tud_remote_wakeup();
}
else
{
// 发送报告上第一条信息
send_hid_report(REPORT_ID_KEYBOARD, btn);
}
}
其中关键函数:send_hid_report()函数实现:
static void send_hid_report(uint8_t report_id, uint32_t btn)
{
// skip if hid is not ready yet
if ( !tud_hid_ready() ) return;
//HID 设备类型
switch(report_id)
{
case REPORT_ID_KEYBOARD:
{
// use to avoid send multiple consecutive zero report for keyboard
static bool has_keyboard_key = false;
if ( btn )
{
uint8_t keycode[6] = { 0 };
keycode[0] = HID_KEY_A; //按键A触发
tud_hid_keyboard_report(REPORT_ID_KEYBOARD, 0, keycode);
has_keyboard_key = true;
}else
{
// send empty key report if previously has key pressed
if (has_keyboard_key) tud_hid_keyboard_report(REPORT_ID_KEYBOARD, 0, NULL);
has_keyboard_key = false;
}
}
break;
case REPORT_ID_MOUSE:
{
int8_t const delta = 5;
// no button, right + down, no scroll, no pan
tud_hid_mouse_report(REPORT_ID_MOUSE, 0x00, delta, delta, 0, 0);
}
break;
case REPORT_ID_CONSUMER_CONTROL:
{
// use to avoid send multiple consecutive zero report
static bool has_consumer_key = false;
if ( btn )
{
// volume down
uint16_t volume_down = HID_USAGE_CONSUMER_VOLUME_DECREMENT;
tud_hid_report(REPORT_ID_CONSUMER_CONTROL, &volume_down, 2);
has_consumer_key = true;
}else
{
// send empty key report (release key) if previously has key pressed
uint16_t empty_key = 0;
if (has_consumer_key) tud_hid_report(REPORT_ID_CONSUMER_CONTROL, &empty_key, 2);
has_consumer_key = false;
}
}
break;
case REPORT_ID_GAMEPAD:
{
// use to avoid send multiple consecutive zero report for keyboard
static bool has_gamepad_key = false;
hid_gamepad_report_t report =
{
.x = 0, .y = 0, .z = 0, .rz = 0, .rx = 0, .ry = 0,
.hat = 0, .buttons = 0
};
if ( btn )
{
report.hat = GAMEPAD_HAT_UP;
report.buttons = GAMEPAD_BUTTON_A;
tud_hid_report(REPORT_ID_GAMEPAD, &report, sizeof(report));
has_gamepad_key = true;
}else
{
report.hat = GAMEPAD_HAT_CENTERED;
report.buttons = 0;
if (has_gamepad_key) tud_hid_report(REPORT_ID_GAMEPAD, &report, sizeof(report));
has_gamepad_key = false;
}
}
break;
default: break;
}
}
伪代码逻辑:
函数入口
{
初始化外设
初始化协议栈
进入循环
{
协议栈任务
按键触发协议传输
LED运行灯任务
}
}
代码过程分析:
进入循环后,每10ms判断一次按键是否触发,触发则触发链式消息中的第一条,发送键盘消息,随后的消息会在协议栈任务中以链式消息顺序发送。
4 修改代码
1)修改按键触发的gpio6.7.8初始化
文件目录 pico/pico-sdk/lib/tinyusb/hw/rp2040/family.c
#define CV_BOARD
#ifdef CV_BOARD
#define KEY_CTRL 6
#define KEY_C 7
#define KEY_V 8
#endif
#ifdef CV_BOARD
gpio_init(KEY_CTRL);
gpio_init(KEY_C);
gpio_init(KEY_V);
gpio_set_dir(KEY_CTRL,GPIO_IN);
gpio_set_dir(KEY_C,GPIO_IN);
gpio_set_dir(KEY_V,GPIO_IN);
#endif
2)删除链式消息中其他HID设备行为
原枚举
enum
{
REPORT_ID_KEYBOARD = 1,
REPORT_ID_MOUSE,
REPORT_ID_CONSUMER_CONTROL,
REPORT_ID_GAMEPAD,
REPORT_ID_COUNT
};
现枚举:
enum
{
REPORT_ID_KEYBOARD = 1,
REPORT_ID_COUNT,
REPORT_ID_MOUSE,
REPORT_ID_CONSUMER_CONTROL,
REPORT_ID_GAMEPAD
};
3)修改原来的按键触发
void hid_task(void){
uint32_t const btn = board_button_read();
}
为
void hid_task(void){
uint32_t const btn = (!gpio_get(KEY_CTRL)) | (!gpio_get(KEY_C)) |(!gpio_get(KEY_V));
}
4)原来的传递的按键码
static void send_hid_report(uint8_t report_id, uint32_t btn)
{
// skip if hid is not ready yet
if ( !tud_hid_ready() ) return;
switch(report_id)
{
case REPORT_ID_KEYBOARD:
{
// use to avoid send multiple consecutive zero report for keyboard
static bool has_keyboard_key = false;
if ( btn )
{
uint8_t keycode[6] = { 0 };
keycode[0] = HID_KEY_A;
tud_hid_keyboard_report(REPORT_ID_KEYBOARD, 0, keycode);
has_keyboard_key = true;
}
else
……
……
……
}
修改为
static void send_hid_report(uint8_t report_id, uint32_t btn)
{
// skip if hid is not ready yet
if ( !tud_hid_ready() ) return;
switch(report_id)
{
case REPORT_ID_KEYBOARD:
{
// use to avoid send multiple consecutive zero report for keyboard
static bool has_keyboard_key = false;
if ( btn )
{
uint8_t keycode[6] = { 0 };
if(!gpio_get(KEY_CTRL)) keycode[0] = HID_KEY_CNOTROL_LEFT;
if(!gpio_get(KEY_C)) keycode[1] = HID_KEY_C;
if(!gpio_get(KEY_V)) keycode[2] = HID_KEY_V;
for(int i = 6; i < 6 ; i++)
{
if(keycode[i] == 0)
{
memcpy(&keycode[i],&kecode[i+1],6 - i + 1);
}
}
tud_hid_keyboard_report(REPORT_ID_KEYBOARD, 0, keycode);
has_keyboard_key = true;
}
else
……
……
……
}
5)编译与烧录
$ make -j4
$ sudo cp dev_hid_composite.uf2 /media/pi/RPI_RP2
5 硬件电路设计
使用GPIO6-8作为键盘三个按键的输入脚。电路原理图如下:
PCB设计
6 成品测试
(补充视频)文章来源:https://www.toymoban.com/news/detail-495314.html
2022/10/3文章来源地址https://www.toymoban.com/news/detail-495314.html
到了这里,关于经典CV键盘——树莓派版本复刻的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!