0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

【i.MX6ULL】驱动开发12——电容触摸驱动实践(上)

码农爱学习 来源:码农爱学习 作者:码农爱学习 2022-05-30 09:38 次阅读

上篇文章介绍了LCD屏幕的使用,这个屏幕还有触摸功能,本篇就来介绍LCD的触摸功能的使用。

关于触摸的内容有点多,分为上下两篇进行讲解,本篇先介绍触摸驱动的编写以及将触摸点坐标实时打印出来进行测试,先有一个整体的使用感受,下篇文章再介绍具体的触摸上报协议以及图形化的测试方法

1 触摸介绍

LCD的触摸功能,本质就是显示屏上再叠加一层透明的触摸屏,实现触摸的方式与LCD进行交互。

触摸屏分为电阻触摸屏电容触摸屏

电阻触摸屏是一种传感器,其结构是薄膜加上玻璃的结构,两结构相邻的一面上均涂有ITO(一种导电性和透明性很好的)涂层。当触摸操作时,两层结构挤压接触,经由感应器传出相应的电信号,通过运算转化为屏幕上的X、Y值。

电容技术触摸屏CTP(Capacity Touch Panel)是利用人体的电流感应进行工作的。电容屏是一块四层复合玻璃屏,电容式触摸屏就是支持多点触摸的人机交互方式,普通电阻式触摸屏只能进行单一点的触控。

1.1 硬件原理图

本篇使用的是野火的7寸电容触摸屏,分辨率和屏幕一样,800x480。触摸驱动芯片我GT911,是IIC接口芯片

pYYBAGKSJuSAWxjCAAHavN6i5gU610.png

触摸芯片有四个引脚:

SDA:触摸芯片的IIC 通信引脚

SCL:触摸芯片的IIC 通信引脚

RSTN:触摸芯片的复位引脚

INT:触摸芯片的中断引脚

对应板子原理图的触摸接口如下:

pYYBAGKSJu6AF1pMAAKnUVgj-yM049.png

对应屏幕原理图的触摸接口如下:

poYBAGKSJvWAbhnLAACITWpbcXI468.png

2 编写触摸驱动代码

触摸芯片用到IIC通信,还要用到复位引脚中断引脚,因此需要先在设备树中对引脚信息进行配置。

2.1 修改设备树

修改imx6ull_myboard.dts文件。

在设备树中把触摸要用到的引脚追加到 iomuxc即可。

引脚 功能
UART4_RX_DATA 复用为 I2C1_SDA,用作 IIC1 的 SDA 引脚
UART4_TX_DATA 复用为 I2C1_SCL,用作 IIC1 的 SCL 引脚
LCD_RST 复用为 GPIO3_IO04 用作触摸芯片的复位引脚
SNVS_TAMPER9 复用为 GPIO5_IO09 用作触摸芯片的 irq 引脚,接收触摸中断

需要注意的是,SNVS_TAMPER9 引脚被复用为 GPIO5_IO09,需要追加到 iomuxc_snvs 节点。

2.1.1 IIC引脚

触摸芯片用到的是IIC1,这两个引脚在设备树中以及默认添加了,无需修改:

pYYBAGKSJwCAEDK-AAGGKwZJBcA642.png

2.1.2 复位引脚

&iomuxc节点中添加:

/*my gt911*/
pinctrl_tsc_reset: tscresetgrp {
    fsl,pins = <
        /* used for tsc reset */
        MX6UL_PAD_LCD_RESET__GPIO3_IO04 0x10b0
        >;
};
pYYBAGKSJweAO1PLAAF1L4dCMbA142.png

2.1.3 中断引脚

&iomuxc_snvs节点中添加:

/*my gt911*/
pinctrl_tsc_irq: tsc_irq {
    fsl,pins = <
        /* used for tsc irq */
        MX6ULL_PAD_SNVS_TAMPER9__GPIO5_IO09   0x4001b8b0
        >;
}
poYBAGKSJw2AHJC1AAHgDlHOwrM928.png

2.1.4 IIC设备添加GT911

GT911触摸驱动作为一个IIC设备挂载在IIC1总线上,找到IIC1节点:

poYBAGKSJxuAfDAvAAD_jq-D5p4206.png

