匿名上位机V7.12协议编程(基于STM32F407+CubeMX+UART外设通信)

这篇具有很好参考价值的文章主要介绍了匿名上位机V7.12协议编程(基于STM32F407+CubeMX+UART外设通信)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

本篇以STM32F407VET6主控为基础进行论述。
文章的目的在于记录和引导,传递一些编写通信类功能会用到的基本思想,以及组合这些功能的思想。
匿名上位机V7版本的功能在本篇没有全部写出来,而是针对本上位机常用的功能举例来描述。
相信读者通过仔细阅读笔者的思想引述和具体代码实现能够触类旁通。
限于笔者水平有限,文章中的一些错误还望批评指正。

一、匿名上位机的通信帧格式

其中DATA 数据内容中的数据,采用小端模式,低字节在前,高字节在后。
匿名上位机V7.12协议编程(基于STM32F407+CubeMX+UART外设通信)
从图中可以一目了然,一个通信帧共有7个部分,每个部分都有规定。这直接影响了后续结构体的定义,及其成员的使用。
每个部分的内存基本单元为字节。

二、简单面向对象封装

1.封装

按照(一)中的通信帧结构定义结构体:

/****参数对象结构体****/
typedef struct 							
{
	uint16_t par_id;                 //<参数id
	int32_t  par_val;                //<参数值
}par_struct;
/****通信帧对象结构体****/
typedef struct
{
	uint8_t head;					 //<帧头
	uint8_t target_addr;			 //<目标地址
	uint8_t function_id;			 //<该帧要实现某功能的功能码id
	uint8_t data_len;				 //<数据长度
	uint8_t data[40];				 //<数据内容,协议最高只支持40字节数据  
	uint8_t sum_check;				 //<和校验
	uint8_t add_check;				 //<附加校验
	
	par_struct* parameter;           //<参数
}ano_frameStruct;

通过结构体的“打包”,我们将一个通信帧当作一个对象来进行操作。
为什么data定义大小是40?
协议中有不同类型的帧,每个类型的帧里面,数据所需的长度都是不一样的,有一些类型的帧数据长度固定,有一些类型的帧可以自主选择帧数据的长度。
其中灵活数据帧是协议中最自由的协议,它的数据长度最大允许为40字节,仔细看文档可知,这同时也是所有类型帧中支持数据长度最长的,这直接决定了我们data部分数组定义的大小:
匿名上位机V7.12协议编程(基于STM32F407+CubeMX+UART外设通信)

2.接口化

应用于匿名上位机的接口化非常简单,就是在编写过程函数的时候,函数的形参,都以通信帧结构体指针作为输入,这样就能实现对通信帧对象的统一操作。
比如:

/*共同点:形参都含ano_frameStruct*类型指针*/
void ano_frame_reset(ano_frameStruct* frame);
void ano_check_calculate(ano_frameStruct* frame);
uint8_t ano_check(ano_frameStruct* frame);
void frame_turn_to_array(ano_frameStruct* frame,uint8_t*str);
//.....
//函数在后面会详细展开描述

笔者简单地举例了几个函数的定义形式,这样定义,就能实现对通信帧对象在某个操作中的通用化,比如复位操作,校验操作,通信帧结构体转连续数组操作等等。同时,这也是API那般高度封装的思想雏形。

三、编写上位机的基本功能(除接收逻辑)

1.部分宏定义

①硬件地址宏定义:
匿名上位机V7.12协议编程(基于STM32F407+CubeMX+UART外设通信)

//目标地址宏定义
#define FRAME_HEADER 		  0XAA   //<匿名协议固定帧头
#define GENERAL_OUTPUT_ADDR	  0XFF   //<广播型输出
#define HOST_ADDR			  0XAF   //<向上位机输出
#define PRO_ADDR			  0X05   //<拓空者PRO飞控
#define SHUCHUAN_ADDR		  0X10   //<匿名数传
#define GUANGLIU_ADDR		  0X22   //<匿名光流
#define UWB_ADDR			  0X30   //<匿名UWB
#define IMU_ADDR			  0X60   //<匿名凌霄IMU
#define LINGXIAO_ADDR		  0X61   //<匿名凌霄飞控

②参数ID宏定义
这里总计有166个参数id对,用户根据需要去进行宏定义即可。
匿名上位机V7.12协议编程(基于STM32F407+CubeMX+UART外设通信)
因为不作飞控用途,笔者主要使用5~10:
匿名上位机V7.12协议编程(基于STM32F407+CubeMX+UART外设通信)
对于序号1,有特殊用途,可以把自己的设备伪装成硬件地址中的各种飞控,这样就能使用这166个参数的交互了。否则是无法使用参数通信功能的!

