news 2026/4/3 8:01:47

串口通信 UART/USART协议、数据收发、中断/DMA模式与调试全解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
串口通信 UART/USART协议、数据收发、中断/DMA模式与调试全解析

文章目录

    • 一、串口通信核心概念: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);}
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/2 1:11:38

如何导出EmotiVoice生成的语音用于商业用途?

如何安全合法地将 EmotiVoice 生成的语音用于商业项目&#xff1f; 在短视频、有声书、智能客服和虚拟偶像日益普及的今天&#xff0c;企业对“会说话、有情绪”的AI语音需求正以前所未有的速度增长。传统配音成本高、周期长&#xff0c;而市面上许多TTS工具又缺乏表现力——直…

作者头像 李华
网站建设 2026/4/1 3:48:16

EmotiVoice在有声读物制作中的高效应用方案

EmotiVoice在有声读物制作中的高效应用方案 在数字内容爆炸式增长的今天&#xff0c;越来越多用户选择“听”来消费信息——通勤时听小说、睡前听故事、工作间隙听知识类音频。然而&#xff0c;传统有声读物的生产方式却显得步履蹒跚&#xff1a;依赖专业配音演员、录制周期长、…

作者头像 李华
网站建设 2026/3/31 22:02:31

EmotiVoice实战应用:为有声读物注入真实情感

EmotiVoice实战应用&#xff1a;为有声读物注入真实情感 在数字内容消费日益增长的今天&#xff0c;有声读物、播客和虚拟角色语音正成为人们获取信息与娱乐的重要方式。然而&#xff0c;长期困扰行业的一个问题是&#xff1a;机器合成的声音虽然清晰流畅&#xff0c;却总是“面…

作者头像 李华
网站建设 2026/4/1 19:47:42

零样本声音克隆黑科技!EmotiVoice让AI语音更个性化

零样本声音克隆黑科技&#xff01;EmotiVoice让AI语音更个性化 在智能语音助手越来越“能说会道”的今天&#xff0c;你有没有想过&#xff1a;为什么Siri听起来永远冷静理性&#xff0c;而电影里的AI角色却可以愤怒、悲伤甚至带着讽刺的语气说话&#xff1f;问题不在于技术做不…

作者头像 李华
网站建设 2026/3/30 5:33:17

LobeChat讯飞星火认知大模型集成步骤

LobeChat 集成讯飞星火&#xff1a;打造安全可控的中文智能对话系统 在企业对 AI 助手的需求从“能用”转向“好用、可控、合规”的今天&#xff0c;如何快速构建一个支持国产大模型、界面友好且具备生产级能力的聊天应用&#xff0c;成为许多开发团队面临的真实挑战。市面上不…

作者头像 李华
网站建设 2026/4/3 4:48:27

EmotiVoice语音合成模型的温度参数对情感表达的影响研究

EmotiVoice语音合成中温度参数对情感表达的调控机制研究 在虚拟助手越来越频繁地进入日常生活的今天&#xff0c;人们早已不再满足于“机器能说话”这一基础功能。我们期待的是一个能感知情绪、会表达喜怒哀乐的“有温度”的声音——无论是游戏里怒吼的BOSS&#xff0c;还是睡前…

作者头像 李华