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

目录

题目

效果

分析

源码


题目

效果

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

分析

这届省赛的题目感觉比较简单,没涉及到通讯、存储等知识的考察。类似ADC电压采集、定时器输入捕获测频率、LCD等开发板都带有基本测试历程,看一下直接拷贝就行。所以我想说的不多,大致以下几点:

1.按键的长按。如果你之前检测按键采用的方法是直接读取引脚电平或者延时消抖,那处理长按这种情况就大概没有那么从容,以后再来个双击检测更是雪上加霜...所以从最初的检测短按开始就要养成良好的习惯。正确的做法是用定时中断检测按键。

首先我们定义一个按键结构体:

typedef struct
{
	bool gpio;  //读取按键引脚电平,初步判断按键是否被按下
	uint8_t state;  //记录按键状态
	char kind;  //判断长按还是短按,其值为s或l或n(short、long、nothing)
	uint16_t count;  //用来计数,判断长短按
}Key;
Key key[4];

 如注释,结构体Key中的gpio用于读取按键引脚电平,初步判断按键是否被按下;state用于记录按键状态;kind用于判断长按还是短按,其值为s或l或n(short、long、nothing);count用来计数,判断长短按。

接着我们开启一个10ms的定时中断,每次进中断时,先会依次读取四个按键的引脚电平,然后进行初步判断,若被检测到被按下(低电平)则将按键状态state置1,下一次进中断时(10ms后)若按键依然检测到被按下,则将按键状态state置2,下一步进行长短按的甄别,即若之后进的200次中断里(即2s)按键都被检测按下,则判断为长按,否则判断为短按。每次四个按键都会进行如此的判断。如下:

void Key_Interrupt(void)
{	
	key[0].gpio=HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0);
	key[1].gpio=HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1);
	key[2].gpio=HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_2);
	key[3].gpio=HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0);
	for(uint8_t i=0;i<4;i++)
	{
		switch(key[i].state)
		{
			case 0:  //初步判断按键是否被按下
				if(key[i].gpio==0) //按下
				{
					key[i].state=1;
					key[i].count=0;
				}
				break;
			case 1:  //10ms消抖,最终判断按键是否被按下
				if(key[i].gpio==0) key[i].state=2;  //按下
				else key[i].state=0;  //松手
				break;
			case 2:  //1.2s判断长按短按
				if(key[i].gpio==1)  //松手
				{
					if(key[i].count<200) key[i].kind='s';
					if(key[i].count>200) key[i].kind='l';
					key[i].state=0;
					key[i].count=0;
				}
				else key[i].count++;  //按着就计数
				break;
			default:break;
		}
	}
}

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
	if(htim->Instance==TIM4)
	{
		Key_Interrupt();
	}
}

2.题目要求PA1引脚输出的pwm频率要可以在5s内均匀地从4kHz变化到8kHz,且步进值不大于200Hz。因为我本身就开了一个10ms的定时中断判断按键,所以当需要pwm频率改变时,我就让它每次进中断的改变一点点,例如每次改变10,那么共需要改变4000/10=400次,也就是需要进400次中断,400×10ms=4s<5s,符合要求。在Key_Interrupt()中for循环的下方加上如下代码:

    if(frq_convert==1) key[1].kind='s';  //防止转换中key2被长按
	if(display==0&&key[1].kind=='s')  //界面1变更频率模式
	{
		frq_convert=1;  //转换中置1
		key[0].kind='n';  //转换其间所有按键均不生效
		key[2].kind='n';
		key[3].kind='n';
		if(++LED2_Blink>10)  //每0.1s闪烁一次
		{
			LED2_Toggle();
			LED2_Blink=0;
		}
		if(pwm_mode=='H')  //高频模式转为低频模式
		{
			frq_output-=10;  //10ms变更一次,每次变更10,一共变更4000,共4s			
			arr=(uint16_t)(1000000/frq_output);
			if(frq_output<=4000)  //完成转换
			{
				frq_output=4000;
				arr=20000;
				pwm_mode='L';
				key[1].kind='n';
				frq_convert=0;  //转换完成后置0
			}
			__HAL_TIM_SET_AUTORELOAD(&htim2,arr);  //设置arr,及设置频率
		}
		if(pwm_mode=='L')  //低频模式转为高频模式
		{
			frq_output+=10;  //10ms变更一次,每次变更10,一共变更4000,共4s
			arr=(uint16_t)(1000000/frq_output);
			if(frq_output>=8000)  //完成转换
			{
				frq_output=8000;
				arr=10000;
				pwm_mode='H';
				key[1].kind='n';
				frq_convert=0;  //转换完成后置0
			}
			__HAL_TIM_SET_AUTORELOAD(&htim2,arr);  //设置arr,及设置频率
		}
		if(frq_convert==0)
		{
			++n;  //转换次数加一
			LED2_OFF();  //转换完成LED2熄灭
			LED2_Blink=0;
		}
	}

