Skip to content

概述

UART 是最常用的串行通信接口,用于与电脑、传感器、GPS 模块等设备通信。Ai-WB2 模组内置 2 组 UART(UART0 和 UART1):UART0 为系统日志口(blog_info 等日志固定由 UART0 输出),UART1 可自由用作通信串口。本教程实现 UART1 接收数据并原样回显(回环)

用大白话讲:UART(串口)就像开发板和电脑之间的一根「电话线」,两边按同样的速度逐字说话(发一个字节、听一个字节)。本教程的「回环」就是让开发板当复读机:电脑发什么,它原样回什么,就像对着山谷喊话听到回声。

本教程基于安信可官方 SDKAi-Thinker-Open/Ai-Thinker-WB2,版本 release_bl_iot_sdk_1.6.40)的官方示例 applications/peripherals/uart 编写,代码可在本地 SDK 中直接找到。

🎯本页目标通过 UART1(TX GPIO16 / RX GPIO7)实现串口回环:接收电脑发来的数据并原样发回,掌握 UART 初始化、接收与发送 API。
🧰前置条件① Ai-WB2 开发板、USB-TTL 串口模块、杜邦线 ② 已按 [SDK 安装](../sdk/sdk_intro) 完成环境搭建。
🔗相关章节日志输出见 [系统控制-日志系统](../system/blog_log);串口接线与日志口区别见本文说明。

硬件接线

按官方示例接线(见 SDK applications/peripherals/uart/README.md),通信串口为 UART1:TX GPIO16、RX GPIO7,与 USB-TTL 模块交叉连接

Ai-WB2 引脚 USB-TTL 模块
IO16(UART1 TX) RXD
IO7(UART1 RX) TXD
GND GND

💡 串口收发必须交叉连接(TX 接对方 RX,就像打电话要对着话筒说话);同时务必共地(GND 接 GND,否则电压没有参考点,通信会乱码)。USB-TTL 模块的 VCC 可接 3V3 供电,也可单独用 Type-C 给开发板供电。

📌 注意区分:开发板 USB 口直连电脑时看到的 log 是 UART0(log 口,TX GPIO4 / RX GPIO3,固定输出系统日志);本教程的 UART1 需要额外用 USB-TTL 模块引出。

进入示例工程

打开终端,进入官方 uart 示例工程目录:

cd ~/Ai-Thinker-WB2/applications/peripherals/uart

说明:cd 是「进入目录」命令,这里进入 uart 示例工程目录;后续的 make 编译、make flash 烧录命令都必须先在这个目录里执行。

编写代码

打开 uart/main.c,本步完整代码已移至文末,见:

📜 完整代码 — 位于本页「完整代码」章节,默认折叠,点击展开,与官方示例(applications/peripherals/uart/uart/main.c)完全一致。

代码要点:

代码 作用
uart_id = 1tx_pin = 16rx_pin = 7 通信串口 UART1(TX16/RX7),波特率(传输速度,两端必须一致)115200;引脚或波特率配错就收不到数据
uart_id = 0tx_pin = 4rx_pin = 3 系统日志口 UART0blog_info 固定由此输出,别把通信数据接到这里
mode = HOSAL_UART_MODE_POLL 轮询模式:hosal_uart_receive 会一直等数据,等不到就卡住不往下走
hosal_uart_receive(&uart_dev_log, data, sizeof(data)) 接收电脑发来的数据,返回实际收到几个字节,收不到(返回 0)就不会回显
hosal_uart_send(&uart_dev_log, data, ret) 把收到的数据原样发回(回环),没有这步电脑端就看不到任何回显
编译工程

在工程目录执行编译:

make -j8

说明:make 是「编译工程」命令,把代码变成开发板能运行的固件(程序文件);-j8 表示用 8 个核并行编译,速度更快。

编译成功后生成固件 build_out/uart.bin

烧录固件

开发板保持 USB 连接,确认串口设备号后执行烧录:

make flash p=/dev/ttyUSB0 b=921600

说明:make flash 是「烧录」命令,把编译好的固件写进开发板芯片。p= 后面是串口设备号(改成你电脑上实际的串口,可用 ls /dev/ttyUSB* 查看),b= 是烧录波特率(传输速度)。

⏳ 烧录过程中按提示长按开发板 EN 键进入下载模式,等待进度条完成即烧录成功。

