单片机3个普通IO识别22个按键
- 格式:doc
- 大小:518.00 KB
- 文档页数:9
用最少的IO口扫最多的按键竟然可以这样在做项目(工程)的时候,我们经常要用到比较多的按键,而且IO资源紧张,于是我们就想方设法地在别的模块中节省IO口,好不容易挤出一两个IO口,却发现仍然不够用,实在没办法了就添加一个IC来扫键。
一个IC虽然价格不高,但对于大批量生产而且产品利润低的厂家来说,这是一笔不菲的开支!那,我们能不能想到比较好的扫键方法:用最少的IO口,扫最多的键?可以吗?举个例:给出5个IO口,能扫多少键?有人说是2*3=6个,如图一:图一对,大部分技术参考书都这么做,我们也经常这样做:用3个IO口作行扫描,2个IO作列检测(为方便描述,我们约定:设置某一IO口输出为“0”――称其为“扫某IO口”)。
用行线输出扫键码,列线检测是否有按键的查询方法进行扫键。
扫键流程:在行线依次输出011,101,110扫键值,行线每输出一个扫键值,列线检测一次。
当列线检测到有按键时,结合输出的扫键值可以判断相应的按键。
但是,5个IO真的只能扫6个键吗?有人说可以扫9个,很聪明!利用行IO与地衍生3个键(要注意上拉电阻),如图二:图二扫键流程:先检测3个行IO口,对K1’,K2’,K3’进行扫键,之后如上述2*3扫键流程。
5个IO口能扫9个键,够厉害吧,足足比6个键多了1/2!动动脑,还能不能再多扫几个?就几个?一个也行!好,再想一下,硬是被逼出来了!如图三:图三不多不少,正好10个键!这种扫键方式比较少见吧!漂亮!扫键流程:设IO1输出为“0”,检测IO2…IO5,若判断有相应健按下,则可知有健;若无键,则继续扫键:设IO2输出为“0”,检测IO3,IO4,IO5,判断有无键按下,如此类推。
这里应注意:当扫某一IO口(输出为“0”)时,不要去检测已经扫过的IO口。
如:此时设置IO2输出为“0”,依次检测IO3,IO4,IO5,但不要去检测IO1,否则会出错(为什么,请思考)。
感觉怎么样?不错吧!让我们再看看图三,好有成就感!看着,看着……又看到了什么?快!见图四:图四真强!被您看出20个键!多了一个对称的三角形。
用一条IO口实现两个按键功能发布时间:2011-02-27 14:11:36 年前去到一个朋友那里,提到现在客户对成本要求非常之高,尤其是玩具行业,已经是一分一厘的去计算产品成本。
朋友感慨为了省成本,方案商是绞尽脑汁地去想各种实现方法,说遇到过为了节省成本,硬是用单片机一条IO实现了两个按键的功能,让他颇为诧异,好久都没想明白原理。
一条IO实现两个按键,听上去确实挺新奇,既然别人能够实现,我想我也应该可以做到,看来得找找实现的方法。
我有一个习惯,遇到某些问题的时候,会在睡觉前想解决方法,想着想着就会睡着了,问题的答案有没有找到则不一定。
我知道有用一条IO实现多个按键的方法,这样的方法大多是选用的IO支持ADC功能,用电阻分压后通过读电压判断键值。
如果IO不支持ADC功能,也不是不行,可以用电容充放电的方法实现ADC,从而用普通IO间接进行测量。
(参见我之前关于键盘扫描的文章)这样用一条IO实现两个按键给我的第一感觉是可能需要利用到电容充放电原理,于是在半梦半醒之中找到了实现的方法。
先看上图左边部分,如果MCU_IO1为双向IO口,假设单片机程序按以下流程处理,看看会得到什么样的结果?1.MCU_IO1设定为输出,输出高电平一段时间,此时电容C1会充电,最后C1上的电压接近电源电压。
2.MCU_IO1设定为输入,如果J1、J2均不按下,此时MCU_IO1可以理解成一个阻值很大的电阻接地,电容C1上的电荷会通过这个电阻逐渐释放掉,这样C1上的电压会逐渐降低到零。
因为C1上的电压下降需要一个过程,当MCU_IO1设为输入后马上读一下MCU_IO1的状态,此时会读到什么结果?显然是高电平状态1。
3.再将MCU_IO1设定为输出,输出低电平一段时间,显然不管电容C1处于什么状态,只要MCU_IO1输出低电平时间足够长,最后C1上的电压应该接近零。
4.再将MCU_IO1设定为输入,如果J1、J2同样不按下,MCU_IO1读到的是低电平状态0。
/*==================================================================** 3个IO接识别22键测试程序 ** ------------------------------------------------ ** MCU: AT89C2051 ** OSC: 12M cysytel ** 程序设计:Cowboy ** 程序版本:V1.0 **==================================================================*/#include <reg52.h>//================== IO口线连接 ================== sbit Bus = P1^0;sbit IO_a = P1^4;sbit IO_b = P1^3;sbit IO_c = P1^2;//================== 变量声明 ==================== unsigned char Disp_buf[3];unsigned char Dig;unsigned char Key_count;unsigned char bdata Key_state;sbit KB0 = Key_state^0;sbit KB1 = Key_state^1;sbit KB2 = Key_state^2;sbit KB3 = Key_state^3;sbit KB4 = Key_state^4;sbit KB5 = Key_state^5;//================== 表格数据 ==================== code unsigned char LED_font[24]={0x84,0x9f,0xa2,0x8a,0x99,0xc8,0xc0,0x9e,0x80, //012345678 0x88,0x90,0xc1,0xe4,0x83,0xe0,0xf0,0xff,0xfb, //9abcdef - };code unsigned char Key_tab[64]= //键码映射表{// 0 1 2 3 4 5 6 7 8 922, 0, 2, 0, 0, 0, 0, 0, 4, 0, //00, 0, 0, 0, 0,18, 0, 0, 0, 0, //1X0, 0, 0, 0, 0, 0, 3,14, 0, 0, //2X20,10, 6, 0, 0, 0, 0, 0, 1,19, //3X0, 5, 0, 0, 0,15, 0,11, 0, 0, //4X0,17, 0, 0,13, 8, 0,21, 0, 9, //5X16,12, 7, 0 //6X};//=============== 检测按键 =================void Key_scan(){unsigned char i;Key_count --; //扫描次序Key_count &= 3;switch (Key_count) //按次序处理{case 2: //第一轮扫描KB0 = IO_b;KB1 = IO_c;IO_a = 1;IO_b = 0;break;case 1: //每二轮扫描KB2 = IO_c;KB3 = IO_a;IO_b = 1;IO_c = 0;break;case 0: //每三轮扫描KB4 = IO_a;KB5 = IO_b;IO_c = 1;break;default: //每四轮扫描if (!IO_a) KB0 = 0;if (!IO_b) KB2 = 0;if (!IO_c) KB4 = 0;IO_a = 0;//======更新显示缓冲区=======i = Key_tab[Key_state];if (i == 0){Disp_buf[2] = 0x11; //显示三横 Disp_buf[1] = 0x11;Disp_buf[0] = 0x11;}else{Disp_buf[2] = 0x0c; //字符"C"Disp_buf[1] = i / 10; //键码十位Disp_buf[0] = B;于 //键码个位}Key_state = 0;}/*=================================================================== ONE WIRE 显示总线驱动程序===================================================================*///=============== 发送一位 =================void Send_bit(bit Dat){unsigned char i = 3;if (!Dat) Bus = 0;else{Bus = 0;Bus = 1;}while(--i); //延时8usBus = 1;}//=============== 总线驱动 =================void Bus_drive(){unsigned char i = 0;unsigned char Sdat;Send_bit(1); //Bit6消隐do Bus = 1; while(--i); //延时768usdo Bus = 0; while(--i); //延时768usBus = 1;Sdat = LED_font[Disp_buf[Dig++]]; //获取显示数据Send_bit(Sdat & 0x01); //发送位0Send_bit(Sdat & 0x02); //发送位1Send_bit(Sdat & 0x04); //发送位2Send_bit(Sdat & 0x08); //发送位3Send_bit(Sdat & 0x10); //发送位4Send_bit(Sdat & 0x20); //发送位5Send_bit(Dig & 0x01); //发送位选1Send_bit(Dig & 0x02); //发送位选2while(--i); //延时512usSend_bit(Sdat & 0x40); //发送位6for (i = 7;i> 0;i--) Send_bit(1); //位6移至Doutif (Dig == 3) Dig = 0;}/*=================================================================== 延时 5ms 程序===================================================================*/ void Delay_5ms(){while(!TF1);TF1 = 0;TH1 = (- 5000) / 256;TL1 = (- 5000) % 256;}/*=================================================================== 主程序===================================================================*/ void main(){TMOD = 0x10; //定时器1,16位模式TCON = 0xc0; //TR1=1;TF1=1;while(1) //主循环{Bus_drive(); //显示总线驱动Key_scan(); //检测按键Delay_5ms(); //延时5MS}}。
本文介绍两种方法解决"如何占用较少的单片机I/O口就能够实现较多的按键功能?"
方法一:二进制编码法
这个方法我在好几个产品上都用过,适合需要的按键不是太多的情况下使用.如果单片机有n 个I/O口,那么在理论上就可以实现2n—1个按键, 下面的电路图是利用3个I/O口实现6个按键的功能,每个按键代表1个二进制编码,如[ENT]键的编码是[0 0 1],其他按键以此类推。
方法二:A/D值判断法
这个方法只占用单片机的1个A/D输入口,就可以实现较多的按键功能.通过采样A/D值的大小就可以判断是哪个按键被按下,缺点是当多个按键同时按下时,容易判断出错.
本文简单介绍“低边与高边电流检测”的主要区别。
图B低边电流检测方案简单而且便宜,一般的运放器都可以实现此功能。
但是很多应用无法接受检测电阻Rs引入的地线干扰问题,负载电流较大时更会加剧这个问题,因为系统中一部分电路的地电位由于低边检流电阻而产生偏移,而这部分电路可能与另一部分地电位没有改变的电路相互联系。
所以当需要大电流检测时,必须重视这个问题。
图A在负载的高端进行电流检测的简易电路,不仅消除了地线干扰,而且能够检测到短路故障,需要注意的是高边检测要求放大器能够处理接近电源电压的共模电压。
本文介绍无源滤波电路的频率计算公式
1. 常用的RC滤波电路
f 0 = 1/(2πRC)
例:R = 16K ,C =10nF
f 0 = 1/(2πRC) = 1/(2π×16×103×10×10-9 ) =1000Hz
2. LC滤波电路
下期介绍。
5个IO口最多能扫描多少个按键?简介:在做项目(工程)的时候,我们经常要用到比较多的按键,而且IO资源紧张,于是我们就想方设法地在别的模块中节省IO口,好不容易挤出一两个IO口,却发现仍然不够用,实在没办法了就添加一个IC来扫键。
一个IC虽然价 ...在做项目(工程)的时候,我们经常要用到比较多的按键,而且IO资源紧张,于是我们就想方设法地在别的模块中节省IO口,好不容易挤出一两个IO口,却发现仍然不够用,实在没办法了就添加一个IC来扫键。
一个IC虽然价格不高,但对于大批量生产而且产品利润低的厂家来说,这是一笔不菲的开支!那,我们能不能想到比较好的扫键方法:用最少的IO口,扫最多的键?可以吗?举个例:给出5个IO口,能扫多少键?有人说是2*3=6个,如图一:图一对,大部分技术参考书都这么做,我们也经常这样做:用3个IO口作行扫描,2个IO作列检测(为方便描述,我们约定:设置某一IO口输出为“0”――称其为“扫某IO口”)。
用行线输出扫键码,列线检测是否有按键的查询方法进行扫键。
扫键流程:在行线依次输出011,101,110扫键值,行线每输出一个扫键值,列线检测一次。
当列线检测到有按键时,结合输出的扫键值可以判断相应的按键。
但是,5个IO真的只能扫6个键吗?有人说可以扫9个,很聪明!利用行IO与地衍生3个键(要注意上拉电阻),如图二:图二扫键流程:先检测3个行IO口,对K1’,K2’,K3’进行扫键,之后如上述2*3扫键流程。
5个IO口能扫9个键,够厉害吧,足足比6个键多了1/2!动动脑,还能不能再多扫几个?就几个?一个也行!好,再想一下,硬是被逼出来了!如图三:图三不多不少,正好10个键!这种扫键方式比较少见吧!漂亮!扫键流程:设IO1输出为“0”,检测IO2…IO5,若判断有相应健按下,则可知有健;若无键,则继续扫键:设IO2输出为“0”,检测IO3,IO4,IO5,判断有无键按下,如此类推。
单片机控制多个按键的方法在很多嵌入式系统中,通常会用到按键进行输入。
单个按键的控制可能相对简单,但是如果需要控制多个按键,就需要用到一些特殊的控制方法。
常用的按键控制方法主要有以下几个方面:1、轮询法:采用逐个扫描的方式来检测按键状态。
2、中断法:接入外部中断口,当按键被按下时,会触发中断,系统会响应中断并执行相应的程序。
3、计时器法:通过计时器的方式来检测按键状态,利用定时器可以定时检测按键的状态。
如果需要控制多个按键,就需要采用一些特殊的控制方法:1、矩阵按键法:将多个按键以矩阵的方式进行排列,通过某种方法对行和列进行扫描,以检测按键的状态。
三、常用的按键检测程序以下是一个常用的按键检测程序,可以用于单片机控制多个按键:void key_scan(void){unsigned char read_date, key1, key2, key3, key4;// 初始化按键控制端口为输入模式P3M0 = 0x00;P3M1 = 0x00;// 所有按键端口均拉高,等待按键输入P3 = 0xff;// 等待按键输入Delay_ms(20);// 读取P3端口状态// 获得按键1状态key1 = read_date & 0x01;// 获得按键2状态key2 = read_date & 0x02;// 获得按键3状态key3 = read_date & 0x04;// 获得按键4状态key4 = read_date & 0x08;// 判断按键1是否被按下if (key1 == 0){// 按键1被按下,执行相应的操作 }// 判断按键2是否被按下if (key2 == 0){// 按键2被按下,执行相应的操作 }// 判断按键3是否被按下if (key3 == 0){// 按键3被按下,执行相应的操作 }// 判断按键4是否被按下{// 按键4被按下,执行相应的操作}}四、总结单片机控制多个按键的方法,需要采用特殊的控制方法,例如矩阵按键法和编码按键法等。
我知道有用一条IO实现多个按键的方法,这样的方法大多是选用的IO支持ADC功能,用电阻分压后通过读电压判断键值。
如果IO不支持ADC功能,也不是不行,可以用电容充放电的方法实现ADC,从而用普通IO间接进行测量。
这样用一条IO实现两个按键给我的第一感觉是可能需要利用到电容充放电原理,于是在半梦半醒之中找到了实现的方法。
先看上图左边部分,如果MCU_IO1为双向IO口,假设单片机程序按以下流程处理,看看会得到什么样的结果?1.MCU_IO1设定为输出,输出高电平一段时间,此时电容C1会充电,最后C1上的电压接近电源电压。
2.MCU_IO1设定为输入,如果J1、J2均不按下,此时MCU_IO1可以理解成一个阻值很大的电阻接地,电容C1上的电荷会通过这个电阻逐渐释放掉,这样C1上的电压会逐渐降低到零。
因为C1上的电压下降需要一个过程,当MCU_IO1设为输入后马上读一下MCU_IO1的状态,此时会读到什么结果?显然是高电平状态1。
3.再将MCU_IO1设定为输出,输出低电平一段时间,显然不管电容C1处于什么状态,只要MCU_IO1输出低电平时间足够长,最后C1上的电压应该接近零。
4.再将MCU_IO1设定为输入,如果J1、J2同样不按下,MCU_IO1读到的是低电平状态0。
如果J1按下,再来看看这四步,此时电容C1已经被强制接到电源上,MCU_IO1对其的充放电已经不起作用,在步骤2中MCU_IO1读到的状态依然是1,但在步骤4中MCU_IO1读到的状态就不再是0,而是变为1。
如果J2按下,同样看这四步,此时电容C1被强制接到地,MCU_IO1对其充放电也失去作用,在步骤2中MCU_IO1读到的状态变为0,但在步骤4中MCU_IO1读到的状态保持为0。
到这里我想大家应该已经明白了实现方法,根据此四步中读到的MCU_IO1状态,就可以判断出J1、J2是否按下。
既然已经找到方法,是不是就万事大吉了呢?不然,我们还得回过头去看看此方法是不是足够可靠。
单片机用一个IO口采集多个按键信号,如
何实现?
一个IO口检测多个按键肯定是可以实现的,下面简单举几个例子,为大家指点一下思路。
使用普通的I/O口检测多个按键通常我们使用按键检测的时候,一般都是一个I/O口检测一个按键,比如:
M×N阵矩阵式按键至少也需(M+N)个I/O口,比如4×4共16个按键的矩阵共需8个I/O口。
那么一个I/O口可不可以实现检测多个按键呢?其实
是可以的,硬件电路设计复杂,值不值得这么做。
比如通过检测占空比来区分不同的按键,每个按键后面做硬件防抖处理后再加555定时器组成的电路。
或者类似于串行通讯的方式,比如第一个按键按下输出1个脉冲,第二个按键按下输出两个脉冲,第三个按下输出3个脉冲等,这种按键后端电路处理更复杂。
使用单片机的一个ADC采样口检测多个按键使用ADC检测多个按键时,按键之间的电压间隔最好相近,而且范围较宽,这样设计误差会更小一些。
比如以5个按键为例,ADC检测电压范围为0~5V,那么在设计电路时各按键之间的电压间隔应大约为5V/5=1V。
上图为本人设计的电路图,S1按下电压为0V;S2按下电压约为0.97V;S3按下电压为2.03V;S4按下电压约为2.94V;S5按下电压约为3.99V。
软件处理时只要设置±0.1V 的检测范围宽度即可。
单片机。
吸取各位前辈的经验,将之前二极管用量多的问题优化一下,目前不用二极管能接6键,2只二极管能接12键,6只二极管能接18键,9只二极管能接21键,第22键要单独占用3只二极管最不化算。
实验用89S51作试验,电路接线就是P1.2,P1.3,P1.4接键盘,P1.0接显示器。
(原文件名:GIF.gif)
(原文件名:DSC00015.JPG)
(原文件名:DSC00016.jpg)
/*==================================================================*
* 3个IO接识别22键测试程序 * * ------------------------------------------------ *
* MCU: AT89C2051 *
* OSC: 12M cysytel *
* 程序设计:Cowboy *
* 程序版本:V1.0 *
*==================================================================*/
#include <reg52.h>
//================== IO口线连接 ==================
sbit Bus = P1^0;
sbit IO_a = P1^4;
sbit IO_b = P1^3;
sbit IO_c = P1^2;
//================== 变量声明 ====================
unsigned char Disp_buf[3];
unsigned char Dig;
unsigned char Key_count;
unsigned char bdata Key_state;
sbit KB0 = Key_state^0;
sbit KB1 = Key_state^1;
sbit KB2 = Key_state^2;
sbit KB3 = Key_state^3;
sbit KB4 = Key_state^4;
sbit KB5 = Key_state^5;
//================== 表格数据 ====================
code unsigned char LED_font[24]=
{
0x84,0x9f,0xa2,0x8a,0x99,0xc8,0xc0,0x9e,0x80, //012345678 0x88,0x90,0xc1,0xe4,0x83,0xe0,0xf0,0xff,0xfb, //9abcdef - };
code unsigned char Key_tab[64]= //键码映射表
{// 0 1 2 3 4 5 6 7 8 9
22, 0, 2, 0, 0, 0, 0, 0, 4, 0, //0
0, 0, 0, 0, 0,18, 0, 0, 0, 0, //1X
0, 0, 0, 0, 0, 0, 3,14, 0, 0, //2X
20,10, 6, 0, 0, 0, 0, 0, 1,19, //3X
0, 5, 0, 0, 0,15, 0,11, 0, 0, //4X
0,17, 0, 0,13, 8, 0,21, 0, 9, //5X
16,12, 7, 0 //6X
};
//=============== 检测按键 =================
void Key_scan()
{
unsigned char i;
Key_count --; //扫描次序
Key_count &= 3;
switch (Key_count) //按次序处理
{
case 2: //第一轮扫描 KB0 = IO_b;
KB1 = IO_c;
IO_a = 1;
IO_b = 0;
break;
case 1: //每二轮扫描 KB2 = IO_c;
KB3 = IO_a;
IO_b = 1;
IO_c = 0;
break;
case 0: //每三轮扫描 KB4 = IO_a;
KB5 = IO_b;
IO_c = 1;
break;
default: //每四轮扫描
if (!IO_a) KB0 = 0;
if (!IO_b) KB2 = 0;
if (!IO_c) KB4 = 0;
IO_a = 0;
//======更新显示缓冲区=======
i = Key_tab[Key_state];
if (i == 0)
{
Disp_buf[2] = 0x11; //显示三横 Disp_buf[1] = 0x11;
Disp_buf[0] = 0x11;
}
else
{
Disp_buf[2] = 0x0c; //字符"C"
Disp_buf[1] = i / 10; //键码十位
Disp_buf[0] = B;于 //键码个位
}
Key_state = 0;
}
}
/*=================================================================== ONE WIRE 显示总线驱动程序
===================================================================*/
//=============== 发送一位 =================
void Send_bit(bit Dat)
{
unsigned char i = 3;
if (!Dat) Bus = 0;
else
{
Bus = 0;
Bus = 1;
}
while(--i); //延时8us
Bus = 1;
}
//=============== 总线驱动 =================
void Bus_drive()
{
unsigned char i = 0;
unsigned char Sdat;
Send_bit(1); //Bit6消隐
do Bus = 1; while(--i); //延时768us
do Bus = 0; while(--i); //延时768us
Bus = 1;
Sdat = LED_font[Disp_buf[Dig++]]; //获取显示数据
Send_bit(Sdat & 0x01); //发送位0
Send_bit(Sdat & 0x02); //发送位1
Send_bit(Sdat & 0x04); //发送位2
Send_bit(Sdat & 0x08); //发送位3
Send_bit(Sdat & 0x10); //发送位4
Send_bit(Sdat & 0x20); //发送位5
Send_bit(Dig & 0x01); //发送位选1
Send_bit(Dig & 0x02); //发送位选2
while(--i); //延时512us
Send_bit(Sdat & 0x40); //发送位6
for (i = 7;i> 0;i--) Send_bit(1); //位6移至Dout
if (Dig == 3) Dig = 0;
}
/*===================================================================
延时 5ms 程序
===================================================================*/ void Delay_5ms()
{
while(!TF1);
TF1 = 0;
TH1 = (- 5000) / 256;
TL1 = (- 5000) % 256;
}
/*===================================================================
主程序
===================================================================*/ void main()
{
TMOD = 0x10; //定时器1,16位模式
TCON = 0xc0; //TR1=1;TF1=1;
while(1) //主循环
{
Bus_drive(); //显示总线驱动
Key_scan(); //检测按键
Delay_5ms(); //延时5MS
}
}。