How To Connect a USB TO IEEE 1284 Parallel Printer Cable To Visual Studio April 15th 2009
I am doing this is a favor for other embedded project experimenters for their bread board projects & experiments.
By Jim Fox – fimjox@yahoo.com – Maybe available for consulting

Update July 15th, 2009

I needed to be able interface from Visual Studio C# and toggle bits on a Parallel Port in the same manner that I could using the Output Instructions in C and Assembly using a normal old DOS parallel port.

I used the Sabrent Connectivity Solution USB to IEEEE 1284 Parallel Printer Cable adapter which only costs about $10 You do not need the drivers on the CD if you are using XP or Vista.


Sabrent Connectivity Solution USB to IEEEE 1284 Parallel Printer Cable

I want to be able to set bits High or Low to control LEDs using CMOS logic chip or can also be used with relays or using JFET or transistor as drivers for a stepper motor controllers, even to communicate to an embedded CPU.

I was not able to access the USB port using old fashioned DOS LPT drivers. I did not have to write a driver.

It was tricky figuring out which pins to connect together on the Centronics 36 pin connector. This can also be used with a USB adapter that uses a normal DB25 connector or PS2 connector. I just happened to buy the Centronics version, so I can also use it on an old laser printer I had.

I would imagine you can also input bit levels using the bidirectional features of the 1284 specification.

You could also use the USB Bit Whacker Board or Arduino board, but this is cheaper and I think easier. At least once I figured out what to do it was easy.

Vista.

Instructions:

An external 5 Volt power supply was needed because pin 18 did not have the +5 Volts that was called for in the specification.
Actually the spec says the printer is supposed to supply the 5V.
To start I bought a female Centronics 36 pin connector and wired the following:

Wire BUSY (pin 11) to any Ground (pins 19-29)
Wire SELECT (pin 13) to +5V (should be pin 18 but test)
Wire STROBE (pin 1) to ACK (pin 10)

I then used D0 (pin 2) – D8 (pin 9) as output pins I could access from C#


Photo Of My Kludge They Grey Wires I Used As Output Bits To Control LEDs

I then installed a printer by clicking Add A Local Printer from Control Panel → Printers
Then select the Port to be USB001 (Virtual Printer Port for USB)
Select from the list of printer Generic / Text Only This loads the proper driver.
Then from the Printer Properties Advanced settings I turned off Spooling the Spool Print Documents selection and selected Print Directly To The Printer

Plug the Female Connector into the Male Connector on the USB cable.

The Generic Printer should now be displayed as Ready in the Printer Control Panel.

You should now be able to print a test page and see on an oscilloscope the data bits toggle.

May 27th,2009 – I believe you have to always use the same USB connector plug on your computer that the USB Parallel Printer Cable was plugged into when the generic printer was installed. It may have something to do with the USB001 etc... Number refers to which physical USB connector / plug you are using on your computer.


July 9th, 2009 – Note in my original tests, I hooked up the Printer Parallel Port Data Bits directly to CMOS logic chips. If you are connecting the Port to input ports on an embedded CPU, you will need to toggle the ACK lines with software in the embedded CPU. You will have to use an output port bit on the embedded CPU to bring it low when you have processed any data received from the USB. This is because transfers are about 2 microseconds a parallel byte transfer. Unless you have a very fast embedded CPU and a very quick loop to receive the data, you probably can't keep up with the transfers.


July 11th, 2009

BUSY and BUSY*

When the host sets the STROBE* line LOW to initiate the transfer of a byte of data to the printer, the printer must immediately raise the BUSY signal. BUSY must go HIGH within 500nsec of STROBE* going low. Typically, when the printer detects the STROBE* line going LOW, it sets BUSY HIGH prior to even reading the data byte from the DATA lines. This is acceptable because the host is forbidden from changing the contents of the DATA lines until the printer indicates that it has read the DATA lines.

Once the printer has read the data byte from the DATA lines, the printer may process that character, perform printing operations, or allow the host to send additional data. Until the printer sets the BUSY line to LOW, the host must not send any additional bytes. There is no limit on how long the BUSY line may remain HIGH, and some slower printers keep the BUSY line high while printing an entire line or even an entire page of text.

