蓝桥杯嵌入式第七届省赛真题

该博客介绍了蓝桥杯嵌入式第七届省赛的一道题目,涉及STM32的液位检测系统设计。系统通过电位器模拟液位传感器,实时采集并处理AD数据,根据用户设定的液位阈值执行报警。用户可使用按键设定阈值,设备保存在E2PROM中。此外,系统通过串口与PC机通信,响应查询并发送液位信息。STM32的USART2用于串口控制,同时LED灯显示运行状态、液位等级变化和通讯状态。
摘要由CSDN通过智能技术生成

蓝桥杯嵌入式第七届省赛真题

代码结尾自取 完全免费!!!!!!

代码结尾自取 完全免费!!!!!!

代码结尾自取 完全免费!!!!!!

1.题目

​ “模拟液位检测告警系统”通过采集模拟电压信号计算液位高度,并根据用户设定的液位阈值执行报警动作,在液位等级发生变化时,通过串行通讯接口将液位信息发送到 PC 机。

设计任务及要求

  1. 液位检测

    通过电位器 R37 模拟液位传感器输出电压信号,设备以 1 秒为间隔采集 R37 输出电压,并与用户设定的液位阈值进行比较。假定液位高度与 R37 输出电压之间具有正比例关系:H = VR37*K,当 VR37=3.3V 时,对应液位高度为 100cm。通过液晶显示当前的液位高度、传感器(R37)输出状态和液位等级,液位检测显示界面如图 1 所示

    oA6NPf.png

    AD 采集得到的结果应经过软件滤波算法处理,显示结果保留小数点后两位有效数字

  2. 液位阈值设定

    设备可设定三个液位阈值,对应四个液位等级,阈值由用户通过按键输入,设备保存阈值,并根据此阈值判断液位等级,假 定用户输入的三个液位阈值为 10cm、20cm 和 30cm,液位高度与液位等级的对应关系如下:
    2.1 液位高度≤10cm 时,液位等级为 0;
    2.2 10cm<液位高度≤20cm 时,液位等级为 1;
    2.3 20cm<液位高度≤30cm 时,液位等级为 2;
    2.4 液位高度>30cm 时,液位等级为 3。
    设备初始液位阈值分别为 30cm、50cm 和 70cm,用户修改阈值后,设备应将此参数保存在 E2PROM 中,当设备重新上电时,可从 E2PROM 中获取。

  3. 液位阈值设定

    B1 按键:“设置”按键,按下后进入阈值设定界面(如图 2 所示),再次按下 B1 按键时退出设置界面,保存用户设定的结果到 E2PROM,并返回图 1 所示的液位检测界面。
    B2 按键:切换选择 3 个待修改的阈值,被选中的阈值应突出显示。
    B3 按键:“加”按键,按下后,被选择的阈值增加 5cm,增加到 95cm 为止。
    B4 按键:“减”按键,按下后,被选择的阈值减少 5cm,减少到 5cm 为止。

    oA6YIP.png

  4. 串口查询与输出功能

    使用 STM32 USART2 完成以下串口功能,波特率设置为 9600。

4.1 查询
通过 PC 机向设备发送字符‘C’,设备返回当前液位高度和液位等级;
通过 PC 机向设备发送字符‘S’,设备返回当前设定的三个阈值。
液位高度和等级返回数据格式举例: “C:H55+L2\r\n 解析:应答高度、等级查询,液位高度为 55cm,液位等级为 2。
阈值返回数据格式举例:“S:TL30+TM50+TH70\r\n” 解析:应答阈值查询,设备内保存的三个阈值分别为 30cm、50cm 和 70cm。
4.2 输出
当液位等级发生变化时,设备自动向 PC 机发送当前液位等级、液位高度和液位变化趋势(上升或下降)。
输出数据格式举例:
“A:H55+L2+D\r\n”
解析:液位变化自动发送,液位高度 55cm,液位等级为 2,变化趋势下降。
“A:H55+L2+U\r\n”
解析:液位变化自动发送,液位高度 55cm,液位等级为 2,变化趋势上升。

5.状态指示

​ LED 指示灯功能定义如下:
​ LD1:运行状态指示灯,以 1 秒为间隔亮灭闪烁;
​ LD2:液位等级变化指示灯,当液位等级发生变化时,LD2 以 0.2 秒为间隔闪烁 5 次;
​ LD3:通讯状态指示灯,当设备接收到查询指令时,LD3 以 0.2 秒为间隔闪烁 5 次

2.题目分析

