矿石收音机论坛

 找回密码
 加入会员

QQ登录

只需一步,快速开始

搜索
楼主: wey05

建议先这样下刀,用uVision 3切入。。。

[复制链接]
 楼主| 发表于 2007-8-8 11:12:03 | 显示全部楼层
还是用一个稍微完整的例子说明定时器程序,如下:
ORG    0000H
          AJMP MAIN

ORG    000BH                         ;000BH是定时器C/T0的中断入口,跳到SUB_CT0处理
          AJMP SUB_CT0

ORG    0030H

MAIN:  MOV SP,#30H
           SETB EA                      ;开总中断
           SETB ET0                    ;开C/T0的中断,否则不响应定时中断,就不能定时
   
           MOV TMOD,#01H         ;C/T1为13位定时器,C/T0为 16位定时器
           MOV TH0,#00H            ;高位计数装入设置值0
           MOV TL0,#06H             ;低位计数装入设置值6,定时时间T=(65536-6)*1=65530微秒(机器周期1微秒)
          
           MOV A,#0FEH               ;P1口亮灯
           CPL A
            MOV P1,A

ATT:   SETB TR0                ;启动16位定时器T0
          JNB F0,$                  ;F0是定时中断中设置的标志,它置位说明定时到,否则踏步
                       ;等待中断
          CLR F0                    ;定时到清除标志
          RR A                     ;移位亮灯
          MOV P1,A
          AJMP ATT              ;循环定时
                     
SUB_CT0: CLR TR0          ;定时中断,先停止定时
         CLR ET0                ;关闭中断
         CLR EA
         SETB F0                ;设置定时到标志,注意必须和PSW一起保存
        PUSH PSW              ;保存PSW 和A
        PUSH ACC
                     
        MOV TH0,#00H        ;重新加载设置值,否则定时到达后均0
        MOV TL0,#06H
                 

        SETB ET0                          ;开中断准备下次定时
        SETB EA
                 
         POP ACC                          ;还原A和PSW
         POP PSW
        
        RETI                                ;T0中断返回
                  
          END
这样就用定时器实现了流水灯。

[ 本帖最后由 wey05 于 2007-8-8 13:14 编辑 ]
 楼主| 发表于 2007-8-9 15:21:35 | 显示全部楼层

用2个18位定时器做一个秒信号发生器

从上面我们知道,用12MHz晶体的C51,定时计数器的方式1最大定时是65536微秒即65.536毫秒,取预置值15536,可得50毫秒的定时值。实际上可以考虑更加精确的定时:从计数满溢出触发中断--执行完现行指令(或再执行下调指令)--生成中断入口--程序指针压栈--从中断入口转到CTn中断子程序需要耗费8个机器周期(具体和中断发生时的现行指令有关)所以把预置值再加8就是15544,让定时器缩短8微秒在中断响应过程中补回来,就得到更加精确的定时。用系统附件中的科学计算器,容易求出对应的16进数是3CB8H,所以就给THn装入3CH,TLn装入0B8H来得到50毫秒定时。然后可以反复定时20次来得到1000毫秒=1秒的定时。还有一个办法就是轮流使用C/T0 C/T1,10次得到1秒定时。
    实际程序如下:用R1作为计数器,每秒钟给它加1,结果以2进制码驱动P1口的灯
ORG    0000H
       AJMP MAIN

ORG    000BH                 ;定时器0中断入口
       AJMP SUB_CT0
ORG    001BH                 ;定时器1中断入口
       AJMP SUB_CT1

ORG    0030H

MAIN:  MOV SP,#30H
      
           SETB EA            ;开总中断
           SETB ET0           ;开T0中断
           SETB ET1           ;开T1中断
      
           MOV TMOD,#11H  ;T0 T1均设置为方式1:16位定时器
           MOV TH0,#3CH     ;T0高预置值如上述
           MOV TL0,#0B8H    ;T0低预置值如上述

           MOV TH1,#3CH     ;T1高预置值如上述
           MOV TL1,#0B8H    ;T1低预置值如上述
           MOV R1,#1           ;R1计数器初值1,以后每秒加1
           MOV A,R1             ;
           CPL A                    ;
                    MOV P1,A             ;R1的值取反后送P1,以便灯亮表示1(实际是0)
        
                  SETB TR0               ;启动定时器T0
        ATT:   MOV R7,#10           ;R7是定时次数计数器,从10倒计数到0就是1秒
                   JNB F0,$          ;F0是PSW中的用户标志位,T1定时10次(从T0启动1秒)置1,未到在这里踏步等待
           CLR F0                   ;清除F0标志以便后续使用,
           INC R1                    ;每秒R1加1,亮灯显示
           MOV A,R1
           CPL A
           MOV P1,A
           AJMP ATT                ;再次循环到R7初值
          
  SUB_ CT0: SETB TR1                 ;T0 50毫秒中断,启动T1定时
                   CLR TR0                  ;停止T0定时
                   CLR ET0                  ;关闭中断
                   CLR EA
           PUSH PSW               ;保存PSW,A
           PUSH ACC
           MOV TH0,#3CH        ;再次给TH0 TL0设置值
           MOV TL0,#0B8H
           SETB ET0                 ;开中断
           SETB EA
           POP ACC                  ;恢复PSW,A
           POP PSW
           RETI                        ;中断返回,注意,定时器T1早在这之前已经启动

SUB_CT1:   SETB TR0                  ;定时器T1中断,基本和T0中断相同
                  CLR TR1
                  CLR ET1
                  CLR EA
          DJNZ R7,NEXT            ;R7每100毫秒递减一次,未到1秒不为0,跳到NEXT
          SETB F0                     ;1秒到,设置标志F0,注意,此位在PSW中必须压栈前设置,否则必须另设标志
                           
NEXT:          PUSH PSW
          PUSH ACC
          MOV TH1,#3CH
          MOV TL1,#0B0H
          POP ACC
          POP PSW
              SETB ET1
          SETB EA
          RETI

          END
       把它整进uVisuon,Build all-->Debug-->Run跑起来,怎么样?可以同时打开Peripherals-->Tomers->T0,T1如图。
TARGET3A.jpg

[ 本帖最后由 wey05 于 2007-8-9 15:51 编辑 ]
     
发表于 2007-8-9 17:31:23 | 显示全部楼层

顶顶...

顶顶...但愿能继续写下去...

进展快慢无所谓...能写到高级应用更好..
 楼主| 发表于 2007-8-12 01:06:34 | 显示全部楼层
再就是关于51的串口123:为了和外部交流信息,串口具有很重要的意义。C51的P3.0和P3.1引脚就是可以用于串行数据收发的引脚RXD(收)和TXD(发)。作为初步了解51机]的串口通信,我们必须记住:
    1。串口有4种工作模式:MOD0,MOD1,MODE2,MODE3
        其中,MODE0是把串行数据缓冲器SBUF(实际上收发各一个,同一地址)中的数据通过RXD线移位送出,或把外部串行数据通过同一线移入,TXD则固定发送移位脉冲控制外部设备同步,实际上多用于数据的串行显示。
     MODE1,用途最广,通过TXD发送,RXD接受。通信速度--波特率由内部定时器T1控制。通信时,发送1个起始位0,8位数据(从最低位发起),1个停止位1,一共10位数据。
     MODE2 ,MODE3在停止位之前插入1位(俄作校验用)一共11位。
     重点掌握MODE1。
    2。收发,从程序上没有什么特别机关:只是取存SBUF:
     收:MOV A,SBUF
         发:MOV SBUF,A
       3。为了让串口有效工作,必须事先设置串口有关的特殊寄存器:
SCON:各位可以位寻址
SM0         SM1         SM2           REN          TB8             RB8                  TI                RI
0              0           多机通信   1收允许   发数据位9  收数据位9   发中断标志  收中断标志
0              1                              0禁止收                        硬件自动置位1,可以程序清除
1              0
1              1
组合对应方式0,1,2,3
因此,对于方式1,这样设置:
MOV SCON,#50H    ;SMO:0,SM1:1,REN:1
PCON:一般只设置最高位SMOD为1:波特率加倍,为0,不加倍,(参见下表):
MOV PCON,#80H

以及公用的中断允许寄存器IE:中的EA(总中断允许),ES:串行中断允许。
    4。定时器T1工作于模式2:自动重载设置值的方式,但是不要开启定时中断ET1。他的预制值TH1(自动加载到TL1)决定了串行通信模式1、3的通信速率:波特率列表如下:

晶振频率      SMOD位          TH1 /TL1        波特率
  12MHz              1            0F3H             4800
    12                    0                     0F3H             2400
    12                    1                     0E6H             1200
    11.0592            0                     0E8H             1200
    11.0592            0                     0F4H             2400
    11.0592            0                     0FAH             4800
    11.0592            0                     0FDH             9600
    11.0592            1                     0FDH          119200

    5。串口的初始化因此也包括对定时器T1的初始化:例如11.0592晶振,9600波特率:
  MOV  SCON,#50H    ;SM1  REN 置位,模式1,接受允许
   CLR    SMOD            ;不倍乘
    SETB  M1                ;T1方式2
    CLR    M0
   MOV  TL1,#0FDH      ;T1预制值
   MOV  TH1.#0FDH
   SETB  TR1               ;启动T1
   SETB  EA                 ;开中断
   SETB  ES
就可以啦。

[ 本帖最后由 wey05 于 2007-8-12 09:27 编辑 ]
 楼主| 发表于 2007-8-13 14:44:15 | 显示全部楼层
下面我们进行模拟串口通信实验,程序如下:
ORG    0000H
          AJMP MAIN

ORG    0023H
          AJMP SER_INTSUB  ;串口中断,注意虽然用定时器T1,却不让他中断

ORG    0030H
MAIN:  MOV SP,#30H
           ACALL  SER_INIT    ;调用串口初始化
           SETB EA                ;开总中断
           SETB ES                ;开串口中断
WAIT:   SJMP $                 ;等待外部串行信号输入

SER_INTSUB:                    ;串口中断处理:
          JBC  RI,NEXT           ;如果有信号输入,RI会置位1,转下面
          SJMP  SEND            ;如果RI未置位,是发送引起中断,转下面清除发送标志
NEXT: MOV A,SBUF           ;有信号输入,接收信号到A
          CPL A                     ;A反相,为的LED显示接收数码
          MOV P1 ,A              ;P1口发光管显示接收的二进码
          CPL A                     ;还原A内容
          MOV SBUF,A           ;把刚收到的内容发回去
          AJMP  OVER            ;结束1次收发
SEND:   CLR TI
OVER:    RETI

SER_INIT:                         ;串口初始化
        MOV  SCON,#50H      ;使用模式1;0起始,8数据,1停止
        ORL TMOD,#20H       ;定时器T1模式2,自动重载TL1计数值
        ORL PCON,#80H        ;波特率翻倍,4800--9600
        MOV TH1,#0F3H        ;重载值,12MHz的波特率4800,见上次内容
        MOV TL1,#0F3H        ;计数值,
        SETB TR1                 ;启动定时器
        SETB REN                 ;允许接收
        RET
END
       光有程序还不行,为了模拟,把电脑后面2个真正的串口按照互相收发连接起来:串口1的3对串口2的4,串口1的4对串口2的3,公共线5-5对联,有9眼D型互连插头最好,还要去下载一个绿色小软件串口助手(例如:xie-gang.com/chuankou.exe),解压后打开就运行:
TARGET7.jpg
这时指定PC使用串口COM1,也就是串口助手收发的串口,
还要把模拟单片机指定使用串口COM2,为此先在KEIL上进入程序DEBUG状态然后在左下脚命令窗口输入:
MODE COM2 9600,0,8,1
ASSIGN COM2 <SIN>SOUT如下:
TARGET7A.jpg
现在可以运行程序,他在等待,所以没有动静
在串口助手下方先清空重填一个16进数码48,
TARGET7B.jpg
再按手动发送,结果如下:
TARGET7c.jpg
成功啦

[ 本帖最后由 wey05 于 2007-8-13 15:36 编辑 ]
 楼主| 发表于 2007-8-17 09:46:21 | 显示全部楼层
51单片机的“位指令”也是一个很有用的方面,除了一般在程序中自动或人工对某些“位”进行置1和清0操作来“记忆”程序发生过的状态以外,通过位指令编程可以使单片机起到“触点控制”的效果,或者说犹如一台微型的可变程序控制器,让它来实现过去用继电器实现的功能。先看一个电工中最经典,资格最老的启动-停止按钮控制电动机的例子,通常,在电工里用触点和连线、线圈构成的“梯形图”表示:

  |-----||--------|
   |       C           |
   |                    |
|--|-----||--------|-------|/|--------|/|---------( )-----|                          220V -----||-------O
           A1                    A2           A3            C                                                  C        M

       “常开“按钮A1用作“启动”,“常闭”按钮A2用作“停止”,另一个常闭触点A3是自动保护触点,例如电动机过负荷,电流太大就自动断开,和他们串联在一起的是继电器线圈C,和A1并联的C触点是C的辅助触点,C的主触点C来控制电动机和220V电源的通断使得电动机:“启动””停止“。工作原理是按下启动按钮A1,停止按钮A2未按下,通路,A3在设备正常时通路,于是继电器线圈C得电吸合,一方面主触点C接通使电动机M转起来,另一方面和A1并联的辅助触点C闭合使得C线圈保持通电,即使你这时断开了A1,C也吸合,除非:1,设备故障,A3一直断开,继电器释放,电机停止,而且按A1也不能启动;2,按下停止按钮A2,它们断开,C释放,C的辅助触点C和启动按钮A1都断开,即使松开A2使他接通,继电器也不会得电除非你再次按下A1。
    下面我们用51的位指令来实现这个功能;现在还是用周先生的仿真模型,让P3.2代替A1,P3.3代表A2,P3.4代表A3,因为状态相反,图中A1闭合相当“1”(置位)断开表示“0”(清位),而模拟单片机是不按P3.X是1,按下接地是0,所以程序中用“反”来表示,具体如下:
ORG 0000H
       AJMP MAIN

ORG 0030H
MAIN:  MOV SP,#30H
           ORL C,/P3.2     ;进位位C起到继电器的作用,“位或“运算就是触点并联,取反P3.2,按下为1
           ANL C,P3.3      ;上面的结果是0还是1都存入C,然后和P3,3串联:“位与”,结果存入C
           ANL C,P3.4      ;再和P3.4串联
           JNC NEXT        ;如果最后C为0,转NEXT,P1.0高,灭灯
           CLR P1.0         ;如果C为1,P1.0低,灯亮
           SJMP MAIN      ;循环监测输入P3.X状态
NEXT:  SETB P1.0
           SJMP MAIN
END
      运行起来后,按P3.2,灯亮松开也亮,按P3.3,或P3.4灯灭,而且在P3.4保持按下(用鼠标点住P3.4并拖开),按P3.2不起作用。
    运行效果如图:
TARGETBIT.jpg
 楼主| 发表于 2007-8-18 15:24:19 | 显示全部楼层
今天说说单片机控制“8字”数码管的技术。早期电子设备用的气体放电数字管:阳极做成123。。90的形状,高压驱动,现在已经成为古董了。LED的8字形数码管,因为接口和编程简单,亮度高,所以现在还有广泛的使用。常用的LED数码管,由7个“字段电极”和1个“小数点电极”再加上1个公共电极组成,本质上就是8个发光二极管。除了发光颜色和尺寸大小不同,分为所有阳极接公共电极构成“共阳极”管和所有阴极接公共电极构成“共阴极”管两类,早期单片机口线低电位吸收电流能力较大而高电位吐出电流较小,所以使用共阳极管较多,现在的单片机吸入和吐出电流的能力都比较大,两种管子都一样用。以共阳极管为例,各字段电极排列和名称如下:
8.jpg
显然公共+极接+,a--g通过限流电阻接单片机某口,例如P0口,各字段电极对应口线:
h          g            f            e           d            c          b         a
P0.7     P0.6        P0.5       P0.4      P0.3       P0.2      P0.1     P0.0
如果
   MOV R1,#90H
      MOV P0,R1
则P0引脚电位为#90H,也就是二进数1001 0000,口线P0.7和P0.4为高电位,结果数码管的字段h和e也为高电位,没有电流不发光,其余各电极均为低电位有电流通过发光对照上图,可见这时显示出来“9”字。共阳极数码管的显示字形代码和实际显示数码对应如下:
      0        1          2          3           4           5         6         7            8          9
     C0H      F9H      A4H      B0H       99H      92H      82H       F8H       80H      90H   
   当然还可以显示A,b,C,d,E,F,H,J,L,n,o,P,r,U,Y,-,等符号,就不多说了。
  对于这些不变的字形代码,通常是把他们按顺序保存在单片机的ROM区,形成“字形表”,用查表指令通过A中的数码取出此数码位置对应的字形代码,再把这些代码送到接数码管的i/o口,使数码管的相应字段发光,从而显示出来所需要的数字。具体程序明天再说,注意:现在需要利用周坚先生开发的第二个模拟单片机了,还是把下载的 simboard.dll考入..\c51\bin文件夹,然后在keil的菜单:Project-->Option for Target 'Target1'打开对话框,在Debug选项卡的左下角Parameter框中原有-p51后面输入-dsimboard,就可以在调试状态打开带数码管和键盘矩阵的仿真单片机了。

[ 本帖最后由 wey05 于 2007-8-18 15:27 编辑 ]
 楼主| 发表于 2007-8-19 10:57:45 | 显示全部楼层
上面对7段LED数码管的工作情况已经有所了解,不过还有两个技术问题:
1。数码管的驱动电流:虽然LED数码管字段电极不需要很大电流例如10毫安以下,就可以得到满意的亮度,可是每个管子如果显示“8”和显示“1”总电流显然不同,假定每个字段仅仅给他5毫安电流,那么显示“1”,总电流10毫安,显示“8”,总电流达到5*7=35毫安,现在的单片机尽管口线的驱动能力都比较高,实际上也就是20毫安,可见,要通过口线直接驱动数码管的公共电极还是有问题的,这个可以通过加接驱动晶体管,例如8550中功率管,或电流驱动集成块例如7406(注意不是74LS06)ULN2003等增强口线的负载能力。
2。1个管子好说,多个数码管怎么办?有很多方法可以解决,例如使用多片8位锁存器,每片对应1个数码管,循环给他们更新每个管子的显示数据,这样管子一直在工作,所谓静态显示,优点是亮度高,不闪烁,缺点要多用硬件,另外一个使用最广泛的方法就是所谓“动态显示”让多个数码管7段数据线都并接,然后每次只让1个管子“选通”:给它的公共电极加电一段时间例如5毫秒,再更新显示数据,选通下一个管子......循环,8个管子全部扫描1次,以每管发光5毫秒计算,也仅仅40毫秒,每秒钟可以刷新显示25次,由于眼睛的视觉暂留效应,使得看起来8个管子都在同时发光。这样实现起来并不困难,也不增加硬件,所以使用很广,注意它需要占用两套I/O口,例如P0口送出显示数据--字段代码,P2口扫描各个公共电极(“位线”),当然要注意当外加前述驱动电路后口线是高电位还是低电位选通树管子的驱动器。
3。为了达到多管动态显示的目的,通常在单片机的RAM区指定和数码管位数同样多的存储单元作为显示缓冲区,里面存放的是待显示数据:注意!!!不是段码!段码是通过读取某显示缓冲单元的数据再通过查ROM显示代码表得出,查表后就立即送口线,并选通相应的位线输出。   
      下面我们进行实验,用上次装上的数码管的模拟单片机,目标是:用单片机的定时器T0产生100毫秒中断,每次中断给8位数码管的最低位加1,满10向高位进1,循环显示8位数:
          汇编程序如下:
ORG 0000H
    AJMP MAIN
ORG 000BH
    AJMP INTSUB_T0

ORG 0030H
MAIN:                                               ;以下是初始化:T0模式1,预制值55536(16进数 D8F0H)得到10毫秒定时
     MOV SP,#30H
     MOV TMOD,#01H
     MOV R7,#10                                  ;       每定时中断10次计数--100毫秒,
     MOV TH0,#0D8H
     MOV TL0,#0F0H
     SETB EA                                         ;以下开中断,启动定时器
     SETB ET0
     SETB TR0

   
     MOV R1,#40H                                   ;指定显示缓冲区第一个单元在40H  
     MOV @R1,#0                                    ;这个单元内容写0
     MOV R2,#0FEH                                  ;R2存储“位选”值
     
START:
      JNB TF0,$                                        ;TF0是由定时器触发的标志,一旦监测到TF0置位就往下走,否则踏步等待
      MOV R1,#40H
                                                            ;以下是显示数据处理:每次从最低位开始如为10(0AH)往高位进位直到最高位
