嵌入式开发代码实践——串口通信(UART)开发

串口通信(UART)开发详解

一、UART通信基础概念

1.1 什么是UART?

UART(Universal Asynchronous Receiver/Transmitter,通用异步收发传输器)是一种异步串行通信接口。它是嵌入式系统中最常用的通信方式之一。

1.2 UART通信特点

  • 异步通信:无需时钟信号,靠起始位、停止位同步

  • 全双工:可同时发送和接收数据

  • 点对点通信:通常用于两个设备之间的通信

  • 传输距离:TTL电平约1米,RS-232可达15米

1.3 UART数据帧格式

复制代码
┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐
│空闲│起始│数据位0│数据位1│...│数据位7│校验位│停止位│空闲│
├───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┤
│高  │低  │数据内容                      │可选  │高   │高  │
└───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘
  • 起始位:1位低电平

  • 数据位:通常8位(也可5、6、7位)

  • 校验位:可选(奇校验、偶校验)

  • 停止位:1或2位高电平

二、硬件原理分析

2.1 i.MX6ULL UART硬件框图

复制代码
┌─────────────────────┐      ┌─────────────────────┐
│      i.MX6ULL       │      │       PC主机        │
│                     │      │                     │
│   UART1控制器       │◄────►│      USB转串口      │
│   - 发送寄存器      │      │       (CH340)       │
│   - 接收寄存器      │      │                     │
│   - 控制寄存器      │      └─────────────────────┘
└─────────┬───────────┘
          │
          ▼
┌─────────────────────┐
│    硬件引脚          │
│  TXD (发送)          │───┐
│  RXD (接收)          │◄──┘
└─────────────────────┘

2.2 引脚定义(参考原理图)

  • UART1_TX:发送数据引脚,数据从芯片发送出去

  • UART1_RX:接收数据引脚,数据从外部接收进来

  • 波特率:115200 bps(每秒传输115200位)

三、代码详细解析

3.1 头文件定义(uart.h)

复制代码
#ifndef _UART_H_
#define _UART_H_

// 声明UART相关函数
extern void uart1_init(void);          // UART1初始化
extern void putc(unsigned char d);     // 发送一个字符
extern void puts(const char *s);       // 发送字符串
extern unsigned char getc(void);       // 接收一个字符

#endif // !_UART_H_

3.2 UART初始化函数(uart.c)

