C8051F340的官方DataSheet上这样写道“通过对应的端口数据寄存器访问端口P3-0,这些寄存器既可以按位寻址也可以按字节寻址。端口P4(仅C8051F340/1/4/5)使用的SFR只能按字节寻址。”一开始没有注意,后来发现P4口连的LED没有反应,检查代码无数遍也没发现错误,知道翻看到这里才发现问题所在。C51寻址分为两种,一种是位寻址,如定义的bit类变量以及使用sbit的IO操作;其余的就为字节寻址了,比如直接对寄存器操作。

         汇编指令详解

8080指令详解

1.8086系统下,Inter指令系统共有117条指令(看似很多,分一下类)

1.数据传送类指令(专门传送数据的)

2.算术运算类指令(加减乘除的运算的)

3.位操作类指令(或  异货 与 ….)

4.串操作类指令 (内存拷贝,内存连续地址拷贝的操作)

5.控制转移类指令(跳转,比如C语言的Goto)

6.处理机控制类指令(计算机的待机 ,重启 等等,让CPU待机睡眠的指令)

                      学习指令的注意事项

1.指令的功能,也就是这个指令可以实现什么操作.通常的话,指令就是指令功能英文单字的缩写方式,比如
mov传送指令,其实全程叫做 move

2.指令支持的寻址方式,也就是说这个指令中的操作数决定了采用何种寻址方式,寻址方式决定了指令的语法

3.指令对标志位的影响,这个指令执行之后,是否会对各个标志位又影响.

4.其他方面,比如使用指令的时候,是否效率高,CPU执行周期是否会长

a.这里提供一个汇编金手指
的软件,可以在今天的百度云盘连接中获取,可以很快的查询各种指令

b.也可以利用Inter手册查询指令的语法,比如昨天讲的怎么看inter手册

 

 

 

 

 

补充知识,理解reg mod mem imm accum segreg英文什么意思

英文名称

含义

Reg

寄存器的意思

Mod

寻址方式是哪一种

Mem

内存

Imm

立即数

Accum

累加寄存器(ax)

Segreg

段寄存器(ds ss es cs)

M8

代表内存8位,一个字节

M16

代表内存16位,两个字节

看一下,下面讲指令的时候会用到

例如mov指令

mov reg/mem,imm 表示指令是可以把一个立即数给 寄存器,或者内存

汇编语法则为  mov  ax,100  ,或者 mov  byte
ptr[2000],10h,把10给2000取内容

为什么有byte ptr(或者 word
ptr)主要为了给2000的时候,不知道读取多少给,指定方式给

 

 
 这个感光模块主要就是用i2c通信,移植的时候其实就是改一下延时,把端口的模式设置正确就行了

因此,若要操作P4上的IO不能使用如下代码:

                数据传送指令类

 

为了尽量减少工作量,我尽量多用宏定义定义,少修改代码,对于延时函数,可以用define
把它替换成stm32里的延时函数
对于变量,注意在stm32里int是四个字节,而51是两个字节,再者就是很关键的一点,51里有bit变量,可以位寻址
 而stm32却没有,而且也没有sbit,对于前者,我就直接把它变成uchar,而后者是有解决办法的,就是位带操作。

1 sbit LED1 = P4^0;2 sbit LED2 = P4^1;

一丶通用数据传送指令

mov  指令 xchg指令 xlat指令

 

位带操作就是,设定了一块特殊的地方,映射到其他地址,每个位对应一个四个字节长度的地方,而访问这个映射的地址,就间接的能访问到位了,因此想要将某一位置一或零可以直接对这个地址进行操作,定义这个地址和51不一样,51是i/o映射,即i/o口和RAM地址是各自独立的,而stm32作为arm,是内存映射,即io口和内存地址统一编码。51是用sfr定义,stm32则可以这样#define
RAM_ADDR      (*(volatile unsigned LONG   *)0x0000555F) 

而应该是这样操作:

1.MOV传送指令

指令

指令支持

指令功能

MOV

1. Mov reg/mem,imm

2. Mov reg/mem/seg,reg

3. Mov reg/seg,mem

4. Mov reg/mem,seg

 

1.立即数传送给寄存器或者主存(内存)

2.寄存器传送给 段寄存器,或者内存,或者 寄存器

3.内存中的内容送给 寄存器,或者段寄存器

4.段寄存器送给寄存器或者给主存取内容给值

例如 mov byte ptr[2000],12 是把2000指向的内存地址的值

 

mov 指令传送功能图

 图片 1

 

请注意,立即数不能直接给段寄存器,都是通过中转的

mov 注意事项

1.两个操作数的类型不一致

      例如源操作数是字节,目的操作数是字,或者是相反

例如  mov al,050AH al是八位寄存器,只能接受八位,而这里是16位了

     
对于存储器,和立即数同时都作为操作数的情况下,必须显示的指明是什么类型

例如 mov [2000h],12h  错误,应该写成 mov byte ptr(或者word
ptr)[2000h],12h

如果只是12h,则用byte ptr,当然你也可以写成word
ptr,但是修改内存的值修改后则是修改的两个字节的长度