本次的题目难度没有很突出,但是有的地方挺有意思的,特别是那个监测液位等级改变时自动发送数据什么的。一开始我还出了个小bug没解决

其实大部分难度都不大,这里我就讲一下那个自动监测等级改变时发送数据的方法吧,后面大家直接看我的注释应该就看得懂了,

高光部分的数据控制有的人可能写的很麻烦,这里我用了我前面 第十届蓝桥杯嵌入式真题 的一个解决办法,大家可以去看一眼

  • 如果要做到监测等级的改变,那么我们就需要一个Old_Level变量,来保存上一次的旧数据,以及一个Liquid_Level来储存当前液位等级

  • 在While(1)主函数之前就要先放一个初始化Old_Level的函数,不然如果我们给Old_Level手动指定默认值的话,假设默认值是0,但是液位等级是1,那么我们一上电,LD2状态指示灯就会闪烁五次,那么这就是我们不愿意看到的。所以在进入主循环之前就要先初始化

  • 后面对比的时候,利用液位高度变量Liquid_Height对比上下限,进而改变Liquid_Level,当对比成功后,先对Liquid_Level赋值,注意,假设我们是上电后第一次改变了液位的等级,那么此时Old_Value里存储的就是我们上电的时候初始的液位等级,那Liquid_Level此时被赋值后和Old_Value的大小就是不一样的了,通过比较他们的大小我们就可以分析出变化趋势然后发送串口数据

    下面开始上大部分控制逻辑代码

3.项目结构

3.1 函数结构和全局变量

extern char USART_RXBUF[10];
extern uint8_t RXOVER;

int TimingDelay;
//显示部分
char Height_Show[20];
char ADC_Show[20];
char Liquid_Level_Show[20];
char Threshold1_Show[20];
char Threshold2_Show[20];
char Threshold3_Show[20];
//如英语翻译所示...
double Adc_Temp;
int Liquid_Height;
int Liquid_Level;
int Old_Level;
//三个阈值
int Threshold1=30;
int Threshold2=50;
int Threshold3=70;
//高亮部分的标志位
int High_Light_flag = 1;
//K1按键模式
int mode=1;
char key;

//灯相关控制	
int Led1_Flag=1;
int Led2_Flag=0;
int Led3_Flag=0;
int Led3_Happen=0;
int Led2_Happen=0;

//显示界面函数
void Liquid_Level_Display();
void Parameter_Setup_Display(int High_Light_flag);
//设置函数
void Setting();
//K3,K4按键控制函数
void Key3_Control(int flag);
void Key4_Control(int flag);
//串口模式分析
int analysis(char * data);
//液位等级控制
void Level_Control();
//串口总控制
void Usart_Control();
//Led控制
void Led_Control();
//初始化液位等级
void Level_Init();
//初始化阈值数据(EEROM)
void Init_Data();
//保存阈值数据(EEROM)
void Save_Data();

3.2 界面

Setting设置函数

void Setting()
{
    //计算液位高度
	Adc_Temp = Get_Adc();
	Liquid_Height=Adc_Temp*30.3;
	key = KEY_Scan();
	switch(key)
	{
		case '1':
            //主界面和设置界面切换标志
			LCD_Clear(White);
            //这个在后续的switch里用
			mode = !mode;
			break;
		case '2':
			High_Light_flag++;
            //高光部分标志位控制 这个做法在我博客的第十届省赛题里就用过
			if(High_Light_flag>3) { High_Light_flag = 1; }
			break;			
		case '3':
            //直接将前面的高光标志位传入 这样按键就能执行高光那行的正确逻辑
			Key3_Control(High_Light_flag);
			break;
		case '4':
			Key4_Control(High_Light_flag);
			break;
	}
    //两个界面的切换显示
	switch(mode)
	{
		case 1:
			Liquid_Level_Display();
			break;		
		case 0:
            //同理传入高光标志位,做到 Key3,Key4,和对应的显示相配合
			Parameter_Setup_Display(High_Light_flag);
			break;
	}
}

void Parameter_Setup_Display(int High_Light_flag) 高光界面函数

这里我只想到这么写,想不到怎么精简了

