题目分析


接下来站在博主的视角来完成这次模拟题。首先,通读题目后我大致能大致能获取到如下信息:要完成一个密码锁的项目,用户通过串口通讯来设置三位密码,使用按键来输入密码。那我的思路先根据硬件框图搭出程序的大致框架,比如他这里的框图包括LED指示灯、LCD显示、串口通讯、按键输入功能,至于控制信号输出我我不知道所指,但第一感觉是输出PWM信号,那就可以先不管,等后面完善题目具体要求的时候看到了再补充就行。
开始搭建框架,打开Cube根据手册配置相应的GPIO。新建一个工程,搜索选择所用的STM32G431RB芯片→在RCC中打开时钟,然后配置时钟树→工程文件设置(文件名等)→以硬件框图模块依次配置GPIO口,打开串口1→生成文件


在生成的工程文件目录下,新建一个文件夹命名为bsp,在其中新建各模块的.c和.h文件。

在工程中添加一个文件夹bsp并将上面的.c文件添加进文件夹。在各模块.c文件中包含其.h文件,在.h文件中把国际框架写好,编译。接下来依次写各模块的代码,依次编译下载验证。
首先key.c,在cube中配置TIM4(前面备赛的经验),

#include "key.h"struct KEY_STA key_sta[4] = {0};      //定义四个按键void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) //定时器溢出中断回调函数
{if(htim->Instance == TIM4)        //判断是不是定时器4导致的中断{key_sta[0].status = HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0);       //先将按键GPIO的电平读出key_sta[1].status = HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1);key_sta[2].status = HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_2);key_sta[3].status = HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0);}for(uint8_t i = 0;i < 4;i++)      //分三个步骤来消抖{switch(key_sta[i].step){case 0:{if(key_sta[i].status == 0)     //若检测到电平为低key_sta[i].step ++;             //进入状态二}break;case 1:{if(key_sta[i].status == 0)          //若到了下次定时器中断电平仍未低{key_sta[i].flag = 1;         //则断定是按键按下key_sta[i].step ++;             //进入状态三}    elsekey_sta[i].step = 0 ;      //若不是按键按下则回到状态一}break;case 2:{if(key_sta[i].status == 1)      //当按键松开后key_sta[i].step = 0 ;          //回到状态一}break;}}
}
#ifndef _KEY_H
#define _KEY_H
#include "main.h"
#include <stdbool.h>          //结构体定义的有布尔数据类型,需要包含这个头文件struct KEY_STA          //定义一个储存按键状态的结构体
{bool flag;         //按键按下标志uint8_t status;         //电平值uint8_t step;          //状态};#endif

led.c文件,用一个函数来指定对应led亮灭

#include "led.h"void led_dis(uint8_t num)      //定义一个选中led亮灯的函数,num为8位
{HAL_GPIO_WritePin(GPIOC,GPIO_PIN_All,GPIO_PIN_SET);        //拉高全部引脚,让led都熄灭HAL_GPIO_WritePin(GPIOC,num << 8,GPIO_PIN_RESET);          //因为led是PC8-15也就是高八位有效,所以先让num左移八位再拉低HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_SET);       //锁存HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_RESET);         }
#ifndef _LED_H
#define _LED_H
#include "main.h"void led_dis(uint8_t num);#endif

有了按键和led模块,就可以结合起来验证一下实物是不是按照我们的想法实现的。先在main.c中包含所有模块的.h文件,发现警告没有找到文件(我打开了实时检测),要在魔术棒里把我们的文件所在路径添加到include path下。将

     if(key_sta[0].flag == 1){led_dis(0x80);key_sta[0].flag = 0;}

加入while/* USER CODE BEGIN 3 */下,这里遇到key_sta的报错,因为这是在key.c中定义的结构体数组,要想在main.c中使用,需要在/* USER CODE BEGIN PTD */下加入extern struct KEY_STA key_sta[4];。编译无误,下载,显示NO LINK,需要在魔术棒的debug中选择。

再次下载,发现按下B1后LD8没有亮。发现在主函数中没有开启定时器中断,在/* USER CODE BEGIN 2 */下加入HAL_TIM_Base_Start_IT(&htim4);。编译下载,发现8个led上电全亮,在cube中将PC8-15设置为高电平后得以解决。
接下来写LCD显示模块。在比赛时给的数据包里会有LCD的例程,我们可以直接调用封装好的函数。首先将数据包里关于LCD的模块函数lcd.c/lcd.h/fonts.h复制到bsp文件夹中,并在工程文件中添加。打开其中LCD的工程文件,阅读代码查漏补缺,将初始化代码LCD_Init();加入main.c中。在/* USER CODE BEGIN WHILE */下加入 LCD_Clear(Black); LCD_SetBackColor(Black); LCD_SetTextColor(White);来初始化屏幕界面。按题目所给要求,需要显示两个界面以区分输入密码前后,具体可见代码注释。

#include "dis_pro.h"uint8_t view = 0;         //定义一个变量来控制界面的切换
uint8_t temp[30] = {0}; void dis_pro()
{if(view == 0)            //通过按键来切换{sprintf((char *)temp,"       P S D");           //将待显示数据放入数组LCD_DisplayStringLine(Line2,temp);                  //显示数组中的数据sprintf((char *)temp,"    B 1 :");LCD_DisplayStringLine(Line4,temp);    sprintf((char *)temp,"    B 2 :");LCD_DisplayStringLine(Line5,temp);  sprintf((char *)temp,"    B 3 :");LCD_DisplayStringLine(Line6,temp);          }if(view == 1){sprintf((char *)temp,"       S T A");LCD_DisplayStringLine(Line2,temp);sprintf((char *)temp,"    F :");LCD_DisplayStringLine(Line4,temp);  sprintf((char *)temp,"    D :");}
}
#ifndef _DIS_PRO_H
#define _DIS_PRO_H#include "stdio.h"
#include "main.h"
#include "lcd.h"void dis_pro(void);#endif

当然,如果有报错说某某函数或变量没有定义,那就在相应的.h文件中添加相关声明或即可。当然在key.c中还定义了一个按键功能函数

void key_pro()
{if(key_sta[3].flag == 1){view ++;if(view > 1)view = 0;LCD_Clear(Black);key_sta[3].flag = 0;}
}

下载验证,结果可观。
接下来写串口通信模块。

#include "uart.h"uint8_t dat = 0;     //创建变量来接收单个数据
uint8_t data[30] = {0};                //创建数组来储存接收的数据
uint8_t pointer = 0;                   //创建一个光标来指示当前所在数组的位置uint8_t a[2] = {'1'};                //接收串口设置的密码的第一位
uint8_t b[2] = {'2'};            //接收串口设置的密码的第二位
uint8_t c[2] = {'3'};                //接收串口设置的密码的第三位
uint8_t d[5] = {0};void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)     //串口接收中断回调函数
{data[pointer++] = dat;          //将当前接收到的单个数据存入数组HAL_UART_Receive_IT(huart,&dat,1);         //开启串口接收中断继续接收}void uart_pro()  //定义一个函数实现串口的具体功能
{if(pointer > 0){if(pointer == 7)          //若接收到七个数据,就把数据分放便于后续比较{sscanf((char*)data,"%3s-%1s%1s%1s",d,a,b,c); //将data这个数组中的数据按照后面定义的格式分别放入dabc四个数组中}else{char text[30];sprintf(text,"ERROR");HAL_UART_Transmit(&huart1,(uint8_t *)text,strlen(text),50);//若未接收完则向用户发送一个erro}pointer = 0;     //光标回首位memset(data,0,30);           //数组归零}
}
#ifndef _UART_H
#define _UART_H#include "main.h"
#include "stdio.h"
#include "usart.h"
#include "string.h"
void uart_pro(void);
#endif

前面分析的基本模块现已基本实现,接下来仔细去看题目的要求,对各模块的具体功能要求。
首先,按键输入@,0-9几个数来模拟输入密码的过程,与串口设置的密码进行比对,B4按下时若密码输入正确,则切换界面。初始状态下三位密码显示皆为@。主要需要修改的就是按键模块。

void key_pro()
{if(key_sta[0].flag == 1)         //B1按键{if(n_1[0] == '@')               //根据ASCII码表先将字符@改为0n_1[0] -= 17;n_1[0]++;                   //按键自增if(n_1[0]>57)              //超过9后变回@,@-0-9n_1[0] = '@';key_sta[0].flag = 0;          //标志位归零}if(key_sta[1].flag == 1)      //B2{if(n_2[0] == '@')n_2[0] -= 17;n_2[0]++;if(n_2[0]>57)n_2[0] = '@';key_sta[1].flag = 0;}if(key_sta[2].flag == 1)           //B3{if(n_3[0] == '@')n_3[0] -= 17;n_3[0]++;if(n_3[0]>57)n_3[0] = '@';key_sta[2].flag = 0;}if(key_sta[3].flag == 1)       //B4{if((a[0] == n_1[0]) && (b[0] == n_2[0]) && (c[0] == n_3[0]))         //判断密码是否输入正确{view ++;             //切换界面if(view > 1)view = 0;LCD_Clear(Black);key_sta[3].flag = 0;}}
}

下载发现初始密码可以解锁,但无法通过按键进行修改。问题出在写串口的时候,没有在main.c中开启串口接收中断,在/* USER CODE BEGIN 2 */ 下加入HAL_UART_Receive_IT(&huart1,&dat,1);开启即可。验证无误。
题目要求,输入密码成功后要有信号输出,前面看不懂的那个模块用处就在于此。使用PA1完成脉冲输出功能,没输入正确密码前PA1输出的是1KHz的方波信号,密码正确则变为2KHz 占空比为10%的方波信号。持续五秒后,又变回原来的信号以及密码输入界面。
那我们去cube中先配置PA1为TIM_CH2。因为初始为1KHz,分频80,重装载1000。而因为我们要在LCD上显示频率和占空比,计算频率可以随便用上升沿或下降沿,捕获到的相当于PWM波一个周期的时间,用80000000(时钟频率)/80(分频系数)/time(捕获到的时间)=频率。而占空比则是利用下降沿的时间除以整个周期时间,则为占空比。所以可以配置一个输入捕获(如PB4),利用其中两个通道来分别捕获上升沿和下降沿的时间来计算频率值。

再次生成代码并打开工程文件。在key.c中写入输入捕获中断回调函数。

void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)     //输入捕获中断回调函数
{if(htim->Instance == TIM3){time_1 = HAL_TIM_ReadCapturedValue(htim,TIM_CHANNEL_1);       //读出计时值time_2 = HAL_TIM_ReadCapturedValue(htim,TIM_CHANNEL_2);__HAL_TIM_SetCounter(htim,0);            //计时值归零frq_1 = (80000000/80)/time_1;       //计算当前频率zk = (time_2/time_1)*100;              //计算占空比(下降沿时间除以上升沿时间)HAL_TIM_IC_Start_IT(htim,TIM_CHANNEL_1);         //打开输入捕获定时器HAL_TIM_IC_Start_IT(htim,TIM_CHANNEL_2); }
}

有了之前的教训,不要忘记在主函数中分别开启PWM信号和输入捕获中断。这里先不去管具体的功能,将frq_1和zk放入dis_pro.c中显示,想测试一下这个信号输出模块是否能成功。果不其然,代码逻辑是1000hz的频率,但显示出来就是233这个数,占空比为0。结果问题出在对变量的定义上,这里的代码没有给出变量定义,但文末我会附上整个工程文件。我写代码有个问题是在变量的定义上,不会去细想,这个数据的本质,只一心想着这个代码逻辑,问就是uint8_t类型,也不会报错,但出了问题也确实给我整嘛了。这里,time应该是double类型,而频率应该是uint16_t类型。
根据题目要求,密码输入正确后,频率会从4k变为2k且占空比为10%。在key_pro()函数中增改如下代码

 if(key_sta[3].flag == 1)          //B4{if((a[0] == n_1[0]) && (b[0] == n_2[0]) && (c[0] == n_3[0]))         //判断密码是否输入正确{LCD_Clear(Black);              //输入正确则清屏切换界面view ++;__HAL_TIM_SET_AUTORELOAD(&htim2,530);        //设置频率为2k__HAL_TIM_SET_COMPARE(&htim2,TIM_CHANNEL_2,50);            //设置占空比HAL_Delay(5000);__HAL_TIM_SET_AUTORELOAD(&htim2,999);            //恢复频率为1kif(view > 1)view = 0;key_sta[3].flag = 0;}}

编译无误,下载验证。再有,题目有信号输出精度要求,但是我按照这样的思路写出来他的频率已经超出精度范围了。我猜测是因为tim3开了两个通道的的缘故,因为一开始我没写占空比的测量,也就只开了一个捕获上升沿的通道,频率精度是正常的。这里可以调高函数中的参数值(如上面的代码__HAL_TIM_SET_AUTORELOAD的参数我设置为了530)。
最后,LED功能部分。题目要求密码验证成功,LED1点亮五秒后熄灭,而如果暑促密码>=3次,LED2以100毫秒的间隔闪烁,五秒后熄灭。在key_pro()函数中增改如下代码

 if(key_sta[3].flag == 1)          //B4{if((a[0] == n_1[0]) && (b[0] == n_2[0]) && (c[0] == n_3[0]))         //判断密码是否输入正确{LCD_Clear(Black);              //输入正确则清屏切换界面view ++;__HAL_TIM_SET_AUTORELOAD(&htim2,530);        //设置频率为2k__HAL_TIM_SET_COMPARE(&htim2,TIM_CHANNEL_2,50);            //设置占空比led_dis(0x01);           //LED1亮5sHAL_Delay(5000);__HAL_TIM_SET_AUTORELOAD(&htim2,999);          //恢复频率为1kled_dis(0x00);if(view > 1)view = 0;}else{wrong ++;           //错误次数if(wrong >= 3){for(uint8_t i = 0; i < 50; i++)          //闪烁五秒{led_dis(0x02);HAL_Delay(100);led_dis(0x00);HAL_Delay(100);}}}key_sta[3].flag = 0;}

下载后的现象是,输入密码正确后会黑屏五秒,led会亮。问题就出在HAL_Delay()这个函数中,他利用的原理就是让程序一直卡在一个while循环中等待,导致其他的操作执行不了。具体可以参照这个视频的讲解。这里的修改也是参考了视频所述的代码逻辑,代码如下:

 if(key_sta[3].flag == 1)          //B4{if((a[0] == n_1[0]) && (b[0] == n_2[0]) && (c[0] == n_3[0]))         //判断密码是否输入正确{wrong = 0;__HAL_TIM_SET_AUTORELOAD(&htim2,530);       //设置频率为2k__HAL_TIM_SET_COMPARE(&htim2,TIM_CHANNEL_2,50);            //设置占空比led_dis(0x01);           //LED1亮5sview ++;LCD_Clear(Black);                //输入正确则清屏切换界面uwTick_pwm = uwTick;while(uwTick - uwTick_pwm < 5000)//定义了一个变量,uwTick属于配置文件中定义好的变量,每隔1ms增加1{dis_pro();           //一开始uwTick_pwm为0,所以这样就相当于延时了5s}__HAL_TIM_SET_AUTORELOAD(&htim2,999);led_dis(0x00);__HAL_TIM_SET_COMPARE(&htim2,TIM_CHANNEL_2,0);LCD_Clear(Black);               //输入正确则清屏切换界面view ++;if(view > 1)view = 0;n_1[0] = '@';n_2[0] = '@';n_3[0] = '@';key_sta[3].flag = 0;    }else{wrong ++;           //错误次数if(wrong >= 3){for(uint8_t i = 0; i < 25; i++)          //闪烁五秒{led_dis(0x02);HAL_Delay(100);led_dis(0x00);HAL_Delay(100);}}key_sta[3].flag = 0;}

这样的操作解决了黑屏问题,但是在延时5s回到密码输入界面时,频率应改回1k,经测验频率不太对。这里我是试错来设置参数值调出的1k和50%的占空比,一直找不到问题在哪里,欢迎小伙伴来一起讨论。
全部文件附上:
链接:https://pan.baidu.com/s/1MFJNhFBoXwUAyG4MwiPW0A?pwd=olm6
提取码:olm6

第十三届蓝桥杯嵌入式省赛真题演练——密码锁相关推荐

  1. 第十三届“蓝桥杯”单片机省赛——程序设计题

    第十三届"蓝桥杯"单片机省赛--程序设计题 在比赛时候编写,代码仅供参考 如有不足,多多指教 1.题目 2.代码 main.c程序 #include "reg52.h&q ...

  2. 英雄之盾-第11届蓝桥杯Scratch省赛真题第4题

    [导读]:超平老师计划推出Scratch蓝桥杯真题解析100讲,这是超平老师解读Scratch蓝桥真题系列的第58讲. 第11届蓝桥杯青少年组省赛原定于2020年3月7日举行,因疫情延期到2020年6 ...

  3. 小猫打螃蟹-第10届蓝桥杯Scratch省赛真题第2题

    [导读]:超平老师计划推出Scratch蓝桥杯真题解析100讲,这是超平老师解读Scratch蓝桥真题系列的第67讲. 第10届蓝桥杯青少年组省赛于2019年3月24日举行,形式为线下考试.Scrat ...

  4. 捉迷藏之二-第10届蓝桥杯Scratch国赛真题第6题程序2

    本文同步发表于"超平的编程课"公众号,更多教程,可移步至公众号. [导读]:超平老师计划推出Scratch蓝桥杯真题解析100讲,这是超平老师解读Scratch蓝桥真题系列的第15 ...

  5. 击鼓游戏-第10届蓝桥杯Scratch省赛真题第7题

    [导读]:超平老师计划推出Scratch蓝桥杯真题解析100讲,这是超平老师解读Scratch蓝桥真题系列的第62讲. 第10届蓝桥杯青少年组省赛于2019年3月24日举行,形式为线下考试.Scrat ...

  6. 跑酷游戏-第13届蓝桥杯Scratch国赛真题第3题

    [导读]:超平老师计划推出Scratch蓝桥杯真题解析100讲,这是超平老师解读Scratch蓝桥真题系列的第27讲. 第13届蓝桥杯青少年组国赛于2022年5月29日举行,形式为在线考试.Scrat ...

  7. 自动驾驶-第10届蓝桥杯Scratch省赛真题第5题

    [导读]:超平老师计划推出Scratch蓝桥杯真题解析100讲,这是超平老师解读Scratch蓝桥真题系列的第64讲. 第10届蓝桥杯青少年组省赛于2019年3月24日举行,形式为线下考试.Scrat ...

  8. 魔法师盖城墙-第11届蓝桥杯Scratch国赛真题第4题

    本文同步发表于"超平的编程课"公众号,更多教程,可移步至公众号. [导读]:超平老师计划推出Scratch蓝桥杯真题解析100讲,这是超平老师解读Scratch蓝桥真题系列的第10 ...

  9. 对对碰-第11届蓝桥杯Scratch省赛真题第6题

    [导读]:超平老师计划推出Scratch蓝桥杯真题解析100讲,这是超平老师解读Scratch蓝桥真题系列的第56讲. 第11届蓝桥杯青少年组省赛原定于2020年3月7日举行,因疫情延期到2020年6 ...

最新文章

  1. 测试ATS对某个特定域名下文件处理效果的方法
  2. 2013 年最不可思议的 10 个硬件开源项目
  3. 【Android工具】Cx文件管理器,Cx File Explorer安卓samba客户端
  4. 遍历文件夹下所有文件,编辑删除
  5. Oracle Certified Master For Java EE 5/6 Comes
  6. JS打开摄像头并截图上传
  7. Spring 官方又孵化了个顶级项目,或将改变前后端API现状!
  8. 【算法笔记】图文结合彻底搞懂后缀数组
  9. 九宫格拼图游戏的总结
  10. EDA学习1.3之开关的封装
  11. if三种实现方式(if if else if else if else switch case for while)
  12. 关于Storyboard故事板, Segue 的使用,storyboard的拆分
  13. Django-bootstrap3插件搭建Django+Bootstrap网站
  14. 一名合格的数据分析师,需要满足哪些条件
  15. 鸿蒙系统通知栏怎么清理,教你两招 彻底关掉手机通知栏烦人的无用通知
  16. 基因相关性——字符串入门
  17. kibana基本操作
  18. 武汉 华为 android,【武汉华为手机大全】武汉华为手机报价及图片大全-列表版-ZOL中关村在线...
  19. 【计算几何】求三角面和直线交点
  20. C# 数据库大作业-学生管理系统

热门文章

  1. ens33线缆被拔出
  2. 彻底理解面向对象,看完这一篇就够了
  3. 4Sum -- LeetCode
  4. HTML DOM selectedIndex 属性
  5. PHP socket使用实例
  6. 漏洞扫描工具Nikto的使用
  7. 《怪物猎人:崛起》伞鸟套装造型曝光!猫狗也能穿
  8. fastjson 序列化 输出空字段
  9. Windows11初体验——感觉图标变圆润、界面更清爽了
  10. dagger android,dagger android 学习(二):AndroidInjector的使用