news 2026/9/14 6:29:49

QMK 固件 UART 驱动完全指南:跨 AVR/ARM 平台的串口通信配置与 API 实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
QMK 固件 UART 驱动完全指南:跨 AVR/ARM 平台的串口通信配置与 API 实战

QMK 固件 UART 驱动完全指南:跨 AVR/ARM 平台的串口通信配置与 API 实战

【免费下载链接】qmk_firmwareOpen-source keyboard firmware for Atmel AVR and Arm USB families项目地址: https://gitcode.com/GitHub_Trending/qm/qmk_firmware

导读

UART(通用异步收发器)是键盘固件与外部设备(如蓝牙模块、传感器、调试工具)进行串行通信的经典方式。QMK 固件为 AVR 与 ARM(ChibiOS)两大平台封装了一套接口统一的 UART 驱动,让开发者无需关心底层寄存器差异即可收发字节流。本文以官方驱动文档为主体,结合本仓库的 drivers/uart.h 头文件与 AVR、ChibiOS 两套平台实现源码,完整讲解 UART 驱动的启用方式、AVR 引脚接线、ARM 外设配置以及全部 6 个 API 函数的用法与底层原理,读完即可在自己的键盘固件中独立接入 UART 设备。


一、UART 驱动概览与使用前提

QMK 中的 UART 驱动为不同 MCU(单片机)提供了一组通用函数,以实现跨平台的代码可移植性——你写的业务代码调用的是同一套 API,而底层由各平台的实现负责适配。

需要特别说明的一个限制是:当前版本的驱动尚不支持启用硬件流控(RTS/CTS引脚),尽管部分 MCU 硬件上具备该能力,未来版本可能会加入支持。这一点在 drivers/uart.h 与两份平台实现中均有体现(配置项默认值中虽定义了 CTS/RTS 引脚,但实际并未参与收发逻辑)。

1.1 自动引入与手动启用

在大多数情况下,只要你使用的某个功能或驱动依赖 UART,驱动代码会被自动包含到编译中。例如在 builddefs/common_features.mk 中可以看到,当选择rn42蓝牙驱动时,构建系统会自动置位UART_DRIVER_REQUIRED = yes并引入rn42.c

ifeq ($(strip $(BLUETOOTH_DRIVER)), rn42) UART_DRIVER_REQUIRED = yes SRC += $(DRIVER_PATH)/bluetooth/bluetooth_drivers.c SRC += $(DRIVER_PATH)/bluetooth/rn42.c endif

如果你需要独立使用UART 驱动(例如自己编写与外部设备通信的代码),则需在键盘或 keymap 的rules.mk中显式声明:

UART_DRIVER_REQUIRED = yes

随后在你的代码中引入头文件即可调用全部 API:

#include "uart.h"

1.2 构建系统如何选择底层实现

设置UART_DRIVER_REQUIRED = yes后,builddefs/common_features.mk 会依据当前平台自动决定链接哪个实现文件:

平台条件编译选项链接的源文件
ChibiOS + RP2040 系列-DHAL_USE_SIO=TRUEplatforms/chibios/drivers/uart_sio.c
ChibiOS + 其他 MCU(如 STM32)-DHAL_USE_SERIAL=TRUEplatforms/chibios/drivers/uart_serial.c
AVRplatforms/avr/drivers/uart.c

