Files
ch34/docs/USAGE_GUIDE.md
2026-04-21 12:57:40 +08:00

24 KiB
Raw Permalink Blame History

CH34 Flutter 插件使用手册

版本: 1.0.0
支持的芯片: CH340/CH341/CH342/CH343/CH344/CH347/CH9101/CH9102/CH9103/CH9104/CH9143


目录

  1. 简介
  2. 安装与配置
  3. 快速开始
  4. API 参考
  5. 类型定义
  6. 完整示例
  7. 常见问题

一、简介

ch34 是一个 Flutter 插件,为 WCH CH34X 系列 USB 转串口芯片提供 Flutter 接口支持。通过该插件Flutter 应用可以与 CH34X 芯片进行串口通信。

支持的芯片型号

芯片型号 说明
CH340 单串口 USB 转串口芯片
CH341 并口/串口 USB 转换芯片
CH342 双串口 USB 转串口芯片
CH343 增强型单串口芯片
CH344 四串口 USB 转串口芯片
CH347 高速 USB 转串口芯片
CH9101 单串口 USB 转串口芯片
CH9102 双串口 USB 转串口芯片
CH9103 四串口 USB 转串口芯片
CH9104 八串口 USB 转串口芯片
CH9143 多串口 USB 转串口芯片

系统要求

  • Android 4.4 及以上版本
  • 支持 USB Host 或 OTG 功能的 Android 设备

二、安装与配置

2.1 添加依赖

pubspec.yaml 中添加:

dependencies:
  ch34: ^1.0.0

2.2 导入插件

import 'package:ch34/ch34.dart';

2.3 初始化配置

在应用启动时进行初始化配置:

// 开启调试模式(可选,默认关闭)
await Ch34Manager.setDebug(true);

// 设置全局读取超时时间(可选,默认 0 表示异步传输)
await Ch34Manager.setReadTimeout(1000); // 1000ms

三、快速开始

3.1 基本使用流程

// 1. 枚举可用设备
final devices = await Ch34Manager.enumDevice();
if (devices.isEmpty) {
  print('未找到 CH34X 设备');
  return;
}

// 2. 选择设备
final device = devices.first;
print('找到设备: ${device.deviceName}, 芯片: ${device.chipType}');

// 3. 打开设备
final opened = await Ch34Manager.openDevice(device.deviceName);
if (!opened) {
  print('打开设备失败');
  return;
}

// 4. 设置串口参数
await Ch34Manager.setSerialParameter(
  device.deviceName,
  0, // 串口号(从 0 开始)
  const SerialParameter(
    baud: 9600,
    dataBits: DataBits.bits8,
    stopBits: StopBits.one,
    parity: Parity.none,
    hardwareFlowControl: false,
  ),
);

// 5. 注册数据回调
Ch34Manager.registerDataCallback(
  device.deviceName,
  0,
  (data) {
    print('收到数据: $data');
  },
);

// 6. 发送数据
await Ch34Manager.writeData(
  device.deviceName,
  0,
  Uint8List.fromList([0x01, 0x02, 0x03]),
);

// 7. 使用完毕后断开
await Ch34Manager.disconnect(device.deviceName);

3.2 权限处理

// 请求 USB 设备权限
final granted = await Ch34Manager.requestPermission(device.deviceName);
if (!granted) {
  print('USB 权限被拒绝');
  return;
}

四、API 参考

4.1 基础方法

getPlatformVersion

获取平台版本。

static Future<String?> getPlatformVersion()

返回值: 平台版本字符串


4.2 设备枚举与识别

enumDevice

枚举当前所有可用的 USB 设备。

static Future<List<UsbDeviceInfo>> enumDevice()

返回值: 可用 USB 设备列表
异常: Ch34Exception - 枚举失败时抛出

getChipType

获取指定设备的芯片型号。

static Future<String?> getChipType(String deviceName)

参数:

  • deviceName - 设备名称

返回值: 芯片型号字符串,null 表示无法识别


4.3 设备打开与权限

openDevice

打开 USB 设备。

static Future<bool> openDevice(String deviceName)

