Flutter使用flutter_libserialport实现Modbus RTU返回数据失败问题
问题分析与解决方案
原代码核心问题
- 接收长度判断错误:Modbus RTU 03功能码的响应字节数并非寄存器数量,而是
5 + 2×numberOfRegisters(从站ID1字节+功能码1字节+数据长度1字节+寄存器数据2×N字节+CRC2字节),原循环条件bytesReceived < numberOfRegisters会导致无限等待。 - 重复订阅串口流:while循环内多次调用
reader.stream.listen,创建多个流订阅,导致数据收集逻辑混乱,且asFuture()无法正确触发完成信号。 - 未管理订阅生命周期:订阅流后未主动取消,导致内存泄漏且无法终止等待。
- 可能的发送帧错误:
port.write(command(data))中command(data)可能是多余封装,导致发送的Modbus帧格式错误。
修复后的readRegisters函数
Future<List<int>> readRegisters(int slaveID, int startAddress, int numberOfRegisters) async { // 构造Modbus读保持寄存器请求帧 List<int> requestFrame = [ slaveID, 0x03, startAddress >> 8, startAddress & 0xFF, numberOfRegisters >> 8, numberOfRegisters & 0xFF ]; // 计算并添加CRC校验 int crc = calculateModbusChecksum(requestFrame); requestFrame.add(crc & 0xFF); requestFrame.add(crc >> 8); // 发送请求帧(若command函数是多余封装,直接用port.write(requestFrame)) port.write(requestFrame); await port.flush(); // 确保数据完全写入串口 // 计算预期接收的总字节数 final expectedBytes = 5 + 2 * numberOfRegisters; final reader = SerialPortReader(port, timeout: 2000); List<int> response = []; StreamSubscription? subscription; try { // 订阅流收集数据 subscription = reader.stream.listen((data) { if (data.isNotEmpty) { response.addAll(data); // 收集到足够数据后主动取消订阅 if (response.length >= expectedBytes) { subscription?.cancel(); } } }, onError: (error) { print('串口读取错误: $error'); subscription?.cancel(); }); // 等待直到收集足够数据或超时 await Future.doWhile(() async { if (response.length >= expectedBytes) return false; await Future.delayed(const Duration(milliseconds: 100)); return true; }); } catch (e) { print('读取过程异常: $e'); } finally { subscription?.cancel(); // 确保订阅被取消 } // 可选:校验返回数据的CRC if (response.length >= 2) { int receivedCrc = (response[response.length - 1] << 8) | response[response.length - 2]; int calculatedCrc = calculateModbusChecksum(response.sublist(0, response.length - 2)); if (receivedCrc != calculatedCrc) { print('CRC校验失败'); return []; } } return response; }
关键修改说明
- 修正长度判断逻辑:用协议规定的响应长度公式计算预期字节数,确保数据收集完成后立即终止等待。
- 单次订阅流:仅创建一次流订阅,避免重复订阅导致的逻辑混乱。
- 主动管理订阅:收集到足够数据或异常时主动取消订阅,防止内存泄漏。
- 添加flush操作:确保发送的请求帧完全写入串口,避免数据滞留。
- 增加CRC校验:验证返回数据的正确性,避免处理错误帧。
调用示例
List<int> receivedData = await readRegisters(0xFE, 0x0000, 0x001A); print('Data: $receivedData');
内容的提问来源于stack exchange,提问作者xximissy
相关产品推荐
相关产品推荐

