实验6 I/O程序设计

来源:互联网 发布:武汉知豆电动汽车4s店 编辑:程序博客网 时间:2024/06/04 19:44

实验6  I/O程序设计

输入/输出是对外部设备进行控制和数据交换的过程。CPU与外设之间的信息交换,是通过接口电路中的I/O端口进行的。I/O程序设计必须使用INOUT指令,对端口进行读、写操作。

6.1 读取实时时钟

现代微机中都包含一个实时钟,它自动地定时更新时间与日期。实时钟信息(年、月、日、时、分、秒)保存在CMOS RAM中,在系统关机后,时钟电路自带的后备电池继续向RAM供电,并且继续更新时间与日期。

1. 实时钟信息的内容

实时钟信息存储在CMOS RAM最前面的14个字节,而其他的字节用于保存微机的配置信息,例如系统的内存容量、软盘、硬盘类型等。实时钟信息的格式如表6-1所示。

6-1  实时钟信息的格式

位移

内容

取值范围

 

0

00H~59H

BCD码,如10H表示10秒,59H表示59秒。

1

报警秒

00H~59H

BCD码,同上。设为0C0H~0FFH时,不报警。

2

00H~59H

BCD码,如06H表示06分,45H表示45分。

3

报警分

00H~59H

BCD码,同上。设为0C0H~0FFH时,不报警。

4

小时

00H~23H

BCD码,如12H表示12点,16H表示16点。

5

报警小时

00H~23H

BCD码,同上。设为0C0H~0FFH时,不报警。

6

星期几

1~7

1=星期日,2=星期一……7=星期六

7

01H~31H

BCD码,如31H表示31日。

8

01H~12H

BCD码,如04H表示4月,12H表示12月。

9

00H~99H

BCD码,年份的后面2位。如03H表示2003年。

10

状态/控制A

 

Bit 7=1,正在刷新。Bit 6~4=010b,基准频率=32768Hz

11

状态/控制B

 

Bit 1=1,使用24小时制。Bit 0=1,使用夏时制。

12

状态C

 

Bit 7=1,产生中断请求。Bit 6~4,中断原因。

13

状态D

 

Bit 7=1CMOS电池正常。

其中,报警信息记录在135单元中。若这些单元的内容为0C0~0FFH时,则报警功能无效。当实时钟到达设定报警时间时,立即产生一个中断信号,并设置状态C中的Bit51

2. 实时钟信息的读取方法

CMOS RAM的访问必须用I/O指令来完成。接口电路设置了两个寄存器,其I/O地址分别是70H71H70HRTC地址寄存器,71HRTC数据寄存器。要访问某个CMOS RAM单元时,CPU先用OUT指令将该单元的地址(00H~7FH)写入到70H端口中,再使用IN指令读入该CMOS RAM单元的内容。如图6-1所示。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

I/O地址

 

70H

 

 

 

 

RTC地址寄存器

 

RTC地址

 

CPU

数据

 

 

 

 

CMOS

 

 

71H

 

RAM

 

/

 

RTC数据寄存器

 

数据

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

RTC接口电路

 

 

 

6-1  RTC输入/输出接口

3. 在用户模式下执行INOUT指令

Windows NT/2000/XP/2003中,应用程序运行最低特权级(CPL=3)。标志寄存器中的IO特权级被设置为3IOPL=3),而且任务状态段中I/O位图中所有位为1,这样,不允许应用程序使用I/O指令。如果在程序中执行IO指令,会产生异常,程序被中止运行。

为避免该问题,可以将giveio.sys驱动程序装入内核,应用程序打开//./giveio设备文件,驱动程序就将任务状态段中I/O位图的全部位设为0。这样,在应用程序中就可以执行INOUT指令了。

giveio.sys驱动程序装入内核的命令为:

c:/asm/bin/allowio -load c:/asm/bin/giveio.sys

从内核中卸载giveio.sys驱动程序的命令为:

c:/asm/bin/allowio -unload

;程序清单:cmosram.asm(读取实时钟)

.386

.model flat,stdcall

option casemap:none

includelib      msvcrt.lib

includelib      kernel32.lib

printf          PROTO C format:ptr sbyte,:vararg

CreateFileA     PROTO stdcall,

                lpFileName:NEAR32, dwDesiredAccess:dword, dwShareMode:dword,

                lpSecurityAttributes:NEAR32, dwCreationDisposition:dword,

                dwFlagsAndAttributes:dword, hTemplateFile:dword 

CloseHandle     PROTO stdcall, hObject:dword

; 以下是程序中用到的一些常量

GENERIC_READ            EQU     80000000h

OPEN_EXISTING           EQU     3

FILE_ATTRIBUTE_NORMAL   EQU     00000080h

INVALID_HANDLE_VALUE    EQU     -1

NULL                    EQU     0

.data

driverStr       byte    "//./giveio", 0         ; 设备文件名

cmosIndex       byte    9, 8, 7, 4, 2, 0        ; /////秒的索引

cmosData        dword   6 dup (?)               ; /////

fmtStr          byte    '20%02d/%02d/%02d %02d:%02d:%02d', 0ah, 0

.code

AllowIo         proc

                pushfd                                  ; 标志寄存器压栈

                pop     eax                             ; 标志寄存器 -> EAX

                and     eax, 00003000h                  ; 取其第12,13

                cmp     eax, 00003000h                  ; IOPL是否等于3 ?

                jnz     IOPLZero           ; IOPL != 3, 需借助于驱动程序

                                           ; IOPL = 3, 程序可以执行I/O

                mov     eax, 1                          ; 返回1, 表示TRUE

                ret

IOPLZero:

                invoke  CreateFileA,                    ; 打开文件

                        offset driverStr,               ; 文件名

                        GENERIC_READ,                   ; 只读方式打开

                        0, 

                        NULL, 

                        OPEN_EXISTING,                  ; 打开已存在的文件

                        0, 

                        0

                cmp     eax, INVALID_HANDLE_VALUE

                jz      OpenFail                        ; 不能打开, 退出

                invoke  CloseHandle, eax                ; 关闭文件

                mov     eax, 1                          ; 返回1, 表示TRUE

                ret

