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

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

- 理解controlTransfer()方法和UsbRequest类

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

分析和理解前面的 【USB-HID通讯例程的开发(1)】博文中的主活动源码的核心方法: monitorRead() 是我接下来要探讨的话题.
和Windows主机环境一样,HID设备在与安卓主机的通讯中同样遵循的原则是: 有求必应,不求不应. 即USB通讯中的HID设备是一种完全受控于安卓主机的仆从角色:  主机发出请求,设备一定要作出响应;主机未发出请求,设备就不能擅自发送任何数据.这一点与串口通讯是完全不同的.

本HID设备(即JGB01开发板)使用了两个非0端点(中断类型端点)来进行读写: EP2(IN), EP1(OUT) . 其中的IN或OUT都是站在安卓主机的角度来理解的。因此若站在HID设备一端来看: IN端点对于HID设备来说就是用于发送 ,OUT端点对于HID设备来说就是用于接收. 而 0端点是控制类型端点,主要是用于SETUP令牌包处理.

0端点(控制类型端点)是使用USB连接类的controlTransfer()来操作的.
非0端点(在本开发板中为中断类型端点)EP2(IN), EP1(OUT)使用UsbRequest类的三个方法: initialize(), queue() , requestWait() 来操作的.
因此UsbRequest有时候又叫异步读写方式,因为发送请求和接收响应是被明显地划分为两个时段来进行的。对于中断端点的操作只能用这个方法处理. 该类的三个方法分别完成:

  • initialize() 完成对指定的有方向的端点(为入参之一)进行初始化, 因此UsbRequest类的实例既可以用于读入(IN),也可以用于写出 (OUT), 这取决于入参的端点方向.
  • queue()进入消息队列, 排队等候处理.
  • requestWait()等待重叠IO操作的完成,阻塞直到读入或写出指定长度数据或出现超时。

本主活动的核心方法monitorRead()处理流程是:
每隔20MS轮询点火标记bAutoFireFlag -> 发送请求->接收响应 ->重复轮询点火标记bAutoFireFlag

点火标记: 在Java开发中经常把一个信号量的出现称之为点火(Fire),例如下面代码段展示: 【温控】按钮被按下后即表示【取温度值开始】的信号出现了。

case R.id.buttTempOn:
               
                //JGB01的字符串指令格式: 用于UsbRequest
                strCurrSend = "GetTemp:1" ;
                
                 //需重复发送此请求的次数
                nCurrSend = 1;
                
                //请求值: 为controlTransfer()的入参之一,与JGB01开发板的C代码实现有关,后面将会有介绍
                nCurrFlag = 30;
                
                //点火标记: 取温度值开始
                bAutoFireFlag =true ;
                
                // 在用户界面上显示字符串指令
                editSend.setText(strCurrSend);
                
                break;

因此要想从本HID设备(JGB01开发板)上取回数据, 依据发送请求和接收响应这两个分开进行的通讯流程,就有了如下三种不同的组合方式:

  • (1)纯controlTransfer()方式: 发送请求和接收响应只需调用一次控制传输方法ontrolTransfer()即可,返回的数据放在controlTransfer()的一个引用入参内。参与端点只有: EP0。
    在本主活动中有两个地方使用了这个方法取回HID设备的数据,读者可自行查找: 一是取回最大逻辑单元和非0端点的数据包长,二是取回所有端点的状态值。此种方法靠占用专用于处理SETUP事务的端点0来取回数据,较为另类,只适用于非频繁的且返回数据很短小的场合。

  • (2) 混合方式: controlTransfer()方法发送无返回值的请求, UsbRequest对象接收响应,参与端点有两个: EP0和EP2

  • (3)纯UsbRequest对象方式: 发送请求和接收响应都用同一个 UsbRequest对象,该对象所完成的IN 或 OUT事务不使用端点0,参与端点有两个: EP1和EP2

  • 例如 IN 或 OUT事务中的IN或OUT令牌包的接收者是SIE-串行接口引擎,这两个事务的握手包也由此SIE硬件体完成发送或接收。

