实验目的
(1)实现中断程序。
(2)不要调用trap来实现字符输入输出。
实验内容
实验任务包括实现以下三部分程序:
A. 用户程序
用户程序将会连续地输出纵横交替的ICS,通过交替,输出两个不同行,如下:
ICS ICS ICS ICS ICS ICS
ICS ICS ICS ICS ICS
ICS ICS ICS ICS ICS ICS
ICS ICS ICS ICS ICS
ICS ICS ICS ICS ICS ICS
ICS ICS ICS ICS ICS
ICS ICS ICS ICS ICS ICS
ICS ICS ICS ICS ICS
确保输出不至于太快,以至于肉眼不能察觉。因而,需要延时操作,可以考虑如下实现:
即用户程序包含一小段代码用于每行间的计数,间隔为从2500开始倒计时,计时结束时,再进行输出
B. 键盘中断服务程序
键盘中断服务程序将会简单地在屏幕上写上十次,用户随机输入的字符并以Enter(x0A)结束。
中断服务程序中要求不使用TRAP指令。
C. 操作系统支持的代码
不幸的是,我们还没在LC-3上安装windows或Linux,所以我们必须要求你在你的用户程序代码前先做到以下三个步骤:
1.正常情况下,操作系统将会先安装一些栈空间,所以当中断发生的时候 PC和PSR可以被放进栈中(正如你知道的,当程序执行RTI,PC和PSR都会被弹出栈,处理器返回到执行被中断的程序)由于没有操作系统,请先把R6初始化为x4000,表示一个空的栈。
2.正常情况下,操作系统会建立中断向量表,它包含对应中断服务程序的起始地址,你必须为键盘中断先建立一个中断向量表。中断向量表的开始地址是x0100,键盘中断的中断向量是x80。你必须在中断向量表提供一个入口供本实验使用。
3.操作系统应该设置KBSR的IE(Interrupt Enable)位。
实验步骤与过程
- 用户程序
初始化部分:
首先,加载堆栈指针的初始值到寄存器 R6,然后设置键盘中断向量表条目,并启用键盘中断。确保程序能够正确处理键盘输入并管理堆栈。
主程序部分:
通过 PUTS 指令打印两个字符串 TEXT1 和 TEXT2,每次打印后调用 WAIT 子程序进行延时。这个循环无限重复,直到程序被手动终止。
延时子程序:
通过一个简单的循环来减少寄存器 R1 的值,直到 R1 的值变为零。利用这种延时机制确保两个字符串的打印之间有足够的时间间隔。 - 中断程序
读取键盘输入:
从键盘读取输入字符,并将其存储到内存中的字符串位置。如果检测到回车键(ENTER),则停止读取输入
结束输入并初始化打印:
输入结束后,程序在字符串末尾添加一个回车符,并将计数器 R3 初始化为 10,准备开始打印循环。
打印字符串:
循环打印,从内存中读取字符,并检查显示器状态是否准备好显示字符。如果字符是回车符,则停止打印;否则,将字符输出到显示器。 - 运行结果
持续输出两行不同的"ICS":
中断:
实验结论或体会
- 这次实验加深了我对KBSR,DSR,DDR的理解,同时也使我更加清楚栈的工作原理,还让我学习到了如何利用计数循环的DELAY子程序实现延时的效果。
- 总体来说,本次实验加深了我对LC-3中断服务程序的理解和掌握程度,明白了真正动手实践解决问题才能够深入理解和掌握知识点的道理,使我收获颇丰。
实验代码
用户程序
.ORIG x3000
; 初始化堆栈指针
LD R6, STACK_PTR
; 设置键盘中断向量表条目
LD R1, INT_ENTRY
LD R2, START_ADDR
STR R2, R1, #0
; 启用键盘中断
LD R3, INT_ENABLE
STI R3, KBSR_ADDR
; 用户程序开始,打印 ICS
MAIN_LOOP:
LEA R0, TEXT1
PUTS
JSR WAIT
LEA R0, TEXT2
PUTS
JSR WAIT
BR MAIN_LOOP
HALT
; 延时子程序
WAIT:
ST R1, R1_SAVE
LD R1, DELAY_COUNT
DELAY_LOOP:
ADD R1, R1, #-1
BRp DELAY_LOOP
LD R1, R1_SAVE
RET
; 数据和中断向量表条目
INT_ENTRY .FILL x0180
START_ADDR .FILL x2000
STACK_PTR .FILL x3000
INT_ENABLE .FILL x4000
KBSR_ADDR .FILL xFE00
; 要打印的字符串
TEXT1 .STRINGZ "ICS ICS ICS ICS ICS ICS \n"
TEXT2 .STRINGZ " ICS ICS ICS ICS ICS ICS \n"
DELAY_COUNT .FILL #2500
R1_SAVE .BLKW 1
.END
中断程序
.ORIG x2000
; 将寄存器 R0-R4 压入堆栈
ADD R6, R6, #-1
STR R0, R6, #0
ADD R6, R6, #-1
STR R1, R6, #0
ADD R6, R6, #-1
STR R2, R6, #0
ADD R6, R6, #-1
STR R3, R6, #0
ADD R6, R6, #-1
STR R4, R6, #0
; 加载字符串的起始地址
LD R4, STR_BEGIN
READ_INPUT:
; 检查键盘输入
LDI R1, KBSR
BRZP READ_INPUT
LDI R0, KBDR
LD R2, ENTER
ADD R2, R2, R0
BRZ END_INPUT ; 如果按下回车键,则停止输入
STR R0, R4, #0 ; 将字符存储到字符串中
ADD R4, R4, #1
BRNZP READ_INPUT
END_INPUT:
; 字符串以回车键结束
AND R0, R0, #0
ADD R0, R0, #10
STR R0, R4, #0
INIT_PRINT:
; 初始化计数器为 10
AND R3, R3, #0
ADD R3, R3, #10
PRINT_LOOP:
LD R4, STR_BEGIN
PRINT_CHAR:
; 读取字符串中的字符
LDR R0, R4, #0
ADD R4, R4, #1
CHECK_DISPLAY:
; 检查屏幕状态
LDI R1, DSR
BRZP CHECK_DISPLAY
LD R2, ENTER
ADD R2, R2, R0
BRZ DONE_PRINT ; 如果是回车键,则跳转到 DONE_PRINT
STI R0, DDR ; 将字符输出到显示器
BRNZP PRINT_CHAR
DONE_PRINT:
ADD R3, R3, #-1
BRP PRINT_LOOP
RESTORE_REGS:
; 从堆栈中恢复寄存器 R0-R4
LDR R4, R6, #0
ADD R6, R6, #1
LDR R3, R6, #0
ADD R6, R6, #1
LDR R2, R6, #0
ADD R6, R6, #1
LDR R1, R6, #0
ADD R6, R6, #1
LDR R0, R6, #0
ADD R6, R6, #1
; 从中断返回
RTI
; 内存映射 I/O 地址
KBSR .FILL xFE00
KBDR .FILL xFE02
DSR .FILL xFE04
DDR .FILL xFE06
; 常量
ENTER .FILL xFFF6 ; -x000A(表示回车键)
STR_BEGIN .FILL x4000 ; 字符串的起始地址
.END