thermal printers/Beyond text/08. Barcodes and QR codes
Beyond text · 08 of 11

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.

384 dots is all the width there is
167 modules × 2 dots = 334
Code
Characters encoded12
Unit width (dots)2
Modules across
167
Width
334 dots
On paper
41.8 mm
Fits, with room. 334 dots of the 384 available, so there is space to centre it. Longer strings grow the code even at the same unit width — a URL is usually what pushes a QR code over the edge.

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

receipt_codes.ino

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

The barcode is cut off on the right

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.

No scanner will read the barcode

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 QR code prints as a black square

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.

CODE128 refuses some of my characters

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.

Where this goes next

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

Community

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.

Browse Modules and blocks on the forum