运行验证

烧录完成后开发板自动重启运行:

  1. 打开两个串口工具:USB 直连口(UART0,波特率 921600,看系统日志)与 USB-TTL 连接的 UART1(波特率 115200,通信测试)。
  2. UART0 日志口输出 Uart Demo Start
  3. 在 USB-TTL 的串口工具里发送任意字符(如 hello),UART1 会原样回显,同时 UART0 日志口打印 Get data
[UART0 日志口]
Uart Demo Start
Get data
Get data

[UART1 通信口]
hello      ← 发送
hello      ← 回显

💡 示例波特率分别为 115200(UART1)与 9600(uart_dev_echo,本示例未实际使用),收发两端波特率(串口传输速度,两端必须设成一样)必须一致,否则收到乱码。

看到 USB-TTL 发出 hello 后原样回显 hello、日志口打印 Get data 即为成功;如果发送后没有任何回显,说明还没成功,对照文末「常见问题与踩坑提示」排查。

代码执行流程

例程从启动到运行的完整流程如下(图中的循环箭头表示反复执行):


本文 API 汇总

hosal_uart_init(uart)

dev 结构体中的 uart_id、引脚、波特率等配置 UART 控制器(本教程初始化 UART1 通信口与 UART0 日志口)。

参数

  • uarthosal_uart_dev_t 结构体指针,必填。关键字段:config.uart_id(串口号,可选值:0/1/2,本教程通信口用 1)、config.tx_pin/config.rx_pin(TX/RX 引脚号,本教程 TX16/RX7)、config.baud_rate(波特率,可选值:115200/921600 等)、config.data_width(可选值:HOSAL_DATA_WIDTH_8BIT)、config.parity(可选值:HOSAL_NO_PARITY 无校验)、config.stop_bits(可选值:HOSAL_STOP_BITS_1)、config.mode(可选值:HOSAL_UART_MODE_POLL 轮询 / HOSAL_UART_MODE_INT 中断收发)

返回值:成功返回 0;失败返回负值错误码

hosal_uart_send(uart, txbuf, size)

将缓冲区数据通过串口发出(本教程用于回显收到的数据)。

参数

  • uarthosal_uart_dev_t 结构体指针
  • txbuf:待发送数据缓冲区指针,必填
  • size:发送字节数,可选值:1~256

返回值:成功返回实际发送的字节数(≤ size);失败返回负值错误码

hosal_uart_receive(uart, data, expect_size)

从串口读取数据到缓冲区(轮询模式下阻塞等待)。

参数

  • uarthosal_uart_dev_t 结构体指针
  • data:接收缓冲区指针,必填
  • expect_size:期望接收字节数

返回值:成功返回实际接收的字节数(可能小于 expect_size);失败返回负值错误码

hosal_uart_finalize(uart)

停止串口并释放占用引脚(不再使用前调用)。

参数

  • uarthosal_uart_dev_t 结构体指针

返回值:成功返回 0;失败返回负值错误码

blog_info(fmt, ...)

输出一条 INFO 级日志(固定由 UART0 输出,受级别过滤)。

参数

  • fmt:格式化字符串,同 printf 用法,必填
  • ...:变参,与 fmt 占位符对应,可省略

返回值:无

xTaskCreate(task, name, stack, param, prio, handle)

创建任务并加入就绪队列,由调度器按优先级调度执行。

参数

  • task:任务入口函数指针,形如 void task(void *arg),必填
  • name:任务名称字符串(调试用),如 "uart_task"
  • stack:任务栈大小(单位:字),可选值:内存允许范围内任意值,如 2048(栈过小易溢出死机)
  • param:传给入口函数的参数指针,无参传 NULL
  • prio:任务优先级,可选值:0(最低)~19(最高,SDK 配置),如 16
  • handle:任务句柄输出指针,不需要可传 NULL

返回值:成功返回 pdPASS;失败返回 pdFAIL(如内存不足)


完整代码

以下为 uart/main.c 完整源码,与官方示例(applications/peripherals/uart/uart/main.c)完全一致:

📜 点击展开 uart/main.c 完整代码
c
/*
 * @Author: xuhongv@yeah.net
 * @Date: 2022-10-03 15:02:19
 * @LastEditors: xuhongv@yeah.net xuhongv@yeah.net
 * @LastEditTime: 2022-10-20 17:42:45
 * @FilePath: \bl_iot_sdk_for_aithinker\applications\get-started\helloworld\helloworld\main.c
 * @Description: Uart
 */
