串口通信(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;
}
工作原理:
-
UART1->USR2 & (1 << 3):检查状态寄存器2的TXDC位(位3)-
0:发送器忙,正在发送数据
-
1:发送完成,可以发送新数据
-
-
while循环:一直等待,直到发送完成标志为1 -
UART1->UTXD = d:将数据写入发送寄存器,自动开始发送
状态寄存器2(USR2)位:
-
位3(TXDC):发送完成标志
-
0:发送器忙或禁用
-
1:发送缓冲区和移位寄存器都为空
-
3.4 字符串发送函数
void puts(const char *s)
{
// 遍历字符串,逐个发送字符
while (*s)
{
putc(*s++); // 发送当前字符,指针后移
}
putc('\n'); // 发送换行符,便于阅读
}
工作原理:
-
while (*s):检查当前字符是否为字符串结束符'\0' -
putc(*s++):发送当前字符,然后指针s指向下一个字符 -
循环直到遇到字符串结束符
-
putc('\n'):额外发送换行符
3.5 字符接收函数
unsigned char getc(void)
{
// 等待接收数据就绪(检查RDR标志位)
while ((UART1->USR2 & (1 << 0)) == 0);
// 读取接收寄存器中的数据
return (unsigned char)UART1->URXD;
}
工作原理:
-
UART1->USR2 & (1 << 0):检查状态寄存器2的RDR位(位0)-
0:没有接收到数据
-
1:有数据可以读取
-
-
while循环:一直等待,直到有数据可读 -
(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 常见问题
-
没有输出/全是乱码
-
检查波特率是否匹配(PC端和开发板都设为115200)
-
检查TX、RX线是否接反
-
检查电源是否正常
-
-
只能发送不能接收
-
检查RX引脚配置是否正确
-
检查PC端串口工具是否已打开串口
-
检查线路连接是否良好
-
-
发送数据不完整
-
检查发送函数是否正确等待发送完成标志
-
检查是否有其他中断影响
-