蓝桥杯嵌入式|第十三届蓝桥杯嵌入式省赛程序设计试题及其题解

题目

    十三届省赛是要制作一个可由串口设置密码的密码锁。在本场比赛中,我们将用到LED模块按键模块串口模块定时器的PWM模块以及官方会提供源码的LCD模块。下面就请看原题:

题解

    在正式题解前,大家需要注意以下几点:

  • 由于LCD与LED有部分引脚是共用的,因此初始化完成LCD后最好手动关闭LED;
  • 由于每次LCD显示的长度可能不同,因此在本次显示前,要不先清屏,要不跟上次显示一样长;
  • 使用CubeMX配置完成串口USART1后需要更改默认引脚为PA9PA10
  • LED模块

        通过查询产品手册知,LED的引脚为PC8~PC15,外加锁存器74HC573需要用到的引脚PD2。(由于题目要求除LED1、LED2外的其他LED都处于熄灭状态,此处特意将所有的LED都初始化)
    CubeMX配置:

    代码样例
        由于G431的所有LED都跟锁存器74HC573连接,因此每次更改LED状态时都需要先打开锁存器,写入数据后再关闭锁存器。

    /*****************************************************
    * 函数功能:改变所有LED的状态
    * 函数参数:
    *			char LEDSTATE: 0-表示关闭 1-表示打开
    * 函数返回值:无
    ******************************************************/
    void changeAllLedByStateNumber(char LEDSTATE)
    {
    	HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15|GPIO_PIN_8
    					|GPIO_PIN_9|GPIO_PIN_10|GPIO_PIN_11|GPIO_PIN_12,(LEDSTATE==1?GPIO_PIN_RESET:GPIO_PIN_SET));
    	//打开锁存器    准备写入数据
    	HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_SET);
    	//关闭锁存器 锁存器的作用为 使得锁存器输出端的电平一直维持在一个固定的状态
    	HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET);
    }
    
    /*****************************************************
    * 函数功能:根据LED的位置打开或者是关闭LED
    * 函数参数:
    *			uint16_t LEDLOCATION:需要操作LED的位置
    *			char LEDSTATE: 0-表示关闭 1-表示打开
    * 函数返回值:无
    ******************************************************/
    void changeLedStateByLocation(uint16_t LEDLOCATION,char LEDSTATE)
    {
    	HAL_GPIO_WritePin(GPIOC,LEDLOCATION,(LEDSTATE==1?GPIO_PIN_RESET:GPIO_PIN_SET));
    	HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_SET);
    	HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET);
    }
    

    按键模块

        通过查询产品手册知,开发板上的四个按键引脚为PB0~PB2、PA0
    CubeMX配置

    代码样例
        由于G431开发板上按键数量较少以及本次按键不涉及长短按、单击双击等复杂按键的设计,因此,我们直接使用含锁机制的if判断即可。

  • 第一步,判断按键是否按键以及锁是否处于打开状态,如果两者有一个不满足函数直接返回;否则,进入下一步;
  • 第二步,上锁,延时消抖;
  • 第三步,再次读取各个按键,判断具体是哪个按键按下;
  • 第四步,判断按键是否松开,如果松开,则开锁;否则函数直接返回。
  • /*********************************************
     * 函数功能:按键扫描 含按键消抖 无长按短按设计
     * 函数参数:无
     * 函数返回值:按键的位置
     *            返回值说明:B1-1 B2-2 B3-3 B4-4
    *********************************************/
    unsigned char scanKey(void)
    {
    	//按键锁
    	static unsigned char keyLock = 1;
        //记录按键消抖时间
        // static uint16_t keyCount = 0;
    
    	//按键按下
        if((HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0) == RESET || HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1) == RESET
          || HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_2) == RESET || HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == RESET) 
          && keyLock == 1){
            //给按键上锁 避免多次触发按键
            keyLock = 0;
            
            //按键消抖 这里最好不要使用延时函数进行消抖 会影响系统的实时性
            // if(++keyCount % 10 < 5) return 0;
            // if(HAL_GetTick()%15 < 10) return 0;
            HAL_Delay(10);
    
            //按键B1
            if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0) == RESET){
                return 1;
            }
            //按键B2
            if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1) == RESET){
                return 2;
            }
            //按键B3
            if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_2) == RESET){
                return 3;
            }
            //按键B4
            if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == RESET){
                return 4;
            }
        }
        //按键松开
        if((HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0) == SET && HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1) == SET
          && HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_2) == SET && HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == SET) 
          && keyLock == 0){
            //开锁
            keyLock = 1;
        }
        return 0;
    }
    

    串口

        本次试题中,串口功能比较简单,只需要能够完成简单的接收数据即可。
    CubeMX配置
        配置时一定一定记得改引脚!!!

    代码样例
        HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)函数解析:

  • UART_HandleTypeDef *huart:串口通道;
  • uint8_t *pData:存放数据的buff;
  • uint16_t Size:一次接收数据的长度
        不过使用时还需要初始化,否则不能够进入中断接收数据;
  • /***使用HAL_UART_Receive_IT中断接收数据 每次接收完成数据后就会执行该函数***/
    void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
    {
    	if(huart->Instance == USART1){
    		// 重新使能中断
    		HAL_UART_Receive_IT(huart,(uint8_t *)&Rxbuff,sizeof(Rxbuff)); 
    	}
    }
    

    定时器输出PWM波

    CubeMX配置

    修改PWM的频率及占空比样例代码(可直接调用)

    /****************************************
    * 函数功能:修改PWM频率工作
    * 函数参数:
    *			unsigned int autoreloadDate:重装载值
    *			unsigned int compareDate:PWM的比较值
    * 函数返回值:无
    ****************************************/
    void pwmWorkByFre(unsigned int autoreloadDate,unsigned int compareDate)
    {
    	//设置重装载值
    	__HAL_TIM_SetAutoreload(&htim2,autoreloadDate-1);
    	__HAL_TIM_SetCompare(&htim2,TIM_CHANNEL_2,compareDate);
    }
    

    LCD模块

        LCD模块官方会提供源码,内含初始化,大家会用即可。如下面是一段将LCD初始化成——文字颜色为白色、背景为蓝色的LCD屏:

    /******************************************************************************
    * 函数功能:LCD初始化
    * 函数参数:无
    * 函数返回值:无
    *******************************************************************************/
    void lcdInit(void)
    {
    	//HAL库的初始化
    	LCD_Init();
    	//设置LCD的背景色
    	LCD_Clear(Blue);
    	//设置LCD字体颜色
    	LCD_SetTextColor(White);
    	//设置LCD字体的背景色
    	LCD_SetBackColor(Blue);
    }
    

    完整的配置文件

        文件说明:

  • sysInit()函数:自己添加的初始化配置;
  • sysWork()函数:系统工作逻辑函数;
  • #include "config.h"
    #include "stdio.h"
    #include "string.h"
    #include "stdlib.h"
    
    
    //外部定义变量
    extern TIM_HandleTypeDef htim7,htim2;
    extern UART_HandleTypeDef huart1;
    extern TIM_HandleTypeDef htim3;
    
    //记录目前LCD处于的界面 0-密码输入界面  1-显示输出界面
    int lcd_view_mod = 0;
    //定时器7计数  计数值加1表示定时器触发一次也就是过了10ms
    int time7_count = 0;
    //保存上一次定时器7计数值
    int time7_count_start_flag = 0;
    //密码输入界面显示的数据
    char psd_txt[20];
    //输出状态界面显示数据
    char sta_txt[20];
    
    char temp[20];
    unsigned int crrl_t = 0;
    uint32_t frd = 0;
    
    //用于循环
    int i,j;
    
    //* 存储串口1接收的数据
    uint8_t Rxbuff[7];
    
    //密码锁密码 注意 这里一定一定要加上字符串结束符'\0' 否则会出现字符串拼接的情况
    char passwd[4] = {'1','2','3','\0'};
    //输入的密码
    char passwd_test[4] = {'0','0','0','\0'};
    //记录密码输错次数
    unsigned int passwd_wrong_count = 0;
    //记录是否重头开始输入密码 0-是重头开始输入且没开始输入的  1-不是重头开始输入密码
    char passwd_flag = 0;
    
    //记录按键的值
    unsigned char key_num = 0;
    
    /***********************************************
    * 函数功能:自定义的系统初始化
    * 函数参数:无
    * 函数返回值:无
    ***********************************************/
    void sysInit(void)
    {
    	//LCD初始化
    	lcdInit();
    	//关闭所有的LED
    	changeAllLedByStateNumber(0);
    	//打开定时器7中断
    	HAL_TIM_Base_Start_IT(&htim7);
    	//打开串口的中断接收功能
    	HAL_UART_Receive_IT(&huart1,(uint8_t *)&Rxbuff,sizeof(Rxbuff)); 
    	//打开定时器2通道2的PWM输出功能
    	HAL_TIM_PWM_Start(&htim2,TIM_CHANNEL_2);
    }
    
    
    /***********************************************
    * 函数功能:系统工作逻辑函数
    * 函数参数:无
    * 函数返回值:无
    ***********************************************/
    void sysWork(void)
    {
    	//更新密码
    	getPasswdByUsart();
    	
    	//密码的相关操作以及显示密码界面
    	if(lcd_view_mod == 0)
    	{
    		keyProc();
    		PSDViewDisplay();
    		pwmWorkByFre(1000,400);
    	}
    	//显示输出界面
    	else if(lcd_view_mod == 1)
    	{
    		STAViewDisplay();
    		pwmWorkByFre(500,50);
    		passwd_flag = 0;
    		time7_count_start_flag = 1;
    	}
    	//LED灯显示
    	ledDisplay();
    	
    	//5秒时间到 定时器值以及标志位归零 界面显示密码界面 密码输错次数归零
    	if(time7_count>500)
    	{
    		time7_count = 0;
    		time7_count_start_flag = 0;
    		lcd_view_mod = 0;
    		passwd_wrong_count = 0;
    	}
    }
    
    /***非阻塞模式下定时器中断回调函数***/
    void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
    {
    	//定时器7的中断回调函数
    	if(htim->Instance == TIM7)
    	{
    		key_num = scanKey();
    		if(time7_count_start_flag)
    			time7_count++;
    	}
    }
    
    /***使用HAL_UART_Receive_IT中断接收数据 每次接收完成数据后就会执行该函数***/
    void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
    {
    	if(huart->Instance == USART1){
    		// 重新使能中断
    		HAL_UART_Receive_IT(huart,(uint8_t *)&Rxbuff,sizeof(Rxbuff)); 
    	}
    }
    
    /***********************************************
    * 函数功能:显示密码输入界面
    * 函数参数:无
    * 函数返回值:无
    ***********************************************/
    void PSDViewDisplay(void)
    {
    	LCD_DisplayStringLine(Line2,(uint8_t*)"       PSD");
    	//在第一次输入密码前显示
    	if(passwd_flag == 0)
    	{
    		sprintf(psd_txt,"    B1 :%c       ",'@');
    		LCD_DisplayStringLine(Line3,(uint8_t*)psd_txt);
    		sprintf(psd_txt,"    B2 :%c       ",'@');
    		LCD_DisplayStringLine(Line4,(uint8_t*)psd_txt);
    		sprintf(psd_txt,"    B3 :%c       ",'@');
    		LCD_DisplayStringLine(Line5,(uint8_t*)psd_txt);
    	}
    	else
    	{
    		sprintf(psd_txt,"    B1 :%c       ",passwd_test[0]);
    		LCD_DisplayStringLine(Line3,(uint8_t*)psd_txt);
    		sprintf(psd_txt,"    B2 :%c       ",passwd_test[1]);
    		LCD_DisplayStringLine(Line4,(uint8_t*)psd_txt);
    		sprintf(psd_txt,"    B3 :%c       ",passwd_test[2]);
    		LCD_DisplayStringLine(Line5,(uint8_t*)psd_txt);
    	}
    }
    
    /***********************************************
    * 函数功能:显示输出界面
    * 函数参数:无
    * 函数返回值:无
    ***********************************************/
    void STAViewDisplay(void)
    {
    	LCD_DisplayStringLine(Line2,(uint8_t*)"       STA");
    	LCD_DisplayStringLine(Line3,(uint8_t*)"    F :2000Hz");
    	LCD_DisplayStringLine(Line4,(uint8_t*)"    D :10%");
    	LCD_ClearLine(Line5);
    }
    
    /***********************************************
    * 函数功能:LED的相关显示
    * 函数参数:无
    * 函数返回值:无
    ***********************************************/
    void ledDisplay(void)
    {
    	static int LED2_state = 1;
    	//为了避免LED显示受到LCD影响 应该先关闭所有LED
    	changeAllLedByStateNumber(0);
    	
    	//密码输入错误三次  LED2以0.1秒间隔闪烁 (5秒后熄灭)
    	if(passwd_wrong_count>=3)
    	{
    		time7_count_start_flag = 1;
    		if(time7_count <= 500)
    		{
    			//定时器计数值刷新  时间到了 LED2状态需要反转
    			changeLedStateByLocation(LED2,LED2_state);
    			LED2_state ^= 1;
    		}
    	}
    	
    	//密码验证成功 LED1点亮5秒 (5秒后熄灭)
    	if(lcd_view_mod && time7_count <= 500)
    	{
    		changeLedStateByLocation(LED1,1);
    	}
    }
    
    /********************************************
    * 函数功能:
    * 函数参数:无
    * 函数返回值:无
    ********************************************/
    void getPasswdByUsart(void)
    {
    	//分析串口接收到数据  需:旧密码匹配才能够设置新密码
    	for(i=0;i<7;i++)
    	{
    		//判断输入是否合理
    		if(!((Rxbuff[i]>='0'&&Rxbuff[i]<='9') || Rxbuff[i]=='-'))
    			return ;
    		//判断旧密码是否正确
    		if(i < 3)
    		{
    			if(Rxbuff[i]!=passwd[i])
    				return ;
    		}
    		//设置新密码
    		if(i>3)
    		{
    			passwd[i%4] = Rxbuff[i];
    		}
    	}
    	//重置密码后也需要重置旧密码输入错误的次数
    	passwd_wrong_count = 0;
    }
    
    /********************************************
    * 函数功能:
    * 函数参数:无
    * 函数返回值:无
    ********************************************/
    void keyProc(void)
    {
    	//按键按下
    	if(key_num)
    	{
    		//按键B0-B2
    		if(key_num!= 3)
    		{
    			if(++passwd_test[key_num]>'9') passwd_test[key_num] = '0';
    			passwd_flag = 1;
    		}
    		//按键B3
    		else
    		{
    			//输入密码正确
    			if(!strcmp(passwd,passwd_test)) 
    			{
    				lcd_view_mod = 1;
    				passwd_wrong_count = 0;
    			}
    			//输入密码错误
    			else
    			{
    				passwd_flag = 0;
    				passwd_wrong_count++;
    			}
    		}
    	}
    }
    
    /****************************************
    * 函数功能:修改PWM的频率工作
    * 函数参数:
    *			unsigned int autoreloadDate:重装载值
    *			unsigned int compareDate:PWM的比较值
    * 函数返回值:无
    ****************************************/
    void pwmWorkByFre(unsigned int autoreloadDate,unsigned int compareDate)
    {
    	//设置重装载值
    	__HAL_TIM_SetAutoreload(&htim2,autoreloadDate-1);
    	__HAL_TIM_SetCompare(&htim2,TIM_CHANNEL_2,compareDate);
    }
    
    
    物联沃分享整理
    物联沃-IOTWORD物联网 » 蓝桥杯嵌入式|第十三届蓝桥杯嵌入式省赛程序设计试题及其题解

    发表评论