TechTip: Using Weintek HMI to Print a label on a Zebra Printer using Free Protocol

In this TechTip we’ll be looking at printing a label to a Zebra printer by sending the ZPL string to the com port or Ethernet port on the HMI.

Zebra printers are increasingly popular in production and packaging and are highly configurable and able to print barcodes, QR codes, Data matrix codes etc. (for reference, the printer used in this demo was a Zebra GK420t.)

This isn’t intended to be a lesson in the ZPL language, but we will run through the label string used for our demo:

The Label we’re creating, the variable data shown with { } – we’ll replace these variables with HMI data

The code we’ll use for this label is below:

^XA
^CF0,50
^FO100,50^FDLamonde Automation Ltd.^FS
^CF0,20
^FO100,100^FDDemonstration of using a Weintek HMI to generate a label on a^FS
^FO100,120^FDa zebra printer using free protocol.^FS
^FO100,160
^BXN,10,200,,,,,1
^FDPart Number: {PARTNUMBER}
^FS
^CF0,40
^FO350,170^FDDate: {DD}:{MM}:{YY}^FS
^FO350,210^FDTime: {HH}:{MM}:{SS}^FS
^FO350,250^FDOperator: {OPERATOR}^FS
^XZ

We can preview and tweak it using this webpage.

A breakdown of our demo ZPL code is below… a useful reference is this PDF.

^XA
A Zebra ZPL string starts with command ^XA
^CF0,50
^CFf,h,w is selecting a font (A-Z & 0-9), its height in dots and width in dots. Leaving “w” out means standard width for the height” . So here we are using font 0, 50 dots high, standard width.
^FO100,50^FDLamonde Automation Ltd.^FS
^FO,x,y,z is “Field Origin” x is the x position, y is the y position and z is the justification (0=left, 1 =right 2=script dependant). ^FD is the Field Data tag. and ^FS is the Field Separator tag. Our text field is at position 100,50.
^CF0,20
Changed font size here to 20 dots high, standard width

^FO100,100^FDDemonstration of using a Weintek HMI to generate a label on a^FS
Field Origin of 100,100 and some text
^FO100,120^FDa zebra printer using free protocol.^FS
Field origin of 100,120 and some more text
^FO100,160
Field origin of 100,60 for our datamatrix
^BXN,10,200,,,,,1
^BXo,h,s,c,r,f,g,a is the code format for generating a datamatrix
“o” is orientation (N=normal, R = rotated 90 c/w, I = inverted, B= read bottom up). “h” is height, “s” is quality (0, 50, 80, 100, 140, 200), “c” is columns to encode, “r” is rows to encode, “f” is format ID (0-6) – default is 6, which is full 256 ISO 8-bit set, “g” is escape control character, and finally “a” is aspect ratio – 1 = square, 2 = rectangle
^FDPart Number: {PARTNUMBER}
This is the Field Data that we will be putting in the datamatrix
^FS
Field separator
^CF0,40
Font 0, 40 dots high, default width
^FO350,170^FDDate: {DD}:{MM}:{YY}^FS
Field origin 30,170, Field Data, Field Separator
^FO350,210^FDTime: {HH}:{MM}:{SS}^FS
Field origin 350,210, Field Data, Field Separator
^FO350,250^FDOperator: {OPERATOR}^FS
Field origin 350,250, Field Data, Field Separator
^XZ
A Zebra string ends with command ^XZ

HMI Communication Setup

For Serial -you may need to change to suit your printer setup, but these are common settings – 9600, Parity: None, 8 Data Bits, 1 Stop bit:

For Ethernet -set the IP address to suit your printer, the port number for Zebra printers is usually 9100, but check your device:

Building The String and Sending To The Printer

In a previous TechTip, we covered using “StringLength” this TechTip uses what we did there to generate a string to send to a Zebra printer. We’re using StringLength since we don’t want any null characters and our data may vary in length.

The “partnumber” variable is from HMI Local Word LW0 onwards and “operator” variable is from HMI Local Word LW100 onwards. The resulting string is also placed in LW 1000 onwards – useful for verifying the output without printing using offline simulation.

Of course, in your application, you could use data from your PLC or connected device instead of HMI local words.

Time and date are taken from the HMI Local words -these are converted from decimal to ascii using the DEC2ASCII command.

The macro in full is shown below:

macro_command main()

char outputstring[600] // the output string

char header[300]="^XA^CF0,50^FO100,50^FDLamonde Automation Ltd.^FS^CF0,20^FO100,100^FDDemonstration of using a Weintek HMI to generate a label on a^FS^FO100,120^FDa zebra printer using free protocol.^FS^FO100,160^BXN,10,200,,,,,1^FD" // first fixed part of the code

char partnumber[24]	// partnumber is ascii char from LW0 onwards
char operator[10]	// operator variable (which we'll get from LW100 onwards)

char fs[3]="^FS" 		// fixed "^FS" code

char datepart[40]="^CF0,40^FO350,170^FDDate: "	// Fixed text for date label - ascii char
char timepart[40] ="^FO350,210^FDTime: "		// Fixed text for time label - ascii char
char operatorpart[40]="^FO350,250^FDOperator: " // Fixed text for operator label - ascii char

