Codementor Events

Data Matrix print label feature in C++ and LabView

Published Dec 12, 2018Last updated May 31, 2020
Data Matrix print label feature in C++ and LabView

About me

I am Manufacturing Test Engineer and a self-taught Visual C++ programmer.

As a Manufacturing Test Engineer, my main responsibility is to develop customized software and hardware that functionally test my company's products to ensure the products operate according to its specifications.

I am mostly responsible for the developing software and I have been developing custom Windows applications in Visual C++ since 2001.

I have recently started developing applications in LabVIEW as well. Management finally mandated that this will be the only test software platform used from this point on -- to standardize along with our European colleagues who have been using LabVIEW for years.

The problem I wanted to solve

Marketing requested that a product line would have a Data Matrix label placed on each product of a particular model type before shipment to the customer. The label would have both a Data Matrix barcode and human readable text.

DM Label Sample.png

The label information would be unique for each product and would contain the following:

  • Model Type
  • Product Revision
  • Date Code
  • MAC Address
  • Serial Number

The optimum area to print the Data Matrix label was in the same work area where this product line was manufactured. However, floor space was at a premium and we could not expand the work area to add another print station -- which is used to create the main product labels for this work area

The work area had two PCs running two different functional testers - a legacy tester developed in 2005 with a LabVIEW application developed by an overseas colleague and a newer tester developed in 2015 with a Visual C++ application developed by me.

Because I developed the latest functional tester for this product line, I am now responsible for both functional testers. So, the Manufacturing Project Lead requested my assistance to solve this problem.

The Manufacturing Project Lead suggested that he thought it would be best if we installed third-party software on the PCs of both functional testers. The idea was that the third-party software would format and print the Data Matrix label at the end of the test.

However, on review, we uncovered some complications that would impede the implementation of his suggestion:

First, if both PCs were going to use third-party software, they would have to be connected to the company's intranet so they can connect to the license server. Both of these PC were stand-alone PCs. One was running on Windows XP and the other was running on standard Windows 7. Both of the PCs were incompatible with our company's intranet. We did not want to purchase a couple licenses for these PCs nor did we want to upgrade the PCs as we would have to take the testers off-line. This would have negatively affected the work area's production capability during a peak production period.

Next, since each Data Matrix label had to be unique to each product, the easiest way to get the unique product information was from the test software applications. However, there was not reliable method for either application, LabVIEW or Visual C++ to interface with the third-party software -- not even through the command line.

Finally, this feature had to be implemented within three weeks.

So, I suggested to the Manufacturing Project Lead that I add a feature for the test software that would format and print the Data Matrix label at the end of the test without the need of third-party software. I would implement this feature on both functional testers.

Why was it best to implement a Data Matrix print label feature in C++ and LabView?

For starters, I have experience in using the Zebra Programming Language II (ZPL II) on former projects -- creating and formatting Code 128 barcodes.

Code 128 barcode.png

So, I reviewed a PDF of the ZPL II handbook and I saw that ZPL II could create Data Matrix labels as well as Code 128 barcode labels.

I was even more excited because I knew that the unique product information that is needed for create the Data Matrix label was all contained on each product's main product label as a Code 128 barcode.

In fact, the product's barcode data is captured by the test application as a means to actually start the test operations. Also, at the end of the test, the captured barcode data is stored into the product's EEPROM to act as the product's MAC address. So, it would be just a simple matter of parsing out the product's barcode data and reformatting that information to create and print the Data Matrix label.

Tech stack

For the both C++ and the LabVIEW code, the idea was to create two main components -- a Data Matrix string and a ZPL II string.

The Data Matrix string is basically the formatted string to create the Data Matrix barcode and it would be embedded in the ZPL II string.

The ZPL II string would contain all of the ZPL II characters to format the barcode and correctly position the Data Matrix barcode and human-readable text.

Now for the C++ code, I used the Microsoft Foundation Classes, CString and CTime, to create the Data Matrix string and I used Standard Template Library class ostrstream to create the ZPL string