2.两个操作数不能是存储器

      例如 mov [2000h],[2200h]
因为内存只是存储的,传送的结果要保存在寄存器当

当中,显然内存没有内置CPU,所以通过寄存器中转,所以不能直接这样    

3.小心段操作寄存器

      1.立即数不能直接给段寄存器

                 例如 mov ds,100  (比如经过寄存器的中转)

      2.不能直接改变cs段寄存器的值

           mov cs,[si]
这条指令是可以编译通过,但是运行的时候,因为你把代码段的值改了,然后CS:IP确定的吓一跳指令就会出错,比如你的这条指令下面还有个mov
ax,0 当你上面改了,那么mov ax,0
永远不会执行,而你熟悉的改了cs的值可以,如果不熟悉那么代码段就被破坏,程序就会执行崩溃.

      3.段寄存器和段寄存器不能直接数据传送

           mov ds,es

以下解释来自点击打开链接

1 #define LED1_STATE         P4&0x012 #define LED1_ON()          P4 |= 0x013 #define LED1_OFF()         P4 &=~0x014 #define LED1_CHANGE()      P4&0x01 ? (P4 &=~0x01) : (P4 |= 0x01)5 #define LED2_STATE         P4&0x026 #define LED2_ON()          P4 |= 0x027 #define LED2_OFF()         P4 &=~0x028 #define LED2_CHANGE()      P4&0x02 ? (P4 &=~0x02) : (P4 |= 0x02)

2.交换指令

      xchg reg/mem/accum ,  reg/mem/accum

其实就是寄存器的值交换

例如ax = 0

       bx = 1

xchg ax,bx 那么此时ax的值就是1,bx就是0

有人说mov 指令也可以,确实是可以,但是指令周期不一样,这个比mov指令快一个

指令周期,而且还不浪费寄存器(否则需要三个寄存器完成交换)

寄存器和存储器交换

xchg ax,[2000h] 字交换,ax正好可以放下16位

      等价于 xchg [2000h],ax 这里会有人说为什么不用 word ptr说明一下

      因为后面跟着的是16位寄存器,它默认就是 word ptr了

xchg al,[2000h] 字节交换

      等同于 xchg [2000h],al 同上为什么不用byte ptr

先把它强制转换为指针类型 (unsigned CHAR
*)0x5F,AVR的SREG是八位寄存器,所以0x5F强制转换为指向 unsigned
CHAR类型。    
volatile(可变的)这个关键字说明这变量可能会被意想不到地改变,这样编译器就不会去假设这个变量的值了。这种“意想不到地改变”,不是由程序去改变,而是由硬件去改变——意想不到。
    第二步,对指针变量解引用,就能操作指针所指向的地址的内容了    
*(volatile unsigned CHAR *)0x5F    
第三步,小心地把#define宏中的参数用括号括起来,这是一个很好的习惯,所以#define
SREG     (*(volatile unsigned CHAR *)0x5F)

3.xlat 换码指令(查表指令)

将bx指定的缓冲区中,al执行的位移处的一个字节数据取出赋值给al

后面没有操作数,默认操作数就是 dx和al

直接 一个 xlat即可.默认操作的就是dx 和al

但是转变成mov 相当于 改为 mov al,ds:[dx+al]

什么意思,就是dx数据里面有一块完整的内存(比如是ASCII码 a b c d)

al 就给定一个下标,然后调用xlat指令,就可以根据al的下标获取出来
abcd其中的一个

ASCII码重新放到al中

相当于数组寻址

C语言代码应该写成

char d[5] = {‘a’,’b’,’c’…};

char ch = 0;

ch =  *(d(数组首地址) *
ch(下标))然后取出内容来重新给ch,看一下汇编怎么写吧

当然上面只是伪代码

 图片 2

 

第一步,使用e命令给400偏移处写入 abcdefg的字符

第二步 使用 d命令查看

 图片 3

 

第三步开始汇编,先给bx赋值偏移量为400,则
DS:[DX]可以取得61(也就是a)的编码

但是怎么取出来,要根据al给定的下标,比如al给了3,那么就是 DS:[DX+AL]
也就是取出63的编码,赋值给al

 

我这里给的是0,所以下标取内容是61

图片 4

 

关于volatile

3.堆栈指令

首先看一下栈

 图片 5

 

SS代表栈的段地址,SP代表是栈顶 BP代表是栈底

堆栈的操作指令有两种,第一种是PUSH压栈,第二种是POP出栈

首先mov指令模拟

第一步,要压栈之前,sp需要-2,留出一个字的空间,这样才可以把字压入栈中

mov sp,bp 栈底和栈顶一样

sup sp,2   栈顶-2留出两个空间让数据压栈

mov bp sp,让栈底和栈顶一样的位置

mov word ptr [bp],1 栈底位置处压入1

出栈

mov bp,sp  首先栈顶栈底一样

mov ax,[bp] 栈底的数据给ax

add sp,2      栈顶指针移动

mov bp,sp   栈底位置和栈顶位置一样

但是事实x86提供了指令

push 和pop

push ax  把 ax压栈

pop ax  出栈的值放到ax中