STA:  CJNE @R1,#0AH,NOCARRY           ;最低位不为10则直接递增
      MOV @R1,#0
      CJNE R1,#47H,NOLAST
      SJMP TODISP
NOLAST: INC R1
      INC @R1
      AJMP STA
NOCARRY: CJNE R1,#47H,NLAST
      AJMP TODISP
NLAST:INC R1
      AJMP STA
TODISP:                                        ;数据处理结束调用显示子程序,然后清除标志位F0
      ACALL DISPA
      CLR TF0
THISEND:
      AJMP START                            ;循环运行

INTSUB_T0:                                  ;定时中断处理
     MOV TH0,#0D8H                      ;重载预制值
     MOV TL0,#0F0H
     DJNZ R7,INTEND                     
     MOV R7,#10                            ;中断10次:100毫秒,重置计数值R7,发出启动标志F0
     SETB TF0
     CPL P1.0                                 1.0发光管每100毫秒亮灭转换1次
     INC 40H                                  ;最低位显示缓冲每100毫秒递增1

INTEND:RETI
                                                   ;显示
DISPA:MOV R1,#40H                    ;显示缓冲最低位
     MOV R2,#0FEH                       ;相应的位选通数值
GET: MOV A,@R1                         ;显示缓冲器内容查表
     MOV DPTR,#DISPTAB
     MOVC A,@A+DPTR                  ;A中是查出的相应字段代码
     CJNE R1,#41H, CON                 ;如果不是第2位,往前转移
     ANL A,#7FH                            ;如果显示第2位,要把字段代码最高位置0:显示小数点
CON: MOV P0,A             ;              字段码送P0口
     MOV P2,R2                            ;位选码送P2口
     ACALL DLY2MS                      ;延时显示
     MOV A,R2                             ;显示下一位
     RL A                                      ;
     MOV R2,A                               ;
     INC R1                                    ;
     CJNE R1, #48H,GET                 ;显示第8位后返回显示第1位
     MOV R2,#0FEH
     MOV R1,#40H

DSPEND:  RET

DLY2MS:MOV 65H,#50;0              :软件延时子程序,5毫秒
D1: MOV 64H,#100;0
    DJNZ 64H,$
    DJNZ 65H,D1
    RET

DISPTAB:                                  ;这就是ROM中的显示字段代码表0-9和全黑
DB 0C0H,0F9H,0A4H,0B0H,99H,92H,82H,0F8H,80H,90H,0C0H,0FFH

END

运行的程序截图如下:
TARGET8.jpg
注意:由下角是RAM内容,第一排0040H处连续8个单元就是显示缓冲区,你看其中内容是否和数码管显示一致?

有点意思?它还可以做什么呢?(这个虚拟的东西,P2.0对应最低位,不考虑口线电流驱动能力)

[ 本帖最后由 wey05 于 2007-8-19 12:03 编辑 ]
发表于 2007-8-21 19:53:42 | 显示全部楼层
受益匪浅~~呵呵谢谢了
 楼主| 发表于 2007-8-22 12:58:24 | 显示全部楼层
上次我们已经看到显示缓冲区在数码管显示的作用,实际上,待显示数据常常在其他内存单元中处理,最后给显缓区的仅仅是待显示数据,这样比较简单灵活,下面我们看一个接近实际的程序:他的意思是,用定时器T0产生10毫秒中断,每10个中断,给内存单元50H加1,加到10,给51H单元进位同时50H单元回0,继续循环,当51H单元加到60:等于16进数3CH,往52H单元加1并使51单元回0,52H单元加阿到60往53H单元加1,52H本身回0,53H单元累加到13回0,那么53H,52H,51H,50H构成了什么?我不说你也知道了吧。(所以就是不说),剩下的问题是如何用8个8字管把50H...53H这4个内存单元的数据显示出来,50H单元好说,因为它只有0到9,所以还是指定40H单元为最低位数码管的显示缓冲器,把50H单元的数据拷贝进40H就行了,而51H单元以后存的都是超过10的16进数(最大59:即3BH),所以我们给他来一个16进数--〉BCD码也就是一种二-十进数的转换,拜托51有除法指令使这事情不难弄:通过以下子程序,因为内存的数不超过59,所以更简单:
先把待转换十六进数考入A,然后用:
HEX2BCD:
      MOV B,#10   ;除数为10
      DIV AB        ;A/B  ,商数在A,余数在B
      SWAP A      ;A的高4位是0,当然,因为商不会大于9,然后高低4位交换,现在商在高4位,低4位为0
      ORL A,B
      RET            ;A或B,结果A的高4位是商,低4位是余数,商和余数分别存入两个显示缓冲单元就妥了,例如59/10,商5存51H,  余数5存52H,就把59拆开了
完整程序如下:
  
  ORG 0000H
    AJMP MAIN
ORG 000BH
    AJMP INTSUB_T0

ORG 0030H
MAIN:
     MOV SP,#30H
     MOV TMOD,#01H
     MOV R7,#10        ;       定时器10倍
     MOV TH0,#0D8H
     MOV TL0,#0F0H
     SETB EA
     SETB ET0
     SETB TR0            ;启动T0

     MOV R1,#40H       ;显缓区:40H...47H
     MOV @R1,#0
     MOV R2,#0FEH      ;R2是选通显示位用的
     MOV 47H,#0BH      ;查字码表11个字用,这个字是“H”
      
START: JNB TF0,$      ;等待定时中断10次的信号:100毫秒1次
      MOV R1,#50H      ;50H单元为显示数据首单元
     
STA:
      CJNE @R1,#0AH,TODISP  ;(50H)<10,转显示(注意50H单元的内容由T0中断10次累加,这里不用加)
      MOV @R1,#0           ;(50H)到达10,本身回0,给(51H)+1
      INC R1
      INC @R1                  ;查51H内容
      
      CJNE @R1,#60,TODISP  ;(51h)要是不满60转显示
      MOV @R1,#0                ;51H满60自己回0,给52H加1
      INC R1
      INC @R1
      
      CJNE @R1,#60,TODISP   ;查(52H),要是不满60转显示
      MOV @R1,#0                ;52H满60自己回0,给53H加1
      INC R1
      INC @R1
      
      CJNE @R1,#13,TODISP    ;查(53H) 要是不满13转显示(可以把他设置为25嘛?)
      MOV @R1,#0                 ;满13自己回0,没有可进位的,回到开始去检查50H
      MOV R1,#50
      AJMP STA

TODISP:                              ;显示子程序
        MOV 40H,50H              ;50H单元送显缓40H
        MOV A,51H                 ;51H单元送A后用16进--BCD处理:拆字
        ACALL HEX2BCD
        PUSH ACC                   ;保存处理后的A:高4位为商,低4位是余数
        ANL A ,#0FH               ;取余数存入41H,A的高4位变成0,没用了必须
        MOV 41H,A
        POP ACC                    ;取出事先保存的完整A
        ANL A,#0F0H              ;去掉低4位,取出高4位:商
        SWAP A                     ;把商换到低4位来,高4位为0
        MOV 42H,A                 ;把商存入显缓42H,拆数字完成

        MOV A,52H                 ;下一数据单元如法炮制
        ACALL HEX2BCD
        PUSH ACC
        ANL A ,#0FH
        MOV 43H,A
        POP ACC
        ANL A,#0F0H
        SWAP A
        MOV 44H,A

        MOV A,53H
        ACALL HEX2BCD
        PUSH ACC
        ANL A ,#0FH
        MOV 45H,A
        POP ACC
        ANL A,#0F0H
        SWAP A
        MOV 46H,A

      ACALL DISPA                  ;调用显示子程序:显缓单元数据查表得到字段码等等上次有交待,就不多说
      CLR TF0                         ;清除 T0中断标志

