举个大栗子 發表於 2023-9-12 15:14:00

Android USB开发—USB通信

<p>USB通信两端分别称为:HOST(USB主机) 与 Device(USB从机/USB配件),常见的主机就是我们的计算机。而Android 可以支持USB主机模式与USB配件模式,意思就是Android既可以是主机也可以是配件。<br>
<img src="https://img2023.cnblogs.com/blog/835426/202305/835426-20230531104748373-1632720238.png" alt="" loading="lazy"><br>
Android作为配件与其通信的主机必须设计为与Android设备兼容,即遵循Android配件通信协议(AOA协议)。也就是说需要按照此协议进行USB主机设备的开发。</p>
<h2 id="配件模式">配件模式</h2>
<p>有兴趣的可以在《4、AOA协议》中了解,此处不再过多描述。</p>
<h2 id="主机模式">主机模式</h2>
<p>主机模式表示Android设备作为主机为USB总线供电与外部设备进行通信。进行USB通信的大致流程如下:<br>
<img src="https://img2023.cnblogs.com/blog/835426/202305/835426-20230531105037810-1456202503.png" alt="" loading="lazy"></p>
<h3 id="1连接检测">1、连接检测</h3>
<p>这一步由电气工程师负责,软件开发人员无需关心(如果感兴趣可以看<code>圈圈教你玩USB1</code>:链接:https://pan.baidu.com/s/1dOen5533QWN5IMtGAO_zXw 提取码:wvs9 )。</p>
<p>以低速设备连接检测为例:<br>
<img src="https://img2023.cnblogs.com/blog/835426/202305/835426-20230531105129652-114529049.png" alt="" loading="lazy"></p>
<ol>
<li>USB主机端口在D+(正极)和D-(负极)上都有15K的下拉电阻,在没有设备连接时,在下拉电阻的作用下,使得两条数据线的电压都是近地的,也就是0V;</li>
<li>低速USB设备在D-上有1.5K的上拉电阻;</li>
<li>在设备连接后,此时主机上D-的下拉电阻和设备上D-的上拉电阻构成分压。此时在D-上会出现3V左右的电压,D+仍然是0V;</li>
<li>主机检测电压维持2ms,则认为当前有USB设备连接,并且是一个低速USB设备。</li>
</ol>
<h3 id="2枚举设备">2、枚举设备</h3>
<p>USB只是一个总线,只提供一个数据通路而已。USB总线驱动程序并不知道一个设备具体如何操作,有哪些行为。具体设备实现什么功能,由设备自己决定,USB主机需要通过描述符了解。这些描述符主要包括:</p>
<ul>
<li><strong>设备描述符</strong>:一个设备只有一个,固定18字节长度,指出设备设备使用的USB协议号、设备信息、厂商和产品ID(vid与pid)等;</li>
<li><strong>配置描述符</strong>:描述设备配置的集合,包含的接口数、配置编号、供电方式、电流需求量等,每个配置描述符中又定义了此配置里有多少接口;</li>
<li><strong>接口描述符</strong>:每个接口有一个接口描述符,接口描述符定义了该接口的编号、接口端点数、接口的类、子类、协议等,每个端点对应一个端点描述符;</li>
<li><strong>端点描述符</strong>:定义了端点大小,类型(读、写)等。</li>
</ul>
<p>枚举设备就是从设备获取各种描述符,选择不同的接口(功能),根据端点完成通信。</p>
<h4 id="描述符间的关系">描述符间的关系</h4>
<p>USB设备会分出一些端点,如端点0、端点1等,因此USB主机要和设备通信,光有设备地址还不够,还需要一个端点地址。有了设备地址和端点地址,就可以准确的对端点发送和读取数据。<br>
<img src="https://img2023.cnblogs.com/blog/835426/202305/835426-20230531110201383-205798486.png" alt="image" loading="lazy"></p>
<p>就好像我们在一台主机上运行多个Java程序,不同的Java程序就是不同的配置,而接口就是一个Java程序中实现个多个功能,包括文件操作、用户操作等功能。文件操作功能中开启多个Socket,其中Socket1接收文件数据,提供完成文件上传;Socket2发送文件数据,提供文件下载。我们需要通过主机IP地址与不同的Socket绑定的端口号去完成通信。</p>
<p><strong>枚举阶段就是从设备获取各种描述符,选择不同的接口(功能)</strong></p>
<h4 id="android-枚举设备">Android 枚举设备</h4>
<h5 id="21枚举设备">2.1、枚举设备</h5>
<p>在Android SDK中已经完成了枚举的封装。Android提供两种方式进行枚举:</p>
<h6 id="211通过intent过滤器">2.1.1、通过Intent过滤器</h6>
<pre><code class="language-xml">&lt;activity ...&gt;
        ...
        &lt;intent-filter&gt;
                &lt;action
                android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" /&gt;
        &lt;/intent-filter&gt;
        &lt;meta-data
                android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
                android:resource="@xml/device_filter" /&gt;
&lt;/activity&gt;
</code></pre>
<p>除了指定&nbsp;Intent 过滤器 之外,还需要指定一个资源文件来指定支持处理的USB设备的属性,在工程<code>res/xml</code>目录下创建 <code>device_filter.xml</code> 文件,文件内容如下:</p>
<pre><code class="language-xml">&lt;?xml version="1.0" encoding="utf-8"?&gt;
&lt;resources&gt;
        &lt;usb-device vendor-id="1234" product-id="5678" /&gt;
&lt;/resources&gt;
</code></pre>
<p>表示只对<code>vid:1234,pid:5678</code> 感兴趣,其他设备接入并不会调起我们的Activity。</p>
<blockquote>
<p>VID:供应商ID,由供应商向USB-IF(Implementers Forum,应用者论坛)申请;<br>
PID:产品ID,由供应商自行决定。不同的产品、相同产品的不同型号、相同型号的不同设计的产品采用不同的PID,以便区别相同厂家的不同设备。</p>
</blockquote>
<p>如果需要处理所有USB设备,则需要将内容修改为:</p>
<pre><code class="language-xml">&lt;?xml version="1.0" encoding="utf-8"?&gt;
&lt;resources&gt;
        &lt;usb-device /&gt;
&lt;/resources&gt;
</code></pre>
<p>配置完成后,在USB设备加入时,就能通过以下方式从 <code>Intent</code> 获取代表所连接设备的 <code>UsbDevice</code> :</p>
<pre><code class="language-java">@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    UsbDevice device = getIntent().getParcelableExtra(UsbManager.EXTRA_DEVICE);
}

