文章目录
- 一、串口通信核心概念:UART vs USART
- 1. 基本定义
- 2. 核心通信参数(必须两端一致)
- 3. 通信原理(异步UART)
- 二、硬件基础:串口通信电路与接线
- 1. 硬件接线(核心)
- 2. 常用硬件模块
- 三、串口数据收发:三种核心模式(查询/中断/DMA)
- 1. 基础模式:查询式收发(轮询模式)
- (1)初始化配置(HAL库)
- (2)查询式发送
- (3)查询式接收
- 2. 进阶模式:中断式收发
- (1)开启串口中断(初始化时配置)
- (2)中断式接收(以IDLE中断为例,接收不定长数据)
- (3)中断式发送
- 3. 高级模式:DMA式收发
- (1)DMA初始化(以STM32F103 USART1 RX/TX DMA为例)
- (2)DMA接收(循环模式,持续接收数据)
- (3)DMA发送(单次模式,发送大数据)
- (4)DMA传输完成回调(可选)
- 四、串口调试:工具与常见问题排查
- 1. 必备调试工具
- 2. 调试步骤(从基础到复杂)
- (1)硬件调试(第一步,排除接线问题)
- (2)软件调试(参数与代码)
- 3. 常见问题与解决方案
- 五、不同平台适配要点
- 1. 51单片机串口配置(对比STM32)
- 2. Arduino串口简化开发
串口通信(UART/USART)是嵌入式开发、硬件调试中最基础且核心的通信方式,广泛应用于单片机与外设、上位机的数据交互。
一、串口通信核心概念:UART vs USART
1. 基本定义
- UART(通用异步收发器):异步通信,无需时钟线,仅通过TX(发送)、RX(接收)两根线实现双向通信,依靠约定的波特率同步数据;
- USART(通用同步异步收发器):兼容UART,额外支持同步通信(需时钟线SCK),本文以更常用的UART异步模式为核心讲解。
2. 核心通信参数(必须两端一致)
串口通信的关键是两端参数匹配,否则会出现乱码或通信失败,核心参数如下:
| 参数 | 说明 | 常用值 |
|---|---|---|
| 波特率 | 每秒传输的比特数,代表通信速率 | 9600、115200(最常用)、38400、57600 |
| 数据位 | 每个字符的有效数据位数 | 8位(主流)、7位 |
| 停止位 | 每个字符结束的标志位 | 1位(主流)、1.5位、2位 |
| 校验位 | 用于检错,可选奇/偶/无校验 | 无校验(主流)、奇校验、偶校验 |
| 流控 | 控制数据传输节奏(可选) | 无流控(主流)、硬件流控(RTS/CTS)、软件流控(XON/XOFF) |
3. 通信原理(异步UART)
- 空闲状态:TX/RX线为高电平;
- 起始位:发送端拉低电平1位时长,标志数据传输开始;
- 数据位:按“低位在前、高位在后”传输(如8位数据);
- 校验位(可选):根据数据位计算奇偶性,用于检错;
- 停止位:拉高电平1/1.5/2位时长,标志数据传输结束;
- 波特率同步:收发双方按约定波特率采样数据,无需时钟线同步。
二、硬件基础:串口通信电路与接线
1. 硬件接线(核心)
串口通信的硬件连接极其简单,核心是“交叉连接”(TX接RX,RX接TX),共地保证电平参考一致:
| 设备A(如STM32) | 设备B(如USB转串口模块/上位机) |
|---|---|
| TX(发送引脚) | RX(接收引脚) |
| RX(接收引脚) | TX(发送引脚) |
| GND(地) | GND(地) |
注意:
- 电平匹配:51单片机/STM32默认是3.3V/5V TTL电平,上位机(PC)是RS232电平(±12V),需通过USB转串口模块(如CH340、PL2303)做电平转换;
- 避免接反:TX/RX接反会导致无法收发数据,是新手最常见错误。
2. 常用硬件模块
- USB转串口模块:CH340(性价比高)、PL2303(兼容性好)、FT232(工业级,稳定);
- 单片机:STM32F103(入门首选)、51单片机(基础)、Arduino(简化开发);
- 调试工具:串口助手(上位机软件)、示波器(分析电平)、逻辑分析仪(抓包)。
三、串口数据收发:三种核心模式(查询/中断/DMA)
以STM32F103为例(寄存器/库函数/HAL库均可,本文以HAL库为例),讲解三种收发模式的实现逻辑,覆盖从基础到进阶的开发场景。
1. 基础模式:查询式收发(轮询模式)
核心逻辑:通过不断检查寄存器状态,判断是否有数据接收/发送完成,适合简单、低频率的通信场景,缺点是占用CPU资源。
(1)初始化配置(HAL库)
#include"stm32f1xx_hal.h"UART_HandleTypeDef huart1;// 串口1初始化(波特率115200,8N1:8位数据位、无校验、1位停止位)voidMX_USART1_UART_Init(void){huart1.Instance=USART1;huart1.Init.BaudRate=115200;huart1.Init.WordLength=UART_WORDLENGTH_8B;huart1.Init.StopBits=UART_STOPBITS_1;huart1.Init.Parity=UART_PARITY_NONE;huart1.Init.Mode=UART_MODE_TX_RX;// 同时开启发送和接收huart1.Init.HwFlowCtl=UART_HWCONTROL_NONE;// 无流控huart1.Init.OverSampling=UART_OVERSAMPLING_16;if(HAL_UART_Init(&huart1)!=HAL_OK){Error_Handler();}}// 底层硬件初始化(引脚、时钟)voidHAL_UART_MspInit(UART_HandleTypeDef*uartHandle){GPIO_InitTypeDef GPIO_InitStruct={0};if(uartHandle->Instance==USART1){__HAL_RCC_USART1_CLK_ENABLE();// 使能USART1时钟__HAL_RCC_GPIOA_CLK_ENABLE();// 使能GPIOA时钟// PA9(TX):推挽复用输出GPIO_InitStruct.Pin=GPIO_PIN_9;GPIO_InitStruct.Mode=GPIO_MODE_AF_PP;GPIO_InitStruct.Speed=GPIO_SPEED_FREQ_HIGH;HAL_GPIO_Init(GPIOA,&GPIO_InitStruct);// PA10(RX):浮空输入GPIO_InitStruct.Pin=GPIO_PIN_10;GPIO_InitStruct.Mode=GPIO_MODE_INPUT;GPIO_InitStruct.Pull=GPIO_NOPULL;HAL_GPIO_Init(GPIOA,&GPIO_InitStruct);}}(2)查询式发送
// 发送字符串(轮询模式)voidUART1_SendString(uint8_t*str){while(*str!='\0'){// 等待发送寄存器为空while(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_TXE)==RESET);// 发送单个字节HAL_UART_Transmit(&huart1,str,1,100);str++;}}// 主函数调用intmain(void){HAL_Init();SystemClock_Config();MX_USART1_UART_Init();while(1){UART1_SendString((uint8_t*)"Hello UART!\r\n");HAL_Delay(1000);// 每秒发送一次}}(3)查询式接收
uint8_trecv_buf[100];uint8_trecv_len=0;// 接收单个字节(轮询模式)uint8_tUART1_RecvByte(void){uint8_tdata=0;// 等待接收寄存器非空while(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_RXNE)==RESET);HAL_UART_Receive(&huart1,&data,1,100);returndata;}// 主函数中接收while(1){recv_buf[recv_len]=UART1_RecvByte();if(recv_buf[recv_len]=='\n')// 以换行符作为结束标志{recv_buf[recv_len+1]='\0';UART1_SendString((uint8_t*)"收到数据:");UART1_SendString(recv_buf);recv_len=0;// 重置接收长度}else{recv_len++;}}2. 进阶模式:中断式收发
核心逻辑:无需CPU轮询,当串口有数据接收/发送完成时,触发中断,CPU仅在中断时处理数据,大幅降低CPU占用率,适合实时性要求高的场景。
(1)开启串口中断(初始化时配置)
在HAL_UART_MspInit中添加中断配置:
voidHAL_UART_MspInit(UART_HandleTypeDef*uartHandle){// 原有引脚、时钟配置...// 开启USART1中断HAL_NVIC_SetPriority(USART1_IRQn,0,0);// 设置优先级HAL_NVIC_EnableIRQ(USART1_IRQn);// 使能中断}(2)中断式接收(以IDLE中断为例,接收不定长数据)
uint8_tuart1_recv_buf[1024];// 接收缓冲区uint16_tuart1_recv_len=0;// 接收长度uint8_tuart1_recv_flag=0;// 接收完成标志// 启动接收中断(开启RXNE和IDLE中断)voidUART1_Start_Recv_IT(void){__HAL_UART_CLEAR_FLAG(&huart1,UART_FLAG_IDLE);// 清除IDLE标志__HAL_UART_ENABLE_IT(&huart1,UART_IT_RXNE);// 开启接收非空中断__HAL_UART_ENABLE_IT(&huart1,UART_IT_IDLE);// 开启IDLE中断(无数据接收时触发)HAL_UART_Receive_IT(&huart1,&uart1_recv_buf[uart1_recv_len],1);}// USART1中断服务函数voidUSART1_IRQHandler(void){uint32_ttemp;if(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_RXNE)!=RESET)// 接收非空{HAL_UART_IRQHandler(&huart1);// 调用HAL库中断处理函数uart1_recv_len++;// 接收长度+1// 继续开启接收中断HAL_UART_Receive_IT(&huart1,&uart1_recv_buf[uart1_recv_len],1);}if(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE)!=RESET)// IDLE中断(接收完成){__HAL_UART_CLEAR_IDLEFLAG(&huart1);// 清除IDLE标志temp=huart1.Instance->SR;// 读SR寄存器(清标志)temp=huart1.Instance->DR;// 读DR寄存器uart1_recv_flag=1;// 置位接收完成标志}}// 主函数处理接收数据intmain(void){HAL_Init();SystemClock_Config();MX_USART1_UART_Init();UART1_Start_Recv_IT();// 启动接收中断while(1){if(uart1_recv_flag==1){// 发送接收到的数据HAL_UART_Transmit(&huart1,uart1_recv_buf,uart1_recv_len,1000);// 重置标志和长度uart1_recv_flag=0;uart1_recv_len=0;}}}(3)中断式发送
uint8_tuart1_send_buf[1024];uint16_tuart1_send_len=0;uint16_tuart1_send_index=0;// 启动发送中断voidUART1_Send_IT(uint8_t*buf,uint16_tlen){uart1_send_len=len;uart1_send_index=0;memcpy(uart1_send_buf,buf,len);__HAL_UART_ENABLE_IT(&huart1,UART_IT_TXE);// 开启发送空中断}// 中断服务函数中处理发送voidUSART1_IRQHandler(void){// 原有接收中断处理...if(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_TXE)!=RESET)// 发送寄存器空{if(uart1_send_index<uart1_send_len){huart1.Instance->DR=uart1_send_buf[uart1_send_index];// 发送单个字节uart1_send_index++;}else{__HAL_UART_DISABLE_IT(&huart1,UART_IT_TXE);// 关闭发送中断}}}// 主函数调用UART1_Send_IT((uint8_t*)"Interrupt Send!\r\n",16);3. 高级模式:DMA式收发
核心逻辑:直接内存访问(DMA),数据收发无需CPU参与,由DMA控制器直接在内存和串口寄存器间传输数据,CPU仅需初始化DMA和处理传输完成事件,适合大数据量、高波特率的通信场景。
(1)DMA初始化(以STM32F103 USART1 RX/TX DMA为例)
DMA_HandleTypeDef hdma_usart1_rx;DMA_HandleTypeDef hdma_usart1_tx;// 串口1 DMA初始化voidMX_USART1_DMA_Init(void){// RX DMA配置(DMA1通道5)hdma_usart1_rx.Instance=DMA1_Channel5;hdma_usart1_rx.Init.Direction=DMA_PERIPH_TO_MEMORY;// 外设到内存hdma_usart1_rx.Init.PeriphInc=DMA_PINC_DISABLE;// 外设地址不递增hdma_usart1_rx.Init.MemInc=DMA_MINC_ENABLE;// 内存地址递增hdma_usart1_rx.Init.PeriphDataAlignment=DMA_PDATAALIGN_BYTE;// 字节对齐hdma_usart1_rx.Init.MemDataAlignment=DMA_MDATAALIGN_BYTE;hdma_usart1_rx.Init.Mode=DMA_CIRCULAR;// 循环模式(持续接收)hdma_usart1_rx.Init.Priority=DMA_PRIORITY_MEDIUM;if(HAL_DMA_Init(&hdma_usart1_rx)!=HAL_OK){Error_Handler();}__HAL_LINKDMA(&huart1,hdmarx,hdma_usart1_rx);// 关联串口和DMA// TX DMA配置(DMA1通道4)hdma_usart1_tx.Instance=DMA1_Channel4;hdma_usart1_tx.Init.Direction=DMA_MEMORY_TO_PERIPH;// 内存到外设hdma_usart1_tx.Init.PeriphInc=DMA_PINC_DISABLE;hdma_usart1_tx.Init.MemInc=DMA_MINC_ENABLE;hdma_usart1_tx.Init.PeriphDataAlignment=DMA_PDATAALIGN_BYTE;hdma_usart1_tx.Init.MemDataAlignment=DMA_MDATAALIGN_BYTE;hdma_usart1_tx.Init.Mode=DMA_NORMAL;// 正常模式(单次发送)hdma_usart1_tx.Init.Priority=DMA_PRIORITY_MEDIUM;if(HAL_DMA_Init(&hdma_usart1_tx)!=HAL_OK){Error_Handler();}__HAL_LINKDMA(&huart1,hdmatx,hdma_usart1_tx);}// 在HAL_UART_MspInit中开启DMA时钟voidHAL_UART_MspInit(UART_HandleTypeDef*uartHandle){// 原有引脚、中断配置...__HAL_RCC_DMA1_CLK_ENABLE();// 使能DMA1时钟// DMA中断配置(可选,用于传输完成回调)HAL_NVIC_SetPriority(DMA1_Channel5_IRQn,1,0);HAL_NVIC_EnableIRQ(DMA1_Channel5_IRQn);HAL_NVIC_SetPriority(DMA1_Channel4_IRQn,1,0);HAL_NVIC_EnableIRQ(DMA1_Channel4_IRQn);}(2)DMA接收(循环模式,持续接收数据)
#defineUART1_DMA_BUF_LEN1024uint8_tuart1_dma_recv_buf[UART1_DMA_BUF_LEN];uint16_tuart1_dma_recv_last=0;// 启动DMA接收voidUART1_Start_DMA_Recv(void){HAL_UART_Receive_DMA(&huart1,uart1_dma_recv_buf,UART1_DMA_BUF_LEN);}// 主函数中处理DMA接收数据intmain(void){HAL_Init();SystemClock_Config();MX_USART1_UART_Init();MX_USART1_DMA_Init();UART1_Start_DMA_Recv();while(1){// 计算新接收的数据长度(循环模式下,通过当前指针和上一次指针的差值)uint16_tcurrent_len=UART1_DMA_BUF_LEN-__HAL_DMA_GET_COUNTER(&hdma_usart1_rx);uint16_tnew_len=current_len-uart1_dma_recv_last;if(new_len>0){// 发送接收到的数据(DMA发送)HAL_UART_Transmit_DMA(&huart1,&uart1_dma_recv_buf[uart1_dma_recv_last],new_len);uart1_dma_recv_last=current_len;// 更新上一次指针}}}(3)DMA发送(单次模式,发送大数据)
// DMA发送字符串voidUART1_Send_DMA(uint8_t*buf,uint16_tlen){// 等待上一次DMA发送完成while(HAL_DMA_GetState(&hdma_usart1_tx)!=HAL_DMA_STATE_READY);HAL_UART_Transmit_DMA(&huart1,buf,len);}// 主函数调用uint8_tsend_data[]="DMA Send: Hello World! This is a long data test...\r\n";UART1_Send_DMA(send_data,sizeof(send_data));(4)DMA传输完成回调(可选)
// 接收完成回调voidHAL_UART_RxCpltCallback(UART_HandleTypeDef*huart){if(huart->Instance==USART1){// 循环模式下,无需重新启动,DMA会自动继续接收// 可在此处处理接收完成逻辑}}// 发送完成回调voidHAL_UART_TxCpltCallback(UART_HandleTypeDef*huart){if(huart->Instance==USART1){// 发送完成,可置位标志}}四、串口调试:工具与常见问题排查
1. 必备调试工具
| 工具 | 用途 | 操作要点 |
|---|---|---|
| USB转串口模块(CH340/PL2303) | 单片机与上位机通信的桥梁 | 安装对应驱动,确认COM口编号 |
| 串口助手(SSCOM/串口调试助手) | 上位机收发数据、验证通信 | 配置波特率/数据位/停止位/校验位与单片机一致 |
| 示波器/逻辑分析仪 | 分析TX/RX电平、波特率、帧格式 | 抓取起始位/数据位/停止位,排查电平异常 |
| 万用表 | 测量TX/RX引脚电平、检查接线 | 空闲时TX/RX应为高电平(3.3V/5V) |
2. 调试步骤(从基础到复杂)
(1)硬件调试(第一步,排除接线问题)
- 检查接线:TX→RX、RX→TX、GND→GND,避免接反;
- 测量电平:空闲时TX/RX引脚应为高电平,发送数据时电平应高低变化;
- 测试模块:用USB转串口模块直接连接电脑,串口助手自发自收,验证模块是否正常。
(2)软件调试(参数与代码)
- 核对参数:波特率、数据位、停止位、校验位必须与上位机一致(如115200 8N1);
- 测试发送:先实现单片机向上位机发送数据,串口助手能接收则发送侧正常;
- 测试接收:上位机发送数据,单片机回显,验证接收侧逻辑;
- 中断/DMA调试:先关闭中断/DMA,用查询模式验证基础通信,再逐步开启高级模式。
3. 常见问题与解决方案
| 问题现象 | 原因分析 | 解决方案 |
|---|---|---|
| 串口助手接收乱码 | 1. 波特率不匹配;2. 晶振频率错误;3. 电平不匹配;4. 数据位/停止位错误 | 1. 核对波特率;2. 检查单片机晶振配置(如STM32系统时钟);3. 用电平转换模块;4. 确认8N1等参数一致 |
| 无法接收数据 | 1. RX/TX接反;2. 接收中断未开启;3. 缓冲区溢出;4. 引脚配置错误 | 1. 调换RX/TX接线;2. 检查中断使能代码;3. 增大缓冲区或开启DMA;4. RX引脚配置为浮空输入 |
| 中断不触发 | 1. 中断优先级配置错误;2. 中断标志未清除;3. NVIC未使能 | 1. 调整中断优先级;2. 中断服务函数中清除标志;3. 调用HAL_NVIC_EnableIRQ() |
| DMA传输失败 | 1. DMA通道配置错误;2. 内存/外设地址错误;3. DMA模式错误 | 1. 核对DMA通道(如USART1_RX对应DMA1通道5);2. 确认Periph→Memory方向;3. 接收用循环模式,发送用正常模式 |
| 数据丢失/不完整 | 1. 缓冲区太小;2. 中断响应不及时;3. 波特率过高(硬件不支持) | 1. 增大缓冲区;2. 优化中断优先级;3. 降低波特率(如从115200降至9600) |
五、不同平台适配要点
1. 51单片机串口配置(对比STM32)
// 51单片机串口初始化(9600波特率,11.0592MHz晶振)voidUART_Init(void){TMOD|=0x20;// 定时器1工作在模式2(8位自动重装)TH1=0xFD;// 9600波特率初值TL1=0xFD;TR1=1;// 启动定时器1SCON=0x50;// 串口工作在模式1,REN=1(允许接收)EA=1;// 开启总中断ES=1;// 开启串口中断}// 51串口中断服务函数voidUART_ISR(void)interrupt4{if(RI)// 接收中断{RI=0;// 清除接收标志P1=SBUF;// 接收到的数据输出到P1口SBUF=SBUF;// 回显数据while(!TI);// 等待发送完成TI=0;// 清除发送标志}}2. Arduino串口简化开发
// Arduino串口示例(9600波特率)voidsetup(){Serial.begin(9600);// 初始化串口,波特率9600}voidloop(){if(Serial.available()>0)// 有数据接收{chardata=Serial.read();// 读取单个字节Serial.print("收到:");Serial.println(data);// 回显数据}Serial.println("Arduino UART Test");delay(1000);}