STM32基于OLED的多级菜单(控制步进/无刷电机/舵机,含FLASH存储数据掉电不丢失)

因为之前已经开源了不少了项目了,所以这次想做个菜单来管理一下之前的那些项目。话不多说,上菜。演示视频已在同名B站发布。

https://www.bilibili.com/video/BV1QH1TBnEdL/

cs 复制代码
int main(void)
{	
	OLED_Init(); //OLED初始化
	Key_Init();  //按键初始化
	MyRTC_Init();//RTC时钟初始化
	delay_init();//延时初始化
	Store_Init();//flash存储初始化(将flash中存储的数据加载到ram中)
	Servo_Init();//舵机初始化
//  dht11_init();    //温湿度传感器初始化(有代码引脚可能有冲突需检查修改)
//	Timer2_Init();   //步进电机初始化(有代码引脚可能有冲突需检查修改)
//	Motor_PWM_Init();//无刷电机初始化(有代码引脚可能有冲突需检查修改)
	
		  // 电调校准(首次使用需要)
//    Set_PWM_Pulse(PWM_MAX);  // 发送最大信号
//    delay_ms(3000);          // 等待3秒
//    Set_PWM_Pulse(PWM_MIN);  // 发送最小信号
//    delay_ms(3000);          // 等待3秒
	
	int menu2;
	while (1)
	{
		menu2 = menu1();//将当前是第几行赋予二级菜单,方便检测然后执行对应的二级函数
		if(menu2 == 1){menu2_Step_Motor();}//步进电机控制(共三级菜单,已写好,但软硬件引脚可能有冲突需自行修改后使用)
		if(menu2 == 2){menu2_brushless_Motor();}//无刷电机控制(共三级菜单,已写好,但软硬件引脚可能有冲突需自行修改后使用)
		if(menu2 == 3){menu2_duoji_pwm();}//舵机控制(共三级菜单已写好)
		if(menu2 == 4){menu2_jixiebi();}//机械臂控制(已写两级菜单,后续可根据我之前开源的机械臂自行diy)
		if(menu2 == 5){menu2_PID();}//四轴PID调参(含flash存储数据断电不丢失)(已写四级菜单,后续可根据我之前开源的四轴飞行器自行diy)
		if(menu2 == 6){menu2_Clock();}//实时时钟+温湿度检测显示(共两级菜单,温湿度传感器代码已写,硬件引脚需自行确认修改)
		if(menu2 == 7){menu2_Music();}//音乐播放(已写两级菜单,三级音乐播放菜单可参考我之前开源的jq8400的使用自行diy)
		if(menu2 == 8){menu2_Set();}//设置(已写三级菜单,本打算写pwr模式选择和看门狗四级菜单,实际未写)
	}
}

main.c主函数包括oled显示屏初始化,按键初始化,时钟初始化,flash初始化,舵机初始化,以及注释掉的三个温湿度传感器初始化,步进电机和无刷电机初始化,这三个的代码之所以被注释掉了,因为我的代码和硬件板子的引脚可能有冲突需要修改一下才能使用,而对于这三个模块的使用方法我之前都有过文章单独讲解可自行前往查看。

然后就是while循环 里的一级菜单,以及进入二级菜单的触发条件。每个二级菜单的功能介绍代码里都有注释,下面我也会一一讲解。首先看一下一级菜单的两个页面八个选项(步进电机,无刷电机,舵机,机械臂控制,四轴PID,实时时钟,音乐和设置)。

下面是一级菜单的代码,按键一是上移,按键二是下移,按键三是确认也就是进入下一级菜单,后面二三级以及更深入的菜单按键的逻辑都和这个类似(除了最深层的控制代码的按键可能是用来增减相关参数的),以及switch语句对应了光标移动到了每一行的oled显示。

cs 复制代码
/***************************************************************************************/
/*-----------------------------------------一级菜单----------------------------------------------*/
/**
  * @brief 一级菜单函数,用于显示一级菜单八行选项
  * @param  无
  * @retval 返回当前行是第几行
  */
int menu1(void)
{
	
	OLED_Update();
	while(1)
	{
		KeyNum = Key_GetNum();
		if(KeyNum == 1)//上一项
		{
			flag--;
			if(flag == 0){flag = 8;}
			D = 1;//上一项标志
			OLED_AnimUpdate();//按下按钮,刷新一次动画效果
		}
		if(KeyNum == 2)//下一项
		{
			flag++;
			if(flag == 9){flag = 1;}
			D = 2;//下一项标志
			OLED_AnimUpdate();//按下按钮,刷新一次动画效果
		}
		if(KeyNum == 3)//确认
		{
			OLED_Clear();//清屏
			OLED_Update();//刷屏
			OLED_AnimUpdate();//按下按钮,刷新一次动画效果
			return flag;
		}
		switch(flag)
		{
			case 1:
			{
				//再次显示,解决乱码和闪烁问题(确保反相前是亮的状态)
				OLED_ShowString(0,0,"步进电机             ",OLED_8X16);
				OLED_ShowString(0,16,"无刷电机            ",OLED_8X16);
				OLED_ShowString(0,32,"SG90舵机            ",OLED_8X16);
				OLED_ShowString(0,48,"机械臂控制          ",OLED_8X16);
				if(D==2){OLED_Animation(0,0,0,0,0,0,64,16);}//下
				if(D==1){OLED_Animation(0,16,64,16,0,0,64,16);}//上
				break;
			}
			case 2:
			{
				//再次显示,解决乱码和闪烁问题(确保反相前是亮的状态)
				OLED_ShowString(0,0,"步进电机             ",OLED_8X16);
				OLED_ShowString(0,16,"无刷电机            ",OLED_8X16);
				OLED_ShowString(0,32,"SG90舵机            ",OLED_8X16);
				OLED_ShowString(0,48,"机械臂控制          ",OLED_8X16);
				if(D==2){OLED_Animation(0,0,64,16,0,16,64,16);}
				if(D==1){OLED_Animation(0,32,64,16,0,16,64,16);}	
				break;				
			}
			case 3:
			{
				//再次显示,解决乱码和闪烁问题(确保反相前是亮的状态)
				OLED_ShowString(0,0,"步进电机             ",OLED_8X16);
				OLED_ShowString(0,16,"无刷电机            ",OLED_8X16);
				OLED_ShowString(0,32,"SG90舵机            ",OLED_8X16);
				OLED_ShowString(0,48,"机械臂控制          ",OLED_8X16);
				if(D==2){OLED_Animation(0,16,64,16,0,32,64,16);}
				if(D==1){OLED_Animation(0,48,56,16,0,32,64,16);}			
				break;
			}
			case 4:
			{
				//再次显示,解决乱码和闪烁问题(确保反相前是亮的状态)
				OLED_ShowString(0,0,"步进电机             ",OLED_8X16);
				OLED_ShowString(0,16,"无刷电机            ",OLED_8X16);
				OLED_ShowString(0,32,"SG90舵机            ",OLED_8X16);
				OLED_ShowString(0,48,"机械臂控制          ",OLED_8X16);
				if(D==2){OLED_Animation(0,32,64,16,0,48,80,16);}
				if(D==1){OLED_Animation(0,64,56,16,0,48,80,16);}
				break;
			}
			case 5:
			{
				//再次显示,解决乱码和闪烁问题(确保反相前是亮的状态)
				OLED_ShowString(0,0,"四轴PID                ",OLED_8X16);
				OLED_ShowString(0,16,"实时时钟              ",OLED_8X16);
				OLED_ShowString(0,32,"音乐Music             ",OLED_8X16);
				OLED_ShowString(0,48,"设置Set               ",OLED_8X16);
				if(D==2){OLED_Animation(0,0,0,0,0,0,56,16);}
				if(D==1){OLED_Animation(0,16,32,16,0,0,56,16);}
				break;
			}
			case 6:
			{
				//再次显示,解决乱码和闪烁问题(确保反相前是亮的状态)
				OLED_ShowString(0,0,"四轴PID                ",OLED_8X16);
				OLED_ShowString(0,16,"实时时钟              ",OLED_8X16);
				OLED_ShowString(0,32,"音乐Music             ",OLED_8X16);
				OLED_ShowString(0,48,"设置Set               ",OLED_8X16);
				if(D==2){OLED_Animation(0,0,24,16,0,16,64,16);}
				if(D==1){OLED_Animation(0,32,32,16,0,16,64,16);}
				break;
			}
			case 7:
			{
				//再次显示,解决乱码和闪烁问题(确保反相前是亮的状态)
				OLED_ShowString(0,0,"四轴PID                ",OLED_8X16);
				OLED_ShowString(0,16,"实时时钟              ",OLED_8X16);
				OLED_ShowString(0,32,"音乐Music             ",OLED_8X16);
				OLED_ShowString(0,48,"设置Set               ",OLED_8X16);
				if(D==2){OLED_Animation(0,16,32,16,0,32,72,16);}
				if(D==1){OLED_Animation(0,48,32,16,0,32,72,16);}
				break;
			}
			case 8:
			{
				//再次显示,解决乱码和闪烁问题(确保反相前是亮的状态)
				OLED_ShowString(0,0,"四轴PID                ",OLED_8X16);
				OLED_ShowString(0,16,"实时时钟              ",OLED_8X16);
				OLED_ShowString(0,32,"音乐Music             ",OLED_8X16);
				OLED_ShowString(0,48,"设置Set               ",OLED_8X16);
				if(D==2){OLED_Animation(0,32,32,16,0,48,56,16);}
				if(D==1){OLED_Animation(0,64,0,0,0,48,56,16);}
				break;
			}
		}
	}
}

接下来将一一讲解各个菜单底下更深入的菜单:

1.步进电机二三级菜单: 步进电机二级菜单为方向、角度、速度控制的选择,三级菜单则是相关对应的控制,方向控制0为反转,1为正转,速度控制0为减速,1为加速,角度控制则对应控制步进电机选择相应的角度。

下面看步进电机二三级菜单代码,步进电机控制原理可参考我之前写的文章,代码里注释也很详细,菜单里按键代码和一级菜单控制逻辑一样,只需要按我上面说的确定软硬件引脚没有冲突打开我注释掉的控制步进电机的代码即可使用。

cs 复制代码
/*--------------------------------------------------------------------------------------*/
/*-----------------------------------二三级步进电机菜单------------------------------------------------*/
/**
  * @brief 三级步进电机方向设置菜单
  * @param  1为正转,0为反转
  * @retval 
  */
int menu3_SetDirection(void)
{
	while(1)
	{
		KeyNum = Key_GetNum();
		if(KeyNum == 1)
		{
			Step_Direction=1;//正转
			//StartMotorMove(1 * STEPS_PER_REV, 0);
			//(前面的数字可调节旋转角度1是360°,0.5就是180°以此类推,后面的数字0是正转,1是反转)
			OLED_AnimUpdate();//按下按钮,刷新一次动画效果
		}
		if(KeyNum == 2)
		{
			Step_Direction=0;//反转
			//StartMotorMove(1 * STEPS_PER_REV, 1);
			//(前面的数字可调节旋转角度1是360°,0.5就是180°以此类推,后面的数字0是正转,1是反转)
			OLED_AnimUpdate();//按下按钮,刷新一次动画效果
		}
		if(KeyNum == 3)
		{
			OLED_AnimUpdate();//按下按钮,刷新一次动画效果
			return 0;//返回
		}
		OLED_ShowString(0,0,"<-            ",OLED_8X16);
		OLED_Printf(0,16,OLED_8X16,"方向控制:%d      ",Step_Direction);
		OLED_Printf(0,32,OLED_8X16,"角度控制:%d      ",Step_Angle);
		OLED_Printf(0,48,OLED_8X16,"速度控制:%d      ",Step_Speed);
		OLED_ReverseArea(72,16,8,16);
		OLED_Update();
	}
}

/**
  * @brief 三级步进电机角度控制菜单
  * @param 实际并未实现,代码可参考上面方向设置的函数StartMotorMove(1 * STEPS_PER_REV, 1)自行实现
  * @retval 
  */