OpenFail:

                mov     eax, 0                          ; 返回0, 表示FALSE

                ret

AllowIo         endp

start:

                call    AllowIo                         ; 是否可以进行I/O?

                cmp     eax, 0                          ; eax=0,不能进行I/O

                jz      ExitIo                          ; 退出

                mov     ecx, 6                          ; 一共要读取6个字节

                mov     esi, 0                          ; 数组下标初始化为0

GetCmos:       

                mov     al, cmosIndex[esi]              ; 取得索引

                out     70h, al                         ; 设置索引

                in      al, 71h                         ; 读取数据

                ; 读取到的数据是BCD码格式

                ; 例如 al=56h 表示 56

                mov     ah, al                          ; al->ah, ah=56h

                shr     ah, 4                           ; 取高4位到ah

                and     al, 0fh                         ; al4位清零,

                aad                                     ; ah*10+al->al

                mov     byte ptr cmosData[esi*4], al    ; 保存

                inc     esi                             ; 索引加1

                loop    GetCmos                         ; 依次取得

                                                        ; /////

                invoke  printf,                         ; 显示结果

                        offset fmtStr,

                        cmosData[0*4],                  ;

                        cmosData[1*4],                  ;

                        cmosData[2*4],                  ;

                        cmosData[3*4],                  ;

                        cmosData[4*4],                  ;

                        cmosData[5*4]                   ;

ExitIo:        

                ret

end             start

6.2 直接读取硬盘扇区

硬盘一般采取IDE(Integrated Drive Electronics)接口,硬盘控制器与硬盘盘体集成在一起。光驱也大多采取IDE接口。IDE连接器是一种40针连接器,针脚间距0.1英寸(2.54mm),通常有“键控”以防止安装时颠倒方向,如图6-2所示。

  

6-2  IDE电缆和硬盘接口

1. IDE控制器

PC机一般都集成了2IDE控制器,主板上具有2IDE插槽(PrimarySecondary,主/)

这些ATA控制器所使用的地址一般也是固定的。主控制器使用1F0H~1F7H3F6H,而从控制器使用170H~177H376H

在“开始”“设置”“控制面板”“系统”“硬件”“设备管理器”中,用菜单“查看”“按类型排序资源”,展开“输入/输出(I/O)”,可以看到图6-3所示的IDE控制器所占用的I/O地址。

 

6-3  IDE控制器所占用的I/O地址

 

2. 主盘和从盘

一条IDE电缆的一端连接到主板(或接口卡)的IDE控制器上,另一端最多可以连接2个硬盘。这2个硬盘中,其中的一个作为主盘(Master driver, 驱动器0),而另一个作为从盘(Slave drive, 驱动器1)。可以通过设置硬盘上的跳线来确定它作为主盘还是从盘,也可以由电缆的连接位置来决定。

两个硬盘连接到同一条电缆,也就是同一个IDE通道(Channel)上,它们在系统中占用相同的I/O地址。

3. 数据传输模式

ATAPI标准定义了IDE控制器与硬盘(或光驱)设备的连接接口。ATAPI是硬盘、CD/DVD设备采用的工业标准。

硬盘有2种数据传输模式:

1 PIO模式

PIO模式即可编程I/O模式,由CPU执行IN/OUT指令访问ATA控制器的端口,将数据从硬盘读出或者写入到硬盘。采用PIO模式时,最快只能达到16.6MB/s的传输率。采用PIO模式,每次传输16位,即2个字节。

2DMA传输模式

DMA模式下,数据的传送在ATA控制器的端口和内存之间直接进行,不需要通过CPU,因此能使CPU的负担减轻,数据传输率也能进一步提高。

单字DMA(DMA single word)每次传输8位,多字DMA(DMA single word)每次传输16位。

4. 扇区编址模式

每个扇区的大小为512字节。主机在读写硬盘时,需要指定它要读写的是哪一个扇区。有两种方式来表示扇区的地址,它们是CHS编址模式和LBA编址模式。

1CHS编址模式

CHS编址方式中,扇区的地址由三个部分组成:柱面号(Cylinder)、磁头号(Head)和扇区号(Sector)。扇区号从1开始,而柱面号和磁头号从0开始。所以,0柱面0磁头1扇区是整个硬盘的第1个扇区。

2LBA编址模式

LBA编址方式中,扇区的地址就是这个扇区的序号,用一个整数来表示。例如,0柱面0磁头1扇区的序号为0LBA的编号从0开始。

LBA编址模式有2种:28位地址和48位地址,扇区序号分别用28位或48位整数来表示。在28LBA编址模式下,扇区序号的最大值为228,能够使用的最大容量为:228×512=137438953472字节=128GB

在对硬盘进行编程时,通过寄存器中的LBA位来区分编址模式。LBA=0时,表示使用CHS编址模式;LBA=1时,表示使用LBA编址模式。

5. ATA设备寄存器

主机对硬盘的读写操作是通过硬盘上的两组寄存器来实现,第一组寄存器中占用8个端口(1F0H~1F7H170~177H),第二组寄存器中只有一个有效端口(3F6H376H)。这些寄存器如表6-2所示。例如,连接在主IDE控制器上的硬盘,它的命令寄存器的地址为1F7H

6-2  ATA设备寄存器的I/O地址

主控制器

从控制器

寄存器

  

1F0~1F1H

170~171H

数据寄存器

读写,主机与ATA设备交换数据

1F1H

171H

特征寄存器

写,只对部分命令有效

1F1H

171H

错误寄存器

读,状态寄存器中ERR=1时有效

1F2H

172H

扇区数寄存器

读写,要求读/写的扇区数

1F3H

173H

扇区号寄存器

读写,扇区号或8LBA

1F4H

174H