3.2.1 引脚配置
复制代码
void uart1_init(void)
{
    // 1. 复用功能配置 - 将引脚配置为UART功能
    IOMUXC_SetPinMux(IOMUXC_UART1_RX_DATA_UART1_RX, 0);  // RX引脚复用为UART
    IOMUXC_SetPinMux(IOMUXC_UART1_TX_DATA_UART1_TX, 0);  // TX引脚复用为UART
    
    // 2. 电气特性配置 - 设置引脚的电气参数
    IOMUXC_SetPinConfig(IOMUXC_UART1_RX_DATA_UART1_RX, 0x10B0);  // RX引脚电气特性
    IOMUXC_SetPinConfig(IOMUXC_UART1_TX_DATA_UART1_TX, 0x10B0);  // TX引脚电气特性

详细解释:

  • IOMUXC_SetPinMux():选择引脚功能

    • 第一个参数:引脚名称(如IOMUXC_UART1_RX_DATA_UART1_RX

    • 第二个参数:复用选择器(0表示选择主要功能)

  • IOMUXC_SetPinConfig():配置引脚电气特性

    • 参数0x10B0含义:

      • 0x:十六进制前缀

      • 10B0:配置驱动强度、上下拉、速度等参数

3.2.2 复位UART控制器
复制代码
    // 3. 软件复位UART控制器
    UART1->UCR2 &= ~(1 << 0);  // 清除UART使能位,相当于复位

寄存器解释:

  • UART1->UCR2:UART控制寄存器2

  • 位0(UARTEN):UART使能位

    • 0:禁用UART(复位状态)

    • 1:使能UART

  • 先清除此位,再重新配置,确保从干净状态开始

3.2.3 配置UART参数
复制代码
    // 4. 配置UART控制寄存器2
    unsigned int t;
    t = UART1->UCR2;                   // 读取当前值
    
    t |= (1 << 14);                    // 设置IRTS位,忽略RTS流控
    t &= ~(1 << 8);                    // 清除PREN位,禁用奇偶校验
    t &= ~(1 << 6);                    // 清除STPB位,1个停止位
    t |= (1 << 5);                     // 设置WS位,8位数据位
    t |= (1 << 2);                     // 设置TXEN位,使能发送器
    t |= (1 << 1);                     // 设置RXEN位,使能接收器
    
    UART1->UCR2 = t;                   // 写回寄存器

UCR2寄存器位详解:

复制代码
位   名称       功能
──────────────────────────
0    UARTEN     UART使能位
1    RXEN       接收器使能
2    TXEN       发送器使能
5    WS         字长选择(0=7位,1=8位)
6    STPB       停止位数量(0=1位,1=2位)
8    PREN       奇偶校验使能
14   IRTS       忽略RTS流控(必须设置为1)
3.2.4 配置其他UART寄存器
复制代码
    // 5. 配置UART控制寄存器3
    UART1->UCR3 |= (1 << 2);           // 设置RXDMUXSEL位(必须为1)
    
    // 6. 配置FIFO控制寄存器
    UART1->UFCR &= ~(7 << 7);          // 清除参考时钟分频位
    UART1->UFCR |= (5 << 7);           // 设置1分频(参考时钟分频器)

UCR3寄存器:

  • 位2(RXDMUXSEL):接收数据多路复用器选择

    • 必须设置为1,芯片工作在MUXED模式

UFCR寄存器:

  • 位7-9(RFDIV):参考时钟分频器

    • 101(十进制5)= 1分频

    • 公式:分频值 = 6 - RFDIV

3.2.5 配置波特率
复制代码
    // 7. 波特率配置(115200 bps)
    UART1->UBIR = 999;                  // 增量寄存器
    UART1->UBMR = 43401;                // 模数寄存器
    
    // 8. 使能UART
    UART1->UCR1 |= (1 << 0);            // 设置UARTEN位,使能UART
}

波特率计算公式:

复制代码
BaudRate = Ref_Freq / (16 × ((UBMR + 1) / (UBIR + 1)))

其中:

  • Ref_Freq = 80MHz(系统参考时钟)

  • UBIR = 999(增量寄存器值)

  • UBMR = 43401(模数寄存器值)

计算验证:

复制代码
波特率 = 80,000,000 / (16 × ((43401 + 1) / (999 + 1)))
       = 80,000,000 / (16 × (43402 / 1000))
       = 80,000,000 / (16 × 43.402)
       = 80,000,000 / 694.432
       ≈ 115200 bps

3.3 字符发送函数

复制代码
void putc(unsigned char d)
{
    // 等待发送完成(检查TXDC标志位)
    while ((UART1->USR2 & (1 << 3)) == 0);
    
    // 写入要发送的数据到发送寄存器
    UART1->UTXD = d;
}

工作原理:

  1. UART1->USR2 & (1 << 3):检查状态寄存器2的TXDC位(位3)

    • 0:发送器忙,正在发送数据

    • 1:发送完成,可以发送新数据

  2. while循环:一直等待,直到发送完成标志为1

  3. UART1->UTXD = d:将数据写入发送寄存器,自动开始发送

状态寄存器2(USR2)位:

  • 位3(TXDC):发送完成标志

    • 0:发送器忙或禁用

    • 1:发送缓冲区和移位寄存器都为空

3.4 字符串发送函数

复制代码
void puts(const char *s)
{
    // 遍历字符串,逐个发送字符
    while (*s)
    {
        putc(*s++);      // 发送当前字符,指针后移
    }
    putc('\n');          // 发送换行符,便于阅读
}

工作原理:

  1. while (*s):检查当前字符是否为字符串结束符'\0'

  2. putc(*s++):发送当前字符,然后指针s指向下一个字符

  3. 循环直到遇到字符串结束符

  4. putc('\n'):额外发送换行符

3.5 字符接收函数

复制代码
unsigned char getc(void)
{
    // 等待接收数据就绪(检查RDR标志位)
    while ((UART1->USR2 & (1 << 0)) == 0);
    
    // 读取接收寄存器中的数据
    return (unsigned char)UART1->URXD;
}

工作原理:

  1. UART1->USR2 & (1 << 0):检查状态寄存器2的RDR位(位0)

    • 0:没有接收到数据

    • 1:有数据可以读取

  2. while循环:一直等待,直到有数据可读

  3. (unsigned char)UART1->URXD:读取接收寄存器并转换为无符号字符

状态寄存器2(USR2)位:

  • 位0(RDR):接收数据就绪标志

    • 0:没有接收到新数据

    • 1:接收到新数据,可以读取

接收寄存器(URXD)注意:

  • URXD寄存器是只读的

  • 读取后,RDR标志会自动清除

四、主程序中的UART使用

4.1 主程序调用(main.c)

复制代码
int main(void)
{
    // 系统初始化
    system_interrupt_init();  // 中断系统初始化
    clock_init();              // 时钟初始化
    led_init();                // LED初始化
    beep_init();               // 蜂鸣器初始化
    key_init();                // 按键初始化
    gpt1_init();               // 定时器初始化
    
    // UART初始化(重点)
    uart1_init();              // 初始化UART1,配置为115200波特率
    
    unsigned char ch;          // 用于存储接收到的字符
    
    while(1)
    {
        // 延时1秒
        delay_us(1000 * 1000);
        
        // 控制LED和蜂鸣器翻转
        led_nor();             // LED状态翻转
        beep_nor();            // 蜂鸣器状态翻转
        
        // UART通信部分
        ch = getc();           // 从串口接收一个字符(会等待直到有数据)
        putc(ch);              // 将接收到的字符发送回去(回显功能)
        puts("\nhello world!"); // 发送字符串
    }
    
    return 0;
}

4.2 程序流程图

复制代码
开始
  ↓
初始化系统(时钟、中断等)
  ↓
初始化UART(配置引脚、波特率115200)
  ↓
┌─────────────────────────────┐
│          主循环             │
│  ↓                          │
│  延时1秒                    │
│  ↓                          │
│  LED和蜂鸣器翻转            │
│  ↓                          │
│  等待接收串口数据           │←──┐
│  ↓                          │   │
│  将接收的数据发送回去       │   │
│  ↓                          │   │
│  发送"hello world!"字符串   │   │
│  ↓                          │   │
│  (循环继续)                 │   │
└──────────────┬──────────────┘   │
               │                  │
               └──────────────────┘

五、常见问题与调试

5.1 常见问题

  1. 没有输出/全是乱码

    • 检查波特率是否匹配(PC端和开发板都设为115200)

    • 检查TX、RX线是否接反

    • 检查电源是否正常

  2. 只能发送不能接收

    • 检查RX引脚配置是否正确

    • 检查PC端串口工具是否已打开串口

    • 检查线路连接是否良好

  3. 发送数据不完整

    • 检查发送函数是否正确等待发送完成标志

    • 检查是否有其他中断影响

相关推荐
Scott9999HH7 小时前
【IIoT流量实战】蒸汽管道阀门全关却仍有流量?用 Python 实现涡街信号 FFT 频谱分析与温压全补偿积算网关,深度拆解靠谱的涡街流量计厂家硬核技术标准
开发语言·python
码智社8 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
AI云海8 小时前
python 列表、元组、集合和字典
开发语言·python
克里斯蒂亚诺更新8 小时前
ruoyi角色权限的对应关系
笔记
萧瑟余晖9 小时前
JDK 26 新特性详解
java·开发语言
马优晨9 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
a11177611 小时前
2FA 验证码生成器(github登录验证 app)
笔记·学习
人邮异步社区11 小时前
怎么把C语言学到精通?
c语言·开发语言
sramdram11 小时前
MCU微控制器OA打印机方案
单片机·嵌入式硬件·mcu·mcu微控制器
心平气和量大福大12 小时前
C#-WPF-控件-TextBox 数据绑定
开发语言·c#·wpf