系统相关
首页 > 系统相关> > linux驱动移植-LCD驱动触摸屏驱动案例

linux驱动移植-LCD驱动触摸屏驱动案例

作者:互联网

一、触摸屏种类

触摸屏的基本原理是,用手指或其他物体触摸安装在显示器前端的触控屏时,所触摸的位置(以坐标形式)由触摸屏控制器检测,并通过接口(如RS-232串行口)送到CPU,从而确定输入的信息。

触摸屏系统一般包括触摸屏控制器(卡)和触摸检测装置两个部分:

1.1 电阻触摸屏

电阻类触摸屏的关键在于材料科技。电阻屏根据引出线数多少,分为四线、五线、六线等多线电阻触摸屏。下面以四线电阻式触摸屏为例介绍。

四线电阻式触摸屏的结构如图,在玻璃或丙烯酸基板上覆盖有两层透平,均匀导电的ITO层,分别做为X电极和Y电极,它们之间由均匀排列的透明格点分开绝缘。其中下层的ITO与玻璃基板附着,上层的ITO附着在PET薄膜上。X电极和Y电极的正负端由“导电条”(图中黑色条形部分)分别从两端引出,且X电极和Y电极导电条的位置相互垂直。引出端X-,X+,Y-,Y+一共四条线,这就是四线电阻式触摸屏名称的由来。

当有物体接触触摸屏表面并施以一定的压力时,上层的ITO导电层发生形变与下层ITO发生接触,该结构可以等效为上图中的电路。

计算触点的X,Y坐标分为如下两步:

 

 所以:

$$x=\frac{V_{x_+}}{V_{driver}} \times width_{screen}$$

$$y=\frac{V_{y_+}}{V_{driver}} \times height_{screen}$$

测得的电压通常由ADC转化为数字信号,再进行简单处理就可以做为坐标判断触点的实际位置。

四线电阻式触摸屏除了可以得到触点的X/Y坐标,还可以测得触点的压力,这是因为top layer施压后,上下层ITO发生接触,在触点上实际是有电阻存在的,如图3的Rtouch。压力越大,接触越充分,电阻越小,通过测量这个电阻的大小可以量化压力大小。

1.2 电容触摸屏

是利用人体的电流感应进行工作的。电容式触摸屏是是一块四层复合玻璃屏,玻璃屏的内表面和夹层各涂有一层ITO,最外层是一薄层矽土玻璃保护层,夹层ITO涂层作为工作面,四个角上引出四个电极,内层ITO为屏蔽层以保证良好的工作环境。 当手指触摸在金属层上时,由于人体电场,用户和触控屏表面形成以一个耦合电容,对于高频电流来说,电容是直接导体,于是手指从接触点吸走一个很小的电流。这个电流分从触控屏的四角上的电极中流出,并且流经这四个电极的电流与手指到四角的距离成正比,控制器通过对这四个电流比例的精确计算,得出触摸点的位置。

二、硬件分析

2.1 硬件接线

由于我们Mini2440开发板使用的S3C2440 SOC,只支持四线电阻式触摸屏。这里我们使用的LCD型号为LCD-T35(TD035STEB4),该4线连接在S3C2440的AIN4~AIN7引脚上,该引脚专门是用来接收模拟输入信号:

引脚说明:

2.2 x坐标获取

如下图,  把XP接3.3V 、XM接0V,YP和YM悬空。我们以按压X坐标的中间位置,X层和Y层便闭合,此时YP就会输出当前X坐标值的1.66V给CPU 。

2.3 y坐标获取

如下图, 把YP接3.3V , YM接0V,XP和XM悬空,我们以按压X坐标的中间位置, X层和Y层便闭合了,此时XP就会输出当前X坐标值的1.66V给CPU 。

 

2.4 s3c2440的触摸屏接口

查看s3c2440手册第16章ADC & TOUCH SCREEN INTERFACE章节,触摸屏A/D转换器和触摸屏接口框图如下:

 

触摸屏接口模式共有四种:

ADC的工作频率最大为2.5MHZ,需要设置寄存器ADCCON->PRSCVL更改分频系数。A/D转换时间:

当PCLK频率在50MHz并且预分频器的值为49时,10位的转换时间为:

$$A/D转换器频率=\frac{50MHZ}{49+1}=1MHz$$

