AVR汇编(四):数据传送指令

AVR指令主要分为五类:算术和逻辑指令、分支指令、位操作指令、数据传送指令、MCU控制指令,今天我们先来认识其中最常用的数据传送指令。

汇编程序的编写、编译和调试

学习任何技术都离不开实践,汇编语言也是如此。在正式学习指令前,我们先来体验一下汇编程序从编写到编译,再到调试的整个过程。

伪指令

根据百度百科:伪指令(Pseudo Instruction)是用于对汇编过程进行控制的指令,该类指令并不是可执行指令,没有机器代码,只用于汇编过程中为汇编程序提供汇编信息。

下面是几个常用的伪指令:

伪指令 说明 举例
.section 定义一个段 .section .text
.section .data
.global 定义一个全局符号 .global _start
.byte 定义一个字节数据 .byte 0x01
.word 定义一个字数据 .word 0x3412
.ascii 定义一个字符串数据 .ascii "hello"
.align 设置对齐方式 .align 4
.equ 定义一个符号常量,类似于C宏定义 .equ INT8_MAX, 0xFF

第一个汇编程序

.equ PINB, 0x03
.equ DDRB, 0x04
.equ PORTB, 0x05 .section .text ; 定义text段
.global _start ; 定义一个全局符号_start _start:
LDI R16, 0x20 ; R16 = 0x20
OUT DDRB, R16 ; 设置PB5为输出
OUT PORTB, R16 ; PB5初始输出高电平
loop:
OUT PINB, R16 ; 翻转PB5电平
RJMP loop ; 跳转到loop处继续执行

上述程序实现的功能很简单,就是不断翻转PB5的电平状态。

使用下面的命令进行编译,生成elf文件:

avr-gcc -mmcu=atmega328p -x assembler-with-cpp -g -Og -Wall -c -o hello.o hello.s
avr-gcc -mmcu=atmega328p -nostartfiles -o hello.elf hello.o

其中, -x assembler-with-cpp 表示编译汇编程序, -nostartfiles 表示不添加默认启动文件,启动文件的作用是初始化MCU,创建C语言运行环境,由于这里编写的是汇编程序,所以不需要它,否则编译时会提示找不到 main 函数。

为了以后每次重新编译的时候不用都输一遍命令,可以写一个 Makefile 文件:

.PHONY: all clean
TARGET := hello all: $(TARGET).elf %.o: %.s
avr-gcc -mmcu=atmega328p -x assembler-with-cpp -g -Og -Wall -c -o $@ $< $(TARGET).elf: $(TARGET).o
avr-gcc -mmcu=atmega328p -nostartfiles -o $@ $< clean:
rm -f *.o $(TARGET).elf

调试程序

使用 simavr 对上面生成的elf文件进行仿真:

simavr -f 16000000 -m atmega328p --gdb hello.elf

为了方便,可以在 Makefile 中添加一个 run 伪目标,将上面的命令添加进去:

.PHONY: all clean run

...

run: $(TARGET).elf
simavr -f 16000000 -m atmega328p --gdb $<

之后需要仿真时,直接执行 make run 即可。

使用 avr-gdb 对程序进行调试, simavr 的GDB端口是 1234

avr-gdb -ex "target remote localhost:1234" -ex "layout split" -q --tui hello.elf

在GDB窗口中,可以输入 s 进行单步执行。

为了观察 PINBDDRBPORTB 寄存器的值,可以借助 x/<n/f/u> <addr> 命令,其中 n 表示要查看的值的个数; f 指定显示格式,如果要十六进制显示,这里就要指定 xu 表示值的单位,如果单位是字节,这里就要指定 b 。这条命令的具体使用方法可以通过 help x 命令查看。

这里我们查看从I/O地址0x03开始的3个字节:

x/3xb 0x03      # 注意,这里的地址是错误的!

结果如下:

发现读取的值并不符合我们的预期,这是因为上面命令中的地址设置错了,有两个因素:

  1. PINBDDRBPORTB 在I/O空间的地址是0x03开始,而在数据空间中的地址需要加上0x20;
  2. AVR的程序空间和数据空间是分别独立编址的,因此地址存在重叠情况。通过 avr-readelf -S hello.elf 查看,可以发现 .data 段的地址是从0x800100开始的,而实际的SRAM地址是从 0x0100 开始的,因此可以知道elf文件中数据空间的地址还需要加上0x800000,如果不加,则代表的是 .text 段(Flash)的地址。

通过上面的分析,将命令中的地址改为0x800023即可正确查看 PINBDDRBPORTB 中的内容:

x/3xb 0x800023

结果如下:

这样显示的结果与我们的程序逻辑是一致的。

数据传送指令

由于AVR具有多种寻址方式,因此数据传送指令也对应有多种。

空间 指令
寄存器堆 MOV
数据空间 LD / ST
程序空间 LPM / SPM
I/O空间 IN / OUT
栈空间 PUSH / POP

一般而言,AVR指令如果有两个操作数,则第一个是目的操作数,第二个是源操作数。

MOV

