摘要:本文为作者对第十届蓝桥杯嵌入式设计与开发项目省赛——程序设计试题的解析。本文包括“题目要求”、“程序设计”、“效果展示”3个部分。供复盘使用。若发现错误之处,请不吝赐教。

链接:蓝桥杯嵌入式方向备赛记录(STM32G431)为作者备赛蓝桥杯嵌入式过程中,整理的学习总结。包括各模块使用要点、各模块程序等,基本搭建好工程框架,给出了各模块处理程序。

目        录

一、题目要求

二、程序设计

1、ADC

2、按键

3、LCD

4、LED

5、整合(其他部分)

三、效果展示


一、题目要求

二、程序设计

  • 工程可以按照几条主线进行下去:

        模拟电压输入(ADC):要采集电压,要判断实时电压和阈值电压的关系。

        按键:首先要能判断哪个按键按下(即按键检测),然后对应按键有何功能。B1:切换界面,程序上即为翻转界面状态标志位;B2:切换参数选择项并高亮显示:可以扩展界面状态标志位,在LCD处理函数中,根据标志位变量值,高亮显示对应行;B3:加按键;B4:减按键。可以通过自加++、自减--运算符改变相应变量值。

        按键部分涉及3点要解决好:(1)高亮显示行,即状态标志位如何改变?(2)加减功能,要处理好,不能出现冲突,即电压上限阈值>电压下限阈值,和,上限下限指示灯不能相同。这2点归根结底,还是变量设置的问题。(3)按按键不要影响到LED,即按键要以非阻塞方式检测按键,并最好进行消抖处理。这1点是所有试题都要解决的。

         LCD:2个界面。主要有2点要解决好:(1)高亮显示;(2)界面切换时,不要有上一界面的数据/背景遗留。

         LED:首先要封装好LED显示的函数,然后就是LED处理。要解决好LED某些位,按一定频率闪烁的问题。即通过Systic实现。程序有2种写法。

         其他注意点:(1)串口重定向后,要勾选“魔术棒”里的“Use MicroLiE”选项,不然屏幕显示不了;(2)EEPROM问题。

         总的来说,我认为变量/状态标志位的设置非常关键。妥善地设置变量,能快速解决一些难点。

              上述思路对应程序,即为程序实现的框架。

1、ADC

//ADC处理函数
void ADC_Proc(void)	
{
	//读取ADC,计算电压
	uint16_t adc = 0;
	HAL_ADC_Start(&hadc2);
	adc = HAL_ADC_GetValue(&hadc2);
	V = adc*3.3/4096;
	//判断电压状态
	ucStatus_Flag = 0; //Normal
   if(V*10 > ucMaxVolt) ucStatus_Flag = 1;//Upper
   if(V*10 < ucMinVolt) ucStatus_Flag = 2;//Lower
}

2、按键

     非阻塞方式检测按键(并消抖)部分,见作者博客。这里给出按键处理函数:

void KEY_Proc(void)		//注意要清除标志位
{
	if(key[0].ucJudgeKeyState == 1)	//如果K1短按	切换选择界面
	{
		if(!ucState)
		{
			ucState = 1;
		}
		else
		{
//			若要存储,则使用这部分
//			x24c02_write(0,ucMaxVolt);
//			x24c02_write(1,ucMinVolt);
//			x24c02_write(2,ucUpperLED);
//			x24c02_write(3,ucLowerLED);	
			ucState = 0;
		}
		key[0].ucJudgeKeyState = 0;
	}
	if(key[1].ucJudgeKeyState == 1)	//如果K2短按。切换选择参数项 注意这里不能像51那里用else if 否则短按后,长按后无效
	{
		if(ucState)
			{
				if(++ucState == 5)//ucState 2~5 分别代表4个选项
					ucState = 1;
			}
		key[1].ucJudgeKeyState = 0;
	}
	if(key[2].ucJudgeKeyState == 1)	//如果K3短按       
	{
		switch(ucState) 
		{ 
			case 1: 
				if(ucMaxVolt <= 30) ucMaxVolt += 3; 
			    break; 
			case 2: 
				if((ucMaxVolt - ucMinVolt) > 3) ucMinVolt += 3; 
			    break; 
			case 3: 
                //先灭刚才闪的灯
                ucLed &=~ 1<<(ucUpperLED-1);
			    Led_Disp(ucLed);

				if(++ucUpperLED > 8) ucUpperLED = 1; 
				if(ucUpperLED == ucLowerLED) 
				{
					if(++ucUpperLED > 8) ucUpperLED = 1; 
				}
			    break; 
			case 4: 
                //先灭刚才闪的灯
                ucLed &=~ 1<<(ucLowerLED-1);
			    Led_Disp(ucLed);

				if(++ucLowerLED > 8) ucLowerLED = 1; 
				if(ucUpperLED == ucLowerLED) 
				{
					if(++ucLowerLED > 8) ucLowerLED = 1; 
				}
				break;
			default: break;
		}
		key[2].ucJudgeKeyState = 0;
	}
	if(key[3].ucJudgeKeyState == 1)	//如果K4短按       
	{
		switch(ucState) 
		{ 
			case 1: 
				if((ucMaxVolt - ucMinVolt) > 3) ucMaxVolt -= 3; 
			break; 
			case 2: 
				if(ucMinVolt > 3) ucMinVolt -= 3; 
			break; 
			case 3: 
                //先灭刚才闪的灯
                ucLed &=~ 1<<(ucUpperLED-1);
			    Led_Disp(ucLed);

				if(--ucUpperLED == 0) ucUpperLED = 8; 
				if(ucUpperLED == ucLowerLED) 
				if(--ucUpperLED == 0) ucUpperLED = 8; 
			break; 
			case 4: 
                //先灭刚才闪的灯
                ucLed &=~ 1<<(ucLowerLED-1);
			    Led_Disp(ucLed);

				if(--ucLowerLED == 0) ucLowerLED = 8; 
				if(ucUpperLED == ucLowerLED) 
				if(--ucLowerLED == 0) ucLowerLED = 8; 
				break;
			default: break;
		}
		key[3].ucJudgeKeyState = 0;
	}
	Led_Disp(ucLed);
}

3、LCD

     界面Main里:注意Status显示,设置了数组char* Status[]存放3个状态,并可通过状态标志位ucStatus_Flag索引相应状态。

     界面Setting里:根据ucState (1~5)的不同值,即代表不同选项,高亮显示相应行。

char buf1[20];
uint8_t ucState=0;//界面状态:0—Main界面   1—setting界面(MaxVolt) 2—setting界面(MaxVolt) 3—setting界面(MinVolt) 4—setting界面(Upper) 5—setting界面(Lower)

uint8_t ucMaxVolt = 24;
uint8_t ucMinVolt = 12;
uint8_t ucUpperLED = 1;
uint8_t ucLowerLED = 2;

char* Status[]={"Normal","Upper","Lower"};//状态
uint8_t ucStatus_Flag = 0;//索引数组

void LCD_Proc(void)
{
	if(!ucState)
	{	
		LCD_DisplayStringLine(Line3,(unsigned char*)"        Main       ");
		
		sprintf(buf1,"   Volt:%.2fV      ",V);
		LCD_DisplayStringLine(Line4,(unsigned char*)buf1);
		
		sprintf(buf1,"   Status:%s        ",Status[ucStatus_Flag]);
		LCD_DisplayStringLine(Line5,(unsigned char*)buf1);
		LCD_DisplayStringLine(Line6,(unsigned char*)"                     ");
		LCD_DisplayStringLine(Line7,(unsigned char*)"                     ");
		LCD_DisplayStringLine(Line8,(unsigned char*)"                     ");
		LCD_DisplayStringLine(Line9,(unsigned char*)"                     ");
	}
	else
	{
		LCD_DisplayStringLine(Line3,(unsigned char*)"      Setting          ");
		
		if(ucState == 1) LCD_SetBackColor(Green);
		sprintf((char*)buf1,"   MaxVolt:%4.2f    ",ucMaxVolt/10.0);
		LCD_DisplayStringLine(Line4,(unsigned char*)buf1);
		LCD_SetBackColor(White);
		
		if(ucState == 2) LCD_SetBackColor(Green);
		sprintf((char*)buf1,"   MinVolt:%4.2f    ",ucMinVolt/10.0);
		LCD_DisplayStringLine(Line5,(unsigned char*)buf1);
		LCD_SetBackColor(White);
		
		if(ucState == 3) LCD_SetBackColor(Green);
	    sprintf(buf1,"   Upper:LD%d       ",ucUpperLED);
		LCD_DisplayStringLine(Line6,(unsigned char*)buf1);
		LCD_SetBackColor(White);
		
		if(ucState == 4) LCD_SetBackColor(Green);
		sprintf(buf1,"   Lower:LD%d       ",ucLowerLED);
		LCD_DisplayStringLine(Line7,(unsigned char*)buf1);
		LCD_SetBackColor(White);
	}
}

