跳转至

uart开发指南

本文档以 uart_demo 为例,带你在 HiDiTing 开发板上快速跑通第一个 uart(通用异步收发器)通信应用,并了解如何基于它构建自己的应用。


UART 驱动背景知识

UART 工作原理

UART(Universal Asynchronous Receiver/Transmitter,通用异步收发传输器)是一种通用串行数据总线,用于异步通信。该总线双向通信,可以实现全双工传输和接收。

核心特性:

特性 说明
串行 指在计算机总线或其他数据通道上,每次传输一个位元数据,并连续进行以上单次过程的通信方式。
异步 无时钟信号,发送方和接收方不共享同一时钟信号,但需约定相同的波特率(即每秒传输的位数)。
全双工 通信允许数据在两个方向上同时传输,它在能力上相当于两个单工通信方式的结合。全双工可以同时进行信号的双向传输。

信号线:

信号线 方向 说明
TXD 输出 发送数据线
RXD 输入 接收数据线
CTS 输入 清除发送(流控制)
RTS 输出 请求发送(流控制)

协议帧格式

UART 通信帧结构如下:

UART协议帧格式

字段 位数 说明
起始位 1 低电平表示帧开始
数据位 5-8 有效数据
校验位 0-1 可选:奇校验/偶校验
停止位 1/1.5/2 高电平表示帧结束

校验方式:

方式 说明
无校验 不使用校验位
奇校验 数据位中"1"的数目为奇数时校验位为0
偶校验 数据位中"1"的数目为偶数时校验位为0

说明:

  • 起始位和停止位:每个数据帧以起始位(低电平)开始,停止位(高电平)结束,用于同步和标识数据边界。部分协议可能包含奇偶校验位用于错误检测。
  • 空闲位:处于逻辑1状态,表示当前线路上没有资料传送。
  • 起始位:先发出一个逻辑0信号,表示传输数据的开始。
  • 数据位:可以是5~8位逻辑0或1。如ASCII码(7位),扩展BCD码(8位)小端传输。
  • 奇偶校验位:

    数据位传送完成后,可以选择是否进行奇偶校验(收发双方约定),串口校验分以下几种方式:

    • 无校验(no parity)。
    • 奇校验(odd parity):如果数据位中“1”的数目是偶数,则校验位为“1”;如果“1”的数目是奇数,校验位为“0”。
    • 偶校验(even parity):如果数据位中“1”的数目是偶数,则校验位为“0”;如果“1”的数目是奇数,校验位为“1”。
    • mark parity:校验位始终为1。
    • space parity:校验位始终为0。
  • 停止位:数据结束标志,可以是1位、1.5位、2位的高电平,芯片默认使用是1位的。

波特率配置

计算公式:

UART 波特率 = 内部总线频率 / (16 × 分频系数)

示例:

  • UART3 内部总线时钟为 81MHz
  • 分频系数最小为 1
  • 最高波特率:81M / (16 × 1) = 5.06MHz

常用波特率: 9600、19200、115200、921600 等

芯片 UART 资源

HiDiTingV100 芯片提供 5 个 UART 单元:

UART 说明
UART2 镜像资源烧录、A核串口日志打印与AT命令
UART3 与电脑 debugkits 交互
UART0/UART1 对接外部芯片(如GPS、CAT1)
UART4 BT WVT UART

说明:

  • 芯片提供5个UART单元:UART2(用于镜像资源的烧录,A核串口日志打印与AT命令)、UART3(用于与电脑上的debugkits上交互),UART0与UART1(用于对接外部芯片),UART4(bt wvt uart)。
  • /src/build/config/target_config/3322/config.py 对应的构建目标中添加宏 FT_SINGLE_UART 后,UART3 的功能将复用到 UART2,UART0 可用于对接 GPS、Cat.1 等其他外设。

UART流控制

当接收端的数据缓冲区已满,无法处理数据时,就发出“不再接收”的信号,发送端则停止发送,直到发送端收到“可以继续发送”的信号再发送数据。

计算机中常用的两种流控制分别是硬件流控制(RTS/CTS、DTR/DSR等)和软件流控制(XON/XOFF)。

