Chinaunix首页 | 论坛 | 博客
  • 博客访问: 126871
  • 博文数量: 37
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 27
  • 用 户 组: 普通用户
  • 注册时间: 2014-03-05 17:21
文章分类

全部博文(37)

文章存档

2014年(37)

我的朋友

分类: 嵌入式

2014-04-02 22:03:07

一、系统简介
        本文介绍友善之臂一线触摸屏接到GSC3280开发板的驱动。本文中,首先讲述GSC3280内部定时器驱动,该驱动作为一线触摸屏数据传送时的采样定时 器。然后讲述一线触摸屏协议驱动。最后讲述通过一线触摸屏的协议,控制触摸屏的背光,并且通过滑动变阻器来线性控制触摸屏的背光,提供了底层和应用层程 序。
二、GSC3280内部定时器驱动
        GSC3280芯片包含一个TIMER定时器模块,TIMER定时器模块包含TIMER0~TIMER3共4个相对独立的32bit 定时器,每一个定时器都有自己独立的时钟源,均支持循环定时模式和单次定时模式两种工作模式,每个定时器都有各自的中断,TIMER定时器模块将4个中断 合并为一个中断输出给中断控制器,并额外提供一个int_for_adc中断。
        首先看下定时器模块初始化函数gsc3280_timer_init():

点击(此处)折叠或打开

  1. struct gsc3280_timer {
  2.     bool in_use;
  3.     void __iomem *base;
  4.     unsigned long rate;
  5.     struct gsc3280_hard_timer *h_timer;
  6.     char *name;
  7. };
  8. static struct gsc3280_timer gsc3280_timer_priv[GSC3280_H_TIMER_NR] = {
  9.     {false, (__iomem void *)GSC3280_TIMER_BASEADDR + 0x00,    0, NULL, "Timer0"},
  10.     {false, (__iomem void *)GSC3280_TIMER_BASEADDR + 0x14,    0, NULL, "Timer1"},
  11.     {false, (__iomem void *)GSC3280_TIMER_BASEADDR + 0x28,    0, NULL, "Timer2"},
  12.     {false, (__iomem void *)GSC3280_TIMER_BASEADDR + 0x3C,    0, NULL, "Timer3"}
  13. };
  14. static int __init gsc3280_timer_init(void)
  15. {
  16.     int i = 0;
  17.     struct clk *clk = NULL;
  18.     timer_tcr_u tcr = {.w32 = 0};

  19.     DBG("############");
  20.     DBG("gsc3280 timer init start\n");
  21.     //enable timer modules
  22.     sysctl_mod_enable(SYSCTL_MOD_TIMER0);
  23.     sysctl_mod_enable(SYSCTL_MOD_TIMER1);
  24.     sysctl_mod_enable(SYSCTL_MOD_TIMER2);
  25.     sysctl_mod_enable(SYSCTL_MOD_TIMER3);
  26.     //get 4 timer rate, use this cal LC
  27.     clk = clk_get(NULL, "timer0");
  28.     gsc3280_timer_priv[TIMER0].rate = clk_get_rate(clk);
  29.     clk = clk_get(NULL, "timer1");
  30.     gsc3280_timer_priv[TIMER1].rate = clk_get_rate(clk);
  31.     clk = clk_get(NULL, "timer2");
  32.     gsc3280_timer_priv[TIMER2].rate = clk_get_rate(clk);
  33.     clk = clk_get(NULL, "timer3");
  34.     gsc3280_timer_priv[TIMER3].rate = clk_get_rate(clk);
  35.     for (i = 0; i < GSC3280_H_TIMER_NR; i++) {
  36.         if (gsc3280_timer_priv[i].rate == 0) {
  37.             DBG("GSC3280 TIMER%d Get Rate Failed!", i);
  38.             return -EAGAIN;
  39.         }
  40.         DBG("timer[%d] = %ld\n", i, gsc3280_timer_priv[i].rate);
  41.         tcr.w32 = __raw_readl(gsc3280_timer_priv[i].base + TIMER_CR);
  42.         tcr.b.enable = 0;    //disable timer
  43.         tcr.b.irq_mask = 1;        //mask timer irq
  44.         __raw_writel(tcr.w32, gsc3280_timer_priv[i].base + TIMER_CR);
  45.     }
  46.     printk(KERN_INFO "GSC3280 TIMER0~3 Enable Success!");
  47.     DBG("############");
  48.     return 0;
  49. }
  50. core_initcall(gsc3280_timer_init);
        说明:
        1) 首先在系统控制模块中使能四个定时器模块。
        2) 分别获取定时器的时钟频率。
        3) 最后,分别初始化定时器的控制寄存器。
        4) 注意此处初始化使用的是core_initcall()宏,具有很高的执行优先级。
        然后看下申请定时器函数,该函数即为一线触摸屏驱动中使用的申请定时器函数。