void Parameter_Setup_Display(int High_Light_flag)
{
	sprintf(Threshold1_Show,"Threshold1: %2d     ",Threshold1);
	sprintf(Threshold2_Show,"Threshold2: %2d     ",Threshold2);
	sprintf(Threshold3_Show,"Threshold3: %2d     ",Threshold3);
	if(High_Light_flag==1)
	{
		LCD_SetBackColor(White);
		LCD_DisplayStringLine(Line2,"   Parameter Setup");
		LCD_DisplayStringLine(Line5,Threshold2_Show);
		LCD_DisplayStringLine(Line6,Threshold3_Show);
		LCD_SetBackColor(Yellow);
		LCD_DisplayStringLine(Line4,Threshold1_Show);
	}
	else if(High_Light_flag==2)
	{
		LCD_SetBackColor(White);
		LCD_DisplayStringLine(Line2,"   Parameter Setup");
		LCD_DisplayStringLine(Line4,Threshold1_Show);
		LCD_DisplayStringLine(Line6,Threshold3_Show);
		LCD_SetBackColor(Yellow);
		LCD_DisplayStringLine(Line5,Threshold2_Show);
	}
	else if(High_Light_flag==3)
	{
		LCD_SetBackColor(White);
		LCD_DisplayStringLine(Line2,"   Parameter Setup");
		LCD_DisplayStringLine(Line5,Threshold2_Show);
		LCD_DisplayStringLine(Line4,Threshold1_Show);
		LCD_SetBackColor(Yellow);
		LCD_DisplayStringLine(Line6,Threshold3_Show);
	}

}

剩下的就是一些数据刷新什么的函数,这里我觉得没啥价值就不写啦,说白了就是Sprintf拼接然后LCD显示就可以了

3.3 K3,K4按键控制

特别简单,举一个K3的例子就行了,K4就是反过来

void Key3_Control(int flag)
{
    //可以看出这里我们是完全配合高光显示界面做的
	if(flag==1)
	{
		if(Threshold1+5>95)
		{
            Threshold1=95;
            Save_Data();
			return;
		}
		Threshold1+=5;
		Save_Data();
	}
	else if(flag==2)
	{
		if(Threshold2+5>95)
		{
			Threshold2=95;
            Save_Data();
			return;
		}
		Threshold2+=5;
		Save_Data();
	}
	else if(flag==3)
	{
		if(Threshold3+5>95)
		{
			Threshold3=95;
            Save_Data();
			return;
		}
		Threshold3+=5;
		Save_Data();
	}
}

3.4 串口控制

Usart_Control()串口总控

void Usart_Control()
{
	int mode;
	char data[20];

	if(RXOVER==1)
	{
		RXOVER = 0;
		//解析数据 判断我们需要返回哪种模式的数据
        //其实这题的逻辑简单,可以直接全写这,但我习惯分模块了
		mode = analysis(USART_RXBUF);
		memset(USART_RXBUF,'\0',sizeof(USART_RXBUF));
		USART_ITConfig(USART2	,USART_IT_RXNE,ENABLE);
		switch(mode)
		{
			case 1:
				sprintf(data,"C:H%d+L%d\r\n",Liquid_Height,Liquid_Level);
				USART_SendString(data);
				break;
			case 2:
				sprintf(data,"S:TL%d+TM%d+TH%d\r\n",Threshold1,Threshold2,Threshold3);
				USART_SendString(data);
   			break;
			default:
			  break;
		}
	}
}

int analysis(char * data) 数据解析函数

int analysis(char * data)
{
	if(data[0]=='C')
	{
        //接收到数据 Led3_Happen标志位置1,触发systick中断里的Led3部分
		Led3_Happen=1;
		return 1;
	}
	else if(data[0]=='S')
	{
        //接收到数据 Led3_Happen标志位置1,触发systick中断里的Led3部分
		Led3_Happen=1;
		return 2;
	}
}

3.5 液位控制

Level_Control() 液位控制

