首页 > 其他分享 >新手如何学会单片机?(五)XC8P9530按键扫描以及功能处理

新手如何学会单片机?(五)XC8P9530按键扫描以及功能处理

时间:2025-01-16 17:00:28浏览次数:3  
标签:__ XC8P9530 KEY 消抖 单片机 HL 新手 BIT define

        单片机的按键扫描方法以及原理,百度有太多的案例解释,我这里就不做太多的赘述,只教大家如何利用XC8P9530配置输入上拉下拉,然后配合时基检测的方法。一般IO口按键检测要高电平有效,那IO口就要配置成输入下拉,反之则配置成输入上拉,一般我们单片机检测按键低电平有效的比较多,我们就以此来介绍。

        按键的消抖程序需要定义消抖的标志位,这个标志位的最终结果就是按键的最终状态。直接上一次的工程加上定义:

#define   BIT_1MS			BIT_FLAG.b0   //1ms的时基
#define   BIT_FADE_HL		BIT_FLAG.b1	  //呼吸方向
#define   BIT_KEY_HL		BIT_FLAG.b2   //按键的状态 0:按下 1:松开

        再定义好按键的IO口位号,这里定义在PORT6_3;

/****************************************/
// --------端口定义--------------------
/****************************************/ 
#define io_ledout       PORT6_2
#define io_keyin        PORT6_3

        再将PORT6_3配置为输入上拉,P6PH = 0xf7;P6CON = 0X08; 

        按键消抖的时间一般是10ms左右,通常我们实际产品直接消抖20ms就可以了,消抖范围一般10ms~100ms都是可以的。按键扫描的程序我们可以在1ms的时基里面写,再用一个变量进行消抖的计数,从松开的状态(BIT_KEY_HL = 1)检测到io_keyin为低电平的时候或者从按下的状态(BIT_KEY_HL = 0)检测到io_keyin为高电平的时候进行计数,超过20就认为消抖完成,将上次的BIT_KEY_HL翻转就可以了。下面直接展示代码:

unsigned char key_count; //用于消抖的计数
void ScanKey(void)
{
	if(((BIT_KEY_HL == 1)&&(io_keyin == 0))||((BIT_KEY_HL == 0)&&(io_keyin == 1)))//松开的状态检测到io_keyin为低电平的时候或者从按下的状态检测到io_keyin为高电平的时候
	{
		key_count++;           //消抖计数
		if(key_count >= 20)    //消抖计数满20
		{
			if(BIT_KEY_HL)  //如果上次是松开的状态,
			{
				BIT_KEY_HL = 0;   //消抖之后就是按下的状态
			}
			else			//如果上次是按下的状态,
			{
				BIT_KEY_HL = 1;   //消抖之后就是松开的状态
			}
		}	
	}
	else
	{
		key_count = 0;    //当前按键IO口io_keyin的电平和状态标志位BIT_KEY_HL的高低一致消抖计数清零(可能是一直松开的状态,也可能是一直按下的状态)
	}
}

        需要把此函数放在1ms的时基里扫描,这样keycount消抖20次才是20ms。

如何验证按键扫描的正确性呢?我们可以在消抖完成后加一个IO输出,反应当前的按键状态。我们用io_ledout(PORT6_2)来验证,验证代码如下:

        通过仿真验证可以看到当按键一直按下的时候,io_ledout为低电平输出,当按键松开的时候,io_ledout为高电平输出。说明按键的判断是OK的。

       一般的产品应用按键扫描包括长按和短按,那么长按是如何判断的呢?其实也很简单,按键的状态标志位BIT_KEY_HL反应了当前的按键状态,如果判断BIT_KEY_HL一直为0,就意味着按键一直处于按下状态,我们从BIT_KEY_HL由1转向0开始计时,超过一段时间就认为是长按,从而触发某一个动作。基于这个思路,代码如下:

        定义一个标志位BIT_KEY_HOLD,BIT_KEY_HL由 1 转向 0 的时候置 1