点击(此处)折叠或打开

  1. int gsc3280_request_hard_timer(struct gsc3280_hard_timer *h_timer)
  2. {
  3.     int i = 0, ret = 0;

  4.     for (i = 0; i < GSC3280_H_TIMER_NR; i++) {
  5.         if (gsc3280_timer_priv[i].h_timer == NULL) {
  6.             gsc3280_timer_priv[i].h_timer = h_timer;
  7.             ret = request_irq(EXT_GSC3280_TIMER_IRQ, &gsc3280_timer_interrupt,
  8.                                 IRQF_SHARED, gsc3280_timer_priv[i].name,
  9.                                 &gsc3280_timer_priv[i]);
  10.             if (ret) {
  11.                 printk(KERN_ERR "Start hard Timer request irq failed!\n");
  12.                 return ret;
  13.             }
  14.             DBG("GSC3280 Request Timer Success.");
  15.             return 0;
  16.         }
  17.     }
  18.     DBG("GSC3280 Request Timer error!");
  19.     return -EBUSY;
  20. }
  21. EXPORT_SYMBOL(gsc3280_request_hard_timer);
        说明:
        1) 首先通过for循环找到第一个没有被使用的硬件定时器。
        2) 找到后,申请中断。由前面介绍可知,四个定时器共享中断。该中断函数接下里会讲述。
        3) 如果申请中断失败,直接退出。
        gsc3280_timer_interrupt()函数如下所示:

点击(此处)折叠或打开

  1. static irqreturn_t gsc3280_timer_interrupt(int irq, void *_ptr)
  2. {
  3.     irqreturn_t ret = IRQ_NONE;
  4.     struct gsc3280_timer *timer = (struct gsc3280_timer *)_ptr;

  5.     if (__raw_readl(timer->base + TIMER_IS) & 0x01) {
  6.         //is real interrupt
  7.         timer->h_timer->function(timer->h_timer->data);
  8.         ret = IRQ_HANDLED;
  9.     }
  10.     //clr interrupt bit
  11.     __raw_readl(timer->base + TIMER_EOI);
  12.     return ret;
  13. }
        说明:
        1) 进入中断后,首先读取中断状态寄存器查看是否真的有中断,不是误触发。
        2) 如果是真的中断,执行中断回调函数。
        3) 通过读取方式清除中断标志,以便下次能够再次进入中断。      
        释放硬件定时器函数如下所示:

点击(此处)折叠或打开

  1. void gsc3280_free_hard_timer(struct gsc3280_hard_timer *h_timer)
  2. {
  3.     int i = 0;

  4.     for (i = 0; i < GSC3280_H_TIMER_NR; i++) {
  5.         if (gsc3280_timer_priv[i].h_timer == h_timer) {
  6.             if (gsc3280_timer_priv[i].in_use)
  7.                 gsc3280_timer_stop(h_timer);
  8.             free_irq(EXT_GSC3280_TIMER_IRQ, &gsc3280_timer_priv[i]);
  9.             gsc3280_timer_priv[i].h_timer = NULL;
  10.         }
  11.     }
  12. }
  13. EXPORT_SYMBOL(gsc3280_free_hard_timer);
        说明:
        1) 通过for循环和传入的形参找到释放的定时器
        2) 判断定时器是否在使用,如果使用,停止定时器。
        3) 释放定时器中断。
        4) 定时器变量清除。
        接下来讲述比较重要的函数,开启定时器gsc3280_timer_start():