int menu3_SetAngle(void)
{
	while(1)
	{
		KeyNum = Key_GetNum();
		if(KeyNum == 1)
		{
			Step_Angle+=10;//按一次角度+10
			if(Step_Angle >= 180){Step_Angle = 180;}
			OLED_AnimUpdate();//按下按钮,刷新一次动画效果
		}
		if(KeyNum == 2)
		{
			Step_Angle-=10;//按一次角度-10
			if(Step_Angle <= 0){Step_Angle = 0;}
			OLED_AnimUpdate();//按下按钮,刷新一次动画效果
		}
		if(KeyNum == 3)
		{
			OLED_AnimUpdate();//按下按钮,刷新一次动画效果
			return 0;//返回
		}
		OLED_ShowString(0,0,"<-            ",OLED_8X16);
		OLED_Printf(0,16,OLED_8X16,"方向控制:%d      ",Step_Direction);
		OLED_Printf(0,32,OLED_8X16,"角度控制:%d      ",Step_Angle);
		OLED_Printf(0,48,OLED_8X16,"速度控制:%d      ",Step_Speed);
		if(Step_Angle < 10){OLED_ReverseArea(72,32,8,16);}
		if((Step_Angle >= 10)&&(Step_Angle < 100)){OLED_ReverseArea(72,32,16,16);}
		if(Step_Angle >= 100) OLED_ReverseArea(72,32,24,16);
		OLED_Update();
	}
}

/**
  * @brief 步进电机三级速度控制菜单
  * @param  1为加速,0为减速
  * @retval 
  */
int menu3_SetSpeed(void)
{
	while(1)
	{
		KeyNum = Key_GetNum();
		if(KeyNum == 1)
		{
			Step_Speed=1;//加速
			//AccelerateMotor();//加速函数
			OLED_AnimUpdate();//按下按钮,刷新一次动画效果
		}
		if(KeyNum == 2)
		{
			Step_Speed=0;//减速
			//DecelerateMotor();//减速函数
			OLED_AnimUpdate();//按下按钮,刷新一次动画效果
		}
		if(KeyNum == 3)
		{
			OLED_AnimUpdate();//按下按钮,刷新一次动画效果
			return 0;//返回
		}
		OLED_ShowString(0,0,"<-            ",OLED_8X16);
		OLED_Printf(0,16,OLED_8X16,"方向控制:%d      ",Step_Direction);
		OLED_Printf(0,32,OLED_8X16,"角度控制:%d      ",Step_Angle);
		OLED_Printf(0,48,OLED_8X16,"速度控制:%d      ",Step_Speed);
		OLED_ReverseArea(72,48,8,16);
		OLED_Update();
	}
}

/**
  * @brief 步进电机控制二级菜单函数
  * @param  无
  * @retval 
  */
int menu2_Step_Motor(void)
{
	int menu3_momotor;
	uint8_t moflag = 1;//默认在第一行
	OLED_Update();
	while(1)
	{
		KeyNum = Key_GetNum();
		if(KeyNum == 1)//上一项
		{
			moflag--;
			if(moflag == 0){moflag = 4;}
			D = 1;//上一项标志
			OLED_AnimUpdate();//按下按钮,刷新一次动画效果
		}
		if(KeyNum == 2)//下一项
		{
			moflag++;
			if(moflag == 5){moflag = 1;}
			D = 2;//下一项标志
			OLED_AnimUpdate();//按下按钮,刷新一次动画效果
		}
		if(KeyNum == 3)//确认
		{
			OLED_Clear();
			OLED_Update();
			menu3_momotor = moflag;
			OLED_AnimUpdate();//按下按钮,刷新一次动画效果
		}
		if(menu3_momotor == 1){return 0;}//返回0,退到第一级菜单
		if(menu3_momotor == 2){menu3_momotor = menu3_SetDirection();}//进入三级方向设置菜单
		if(menu3_momotor == 3){menu3_momotor = menu3_SetAngle();}//进入三级角度设置菜单
		if(menu3_momotor == 4){menu3_momotor = menu3_SetSpeed();}//进入三级速度控制菜单
		
		switch(moflag)
		{
			//再次显示,解决乱码和闪烁问题(确保反相前是亮的状态)
			case 1:
			{
				OLED_ShowString(0,0,"<-            ",OLED_8X16);
				OLED_Printf(0,16,OLED_8X16,"方向控制:%d      ",Step_Direction);
				OLED_Printf(0,32,OLED_8X16,"角度控制:%d      ",Step_Angle);
				OLED_Printf(0,48,OLED_8X16,"速度控制:%d      ",Step_Speed);
				if(D==2){OLED_Animation(0,0,0,0,0,0,16,16);}//下
				if(D==1){OLED_Animation(0,16,64,16,0,0,16,16);}//上
				break;
			}
			//再次显示,解决乱码和闪烁问题(确保反相前是亮的状态)
			case 2:
			{
				OLED_ShowString(0,0,"<-            ",OLED_8X16);
				OLED_Printf(0,16,OLED_8X16,"方向控制:%d      ",Step_Direction);
				OLED_Printf(0,32,OLED_8X16,"角度控制:%d      ",Step_Angle);
				OLED_Printf(0,48,OLED_8X16,"速度控制:%d      ",Step_Speed);
				if(D==2){OLED_Animation(0,0,16,16,0,16,64,16);}//下
				if(D==1){OLED_Animation(0,32,64,16,0,16,64,16);}//上
				break;
			}
			//再次显示,解决乱码和闪烁问题(确保反相前是亮的状态)
			case 3:
			{
				OLED_ShowString(0,0,"<-            ",OLED_8X16);
				OLED_Printf(0,16,OLED_8X16,"方向控制:%d      ",Step_Direction);
				OLED_Printf(0,32,OLED_8X16,"角度控制:%d      ",Step_Angle);
				OLED_Printf(0,48,OLED_8X16,"速度控制:%d      ",Step_Speed);
				if(D==2){OLED_Animation(0,16,64,16,0,32,64,16);}//下
				if(D==1){OLED_Animation(0,48,64,16,0,32,64,16);}//上
				break;
			}
			//再次显示,解决乱码和闪烁问题(确保反相前是亮的状态)
			case 4:
			{
				OLED_ShowString(0,0,"<-            ",OLED_8X16);
				OLED_Printf(0,16,OLED_8X16,"方向控制:%d      ",Step_Direction);
				OLED_Printf(0,32,OLED_8X16,"角度控制:%d      ",Step_Angle);
				OLED_Printf(0,48,OLED_8X16,"速度控制:%d      ",Step_Speed);
				if(D==2){OLED_Animation(0,32,64,16,0,48,64,16);}//下
				if(D==1){OLED_Animation(0,64,64,16,0,48,64,16);}//上
				break;
			}
		}
	}
}

**2.无刷电机二三级菜单:**无刷电机二级菜单分别为加速、减速、停止的选择,而三级菜单就是控制它的转速了。

下面看代码,同样的同上面步进电机一样,原理可参考我之前无刷电机控制原理文章,代码里注释很详细,只需确定软硬件引脚没有冲突打开我注释的代码就能控制无刷电机了。

cs 复制代码
/*--------------------------------------------------------------------------------------*/
/*-----------------------------------二三级无刷电机菜单-----------------------------------------*/
/**
  * @brief 无刷电机三级加速菜单
  * @param  无
  * @retval 
  */
int menu3_blmotorjiasu(void)
{
	while(1)
	{
		KeyNum = Key_GetNum();
		if(KeyNum == 1)
		{
			brushless_speed+=50;//按一次速度+50
			if(brushless_speed >= 1000){brushless_speed = 1000;}
			OLED_AnimUpdate();//按下按钮,刷新一次动画效果
		}
		if(KeyNum == 2)
		{
			brushless_speed+=100;//按一次速度+100
			if(brushless_speed >= 1000){brushless_speed = 1000;}
			OLED_AnimUpdate();//按下按钮,刷新一次动画效果
		}
		if(KeyNum == 3)
		{
			OLED_AnimUpdate();//按下按钮,刷新一次动画效果
			return 0;//返回
		}
		OLED_ShowString(0,0,"<-            ",OLED_8X16);
		OLED_Printf(0,16,OLED_8X16,"加速:%d      ",brushless_speed);
		OLED_Printf(0,32,OLED_8X16,"减速:%d      ",brushless_speed);
		OLED_ShowString(0,48,"停止          ",OLED_8X16);
		if(brushless_speed < 100){OLED_ReverseArea(40,16,16,16);}
		if((brushless_speed >= 100)&&(brushless_speed < 1000)){OLED_ReverseArea(40,16,24,16);}
		if(brushless_speed >= 1000) OLED_ReverseArea(40,16,32,16);
		OLED_Update();
	}
}

/**
  * @brief 无刷电机三级减速菜单
  * @param  无
  * @retval 
  */
int menu3_blmotorjiansu(void)
{
	while(1)
	{
		KeyNum = Key_GetNum();
		if(KeyNum == 1)
		{
			brushless_speed-=50;//按一次速度-50
			if(brushless_speed <= 0){brushless_speed = 0;}
			OLED_AnimUpdate();//按下按钮,刷新一次动画效果
		}
		if(KeyNum == 2)
		{
			brushless_speed-=100;//按一次速度-100
			if(brushless_speed <= 0){brushless_speed = 0;}
			OLED_AnimUpdate();//按下按钮,刷新一次动画效果
		}
		if(KeyNum == 3)
		{
			OLED_AnimUpdate();//按下按钮,刷新一次动画效果
			return 0;//返回
		}
		OLED_ShowString(0,0,"<-            ",OLED_8X16);
		OLED_Printf(0,16,OLED_8X16,"加速:%d      ",brushless_speed);
		OLED_Printf(0,32,OLED_8X16,"减速:%d      ",brushless_speed);
		OLED_ShowString(0,48,"停止          ",OLED_8X16);
		if(brushless_speed < 100){OLED_ReverseArea(40,32,16,16);}
		if((brushless_speed >= 100)&&(brushless_speed < 1000)){OLED_ReverseArea(40,32,24,16);}
		if(brushless_speed >= 1000) OLED_ReverseArea(40,32,32,16);
		OLED_Update();
	}
}

/**
  * @brief 无刷电机二级菜单函数
  * @param  无
  * @retval 
  */
int menu2_brushless_Motor(void)
{
	int menu3_blmotor;
	uint8_t moflag = 1;//默认在第一行
	OLED_Update();
	while(1)
	{
		KeyNum = Key_GetNum();
		if(KeyNum == 1)//上一项
		{
			moflag--;
			if(moflag == 0){moflag = 4;}
			D = 1;//上一项标志
			OLED_AnimUpdate();//按下按钮,刷新一次动画效果
		}
		if(KeyNum == 2)//下一项
		{
			moflag++;
			if(moflag == 5){moflag = 1;}
			D = 2;//下一项标志
			OLED_AnimUpdate();//按下按钮,刷新一次动画效果
		}
		if(KeyNum == 3)//确认
		{
			OLED_Clear();//清屏
			OLED_Update();//刷屏
			menu3_blmotor = moflag;
			OLED_AnimUpdate();//按下按钮,刷新一次动画效果
		}
		if(menu3_blmotor == 1){return 0;}//返回0,退到第一级菜单
		if(menu3_blmotor == 2){menu3_blmotor = menu3_blmotorjiasu();}//进入无刷电机三级加速菜单
		if(menu3_blmotor == 3){menu3_blmotor = menu3_blmotorjiansu();}//进入无刷电机三级减速菜单
		if(menu3_blmotor == 4){brushless_speed=0;}
			
		//Motor_Control(brushless_speed);//开启此注释即可控制无刷电机(前提硬件引脚已连接好且无冲突)
		
		switch(moflag)
		{
			//再次显示,解决乱码和闪烁问题(确保反相前是亮的状态)
			case 1:
			{
				OLED_ShowString(0,0,"<-            ",OLED_8X16);
				OLED_Printf(0,16,OLED_8X16,"加速:%d      ",brushless_speed);
				OLED_Printf(0,32,OLED_8X16,"减速:%d      ",brushless_speed);
				OLED_ShowString(0,48,"停止          ",OLED_8X16);
				if(D==2){OLED_Animation(0,0,0,0,0,0,16,16);}//下
				if(D==1){OLED_Animation(0,16,64,16,0,0,16,16);}//上
				break;
			}
			//再次显示,解决乱码和闪烁问题(确保反相前是亮的状态)
			case 2:
			{
				OLED_ShowString(0,0,"<-            ",OLED_8X16);
				OLED_Printf(0,16,OLED_8X16,"加速:%d      ",brushless_speed);
				OLED_Printf(0,32,OLED_8X16,"减速:%d      ",brushless_speed);
				OLED_ShowString(0,48,"停止          ",OLED_8X16);
				if(D==2){OLED_Animation(0,0,16,16,0,16,32,16);}//下
				if(D==1){OLED_Animation(0,32,64,16,0,16,32,16);}//上
				break;
			}
			//再次显示,解决乱码和闪烁问题(确保反相前是亮的状态)
			case 3:
			{
				OLED_ShowString(0,0,"<-            ",OLED_8X16);
				OLED_Printf(0,16,OLED_8X16,"加速:%d      ",brushless_speed);
				OLED_Printf(0,32,OLED_8X16,"减速:%d      ",brushless_speed);
				OLED_ShowString(0,48,"停止          ",OLED_8X16);
				if(D==2){OLED_Animation(0,16,64,16,0,32,32,16);}//下
				if(D==1){OLED_Animation(0,48,64,16,0,32,32,16);}//上
				break;
			}
			//再次显示,解决乱码和闪烁问题(确保反相前是亮的状态)
			case 4:
			{
				OLED_ShowString(0,0,"<-            ",OLED_8X16);
				OLED_Printf(0,16,OLED_8X16,"加速:%d      ",brushless_speed);
				OLED_Printf(0,32,OLED_8X16,"减速:%d      ",brushless_speed);
				OLED_ShowString(0,48,"停止          ",OLED_8X16);
				if(D==2){OLED_Animation(0,32,64,16,0,48,32,16);}//下
				if(D==1){OLED_Animation(0,64,64,16,0,48,32,16);}//上
				break;
			}
		}
	}
}

