针对越来越多的商显项目,由于有触摸的交互需求,那么触控方案选择也就比较重要。目前市面流行的主要
有红外框的,电容屏的。单系统(安卓 or OPS)的又分为串口,usb口。双系统(安卓 + OPS) 则有“串口+USB”,“双USB” 方案。如下:
其中图一,是单系统连接,比较简单,打开设备(/dev/ttySx , /dev/hidarwx ,其中<x = 0,1,2,3 … >),就可以获取到触摸数据的信息。
图二,图三,是双系统方案,一个相同点是,连接OPS 的都是USB 接口,传输过来的都是标准的HID事件,OPS 就可以直接使用,另外触摸板连接安卓主板的通道都是双向的,包含触摸数据(touch panel -> Android),指令控制数据(Android -> touch panel),到OPS端都是虚线,这个是针对通道不在OPS时,触摸数据是不会传送到OPS的,只有在OPS通道时才是通的。
另外针对,不同类型的触摸数据,处理方式不一样。标准的HID事件 OPS 可以直接支持,Android在kernel开启
+CONFIG_INPUT_TOUCHSCREEN=y
+CONFIG_TOUCHSCREEN_PROPERTIES=y
+CONFIG_HID_MULTITOUCH=y
宏之后可以支持。串口的触摸数据则需要将坐标点的属性解析出来,
虚拟成input 事件写入Android的/dev/uinput 设备达到效果,当然也可以封装成标准的HID事件向Android 的usb 口传输出去控制其他外设。
关于串口触摸数据的解析,转发可以参考:https://blog.youkuaiyun.com/kehyuanyu/article/details/97892667
下面我们分享图三的触摸方案。
触摸流程
如上图,touch panel连接android usb1 有两个intf分别对应两个设备节点(/dev/hidraw(N) 和/dev/hidraw(N+1)),两个设备对应的是一个传输触摸数据(/dev/hidraw(N+1)),一个发送控制指令(/dev/hidraw(N))。 总体流程就是: 触摸后,数据从touch panel 两个usb 接口流出,/dev/hidraw(N+1)中的HID事件直接到了安卓,而OPS能否收到事件取决于/dev/hidraw(N) 节点给的控制指令,在这里需要说明一下,默认情况下触摸框是直传模式,usb1 和 usb2都有数据的且是一样的, 切换到touch panel 为透传模式时,ops 收到的数据就是从/dev/hidraw(N) 转发过去的数据。如下:
设备ID获取
Android设备,使用自带工具lsusb 在硬件联通成功条件下既可获取到vid,pid信息
在OPS 端,通过bus hound 或者 usb tree,或者设备管理器都可以获取到vid,pid信息
连接Android 和 OPS 的两个usb 对应的vid,pid 分别是0x28e1,0xb100;0x2757, 0x0107
HID触摸协议
通过bus hound 工具,抓取usb2 端口的标准数模数据格式。每包64 bytes:
byte[0] 为包头,也即reportID 默认为0x02;
byte[1~10] 10个字节存储一个点的属性
byte[1] 点的状态,值一般为:0x04( up event), 0x07( duwn/move event)
byte[2] 为点的ID,第一个为0x00,后面依次类推
byte[3],byte[4] 为点x 坐标的地位和高位, 也即 (byte[3] & 0xFF) | (byte[4] << 8 & 0xFF00)
byte[5],byte[6] 为点y 坐标的地位和高位, 也即 (byte[5] & 0xFF) | (byte[6] << 8 & 0xFF00)
byte[7],byte[8] 为点x 宽度的地位和高位, 也即 (byte[7] & 0xFF) | (byte[8] << 8 & 0xFF00)
byte[9],byte[10] 为点y 宽度的地位和高位, 也即 (byte[9] & 0xFF) | (byte[10] << 8 & 0xFF00)
byte[11 ~ 20] 为第二点的属性,没有时填充0x00
byte[21 ~ 30] 为第三点的属性,没有时填充0x00
byte[31 ~ 40] 为第四点的属性,没有时填充0x00
byte[41 ~ 50] 为第五点的属性,没有时填充0x00
byte[51 ~ 60] 为第六点的属性,没有时填充0x00
byte[61~62] 为一般为校验码,具体有触摸框供应商提供;
byte[63] 为总共点的个数。
了解了协议我们就可以操控设备了
1. 打开设备
BOOL MSrv_DualUsbTouch::openDualUsbDev()
{
int nRet = -1;
int nIndex = 0;
int nCount = 0;
int nHidDevHandle = -1;
char devPath[HID_PATH_SIZE];
struct hidraw_devinfo info;
BOOL bFindWriteHandle = FALSE;
BOOL bFindReadHandle = FALSE;
for(nIndex = 0; nIndex < HID_PATH_COUNT; nIndex++)
{
memset(devPath,0x00,sizeof(devPath));
memset(&info,0x00,sizeof(info));
snprintf(devPath, sizeof(devPath), "%s%d", HID_DEV_PATH, nIndex);
nHidDevHandle = open(devPath , O_RDWR);
if(nHidDevHandle < 0)
{
dbg_info("open %s failed.", devPath);
close(nHidDevHandle);
continue;
}
else
{
dbg_warn("open %s succuss, handle : 0x%x.\n", devPath,nHidDevHandle);
}
nRet = ioctl(nHidDevHandle, HIDIOCGRAWINFO , &info);
if(nRet < 0)
{
dbg_err("get hid dev [%s] info failed, nRet = %d.",devPath,nRet);
close(nHidDevHandle);
continue;
}
if(m_HidDev_Vendor_id == info.vendor && m_HidDev_Product_id == (info.product & 0xFFFF) && (nCount < 2))
{
if(!bFindWriteHandle)
{
m_HidDevWriteHandle = nHidDevHandle;
dbg_warn("find the hid dev [%s] ==> vid&pid -> [%x:%x], write-handle = 0x%x.", devPath, info.vendor , \
info.product&0xFFFF,nHidDevHandle);
bFindWriteHandle = TRUE;
continue;
}
if((bFindWriteHandle) && (!bFindReadHandle))
{
m_HidDevReadHandle = nHidDevHandle;
dbg_warn("find the hid dev [%s] ==> vid&pid -> [%x:%x], read-handle = 0x%x .", devPath, info.vendor , \
info.product&0xFFFF,nHidDevHandle);
bFindReadHandle = TRUE;
break;
}
}
}
if(bFindWriteHandle && bFindReadHandle && (m_HidDevReadHandle > 0) && (bFindWriteHandle > 0))
{
return TRUE;
}
else
{
dbg_err("can`t find the hid dev [vid&pid -> %x:%x], read-handle = 0x%x , write-handle = 0x%x .", \
m_HidDev_Vendor_id , m_HidDev_Product_id , m_HidDevReadHandle,m_HidDevWriteHandle);
return FALSE;
}
}
遍历/dev/hidrawX 设备节点,读取设备属性HIDIOCGRAWINFO,匹配m_HidDev_Vendor_id, m_HidDev_Product_id ,这里也即是0x28e1,0xb100。 打开后可以拿到句柄。
2. 配置usb模式
BOOL MSrv_DualUsbTouch::setupForwardMode()
{
int nRet = 0;
int nLoop = 0;
if(m_HidDevWriteHandle <= 0 || m_HidDevReadHandle <= 0)
{
dbg_err("hid dev was non inited, continue to init !!!\n");
goto reopen;
}
setup:
if(m_HidDevWriteHandle > 0)
{
memset(s_CmdBuf,0x00,sizeof(s_CmdBuf));
s_CmdBuf[0] = 0x06;
s_CmdBuf[1] = 0xA8;
s_CmdBuf[2] = 0x01;
s_CmdBuf[3] = 0x00;
write_lock();
nRet = write(m_HidDevWriteHandle,s_CmdBuf,BUFF_MAX);
write_unlock();
}
if(nRet > 0)
{
dbg_info("setup forward mode succuss, nRet = %d. handle = 0x%x. \n",nRet,m_HidDevWriteHandle);
m_current_DataMode = EM_FORWARD;
return TRUE;
}
else
{
dbg_err("write %d to slave usb, handle = 0x%x. \n",nRet,m_HidDevWriteHandle);
if(nLoop > 5)
{
return FALSE;
}
nLoop++;
usleep(nLoop*500*1000);
goto reopen;
}
reopen:
cleanUpTheDev();
openDualUsbDev();
goto setup;
}
配置转发模式,指令为“0x06 0xA8 0x01 0x00,后面加60 个字节的0x00” 。此处是往/dev/hidrawN 里面写入指令切换触摸框的工作模式为转发模式。此处加了一些reopen 的处理,主要是针对硬件不稳定,导致hidrawN 设备文件经常发生变化的问题。如果需要切换为直发模式(usb1,usb2都收到触摸框同样的HID数据)
只需要将s_CmdBuf[2] = 0x01改成s_CmdBuf[2] = 0x00即可。
3. 获取HID数据
int MSrv_DualUsbTouch::recvTouchData(uint8_t* buf, uint8_t nLen)
{
int nRet = 0;
if (NULL == buf || nLen <= 0)
{
dbg_err("param error! \n");
return -1;
}
nRet = read(m_HidDevReadHandle, buf, nLen);
return nRet;
}
m_HidDevReadHandle 为设备/dev/hidraw(N+1) 文件句柄,源源不断获取usb1 接口出来的以0x02 开头的触摸数据。
4. 发送数据
BOOL MSrv_DualUsbTouch::packagePassthroughData(uint8_t * inBuf, uint8_t* outBuf, uint8_t nLen)
{
int nIndex = 0;
if(inBuf == NULL || outBuf == NULL || nLen < 0)
{
return FALSE;
}
memset(outBuf,0x00,sizeof(outBuf));
for(nIndex = 0; nIndex < BUFF_MAX - 2; nIndex++)
{
outBuf[nIndex + 2] = inBuf[nIndex];
}
outBuf[0] = 0x06;
outBuf[1] = 0x18;
return TRUE;
}
在转发模式,将usb1读取的62 bytes有效数据,拼接0x06,0x18 ,往/dev/hidraw(N) 发送,touch panel 收到后,组装成标准HID信息发给usb2的OPS设备。
int MSrv_DualUsbTouch::forwardTouchDataToSalve(uint8_t* inBuf, uint8_t nLen)
{
int nRet = -1;
uint8_t TmpBuf[BUFF_MAX] = {0};
if(isOlation(inBuf, nLen))
{
return -2;
}
packagePassthroughData(inBuf, TmpBuf, nLen);
printHex(BUFF_MAX/4,TmpBuf);
nRet = passthrough(TmpBuf,nLen);
return nRet;
}
int MSrv_DualUsbTouch::passthrough(uint8_t* buff, uint8_t nSize)
{
int nRet = -1;
write_lock();
nRet = write(m_HidDevWriteHandle,buff,nSize);
write_unlock();
dbg_info("write %d bytes to usb2, handle: 0x%x. \n", nRet, m_HidDevWriteHandle);
return nRet;
}
封装后,forwardTouchDataToSalve 完成发送。
5.转发测试
Android端,数据的收,转发。
上图为,ops 端收到的转发过来的消息。
6. 区域触控
由于屏幕只有一个,一些全局图标,不管在哪个通道下都需要去响应,但是全局图标下的应用不需要响应。这个场景下,全局图标这个区域就只能在usb1 下响应,USB2 下不响应。
BOOL MSrv_DualUsbTouch::isOlation(uint8_t* inBuf, uint8_t nLen)
{
BOOL bRet = FALSE;
uint8_t nIndex = 0;
uint32_t uPos_x = ((inBuf[4] << 8) & 0xFF00 ) | (inBuf[3] & 0xFF);
uint32_t uPos_y = ((inBuf[6] << 8) & 0xFF00 ) | (inBuf[5] & 0xFF);
for(nIndex = 0; nIndex < sizeof(sLimitedArea)/sizeof(sLimitedArea[0]); nIndex++)
{
if(!sLimitedArea[nIndex].bUsed)
{
continue;
}
if(uPos_x >= sLimitedArea[nIndex].m_limitedArea.x && uPos_x <= sLimitedArea[nIndex].m_limitedArea.width &&\
uPos_y >= sLimitedArea[nIndex].m_limitedArea.y && uPos_y <= sLimitedArea[nIndex].m_limitedArea.height)
{
bRet = TRUE;
break;
}
}
return bRet;
}
转发数据前,比较坐标点是否在隔离区域(sLimitedArea)内,在则不转发。从而达到这种效果。
坐标系
协议描述:
hid 编程参考:
https://www.cnblogs.com/arnoldlu/p/11418391.html
http://libusb.sourceforge.net/api-1.0/api.html
固定 hid 设备节点,可参考博文:
https://blog.youkuaiyun.com/buding_code/article/details/55046648?utm_source=blogxgwz1