最近把吃灰许久的ESP32-C3翻出来做了一个BLE HID键盘把办公室那套“只能看不能用”的机械键盘方案彻底换掉了。以前大家总说蓝牙鼠标能DIY键盘就没见过几个像样的教程其实用ESP32-C3做HID键盘比想象中简单得多而且成本极低——一块开发板加上几个按键就能实现自定义键位、组合键、快捷宏甚至还能调功耗做到一颗电池用很久。这篇就把整个项目从选型到固件到调试一条龙讲清楚。核心围绕三个词ESP32-C3、BLE、HID。你不需要有很深的嵌入式基础会用Arduino IDE、能看懂GPIO就能跟着做。代码是完整可用的烧进去就能连手机或电脑打字剩下的就是往矩阵里加你自己的键位映射。1. 为什么是ESP32-C3方案对比与硬件特性1.1 常见BLE HID方案怎么选做蓝牙键盘市面上常见的芯片方案就那么几类nRF52840、STM32WB系列、ESP32-C3再加上各类专用键盘主控比如Atmel/Microchip的USB HID芯片。如果你只想要一个标准键盘买现成的键盘主控板最省事但灵活性很差想加宏定义、改键位布局、做自己专属的功能键就很痛苦。nRF52840是北欧半导体的老牌BLE芯片协议栈成熟很多客制化键盘都在用但缺点是开发门槛偏高资料大多是英文的板子价格也不算便宜。STM32WB系列性能强、资源丰富但同样存在开发环境复杂、BLE协议栈上手成本高的问题。ESP32-C3的优势在于价格便宜十几块到二十几块一个模组集成BLE 5.0GPIO数量够用最关键是开发资料多、踩坑的人多你遇到问题一搜就能找到答案。虽然它定位是Wi-FiBLE双模芯片但做HID键盘时我们只跑BLE把Wi-Fi彻底关掉功耗也能压到可接受范围。对于“想折腾、想完全自定义”的DIY玩家ESP32-C3是目前性价比最高的选择。1.2 ESP32-C3的核心硬件资源ESP32-C3用的是RISC-V单核处理器主频最高160MHz对于键盘扫描、HID报文组装这种轻量任务来说性能绰绰有余。它内置了BLE 5.0支持长广播、扩展广播等功能射频性能在室内环境下实测能穿一堵墙办公场景完全够用。常用开发板是SuperMini或者合宙的C3开发板引脚引出比较全板载USB口可以用来烧录和供电。GPIO方面做一个小键盘比如6键宏键盘、数字小键盘完全够用。就算做全键位矩阵键盘C3的GPIO数量也能覆盖——常见的4x4矩阵用8个GPIO就能实现16个按键5x6矩阵用11个GPIO就能覆盖30键日常DIY妥妥的。1.3 这个方案能实现什么效果我用这套方案做了一个20键的小键盘包含数字区、回车、退格、音量控制和几个自定义宏键。连上电脑后系统直接识别为“蓝牙键盘”不需要装任何驱动手机、平板、电脑通吃。因为是标准的HID协议所以不光是打字媒体键、音量键、甚至开机进入BIOS时都能用前提是电脑BIOS支持蓝牙键盘这个后面会讲。和市面上成品蓝牙键盘相比自制的最大优势就是“键位我说了算”。想把某个按键映射成组合键CtrlC改一行代码的事。想做一个“一键打开终端并输入特定命令”的宏键代码里写好字符串发送逻辑就行。这种自由度是成品键盘给不了的也是DIY的核心乐趣。2. 硬件准备与电路设计2.1 物料清单做这个项目需要的东西不多大部分都是几块钱的零件ESP32-C3开发板SuperMini或任意C3核心板注意选引出GPIO多的版本一块机械轴或轻触按键数量看你需要的键位二极管1N4148或1N5819数量与按键数量相同10k欧姆电阻若干如果打开内部上拉可以省掉面包板、洞洞板或PCB面包板适合原型验证导线若干、USB线一根供电和烧录可选3D打印外壳、锂电池、充电模块做便携版按键用机械轴的话手感更好Gateron、Cherry轴都行但需要配轴座或直接焊到洞洞板上。如果只是想快速验证功能用几个轻触开关在面包板上搭一个最小系统就够跑通逻辑了。2.2 矩阵键盘的原理按键一多一个按键占一个GPIO的方式就浪费引脚了。矩阵扫描的原理是把按键排列成M行N列的网格行线接GPIO输出列线接GPIO输入。扫描时逐行拉低或拉高然后读列的电平变化就能判断是哪个按键被按下。我自己用的20键布局是4行×5列一共占9个GPIO。如果是全键盘104键用12×8矩阵也才20个GPIO仍然在C3的能力范围内。矩阵方案是所有键盘主控的通用做法省引脚、原理简单唯一的坑是“鬼键”问题——当三个按键围成一个矩形时第四角会被误判为按下。解决方案就是给每个按键串联一个二极管利用二极管的单向导通特性切断反向通路。所以物料清单里的二极管不是可选项矩阵键盘必须有不然会出现按键错乱。2.3 接线图与上拉配置接线思路定义行引脚和列引脚每个按键的一端接行线、另一端经过二极管接列线。二极管方向要统一比如都从行到列导通这样扫描信号不会被串到其他行列。列引脚设置为输入模式并打开内部上拉行引脚设置为输出模式默认输出高电平扫描时依次将某一行拉低然后读所有列的电平。被按下的按键会把对应列的电平拉低就能准确定位键位。我实测过直接使用ESP32-C3的内部上拉电阻就能稳定工作不需要外接上拉电阻。但要注意C3的内部上拉阻值较高在面包板环境干扰大时可能会出现误触发这时给列引脚外接10kΩ下拉或上拉电阻能提高稳定性。另外全部GPIO走线尽量短不要和电源线并行走减少耦合干扰。3D打印外壳这一步可以后置先把电路在面包板上跑通确认键位和扫描逻辑没问题再考虑外壳不然一次做太多事出了问题不好排查。3. 固件编写与BLE HID核心逻辑3.1 BLE HID协议栈是怎么工作的BLE HID的本质是把蓝牙设备模拟成一个标准的HID设备比如键盘、鼠标、游戏手柄。BLE协议栈里有个GATT通用属性协议层HID设备就是通过GATT服务来暴露输入数据的。核心的Profile包括HID Service、Battery Service和Device Information Service其中HID Service里面最重要的两个特征值是HID Report Map和HID Report。HID Report Map描述的是“这个设备是键盘还是鼠标、有哪些按键、报告的长度是多少”相当于告诉主机“我是一把键盘”。真正的按键数据通过HID Report特征值上报比如标准键盘每按一次就发送一个8字节的报告——第1字节是修饰键Ctrl、Shift、Alt、Win第2字节是保留字节第3到第8字节是当前HID键码。这里说的键码是HID Usage ID比如字母A对应0x04字母B对应0x05和ASCII码不是一回事。在建连时BLE HID设备还有一个“Boot Mode”的概念。这个模式是用最简化的HID报告格式不需要主机的HID驱动做复杂解析。很多电脑的BIOS只认Boot Mode的蓝牙键盘所以如果你想在开机时用这键盘进BIOS必须在代码里先初始化并进入Boot Mode否则进系统后才能识别。3.2 ESP32-C3上跑BLE HID的代码结构我用的是Arduino框架配合Espressif官方的BLE库和HID库。Arduino框架把ESP-IDF的很多细节封装好了对DIY玩家非常友好。代码核心就四块初始化BLE、创建HID设备、扫描矩阵按键、发送HID报告。第一步初始化BLE并设置设备名称比如“ESP32 Keyboard”。广播参数里要带上HID服务的UUID这样手机或电脑的蓝牙列表里会显示它是一个键盘图标而不是未知设备。初始化HID设备时要注册Report Map、设置输入报告回调并调用hidDev.setBootMode(0)让设备进入Boot Mode。第二步是写报告描述符。标准键盘的报告描述符大概几十个字节定义了输入报告的各个字段。这部分我直接从USB HID规范里抄的标准描述符你完全可以照用只要不改动字段定义就不用动它。第三步才是真正和硬件打交道的地方——按键扫描。扫描函数要处理好防抖、组合键、连续按键的情况。后面细说。3.3 矩阵按键扫描与防抖按键扫描的逻辑不复杂但要做好细节。我用的扫描函数框架如下// 定义行列引脚 const int ROW_PINS[] {4, 5, 6, 7}; // 行引脚 const int COL_PINS[] {8, 9, 10, 11, 12}; // 列引脚 // 键位映射表0xFF表示无对应按键其他值为HID键码 const uint8_t keymap[4][5] { {0x52, 0x54, 0x55, 0x56, 0x57}, // 上、下、左、右、回车 {0x59, 0x5A, 0x5B, 0x5C, 0x5D}, // 数字1~4、退格 {0x04, 0x05, 0x06, 0x07, 0x08}, // A~D {0x09, 0x0A, 0x0B, 0x0C, 0x2A}, // E~H、删除 }; uint8_t readMatrix() { for (int r 0; r 4; r) { pinMode(ROW_PINS[r], OUTPUT); digitalWrite(ROW_PINS[r], LOW); for (int c 0; c 5; c) { if (digitalRead(COL_PINS[c]) LOW) { return keymap[r][c]; } } digitalWrite(ROW_PINS[r], HIGH); pinMode(ROW_PINS[r], INPUT); } return 0; }这段代码的思路依次把每行拉低检查所有列引脚只要某列变成低电平就说明该行该列的按键被按下。扫描到按键后从keymap里取出对应的HID键码。防抖是关键机械按键在按下和释放的瞬间会产生几十毫秒的电平抖动不处理就会出现一次按键发送多次、或者按一下没反应的情况。我用的软件防抖方案是第一次检测到电平变化时不立即上报而是延时10~20ms后再读一次确认电平稳定了才发送。这个方案简单实用不需要外部硬件滤波。扫描周期也要控制好。我在主循环里每10ms扫描一次矩阵加上20ms的防抖确认整体的响应速度在30ms左右人眼完全感觉不到延迟和成品键盘的手感差距不大。3.4 组合键、修饰键和NKRO键盘报告的前两个字节专门用来放修饰键比如Ctrl、Shift、Alt、Win对应位是bit0~bit3。要发送CtrlC就需要在报告里同时把Control修饰键位置1再把C的键码0x06放到键码区。BLE HID的普通报告一次最多带6个按键的键码。这意味着理论上你最多同时按6个不同按键足以覆盖正常打字场景。游戏场景下可能需要同时按更多键这就是所谓的NKRO全键无冲。但标准BLE HID在Boot Mode下面不支持NKRO只有部分专业游戏键盘通过私有协议实现。DIY场景下除非你要打音游或者某些需要多键同按的游戏否则6键无冲完全够用。如果要追求更高的同按数量可以尝试修改报告描述符把按键字段扩展到14字节以上但很多操作系统和HID驱动不一定认兼容性会变差建议别折腾。修饰键的处理是另一个容易踩坑的地方。我一开始只发送HID键码结果CtrlC发出去变成了单独按C。后来才发现修饰键必须和普通按键在同一个报告里发送主机才能解析成组合键。我在代码里维护了一个modifierKey变量扫描到Ctrl键时把它对应的位赋值同时把其他按键放入键码区组装成完整的8字节报告一起发送。4. 烧录、调试与常见问题4.1 烧录失败的排查思路ESP32-C3的烧录相比老ESP32还多了一些坑很多人在第一步就卡住。最常见的现象是烧录时报A fatal error occurred: Failed to connect to ESP32-C3: No serial data received。原因和解决方式按概率排序没有进入下载模式。C3需要按住BOOT按键再上电或复位才能进入串口下载模式。用Arduino IDE烧录时先按住BOOT键点烧录看到“Connecting...”时松开。国产开发板很多没有自动下载电路这个步骤绕不开。串口驱动没装。C3的USB转串口芯片很多是CH340或CP2102需要安装对应驱动。如果电脑识别不到串口多半是驱动问题。端口选错。拔掉其他USB串口设备只保留C3重新打开设备管理器看端口号。烧录速度太高。在Arduino IDE或esptool里把烧录速度降到115200有些劣质USB线在高速下载时不稳定。你如果反复尝试都不行换一根短一点的USB线试试。USB线的供电和数据线质量直接影响下载成功率这个问题我排查了整整一晚上才找到教训深刻。4.2 设备搜不到、连不上、爱断连烧录成功后第一次搜索蓝牙设备搜不到或者连上了几分钟就断开这几乎是每个人都遇到过的问题。搜不到设备时先检查广播参数。确保广播包里包含了HID服务的UUID并且广播类型设置为可连接广播。另外ESP32-C3默认可能会同时广播Wi-Fi信息确认Wi-Fi已经被关掉了否则广播包包含不必要的信息干扰HID设备发现。连接后频繁断开多半和两方面有关一是电源不稳定。C3在BLE广播和连接事件期间电流会有明显波动如果你用的是电池或者劣质USB供电电压跌落会导致射频模块重启。给板子加一个大电容100μF电解电容或者换一个稳定的5V电源就能解决。二是连接参数不匹配。ESP32-C3默认的连接间隔可能偏长造成主机认为设备无响应。我在代码里把最小连接间隔设为15ms最大30ms从机延迟设为0这样连接稳定性好很多同时也不至于太耗电。还有一个很常见的场景是手机连得上、电脑连不上反过来也一样。这通常是电脑蓝牙适配器对Boot Mode报告解析的兼容性问题。我个人的经验是Windows 10/11的蓝牙驱动有时候在设备首次配对后需要重启蓝牙适配器才能正确识别HID服务笔记本的Fn键开关蓝牙也会影响搜索。4.3 功耗优化实测做无线键盘功耗是绕不开的痛点。ESP32-C3跑BLE在不做任何优化时工作电流大概在80~120mA这显然不能接受。我优化后把整机平均电流压到了5mA以下两节AA电池能用一个月以上。优化的核心就几点彻底关闭Wi-Fi。只跑BLE时调用WiFi.mode(WIFI_OFF)可以省掉很大一部分射频功耗。降低CPU频率。键盘扫描不需要160MHz把主频降到40MHz甚至20MHz对功耗改善非常明显。进入modem sleep模式。C3在待机时会自动进入modem sleep射频部分关闭只有BLE协议栈定时醒来监听广播或连接事件。实测modem sleep期间电流只有1mA左右。按键唤醒。给按键对应的GPIO配置唤醒功能键盘空闲时进入深度睡眠按下任意键才唤醒这样键盘放着不用的功耗可以降到几十微安级别。我实测不同状态下的电流数据如下表状态平均电流无优化、Wi-Fi开启约110mA关闭Wi-Fi、默认主频约35mA关闭Wi-Fi、40MHz、modem sleep约5mA深度睡眠GPIO唤醒约50μA如果你做的是便携键盘强烈建议加上深度睡眠。唤醒后BLE重连需要1~2秒打字时完全能接受但换来的续航提升非常可观。4.4 用BLE调试工具验证HID报告写完代码烧录后需要验证HID报告有没有正确发出。我常用的是手机上的nRF Connect应用它能看到设备所有的GATT服务和特征值也能手动读出HID Report的值。调试方法连上设备后找到HID ServiceUUID是0x1812点开Report特征值打开通知开关然后按你的键盘按键就能实时看到报告数据。比如按下字母A应该能看到类似00 00 04 00 00 00 00 00的数据流。第一位是修饰键第三位0x04就是A的HID键码。如果看到数据但没有按下对应功能多半是HID键码映射错了。电脑端还有一个更强大的工具是Wireshark搭配蓝牙适配器抓包可以看到完整的BLE协议栈通信过程包括广播、连接、ATT读写、HID报告上报层次非常清晰。不过配置相对复杂建议遇到疑难杂症才用平时用nRF Connect就够。4.5 常见问题速查表问题表现可能原因解决办法烧录时连接失败没进下载模式按住BOOT再上电设备搜索不到广播参数不对检查HID UUID是否在广播包里连接经常断开供电不稳定加电容或换稳定电源按键按一下发两次软件防抖不充分延长防抖确认时间到20ms组合键不生效修饰键和按键码不同包在同一份报告里发送修饰键键码电脑识别为“未知设备”HID描述符有误用nRF Connect核对Report Map键盘不能进BIOS未启用Boot Mode初始化时调用setBootMode(0)5. 进阶玩法与经验总结5.1 从“能用”到“好用”的几个扩展方向键盘跑通基础功能只是第一步DIY的乐趣在于按自己的需求不断迭代。我目前正在往这几个方向扩展一是加旋钮编码器。EC11旋钮的三根引脚接到空闲GPIO上通过旋钮的正反转控制音量和页面滚动按下去作为静音键。编码器是增量式的硬件输入直接读GPIO电平变化就能判断方向实现起来比按键扫描更简单但交互手感提升非常明显。二是加OLED屏幕。ESP32-C3支持I2C接口的0.91寸或0.96寸OLED可以实时显示当前层数、大小写状态、电量等信息。显示驱动库选择U8g2内存占用大约2KB左右C3完全吃得消。在做宏键盘时屏幕上显示每个按键当前的宏功能就不用费劲记键位了。三是做多按键层。现在的键盘固件大多支持多层键位类似于键盘的Fn层、电竞模式层。按下某个切层键整个键位映射表就切换成另一套相当于用一个20键的小键盘实现了40键甚至60键的功能。层切换在代码里就是一个索引数组的切换逻辑非常简单但实用性极强。四是加锂电池和充电模块。用TP4056充电模块加一块3.7V锂电池把开发板接到电池正负极上C3内部有LDO稳压电压范围在2.8~3.6V之间都能稳定工作。再配合深度睡眠逻辑一个真正的无线键盘就成型了。注意电池负极要接开发的GND不要和USB共地混乱。做得再深一点可以给键盘加上USB HID模式。C3的USB外设能模拟成USB键盘这样同一个硬件蓝牙连不了的时候插线还能当有线键盘用。固件里做一个切换逻辑检测到USB接入就自动切到USB HID模式拔线后再切回BLE。5.2 我踩过的几个坑希望你绕开写出来这几个坑都是我实际遇到过、排查了很久才解决的供大家参考第一个是关于GPIO的复用功能。C3很多GPIO同时是SPI、I2C、UART、USB、ADC的复用引脚如果某个按键总是触发异常先查一下它是不是被复用了。我遇到过把按键接到GPIO18上结果每次扫描都会触发一次莫名其妙的中断查了半天才发现这个引脚默认绑定了USB D功能需要先调用pinMatrixOutAttach或配置IO MUX才能当普通GPIO用。第二个是HID键码和键盘布局的关系。HID键码定义的是物理位置不是字符。同样是0x04在美式键盘上是字母A在德式键盘上是A也就是同一个位但如果你切换到俄语布局物理位置对应的字符就会变。这个不是键盘固件的问题是主机系统的键盘布局在起作用。如果你给中文用户做键盘最好参考系统默认的美式布局来映射键位。第三个是BLE重连的问题。C3在连接断开后默认不会自动重新广播这就导致键盘闲置几分钟断开后你想再用时必须手动按一下配对键重新进入广播状态。我在代码里加了一个检测逻辑连接断开后立即重新开始广播这样再次配对就不用按任何按键体验和成品键盘基本一致。还有一个办法是让某个按键专门负责“断开并重新广播”作为应急键。5.3 最后分享一个实用技巧调试期间最怕的就是反复烧录、反复拔线很伤开发板的USB口。我的做法是所有代码里都保留一个“自检模式”——按住某个指定按键再上电板子会跳过BLE初始化直接把当前扫描到的键值通过串口打印出来。这样排查矩阵接线、防抖参数都不用重新烧录直接在串口监视器看数据就行。这个自检模式我每次做键盘都会加上能省不少事。如果你也准备入坑自制键盘我的建议是先从最小系统开始6个按键、4行X3列先跑通确认能在手机上打字成功再往上面加功能。一次贪多容易陷入“哪里都坏了”的排查泥潭。把BLE HID的原理吃透剩下的就是堆功能而已。