第十五届蓝桥杯嵌入式组省赛题目分析及代码

本文详细描述了一名参赛者在蓝桥杯嵌入式比赛中关于频率测量、LCD显示和按键控制的程序设计,包括频率捕获、定时中断、LCD界面切换和参数控制等内容。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

题目

效果

分析

频率测量

LCD显示

按键

频率超限

频率突变

程序结构

源码


刚刚结束24年蓝桥杯,要做第一个发码的人!(写的不一定对,大家多多指正)

题目

效果

第十五届蓝桥杯嵌入式组省赛

分析

本题使用到的外设较少,主要考察程序逻辑的编写。

在CubeMX中只需要打开两个频率捕获通道(TIM2_CH1、TIM3_CH1)、两个定时中断(随便选,我用的是TIM4和TIM17)、LED(PC8~PC15、PD2)、按键(PB0~PB2、PA0)。开启后在主循环前初始化:

HAL_TIM_Base_Start_IT(&htim4);  //开启10ms定时中断
HAL_TIM_Base_Start_IT(&htim17);  //开启0.1s定时中断
HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_1);  //开启捕获中断
HAL_TIM_IC_Start_IT(&htim3, TIM_CHANNEL_1);  //开启捕获中断
LED_Init(); //关闭所有led
LED_Ctrl(led|=0x01); //点亮led1
key_init();  //按键初始化
LCD_Init();
LCD_Clear(Black);
LCD_SetTextColor(White);
LCD_SetBackColor(Black);

程序的主体主要分为四块:频率测量、lcd显示、按键功能、中断处理,比较难的是频率突变(PD)、频率超限(PH)功能的实现。

首先我们先定义一些变量:

uint8_t display=0; //lcd显示界面标志位,0→数据界面,1→参数界面,2→统计界面
char buf[25];  //lcd显示用的每行字符串承装器
int  cnt2 = 0 ,frq2 = 0;  //单位为Hz,uS									
int  cnt3 = 0 ,frq3 = 0;  
char f_or_t='f';  //切换频率周期显示
char DHX='D';  //切换参数选择
float frqA,frqB;  //超过1kHz变为浮点数
float tA,tB;  //超过1kuS变为mS
int PD=1000,PH=5000,PX=0;  //参数
int NDA=0,NDB=0,NHA=0,NHB=0;  //统计次数

频率测量

这部分就是常规的频率测量,在板子的基本例程中就可以找到。

void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
	if(htim == &htim2)
	{
		cnt2 = __HAL_TIM_GET_COUNTER(&htim2);
		__HAL_TIM_SetCounter(&htim2,0);
		frq2 = 1000000/cnt2;
		HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_1);
	}
	if(htim == &htim3)
	{
		cnt3 = __HAL_TIM_GET_COUNTER(&htim3);
		__HAL_TIM_SetCounter(&htim3,0);
		frq3 = 1000000/cnt3;
		HAL_TIM_IC_Start_IT(&htim3, TIM_CHANNEL_1);
	}
}

LCD显示

根据题目要求,一共三个界面(数据、参数、记录),根据display的值选择展示哪个界面,根据题目要求展示相应内容。大家可以一边对着代码一边对着题目要求对照,注意各个变量的作用,我就不赘述了。