HISEND:AJMP START


INTSUB_T0:                          ;T0中断子程
                MOV TH0,#0D8H   ;重载设置值
         MOV TL0,#0F0H
         DJNZ R7,INTEND  ;计数减1,不到10次返回
         MOV R7,#10       ;计数10次:100毫秒则
         SETB TF0           ;设定标志,启动主程序
                 CPL P1.0            ;P1,0搞一个眨眼显示
                 INC 50h             ;这是关键!每100毫秒50H加1
INTEND:     RETI

DISPA:MOV R1,#40H          ;查表,轮流显示7个数码管,不多说
          MOV R2,#0FEH
GET:   MOV A,@R1
         MOV DPTR,#DISPTAB
         MOVC A,@A+DPTR
         CJNE R1,#41H, CON
         ANL A,#7FH                   ;这个是让第2位数码管的小数点显出来
CON: CJNE R1,#43H, CON1
        ANL A,#7FH                      ;这个是让第4位数码管的小数点显出来
CON1:CJNE R1,#45H, CON2
         ANL A,#7FH                      ;这个是让第6位数码管的小数点显出来
CON2: MOV P0,A             ;LED 数码送P0
         MOV P2,R2
         ACALL DLY2MS      ;每个管子延时一小会
         MOV A,R2
         RL A
        MOV R2,A
        INC R1
        CJNE R1, #48H,GET
        MOV R2,#0FEH
        MOV R1,#40H
DSPEND:  RET

HEX2BCD:                       ;拆数据
      MOV B,#10
      DIV AB
      SWAP A
      ORL A,B
      RET


DLY2MS:MOV 65H,#20;0  ;延时
D1: MOV 64H,#100;0
    DJNZ 64H,$
    DJNZ 65H,D1
    RET

DISPTAB:
DB 0C0H,0F9H,0A4H,0B0H,99H,92H,82H,0F8H,80H,90H,0C0H,089H

END  
程序运行截图:

TARGET8C.jpg

右下方内存区:(调试状态用菜单VIEW选出如图),绿色圈住的是显缓区,和数码管显示一致;红线圈出的是数据区,因为全部模拟运行速度和电脑性能有关,不过原理就是这样了。
 楼主| 发表于 2007-8-25 15:18:29 | 显示全部楼层
今天的内容是51机的键盘矩阵接口。对于人想对单片机运行加以干预,最简单就是用按键了,像上一个仿真机就是在p3.2,p3.3,p3.4等口线上接小按钮,通过按下使得相应口线接地成为0,通过查询或INT0,INT1外部中断进行按键处理。
     可是如果按键很多,例如16个,每个键接一根口线的方法势必大量占用接口,P1,P2,P3总共24个,一下子就占掉16个,未免太不合适,所以除了使用专门的接口芯片外,最简单合理,占用资源又少的方法就是把按键结成矩阵形式,例如4行4列,每根行线或列线占据1个口,再想行列线交叉处连接按钮,那么占用8根口线却可以安排16个按钮,例如我们这个仿真单片机就是这么接的:
TARGET91.jpg
怎么工作呢?它是依次让P3.4--P3.5--P3.6--P3.7-P3.4...输出0电位,如果没有任何键按下则P3.0...P3.3全部是高电位,一旦检查到某根线出现0电位,例如P3.2为0,则表明有键按下,如图可知是E,A,6,2中的1个究竟是哪个,查一下是哪根线输出的0电位,如果P3.6输出0,则按下的键必然是A无疑。编程中,我们把么每个键一个“键值”让程序区别处理。
下面这个程序就是在按下仿真机的某个按钮时,在数码管显示键值:
ORG 0
       AJMP MAIN

ORG 30H
MAIN : MOV SP,#5FH

KEY:   MOV R0,#4      ; 行线(P3.4--P3.7)计数,转移1次减1,减到0说明已经到P3.7
        MOV R3,#0EFH  ;  开始准备给行线P3.4低电位
NOKEY:  MOV A,R3   
        MOV P3,A          ;给以行线P3.4低电位
        MOV A,P3          ;检查P3口——列线P3.0...P3.3
        ORL A,#0F0H     ;  把A(等于P3口)高4位置1,
        CJNE A,#0FFH,KEYIN   ;如果低4位不全为1则有键按下,存状态
        MOV A,R3         ;这行没有按键,准备检查下行(p3.5)
        RL A                 ;左移A,现在由原来的0EFH变成了0DFH
        MOV R3,A
        DJNZ R0,NOKEY ;未到最后行P3.7继续回去查
        AJMP KEY         ;已到最后行,无键,继续从头循环查询
KEYIN:  MOV R4,A     ,有键,先存低位状态
        ACALL DLY        ;延时去抖(实际上比这个复杂,现在精简了)
KDOWN:  
        ACALL KEYVAL  ;调用键值计算
        MOV 50H,A       ;这个多余了,可以不要把A存入50H
        ACALL TODISP  ;显示每取显示码
        MOV P0,A         ;送数码管,显示最低位
        MOV P2,#0FEH
        AJMP KEY          ;循环

KEYVAL:  MOV B,#0    ;用B辅助取得键值
         MOV A,R4         ,取出先前保存的低4位状态
         
K1:        RRC A             ;带进位右移动:从最低位开始依次移入进位位,如果某位为0,则会使得进位位变0
         JNC K2          ;若进位位为0,检查到了处于低位的列线,1次右移就查出来的是右面第一列
         INC B           ;如果进位位不为0,要检查下一列,下列的键值比前一列大1
         
         SJMP K1             ;继续回去右移检查列线
K2:      MOV A,R3         ;现在检查行线,最终确定键位--键值,取出存在R3中的行信息
         SWAP A             ;交换A的高低4位,使得行线对应位P3.7--P3.4移到4..0位置
K3:      RRC A              ;类似于列线检查办法:带进位右移
         JNC K4               ;查出为0的行线转
         INC B                 ;没查出,转查下行,下行每个键值都比前行大4
         INC B                 ;B中的键值转1行加4
         INC B
         INC B
         SJMP K3             ;再查,直到找出为止
K4:      MOV A,B           ;B中根据行列位查出了键置放到A中
         RET
DLY:     MOV R5,#10
L7:      MOV R6,#200
L8:      DJNZ R6,L8
         DJNZ R5,L7
         RET

TODISP:  INC A                ;按照A中的键值查表得到显示码,因为查表要跳过RET,故A要先加1
         MOVC A,@A+PC      ;也可以用mov dptr,#disptab  movc a,@a+dptr,一样的
         RET