点击(此处)折叠或打开

  1. static void start_hard_timer(struct gsc3280_timer *timer)
  2. {
  3.     unsigned long tlc = 0;
  4.     timer_tcr_u tcr = {.w32 = 0};
  5.     
  6.     if (timer->h_timer->value_type == 0)
  7.         tlc = (timer->rate * timer->h_timer->expires) /1000;
  8.     else
  9.         tlc = timer->rate / timer->h_timer->bps - 1;
  10.     tcr.w32 = __raw_readl(timer->base + TIMER_CR);
  11.     if (timer->h_timer->type == LOOP)
  12.         tcr.b.mode = 1;
  13.     else
  14.         tcr.b.mode = 0;
  15.     __raw_writel(tcr.w32, timer->base + TIMER_CR);
  16.     __raw_writel(tlc, timer->base + TIMER_LC);
  17.     //Enable Timer Interupt
  18.     tcr.w32 = __raw_readl(timer->base + TIMER_CR);
  19.     tcr.b.irq_mask = 0;        //not mask timer irq
  20.     tcr.b.enable = 1;    //enable timer
  21.     __raw_writel(tcr.w32, timer->base + TIMER_CR);
  22.     __raw_readl(timer->base + TIMER_EOI);
  23.     timer->in_use = true;
  24. }
  25. int gsc3280_timer_start(struct gsc3280_hard_timer *h_timer)
  26. {
  27.     int i =0;

  28.     if ((h_timer->data == 0) || (h_timer->function == NULL)) {
  29.         printk(KERN_ERR "GSC3280 Request Timer, But The Param Wrong!");
  30.         return -EINVAL;
  31.     }
  32.     for (i = 0; i < GSC3280_H_TIMER_NR; i++) {
  33.         if (gsc3280_timer_priv[i].h_timer == h_timer) {
  34.             if (!gsc3280_timer_priv[i].in_use)
  35.                 start_hard_timer(&gsc3280_timer_priv[i]);
  36.             return 0;
  37.         }
  38.     }
  39.     DBG("gsc3280_timer_start error!!!!\n");
  40.     return -EBUSY;
  41. }
  42. EXPORT_SYMBOL(gsc3280_timer_start);
        说明:
        1) 首先检查形参变量,如果变量错误,直接返回。
        2) 根据形参变量寻找相应的定时器,调用start_hard_timer()函数开启定时器。
        3) start_hard_timer()函数首先根据需要定时的类型,计算定时器的重载值。对于本定时器驱动,支持两种分辨率的定时时间。第一种为毫秒级, 该方法定时误差较大。第二种为bit每秒,即高精度定时,本文所使用的即是此种定时方法。然后操作控制寄存器,打开中断,使能定时器,让定时器开始工 作。       
        接下来讲述停止定时器函数gsc3280_timer_stop():

点击(此处)折叠或打开

  1. static void stop_hard_timer(struct gsc3280_timer *timer)
  2. {
  3.     timer_tcr_u tcr = {.w32 = 0};

  4.     tcr.w32 = __raw_readl(timer->base + TIMER_CR);
  5.     tcr.b.irq_mask = 1;        //mask timer irq
  6.     tcr.b.enable = 0;    //disable timer
  7.     __raw_writel(tcr.w32, timer->base + TIMER_CR);
  8.     timer->in_use = false;
  9. }
  10. void gsc3280_timer_stop(struct gsc3280_hard_timer *h_timer)
  11. {
  12.     int i= 0;

  13.     for (i = 0; i < GSC3280_H_TIMER_NR; i++) {
  14.         if (gsc3280_timer_priv[i].h_timer == h_timer) {
  15.             if (gsc3280_timer_priv[i].in_use) {
  16.                 stop_hard_timer(&gsc3280_timer_priv[i]);
  17.                 break;
  18.             }
  19.         }
  20.     }
  21. }
  22. EXPORT_SYMBOL(gsc3280_timer_stop);
        说明:
        1) 首先根据形参寻找相应的定时器。
        2) 找到后,判断定时器是否在使用,如果在使用,调用函数stop_hard_timer()停止定时器。
        3) stop_hard_timer()函数中,就是操作控制寄存器,屏蔽中断,禁止定时器。       
        最后介绍重新启动定时器函数gsc3280_mod_timer():

点击(此处)折叠或打开

  1. int gsc3280_mod_timer(struct gsc3280_hard_timer *h_timer)
  2. {
  3.     int i = 0;

  4.     for (i = 0; i < GSC3280_H_TIMER_NR; i++) {
  5.         if (gsc3280_timer_priv[i].h_timer == h_timer) {
  6.             if (gsc3280_timer_priv[i].in_use) {
  7.                 stop_hard_timer(&gsc3280_timer_priv[i]);
  8.                 start_hard_timer(&gsc3280_timer_priv[i]);
  9.             }
  10.         }
  11.     }
  12.     return 0;
  13. }
  14. EXPORT_SYMBOL(gsc3280_mod_timer);
        说明:
        1) 该函数首先通过形参寻找相应的定时器。
        2) 找到后,判断定时器是否在使用,如果在使用,首先停止定时器,然后再一次开启。
三、一线触摸屏协议驱动
3.1、一线触摸屏协议的基本内容
        一线触摸屏首先定义一个软定时器,该定时器每隔25ms执行一次,在该定时器的中断函数中,会判断此次是什么类型的数据传输,包括初始化一线触摸屏、控制 背光命令或者读取触摸屏位置信息命令。命令设置成功后,进入测量函数,在测量函数中,打开硬件定时器,使用位速率为9600和一线触摸屏通信,最后取得有 效数据后,如果是测量位置信息,通过input子系统将其报告给应用层,如果是控制背光,则什么也不做。
3.2、一线触摸屏系统初始化和退出函数