需要在 IIC1 设备节点下追加相应的子节点:

gt911_tsc@5d {
    compatible = "goodix,gt911";
	reg = <0x5d>;
    pinctrl-0 = <&pinctrl_tsc_reset>;
    pinctrl-1 = <&pinctrl_tsc_irq>;
    reset-gpios = <&gpio3 4 GPIO_ACTIVE_LOW>;
    irq-gpios = <&gpio5 9 GPIO_ACTIVE_HIGH>;
    interrupt-parent = <&gpio5>;
    interrupts = <9 IRQ_TYPE_EDGE_FALLING>;
};

修改后:

poYBAGKSJyGAKq2rAAFOKabFMgk132.png

reg = <0x5d>是GT911触摸芯片在 IIC1总线上的地址。

2.2 触摸芯片数据寄存器

查看GT911的数据手册,找到寄存器相关的表格:

pYYBAGKSJyiAQL2BAAGbiPwsrBI246.png

主要关注以下这些寄存器,它们是用来读取触摸坐标点的:

Addr Access bit7~bit0
0x814E R/W buffer status(7) large detect(6) Reserved(5~4) number of touch points(3~0)
0x814F R track id
0x8150 R point 1 x coordinate (low byte)
0x8151 R point 1 x coordinate (high byte)
0x8152 R point 1 y coordinate (low byte)
0x8153 R point 1 y coorte (high byte)
0x8154 R Point 1 size (low byte)
0x8155 R Point 1 size (high byte)
0x8156 R Reserved
0x8157 R track id
... ... ...
0x815F R track id
... ... ...
0x8167 R track id
... ... ...
0x816F R track id
0x8170 R point 5 x coordinate (low byte)
0x8171 R point 5 x coordinate (high byte)
0x8172 R point 5 y coordinate (low byte)
0x8173 R point 5 y coordinate (high byte)
0x8174 R Point 5 size (low byte)
0x8175 R Point 5 size (high byte)
0x8176 R Reserved

0x814E:这个寄存器很重要,它是可读可写的寄存器,通过读取该寄存器,可以知道当前是否有触摸点(由最高位表示),以及有几个触摸点(由低3位表示)

0x814F~0x8156:是第一组触摸的坐标数据

0x814F:是触摸点的追踪id,GT911支持5点触摸,这里id的取值为0~4

0x8150:触摸点1的x坐标(低字节)

0x8151:触摸点1的x坐标(高字节)y

0x8152:触摸点1的y坐标(低字节)

0x8153:触摸点1的y坐标(高字节)

0x8154~0x8156:暂不使用

0x8157以后的寄存器:与第一组触摸的坐标数据的含义类似,一个有五组

注:GT911支持硬件追踪触摸点,因此为每个触摸点提供了一个track id,举个简单的例子,当5个手指依次触摸到屏幕时,5组坐标寄存器中的track id会依次是0、1、2、3、4,当松开第1个手指时,即track id为0的点没有了,此时5组坐标寄存器,是只有前45组坐标寄存器有数据,track id会依次是1、2、3、4(理解这个很重要,因为我一开始想当然的误认为,移开第1个手指时,是第1组坐标寄存器没数据了)

2.3 编写驱动程序

新建gt911.c文件作为驱动文件

触摸芯片GT911的使用,本质是使用IIC通信,进行数据的读写,因为触摸屏的驱动,实际就是IIC驱动。另外,触摸的数据是通过中断的方式触发的,因此触摸驱动的编写,涉及到中断的处理。在中断时,读取到触摸数据后,要传递到应用层,这里是使用Linuxinput子系统(这也是Linux的一种软件分层设计的方式)。

所以,编写触摸驱动,主要涉及3点:

IIC协议的驱动

中断的处理(获取触摸数据)

input子系统(将触摸数据传递到应用层)

2.3.1 IIC驱动架构

GT911的驱动按照IIC驱动来写,当驱动运行时,会自动运行gt911_probe,在这个函数中会进行各种初始化操作。另外注意匹配列表,这里的“goodix,gt911”对应设备树中添加的设备节点,两处的名字要一致。

