主要参考学习资料:

B站@江协科技

STM32入门教程-2023版 细致讲解 中文字幕

开发资料下载链接:https://pan.baidu.com/s/1h_UjuQKDX9IpP-U1Effbsw?pwd=dspb

单片机套装:STM32F103C8T6开发板单片机C6T6核心板 实验板最小系统板套件科协

实验:

  • 串口收发HEX数据包
  • 串口收发文本数据包

数据包

数据包的作用是进行多字节的数据通信,将属于同一批的数据进行打包和分割,以便接收方识别。打包和分割的方法可以自行设计。

HEX数据包

HEX数据包直接传输十六进制数据,本实验规定使用FF作为包头、FE作为包尾的方式打包和分割数据包。

针对包头包尾和载荷数据重复的问题,一种解决方法是限制载荷数据的范围,在发送时对数据进行限幅,使其不会重复;第二种方法是尽量使用固定长度的数据包,只要通过包头包尾对齐了数据,就可以严格知道哪个数据时包头包尾,哪个数据是载荷数据;第三种方法是增加包头包尾的数量,并让尽量使用载荷数据不会出现的状态,例如用FF、FE作为包头,FD、FC作为包尾。

包头和包尾并非都需要,可以只保留包头,删除包尾,但载荷与包头重复的问题也会更严重。

文本数据包

在文本数据包中,每个字节经过了一层编码和译码,以文本格式表现。字符在包头包尾的选择中可以有效避免与载荷数据重复的问题,本实验规定以@作为包头,以换行字符\r\n作为包尾。载荷数据接收为字符串,软件通过对字符串判断和操作可以实现各种指令控制功能。

HEX数据包的优点是传输直接,解析简单,适合陀螺仪、传感器等模块发送原始数据,缺点是灵活性不足,载荷容易和包头包尾重复。文本数据包的优点是数据直观易理解,非常灵活,适合通过指令进行人机交互的场合,缺点是解析效率低。

数据包收发流程

数据包发送的过程很简单,只需定义数组,填充数据并发送。此处主要介绍接收流程。

HEX数据包

在接收数据中,针对包头、数据载荷和包尾三种状态需要不同的处理逻辑,因此我们使用状态机思维设计程序,通过一个能记住不同状态的机制,在不同状态执行不同的操作,同时还要进行状态的合理转移。上图为接收固定包长含包头包尾的HEX数据包的状态转移图,变量S标志不同的状态,在满足相应的条件时跟随箭头方向转移。

文本数据包

实验22 串口收发HEX数据包

接线图

Serial驱动

在实验21的基础上更改。

Serial.h

#ifndef __SERIAL_H
#define __SERIAL_H

#include <stdio.h>

//允许外部调用发送和接收数据包
extern uint8_t Serial_TxPacket[];
extern uint8_t Serial_RxPacket[];

void Serial_Init(void);
void Serial_SendByte(uint8_t Byte);
void Serial_SendArray(uint8_t *Array, uint16_t Length);
void Serial_SendString(char *String);
void Serial_SendNumber(uint32_t Number, uint8_t Length);
void Serial_Printf(char *format, ...);
void Serial_SendPacket(void);
uint8_t Serial_GetRxFlag(void);

#endif

Serial.c

只展示更改部分:

//在驱动中暂存接受数据包
uint8_t Serial_TxPacket[4];
uint8_t Serial_RxPacket[4];
uint8_t Serial_RxFlag;

//新增发送数据包函数
void Serial_SendPacket(void)
{
	//发送包头
	Serial_SendByte(0xFF);
	//发送数据载荷
	Serial_SendArray(Serial_TxPacket, 4);
	//发送包尾
	Serial_SendByte(0xFE);
}

//删去Serial_GetRxData函数
//更改中断函数
void USART1_IRQHandler(void)
{
	//状态变量
	static uint8_t RxState = 0;
	//指示数据接收到第几个
	static uint8_t pRxPacket = 0;
	if(USART_GetITStatus(USART1, USART_IT_RXNE))
	{
		//接收单个字节
		uint8_t RxData = USART_ReceiveData(USART1);
		//根据状态不同进入不同的处理程序
		switch(RxState)
		{
			//等待包头
			case 0:
				//收到包头转移状态
				if(RxData == 0xFF)
					RxState = 1;
				break;
			//接收数据
			case 1:
				Serial_RxPacket[pRxPacket] = RxData;
				pRxPacket ++;
				//接收满4个数据
				if(pRxPacket >= 4)
				{
					RxState = 2;
					pRxPacket = 0;
				}
				break;
			//等待包尾
			case 2:
				//收到包尾
				if(RxData == 0xFE)
				{
					RxState = 0;
					//置接收标志位
					Serial_RxFlag = 1;
				}
				break;
		}
		USART_ClearITPendingBit(USART1, USART_IT_RXNE);
	}
}

主程序

#include "stm32f10x.h" 
#include "Delay.h"
#include "OLED.h"
#include "Serial.h"
#include "Key.h"

uint8_t KeyNum;