**3.SG90舵机二三级菜单:**舵机二级菜单分别为角度的加减选择,以及角度的设置,角度的设置也就是最终的舵机角度控制。三级菜单也就是角度的加加减减了。

下面看代码:舵机的代码原理就更简单了,就是pwm控制,正好手边有个舵机也就试了一下也是完全没问题的,代码里注释也很详细可自行观看。

cs 复制代码
/*--------------------------------------------------------------------------------------*/
/*-----------------------------二三级舵机菜单-----------------------------------------*/

/**
  * @brief 舵机三级加角度菜单函数
  * @param  无
  * @retval 
  */
int menu3_duojijia(void)
{
	while(1)
	{
		KeyNum = Key_GetNum();
		if(KeyNum == 1)
		{
			jiaodu+=5;//按一次角度+5
			if(jiaodu >= 180){jiaodu = 180;}
			OLED_AnimUpdate();//按下按钮,刷新一次动画效果
		}
		if(KeyNum == 2)
		{
			jiaodu+=10;//按一次角度+10
			if(jiaodu >= 180){jiaodu = 180;}
			OLED_AnimUpdate();//按下按钮,刷新一次动画效果
		}
		if(KeyNum == 3)
		{
			OLED_AnimUpdate();//按下按钮,刷新一次动画效果
			return 0;//返回
		}
		OLED_ShowString(0,0,"<-            ",OLED_8X16);
		OLED_Printf(0,16,OLED_8X16,"角度加:%d      ",jiaodu);
		OLED_Printf(0,32,OLED_8X16,"角度减:%d      ",jiaodu);
		OLED_ShowString(0,48,"角度设置          ",OLED_8X16);
		if(jiaodu < 10){OLED_ReverseArea(56,16,8,16);}
		if((jiaodu >= 10)&&(jiaodu < 100)){OLED_ReverseArea(56,16,16,16);}
		if(jiaodu >= 100) OLED_ReverseArea(56,16,24,16);
		OLED_Update();
	}
}

/**
  * @brief 舵机三级减角度菜单函数
  * @param  无
  * @retval 
  */
int menu3_duojijian(void)
{
	while(1)
	{
		KeyNum = Key_GetNum();
		if(KeyNum == 1)
		{
			jiaodu-=5;//按一次角度减5
			if(jiaodu <= 0){jiaodu = 0;}
			OLED_AnimUpdate();//按下按钮,刷新一次动画效果
		}
		if(KeyNum == 2)
		{
			jiaodu-=10;//按一次角度减10
			if(jiaodu <= 0){jiaodu = 0;}
			OLED_AnimUpdate();//按下按钮,刷新一次动画效果
		}
		if(KeyNum == 3)
		{
			OLED_AnimUpdate();//按下按钮,刷新一次动画效果
			return 0;//返回
		}
		OLED_ShowString(0,0,"<-            ",OLED_8X16);
		OLED_Printf(0,16,OLED_8X16,"角度加:%d      ",jiaodu);
		OLED_Printf(0,32,OLED_8X16,"角度减:%d      ",jiaodu);
		OLED_ShowString(0,48,"角度设置          ",OLED_8X16);
		if(jiaodu < 10){OLED_ReverseArea(56,32,8,16);}
		if((jiaodu >= 10)&&(jiaodu < 100)){OLED_ReverseArea(56,32,16,16);}
		if(jiaodu >= 100) OLED_ReverseArea(56,32,24,16);
		OLED_Update();
	}
}

/**
  * @brief 舵机二级菜单函数
  * @param  无
  * @retval 
  */
int menu2_duoji_pwm(void)
{
	int menu3_duoji;
	uint8_t moflag = 1;//默认在第一行
	OLED_Update();
	while(1)
	{
		KeyNum = Key_GetNum();
		if(KeyNum == 1)//上一项
		{
			moflag--;
			if(moflag == 0){moflag = 4;}
			D = 1;//上一项标志
			OLED_AnimUpdate();//按下按钮,刷新一次动画效果
		}
		if(KeyNum == 2)//下一项
		{
			moflag++;
			if(moflag == 5){moflag = 1;}
			D = 2;//下一项标志
			OLED_AnimUpdate();//按下按钮,刷新一次动画效果
		}
		if(KeyNum == 3)//确认
		{
			OLED_Clear();//清屏
			OLED_Update();//刷屏
			menu3_duoji = moflag;
			OLED_AnimUpdate();//按下按钮,刷新一次动画效果
		}
		if(menu3_duoji == 1){return 0;}//返回0,退到第一级菜单
		if(menu3_duoji == 2){menu3_duoji = menu3_duojijia();}//进入舵机加角度三级菜单
		if(menu3_duoji == 3){menu3_duoji = menu3_duojijian();}//进入舵机减角度三级菜单
		if(menu3_duoji == 4){Servo_SetAngle1(jiaodu);}//通过角度信息控制舵机
		
		switch(moflag)
		{
			//再次显示,解决乱码和闪烁问题(确保反相前是亮的状态)
			case 1:
			{
				OLED_ShowString(0,0,"<-            ",OLED_8X16);
				OLED_Printf(0,16,OLED_8X16,"角度加:%d      ",jiaodu);
				OLED_Printf(0,32,OLED_8X16,"角度减:%d      ",jiaodu);
				OLED_ShowString(0,48,"角度设置          ",OLED_8X16);
				if(D==2){OLED_Animation(0,0,0,0,0,0,16,16);}//下
				if(D==1){OLED_Animation(0,16,64,16,0,0,16,16);}//上
				break;
			}
			//再次显示,解决乱码和闪烁问题(确保反相前是亮的状态)
			case 2:
			{
				OLED_ShowString(0,0,"<-            ",OLED_8X16);
				OLED_Printf(0,16,OLED_8X16,"角度加:%d      ",jiaodu);
				OLED_Printf(0,32,OLED_8X16,"角度减:%d      ",jiaodu);
				OLED_ShowString(0,48,"角度设置          ",OLED_8X16);
				if(D==2){OLED_Animation(0,0,16,16,0,16,48,16);}//下
				if(D==1){OLED_Animation(0,32,64,16,0,16,48,16);}//上
				break;
			}
			//再次显示,解决乱码和闪烁问题(确保反相前是亮的状态)
			case 3:
			{
				OLED_ShowString(0,0,"<-            ",OLED_8X16);
				OLED_Printf(0,16,OLED_8X16,"角度加:%d      ",jiaodu);
				OLED_Printf(0,32,OLED_8X16,"角度减:%d      ",jiaodu);
				OLED_ShowString(0,48,"角度设置          ",OLED_8X16);
				if(D==2){OLED_Animation(0,16,64,16,0,32,48,16);}//下
				if(D==1){OLED_Animation(0,48,64,16,0,32,48,16);}//上
				break;
			}
			//再次显示,解决乱码和闪烁问题(确保反相前是亮的状态)
			case 4:
			{
				OLED_ShowString(0,0,"<-            ",OLED_8X16);
				OLED_Printf(0,16,OLED_8X16,"角度加:%d      ",jiaodu);
				OLED_Printf(0,32,OLED_8X16,"角度减:%d      ",jiaodu);
				OLED_ShowString(0,48,"角度设置          ",OLED_8X16);
				if(D==2){OLED_Animation(0,32,64,16,0,48,64,16);}//下
				if(D==1){OLED_Animation(0,64,64,16,0,48,64,16);}//上
				break;
			}
		}
	}
}

**4.机械臂控制二级菜单:**机械臂控制就写到了二级菜单,将三种控制模式(摇杆蓝牙模式、示教器模式、记忆模式)显示出来了,老粉应该都知道我之前开源过一个四轴机械臂,对机械臂更深入菜单的diy可自行参考我之前开源的机械臂进行移植diy。

下面看代码,因为只写到二级菜单比较简单就不多赘述了自行看注释吧。

cs 复制代码
/*--------------------------------------------------------------------------------------*/
/*-----------------------------二级机械臂模式选择菜单------------------------------------------*/
/**
  * @brief 二级机械臂模式选择菜单函数(摇杆蓝牙模式,示教器模式,记忆模式)
  * @param  无
  * @retval 
  */
int menu2_jixiebi(void)
{
	int menu3_jxb;
	uint8_t jxbflag = 1;//默认在第一行
	OLED_Update();
	while(1)
	{
		KeyNum = Key_GetNum();
		if(KeyNum == 1)//上一项
		{
			jxbflag--;
			if(jxbflag == 0){jxbflag = 4;}//共四行
			D = 1;//上一项标志
			OLED_AnimUpdate();//按下按钮,刷新一次动画效果
		}
		if(KeyNum == 2)//下一项
		{
			jxbflag++;
			if(jxbflag == 5){jxbflag = 1;}
			D = 2;//下一项标志
			OLED_AnimUpdate();//按下按钮,刷新一次动画效果
		}
		if(KeyNum == 3)//确认
		{
			OLED_Clear();
			OLED_Update();
			menu3_jxb = jxbflag;
			OLED_AnimUpdate();//按下按钮,刷新一次动画效果
		}
		if(menu3_jxb == 1){return 0;}//返回0,退到第一级菜单
		if(menu3_jxb == 2){return 0;}//返回0,退到第一级菜单(三级菜单函数可自行diy)
		if(menu3_jxb == 3){return 0;}//返回0,退到第一级菜单(三级菜单函数可自行diy)
		if(menu3_jxb == 4){return 0;}//返回0,退到第一级菜单(三级菜单函数可自行diy)
		
		switch(jxbflag)
		{
			//再次显示,解决乱码和闪烁问题(确保反相前是亮的状态)
			case 1:
			{
				OLED_ShowString(0,0,"<-                 ",OLED_8X16);
				OLED_ShowString(0,16,"摇杆蓝牙模式      ",OLED_8X16);
				OLED_ShowString(0,32,"示教器模式        ",OLED_8X16);
				OLED_ShowString(0,48,"记忆模式          ",OLED_8X16);
				if(D==2){OLED_Animation(0,0,0,0,0,0,16,16);}//下
				if(D==1){OLED_Animation(0,16,48,16,0,0,16,16);}//上
				break;
			}
			//再次显示,解决乱码和闪烁问题(确保反相前是亮的状态)
			case 2:
			{
				OLED_ShowString(0,0,"<-                 ",OLED_8X16);
				OLED_ShowString(0,16,"摇杆蓝牙模式      ",OLED_8X16);
				OLED_ShowString(0,32,"示教器模式        ",OLED_8X16);
				OLED_ShowString(0,48,"记忆模式          ",OLED_8X16);
				if(D==2){OLED_Animation(0,0,16,16,0,16,96,16);}//下
				if(D==1){OLED_Animation(0,32,48,16,0,16,96,16);}//上
				break;
			}
			//再次显示,解决乱码和闪烁问题(确保反相前是亮的状态)
			case 3:
			{
				OLED_ShowString(0,0,"<-                 ",OLED_8X16);
				OLED_ShowString(0,16,"摇杆蓝牙模式      ",OLED_8X16);
				OLED_ShowString(0,32,"示教器模式        ",OLED_8X16);
				OLED_ShowString(0,48,"记忆模式          ",OLED_8X16);
				if(D==2){OLED_Animation(0,16,48,16,0,32,80,16);}//下
				if(D==1){OLED_Animation(0,48,48,16,0,32,80,16);}//上
				break;
			}
			//再次显示,解决乱码和闪烁问题(确保反相前是亮的状态)
			case 4:
			{
				OLED_ShowString(0,0,"<-                 ",OLED_8X16);
				OLED_ShowString(0,16,"摇杆蓝牙模式      ",OLED_8X16);
				OLED_ShowString(0,32,"示教器模式        ",OLED_8X16);
				OLED_ShowString(0,48,"记忆模式          ",OLED_8X16);
				if(D==2){OLED_Animation(0,32,48,16,0,48,64,16);}//下
				if(D==1){OLED_Animation(0,64,48,16,0,48,64,16);}//上
				break;
			}
		}
	}
}

