
EDA-Keypad小键盘
简介
基于【立创开发板 ColorEasyPico2 RP2350】构建,是一个双功能的小键盘,会根据连接的设备自动切换功能,既能当小键盘又能当计算器。 项目结构与电路设计简单,适合新手复刻焊接。
简介:基于【立创开发板 ColorEasyPico2 RP2350】构建,是一个双功能的小键盘,会根据连接的设备自动切换功能,既能当小键盘又能当计算器。 项目结构与电路设计简单,适合新手复刻焊接。开源协议
:GPL 3.0
描述
本项目基于【立创开发板 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信号控制,支持彩色
硬件设计
这里我们使用支持热插拔的轴座,你可以自行配置你喜欢的键盘轴体。
- 需要注意的是,这里我们为了能流畅走线,所以特定好了按键的IO接口,这必然会为后面软件开发带来一定复杂性,你当然可以按照自己的想法接线,但可能走线会很乱。

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++
- 第10章 Keyboard/KeypadPage(0x07)
- 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的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预览图 | 实物图 |
|---|---|
![]() | ![]() |
| 1 | 2 |
|---|---|
![]() | ![]() |
![]() | ![]() |
| 小键盘模式 | 计算器模式 |
![]() | ![]() |
- 更多RGB灯效
- 上位机软件控制
- 自定义快捷键
- 电脑性能显示
- 温湿度监测
- ......
设计图
未生成预览图,请在编辑器重新保存一次BOM
暂无BOM
克隆工程知识产权声明&复刻说明
本项目为开源硬件项目,其相关的知识产权归创作者所有。创作者在本平台上传该硬件项目仅供平台用户用于学习交流及研究,不包括任何商业性使用,请勿用于商业售卖或其他盈利性的用途;如您认为本项目涉嫌侵犯了您的相关权益,请点击上方“侵权投诉”按钮,我们将按照嘉立创《侵权投诉与申诉规则》进行处理。
请在进行项目复刻时自行验证电路的可行性,并自行辨别该项目是否对您适用。您对复刻项目的任何后果负责,无论何种情况,本平台将不对您在复刻项目时,遇到的任何因开源项目电路设计问题所导致的直接、间接等损害负责。

