#define HWTYPE            0X01   //<用于存储下位机的类型信息
#define ID_INFO5 		  0X05   //<预留位
#define ID_INFO6	      0X06   //<预留位
#define ID_INFO7		  0X07   //<预留位
#define ID_INFO8		  0X08   //<预留位
#define ID_INFO9		  0X09   //<预留位
#define ID_INFO10		  0X0A   //<预留位

③字符输出,有三个指定颜色

#define ANO_BLACK         0X00  //<字符黑色打印
#define ANO_RED           0X01  //<字符红色打印
#define ANO_GREEN         0X02  //<字符绿色打印

2.对象初始化与复位

#include <string.h>

static par_struct      send_parameter;       //<发送帧中的参数;	
static par_struct      rec_parameter;        //<接收帧的参数;
static ano_frameStruct send_frame_struct;    //<(发送)通信帧结构体
__IO ano_frameStruct   rec_frame_struct;     //<(接收)通信帧结构体,因不止在本.c文件使用,故不用static修饰

/**
  * @brief   初始化通信帧结构体,使用前必须调用
  * @param   无输入参数
  * @retval  无返回
  **/
void ano_frame_init(void)
{
    /*参数结构体初始化*/
	send_frame_struct.parameter=&send_parameter;
	rec_frame_struct.parameter=&rec_parameter;
	send_frame_struct.parameter->par_id=0;
	send_frame_struct.parameter->par_val=0;
	rec_frame_struct.parameter->par_id=0;
	rec_frame_struct.parameter->par_val=0;
	
	send_frame_struct.head=rec_frame_struct.head=FRAME_HEADER;//帧头固定是0XAA
	send_frame_struct.target_addr=rec_frame_struct.target_addr=HOST_ADDR;
	send_frame_struct.function_id=0XFF;//<协议中没有定义的功能ID,这样初始化目的是为了启动瞬间不做任何动作


	memset(send_frame_struct.data,0,40);//<缓存默认全部置0
	memset(rec_frame_struct.data,0,40);
}

/**
  * @brief   复位通信帧结构体,ano_frame_init()必须要运行过一次
  * @param   通信帧结构体对象
  * @retval  无返回
  **/
void ano_frame_reset(ano_frameStruct* frame)
{
  frame->function_id=0XFF;						
  frame->data_len=0;
  memset(frame->data,0,40);
  frame->add_check=0;
  frame->sum_check=0;
}
/**
  * @brief   通信帧中参数结构体内成员的配置
  * @param   通信帧结构体对象,参数ID与参数值
  * @retval 
  **/
void ano_par_struct_config(ano_frameStruct* frame,uint16_t id,int32_t val)
{
	frame->parameter->par_id=id;
	frame->parameter->par_val=val;
}

3.数据校验的逻辑

匿名上位机V7.12协议编程(基于STM32F407+CubeMX+UART外设通信)

/**
  * @brief   通信帧校验计算
  * @param   通信帧结构体对象
  * @retval  无返回值
  **/
static void ano_check_calculate(ano_frameStruct* frame)
{
	frame->sum_check=0;
	frame->add_check=0;
	
	//除去和校验,附加校验及数据部分,有4个部分4个字节,长度固定
	for(uint32_t i=0;i<4;i++)
	{
	  frame->sum_check+= *(uint8_t*)(&frame->head+i);
	  frame->add_check+=frame->sum_check;
	}
	//获取数据长度部位,把数据部分全加上
	for(uint32_t i=0;i<frame->data_len;i++)
	{
	  frame->sum_check+=*((uint8_t*)(frame->data)+i);
	  frame->add_check+=frame->sum_check;
	} 
 }
/**
  * @brief   通信帧校验检查(接收上位机通信帧时用)
  * @param   通信帧结构体对象
 * @retval   1:校验成功 0:校验失败
  **/
static uint8_t ano_check(ano_frameStruct* frame)
{
	uint8_t sum_check=0;
	uint8_t add_check=0;

	for(uint32_t i=0;i<4;i++)
	{
	  sum_check+= *(uint8_t*)(&frame->head+i);
	  add_check+=sum_check;
	}
	for(uint32_t i=0;i<frame->data_len;i++)
	{
	  sum_check+=*((uint8_t*)(frame->data)+i);
	  add_check+=sum_check;
	}
    //如果计算与获取的相等,校验成功
	if((sum_check==frame->sum_check)&&(add_check==frame->add_check))
	 return 1;
	else
	 return 0;
}

