寄存器 Register

寄存器用于数据的临时存储, 其数据可以表示为

  • 用于处理的数据字节
  • 指向数据的地址

寄存器的结构

8051的寄存器几乎都是8位寄存器, 因为8位MCU处理的主要是8位数据, 如果数据大于8位, 则需要拆成多段分别处理. 一个8位的寄存器, 从D7到D0代表起第7位到第0位, D7这端为MSB(most significant bit), D0这端为LSB(least significant bit).

常用寄存器

  • A (累加器)
  • B, R0, R1, R2, R3, R4, R5, R6, R7 在函数中使用的变量, R0-R7是变量, 地址并非唯一, 其绝对地址由AR0-AR7指定.
  • DPTR(data pointer), PC(program counter) 这两个都是16位双字节寄存器
    • PC 指向下一个指令的地址, 16位宽度, 因此代码区的最大范围为0 - 0xFFFF, 64K字节
    • 8051启动时, PC值为0x0000, 从代码区0x0000开始执行第一条指令
  • SP 栈顶指针, 其值为堆栈栈顶的地址, SDCC中, 堆栈的地址是向上增长的, 这个与常见的向下增长不同
  • BP 基址指针寄存器BP(base pointer), 和堆栈指针SP联合使用, 在函数中把SP的值传递给BP, 通过BP来读取堆栈里数据或者地址.

程序状态寄存器 PSW

其中的6个位是预先定义的, 分别为

  • CY 最高位进位标志位, 当加法等运算结果超过0xFF, 产生进位时, 这个bit会被置1
  • AC D3 至 D4 进位标志位, 当加法等运算低四位产生进位时, 这个bit会被置1
  • F0
  • RS1 寄存器组选择, RS1,RS0的组合对应的选择为
  • RS0 寄存器组选择: 0,0:bank0, 0,1:bank1, 1,0:bank2, 1,1:bank3
  • OV 溢出标志位
  • P 奇偶校验位, 如果在寄存器A中的, 1的个数为偶数时这个bit为0, 1的个数为奇数时这个bit为1

内存结构

8051的基础内存为128字节, 地址为00H - 7FH, 这128字节被分成三组

  1. 00H - 1FH, 32个字节, 用于寄存器组和堆栈

    • 这32个字节被分成4组(4 banks), 每组8个寄存器R0-R7
    • 当8051加电时, 默认使用寄存器组0(bank 0)
    • 通过PWS的D4,D3选择
    • 栈顶地址存储在SP寄存器
    • SP寄存器只有8位, 因此其范围只有00H-FFH
    • 当8051加电时, SP寄存器的值为07, 因此内存地址08H就是堆栈的第一个地址, 这个地址与寄存器组1是重合的
    • 可以给堆栈指定其它的地址
    • PUSH操作时, SP会加1, 向上增长
    • POP操作时, SP减1, 向下收缩
  2. 20H - 2FH, 16个字节用于可以按位寻址内存访问
  3. 30H - 7FH, 80个字节属于通用内存(scratch pad)

寄存器操作示例

MOV 赋值操作

格式为 MOV destination, source

赋值操作有几种类型

直接数赋值

将值0x55赋值给寄存器A

MOV A,#55H
  • 直接数可以赋值的寄存器为A, B, R0-R7
  • 如果给寄存器赋值 #0 至 #F, 等价于赋值 #00H 至 #0FH
  • 如果直接数超过8位数值, 会产生错误

寄存器赋值

将寄存器A的值赋值给R0

MOV R0,A

地址赋值

将R0中存储的值作为地址, 这个地址存储的值赋值给A

MOV  A,@R0

加法操作

ADD A, source
  • 将source的值与A相加, 结果存储在A
  • source可以是寄存器或直接数, 但是结果一定存储在寄存器A
  • ADD的第一个参数, 目标寄存器必须A

例如计算 #25H + #34H

MOV A, #25H     ;load one operand;into A (A=25H)
ADD A, #34H     ;add the second;operand 34H to A

