The first thing you print · 06 of 11

Text on a receipt

A receipt is a stream of characters with a line feed at the end of each line. Five bytes print the first one, and the last of the five is the one that makes the paper move.

What a line of text actually is

Nothing prints until the line feed
nothing sent
Two of these bytes are not text at all. ESC @1B 40 — is the reset every sketch should open with, because a printer remembers the font size and alignment from the last job, including the job that crashed.

Two of those five bytes are not text. 1B 40 is ESC @, the reset, and it belongs at the top of every sketch — the printer remembers the font size and alignment from the last job it ran, including the one that crashed, and a receipt that comes out in double-width capitals is usually this.

The byte at the end is the one that prints. Characters arrive and wait in a buffer; 0A, the line feed, is what commits the line to paper. In Arduino terms that is exactly the difference between print and println, and it is why a sketch that ends on print appears to lose its last line.

The commands worth knowing

ESC/POS has hundreds. Four of them cover most receipts:

BytesWhat it does
1B 40Reset — font, size and alignment back to default
1B 21 nFont size: bit 4 doubles the height, bit 5 the width
1B 61 nAlign: 0 left, 1 centre, 2 right
0ALine feed — print what is buffered

Everything else in a receipt is ordinary text.

Leave room at the end

The print head sits behind the tear bar, so the last thing printed is still inside the machine when the job finishes. Three or four blank lines at the end push it out where you can read and tear it. That margin at the bottom of every till receipt you have ever been handed is this, and not decoration.

The code

receipt_first_lines.ino

The smallest useful receipt: a reset, a heading in double-width, three lines of items and a total, then enough feed to clear the tear bar. Everything here is ESC/POS, which is what the receipt printer speaks — none of it applies to the label printer.

// 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" so the Serial Monitor works. No library needed.

#define PRN_RX 16   // from the printer's TXD
#define PRN_TX 17   // to the printer's RXD
#define PRN_BAUD 9600   // printed on the printer: TTL:9600 N-8-1 9V

HardwareSerial printer(2);

// ESC @ - forget the font, size and alignment the last job left behind.
void reset() {
  printer.write(0x1B); printer.write(0x40);
  delay(50);
}

// ESC ! n - bit 4 doubles the height, bit 5 doubles the width.
void size(uint8_t n) {
  printer.write(0x1B); printer.write(0x21); printer.write(n);
}

// ESC a n - 0 left, 1 centre, 2 right.
void align(uint8_t n) {
  printer.write(0x1B); printer.write(0x61); printer.write(n);
}

void setup() {
  Serial.begin(115200);
  printer.begin(PRN_BAUD, SERIAL_8N1, PRN_RX, PRN_TX);
  delay(500);

  reset();

  align(1);
  size(0x30);                       // double width and height
  printer.println("LONELY BINARY");
  size(0x00);
  printer.println("Order 00042");
  printer.println("--------------------------------");

  align(0);
  printer.println("Coffee            x1      3.50");
  printer.println("Latte             x1      4.20");
  printer.println("Cake              x1      4.50");
  printer.println("--------------------------------");

  align(2);
  size(0x10);                       // double height only
  printer.println("Total   12.20");
  size(0x00);

  // Feed past the tear bar, or the last line is still inside the printer.
  printer.println();
  printer.println();
  printer.println();

  Serial.println("sent");
}

void loop() {}

If the paper feeds but stays blank, the roll is in upside down: thermal paper only darkens on one side. If characters appear but they are not the ones you sent, the baud rate is wrong. If nothing happens at all, the supply never reached 9 V.

When it does not work

The last line never prints

Characters sit in the printer's buffer until a line feed arrives, so a sketch that ends with print rather than println leaves its final line unprinted. It looks like the printer stopped early. Use println, or send a 0x0A of your own.

The last few lines are inside the machine

The print head sits some distance behind the tear bar, so whatever printed last is still in the mechanism. Feed three or four blank lines at the end of a receipt — that is what the blank printlns at the bottom of the sketch are for, and it is why real till receipts have a margin.

Everything is double-width and I did not ask for it

The printer remembers its font, size and alignment across jobs, including the job that crashed half way through. That is what ESC @ at the top of the sketch is for: it puts every setting back to default before you start.

The paper feeds but comes out blank

The roll is in the wrong way up. Thermal paper has a coating on one side only and the head is underneath, so a roll loaded backwards prints perfectly onto the side nobody can see. Pull a few centimetres out and scratch it with a fingernail — the coated side marks.

Where this goes next

The same wiring, a different machine, and four steps where the receipt printer had one.

A label is not a receipt

Edit this page — content/books/thermal-printer/text-on-a-receipt.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