push的指令

      push r16/m16/seg  (可以是16位寄存器,2个字节的内存,或者段寄存器)

pop 一样

 

因为 C 编译器并不知道同一个比特可以有两个地址。所以就要通过
volatile,使得编译器每次都如实地把新数值写入存储器,而不再会出于优化的
虑 在中途使用寄存器来操作数据的复本,直到最才把复本写回(这和 cache
的原理是一样的)

标志寄存器传送指令

      1.有两对4条指令(分别对应8位寄存器,和16位寄存器)

低八位传送  LAHF 和SAHF

16位传送 PUSHF和POPF

什么意思

LAHF 代表 的意思   第一个字母 L代表load(加载的意思)
AH八位寄存器AH,F是标志位

表示把 Flag标志寄存器里面的低八位标志,传送到 AH中

伪代码

      mov ah,flag(当然这是不行的,flag是16位,比如使用关键字)

SAHF   S可以理解为设置,或者保存的意思 

意思就是 AH的高八位当做标志位给Flag寄存器的标志位赋值

表示我们通过AH的值,设置flag标志寄存器

如果我们要把标志位都清零怎么办

xor ah,ah  (为什么用这个,因为这个CPU寻址的时候指令执行周期短,如果

写成 mov ah,0那么带有立即数所以比较慢,玩汇编就是要这样玩的)

SAHF 清零标志位

PUSHF 和POPF都是一样的,只不过有入栈出栈的操作,所以指令周期更长

清零

pushf   获取标志位到ax中,但是需要pop获取

pop ax

xor ax,ax  获取到了设置为0

push ax   重新压栈

popf  即可  popf设置标志寄存器,一般来说不用设置标志寄存器的高八位

下面还有一段代码

pushf          ;保存全部标志到堆栈

pop ax        ;从堆栈中取出全部标志

or ax,0100h ;设置D8=TF=1,这个地方就是对某一位设置

            ;ax其他位不变

push ax ;将ax压入堆栈

popf     ;FLAGS←AX

;将堆栈内容取到标志寄存器   

上面这个代码写调试器相当于单步代码

 类似的,如果使用一个32位处理器,要对一个32位的内存地址进行访问,可以这样定义#define
RAM_ADDR      (*(volatile unsigned LONG   *)0x0000555F)     
然后就可以用C语言对这个内存地址进行读写操作了      读:tmp = RAM_ADDR;
     写:RAM_ADDR = 0x55;

2.地址传送指令

LEA 有效地址传送指令  例如 LEA AX,[bp] 这样

      执行 LEA r16,mem  (具体查询 inter手册)

     
上面这样写只是求出内存地址,假设bp的值是2000,则给ax是2000,而不是2000里面的内容,这样写比add快,但是真正用法不是这样的,真正用法就是求内存地址而已(注意不是求出内存单元的值,如果写成
add
ax,[bp],如果是这样,那么ax的值就不是2000了,就是2000地址里面的内容了)

LDS LES指针传送指令  

LDS
改变段寄存器的,试想一下,程序一大,数据段就应该有很多,但是你不能只有一个

两个段使用的时候可以来回切换

使用 lds ax,ds:[0]

使用后会修改 ax的值和ds段寄存器的值

内存的前两个字节给 r16

内存地址+2的连个字节给ds

LES是一样的

例子

mov word ptr [3060h],0100h

mov word ptr [3062h],1450h

les di,[3060h]     ;es=1450h,di=0100h

lds si,[3060h]     ;ds=1450h,si=0100h

mem指定主存的连续4个字节作为逻辑地址(32位的地址指针),送入DS:r16或ES:r16

 

 

三丶输入输出指令

IN

OUT

这个指令很强大,可以直接通过端口操作硬件

比如键盘的端口是 64

我们可以利用IN从键盘中读取数据

OUT往键盘的缓冲区写入数据

当然怎么写还有看键盘的规范,寄存器呀,参数呀等等.

这个功能和驱动过保护有关,以前都说驱动过保护,就是就是玩着两个指令,

比如HOOK(钩子,不懂可以百度)你去HOOK应用层的软件,但是HOOK不了我,你HOOK的时候需要调用API,我连API都不用调用,直接在底层就操作数据了

从键盘缓冲区取一个指令

IN ax,64(端口号)

输出(伪代码,没有看硬盘厂家的标准)

OUT ax,64

IN AL,i8

;字节输入:AL←I/O端口(i8直接寻址)

IN AL,DX

;字节输入:AL←I/O端口(DX间接寻址)

IN AX,i8

;字输入:AX←I/O端口(i8直接寻址)

IN AX,DX

;字输入:AX←I/O端口(DX间接寻址)

将数据传送给外设(伪代码)

 

OUT i8,AL

;字节输出:I/O端口←AL(i8直接寻址)

OUT DX,AL

;字节输出:I/O端口←AL(DX间接寻址)

OUT i8,AX

;字输出:I/O端口←AX(i8直接寻址)

OUT DX,AX

;字输出:I/O端口←AX(DX间接寻址)

 

这样基本就可以像51一样操作端口

发表评论

电子邮件地址不会被公开。 必填项已用*标注