安卓设备的USB-HID通讯例程的开发 (1)

安卓设备的USB-HID通讯例程的开发(1)

本博文系JGB联合商务组的原创作品,引用请标明出处

此通讯例程实际名称为【USB-HID调试器】,是在Android Studio 4.1环境下开发的安卓主机与一台HID设备的通讯例子。
按惯例,先上调试场景图,不感兴趣的直接忽略之。
整个外部的HID设备连线非常简洁,成本又十分低廉,但其中的技术含量却不小,很适合于搞毕设的同学拿来参考。

本HID设备(JGB01开发板)使用的芯片仍然是STM32F103C8T6,所烧录的C代码部分将在下一篇博文介绍。
在这里插入图片描述
上图是HID设备刚被打开时的情形,它显示: 此HID设备使用了两个非0端点,EP2(IN) 和EP1(OUT),加上0端点共有三个逻辑单元。非0端点的最大数据包长度为28字节。
下图显示了在【取设备状态】的操作完成后所返回的当前设备状态参数,
其中:

  • TX-LAST: 表示HID设备最后发送字节数和耗时
  • TEMP: HID设备刚上电时的第一次测量温度值
  • CUID: HID设备所使用的STM32芯片的唯一且永远不变的标识
  • SIZE: Flash的容量
  • VERS: 固件开发所用的函数库版本号

在这里插入图片描述

依次按动界面中间的: 点亮-熄灭-温控-停止 四个按钮后,安卓设备(作为主机)的屏幕上分别显示了由JGB01开发板回传的数据。
本调试器可运行在手机,机顶盒,安卓电视等设备上,亲测在如下四个版本的安卓设备上运行均稳定可靠,按钮的动作响应也非常的及时流畅:

  • 4.2版本的安卓竖屏电视
  • 4.4版本的机顶盒
  • 8.0版本的手机
  • 9.1版本的手机

在这里插入图片描述

本调试器所用的软件和硬件结构均继承了我的另一篇博文(以下简称为前文):

<安卓设备通过USB串口与STM32单片机通讯之一>

因此本文的许多需要说明但又与前文有重复的地方就不再叙述了。同样,本人水平有限错误自然难免,希见谅。但有一点可确认:后面所涉及的JAVA和C源码均在相应的开发工具中是编译通过了的。

USB主机与HID设备的通讯是目前主流的通讯方式之一。

当我开始创建这个Android Studio 4.1的USB通讯项目时我就已默默地把前文多次重点提及的JGB01开发板上的串口桥接器移除了,我也不想再去用什么第三方的开源驱动了,把它弃用了吧。我要开发的是USB-HID设备的通讯,硬件端口用最小板上提供的microUSB插座,软件APP只需引入安卓的USB库就足够了,它们是:

import android.hardware.usb.UsbConstants;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbInterface;
import android.hardware.usb.UsbEndpoint;
import android.hardware.usb.UsbManager;
import android.hardware.usb.UsbRequest;

android.hardware.usb看上去真的是一个含意明确,结构又很分明的包。它清晰地指出了HID设备的三大接收者的层次关系: 设备 - 接口 - 端点

本系统APP界面设计同样继承自前文的【USB串口调试器】,只是增加了一个【清空】按钮,另外改了最前面两个标签:芯片类改为设备类,波特率改为操作符 。

增加的【清空】按钮位于【发送】按钮之后:

<Button
            android:layout_marginLeft="4dp"
            android:layout_marginTop="2dp"
            android:layout_marginBottom="2dp"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:gravity="center"
            android:text="清空"
            android:id="@+id/buttClear" />

主活动依然只有一个,很方便你的理解,把本博原创的核心代码贴上相信也是你最为关心的,代码中的许多注释和段落也与前文完全相同,只是增删了一些读写操作的代码块而已,其它的如枚举设备,打开关闭设备,运行期授权,通讯指令定义等则完全一样。

package com.example.jgbusbhid;


import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;

import android.annotation.SuppressLint;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.hardware.usb.UsbConstants;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbInterface;
import android.hardware.usb.UsbEndpoint;
import android.hardware.usb.UsbManager;
import android.hardware.usb.UsbRequest;
import android.os.Bundle;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Spinner;
import android.widget.TextView;

import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;