STROBE*

When the host transmits data to the printer, it first places an eight-bit byte to be sent on the DATA lines, DATA0 through DATA7. Once those electrical levels are stable, the STROBE* line goes LOW for 1.5 microseconds, plus or minus 500nsec. The printer is alerted to the presence of new data when the STROBE* line goes LOW.

Although Centronics-compatible computers typically used a one-shot timer to ensure that the STROBE* pulsed for the correct minimum amount of time with no other intervention by the host processor, the IBM PC elected to omit the electronics needed to do this, and instead the host processor software should execute an OUT instruction to the printer control port to make STROBE* go LOW, and then execute a second OUT instruction to return STROBE* to a HIGH state. (The data on the DATA lines must remain unchanged until further notice.)

Because different computers operate at different speeds, the timing of STROBE* required by IBM PC compatible printers is quite lax, with some allowing the pulse to be as long as 100msec. Despite this enormous amount of time (compared to the 1.5usec typical), slower IBM PC compatible computer operating systems may have to briefly disable interrupts in the printer driver software in order to perform the two OUT instructions that are needed to control the STROBE* signal on that platform.

When STROBE* goes low, BUSY must be set HIGH by the printer within 500nsec. This is less than the minimum duration of the STROBE* pulse.

July 15th, 2009

To intrerface to an external embedded CPOU, I am redesigning using a 555 in a bistable latch mode. Using the SROBE line going low to set the 555 high. The 555 output is then hooked up to the BUSY line. The STROBE is also used to cause a hardware interrupt input on my embedded CPU. After the interrupt is handled by the CPU, When the CPU is done reading the Parallel Port Data bits and processing them and ready to receive the next byte. I use an output bit on the CPU to reset the output of the 555 to a 0, telling BUSY ready to receive the next byte. That should work.


I don't think you need the pullup resistors in the below schematic.


555/556 Bistable (flip-flop) - a memory circuit



555 bistable circuit

The circuit is called a bistable because it is stable in two states: output high and output low. It is also known as a 'flip-flop'.

It has two inputs:

The power-on reset, power-on trigger and edge-triggering circuits can all be used as described above for the monostable.





It may have been that the Generic Printer showed to be offline until I plugged in my old HP LasterJet 4 into the USB Centronics Cable. It would then load the HP drivers and both the HP & Generic printer showed to be online.

Make sure that the Generic printer port in it's printer properties is the same USBx as the printer you plugged in In my case USB2 was the HP and I'd set the Generic to USB2
Sometimes the Generic would out of the blue go offline again and I'd have to replug the HP back in and repeat the above steps. Pain in the butt. This is more of hack but works'ish.



36 Pin Centronic Connector PinOut

Pin #

Pin Name

Pin Description and Function

1

/STROBE

Data Strobe (May be called /PSTROBE, HostCLK)

2

D0

Data Bit 0

3

D1

Data Bit 1

4

D2

Data Bit 2

5

D3

Data Bit 3

6

D4

Data Bit 4

7

D5

Data Bit 5

8

D6

Data Bit 6

9

D7

Data Bit 7

10

/ACK

Acknowledge receipt of Data (or /PACK, PtrCLK)

11

BUSY

Strobe received, Waiting on Acknowledge (or /PBUSY, PtrBusy)

12

PAPER ERROR

Paper Out / Paper Error (AckDataReq)

13

SELECT Out

Daisy-Chain Device Select Signal (May be tied high in some Printers)

14

/AUTOFEED

Auto-Feed paper, Not used with PostScript printer (HostBusy)

15

Select IN

Daisy-Chain IN (This Is Not The Select Pin To Use)

16

Signal GND

Logic Ground

17

CHASSIS GND

Shield Ground

18

+5 V PULLUP

+5 V DC (50 mA max)

19

GND

Signal Ground (Strobe Ground)

20

GND

Signal Ground (Data 0 Ground)

21

GND

Signal Ground (Data 1 Ground)

22

GND

Signal Ground (Data 2 Ground)

23

GND

Signal Ground (Data 3 Ground)

24

GND

Signal Ground (Data 4 Ground)

25

GND

Signal Ground (Data 5 Ground)

