微机系统串行口的全双工通信测试


前置说明

本项目使用未来汇编和notepad++,基于NJUPT CS专业课程设计要求编写

项目要求

1.课题内容:运用微机系统串行口知识,进行微机系统串行口的全双工通信测试

2.基本要求:

(1)设计发送和接收两个窗口;

(2)发送的数据从键盘键入,数据接收后送屏幕显示;数据发送采用查询方式,数据接收采用中断方式;

(3)波特率、帧格式可以设置;

(4)实物演示时要求讲出程序原理和设计思想;

(5)程序运行良好、界面清晰。

3.提高要求:

(1)界面色彩鲜明、人机交互友好;

(2)可以发送、接收文件。

需求分析

​ 首先分析核心要求以搭建编译环境,即运用微机系统串行口进行全双工通信测试,要求采用查询发送和中断接收方式,由于在WIN32操作系统中禁止应用程序像DOS中那样直接访问计算机硬件,因此,无法像以前那样采用中断读写串口,所以这里采用TNotepad++编译环境,该系统集成了DOSBox(X86 DOS环境模拟)和TASM(汇编编译器)。同时本课题基于586处理器进行编写,因为需求中要求设置波特率,这里需要使用大数乘除法,所以需要用到32位寄存器,所以使用586环境。

​ 针对基本要求(1),在DOS环境下模拟两个窗口并不是一件容易的事,笔者设想使用BIOS9号功能在光标位置显示字符及其属性的原理手动绘制窗口。

​ 针对基本要求(2),即串口通信的核心功能,基本思想是键盘读入字符在发送窗口内显示出来,然后通过中断接收后,存入数组,最后再一起显示在接收窗口中。

​ 针对基本要求(3),基本思想是键盘读入波特率,然后计算分频系数,帧格式则考虑校验位、停止位和数据位的设置,同样由用户选择。

​ 针对基本要求(4),由报告、注释、程序说明和现场讲解几部分组成。

​ 针对基本要求(5),主要包括界面整洁和健壮性处理,包括控制用户读入和文件读写中的状态反馈。

​ 针对提高要求(1),通过设置彩色背景提示字和不同颜色窗口以达到色彩鲜明的目的,交互方面则变现在用不同背景颜色标明提示字,如提示字为蓝色背景,警示字为红色背景,对用户读入字符的反馈和运行结果的反馈。

​ 针对提高要求(2),通过读入txt文件信息进入串口通信发送给另一个txt文件。

核心设计

功能

辅助模块

提示字

使用BIOS13H功能,显示字符串,具体针对宏块DISP:

DISP MACRO Y,X,ADDR,LONG,COLOR         

		MOV AX,1301H
		MOV BH,0
		MOV BL,COLOR     
		MOV CX,LONG
		MOV DH,Y
		MOV DL,X
		MOV BP,OFFSET ADDR
		INT 10H
ENDM

其中,DH / DL= Y / X,表示起始行/列;CX= LONG,表示串长度;ES:BP= ADDR,表示串地址;BL=COLOR,表示颜色。这里我使用的是AL=1的情况,即光标跟随移动,串结构都是字符。

在整个软件设计中,主要使用两种模式,即白字蓝底提示字和白字红底警示字,分布对应COLOR值00011111B和01001111B,如下图所示,白字蓝底提示字表示提示输入信息,白字红底警示字表示输入错误的警示。

清屏模块

针对宏块CLEAN:

CLEAN   MACRO A,B
		MOV 	NUM,0
		ADD     NUM,A
CYCLE_CLEAN:
		
		mov     ah,2             ;设置光标位置
        mov     bh,0             ;page= 0
        mov     dh,NUM            ;0行0列
        mov     dl,B	
        int     10h
				
				;BIOS9号功能在光标位置显示字符及其属性
				;BH=显示页
				;AL=字符
				;BL=属性
				;CX=字符重复次数
        mov     ah,09            ;BIOS9号功能显示一个字符
        mov     al,' '
        mov     bl,0 		 	 ;纯黑背景   
        mov     cx,80			 ;0行0列显示50个空格
        int     10h
		INC     NUM
		CMP     NUM,80
		JE      DROP
		JMP     CYCLE_CLEAN
DROP:
		
ENDM

具体功能是在0号页面A行B列处开始清屏,原理是结合BIOS2号的定位功能和9号输出带属性字符的功能,连续在相应光标处输出一个黑底空格字符,这样可以起到清空屏幕的作用,以等待接下来两个窗口的绘制。设置一帧数据格式

针对宏块SET_FORMAT:

SET_FORMAT MACRO A,B      
		DISP A,B,MSG_VERIFY,L3,00011111B 		;从1行1列设置左框提示字,蓝底白字显示提示信息(必须紧跟彩色文本方式设置)		
		MOV	ah,2             ;设置光标位置准备读入
        MOV	bh,0             ;page= 0
        MOV	dh,A+6             ;2行1列
        MOV	dl,B	
        INT	10h
SET_FORMAT_CYCLE00:		
		MOV AH,01H
		INT 21H			;DOS21H调用1号功能输入一个字符并显示出来
		CMP AL,'1'		;注意读入的是字符,应该有引号
		JNE SET_FORMAT_CYCLE01
		MOV FORMAT,00000000B;
		JMP SET_FORMAT_CYCLE06
SET_FORMAT_CYCLE01:
		CMP AL,'2'
		JNE SET_FORMAT_CYCLE02
		MOV FORMAT,00001000B;
		JMP SET_FORMAT_CYCLE06
SET_FORMAT_CYCLE02:
		CMP AL,'3'
		JNE SET_FORMAT_CYCLE03
		MOV FORMAT,00011000B;
		JMP SET_FORMAT_CYCLE06		
SET_FORMAT_CYCLE03:
		CMP AL,'4'
		JNE SET_FORMAT_CYCLE04
		MOV FORMAT,00101000B;
		JMP SET_FORMAT_CYCLE06		