void Level_Control()
{

	char data[20];
	//0等级
	if(Liquid_Height>0 && Liquid_Height < Threshold1)
	{
        //先对当前液位等级变量赋值
		Liquid_Level=0;
        //这里只需要判断一次上升,下降我们交给1等级取判断
		if(Old_Level>Liquid_Level)
		{
            //状态改变,Led2_Happen标志位置1,触发systick中断里的Led2的部分
			Led2_Happen=1;
			sprintf(data,"H%d+L%d+D\r\n",Liquid_Height,Liquid_Level);
			USART_SendString(data);
		}
  		//判断完了后再对这个旧等级变量赋值
		Old_Level=0;
	}
    //1等级
	else if(Liquid_Height > Threshold1 && Liquid_Height < Threshold2)
	{
        //同理
		Liquid_Level=1;
        //旧值比新值大,那么就是下降
		if(Old_Level>Liquid_Level)
		{
			Led2_Happen=1;
			sprintf(data,"H%d+L%d+D\r\n",Liquid_Height,Liquid_Level);
			USART_SendString(data);
		}
        //旧值比新值小,那么就是上升
		if(Old_Level<Liquid_Level)
		{
			Led2_Happen=1;
			sprintf(data,"H%d+L%d+U\r\n",Liquid_Height,Liquid_Level);
			USART_SendString(data);
		}
        //同理
		Old_Level=1;
	}
    //后续同理
	else if(Liquid_Height > Threshold2 && Liquid_Height < Threshold3)
	{
		Liquid_Level=2;
		if(Old_Level>Liquid_Level)
		{
			Led2_Happen=1;
			sprintf(data,"H%d+L%d+D\r\n",Liquid_Height,Liquid_Level);
			USART_SendString(data);
		}
		if(Old_Level<Liquid_Level)
		{
			Led2_Happen=1;
			sprintf(data,"H%d+L%d+U\r\n",Liquid_Height,Liquid_Level);
			USART_SendString(data);
		}
		Old_Level=2;
	}
	else if( Liquid_Height >= Threshold3)
	{
		Liquid_Level=3;
		if(Old_Level<Liquid_Level)
		{
			Led2_Happen=1;
			sprintf(data,"H%d+L%d+U\r\n",Liquid_Height,Liquid_Level);
			USART_SendString(data);
		}
		Old_Level=3;
	}
}

这里是前面提到的 等级初始化函数void Level_Init()

void Level_Init()
{
	int i;
	char data[20];
	//刚上电的时候ADC读取的数据是错误的,所以这里先让他读十次
	for(i=0 ;i<10;i++){  	Adc_Temp = Get_Adc();  }
	Liquid_Height=Adc_Temp*30.3;
	//判断初始化的状态赋值
	if(Liquid_Height>0 && Liquid_Height < Threshold1)
	{
		Old_Level=0;
		Liquid_Level=0;
	}
	else if(Liquid_Height > Threshold1 && Liquid_Height < Threshold2)
	{
		Old_Level=1;
		Liquid_Level=1;

	}
	else if(Liquid_Height > Threshold2 && Liquid_Height < Threshold3)
	{
		Old_Level=2;
		Liquid_Level=2;
	}
	else if( Liquid_Height >= Threshold3)
	{
		Old_Level=3;
		Liquid_Level=3;
	}
}

3.6 Led控制

亮灭Led_Control(主要通过systick中断来改变标志位)

void Led_Control()
{
	LED_Control(LED1,Led1_Flag);
	LED_Control(LED3,Led3_Flag);
	LED_Control(LED2,Led2_Flag);
}

中断

extern int Led1_Flag;
extern int Led2_Flag;
extern int Led3_Flag;

extern int Led3_Happen;
extern int Led2_Happen;
int Led1_Delay=0;
int Led2_Delay=0;
int Led3_Delay=0;
int Led3_Count=0;
int Led2_Count=0;
void SysTick_Handler(void)
{
	TimingDelay--;
    //正常工作 1s闪烁一次
	if(++Led1_Delay==1000)
	{
		Led1_Delay=0;
		Led1_Flag=!Led1_Flag;
	}
    //状态发生了改变
	if(Led2_Happen)
	{
		if(++Led2_Delay==200)
		{
			Led2_Count++;
			Led2_Delay=0;
			Led2_Flag=!Led2_Flag;
		}
        //因为亮灭交替,所以是>10,  1,3,5,7,9亮灯
		if(Led2_Count>10)
		{
			Led2_Happen=0;
			Led2_Count=0;
		}
	}
    //接收到了数据
	if(Led3_Happen)
	{
		if(++Led3_Delay==200)
		{
			Led3_Count++;
			Led3_Delay=0;
			Led3_Flag=!Led3_Flag;
		}
		if(Led3_Count>10)
		{
			Led3_Happen=0;
			Led3_Count=0;
		}
	}
}

3.7主函数

//...省略大部分初始化
	//这是之前说的先初始化液位等级
	Level_Init();
	Init_Data();
	while(1)
	{
        //分模块编程 所以主函数非常简洁
		Setting();
		Level_Control();
		Usart_Control();
		Led_Control();
	}

基本代码都已经在这了,论什么是真正的保姆级教学哈哈哈哈 ,就剩下一些界面刷新啊,EEPROM的存和读数据的,非常非常基础的代码。我觉得没必要再放了。

祝大家的编程水平蒸蒸日上哈。本次的结构我个人还算是比较满意的

源码
提取码:7gqa

  • 6
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

这里煤球

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值