STM32 UART串口通信详解:接收与发送HEX与文本数据的代码实践

UART串口通信

  • 一 UART通信原理
  • 1 UART基本介绍
  • 2 配置方式
  • 二 收发数据函数编写
  • 1 向串口发送数据
  • 2 调试优化之:printf重定向
  • 3 接收串口数据
  • (1) HEX模式:单个变量
  • (2) HEX模式:一次接收一数组存储
  • (3) 文本模式:接收字符串
  • 三 完整代码
  • 1 封装.h文件
  • 2 封装.c文件
  • 3 main函数测试demo
  • 4 测试图
  • 一 UART通信原理

    本章介绍UART原理与配置方式

    1 UART基本介绍

    (1) 原理
    异步传输方式:UART通信采用异步传输方式,即发送端和接收端不需要通过时钟线来同步数据传输。双方只需约定相同的波特率(每秒传输的位数),就可以进行数据通信。

    (2) 数据帧结构:
    UART通信的数据帧通常由起始位数据位、可选的奇偶校验位和停止位组成。

  • 起始位:用于标识数据帧的开始,通常为一个逻辑“0”;
  • 数据位:是要传输的实际数据,可以是5到9位,常见的是8位
  • 奇偶校验:位用于检测数据传输过程中的错误,可以设置为奇校验或偶校验;
  • 停止位:用于标识数据帧的结束,可以是1位、1.5位或2位。
  • (3) 通信过程
    发送过程:当需要发送数据时,发送端首先将待发送的并行数据转换为串行数据,然后按照约定的波特率将数据逐位发送到数据线上。在发送数据之前,发送端会先发送一个起始位,表示数据帧的开始,然后依次发送数据位可选的奇偶校验位停止位。数据发送完毕后,发送端会等待接收端的确认信号(如果有的话),或者继续发送下一个数据帧。

    接收过程:接收端在接收数据时,会不断检测数据线上的信号变化。当检测到起始位时,接收端开始以约定的波特率对数据进行采样,并将采样得到的数据位存储起来。接收完所有数据位后,接收端会根据奇偶校验位(如果有的话)对数据进行校验,以确保数据传输的正确性。如果校验通过,接收端将接收到的数据转换为并行数据,并交给后续的处理程序进行处理;如果校验失败,接收端可能会请求发送端重新发送数据帧。

    (4) 应用场景
    常用于简单的异步串口通信应用,如与外围设备的基本通信、蓝牙模块与微控制器通信、GPS模块与计算机之间的数据传输、调制解调器与计算机之间的串行通信等,一种简单实用的异步串口通信协议,适合基本通信需求。另外使用微控制器对外设进行控制调试时,有很大的帮助。

    波特率:波特率表示单位时间内通过线路传输的二进制数据的位数,通常用bps(bits per second)表示。例如,如果波特率为9600bps,则每秒钟可以传输9600个比特位的数据。

    2 配置方式

    在STM32 中,使用串口通信控制外设通常涉及以下几个步骤:初始化串口、配置中断、编写接收和发送数据的函数

    接线比较简单,以USB转串口模块连接stm32f103c8t6为例:(不同开发板需要 对照引脚图,选择合适的引脚,合适的引脚也未必只有一对,这里以A10A9为例)

    STM32提供的RX TX接口 开发板 USB转串口
    uart1_TX A9 RX
    uart1_RX A10 TX

    <1> 初始化串口配置
    我们不需要从通信协议开始编写,使用stm32提供的串口设置进行更改,然后在各种串口助手软件中,对应参数修改一致即可成功连接。重点关于起始位,波特率,数据位,停止位的设置。

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;   //TX初始-A9 
    GPIO_InitStructure.GPIO_Pin =  GPIO_Pin_9; 
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA,&GPIO_InitStructure);  
    	
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;     //RX初始-A10
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    	  
    	
    //USART赋值结构体
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);	//开启USART1的时钟
    USART_InitTypeDef USART_InitStructure;  
    USART_InitStructure.USART_BaudRate = 9600;    					//波特率
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; 
    USART_InitStructure.USART_Mode = USART_Mode_Tx| USART_Mode_Rx; //模式设置
    USART_InitStructure.USART_Parity = USART_Parity_No; 			//奇偶效验
    USART_InitStructure.USART_StopBits = USART_StopBits_1;  		//停止位
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;		//数据长度
    USART_Init(USART1,&USART_InitStructure);
    USART_Cmd(USART1,ENABLE); //选中串口
    

    <2> 打开中断
    使用串口通信进行调试时,更加方便。

    /*中断输出配置*/
    USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);			//开启串口接收数据的中断
    /*NVIC中断分组*/
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);			//配置NVIC为分组2
    /*NVIC配置*/
    NVIC_InitTypeDef NVIC_InitStructure;					//定义结构体变量
    NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;		//选择配置NVIC的USART1线
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;			//指定NVIC线路使能
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;		//指定NVIC线路的抢占优先级为1
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;		//指定NVIC线路的响应优先级为1
    NVIC_Init(&NVIC_InitStructure);							//将结构体变量交给NVIC_Init,配置NVIC外设
    

    二 收发数据函数编写

    本章介绍如何实现向串口收发数据,并简单介绍printf重定向

    1 向串口发送数据

    标准库已提供:使用UART发送1字节数据函数

    USART_SendData(USART1,Byte);
    

    基于此可简单设计处发送:字节,数组,字符串,数字/变量等基础操作,不在赘述。

    STM32 提供 USART模式,但USART 模块可以通过配置寄存器来选择工作在 UART 模式还是 USART 模式
    UART:仅支持异步通信模式,没有共享时钟信号,发送方和接收方通过预设的波特率来保持同步。
    USART:支持同步和异步两种通信模式,既可以像UART一样以异步方式工作,也可以在同步模式下通过共享时钟信号来实现更精确的数据同步


    2 调试优化之:printf重定向

    本节简介重定向原因 与 可变参数列表知识

    (1) 重定向原因
    虽然由上一点可以设计出多个功能分离的发送函数,但分化太多会显得有点啰嗦,用起来并不方便。而如果能使用printf的输出方式,则非常的简单,一行语句就可以控制输出复杂的格式,printf 是一个功能强大且使用广泛简单的格式化输出函数。

    并且printf支持通过重写一些操作,把输出内容重定向串口。于是就可以直接在串口调试助手查看程序的运行结果和变量的值。无需频繁地下载程序或使用其他复杂的调试工具,大大提高了调试效率。

    重定向方法:通过重写fputc函数,将printf的输出重定向到UART串口。

    注意:需要头文件 stdio,和stdarg

    (2) 涉及stdarg.h宏简介
    stdarg.h 头文件中提供了一些宏和类型定义,允许编写可接受可变数量参数的函数。在stm32串口通信中,常常需要实现类似 printf 这样的可变参数函数,以便能够方便地输出各种不同类型的数据。通过使用 stdarg.h 中的宏,如 va_startva_argva_end 等,可以在函数内部获取和处理可变数量的参数,从而实现灵活的数据处理和输出。

  • va_list:用于声明一个指向可变参数列表的指针类型变量。该变量通常用于存储可变参数列表中的参数信息。

  • va_start:初始化va_list类型的变量,使其指向可变参数列表的第一个参数。它需要两个参数va_start(a,b),第一个是va_list类型的变量,第二个是最后一个固定参数的地址。

  • va_arg:获取可变参数列表中的下一个参数,并将其转换为指定类型。它需要两个参数,第一个是va_list类型的可变参数列表指针,第二个是要获取的参数的类型。调用va_arg后,可变参数列表指针会自动指向下一个参数。

  • va_end:结束可变参数列表的访问,清理va_list类型的变量,以便该变量可以被再次使用。


  • 3 接收串口数据

    (1) HEX模式:单个变量

    可用于控制单个外设打开关闭,如:收到串口数据0,关闭外设;收到1打开外设;亦或则是收到3打开更多模式等,有助于调试外设模块,比起来在单独接一个开关模块,要方便并且稳定快速的多,可供选择的模式也更加丰富。

    核心代码:

    //从串口接收一个数据,返回值类型为uint16_t
    USART_ReceiveData(USART1);
    

    将此代码放在中断函数中,每次收到数据后,将数据保存下来即可,可以用全局变量保存,并写一个简单的函数,获取这个变量的值即可。

    /*
    功能:接受串口发送的单个整数数据,可以用串口控制外设
    参数:串口HEX模式,输入一个值发送,可用该函数接收
    解释:接收到串口数据的值,(文本格式也测试,但无法保存,仅能查看)
          方便调试,如 0 关闭外设,1打开外设,简化接线
    返回: 根据具体输入有关,若无输入,则-1表示无输入,常用01即可
    
    **HEX模式请发送 2位数据,如发送0为:0x00 ,并非是整数格式,只是0x省略了而已**
    */
    int Serial_RXInt(void){
    		if(receive_int!=-1){  //检测到输入
    			int tem=receive_int;
    			receive_int=-1;
    		return tem;
    		}  
    		return -1;     //未检测到输入
    }
    

    (2) HEX模式:一次接收一数组存储

    当我们需要同时设置多个外设的模式时,只发单个值已不能满足需求,可以自定义一种特殊的发送格式,加上起始判断与结束位判断,保留中间从串口接收的数据,并将这些数据保存在一个全局数组中即可。对于多为数据的存储方式,使用不同的flag状态处理。(万能的fllag)。最后在设置一个全局标记,用于记录是获取到了串口数据,及时更新即可。

    核心代码:

    uint8_t receive[4];  //值:从串口接受的数组数据(限定4元素)
    int receive_arry;    //标记:是否从串口收到完整数组
    
    static uint8_t RxState = 0;//状态标记
    static uint8_t k = 0 ;     //数组下标
    //--------------------核心部分,此处在触发中断后执行-------------
    if(RxState==0){
      if(RX_data==0xFF){   //接受数组:数组RxState状态-012
          RxState=1;
    	   k=0;
    	}else
    		receive_int=RX_data;  //单个数据 不需要状态
    }else if(RxState==1){
    	receive[k++]=RX_data;
    	if(k>=4)     //修改K最大的值与 receive 数组下标,可修改一次发送的数据量
    		RxState=2;
    }else if(RxState==2){
    	if(RX_data==0xFE){    //次数必须有条件限制
    	    RxState=0;
    		receive_arry=1;
        } 
    }
    

    根据标记值,判断是否收到数据:

    /*
    功能:确定串口是否发送了数组数据,数据保存在receive[]全局数组
    参数:串口发送格式:FF开头,FE结尾,中间4个数,如:FF 00 01 02 03 FE
    解释:方便多个外设调试,如FF 00 01 00 01 FE,一键设定4个外设状态
    返回: 1表示输入完成存入receive数组,0表示未成功接收
    
    **HEX模式请发送 2位数据,如发送0为:0x00 ,并非是整数格式,只是0x省略了而已**
    */
    int Serial_RXArry(void){
    		if(receive_arry){  //检测到输入
    			receive_arry=0;
    		  return 1;
    		}
    		receive_arry=0;//常态位0
    		return 0;     //未检测到输入
    }
    

    (3) 文本模式:接收字符串

    可以用一个字符口令控制外设开关,如:Turn on ,Turn off等等,更有利于观察理解。实现原理同上述数组,只不过开始标志与结束标志换成字符而已。核心代码:

    uint8_t receive[4];  //值:从串口接受的数组数据(限定4元素)
    int receive_arry;    //标记:是否从串口收到完整数组
    
    //----------------------中断函数内部-----------------------
    if(RX_data == '@'){ //接受文本:状态-0 11 12 ,与数组区分
    	 RxState=11;
    	 k=0;  //文本和数组,同时只有一个占用
    }else if(RxState==11){
    	 if (RX_data == '#')	 //以输入#判定结束,开始与结束不要相同
    		 RxState = 12;     //因为字符串没有长度限制,只能设定标记
    	 else
    		 text[k++] = RX_data;
    }else if(RxState==12){
    	if (RX_data == '#'){   //*此处一定有条件限制,否则进入RxState12状态后
    	  RxState=0;           //会无限进入此处if语句
    	 text[k]='\0';
    	 receive_text = 1;	
    	 k=0;
    	}
    }
    

    同理,判断是否收到数据:

    /*
    功能:确定串口是否发送了文本数据,数据保存在字符数组text[]
    参数:数组格式:@开头,##结尾,中间为文本内容,支持空格换行。如:@sample test@
    解释:可以用英文口令控制外设开关,如:turn on,turn off,使用strcmp匹配判断相等
    返回: 1表示输入完成存入text数组,0表示无输入
    */
    int Serial_RXString(void){
    	if(receive_text){  //检测到输入
    			receive_text=0;
    		  return 1;
    		}
    		receive_text=0;//常态为0
    		return 0;     //未检测到输入
    }
    

    最后把接收单个字符数组,与文本的判断全部组合在一起,互不影响就可以完成基础功能 完善的串口通信模板。


    三 完整代码

    按需取用

    1 封装.h文件

    #ifndef __SERIAL_H
    #define __SERIAL_H
    
    /*接线:
                 开发板   USB转串口  
    (uart1_TX)   A9   -> RX  
    (uart1_RX)   A10  -> TX
    */
    
    //串口接收数据保存位置
    extern uint8_t send[4];     //封装要发送的数组,Serial_SendPacket() 快速发送
    extern uint8_t receive[4];  //从RX接收的4元素数组,方便调试多个外设
    extern char text[100];      //从RX接收的字符串
    
    //串口发送
    void Serial_Init(void);
    void Serial_SendByte(u8 Byte);
    void Serial_SendArray(u8 *Array,u16 Length);
    void Serial_SendPacket(void);   //快速发送send[]数组,提供全局send[]数组
    void Serial_SendString(char *String);  //串口输出中文时,确定好UTF-8与GBK格式
    u32 Serial_Pow(u32 X,u32 Y);
    void Serial_SendNumber(u32 Number,u8 Length);
    
    void Serial_Printf(char *format,...); //printf重定向,用法同printf
    
    
    //串口接收
    int Serial_RXInt(void);       //收到单个HEX数据
    int Serial_RXArry(void);      //收到连续4个HEX数据,1表示receive已存入全局数组
    int Serial_RXString(void);    //收到文本数据
    
    #endif
    
    

    2 封装.c文件

    #include "stm32f10x.h" 
    #include <stdio.h> //用于printf函数
    #include <stdarg.h>  //用于printf重定向
    
    uint8_t send[4];     //向串口打包发送的数据
    int receive_int;     //值:从串口接受的单个数据
    
    uint8_t receive[4];  //值:从串口接受的数组数据(限定4元素)
    int receive_arry;    //标记:是否从串口收到完整数组
    
    char text[100];      //值:从串口收到的文本  (限定100长度)
    int receive_text;    //标记:是否从串口收到文本
    
    //初始化引脚,与配置内部UART
    void Serial_Init(void)
    {
    		RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
    		RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);	//开启USART1的时钟
    
        GPIO_InitTypeDef GPIO_InitStructure;
    	  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  //TX初始
        GPIO_InitStructure.GPIO_Pin =  GPIO_Pin_9; 
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_Init(GPIOA,&GPIO_InitStructure);  
    	
    		GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //RX初始
    	  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
    	  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    	  GPIO_Init(GPIOA, &GPIO_InitStructure);
    	  
    	
    	  //USART赋值结构体
    	  USART_InitTypeDef USART_InitStructure;  
        USART_InitStructure.USART_BaudRate = 9600;    //波特率
        USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; 
        USART_InitStructure.USART_Mode = USART_Mode_Tx| USART_Mode_Rx; //模式设置
        USART_InitStructure.USART_Parity = USART_Parity_No; //奇偶效验
        USART_InitStructure.USART_StopBits = USART_StopBits_1;  //停止位
        USART_InitStructure.USART_WordLength = USART_WordLength_8b;//数据长度
        USART_Init(USART1,&USART_InitStructure);
        USART_Cmd(USART1,ENABLE); //选中串口
    	  
    		/*中断输出配置*/
    		USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);			//开启串口接收数据的中断
    		/*NVIC中断分组*/
    		NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);			//配置NVIC为分组2
    		/*NVIC配置*/
    		NVIC_InitTypeDef NVIC_InitStructure;					//定义结构体变量
    		NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;		//选择配置NVIC的USART1线
    		NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;			//指定NVIC线路使能
    		NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;		//指定NVIC线路的抢占优先级为1
    		NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;		//指定NVIC线路的响应优先级为1
    		NVIC_Init(&NVIC_InitStructure);							//将结构体变量交给NVIC_Init,配置NVIC外设
    
    }
    
    
    //向串口发送数据,以字节发送
    void Serial_SendByte(u8 Byte)
    {
        USART_SendData(USART1,Byte);
        while (USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET);
    	  /*下次写入数据寄存器会自动清除发送完成标志位,故此循环后,无需清除标志位*/
    }
    
    //数组名,长度
    void Serial_SendArray(u8 *Array,u16 Length)
    {
        u16 i;
        for ( i = 0; i < Length; i++)
        {
            Serial_SendByte(Array[i]);
        }
    }
    
    //发字符串
    void Serial_SendString(char *String)
    {
        u8 i;
        for(i=0; String[i]!='\0'; i++) // 以'\0'位结尾
        {
            Serial_SendByte(String[i]); 
        }
    }
    
    //发数字,x的y次方
    u32 Serial_Pow(u32 X,u32 Y)        //2 3
    {
        u32 Result = 1;
        while(Y--)                     //3--
        {
            Result *= X;               //1*2 *2 *2 = 2^3
        }
        return Result;
    }
    
    //发一个数字,然后是数字位数
    void Serial_SendNumber(u32 Number,u8 Length)
    {
        u8 i;
        for ( i = 0; i < Length; i++)
        {
            /*
               (Number / Serial_Pow(10,i) % 10)     1234/1%10=4   1234/10%10=3 1234/100%10=2
                (Number / Serial_Pow(10, Length - i -1) % 10)
    
            */
            Serial_SendByte((Number / Serial_Pow(10,Length - i - 1) % 10) + '0');    
        }
        
    }
    
    
    /*printf重定向,可通过重写fputc函数,将printf的输出重定向到USART串口。
    (0) printf原理:用于向标准输出设备(通常是屏幕)打印格式化文本,带缓冲区。
    (1) 头文件 stdio,和stdarg
    (2) 通过重写fputc函数,将printf的输出重定向到USART串口
    
    
    功能:将ch传递Serial_SendByte,发送到串口
    参数1:要发送的字符。它是一个整数类型,但通常表示为一个字符(例如 'A'、'B' 等)
    参数2:FILE *f: 这是一个指向文件结构体的指针。没用,为了符合标准库函数的格式签名
    */
    
    int fputc(int ch,FILE *f)
    {
        Serial_SendByte(ch);
        return ch;
    }
    
    /*实现可变参数列表
     stdarg.h宏:va_list、va_start、va_arg和va_end,遍历并获取参数值
    */
    void Serial_Printf(char *format,...)
    {
        char String[100];
        va_list arg;       //声明指针变量arg(可变参数列表)
    	  va_start(arg,format);  //初始化指针,指向初始位置
        vsprintf(String,format,arg); //处理好后的文本存储到strring,arg引导下一项
        va_end(arg);               //结束访问
        Serial_SendString(String);
    }
    
    /*
    功能: 封装发数据包,设置一固定全局数组send,方便修改与发送
    参数:-
    解释:部分串口软件 HEX模式不方便换行,长度取一个4便于观察
          FF 与 FE 一般 用不到,所以作开始和结束符号
    */
    void Serial_SendPacket(void){
    	Serial_SendByte(0xFF); //开始
    	Serial_SendArray(send,4);      //可修改调整send数组下标 与 该处数字4
    	Serial_SendByte(0xFE); 
    }
    
    /*
    功能:接受串口发送的单个整数数据,可以用串口控制外设
    参数:串口HEX模式,输入一个值发送,可用该函数接收
    解释:接收到串口数据的值,(文本格式也测试,但无法保存,仅能查看)
          方便调试,如 0 关闭外设,1打开外设,简化接线
    返回: 根据具体输入有关,若无输入,则-1表示无输入,常用01即可
    
    **HEX模式请发送 2位数据,如发送0为:0x00 ,并非是整数格式,只是0x省略了而已**
    */
    int Serial_RXInt(void){
    		if(receive_int!=-1){  //检测到输入
    			int tem=receive_int;
    			receive_int=-1;
    		return tem;
    		}  
    		return -1;     //未检测到输入
    }
    
    
    /*
    功能:确定串口是否发送了数组数据,数据保存在receive[]全局数组
    参数:串口发送格式:FF开头,FE结尾,中间4个数,如:FF 00 01 02 03 FE
    解释:方便多个外设调试,如FF 00 01 00 01 FE,一键设定4个外设状态
    返回: 1表示输入完成存入receive数组,0表示未成功接收
    
    **HEX模式请发送 2位数据,如发送0为:0x00 ,并非是整数格式,只是0x省略了而已**
    */
    int Serial_RXArry(void){
    		if(receive_arry){  //检测到输入
    			receive_arry=0;
    		  return 1;
    		}
    		receive_arry=0;//常态位0
    		return 0;     //未检测到输入
    }
    
    
    /*
    功能:确定串口是否发送了文本数据,数据保存在字符数组text[]
    参数:数组格式:@开头,##结尾,中间为文本内容,支持空格换行。如:@sample test@
    解释:可以用英文口令控制外设开关,如:turn on,turn off,使用strcmp匹配判断相等
    返回: 1表示输入完成存入text数组,0表示无输入
    */
    int Serial_RXString(void){
    	if(receive_text){  //检测到输入
    			receive_text=0;
    		  return 1;
    		}
    		receive_text=0;//常态为0
    		return 0;     //未检测到输入
    }
    
    
    /*
    功能:USART1中断函数
    参数:无
    解释:中断函数,无需调用,中断触发后自动执行,请确保函数名正确
          *UART通信格式:USART_ReceiveData返回输入的值。
          内含有对于RX数据的接收:含单个数据,4元素数组,与字符串文本
    注意:在不同的RxState状态模式下,每当进入一个新的RxState状态,内部必须有条件限制
          否则将会无限进入此状态分支,造成错误与延时
    */
    void USART1_IRQHandler(void)
    {
    	static uint8_t RxState = 0;//状态标记
    	static uint8_t k = 0 ;     //数组下标
    	if(USART_GetITStatus(USART1, USART_IT_RXNE) == SET){ //判断是否是USART1的接收事件触发的中断
    		uint8_t RX_data = USART_ReceiveData(USART1); //存放接受的数据
    		
    		if(RxState==0){
    			if(RX_data==0xFF){   //接受数组:数组RxState状态-012
    				 RxState=1;
    				 k=0;
    			}
    			else if(RX_data == '@'){ //接受文本:状态-0 11 12 ,与数组区分
    				 RxState=11;
    				 k=0;  //文本和数组,同时只有一个占用
    			}else
    				receive_int=RX_data;  //单个数据 不需要状态
    		}else if(RxState==1){
    			receive[k++]=RX_data;
    			if(k>=4)               //修改K最大的值与 receive 数组下标,可修改一次发送的数据量
    				RxState=2;
    		}else if(RxState==2){
    			if(RX_data==0xFE){    //次数必须有条件限制
    				RxState=0;
    				receive_arry=1;
    			}
    		}else if(RxState==11){
    			if (RX_data == '#')	 //以输入#判定结束,开始与结束不要相同
    				RxState = 12;     //因为字符串没有长度限制,只能设定标记
    			else
    				text[k++] = RX_data;
    		}else if(RxState==12){
    			if (RX_data == '#'){   //*此处一定有条件限制,否则进入RxState12状态后
    			  RxState=0;           //会无限进入此处if语句
    				text[k]='\0';
    				receive_text = 1;	
    			  k=0;
    			}
    		}
    		USART_ClearITPendingBit(USART1, USART_IT_RXNE);		//清除标志位
    	}
    }
    
    

    3 main函数测试demo

    #include "stm32f10x.h"                  // USE_STDPERIPH_DRIVER
    #include "delay.h"
    #include "Serial.h"
    #include "OLED.h" //可不加
    
    int main(void)
    {
    			Serial_Init();
    	    /*基本测试
    	     //1 TX发送 测试
    	     Serial_SendByte(127);
    	     uint8_t a[]={1,2,3,4,5,6,7,8,9};
    	     Serial_SendArray(a,3);
    	     Serial_SendString("给我输中文,混蛋\r\n");  
    	     Serial_SendNumber(i,2);
    	     for(int i=0; i<4;i++)   
    	       send[i]=i;
    			  Serial_SendPacket();   //封装发送
    	
    	     //2 几种流输入输出
    	     char c[20];
    	     int a=1010,b=99; 
    		   printf("a=%d,b=%d\r\n",a,b);          //一处
    	     sprintf(c,"num3 a=%d,b=%d \r\n",a,b); //格式转化
    	     Serial_SendString(c); 
    	     Serial_Printf("a=%d,b=%d \r\n",a,b);  //重定位printf
    	     Serial_Printf("如果我说你是我的太阳\r\n");
    	
    			 */
    			while(1){
            
    				//串口输入测试
    				//HEX模式,串口软件中:直接输入单个数据,点击发送
    				int t1=Serial_RXInt();  //收到的单个HEX数据
    				if(t1!=-1)
    					Serial_SendByte(t1);
    				
    				//HEX模式,串口软件中:输入一组4个数据,可用于控制多个外设
    				//格式:FF 01 02 03 04 FE
    				if(Serial_RXArry()){  //HEX数组4元素
    					Serial_SendByte(0xFF);
    					Serial_SendArray(receive,4);
    					Serial_SendByte(0xFE);
    				}
    				
    				//文本模式, 输入字符串格式:@文本内容##
    				if(Serial_RXString()){   //文本
    					Serial_SendString("Text:");
    					Serial_SendString(text);
    					Serial_SendString("\r\n");
    				}
    				
    			}
    }
    
    

    4 测试图

    (1)接受串口发送的单个字符(收到后通过串口软作显示,下同)

    (2) 接受来自串口的多个数据(点击了多次)

    (3) 接收文本数据


    作者:riversuer

    物联沃分享整理
    物联沃-IOTWORD物联网 » STM32 UART串口通信详解:接收与发送HEX与文本数据的代码实践

    发表回复