低位柱面寄存器

读写,磁道号或8LBA

1F5H

175H

高位柱面寄存器

读写,磁道号或8LBA

1F6H

176H

设备/磁头寄存器

读写,指定LBA模式、DEV位、磁头号及4LBA

1F7H

177H

状态寄存器

读,读出ATA设备的状态

1F7H

177H

命令寄存器

写,向ATA设备发出命令

3F6H

376H

设备控制寄存器

写,设置HOB、复位、nIEN

每一个控制器上可以连接两个ATA设备,这两个设备使用同样的I/O地址,在设备/磁头寄存器中用DEV位来区分这两个设备。DEV=0时,表示主盘;DEV=1时,表示从盘。

 (1) 命令寄存器(Command Register, 8)

将一个命令写入该寄存器后,设备就立即开始执行这个命令。执行命令所需的其它参数需要预先写入其它的寄存器。例如,要读一个扇区,可以将命令码(20h)写入命令寄存器,但之前必须把扇区号、要读取的扇区数目写入相应的寄存器。

ATA标准中规定了命令码,常用的命令码如表6-3所示。

6-3  ATA设备的部分命令码

命令码

 

 

20h

READ SECTOR(S)

读扇区,支持CHS模式和28LBA

24h

READ SECTOR(S) EXT

读扇区,支持CHS模式和48LBA

30h

WRITE SECTOR(S)

写扇区,支持CHS模式和28LBA

34h

WRITE SECTOR(S) EXT

写扇区,支持CHS模式和48LBA

C8h

READ DMA

采用DMA方式读扇区

CAh

WRITE DMA

采用DMA方式写扇区

ECh

IDENTIFY DEVICE

读取设备信息,包括型号、容量、序列号等

 (2) 状态寄存器(Status Register, 8)

包含了设备的当前状态信息。该寄存器的格式如图6-4所示。

7

6

5

4

3

2

1

0

BSY

DRDY

DF

DSC

DRQ

CORR

IDX

ERR

6-4 ATA状态寄存器

BSY代表是否忙(Busy)。当BSY1时,主机不能执行对设备寄存器的读写操作(除开状态寄存器外)。

DRDY代表设备是否准备好(Device Ready)。DRDY1表明设备现在可以执行一个命令。

DF代表Device Fault,该设备不支持状态寄存器的这一位,所以DF位总是为0

DSC代表Device Seek Complete,如果DSC位为1表明磁头定位已经完成。

DRQ代表Data RequestDRQ1表明设备已经准备好在主机和设备之间传输数据。当DRQ1时,主机不能向命令寄存器中写命令码。

CORR代表Corrected Data,该位总是为0

IDX代表Index

ERR代表是否有错误(Error),ERR位为1表明在执行前面一条命令的过程中出现了错误。错误的具体类型由错误寄存器(Error Register)的内容决定。

(3) 数据寄存器(Data Register, 16)

数据寄存器用于在主机和设备之间传输数据块。当状态寄存器中的DRQ1时,数据寄存器中包含的数据是有效的。

(4) 设备控制寄存器(Device Control Register, 8位)

设备控制寄存器的格式如图6-5所示。

7

6

5

4

3

2

1

0

HOB

保留

保留

保留

保留

SRST

nIEN

0

6-5  ATA控制寄存器

HOB=1时,表示要将扇区数的15~8位、LBA31~24位、39~32位、47~40位写入扇区数寄存器、扇区号寄存器、低位柱面寄存器和高位柱面寄存器。写入完毕后,应将HOB设为0

SRST=1,表示复位该IDE通道。

nIEN=1,表示禁止设备产生中断。=0,允许设备产生中断。

 (5) 扇区数寄存器(Sector Count Register, 8位)

包含要读写的扇区的个数。在28LBA模式或者CHS模式下,一次命令可以传送1~256个扇区。写入0代表传送256个扇区。在48LBA模式下,一次命令可以传送1~65536个扇区。需要写入2次,HOB置为0时写入低8位,HOB置为1时写入高8位。16位全部为0时代表要传送65536个扇区。

(6) 设备/磁头寄存器(Device/Head Register

设备/磁头寄存器的格式如图6-6所示。

7

6

5

4

3

2

1

0

保留

LBA

保留

DEV

磁头号

6-6  ATA设备/磁头寄存器

6LBA等于0时,表示使用CHS寻址模式。等于1时,表示使用LBA寻址模式。

4DEV等于0时,表示选择drive 0(主盘)。等于1时,选择drive 1(从盘)

28LBA模式时设备/磁头寄存器的低4位表示LBA27~24位。

(7) 高位柱面寄存器(Cylinder High Register, 8位)、低位柱面寄存器(Cylinder Low Register, 8位)、扇区号寄存器(Sector Number Register, 8位)

这些寄存器表示扇区的地址。一共有3种情况:CHS模式、28LBA模式、48LBA模式。3种模式下,各寄存器的使用如表6-4所示。

6-4  扇区地址在寄存器中的表示

寄存器

CHS模式

28LBA模式

48LBA模式

1次写入(HOB=1)

2次写入(HOB=0)

高位柱面寄存器

磁道号高8

LBA23~16

LBA47~40

LBA23~16

低位柱面寄存器

磁道号低8

LBA15~8

LBA39~32

LBA15~8

扇区号寄存器

8位扇区号

LBA7~0

LBA31~24

LBA7~0

设备/磁头寄存器

4位磁头号

LBA27~24

未使用

未使用

扇区数寄存器

要读写的扇区数(8)

要读写的扇区数(8)

扇区数(8)

扇区数(8)

使用CHS模式时,设备/磁头寄存器中的LBA位设为0,将磁道号、扇区号、磁头号写入相应寄存器;使用28LBA模式时,LBA位设为1,将LBA28位写入上述4个寄存器;使用48LBA模式时,LBA位设为1,先将LBA的高24位(3个字节)分别写入前面3个寄存器,再写入LBA的低24位;

(8) 错误寄存器(Error Register, 8位)

包含设备上一次执行命令后的状态信息。错误寄存器中各位的意义与执行的命令有关。

(9) 特征寄存器(Feature Register, 8位)

只有使用少数特殊命令才使用到这个寄存器。

6. 采用PIO方式读取硬盘扇区

20h命令(READ SECTOR(S))是用PIO方式读取硬盘的命令。其格式如图6-7所示。

6-7  READ SECTOR(S)格式

按照以下步骤读取硬盘扇区:

(1)      复位硬盘,将SRST=1,再设为0SRST=1)。

