摘要
并行端口是最流行的接口选择。并行端口的8条数据输出线,有时还包括用于向计算机输入数据的控制和状态端口,对于一些复杂项目来说是不够的。有些项目需要更多的数据I/O线。本项目展示了如何通过接口ISA总线获得32条通用I/O线。虽然PCI总线可以作为接口实验的候选者,但其更高的速度和丰富的特性使得硬件和软件对初学者来说非常复杂。因此,本项目可以作为那些最终希望进行PCI总线接口实验的人的垫脚石。本项目对于那些希望使用PC、A/D和D/A转换器、微控制器编程器等制作数字示波器的人也很有用。
在详细说明如何接口ISA总线的整个项目之前,我们必须熟悉以下一些细节:
引脚描述
我们已经考虑将 X(n) 视为包含标准 ISA 卡上组件的一侧。同样,Y(n) 是包含焊接部分的一侧。请务必清楚上述约定。如果你连接的方式相反,将会损坏主板。
以下是最常用针脚的描述:
X 侧
D0 – D7(X2 到 X9 针脚)
这些是代表 8 位数据总线的 8 条数据线。
A0 – A19(X31 到 X12 针脚)
这些是包含地址位的 20 条地址线。此地址总线可以寻址 1MB (2^20=1024 Kbytes)。
AEN(X11 针脚)
DMA 控制器在 DMA 传输期间使用此线接管数据总线和地址总线。
Y 侧
GND(Y1、Y10、Y31 针脚)
这些针脚连接到计算机的地线。
+5V(Y3 针脚)
+5 伏直流输出。
-5V(Y5 针脚)
-5 伏直流输出。
+12V(Y9 针脚)
+12 伏直流输出。
-12V(Y7 针脚)
-12 伏直流输出。
MEMW(Y11 针脚)*
微处理器在写入内存时会将此线降低。
MEMR(Y12 针脚)*
微处理器在从内存读取时会将此线降低。
IOW(Y13 针脚)
微处理器在写入端口时会将此线降低。例如:当你执行 outportb(ADDRESS, BYTE) 时,此线变低。
IOR(Y14 针脚)
微处理器在从端口读取时会将此线降低。例如:当你执行 byte = inportb(ADDRESS) 时,此线变低。
DACK0 – DACK3(Y19、Y17、Y26、Y15 针脚)*
DMA 控制器在这些线上发出信号,让设备知道 DMA 拥有总线控制权。
DRQ1 – DRQ3(Y18、Y6、Y16 针脚)*
这些针脚允许外围板请求使用总线。
T/C(Y27 针脚)*
DMA 控制器设置此信号以通知外围设备已发送的编程字节数。
IRQ2 – IRQ7(Y4、Y25、Y24、Y23、Y22、Y21 针脚)*
中断信号。外围设备设置这些信号以请求微处理器的注意。
ALE(Y28 针脚)*
地址锁存使能信号。微处理器在内存或端口输入/输出操作期间使用此信号锁存较低的 16 位地址总线。
CLOCK(Y20 针脚)*
系统时钟。
OSC(Y30 针脚)*
这是一个高频时钟,可用于 I/O 板。
*:这些针脚不会在这个项目中使用。
从 ISA 总线上获取四条输出线
在详细介绍整个项目之前,先解释只处理四条 8 位输出线的部分。地址范围 0x338 至 0x33B 在我们的计算机中未被任何设备用于输入输出操作。
三个74LS138 IC处理地址解码部分。我们以一种方式连接地址线,使得每当地址线包含0x338到0x33B范围内的地址并且请求端口输出(IOW)时,在这些线(用绿色电线表示)中会产生一个短暂脉冲(CLOCK)。
每当74LS374收到一个时钟脉冲时,它会将数据总线上存在的8位数据锁存在内部。74LS245是一个三态八位总线收发器。它减少数据总线上的直流负载,并在需要时允许隔离数据总线。
为了确定I/O端口地址在这个项目中可用,我们检查Linux系统的/proc目录中的ioports内容。
[root@thelinuxmaniac~]# cat /proc/ioports
0000-001f: dma1
0020-0021: pic1
0040-0043: timer0
.......................
.......................
01f0-01f7: ide0
0378-037a : parport0
037b-037f : parport0
03c0-03df : vga+
.......................
.......................
从上面的输出可以看出,地址0x238-0x23B和0x338-0x33B没有被任何设备使用。这通常是大多数计算机的情况。然而,如果这些地址被某些设备占用,那么你需要更改三个74LS138集成电路(用于地址解码)的地址线布线。我们在此简要描述地址解码技术,以便你可以根据计算机中未使用的地址设置我们正在构建的I/O设备的唯一地址。
地址解码
我们使用74LS138,即3-8复用器进行地址解码。假设我们想将地址0x338-0x33B分配给四条8位输出线,将地址0x238-0x23B分配给四条8位输入线。这些地址的二进制等价如下:
0x338 0 0 0 0 0 0 1 1 0 0 1 1 1 0 0 0
0x339 0 0 0 0 0 0 1 1 0 0 1 1 1 0 0 1
0x33A 0 0 0 0 0 0 1 1 0 0 1 1 1 0 1 0
0x33B 0 0 0 0 0 0 1 1 0 0 1 1 1 0 1 1
0x238 0 0 0 0 0 0 1 0 0 0 1 1 1 0 0 0
0x239 0 0 0 0 0 0 1 0 0 0 1 1 1 0 0 1
0x23A 0 0 0 0 0 0 1 0 0 0 1 1 1 0 1 0
0x23B 0 0 0 0 0 0 1 0 0 0 1 1 1 0 1 1
A15 A14 A13 A12 A11 A10 A9 A8 A7 A6 A5 A4 A3 A2 A1 A0
我们观察到,地址线 A8、A1、A0 只有在所有八个地址的情况下才会改变。将线连接到 74LS138 IC 的整个过程就像解谜游戏一样。将剩余的电线(A15、A14、A13、A12、A11、A10、A9、A7、A6、A5、A4、A3、A2)连接到两个 74LS138,以便它们最终在这些线具有与我们的地址部分匹配的地址位(除了 A8、A1、A0)时给出低输出。现在我们将剩余的线(A8、A1、A0、A2)连接到第三个 74LS138。当此 IC 的所有 8 个输出被 NORed 与 IOR 和 IOW 使用 74LS02 以区分内存 IO 和端口 IO 地址后,用于选择对应于输入和输出地址的 74LS374 锁存器。
74LS138 真值表
G1 G2 C B A Y0 Y1 Y2 Y3 Y4 Y5 Y6 Y7
X H X X X H H H H H H H H
L X X X X H H H H H H H H
H L L L L L H H H H H H H
H L L L H H L H H H H H H
H L L H L H H L H H H H H
H L L H H H H H L H H H H
H L H L L H H H H L H H H
H L H L H H H H H H L H H
H L H H L H H H H H H L H
H L H H H H H H H H H H L
现在我们准备描述一个完整电路的功能,该电路将为我们提供32个通用I/O线。本项目中使用IC的描述如下:
74LS138 和 74LS139
解码器/去多路器。用于地址解码
74LS245
八路3态缓冲器/线路驱动器/线路接收器
74LS374
八路透明锁存器,具有3态输出;八路D型触发器,具有3态输出
74LS02
四个双输入NOR门
三个74LS138 IC和两个74LS02(双输入NOR门)用于地址解码。每当地址线上找到匹配时,第三个74LS138 IC(连接到两个74LS02 IC)的相应输出线Y(x)变为低电平。这些线以及IOW(和IOR)线连接到NOR门(74LS02),只有当两个输入同时变低时才会输出高电平。
因此,输出只有在以下情况下才会为高电平:
地址线中找到匹配
IOW或IOR线变低,表示端口IO操作。
请注意,如果我们不考虑第二种情况,我们的设备将在地址范围为0x238-0x23B和0x338-0x33B的内存IO操作中产生冲突。
我们可以在电路图中看到,NOR门的输出线连接到74LS374锁存器的时钟引脚(用绿色线表示)。因此,只有当上述两个条件同时满足时,时钟脉冲才会发送到相应的锁存器,数据总线中的数据被锁存并出现在输出线上。
编码
isa.c展示了一些简单的编码方法,以控制和测试在本项目中创建的设备的I/O线。
if(ioperm(OUTPUT_PORT,LENGTH+1,1))
{
...
}
if(ioperm(INPUT_PORT,LENGTH+1,1))
{
...
}
outb(data,port);
data = inb(port);
ioperm() 函数用于从内核获取访问指定端口的权限。sys/io.h 头文件中的 outb() 和 inb() 函数可以帮助我们向指定端口写入和读取数据。
一些调试技巧
仅凭阅读类似这样的文章并不容易使某些东西正常工作。在某一时刻,你将需要调试你的硬件。这些调试技巧将帮助你(就像它们帮助了我们一样)找到工作中的问题。你将需要一台万用表和一些 LED。记住,在调试过程中,我们学到的是,当你没有复杂的调试仪器时,LED 是调试此类硬件的最佳方式。我们在调试过程中发现的一些重要技巧如下:
使用万用表
万用表对检查 IC 间传递的零和一非常有用。验证每个 IC 到达预期输出。零将测量为 0.8V,一将测量为 3.8V(这会随计算机而变化)。如果地址解码未正常工作,或者在输出线上看到意外数据,可以使用此方法。不要将万用表探头直接连接到数据总线或地址线,总是将它连接到相应 IC 的输出端!
使用 LED
LED 可以非常有用来验证来自输出线的数据位;通过锁存器可见LED的点亮。要检查时钟脉冲是否到达正确的锁存器,将 LED 连接到 CLK 引脚,并将数据以连续循环发送到该端口,如下所示:
while(1){
outb(0x80,0x338);
}
isa.c
#include<stdio.h>
#include<sys/io.h>
#include<unistd.h>
const int OUTPUT_PORT = 0x338;
const int INPUT_PORT = 0x238;
const int LENGTH = 3;
const int SLEEP_TIME = 1;
void clear_screen(void);
void write_port(void);
void read_port(void);
int a[9] = {1,2,4,8,16,32,64,128,256};
int b[9] = {256,128,64,32,16,8,4,2,1};
void test_output(short count)
{
short i,j;
clear_screen();
printf("No of tests\t= %d",count);
printf("\nDELAY\t\t= %d",SLEEP_TIME);
printf("\n\n\t\tCan you see the lights dancing ;)\n");
for(i=0;i<count;i++)
{
printf("\nCOUNT %d",i+1);
for(j=0;j<9;j++)
{
outb(a[j],OUTPUT_PORT);
outb(a[j],OUTPUT_PORT+1);
outb(a[j],OUTPUT_PORT+2);
outb(a[j],OUTPUT_PORT+3);
j==8?printf("\t#"):printf("\t*");
fflush(stdout);
sleep(SLEEP_TIME);
}
outb(0x00,OUTPUT_PORT);
outb(0x00,OUTPUT_PORT+1);
outb(0x00,OUTPUT_PORT+2);
outb(0x00,OUTPUT_PORT+3);
sleep(SLEEP_TIME);;
}
}
void clear_screen(void)
{
int i;
for(i=0;i<24;i++)
printf("\n");
}
void write_port(void)
{
clear_screen();
int port,data;
printf("\n\t\tWrite to ports 0x%x - 0x%x",OUTPUT_PORT,OUTPUT_PORT+LENGTH);
printf("\n\t\tEnter negative address to return to MAIN MENU\n");
printf("\t\tEnter hexadecimal values for address and data.\n");
while(1)
{
printf("\nEnter port address to WRITE TO: ");
scanf("%x",&port);
if(port <0)
return;
if(port<OUTPUT_PORT || port > OUTPUT_PORT+LENGTH)
printf("\tERR: Port address out of range 0x%x - 0x%x",OUTPUT_PORT,OUTPUT_PORT+LENGTH);
else
{
printf("Enter data: ");
scanf("%x",&data);
outb(data,port);
printf("\tWritten 0x%x to address 0x%x",data,port);
}
}
}
void read_port(void)
{
int port,data;
clear_screen();
printf("\n\t\tRead from ports 0x%x - 0x%x",INPUT_PORT,INPUT_PORT+LENGTH);
printf("\n\t\tEnter negative address to return to MAIN MENU\n");
printf("\t\tEnter hexadecimal values for address and data.\n");
while(1)
{
printf("\nEnter port address to READ FROM: ");
scanf("%x",&port);
if(port <0)
return;
if(port<INPUT_PORT || port > INPUT_PORT+LENGTH)
printf("\tERR: Port address out of range 0x%x - 0x%x",INPUT_PORT,INPUT_PORT+LENGTH);
else
{
data = inb(port);
printf("\tByte read from address 0x%x = 0x%x",port,data);
}
}
}
int main()
{
char choice;
int count;
count = 1;
if(ioperm(OUTPUT_PORT,LENGTH+1,1))
{
perror("ERR");
printf("Unable to get permission to access OUTPUT ADDRESS 0x%x - 0x%x",OUTPUT_PORT,OUTPUT_PORT+LENGTH);
printf("\nYou must be 'root' to be able to run this program.\n\n");
exit(1);
}
if(ioperm(INPUT_PORT,LENGTH+1,1))
{
perror("ERR");
printf("Unable to get permission to access INPUT ADDRESS 0x%x - 0x%x",INPUT_PORT,INPUT_PORT+LENGTH);
printf("\nYou must be 'root' to be able to run this program.\n\n");
exit(1);
}
do
{
clear_screen();
printf("\nProgram for Input/Output operations using ISA Interface");
printf("\n-------------------------------------------------------");
printf("\nMain Menu");
printf("\n\n\t1. Write to a port");
printf("\n\t2. Read from a port");
printf("\n\t3. Run the output test");
printf("\n\t9. Exit");
printf("\n\nEnter Choice (0 - 9): ");
fflush(stdin);
choice = getchar();
switch(choice)
{
case '1':
write_port();
break;
case '2':
read_port();
break;
case '3':
printf("\nNo of times to run the test ? ");
scanf("%d",&count);
test_output(count);
break;
case '9':
printf("\nBye!\n");
break;
default:
break;
}
}
while(choice != '9');
ioperm(OUTPUT_PORT,LENGTH,0);
ioperm(INPUT_PORT,LENGTH,0);
return 0;
}