Label Printers and libUSB  


This article provides a basic introduction to libUSB by using a Brother tape printer. All sources and the finished library are on GitHub.

I remember that as a kid, my first serious workstation was an Eskom 80386-DX40 computer as part of the package my parents also got a Fujitsu DL dot-matrix printer with endless paper feed. Since the internet and online sharing of your creations were still in the distant future, a lot of school work and art you made was still shared with friends via hardcopy (a good euphemism for actual prints). Eventually, being a coding nerd, I coded up a few programs to directly interface this printer for organizing my school work and personal floppy collections in Turbo Pascal. Pretty much all printers back in the day had a standard character mode and were supported by the printer unit. The familiar noise of the dot head hammering out data could be heard in the 90s doctor’s offices, airline reservation centers, and pretty much every small business that had to issue invoices.

These days paper reliance has taken the backseat for a lot of personal and business work, except when it comes to required product labeling and various forms of government contracts with formerly Her Majesty now His Majesty in various Commonwealth nations like Canada. Most consumer printers will not be supported without drivers by low-level programming languages. The familiar Centronics interface has long been replaced by USB, Wifi, Bluetooth, or Ethernet interfaces. The closest thing you come to an exclusively-controlled printer that is hardwired to a workstation is a USB interface.

I hit an interesting problem at work, where as part of the end-of-line assembly and test of a product we had to add a product label that would change with every device. Most commercial printers that would come with an SDK and the related tooling will put you in the thousands. A lower-cost alternative was to look into laminated tape printers that we have long used for labeling cable assemblies and other low-volume interfaces. For Brothers TZe tape you can get all sorts of tapes up to 36mm wide, and this tape is surprisingly rugged:

  • Brother provides drivers for various platforms as well as an SDK for Windows (only). However, one of the main drawbacks of Brothers’ software stack is that it obfuscates direct raster print. I have not found a method yet to avoid dithering artifacts from scaling images or other features down to raster print them. For text and parametric features, the software works well, but if you really want to exploit the surprisingly high resolution of these printers, you have to hit the bare metal and go direct, since this was meant to go into a production setting, using USB looked more promising than any wireless interface like Bluetooth.

Enter USB

USB has been around since the mid-90s. It was a revolutionary attempt to find one universal interface for many computer peripherals. On advantage USB has over legacy interfaces is that it supports tree topologies. One USB host can support up to 127 devices. The USB standard evolved over the years to increase speed and features, new device classes have been added. In a nutshell each device advertises a vendor and device identifier, the USB host enumerate the device as it is plugged in (hot). Each device can have up to 30 endpoints (i.e. IDs from 1 to 15 and separate read and write endpoints) to communicate with the host. Endpoint 0 IN and OUT is used as control channel. Think of an endpoint as a buffer that can be either read or written to by the USB host. To communicate to the endpoint the host opens a pipe. Depending on the USB standard the pipe supports various types of transfers.

USB Topology, Axelson

On a higher level, the USB device can identify itself as a device class via endpoint 0. For this post, we will leave it at the endpoint level for now. A quick peek at the command reference reveals that our chosen printer has two data endpoints, one for writing (EP2) and one for reading (EP1).

USB Endpoints of the Printer

A fairly straightforward way to communicate with USB devices is libUSB. To keep it fast and simple, we will demo the interface using its Python wrapper PyUSB. To get started let us set up a virtual environment.

sudo apt install libusb-1.0-0-dev python3 python3-venv
mkdir usbtest
cd usbtest
python3 -m venv venv
source venv/bin/activate
pip install pyusb
# start python interactively
python3
# ...

Now plug in the printer and power it on. Note, for some reason this printer powers down after a few minutes of idle. If this does not work, make sure the printer is active.

>>> import usb.core
>>> import usb.util
>>> dev = usb.core.find(idVendor=0x04F9, idProduct=0x20AF)
>>> print(dev)
None
>>> dev = usb.core.find(idVendor=0x04F9, idProduct=0x20AF)
>>> print(dev)
DEVICE ID 04f9:20af on Bus 001 Address 014 =================
 bLength                :   0x12 (18 bytes)
 bDescriptorType        :    0x1 Device
 bcdUSB                 :  0x200 USB 2.0
 bDeviceClass           :    0x0 Specified at interface
 bDeviceSubClass        :    0x0
 bDeviceProtocol        :    0x0
 bMaxPacketSize0        :   0x40 (64 bytes)
 idVendor               : 0x04f9
 idProduct              : 0x20af
 bcdDevice              :  0x100 Device 1.0
 iManufacturer          :    0x1 Brother
 iProduct               :    0x2 PT-P710BT
 iSerialNumber          :    0x3 000C2Z431003
 bNumConfigurations     :    0x1
  CONFIGURATION 1: 500 mA ==================================
   bLength              :    0x9 (9 bytes)
   bDescriptorType      :    0x2 Configuration
   wTotalLength         :   0x20 (32 bytes)
   bNumInterfaces       :    0x1
   bConfigurationValue  :    0x1
   iConfiguration       :    0x0
   bmAttributes         :   0x80 Bus Powered
   bMaxPower            :   0xfa (500 mA)
    INTERFACE 0: Printer ===================================
     bLength            :    0x9 (9 bytes)
     bDescriptorType    :    0x4 Interface
     bInterfaceNumber   :    0x0
     bAlternateSetting  :    0x0
     bNumEndpoints      :    0x2
     bInterfaceClass    :    0x7 Printer
     bInterfaceSubClass :    0x1
     bInterfaceProtocol :    0x2
     iInterface         :    0x0
      ENDPOINT 0x2: Bulk OUT ===============================
       bLength          :    0x7 (7 bytes)
       bDescriptorType  :    0x5 Endpoint
       bEndpointAddress :    0x2 OUT
       bmAttributes     :    0x2 Bulk
       wMaxPacketSize   :   0x40 (64 bytes)
       bInterval        :    0x0
      ENDPOINT 0x81: Bulk IN ===============================
       bLength          :    0x7 (7 bytes)
       bDescriptorType  :    0x5 Endpoint
       bEndpointAddress :   0x81 IN
       bmAttributes     :    0x2 Bulk
       wMaxPacketSize   :   0x40 (64 bytes)
       bInterval        :    0x0

Yay, we see the printer and can get to the endpoints from the manual. From the above trace we note that the device output endpoint for sending data to the printer has address 0x2 and our input endpoint for receiving status information has address 0x81. Let’s see if we can get to the status of the printer, please see the command reference for decoding.

# Detach kernel driver (if present, we cannot use the device if Linux already claimed it)
>>> dev.detach_kernel_driver(0)
# Initialize device
>>> dev.set_configuration()
>>> dev.write(0x2, b'iS')
3
>>> res = dev.read(0x81, 32)
>>> bytes(res)
>>> res[11]
1 # -> we got laminated tape
>>> res[10]
24 # -> that is 24mm wide

Yes this was a simple demonstration of how to get to a USB printer. Armed with this success, I turned this into a raster print package for that printer series that can print directly from PNG files to tape. You can find all the sources here on GitHub.

Maybe in the future, we can continue this as a retro project on an MCS-51 derivative and print directly to USB from a microcontroller such as the WCH CH559.


Published: 2022-09-21
Updated  : 2025-10-04
Not a spam bot? Want to leave comments or provide editorial guidance? Please click any of the social links below and make an effort to connect. I promise I read all messages and will respond at my choosing.
← EMS and 8051s, the TM-502 A New Pal →