硬件流控制原理

UART支持4线模式(4线模式即TX、RX、CTS、RTS):

  • 在4线工作模式下,UART RX FIFO达到设定的中断阈值,则把RTS管脚电平拉高,相当于输出“不再接收”的信号。
  • 在4线工作模式下,UART CTS管脚被拉高,则UART无法通过TX发送数据,相当于输入“不可继续发送”的信号。

API 接口列表

本文档示例代码中使用的接口:

接口函数 说明
uapi_uart_init 初始化 UART
uapi_uart_deinit 去初始化 UART
uapi_uart_write 写数据
uapi_uart_read 读数据
uapi_uart_register_rx_callback 注册接收回调函数

说明: 若使用 DMA 模式,需确保 DMA 驱动已完成初始化。

完整 API 列表

更多 UART 接口(如 DMA 相关接口、错误回调接口等)请参考:UART API 参考

操作流程

  1. UART 初始化:调用 uapi_uart_init() 初始化 UART 模块(属性在初始化时通过参数配置)
  2. 注册回调:调用 uapi_uart_register_rx_callback() 注册接收回调函数
  3. 写数据:调用 uapi_uart_write() 发送数据
  4. 读数据:中断回调中接收数据或调用 uapi_uart_read() 读取数据
  5. 去初始化:调用 uapi_uart_deinit() 释放资源

传输模式

UART 支持以下数据传输模式:

模式 说明 适用场景
轮询模式 CPU 主动轮询状态寄存器 简单场景、低数据量
中断模式 数据到达时触发中断 中等数据量、低延迟
DMA 模式 直接内存访问,减少 CPU 参与 大量数据、低 CPU 占用

流控制

UART 支持硬件流控制(RTS/CTS),在 4 线模式下:

  • 当 RX FIFO 满时,RTS 管脚拉高表示"不再接收"
  • 当 CTS 管脚被拉高时,停止发送数据

说明: 建议使用软件流控制或不使用流控制,硬件流控制为可选功能。


快速跑通 UART Demo

配置说明

UART Demo 通过 AT 命令方式提供交互式控制,支持以下命令:

AT+SETUART=115200
| AT 命令 | 功能 | 参数说明 | |---|---|---| | AT+SETUART=Baud_rate | 配置uart,实现数据传输 | Baud_rate uart的波特率 |

编译

完成一站式 CLI 环境配置后执行:

# 编译固件,编译生成的固件从 output/3322/fwpkg 中获取 diting-community.fwpkg
fbb set-target pack_diting_community
fbb build

构建成功后,使用一站式 CLI 烧写固件并打开 UART2 串口监视器。以下为 Windows USB DFU 示例;将 COM3 替换为实际日志串口,其他平台和串口烧写参数参见一站式 CLI 开发环境使用指南

fbb flash -f "$env:FBB_SDK_DIR\output\3322\fwpkg\diting-community.fwpkg" --chip 3322 -d --timeout 180
fbb monitor --port COM3 --baud 750000

使用方式

测试说明

  • 本示例默认使用 UART 中断模式读写数据,为了充分验证 UART 的数据传输功能,在 demo 中对 UART3 进行了配置,

验证 UART 的数据传输的完整功能建议使用两个U转串模块分别连接 UART2,UART3。分别用串口助手用来日志打印和 UART 数据传输。

  • 由于在系统工程中 UART3 在系统初始化过程中已经配置完成,为了验证 uart_demo 的完整性在 demo 中对 UART3做了去初始化的操作,作为用户使用 UART3 功能可以跳过配置的步骤。

直接使用 UART3 的数据传输功能。波特率为921600。

  • UART接线可参考下图:

UART接线可参考下图

  1. 烧录固件并启动设备
  2. 通过串口发送 AT 命令,格式如下:

    # uart 配置示例:设置 uart 的波特率为115200的示例
    AT+SETUART=115200
    

通过串口发送 AT 命令,格式如下

预期结果

UART AT指令执行结果

UART2的串口助手工具的界面

UART2的串口助手工具的界面