**5.四轴PID调参二三四级菜单(含flash数据保持掉电不丢失):**四轴PID二级菜单为XYZ三个轴的选择,三级菜单为PID三个参数的选择,而四级菜单就是对PID参数的修改了且这个数据断电不会丢失,数据保存在flash中,上电后会加载回代码运行的sram中显示到屏幕上。

stm32的ROM主要包括:

1. 内部Flash存储器

STM32的ROM主要是内置Flash,用于存储程序代码和常量数据。

主要特性
  • 非易失性:断电后数据不丢失

  • 读取速度:0等待周期@≤24MHz,1等待周期@≤48MHz,2等待周期@≤72MHz

  • 编程/擦除次数:约1万次(典型值)

  • 数据保持时间:20-100年(取决于温度)

2. 系统存储器

  • 位置:0x1FFFF000 - 0x1FFFF7FF

  • 内容:Bootloader程序,用于串口、USB等下载方式

  • 保护:用户无法擦写,由ST预编程

3. 选项字节

  • 功能:配置读写保护、看门狗、复位模式等

  • 地址:0x1FFFF800 - 0x1FFFF80F

STM32的RAM主要包括:

1. SRAM(静态随机存取存储器)

基本特性

  • 类型:静态RAM,无需刷新电路

  • 速度:CPU时钟频率,零等待周期访问

  • 功耗:相对较高(持续供电维持数据)

  • 容量:通常几十KB到几百KB

2. 外设寄存器

基本概念

  • 地址映射:外设寄存器被映射到固定的内存地址

  • 访问方式:通过指针直接访问,类似内存操作

  • 位操作:支持位带操作(bit-banding)进行原子位操作

3. 内核外设寄存器

Cortex-M内核寄存器概述

  • 地址范围:0xE0000000 - 0xE00FFFFF

  • 功能:系统控制、中断控制、调试支持等

  • 访问权限:特权模式访问(部分寄存器)

介绍完rom和ram我们看代码,这里代码看着很多但其实只是因为有四级菜单,量很大但很多都是重复的步骤,只要看懂一部分就能懂全部了。同样的代码注释很详细,PID数据在哪里被保存进flash都有注释,而PID数据被从flash中读回sram在最开始的main主函数初始化里进行。

cs 复制代码
/*--------------------------------------------------------------------------------------*/
/*---------------------------------二三四级四轴PID菜单------------------------------------------*/

/**
  * @brief 四轴PID四级调参俯仰P函数菜单
  * @param  无
  * @retval 
  */
int menu4_pitch_p(void)
{
	while(1)
	{
		KeyNum = Key_GetNum();
		if(KeyNum == 1)
		{
			pitch_P+=100;//按一次pitch_P+100
			Store_Data[1]=pitch_P;//将pitch_P值存入数组
			if(pitch_P >= 1000){pitch_P = 1000;}
			OLED_AnimUpdate();//按下按钮,刷新一次动画效果
		}
		if(KeyNum == 2)
		{
			pitch_P-=100;//按一次pitch_P-100
			Store_Data[1]=pitch_P;//将pitch_P值存入数组
			if(pitch_P <= 0){pitch_P = 0;}
			OLED_AnimUpdate();//按下按钮,刷新一次动画效果
		}
		if(KeyNum == 3)
		{
			Store_Save();//将pitch_P值存入flash
			OLED_AnimUpdate();//按下按钮,刷新一次动画效果
			return 0;//返回
		}
		OLED_ShowString(0,0,"<-            ",OLED_8X16);
		OLED_Printf(0,16,OLED_8X16,"pitch-P:%d      ",pitch_P);
		OLED_Printf(0,32,OLED_8X16,"pitch-I:%d      ",pitch_I);
		OLED_Printf(0,48,OLED_8X16,"pitch-D:%d      ",pitch_D);
		if(pitch_P < 100){OLED_ReverseArea(64,16,8,16);}
		if((pitch_P >= 100)&&(pitch_P < 1000)){OLED_ReverseArea(64,16,24,16);}
		if(pitch_P >= 1000) OLED_ReverseArea(64,16,32,16);
		OLED_Update();
	}
}

/**
  * @brief 四轴PID四级调参俯仰I函数菜单
  * @param  无
  * @retval 
  */
int menu4_pitch_i(void)
{
	while(1)
	{
		KeyNum = Key_GetNum();
		if(KeyNum == 1)
		{
			pitch_I+=1;//按一次pitch_I+1
			Store_Data[2]=pitch_I;//将pitch_I值存入数组
			if(pitch_I >= 10){pitch_I = 10;}
			OLED_AnimUpdate();//按下按钮,刷新一次动画效果
		}
		if(KeyNum == 2)
		{
			pitch_I-=1;//按一次pitch_I-1
			Store_Data[2]=pitch_I;//将pitch_I值存入数组
			if(pitch_I <= 0){pitch_I = 0;}
			OLED_AnimUpdate();//按下按钮,刷新一次动画效果
		}
		if(KeyNum == 3)
		{
			Store_Save();//将pitch_I值存入flash
			OLED_AnimUpdate();//按下按钮,刷新一次动画效果
			return 0;//返回
		}
		OLED_ShowString(0,0,"<-            ",OLED_8X16);
		OLED_Printf(0,16,OLED_8X16,"pitch-P:%d      ",pitch_P);
		OLED_Printf(0,32,OLED_8X16,"pitch-I:%d      ",pitch_I);
		OLED_Printf(0,48,OLED_8X16,"pitch-D:%d      ",pitch_D);
		if(pitch_I < 10){OLED_ReverseArea(64,32,8,16);}
		if(pitch_I >= 10) OLED_ReverseArea(64,32,16,16);
		OLED_Update();
	}
}

/**
  * @brief 四轴PID四级调参俯仰D函数菜单
  * @param  无
  * @retval 
  */
int menu4_pitch_d(void)
{
	while(1)
	{
		KeyNum = Key_GetNum();
		if(KeyNum == 1)
		{
			pitch_D+=10;//按一次pitch_D+10
			Store_Data[3]=pitch_D;//将pitch_D值存入数组
			if(pitch_D >= 100){pitch_D = 100;}
			OLED_AnimUpdate();//按下按钮,刷新一次动画效果
		}
		if(KeyNum == 2)
		{
			pitch_D-=10;//按一次pitch_D-10
			Store_Data[3]=pitch_D;//将pitch_D值存入数组
			if(pitch_D <= 0){pitch_D = 0;}
			OLED_AnimUpdate();//按下按钮,刷新一次动画效果
		}
		if(KeyNum == 3)
		{
			Store_Save();////将pitch_D值存入flash
			OLED_AnimUpdate();//按下按钮,刷新一次动画效果
			return 0;//返回
		}
		OLED_ShowString(0,0,"<-            ",OLED_8X16);
		OLED_Printf(0,16,OLED_8X16,"pitch-P:%d      ",pitch_P);
		OLED_Printf(0,32,OLED_8X16,"pitch-I:%d      ",pitch_I);
		OLED_Printf(0,48,OLED_8X16,"pitch-D:%d      ",pitch_D);
		if(pitch_D < 100){OLED_ReverseArea(64,48,16,16);}
		if(pitch_D >= 100) OLED_ReverseArea(64,48,24,16);
		OLED_Update();
	}
}

/**
  * @brief 四轴PID三级俯仰调参函数菜单
  * @param  无
  * @retval 
  */
int menu3_pitch(void)
{
	int menu4_PPID=0;
	uint8_t pidflag = 1;//默认在第一行
	OLED_Update();
	while(1)
	{
		KeyNum = Key_GetNum();
		if(KeyNum == 1)//上一项
		{
			pidflag--;
			if(pidflag == 0){pidflag = 4;}
			D = 1;//上一项标志
			OLED_AnimUpdate();//按下按钮,刷新一次动画效果
		}
		if(KeyNum == 2)//下一项
		{
			pidflag++;
			if(pidflag == 5){pidflag = 1;}
			D = 2;//下一项标志
			OLED_AnimUpdate();//按下按钮,刷新一次动画效果
		}
		if(KeyNum == 3)//确认
		{		
			OLED_Clear();//清屏
			OLED_Update();//刷屏
			menu4_PPID = pidflag;
			OLED_AnimUpdate();//按下按钮,刷新一次动画效果
		}
		
		if(menu4_PPID == 1){return 0;}//返回0,退到第一级菜单
		if(menu4_PPID == 2){menu4_PPID = menu4_pitch_p();}//进入俯仰pid四级调参菜单
		if(menu4_PPID == 3){menu4_PPID = menu4_pitch_i();}//进入俯仰pid四级调参菜单
		if(menu4_PPID == 4){menu4_PPID = menu4_pitch_d();}//进入俯仰pid四级调参菜单
		
		pitch_P=Store_Data[1];//将flash中数据加载进ram
		pitch_I=Store_Data[2];//将flash中数据加载进ram
		pitch_D=Store_Data[3];//将flash中数据加载进ram
		
		switch(pidflag)
		{
			//再次显示,解决乱码和闪烁问题(确保反相前是亮的状态)
			case 1:
			{
				OLED_ShowString(0,0,"<-               ",OLED_8X16);
				OLED_Printf(0,16,OLED_8X16,"pitch-P:%d      ",pitch_P);
				OLED_Printf(0,32,OLED_8X16,"pitch-I:%d      ",pitch_I);
				OLED_Printf(0,48,OLED_8X16,"pitch-D:%d      ",pitch_D);
				if(D==2){OLED_Animation(0,0,0,0,0,0,16,16);}//下
				if(D==1){OLED_Animation(0,16,32,16,0,0,16,16);}//上
				break;
			}
			//再次显示,解决乱码和闪烁问题(确保反相前是亮的状态)
			case 2:
			{
				OLED_ShowString(0,0,"<-               ",OLED_8X16);
				OLED_Printf(0,16,OLED_8X16,"pitch-P:%d      ",pitch_P);
				OLED_Printf(0,32,OLED_8X16,"pitch-I:%d      ",pitch_I);
				OLED_Printf(0,48,OLED_8X16,"pitch-D:%d      ",pitch_D);
				if(D==2){OLED_Animation(0,0,16,16,0,16,56,16);}//下
				if(D==1){OLED_Animation(0,32,64,16,0,16,56,16);}//上
				break;
			}
			//再次显示,解决乱码和闪烁问题(确保反相前是亮的状态)
			case 3:
			{
				OLED_ShowString(0,0,"<-               ",OLED_8X16);
				OLED_Printf(0,16,OLED_8X16,"pitch-P:%d      ",pitch_P);
				OLED_Printf(0,32,OLED_8X16,"pitch-I:%d      ",pitch_I);
				OLED_Printf(0,48,OLED_8X16,"pitch-D:%d      ",pitch_D);
				if(D==2){OLED_Animation(0,16,32,16,0,32,56,16);}//下
				if(D==1){OLED_Animation(0,48,32,16,0,32,56,16);}//上
				break;
			}
			//再次显示,解决乱码和闪烁问题(确保反相前是亮的状态)
			case 4:
			{
				OLED_ShowString(0,0,"<-               ",OLED_8X16);
				OLED_Printf(0,16,OLED_8X16,"pitch-P:%d      ",pitch_P);
				OLED_Printf(0,32,OLED_8X16,"pitch-I:%d      ",pitch_I);
				OLED_Printf(0,48,OLED_8X16,"pitch-D:%d      ",pitch_D);
				if(D==2){OLED_Animation(0,32,64,16,0,48,56,16);}//下
				if(D==1){OLED_Animation(0,64,32,16,0,48,56,16);}//上
				break;
			}
		}
	}
}