4.串口UART的接口函数

作为STM32的UART外设接口,根据自己使用的串口修改就好。

/**
  * @brief  匿名串口发送
  * @param  字符串,数据字节个数
  * @retval 
  */
static void ano_usart_send(uint8_t*str,uint16_t num)
{
 uint16_t cnt=0;
 do
	{
		HAL_UART_Transmit(&huart1,((uint8_t*)(str))+cnt,1,1000);
		cnt++;
	}while(cnt<=num);
}

5.数据的处理

上位机只支持整形数据的通信。
对于数据部分,我们可能会发送8位的参数,16位的参数或32位的参数。
但是数据部分要求我们一个字节一个字节从低到高发送,所以我们需要对待传输的数据进行由低位到高位的字节截断

//32位数据进行四个字节剥离拆分,从低位到高位
#define BYTE0(temp)	   (*(char*)(&temp))
#define BYTE1(temp)	   (*((char*)(&temp)+1))
#define BYTE2(temp)	   (*((char*)(&temp)+2))
#define BYTE3(temp)	   (*((char*)(&temp)+3))

如果一个通信帧的内容全部定下来了,我们需要把通信帧转为从低位到高位的线性数组:

/**
  * @brief   通信帧结构体转化为线性数组
  * @param   要转换的通信帧,缓存数组
  * @retval 
  **/
static void frame_turn_to_array(ano_frameStruct* frame,uint8_t*str)
{
	memcpy(str,(uint8_t*)frame,4);
	memcpy(str+4,(uint8_t*)frame->data,frame->data_len);
	memcpy(str+4+frame->data_len,(uint8_t*)(&frame->sum_check),2);
}

6.向上位机发送字符串

匿名上位机V7.12协议编程(基于STM32F407+CubeMX+UART外设通信)

 /**
  * @brief  向上位机发送ASCII字符串
  * @param  color:希望上位机显示的字符串颜色,str:要发送的字符串
  * @retval 无返回值
  */
void ano_send_string(uint8_t color,char* str)
{
	uint8_t i=0,cnt=0;
	uint8_t buff[46];										
	memset(send_frame_struct.data,0,40);
	send_frame_struct.function_id=0XA0;           //<信息输出--字符串(功能码0XA0)
	send_frame_struct.data[cnt++]=color;          //<选择上位机打印的颜色
	/*字符串数据直接存入数据部分*/
	while(*(str+i)!='\0')
	{
		send_frame_struct.data[cnt++]=*(str+i++);
		if(cnt>40)                                //<若字符串长度超过40,强制结束
			break;
	}
	send_frame_struct.data_len=cnt;               //<记录下数据部分长度
	
	ano_check_calculate(&send_frame_struct);      //<计算校验和
	frame_turn_to_array(&send_frame_struct,buff); //<通信帧转线性数组
	ano_usart_send(buff,6+send_frame_struct.data_len);
}

/**
  * @brief  向上位机发送ASCII字符串+数字组合
  * @param  value:32位的数值,str:要发送的字符串
  * @retval 
  */
void ano_send_message(char* str,int32_t value)
{
	uint8_t i=0,cnt=0;
	uint8_t buff[46];										
	memset(send_frame_struct.data,0,40);
	send_frame_struct.function_id=0XA1;	          //信息输出--字符串+数字
	
	/*协议规定VAL在前,先对要求的32位数据进行截断*/
	send_frame_struct.data[cnt++]=BYTE0(value);
	send_frame_struct.data[cnt++]=BYTE1(value);
	send_frame_struct.data[cnt++]=BYTE2(value);
	send_frame_struct.data[cnt++]=BYTE3(value);
	/*再轮到字符串数据*/
	while(*(str+i)!='\0')
	{
		send_frame_struct.data[cnt++]=*(str+i++);
		if(cnt>40)
			break;
	}
	
	send_frame_struct.data_len=cnt;				  //<记录下数据部分长度
	
	ano_check_calculate(&send_frame_struct);	  //<计算校验和
	frame_turn_to_array(&send_frame_struct,buff); //<通信帧转线性数组
	ano_usart_send(buff,6+send_frame_struct.data_len);
}

7.向上位机发送灵活数据帧

