蓝桥杯单片机第二周第二课:按键控制入门

:electric_plug: 蓝桥杯单片机第二周第二课:按键控制入门

:brain: 学习目标

  • 认识按键的硬件原理(就像看懂开关的接线图)

  • 学会用代码“听”按键的“说话”(按下和松开)

  • 掌握用按键控制LED的几种方式(点灯、流水灯、调速)


:one: 认识按键:单片机的“耳朵”

:video_game: 按键是什么?

按键就像是单片机的耳朵开关。当它被按下时,会给单片机一个信号:“嘿,我被按了!”

:electric_plug: 原理图解读(看图说话)

  • 按键按下 = 低电平 = 引脚读到 0

  • 按键松开 = 高电平 = 引脚读到 1

:round_pushpin: 独立按键(4个独立开关)

 P3.4 引脚 → 按键1
 P3.5 引脚 → 按键2  
 P3.6 引脚 → 按键3
 P3.7 引脚 → 按键4

就像你家墙上的4个独立电灯开关

:puzzle_piece: 矩阵按键(4×4键盘)

用4个行线 + 4个列线,组成16个按键的键盘。

  • 行线:控制哪一行“通电”

  • 列线:检测哪一列“被接通”

就像在地图上找坐标:先确定行,再确定列


:two: 模块化编程:把代码装进“盒子”

:package: 为什么要模块化?

想象你要做菜:

  • :cross_mark: 糟糕的方式:每次做菜都从头种菜、养鸡、榨油…

  • :white_check_mark: 聪明的方式:把常用的油、盐、酱、醋装在调料盒里,随取随用

模块化编程就是把常用功能装进“代码盒子”

:memo: 函数定义(制作调料盒)

 返回值类型 函数名(参数)  // 盒子标签
 {
     // 函数体(盒子里的内容)
     // 实现特定功能的代码
 }

示例:延时函数(就像“等一会儿”)

 void Delay(unsigned char xms)  // 要等多长时间?(单位:毫秒)
 {
     unsigned char i, j;
     while(xms--)  // 数够指定的次数
     {
         i = 2;
         j = 259;
         do {
             while(--j);  // 空循环,消耗时间
         } while(--i);
     }
 }

:three: 实战编程:让按键“活”起来

3.1 独立按键控制LED(一按一亮)

 /* 按键读取函数:检查谁被按了 */
 unsigned char Key_Read()
 {
     unsigned char temp = 0;  // 临时记分牌,记录哪个键被按
     
     // 检查每个引脚,就像老师点名
     if(P3_4 == 0) temp = 1;  // 按键1被按
     if(P3_5 == 0) temp = 2;  // 按键2被按
     if(P3_6 == 0) temp = 3;  // 按键3被按
     if(P3_7 == 0) temp = 4;  // 按键4被按
     
     return temp;  // 返回“点名结果”
 }
 ​
 /* 主程序:不断检查按键并控制LED */
 void main()
 {
     while(1)  // 永不停止的循环
     {
         // 关键技巧:检测按键的“按下瞬间”和“松开瞬间”
         Key_Val = Key_Read();  // 读取当前按键状态
         Key_Down = Key_Val & (Key_Val ^ Key_Old);  // 检测“按下瞬间”
         Key_Up = ~Key_Val & (Key_Val ^ Key_Old);   // 检测“松开瞬间”
         Key_Old = Key_Val;  // 记住这次的状态,下次对比用
         
         // 控制LED
         if(Key_Down == 1)    // 按键1按下瞬间
             P1_0 = 0;        // LED1亮(0代表亮,1代表灭)
         
         if(Key_Up == 2)      // 按键2松开瞬间  
             P1_0 = 1;        // LED1灭
         
         // 按键3:按住就亮,松开就灭(像手电筒)
         if(Key_Old == 3)     // 按键3正被按着
             P1_1 = 0;        // LED2亮
         else
             P1_1 = 1;        // LED2灭
     }
 }

:bullseye: 理解键值检测
想象侦探查案:

  • Key_Old:上次现场照片

  • Key_Val:这次现场照片

  • Key_Down:通过对比发现新出现的指纹(按下瞬间)

  • Key_Up:通过对比发现消失的指纹(松开瞬间)

3.2 按键控制流水灯(开关和调速)

 /* 变量声明 */
 bit System_Flag = 0;       // 系统标志:0=停止,1=运行
 unsigned int time = 500;   // 流水速度:数字越大越慢
 ​
 /* 主程序逻辑 */
 void main()
 {
     while(1)
     {
         // 1. 检测按键(同上)
         Key_Val = Key_Read();
         Key_Down = Key_Val & (Key_Val ^ Key_Old);
         Key_Old = Key_Val;
         
         // 2. 如果系统正在运行,就让灯“流水”
         if(System_Flag == 1)
         {
             ucLed = _crol_(ucLed, 1);  // 将灯的状态向左旋转1位
             P1 = ucLed;                // 更新LED显示
             Delay(time);               // 等待一段时间(控制流速)
         }
         
         // 3. 根据按键控制
         switch(Key_Down)  // switch比多个if更清晰
         {
             case 1:  // 按键1:开始流水
                 System_Flag = 1;
                 break;
             case 2:  // 按键2:停止流水  
                 System_Flag = 0;
                 break;
             case 3:  // 按键3:减速(时间+100)
                 time += 100;
                 break;
             case 4:  // 按键4:加速(时间-100)
                 if(time > 100)  // 防止过快
                     time -= 100;
                 break;
         }
     }
 }