SET_FORMAT_CYCLE04:
		CMP AL,'5'
		JE SET_FORMAT_CYCLE05
	
		;BIOS3号功能读光标位置,不能超出窗口范围
		;BH=页号
		;CL/CH=光标起始行/光标结束行
		;DH/DL=行/列
		MOV     ah,3             ;定位光标在A行
		MOV     TEMP,DH			 ;注意TEMP代表变量,+n表示向后取值	        
		INT     10h

		ADD     TEMP,1	
		DISP TEMP,B,error_message2,L4,01001111B     ;从下一行B列设置错误红色背景提示字

		MOV     ah,3             ;定位光标在A行
        INT     10h
		MOV     TEMP,DH
		
		ADD     TEMP,1
		MOV     ah,2             ;设置光标到下一行重新读入
        MOV     bh,0             ;page= 0
        MOV     dh,TEMP             ;DI+1行B列
        MOV     dl,B	
        INT     10h
		JMP SET_FORMAT_CYCLE00
SET_FORMAT_CYCLE05:		
		MOV FORMAT,00111000B;	
		JMP SET_FORMAT_CYCLE06
		
;设置停止位			
SET_FORMAT_CYCLE06:
		;BIOS3号功能读光标位置,不能超出窗口范围
		;BH=页号
		;CL/CH=光标起始行/光标结束行
		;DH/DL=行/列	
		MOV     ah,3             ;定位光标在A行
        INT     10h
		MOV     TEMP,DH	
		ADD     TEMP,1
	
		DISP TEMP,B,MSG_STOP_BIT,L5,00011111B ;从1行1列设置左框提示字,蓝底白字显示提示信息
		
		ADD     TEMP,4
		MOV     ah,2             ;设置光标位置准备读入
        MOV     bh,0             ;page= 0
        MOV     dh,TEMP             ;2行1列
        MOV     dl,B	
        INT     10h
SET_FORMAT_CYCLE10:		
		MOV AH,01H	
		INT 21H					 ;DOS21H调用1号功能输入一个字符并显示出来
		CMP AL,'1'
		JNE SET_FORMAT_CYCLE11
		AND FORMAT,1111011B
;10模块设置1位停止位,可以选择5、6、7、8位数据位			
;+++++++++++++++++++++		
		mov     ah,3             ;定位光标
        int     10h
		MOV     TEMP,DH	
		ADD     TEMP,1
		DISP TEMP,B,MSG_DATA_BIT,L6,00011111B 		;从1行1列设置左框提示字,蓝底白字显示提示信息(必须紧跟彩色文本方式设置)	
		ADD     TEMP,5
		mov     ah,2             ;输出提示信息后把光标挪后5行准备读入
        mov     bh,0             ;page= 0
        mov     dh,TEMP          ;2行1列
        mov     dl,B	
        int     10h
SET_FORMAT_CYCLE101:		
		MOV AH,01H	
		INT 21H					 ;DOS21H调用1号功能输入一个字符并显示出来
		CMP AL,'1'
		JNE SET_FORMAT_CYCLE102
		AND FORMAT,11111100B		 ;设置5位数据位			
		JMP SET_FORMAT_CYCLE20		
SET_FORMAT_CYCLE102:
		CMP AL,'2'
		JNE SET_FORMAT_CYCLE103
		OR FORMAT,00000001B		 ;设置6位数据位
		AND FORMAT,11111101B	
		JMP SET_FORMAT_CYCLE20
SET_FORMAT_CYCLE103:
		CMP AL,'3'
		JNE SET_FORMAT_CYCLE104
		OR FORMAT,00000010B		 ;设置7位数据位
		AND FORMAT,11111110B	
		JMP SET_FORMAT_CYCLE20	
SET_FORMAT_CYCLE104:
		CMP AL,'4'
		JE SET_FORMAT_CYCLE105
		mov     ah,3             ;定位光标在A行
		MOV     TEMP,DH			 ;注意TEMP代表变量,+n表示向后取值	        
		int     10h
		ADD     TEMP,1		
		DISP TEMP,B,error_message2,L4,01001111B  ;从下一行B列设置错误红色背景提示字		
		ADD     TEMP,1
		mov     ah,2             ;设置光标到下一行重新读入
        mov     bh,0             ;page= 0
        mov     dh,TEMP             
        mov     dl,B	
        int     10h
		JMP SET_FORMAT_CYCLE101		
SET_FORMAT_CYCLE105:				
		OR FORMAT,00000011B		 ;设置8位数据位				
		JMP SET_FORMAT_CYCLE20
;+++++++++++++++++++++						 
SET_FORMAT_CYCLE11:
		CMP AL,'2'
		JNE SET_FORMAT_CYCLE12
		AND FORMAT,11111100B
		OR	FORMAT,00000100B
		JMP SET_FORMAT_CYCLE20	 ;1.5位停止位要求D0D1=00,所以设好后直接转走
SET_FORMAT_CYCLE12:
		CMP AL,'3'
		JE SET_FORMAT_CYCLE13
		
		mov     ah,3             ;定位光标在A行
		MOV     TEMP,DH			 ;注意TEMP代表变量,+n表示向后取值	        
		int     10h

		ADD     TEMP,1
		
		DISP TEMP,B,error_message2,L4,01001111B     ;从下一行B列设置错误红色背景提示字
		
		ADD     TEMP,1
		mov     ah,2             ;设置光标到下一行重新读入
        mov     bh,0             ;page= 0
        mov     dh,TEMP             
        mov     dl,B	
        int     10h
		JMP SET_FORMAT_CYCLE10
SET_FORMAT_CYCLE13:				 ;选择2位停止位,立马进行6、7、8位数据位选择
		OR FORMAT,00000100B      ;设置D2=1
		
;=======13模块选择6、7、8位数据位===================================

		mov     ah,3             ;定位光标在A行
        int     10h
		MOV     TEMP,DH	
		ADD     TEMP,1
		DISP TEMP,B,MSG_DATA_BIT2,L7,00011111B 		;从1行1列设置左框提示字,蓝底白字显示提示信息(必须紧跟彩色文本方式设置)
		
		ADD     TEMP,4
		mov     ah,2             ;输出提示信息后把光标挪后5行准备读入
        mov     bh,0             ;page= 0
        mov     dh,TEMP          ;2行1列
        mov     dl,B	
        int     10h
SET_FORMAT_CYCLE131:		
		MOV AH,01H	
		INT 21H					 ;DOS21H调用1号功能输入一个字符并显示出来
		CMP AL,'1'
		JNE SET_FORMAT_CYCLE132
		OR FORMAT,00000001B		 ;设置6位数据位
		AND FORMAT,11111101B			
		JMP SET_FORMAT_CYCLE20	 