DISPTAB: DB 0C0H,0F9H,0A4H,0B0H,99H,92H,82H,0F8H,80H,90H,10001000B,10000011B,11000110B,10100001B,10000110B,10001110B    用2进数的是A.bCdEF的代码

END
打雷,停了半天
程序运行如图:启动后每用鼠标点按各个按键,则显示相应键值

TARGET92.jpg

[ 本帖最后由 wey05 于 2007-8-25 19:48 编辑 ]
 楼主| 发表于 2007-8-28 00:03:05 | 显示全部楼层
高楼大厦也是由砖块砌筑而成,程序何尝不是由一条条单独不起作用的指令编制?对于比较复杂的程序,最好在事先把主要任务分解为较小的单元“模块”,然后把他们组装起来。例如上述矩阵键盘程序和以前做的8位数码管显示程序就可以组装成为一个新的程序:按键,数码管依次显示键值:每按下新键,键值都在最低位数码管显示,而原有数据一一左移1位。这个程序用“流程图”表示为:
                                         (开始)
                                               |
                                        (初始化)
                                               |<----------------  |
                                      (键盘扫描)               |
                                               |                        |
                                         (存放键值)              |
                                               |                        |
                                       (数码管显示)           |
                                               |                        |
                                               |-------------------|
       这次就是加入了存放键值部分:它的设计这样考虑:把内存50H...57H共8个单元来存放键值,最后的键值存放在50H单元,50H...56H的原有数值均相左移动1位,原有最高位抛弃,如同计算器或电话的按键方式;程序如下:

ORG 0
     AJMP MAiN

ORG 30H
MAIN:   MOV SP, #30H
START:
      MOV R1,#50H         ;(50h)<---这次让所有位先不显示,
      MOV @R1,#10H
AGAIN:  INC R1            ;(51..87h)<-10h
      MOV @R1,#10H
      CJNE R1,#58H,AGAIN

LOOP: ACALL KEY                       ;键盘扫描
          ACALL        PUTKVAL           ;键值存放
          ACALL TODISP                 ;数码管显示
          AJMP LOOP                      ;循环:主程序就是这3大块

TODISP:                                            ;数码管显示,解释见上次
        MOV 40H,50H
        MOV 41H,51H
        MOV 42H,52H
        MOV 43H,53H
        MOV 44H,54H
        MOV 45H,55H
        MOV 46H,56H
        MOV 47H,57H
DISPA:MOV R1,#40H
        MOV R2,#0FEH
GET: MOV A,@R1
        MOV DPTR,#DISPTAB
        MOVC A,@A+DPTR
         MOV P0,A             ;
         MOV P2,R2
         ACALL DLY
         MOV A,R2
         RL A
         MOV R2,A
         INC R1
         CJNE R1, #48H,GET
          MOV R2,#0FEH
          MOV R1,#40H
DSPEND:  RET

KEY:    MOV R0,#4          ;键盘扫描,解释同一上次
           MOV R3,#0EFH
NOKEY:  MOV A,R3           ;
        MOV P3,A
        MOV A,P3                  
        ORL A,#0F0H
        CJNE A,#0FFH,KEYIN  
        MOV A,R3            
        RL A               
        MOV R3,A
        DJNZ R0,NOKEY      
        AJMP KEY           
                  
KEYIN:  MOV R4,A            
          ACALL DLY                          
KEYVAL:  MOV B,#0
           MOV A,R4
K1:      RRC A
          JNC K2
          INC B
          SJMP K1
K2:      MOV A,R3
         SWAP A
K3:      RRC A
         JNC K4
         INC B
         INC B
         INC B
         INC B
         SJMP K3
K4:     RET

putkval: MOV R0,#56H                ;键值存放,先把56H单元移入57H单元,以此类推直到50H移入51H
            MOV R1,#57H
PUT1:     MOV A,@R0
         MOV @R1,A
         DEC R0
         DEC R1
         CJNE R0,#4FH,PUT1
         INC R0
         MOV @R0,B                         ;新的键值存入50H
putend: ret

DLY:     MOV R5,#30
L7:       MOV R6,#200
L8:       DJNZ R6,L8
            DJNZ R5,L7
            RET
DISPTAB: DB 0C0H,0F9H,0A4H,0B0H,99H,92H,82H,0F8H,80H,90H,10001000B,10000011B,11000110B,10100001B,10000110B,10001110B,0ffh
END

运行如图:
TARGET93.jpg
有那么点意思吧?

[ 本帖最后由 wey05 于 2007-8-28 00:06 编辑 ]
 楼主| 发表于 2007-9-5 00:50:01 | 显示全部楼层
最后,做一个综合实验:目标--正前方;利用这个仿真单片机软件,搭建一个简单的加减计算器,功能是:1,8位10进加法
    2,减法
    3,0-99递增
    4,清除错误输入
    5,全清
用数字键输入数字,用a-f共6个键实现功能太晚,明天再说...接着说:
这些功能,看似简单,然而要考虑问题的方方面面,或许就不那么简单,比如加法计算,按照习惯,得先输入被加数,然后按下+键,然后输入加数,再按等号键显示和,这样,就要考虑加数,被加数放在哪里?+号键的功能,=号键的功能,而且=号键还要用于减法计算,如何区别?等等都要考虑好。下面先交待一下有关技术热身:
    1。汇编语言为了便于理解阅读,加入了一些“伪指令”它们只是用来控制汇编过程,或者表示某些标号,例如经常用到的ORG,DB,END就是,现在介绍一个比较常用的EQU命令:例如 NUMBER1 EQU R5  表示可以在程序中在用到R5寄存器的地方用NUMBER1来代替,这样的好处是便于结合R5的用途,理解起来更加容易,最常用的伪指令也就是这几个。此外还有一些,这里就不一一解释了。
    2.散转指令:这个在处理多条件选择入口方面,很有用,例如今天我们要处理6个按键,就用的这条指令 JMP @A+DPTR.意思是按照A的值转移到不同位置,这个位置的基准点在DPTR中,为此先要设置基准点:例如ORG 0200H  (也就是在ROM的512单元)开始安排一系列无条件转移指令,结合A的内容转移到不同的位置去:
ORG 0200H
           TODO1:AJMP mmmm
           TODO2:AJMP nnnn
           TODO3:AJMP xxxx
           TODO4:AJMP yyyy
注意AJMP指令要占用2个字节,所以TODO1,TODO2,TODO3,TODO4的起始地址分别是0200H,0202H,0204H,0206H.那么,只要A中的“偏移量”是,0,2,4,6,在执行JMP @A+DPTR后,就根据偏移量的不同分别转到mmmm,nnnn,xxxx,yyyyROM地址去。这样我们处理功能按键就方便了
     3再认识条件转移,前面我们多次用到条件转移CJNE----不等则转移(相等则直接执行下一条),在这里其实可以从“不等“进一步判断:如果进位标志C为0,两数前者大于后者,C为1,前者小于后者。
    4通常单片机内部进行的都是二进制加法,现在我们做计算器,就要采用“形式上的”十进制,就是内存单元中的数超过9就要进位。当然也可以采用其他方法不过这个便于我们理解。减法,采用补码加法更容易编程,也就是说,把9 - 4 变成 9+ 6 -10 ,( 6=10-4,就是4的10进数补码)10也就是进位,9+6=14,去掉进位就是4,看起来麻烦实际在编程时比处理借位减法更容易。
      好了,下面就是这个计算器程序的主要过程:
                   启动  ---〉初始化-----〉循环显示-----〉按键扫描----〉有键---〉查键,取键值---〉数字键   
