题目再现
题目分析
通过阅读本届试题可知,其功能比较单一,除了试题中常客——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,100);
其参数1表示定时器的名称,参数2表示定时器通道,参数3表示占空比。(PWM中占空比的取值范围为0~100)
使用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,showData->frdP6);
__HAL_TIM_SetCompare(&htim17,TIM_CHANNEL_1,showData->frdP7);
}
//自动模式 电压值为0V
else if(showData->mode%2 == 0 && showData->vValue == 0)
{
__HAL_TIM_SetCompare(&htim3,TIM_CHANNEL_1,0);
__HAL_TIM_SetCompare(&htim17,TIM_CHANNEL_1,0);
}
//自动模式 电压值为3.3V
else if(showData->mode%2 == 0 && showData->vValue == 3.3)
{
__HAL_TIM_SetCompare(&htim3,TIM_CHANNEL_1,100);
__HAL_TIM_SetCompare(&htim17,TIM_CHANNEL_1,100);
}
}
/***********************************************
* 函数功能: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);
}
}
福利
下边是小编个人整理出来免费的蓝桥杯嵌入式福利,有需要的童鞋可以自取哟!🤤🤤🤤
也欢迎大家留言或私信交流,共同进步哟!😉😉😉
【蓝桥杯嵌入式】第十二届蓝桥杯嵌入式省赛程序设计试题以及详细题解
【蓝桥杯嵌入式】第十三届蓝桥杯嵌入式省赛程序设计试题及其详细题解
【蓝桥杯嵌入式】第十三届蓝桥杯嵌入式省赛(第二场)程序设计试题及其题解