www.angelfire.com/dragon/letstry
cwave04 at yahoo dot com

Using the PC parallel port

We want to send pulses to the microcontroller from the parallel port. So let us make sure we can indeed send pulses through the parallel port. First we must familiarise ourselves with the pins of the parallel port.

The basics

The parallel port connector is commonly called DB-25. The connector that sticks out of the back side of your PC is a female connector. You'll need a matching male connector. The connector has 25 pins arranged as follows.

Parallel port

Consider the left hand diagram first. This is drawn looking into the female DB25 connector at the back of a PC. If you look carefully at a male connector you'll see the numbers 1 to 25 engraved beside the pins for easy identification. Make sure you know for sure which pin is which. The parallel port is actually made of three ports put together. Each of these ports is identified by a number. The numbers are (by default) 0x378, 0x379 and 0x37A. The red pins corrspond to 0x378. Thus if you write a byte to this port using a C instruction like

Out32(0x378,0x30);

the bit pattern for 0x30 will show up through the pins. Since 0x30 is 00110000 in binary this means pins 6 and 7 will show 5V, while pins pins 2, 3, 4, 5, 8 and 9 will be at 0V. By the way Out32 is not a standard C function. We shall soon learn how to get it.

These pins are dedicated for output purposes. If you externally connect one of these pins to 0V (or +5V) this will not change the value of the port numbered 0x378 (though there is some chance of your parallel port being damaged). However, I have done this mistake a number of times without destroying the parallel port. Experiment at your own risk!

The port numbered 0x379 is internally connected to the blue pins. Notice the wierd numbering of these pins. If you externally connect thse pins to 0V (or +5V) and then try to read the port using a C statement like

val = Inp32(0x379);

you'll find that bits 3,4,5,6 and 7 reflect the logic values present at the corresponding pins. But pins 0,1 and 2 will have fixed values. The Inp32 function, just like the Out32 function, is not a standard C function. We shall discuss about it soon.

The ocre coloured pins are connected to port number 0x37A. These are typically output pins (though on some machines they may also be used as input pins). We shall not go into their details as we shall not use them in our programmer.

The green pins are all grounds.

Communicating with the parallel port

Now that we know the basics of the parallel port we shall manipulate them using software. This step is slightly tricky owing to the diversity among operating systems.
  • DOS or Windows 95/98: The inport and outport functions of Turbo C will be all that you need to communicate with the parallel port. I have not tried this.
  • Windows XP: You will need a dll file called inpout32.dll (there are plenty of sites providing it for free. Just do a web search). This dll has the functions Out32 and Inp32 that we have mentioned above. We shall discuss how to access these functions from the dll file soon. You'll need root privilege to communicate with the parallel port.
  • Knoppix (an on-CD version of Linux): The GNU C compiler in gcc has the functions outb and inb in the header asm/io.h.
  • Fedora 8: I tried to use asm/io.h just as in Knoppix, but the gcc installed in my machine did not have that header file. One possible option was to install a more comprehensive version of gcc, which did not appear that easy! Instead, I used a different technique: I opened /dev/port and accessed the ports directly. I shall show how to do this presently.

I am fond of the C language, so I have talked about only C. But other programming languages (like Java, Ruby, VB) should be fine too (these can load inpout32.dll). In Java you have to use native code.

Writing to the parallel port

Using Borland C in Windows XP

Here is a simple C code (to be compiled with the free Borland C commandline compiler) that writes a byte through your parallel port.
writefixed.c

#define PPORT_BASE 0x378
#define PPORT_OUT PPORT_BASE
#define PPORT_INP (PPORT_BASE+1)
1

typedef short (_stdcall *inpfuncPtr)(short portaddr);
typedef void (_stdcall *oupfuncPtr)(short portaddr, short datum);
inpfuncPtr inp32fp;
oupfuncPtr oup32fp;
2

void init(void) {
  HINSTANCE hLib;
  hLib = LoadLibrary("inpout32.dll");

  if(hLib == NULL) {
    fprintf(stderr,"LoadLibrary Failed.\n");
    exit(1);
  }

  inp32fp = (inpfuncPtr) GetProcAddress(hLib, "Inp32");

  if(inp32fp == NULL) {
    fprintf(stderr,"GetProcAddress for Inp32 Failed.\n");
    exit(1);
  }

  oup32fp = (oupfuncPtr) GetProcAddress(hLib, "Out32");

  if(oup32fp == NULL) {
    fprintf(stderr,"GetProcAddress for Out32 Failed.\n");
    exit(1);
  }

}
3
short Inp32 (short portaddr) {
  return (inp32fp)(portaddr);
}

void Out32 (short portaddr, short datum) {
  (oup32fp)(portaddr,datum);
} 
4
void main() {
  init();
  Out32(PPORT_OUT, 0x34);
}
5

Explanation of the code:

1:
These are the addresses of the three memory locations associated with the parallel port.
2:
This is the most sophisticated piece of C programming that we shall need in this tutorial. In fact, you can just copy-n-paste this part without caring about what it means. But here is a short explanation for the more inquisitive. We are declaring two new types which we have called inpfuncPtr and oupfuncPtr. A C object of type inpfuncPtr is supposed to be a function that takes a short value as input, and returns a short value as output. A C object of type outfuncPtr is supposed to be a function that takes two short values as input, and returns nothing.
3:
This is where we extract the two functions Inp32 and Out32 from the dll file.
4:
Here we provide two wrapper functions around the newly extracted functions. They make it easier to call the functions.
5:
The main functions sets up the port communication and writes 0x34 (which is the bit pattern 0011 0100) to the parallel port.