#include <stdio.h>
#include <string.h>
#include <FreeRTOS.h>
#include <task.h>
#include <blog.h>
#include "bl_sys.h"
#include <stdio.h>
#include <cli.h>
#include <hosal_uart.h>
#include <blog.h>
#include <hosal_uart.h>

void TaskUart(void *param)
{

    uint8_t data[32];
    int ret;

    hosal_uart_dev_t uart_dev_echo = {
        .config = {
            .uart_id = 0,
            .tx_pin = 4, // TXD GPIO
            .rx_pin = 3, // RXD GPIO
            .cts_pin = 255,
            .rts_pin = 255,
            .baud_rate = 9600,
            .data_width = HOSAL_DATA_WIDTH_8BIT,
            .parity = HOSAL_NO_PARITY,
            .stop_bits = HOSAL_STOP_BITS_1,
            .mode = HOSAL_UART_MODE_POLL,
        },
    };

    hosal_uart_dev_t uart_dev_log = {
        .config = {
            .uart_id = 1,
            .tx_pin = 16, // TXD GPIO
            .rx_pin = 7,  // RXD GPIO
            .cts_pin = 255,
            .rts_pin = 255,
            .baud_rate = 115200,
            .data_width = HOSAL_DATA_WIDTH_8BIT,
            .parity = HOSAL_NO_PARITY,
            .stop_bits = HOSAL_STOP_BITS_1,
            .mode = HOSAL_UART_MODE_POLL,
        },
    };

    /* Uart init device */
    hosal_uart_init(&uart_dev_log);
    /* Uart init device */
    hosal_uart_init(&uart_dev_echo);
    blog_info("Uart Demo Start");
    while (1)
    {
        /* Uart receive poll */
        ret = hosal_uart_receive(&uart_dev_log, data, sizeof(data));
        if (ret > 0)
        {
            /* Uart send poll */
            hosal_uart_send(&uart_dev_log, data, ret);
            blog_info("Get data ");
        }
    }
}

/**
 * @brief main
 *
 */
void main(void)
{

    xTaskCreate(TaskUart, "TaskUart", 1024, NULL, 15, NULL);
}

常见问题与踩坑提示

⚠️ USB-TTL 发送无回显
原因:TX/RX 未交叉连接、未共地,或波特率不一致
解决:确认 IO16 → USB-TTL RXD、IO7 → USB-TTL TXD,GND 必须相连;通信口波特率 115200 且两端一致

⚠️ 收到乱码
原因:波特率不匹配,或 USB-TTL 模块电平不兼容(3.3V vs 5V)
解决:确认两端波特率一致(115200);使用 3.3V 电平的 USB-TTL 模块,勿用 5V 供电的旧模块直连

⚠️ 把日志口和通信口搞混
原因:USB 直连电脑看到的是 UART0 日志,不是 UART1 通信数据
解决:牢记两组串口:UART0 = log(TX4/RX3)UART1 = 通信(TX16/RX7)blog_info 输出永远走 UART0

⚠️ 系统重启后日志口不打印
原因:官方 README 提示某些配置下重启会关闭 log 输出
解决:需要重启后继续打印日志,在 proj_config.mk 中设置 CONFIG_SYS_REBOOT_LOG_DISENABLE:=1

⚠️ 烧录一直卡住等待,进度条不动
原因:未进入下载模式,或数据线只能充电不能传数据
解决:烧录时按提示长按 EN 键进入下载模式;换一根能传数据的 Type-C 数据线后重试

⚠️ 找不到串口设备或提示无权限
原因:Linux 下 /dev/ttyUSB0 不存在或权限不足,Windows 下未安装 USB 转串口驱动
解决:Linux 用 ls /dev/ttyUSB* 确认设备号,权限不足执行 sudo usermod -aG dialout $USER 后重新登录;Windows 在设备管理器安装驱动并确认 COM 口号

运行自检

USB-TTL 发送字符后收到原样回显,且日志口打印 Get data,即 UART 通信验证通过。

遇到问题?

如有其他问题,请到统一的提问与讨论区:Ai-Thinker Discussions

Released under the MIT License. Build Time 2026-09-11 14:52:23