C51 矩阵键盘+数码管 汇编功能实现


C51 矩阵键盘+数码管 汇编功能实现

要求

proteus仿真电路图

仿真电路

对应题目要求搭建。其中数码管的位选出加了三极管反相,是因为proteus里的这个四位数码管的位选是低电平驱动,而实验室里的真正的数码管是高电平驱动。

功能1

记录按键次数:启动时数码管显示“0”,按一下键盘数码管显示自动加一。

先将完整代码附上,之后详细进行讲解。

/*
      made by xx, xyz's bf
*/
ORG 0000H
AJMP MAIN
ORG 0100H

MAIN:    
    MOV R3,#00H
    MOV DPTR,#Table

LOOP:               //数码管动态扫描
    MOV A,R3 
    MOV R4,A

    MOV P1,#08H 
    LCALL DISP
    MOV P1,#04H
    LCALL DISP
    MOV P1,#02H
    LCALL DISP

    AJMP SCAN

DISP:              //数码管每位的数的显示,写成子函数的原因是使代码简洁
    MOV A,R4
    MOV B,#10
    DIV AB
    MOV R4,A
    MOV A,B        
    MOVC A,@A+DPTR    
    MOV P0,A
    ACALL D10MS    
    MOV P1,#00H
    RET                        

SCAN:                   //矩阵键盘扫描
    MOV P2,#0FH          //控制列的四位端口输出置为低
    MOV A,P2          
    ANL A,#0FH          
    CJNE A,#0FH,KCODE  
    AJMP LOOP          

KCODE:                   //扫描到按键按下,显示的数+1
    INC R3

XIAODOU:               //松手消抖
    MOV A,P2
    ANL A,#0FH
    CJNE A,#0FH,XIAODOU
    AJMP LOOP

D10MS:                  //延时10ms函数
    L3: MOV R6,#25
    L1: MOV R7,#250
    L2: DJNZ R7,L2
        DJNZ R6,L1
    RET

Table: DB 03FH,06H,05BH,04FH,66H,6DH,7DH,07H,7FH,6FH  //数码管显示0~9

END

延时函数和Table

首先先说一下最简单的,延时函数D10ms和Table,应该接触过C51汇编实现的都会知道这两部分,这里就简单说一下延时函数的原理,这里就是相当于C语言中的2个嵌套的for循环。外面的循环25次,内部的循环250次,这样就相当于总共执行的25×250=6250次DJNZ(如果不知道DJNZ是什么,可以去网上或文档里搜索),且执行一次DJNZ的机器周期为2,所以总共经历了6250×2=12500个机器周期。

而C51的时钟周期为11.0592MHz,我们这里把它认为是12MHz,一个机器周期包含12个时钟周期,所以一个机器周期为:

所以执行一次延时函数D10ms所用的时间为:

当然这里完全可以更精确些,但是因为本次程序对延时时间的要求不需要那么精准,所以没有更改。

数码管显示

MAIN:    
    MOV R3,#00H        //R3为数码管要显示的数值,在这里先初始化为0
    MOV DPTR,#Table //将Table数组的首地址放在16位寄存器DPTR中

LOOP: //数码管动态扫描
    MOV A,R3 //这两句代码的作用是使个位也可以调用DISP函数
    MOV R4,A

    MOV P1,#08H //数码管位选: 个位
    LCALL DISP //调用DISP子函数
    MOV P1,#04H    //数码管位选:十位
    LCALL DISP
    MOV P1,#02H    //数码管位选:百位
    LCALL DISP

    AJMP SCAN //无条件跳转到SCAN键盘扫描函数

DISP: //数码管每位的数的显示,写成子函数的原因是使代码简洁,否则得重复三遍
    MOV A,R4      
    MOV B,#10
    DIV AB //A除以10,商放在了A中,余数放在了B中
    MOV R4,A          
    MOV A,B        
    MOVC A,@A+DPTR //Table的首地址加上偏移地址之后的地址里的内容赋给A,这个内容就是要显示的数    
    MOV P0,A //把A赋给P0即可进行显示,P0口连接着数码管的段选
    ACALL D10MS    //延时10秒
    MOV P1,#00H    //把数码管位选口关上,和延时的目的都是为了程序的稳定正常运行
    RET

