Parallel Port Control with Delphi

Published:
Introduction
The parallel port is a very commonly known port, it was widely used to connect a printer to the PC, if you look at the back of your computer, for those who don't have newer computers, there will be a port with 25 pins and a small printer symbol. This port is known as LPT port or printer port. We can program this port for device control and/or data transfer, in projects of robotics, electronics and even LCD interfacing. In this article, I'll show the basics of parallel port and some programming principles using the LPTPort Component.

Parallel Port Concepts
The parallel port was initially designed to connect a printer to the computer, however we can program this port for many more applications beyond that.
The parallel port can be used for two main reasons: device control and communication. We can program the parallel port for both functions. The parallel ports are easier to program and faster compared to serial ports. However the main disadvantage is that it needs more transmission lines (9 in comparison to the 3 lines used in serial port). Because of this, parallel ports are not used in long distance communications. So let's see the basic differences between the transmission of parallel port and serial port (Figure 1).
In serial ports, we have two data pins: one for transmission and the other for reception. To send data in serial port, it has to be sent one bit after another with some extra bits like start bit, stop bit and parity bit to detect errors. But in parallel port, all the 8 bits of a byte will be sent to the port at the same time and an indication that the data is ready for transmission will be sent in another pin. There will be some data pins, some control and some handshaking pins in parallel port. Commonly at the computer's backside we have a DB25 female connector with 25 pins (see Figure 2), the Table 1 presents the respective DB25 pins and functions.

  Figure 1
  Table 1

  Figure 2
Parallel's Port Organization
Now we are going to see how the parallel port is organized, there are 3 registers associated with the LPT port: the data, status and control registers.
The data register takes care of the port's data pins, this means that if want to send a byte to the data pins, we need to address this register, in order to send the byte to the data pins. Normally, the data, status and control registers will have the following addresses (see Table 2), we will need them during the programming.

Table 2
To know the details of parallel ports available in your computer, follow this procedure:
1.      Right click on My Computer, go to "Properties".
2.      Select the tab Hardware, click Device manager.
3.      You will get a tree structure of devices; in that expand "Ports (COM & LPT)".  
4.      Double click on the ECP Printer Port (LPT1) or any other LPT port if available (see Figure 3).

Figure 3
5.      You will get details of LPT port. Make sure that "Use this Port (enable)" is selected.
6.      Select tab Resources. In that you will get the address range of port (see Figure 4).    

Figure 4
Programming the Parallel Port
For security reasons NT/2000 and XP versions of Windows don't allow direct access to hardware, in other words, one cannot direct manipulate the hardware resources, like parallel ports, therefore a device driver is necessary to talk to the system's core (kernel) to get access to computer's physical ports.
In the 95/Me and 98 Windows versions there is no restriction and the we have direct access, so there's no need for drivers.
Among the difficulties in using dlls, is the fact that you must distribute them with your application, in order for it to work, but we will use a free superb component, that will do all the work, of loading its internal drivers to enable us with direct access to computer's parallel ports.
We are going to make a simple application to demonstrate the many uses and advantages of the LPTPort component: LPT port detection, data pin manipulation, writing values to and reading values from the parallel port.
You can download the LPTPort component at: www.delphifr.com.
Initially open the BDS and create a new application, save it as ParallelControl.dpr, and the main form as uMain.pas.
Put on the created form one XPManifest and a LPTPort component, now double click the form and on the event OnFormCreate, type the following code:

LPTPort1.SelectPort($378);

Open in new window


With this code we will be selecting the base parallel port address ($378) that is the standard in most computers (888 in decimal notation), we need to use the $ in order for the compiler to recognize that's a hexadecimal value. Pick a GroupBox, and alter its Name property to grbPins, add 5 labels onto the GroupBox and modify its properties like described in Table 3.

Table 3
These Labels will be used to show the decimal and binary values from the port's data register using the GetState method (see Listing 1).

Listing 1: Code for presenting the decimal and binary values from data register
procedure TfrmMain.GetState;
                      begin
                        lblDec.Caption := IntToStr(LPTPort1.Inp);
                        lblBin.Caption := DecToBin(LPTPort1.Inp);
                      end;