26

GND

Signal Ground (Data 6 Ground)

27

GND

Signal Ground (Data 7 Ground)

28

GND

Signal Ground (Acknowledge Ground)

29

GND

Signal Ground (Busy Ground)

30

/GNDRESET

Reset Ground

31

/RESET

Cancel Current Job (May be called /PRIME)

32

/FAULT

Fault with Printer (Low when offline)

33

0 V

Signal Ground

34

n/c

Not used

35

+5 V

+5 V DC

36

/SLCT IN

Select In; Taking low or high sets printer on line or off line

36 Pin Centronics Signal Name Description

Data8 - Data1

Unidirectional data lines. Data8 is the most significant.

STROBE*

Data is valid during an active low pulse on this line.

AUTOFD*

Usage of this line varies. Most printers will perform a line feed after each carriage return when this line is low, and carriage returns only when this line is high.

INIT*

This line is held low for a minimum of 50mils to reset the printer and clear the print buffer.

SelectIn*

The host drives this line low to select the peripheral.

ACK*

The peripheral pulses this line low when it has received the previous data and is ready to receive more data. The rising edge of ACK* can be enabled to interrupt the host.

BUSY

The peripheral drives this signal high to indicate that it is not ready to receive data.

PError

Usage of this line varies. Printers typically drive this signal high during a paper empty condition.

Select

The peripheral drives this signal high when it is selected and ready for data transfer.

FAULT*

Usage of this line varies. Peripherals usually drive this line low when an error condition exists.



I used the following code I got from Microsoft for accessing the Parallel Port from Visual Studio.
The string s which is set to “Hello” in the method button2_Click to a series of data I wanted to output to the port
I did notice that the method calls to OpenPrinter, StartDocPrinter only had be called once at the beginning of the session.

