【蓝桥杯嵌入式】第十一届蓝桥杯嵌入式省赛(第二场)程序设计试题及其题解

题目再现

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
题目分析

    通过阅读本届试题可知,其功能比较单一,除了试题中常客——LED、LCD、按键三巨头外,还包含了定时器的PWM以及ADC读取这两个部分,考察的重点在于对定时器产生PWM的应用以及如何修改PWM的占空比,完全消化这套试题后对PWM的理解会更加精进。

详细题解

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

  • 由于LCD与LED有部分引脚是共用的,因此初始化完成LCD后,最好手动关闭LED
  • 由于每次LCD显示的长度可能不同,因此在本次显示前,要不先清屏,要不跟上次显示一样长

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;
}

LCD模块

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

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

定时器输出PWM波

CubeMX配置
在这里插入图片描述
在这里插入图片描述
题目中要求选手修改占空比,那么我们直接使用HAL库提供的函数修改即可。

__HAL_TIM_SetCompare(&htim3,TIM_CHANNEL_1,1000);

其参数1表示定时器的名称,参数2表示定时器通道,参数3表示比较值。

使用ADC读取电位器R37的值

    由于题目中所用的开发板是STM32F103RBT6,但是小编实际使用的是STM32G431RBT6,所以会存在引脚不一样的情况。(因为蓝桥杯改革了,所以现在的比赛使用的开发板会跟之前有点不同)
    在G431开发板上,与电位器R37相连接的引脚是PB15,因此我们就使用PB15作为ADC的引脚来初始化。

CubeMX配置
在这里插入图片描述
读取ADC的值

    在这里需要注意的是,如果需要连续读取ADC的值,在相邻的两次读取中,最好留一点缓冲时间,否则会对数据产生影响。

/*******************************************************************
* 函数功能:获取ADC的值
* 函数参数:
* 			ADC_HandleTypeDef *hadc:ADC的通道值
* 函数返回值:
* 			double:转换后的ADC值
*******************************************************************/
double getADC(ADC_HandleTypeDef *hadc)
{
	unsigned int value = 0;
	//开启转换ADC并且获取值
	HAL_ADC_Start(hadc);
	value = HAL_ADC_GetValue(hadc);
	//ADC值的转换 3.3V是电压 4096是ADC的精度为12位也就是2^12=4096
	return value*3.3/4096;
}

完整的工作配置文件

config.c文件

    sysInit函数是自定义外加的系统配置文件,sysWork函数是配置的系统工作函数。使用时,直接将sysInit函数添加到CubeMx初始化后,将sysWork函数在while(1)函数中调用。

#include "config.h"

//定义存储显示数据的结构体
struct displayData*showData;

//用于计时
unsigned int count = 0;

/***********************************************
* 函数功能:自定义的系统初始化函数
* 函数参数:无
* 函数返回值:无
***********************************************/
void sysInit(void)
{
	//LCD初始化
	lcdInit();
	//关闭LED
	changeAllLedByStateNumber(OFF);
	
	//打开定时器产生PWM的通道
	HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_1);
	HAL_TIM_PWM_Start(&htim17,TIM_CHANNEL_1);
	
	//初始化结构体
	showData = (struct displayData*)malloc(sizeof(struct displayData));
	showData->frdP6 = 10;
	showData->frdP7 = 10;
	showData->mode = 0;
	showData->vValue = 0;
	showData->page = 0;
}

/***********************************************
* 函数功能:自定义的系统工作函数
* 函数参数:无
* 函数返回值:无
***********************************************/
void sysWork(void)
{
	unsigned char keyNumber = scanKey();
	switch(keyNumber)
	{
		//切换显示界面
		case 1:
			showData->page++;
			break;
		//手动模式  参数界面 修改PA6的参数
		case 2:
			if(showData->mode%2==1 && showData->page%2==1)
				showData->frdP6 += 10;
			if(showData->frdP6 == 100)
				showData->frdP6 = 10;
			break;
		//手动模式  参数界面 修改PA7的参数
		case 3:
			if(showData->mode%2==1 && showData->page%2==1)
				showData->frdP7 += 10;
			if(showData->frdP7 == 100)
				showData->frdP7 = 10;
			break;
		//切换模式
		case 4:
			showData->mode++;
			break;
		default:break;
	}
	
	//获取ADC的值 降低获取ADC值的频率
	if(count++ %5 == 0)
		showData->vValue = getADC(&hadc2);
	//切换PWM的频率
	changePwmFrd();
	//LCD显示
	display();
	//LED显示
	ledWork();
}

/***********************************************
* 函数功能:更改PWM占空比函数
* 函数参数:无
* 函数返回值:无
***********************************************/
void changePwmFrd(void)
{
	//手动模式
	if(showData->mode%2 == 1)
	{
		__HAL_TIM_SetCompare(&htim3,TIM_CHANNEL_1,100*showData->frdP6);
		__HAL_TIM_SetCompare(&htim17,TIM_CHANNEL_1,50*showData->frdP7);  
	}
	//自动模式 电压值为0V
	else if(showData->mode%2 == 0 && showData->vValue == 0)
	{
		__HAL_TIM_SetCompare(&htim3,TIM_CHANNEL_1,10000);
		__HAL_TIM_SetCompare(&htim17,TIM_CHANNEL_1,5000);
	}
	//自动模式  电压值为3.3V 
	else if(showData->mode%2 == 0 && showData->vValue == 3.3)
	{
		__HAL_TIM_SetCompare(&htim3,TIM_CHANNEL_1,0);
		__HAL_TIM_SetCompare(&htim17,TIM_CHANNEL_1,0);
	}
}

/***********************************************
* 函数功能:LED工作函数
* 函数参数:无
* 函数返回值:无
***********************************************/
void ledWork(void)
{
	//关闭所有的LED灯
	changeAllLedByStateNumber(OFF);
	
	//自动模式
	if(showData->mode%2 == 0)
		changeLedStateByLocation(LED1,ON);
	
	//数据界面
	if( showData->page%2 == 0 )
		changeLedStateByLocation(LED2,ON);
}

/***********************************************
* 函数功能:显示函数
* 函数参数:无
* 函数返回值:无
***********************************************/
void display(void)
{
	char temp[10];
	//数据显示界面
	if( showData->page%2 == 0 )
	{
		LCD_DisplayStringLine(Line0,(uint8_t*)"       Data");
		sprintf(temp,"     V:%.2fV",showData->vValue);
		LCD_DisplayStringLine(Line2,(uint8_t*)temp);
		//自动模式
		if(showData->mode%2 == 0)
			LCD_DisplayStringLine(Line4,(uint8_t*)"     Mode:AUTO     ");
		//手动模式
		else
			LCD_DisplayStringLine(Line4,(uint8_t*)"     Mode:MANU     ");		
	}
	//显示参数界面
	else if( showData->page%2 == 1)
	{
		LCD_DisplayStringLine(Line0,(uint8_t*)"       Pata");
		sprintf(temp,"    PA6:%d%%        ",showData->frdP6);
		LCD_DisplayStringLine(Line2,(uint8_t*)temp);
		sprintf(temp,"    PA7:%d%%        ",showData->frdP7);
		LCD_DisplayStringLine(Line4,(uint8_t*)temp);
	}
}

福利

下边是小编个人整理出来免费的蓝桥杯嵌入式福利,有需要的童鞋可以自取哟!🤤🤤🤤
也欢迎大家留言或私信交流,共同进步哟!😉😉😉

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

  • 27
    点赞
  • 123
    收藏
    觉得还不错? 一键收藏
  • 12
    评论
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值