MOV 指令用于寄存器之间的数据传送(一个字节),后缀如果加 W 表示传送一个字的数据。

例如:

LDI  R16, 0x10    ; R16 = 0x10
MOV R0, R16 ; R0 = 0x10 LDI R16, 0x20 ; R16 = 0x20
LDI R17, 0x30 ; R17 = 0x30
MOVW R0, R16 ; R0 = 0x20, R1 = 0x30

LD

LD 指令用于将数据从数据空间加载到寄存器中,后缀加 I 表示加载立即数,加 D 表示偏移量寻址,加 S 表示直接寻址。

X / Y / Z 寄存器可以用于间接寻址,如果前缀加 - ,表示执行操作前寄存器的值自减一,如果后缀加 + ,表示执行操作后寄存器的值自加一。

Y / Z 寄存器可以用于偏移量寻址(注意不包括 X 寄存器),后面加 +q 表示偏移量为 q

例如:

LDI R16, 0xAA    ; R16 = 0xAA
LDI XL, 0x00
LDI XH, 0x01 ; X = 0x0100
ST X, R16 ; (0x0100) = 0xAA
LD R0, X+ ; R0 = 0xAA, X = 0x0101 LDI ZL, 0xF1
LDI ZH, 0x00 ; Z = 0x00F1
LDD R1, Z+0xF ; R1 = 0xAA LDS R2, 0x0100 ; R2 = 0xAA

ST

ST 指令用于将数据从寄存器写入到数据空间中,后缀加 D / S 的意义同 LD ,注意 ST 不支持立即寻址,即没有 STI 这样的指令!

例如:

LDI R16, 0x55    ; R16 = 0x55
LDI XL, 0x80
LDI XH, 0x01 ; X = 0x0180
ST X, R16 ; (0x0180) = 0x55
LD R0, X ; R0 = 0x55 LDI R16, 0xAA ; R16 = 0xAA
LDI ZL, 0x50
LDI ZH, 0x01 ; Z = 0x0150
STD Z+0x30, R16 ; (0x0180) = 0xAA
LD R1, X ; R1 = 0xAA LDI R16, 0xA5 ; R16 = 0xA5
STS 0x0180, R16 ; (0x0180) = 0xA5
LD R2, X ; R2 = 0xA5

LPM / SPM

LPM / SPM 指令用于将数据从程序空间加载到寄存器/从寄存器写入到程序空间。

例如:

LDI ZL, lo8(const)
LDI ZH, hi8(const) ; Z = const
LPM R0, Z+ ; R0 = 0xA5, Z = CONST + 1
LPM R1, Z+ ; R1 = 0x5A, Z = CONST + 2
LPM R2, Z+ ; R2 = 0x55, Z = CONST + 3
LPM R3, Z ; R3 = 0xAA const:
.byte 0xA5
.byte 0x5A
.word 0xAA55

SPM 指令的用法较为特殊,后面有机会再来介绍。

IN / OUT

IN / OUT 用于从I/O空间读入数据到寄存器/向I/O空间写入寄存器中的数据,注意 P 为I/O空间的地址,此命令不能访问扩展I/O空间。

例如:

OUT DDRB, 0x00    ; DDRB = 0xAA
IN R0, DDRB ; R0 = 0xAA

PUSH / POP

PUSH / POP 用于将数据压入/弹出栈,使用时需要注意SP的初始值要设置正确(AVR是空减栈),并要避免出现栈溢出的情况。

例如:

LDI  R16, 0xA5    ; R16 = 0xA5
PUSH R16 ; (SP) = 0xA5, SP -= 1
POP R0 ; R0 = 0xA5, SP += 1

参考资料

  1. ATmega328P Datasheet
  2. AVR Instruction Set Manual
  3. 百度百科 - 伪指令

