系列文章目录
备战蓝桥杯(3)——第八届蓝桥杯嵌入式省赛赛题实战
备战蓝桥杯(2)——第七届蓝桥杯嵌入式省赛赛题实战
备战蓝桥杯(1)——第六届蓝桥杯嵌入式省赛赛题实战
文章目录
前言
这次第九届蓝桥杯的省赛的题比第八届省赛的题简单了,但是我在做的时候也有很多小问题导致卡壳,也是将之前学习过的东西在做了一次小复习,同时这次省赛的长短按键也是比第一次接触到,很有收获。本次仍然是基于CT117E-M4平台,用的CUBEMX与keil开发。
一、第九届赛题蓝桥杯嵌入式省赛赛题展示
二、程序流程图
程序流程图是在程序写完之后画的,根据我编的程序的逻辑关系绘制的。这道题的逻辑关系不复杂,思路很清晰。
三、CUBEMX配置
这里的配置都是一些常规的配置,看着题目来,要啥配啥就行。这里要注意一下(也是我范的错误),官方给的EEPROM代码里面虽然有配置I2C的引脚(PA6,PA7),但是需要在CUBEMX里面配置一下,参数默认即可。我看了代码,我觉得是因为例程里的引脚没有初始化就直接拿来用了,所以如果没有在CUBEMX里面配置的话,是无法读取和写入EEPROM的。
四、各部分代码编写
先上主函数和变量定义:
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "tim.h"
#include "gpio.h"
/* USER CODE BEGIN Includes */
#include "lcd.h"
#include "i2c.h"
#include "stdio.h"
#include "string.h"
/* USER CODE END Includes */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
struct Time{
uint8_t hour;
uint8_t min;
uint8_t sec;
} TT_1;
/* USER CODE END PD */
int go_stop = 1; //计时开始/结束标志位
int time_cnt4 = 0; //定时器4计数值
int long_pushB2 = 0; //B2按键长按标志位
int long_pushB3 = 0; //B3按键长按标志位
uint8_t str[20];
uint8_t str1[10];
uint8_t str2[20] = " ** ";
uint8_t str3[20] = " ** ";
uint8_t str4[20] = " ** ";
unsigned char str5[10];
uint8_t re_loc = 1; //存储位置标志位,1至5
extern uint8_t time_cnt; //外部变量,在xxxxxit.c,定时器计数值
extern uint8_t sec_08; //外部变量,在xxxxxit.c,0.8秒计时标志位
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */
uint8_t x24c02_read(uint8_t address); //EEPROM读取函数
void x24c02_write(unsigned char address,unsigned char info); //EEPROM写入函数
void loc_change(void); //改变存储位置
void time_setting(void); //设置时间
void time_go_stop(void); //启动/结束定时器
void led_flash(void); //led灯闪烁
void PWM_gen(void); //PWM波产生函数
void res_time(void);//存储时间
void read_time(void);//读取时间
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
int main(void)
{
/* USER CODE BEGIN 1 */
TT_1.hour = 0;
TT_1.min = 0;
TT_1.sec = 0;
/* USER CODE END 1 */
HAL_Init();
/* USER CODE BEGIN Init */
I2CInit();
LCD_Init();
LCD_Clear(White);
LCD_SetBackColor(White);
LCD_SetTextColor(Black);
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_TIM2_Init();
MX_TIM3_Init();
MX_TIM4_Init();
/* USER CODE BEGIN 2 */
read_time();
sprintf((char *)str," %02d:%02d:%02d",TT_1.hour,TT_1.min,TT_1.sec);
LCD_DisplayStringLine(Line4,str);
sprintf((char *)str1," No %d",re_loc);
LCD_DisplayStringLine(Line1,str1);
LCD_DisplayStringLine(Line7," Standby");
/* USER CODE END 2 */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
loc_change();
time_setting();
time_go_stop();
//PWM_gen();
HAL_Delay(5);
}
/* USER CODE END 3 */
}
接下来文末按各个要求来编写各个功能的函数
1.存储位置改变功能的实现
这个功能是比较基础的,只是短按,很常规,但是要注意每次更换存储位置都要重新读取一次EEPROM的时间。代码如下:
void loc_change(void)
{
if(HAL_GPIO_ReadPin(KEY_B1_GPIO_Port,KEY_B1_Pin) == 0)
{
HAL_Delay(100);
if(HAL_GPIO_ReadPin(KEY_B1_GPIO_Port,KEY_B1_Pin) == 0)//判断按键是否按下
{
re_loc++;
if(re_loc > 5)//大于5变成1,循环
{
re_loc = 1;
}
read_time(); //变换了就要重新读取时间
sprintf((char *)str1," No %d",re_loc); //下面都是LCD显示的函数
LCD_DisplayStringLine(Line1,str1);
sprintf((char *)str," %02d:%02d:%02d",TT_1.hour,TT_1.min,TT_1.sec);
LCD_DisplayStringLine(Line4,str);
}
}
}
2.时间设置功能函数的编写
这一块是最复杂的,涉及了按键长短按,按键嵌套等,逻辑关系比较复杂,这里我讲解一下我的思路:首先,按下B2后程序进入一个死循环,在里面扫描判断是否有按键按下,根据不同的按键做出不同的反应,同时计算按键按下的时间,若大于0.8秒九认为是长按并做出相应动作。大体思路是这样,接下来上程序,更具我的思路和程序的注释应该可以理解我编程的思路。
void time_setting(void)
{
int i = 0;
if(HAL_GPIO_ReadPin(KEY_B2_GPIO_Port,KEY_B2_Pin) == 0)
{
HAL_Delay(150);
if(HAL_GPIO_ReadPin(KEY_B2_GPIO_Port,KEY_B2_Pin) == 0)//判断B2是否按下
{
LCD_DisplayStringLine(Line5,str4);
LCD_DisplayStringLine(Line7," Setting");//显示Setting
while(1) //进入死循环
{
if(HAL_GPIO_ReadPin(KEY_B2_GPIO_Port,KEY_B2_Pin) == 0)
{
HAL_Delay(100);
if(HAL_GPIO_ReadPin(KEY_B2_GPIO_Port,KEY_B2_Pin) == 0)//判断B2是否按下
{
i++; //这个是设置时分秒的标志位,0---秒,1---分,2---时
if(i > 2)
{
i = 0;
}
if( i == 0)
{
LCD_DisplayStringLine(Line5,str4);
}
if( i == 1)
{
LCD_DisplayStringLine(Line5,str3);
}
if( i == 2)
{
LCD_DisplayStringLine(Line5,str2);
}
while(HAL_GPIO_ReadPin(KEY_B2_GPIO_Port,KEY_B2_Pin) == 0)//如果一直按着
{
HAL_TIM_Base_Start_IT(&htim2);//打开定时器
if(sec_08 == 1)//判断0.8秒的标志位是否为1,为1就说明0.8秒到了
{
long_pushB2 = 1;//长按标志位置1
break;
}
}
HAL_TIM_Base_Stop_IT(&htim2);
time_cnt = 0;
sec_08 = 0;
}
}
if(HAL_GPIO_ReadPin(KEY_B3_GPIO_Port,KEY_B3_Pin) == 0)
{
HAL_Delay(100);
if(HAL_GPIO_ReadPin(KEY_B3_GPIO_Port,KEY_B3_Pin) == 0)//判断B3是否按下
{
if(i == 0 )//根据i的值来决定增长的数
{
TT_1.sec++;
}
if(i == 1 )
{
TT_1.min++;
}
if(i == 2 )
{
TT_1.hour++;
}
sprintf((char *)str," %02d:%02d:%02d",TT_1.hour,TT_1.min,TT_1.sec);
LCD_DisplayStringLine(Line4,str);
while(HAL_GPIO_ReadPin(KEY_B3_GPIO_Port,KEY_B3_Pin) == 0)//如果一直按着
{
HAL_TIM_Base_Start_IT(&htim2);//打开定时器
if(sec_08 == 1)//判断0.8秒的标志位是否为1,为1就说明0.8秒到了
{
long_pushB3 = 1;//长按标志位置1
break;
}
}
HAL_TIM_Base_Stop_IT(&htim2);
time_cnt = 0;
sec_08 = 0;
while(long_pushB3 == 1 && HAL_GPIO_ReadPin(KEY_B3_GPIO_Port,KEY_B3_Pin) == 0)//长按标志位为1并且一直按着
{
if(i == 0 )//根据i的值来决定快速增长的数
{
TT_1.sec++;
HAL_Delay(95);
if(TT_1.sec == 60)
{
TT_1.sec = 0;
TT_1.min++;
}
}
if(i == 1 )
{
TT_1.min++;
HAL_Delay(95);
if(TT_1.min == 60)
{
TT_1.min = 0;
TT_1.hour++;
}
}
if(i == 2 )
{
TT_1.hour++;
HAL_Delay(95);
if(TT_1.hour == 24)
{
TT_1.hour = 0;
}
}
sprintf((char *)str," %02d:%02d:%02d",TT_1.hour,TT_1.min,TT_1.sec);//更新时间
LCD_DisplayStringLine(Line4,str);
}
long_pushB3 = 0;
}
}
if(HAL_GPIO_ReadPin(KEY_B4_GPIO_Port,KEY_B4_Pin) == 0 || long_pushB2 == 1)
{
HAL_Delay(60);
if(HAL_GPIO_ReadPin(KEY_B4_GPIO_Port,KEY_B4_Pin) == 0 || long_pushB2 == 1)//判断B4是否按下或者B2是否长按
{
if(long_pushB2 == 0)//B2没有长按
{
LCD_DisplayStringLine(Line5," ");
LCD_DisplayStringLine(Line7," Running");
go_stop = 1;//启动定时器
break;
}
if(long_pushB2 == 1)//B2长按了
{
LCD_DisplayStringLine(Line5," ");
LCD_DisplayStringLine(Line7," Standby");
long_pushB2 = 0;
res_time();//将时间存到EEPROM里面
HAL_Delay(200);
break;
}
}
}
}
}
}
}
3.定时器启动/结束函数
这一块的逻辑关系比较简单,就是 按下按键启动定时器,再按一下停止定时器,长按就回到初始页面,也有按键嵌套和按键长短按。但是这一块我遇到了一个问题,因为倒计时我用的是定时器中断,但是我的时间使用结构体定义的,用extern外部调用时发现不好使,查了资料发现结构体用extern的话很麻烦(我太懒了),所以我用的中断回调函数:
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
用keil敲这个挺费劲,平时用vscode和clion的时候没这个感觉。定时器中断这块我就不多说了,直接上代码:
void time_go_stop(void)
{
if(HAL_GPIO_ReadPin(KEY_B4_GPIO_Port,KEY_B4_Pin) == 0)
{
HAL_Delay(150);
if(HAL_GPIO_ReadPin(KEY_B4_GPIO_Port,KEY_B4_Pin) == 0)//判断按键B4是否按下
{
while(1)//进入死循环
{
if(HAL_GPIO_ReadPin(KEY_B4_GPIO_Port,KEY_B4_Pin) == 0)
{
HAL_Delay(50);
if(HAL_GPIO_ReadPin(KEY_B4_GPIO_Port,KEY_B4_Pin) == 0)//判断按键4是否按下
{
go_stop = 1-go_stop;
while(HAL_GPIO_ReadPin(KEY_B4_GPIO_Port,KEY_B4_Pin) == 0)//如果一直按着
{
HAL_TIM_Base_Start_IT(&htim2);
if(sec_08 == 1)//判断0.8秒的标志位是否为1,为1就说明0.8秒到了
{
long_pushB4 = 1;//判断0.8秒的标志位是否为1,为1就说明0.8秒到了
break;
}
}
HAL_TIM_Base_Stop_IT(&htim2);
time_cnt = 0;
sec_08 = 0;
}
}
if(long_pushB4 == 1)//长按就退出,回到初始页面
{
long_pushB4 = 0;
LCD_DisplayStringLine(Line7," Standby");
sprintf((char *)str," %02d:%02d:%02d",TT_1.hour,TT_1.min,TT_1.sec);
LCD_DisplayStringLine(Line4,str);
HAL_Delay(150);
break;
}
if(go_stop == 1)//定时器启动
{
LCD_DisplayStringLine(Line7," Running");
HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_1);//启动PWM
led_flash();//开启LED闪烁
HAL_TIM_Base_Start_IT(&htim4);//启动定时器TIM4
if(TT_1.hour == 0 && TT_1.min==0 && TT_1.sec == 0)
{
HAL_TIM_Base_Start_IT(&htim4);
go_stop = 0;
}
sprintf((char *)str," %02d:%02d:%02d",TT_1.hour,TT_1.min,TT_1.sec);
LCD_DisplayStringLine(Line4,str);
}
if(go_stop == 0)//定时器结束
{
LCD_DisplayStringLine(Line7," Pause ");
HAL_TIM_Base_Stop_IT(&htim4);
time_cnt4 = 0;
HAL_TIM_PWM_Stop(&htim3,TIM_CHANNEL_1);
HAL_GPIO_WritePin(LED_GPIO_Port,LED_Pin,GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_RESET);
}
}
}
}
}
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)//中断回调函数
{
if(htim -> Instance == htim4.Instance)//如果时TIM4中断
{
time_cnt4++;
if(time_cnt4 == 100)//1秒计时到
{
TT_1.sec--;
if(TT_1.sec == 255)//uint8类型 0-1==255,补码运算
{
TT_1.sec = 59;
TT_1.min--;
}
if(TT_1.min == 255)
{
TT_1.min = 59;
TT_1.hour--;
}
time_cnt4 = 0;
}
}
}
至此项目主要的代码就写好啦,实测也没有问题,这题就圆满地结束了。
总结
- 使用EEPROM时,需要在CUBEMX配置好对应的I2C管脚,因为官方例程里没有IO口的初始化。
- 连续写入/读取EEPROM数据时,需要在两次操作间进行一个延时,10ms足矣,应为I2C通信会有应答,不延时的话就没法连续写入/读取。
- 对结构体的外部调用不太熟悉,需要多多查资料学习。
第九届省赛工程文件下载:下载链接