linux串口触摸屏设计总结_北尔触摸屏使用总结
linux串口触摸屏设计总结由刀豆文库小编整理,希望给你工作、学习、生活带来方便,猜你可能喜欢“北尔触摸屏使用总结”。
Linux serial touch 设计总结
概述:
最近在做嵌入式linux下串口触摸屏设计,遇到一些问题,经过查找资料和请教同事,总算把问题解决了,事后有把linux相关的内核代码仔细看了一遍,为了有点成果,特别写了个总结。如有任何问题请联系yxj_5421@163.com,转载请标明出处。
系统资源:
Linux:2.6.36
UI:QT+TSLIB 硬件资源不关心
设计方法:
有两种实现途径。
1、是将要使用的串口单独拿出来,作为一个platform总线设备实现,在嵌入式平台mach文件里面,加上串口中断号和寄存器首地址,然后将这个串口注册成一个platform总线设备。在驱动probe函数里面需要得到这个串口中断号以及寄存器映射地址,通过寄存器映射地址设置串口波特率,数据位,停止位等,通过中断号注册中断等,然后调用input_register_device注册一个input设备。在中断里面得到外面触摸屏的数据,然后根据input touch协议上报触摸数据。这种方法实现简单明了,不需要和linux的tty,serio等打交道。但是要求知道串口硬件spec,比如寄存器等,而且这个串口就只能给触摸屏使用了,不能作为tty使用。因为是嵌入式开发,因此很容易知道硬件spec,而且嵌入式平台一旦确定,那么这个串口肯定就是给触摸屏使用了。因此在嵌入式平台上,推荐使用这个方法。
是将串口作为一个serio总线设备,利用linux内核提供serio总线驱动,通过设置对应的串口,调用serport提供的函数将串口当做serio总线设备,在驱动里面需要按照serio总线设备驱动的框架来实现,这方面的例子linux里面有很多,比如touchright.c,在模块init函数里面调用serio_register_driver注册serio总线设备驱动,如果serio总线上对应的serio设备存在,就调用connect函数,在这个函数里面调用input_register_device注册一个input设备。具体驱动不再分析了,很简单,相信各位都能看的懂。
至此,两种方法都实现了串口触摸屏的驱动,讲到这里是不是就完了,非也,本文的重点还在后面,请看下面分析:
第一种方法只要驱动模块被加载,就会在/dev/input下面创建一个eventx节点,tslib就能访问这个节点,获得触摸坐标,然后送给qt。第二种方法驱动模块加载后,并没有创建eventx节点,也就是说connect函数没有被调用,按照linux驱动模型来看,就是serio总线上还没有对应的serio设备,因此驱动加载时没有对应的设备,就不会调用connect函数,这时的串口还是作为一个linux tty设备存在。
我遇到的问题就是serio驱动加载了,但是没有创建eventx节点,查找资料也只有一个说是要把tty设置成N_MOUSE,然后读,说的不清楚,也不知道怎么实现,经过自己摸索,终于把问题解决了。
2、Linux 启动后串口形式: Linux一启动是将串口作为tty来设置的。看下的调用:
start_kernel
init/main.c大家对这个函数不陌生吧,linux启动过程中重要的一个函数
console_init();
drivers/tty/tty_io.c
tty_register_ldisc(N_TTY, &tty_ldisc_N_TTY);drivers/tty/tty_idisc.c 给串口注册一个tty链路层处理函数ops。
现在我们需要写一个上层的应用程序,对这个tty进行设置,需要设置波特率,数据位,停止位等,最重要的是要将这个tty设备设置成一个serio总线设备,然后把它注册在serio总线上,请看下面的代码:
fd = open(device, O_RDWR | O_NOCTTY | O_NONBLOCK);
if(fd
setline(fd, type->flags, type->speed);ldisc = N_MOUSE;if(ioctl(fd, TIOCSETD, &ldisc)){
} fprintf(stderr, “inputattach: can't set line disciplinen”);return EXIT_FAILURE;
} fprintf(stderr, “inputattach: '%s'-%sn”, device, strerror(errno));return 1;
里面的device就是对应要使用的那个串口,linux里面一般是/dev/ttyS0,首先是打开串口 open(device, O_RDWR | O_NOCTTY | O_NONBLOCK)接着设置波特率等 setline(fd, CS8, B9600);static void setline(int fd, int flags, int speed){
} struct termios t;tcgetattr(fd, &t);t.c_cflag = flags | CREAD | HUPCL | CLOCAL;t.c_iflag = IGNBRK | IGNPAR;t.c_oflag = 0;t.c_lflag = 0;t.c_cc[VMIN ] = 1;t.c_cc[VTIME] = 0;cfsetispeed(&t, speed);cfsetospeed(&t, speed);tcsetattr(fd, TCSANOW, &t);devt = type->type |(id
read(fd, NULL, 0);
接下来就是重点了
ldisc = N_MOUSE;if(ioctl(fd, TIOCSETD, &ldisc))
跟踪代码到内核层ioctl:
long tty_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
drivers/tty/tty_io.c case TIOCSETD: return tiocsetd(tty, p);
drivers/tty/tty_io.c
tty_set_ldisc(tty, ldisc);drivers/tty/tty_idisc.c,ldisc等于N_MOUSE new_ldisc = tty_ldisc_get(ldisc);
ldops = get_ldops(disc);
这段代码需要得到N_MOUSE的链路层,先在tty_ldiscs里面查找是否有N_MOUSE链路层的处理函数ops,如果没有,就需要加载serport模块,看看这个模块init函数 retval = tty_register_ldisc(N_MOUSE, &serport_ldisc);注册一个N_MOUSE链路层的处理函数ops 创建一个新的N_MOUSE链路层new_ldisc,接着调用 tty_ldisc_aign(tty, new_ldisc);
把新的链路层放在tty里面 retval = tty_ldisc_open(tty, new_ldisc);打开这个新的链路层
至此,已经给串口增加了一个N_MOUSE的链路层,并且把链路层的处理函数也注册进去了。这个串口当前的链路层就是N_MOUSE。目前为止串口还只是个tty设备,并没有注册到serio总线上。继续看我们的应用程序:
devt = type->type |(id
fprintf(stderr, “inputattach: can't set device typen”);
return EXIT_FAILURE;} ret = ld->ops->open(tty)
ld->ops就是serport注册的serport_ldisc static int serport_ldisc_open(struct tty_struct *tty)drivers/input/serio/serport.c 这个函数里面会创建一个serport结构体,并初始化
调用
long tty_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
drivers/tty/tty_io.c retval = ld->ops->ioctl(tty, file, cmd, arg);static int serport_ldisc_ioctl(struct tty_struct * tty, struct file * file, unsigned int cmd, unsigned long arg)设置
serport->id.proto = type & 0x000000ff;serport->id.id
=(type & 0x0000ff00)>> 8;serport->id.extra =(type & 0x00ff0000)>> 16;这里三个值一定要和serio总线驱动里面对应的值一致,serio总线就是靠它们来给设备和驱动建立联系的。
调用
read(fd, NULL, 0);跟踪代码到内核层tty_read:
static ize_t tty_read(struct file *file, char __user *buf, size_t count,loff_t *ppos)(ld->ops->read)(tty, file, buf, count)
这个ld就是tty当前的链路层结构,上面我们已经设置N_MOUSE为tty的当前链路层,因此ld->ops就是serport注册的serport_ldisc static ize_t serport_ldisc_read(struct tty_struct * tty, struct file * file, unsigned char __user * buf, size_t nr)
serio_register_port(serport->serio);
serio_init_port(serio);
serio_queue_event(serio, owner, SERIO_REGISTER_PORT);注册一个serio总线设备,关于serio总线,网络有很多资料介绍,这里就不说了。至此,我们的串口设备已经当做serio总线设备注册在serio总线上了,如果相应的驱动也在serio总线上,就会进行设备和驱动的匹配,然后调用驱动里面的connect函数,在这个函数里面就会创建input节点。我们的驱动和设备已经运行起来了,现在看看数据是如何传递的
先看具体串口中断函数: 我们以altera_uart.c为例: altera_uart_interrupt
altera_uart_rx_chars(pp)
tty_flip_buffer_push(port->state->port.tty);
flush_to_ldisc(&tty->buf.work);
disc->ops->receive_buf(tty, char_buf, flag_buf, count);disc->ops就是serport注册的serport_ldisc static void serport_ldisc_receive(struct tty_struct *tty, const unsigned char *cp, char *fp, int count)
serio_interrupt(serport->serio, cp[i], ch_flags);
ret = serio->drv->interrupt(serio, data, dfl);drv->interrupt就是我们驱动函数提供一个函数,它每次接受一个字符,在这个函数里面,接受到足够信息后,就能得到触摸屏坐标信息,然后通过input_report上报上去。看看数据处理流程图:
总结:
要想让基于serio总线驱动的串口触摸屏能正常工作,在linux内核需要加载驱动模块,serport模块。还需要一个上层应用程序,这个程序需要进行以下工作
1、打开你要使用的串口,比如
open(device, O_RDWR | O_NOCTTY | O_NONBLOCK)
device为/dev/ttyS02、设置串口波特率等,和你的串口触摸屏一致
3、给串口增加一个N_MOUSE链路层
4、设置你的串口触摸屏type,id,extra5、读串口read(fd, NULL, 0);