如何正确地与3D打印机通信

时间:2018-04-08 16:40:33

标签: java serial-port serial-communication 3d-printing

我必须编写一个java程序,通过网络接收G-Code命令,并通过串行通信将它们发送到3D打印机。原则上一切似乎都没问题,只要打印机需要超过300毫秒来执行命令。如果执行时间短于此时间,则打印机接收下一个命令需要花费太多时间,并且导致命令执行之间的延迟(打印机喷嘴静止约100-200ms)。这可能会成为3D打印中的一个问题,所以我必须消除这种延迟。

为了进行比较:像Repetier Host或Cura这样的软件可以通过seial发送相同的命令,命令执行之间没有任何延迟,所以它必须以某种方式。

我使用jSerialComm库进行串行通信。

这是向打印机发送命令的线程:

@Override
public void run() {
if(printer == null) return;
    log("Printer Thread started!");
    //wait just in case
    Main.sleep(3000);

    long last = 0;
    while(true) {

        String cmd = printer.cmdQueue.poll();
        if (cmd != null && !cmd.equals("") && !cmd.equals("\n")) {
            log(cmd+" last: "+(System.currentTimeMillis()-last)+"ms");
            last = System.currentTimeMillis();
            send(cmd  + "\n", 0);
        }

    }
}

private void send(String cmd, int timeout) {
    printer.serialWrite(cmd);
    waitForBuffer(timeout);
}

private void waitForBuffer(int timeout) {
    if(!blockForOK(timeout))
        log("OK Timeout ("+timeout+"ms)");
}

public boolean blockForOK(int timeoutMillis) {
    long millis = System.currentTimeMillis();
    while(!printer.bufferAvailable) {
        if(timeoutMillis != 0)
            if(millis + timeoutMillis < System.currentTimeMillis()) return false;
        try {
            sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    printer.bufferAvailable = false;
    return true;
}

这是printer.serialWrite :(由Arduino Java Lib“启发”)

public void serialWrite(String s){
    comPort.setComPortTimeouts(SerialPort.TIMEOUT_SCANNER, 0, 500);
    try{Thread.sleep(5);} catch(Exception e){}

    PrintWriter pout = new PrintWriter(comPort.getOutputStream());
    pout.print(s);
    pout.flush();

}

printer是类Printer的对象,它实现com.fazecast.jSerialComm.SerialPortDataListener

打印机的相关功能

@Override
public int getListeningEvents() {
    return SerialPort.LISTENING_EVENT_DATA_AVAILABLE;

}

@Override
public void serialEvent(SerialPortEvent serialPortEvent) {
    byte[] newData = new byte[comPort.bytesAvailable()];
    int numRead = comPort.readBytes(newData, newData.length);
    handleData(new String(newData));
}

private void handleData(String line) {
    //log("RX: "+line);
    if(line.contains("ok")) {
        bufferAvailable = true;
    }
    if(line.contains("T:")) {
        printerThread.printer.temperature[0] = Utils.readFloat(line.substring(line.indexOf("T:")+2));
    }
    if(line.contains("T0:")) {
        printerThread.printer.temperature[0] = Utils.readFloat(line.substring(line.indexOf("T0:")+3));
    }
    if(line.contains("T1:")) {
        printerThread.printer.temperature[1] = Utils.readFloat(line.substring(line.indexOf("T1:")+3));
    }
    if(line.contains("T2:")) {
        printerThread.printer.temperature[2] = Utils.readFloat(line.substring(line.indexOf("T2:")+3));
    }
}

Printer.bufferAvailable被声明为volatile 我也尝试在另一个线程中阻塞jserialcomm的函数,结果相同。 我的瓶颈在哪里?我的代码中是否存在瓶颈或者jserialcomm是否会产生过多的开销?

对于那些没有3D打印经验的人: 当打印机收到有效命令时,它会将该命令放入内部缓冲区以最大限度地减少延迟。只要内部缓冲区中有可用空间,它就会回复ok。当缓冲区已满时,ok会延迟,直到再次有空闲空间。 所以基本上你只需要发送命令,等待确定,立即发送另一个命令。

2 个答案:

答案 0 :(得分:4)

@Override
public void serialEvent(SerialPortEvent serialPortEvent) {
    byte[] newData = new byte[comPort.bytesAvailable()];
    int numRead = comPort.readBytes(newData, newData.length);
    handleData(new String(newData));
}

此部分存在问题,可能在读取完整行之前触发了事件,因此可能只收到了ok的一半。在尝试将其解析为完整消息之前,您需要先缓冲(通过多个事件)并重新分配到消息中。

最坏的情况是,这可能导致完全失去温度读数或ok消息,因为它们被撕成两半。

请参阅InputStream example并将其打包在BufferedReader中,以便访问BufferedReader::readLine()。使用BufferedReader,您可以直接在主线程中使用它来轮询并同步处理响应。

try{Thread.sleep(5);} catch(Exception e){}
sleep(1);

你不想睡觉。根据您的系统环境(我强烈认为这不是在x86上的Windows上运行,而是在嵌入式平台上运行Linux),sleep可能比预期的要长得多。最长30ms或100ms,具体取决于内核配置。

写入之前的睡眠首先没有多大意义,您知道串口已准备好写入,因为您已经收到ok确认接收先前发送的命令。 / p>

使用BufferedReader时,接收期间的睡眠变得毫无意义。

comPort.setComPortTimeouts(SerialPort.TIMEOUT_SCANNER, 0, 500);

这实际上是导致你的问题。 SerialPort.TIMEOUT_SCANNER会在阅读时激活等待期。在收到第一个字节后,它将至少等待100ms,看它是否会成为消息的一部分。因此,在它看到ok之后,它会在OS内部等待100ms,然后才假定这就是全部。

您需要SerialPort.TIMEOUT_READ_SEMI_BLOCKING才能获得低延迟,但是除非进行缓冲,否则第一段中预测的问题就会出现。

重复设置也会导致另一个问题,因为内部Serialport::setComPortTimeouts内有200毫秒的休眠。每串口连接设置一次,不超过这个。

答案 1 :(得分:1)

检查打印机的手册(或告诉我们型号)不确定您是否确实需要等待ok,因此您可以同时读/写。有些时候有硬件流控制为你处理这些东西,有足够大的缓冲区。尝试发送命令而不等待ok,看看会发生什么。

如果您只想将命令从网络管道传输到串口,可以使用现成的解决方案,如socat。例如,运行以下内容:

socat TCP-LISTEN:8888,fork,reuseaddr FILE:/dev/ttyUSB0,b115200,raw

将来自连接到8888端口的客户端的所有字节直接传输到/dev/ttyUSB0,波特率为115200(反之亦然)。