uart开发指南
本文档以 uart_demo 为例,带你在 HiDiTing 开发板上快速跑通第一个 uart(通用异步收发器)通信应用,并了解如何基于它构建自己的应用。
UART 驱动背景知识
UART 工作原理
UART(Universal Asynchronous Receiver/Transmitter,通用异步收发传输器)是一种通用串行数据总线,用于异步通信。该总线双向通信,可以实现全双工传输和接收。
核心特性:
| 特性 | 说明 |
|---|---|
| 串行 | 指在计算机总线或其他数据通道上,每次传输一个位元数据,并连续进行以上单次过程的通信方式。 |
| 异步 | 无时钟信号,发送方和接收方不共享同一时钟信号,但需约定相同的波特率(即每秒传输的位数)。 |
| 全双工 | 通信允许数据在两个方向上同时传输,它在能力上相当于两个单工通信方式的结合。全双工可以同时进行信号的双向传输。 |
信号线:
| 信号线 | 方向 | 说明 |
|---|---|---|
| TXD | 输出 | 发送数据线 |
| RXD | 输入 | 接收数据线 |
| CTS | 输入 | 清除发送(流控制) |
| RTS | 输出 | 请求发送(流控制) |
协议帧格式
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位的。
波特率配置
计算公式:
示例:
- 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 参考
操作流程
- UART 初始化:调用 uapi_uart_init() 初始化 UART 模块(属性在初始化时通过参数配置)
- 注册回调:调用 uapi_uart_register_rx_callback() 注册接收回调函数
- 写数据:调用 uapi_uart_write() 发送数据
- 读数据:中断回调中接收数据或调用 uapi_uart_read() 读取数据
- 去初始化:调用 uapi_uart_deinit() 释放资源
传输模式
UART 支持以下数据传输模式:
| 模式 | 说明 | 适用场景 |
|---|---|---|
| 轮询模式 | CPU 主动轮询状态寄存器 | 简单场景、低数据量 |
| 中断模式 | 数据到达时触发中断 | 中等数据量、低延迟 |
| DMA 模式 | 直接内存访问,减少 CPU 参与 | 大量数据、低 CPU 占用 |
流控制
UART 支持硬件流控制(RTS/CTS),在 4 线模式下:
- 当 RX FIFO 满时,RTS 管脚拉高表示"不再接收"
- 当 CTS 管脚被拉高时,停止发送数据
说明: 建议使用软件流控制或不使用流控制,硬件流控制为可选功能。
快速跑通 UART Demo
配置说明
UART Demo 通过 AT 命令方式提供交互式控制,支持以下命令:
| 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接线可参考下图:

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

预期结果
UART AT指令执行结果
UART2的串口助手工具的界面

UART 数据传输展示
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.pyat_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文件结构:
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
新demo完成后,逐项验收:
- 设备启动后发送 "Hello UART!" 字符串
- 能够正确接收并打印接收到的数据
- 串口输出格式正确
app_run运行配置
app_run应用默认是关闭的,如需启用此应用,需用户手动在acore.prelds文件中添加 KEEP (*(SORT(.zinitcall.app_run*.init))) 具体参考示意图如下:

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 配置参数和缓冲区对齐 |