51单片机 汇编 时钟代码 实例

本例子为用汇编在普中单片机上实现一个简单的秒表功能,加一个清零按键。

新建keil工程,新建.asm文件并加入工程,不添加startup.a51文件

在写代码之前,讲一些后面写51汇编必要的基础知识,因为汇编与硬件息息相关,这些不知道就没法写

1、单片机上电后的大致运行过程

单片机内部有自带的检测烧写程序,当上电后在一定引脚上检测到一定信号,则接收串口发来的数据烧写到程序存储器中(从0000H开始),

再执行烧写好的程序,否则执行上一次烧写的程序。

单片机执行的第一条指令是程序存储器的0000H里内容,一般在这里写一个跳转指令到主程序起始地址(比如1000H之类的,只是举个例子,不一定这里)

在执行主程序时,若发生中断,会跳到程序存储器的固定地址(比如T0会跳到000BH处),一般在这写个跳转指令,使程序跳转到中断处理程序处

这里的固定地址就是所谓的中断向量。所以我们写51汇编程序首先得在这些地址处写好跳转指令

 2、部分常用到的寄存器

单片机配置相关的特殊寄存器比如P0TMOD等就不说了,和写C语言的时候差不多,写汇编接触比较多的是工作寄存器

AB寄存器、DPTR寄存器、PSW寄存器、SP寄存器等,这里特别提下工作寄存器,其他自行百度即可。

工作寄存器有4组,每组都是8个工作寄存器R0~R7,通过PSW中的RS1、RS0两位来选择使用哪一组,如果不选,默认是选择第0组。
RS1RS0组合为00时,选中第0组工作寄存器,R0~R7地址为00H~07H;(这里的地址为内部RAM区)
RS1RS0组合为01时,选中第1组工作寄存器,R0~R7地址为08H~0FH;
RS1RS0组合为10时,选中第2组工作寄存器,R0~R7地址为10H~17H;
RS1RS0组合为11时,选中第3组工作寄存器,R0~R7地址为18H~1FH。
进入中断时,可以通过改变RS1RS0来分别使用不同的R0-R7

 351单片机的存储器结构

https://images2018.cnblogs.com/blog/1104670/201806/1104670-20180622130913264-464046793.png

写汇编代码一定要清楚上面那张图,因为汇编是直接操作上述存储器,不同区域的存储器可能会用到不一样的指令。

程序存储器用于保存程序编译后的指令,以及只需要读取,不需要修改的常量,读取用MOVC命令实现,例子如下

disBuf: DB 00H,01H,02H,03H,04H,05H,06H,07H
MOV DPTR,#disBuf
MOV A,00H
;代表读取disBuf的第一个,01H则读第二个
MOVC A,@A+DPTR;rom取数据

这里我还有点不懂的地方,DB这样的伪指令在单片机里难道是只能用来定义常量(在rom里)的吗?8086中好像是变量

我试了几次也查了下没找到不错的回答,最后定义变量是直接   ‘变量名 EQU  内部ram的地址来实现的

数据存储器即ram区,分为片内和片外,访问用到的指令不一样,片内直接MOV就行,片外ram只能通过A寄存器来读取或访问(具体指令看后面附上的表)

 这里可以发现51单片机数据存储区和代码存储区是分开的,从这点来看它是哈佛结构的,但是取指和读数据又是分时的,网上有人说这是冯诺伊曼结构的特点。。。所以有争论

 

4、需要掌握的调试技巧

汇编毕竟直接操作存储器,所以debug使用非常重要,下面稍微提一下关键的几个使用

https://images2018.cnblogs.com/blog/1104670/201806/1104670-20180622134136763-1689878131.png

第一块地方从左到右分别是:重置(丛头开始)、全速运行、停止、单步运行(进入子程序)、单步运行(子程序不进去单步运行)

第二块能看到常用的寄存器的值,这里的R0是指第一组的R0,想看其他组得用第三块的功能

第三块用于查看存储器里的值,如下:

Memory窗口中输入地址值,得到的结果是程序代码区的内容。要查看各种内存区域的内容,只要在Address框内输入字母:地址即可显示相应的内存值。其中字母可以是CDIX,分别代表的意义是:

  C:代码存储空间

  D:直接寻址片内存储空间

  I :间接寻址片内存储空间

  X:扩展的外部RAM空间

如输入“d:0x30”就可显示直接寻址片内30H存储空间的内容了。

还可以使用右键“Modify Memory”选项修改指定内存的内容。

第四块、在指定地方打断点

 

5、汇编代码注意点

我个人最容易错的地方就是,给某个寄存器赋常数值经常忘记打#号,汇编和C语言的习惯不一样,赋常数值前面一定要带#号,否则就会被当成该数值代表的地址里的内容

MOV R00x01就会把内部RAM地址为0x01的存储单元里内容拷到R0,要想给R0赋值为1应写为:MOV R0#0x01

还有就是代码段自己手动安排起始位置一定要保证各段之间没有重合(我不知道能不能自动安排)

 