-                                            |                                |                                        |
                                             |----------------------------无键                                    功能键
      0。。。9示数字键,A.。。。F是功能键,显然容易从键值判断
       数字键要按照作用存放不同的位置;
       功能键要按照键值不同作散转处理;
       A代表+  ,D代表 - ,B,用作0--99自增,C,撤销刚才键入的数字,E代表=,F代表全部清除。
汇编程序如下:
KADD BIT 70H            ;BIT也是伪指令,作用类似EQU,但BIT是指定“位“的代号,把2EH单元70H位用KADD代表
KDEC BIT 71H            ;这些都是设立功能键的标志,不一定个个都用到,设置了不用,除了浪费点,无大碍
KCE  BIT 72H            ;K ADD KDEC,KCE,KEQU,KINC,KCEALL 分别是+,- ,CE,CEALL,=,INC的标志
KCEALL BIT 73H
KEQU BIT 74H
KINC Bit 75H            
KzRo Bit 76H     

ORG 0

  AJMP MAiN
ORG 30H
MAIN:   MOV SP, #30H
      
INIT: MOV 2EH,#0                 ;2EH用作标志存储,各个位存放上述标志,先清除
PREDISP: MOV A,2EH
        CJNE A,#0,PREDISP2    ;设立50H...57H和60H...67H两组数据存放单元,先看2E单元有无标志如无,
                                             ;2EH 为0把50H...57H的数拷贝到显示缓冲40H...47H
        MOV 40H,50H        ;
        MOV 41H,51H        ;
        MOV 42H,52H        ;
        MOV 43H,53H
        MOV 44H,54H
        MOV 45H,55H
        MOV 46H,56H
        MOV 47H,57H
        AJMP DISP                     ;转显示
PREDISP2:  MOV 40H,60H        ;如果2EH不是0,数字键是在功能键之后按下的显示60H...67H中的数
        MOV 41H,61H        ;
        MOV 42H,62H        ;
        MOV 43H,63H
        MOV 44H,64H
        MOV 45H,65H
        MOV 46H,66H
        MOV 47H,67H
     AJMP DISP
DISP: MOV R1,#47H       ;下面是要不显示数字1...9左面的数值以至于成为类似00002345
GET: MOV A,@R1          ;从显缓区右端向左查,把数字1---9左边的0用10H代换10H的字段码是0FFH,不显示
     CJNE A,#0,GET1
     MOV @R1,#10H
     DEC R1
     CJNE R1,#3fH, GET
GET1:                            ;下面开始把显缓区的数字查段码逐位显示
     MOV R2,#0FEH
     MOV R1,#40H
GET2:MOV A,@R1
     MOV DPTR,#DISPTAB
     MOVC A,@A+DPTR
   
     MOV P0,A      ;LED DATA
     MOV P2,R2
     ACALL DLY
     MOV A,R2
     RL A
     MOV R2,A
     INC R1
     CJNE R1, #48H,GET2
     MOV R2,#0FEH
     MOV R1,#40H
     CPL P1.0                                ;40H...47H都显示一遍,转入键程序
DSPEND:  SJMP KEY
KEY:
        MOV P3,#0FH                      ;先看有无键,方法直接把P3高4位都输出0,检查P3.低4位,
        MOV A,P3
        ORL A,#0F0H
        CJNE A,#0FFH,KEYCHK       ;有键详查
        MOV P3,#0FFH
        AJMP DISP                           ;无键转显
KEYCHK: ACALL DLY                   ;查键前先延时消除触点抖动
        MOV R0,#4                         ;扫描查键
        MOV R3,#0EFH                  ;
NOKEY:  MOV A,R3                      ;
        MOV P3,A
        MOV A,P3                             ;
        ORL A,#0F0H
        CJNE A,#0FFH,KEYIN            ;有键转移
        MOV A,R3                             ;
        RL A                                      ;
        MOV R3,A
        DJNZ R0,NOKEY                    ;
        AJMP KEYCHK
KEYIN:  MOV P3,#0ffH        ;
         ACALL DLY                ;
                     
         MOV R4,A                 ;以下求键值  
KEYVAL:  MOV B,#0
         MOV A,R4
         
K1:      RRC A
         JNC K2
         INC B
         SJMP K1
K2:      MOV A,R3
         SWAP A
K3:      RRC A
         JNC KVALEND
         INC B
         INC B
         INC B
         INC B
         SJMP K3
KVALEND:                                 ;求出键值在B中
         MOV P3,#0FH                   ;再次查键,等待按键释放
         MOV A,P3
         ORL A,#0F0H
         CJNE A,#0FFH,KVALEND
         MOV A,B                           ;按键释放键值给A
         CJNE A,#0AH,KNUM_CTL   ;键值不是0AH,是数字键或功能键,进一步查
         AJMP KCTL                        ;键值是0AH,是功能键转移
KNUM_CTL:JNC KCTL                  ;如果C标志为0,键值大于0AH,转功能键处理
KNUM:    JB KCE,KNUM3             ;C为1,键值小于0AH,如果KCE置位,已经按过CE键,转
         MOV A,2EH                        ;看数字键是在功能键前还是后输入
         JNZ  KNUM2                       ;功能键后,转
         MOV R0,#56H                     ;功能键前,先顺序位移56H...50H,
         MOV R1,#57H                    ;
KPUT1:   MOV A,@R0
         MOV @R1,A
         DEC R0
         DEC R1
         CJNE R0,#4FH,KPUT1
         INC R0
         MOV @R0,B                        ;键值输入50H
         AJMP  KNUMEND
KNUM2:   MOV R0,#66H               ;功能键后数字键,要存入60H单元;
         MOV R1,#67H
KPUT2:   MOV A,@R0
         MOV @R1,A
         DEC R0
         DEC R1
         CJNE R0,#5FH,KPUT2
         INC R0
         MOV @R0,B
         AJMP KNUMEND
KNUM3:    CLR KCE                    ;KCE键后的输入数字根据其他功能键是否按下(2E单元是否0)决定是覆盖;
          MOV A,2EH
          JNZ KNUM31
          MOV 50H,B                        ;50H
          AJMP KNUMEND
KNUM31:   MOV 60H,B                   还是覆盖60H
KNUMEND:  AJMP PREDISP        ;转显示
                          
KCTL:    MOV A,B                        ;功能键处理 ,功能键值从0AH到 0FH,不便直接散转
         SUBB A,#0AH                     ;键值减10,得到0。。。6
         RL A                                   ;左移得到0,2,4,6,正是散转所需的偏移量
         MOV DPTR,# CTLDO           ;
         JMP @A+dPtR                   ;散转喽!见最后,最终转移到下面的KA,KB,KC,KD,KE,KF入口