@Override
protected void onNewIntent(Intent intent) {
    super.onNewIntent(intent);
    UsbDevice device = getIntent().getParcelableExtra(UsbManager.EXTRA_DEVICE);
}
</code></pre>
<h6 id="212主动枚举设备">2.1.2、主动枚举设备</h6>
<p>除了使用<code>Intent</code>过滤器在设备插入时接收连接设备的 <code>UsbDevice</code> 之外,还可以使用 <code>getDeviceList()</code> 方法获取连接的所有 <code>USB</code> 设备的哈希映射。</p>
<pre><code>UsbManager usbManager = (UsbManager) getSystemService(Context.USB_SERVICE);
...
Map&lt;String, UsbDevice&gt; deviceMap = usbManager.getDeviceList();
Collection&lt;UsbDevice&gt; values = deviceMap.values();
Iterator&lt;UsbDevice&gt; iterator = values.iterator();
while (iterator.hasNext()) {
    devices.add(iterator.next());
}
</code></pre>
<h5 id="22获得通信权限">2.2、获得通信权限</h5>
<p>如果应用使用 <code>Intent</code> 过滤器来发现已连接的 <code>USB</code> 设备,则它会在用户允许应用处理 <code>Intent</code> 时自动获得权限。否则,必须在应用中明确请求权限,然后才能连接到设备。因此在尝试与设备通信之前,必须先检查是否具有访问设备的权限。</p>
<p>如果还未具备设备访问权限则需要通过 <code>requestPermission()</code> 完成请求:</p>
<pre><code class="language-java">@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
       
    UsbReceiver usbReceiver = new UsbReceiver();
    IntentFilter filter = new IntentFilter(UsbReceiver.ACTION_PERMISSION);
    registerReceiver(usbReceiver, filter);
    if (!usbManager.hasPermission(usbDevice)) {
      //申请权限,系统弹出对话框,用户选择后发出广播,触发注册的广播接收者
      PendingIntent permissionIntent = PendingIntent.getBroadcast(this, 0,
                new Intent(UsbReceiver.ACTION_PERMISSION), 0);
      usbManager.requestPermission(usbDevice, permissionIntent);
    }
}

public class UsbReceiver extends BroadcastReceiver {
    public static final String ACTION_PERMISSION = "com.zxj.usb.USB_PERMISSION";

