ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

关于矩阵键盘、一个工程中UI界面的键盘&显示的逻辑

2021-07-24 11:30:02  阅读:156  来源: 互联网

标签:return 矩阵 键盘 Item while UI 按键 GPIO currState


上一篇文章中,转载了一种上流的方法,使用状态机、函数指针、结构体、结构体指针等,异常的上流,但是有些bug。

目录


首先记录一下调试的过程。

矩阵调试与其他思路

调试

首先源代码的主函数中

u8 Key_Value;

int main(void)
{
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_USART1_UART_Init();
  MX_TIM2_Init();
  MX_TIM3_Init();
 while (1)
  {
  
	if(App_LEAVE != currState->loop())  //(检测是否可以离开当前的状态,不可以就会推出当前状态)
	{                                 //就是调用currstate的loop函数,得到返回值
		return 0;
	}
	// Button released   (检测到了松开,就执行动作)
	if(currState == &colScanningPressed)
	{
		printf("KEY:%d \r\n",Key_Value); 
	}
	// Next state(执行完当前状态就应该把状态转换,到下个状态)
	//(可以看出,currState,作为一个指针结构体,就是一个指针的作用,存储当前状态用于判断)
	currState = currState == &rowScanning ? &colScanning : currState == &colScanning ? &colScanningPressed : &rowScanning;
	//如果切换到下一个状态,则调用其 enter() 函数
	currState->enter();	
	}
	

这时候,在

uint8_t colScanningLoop()
{
	if(GPIO_PIN_RESET == checkPressedLow(COL0_GPIO_Port,COL0_Pin))
	{
//		Key_Value |= 0;printf("KEY:%d \r\n",Key_Value); 
    return App_LEAVE;
	}
	if(GPIO_PIN_RESET == checkPressedLow(COL1_GPIO_Port,COL1_Pin))
	{
		Key_Value |= 1; //低两位存储列值
//		printf("KEY:%d \r\n",Key_Value); 
    return App_LEAVE;
	}
	if(GPIO_PIN_RESET == checkPressedLow(COL2_GPIO_Port,COL2_Pin))
	{
		Key_Value |= 2; //低两位存储列值
//		printf("KEY:%d \r\n",Key_Value); 
    return App_LEAVE;
	}
	if(GPIO_PIN_RESET == checkPressedLow(COL3_GPIO_Port,COL3_Pin))
	{
		Key_Value |= 3; //低两位存储列值
//		printf("KEY:%d \r\n",Key_Value); 
    return App_LEAVE;
	}
	 return App_STAY;
}

这里的printf已经可以正常输出了,但是main中的printf就是没有,或者说,需要好多次才有概率触发一次。
于是调试一波,emmm,if(App_LEAVE != currState->loop()) //(检测是否可以离开当前的状态,不可以就会推出当前状态) { //就是调用currstate的loop函数,得到返回值 return 0; }
这里直接写在main函数中的, return都是直接退出函数的,所以这里只要没有检测到按键就会一直退出main函数重进, 于是改一下
把if改成这个while循环即可。

while(App_LEAVE != currState->loop());

但是还有问题,就是现在按一次可能会输出好几次,也有按键消抖了,但还是不稳。
那么有一种解决方法是,在if(currState == &colScanningPressed) { printf("KEY:%d \r\n",Key_Value); } 这里面发送完之后加一个while判断,只要按键值不改变就不退出,但是这样就存在一个问题,假如我就是要按两次同一按键就会出现bug

所以嘛,拆分了一下这一句currState = currState == &rowScanning ? &colScanning : currState == &colScanning ? &colScanningPressed : &rowScanning;
如下:

	currState=&rowScanning;
		currState->enter();	
	while(App_LEAVE != currState->loop());
//	if(0)  //(检测是否可以离开当前的状态,不可以就会推出当前状态)
//	{                                 //就是调用currstate的loop函数,得到返回值
//		return 0;
//	}
	// Button released   (检测到了松开,就执行动作)
		
	currState= &colScanning;
		currState->enter();	
	currState->loop();
		
	currState= &colScanningPressed;
		currState->enter();	

	while(App_LEAVE != currState->loop());
	printf("KEY:%d \r\n",Key_Value); 

把那个条件语句改成了三步实现,但还是没有解决这个问题。

最后想想,按键消抖那里可能不太严格,还得回那里去看看。

//按键消抖要做好
GPIO_PinState checkPressedLow(GPIO_TypeDef *port, uint16_t pin)
{
		if(GPIO_PIN_RESET == HAL_GPIO_ReadPin(port, pin))
		{
			 HAL_Delay(DEBOUNCE_DELAY);
			if(GPIO_PIN_RESET == HAL_GPIO_ReadPin(port, pin))
			return HAL_GPIO_ReadPin(port, pin);
		}
		if(GPIO_PIN_SET == HAL_GPIO_ReadPin(port, pin))
		{
			 HAL_Delay(DEBOUNCE_DELAY);
			if(GPIO_PIN_SET == HAL_GPIO_ReadPin(port, pin))
			return HAL_GPIO_ReadPin(port, pin);
		}
		 return GPIO_PIN_SET;
}
	

经过这个改善之后终于可以了。

补充一点改善代码简洁度

对于这个函数


uint8_t colScanningPressedLoop()
{
	int col = 3 & Key_Value; //读取列值 
	if(0 == col)
	{
		if(GPIO_PIN_SET == HAL_GPIO_ReadPin(COL0_GPIO_Port, COL0_Pin)) 
		{
			return App_LEAVE;
		}
	}
	else if(1 == col)
	{
		if(GPIO_PIN_SET == HAL_GPIO_ReadPin(COL1_GPIO_Port, COL1_Pin)) 
		{
			return App_LEAVE;
		}
	}
	else if(2 == col)
	{
		if(GPIO_PIN_SET == HAL_GPIO_ReadPin(COL2_GPIO_Port, COL2_Pin)) 
		{
			return App_LEAVE;
		}
	}
	else 
	{
		if(GPIO_PIN_SET == HAL_GPIO_ReadPin(COL3_GPIO_Port, COL3_Pin)) 
		{
			return App_LEAVE;
		}
	}

	return App_STAY;
}
其实写复杂了,可以改成switch case
会更简洁一点

其他矩阵键盘扫描的思路

很经典的一种扫描思路不需要说了,就是用8根线,一个字节的值,查表,这种可以之后再写一下。这里主要想来一下暴力的方式。

暴力扫描

就是轮询每一个按键对应的IO口的值,比如
while等待结束再return

最后两版代码放在这里

另外记录一下UI界面的思路

一直想理一下,在做一个项目的时候,人机交互这种界面怎么去做,怎么实现这个控制逻辑。下面理一下思路

main函数while扫描1(两个按键多种界面)

main函数中的while(1)就不干别的了,只做按键扫描和屏幕显示。
以一个工程为例。首先我们只有两个按键,有三种模式,每种模式(模式123),用一个变量mode表示,每个模式有若干种子模式(子模式123…),用一个变量zmode表示,我在显示屏的右上角就会显示Mab(其中ab分别为模式与子模式的号),比如M13就是第1种模式的第3种子模式。

那好只有两个按键,怎么控制呢,
用伪代码的形式来表示

mode 模式
zmode 子模式
while(1){
	if(key1 ==0)
	{
		mode++;
		mode %=3;这样控制在3中模式之内
	}
	if(key2 == 0)
	{
		zmode ++;
		zmode %= 3; 
	}
	switch(mode)
	{
		case 1: {
				switch(zmode)
					{
						case 1:屏幕显示函数等操作
						case 2:
						case 3:
	这里需要注意的一点是,这三个case不加break,
	那么就可以实现的效果是,
	模式1可以显示三种,模式2只显示两种,模式3只显示一种东西,就是这种效果。
					}
				}
		case 2:{ switch(zmode) }
		...
	}
	
}

就是这样一种嵌套的switch来实现,前面只扫描按键值,
后面来做渲染,然后不停的循环

问题1——改进

现在,我在模式M13,然后改到了模式2会自动是子模式3,即M23。因为他们两个是分离的两个变量。

那假如每次mode的值改了的时候,zmode都会从1开始 ,那是不是可以解决呢,
NO,现在就有另外一个问题。
本来是M13,现在调成了模式M21,等我重新调回模式1 的时候,又变成了模式M11,这样就不够人性化,所以,我们进一步改进,使用二维数组

i,j
modetabel[i][j]={初始化值} Mij

while(1){
	if(key1 == 0)
	{
		i++;
		if(i>maxi)
		i = 0;
		
	}
	if(key2 == 0)
	{
		j++;
		if(j>maxj)
		j = 0;
	}
	switch(mode[])
	{
		case 1: {
				switch(zmode)
					{
						case 1:屏幕显示函数等操作
						case 2:
						case 3:
	这里需要注意的一点是,这三个case不加break,
	那么就可以实现的效果是,
	模式1可以显示三种,模式2只显示两种,模式3只显示一种东西,就是这种效果。
					}
				}
		case 2:{ switch(zmode) }
		...
	}
	
}

就是这样一种嵌套的switch来实现,前面只扫描按键值,
后面来做渲染,然后不停的循环

补充2

现在两个按键,还可以加入长按的逻辑,加入第三个逻辑,增加一个变量 Pmode,显示长按的界面

if(key1 == 0)
	{
	while(key == 0)
		{time++;
		delay
		if delay >= maxtime
		Pmode = 1;
		}
		此时这个长按的界面,如果进入之前我的模式是M12
		但此时不需要调模式,如果此时单击按键1的话,
		回去的时候可能就是M2 或者M3,主模式就变了,
		所以此时在逻辑上需要改一下
		if(Pmode == 0)
		{
		只有Pmode = 0才会执行加一的操作
		i++;
		if(i>maxi)
		i = 0;
		}
	}
然后,就可以在刚才的switch case外面再套一个,
对Pmode进行switch case 
同时此时也可以继续使用key2 ,继续进行switch的嵌套 

main函数while扫描2(两个按键多种界面)

这种就是,至少需要四个按键,
key1,进入一个状态,不停地等待,卡在这里不出去,里面key2进行设置,key3进行确定,key4进行退出。
示例


int main(void)
{
	//u16 kkk=445*5;
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);  //矢
	delay_init();
	LED_Init();
	Beep_Init();	
	uart_init();
	KEY_Init();
	EXTIX_Init();
	GPIO_Stepper_Init();
	
	TIM2_Int_Init(MOTOR1_FRE,0);
  TIM3_Int_Init(MOTOR1_FRE,0);
  TIM4_Int_Init(MOTOR1_FRE,0);
	TIM5_Int_Init(MOTOR1_FRE,0);
	MOTOR1_PUL_H;
	OLED_GPIO_Init();
  OLED_Init();
	
	Item.i=1;Item.j1=1;Item.j2=1;Item.k=1;Item.k2=3;Item.L=0,Item.D=50;
	
	OLED_CLS();
	OLED_P8x16Str(0,0,"Reseting...");
	delay_ms(200);
	
	
	Stepper_Reset();
	
	
	delay_ms(200);
	OLED_CLS();
	OLED_P8x16Str(0,0,"WELCOME...");
	
	while(1)
	{		
		if(!KEY2){
			show_setting();
			delay_ms(300);
			while(1){
				if(!KEY2){
					Item.i++;
					if(Item.i>2)Item.i=1;
					OLED_CLS();
					show_setting();
					delay_ms(300);
				}
				if(!KEY3){
					if(Item.i==1)Item.j1++;
					if(Item.i==2)Item.j2++;
					if(Item.j1>2)Item.j1=1;
					if(Item.j2>4)Item.j2=1;
					show_setting();
					delay_ms(300);
				}
				if(!KEY4){
					if(Item.i==1){Item.k++;delay_ms(300);}
					if(Item.i==2){Item.k2++;delay_ms(300);}
					if(Item.k>2)Item.k=1;
					if(Item.k2>15)Item.k2=1;
					show_setting();
				}
				if(!KEY5){
					if(Item.i==1){Item.L++;}
					if(Item.j2==3){Item.D+=10;}
					if(Item.L>15)Item.L=0;
					if(Item.D>90)Item.D=0;
					show_setting();
					delay_ms(300);
				}
				if(!KEY1){
					OLED_P8x16Str(0,0,"   WAITING   ");
					break;
				}
			}
		}
		

广播的方式

使用全局变量,不断的按键扫描,然后更改全局变量的值,在按键动作函数中进行switchcase执行各种函数

另外,也可以使用外部中断,中断中什么也不干,就只给一个变量赋值,然后在主函数中不断的进行判断,比如中断中使key1= 1
main中
if(key1 = 1)
{
key1 = 0

}

标签:return,矩阵,键盘,Item,while,UI,按键,GPIO,currState
来源: https://blog.csdn.net/lgyLGY35/article/details/119047232

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有