如果说前序课程完成了 GPIO 外设、设备树与驱动模型的基础入门,那么第五课《串行通信(UART)》则是嵌入式开发中数据交互的核心进阶内容。本课以通用异步收发器(UART)为核心,从协议原理、驱动 API 到实操开发,完整讲解 nRF Connect SDK 中 UART 异步(中断驱动)通信的实现方法,最终通过串口指令控制开发板 LED,掌握嵌入式设备与 PC、传感器、外设间最常用的串口通信能力。
本课面向嵌入式固件开发、物联网终端开发工程师,是 nRF Connect SDK 外设开发的必备核心课程,为后续无线通信、复杂外设交互打下基础。
课程定位:掌握嵌入式最通用的串口通信能力
UART 是嵌入式领域无时钟同步、点对点的经典串行通信协议,广泛用于传感器数据读取、调试日志打印、PC 与开发板交互(USB 转 UART)等场景。
本课基于 Zephyr RTOS 驱动框架,聚焦异步中断驱动模式的 UART 开发,摒弃低效轮询方式,实现数据收发的高效、低功耗处理。学完本课,你将具备独立完成 UART 初始化、配置、收发、回调处理的全流程开发能力,适配 Nordic 全系列 DK 开发板。
核心学习目标
完成本课学习后,你将建立 UART 通信的完整知识与开发体系,核心掌握:
- 理解 UART 协议原理、硬件连接、流控制机制
- 掌握 nRF Connect SDK 中 UART 异步驱动 API 的使用
- 完成 UART 波特率、数据位、校验位、停止位等硬件参数配置
- 实现 UART 异步收发数据、中断回调处理
- 通过串口指令完成开发板 LED 控制实操
- 理解 UART 三种访问方式(轮询、中断、异步)的差异
核心知识模块:从协议原理到驱动开发全拆解
本课围绕UART 协议原理 → UART 驱动框架 → 异步 API 开发 → 实操练习四层架构展开,理论与代码一一对应。
模块一:UART 协议原理 —— 硬件通信基础
UART(Universal Asynchronous Receiver/Transmitter)是点对点异步硬件通信协议,通信双方无需时钟线同步,仅通过 TX、RX、GND 三根线完成数据传输。
1. 核心硬件连接
- TX:发送引脚,本地 TX 连接对端 RX
- RX:接收引脚,本地 RX 连接对端 TX
- GND:共地,保证电平参考一致
- Nordic 全系列 DK 板载USB 转 UART芯片,可直接通过 USB 与 PC 串口通信
2. 数据帧格式
UART 数据按帧传输,固定结构:
- 起始位:1 个时钟周期低电平,标志传输开始
- 数据位:通常 8 位,依次传输
- 校验位:可选,用于传输校验(奇校验 / 偶校验 / 无校验)
- 停止位:1 个时钟周期高电平,标志传输结束
3. 关键参数
- 波特率:通信速率,常用 115200、9600 波特,双方必须一致
- 硬件流控制:通过 RTS(请求发送)、CTS(清除发送)信号,解决收发速率不匹配导致的数据丢失
4. UART 硬件流控制(RTS/CTS)
当收发设备处理速度不同时,流控制可避免数据丢失:
- 设备通过 RTS 信号告知对方自身可接收数据
- 设备读取 CTS 信号判断对方可接收数据
- Nordic SoC UART 外设支持自动硬件流控制
模块二:UART 驱动框架 ——Zephyr 三层访问方式
nRF Connect SDK 基于 Zephyr 提供三种 UART 访问方式,本课重点讲解异步 API(Asynchronous API),这是最高效、最常用的方式。
1. 三种访问方式对比
方式 | 特点 | 适用场景 |
轮询 Polling | 非阻塞读、阻塞写,简单低效 | 简单测试、小数据量 |
中断驱动 Interrupt | 中断处理数据,需配合 FIFO | 中等数据量 |
异步 Asynchronous | 基于 EasyDMA,后台收发,支持超时 | 工业级、低功耗、大数据量 |
2. 异步 API 核心优势
- 后台收发数据,不阻塞主线程
- 支持接收超时、缓冲区控制
- 适配 Nordic 芯片 EasyDMA 特性
- 覆盖绝大多数嵌入式 UART 场景
模块三:UART 异步驱动 API 开发全流程
本课所有代码基于 nRF Connect SDK v3.0.0 及以上,兼容 nRF52/nRF53/nRF54/nRF7002 DK。
1. 开启 UART 驱动(prj.conf)
在工程配置文件中开启串口驱动与异步 API:
CONFIG_SERIAL=y
CONFIG_UART_ASYNC_API=y
- CONFIG_SERIAL:板级设备树默认开启
- CONFIG_UART_ASYNC_API:必须手动开启异步 API
2. 头文件引入
在 main.c 中引入 UART 驱动头文件:
#include <zephyr/drivers/uart.h>
3. 获取 UART 设备指针
通过设备树宏获取 UART 硬件实例,nRF54 系列使用 uart20,其余使用 uart0:
// 通用 DK
const struct device *uart = DEVICE_DT_GET(DT_NODELABEL(uart0));
// nRF54 系列 DK
const struct device *uart = DEVICE_DT_GET(DT_NODELABEL(uart20));
// 检查设备就绪
if (!device_is_ready(uart)) {
printk("UART device not ready\r\n");
return 1;
}
4. UART 动态配置
通过 uart_config 结构体配置波特率、校验位、停止位等参数:
const struct uart_config uart_cfg = {
.baudrate = 115200,
.parity = UART_CFG_PARITY_NONE,
.stop_bits = UART_CFG_STOP_BITS_1,
.data_bits = UART_CFG_DATA_BITS_8,
.flow_ctrl = UART_CFG_FLOW_CTRL_NONE
};
// 应用配置
int err = uart_configure(uart, &uart_cfg);
if (err == -ENOSYS) {
return -ENOSYS;
}
5. UART 回调函数(ISR)定义
异步模式通过回调函数处理收发事件,回调优先级高于所有线程,需保持简洁:
static void uart_cb(const struct device *dev, struct uart_event *evt, void *user_data)
{
switch (evt->type) {
// 发送完成
case UART_TX_DONE:
break;
// 接收数据就绪
case UART_RX_RDY:
break;
// 接收关闭,重新开启
case UART_RX_DISABLED:
uart_rx_enable(dev, rx_buf, sizeof(rx_buf), RECEIVE_TIMEOUT);
break;
default:
break;
}
}
常用 UART 事件
事件 | 含义 |
UART_TX_DONE | 发送缓冲区数据全部发送完成 |
UART_TX_ABORTED | 发送被中止 |
UART_RX_RDY | 接收数据就绪(超时 / 缓冲区满) |
UART_RX_DISABLED | 接收关闭,需重新使能 |
6. 注册回调函数
err = uart_callback_set(uart, uart_cb, NULL);
if (err) {
return 1;
}
7. UART 数据发送
定义发送缓冲区,调用 uart_tx 发送数据:
// 发送缓冲区
static uint8_t tx_buf[] = {"nRF Connect SDK Fundamentals\r\n"};
// 启动发送(无流控制,永久超时)
err = uart_tx(uart, tx_buf, sizeof(tx_buf), SYS_FOREVER_US);
if (err) {
return 1;
}
8. UART 数据接收
- 定义接收缓冲区与超时
#define RECEIVE_BUFF_SIZE 10
#define RECEIVE_TIMEOUT 100 // 微秒
static uint8_t rx_buf[RECEIVE_BUFF_SIZE] = {0};
- 使能接收
err = uart_rx_enable(uart, rx_buf, sizeof(rx_buf), RECEIVE_TIMEOUT);
if (err) {
return 1;
}
- 接收数据读取(UART_RX_RDY 事件中)
case UART_RX_RDY:
// 数据长度
uint8_t len = evt->data.rx.len;
// 数据偏移
uint8_t offset = evt->data.rx.offset;
// 读取数据
uint8_t data = evt->data.rx.buf[offset];
break;
模块四:实操练习 ——UART 控制 LED
本课实操基于 Blinky 示例,通过串口发送指令(1/2/3)翻转对应 LED,不支持 Thingy 系列设备。
1. 实操目标
- 初始化 UART 异步通信
- 开机发送欢迎信息
- 串口发送 1/2/3 控制 LED 翻转
- 实现连续接收
2. LED 设备获取与配置
// 获取 LED 设备树实例
static const struct gpio_dt_spec led0 = GPIO_DT_SPEC_GET(DT_ALIAS(led0), gpios);
static const struct gpio_dt_spec led1 = GPIO_DT_SPEC_GET(DT_ALIAS(led1), gpios);
static const struct gpio_dt_spec led2 = GPIO_DT_SPEC_GET(DT_ALIAS(led2), gpios);
// 配置 LED 为输出
gpio_pin_configure_dt(&led0, GPIO_OUTPUT_ACTIVE);
gpio_pin_configure_dt(&led1, GPIO_OUTPUT_ACTIVE);
gpio_pin_configure_dt(&led2, GPIO_OUTPUT_ACTIVE);
3. 优化 UART 回调函数(LED 控制)
static void uart_cb(const struct device *dev, struct uart_event *evt, void *user_data)
{
switch (evt->type) {
case UART_RX_RDY:
if (evt->data.rx.len == 1) {
// 接收 1 翻转 LED0
if (evt->data.rx.buf[evt->data.rx.offset] == '1') {
gpio_pin_toggle_dt(&led0);
}
// 接收 2 翻转 LED1
else if (evt->data.rx.buf[evt->data.rx.offset] == '2') {
gpio_pin_toggle_dt(&led1);
}
// 接收 3 翻转 LED2
else if (evt->data.rx.buf[evt->data.rx.offset] == '3') {
gpio_pin_toggle_dt(&led2);
}
}
break;
case UART_RX_DISABLED:
// 重新使能接收,实现连续接收
uart_rx_enable(dev, rx_buf, sizeof(rx_buf), RECEIVE_TIMEOUT);
break;
default:
break;
}
}
4. 编译与测试
- 编译工程并烧录到 DK
- 打开串口终端(波特率 115200)
- 终端显示欢迎信息
- 键盘输入 1/2/3,对应 LED 翻转
5. 注意事项
- nRF7002 DK 仅 2 个 LED,指令为 1/2
- nRF54 DK LED 为 0 索引,指令为 0/1/2
- 串口终端建议使用字符模式,避免发送回车换行符
课后测评
为了巩固本课核心知识, Nordic 官方设置了针对性测评,帮助你验证对 UART 异步通信及驱动 API 的掌握程度,测评链接如下:
测评核心考察点
- UART 协议基础:包括数据帧格式、关键参数(波特率、校验位、流控制)的理解,判断不同参数配置对通信的影响。
- 异步 API 应用:考查 UART 异步模式的核心优势、回调函数的使用场景、uart_rx_enable 与 uart_tx 函数的参数含义及错误处理。
- 实操场景落地:结合 LED 控制实操,考查串口指令解析、LED 翻转逻辑实现的代码逻辑,以及不同开发板(如 nRF7002、nRF54)的适配注意事项。
完成测评后,可清晰知晓自身知识薄弱点,针对性复盘本课内容,为后续学习高级通信外设(如 I2C、SPI 及无线通信协议)筑牢基础。
本课学习价值
第五课是 nRF Connect SDK 外设开发的关键承上启下课程:
-
巩固设备树、驱动模型、GPIO 操作等前序知识
-
掌握嵌入式最通用的 UART 异步通信开发
-
建立中断驱动、低功耗、异步处理的开发思维
-
为后续 I2C、SPI、无线通信开发打下框架基础