目录
[1.1 串行与并行通信](#1.1 串行与并行通信)
[1.2 同步与异步通信](#1.2 同步与异步通信)
[1.3 通信方式分类](#1.3 通信方式分类)
[1.4 串口通信标准](#1.4 串口通信标准)
[1.5 波特率与数据格式](#1.5 波特率与数据格式)
[2.1 核心硬件连接](#2.1 核心硬件连接)
[2.2 核心芯片作用](#2.2 核心芯片作用)
[2.3 核心寄存器](#2.3 核心寄存器)
[3.1 初始化过程](#3.1 初始化过程)
[3.2 数据发送函数](#3.2 数据发送函数)
[3.3 数据接收函数](#3.3 数据接收函数)
[3.4 标准I/O库移植](#3.4 标准I/O库移植)
[3.4.1 移植步骤](#3.4.1 移植步骤)
[3.4.2 移植验证](#3.4.2 移植验证)
一、串口通信基础概念
UART 属于异步串行全双工通信,我们先通过对比明确其定位:
1.1 串行与并行通信
- 串行通信:数据拆分成单个比特,按顺序在一根数据线上依次传输,占用资源少,结构简单。
- 并行通信:多个比特同时通过多根数据线传输,速度快但占用资源多。
1.2 同步与异步通信
- 同步通信:有专门的时钟线(SCL)控制数据传输节奏,如 I2C、SPI。
- 异步通信:没有共享时钟,收发双方依靠预先约定的波特率进行通信,UART 即属于此类。
1.3 通信方式分类
| 模式 | 特点 | 举例 |
|---|---|---|
| 单工 | 一方固定为发送端,另一方固定为接收端 | 电报机 |
| 半双工 | 数据传输可在两个方向交替进行,但不能同时进行 | 对讲机 |
| 全双工 | 数据传输可同时在两个方向进行 | 电话 |
三种通信方式
1.4 串口通信标准
- TTL电平:直接使用芯片引脚电平,51单片机为5V,ARM为3.3V。传输距离限制在10-20米。
- RS232:逻辑1为-3V~-15V,逻辑0为+3V~+15V。采用三线制(收、发、地),全双工,传输距离20-30米。
- RS485:采用差分信号传输(A、B两线),通过电压差识别信息。半双工,抗干扰能力强,传输距离可达1200米。
1.5 波特率与数据格式
- 波特率:表示每秒传输的比特数(bps),常见值有9600、115200等。
波特率计算公式
- 数据格式:1个起始位 + 5~8个数据位 + 0~1个校验位 + 1~2个停止位
串口通信时序图
二、硬件与开发环境
2.1 核心硬件连接
UART 接口:使用 UART1 模块,引脚复用配置如下:
- TX 引脚:IOMUXC_UART1_TX_DATA_UART1_TX(对应开发板引脚);
- RX 引脚:IOMUXC_UART1_RX_DATA_UART1_RX;
- 电源与地:通过 CH340 芯片实现 USB-TTL 转换,为串口通信提供电源(不建议直接用 USB 供电,避免板卡发热)。
2.2 核心芯片作用
- CH340(U8):USB 转 TTL 芯片,实现 PC 端 USB 接口与开发板 UART 串口的协议转换;
- DCDC 电源稳压模块(U12/U13):防抖、抗干扰,确保串口通信电源稳定。
2.3 核心寄存器
| 寄存器 | 作用 | 位定义 |
|---|---|---|
| UART1_UCR1 | 控制寄存器1 | [0]: UARTEN (使能) |
| UART1_UCR2 | 控制寄存器2 | [0]: 软件复位 [1]: RXEN (接收使能) [2]: TXEN (发送使能) [5]: WS (字长) [6]: STPB (停止位) [8]: PREN (奇偶校验使能) [14]: IRTS (忽略RTS流控) |
| UART1_UCR3 | 控制寄存器3 | [2]: RXDMUXSEL (接收多路复用选择) |
| UART1_UFCR | FIFO控制寄存器 | [7-9]: RFDIV (参考时钟分频) |
| UART1_UBIR | 波特率增量寄存器 | [0-15]: UBIR |
| UART1_UBMR | 波特率模数寄存器 | [0-15]: UBMR |
| UART1_USR2 | 状态寄存器2 | [0]: RDR (接收数据就绪) [3]: TXDC (发送完成) |
三、代码实现
3.1 初始化过程
cpp
void uart1_init(void)
{
// 1. 引脚复用配置:将引脚映射为UART1_TX/RX功能
IOMUXC_SetPinMux(IOMUXC_UART1_RX_DATA_UART1_RX, 0);
IOMUXC_SetPinMux(IOMUXC_UART1_TX_DATA_UART1_TX, 0);
// 2. 电气特性配置:上拉、驱动速度、压摆率等,确保信号稳定
IOMUXC_SetPinConfig(IOMUXC_UART1_RX_DATA_UART1_RX, 0x10B0);
IOMUXC_SetPinConfig(IOMUXC_UART1_TX_DATA_UART1_TX, 0x10B0);
// 3. UART软件复位,确保初始化状态干净
UART1->UCR2 &= ~(1 << 0);
// 4. UCR2寄存器配置
unsigned int t = 0;
t = UART1->UCR2;
t |= (1 << 14); // 忽略RTS流控(无需硬件流控)
t &= ~(1 << 8); // 禁用奇偶校验
t &= ~(1 << 6); // 1位停止位
t |= (1 << 5); // 8位数据位
t |= (1 << 2); // 使能发送器
t |= (1 << 1); // 使能接收器
UART1->UCR2 = t;
// 5. UCR3寄存器配置:必须设置为多路复用模式
UART1->UCR3 |= (1 << 2);
// 6. FIFO控制寄存器:参考时钟1分频(FIFO深度默认,无需开启额外配置)
UART1->UFCR &= ~(7 << 7);
UART1->UFCR |= (5 << 7);
// 7. 波特率配置(基于80MHz参考时钟,目标波特率115200)
UART1->UBIR = 999; // 增量寄存器
UART1->UBMR = 43401;// 调制器寄存器
UART1->UCR1 |= (1 << 0); // 使能UART1模块
}
3.2 数据发送函数
cpp
// 发送单个字符
void putc(unsigned char d)
{
while ((UART1->USR2 & (1 << 3)) == 0); // 等待TXDC=1(发送完成,缓冲区空)
UART1->UTXD = d; // 写入发送寄存器,触发发送
}
// 发送字符串
void puts(const char *s)
{
while (*s) // 循环发送每个字符,直到字符串结束符'\0'
{
putc(*s++);
}
putc('\n'); // 末尾添加换行
}
3.3 数据接收函数
cpp
unsigned char getc(void)
{
while ((UART1->USR2 & (1 << 0)) == 0); // 等待RDR=1(接收数据就绪)
return (unsigned char)UART1->URXD; // 读取接收寄存器数据
}
3.4 标准I/O库移植
底层收发函数(putc/getc)仅支持单个字符操作,实际开发中更需要 printf(格式化输出)和 scanf(格式化输入)。通过移植标准库 stdio,可直接使用这些接口。
3.4.1 移植步骤
(1)添加必要函数
stdio 库依赖 raise 函数(异常处理),需在 uart.c 中添加空实现(否则编译报错):
cpp
void raise(int n)
{
// 空实现,仅满足链接需求
}
(2)修改汇编文件扩展名
若启动文件为 start.s,需改为 start.S(大写 S)------ 因为大写 S 会触发预处理,小写 s 直接编译,而 stdio 库依赖预处理流程。
(3)更新 Makefile
添加 stdio 库路径、头文件目录和源码目录,确保编译器能找到相关文件:
bash
# 编译器与链接器配置
cc = arm-linux-gnueabihf-gcc
ld = arm-linux-gnueabihf-ld
# 目标文件
target = imx6ull_uart_demo
# stdio库路径(根据编译器安装路径调整)
libpath = -lgcc -L/usr/local/arm/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/lib/gcc/arm-linux-gnueabihf/4.9.4
# 头文件目录(包含stdio头文件)
incdirs = bsp imx6ull stdio/include
# 源码目录(包含stdio库源码)
srcdirs = bsp project stdio/lib
# 编译选项:禁用标准库、无内置函数、Thumb指令集
CFLAGS = -Wall -Wa,-mimplicit-it=thumb -nostdlib -fno-builtin
# 链接选项:使用自定义链接脚本
$(ld) -Timx6ull.lds -o$(target).elf $^ $(libpath)
3.4.2 移植验证
移植完成后,可直接使用 printf 和 scanf 实现数据交互,例如实现两数相加功能:
cpp
int main(void)
{
// 系统初始化
system_interrupt_init();
clock_init();
beep_init();
led_init();
key_init();
gpt1_init();
uart1_init();
int num1 = 0, num2 = 0;
while (1)
{
// 通过串口接收两个数字
scanf("%d %d", &num1, &num2);
// 计算和并输出结果
printf("%d + %d = %d\n", num1, num2, num1 + num2);
// 其他功能可以在这里扩展
// 例如:控制LED或蜂鸣器
}
return 0;
}
实验效果:在串口调试工具中输入10 20,会立即输出10 + 20 = 30,证明 stdio 移植成功。
基于基础 UART 功能,我们可以实现更复杂的交互系统:
- 通过串口命令控制LED灯、蜂鸣器
- 实现简单的调试信息输出
- 与其他设备进行数据交换
- 通过串口升级固件
四、总结
本次基于 IMX6ULL 开发板,完成了 UART 串口通信的全流程开发:从核心概念入手,详解了底层寄存器配置、收发函数实现、stdio 库移植,最后通过外设控制实战验证了通信稳定性。UART 作为嵌入式开发的 "基本功",掌握其原理和实现方式,能为后续更复杂的通信(如 Modbus、MQTT)打下基础。