UART 数据传输展示

UART3的串口助手工具的界面

UART3的串口助手工具的界面

文件结构与代码走读

文件结构

UART Demo 的文件位于以下路径:

/samples/native_samples/uart/
├── uart_demo.c                 # uart 示例主文件, 包含 AT 命令处理函数
├── uart_demo.h                 # uart 示例头文件,包含数据结构与命令表定义
└── CMakeLists.txt              # 编译配置

各文件职责

文件 职责 关键内容
uart_demo.c uart 示例主文件,包含 uart 基础配置和数据传输过程。 at_uart_sample() AT 入口函数、uart_run_sample() uart 主要功能函数
uart_demo.h 头文件,定义数据结构、AT 命令表和函数声明 uart_config_args_t, g_at_uart_cmd_parse_table, 函数声明
CMakeLists.txt 编译配置,定义源文件、头文件路径和编译选项 set(SOURCES ...), build_component()

代码走读

CMakeLists.txt 解析

set(COMPONENT_NAME "uart_sample")

set(SOURCES
    ${CMAKE_CURRENT_SOURCE_DIR}/uart_demo.c
)

set(PUBLIC_HEADER
    ${CMAKE_CURRENT_SOURCE_DIR}
)

set(PRIVATE_HEADER
    ${ROOT_DIR}/src/include/driver
    ${ROOT_DIR}/middleware/utils/at/at
    ${ROOT_DIR}/middleware/utils/at/at/include
    ${ROOT_DIR}/middleware/utils/at/at/src
)

set(PRIVATE_DEFINES
)

set(PUBLIC_DEFINES
    AT_DITING_EXAMPLE_UART
)

set(COMPONENT_CCFLAGS
    -Wincompatible-function-pointer-types
)

set(WHOLE_LINK
    true
)

set(MAIN_COMPONENT
    false
)

build_component()

install_sdk(${CMAKE_CURRENT_SOURCE_DIR} "*.h")

关键配置说明:

  • COMPONENT_NAME:组件名称为uart_sample,需在config.py中的diting-community配置项下添加该组件名。
  • SOURCES:指定源文件为 uart_demo.c
  • PUBLIC_HEADER:公开头文件路径,使其他模块可以引用 uart_demo.h
  • PRIVATE_HEADER:私有头文件路径,用于编译时查找依赖头文件。
  • WHOLE_LINK:设置为 true,确保整个组件被链接。
  • PUBLIC_DEFINES:定义宏 AT_DITING_EXAMPLE_UART,用于在AT命令适配层文件at_adapter.c中条件性的添加AT命令注册。
  • 为使该uart文件参与编译,还需在上层CMakeLists.txt文件中以宏CONFIG_ENABLE_UART_SAMPLE为条件添加该uart文件。
  • config.py
  • at_adapter.c

uart_demo.h 解析

数据结构定义:

typedef struct {
    uint32_t para_map;              // 映射参数
    uint32_t baud_rate;             // 波特率
} uart_config_args_t;               // AT 命令参数结构体
参数语法定义:

static const at_para_parse_syntax_t g_uart_config_args_syntax[] = {
    {
        .type = AT_SYNTAX_TYPE_INT,
        .last = true,
        .attribute = AT_SYNTAX_ATTR_AT_MIN_VALUE | AT_SYNTAX_ATTR_AT_MAX_VALUE,
        .entry.int_range.min_val = 9600,
        .entry.int_range.max_val = 115200,      // 波特率可设范围
        .offset = offsetof(uart_config_args_t, baud_rate)
    },
};

AT 命令表定义:

static const at_cmd_entry_t g_at_uart_cmd_parse_table[] = {
    // AT+SETUART
    {
        "SETUART",                              // 指令字符串 - 用户实际输入的AT命令(如"AT+SETUART")
        0x2261,                                 // 指令编码 - 用于内部识别的唯一标识符(十六进制形式)
        1,                                      // 参数数量 - 该指令支持的参数个数
        g_uart_config_args_syntax,              // 指令参数语法定义 - 指向参数格式定义的指针
        NULL,                                   // 测试函数指针 - 用于AT+CMD=?测试命令的响应函数
        (at_set_func_t)at_uart_sample,          // 设置函数指针 - 执行AT+CMD=<...>时的处理函数
        NULL,                                   // 查询函数指针 - 执行AT+CMD?查询命令时的处理函数
        NULL,                                   // 其他功能函数指针 - 保留用于扩展功能
    },
};