/* 匹配列表 */
static const struct of_device_id gt911_of_match[] = {
    {.compatible = "goodix,gt911"},
    {/* Sentinel */}
};

/* i2c驱动结构体 */
struct i2c_driver gt911_i2c_driver = {
    .driver = {
        .owner = THIS_MODULE,
        .name = "gt911",   /* 驱动名字 用于和设备匹配  适用于没有设备树的情况*/
        .of_match_table =gt911_of_match,  /* 设备树匹配列表 */
    },
    .probe =gt911_probe,
    .remove =gt911_remove,
    .id_table = gt911_id, /* id配置列表 */
};

2.3.2 驱动的初始化流程

gt911_probe函数进行触摸驱动的初始化,基本流程就是从设备树获取触摸节点,然后进行IO的初始化,中断和复位的初始化以及触摸器件的初始化等。

static int gt911_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
    u8 ret = 0; 
    gt911.client = client; 
    printk("[BSP] gt911 driver and device has match!\r\n");

    /* 获取设备树中的中断和复位引脚 */
    printk("[BSP] get gpios\r\n");
    gt911.irq_pin   = of_get_named_gpio(client->dev.of_node, "irq-gpios", 0);
    gt911.reset_pin = of_get_named_gpio(client->dev.of_node, "reset-gpios", 0);

    /* 初始化复位引脚 */
    ret = gt911_ts_reset(client, >911);

    /* 初始化gt911 */
    printk("[BSP] init gt911\r\n");
    gt911_write_reg(>911, GT_CTRL_REG, 2);  /* 软复位 */
    mdelay(100);
    gt911_write_reg(>911, GT_CTRL_REG, 0);  /* 停止软复位 */
    mdelay(100);

    /* input 注册设备*/
    printk("[BSP] init input device\r\n");
    gt911.input  = devm_input_allocate_device(&client->dev);
    
    /* 初始化input */
    gt911.input->name = client->name;
    gt911.input->id.bustype = BUS_I2C;
    gt911.input->dev.parent = &client->dev;
    
    /* 设置input设备需要上报哪些事件*/
    __set_bit(EV_SYN, gt911.input->evbit);
    __set_bit(EV_KEY, gt911.input->evbit);      /* 按键事件 */
    __set_bit(EV_ABS, gt911.input->evbit);      /* 重复事件 */
    /* 设置input设备需要上报哪些按键*/
    __set_bit(BTN_TOUCH, gt911.input->keybit);  /* 触摸值 */
    
    /* 多点触摸 */
    input_mt_init_slots(gt911.input, MAX_SUPPORT_POINTS, 0);  /*触摸点的数量 */
    input_set_abs_params(gt911.input, ABS_MT_POSITION_X,0, 800, 0, 0);
    input_set_abs_params(gt911.input, ABS_MT_POSITION_Y,0, 480, 0, 0);

    /* 注册input */
    ret = input_register_device(gt911.input);
    
    /* 最后初始化中断 */
    ret = gt911_ts_irq(client, >911);
    
    printk("[BSP] %s done \r\n",__FUNCTION__);
    return 0;
}

2.3.3 复位与中断的初始化

复位引脚的初始化主要就是拉低再拉高复位引脚,实现复位,主要内容为:

/* 申请复位IO 并且默认输出高电平 */
devm_gpio_request_one(&client->dev,
                      dev->reset_pin,
                      GPIOF_OUT_INIT_HIGH,
                      "gt911 reset");

gpio_set_value(dev->reset_pin, 0);  /* 复位 */
msleep(10);
gpio_set_value(dev->reset_pin, 1);  /* 停止复位 */
msleep(300);

中断的初始化,包括IO的申请和中断函数的注册:

/* 申请复位 IO */
devm_gpio_request_one(&client->dev,
                      dev->irq_pin,
                      GPIOF_IN,
                      "gt911 irq");

/* 申请中断  */
devm_request_threaded_irq(&client->dev,
                          client->irq,
                          NULL,
                          gt911_irq_handler, /* 中断处理函数 */
                          IRQF_TRIGGER_FALLING | IRQF_ONESHOT, /* 触发方式 */
                          client->name,
                          >911);

2.3.4 中断处理函数

