Tiny210 I2C总线

夙愿已清 提交于 2019-12-20 03:39:46

1、i2c总线简介

        I2C BUS(Inter IC BUS)是NXP推出的芯片间串行传输总线,它以2根连线实现了完善的双向数据传送,可以很方便地构成朵机系统和外围器件扩展系统。I2C总线的2根线(串行数据SDA,串行时钟SCL)连接到总线上的任何一个器件,每个器件都应有一个唯一的地址,而且都可以作为一个发送器或者一个接受器。此外,器件在执行数据传输时也可以被看作是主机或从机。

发送器:本次传送中发送数据(不包括地址和命令)到总线的器件。

接受器:本次传送中从总线接受数据(不包括地址和命令)的器件。

主机:  初始化发送、产生时钟信号和终止发送的器件,它可以是发送器或接受器。主机通常是微控制器。

从机:  被主机寻址的器件,它可以是发送器或接收器。

        SDA和SCL都是双向线路。连接到总线的器件的输出级必须是漏级开路或集电极开路,都通过一个电流源或上拉电阻连接到正的电源电压,这样才能实现“线与”功能。当总线空闲时,这2条线路都是高电平。

        在标准模式下,总线数据传输的速度为0-100kbit/s,在高速模式下,可以达到0-400kbit/s。总线速率与总线上拉电阻的关系:总线速率越高,总线上拉电阻要越小。100kbit/s总线速率,通常使用5.1k的上拉电阻。

2、数据传输格式

  2.1、空闲状态

         I2C总线总线的SDA和SCL两条信号线同时处于高电平时,规定为总线的空闲状态。此时各个器件的输出级场效应管均处在截止状态,即释放总线,由两条信号线各自的上拉电阻把电平拉高。

 2.2、起始信号和停止信号

       起始信号(重复起始信号):在SCL为高电平时,SDA从高电平向低电平切换。

       停止信号:在SCL为高电平时,SDA由低电平向高电平切换。

                        

        起始信号和停止信号一般由主机产生。起始信号作为一次传输的开始,在起始信号后,总线被认为处于忙的状态。停止信号作为一次传送的结束,在停止信号的某时段后,总线被认为再次处于空闲状态。重复传送信号即作为上次传送的结束,也作为下次传送的开始。

2.3、ACK

          发送器每发送一个字节,就在时钟脉冲9期间释放数据线,由接收器反馈一个应答信号。 应答信号为低电平时,规定为有效应答位(ACK简称应答位),表示接收器已经成功地接收了该字节;应答信号为高电平时,规定为非应答位(NACK),一般表示接收器接收该字节没有成功。 对于反馈有效应答位ACK的要求是,接收器在第9个时钟脉冲之前的低电平期间将SDA线拉低,并且确保在该时钟的高电平期间为稳定的低电平。 如果接收器是主控器,则在它收到最后一个字节后,发送一个NACK信号,以通知被控发送器结束数据发送,并释放SDA线,以便主控接收器发送一个停止信号P。

                       

2.4、数据传输

2.4.1、主设备向从设备发送数据(即写数据)

  1. 主设备发送起始信号;
  2. 主设备发送一个8位地址信号(这里就是我们平常说的设备I2C地址),地址信号第8位为低电平,表示写信号;
  3. 主设备会在发送一个Word address(这里的地址就是我们平常用到写数据进去的寄存器地址);
  4. 发送数据;
  5. 发送停止信号

 

2.4.2、主设备读从设备数据(即读数据)

   读数据比写数据要复杂,在读数据前,首先要告诉从设备哪个寄存器是需要读的,所以读数据前,先会有一个写数据过程。

  1. 主设备发送起始信号;
  2. 主设备发送一个8位地址信号(这里就是我们平常说的设备I2C地址),地址信号第8位为低电平,表示写信号;
  3. 发送内部寄存器地址;
  4. 重复起始信号,重新发送设备地址,此时第8位变成高电平,表示读信号;
  5. 读取数据;
  6. 主设备发送停止信号。

 

3、代码分析

Linux的I2C体系结构分为3个组成部分:

I2C核心:I2C核心提供了I2C总线驱动和设备驱动的注册,注销方法,I2C通信方法(”algorithm”)上层的,与具体适配器无关的代码以及探                    测设备,检测设备地址的上层代码等。