SET_FORMAT_CYCLE132:
		CMP AL,'2'
		JNE SET_FORMAT_CYCLE133
		OR FORMAT,00000010B		 ;设置7位数据位
		AND FORMAT,11111110B	
		JMP SET_FORMAT_CYCLE20	 
SET_FORMAT_CYCLE133:
		CMP AL,'3'
		JE SET_FORMAT_CYCLE134
		
		mov     ah,3             ;定位光标在A行
		MOV     TEMP,DH			 ;注意TEMP代表变量,+n表示向后取值	        
		int     10h

		ADD     TEMP,1
		
		DISP TEMP,B,error_message2,L4,01001111B     ;从下一行B列设置错误红色背景提示字
		
		ADD     TEMP,1
		mov     ah,2             ;设置光标到下一行重新读入
        mov     bh,0             ;page= 0
        mov     dh,TEMP             
        mov     dl,B	
        int     10h
		JMP SET_FORMAT_CYCLE131
		
SET_FORMAT_CYCLE134:		
		
		OR FORMAT,00000011B		 ;设置8位数据位
;=======================================================
SET_FORMAT_CYCLE20:
		mov     ah,3             ;定位光标在A行
		MOV     TEMP,DH			 ;注意TEMP代表变量,+n表示向后取值	        
		int     10h

		ADD     TEMP,1
		mov     ah,2             ;设置光标到下一行
        mov     bh,0             ;page= 0
        mov     dh,TEMP             
        mov     dl,B	
        int     10h
ENDM

用于在A行B列设置数据格式,主要分为校验位、停止位和数据位,首先设置一个DB变量FORMAT用于存储最终的一帧数据格式,显示校验位输入提示信息MSG_VERIFY后,向后6行定位光标让用户输入1-5以做出以下五种选择(超出范围会警示并重新输入),选择后直接将对应的D5D4D3位设置成相应的0或1,其他位暂时置0:

·不设置校验位(None) -> 00000000B

·奇校验(Odd) -> 00001000B

·偶校验(Parity) -> 00011000B

·恒为1 -> 00101000B

·恒为0 -> 00111000B

接下来是设置停止位,首先使用BIOS3号功能确定当前光标的位置,设置停止位输入提示信息MSG_STOP_BIT,向后4行定位光标让用户输入1-3以做出以下四种选择(超出范围会警示并重新输入),其中如果用户选择了1位停止位,则接下来正常进入数据位的选择;如果选择了1.5位停止位,则会默认将D1D0赋值为00,就可以跳过数据位的选择;如果用户选择了2位停止位,因为要求D1D0不等于00,所以接下来选择数据位只能是6-8位:

·1bit -> AND 1111011B(D2=0)

·1.5bit -> AND 11111100B(D1D0=00)

​ -> OR 00000100B(D2=1)

·2bit -> OR 00000100B(D2=1)

针对数据位设置:

·5bit -> AND 11111100B(D1D0=00)

·6bit -> OR 00000001B(D0=1)

​ -> AND 11111101B(D1=0)

·7bit -> OR 00000010B(D1=1)

​ -> AND 11111110B(D0=0)

·8bit -> OR 00000011B(D1D0=11)

设置波特率

针对宏块DIVISIONMODULE:

DIVISIONMODULE MACRO
MULMODULE:		
		;乘法模块,需要加一条提示语
		mov     ah,3             ;定位光标
        int     10h
		MOV     TEMP,DH	
		
		DISP TEMP,0,MSG_DIVISION,L8,00011111B
		;初始化归0
		MOV C1,01H
		MOV C10,10
		MOV REALNUM,0B;实际的数字可能为32位
		MOV CARRY,0;存放进位情况,超过16位为1
		MOV CARRY2,0;临时存放进位情况
		MOV CARRY3,0;代表乘10时的进位情况,但会保持下去
		MOV COUNT,0
		
		;MOV LEN,16;测试用的LEN,最后可删除
		
		LEA SI,TRANSFORM
READBUF:
		MOV AH,1
		INT 21H
		CMP AL,13		;回车结束循环
		JE  MIDDLE_MUL
		SUB AL,48
		MOV AH,0
		MOV [SI],AX
		INC SI			;注意对DW变量一次是加减2
		INC SI
		INC COUNT		;记录循环次数
		CMP COUNT,9    ;为了防止超过32位,超过9位直接报错
		JA  MIDDLE_ERROR_READ_END
		JMP READBUF
MIDDLE_MUL:

		
		DEC SI			;前面多加了一次
		DEC SI
MULTIPLE:
		;高位乘法原则:原高位*乘数+乘法结果中的高位->新高位
		MOV AX,C1
		MOV BX,[SI]
		MUL BX
		MOV CX,AX		;将低16位部分暂存在CX
		MOV CARRY2,DX	;将乘法结果中的高位暂存 
		
		MOV BX,CARRY3	;初始化CARRY,上部分原高位是下部分乘10模块的进位结果
		MOV CARRY,BX
		
		MOV AX,CARRY	;原高位部分也要乘,因为16位乘16位不会超过32位,所以这里不考虑原高位乘法后的高16位
		MOV BX,[SI]
		MUL BX
		ADD CARRY2,AX	;AX存放原高位*乘数结果
		MOV AX,CARRY2
		MOV CARRY,AX	;新高位
		
		DEC SI
		DEC SI

		MOV EBX,0		;初始化全为0
		MOV BX,CARRY	;将存在高16位的值取出
		SAL EBX,16		;将值左移16位固定最高位
		MOV BX,CX		;补充低16位值
		
		ADD REALNUM,EBX 
		DEC COUNT
		CMP COUNT,0
		JE  ENDMUL
		
		MOV AX,C1
		MUL C10			;下部分乘10模块
		MOV C1,AX
		MOV CARRY2,DX
		
		MOV AX,CARRY3	;下部分原高位也是按照高位乘法原则乘,但这个CARRY3会一直保留,不会刷新值
		MUL C10
		ADD CARRY2,AX		
		MOV AX,CARRY2
		MOV CARRY3,AX		
				
		JMP MULTIPLE
MIDDLE_ERROR_READ_END:
		;换行
		MOV     ah,3             ;定位光标
        int     10h
		MOV     TEMP,DH	
		ADD     TEMP,1
		
		MOV     ah,2             ;设置光标到下一行重新读入
        MOV     bh,0             ;page= 0
        MOV     dh,TEMP             
        MOV     dl,0	
        int     10h
		
		JMP ERROR_READ_END