$$转换时间=frac{1}{1M/5}=\frac{1}{200k}=5us$$

2.5 ADC 和触摸屏接口特殊寄存器

ADC控制寄存器(ADCCON)

寄存器信息:

寄存器名

地址

是否读写

描述

复位值

ADCCON

0x58000000

R/W

ADC控制寄存器

0x3FC4

寄存器位信息:

ADCCON 描述 初始状态
ECFLG [15]

转换结束标志位(只读)

0 = A/D正在转换   1 = A/D转换已结束

 0
PRSCEN [14]

A/D转换器预分频器使能

0 = 禁止    1 = 使能

 0
PRSCVL [13:6]

A/D转换器预分频值

数值范围: 0~255

注意:ADC频率应该设置为低于PCLK的1/5

 0xFF
SEL_MUX [5:3]

模拟输入通道选择

000 = AIN0  001= AIN1  010 = AIN2  011 = AIN3
100 = YM     101 = yp     110 = XM    111 = XP 

 0
STDBM [2]

待机模式选择

0 = 正常工作模式   1 = 待机模式 

 1
READ_ START [1]

读启动 A/D 转换

0 = 禁止读启动操作 1 = 使能读启动操作

0
ENABLE_START [0]

使能A/D转换启动。如果 READ_START 为使能,则此值无效

0 = 无操作      1 =  A/D 转换启动且此位在启动后被清零

0

ADC触摸屏控制寄存器(ADCTSC)

寄存器信息:

寄存器名

地址

是否读写

描述

复位值

ADCTSC

0x58000004

R/W

ADC触摸屏控制寄存器

0x58

寄存器位信息:

ADCTSC 描述 初始状态
UD_SEB [8]

检测笔尖起落状态

0 = 检测笔尖落下中断信号 1 = 检测笔尖抬起中断信号

 0
YM_SEN [7]

YM 开关使能

0 = YM 输出驱动器禁止 1 = YM 输出驱动器使能

 0
YP_SEN [6]

YP 开关使能

0 = YP 输出驱动器禁止 1 = YP 输出驱动器使能

1
XM_SEN [5]

XM 开关使能

0 = XM 输出驱动器禁止 1 = XM 输出驱动器使能

 0
XP_SEN [4]

XP 开关使能

0 = XP 输出驱动器禁止 1 = XP 输出驱动器使能

 1
PULL_UP [3]

上拉开关使能

0 = XP 上拉使能   1 = XP 上拉禁止

1
AUTO_PST [2]

0 = XP 上拉使能 1 = XP 上拉禁止

0 = 正常 ADC 转换    1 = 自动顺序X方向和Y方向测量

0
XY_PST [1:0]

 手动测量X方或Y方向

00 = 无操作模式    01 = X 方向测量

10 = Y 方向测量    11 = 等待中断模式

0

ADC转换数据寄存器(ADCDAT0)

寄存器信息:

寄存器名

地址

是否读写

描述

复位值

ADCDAT0

0x5800000C

R

ADC转换数据寄存器

-

寄存器位信息:

 

ADCDAT0 描述 初始状态
UPDOWN [15]

等待中断模式中笔尖的起落状态

0 = 笔尖落下态 1 = 笔尖抬起态

-
AUTO_PST [14]

自动顺序 X 方向和 Y 方向转换

0 = 正常 ADC 转换 1 = 顺序 X 方向、Y 方向测量

 -
XY_PST [13:12]

手动 X 方向或 Y 方向测量

00 = 无操作模式 01 = X 方向测量

10 = Y 方向测量 11 = 等待中断模式

-
保留 [11:10]

保留

-
XPDATA [9:0]

X 方向转换数值(包括正常 ADC 转换数值)

数值范围:0 至 3FF

-

ADC转换数据寄存器(ADCDAT1)

寄存器信息:

寄存器名

地址

是否读写

描述

复位值

ADCDAT1

0x58000010

R

ADC转换数据寄存器

-

寄存器位信息:

ADCDAT1 描述 初始状态
UPDOWN [15]

等待中断模式中笔尖的起落状态

0 = 笔尖落下态 1 = 笔尖抬起态

-
AUTO_PST [14]

自动顺序X方向和Y方向转换

0 = 正常ADC 转换  1 = 顺序X方向、Y方向测量

 -
