This is the Tutorial Section. In it we will describe the hardware of some parts of a modern PC. If we take a look at the standard Input/Output PC connectors we can find: the parallel port, to which the printer is connected, two COM ports (the RS_232 ports), and the joystick port. We will start by describing the parallel and the joystick ports. But first, we will show you how can we use the power source of the PC to connect other devices.
When the box of your computer is open you can see the different parts of the PC: the motherboard, the disk units, the video board and the power source. The power source consists of an aluminum box with wires of different colors getting out of it.
Colors of wires | |
COLOR | VOLTAGE |
black | ground |
red | 5V |
yellow | 12V |
The parallel, the joystick and the serial ports have no pin connected to the PC source. So, all the devices you connect to this port must use an extern source. However, you have got another option: the power source of the PC could be used to give power to other devices
The source of the computer has got five outputs. These outputs consist of ,many wires connected to GND, 5V, -5 V, 12V and -12 V. These are the voltages that the computer works with. If you take these wires out of the computer box you can use them to power up some devices.
The most used voltages are 5V, 12V and GND. To identify these outputs most dealers of PC power sources use the colors shown in the table 1.1.
The Parallel port is a standard designed to connect a printer to a computer. It is used for the CPU to send data to a printer. This interface drives some input and output signals. The purpose of these signals is to let the computer know the state of the printer and control it. Eight data bits carry all the information sent with each clock pulse.
The hardware of this port consists of 8 output data bits, 5 input control bits and 5 output control bits. The control signals are listed below:
Outputs:
STROBE/: Tells the printer when the eight data bits are ready to be read. Turns to a low logic level when the data are ready.
INIT/: Reset the printer.
SLCT IN/: Selects the printer when it turns to a low logic level.
AUTO FD/: Tells the printer to print an empty line followed by a carriage return.
D0-D7: Data bits.
Inputs:
ACK/: Tells the CPU that the data has been correctly received.
BUSY: The printer sets this line when its buffer is full. The computer will stop sending more data.
SLCT: Tells the computer that a printer is present.
ERROR/: An error has occurred. The CPU stop sending more data.
PE: The printer is out of paper.
All these signals are connected to a 25 PIN connector. All the bits have TTL logic levels.
Table 2.1
Data Bits Table | ||
BIT | FUNCTION | PIN |
D0 | data 0 | 2 |
D1 | data 1 | 3 |
D2 | data 2 | 4 |
D3 | data 3 | 5 |
D4 | data 4 | 6 |
D5 | data 5 | 7 |
D6 | data 6 | 8 |
D7 | data 7 | 9 |
In the MS-DOS operative system three parallel ports, called LPT1, LPT2 and LPT3, are supported. So we can find three addresses dedicated to these ports in the memory map of the PC. Let's study the addresses dedicated to LPT1 first. Each parallel port uses three addresses of the I/O map. For LPT1 these addresses are 378H, 379H and 37AH.
378H PORT: In this address the CPU writes the data to be sent to the printer. It is an OUTPUT port. The eight data bits (D0-D7) are latched to appear in the output connector. In the table 2.1 we can see which pins of the connector are used.
379H PORT: This is an INPUT port. These signals are used by the CPU to know the state of the printer. The location of the bits is listed in table 2.2.
37AH PORT: In this port the computer writes the signals that control the printer. Therefore, it is an OUTPUT port, see table 2.3.
Table 2.2
Status Bits Table | ||
BIT | FUNCTION | PIN |
D0 | not used | |
D1 | not used | |
D2 | not used | |
D3 | ERROR/ | 15 |
D4 | SLCT/ | 17 |
D5 | PE | 12 |
D6 | ACK/ | 10 |
D7 | BUSY/ | 11 |
Table 2.3
Control Bits Table | ||
BIT | FUNCTION | PIN |
D0 | STROBE | 1 |
D1 | AUTO FD | 14 |
D2 | INIT/ | 16 |
D3 | SLCT IN/ | 17 |
D4 | Habilitation IRQ7 | |
D5 | not used | |
D6 | not used | |
D7 | not used |
The computer has three LPTn ports. The addresses of the control and data signals for each LPTn port are listed below. Each port works in the same way that LPT1 does.
Table 2.4
Adresses of LPTn | |||
PORT | DATA | STATUS | CONTROL |
LPT1 | 378H | 379H | 37AH |
LPT2 | 278H | 279H | 27AH |
LPT3 | 3BCH | 2BDH | 3BEH |
As you can see, the parallel port is able to control the IRQ7 interruption channel. This is a very powerful capability of this port. In a further tutorial we will show you how to use this signal.
The programs below are examples about the way you can program the parallel port. They are all compiled with the Borland C++ 3.1 compiler.
#include <stdio.h> #include <dos.h> #include <conio.h> /********************************************/ /*This program set the parallel port outputs*/ /********************************************/ void main (void) { clrscr(); outportb(0x378,0xff); outportb(0x37a,0xff); getch();} |
The first program shows you how to send a byte to the parallel port output addresses. It's as easy as you can see. The outportb(); function sends a byte to a specified I/O port. The first function parameter is the address of the port to write a byte. The second parameter is the value of the byte to send. Both parameters can be defined as variables. In this case the first parameter must be an unsigned int, and the second an unsigned char.
#include <stdio.h> #include <dos.h> #include <conio.h> /*****************************************/ /*This function read parallel port inputs*/ /*****************************************/ int Read_Input() {int Byte; Byte=inportb(0x379); return Byte;} void main (void) {int PP_Input; clrscr(); PP_Input = Read_Input; printf("%d",var); getch();} |
The second example shows you how to read a byte from the parallel port input address. The main function is only used to show the value of the byte in the screen. The inportb(); function read a byte from the specified I/O address of the computer. The parameter must be an unsigned int.
The joystick port is designed as an interface with two analog joysticks. Each joystick has two buttons.
Usually, the joystick port is not integrated like a component of the motherboard. The joystick port is implemented in multi-I/O or sound boards. The connector of the port enables the control of two joysticks at the same time. It is very easy to know if the Joystick port is present in your PC. It is the only 15 pin connector that you can find in your PC rear panel.
The stick is attached to two 100K-ohm potentiometers. One of the resistors changes its value with a change in the position of the stick along the X axis. The other potentiometer does the same with the Y axis. A change in the value of the resistors changes the frequency of a digital pulse. In the conversion of the resistance to a variable frequency pulse, a NE558 is used. This component is very similar to the popular NE555, but the NE558 has four timers integrated in the same IC. Two of these ICs are used for each joystick. One pulse shows the X coordinate of the joystick and the second pulse shows the Y coordinate.
The frequency of the pulses can be calculated by the formula shown below:
T = 10 x R + 24.2
Where R is the value of the resistance in K-omh and T is the time of a pulse, in micro s.
The value of the resistors is 100K-omh, so the maximum time of the pulse is 1.024micro s and the minimum time of each pulse is 24.2 micro s.
The joystick port is located in the 201H address of the I/O port.
By writing any value in the address of the Joystick port, the timers start generating pulses. Reading this port, we get a byte. The low nibble gives the state of the four digital pulses. The state of the shoot buttons is given in the high nibble. The meaning of each bit of this port is shown in the table.
Table 3.1
JOYSTICK B | JOYSTICK A | JOYSTICK B | JOYSTICK A | ||||
button 2 | button 1 | button 2 | button 1 | coordinate y | coordinate x | coordinate y | coordinate x |
BIT 7 | BIT 6 | BIT 5 | BIT 4 | BIT 3 | BIT 2 | BIT 1 | BIT 0 |
To read the position of the joystick you can use the 84H function of the 15H interruption of the BIOS
The program shows you how to read the buttons state and the joystick position. The main function is only used to display the joystic position and the buttons state. The GetJoyButton(); and the GetJoyPos(); function work the same way. Tehy make a call to the 84H function of the BIOS.
#include <dos.h> #include <conio.h> #include <stdio.h> #include <stdarg.h> int key=1; /*== type declaration ================================================*/ typedef unsigned char BYTE; typedef struct { /*describe the position of a joystick*/ int x; int y; } JSPOS; /***********************************************************************/ /*Read the state of the joystick buttons. The variables are set to 1, */ /*(TRUE) if its joystick button is pressed and 0, (FALSE) if not. */ /***********************************************************************/ void GetJoyButton( BYTE *j1b1, BYTE *j1b2, BYTE *j2b1, BYTE *j2b2 ) { union REGS regs; /*registers of the processor*/ regs.h.ah = 0x84; /*Function 84h*/ regs.x.dx = 0; /*subfunction 00h*/ int86( 0x15, ®s, ®s ); /*Call to 15h interruption*/ *j1b1 = (( regs.h.al & 16 ) >> 4) ^ 1; /*Bit 4 of AL = J1B1*/ *j1b2 = (( regs.h.al & 32 ) >> 5) ^ 1; /*Bit 5 of AL = J1B2*/ *j2b1 = (( regs.h.al & 64 ) >> 6) ^ 1; /*Bit 6 of AL = J2B1*/ *j2b2 = (( regs.h.al & 128 ) >> 7) ^ 1; /*Bit 7 of AL = J2B2*/ } /***********************************************************************/ /*Return the position of the Joystick */ /*JS1PTR,JS2PTR PointerS to the structure of the Joystick */ /***********************************************************************/ void GetJoyPos( JSPOS *Js1Ptr, JSPOS *Js2Ptr ) { union REGS regs; /*registers of the processor*/ regs.h.ah = 0x84; /*Function 84h*/ regs.x.dx = 1; /*Subfunction 01h*/ int86( 0x15, ®s, ®s ); /*Call to 15h interruption*/ Js1Ptr->x = regs.x.ax; /*X Position of the Joystick 1*/ Js1Ptr->y = regs.x.bx; /*Y Position of the Joystick 1*/ Js2Ptr->x = regs.x.cx; /*X Position of the Joystick 2*/ Js2Ptr->y = regs.x.dx; /*Y Position of the Joystick 2*/ } void keyb() { union u_type{int a;char b[3];}keystroke;char inkey=0; if(bioskey(1)==0) return; keystroke.a=bioskey(0); inkey=keystroke.b[1]; switch (inkey) { case 1: tecla=0; return; /*ESC*/ default: tecla=15; return; } } void main() {JSPOS joystick1,joystick2; BYTE bj1a,bj1b,bj2a,bj2b; clrscr(); while(key != 0) { keyb(); GetJoyButton( &bj1a, &bj1b, &bj2a, &bj2b ); GetJoyPos( &joystick1, &joystick2 ); gotoxy(2,1); cprintf("The X coord. is: %3d and the Y coord. is: %3d of the joystick1",joystick1.x,joystick1.y); gotoxy(2,3); cprintf("The X coord. is: %3d and the Y coord. is: %3d of the joystick2",joystick2.x,joystick2.y); gotoxy(2,5); cprintf("The button 1, joystick1 is: %3d",bj1a); gotoxy(2,7); cprintf("The button 2, joystick1 is: %3d",bj1b); gotoxy(2,9); cprintf("El button1, joystick2 is: %3d",bj2a); gotoxy(2,11); cprintf("El button2, joystick2 is: %3d",bj2b); } } |
The serial communications are used for transferring data over long distances, because parallel communications requires too many wires. Serial data received from a modem or other devices are converted to parallel so that it can be transferred to the PC bus.
The serial communications equipment can be divided into simplex, half-duplex and full-duplex. A simplex serial communication send information only in one direction (i.e. a commercial radio station). Half-duplex means that data can be send in either direction between two systems, but only in one direction at a time. In a full-duplex transmission each system can send and receive data at the same time.
There are two ways to transmit serial data: synchronously or asynchronously. In a synchronous transmission data is sent in blocks, the transmitter and the receiver are synchronized by one or more special characters called sync characters.
The serial port of the PC is an asynchronous device, so we will describe this kind of systems. For asynchronous transmission, a bit identifies its start and 1 or 2 bits identify its end, don't need any synchronization. The data bits are sent to the receiver after the start bit. The least significant bit is transmitted first. A data character usually consist of 7 or 8 bits. Depending on the configuration of the transmission a parity bit is send after each data bit. It is used to check errors in the data characters. Finally 1 or 2 stop bits are send.
The serial port of the PC is compatible with the RS-232C standard. This standard was designed in the 1960s to communicate a data terminal equipment or DTE (the PC in this case) and a data communication equipment or DCE (usually a modem).
The standard specifies 25 signal pins, and that the DTE connector should be a male and the DCE connector should be a female. The most used connectors are the DB-25 male, but many of the 25 pins are not needed. For that reason in many modern PCs a DB-9 male connector is used. So you will find one or more of these connectors in the rear panel of the PC. The voltage levels are between -3V and -15V for a logic high. A logic low is a voltage between +3V and +15V. The commonly used voltages are +12V and -12V.
The most commonly used signals are listed below:
/DTR (Data-Terminal-Ready):The PC tells the modem that is powered up and ready to send data.
/DSR (Data-Set-Ready): The modem tells the PC it is powered up and ready to transmit or receive data.
/RTS (Request-To-Send): The PC sets this signal when has a character ready to be sent.
/CD (Carrier-Detect): The modem sets this signal when has detected the computer.
/CTS (Clear-To-Send): The modem is ready to transmit data. The computer will start sending data to the modem.
TxD: The modem receives data from de PC.
RxD: The modem transmits data to the PC.
The integrated circuits that convert the serial data lines to parallel and vice versa are called UART (Universal Asynchronous Receiver-Transmitter). The typical PC UART is the Intel 8251A, this IC can be programmed like a synchronous or an asynchronous device.
Eight data bits (D0-D7) connect the 8251A to the data bus of the PC. The chip select (/CS) input enables the IC when is asserted by de control bus of the PC system. This IC has two internal addresses, a control address and a data address. The control address is selected when the C-/D input is high. The data address is selected when the C-/D input is low. The RESET signal resets the IC. When the /RD is low the computer reads a control or a data byte. The /WR enables the PC to write a byte. Both signals are connected to the system signals with the same names.
The UART includes four internal registers:
THR: Temporary output register.
TSR: Output register.
RDR: Input register.
RSR: Temporary input register.
Every character to be transmitted is stored in the THR register. The UART adds the start and stop bits. Then copies all bits (data, start and stop bits) to the TSR. To finish the process the bits are sent to the line by the TD signal.
Every character received from the line RD is stored in the RSR register. The start and stop bits are eliminated and the UART writes this character to the RDR. To finish the process the character is read for the PC.
It is two ways to address the serial port, by the 14H BIOS interrupt and by the 21H DOS interrupt.
The 14H BIOS interrupt uses four functions to program the serial port. Each function is selected assigning a value to the AH register of the microprocessor. We list the four functions below:
Function 00H: Initializes the serial port and sets the speed, data and stop bits and the parity parameters.
Function 01H: Sends a character to the specified serial port.
Function 02H: Reads a character from the specified serial port.
Function 003: Returns the state of the specified serial port.
There is three functions in the 21H DOS interrupt related to the operation of the serial port:
Function 03H: Reads a character from the COM1 serial port.
Function 04H: Writes a character to the COM1 serial port.
Function 40H: It is a common out function for all files and devices that use a handle access. This function send a number of bytes from a buffer to the specified device.
The program shows you how to communicate two PC s via serial port:
//Program to communicate two PC s via serial port //00H bios function (AL register) //bits 7 6 5 Baud rate // 0 0 0 110 // 0 0 1 150 // 0 1 0 300 // 0 1 1 600 // 1 0 0 1200 // 1 0 1 2400 // 1 1 0 4800 // 1 1 1 9600 //bits 4 3 Parity bits // 0 0 no parity // 0 1 odd parity // 1 1 even //bits 2 stop bits // 0 1 stop bit // 1 2 stop bit //bits 1 0 Number of bits per data // 1 0 7 data bits // 1 1 8 data bits //Register Dx 0->com1, 1->com2, 2->com3, 3->com4 //Configuration: 9600 bd,no parity,2 stop bits and 8 data bits //AL register value is 1 1 1 0 0 1 1 1 -> 0xE7 #include <stdio.h> #include <process.h> #include <conio.h> #include <dos.h> #include <bios.h> #define TRUE 1 #define PARAM 0xA7 #define COM1 0 #define COM2 1 void init_port(void); char state_port(void); void send_byte(unsigned char); unsigned char read_byte(void); void keyb(void); int tecla = 1; void main ( void ) { unsigned char read_com; unsigned char read_kb; clrscr(); init_port(); while(read_kb != 'c') { read_kb=getch(); send_byte(read_kb); read_com=read_byte(); if(read_com!=0){printf("%c",read_com);} } } void init_port() { union REGS regs; regs.h.ah = 0x00; regs.x.dx = COM2; regs.h.al = PARAM; int86( 0x14, ®s, ®s); } //return the state of the port char state_port() { union REGS regs; regs.h.ah = 0x03; regs.x.dx = COM2; int86( 0x14, ®s, ®s); if(regs.h.ah & 0x80) printf("\t EXCEED TIME\n"); if(regs.h.ah & 0x40) printf("\t TSR EMPTY\n"); if(regs.h.ah & 0x20) printf("\t THR EMPTY\n"); if(regs.h.ah & 0x10) printf("\t INTERRUPTION\n"); if(regs.h.ah & 0x08) printf("\t THREAT ERROR\n"); if(regs.h.ah & 0x04) printf("\t PARITY ERROR\n"); if(regs.h.ah & 0x02) printf("\t OVERLOAD ERROR\n"); return (regs.h.ah); } //Keyboard handle void keyb() { union u_type{int a;char b[3];}keystroke;char inkey=0; if(bioskey(1)==0) return; keystroke.a=bioskey(0); inkey=keystroke.b[1]; switch (inkey) { case 1: keyb=0; return; /*ESC*/ default: keyb=15; return; } } //Send a character to the serial port void send_byte(unsigned char byte) { union REGS regs; regs.h.ah = 0x01; regs.x.dx = COM2; regs.h.al = byte; int86( 0x14, ®s, ®s); if( regs.h.ah & 0x80) { printf("\t SENDING ERROR "); exit(1); } } //read a character from serial port unsigned char read_byte() { int x,a; union REGS regs; if((estate_port() & 0x01)) { regs.h.ah = 0x02; regs.x.dx = COM2; int86(0x14,®s,®s); if(regs.h.ah & 0x80) { printf("\t RECEIVING ERROR"); exit(1); } return(regs.h.al);} else { return(0); } } |
The serial port enables to the electronic designer to communicate a PC with devices that supports the RS-232 standard. On the other hand, the parallel port outputs eight data bits and five input signals. It outputs only one interrupt signal (IRQ7).
On the contrary the ISA BUS outputs a 20 bit address bus and a 8 bit data BUS. Allows to manage most of the PC interrupt signals and even, drive DMA (direct memory access) transfers.
In the figure you can see the pinouts of the ISA BUS. The BUS is divided into two sides. The first side pins are named A1 to A31 and it is the components side. It consist of the address and data buses. The second side pins are named B1 to B31 and it is the solder side. This side contents the power pins and the signals related to interrupts and DMA transfers.
We list the most used pins and its description below. For the "A" side:
A0-A19 (pins A31 to A12): This twenty lines are the address BUS. They can address 1MB (2^20 bytes).
D0-D7 (pins A9 to A2): The data BUS consist of this eight data lines.
AEN (pin B11): It is used for the DMA controller to take over the data and address buses in a DMA transfer.
For the "B" side:
GND (pins B1, B10, B31): Connected to the ground of the computerLet's start describing the operation of the ISA bus with a simple read cycle from an Input/Output port. The first thing the microprocessor does is to send out a high on the ALE signal, then sends out the A0-A19 lines. After, the ALE signal goes low. From now on the address of the target port to be read will be latched. Then the BUS takes the -IOR signal to a low level. So that the addressed device will take a data byte to the D0-D7 data bus. The microprocessor will read then the data bus and take the -IOR signal to a high again.
A write cycle to a port works this way: The microprocessor asserts the ALE high, then outputs A0-A19. ALE goes low again. The microprocessor send out the data byte to be write. It then asserts the -IOW signal. After the device have time to read the data byte, the uP raises the -IOW signal high again.
The only difference between a memory read/write cycle and a port read/write cycle is that in a memory cycle the -MEMR and -MEMW signals will be asserted, working the same way as -IOR and -IOW do.
In the PC memory map we can find two kinds of interrupts, the software interrupts and the hardware interrupts. In this tutorial only the hardware interrupts will be explained. In a PC, the external interrupts are driven by the 8259A priority interrupt controller. When an 8259A receives an interrupt signal through the IRQ2 to IRQ7 signals, it sends an interrupt request signal to the INTR input of the uP. Then the 8086 outputs an INTA (interrupt-acknowledge) signal to the 8259. So that the uP can get interrupt type of the external device. The 8086 then uses the type read from the external device to get the address for the interrupt-service procedure from the interrupt pointer table in memory. Note that INTR and INTA are not present in the ISA bus, this signals are only used for the uP and the 8259A.
The basic target of an interrupt is to execute a function that response to the request of a hardware device. An interrupt vector contents the address of this function. In an 8086 system the first Kbyte of memory (from 00000H to 003FFH) is used for the interrupt vectors. To point to any address of the whole memory map four bytes are needed. 16 bits for the base address and 16 bits to identify the segment. So, a 1Kbyte of memory allows to store 256 interrupt vectors. Some of the 256 interrupt vectors are used for the system, others are free to be used for the user programs. To install a user interrupt service procedure you can use a program like the one of the example.
The program install an interrupt routine in the IRQ1 interrupt channel, which is the system timer.
This timer generates an interrupt 18.2 times per second. In the interrupt service routine,
we increment a global variable. When this variable equals to 18 is printed on the screen.
So that, we will get on the screen a second counter.
#include <dos.h> #include <stdio.h> #include <conio.h> #include <bios.h> #define IMR 0x21 int _key=1; int global=0; void interrupt (*_old_int_function)(); char _old_mask; char _interrupt_mask(int IRQn) { char p=1; p=p< } void _install_int_function(int IRQn, void interrupt (*_new_int_function)()) { int inter = IRQn + 8; _disable(); //disable interrupts _old_int_function=_dos_getvect(inter); //save the old interrupt vector _dos_setvect(inter,_new_int_function); //install the new interrupt vector _old_mask=inportb(IMR); //save the state of the 8259A IMR register outportb(IMR,_old_mask&_interrupt_mask(IRQn)); //Set new value for IMR register _enable(); //enable interrupts } void _end_interrupt(void) { outportb(0x20,0x20); } void _Unistall_new_int_function(int IRQn) { int inter = IRQn + 8; _disable(); //disable interrupts _dos_setvect(inter,_old_int_function); //restore the old interrupt service function outportb(IMR,_old_mask); //restore the IMR _enable(); //enable interrupts again } void interrupt _new_int_function() { _disable(); //disable interrupts global++; //global count the number of interrupts //that the system has requested _end_interrupt(); //to tell the system the interrupt service function has finished _enable(); //enable interrupts again } /*********************************************************/ /*Read the keyboard. If "ESC" is pressed the program ends*/ /*********************************************************/ void _keyboard(void) { union u_type{int a;char b[3];}keystroke;char inkey=0; if(bioskey(1)==0) return; keystroke.a=bioskey(0); inkey=keystroke.b[1]; switch (inkey) { case 1: _key=0; return; case 11: _key=39; return; /*_key 0*/ default: _key=15; return; } } void main(void) { int second=0; clrscr(); cprintf("Press 'ESC' to exit \n \n"); _install_int_function(0,_new_int_function); do { _keyboard(); //read the keyboard if (global >=18) { second++; //incremented each second gotoxy(2,2); cprintf("Time: %d",second); global=0; } } while(_key !=0); _Unistall_new_int_function(0); } |
Some input/output devices send data faster than the microprocessor can get it. The DMA (Direct Memory Access) is a dedicated IC that sends and receives data faster than the microprocessor. So magnetic and optical disks use this IC to access the memory of the system.
The DMA (Direct Memory Access) controller borrows the address bus, the data bus and the control bus from the system and transfers a programmed series of bytes from a fast I/O device to the memory. The 8237 DMA controller is the device used for the PC to do this job.
When a device has a data block ready to be send to the memory, sends a DMA request asserting a DRQn signal to the DMA controller. If the requested channel is unmasked, the DMA will send a HRQ (hold request) signal to the microprocessor. The microprocessor will respond floating its buses and sending a HLDA (hold acknowledge) signal to the DMA. Then the DMA gets the control of the buses asserting the AEN signal high and sending out the address of memory to be written. Next the DMA outputs the DACKn (DMA acknowledge) to the device. Finally the DMA controller asserts the MEMW and IOR signals on the control bus. When the data transfer is completed unasserts the HRQ signal and the processor gets the control of the buses again.
If a device needs some data from the memory, the process is similar. The only difference is that the DMA controller outputs the MEMR and IOW on the control bus.