Using gcc in Knoppix

#include <stdio.h>
#include <assert.h>
#include <string.h>
#include <unistd.h>
#include <asm/io.h>

#define PPORT_BASE 0x378
#define PPORT_OUT PPORT_BASE
#define PPORT_INP (PPORT_BASE+1)
void init() {
  if(ioperm(PPORT_BASE, 3, 1)) {
    perror("Cannot grab port!\n"); 
    exit(1);
  }
}

void done() {
  if(ioperm(PPORT_BASE, 3, 0)) {
    perror("Cannot release port!\n"); 
    exit(1);
  }
}
1
main() {
  init();
  outb(0x37,PPORT_OUT);
  done();
}

Explanation of the code:

1:
The ioperm functions reserves permission for accessing the ports.

Using gcc in Fedora 8

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#define PPORT_BASE 0x378
#define PPORT_OUT PPORT_BASE
#define PPORT_INP (PPORT_BASE+1)
1
void init() {
  if(!(fport = open("/dev/port",O_RDWR))) {
    perror("Cannot grab port!\n"); 
    exit(1);
  }
}

void done() {
  if(close(fport)) {
    fprintf(stderr,"Cannot close port!\n"); 
    exit(1);
  }
}
2
int inb(int fromAddr) {
  char buff;
  if(lseek(fport,fromAddr,SEEK_SET)!=fromAddr) {
    fprintf(stderr,
      "Cannot arrive at port %x for reading.\n",fromAddr); 
    fflush(stderr);
    exit(1);
  }

  if(read(fport,&buff,1)!=1) {
    fprintf(stderr,"Cannot read from port %x.\n",fromAddr);
    fflush(stderr);
    exit(1);
  }

  return buff;
}
3
void outb(int what, int toAddr) {

  if(lseek(fport,toAddr,SEEK_SET)!=toAddr) {
    fprintf(stderr,
      "Cannot arrive at port %x for writing.\n",toAddr);
    fflush(stderr);
    exit(1);
  }

  if(write(fport,&what, 1)!=1) {
    fprintf(stderr,
	    "Cannot write to port %x.\n",toAddr);
    fflush(stderr);
    exit(1);
  }
}
4
main() {
  init();
  outb(0x37,PPORT_OUT);
  done();
}

Explanation of the code:

1:
The parallel port addresses (just as in the windows version).
2:
Opening and closing the ports. Do not use the fopen function here, as it uses buffering. Also, use the /dev/port carefully. Once it is opened, it gives you access to the entire hardware. So writing to a wrong location may even corrupt your harddrive!
3:
Notice how we are lseeking everytime we want to read something. Each lseek advances the reading head by 1 byte, so if we do not reset it back to desired position before reading we shall be reading form the wrong location. Remember that /dev/port is not like an ordinary file. For an ordinary file the content remains fixed, and the reading head moves. For /dev/port the reading head should stay fixed (at the parallel port input address, say) and the content at that location will change.
4:
This is the function for writing to a port. Again note the use of lseek. Be very caregul that you do not write to a wrong location!

Sending timed pulses

OK, now that we have been able to access the port, we are ready to send timed pulses. We shall first write a program that pulses a LED connected to the parallel port. Connect a LED from any of the output pins (pins 2 through 9) to ground as shown below. The 1K resistance is for safety purposes (it prevents the LED from drawing too much current from the parallel port). The resistance causes the LED to glow very feebly. I have also tried the experiment without the resistance. Then the LED glows brightly (and my parallel port is still alive). Experiment at your own risk!

Conecting a LED to the parallep port

We want to make this LED pulse.

To send a pulse we need to write something to a port, wait for sometime, and then restore the original value, like write 1, wait, write 0. We already know how to achieve the writing part in various platforms. The only new thing here is the waiting. It turns out that there is a little bit of platform-dependence even here.

Using Borland C in Windows XP

In Borland C for Windows XP we need to use the Sleep(long) function, which takes as input the duration of the wait in milliseconds. Thus Sleep(1000) waits for 1 second.
Part of writepulse.c


#include <windows.h>

/*Load dll as before*/

void main() {
  unsigned short i;

  init();
  while(1) {
    for(i=0;i<256;i++) {
      Out32(PPORT_OUT,i);
      Sleep(1000);
    }
  }
}

Using gcc in Knoppix/Fedora 8

Here we can use the usleep(long) function that takes the duration of wait in microseconds.
Part of writepulse.c




/*Define init, done etc as before*/

void main() {
  unsigned short i;

  init();
  while(1) {
    for(i=0;i<256;i++) {
      outb(PPORT_OUT,i);
      usleep(1000000);
    }
  }
}

The three R's of platform-dependence

In this page we have discussed all the platform-dependence that is needed in this tutorial: reading, 'riting and resting (i.e., waiting). Henceforth we shall use the Windows names Inp32, Out32 and Sleep generically for all platforms. You should remember to change them approriately for your platform as above.

PrevNext
© Arnab Chakraborty (2008)