匿名上位机V7.12协议编程(基于STM32F407+CubeMX+UART外设通信)

/**
  * @brief  发送灵活格式帧
  * @param  id:0xF1~0XFA,x_coordinate:x轴坐标值 ,y_coordinate:y轴坐标值
  *         !!!要传多少个参数完全可以自己进行计算,最高只支持40字节的数据,低位先输出
  *         一般10个以内够用,40个字节限制,一个32位数据占4个字节,可以发送10个
  * @retval 
  */
void ano_send_flexible_frame(uint8_t id,int32_t x_coordinate,int32_t y_coordinate)
{
	uint8_t buff[46];
	
	memset(send_frame_struct.data,0,40);
	send_frame_struct.function_id=id;
	send_frame_struct.data_len=8;			   //<根据自己的参数数填写							
	
	/*第一个x_coordinate数据从低位到高位截断*/
	send_frame_struct.data[0]=BYTE0(x_coordinate);
	send_frame_struct.data[1]=BYTE1(x_coordinate);
	send_frame_struct.data[2]=BYTE2(x_coordinate);
	send_frame_struct.data[3]=BYTE3(x_coordinate);
	/*第二个数据y_coordinate从低位到高位截断*/
	send_frame_struct.data[4]=BYTE0(y_coordinate);
	send_frame_struct.data[5]=BYTE1(y_coordinate);
	send_frame_struct.data[6]=BYTE2(y_coordinate);
	send_frame_struct.data[7]=BYTE3(y_coordinate);
	/*第N个数据xxx从低位到高位截断*/
	//......用户自行添加

	ano_check_calculate(&send_frame_struct);
	frame_turn_to_array(&send_frame_struct,buff);


	ano_usart_send(buff,6+send_frame_struct.data_len);
}

效果图:
匿名上位机V7.12协议编程(基于STM32F407+CubeMX+UART外设通信)

四、有限状态机FSM

有限状态机,顾名思义,这类状态机的状态数量是有限的,在不同阶段会呈现不同的运行状态,并且不重复。如果设计的系统使用了有限状态机的方法,那么在某一个时刻,它必定是处于所有状态中的其中一个状态。

1.有限状态机的基本要素

①状态。一个状态机,必定有多个状态。
②条件。进入一个状态后,要判断一些条件,看看已有的条件,满不满足现在所处的状态的特征,或,要做什么动作,或决定从现状态要迁移到哪个状态。
③动作,在当前状态,要执行什么操作,同时迁移也是一种动作。
④迁移,一个状态迁移到另一个状态。

2.一个简单的状态机

匿名上位机V7.12协议编程(基于STM32F407+CubeMX+UART外设通信)
①状态分别有S0,S1,S2,S3,S4五个状态,其中S0是初始状态
②条件。每到一个状态,bool初始化为-1,当外部事件或中断发生,把bool变成0或1时,这是我们进行迁移的条件。
③动作。主要是对条件进行判断和迁移两个动作
④迁移,满足条件就迁移。若外部事件或中断没有到来,bool始终是-1,将不断停留在现在的状态。

3.匿名上位机与MCU的交互

匿名上位机V7.12协议编程(基于STM32F407+CubeMX+UART外设通信)

匿名上位机V7.12协议编程(基于STM32F407+CubeMX+UART外设通信)
匿名上位机V7.12协议编程(基于STM32F407+CubeMX+UART外设通信)
匿名上位机V7.12协议编程(基于STM32F407+CubeMX+UART外设通信)

4.STM32串口的接收机制

对于STM32串口接收,一次接收,只能接收一个字节,但是,我们匿名上位机的通信帧,最多达46字节,才算一次完整的通信帧接收。基于硬件机制与协议内容,我们需要在接收函数中,建立一个状态机
本例示范中断接收方式:

void USART1_IRQHandler(void)
{
  /* USER CODE BEGIN USART1_IRQn 0 */
	static uint8_t data=0;
  /* USER CODE END USART1_IRQn 0 */
//  HAL_UART_IRQHandler(&huart1);
  /* USER CODE BEGIN USART1_IRQn 1 */
	if(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_ORE)!=RESET)
	{
	  data=huart1.Instance->DR;
	  __HAL_UART_CLEAR_OREFLAG(&huart1);//<开启错误中断,目的是防止波特率过高造成的错误
	}
	/*接收逻辑*/
	if(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_RXNE)!=RESET)
  {
    data=huart1.Instance->DR;
    ano_read_one_byte(data);//<状态机函数,实现数据接收与状态机
    __HAL_UART_CLEAR_FLAG(&huart1,UART_FLAG_RXNE);
  }
  /* USER CODE END USART1_IRQn 1 */
}

