# EDA-Keypad小键盘 文档:https://wiki.yang17.top/products/oshwhub/oshw-e9b800b25666423da8a4b87f4755e313/ 项目来源:https://oshwhub.com/course-examples/keyboard 分类:硬件项目 更新时间:2026-07-28T09:59:17.000Z 基于【立创开发板 ColorEasyPico2 RP2350】构建,是一个双功能的小键盘,会根据连接的设备自动切换功能,既能当小键盘又能当计算器。 资料超详细 👇 项目介绍 硬件设计 软件设计 3D结构 附件下载 硬件工程 项目介绍 本项目基于【立创开发板 ColorEasyPico2 RP2350】构建,是一个双功能的小键盘,会根据连接的设备自动切换功能,既能当小键盘又能当计算器。 项目结构与电路设计简单,仅单面布线,成本底且适合新手复刻焊接。 项目功能 RP2350具有双核Arm Cortex-M33核心和双核RISC-V Hazard3核心,可自由选择gcc编译,在这个项目中我们提供的两种固件,分别是: Arm Cortex-M33 300Mhz超频固件 RISC-V Hazard3 默频固件 Raspberrypi的芯片一直都具备有一定的超频空间,适合LVGL之类的图形化场景,且RP2350的RISC-V核心还可运行Linux Buildroot系统,可玩性很高 固件功能 ✅ USB-HID小键盘(连接到主机USB接口时) ✅ 计算器(连接到电源时) ✅ 按键RGB灯效 ✅ RGB亮度调节 ✅ 小屏幕信息显示 开发文档及教程 嘉立创EDA-教育与开源文档中心 硬件参数 基于【立创开发板 ColorEasyPico2 RP2350】,支持USB-HID功能,通过HID模拟键盘 屏幕使用 SSD1306 驱动 0.91 寸屏幕,用于显示计算器,灯光亮度等信息 按键采用热插拔设计,可自由更换键盘轴体 旋钮采用EC11,HID模式下按下为NUM LOCK按键,计算器模式下为AC清除按键 灯光采用WS2812 RGB灯珠,通过pwm信号控制,支持彩色 硬件设计 主控电路 这里直接使用立创开发板 ColorEasyPico2 RP2350开发板,这里我们只引出了用到的几个接口,你可以根据需求引出需要的IO 查看数据手册 开发板源工程 前往立创商城 屏幕电路 这里直接采用SSD1306驱动的0.91寸LCD 4Pin接口屏幕模块 查看数据手册 前往立创商城 旋钮电路 这里直接采用EC11旋钮,电路中为方便焊接,没有加入硬件防抖,全部通过软件处理抖动,降低焊接难度及成本。 查看数据手册 前往立创商城 热插拔轴座 这里我们使用支持热插拔的轴座,你可以自行配置你喜欢的键盘轴体。 需要注意的是,这里我们为了能流畅走线,所以特定好了按键的IO接口,这必然会为后面软件开发带来一定复杂性,你当然可以按照自己的想法接线,但可能走线会很乱。 查看数据手册 前往立创商城 RGB灯组 RGB灯组这里因为剩余的IO足够,所以我们设计为16个RGB组成的矩阵为一组RGB,EC11右侧的独立按键为另外一组RGB,当然你也可以全部串联到一起节省一个IO口。 这里同按键设计一样,为了能流畅走线,所以特定好了按键的IO接口,这必然会为后面软件开发带来一定复杂性,你当然可以按照自己的想法接线,但可能走线会很乱。 查看数据手册 前往立创商城 供电电路 供电这里依照WS2812的数据手册要求供电电压在3.5-5V电压之间,而我们开发板上只有3.3V和5V电压,为确保稳定性这里选择了5V供电。但依照WS2812数据手册中对信号电压的要求是+0.5VDD到-0.23VDD,而pico2开发板原理图中IO电压为3.3V,理论上还要增加一个电平转换电路,不过在实际测试中WS2812的信号电压在2.4V以上时就被判断为1了,也就是说逻辑判断电压实际和VDD电压影响不大,所以为了简化电路降低成本,选择不做电平转换直接连接 软件开发 开发环境 软件环境:VSCode+PlatformIO 开发语言:C/C++ USB HID协议 在本项目中我们主要使用的是USB HID协议,在开发之前建议先认真阅读USB组织提供的USB HID协议文档 第10章 Keyboard/KeypadPage(0x07) USB HID 协议官方文档 库 通过以下库完成本项目开发 TinyUSB Library:用于USB协议收发 NeoPixel:用于RGB驱动 GFX Library:用于图形绘制 SSD1306:用于屏幕驱动 函数定义 通过以下函数完成小键盘逻辑处理 uint32_t Wheel(byte WheelPos) :彩虹色轮生成函数 void effectRainbow():彩虹灯效 void effectMarquee():跑马灯效 void runLEDEffect():灯效调用 void handleEncoderRotation():EC11旋钮逻辑判断 float evaluateExpression(String expr):计算器逻辑处理 色轮生成函数 先将整个255色环被均分为三个区段: 红->蓝(0-84) 2. 蓝->绿(85-169) 3. 绿->红(170-255) 每个区段通过两个颜色分量的线性变化实现平滑过渡 使用255色阶(3*85=255)确保颜色变化连贯无断层 uint32_t Wheel(byte WheelPos) { // 反转输入值,使颜色轮逆向旋转(255为起点,0为终点) WheelPos = 255 - WheelPos; /* 第一阶段:红色 -> 蓝色渐变(当WheelPos在0-84范围时) if (WheelPos < 85) return strip.Color( 255 - WheelPos * 3, 0, WheelPos * 3 ); /* 第二阶段:蓝色 -> 绿色渐变(当WheelPos在85-169范围时) if (WheelPos < 170) { WheelPos -= 85; return strip.Color( 0, WheelPos * 3, 255 - WheelPos * 3 ); } /* 第三阶段:绿色 -> 红色渐变(当WheelPos在170-255范围时) WheelPos -= 170; return strip.Color( WheelPos * 3, 255 - WheelPos * 3, 0 ); } 灯效显示 这里直接用NeoPixel的setPixelColor函数指定RGB并显示就好了 void effectRainbow():彩虹渐变灯效 void effectMarquee():红绿蓝跑马灯灯效 void runLEDEffect():灯效模式调用函数 void effectRainbow() { static uint8_t offset = 0; for (int i = 0; i < NUM_LEDS; i++) { strip.setPixelColor(i, Wheel((i + offset) & 255)); } strip.show(); offset++; delay(50); } // 跑马灯效果 void effectMarquee() { if (millis() - effectTimer > 200) { effectTimer = millis(); strip.clear(); strip.setPixelColor(marqueePosition, 0xFF0000); // 红色光点 strip.setPixelColor((marqueePosition + 4) % NUM_LEDS, 0x00FF00); // 绿色跟随 strip.setPixelColor((marqueePosition + 8) % NUM_LEDS, 0x0000FF); // 蓝色跟随 strip.show(); marqueePosition = (marqueePosition + 1) % NUM_LEDS; } } // 灯效模式调用 void runLEDEffect() { switch (ledMode) { case MODE_RAINBOW: effectRainbow(); break; case MODE_MARQUEE: effectMarquee(); break; } } EC11旋钮逻辑判断 在旋钮逻辑判断函数里,这里对旋钮旋转方向的判断是通过EC11的CLK和DT两条信号线判断,这里我们可以先看一下旋钮旋转时的时序图 把这两个引脚状态合并成二进制来看,也就是说00 -> 01 -> 11 -> 10 -> 00为顺时针旋转,00 -> 10 -> 11 -> 01 -> 00为逆时针旋转,通过两两之间的变化就可以知道EC11旋钮是顺时针旋转还是逆时针旋转了 void handleEncoderRotation() { // 静态变量保存前次状态(CLK和DT的组合状态) static uint8_t lastState = 0; // 存储上一次的引脚组合状态(2位二进制) static unsigned long lastTime = 0; // 存储上次状态变化的时间戳(防抖用) const unsigned long debounceDelay = 5; // 消抖时间阈值(单位:毫秒) // 读取当前编码器状态(将两个引脚状态合并为2位二进制数) // 格式:高1位是CLK引脚状态,低1位是DT引脚状态 uint8_t state = (digitalRead(EC11_CLK) << 1) | digitalRead(EC11_DT); // 检测有效状态变化(状态不同且满足消抖时间条件) if (state != lastState && (millis() - lastTime > debounceDelay)) { /* 顺时针旋转状态序列检测(EC11编码器四步变化规律): * 00 -> 01 -> 11 -> 10 -> 00 * 当检测到以下任一有效转换时判定为顺时针旋转: */ if ((lastState == 0b00 && state == 0b01) || (lastState == 0b01 && state == 0b11) || (lastState == 0b11 && state == 0b10) || (lastState == 0b10 && state == 0b00)) { // 顺时针旋转:降低目标亮度(需确保不低于最小值) targetBrightness = (targetBrightness - BRIGHTNESS_STEP); } /* 逆时针旋转状态序列检测(反向四步变化规律): * 00 -> 10 -> 11 -> 01 -> 00 * 当检测到以下任一有效转换时判定为逆时针旋转: */ else if ( (lastState == 0b00 && state == 0b10) || (lastState == 0b10 && state == 0b11) || (lastState == 0b11 && state == 0b01) || (lastState == 0b01 && state == 0b00)) { // 逆时针旋转:增加目标亮度(需确保不超过最大值) targetBrightness = (targetBrightness + BRIGHTNESS_STEP); } // 更新状态变化时间戳 lastTime = millis(); // 保存当前状态用于下次比较 lastState = state; } } 计算器逻辑 计算器这里则是通过按键拼接字符串,然后再解析字符串计算实现的,这里的函数主要是解析字符串 if (isdigit(c) || c == '.' || (c == '-' && newNum)):数字解析,解析正负数及小数 else if (!newNum):运算符处理,解析加减乘除 if (c == '+' || c == '-'):运算符类型判断,确保先乘除后加减 if (numStr.length() > 0):处理表达式末尾未被运算符触发的剩余数字 float evaluateExpression(String expr) { expr.replace(" ", ""); // 移除所有空格,确保表达式紧凑 float result = 0; // 最终计算结果 float currentTerm = 0; // 当前处理的运算项(用于处理乘除优先级) char op = '+'; // 当前运算符(初始化为+) String numStr; // 临时存储数字字符串(支持多位数和小数) bool newNum = true; // 标志位,表示是否开始新数字输入 // 逐字符解析表达式 for (int i = 0; i < expr.length(); i++) { char c = expr[i]; /* 数字/符号处理: * 1. 常规数字:0-9 * 2. 小数点:. * 3. 合法负号:出现在数字开头或运算符后 */ if (isdigit(c) || c == '.' || (c == '-' && newNum)) { numStr += c; // 将字符追加到数字缓冲区 newNum = false; // 标记进入数字输入状态 } // 运算符处理 else if (!newNum) { float num = numStr.toFloat(); // 转换缓冲区为浮点数 numStr = ""; // 清空数字缓冲区 /* 根据前一个运算符处理当前项(实现乘除优先级): * 加减运算符:将当前项加入结果 * 乘除运算符:立即计算当前项 */ switch (op) { case '+': currentTerm = num; // 加法项直接存入当前项 break; case '-': currentTerm = -num; // 减法项转为负数存储 break; case 'x': currentTerm *= num; // 乘法立即计算 break; case '/': if (num == 0) return NAN; // 除零错误返回特殊值 currentTerm /= num; // 除法立即计算 break; } /* 运算符类型判断: * 遇到加减运算符时将当前项累加到结果 * 遇到乘除运算符时暂存当前项继续运算 */ if (c == '+' || c == '-') { result += currentTerm; // 将累计的当前项加入最终结果 currentTerm = 0; // 重置当前项 } op = c; // 更新当前运算符 newNum = true; // 标记需要开始新数字 } } /* 处理最后一个数字项(表达式末尾没有运算符的情况) if (numStr.length() > 0) { float num = numStr.toFloat(); switch (op) // 根据最后的运算符处理 { case '+': currentTerm = num; break; case '-': currentTerm = -num; break; case 'x': currentTerm *= num; break; case '/': if (num == 0) return NAN; currentTerm /= num; break; } } result += currentTerm; // 将最后的当前项加入结果 return result; // 返回最终计算结果 } 3D外壳结构 前盖 侧面1 侧面2 外壳设计有螺丝孔柱,用于固定。 壳体内部添加倒角加强,屏幕部分为排针焊点添加槽位,屏幕下侧添加限位槽,避免屏幕受外力下凹。 主体外壳采用大圆角,使得整体美观圆润。 后盖 正面 背面 后壳同样采用大圆角,使得整机圆润。 边角处同样采用倒角加强,并开Type-C挖孔。 壳内三角添加PCB支撑,避免PCB受力不均、凹凸不平。 安装结构 项目采用的是三段式结构,由前盖、主板、后盖构成 3D预览图 实物图 安装屏幕 首先将屏幕插入进前盖屏幕限位槽中 安装主板 将拼装完成的主板倒扣,主板屏幕排母对准屏幕排针插入 安装后盖 后盖倒扣插入即可 免费打样 嘉立创提供一站式产业互联智造平台,涵盖电子、机械产业一站式服务🛠️ 嘉立创免费PCB打样 嘉立创免费3D打样 实物图 1 2 小键盘模式 计算器模式 拓展方向 本项目提供了一个简单的多功能小键盘,你可以在本项目的基础上添加更多灯效,屏幕功能等等。另外RP2350还带有USB串口通讯,你可以借助UART通讯使用QT/Flutter为小键盘开发一个上位机,实现更多自定义功能,比如自定义键位、快捷键、音乐律动灯效、电脑性能显示等,而且主板还有余2个IO,1个可共用的IIC,完全可以拓展很多功能。 更多RGB灯效 上位机软件控制 自定义快捷键 电脑性能显示 温湿度监测 ......