3.3 矩阵按键扫描(16键键盘)

 /* 矩阵按键扫描:像在表格里找位置 */
 unsigned char Key_Read()
 {
     unsigned char temp = 0;
     
     // 第一行:让P3.0=0,其他行=1,然后检查列
     P3_0 = 0; P3_1 = 1; P3_2 = 1; P3_3 = 1;
     if(P3_4 == 0) temp = 1;   // (1,1)
     if(P3_5 == 0) temp = 2;   // (1,2)
     if(P3_6 == 0) temp = 3;   // (1,3)
     if(P3_7 == 0) temp = 4;   // (1,4)
     
     // 第二行:让P3.1=0,其他行=1
     P3_0 = 1; P3_1 = 0; P3_2 = 1; P3_3 = 1;
     if(P3_4 == 0) temp = 5;   // (2,1)
     // ... 以此类推
     
     return temp;  // 返回按键编号1-16
 }

:bar_chart: 矩阵按键原理
就像在Excel表格里找单元格:

  1. 选中第一行(让第一行接地)

  2. 检查哪一列被接通(读列线状态)

  3. 得到坐标(行,列) → 按键编号

  4. 重复扫描所有行


:graduation_cap: 核心要点总结

概念 比喻 关键代码 作用
按键检测 侦探对比现场变化 Key_Down = Key_Val & (Key_Val ^ Key_Old) 捕捉按下瞬间
独立按键 一排独立开关 if(P3_4 == 0) temp=1; 简单控制
矩阵按键 坐标网格查找 行扫描 + 列检测 节省引脚(16键只用8线)
模块化 调料盒分类存放 unsigned char Key_Read() 代码复用、清晰
流水灯控制 传送带搬运 _crol_(ucLed, 1) LED循环点亮

:light_bulb: 学习建议

  1. 先理解原理:把按键想象成开关,把单片机想象成聪明的管家

  2. 从简单开始:先搞定1个按键控制1个灯,再逐步增加复杂度

  3. 多用比喻:技术概念往往有生活对应物,找到它就容易理解

  4. 动手实验:代码要自己敲、自己改参数,看实际效果

记住:按键是人机交互的基础,掌握了它,你就能让单片机“听懂”你的指令了!:rocket:

完整代码:

 /*头文件区域*/
 #include <REGX52.H>
 #include <intrins.h>
 ​
 /*延时函数*/
 void Delay(unsigned char xms)
 {
     unsigned char i,j;
     while(xms--)
     {
         i=2;
         j=259;
         do
         {
             while(--j);
         }while(--i);
     }
 }
 ​
 /*变量声明*/
 unsigned char ucLed=0xFE;
 unsigned char Key_Val,Key_Down,Key_Up,Key_Old;
 unsigned int time = 500;
 bit System_Flag;
 ​
 /*按键读取函数*/
 unsigned char Key_Read()
 {
     unsigned char temp=0;//选中行扫描列
     P3_0 = 0;P3_1 = 1;P3_2 = 1;P3_3 =1;
     if(P3_4 == 0) temp=1;
     if(P3_5 == 0) temp=2;
     if(P3_6 == 0) temp=3;
     if(P3_7 == 0) temp=4;
     P3_0 = 1;P3_1 = 0;P3_2 = 1;P3_3 =1;
     if(P3_4 == 0) temp=5;
     if(P3_5 == 0) temp=6;
     if(P3_6 == 0) temp=7;
     if(P3_7 == 0) temp=8;
     P3_0 = 1;P3_1 = 1;P3_2 = 0;P3_3 =1;
     if(P3_4 == 0) temp=9;
     if(P3_5 == 0) temp=10;
     if(P3_6 == 0) temp=11;
     if(P3_7 == 0) temp=12;
     P3_0 = 1;P3_1 = 1;P3_2 = 1;P3_3 =0;
     if(P3_4 == 0) temp=13;
     if(P3_5 == 0) temp=14;
     if(P3_6 == 0) temp=15;
     if(P3_7 == 0) temp=16;
     return temp;
 }
 ​
 ​
 /*Main*/
 void main()
 {
     while(1)
     {
         Key_Val = Key_Read();//读取键码值
         Key_Down = Key_Val & (Key_Val ^ Key_Old);//检测下降沿
         Key_Up= ~Key_Val & (Key_Val ^ Key_Old);//检测上升沿
         Key_Old= Key_Val;//扫描辅助变量
     
         if(System_Flag == 1)
         {
             ucLed = _crol_(ucLed,1);
             P1 = ucLed;
             Delay(time);
         }
         
         switch(Key_Down)
         {
             case 1:
                 System_Flag = 1;//开始
             break;
             case 2:
                 System_Flag = 0;//关闭
             break;
             case 3:
                 time += 100;//减速
             break;
             case 4:
                 time -= 100;//加速
             break;
        
     }
 }