5.接收状态分析

五个状态:正在接收帧头,正在接收帧目标地址,正在接收帧功能码ID,正在接收帧数据部分长度信息,正在接收帧数据部分,正在接收帧的和校验,正在接收帧的附加校验
匿名上位机V7.12协议编程(基于STM32F407+CubeMX+UART外设通信)
简单描述一下接收过程:
初始状态(它的选定很重要): 等待接收帧头,如果接收到的不是0XAA,认为不是上位机发过来的数据,停留于该阶段,如 绿色自返箭头所示
②接收目标地址:接收的地址必定是硬件地址那8种,可以选择性根据这一特征进行判断,来决定是否迁移;笔者这里没有做判断,接收完地址,直接迁移到下一个状态。
③接收功能码ID:必定是0XE0,0XE1或0XE2其一,若不是,通信帧发生错误,迁移到初始状态;若是,迁移到下一步。
④接收数据部分长度信息:必定是小于或等于40,如果小于0或大于40,通信出错,迁移到初始状态;若是,迁移到下一步。
⑤接收数据:在到达数据长度前,都 自返于此状态接收数据,接收完之后呢,就迁移到下一个状态;
⑥接收和校验
接收附加校验: 到这时候,完整的一帧,已经接收完了 ,执行反馈操作,初始化通信帧,等待下一个接收到来。

五、状态机思想应用于接收逻辑

1.头文件增加类型定义

//数据帧的7个不同状态
enum FRAME_PART
{
	HEAD_PART=0,             //<正在接收帧头
	ADDR_PART,               //<正在接收帧目标地址
	ID_PART,                 //<正在接收帧功能码ID          
	DATA_LEN_PART,           //<正在接收帧数据部分长度信息
	DATA_PART,               //<正在接收帧数据部分
	SC_PART,                 //<正在接收帧的和校验
	AC_PART                  //<正在接收帧的附加校验
};

2.参数读写类帧的通信

①反馈给上位机的校验帧

匿名上位机V7.12协议编程(基于STM32F407+CubeMX+UART外设通信)

/**
  * @brief  发送数据校验帧
  * @param  id_get:接收到的通信帧功能id,sc_get:接收到的通信帧校验,ac_get:接收到的通信帧附加校验
  * @retval 
  */
static void ano_send_check_frame(uint8_t id_get,uint8_t sc_get,uint8_t ac_get)
{
	uint8_t buff[46];
	
	memset(send_frame_struct.data,0,40);
	send_frame_struct.function_id=0X00;
	send_frame_struct.data_len=3;												//这里要随传递的参数自行计算,最好不要用sizeof,会减慢通信速度
	
	send_frame_struct.data[0]=id_get;
	send_frame_struct.data[1]=sc_get;
	send_frame_struct.data[2]=ac_get;
	
	ano_check_calculate(&send_frame_struct);
	frame_turn_to_array(&send_frame_struct,buff);


	ano_usart_send(buff,6+send_frame_struct.data_len);
}

②反馈给上位机的参数读取返回帧

匿名上位机V7.12协议编程(基于STM32F407+CubeMX+UART外设通信)

/**
  * @brief  参数读取返回
  * @param  发送通信帧结构体对象,上位机要读取的值
  * @retval 无返回
  */
void ano_send_parameter_frame(ano_frameStruct* send_frame,int32_t val)
{
	uint8_t buff[46];
	memset(send_frame_struct.data,0,40);
	send_frame_struct.function_id=0XE2;//参数写入反馈
	send_frame_struct.data_len=6;
	/*这里固定*/
	send_frame_struct.data[0]=BYTE0(send_frame->parameter->par_id);
	send_frame_struct.data[1]=BYTE1(send_frame->parameter->par_id);

	send_frame_struct.data[2]=BYTE0(val);
	send_frame_struct.data[3]=BYTE1(val);
	send_frame_struct.data[4]=BYTE2(val);
	send_frame_struct.data[5]=BYTE3(val);

	ano_check_calculate(&send_frame_struct);
	frame_turn_to_array(&send_frame_struct,buff);
	ano_usart_send(buff,6+send_frame_struct.data_len);
}

③状态机

/**
  * @brief  接收上位机的数据,根据所处的不同状态做不同的动作
  * @param  串口收到的单字节数据
  * @retval 无
  */