参数:

  • deviceName - 设备名称

返回值: true 成功,false 失败

requestPermission

申请 USB 设备的权限。

static Future<bool> requestPermission(String deviceName)

参数:

  • deviceName - 设备名称

返回值: true 已授权,false 被拒绝


4.4 USB 状态监听

setUsbStateListener

注册 USB 设备插拔状态监听。

static void setUsbStateListener(
  void Function(String deviceName, bool connected) onStateChanged,
)

参数:

  • onStateChanged - 状态变化回调函数

removeUsbStateListener

移除 USB 状态监听。

static void removeUsbStateListener()

4.5 串口信息

getSerialCount

获取设备的串口数目。

static Future<int> getSerialCount(String deviceName)

参数:

  • deviceName - 设备名称

返回值: 串口数目,-1 表示读取芯片型号失败

getSerialBaud

获取串口波特率(仅 CH9114 系列有效)。

static Future<int> getSerialBaud(String deviceName, int serialNumber)

参数:

  • deviceName - 设备名称
  • serialNumber - 串口号

返回值: 大于 0 表示波特率,小于 0 表示出错

getChipMasterFrequency

获取芯片主频(仅 CH9114 系列有效)。

static Future<ChipMasterFrequency> getChipMasterFrequency(String deviceName)

参数:

  • deviceName - 设备名称

返回值: 芯片主频信息对象

enableSerial

打开或关闭串口(仅 CH9114 系列有效)。

static Future<bool> enableSerial(
  String deviceName,
  int serialNumber,
  bool enable,
)

参数:

  • deviceName - 设备名称
  • serialNumber - 串口号
  • enable - true 打开,false 关闭

返回值: true 设置成功,false 设置失败


4.6 串口参数设置

setSerialParameter

设置串口参数。

static Future<bool> setSerialParameter(
  String deviceName,
  int serialNumber,
  SerialParameter parameter,
)

参数:

  • deviceName - 设备名称
  • serialNumber - 串口号(从 0 开始)
  • parameter - 串口参数配置

返回值: true 设置成功,false 设置失败


4.7 数据读写

writeData

发送串口数据(同步发送)。

static Future<int> writeData(
  String deviceName,
  int serialNumber,
  Uint8List data, {
  int timeout = 0,
})

参数:

  • deviceName - 设备名称
  • serialNumber - 串口号
  • data - 要发送的数据
  • timeout - 超时时间毫秒0 表示不超时

返回值: 实际发送的字节数

asyncWriteData

发送串口数据(异步发送)。

static Future<void> asyncWriteData(
  String deviceName,
  int serialNumber,
  Uint8List data,
)

参数:

  • deviceName - 设备名称
  • serialNumber - 串口号
  • data - 要发送的数据

说明: 将数据加入缓存持续发送,不返回状态和结果

readData

阻塞读取串口数据。

static Future<Uint8List> readData(
  String deviceName,
  int serialNumber,
)

参数:

  • deviceName - 设备名称
  • serialNumber - 串口号

返回值: 读取到的数据

readDataWithTimeout

主动读取串口数据(带超时参数)。

static Future<Uint8List> readDataWithTimeout(
  String deviceName,
  int serialNumber,
  int vTime,
  int vMin,
)

参数:

  • deviceName - 设备名称
  • serialNumber - 串口号
  • vTime - 等待时间(毫秒)
  • vMin - 读取的最小字节数

返回值: 读取到的数据

读取行为说明:

  • vTime>0, vMin>0: 阻塞直到读取到第一个字符后开始计时,时间到或已读够 vMin 个字符则返回
  • vTime>0, vMin=0: 读到数据立即返回,否则最多等待 vTime
  • vTime=0, vMin>0: 一直阻塞直到读到 vMin 个字符后返回

registerDataCallback

注册串口数据回调。

static Future<void> registerDataCallback(
  String deviceName,
  int serialNumber,
  void Function(Uint8List data) onData,
)

参数:

  • deviceName - 设备名称
  • serialNumber - 串口号
  • onData - 数据接收回调函数

说明: 注册后数据自动推送,不需要主动调用 readData推荐使用此方式接收数据

