ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

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

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 与 ARMChibiOS两大平台封装了一套接口统一的 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.cifeq ($(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.h1.2 构建系统如何选择底层实现设置UART_DRIVER_REQUIRED yes后builddefs/common_features.mk 会依据当前平台自动决定链接哪个实现文件平台条件编译选项链接的源文件ChibiOS RP2040 系列-DHAL_USE_SIOTRUEplatforms/chibios/drivers/uart_sio.cChibiOS 其他 MCU如 STM32-DHAL_USE_SERIALTRUEplatforms/chibios/drivers/uart_serial.cAVR无platforms/avr/drivers/uart.c也就是说同一个uart.h头文件背后有三种实现分别对应AVR 原生 USART 驱动、ChibiOS 串行驱动SD与RP2040 的 SIO 驱动。二、AVR 配置无需特殊设置只需接对引脚在 AVR 平台上不需要任何特殊配置——只需将 UART 设备的RX/TX引脚连接到 MCU 的相反引脚即设备的 TX 接 MCU 的 RX设备的 RX 接 MCU 的 TX即可。官方文档给出了各常用 AVR MCU 的 UART 引脚对照表CTS/RTS标注*n/a*表示该 MCU 无对应引脚或当前不支持MCUTXRXCTSRTSATmega16/32U2D3D2D7D6ATmega16/32U4D3D2D5B7AT90USB64/128D3D2n/an/aATmega32AD1D0n/an/aATmega328/PD1D0n/an/a2.1 AVR 实现源码要点从 platforms/avr/drivers/uart.c 可以印证上述引脚差异的底层原因源码通过预处理器按 MCU 型号将寄存器宏重定向到对应的 USART 外设。ATmega16/32U2、ATmega16/32U4、AT90USB64/128 等芯片使用USART1对应宏被重定义为UDR1、UBRR1L、UCSR1A等uart.c#L32-L46ATmega32A 使用USART0命名不带数字后缀见 uart.c#L47-L61ATmega328/P 使用USART0显式重定义为UDR0、UBRR0L等uart.c#L62-L77。uart_init()的波特率计算使用双倍速率U2X模式UBRRnL (F_CPU / 4 / baud - 1) / 2并开启RXEN、TXEN与RXCIE接收中断三个位uart.c#L91-L100。接收与发送采用环形缓冲区实现——RX 缓冲区 64 字节、TX 缓冲区 256 字节uart.c#L79-L88通过USARTn_UDRE_vect发送空中断与USARTn_RX_vect接收中断两个 ISR 驱动因此uart_read()在缓冲区为空时会阻塞等待uart.c#L120-L130uart_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-CSTM32F303板一致config.hOverride说明默认值UART_DRIVER要使用的 USART 外设——USART1 对应SD1USART2 对应SD2以此类推SD1UART_TX_PIN用于 TX 的引脚A9UART_TX_PAL_MODETX 的复用功能Alternate Function模式号7UART_RX_PIN用于 RX 的引脚A10UART_RX_PAL_MODERX 的复用功能模式号7UART_CTS_PIN用于 CTS 的引脚A11UART_CTS_PAL_MODECTS 的复用功能模式号7UART_RTS_PIN用于 RTS 的引脚A12UART_RTS_PAL_MODERTS 的复用功能模式号73.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 库的旧款 MCUUSE_GPIOV1被定义PAL 模式默认值会自动切换为PAL_MODE_ALTERNATE_PUSHPULL发送与PAL_MODE_INPUT接收开发者无需手动指定数字模式号。在uart_init()内部驱动会以palSetLineMode()将 TX/RX 引脚配置为带PAL_MODE_ALTERNATE() | PAL_OUTPUT_TYPE_PUSHPULL | PAL_OUTPUT_SPEED_HIGHEST的复用推挽输出uart_serial.c#L119-L125调用sdStart(UART_DRIVER, serialConfig)启动 ChibiOS 串行驱动其中SerialConfig的波特率被动态设置为传入的bauduart_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 驱动默认外设为SIOD1uart_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-L100ChibiOS 平台将其写入SerialConfig.speed后调用sdStartRP2040 平台写入SIOConfig.baud后调用sioStart。4.2void uart_write(uint8_t data)发送单个字节。参数uint8_t data要写入的字节。实现说明AVR 平台将字节写入发送环形缓冲区并开启发送中断uart.c#L103-L117ChibiOS 平台调用sdPut(UART_DRIVER, data)RP2040 平台调用chnPutTimeout(..., TIME_INFINITE)阻塞直到发送完成。4.3uint8_t uart_read(void)接收单个字节。返回值从接收缓冲区读到的字节。若缓冲区为空无数据可读此函数会阻塞等待直到数据到达。实现说明AVR 平台在rx_buffer_head rx_buffer_tail时自旋等待uart.c#L120-L130RP2040 平台使用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-L136ChibiOS 平台直接调用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-L142ChibiOS 平台调用sdRead(UART_DRIVER, data, length)。4.6bool uart_available(void)返回接收缓冲区是否已有数据。返回值true表示有数据可读。用法在调用uart_read()之前先调用此函数可判断uart_read()是否会立即返回而不阻塞。实现说明AVR 平台通过比较环形缓冲区头尾指针判断uart.c#L147-L154ChibiOS 平台返回!sdGetWouldBlock(UART_DRIVER)RP2040 平台返回!sioIsRXEmptyX(UART_DRIVER)。五、实战示例在 QMK 中调用 UART API5.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 驱动的典型使用模式初始化 → 批量/单字节写入 → 读取响应。六、总结与注意事项启用方式在rules.mk中添加UART_DRIVER_REQUIRED yes或依赖会自动引入 UART 的功能如rn42蓝牙驱动AVR 平台无需配置按 docs/drivers/uart.md 的引脚表接线即可注意 RX 接 TX、TX 接 RXARM 平台先在mcuconf.h启用对应外设如STM32_SERIAL_USE_USART2 TRUE再按需在config.h中覆盖UART_DRIVER及 8 个引脚/复用模式宏默认值对应 Proton-CSTM32F303的SD1/A9/A10初始化约束uart_init()只能调用一次ChibiOS 实现内置了重复调用保护但业务代码仍应保持“一次初始化”的习惯阻塞行为uart_read()/uart_receive()在无数据时会阻塞务必配合uart_available()使用缓冲区大小AVR 实现 RX 缓冲区为 64 字节、TX 缓冲区为 256 字节高吞吐场景需评估是否满足需求流控限制当前驱动版本不支持硬件流控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),仅供参考
返回列表