void ano_read_one_byte(uint8_t data)
{
	static uint8_t status=HEAD_PART;       //<初始状态,默认等待接收帧头
	static uint8_t cnt=0;
	
	switch (status)
	{
	 case HEAD_PART:
	 {
		 if(data==0XAA)					   //<判断,确保HEAD_PART状态收到的就是固定帧头0XAA,否则一直停留于该状态
		 {
		 status=ADDR_PART;
		                                  //<复位接收通信帧,因为接下来的内容都来源于接收中断
		 ano_frame_reset(&rec_frame_struct);
		 }
		break;
	 }
	 case ADDR_PART:
	 {
		rec_frame_struct.target_addr=data;			
		status=ID_PART;
		break;
	 }
	 case ID_PART:
	 {
		rec_frame_struct.function_id=data;
		                                //<匿名上位机只有在0XE0,0XE1或0XE2的功能命令中需要MCU反馈,否则就是接收出现了错误
		if((rec_frame_struct.function_id==0XE0)||(rec_frame_struct.function_id==0XE1)||(rec_frame_struct.function_id==0XE2))
			status=DATA_LEN_PART;
		else
		{
			status=HEAD_PART;
			ano_frame_reset(&rec_frame_struct);
		}
		break;	
	 }
	 case DATA_LEN_PART:
	 {
		if(data>40)                     //<数据长度不可能大于40
		{
		 status=HEAD_PART;
		 ano_frame_reset(&rec_frame_struct);
		}
		else
		{
		rec_frame_struct.data_len=data;
		status=DATA_PART;
		}
	    break;
	 }
	 case DATA_PART:
	 {
		*(rec_frame_struct.data+cnt)=data; 
		cnt++;
		if(cnt>=rec_frame_struct.data_len)
		{
			status=SC_PART;                //<超过40,不再接收,强行结束,迁移到下一个状态
			cnt=0;
		}
		break;
	 }
	 case SC_PART:
	 {
		 rec_frame_struct.sum_check=data;
		 status=AC_PART;
		 break;
	 }
	 case AC_PART:
	 {
		rec_frame_struct.add_check=data;
		                               //<完整的一帧接收完了,马上反馈信息给上位机
		ano_parameter_feedback(&rec_frame_struct);
		status=HEAD_PART;             //<等待下一帧到来
		break;
	 }
   }
}

④接收一帧完成后的动作

static void ano_parameter_feedback(ano_frameStruct* rec_frame)
{
    /*这样做是为了命名区分开发送和接收,看下列操作其实两者的参数信息同步*/
	send_frame_struct.parameter->par_id=0;
	rec_frame->parameter->par_id=0;
	send_frame_struct.parameter->par_val=0;
	rec_frame->parameter->par_val=0;


	if(ano_check(rec_frame))          //<对于接收的完整通信帧,如果校验成功,表示帧的接收没有出现错误
	{ 
	 rec_frame->parameter->par_id=rec_frame->data[0]+(rec_frame->data[1]<<8); //<记录上位机给定的参数ID
	 send_frame_struct.parameter->par_id=rec_frame->parameter->par_id;
	 if (rec_frame->function_id==0XE1)//<说明是上位机参数读取,下位机参数读取返回
	 {		 	
		 switch (rec_frame->parameter->par_id)
		 {
			 case HWTYPE:
			 {
				 ano_send_parameter_frame(&send_frame_struct,PRO_ADDR);//将设备伪装成拓空者飞控
				 break;
			 }
			 case ID_INFO5:
			 {
				 ano_send_parameter_frame(&send_frame_struct,INFO5);
				 break;
			 }
			 case ID_INFO6:
			 {
				 ano_send_parameter_frame(&send_frame_struct,INFO6);
				 break;
			 }
			 case ID_INFO7:
			 {
				 ano_send_parameter_frame(&send_frame_struct,INFO7);
				 break;
			 }
			 case ID_INFO8:
			 {
				 ano_send_parameter_frame(&send_frame_struct,INFO8);
				 break;
			 }
			 case ID_INFO9:
			 {
				 ano_send_parameter_frame(&send_frame_struct,INFO9);
				 break;
			 }
			 case ID_INFO10:
			 {
				 ano_send_parameter_frame(&send_frame_struct,INFO10);
				 break;
			 }
			///....其他参数id的情况,就交给读者自己定义了
		 }
		 return;
	 }
		else if(rec_frame->function_id==0XE2)	//<说明是上位机改变了下位机的值并校验返回
	 {
	 	 rec_frame->parameter->par_val=rec_frame->data[2]+(rec_frame->data[3]<<8)+(rec_frame->data[4]<<16)+(rec_frame->data[5]<<24);
		 send_frame_struct.parameter->par_val=rec_frame->parameter->par_val;
		 switch (rec_frame->parameter->par_id)
		 {
			 case ID_INFO5:
			 {
				 ID_INFO5=rec_frame->parameter->par_val;
				 break;
			 }
			 case ID_INFO6:
			 {
				 ID_INFO6=rec_frame->parameter->par_val;
				 break;
			 }
			 case ID_INFO7:
			 {
				 ID_INFO7=rec_frame->parameter->par_val;
				 break;
			 }
			 case ID_INFO8:
			 {
				 ID_INFO8=rec_frame->parameter->par_val;
				 break;
			 }
			 case ID_INFO9:
			 {
				 ID_INFO9=rec_frame->parameter->par_val;
				 break;
			 }
			 case ID_INFO10:
			 {
				 ID_INFO10=rec_frame->parameter->par_val;
				 break;
			 }
			 ///....其他参数id的情况,就交给读者自己定义了
		 }
		 ano_send_check_frame(rec_frame->function_id,rec_frame->sum_check,rec_frame->add_check);
		 return;
	 }
  }
}

