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

目录

一、串口通信基础概念

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

相关推荐
LYS_06182 小时前
RM赛事C型板九轴IMU解算(3)(姿态融合算法)
c语言·算法·imu·姿态解算·四元数到欧拉角
zmj3203242 小时前
单片机中NVM和FLASH的区别
单片机·嵌入式硬件
想放学的刺客2 小时前
单片机嵌入式试题(第21期)嵌入式系统启动异常排查与多任务同步机制设计两个核心方向,涵盖硬件调试、软件架构等综合能力考察。
stm32·单片机·嵌入式硬件·物联网·51单片机·proteus
Y1rong2 小时前
STM32之DMA
stm32·单片机·嵌入式硬件
杜子不疼.2 小时前
【Linux】基础IO(一):C 接口文件讲解
linux·c语言·开发语言·人工智能
LS_learner2 小时前
ros2命令行工具的完整命令简介
嵌入式硬件
飞凌嵌入式2 小时前
嵌入式AI领域的主控选择
linux·arm开发·人工智能·嵌入式硬件
猫猫的小茶馆2 小时前
【Linux 驱动开发】四. 平台总线驱动
linux·c语言·arm开发·驱动开发·嵌入式硬件·mcu·物联网
Yupureki2 小时前
《算法竞赛从入门到国奖》算法基础:入门篇-分治
c语言·开发语言·数据结构·c++·算法·贪心算法