void DI(void)  //Display Interface
{
	switch(display)
	{
		case 0:  //数据界面
		{
			LCD_DisplayStringLine(Line1,(uint8_t *)"        DATA        ");
			if(f_or_t=='f')
			{
				frq2+=PX;
				frq3+=PX;
				if(frq2<0) LCD_DisplayStringLine(Line3,(uint8_t *)"     A=NULL      ");
				else if(frq2>1000)
				{
					frqA=(float)frq2/1000;
					sprintf(buf,"     A=%.2fKHz      ",frqA);
					LCD_DisplayStringLine(Line3,(uint8_t *)buf);					
				}
				else 
				{
					sprintf(buf,"     A=%dHz      ",frq2);
					LCD_DisplayStringLine(Line3,(uint8_t *)buf);
				}
				if(frq3<0) LCD_DisplayStringLine(Line4,(uint8_t *)"     B=NULL      ");
				else if(frq3>1000)
				{
					frqB=(float)frq3/1000;
					sprintf(buf,"     B=%.2fKHz      ",frqB);
					LCD_DisplayStringLine(Line4,(uint8_t *)buf);					
				}
				else 
				{
					sprintf(buf,"     B=%dHz      ",frq3);
					LCD_DisplayStringLine(Line4,(uint8_t *)buf);
				}
			}
			else if(f_or_t=='t')
			{
				if(frq2<0) LCD_DisplayStringLine(Line3,(uint8_t *)"     A=NULL      ");
				else if(cnt2>1000)
				{
					tA=(float)cnt2/1000;
					sprintf(buf,"     A=%.2fmS      ",tA);
					LCD_DisplayStringLine(Line3,(uint8_t *)buf);	
				}
				else 
				{
					sprintf(buf,"     A=%duS      ",cnt2);
					LCD_DisplayStringLine(Line3,(uint8_t *)buf);	
				}
				if(frq3<0) LCD_DisplayStringLine(Line4,(uint8_t *)"     B=NULL      ");
				else if(cnt3>1000)
				{
					tB=(float)cnt3/1000;
					sprintf(buf,"     B=%.2fmS      ",tB);
					LCD_DisplayStringLine(Line4,(uint8_t *)buf);	
				}
				else 
				{
					sprintf(buf,"     B=%duS      ",cnt3);
					LCD_DisplayStringLine(Line4,(uint8_t *)buf);	
				}
			}
			break;
		}
		case 1:  //参数界面
		{
			LCD_DisplayStringLine(Line1,(uint8_t *)"        PARA        ");
			sprintf(buf,"     PD=%dHz      ",PD);
			LCD_DisplayStringLine(Line3,(uint8_t *)buf);
			sprintf(buf,"     PH=%dHz      ",PH);
			LCD_DisplayStringLine(Line4,(uint8_t *)buf);
			sprintf(buf,"     PX=%dHz      ",PX);
			LCD_DisplayStringLine(Line5,(uint8_t *)buf);
			break;
		}
		case 2:  //记录界面
		{
			LCD_DisplayStringLine(Line1,(uint8_t *)"        RECD        ");
			sprintf(buf,"     NDA=%d        ",NDA);
			LCD_DisplayStringLine(Line3,(uint8_t *)buf);
			sprintf(buf,"     NDB=%d        ",NDB);
			LCD_DisplayStringLine(Line4,(uint8_t *)buf);
			sprintf(buf,"     NHA=%d        ",NHA);
			LCD_DisplayStringLine(Line5,(uint8_t *)buf);
			sprintf(buf,"     NHB=%d        ",NHB);
			LCD_DisplayStringLine(Line6,(uint8_t *)buf);
			break;
		}
		default:break;
	}
	if(NDA>=3||NDB>=3) LED_Ctrl(led|=0x80);
	else LED_Ctrl(led&=~0x80);
}

按键

按键的判断我采用的是状态机法,基本操作在第十四届蓝桥杯嵌入式省赛中已经进行过讲解了,也可以去网上学习一下,我在这里主要讲主循环中的按键循环函数。

void key_while(void)
{
	if(key[0].kind=='s')
	{
		if(display==1)
		{
			if(DHX=='D')
			{
				PD+=100;
				if(PD>1000) PD=1000;
				if(PD<100) PD=100;
			}
			else if(DHX=='H')
			{
				PH+=100;
				if(PH>10000) PH=10000;
				if(PH<1000) PH=1000;
			}
			else if(DHX=='X') 
			{
				PX+=100;
				if(PX>1000) PX=1000;
				if(PX<-1000) PX=-1000;
			}
		}
		key[0].kind='n';
	}
	if(key[1].kind=='s')
	{
		if(display==1)
		{
			if(DHX=='D')
			{
				PD-=100;
				if(PD>1000) PD=1000;
				if(PD<100) PD=100;
			}
			else if(DHX=='H')
			{
				PH-=100;
				if(PH>10000) PH=10000;
				if(PH<1000) PH=1000;
			}
			else if(DHX=='X')
			{
				PX-=100;
				if(PX>1000) PX=1000;
				if(PX<-1000) PX=-1000;
			}
		}
		key[1].kind='n';
	}
	if(key[2].kind=='s')
	{
		if(display==0)
		{
			if(f_or_t=='f') f_or_t='t';
			else if(f_or_t=='t') f_or_t='f';
		}
		else if(display==1)
		{
			if(DHX=='D') DHX='H';
			else if(DHX=='H') DHX='X';
			else if(DHX=='X') DHX='D';
		}
		key[2].kind='n';
	}
	if(key[2].kind=='l')
	{
		if(display==2)
			NDA=0,NDB=0,NHA=0,NHB=0;
		key[2].kind='n';
	}
	if(key[3].kind=='s')
	{
		if(display==0)
		{
			LCD_Clear(Black);
			display=1;
			LED_Ctrl(led&=~0x01);
			DHX='D';
		}
		else if(display==1)
		{
			LCD_Clear(Black);
			display=2;
		}
		else if(display==2)
		{
			LCD_Clear(Black);
			display=0;
			f_or_t='f';
			LED_Ctrl(led|=0x01);
		}
		key[3].kind='n';
	}
}