R0-R7都是C51汇编中的寄存器,可以拿来作为变量使用(一般使用R2-R7)。在这里使用了R3作为递增变量(在一开始初始化为0),R4是中间变量。R3是要显示的数(后面会提)。第一次R3整除10,余数就是要显示的个位,商放在R4中;第二次用R4整除10,余数就是要显示的十位,商放在R4中。然后再执行一次,此时的余数就是要显示的百位。

因为R3寄存器是8位的,所以它的范围为: 0-255。

关于程序MOVC A,@A+DPTR的易懂的解释(来源:网络):

本指令是将ROM中的数送入A中。本指令也被称为单片机查表指令,常用此指令来查一个已做好在ROM中的表格。

此条指令引出一个新的寻址办法:变址寻址。本指令是要在ROM的一个地址单元中找出数据,显然必须知道这个单元的地址,这个单元的地址是这样确定的:在执行本指令立脚点DPTR中有一个数,A中有一个数,执行指令时,将A和DPTR中的数加起为,就成为要查找的单元的地址。

查找到的结果被放在A中,因此,本条指令执行前后,A中的值不一定相同。

例:有一个数在R0中,要求用查表的办法确定它的平方值(此数的取值范围是0-5)

MOV DPTR,#TABLE
MOV A,R0
MOVC A,@A+DPTR
TABLE: DB 0,1,4,9,16,25

设R0中的值为2,送入A中,而DPTR中的值则为TABLE,则最终确定的ROM单元的地址就是TABLE+2,也就是到这个单元中去取数,取到的是4,显然它正是2的平方。其它数据也能类推。

矩阵键盘扫描

矩阵键盘扫描

P2.0-P2.3控制矩阵键盘的行,P2.4-P2.7控制矩阵键盘的列。

扫描的原理是先令高四位为低电平,即控制列的四个端口输出低电平,此时如果四个控制行的管脚有输出低电平

的,则意味着有按键被按下了。因为本次的功能是只检测是否有按键被按下,而不用检测具体哪个按键被按下了,所以会稍微简单一点,检测具体的按键的功能在第二个题目中有要求,所以后面再提。

SCAN: //矩阵键盘扫描
    MOV P2,#0FH    //控制列的四位端口输出置为低
    MOV A,P2 //P2的电平输出到A中
    ANL A,#0FH //将A的数与0FH相与,如果低四位有低电平,那么A将和0FH不相等
    CJNE A,#0FH,KCODE //如果A和0FH不相等,意味着有按键被按下,跳转至KCODE
    AJMP LOOP //无条件跳转至LOOP,重新开始循环

KCODE:                   
    INC R3 //扫描到按键按下,显示的数+1              

XIAODOU: //松手消抖
    MOV A,P2 
    ANL A,#0FH
    CJNE A,#0FH,XIAODOU
    AJMP LOOP

现在说一下松手消抖的原理,可以很明显看到松手消抖子函数的代码与SCAN中的几行非常相似。它的原理就是检测控制矩阵键盘的四行的P2口低四位是否还有低电平,如果有,说明键盘还在被按着,进入死循环。否则退出,跳转至LOOP重新进行循环。

仿真情况

按键按了十次之后

功能2

显示哪个键被按下:启动时数码管无显示,按下键盘,数码管显示该键盘所在的行列值。

完整代码为:

/*
      made by xx, xyz's bf
*/
ORG 0000H
AJMP MAIN
ORG 0100H

MAIN:
    MOV DPTR,#Table      
    MOV R3,#0FEH     
START:                 //加一个START的目的是要求说一开始不能有显示,
    CJNE R4,#0,LOOP     //如果不加的话一开始会显示0
    MOV P0,#00H
    AJMP SCAN
LOOP:                 //数码管动态扫描循环
    MOV A,R4
    MOV P1,#04H 
    MOVC A,@A+DPTR    
    MOV P0,A
    LCALL D20MS
    MOV P0,#0
    MOV A,R5
    MOV P1,#08H
    MOVC A,@A+DPTR    
    MOV P0,A
    LCALL D20MS
    MOV P0,#0

