AM335x(TQ335x)学习笔记——触摸屏驱动编写

本文涉及的产品
图片翻译,图片翻译 100张
语种识别,语种识别 100万字符
文本翻译,文本翻译 100万字符
简介: <p style="margin-top: 0px; margin-bottom: 0px; padding-top: 0px; padding-bottom: 0px; font-family: Arial; font-size: 14px; line-height: 26px;">前面几篇文章已经通过配置DTS的方式完成了多个驱动的移植,接下来我们解决TQ335x的触摸驱动问题。由于种种原

前面几篇文章已经通过配置DTS的方式完成了多个驱动的移植,接下来我们解决TQ335x的触摸驱动问题。由于种种原因,TQ335x的触摸屏驱动是以模块方式提供的,且Linux官方内核中也没有带该触摸屏的驱动源码,单纯的配置DTS是无法完成TQ335x的触摸驱动移植工作的,因此,本文参考内核中原有的pixcir_i2c_ts驱动编写TQ335x的触摸屏(TN92)驱动。

在之前移植TQ210时,我已经编写过TQ210的触摸屏驱动,我的TQ335x还是使用的TQ210的屏,因此,难度不是很大。这里需要说明一点,在TQ210驱动移植时对多点触摸协议的理解还不够深入,当时编写的驱动单点触摸是可以正常使用的,但是多点触摸不对(这次编写TQ335x的触摸驱动是才意识到的)。但是编写的TQ210驱动多点触摸实际上使用的多点触摸的A协议,但是用错了一些地方,本文基于TQ335x的重新编写的触摸驱动是按照多点触摸B协议编写,使用tslib测试正常,文章末尾有效果图。

TN92触摸屏使用的触控芯片是GT811,下面我们来分析下触摸屏驱动的编写。

(1) 查看原理图

从触摸屏原理图中可以看到,GT811与开发板相连的引脚有四条,分别是SDA、SDL、INT和RESET。其中,I2C_SDA和I2C_SDL是连接到AM335x的I2C1端口上的,用来与SoC通信;INT引脚是连接在GPIO1的27号引脚上的,在检测到触摸时GT811通过该引脚向SoC发起中断请求;RESET引脚接到SoC的GPIO1的26号引脚上的,是用来接收SoC复位操作,对于本文,SoC就是AM335x。


其中,GT811与SoC的管脚连接信息可以从底板原理图中找到,SDA和SCL的我就不往外贴了,INT和RESET的连接关系如下:


YP连接到了GT811的RESET脚上,然后通过短路帽与GPIO1_26链接,YM连接到GT811的INT脚上,通过短路帽与GPIO1_27链接,因此,需要将GPIO1_27配置为终端输入引脚,GPIO1_26配置为输出引脚。此外,查看GT811的芯片手册可获得INT和RESET的操作信息:

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. 1. GT811检测到触摸时会拉低中断引脚,因此,GPIO1_27需要配置为下降沿触发。  
  2. 2. GT811的RESET脚为低电平有效,因此,上电时需要拉低RESET引脚。  
  3. 3. GT811的RESET引脚自带上拉,因此,使用GPIO1_26将GT811的RESET拉低复位后切换为悬浮输入太即可。  
了解这些信息后我们就可以开始分析驱动结构了。

(2) DTS配置Platform信息

通过前面的分析,我们知道需要配置AM335x的四条引脚才能使GT811正常工作。其中,GT811通过I2C接口连接到AM335x的I2C1上,因此,需要配置AM335x的I2C1的两条引脚为I2C功能;GPIO1_27需要配置为中断输入、下降沿触发,中断号也可以确定下来了,就是GPIO1的27号角(内核能将引脚转换为中断号);最后就是RESET脚,驱动初始化GT811时需要操作RESET引脚,且GT811为拉低复位,故可将GPIO1的26角设为输出电平状态。通过前面几篇文章的学习,我们知道DTS可以配置pinmux,然后分析内核自带的DTS文件可以,DTS也可以将连接信息传递给内核。经过分析及参考,我最TQ335x.dts文件做了如下修改:

Step1. 检查I2C引脚的pinmux配置

