【蓝桥杯嵌入式】第十四届蓝桥杯嵌入式[模拟赛2]程序设计试题及详细题解

文章目录

  • 原题展示
  • 试题简析
  • 题解
  • LED相关
  • LCD相关
  • 按键相关
  • 定时器相关
  • 串口相关
  • 文章福利
  • 原题展示



    试题简析

    这次的模拟赛试题模块还是一些常见模块:LCDLED按键定时器以及串口,相对比较常规,相比于真正的省赛也比较简单。但是它适合刚刚学完各个模块需要做真题的同学,可以借此来巩固自己之前所学;对于已经能够掌握各个模块的同学也是有帮助的,就是平台上提供的测试环境咱可以白嫖一下😂😂😂。

    题解

    LED相关

    通过查询产品手册知,LED的引脚为PC8~PC15,外加锁存器74HC573需要用到的引脚PD2。(由于题目要求除LED1、LED2、LED3、LED4外的其他LED都处于熄灭状态,此处特意将所有的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);
    }
    

    试题要求的LED显示其条件都比较单一,在满足点亮条件时直接点亮,否则,就直接熄灭即可,至于闪烁的周期控制,可以借助与sysTick中断实现,如果就因此再开一个定时器就有点浪费资源了。(虽然小编以前经常这样子干🤣🤣🤣)

    小编写的LED工作逻辑函数:

    /***************************************
    * 函数功能:LED显示逻辑函数
    * 函数参数:无
    * 函数返回值:无
    ***************************************/
    static void ledPro(void)
    {
    	// PA1数据界面  LED1点亮
    	if(displayCount == 0 && flag != 1)
    		changeLedStateByLocation(LED1,1);
    	else if(displayCount == 1 && flag != 1)
    		changeLedStateByLocation(LED1,0);
    	// PA7数据界面  LED2点亮
    	if(displayCount == 1 && flag != 2)
    		changeLedStateByLocation(LED2,1);
    	else if(displayCount == 0 && flag != 2)
    		changeLedStateByLocation(LED2,0);
    	// 按键控制模式下
    	if(contrlMod == 0)
    		changeLedStateByLocation(LED3,1);
    	else
    		changeLedStateByLocation(LED3,0);
    	// PA1频率大于PA7  LED1以0.1秒闪烁
    	if(displayData[0].f > displayData[1].f)
    		flag = 1;
    	// PA1频率小于于PA7  LED2以0.1秒闪烁
    	else if(displayData[0].f < displayData[1].f)
    		flag = 2;
    	else
    		flag = 0;
    	// 对LED1 LED2进行闪烁
    	if(flag == 1 && count[0] >= 100)
    	{
    		rollbackLedByLocation(LED1);
    		count[0] = 0;
    	}
    	if(flag == 2 && count[0] >= 100)
    	{
    		rollbackLedByLocation(LED2);
    		count[0] = 0;
    	}
    }
    
    

    LCD相关

    样例代码
    为了更方便地显示数据,小编在程序中定义了一个结构体存储PWM的相关信息:

    // LCD显示数据的结果提
    struct data{
    	char name;
    	int f;
    	int d;
    };
    

    由于LCD的相关代码在官方给的比赛资源数据包中存在,因此,可以直接调用资源包中的.c、.h文件来完成LCD的相关初始化以及显示。这是一个简单的LCD初始化函数,其功能是将LCD显示屏初始化为一个背景色为黑色、字体颜色为白色的屏幕,具体代码如下:

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

    在显示时,可以借助于sprintf()函数将需要显示的数据格式成一个字符串,再在LCD上显示这个字符串。

    	char temp[20];
    	sprintf(temp,"       PA%d",displayData[displayCount].name); 
    	LCD_DisplayStringLine(Line1,(u8*)temp);
    

    为了操作LED与LCD显示方便,不让其相互干扰,小编这里对LCD进行了部分源码改写,使得每次LCD显示时不改变LED的显示状态,具体的方法各位可以点击查看【蓝桥杯】一文解决蓝桥杯嵌入式开发板(STM32G431RBT6)LCD与LED显示冲突问题,并讲述LCD翻转显示

    按键相关

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

    样例代码
    由于主板上的按键数量较少,因此小编这里的按键读取操作相对简单粗暴,其实现步骤为:

  • 步骤一:判断按键是否按下以及按键锁是否打开,在两者同时满足的情况下进入下一步;
  • 步骤二:关闭按键锁并且延时10ms,实现按键的延时消抖;
  • 步骤三:再次读取每个按键的值,判断按键按下的位置;
  • 步骤四:读取每个按键的状态,如果都处于松开状态就打开按键锁;
  • 具体代码实现:

    /*********************************************
     * 函数功能:按键扫描 含按键消抖 无长按短按设计
     * 函数参数:无
     * 函数返回值:按键的位置
     *            返回值说明: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;
    }
    

    调用上述函数后,即可判断每次按键按下的位置,之后的按键逻辑函数就相对简单了,大家一起来看看吧!

    /***************************************
    * 函数功能:按键逻辑函数
    * 函数参数:无
    * 函数返回值:无
    ***************************************/
    static void keyPro(void)
    {
    	// 按键扫描
    	unsigned char keyValue = scanKey();
    	switch(keyValue)
    	{
    		// B1 频率增加
    		case 1:
    			if(contrlMod) return ;
    			displayData[displayCount].f += 1000;
    			if( displayData[displayCount].f == 11000 )
    					displayData[displayCount].f = 1000;
    			break;
    		// B2 占空比增加
    		case 2:
    			if(contrlMod) return ;
    			displayData[displayCount].d += 10;
    			if( displayData[displayCount].d == 100 )
    				displayData[displayCount].d = 10;
    			break;
    		// B3 切换PA1 PA7界面
    		case 3:
    			if(contrlMod) return ;
    			displayCount ^= 1;
    			break;
    		// B4 切换模式
    		case 4:
    			contrlMod ^= 1;
    			break;
    		default :break;
    	}
    }
    

    定时器相关

    CubeMX配置
    本届试题定时器的功能为PWM输入,PWM输出时,如果大家不需要改变其占空比或者是频率,那么大家就可以不用再管理这些个定时器了。

    大家一起来看看定时器的PWM输出的配置吧!

    另一个定时器输出PWM波相关的配置是一样的,小编在此处就不过多展示啦!😁😁😁

    样例代码
    模拟题中,需要大家能够改变PWM输出的频率或占空比,相比大家对这个也非常感兴趣吧,那么大家一起来看看吧!😉😉😉

    /***************************************
    * 函数功能:按键逻辑函数
    * 函数参数:无
    * 函数返回值:无
    ***************************************/
    static void changePWM(void)
    {
    	/***设置重装载值 与 设置比较值 用于得到占空比***/
    
    	// 设置PA1 TIM2
    	if(oldData[0].f != displayData[0].f || oldData[0].d != displayData[0].d)
    	{
    		__HAL_TIM_SetAutoreload(&htim2,1000000/displayData[0].f-1);
    		
    		__HAL_TIM_SetCompare(&htim2,TIM_CHANNEL_2,(1000000/displayData[0].f+1)*displayData[0].d/100);
    		// 更新寄存器里的值
    //		TIM2->EGR = TIM_EGR_UG;
    		HAL_TIM_GenerateEvent(&htim2, TIM_EVENTSOURCE_UPDATE);
    		oldData[0].d = displayData[0].d;
    		oldData[0].f = displayData[0].f;
    	}
    	
    	// 设置PA7 TIM3
    	if(oldData[1].f != displayData[1].f || oldData[1].d != displayData[1].d)
    	{
    		__HAL_TIM_SetAutoreload(&htim3,1000000/displayData[1].f-1);
    		__HAL_TIM_SetCompare(&htim3,TIM_CHANNEL_2,(1000000/displayData[1].f+1)*displayData[1].d/100);
    		HAL_TIM_GenerateEvent(&htim3, TIM_EVENTSOURCE_UPDATE);
    		oldData[1].f = displayData[1].f;
    		oldData[1].d = displayData[1].d;
    	}
    }
    

    哦!对了,大家在使用定时器前还需要使用函数开启定时器的PWM功能嗷(其实这里我更喜欢说初始化) HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_2);

    还有一个重要的东西,小编在更改PWM周期后更新了定时器,因为小编发现不更新改变定时器输出PWM的频率是没啥用的。这里的更新有两种方法:

  • 方法一:直接更改定时器的寄存器——TIM2->EGR = TIM_EGR_UG;
  • 方法二:使用函数更新,这里我们使用的触发源为计时器更新事件——HAL_TIM_GenerateEvent(&htim2, TIM_EVENTSOURCE_UPDATE)
  • 串口相关

    CubeMX配置
        配置时一定一定记得改引脚!!!
    样例代码
    本程序中小编使用的是中断接收PC发送的数据其函数原型为:

    // 函数原型:
    HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
    // 参数解析:
    UART_HandleTypeDef *huart:串口通道;
    uint8_t *pData:存放数据的buff;
    uint16_t Size:一次接收数据的长度
    

    在使用时还需要使用该函数“中断初始化”,否则不能够进入中断接收数据;

    下面就是一个串口接收定长数据的demo:

    /**********************************************串口相关************************************/
    
    //定义一个串口信息的结构
    uint8_t _ucRxbuff[1];
    
    /***使用HAL_UART_Receive_IT中断接收数据 每次接收完成数据后就会执行该函数***/
    void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
    {	
    	if(huart->Instance == USART1){
    		// 重新使能中断		
    		HAL_UART_Receive_IT(huart,(uint8_t *)&_ucRxbuff,sizeof(_ucRxbuff)); 
    	}
    }
    

    本届试题要求的是定长数据,因此我们只要使用HAL_UART_Receive_IT(huart,(uint8_t *)&_ucRxbuff,sizeof(_ucRxbuff)); 触发中断即可,不需要改变串口接收数据的长度。

    题中要求串口功能不仅仅是接收数据这么简单,其还需要能够解析串口接收的数据,并且以此为指令将合适的结果发送给PC。下面就是小编写的一个简单的数据处理demo:

    /***************************************
    * 函数功能:串口逻辑函数
    * 函数参数:无
    * 函数返回值:无
    ***************************************/
    static void usartPro(void)
    {
    	// 判断是否接收到串口数据
    	if(strlen((char*)_ucRxbuff) == 0)
    		return ;
    	
    	// 按键控制模式下接收到串口数据
    	if(contrlMod == 0) 
    		HAL_UART_Transmit_DMA(&huart1,(uint8_t*)"KEY CONTROL\r\n",sizeof("KEY CONTROL\r\n")); 
    	
    	// 串口模式下接收到数据
    	if(contrlMod == 1)
    	{
    		if(strcmp((char*)_ucRxbuff,"@") == 0)
    			displayCount = 0;
    		else if(strcmp((char*)_ucRxbuff,"#") == 0)
    			displayCount = 1;
    		else 
    			HAL_UART_Transmit(&huart1,(uint8_t*)"ERROR\r\n",sizeof("ERROR\r\n"),20); 
    	}
    	memset(_ucRxbuff,0,sizeof((char*)_ucRxbuff));
    }
    

    大家注意:在使用串口接收时,还需要使用函数启动一次串口中断接收嗷,因为中断的第一次执行也是需要打开相关的嘛(这里我也习惯于说成初始化 哈哈哈哈哈)HAL_UART_Receive_IT(&huart1,(uint8_t *)&_ucRxbuff,sizeof(_ucRxbuff));

    (写在后面:小编在四梯平台上提交后当时没有分数哈,听说这平台测评需要很久😅😅😅因此这玩意仅做参考哈!)

    文章福利

    下边是小编个人整理出来免费的蓝桥杯嵌入式福利,有需要的童鞋可以自取哟!🤤🤤🤤
    省赛:

  • 【蓝桥杯嵌入式】第十一届蓝桥杯嵌入式省赛(第二场)程序设计试题及其题解
  • 【蓝桥杯嵌入式】第十二届蓝桥杯嵌入式省赛程序设计试题以及详细题解
  • 【蓝桥杯嵌入式】第十三届蓝桥杯嵌入式省赛程序设计试题及其详细题解
  • 【蓝桥杯嵌入式】第十三届蓝桥杯嵌入式省赛(第二场)程序设计试题及其题解
  • 【蓝桥杯嵌入式】第十三届蓝桥杯嵌入式省赛客观题以及详细题解
  • 国赛:

  • 【蓝桥杯嵌入式】第十二届蓝桥杯嵌入式国赛程序设计试题以及详细题解
  • 【蓝桥杯嵌入式】第十三届蓝桥杯嵌入式国赛客观题以及详细题解
  • 其他:

  • 【蓝桥杯嵌入式】第十四届蓝桥杯嵌入式[模拟赛2]客观题及详细题解
  • 【蓝桥杯】一文解决蓝桥杯嵌入式开发板(STM32G431RBT6)LCD与LED显示冲突问题,并讲述LCD翻转显示
  • 也欢迎大家留言或私信交流,共同进步哟!😉😉😉

    物联沃分享整理
    物联沃-IOTWORD物联网 » 【蓝桥杯嵌入式】第十四届蓝桥杯嵌入式[模拟赛2]程序设计试题及详细题解

    发表评论