1. 项目概述为什么选择TCA8418构建分体正交键盘如果你玩过DIY机械键盘大概率对“矩阵扫描”这个词不陌生。传统做法是让主控芯片比如我们熟悉的ATmega32U4或者RP2040的GPIO引脚直接连接键盘矩阵的行和列然后在主循环里不断地、逐行地输出扫描信号再读取列线的状态来判断哪个键被按下。这个方法经典、直接但有个绕不开的问题它极其消耗主控的资源。一个60%的键盘6x1484键就需要至少20个GPIO引脚6行14列这还没算指示灯、旋钮等其他功能。更麻烦的是扫描逻辑完全由软件实现主控必须时刻“惦记”着去轮询键盘在复杂或多任务的应用中这可能会引入延迟或者因为主循环被其他任务阻塞而导致“丢键”。这正是TCA8418这类专用键盘矩阵扩展器芯片的价值所在。它本质上是一个“键盘协处理器”。你把矩阵的行和列线直接接到它专用的引脚上它内部就帮你完成了所有繁琐的扫描、消抖和状态记录工作。当有按键事件按下或释放发生时它会把事件存入一个8级的硬件FIFO队列然后通过一个中断引脚INT通知主控“有事儿快来处理”。主控只需要在空闲时通过简单的I2C通信从TCA8418的寄存器里读取事件队列即可。这样一来主控从重复性的扫描劳动中解放出来GPIO引脚也只需要占用I2C的两根线SDA, SCL和一根可选的INT线实现了资源的极致优化。我们这次要做的分体式正交键盘就是将这个理念发挥到极致的一个案例。键盘采用左右分离的“分体”设计这能让你以更自然、舒适的角度摆放双手缓解手腕疲劳。而“正交”布局所有键帽整齐排列成网格而非传统的交错式则让手指移动的轨迹更短、更符合逻辑长期使用能提升输入效率。整个项目由三个核心模块构成左右各一块负责30个按键6行x5列扫描的TCA8418以及一块作为大脑和USB接口的QT Py RP2040。两块TCA8418因为地址固定且相同所以必须分别连接到QT Py RP2040的两个独立I2C总线上。最终我们得到的是一个完全开源、从固件到外壳都可自定义的USB键盘。注意TCA8418的I2C地址是固定的0x347位地址。这意味着在一条I2C总线上你只能挂载一块TCA8418。本项目巧妙地利用了QT Py RP2040拥有两个独立I2C外设的特性board.STEMMA_I2C()和board.I2C()完美解决了地址冲突问题。如果你的主控只有一个I2C接口则需要使用I2C多路复用器如TCA9548A来扩展。2. 硬件深度解析从芯片选型到电路设计2.1 核心器件选型与原理1. 主控Adafruit QT Py RP2040选择它而非常见的Pro Micro或Pi Pico有几个非常实际的理由。首先是尺寸QT Py的板型极其小巧约22x18mm为紧凑的键盘外壳设计留出了宝贵空间。其次是它原生自带一个STEMMA QTQwiic兼容接口这是一个防反插的4针I2C连接器极大简化了与第一个TCA8418的连线提升了可靠性和整洁度。最后也是最重要的RP2040芯片本身双核、高性能且CircuitPython对其支持非常完善让我们可以专注于键盘逻辑而非底层寄存器配置。2. 矩阵扩展器Adafruit TCA8418 Breakout这是项目的灵魂。TI的TCA8418芯片本身功能强大但直接焊接芯片和必要的外围电路如上拉电阻、去耦电容对新手不友好。Adafruit的Breakout板帮我们做好了这一切并引出了所有关键引脚。它提供18个可配置GPIO可被独立设置为8行 x 10列的键盘矩阵引脚。内置去抖和状态保持无需软件处理按键抖动。中断输出与事件队列最大8事件FIFO确保快速连击不丢事件。I2C接口仅需两根线与主控通信。3. 键轴与键帽PCBNeoKey 5x6 Ortho Snap-Apart这不是普通的热插拔轴座。NeoKey PCB的创新之处在于其“可分体”设计。每块PCB是5x6的矩阵但边缘有预定义的断裂孔。你可以轻松地将其掰开成更小的模块如2x3为未来自定义不同布局如40%键盘提供了可能。板上集成了二极管每个按键对应一个1N4148开关二极管这是实现“全键无冲”NKRO的关键防止同一列上多个按键同时按下时产生“鬼影”。2.2 电路连接详解与布线技巧整个系统的电路连接可以分为三个层次矩阵网络、I2C总线、电源。矩阵网络连接核心 每块NeoKey PCB有6行R0-R5和5列C0-C4的焊盘。你需要用30根导线杜邦线或更美观的硅胶线将它们连接到对应的TCA8418引脚上。行线Rows连接PCB的R0-R5焊盘到TCA8418的R0-R5引脚。列线Columns连接PCB的C0-C4焊盘到TCA8418的C0-C4引脚。 这里有一个关键细节TCA8418的引脚定义中R0-R7是行C0-C9是列。在我们的6x5矩阵中我们只用了R0-R5和C0-C4。在代码初始化时我们会明确告知芯片哪些引脚被用作键盘矩阵。I2C总线连接通信 由于两块TCA8418地址相同我们必须使用两个独立的I2C总线。左侧TCA8418通过一根短的STEMMA QT/Qwiic线缆直接插在QT Py RP2040板载的STEMMA QT接口上。这个接口对应的是board.STEMMA_I2C()。右侧TCA8418需要焊接一个STEMMA QT/Qwiic Breakout板到QT Py RP2040的特定引脚SDA(GPIO0),SCL(GPIO1),3V,GND。这个Breakout板提供了另一个STEMMA QT接口用于连接右侧键盘。这个总线对应的是board.I2C()。电源连接 两块TCA8418和QT Py RP2040需要共地。电源3.3V从QT Py RP2040的3V引脚引出分别供给两块TCA8418的VIN引脚。在Breakout板上VIN会通过一个稳压器降到芯片工作电压因此直接接3.3V没问题。实操心得布线规划与焊接颜色编码强烈建议对行线、列线使用不同颜色的导线。例如所有行线用红色所有列线用黑色。这能在后期调试时帮你快速定位问题。线长管理在连接PCB和TCA8418时尽量让导线长度一致并留有余量方便将模块安装到外壳内。可以使用扎带或线缆固定座来整理。焊接顺序先焊接所有PCB上的轴座如果需要然后焊接二极管如果PCB未预装最后再焊接连接TCA8418的导线。焊接TCA8418 Breakout板引脚时注意烙铁温度不要过高建议350°C左右防止损坏芯片或焊盘。通电前检查务必用万用表蜂鸣档检查所有电源3V, GND对地是否短路以及I2C线路之间是否短路。这是避免“烧板”的最重要一步。3. 结构设计与组装打造你的专属外壳3.1 外壳方案选择全3D打印 vs 混合材质项目提供了两种外壳方案各有优劣。全3D打印方案文件需要打印sok_plate定位板x2,sok_top上盖x2,sok_base_L左底壳,sok_baseR右底壳。优点一站式解决只需一台3D打印机。结构一体性强外观一致性高。缺点打印时间较长大面积平面可能因热床不平或收缩产生翘曲。表面质感取决于打印机和材料。打印建议使用PLA材料层高0.2mm填充率10-15%即可保证强度。建议启用“裙边”Brim以防止边角翘起。3D打印激光切割混合方案3D打印件sok_qt(QT Py支架),sok_mid中间框架x2。激光切割件sok_curves曲面面板x2材料可用亚克力、木材甚至金属。优点外观更精致、专业。亚克力面板透光性好方便未来添加RGB底光。材料选择多样。缺点需要接触激光切割机或CNC门槛稍高。组装时需要对孔位更仔细。我个人更推荐混合方案因为亚克力面板的质感和透光性是纯3D打印难以比拟的能让你的作品脱颖而出。3.2 核心组装步骤与避坑指南组装流程遵循从内到外、从框架到细节的原则。1. 热熔螺母Heat-Set Insert的安装这是连接塑料件与金属螺丝最牢固的方式。你需要M3规格的黄铜热熔螺母和一把电烙铁。操作将烙铁头可换上专用平头或锥形头加热至约250-300°C。将热熔螺母放入底壳的预留孔中用烙铁头抵住螺母上端轻轻向下施加压力。关键等待塑料PLA熔化并包裹住螺母的滚花螺纹这个过程大约需要5-10秒。然后保持压力垂直提起烙铁。你会看到熔化的塑料从螺母顶部溢出并形成一圈完美的包覆。切勿加热过久否则塑料会过度熔化导致孔位变形或螺母下沉过深。检查冷却后尝试拧入一颗M3螺丝。应该感觉顺滑但有阻力如果很松或拧不进去说明安装失败可能需要用补土或更大的螺母重试。2. 内部支撑结构与模块固定支撑柱在底壳内部使用M2.5的尼龙支撑柱和螺丝将NeoKey PCB和TCA8418 Breakout板悬空固定。这既提供了稳固的支撑也避免了PCB背面元件与底壳短路的风险。QT Py的安装专门设计的sok_qt支架可以卡住QT Py。务必让QT Py的USB-C口和复位按钮朝向底壳的开孔否则后续更新固件将极其麻烦。线缆管理连接左右半区的长STEMMA QT线缆在穿过外壳中间时可以利用支撑柱作为线缆的走向引导和应力释放点防止频繁插拔导致焊点脱落。3. 键轴与定位板的安装方向统一所有机械轴在插入PCB前确保针脚方向一致通常有商标的一面朝上。同时检查轴体的两个塑料脚是否与定位板Plate的开口匹配MX轴体是十字形。“按摩式”安装将装好轴的定位板对准PCB这是最需要耐心的步骤。不要用蛮力先对齐角落的一个轴轻轻按下听到“咔哒”声表示针脚入座。然后像对角线一样依次按压其他角的轴体。如果感觉某个轴特别紧拔出来检查针脚是否弯曲或孔位是否有异物。可以播放点音乐慢慢来强行按压可能导致PCB焊盘脱落功亏一篑。4. 最终合盖在所有内部连接检查无误后特别是左右半区的I2C线缆盖上顶板用M3螺丝通过之前安装的热熔螺母拧紧。力度适中感觉螺丝吃上劲即可过度拧紧可能导致亚克力开裂或3D打印件滑丝。4. 固件烧录与CircuitPython环境搭建4.1 初识CircuitPython为何选择它对于此类DIY项目CircuitPython简称CPy相比传统的Arduino C/C或QMK固件有着巨大的优势。它是一门基于Python 3的解释型语言在微控制器上运行。其最大特点是“所见即所得”的开发模式当你把支持CPy的板子如QT Py RP2040通过USB连接到电脑后它会显示为一个名为CIRCUITPY的U盘。你只需用任何文本编辑器如VS Code, Mu, 甚至记事本编辑这个盘里的code.py文件保存后代码会自动重启并运行。这消除了编译、刷写固件的步骤调试和迭代速度快得惊人。对于键盘项目Adafruit提供了极其完善的adafruit_hid库可以轻松模拟键盘、鼠标等USB HID设备。我们不需要处理复杂的USB协议栈只需调用kbd.press(Keycode.A)这样的高级接口即可。4.2 详细烧录步骤与故障排除下载UF2固件访问CircuitPython官网找到QT Py RP2040的页面下载最新的.uf2固件文件。进入Bootloader模式确保键盘未连接USB。按住QT Py板上的BOOT按钮通常标有“BOOT”或“BOOTSEL”。保持按住的同时将键盘插入电脑USB口。等待约1-2秒电脑上会出现一个名为RPI-RP2的U盘。此时可以松开BOOT按钮。刷写固件将下载好的.uf2文件例如adafruit_circuitpython_qtpy_rp2040_xx.uf2直接拖入RPI-RP2U盘。U盘会自动弹出稍等片刻一个新的名为CIRCUITPY的U盘会出现。这表明CircuitPython系统已成功刷入。常见问题与排查问题插入USB后没有出现RPI-RP2或CIRCUITPY盘符。排查1检查USB数据线。很多手机充电线只能供电不能传输数据。务必使用一条已知良好的USB数据线。排查2重新进入Bootloader。有时时机没掌握好。可以尝试先按住BOOT键再短按一下RESET键如果板子上有或者直接重新插拔USB。排查3使用“核弹”UF2。如果板子处于一种奇怪的状态可以下载一个特殊的“flash nuke” UF2文件将其拖入RPI-RP2盘。这会彻底清空闪存之后你再重复步骤2、3安装CircuitPython。问题可以识别CIRCUITPY盘但无法编辑里面的文件提示“只读”。解决这通常是因为之前的代码有致命错误导致CircuitPython进入了安全模式。安全模式下系统不会自动运行code.py且磁盘可能只读。要进入安全模式在板子通电或复位后的最初1秒内看到黄色LED闪烁时快速按一下复位键。此时你可以删除或修复有问题的code.py文件然后再次复位即可正常启动。5. 代码全解从矩阵扫描到键位映射5.1 工程文件结构与库依赖将项目压缩包解压后你会得到以下核心文件将它们全部复制到CIRCUITPY根目录code.py主程序入口包含键盘扫描和HID事件发送的核心逻辑。keymaps.py键位映射定义文件所有按键功能都在这里自定义。/lib文件夹存放必要的CircuitPython库文件。必须包含adafruit_tca8418.mpyTCA8418的驱动库。adafruit_hid/包含keyboard.mpy,keycode.mpy等用于USB键盘模拟。库的安装如果lib文件夹里没有这些库你需要手动从Adafruit的CircuitPython库包中获取。访问Adafruit的CircuitPython库页面下载最新的“Library Bundle for Version x.x.x”找到对应的.mpy文件复制过来即可。5.2 核心代码逐行解析让我们深入code.py理解每一部分是如何工作的。import time import board from adafruit_tca8418 import TCA8418 import usb_hid from adafruit_hid.keyboard import Keyboard from adafruit_hid.keycode import Keycode from keymaps import layer_keymaps导入部分除了标准库关键在adafruit_tca8418和adafruit_hid。keymaps是我们自己写的配置文件被作为模块导入。kbd Keyboard(usb_hid.devices) num_layers len(layer_keymaps) current_layer 1初始化HID键盘和层状态创建一个USB键盘设备对象。layer_keymaps是一个包含所有层定义的元组num_layers计算总层数。current_layer初始化为1即默认激活主字母层层索引从0开始但这里1对应主层。i2c_left board.STEMMA_I2C() i2c_right board.I2C() tca_left TCA8418(i2c_left) tca_right TCA8418(i2c_right) tcas (tca_left, tca_right)I2C与TCA8418初始化创建两个I2C对象分别对应板载STEMMA QT接口和GPIO0/1引出的第二个I2C总线。然后用它们初始化两个TCA8418对象并放入元组tcas以便循环处理。KEYPADPINS ( TCA8418.R0, TCA8418.R1, TCA8418.R2, TCA8418.R3, TCA8418.R4, TCA8418.C0, TCA8418.C1, TCA8418.C2, TCA8418.C3, TCA8418.C4, TCA8418.C5 ) for tca in tcas: for pin in KEYPADPINS: tca.keypad_mode[pin] True tca.enable_int[pin] True tca.event_mode_fifo[pin] True tca.key_intenable True配置矩阵引脚KEYPADPINS定义了我们将使用的11个引脚6行R0-R5 5列C0-C4注意代码中只列到R4和C5根据注释应是6行5列这里可能是个笔误实际应为R0-R5和C0-C4。然后对每块TCA8418进行配置keypad_mode[pin] True将该引脚设置为键盘矩阵模式。enable_int[pin] True启用该引脚上的中断。event_mode_fifo[pin] True将该引脚的事件放入FIFO队列。key_intenable True最后全局启用键盘中断。主循环逻辑是代码的核心while True: for i in range(len(tcas)): # 遍历左右两块TCA tca tcas[i] keymap layer_keymaps[current_layer][i] # 获取当前层、当前侧的键位映射 if tca.key_int: # 检查是否有按键事件中断 events tca.events_count # 获取队列中事件数量 for _ in range(events): # 处理所有排队的事件 keyevent tca.next_event # 读取一个事件 keymap_number (keyevent 0x7F) # 低7位是键编号 (modifier, keycode) keymap[keymap_number] # 从映射表获取修饰键和键值 # ... 根据事件类型按下/释放和修饰键执行相应操作 ... tca.key_int True # 清除中断标志 time.sleep(0.01) # 短暂延时降低CPU占用这段代码高效地轮询两块芯片。keyevent的最高位第7位表示是按下1还是释放0。keymap_number是物理按键在矩阵中的编号通过它索引keymaps.py中定义的字典得到对应的(modifier, keycode)元组。5.3 键位映射文件keymaps.py的完全自定义指南keymaps.py是整个键盘的灵魂它决定了你按下某个物理按键时电脑会收到什么指令。其结构是三层嵌套最外层layer_keymaps一个包含所有层的元组。中间层每个层是一个元组包含左半区和右半区两个键位映射字典例如(km_lf_1, km_rt_1)。最内层每个映射字典如km_lf_1其**键Key是TCA8418报告的数字1-46对应矩阵中的每个交叉点其值Value**是一个元组(modifier, keycode)。理解键编号 键编号不是随意的它由TCA8418的硬件扫描顺序决定。通常它按行扫描。对于一个6x5的矩阵编号可能如下分布具体需参考PCB布线第1行: 1, 2, 3, 4, 5, 6第2行: 11, 12, 13, 14, 15, 16第3行: 21, 22, 23, 24, 25, 26...以此类推。 你需要根据自己焊接时行、列线与PCB焊盘的对应关系来最终确定每个物理位置对应的键编号。一个实用的方法是在代码中临时添加打印语句print(keymap_number)然后依次按下每个键记录下输出的数字再更新到keymaps.py中。修饰符Modifier详解0普通按键。直接发送对应的Keycode。1LOWER键。按下后键盘将切换到更低的层如从主层1切换到功能层0。通常用于访问数字键、F键等。2RAISE键。按下后切换到更高的层如从主层1切换到符号层2。通常用于访问标点符号、方向键等。7一次性Shift组合键。这是一个特殊修饰符它会让该键在按下时自动模拟ShiftKeycode。例如将键值设为(7, Keycode.LEFT_BRACKET)那么这个键按下时直接输出的是{假设你的键盘布局下[加Shift是{。如何定义自己的布局 假设你想把左上角第一个键键编号1定义为字母A在主层layer 1的左半区。你需要在km_lf_1字典中添加或修改一行(1) : (0, Keycode.A),如果你想让它作为LOWER层切换键则修改为(1) : (1, None), # 修饰符为1键值无所谓因为不会被发送如果你想让它直接输出大括号{则修改为(1) : (7, Keycode.LEFT_BRACKET), # 修饰符7 左括号键码 {高级技巧实现瞬时层切换Momentary Layer与层锁定Toggle Layer项目默认的层切换是“瞬时”的即你按住LOWER键时键盘处于下层松开则返回主层。如果你想实现“锁定”切换按一下切到下层并保持再按一下返回需要对代码进行修改。一种常见思路是在keymaps.py中为锁定功能定义一个特殊的修饰符例如8然后在code.py的主循环中当检测到这个修饰符时不是改变current_layer而是切换一个标志位并根据这个标志位来决定从哪个层的映射字典中读取键值。这需要更复杂的状态管理但能极大增强键盘的功能性。6. 调试、优化与高级玩法6.1 系统调试与问题排查清单即使按照指南一步步操作第一个DIY键盘也难免遇到问题。下面是一个系统化的排查清单问题现象可能原因排查步骤电脑完全无法识别键盘无USB设备1. QT Py RP2040未正确刷入CircuitPython。2. USB线仅为充电线。3. 主控板硬件损坏。1. 重新进入Bootloader模式检查是否出现RPI-RP2盘符并重刷UF2。2. 更换一条确认可传输数据的USB-C线。3. 尝试给QT Py单独供电并通过串口监视器查看是否有输出。电脑识别为“CIRCUITPY”盘但按键无反应1.code.py或库文件有语法错误导致程序未运行。2. I2C通信失败。3. TCA8418未正确供电或初始化。1. 连接串口监视器如Mu编辑器、PuTTY波特率115200。查看启动时是否有错误信息。2. 在code.py开头添加print(“Hello”)测试程序是否运行。3. 检查I2C连线SDA, SCL是否接反、虚焊。用万用表测量TCA8418的VCC是否为3.3V。4. 尝试在代码中添加I2C扫描打印发现的设备地址。部分按键无反应或反应错乱1. 该按键对应的行/列线虚焊或断路。2. 键位映射keymaps.py中该键的编号定义错误。3. 二极管方向焊反或损坏。1. 使用万用表通断档检查从PCB焊盘到TCA8418对应引脚的线路是否导通。2. 使用前述的“打印键编号”方法确认每个物理按键的实际编号并与keymaps.py核对。3. 检查二极管方向。通常二极管阴极有标记的一头应朝向列线Column。按键出现“鬼键”或串键按A出B1. 矩阵中缺少二极管或二极管失效导致“鬼影”现象。2. 多个按键共用同一行或列的定义在代码中冲突。1.这是最可能的原因。确保每个按键都正确串联了一个二极管且方向一致。用万用表二极管档测试每个二极管是否正常。2. 复查KEYPADPINS定义和PCB实际连线确保没有重复或错误的引脚分配。层切换功能失灵1. 定义为LOWER/RAISE的按键其修饰符1或2设置错误。2.current_layer变量在逻辑中越界。1. 检查keymaps.py中层切换键的修饰符是否为1LOWER或2RAISE。2. 在代码中打印current_layer的值观察按下层切换键时其变化是否正确应在0到num_layers-1之间循环。6.2 性能优化与功能扩展降低功耗当前主循环中有time.sleep(0.01)即10ms的延迟。这对于键盘扫描来说足够快且能降低CPU占用。如果你希望极致的响应速度可以尝试减少到0.0011ms但几乎感知不到区别。更有效的优化是让主控在无事件时进入“休眠”状态这需要配置TCA8418的中断引脚INT并让RP2040进入深度睡眠代码会复杂很多。添加RGB背光NeoKey PCB支持每个按键独立的SK6812 MINI-E RGB LED。你可以通过额外的GPIO引脚控制这些LED。需要在code.py中导入neopixel库并编写LED控制逻辑例如根据当前激活的层改变背光颜色。注意计算总电流可能需要外接电源。添加旋钮或OLED屏幕QT Py RP2040还有多余的GPIO引脚。你可以连接一个旋转编码器作为音量旋钮或者一个小型OLED屏幕来显示层状态、电池电量如果使用电池供电或自定义信息。这需要额外的库和代码但能极大提升键盘的交互性和颜值。实现更复杂的宏与组合键目前的代码处理的是单键修饰符。如果你想实现“按住CtrlAltDel”这样的复杂宏或者“双击CAPS LOCK锁定”这样的高级功能就需要引入状态机来跟踪按键时序和组合状态。这属于进阶玩法可以大大提升工作效率。从理解TCA8418如何将硬件扫描任务化繁为简到亲手焊接每一个二极管和轴体再到在keymaps.py中自由定义属于你自己的输入逻辑整个过程不仅仅是在“组装一个键盘”更是在构建一个完全贴合你双手和思维习惯的输入工具。这种从底层硬件到上层软件完全掌控的感觉是购买任何量产键盘都无法给予的。当你的手指第一次在自己打造的键盘上流畅地敲出代码或文字时那种成就感就是对这个项目最好的回报。希望这份详细的指南能帮你绕开我当年踩过的那些坑更顺畅地完成这件充满创造力的作品。