查找i2c1可以找到i2c1节点,该节点的pinctrl-0只想的phandler对i2c1的两个引脚进行了相关的配置,因此,不需任何修改。

Step2. 配置INT和RESET引脚

在am33xx_pinmux节点内添加引脚配置信息,具体内容如下:

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. gt811_ts_pins: gt811_ts_pins {  
  2.         pinctrl-single,pins = <  
  3.         0x68 (PIN_INPUT_PULLUP | MUX_MODE7)  
  4.         0x6c (PIN_INPUT_PULLUP | MUX_MODE7)  
  5.     >;  
  6. };  
Step3. 在i2c1节点内添加GT811设备信息

GT811的设备节点内需要提供以下信息,i2c设备地址、pinmux配置、中断信息、屏幕大小等,具体如下(这里就不细说了,有什么不清楚的可以留言讨论)。

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. gt811_ts@5d {  
  2.     compatible = "gt811,gt811_ts";  
  3.     pinctrl-names = "default";  
  4.     pinctrl-0 = <&gt811_ts_pins>;  
  5.     reg = <0x5d>;  
  6.     interrupt-parent = <&gpio1>;  
  7.     interrupts = <27 2>;  
  8.   
  9.     gpios = <&gpio1 26 0>;  
  10.   
  11.     touchscreen-size-x = <800>;  
  12.     touchscreen-size-y = <480>;  
  13.   
  14.     touchscreen-swap = <1>;  
  15.     touchscreen-revert-x = <1>;  
  16.     touchscreen-revert-y = <1>;  
  17. };  
至此,DTS的配置工作就完成了,下面就是GT811的驱动编写。
(3)驱动编写

