490 likes | 629 Views
第4章 汇编语言程序设计. 汇编语言是一种面向机器的语言, 与面向过程的高级语言相比, 其 缺点 是:编程不够方便、不易移植到其他类型机器。但其显著 优点 是: 程序结构紧凑、占用存储空间小; 实时性强, 执行速度快; 能直接管理和控制存储器及硬件接口、充分发挥硬件的作用。因而特别适用于编制要求实时测控、软硬件关系密切、程序编制工作量不太大的微控制器应用程序。. 本章主要内容. 4 . 1 汇编语言的格式与构成 4 . 1 . 1 汇编语言的格式 4 . 1 . 2 伪指令 4 . 1 . 3 源程序的汇编
E N D
汇编语言是一种面向机器的语言, 与面向过程的高级语言相比, 其缺点是:编程不够方便、不易移植到其他类型机器。但其显著优点是: 程序结构紧凑、占用存储空间小; 实时性强, 执行速度快; 能直接管理和控制存储器及硬件接口、充分发挥硬件的作用。因而特别适用于编制要求实时测控、软硬件关系密切、程序编制工作量不太大的微控制器应用程序。
本章主要内容 • 4.1 汇编语言的格式与构成 • 4.1.1 汇编语言的格式 • 4.1.2 伪指令 • 4.1.3 源程序的汇编 • 4.2 汇编语言程序设计方法 • 4.2.1 分支结构 • 4.2.2 循环结构 • 4.2.3 子程序调用
4.1 汇编语言的格式与构成 • 4.1.1 汇编语言的格式 • 为了让机器能够正确识别和汇编, 汇编语言语句格式规定顺序为 • 标号 指令操作码 操作数 ; 注释 • 其中, 只有指令操作码是不可缺少的。
1. 标号 • 标号(label) 是语句地址的标志符号, 并不是每一条语句都需要加标号,只有那些被其他语句引用的语句之前才需要加标号。汇编时把指令机器码第一个字节在程序存储器中的地址赋值给该标号,这样, 在其他指令的操作数字段中,标号就可以作为一个确定的数值来使用, 便于子程序的调用、转移指令的转入及调试时查找、修改。
各种微控制器对使用标号的规定各有不同, 比较通常、明晰而不易出错的使用标号方法是: (1) 标号是用户定义的字符串, 一般由字母和数字组成, 第一个字符宜用大写字母; 为避免混淆, 不要用指令助记符、寄存器名、标志符或其他在系统中已有固定用途的字符串(又称为系统保留字) 等作为标号。 (2) 标号一般须从一行语句的第一个字符开始, MCS-51 规定标号后紧跟冒号“ :” 作为标号的结束, 一般用空格、Tab 制表符与操作码隔开; M68HC08和PIC 的标号后可以不用“ :” ; PIC 则规定标号须另起一行。
(3) 汇编程序对标号的长度(字符个数) 各有规定, 一般不要超过8 个字符以免超过长度后无效。为便于编写程序, 经常用到的标号长度可以定义得短一些。 (4) 一个标号只能表示一个地址, 不允许多个地址用一个标号重复定义。
2. 指令操作码和操作数 • 指令分为两类: 执行指令(即计算机指令系统给出的各种指令) 和伪指令(由汇编程序规定, 提供汇编控制信息的指令) 。 • 执行指令由操作码(opcode) 和操作数(operand) 组成,操作码和操作数之间至少需有一个空格。操作码是每一条汇编指令中不可缺少的部分, 而操作数可以没有, 或者有1 个、2 个、3 个, 各个操作数之间需用“ ,”分隔。
操作数可以是: (1) 各种进制的立即数, 一般规定二进制加后缀B 、十进制加后缀D 或不加、十六进制加后缀H (以字母开头需加前导0) ; M68 HC08 则规定: 二进制、十进制、十六进制数分别加前缀% 、!、$ 或者后缀Q 、T 、H 表示, ASCII 码用单引号(‘’) 表示。 (2) 工作寄存器或特殊功能寄存器的代号。 (3) 已定义或赋值的代表数据或地址的标号或字符串。 (4) 经过定义的符号以及表达式。
3. 注释 • 注释(comment) 只是对程序的注解和说明, 不是程序的功能部分; 但是最好养成附带注释的习惯, 以提高程序可读性, 便于阅读、交流、修改和调试。通常对程序完成的功能、主要内容、指令的作用和执行的条件、进入和退出子程序的出入口条件等关键进行注释。 • 注释和源程序一起存储、打印, 但汇编时不被翻译, 因而在机器代码的目标程序中并不出现, 也不影响程序的执行。 • 注释必须以分号“ ;” 开始, 注释较长占用多行时, 每一行都必须以“ ;”开始。
4.1.2 伪指令 • 伪指令仅仅在机器汇编时供汇编程序识别和执行, 用来对汇编过程进行控制和操作。例如: 规定汇编生成的目标代码在内存中的存放区域、给源程序中的符号和标号赋值、指示汇编的结束等。汇编时伪指令并不产生供机器直接执行的机器码, 也不会直接影响存储器中代码和数据的分布。 • 不同的微控制器汇编程序对伪指令的规定略有不同, 常见的伪指令如下:
1. 定位伪指令 • ORG 指定地址用的16位数、标号或表达式 • 定位伪指令ORG (origin) 指示对源程序开始进行汇编, 并指定其后机器指令的地址, 即生成目标程序或数据块的起始存储地址。一个汇编语言源程序中可以使用多条ORG 伪指令, 但后一条ORG 指定的地址应大于前面机器码已占用的存储地址。
2. 结束汇编伪指令 • END • 结束汇编伪指令END 必须放在汇编语言源程序的末尾。机器汇编时遇到END 就认为源程序已经结束, 对END 后面指令都不再汇编。
3. 赋值伪指令 • 名称字符 EQU 数据或汇编符 • 赋值伪指令EQU (equate) 将其右边的“数据或汇编符” 赋给左边的“名称字符” 。“名称字符” 必须先赋值后使用, 通常将赋值语句放在源程序的开头。 • “名称字符” 被赋值后, 在程序中就可以作为一个8 位或16 位的数据或地址来使用。
4. 定义字节伪指令( MCS-51和M68HC08) • DB 一个或一串用逗号分开的字节 • 定义字节伪指令DB (define byte) 将其右边的数据依次存放到以左边标号为始址的存储单元中, 8 位二进制数可以采用二进制、十进制、十六进制和ASCII 码等多种表示形式。例如: DB 15H,73,01011010B,‘5’,‘A’
5. 定义字伪指令( MCS-51 和M68 HC08) • DW 一个或一串用逗号分开的字(2 个字节) • 定义字伪指令DW (define word) 功能与DB 相似, 但DW 定义的是一个字(2 个字节) , 主要用于定义16 位地址(高8 位在前, 低8 位在后) 。
6. 定义存储空间伪指令(MCS-51和M68HC08) • DS 表达式 • 定义存储空间伪指令DS (define storage) 指示汇编程序预留一定数量的内存单元, 预留单元的个数由“表达式” 的值决定。
7. 数据地址赋值伪指令(MCS-51) • 名称字符 DATA 表达式 • 数据地址赋值伪指令DATA 将其右边“表达式” 的值赋给左边的“名称字符” 。表达式可以是一个8 位或16 位的数据或地址, 也可以是包含所定义“名称字符” 在内的表达式, 但不可以是一个汇编符号(如R0 ~ R7) 。 • DATA 伪指令定义的“字符名称” 没有先定义后使用的限制, 可以用在源程序的开头或末尾。
8. 位地址赋值伪指令(MCS-51) • 名称字符 BIT 位地址 • 位地址赋值伪指令BIT 把右边的位地址赋值给左边的“名称字符” 。
[例4-1]伪指令在MCS-51汇编程序中的应用。 解: ORG 8000H AA EQU R1 A10 EQU 10H DELAY EQU 87E6H START :MOV R0 ,A10 ; R0 ← (10H) MOV A , AA ; A ← (R1) LCALL DELAY ; 调用起始地址为87E6H 的子程序 … ORG 8700H NEXT : MOV R2 , # 42H ; R2 ← 42H … END
以START 开始的程序汇编为机器码后从8000 H 存储单元开始连续存放,以NEXT 开始的程序机器码则从8700 H 存储单元开始连续存放。 • EQU 赋值后, AA 为寄存器R1 , A10 为8 位直接地址, DELAY 为16 位地址87E6 H 。
[例4-2]伪指令在M68HC08汇编程序中的应用。 解:PTA EQU $0000 DDRA EQU $0004 N1 EQU $0040 ORG $0050 DB 1 , 2 , 3 , ‘4’, ‘5’ … ORG $8000 Main : LDA #$00 ; A 口设置为输入口 STA DDRA LDA PTA ; 读A 口状态 STA N1 ; 送到N1 … ORG $FFFE ; 复位矢量 DW Main
程序通过EQU 伪指令把$0000 、$0004 和$0040 分别赋值给PTA 、DDRA和N1 , DB 指令定义了$0050 开始的RAM 区域中五个单元的内容$01 、$02 、$03 、$34 (4 的ASCII 码) 、$35 (5 的ASCII 码) , Main 开始的程序编译后放在$8000 开始的程序存储器区域, 程序的复位入口地址也由DW 指令送入复位矢量区。
[例4-3]伪指令在PIC 汇编程序中的应用。 解: list p = 16f877 #include p16f877.inc Dest equ 0x0B ; Define constant org 0x0000 ; Reset vector goto Start org 0x0020 ; Begin program Start movlw 0x0A movwf Dest bcf Dest,3 ;This line uses 2 operands goto Start end
list 是列表选项伪指令, p 设定了PIC 微控制器的型号为16F877 。 • include 是调用外部程序, 这里调用的是PIC16F877 的定义文件。 • equ 是符号名赋值指令, 这里定义了Dest 的值为0BH 。 • org 定义了Start 程序段的起始地址为0020 H , 复位地址是0000 H 。
4.1.3 源程序的汇编 • 把汇编语言源程序翻译成目标代码的过程称为汇编。方法有人工汇编和机器汇编两种。 1. 人工汇编 对于包含有转移指令和符号在内的汇编语言源程序, 人工汇编需要进行两次才能完成: 第一次完成指令码的翻译, 第二次完成地址偏移量的“代真” 。
1) 第一次汇编 • 首先根据标号和伪指令, 确定源程序在内存的地址。然后根据指令码表依次找出每条指令的机器码, 逐一对应写出内存地址及其机器码。对于还无法确定实际值的地址偏移量则照原样保留在指令码的相应位置上。 2) 第二次汇编 • 第二次汇编的任务是根据每条指令码的始址, 按下列公式计算尚未确定的标号或地址偏移量的值: • 地址偏移量= 目标地址- 转移指令始址- 转移指令字节数 当得到的地址偏移量为负数时, 用补码表示
2. 机器汇编 • 人工汇编简单易行, 但在程序较长、较复杂时效率低, 出错率高。因此, 工程上实用程序都采用机器汇编。 • 机器汇编由系统机(如PC 机) 自动把汇编语言源程序(助记符形式) 翻译成目标代码(机器码) , 其原理就是人工汇编的模拟。完成这一翻译工作的软件就是“汇编程序” , 通常由计算机厂家提供。
4.2 汇编语言程序设计方法 • 汇编语言程序设计的特点是: (1) 软硬件结合, 且技巧性较高。 (2) 要求设计人员对微控制器的硬件结构有较为详细的了解。特别是熟练使用各类寄存器、端口、定时器/计数器、中断等。 (3) 数据的存放、寄存器和工作单元的使用等要由设计者安排。
与其他结构化程序一样, 微控制器汇编语言程序也有4 种基本结构: 顺序、分支、循环和子程序调用。 • 顺序结构是最基本、最简单的编程结构, 程序由低地址到高地址顺序一条一条地执行指令。
4.2.1 分支结构 • 分支结构使程序具有判断和选择能力。分支程序可以分为无条件分支和条件分支两类。 • 无条件分支程序中含有无条件转移指令, 比较简单。 • 条件分支程序可分为串行多分支和并行多分支, 设计的主要技巧是正确运用各种条件转移指令进行编程。
[例4-4] 使用多条A 中数值判断转移指令, 通过逐次比较实现多分支程序转移。 解: 假定分支序号值在A 中, 则可使用A 中数值判断转移指令, 其分支流程如图4-1 所示。这种多分支方法的优点是层次清晰, 程序简单易懂; 但当层次较多时分支速度较慢, 并且分支入口地址范围受到转移指令偏移量有效范围的限制。 • 三分支归一的条件转移问题, 有“先分支后赋值” 和“先赋值后分支” 两种求解办法(见图4-2) , 先分支后赋值较简单。
4.2.2 循环结构 • 循环结构就是多次重复执行某一程序段, 直到满足结束条件为止, 再向下顺序执行。循环结构并不减少程序执行时间, 但可使程序紧凑, 节省程序存储单元, 循环次数越多, 程序长度缩短越显著。
循环结构通常由四部分组成: 1. 初始化 设置循环控制计数器、数据指针、各工作寄存器及其他变量初值。 2. 循环处理 重复执行需要的操作, 是循环结构的核心, 编写的简炼对程序的执行速度影响很大。 3. 循环控制 包括修改和判断: 修改循环计数器、数据指针; 判断循环结束条件是否满足。
当循环次数已知时, 常采用计数方法控制循环; 若循环次数未知, 可采用条件判断, 例如用条件转移指令来判断、控制循环的结束。 4. 循环结束 处理、存放循环程序的执行结果以及恢复各工作单元初值。 按照处理和控制的先后关系, 循环程序有两种编制方法(见图4-3) : (1) 先处理后判断, 至少执行一次循环处理工作。 (2) 先判断后处理, 可能一次也不执行循环处理工作。
[例4-5]已知内部RAM 中, 从BLOCK 单元开始有一无符号数据块, 块长在LEN 单元, 编制程序求数据块中各数累加和的低8 位, 并存入SUM 单元。 • 解: 图4-4 给出了(a) 先处理后判断和(b) 先判断后处理两种设计方案,完成的条件是计数单元R2 中的值减为0 。
4. 2. 3 子程序调用 • 编程时, 往往有许多地方需要执行同样的操作或运算, 但程序又不很规则,不能用循环程序来实现。这时可以把这些多次使用的程序段从整个程序中独立出来, 单独编写成一个公用程序段, 并经优化(占用存储单元尽量少, 执行速度尽量快) 后存放在某一存储区中, 需要时调出来使用。这种相对独立、具有一定功能的公用程序称为子程序, 调用子程序的程序称为主程序。
子程序调用的优点是: • 避免在几个地方对同样的操作或运算重复编程; 简化程序的逻辑结构; 缩短程序长度; 便于调试。 • 各种微控制器的指令系统中, 都有专门的调用指令用于调用子程序, 返回指令用于子程序执行完毕后返回到主程序断点处继续执行。
子程序结构中, 应注意两个问题: 1. 现场的保护和恢复 • 主程序中已经用到过的某些寄存器和工作单元, 如果在子程序中也要使用,它们的内容将会改变, 返回主程序后再使用时就会发生错误。因此在子程序执行前, 需将其内容保存起来(通常是压进堆栈) , 称为保护现场。在子程序执行完后再取出这些保存起来的内容, 送回到原来的寄存器或有关单元, 称为恢复现场。
现场的保护和恢复有两种方式: (1)调用前保护、调用后恢复; (2)调用后保护、返回前恢复。 • 前者在主程序中编写保护、恢复程序, 适用于每次调用需保护内容不同的情况; 后者在子程序中编写, 适用于保护内容固定的情况。
2. 主程序和子程序之间的参数传递 • 调用子程序时, 主程序应把参与子程序运算或操作的有关参数(入口参数)存放在约定的地方, 让子程序取得参数运行; 同样, 子程序在运行结束前, 也应把运算结果(出口参数) 送到约定位置, 返回后, 主程序可以从这些地方取得需要的结果。 • 参数传递方法有多种, 可以根据减小程序长度、加快运行速度、节省工作单元等要求, 选用一种或几种合并使用。
(1) 用工作寄存器或累加器传递参数 • 将入口参数或出口参数放在工作寄存器或累加器中, 程序最简单, 运算速度也最高。缺点是工作寄存器数量有限, 不能传递太多的数据; 主程序必须先把数据送到工作寄存器; 参数个数固定, 不能由主程序任意设定。 (2) 用寄存器指针传递参数 • 用指针来指示数据存放在存储器中的位置, 可以大大节省传递数据的工作量, 并实现可变长度运算。如MCS-51 内核微控制器的参数在内部RAM 中, 可用R0 、R1 作指针; 参数在外部RAM 或程序存储器中, 可用DPTR 作指针。可变长度运算时, 可用一个寄存器来指出数据长度, 也可使用结束标记等。
(3) 用堆栈传递参数 • 主程序把参数压入堆栈, 子程序可按堆栈指针来间接访问堆栈中的参数, 把结果参数送回堆栈中; 返回主程序后, 可用出栈指令得到这些结果参数。这种方法的优点是简单, 能传递大量参数, 不必为特定的参数分配存储单元。使用堆栈可以使中断响应的现场保护大大简化。 (4) 程序段参数传递(直接参数传递) • 传递大量常数参数, 可以将常数作为程序代码的一部分, 紧跟在调用子程序指令后面。子程序直接从程序存储器中读出这些参数。
习题与思考题 • 4.1 把始址为DATA1 的数据块(数据块长度为L) 传送到始址为DATA2 的存储区,可以有几种办法? 画出相应的程序流程图。 • 4.2 编制程序, 求XX 和YY 单元内两数差的绝对值, 并把操作结果|(XX) - (YY)|保存在ZZ 单元。 • 4.3 编制程序, 比较下列两个ASCII 码字符串是否相等。第1 个字符串的首地址在ZF1 , 第2 个字符串的首地址为ZF2 , 两个字符串的长度分别在 • C1 和C2 单元。如果两个字符串相等, 则置JG 单元为00H ; 否则, 置JG 单元为FFH 。 • 4.4 规定6 个单字符命令A ~ F 的命令处理入口地址分别为PGMA 、PGMB 、PGMC 、PGMD 、PGME 和PGMF , 输入命令的ASCII 字符存放于ML 。编制程序, 功能为: 如果(ML) 为输入的合法命令字符A ~ F 之一, 则将相应命令处理的入口地址存入DPTR ; 否则,将出错处理的入口地址CDER 存入DPTR 。
4.5 编写程序, 将2 个双字节无符号数相乘的结果存入JG (高位) ~ JG + 3 (低位)单元。 • 4.6 数据块从外部RAM 的SOUCE 单元开始, 长度在LEN 单元中。编制程序, 对每一个数据进行奇偶校验, 并将满足奇校验(奇数个1) 的数据送到始址为DIST 的存储区。 • 4.7 数据块的始址在FIRST (高位) 和FIRST + l (低位) 单元, 长度在LEN 单元而且不为0 ; 要求统计该数据块中正偶数和负奇数个数, 并将它们分别存放在PAPE 单元和NAOE 单元, 画出程序流程图并编写相应程序。 • 4.8 编写程序, 将JG ~ JG + 3 单元中的内容循环右移4 位。 • 4.9 将字符串“Microcontroller”存入MCU 开始的区域, 可以有几种传送参数的方法? • 4.10 设MCU 的机器周期为1μs , 用双重循环结构写出延时50ms 的延时程序。