news 2026/9/5 13:04:35

【51单片机】1.2 如何实现按键?

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【51单片机】1.2 如何实现按键?

目录

一、独立/矩阵按键

二、定时器原理


一、独立/矩阵按键

单片机往往是检测状态而不是检测过程
按键消抖
1.延时10ms(不建议这种方法)
2.定时器消抖,定时2ms检测,如下红色部分
独立按键:主循环while(1)处理按键事件,中断用于确定按键状态:一个2ms定时器不断读key电平,消抖用一个变量0xff,进一次定时器中断2ms,0xff左移一位或上key,最终判断此变量是否为0xff或0x0; // 这里用的是定时器中断定时一直读按键状态,其实有点冗余,一直在检测。
#include <reg52.h> sbit ADDR0 = P1^0; sbit ADDR1 = P1^1; sbit ADDR2 = P1^2; sbit ADDR3 = P1^3; sbit ENLED = P1^4; //按键一定要仔细对照原理图,这里KEY1=P2^4……,按下键KEY1会变为0,然后给P2^4。这里定义就是因为可以检测 (对应矩阵按键电路,就是单片机的P2口资源) sbit KEY1 = P2^4; sbit KEY2 = P2^5; sbit KEY3 = P2^6; sbit KEY4 = P2^7; bit KeySta = 1; //当前按键状态 void main() { bit backup = 1; //按键值备份,保存前一次的扫描值 unsigned char cnt = 0; //按键计数 EA = 1; //使能总中断 ENLED = 0; //选择数码管DS1进行显示 ADDR3 = 1; ADDR2 = 0; ADDR1 = 0; ADDR0 = 0; TMOD = 0x01; //设置T0为模式1 TH0 = 0xF8; //为T0赋初值0xF8CD,定时2ms TL0 = 0xCD; ET0 = 1; //使能T0中断 TR0 = 1; //启动T0 P2 = 0xF7; //P2.3置0,即KeyOut1输出低电平, 读外部按键自己必须是高电平(Keyin置高电平)读取Keyin的情况,就独立的一排按键,通过输入来判断按键是否变(这里要仔细看截图和原理图) P0 = LedChar[cnt]; //显示按键次数初值 while (1) { if (KeySta != backup) //不相等说明此时按键有动作,KeySta是在中断中实时检测的。 中断nb { if (backup == 0) //弹起动作?: KeySta的0给了backup, 然后前面KeySta在中断中检测到弹起,变为1,满足条件,进入第一个和第二个if。 { cnt++; //按键次数+1 if (cnt >= 10) { //只用1个数码管显示,所以加到10就清零重新开始 cnt = 0; } //P0 = LedChar[cnt]; //计数值显示到数码管上 } backup = KeySta; //更新备份为当前值,以备进行下次比较 } } } void InterruptTimer0() interrupt 1 { static unsigned char keybuf = 0xFF; //扫描缓冲区,保存一段时间内的扫描值。给初值, 疑问:怎么个扫描法 哦!就是连续判断再加一个key TH0 = 0xF8; //重新加载初值 TL0 = 0xCD; //下面这条语句很好: keybuf = (keybuf<<1) | KEY4; // key4会强制类型转换 // keybuf左移后:xxxxxxx0 | xxxxxxxKEY4 //缓冲区左移一位,并将当前扫描值移入最低位。一次只移动一个bit if (keybuf == 0x00) { //连续8次扫描值都为0,即16ms内都只检测到按下状态时,可认为按键已按下 KeySta = 0; } else if (keybuf == 0xFF) { //连续8次扫描值都为1,即16ms(定时的2ms) 内都只检测到弹起状态时,可认为按键已弹起 KeySta = 1; } else { } //其它情况则说明按键状态尚未稳定,则不对KeySta变量值进行更新 }

