1、实验简介

参考网址:https://gitee.com/Lockzhiner-Electronics/lz3863/tree/master/apps/b6_uart2

1.1、实验目的

本实验旨在帮助学习者掌握 OpenHarmony 轻量系统中 UART(Universal Asynchronous Receiver/Transmitter,通用异步收发器) 外设的基本使用方法。通过本实验,你将学会:

  1. 理解 UART 串口通信的工作原理及帧格式;
  2. 使用 IoT Hardware 接口配置 GPIO 引脚复用为 UART2 的 TXD/RXD;
  3. 掌握 UART 中断回调接收模式,实现非阻塞数据接收;
  4. 实现串口 回声(Echo) 功能,完成数据的接收与回发;
  5. 完成案例代码的编译、烧录与串口现象观察。

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、程序执行流程

外部设备(PC) uart2_rx_callback UART2 硬件 uart2_callback_only_init 系统启动 外部设备(PC) uart2_rx_callback UART2 硬件 uart2_callback_only_init 系统启动 APP_FEATURE_INIT 触发 IoSetFunc(GPIO7/8) + IoTUartInit() uapi_uart_register_rx_callback() "hello uart2" + 初始化成功日志 发送数据 "OpenHarmony" 触发接收中断回调 拷贝数据 + 打印 Recv 日志 uapi_uart_write("[Echo] OpenHarmony") 回发回声数据 打印 Echo Send 日志

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、实验扩展

完成基本实验后,可尝试以下扩展练习:

  1. 修改波特率:将 BAUDRATE 改为 9600921600,观察通信是否正常,理解波特率匹配的重要性;
  2. 轮询接收对比:去掉中断回调,改用 osThreadNew 创建线程循环调用 IoTUartRead(),对比 CPU 占用与响应延迟;
  3. 协议解析:在回调中解析固定格式的命令(如 LED ON\r\n),结合 b1_led_gpio 案例控制 LED 亮灭;
  4. 数据透传:将 UART2 接收的数据转发到 UART0(调试串口),实现双串口透传网关;
  5. 超时组帧:利用 UART_RX_CONDITION_FULL_OR_IDLE 的空闲触发特性,实现按帧接收不定长数据包;
  6. 增大缓冲区:修改 RECV_BUF_SIZE 为 256 或 512,测试长数据包的接收与回声能力;
  7. 结合传感器实验:配合其他外设案例,将传感器读数通过 UART2 定时上报给上位机。
Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