Likewise the EndDocPrinter & ClosePrinter method calls only had to be called once at the end of the session. (Don't think is actually needed)
But every time SendBytesToPrinter is called code still has to call StartPagePrinter before SendBytesToPrinter and EndPagePrinter after.

If you loop and keep calling SendStringToPrinter, using StartPagePrinter & EndPage Printer, The string gets sent out very quickly as a packet, but there is about a 1 millisecond delay before the next packet.

There are probably other ways to clean up the code to make it run faster. I was able to toggle bits using a 2 gHz dual CPU of packets at 200+ kBytes a second
In C#, I did something like this:


public static void AniUpdate() // Update the screen from CurPixel & CurSlice

{

string outputDataString = "";

outputDataString += 0x55; // Build an output string of databytes. As little as 1 byte or as many you want to send

outputDataString += 0xAA;

...

if (pd != null) // test of USB online before printning

RawPrinterHelper.SendStringToPrinter(pd.PrinterSettings.PrinterName, LevelString); // This is in Microsoft Library that follows.

}


FROM MICROSOFT:

C# Code:
Create a Project That Prints Preformatted Data

  1. Start Visual Studio .NET. On the File menu, click New, and then click Project. Under Project Types, click the Visual C# Projects folder. On the Templates list, click Windows Application, and then click OK. By default, Form1 is created.

  2. On the View menu, click Toolbox to display the Toolbox, and then add a button to Form1. This button is named Button1.

  3. Add another button to Form1. This button is named Button2.

  4. Double-click Button1. The code window for the form appears.

  5. Replace the Button1_Click subroutine with the following code:

    private void button1_Click(object sender, System.EventArgs e)
    {
        // Allow the user to select a file.
        OpenFileDialog ofd = new OpenFileDialog();
        if( DialogResult.OK == ofd.ShowDialog(this) )
        {
            // Allow the user to select a printer.
            PrintDialog pd  = new PrintDialog();
            pd.PrinterSettings = new PrinterSettings();
            if( DialogResult.OK == pd.ShowDialog(this) )
            {
                // Print the file to the printer.
                RawPrinterHelper.SendFileToPrinter(pd.PrinterSettings.PrinterName, ofd.FileName);
            }
        }
    }
                                            
  6. Replace the Button2_Click subroutine with the following code:

    private void button2_Click(object sender, System.EventArgs e)
    {
        string s = "Hello"; // THIS IS THE STRING YOU SET FOR THE DATA YOU WANT TO OUTPUT
        
        // Allow the user to select a printer.
        PrintDialog pd  = new PrintDialog();
        pd.PrinterSettings = new PrinterSettings();
        if( DialogResult.OK == pd.ShowDialog(this) )
        {
            // Send a printer-specific to the printer.
            RawPrinterHelper.SendStringToPrinter(pd.PrinterSettings.PrinterName, s);
        }
    }
                                            
  7. Insert the following code at the top of the file:

    using System;
    using System.Drawing;
    using System.Drawing.Printing;
    using System.Windows.Forms;
    using System.Runtime.InteropServices;
                                            
  8. Add the following code inside the main application namespace but outside any class definitions:

    public class RawPrinterHelper
    {
        // Structure and API declarions:
        [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi)]
        public class DOCINFOA
        {
            [MarshalAs(UnmanagedType.LPStr)] public string pDocName;
            [MarshalAs(UnmanagedType.LPStr)] public string pOutputFile;
            [MarshalAs(UnmanagedType.LPStr)] public string pDataType;
        }
        [DllImport("winspool.Drv", EntryPoint="OpenPrinterA", SetLastError=true, CharSet=CharSet.Ansi, ExactSpelling=true, CallingConvention=CallingConvention.StdCall)]
        public static extern bool OpenPrinter([MarshalAs(UnmanagedType.LPStr)] string szPrinter, out IntPtr hPrinter, IntPtr pd);
    
        [DllImport("winspool.Drv", EntryPoint="ClosePrinter", SetLastError=true, ExactSpelling=true, CallingConvention=CallingConvention.StdCall)]
        public static extern bool ClosePrinter(IntPtr hPrinter);
    
        [DllImport("winspool.Drv", EntryPoint="StartDocPrinterA", SetLastError=true, CharSet=CharSet.Ansi, ExactSpelling=true, CallingConvention=CallingConvention.StdCall)]
        public static extern bool StartDocPrinter( IntPtr hPrinter, Int32 level,  [In, MarshalAs(UnmanagedType.LPStruct)] DOCINFOA di);
    
        [DllImport("winspool.Drv", EntryPoint="EndDocPrinter", SetLastError=true, ExactSpelling=true, CallingConvention=CallingConvention.StdCall)]
        public static extern bool EndDocPrinter(IntPtr hPrinter);
    
        [DllImport("winspool.Drv", EntryPoint="StartPagePrinter", SetLastError=true, ExactSpelling=true, CallingConvention=CallingConvention.StdCall)]
        public static extern bool StartPagePrinter(IntPtr hPrinter);
    
        [DllImport("winspool.Drv", EntryPoint="EndPagePrinter", SetLastError=true, ExactSpelling=true, CallingConvention=CallingConvention.StdCall)]
        public static extern bool EndPagePrinter(IntPtr hPrinter);
    
        [DllImport("winspool.Drv", EntryPoint="WritePrinter", SetLastError=true, ExactSpelling=true, CallingConvention=CallingConvention.StdCall)]
        public static extern bool WritePrinter(IntPtr hPrinter, IntPtr pBytes, Int32 dwCount, out Int32 dwWritten );
    
        // SendBytesToPrinter()
        // When the function is given a printer name and an unmanaged array
        // of bytes, the function sends those bytes to the print queue.
        // Returns true on success, false on failure.
        public static bool SendBytesToPrinter( string szPrinterName, IntPtr pBytes, Int32 dwCount)
        {
            Int32    dwError = 0, dwWritten = 0;
            IntPtr    hPrinter = new IntPtr(0);
            DOCINFOA    di = new DOCINFOA();
            bool    bSuccess = false; // Assume failure unless you specifically succeed.
    
            di.pDocName = "My C#.NET RAW Document";
            di.pDataType = "RAW";
    
            // Open the printer.
            if( OpenPrinter( szPrinterName.Normalize(), out hPrinter, IntPtr.Zero ) )
            {
                // Start a document.
                if( StartDocPrinter(hPrinter, 1, di) )
                {
                    // Start a page.
                    if( StartPagePrinter(hPrinter) )
                    {
                        // Write your bytes.
                        bSuccess = WritePrinter(hPrinter, pBytes, dwCount, out dwWritten);
                        EndPagePrinter(hPrinter);
                    }
                    EndDocPrinter(hPrinter);
                }
                ClosePrinter(hPrinter);
            }
            // If you did not succeed, GetLastError may give more information
            // about why not.
            if( bSuccess == false )
            {
                    dwError = Marshal.GetLastWin32Error();
            }
            return bSuccess;
        }
    
        public static bool SendFileToPrinter( string szPrinterName, string szFileName )
        {
            // Open the file.
            FileStream fs = new FileStream(szFileName, FileMode.Open);
            // Create a BinaryReader on the file.
            BinaryReader br = new BinaryReader(fs);
            // Dim an array of bytes big enough to hold the file's contents.
            Byte []bytes = new Byte[fs.Length];
            bool bSuccess = false;
            // Your unmanaged pointer.
            IntPtr pUnmanagedBytes = new IntPtr(0);
            int nLength;
    
            nLength = Convert.ToInt32(fs.Length);
            // Read the contents of the file into the array.
            bytes = br.ReadBytes( nLength );
            // Allocate some unmanaged memory for those bytes.
            pUnmanagedBytes = Marshal.AllocCoTaskMem(nLength);
            // Copy the managed byte array into the unmanaged array.
            Marshal.Copy(bytes, 0, pUnmanagedBytes, nLength);
            // Send the unmanaged bytes to the printer.
            bSuccess = SendBytesToPrinter(szPrinterName, pUnmanagedBytes, nLength);
            // Free the unmanaged memory that you allocated earlier.
            Marshal.FreeCoTaskMem(pUnmanagedBytes);
            return bSuccess;
        }
        public static bool SendStringToPrinter( string szPrinterName, string szString )
        {
            IntPtr pBytes;
            Int32 dwCount;
            // How many characters are in the string?
            dwCount = szString.Length;
            // Assume that the printer is expecting ANSI text, and then convert
            // the string to ANSI text.
            pBytes = Marshal.StringToCoTaskMemAnsi(szString);
            // Send the converted ANSI string to the printer.
            SendBytesToPrinter(szPrinterName, pBytes, dwCount);
            Marshal.FreeCoTaskMem(pBytes);
            return true;
        }
    }
                                            
  9. Press F5 to build, and then run the program.

  10. Click Button1 to load and print a file's contents.

  11. Click Button2 to print a string. (You may have to eject the page manually because the string is sent without the formfeed command.)