(2)      读取状态寄存器,等待其BSY=0DRQ=0

(3)      按照图6-7,设置7个寄存器。

(4)      读取状态寄存器,等待其DRDY=1DSC=1DRQ=1

(5)      从数据寄存器中读取扇区内容。

在第(3)步之后,硬盘控制器将扇区数据读入硬盘内部的缓冲区,再将状态寄存器的DRDYDSCDRQ设为1。因此在第(4)步必须查询状态寄存器,等待硬盘控制器完成读扇区的操作。

在第(5)步,每次读写2个字节。每个扇区为512个字节,一个扇区执行256次读取操作。

;程序清单:hddio.asm(PIO方式读取硬盘扇区)

.386

.model flat,stdcall

option casemap:none

includelib      msvcrt.lib

includelib      kernel32.lib

printf          PROTO C format:ptr sbyte,:vararg

CreateFileA     PROTO stdcall,

                lpFileName:NEAR32,dwDesiredAccess:dword,dwShareMode:dword,

                lpSecurityAttributes:NEAR32,dwCreationDisposition:dword,

                dwFlagsAndAttributes:dword,hTemplateFile:dword 

CloseHandle     PROTO stdcall,hObject:dword

; 以下是程序中用到的一些常量

GENERIC_READ            EQU     80000000h

OPEN_EXISTING           EQU     3

FILE_ATTRIBUTE_NORMAL   EQU     00000080h

INvalID_HANDLE_valUE    EQU     -1

NULL                    EQU     0

 

pio_base_addr1          EQU     01F0H

pio_base_addr2          EQU     03F0H

numSect                 EQU     1               ; 读入的扇区个数

lbaSector               EQU     0               ; 扇区编号LBA

 

.data                       

driverStr       byte    "//./giveio",0          ; 设备文件名

errStr          byte    'Load giveio.sys first!',0ah,0

_Buffer         byte    512*numSect dup (2)     ; 用于保存读入的扇区数据

_BufferLen      equ     $-_Buffer

szFmt           byte    '%02X ',0

szCRLF          byte    0dh, 0ah, 0

 

outx            MACRO   port,val

                mov     dx,port

                mov     al,val

                out     dx,al

                ENDM

 

inx             MACRO   port

                mov     dx,port

                in      al,dx

                ENDM

.code

AllowIo         proc

                invoke  CreateFileA,                   ; 打开文件

                        offset driverStr,              ; 文件名

                        GENERIC_READ,                  ; 只读方式打开

                        0,

                        NULL,

                        OPEN_EXISTING,                 ; 打开已存在的文件

                        0,

                        0

                cmp     eax,INvalID_HANDLE_valUE

                jz      OpenFail                       ; 不能打开,退出

                invoke  CloseHandle,eax                ; 关闭文件

                mov     eax,1                          ; 返回1,表示TRUE

                ret

OpenFail:

                mov     eax,0                          ; 返回0,表示FALSE

                ret

AllowIo         endp

start:

                call    AllowIo                        ; 是否可以进行I/O?

                cmp     eax,0                          ; eax=0,不能进行I/O

                jnz     AllowIoLoadOk                  ; 退出

                invoke  printf,offset errStr           ; 显示提示信息

                ret

AllowIoLoadOk:    

 

 

                ; SRST=1, 复位硬盘

                outx    pio_base_addr2+6,04h           ; SRST=1

 

                outx    pio_base_addr2+6,00h           ; SRST=0

 

                ; 等待,直到BSY=0而且DRQ=0.

waitReady:

                inx     pio_base_addr1+7               ; read primary status

                and     al,10001000b                   ; busy,or data request

                jnz     waitReady

 

                ; 设置feature port寄存器

                outx    pio_base_addr1+1,00h

                ; 设置sector count寄存器, 要读写的扇区数

                outx    pio_base_addr1+2,numSect

                ; 设置sector numbert寄存器, LBA(7:0)

                outx    pio_base_addr1+3,((lbaSector shr 0) and 0ffh)

                ; 设置cylinder low寄存器,   LBA(15:8)

                outx    pio_base_addr1+4,((lbaSector shr 8) and 0ffh)   ;

                ; 设置cylinder high寄存器,  LBA(23:16)

                outx    pio_base_addr1+5,((lbaSector shr 16) and 0ffh)  ;

                ; 设置device/head寄存器, LBA=1, DEV=0, LBA(27:24)

                outx    pio_base_addr1+6,01000000b or ((lbaSector shr 24) and 0fh)  

                ; 设置command寄存器, 20h表示READ SECTOR(S)

                outx    pio_base_addr1+7,020h

 

                ; 等待,直到DRDY=1, DSC=1, DRQ=1.

waitHDD:               

                inx     pio_base_addr1+7

                cmp     al,01011000b

                jnz     waitHDD

 

                ; 每个扇区读入256次,每次读入2个字节,顺序保存在_Buffer

                lea     edi,_Buffer

                cld

                mov     ecx,numSect*256

Read2Bytes:

                mov     dx,pio_base_addr1

                in      ax,dx

                stosw

                loop    Read2Bytes

 

                ; 显示_Buffer内容

                call    DisplayBuffer

                               

                ret

                

DisplayBuffer   proc

                lea     esi,_Buffer

                mov     ecx,_BufferLen

                xor     eax,eax

                xor     ebp,ebp

                cld