点击(此处)折叠或打开

  1. #if defined(CONFIG_GSC3280_1WIRE_TS)
  2. static struct platform_device ts_1wire_device = {
  3.     .name    = "ts_1wire_device",
  4.     .id        = -1,
  5. };
  6. #endif
  7. static struct platform_driver ts_1wire_device_driver = {
  8.     .probe    = ts_1wire_probe,
  9.     .remove    = __devexit_p(ts_1wire_remove),
  10.     .driver    = {
  11.         .name    = "ts_1wire_device",
  12.         .owner    = THIS_MODULE,
  13.     }
  14. };

  15. static int __init ts_1wire_init(void)
  16. {
  17.     return platform_driver_register(&ts_1wire_device_driver);
  18. }
  19. static void __exit ts_1wire_exit(void)
  20. {
  21.     platform_driver_unregister(&ts_1wire_device_driver);
  22. }
  23. module_init(ts_1wire_init);
  24. module_exit(ts_1wire_exit);

  25. MODULE_AUTHOR("Davied");
  26. MODULE_DESCRIPTION("GSC3280 one wire ts Driver");
  27. MODULE_LICENSE("GPL");
  28. MODULE_ALIAS("gsc3280 one wire ts")
        说明:
        1) 将一线触摸屏定义为平台设备,因为它不操作任何CPU内部资源,所以平台设备的定义中只有名称和id。
        2) 使用平台设备的注册和注销函数对其进行操作。
        接下来讲述平台设备的探测函数ts_1wire_probe(),程序如下:

点击(此处)折叠或打开

  1. static int ts_1wire_probe(struct platform_device *pdev)
  2. {
  3.     int ret = 0;
  4.     struct ts_1wire_t *ts = NULL;
  5.     struct input_dev *input = NULL;

  6.     DBG("############\n");
  7.     printk(KERN_INFO "ts 1wire probe start.\n");
  8.     ts = kzalloc(sizeof(struct ts_1wire_t), GFP_KERNEL);
  9.     if (!ts) {
  10.         DBG("kzalloc error\n");
  11.         return -ENOMEM;
  12.     }
  13.     input = input_allocate_device();
  14.     if (!input) {
  15.         ret = -ENOMEM;
  16.         goto err_free_mem;
  17.     }
  18.     spin_lock_init(&ts->slock);
  19.     ts->dev = &pdev->dev;
  20.     snprintf(ts->phys, sizeof(ts->phys), "%s/input0", dev_name(ts->dev));
  21.     input->name = "h3600_ts";
  22.     input->phys = ts->phys;
  23.     input->dev.parent = ts->dev;
  24.     input->id.vendor = 0x00;    //tsdev->vendor;
  25.     input->id.version = 0x00;    //tsdev->rev;
  26.     input->id.product = 0x03;    //tsdev->rev;
  27.     input->id.bustype = BUS_HOST;    //should be spi
  28.     input->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
  29.     input->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
  30.     ts->input = input;
  31.     input_set_abs_params(ts->input, ABS_X, GSC_X_MIN, GSC_X_MAX, GSC_X_FUZZ, 0);
  32.     input_set_abs_params(ts->input, ABS_Y, GSC_Y_MIN, GSC_Y_MAX, GSC_Y_FUZZ, 0);
  33.     input_set_abs_params(ts->input, ABS_PRESSURE, GSC_PRESSURE_MIN, GSC_PRESSURE_MAX, 0, 0);
  34.     ret = input_register_device(ts->input);
  35.     if (ret) {
  36.         DBG("!!!!input register device error!\n");
  37.         goto err_free_input;
  38.     }
  39.     strlcpy(ts->name, TS_1WIRE_NAME, sizeof(ts->name));
  40.     ret = gpio_request(TS_1WIRE_DATA_PIN, "TS_1WIRE_DATA_PIN");
  41.     if (ret) {
  42.         DBG("gpio request error!\n");
  43.         goto err_free_input;
  44.     }
  45.     set_pin_up();
  46.     set_pin_value(1);
  47.     set_pin_as_output();
  48.     
  49.     init_hard_timer_for_1wire(ts);
  50.     ret = gsc3280_request_hard_timer(&ts->ts_hard_timer);
  51.     if (ret) {
  52.         DBG("gsc3280 request hard timer error\n");
  53.         goto err_gpio_req;
  54.     }
  55.     ret = misc_register(&bl_misc);
  56.     if (ret != 0) {
  57.         DBG("misc_register bl_misc error!\n");
  58.         goto err_hard_timer_req;
  59.     }
  60.     ts->one_wire_status = IDLE;
  61.     init_timer(&ts_1wire_timer);
  62.     ts_1wire_timer.data = (unsigned long)ts;
  63.     ts_1wire_timer.expires = jiffies + TIMER_DELAY;
  64.     ts_1wire_timer.function = one_wire_timer_callback;
  65.     add_timer(&ts_1wire_timer);
  66.     mutex_lock(&wire1_ts_list_lock);
  67.     list_add(&ts->device_entry, &wire1_ts_list);
  68.     mutex_unlock(&wire1_ts_list_lock);
  69.     platform_set_drvdata(pdev, ts);
  70.     printk(KERN_INFO "ts 1wire probe success.\n");
  71.     DBG("############\n");
  72.     return 0;

  73. err_hard_timer_req:
  74.     gsc3280_free_hard_timer(&ts->ts_hard_timer);
  75. err_gpio_req:
  76.     gpio_free(TS_1WIRE_DATA_PIN);
  77. err_free_input:
  78.     input_free_device(input);
  79. err_free_mem:
  80.     kfree(ts);
  81.     printk(KERN_INFO "!!!!ts 1wire probe error!!!!!\n");
  82.     return ret;
  83. }
        说明:
        1) 首先申请一线触摸屏结构体内存。
        2) 然后申请input设备内存。
        3) 接下来初始化一线触摸屏结构体成员变量,包括自旋锁、IO申请等。
        4) 申请硬件定时器。将控制背光定义为混杂设备。
        5) 申请软件定时器,包括设置数据、延时时间和回调函数,然后将这个软定时器加入到系统中。