XY_PST [13:12]

手动X方向或Y方向测量

00 = 无操作模式 01 = X 方向测量

10 = Y 方向测量 11 = 等待中断模式

-
保留 [11:10]

保留

-
YPDATA [9:0]

Y方向转换数值(包括正常 ADC 转换数值)

数值范围:0 至 3FF

-

三、触摸屏驱动编写步骤

3.1 输入子系统

对于触摸屏驱动,也是使用输入子系统框架进行编写,输入子系统相关内容在linux驱动移植-输入子系统示例里详细分析了:

右边的驱动事件处理,内核是已经写好了的,所以我们的触摸屏只需要写具体的驱动设备,然后内核会与触摸屏驱动tsdev.c自动连接 。

3.2 驱动入口函数

(1) 我们首先通过input_allocate_device动态创建struct input_dev结构对象dev;

(2) 初始化dev;

(3) 然后调用input_register_device注册这个设备;

(4) 初始化触摸屏相关的硬件:

3.3 驱动出口函数

3.4 IRQ_TC中断函数

3.5 IRQ_ADC中断函数

3.6 定时器超时函数

四、编写代码

4.1 创建项目

在/work/sambashare/drivers路径下创建12.lcd_touch项目,并创建lcd_touch_dev.c和Makefile文件。

4.2 lcd_touch_dev.c

 

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/io.h>
#include <linux/uaccess.h>
#include <linux/gpio.h>
#include <linux/irq.h>        // 包含了mach/irqs.h
#include <linux/interrupt.h>
#include <linux/gpio/machine.h>
#include <mach/gpio-samsung.h>
#include <linux/input.h>
#include <linux/timer.h>
#include <linux/clk.h>

#define IRQF_SAMPLE_RANDOM       0x00000040

/*
 * s3c2440 APC和触摸屏相关寄存器
 */
struct s3c_ts_regs{
    unsigned long adccon;
    unsigned long adctsc;
    unsigned long adcdly;
    unsigned long adcdat0;
    unsigned long adcdat1;
    unsigned long adcupdn;
};

/* 定义一个input_dev结构体 */
static struct input_dev *s3c_ts_dev;
/* 寄存器 */
static struct s3c_ts_regs *s3c_ts_regs;
/* 定时器 */
static struct timer_list s3c_ts_timer;

/* 进入等待笔尖按下中断模式 */
static void enter_wait_pen_down_mode(void)
{
    /* 设置寄存器ADCTSC=0x0d3,开启INT_TC中断,笔尖按下触发*/
    s3c_ts_regs->adctsc = 1 << 7 | 1 << 6 |  1<<4  | 3 << 0;
}

/* 进入等待笔尖抬起中断模式 */
static void enter_wait_pen_up_mode(void)
{
    /* 设置寄存器ADCTSC=0x01d3,开启IRQ_TC中断,笔尖抬起触发*/
    s3c_ts_regs->adctsc = 1<< 8 | 1 << 7 | 1 << 6 |  1<<4  | 3 << 0;
}

/* 进入XY自动转换模式,ADC转换完成后,触发INT_ADC中断 */
static void enter_measure_xy_mode(void)
{
    /* 启动XY自动转换 */
    s3c_ts_regs->adctsc = (1<<3)|(1<<2);
     /* 启动1次ADC转换,开启一次ADC转换,当ADC转换成功该位清0 */
    s3c_ts_regs->adccon |= (1<<0);
}


/*
 * IRQ_TC中断处理服务
 * 笔尖按下或者抬起进入
 */
static irqreturn_t tc_handler(int irq, void *dev_id)
{
    //如果触摸屏当前为松开状态
   if (s3c_ts_regs->adcdat0 & (1<<15))
       {
           /*上报压力值为0,等价 input_event(s3c_ts_dev, EV_ABS, ABS_PRESSURE,  0) */
           input_report_abs(s3c_ts_dev, ABS_PRESSURE, 0);
           /*上报BTN_TOUCH按键值松开,等价 input_event(s3c_ts_dev, EV_KEY, BTN_TOUCH,  0) */
           input_report_key(s3c_ts_dev, BTN_TOUCH, 0);
           /*上报同步事件,通知系统有事件上报 */
           input_sync(s3c_ts_dev);
           /* 进入等待笔尖按下中断模式 */
           enter_wait_pen_down_mode();
           printk("tc_handler up\n");
       }
       else
       {
           /* 进入测量X/Y坐标模式并启动一次ADC */
           enter_measure_xy_mode();
           printk("tc_handler down\n");
       }

    return IRQ_RETVAL(IRQ_HANDLED);
}