矩阵按键:与上面的独立按键很类似,主循环依旧处理按键事件while(1),中断也很类似独立按键,但要注意一些代码的执行顺序。
//按键一般会保持100ms以上
#include <reg52.h> sbit ADDR0 = P1^0; sbit ADDR1 = P1^1; sbit ADDR2 = P1^2; sbit ADDR3 = P1^3; sbit ENLED = P1^4; sbit KEY_IN_1 = P2^4; sbit KEY_IN_2 = P2^5; sbit KEY_IN_3 = P2^6; sbit KEY_IN_4 = P2^7; sbit KEY_OUT_1 = P2^3; sbit KEY_OUT_2 = P2^2; sbit KEY_OUT_3 = P2^1; sbit KEY_OUT_4 = P2^0; unsigned char code LedChar[] = { //数码管显示字符转换表0-F 0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8, 0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E }; unsigned char KeySta[4][4] = { //全部矩阵按键的当前状态 断开 {1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1} }; void main() { unsigned char i, j; // i 行 j 列 unsigned char backup[4][4] = { //按键值备份,保存前一次的值 {1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1} }; EA = 1; //使能总中断 ENLED = 0; //选择数码管DS1进行显示 ADDR3 = 1; ADDR2 = 0; ADDR1 = 0; ADDR0 = 0; TMOD = 0x01; TH0 = 0xFC; //定时1ms TL0 = 0x67; ET0 = 1; //使能T0中断 TR0 = 1; //启动T0 P0 = LedChar[0]; //默认显示0 while (1) { for (i=0; i<4; i++) //循环检测4*4的矩阵按键 { for (j=0; j<4; j++) { if ( KeySta[i][j] !=backup[i][j] ) //检测按键动作 { if (backup[i][j] != 0) //按键按下时,又马上松开,就是松开才会显示字符 { P0 = LedChar[i*4+j]; //将编号显示到数码管,没错 } backup[i][j] = KeySta[i][j]; //更新前一次的备份值,KeySta[i][j]实时变化 } } } } } void InterruptTimer0() interrupt 1 { unsigned char i; static unsigned char keyout = 0; //注意赋初值 0; static unsigned char keybuf[4][4] = { //矩阵按键扫描缓冲区,全部是高位 {0xFF, 0xFF, 0xFF, 0xFF}, {0xFF, 0xFF, 0xFF, 0xFF}, {0xFF, 0xFF, 0xFF, 0xFF}, {0xFF, 0xFF, 0xFF, 0xFF} }; TH0 = 0xFC; //重新加载初值 TL0 = 0x67; //将一行的4个按键值移入缓冲区!!!!! keybuf[keyout][0] = (keybuf[keyout][0] << 1) | KEY_IN_1; keybuf[keyout][1] = (keybuf[keyout][1] << 1) | KEY_IN_2; keybuf[keyout][2] = (keybuf[keyout][2] << 1) | KEY_IN_3; keybuf[keyout][3] = (keybuf[keyout][3] << 1) | KEY_IN_4; //消抖后更新按键状态 for (i=0; i<4; i++) //每行4个按键,所以循环4次 { if ((keybuf[keyout][i] & 0x0F) == 0x00) //这里是要 & (按位与) { //连续4次扫描值为0,即4*4ms内都是按下状态时,可认为按键已稳定的按下,是4*4 ms KeySta[keyout][i] = 0; } else if ((keybuf[keyout][i] & 0x0F) == 0x0F) { //连续4次扫描值为1,即4*4ms内都是弹起状态时,可认为按键已稳定的弹起 KeySta[keyout][i] = 1; } } //执行下一次的扫描输出 keyout++; //输出索引递增 keyout = keyout & 0x03; //加到4即归零,这个& 很6 switch (keyout) //根据索引,释放当前输出引脚 { //case 3: KEY_OUT_4 = 0; KEY_OUT_3 = 1; KEY_OUT_2 = 1;KEY_OUT_1 = 1; 这里可以省略是因为前面那个值就是1 case 0: KEY_OUT_4 = 1; KEY_OUT_1 = 0; break; //发现没有,是先case1 2 3 0的顺序,如果是这样,那case 1:中就不能省略其余KEY_OUT,因为没有赋初值呀???(中断多次,就在switch里面初始化了,注意顺序,注意吧拉高电平放到前面) case 1: KEY_OUT_1 = 1; KEY_OUT_2 = 0; break; case 2: KEY_OUT_2 = 1; KEY_OUT_3 = 0; break; case 3: KEY_OUT_3 = 1; KEY_OUT_4 = 0; break; default: break; //注意:是先对按键进行读取,然后进行判断/切换(KEY_OUT)为什么要这样呢,如果先切换,可能1还没变成0就被读取了(执行速度比较慢 } }

二、定时器原理

简单的示意图:
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/1 5:46:01

【51单片机】1.9 AD转换原理

A/D Analog to Digital Converter主要指标&#xff1a;1.ADC的位数&#xff1a;1个n位的ADC表示这个ADC共有2的n次方个刻度2.基准源&#xff1a;基准电压&#xff0c;是ADC的参考标准; 类似一个尺子,尺子的长度&#xff0c;所以采集的电压不能超过这个值 3.分辨率&#xff1a;…

作者头像 李华
网站建设 2026/9/5 13:03:18

AI软件工厂设计模式:从多Agent协作到稳定流程落地

最近看了一眼“AI软件工厂设计模式直播第71期”这个主题&#xff0c;第一反应是&#xff1a;设计模式这种东西&#xff0c;在 AI 编程越来越成熟的今天&#xff0c;还有必要专门开直播讲吗&#xff1f;但再往下想&#xff0c;你会发现事情没那么简单。传统设计模式解决的是“代…

作者头像 李华
网站建设 2026/9/1 7:38:07

TouchGFX按键控制光圈移动:STM32F746的MVP架构与刷新机制解析

先交代一下这个活儿是怎么来的。手头有一块基于 STM32F746 的 RGB 屏开发板&#xff0c;4.3 寸 480272 分辨率&#xff0c;最初的需求一句话就能说完&#xff1a;做一个模拟相机取景器的界面&#xff0c;屏幕中间显示一个光圈&#xff0c;然后用板子上的方向键去控制这个光圈移…

作者头像 李华
网站建设 2026/9/1 0:05:20

DACRI:用因果推断优化供应链干预优先级排序

当关键供应链面临中断风险时&#xff0c;一个经常被问到的问题是&#xff1a;手里握着一批有限的干预资源&#xff0c;到底是先帮哪家供应商、先补哪个节点、先启动哪套应急预案&#xff1f;如果只看风险分或历史绩效&#xff0c;往往会发现“排在前面的不一定该被帮”&#xf…

作者头像 李华
网站建设 2026/9/2 9:30:13

城市级具身智能实践:ROS2导航与数据闭环解析

走在城市街头&#xff0c;遇到的不再只是行人和车辆&#xff0c;还有一台台正在执行配送、巡检、清扫等任务的机器人。机器人从实验室走向真实城市环境&#xff0c;背后依靠的是一个被称为“具身智能”的技术方向。本文以城市级具身智能实验为切入点&#xff0c;拆解这类系统背…

作者头像 李华