Barcodes and QR codes
Both printers draw barcodes and QR codes themselves, from a string you send. What they will not do is tell you when the result is wider than the paper — they print the part that fits.
The printer does the drawing
You send LB1234567 and the printer works out the bars. That is worth
appreciating: the alternative is generating a bitmap on a microcontroller and
shipping every dot of it down a 9600 baud wire, which is the subject of the
next article and takes about a thousand times longer.
What the printer will not do is check that the result fits.
The head is 384 dots and there is no more. A code wider than that is printed as far as the edge and then simply stops — no error, no warning, and a barcode missing its right-hand end scans as nothing at all.
The arithmetic, roughly
A CODE128 barcode is about (characters + 3) × 11 modules wide, plus a
termination bar, and each module is as many dots as the unit width you set. A
nine-character code at unit width 2 is around 270 dots: comfortable. The same
code at unit width 3 is about 400, which is off the paper.
A QR code grows in steps rather than smoothly — 21 modules across for very short strings, 25, 29, and so on as the data grows past each version's capacity. A URL usually lands around 33 to 41 modules, which at cell size 6 is 198 to 246 dots. Cell size 8 on the same URL overflows.
The two printers do it differently
The receipt printer takes the ESC/POS commands above, which begin 1D 28 6B for
QR and 1D 6B for barcodes. The label printer has its own, 1A 31 and 1A 30,
wrapped by the library as qrCode() and barCode(). The arithmetic is the
same on both because the head is the same width.
The code
A CODE128 barcode and a QR code on the receipt printer, both centred, both sized to stay inside the 384-dot line. The QR command takes four short setup commands before the data, which is the part that looks alarming and is entirely mechanical.
// Receipt printer wiring for this sketch.
//
// USB-C PD charger (9 V capable) -> base board PD TYPE-C
// base board PRINTER POWER -> printer POWER socket
// base board PRINTER DATA TTL -> printer TTL socket
//
// base board 3V3 MCU header -> ESP32-S3:
// TX -> GPIO16 (this board's RX)
// RX -> GPIO17 (this board's TX)
// 3V3 -> 3V3
// GND -> GND
//
// Arduino IDE: Tools > Board "ESP32S3 Dev Module", Tools > USB CDC On Boot
// "Enabled". No library needed.
#define PRN_RX 16
#define PRN_TX 17
#define PRN_BAUD 9600
HardwareSerial printer(2);
void align(uint8_t n) {
printer.write(0x1B); printer.write(0x61); printer.write(n);
}
void barcode128(const char *s) {
uint8_t len = strlen(s);
printer.write(0x1D); printer.write(0x68); printer.write(80); // height, dots
printer.write(0x1D); printer.write(0x77); printer.write(2); // unit width
printer.write(0x1D); printer.write(0x48); printer.write(2); // print the text under it
printer.write(0x1D); printer.write(0x6B); printer.write(73); // CODE128
printer.write(len + 2);
printer.write('{'); printer.write('B'); // code set B
printer.write((const uint8_t *)s, len);
delay(200);
}
void qr(const char *s, uint8_t cell) {
uint16_t len = strlen(s) + 3;
// Model 2, then the cell size, then error correction M, then the data.
printer.write(0x1D); printer.write(0x28); printer.write(0x6B);
printer.write(4); printer.write(0); printer.write(0x31); printer.write(0x41);
printer.write(50); printer.write(0);
printer.write(0x1D); printer.write(0x28); printer.write(0x6B);
printer.write(3); printer.write(0); printer.write(0x31); printer.write(0x43);
printer.write(cell);
printer.write(0x1D); printer.write(0x28); printer.write(0x6B);
printer.write(3); printer.write(0); printer.write(0x31); printer.write(0x45);
printer.write(49);
printer.write(0x1D); printer.write(0x28); printer.write(0x6B);
printer.write(len & 0xFF); printer.write(len >> 8);
printer.write(0x31); printer.write(0x50); printer.write(0x30);
printer.print(s);
// And now print what was stored.
printer.write(0x1D); printer.write(0x28); printer.write(0x6B);
printer.write(3); printer.write(0); printer.write(0x31); printer.write(0x51);
printer.write(48);
delay(300);
}
void setup() {
Serial.begin(115200);
printer.begin(PRN_BAUD, SERIAL_8N1, PRN_RX, PRN_TX);
delay(500);
printer.write(0x1B); printer.write(0x40); // ESC @
align(1);
printer.println("ORDER LB1234567");
barcode128("LB1234567");
printer.println();
qr("https://learn.lonelybinary.com", 6);
printer.println();
printer.println();
printer.println();
Serial.println("sent");
}
void loop() {}If a scanner refuses the barcode, widen the unit width to 3 before you suspect the printer — thermal paper spreads the ink slightly and one-dot bars blur into each other. If the right-hand end of the code is missing, it was wider than 384 dots and the rest was never printed.
When it does not work
It was wider than the 384 dots the head has, and everything past that edge is not printed. There is no error. Shorten the string, or drop the unit width from 3 to 2 — the width is roughly the number of characters plus three, times eleven, times the unit width.
Usually a unit width of 1. At 8 dots per millimetre a single-dot bar is an eighth of a millimetre wide, and thermal paper spreads the heat slightly as it prints, so the thin bars blur together. Two is the floor and three is comfortable.
The cell size is too large for the amount of data, so a handful of modules fill the whole width. Drop the cell size, or check that the string actually reached the printer — an empty string still produces a valid, meaningless code.
Code set B covers printable ASCII and nothing else. Accented characters, symbols outside ASCII and anything above 127 will not encode. If the data has to carry those, use a QR code instead, which takes arbitrary bytes.
Why a barcode is instant and a logo is not, and how to work out in advance how long an image will take.
Pictures take time →Edit this page — content/books/thermal-printer/barcodes-and-qr-codes.mdx
Questions about this product
See what other owners have asked, and read their solutions.
This page covers several products. Choose yours to see the right discussions.
Discuss this article
Ask about this page. The answer stays here, on the page it belongs to, for whoever hits the same wall next.