Prusa i3 Mk3s+通过C#串口传输GCODE异常:如何避免缓冲区过载?
问题描述
我正在进行一个周末拓展项目,尝试通过Windows PC的串口,使用C#向Prusa i3 Mk3s+打印机发送并打印GCODE文件。目前已成功建立连接,但向串口流写入数据时结果不稳定,且不确定串口配置是否正确(未找到OctoPrint与该打印机的连接示例)。我推测问题在于一次性发送数据量过大,同时读取串口返回信息时,难以准确等待"ok"指令(返回信息顺序混乱)。单条指令发送无问题,但发送完整GCODE文件时,打印机在执行到文件不同位置时会出现卡顿。请问如何通过串口向3D打印机流式传输大文件,避免其内部缓冲区过载?
现有代码
private static async Task Main(string[] args) { var ports = SerialPort.GetPortNames(); if (!ports.Any()) { Console.WriteLine("No ports found"); Console.WriteLine("Press any key to continue..."); Console.WriteLine(); Console.ReadKey(); } else { using (SerialPort mySerialPort = new SerialPort(ports.First())) { mySerialPort.BaudRate = 115200; mySerialPort.Parity = Parity.None; mySerialPort.StopBits = StopBits.One; mySerialPort.DataBits = 8; mySerialPort.Handshake = Handshake.None; mySerialPort.Open(); mySerialPort.DataReceived += DataRecieved; using (var fileToPrint = File.OpenRead("{Path to GCODE file}")) using (var lineReader = new StreamReader(fileToPrint)) { while (!lineReader.EndOfStream && lineReader is not null) { var line = await lineReader.ReadLineAsync() ?? string.Empty; if (!line.StartsWith(";")) { var cleanLine = line.Split(";").First().Trim(); mySerialPort.WriteLine(cleanLine); } } } Console.WriteLine("Press any key to continue..."); Console.WriteLine(); Console.ReadKey(); mySerialPort.Close(); } } } private static void DataRecieved(object sender, SerialDataReceivedEventArgs e) { SerialPort sp = (SerialPort)sender; var line = sp.ReadLine(); Console.WriteLine("Data from printer: {0}", line); }
解决方案
1. 确认串口配置正确性
Prusa i3 Mk3s+的标准串口配置和你当前设置完全一致,无需调整:
- 波特率:115200
- 奇偶校验:无
- 停止位:1
- 数据位:8
- 握手:无
2. 核心改进:实现"等待ok再发下一条"的流控逻辑
打印机串口缓冲区容量有限,不能连续无限制发送指令。必须采用一问一答的方式,收到打印机返回的"ok"后再发送下一条GCODE,这是解决缓冲区过载、卡顿问题的关键。
修改后的示例代码:
private static async Task Main(string[] args) { var ports = SerialPort.GetPortNames(); if (!ports.Any()) { Console.WriteLine("未找到可用串口"); Console.WriteLine("按任意键继续..."); Console.ReadKey(); return; } using (SerialPort mySerialPort = new SerialPort(ports.First())) { // 保持原有正确配置 mySerialPort.BaudRate = 115200; mySerialPort.Parity = Parity.None; mySerialPort.StopBits = StopBits.One; mySerialPort.DataBits = 8; mySerialPort.Handshake = Handshake.None; mySerialPort.NewLine = "\n"; // 明确指定换行符,匹配打印机通信协议 // 用于同步指令发送与响应的信号量,初始为0,等待打印机的第一个ok var responseSemaphore = new SemaphoreSlim(0, 1); mySerialPort.DataReceived += (sender, e) => { SerialPort sp = (SerialPort)sender; string line; // 循环读取所有可用数据,避免遗漏返回信息 while (sp.BytesToRead > 0) { line = sp.ReadLine().Trim(); Console.WriteLine("打印机返回: {0}", line); // 检测到ok信号后,释放信号量允许发送下一条指令 if (line.Equals("ok", StringComparison.OrdinalIgnoreCase)) { responseSemaphore.Release(); } } }; mySerialPort.Open(); Console.WriteLine("等待打印机初始化完成..."); // 等待打印机发送第一个ok,确认连接就绪 await responseSemaphore.WaitAsync(); using (var fileToPrint = File.OpenRead("{GCODE文件路径}")) using (var lineReader = new StreamReader(fileToPrint)) { string line; while ((line = await lineReader.ReadLineAsync()) != null) { // 过滤注释行、空行,减少无效通信 line = line.Split(';')[0].Trim(); if (string.IsNullOrWhiteSpace(line)) continue; // 发送指令 Console.WriteLine("发送指令: {0}", line); mySerialPort.WriteLine(line); // 等待打印机返回ok后再继续 await responseSemaphore.WaitAsync(); } } Console.WriteLine("打印任务已发送完成"); Console.WriteLine("按任意键继续..."); Console.ReadKey(); mySerialPort.Close(); } }
3. 其他优化点
- 读取逻辑优化:在DataReceived事件中循环读取所有可用数据,避免因单次读取一行导致的返回信息遗漏或顺序混乱
- 指令过滤:提前过滤空行和纯注释行,减少不必要的串口传输
- 初始化等待:打印机开机后需要等待其完成初始化并返回第一个"ok",再开始发送指令,避免初始指令丢失
4. 可选增强
可以给WaitAsync()添加超时参数(如await responseSemaphore.WaitAsync(TimeSpan.FromSeconds(10))),避免因打印机无响应导致程序卡死。
内容的提问来源于stack exchange,提问作者Bailey Miller
相关产品推荐
相关产品推荐