removeDataCallback

取消注册串口数据回调。

static void removeDataCallback(String deviceName)

参数:

  • deviceName - 设备名称

4.8 连接状态

isConnected

判断 USB 设备是否已经连接。

static Future<bool> isConnected(String deviceName)

参数:

  • deviceName - 设备名称

返回值: true 已连接,false 未连接

getConnectedDevices

获取当前已经打开的设备列表。

static Future<List<String>> getConnectedDevices()

返回值: 已打开的设备名称列表


4.9 断开与关闭

disconnect

断开 USB 设备的连接。

static Future<void> disconnect(String deviceName)

参数:

  • deviceName - 设备名称

close

释放资源,关闭所有串口设备。

static Future<void> close()

4.10 GPIO 功能

isSupportGpio

查询设备是否支持 GPIO 功能。

static Future<bool> isSupportGpio(String deviceName)

参数:

  • deviceName - 设备名称

返回值: true 支持,false 不支持

queryGpioCount

查询该 USB 设备的 GPIO 数目。

static Future<int> queryGpioCount(String deviceName)

参数:

  • deviceName - 设备名称

返回值: GPIO 数目

queryGpioStatus

查询指定 GPIO 的状态。

static Future<GpioStatus> queryGpioStatus(
  String deviceName,
  int gpioIndex,
)

参数:

  • deviceName - 设备名称
  • gpioIndex - GPIO 编号(从 0 开始)

返回值: GPIO 状态

queryAllGpioStatus

查询所有 GPIO 状态。

static Future<List<GpioStatus>> queryAllGpioStatus(String deviceName)

参数:

  • deviceName - 设备名称

返回值: 全部 GPIO 状态列表

enableGpio

使能指定 GPIO。

static Future<bool> enableGpio(
  String deviceName,
  int gpioIndex,
  bool enable,
  GpioDirection direction,
)

参数:

  • deviceName - 设备名称
  • gpioIndex - GPIO 编号
  • enable - true 使能,false 关闭
  • direction - GPIO 方向

返回值: true 使能成功,false 使能失败

setGpioVal

设置指定 GPIO 的电平值。

static Future<bool> setGpioVal(
  String deviceName,
  int gpioIndex,
  GpioValue value,
)

参数:

  • deviceName - 设备名称
  • gpioIndex - GPIO 编号
  • value - GPIO 电平值

返回值: true 设置成功,false 设置失败

getGpioVal

获取指定 GPIO 的电平值。

static Future<GpioValue> getGpioVal(
  String deviceName,
  int gpioIndex,
)

参数:

  • deviceName - 设备名称
  • gpioIndex - GPIO 编号

返回值: GPIO 电平值


4.11 信号控制

setDtr

设置 DTR 信号。

static Future<bool> setDtr(
  String deviceName,
  int serialNumber,
  bool valid,
)

参数:

  • deviceName - 设备名称
  • serialNumber - 串口号
  • valid - 是否有效(低电平有效)

返回值: true 设置成功,false 设置失败

setRts

设置 RTS 信号。

static Future<bool> setRts(
  String deviceName,
  int serialNumber,
  bool valid,
)

参数:

  • deviceName - 设备名称
  • serialNumber - 串口号
  • valid - 是否有效(低电平有效)

返回值: true 设置成功,false 设置失败

setBreakSignal

设置 Break 信号。

static Future<bool> setBreakSignal(
  String deviceName,
  int serialNumber,
  bool valid,
)

参数:

  • deviceName - 设备名称
  • serialNumber - 串口号
  • valid - 是否有效(低电平有效)

返回值: true 设置成功,false 设置失败


4.12 Modem 状态回调

registerModemStatusCallback

注册 Modem 控制信号状态回调。

static Future<void> registerModemStatusCallback(
  String deviceName,
  void Function(ModemStatus status) onModemStatus,
)

参数:

  • deviceName - 设备名称
  • onModemStatus - Modem 状态变化回调

removeModemStatusCallback

移除 Modem 状态回调。

static void removeModemStatusCallback(String deviceName)

参数:

  • deviceName - 设备名称

