一线触摸屏协议驱动 3.1、一线触摸屏协议的基本内容 一线触摸屏首先定义一个软定时器,该定时器每隔25ms执行一次,在该定时器的中断函数中,会判断此次是什么类型的数据传输,包括初始化一线触摸屏、控制 背光命令或者读取触摸屏位置信息命令。命令设置成功后,进入测量函数,在测量函数中,打开硬件定时器,使用位速率为9600和一线触摸屏通信,最后取得有 效数据后,如果是测量位置信息,通过input子系统将其报告给应用层,如果是控制背光,则什么也不做。 3.2、一线触摸屏系统初始化和退出函数
- #if defined(CONFIG_GSC3280_1WIRE_TS)
- static struct platform_device ts_1wire_device = {
- .name = "ts_1wire_device",
- .id = -1,
- };
- #endif
- static struct platform_driver ts_1wire_device_driver = {
- .probe = ts_1wire_probe,
- .remove = __devexit_p(ts_1wire_remove),
- .driver = {
- .name = "ts_1wire_device",
- .owner = THIS_MODULE,
- }
- };
- static int __init ts_1wire_init(void)
- {
- return platform_driver_register(&ts_1wire_device_driver);
- }
- static void __exit ts_1wire_exit(void)
- {
- platform_driver_unregister(&ts_1wire_device_driver);
- }
- module_init(ts_1wire_init);
- module_exit(ts_1wire_exit);
- MODULE_AUTHOR("Davied");
- MODULE_DESCRIPTION("GSC3280 one wire ts Driver");
- MODULE_LICENSE("GPL");
- MODULE_ALIAS("gsc3280 one wire ts")
说明: 1) 将一线触摸屏定义为平台设备,因为它不操作任何CPU内部资源,所以平台设备的定义中只有名称和id。 2) 使用平台设备的注册和注销函数对其进行操作。 接下来讲述平台设备的探测函数ts_1wire_probe(),程序如下:
- static int ts_1wire_probe(struct platform_device *pdev)
- {
- int ret = 0;
- struct ts_1wire_t *ts = NULL;
- struct input_dev *input = NULL;
- DBG("############\n");
- printk(KERN_INFO "ts 1wire probe start.\n");
- ts = kzalloc(sizeof(struct ts_1wire_t), GFP_KERNEL);
- if (!ts) {
- DBG("kzalloc error\n");
- return -ENOMEM;
- }
- input = input_allocate_device();
- if (!input) {
- ret = -ENOMEM;
- goto err_free_mem;
- }
- spin_lock_init(&ts->slock);
- ts->dev = &pdev->dev;
- snprintf(ts->phys, sizeof(ts->phys), "%s/input0", dev_name(ts->dev));
- input->name = "h3600_ts";
- input->phys = ts->phys;
- input->dev.parent = ts->dev;
- input->id.vendor = 0x00; //tsdev->vendor;
- input->id.version = 0x00; //tsdev->rev;
- input->id.product = 0x03; //tsdev->rev;
- input->id.bustype = BUS_HOST; //should be spi
- input->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
- input->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
- ts->input = input;
- input_set_abs_params(ts->input, ABS_X, GSC_X_MIN, GSC_X_MAX, GSC_X_FUZZ, 0);
- input_set_abs_params(ts->input, ABS_Y, GSC_Y_MIN, GSC_Y_MAX, GSC_Y_FUZZ, 0);
- input_set_abs_params(ts->input, ABS_PRESSURE, GSC_PRESSURE_MIN, GSC_PRESSURE_MAX, 0, 0);
- ret = input_register_device(ts->input);
- if (ret) {
- DBG("!!!!input register device error!\n");
- goto err_free_input;
- }
- strlcpy(ts->name, TS_1WIRE_NAME, sizeof(ts->name));
- ret = gpio_request(TS_1WIRE_DATA_PIN, "TS_1WIRE_DATA_PIN");
- if (ret) {
- DBG("gpio request error!\n");
- goto err_free_input;
- }
- set_pin_up();
- set_pin_value(1);
- set_pin_as_output();
-
- init_hard_timer_for_1wire(ts);
- ret = gsc3280_request_hard_timer(&ts->ts_hard_timer);
- if (ret) {
- DBG("gsc3280 request hard timer error\n");
- goto err_gpio_req;
- }
- ret = misc_register(&bl_misc);
- if (ret != 0) {
- DBG("misc_register bl_misc error!\n");
- goto err_hard_timer_req;
- }
- ts->one_wire_status = IDLE;
- init_timer(&ts_1wire_timer);
- ts_1wire_timer.data = (unsigned long)ts;
- ts_1wire_timer.expires = jiffies + TIMER_DELAY;
- ts_1wire_timer.function = one_wire_timer_callback;
- add_timer(&ts_1wire_timer);
- mutex_lock(&wire1_ts_list_lock);
- list_add(&ts->device_entry, &wire1_ts_list);
- mutex_unlock(&wire1_ts_list_lock);
- platform_set_drvdata(pdev, ts);
- printk(KERN_INFO "ts 1wire probe success.\n");
- DBG("############\n");
- return 0;
- err_hard_timer_req:
- gsc3280_free_hard_timer(&ts->ts_hard_timer);
- err_gpio_req:
- gpio_free(TS_1WIRE_DATA_PIN);
- err_free_input:
- input_free_device(input);
- err_free_mem:
- kfree(ts);
- printk(KERN_INFO "!!!!ts 1wire probe error!!!!!\n");
- return ret;
- }
说明: 1) 首先申请一线触摸屏结构体内存。 2) 然后申请input设备内存。 3) 接下来初始化一线触摸屏结构体成员变量,包括自旋锁、IO申请等。 4) 申请硬件定时器。将控制背光定义为混杂设备。 5) 申请软件定时器,包括设置数据、延时时间和回调函数,然后将这个软定时器加入到系统中。 3.3、软定时器程序分析 根据前面的一线电阻触摸屏原理分析,首先看下回调函数one_wire_timer_callback():
- static void one_wire_timer_callback(unsigned long data)
- {
- unsigned long flags = 0;
- struct ts_1wire_t *ts = (struct ts_1wire_t *)data;
- //mod_timer(&ts_1wire_timer, jiffies + TIMER_DELAY);
- spin_lock_irqsave(&ts->slock, flags);
- if (ts->lcd_type == 0) {
- //DBG("REQ_INFO\n");
- ts->req = REQ_INFO;
- }
- else if (!ts->backlight_init_success) {
- //DBG("backlight_init_success\n");
- ts->backlight_init_success = 1;
- ts->req = BL_INIT;
- }
- else if (ts->backlight_req) {
- //DBG("backlight_req\n");
- ts->req = ts->backlight_req;
- ts->backlight_req = 0;
- } else {
- //DBG("REQ_TS\n");
- ts->req = REQ_TS;
- }
- spin_unlock_irqrestore(&ts->slock, flags);
- start_one_wire_session(ts);
- }
说明: 1) 首先上自旋锁,防止多个CPU在执行此函数。 2) 根据不同的类型,赋值不同的命令。获取触摸屏位置信息是默认命令。系统启动后,REQ_INFO和BL_INIT命令一般只执行一次。 3) 释放自旋锁,开始一次会话。 会话函数start_one_wire_session(ts)如下所示:
- static void start_one_wire_session(struct ts_1wire_t *ts)
- {
- u8 crc = 0;
- unsigned long flags = 0;
-
- if (ts->one_wire_status != IDLE) {
- DBG("one_wire_status: %d error!!!!\n", ts->one_wire_status);
- return;
- }
- spin_lock_irqsave(&ts->slock, flags);
- ts->one_wire_status = START; //IDLE to START
- set_pin_value(1);
- set_pin_as_output();
- crc8_init(crc);
- crc8(crc, ts->req);
- ts->io_data = (ts->req << 8) + crc;
- ts->io_data <<= 16;
- ts->io_bit_count = 1;
- set_pin_as_output();
- spin_unlock_irqrestore(&ts->slock, flags);
-
- local_irq_save(flags);
- gsc3280_timer_start(&ts->ts_hard_timer);
- set_pin_value(0);
- local_irq_restore(flags);
- }
说明: 1) 首先判断触摸屏状态,如果是不是IDLE,错误退出。 2) 上自旋锁,初始化触摸屏状态为开始,设置一线IO管脚状态,计算crc,组装数据。 3) 解自旋锁,开启硬件定时器,启动传输。 3.4、硬件定时器传输数据
- static void init_hard_timer_for_1wire(struct ts_1wire_t *ts)
- {
- ts->ts_hard_timer.type = LOOP;
- ts->ts_hard_timer.value_type = 1;
- ts->ts_hard_timer.bps = SAMPLE_BPS;
- ts->ts_hard_timer.function = ts_1wire_hardtimer_callback;
- ts->ts_hard_timer.data = (unsigned long)ts;
- }
- static void ts_1wire_hardtimer_callback(unsigned long data)
- {
- struct ts_1wire_t *ts = (struct ts_1wire_t *)data;
- //DBG("ts_1wire_hardtimer_callback start\n");
- ts->io_bit_count--;
- switch(ts->one_wire_status) {
- case START:
- //DBG("START\n");
- if (ts->io_bit_count == 0) {
- ts->io_bit_count = 16;
- ts->one_wire_status = REQUEST;
- }
- break;
- case REQUEST:
- //Send a bit
- //DBG("REQUEST\n");
- set_pin_value(ts->io_data & (1U << 31));
- ts->io_data <<= 1;
- if (ts->io_bit_count == 0) {
- ts->io_bit_count = 2;
- ts->one_wire_status = WAITING;
- }
- break;
- case WAITING:
- //DBG("WAITING\n");
- if (ts->io_bit_count == 0) {
- ts->io_bit_count = 32;
- ts->one_wire_status = RESPONSE;
- }
- else if (ts->io_bit_count == 1) {
- set_pin_as_input();
- set_pin_value(1);
- }
- break;
- case RESPONSE:
- //Get a bit
- //DBG("RESPONSE\n");
- ts->io_data = (ts->io_data << 1) | get_pin_value();
- if (ts->io_bit_count == 0) {
- ts->io_bit_count = 2;
- ts->one_wire_status = STOPING;
- set_pin_value(1);
- set_pin_as_output();
- one_wire_session_complete(ts);
- }
- break;
- case STOPING:
- //DBG("STOPING\n");
- if (ts->io_bit_count == 0) {
- ts->one_wire_status = IDLE;
- gsc3280_timer_stop(&ts->ts_hard_timer);
- mod_timer(&ts_1wire_timer, jiffies + TIMER_DELAY);
- }
- break;
- default:
- //DBG("default\n");
- gsc3280_timer_stop(&ts->ts_hard_timer);
- mod_timer(&ts_1wire_timer, jiffies + TIMER_DELAY);
- break;
- }
- }
说明: 1) 硬件定时器初始化init_hard_timer_for_1wire()函数在驱动探测函数中执行,此函数定义了硬件定时器工作在循环模式,数据类型为位速率,位速率为9600,定义了硬件定时器的回调函数。 2) 在硬件定时函数中,通过一个switch-case结构来区分不同的工作状态。启动一次会话后,其工作状态分别经过:START->REQUEST->WAITING->RESPONSE->STOPING。 3) 在START中,首先设置REQUEST中需要传送的位数为16位。在REQUEST中传送完16位数据后,设置进入两次WAITING状态。第一次WAITING将IO管脚设置为输入,准备接收数据。第二次WAITING设置当前状态为RESPONSE,接收数据长度为32位。在RESPONSE中,接收到32位数据后,设置进入两次STOPING,并且调用one_wire_session_complete(ts);函数表示一次会话完成。第二次进入STOPING后,停止硬件定时器,重新启动软件定时器。 one_wire_session_complete(ts);函数如下所示:
- static inline void notify_info_data(struct ts_1wire_t *ts, unsigned char lcd_type,
- unsigned char ver_year, unsigned char week)
- {
- if (lcd_type != 0xFF) {
- ts->lcd_type = lcd_type;
- //firmware_ver = ver_year * 100 + week;
- }
- }
- static inline void notify_bl_data(struct ts_1wire_t *ts, u8 a, u8 b, u8 c)
- {
- ts->bl_ready = 1;
- wake_up_interruptible(&bl_waitq);
- }
- static void one_wire_session_complete(struct ts_1wire_t *ts)
- {
- u8 crc = 0;
- const unsigned char *p = (const u8*)&(ts->io_data);
- crc8_init(crc);
- crc8(crc, p[3]);
- crc8(crc, p[2]);
- crc8(crc, p[1]);
- if (crc != p[0]) {
- DBG("one_wire_session_complete crc error\n");
- return;
- }
- switch(ts->req) {
- case REQ_TS:
- ts->x = ((p[3] >> 4U) << 8U) + p[2];
- ts->y = ((p[3] & 0xFU) << 8U) + p[1];
- ts->z = (ts->x != 0xFFFU) && (ts->y != 0xFFFU);
- notify_ts_data(ts);
- break;
- case REQ_INFO:
- notify_info_data(ts, p[3], p[2], p[1]);
- break;
- default:
- notify_bl_data(ts, p[3], p[2], p[1]);
- break;
- }
- }
说明: 1) 函数首先进行crc校验,如果crc错误,则直接退出。 2) 如果校验正确,根据不同的命令,进入不同的报告类型。其中REQ_TS和REQ_INFO不需要向应用层报告数据。notify_info_data()函数通过修改ts->lcd_type变量来保证系统起来以后,一线触摸屏执行REQ_INFO命令只有一次。 notify_ts_data(ts);函数如下所示:
- static void gsc3280_report_event(struct ts_1wire_t *ts, u32 z)
- {
- #ifdef CONFIG_GSC3280_POS_PRINT
- printk(KERN_INFO "x = %d\n", ts->x);
- printk(KERN_INFO "y = %d\n", ts->y);
- printk(KERN_INFO "z = %d\n", z);
- #endif
- input_report_abs(ts->input, ABS_PRESSURE, z);
- input_report_abs(ts->input, ABS_X, ts->x);
- input_report_abs(ts->input, ABS_Y, ts->y);
- if (z > 0)
- input_report_key(ts->input, BTN_TOUCH, 1);
- else
- input_report_key(ts->input, BTN_TOUCH, 0);
- input_sync(ts->input);
- }
- static inline void notify_ts_data(struct ts_1wire_t *ts)
- {
- if (ts->z == 1) {
- ts->x = ((ts->x -285) * gXres) / (3944 - 285);
- ts->y = ((3936 - ts->y) * gYres) / (3936 - 102);
- if ((ts->x > 0) && (ts->x < gXres) && (ts->y > 0) && (ts->y < gYres)) {
- if (ts->flg == 0) {
- ts->flg = 1;
- gsc3280_report_event(ts, 0);
- }
- gsc3280_report_event(ts, ts->z);
- }
- } else if (ts->z == 0) {
- if (ts->flg == 1) {
- ts->flg = 0;
- gsc3280_report_event(ts, 0);
- }
- }
- }
说明: 1) 首先根据公式计算触摸点的绝对坐标,如果该坐标值在正确的范围内,则上报该位置信息。 2) 由于是电阻触摸屏,其电阻值和位置是线性的,公式为:(电压 - 0) / (总电压) = (分辨个数 - 0) / (总个数)。即分辨个数 = 总个数 * (电压) / (总电压)。通过测量触摸屏四个脚点的电压值,分别得到X和Y方向上的最大总电压,然后在实际测量中,根据测得的电压值,带入公式中的电压项,分别计算出 X和Y方向的绝对坐标。本文使用的触摸屏分辨率为800*480,所以X方向的总个数为800,Y方向的为480。 3) 位置报告函数将在第三篇文章----input核心中讲述。 4) 本文设计的程序可以通过make menuconfig命令输入分辨率,Kconfig程序如下:
- config INPUT_TSDEV_SCREEN_X
- int "Horizontal screen resolution"
- depends on INPUT_TOUCHSCREEN
- default "800"
- help
- If you're using a digitizer, this is the X window screen resolution you are
- using to correctly scale the data. If you're not using a digitizer, this value
- is ignored.
- config INPUT_TSDEV_SCREEN_Y
- int "Vertical screen resolution"
- depends on INPUT_TOUCHSCREEN
- default "480"
- help
- If you're using a digitizer, this is the Y window screen resolution you are
- using to correctly scale the data. If you're not using a digitizer, this value
- is ignored.
在.c文件中通过下列程序获得输入的值:
- #ifndef CONFIG_INPUT_TSDEV_SCREEN_X
- #define CONFIG_INPUT_TSDEV_SCREEN_X 800
- #endif
- #ifndef CONFIG_INPUT_TSDEV_SCREEN_Y
- #define CONFIG_INPUT_TSDEV_SCREEN_Y 480
- #endif
- static const int gXres = CONFIG_INPUT_TSDEV_SCREEN_X;
- static const int gYres = CONFIG_INPUT_TSDEV_SCREEN_Y;
四、控制背光驱动和应用层程序 4.1、驱动程序 在系统探测函数中,将控制背光驱动定义为混杂设备并进行注册,定义混杂设备的源码如下:
- static struct file_operations bl_fops = {
- owner : THIS_MODULE,
- write : bl_write,
- open : bl_open,
- };
- static struct miscdevice bl_misc = {
- .minor = MISC_DYNAMIC_MINOR,
- .name = BACKLIGHT_DEVICE_NAME,
- .fops = &bl_fops,
- };
说明: 1) 混杂设备的文件操作函数中有打开和写,首先看下打开函数:
- static int bl_open(struct inode *inode, struct file *filp)
- {
- int status = -ENXIO;
- struct ts_1wire_t *ts = NULL;
- mutex_lock(&wire1_ts_list_lock);
- list_for_each_entry(ts, &wire1_ts_list, device_entry) {
- if(strcmp(ts->name, TS_1WIRE_NAME) == 0) {
- status = 0;
- break;
- }
- }
- mutex_unlock(&wire1_ts_list_lock);
- if (status == 0) {
- filp->private_data = ts;
- } else {
- return status;
- }
- return nonseekable_open(inode, filp);
- }
说明: 1) 首先通过本地全局链表和锁获得在探测函数中定义的设备结构体内存指针。 2) 然后调用无定位打开函数返回。 写数据即发送命令控制背光,程序如下:
- static ssize_t bl_write(struct file *filp, const char __user *buffer, size_t count, loff_t *ppos)
- {
- int ret = 0;
- u8 bl_data = 0;
- struct ts_1wire_t *ts = filp->private_data;
- DBG("bl_write start\n");
- if (get_user(bl_data, (u8 __user *)buffer)) {
- printk(KERN_INFO "get_user error!\n");
- return -EFAULT;
- }
- if (bl_data > 127) {
- bl_data = 127;
- }
- ts->bl_ready = 0;
- ts->backlight_req = bl_data + 0x80U;
- ret = wait_event_interruptible_timeout(bl_waitq, ts->bl_ready, HZ);
- if (ret < 0) {
- printk(KERN_INFO "wait_event_interruptible_timeout error!\n");
- return ret;
- } else if (ret == 0) {
- printk(KERN_INFO "time out error!\n");
- return -ETIMEDOUT;
- } else {
- return count;
- }
- }
说明: 1) 首先从应用层获取写入背光值。 2) 组合数据,形成背光控制命令。 3) 在会话函数中由于ts->backlight_req不为0,即可发送背光控制命令。 4.2、应用层程序 通过滑动变阻器来控制背光。根据协议,背光总共可以分为0~127个档位,滑动变阻器的测量值范围为2010~4010,也就是说,背光每变化一个档位, 滑动变阻器电阻值变化:(4010 - 2010) / 128 = 16。那么我们给背光发送的档位值即为:(x - 2010) / 16。应用层程序如下:
- #include <fcntl.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <sys/ioctl.h>
- #include <linux/ioctl.h>
- #define ADC_DEV_IOC_MAGIC 'a'
- #define ADC_DEV_IOC_MAXNR 2
- #define ADC_DEV_CON_PBAT _IOR(ADC_DEV_IOC_MAGIC, 0, int)
- #define ADC_DEV_CON_CHX _IOWR(ADC_DEV_IOC_MAGIC, 1, int)
- int main(int argc, char **argv)
- {
- unsigned int idCmd = 0;
- //unsigned char buffer[4] = {0};
- unsigned char bl_data = 0;
- int fd1 = 0, fd2 = 0, ret = 0, data = 0;
-
- //以阻塞方式打开设备文件,非阻塞时flags=O_NONBLOCK
- fd1 = open("/dev/adc0", 0);
- if (fd1 < 0) {
- printf("Open ADC Device Faild!\n");
- exit(1);
- }
- fd2 = open("/dev/backlight-1wire", O_RDWR);
- if (fd2 < 0) {
- printf("Open wire1 backlight Device Faild!\n");
- exit(1);
- }
- while(1) {
- data = 1;
- idCmd = ADC_DEV_CON_CHX;
- ret = ioctl(fd1, idCmd, &data);
- if (ret != 0) {
- printf("Read ADC Device Faild!\n");
- break;
- }
- //printf("data = %d\n", data);
- bl_data = (data - 2010) /16;
- //printf("bl_data = %d\n", bl_data);
- ret = write(fd2, &bl_data, 1);
- if (ret < 0) {
- printf("wire1 backlight ret = %d\n", ret);
- //break;
- }
- //sleep(1);
- for (ret = 0; ret < 655360; ret++)
- ;;;;
- }
- close(fd1);
- close(fd2);
- return 0;
- }
编译后,将程序设为后台执行,系统启动后,任何时间滑动变阻器,一线触摸屏的背光都会根据电阻的位置而做相应的变化。
|