SCAN:                  //键盘扫描
    MOV A,R3
    MOV P2,A
    MOV A,P2          
    ANL A,#0F0H           //将#0FH与A相与,结果放在A中
    CJNE A,#0F0H,NEXT  //如果不相等,代表有按键按下 
    SETB C              
    MOV A,R3    
    RLC A              
    MOV R3,A
    CJNE A,#0EFH,START  //还没扫描完一遍,继续扫
    AJMP MAIN            //四行都检测完一遍,跳转至MAIN重新循环

NEXT:                 //已经检测到了按键按下,输出按键的行和列的位置
    SWAP A
    MOV R2,A
    LCALL COMP
    MOV R5,A

    MOV A,R3
    MOV R2,A
    LCALL COMP
    MOV R4,A
    AJMP XIAODOU    

COMP:                //计算按下的按键的行数和列数
    MOV A,R2
    ANL A,#01H
    JZ C1
    MOV A,R2
    ANL A,#02H
    JZ C2
    MOV A,R2
    ANL A,#04H
    JZ C3
    MOV A,R2
    ANL A,#08H
    JZ C4
    C1: MOV A,#1
        RET
    C2:    MOV A,#2
        RET
    C3: MOV A,#3
        RET
    C4: MOV A,#4
        RET

XIAODOU:              //松手消抖
    MOV A,P2
    ANL A,#0F0H
    CJNE A,#0F0H,XIAODOU
    AJMP START

D20MS:                 //延时20ms函数
    L3: MOV R6,#50
    L1: MOV R7,#250
    L2: DJNZ R7,L2
        DJNZ R6,L1
    RET

Table: DB 03FH,06H,05BH,04FH,66H,6DH,7DH,07H,7FH,6FH  //数码管显示0~9

END

延时函数和Table数组通要求一,在这里不再讲解。

数码管显示

因为此次显示的是所按按键的行数和列数,所以只用到了两位数码管。

LOOP: //数码管动态扫描循环
    MOV A,R4 //R4中为所按按键的行数
    MOV P1,#04H //数码管位选:显示行数的位
    MOVC A,@A+DPTR //要求一中有详细解释
    MOV P0,A    
    LCALL D20MS //延时20ms
    MOV P0,#0 //关闭位选,和延时函数的功能一样:保证程序稳定正常运行
    MOV A,R5 //R5中为所按按键的列数
    MOV P1,#08H //数码管位选:显示列数的位
    MOVC A,@A+DPTR    
    MOV P0,A
    LCALL D20MS //延时20ms
    MOV P0,#0 //关闭位选,和延时函数的功能一样:保证程序稳定正常运行

本次的数码管显示要求是要比上一个简单,显示两个数:按键的行数和列数,而不像要求一一样显示一个百位的数,还需要先把百、十、个位分离才能进行显示。

矩阵键盘扫描

MOV R3,#0FEH //R3是用来输出给P2的,使P2的低四位(控制行的端口)轮流输出低电平,达到"扫描"的目的
             //0FEH即代表最低位时低电平,即控制第一行的端口输出为低电平。当16进制最高位是字母时要加个0
SCAN: //键盘扫描
    MOV A,R3 
    MOV P2,A //把R3赋给P2,对应端口输出低电平
    MOV A,P2 //将P2电平返回,在对应行有按键按下,则P2的高四位中有低电平存在          
    ANL A,#0F0H //将#0FH与A相与,结果放在A中
    CJNE A,#0F0H,NEXT //如果不相等,代表有按键按下 
    SETB C //C是进位标志位,这里指将溢出位置为高电平          
    MOV A,R3     
    RLC A //带进位标志位循环左移          
    MOV R3,A 
    CJNE A,#0EFH,START //还没扫描完一遍,继续扫
    AJMP MAIN //四行都检测完一遍,跳转至MAIN重新循环

首先来讨论一下带进位标志位的循环左移问题:

带进位标志位的循环左移

举个例子,现在我们要循环左移一个数A=10000001,如果现在进位标志位时0的话,循环左移后的结果为00000010,最高位的1进入进位标志位,进位标志位中的0到达数A的最低位;同理,当进位标志位为1时,结果为00000011,进位标志位变为1。

现在回到矩阵键盘扫描的程序,一开始初始化R3为0FEH,即11111110,赋给口之后会使P2.0输出低电平,即控制第一行的端口输出低电平,来扫描第一行的按键有没有被按下,如果此时在第一行有被按下的按键,则高四位,即控制列的端口一定有电平为低的。例如,如果按下的按键是第一行第一列,现在R3=0FEH赋给了P2,则会导致控制第一列的端口P2.4也输出低电平。