DisplayByte:

                push    ecx               

                lodsb

                invoke  printf,offset szFmt,eax

                inc     ebp

                test    ebp, 0fh

                jnz     DisplayCRLF

                invoke  printf,offset szCRLF

DisplayCRLF:              

                pop     ecx

                loop    DisplayByte

                ret

DisplayBuffer   endp               

end             start

运行该程序之前,必须装入giveio.sys

allowio -load c:/asm/bin/giveio.sys

程序中定义了outxinx两个宏,调用这些宏能够方便地执行OUTIN操作。

hddio.exe读入硬盘的0扇区,运行程序的结果为:

C:/asm/sample>hddio

33 C0 8E D0 BC 00 7C FB 50 07 50 1F FC BE 1B 7C

BF 1B 06 50 57 B9 E5 01 F3 A4 CB BD BE 07 B1 04

38 6E 00 7C 09 75 13 83 C5 10 E2 F4 CD 18 8B F5

83 C6 10 49 74 19 38 2C 74 F6 A0 B5 07 B4 07 8B

F0 AC 3C 00 74 FC BB 07 00 B4 0E CD 10 EB F2 88

4E 10 E8 46 00 73 2A FE 46 10 80 7E 04 0B 74 0B

80 7E 04 0C 74 05 A0 B6 07 75 D2 80 46 02 06 83

46 08 06 83 56 0A 00 E8 21 00 73 05 A0 B6 07 EB

BC 81 3E FE 7D 55 AA 74 0B 80 7E 10 00 74 C8 A0

B7 07 EB A9 8B FC 1E 57 8B F5 CB BF 05 00 8A 56

00 B4 08 CD 13 72 23 8A C1 24 3F 98 8A DE 8A FC

43 F7 E3 8B D1 86 D6 B1 06 D2 EE 42 F7 E2 39 56

0A 77 23 72 05 39 46 08 73 1C B8 01 02 BB 00 7C

8B 4E 02 8B 56 00 CD 13 73 51 4F 74 4E 32 E4 8A

56 00 CD 13 EB E4 8A 56 00 60 BB AA 55 B4 41 CD

13 72 36 81 FB 55 AA 75 30 F6 C1 01 74 2B 61 60

6A 00 6A 00 FF 76 0A FF 76 08 6A 00 68 00 7C 6A

01 6A 10 B4 42 8B F4 CD 13 61 61 73 0E 4F 74 0B

32 E4 8A 56 00 CD 13 EB D6 61 F9 C3 49 6E 76 61

6C 69 64 20 70 61 72 74 69 74 69 6F 6E 20 74 61

62 6C 65 00 45 72 72 6F 72 20 6C 6F 61 64 69 6E

67 20 6F 70 65 72 61 74 69 6E 67 20 73 79 73 74

65 6D 00 4D 69 73 73 69 6E 67 20 6F 70 65 72 61

74 69 6E 67 20 73 79 73 74 65 6D 00 00 00 00 00

00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

00 00 00 00 00 2C 44 63 C8 6D 06 54 00 00 80 01

01 00 06 FE 3F FE 3F 00 00 00 00 82 3E 00 00 00

01 FF 07 FE FF FF 3F 82 3E 00 29 53 7E 05 00 00

C1 FF 05 FE FF FF 68 D5 BC 05 57 68 3F 01 00 00

00 00 00 00 00 00 00 00 00 00 00 00 00 00 55 AA

可以修改程序中的numSectlbaSector,读入多个硬盘扇区,以及扇区编号。

7. 验证扇区数据

为验证程序是否读入了正确的扇区,可以执行WinHex等磁盘编辑软件。

运行WinHex时,从菜单中选择Tools→Open Disk…,选择第1个物理硬盘(Physical Media),如图6-8所示。

 

6-8 选择物理磁盘

之后,再按Ctrl+G,或者从菜单中选择Position→Go To Sector…,出现图6-9所示的对话框,在Logical中的Sector后面输入扇区序号(即LBA)。

 

6-9 输入LBA

WinHex显示出扇区中的数据,如图6-10所示。与hddio输出的结果相对比,确认hddio是否正确。

 

6-10 显示扇区的内容

6.3  双机全双工串行通信

串行接口是微机应用系统常用的接口,也被称做COM口。微机中一般都配备了两个COM口,称为COM1COM2。部分微机还有另外两个串口COM3COM4

1. RS 232

计算机的串行接口使用RS 232-C标准的9芯连接器,如图6-11所示。

 

6-11 9芯连接器

6-3列出了计算机中常用的RS-232-C信号的名称、功能。在通信中,Modem起着传输信号的作用,是一种数据通信设备(Data Communication Equipment),简称DCE。接收设备和发送设备称为数据终端设备(Data Terminal Equipment),简称DTE

6-3 常用RS-232-C信号

引脚编号

名 称

功 能

方 向

1

DCD

接收线路信号检测

DCEDTE

2

RxD

接收数据

DCEDTE

3

TxD

发送数据

DTEDCE

4

DTR

DTE就绪

DTEDCE

5

SG

信号地

信号公共地

6

DSR

DCE就绪

DCEDTE

7

RTS

请求发送

DTEDCE

8

CTS

允许/清除发送

DCEDTE

9

RI

振铃指示

DCEDTE

这些信号的作用为:

·TXD:输出,串行数据发送引脚,将串行数据从数据终端(CPU方面)发出,传送到数据装置(调制解调或外设)。

·RXD:输入,串行数据接收引脚,数据终端接收从数据装置传来的串行数据。

·RTS#:输出,数据终端请求发送。低电平时有效,RTS#为低,表示数据终端准备好发送数据,是计算机一方送往外设或调制解调的信号,表示计算机请求发送数据。

·CTS#:输入,数据设备清除请求发送(允许发送)信号,低有效,该信号来源于数据装置,它和RTS#组成一对联络信号,是对RTS#的响应,表示外设或调制解调准备好,允许计算机发送数据。当CTS有效时,计算机才能向外设发送数据。