3.能够判断长按后就可以对pwm占空比进行锁定了。定义一个锁定标志位,默认为0,在界面1下显示占空比时先判断下锁定标志位是否为0,若是则读取ADC控制占空比;如果检测到Key4长按,则将锁定标志位置1,此时判断条件不再成立,if中的语句不再执行,所以占空比一直保留上次锁定前的数值;如果检测到key4短按,判断下是否在界面1下锁定标志位是否为1,若是则将标志位转为0取消锁定。

4.主循环中执行两个函数:

while (1)
{
	key_circulate();
	DI();
}

这两个函数是对按键和LCD显示界面的判断,分别长这样:

void key_circulate(void)  //主函数按键循环
{
	if(key[0].kind=='s')
	{
		LCD_Clear(Blue);    //清除上个界面的遗留
		LCD_SetBackColor(Blue);
		LCD_SetTextColor(White);
		if(display==0)
		{
			display=1;
			LED1_OFF();
		}
		else if(display==1) display=2;
		else if(display==2)
		{
			display=0;
			LED1_ON();
		}
		key[0].kind='n';
	}
	if(key[1].kind=='s')
	{
//		if(display==0) 频率更改在中断里进行
		if(display==1)
		{
			if(RK_Select==0) RK_Select=1;
			else if(RK_Select==1) RK_Select=0;
		}
		key[1].kind='n';
	}
	if(key[2].kind=='s')
	{
		if(display==1)
		{			
			if(RK_Select==0)
			{
				++R;
				if(R>10) R=1;
			}
			if(RK_Select==1)
			{
				++K;
				if(K>10) K=1;
			}
		}
		key[2].kind='n';
	}
	if(key[3].kind=='s')  //key3短按
	{
		if(display==0) 
		{	
			if(pwm_lock==1) 
			{
				pwm_lock=0;
				LED3_OFF();
			}
		}
		if(display==1)
		{
			if(RK_Select==0)
			{
				--R;
				if(R<1) R=10;
			}
			if(RK_Select==1)
			{
				--K;
				if(K<1) K=10;
			}
		}
		key[3].kind='n';
	}
	if(key[3].kind=='l')  //key3长按
	{
		if(display==0)
		{
			pwm_lock=1;
			LED3_ON();
		}
		key[3].kind='n';
	}
}
void DI(void)  //Display Interface
{
	switch(display)
	{
		case 0:
		{
			velocity=frq_input*6.28*R/100/K;
			LCD_DisplayStringLine(Line1, (uint8_t *)"        DATA        ");
			sprintf(buf,"     M=%c  ",pwm_mode);
			LCD_DisplayStringLine(Line3, (uint8_t *)buf);
			if(pwm_lock==0)
			{
				ccr=(0.0003*Get_ADCvalue(&hadc2)-0.2755)*arr;
				if(ccr>=0.85*arr) ccr=0.85*arr;
				if(ccr<=0.1*arr) ccr=0.1*arr;
				__HAL_TIM_SET_COMPARE(&htim2,TIM_CHANNEL_2,ccr);
			}
			sprintf(buf,"     P=%d%%  ",(int)ccr*100/arr);
			LCD_DisplayStringLine(Line4, (uint8_t *)buf);
			sprintf(buf,"     V=%.1f  ",velocity);
			LCD_DisplayStringLine(Line5, (uint8_t *)buf);
			break;
		}
		case 1:
		{
			LCD_DisplayStringLine(Line1, (uint8_t *)"        PARA        ");
			sprintf(buf,"     R=%d  ",R);
			LCD_DisplayStringLine(Line3, (uint8_t *)buf);
			sprintf(buf,"     K=%d  ",K);
			LCD_DisplayStringLine(Line4, (uint8_t *)buf);
			break;
		}
		case 2:
		{
			LCD_DisplayStringLine(Line1, (uint8_t *)"        RECD        ");
			sprintf(buf,"     N=%d  ",n);
			LCD_DisplayStringLine(Line3, (uint8_t *)buf);
			MH=8000*6.28*R/100/K;			
			sprintf(buf,"     MH=%.1f  ",MH);
			LCD_DisplayStringLine(Line4, (uint8_t *)buf);
			ML=4000*6.28*R/100/K;
			sprintf(buf,"     ML=%.1f  ",ML);
			LCD_DisplayStringLine(Line5, (uint8_t *)buf);
			break;
		}
		default:break;
	}
}

 就是按照题目要求进行判断来实现功能的切换和界面的显示。

5.吐槽一下 这个带锁存器的LED我是真的无语。。。从未见过如此难用的LED,单操作一个的时候要打开锁存器PD2,但是一旦打开所有的LED就都会被置低电平(亮),所以想要操作多个LED的亮灭是真的费心。。。不过也可能是我的操作方法有误,欢迎小伙伴们教授我正确的操作方法!

源码

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

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

评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值