For the LabVIEW code, I created a VI (or Virtual Instrument - LabVIEW's version of a function/procedure) named Print_DM_Address.vi

The process of building a Datamatrix Label print label feature in C++ and LabView

I started with the C++ code first since I am more familiar with C++ (and I have a very strong bias in favor of C++).

I created a struct named DataMatrixCoder. The purpose of this struct is basically to parse the product information from the product label's Code 128 barcode and create a DMC product information string.

#include <afx.h>

// Forward Declaration to connect to the test software's main window.
class CMainWin;	

const CString SSNCODE	= "1P";
const CString ASNCODE	= "31P";
const CString REVCODE	= "2PFS";
const CString PDATECODE = "16D";
const CString MACCODE	= "23S";
const CString SNCODE	= "43S";

struct DataMatrixCoder
{
public:
  DataMatrixCoder(CMainWin* win);
  ~DataMatrixCoder();

  void ClearData();
    
  // Create the string that creates the Datamatrix barcode
  void CreateDMCodeString(CString& dmcString); 
  
  CString ssnStr;	   // SSN  
  CString asnStr;	   // ASN
  CString prodRev;	// Product revision
  CString prodDate;	// Production Date
  CString macAddress;	// MAC Address 
  CString serialNum;  // Last 5 digits of top-level barcode

private:
  // Create the date code from CTime
  void CreateProductionDate();
  
 // Store data into prodRev, macAddress and serialNum from product label's 
 // barcode.
 void ParseBarcode(); 
  
  CMainWin* mainWin; // Pointer to main window
  
};

I made DataMatrixCoder a struct instead of a class so I could create more readable code -- specifically, when accessing DataMatrixCoder's CString members to format Data Matrix label's human-readable text in the ZPL string.

The implementation of this struct is relatively simple so I will only dwell on the major methods:

DataMatrixCoder::DataMatrixCoder(CMainWin* win): mainWin(win)
{
 // Product Data is a struct that contains the product information such as the
 // product's model number (prodData->prodNum)
  ProductData* prodData = mainWin->GetProductData();
  
  asnStr = prodData->prodNum;
    
  // "BPZ" is a marketing code that was requested for this field.
    ssnStr = "BPZ:" + asnStr;  

  ParseBarcode();
  CreateProductionDate();
}

void DataMatrixCoder::CreateDMCodeString(CString& dmcString)
{
  dmcString.Format("%s%s+%s%s+%s%s+%s%s+%s%s+%s%s",
           SSNCODE, ssnStr, 
           ASNCODE, asnStr, 
           REVCODE, prodRev, 
           PDATECODE, prodDate, 
           MACCODE, macAddress, 
           SNCODE, serialNum);	
}

void DataMatrixCoder::CreateProductionDate()
{
  CTime currentTime = CTime::GetCurrentTime();

  prodDate = currentTime.Format("%y%m%d");
}

void DataMatrixCoder::ParseBarcode()
{
  // TestData is a struct that is used to hold important test data such as 
  // the product's Code 128 barcode information.
  TestData* data = mainWin->GetTestData();
  CString barcode = data->barcode; // the product's Code 128 barcode. 
  
  // Parse barcode for the product revision. 
    // The product's Code 128 barcode could contain either an 'A' or 'K' at 
    // index 8.  
    // This would have to checked to correctly retrieve the two-character 
    // product revision code.
  short revIndex = 0;
  if (barcode[8] == 'A')
  {
    revIndex = 10;
  }
  else if (barcode[8] == 'K')
  {
    revIndex = 11;
  }
  prodRev = barcode.Mid(revIndex, 2);

  // Create MAC address from barcode.
  // For this product, the entire barcode is written into the product's
  // EEPROM to act as its MAC address (hyphens and spaces are removed)
  macAddress = barcode;
  macAddress.Remove('-'); macAddress.Remove(' ');

  // Parse barcode for serial number (last 5 barcode characters)
  serialNum = barcode.Right(5);
}

I previously created a class called SerialPrinter and separate header file named ZPL Constants.h to create ZPL strings and print Code 128 barcodes.

// ZPL CONSTANTS.H
// These provide the string constants to format data being sent 
// the barcode printer for the Code 128 and Data Matrix barcode labels
//////////////////////////////////////////////////////////////////

// Setup Commands
const char SETZPL[] = "^SZ2";	// Set ZPL II code 
const char SETDPM[] = "^JMA";	// Set Dots per Millimeter (See ZPL guide for more data)
const char CLRBM[] = "^MCY";	// Clear Bit Map ('Y' = Yes; 'N' = No)
const char PRNTMI[] = "^PMN";	// Print mirror image on label ('Y' = Yes; 'N' = No)
const char PRNTWTH[] = "^PW796"; // Print Width (See ZPL guide for more data)
const char CHNGBF[] = "~JSN";	// Change Backfeed Sequence (Set at Normal -- see ZPL guide for more data)
const char REPRNT[] = "^JZY";	// Reprint after error ('Y' = Yes; 'N' = No)
const char RVSEPRNT[] = "^LRN";	// Label Reverse Print ('Y' = Yes; 'N' = No)

// Universal Commands
const char START[] = "^XA"; // Start of format.
const char HOMEPOS[] = "^LH0,0";  // Set Label Home Position.
const char DATASTART[] = "^FD"; // Start of data
const char DATAEND[] = "^FS"; // End of data
const char END[] = "^XZ"; // End of format.

// Printing Commands for Code 128 Barcodes
const char FONT[] = "^AB,20,10"; // Font selection - LOCKED
const char BCTYPE[] = "^BY1,2.0,30,^BCN,30,N,N,N"; // Set Barcode type (128 w/width modifier);
const char BC1[] = "^FO45,10"; // Set field origin for first barcode
const char BC2[] = "^FO45,85" ; // Set field origin for second barcode
const char BC3[] = "^FO45,155" ; // Set field origin for second barcode
const char ALPHA1[] = "^FO75,50"; // Set field origin for first text
const char ALPHA2[] = "^FO75,120"; // Set field origin for second text
const char ALPHA3[] = "^FO75,190"; // Set field origin for second text

// Printing Commands for Data Matrix Barcodes
const char DMFONT[] = "^A0,30,20"; // Font selection - LOCKED
const char DMTYPE[] = "^BXN,6,200"; // was ^BXN, 5, 200
const char DM1[]	= "^FO20,30"; // Set field origin for Data Matrix barc

const char DMTEXT11[] = "^FO250,30";	// Set field origin for first text 
const char DMTEXT12[] = "^FO250,65";	// Set field origin for second text
const char DMTEXT13[] = "^FO250,100";	// Set field origin for third text
const char DMTEXT14[] = "^FO250,135";	// Set field origin for fourth text
const char DMTEXT15[] = "^FO250,170";	// Set field origin for fifth text
const char DMTEXT16[] = "^FO250,205";	// Set field origin for sixth text
#include <afx.h>

class CCriticalSection;	// Forward Class Declaration (for multithreading)
class Serial; // Forward Declaration (used for RS-232 communications) 

struct DataMatrixCoder;	// Forward Declaration

class SerialPrinter  
{
public:
  SerialPrinter(CString portName = "\\\\.\\COM7", UINT baudrate = 115200);
  ~SerialPrinter();

  short DateTimeStamp(); // Prints the current date and time.
  short PaperFeed(); // Operates printer paper feed.
  short Print(char* string); // Prints data.
  
    // Format data as Code 128 barcode using ZPL II format
    short PrintBarcode(char* string);		
    
    // Format data as Datamatrix using ZPL II format
    short PrintDataMatrix(DataMatrixCoder& dmc); 

private: 
  CCriticalSection* printLock;
  Serial* serialPort;

};

So I added extra constants to ZPL Constant.h and I added a new method to SerialPrinter named PrintDataMatrix to create and properly format the ZPL string to print the Datamatrix label

short SerialPrinter::PrintDataMatrix(DataMatrixCoder& dmc)
{
  // Lock access to print buffer and printer.
    // Used with CCriticalSection.
  CSingleLock key(printLock);
  key.Lock();

  CString dmcString;
    
  dmc.CreateDMCodeString(dmcString);

  // Create the ZPL II string
  const int ZBUFSIZE = 500;
  char zplString[ZBUFSIZE] = "\0";
  ostrstream strout(zplString, ZBUFSIZE); 
  
  strout << START << SETZPL << SETDPM
       << CLRBM << PRNTMI << PRNTWTH
       << CHNGBF << REPRNT << RVSEPRNT
       << END;
    
  // Datamatrix barcode
  strout << START  << HOMEPOS << DM1 << DMTYPE 
       << DATASTART << dmcString << DATAEND; 

  // Human-readable text
  strout << DMTEXT11 << DMFONT 
       << DATASTART << SSNCODE 
       << "     " << dmc.ssnStr 
       << DATAEND; 
  
  strout << DMTEXT12 << DMFONT 
       << DATASTART << ASNCODE 
       << "   " << dmc.asnStr 
       << DATAEND;

  strout << DMTEXT13 << DMFONT 
       << DATASTART << REVCODE 
       << " " << dmc.prodRev 
       << DATAEND;  
  
  strout << DMTEXT14 << DMFONT 
       << DATASTART << PDATECODE 
       << "  " << dmc.prodDate 
       << DATAEND;
  
  strout << DMTEXT15 << DMFONT 
       << DATASTART << MACCODE 
       << "	  " << dmc.macAddress 
       << DATAEND;
       
  strout << DMTEXT16 << DMFONT 
       << DATASTART << SNCODE 
       << "	  " << dmc.serialNum
       << DATAEND 
       << END;

   short printReturn = Print(zplString);

  // Unlock access to print buffer and printer.
    // Used with CCriticalSection.
  key.Unlock();

  return ;
}

Challenges I faced

The most challenging part of this project was to get the human readable text properly formatted and printed. That took quite a bit of trial and error. But once that was completed, it was more or less a done deal.

But once that was done, I had to implement this solution in LabVIEW.

For those who are not familiar with LabVIEW, it is a proprietary graphical programming language by National Instruments (NI).

I am not a big fan of LabVIEW. It is not really bad; it is just not to my liking. Mostly because I believe it slows me down in coding and some parts of it are just tedious.

Print_DM_Address.vi is an example of this.

As I said earlier, the VI (Virtual Instrument) is LabVIEW's version of a function. It is made up of two components - the front panel (which is mainly a user interface for each VI) and the block diagram (the actual code)

In any case, here is the Print_DM_Address.vi in all of its glory:
Print_DM_Address Front Panel.png

Print_DM_Address BD1 .png

Print_DM_Address BD2 .png

LabVIEW would be so much more enjoyable to use if it had a zoom feature -- like Microsoft Visio, AutoCAD, Designer, etc. You know, the other applications used to make graphic designs. Maybe one day, NI will add that feature. I say this because connecting all of those wires to those boxes was a very tedious chore.

However, believe it or not, this implementation works exactly like the Visual C++ implementation.

Key learnings

I learned the ZPL II code to create Data Matrix barcodes and it didn't take very long. I had this feature operational in both software applications in less than two weeks.

Final thoughts and next steps

This project was a bit challenging, due to the time constraints, but it was also a lot of fun.

The Manufacturing Project Lead and the Marketing Representative were very pleased with the turnaround time in the implementation of this feature.

The Data Matrix label was exactly per specifications -- without the use of third-party software.

And since Data Matrix labels will be part of future products, I will probably get a lot of use from this solution.

Data Matrix 2.png

Discover and read more posts from Anton Anderson
get started
post commentsBe the first to share your opinion
Elijah Robert
6 months ago

ARE YOU AN INVESTMENT OR ONLINE SCAM VICTIM? iBOLT CYBER HACKER CAN ASSIST YOU IN RECOVERING ALL OF YOUR LOST BTC AS WELL AS SOLVING OTHER CRYPTOCURRENCY PROBLEMS.

Elijah Robert Is My Name, Senior Lecturer at Loughborough University Design School.
I’m writing to acknowledge my amazing experience in reclaiming my stolen bitcoin and to thank iBOLT CYBER HACKER for all of their assistance. I spent a lot of money on a binary option platform. After several transactions, I decided to withdraw my money because the website said I had made a lot of profit, but the withdrawal failed. I tried multiple times to contact them but they all failed. I tried every other feasible method to ensure that I retrieved my stolen bitcoin. bI contacted a hacker whose recommendations I had seen online, iBOLT CYBER HACKER, and they came in handy at the appropriate time, rescuing me from these unscrupulous investment schemes with their top-notch services. I regained my finances as well as my peace of mind. iBOLT CYBER HACKER RECEIVES MY HEARTFELT RECOMMENDATION.

Contact Info:
Emai: ibolt @ cyber - wizard . com
Whtsp: +3.9.3.5.0.9.2.9.0.5.5.4.

Show more replies