函数声明:

// uart AT 命令注册
void at_diting_uart_example_cmd_register(void);
// AT 命令入口函数
at_ret_t at_uart_sample(const uart_config_args_t *args);

命令注册函数

// uart AT 命令注册函数
void at_diting_uart_example_cmd_register(void)
{
    uapi_at_cmd_table_register(g_at_uart_cmd_parse_table, AT_UART_FUNC_NUM, EXT_AT_UART_CMD_MAX_LEN);
}

代码逻辑解析:

  • 调用 uapi_at_cmd_table_register 注册 uart 命令表。
  • g_at_uart_cmd_parse_table:命令表数组。
  • AT_UART_FUNC_NUM:命令数量(通过宏计算数组大小)。
  • EXT_AT_UART_CMD_MAX_LEN:命令最大长度(128)。

UART 配置初始化主流程

// uart 配置函数
static errcode_t app_uart_init_config(uint32_t Baud_rate)
{
    uart_attr_t attr = {
        .baud_rate = Baud_rate,                         // 波特率
        .data_bits = UART_DATA_BIT_8,                   // 数据位
        .stop_bits = UART_STOP_BIT_1,                   // 停止位
        .parity = UART_PARITY_NONE                      // 校验位
    };                                                  // uart 基本配置
    // 去初始化 uart
    // 注: uart3 在系统初始化过程中已经初始化完成,为了验证 uart 的完整功能现在将 uart3 去初始化,带你重新体验一下 uart 完整配置过程。
    uapi_uart_deinit(CONFIG_UART_BUS_ID);
    // step2:初始化 uart
    errcode_t ret = uapi_uart_init(CONFIG_UART_BUS_ID, NULL, &attr, NULL, &g_app_uart_buffer_config);   // 初始化 uart
    if (ret != ERRCODE_SUCC) {
        at_sample_print("UART init failed=%d\n", ret);
        uapi_uart_deinit(CONFIG_UART_BUS_ID);
        return ret;
    }
    app_uart_register_rx_callback();                    // 注册接收回调函数

    return ERRCODE_SUCC;
}

UART 核心数据传输逻辑

static errcode_t uart_run_sample(const uart_config_args_t *args)
{
    // step1:uart 初始化配置
    at_sample_print("uart%d init config start!\n", CONFIG_UART_BUS_ID);
    errcode_t config_ret = app_uart_init_config(args->baud_rate);
    if (config_ret != ERRCODE_SUCC) {
        at_sample_print("uart%d init config fail!\n", CONFIG_UART_BUS_ID);
        return config_ret;
    }
    at_sample_print("uart%d init config succ!\n", CONFIG_UART_BUS_ID);
    // step2:uart 读写数据
    while (1) {
        (void)uapi_watchdog_kick();
        // step3:支持中断模式,等待接收标志位改变
        while (g_app_uart_int_rx_flag != 1) {
            osal_msleep(CONFIG_UART_INT_WAIT_MS);
            (void)uapi_watchdog_kick();
        }
        g_app_uart_int_rx_flag = 0;
        at_sample_print("uart%d int mode send back!\n", CONFIG_UART_BUS_ID);
        // step4:将接收到的数据回写到 uart
        if (uapi_uart_write(CONFIG_UART_BUS_ID, g_app_uart_int_rx_buff, g_app_uart_int_index,
            0) == g_app_uart_int_index) {
            at_sample_print("uart%d write data: ", CONFIG_UART_BUS_ID);
            for (uint16_t i = 0; i < g_app_uart_int_index; i++) {
                at_sample_print("%c", g_app_uart_int_rx_buff[i]);
            }
            at_sample_print("\n");
            at_sample_print("uart%d int mode send back succ!\n", CONFIG_UART_BUS_ID);
            if (strncmp((const char *)g_app_uart_int_rx_buff, "exit", 4) == 0) {
                break;
            }
        }
    }
    return ERRCODE_SUCC;
}