3.3、软定时器程序分析
        根据前面的一线电阻触摸屏原理分析,首先看下回调函数one_wire_timer_callback():

点击(此处)折叠或打开

  1. static void one_wire_timer_callback(unsigned long data)
  2. {
  3.     unsigned long flags = 0;
  4.     struct ts_1wire_t *ts = (struct ts_1wire_t *)data;

  5.     //mod_timer(&ts_1wire_timer, jiffies + TIMER_DELAY);
  6.     spin_lock_irqsave(&ts->slock, flags);
  7.     if (ts->lcd_type == 0) {
  8.         //DBG("REQ_INFO\n");
  9.         ts->req = REQ_INFO;
  10.     }
  11.     else if (!ts->backlight_init_success) {
  12.         //DBG("backlight_init_success\n");
  13.         ts->backlight_init_success = 1;
  14.         ts->req = BL_INIT;
  15.     }
  16.     else if (ts->backlight_req) {
  17.         //DBG("backlight_req\n");
  18.         ts->req = ts->backlight_req;
  19.         ts->backlight_req = 0;
  20.     } else {
  21.         //DBG("REQ_TS\n");
  22.         ts->req = REQ_TS;
  23.     }
  24.     spin_unlock_irqrestore(&ts->slock, flags);
  25.     start_one_wire_session(ts);
  26. }
        说明:
        1) 首先上自旋锁,防止多个CPU在执行此函数。
        2) 根据不同的类型,赋值不同的命令。获取触摸屏位置信息是默认命令。系统启动后,REQ_INFO和BL_INIT命令一般只执行一次。
        3) 释放自旋锁,开始一次会话。
        会话函数start_one_wire_session(ts)如下所示:

点击(此处)折叠或打开

  1. static void start_one_wire_session(struct ts_1wire_t *ts)
  2. {
  3.     u8 crc = 0;
  4.     unsigned long flags = 0;
  5.     
  6.     if (ts->one_wire_status != IDLE) {
  7.         DBG("one_wire_status: %d error!!!!\n", ts->one_wire_status);
  8.         return;
  9.     }
  10.     spin_lock_irqsave(&ts->slock, flags);
  11.     ts->one_wire_status = START;    //IDLE to START
  12.     set_pin_value(1);
  13.     set_pin_as_output();
  14.     crc8_init(crc);
  15.     crc8(crc, ts->req);
  16.     ts->io_data = (ts->req << 8) + crc;
  17.     ts->io_data <<= 16;
  18.     ts->io_bit_count = 1;
  19.     set_pin_as_output();
  20.     spin_unlock_irqrestore(&ts->slock, flags);
  21.     
  22.     local_irq_save(flags);
  23.     gsc3280_timer_start(&ts->ts_hard_timer);
  24.     set_pin_value(0);
  25.     local_irq_restore(flags);
  26. }
        说明:
        1) 首先判断触摸屏状态,如果是不是IDLE,错误退出。
        2) 上自旋锁,初始化触摸屏状态为开始,设置一线IO管脚状态,计算crc,组装数据。
        3) 解自旋锁,开启硬件定时器,启动传输。
3.4、硬件定时器传输数据

