资讯详情 TSC打印机开发实战:TSPL2指令、二维码打印与队列状态回读
📅 2026/10/31 18:31:08
简介这份面向TSC打印机二次开发入门者的压缩包聚焦标签打印与二维码打印等常见任务中的库文件部署和第三方接口调用适合刚接触设备编程、希望减少环境配置弯路的新手。包内共8个文件以2个lib静态库、2个h头文件、2个dll动态库为主另附1个驱动安装程序和1份中文TSPL文档整体约18.89MBwin32与x64两套库及头文件搭配齐全可直接复制使用省去手动编写函数声明的麻烦。目前已有507人学习下载说明其基础文件组合对入门者具有一定参考价值。对需要快速验证打印效果、理解库文件组织方式的读者来说这组资料能提供完整的基础文件集合配合中文文档可辅助认识指令与接口降低环境搭建和调用第三方库时的试错成本。1. 仓库里那台打不出二维码的标签机前一阵去一个朋友的仓库帮忙他们的出库流程卡在一个很小的环节贴在外箱上的标签二维码扫码枪十次里有三次读不出来剩下七次得对着标签换好几个角度。打印软件用的是设备自带的编辑工具模板改一处要手点十几下一天几百箱货人工核对的时间比打包还长。问题的根子不在打印机硬件标签内容是操作员手工拼出来的二维码尺寸、纠错等级、留白全靠肉眼估打印机只是照着瞎打。这份名为“TSC 打印机开发所需”的资源包针对的正是这一段标签打印、条码打印、二维码打印这条链路上最容易翻车的地方。它把 TSPL/TSPL2 指令手册、SDK 动态库、驱动、多语言示例代码和调机工具打包在一起。适合两类人一是要给 WMS、MES、收银、溯源这类业务系统加上标签打印能力的后端开发二是刚从“用自带编辑软件画标签”过渡到“用代码控制标签”的工程师。下面按我拆包、跑通、踩坑、改造的顺序讲。2. 资源包拆开看目录结构、SDK 与两种开发模式2.1 解压之后先认清这几层目录解压出来东西不少但真正每天会用到的就三块指令手册、SDK 目录、工具目录。驱动基本只装一次示例代码看一遍就丢开。目录结构整体是这样的TSC打印机开发所需/ ├── SDK/ │ ├── Windows/ │ │ ├── DLL/ # 32/64 位动态库C#/C/VB 都调它 │ │ ├── C#/ # 示例工程.sln 直接开 │ │ ├── C/ │ │ └── Java/ # JNA 封装适合服务端 │ ├── Linux/ │ └── Android/ ├── 指令手册/ │ ├── TSPL2指令手册.pdf # 核心通读一遍胜过搜十篇帖子 │ └── 指令速查表.xlsx ├── 驱动/ │ └── Windows/ # 安装包 inf设备管理器手动指 ├── 工具/ │ ├── PrinterTool/ # 状态监控、固件升级、参数回读 │ └── DiagTool/ └── 模板示例/ └── *.prn # 存好的指令文本记事本能直接打开各目录的实际用途我按开发阶段列一下目录里面是什么什么时候会翻它指令手册/TSPL2 全部指令 参数说明写每一行指令、查参数范围SDK/Windows/DLL/动态库 接口头文件用驱动模式或需要调打印机高级功能SDK/*/示例可编译的最小工程第一次跑通、验证端口与库是否匹配驱动/Windows 打印驱动走 USB 虚拟打印队列时工具/状态监控、参数回读标签偏位、报错灯闪、固件异常模板示例/*.prn纯文本指令学指令写法最快的素材模板示例这个目录容易被忽略其实价值很高。.prn文件就是一段纯文本指令用记事本打开能看到别人是怎么组织 SIZE、GAP、BARCODE 顺序的。我一般先挑一个和业务最接近的.prn把里面的文字和条码内容替换掉直接发到打印机上验证比从零开始写指令快得多。提示手册的版本要和机器固件对上。固件偏老的机器某些新指令会被静默忽略指令发过去了标签上就是少一块不报错。2.2 指令流模式和驱动模式选哪个资源包里其实藏了两条完全不同的开发路径很多人一开始没分清结果代码写了一半才发现路走歪了。第一条是指令流模式你自己拼 TSPL2 文本指令通过 TCP 9100、USB、串口直接写给打印机。打印机收到的是“画一行字在 (20,20)”“画一个 128 码在 (20,60)”它自己负责渲染。这条路不依赖操作系统驱动Windows、Linux、安卓、树莓派都能跑一台打印机换个 IP 就能用是服务端最常选的方式。第二条是驱动模式先在系统里装好打印驱动打印机变成一个标准打印队列然后走 SDK 里的动态库常见是openport、sendcommand、printerfont、windowsfont、barcode、qrcode这类函数或者直接 GDI 画图。好处是排版自由度高能贴任意图片、任意字体坏处是绑死操作系统Linux 上会很难受一台服务器带多台不同型号的打印机时驱动版本还容易打架。我自己的判断标准很朴素标签内容是结构化的订单号、料号、条码、二维码就走指令流标签上要贴复杂图形、可变长富文本、公司 logo 带灰阶就走驱动模式。大部分仓储、产线、溯源的场景其实属于前者。对比项指令流模式驱动模式跨平台好Linux/安卓都能发差基本绑 Windows依赖只要网络通要装驱动 动态库排版能力受指令集限制接近普通文档打印多打印机换 IP 即可每台建一个队列调试难度指令可见好排查出错信息藏在打印队列里性能高几乎无中间层一般要过 spooler2.3 先把第一张标签打出来别急着写业务代码先确认“能打”这件事。网络打印机通常监听 9100 端口走 RAW 协议用一段二十行的脚本就能验证整条链路通不通import socket PRINTER_IP 192.168.1.50 # 打印机实际 IP自检页上能看到 PRINTER_PORT 9100 # RAW 打印端口绝大多数网络机型默认 # 最小可用指令集尺寸 - 间隙 - 方向 - 清缓存 - 画字 - 出纸 cmds ( SIZE 60 mm,40 mm\r\n # 标签纸宽 60mm、高 40mm GAP 2 mm,0 mm\r\n # 间隙纸间隙 2mm偏移 0 DIRECTION 1\r\n # 出纸方向0 和 1 差 180 度 CLS\r\n # 清空图像缓冲区防止上一张残留 TEXT 20,20,3,0,1,1,HELLO\r\n # x20,y20字体3不旋转1倍 PRINT 1,1\r\n # 打印 1 份每份 1 张 ) with socket.create_connection((PRINTER_IP, PRINTER_PORT), timeout5) as s: s.sendall(cmds.encode(ascii)) print(指令已发送)这段代码里每一个参数都不是随便填的。SIZE决定打印机的成像宽度写小了右边内容会被裁掉写大了会浪费碳带GAP写错是最常见的“打三张偏一张”DIRECTION决定你看到的标签是正的还是倒的CLS一定要加不加的话上一张没清掉的图会叠到下一张上尤其是循环打标签时特别明显。指令之间用\r\n分隔用\n在某些固件上会解析失败。如果这张标签没出来按这个顺序查打印机自检页打一张确认 IP 和型号telnet IP 9100或nc -vz IP 9100看端口通不通打印机的介质设置从“标签”改成“连续纸”再改回来强制它重新测纸最后才是怀疑指令。我见过太多次是 GAP 校准没做机器一直以为自己没纸。3. TSPL2 指令拆解一条指令对应标签上的什么3.1 坐标系用的是 dot不是毫米这是新手第一个大坑。手册上写SIZE 60 mm,40 mm可以带单位但TEXT、BARCODE、QRCODE里的 x、y 坐标全是dot点不是毫米。203dpi 的机器一毫米约 8 个点300dpi 约 11.8 个点。你在 203dpi 机器上按毫米填坐标内容会全部挤到左上角。我习惯在代码里统一用毫米思考进函数前换算成 dotdef mm2dot(mm: float, dpi: int 203) - int: 毫米转打印机点dpi 决定换算比例203dpi≈8dot/mm300dpi≈11.8dot/mm return int(round(mm * dpi / 25.4)) print(mm2dot(10)) # 203dpi 下约 80 dot print(mm2dot(10, 300)) # 300dpi 下约 118 dot换机器的时候把dpi改掉所有坐标自动跟着走。这个函数看着简单但少了它203 和 300 两种机器混用时标签内容会整体缩到左边三分之一调起来非常折磨。买机器的批次不统一是项目里很常见的情况。3.2 文字、一维码、二维码的指令写法三条核心指令参数顺序手册上写得清楚但有几个位置容易填错。TEXT的格式是TEXT x,y,字体,旋转角度,x方向倍数,y方向倍数,内容BARCODE是BARCODE x,y,码制,高度,是否显示可读字符,旋转,窄条宽,宽条比,内容QRCODE是QRCODE x,y,纠错等级,模块宽度,模式,旋转,内容。下面是我常用的一段完整标签指令把文字、128 码和二维码放一起label ( SIZE 60 mm,40 mm\r\n GAP 2 mm,0 mm\r\n DIRECTION 1\r\n CLS\r\n # 标题文字字体 3 是内置矢量字缩放 1 倍左上角起 TEXT 16,16,3,0,1,1,MATERIAL TAG\r\n # Code128 一维码高度 60dot可读字符开窄条宽 2dot BARCODE 16,60,128,60,1,0,2,2,SN20240513001\r\n # 二维码纠错等级 M模块宽 3dot自动模式 QRCODE 16,150,M,3,A,0,SN20240513001|LOT-A7\r\n PRINT 1,1\r\n )逐项说下容易踩的地方。一维码的“窄条宽”给 1 或 2 时看着很省地方但仓库里拿手持枪隔半米扫2 以下的识别率会明显掉我一般给 2 到 3机器是 300dpi 的话给 3 到 4。高度 60 dot 大约是 7.5 毫米够扫码枪读但如果是传送带上高速动态扫描得给到 100 dot 以上。“是否显示可读字符”这个开关产线上建议开工人肉眼能核对一部分出问题时也好定位。二维码这边纠错等级M是工业标签上比较稳的选择L省空间但脏一点就读不出Q、H更抗污但同样内容码会变大小标签上可能放不下。模块宽度给 2 到 3 dot低于 2 打印头的点阵就拼不出清晰的黑白边界了扫不出来十有八九是这个值给太小。内容里带竖线|做分隔是常见的做法扫码端自己拆。注意指令手册里码制名称是带引号的字符串比如128、EAN13、CODE39。写成数字不加引号部分固件会静默忽略整条 BARCODE标签上那一片就是空白也不报错。3.3 位图和图片先把图转成单色位图标签上要打公司 logo 或产品图就要走BITMAP指令。它接收的是单色位图数据流不是 PNG/JPG 文件本身而且每行宽度要按字节对齐。很多人在这一步卡住是因为直接把图片文件的二进制塞给打印机了。资源包里的示例代码通常会用 SDK 的图片函数处理但指令流模式下我一般自己在服务端把图处理好再发from PIL import Image def img_to_bitmap(path: str) - tuple[int, int, bytes]: 把图片转成 BITMAP 指令需要的单色数据返回 (字节宽度, 高度, 数据) img Image.open(path).convert(1) # 二值化非黑即白 w, h img.size width_bytes (w 7) // 8 # 每行按字节对齐向上取整 buf bytearray() for y in range(h): row bytearray() for x in range(w): # convert(1) 后 1 为白 0 为黑BITMAP 指令里 1 表示打点 row.append(0 if img.getpixel((x, y)) else 1) # 补齐到字节边界不足位补 0 while len(row) % 8: row.append(0) for i in range(0, len(row), 8): byte 0 for b in row[i:i 8]: byte (byte 1) | b buf.append(byte) return width_bytes, h, bytes(buf)convert(1)这一步做了抖动处理效果取决于原图对比度纯色 logo 一般没问题带渐变的图会变成一团麻点最好先在设计软件里处理成黑白再放进来。width_bytes和h要原样填进BITMAP x,y,width_bytes,height,mode,data其中mode填 0 表示覆盖填 1 表示与已有内容做逻辑运算贴图一般用 0。数据部分直接拼在指令后面所以整条指令不能按字符串处理得用 bytes 拼包。3.4 把发送逻辑封装成一个可靠函数直接把sendall散在业务代码里出问题最难查。我一般封一个发送函数把超时、分块、编码三件事收在一处import socket def send_tspl(ip: str, payload: str, port: int 9100, timeout: float 5.0): 发送 TSPL2 指令返回是否成功送达 data payload.encode(gb18030, errorsreplace) # 中文按 GB 编码 try: with socket.create_connection((ip, port), timeouttimeout) as s: # 大位图可能几 MB按 8KB 分块发避免打印机缓冲区溢出 for i in range(0, len(data), 8192): s.sendall(data[i:i 8192]) return True except (socket.timeout, ConnectionError) as e: print(f打印发送失败 {ip}: {e}) return False编码这块有讲究中文内容如果打印机没装 Unicode 字体用 UTF-8 发过去就是乱码得用gb18030。这个参数我在不同固件上试过好几次保险的做法是机器上先用工具把字体下装好代码里固定gb18030。分块发送是因为一张带位图的标签指令可能到几 MB一次性sendall在部分老机型上会撑爆接收缓冲表现为“打印一半卡住”。函数返回布尔值业务层就能据此决定要不要重试、要不要把任务挂起。打印这件事不能假设一定成功网线被拔、打印机断电、纸用完了都会让标签悄悄消失。4. 上层封装模板化、队列和状态回读4.1 模板不要写死在代码里业务一变就改代码发版是最难受的维护方式。我的做法是把标签模板存成带占位符的字符串放数据库或者配置文件里打印时做一次字段替换TEMPLATE ( SIZE 60 mm,40 mm\r\nGAP 2 mm,0 mm\r\nDIRECTION 1\r\nCLS\r\n TEXT 16,16,3,0,1,1,{title}\r\n BARCODE 16,60,128,60,1,0,2,2,{sn}\r\n QRCODE 16,150,M,3,A,0,{sn}|{lot}\r\n PRINT {copies},1\r\n ) def render(tpl: str, data: dict) - str: 安全替换占位符缺字段直接抛错避免打出半张空标签 for key in (title, sn, lot, copies): if key not in data: raise ValueError(f模板字段缺失: {key}) return tpl.format(**data)PRINT m,n里 m 是份数、n 是每份张数做成占位符之后补打几张不用改代码。字段缺失时宁可抛错也不要打半张空标签这是血泪经验空标签贴到货上比不贴更麻烦仓库得回头一张张翻。模板存数据库还有个附加好处不同客户、不同物料可以用不同的标签版式运营在后台配一下就行不用开发介入。模板字段我一般加个version改版之后保留旧版本出问题能快速回滚。4.2 多台打印机怎么排队一个仓库往往好几台打印机分布在不同的打包台。我的做法是按打印机建若干个队列每个队列一个消费线程业务侧只负责“把任务投到某台机器的队列里”不关心它什么时候打import queue, threading class PrinterWorker(threading.Thread): def __init__(self, ip: str): super().__init__(daemonTrue) self.ip ip self.q queue.Queue(maxsize500) # 限长防止打印机离线时内存涨爆 def submit(self, payload: str): self.q.put(payload) # 队列满会抛 Full业务层捕获后转人工 def run(self): while True: payload self.q.get() for attempt in range(3): # 失败重试 3 次间隔递增 if send_tspl(self.ip, payload, timeout5): break threading.Event().wait(0.5 * (attempt 1)) self.q.task_done()队列要限长这一点很多人不做。打印机断电的时候任务还在不停往里塞内存一路涨上去最后整个服务被拖垮。限长之后满了就抛异常业务层提示“打印机异常请转人工”比默默堆积好得多。重试次数也不要给太多标签是有时效的重试五次打出来的标签可能已经过期了。4.3 状态回读别假设打印机一定打出来了指令发出去只是“送达”不等于“打出来了”。要确认结果得读打印机状态。常见做法是发~HS指令打印机返回一串以逗号分隔的状态字段涵盖纸张、碳带、暂停、错误标志这些信息~!?可以查型号~!I查固件信息。字段顺序各型号略有差异以手册为准。需要注意~HS的返回是从打印机的双向通道回来的走 RAW 9100 时部分机型不回数据这时候就得靠驱动模式或者换用带双向能力的端口。我在项目里一般做两件事一是每次批量打印前先发一次状态查询机器处于错误态就提前告警二是打完最后一张后等一秒再查一次确认没有卡纸。这两步加起来不到二十行代码但能把“标签没出来却没人知道”这类问题挡在仓库外面。5. 避坑与排查五个让我返工的细节5.1 中文全变成问号或方块现象英文数字正常中文位置全是?或者一片空白。原因编码不对或者打印机里没有对应的中文字体。用 UTF-8 发指令固件按 GB 解码就是乱码字号用的是内置点阵字本身就不含中文点阵。解决发送端统一encode(gb18030)中文改用 TrueType 字体先通过工具把字体下装到打印机指令里引用字体名。下装一次长期有效但换机器要重下部署脚本里记得带上这一步。5.2 二维码肉眼很清楚扫码枪就是读不出现象标签上二维码看着方正清晰手持枪贴近了也识别不了。原因模块宽度太小热敏打印头的点扩散把相邻模块糊成一片或者二维码周围留白不够被边框、文字挤掉了静区。解决模块宽度提到 3 dot 起步203dpi 的机器上尤其明显二维码四周至少留 4 个模块的空白坐标算的时候把这块位置预留出来纠错等级从 L 提到 M。改完先拿实际要用的那把扫码枪试不同枪的识读能力差别很大。5.3 打三五张就偏一点越打越歪现象前几张位置正常打十几张之后内容整体往上或往下跑。原因介质类型设置和实际纸张不匹配机器按连续纸在走没有按间隙定位或者 GAP 校准值是旧的换了纸卷没重新测。解决先做一次测纸多数机型是关机按住走纸键开机等指示灯闪几次松手让机器重新学习间隙位置指令里GAP 2 mm,0 mm的数值要和实际纸张对齐换纸卷批次之后重新测一次。这个坑我吃过两次第二次是因为换了供应商纸的间隙厚度变了。5.4 服务里打印时好时坏日志看不出来现象同样的代码有时候一次成功有时候超时重试又能成功。原因网络打印机响应慢5 秒超时不够或者多线程共用一个 socket 连接指令互相穿插打印机收到半条指令。解决超时给到 8 到 10 秒每次发送独立建连不要在多个线程间共享 socket发送前加锁或者干脆用队列串行化。指令流是纯文本协议两条指令交错在一起打印机会整段丢弃表现为“什么都没打”。5.5 批量打印时丢标签现象一次投 200 个任务实际只出来 190 张日志里没有报错。原因连续快速发送时打印机缓冲区被填满TCP 层面送达了打印机内部丢包或者队列没做背压业务侧投得太快。解决两个 PRINT 批次之间加几十毫秒间隔队列消费端控制速率一批打完再收下一批关键业务在打印后回读状态确认。这个过程没法做到百分百可靠所以我在业务上留了“补打”入口扫码发现缺号能立刻重打一张。6. 二次开发改造让打印服务长在业务系统里真正落地的时候我会把打印从“一个工具函数”提升为“一个有状态的服务”。核心改动是三张表模板表存指令模板和版本打印机表存 IP、型号、dpi、所属工位任务表存每一次打印的 payload、目标打印机、状态和重试次数。任务落库之后再进内存队列服务重启时能从库里把未完成的任务捞回来重新投递避免重启丢单。这套结构带来的最大好处是可追溯。出了问题不用猜直接查任务表这条标签什么时候投的、发给了哪台机器、重试了几次、状态回读是什么。我在一个产线项目里就是靠这张表定位到某台机器的 dpi 配置被人改成了 300而实际是 203 的机器所有坐标都偏了三分之一。验证改造效果我一般跑三个用例单张正常打印、批量 200 张不丢号、拔掉网线后任务能挂起并在恢复后补打。第三个用例最能暴露设计缺陷很多实现压根没考虑过打印机离线的情况。改造点改之前改之后模板写死在代码里存库 版本号打印触发同步调用阻塞业务落库 异步队列失败处理打日志没人看状态回读 补打入口多打印机配置项硬编码 IP打印机表按工位路由排查手段翻应用日志查任务表 重放 payload从那以后我每次接标签打印的需求都强制先跑一遍“拔网线”的测试用例再谈功能开发。这台机器不联网也能把任务存住是底线不是加分项。希望帮到你。本文还有配套的精品资源点击获取