Open in new window


 This routine reads the data register using the Inp function and presents the value using the labels Caption property value. Observe that the LPTPort component has a method for converting decimal into binary values with DecToBin.
Now insert a RadioGroup on the form with its Name property altered to rdgRegisters, and in its Items property type these three lines: Data Register, Status Register and Control Register. This way, we are inserting three RadioButtons corresponding to these three lines. Now double click the rdgRegisters and type Listing 2 code.

Listing 2: Code for parallel port register selection (rdgRegistersClick)
procedure TfrmMain.rdgRegistersClick(Sender: TObject);
                      begin
                       case (rdgRegisters.ItemIndex) of	
                      	0:
                      		begin
                      		SetPinOutCheckBoxState(True);
                      		btnSend.Enabled := True;
                      		btnRead.Enabled := True;
                      		lblBinHeader.Enabled := True;
                      		lblBin.Enabled := True;
                      		lblDecHeader.Enabled := True;
                      		lblDec.Enabled := True;
                      		edtSend.Enabled := True;
                      		btnSend.Enabled := True;		
                      		end; // Data Register
                      	1:
                      		begin
                      		SetPinOutCheckBoxState(False);
                      		btnSend.Enabled := False;
                      		btnRead.Enabled := True;
                      		lblBinHeader.Enabled := False;
                      		lblBin.Enabled := False;
                      		lblDecHeader.Enabled := False;
                      		lblDec.Enabled := False;
                      		edtSend.Enabled := False;
                      		btnSend.Enabled := False;		
                      		end; // Status Register
                      		(* the status register is read-only*)
                                      2:
                      		begin
                      		SetPinOutCheckBoxState(False);
                      		btnSend.Enabled := True;
                      		btnRead.Enabled := True;
                      		lblBinHeader.Enabled := False;
                      		lblBin.Enabled := False;
                      		lblDecHeader.Enabled := False;
                      		lblDec.Enabled := False;
                      		edtSend.Enabled := True;
                      		btnSend.Enabled := True;		
                      		end; // Control Register
                      	end;
                      
                      end;

Open in new window


After that, we are going to put 8 CheckBoxes onto the grbPins and alter its property accordingly to the Table 4.

Table 4
Theses CheckBoxes will simulate the port's data pins, because these pins are used the most in electronic projects, the component can associate the pin state directly using the method below:

SetPin(String Name, Boolean State);

Open in new window


This method needs the pin's Name (the string para-meter) and the State Boolean parameter (that represents the pin's state, low or high) for setting the desired state of one of the data pins.

Because we are going to shift between registers, we need a routine to disable the CheckBoxes when we are using the status and control registers, in order to do that type the Listing 3 code.

Listing 3: Code for enabling or disabling the CheckBoxes representing the data pins
procedure TFrmMain.SetPinOutCheckBoxState(State: Boolean);
                      var
                      I : Integer;
                      C : TCheckBox;
                      begin
                          for i := 0 to ComponentCount -1 do
                          begin
                              if Components[i] is TCheckBox then
                              begin
                                  C := TCheckBox(Components[i]);
                                  if Copy(UpperCase(C.Name), 1, 4)= 'CHKD' then
                                  C.Enabled := State;
                              end;
                          end;
                      end;

Open in new window


Do not forget to prototype this method in the uMain.pas! By using the typecasting technique this method will verify all the form's components, and if the component belongs to the TCheckBox class, then we will enable or not, the data pins CheckBoxes, depending on the State parameter's value.

Now insert 3 buttons in the form that will be called btnOpen, btnSend and btnRead respectively, modify their Enable property to False; double click btnOpen and on its OnClick event type the code from Listing 4.

