友善的硬件手册有如下一段话:
其中,37
、38
、39
、40
为四线触摸屏接口,这4
个信号直接从CPU
引出,可以使用CPU
本身所带的触摸屏控制器,直接连接四线电阻触摸屏使用。不过,采用CPU
自带的AD
转换器连接四线电阻触摸屏很难达到较好的触摸效果,特别是当触摸屏尺寸比较大的时候(
比如7
寸以上)
。
为了达到更好的触摸效果,我们特意设计了一线精准触摸电路,并集成到LCD的驱动板上,它采用专业的触摸屏控制芯片ADS7843(或兼容),配合一个单片机,构成一个独立的四线电阻触摸屏采集电路,可以实现更好的数据采集,去抖处理,最后通过一个普通的GPIO
口把处理过的数据发送出去,在开发板上与之相连的是LCD
接口的第31
脚,该端口是可复用的,我们只使用了它的GPIO
功能,也就是GPF15
,这也是“一线触摸”名称的由来。
注意:如果你需要全色的LCD信号(即8:8:8模式),则还需要从核心板的CON2接口引出缺失的LCD信号(详见Tiny6410核心板引脚定义说明),它们将对应于下表中的红色字体”NC”。
由此可知,对于ADS7843和单片机做的那一部分数据采集和传输我们是没办法得知了,我们只能分析内核里的和单片机通讯的那部分即 (drivers\input\touchscreen\Mini6410_1wire_host.c)
内核中有关触摸屏的就这一个文件有用,至于 ts-if.c虽然内核中把 CONFIG_TOUCHSCREEN_IF配置为 y 但这个文件根本没有参与链接只是编译成 .o文件而已。。。 下面我们来分析一下Mini6410_1wire_host.c 这个所谓的一线触摸驱动
它一开始就注册了两个 混杂设备
ret =misc_register(&ts_misc) | misc_register(&bl_misc) ;
所谓混杂设备只不过是一种相对简洁的字符设备
它的主设备号都是10
次设备号可以自己指定也可以内核分配
staticstruct miscdevice ts_misc = {
.minor = 181,
.name = TOUCH_DEVICE_NAME,
.fops = &ts_fops,
};
staticstruct file_operations ts_fops = {
owner: THIS_MODULE,
read: ts_read,
poll: ts_poll,
};
对于 ts_misc
次设备号为181
设备名为touchscreen-1wire
它的file_operations
里只有一个 read
和poll
可见它用到了Poll
机制和仅对外提供read
接口
staticstruct miscdevice bl_misc = {
.minor = MISC_DYNAMIC_MINOR,
.name = BACKLIGHT_DEVICE_NAME,
.fops = &bl_fops,
};
staticstruct file_operations bl_fops = {
owner: THIS_MODULE,
write: bl_write,
};
对于 bl_misc
主设备号为10
次设备号由内核分配
设备名为 backlight-1wire
File_operations
里仅仅提供了一个write
函数
这是用来控制背光的
再回到init
入口
set_pin_up(); //GPF15
上拉使能,因为它要用这个口发送指令,使能上拉电阻可以减少CPU
功耗
set_pin_value(1); //
把GPF15
设为高电平
set_pin_as_output(); //
设置GPF15
为输出模式
setup_irq(IRQ_TIMER3,&timer_for_1wire_irq);
这个函数设置了一个TIMER3
中断
当定时器3
产生中断时会跳到
staticstruct irqaction timer_for_1wire_irq = {
.name = "1-wire Timer Tick",
.flags = IRQF_DISABLED | IRQF_IRQPOLL,
.handler = timer_for_1wire_interrupt,
.dev_id = &timer_for_1wire_irq,
};
里的 handler
去执行
即 timer_for_1wire_interrupt();
这个就是用来发送和接收数据的函数
ret =init_timer_for_1wire();
是对timer3
的一些设置
包括
通过 clk_get_rate(clk);
获得 pclk,
通过
读取 TCFG0
寄存器的 bit[15:8]
获得 timer3
的预分频器
再通过 Frequency = PCLK / ( {prescaler value + 1}) / {divider value}
计算出
TCNT_FOR_SAMPLE_BIT = pclk /(prescale1_value + 1) / SAMPLE_BPS - 1; // 与时序相关 TCNT_FOR_SAMPLE_BIT即重装载寄存器的值
init_timer(&one_wire_timer); //
设置了一个内核定时器
staticstruct timer_list one_wire_timer = {
.function = one_wire_timer_proc,
};
//
该定时器的处理函数是
voidone_wire_timer_proc(unsigned long v)
{
unsigned char req;
if (exitting) {
return;
}
one_wire_timer.expires = jiffies + HZ /50; //20ms
后产生中断
add_timer(&one_wire_timer);
if (lcd_type == 0) {
req = REQ_INFO;
} else if (!backlight_init_success) {
req = 127;
} else if (backlight_req) {
req = backlight_req;
backlight_req = 0;
} else {
req = REQ_TS;
}
start_one_wire_session(req); //
启动传输
}
这个函数就是用来启动与单片机的通讯的函数
可以看出每20ms
启动一次
即20ms
和单片机通讯一次用来控制lcd
背光或者读取信息
one_wire_timer_proc(0);//
启动和单片机通讯
一开始 lcd_type
和 backlight_init_success
都是0
所以通讯启动后 6410
会发送
类型为REQ_INFO
去请求获得lcd_type
获得成功后会在
staticinline void notify_info_data(unsigned char _lcd_type, unsigned char ver_year,unsigned char week)
{
if (_lcd_type != 0xFF) {
lcd_type = _lcd_type;
firmware_ver = ver_year * 100+ week;
}
}
里设置 lcd_type
以及
固件版本号firmware_ver
然后发送
类型为127
的请求给单片机
最终会在 static inline void notify_bl_data(unsigned char a, unsigned char b,unsigned char c)
{
bl_ready = 1;
backlight_init_success = 1;
wake_up_interruptible(&bl_waitq);
}
里设置 backlight_init_success
if(lcd_type == 0) {
req = REQ_INFO;
} else if (!backlight_init_success) {
req = 127;
} else if (backlight_req) {
req = backlight_req;
backlight_req = 0;
} else {
req = REQ_TS;
}
start_one_wire_session(req);
以后有backlight_req
请求就发请求给单片机控制背光
没有的话就是直接是发送 REQ_TS
请求获得触摸屏数据了
Init
里的最后一个函数
create_proc_read_entry("driver/one-wire-info",0, NULL, read_proc, NULL);
这个函数在
开发板的 /proc
目录下创建了 driver/one-wire-info
这个文件
对这个文件执行读操作
如
: cat /proc/driver/one-wire-info
内核就会调用read_proc
函数
更多关于这个函数的解释可以参考:
http://www.360doc.com/content/15/0129/12/11400509_444695297.shtml
staticint read_proc(char *buf, char **start, off_t offset, int count, int *eof, void*data)
{
int len;
len = sprintf(buf, "%u %u %u %u%04X %08X\n", lcd_type, firmware_ver, total_received, total_error,last_req, last_res);
*eof = 1;
return len;
}
这个函数会打印 lcd_type firmware_ver
等变量
效果如下:
[root@FriendlyARM/]# cat /proc/driver/one-wire-info
3 11271211 42 408A FFFFFFBD
[root@FriendlyARM/]# cat /proc/driver/one-wire-info
3 11271751 42 408A FFFFFFBD
下面就来分析一下有关和单片机通讯方面的东西
staticvoid start_one_wire_session(unsigned char req)
{
unsigned long tcon;
unsigned long flags;
if (one_wire_status != IDLE) {
printk("one_wire_status:%d\n", one_wire_status);
return;
}
one_wire_status = START;
set_pin_value(1);
set_pin_as_output();
// IDLE to START
{
unsigned char crc;
crc8_init(crc);
crc8(crc, req);
io_data = (req << 8) +crc;
io_data <<= 16; //
构造io_data
把req
放到高8
位接着是crc
检验码
}
last_req = (io_data >> 16);
one_wire_request = req;
io_bit_count = 1;
set_pin_as_output();
writel(TCNT_FOR_SAMPLE_BIT,S3C2410_TCNTB(3));
// init tranfer and start timer
tcon = __raw_readl(S3C2410_TCON);
tcon &= ~(0xF << 16);
tcon |= S3C2410_TCON_T3MANUALUPD; //
设置为手动更新
writel(tcon, S3C2410_TCON);
tcon |= S3C2410_TCON_T3START; //
启动定时器3
tcon |= S3C2410_TCON_T3RELOAD; //
自动重装载
tcon &= ~S3C2410_TCON_T3MANUALUPD; //
取消手动更新
local_irq_save(flags);
writel(tcon, S3C2410_TCON);
set_pin_value(0);
local_irq_restore(flags);
}
#definecrc8_init(crc) ((crc) = 0XACU)
#definecrc8(crc, v) ( (crc) = crc8_tab[(crc) ^(v)])
其中的
crc8_init(crc); crc8(crc, req);
是crc
校验
为了确保数据的可靠性而必须这样做
两边接收到数据都会根据接收到的数据来计算crc
校验码
如果和接收到的crc
校验码不一致就丢弃这组数据
crc
校验非常简单
就是先把 crc
赋值为 0XAC
再和 v
异或
并以得到的结果为下标在 crc8_tab
里得到一项
并把它的值赋值给 crc
这样就得到了crc
校验码
writel(TCNT_FOR_SAMPLE_BIT,S3C2410_TCNTB(3));
这就是往 TCNTB3
寄存器写入 TCNT_FOR_SAMPLE_BIT
TCNTB3
就相当于单片机里说的重装载寄存器TCNT_FOR_SAMPLE_BIT
就是在init_timer_for_1wire()
里算出来的
把这个数写到TCNTB3
里 timer3
时钟每过一个脉冲 TCNTB3
里的计数器就加1
直到溢出产生中断
从 TCNT_FOR_SAMPLE_BIT = pclk /(prescale1_value + 1) / SAMPLE_BPS - 1;
且 SAMPLE_BPS = 9600
大概可以猜测这种通讯协议类型串口波特率的 9600
local_irq_save(flags);
和 local_irq_restore(flags);
分别是保存cpsr
寄存器里的 flags
和
把之前保存的flags
重新写回 cpsr
寄存器
staticinline unsigned long arch_local_save_flags(void)
{
unsigned long flags;
asm volatile(
" mrs %0, cpsr @ local_save_flags"
:"=r" (flags) : : "memory", "cc");
return flags;
}
和
staticinline void arch_local_irq_restore(unsigned long flags)
{
asm volatile(
" msr cpsr_c, %0 @ local_irq_restore"
:
: "r" (flags)
: "memory","cc");
}
这里用到了 gcc
内嵌arm
汇编的知识
具体可以参考
:
再来看看tiemr3
中断处理函数
staticirqreturn_t timer_for_1wire_interrupt(int irq, void *dev_id)
{
io_bit_count--;
switch(one_wire_status) {
case START:
if (io_bit_count == 0) {
io_bit_count = 16;
one_wire_status =REQUEST;
}
break;
case REQUEST:
// Send a bit
set_pin_value(io_data &(1U << 31));
io_data <<= 1;
if (io_bit_count == 0) {
io_bit_count = 2;
one_wire_status =WAITING;
}
break;
case WAITING:
if (io_bit_count == 0) {
io_bit_count = 32;
one_wire_status =RESPONSE;
}
if (io_bit_count == 1) {
set_pin_as_input();
set_pin_value(1);
}
break;
case RESPONSE:
// Get a bit
io_data = (io_data <<1) | get_pin_value();
if (io_bit_count == 0) {
io_bit_count = 2;
one_wire_status =STOPING;
set_pin_value(1);
set_pin_as_output();
one_wire_session_complete(one_wire_request,io_data);
}
break;
case STOPING:
if (io_bit_count == 0) {
one_wire_status =IDLE;
stop_timer_for_1wire();
}
break;
default:
stop_timer_for_1wire();
}
return IRQ_HANDLED;
}
里面就一个 switch case
非常简洁而且都是按顺序来执行的
先是START
然后 REQUEST
发送请求
然后WAITING(
这里把GPF15
设置为输入引脚)
接着是RESPONSE
通过读GPF15
引脚的数据来把数据一位一位存入io_data
完了之后把GPF15
设为输出引脚并执行
staticvoid one_wire_session_complete(unsigned char req, unsigned int res)
{
unsigned char crc;
const unsigned char *p = (constunsigned char*)&res;
total_received ++;
last_res = res;
crc8_init(crc);
crc8(crc, p[3]);
crc8(crc, p[2]);
crc8(crc, p[1]);
if (crc != p[0]) {
// CRC dismatch
if (total_received > 100){
total_error++;
}
return;
}
switch(req) {
case REQ_TS:
{
unsigned short x,y;
unsigned pressed;
x = ((p[3] >> 4U) << 8U) + p[2];
y = ((p[3] & 0xFU) << 8U) + p[1];
pressed = (x !=0xFFFU) && (y != 0xFFFU);
notify_ts_data(x, y,pressed);
}
break;
case REQ_INFO:
notify_info_data(p[3], p[2],p[1]);
break;
default:
notify_bl_data(p[3], p[2],p[1]);
break;
}
}
这个函数首先会计算接收到的crc
校验码
如果不对就丢弃
如果正确就根据自己发送的请求来对接收到的数据进行处理
对触摸屏数据的处理如下:
staticinline void notify_ts_data(unsigned x, unsigned y, unsigned down)
{
if (!down && !(ts_status&(1U << 31))) {
// up repeat, give it up
return;
}
ts_status = ((x << 16) | (y)) |(down << 31);
ts_ready = 1;
wake_up_interruptible(&ts_waitq);
}
把x,y
和down
存到ts_status
里
并且
通过 ts_ready = 1; wake_up_interruptible(&ts_waitq);
来唤醒在ts_read()
因为
在应用读取触摸屏设备数据时如果没有数据就会在ts_read
那里休眠(
当然如果是非阻塞方式打开的就不会)
由此我们可以知道
应用程序要获得触摸屏数据可以通过打开 /dev/touchscreen-1wire
通过 read
函数来获得数据
下面是我写的测试程序:
#include<stdio.h>
#include<unistd.h>
#include<fcntl.h>
int main(int argc,char **argv)
{
int fd;
unsigned int buf,ret;
int x,y;
if (argc != 1)
{
printf("Usage:argv[1]\n");
}
fd =open("/dev/touchscreen-1wire",O_RDONLY);
if (fd < 0)
{
printf("can't open thedev\n");
return -1;
}
while (1)
{
ret =read(fd,&buf,sizeof(buf));
if (ret ==sizeof(buf))
{
x = (buf &0xfff)*800/4095;
y = (480 - ((buf>> 16) & 0xfff)*480/4095); //
坐标转换
printf("up =%d, x = %d, y = %d\n",((buf>>31)?1:0), x,y);
}
else
{
printf("ret =%d\n", ret);
}
}
close(fd);
return 0;
}
根据驱动来分辨x
,y
数据的存放位置再加以转换 (
其中,x,y
都是ad
转换码
就是12
位ad
转换器转换的值
我们自己换算成
实际坐标)
实验现象:
先把qt
关掉 (
通过注释掉 /etc/init.d/rcS
里的 qt)
否则我们的测试程序只有很小的可能可以抢到资源。。。。
Try tobring eth0 interface up......NFS root ...Done
Pleasepress Enter to activate this console.
[root@FriendlyARM/]# ./ts_test
up = 1,x = 121, y = 212
up = 1,x = 120, y = 212
up = 1,x = 120, y = 211
up = 1,x = 120, y = 211
up = 1,x = 120, y = 211
up = 1,x = 120, y = 211
up = 0,x = 800, y = 0
up = 1,x = 468, y = 257
up = 1,x = 468, y = 257
up = 1,x = 468, y = 257
up = 1,x = 468, y = 258
up = 1,x = 469, y = 259
up = 1,x = 470, y = 260
up = 0,x = 800, y = 0
up = 1,x = 548, y = 289
up = 1,x = 547, y = 287
up = 1,x = 546, y = 287
up = 1,x = 545, y = 288
up = 0,x = 800, y = 0
up = 1,x = 338, y = 361
up = 1,x = 338, y = 361
up = 1,x = 338, y = 361
up = 1,x = 338, y = 361
up = 1,x = 338, y = 362
up = 1,x = 338, y = 362
up = 0,x = 800, y = 0
up = 1,x = 732, y = 375
up = 1,x = 732, y = 375
up = 1,x = 732, y = 375
up = 1,x = 731, y = 375
up = 0,x = 800, y = 0
up = 1,x = 34, y = 426
up = 1,x = 35, y = 425
up = 0,x = 800, y = 0
up = 1,x = 289, y = 66
up = 1,x = 289, y = 66
up = 1,x = 289, y = 66
up = 1,x = 289, y = 66
up = 1,x = 289, y = 66
up = 1,x = 289, y = 66
up = 1,x = 289, y = 66
up = 1,x = 289, y = 66
up = 1,x = 288, y = 67
up = 0,x = 800, y = 0
up = 1,x = 321, y = 434
up = 1,x = 322, y = 434
up = 1,x = 321, y = 433
up = 1,x = 321, y = 433
up = 0,x = 800, y = 0
下面是移植tslib1.4
由于tsblib1.4
是针对使用input
子系统的触摸屏做的而tiny6410
并不属于这一类
所以里面要改一些东西
否则不能用
具体参考:
事实上要改的也就一个函数
staticint ts_input_read(struct tslib_module_info *inf,
struct ts_sample *samp, int nr)
{
struct tsdev *ts = inf->dev;
int ret = nr;
int total = 0;
unsigned long data;
while (total < nr)
{
ret =read(ts->fd, &data, sizeof(data));
if (ret == -1)
return -1;
samp->pressure =((data>>31) & 0x1) ;
samp->x= ((data>> 16) & 0xfff);
samp->y = data& 0xfff;
total++;
samp++;
data = 0;
}
ret = total;
return ret;
}
自带的是通过 input
子系统的一些接口来存储触摸屏数据
而我们分析过触摸屏驱动中的ts_status
的数据格式就可以从中提取出x,y, pressure
还有把export TSLIB_TSDEVICE=/dev/event1
改成 export TSLIB_TSDEVICE=/dev/touchscreen-1wire
就改这些就可以了
其他都按照韦老师那个tslib
编译使用方法
笔记做就行了
总结:
友善的一线触摸另辟蹊径
并没有使用input
子系统那一套
而是直接做成了混杂设备(
字符设备)
向外提供read
接口
应用中通过 read
来读取触摸屏数据
它与单片机的通讯协议也并不复杂
所谓的128
级背光控制也是由单片机来控制的
和单片机通讯过程中 6410
只负责发命令和接收数据并处理数据
所有的数据采集优化等都是单片机和ADS7843
来做
转载于:http://www.100ask.org/bbs/forum.php?mod=viewthread&tid=12312
转载于:http://www.100ask.org/bbs/forum.php?mod=viewthread&tid=12312