AVR汇编(四):数据传送指令
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 进行单步执行。
为了观察 PINB 、 DDRB 、 PORTB 寄存器的值,可以借助 x/<n/f/u> <addr> 命令,其中 n 表示要查看的值的个数; f 指定显示格式,如果要十六进制显示,这里就要指定 x ; u 表示值的单位,如果单位是字节,这里就要指定 b 。这条命令的具体使用方法可以通过 help x 命令查看。
这里我们查看从I/O地址0x03开始的3个字节:
x/3xb 0x03 # 注意,这里的地址是错误的!
结果如下:

发现读取的值并不符合我们的预期,这是因为上面命令中的地址设置错了,有两个因素:
PINB、DDRB、PORTB在I/O空间的地址是0x03开始,而在数据空间中的地址需要加上0x20;- AVR的程序空间和数据空间是分别独立编址的,因此地址存在重叠情况。通过
avr-readelf -S hello.elf查看,可以发现.data段的地址是从0x800100开始的,而实际的SRAM地址是从0x0100开始的,因此可以知道elf文件中数据空间的地址还需要加上0x800000,如果不加,则代表的是.text段(Flash)的地址。

通过上面的分析,将命令中的地址改为0x800023即可正确查看 PINB 、 DDRB 、 PORTB 中的内容:
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
参考资料
AVR汇编(四):数据传送指令的更多相关文章
- 汇编语言--微机CPU的指令系统(五)(数据传送指令)
五.微机CPU的指令系统 1.汇编语言指令格式 汇编语言的指令格式如下: 指令助忆符 [操作数1 [, 操作数2 [, 操作数3]]] [;注释] 指令助忆符体现该指令的功能,它对应一条二进制编码的机 ...
- 学 Win32 汇编[21] - 传送指令: MOV、LEA、XCHG、XLATB、XLAT、MOVZX、MOVSX
汇编指令的一般性要求: 1.两个操作数的尺寸必须一致; 2.操作数不能同为内存. MOV(Move): 最常用的数据传送指令 ;该指令不影响 EFlags ;指令格式: (其中的 r.m.i 分别表示 ...
- Intel汇编语言程序设计学习-第四章 数据传送、寻址和算术运算-下
4.3 和数据相关的操作符和伪指令 操作符和伪指令并非机器可执行的指令,相反,它们是由汇编器进行解释的.开发者可以使用一系列的MASM操作符或伪指令获取数据的地址以及大小等特征信息: OFFSET操 ...
- 【汇编】字符串处理指令 stosb、lodsb、movsw、scasb、rep
一.字符串处理指令 (1) lodsb.lodsw:把DS:SI指向的存储单元中的数据装入AL或AX,然后根据DF标志增减SI (2) stosb.stosw:把AL或AX中的数据装入ES:DI指向的 ...
- python接口自动化(十)--post请求四种传送正文方式(详解)
简介 post请求我在python接口自动化(八)--发送post请求的接口(详解)已经讲过一部分了,主要是发送一些较长的数据,还有就是数据比较安全等.我们要知道post请求四种传送正文方式首先需要先 ...
- 汇编 MOVSX与MOVZX 指令
知识点: MOVSX符号扩展传送 MOVZX零扩展传送 一.MOVSX与MOVZX格式 MOVSX 操作数A ,操作数B MOVZX 操作数A ,操作数B 相同点:操作数B 空间必须小于 操作 ...
- python3+requests:post请求四种传送正文方式(详解)
前言:post请求我在python接口自动化2-发送post请求详解(二)已经讲过一部分了,主要是发送一些较长的数据,还有就是数据比较安全等,可以参考Get,Post请求方式经典详解进行学习一下. 我 ...
- 8237dma的四种传送方式简介
8237A有四种工作方式:单字节传送.数据块传送.请求传送和多片级联. (1)单字节传送(single mode) 单字节传送方式是每次DMA传送时,仅传送一个字节.传送一个字节之后,当前字节计数器减 ...
- post请求四种传送正文的方式
一.简介 HTTP协议规定post提交的数据必须放在消息主体(entity-body)中,但协议没有规定数据必须使用什么编码方式.HTTP协议是以ASCII码传输,建立再TCP/IP协议之上的应用层规 ...
- python3+requests:post请求四种传送正文方式
https://www.cnblogs.com/insane-Mr-Li/p/9145152.html 前言:post请求我在python接口自动化2-发送post请求详解(二)已经讲过一部分了,主要 ...
随机推荐
- 2020-12-12:现场写代码,把CPU打满,java和go都行,并解释为什么。
福哥答案2020-12-12: 现在的电脑一般是多核的,单个for循环cpu是不会打满的. 我的电脑是四核八线程的,不管是java还是go,6个for循环就能把cpu打满,4个和5个cpu打不满. 为 ...
- 2020-11-04:java里,总体说一下集合框架。
福哥答案2020-11-04: 福哥口诀法:收马李色坤(Collection.Map.List.Set.Queue).李矢数链写(List:Vector矢量.ArrayList数组.LinkedLis ...
- GPT大语言模型Vicuna本地化部署实践(效果秒杀Alpaca)
背景 上一篇文章<GPT大语言模型Alpaca-lora本地化部署实践>介绍了斯坦福大学的Alpaca-lora模型的本地化部署,并验证了实际的推理效果. 总体感觉其实并不是特别理想,原始 ...
- Spring源码:Bean生命周期(终章)
前言 本系列前面讲解了Spring的bean定义.bean实例化.bean初始化等生命周期.这些步骤使我们能够了解bean从创建到准备好使用所经历的过程.但是,除了这些步骤,bean的销毁也是非常重要 ...
- 源码解析:数据批量导入bukl_crete()原理
在Django中需要向数据库中插入多条数据(list).使用如下方法,每次save()的时候都会访问一次数据库.导致性能问题: for i in resultlist: p = Account(nam ...
- 驱动开发:内核解析PE结构节表
在笔者上一篇文章<驱动开发:内核解析PE结构导出表>介绍了如何解析内存导出表结构,本章将继续延申实现解析PE结构的PE头,PE节表等数据,总体而言内核中解析PE结构与应用层没什么不同,在上 ...
- R 绘制 GWAS 研究的 Manhattan 图
曼哈顿图本质上是一个散点图,用于显示大量非零大范围波动数值,最早应用于全基因组关联分析(GWAS)研究展示高度相关位点.它得名源于样式与曼哈顿天际线相似(如下图). 近几年,在宏基因组领域,尤其是差异 ...
- k8s实战案例之部署Zookeeper集群
1.Zookeeper简介 zookeeper是一个开源的分布式协调服务,由知名互联网公司Yahoo创建,它是Chubby的开源实现:换句话讲,zookeeper是一个典型的分布式数据一致性解决方案, ...
- 案例实践 | 某能源企业API安全实践
随着智能电网.全球能源互联网."互联网+电力".新电改的全面实施,分布式能源.新能源.电力交易.智能用电等新型业务不断涌现,运营模式.用户群体都将发生较大变化,电力市场由相对专业向 ...
- vivo 帐号服务稳定性建设之路-平台产品系列06
作者:vivo 互联网平台产品研发团队- Shi Jianhua.Sun Song 帐号是一个核心的基础服务,对于基础服务而言稳定性就是生命线.在这篇文章中,将与大家分享我们在帐号稳定性建设方面的经验 ...