//需要实现按钮动作接口类: View.OnClickListener 和 AdapterView.OnItemSelectedListener
public class MainActivity extends AppCompatActivity implements View.OnClickListener,
        View.OnFocusChangeListener,
        AdapterView.OnItemSelectedListener {
   


    private EditText editRead = null;
    private EditText editSend = null;
    private TextView tvRead = null;
    private TextView tvAuthor = null;

    private Button buttSend = null;

    private Button buttClear = null;

    private Button buttOpen = null;
    private Button buttClose = null;

    private Button buttLedxOn = null;
    private Button buttLedxOff = null;
    private Button buttTempOn = null;
    private Button buttTempOff = null;

    private Spinner spChip = null;
    private Spinner spBaud = null;




    private enum UsbPermission {
    Unknown, Requested, Granted, Denied };

    private UsbManager usbManager = null;

    //针对某一个特定的USB PortA要用到的几个对象组合
    private UsbDevice  deviceA = null;
    private UsbDeviceConnection connectionA = null;
    private UsbPermission usbPermissionA = UsbPermission.Unknown;

    private static final String INTENT_ACTION_GRANT_USB =  "MY_GRANT_USB";

    //接收USB设备插入后所发送消息的广播接收器
    private BroadcastReceiver broadcastReceiver;
    private PendingIntent penIntent = null;


    private List<String> chipList = new ArrayList<>();
    private List<String> baudList = new ArrayList<>();

    private ArrayAdapter<String> adapterChip ;
    private ArrayAdapter<String> adapterBaud ;


    //标题栏
    private ActionBar actionBar = null;

    //行缓冲区,每一行最多1024字节,超过将被清空
    private byte[] lineBuff ;

    //此行的真实长度
    private int nLineActLen =0 ;


    //[收到数据]editRead这个文本框的当前内容行数
    private int nContentRows = 0;

    //[收到数据]editRead这个文本框能显示的行数
    private int nPageRows =1 ;

    //[收到数据]editRead这个文本框的页面计数器
    private int nPage =1 ;


    private UsbInterface interfaceA  =null;
    private UsbEndpoint epOut =null;
    private UsbEndpoint epIn=null;

    //最大包长度 : 28字节     (设为 32 OK, 34 Not)
    private final int MAXPacketSize = 28 ;
    private byte[]  writeBuff ;
    private byte[]  readBuff ;

    private boolean bRefreshRead =false ;
    private boolean bRefreshExit =false ;


    //为真时退出监视Usb读取的线程
    private boolean bUsbReadExit =false ;
    //读线程正在运行的标记
    private boolean bUsbReadRunning = false ;
    //读线程锁块
    private final String strUsbReadRunning = "LOCKREAD" ;

    //读线程中要使用的USB请求类
    private UsbRequest okUsbReq = null ;


    //写未完成
    private boolean bUsbWriteOK =false ;


    /*
     * Configuration Request Types
     */


    // 还有另一种HID配置格式: 对于requestType, 分别是0x21(输出) 和 0xA1(输入),
    //它表示该令牌为HID类请求(bit6 bit5=0 1)而且接口为其接收者(低5位为00001)
  
     //0010 0001  最高位bit7为0的方向(输出--主机到设备),  低5位00001表示请求对象的接收者为接口
     // bit6-bit5:   请求的类型---   0表示标准请求,  1表示类请求,  2表示供应商提供的厂商请求, 3保留
     private final int JGB_HOST_TO_DEVICEA = 0x21;

     //1010 0001  最高位为1的方向(输入--设备到主机) , 低5位00001表示请求对象的接收者为接口
     // bit6-bit5:   请求的类型---   0表示标准请求,  1表示类请求,  2表示供应商提供的厂商请求, 3保留
     private final int JGB_DEVICE_TO_HOSTA = 0xA1;


    //另外: JGB01开发板还可用的厂商请求定义为:
    //0100 0001  最高位为0的方向(输出--主机到设备),  低5位00001表示请求对象的接收者为接口
    //它表示该控制传输为厂商请求(bit6 bit5=1 0),  而且接口为其接收者(低5位为00001)
    private final int JGB_HOST_TO_DEVICEB = 0x41;

    //1100 0001  最高位为1的方向(输入--设备到主机) , 低5位00001表示请求对象的接收者为接口
    //它表示该控制传输为厂商请求(bit6 bit5=1 0),  而且接口为其接收者(低5位为00001)
    private final int JGB_DEVICE_TO_HOSTB = 0xc1;

    //JGB01开发板请求码
    private final int JGB_STATUS_REQUEST_CODE =0xfe ;

    //JGB01开发板只有一个接口0
    private final int JGB_PORT_INDEX =0 ;

    //为真表示用控制传输模式发送请求,为假表示用UsbRequest异步模式发送请求(此为默认模式)
    private boolean bControlMode = false ;

    //当前的命令字
    private String strCurrSend ="" ;

    //命令字需要重复发送的次数
    private int nCurrSend = 0 ;

    //命令字的ID
    private int nCurrFlag = 0;

    //为真则表示每间隔5秒就自动传输一次
    private boolean bAutoFireFlag = false ;

    //对JGB01开发板的控制传输操作后的返回值存放点(最多MAXPacketSize字节)
    private byte[] jgbBuffer;
    private boolean bRefreshJGB = false ;
    private int nJGBLength =0;

    @Override
    protected void onStart() {
   
        super.onStart();

    }


    @Override
    protected void onResume() {
   
        super.onResume();

        //开始时的输入焦点
        spChip.setFocusable(true);
        spChip.setFocusableInTouchMode(true);

    }



    @Override
    protected void onDestroy() {
   
        super.onDestroy();

        //反注册广播接收器
        unregisterReceiver(broadcastReceiver);

        //需关闭端口并且退出读线程
        closeUsbPortA();


        //退出界面定时更新USB读取到的数据之线程
        bRefreshExit =true ;

        //退出监视Usb读取的线程
        bUsbReadExit =true ;


    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
   
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);


        editRead = (EditText) findViewById(R.id.editRead);
        editSend = (EditText) findViewById(R.id.editSend);

        tvRead   =   (TextView) findViewById(R.id.tvRead);
        tvAuthor =   (TextView) findViewById(R.id.tvAuthor);

        buttSend = (Button) findViewById(R.id.buttSend);
        buttClear = (Button) findViewById(R.id.buttClear);
        buttOpen = (Button) findViewById(R.id.buttOpen);
        buttClose = (Button) findViewById(R.id.buttClose);


        buttLedxOn = (Button) findViewById(R.id.buttLedxOn);
        buttLedxOff = (Button) findViewById(R.id.buttLedxOff);
        buttTempOn = (Button) findViewById(R.id.buttTempOn);
        buttTempOff =(Button) findViewById(R.id.buttTempOff);

        spChip = (Spinner) findViewById(R.id.spChip);
        spBaud = (Spinner) findViewById(R.id.spBaud);


        //注册按键动作监听器
        //入参为: View.OnClickListener 的接口实例,即 this
        buttSend.setOnClickListener(this);
        buttClear.setOnClickListener(this);
        buttOpen.setOnClickListener(this);
        buttClose.setOnClickListener(this);
        buttLedxOn.setOnClickListener(this);
        buttLedxOff.setOnClickListener(this);
        buttTempOn.setOnClickListener(this);
        buttTempOff.setOnClickListener(this);

        //取得或失去焦点时的监听
        editSend.setOnFocusChangeListener(this);
        spChip.setOnFocusChangeListener(this);
        spBaud.setOnFocusChangeListener(this);
        buttTempOff.setOnFocusChangeListener(this);

        //不能弹出输入键盘
        editRead.setKeyListener(null);
        //不能取得输入焦点
        editRead.setFocusable(false);
        editRead.setFocusableInTouchMode(false);
        buttClose.setEnabled(false);

        //修改标题栏文字内容
        actionBar=getSupportActionBar();
        if(actionBar != null){
   
            actionBar.setTitle("USB-HID调试器");
        }

        //设备选择
        //创建一个数组适配器
        adapterChip = new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, chipList);
        //设置下拉列表框的下拉选项样式
        adapterChip.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
        spChip = (Spinner)findViewById(R.id.spChip);
        spChip.setAdapter(adapterChip);


         //行缓冲区,每一行最多1024字节,超过将被忽略
         lineBuff = new byte[1024];
         Arrays.fill(lineBuff, (byte) 0);
         //此行的真实长度
         nLineActLen =0 ;


         //最大包长度MAXPackageSize
         readBuff =new byte[MAXPacketSize];
         writeBuff =new byte[MAXPacketSize];

         //对JGB01开发板的控制传输操作后的返回值存放点(最多MAXPacketSize字节)
         jgbBuffer=new byte[MAXPacketSize];
         bRefreshJGB = false ;


         //清空退出USB读取线程的标记
         bUsbReadExit =false ;

         bRefreshRead =false ;
         bRefreshExit =false ;
         //strRefreshALL ="";

         //开启定时界面更新线程
         refreshReadData();


        //加入几种操作符
        baudList.clear();
        baudList.add("Select-下拉选择");
        baudList.add("SetLedx:1-点亮");
        baudList.add("SetLedx:0-熄灭");
        baudList.add("GetTemp:1-温控");
        baudList.add("GetTemp:0-停止");
        baudList.add("GetStatus:1-取设备状态");
        baudList.add("GetEPStatus:1-取所有端点状态");
        baudList.add("ClrInfo:1-清空显示并切换到控制传输");

        //创建一个数组适配器
        adapterBaud = new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, baudList);
        //设置下拉列表框的下拉选项样式
        adapterBaud.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
        spBaud = (Spinner)findViewById(R.id.spBaud);
        spBaud.setAdapter(adapterBaud);


        //注册监听器
        spChip.setOnItemSelectedListener(this);

        //注册监听器
        spBaud.setOnItemSelectedListener(
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

weixin_42038778

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值