·DSR#:输入,数据设备准备好,是外设送往CPU方面的信号,低电平有效,表示当前外部设备已经准备好。它和DTR#组成一对联络信号。

·GND:地信号,是数据终端和数据装置的公共地。

·DCD#:输入,载波检测信号,来源于调制解调器,低电平有效,它指示调制解调器已经建立了有效的连接。只有在DCD#的有效状态下,CPU才能开始传送数据。

·DTR#:输出,数据终端准备好,低有效,是由计算机送往外设的信号,通知外部设备CPU当前已经准备就绪。

·RI:输入,振铃指示信号,高有效,当调制解调器在线路上检测到一个响铃信号,便使RI信号有效。该信号用来通知CPU有一个输入的呼叫。

在传输距离较近时,不必使用调制解调器,可以直接将两台计算机(或数据终端)RS-232接口相连接。为了变换信息,TxDRxD应当交叉连接,如图6-12所示。

 

6-12 计算机串口之间的连接

1. 串口设备寄存器

在“设备管理器”中,用鼠标双击COM1设备,选择“资源”,显示出为COM1设备分配的端口地址(3F8H~3FFH)和中断号(IRQ4),如图6-13所示。

 

6-13 COM1设备所占用的资源

微机的串口设备通常采用16550芯片或兼容产品。16550内部有12个可访问的寄存器,但只为它分配了8I/O地址(A2A1A0=000~111)。表6-3列出了这些寄存器和I/O地址的对应关系。

DLAB是线路控制寄存器(LCR)的D7位。要设置波特率除数寄存器时,先设置线路控制寄存器中的DLAB1,然后再写入波特率除数寄存器的低字节和高字节。低字节写入到A2A1A0=000b的地址;高字节写入到A2A1A0=001b的地址。写入波特率除数寄存器后,应该将DLAB设为0

A2A1A0=010b对应于中断识别寄存器(IER)和FIFO控制寄存器(FCR)。CPU写入该地址的字节作为FIFO控制寄存器;而CPU从该地址读入的字节是中断识别寄存器。

6-3 16550内部寄存器地址

A2A1A0

被访问的寄存器

000

DLAB=0:接收缓冲寄存器RBR(),发送保持寄存器THR()

DLAB=1:波特率除数寄存器DLL(低字节)

001

DLAB=0:中断允许寄存器IER

DLAB=1:波特率除数寄存器DLM(高字节)

010

中断识别寄存器IIR()FIFO控制寄存器FCR()

011

线路控制寄存器LCR

100

MODEM控制寄存器MCR

101

线路状态寄存器LSR

110

MODEM状态寄存器MSR

111

Scratch寄存器SCR

1)发送保持寄存器(Transmitter Holding RegisterTHR

CPU将要发送的数据字节输出到这个寄存器,串行接口电路就将这个数据字节转换成串行信号传送给对方。数据字节的有效位可以是5位、6位、7位或8位。

发送时,CPU将待发送的字符写到THR后,然后进入发送移位寄存器,在发送时钟的作用下,按起始位、数据位、校验位、停止位的顺序,从SOUT引脚逐位输出。一旦THR的内容送到发送移位寄存器TSR后,THR变空,就使LSRTHRE位置1,产生中断请求。THRE位置1时,表示CPU可以发送下一个字符。CPUTHR写入一个字符后,THRE0

2)接收缓冲寄存器(Receiver Buffer RegisterRBR

串行接口电路接收到发送方的一个数据字节后,CPU可以从这个寄存器读取这个数据字节。

接收时,串行数据在接收时钟作用下,从SIN引脚以串行移位的方式输入到接收移位寄存器RSR,然后由RSR并行输入到接收缓冲寄存器RBR,一旦RBR变满,就使LSRDR位置1DR位为1时,表示CPU可以读取数据字符。CPURBR读取一个字符后,DR0

3)线路状态寄存器(Line Status RegisterLSR

CPU读取LSR来取得串行数据传送的状态。寄存器中各位的定义如图6-14所示。

7

6

5

4

3

2

1

0

RFE

TEMT

THRE

BI

FE

PE

OE

DR

 

RFE

接收FIFO出错(只对16550有效)。等于1时,表示接收FIFO出错

TEMT

发送移位器空。等于1时,表示发送器空。

THRE

发送保持寄存器空。等于1时,表示THR中的数据字节已被取走。数据字节写入到THR时,此位被清零。

BI

间断识别指示。等于1时,接收线SIN空闲的时间超过了传送一个字符的时间,对方发送过程出现了间断。

FE

帧格式错。等于1时,表示传输的数据格式错误。

PE

奇偶校验错。等于1时,表示奇偶校验错误。

OE

覆盖错。等于1时,表示接收到有效的数据但被丢失。

DR

接收缓冲寄存器有效。等于1时,已接收到一个数据字节放入到RBR中。读取RBR后,此位被清零。

6-14  线路状态寄存器LSR的格式

LSR的第0DR和第5THRE是最基本的指示位。只有DR=1时,CPURBR中读取的数据字节才是从发送方发出的有效数据字节。DR=0时,CPURBR中读出的是“旧的”数据字节或无效的数据字节。只有THRE=1时,CPU写到THR的数据字节才会被正确地发送出去。THRE=0时,THR中的数据字节还没有被取走,不能向其中写入新的数据字节。

8250的基地址为3F8H(对应于A2A1A0=000b),那么发送保持寄存器、接收缓冲寄存器的地址为3F8H,而线路状态寄存器的地址为3FDHA2A1A0=101b)。

