基于小智派-LZ3863星闪开发版:OpenHarmony UART 外设开发 — UART2 串口通信
1、实验简介
参考网址:https://gitee.com/Lockzhiner-Electronics/lz3863/tree/master/apps/b6_uart2
1.1、实验目的
本实验旨在帮助学习者掌握 OpenHarmony 轻量系统中 UART(Universal Asynchronous Receiver/Transmitter,通用异步收发器) 外设的基本使用方法。通过本实验,你将学会:
- 理解 UART 串口通信的工作原理及帧格式;
- 使用 IoT Hardware 接口配置 GPIO 引脚复用为 UART2 的 TXD/RXD;
- 掌握 UART 中断回调接收模式,实现非阻塞数据接收;
- 实现串口 回声(Echo) 功能,完成数据的接收与回发;
- 完成案例代码的编译、烧录与串口现象观察。
1.2、实验内容
本案例在 LZ3863-星闪开发板上,通过 UART2 接口实现与外部设备(如 PC 端 USB 转串口模块)的双向通信。开发板接收外部发送的数据后,自动将数据以 [Echo] 前缀回发,并在串口输出接收与发送日志。
| 项目 | 说明 |
|---|---|
| UART 端口 | UART2(UART_ID = 2) |
| RXD 引脚 | GPIO7(IOT_IO_FUNC_GPIO_7_UART2_RXD) |
| TXD 引脚 | GPIO8(IOT_IO_FUNC_GPIO_8_UART2_TXD) |
| 波特率 | 115200 |
| 数据格式 | 8 位数据位、1 位停止位、无校验(8N1) |
| 接收模式 | 非阻塞 + 中断回调 |
| 发送模式 | 阻塞发送 |
| 接收缓冲区 | 64 字节(RECV_BUF_SIZE) |
| 初始化入口 | uart2_callback_only_init |
程序在系统启动时通过 APP_FEATURE_INIT 自动完成 UART2 初始化,注册接收中断回调后进入待命状态;当外部设备发送数据时,回调函数自动处理接收、日志打印与回声回发,无需主线程轮询。
1.3、实验环境
| 项目 | 说明 |
|---|---|
| 硬件 | LZ3863-星闪开发板、USB 数据线、USB 转串口模块(或杜邦线连接 PC 串口) |
| 软件 | OpenHarmony v5.1.0 源码、hb 编译工具、串口调试助手 |
| 调试工具 | 串口助手(波特率 115200,8N1) |
| 案例路径 | applications/sample/wifi-iot/app/b6_uart2/ |
2、基础知识
2.1、什么是 UART
UART(通用异步收发器) 是一种常用的 异步串行通信接口,仅需 TXD(发送) 和 RXD(接收) 两根信号线(外加 GND 地线)即可实现设备间的点对点数据传输。
UART 通信的核心特点:
| 概念 | 说明 |
|---|---|
| 异步通信 | 无需共享时钟线,收发双方通过约定的波特率同步 |
| 全双工 | TXD 与 RXD 独立,可同时发送和接收 |
| 帧格式 | 通常包含起始位、数据位、可选校验位、停止位 |
| 点对点 | 两设备间直接连接,不支持多设备总线挂载 |
本实验采用 8N1 格式:8 位数据位、无校验位(None)、1 位停止位,波特率 115200 bps。
2.2、UART 硬件连接
UART 通信采用 交叉连接 方式:
外部设备 TXD ──→ 开发板 RXD(GPIO7 / UART2_RXD)
外部设备 RXD ←── 开发板 TXD(GPIO8 / UART2_TXD)
外部设备 GND ──→ 开发板 GND
注意:UART 为 TTL 电平(通常 3.3 V),请勿直接与 RS-232 电平设备连接,需使用电平转换模块。
2.3、引脚复用(Mux)
WS63 芯片的 GPIO7、GPIO8 可复用为 UART2 的 RXD 和 TXD。使用前必须通过 IoSetFunc() 将引脚功能设置为 UART 模式:
IoSetFunc(IOT_IO_NAME_GPIO_7, IOT_IO_FUNC_GPIO_7_UART2_RXD); // GPIO7 -> UART2_RXD
IoSetFunc(IOT_IO_NAME_GPIO_8, IOT_IO_FUNC_GPIO_8_UART2_TXD); // GPIO8 -> UART2_TXD
2.4、中断回调接收模式
本案例采用 中断回调模式 接收数据,相比轮询方式具有以下优势:
| 对比项 | 轮询模式 | 中断回调模式 |
|---|---|---|
| CPU 占用 | 需持续查询接收缓冲区 | 有数据时才触发处理 |
| 实时性 | 取决于轮询间隔 | 数据到达即响应 |
| 代码结构 | 主循环中反复调用 IoTUartRead() |
注册回调函数,事件驱动 |
工作流程:
外部设备发送数据 → UART 硬件接收 → 触发接收中断 → 调用 uart2_rx_callback()
→ 拷贝数据 → 打印日志 → 回声回发
2.5、软件调用层次
本案例的软件调用层次如下:
应用层(uart2_example.c)
├── IoSetFunc() ← 引脚复用为 UART2
├── IoTUartInit() ← 初始化 UART 通信参数
├── uapi_uart_register_rx_callback() ← 注册接收中断回调
├── uapi_uart_write() ← 发送数据
└── uart2_rx_callback() ← 接收中断回调处理
└── uart2_log() ← 格式化日志输出
└── IoT Hardware / HAL 层
2.6、核心 API 介绍
2.6.1、头文件
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <stdbool.h>
#include "ohos_init.h"
#include "cmsis_os2.h"
#include "iot_gpio.h"
#include "iot_gpio_ex.h"
#include "iot_uart.h"
#include "iot_errno.h"
#include "uart.h"
2.6.2、UART 常用 API
| API 名称 | 功能说明 |
|---|---|
IoSetFunc |
设置引脚复用功能(UART TXD/RXD) |
IoTUartInit |
初始化 UART 端口,配置波特率、数据位等参数 |
IoTUartRead |
主动读取 UART 接收缓冲区数据(轮询模式) |
IoTUartWrite |
向 UART 发送数据(IoT Hardware 层) |
uapi_uart_write |
向 UART 发送数据(驱动层,支持超时控制) |
uapi_uart_register_rx_callback |
注册 UART 接收中断回调函数 |
2.6.3、IoTUartInit — 初始化 UART
unsigned int IoTUartInit(unsigned int id, const IotUartAttribute *param);
| 参数 | 说明 |
|---|---|
id |
UART 端口号,本案例为 2(UART2) |
param |
指向 IotUartAttribute 结构体,配置通信参数 |
IotUartAttribute 结构体主要字段:
| 字段 | 说明 |
|---|---|
baudRate |
波特率,如 115200 |
dataBits |
数据位,如 IOT_UART_DATA_BIT_8 |
stopBits |
停止位,如 IOT_UART_STOP_BIT_1 |
parity |
校验位,如 IOT_UART_PARITY_NONE |
rxBlock |
接收阻塞模式,IOT_UART_BLOCK_STATE_NONE_BLOCK 表示非阻塞 |
txBlock |
发送阻塞模式,IOT_UART_BLOCK_STATE_BLOCK 表示阻塞发送 |
2.6.4、uapi_uart_register_rx_callback — 注册接收回调
errcode_t uapi_uart_register_rx_callback(
uart_bus_t bus,
uart_rx_condition_t condition,
uint16_t threshold,
UartRxCallbackFunc callback
);
| 参数 | 说明 |
|---|---|
bus |
UART 总线编号,本案例为 2 |
condition |
触发条件,本案例为 UART_RX_CONDITION_FULL_OR_IDLE(缓冲区满或空闲时触发) |
threshold |
触发阈值(字节数),本案例为 1(收到 1 字节即触发) |
callback |
接收回调函数指针 |
2.6.5、uapi_uart_write — 发送数据
int uapi_uart_write(uart_bus_t port, const uint8_t *data, uint32_t len, uint32_t timeout);
| 参数 | 说明 |
|---|---|
port |
UART 端口号 |
data |
待发送数据缓冲区指针 |
len |
发送数据长度(字节) |
timeout |
超时时间,本案例使用 1 表示阻塞等待发送完成 |
2.6.6、APP_FEATURE_INIT — 应用入口注册
APP_FEATURE_INIT(uart2_callback_only_init);
该宏将 uart2_callback_only_init 注册为应用特性初始化函数,系统启动完成后自动调用,无需手动修改 main() 函数。
3、程序设计
3.1、程序架构
本案例目录结构:
b6_uart2/
├── uart2_example.c # 主程序源码
├── BUILD.gn # GN 编译配置
├── README_zh.md # 案例简要说明
└── 实验手册.md # 本实验手册
本案例采用 事件驱动 + 中断回调 结构:
系统启动
│
▼
uart2_callback_only_init() ← APP_FEATURE_INIT 注册,系统启动时自动执行
│
▼
uart2_init() ← UART2 初始化
├── IoSetFunc() ← GPIO7/8 复用为 UART2 RXD/TXD
├── IoTUartInit() ← 配置 115200 8N1
├── uapi_uart_register_rx_callback() ← 注册接收回调
└── 输出启动信息 "hello uart2"
│
▼(外部设备发送数据时触发)
uart2_rx_callback() ← 接收中断回调
├── 防重入保护 + 数据拷贝
├── uart2_log() 打印接收日志
├── 构建 "[Echo] xxx" 回发数据
└── uapi_uart_write() 发送回声
3.2、源文件说明
| 文件 | 说明 |
|---|---|
uart2_example.c |
主程序源码,包含 UART2 初始化、接收回调与回声逻辑 |
BUILD.gn |
GN 编译配置文件,定义静态库 uart2_example |
3.3、关键代码分析
(1)宏定义 — UART 核心配置
#define UART_ID 2 // 目标 UART 端口号(UART2)
#define BAUDRATE 115200 // 通信波特率
#define RECV_BUF_SIZE 64 // 接收数据缓冲区大小
#define RX_TRIGGER_THRESHOLD 1 // 接收中断触发阈值(1 字节触发)
#define LOG_BUF_SIZE 128 // 日志格式化缓冲区大小
(2)全局变量 — 状态与缓冲区
static unsigned char g_recv_buf[RECV_BUF_SIZE] = {0}; // 接收数据缓冲区
static volatile bool g_is_rx_processing = false; // 接收处理状态标记(防中断重入)
static volatile bool g_uart2_inited = false; // UART2 初始化完成标记
g_is_rx_processing 用于防止回调函数重入:若上一次接收尚未处理完毕,新的中断将被忽略。
(3)UART2 初始化 — uart2_init
uart2_init 完成引脚复用、UART 参数配置、接收回调注册,并在成功后输出启动信息:
static int uart2_init(void)
{
// 配置 GPIO 引脚功能(复用为 UART2 的 RXD 和 TXD)
IoSetFunc(IOT_IO_NAME_GPIO_7, IOT_IO_FUNC_GPIO_7_UART2_RXD);
IoSetFunc(IOT_IO_NAME_GPIO_8, IOT_IO_FUNC_GPIO_8_UART2_TXD);
// 配置 UART 通信参数(8 位数据位,1 位停止位,无校验位)
IotUartAttribute uart_attr = {
.baudRate = BAUDRATE,
.dataBits = IOT_UART_DATA_BIT_8,
.stopBits = IOT_UART_STOP_BIT_1,
.parity = IOT_UART_PARITY_NONE,
.rxBlock = IOT_UART_BLOCK_STATE_NONE_BLOCK, // 接收非阻塞(中断模式)
.txBlock = IOT_UART_BLOCK_STATE_BLOCK, // 发送阻塞
.pad = 0
};
if (IoTUartInit(UART_ID, &uart_attr) != IOT_SUCCESS)
{
return -1;
}
// 注册接收中断回调函数
errcode_t ret = uapi_uart_register_rx_callback(
UART_ID,
UART_RX_CONDITION_FULL_OR_IDLE,
RX_TRIGGER_THRESHOLD,
uart2_rx_callback
);
if (ret != ERRCODE_SUCC)
{
return -1;
}
g_uart2_inited = true;
uart2_log("UART2 Init Success! Baudrate: %d\n", BAUDRATE);
uapi_uart_write(UART_ID, (uint8_t *)"hello uart2\n", 12, 1);
return 0;
}
(4)接收中断回调 — uart2_rx_callback
当 UART2 接收到数据时,驱动自动调用此回调函数,完成数据拷贝、日志打印与回声回发:
static void uart2_rx_callback(const void *buffer, uint16_t length, bool error)
{
unused(error);
// 防重入保护 + 有效性校验
if (g_is_rx_processing || buffer == NULL || length == 0 || length > RECV_BUF_SIZE)
{
return;
}
g_is_rx_processing = true;
// 拷贝接收数据到本地缓冲区
memcpy(g_recv_buf, buffer, length);
g_recv_buf[length] = '\0';
// 打印接收日志
uart2_log("Recv: Len=%d, Data=%s\n", length, g_recv_buf);
// 构建回发数据
char send_back_buf[RECV_BUF_SIZE + 20] = {0};
snprintf(send_back_buf, sizeof(send_back_buf), "[Echo] %s\n", g_recv_buf);
// 发送回声数据
int send_len = uapi_uart_write(UART_ID, (uint8_t *)send_back_buf, strlen(send_back_buf), 1);
if (send_len > 0)
{
uart2_log("Echo Send: Len=%d, Data=%s", send_len, send_back_buf);
}
else
{
uart2_log("Echo Send Failed! Len: %d\n", send_len);
}
g_is_rx_processing = false;
}
(5)日志打印 — uart2_log
uart2_log 是 UART2 专用日志函数,仅在初始化完成后生效,通过 vsnprintf 格式化后发送到 UART2:
static void uart2_log(const char *format, ...)
{
if (!g_uart2_inited)
return;
va_list args;
va_start(args, format);
char log_buf[LOG_BUF_SIZE] = {0};
vsnprintf(log_buf, LOG_BUF_SIZE - 1, format, args);
va_end(args);
uapi_uart_write(UART_ID, (uint8_t *)log_buf, strlen(log_buf), 1);
}
(6)应用启动入口 — uart2_callback_only_init
系统启动后,通过 APP_FEATURE_INIT 宏自动调用此函数,完成 UART2 初始化:
static void uart2_callback_only_init(void)
{
if (uart2_init() != 0)
{
uapi_uart_write(UART_ID, (uint8_t *)"UART2 Init Failed!\n", 18, 1);
}
}
APP_FEATURE_INIT(uart2_callback_only_init);
3.4、程序执行流程
4、编译步骤
4.1、确认案例目录
确认案例已位于 OpenHarmony 源码目录下:
applications/sample/wifi-iot/app/b6_uart2/
├── uart2_example.c
├── BUILD.gn
├── README_zh.md
└── 实验手册.md
若从外部复制,请将 b6_uart2 目录放到上述 app/ 路径下。
4.2、修改 BUILD.gn(注册编译组件)
编辑 applications/sample/wifi-iot/app/BUILD.gn,在 features 列表中添加本案例:
lite_component("app") {
features = [
"startup",
"b6_uart2:uart2_example", // 添加此行
]
}
4.3、修改 SDK 配置文件
步骤 1:编辑 device/soc/hisilicon/ws63v100/sdk/build/config/target_config/ws63/config.py
找到 'ws63-liteos-app' 配置段,在其 'ram_component' 列表中添加:
"uart2_example"
步骤 2:编辑 device/soc/hisilicon/ws63v100/sdk/libs_url/ws63/cmake/ohos.cmake
找到 "ws63-liteos-app" 对应的 set(COMPONENT_LIST 部分,添加:
"uart2_example"
4.4、执行编译
在 OpenHarmony 源码根目录下执行:
rm -rf out
hb set -root .
# 通过上下方向键选择 ws63 对应的编译分支(如 nearlink_dk_3863 / ws63-liteos-app)
hb build -f
编译成功后,固件输出路径通常在 out/ws63/ 目录下。
4.5、烧录固件
使用开发板配套的烧录工具,将编译生成的固件烧写到 LZ3863-星闪开发板。具体烧录步骤请参考开发板用户手册。
5、运行结果
5.1、硬件连接
将 USB 转串口模块与开发板 UART2 引脚连接:
| USB 转串口模块 | 开发板引脚 | 说明 |
|---|---|---|
| TXD | GPIO7(UART2_RXD) | 模块发送 → 开发板接收 |
| RXD | GPIO8(UART2_TXD) | 开发板发送 → 模块接收 |
| GND | GND | 共地 |
5.2、串口配置
打开 PC 端串口调试助手,配置参数如下:
| 参数 | 值 |
|---|---|
| 波特率 | 115200 |
| 数据位 | 8 |
| 停止位 | 1 |
| 校验位 | 无 |
| 流控 | 无 |
5.3、预期现象
烧录并复位开发板后,串口助手可观察到:
(1)上电初始化输出
hello uart2
UART2 Init Success! Baudrate: 115200
(2)发送数据后的回声响应
在串口助手发送区输入 OpenHarmony 并发送,接收区将显示:
Recv: Len=12, Data=OpenHarmony
[Echo] OpenHarmony
Echo Send: Len=20, Data=[Echo] OpenHarmony
其中:
Recv行为开发板接收日志,显示接收字节数与内容;[Echo] OpenHarmony为开发板回发的回声数据;Echo Send行为回发日志,显示发送字节数与内容。
5.4、结果分析
| 现象 | 说明 |
|---|---|
输出 hello uart2 |
UART2 初始化成功,发送通道正常 |
输出 UART2 Init Success! |
uart2_log() 日志函数工作正常 |
收到 [Echo] xxx |
接收中断回调触发成功,回声逻辑正常 |
| 无任何输出 | 可能引脚连接错误或波特率不匹配 |
| 输出乱码 | 波特率或数据格式配置不一致 |
输出 UART2 Init Failed! |
GPIO 复用或 UART 初始化失败 |
输出 Register UART RX Callback Failed! |
接收回调注册失败 |
5.5、常见问题排查
| 问题 | 可能原因 | 解决方法 |
|---|---|---|
| 完全无串口输出 | 案例未加入编译或烧录了错误固件 | 核对 4.2、4.3 节三处配置,重新编译烧录 |
| 有初始化信息但发送无响应 | TXD/RXD 接反或未共地 | 确认交叉连接:模块 TXD → 开发板 RXD(GPIO7) |
| 接收数据乱码 | 波特率不一致 | 确认双方均为 115200 8N1 |
| 只能收不能发(或反之) | 单根信号线接触不良 | 检查杜邦线连接,测量引脚电压 |
| 快速连续发送丢数据 | 接收缓冲区仅 64 字节 | 降低发送频率,或增大 RECV_BUF_SIZE |
编译报错找不到 uart2_example |
BUILD.gn 或 config.py 未正确修改 | 逐步核对 4.2、4.3 节的配置项 |
6、实验扩展
完成基本实验后,可尝试以下扩展练习:
- 修改波特率:将
BAUDRATE改为9600或921600,观察通信是否正常,理解波特率匹配的重要性; - 轮询接收对比:去掉中断回调,改用
osThreadNew创建线程循环调用IoTUartRead(),对比 CPU 占用与响应延迟; - 协议解析:在回调中解析固定格式的命令(如
LED ON\r\n),结合b1_led_gpio案例控制 LED 亮灭; - 数据透传:将 UART2 接收的数据转发到 UART0(调试串口),实现双串口透传网关;
- 超时组帧:利用
UART_RX_CONDITION_FULL_OR_IDLE的空闲触发特性,实现按帧接收不定长数据包; - 增大缓冲区:修改
RECV_BUF_SIZE为 256 或 512,测试长数据包的接收与回声能力; - 结合传感器实验:配合其他外设案例,将传感器读数通过 UART2 定时上报给上位机。
更多推荐
所有评论(0)