站内搜索
发作品签到
EDA-Keypad小键盘
专业版

EDA-Keypad小键盘

5.7w
0
0
123

简介

基于【立创开发板 ColorEasyPico2 RP2350】构建,是一个双功能的小键盘,会根据连接的设备自动切换功能,既能当小键盘又能当计算器。 项目结构与电路设计简单,适合新手复刻焊接。

简介:基于【立创开发板 ColorEasyPico2 RP2350】构建,是一个双功能的小键盘,会根据连接的设备自动切换功能,既能当小键盘又能当计算器。 项目结构与电路设计简单,适合新手复刻焊接。
复刻成本:30

开源协议

GPL 3.0

创建时间:2025-04-18 14:15:11更新时间:2025-11-20 15:46:19

描述

项目介绍

本项目基于【立创开发板 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

    29726366532200.png

屏幕电路
这里直接采用SSD1306驱动的0.91寸LCD 4Pin接口屏幕模块

    30142828333300.png

旋钮电路
这里直接采用EC11旋钮,电路中为方便焊接,没有加入硬件防抖,全部通过软件处理抖动,降低焊接难度及成本。

    30435263613000.png

热插拔轴座

这里我们使用支持热插拔的轴座,你可以自行配置你喜欢的键盘轴体。

    • 需要注意的是,这里我们为了能流畅走线,所以特定好了按键的IO接口,这必然会为后面软件开发带来一定复杂性,你当然可以按照自己的想法接线,但可能走线会很乱。

    38052856863500.png

RGB灯组

RGB灯组这里因为剩余的IO足够,所以我们设计为16个RGB组成的矩阵为一组RGB,EC11右侧的独立按键为另外一组RGB,当然你也可以全部串联到一起节省一个IO口。

    • 这里同按键设计一样,为了能流畅走线,所以特定好了按键的IO接口,这必然会为后面软件开发带来一定复杂性,你当然可以按照自己的想法接线,但可能走线会很乱。

    38331850926500.png

供电电路

供电这里依照WS2812的数据手册要求供电电压在3.5-5V电压之间,而我们开发板上只有3.3V和5V电压,为确保稳定性这里选择了5V供电。但依照WS2812数据手册中对信号电压的要求是+0.5VDD到-0.23VDD,而pico2开发板原理图中IO电压为3.3V,理论上还要增加一个电平转换电路,不过在实际测试中WS2812的信号电压在2.4V以上时就被判断为1了,也就是说逻辑判断电压实际和VDD电压影响不大,所以为了简化电路降低成本,选择不做电平转换直接连接

    3046631416200.png

    3143412152100.png

软件开发

开发环境
  • 软件环境:VSCode+PlatformIO
  • 开发语言:C/C++
USB HID协议
在本项目中我们主要使用的是USB HID协议,在开发之前建议先认真阅读USB组织提供的USB HID协议文档
  • 第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色环被均分为三个区段:
    1. 红->蓝(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两条信号线判断,这里我们可以先看一下旋钮旋转时的时序图

image.png

把这两个引脚状态合并成二进制来看,也就是说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
image.pngimage.png
  • 外壳设计有螺丝孔柱,用于固定。
  • 壳体内部添加倒角加强,屏幕部分为排针焊点添加槽位,屏幕下侧添加限位槽,避免屏幕受外力下凹。
  • 主体外壳采用大圆角,使得整体美观圆润。
后盖
正面背面
image.pngimage.png
  • 后壳同样采用大圆角,使得整机圆润。

  • 边角处同样采用倒角加强,并开Type-C挖孔。

  • 壳内三角添加PCB支撑,避免PCB受力不均、凹凸不平。

安装结构

项目采用的是三段式结构,由前盖、主板、后盖构成

3D预览图实物图
image.pngimage.png
安装屏幕
首先将屏幕插入进前盖屏幕限位槽中
安装主板
将拼装完成的主板倒扣,主板屏幕排母对准屏幕排针插入
安装后盖
后盖倒扣插入即可
免费打样
嘉立创提供一站式产业互联智造平台,涵盖电子、机械产业一站式服务🛠️
实物图
12
img_v3_02mn_29d486f0-f91b-45a1-8094-60aa5474954g.jpgimg_v3_02mn_6d3d0bb1-cb98-4e04-b8a3-357b1dcaa1ag.jpg
img_v3_02mn_58358ca4-9e2b-4188-b477-9c9d4d0308eg.jpgimg_v3_02mn_d7b76942-b470-457e-86c0-583ae8878dbg.jpg
小键盘模式计算器模式
612f010a-e96e-46ca-8583-66261d50f027.jpg6126c416-026c-46d1-b80d-0cfc5c2aae27.jpg
拓展方向
本项目提供了一个简单的多功能小键盘,你可以在本项目的基础上添加更多灯效,屏幕功能等等。另外RP2350还带有USB串口通讯,你可以借助UART通讯使用QT/Flutter为小键盘开发一个上位机,实现更多自定义功能,比如自定义键位、快捷键、音乐律动灯效、电脑性能显示等,而且主板还有余2个IO,1个可共用的IIC,完全可以拓展很多功能。
  • 更多RGB灯效
  • 上位机软件控制
  • 自定义快捷键
  • 电脑性能显示
  • 温湿度监测
  • ......

设计图

未生成预览图,请在编辑器重新保存一次

BOM

暂无BOM

3D模型

序号文件名称下载次数
1
keyboardbuttom.STEP
178
2
keyboardec11.STEP
136
3
keybodtop.STEP
136

附件

序号文件名称下载次数
1
EDA-Keypad资源包.zip
814
2
演示视频 .mp4
313
3
EDA-Keypad资源包.zip
88
克隆工程
添加到专辑
0
0
分享
侵权投诉
知识产权声明&复刻说明

本项目为开源硬件项目,其相关的知识产权归创作者所有。创作者在本平台上传该硬件项目仅供平台用户用于学习交流及研究,不包括任何商业性使用,请勿用于商业售卖或其他盈利性的用途;如您认为本项目涉嫌侵犯了您的相关权益,请点击上方“侵权投诉”按钮,我们将按照嘉立创《侵权投诉与申诉规则》进行处理。

请在进行项目复刻时自行验证电路的可行性,并自行辨别该项目是否对您适用。您对复刻项目的任何后果负责,无论何种情况,本平台将不对您在复刻项目时,遇到的任何因开源项目电路设计问题所导致的直接、间接等损害负责。

底部导航