中断模式回调函数

// uart 接收中断服务函数
static void app_uart_read_int_handler(const uint8_t *buffer, uint16_t length, bool error)
{
    unused(error);
    // step1:参数检查
    if (buffer == NULL || length == 0) {
        at_sample_print("uart%d int mode transfer illegal data!\n", CONFIG_UART_BUS_ID);
        return;
    }
    // step2:数据打印
    uint8_t *buff = (uint8_t *)buffer;
    at_sample_print("uart%d  read data: ", CONFIG_UART_BUS_ID);
    for (uint16_t i = 0; i < length; i++) {
        at_sample_print("%c", buff[i]);
    }
    at_sample_print("\n");

    // step3:接收数据长度检查,如果接收数据长度超过接收缓冲区大小,只接收缓冲区大小数据
    if (length > CONFIG_UART_TRANSFER_SIZE){
        length = CONFIG_UART_TRANSFER_SIZE;
        at_sample_print("app_uart_read_int_handler buffer overflow!\n");
    }

    // step4:数据拷贝:将接收到的数据从 buffer 拷贝到全局接收缓冲区 g_app_uart_int_rx_buff 中
    if (memcpy_s(g_app_uart_int_rx_buff, length, buff, length) != EOK) {
        memset_s(g_app_uart_int_rx_buff, length, 0, length);
        at_sample_print("uart%d int mode data1 copy fail!\n", CONFIG_UART_BUS_ID);
    }

    // step5:更新索引和标志
    g_app_uart_int_index = length;
    g_app_uart_int_rx_flag = 1;
}
// uart 接收回调函数实现
static void app_uart_register_rx_callback(void)
{
    at_sample_print("uart%d int mode register receive callback start!\n", CONFIG_UART_BUS_ID);
    // step1:注册接收回调函数
    if (uapi_uart_register_rx_callback(CONFIG_UART_BUS_ID, UART_RX_CONDITION_FULL_OR_SUFFICIENT_DATA_OR_IDLE,
                                       1, app_uart_read_int_handler) == ERRCODE_SUCC) {
        at_sample_print("uart%d int mode register receive callback succ!\n", CONFIG_UART_BUS_ID);
    }
}

返回值说明

返回值 说明
AT_RET_OK 操作成功
AT_RET_RUN_ERROR 运行时错误

基于 UART Demo 开发自己的应用

上面的demo是使用AT指令触发运行,HiDiTing还支持app_run方式触发应用在系统启动时自动运行,以下示例将以app_run的方式开发一个开发者自己的应用

  • app_run(func) 是HiDiTing中应用层注册应用函数的宏,基于 GCC 编译器属性和自定义段区(section)自动注册来实现集中调用应用函数,系统启动时会自动遍历所有用 app_run 注册过的函数并执行,无需在系统 main 函数里逐个调用函数。

代码清单

新建一个 uart 通信应用(以 my_uart_demo 为例)通常只需要以下改动:

  • 新建 my_uart_demo.c 源文件
  • 新建 my_uart_demo.h 头文件
  • 新建 CMakeLists.txt 源文件
  • my_uart_demo.c 中实现 uart 操作函数
  • my_uart_demo.h 中定义宏常量和函数声明
  • CMakeLists.txt 源文件编译规则

my_uart_demo文件结构:

/samples/native_samples/my_uart_demo/
├── my_uart_demo.c
├── my_uart_demo.h
└── CMakeLists.txt

CMakeLists.txt 修改示例

# 在新建的 CMakeLists.txt 中添加应用的源文件
set(SOURCES
    ${CMAKE_CURRENT_SOURCE_DIR}/my_uart_demo.c
)

set(PUBLIC_HEADER
    ${CMAKE_CURRENT_SOURCE_DIR}
)

set(PRIVATE_HEADER

)

set(PUBLIC_DEFINES
    MY_UART_DEMO_ENABLE
)