4.13 错误查询

querySerialErrorCount

查询串口错误状态。

static Future<int> querySerialErrorCount(
  String deviceName,
  int serialNumber,
  SerialErrorType errorType,
)

参数:

  • deviceName - 设备名称
  • serialNumber - 串口号
  • errorType - 错误类型

返回值: 该种错误出现的次数


4.14 全局配置

setReadTimeout

设置读取超时时间。

static Future<void> setReadTimeout(int timeout)

参数:

  • timeout - 超时时间(毫秒)

说明: 全局有效,应在 APP 初始化时调用

addNewHardware

添加自定义硬件 VID/PID。

static Future<void> addNewHardware(
  int vid,
  int pid, {
  String? chipType,
})

参数:

  • vid - 硬件 VID
  • pid - 硬件 PID
  • chipType - 芯片类型(可选)

setDebug

设置调试模式。

static Future<void> setDebug(bool enabled)

参数:

  • enabled - true 开启,false 关闭

isDebugMode

返回当前是否处于调试模式。

static Future<bool> isDebugMode()

返回值: true 处于调试模式,false 不处于


五、类型定义

5.1 枚举类型

DataBits - 数据位

enum DataBits {
  bits5(5),  // 5 位数据位
  bits6(6),  // 6 位数据位
  bits7(7),  // 7 位数据位
  bits8(8);  // 8 位数据位(默认)
}

StopBits - 停止位

enum StopBits {
  one(1),   // 1 位停止位(默认)
  two(2),   // 2 位停止位
}

Parity - 校验位

enum Parity {
  none(0),    // 无校验(默认)
  odd(1),     // 奇校验
  even(2),    // 偶校验
  mark(3),    // 标记校验
  space(4),   // 空格校验
}

GpioDirection - GPIO 方向

enum GpioDirection {
  inDir,   // 输入方向
  outDir,  // 输出方向
}

GpioValue - GPIO 电平值

enum GpioValue {
  low,   // 低电平
  high,  // 高电平
}

SerialErrorType - 串口错误类型

enum SerialErrorType {
  framingError,     // 帧错误
  parityError,      // 校验错误
  overrunError,     // 溢出错误
  breakInterrupt,   // 中断错误
}

5.2 数据类

SerialParameter - 串口参数

class SerialParameter {
  const SerialParameter({
    this.baud = 115200,                    // 波特率
    this.dataBits = DataBits.bits8,        // 数据位
    this.stopBits = StopBits.one,          // 停止位
    this.parity = Parity.none,             // 校验位
    this.hardwareFlowControl = false,      // 硬件流控
  });
}

常用波特率: 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200

UsbDeviceInfo - USB 设备信息

class UsbDeviceInfo {
  final String deviceName;     // 设备名称
  final int productId;         // 产品 ID (PID)
  final int vendorId;          // 厂商 ID (VID)
  final int serialCount;       // 串口数量
  final String? chipType;      // 芯片型号(如果识别)
}

GpioStatus - GPIO 状态

class GpioStatus {
  final int index;              // GPIO 编号
  final GpioDirection direction; // 方向
  final GpioValue value;        // 电平值
  final bool enabled;           // 是否已使能
}

ChipMasterFrequency - 芯片主频

class ChipMasterFrequency {
  final int frequency;          // 芯片主频Hz
  final bool switchEnable;      // 是否允许切换主频
  final int coStatus;           // 晶振状态
}

ModemStatus - Modem 状态

class ModemStatus {
  final bool cts;   // Clear To Send
  final bool dsr;   // Data Set Ready
  final bool ri;    // Ring Indicator
  final bool dcd;   // Data Carrier Detect
}

六、完整示例

6.1 设备扫描与管理

import 'package:ch34/ch34.dart';
import 'dart:typed_data';

class SerialDeviceManager {
  static List<UsbDeviceInfo> _devices = [];