KA:  MOV 2EH,#0                       ;+,设立标志即可,真正计算是在=中
     SETB KADD
     AJMP DISP
KB:    MOV 2EH,#0                     ;B键处理
KBINC: MOV R0,#50H
       MOV A,@R0
INC0:  INC A
       MOV @R0,A
INC1:  CJNE @R0,#10,KIND
       MOV @R0,#0
       INC R0
       MOV A,@R0
       Add A,#1
       MOV @R0,A
       CJNE @R0, #10, INC1
       DEC R0
INC2:  MOV 50H,#0
       MOV 51H,#0
KiIND:  AJMP PREDISP
KC:  MOV A,2EH                                     ;C键处理
     JNZ KC1                                          ;功能键前删除50H,否则删除 60H
     MOV 50H,#0H
     SJMP KCEND
KC1: MOV 60H,#0H
KCEND: SETB KCE                             ;设标志下次输入数字,不位移直接覆盖
     AJMP DISP
KD:  MOV 2EH,#0                                 ;减法键仅仅设标志
     SETB KDEC
     AJMP DISP
KE:  JB KADD,KE1     ;=键,如果有加法键标志,转加法
     JB KDEC,KE2      ;如果有减法标志,转减法
     AJMP DISP       ;什么键不按就按等号,转显示
KE1: MOV 2EH,#0       ;加法:清除原有按键标志
     ACALL TOADD      ;调用加法子程序
     AJMP PREDISP     ;从头显示
KE2: MOV 2EH,#0       ;清标志
     ACALL TODEC      ;调减法子程序
     AJMP PREDISP     ;从头显示

KF  MOV A,2EH;        ;全清,把50h...57h和60h...67h中的数据全部清除,以便下次计算
     JNZ KF1
     PUSH 1
     MOV R1,#50H
KF0: MOV @R1,#0H
     INC R1
     CJNE R1,#58H,KF0
      MOV R1,#60H
KF01: MOV @R1,#0H
     INC R1
     CJNE R1,#68H,KF01
     POP 1
     AJMP KFEND
KF1: PUSH 1
     MOV R1,#60H
KF10: MOV @R1,#0H
     INC R1
     CJNE R1,#68H,KF10
     POP 1
KFEND: AJMP PREDISP
      
TOADD:  MOV R0,#50H    ;加法子程序
        MOV R1,#60H
        MOV 49H,#0     ;单元49内存放进位,开始前清除
TOLOOP: MOV A,@R1      ;依次逐位把50h..和60h..中的十进数相加
        ADD A,@R0
        ADD A,49H      ;再加进位
        CJNE A,#10,TOADD1;结果不等于10,则转
TOADD0: SUBB A,#10       ;结果等于10,减去10
        MOV 49H,#1       ;进位
        SJMP TOADD2
TOADD1: JNC  TOADD0      ;相加结果大于10,和等于10同样处理
        MOV 49H,#0       ;相加结果小于10,不进位
TOADD2: MOV @R0,A        ;存放结果,转向高一位相加
        INC R0
        INC R1
        CJNE R0,#58H,TOLOOP  ;直到8位加完
        MOV R0,#50H
        MOV R1,#60H
        RET
TODEC:  MOV R0,#50H        ;减法
        MOV R1,#67H        ;67h单元存放减数最高位(可以是0)
        MOV 2DH,#0         ;单元2dh内存放借位
PROD:  MOV A,#10         ;从最高往下求减数补码,
        SUBB A,@R1      
        MOV @R1,A        ;用补码代替原有减数
PROD2:  DEC R1           ;一直代换到最低位60h单元
        CJNE R1,#5fH,PROD
        MOV R1,#60H      ;从最低位开始,计算 被键数+减数补码-借位
DEC0:   MOV A,@R1
        ADD A,@R0
        SUBB A,2dH
        CJNE A,#10,DEC01  ;结果不为10,转
        SUBB A,#10      ;结果为10,减10
        MOV 2dH,#0      ;清借位
        AJMP DEC03
DEC01:  JNC  DEC02     ;结果>10,和等于10同样处理
        MOV 2dH,#1     ;结果小于10,要把借位置1
        SJMP DEC03
DEC02:  SUBB A,#10
        MOV 2DH,#0
DEC03:  MOV @R0,A     ;促模仿结果,转高位处理,直到最高位
        INC R0
        INC R1
        CJNE R0,#58H,DEC0
        MOV R0,#50H
DECEND: MOV 2DH,#0
        RET

DLY:     MOV R5,#40
L7:      MOV R6,#250
L8:      DJNZ R6,L8
         DJNZ R5,L7
         RET

DISPTAB: DB 0C0H,0F9H,0A4H,0B0H,99H,92H,82H,0F8H,80H,90H,10001000B,10000011B,11000110B,10100001B,10000110B,10001110B,0ffH,10111111B
ORG 400H   ;散转基准地址
KCTLdo:    ;散转到下列位置
    AJMP KA  ;地址为400h
    AJMP KB  ;403h
    AJMP Kc  ;404h
    AJMP Kd  ;406h
    AJMP KE  ;408h
    AJMP Kf  ;40ah
END

[ 本帖最后由 wey05 于 2007-9-5 22:49 编辑 ]
 楼主| 发表于 2007-9-10 20:13:50 | 显示全部楼层

有什么感觉?

说到这里,大家可能对单片机有点感觉了吧!总之,说单片机入门不难,可是我们不能总是站在门口吧?从上面例子可以看出两点:
1,指令来回就是那么多,可是要搞点稍微有点用的东西----不仅仅停留在跑马灯之类----都要花得一番气力,把指令组织成为程序,而不是那么简单;
2,如果不对程序加以注解,那么别人完全搞懂你的程序不是一件容易事,时间久了以后,如果没有注解编程者自己也可能忘记原先搞的什么玩艺;所以要想
进一步钻进去,还得进一步学习以下内容:
1。还是单片机本身的硬件结构,包括各种资源的功能和用法
2。进一步学习高级一点的编程语言:C
3。进一步了解外围器件怎样和单片机在硬件和软件上连接起来,如扩展口,c,串行eeprom,ad/da, 液晶模块,温度传感。。。。。。等等,真正使得单片机可以在实际中用起来。
4。具体方法:1是通过用硬件组装调试,如同装矿石收音机,不过麻烦的多;2现在还有一个软件模拟的方法:在软件平台上搭建电路并且调试起来,不过和实际还是多少有距离;
想好了吧?祝你成功!
发表于 2007-9-14 19:38:09 | 显示全部楼层
  
什么时候教教LCD的?
还有如何C语言和汇编合用呢?
关注中
您需要登录后才可以回帖 登录 | 加入会员

本版积分规则

小黑屋|手机版|矿石收音机 ( 蒙ICP备05000029号-1 )

蒙公网安备 15040402000005号

GMT+8, 2025-4-28 10:21

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表