SDCC汇编基础概念

汇编语言的指令格式为

[label:] Mnemonic [operands] [;comment]

汇编语言的编译过程

  1. 文本汇编程序, file.asm
  2. 汇编编译, 产生lst文件 file.lst 和 obj文件 file.obj
  3. 连接器, 产生abs文件, file.abs
  4. Object to Hex转换, 产生hex文件, file.hex

如果使用VSCode + PlatformIO开发, 可以在项目的 .pio 目录下看到这些文件

LST文件

lst(list)文件对于开发者非常有用, 在里面会按行显示每一句汇编语句对应的机器指令, 及其在代码区的偏移位置. 可以检查语法错误已经debug分析.

伪指令

  • DB: 用于定义数据, 可以是十进制数, 二进制数, 十六进制数, ASCII等
  • ORG(origin): 用于指定起始地址
  • END: 标识代码结束
  • EQU(equate): 用于定义常量

SDCC汇编函数参数传递

第一个参数和返回值

编译器总是使用全局寄存器 DPL, DPH, B 和 ACC 传递第一个函数参数(必须是非bit型参数)和传递函数返回结果

  • 1个字节返回值存储在DPL
  • 2个字节: DPL(LSB)和DPH(MSB)
  • 3个字节(通用指针): DPH, DPL和 B
  • 4个字节: DPH, DPL, B 和 ACC

在 B 中存储通用指针的类型:

  • 0x00 – xdata/far, 外部数据存储
  • 0x40 – idata/near – , 内部数据存储
  • 0x60 – pdata, 外部数据存储
  • 0x80 – code, 代码区

bit型参数, 位参数

  • 在可重入函数中, 位参数在位可寻址空间中被传递到虚拟寄存器bits中
  • 其它的情况, 直接在位内存中存储

第二个之后的参数

第二个参数可以在堆栈上存储(reentrant 或者使用 --stack-auto ), 也可以在数据/xdata存储器中存储(取决于存储器型号)

可重入函数

对于通过函数指针调用, 并且带两个或两个以上参数的函数, 必须是可重入的, 这样编译器才能正确地传递参数.

相关寄存器的说明

除非函数被定义为 _naked 或 --callee-saves/–all-callee-saves 或者使用了 callee_saves pragma, 调用方会在调用前后对寄存器 R0-R7 的值进行保护和恢复, 所以被调用的函数可以随意读写 R0-R7.

并且如果函数未被定义为 _naked, 如果调用方和被调用函数使用了不同的寄存器组(register banks, 使用 __using 声明), 调用方会在调用前后处理寄存器组的切换.

被调用的函数使用 DPL, DPH, B 和 ACC 获取参数和存储返回结果

示例说明

非重入的函数调用

在 C 语言里, 调用函数时会将函数参数以及函数的局部变量放入堆栈, 但是由于8位MCS-51芯片内部堆栈空间有限, 无法像 windows/unix 那样使用堆栈, 所以无法使用这种方式, 而是为每个函数的局部变量和参数申请一个空间来存放.

下面的例子是一个简单的函数int test(int a, int b)用于计算 a 与 b 的和并返回. 其中

  • 第一个参数用 DPL, DPH, B, ACC 传递(从LSB -> MSB), a 是双字节, 所以存储在 DPL, DPH
  • 第二个参数用全局变量传递_test_PARM_2