AVR汇编(四):数据传送指令的更多相关文章

  1. 汇编语言--微机CPU的指令系统(五)(数据传送指令)

    五.微机CPU的指令系统 1.汇编语言指令格式 汇编语言的指令格式如下: 指令助忆符 [操作数1 [, 操作数2 [, 操作数3]]] [;注释] 指令助忆符体现该指令的功能,它对应一条二进制编码的机 ...

  2. 学 Win32 汇编[21] - 传送指令: MOV、LEA、XCHG、XLATB、XLAT、MOVZX、MOVSX

    汇编指令的一般性要求: 1.两个操作数的尺寸必须一致; 2.操作数不能同为内存. MOV(Move): 最常用的数据传送指令 ;该指令不影响 EFlags ;指令格式: (其中的 r.m.i 分别表示 ...

  3. Intel汇编语言程序设计学习-第四章 数据传送、寻址和算术运算-下

    4.3  和数据相关的操作符和伪指令 操作符和伪指令并非机器可执行的指令,相反,它们是由汇编器进行解释的.开发者可以使用一系列的MASM操作符或伪指令获取数据的地址以及大小等特征信息: OFFSET操 ...

  4. 【汇编】字符串处理指令 stosb、lodsb、movsw、scasb、rep

    一.字符串处理指令 (1) lodsb.lodsw:把DS:SI指向的存储单元中的数据装入AL或AX,然后根据DF标志增减SI (2) stosb.stosw:把AL或AX中的数据装入ES:DI指向的 ...

  5. python接口自动化(十)--post请求四种传送正文方式(详解)

    简介 post请求我在python接口自动化(八)--发送post请求的接口(详解)已经讲过一部分了,主要是发送一些较长的数据,还有就是数据比较安全等.我们要知道post请求四种传送正文方式首先需要先 ...

  6. 汇编 MOVSX与MOVZX 指令

    知识点:  MOVSX符号扩展传送  MOVZX零扩展传送 一.MOVSX与MOVZX格式 MOVSX 操作数A ,操作数B MOVZX 操作数A ,操作数B 相同点:操作数B 空间必须小于 操作 ...

  7. python3+requests:post请求四种传送正文方式(详解)

    前言:post请求我在python接口自动化2-发送post请求详解(二)已经讲过一部分了,主要是发送一些较长的数据,还有就是数据比较安全等,可以参考Get,Post请求方式经典详解进行学习一下. 我 ...

  8. 8237dma的四种传送方式简介

    8237A有四种工作方式:单字节传送.数据块传送.请求传送和多片级联. (1)单字节传送(single mode) 单字节传送方式是每次DMA传送时,仅传送一个字节.传送一个字节之后,当前字节计数器减 ...

  9. post请求四种传送正文的方式

    一.简介 HTTP协议规定post提交的数据必须放在消息主体(entity-body)中,但协议没有规定数据必须使用什么编码方式.HTTP协议是以ASCII码传输,建立再TCP/IP协议之上的应用层规 ...

  10. python3+requests:post请求四种传送正文方式

    https://www.cnblogs.com/insane-Mr-Li/p/9145152.html 前言:post请求我在python接口自动化2-发送post请求详解(二)已经讲过一部分了,主要 ...

随机推荐

  1. 2022-10-11:一个整数区间 [a, b] ( a < b ) 代表着从 a 到 b 的所有连续整数,包括 a 和 b。 给你一组整数区间intervals,请找到一个最小的集合 S, 使得

    2022-10-11:一个整数区间 [a, b] ( a < b ) 代表着从 a 到 b 的所有连续整数,包括 a 和 b. 给你一组整数区间intervals,请找到一个最小的集合 S, 使 ...

  2. 2021-10-08:填充每个节点的下一个右侧节点指针。给定一个 完美二叉树 ,其所有叶子节点都在同一层,每个父节点都有两个子节点。填充它的每个 next 指针,让这个指针指向其下一个右侧节点。如果找

    2021-10-08:填充每个节点的下一个右侧节点指针.给定一个 完美二叉树 ,其所有叶子节点都在同一层,每个父节点都有两个子节点.填充它的每个 next 指针,让这个指针指向其下一个右侧节点.如果找 ...

  3. Java基础--数据结构

    数据结构 Java工具包提供了强大的数据结构.在Java中的数据结构主要包括以下几种接口和类: 枚举(Enumeration).位集合(BitSet).向量(Vector).栈(Stack).字典(D ...

  4. Nacos必知必会:这些知识点你一定要掌握!

    前言 Nacos 是一个开源的服务发现.配置管理和服务治理平台,是阿里巴巴开源的一款产品. Nacos 可以帮助开发者更好地管理微服务架构中的服务注册.配置和发现等问题,提高系统的可靠性和可维护性. ...

  5. AcWing 423. 采药

    辰辰是个天资聪颖的孩子,他的梦想是成为世界上最伟大的医师. 为此,他想拜附近最有威望的医师为师. 医师为了判断他的资质,给他出了一个难题. 医师把他带到一个到处都是草药的山洞里对他说:"孩子 ...

  6. find提权

    更新中.............. find 常用参数 语法:find [path-] [expression] path为查找路径,.为当前路径,/为根目录 expression即为参数 -name ...

  7. 带你彻底掌握Bean的生命周期

    摘要:我们将深入研究Spring Framework的核心部分--Spring Bean的生命周期. 本文分享自华为云社区<Spring高手之路5--彻底掌握Bean的生命周期>,作者: ...

  8. js如何操作video标签

    一.简介 在做web ui自动化时,遇到操作视频的时候有时比较让人头疼,定位时会发现只有一个<video>标签,用selenium来实现的话比较麻烦,使用js后我们只需定位到video标签 ...

  9. Mybatis使用级联映射时 , 查询的结果为null

    错误原因 在学习多对一映射处理中的级联方式处理映射关系时 , 发现自己查询的结果有一个为为null 于是就开始对代码进行排查 , debug ,最终发现错误 , 原来是自己映射中的 property ...

  10. 一个跨平台的`ChatGPT`悬浮窗工具

    一个跨平台的ChatGPT悬浮窗工具 使用avalonia实现的ChatGPT的工具,设计成悬浮窗,并且支持插件. 如何实现悬浮窗? 在使用avalonia实现悬浮窗也是非常的简单的. 实现我们需要将 ...