#define   BIT_KEY_HOLD		BIT_FLAG.b3   //长按的维持计时 1:开始计时,0:不计时
unsigned char key_count;			//用于消抖的计数
unsigned int key_long_count;		//用于长按的计数
void ScanKey(void)
{
	if(((BIT_KEY_HL == 1)&&(io_keyin == 0))||((BIT_KEY_HL == 0)&&(io_keyin == 1)))//松开的状态检测到io_keyin为低电平的时候或者从按?碌淖刺觳獾絠o_keyin为高电平的时候
	{
		key_count++;           //消抖计数
		if(key_count >= 20)    //消抖计数满20
		{
			if(BIT_KEY_HL)  //如果上次是松开的状态,
			{
				BIT_KEY_HL = 0;   //消抖之后就是按下的状态
				BIT_KEY_HOLD = 1;
				key_long_count = 0; //重新计时
			}
			else			//如果上次是按下的状态,
			{
				BIT_KEY_HL = 1;   //消抖之后就是松开的状态
				if(BIT_KEY_HOLD)	//按下长按计时到不到2秒认为是短按
				{
					BIT_KEY_HOLD = 0;
					/*
						可以放置短按的处理代码
					*/
                    io_ledout = !io_ledout;
				}
			}
		}	
	}
	else
	{
		key_count = 0;    //当前按键IO口io_keyin的电平和状态标志位BIT_KEY_HL的高低一致消抖计数清零(可能是一直松开的状态,也可能是一直?聪碌淖刺?
	}
	if(BIT_KEY_HOLD)  //没有触发长按的时候
	{	
		key_long_count ++;	
		if(key_long_count >= 2000)  //长按小于2秒
		{
			BIT_KEY_HOLD = 0;     //计时到2秒清零
			/*
				可以放置长按的处理代码
			*/
			io_ledout = !io_ledout;
		}
	}
}

        为了验证长按和短按的区分判断有没有问题,我们可以在长按和短按之后,将io_ledout翻转,来验证代码的正确性。经过验证,是正确的。

        为了巩固之前学习内容,接下来可以延申一下做一个小项目,先看功能需求:

        下面是完整的参考代码:

.C文件代码

#include "XC8P9530.h"
#include "XJ_Define.h"
//===================================//
//void file_clrRam(void);
//void file_init(void);
//void file_project_init(void);
//===================================//
//中断服务程序
//===================================//
unsigned char time_1ms;//计数产生1ms时基
unsigned char cyc_count; //PWM周期满量程
unsigned char pwm_duty;   //pwm占空比
unsigned char fade_time;  //用于改变呼吸的周期 
unsigned char sys_mode;   //挡位变量
void int_isr(void) __interrupt 
{    	
	__asm__("org 0x08");			//中断入口地址			
	PUSH(_A_BUFF,_R3_BUFF);			//中断入栈保护
//=========TC0中断程序===============//
	if(TC0IF)						//判断TC0IF是否为1
	{
		TC0C	+= 156;				// 100us
		INTF	= 0xfe;				//清TC0中断标志位
		time_1ms++;
		if(time_1ms>=10)
		{
			time_1ms = 0;
			BIT_1MS = 1;
		}
		cyc_count++;
		if(cyc_count>=100)
		{
			cyc_count = 0;
		}
		if(cyc_count<pwm_duty)
		{
			io_ledout = 1;
		}
		else
		{
			io_ledout = 0;
		}
	}	 
	INTF = 0x09;					//清未用到的标志位,置1无效,只可清零
	POP(_A_BUFF,_R3_BUFF);			//中断出栈保护恢复
}
//===================================//
//初始化清RAM:10H~3FH
//===================================//
void file_clrRam()
{
	for(RSR=0xD0;RSR<0xFF;RSR++)	//RAM清零
									//RSR的高两位默认只读为1					
	{IAR = 0;}
	 IAR = 0;
}
//===================================//
//端口初始化
//===================================//
void file_init(void)
{
	IOCP_W(WDTCON,0x00);			//WDT 使能控制寄存器	
	CONTW(0x80);					//TC0使能 8分频
	TC0C = 156;						//1/2 * 8 * (256-6) = 1000us	公式:1/IRC频率 * 预分频 *(256-初值)
	PORT6 = 0;						//P6口输出低
	IOCP_W(P6CON,0x08);				//P6口设为输出,1输入
	IOCP_W(P6PH,0xf7);				//bit5-bit0对应P65-P60端口上拉,1禁止
	IOCP_W(P6PD,0xff);				//bit5-bit0对应P65-P60端口下拉,1禁止
	IOCP_W(INTE,0x01);				//中断使能控制寄存器 
	INTF = 0x0;						//清TC0中断标志位 
}

