文章目录
- 原题展示
- 原题分析
- 原题题解
- LED相关
- LCD相关
- 按键相关
- ADC相关
- 定时器相关
- PWM
- 输入捕获
- 小结
- 文章福利
原题展示





原题分析
今年的第一场比赛绝对np,官方将串口直接省掉了,将其替换成很多小功能,如:切换计时、频率均匀变化、锁机制等等,总的来说本届赛题的难度提升了不少。
本届试题需要用到的功能模块有LCD、LED、按键、定时器输入捕获、定时器PWM输出、ADC获取,虽然这届试题模块简单,但是功能实现一点也不简单,感觉跟十二届省赛一样😂😂😂。
还值得注意的是:本届试题有三个地方需要计时,即模式切换、LED闪烁与长按键,,这可能是蓝桥杯为了提升难度的一个方向。(小编感觉这计时真的是恶心🤣🤣🤣)
原题题解
LED相关
通过查询产品手册知,LED的引脚为PC8~PC15,外加锁存器74HC573需要用到的引脚PD2。(由于题目要求除题目要求需要使用的LED外其他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 ledWork(void) { // 数据界面LED1电量 if(mod == 0) changeLedStateByLocation(LED1,1); else changeLedStateByLocation(LED1,0); // 切换期间 LED2闪烁 if(LED2Flag && sysCount[1] >= 100) { rollbackLedByLocation(LED2); sysCount[1] = 0; } else if(!LED2Flag) changeLedStateByLocation(LED2,0); // 锁定模式下 LED3电量 if(lock) changeLedStateByLocation(LED3,1); else changeLedStateByLocation(LED3,0); }LCD相关
样例代码
由于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," M=%c ",mTable[M]); LCD_DisplayStringLine(Line3,(u8*)temp);
为了操作LED与LCD显示方便,不让其相互干扰,小编这里对LCD进行了部分源码改写,使得每次LCD显示时不改变LED的显示状态,具体的方法各位可以点击查看【蓝桥杯】一文解决蓝桥杯嵌入式开发板(STM32G431RBT6)LCD与LED显示冲突问题,并讲述LCD翻转显示。
下面附上小编完成的LCD部分的详细代码:
/*************************************** * 函数功能:LCD显示数据 * 函数参数:无 * 函数返回值:无 ***************************************/ static void lcdDisplay() { char temp[20]; extern uint32_t cclValue ; // 数据显示界面 if(mod == 0) { LCD_DisplayStringLine(Line1,(u8*)" DATA "); sprintf(temp," M=%c ",mTable[M]); LCD_DisplayStringLine(Line3,(u8*)temp); sprintf(temp," P=%d%% ",pa1Zhan[1]); LCD_DisplayStringLine(Line4,(u8*)temp); sprintf(temp," V=%.1f ",V); LCD_DisplayStringLine(Line5,(u8*)temp); } // 参数显示界面 else if(mod == 1) { LCD_DisplayStringLine(Line1,(u8*)" PARA "); sprintf(temp," R=%d ",RK[0]); LCD_DisplayStringLine(Line3,(u8*)temp); sprintf(temp," K=%d ",RK[1]); LCD_DisplayStringLine(Line4,(u8*)temp); LCD_ClearLine(Line5); } // 统计界面 else if(mod == 2) { LCD_DisplayStringLine(Line1,(u8*)" RECD "); sprintf(temp," N=%d ",N); LCD_DisplayStringLine(Line3,(u8*)temp); sprintf(temp," MH=%.1f ",MH); LCD_DisplayStringLine(Line4,(u8*)temp); sprintf(temp," ML=%.1f ",ML); LCD_DisplayStringLine(Line5,(u8*)temp); } }(是不是非常简单粗暴。哈哈哈哈)
按键相关
通过查询产品手册知,开发板上的四个按键引脚为PB0~PB2、PA0。
CubeMX配置

代码样例
由于题中涉及到长按键,因此此处将不再使用延时消抖,可以使用三行按键完成长按键与短按键设计,其核心代码就是三行逻辑运算完成消抖等一系列操作,但是在轮询系统中可能会存在漏检的问题。其完整代码如下:
// 声明获取按键的状态值 #define getKeysState() ( HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0) // 获取按键状态 uint16_t state = getKeysState(); // 对按键的值进行异或处理 一次判断 uint8_t key_temp = 0xFF ^ (0xF0 | state); /*** 通过逻辑运算处理消抖 **/ // 按下状态 keyFalling = key_temp & (key_temp ^ keyOldState); // 松开状态 keyRising = ~key_temp & (key_temp ^ keyOldState); // 保存本次按键的值 keyOldState = key_temp; } signed char i = 0; // 按键扫描 keyRefresh(); // 按键按下 并且按键的值等于8也就是B4 if(keyFalling == 8) uwKeyTick = HAL_GetTick(); switch(keyRising) { // 按键B1 case 1: mod++; // 每次进去参数界面默认参数为R if(mod == 1) rkCount = 0; // 退出参数界面 if(mod != 1 ) { // 遍历参数 并且刷新 for(i=0;i sysCount[0] = 0; LED2Flag = 1; } // 参数界面 if(mod == 1) rkCount ^= 1; break; // 按键B3 case 4: // 参数界面 加1 if(mod == 1) if(++RKOld[rkCount] == 11) RKOld[rkCount] = 1; break; // 按键B4 case 8: // 数据界面 if(mod == 0 ) // 长按键锁住 if(HAL_GetTick() - uwKeyTick 2000) lock = 1; // 短按键解锁 else lock = 0; // 参数界面 减1 else if(mod == 1) if(--RKOld[rkCount] == 0) RKOld[rkCount] = 10; break; // 其他 default: break; } // 延时5秒切换 if(LED2Flag&& sysCount[0]=5000) { M ^= 1; // 切换次数增加 N++; LED2Flag = 0; } } unsigned int value = 0,i = 0; //开启转换ADC并且获取值 HAL_ADC_Start(hadc); for(i=0;i HAL_ADC_PollForConversion(hadc,10); value += HAL_ADC_GetValue(hadc); } //ADC值的转换 3.3V是电压 4096是ADC的精度为12位也就是2^12=4096 return value/10*3.3/4096; } if(0 __HAL_TIM_SetCompare(&htim2,TIM_CHANNEL_2,pa1F[M]*pa1Zhan[1]/100); pa1Zhan[0] = pa1Zhan[1]; } } // 保存TIMx_CCR的值 uint32_t cclValue = 0; // 定时器3时执行该段 if(htim-Instance == TIM3) { cclValue = __HAL_TIM_GET_COUNTER(&htim3); __HAL_TIM_SetCounter(&htim3, 0); f = 1000000 / cclValue; HAL_TIM_IC_Start_IT(&htim3, TIM_CHANNEL_2); } } V = (f*2*3.14*RK[0]*1.0)/(100*RK[1]); fOld = f; sysCount[2] = 0; }


