ARM嵌入式 --- UART串口通信

张开发
2026/6/8 10:19:43 15 分钟阅读
ARM嵌入式 --- UART串口通信
一、串口通信基础概念1.1 串行与并行通信串口通信的核心是串行传输数据按位依次在一根线上传输而并行通信是多根线同时传输多位数据。串行通信线路少、抗干扰强、传输距离远适合嵌入式设备的外设通信如 UART、USB、SPI并行通信速度快但抗干扰差、距离短现在嵌入式中已很少使用1.2 同步与异步通信同步通信收发双方共用同一个时钟信号严格同步如 SPI、I2C需要额外时钟线异步通信收发双方各自使用本地时钟通过约定波特率同步UART 就是典型的异步串行通信仅需 TX/RX 两根线即可通信1.3 通信方式分类UART 属于全双工异步串行通信全双工TX 和 RX 独立工作可同时收发数据异步无共享时钟靠波特率、起始位 / 停止位同步串行单 bit 逐位传输1.4 串口通信标准常用的串口标准有 RS-232、RS-485 等嵌入式开发中最常用的是TTL 电平串口3.3V/5V 逻辑电平通过 USB 转 TTL 模块即可和电脑通信无需额外电平转换芯片。1.5 波特率与数据格式波特率单位是 bpsbit per second表示每秒传输的二进制位数常用值9600、115200、1152000 等。收发双方必须设置完全一致的波特率否则会出现乱码。数据格式标准 UART 数据帧格式起始位(1bit) 数据位(5-8bit常用8bit) 校验位(可选奇/偶校验) 停止位(1/1.5/2bit常用1bit)本次代码使用的是8 位数据位、1 位停止位、无校验位是嵌入式开发最通用的配置。二、硬件与开发环境2.1 核心硬件连接本次基于NXP i.MX6ULL开发板实现 UART1 串口通信硬件连接如下开发板 UART1_TX → USB 转 TTL 模块的 RX开发板 UART1_RX → USB 转 TTL 模块的 TX开发板 GND → USB 转 TTL 模块的 GND必须共地否则电平不匹配电脑端通过串口调试助手如 Xshell、MobaXterm收发数据波特率设置 1152008N1 格式2.2 核心芯片作用i.MX6ULL 内部集成了多个 UART 控制器UART1~UART8本次使用 UART1 控制器其核心作用完成并行数据→串行数据的转换发送时、串行数据→并行数据的转换接收时自动生成起始位、停止位校验位可选波特率发生器根据配置生成对应波特率的发送 / 接收时钟提供 FIFO 缓存提升数据收发的稳定性2.3 核心寄存器i.MX6ULL 的 UART 控制器通过寄存器配置核心寄存器如下表寄存器作用关键位定义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]: RXD MUXSEL (接收多路复用选择)UART1_UFCRFIFO 控制寄存器[7-9]: RFDIV (参考时钟分频)UART1_UBIR波特率增量寄存器[0-15]: UBIRUART1_UBMR波特率模数寄存器[0-15]: UBMRUART1_USR2状态寄存器 2[0]: RDR (接收数据就绪)[3]: TXDC (发送完成)三、代码实现3.1 初始化过程UART 初始化是串口通信的核心分为 5 个关键步骤步骤 1引脚复用与电气配置i.MX6ULL 的引脚是复用的必须先将 UART1_TX/RX 对应的引脚配置为串口功能再配置电气特性上拉、驱动强度等保证信号稳定。// 引脚复用 IOMUXC_SetPinMux(IOMUXC_UART1_RX_DATA_UART1_RX, 0); IOMUXC_SetPinMux(IOMUXC_UART1_TX_DATA_UART1_TX, 0); // 电气配置 IOMUXC_SetPinConfig(IOMUXC_UART1_RX_DATA_UART1_RX, 0x10b0); IOMUXC_SetPinConfig(IOMUXC_UART1_TX_DATA_UART1_TX, 0x10b0);步骤 2软件复位 UART配置前先关闭 UART执行软件复位避免原有配置干扰新配置UART1-UCR2 ~(1 0); // 软件复位 delay_us(1); // 等待复位完成步骤 3数据格式与功能配置配置 UART2 控制寄存器设置8 位数据位、1 位停止位、无校验位忽略流控使能收发unsigned int tmp UART1-UCR2; tmp | (1 14); // 忽略RTS流控 tmp ~(1 8); // 关闭奇偶校验 tmp ~(1 6); // 1位停止位 tmp | (1 5); // 8位数据位 tmp | (1 2); // 使能发送 tmp | (1 1); // 使能接收 UART1-UCR2 tmp;步骤 4波特率配置i.MX6ULL 的 UART 波特率计算公式Baud (REF_CLK) / (16 * (UBMR 1)/(UBIR 1))本次使用 80MHz 参考时钟目标波特率 115200计算得UBIR999UBMR43404同时配置 FIFO 时钟分频为 1// 配置FIFO分频 tmp UART1-UFCR; tmp ~(0x7 7); tmp | (0x5 7); UART1-UFCR tmp; // 配置波特率寄存器 UART1-UBIR 999; UART1-UBMR 43404;步骤 5使能 UART最后通过 UCR1 寄存器的 UARTEN 位使能 UART 控制器完成初始化UART1-UCR1 | (1 0);3.2 数据发送函数发送单字节的核心逻辑等待发送完成→写入发送寄存器通过USR2寄存器的TXDC位第 3 位判断发送是否完成为 1 时表示发送寄存器为空可以写入新数据void uart_send_byte(const unsigned char data) { while(!(UART1-USR2 (1 3))); // 等待发送完成 UART1-UTXD data; // 写入发送寄存器 }同时实现了putc函数用于后续标准 I/O 库的重定向逻辑完全一致。3.3 数据接收函数接收单字节的核心逻辑等待接收就绪→读取接收寄存器通过USR2寄存器的RDR位第 0 位判断是否有数据就绪为 1 时表示接收寄存器有有效数据读取URXD寄存器即可unsigned char uart_recv_byte(void) { unsigned char data 0; while(!(UART1-USR2 (1 0))); // 等待接收就绪 data UART1-URXD 0xff; // 读取数据仅取低8位 return data; }同时实现了getc函数用于标准 I/O 库的重定向。3.4 标准 I/O 库移植3.4.1 移植步骤嵌入式开发中我们通常需要用printf打印调试信息scanf读取用户输入因此需要将标准 I/O 库重定向到 UART重定向putc/getc函数将putc绑定到 UART 发送getc绑定到 UART 接收实现空的raise函数标准库链接时需要raise函数空实现即可避免报错修改链接脚本 / 编译选项确保编译器使用微库如--specsnosys.specs避免系统调用报错本次代码已经完成了putc/getc/raise的实现直接编译即可支持printf/scanf。3.5 完整uart.c代码如下#include MCIMX6Y2.h #include fsl_iomuxc.h #include uart.h #include gpt.h /** * brief UART1初始化函数 * note 配置波特率1152008N1格式使能收发 * param 无 * retval 无 */ void uart_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先关闭UART再配置 UART1-UCR2 ~(1 0); delay_us(1); // 等待复位完成 // 4. 配置UART2控制寄存器8N1格式忽略流控使能收发 unsigned int tmp UART1-UCR2; tmp | (1 14); // 忽略RTS流控 tmp ~(1 8); // 关闭奇偶校验 tmp ~(1 6); // 1位停止位 tmp | (1 5); // 8位数据位(WS1) tmp | (1 2); // 使能发送(TXEN) tmp | (1 1); // 使能接收(RXEN) UART1-UCR2 tmp; // 5. 配置UART3控制寄存器使能接收多路复用 UART1-UCR3 | (1 2); // 6. 配置FIFO控制寄存器参考时钟分频为1 tmp UART1-UFCR; tmp ~(0x7 7); // 清除原有分频 tmp | (0x5 7); // 设置分频为1(RFDIV5) UART1-UFCR tmp; // 7. 配置波特率115200公式Baud (REF_CLK) / (16 * (UBMR 1)/(UBIR 1)) // REF_CLK80MHz计算得UBIR999UBMR43404 UART1-UBIR 999; UART1-UBMR 43404; // 8. 使能UART控制器 UART1-UCR1 | (1 0); } /** * brief UART1发送单字节数据 * param data: 待发送的字节 * retval 无 */ void uart_send_byte(const unsigned char data) { // 等待发送完成(TXDC位为1) while(!(UART1-USR2 (1 3))); // 写入发送寄存器 UART1-UTXD data; } /** * brief UART1接收单字节数据 * param 无 * retval 接收到的字节 */ unsigned char uart_recv_byte(void) { unsigned char data 0; // 等待接收数据就绪(RDR位为1) while(!(UART1-USR2 (1 0))); // 读取接收寄存器仅取低8位 data UART1-URXD 0xff; return data; } /** * brief 标准库putc重定向用于printf * param data: 待发送的字符 * retval 发送的字符 */ void putc(unsigned char data) { while(!(UART1-USR2 (1 3))); UART1-UTXD data; } /** * brief 标准库getc重定向用于scanf * param 无 * retval 接收到的字符 */ unsigned char getc(void) { unsigned char data 0; while(!(UART1-USR2 (1 0))); data UART1-URXD 0xff; return data; }四、总结1. UART 本质通用异步收发器全双工、异步串行通信接口不需要时钟线靠约定波特率同步只需要TX、RX、GND即可通信2. 通信帧格式标准格式起始位 (1bit) 数据位 (8bit) 校验位 (可选) 停止位 (1bit)常用配置8N18 位数据、无校验、1 位停止3. 波特率是什么为什么必须一致波特率每秒传输的比特数115200 最常用收发双方波特率必须严格一致否则乱码本质用相同采样时钟对齐数据位4. i.MX6ULL UART 初始化核心步骤引脚复用配置 TX/RX 为 UART 功能电气属性设置上下拉、速度、驱动能力软件复位关闭 UART 再配置配置帧格式8N1、使能收发、关闭流控配置波特率设置 UFCR、UBIR、UBMR使能 UARTUARTEN 位置 15. 发送 / 接收原理发送查询 TX 空标志 → 写数据到 UTXD接收查询 RX 就绪标志 → 从 URXD 读数据标志位在 USR2 寄存器RDR (0)接收就绪TXDC (3)发送完成6. 轮询 vs 中断轮询简单稳定、占用 CPU、低效率中断CPU 利用率高、实时性强、代码复杂调试用轮询工程多用中断 DMA7. 常见问题与排错乱码波特率不对 / 时钟配置错误发不出数据TX 引脚未复用、UART 未使能收不到数据RX 引脚配置错误、共地不良丢数据FIFO 未开、波特率过高、CPU 太忙UART 异步全双工串口8N1 格式波特率必须一致靠时钟采样对齐初始化引脚→复位→帧格式→波特率→使能收发查标志位TX 空、RX 就绪重定向 putc 实现 printf轮询简单中断高效

更多文章