Listing 4: Code from btnOpenClick event
procedure TfrmMain.btnOpenClick(Sender: TObject);
                      begin
                      case btnOpen.Tag of
                          0 :
                          begin
                              btnOpen.Caption := 'Close';
                              btnOpen.Tag     := 1;
                              LPTPort1.LPTPort := $378; // LPT1 by default
                              if LPTPort1.Open then
                              begin
                                  SetPinOutCheckBoxState(True);
                                  cbLPTPort.Enabled  := True;
                                  btnSend.Enabled  := True;
                                  btnRead.Enabled     := True;
                              end;
                          end;	    
                         1 :
                          begin
                              btnOpen.Caption := 'Open';
                              btnOpen.Tag     := 0;
                              LPTPort1.Close;
                              SetPinOutCheckBoxState(False);
                              cbLPTPort.Enabled    := False;
                              btnSend.Enabled    := False;
                              btnRead.Enabled       := False;
                          end;
                          end;
                      end;

Open in new window


By using this button's Tag property, which initially is 0, we will define the port's base address and load the dlls needed to access the port's hardware using the method Open from the LPTPort, this button's click will also enable the btnSend and btnRead button, making the CheckBoxes available. If you wish to unload the dlls and have no more hardware Access just click btnOpen again, this will call the Close method.

Back to the CheckBoxes, we need a method for verifying the CheckBoxes Checked property state and apply this to the state of the data pins, therefore reflecting the port's state and value. For this prototype the function OnPinOutClick in the uMain.pas and type the code from Listing 5.

Listing 5: Code for pin activation by using the CheckBoxes
procedure TfrmMain.OnPinOutClick(Sender: TObject);
                      var
                      C   : TCheckBox;
                      Pin : String;
                      begin
                          if Sender is TCheckBox then
                          begin
                              C := (Sender as TCheckBox);
                              Pin := Format('D%u' , [C.Tag]);
                              case C.State of
                                cbChecked   : LPTPort1.SetPin(Pin, True);
                                cbUnChecked : LPTPort1.SetPin(Pin , False);
                              end;
                              GetState;
                          end;
                      end;

Open in new window


How to list the computer's LPT ports
It's possible to list all available computers' LPT Ports using the LPTPort, to do that we are going to insert a ComboBox onto the grbPins and change its Name property to cbLPTPort, and on its DropDown event we will use the following code:

procedure  TfrmMain.cbLPTPortDropDown(Sender: TObject);
                      begin
                        LPTPort1.EnumPorts(cbLPTPort.Items);
                      end;

Open in new window


The method EnumPorts receives a TStrings parameter, and lists all LPT ports available and stores them in the cbLPTPort.Items property.

Listing the LPT Port's address
It's also possible to determine the parallel's port address, by using the component's Ports property. This property is a list of all the computer's parallel ports, and with this list we can recover both port's Name and Address, using respectively the properties Name and Port from the Ports list, just by using the code below on the cbLPTPort's OnClick event:

procedure TfrmMain.cbLPTPortClick(Sender: TOb-ject);
                      begin
                      if cbLPTPort.ItemIndex = -1 then Exit;
                          LPTPort1.SelectPortByIndex(cbLPTPort.ItemIndex);
                          lblPort.Caption := Format('%s = 0x%.3xH' ,
                          [LPTPort1.Ports[cbLPTPort.ItemIndex].Name ,
                           LPTPort1.Ports[cbLPTPort.ItemIndex].Port]);
                      end;

Open in new window


Writing values to the LPT port
Now we are going to write values to the parallel port, the LPTPort facilitates writing values to the port, we need to use 2 Edits that Will be used respectively to send and store the parallel port's value, name this Edits as edtSend and edtRead, in Figure 5, we have a GUI suggestion for this application, but nothing's better than use your creativity in creating GUI's.