ENDMUL:
		;判断读取数字范围模块
		CMP REALNUM,2
		JB ERROR_READ_END
		CMP REALNUM,115200		
		JG ERROR_READ_END
		JMP DIVMODULE
ERROR_READ_END:
		;加一句读错提示语
		
		MOV     ah,3             ;定位光标
		MOV     BH,0			 ;BH也要赋值,强调页面0,因为前面进位计算会改变这个值
        int     10h
		MOV     TEMP,DH	
		
		DISP TEMP,0,error_message2,L4,01001111B	
		;换行
		MOV     ah,3             ;定位光标
        int     10h
		MOV     TEMP,DH	
		ADD     TEMP,1
		MOV     ah,2             ;设置光标到下一行
        MOV     bh,0             ;page= 0
        MOV     dh,TEMP             
        MOV     dl,0	
        int     10h
		
		JMP MULMODULE
DIVMODULE:
		;除法模块,使用32位除法,得到整数直接就是分频系数,结果一定是16位
		MOV EAX,115200
		MOV EDX,0
		MOV EBX,REALNUM
		div EBX
		MOV DIVISION_FACTOR,AX
		
		MOV ah,3             ;定位光标
		MOV BH,0
		int 10h
		
		MOV     TEMP,DH			 ;注意TEMP代表变量,+n表示向后取值
		MOV     ah,2             ;设置光标到下一行
        MOV     bh,0             ;page= 0
        MOV     dh,TEMP             
        MOV     dl,0	
        int     10h
ENDM

这里用户可以输入的波特率大小在2-115200范围内,默认主频为1843200Hz,相应算法是:分频系数 = 1843200 / (16 * 波特率),对应分频系数范围是E100H-0001H。

核心算法是一个个读入字符,然后把字符-48变成数字,先按照8bit一个字符存储在TRANSFORM数组中,最后判断循环次数即字符数,读入回车符结束,根据循环次数判断输入是否合理(不能超过32位)。

进入乘法模块进行循环,初始C1是1,每次给C1乘10,再乘到对应的TRANSFORM位置上,即按照乘10、乘100…相加转成一个整数,对高16位的处理如下:

原高位*乘数+乘法结果中的高位->新高位

对高16位的处理分为上下两部分,上部分原高位是下部分乘10模块的进位结果,这个结果是一直保留的,下部分按照高位乘法原则即可。

对于乘法过程中结果的暂存是通过移位的方式进行的,先将高16位DX存入EAX,再左移16位,再存入低16位,对EAX进行范围判断,不合适重新读入,合适进行分频系数除法计算。

分频系数的计算使用大数除法运算,然后取整,将分频系数存入16位寄存器,按照高8位和低8位在8250初始化时分别赋值。

功能选择

针对宏块FUNCTION_CHOOSE:

FUNCTION_CHOOSE MACRO
		mov ah,3             ;定位光标
        int 10h
		MOV TEMP,DH	
		
		DISP TEMP,0,MSG_CHOOSE,L00,00011111B
		ADD TEMP,3
		mov ah,2             ;设置光标位置准备读入
        mov bh,0             ;page= 0
        mov dh,TEMP          
        mov dl,0	
        int 10h
			
		MOV AH,01H
		INT 21H
		CMP AL,'2'
		JE 	MIDDLE_FUNCTION_CHOOSE
		MOV CHOOSE,0
		JMP END_FUNCTION_CHOOSE
MIDDLE_FUNCTION_CHOOSE:
		MOV CHOOSE,1
END_FUNCTION_CHOOSE:		
ENDM

在屏幕上显示功能选择提示字后,用户选择1.键盘或2.文件读入方式。

窗口

针对宏块KDISP:

KDISP MACRO A,B,LEFT_HEIGHT0,RIGHT_HEIGHT0,DOWN0,ATTRIBUTE0      

		;BIOS2号功能置光标位置
		;BH=显示页号
		;DH/DL=行/列
		MOV NUM,1
		MOV	ah,2             ;设置光标位置
		MOV	bh,0             ;page= 0
		MOV	dh,A            ;0行0列
		MOV	dl,B	
		INT	10h
					
		;BIOS9号功能在光标位置显示字符及其属性
		;BH=显示页
		;AL=字符
		;BL=属性
		;CX=字符重复次数
		MOV	ah,09            ;BIOS9号功能显示一个字符
		MOV	al,' '
		MOV	bl,ATTRIBUTE0 		 ;1 101 1 010(1.5.1.2)   
		MOV	cx,29			 ;0行0列显示29个空格
		INT	10h                                
LEFT_HEIGHT0:                
		MOV	ah,2  			 ;NUM行0列(初始1行) 
		MOV	bh,0  
		MOV	dh,A+NUM 
		MOV	dl,B  
		INT	10h
					
		MOV	ah,09   
		MOV	al,' '
		MOV	bl,ATTRIBUTE0    
		MOV	cx,1			 ;DI行0列显示1个空格
		INT 10h
					
		INC	NUM
		CMP	NUM,11			 ;一共要显示10行
		JNE	LEFT_HEIGHT0
		MOV	NUM,1
RIGHT_HEIGHT0:								
		MOV	ah,2  			 ;NUM行0+28=28列  
		MOV	bh,0  
		MOV	dh,A+NUM
		MOV	dl,B+28  
		INT	10h
					
		MOV	ah,09  
		MOV	al,' '
		MOV	bl,ATTRIBUTE0    
		MOV	cx,1			 ;显示1个空格
		INT	10h
					
		INC	NUM
		CMP	NUM,11
		JNE	RIGHT_HEIGHT0			
DOWN0:				
		MOV	ah,2             ;NUM行0列(这里已经是加过的NUM相当于11行)       
		MOV	bh,0            
		MOV	dh,A+NUM           
		MOV	dl,B+0           
		INT	10h
				
		MOV	ah,09    		       
		MOV	al,' '
		MOV	bl,ATTRIBUTE0    
		MOV	cx,29			 ;2行0列显示29个空格
		INT	10h
ENDM