;--------------------------------------------------------
; Public variables in this module
;--------------------------------------------------------.globl _test_PARM_2.globl _test;--------------------------------------------------------
; overlayable items in internal ram
;--------------------------------------------------------.area  OSEG    (OVR,DATA)          ; DATA area 0x00 ~ 0x80, 可重叠的空间
_test_PARM_2:.ds 2                             ; 预留的空间, 2字节;--------------------------------------------------------
; code
;--------------------------------------------------------.area CSEG    (CODE)
;------------------------------------------------------------
;Allocation info for local variables in function 'test'
;------------------------------------------------------------
;b                         Allocated with name '_test_PARM_2'
;a                         Allocated to registers r6 r7
;c                         Allocated to registers
;------------------------------------------------------------
;   src/st7567_stc8h3k.c:32: int test(int a, int b)
;   -----------------------------------------
;    function test
;   -----------------------------------------
_test:ar7 = 0x07                        ; ar0-ar7表示当前选中的寄存器组r0-r7的寄存器绝对地址, ; 这里将r0-r7的地址设置为00H到07Har6 = 0x06ar5 = 0x05ar4 = 0x04ar3 = 0x03ar2 = 0x02ar1 = 0x01ar0 = 0x00mov r6,dpl                        ; 将第一个参数存入 r6, r7mov  r7,dph
;   src/st7567_stc8h3k.c:34: int c = a + b;mov    a,_test_PARM_2                ; 将第二个参数的LSB存入aadd    a,r6                          ; 低8位相加mov    dpl,a                         ; 结果存入DPLmov  a,(_test_PARM_2 + 1)          ; 将第二个参数的MSB存入aaddc  a,r7                        ; 高8位相加, 带前一次运算的进位mov   dph,a                         ; 结果存入DPH
;   src/st7567_stc8h3k.c:35: return c;
;   src/st7567_stc8h3k.c:36: }ret

从main中调用时, 第一个参数存入DPL, DPH, 第二个参数存入 _test_PARM_2_test_PARM_2 + 1

 mov _test_PARM_2,r4mov  (_test_PARM_2 + 1),r5mov   dpl,r6mov   dph,r7lcall _test

可重入的函数调用

SDCC 将局部变量放到全局变量中后, 相当于成为了静态变量, 因此无法在递归函数中使用(无法重入), 并且在 interrupt function 中不能调用, 因为当中断发生在这些函数中时就会发生重入,
造成局部变量被修改, 造成不可预期的结果. 所以对于这类场景, 需要在函数上加上__reentrant关y键词. 此时编译器会将局部变量放到堆栈上. 在这种情况下, 第二个及之后的参数将被放在堆栈中,
参数从右到左依次入栈, 因此第二个参数总是最后一个入栈, 在堆栈的顶部.

下面的例子还是上面的简单函数int test(int a, int b), 但是加了__reentrant关键词.

在函数的入口, 旧的 _bp 被入栈, 之后 SP 的值被复制给 _bp, 如果在堆栈上有局部变量, 也会在 SP 上存储, 此时 _bp 指向的是堆栈上的第一个局部变量, 参数则存储在更低的地址上.
sp是栈指针, 如果不保存的话就无法返回调用它的程序,
因为下面要改变栈指针, 所以不能用入栈的方法保存, 只能保存在寄存器中
bp这个寄存器是专门在栈段操作在栈区的子程序的临时变量用的, 很方便, 所以用bp保存sp的内容

