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

如何实现C# MAUI Android应用通过USB OTG与Arduino串口通信

解决MAUI Android与Arduino USB OTG串口通信问题

核心问题排查与修复步骤

  • 修正CH340驱动匹配:原代码给CH340设备错误绑定了CdcAcmSerialDriver,需替换为专用的Ch34xSerialDriver
  • 修复权限请求逻辑:权限请求是异步操作,需等待用户授权后再执行后续连接,不能直接返回
  • 替换async void为async Task:避免异步操作异常无法捕获、流程无法正确等待的问题
  • 完善资源释放:添加资源清理方法,确保串口和连接在使用后正确关闭
  • 优化数据收发:给读写操作添加异常捕获,发送数据时增加换行符适配Arduino读取逻辑

修正后的完整代码

#if ANDROID
using Android.App;
using Android.Content;
using Android.Content.PM;
using Android.Hardware.Usb;
using Android.OS;
using Hoho.Android.UsbSerial.Driver;
using System;
using System.Threading.Tasks;
#endif
using System.Collections.Generic;
using System.Text;

namespace DroneBrainPlus;

public static class ArduinoController
{
#if ANDROID
    public static UsbManager? _usbManager;
    private static IUsbSerialDriver? _usbSerialDriver;
    private static IUsbSerialPort? _usbSerialPort;
    private static UsbDeviceConnection? _connection;
    private static TaskCompletionSource<bool>? _permissionTcs;

    // 权限广播接收器
    private class UsbPermissionReceiver : BroadcastReceiver
    {
        public const string ACTION_USB_PERMISSION = "com.dronebrainplus.USB_PERMISSION";

        public override void OnReceive(Context? context, Intent? intent)
        {
            if (intent?.Action == ACTION_USB_PERMISSION)
            {
                bool granted = intent.GetBooleanExtra(UsbManager.ExtraPermissionGranted, false);
                _permissionTcs?.SetResult(granted);
            }
        }
    }

    public static async Task FindAndConnectToCH34x()
    {
        _usbManager = (UsbManager)Android.App.Application.Context.GetSystemService(Android.App.Application.UsbService);
        var permissionFilter = new IntentFilter(UsbPermissionReceiver.ACTION_USB_PERMISSION);
        Android.App.Application.Context.RegisterReceiver(new UsbPermissionReceiver(), permissionFilter);

        // 正确配置CH340驱动
        var customTable = UsbSerialProber.DefaultProbeTable;
        customTable.AddProduct(0x1A86, 0x7523, Class.FromType(typeof(Ch34xSerialDriver)));

        var prober = new UsbSerialProber(customTable);
        var availableDrivers = prober.FindAllDrivers(_usbManager);

        if (availableDrivers.Count == 0)
        {
            await Shell.Current.DisplayAlert("USB", "未找到兼容的串口设备", "确定").ConfigureAwait(false);
            return;
        }

        var usbSerialDriver = availableDrivers.FirstOrDefault();
        if (usbSerialDriver == null)
        {
            await Shell.Current.DisplayAlert("USB", "未找到串口驱动", "确定").ConfigureAwait(false);
            return;
        }

        var device = usbSerialDriver.Device;

        // 处理权限请求
        if (!_usbManager.HasPermission(device))
        {
            _permissionTcs = new TaskCompletionSource<bool>();
            var permissionIntent = PendingIntent.GetBroadcast(Android.App.Application.Context, 0,
                new Intent(UsbPermissionReceiver.ACTION_USB_PERMISSION), PendingIntentFlags.Immutable);

            _usbManager.RequestPermission(device, permissionIntent);
            bool permissionGranted = await _permissionTcs.Task.ConfigureAwait(false);

            if (!permissionGranted)
            {
                await Shell.Current.DisplayAlert("USB", "未获取到USB设备权限", "确定").ConfigureAwait(false);
                return;
            }
        }

        // 打开设备连接
        _connection = _usbManager.OpenDevice(device);
        if (_connection == null)
        {
            await Shell.Current.DisplayAlert("USB", "无法打开USB设备连接", "确定").ConfigureAwait(false);
            return;
        }

        var usbSerialPort = usbSerialDriver.Ports.FirstOrDefault();
        if (usbSerialPort == null)
        {
            await Shell.Current.DisplayAlert("USB", "未找到可用串口", "确定").ConfigureAwait(false);
            return;
        }

        try
        {
            _usbSerialPort = usbSerialPort;
            _usbSerialPort.Open(_connection);
            _usbSerialPort.SetParameters(9600, 8, StopBits.One, Parity.None);
            await Shell.Current.DisplayAlert("USB", "设备连接成功", "确定").ConfigureAwait(false);
        }
        catch (Exception ex)
        {
            await Shell.Current.DisplayAlert("错误", $"连接失败:{ex.Message}", "确定").ConfigureAwait(false);
            CleanupResources();
        }
    }

    public static async Task SendData(string message)
    {
        if (_usbSerialPort == null)
        {
            await Shell.Current.DisplayAlert("提示", "未连接到设备", "确定").ConfigureAwait(false);
            return;
        }

        try
        {
            byte[] buffer = Encoding.UTF8.GetBytes(message + "\n"); // 适配Arduino换行触发读取逻辑
            int bytesWritten = _usbSerialPort.Write(buffer, 1000);
            await Shell.Current.DisplayAlert("USB", $"已发送:{message}({bytesWritten}字节)", "确定").ConfigureAwait(false);
        }
        catch (Exception ex)
        {
            await Shell.Current.DisplayAlert("错误", $"发送失败:{ex.Message}", "确定").ConfigureAwait(false);
        }
    }

    public static async Task ReadData()
    {
        if (_usbSerialPort == null)
        {
            await Shell.Current.DisplayAlert("提示", "未连接到设备", "确定").ConfigureAwait(false);
            return;
        }

        try
        {
            byte[] buffer = new byte[64];
            int numBytesRead = _usbSerialPort.Read(buffer, 1000);
            if (numBytesRead > 0)
            {
                string receivedData = Encoding.UTF8.GetString(buffer, 0, numBytesRead).Trim();
                await Shell.Current.DisplayAlert("USB", $"已接收:{receivedData}", "确定").ConfigureAwait(false);
            }
            else
            {
                await Shell.Current.DisplayAlert("提示", "未读取到数据", "确定").ConfigureAwait(false);
            }
        }
        catch (Exception ex)
        {
            await Shell.Current.DisplayAlert("错误", $"读取失败:{ex.Message}", "确定").ConfigureAwait(false);
        }
    }

    public static void CleanupResources()
    {
        _usbSerialPort?.Close();
        _connection?.Close();
        _usbSerialPort = null;
        _connection = null;
        _usbSerialDriver = null;
    }
#endif
}

额外配置与注意事项

  • Android权限配置:在AndroidManifest.xml中添加以下权限:
    <uses-permission android:name="android.permission.USB_PERMISSION" />
    <uses-feature android:name="android.hardware.usb.host" android:required="false" />
    
  • 设备兼容性:确保Android设备支持USB OTG功能,使用合格的OTG线缆
  • Arduino端适配:Arduino代码需用Serial.readStringUntil('\n')等方式读取带换行符的数据

内容的提问来源于stack exchange,提问作者Maganes

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 11:55:54