对整个屏幕进行清屏,清除之前显示的所有提示字和键盘输入信息,准备开始绘制窗口,两窗口通过BIOS2号功能定位、9号功能在光标位置绘制协同工作,在左窗口以空格字符紫色背景连续绘制11*29大小的窗口,在右窗口以空格字符青色背景连续绘制11*29大小的窗口,针对用户选择的功能在两窗口内显示不同的提示字,对应键盘功能两个窗口的功能分别是用户读入字符窗口和字符显示窗口,对应文件功能两个窗口的功能分别是文件字符显示和文件写入结果状态显示。

键盘功能演示

文件功能演示

键盘读入与输出

首先定位到可输出的第三行开始读入字符,事先判断当前光标的位置,以左框为例,超出第27列则置光标到下一行,这样可以保证输入的字符显示不会超出边框界限。高度上,光标超出第9号自动出局,默认字符读取完毕,开始进行串口通信。因为事先清屏使得屏幕上含有空格字符,所以这里只能使用BIOS功能进行显示,输入字符使用DOS8号功能不回显字符读入,显示使用BIOS9号功能,采用黑底白字属性,读入以回车符结束,结束后回车符不放入BUF数组并且在末尾加入$符号。(事先要使用BIOS3号功能将相应的寄存器赋上正确的值),对应宏块READ_KEY:

READ_KEY MACRO A,B
		MOV NUM,3
		ADD NUM,A
		LEA SI,BUF
		mov ah,2             ;设置光标位置准备读入
        mov bh,0             ;page= 0
        mov dh,2             ;2行1列
        mov dl,1	
        int 10h
CYCLE_READ_KEY:				
		;BIOS3号功能读光标位置,不能超出窗口范围
		;BH=页号
		;CL/CH=光标起始行/光标结束行
		;DH/DL=行/列
		mov ah,3             ;设置光标位置准备读入
        int 10h
	    CMP DL,28             ;超出第27列置光标到下一行
		JNE NEXT_CYCLE_READ_KEY
				
		CMP dh,10             ;超出第9行出局
		JE	END_READ_KEY
		mov ah,2             ;设置光标位置到下一行
        mov bh,0             ;page= 0
        mov dh,NUM           ;NUM行1列
        mov dl,1	
        int 10h
		INC NUM
NEXT_CYCLE_READ_KEY:
		CMP FLAG,1      ;标志位为-1就结束
		JE OVER_READ_KEY
		MOV DX,2FDH		;通信线状态寄存器
		IN AL,DX		;从端口读状态进AL
		TEST AL,20H     ;查询D5看发送保持寄存器是否空闲
		JZ NEXT_CYCLE_READ_KEY        ;不空闲就不给送数据
		
		MOV AH,08H
		INT 21H			;DOS21H调用8号功能输入一个字符串不显示出来 
		;存下一个位置
		CMP AL,13		;没到最后就继续存
		JE END_READ_KEY	
		
		MOV AH,3
		MOV BH,0
		INT 10H
		
		MOV AH,09H
		MOV BH,0
		MOV BL,7
		MOV CX,1
		INT 10H
		
		MOV TEMP,DH
		MOV TEMP2,DL
		ADD TEMP2,1
		
		MOV AH,2
		MOV BH,0
		MOV DH,TEMP
		MOV DL,TEMP2
		INT 10H
		
		MOV [SI],AL		;输入的字符就存起来
		INC SI	
		JMP CYCLE_READ_KEY		
OVER_READ_KEY:

		CALL CLOSE     	;恢复系统0BH中断向量
						
		mov     ah,2             ;设置光标位置输出结束信息
        mov     bh,0             ;page= 0
        mov     dh,30             ;30行0列
        mov     dl,0	
        int     10h
		
		MOV AH,4CH		;DOS21H调用4CH号功能终止程序
		INT 21H	
END_READ_KEY:
	 	MOV BUF[SI],'$'
		MOV NUM,3   ;NUM放在第3行准备换行				
		LEA SI,BUF		;重新定位SI的偏移地址	
ENDM

读入的字符通过串口通信后接收到BUF2数组中,将BUF2数组按照类似左窗口的方式显示在右窗口中,对应宏块WRITE_KEY:

WRITE_KEY MACRO
BRG_WRITE_KEY:
		mov ah,2             ;设置光标位置准备读入
        mov bh,0             ;page= 0
        mov dh,2             ;2行1+29列
        mov dl,30	
        int 10h
		LEA	DI,BUF2
		MOV NUM,3
CYCLE_WRITE_KEY:	
		
		;BIOS3号功能读光标位置,不能超出窗口范围
		;BH=页号
		;CL/CH=光标起始行/光标结束行
		;DH/DL=行/列
		mov     ah,3             ;设置光标位置准备读入
        int     10h
	    CMP     DL,57             ;超出第27+29=56列置光标到下一行
		JNE     NEXT_CYCLE_WRITE_KEY
				
		CMP     dh,10             ;超出第9行出局
		JE		OUTSIDE_WRITE_KEY
		mov     ah,2             ;设置光标位置到下一行
        mov     bh,0             ;page= 0
        mov     dh,NUM           ;NUM行30列
        mov     dl,30	
        int     10h
		INC     NUM
NEXT_CYCLE_WRITE_KEY:
		MOV AL,[DI]
		INC DI			;一个个拿出来
		
		MOV AH,3		;定位
		MOV BH,0
		INT 10H
		
		MOV AH,09H		;读字符
		MOV BH,0
		MOV BL,7
		MOV CX,1
		INT 10H
		
		MOV TEMP,DH		;光标后移
		MOV TEMP2,DL
		ADD TEMP2,1
		
		MOV AH,2		;设置光标
		MOV BH,0
		MOV DH,TEMP
		MOV DL,TEMP2
		INT 10H
		
		CMP AL,'$'		;没到最后就继续拿
		JNE CYCLE_WRITE_KEY
		;JMP CYCLE_WRITE_KEY      ;若读取一个字符就会显示一个,只要不检测到$,所以正常是一直循环状态(法1)
						;这里的方式是选择一起读取再显示则应该将字符存储起来再进行查询发送(法2)
						
OUTSIDE_WRITE_KEY:
		LEA DI,BUF2		;重新定位DI的偏移地址						
ENDM

文件读入与输出