驱动编写根之前TQ210相比,没有多少变化,都是采用的新式I2C设备驱动架构,重要的区别在于支持了多点触摸,驱动的详细分析我就不多说了,具体可以参考韦东山老师的视频教程(绝对物有所值)。下面是GT811的多点触摸驱动源码。从GT811设备节点中获取坐标信息的部分我直接贴出来了,完整的代码还是请到资源里下载,还是有点贵哈,不过这个驱动可以直接拿去使用了,不需要任何修改。下面是不完整的代码,可以打印出触摸点坐标,该代码已经把本驱动的核心的部分都写出来了。其实很多朋友不需要下载源码就可以补充完善这个驱动了,而且我真心的希望阅读本文的朋友不需要下载源码就能自己写出触摸驱动。

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. #include <linux/module.h>  
  2. #include <linux/i2c.h>  
  3. #include <linux/platform_device.h>  
  4. #include <linux/gpio.h>  
  5. #include <linux/of.h>  
  6. #include <linux/of_platform.h>  
  7. #include <linux/of_gpio.h>  
  8. #include <linux/input.h>  
  9. #include <linux/input/mt.h>  
  10. #include <linux/interrupt.h>  
  11. #include <linux/delay.h>  
  12.   
  13. struct gt811_ts_platdata  
  14. {  
  15.     u32 size_x;  
  16.     u32 size_y;  
  17.     u32 size_p;  
  18.     u32 swap;  
  19.     u32 revert_x;  
  20.     u32 revert_y;  
  21.     u32 reset_pin;  
  22.     u32 interrupt_pin;  
  23.     u32 ponits_max;  
  24.     struct i2c_client *client;  
  25.     struct input_dev *input;  
  26.     struct work_struct work;  
  27. };  
  28.   
  29. static const struct of_device_id gt811_ts_of_match[] = {  
  30.     { .compatible = "gt811,gt811_ts", .data = NULL },  
  31.     { }  
  32. };  
  33.   
  34. static int i2c_write_bytes(struct i2c_client *client, uint8_t *data, int len){  
  35.     struct i2c_msg msg;  
  36.   
  37.     msg.flags=!I2C_M_RD;  
  38.     msg.addr=client->addr;  
  39.     msg.len=len;  
  40.     msg.buf=data;  
  41.   
  42.     return i2c_transfer(client->adapter,&msg, 1);  
  43. }  
  44.   
  45. static int i2c_read_bytes(struct i2c_client *client, uint8_t *buf, int len){  
  46.     struct i2c_msg msgs[2];  
  47.   
  48.     msgs[0].flags=!I2C_M_RD;  
  49.     msgs[0].addr=client->addr;  
  50.     msgs[0].len=2;  
  51.     msgs[0].buf=&buf[0];  
  52.   
  53.     msgs[1].flags=I2C_M_RD;  
  54.     msgs[1].addr=client->addr;  
  55.     msgs[1].len=len-2;  
  56.     msgs[1].buf=&buf[2];  
  57.   
  58.     return i2c_transfer(client->adapter,msgs, 2);  
  59. }  
  60. static void gt811_ts_handler(struct work_struct *work)  
  61. {  
  62.     struct gt811_ts_platdata *pdata = container_of(work, struct gt811_ts_platdata, work);  
  63.     struct device *dev = &pdata->client->dev;  
  64.     uint8_t buffer[36] = {0x07, 0x21, 0};  
  65.     uint8_t count, index, flags, position;  
  66.     int x, y;  
  67.   
  68.     buffer[0] = 0x0f;  
  69.     buffer[1] = 0xff;  
  70.     if (i2c_write_bytes(pdata->client,buffer,2) < 0) {  
  71.         dev_err(dev, "Failed to write wakeup message.\n");  
  72.         goto reenable_irq;  
  73.     }  
  74.   
  75.     buffer[0] = 0x07;  
  76.     buffer[1] = 0x21;  
  77.     if (i2c_read_bytes(pdata->client, buffer, sizeof(buffer)) < 0) {  
  78.         dev_err(dev, "Failed to read touch message.\n");  
  79.         goto reenable_irq;  
  80.     }  
  81.   
  82.     buffer[0] = 0x80;  
  83.     buffer[1] = 0x00;  
  84.     if (i2c_write_bytes(pdata->client, buffer, 2) < 0) {  
  85.         dev_err(dev, "Failed to write sleep message.\n");  
  86.         goto reenable_irq;  
  87.     }  
  88.   
  89.     buffer[25] = buffer[19];  
  90.     buffer[19] = 0;  
  91.   
  92.     flags = buffer[2]&0x1f;  
  93.   
  94.     while (flags) {  
  95.         if (!(flags&0x1)) {  
  96.             continue;  
  97.         }  
  98.   
  99.         if (index < 3) {  
  100.             position = 4 + index * 5;  
  101.         }  
  102.         else{  
  103.             position = 25 + (index - 3) * 5;  
  104.         }  
  105.   
  106.         x = (buffer[position] << 8) | buffer[position + 1];  
  107.         y = (buffer[position + 2] << 8) | buffer[position + 3];  
  108.   
  109.         if(pdata->swap) {  
  110.             swap(x, y);  
  111.         }  
  112.         if(pdata->revert_x){  
  113.             x = pdata->size_x - x;  
  114.         }  
  115.         if(pdata->revert_y){  
  116.             y = pdata->size_y - y;  
  117.         }  
  118.   
  119.         printk("point:(x:%03d, y:%03d)\n", x, y);  
  120.     }  
  121.   
  122.     // 组织检测出来的触摸点信息上报到输入子系统节点即可  
  123.   
  124. reenable_irq:  
  125.     enable_irq(pdata->client->irq);  
  126. }  
  127.   
  128. static irqreturn_t gt811_ts_isr(int irq, void *dev_id)  
  129. {  
  130.     struct gt811_ts_platdata* pdata = (struct gt811_ts_platdata*)dev_id;  
  131.   
  132.     disable_irq_nosync(pdata->client->irq);  
  133.     schedule_work(&pdata->work);  
  134.   
  135.     return IRQ_HANDLED;  
  136. }  
  137.   
  138. static int gt811_ts_initilize(struct i2c_client *client)  
  139. {  
  140.     struct device *dev = &client->dev;  
  141.     struct gt811_ts_platdata *pdata = (struct gt811_ts_platdata*)i2c_get_clientdata(client);  
  142.     int status = 0, count = 0;  
  143.     uint8_t version[4] = {0x7, 0x17, 0};  
  144.     uint8_t config[] = {  
  145.         0x06,0xA2,  
  146.         0x12,0x10,0x0E,0x0C,0x0A,0x08,0x06,0x04,0x02,0x00,0xE2,0x53,0xD2,0x53,0xC2,0x53,  
  147.         0xB2,0x53,0xA2,0x53,0x92,0x53,0x82,0x53,0x72,0x53,0x62,0x53,0x52,0x53,0x42,0x53,  
  148.         0x32,0x53,0x22,0x53,0x12,0x53,0x02,0x53,0xF2,0x53,0x0F,0x13,0x40,0x40,0x40,0x10,  
  149.         0x10,0x10,0x0F,0x0F,0x0A,0x35,0x25,0x0C,0x03,0x00,0x05,0x20,0x03,0xE0,0x01,0x00,  
  150.         0x00,0x34,0x2C,0x36,0x2E,0x00,0x00,0x03,0x19,0x03,0x08,0x00,0x00,0x00,0x00,0x00,  
  151.         0x14,0x10,0xEC,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0D,0x40,  
  152.         0x30,0x3C,0x28,0x00,0x00,0x00,0x00,0xC0,0x12,0x01  
  153.     };  
  154.   
  155.     config[62] = 480 >> 8;  
  156.     config[61] = 480 & 0xff;  
  157.     config[64] = 800 >> 8;  
  158.     config[63] = 800 & 0xff;  
  159.   
  160.     if (!gpio_is_valid(pdata->reset_pin)) {  
  161.         dev_err(dev, "The reset pin number is invalid.\n");  
  162.         return -EINVAL;  
  163.     }  
  164.   
  165.     count = 3;  
  166.     while (count--) {  
  167.         gpio_direction_output(pdata->reset_pin, 0);  
  168.         msleep(10);  
  169.         gpio_direction_output(pdata->reset_pin, 1);  
  170.         msleep(100);  
  171.   
  172.         if (i2c_read_bytes(client, version, sizeof(version)) < 0) {  
  173.             dev_err(dev, "Failed to get the version of GT811, try again...\n");  
  174.             status = -ENODEV;  
  175.         }  
  176.         else {  
  177.             dev_info(dev, "Gt811 detected, version(%04x)...\n", (version[2]<<8)|version[3]);  
  178.             status = 0;  
  179.             break;  
  180.         }  
  181.     }  
  182.   
  183.     if (status) {  
  184.         return status;  
  185.     }  
  186.   
  187.     count = 3;  
  188.     while (count--) {  
  189.         if (i2c_write_bytes(client, config, sizeof(config)) < 0) {  
  190.             dev_err(dev, "Failed to configure the GT811, try again...\n");  
  191.             status = -EINVAL;  
  192.         }  
  193.         else {  
  194.             dev_info(dev, "Gt811 configue succeed\n");  
  195.             status = 0;  
  196.             break;  
  197.         }  
  198.     }  
  199.   
  200.     return status;  
  201. }  
  202.   
  203. static struct gt811_ts_platdata *gt811_ts_parse_devtree(struct i2c_client *client)  
  204. {  
  205.     struct device *dev = &client->dev;  
  206.     struct device_node *node;  
  207.     struct gt811_ts_platdata *pdata;  
  208.     enum of_gpio_flags flags;  
  209.   
  210.     node = dev->of_node;  
  211.     if (!node) {  
  212.         dev_err(dev, "The of_node is NULL.\n");  
  213.         return ERR_PTR(-ENODEV);  
  214.     }  
  215.   
  216.     pdata = devm_kzalloc(dev, sizeof(struct device_node), GFP_KERNEL);  
  217.     if (!pdata) {  
  218.         dev_err(dev, "No enough memory left.\n");  
  219.         return ERR_PTR(-ENOMEM);  
  220.     }  
  221.   
  222.     pdata->reset_pin = of_get_gpio_flags(node, 0, &flags);  
  223.     if (pdata->reset_pin < 0) {  
  224.         dev_err(dev, "Get RST pin failed!\n");  
  225.         return ERR_PTR(-EINVAL);  
  226.     }  
  227.   
  228.     if (of_property_read_u32(node, "touchscreen-size-x", &pdata->size_x )) {  
  229.         dev_err(dev, "Failed to get the touch screen x size.\n");  
  230.         return ERR_PTR(-EINVAL);  
  231.     }  
  232.   
  233.     if (of_property_read_u32(node, "touchscreen-size-y", &pdata->size_y)) {  
  234.         dev_err(dev, "Failed to get the touch screen y size.\n");  
  235.         return ERR_PTR(-EINVAL);  
  236.     }  
  237.   
  238.     if (of_property_read_u32(node, "touchscreen-size-p", &pdata->size_p)) {  
  239.         pdata->size_p = 255;  
  240.     }  
  241.   
  242.     if (of_property_read_u32(node, "touchscreen-swap", &pdata->swap)) {  
  243.         pdata->swap = 1;  
  244.     }  
  245.   
  246.     if (of_property_read_u32(node, "touchscreen-revert-x", &pdata->revert_x)) {  
  247.         pdata->revert_x = 1;  
  248.     }  
  249.   
  250.     if (of_property_read_u32(node, "touchscreen-revert-x", &pdata->revert_y)) {  
  251.         pdata->revert_y = 1;  
  252.     }  
  253.   
  254.     return pdata;  
  255. }  
  256.   
  257. static int gt811_ts_probe(struct i2c_client *client, const struct i2c_device_id *id)  
  258. {  
  259.     struct device *dev = &client->dev;  
  260.     struct gt811_ts_platdata *pdata = dev_get_platdata(dev);  
  261.     struct input_dev *input;  
  262.     int error = 0;  
  263.   
  264.     if (!of_match_device(of_match_ptr(gt811_ts_of_match), dev)) {  
  265.         dev_err(dev, "Failed to match.\n");  
  266.         return -EINVAL;  
  267.     }  
  268.   
  269.     if (!pdata) {  
  270.         pdata = gt811_ts_parse_devtree(client);  
  271.         if (IS_ERR(pdata)) {  
  272.             dev_err(dev, "Get device data from device tree failed!\n");  
  273.             error = -EINVAL;  
  274.             goto failed_exit;  
  275.         }  
  276.     }  
  277.   
  278.     pdata->client = client;  
  279.   
  280.     i2c_set_clientdata(client, pdata);  
  281.   
  282.     input = devm_input_allocate_device(dev);  
  283.     if (!input) {  
  284.         dev_err(dev, "Failed to allocate input device\n");  
  285.         error = -ENOMEM;  
  286.         goto pdata_free;  
  287.     }  
  288.   
  289.     pdata->input = input;  
  290.   
  291.     input->name = client->name;  
  292.     input->id.bustype = BUS_I2C;  
  293.     input->id.product = 0xBEEF;  
  294.     input->id.vendor  =0xDEAD;  
  295.     input->dev.parent = &client->dev;  
  296.   
  297.     __set_bit(EV_KEY, input->evbit);  
  298.     __set_bit(EV_ABS, input->evbit);  
  299.     __set_bit(BTN_TOUCH, input->keybit);  
  300.     input_set_abs_params(input, ABS_X, 0, pdata->size_x, 0, 0);  
  301.     input_set_abs_params(input, ABS_Y, 0, pdata->size_y, 0, 0);  
  302.     input_set_abs_params(input, ABS_MT_POSITION_X, 0, pdata->size_x, 0, 0);  
  303.     input_set_abs_params(input, ABS_MT_POSITION_Y, 0, pdata->size_y, 0, 0);  
  304.   
  305.     error = input_mt_init_slots(input, 5, INPUT_MT_DIRECT | INPUT_MT_DROP_UNUSED);  
  306.     if (error) {  
  307.         dev_err(dev, "Failed to initialize the multi-touch slots.\n");  
  308.         goto input_free;  
  309.     }  
  310.   
  311.     input_set_drvdata(input, pdata);  
  312.   
  313.     error = input_register_device(input);  
  314.     if (error) {  
  315.         dev_err(dev, "Register input device failed!\n");  
  316.         goto input_free;  
  317.     }  
  318.   
  319.     if (gt811_ts_initilize(client)) {  
  320.         dev_err(dev, "Failed to initialize GT811.\n");  
  321.     }  
  322.   
  323.     INIT_WORK(&pdata->work, gt811_ts_handler);  
  324.   
  325.     error = devm_request_any_context_irq(dev, client->irq, gt811_ts_isr,  
  326.                       IRQF_TRIGGER_FALLING, client->name, pdata);  
  327.     if (error) {  
  328.         dev_err(dev, "Failed to request irq(number:%d)\n", client->irq);  
  329.         goto input_free;  
  330.     }  
  331.   
  332.     return 0;  
  333.   
  334. input_free:  
  335.     devm_kfree(dev, input);  
  336. pdata_free:  
  337.     devm_kfree(dev, pdata);  
  338. failed_exit:  
  339.     return error;  
  340. }  
  341.   
  342. static int gt811_ts_remove(struct i2c_client *client)  
  343. {  
  344.     struct gt811_ts_platdata *pdata = (struct gt811_ts_platdata*)i2c_get_clientdata(client);  
  345.   
  346.     devm_free_irq(&client->dev, client->irq, i2c_get_clientdata(client));  
  347.   
  348.     input_unregister_device(pdata->input);  
  349.   
  350.     devm_kfree(&client->dev, pdata);  
  351.     return 0;  
  352. }  
  353.   
  354. static const struct i2c_device_id gt811_ts_id[] = {  
  355.     { "gt811_ts", 0 },  
  356.     { }  
  357. };  
  358.   
  359. static struct i2c_driver gt811_ts_driver = {  
  360.     .driver = {  
  361.         .owner  = THIS_MODULE,  
  362.         .name   = "gt811_ts",  
  363.         .of_match_table = of_match_ptr(gt811_ts_of_match),  
  364.     },  
  365.     .probe      = gt811_ts_probe,  
  366.     .remove     = gt811_ts_remove,  
  367.     .id_table   = gt811_ts_id,  
  368. };  
  369.   
  370. module_i2c_driver(gt811_ts_driver);  
  371.   
  372. MODULE_AUTHOR("girlkoo <nightmeng@gmail.com>");  
  373. MODULE_DESCRIPTION("Gt811 I2C Touchscreen Driver");  
  374. MODULE_LICENSE("GPL");  