第一种方法不适用于读写操作很频繁的场合,但取回的数据仍然是正确和可信的。
后两种方法得到的结果则是完全一样的,软件使用者感觉不到任何差异。就我个人而言比较喜欢用的是第三种,即纯UsbRequest对象方式。因为它不占用大多时候专用于SETUP令牌包处理的端点0,而且只需要维护一个UsbRequest对象即可,简单明了。

以下是核心方法monitorRead()中针对上述(2)和(3)两种方式的切换段落:使用了一个逻辑标记bControlMode来切换,由于这两种方法调用后得到的结果是完全一样的,因此在实际使用的场合中只需挑选其一即可。

//***********************这里先完成指令的发送 *************************
                    synchronized (this) {

                        //主机有两种方式来发送请求  : 实测二者效果完全一样
                        if (bControlMode)
                            //使用控制传输 controlTransfer()方法发送请求
                            sendCommand(nCurrFlag);
                         else
                            //使用异步传输(UsbRequest)方式发送请求
                            writeString(strCurrSend);

                    }  // synchronized (this) end

  

附录: ----本人对USB端点和其对应缓冲区的一些补充理解----

本质上来说: 端点其实就是一块访问带有方向的共享内存(ReadOnly 或WriteOnly). 这块内存有多大? 对于STM32来说是所有8个端点一起共享512字节(还应去掉位于头部的64字节总长度的不适宜占用的缓冲区描述表),因此如果一个端点的缓冲区分配多了,其它端点能分配的自然就少了. 好在多数设备一般也就用到两三个端点,每个端点几十字节,因此这块共享内存是够用的.

端点的类型 : 控制, 批量 , 同步, 中断. 0端点总是控制类型的, JGB01开发板的两个非0端点EP2和EP1都是中断类型的.

端点的状态 : W/R (读写) ,IDLE(空闲) , HALT(挂起).
例如主机发来的SET_FEATURE请求可设定挂起, 而CLEAR_FEATURE请求则清除挂起.

对端点的读写操作 其实就是对端点缓冲区的读写操作。 STM32的USB硬件引擎可以直接访问这512字节的数据且无需MCU的干预。而单片机的C代码对这512字节的数据访问都是按缓冲区描述表给定的地址指针来访问的。

因此端点缓冲区描述表的重要性是不言而喻的。
每个端点都占用缓冲区描述表8个字节(16个地址单元),它分四个字段顺序排列: 发送缓冲地址/发送数据长度/接收缓冲地址/接收数据长度
缓冲地址的真实数值是一个相对于**#define PMAAddr (0x40006000L)**的地址偏移值,而其中的 PMAAddr叫做缓冲区基址。

每一个端点的描述表共占据8个字节, 但占据16个地址偏移.因此512字节的缓冲区共占据1024个地址单元。
即PMAAddr缓冲区包括缓冲区描述表的数据放置格式类似如下: AB00 CD00 EF00 GH00 …, 其中A,B,C,D,E,F,G,H均是有效的单个字节数据, 0是无用的单个填充字节数据,
也就是说: 每四个字节中的低二个字节是有效的字节数据,高二个字节填充为0(不用),因此每二个字节就占据了四个地址存储单元。

利用下面的代码可在JGB01开发板的串口上打印出此全部512字节的缓冲区数据:

//在UA3串口输出USB读写缓冲区的前1024个地址单元的以8个字节为一行的十六进制数据 :    

                 #define PMAAddr (0x40006000);
                ......
                 u16 iv=0;
                 char ch[64]={0};
     	         for(iv = 0; iv <= (1024-8);iv+=8){
		            memset(ch,0,sizeof(ch)) ;
		            sprintf(ch," %02X %02X %02X %02X %02X %02X %02X %02X\n",
		                    *(u8*)(PMAAddr+iv),
		                    *(u8*)(PMAAddr+1+iv),
	                    	*(u8*)(PMAAddr+2+iv),
		                    *(u8*)(PMAAddr+3+iv),
		                    *(u8*)(PMAAddr+4+iv),
		                    *(u8*)(PMAAddr+5+iv),
		                    *(u8*)(PMAAddr+6+iv),
		                    *(u8*)(PMAAddr+7+iv)); 
		            printf("%s",ch);
		         }
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

weixin_42038778

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

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

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

打赏作者

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

抵扣说明:

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

余额充值