事先存好读取文件和写入文件的地址,使用DOS3DH号功能打开文件,其中DS:DX=ASCIIZ串地址,AL=0/1/3读/写/读写,成功:AX=文件代号,错误:AX=错误码,使用AL=0读功能;使用DOS3FH号功能读文件,其中DS:DX=数据缓冲区地址,BX=文件代号,CX=读取的字节数,成功:AX=实际读入的字节,AX=0已到文件尾,读出错:AX=错误码,将读取到的字符存入BUF数组中并在末尾加入$符号,并使用WDISP宏在左窗口显示刚刚读到的BUF字符;最后使用DOS3EH号功能关闭文件,若此过程中遇到错误会在右窗口显示“ERROR!”,对应宏块READ_FILE:

READ_FILE MACRO
;文件读取-----------------
		
		;DOS3DH号功能打开文件,DS:DX=ASCIIZ串地址,AL=0/1/3读/写/读写,成功:AX=文件代号,错误:AX=错误码		  
        MOV DX , OFFSET FILE1        ;dx获取file的偏移地址
		MOV AL , 0                
        MOV AH , 3DH                
        INT 21H 
        JC MIDDLE1_ERROR                  ;若打开出错,转error
        MOV handle , AX           ;保存文件句柄
        MOV BX , AX                ;文件句柄
			  
		;DOS3FH号功能读文件或设备,DS:DX=数据缓冲区地址,BX=文件代号,CX=读取的字节数,成功:AX=实际读入的字节,AX=0已到文件尾,读出错:AX=错误码
        MOV CX , 255                ;读取255字节
        MOV DX , OFFSET BUF        ;获取buf的偏移地址
        MOV AH , 3fh    
        INT 21H                  ;从文件中读255字节进buf
        JC MIDDLE1_ERROR                  ;若读出错,转error
        MOV BX , AX              ;实际读到的字符数送入bx
			  
        MOV BUF[BX] , '$'          ;在文件结束处放置一“$”符
		
		LEA SI,BUF				 ;重新取BUF的偏移地址
		MOV NUM,3
		
		JMP MIDDLE2_ERROR
MIDDLE1_ERROR:
		JMP ERROR		
MIDDLE2_ERROR:
			
		JMP MIDDLE2_OVER		
MIDDLE2_OVER:
		
		WDISP 0,0,BUF,BRG,CYCLE,OUTPUT_BUF,OUTSIDE		 ;显示读取到的BUF(左界面边框0号列)

		MOV BX , handle                    ;文件句柄	  
        MOV AH , 3EH                        
        INT 21H                            ;DOS3EH功能关闭文件,BX=文件代号,AX=错误码	
        JNC OVER_READFILE             ;若关闭过程无错,转到WRITE处开始发送
ERROR:
        MOV NUM,3
		LEA SI,error_message
		WDISP 0,29,error_message,BRG2,CYCLE2,OUTPUT_BUF2,OUTSIDE2

OVER_READFILE:

ENDM

使用DOS3CH号功能创建文件,其中DS:DX=ASCIIZ串地址,CX=文件属性,成功:AX=文件代号,错误:AX=错误码,若磁盘上原有此文件,则覆盖。再使用DOS40H号功能写文件,将中断接收存入BUF2的字符写入刚刚创建的文件中,如果写入成功则显示“SUCCESSFUL WRITTEN!”写入失败则显示“ERROR!”,最后使用DOS3EH号功能关闭文件,对应宏块WRITE_FILE:

WRITE_FILE MACRO 
;-------------
		;DOS3CH功能创建文件,DS:DX=ASCIIZ串地址,CX=文件属性,成功:AX=文件代号,错误:AX=错误码		  若磁盘上原有此文件,则覆盖
		
        MOV dx , offset file2
        MOV cx , 0
        MOV ah , 3ch
        int 21h               
        jc error2               ;创建出错,转error2处
			  
        MOV handle , ax         ;保存文件号
        MOV bx , ax
        ;DOS40H功能写文件,DS:DX=数据缓冲区地址,BX=文件代号,CX=写入字节数,成功:AX=实际写入字节数,错误:AX=错误码
		MOV cx , 256 
        MOV dx , offset buf2
        MOV ah , 40h
        int 21h                          ;向文件中写入256个字节内容
        jc error2                          ;写出错,转error2处
			  
        MOV bx , handle
        MOV ah , 3eh
        int 21h                          ;关闭文件
        jc error2                           ;关闭文件出错,转error2处
		
		MOV NUM,3
		;mov SI , offset message
		LEA SI,message
		WDISP 0,29,message,BRG3,CYCLE3,OUTPUT_BUF3,OUTSIDE3;操作成功后显示提示
        jmp OVER_WRITEFILE
error2:
        LEA SI,error_message
		WDISP 0,29,error_message,BRG4,CYCLE4,OUTPUT_BUF4,OUTSIDE4;操作失败后显示提示
OVER_WRITEFILE:
		
;----------
ENDM 

文件信息和读取状态输出,对应宏块WDISP:

WDISP MACRO A,B,BUF0,BRG,CYCLE,OUTPUT_BUF,OUTSIDE  ;A/B是边框初始位置行/列,NUM0表示内容首次换行一般是3,X是偏移地址(注意边框大小是11*29)   		
BRG:
		LEA	SI,BUF0
		MOV	NUM,3
		ADD	NUM,A				
		MOV	ah,2             ;设置光标位置准备读入
		MOV	bh,0             ;page= 0
		MOV	dh,A+2             ;A行B列
		MOV	dl,B+1	
		INT	10h
CYCLE:				
		;BIOS3号功能读光标位置,不能超出窗口范围
		;BH=页号
		;CL/CH=光标起始行/光标结束行
		;DH/DL=行/列
		MOV	ah,3             ;设置光标位置准备读入
		INT	10h
		CMP	DL,B+27             ;超出第27列置光标到下一行
		JNE	OUTPUT_BUF
					
		CMP	dh,A+9             ;超出第9行出局
		JE	OUTSIDE
		MOV	ah,2             ;设置光标位置到下一行
		MOV	bh,0             ;page= 0
		MOV	dh,NUM          ;NUM行1列
		MOV	dl,B+1	
		INT	10h
		INC	NUM		