char xz[3]="^XZ"

char dateday[2]			// day as ascii char
char datemonth[2]		// month as ascii char
char dateyear[4]		// year as ascii char
char colon[1]=":" 		// colon separator
char hours[2]			// hours as ascii char
char minutes[2]="06"		// mins as ascii char
char seconds[2]="00"			// secs as ascii char

short datedaydec		// day as 16bit integer from the HMI
short datemonthdec		// month as 16bit integer from the HMI
short dateyeardec		// year as 16bit integer from the HMI
short hoursdec			// hours as 16bit integer from the HMI
short minutesdec		// minutes as 16bit integer from the HMI
short secondsdec		// seconds as 16bit integer from the HMI

short length1 			// first offset used in string concatenation
short length2			// second offset used in string concatenation

FILL(OutputString[0], 0, 600) // clear outputstring

GetData(datedaydec, "Local HMI", LW, 9020, 1) 	// get the day as a number and...
DEC2ASCII(datedaydec, dateday[0], 2)			// convert to ascii and put in dateday
GetData(datemonthdec, "Local HMI", LW, 9021, 1) // get the month as a number and... 
DEC2ASCII(datemonthdec, datemonth[0], 2)		// convert to ascii and put in datemonth
GetData(dateyeardec, "Local HMI", LW, 9022, 1)	// get the year as a number and... 
DEC2ASCII(dateyeardec, dateyear[0], 4)			// convert to ascii and put in dateyear

GetData(hoursdec, "Local HMI", LW, 9019, 1)		// get the hours as a number and...
DEC2ASCII(hoursdec, hours[0], 2)				// convert to ascii and put in hours
GetData(minutesdec, "Local HMI", LW, 9018, 1)  	// get the minutes as a number and...
DEC2ASCII(minutesdec, minutes[0], 2)			// convert to ascii and put in minutes
GetData(secondsdec, "Local HMI", LW, 9017, 1)	// get the seconds as a number and...
DEC2ASCII(secondsdec, seconds[0], 2)			// convert to ascii and put in seconds

GetData(partnumber[0], "Local HMI", LW, 0, 24)	// get partnumber
GetData(operator[0], "Local HMI", LW, 100, 10) 	// get operator name

StringCat(header[0], OutputString[0]) 			// concatenate header into OutputString
length1 = StringLength(header[0]) 				// measure header and put result into “length1”

StringCat(partnumber[0], OutputString[length1]) // offset partnumber into outputstring by length of header (length1)
length2 = StringLength(partnumber[0]) + length1 // measure partnumber and add to length1 to give the next offset

StringCat(fs[0], OutputString[length2]) 		// use length2 as the offset
length1 = StringLength(fs[0]) + length2 		// measure fs and add to length2 to give the next offset

StringCat(datepart[0], OutputString[length1]) 	// etc
length2 = StringLength(datepart[0]) + length1 

StringCat(dateday[0], OutputString[length2]) 	// etc
length1 = StringLength(dateday[0]) + length2 

StringCat(colon[0], OutputString[length1]) 
length2 = StringLength(colon[0]) + length1 

StringCat(datemonth[0], OutputString[length2]) 
length1 = StringLength(datemonth[0]) + length2 

StringCat(colon[0], OutputString[length1]) 
length2 = StringLength(colon[0]) + length1 

StringCat(dateyear[0], OutputString[length2])
length1 = StringLength(dateyear[0]) + length2 

StringCat(fs[0], OutputString[length1]) 
length2 = StringLength(fs[0]) + length1 

StringCat(timepart[0], OutputString[length2]) 
length1 = StringLength(timepart[0]) + length2 

StringCat(hours[0], OutputString[length1]) 
length2 = StringLength(hours[0]) + length1 

StringCat(colon[0], OutputString[length2]) 
length1 = StringLength(colon[0]) + length2 

StringCat(minutes[0], OutputString[length1])
length2 = StringLength(minutes[0]) + length1

StringCat(colon[0], OutputString[length2]) 
length1 = StringLength(colon[0]) + length2

StringCat(seconds[0], OutputString[length1])
length2 = StringLength(seconds[0]) + length1 

StringCat(fs[0], OutputString[length2]) 
length1 = StringLength(fs[0]) + length2 

StringCat(operatorpart[0], OutputString[length1]) 
length2 = StringLength(operatorpart[0]) + length1 

StringCat(operator[0], OutputString[length2]) 
length1 = StringLength(operator[0])

StringCat(fs[0], OutputString[length1]) 
length2 = StringLength(fs[0]) + length1 

StringCat(xz[0], OutputString[length2]) 
length1 = StringLength(xz[0])

OUTPORT(outputstring[0], "Free Protocol", 600) // fire it out of the serial or ethernet port depeding on device config

SetData(outputstring[0], "Local HMI", LW, 1000, 600)// use this to check your assembled string on screen using offline simulator


end macro_command

As usual, we have a demo project available for download here.