int main(void)
{
	OLED_Init();
	Key_Init();
	Serial_Init();
	
	OLED_ShowString(1, 1, "TxPacket");
	OLED_ShowString(3, 1, "RxPacket");
	//初始化待发送数据包
	Serial_TxPacket[0] = 0x01;
	Serial_TxPacket[1] = 0x02;
	Serial_TxPacket[2] = 0x03;
	Serial_TxPacket[3] = 0x04;
	
	while(1)
	{
		KeyNum = Key_GetNum();
		//每次按下按键使发送数据包加一并发送
		if(KeyNum)
		{
			Serial_TxPacket[0] ++;
			Serial_TxPacket[1] ++;
			Serial_TxPacket[2] ++;
			Serial_TxPacket[3] ++;
			Serial_SendPacket();
			OLED_ShowHexNum(2, 1, Serial_TxPacket[0], 2);
			OLED_ShowHexNum(2, 4, Serial_TxPacket[1], 2);
			OLED_ShowHexNum(2, 7, Serial_TxPacket[2], 2);
			OLED_ShowHexNum(2, 10, Serial_TxPacket[3], 2);
		}
		//判断标志位接收数据包
		if(Serial_GetRxFlag())
		{
			OLED_ShowHexNum(4, 1, Serial_RxPacket[0], 2);
			OLED_ShowHexNum(4, 4, Serial_RxPacket[1], 2);
			OLED_ShowHexNum(4, 7, Serial_RxPacket[2], 2);
			OLED_ShowHexNum(4, 10, Serial_RxPacket[3], 2);
		}	
	}
}

实验23 串口收发文本数据包

实现功能:使用串口发送文本指令控制LED亮灭。

接线图

Serial驱动

在实验22的基础上修改。

Serial.h

#ifndef __SERIAL_H
#define __SERIAL_H

#include <stdio.h>

extern char Serial_RxPacket[];
//声明标志位为外部可调用
extern uint8_t Serial_RxFlag;

void Serial_Init(void);
void Serial_SendByte(uint8_t Byte);
void Serial_SendArray(uint8_t *Array, uint16_t Length);
void Serial_SendString(char *String);
void Serial_SendNumber(uint32_t Number, uint8_t Length);
void Serial_Printf(char *format, ...);

#endif

Serial.c

只展示更改部分:

//接收数据类型改为char,上限100个字符
char Serial_RxPacket[100];
uint8_t Serial_RxFlag;
//删除发送数据包函数和获取标志位函数
void USART1_IRQHandler(void)
{
	static uint8_t RxState = 0;
	static uint8_t pRxPacket = 0;
	if(USART_GetITStatus(USART1, USART_IT_RXNE))
	{
		uint8_t RxData = USART_ReceiveData(USART1);
		switch(RxState)
		{
			case 0:
				//更改包头
				//为防止程序处理数据包不及时导致数据包错位
				//需判断接收标志位清零后再接收下一个数据包
				if(RxData == '@' && !Serial_RxFlag)
				{
					RxState = 1;
					pRxPacket = 0;
				}
				break;
			case 1:
				//判断包尾是否到来
				if(RxData == '\r')
					RxState = 2;
				else
				{
					Serial_RxPacket[pRxPacket] = RxData;
					pRxPacket ++;
				}
				//无需检测数据数量
				break;
			case 2:
				//等待第二个包尾
				if(RxData == '\n')
				{
					RxState = 0;
					//添加字符串结束标志
					Serial_RxPacket[pRxPacket] = '\0';
					Serial_RxFlag = 1;
				}
				break;
		}
		USART_ClearITPendingBit(USART1, USART_IT_RXNE);
	}
}

主程序

#include "stm32f10x.h" 
#include "Delay.h"
#include "OLED.h"
#include "Serial.h"
#include "LED.h"
//判断字符串的官方库
#include <string.h>

int main(void)
{
	OLED_Init();
	LED_Init();
	Serial_Init();
	
	OLED_ShowString(1, 1, "TxPacket");
	OLED_ShowString(3, 1, "RxPacket");
	
	while(1)
	{
		if(Serial_RxFlag)
		{
			//由于字符串长度不确定,需擦除显示行再覆写下一个字符串
			OLED_ShowString(4, 1, "                ");
			OLED_ShowString(4, 1, Serial_RxPacket);
			//字符串相等返回0
			//开灯指令
			if(!strcmp(Serial_RxPacket, "LED_ON"))
			{
				LED1_ON();
				//回传反馈
				Serial_SendString("LED_ON_OK\r\n");
				OLED_ShowString(2, 1, "                ");
				OLED_ShowString(2, 1, "LED_ON_OK");
			}
			//关灯指令
			else if(!strcmp(Serial_RxPacket, "LED_OFF"))
			{
				LED1_OFF();
				Serial_SendString("LED_OFF_OK\r\n");
				OLED_ShowString(2, 1, "                ");
				OLED_ShowString(2, 1, "LED_OFF_OK");
			}
			//错误指令
			else
			{
				Serial_SendString("ERROR_COMMAND\r\n");
				OLED_ShowString(2, 1, "                ");
				OLED_ShowString(2, 1, "ERROR_COMMAND");				
			}
			//清空标志位
			Serial_RxFlag = 0;
		}
	}
}
Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