2022年蓝桥杯嵌入式第二场程序题

笔者有幸,参加了第十三届蓝桥杯嵌入式比赛,由于笔者使用的是旧板子(F103RBT6),所以在编程时用的是Keil473,在比赛过程中遇到了不少问题。 

 

在比赛开始一个半小时后,初始化跟大体框架都写完了,在调试按键时碰到了第一个BUG,KEY1按不动。在之前做往届赛题时根本没出现的BUG,由于之前没碰到过,在赛场上就直接背模板,嘎嘎乱敲,使用结构体数组+定时器扫描的模式。以下是扫描函数。

void TIM3_IRQHandler(void){//KEY  //10ms
	int i = 0;
	key[0].key_sta = GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0);
	key[1].key_sta = GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_8);
	key[2].key_sta = GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1);
	key[3].key_sta = GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_2);

	if(TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET)
	{
		TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
		for(i=0;i<4;++i)
		{
			switch (key[i].stap)
			{
				case 0:
				{
					if(key[i].stap==0) 
					{
						key[i].stap=1;
						key[i].key_time=0;
					}
				}
				break;
				case 1:
				{
					if(key[i].key_sta==0)
					{
						key[i].stap=2;
					}
					else key[i].stap=0;
				}
				break;
				case 2:
				{
					if(key[i].key_sta==1)
					{
						key[i].stap=0;					
						if(key[i].key_time<70) key[i].short_flag=1;
					}
					else 
					{
						key[i].key_time++;
						if(key[i].key_time>70)  key[i].long_flag=1;
					}
				}
				break;	
			}
		}
	}
}

 

 在测试的时候发现KEY1不好使,按下无反应,换KEY2有反应,当时人都麻了。440e910611e04aacb542a4545089405f.jpeg

 经过后期调试,发现问题存在于数组里,将key[x]中的所有下标增加一位,也就是从1开始,哎,它就好使,从0开始1号按键就不能用,还有一个问题就是结构体,愚以为用结构体将商品的数量、单价、库存用结构体来写比较方便,但是,X商品的数量会无故暴增,即使定义为int,在显示时也是到5亿多,现在我也没解决这些问题,但是,在Keil5中就没有这些问题,不晓得是不是编译器的问题。欢迎各位大哥指正!

 

不废话了,上干货!

 

LCD部分

        对于这一部分没什么太多需要注意的点,唯一需要注意的就是商品价格界面的价格范围在

1.0---2.0,也就是说当价格大于2.0时,要将价格设置为1.0。c1b5771c13ae4a5c9f7c832ca85b75fb.png

 附LCD部分的代码

void LCD_func(void)
{ 
  switch(MODE)
  {
    case 1:{
      sprintf((char*)LCD_str,"        SHOP        ");
      LCD_DisplayStringLine(Line2,LCD_str);
      sprintf((char*)LCD_str,"     X:%d            ",X_num);
      LCD_DisplayStringLine(Line4,LCD_str);
      sprintf((char*)LCD_str,"     Y:%d            ",Y_num);
      LCD_DisplayStringLine(Line5,LCD_str);
      
    }break;
    
  case 2:{
    sprintf((char*)LCD_str,"        PRICE       ");
    LCD_DisplayStringLine(Line2,LCD_str);
    sprintf((char*)LCD_str,"     X:%.1f            ",(float)price[0]/10);
    LCD_DisplayStringLine(Line4,LCD_str);
    sprintf((char*)LCD_str,"     Y:%.1f            ",(float)price[1]/10);
    LCD_DisplayStringLine(Line5,LCD_str);  
      
    }break;
  case 3:{
    sprintf((char*)LCD_str,"        REP         ");
    LCD_DisplayStringLine(Line2,LCD_str);
    sprintf((char*)LCD_str,"     X:%d            ",stock[0]);
    LCD_DisplayStringLine(Line4,LCD_str);
    sprintf((char*)LCD_str,"     Y:%d            ",stock[1]);
    LCD_DisplayStringLine(Line5,LCD_str);  
      
    }break;
 
  }
}

KEY部分

 KEY1

        切换界面,在商品购买、商品价格、库存三个界面来回切换,无非就是一个变量的累加,到界限后回滚。

3281f44cf5d7485b8bd245b154c22f1e.png

 KEY1代码:

if(key[0].short_flag == 1)
  { 
    key[0].short_flag = 0;
    MODE ++;
    if(MODE == 4) MODE = 1;
  }

KEY2

        定义为商品X的累加键。

        在商品购买界面下,B2单击购买量++,但要注意的是,购买量只能在0到库存的范围内,当购买量大于当前库存时,购买量回滚到0。并且库存发生变化时,购买量的范围也发生相应变化。

        在商品价格界面下,B2单击商品X的单价增加0.1,但还是存在范围限制,智能在1.0到2.0之间调整,大于2.0时回滚到1.0。

        在库存界面下,B2单击X库存++,但题目并未指出库存上限,为了防止意外,建议库存的变量可以用范围大一点的类型来定义。904a75f860c849088a65752ab53cd1c7.png

 KEY2代码:

if(key[1].short_flag == 1)
  {
    key[1].short_flag = 0;
    
    switch(MODE)
    {
      case 1:{
        X_num += 1;
        if(X_num == (stock[0]+1)) X_num = 0;
      }break;
      case 2:{
        price[0] += 1;
        if(price[0] == 21) price[0] = 10;
        EEPROM_write(price,0x02,2);
        Delay_Ms(10); 
      }break;
      case 3:{
        stock[0]++;
        stock[0] = stock[0] - X_num;
        EEPROM_write(stock,0x00,2);
        Delay_Ms(10);
      }break;
    }
  }

KEY3

        与B2相同,不在赘述。

4c0af56c7cde4471ad964bff62f9823c.png

 KEY3代码:

if(key[2].short_flag == 1)
  {
    key[2].short_flag = 0;
    
    switch(MODE)
    {
      case 1:{
        Y_num += 1;
        if(Y_num == (stock[1]+1)) Y_num = 0;
 
      }break;
      case 2:{
        price[1] += 1;
        if(price[1] == 21) price[1] = 10;
        EEPROM_write(price,0x02,2);
        Delay_Ms(10);
      }break;
      case 3:{
        stock[1]++;
        stock[1] = stock[1] - Y_num;
        EEPROM_write(stock,0x00,2);
        Delay_Ms(10);
        
      }break;
      
    }
  }

 

KEY4

        定义为“确认键”。需要注意的是只有在商品购买界面下按下B4才有效,我这里用标志位的方式来进行操作。

fc4219432336457482367c71add1ce05.png

 KEY4代码:  

if(key[3].short_flag == 1)
  {
    key[3].short_flag = 0;
    
    if(MODE == 1)
    {
      stock[0] = stock[0]-X_num;
      stock[1] = stock[1]-Y_num;
      EEPROM_write(stock,0x00,2);
      FLAG.S5 = 1;
      FLAG.DU30 = 1;
      S5_e = 1;
      FLAG.USART = 1;
    }
  }

EEPROM部分

        题目要求储存商品的库存数量跟价格,还要求发生变动才存储,无变化不写入,这也就意味着不能采用定时写入的方式,于是乎,我才用了在按键按下后就写入,改变一次就写入一次,但这确实有一个可以抬杠的地方,原本价格是1.3,我按了一圈回来又到1.3,这算不算改变,要不要写入。对此,我的看法是改变了,还改变了不止一次,所以要写入。此处欢迎交流。

        还有一点就是上电读取数据。由于不知道官方测试时用的24C02里存的是什么,所以在上电读取时需要判断一下上次写入的是不是想要的数据。对此,我的想法是运用校验位。如果校验位不对,就说明不是我要的数据,所以直接用预定的数据将对应地址的原数据覆盖,并且写入校验位。当检验位匹配正确后,就可以将对应地址的数据读出,处理后直接赋给变量。

727f8e57527c441b8fe9b6f0db0a0161.png

 数据写入就在按键程序里,往上看看。

下边是上电校验的代码:

EEPROM_read(check_r,0x66,2);
  Delay_Ms(10);
  if((check_r[0]==6)&&(check_r[1]==6))
  {
    EEPROM_read(price,0x02,2);
    Delay_Ms(10);
    EEPROM_read(stock,0x00,2);
    Delay_Ms(10);
    
  }
  else 
  {
    EEPROM_write(price,0x02,2);
    Delay_Ms(10);
    EEPROM_write(stock,0x00,2);
    Delay_Ms(10);
    EEPROM_write(check_w,0x66,2);
    Delay_Ms(10);
    LED_Control(LED8,ON);

  }

 

串口部分

        在购买界面下,B4单击,串口向上位机发送数量跟总价格,格式为:X:2,Y:2,Z:4.0。那么问题又来了,要不要\r\n,此处欢迎交流。

        在任意界面下,给单片机发送'?',单片机给上位机返回当前的商品价格。格式为:X:1.0,Y:1.0。这里还需要注意一点,接收到的字符长度。

5c6f57b8641c4bc9a9a8a9c59921a4f2.png

 69ad36e5a1a94f3ba3b54d9df0346b9d.png

 串口接收“?”代码如下:

void USART_func(void)
{
  u8 len;
  if(USART_RX_STA&0x8000)
  {
    len = USART_RX_STA&0x3fff;
    USART_RX_STA = 0;
    if(len == 1)
    {
      if(USART_RX_BUF[0] == '?')
        printf("X:%.1f,Y:%.1f",((float)price[0]/10),((float)price[1]/10));
    } 
  }
}

B4按下后,向上位机发送数据代码:

if(FLAG.USART == 1)
    {
      Z = (X_num*((float)price[0]/10)+Y_num*((float)price[1])/10);
      printf("X:%d,Y:%d,Z:%.1f",X_num,Y_num,Z);
      
      X_num = 0;
      Y_num = 0;
      FLAG.USART = 0;
      
    }