OUTPUT_BUF:
		
		;因为BIOS显示会覆盖DOS显示,所以这里应该使用BIOS9号功能输出其中BL=0	
		MOV	ah,09            ;BIOS9号功能显示一个字符
        MOV	al,[SI]
        MOV	bl,00000111B		 ;1 101 1 010(1.5.1.2)   
        MOV	cx,1			 ;0行0列显示29个空格
        INT	10h
		
		MOV	ah,3             ;设置光标位置到下一列
        INT	10h
		ADD	DL,1
		
		MOV	ah,2             ;设置光标位置准备读入
        MOV	bh,0             ;page= 0
        MOV	dl,DL	
        INT	10h
				
		MOV DL,[SI+1]	;把下一个位置字符放到DL准备检测
		INC SI			;显示下一个位置
		
		CMP DL,'$'		;没到最后就继续存
		JNE CYCLE
		MOV NUM,3   ;NUM放在第3行准备换行
OUTSIDE:
		MOV NUM,3   ;NUM放在第3行准备换行				
		LEA SI,BUF0		;重新定位SI的偏移地址
ENDM

主体部分

8250查询方式发送程序

8250初始化

8259A接收主程序流程

接收中断服务子程序流程

DATA	SEGMENT USE16
OLD0B 	DD ?    		;存储系统0BH中断向量
FLAG  	DB -1      		;标志位
FILE1   db   'd:\1.txt' , 0       ;文件名,dosbox 设置的c盘下的路径
FILE2   db   'd:\2.txt' , 0           ;创建文件的文件名
BUF     db   256 dup(?)        ;文件内容暂存区
BUF2    db   256 dup(?)        ;文件内容暂存区
BUF3 	DB   256 DUP(?)	;用于存储输入字符
FORMAT  db   0				   ;帧格式初始化是0
TEMP    DB   1
TEMP2   DB   0
 ;乘除法模块所需变量
 C1 	   			DW 01H
 C10 	   			DW 10
 TRANSFORM 			DW 20 DUP(?);最多存6位数范围:2-115200,对应分频系数E100H-0001H
 REALNUM  			DD 0B;实际的数字可能为32位
 CARRY  			DW 0;存放进位情况,超过16位为1
 CARRY2 			DW 0;临时存放进位情况
 CARRY3 			DW 0;代表乘10时的进位情况,但会保持下去
 COUNT 				DB 0
 DIVISION_FACTOR	DW 0H;分频系数

error_message  		db   'ERROR!','$'     ;出错时的提示
message        		db   'SUCCESSFUL WRITTEN!','$'          ;操作成功后的提示
handle  			dw  ?           ;保存文件号

MSG_CHOOSE      DB   'Please choose function:',0dh,0ah,'1. Keyboard',0dh,0ah,'2. Document',0dh,0ah;校验位设置
L00 			EQU $-MSG_CHOOSE
CHOOSE  		DB 0;默认键盘方式
MSG01 			DB 'PLEASE ENTER STRING:'  ;提示输入
L01 			EQU $-MSG01
MSG02 			DB 'YOUR STRING IS:'  ;展示字符
L02 			EQU $-MSG02

MSG1 			DB 'DOCUMENT INTENT IS:'  ;展示字符
L1 				EQU $-MSG1
MSG2 			DB 'CURRENT STATE:'  ;展示状态
L2 				EQU $-MSG2
MSG_VERIFY      DB   'Check bit setting:',0dh,0ah,'1. None',0dh,0ah,'2. Odd check',0dh,0ah,'3. Parity check',0dh,0ah,'4. Constant 1',0dh,0ah,'5. Constant 0',0dh,0ah;校验位设置
L3 				EQU $-MSG_VERIFY
error_message2  DB   'ERROR!Please read again!'     ;不含$的出错时的提示
L4 				EQU $-error_message2
MSG_STOP_BIT    DB   'Stop bit setting:',0dh,0ah,'1. 1bit',0dh,0ah,'2. 1.5bit',0dh,0ah,'3. 2bit',0dh,0ah;停止位设置
L5 				EQU $-MSG_STOP_BIT

MSG_DATA_BIT    DB   'Data bit setting:',0dh,0ah,'1. 5bit',0dh,0ah,'2. 6bit',0dh,0ah,'3. 7bit',0dh,0ah,'4. 8bit',0dh,0ah;停止位设置
L6 				EQU $-MSG_DATA_BIT
	
MSG_DATA_BIT2   DB   'Data bit setting:',0dh,0ah,'1. 6bit',0dh,0ah,'2. 7bit',0dh,0ah,'3. 8bit',0dh,0ah;停止位设置
L7 				EQU $-MSG_DATA_BIT2

MSG_DIVISION 	DB	 'Baud rate setting(Please enter a number in the range of 2 to 115200):',0dh,0ah;波特率设置
L8 				EQU $-MSG_DIVISION

NUM  			DB 1
DATA  	ENDS

CODE 	SEGMENT USE16
		ASSUME CS:CODE,DS:DATA
BEG: 	MOV AX,DATA
		MOV DS,AX
		
		MOV ES,AX
		LEA SI,BUF
		LEA DI,BUF2
		
		
		MOV AL,42H                      ;640*400彩色文本方式
		MOV AH,0
		INT 10H				
		
		SET_FORMAT 0,0
		DIVISIONMODULE
		FUNCTION_CHOOSE
		CLEAN 0,0
		
		
		
		KDISP 0,0,LEFT_HEIGHT1,RIGHT_HEIGHT1,DOWN1,0dah  ;从0行0列设置左框
		CMP CHOOSE,0
		JE  CHOOSE_KEYBOARD
		DISP 1,1,MSG1,L1,00011111B 		;从1行1列设置文件左框提示字,蓝底白字显示提示信息(必须紧跟彩色文本方式设置)
		DISP 1,30,MSG2,L2,00011111B     ;从1行30列设置文件右框提示字
		JMP CHOOSE_DOCUMENT
CHOOSE_KEYBOARD:
		DISP 1,1,MSG01,L01,00011111B 		;从1行1列设置文件左框提示字,蓝底白字显示提示信息(必须紧跟彩色文本方式设置)
		DISP 1,30,MSG02,L02,00011111B     ;从1行30列设置文件右框提示字
CHOOSE_DOCUMENT:
		KDISP 0,29,LEFT_HEIGHT2,RIGHT_HEIGHT2,DOWN2,0bah ;从0行29列设置右框		
		
		LEA SI,BUF
		LEA DI,BUF2	
		CLI				;关中断
		CALL I8250		;辅串口初始化
		CALL I8259		;开放8259A辅串口中断
		CALL RD0B		;保存0BH中断向量
		CALL WR0B	    ;置换0BH中断向量
		STI				;开中断