好了,知道了这些,对照这指令就可以开始写汇编代码了,我直接贴上来吧

duan       EQU     P0;

wei        EQU     P2;

key        BIT     P3.7;

 

ORG   0000H

AJMP  MAIN;绝对转移指令,2kb范围(11位)内跳转 LJMP16位 64kb范围内跳转

;短转移指令的功能是先使程序计数器PC加1两次(即:取出指令码),然后把加2后的地址和rel相加作为目标转移地址。因此,短转移指令是一条相对转移指令,是一条双字节双周期指令

 

 

ORG   0030H;指明后面的程序从程序存储器的0030H单元开始存放

DELAY200US:            ;@11.0592MHz

    NOP

    NOP

    NOP

    PUSH 30H

    PUSH 31H

    MOV 30H,#2

    MOV 31H,#179

NEXT:

    DJNZ 31H,NEXT

    DJNZ 30H,NEXT

    POP 31H

    POP 30H

    RET

 

 

 

ORG 0060H

;DISPLAY子程序

DISPLAY:

PUSH ACC;不能写A,此处ACC代表地址,push后跟地址,代表把地址内的内容压入栈中

PUSH 00H;R0

PUSH 06H;R6

PUSH 07H;R7

PUSH 83H;DPH

PUSH 82H;DPL

MOV R6,#01H;位选数据,01指的是缓冲区最低位数据

MOV R7,#08H;循环次数

FLAG:

MOV duan,#0x00;消影

MOV A,R6

CPL A;取反

MOV wei,A;位选

 

MOV   A,#disBufDat

ADD   A,R7

SUBB  A,#0X08

MOV   R0,A

MOV   A,@R0;读出要显示的数据到A

MOV   DPTR,#disTab

MOVC  A,@A+DPTR;从rom取数据,取出要显示的数据对应的段码   

MOV   duan,A;段选

 

MOV A,R6

RL A

MOV R6,A;更新下一次位选

 

LCALL DELAY200US

DJNZ R7,FLAG

POP 82H;DPL

POP 83H;DPH

POP 07H

POP 06H

POP 00H

POP ACC

RET

 

 

ORG 0100H

;定时器中断0初始化

T0_INIT:

MOV TMOD,#0X01

MOV TH0,#0X3C

MOV TL0,#0XB0

SETB EA

SETB TR0

SETB ET0

RET

 

ORG 0130H

;T0中断处理程序

INT_TIMERE0:

PUSH ACC

SETB RS0

MOV TH0,#0X3C

MOV TL0,#0XB0

INC R0

MOV A,R0

SUBB A,#0X14

JB CY,SECFLAG

MOV R0,#0x00

INC sec

SECFLAG:

CLR  RS0

POP ACC

RETI

 

 

ORG 000BH ;定时器/计数器T0入口地址

LJMP INT_TIMERE0 ;跳转到定时器/计数器中断服务程序中去   

 

disTab: DB 0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71,0x00,0x40;0-f,空白,横杠的段选数据

disBufDat EQU 47H;定义显示缓冲数据变量区,8个

disBufDatHead EQU 40H //单片机上显示在最左边

sec EQU 48H

   

   

;主程序

ORG 0180H

MAIN:

MOV SP,#0X60;将0x60到0x7f设为堆栈区

LCALL T0_INIT

MOV disBufDatHead,#0X00

MOV disBufDatHead+1,#0X00

MOV disBufDatHead+2,#0X11

MOV disBufDatHead+3,#0X11

MOV disBufDatHead+4,#0X11

MOV disBufDatHead+5,#0X11

MOV disBufDatHead+6,#0X11

MOV disBufDatHead+7,#0X11

MOV sec,#0X3A

 

WHILE:

JB key,KEYSCAN

MOV sec,0x00

KEYSCAN:

MOV A,sec

SUBB A,#3CH;超过60s归零

JB   CY,CLEAR

MOV sec,#0X00;clr加ram地址无效

CLEAR:

MOV A,sec

MOV B,#0AH

DIV AB;A/B,商存到A中,余数存B中

MOV disBufDatHead,A

MOV disBufDatHead+1,B

LCALL DISPLAY

LJMP   WHILE;循环

END;

   

大致思路就是先初始化T0,和显示的缓冲区,再在后面的while循环里不断把sec变量拆开写入显示缓冲区(若大于等于60清零),调用DISPLAY子程序显示,若按键按下,清零sec

显示子程序比较难看懂,功能就是把内部ram40h47h8个字节当作显示缓冲区,这八个字节对应8个数码管,每个数码管显示对应字节里的内容

本来是打算按键用外部中断的。。。结果一用时钟就不走了,找了半天没找到问题,只能先完成任务,以后有空再说。

 

指令说明:https://blog.csdn.net/qq_35535992/article/details/52702922

https://wenku.baidu.com/view/33a31c6e793e0912a21614791711cc7931b778c5.html

 

猜你喜欢

转载自blog.csdn.net/sinat_30457013/article/details/89381474