LED部分

        LED1在B4按下后点亮5s,后熄灭。

        LED2在X与Y的库存均为0时才以0.1s间隔闪烁。那么也就是在库存不全为0,LED2为熄灭状态。

f10a4f5dfc544b67bcc3a4a27452d56f.png

 LED控制代码:

//LED
void LED_Control(uint16_t LED,uint8_t LED_Status){
	uint16_t LED_Buff=0xffff;
	uint16_t pinpos = 0x0000,pos = 0x0000,currentpin = 0x0000;
	for(pinpos = 0x00;pinpos < 0x08;pinpos ++)
	{
		pos = ((uint32_t)0x01) << (pinpos + 0x08);
		currentpin = (LED) & pos;
		if(currentpin == pos)
		{
			if(LED_Status == OFF){
			GPIOC_Buff |= (((uint32_t)0x0001) << (pinpos + 0x08));
			}        
			else if(LED_Status == ON)
			{
				GPIOC_Buff &=~ (((uint32_t)0x0001) << (pinpos + 0x08));
			}
			else
			{
				LED_Buff = GPIOC_Buff;  
				if(LED_Buff & (((uint32_t)0x0001) << (pinpos + 0x08)))  //LED 口为1,表示LED关着, 反转则需要开启
				{
					GPIOC_Buff &=~ (((uint32_t)0x0001) << (pinpos + 0x08));
				}
				else{
					GPIOC_Buff |= (((uint32_t)0x0001) << (pinpos + 0x08)); 
				} 
			}
		}			
	}
	GPIOC->ODR |= 0Xff00;  //清空LCD写入的缓存问题!!!	
	GPIOC->ODR &= GPIOC_Buff;	    //只有开的LED才有资格被写入ODR
	GPIO_SetBits(GPIOD,GPIO_Pin_2);  //高电平允许
	GPIO_ResetBits(GPIOD,GPIO_Pin_2);  //低电平状态锁存
}

LED2闪烁代码:

if((stock[0]==0)&&(stock[1]==0))
    {
      S01_e = 1;
      if(S01 == 1)
      {
        LED_Control(LED2,FILP);
        S01 = 0;
        S01_e = 0;
      }
    }
    else LED_Control(LED2,OFF);

LED1亮5s代码:

if(FLAG.S5 == 1)
    {
      LED_Control(LED1,ON);
      if(S5 == 1)
      {
        LED_Control(LED1,OFF);
        FLAG.S5 = 0;
        S5_e = 0;
      } 
     
    }

 

PWM部分

        在B4按下后,PA1输出频率2KHz,占空比30%的方波,时长为5s,后PA1输出频率2KHz,占空比5%的方波。也就是说,只有B4按下后的5s内,PA1的占空比为30%,其余都是5%。

3beef04995d841abb2a213c69f7ccb59.png

 PWM输出代码:

void PWM_Set(u32 ch1fre,u32 ch1duty,u32 ch2fre,u32 ch2duty)
{
  ch1val=1000000/ch1fre;   
	ch2val=1000000/ch2fre;
	ch1dut=ch1val*ch1duty/100;
	ch2dut=ch2val*ch2duty/100;
}

void TIM2_IRQHandler(void){		//PWM
	u16 capture;
 
  if (TIM_GetITStatus(TIM2, TIM_IT_CC2) != RESET)
  {
    TIM_ClearITPendingBit(TIM2, TIM_IT_CC2 );
    capture = TIM_GetCapture2(TIM2);
		if(ch1flag)   TIM_SetCompare2(TIM2, capture + ch1dut );
    else          TIM_SetCompare2(TIM2, capture +ch1val- ch1dut );
		ch1flag^=1;
  }


  if (TIM_GetITStatus(TIM2, TIM_IT_CC3) != RESET)
  {
    TIM_ClearITPendingBit(TIM2, TIM_IT_CC3);
    capture = TIM_GetCapture3(TIM2);
		if(ch2flag)   TIM_SetCompare3(TIM2, capture + ch2dut );
    else          TIM_SetCompare3(TIM2, capture +ch2val- ch2dut );
		ch2flag^=1;
  }
}

B4按下后以及其他情况下的PWM设置:

if(FLAG.DU30 == 1)  PWM_Set(2000,30,2000,100);
    else PWM_Set(2000,5,2000,100);

 

附完整工程:

链接:https://pan.baidu.com/s/1YRrYYJLpPus-Iqx04gsy2g?pwd=LMTX 
提取码:LMTX

小生文采拙劣,附几位泰斗的文章。

嵌入式组:第十三届——蓝桥杯嵌入式第二场_WENFGCG的博客-CSDN博客

(97条消息) 第十三届第二场蓝桥杯嵌入式主观题讲解_amkl的博客-CSDN博客

单片机组:蓝桥杯单片机第十三届第二场_XzKaiXinFh的博客-CSDN博客

蓝桥杯十三届第二场程序设计_Namaste795的博客-CSDN博客_第十三届蓝桥杯第二场

 

 

 


版权声明:本文为No1match原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。