这里仅贴出gt911_irq_handler的主要内容,基本思路是先读取0x814E(GT_GSTID_REG)这一个寄存器,判断触摸点的数量,然后再读取对应的坐标点数据寄存器,依次上报数据。

/* -----读取触摸信息寄存器----- */
ret = gt911_read_regs(dev, GT_GSTID_REG, &data, 1); 
if(data == 0x00) /* 没有触摸数据*/
{  
    goto fail;
}
else
{  /* 统计触摸信息 */
    status       = data >> 7;          // bit7:1表示坐标(或按键)已经准备好,主控可以读取  0 表示未就绪,数据无效
    large_detect = (data >> 6) & 0x01; // bit6:
    touch_num    = data & 0x0f;        // bit3~0:屏上的坐标点个数
}

if(touch_num) /* 有触摸按下 */
{
    /* -----读取具体的触摸点数据寄存器----- */
    gt911_read_regs(dev, GT_TP1_REG, buf, BUFFER_SIZE);
    id = buf[0]; // 数据中的第一个触摸点的id
    touch_index |= (0x01input, MT_TOOL_FINGER, true);   // 指定手指触摸  连续触摸
            input_report_abs(dev->input, ABS_MT_POSITION_X, input_x);   // 上报触摸点坐标信息 
            input_report_abs(dev->input, ABS_MT_POSITION_Y, input_y);   // 上报触摸点坐标信息
            printk("[%d](%d, %d) ", id, input_x, input_y);

            report_num++;
            if (report_num < touch_num)
            {
                pos += 8;
                id = buf[pos];
                touch_index |= (0x01input, MT_TOOL_FINGER, false);   // 关闭手指触摸 
        }
    }
    printk("\r\n");
}
else if(last_index)/* 触摸释放 */
{
    for (i = 0; i < 5; i++)
    {
        if ((last_index & (0x01<input, i); /* 上报触摸点 */
            input_mt_report_slot_state(dev->input, MT_TOOL_FINGER, false); // 关闭手指触摸 
        }
    }
}
last_index = touch_index;

input_mt_report_pointer_emulation(dev->input, true); 
input_sync(dev->input);   /* 同步数据 数据上报完成 */

data = 0x00;  /* 向0x814E寄存器写0  不然就会一直进入中断 */
gt911_write_regs(dev, GT_GSTID_REG, &data, 1);   //写入)))>);>);>

总结一下GT911多点触摸驱动的执行流程:

poYBAGKSJzWAb7scAADyGKtO3PU897.png

3 使用Linux内核自带的驱动(未测试)

对于触摸屏的驱动,NXP已经编写好了触摸驱动,加以修改可以在自己的板子上使用。

不过我没有测试成功,以后有时间再搞,所以这一部分内容可以跳过

我这个7寸屏的驱动型号为GT911,属于 GOODIX 公司生产的触摸芯片,该触摸驱动已默认添加到了Linux内核中,位于:/drivers/input/touchscreen/goodix.c。

pYYBAGKSJ0CAM-bfAAFy10zLf6k652.png

使用Linux内核自代的驱动,还需要进行内核配置。在Linux内核源码目录,输入以下指令打开内核的图形化配置:

make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig

到达Linux内核配置界面,然后按下路径找到对应的配置项:

-> Device Drivers 
	-> Input device support         
		-> Touchscreens (INPUT_TOUCHSCREEN [=y])
			<*>   Goodix I2C touchscreen  

最终到达这个界面:

poYBAGKSJ0iAQU1RAAD3UhYPS24770.png

按下y勾选上星号,连按多次ESC退出,最后提示保存,按下y保存配置。

然后需要重新编译zImage和设备树,到Linux内核源码目录,执行之前的编写的编译脚本

./build_myboard.sh 

编译的时候会弹出Linux图形配置界面, 不需要做任何的配置, 直接按两下ESC键退出图形界面

将编译出zImage(arch/arm/boot目录)和imx6ull-myboard.dtb (arch/arm/boot/dts目录)复制到网络启动位置

cp arch/arm/boot/zImage ~/myTest/tftpboot/nxp/
cp arch/arm/boot/dts/imx6ull-myboard.dtb ~/myTest/tftpboot/nxp/

4 触摸测试

使用自己编写的触摸驱动程序,进行测试。

make imx6ull-myboard.dtb
cp arch/arm/boot/dts/imx6ull-myboard.dtb ~/myTest/tftpboot/nxp/

4.1 编译设备树

首先是编译设备树,验证添加的触摸节点是否工作正常,在Linux内核源码目录执行下面的命令,重新编译设备树并拷贝到网络启动位置。

make imx6ull-myboard.dtb
cp arch/arm/boot/dts/imx6ull-myboard.dtb ~/myTest/tftpboot/nxp/

然后重启开发板,可以先到如下位置,查看设备树的节点是否正常:

poYBAGKSJ1SAPObmAAEBuHNsHUw215.png

4.2 编译驱动文件

然后是编译驱动文件,也就是gt911.c,编译方式和之前一样,在ubuntu中使用Makefile进行交叉编译。

本篇暂未用到对应的触摸应用程序,所有的触摸坐标打印都是在驱动程序中通过printk的方式进行内核打印。

编译完驱动后,将对应的.ko文件复制到板子中。

4.3 测试触摸点的坐标输出

先加载触摸驱动,串口会打印出为触摸分配的event,我这里是event2

然后执行下面的指令进行触摸测试:

hexdump /dev/input/event2 
pYYBAGKSJ16AMwoMAACxRG-D43E567.png

将手指放到屏幕上,就可以在LCD屏幕上看到坐标值的打印,比如将手指放到屏幕左下角,对应输出的值大致就是屏幕的最大位置(800,480):

poYBAGKSJ2WANC1vAAHwHFl6AwA500.png

GT911支持多点触摸,驱动程序中也对多点数据进行了获取和打印,将多个手指放到屏幕上,可以看到最多有5个触摸点的坐标打印:

poYBAGKSJ2yAZCRqAAOA6ZMaJPE087.png

5 总结

本篇主要介绍了多点触摸芯片GT911的驱动编写与使用,并通过将触摸点实时打印的方式,测试触摸功能。

附:视频演示

https://www.bilibili.com/video/BV1sZ4y1Q7da

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 嵌入式
    +关注

    关注

    5057

    文章

    18964

    浏览量

    301816
  • lcd
    lcd
    +关注

    关注

    34

    文章

    4402

    浏览量

    166893
  • 驱动
    +关注

    关注

    12

    文章

    1818

    浏览量

    85103
  • 电容触摸
    +关注

    关注

    0

    文章

    70

    浏览量

    16435
  • i.MX6
    +关注

    关注

    1

    文章

    37

    浏览量

    16272
收藏 人收藏

    评论

    相关推荐

    i.MX6ULL 驱动开发7—按键输入捕获与GPIO输入配置与高低电平读取

    本篇主要介绍了i.MX6ULL的按键检测的使用,主要的知识点是设备树的修改,以及GPIO的输入配置与高低电平的读取。
    的头像 发表于 05-24 09:11 6087次阅读
    <b class='flag-5'>i.MX6ULL</b> <b class='flag-5'>驱动</b><b class='flag-5'>开发</b>7—按键输入捕获与GPIO输入配置与高低电平读取

    i.MX6ULL嵌入式Linux开发1-uboot移植初探

    本系列教程以i.MX6ULL处理器的ARM开发板为实验基础,学习记录嵌入式Linux开发的各种知识与经验,主要内容包括嵌入式Linux移植,嵌入式Linux驱动
    的头像 发表于 03-07 08:57 3830次阅读
    <b class='flag-5'>i.MX6ULL</b>嵌入式Linux<b class='flag-5'>开发</b>1-uboot移植初探

    使用i.MX6ULL开发板进行Linux根文件系统的完善

    一篇推文讲了怎么移植根文件系统,并在i.MX6ULL开发板中运行起来,但是会出现一些提示,现在来进行根文件的完善。
    发表于 10-17 11:13 755次阅读

    移植NXP官方linux 5.4内核到i.MX6ULL开发

    本文描述移植NXP官方 linux 5.4 内核到i.MX6ULL开发板。
    发表于 12-19 11:10 1994次阅读

    i.MX6UL/i.MX6ULL开发常见问题】单独编译内核,uboot生成很多文件,具体用哪一个?

    i.MX6UL/i.MX6ULL开发常见问题》基于米尔电子 i.MX6UL/i.MX6ULL产品(V.10)2.3单独编译内核,uboot
    发表于 07-01 17:50

    迅为I.MX6ULL终结者开发板支持JTAG调试

    的硬件环境1、迅为-i.MX6ULL终结者开发板一块2、JLNK V9下载器一个3、JLINK V9转换板一个(2.54mm转2.0mm)1.2 搭建开发环境1.2.1 安装JLINK V9
    发表于 05-06 14:09

    I.MX6ULL终结者开发板裸机仿真jlink调试

    I.MX6ULL‘终结者’开发板预留了JTAG仿真接口,并给出了开发文档,可以实现在JLINK仿真器条件下的单步跟踪、断点调试等功能,使得开发研究i
    发表于 07-07 10:56

    i.MX6ULL开发板硬件资源

    迅为i.MX6ULL 终结者开发板硬件资源非常丰富,几乎将 i.MX6ULL 芯片的所有资源都扩展引出到底板上了,底板提供了丰富的外设接口,开发板的尺寸是 190mm*125mm,充分
    发表于 12-29 06:18

    初识 i.MX6ULL 寄存器

    裸机开发_L1_汇编LED实验0. 本节目标1. 硬件层电路2. 初识 i.MX6ULL 寄存器2.1 i.MX6ULL 时钟控制寄存器2.2 i.MX6ULL IO复用寄存器2.3
    发表于 12-20 07:13

    如何使用Linux版本在i.mx6ull启用USB网络共享?

    。 请告知如何使用此 Linux 版本在 i.mx6ull 启用 USB 网络共享。否则请告知如何构建一个支持此功能的?
    发表于 05-09 08:06

    飞凌i.MX6ULL开发板的评测,再次进阶拥有更高的性价比

    处理器MCIMX6Y2开发设计,采用先进的ARMCortex-A7内核,运行速度高达800MHz。i.MX6ULL应用处理器包括一个集成的电源管理模块,降低了外接电源的复杂性,并简化了
    发表于 10-27 11:55 1446次阅读
    飞凌<b class='flag-5'>i.MX6ULL</b><b class='flag-5'>开发</b>板的评测,再次进阶拥有更高的性价比

    基于NXP i.MX6ULL处理器的FETMX6ULL-C核心板

    合作伙伴,飞凌不负美誉,基于i.MX6ULL匠心打造的FETMX6ULL-S核心板一经问世便好评不断,且已有数百家来自工业、医疗、电力、物联网等行业的用户采用此款核心板快速完成了整机产品的开发上市。
    发表于 04-11 15:05 1128次阅读
    基于NXP <b class='flag-5'>i.MX6ULL</b>处理器的FETMX<b class='flag-5'>6ULL</b>-C核心板

    i.MX6ULL驱动开发4——点亮LED(寄存器版)

    本篇主要介绍了如何通过操作寄存器来点亮i.MX6ULL开发的led,通过编写LED对应的驱动程序和应用程序,实现程序设计的分层。
    的头像 发表于 05-21 21:26 2915次阅读
    【<b class='flag-5'>i.MX6ULL</b>】<b class='flag-5'>驱动</b><b class='flag-5'>开发</b>4——点亮LED(寄存器版)

    【北京迅为】i.MX6ULL开发板移植 Debian 文件系统

    【北京迅为】i.MX6ULL开发板移植 Debian 文件系统
    的头像 发表于 02-10 15:34 1094次阅读
    【北京迅为】<b class='flag-5'>i.MX6ULL</b><b class='flag-5'>开发</b>板移植 Debian 文件系统

    基于i.MX6ULL的掉电检测设计与软件测试

    基于i.MX6ULL的掉电检测设计与软件测试基于i.MX6ULL平台设计实现掉电检测功能,首先选择一路IO,利用IO电平变化触发中断,在编写驱动时捕获该路GPIO的中断,然后在中断响应函数中发
    的头像 发表于 11-09 10:40 794次阅读
    基于<b class='flag-5'>i.MX6ULL</b>的掉电检测设计与软件测试