ILITEK系列触摸屏调试个人总结

项目场景:

需要在安卓系统屏幕上增加一块触摸屏,实现该触摸功能,本项目使用的是ILITEK251系列IC采集触摸屏的点位数据,通过LVDS串行解串芯片传输,基于芯驰X9系列SOC平台实现。

一、软硬件框架

框架示意图
SOC:该芯片是一款多核异构的SOC,R5核跑的是传统的轻量级OS,例如freeRTOS等轻量级系统,A核可以跑像安卓、linux等多任务系统,这种生态的好处是R5核可以满足实时性的要求,同时A核可以实现高处理能力以及多生态环境,多核之间的通信原理是基于寄存器和中断,在共享内存中实现跨系统的数据访问。目前主流的通信实现采用Virtio下的RPMsg。更详细的可以自行去官网查看理解。

串行解串器:用一对差分信号线实现短距离的数据串行传输。一般是成对匹配使用,芯片和SOC之间采用IIC的协议通信,串行器,解串器分别可以看成是两个设备,挂在一条IIC的总线上。串行解串芯片内部自带中断控制INT,还会集成其他外设,例如LVDS、I2S、通用可透传的GPIO、SPI等等。

ILITEK:该触摸IC是负责采集触摸屏的报点数据,然后触发中断,中断通过解串器,由LINK传回串行器,硬件上串行器INT连接SOC的GPIO,配置GPIO为外部中断触发告知SOC。本项目中,由SOC通过IIC配置串行器的寄存器,实现触摸IC在IIC总线上的挂载,然后在SOC通过IIC对该IC芯片的寄存器读写,既一条IIC总线上挂载三个设备,串行器、解串器以及触摸IC。

二、驱动移植

1、首先对设备树进行配置:

&i2c8 {
        status = "okay";
		ilitek:ilitek@41 {
		compatible = "tchip,ilitek";//设备和驱动根据compatible属性配对
		reg = <0x41>;				//设备的I2C地址
		ilitek,irq-gpio = <&port4d 12 0x0>;//外部中断号108
		ilitek,vbus = "vcc_i2c";	//一些设备信息的描述
		ilitek,vdd = "vdd";
		ilitek,name = "ilitek_i2c";

	};
};

在具体的dts文件中,引用&i2c8,然后status使能okay,证明开启该节点与驱动进行配对,如果为disabled则不启动配对

i2c8: i2c@30b00000 {
		/*compatible = "snps,designware-i2c";*/
		compatible = "sd,virtual-i2c";//使用虚拟i2c连接R5核
		phy-num = /bits/ 8 <8>;
		reg = <0x0 0x30b00000 0x0 0x1000>;
		interrupts = <0 40 4>;
		#address-cells = <1>;
		#size-cells = <0>;
		clocks=<&I2C_SEC0_9>;
		clock-names = "i2c-clk";
		#clock-frequency = <100000>;
		timeout = <10>; /* ms */
		status = "disabled";
	};

修改原厂i2c8的设备树描述信息,因为实际的i2c8总线控制已经挂到R5核上,所以我们这里不使用实际的IIC总线,而是注册一个虚拟的IIC总线控制模拟器,会匹配另外一个虚拟化的IIC驱动,由这个控制器传递IIC通信数据给到R5核上实际对应的IIC驱动,然后发往设备端.

2、移植驱动文件到指定的内核目录下,对于触摸屏驱动,linux内核下有专门的输入子系统,该路径为/drivers/input/touchscreen,,修改input目录下的makefile
注释: #obj-$(CONFIG_INPUT_TOUCHSCREEN) += touchscreen/
增加:obj-y += touchscreen/
我这里为了方便调试,直接使用obj-y来使能编译 touchscreen/下的文件,正常的话应该make menuconfig来配置CONFIG_INPUT_TOUCHSCREEN = y
然后在 touchscreen/创建一个文件夹ilitek,修改touchscreen/下的makefile
增加:obj-y += ilitek/ 使能编译该目录下的文件,然后将驱动文件放入该目录下,清除内核输出信息,重新编译即可

三、驱动代码部分解析

static int __init ilitek_touch_driver_init(void)
{
	tp_msg("add ILITEK touch device driver\n");
	return i2c_add_driver(&ilitek_touch_device_driver);  //加入I2C总线匹配
}