  /// 扫描并连接设备
  static Future<void> scanAndConnect() async {
    try {
      // 获取已连接设备
      final connected = await Ch34Manager.getConnectedDevices();
      print('已连接设备: $connected');

      // 枚举所有可用设备
      _devices = await Ch34Manager.enumDevice();
      print('发现 ${_devices.length} 个设备');

      for (final device in _devices) {
        print('设备: ${device.deviceName}');
        print('  VID: 0x${device.vendorId.toRadixString(16).toUpperCase().padLeft(4, '0')}');
        print('  PID: 0x${device.productId.toRadixString(16).toUpperCase().padLeft(4, '0')}');
        print('  芯片: ${device.chipType ?? "未知"}');
        print('  串口数: ${device.serialCount}');
      }
    } on Ch34Exception catch (e) {
      print('设备扫描失败: $e');
    }
  }
}

6.2 串口通信封装

class SerialPortService {
  String? _deviceName;
  int _serialNumber = 0;
  bool _isConnected = false;

  /// 打开串口
  Future<bool> open(String deviceName, {int baudRate = 9600}) async {
    try {
      // 请求权限
      final granted = await Ch34Manager.requestPermission(deviceName);
      if (!granted) {
        return false;
      }

      // 打开设备
      final opened = await Ch34Manager.openDevice(deviceName);
      if (!opened) {
        return false;
      }

      // 设置参数
      final configured = await Ch34Manager.setSerialParameter(
        deviceName,
        _serialNumber,
        SerialParameter(
          baud: baudRate,
          dataBits: DataBits.bits8,
          stopBits: StopBits.one,
          parity: Parity.none,
        ),
      );

      if (!configured) {
        await Ch34Manager.disconnect(deviceName);
        return false;
      }

      _deviceName = deviceName;
      _isConnected = true;
      return true;
    } catch (e) {
      print('打开串口失败: $e');
      return false;
    }
  }

  /// 关闭串口
  Future<void> close() async {
    if (_deviceName != null) {
      Ch34Manager.removeDataCallback(_deviceName!);
      await Ch34Manager.disconnect(_deviceName!);
      _deviceName = null;
      _isConnected = false;
    }
  }

  /// 发送数据
  Future<int> send(List<int> data) async {
    if (!_isConnected || _deviceName == null) {
      throw StateError('串口未连接');
    }

    return await Ch34Manager.writeData(
      _deviceName!,
      _serialNumber,
      Uint8List.fromList(data),
    );
  }

  /// 异步发送数据
  Future<void> sendAsync(List<int> data) async {
    if (!_isConnected || _deviceName == null) {
      throw StateError('串口未连接');
    }

    await Ch34Manager.asyncWriteData(
      _deviceName!,
      _serialNumber,
      Uint8List.fromList(data),
    );
  }

  /// 注册数据接收回调
  Future<void> onData(void Function(Uint8List data) callback) async {
    if (_deviceName == null) {
      throw StateError('串口未连接');
    }

    await Ch34Manager.registerDataCallback(
      _deviceName!,
      _serialNumber,
      callback,
    );
  }

  /// 获取连接状态
  bool get isConnected => _isConnected;
}

6.3 GPIO 控制示例

class GpioController {
  /// 配置并设置 GPIO
  static Future<void> configureGpio(
    String deviceName,
    int gpioIndex,
  ) async {
    // 检查是否支持 GPIO
    final supported = await Ch34Manager.isSupportGpio(deviceName);
    if (!supported) {
      print('设备不支持 GPIO 功能');
      return;
    }

    // 查询 GPIO 数量
    final count = await Ch34Manager.queryGpioCount(deviceName);
    if (gpioIndex >= count) {
      print('GPIO 编号超出范围');
      return;
    }

    // 使能 GPIO输出方向
    final enabled = await Ch34Manager.enableGpio(
      deviceName,
      gpioIndex,
      true,
      GpioDirection.outDir,
    );

    if (!enabled) {
      print('使能 GPIO 失败');
      return;
    }

    // 设置高电平
    await Ch34Manager.setGpioVal(
      deviceName,
      gpioIndex,
      GpioValue.high,
    );

    // 读取电平值验证
    final value = await Ch34Manager.getGpioVal(deviceName, gpioIndex);
    print('GPIO $gpioIndex 电平: $value');
  }

