Xamarin Forms Android与ESP32 USB串口通信读取数据实现咨询
Android端USB连接ESP32读取串口数据实现方案
你不需要直接操作/dev/bus/usb路径下的设备节点,Android系统的SELinux权限规则默认禁止普通应用直接访问这类节点,直接读写必然失败。你已经完成了USB设备枚举,剩下的流程只需要按照USB Host模式的标准流程走即可,和Windows下System.IO.Ports的使用逻辑差异不大。
1 前置权限配置
首先在你的Android项目AndroidManifest.xml里添加以下权限和特性声明:
<uses-feature android:name="android.hardware.usb.host" android:required="true" /> <uses-permission android:name="android.permission.USB_PERMISSION" />
枚举到目标ESP32设备后,必须先申请访问权限,没有授权的情况下任何USB操作都会被拒绝:
// 替换为你枚举到的目标ESP32设备实例 UsbDevice targetEsp32Device = null; // 这里补全你之前的枚举逻辑,通过VID/PID匹配到ESP32后赋值给targetEsp32Device private const string USB_PERMISSION_ACTION = "com.yourpackagename.USB_PERMISSION"; private UsbManager _usbManager; private BroadcastReceiver _usbPermissionReceiver; protected override void OnCreate(Bundle savedInstanceState) { base.OnCreate(savedInstanceState); _usbManager = (UsbManager)GetSystemService(UsbService); // 注册权限申请结果广播 _usbPermissionReceiver = new UsbPermissionReceiver(this); RegisterReceiver(_usbPermissionReceiver, new IntentFilter(USB_PERMISSION_ACTION)); // 发起权限申请 PendingIntent permissionIntent = PendingIntent.GetBroadcast( this, 0, new Intent(USB_PERMISSION_ACTION), PendingIntentFlags.Mutable ); _usbManager.RequestPermission(targetEsp32Device, permissionIntent); } // 权限结果接收类 private class UsbPermissionReceiver : BroadcastReceiver { private readonly MainActivity _activity; public UsbPermissionReceiver(MainActivity activity) => _activity = activity; public override void OnReceive(Context context, Intent intent) { if (intent.GetBooleanExtra(UsbManager.ExtraPermissionGranted, false)) { _activity.OpenSerialPort(targetEsp32Device); } } }
2 串口连接与数据读取
不需要自己解析USB CDC协议,直接安装NuGet包UsbSerialForAndroid即可,这个库封装了所有常见USB转串口芯片(CH340、CP2102、FTDI)以及原生CDC-ACM设备(ESP32原生USB串口)的驱动,API逻辑和Windows下的SerialPort高度相似。
拿到权限后调用以下方法初始化串口、读取数据:
private UsbSerialPort _serialPort; private UsbDeviceConnection _connection; private CancellationTokenSource _readCts; private void OpenSerialPort(UsbDevice device) { // 匹配设备驱动 UsbSerialProber prober = UsbSerialProber.GetDefaultProber(); UsbSerialDriver driver = prober.ProbeDevice(device); // 如果默认探针没识别到原生ESP32 USB CDC设备,手动创建CDC驱动 driver ??= new CdcAcmSerialDriver(device, 0); _connection = _usbManager.OpenDevice(device); if (_connection == null) return; _serialPort = driver.Ports[0]; _serialPort.Open(_connection); // 波特率、数据位、停止位、校验位必须和ESP32端输出配置完全一致 _serialPort.SetParameters( baudRate: 115200, dataBits: 8, stopBits: UsbSerialPort.Stopbits1, parity: UsbSerialPort.ParityNone ); // 启动子线程循环读取数据 _readCts = new CancellationTokenSource(); _ = Task.Run(async () => { byte[] buffer = new byte[4096]; while (!_readCts.IsCancellationRequested && _serialPort.IsOpen) { try { int readLen = _serialPort.Read(buffer, 500); // 读超时500ms if (readLen > 0) { string receivedData = Encoding.UTF8.GetString(buffer, 0, readLen); // 此处处理接收到的RFID/iButton序列号 System.Diagnostics.Debug.WriteLine($"串口收到数据: {receivedData}"); } } catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"串口读取异常: {ex.Message}"); break; } } }, _readCts.Token); } // 页面销毁时记得释放资源 protected override void OnDestroy() { base.OnDestroy(); _readCts?.Cancel(); if (_serialPort?.IsOpen == true) _serialPort.Close(); _connection?.Close(); UnregisterReceiver(_usbPermissionReceiver); }
3 常见问题说明
- 不要尝试直接通过文件IO访问
/dev/bus/usb/下的节点,普通应用没有该路径的访问权限,Android 10及以上版本的SELinux规则会直接拦截这类操作。 - 如果收到乱码,优先检查两端串口参数是否匹配,尤其是波特率是否和ESP32代码中配置的一致。
- 所有串口读写操作必须放在子线程执行,不能在主线程做阻塞式IO,否则会触发应用无响应(ANR)。
- 如果是定制的自助终端Android系统,可以在系统设置中将你的App设置为USB设备默认处理应用,避免每次插入设备都弹出授权弹窗。
内容的提问来源于stack exchange,提问作者Andrew Taylor
相关产品推荐
相关产品推荐