也就是说,同一个uart.h头文件背后有三种实现,分别对应AVR 原生 USART 驱动ChibiOS 串行驱动(SDRP2040 的 SIO 驱动


二、AVR 配置:无需特殊设置,只需接对引脚

在 AVR 平台上,不需要任何特殊配置——只需将 UART 设备的RX/TX引脚连接到 MCU 的相反引脚(即设备的 TX 接 MCU 的 RX,设备的 RX 接 MCU 的 TX)即可。

官方文档给出了各常用 AVR MCU 的 UART 引脚对照表(CTS/RTS标注*n/a*表示该 MCU 无对应引脚或当前不支持):

MCUTXRXCTSRTS
ATmega16/32U2D3D2D7D6
ATmega16/32U4D3D2D5B7
AT90USB64/128D3D2n/an/a
ATmega32AD1D0n/an/a
ATmega328/PD1D0n/an/a

2.1 AVR 实现源码要点

从 platforms/avr/drivers/uart.c 可以印证上述引脚差异的底层原因:源码通过预处理器按 MCU 型号将寄存器宏重定向到对应的 USART 外设。

  • ATmega16/32U2、ATmega16/32U4、AT90USB64/128 等芯片使用USART1,对应宏被重定义为UDR1UBRR1LUCSR1A等(uart.c#L32-L46);
  • ATmega32A 使用USART0(命名不带数字后缀,见 uart.c#L47-L61);
  • ATmega328/P 使用USART0,显式重定义为UDR0UBRR0L等(uart.c#L62-L77)。

uart_init()的波特率计算使用双倍速率(U2X)模式UBRRnL = (F_CPU / 4 / baud - 1) / 2,并开启RXENTXENRXCIE(接收中断)三个位(uart.c#L91-L100)。接收与发送采用环形缓冲区实现——RX 缓冲区 64 字节、TX 缓冲区 256 字节(uart.c#L79-L88),通过USARTn_UDRE_vect(发送空中断)与USARTn_RX_vect(接收中断)两个 ISR 驱动,因此uart_read()在缓冲区为空时会阻塞等待(uart.c#L120-L130),uart_available()则通过比较头尾指针判断缓冲区是否有数据(uart.c#L147-L154)。

注意:以上针对的是UART_DRIVER_REQUIRED = yes且未使用 ChibiOS 的情况。若使用 ChibiOS 平台,请参考下一节。


三、ChibiOS / ARM 配置:启用外设并设置引脚复用

ARM 平台(以 STM32 为代表)通常有多个 UART 外设,例如 USART1、USART2、USART3……你需要在板级配置中明确启用选定的外设。

3.1 在mcuconf.h中启用外设

修改你所用板子的mcuconf.h,将所选外设从禁用改为启用。以 USART2 为例:

#pragma once #include_next <mcuconf.h> #undef STM32_SERIAL_USE_USART2 // [!code focus] #define STM32_SERIAL_USE_USART2 TRUE // [!code focus]

3.2 通过config.h覆盖引脚与外设(默认值为 Proton-C 即 STM32F303)

配置层面,你需要按照所选 MCU 的数据手册来设定外设参数。官方文档给出的默认值与Proton-C(STM32F303)板一致:

config.hOverride说明默认值
UART_DRIVER要使用的 USART 外设——USART1 对应SD1,USART2 对应SD2,以此类推SD1
UART_TX_PIN用于 TX 的引脚A9
UART_TX_PAL_MODETX 的复用功能(Alternate Function)模式号7
UART_RX_PIN用于 RX 的引脚A10
UART_RX_PAL_MODERX 的复用功能模式号7
UART_CTS_PIN用于 CTS 的引脚A11
UART_CTS_PAL_MODECTS 的复用功能模式号7
UART_RTS_PIN用于 RTS 的引脚A12
UART_RTS_PAL_MODERTS 的复用功能模式号7

3.3 实现源码对默认值的印证

platforms/chibios/drivers/uart_serial.c 中可以看到这些默认值正是通过#ifndef逐一定义的(uart_serial.c#L10-L60):

#ifndef UART_DRIVER # define UART_DRIVER SD1 #endif #ifndef UART_TX_PIN # define UART_TX_PIN A9 #endif #ifndef UART_TX_PAL_MODE # ifdef USE_GPIOV1 # define UART_TX_PAL_MODE PAL_MODE_ALTERNATE_PUSHPULL # else # define UART_TX_PAL_MODE 7 # endif #endif // ... 其余引脚依此类推

值得注意的是,对于使用GPIOv1 库的旧款 MCU(USE_GPIOV1被定义),PAL 模式默认值会自动切换为PAL_MODE_ALTERNATE_PUSHPULL(发送)与PAL_MODE_INPUT(接收),开发者无需手动指定数字模式号。

uart_init()内部,驱动会:

  1. palSetLineMode()将 TX/RX 引脚配置为带PAL_MODE_ALTERNATE() | PAL_OUTPUT_TYPE_PUSHPULL | PAL_OUTPUT_SPEED_HIGHEST的复用推挽输出(uart_serial.c#L119-L125);
  2. 调用sdStart(&UART_DRIVER, &serialConfig)启动 ChibiOS 串行驱动,其中SerialConfig的波特率被动态设置为传入的baud(uart_serial.c#L105-L128)。

此外,uart_init()内部有static bool is_initialised保护,重复调用会被直接忽略,确保驱动只初始化一次(uart_serial.c#L106-L111)。

3.4 RP2040 的特殊路径

当使用 RP2040 芯片时,构建系统会改用 platforms/chibios/drivers/uart_sio.c。该实现使用 ChibiOS 的SIO 驱动:默认外设为SIOD1(uart_sio.c#L10-L12),默认配置为“38400 波特率、8 位数据位、1 位停止位、无校验、无流控”(uart_sio.c#L74-L82),并通过chnPutTimeout/chnGetTimeout/chnRead/chnWrite实现收发。读取时会额外检查并清除 RX 错误标志(uart_sio.c#L120-L140)。


四、UART API 全解:6 个函数逐一拆解

所有 API 的声明集中在 drivers/uart.h,对应不同平台的三份实现行为一致。以下是完整 API 参考(与官方文档一致,并补充实现细节)。

4.1void uart_init(uint32_t baud)

初始化 UART 驱动。此函数必须且只能调用一次,并且必须先于下面所有函数调用。

  • 参数uint32_t baud:收发波特率,取决于你要通信的设备。常用值:1200、2400、4800、9600、19200、38400、57600、115200
  • 实现说明:AVR 平台据此计算波特率寄存器并开启收发与接收中断(uart.c#L91-L100);ChibiOS 平台将其写入SerialConfig.speed后调用sdStart;RP2040 平台写入SIOConfig.baud后调用sioStart

4.2void uart_write(uint8_t data)

发送单个字节。

  • 参数uint8_t data:要写入的字节。
  • 实现说明:AVR 平台将字节写入发送环形缓冲区并开启发送中断(uart.c#L103-L117);ChibiOS 平台调用sdPut(&UART_DRIVER, data);RP2040 平台调用chnPutTimeout(..., TIME_INFINITE)阻塞直到发送完成。

4.3uint8_t uart_read(void)

接收单个字节。

  • 返回值:从接收缓冲区读到的字节。若缓冲区为空(无数据可读),此函数会阻塞等待,直到数据到达。
  • 实现说明:AVR 平台在rx_buffer_head == rx_buffer_tail时自旋等待(uart.c#L120-L130);RP2040 平台使用chnGetTimeout(..., TIME_INFINITE)并清除 RX 错误标志。

4.4void uart_transmit(const uint8_t *data, uint16_t length)

发送多个字节。

  • 参数const uint8_t *data:指向待发送数据的指针。
  • 参数uint16_t length:要发送的字节数。注意不要越界访问data数组
  • 实现说明:AVR 平台循环调用uart_write()(uart.c#L132-L136);ChibiOS 平台直接调用sdWrite(&UART_DRIVER, data, length)一次完成批量发送。

4.5void uart_receive(uint8_t *data, uint16_t length)

接收多个字节。

  • 参数uint8_t *data:指向接收缓冲区的指针(注意头文件签名中为uint8_t *,官方文档此处写作char *属文档笔误,以 drivers/uart.h 为准)。
  • 参数uint16_t length:要读取的字节数。同样注意不要越界
  • 实现说明:AVR 平台循环调用uart_read()(uart.c#L138-L142);ChibiOS 平台调用sdRead(&UART_DRIVER, data, length)

4.6bool uart_available(void)

返回接收缓冲区是否已有数据。

  • 返回值:true表示有数据可读。
  • 用法:在调用uart_read()之前先调用此函数,可判断uart_read()是否会立即返回而不阻塞。
  • 实现说明:AVR 平台通过比较环形缓冲区头尾指针判断(uart.c#L147-L154);ChibiOS 平台返回!sdGetWouldBlock(&UART_DRIVER);RP2040 平台返回!sioIsRXEmptyX(&UART_DRIVER)

五、实战示例:在 QMK 中调用 UART API

5.1 标准调用流程

一个典型的 UART 使用流程如下(例如在键盘的自定义代码或 feature 中):

#include "uart.h" void my_uart_setup(void) { uart_init(115200); // 只初始化一次,选择设备支持的波特率 } void my_uart_loop(void) { if (uart_available()) { // 先检查是否有数据,避免阻塞 uint8_t byte = uart_read(); // 处理接收到的字节... } uart_write(0x55); // 发送单字节 const uint8_t buf[] = {0x01, 0x02, 0x03}; uart_transmit(buf, sizeof(buf)); // 批量发送 }

5.2 仓库内的真实用例:RN42 蓝牙模块

本仓库的 drivers/bluetooth/rn42.c 是一个完整的 UART 驱动实战范例——RN42 蓝牙模块正是通过 UART 与主控通信的。可以看到它:

  • 调用uart_init(RN42_BAUD_RATE)完成初始化(rn42.c#L67);
  • 使用uart_write()逐字节发送 HID 报告(修饰键、按键码等),例如发送按键报告时的多字节写入(rn42.c#L71-L88)。

这验证了 UART 驱动的典型使用模式:初始化 → 批量/单字节写入 → 读取响应


六、总结与注意事项

  1. 启用方式:在rules.mk中添加UART_DRIVER_REQUIRED = yes,或依赖会自动引入 UART 的功能(如rn42蓝牙驱动);
  2. AVR 平台:无需配置,按 docs/drivers/uart.md 的引脚表接线即可(注意 RX 接 TX、TX 接 RX);
  3. ARM 平台:先在mcuconf.h启用对应外设(如STM32_SERIAL_USE_USART2 TRUE),再按需在config.h中覆盖UART_DRIVER及 8 个引脚/复用模式宏,默认值对应 Proton-C(STM32F303)的SD1/A9/A10
  4. 初始化约束uart_init()只能调用一次,ChibiOS 实现内置了重复调用保护,但业务代码仍应保持“一次初始化”的习惯;
  5. 阻塞行为uart_read()/uart_receive()在无数据时会阻塞,务必配合uart_available()使用;
  6. 缓冲区大小:AVR 实现 RX 缓冲区为 64 字节、TX 缓冲区为 256 字节,高吞吐场景需评估是否满足需求;
  7. 流控限制:当前驱动版本不支持硬件流控(RTS/CTS),配置宏仍保留有默认引脚值但未参与实际收发逻辑,使用前请确认你的应用不依赖流控。

若需了解 UART 在蓝牙等具体场景中的应用,可进一步阅读 drivers/bluetooth/rn42.c 与 builddefs/common_features.mk 中与UART_DRIVER_REQUIRED相关的构建逻辑。

【免费下载链接】qmk_firmwareOpen-source keyboard firmware for Atmel AVR and Arm USB families项目地址: https://gitcode.com/GitHub_Trending/qm/qmk_firmware

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/14 6:28:55

Mac SSH客户端termcc深度体验:串口调试与密钥认证全攻略

搞了十几年网络设备和服务器运维&#xff0c;我对Mac上的SSH客户端工具一直有种“找不到趁手家伙”的无力感。Windows时代有SecureCRT、Xshell&#xff0c;切换设备、保存会话都很顺手&#xff0c;但到了Mac上&#xff0c;要么是iTerm2配命令行&#xff0c;要么是各种重型的现代…

作者头像 李华
网站建设 2026/9/14 6:27:55

大模型隐私数据删除技术:PrivacyScalpel原理与实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/14 6:25:37

Zulip 升级失败后如何回滚到之前的版本

Zulip 升级失败后如何回滚到之前的版本 【免费下载链接】zulip Zulip server and web application. Open-source team chat that helps teams stay productive and focused. 项目地址: https://gitcode.com/GitHub_Trending/zu/zulip 在自托管的 Zulip 服务器上执行 upg…

作者头像 李华
网站建设 2026/9/14 6:24:07

自然研学活动设计:色彩与气味的感官教育实践

1. 六亩半田埂上的研学构想在乌鲁木齐城郊一片六亩半的农田边缘&#xff0c;我萌生了做一场特殊研学活动的念头。这片田地不算大&#xff0c;但足够让孩子们奔跑嬉戏&#xff1b;不算规整&#xff0c;却正好保留着最自然的农耕肌理。每当春风吹过&#xff0c;田埂上的野花就摇曳…

作者头像 李华
网站建设 2026/9/14 6:23:57

OpenSpec+Superpowers实现契约驱动开发工作流

1. 这不是又一个“AI工作流”概念秀&#xff0c;而是真正能落地的工程化协作范式 OpenSpec Superpowers 搭建 SDDTDD 工作流——光看标题&#xff0c;很多人第一反应是&#xff1a;“又是套新词包装的老东西&#xff1f;”但如果你真花30分钟跑通这个组合&#xff0c;会发现它…

作者头像 李华