变量INF05-INF010是用户自己定义的,取决于读者在项目中用于哪个参数的调试,这里只是给个模板。

六、展开

1.上位机的使用

官方使用教程

2.串口中断接收的优先级

发送逻辑不受影响。由于示例采用的是中断接收模式
很遗憾,代码的交互任务受到中断优先级影响十分严重。因为在一个项目中,通信类任务往往是中频任务,这就意味着它经常被高频任务中断打断,在这样的情况下,正常得实现交互几乎不可能(主要在于MCU难以在频繁被打断的情况下接收完整的一帧数据),起码按照笔者上述的代码设计是难以实现在中频任务中(高频,中频和低频任务同时存在)仍旧能保持正常通信的。
所以,尽量确保串口接收中断的优先级要高,不被打断:
匿名上位机V7.12协议编程(基于STM32F407+CubeMX+UART外设通信)
或者是,使用占用MCU资源更少的方法,来解决这个问题,也就是配合DMA,这样就不怕这个问题。但即使使用了DMA,在高频任务(比如电控的FOC任务)抢占的时候,作为中频或低频的交互任务,能够顺利运行,仍然对软件工程师来说是个巨大的挑战。

3.效率问题

无法否认一个事实,函数的高度封装,对于MCU这种主频低的芯片来说,所带来的影响是巨大的。好比如上述的frame_turn_to_array() 函数以及ano_send_flexible_frame(uint8_t id,int32_t x_coordinate,int32_t y_coordinate)函数,它们的设计思想就是把某一个操作封装好,然后通用化的,放置在一个功能函数里面,函数的调用多了,意味着进栈出栈频率高,故它的效率,笔者是没法保证的。
所以,上位机的函数,绝对绝对不能放进要求有固定控制周期的控制中断函数中,比如,一个PID控制周期是5ms,每5ms中断就产生,但是匿名上位机的发送函数和接收逻辑一来,MCU的处理反应远远大于5ms,这样你的控制项目,它的控制周期是不稳定的,严重影响你的带宽,也不可能做出一个稳定的系统。

4.DMA接收模式

就交给大家能不能利用这个模式去开发啦!
笔者若后期有这个需求会更新到这里来。文章来源地址https://www.toymoban.com/news/detail-413314.html