Visual Basic Code:
Create a project that prints preformatted data

To create the project, follow these steps:

  1. Start Visual Studio .NET. On the File menu, click New, and then click Project. Under Project Types, click the Visual Basic Projects folder. On the Templates list, click Windows Application, and then click OK. By default, Form1 is created.

  2. On the View menu, click Toolbox to display the Toolbox, and then add a button to Form1. This button is named Button1.

  3. Add another button to Form1. This button is named Button2.

  4. Double-click Button1. The code window for the form appears.

  5. Replace the Button1_Click subroutine with the following code:

        ' Click event handler for a button - designed to show how to use the
        ' SendFileToPrinter and SendBytesToPrinter functions.
        Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
            ' Allow the user to select a file.
            Dim ofd As New OpenFileDialog()
            If ofd.ShowDialog(Me) Then
                ' Allow the user to select a printer.
                Dim pd As New PrintDialog()
                pd.PrinterSettings = New PrinterSettings()
                If (pd.ShowDialog() = DialogResult.OK) Then
                    ' Print the file to the printer.
                    RawPrinterHelper.SendFileToPrinter(pd.PrinterSettings.PrinterName, ofd.FileName)
                End If
            End If
        End Sub ' Button1_Click()
                                            
  6. Replace the Button2_Click subroutine with the following code:

        ' Click event handler for a button - designed to show how to use the
        ' SendBytesToPrinter function to send a string to the printer.
        Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
            Dim s As String
            Dim pd As New PrintDialog()
    
            ' You need a string to send.
            s = "Hello"   '  THIS IS THE STRING YOU SET FOR THE DATA YOU WANT TO OUTPUT
            ' Open the printer dialog box, and then allow the user to select a printer.
            pd.PrinterSettings = New PrinterSettings()
            If (pd.ShowDialog() = DialogResult.OK) Then
                RawPrinterHelper.SendStringToPrinter(pd.PrinterSettings.PrinterName, s)
            End If
        End Sub ' Button2_Click()
                                            
  7. Insert the following code at the top of the file:

    Imports System.IO
    Imports System.Drawing.Printing
    Imports System.Runtime.InteropServices
                                            
  8. Add the following code inside the main application namespace but outside any class definitions:

    Public Class RawPrinterHelper
        ' Structure and API declarions:
        <StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Unicode)> _
        Structure DOCINFOW
            <MarshalAs(UnmanagedType.LPWStr)> Public pDocName As String
            <MarshalAs(UnmanagedType.LPWStr)> Public pOutputFile As String
            <MarshalAs(UnmanagedType.LPWStr)> Public pDataType As String
        End Structure
    
        <DllImport("winspool.Drv", EntryPoint:="OpenPrinterW", _
           SetLastError:=True, CharSet:=CharSet.Unicode, _
           ExactSpelling:=True, CallingConvention:=CallingConvention.StdCall)> _
        Public Shared Function OpenPrinter(ByVal src As String, ByRef hPrinter As IntPtr, ByVal pd As Long) As Boolean
        End Function
        <DllImport("winspool.Drv", EntryPoint:="ClosePrinter", _
           SetLastError:=True, CharSet:=CharSet.Unicode, _
           ExactSpelling:=True, CallingConvention:=CallingConvention.StdCall)> _
        Public Shared Function ClosePrinter(ByVal hPrinter As IntPtr) As Boolean
        End Function
        <DllImport("winspool.Drv", EntryPoint:="StartDocPrinterW", _
           SetLastError:=True, CharSet:=CharSet.Unicode, _
           ExactSpelling:=True, CallingConvention:=CallingConvention.StdCall)> _
        Public Shared Function StartDocPrinter(ByVal hPrinter As IntPtr, ByVal level As Int32, ByRef pDI As DOCINFOW) As Boolean
        End Function
        <DllImport("winspool.Drv", EntryPoint:="EndDocPrinter", _
           SetLastError:=True, CharSet:=CharSet.Unicode, _
           ExactSpelling:=True, CallingConvention:=CallingConvention.StdCall)> _
        Public Shared Function EndDocPrinter(ByVal hPrinter As IntPtr) As Boolean
        End Function
        <DllImport("winspool.Drv", EntryPoint:="StartPagePrinter", _
           SetLastError:=True, CharSet:=CharSet.Unicode, _
           ExactSpelling:=True, CallingConvention:=CallingConvention.StdCall)> _
        Public Shared Function StartPagePrinter(ByVal hPrinter As IntPtr) As Boolean
        End Function
        <DllImport("winspool.Drv", EntryPoint:="EndPagePrinter", _
           SetLastError:=True, CharSet:=CharSet.Unicode, _
           ExactSpelling:=True, CallingConvention:=CallingConvention.StdCall)> _
        Public Shared Function EndPagePrinter(ByVal hPrinter As IntPtr) As Boolean
        End Function
        <DllImport("winspool.Drv", EntryPoint:="WritePrinter", _
           SetLastError:=True, CharSet:=CharSet.Unicode, _
           ExactSpelling:=True, CallingConvention:=CallingConvention.StdCall)> _
        Public Shared Function WritePrinter(ByVal hPrinter As IntPtr, ByVal pBytes As IntPtr, ByVal dwCount As Int32, ByRef dwWritten As Int32) As Boolean
        End Function
    
        ' SendBytesToPrinter()
        ' When the function is given a printer name and an unmanaged array of  
        ' bytes, the function sends those bytes to the print queue.
        ' Returns True on success or False on failure.
        Public Shared Function SendBytesToPrinter(ByVal szPrinterName As String, ByVal pBytes As IntPtr, ByVal dwCount As Int32) As Boolean
            Dim hPrinter As IntPtr      ' The printer handle.
            Dim dwError As Int32        ' Last error - in case there was trouble.
            Dim di As DOCINFOW          ' Describes your document (name, port, data type).
            Dim dwWritten As Int32      ' The number of bytes written by WritePrinter().
            Dim bSuccess As Boolean     ' Your success code.
    
            ' Set up the DOCINFO structure.
            With di
                .pDocName = "My Visual Basic .NET RAW Document"
                .pDataType = "RAW"
            End With
            ' Assume failure unless you specifically succeed.
            bSuccess = False
            If OpenPrinter(szPrinterName, hPrinter, 0) Then
                If StartDocPrinter(hPrinter, 1, di) Then
                    If StartPagePrinter(hPrinter) Then
                        ' Write your printer-specific bytes to the printer.
                        bSuccess = WritePrinter(hPrinter, pBytes, dwCount, dwWritten)
                        EndPagePrinter(hPrinter)
                    End If
                    EndDocPrinter(hPrinter)
                End If
                ClosePrinter(hPrinter)
            End If
            ' If you did not succeed, GetLastError may give more information
            ' about why not.
            If bSuccess = False Then
                dwError = Marshal.GetLastWin32Error()
            End If
            Return bSuccess
        End Function ' SendBytesToPrinter()
    
        ' SendFileToPrinter()
        ' When the function is given a file name and a printer name, 
        ' the function reads the contents of the file and sends the
        ' contents to the printer.
        ' Presumes that the file contains printer-ready data.
        ' Shows how to use the SendBytesToPrinter function.
        ' Returns True on success or False on failure.
        Public Shared Function SendFileToPrinter(ByVal szPrinterName As String, ByVal szFileName As String) As Boolean
            ' Open the file.
            Dim fs As New FileStream(szFileName, FileMode.Open)
            ' Create a BinaryReader on the file.
            Dim br As New BinaryReader(fs)
            ' Dim an array of bytes large enough to hold the file's contents.
            Dim bytes(fs.Length) As Byte
            Dim bSuccess As Boolean
            ' Your unmanaged pointer.
            Dim pUnmanagedBytes As IntPtr
    
            ' Read the contents of the file into the array.
            bytes = br.ReadBytes(fs.Length)
            ' Allocate some unmanaged memory for those bytes.
            pUnmanagedBytes = Marshal.AllocCoTaskMem(fs.Length)
            ' Copy the managed byte array into the unmanaged array.
            Marshal.Copy(bytes, 0, pUnmanagedBytes, fs.Length)
            ' Send the unmanaged bytes to the printer.
            bSuccess = SendBytesToPrinter(szPrinterName, pUnmanagedBytes, fs.Length)
            ' Free the unmanaged memory that you allocated earlier.
            Marshal.FreeCoTaskMem(pUnmanagedBytes)
            Return bSuccess
        End Function ' SendFileToPrinter()
    
        ' When the function is given a string and a printer name,
        ' the function sends the string to the printer as raw bytes.
        Public Shared Function SendStringToPrinter(ByVal szPrinterName As String, ByVal szString As String)
            Dim pBytes As IntPtr
            Dim dwCount As Int32
            ' How many characters are in the string?
            dwCount = szString.Length()
            ' Assume that the printer is expecting ANSI text, and then convert
            ' the string to ANSI text.
            pBytes = Marshal.StringToCoTaskMemAnsi(szString)
            ' Send the converted ANSI string to the printer.
            SendBytesToPrinter(szPrinterName, pBytes, dwCount)
            Marshal.FreeCoTaskMem(pBytes)
        End Function
    End Class
                                            
  9. Press F5 to build, and then run the program.

  10. Click Button1 to load and print a file's contents.

  11. Click Button2 to print a string. (You may have to eject the page manually because the string is sent without the formfeed command.)

References:

How to send raw data to a printer by using Visual Basic .NET http://support.microsoft.com/kb/322090

How to send raw data to a printer by using Visual C# .NET http://support.microsoft.com/kb/322091

Centronics and IBM Compatible Parallel Printer Interface Pin Assignment Reference: http://nemesis.lonestar.org/reference/computers/interfaces/centronics.html

Keywords: One Zero High Low Set Clear Reset 1 0