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-12-31:以下go语言代码输出什么?A:1 1;B:-1 1;C:-1 -1;D:编译错误。 package main import “fmt“ func main() { a

    2022-12-31:以下go语言代码输出什么?A:1 1:B:-1 1:C:-1 -1:D:编译错误. package main import "fmt" func main() ...

  2. 2021-01-10:linux中,我要看某一个进程的并发,通过什么命令去查?

    福哥答案2021-01-10:[答案来自此链接:](https://blog.csdn.net/sinat_31275315/article/details/108239492)方法一:PS在ps命令 ...

  3. C# 实现 Linux 视频会议(源码,支持信创环境,银河麒麟,统信UOS)

    信创是现阶段国家发展的重要战略之一,面对这一趋势,所有的软件应用只有支持信创国产化的基础软硬件设施,在未来才不会被淘汰.那么,如何可以使用C#来实现支持信创环境的视频会议系统吗?答案是肯定的. 本文讲 ...

  4. Node.js出现‘Cannot find module init’ 解决方法

    1. 首先查看当前根目录是否有node_module文件夹,如果有,请删除 2. 输入 npm clean cache 3. 再次输入 node init -y 大功告成

  5. 谷歌语法Github及利用方式

    0x01简介 GoogleHack(谷歌语法)是指使用Google等搜索引擎对某些特定的网络主机漏洞(通常是服务器上的脚本漏洞)进行搜索,以达到快速找到漏洞主机或特定主机的漏洞的目的.比如使用搜索包含 ...

  6. 基于 Linux 集群环境上 GPFS 的问题诊断

    作者:谷珊,帅炜,陈志阳来源:IBM Developer GPFS 的概述 GPFS 是 IBM 公司提供的一个共享文件系统,它允许所有的集群节点可以并行访问整个文件系统.GPFS 允许客户共享文件, ...

  7. 有懂的没,json对象中 嵌入 json字符串 它规范吗?

    json字符串 和 json对象 1.JSONObject中的String json串中data对应的值是String,String字符串中双引号需要使用反斜杠\进行转义, 痛恨这种, 解析时要进行二 ...

  8. 「学习笔记」Garsia-Wachs 算法

    前言 本文的资料和图片均来自 \(\texttt{OI-Wiki}\). 引入 题目描述 在一个操场上摆放着一排 \(N\) 堆石子.现要将石子有次序地合并成一堆.规定每次只能选相邻的 \(2\) 堆 ...

  9. Greenplum 上手

    最近因为一个项目需要,我们准备考虑使用 Greenplum 应用在我们的大数据平台中的数据治理项目中,期待着可以在我们的项目过程中有着更大的价值发现 Greenplum 介绍 Greenplum是一种 ...

  10. 通用密钥,无需密码,在无密码元年实现Passkeys通用密钥登录(基于Django4.2/Python3.10)

    毋庸讳言,密码是极其伟大的发明,但拜病毒和黑客所赐,一旦密码泄露,我们就得绞尽脑汁再想另外一个密码,但记忆力并不是一个靠谱的东西,一旦遗忘密码,也会造成严重的后果,2023年业界巨头Google已经率 ...