到了这里,关于匿名上位机V7.12协议编程(基于STM32F407+CubeMX+UART外设通信)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 基于STM32F407-LAN9252的EtherCAT从站协议移植过程

    EtherCAT移植过程 前言:刚拿到一个EtherCAT的开发需求,本来想安心当个CV战士的,结果在网上找了一圈,只有https://www.hexcode.cn/article/5e3ee9a835616641b2daef97 这篇写的相对详细,看来偷不了懒了,只有自己重新整理开发了。 1 、需求 基于STM32F407芯片、LAN9253芯片(自带PHY芯片),验

    2024年02月09日
    浏览(34)
  • STM32F407高级定时器-死区时间研究-STM32CubeMX

    距离上次写笔记,已经过去好长时间了 中间也折腾过不少东西,但是都没咋整理,主要是这中间都是在干活儿,不是自己想要研究的,也没想着要写。 从去年10月份开始想要学习FOC,10月份研究了一个月,到11月初,实现了SVPWM驱动BLDC电机,使用串口实现开环下转速和力矩调

    2023年04月23日
    浏览(53)
  • STM32 匿名助手提升电机控制调试效率(下位机代码和上位机操作演示)

    目录 前言 通信协议选择 上位机配置实现 下位机代码实现 通信效果演示 总结 前面介绍了通过VOFA上传变量并显示成波形方便调试的方法,VOFA上传的是浮点,一个浮点需要4个byte才能够表示,这对本就不是很快的串口带来说有不小负担,而且对于定点的MCU上传数据就不友好了

    2024年02月03日
    浏览(85)
  • STM32CubeMX+STM32F407+FreeRTos+LAN8720 以太网通信实现数据收发功能

    目录 前言 一、STM32CubeMX配置 二、修改代码 三、硬件测试 总结 该工程应用的以太网芯片是LAN8720,代码是基于STM32CUbeMx6.2.1配置生成的,在CubeMx中配置了ETH和LWIP,还有串口1和FREERTOS,最后通过创建任务函数实现udp的以太网数据收发功能。在测试中,可以在电脑的DOS窗口ping通在

    2024年02月08日
    浏览(54)
  • STM32 F407探索者移植正点原子LCD例程(CubeMx)

    先说明本次实验所使用的硬件型号,本次实验使用正点原子F407探索者V3.4版本,屏幕尺寸为4.3寸 TFTLCD。 本文章主要讲解如何移植正点原子的官方LCD代码,并不涉及太多理论知识。然后,官方的LCD代码应该是更新过了,因为之前也移植过一次其他板子的,好像也不是酱紫,所以

    2024年04月17日
    浏览(121)
  • [STM32] - STM32F407VET6使用STM32CubeMX配置FatFs,以及挂载时返回03错误码问题的解决

    为测试新买的开发板TF卡读写是否正常,使用STM32CubeMX(后简称CubeMX)进行代码构建。生成代码后烧录测试,发现在挂载TF卡时无法成功,返回值为错误3( FR_NOT_READY )。经排查后问题已解决,遂记录配置过程供大家参考,并讲解挂载时返回错误3的解决方式。 ①为了输出TF卡(

    2024年02月22日
    浏览(67)
  • 【物联网学习笔记】CubeMx+STM32F407ZGT6+LWIP最最最基础配置

    使用正点原子探索者开发板 板载芯片:STM32F407ZGT6 PHY芯片:LAN8720 LWIP版本:2.1.2 本次目标:先ping通开发板再说! 修订: · 2024.4.23经评论区反馈又重新下载6.10版本CubeMX,发现确实没有Advanced Parameters,目前使用6.6.1存在,如有需要可以改成这个版本。 更改引脚 在ethernetif.c中找

    2024年04月27日
    浏览(69)
  • STM32CubeMX教程12 DMA 直接内存读取

    开发板(正点原子stm32f407探索者开发板V2.4) STM32CubeMX软件(Version 6.10.0) keil µVision5 IDE(MDK-Arm) ST-LINK/V2驱动 野火DAP仿真器 XCOM V2.6串口助手 使用STM32CubeMX软件配置STM32F407开发板上 串口USART1以DMA方式传输数据 ,然后实现与实验“STM32CubeMX教程9 USART/UART 异步通信”相同的目标

    2024年02月03日
    浏览(81)
  • 【STM32 CubeMX】串口编程DMA

    在嵌入式系统中,串口通信是一项至关重要的功能,它允许单片机与外部设备进行数据交换,如传感器、显示器或其他设备。然而,在高速数据传输的场景下,传统的串口通信方式可能会使CPU过于繁忙,从而影响系统的性能。为了解决这一问题,STM32系列微控制器提供了DMA(

    2024年02月20日
    浏览(56)
  • 【STM32F407】Note_01 STM32 编程环境搭建 -- Keil与VS code组合

    在使用进行STM32F407开发的时候,一般被推荐使用的是Keil。经过一整子的Keil洗礼,我真的要崩溃了,在界面、调试、代码跳转、代码格式化、开发效率等方面一直很抓狂。之前一直使用vs code开发,用着还蛮顺心,界面风格很讨人喜欢。因此,尝试在vs code上进行STM32开发。接下

    2024年02月16日
    浏览(41)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包