/*
 * 计算若干次的采样值是否存在较大误差,如果存在较大误差,放弃这次采样
 */
static int s3c_filter_ts(int x[], int y[])
{
// 定义最大误差
#define ERR_LIMIT 10

    int avr_x, avr_y;
    int det_x, det_y;

    avr_x = (x[0] + x[1])/2;
    avr_y = (y[0] + y[1])/2;

    det_x = (x[2] > avr_x) ? (x[2] - avr_x) : (avr_x - x[2]);
    det_y = (y[2] > avr_y) ? (y[2] - avr_y) : (avr_y - y[2]);

    if ((det_x > ERR_LIMIT) || (det_y > ERR_LIMIT))
        return 0;

    avr_x = (x[1] + x[2])/2;
    avr_y = (y[1] + y[2])/2;

    det_x = (x[3] > avr_x) ? (x[3] - avr_x) : (avr_x - x[3]);
    det_y = (y[3] > avr_y) ? (y[3] - avr_y) : (avr_y - y[3]);

    if ((det_x > ERR_LIMIT) || (det_y > ERR_LIMIT))
        return 0;

    return 1;
}

/*
 * IRQ_ADC中断处理服务 获取x、y坐标
 * 自动顺序X/Y方向转换模式,ADC转换成功后,会进入IRQ_ADC中断函数(系统自动把X坐标写入到ADCDAT0、把Y坐标写入ADCDAT1)
 */
static irqreturn_t adc_handler(int irq, void *dev_id)
{
    /* 计数 */
    static int cnt=0;
    /* 保存x、y  */
       static int x[4],y[4];

    //如果触摸屏当前为松开状态
       if (s3c_ts_regs->adcdat0 & (1<<15))
       {
           /*上报压力值为0,等价 input_event(s3c_ts_dev, EV_ABS, ABS_PRESSURE,  0) */
        input_report_abs(s3c_ts_dev, ABS_PRESSURE, 0);
           /*上报BTN_TOUCH按键值松开,等价 input_event(s3c_ts_dev, EV_KEY, BTN_TOUCH,  0) */
        input_report_key(s3c_ts_dev, BTN_TOUCH, 0);
           /*上报同步事件,通知系统有事件上报 */
        input_sync(s3c_ts_dev);
        /* 进入等待笔尖按下中断模式 */
        enter_wait_pen_down_mode();
           cnt = 0;
       }
       else
       {   // 笔尖按下后 连续测量4次
           // x坐标
           x[cnt]=s3c_ts_regs->adcdat0 & 0x3ff;
           // y
           y[cnt]=s3c_ts_regs->adcdat1 & 0x3ff;
           cnt++;
        // 4次求平均
           if(cnt == 4){
            // 计算若干次的采样值是否存在较大误差,如果存在较大误差,放弃这次采样
               if (s3c_filter_ts(x, y)){
                   //上报X方向值
                   input_report_abs(s3c_ts_dev, ABS_X, (x[0]+x[1]+x[2]+x[3])/4);
                   //上报Y方向值
                   input_report_abs(s3c_ts_dev, ABS_Y, (y[0]+y[1]+y[2]+y[3])/4);
                   //上报压力方向值
                   input_report_abs(s3c_ts_dev, ABS_PRESSURE, 1);
                   //上报BTN_TOUCH按键按下
                   input_report_key(s3c_ts_dev, BTN_TOUCH, 1);
                   //上报同步事件,通知系统有事件上报
                   input_sync(s3c_ts_dev);
                   printk("X:  %04d,y:  %04d \n",(x[0]+x[1]+x[2]+x[3])/4,(y[0]+y[1]+y[2]+y[3])/4);      //中值滤波
               }
               cnt = 0;
               /* 进入等待笔尖抬起中断模式 */
               enter_wait_pen_up_mode();
               /* 启动定时器处理长按/滑动的情况 10ms后如果仍然没有笔尖抬起,再次进行测量坐标 */
               mod_timer(&s3c_ts_timer, jiffies + HZ/100);
           }
           else
           {
               /* 进入测量X/Y坐标模式并启动一次ADC */
               enter_measure_xy_mode();
           }
       }

    return IRQ_RETVAL(IRQ_HANDLED);
}

