微机原理硬件实验一:跑马灯

本文针对:北邮微机原理硬件实验一,实验平台介绍和实验一IO的使用。用基于8086的汇编进行编程实验。
前半学期荒废,基本没学汇编语言,看着实验要求就一个头痛。
同学找来学长代码,成功救赎。
写下本文,以膜拜学长,并从中学习微原硬件实验编程方法。

实验要求

1.学习使用Debug命令,并用I、O命令直接对端口进行读写操作,
2.用汇编语言编写跑马灯程序。(使用EDIT编辑工具)实现功能
A.通过读入端口状态(ON为低电平),选择工作模式(灯的闪烁方式、速度等)。
B.通过输出端口控制灯的工作状态(低电平灯亮)

学长的代码

我就直接把学长的代码粘贴过来啦,保留了作者信息,学长会原谅我的。

; FILENAME : marquee.asm
; AUTHOR : XIAO, Zhiqing ( No. 13 , Cl. 07105 )
; DATE : 20091209
; MODIFIED: feichashao, 2013-12-7
; DESCRIPTION : revolving scenic lamps which shift in either direction
; USAGE : switchers -- ( SW0 is the most left one )
;	SW0 ( play ) : 		UP exit			DOWN play
;	SW1 ( direction ): 	UP right		DOWN left
;	SW2 ( pause ) :		UP pause		DOWN play
;	SW3 ( speed ) :		UP slow			DOWN fast
;	SW4 ( mode ) :		UP three lights	DOWN one light

Data SEGMENT
SwState DB 0
LdState DB 0FEH
Buffer DB 0
Data ENDS

Stack SEGMENT STACK 'STACK'
      DB 100H DUP(0)
Stack ENDS

Code SEGMENT
   ASSUME   DS:Data,SS:Stack,CS:Code

Main PROC
  PUSH DS
  XOR  AX,AX
  PUSH AX
  MOV AX,Data
  MOV DS,AX
  MOV ldState,0FEh;initialize
  CALL EndlessRepeat
  RET;unreachable instruction
Main ENDP

EndlessRepeat PROC
EndlessLoop: CALL ProcToRepeat
   JMP EndlessLoop
   RET
EndlessRepeat ENDP

ProcToRepeat PROC
  CALL readSw
  CALL excuteDirection
  CALL excuteMode
  CALL excutePause
  CALL excuteReturnToDos
  CALL excuteSpeed
  RET
ProcToRepeat ENDP

DivFreq PROC
  PUSH CX
  MOV  CX,1000H
MyLoop: PUSH CX
  MOV  CX,8000H
MyNestedLoop:DEC CX
  JNZ MyNestedLoop
  POP CX
  DEC CX
  JNZ MyLoop
  POP CX
  RET
DivFreq ENDP

ReadSw PROC
  PUSH AX
  PUSH DX
  MOV  DX,0E8E0H
  IN   AL,DX
  MOV  SwState,AL
  POP  DX
  POP AX
  RET
ReadSw ENDP

WriteLd PROC
  PUSH DX
  PUSH AX
  MOV AL,LdState
  MOV DX,0E8E0H
  OUT DX,AL
  POP AX
  POP DX
  RET
WriteLd ENDP

excuteReturnToDos PROC
  TEST SWSTATE,01H
  JZ exitexcuteReturnToDos
  CALL returnToDos
exitexcuteReturnToDos:RET
excuteReturnToDos ENDP

ReturnToDos PROC
  MOV AX,4C00H
  INT 21H
ReturnToDos ENDP

excuteDirection PROC
   TEST SWSTATE,02H
   JZ   shiftLeft
   ROR  LDSTATE,01H
   ROR  LDState,01H
shiftLeft:ROL LDSTATE,01H
exitexcuteDirection:RET
excuteDirection ENDP

excutePause PROC
beginexcutePause:CALL readSw
   TEST swState,04H
   JNZ beginexcutePause
   RET
excutePause ENDP

excuteSpeed PROC
   CALL divFreq
   TEST swState,08H
   JZ exitexcuteSpeed
   CALL divFreq
   CALL divFreq
exitexcuteSpeed: RET
excuteSpeed  ENDP

excuteMode PROC
   PUSH WORD PTR ldState
   TEST swState,10H
   JZ simpleMode
   PUSH AX
   PUSH BX
   PUSH CX
   MOV AL,ldState
   MOV BL,AL
   ROL BL,1
   AND BL,AL
   MOV CL,BL
   ROL CL,1
   AND AL,CL
   MOV ldState,AL
   POP CX
   POP BX
   POP AX
simpleMode: CALL writeLD
exitexcuteMode: POP WORD PTR ldState
   RET
excuteMode ENDP

Code ENDS
END Main

程序结构,伪代码

因为坑爹的实验要在DOS环境下敲代码,老师又各种催,所以注释没有加上。不过学长的变量和函数命名都相当清晰,相信大家一看就能懂。
程序以main为入口,跟一般高级语言编程是一个思路,太喜欢啦~
我就把程序的大致框架用伪代码写一下:

int main()
{
	initialize();
	while(1){
		read_switch_state(); // Read the physical switches state.
		execute_direction(); // Change the marquee direction according to the switches state read.
		execute_mode(); // Change mode(Single/Triple Flashing) according to the switches state read.
		execute_pause(); // Pause if the switches state acquires.
		execute_return_to_dos(); // Return to DOS if the switches state acquires.
		excute_speed(); // Delay. The latency is determined by the switches state read.
	}
	return 0;
}

类似函数的分块法

仔细品读了学长的代码,我又学会了汇编语言是怎样给程序分块的,也就是说写各种不同的“函数”,然后调用。
所有代码都在Code Segment里面写。“函数”(Process)是这样定义的:

(过程名) PROC
  一些操作
  RET
(过程名) ENDP

在“函数”(Process)的开始,一般会用栈保存寄存器的数据,操作结束的时候再恢复,这样函数就不会搞乱整个程序了。

汇编中使用变量

单靠寄存器,没有变量实在没法活呀。
哈哈,学长的代码就有“变量”的定义和使用方法,以后可以学着用。
留意到,代码里的变量都是直接定义在Data Segment,直接在程序里使用变量名就可以对变量进行读写等操作。

Data SEGMENT
SwState DB 0
LdState DB 0FEH
Buffer DB 0
Data ENDS

要注意的是,程序初始化的时候,一定要把DS指向Data Segment,否则变量是无法使用的!切记!

  PUSH DS
  XOR  AX,AX
  PUSH AX
  MOV AX,Data
  MOV DS,AX

好啦,就写这么多啦,详情见代码吧。