STM32 USART串口数据包通信实例

文章目录

  • 前言
  • 一、介绍部分
  • 数据包两种包装方式(分割数据)
  • HEX数据包
  • 文本数据包
  • 数据包的收发流程
  • 数据包的发送
  • 数据包的接收
  • 固定包长的hex数据包接收
  • 可变包长的文本数据包接收
  • 二、实例部分
  • 固定包长的hex数据包接收
  • 连接线路
  • 代码实现
  • 可变包长的文本数据包接收
  • 连接线路
  • 代码实现
  • 补充

  • 前言

    当串口需要发送多个字节的数据时,使用数据包的形式来发送和接收是更加方便的,还可以更好的区分各个字节数据所对应的内容。例如需要发送X,Y,Z来控制陀螺仪传感器的X,Y,Z,就可以使用数据包的格式。


    一、介绍部分

    数据包两种包装方式(分割数据)

    发送一连串数据时,无法精准的确定此数据对应哪一位,所以需要分割数据来使其位置一一对应。或者限制数据的大小来避免数据与包头包尾重复。

    1. 使用包头加包尾把数据给包裹起来
    在数据与包头包尾有重复时,尽量使用固定包长的方法,以免错误的识别到包头或包尾导致数据错误。
    2. 使用一位标志位来确定数据开始

    HEX数据包

    文本数据包

    数据包的收发流程

    数据包的发送

    和普通的数据发送一致,使用一个数组来接收这个数据包,然后使用相关的发送函数即可。

    数据包的接收

    每接收一个字节的数据,程序就会进入一次中断然后退出中断,所以每个数据的接收是独立的,所以要针对不同的状态进行对应的判断和进行相关的偏移,使这些数据关联起来,也就是状态机思路。

    固定包长的hex数据包接收

    如图,判断包头为状态s=1,判断成功使状态s=2,则接收数据状态,接收满4个数据后,使状态s=3,判断包尾,判断成功后进入下一轮。

    可变包长的文本数据包接收

    二、实例部分

    固定包长的hex数据包接收

    使按下按键可以发送递增数据包,将接收的数据包与发送的数据包都显示到OLED上

    连接线路

    代码实现

    串口配置Serial.c

    #include "stm32f10x.h"                  // Device header
    #include <stdio.h>
    #include <stdarg.h>
    
    uint8_t TxData[4];	// 发送数据包
    uint8_t RxData[4];  // 接收数据包
    uint8_t RxFlag;
    
    void Serial_Init(void){
    	// 开启时钟
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
    	
    	// 初始化引脚
    	GPIO_InitTypeDef GPIO_InitStructure;
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;		// 复用推挽输出
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;	// A9 发送数据
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;		// 50Hz翻转速度
    	GPIO_Init(GPIOA, &GPIO_InitStructure);
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;		// 上拉输出
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;	// A10 接收数据
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;		// 50Hz翻转速度
    	GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    	// 初始化串口配置
    	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_ITConfig(USART1,USART_IT_RXNE,ENABLE);
    	
    	//初始化NVIC
    	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);	// 分组
    	NVIC_InitTypeDef NVIC_InitStructure;
    	// 中断通道
    	NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
    	// 中断通道使能
    	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    	// 抢占优先级
    	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
    	// 响应优先级
    	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
    	NVIC_Init(&NVIC_InitStructure);
    
    	
    	// USART1使能
    	USART_Cmd(USART1,ENABLE);
    }
    
    // 发送函数
    void USART_SendByte(uint8_t Byte){
    	USART_SendData(USART1,Byte);
    	// 等待写入完成,写入完成之后会将标志位自动清0
    	while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET);
    }
    
    // 发送数组函数
    void USART_SendArray(uint8_t *Array,uint16_t Length){
    	uint8_t i = 0;
    	for(i=0;i<Length;i++){
    		USART_SendData(USART1,Array[i]);
    		// 等待写入完成,写入完成之后会将标志位自动清0
    		while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET);
    	}
    }
    
    // 发送字符串函数
    void USART_SendString(uint8_t *String){
    	uint8_t i = 0;
    	for(i=0;String[i]!='\0';i++){
    		USART_SendData(USART1,String[i]);
    		// 等待写入完成,写入完成之后会将标志位自动清0
    		while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET);
    	}
    }
    
    // 返回X的Y次方
    uint32_t Serial_Pow(uint32_t X,uint32_t Y){
    	uint32_t Result = 1;
    	while(Y--){
    		Result *= X;
    	}
    	return Result;
    }
    // 发送数字函数
    void USART_SendNum(uint32_t Num,uint16_t Length){
    	uint8_t i = 0;
    	for(i=0;i<Length;i++){
    		USART_SendByte(Num / Serial_Pow(10,Length-i-1) % 10 + 0x30);
    		// 等待写入完成,写入完成之后会将标志位自动清0
    		while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET);
    	}
    }
    
    //重定向fputc函数,fputc是printf函数的底层,printf通过不停的调用fputc来达到输出的效果
    //重定向到串口
    int fputc(int ch,FILE *f){
    	USART_SendByte(ch);
    	return ch;
    }
    
    // 封装使用sprintf输出到串口
    void Serial_Printf(char *format, ...)
    {
    	char String[100];
    	va_list arg;							// 可变参数列表
    	va_start(arg, format);		// 从format开始接收可变参数
    	vsprintf(String, format, arg);
    	va_end(arg);
    	USART_SendString((uint8_t*)String);
    }
    
    // 获取RxFlag
    uint8_t USART_GetRxFlag(void){
    	if(RxFlag == 1){
    		RxFlag = 0;
    		return 1;
    	}
    	return 0;
    }
    
    
    void USART_SendPacket(void){
    	USART_SendByte(0xff);				// 发送包头
    	USART_SendArray(TxData,4);	// 发送数据
    	USART_SendByte(0xfe);				// 发送包尾
    }
    
    //中断函数
    void USART1_IRQHandler(void){
    	static uint8_t RxStatus = 0;		// 数据包状态
    	static uint8_t RxCount = 0;
    	if(USART_GetITStatus(USART1,USART_IT_RXNE)==SET){
    		uint8_t RXDATA = USART_ReceiveData(USART1);	// 获取接收到的数据
    		if(RxStatus == 0){
    			if(RXDATA == 0xff){
    				RxStatus = 1;
    			}
    		}else if(RxStatus == 1){
    			RxData[RxCount] = RXDATA;
    			RxCount++;
    			if(RxCount >= 4){
    				RxCount = 0;
    				RxStatus = 2;
    			}
    		}else if(RxStatus == 2){
    			if(RXDATA == 0xfe){
    				RxStatus = 0;
    				RxFlag = 1;
    			}
    		}
    	}
    }
    
    
    

    按键button.c

    #include "stm32f10x.h"                  // Device header
    #include "Delay.h"
    
    /**
      * @brief  初始化Button相关端口
      * @param 	无
      * @retval 无
      */
    void Button_Init(void){
    	// 初始化时钟
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
    	
    	GPIO_InitTypeDef GPIO_InitStructure;
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;		// 上拉输出,按下为0
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;	
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;		// 50Hz翻转速度
    	GPIO_Init(GPIOB, &GPIO_InitStructure);
    	// 使这两个端口默认高电平,不然初始化后默认是低电平
    	GPIO_SetBits(GPIOB, GPIO_Pin_1);
    }
    /**
      * @brief  返回所按按键值
      * @param 	无
      * @retval KeyNum 按键值
      */
    uint8_t Key_Num(void){
    	uint8_t KeyNum = 0;
    	if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1)==0){
    		Delay_ms(20);
    		while(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1)==0);
    		Delay_ms(20);
    		KeyNum = 1;
    	}
    	if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11)==0){
    		Delay_ms(20);
    		while(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11)==0);
    		Delay_ms(20);
    		KeyNum = 11;
    	}
    	return KeyNum;
    }
    
    

    主函数main.c

    #include "stm32f10x.h"                  // Device header
    #include "Delay.h"
    #include "OLED.h"
    #include "Serial.h"
    #include "button.h"
    
    uint8_t KeyNum;
    
    int main(void)
    {
    	OLED_Init();
    	OLED_ShowString(1, 1, "TxData:");
    	OLED_ShowString(3, 1, "RxData:");
    	Button_Init();
    	Serial_Init();
    	TxData[0] = 0x01;
    	TxData[1] = 0x02;
    	TxData[2] = 0x03;
    	TxData[3] = 0x04;
    	
    	//USART_SendPacket();
    	while (1)
    	{
    		KeyNum = Key_Num();
    		if(KeyNum == 1){
    			TxData[0] ++;
    			TxData[1] ++;
    			TxData[2] ++;
    			TxData[3] ++;
    			USART_SendPacket();
    			OLED_ShowHexNum(2,1,TxData[0],2);
    			OLED_ShowHexNum(2,4,TxData[1],2);
    			OLED_ShowHexNum(2,7,TxData[2],2);
    			OLED_ShowHexNum(2,10,TxData[3],2);
    		}
    		if(USART_GetRxFlag() == 1){
    			OLED_ShowHexNum(4,1,RxData[0],2);
    			OLED_ShowHexNum(4,4,RxData[1],2);
    			OLED_ShowHexNum(4,7,RxData[2],2);
    			OLED_ShowHexNum(4,10,RxData[3],2);
    		}
    	}
    }
    
    

    可变包长的文本数据包接收

    主要功能为接收字符串,通过特定的字符串来实现led的点亮和关闭,并把相关信息显示在OLED上

    连接线路

    代码实现

    led配置led.c

    #include "stm32f10x.h"                  // Device header
    
    
    /**
      * @brief  初始化LED相关端口,让LED所在端口可以被直接赋值
      * @param 	无
      * @retval 无
      */
    void LED_Init(void){
    	// 初始化时钟
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    	// 配置LED所在端口
    	GPIO_InitTypeDef GPIO_InitStructure;
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;		// 通用推挽输出
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;		// 50Hz翻转速度
    	GPIO_Init(GPIOA, &GPIO_InitStructure);
    	// 使这两个端口默认高电平,不然初始化后默认是低电平
    	GPIO_SetBits(GPIOA, GPIO_Pin_1);
    }
    /**
      * @brief  LED1亮
      * @param 	无
      * @retval 无
      */
    void LED1_On(void){
    	GPIO_ResetBits(GPIOA, GPIO_Pin_1);
    }
    /**
      * @brief  LED1关
      * @param 	无
      * @retval 无
      */
    void LED1_Off(void){
    	GPIO_SetBits(GPIOA, GPIO_Pin_1);
    }
    /**
      * @brief  LED2亮
      * @param 	无
      * @retval 无
      */
    void LED2_On(void){
    	GPIO_ResetBits(GPIOA, GPIO_Pin_2);
    }
    /**
      * @brief  LED2关
      * @param 	无
      * @retval 无
      */
    void LED2_Off(void){
    	GPIO_SetBits(GPIOA, GPIO_Pin_2);
    }
    /**
      * @brief  LED1取反
      * @param 	无
      * @retval 无
      */
    void LED1_Reverse(void){
    	// 读取端口状态,根据状态取反
    	if(GPIO_ReadOutputDataBit(GPIOA,GPIO_Pin_1) == 0)
    		GPIO_SetBits(GPIOA, GPIO_Pin_1);
    	else
    		GPIO_ResetBits(GPIOA, GPIO_Pin_1);
    }
    /**
      * @brief  LED2取反
      * @param 	无
      * @retval 无
      */
    void LED2_Reverse(void){
    	// 读取端口状态,根据状态取反
    	if(GPIO_ReadOutputDataBit(GPIOA,GPIO_Pin_2) == 0)
    		GPIO_SetBits(GPIOA, GPIO_Pin_2);
    	else
    		GPIO_ResetBits(GPIOA, GPIO_Pin_2);
    }
    
    

    串口配置serial.c

    #include "stm32f10x.h"                  // Device header
    #include <stdio.h>
    #include <stdarg.h>
    
    char RxData[100];  // 接收数据包
    uint8_t RxFlag;
    
    void Serial_Init(void){
    	// 开启时钟
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
    	
    	// 初始化引脚
    	GPIO_InitTypeDef GPIO_InitStructure;
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;		// 复用推挽输出
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;	// A9 发送数据
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;		// 50Hz翻转速度
    	GPIO_Init(GPIOA, &GPIO_InitStructure);
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;		// 上拉输出
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;	// A10 接收数据
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;		// 50Hz翻转速度
    	GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    	// 初始化串口配置
    	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_ITConfig(USART1,USART_IT_RXNE,ENABLE);
    	
    	//初始化NVIC
    	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);	// 分组
    	NVIC_InitTypeDef NVIC_InitStructure;
    	// 中断通道
    	NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
    	// 中断通道使能
    	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    	// 抢占优先级
    	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
    	// 响应优先级
    	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
    	NVIC_Init(&NVIC_InitStructure);
    
    	
    	// USART1使能
    	USART_Cmd(USART1,ENABLE);
    }
    
    // 发送函数
    void USART_SendByte(uint8_t Byte){
    	USART_SendData(USART1,Byte);
    	// 等待写入完成,写入完成之后会将标志位自动清0
    	while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET);
    }
    
    // 发送数组函数
    void USART_SendArray(uint8_t *Array,uint16_t Length){
    	uint8_t i = 0;
    	for(i=0;i<Length;i++){
    		USART_SendData(USART1,Array[i]);
    		// 等待写入完成,写入完成之后会将标志位自动清0
    		while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET);
    	}
    }
    
    // 发送字符串函数
    void USART_SendString(uint8_t *String){
    	uint8_t i = 0;
    	for(i=0;String[i]!='\0';i++){
    		USART_SendData(USART1,String[i]);
    		// 等待写入完成,写入完成之后会将标志位自动清0
    		while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET);
    	}
    }
    
    // 返回X的Y次方
    uint32_t Serial_Pow(uint32_t X,uint32_t Y){
    	uint32_t Result = 1;
    	while(Y--){
    		Result *= X;
    	}
    	return Result;
    }
    // 发送数字函数
    void USART_SendNum(uint32_t Num,uint16_t Length){
    	uint8_t i = 0;
    	for(i=0;i<Length;i++){
    		USART_SendByte(Num / Serial_Pow(10,Length-i-1) % 10 + 0x30);
    		// 等待写入完成,写入完成之后会将标志位自动清0
    		while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET);
    	}
    }
    
    //重定向fputc函数,fputc是printf函数的底层,printf通过不停的调用fputc来达到输出的效果
    //重定向到串口
    int fputc(int ch,FILE *f){
    	USART_SendByte(ch);
    	return ch;
    }
    
    // 封装使用sprintf输出到串口
    void Serial_Printf(char *format, ...)
    {
    	char String[100];
    	va_list arg;							// 可变参数列表
    	va_start(arg, format);		// 从format开始接收可变参数
    	vsprintf(String, format, arg);
    	va_end(arg);
    	USART_SendString((uint8_t*)String);
    }
    
    
    //中断函数
    void USART1_IRQHandler(void){
    	static uint8_t RxStatus = 0;		// 数据包状态
    	static uint8_t RxCount = 0;
    	if(USART_GetITStatus(USART1,USART_IT_RXNE)==SET){
    		uint8_t RXDATA = USART_ReceiveData(USART1);	// 获取接收到的数据
    		if(RxStatus == 0){
    			if(RXDATA == '@' && RxFlag == 0){
    				RxStatus = 1;
    				RxCount = 0;		// 初始化之前的数据长度
    			}
    		}else if(RxStatus == 1){
    			if(RXDATA == '\r')
    				RxStatus = 2;
    			else{
    				RxData[RxCount] = RXDATA;
    				RxCount++;
    			}
    		}else if(RxStatus == 2){
    			if(RXDATA == '\n'){
    				RxStatus = 0;
    				RxFlag = 1;
    				RxData[RxCount] = '\0';		// 加入字符串结束标志位
    			}
    		}
    		USART_ClearITPendingBit(USART1, USART_IT_RXNE); // 清除标志位
    	}
    }
    
    
    

    主函数mian.c

    #include "stm32f10x.h"                  // Device header
    #include "Delay.h"
    #include "OLED.h"
    #include "Serial.h"
    #include "LED.h"
    #include <string.h>
    
    
    int main(void)
    {
    	OLED_Init();
    	Serial_Init();
    	LED_Init();
    	OLED_ShowString(1, 1, "TxData:");
    	OLED_ShowString(3, 1, "RxData:");
    	
    	while (1)
    	{
    		if(RxFlag == 1){
    			OLED_ShowString(4,1,"                "); // 显示前擦除这行
    			OLED_ShowString(4,1,RxData);
    			
    			if(strcmp(RxData,"LED_ON")==0){
    				LED1_On();
    				USART_SendString((uint8_t*)"LED_ON_OK\r\n");
    				OLED_ShowString(2,1,"                ");
    				OLED_ShowString(2,1,"LED_ON_OK");
    			}
    			else if(strcmp(RxData,"LED_OFF")==0){
    				LED1_Off();
    				USART_SendString((uint8_t*)"LED_OFF_OK\r\n");
    				OLED_ShowString(2,1,"                ");
    				OLED_ShowString(2,1,"LED_OFF_OK");
    			}
    			else{
    				USART_SendString((uint8_t*)"ERR_CMD\r\n");
    				OLED_ShowString(2,1,"                ");
    				OLED_ShowString(2,1,"ERR_CMD");
    			}
    			RxFlag = 0;
    		}
    	}
    }
    
    

    补充

    这里没有使用函数封装标志位自动清除,在许多读写操作进行时可能因为读取速度太慢导致数据接收错位,所以在主函数中手动把标志位清0,保证把每个数据包接收完毕后才可以开始下一次接收,同时把serial.c的包头判断时加入标志位判断即if(RXDATA == ‘@’ && RxFlag == 0)。


    作者:CC Cian

    物联沃分享整理
    物联沃-IOTWORD物联网 » STM32 USART串口数据包通信实例

    发表回复