1.预备知识
学过微机系统,我们知道,汇编语言与硬件结构是紧密绑定的,要写汇编程序,须先深谙要运行此程序的机器的硬件结构。
关于MCS-51单片机的硬件结构,最核心就是得掌握RAM的结构,因为寄存器在RAM里,写汇编代码主要就得熟悉各种寄存器的功能,学校资料附录里讲的略有一点不清楚。
首先“寄存器在RAM里”是怎么回事?RAM是数据存储器,寄存器是寄存器,我们曾学过的8086,也自己用logisim搭建过CPU,我们知道CPU中有一个组件叫“寄存器堆”,Regs,而RAM是存储器,如今怎么将二者混为一谈?
因为MCS-51是单片机,和我们曾接触过的CPU不太一样,
1.它没有寄存器堆,
是RAM中的部分单元来充当寄存器的,所以说“寄存器在RAM里”。
RAM的具体结构如下:
工作寄存器区:
内部 RAM 的 0-1FH 为 4 组工作寄存器区,每个区有 8 个工作寄存器(R0 -R7)。在同一时刻,只能使用一组工作寄存器,这是通过程序状态字 PSW 的地 3,4 位来控制的。例如当此两位为 00 时,使用第 0 组工作寄存器,对应于 00H 到 07H 的内部 RAM 空间。也就是说,这时指令中使用 R0 与直接使用 00 单元是 等价的,不过使用工作寄存器的指令简单,且执行快。
位寻址区:
内部 RAM 的 20H-2FH 为位寻址区域,这 16 个单元的每一位都对应一个位 地址,占据位地址空间的 0-7FH。每一位都可以独立置位、清除、取反等操作,
堆栈区:
栈顶指针SP的初始值为07H,恰好和R7是重的,所以我们要使用堆栈必须设定SP的值
· 通用数据区:
初始为空的数据区,专门用于给用户存储中间结果等数据
特殊功能寄存器区:
学校的附录中并没有给出SFR的具体结构,只是简略带过,差评(手动狗头
2.程序和数据分开存储,
我们曾经接触过的CPU,存储器就是电脑的内存,内存是连续的一大块存储区域,内部分区为代码区、数据区、堆区、栈区,而如今的单片机,有完全分离的两个存储器——代码存储器RAM&数据存储器ROM。我们曾经接触的CPU,是应用于现代完整个人电脑的CPU,而如今学的是单片机,更为微型,两者架构存在差别。
当然单片机与常规PC的CPU还有诸多不同,不过本人觉得最显著就这两点,至少知道这些已经足够我们建立对单片机的初步认识和完成实验1了。
2.题目要求
一.画图
二.编程
3.答案(仅供参考)
电路图:
代码:
$NOMOD51
$INCLUDE (8051.MCU)ORG 0000H ; 程序的起始地址CLR LED2MOV P2, #3FHSTART:CALL READ_SWITCHESCJNE A, #0, LIGHT_LED1 ; 检查是否点亮LED1CLR LED1 ; 如果没有开关被激活,则熄灭LED1JMP WAITLIGHT_LED1:SETB LED1JMP WAITWAIT:CALL CALCULATE_TOTALCALL DISPLAY_AMOUNT ;展示总金额CHECK_VALUE:; 检查总金额是否大于预定值MOV B, #PREDEFINED_VALUE ; 将预定值加载到寄存器B中SUBB A, B ; 比较累加器A与B的值JC RETURN_TO_START ; 如果累加器A的值大于B的值,则跳转到GREATER_THAN_VALUEJMP GREATER_THAN_VALUEGREATER_THAN_VALUE:; 总金额大于预定值JMP FLASH_LED2FLASH_LED2:SETB LED2CALL DELAYCLR LED2CALL DELAYRETURN_TO_START:JMP START; 子程序:读取开关状态
READ_SWITCHES:; 读取开关状态并存储到寄存器MOV A, P1ANL A, #0FH ; 只保留低四位,因为开关连接在P1.0至P1.3RET; 子程序:计算总金额
CALCULATE_TOTAL:; 根据开关状态计算总金额并存储到累加器ACCMOV B, #0 ; 使用寄存器B来累计金额JNB BIT0, SKIP1 ; 如果开关1为高电平,则加上其价值MOV A, BADD A, #SWITCH1_VALUEMOV B, A
SKIP1:JNB BIT1, SKIP2 ; 如果开关2为高电平,则加上其价值MOV A, BADD A, #SWITCH2_VALUEMOV B, A
SKIP2:JNB BIT2, SKIP3 ; 如果开关3为高电平,则加上其价值MOV A, BADD A, #SWITCH3_VALUEMOV B, A
SKIP3:JNB BIT3, SKIP4 ; 如果开关4为高电平,则加上其价值MOV A, BADD A, #SWITCH4_VALUEMOV B, A
SKIP4:MOV A, B ; 将累加器B的值复制到累加器ARET; 子程序:显示总金额
DISPLAY_AMOUNT:; 根据累加器ACC中的值显示相应的数字MOV DPTR, #CODE_TABLEMOV B, AMOVC A, @A+DPTRMOV P2, AMOV A, BRET; 子程序:延时
DELAY:; 延时子程序MOV R1, #5
DELAY_LOOP1:MOV R2, #200
DELAY_LOOP2:DJNZ R2, $ ; 内部循环DJNZ R1, DELAY_LOOP1 ; 外部循环RET; 数据表:7段数码管显示代码
CODE_TABLE: DB 0C0H, 0F9H, 0A4H, 0B0H, 099H, 092H, 082H, 0F8H, 080H, 090H; 预定义值
PREDEFINED_VALUE EQU 5 ; 预定金额值; LED端口号
LED1 EQU P1.4 ; LED1连接到P1.4
LED2 EQU P1.5 ; LED2连接到P1.5; 开关对应的值
SWITCH1_VALUE EQU 1
SWITCH2_VALUE EQU 2
SWITCH3_VALUE EQU 2
SWITCH4_VALUE EQU 5; 开关状态检查位
BIT0 EQU P1.0
BIT1 EQU P1.1
BIT2 EQU P1.2
BIT3 EQU P1.3END
;====================================================================
4.思考题
像一二问这种完全可以交给GPT啦
1.MCS-51中可存取单元及存取方式
MCS-51单片机中,可供程序访问的单元主要包括:
1. 内部RAM
- 作用: 用于存放程序运行时需要频繁访问的数据,如变量、中间结果等。
- 组成: 包括通用寄存器组(R0-R7)、特殊功能寄存器(SFR)和位寻址区。
- 存取方式:
- 直接寻址: 通过寄存器或立即数直接指定地址。
- 寄存器间接寻址: 使用寄存器作为地址指针,间接访问存储单元。
- 特点: 访问速度快,但容量有限。
2. 外部RAM
- 作用: 扩展内部RAM的容量,用于存放大量数据。
- 存取方式: 通过数据指针寄存器(DPTR)间接寻址。
- 特点: 容量大,但访问速度比内部RAM慢。
3. ROM
- 作用: 存放程序代码和常量数据。
- 存取方式: 通常只读,不能修改。
- 特点: 内容非易失,掉电后数据不会丢失。
4. 特殊功能寄存器(SFR)
- 作用: 控制单片机的各种功能,如定时器、中断、I/O端口等。
- 存取方式: 直接寻址。
- 特点: 每个SFR都有特定的功能,不能随意修改。
5. I/O端口
- 作用: 与外部设备进行数据交换。
- 存取方式: 直接寻址。
- 特点: 可以读写外部设备的数据。
各单元之间的区别和联系
单元 | 主要功能 | 存取方式 | 特点 |
---|---|---|---|
内部RAM | 存放变量、中间结果 | 直接寻址、寄存器间接寻址 | 访问速度快,容量有限 |
外部RAM | 扩展存储空间 | DPTR间接寻址 | 容量大,访问速度相对较慢 |
ROM | 存放程序代码、常量 | 只读 | 内容非易失 |
SFR | 控制单片机功能 | 直接寻址 | 每个SFR都有特定功能 |
I/O端口 | 与外部设备交互 | 直接寻址 | 可读写外部设备数据 |
总结
- 内部RAM 是程序运行时最常用的存储单元,用于存放变量和中间结果。
- 外部RAM 用于扩展存储空间,存放大量数据。
- ROM 存放程序代码和常量数据,是程序的灵魂。
- SFR 控制单片机的各种功能,是硬件与软件的接口。
- I/O端口 用于与外部设备进行数据交换。
选择合适的存储单元,是编写高效、可靠的MCS-51程序的关键。在编写程序时,需要根据数据的类型、访问频率和存储空间的需求,合理地分配各个存储单元。
例如:
- 频繁访问的数据 应该放在内部RAM中,以提高访问速度。
- 程序代码和常量数据 应该放在ROM中,以保证数据的安全性。
- 大量的数据 可以放在外部RAM中。
2.MOVC指令的使用方式
MOVC A, @A+DPTR
- A: 累加器A。
- @A+DPTR: 表示对累加器A和数据指针DPTR的内容相加,得到一个程序存储器的地址,然后从该地址读取数据。
3.数码管由共阳极换成共阴极
首先我们应知道共阳极和共阴极的区别:
共阳极数码管
- 特点: 所有段的阳极(正极)连接在一起,作为公共端。要点亮某一段,则需要将该段的阴极接地(低电平)。
- 驱动方式: 通常采用低电平有效的方式驱动。
- 形象比喻: 可以想象成一个水池,水池底部有许多孔。要让某个孔喷水,就要打开对应的阀门,让水流出。
共阴极数码管
- 特点: 所有段的阴极(负极)连接在一起,作为公共端。要点亮某一段,则需要将该段的阳极接高电平。
- 驱动方式: 通常采用高电平有效的方式驱动。
- 形象比喻: 可以想象成一个灯泡,灯泡的底座是公共端。要让某个灯泡亮,就要给对应的灯丝通电。
所以,只要修改code table,把原来的全取反就可以了:
CODE_TABLE: DB 3FH, 06H, 5BH, 4FH, 66H, 6DH, 7DH, 07H, 00H, 6FH
4.控制显示数码管的亮度
直接调整数码管属性
- 找到数码管元件: 在Proteus元件库中找到你使用的七段数码管模型。
- 打开属性窗口: 右键单击数码管元件,选择“Properties”或“属性”选项。
- 查找亮度设置: 在属性窗口中,可能会找到一个名为“Brightness”或“亮度”的选项。直接调整该选项的值即可改变数码管的亮度。
这个答案可能有点草率,不过目前我不知道如何用“高端”的方法来实现。