【问题标题】:How to correctly communicate with 3D Printer如何与 3D 打印机正确通信
【发布时间】:2018-04-08 16:40:33
【问题描述】:

我必须编写一个 java 程序,通过网络接收 G-Code 命令并通过串行通信将它们发送到 3D 打印机。原则上一切似乎都很好,只要打印机需要超过 300 毫秒来执行命令。如果执行时间比这短,打印机接收下一个命令需要太多时间,导致命令执行之间出现延迟(打印机喷嘴静止约 100-200 毫秒)。这可能会成为 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 会延迟,直到再次有空闲空间。 所以基本上你只需要发送一个命令,等待ok,立即发送另一个。

【问题讨论】:

  • “我的瓶颈在哪里?” -- 我不懂 Java,但我在 blockForOK() 中看到了 sleep(1)常规。这意味着您的代码不是事件驱动的,而是轮询(以一秒的速度?),因此它不会非常敏感。最坏的情况是等待条件测试为真,但在测试后立即变为假,因此在最终识别出继续条件之前已经过了完整的延迟间隔(1000 毫秒?)。您的平均响应延迟为 500 毫秒?
  • sleep(1) 会导致 1 毫秒 的延迟,而不是 1000 毫秒。另外,我尝试过事件驱动的代码,它不能消除延迟。
  • 哇,我的学位也遇到了同样的问题,我从没想过其他人也会遇到这个问题……但对我来说,事件驱动的解决方案运行良好。你能包括你的代码吗?
  • 没有解决方案,但有一些想法。我不会使用System.currentTimeMillis() 进行测量,因为它不精确 - 请参阅here。但是,您的超时设置为 0,因此我不会将此视为问题。我的猜测是sleep(1),它使线程休眠至少 1 毫秒,这取决于线程何时再次激活的调度程序 - 请参阅here。是否可以完全删除线程并将您的业务逻辑添加到 handleData() 而不是使用 volatile 布尔值?
  • 感谢您的意见!业务逻辑到底是什么意思?如果您的意思是调用发送的部分(目前在“打印机线程”的“运行”中),我认为可以给我更多的延迟,因为仅在从打印机收到命令时才调用 handleData()。但另一方面,我真的应该尝试一下。

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


【解决方案1】:
@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 可能比预期的要长得多。最长 30 毫秒或 100 毫秒,具体取决于内核配置。

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

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


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

这实际上导致了您的问题。 SerialPort.TIMEOUT_SCANNER 在读取时激活等待期。在接收到第一个字节后,它将至少再等待 100 毫秒,以查看它是否会成为消息的一部分。因此,在它看到ok 之后,它会在操作系统端内部等待 100 毫秒,然后再假定这就是全部。

您需要SerialPort.TIMEOUT_READ_SEMI_BLOCKING 以实现低延迟,但除非缓冲,否则将出现第一段中预测的问题。

重复设置也会导致另一个问题,因为Serialport::setComPortTimeouts内部有200ms的睡眠。每个串行连接设置一次,仅此而已。

【讨论】:

  • 这听起来很合理。我会尽快测试并给你反馈! (和赏金)
【解决方案2】:

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

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

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

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

【讨论】:

  • 如果我没有任何延迟地发送它适用于前几个命令,然后它似乎跳过命令。所以这里没有硬件流控制。
猜你喜欢
  • 2019-05-27
  • 1970-01-01
  • 2016-07-16
  • 1970-01-01
  • 2013-05-12
  • 1970-01-01
  • 2014-05-10
  • 1970-01-01
  • 2013-01-26
相关资源
最近更新 更多