;--------------------------------------------------------
; Public variables in this module
;--------------------------------------------------------.globl _test                         ; 可以看到, 只有函数声明, 没有参数二的声明; 也没有.area    OSEG中对参数二的存储预留;--------------------------------------------------------
; code
;--------------------------------------------------------.area CSEG    (CODE)
;------------------------------------------------------------
;Allocation info for local variables in function 'test'
;------------------------------------------------------------
;b                         Allocated to stack - _bp -4       ; 参数二被放到了 _bp -4 位置, ; 如果还有参数三, 并且也是int, 会被放到 _bp -6 的位置
;a                         Allocated to registers r6 r7
;c                         Allocated to registers
;------------------------------------------------------------
;   src/st7567_stc8h3k.c:32: int test(int a, int b) __reentrant
;   -----------------------------------------
;    function test
;   -----------------------------------------
_test:ar7 = 0x07                  ; ar0-ar7表示当前选中的寄存器组r0-r7的寄存器绝对地址, 这里将r0-r7的地址设置为00H到07Har6 = 0x06ar5 = 0x05ar4 = 0x04ar3 = 0x03ar2 = 0x02ar1 = 0x01ar0 = 0x00push    _bp                   ; 将堆栈帧指针入栈, 原来栈顶是返回地址, _bp入栈后, 栈顶变成了: ; _bp, 返回地址, 参数二, 因为入栈动作, 栈顶地址增长了(SDCC中堆栈地址是往上增长的), ; SP指向了新的栈顶地址mov    _bp,sp                  ; 将此时的栈顶赋值给_bp, 注意, 这时候_bp里保存的变成了一个地址, 栈顶的地址.mov    r6,dpl                  ; 将参数一放入r6, r7mov   r7,dph
;   src/st7567_stc8h3k.c:34: int c = a + b;mov    a,_bp                   ; 将_bp值赋值给a, 此时a里面存了栈顶地址add a,#0xfc                 ; 8bit数加0xfc就等于减4, 得到最后一个参数的指针, 这里第二个参数就是最后一个参数mov  r0,a                    ; 结果赋值给r0mov    a,@r0                   ; 将r0作为地址, 取到的值赋值给aadd a,r6                    ; 与r6相加(低8位)mov r6,a                    ; 结果存回r6inc r0                      ; r0++(下一个字节的地址)mov   a,@r0                   ; 将r0作为地址, 取到的值赋值给aaddc    a,r7                  ; 与r7相加(高8位), 带前一步的进位mov  r7,a                    ; 将结果存回r7mov    dpl,r6                  ; 将返回结果存到dpl, dphmov    dph,r7
;   src/st7567_stc8h3k.c:35: return c;
;   src/st7567_stc8h3k.c:36: }pop   _bp                     ; 在返回前, 恢复堆栈指针ret

从main中调用这个函数, 在调用后恢复栈顶指针

 push    ar4       ; 参数二入栈push   ar5mov  dpl,r6      ; 参数一赋值给DPTRmov dph,r7lcall _test     ; 调用(此时会将返回地址入栈)dec   sp          ; 此时恢复到了调用前的栈顶地址, 再dec两次抵消掉参数二入栈产生的地址增长, 恢复栈顶位置dec  sp

在汇编的 reentrant 函数开头, 有一个变量_bp, 这个变量在 sdcc/lib/src/_bp.c 中声明, 是基址指针寄存器, 用来计算进入堆栈的参数和局部变量的偏移. _bp is the stack frame pointer and is used to compute the offset into the stack for parameters and local variables.

基址指针寄存器BP(base pointer)的用途比较特殊, 是和栈顶地址SP联合使用的, 例如在带参数的子函数中用BP来获取参数和访问设在堆栈里面的临时变量. 在堆栈中压入了数据或者地址, 如果想访问这些数据或者地址, 但SP指向栈顶, 不能随便乱改, 并且SP会随着堆栈操作(PUSH, CALL, INT, RETF)而变化, 这时候可以将SP赋值给BP, 通过BP来读取堆栈里数据.

参考

  • SDCC使用手册 http://sdcc.sourceforge.net/doc/sdccman.pdf
  • 关于_BP和重入 http://jyhshin3.blogspot.com/2010/