  /// 查询所有 GPIO 状态
  static Future<void> printAllGpioStatus(String deviceName) async {
    final statuses = await Ch34Manager.queryAllGpioStatus(deviceName);
    for (final status in statuses) {
      print('GPIO ${status.index}: '
          '方向=${status.direction}, '
          '电平=${status.value}, '
          '使能=${status.enabled}');
    }
  }
}

6.4 Modem 状态监听

class ModemMonitor {
  static Future<void> startMonitoring(String deviceName) async {
    await Ch34Manager.registerModemStatusCallback(
      deviceName,
      (ModemStatus status) {
        print('Modem 状态变化:');
        print('  CTS: ${status.cts}');
        print('  DSR: ${status.dsr}');
        print('  RI:  ${status.ri}');
        print('  DCD: ${status.dcd}');
      },
    );
  }

  static void stopMonitoring(String deviceName) {
    Ch34Manager.removeModemStatusCallback(deviceName);
  }
}

6.5 错误检测

class ErrorDetector {
  static Future<void> checkErrors(String deviceName, int serialNumber) async {
    final framingErrors = await Ch34Manager.querySerialErrorCount(
      deviceName,
      serialNumber,
      SerialErrorType.framingError,
    );

    final parityErrors = await Ch34Manager.querySerialErrorCount(
      deviceName,
      serialNumber,
      SerialErrorType.parityError,
    );

    print('帧错误: $framingErrors, 校验错误: $parityErrors');
  }
}

七、常见问题

7.1 找不到设备

问题: enumDevice() 返回空列表

解决方法:

  1. 确认设备支持 USB Host/OTG 功能
  2. 检查 USB 线缆连接是否正常
  3. 确认 Android 设备已开启 OTG 功能(部分手机需要手动开启)
  4. 检查芯片是否在支持列表中

7.2 权限被拒绝

问题: requestPermission() 返回 false

解决方法:

  1. 确保在 AndroidManifest.xml 中添加了 USB 权限声明
  2. 用户需要在系统弹窗中点击"允许"
  3. 可以尝试重新请求权限

7.3 数据收发异常

问题: 发送数据成功但收不到回复

解决方法:

  1. 确认串口参数(波特率、数据位、停止位、校验位)与对端设备一致
  2. 使用 registerDataCallback 代替 readData,推荐回调方式接收数据
  3. 检查线缆连接和电平匹配3.3V vs 5V
  4. 使用 querySerialErrorCount 检查是否有串口错误

7.4 多串口设备使用

问题: CH344/CH9104 等多串口设备如何使用

解决方法:

// 多串口设备每个串口都需要单独配置
for (int i = 0; i < device.serialCount; i++) {
  await Ch34Manager.setSerialParameter(
    device.deviceName,
    i,  // 不同的串口号
    const SerialParameter(baud: 9600),
  );

  Ch34Manager.registerDataCallback(
    device.deviceName,
    i,
    (data) {
      print('串口 $i 收到数据: $data');
    },
  );
}

7.5 设备热插拔处理

问题: 设备拔出后如何检测

解决方法:

// 注册 USB 状态监听
Ch34Manager.setUsbStateListener((deviceName, connected) {
  if (!connected) {
    print('设备 $deviceName 已拔出');
    // 清理资源
    Ch34Manager.removeDataCallback(deviceName);
  } else {
    print('设备 $deviceName 已插入');
    // 重新打开设备
  }
});

7.6 自定义 VID/PID

问题: 如何支持非标准 VID/PID 的设备

解决方法:

// 添加自定义硬件
await Ch34Manager.addNewHardware(
  0x1234,  // 自定义 VID
  0x5678,  // 自定义 PID
  chipType: 'CH340',  // 可选:指定芯片类型
);

附录:通信通道说明

插件内部使用以下通道与 Android 原生端通信:

通道类型 名称 用途
MethodChannel ch34 双向方法调用
EventChannel ch34/data 串口数据推送
EventChannel ch34/modem Modem 状态变化
EventChannel ch34/usb_state USB 插拔状态

文档版本: 1.0.0
插件版本: 1.0.0