/**
  * @brief 四轴PID四级调参横滚P函数菜单
  * @param  无
  * @retval 
  */
int menu4_roll_p(void)
{
	while(1)
	{
		KeyNum = Key_GetNum();
		if(KeyNum == 1)
		{
			roll_P+=100;//按一次roll_P+100
			Store_Data[4]=roll_P;//将roll_P存入数组
			if(roll_P >= 1000){roll_P = 1000;}
			OLED_AnimUpdate();//按下按钮,刷新一次动画效果
		}
		if(KeyNum == 2)
		{
			roll_P-=100;//按一次roll_P-100
			Store_Data[4]=roll_P;//将roll_P存入数组
			if(roll_P <= 0){roll_P = 0;}
			OLED_AnimUpdate();//按下按钮,刷新一次动画效果
		}
		if(KeyNum == 3)
		{
			Store_Save();//将roll_P存入flash
			OLED_AnimUpdate();//按下按钮,刷新一次动画效果
			return 0;//返回
		}
		OLED_ShowString(0,0,"<-            ",OLED_8X16);
		OLED_Printf(0,16,OLED_8X16,"roll-P:%d      ",roll_P);
		OLED_Printf(0,32,OLED_8X16,"roll-I:%d      ",roll_I);
		OLED_Printf(0,48,OLED_8X16,"roll-D:%d      ",roll_D);
		if(roll_P < 100){OLED_ReverseArea(56,16,8,16);}
		if((roll_P >= 100)&&(roll_P < 1000)){OLED_ReverseArea(56,16,24,16);}
		if(roll_P >= 1000) OLED_ReverseArea(56,16,32,16);
		OLED_Update();
	}
}

/**
  * @brief 四轴PID四级调参横滚I函数菜单
  * @param  无
  * @retval 
  */
int menu4_roll_i(void)
{
	while(1)
	{
		KeyNum = Key_GetNum();
		if(KeyNum == 1)
		{
			roll_I+=1;//按一次roll_I+1
			Store_Data[5]=roll_I;//将roll_I存入数组
			if(roll_I >= 10){roll_I = 10;}
			OLED_AnimUpdate();//按下按钮,刷新一次动画效果
		}
		if(KeyNum == 2)
		{
			roll_I-=1;//按一次roll_I-1
			Store_Data[5]=roll_I;//将roll_I存入数组
			if(roll_I <= 0){roll_I = 0;}
			OLED_AnimUpdate();//按下按钮,刷新一次动画效果
		}
		if(KeyNum == 3)
		{
			Store_Save();//将roll_I存入flash
			OLED_AnimUpdate();//按下按钮,刷新一次动画效果
			return 0;//返回
		}
		OLED_ShowString(0,0,"<-            ",OLED_8X16);
		OLED_Printf(0,16,OLED_8X16,"roll-P:%d      ",roll_P);
		OLED_Printf(0,32,OLED_8X16,"roll-I:%d      ",roll_I);
		OLED_Printf(0,48,OLED_8X16,"roll-D:%d      ",roll_D);
		if(roll_I < 10){OLED_ReverseArea(56,32,8,16);}
		if(roll_I >= 10) OLED_ReverseArea(56,32,16,16);
		OLED_Update();
	}
}

/**
  * @brief 四轴PID四级调参横滚D函数菜单
  * @param  无
  * @retval 
  */
int menu4_roll_d(void)
{
	while(1)
	{
		KeyNum = Key_GetNum();
		if(KeyNum == 1)
		{
			roll_D+=10;//按一次roll_D+10
			Store_Data[6]=roll_D;//将roll_D存入数组
			if(roll_D >= 100){roll_D = 100;}
			OLED_AnimUpdate();//按下按钮,刷新一次动画效果
		}
		if(KeyNum == 2)
		{
			roll_D-=10;//按一次roll_D-10
			Store_Data[6]=roll_D;//将roll_D存入数组
			if(roll_D <= 0){roll_D = 0;}
			OLED_AnimUpdate();//按下按钮,刷新一次动画效果
		}
		if(KeyNum == 3)
		{
			Store_Save();//将roll_D存入flash
			OLED_AnimUpdate();//按下按钮,刷新一次动画效果
			return 0;//返回
		}
		OLED_ShowString(0,0,"<-            ",OLED_8X16);
		OLED_Printf(0,16,OLED_8X16,"roll-P:%d      ",roll_P);
		OLED_Printf(0,32,OLED_8X16,"roll-I:%d      ",roll_I);
		OLED_Printf(0,48,OLED_8X16,"roll-D:%d      ",roll_D);
		if(roll_D < 100){OLED_ReverseArea(56,48,16,16);}
		if(roll_D >= 100) OLED_ReverseArea(56,48,24,16);
		OLED_Update();
	}
}

/**
  * @brief 四轴PID三级横滚调参函数菜单
  * @param  无
  * @retval 
  */
int menu3_roll(void)
{
	int menu4_PPID=0;
	uint8_t pidflag = 1;//默认在第一行
	OLED_Update();
	while(1)
	{
		KeyNum = Key_GetNum();
		if(KeyNum == 1)//上一项
		{
			pidflag--;
			if(pidflag == 0){pidflag = 4;}
			D = 1;//上一项标志
			OLED_AnimUpdate();//按下按钮,刷新一次动画效果
		}
		if(KeyNum == 2)//下一项
		{
			pidflag++;
			if(pidflag == 5){pidflag = 1;}
			D = 2;//下一项标志
			OLED_AnimUpdate();//按下按钮,刷新一次动画效果
		}
		if(KeyNum == 3)//确认
		{		
			OLED_Clear();//清屏
			OLED_Update();//刷屏
			menu4_PPID = pidflag;
			OLED_AnimUpdate();//按下按钮,刷新一次动画效果
		}
		
		if(menu4_PPID == 1){return 0;}//返回0,退到第一级菜单
		if(menu4_PPID == 2){menu4_PPID = menu4_roll_p();}//进入四级横滚pid调参菜单
		if(menu4_PPID == 3){menu4_PPID = menu4_roll_i();}//进入四级横滚pid调参菜单
		if(menu4_PPID == 4){menu4_PPID = menu4_roll_d();}//进入四级横滚pid调参菜单
		
		roll_P=Store_Data[4];//将flash中数据加载进ram
		roll_I=Store_Data[5];//将flash中数据加载进ram
		roll_D=Store_Data[6];//将flash中数据加载进ram
		
		switch(pidflag)
		{
			//再次显示,解决乱码和闪烁问题(确保反相前是亮的状态)
			case 1:
			{
				OLED_ShowString(0,0,"<-               ",OLED_8X16);
				OLED_Printf(0,16,OLED_8X16,"roll-P:%d      ",roll_P);
				OLED_Printf(0,32,OLED_8X16,"roll-I:%d      ",roll_I);
				OLED_Printf(0,48,OLED_8X16,"roll-D:%d      ",roll_D);
				if(D==2){OLED_Animation(0,0,0,0,0,0,16,16);}//下
				if(D==1){OLED_Animation(0,16,32,16,0,0,16,16);}//上
				break;
			}
			//再次显示,解决乱码和闪烁问题(确保反相前是亮的状态)
			case 2:
			{
				OLED_ShowString(0,0,"<-               ",OLED_8X16);
				OLED_Printf(0,16,OLED_8X16,"roll-P:%d      ",roll_P);
				OLED_Printf(0,32,OLED_8X16,"roll-I:%d      ",roll_I);
				OLED_Printf(0,48,OLED_8X16,"roll-D:%d      ",roll_D);
				if(D==2){OLED_Animation(0,0,16,16,0,16,48,16);}//下
				if(D==1){OLED_Animation(0,32,64,16,0,16,48,16);}//上
				break;
			}
			//再次显示,解决乱码和闪烁问题(确保反相前是亮的状态)
			case 3:
			{
				OLED_ShowString(0,0,"<-               ",OLED_8X16);
				OLED_Printf(0,16,OLED_8X16,"roll-P:%d      ",roll_P);
				OLED_Printf(0,32,OLED_8X16,"roll-I:%d      ",roll_I);
				OLED_Printf(0,48,OLED_8X16,"roll-D:%d      ",roll_D);
				if(D==2){OLED_Animation(0,16,32,16,0,32,48,16);}//下
				if(D==1){OLED_Animation(0,48,32,16,0,32,48,16);}//上
				break;
			}
			//再次显示,解决乱码和闪烁问题(确保反相前是亮的状态)
			case 4:
			{
				OLED_ShowString(0,0,"<-               ",OLED_8X16);
				OLED_Printf(0,16,OLED_8X16,"roll-P:%d      ",roll_P);
				OLED_Printf(0,32,OLED_8X16,"roll-I:%d      ",roll_I);
				OLED_Printf(0,48,OLED_8X16,"roll-D:%d      ",roll_D);
				if(D==2){OLED_Animation(0,32,64,16,0,48,48,16);}//下
				if(D==1){OLED_Animation(0,64,32,16,0,48,48,16);}//上
				break;
			}
		}
	}
}

/**
  * @brief 四轴PID四级调参偏航P函数菜单
  * @param  无
  * @retval 
  */
int menu4_yaw_p(void)
{
	while(1)
	{
		KeyNum = Key_GetNum();
		if(KeyNum == 1)
		{
			yaw_P+=100;//按一次yaw_P+100
			Store_Data[7]=yaw_P;//将yaw_P存入数组
			if(yaw_P >= 1000){yaw_P = 1000;}
			OLED_AnimUpdate();//按下按钮,刷新一次动画效果
		}
		if(KeyNum == 2)
		{
			yaw_P-=100;//按一次yaw_P-100
			Store_Data[7]=yaw_P;//将yaw_P存入数组
			if(yaw_P <= 0){yaw_P = 0;}
			OLED_AnimUpdate();//按下按钮,刷新一次动画效果
		}
		if(KeyNum == 3)
		{
			Store_Save();////将yaw_P存入flash
			OLED_AnimUpdate();//按下按钮,刷新一次动画效果
			return 0;//返回
		}
		OLED_ShowString(0,0,"<-            ",OLED_8X16);
		OLED_Printf(0,16,OLED_8X16,"yaw-P:%d      ",yaw_P);
		OLED_Printf(0,32,OLED_8X16,"yaw-I:%d      ",yaw_I);
		OLED_Printf(0,48,OLED_8X16,"yaw-D:%d      ",yaw_D);
		if(yaw_P < 100){OLED_ReverseArea(48,16,8,16);}
		if((yaw_P >= 100)&&(yaw_P < 1000)){OLED_ReverseArea(48,16,24,16);}
		if(yaw_P >= 1000) OLED_ReverseArea(48,16,32,16);
		OLED_Update();
	}
}

/**
  * @brief 四轴PID四级调参偏航I函数菜单
  * @param  无
  * @retval 
  */
