You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter使用flutter_libserialport实现Modbus RTU返回数据失败问题

问题分析与解决方案

原代码核心问题

  1. 接收长度判断错误:Modbus RTU 03功能码的响应字节数并非寄存器数量,而是 5 + 2×numberOfRegisters(从站ID1字节+功能码1字节+数据长度1字节+寄存器数据2×N字节+CRC2字节),原循环条件bytesReceived < numberOfRegisters会导致无限等待。
  2. 重复订阅串口流:while循环内多次调用reader.stream.listen,创建多个流订阅,导致数据收集逻辑混乱,且asFuture()无法正确触发完成信号。
  3. 未管理订阅生命周期:订阅流后未主动取消,导致内存泄漏且无法终止等待。
  4. 可能的发送帧错误: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 14:00:33