/*系统变量的初始化*/
void Sys_init(void)
{
	sys_mode = 3;
}

//===================================//
//MAIN主程序
//===================================//
unsigned char key_count;			//用于消抖的计数
unsigned int key_long_count;		//用于长按的计数
/*按键的长短按扫描函数*/
void ScanKey(void)
{
	if(((BIT_KEY_HL == 1)&&(io_keyin == 0))||((BIT_KEY_HL == 0)&&(io_keyin == 1)))//松开的状态检测到io_keyin为低电平的时候或者从按?碌淖刺觳獾絠o_keyin为高电平的时候
	{
		key_count++;           //消抖计数
		if(key_count >= 20)    //消抖计数满20
		{
			if(BIT_KEY_HL)  //如果上次是松开的状态,
			{
				BIT_KEY_HL = 0;   //消抖之后就是按下的状态
				BIT_KEY_HOLD = 1;
				key_long_count = 0; //重新计时
			}
			else			//如果上次是按下的状态,
			{
				BIT_KEY_HL = 1;   //消抖之后就是松开的状态
				if(BIT_KEY_HOLD)	//按下长按计时到不到2秒认为是短按
				{
					BIT_KEY_HOLD = 0;
					/*
						可以放置短按的处理代码
					*/
					if(!BIT_SYS_OFF)  //如果是开机的状态,才能操作
					{
						sys_mode++;    //切换下一挡位
						if(sys_mode >= 4)  //如果挡位超过第四挡,循环到第一挡
						{
							sys_mode = 0;
						}
					}
				}	
			}
		}	
	}
	else
	{
		key_count = 0;    //当前按键IO口io_keyin的电平和状态标志位BIT_KEY_HL的高低一致消抖计数清零(可能是一直松开的状态,也可能是一直?聪碌淖刺?
	}
	if(BIT_KEY_HOLD)  //没有触发长按的时候
	{	
		key_long_count ++;	
		if(key_long_count >= 2000)  //长按小于2秒
		{
			BIT_KEY_HOLD = 0;     //计时到2秒清零
			/*
				可以放置长按的处理代码
			*/
			if(BIT_SYS_OFF)		//如果长按之前上次是关机的状态
			{
				BIT_SYS_OFF = 0;	//开机
				if(sys_mode == 3)   //如果是呼吸挡
				{
					pwm_duty = 0;	  //初始化呼吸模式从最低亮度开始
					BIT_FADE_HL = 0;  //呼吸方向为增长趋势
				}
			}
			else				//如果长按之前上次是开机的状态
			{
				BIT_SYS_OFF = 1;	//关机
			}
		}
	}
}


/*led输出的处理函数*/
void HandleLedOut(void)   
{
	if(BIT_SYS_OFF)		//关机
	{
		pwm_duty = 0;	//灭灯
	}
	else				//开机
	{
		if(sys_mode == 0)  //第一挡
		{
			pwm_duty = 80; //80%
		}
		else if(sys_mode == 1)  //第二挡
		{
			pwm_duty = 40; //40%
		}
		else if(sys_mode == 2)  //第三挡
		{
			pwm_duty = 5; //5%
		}
		else if(sys_mode == 3)	//第四挡
		{
			fade_time++;
			if(fade_time >= 20) //40ms加减一次占空比
			{
				fade_time = 0;
				if(!BIT_FADE_HL)
				{
					if(pwm_duty<100)
					{
						pwm_duty++;
					}
					else
					{
						BIT_FADE_HL = 1;  //改变呼吸方向
					}
				}
				else
				{
					if(pwm_duty>0)
					{
						pwm_duty--;
					}
					else
					{
						BIT_FADE_HL = 0;  //改变呼吸方向
					}
				}
			}
		}
	}
}