int menu4_yaw_i(void)
{
	while(1)
	{
		KeyNum = Key_GetNum();
		if(KeyNum == 1)
		{
			yaw_I+=1;//按一次yaw_I+1
			Store_Data[8]=yaw_I;//将yaw_I存入数组
			if(yaw_I >= 10){yaw_I = 10;}
			OLED_AnimUpdate();//按下按钮,刷新一次动画效果
		}
		if(KeyNum == 2)
		{
			yaw_I-=1;//按一次yaw_I-1
			Store_Data[8]=yaw_I;//将yaw_I存入数组
			if(yaw_I <= 0){yaw_I = 0;}
			OLED_AnimUpdate();//按下按钮,刷新一次动画效果
		}
		if(KeyNum == 3)
		{
			Store_Save();//将yaw_I存入flash
			OLED_AnimUpdate();//按下按钮,刷新一次动画效果
			return 0;//返回
		}
		OLED_ShowString(0,0,"<-            ",OLED_8X16);
		OLED_Printf(0,16,OLED_8X16,"yaw-P:%d      ",yaw_P);
		OLED_Printf(0,32,OLED_8X16,"yaw-I:%d      ",yaw_I);
		OLED_Printf(0,48,OLED_8X16,"yaw-D:%d      ",yaw_D);
		if(yaw_I < 10){OLED_ReverseArea(48,32,8,16);}
		if(yaw_I >= 10) OLED_ReverseArea(48,32,16,16);
		OLED_Update();
	}
}

/**
  * @brief 四轴PID四级调参偏航D函数菜单
  * @param  无
  * @retval 
  */
int menu4_yaw_d(void)
{
	while(1)
	{
		KeyNum = Key_GetNum();
		if(KeyNum == 1)
		{
			yaw_D+=10;//按一次yaw_D+10
			Store_Data[9]=yaw_D;//将yaw_D存入数组
			if(yaw_D >= 100){yaw_D = 100;}
			OLED_AnimUpdate();//按下按钮,刷新一次动画效果
		}
		if(KeyNum == 2)
		{
			yaw_D-=10;//按一次yaw_D-10
			Store_Data[9]=yaw_D;//将yaw_D存入数组
			if(yaw_D <= 0){yaw_D = 0;}
			OLED_AnimUpdate();//按下按钮,刷新一次动画效果
		}
		if(KeyNum == 3)
		{
			Store_Save();//将yaw_D存入flash
			OLED_AnimUpdate();//按下按钮,刷新一次动画效果
			return 0;//返回
		}
		OLED_ShowString(0,0,"<-            ",OLED_8X16);
		OLED_Printf(0,16,OLED_8X16,"yaw-P:%d      ",yaw_P);
		OLED_Printf(0,32,OLED_8X16,"yaw-I:%d      ",yaw_I);
		OLED_Printf(0,48,OLED_8X16,"yaw-D:%d      ",yaw_D);
		if(yaw_D < 100){OLED_ReverseArea(48,48,16,16);}
		if(yaw_D >= 100) OLED_ReverseArea(48,48,24,16);
		OLED_Update();
	}
}

/**
  * @brief 四轴PID三级调参偏航函数菜单
  * @param  无
  * @retval 
  */
int menu3_yaw(void)
{
	int menu4_PPID=0;
	uint8_t pidflag = 1;//默认在第一行
	OLED_Update();
	while(1)
	{
		KeyNum = Key_GetNum();
		if(KeyNum == 1)//上一项
		{
			pidflag--;
			if(pidflag == 0){pidflag = 4;}
			D = 1;//上一项标志
			OLED_AnimUpdate();//按下按钮,刷新一次动画效果
		}
		if(KeyNum == 2)//下一项
		{
			pidflag++;
			if(pidflag == 5){pidflag = 1;}
			D = 2;//下一项标志
			OLED_AnimUpdate();//按下按钮,刷新一次动画效果
		}
		if(KeyNum == 3)//确认
		{		
			OLED_Clear();//清屏
			OLED_Update();//刷屏
			menu4_PPID = pidflag;
			OLED_AnimUpdate();//按下按钮,刷新一次动画效果
		}
		
		if(menu4_PPID == 1){return 0;}//返回0,退到第一级菜单
		if(menu4_PPID == 2){menu4_PPID = menu4_yaw_p();}//进入四级偏航pid调参菜单
		if(menu4_PPID == 3){menu4_PPID = menu4_yaw_i();}//进入四级偏航pid调参菜单
		if(menu4_PPID == 4){menu4_PPID = menu4_yaw_d();}//进入四级偏航pid调参菜单
		
		yaw_P=Store_Data[7];//将flash中数据加载进ram
		yaw_I=Store_Data[8];//将flash中数据加载进ram
		yaw_D=Store_Data[9];//将flash中数据加载进ram
		
		switch(pidflag)
		{
			//再次显示,解决乱码和闪烁问题(确保反相前是亮的状态)
			case 1:
			{
				OLED_ShowString(0,0,"<-               ",OLED_8X16);
				OLED_Printf(0,16,OLED_8X16,"yaw-P:%d      ",yaw_P);
				OLED_Printf(0,32,OLED_8X16,"yaw-I:%d      ",yaw_I);
				OLED_Printf(0,48,OLED_8X16,"yaw-D:%d      ",yaw_D);
				if(D==2){OLED_Animation(0,0,0,0,0,0,16,16);}//下
				if(D==1){OLED_Animation(0,16,32,16,0,0,16,16);}//上
				break;
			}
			//再次显示,解决乱码和闪烁问题(确保反相前是亮的状态)
			case 2:
			{
				OLED_ShowString(0,0,"<-               ",OLED_8X16);
				OLED_Printf(0,16,OLED_8X16,"yaw-P:%d      ",yaw_P);
				OLED_Printf(0,32,OLED_8X16,"yaw-I:%d      ",yaw_I);
				OLED_Printf(0,48,OLED_8X16,"yaw-D:%d      ",yaw_D);
				if(D==2){OLED_Animation(0,0,16,16,0,16,40,16);}//下
				if(D==1){OLED_Animation(0,32,64,16,0,16,40,16);}//上
				break;
			}
			//再次显示,解决乱码和闪烁问题(确保反相前是亮的状态)
			case 3:
			{
				OLED_ShowString(0,0,"<-               ",OLED_8X16);
				OLED_Printf(0,16,OLED_8X16,"yaw-P:%d      ",yaw_P);
				OLED_Printf(0,32,OLED_8X16,"yaw-I:%d      ",yaw_I);
				OLED_Printf(0,48,OLED_8X16,"yaw-D:%d      ",yaw_D);
				if(D==2){OLED_Animation(0,16,32,16,0,32,40,16);}//下
				if(D==1){OLED_Animation(0,48,32,16,0,32,40,16);}//上
				break;
			}
			//再次显示,解决乱码和闪烁问题(确保反相前是亮的状态)
			case 4:
			{
				OLED_ShowString(0,0,"<-               ",OLED_8X16);
				OLED_Printf(0,16,OLED_8X16,"yaw-P:%d      ",yaw_P);
				OLED_Printf(0,32,OLED_8X16,"yaw-I:%d      ",yaw_I);
				OLED_Printf(0,48,OLED_8X16,"yaw-D:%d      ",yaw_D);
				if(D==2){OLED_Animation(0,32,64,16,0,48,40,16);}//下
				if(D==1){OLED_Animation(0,64,32,16,0,48,40,16);}//上
				break;
			}
		}
	}
}

/**
  * @brief 四轴PID二级菜单函数
  * @param  无
  * @retval 
  */
int menu2_PID(void)
{
	int menu3_PPID;
	uint8_t pidflag = 1;//默认在第一行
	OLED_Update();
	while(1)
	{
		KeyNum = Key_GetNum();
		if(KeyNum == 1)//上一项
		{
			pidflag--;
			if(pidflag == 0){pidflag = 4;}//共四行
			D = 1;//上一项标志
			OLED_AnimUpdate();//按下按钮,刷新一次动画效果
		}
		if(KeyNum == 2)//下一项
		{
			pidflag++;
			if(pidflag == 5){pidflag = 1;}
			D = 2;//下一项标志
			OLED_AnimUpdate();//按下按钮,刷新一次动画效果
		}
		if(KeyNum == 3)//确认
		{
			OLED_Clear();//清屏
			OLED_Update();//刷屏
			menu3_PPID = pidflag;
			OLED_AnimUpdate();//按下按钮,刷新一次动画效果
		}
		if(menu3_PPID == 1){return 0;}//返回0,退到第一级菜单
		if(menu3_PPID == 2){menu3_PPID = menu3_pitch();}//进入三级俯仰pid菜单
		if(menu3_PPID == 3){menu3_PPID = menu3_roll();}//进入三级横滚pid菜单
		if(menu3_PPID == 4){menu3_PPID = menu3_yaw();}//进入三级偏航pid菜单
		
		switch(pidflag)
		{
			//再次显示,解决乱码和闪烁问题(确保反相前是亮的状态)
			case 1:
			{
				OLED_ShowString(0,0,"<-                ",OLED_8X16);
				OLED_ShowString(0,16,"X俯仰环          ",OLED_8X16);
				OLED_ShowString(0,32,"Y横滚环          ",OLED_8X16);
				OLED_ShowString(0,48,"Z偏航环          ",OLED_8X16);
				if(D==2){OLED_Animation(0,0,0,0,0,0,16,16);}//下
				if(D==1){OLED_Animation(0,16,48,16,0,0,16,16);}//上
				break;
			}
			//再次显示,解决乱码和闪烁问题(确保反相前是亮的状态)
			case 2:
			{
				OLED_ShowString(0,0,"<-                ",OLED_8X16);
				OLED_ShowString(0,16,"X俯仰环          ",OLED_8X16);
				OLED_ShowString(0,32,"Y横滚环          ",OLED_8X16);
				OLED_ShowString(0,48,"Z偏航环          ",OLED_8X16);
				if(D==2){OLED_Animation(0,0,16,16,0,16,56,16);}//下
				if(D==1){OLED_Animation(0,32,48,16,0,16,56,16);}//上
				break;
			}
			//再次显示,解决乱码和闪烁问题(确保反相前是亮的状态)
			case 3:
			{
				OLED_ShowString(0,0,"<-                ",OLED_8X16);
				OLED_ShowString(0,16,"X俯仰环          ",OLED_8X16);
				OLED_ShowString(0,32,"Y横滚环          ",OLED_8X16);
				OLED_ShowString(0,48,"Z偏航环          ",OLED_8X16);
				if(D==2){OLED_Animation(0,16,48,16,0,32,56,16);}//下
				if(D==1){OLED_Animation(0,48,48,16,0,32,56,16);}//上
				break;
			}
			//再次显示,解决乱码和闪烁问题(确保反相前是亮的状态)
			case 4:
			{
				OLED_ShowString(0,0,"<-                ",OLED_8X16);
				OLED_ShowString(0,16,"X俯仰环          ",OLED_8X16);
				OLED_ShowString(0,32,"Y横滚环          ",OLED_8X16);
				OLED_ShowString(0,48,"Z偏航环          ",OLED_8X16);
				if(D==2){OLED_Animation(0,32,48,16,0,48,56,16);}//下
				if(D==1){OLED_Animation(0,64,48,16,0,48,56,16);}//上
				break;
			}
		}
	}
}

6.实时时钟的二级菜单: 提供日期时间的显示以及温湿度检测的显示。因为我手上目前没有温湿度传感器就没显示出来,但代码已经写好,只需连接上温湿度传感器即可。

下面看代码,这里的初始时间可以在MyRTC.c中最上面的定义自行修改,修改完进来即可从你定义的时间开始读秒,只要不断电,秒数就会一直走。

cs 复制代码
/*--------------------------------------------------------------------------------------*/
/*---------------------------------二级时钟菜单------------------------------------------*/
/**
  * @brief 时钟二级菜单函数
  * @param  无
  * @retval 
  */
