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.
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).
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
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.
>>> dev.detach_kernel_driver(0)
>>> dev.set_configuration()
>>> dev.write(0x2, b'iS')
3
>>> res = dev.read(0x81, 32)
>>> bytes(res)
>>> res[11]
1
>>> res[10]
24
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.