点击(此处)折叠或打开

  1. static void init_hard_timer_for_1wire(struct ts_1wire_t *ts)
  2. {
  3.     ts->ts_hard_timer.type = LOOP;
  4.     ts->ts_hard_timer.value_type = 1;
  5.     ts->ts_hard_timer.bps = SAMPLE_BPS;
  6.     ts->ts_hard_timer.function = ts_1wire_hardtimer_callback;
  7.     ts->ts_hard_timer.data = (unsigned long)ts;
  8. }
  9. static void ts_1wire_hardtimer_callback(unsigned long data)
  10. {
  11.     struct ts_1wire_t *ts = (struct ts_1wire_t *)data;

  12.     //DBG("ts_1wire_hardtimer_callback start\n");
  13.     ts->io_bit_count--;
  14.     switch(ts->one_wire_status) {
  15.     case START:
  16.         //DBG("START\n");
  17.         if (ts->io_bit_count == 0) {
  18.             ts->io_bit_count = 16;
  19.             ts->one_wire_status = REQUEST;
  20.         }
  21.         break;
  22.     case REQUEST:
  23.         //Send a bit
  24.         //DBG("REQUEST\n");
  25.         set_pin_value(ts->io_data & (1U << 31));
  26.         ts->io_data <<= 1;
  27.         if (ts->io_bit_count == 0) {
  28.             ts->io_bit_count = 2;
  29.             ts->one_wire_status = WAITING;
  30.         }
  31.         break;
  32.     case WAITING:
  33.         //DBG("WAITING\n");
  34.         if (ts->io_bit_count == 0) {
  35.             ts->io_bit_count = 32;
  36.             ts->one_wire_status = RESPONSE;
  37.         }
  38.         else if (ts->io_bit_count == 1) {
  39.             set_pin_as_input();
  40.             set_pin_value(1);
  41.         }
  42.         break;
  43.     case RESPONSE:
  44.         //Get a bit
  45.         //DBG("RESPONSE\n");
  46.         ts->io_data = (ts->io_data << 1) | get_pin_value();
  47.         if (ts->io_bit_count == 0) {
  48.             ts->io_bit_count = 2;
  49.             ts->one_wire_status = STOPING;
  50.             set_pin_value(1);
  51.             set_pin_as_output();
  52.             one_wire_session_complete(ts);
  53.         }
  54.         break;
  55.     case STOPING:
  56.         //DBG("STOPING\n");
  57.         if (ts->io_bit_count == 0) {
  58.             ts->one_wire_status = IDLE;
  59.             gsc3280_timer_stop(&ts->ts_hard_timer);
  60.             mod_timer(&ts_1wire_timer, jiffies + TIMER_DELAY);
  61.         }
  62.         break;
  63.     default:
  64.         //DBG("default\n");
  65.         gsc3280_timer_stop(&ts->ts_hard_timer);
  66.         mod_timer(&ts_1wire_timer, jiffies + TIMER_DELAY);
  67.         break;
  68.     }
  69. }
        说明:
        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);函数如下所示:

点击(此处)折叠或打开

  1. static inline void notify_info_data(struct ts_1wire_t *ts, unsigned char lcd_type,
  2.                                     unsigned char ver_year, unsigned char week)
  3. {
  4.     if (lcd_type != 0xFF) {
  5.         ts->lcd_type = lcd_type;
  6.         //firmware_ver = ver_year * 100 + week;
  7.     }
  8. }
  9. static inline void notify_bl_data(struct ts_1wire_t *ts, u8 a, u8 b, u8 c)
  10. {
  11.     ts->bl_ready = 1;
  12.     wake_up_interruptible(&bl_waitq);
  13. }
  14. static void one_wire_session_complete(struct ts_1wire_t *ts)
  15. {
  16.     u8 crc = 0;
  17.     const unsigned char *p = (const u8*)&(ts->io_data);

  18.     crc8_init(crc);
  19.     crc8(crc, p[3]);
  20.     crc8(crc, p[2]);
  21.     crc8(crc, p[1]);
  22.     if (crc != p[0]) {
  23.         DBG("one_wire_session_complete crc error\n");
  24.         return;
  25.     }
  26.     switch(ts->req) {
  27.     case REQ_TS:
  28.         ts->x = ((p[3] >> 4U) << 8U) + p[2];
  29.         ts->y = ((p[3] & 0xFU) << 8U) + p[1];
  30.         ts->z = (ts->x != 0xFFFU) && (ts->y != 0xFFFU);
  31.         notify_ts_data(ts);
  32.         break;
  33.     case REQ_INFO:
  34.         notify_info_data(ts, p[3], p[2], p[1]);
  35.         break;
  36.     default:
  37.         notify_bl_data(ts, p[3], p[2], p[1]);
  38.         break;
  39.     }
  40. }
        说明:
        1) 函数首先进行crc校验,如果crc错误,则直接退出。
        2) 如果校验正确,根据不同的命令,进入不同的报告类型。其中REQ_TS和REQ_INFO不需要向应用层报告数据。notify_info_data()函数通过修改ts->lcd_type变量来保证系统起来以后,一线触摸屏执行REQ_INFO命令只有一次。
        notify_ts_data(ts);函数如下所示:

点击(此处)折叠或打开

  1. static void gsc3280_report_event(struct ts_1wire_t *ts, u32 z)
  2. {
  3. #ifdef CONFIG_GSC3280_POS_PRINT
  4.     printk(KERN_INFO "x = %d\n", ts->x);
  5.     printk(KERN_INFO "y = %d\n", ts->y);
  6.     printk(KERN_INFO "z = %d\n", z);
  7. #endif

  8.     input_report_abs(ts->input, ABS_PRESSURE, z);
  9.     input_report_abs(ts->input, ABS_X, ts->x);
  10.     input_report_abs(ts->input, ABS_Y, ts->y);
  11.     if (z > 0)
  12.         input_report_key(ts->input, BTN_TOUCH, 1);
  13.     else
  14.         input_report_key(ts->input, BTN_TOUCH, 0);
  15.     input_sync(ts->input);
  16. }
  17. static inline void notify_ts_data(struct ts_1wire_t *ts)
  18. {
  19.     if (ts->z == 1) {
  20.         ts->x = ((ts->x -285) * gXres) / (3944 - 285);
  21.         ts->y = ((3936 - ts->y) * gYres) / (3936 - 102);
  22.         if ((ts->x > 0) && (ts->x < gXres) && (ts->y > 0) && (ts->y < gYres)) {
  23.             if (ts->flg == 0) {
  24.                 ts->flg = 1;
  25.                 gsc3280_report_event(ts, 0);
  26.             }
  27.             gsc3280_report_event(ts, ts->z);
  28.         }
  29.     } else if (ts->z == 0) {
  30.         if (ts->flg == 1) {
  31.             ts->flg = 0;
  32.             gsc3280_report_event(ts, 0);
  33.         }
  34.     }
  35. }
        说明:
        1) 首先根据公式计算触摸点的绝对坐标,如果该坐标值在正确的范围内,则上报该位置信息。
        2) 由于是电阻触摸屏,其电阻值和位置是线性的,公式为:(电压 - 0) / (总电压) = (分辨个数 - 0) / (总个数)。即分辨个数 = 总个数 * (电压) / (总电压)。通过测量触摸屏四个脚点的电压值,分别得到X和Y方向上的最大总电压,然后在实际测量中,根据测得的电压值,带入公式中的电压项,分别计算出 X和Y方向的绝对坐标。本文使用的触摸屏分辨率为800*480,所以X方向的总个数为800,Y方向的为480。
        3) 位置报告函数将在第三篇文章----input核心中讲述。
        4) 本文设计的程序可以通过make menuconfig命令输入分辨率,Kconfig程序如下:

点击(此处)折叠或打开

  1. config INPUT_TSDEV_SCREEN_X
  2.     int "Horizontal screen resolution"
  3.     depends on INPUT_TOUCHSCREEN
  4.     default "800"
  5.     help
  6.      If you're using a digitizer, this is the X window screen resolution you are
  7.      using to correctly scale the data. If you're not using a digitizer, this value
  8.      is ignored.

  9. config INPUT_TSDEV_SCREEN_Y
  10.     int "Vertical screen resolution"
  11.     depends on INPUT_TOUCHSCREEN
  12.     default "480"
  13.     help
  14.      If you're using a digitizer, this is the Y window screen resolution you are
  15.      using to correctly scale the data. If you're not using a digitizer, this value
  16.      is ignored.
        在.c文件中通过下列程序获得输入的值:

点击(此处)折叠或打开

  1. #ifndef CONFIG_INPUT_TSDEV_SCREEN_X
  2. #define CONFIG_INPUT_TSDEV_SCREEN_X    800
  3. #endif
  4. #ifndef CONFIG_INPUT_TSDEV_SCREEN_Y
  5. #define CONFIG_INPUT_TSDEV_SCREEN_Y    480
  6. #endif

  7. static const int gXres = CONFIG_INPUT_TSDEV_SCREEN_X;
  8. static const int gYres = CONFIG_INPUT_TSDEV_SCREEN_Y;
四、控制背光驱动和应用层程序
4.1、驱动程序
        在系统探测函数中,将控制背光驱动定义为混杂设备并进行注册,定义混杂设备的源码如下:

点击(此处)折叠或打开

  1. static struct file_operations bl_fops = {
  2.     owner    : THIS_MODULE,
  3.     write    : bl_write,
  4.     open    : bl_open,
  5. };
  6. static struct miscdevice bl_misc = {
  7.     .minor    = MISC_DYNAMIC_MINOR,
  8.     .name    = BACKLIGHT_DEVICE_NAME,
  9.     .fops    = &bl_fops,
  10. };
        说明:
        1) 混杂设备的文件操作函数中有打开和写,首先看下打开函数:

点击(此处)折叠或打开

  1. static int bl_open(struct inode *inode, struct file *filp)
  2. {
  3.     int status = -ENXIO;
  4.     struct ts_1wire_t *ts = NULL;

  5.     mutex_lock(&wire1_ts_list_lock);
  6.     list_for_each_entry(ts, &wire1_ts_list, device_entry) {
  7.         if(strcmp(ts->name, TS_1WIRE_NAME) == 0) {
  8.             status = 0;
  9.             break;
  10.         }
  11.     }
  12.     mutex_unlock(&wire1_ts_list_lock);
  13.     if (status == 0) {
  14.         filp->private_data = ts;
  15.     } else {
  16.         return status;
  17.     }
  18.     return nonseekable_open(inode, filp);
  19. }
        说明:
        1) 首先通过本地全局链表和锁获得在探测函数中定义的设备结构体内存指针。
        2) 然后调用无定位打开函数返回。
        写数据即发送命令控制背光,程序如下:

点击(此处)折叠或打开

  1. static ssize_t bl_write(struct file *filp, const char __user *buffer, size_t count, loff_t *ppos)
  2. {
  3.     int ret = 0;
  4.     u8 bl_data = 0;
  5.     struct ts_1wire_t *ts = filp->private_data;

  6.     DBG("bl_write start\n");
  7.     if (get_user(bl_data, (u8 __user *)buffer)) {
  8.         printk(KERN_INFO "get_user error!\n");
  9.         return -EFAULT;
  10.     }
  11.     if (bl_data > 127) {
  12.         bl_data = 127;
  13.     }
  14.     ts->bl_ready = 0;
  15.     ts->backlight_req = bl_data + 0x80U;
  16.     ret = wait_event_interruptible_timeout(bl_waitq, ts->bl_ready, HZ);
  17.     if (ret < 0) {
  18.         printk(KERN_INFO "wait_event_interruptible_timeout error!\n");
  19.         return ret;
  20.     } else if (ret == 0) {
  21.         printk(KERN_INFO "time out error!\n");
  22.         return -ETIMEDOUT;
  23.     } else {
  24.         return count;
  25.     }
  26. }
        说明:
        1) 首先从应用层获取写入背光值。
        2) 组合数据,形成背光控制命令。
        3) 在会话函数中由于ts->backlight_req不为0,即可发送背光控制命令。
4.2、应用层程序
        通过滑动变阻器来控制背光。根据协议,背光总共可以分为0~127个档位,滑动变阻器的测量值范围为2010~4010,也就是说,背光每变化一个档位, 滑动变阻器电阻值变化:(4010 - 2010) / 128 = 16。那么我们给背光发送的档位值即为:(x - 2010) / 16。应用层程序如下:

点击(此处)折叠或打开

  1. #include <fcntl.h>
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. #include <sys/ioctl.h>
  5. #include <linux/ioctl.h>


  6. #define ADC_DEV_IOC_MAGIC        'a'
  7. #define ADC_DEV_IOC_MAXNR        2
  8. #define ADC_DEV_CON_PBAT        _IOR(ADC_DEV_IOC_MAGIC, 0, int)
  9. #define ADC_DEV_CON_CHX            _IOWR(ADC_DEV_IOC_MAGIC, 1, int)

  10. int main(int argc, char **argv)
  11. {
  12.     unsigned int idCmd = 0;
  13.     //unsigned char buffer[4] = {0};
  14.     unsigned char bl_data = 0;
  15.     int fd1 = 0, fd2 = 0, ret = 0, data = 0;
  16.     
  17.     //以阻塞方式打开设备文件,非阻塞时flags=O_NONBLOCK
  18.     fd1 = open("/dev/adc0", 0);
  19.     if (fd1 < 0) {
  20.         printf("Open ADC Device Faild!\n");
  21.         exit(1);
  22.     }
  23.     fd2 = open("/dev/backlight-1wire", O_RDWR);
  24.     if (fd2 < 0) {
  25.         printf("Open wire1 backlight Device Faild!\n");
  26.         exit(1);
  27.     }
  28.     while(1) {
  29.         data = 1;
  30.         idCmd = ADC_DEV_CON_CHX;
  31.         ret = ioctl(fd1, idCmd, &data);
  32.         if (ret != 0) {
  33.             printf("Read ADC Device Faild!\n");
  34.             break;
  35.         }
  36.         //printf("data = %d\n", data);
  37.         bl_data = (data - 2010) /16;
  38.         //printf("bl_data = %d\n", bl_data);
  39.         ret = write(fd2, &bl_data, 1);
  40.         if (ret < 0) {
  41.             printf("wire1 backlight ret = %d\n", ret);
  42.             //break;
  43.         }
  44.         //sleep(1);
  45.         for (ret = 0; ret < 655360; ret++)
  46.             ;;;;
  47.     }
  48.     close(fd1);
  49.     close(fd2);
  50.     return 0;
  51. }
          编译后,将程序设为后台执行,系统启动后,任何时间滑动变阻器,一线触摸屏的背光都会根据电阻的位置而做相应的变化。
阅读(1140) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~