/*
 * 定时器超时函数
 * 将输入转换为转换为统一事件形式
 */
static void s3c_ts_timer_function(struct timer_list *t)
{
    /* 如果触摸屏当前为松开状态 */
    if (s3c_ts_regs->adcdat0 & (1<<15))
    {
        /* 上报压力值为0,等价 input_event(s3c_ts_dev, EV_ABS, ABS_PRESSURE,  0) */
        input_report_abs(s3c_ts_dev, ABS_PRESSURE, 0);
           /*上报BTN_TOUCH按键值松开,等价 input_event(s3c_ts_dev, EV_KEY, BTN_TOUCH,  0) */
        input_report_key(s3c_ts_dev, BTN_TOUCH, 0);
           /*上报同步事件,通知系统有事件上报 */
        input_sync(s3c_ts_dev);
        /* 进入等待笔尖按下中断模式 */
        enter_wait_pen_down_mode();
    }
    else
    {
         /* 进入测量X/Y坐标模式并启动一次ADC */
         enter_measure_xy_mode();
    }
}

/*
 * 入口函数
 */
static int s3c_ts_init(void)
{
    int err;
    struct clk * clk;
    printk("touch screen driver init\n");

    /* 向内核 申请input_dev结构体 */
    s3c_ts_dev = input_allocate_device();

    /* 设置input_dev、 输入事件code定义在include/uapi/linux/input-event-codes.h */
    set_bit(EV_KEY,s3c_ts_dev->evbit);       // 支持按键事件
    set_bit(EV_ABS,s3c_ts_dev->evbit);       // 支持绝对位移事件
    input_set_capability(s3c_ts_dev,EV_KEY,BTN_TOUCH);   //触摸屏笔尖按下
    input_set_abs_params(s3c_ts_dev, ABS_X, 0, 0x3FF, 0, 0);//s3c2440手册ADC是10位,所以第四个参数设置为3FF
    input_set_abs_params(s3c_ts_dev, ABS_Y, 0, 0x3FF, 0, 0);
    input_set_abs_params(s3c_ts_dev, ABS_PRESSURE, 0, 1, 0, 0);

    /* 注册input_dev */
    err = input_register_device(s3c_ts_dev);
    if (err) {
       printk("input touch screen driver registration failed\n");
       /* 释放驱动结构体 */
       input_free_device(s3c_ts_dev);
       return err;
    } else {
        printk("input touch screen driver registered successfully\n");
    }

    /* 使能时钟(设置CLKCON[15]) */
    clk = clk_get(NULL,"adc");
    clk_prepare_enable(clk);

    /* 设置s3c2440的ADC/ts寄存器 */
    s3c_ts_regs = ioremap(0x58000000, sizeof(struct s3c_ts_regs));
    s3c_ts_regs->adccon = (1<<14)|(49<<6);   // 预分频器使能,分频值为49  1MHZ
    s3c_ts_regs->adcdly = 0xffff;

    /* 注册中断,这里指定了中断线程化处理函数 */
    request_irq(IRQ_TC, tc_handler, IRQF_SAMPLE_RANDOM, "ts_tc", 0);
    request_irq(IRQ_ADC, adc_handler, IRQF_SAMPLE_RANDOM, "ts_adc", 0);

    /* 初始化定时器 */
    timer_setup(&s3c_ts_timer,s3c_ts_timer_function,0);
    add_timer(&s3c_ts_timer);

    /* 进入等待笔尖按下中断模式*/
    enter_wait_pen_down_mode();

    return 0;
}

/*
 * 出口函数
 */
static void __exit s3c_ts_exit(void)
{
    printk("touch screen driver exit\n");

     /* 删除定时器 */
    del_timer(&s3c_ts_timer);

     /* 释放中断 */
    free_irq(IRQ_TC, NULL);
    free_irq(IRQ_ADC, NULL);

    /* 注销虚拟地址 */
    iounmap(s3c_ts_regs);

     /* 卸载类下的驱动设备 */
    input_unregister_device(s3c_ts_dev);
    /* 释放驱动结构体 */
    input_free_device(s3c_ts_dev);
    return;
}