/*主函数*/
void main()
{
	file_clrRam();					//清RAM
	file_init();					//io寄存器初始化
	Sys_init();						//系统变量初始化
	EI();							//打开总中断
	while(1)
	{
		if(BIT_1MS)
		{
			BIT_1MS = 0;
			ScanKey();
			HandleLedOut();
		}
	}
}

.h文件代码

typedef union{
	struct{
		unsigned b0			:1;
		unsigned b1			:1;
		unsigned b2			:1;
		unsigned b3			:1;
		unsigned b4			:1;
		unsigned b5			:1;
		unsigned b6			:1;
		unsigned b7			:1;
	};
}ob8;
ob8 BIT_FLAG;
#define   BIT_1MS			BIT_FLAG.b0   //1ms的时基
#define   BIT_FADE_HL		BIT_FLAG.b1	  //呼吸方向  0:增长趋势 1:下降趋势
#define   BIT_KEY_HL		BIT_FLAG.b2   //按键的状态 0:按下 1:松开
#define   BIT_KEY_HOLD		BIT_FLAG.b3   //长按的维持计时 1:开始计时,0:不计时
#define   BIT_SYS_OFF		BIT_FLAG.b4   //开关机的标志位定义 0:开机 1:关机

#define EI()  __asm__(" ei ")
#define DI()  __asm__(" di ")
#define NOP() __asm__(" nop ")
#define CWDT() __asm__(" CWDT ")
#define SLEEP() __asm__(" sleep ")
/************************************/
//-----------寄存器读写示例----------  
/************************************/
#define CONTW(VAL)			__asm__("mov a,@"#VAL"\n ctw")			//CTW = VAL:CONT寄存器赋值
#define IOCP_W(REG,VAL)		__asm__("mov a,@"#VAL"\n iw "#REG)		//REG = VAL:IOC页寄存器赋值
#define IOCP_R(RAM,REG)		__asm__("ir "#REG"\n mov "#RAM",a")		//RAM = REG:IOC页寄存器读值

#define IOCP_W_AND(REG,VAL) __asm__("ir "#REG"\n and a,@"#VAL"\n iw "#REG)
#define IOCP_W_OR(REG,VAL)  __asm__("ir "#REG"\n or a,@"#VAL"\n iw "#REG)

#define PUSH(A_REG,R3_REG)	__asm__("mov "#A_REG",a\n swap "#A_REG"\n swapa STATUS\n mov "#R3_REG",a")	//中断入栈保护	
#define POP(A_REG,R3_REG)	__asm__("swapa "#R3_REG"\n mov STATUS,a\n swapa "#A_REG)					//中断出栈保护恢复
/****************************************/
// ---------数据定义--------------------
/****************************************/ 
#define	data_tc0 6			//;加到256溢出,1ms进一次中断 
/****************************************/
// --------端口定义--------------------
/****************************************/ 
#define io_ledout       PORT6_2
#define io_keyin        PORT6_3
/****************************************/
// ---------寄存器定义--------------------
/****************************************/ 
volatile __at(0x10) unsigned char A_BUFF;		//中断ACC保护RAM
volatile __at(0x11) unsigned char R3_BUFF;		//中断STATUS保护RAM

        这期的教学就到这里,下一期我们来讲解单片机在关机之后,如何做低功耗休眠的处理。

标签:__,XC8P9530,KEY,消抖,单片机,HL,新手,BIT,define
From: https://blog.csdn.net/Nzyz180/article/details/145165650

相关文章

  • 渗透测试新手必刷14大靶场推荐
    目录标题基础靶场DVWApikachuxss-labssql-labsupload-labs极核CTF综合渗透靶场vulnstack红日靶场HackTheBox墨者学院TryHackMewebgoatvulfocusvulhubvulnhub基础靶场在学习渗透测试以及挖src的过程中,这些基础靶场必须得了解其原理以及利用其漏洞DVWA涵盖多种......
  • 课设毕设分享:基于单片机的风扇控制器设计 (AT89C52、Proteus、Keil)二、DHT11温湿度传感
    往期内容课设毕设分享:基于单片机的风扇控制器设计(AT89C52、Proteus、Keil)一、初期准备+单片机最小系统(时钟电路、复位电路)手把手教学如何做一份高分课设文章目录往期内容前言一、课设要求1.基本要求2.方案设定首先进行前期准备工作配置环境绘制思维导图功能要求分析大......
  • 前端新手如何用vite构建小程序中使用的模块(以AES加密模块crypto-js为例)
    如果你只是想简单地把在vite项目中使用的模块引入到小程序中,不妨试试库模式。以crypto-js为例,你需要写两个JS文件:一个是构建脚本,类似于vite.config.js;//build.cjsconst{build}=require('vite'),path=require('path');build({publicDir:false,configFile:false......
  • 从零到手搓一个Agent:AI Agents新手入门精通(一)
    今日主题:当什么是Agent,与LLM的区别又是啥这一天,你的女朋友问你(假设我们有女朋友),宝宝,什么是Agent啊,Agent和LLM有什么区别呀,最近大家都在说的Agent究竟是什么,包括很多文章都在写的Agent,还有之前谷歌发布的Agents白皮书究竟是什么,对我们有什么帮助,对我们有什么影响呢?现在,编者......
  • 基于单片机的书写坐姿规范提醒器的设计(论文+源码)
    1功能设计本课题为基于单片机的书写坐姿规范提醒器的设计,其主要针对学生在进行书写时,经常会出现坐姿不对等现象,这样长期下去会影响学生的身体健康,因此本系统在功能上设计如下:采用超声波传感器检测坐姿,如果距离太近,则通过语音播报“距离过近请注意坐姿”的提示信息;通过光敏电......
  • 无需技术!新手小白也能一键部署幻兽帕鲁服务器!
    幻兽帕鲁(Palworld)是一款开放世界生存制作游戏,受到了全球玩家的喜爱。自行搭建专属游戏联机服务器可以获得更好的游戏体验,还能享受到自定义游戏规则的乐趣。然而,搭建游戏联机服务器往往需要一定的技术基础,让许多新手小白望而却步。不过,目前主流的云计算服务提供商都提供了幻兽帕......
  • 黑群晖最新安装方式|RR新手
    引导盘制作1、下载最新的黑群晖引导镜像原版链接:wjz304/rr百度云盘:链接:https://pan.baidu.com/s/12z3v_kVYUDdWNzWBWN_NTQ?pwd=e67k2、将下载好的压缩包解压,得到一个后缀为img的文件。3、使用写盘工具Rufus将镜像文件写到u盘中,点击选择,找到解压好后缀为img的文件,其他保持......
  • 故障分析---单片机上点后没有运转,如何检查
    故障分析---单片机上点后没有运转,如何检查确认电源电压是否正常。用万用表测量地引脚和电源引脚之间的电压,看是否符合规范电压;检查复位引脚电压是否正常。分别测量复位按键按下和松开时的电压,看是否符合;检查晶振是否起振了,一般用示波器来查看晶振引脚的波形,示波器探头使用“*1......
  • 新手指南 | 手把手教你快速安装WebUI,轻松开启AI创作之旅!
    什么是WebUI?WebUI是一款基于AI模型(如StableDiffusion)的用户界面工具,允许你轻松生成图片、设计艺术作品、甚至实现多样化的创意应用。(重点!它是开源的,免费的!!!)今天,我们为你准备了详细的新手安装教程,让你快速上手!一、准备工作:安装前的必要条件1.系统需求操作系统建议Wi......
  • 大模型书籍李开复周鸿祎力荐《实战AI大模型》!NUS尤洋教授首发新书深入浅出热门AI大模
    《实战AI大模型》这本大模型书籍已经上传CSDN,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费】在GPT-4的惊艳亮相之际,AI大模型成为了学界和工业界的热门话题。这些模型的复杂性和不断发展的技术为我们带来了新的挑战和机遇。人工智能正在从......