(4) 使用tslib工具测试

tslib的编译方法请参考本博客的另一片文章,链接如下:

S5PV210(TQ210)学习笔记——触摸屏驱动编写

本文就不再重复tslib的配置方法。

(5)效果展示

(6) 完整驱动代码

请到本人的资源中下载TQ335x的触摸屏驱动源码,链接如下:

http://download.csdn.net/download/girlkoo/8202177

相关文章
|
7月前
嵌入式开发板串口驱动框架
嵌入式开发板串口驱动框架
74 0
|
7月前
|
Java Linux Android开发
嵌入式Android系统耳机驱动基本知识
嵌入式Android系统耳机驱动基本知识
86 0
LabVIEW控制Arduino驱动1602液晶显示屏(基础篇—10)
本篇博文将利用LIAT中的LCD显示函数库,驱动1602液晶显示屏。
|
Linux 芯片
LED驱动程序--可拓展的LED驱动程序
LED驱动程序--可拓展的LED驱动程序
124 0
|
传感器 芯片 Python
【HaaS Python硬件积木】ULN2003A步进电机驱动
【HaaS Python硬件积木】ULN2003A步进电机驱动
251 0
|
传感器 开发框架 开发者
【HaaS Python硬件积木】4路触摸电容模块-TTP224
【HaaS Python硬件积木】4路触摸电容模块-TTP224
186 0
|
Linux 开发工具 芯片
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(三十四)查询方式的按键驱动程序_编写框架
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(三十四)查询方式的按键驱动程序_编写框架
182 0
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(三十四)查询方式的按键驱动程序_编写框架
|
Ubuntu Linux API
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(十五)输入系统应用编程(上)
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(十五)输入系统应用编程
300 0
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(十五)输入系统应用编程(上)
|
Linux 芯片
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(十五)输入系统应用编程(中)
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(十五)输入系统应用编程
255 0
嵌入式linux/鸿蒙开发板(IMX6ULL)开发(十五)输入系统应用编程(中)