module_init(s3c_ts_init);
module_exit(s3c_ts_exit);
MODULE_LICENSE("GPL");

 

 

 

4.3 Makefile

KERN_DIR :=/work/sambashare/linux-5.2.8
all:
    make -C $(KERN_DIR) M=`pwd` modules 
clean:
    make -C $(KERN_DIR) M=`pwd` modules clean
    rm -rf modules.order

obj-m += lcd_touch_dev.o

五、测试

我们在linux驱动移植-LCD驱动分析这一节的基础上进行试验。

5.1 配置内核

配置内核,移除将内核自带的触摸屏驱动:

root@zhengyang:/work/sambashare/linux-5.2.8# make menuconfig

 Device Drivers  --->

保存文件,输入文件名s3c2440_defconfig,在当前路径下生成s3c2440_defconfig:存档:

mv s3c2440_defconfig ./arch/arm/configs/

5.2 编译内核

此时重新执行:

make distclean
make s3c2440_defconfig    
make uImage V=1

将uImage复制到tftp服务器路径下:

 cp /work/sambashare/linux-5.2.8/arch/arm/boot/uImage /work/tftpboot/

5.3  烧录内核

开发板uboot启动完成后,内核启动前,按下任意键,进入uboot,下载内核到内存,并写NAND FLASH:

tftp 30000000 uImage
nand erase.part kernel
nand write 30000000 kernel

下载完成后,重启开发板,内核启动完成后会在显示屏上看到启动logo。

5.4  编译LCD触摸屏驱动

在12.lcd_touch_dev路径下编译:

root@zhengyang:/work/sambashare/drivers/12.lcd_touch_dev# cd /work/sambashare/drivers/12.lcd_touch_dev
root@zhengyang:/work/sambashare/drivers/12.lcd_touch_dev# make

拷贝驱动文件到nfs文件系统:

root@zhengyang:/work/sambashare/drivers/12.lcd_touch_dev# cp lcd_touch_dev.ko  /work/nfs_root/rootfs/

5.5 安装驱动

重启开发板,加载lcd驱动,执行如下命令:

insmod lcd_touch_dev.ko

运行命令cat /proc/interrupts可以查看当前系统有哪些中断服务:

 

[root@zy:/]# cat /proc/interrupts
           CPU0       
 29:      21110       s3c  13 Edge      samsung_time_irq
 32:          0       s3c  16 Edge      s3c2410-lcd
 42:          0       s3c  26 Edge      ohci_hcd:usb1
 43:          0       s3c  27 Edge      s3c2440-i2c.0
 55:       1460   s3c-ext   7 Edge      eth0
 74:         14  s3c-level   0 Edge      s3c2440-uart
 75:        117  s3c-level   1 Edge      s3c2440-uart
 83:          0  s3c-level   9 Edge      ts_tc
 84:          0  s3c-level  10 Edge      ts_adc
 87:          0  s3c-level  13 Edge      s3c2410-wdt

 

 

 

查看设备节点文件:

 

[root@zy:/]# ls /dev/input -l
total 0
crw-rw----    1 0        0          13,  64 Jan  1 00:00 event0

 

 

 

5.6 测试

执行如下命令:

 

hexdump /dev/input/event0 

 

 

 

 

 

 

此时再点一下触摸屏,串口端输入如下:

 

 

第1列表示hexdump序列号(如0000000)
第2、3列表示秒(如013c 0000)
第4、5列表示微妙(如c7c6 0000)
第6列表示type(如0003表示ABS绝对位移类型,0001表示按键类型)
第7列表示code(如0000表示x方向ABS_X,0001表示y方向ABS_Y,0018表示ABS_PRESSURE,014a表示BTN_TOUCH)
第8、9列表示对应type的对应code值(如020b 0000)

 

参考文章

[1]十三、Linux驱动之触摸屏驱动

[2]18.Llinux-触摸屏驱动(详解)

[3]四线电阻触摸屏工作原理的示意图

[4]四线电阻触摸屏原理

[5]常用低成本:四线电阻式触摸屏原理

标签:寄存器,ts,dev,LCD,ADC,linux,驱动,触摸屏,s3c
来源: https://www.cnblogs.com/zyly/p/16210859.html