4)线路控制寄存器(Line Control Register, LCR

LCR主要用来指定异步通信数据格式,同时它的最高位DLAB用来指定允许访问除数寄存器,如图6-15所示。

7

6

5

4

3

2

1

0

DLAB

SB

SP

EPS

PEN

STB

WLS1

WLS0

 

WLS1

WLS0

WLS1 WLS0=00b, 字符长度为5位; =01b, 字符长度为6位;

=10b, 字符长度为7位; =11b, 字符长度为8位。

STB

=0,停止位长度为1位;=11.5位或2位(字符长度为5位时采用1.5位停止位,字符长度为678位时采用2位停止位)。

PEN

=0, 不使用奇偶校验。发送接收时没有校验位。

EPS

=0, 奇校验; =1, 偶校验。EP=0时,此位无效。

SP

=1时,奇偶校验位固定为01=0时,根据字符计算校验位。

SB

=1时,发送线SOUT设为0并保持至少一个字符的时间,即产生一个间断,进入发送间断状态。=0时,退出间断状态。

DLAB

=1,访问除数寄存器;DLAB=0,访问其他寄存器。

6-15  线路控制寄存器LCR的格式

5SPSTICK PARITY)是固定校验选择位。当PEN=1(有奇偶校验)时,若SP=1,那么校验位完全根据EPS来设定,而不考虑发送字符的内容。PEN=1SP=1EPS=0时,校验位始终为1PEN=1SP=1EPS=1时,校验位始终为0SP设为1的目的是发送方把采用何种奇校验还是偶校验(即发送方采用的EPS值)告诉接收方。显然,在收发双方已约定奇偶校验方式的情况下,那么就不需要采用这种方式来沟通,应该使SP=0

