第十三届蓝桥杯嵌入式省赛真题演练

您所在的位置:网站首页 按键式6位密码门锁怎么改密码 第十三届蓝桥杯嵌入式省赛真题演练

第十三届蓝桥杯嵌入式省赛真题演练

2024-04-09 02:24| 来源: 网络整理| 查看: 265

题目分析

在这里插入图片描述 接下来站在博主的视角来完成这次模拟题。首先,通读题目后我大致能大致能获取到如下信息:要完成一个密码锁的项目,用户通过串口通讯来设置三位密码,使用按键来输入密码。那我的思路先根据硬件框图搭出程序的大致框架,比如他这里的框图包括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 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 ++; //进入状态三 } else key_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 //结构体定义的有布尔数据类型,需要包含这个头文件 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 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码表先将字符@改为0 n_1[0] -= 17; n_1[0]++; //按键自增 if(n_1[0]>57) //超过9后变回@,@-0-9 n_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); //恢复频率为1k if(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亮5s HAL_Delay(5000); __HAL_TIM_SET_AUTORELOAD(&htim2,999); //恢复频率为1k led_dis(0x00); if(view > 1) view = 0; } else { wrong ++; //错误次数 if(wrong >= 3) { for(uint8_t i = 0; i 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亮5s view ++; LCD_Clear(Black); //输入正确则清屏切换界面 uwTick_pwm = uwTick; while(uwTick - uwTick_pwm wrong ++; //错误次数 if(wrong >= 3) { for(uint8_t i = 0; i


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3