欢迎光临
我们一直在努力

ARM架构——UART 串口通信详解

目录

一、串口通信基础概念

1.1 串行与并行通信

1.2 同步与异步通信

1.3 通信方式分类

1.4 串口通信标准

1.5 波特率与数据格式

二、硬件与开发环境

2.1 核心硬件连接

2.2 核心芯片作用

2.3 核心寄存器

三、代码实现

3.1 初始化过程

3.2 数据发送函数

3.3 数据接收函数

3.4 标准I/O库移植

3.4.1 移植步骤

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 初始化过程

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 数据发送函数

// 发送单个字符
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 数据接收函数

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 中添加空实现(否则编译报错):

void raise(int n)
{
// 空实现,仅满足链接需求
}

(2)修改汇编文件扩展名

若启动文件为 start.s,需改为 start.S(大写 S)—— 因为大写 S 会触发预处理,小写 s 直接编译,而 stdio 库依赖预处理流程。

(3)更新 Makefile

添加 stdio 库路径、头文件目录和源码目录,确保编译器能找到相关文件:

# 编译器与链接器配置
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 实现数据交互,例如实现两数相加功能:

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)打下基础。

赞(0)
未经允许不得转载:171主机测评 » ARM架构——UART 串口通信详解
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址