static void __exit ilitek_touch_driver_exit(void)
{
	tp_msg("remove touch device driver i2c driver.\n");

#ifdef ILITEK_SPI_INTERFACE
	spi_unregister_driver(&ilitek_touch_device_driver);
#else
	i2c_del_driver(&ilitek_touch_device_driver);
#endif
}
#endif

module_init(ilitek_touch_driver_init);		//平台模块注册入口函数
module_exit(ilitek_touch_driver_exit);
MODULE_AUTHOR("ILITEK");
MODULE_LICENSE("GPL");``


static struct i2c_driver ilitek_touch_device_driver = {
#endif
	.driver = {
		.name = ILITEK_TS_NAME,
		.owner = THIS_MODULE,
#ifdef CONFIG_OF
		.of_match_table = ilitek_touch_match_table,  //设备树的匹配表
#endif
	},

	.probe = ilitek_touch_driver_probe,								//如果驱动和设备配对成功则调用该函数
	.remove = ilitek_touch_driver_remove,

};

#ifdef CONFIG_OF
static struct of_device_id ilitek_touch_match_table[] = {
	{.compatible = "tchip,ilitek",},	//该信息要与设备树描述的compatible属性一致
	{},
};
#endif

static int ilitek_touch_driver_probe(struct i2c_client *client,
				     const struct i2c_device_id *id)
{
	if (!client) {
		tp_err("i2c client is NULL\n");
		return -ENODEV;
	}
	return ilitek_main_probe(client, &client->dev); //匹配成功以后执行主线函数
}

则选ilitek_main_probe函数中部分功能分析

int ilitek_main_probe(void *client, struct device *device)
{
	.......
	if (!(ts = kzalloc(sizeof(*ts), GFP_KERNEL))) {  //分配一块内存
		tp_err("allocate ts failed\n");
		return -ENOMEM;
	}

	ilitek_request_gpio();//注册一个外部的中断io

	ts->dev = ilitek_dev_init(interface_i2c, &dev_cb, ts);//对ts结构注册一个回调函数集
	if (!ts->dev)
		goto err_free_gpio;
	
		ts->process_and_report = ilitek_read_data_and_report_3XX; //中断下半部执行函数
		ts->irq_trigger_type = IRQF_TRIGGER_FALLING;			  //下降沿触发

	if (ilitek_request_irq())//注册中断,申请一个软件中断号
		goto err_dev_exit;

	......
	ilitek_create_sysfsnode(); //在/sys/下创建信息节点
	ilitek_create_tool_node();//在/proc/下创建信息节点
	ilitek_init_netlink();//创建一个netlink,用于应用和驱动之间的通信

	......
	return 0;

err_free_irq:
	free_irq(ts->irq, ts);

err_dev_exit:
	ilitek_dev_exit(ts->dev);

err_free_gpio:
	ilitek_free_gpio();
	kfree(ts);
	return -ENODEV;
}

看下中断函数的注册则选

static int ilitek_request_irq(void)
{
	int error;
	ts->irq = gpio_to_irq(ts->irq_gpio);//将GPIO号转换
	......
	tp_msg("ts->irq: %d\n", ts->irq);
	if (ts->irq <= 0)
		return -EINVAL;
	//方法一、中断注册函数,内核线程注册方法
	/*error = request_threaded_irq(ts->irq, NULL, ilitek_i2c_isr,
				     ts->irq_trigger_type | IRQF_ONESHOT,
				     "ilitek_touch_irq", ts);*/
				     
	//方法二、这是中断注册函数方法,配合工作队列
	error = request_irq(ts->irq, ilitek_i2c_isr, ts->irq_trigger_type, "ilitek_touch_irq", ts);	 
	if (error) {
		tp_err("request threaded irq failed, err: %d\n", error);
		return error;
	}
	//由于之前调试出点问题,怀疑过是不是方法一有问题,所以注册了一个工作队列代替
	//初始化一个工作队列
	INIT_WORK(&ts->my_workqueue,autofure_work);
	ts->irq_registerred = true;
	atomic_set(&ts->irq_enabled, 1);

	return 0;
}

看下中断下半部则选