I2C总线驱动:I2C总线驱动是对I2C硬件体系结构中适配器端的实现,适配器可由CPU控制,甚至可以直接集成在CPU内部。

I2C设备驱动:I2C设备驱动(也称为客户驱动)是对I2C硬件体系结构中设备端的实现,设备一般挂接在受CPU控制的I2C适配器上,通过                             I2C适配器与CPU交换数据。

具体框架如下所示:

         I2c利用platform-bus总线进行注册的,device信息在mach-mini210.c中填充,i2c-s3c2410.c通过name匹配,注册device,并且注册i2c_adapter通过__i2c_board_list全局链表匹配版级设备,版级设备在mach-mini210.c中填充,设备驱动通过设备name跟版级设备匹配,具体流程如下图所示:

3.1、device部分

        I2c总线跟其他总线类似,device部分在mini210_machine_init函数中注册,同时会将io资源进行注册,mini210_machine_init开机会运行,mini210_machine_int函数在linux-3.0.8/arch/arm/mach-s5pv210/mini210-lcds.c中定义,如下所示:

struct platform_device s3c_device_i2c0 = {
	.name		  = "s3c2410-i2c",
#ifdef CONFIG_S3C_DEV_I2C1
	.id		  = 0,
#else
	.id		  = -1,
#endif
	.num_resources	  = ARRAY_SIZE(s3c_i2c_resource),
	.resource	  = s3c_i2c_resource,
};
static struct platform_device *mini210_devices[] __initdata = {
………
	&s3c_device_i2c0,
	&s3c_device_i2c1,
	&s3c_device_i2c2,
	………
}
static void __init mini210_machine_init(void)
{
	arm_pm_restart = smdkc110_pm_restart;

	s3c_pm_init();

	mini210_wifi_init();
#ifdef CONFIG_DM9000
	mini210_dm9000_init();
#endif

	/* Disable USB PHY for soft reboot */
	if (__raw_readl(S5P_RST_STAT) & (1 << 3)) {
		__raw_writel(0, S5P_USB_PHY_CONTROL);
	}

	platform_add_devices(mini210_devices, ARRAY_SIZE(mini210_devices));

        设备的版级信息也是在mini210_machine_int函数进行注册:

static struct i2c_board_info mini210_i2c_devs0[] __initdata = {
	{	I2C_BOARD_INFO("24c08", 0x50), },     /* Samsung S524AD0XD1 */
#ifdef CONFIG_SND_SOC_WM8580
	{	I2C_BOARD_INFO("wm8580", 0x1b), },
#endif
#ifdef CONFIG_SND_SOC_WM8960_MINI210
	{
		I2C_BOARD_INFO("wm8960", 0x1a),
		.platform_data = &wm8960_pdata,
	},
#endif
#ifdef CONFIG_SENSORS_MMA7660
	{
		I2C_BOARD_INFO("mma7660", 0x4c),
		.platform_data = &mma7660_pdata,
	},
#endif
};

static void __init mini210_machine_init(void)
{
	s3c_i2c2_set_platdata(&i2c2_data);
	i2c_register_board_info(0, mini210_i2c_devs0,
			ARRAY_SIZE(mini210_i2c_devs0));
	i2c_register_board_info(1, mini210_i2c_devs1,
			ARRAY_SIZE(mini210_i2c_devs1));
	i2c_register_board_info(2, mini210_i2c_devs2,
			ARRAY_SIZE(mini210_i2c_devs2));
#if defined(CONFIG_TOUCHSCREEN_1WIRE)
	onewire_i2c_bdi.irq = gpio_to_irq(S5PV210_GPH1(6));
	i2c_register_board_info(2, &onewire_i2c_bdi, 1);
#endif
#ifdef CONFIG_TOUCHSCREEN_EGALAX
	i2c_register_board_info(5, i2c_devs5, ARRAY_SIZE(i2c_devs5));
#endif

        i2c_register_board_info位注册版级设备,函数中填充好版级信息,然后搜索全局链表__i2c_board_list,如果不存在相应版级信息,将新的版级信息注册到__i2c_board_list链表中:

int __init i2c_register_board_info(int busnum,
	struct i2c_board_info const *info, unsigned len)
{
	int status;

	down_write(&__i2c_board_lock);

	/* dynamic bus numbers will be assigned after the last static one */
	if (busnum >= __i2c_first_dynamic_bus_num)
		__i2c_first_dynamic_bus_num = busnum + 1;

	for (status = 0; len; len--, info++) {
		struct i2c_devinfo	*devinfo;

		devinfo = kzalloc(sizeof(*devinfo), GFP_KERNEL);
		if (!devinfo) {
			pr_debug("i2c-core: can't register boardinfo!\n");
			status = -ENOMEM;
			break;
		}

		devinfo->busnum = busnum;
		devinfo->board_info = *info;
		list_add_tail(&devinfo->list, &__i2c_board_list);
	}

	up_write(&__i2c_board_lock);

	return status;
}

3.2 i2c-core部分

        I2c-core是I2C核心提供了I2C总线驱动和设备驱动的注册,注销方法,I2C通信方法(”algorithm”)上层的,与具体适配器无关的代码以及探测设备,检测设备地址的上层代码等。Tiny210该部分代码在i2c-s3c2410.c中。

       s3c24xx_i2c_probe中会指定i2c通信函数s3c24xx_i2c_algorithm,,初始化平台相关的硬件设备,然后注册一个i2c-adapter,这里先看i2c-adapter是如何注册的。

static int i2c_register_adapter(struct i2c_adapter *adap)
{
	int res = 0;

	/* Can't register until after driver model init */
	if (unlikely(WARN_ON(!i2c_bus_type.p))) {
		res = -EAGAIN;
		goto out_list;
	}

	/* Sanity checks */
	if (unlikely(adap->name[0] == '\0')) {
		pr_err("i2c-core: Attempt to register an adapter with "
		       "no name!\n");
		return -EINVAL;
	}
	if (unlikely(!adap->algo)) {
		pr_err("i2c-core: Attempt to register adapter '%s' with "
		       "no algo!\n", adap->name);
		return -EINVAL;
	}

	rt_mutex_init(&adap->bus_lock);
	mutex_init(&adap->userspace_clients_lock);
	INIT_LIST_HEAD(&adap->userspace_clients);

	/* Set default timeout to 1 second if not already set */
	if (adap->timeout == 0)
		adap->timeout = HZ;

    //设置 adap->dev.kobj.name 为 i2c-0 ,它将出现在 sysfs 中 
	dev_set_name(&adap->dev, "i2c-%d", adap->nr);
    //设置所属的总线
	adap->dev.bus = &i2c_bus_type;
	adap->dev.type = &i2c_adapter_type;
    //将 adap->dev注册到i2c bus总线
	res = device_register(&adap->dev);
	if (res)
		goto out_list;

	dev_dbg(&adap->dev, "adapter [%s] registered\n", adap->name);

#ifdef CONFIG_I2C_COMPAT
	res = class_compat_create_link(i2c_adapter_compat_class, &adap->dev,
				       adap->dev.parent);
	if (res)
		dev_warn(&adap->dev,
			 "Failed to create compatibility class link\n");
#endif

	/* create pre-declared device nodes */
    //扫描 __i2c_board_list 链表里的设备信息,自动创建 client ,并注册到 i2c_bus_type
	if (adap->nr < __i2c_first_dynamic_bus_num)
		i2c_scan_static_board_info(adap);

	/* Notify drivers */
	mutex_lock(&core_lock);
    //遍历 i2c_bus_type的driver 链表,取出每一个driver 调用 i2c_do_add_adapter
	bus_for_each_drv(&i2c_bus_type, NULL, adap, __process_new_adapter);
	mutex_unlock(&core_lock);

	return 0;

out_list:
	mutex_lock(&core_lock);
	idr_remove(&i2c_adapter_idr, adap->nr);
	mutex_unlock(&core_lock);
	return res;
}
static void i2c_scan_static_board_info(struct i2c_adapter *adapter)
{
	struct i2c_devinfo	*devinfo;

	down_read(&__i2c_board_lock);
    //遍历__i2c_board_list链表,取出每一个devinfo
	list_for_each_entry(devinfo, &__i2c_board_list, list) { //每一个设备都注册一个
		if (devinfo->busnum == adapter->nr
				&& !i2c_new_device(adapter,
						&devinfo->board_info))
			dev_err(&adapter->dev,
				"Can't create device at 0x%02x\n",
				devinfo->board_info.addr);
	}
	up_read(&__i2c_board_lock);
}
static void i2c_scan_static_board_info(struct i2c_adapter *adapter)
{
	struct i2c_devinfo	*devinfo;

	down_read(&__i2c_board_lock);
    //遍历__i2c_board_list链表,取出每一个devinfo
	list_for_each_entry(devinfo, &__i2c_board_list, list) { //每一个设备都注册一个
		if (devinfo->busnum == adapter->nr
				&& !i2c_new_device(adapter,
						&devinfo->board_info))
			dev_err(&adapter->dev,
				"Can't create device at 0x%02x\n",
				devinfo->board_info.addr);
	}
	up_read(&__i2c_board_lock);
}

        i2c_scan_static_board_info会扫描__i2c_board_list 链表中i2c_register_board_info填充的版级信息,然后作为device设备注册成i2c bus设备。

3.3 i2c_add_driver注册driver

        i2c_add_driver函数是i2c-core暴露出来提供给设备驱动注册i2c driver的,传入的参数是一个i2c_driver类型结构体:

struct i2c_driver {
	unsigned int class;

	/* Notifies the driver that a new bus has appeared or is about to be
	 * removed. You should avoid using this, it will be removed in a
	 * near future.
	 */
	int (*attach_adapter)(struct i2c_adapter *) __deprecated;
	int (*detach_adapter)(struct i2c_adapter *) __deprecated;

	/* Standard driver model interfaces */
    /*探测函数,匹配成功之后执行,会将匹配到的i2c_client对象传入,
          完成申请资源,初始化,提供接口等工作*/
	int (*probe)(struct i2c_client *, const struct i2c_device_id *);
    /*移除函数,设备消失时会调用,驱动模块被rmmod时也会先被调用,
         完成和probe相反的操作*/
	int (*remove)(struct i2c_client *);

	/* driver model interfaces that don't relate to enumeration  */
    //电源管理相关的接口
	void (*shutdown)(struct i2c_client *);
	int (*suspend)(struct i2c_client *, pm_message_t mesg);
	int (*resume)(struct i2c_client *);

	/* Alert callback, for example for the SMBus alert protocol.
	 * The format and meaning of the data value depends on the protocol.
	 * For the SMBus alert protocol, there is a single bit of data passed
	 * as the alert response's low bit ("event flag").
	 */
	void (*alert)(struct i2c_client *, unsigned int data);

	/* a ioctl like command that can be used to perform specific functions
	 * with the device.
	 */
	int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);

    //设备驱动类,用于匹配设备树的of_match_table域在这里
	struct device_driver driver;
    //用于使用平台文件或模块编写设备信息时进行匹配使用,相当于platform_driver中的id_table
	const struct i2c_device_id *id_table;

	/* Device detection callback for automatic device creation */
	int (*detect)(struct i2c_client *, struct i2c_board_info *);
	const unsigned short *address_list;
    //用于将所有i2c_driver联系到一起的节点
	struct list_head clients;
};

        在设备驱动中,不需要每个变量都定义,只需定义部分变量,下面是goodix TP驱动中注册driver实例:

#ifdef GTP_CONFIG_OF
static const struct of_device_id goodix_match_table[] = {
		{.compatible = "goodix,gt9xx",},
		{ },
};
#endif

static const struct i2c_device_id goodix_ts_id[] = {
    { GTP_I2C_NAME, 0 },
    { }
};
static struct i2c_driver goodix_ts_driver = {
    .probe      = goodix_ts_probe,
    .remove     = goodix_ts_remove,
    .id_table   = goodix_ts_id,
    .driver = {
        .name     = GTP_I2C_NAME,
        .owner    = THIS_MODULE,
#ifdef GTP_CONFIG_OF
        .of_match_table = goodix_match_table,
#endif
#if !defined(CONFIG_FB) && defined(CONFIG_PM)
		.pm		  = &gtp_pm_ops,
#endif
    },
};
ret = i2c_add_driver(&goodix_ts_driver);

        上面注册的driver name跟device注册的name是相同的,i2c_add_driver函数会调用到i2c-core.c中的i2c_register_driver函数:

int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
{
	int res;

	/* Can't register until after driver model init */
	if (unlikely(WARN_ON(!i2c_bus_type.p)))
		return -EAGAIN;

	/* add the driver to the list of i2c drivers in the driver core */
	driver->driver.owner = owner;
	driver->driver.bus = &i2c_bus_type;

	/* When registration returns, the driver core
	 * will have called probe() for all matching-but-unbound devices.
	 */
	res = driver_register(&driver->driver);
	if (res)
		return res;

	/* Drivers should switch to dev_pm_ops instead. */
	if (driver->suspend)
		pr_warn("i2c-core: driver [%s] using legacy suspend method\n",
			driver->driver.name);
	if (driver->resume)
		pr_warn("i2c-core: driver [%s] using legacy resume method\n",
			driver->driver.name);

	pr_debug("i2c-core: driver [%s] registered\n", driver->driver.name);

	INIT_LIST_HEAD(&driver->clients);
	/* Walk the adapters that are already present */
	i2c_for_each_dev(driver, __process_new_driver);

	return 0;
}

       i2c_register_driver中会调用driver_register去注册driver,然后通过i2c_for_each_dev(driver, __process_new_driver);将i2c_driver与i2c_adapter关联起来。

driver_register主要完成下面工作:

  1. 查找bus总线是否注册了driver
  2. bus_add_driver中调用driver_attach,通过__driver_attach函数中的driver_match_device调用bus中的match函数,也是i2c_device_match函数,通过比较i2c_driver->id_table->name和client->name,如果相同,则匹配上,匹配上之后,运行driver_register调用driver_probe_device进行设备与驱动绑定。
  3. __driver_attach中调用driver_probe_device进入不同设备函数中的probe进行匹配。
  4. 在sys下面创建对应的driver节点
  5. 3.4s3c24xx_i2c_algorithm通讯函数
  6. 不同平台的i2c函数都会有自己的通讯函数,tiny210开发板的通讯函数是s3c24xx_i2c_algorithm,linux会将通讯函数进行封装,提供给驱动使用都是i2c_transfer函数。

       i2c_transferàadap->algo->master_xfer(adap, msgs, num)à s3c24xx_i2c_algorithm(s3c24xx_i2c_probe中有填充)

static const struct i2c_algorithm s3c24xx_i2c_algorithm = {
	.master_xfer		= s3c24xx_i2c_xfer,
	.functionality		= s3c24xx_i2c_func,
};
static int s3c24xx_i2c_xfer(struct i2c_adapter *adap,
			struct i2c_msg *msgs, int num)
{
	struct s3c24xx_i2c *i2c = (struct s3c24xx_i2c *)adap->algo_data;
	int retry;
	int ret;

    //使能clk
	clk_enable(i2c->clk);

	for (retry = 0; retry < adap->retries; retry++) {

		ret = s3c24xx_i2c_doxfer(i2c, msgs, num);

		if (ret != -EAGAIN) {
			clk_disable(i2c->clk);
			return ret;
		}

		dev_dbg(i2c->dev, "Retrying transmission (%d)\n", retry);

		udelay(100);
	}

    //关闭clk
	clk_disable(i2c->clk);
	return -EREMOTEIO;
}
static int s3c24xx_i2c_doxfer(struct s3c24xx_i2c *i2c,
			      struct i2c_msg *msgs, int num)
{
	unsigned long timeout;
	int ret;

	if (i2c->suspended)
		return -EIO;

    //设置开始传输寄存器
	ret = s3c24xx_i2c_set_master(i2c);
	if (ret != 0) {
		dev_err(i2c->dev, "cannot get bus (error %d)\n", ret);
		ret = -EAGAIN;
		goto out;
	}

	spin_lock_irq(&i2c->lock);

	i2c->msg     = msgs;
	i2c->msg_num = num;
	i2c->msg_ptr = 0;
	i2c->msg_idx = 0;
	i2c->state   = STATE_START;

    //使能传输中断
	s3c24xx_i2c_enable_irq(i2c);
	s3c24xx_i2c_message_start(i2c, msgs); //数据传输
	spin_unlock_irq(&i2c->lock);

    //超时等待
	timeout = wait_event_timeout(i2c->wait, i2c->msg_num == 0, HZ * 5);

	ret = i2c->msg_idx;

	/* having these next two as dev_err() makes life very
	 * noisy when doing an i2cdetect */

	if (timeout == 0)
		dev_dbg(i2c->dev, "timeout\n");
	else if (ret != num)
		dev_dbg(i2c->dev, "incomplete xfer (%d)\n", ret);

	/* ensure the stop has been through the bus */

	udelay(10);

 out:
	return ret;
}
  1. 、s3c24xx_i2c_set_master

        writel(iicstat & ~S3C2410_IICSTAT_TXRXEN, i2c->regs + S3C2410_IICSTAT); // 1<<4  I2C-bus data output enable/ disable bit

        if (!(readl(i2c->regs + S3C2410_IICSTAT) & S3C2410_IICSTAT_BUSBUSY)// 判断总线是否忙

 

    2、s3c24xx_i2c_enable_irq(i2c)

         readl(i2c->regs + S3C2410_IICCON) //1<<5 I2C-Bus Tx/Rx interrupt enable/ disable bit.

        writel(tmp | S3C2410_IICCON_IRQEN, i2c->regs + S3C2410_IICCON); //第5位置1

 

   3、s3c24xx_i2c_message_start(i2c, msgs)

     s3c24xx_i2c_enable_ack(i2c); // 0xE180_0000写入1<<7位,使能ack位

     writel(stat, i2c->regs + S3C2410_IICSTAT); // 2<<6 Master receive mode  3<<6 Master transmit mode

      writeb(addr, i2c->regs + S3C2410_IICDS);

/*  I2C-Bus busy signal status bit.

 0 = read) Not busy (If read) write) STOP signal generation

1 = read) Busy (If read) write) START signal generation. The data in I2 CDS is transferred automatically just after the start signal

*/

stat |= S3C2410_IICSTAT_START;

    writel(stat, i2c->regs + S3C2410_IICSTAT); //1<<4 i2c-bus data output enable

3.5硬件寄存器设置

       在上面s3c24xx_i2c_probe和s3c24xx_i2c_doxfer都会有一些寄存器设置,下面来看看具体含义。

static int s3c24xx_i2c_init(struct s3c24xx_i2c *i2c)
{
	unsigned long iicon = S3C2410_IICCON_IRQEN | S3C2410_IICCON_ACKEN;
	struct s3c2410_platform_i2c *pdata;
	unsigned int freq;

	/* get the plafrom data */

	pdata = i2c->dev->platform_data;

	/* inititalise the gpio */

	if (pdata->cfg_gpio)
		pdata->cfg_gpio(to_platform_device(i2c->dev));

	/* write slave address */

    //write 0xE1800008 Specifies the I2C-Bus Interface0 address register  P894
	writeb(pdata->slave_addr, i2c->regs + S3C2410_IICADD);

	dev_info(i2c->dev, "slave address 0x%02x\n", pdata->slave_addr);

    //write 0xE1800000; data: 1<<5 | 1<<7  Specifies the I2C-Bus Interface0 control register
	writel(iicon, i2c->regs + S3C2410_IICCON);

	/* we need to work out the divisors for the clock... */

	if (s3c24xx_i2c_clockrate(i2c, &freq) != 0) {
		writel(0, i2c->regs + S3C2410_IICCON);
		dev_err(i2c->dev, "cannot meet bus frequency required\n");
		return -EINVAL;
	}

	/* todo - check that the i2c lines aren't being dragged anywhere */

	dev_info(i2c->dev, "bus frequency set to %d KHz\n", freq);
	dev_dbg(i2c->dev, "S3C2410_IICCON=0x%02lx\n", iicon);

	dev_dbg(i2c->dev, "S3C2440_IICLC=%08x\n", pdata->sda_delay);
    //Specifies the I2C-Bus Interface0 multi-master line control register
	writel(pdata->sda_delay, i2c->regs + S3C2440_IICLC);

	return 0;
}

1、writeb(pdata->slave_addr, i2c->regs + S3C2410_IICADD);

   指定i2c总线0寄存器地址

2、writel(iicon, i2c->regs + S3C2410_IICCON);

   0xE1800000写1<<7,1<<5

3、writel(pdata->sda_delay, i2c->regs + S3C2440_IICLC);

 

 

 

 

 

 

 

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!