Figure 5
Double click the btnSend button and add the code from Listing 6 on its OnClick event, this code will send the numerical value typed in the edtSend to the  component's unit, we verify if the value stored the in edtSend.Text is really a numerical value, otherwise, we show a message using the MessageDlg function. This way, according to the selected RadioButton, we selected register in the switch case code. First we recover the edtSend's Text property value using the Trim function, which removes empty characters, (e.g. Carrier return) from the string's beginning and end. And using the IsNumeric function declared in the component's unit, we verify if the value stored the in edtSend.Text is really a numerical value, otherwise, we show a message using the MessageDlg function. This way, according to the selected RadioButton, we choose the desired port's register (just remembering that the status register, $379 address, is read-only ), after this verification we need convert this string into a integer value and send it to the port using the LPTPort's  Out function. In case the data register is the selected one, we update the labels lblDec and lblBin Caption property using the GetState.  
Note that we can send to the data register values up to 255, because each pin represent a bit, with a total of 8 bits (1 byte), so we have a range from 0 to 255.

Listing 6: Code for writing values to the LPT port (btnSendClick)
procedure TfrmMain.btnSendClick(Sender: TObject);
                      var
                      S : String;
                      begin
                          S := Trim(edtSend.Text);
                          if not(IsNumeric(S)) then
                          begin
                              MessageDlg(Format('Value %s incorrect!', [S]), mtWarning, [mbOk], 0);
                              Exit;
                          end;
                      
                          case rdgRegisters.ItemIndex of
                          0 : LPTPort1.SelectPort($378); //Data Register address
                          1 : LPTPort1.SelectPort($379); //Status Register address
                          2 : LPTPort1.SelectPort($37A); //Control Register address
                          end;
                      
                          LPTPort1.out(StrToInt(S));
                          if rdgRegisters.ItemIndex = 0 then GetState;
                      
                      end;

Open in new window


Reading values from the LPT port
Now that we already know how to send values to the parallel port, we are going to move on reading values from the port. In order to do this, first double click the btnRead and type the code listed at Listing 7 in the button's OnClick event. Note that we selected the base address (e.g $378) and depending on the selected register in the switch case command we are altering the LPTPort's InpOffset property value, this property holds an integer value, that selects what register is going to be read by the LPTPort's Inp function. So if we want to read, for example the control register, we have to select the base address ($378 in our case), and modify the InpOffset property to the value 2. In our example we are reading the selected register value and showing it in the edtRead's Text property.
 
Listing 7: Code for reading values from the LPT port (btnReadClick)
procedure TfrmMain.btnReadClick(Sender: TObject);
                      begin
                        LPTPort1.SelectPort($378);
                        case rdgRegistros.ItemIndex of
                          0 : LPTPort1.InpOffset := 0; //Data Register Offset
                          1 : LPTPort1.InpOffset := 1; //Status Register Offset
                          2 : LPTPort1.InpOffset := 2; //Control Register Offset
                        end;
                      
                        edtRead.Text := IntToStr(LPTPort1.Inp);
                      
                      end;

Open in new window


Some tips
The development of programs that need to access the parallel port can be a hard time those who don't have basic electronics knowledge, because the measurement of the pin's voltage facilitates the visualization of what's going on with the port's pins. There is a parallel port monitor tool that enables through graphic visualization, the pin's state and one can read, write values to the port's registers, called Parallel Port Monitor (see Figure 6), I really recommend using this tool during the development of any programs that make use of the parallel port. This free tool can be downloaded at: http://www.geekhideout.com/downloads/parmon.zip.

 Figure 6
Conclusions
As we can see in this article, it's pretty easy to control the parallel port's registers in Delphi using the LPTPort component, without the need of dlls. This way we can enjoy our good old LPT ports for developing device control, electronics projects, LCD interfacing, etc. That's all for now people, see you next time.

NOTE: The I/O port level controlling details here has proven to work well with parallel ports on the PC motherboard and expansion cards connected to ISA bus. The programming examples might not work with PCI bus based I/O cards (they can use different hardware and/or I/O addresses, their drivers make they just look like parallel ports to "normal" applications). The programming examples do not work with USB to parallel port adapters (they use entirely different hardware, their drivers make them to look like normal parallel port to operating system "normal" applications).


8
22,982 Views

Comments (1)

Commented:
hi
can this be used to program a full scree video with lots of led light for displaying images or letters for street advertisement?

Have a question about something in this article? You can receive help directly from the article author. Sign up for a free trial to get started.