以上就是针对本题的按键判断函数,可以对照题目要求一一查看各个功能。函数中的变量在前面有说明。

频率超限

题目要求当通道 A 或 B 的频率大于 PH 时,对应的超限次数 NHA/B加1,如图:

为了实现此要求,我们需要设置一个长度为2的队列记录此时刻和上一时刻的频率,如果此时刻的频率大于PH且上一时刻的频率小于PH,则说明超限一次,NHx++。如下:

int PHA[2],PHB[2];  
void PH_beyond(void)  //频率超限计数
{
	PHA[1]=frq2;
	PHB[1]=frq3;
	if(PHA[1]>PH&&PHA[0]<PH) NHA++;
	if(PHB[1]>PH&&PHB[0]<PH) NHB++;
	if(PHA[1]>PH) LED_Ctrl(led|=0x02);
	else LED_Ctrl(led&=~0x02);
	if(PHB[1]>PH) LED_Ctrl(led|=0x04);
	else LED_Ctrl(led&=~0x04);
	PHA[0]=PHA[1];
	PHB[0]=PHB[1];
}

频率突变

题目要求时间窗口内(3 秒),某个通道采集到的频率最大值和最小值的差值大于 PD 时,该通道频率突变次数 NDA/B 加 1,如图:

性能要求中明确指出频率数据更新频次为10 次/秒,所以我们在这里开启一个0.1s的定时中断,并设置一个长度为3s÷0.1s=30的队列,以存储临近3s内各个时刻的频率值。

每次进中断时,将队头元素出队,将新测得的此时刻的频率值入队,再找出队列中的最大值与最小值,若二者之差大于PD,则NDx++。

(比赛时时间紧张,为了图省事我直接用的数组,故时间复杂度要多一些,不过也无所谓)

uint8_t i=0;
int PDA[30]={0},PDB[30]={0};
int max_a,min_a,max_b,min_b;
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
	if(htim->Instance==TIM4)
	{
		key_interrupt();
	}
	if(htim->Instance==TIM17)
	{
		for(i=0;i<29;++i)  //更新
		{
			PDA[i]=PDA[i+1];
			PDB[i]=PDB[i+1];
		}
		PDA[29]=frq2;
		PDB[29]=frq3;
		if(PDA[0]!=0 && PDB[0]!=0)
		{
			max_a=PDA[29],min_a=PDA[29];
			max_b=PDB[29],min_b=PDB[29];
			for(i=28;i>0;--i)  //排序
			{
				if(PDA[i]>max_a) max_a=PDA[i];
				if(PDA[i]<min_a) min_a=PDA[i];
				if(PDB[i]>max_b) max_b=PDB[i];
				if(PDB[i]<min_b) min_b=PDB[i];
			}
			if(max_a-min_a>PD) ++NDA;
			if(max_b-min_b>PD) ++NDB;
	    }
	}
}

由于PDx数组初始值默认为0,所以为防止刚上电时PDx[29]很大,而后面的时刻还没有被更新,导致差值一定大于零引起误判,所以加一条判断语句使系统3s后再执行频率突变检测功能。

程序结构

在主循环中执行这三个函数:

while (1)
{
	key_while();
	DI();
	PH_beyond();
}

中断的工作在“频率突变”讲解部分中已经体现。

源码

完整工程已经上传到CSDN,点击下方链接即可,免费下载,欢迎批评指正。

https://download.csdn.net/download/yipenmian/89129077

评论 26
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值