with Intermec and Zebra Bar Code Printers
If you're administering a UNIX application requiring bar
code printer support, plan on serious development time. Every programming
example I've seen is DOS/Windows based. In this article, I
will demonstrate a UNIX "C" tool kit for communicating
with Intermec and Zebra bar code printers, both serial and TCP/IP
network connected. Intermec and Zebra are two premier providers
of quality bar code printers.
The demonstration program, bc_demo, uses the tool kit to
create an Intermec and Zebra test label containing human-readable
text, code 39 bar codes, and the ubiquitous Consumer Electronics
(CE) graphics character. Figure 1 shows the example label.
In normal production, required data such as labels, serial devices,
network ports, and data sent to the bar code printer are stored
in a database, a file system, or other repository and retrieved
as needed. Because the emphasis is on using the tool kit, these
objects are hard-coded in a "C" structure and arrays of
character pointers to strings and are not retrieved.
How Do Bar Code Printers Work?
The printer vendors provide a control language for controlling
the printer's firmware downloading a label format to the printer's
RAM. The label format is the definition of what objects appear on
the label. For a given object, the label defines the font and size,
location, human readable or bar code, or whether the field data
is constant or variable. Also, user-defined graphics characters,
such as the CE mark, may also be downloaded to the printer.
Once a format exists in the printer's RAM, the control language
downloads the required variable data for each required label and
prints the label. Typically, a format downloads to the printer once
and is used many times.
For Intermec and Zebra printers, the control language is the Intermec
Printer Command Language and the Zebra Programming Language II (ZPL
II), respectively. A detailed explanation of these languages is
beyond the scope of this article. Just enough control language is
used to download and print the example label in Figure 1.
What is the Environment?
The tool kit was developed under Solaris 7 (11/99) using
an Intel Dual Xeon System with an Intel Pro100+ network adapter.
Because the operating system is Solaris, the network communication
will be decidedly BSD. The printers are the Intermec 3400B (200
dpi) and the Zebra 105Se (203 dpi). While the tool kit functions
with any printer supporting either of the two control languages,
the demo label's real estate (i.e., where the objects reside
on the label) is designed for the above printers.
How Does the bc_demo Program Work?
In a production environment, the label format and graphics
characters are downloaded once to the printer. From that point,
for each label required, the variable data will be collected, and
in one step, the data and commands will be downloaded, and the label
will be printed. Emulating production, the demo program (bc_demo)
downloads the required format and prints two labels with differing
data. The pseudo code is:
1. Choose the printer (Intermec TCP/IP, Zebra TCP/IP, Intermec
Serial, or Zebra Serial).
2. Open the network port or serial device based on the chosen
3. Download the CE graphic character to the printer.
4. Download the label format to the printer.
5. Download the data to the printer for label 1 and print label
6. Download the data to the printer for label 2 and print label
7. Close the network port or serial device.
How Do I Choose a Printer?
Via the command line, choose the printer to demo. The keyword
intermectcpip is the Intermec network printer; zebratcpip
is the Zebra network printer; intermecserial is the Intermec
serial printer; and zebraserial is the Zebra serial printer.
In the structure declaration section of Listing 1 (printer_choice),
declare the four keyword definitions: the function to call using
a pointer to a function, (*fptr()), and the three arguments
to the function. The arguments are the network address or serial
device, the port number of the network device or baud rate of the
serial device, and the printer choice (1 = Intermec, 2 = Zebra).
Assign the address of the function to call -- either bc_tcpip
or bc_serial -- to the pointer to a function. At runtime,
the loop processing the command-line options determines the correct
function to call based on printer choice. The NULL pointer at the
end of the program structure terminates the loop. The functions
bc_tcpip and bc_serial must contain the same argument
types and return the same data type.
How Do I Use a Serial Printer?
If the choice is a serial printer, the bc_serial
function in serial.c (Listing 2) executes with the following
parameters: which serial port to open, the baud rate (9600 in the
example), and which serial printer to use (Intermec = 1 and Zebra
In UNIX, a device can be opened just as a file is opened. The
open function opens the device and returns the operating
system's lowest available integer file descriptor. The bitwise
inclusive (OR or O_RDWR and O_SYNC) opens the
device for read-write and causes data to be delivered to the device
Once the file descriptor is obtained, a call to the set_terminal
function manipulates printer options using the termio structure
and the ioctl control device function:
1. The terminal input control flag, termio.c_iflag, with
an inclusive OR sets the ignore break condition, (IGNBRK),
inputs parity checking (INPCK), strips characters to 7 bits
(ISTRIP), and enables start/stop control (IXON|IXOFF).
2. The terminal control mode flag, termio.c_cflag, bitwise
inclusive OR sets the baud rate (B9600 or B19200),
enables receiver (CREAD), 7-bit character size (CS7),
and enables parity (PARENB).
3. The terminal special control character array, termio.c_cc[VMIN]
= 1 and c_cc[VTIME] = 0, controls processing just one character
at a time.
The original terminal values were saved before calling set_terminal.
When the label printing is completed, the original terminal values
How Do I Communicate with the Printer?
Once the printer is opened, write objects to the device
using the write_load_commands function, located in bc_demo.c.
The write_load_commands parameters are the array of character
pointers comprising the data and the integer file descriptor returned
from the open function call or from a network socket call.
For each line of text in the array, you can call the write_port_data
function, also located in bc_demo.c, to write the string
to the printer. The write_port_data function writes the passed
string one character at a time up to the null terminator.
Any array of pointers passed to the write_load_commands
function must be terminated with a null character pointer, (char
How Do I Download a Graphics Character?
The first object to download is the CE graphics character
located in cemarks.h, Listing 3. The array of character pointers
containing the Zebra CE definition is *zebra_ce and the
Intermec definition is *intermec_ce. Download the correct
array based on which printer is chosen.
Intermec and Zebra each have their own method for creating graphics
characters from bit-mapped objects. While the methods are beyond
this article's scope, Intermec calls their characters 6-bits
per byte graphics and Zebra refers to their graphics as hexadecimal
Besides the obvious differences of graphics definition and command-language
syntax, a few of the differences between the two vendors are:
1. Zebra requires the graphics character to be downloaded as a
one-character stream, while the Intermec supports writing multiple
2. Zebra identifies the character by name (i.e., CEMARK.ZPL) while
Intermec identifies the character by graphics position in the printer
3. Intermec can, optionally, embed a graphics character definition
within a normal label format and download both at the same time.
Zebra requires graphics characters to be downloaded separately from
the label format.
4. Intermec embeds the command to print a previously downloaded
graphics character within the label format, while Zebra embeds the
print graphics command with the other print commands.
How Do I Download a Label Format?
The Intermec and Zebra label formats are stored in intermec.h,
Listing 4, and zebra.h, Listing 5, respectively. As with
the graphics characters, labels are stored in an array of character
points with the Intermec format stored in *load_intermec_label
and the Zebra format stored in *load_zebra_label. Via a
call to the write_load_commands function, you can download
the correct array based on the chosen printer.
How Do I Print the Label?
Once the graphics character and label format are downloaded,
download the data and print commands to the printer and the label
prints. The Intermec and Zebra data and print commands are also
stored in intermec.h and zebra.h respectively. The
first Intermec label is *print_intermec_label1, and the
second is *print_intermec_label2. The Zebra data and print
commands are named similarly in zebra.h. Via a call to write_load_commands
function, download the correct array based on the chosen printer
and the label prints.
If the choice is to use a TCP/IP network printer instead of a
serial printer, follow the same downloading and printing scenario
as with the previously described serial printer, only replace the
serial open with a TCP/IP network open.
How Do I Use a TCP/IP Network Printer?
To use the TCP/IP printer, the bc_tcpip function
in tcpip.c (Listing 6) executes with three parameters: the
network address to open, the network port (3001 for Intermec and
9100 for the Zebra), and the network printer (Intermec = 1 and Zebra
The open_network_device function, using the Berkeley Network
Library Routines, opens the printer port and returns a socket descriptor
analogous to the file descriptor when opening a file:
1. Assuming the printer address is properly set up in /etc/hosts,
resolve the printer address by calling the gethostbyname
library function returning a pointer to a hostent structure.
2. To perform the network I/O, create a socket descriptor by calling
the socket library function returning an integer socket descriptor.
The arguments to the socket function are the AF_INET constant
to designate the Internet protocol and the SOCK_STREAM constant
defining a stream socket.
3. Before making a connection to the printer, create a socket
structure of type sockaddr_in:
A. Initialize the socket with a bzero function call.
B. Set the socket family to the AF_INET constant designating
the Internet protocol.
C. Set the socket address to the hostent address determined in
D. Set the socket port number and verify proper byte ordering
with the htons library function.
4. Establish the TCP/IP connection using the library function
connection. The arguments to connection function are the socket
descriptor determined in Step 2 and the address of the socket structure
determined in Step 3.
5. Return the socket descriptor to the bc_serial function
and continue processing.
How Do I Compile and Link bc_demo?
Under Solaris, the TCP/IP program tcpip.c, requires
a different C compiler from the default to compile and link correctly.
The Makefile (Listing 7) will compile and link the bc_demo
This article has presented a tool kit for communicating
with serial printers. I'd like to conclude by noting the following
1. A two-second sleep command exists after both the serial and
network open because a small amount of time is required to ensure
the device is ready to accept input. Production code should provide
a more foolproof method than a sleep.
2. The serial part of the tool kit is designed using the Solaris
serial ports. If you're using a port server on a different
UNIX variant, I do not guarantee the tool kit will function correctly.
If trouble arises using a port server, experiment with the open
function parameters. As a last resort, download the data in the
structures in cemarks.h, intermec.h, and zebra.h
to an ASCII file and concatenate the contents of the file to the
serial port device using the UNIX cat command.
3. The Intermec network printer setup requires a step not documented.
Point your browser to the Intermec printer address (both the Intermec
and the Zebra bar code printers have their own home page). After
logging in as administrator, change the following:
A. Check the "Reverse Telnet Options Enabled" box.
B. Set the Printer 1 Reverse Telnet Port Number to the proper
port number (3001 in this case).
Austin, Lori. Intermec 3400 Bar Code Label Printer User's
Manual, Intermec Corporation, 1995.
ZPL II Programming Guide, Zebra Technologies Corporation,
Stevens, W. Richard. Unix Network Programming, Prentice-Hall,
The word "Intermec" is a registered trademark
of Intermec Corporation. The word "Zebra" is a registered
trademark of Zebra Technologies Corporation.
Ed Schaefer is a frequent contributor to Sys Admin.
He is a Senior Programmer Analyst for Intel's Factory Integrated
Information Systems (FIIS) Group of Aloha, OR. His viewpoints on
bar code printing or any other subject do not reflect Intel's