TSC条码打印机JAVA调用范例:从串口到网口,避坑与批量打印实战 简介这份资源面向需要将条码打印功能集成进业务系统的Java开发者聚焦TSC条码打印机的二次开发调用。内容围绕Java如何借助JNI或JNA调用TSC提供的DLL驱动接口展开涉及条码类型设置、打印尺寸与位置控制、打印模板设计、数据与错误处理等环节并配有可运行的调用范例帮助开发者理解从初始化打印机到发送打印指令、处理打印结果的完整流程。压缩包共47个文件约1.18MB以html文档、xml与properties配置、class与java源码为主另含dll动态库、jar依赖、lib库文件及doc说明文档兼顾接口说明与工程配置。目前已有797人学习下载适合从事零售、物流、仓储等场景打印集成的中高级开发者参考可据此快速搭建调试环境、对照示例排查调用问题并沉淀可复用的封装思路。1. 从一次标签打歪说起TSC条码打印机-JAVA调用范例到底在解决什么仓库出库口那台TSC打印机某天开始每隔十几张就歪一张操作员拿尺子量了半天怀疑是机器老化。我过去看了一眼代码发现调用方每次打印都重新openPort、sendCommand、closePort中间没有任何等待和状态确认。打印机不是坏了是被指令节奏搞晕了。这件事之后我把TSC的JAVA调用重新梳理了一遍也就是这篇要讲的TSC条码打印机-JAVA调用范例。TSC是台半TSC Auto ID的条码打印机品牌在仓储、产线、零售、医疗这些场景里出货量很大。它的指令体系分两套一套是TSPL/TSPL2原生文本指令直接控制打印内容、速度、浓度、走纸另一套是ESC/POS兼容模式主要给收银小票场景用。JAVA调用范例的核心就是用JAVA程序通过串口、USB虚拟串口或网口把TSPL指令发给打印机让它按模板打出标签。适合读这篇的人有三类一是第一次接TSC打印机、不知道从哪下手的JAVA后端二是已经能打但经常遇到歪纸、乱码、丢单的维护人员三是想把打印逻辑从桌面端搬到服务端、做集中打印调度的架构同学。下面按「先跑通最小闭环再拆参数再讲避坑」的顺序展开代码可以直接抄。2. 用JAVA把TSC打印机跑通从环境准备到第一张标签2.1 先确认连接方式再决定用哪个JAVA库TSC打印机的物理连接方式决定了JAVA侧用什么库这一步选错后面全是白费功夫。常见三种连接方式系统识别形态JAVA侧常用库适用场景USB虚拟串口Windows下COMxLinux下/dev/ttyUSBxjSerialComm / RXTX单机桌面、工位机串口RS232COMx / /dev/ttySxjSerialComm老设备、工业现场网口以太网/WiFiTCP Socket默认9100端口原生Socket服务端集中打印、多机共享我一般优先推荐jSerialComm原因是它跨平台、不需要额外装本地库、API比RXTX干净。网口场景直接用java.net.Socket就行不需要第三方库。下面以USB虚拟串口为例网口版本在2.3节给。Maven依赖只需要一个dependency groupIdcom.fazecast/groupId artifactIdjSerialComm/artifactId version2.10.4/version /dependency版本号按你项目实际能拉到的写2.9以上都够用。注意别同时引入RXTX和jSerialComm两者抢串口会报Port busy。2.2 最小可运行范例打开端口、发TSPL、关端口先看一段能直接跑的最小代码功能是打一张40mm×30mm的标签内容是一行文字加一个Code128条码。import com.fazecast.jSerialComm.SerialPort; import java.nio.charset.StandardCharsets; public class TscPrintDemo { public static void main(String[] args) throws Exception { // 1. 找到打印机对应的串口Windows下通常是COM3/COM4 SerialPort port SerialPort.getCommPort(COM3); // 2. 波特率必须和打印机自检页上的一致TSC默认多为9600 port.setComPortParameters(9600, 8, SerialPort.ONE_STOP_BIT, SerialPort.NO_PARITY); port.setComPortTimeouts(SerialPort.TIMEOUT_WRITE_BLOCKING, 3000, 0); if (!port.openPort()) { throw new IllegalStateException(串口打开失败检查是否被其他程序占用); } try { // 3. 拼TSPL指令SIZE和GAP必须和实际标签纸匹配 String tspl SIZE 40 mm,30 mm\r\n GAP 2 mm,0 mm\r\n DIRECTION 1\r\n CLS\r\n TEXT 20,20,\3\,0,1,1,\SN-2024-0001\\r\n BARCODE 20,80,\128\,60,1,0,2,4,\SN20240001\\r\n PRINT 1,1\r\n; byte[] data tspl.getBytes(StandardCharsets.US_ASCII); int written port.writeBytes(data, data.length); if (written ! data.length) { throw new IllegalStateException(指令未完整写入实际写入 written); } // 4. 给打印机留出解析和执行时间别立刻关端口 Thread.sleep(500); } finally { port.closePort(); } } }逻辑说明SIZE定义标签宽高GAP定义两张标签之间的间隙DIRECTION 1是打印方向CLS清空缓冲区TEXT和BARCODE是内容PRINT 1,1表示打1份、每份1张。参数说明TEXT的3是字体名后面0,1,1分别是旋转角度、x倍率、y倍率BARCODE里60是条码高度1是可读文本开关0是旋转2是窄条宽度4是宽窄比。提示Thread.sleep(500)不是可有可无的。TSC打印机收到PRINT后需要走纸、加热、回退这段时间内如果端口被关闭或下一条指令挤进来就会出现歪纸或半张。2.3 网口版本把同一套TSPL指令走Socket发出去如果打印机接的是网口把上面的串口部分换成Socket即可TSPL指令体完全不变。import java.io.OutputStream; import java.net.InetSocketAddress; import java.net.Socket; import java.nio.charset.StandardCharsets; public class TscNetPrint { public static void main(String[] args) throws Exception { String ip 192.168.1.50; // 打印机IP从自检页或路由器里查 int port 9100; // TSC网口默认RAW端口 String tspl SIZE 40 mm,30 mm\r\n GAP 2 mm,0 mm\r\n CLS\r\n TEXT 20,20,\3\,0,1,1,\NET-PRINT-001\\r\n PRINT 1,1\r\n; try (Socket socket new Socket()) { socket.connect(new InetSocketAddress(ip, port), 3000); socket.setSoTimeout(3000); OutputStream out socket.getOutputStream(); out.write(tspl.getBytes(StandardCharsets.US_ASCII)); out.flush(); Thread.sleep(500); } } }逻辑说明网口打印没有「打开端口」的概念连上就能写。参数说明connect超时设3秒避免打印机离线时线程卡死setSoTimeout管的是读超时这里只写不读设了也无妨。注意有些网络环境里打印机的9100端口会被防火墙拦先在命令行用telnet 打印机IP 9100确认能通。2.4 指令拼接的三种写法以及我为什么不用字符串相加上面用的是字符串相加适合演示不适合生产。生产里我一般用StringBuilder或者模板引擎原因是TSPL对换行和空格敏感字符串相加容易在拼接处漏掉\r\n。StringBuilder sb new StringBuilder(); sb.append(SIZE 40 mm,30 mm\r\n); sb.append(GAP 2 mm,0 mm\r\n); sb.append(CLS\r\n); sb.append(String.format(TEXT 20,20,\3\,0,1,1,\%s\\r\n, escapeTspl(orderNo))); sb.append(PRINT 1,1\r\n);escapeTspl是个自定义方法把内容里的双引号和反斜杠转义掉否则一个带引号的订单号就能让整条指令解析失败。这是血泪经验曾经有个客户订单号里带打印机直接吐白纸。3. TSPL指令参数怎么设SIZE、GAP、DENSITY、SPEED的取值逻辑3.1 SIZE和GAP必须和实物标签一致差1mm就歪SIZE写的是标签纸的宽和高GAP写的是两张标签之间的间隙。这两个参数不是「大概齐」是打印机定位的基准。常见规格对照标签规格SIZE写法GAP写法40×30mm 间隙纸SIZE 40 mm,30 mmGAP 2 mm,0 mm50×40mm 间隙纸SIZE 50 mm,40 mmGAP 3 mm,0 mm60×40mm 黑标纸SIZE 60 mm,40 mmBLINE 3 mm,0 mm连续纸SIZE 60 mm,40 mmGAP 0 mm,0 mm参数说明GAP第一个值是间隙长度第二个值是偏移量一般写0。黑标纸要用BLINE而不是GAP用错了打印机会一直走纸找不到定位点。连续纸必须写GAP 0,0否则打印机会按默认间隙去找找不到就报错。我一般会在代码里把标签规格做成配置项而不是硬编码。换一批纸就要改代码这种事在生产环境里很危险。3.2 DENSITY和SPEED打不清和打太慢的平衡点DENSITY是打印浓度范围0到15数值越大越黑。SPEED是打印速度单位是英寸每秒常见范围2到6。sb.append(DENSITY 8\r\n); // 浓度8中等偏黑 sb.append(SPEED 4\r\n); // 速度4 ips兼顾清晰度和效率参数说明浓度太高会糊字、伤打印头太低在铜版纸上会发灰。速度太快条码边缘会毛扫码枪读不出来。我一般先用DENSITY 8、SPEED 4打样用扫码枪实测读得顺就不动读不顺先降速度再升浓度。注意不同批次的碳带和标签纸对浓度敏感度不一样。换耗材后要重新打样别沿用上一批的参数。3.3 TEXT和BARCODE的坐标与倍率别让内容跑出标签TSPL的坐标原点在左上角单位是点dot203dpi的机器上1mm约等于8点。TEXT x,y,...里的x、y就是内容左上角的坐标。// 203dpi机器标签40mm宽约320点30mm高约240点 sb.append(TEXT 20,20,\3\,0,1,1,\订单号\\r\n); sb.append(TEXT 20,60,\3\,0,1,1,\SN-2024-0001\\r\n); sb.append(BARCODE 20,120,\128\,60,1,0,2,4,\SN20240001\\r\n);参数说明TEXT的倍率1,1是原始大小改成2,2就是两倍。BARCODE的窄条宽度2在203dpi下约0.25mm宽窄比4是Code128的常用值。坐标算错内容会被裁掉打印机不会报错只会默默打半截。我一般会在代码里加一个边界检查x加内容宽度超过标签宽度就抛异常。3.4 用CLEAR和CLS的区别以及什么时候要加BACKFEEDCLS是清空图像缓冲区CLEAR是清空整个缓冲区包括指令队列。大多数场景用CLS就够。BACKFEED是回退纸张用在需要先打后撕的场景。sb.append(CLS\r\n); // ... 内容指令 sb.append(BACKFEED 100\r\n); // 回退100点方便撕纸 sb.append(PRINT 1,1\r\n);参数说明BACKFEED的数值是回退点数203dpi下100点约12.5mm。回退太多会卡纸太少撕不到。这个参数要配合撕纸位置调没有万能值。4. 批量打印与状态回读从单张到产线级调用4.1 批量打印不要循环开端口一次连接打多张单张打印的代码直接循环每次开端口关端口在产线上会出问题。正确做法是一次连接连续发多条PRINT。SerialPort port SerialPort.getCommPort(COM3); port.setComPortParameters(9600, 8, SerialPort.ONE_STOP_BIT, SerialPort.NO_PARITY); port.openPort(); try { for (Order order : orders) { StringBuilder sb new StringBuilder(); sb.append(SIZE 40 mm,30 mm\r\n); sb.append(GAP 2 mm,0 mm\r\n); sb.append(CLS\r\n); sb.append(String.format(TEXT 20,20,\3\,0,1,1,\%s\\r\n, order.getNo())); sb.append(String.format(BARCODE 20,80,\128\,60,1,0,2,4,\%s\\r\n, order.getCode())); sb.append(PRINT 1,1\r\n); byte[] data sb.toString().getBytes(StandardCharsets.US_ASCII); port.writeBytes(data, data.length); Thread.sleep(300); // 每张之间留间隔别让缓冲区堆积 } } finally { port.closePort(); }逻辑说明Thread.sleep(300)是给打印机留出单张处理时间。参数说明这个值按打印机型号和标签复杂度调简单标签200ms够复杂标签可能要500ms。判断标准是打印机不丢单、不歪纸。4.2 用~HS指令回读打印机状态别靠猜TSC支持用~HS指令查询状态返回一串状态字节。JAVA侧读回来解析能知道缺纸、开盖、暂停这些情况。// 发送状态查询 String query ~HS\r\n; port.writeBytes(query.getBytes(StandardCharsets.US_ASCII), query.length()); // 读返回TSC返回格式是ASCII字符串以\r\n结尾 byte[] buffer new byte[128]; int read port.readBytes(buffer, buffer.length); String status new String(buffer, 0, read, StandardCharsets.US_ASCII); System.out.println(打印机状态: status);参数说明readBytes是阻塞读要配合setComPortTimeouts设读超时否则打印机不返回时线程会卡死。返回内容里第几位代表什么状态要查对应型号的TSPL手册不同型号位定义有差异。我一般只解析「缺纸」和「开盖」两个最关键的位其他忽略。4.3 打印队列与重试网络打印必须做幂等网口打印比串口更容易丢单因为网络抖动、打印机重启都会让指令丢失。我一般会在应用层做一个简单的队列加重试。public boolean printWithRetry(String ip, int port, String tspl, int maxRetry) { for (int i 0; i maxRetry; i) { try (Socket socket new Socket()) { socket.connect(new InetSocketAddress(ip, port), 2000); OutputStream out socket.getOutputStream(); out.write(tspl.getBytes(StandardCharsets.US_ASCII)); out.flush(); Thread.sleep(500); return true; } catch (Exception e) { // 记录日志等一会儿再试 try { Thread.sleep(1000); } catch (InterruptedException ignored) {} } } return false; }逻辑说明重试次数一般设2到3次太多会拖慢产线。参数说明connect超时2秒Thread.sleep(500)是打印等待重试间隔1秒。注意重试要有上限且要记录失败单号人工补打。5. 避坑与排查TSC JAVA调用最常见的5个翻车现场5.1 现象打印出来是乱码或指令原文原因编码不对。TSPL指令是ASCII但中文内容如果直接按UTF-8发打印机会把多字节当指令解析。解决中文内容要么用打印机内置字库的区位码要么把中文转成点阵图用BITMAP指令发。我一般推荐后者兼容性最好。5.2 现象每张都歪越打越偏原因GAP值和实际标签间隙不匹配或者标签纸没校准。解决先做打印机自检和校准按住走纸键开机确认自检页上的GAP值再改代码里的GAP。如果换了标签纸批次要重新校准。5.3 现象串口打开失败报Port busy原因端口被其他程序占用或者上一次没正常关闭。解决在代码里确保finally块关闭端口Windows下可以用设备管理器看端口占用Linux下用lsof /dev/ttyUSB0查。另外别同时引入RXTX和jSerialComm。5.4 现象网口打印时通时不通原因打印机IP是DHCP分配的重启后IP变了或者网络里有防火墙拦9100端口。解决给打印机配静态IP在代码里把IP做成配置项先用telnet确认端口通不通。5.5 现象批量打印丢单中间少了几张原因指令发太快打印机缓冲区溢出或者没等上一张打完就发下一张。解决每张之间加Thread.sleep或者用~HS回读状态确认上一张完成再发下一张。我一般用固定间隔加状态回读双保险。6. 把打印逻辑做成可配置模板一个能落地的进阶技巧走到这里单张和批量都能打了但生产环境里标签模板会变——今天加个二维码明天改个字体每次都改JAVA代码不现实。我一般会把TSPL指令做成模板文件JAVA侧只做变量替换。模板文件label_template.tspl长这样SIZE ${width} mm,${height} mm GAP ${gap} mm,0 mm DENSITY ${density} SPEED ${speed} CLS TEXT 20,20,3,0,1,1,${orderNo} TEXT 20,60,3,0,1,1,${productName} BARCODE 20,120,128,60,1,0,2,4,${barcode} QRCODE 200,20,M,4,A,0,${qrContent} PRINT ${copies},1JAVA侧用简单的占位符替换public String renderTemplate(String template, MapString, String vars) { String result template; for (Map.EntryString, String entry : vars.entrySet()) { result result.replace(${ entry.getKey() }, escapeTspl(entry.getValue())); } return result; }逻辑说明escapeTspl负责转义双引号和反斜杠防止内容破坏指令结构。参数说明QRCODE指令的M是纠错等级4是模块大小A是自动编码模式。模板文件放在配置目录改模板不用重新编译。这个做法有三个好处一是模板和代码分离运营能自己改二是不同产品用不同模板切换只换文件三是模板可以版本管理出问题能回滚。我一般还会在启动时校验模板里的占位符是否都能被变量覆盖缺变量直接启动失败别等到打印时才发现。验证方法很简单拿一张实际标签用模板渲染后打印和设计稿比对。重点看三个地方——条码能不能扫、文字有没有被裁、二维码纠错等级够不够。我习惯在产线部署前先打50张连续标签中间不干预看有没有歪纸和丢单。这个习惯帮我拦下过好几次参数配错的问题。最后说个我自己的教训早期我图省事把打印机IP和标签规格硬编码在代码里结果客户换了个车间IP段变了整条线停了两小时。从那以后凡是和硬件相关的参数我一律做成外部配置代码里只留读取逻辑。希望帮到你。本文还有配套的精品资源点击获取