tiny6410驱动移植之触摸屏驱动

友善的硬件手册有如下一段话:

其中,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
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值