SDCC 的 MCS-51 汇编基础概念和传参方式相关推荐

  1. win32 汇编基础概念整理

    一.关于寄存器 寄存器有EAX,EBX,ECX,EDX,EDI,ESI,ESP,EBP等,似乎IP也是寄存器,但只有在CALL/RET在中会默认使用它,其它情况很少使用到,暂时可以不用理会. EAX是 ...

  2. win32汇编基础概念

    一.关于寄存器 寄存器有EAX,EBX,ECX,EDX,EDI,ESI,ESP,EBP等,似乎IP也是寄存器,但只有在CALL/RET在中会默认使用它,其它情况很少使用到,暂时可以不用理会. EAX是 ...

  3. 【Java基础】前端传一个数组或者集合后台怎么接受(案例详解)

    [辰兮要努力]:hello你好我是辰兮,很高兴你能来阅读,昵称是希望自己能不断精进,向着优秀程序员前行! 博客来源于项目以及编程中遇到的问题总结,偶尔会有读书分享,我会陆续更新Java前端.后台.数据 ...

  4. 【操作系统】Linux Kernel中memcpy的汇编实现 详解(包括必要基础概念等)

    memcpy汇编实现 由于这篇博客是从我的各种笔记上搬的 我不知道自己为什么看了mmcpy汇编实现,也许单纯就是因为好玩 在arch/x86/boot/Copy.S中,由Linus在1992年写的一段 ...

  5. 【Linux开发】linux设备驱动归纳总结(一):内核的相关基础概念

    linux设备驱动归纳总结(一):内核的相关基础概念 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ...

  6. C++ 基础概念、语法和易错点整理

    目录 基础知识 构造函数与析构函数 虚函数 继承 单例模式 重载.隐藏和重写(覆盖) vector 扩容机制应注意的问题 STL 迭代器 前言 快秋招了,专门用一篇博客整理一下 C++ 的一些基础概念 ...

  7. linux驱动内核哪个文件夹,linux设备驱动归纳总结(一):内核的相关基础概念...

    linux设备驱动归纳总结(一):内核的相关基础概念 1. 内核与 linux 设备驱动的作用与关系 内核:用于管理软硬件资源,并提供运行环境.如分配 4G 虚拟空间等. linux 设备驱动:是连接 ...

  8. 【Linux开发】linux设备驱动归纳总结(二):模块的相关基础概念

    linux设备驱动归纳总结(二):模块的相关基础概念 系统平台:Ubuntu 10.04 开发平台:S3C2440开发板 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ...

  9. 反汇编---汇编基础学习

    一.一些简单的说明 本文描述的是x86汇编,采用ATT汇编代码格式,讨论的是一个运行Linux的x86系统.使用gcc编译器,gdb调试器,objdump等相关工具观察反汇编代码. 关于寻址模式.堆栈 ...

最新文章

  1. 微软,您的.net为中国程序员带来了什么?
  2. 哀悼!华人著名计算机科学家刘炯朗逝世,图灵奖得主姚期智为其得意门生
  3. 修改Idea默认的全局设置,如Maven等
  4. boost::polygon模块实现多边形集数据相关的测试程序
  5. 有1~5000一组乱序数列,请使用伪代码对该数进行排列
  6. MySQL 快速定位性能问题
  7. kafkaspot在ack机制下如何保证内存不溢
  8. media jquery 适配ios不同手机
  9. android布局属性详解(转)
  10. java 在一个类中定义类_Java 中程序代码必须在一个类中定义,类使用( )关键字来定义。_学小易找答案...
  11. 笨办法学 Python · 续 练习 0:起步
  12. Redis单机版半自动安装
  13. NoSQL之【MongoDB】学习(二):DML和查询操作说明
  14. 计算机组成原理-白中英版
  15. NTU RGB-D数据集申请
  16. busybox制作的rootfs,启动脚本修改定制
  17. 中国房价下跌序幕刚刚拉开
  18. Python项目实战:下载腾讯漫画的脚本及源码【女朋友在也不担心我花钱购买漫画了】
  19. 计算机视觉专业排名,2020美国人工智能专业排名TOP10!
  20. 爵士、古典、摇滚、流行音乐

热门文章

  1. 美国互联网影视的盈利模式 ——HuLu模式
  2. Python-pygame 使用subsurface()遍历图片达到动画效果
  3. 云计算的认识和看法_如何通俗的理解云计算和大数据?
  4. 使用Prettier配合TsLint/Eslint 统一前端代码格式化规则,保证代码质量
  5. spring-boot-2.0.3不一样系列之源码篇 - run方法(三)之createApplicationContext,绝对有值得你看的地方
  6. 分布式接口幂等性、分布式限流总结整理
  7. 用蓝牙耳机播放键激活PC小娜
  8. 扫雷小游戏 2.0版本
  9. python爬取中央气象台每日预报结果
  10. MyBatis研习录(07)——MyBatis参数传递