原理图:
STM32CubeMX配置:
打开软件,根据原理图,选中所需的引脚:PB0、PB1、PB2、PA0,选择GPIO Input。
GPIO -> 选中按键的引脚 -> Pull-up。
然后选一个没有被占用的定时器,例如TIM3/TIM4,这里以TIM4作为例子:
关于PSC、ARR的设置,定时器每隔Tms进入中断一次:
F=主频/((PSC+1) * (ARR+1))
T=F/10;
例如,此处PSC=80-1,ARR=10000-1,F=80000000/(80*10000)=100Hz
T=10ms,定时器每隔10ms进入中断一次。
打开中断。
配置完成。
Keil5:
打开软件,新建两个文件,命名为interrupt.c interrupt.h,保存至自己项目对应的文件夹中。
添加至Group中:
在interrupt.c中,实现中断回调服务函数:
interrupt.c:
//interrupt.c
#include "interrupt.h"
struct key keys[4];
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance==TIM4)//判断中断是哪个定时器触发的
{
//读取每个按键的状态,0是被按下
keys[0].key_status=HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0);//PB0
keys[1].key_status=HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1);//PB1
keys[2].key_status=HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_2);//PB2
keys[3].key_status=HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0);//PA0
//利用状态机消抖按键
for(int i=0;i<4;i++)
{
switch(keys[i].judge_status)
{
case 0:
{
if(keys[i].key_status==0)//如果现在被按下,进入第二步
{
keys[i].judge_status=1;
keys[i].key_time=0;
}
}
break;
case 1:
{
if(keys[i].key_status==0)//如果现在还是被按下,进入第三步
{
keys[i].judge_status=2;
}else //否则回到第一步
{
keys[i].judge_status=0;
}
}
break;
case 2:
{
if(keys[i].key_status==0)//如果现在还是被按下,记录按下的时长,如果大于70次,判定为长按
{
keys[i].key_time++;
if(keys[i].key_time>=70)
{
keys[i].long_flag=1;
}
}else //当松开时,判断按下的时长,根据时长判断长按和短按的情况
{
keys[i].judge_status=0;
if(keys[i].key_tim<70)
{
keys[i].single_flag=1;
}
}
}
break;
}
}
}
}
interrupt.h:;
//interrupt.h
#ifndef _INTERRUPT_H_
#define _INTERRUPT_H_
#include "main.h"
#include "stdbool.h"
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim);
struct key
{
bool single_flag;//短按标志位
bool long_flag;//长按标志位
uint key_time;//按下时间
uint key_status;//当前状态是否被按下
uint judge_status;//进行到第几步?
};
#endif
在使用按键前,要先开启定时器TIM4,在main函数初始化位置添加如下代码:
HAL_TIM_Base_Start_IT(&htim4);
在main函数中添加头文件,利用extern关键字,将刚刚定义的keys,引到main.c中。
#include "interrupt.h"
extern struct key keys[4];
现在我们根据题目要求,编写代码实现界面切换的功能:
我们定义一个变量view,来表示当前的界面是“车位显示界面”还是“费率设置界面”,通过按键B1,改变变量view的值,再根据view的值来显示对应的界面:
定义一个函数key_process(),来判断按键的情况:
void key_process(void)
{
if(keys[0].single_flag==1)
{
view=(view+1)%2;
keys[0].single_flag=0;
}
}
定义一个函数lcd_process(),来根据view的值来显示对应界面:
void lcd_process(void)
{
char text[30];
if(view==0)
{
Clear();
sprintf(text," Data ");
LCD_DisplayStringLine(Line1,(unsigned char*)text);
sprintf(text," CNBR:%d ",CNBR);
LCD_DisplayStringLine(Line3,(unsigned char*)text);
sprintf(text," VNBR:%d ",VNBR);
LCD_DisplayStringLine(Line5,(unsigned char*)text);
sprintf(text," IDLE:%d ",IDLE);
LCD_DisplayStringLine(Line7,(unsigned char*)text);
}else if(view==1)
{
Clear();
sprintf(text," Para ");
LCD_DisplayStringLine(Line1,(unsigned char*)text);
sprintf(text," CNBR:%.2f ",P_CNBR);
LCD_DisplayStringLine(Line3,(unsigned char*)text);
sprintf(text," VNBR:%.2f ",P_VNBR);
LCD_DisplayStringLine(Line5,(unsigned char*)text);
}
}
Clear()函数的作用是在切换显示界面的时候,进行清屏,并且不闪烁:
void Clear(void)
{
if(keys[0].single_flag==1)
{
LCD_Clear(Black);
keys[0].single_flag=0;
}
}
在while循环中调用:
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
lcd_process();
key_process();
}
编译,下载。
下载完成后,显示的初始界面是Data界面,当我按下B1按键的时候就跳转到了Para界面: