- 打卡等级:常住居民I
- 打卡总天数:68
- 最近打卡:2026-02-10 09:53:37
注册会员
- 积分
- 164
|
先上电路
如左图独立键盘及四个并联的开关,开关没有按下时电路开路输出 DK 为高电平,开关按下后形成闭合电路,输出 DK 为低电平。
程序读入键盘的状态输出给 LED 灯,显示具体哪个开关按下。
右图为 AD 开关,第一个开关按下输出电平为 VCC,第二个是 3/4VCC,第三个1/2VCC,第四个 1/4VCC。可以由万用表测得对应
按键输出的电压值,也可以由 AD转换电路将输出的电压值读入单片机中完成键位的区分。AD 开关多使用于节省 I/O口开销,如:
需要用一个 I/O 口读 8 个开关,就可以通过读到的电压值来判断具体按下的开关。
如图3-2,4*4 键盘的行通过连接限流电阻接VCC,列由限流电阻连接到I/O口,按键没有按下时KL为1,KR 为0。按键按下后,行仍为高
电平,对应的列变为高电平。为检测是哪个键按下要在列上输出低电平来检测。检测按键状态可以使用逐列扫描或者翻转法,这里我们使用翻
转法。首先读入当前的列状态,然后对列输出全0,读入行状态,通过查表判断当前按键按下的状态,在数码管上输出。
实验测试
连接:JP81连接到 P0,P0^0连接A;JP82连接到P2;JP76连接到P1 ,低位接KR0-3,高位接KL0-3。
实验现象:数码管显示对应按键的值。
代码分析
/*-----------------------------------------------
名称:矩阵键盘 使用翻转法
接线:JP81连接到 P0,P0^0连接A;JP82连接到 P2;JP76连接到 P1 ,低
位接KR0-3,高位接KL0-3
------------------------------------------------*/
#include "hal.h"
#define dataport P0
#define weiport P2
#define key P1
bit flag;
unsigned char code dat[] = {0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71,0x40};// 显示段码值0~F
unsigned char code wei[]={0x7f,0xbf,0xdf,0xef,0xf7,0xfb,0xfd,0xfe};//分别对应相应的数码管点亮,即位码
unsigned char TempData[8]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07};//存储显示值的全局变量
unsigned char num,tmp1,tmp2,val;
unsigned char temp[8];
void Display(unsigned char FirstBit,unsigned char Num);
unsigned char KeyScan(void);
void dealkey(void);
unsigned char getnum();
/*------------------------------------------------
定时器初始化子程序
------------------------------------------------*/
void Init_Timer0(void)
{
TIMER_CLK_DIV(0,1); //定时器时钟12T模式
TIMER_TIME(0); //设置定时器0定时模式
TIMER_MODE(0,0); //设置定时器0模式0
TH0=(65536-10000)/256; //给定初值,从0开始计数一直到65535溢出
TL0=(65536-10000)%256;
CLR_TIMER_FLAG(0); //清除TF0标志
}
void Init_Timer1(void) //5毫秒@12.000MHz
{
TIMER_CLK_DIV(1,12); //定时器时钟12T模式
TIMER_TIME(1); //设置定时器0定时模式
TIMER_MODE(1,0); //设置定时器0模式0
TL1 = 0xB0; //设置定时初值
TH1 = 0x3C; //设置定时初值
CLR_TIMER_FLAG(0); //清除TF0标志
}
//-------------------------------------------------------
main()
{
static unsigned char i,j;
P0M0 = 0xff;
P0M1 = 0x00;
Init_Timer0();
Init_Timer1();
TIMER_RUN(1,START); //定时器0开始计时
TIMER_INT_EN(1,ON); //开启定时器0中断
TIMER_RUN(0,START); //定时器0开始计时
TIMER_INT_EN(0,ON); //开启定时器0中断
INT_GLOBAL_ENABLE(ON); //开启全局中断
flag=0;
while(1)
{
if(flag)
{
dealkey(); //读按键
flag=0;
if(num<0x11) //判断有效按键
{
if(i<8)
{
temp[i]=dat[num-1];
for(j=0;j<=i;j++)
TempData[7-i+j]=temp[j];
}
i++;
if(i==9) //多出一个按键输入为了清屏 原本应该 为8
{
i=1;
for(j=0;j<8;j++) //清屏
TempData[j]=0;
temp[0]=dat[num-1];
TempData[7]=dat[num-1];
}
num=0;
}
}
}
}
/*------------------------------------------------
定时器0中断子程序
------------------------------------------------*/
void Timer0_isr(void) interrupt 1
{
Display(0,8);
}
/*------------------------------------------------
定时器1中断子程序
------------------------------------------------*/
void Timer1_isr(void) interrupt 3
{
flag=1;
}
void Display(unsigned char FirstBit,unsigned char Num)
{
static unsigned char i=0;
dataport=0; //清空数据,防止有交替重影
weiport=wei[i+FirstBit]; //取位码0x3f;
dataport=TempData[i]; //取显示数据
i++;
if(i==Num)
i=0;
}
/*------------------------------------------------
按键扫描函数,返回扫描键值
-----------------------------------------------*/
unsigned char KeyScan(void) //键盘扫描函数
{
P1M0 = 0x00; //双态
P1M1 = 0x00;
key = 0xf0; //读行
tmp1 = key;
tmp1 = ~tmp1;
tmp1 = tmp1&0xf0;
P1M0 = 0x00; //仅为输入
P1M1 = 0xff;
tmp2 = key; //读列
tmp2 = tmp2&0x0f;
val=getnum();
return val;
}
unsigned char getnum()
{
static unsigned char ch,ch0;
ch=tmp1+tmp2;
switch(ch)
{
case 0x11:ch0=1;break; //第一行第一个按键按下
case 0x12:ch0=2;break;
case 0x14:ch0=3;break;
case 0x18:ch0=4;break;
case 0x21:ch0=5;break;
case 0x22:ch0=6;break;
case 0x24:ch0=7;break;
case 0x28:ch0=8;break;
case 0x41:ch0=9;break;
case 0x42:ch0=10;break;
case 0x44:ch0=11;break;
case 0x48:ch0=12;break;
case 0x81:ch0=13;break;
case 0x82:ch0=14;break;
case 0x84:ch0=15;break;
case 0x88:ch0=16;break;
default:ch0=0xff;break;
}
return ch0;
}
void dealkey()
{
static unsigned char val1,state,s;
val1=KeyScan();
if(val1==0xff) //是否有合理按键按下
s=0;
else
s=1;
switch(state)
{
case 0:
if(s)
state=1;
break; //读到按键
case 1:
if(s) //确认按键
{
num=val1;
state=2;
}
else
state=0;
break;
case 2:
if(s==0)
state=0;
break; //按键松开
}
}
实验讲解
流程图:
消抖部分主要使用了状态机来完成
有限状态机分析设计的基本原理
有限状态机是实时系统设计中的一种数学模型,它可以应用于从系统分析到设计(包括硬件、软件)的所有阶段。
有限状态机由有限的状态及其之间的转换构成,在任何时候只能处于给定的目的状态中的一个。当接收到一个输入事件时,
状态机产生一个输出,同时也可能伴随着状态的转移。在状态机中,从硬件角度看,时间序列如同一个触发脉冲序列或同步信号;
而从软件上看,时间序列就是一个定时器。状态机有时间序列同步触发,定时检测输入,以及根据当前的状态输出相应的信号,
并确定下一次系统状态的转换。当时间序列进入下一次触发时,系统的状态将根据前一次的状态和输入情况发生状态的转移。
通常主要有两种方法来建立有限状态机:一种是“状态转移图”,另一种是“状态转移表”。
这里以单按键消抖状态机为例做简单分析。把单个按键看成一个状态机,首先需要对一次按键操作和确认的实际过程进行分析,
然后根据实际情况和系统需要确定按键在整个过程的状态和每个状态的输入信号和输出信号,以及状态之间的转换关系,
最后还要考虑时间序列的间隔。
在一个嵌入式系统中,按键的操作是随机的,因此系统软件对按键需要一直循环查询。由于按键的检测过程需要进行消抖处理,
因此取状态机的时间序列的周期约为 10ms。这样不仅可以跳过按键抖动的影响,同时也远小于按键 0.3~0.5s 的稳定闭合期,
不会将按键操作丢失。很明显,系统的输入信号是与按键连接的 I/O 口电平,“1”表示按键处于放开状态,“0”表示按键处于闭合状态。
而系统的输出信号则表示检测和确认到一次按键的闭合操作,用“1”表示。
图中,状态 0 为按键的初始状态,当按键输入为“1”时,表示按键处于放开状态,输出“0”(1/0),
下一状态仍为 0;当按键输入为“0”时,表示按键闭合,但输出还是“0”(0/0),下一状态进入状态 1。
|
|