CHECK2:
		CMP FLAG,1      ;标志位为-1就结束
		JE  OVER			;8086跳转范围有限,需加中转
		MOV DX,2FDH		;通信线状态寄存器
		IN AL,DX		;从端口读状态进AL
		TEST AL,20H     ;查询D5看发送保持寄存器是否空闲
		JZ CHECK2        ;不空闲就不给送数据
		
;---------------------------------------------------------------
;读取
		CMP CHOOSE,0
		JE  CHOOSE_KEYBOARD2
		READ_FILE
		JMP CHOOSE_DOCUMENT2
CHOOSE_KEYBOARD2:
		READ_KEY 0,0
CHOOSE_DOCUMENT2:		
		LEA SI,BUF
		LEA DI,BUF2
		
MAIN_WRITE:	
		CMP FLAG,1      ;标志位为-1就结束(中断过程碰到$自己会结束)
		JE	WRITEFILE

		MOV DX,2F8H
		MOV AL,[SI]		;把存起来的拿出来准备发送
		OUT DX,AL
		CMP AL,'$'		;没到最后就继续拿
		JE  WRITEFILE
MAIN_CHECK:
		MOV DX,2FDH		;通信线状态寄存器
		IN AL,DX		;读取端口状态进AL
		TEST AL,40H     ;查询D6看发送移位寄存器是否空了
		JZ MAIN_CHECK  		;空了就要送数据
		INC SI			;一个个拿出来
		JMP MAIN_WRITE      ;若读取一个字符就会显示一个,只要不检测到$,所以正常是一直循环状态(法1)
						;这里的方式是选择一起读取再显示则应该将字符存储起来再进行查询发送(法2)
						
WRITEFILE:
		
		CMP CHOOSE,0
		JE  CHOOSE_KEYBOARD3
		WRITE_FILE
		JMP CHOOSE_DOCUMENT3
CHOOSE_KEYBOARD3:
		WRITE_KEY
CHOOSE_DOCUMENT3:



OVER:           	  	;当数据发送完则执行结束程序
		CALL CLOSE     	;恢复系统0BH中断向量
		
		mov     ah,2             ;设置光标位置输出结束信息
        mov     bh,0             ;page= 0
        mov     dh,30             ;30行0列
        mov     dl,0	
        int     10h
		
		MOV AH,4CH		;DOS21H调用4CH号功能终止程序
		INT 21H
	 


;接收中断服务子程序
RECEIVE PROC
		PUSH AX
		PUSH DX
		PUSH DS
		MOV AX,DATA
		MOV DS,AX
		MOV DX,2F8H  	;接收缓冲寄存器
		IN  AL,DX  	

		CMP AL,'$'		;判断是否是$
		JE	NEXT			;是$结束,令标志位为1
		
		MOV [DI],AL		;将接收的字符读入BUF2
		INC DI

		
		JMP EXIT		;直接退出
NEXT: 	MOV FLAG,1
EXIT: 	MOV AL,20H
		OUT 20H,AL
		POP DS
		POP DX
		POP AX
		IRET    		;中断返回
RECEIVE ENDP


I8250 PROC			;全部使用辅串口
		MOV DX,2FBH	;通信线寄存器
		MOV AL,80H	;设置寻址位1,表示访问除数寄存器
		OUT DX,AL	;数据进端口
		
		MOV BX,DIVISION_FACTOR		
		MOV DX,2F9H	;除数寄存器高8位
		MOV AL,BH	;数据设为0
		OUT DX,AL
		
		MOV DX,2F8H	;除数寄存器低8位
		MOV AL,BL	;这里设置分频系数为0030H(2400波特)分频系数 = 1843200 / (16 * 波特率)
		OUT DX,AL
		
		MOV DX,2FBH	;通信线控制寄存器
		MOV AL,FORMAT	;数据格式由用户选择,校验+停止位+数据位
		OUT DX,AL
		MOV DX,2F9H	;中断允许寄存器
		MOV AL,01H	;允许接收到一帧数据后内部提出接收中断请求
		OUT DX,AL
		MOV DX,2FCH	;MODEM控制寄存器
		MOV AL,18H	;内环+开放中断+无联络(00011000B)
		OUT DX,AL
		RET			;自动返回原位置		
I8250 ENDP



;开放主8259辅串口中断  D3位
I8259 PROC
		IN  AL,21H
		AND AL,11110111B
		OUT 21H,AL
		RET     		;段内返回
I8259 ENDP

;保存0BH中断向量(转移系统0BH中断向量)
RD0B PROC
		MOV AX,350BH	;35H读中断向量,转移0BH型中断向量<=>MOV AH,35H;MOV AL,0BH
		INT 21H
		MOV WORD PTR OLD0B,BX	;偏移地址保存到变量
		MOV WORD PTR OLD0B+2,ES	;段基址保存到变量
		RET   			;段内返回
RD0B ENDP

;置换0BH中断向量(写入用户0BH中断向量)
WR0B PROC
		PUSH DS
		MOV AX,CODE
		MOV DS,AX
		MOV DX,OFFSET RECEIVE
		MOV AX,250BH	;25H为写中断向量
		INT 21H
		POP DS
		RET			
WR0B ENDP
	 
;恢复系统0BH中断向量 
CLOSE PROC
		IN AL,21H
		OR AL,08H		;00001000B将中断屏蔽寄存器的辅串口中断屏蔽字置1,关闭8259辅串口中断
		OUT 21H,AL
		MOV AX,250BH
		MOV DX,WORD PTR OLD0B	;偏移地址从变量里拿出来
		MOV DS,WORD PTR OLD0B+2	;段基址从变量里拿出来
		INT 21H
		RET	
CLOSE ENDP

CODE ENDS
	 END BEG

运行结果

如图,以偶检验、2位停止位、8位数据位为例,波特率输入3333333333,超出了给定的范围,所以自动报错并重新输入3333

如图所示,3333后输入了错误字符也会报错,正确输入后继续选择键盘功能。

任意输入字符,经过一定的时间后,字符被完全接收并显示在了右窗口中。

如果选择文件功能,左边会显示读取到的字符,右边会显示状态,在相应的文件里也会输出字符。


文章作者: Alex Lee
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Alex Lee !
评论
  目录