build_component()

关键代码片段

my_uart_demo.c —— UART 通信

#include "uart.h"
#include "pinctrl.h"
#include "app_init.h"
#include "common_def.h"

#define MY_UART_BUS_ID          3           // UART的ID

#define MY_UART_TASK_PRIO       24          // 任务优先级
#define MY_UART_STACK_SIZE      0x1000      // 任务堆栈大小

static void my_uart_task(const char *arg)
{
    unused(arg);

    // step2:配置 uart
    uart_attr_t attr = {
        .baud_rate = 115200,
        .data_bits = UART_DATA_BIT_8,
        .stop_bits = UART_STOP_BIT_1,
        .parity = UART_PARITY_NONE
    };

    // step3:初始化 uart
    uapi_uart_deinit(MY_UART_BUS_ID);
    uapi_uart_init(MY_UART_BUS_ID, NULL, &attr, NULL, NULL);

    // step4:发送测试数据
    uint8_t tx_data[] = "Hello UART!";
    uapi_uart_write(MY_UART_BUS_ID, tx_data, sizeof(tx_data), 0);

    // step5:接收数据
    uint8_t rx_data[32] = {0};
    int rx_len = uapi_uart_read(MY_UART_BUS_ID, rx_data, sizeof(rx_data), 1000);
    if (rx_len > 0) {
        osal_printk("Received %d bytes: %s\n", rx_len, rx_data);
    }
}

void my_uart_entry(void)
{
    // step1:创建 uart 数据传输任务
    osal_task *task_handle = osal_kthread_create((osal_kthread_handler)my_uart_task,
                                                0, "MyUartTask", MY_UART_STACK_SIZE);
    if (task_handle != NULL) {
        osal_kthread_set_priority(task_handle, MY_UART_TASK_PRIO);
    }
}

app_run(my_uart_entry);

测试验证

完成一站式 CLI 环境配置后执行:

# 编译固件,编译生成的固件从 output/3322/fwpkg 中获取 diting-community.fwpkg
fbb set-target pack_diting_community
fbb build
1. 烧录固件并启动设备 2. 通过串口观察输出日志:

新demo完成后,逐项验收:

  • 设备启动后发送 "Hello UART!" 字符串
  • 能够正确接收并打印接收到的数据
  • 串口输出格式正确

app_run运行配置

app_run应用默认是关闭的,如需启用此应用,需用户手动在acore.prelds文件中添加 KEEP (*(SORT(.zinitcall.app_run*.init))) 具体参考示意图如下:

apprun运行配置

acore.prelds

KEEP (*(SORT(.zinitcall.app_run.init)))需要正确的配置 app_run 错误的配置将导致 app_run 无法运行,编译过程也不会将错误抛出。

注意事项

引脚配置注意事项:

  • 确保 TXD/RXD 引脚编号与硬件原理图一致
  • 引脚工作模式需与芯片手册匹配

波特率匹配:

  • 发送端和接收端必须使用相同的波特率设置

中断优先级:

  • 中断处理函数应尽量简短,避免长时间占用 CPU

资源说明:

  • DMA 模式需要额外配置 DMA 控制器
  • 中断模式需要确保中断优先级设置合理

常见错误

错误现象 原因 解决方法
undefined reference to 'uapi_uart_init' 未链接 UART 驱动库 检查 CMakeLists.txt 中是否正确添加了源文件
undefined reference to 'uapi_at_cmd_table_register' 未链接 AT 命令库 检查 CMakeLists.txt 中是否正确添加了 AT 相关头文件路径
编译报 at_ret_t 未定义 未包含 at.h 头文件 添加 #include "at.h"
串口无输出 AT 命令未注册 确认调用了 at_diting_uart_example_cmd_register()
串口通信失败 引脚配置错误或波特率不匹配 检查引脚编号和工作模式,确认波特率一致
中断回调未触发 中断未使能或优先级设置错误 检查 CONFIG_UART_SUPPORT_INT_MODE 配置和中断优先级
DMA 模式报错 DMA 未正确初始化 检查 DMA 配置参数和缓冲区对齐