5)除数锁存器(Divisor Latch LSB/MSB, DLL/DLM

8250芯片传输数据的速率是由除数锁存器控制的。外接的1.8432MHz基准时钟,通过除数寄存器给定的分频值,在8250内部产生不同的波特率,通过BAUDOUT#引脚输出到RCLK,控制接收传输速率。对一个已知的波特率,按照以下公式计算除数锁存器的内容:

f工作时钟 =  f基准时钟 ÷ 除数锁存器 = 波特率 × 16

这里f基准时钟= 1.8432MHz = 1843200Hz

除数锁存器 =  f基准时钟 ÷ (波特率 × 16) = 1843200 ÷ (波特率 × 16) = 115200 ÷波特率

6)中断允许寄存器(Interrupt Enable Register, IER

在以下4种情况都可以产生中断:接收数据出错中断、接收缓冲器满中断、发送保持寄存器空中断、以及来自MODEM的控制信号状态改变。

通过设置IER,可以允许或禁止上述中断。

7

6

5

4

3

2

1

0

0

0

0

0

EDSSI

ELSI

ETBEI

ERBFI

 

ERBFI

接收缓冲器满以后,是否产生中断。=0, 禁止;=1, 允许。

ETBEI

发送保持寄存器空以后,是否产生中断。=0, 禁止;=1, 允许。

ELSI

接收数据出错后,是否产生中断。=0, 禁止;=1, 允许。

EDSSI

MODEM的控制信号状态改变,是否产生中断。=0, 禁止;=1, 允许。

6-16  中断允许寄存器IER的格式

7Modem控制寄存器(Modem Control Register, MCR

MODEM控制寄存器MCR用来设置对MODEM的联络控制信号(DTRDSR)OUT1#OUT2#引脚的电平也由MCR的第23位来控制。LOOP=1常用于芯片自检。LOOP=1时,16550工作在自环路状态,从THR发送的字符被复制到RBR中,就像从Rx线路上接收到一个字符一样。

7

6

5

4

3

2

1

0

0

0

0

LOOP

OUT2

OUT1

RTS

DTR

 

DTR

=0, DTR#引脚输出高电平;=1, DTR#引脚输出低电平。

RTS

=0, RTS #引脚输出高电平;=1, RTS#引脚输出低电平。

OUT1

=0, OUT1#引脚输出高电平;=1, OUT1#引脚输出低电平。

OUT2

=0, OUT2#引脚输出高电平;=1, OUT2#引脚输出低电平。

LOOP

=0,正常模式;=1,诊断模式,自发自收。

6-17  Modem控制寄存器MCR的格式

下面的程序在两台计算机的COM1之间进行全双工串行通信。它从键盘上接收字符,发送到对方。发送的字符显示在屏幕上的第23行,接收的字符显示在屏幕上的第24行,输入Esc键后,程序结束。

;程序清单: comiox.asm (双机串行通信程序)

.model small                        ; 模式为small, 在虚拟8086模式下运行

.stack 1024                         ; 堆栈段, 大小为1024字节

.data                               ; 数据段

sendChar        db      0           ; 待发送字符

recvChar        db      0           ; 接收到的字符

nSendPos        db      0           ; 已发送字符的个数

nRecvPos        db      0           ; 已接收字符的个数

loopBack        db      0           ; 是否支持LoopBack

com_port_base   equ     3f8h        ; COM1设备寄存器基地址

.code

; 从键盘读取待发送字符

ReadKb  proc

        cmp     sendChar, 0         ; sendChar!=0, 表示该字符还未被发送,

        jnz     NoKey               ; 不检查键盘

 

        mov     ah, 1               ; AH=1, INT 16h

        int     16h                 ; 检查键盘是否输入了字符

        jz      NoKey               ; ZF=1, 未输入字符

 

        mov     ah, 0               ; AH=0, INT 16h

        int     16h                 ; 从键盘读取字符

        cmp     al, 0               ; AL=字符的ASCII

        jz      NoKey               ; AL=0, 扩展键

 

        mov     sendChar, al        ; sendChar=待发送字符

NoKey:

        ret

ReadKb  endp

; 通过COM1发送字符sendChar

SndChar proc                       

        mov     dx, com_port_base+5 ; DX=线路状态寄存器(LCR)地址

        in      al, dx              ; 读取线路状态寄存器

        test    al, 20h             ; 检测THRE

        jz      SndBusy             ; THRE=0, ZF=1, THR中的字符还没有被取走

 

        mov     al, sendChar        ; AL=待发送字符

        mov     dx, com_port_base+0 ; DX=发送保持寄存器(THR)地址

        out     dx, al              ; 将字符写入THR

 

        mov     ah,2                ; AH=09, INT 10h, 置光标

        mov     dh,23

        mov     dl,nSendPos

        mov     bh,0

        int     10h                 ; 将光标置于(23,nSendPos)

 

        mov     ah,09h              ; AH=09, INT 10h, 显示字符

        mov     al,sendChar

        mov     cx,1                ; 显示一次

        mov     bl,06h              ; 字符属性06h(颜色)

        mov     bh,0

        int     10h                 ; 在光标位置显示字符sendChar

 

        inc     nSendPos            ; nSendPos=已经发送字符的个数

SndBusy:

        ret

SndChar endp

; 通过COM1接收字符recvChar

RcvChar proc

        mov     dx, com_port_base+5 ; DX=线路状态寄存器(LCR)地址

        in      al, dx              ; 读取线路状态寄存器

        test    al, 01h             ; 检测DR

        jz      NoChar              ; DR=0, ZF=1, 没有可接收的字符

        test    al, 0eh             ; 检测FE,PE,OE位是否为1

        jnz     NoChar              ; 任何一位为1, 接收出错

 

        mov     dx, com_port_base+0 ; DX=接收缓冲寄存器(RBR)地址

        in      al, dx              ; RBR中读入字符

 

        mov     recvChar, al        ; 保存在recvChar

 

        mov     ah,2

        mov     dh,24

        mov     dl,nRecvPos

        mov     bh,0

        int     10h                 ; 将光标置于(24,nRecvPos)

       

        mov     ah,09h

        mov     al,recvChar

        mov     cx,1

        mov     bl,0Fh              ; 字符属性0Fh(颜色)

        mov     bh,0

        int     10h                 ; 在光标位置显示字符recvChar

 

        inc     nRecvPos            ; nRecvPos=已经接收字符的个数

 

        ret

NoChar:

        ret

RcvChar endp

; 对串口进行初始化

InitCom proc                       

        mov     dx, com_port_base+3 ; 线路控制寄存器地址

        mov     al, 80h        

        out     dx, al              ; DLAB=1

        mov     dx, com_port_base+0 ; 低位除数寄存器

        mov     al, 120             ; 9600波特率的除数低8

        out     dx, al 

        mov     al, 00

        inc     dx                  ; 高位除数寄存器

        out     dx, al

       

        mov     al, 00011011b       ; 偶校验、1位停止位, 8位数据位

        mov     dx, com_port_base+3 ; 线路控制寄存器地址

        out     dx, al 

                               

        mov     al, 0               ; 禁止中断

        mov     dx, 3f9h            ; 中断允许寄存器地址

        out     dx, al

 

        mov     al, 03h             ; RTS=1,DTR=1

        cmp     loopBack, 0

        jz      NotLoopBack

        or      al, 10h             ; Loopback位设为1

NotLoopBack:

        mov     dx, com_port_base+4 ; MODEM控制寄存器地址

        out     dx, al

 

        ret

InitCom endp

; 主程序

main    proc

        mov     ax, seg loopBack

        mov     ds, ax              ; 设置DS指向数据段

        mov     es, ax              ; 设置ES指向数据段

        mov     loopBack, 0         ; loopBack=1,可以在单机上调试此程序

        call    InitCom             ; COM1进行初始化

SendRecvChars:

        call    ReadKb              ; 从键盘读入字符

        cmp     sendChar, 0

        jz      ReadCom2            ; 未读入字符,不发送

       

        call    SndChar             ; 发送字符

        cmp     sendChar, 1bh

        jz      ExitLoop            ; 输入了Esc字符, 程序结束

 

        mov     sendChar,0          ; sendChar0, 避免重复发送

 

ReadCom2:

        call    RcvChar             ; 接收字符

 

        cmp     recvChar, 1bh

        jz      ExitLoop            ; 接收到Esc字符, 程序结束

 

        jmp     SendRecvChars       ; 循环执行

 

ExitLoop:

        mov     ax, 4c00h

        int     21h                 ; 程序退出

main    endp

end     main

该程序运行在虚拟8086模式或纯DOS下,编译过程与其他程序有所不同。执行以下两个命令可完成它的编译、连接:

masm comiox;

link comiox;

从键盘接收字符时,调用了INT 16H中断的1(AH=1)功能,当键盘没有输入字符时,程序可接收对方发送的字符。

将“mov  loopBack, 0中的0替换为1,则设定了16550的自环路模式,便于在单机下调试此程序。在自环路模式下,该程序能够接收到它自己发送出的字符。

6.4 实验题:读取硬盘序列号

操作系统是通过IDENTIFY DEVICE命令获得硬盘的型号、容量等信息的。这个命令还能够报告硬盘的序列号、硬盘固件程序版本等。

IDENTIFY DEVICE命令是一个PIO命令,返回512字节内容,其中重要的信息如图6-18所示。

偏移量

长度

内容

20

20

硬盘序列号字符串(前后2个字节需颠倒顺序)

46

8

硬盘固件版本号字符串(前后2个字节需颠倒顺序)

54

40

硬盘型号字符串(前后2个字节需颠倒顺序)

120

4

  硬盘容量(扇区数)

6-18  IDENTIFY DEVICE信息格式

要求:

1.    采用PIO方式,读取并显示硬盘的型号、序列号、固件版本、容量,例如:

Model number:       [ST960821A                               ]

Serial number:      [            3LF1VQ9E]

Firmware reversion: [3.02    ]

Total size:         [60011642880] bytes

2.    改进程序,使它能够读取从盘(DEV=1)的信息。

3.    改进程序,使它能够读取连接在第2IDE通道上的硬盘。

4.    改进程序,使它搜索计算机上连接的所有IDE硬盘。

5.    本程序是否能够获取SATA硬盘的信息,为什么?

可参考IdeDiskInfo程序示例,显示结果如图6-19所示。

 

6-19 IdeDiskInfo运行结果

 

 
原创粉丝点击