Command builder for the ICT SP1 thermal receipt printer, an
ESC/POS-style printer used in kiosks and payment machines. Pure
C99, no dependencies, no -lm.
This module turns high-level calls — set font, align text, print, a
QR code, commit the job — into the exact ESC/POS byte sequences the
SP1 expects, and emits them through a caller-supplied write function
pointer. That pointer is the only seam to hardware: no I/O, no
allocation, no globals. It is a port of
go-ict/sp1 with the
goroutine write-queue stripped out.
#include "ict_sp1.h"
static void uart_tx(const uint8_t *p, size_t n, void *cookie)
{
(void)cookie;
my_uart_write(p, n);
}
st_sp1 d;
sp1_init(&d, uart_tx, NULL);
sp1_init_printer(&d); /* ESC # then ESC @ */
sp1_print_aligned(&d, SP1_LARGE, SP1_CENTER, "SHOP NAME");
sp1_line_feed(&d);
sp1_print_aligned(&d, SP1_NORMAL, SP1_RIGHT, "TOTAL 120.00");
sp1_line_feed(&d);
sp1_qr_size(&d, 6);
sp1_qr(&d, "https://pay.example/abc123");
sp1_execute(&d); /* CR commits the job */The Go driver used a background queue to space bytes out; this module emits immediately, so two firmware timing rules become your responsibility:
- after
sp1_execute()(aCRcommits the job) leave the printer idle ~3 s before sending more bytes; - after
sp1_qr_size()give the QR engine ~1 s before the next command.
| Function | ESC/POS | Purpose |
|---|---|---|
sp1_init(d, write, cookie) |
— | Store the output seam. |
sp1_activate_escpos(d) |
ESC # |
Enter ESC/POS mode (first after power-on). |
sp1_reset(d) |
ESC @ |
Restore factory defaults. |
sp1_init_printer(d) |
ESC # ESC @ |
Activate + reset in one call. |
sp1_disable_autoclear(d) |
ESC c 5 |
Stop buffer auto-clear on status changes. |
sp1_set_font_size(d, size) |
ESC M n |
SP1_SMALL / SP1_NORMAL / SP1_LARGE. |
sp1_set_underline(d, on) |
ESC - n |
Toggle underline. |
sp1_highlight(d, on) |
GS B n |
Toggle inverted (highlighted) text. |
sp1_print(d, text) |
— | Emit text verbatim. |
sp1_print_aligned(d, size, align, text) |
ESC M n + spaces |
Set font, software-align with leading spaces, print. |
sp1_line_feed(d) |
LF |
One newline. |
sp1_execute(d) |
CR |
Commit the current print job. |
sp1_qr_size(d, size) |
GS ( k … |
QR module size, clamped to 1..7. |
sp1_qr(d, data) |
GS k … |
Emit a QR code holding data. |
sp1_get_system_info(d) |
ESC ( T |
Request the printer's identification (reply on your RX path). |
Alignment is done in software by prepending spaces, which works at every font size. Width is counted in code points, so a Thai or accented character takes one column just like ASCII.
| Function | Purpose |
|---|---|
sp1_utf8_runes(s) |
Count UTF-8 code points in s. |
sp1_align_pad(size, align, text, out, cap) |
Build the space-padded line into out; returns its length, or 0 if it would not fit. SP1_LEFT (or text already filling the line budget) is copied unchanged. |
Per-font budgets: SP1_MAX_SMALL_CHARS 48, SP1_MAX_NORMAL_CHARS
24, SP1_MAX_LARGE_CHARS 16.
make testBuilds example/check.c, runs it, prints ict_sp1: All OK. on
success and exits zero. The check wires the write seam to a capture
buffer and asserts:
- every setup / styling / text / QR command emits its exact ESC/POS
byte sequence (e.g.
sp1_set_font_size(SP1_LARGE)→1B 4D 32,sp1_qr("AB")→1D 6B 48 02 00 41 42). sp1_qr_sizeclamps to 1..7.sp1_print_alignedemits the font command, then the right number of leading spaces, then the text.sp1_utf8_runescounts code points for ASCII, accented, and Thai input;sp1_align_padcentres / right-aligns, leaves Left and over-budget text unchanged, and reports 0 on a too-small buffer.
Drop ict_sp1.h and ict_sp1.c into your sources. No other module
dependencies, no libc beyond <string.h>.
MIT.