int ilitek_read_data_and_report_3XX(void)
{
	......
	ts->buf[0] = 0x10;
	ret = ilitek_write_and_read(ts->buf, 1, 1, ts->buf, 32);//接收IIC过来的数据包
	if (ret < 0) {
		tp_err("get touch information err\n");
		if (ts->is_touched) {
			ilitek_touch_release_all_point();
			ilitek_check_key_release(x, y, 0);
		}
		return ret;
	}
		......
		//取出数据包中x y的坐标
		x = ((ts->buf[i * 5 + 1] & 0x3F) << 8) + ts->buf[i * 5 + 2];
		y = (ts->buf[i * 5 + 3] << 8) + ts->buf[i * 5 + 4];

		if (!(ts->touch_key_hold_press)) 
		{
			if (ILITEK_REVERT_X)
				x = ts->dev->screen_info.x_max - x + ts->dev->screen_info.x_min;
			if (ILITEK_REVERT_Y)
				y = ts->dev->screen_info.y_max - y + ts->dev->screen_info.y_min;

		//根据分辨率将x y坐标转换
		#ifdef ILITEK_USE_LCM_RESOLUTION  
			x = (x - ts->dev->screen_info.x_min) * TOUCH_SCREEN_X_MAX /
			(ts->dev->screen_info.x_max - ts->dev->screen_info.x_min);
			y = (y - ts->dev->screen_info.y_min) * TOUCH_SCREEN_Y_MAX /
			(ts->dev->screen_info.y_max - ts->dev->screen_info.y_min);
		#endif

			ts->is_touched = true;
			tp_msg("Touch id=%02X, x: %04d, y: %04d\n", i, x, y);
			ilitek_touch_down(i, x, y, 10, 128, 1);//触摸过程的上报
		}
	
	......
	input_sync(input); //每次上报应用都要sync一下
	return 0;
}

四、调试异常和解决

1、IIC通信不成功:
刚开始的硬件连接是soc-串行器-解串器-触摸屏,发现IIC只能访问串行器-解串器,却读取不到触摸屏的设备地址,然后屏幕和触摸IC都换了一个发现还是不行,后面尝试跳线SOC直连到触摸IC,发现读取成功,那问题就是串行解串转换出了问题,然后排查到解串器的SDA 、SCL是上拉到1.8V,然后通过电平转换到3.3V到触摸IC(触摸IC是3.3V的上拉),看似没问题,将电平转换芯片去掉然后解串器的SDA 、SCL上拉到3.3V,通信成功!!后面排查可能原因是电平转换过程中电平不稳定导致的通信失败。

2、IIC通信过程中,读写长字节数据会出现错误的异常:
SOC在发送和接收触摸IC的数据过程中,读写一些几个字节的数据通信都是正常,例如获取版本号、设置工作模式等等,但是当触发中断接收长字节数据时,后面十几个字节开始收到的数据都是不正常的,一开始觉得是触摸IC厂发过来的数据包有问题(战略上拉扯了很久…),然后实在没办法,我叫硬件给我改回来直连SOC试试(之前硬件工程师觉得都可以访问到设备就证明是没问题),然后我坚定要改回来试试,一改发现直连触摸IC一点问题都没有…还是配对芯片的问题,后面排查发现解串器的SDA、SCL是上拉来10K电阻,然后根据数据手册和他们原厂支持改成2.2K,完美通信成功。

3、有部分触摸区域没反应:
触摸过程中边缘的一块区域没反应,触摸也不会产生下降沿中断,然后他们IC技术支持一直说是SOC驱动的问题,我就说你中断信号都过不来,我这边只是做数据处理,后面使用他们提供的画线工具,接上USB到windows下画线,发现同样没有反应,最后是他们提供一版新的触摸IC固件,烧录后成功解决。

五、总结

驱动的调试过程中,都会遇到各种各样意想不到的问题,有时候要发散思维,从多个点切入,将某个疑难问题分部解析来看待,调试与硬件相关的问题都是很考验人的,所以遇到这些问题的时候要冷静多角度去考虑。

最后,在安卓系统中完成触摸的功能,验证来点击区域的正确性。

物联沃分享整理
物联沃-IOTWORD物联网 » ILITEK系列触摸屏调试个人总结

发表评论