4、LED

      利用Systic处理LED按0.2秒闪烁问题。程序有2种写法。

      2024/2/18:LED这里发现bug~ , 已解决

      BUG1:按B3、B4,对指示灯调整时,假设LD1亮(还未熄灭时),按下B3,指示灯序号递增,LD2闪烁时,LD1还在亮,继续按B3,后面都是如此。

      解决方法:在B3、B4按下后,ucUpperLED、ucLowerLED加减前,熄灭刚才闪的指示灯。

      解决过程:(1)在B3、B4功能处理内,调用  Led_Disp(0);  熄灭所有灯。发现,未奏效。(2)原因是只在表面上熄灭了,没有改变ucLed,因为全局Led_Disp();都是对ucLed位带操作的。所以必须改变ucLed 。添加语句如下所示(已添加到2、按键处理函数中):

//B3里
ucLed &=~ 1<<(ucUpperLED-1);
Led_Disp(ucLed);

//B4里
ucLed &=~ 1<<(ucLowerLED-1);
Led_Disp(ucLed);

       上述BUG1解决后,出现BUG2:

        BUG2 : 高压下(>2.4),上电后,高压下,LD2会常亮,直到退出高压状态。BUG2暂未解决,如有朋友知晓,请不吝赐教,不胜感激。

      方法1:

//main.c里
void LED_Proc(void)
{
	//方法1
	if(ucStatus_Flag == 0)
		ucLed=0;
	
	else if(ucStatus_Flag == 1)//上
		{
			if(usTms==200 )
			{
				ucLed ^= 1<<(ucUpperLED-1);	
				usTms=0;
			}
		}
	else if(ucStatus_Flag == 2)//下
	{
		    if(usTms==200 )
			{
				ucLed ^= 1<<(ucLowerLED-1);
				usTms=0;
			}
	}
	else
		ucLed = 0;
	Led_Disp(ucLed);
}

//it.c里
void SysTick_Handler(void)
{
  /* USER CODE BEGIN SysTick_IRQn 0 */

  /* USER CODE END SysTick_IRQn 0 */
  HAL_IncTick();
  /* USER CODE BEGIN SysTick_IRQn 1 */
	//方法1
	if(usTms<200)	//计数小于200ms,ms加
	{
		usTms++;
	}
  /* USER CODE END SysTick_IRQn 1 */
}

      方法2: 

//在it.c里
uint16_t usTms = 0;
 
extern uint8_t ucUpperLED;
extern uint8_t ucLowerLED;
extern uint8_t ucState;
extern uint8_t ucStatus_Flag;
extern uint8_t ucLed;
extern void LED_Disp(uint8_t);
 
 
void SysTick_Handler(void)
{
  /* USER CODE BEGIN SysTick_IRQn 0 */
 
  /* USER CODE END SysTick_IRQn 0 */
  HAL_IncTick();
  /* USER CODE BEGIN SysTick_IRQn 1 */
	//方法2
	usTms++;
		switch(ucStatus_Flag) 
		{ 
			case 0: 
				ucLed = 0; 
				break; 
			case 1: 
				if(usTms%200 == 0) ucLed ^= 1<<(ucUpperLED-1); 
				break; 
			case 2: 
				if(usTms%200 == 0) ucLed ^= 1<<(ucLowerLED-1); 
				break;
			default: break;
		} 
		
	Led_Disp(ucLed);
  /* USER CODE END SysTick_IRQn 1 */
}

5、整合(其他部分)

 /* USER CODE BEGIN 2 */
    HAL_TIM_Base_Start_IT(&htim4);
	
    LCD_Init();
    LCD_Clear(White);
    LCD_SetBackColor(White);
    LCD_SetTextColor(Blue);
	
	I2CInit();
	
//  若要存储,则使用这部分
//  ucMaxVolt  = x24c02_read(0);
//	ucMinVolt  = x24c02_read(1);
//	ucUpperLED = x24c02_read(2);
//	ucLowerLED = x24c02_read(3);

    //设备应具备错误设置的保护功能
	if(ucMaxVolt  >= 33) ucMaxVolt = 24; 
    if(ucMinVolt  >= 33) ucMinVolt = 12; 
    if(ucUpperLED >= 9) ucUpperLED = 1; 
    if(ucLowerLED >= 9) ucLowerLED = 2;
  /* USER CODE END 2 */

  /* USER CODE BEGIN 3 */
	   ADC_Proc();
	   KEY_Proc();
	   LCD_Proc();
	   LED_Proc();
    }
  /* USER CODE END 3 */

三、效果展示

       视频后续上传。 

Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