    @Override
    public void onReceive(Context context, Intent intent) {
      String action = intent.getAction();
      if (ACTION_PERMISSION.equals(action)) {
            UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
            if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
                Log.d("Lance", "获取权限 ");
            } else {
                Log.d("Lance", "没有权限 ");
            }
      }
    }
}
</code></pre>
<h4 id="根据描述符筛选设备">根据描述符筛选设备</h4>
<p>在获得通信权限进行通信之前,一般会根据USB设备的描述信息来判断程序是否支持当前设备的通信。<br>
如下示例为判断是否为大容量存储设备(U盘):<br>
https://www.usb.org/defined-class-codes</p>
<pre><code class="language-java">for (int i = 0; i &lt; usbDevice.getInterfaceCount(); i++) {
    UsbInterface usbInterface = usbDevice.getInterface(i);
   
    // 1、类为:USB_CLASS_MASS_STORAGE 0x08
    // 2、子类为:0x06 (大部分U盘使用)
    // 3、协议为:0x50 (Bulk-Only协议 批量传输)
    if (usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_MASS_STORAGE//大容量设备
            &amp;&amp; usbInterface.getInterfaceSubclass() == 0x06//U盘
            &amp;&amp; usbInterface.getInterfaceProtocol() == 0x50) {//通信协议采用Bulk-Only协议
      //每个存储设备一定有两个端点:in 和 out
      UsbEndpoint outEndpoint = null, inEndpoint = null;
      for (int j = 0; j &lt; usbInterface.getEndpointCount(); j++) {
            UsbEndpoint endpoint = usbInterface.getEndpoint(j);//获取到端点
            if (endpoint.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK) {
                if (endpoint.getDirection() == UsbConstants.USB_DIR_OUT) {
                  outEndpoint = endpoint;//写端点
                } else {
                  inEndpoint = endpoint;//读端点
                }
            }
      }
    }
}
</code></pre>
<h4 id="3数据传输">3、数据传输</h4>
<p>在完成设备枚举后,选定 <code>UsbDevice</code> 对象并获取权限后,接下来就可以与之进行数据交互。在USB通信过程中主要有四种数据传输方式:<code>Control Transaction</code>(控制传输)、<code>Interrupt Transaction</code>(中断传输)、<code>Bulk Transaction</code>(批量传输)和<code>Isochronous Transaction</code>(等时传输)。</p>
<ul>
<li><strong>控制传输</strong>:<br>
所有USB设备与主机必须支持的方式,特点是数据量小、正确性高,一般用于信息获取、命令控制与参数配置等。在获取设备描述符阶段采用此方式。</li>
<li><strong>中断传输</strong>:<br>
中断传输是一种保证查询频率的传输,主机会保证在小于这个时间间隔的范围内安排一次传输,常用于数据量不大,但是对时间要求严格的设备,比如键盘按一个键值,鼠标移动的位移量。</li>
<li><strong>批量传输</strong>:<br>
主要用于传输大量的,但是对时间无要求的数据,比如读取U盘的数据。</li>
<li><strong>等时传输</strong>:<br>
适用于数据量大且恒定的数据,比如USB摄像头。</li>
</ul>
<p><img src="https://img2023.cnblogs.com/blog/835426/202305/835426-20230531114457327-788104583.png" alt="image" loading="lazy"><br>
Android提供使用 批量传输<code>bulkTransfer()</code>与控制传输<code>controlTransfer()</code>在端点上传输的数据</p>
<blockquote>
<p>如果要完成中断与等时传输可以借助后面介绍的第三方C库:libusb。</p>
</blockquote>
<p>在进行传输之前,首先需要获取设备对应的<code>UsbDeviceConnection</code>对象</p>
<pre><code class="language-java">UsbDeviceConnection deviceConnection = usbManager.openDevice(usbDevice);
//锁定此UsbInterface (其实就是锁定端口,同时只能一处使用)
deviceConnection.claimInterface(usbInterface, true);

// 控制传输
byte[] maxLun = new byte;
deviceConnection.controlTransfer(requestType,request, value, index, bytes, bytes.length, TIMEOUT);
// 批量传输
deviceConnection.bulkTransfer(endpoint, bytes, bytes.length, TIMEOUT);
</code></pre>
<h4 id="4关闭通信">4、关闭通信</h4>
<p>当完成与设备的通信后,需要调用 <code>releaseInterface()</code> 和 <code>close()</code> 来关<br>
闭 <code>UsbInterface</code> 和 <code>UsbDeviceConnection</code> 。</p>
<pre><code class="language-java">//关闭
deviceConnection.releaseInterface(usbInterface);
deviceConnection.close();
</code></pre>
<p>而如果是设备断开连接,可以通过广播监听:</p>
<pre><code class="language-java">BroadcastReceiver usbReceiver = new BroadcastReceiver() {
    public void onReceive(Context context, Intent intent) {
      String action = intent.getAction();
      //某个USB设备断开连接
      if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(action)) {
            UsbDevice device =
                  intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
            if (device != null &amp;&amp; //当前通信设备) {
                deviceConnection.releaseInterface(usbInterface);
                deviceConnection.close();
      }
    }
}
</code></pre><br><br>
来源:https://www.cnblogs.com/zuojie/p/17445653.html
頁: [1]
查看完整版本: Android USB开发—USB通信