int menu2_Clock(void)
{
	float wd = 0.0;
  float sd = 0.0;
	while(1)
	{
		dht11_measure(0);//温湿度传感器测量温湿度
		wd = dht11_get_temperature();//获取温度
		sd = dht11_get_humidity();   //获取湿度
		MyRTC_ReadTime();//将RTC时间写入数组中(初始时间可在MyRTC.c中最上面的定义自行修改)
		KeyNum = Key_GetNum();
		if(KeyNum == 3)//返回
		{
      OLED_AnimUpdate();//按下按钮,刷新一次动画效果
			return 0;		
		}
			OLED_ShowString(0,0,"<-               ",OLED_8X16);
			OLED_Printf(0,16,OLED_8X16,"Day:%d-%d-%d",MyRTC_Time[0],MyRTC_Time[1],MyRTC_Time[2]);
			OLED_Printf(0,32,OLED_8X16,"Time:%d:%d:%d",MyRTC_Time[3],MyRTC_Time[4],MyRTC_Time[5]);
		  OLED_Printf(0,48,OLED_8X16,"温湿度:%d-%d",wd,sd);
			OLED_Animation(0,0,0,0,0,0,16,16);//下
			OLED_Animation(0,0,16,16,0,0,16,16);//上
      OLED_Update();		
	}
}

RTC(Real-Time Clock)是STM32中一个独立的低功耗定时器,用于提供精确的日历和时间功能。即使在主电源关闭时,通过后备电池供电,RTC也能持续运行。

基本功能

  • 日历功能:秒、分、时、日、月、年(自动处理闰年)

  • 亚秒级精度:可配置的亚秒寄存器

  • 闹钟功能:可编程的闹钟,可配置为任意时间/日期触发

  • 周期性唤醒:可配置的自动唤醒定时器

  • 时间戳功能:记录外部事件的发生时间

  • 篡改检测:安全相关的入侵检测功能

  • 数字校准:软件时钟精度补偿

**7.音乐Music二级菜单:**这里二级菜单就选了几首博主喜欢的歌名显示了出来。

下面看代码,当然如果你想自己diy播放音乐的话,我之前发过一篇文章讲解了jq8400的简单使用,使用一个存储了音乐的jq8400再连接一个喇叭,也是可以实现的,感兴趣的朋友自参考我之前的文章自行进行音乐三级菜单的diy。

cs 复制代码
/*--------------------------------------------------------------------------------------*/
/*---------------------------------二级音乐菜单-----------------------------------------*/
/**
  * @brief 音乐二级菜单函数
  * @param  无
  * @retval 
  */
int menu2_Music(void)
{
	int menu3_MMusic;
	uint8_t musicflag = 1;//默认在第一行
	OLED_Update();
	while(1)
	{
		KeyNum = Key_GetNum();
		if(KeyNum == 1)//上一项
		{
			musicflag--;
			if(musicflag == 0){musicflag = 4;}//共四行
			D = 1;//上一项标志
			OLED_AnimUpdate();//按下按钮,刷新一次动画效果
		}
		if(KeyNum == 2)//下一项
		{
			musicflag++;
			if(musicflag == 5){musicflag = 1;}
			D = 2;//下一项标志
			OLED_AnimUpdate();//按下按钮,刷新一次动画效果
		}
		if(KeyNum == 3)//确认
		{
			OLED_Clear();//清屏
			OLED_Update();//刷屏
			menu3_MMusic = musicflag;
			OLED_AnimUpdate();//按下按钮,刷新一次动画效果
		}
		if(menu3_MMusic == 1){return 0;}//返回0,退到第一级菜单
		if(menu3_MMusic == 2){return 0;}//返回0,退到第一级菜单(三级菜单函数可自行diy)
		if(menu3_MMusic == 3){return 0;}//返回0,退到第一级菜单(三级菜单函数可自行diy)
		if(menu3_MMusic == 4){return 0;}//返回0,退到第一级菜单(三级菜单函数可自行diy)
		
		switch(musicflag)
		{
			//再次显示,解决乱码和闪烁问题(确保反相前是亮的状态)
			case 1:
			{
				OLED_ShowString(0,0,"<-               ",OLED_8X16);
				OLED_ShowString(0,16,"我记得          ",OLED_8X16);
				OLED_ShowString(0,32,"春风十里        ",OLED_8X16);
				OLED_ShowString(0,48,"此生皆欢喜      ",OLED_8X16);
				if(D==2){OLED_Animation(0,0,0,0,0,0,16,16);}//下
				if(D==1){OLED_Animation(0,16,48,16,0,0,16,16);}//上
				break;
			}
			//再次显示,解决乱码和闪烁问题(确保反相前是亮的状态)
			case 2:
			{
				OLED_ShowString(0,0,"<-               ",OLED_8X16);
				OLED_ShowString(0,16,"我记得          ",OLED_8X16);
				OLED_ShowString(0,32,"春风十里        ",OLED_8X16);
				OLED_ShowString(0,48,"此生皆欢喜      ",OLED_8X16);
				if(D==2){OLED_Animation(0,0,16,16,0,16,48,16);}//下
				if(D==1){OLED_Animation(0,32,48,16,0,16,48,16);}//上
				break;
			}
			//再次显示,解决乱码和闪烁问题(确保反相前是亮的状态)
			case 3:
			{
				OLED_ShowString(0,0,"<-               ",OLED_8X16);
				OLED_ShowString(0,16,"我记得          ",OLED_8X16);
				OLED_ShowString(0,32,"春风十里        ",OLED_8X16);
				OLED_ShowString(0,48,"此生皆欢喜      ",OLED_8X16);
				if(D==2){OLED_Animation(0,16,48,16,0,32,64,16);}//下
				if(D==1){OLED_Animation(0,48,48,16,0,32,64,16);}//上
				break;
			}
			//再次显示,解决乱码和闪烁问题(确保反相前是亮的状态)
			case 4:
			{
				OLED_ShowString(0,0,"<-               ",OLED_8X16);
				OLED_ShowString(0,16,"我记得          ",OLED_8X16);
				OLED_ShowString(0,32,"春风十里        ",OLED_8X16);
				OLED_ShowString(0,48,"此生皆欢喜      ",OLED_8X16);
				if(D==2){OLED_Animation(0,32,48,16,0,48,80,16);}//下
				if(D==1){OLED_Animation(0,64,48,16,0,48,80,16);}//上
				break;
			}
		}
	}
}

**8.设置Set二三级菜单:**下图是设置二级菜单以及分别对应的三级菜单,分别是pwr的电源模式选择,看门狗的选择以及关于。

下面看代码,本来是打算把stm32的三种低功耗模式和两种看门狗都写出来的,这里因为一些原因我就介绍一下原理吧,后续就交给你们了。

cs 复制代码
/*--------------------------------------------------------------------------------------*/
/*---------------------------------二三级设置菜单-----------------------------------------*/

/**
  * @brief 设置三级菜单模式选择函数菜单
  * @param  无
  * @retval 
  */
int menu3_setmode(void)
{
	uint8_t ssetflag = 1;//默认在第一行
	OLED_Update();
	while(1)
	{
		KeyNum = Key_GetNum();
		if(KeyNum == 1)//上一项
		{
			ssetflag--;
			if(ssetflag == 0){ssetflag = 4;}
			D = 1;//上一项标志
			OLED_AnimUpdate();//按下按钮,刷新一次动画效果
		}
		if(KeyNum == 2)//下一项
		{
			ssetflag++;
			if(ssetflag == 5){ssetflag = 1;}
			D = 2;//下一项标志
			OLED_AnimUpdate();//按下按钮,刷新一次动画效果
		}
		if(KeyNum == 3)//确认
		{		
			OLED_AnimUpdate();//按下按钮,刷新一次动画效果
			return 0;
		}
		
		switch(ssetflag)
		{
			//再次显示,解决乱码和闪烁问题(确保反相前是亮的状态)
			case 1:
			{
				OLED_ShowString(0,0,"<-               ",OLED_8X16);
				OLED_ShowString(0,16,"睡眠            ",OLED_8X16);
				OLED_ShowString(0,32,"停止            ",OLED_8X16);
				OLED_ShowString(0,48,"待机            ",OLED_8X16);
				if(D==2){OLED_Animation(0,0,0,0,0,0,16,16);}//下
				if(D==1){OLED_Animation(0,16,32,16,0,0,16,16);}//上
				break;
			}
			//再次显示,解决乱码和闪烁问题(确保反相前是亮的状态)
			case 2:
			{
				OLED_ShowString(0,0,"<-               ",OLED_8X16);
				OLED_ShowString(0,16,"睡眠            ",OLED_8X16);
				OLED_ShowString(0,32,"停止            ",OLED_8X16);
				OLED_ShowString(0,48,"待机            ",OLED_8X16);
				if(D==2){OLED_Animation(0,0,16,16,0,16,32,16);}//下
				if(D==1){OLED_Animation(0,32,64,16,0,16,32,16);}//上
				break;
			}
			//再次显示,解决乱码和闪烁问题(确保反相前是亮的状态)
			case 3:
			{
				OLED_ShowString(0,0,"<-               ",OLED_8X16);
				OLED_ShowString(0,16,"睡眠            ",OLED_8X16);
				OLED_ShowString(0,32,"停止            ",OLED_8X16);
				OLED_ShowString(0,48,"待机            ",OLED_8X16);
				if(D==2){OLED_Animation(0,16,32,16,0,32,32,16);}//下
				if(D==1){OLED_Animation(0,48,32,16,0,32,32,16);}//上
				break;
			}
			//再次显示,解决乱码和闪烁问题(确保反相前是亮的状态)
			case 4:
			{
				OLED_ShowString(0,0,"<-               ",OLED_8X16);
				OLED_ShowString(0,16,"睡眠            ",OLED_8X16);
				OLED_ShowString(0,32,"停止            ",OLED_8X16);
				OLED_ShowString(0,48,"待机            ",OLED_8X16);
				if(D==2){OLED_Animation(0,32,64,16,0,48,32,16);}//下
				if(D==1){OLED_Animation(0,64,32,16,0,48,32,16);}//上
				break;
			}
		}
	}
}

/**
  * @brief 设置三级dog函数菜单
  * @param  无
  * @retval 
  */
int menu3_setdog(void)
{
	uint8_t ssetflag = 1;//默认在第一行
	OLED_Update();
	while(1)
	{
		KeyNum = Key_GetNum();
		if(KeyNum == 1)//上一项
		{
			ssetflag--;
			if(ssetflag == 0){ssetflag = 4;}
			D = 1;//上一项标志
			OLED_AnimUpdate();//按下按钮,刷新一次动画效果
		}
		if(KeyNum == 2)//下一项
		{
			ssetflag++;
			if(ssetflag == 5){ssetflag = 1;}
			D = 2;//下一项标志
			OLED_AnimUpdate();//按下按钮,刷新一次动画效果
		}
		if(KeyNum == 3)//确认
		{		
			OLED_AnimUpdate();//按下按钮,刷新一次动画效果
			return 0;
		}
		
		switch(ssetflag)
		{
			//再次显示,解决乱码和闪烁问题(确保反相前是亮的状态)
			case 1:
			{
				OLED_ShowString(0,0,"<-               ",OLED_8X16);
				OLED_ShowString(0,16,"独立看门狗            ",OLED_8X16);
				OLED_ShowString(0,32,"窗口看门狗            ",OLED_8X16);
				OLED_ShowString(0,48,"汪汪汪汪汪            ",OLED_8X16);
				if(D==2){OLED_Animation(0,0,0,0,0,0,16,16);}//下
				if(D==1){OLED_Animation(0,16,32,16,0,0,16,16);}//上
				break;
			}
			//再次显示,解决乱码和闪烁问题(确保反相前是亮的状态)
			case 2:
			{
				OLED_ShowString(0,0,"<-               ",OLED_8X16);
				OLED_ShowString(0,16,"独立看门狗            ",OLED_8X16);
				OLED_ShowString(0,32,"窗口看门狗            ",OLED_8X16);
				OLED_ShowString(0,48,"汪汪汪汪汪            ",OLED_8X16);
				if(D==2){OLED_Animation(0,0,16,16,0,16,80,16);}//下
				if(D==1){OLED_Animation(0,32,64,16,0,16,80,16);}//上
				break;
			}
			//再次显示,解决乱码和闪烁问题(确保反相前是亮的状态)
			case 3:
			{
				OLED_ShowString(0,0,"<-               ",OLED_8X16);
				OLED_ShowString(0,16,"独立看门狗            ",OLED_8X16);
				OLED_ShowString(0,32,"窗口看门狗            ",OLED_8X16);
				OLED_ShowString(0,48,"汪汪汪汪汪            ",OLED_8X16);
				if(D==2){OLED_Animation(0,16,32,16,0,32,80,16);}//下
				if(D==1){OLED_Animation(0,48,32,16,0,32,80,16);}//上
				break;
			}
			//再次显示,解决乱码和闪烁问题(确保反相前是亮的状态)
			case 4:
			{
				OLED_ShowString(0,0,"<-               ",OLED_8X16);
				OLED_ShowString(0,16,"独立看门狗            ",OLED_8X16);
				OLED_ShowString(0,32,"窗口看门狗            ",OLED_8X16);
				OLED_ShowString(0,48,"汪汪汪汪汪            ",OLED_8X16);
				if(D==2){OLED_Animation(0,32,64,16,0,48,80,16);}//下
				if(D==1){OLED_Animation(0,64,32,16,0,48,80,16);}//上
				break;
			}
		}
	}
}

