• 第一课,了解单片机单片机的控制原理和DX516 的用法,控制一个LED 和灭本章学习内容:单片机基本原理,如何使用DX516 仿真器,如何编程点亮和灭掉一个LED ,如何进入KEILC51uV调试环境,如何使用单步,断点...

     第一课,了解单片机及单片机的控制原理和DX516 的用法,控制一个LED 灯的亮和灭
    本章学习内容:
    单片机基本原理,如何使用DX516 仿真器,如何编程点亮和灭掉一个LED 灯,如何进入KEILC51uV
    调试环境,如何使用单步,断点,全速,停止的调试方法
    聂小猛 2006 年6 月
    单片机现在是越来越普及了,学习单片机的热潮也一阵阵赶来,许多人因为工作需要或者个人兴趣需
    要学习单片机。可以说,掌握了单片机开发,就多了一个饭碗。
    51 单片机已经有30 多年的历史了,在中国,高校的单片机课程大多数都是51,而51 经过这么多年的
    发展,也增长了许多的系列,功能上有了许多改进,也扩展出了不少分支。而国内书店的单片机专架上,
    也大多数都是51 系列。可以预见,51 单片机在市场上只会越来越多,功能只会越来越丰富,在可以预见的
    数十年内是不可能会消失的。
    作为一个初学者,如何单片机入门?需要那些知识和设备呢?知识上,其实不需要多少东西,会简单
    的C 语言,知道51 单片机的基本结构就可以了。一般的大学毕业生都可以快速入门,自学过这2 门课程的
    高中生也够条件。
    就算你没有学过单片机课程,只掌握了C 语言的皮毛,通过本系列的教程,您也会逐渐的进入单片机
    的大门。当然在学习的过程中,您还是必须多去研读单片机书籍,了解他们的基本结构及工作方式。
    下面以51 为例来了解一下单片机是什么东西,控制原理又是什么?
    在数字电路中,电压信号只有两种情况,高电平和低电平,用数字来记录就是1 和0。单片机内部的
    CPU,寄存器,总线等等结构都是通过1 和0 两种信号来运作的,数据也是以1 或者0 来保存的。单片机
    的输入输出管脚,也就是IO 口,也是只输出或识别1 和0 两种信号,也就是高电平和低电平。当单片机输
    出一个或一组电平信号到IO 口后,外部的设备就可以读到这些信号,并进行相应操作,这就是单片机对外
    部的控制。当外部一个或一组电平信号送到单片机的IO 口时,单片机也可以读到这些信号,并进行分析操
    作,这就是单片机对外部设备信号的读取。当然实际的操作中,这些信号可能十分复杂,必须严格地按照
    规定的时间顺序(时序)输入输出。每种设备也都规定了自己的时序,只要都严格遵守,就可以控制任何
    设备,做出只要你想象得出的任何事情。
    您可能会再问,我如何让单片机去控制和分析外部设备呢?答案是程序,您可以编写相关的程序,并
    且把他们烧写到单片机内部的程序空间,单片机在上电时,就会一步一步按照您写的程序去执行指令,做
    您想做的事情。
    在51 标准芯片中,有32 个输入输出IO,分为4 组,每组8 个,分别为P0 口,P1 口,P2 口,P3 口。
    P1 口的8 条脚就用P1.0 至P1.7 表示,其余类似。51 就是用这32 个口来完成所有外部操作的。对于51 的
    内部结构,如果您已经了解,那是最好;如果不懂,也可以先放下,在完成了本教程开始的几个章节之后,
    您就会大有兴趣,自己去寻找资料阅读了。当然,如果您希望成为一个优秀的单片机开发程序员,还是必
    须熟悉单片机的内部结构及工作原理,切不可偷懒!
    在这一章,您将用程序去控制一个LED 发光管的亮和灭。你应该知道,LED 发光管在通过一定电流时
    亮,不通电就灭。为了不让LED 通过太大的电流把它烧坏,我们还要串上限流电阻。51 的IO 是弱上拉的
    方式,在输出高电平时,只能输出几十微安的电流到地,而在输出低电平时,VCC 电源可以输入几十毫安
    的电流到IO。一般LED 需要10 毫安左右电流点亮,我们就将LED 接在电源VCC 和IO 口之间,中间串
    上电阻,当IO 输出低电平时,灯就亮了,反之,灯就灭了。我们在这个程序里要控制的是P1.0。请参考一
    下我们将要使用的试验板的电路图,这个试验板是在购买dx516 仿真器是赠送的。
    图1,试验电路图


    图2:试验板外观图


    下面介绍一下仿真器和仿真环境。
    在实际的单片机学习和开发中,你可以用仿真器模拟一个CPU 芯片,让它按照您编写的程序工作,并
    且进行调试,一步步排除程序的bug,使程序正常工作。程序工作正常后,您就可以用烧写器将您编写的程
    序烧入购买来的单片机芯片中,让它自己去运行了。
    要使用仿真器,还得有一个编译调试的环境,这个环境是在计算机上运行的,我们就在计算机上编写
    和调试程序,计算机和仿真器有连接,仿真器中的各种数据和程序,都可以从计算机上观察到,并可以观
    察变量,写入变量的值,单步调试程序,在程序中设置断点调试,全速运行,停止程序运行,等等操作。
    我们使用世界上目前最先进的keilC51 编译调试环境,仿真器使用大虾电子网( http://www.daxia.com
    设计的DX516 专业版仿真器,这个仿真器功能齐全,性价比最佳,是学习开发的好工具!
    您可以在此页http://www.daxia.com/product/dx58/ 的资料下载栏目里下载到keilc51 相关的中文说明资
    料,这些资料详细地说明了如何使用C51 编程和如何使用keil uV2 环境调试,请在本章试验完成或者试验
    过程中,如果遇到不懂的地方,一定要抽时间阅读!
    您应该也可以在上面的网页中找到下载破解版本的keilc51 的办法。中国法律规定,在学习和研究工作
    中使用有版权的软件是可以的,但是,如果您开发产品时,建议您还是去购买一个正版的软件。
    下面是DX516 仿真器的使用介绍:
    1。安装
    将仿真器和试验板按图3 组装好,串口线按照正确方向插入仿真器,另一端和电脑串口连接,请尽量
    使用计算机的硬串口。
    仿真器底座左边的跳线,请放在EMB 这边,以进入仿真状态。如果放在RUN 这边,将会进入脱机运
    行状态。
    晶振选择跳线请放在IN 这边,以使用仿真器内部晶振,内部晶振更加可靠。如果放在OUT 这边,则
    会使用外部的用户板晶振。
    图3 仿真器插在试验板上


    2。电源
    因为用户板使用电流不大,可以使用usb 取电,usb 最大电流可以提供500mA,将usb 取电板插入电脑
    的usb 口中。(实际应用中,如果用户板使用电流超过100mA,我们就建议使用外部电源)
    3。启动
    在仿真器上电,或者按一下仿真器上面的按钮时,仿真器会发出“嘀-”,表示仿真器正常启动。同时
    仿真器上面的灯闪烁一次,表示进入正常仿真状态。
    4。仿真设置
    第一个设置:
    C51 用户请在您的代码的main()函数前面,加上一句:
    char code dx516[3] _at_ 0x003b;
    如果以上设置你没有做,在装载过程中,仿真器会发出“嘀嘀嘀” 的三声短声报警,这时的仿真结果将
    可能不正确。
    在我们的例程中,这句话已经加入了。这句话并不会影响程序的工作,可以一直保留。
    第二个设置:
    请在硬件仿真设置选项中,选择serial interrupt,在前面打勾。
    如果以上设置你没有做,在装载过程中,仿真器会发出“嘀-” 的一声长声报警,这时的仿真结果将可
    能不正确。
    其余设置:
    请选择use keil Monitor-51 Driver ,这样才会使用硬件仿真
    请选择load Application at start ,在启动时直接装载程序
    请选择Go till main ,装载后直接运行到main 函数
    请在硬件仿真设置选项中,选择115200bps 波特率,所有cache 都可以不选,或者只选cache code。同
    时请选择正确的串口号。
    图4 仿真设置


    好了,现在可以开始做试验了,我们打开已经建立好的工程和编写好的程序试验。顺便还会学习一下程序调
    试的技巧。至于如何建立一个新工程,请参考C51 的帮助文件,或者自己摸索一下,WWW.DAXIA.COM 的DX516
    专栏里也有“一步步教你如何第一次做…”的文章可以学习。
    请双击lessoncode01 目录下的lesson1.uv2,打开后界面如下:
    图6:程序界面


    这个界面是uV3 的,和uV2 是一样用的。
    点一下上图第三排第2 或者第3 个按钮(您的编译器按钮位置不一定在那个位置,自己找找),就可以看
    到编译结果了。上面显示是0errrs,0warnings,这是最佳的编译结果,如果有error,则无法进行下一步仿真,如果
    有warning,一定要尽量消除,确实无法消除的,也要确认不会对程序造成影响,才进行下一步的仿真。
    在编译结果中,我们还可以看到有data,xdata,code 等用了多少字节的报告,要注意您的单片机中是否有
    这么多的资源,如果不够,将来烧片运行时就可能出现问题。比如AT89C51 的程序空间是4K,xdata 如果没有外
    扩就是0 个,data 是128 个。超出这些范围,程序就不能在AT89c51 中运行。不同的芯片有不同的容量,如
    SST89E516RD 就有64K 程序,内部768 字节XDATA,还有256 个字节的data。我们的例程中肯定都考虑了这些
    了,肯定不会超出,因为DX516 仿真器是和SST89E516RD 有同样的容量的,将来自己开发时就要注意了。
    下面我们故意把第9 行的P10 写成P11,点编译,因为没有预先定义P11,所以就报告错误了,如下
    图:


    双击一下错误报告的那一行,窗口就也会跳到这一行,方便您进行修改。好了,现在请把错误改回去,再编
    译一次,出现报告正确了以后,下面开始仿真了。
    点一下第二行第5 个一个放大镜里面一个d 字母的按钮,就可以进入仿真了,仿真器要事先连接好哟。进入
    仿真后要退出仿真环境也是点这个按钮。注意,等会如果程序在正在全速运行时,仿真环境是不能直接退出的,
    得先点停止运行后,再点仿真按钮才可以退出。
    点进入仿真按钮,程序开始装载,PC 自动运行到了main()停下,并指向了main()函数的第一行。
    下面是进入了仿真环境的截图:


    再顺便把调试界面上的按钮介绍一下:
    图5:按钮说明


    进入仿真窗口后,如果出现的不是前面的源代码窗口,而是夹有反汇编代码的窗口,直接关掉这个窗口就会
    恢复到代码窗口。下次进入也会直接进入到源代码窗口。
    现在先试验单步,点单步(两个单步都可以,一般点单步跨过)。可以看到灯亮了。PC 指针也指向了下一个
    程序行。
    图:照片,灯亮
    再点一下单步,PC 又走下一步,灯灭了。
    再点一次,PC 走到挂起的程序行了,继续点仍然在这一行。这句指令其实就是使程序不断地跳到自己这一
    行,别的什么也不做。一般称作程序挂起。
    一般的实际应用中的程序是不会挂起的,一般是在main 函数里做一个大循环,程序如下:
    void main(void) // 主程序
    {
    while(1)
    {
    P11=0;//亮灯
    P10=1;//灭灯
    }
    }
    请将main 函数程序改为上面的代码,我们下一步将试验断点的操作。
    编译后结果如下:


    图:
    进入仿真后
    图:


    可以看到下面的提示窗口中显示:“connected to Monitor-51 V.DX”,后面的V.DX 就是已经连接到大虾
    仿真器的提示了。V.DX 是大虾仿真器特有的标识。
    在第15 行双击一下,可以看到程序行左边出现了一个红方块,这就是设置断点,再双击一次,断点
    就取消了。如果程序在全速运行的过程中遇到断点,就会自动停下来给你分析。注意在进入仿真后,并且
    程序是停止状态时,才可以设置或者取消断点。
    图:设置了断点


    现在点全速运行,可以看到程序在断点处停了下来,并且由于前一句指令刚刚执行了点灯,所以这时灯是
    亮着的。
    现在在第14 行设置断点,并且取消上一个断点。
    图:设置了另一个断点


    现在点全速运行,可以看到程序在断点处停了下来,并且由于刚刚执行了灭灯,灯是灭着的。
    好,现在试验全速运行和停止。
    把断点取消,再点全速运行,可以看到灯是亮着的,但是不是很亮,这是由于程序是循环的,亮灭交替进
    行,亮的时间并不是全部的时间。
    现在点停止,可以看到程序停止了,重复几次进行全速和停止,可以发现每次停止的地方不一定是同一位
    置。
    这一课就先结束了,我们学习了如何点灯及一些基本的编译和调试操作,下一课将学习如何使LED 闪烁,
    和更多的调试方法,和如何查看运行状态和设置内部寄存器的值。
    课后作业:
    改为第2 个LED 灯(P1.1)做完本章的试验。

    展开全文
  • 单片机汇编语言指令

    2010-01-18 12:53:00
    图1从图中我们可以看出,在51单片机内部有一个CPU用来运算、控制,有四个并行I/O口,分别是P0、P1、P2、P3,有ROM,用来存放程序,有RAM,用来存放中间结果,此外还有定时/计数器,串行I/O口,中断系统,以及一个...

    图1


    从图中我们可以看出,在51单片机内部有一个 CPU 用来运算、控制,有四个并行 I/O 口 ,分别是P0、P1、P2、P3,有ROM ,用来存放程序,有RAM ,用来存放中间结果,此外还有定时 / 计数器 串行 I/O 口 中断系统 ,以及一个内部的时钟电路 。在一个51单片机的内部包含了这么多的东西。



    例如:

             D1: MOV R6,#250       ;

             D2: DJNZ R6,D2     ;  



    MOV :

    在 250 前面有个 # 号,这又是什么意思呢?这个 # 就是用来说明 250 就是一个被传递的东西本身,而不是传递者。


    DJNZ (D ec 1 J mp if N ot Z ero):

    第一个参数中的值减 1 ,然后看一下,这个值是否等于 0 ,如果等于 0 ,就往下执行,如果不等于 0 ,就转移,转到什么地方去呢?可能大家已猜到了,转到第二个参数所指定的地方去(请 大家用自已的话讲一下这条语句是怎样执行的)。本条指令的最终执行结果就是,在原地转圈 250次。



    ----------------------------------------------------------------------------------------------------------------------------------------------------------

    对上面的图进行进一步的分析,我们已知,对并行I/O口的读写只要将数据送入到相应I/O口的锁存器就可以了,那么对于 定时/计数 器,串行I/O口等怎么用呢?在单片机中有一些独立的存储单元是用来控制这些器件的,被称之为特殊功能寄存器( SFR 。事实上,我们已接触过P1这个特殊功能寄存器了,还有哪些 呢?看表1

    符号

    地址

    功能介绍

    B

    F0H

    B 寄存器

    ACC

    E0H

    累加器

    PSW

    D0H

    程序状态字

    IP

    B8H

    中断优先级控制寄存器

    P3

    B0H

    P3 口锁存器

    IE

    A8H

    中断允许控制寄存器

    P2

    A0H

    P2 口锁存器

    SBUF

    99H

    串行口锁存器

    SCON

    98H

    串行口控制寄存器

    P1

    90H

    P1 口锁存器

    TH1

    8DH

    定时器 / 计数器 1 (高 8 位)

    TH0

    8CH

    定时器 / 计数器 1 (低 8 位)

    TL1

    8BH

    定时器 / 计数器 0 (高 8 位)

    TL0

    8AH

    定时器 / 计数器 0 (低 8 位)

    TMOD

    89A

    定时器 / 计数器方式控制寄存器

    TCON

    88H

    定时器 / 计数器控制寄存器

    DPH

    83H

    数据地址指针(高 8 位)

    DPL

    82H

    数据地址指针(低 8 位)

    SP

    81H

    堆栈指针

    P0

    80H

    P0 口锁存器

    PCON

    87H

    电源控制寄存器

    表1


    SFR ( Special Function Register)是特殊功能寄存器的总称,是单片计算机中的一组特殊的临时存储区域,用于动态存放计算机运行过程的一些状态信息、并依此做相应的控制。MCS-51单片机就设有18个专用寄存器,P0-P3端口,定时/计数器T0、T1,TMOD、TCON、PCON、SCON 、PSW、IE、A、B、IP等等。SFR越多,编和控制功能越强、越灵活,但需要硬资源,所以系统设计时会根据需要来确定, 介绍一下几个常用的SFR:

    1. ACC:累加器,通常用A表示。这是个什么东西,可不能从名 字上理解,它是一个寄存器,而不是一个做加法的东西,为什么给它这么一个名字呢?或许是因为在运算器做运算时其中一个数一定是在ACC中的缘故吧。它的名字特殊,身 份也特殊,稍后我们将学到指令,可以发现,所有的运算类指令都离不开它。

         2、B:一个寄存器。在做乘、除法时放乘 数或除数,不做乘除法时,随你怎么用。

         3、PSW:程序状态字。这是一个很重要的东西,里面放了CPU工作时的很多状态,借此,我们可以了解CPU的当前状态,并作出相应 的处理。它的各位功能请看表2


    D7

    D6

    D5

    D4

    D3

    D2

    D1

    D0

    CY

    AC

    F0

    RS1

    RS0

    OV



    表2

    下面我们逐一介绍各位的用途

    1 CY :进位标志。 8051 中的运算器是一种 8 位的运算器,我们知道, 8 位运算器只能表示到 0-255 ,如果做加法的话,两数相加可能会超过 255 ,这样最高位就会丢失,造成运算的错误,怎么办?最高位就进到这里来。这样就没事 了。

      例: 78H+97H 01111000+10010111

    (2)AC :半进位标志。

                例: 57H+3AH 01010111+00111010

    (3)F0 :用户标志位,由我们 (编程人员)决定什么时候用,什么时候不用。

    (4)RS1 RS0 :工作寄存器组选择位。这个我 们已知了。

    (5)0V :溢出标志位。什么是溢出 我们稍后再谈吧。

    (6)P :奇偶校验位:它用来表示 ALU 运算结果中二进制数位“ 1 ”的个数的奇偶性。若为奇数,则 P=1 ,否则为 0。

                           例:某运算结果是 78H 01111000 ),显然 1 的个数为偶数,所以 P=0

           4.DPTR:51单片机中的可十六位寻址的数据指针寄存器,由两个8Bit的寄存器(DPH和DPL)组成。使用mov指令可以一次写入它的高字节和低字节,如: mov dptr,#add16 

           5 P0 、P1、P2、 P3 :这个我们已经知道,是四个并行 输入 / 输出 口的寄存器。它里面的内容对应着管脚的输出。

           6、SP :堆栈指针。

           堆栈介绍:日常生活中,我们都注意到过这样的现象,家里洗的碗,一只一只摞起来,最晚放上去的放在最上面,而最早放上去 的则放在最下面,在取的时候正好相 反,先从最上面取,这种现象我们用一句话来概括:“先进后出,后进先出”。请大家想想,还有什么地方有这种现象?其实比比皆是,建筑工地上堆放的砖头、材 料,仓库里放的货物,都是“先进后出,后进先出”,这实际是一种存取物品的规则,我们称之为“堆栈”。

                在单片机中,我们也可以在 RAM 中构造这样一个区域,用来存放数 据,这个区域存放数据的规则就是“先进后出,后进先出”,我们称之为“堆栈”。为什么需要这样来存放数据呢?存储器本身不是可以按地址来存放数据吗?对, 知道了地址的确就可以知道里面的内容,但如果我们需要存放的是一批数据,每一个数据都需要知道地址那不是麻烦吗?如果我们让数据一个接一个地放置,那么我 们只要知道第一个数据所在地址单元就可以了(看图 2 )如果第一个数据在 27H ,那么第二、三个就在 28H 29H 了。所以利用堆栈这种方法来放数据可以简化操作

    那么 51 中堆栈什么地方呢?单片机中能存放数据的区域有限,我们不能够专门分配一块地方做堆 栈,所以就在内存( RAM )中开辟一块地方,用于堆栈,但是用内存的哪一块呢?还是不好定,因为 51 是一种通用的单片机,各人的实际需求各不相同,有人需要多一些堆栈,而有人则不需要 那么多,所以怎么分配都不合适,怎样来解决这个问题 ?分不好干脆就不分了,把分的权利给用户(编程者),根据自已的需要去定吧,所以 51 单片机中堆栈的位置是可以变化的。 而这种变化就体现在 SP 中值的变化,看图 2 SP 中的值等于 27H 不就相当于是一个指针指向 27H 单元吗?当然在真正的 51 机中,开始指针所指的位置并非就是数据存放的位置,而是数据存放的前一个位置,比如 一开始指针是指向 27H 单元的,那么第一个数据的位置是 28H 单元,而不是 27H 单元,为什么会这样,我们在学堆栈命令时再说明。

    图2

    转自伟纳电子(截止至单片机第七课)

    展开全文
  • 使用汇编语言编写程序,设计一个用8051单片机控制的循环彩灯控制系统要求:1、能输出4种花型,并能由人工进行花型的切换。可以使用按键,也可以使用拨动开关来实现切换,方法不限。2、循环彩灯的循环速度可调。3、...

    使用汇编语言编写程序,设计一个用8051单片机控制的循环彩灯控制系统
    要求:
    1、能输出4种花型,并能由人工进行花型的切换。可以使用按键,也可以使用拨动开关来实现切换,方法不限。
    2、循环彩灯的循环速度可调。
    3、按键要求:
    (1)启动/暂停。按动一次启动,再按一次暂停。是一个双态转换键。
    (2)花型变化。按一次,立即停止当前花型的显示,转换为下一种花型。4种花型可以循环切换。
    (3)速度+。按键一次,速度略加快一点。
    (4)速度-。按键一次,速度略减慢一点。
    4、在LED显示器上显示相应的花型号。

    悬赏分:30 - 离问题结束还有 14 天 21 小时
    ;==============================================================
    上次编写了一个“键控流水灯”,后来不断有网友来电,希望再编写些其它花样的。
    做而论道也有了一些设想,正巧有这位网友提出的要求,和做而论道的构思暗合,故此连夜调出个程序,也算是答题吧。
    用PROTEUS仿真的画面如下:

    使用汇编语言编写程序,设计一个用8051单片机控制的循环彩灯控制系统 - 非著名博主 - 电子信息角落

    图片链接:http://hi.baidu.com/%D7%F6%B6%F8%C2%DB%B5%C0/album/item/91d1fff221a004f50b46e0ef.html
    ;---------------------------------------------------
    控制程序如下:

        ORG 0000H
        JMP START
        ORG 0003H
        JMP X0_INT
        ORG 000BH
        JMP T0_INT
        ORG 0013H
        JMP X1_INT
    ;---------------------------------
    START:
        MOV TMOD, #51H       ;/*01010001 T1计数,T0定时*/
        MOV TH0, #HIGH(65536 - 50000) ;50ms@12MHz
        MOV TL0, #LOW (65536 - 50000)
        MOV TH1, #0
        MOV TL1, #0
        SETB TR0
        SETB TR1
        SETB IT0
        SETB IT1
        MOV IE, #10000111B
        MOV IP, #00000101B

        MOV R7, #20
        MOV 30H, #20
        MOV 31H, #0
        MOV 32H, #0

        MOV P2, 31H
        INC P2

        SJMP $
    ;---------------------------------
    T0_INT:          ;50ms执行一次
        MOV TL0, #LOW (65536 - 50000) ;重新写入初始值
        MOV TH0, #HIGH(65536 - 50000) ;50ms@12MHz
    ;------------------
    JIA:
        JB   P3.0, JIAN
        DEC 30H          ;加速
        MOV A, 30H
        JNZ XXX          ;不能让它为0
        INC 30H
    XXX:
    ;    JNB P3.0, $
    ;------------------
    JIAN:
        JB   P3.1, XIANSHI
        INC 30H
        MOV A, 30H
        SUBB A, #80
        JC   CCC
        MOV 30H, #80
    CCC:
    ;    JNB P3.1, $
    ;------------------
    XIANSHI:
        DJNZ R7, T0_END
        MOV R7, 30H
        INC 32H
        ANL 32H, #7
    ;------------------
        MOV A, 31H
        ANL A, #3
        RL   A
        RL   A
        RL   A
        ADD A, 32H
        MOV DPTR, #HUA_Y
        MOVC A, @A+DPTR
        CPL A
        MOV P1, A
    ;------------------
    T0_END:
        RETI
    ;---------------------------------
    X0_INT:          ;设置花型
        INC 31H
        ANL 31H, #3
        MOV P2, 31H
        INC P2
        RETI
    ;---------------------------------
    X1_INT:          ;启动/停止
        CPL TR0
        RETI
    ;---------------------------------
    HUA_Y:
        DB 0x80,0x40,0x20,0x10,0x08,0x04,0x02,0x01
        DB 0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80
        DB 0x81,0x42,0x24,0x18,0x81,0x42,0x24,0x18
        DB 0x18,0x24,0x42,0x81,0x18,0x24,0x42,0x81
    END

    展开全文
  • 理解单片机系统 一、理解CPU的三种工作模式 从80386开始,CPU有三种工作方式:实模式(real-mode)、保护模式(protected-mode)和虚拟8086模式。只有在刚刚启动的时候是实模式,等到操作系统运行起来以后就切换到...

    理解单片机系统

    一、理解CPU的三种工作模式

    从80386开始,CPU有三种工作方式:实模式(real-mode)、保护模式(protected-mode)和虚拟8086模式。只有在刚刚启动的时候是实模式,等到操作系统运行起来以后就切换到保护模式。实模式只能访问地址在1M以下的内存称为常规内存,我们把地址在1M 以上的内存称为扩展内存。在保护模式下,全部32条地址线有效,可寻址高达4G字节的物理地址空间; 扩充存储器分段管理机制存储器分页管理机制(可选的)不仅为存储器共享和保护提供了硬件支持,而且为实现虚拟存储器提供了硬件支持;支持多任务,能够快速地进行任务切换(switch)和保护任务环境(context); 4个特权级和完善的特权检查机制,既能实现资源共享又能保证代码和数据的安全和保密及任务的隔离; 支持虚拟8086方式,便于执行8086程序。

    实模式(Real Mode)

    它是 Intel公司80286及以后的x86(如80386,80486和80586等)处理器为了兼容以前的处理器(CPU)的一种操作模式。实模式被特殊定义为20位地址内存可访问空间上,这就意味着它的容量是2的20次幂(1M)的可访问内存空间(物理内存和BIOS-ROM),软件可通过这些地址直接访问BIOS程序和外围硬件。实模式下处理器没有硬件级的内存保护概念和多道任务的工作模式。但是为了向下兼容以前的处理器,所以80286及以后的x86系列处理器在开机启动时仍然先工作在实模式下。80186和早期的处理器仅有一种操作模式,就是后来我们所定义的实模式。实模式虽然能访问到1M的地址空间,但是由于BIOS的映射作用(即BIOS占用了部分空间地址资源),所以真正能使用的物理内存空间(内存条),也就是在640k到924k之间。1M 地址空间组成是由 16位的段地址和16位的段内偏移地址组成的。用公式表示为:物理地址=左移4位的段地址+偏移地址。

    80286处理器体系结构引入了地址保护模式的概念,处理器能够对内存及一些其他外围设备做硬件级的保护设置(保护设置实质上就是屏蔽一些地址的访问)。使用这些新的特性,然而必不可少一些额外的在80186及以前处理器没有的操作规程。自从最初的x86微处理器规格以后,它对程序开发完全向下兼容,80286芯片被制作成启动时继承了以前版本芯片的特性。它工作在实模式下时暂时先关闭了新增的保护功能特性等,因此能使以往的软件继续工作在新的芯片下。直到今天,甚至最新的x86处理器都是在计算机加电启动时都是工作在实模式下,它能运行为以前处理器芯片写的程序。

    DOS操作系统(例如 MS-DOS,DR-DOS)工作在实模式下,微软Windows早期的版本(它本质上是运行在DOS上的图形用户界面应用程序,实际上本身并不是一个操作系统)也是运行在实模式下,直到Windows3.0,它运行期间既有实模式又有保护模式,所以说它是一种混合模式工作。它的保护模式运行有两种不同意义(因为80286并没有完全地实现80386及以后的保护模式功能):
    “标准保护模式”:这就是程序运行在保护模式下;
    “虚拟保护模式”:它实质上还是实模式,是实模式上模拟的保护模式也使用32位地址寻址方式。Windows3.1彻底删除了对实模式的支持。在80286处理器芯片以后,Windows3.1成为主流操作系统(Windows/80286不是主流产品)。目前差不多所有的X86系列处理器操作系统(Linux,Windows95 and later,OS/2等)都是在启动时进行处理器设置而进入保护模式的。

    实模式工作机理:

    • 对于8086/8088来说计算实际地址是用绝对地址对1M求模。8086的地址线的物理结构:20根,也就是它可以物理寻址的内存范围为2^20个字节,即1 M空间,但由于8086/8088所使用的寄存器都是16位,能够表示的地址范围只有0-64K,这和1M地址空间来比较也太小了,所以为了在8086/8088下能够访问1M内存,Intel采取了分段寻址的模式:16位段基地址:16位偏移EA,其绝对地址计算方法为:16位基地址左移4位+16位偏移=20位地址。比如:DS=1000H EA=FFFFH 那么绝对地址就为:10000H +0FFFFH = 1FFFFH 地址单元 。通过这种方法来实现使用16位寄存器访问1M的地址空间,这种技术是处理器内部实现的,通过上述分段技术模式,能够表示的最大内存为: FFFFh: FFFFh=FFFF0h+FFFFh=10FFEFh=1M+64K-16Bytes(1M多余出来的部分被称做高端内存区HMA)。但8086/8088只有20位地址线,只能够访问1M地址范围的数据,所以如果访问100000h~10FFEFh之间的内存(大于1M空间),则必须有第21根地址线来参与寻址(8086/8088没有)。因此,当程序员给出超过1M(100000H-10FFEFH)的地址时,因为逻辑上正常,系统并不认为其访问越界而产生异常,而是自动从0开始计算,也就是说系统计算实际地址的时候是按照对1M求模的方式进行的,这种技术被称为wrap-around。
    •  对于80286或以上的CPU通过A20 GATE来控制A20地址线。技术发展到了 80286,虽然系统的地址总线由原来的20根发展为24根,这样能够访问的内存可以达到2^24=16M,但是Intel在设计80286时提出的目标是向下兼容,所以在实模式下系统所表现的行为应该和8086/8088所表现的完全一样,也就是说,当启动运行在实模式下时,80386以及后续系列应该和8086/8088完全兼容仍然使用A20地址线。所以说80286芯片存在一个BUG:它开设A20地址线时,在保护模式下,如果程序员访问100000H-10FFEFH之间的内存,系统将实际访问这块内存(没有wrap-around技术);如果在实模式下,也可以访问100000H-10FFEFH间的内存,这时采用的是wrap-around技术,这时两种模式下访问同一个内存区却会得到不同的数据,因此说是一个bug。我们来看一副图:

      https://img-my.csdn.net/uploads/201205/14/1336959769_1239.jpg


      为了解决上述兼容性问题,IBM使用键盘控制器上剩余的一些输出线来管理第21根地址线(从0开始数是第20根) 的有效性,被称为A20 Gate:
    • 如果A20 Gate被打开,则当程序员给出100000H-10FFEFH之间的地址的时候,系统将真正访问这块内存区域;
    • 如果A20 Gate被禁止,则当程序员给出100000H-10FFEFH之间的地址的时候,系统仍然使用8086/8088的方式即取模方式(8086仿真)。绝大多数IBM PC兼容机默认的A20 Gate是被禁止的。现在许多新型PC上存在直接通过BIOS功能调用来控制A20 Gate的功能。

    保护模式(Protected Mode)

    在实模式下,在80286以及更高系列的PC中,即使A20 Gate被打开,在实模式下所能够访问的内存最大也只能为10FFEFH,尽管它们的地址总线所能够访问的能力都大大超过这个限制。为了能够访问10FFEFH以上的内存,则必须进入保护模式。

    (286是Intel 80286的另一种叫法) 它又被称作为虚拟地址保护模式。尽管在Intel 80286手册中已经提出了虚地址保护模式,但实际上它只是一个指引,真正的32位地址出现在Intel 80386上。保护模式本身是80286及以后兼容处理器序列之后产成的一种操作模式,它具有许多特性设计为提高系统的多道任务和系统的稳定性。例如内存的保护,分页机制和硬件虚拟存储的支持。现代多数的x86处理器操作系统都运行在保护模式下,包括Linux,Free BSD,和Windows3.0(它也运行在实模式下,为了和Windows 2.x应用程序兼容)及以后的版本。

    80286及以后的处理器另一种工作模式是实模式(仅当系统启动的一瞬间),本着向下兼容的原则屏蔽保护模式特性,从而容许老的软件能够运行在新的芯片上。作为一个设计规范,所有的x86系列处理器,除嵌入式Intel80387之外,都是系统启动工作在实模式下,确保遗留下的操作系统向下兼容。它们都必须被启动程序(操作系统程序最初运行代码)重新设置而相应进入保护模式的,在这之前任何的保护模式特性都是无效的。在现代计算机中,这种匹配进入保护模式是操作系统启动时最前沿的动作之一。

    在被调停的多道任务程序中,它可以从新工作在实模式下是相当可能的。保护模式的特性是阻止被其他任务或系统内核破坏已经不健全的程序的运行,保护模式也有对硬件的支持,例如中断运行程序,移动运行进程文档到另一个进程和置空多任务的保护功能。

    386及以后系列处理器不仅具有保护模式又具有32位寄存器,结果导致了处理功能的混乱,因为80286虽然支持保护模式,但是它的寄存器都是16位的,它是通过自身程序设定而模拟出的32位,并非32位寄存器处理。归咎于这种混乱现象,它促使Windows/386 及以后的版本彻底抛弃80286的虚拟保护模式,以后保护模式的操作系统都是运行在80386以上,不再运行在80286(尽管80286模式支持保护模式),所以说80286是一个过渡芯片,它是一个过渡产品。

    尽管 286和386处理器能够实现保护模式和兼容以前的版本,但是内存的1M以上空间还是不易存取,由于内存地址的回绕,IBM PC XT (现以废弃)设计一种模拟系统,它能过欺骗手段访问到1M以上的地址空间,就是开通了A20地址线。在保护模式里,前32个中断为处理器异常预留,例如,中断0D(十进制13)常规保护故障和中断00是除数为零异常。

    如果要访问更多的内存,则必须进入保护模式,那么,在保护模式下,A20 Gate对于内存访问有什么影响呢?

    为了搞清楚这一点,我们先来看一看A20的工作原理。A20,从它的名字就可以看出来,其实它就是对于A20(从0开始数)的特殊处理(也就是对第21根地址线的处理)。如果A20 Gate被禁止,对于80286来说,其地址为24根地址线,其地址表示为EFFFFF;对于80386极其随后的32根地址线芯片来说,其地址表示为FFEFFFFF。这种表示的意思是:

    https://img-my.csdn.net/uploads/201205/14/1336960202_9190.jpg

    •  如果A20 Gate被禁止。则其第A20在CPU做地址访问的时候是无效的,永远只能被作为0。所以,在保护模式下,如果A20 Gate被禁止,则可以访问的内存只能是奇数1M段,即1M,3M,5M…,也就是00000-FFFFF,200000-2FFFFF,300000-3FFFFF…
    • 如果A20 Gate被打开。则其第20-bit是有效的,其值既可以是0,又可以是1。那么就可以使A20线传递实际的地址信号。如果A20 Gate被打开,则可以访问的内存则是连续的。

    实模式和保护模式的区别

    从表面上看,保护模式和实模式并没有太大的区别,二者都使用了内存段、中断和设备驱动来处理硬件,但二者有很多不同之处。我们知道,在实模式中,内存被划分成段,每个段的大小为 64KB ,而这样的段地址可以用 16位来表示。内存段的处理是通过和段寄存器相关联的内部机制来处理的,这些段寄存器( CS 、 DS 、 SS 和ES )的内容形成了物理地址的一部分。具体来说,最终的物理地址是由 16 位的段地址和 16 位的段内偏移地址组成的。用公式表示为:物理地址 = 左移 4 位的段地址 + 偏移地址。在保护模式下,段是通过一系列被称之为 “ 描述符表 ” 的表所定义的。段寄存器存储的是指向这些表的指针。用于定义内存段的表有两种:全局描述符表 (GDT) 和局部描述符表 (LDT) 。GDT 是一个段描述符数组,其中包含所有应用程序都可以使用的基本描述符。在实模式中,段长是固定的 ( 为 64KB) ,而在保护模式中,段长是可变的,其最大可达 4GB 。LDT 也是段描述符的一个数组。与 GDT 不同,LDT 是一个段,其中存放的是局部的、不需要全局共享的段描述符。每一个操作系统都必须定义一个 GDT ,而每一个正在运行的任务都会有一个相应的 LDT 。每一个描述符的长度是 8 个字节,格式如图 3 所示。当段寄存器被加载的时候,段基地址就会从相应的表入口获得。描述符的内容会被存储在一个程序员不可见的影像寄存器 (shadow register) 之中,以便下一次同一个段可以使用该信息而不用每次都到表中提取。物理地址由 16 位或者 32 位的偏移加上影像寄存器中的基址组成。实模式和保护模式的不同可以从下图很清楚地看出来。

    实模式地址
     

    https://img-my.csdn.net/uploads/201205/14/1336960423_5675.jpg

    保护模式地址

    https://img-my.csdn.net/uploads/201205/14/1336960439_4547.jpg

    总结:保护模式同实模式的根本区别是进程内存受保护与否。可寻址空间的区别只是这一原因的果。实模式将整个物理内存看成分段的区域,程序代码和数据位于不同区域,系统程序和用户程序没有区别对待,而且每一个指针都是指向"实在"的物理地址。这样一来,用户程序的一个指针如果指向了系统程序区域或其他用户程序区域,如果这个指针修改了这个区域的某一个值,那么对于这个被修改的系统程序或用户程序,其后果就很可能是灾难性的。为了克服这种低劣的内存管理方式,处理器厂商开发出保护模式。这样,物理内存地址不能直接被程序访问,程序内部的地址(虚拟地址)要由操作系统转化为物理地址去访问,程序对此一无所知

    至此,进程有了严格的边界,任何其他进程根本没有办法访问不属于自己的物理内存区域,甚至在自己的虚拟地址范围内也不是可以任意访问的,因为有一些虚拟区域已经被放进一些公共系统运行库。这些区域也不能随便修改,若修改就会有: SIGSEGV(linux 段错误);非法内存访问对话框(windows 对话框)。

    CPU启动环境为16位实模式,之后可以切换到保护模式。但从保护模式无法切换回实模式 。对于80X86处理器来说,从80386处理器开始,除了以前的实模式外,还增添了保护模式和V86模式。实模式和V86模式都是为了和8086兼容而设置的。

    实模式: 
          内存寻址方式为:段式寻址,即物理地址=段地址*16   +   段内偏移地址 
          可寻址任意地址,所有指令都相当于工作在特权级。
          dos工作在实模式下 
    保护模式: 
          内存寻址方式为:支持内存分页和虚拟内存 
          支持多任务,可依靠硬件用一条指令即可实现任务切换,不同任务可工作在不同的优先级下,操作系统工作在最高优先级0上,应用程序则运行在较低优先级上。从实模式到保护模式,需要建立GDT、IDT等数据表,然后通过修改控制寄存 器CR0的控制位(位0)来实现。
          Windows工作在保护模式下。
    虚拟8086模式: 
    内存寻址方式:段式寻址,与实模式一样 
            支持多任务和内存分页 
            v86模式主要是为了在保护模式下兼容以前的实模式应用,即可支持多任务,但每个任务都是实模式的工作方式。另外,中断和异常等的处理对于不同的工作模式都是不同的,具体的可以去参看一些相关书籍。

    二、理解8086微机系统的组成

    https://i.imgur.com/LiO0fGt.png

    1、对于汇编程序而言,我们需要关心CPU中的寄存器、存储器地址、端口(I/O地址)
    【内存单元的两个元素】: 地址(编号)和值(内容)。
    【字节、字、双字】:8086的内存以字节编址,每个内存单元有唯一的地址(物理地址),可以存放一个字节。字:一个字占据两个连续的字节。双字:双字占据两个连续的字。
    【数据的地址对齐】:字单元安排在偶地址(xxx0B),双字单元安排在模4地址(xx00B)等。对于非对齐地址的数据,处理器访问时需要多次访问存储器,这样做花费时间较多。
    【总线】:8086的系统总线有3种:数据总线;地址总线:8086CPU外部一共有20条地址总线,但在CPU内部一次只能传送16位地址;控制总线
    【I/O】:I/O地址叫做端口,通常采用十六进制数来表达端口:8086的I/O端口为16位,可支持64k个8位端口;I/O地址范围为:0000H ~ FFFFH
    【8086的功能结构】:总线接口单元BIU:主要负责读取指令和操作数。执行单元EU:主要负责指令译码和执行。

    2、汇编语言程序、汇编程序、连接程序、调试程序
    汇编程序:汇编程序将汇编语言源程序翻译(或称作“汇编”)成机器代码目标模块。
    .ASM -> .OBJ;注意区分汇编程序与汇编语言源程序。
    连接程序:连接程序将汇编后的目标模块转换为可执行程序。
    调试程序:调试程序以便排错、分析等。

    3、寄存器组

    16位通用寄存器】
    AX、BX、CX、DX、SI、DI、BP、SP
    其中AX、BX、CX、DX可以分作高8位和低8位的两个独立寄存器。如:AH和AL。我们对其中8位的操作,并不影响另外对应的8位数据。

    1、数据寄存器ax、bx、cx、dx
    数据寄存器用来存放计算的结果和操作数,也可以存放地址。
    每个寄存器又有它们各自的专用目的。

    AX——累加器,使用频度最高,用于算术、逻辑运算以及与外设传送信息等;
    BX——基址寄存器,常用做存放存储器地址;
    CX——计数器,作为循环和串操作等指令中的隐含计数器;
    DX——数据寄存器,常用来存放双字长数据的高16位,或存放外设端口地址。

    2、变址寄存器si、di
    变址寄存器常用于存储器寻址时提供地址

    SI是源变址寄存器

    DI是目的变址寄存器
    串操作类指令中,SI和DI具有特别的功能

    3、指针寄存器sp、bp
    指针寄存器用于寻址内存堆栈内的数据:
    SP为堆栈指针寄存器,指示栈顶的偏移地址。
    SP不能再用于其他目的,具有专用目的。SP始终指向栈顶。
    BP为基址指针寄存器,表示数据在堆栈段中的基地址。
    SP和BP寄存器与SS段寄存器联合使用以确定堆栈段中的存储单元地址

    4、堆栈
    8086中堆栈通常有处理器自动维持,由堆栈段寄存器SS和堆栈指针寄存器SP共同指示。

    5、指令指针寄存器IP
    指示代码段中指令的偏移地址。与代码段寄存器CS连用(CS:IP)。

    6、标志寄存器
    标志(flag)用于反映指令执行结果或控制指令执行形式。

    16位的标志寄存器——程序状态字PSW寄存器。
    https://i.imgur.com/s3JIoyw.png
    其中,状态标志(6个,CF ZF SF PF OF AF)——用来记录程序运行结果的状态信息,许多指令的执行都将相应地设置它。控制标志(3个,DF IF TF)——可由程序根据需要用指令设置,用于控制处理器执行指令的方式。

    • 进位标志CF(Carry Flag):当运算结果的最高有效位有进位(加法)或借位(减法)时,CF=1,or CF=0;
    • 零标志位ZF(Zero Flag):若运算结果为0时,ZF=1,or ZF=0;
    • 符号标志位SF(Sign Flag):运算结果最高位为1,则SF=1,or SF=0。
      有符号数据用最高有效位表示数据的符号。所以,最高有效位就是符号标志的状态
    • 奇偶标志位PF(Parity Flag):当运算结果最低字节中“1”的个数为零或偶数时,PF=1;or PF=0。
      PF标志仅反映最低8位中“1”的个数是偶或奇,即使是进行16位字操作。
    • 溢出标志OF(Overflow Flag):若算术结果有溢出,OF=1,or OF=0;【什么是溢出?溢出判断】
    • 辅助进位标志AF(Auxiliary Carry Flag):运算时D3位(低半字节)有进位或借位时,AF=1,or AF=0。这个标志主要由处理器内部使用,用于十进制算术运算调整指令中,用户一般不必关心。
    • 方向标志DF(Direction Flag):用于串操作指令中,控制地址的变化方向:设置DF=0,存储器地址自动增加;设置DF=1,存储器地址自动减少。CLD指令复位方向标志:DF=0;STD指令置位方向标志:DF=1。
    • 中断允许标志IF(Interrupt-enable Flag):用于控制外部可屏蔽中断是否可以被处理器响应:设置IF=1,允许中断;设置IF=0,禁止中断。CLI指令复位中断标志:IF=0;STI指令置位中断标志:IF=1。
    • 陷阱标志TF(Trap Flag):用于控制处理器进入单步操作模式:设置TF=0,处理器正常工作;设置TF=1,处理器单步执行指令。单步执行指令——处理器在每条指令执行结束时,便产生一个编号为1的内部中断。这种内部中断称为单步中断。所以TF也称为单步标志。

    7、段寄存器
    段地址——段的起始地址的高16位地址。段内再由16位二进制数来寻址。
    偏移地址——段内存储单元到段首地址的字节的距离。
    物理地址——用20位二进制数表示。地址范围为00000H ~ FFFFFH。物理地址唯一标识一个存储单元。
    逻辑地址——段地址:偏移地址,逻辑地址不唯一。
    物理地址=段地址x16+偏移地址

    8086有4个16位的段寄存器。

    代码段CS(Code Segment):指明代码段的 起始地址。
    代码段用来存放程序的指令序列。指令指针寄存器IP指示下一条指令的偏移地址。处理器利用CS:IP取得下一条要执行的指令。

    堆栈段SS(Stack Segment):指明堆栈段的起始地址。
    堆栈段确定堆栈所在的主存区域。堆栈指针寄存器SP指示堆栈栈顶的偏移地址。处理器利用SS:SP操作堆栈栈顶的数据。

    数据段DS(Data Segment):指明数据段的起始地址。
    数据段存放运行程序所用的数据。各种主存寻址方式(有效地址EA)得到存储器中操作数的偏移地址。处理器利用DS:EA存取数据段中的数据。

    附加段ES(Extra Segment):指明附加段的起始地址。
    附加段是附加的数据段,也用于数据的保存。各种主存寻址方式(有效地址EA)得到存储器中操作数的偏移地址。处理器利用ES:EA存取附加段中的数据。
    串操作指令将附加段作为其目的操作数的存放区域。

    【关于分段】

    1、8086对逻辑段的要求:
    ① 段地址低4位均为0
    ② 每段最大不超过64KB(216 B),但并不要求必须为64KB
    ③ 各段之间可以独立,也可以有重叠

    2、如何分配各个逻辑段:
    ① 程序的指令序列必须安排在代码段。
    ② 程序使用的堆栈一定在堆栈段。
    ③ 程序中的数据默认是安排在数据段,也经常安排在附加段,尤其是串操作的目的区必须是附加段。数据的存放比较灵活,实际上可以存放在任何一种逻辑段中。

    8、段超越前缀指令
    没有指明时,一般的数据访问在DS段;使用BP访问主存,则在SS段
    默认的情况允许改变,需要使用段超越前缀指令;8086指令系统中有4个,用于明确指定数据所在的逻辑段:
    CS: ;代码段超越,使用代码段的数据
    SS: ;堆栈段超越,使用堆栈段的数据
    DS: ;数据段超越,使用数据段的数据
    ES: ;附加段超越,使用附加段的数据

    【段超越示例】

    https://i.imgur.com/MmY9rui.png

    【不允许使用段超越的情况】
    串处理指令的目的串必须用ES段;PUSH指令的目的和POP指令的源必须用SS段;指令必须存放在CS段。

    【段寄存器的使用规定】

    https://i.imgur.com/x6FdLiI.png

    【补充】

    【什么是溢出】
    处理器内部以补码表示有符号数。8位表达的整数范围是:+127~-12816位表达的范围是:+32767~-32768。如果运算结果超出这个范围,就产生了溢出。有溢出,说明有符号数的运算结果不正确。

    【溢出和进位】
    溢出标志OF和进位标志CF是两个意义不同的标志。
    进位标志表示无符号数运算结果是否超出范围,运算结果仍然正确;
    溢出标志表示有符号数运算结果是否超出范围,运算结果已经不正确。

    https://i.imgur.com/InwlxCm.png

    【如何运用溢出和进位】
    处理器对两个操作数进行运算时,按照无符号数求得结果,并相应设置进位标志CF;同时,根据是否超出有符号数的范围设置溢出标志OF。应该利用哪个标志,则由程序员来决定。也就是说,如果将参加运算的操作数认为是无符号数,就应该关心进位;认为是有符号数,则要注意是否溢出。

    【溢出判断】
    判断运算结果是否溢出有一个简单的规则:
    只有当两个相同符号数相加(包括不同符号数相减),而运算结果的符号与原数据符号相反时,产生溢出;因为,此时的运算结果显然不正确。其他情况下,则不会产生溢出。

    补充

    1.什么是逻辑地址?
    逻辑地址是用户编程时使用的地址,分为段地址和偏移地址两部分。
    逻辑地址表示形式:3020:055AH---------(汇编语言中,数字后面加H表示16进制)

    2.为什么要用逻辑地址?(逻辑地址的产生背景)
    8086cpu访问存储器时,地址寄存器(16位)要先向地址总线发出地址信号(地址总线是专门用来存取内存地址的,故与内存单元有关,20位),而地址寄存器只有16位,从地址寄存器发出的地址信号,所能访问的存储空间只有2^16 = 65536 = 64KB,达不到20位地址总线所提供的地址范围。针对这种情况,就把内存地址分为若干段,每段有一些存储单元构成。用段地址指出是哪一段,偏移地址标明是段中的哪一个单元。

    3.什么叫段地址,偏移地址?之间有什么关系?
    Ⅰ.把内存地址分为若干段,每段有一些存储单元构成。用段地址指出是哪一段(若是指向同一个存储单元,段地址可以不一样,无非就是偏移地址不一样而已,但是都可以指向同一个物理地址,因此段地址只是一个被访问的存储单元(变量)的起始地址,并不是固定的),偏移地址标明是段中的哪一个单元
    Ⅱ.段地址和偏移地址都是16位2进制数。
    Ⅲ.段地址和偏移地址有多种组合,故存在多个地址组合指向同一个存储单元上。

    4.逻辑地址唯一么?
    不唯一,因为段地址和偏移有多种组合,故存在多个地址组合指向同一个存储单元上。例如:3020:055AH和3000:07AAH就是两种组合,但都是指向同一个存储单元,

    5.cpu执行程序时,采用的是逻辑地址还是物理地址?
    物理地址---用户编程时采用的逻辑地址在cpu执行程序时都要转换成物理地址。这是由cpu的地址加法器完成的。

    6.逻辑地址怎样转换为物理地址?
    转换时,先将16位的段地址左移4位,相当于乘以16或者16进制的10H,再和偏移地址相加。转换公式为:物理地址 = 段地址*10H + 偏移地址。如:将3020:055AH转换为物理地址:----= 3020*10H(左移四位)+055AH = 3075AH

    7.段与偏移地址是什么关系?
    段是由存储单元构成的,段包含偏移地址对应的存储单元。即偏移地址对应的字节存储单元在段中。.

    8.段的大小指的是什么?
    指的是这个段包含存储单元的多少。

    9.将内存分段的依据?以及段的相关知识
    段地址和偏移地址都是16位二进制数,每段最大64K字节单元(2^16=65536 = 64KB),每段最小16个字节单元(硬性规定),也可以100个,1000个到最多达到65536个。偏移地址范围:0000H --- FFFFH

    10.什么叫小段?
    规定每16个字节单元为一小段。

    三、理解“逻辑地址、线性地址、物理地址和虚拟地址

    1、各种地址概念

    物理地址(physical address)
    用于内存芯片级的单元寻址,与处理器和CPU连接的地址总线相对应。
    ——这个概念应该是这几个概念中最好理解的一个,但是值得一提的是,虽然可以直接把物理地址理解成插在机器上那根内存本身,把内存看成一个从0字节一直到最大空量逐字节的编号的大数组,然后把这个数组叫做物理地址,但是事实上,这只是一个硬件提供给软件的抽像,内存的寻址方式并不是这样。所以,说它是与地址总线相对应,是更贴切一些,不过抛开对物理内存寻址方式的考虑,直接把物理地址与物理的内存一一对应,也是可以接受的。也许错误的理解更利于形而上的抽像。
    虚拟内存(virtual memory)
    这是对整个内存的抽像描述。它是相对于物理内存来讲的,可以直接理解成不直实的假的内存,例如,一个0x08000000内存地址,它并不对就物理地址上那个大数组中0x08000000 - 1那个地址元素;之所以是这样,是因为现代操作系统都提供了一种内存管理的抽像,即虚拟内存(virtual memory)。进程使用虚拟内存中的地址,由操作系统协助相关硬件,把它转换成真正的物理地址。这个转换,是所有问题讨论的关键。有了这样的抽像,一个程序,就可以使用比真实物理地址大得多的地址空间,甚至多个进程可以使用相同的地址。不奇怪,因为转换后的物理地址并非相同的。
    ——可以把连接后的程序反编译看一下,发现连接器已经为程序分配了一个地址,例如,要调用某个函数A,代码不是call A,而是call 0x0811111111 ,也就是说,函数A的地址已经被定下来了。没有这样的转换,没有虚拟地址的概念,这样做是根本行不通的。打住了,这个问题再说下去,就收不住了。
    逻辑地址(logical address)
    Intel为了兼容,将远古时代的段式内存管理方式保留了下来。逻辑地址指的是机器语言指令中,用来指定一个操作数或者是一条指令的地址。以上例,我们说的连接器为A分配的0x08111111这个地址就是逻辑地址。不过不好意思,这样说,好像又违背了Intel中段式管理中,对逻辑地址要求,一个逻辑地址,是由一个段标识符加上一个指定段内相对地址的偏移量,表示为 [段标识符:段内偏移量],也就是说,上例中那个0x08111111,应该表示为[A的代码段标识符: 0x08111111],这样,才完整一些
    线性地址(linear address)
    线性地址或也叫虚拟地址(virtual address)跟逻辑地址类似,它也是一个不真实的地址,如果逻辑地址是对应的硬件平台段式管理转换前地址的话,那么线性地址则对应了硬件页式内存的转换前地址。
    CPU将一个虚拟内存空间中的地址转换为物理地址,需要进行两步:首先将给定一个逻辑地址(其实是段内偏移量,这个一定要理解!!!),CPU要利用其段式内存管理单元,先将为个逻辑地址转换成一个线程地址,再利用其页式内存管理单元,转换为最终物理地址。
    这样做两次转换,的确是非常麻烦而且没有必要的,因为直接可以把线性地址抽像给进程。之所以这样冗余,Intel完全是为了兼容而已。

    //导读注意:下面2-5部分,分别依次按照CPU段式内存管理、Linux段式内存管理、CPU页式内存管理、Linux页式内存管理

    2CPU段式内存管理,逻辑地址如何转换为线性地址

    一个逻辑地址由两部份组成,段标识符: 段内偏移量。段标识符是由一个16位长的字段组成,称为段选择符。其中前13位是一个索引号。后面3位包含一些硬件细节,如图:

    索引号,或者直接理解成数组下标——那它总要对应一个数组吧,它又是什么东东的索引呢?这个东东就是段描述符(segment descriptor)”,呵呵,段描述符具体地址描述了一个段(对于这个字眼的理解,我是把它想像成,拿了一把刀,把虚拟内存,砍成若干的截——段)。这样,很多个段描述符,就组了一个数组,叫段描述符表,这样,可以通过段标识符的前13位,直接在段描述符表中找到一个具体的段描述符,这个描述符就描述了一个段,我刚才对段的抽像不太准确,因为看看描述符里面究竟有什么东东——也就是它究竟是如何描述的,就理解段究竟有什么东东了,每一个段描述符由8个字节组成,如下图:

    这些东东很复杂,虽然可以利用一个数据结构来定义它,不过,我这里只关心一样,就是Base字段,它描述了一个段的开始位置的线性地址。
    Intel设计的本意是,一些全局的段描述符,就放在全局段描述符表(GDT)”中,一些局部的,例如每个进程自己的,就放在所谓的局部段描述符表(LDT)”中。那究竟什么时候该用GDT,什么时候该用LDT呢?这是由段选择符中的T1字段表示的,=0,表示用GDT=1表示用LDT
    GDT在内存中的地址和大小存放在CPUgdtr控制寄存器中,而LDT则在ldtr寄存器中。
    好多概念,像绕口令一样。这张图看起来要直观些:

    首先,给定一个完整的逻辑地址[段选择符:段内偏移地址]
    1、看段选择符的T1=0还是1,知道当前要转换是GDT中的段,还是LDT中的段,再根据相应寄存器,得到其地址和大小。我们就有了一个数组了。
    2、拿出段选择符中前13位,可以在这个数组中,查找到对应的段描述符,这样,它了Base,即基地址就知道了。
    3、把Base + offset,就是要转换的线性地址了。
    还是挺简单的,对于软件来讲,原则上就需要把硬件转换所需的信息准备好,就可以让硬件来完成这个转换了。OK,来看看Linux怎么做的。

    3Linux的段式内存管理

    Intel要求两次转换,这样虽说是兼容了,但是却是很冗余,呵呵,没办法,硬件要求这样做了,软件就只能照办,怎么着也得形式主义一样。另一方面,其它某些硬件平台,没有二次转换的概念,Linux也需要提供一个高层抽像,来提供一个统一的界面。所以,Linux的段式管理,事实上只是哄骗了一下硬件而已。按照Intel的本意,全局的用GDT,每个进程自己的用LDT——不过Linux则对所有的进程都使用了相同的段来对指令和数据寻址。即用户数据段,用户代码段,对应的,内核中的是内核数据段和内核代码段。这样做没有什么奇怪的,本来就是走形式嘛,像我们写年终总结一样。
    include/asm-i386/segment.h

    1. #define GDT_ENTRY_DEFAULT_USER_CS        14
    2. #define __USER_CS (GDT_ENTRY_DEFAULT_USER_CS * 8 + 3)
    3.  
    4. #define GDT_ENTRY_DEFAULT_USER_DS        15
    5. #define __USER_DS (GDT_ENTRY_DEFAULT_USER_DS * 8 + 3)
    6.  
    7. #define GDT_ENTRY_KERNEL_BASE        12
    8.  
    9. #define GDT_ENTRY_KERNEL_CS       (GDT_ENTRY_KERNEL_BASE + 0)
    10. #define __KERNEL_CS (GDT_ENTRY_KERNEL_CS * 8)
    11.  
    12. #define GDT_ENTRY_KERNEL_DS     (GDT_ENTRY_KERNEL_BASE + 1)
    13. #define __KERNEL_DS     (GDT_ENTRY_KERNEL_DS * 8)

    复制代码
    把其中的宏替换成数值,则为:

    1. #define __USER_CS 115        [00000000 1110  0  11]
    2. #define __USER_DS 123        [00000000 1111  0  11]
    3. #define __KERNEL_CS 96      [00000000 1100  0  00]
    4. #define __KERNEL_DS 104    [00000000 1101  0  00]

    复制代码
    方括号后是这四个段选择符的16位二制表示,它们的索引号和T1字段值也可以算出来了

    1. __USER_CS              index= 14   T1=0
    2. __USER_DS              index= 15   T1=0
    3. __KERNEL_CS           index=  12  T1=0
    4. __KERNEL_DS           index= 13   T1=0

    复制代码
    T1均为0,则表示都使用了GDT,再来看初始化GDT的内容中相应的12-15(arch/i386/head.S)

    1. .quad 0x00cf9a000000ffff        /* 0x60 kernel 4GB code at 0x00000000 */
    2. .quad 0x00cf92000000ffff        /* 0x68 kernel 4GB data at 0x00000000 */
    3. .quad 0x00cffa000000ffff        /* 0x73 user 4GB code at 0x00000000 */
    4. .quad 0x00cff2000000ffff        /* 0x7b user 4GB data at 0x00000000 */

    复制代码
    按照前面段描述符表中的描述,可以把它们展开,发现其16-31位全为0,即四个段的基地址全为0
    这样,给定一个段内偏移地址,按照前面转换公式,0 + 段内偏移,转换为线性地址,可以得出重要的结论,Linux下,逻辑地址与线性地址总是一致(是一致,不是有些人说的相同)的,即逻辑地址的偏移量字段的值与线性地址的值总是相同的。!!!
    忽略了太多的细节,例如段的权限检查。呵呵。
    Linux中,绝大部份进程并不例用LDT,除非使用Wine ,仿真Windows程序的时候。

    4.CPU的页式内存管理

    CPU的页式内存管理单元,负责把一个线性地址,最终翻译为一个物理地址(注意,相互独立的进程都有自己的页目录和页表,就算多个进程具有相同的线性地址,最后转换到物理地址也是不一样的,所以不会互相干扰)。从管理和效率的角度出发,线性地址被分为以固定长度为单位的组,称为页(page),例如一个32位的机器,线性地址最大可为4G,可以用4KB为一个页来划分,这页,整个线性地址就被划分为一个tatol_page[2^20]的大数组,共有220个次方个页。这个大数组我们称之为页目录。目录中的每一个目录项,就是一个地址——对应的页的地址。另一类“,我们称之为物理页,或者是页框、页桢的。是分页单元把所有的物理内存也划分为固定长度的管理单位,它的长度一般与内存页是一一对应的。
    这里注意到,这个total_page数组有2^20个成员,每个成员是一个地址(32位机,一个地址也就是4字节),那么要单单要表示这么一个数组,就要占去4MB的内存空间。为了节省空间,引入了一个二级管理模式的机器来组织分页单元。文字描述太累,看图直观一些:

    如上图,
    1、分页单元中,页目录是唯一的,它的地址放在CPUcr3寄存器中,是进行地址转换的开始点。
    2、每一个活动的进程,因为都有其独立的对应的虚似内存(页目录也是唯一的),那么它也对应了一个独立的页目录地址。——运行一个进程,需要将它的页目录地址放到cr3寄存器中,将别个的保存下来。
    3、每一个32位的线性地址被划分为三部份,面目录索引(10):页表索引(10):偏移(12)

    依据以下步骤进行转换:
    1、从cr3中取出进程的页目录地址(操作系统负责在调度进程的时候,把这个地址装入对应寄存器);
    2、根据线性地址前十位,在数组中,找到对应的索引项,因为引入了二级管理模式,页目录中的项,不再是页的地址,而是一个页表的地址。(又引入了一个数组),页的地址被放到页表中去了。
    3、根据线性地址的中间十位,在页表(也是数组)中找到页的起始地址;
    4、将页的起始地址与线性地址中最后12位相加,得到最终我们想要的葫芦;

    这个转换过程,应该说还是非常简单地。全部由硬件完成,虽然多了一道手续,但是节约了大量的内存,还是值得的。那么再简单地验证一下:
    1、这样的二级模式是否仍能够表示4G的地址;
    页目录共有:2^10项,也就是说有这么多个页表
    每个目表对应了:2^10页;
    每个页中可寻址:2^12个字节。
    还是2^32 = 4GB

    2、这样的二级模式是否真的节约了空间;
    也就是算一下页目录项和页表项共占空间 (2^10 * 4 + 2 ^10 *4) = 8KB。哎,……怎么说呢!!!
    红色错误,标注一下,后文贴中有此讨论。。。。。。
    <深入理解计算机系统>中的解释,二级模式空间的节约是从两个方面实现的:
    A
    、如果一级页表中的一个页表条目为空,那么那所指的二级页表就根本不会存在。这表现出一种巨大的潜在节约,因为对于一个典型的程序,4GB虚拟地址空间的大部份都会是未分配的;
    B、只有一级页表才需要总是在主存中。虚拟存储器系统可以在需要时创建,并页面调入或调出二级页表,这就减少了主存的压力。只有最经常使用的二级页表才需要缓存在主存中。——不过Linux并没有完全享受这种福利,它的页表目录和与已分配页面相关的页表都是常驻内存的。
    值得一提的是,虽然页目录和页表中的项,都是4个字节,32位,但是它们都只用高20位,低12位屏蔽为0——把页表的低12屏蔽为0,是很好理解的,因为这样,它刚好和一个页面大小对应起来,大家都成整数增加。计算起来就方便多了。但是,为什么同时也要把页目录低12位屏蔽掉呢?因为按同样的道理,只要屏蔽其低10位就可以了,不过我想,因为12>10,这样,可以让页目录和页表使用相同的数据结构,方便。
    本贴只介绍一般性转换的原理,扩展分页、页的保护机制、PAE模式的分页这些麻烦点的东东就不啰嗦了……可以参考其它专业书籍。

    5.Linux的页式内存管理

    原理上来讲,Linux只需要为每个进程分配好所需数据结构,放到内存中,然后在调度进程的时候,切换寄存器cr3,剩下的就交给硬件来完成了(呵呵,事实上要复杂得多,不过偶只分析最基本的流程)。
    前面说了i386的二级页管理架构,不过有些CPU,还有三级,甚至四级架构,Linux为了在更高层次提供抽像,为每个CPU提供统一的界面。提供了一个四层页管理架构,来兼容这些二级、三级、四级管理架构的CPU。这四级分别为:
    页全局目录PGD(对应刚才的页目录)
    页上级目录PUD
    (新引进的)
    页中间目录PMD
    (也就新引进的)
    页表PT
    (对应刚才的页表)。

    整个转换依据硬件转换原理,只是多了二次数组的索引罢了,如下图:

    那么,对于使用二级管理架构32位的硬件,现在又是四级转换了,它们怎么能够协调地工作起来呢?嗯,来看这种情况下,怎么来划分线性地址吧!
    从硬件的角度,32位地址被分成了三部份——也就是说,不管理软件怎么做,最终落实到硬件,也只认识这三位老大。
    从软件的角度,由于多引入了两部份,,也就是说,共有五部份。——要让二层架构的硬件认识五部份也很容易,在地址划分的时候,将页上级目录和页中间目录的长度设置为0就可以了。
    这样,操作系统见到的是五部份,硬件还是按它死板的三部份划分,也不会出错,也就是说大家共建了和谐计算机系统。
    这样,虽说是多此一举,但是考虑到64位地址,使用四层转换架构的CPU,我们就不再把中间两个设为0了,这样,软件与硬件再次和谐——抽像就是强大呀!!!
    例如,一个逻辑地址已经被转换成了线性地址,0x08147258,换成二制进,也就是:
    0000100000 0101000111 001001011000
    内核对这个地址进行划分
    PGD = 0000100000
    PUD = 0
    PMD = 0
    PT = 0101000111
    offset = 001001011000

    现在来理解Linux针对硬件的花招,因为硬件根本看不到所谓PUD,PMD,所以,本质上要求PGD索引,直接就对应了PT的地址。而不是再到PUDPMD中去查数组(虽然它们两个在线性地址中,长度为02^0 =1,也就是说,它们都是有一个数组元素的数组),那么,内核如何合理安排地址呢?
    从软件的角度上来讲,因为它的项只有一个,32位,刚好可以存放与PGD中长度一样的地址指针。那么所谓先到PUD,到到PMD中做映射转换,就变成了保持原值不变,一一转手就可以了。这样,就实现了逻辑上指向一个PUD,再指向一个PDM,但在物理上是直接指向相应的PT的这个抽像,因为硬件根本不知道有PUDPMD这个东西

    然后交给硬件,硬件对这个地址进行划分,看到的是:
    页目录 = 0000100000
    PT = 0101000111
    offset = 001001011000

    嗯,先根据0000100000(32),在页目录数组中索引,找到其元素中的地址,取其高20位,找到页表的地址,页表的地址是由内核动态分配的,接着,再加一个offset,就是最终的物理地址了。

    参见:
    http://www.cnblogs.com/diyingyun/archive/2012/01/03/2311327.html
    "
    http://blog.sina.com.cn/s/blog_79ba23780102vz77.html"
    "https://www.cnblogs.com/exRunner/p/7531850.html"

     

    展开全文
  • 单片机汇编语言实验及代码 以下内容为本人实验课亲自编写且通过51单片机运行实现的实验代码,有错误或者改进的地方请批评指正。 实验一:开发环境的应用 (1)将数据存储器的40H-4FH的内容复制成00H-0FH,并将40H-4...

    单片机汇编语言实验及代码

    以下内容为本人实验课亲自编写且通过51单片机运行实现的实验代码,有错误或者改进的地方请批评指正。

    实验一:开发环境的应用

    (1)将数据存储器的40H-4FH的内容复制成00H-0FH,并将40H-4FH的内容整体复制到外部数据存储器的1050H-105FH.
    (2)P1口接八只二极管,使二极管逐个循环点亮

    ORG 0000H
    AJMP START
    ORG 0100H
    START:
          MOV A,#00H
    	  MOV R0,#40H
    LOOP:
          MOV @R0,A
    	  INC A
    	  INC R0
    	  CJNE A,#10H,LOOP
    SECOND:
          MOV DPTR,#1050H
    	  MOV R1,#40H
    COPY:
          MOV A,@R1
    	  MOVX @DPTR,A
    	  INC DPTR
    	  INC R1
    	  CJNE R1,#50H,COPY
    	  SJMP $
    	  END
    

    实验二:外部中断

    (1)用单脉冲信号申请中断,在中断服务程序中对输出信号进行翻转(可通过P1.0接一二极管观察)

    ORG 0000H
    LJMP START
    ORG 0003H
    LJMP INT
    ORG 0100H
    START:
          SETB EX0
    	  SETB PX0
    	  SETB IT0
    	  SETB EA
    	  SJMP $
    INT:
    	  CPL P1.0
    	  RETI
    	  END
    

    (2)用单脉冲信号申请中断,要求对中断进行累加计数,并将计数结果显示在二极管上

    ORG 0000H
    LJMP START
    ORG 0003H
    LJMP INT
    ORG 0100H
    START:
          MOV A,#00H
    	  MOV P1,A
    	  SETB EX0
    	  SETB PX0
    	  SETB IT0
    	  SETB EA
    	  SJMP $
    INT:  
    	  MOV P1,A
    	  INC A
    	  RETI
    	  END
    

    实验三:定时器实验

    (1)用定时器1的方式1定时,P1.0节发光二极管,使二极管每秒闪烁五次

    ORG 0000H
    LJMP MAIN
    ORG 001BH
    LJMP INT
    ORG 0100H
    MAIN:
         MOV TMOD,#10H
    	 MOV TH1,#3CH
    	 MOV TL1,#0B0H
    	 MOV R2,#2
    	 SETB EA
    	 SETB ET1
    	 SETB TR1
    	 SJMP $
    INT:
         DJNZ R2,NEXT
    	 CPL P1.0
    	 MOV R2,#2
    NEXT:
         MOV TH1,#3CH
    	 MOV TL1,#0B0H
    	 SETB TR1
    	 RETI
    	 END
    

    (2)用定时器1的方式2计数,T1引脚接单脉冲,用P1口接二极管显示计数。

    ORG 0000H
    LJMP MAIN
    ORG 001BH
    LJMP LOOP
    ORG 0100H
    MAIN:
         MOV TMOD,#60H
    	 MOV TH1,#00H
    	 MOV TL1,#00H
    	 SETB EA
    	 SETB ET1
    	 SETB TF1
    	 SETB TR1
    	 SJMP $
    LOOP:
         MOV P1,TL1
    	 LJMP LOOP
    	 RETI
    	 END
    

    实验四:串行通信

    波特率2400 bit/s。PC发送8个字节的数据给单片机,单片机接受完数据后发送两个字节55H和AAH给PC 机(使用查询方式)。

    ORG 0000H
    AJMP MAIN
    ORG 0030H
    MAIN:
    MOV SCON,#50H
    MOV PCON,#00H
    MOV TMOD,#20H
    SETB TR1
    MOV	TL1,#0F3H
    MOV TH1,#0F3H
    LOOP:
        MOV R0,#30H
    	MOV R1,#08H
    WAIT1:
        JBC RI,RECEIVE
    	SJMP WAIT1
    RECEIVE:
        MOV A,SBUF
    	MOV @R0,A
    	INC R0
    	DJNZ R1,WAIT1
    	MOV A,#55H
    	MOV SBUF,A
    WAIT21:
        JBC TI,SEND
    	SJMP WAIT21
    SEND:
        MOV A,#0AAH
    	MOV SBUF,A
    WAIT22:
        JNB TI,WAIT22
    	CLR TI
    	SJMP LOOP
    	END
    

    实验五:数码管显示及键盘扫描

    (1)将30H-32H中存储的6位十进制数在6个数码管上显示出来(要求编写一个将内存单元中的压缩BCD码转换为两个七段显示码的子程序和延时子程序)

    ORG 0000H
    LJMP MAIN
    ORG 0030H
    
    MAIN:
       MOV 30H,#12H
       MOV 31H,#34H
       MOV 32H,#56H
       MOV R0,#30H
       MOV R1,#20H
       
    BCD:
       MOV A,@R0
       SWAP A
       ANL A,#0FH
       ACALL CTAB
       MOV R2,A
       MOV A,@R0
       ANL A,#0FH
       ACALL CTAB
       MOV R3,A
       
       MOV DPTR,#8002H
       MOV A,R1
       MOVX @DPTR,A
       MOV DPTR,#8004H
       MOV A,R2
       MOVX @DPTR,A
       MOV A,R1
       RR A
       MOV R1,A
       LCALL DLEY
       
       MOV DPTR,#8002H
       MOV A,R1
       MOVX @DPTR,A
       MOV DPTR,#8004H
       MOV A,R3
       MOVX @DPTR,A
       MOV A,R1
       RR A
       MOV R1,A
       LCALL DLEY
       INC R0
       CJNE R0,#33H,BCD
       LJMP MAIN
       
    CTAB:
       PUSH DPH
       PUSH DPL
       MOV DPTR,#TAB
       MOVC A,@A+DPTR
       POP DPL
       POP DPH
       RET
       
    TAB:
       DB 3FH,06H,5BH,4FH,66H,6DH,7DH,07H,7FH,6FH,77H,7CH,39H,5EH,79H,71H
    DLEY:
       MOV R7,#02H
     DL:MOV R6,#0FFH
     DL1:DJNZ R6,DL1
          DJNZ R7,DL
    	  RET  
    	  END
    

    (2)扫描矩阵键盘,将对应键盘值送LED显示

         ORG 0000H
           LJMP START
           ORG 1000H
    START:
    	   ACALL   KEY1
    	   AJMP    START
    KEY1:  ACALL   KEY14
    	   JNZ     KEY3
    KEY2:  ACALL   KEY16 
    	   AJMP    KEY1 
    KEY3:  ACALL   KEY16
    	   ACALL   DELAY 
    	   ACALL   KEY14
    	   JNZ     KEY4
    	   ACALL   KEY16
    	   AJMP    KEY1
    
    KEY4:  MOV     R2, #0FEH 
    	   MOV     R4, #00H
    
    KEY5:  MOV     DPTR,#8002H 
    	   MOV     A, R2 
    	   MOVX    @DPTR, A 
    	   MOV     DPTR,#8001H
    	   MOVX    A,@DPTR 
    	   JB      ACC.0,KEY6 
    	   MOV     A, #00H 
    	   AJMP    KEY9
    
    KEY6:  JB      ACC.1,KEY7 
    	   MOV     A, #06H
    	   AJMP    KEY9 
    
    KEY7:  JB      ACC.2,KEY8
    	   MOV     A, #0CH
    	   AJMP    KEY9
    	    
    KEY8:  JB      ACC.3, KEY12 
    	   MOV     A, #12H 
    KEY9:  ADD     A, R4 
    KEY10: MOV     R5,A
    KEY11: ACALL   KEY14  
    	   JNZ     KEY11 
    	   MOV     A,R5
    	   ACALL   KEY15
    	   RET 
    
    KEY12: INC     R4  
    	   MOV     A, R2  
    	   JNB     ACC.5,KEY13  
    	   RL      A  
    	   MOV     R2, A  
    	   AJMP    KEY5 
    	
    KEY13: AJMP    KEY1 
    KEY14: MOV     DPTR,#8002H 
    	   MOV     A,#00H 
    	   MOVX    @DPTR,A 
    	   MOV     DPTR,#8001H
    	   MOVX    A,@DPTR 
    	   CPL     A 
    	   ANL     A, #0FH 
    	   RET
    
    KEY15: MOV     DPTR,#TABLE
    	   MOVC    A,@A+DPTR
    	   MOV     R3,A
    
    KEY16: MOV     DPTR,#8002H
           MOV     A,#01H
    	   MOVX    @DPTR,A
           MOV     DPTR,#8004H
    	   MOV     A,R3
    	   MOVX    @DPTR,A
    	   ACALL   DELAY
    	   RET
    
    DELAY: MOV     R7,#12
    LOOP:  MOV     R6,#250
    	   DJNZ    R6,$
    	   DJNZ    R7,LOOP
    	   RET
    TABLE:
    
    	   DB      7EH,7EH,5EH,79H,71H,3FH,7EH,7EH,39H,4FH,5BH,06H,7EH,7EH,7CH,7DH,6DH,66H	,7EH,7EH,77H,6FH,7FH,07H
    
           END
    

    实验六:D/A和A/D

    (1)利用实验箱上的电位器提供模拟量输入,将模拟量转换成二进制数字量,将转换的结果用P1口输出到二极管显示;

    ORG 0000H
    LJMP MAIN
    ORG 0100H
    MAIN:
        MOV DPTR,#8000H
    	MOV A,#00H
    	MOVX @DPTR,A
    	MOV R2,#100
    	DJNZ R2,$
    	MOVX A,@DPTR
    	MOV P1,A
    	LJMP MAIN
    
    	END
    

    (2)用DAC0832将一个数值转换为模拟电压,该模拟电压接ADC0809的一个模拟输入进行模/数转换,转换结果通过P1口送到二极管显示。

    ORG 0000H
    LJMP MAIN
    ORG 0100H
    MAIN:
        MOV A,#90H
    	MOV DPTR,#9000H
    	MOVX @DPTR,A
    	MOV DPTR,#8000H
    	MOVX @DPTR,A
    	MOV R2,#64H
    	DJNZ R2,$
    	MOVX A,@DPTR
    	MOV P1,A
    	SJMP $
    	END
    

    实验七:电子钟

    实现一个24小时制的电子钟程序,在6个数码管上显示时分秒(用定时器0中断更新计时,以压缩BCD码形式保存在内部RAM的30H、31H和32H单元);可以通过串行口调时!

    ORG  0000H
    AJMP MAIN
    ORG  000BH
    LJMP CLOCK
    ORG  0023H
    LJMP INPUT
    ORG  0100H
    
    MAIN:
        MOV  TMOD,#21H
        CLR  A
    	MOV  30H,A
    	MOV  31H,A
    	MOV  32H,A
    
    	MOV  TH0,#3CH
    	MOV  TL0,#0B0H
    	MOV  50H,#14H	  ;50 * 20
    	MOV  TH1,#0F3H
    	MOV  TL1,#0F3H	  ;波特率
    
    	MOV  SCON,#50H	
    	SETB PS
    	MOV  IE,#92H	
    	SETB TR1
    	SETB TR0
    	
    SLED:
        MOV  R0,#30H
    	MOV  R1,#20H
    	LJMP LED
    	
    CLOCK:
    
    	MOV  TH0,#3CH
    	MOV  TL0,#0B0H
    	DJNZ 50H,RETURN
    	MOV  50H,#14H
    	
    	MOV  A,#01H
    	ADD  A,32H
    	DA   A
    	MOV  32H,A
    	CJNE A,#60H,RETURN
    	MOV  32H,#00H
    	
    	MOV  A,#01H
    	ADD  A,31H
    	DA   A
    	MOV  31H,A
    	CJNE A,#60H,RETURN
    	MOV  31H,#00H
    	
    	MOV  A,#01H
    	ADD  A,30H
    	DA   A
    	MOV  30H,A
    	CJNE A,#24H,RETURN
    	MOV  30H,#00H
    	
    RETURN:
    
    	RETI	
    	
    LED:
        MOV  A,@R0
    	SWAP A
    	ANL  A,#0FH
    	ACALL CTAB
    	MOV  R2,A
    	
    	MOV  A,@R0
    	ANL  A,#0FH
    	LCALL CTAB
    	MOV  R3,A
    	
    	MOV  DPTR,#8002H
    	MOV  A,R1
    	MOVX @DPTR,A
    	MOV  DPTR,#8004H
    	MOV  A,R2
    	MOVX @DPTR,A
    	MOV  A,R1
    	RR   A
    	MOV  R1,A
    	LCALL DLY
    	
    	MOV  DPTR,#8002H
    	MOV  A,R1
    	MOVX @DPTR,A
    	MOV  DPTR,#8004H
    	MOV  A,R3
    	MOVX @DPTR,A
    	MOV  A,R1
    	RR   A
    	MOV  R1,A
    	LCALL DLY
    	
    	INC  R0
    	CJNE R0,#33H,LED
    	LJMP SLED
    	
    CTAB:
        PUSH DPH
    	PUSH DPL
    	MOV  DPTR,#TAB
    	MOVC A,@A+DPTR
    	POP  DPL
    	POP  DPH
    	RET
    	
    INPUT:
    
    	CLR  RI
    	CLR  EA
    	MOV  30H,SBUF
    	JNB  RI,$
    	CLR  RI
    	MOV  31H,SBUF
    	JNB  RI,$
    	CLR  RI
    	MOV  32H,SBUF
    	SETB EA
    	RETI
    
    TAB:
        DB 3FH,06H,5BH,4FH,66H,6DH,7DH,07H,7FH,6FH
    	
    DLY:
        MOV  R7,#02H
     DL:
        MOV  R6,#0FFH
    DL1:
        DJNZ R6,DL1
    	DJNZ R7,DL
    	RET
    	
    	END
    
    展开全文
  • 要求:将定时器 T1 设置为外部事件计数器,要求每计 500 个脉冲,再将 T1 转为定时方式,在 P1.2 输出一个脉宽 10ms 的正脉冲。周而复始。设系统时钟频率为 12MHz。做而论道编写的程序如下: ORG 0000H...
  • 基于51单片机的定时器代码,用汇编语言写的,在实际开发板上运行过,有用.
  • 51单片机汇编写40%占空比pwm LJMP MAIN; ORG 000BH; LJMP ITP0; ORG 0100H; MAIN: ACALL PT0M0; MOV A,#00; MOV 30H,#4; PT0M0: MOV TMOD,#02H;Initialize FANGSHI ER mode MOV TL0,#0CEH;...
  • MCS-51单片机的特殊功能寄存器 符号 地址 功能介绍 B F0H B寄存器 ACC E0H 累加器 PSW D...
  • 80C51单片机的时钟频率为12MHz,利用定时器T1和P1.0输出矩形脉冲。波形只画出了2段:一段为100us 另一段为50us。要完全的、完整的、详细的编写此程序的过程!谢谢悬赏分:5 | 解决时间:2011-1-9 15:24-------------...
  • lab8000单片机仿真实验 汇编语言程序
  • 1.ASM1_KEY: 按键控制LED灭,3种情况 /** ****************************************************************************** * @file asm.asm * @author Alex——小白 * @version V1.0 ...
  • 51单片机汇编语言

    2020-05-24 14:04:23
    51单片机汇编语言操作指令总结 1.指令格式 【标号:】操作码 目的操作数 ,源操作数;注释 如:LOOP:MOV A, 33H;将33H输入到累加器A中 2.ORG xxxxH命令 是指向程序不同功能固定入口,如: 0000H----系统复位入口 ...
  • 5.ASM5_USART: 串口发送0-9及换行,接收任意数据并显示 /** ****************************************************************************** * @file USART.asm * @author Alex——小白 ...
  • (1)组成:两个16位的定时器T0和T1,以及他们的工作方式寄存器TMOD和控制寄存器TCON等组成。内部由总线与CPU连接。 (2)工作模式 每个定时器内部结构实际上就是一个可编程的加法计数器,由编程来设置它工作在定时...
  • 结果老师给了他一份汇编程序,由于自己之前做过汇编编程的工作,就胆(no)大(zuo)妄(no)为(dai)的说是给他翻译成C语言的。 当我仔细瞅了那份程序之后。额,好吧,我只能说我尽力而为吧! 东西只有一篇近2000行的汇编...
  • 单片机定时器控制LED。P1口的P1.0-P1.7分别接八个发光二极管。编写程序模拟时序控制装置。开机后第一秒钟L1,L3,第二秒钟L2,L4,第三秒钟L5,L7,第四秒钟L6,L8,第五秒钟L1,L3,L5,L7,第六秒钟L2...
  • 第一章 单片机概述(1KB=1024B、1B=8b) 单片机(嵌入式控制器、微控制器):在一片硅导体上,集成了“中央处理单元(CPU)、存储器(RAM、ROM)、中断系统、定时器\计数器 并行IO、串行IO、时钟电路及总线”,应用...
  • 1.C语言实现  #include typedef unsigned char uint8; typedef unsigned int uint16;...typedef unsigned long uint32;...uint8 number[] = {0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80
  • 51单片机定时器(汇编)

    2020-05-26 21:06:10
    定时器控制流水的速度,但流水到边缘的时候,自动调整方向,先反方向继续流动 使用的是 定时器0 ORG 0000H LJMP MIAN ORG 000BH LJMP T0_break ORG 0200H INIT: MOV TMOD,#01H ;定时器0为...
1 2 3 4 5 ... 20
收藏数 893
精华内容 357