如果第一行没有扫描到按键,则接下来会继续扫描第二行,这里就用到了循环左移,R3本来使FEH,即111111110,现在设进位标志位C=1, 则由上面描述的循环左移的原理可知,R3循环左移一次的结果是11111101, 即FDH,看出来了吗,这个R3=11111101赋给P2的话,就是检测第二行了!之后第三、四行同理。

之后的一句:

CJNE A,#0EFH,START //还没扫描完一遍,继续扫

意思就是当移位到11101111的时候,表示移位过头了,那么接下来就应该重头开始循环左移了呀。

计算行和列的位置

当我们检测到有按键按下之后,接下来要做的就是确定是哪个按键按下,即确定按下的按键的行和列数。

NEXT: //已经检测到了按键按下,输出按键的行和列的位置
    SWAP A //A高四位和低四位位置互换,即:11010000→00001101
    MOV R2,A //COMP函数的输入参数为R2         
    LCALL COMP //调用计算行数和列数的子函数     
    MOV R5,A //R5为计算得到的列数

    MOV A,R3 //R3为我们自己设的用来按键扫描的数,控制行的低四位轮流为低电平,这里就是确定哪个为低电平
    MOV R2,A //COMP函数的输入参数为R2
    LCALL COMP //调用计算行数和列数的子函数
    MOV R4,A //R4为计算得到的行数
    AJMP XIAODOU //此部分程序结束,跳转至“消抖”部分    

COMP: //计算按下的按键的行数和列数
    MOV A,R2 
    ANL A,#01H //检测是否是第一行/列
    JZ C1 //上一行相与的结果如果是0,则跳转至C1
    MOV A,R2 
    ANL A,#02H //检测是否是第二行/列
    JZ C2 //上一行相与的结果如果是0,则跳转至C2
    MOV A,R2 
    ANL A,#04H //检测是否是第三行/列
    JZ C3 //上一行相与的结果如果是0,则跳转至C3
    MOV A,R2 
    ANL A,#08H //检测是否是第四行/列
    JZ C4 //上一行相与的结果如果是0,则跳转至C4
    C1: MOV A,#1 //行/列数是1
        RET //退出子函数
    C2:    MOV A,#2 //行/列数是2
        RET
    C3: MOV A,#3 //行/列数是3
        RET
    C4: MOV A,#4 //行/列数是4
        RET

对于这个部分,或许举个例子可以更容易理解。

假设我们按下的按键是第三行、第二列。那么键盘扫描至第三行,即R3=0FBH,即R3=11111011时,高四位中控制第二列的端口P2.5会为低电平。这部分一开始的A来自于SCAN函数第四行与0F0H相与后的A,即本来A是P2的电平值,在这里就是11011011,相与后就是11010000,SWAP高低四位互换后就成00001101,当这个数与02H相与后结果就是0,对应就会输出列数为2。

下面来确定行数。我们进行键盘扫描时轮流使控制每一行的端口输出低电平,即使R3循环左移(还记得嘛,不记得就往上翻翻),所以我们就可以R3来获取它的行数。当按下的按键在第三行时,R3=11111011,取反后就是R3=00000100,与04H相与后结果是0,对应就会输出行数为3。

这一部分可能有点绕,事实上我写程序的时候也绕晕了会儿哈哈,需要耐心来进行思考和研究哦。

一开始不能有显示

这一部分其实不难,我借助的是行数和列数,一开始没有按键按下去,则没有计算获取行数和列数,所以储存行数和列数的寄存器R4和R5都是0,当获取之后两个都不可能是0,因为行和列的范围都是1-4,借助这个信息就可以完成这个功能:

START:                 
    CJNE R4,#0,LOOP    //如果R4不是0,则进入LOOP数码管扫描循环 
    MOV P0,#00H
    AJMP SCAN //R4是0,跳过数码管扫描循环,直接进入矩阵键盘扫描循环,所以数码管不显示

仿真情况

一开始未按按键时

按下第三行、第二列的按键

END


文章作者: Mat Jenin
文章链接: http://matjenin.xyz
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Mat Jenin !
  目录