/**
  * @brief 设置三级关于函数菜单
  * @param  无
  * @retval 
  */
int menu3_setabout(void)
{
	uint8_t ssetflag = 1;//默认在第一行
	OLED_Update();
	while(1)
	{
		KeyNum = Key_GetNum();
		if(KeyNum == 1)//上一项
		{
			ssetflag--;
			if(ssetflag == 0){ssetflag = 4;}
			D = 1;//上一项标志
			OLED_AnimUpdate();//按下按钮,刷新一次动画效果
		}
		if(KeyNum == 2)//下一项
		{
			ssetflag++;
			if(ssetflag == 5){ssetflag = 1;}
			D = 2;//下一项标志
			OLED_AnimUpdate();//按下按钮,刷新一次动画效果
		}
		if(KeyNum == 3)//确认
		{		
			OLED_AnimUpdate();//按下按钮,刷新一次动画效果
			return 0;
		}
		
		switch(ssetflag)
		{
			//再次显示,解决乱码和闪烁问题(确保反相前是亮的状态)
			case 1:
			{
				OLED_ShowString(0,0,"<-               ",OLED_8X16);
				OLED_ShowString(0,16,"我要永远地走,  ",OLED_8X16);
				OLED_ShowString(0,32,"因为远方无数次  ",OLED_8X16);
				OLED_ShowString(0,48,"将我拯救。      ",OLED_8X16);
				if(D==2){OLED_Animation(0,0,0,0,0,0,16,16);}//下
				if(D==1){OLED_Animation(0,16,32,16,0,0,16,16);}//上
				break;
			}
			//再次显示,解决乱码和闪烁问题(确保反相前是亮的状态)
			case 2:
			{
				OLED_ShowString(0,0,"<-               ",OLED_8X16);
				OLED_ShowString(0,16,"我要永远地走,  ",OLED_8X16);
				OLED_ShowString(0,32,"因为远方无数次  ",OLED_8X16);
				OLED_ShowString(0,48,"将我拯救。      ",OLED_8X16);
				if(D==2){OLED_Animation(0,0,16,16,0,16,102,16);}//下
				if(D==1){OLED_Animation(0,32,64,16,0,16,102,16);}//上
				break;
			}
			//再次显示,解决乱码和闪烁问题(确保反相前是亮的状态)
			case 3:
			{
				OLED_ShowString(0,0,"<-               ",OLED_8X16);
				OLED_ShowString(0,16,"我要永远地走,  ",OLED_8X16);
				OLED_ShowString(0,32,"因为远方无数次  ",OLED_8X16);
				OLED_ShowString(0,48,"将我拯救。      ",OLED_8X16);
				if(D==2){OLED_Animation(0,16,32,16,0,32,112,16);}//下
				if(D==1){OLED_Animation(0,48,32,16,0,32,112,16);}//上
				break;
			}
			//再次显示,解决乱码和闪烁问题(确保反相前是亮的状态)
			case 4:
			{
				OLED_ShowString(0,0,"<-               ",OLED_8X16);
				OLED_ShowString(0,16,"我要永远地走,  ",OLED_8X16);
				OLED_ShowString(0,32,"因为远方无数次  ",OLED_8X16);
				OLED_ShowString(0,48,"将我拯救。      ",OLED_8X16);
				if(D==2){OLED_Animation(0,32,64,16,0,48,70,16);}//下
				if(D==1){OLED_Animation(0,64,32,16,0,48,70,16);}//上
				break;
			}
		}
	}
}

/**
  * @brief 设置二级菜单函数
  * @param  无
  * @retval 
  */
int menu2_Set(void)
{
	int menu3_SSet;
	uint8_t setflag = 1;//默认在第一行
	OLED_Update();
	while(1)
	{
		KeyNum = Key_GetNum();
		if(KeyNum == 1)//上一项
		{
			setflag--;
			if(setflag == 0){setflag = 4;}
			D = 1;//上一项标志
			OLED_AnimUpdate();//按下按钮,刷新一次动画效果
		}
		if(KeyNum == 2)//下一项
		{
			setflag++;
			if(setflag == 5){setflag = 1;}
			D = 2;//下一项标志
			OLED_AnimUpdate();//按下按钮,刷新一次动画效果
		}
		if(KeyNum == 3)//确认
		{
			OLED_Clear();//清屏
			OLED_Update();//刷屏
			menu3_SSet = setflag;
			OLED_AnimUpdate();//按下按钮,刷新一次动画效果
		}
		if(menu3_SSet == 1){return 0;}//返回0,退到第一级菜单
		if(menu3_SSet == 2){menu3_SSet = menu3_setmode();}//进入三级模式选择菜单
		if(menu3_SSet == 3){menu3_SSet = menu3_setdog();}//进入三级看门狗菜单
		if(menu3_SSet == 4){menu3_SSet = menu3_setabout();}//进入三级关于菜单
		
		switch(setflag)
		{
			//再次显示,解决乱码和闪烁问题(确保反相前是亮的状态)
			case 1:
			{
				OLED_ShowString(0,0,"<-               ",OLED_8X16);
				OLED_ShowString(0,16,"模式            ",OLED_8X16);
				OLED_ShowString(0,32,"看门狗          ",OLED_8X16);
				OLED_ShowString(0,48,"关于星辰PID     ",OLED_8X16);
				if(D==2){OLED_Animation(0,0,0,0,0,0,16,16);}//下
				if(D==1){OLED_Animation(0,16,32,16,0,0,16,16);}//上
				break;
			}
			//再次显示,解决乱码和闪烁问题(确保反相前是亮的状态)
			case 2:
			{
				OLED_ShowString(0,0,"<-               ",OLED_8X16);
				OLED_ShowString(0,16,"模式            ",OLED_8X16);
				OLED_ShowString(0,32,"看门狗          ",OLED_8X16);
				OLED_ShowString(0,48,"关于星辰PID     ",OLED_8X16);
				if(D==2){OLED_Animation(0,0,16,16,0,16,32,16);}//下
				if(D==1){OLED_Animation(0,32,64,16,0,16,32,16);}//上
				break;
			}
			//再次显示,解决乱码和闪烁问题(确保反相前是亮的状态)
			case 3:
			{
				OLED_ShowString(0,0,"<-               ",OLED_8X16);
				OLED_ShowString(0,16,"模式            ",OLED_8X16);
				OLED_ShowString(0,32,"看门狗          ",OLED_8X16);
				OLED_ShowString(0,48,"关于星辰PID     ",OLED_8X16);
				if(D==2){OLED_Animation(0,16,32,16,0,32,48,16);}//下
				if(D==1){OLED_Animation(0,48,32,16,0,32,48,16);}//上
				break;
			}
			//再次显示,解决乱码和闪烁问题(确保反相前是亮的状态)
			case 4:
			{
				OLED_ShowString(0,0,"<-               ",OLED_8X16);
				OLED_ShowString(0,16,"模式            ",OLED_8X16);
				OLED_ShowString(0,32,"看门狗          ",OLED_8X16);
				OLED_ShowString(0,48,"关于星辰PID     ",OLED_8X16);
				if(D==2){OLED_Animation(0,32,64,16,0,48,88,16);}//下
				if(D==1){OLED_Animation(0,64,32,16,0,48,88,16);}//上
				break;
			}
		}
	}
}

STM32提供了多种低功耗模式,可以在不同应用场景下显著降低功耗。三种主要低功耗模式为:睡眠模式停止模式待机模式

1. 睡眠模式(Sleep Mode)

特性

  • 功耗:中等功耗降低

  • 唤醒时间:最快(几个时钟周期)

  • 数据保持:所有寄存器和SRAM数据保持

  • 时钟状态:仅内核时钟停止,外设时钟继续运行

2. 停止模式(Stop Mode)

特性

  • 功耗:低功耗(微安级)

  • 唤醒时间:中等(需要时钟重新稳定)

  • 数据保持:所有寄存器和SRAM数据保持

  • 时钟状态:所有时钟停止,1.8V域电源保持

3. 待机模式(Standby Mode)

特性

  • 功耗:最低功耗(纳安级)

  • 唤醒时间:最长(相当于复位)

  • 数据保持:仅备份域寄存器保持,SRAM数据丢失

  • 时钟状态:1.8V域电源关闭

STM32的三种低功耗模式提供了灵活的功耗管理方案:

  • 睡眠模式:适合短时间休眠,快速响应的场景

  • 停止模式:适合周期性任务,需要保持数据的长时休眠

  • 待机模式:适合超低功耗需求,对唤醒时间不敏感的应用

STM32提供了两种看门狗定时器,用于检测和解决软件故障:

  • 独立看门狗(IWDG):基于独立时钟源,主要用于处理硬件故障

  • 窗口看门狗(WWDG):基于系统时钟,主要用于监测软件序列异常

1. 独立看门狗(IWDG)

基本特性

  • 时钟源:独立的低速内部RC振荡器(LSI,~32kHz)

  • 独立性:不依赖主时钟,即使在主时钟故障时也能工作

  • 复位条件:计数器向下计数到0时产生系统复位

  • 应用场景:防止系统死机、程序跑飞

2. 窗口看门狗(WWDG)

基本特性

  • 时钟源:APB1时钟(PCLK1)

  • 窗口特性:必须在特定时间窗口内喂狗

  • 复位条件

    • 计数器值 > 窗口值时喂狗(过早)

    • 计数器值从0x40递减到0x3F时(过晚)

  • 应用场景:监测软件执行时序

STM32的两种看门狗提供了不同级别的系统保护:

  • IWDG:提供基本的"最后防线"保护,确保系统不会完全死机

  • WWDG:提供更精细的时序监控,确保软件按预期顺序执行

此次stm32基于oled的菜单分享就到这里了,创作不易,感兴趣的朋友可以一键三联后私聊获取完整菜单代码工程!能够看到这里也感谢您的观看!欢迎大家在我的菜单基础上进行更深层次的diy和完善!

相关推荐
飞睿科技4 小时前
乐鑫ESP32-C2小尺寸高性价比,物联网应用的理想无线连接方案
嵌入式硬件·物联网·智能路由器
RFID舜识物联网4 小时前
NFC与RFID防伪标签:构筑产品信任的科技防线
大数据·人工智能·科技·嵌入式硬件·物联网·安全
FanXing_zl4 小时前
在整数MCU上实现快速除法计算:原理、方法与优化
单片机·嵌入式硬件·mcu·算法·定点运算
Dunkle.T6 小时前
单片机中NRST引脚复用为GPIO
单片机·嵌入式硬件·复用·py32f002bl15s7·nrst
逆小舟6 小时前
【STM32】中断
stm32·单片机·嵌入式硬件
我先去打把游戏先6 小时前
ESP32C3开发指南(基于IDF):console控制台命令行交互功能
笔记·嵌入式硬件·mcu·物联网·学习·esp32·交互
rit84324997 小时前
基于STM32+OV7725+DHT11+ESP8266的物联网数据采集与显示
stm32·嵌入式硬件·物联网
Shylock_Mister8 小时前
Linux 常用命令大全:从入门到精通
c语言·嵌入式硬件·物联网
准测仪器16 小时前
6项提高电机制造质量的电气测试方案
单片机·嵌入式硬件·制造·电机·电气·放电测试·局部放电测试