前置说明
本项目使用未来汇编和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
主体部分
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后输入了错误字符也会报错,正确输入后继续选择键盘功能。
任意输入字符,经过一定的时间后,字符被完全接收并显示在了右窗口中。
如果选择文件功能,左边会显示读取到的字符,右边会显示状态,在相应的文件里也会输出字符。