打印
[应用相关]

I2C-EEPROM超详细图解(基于STM32hal库 软件模拟加硬件IIC详解附源码)

[复制链接]
459|0
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
一、IIC总线技术概述
1.1 IIC总线的发展历史
IIC(Inter-Integrated Circuit)总线,又称I²C总线,是由飞利浦半导体公司(现为恩智浦NXP)在1980年代开发的一种串行通信总线。最初设计目的是为电视机内的集成电路提供简单的控制接口,后来因其简洁性和高效性在各种嵌入式系统中得到广泛应用。

随着技术的发展,IIC总线标准经历了多次更新:

1982年:首次推出,标准模式速度100kbps

1992年:推出快速模式(400kbps)

1998年:推出高速模式(3.4Mbps)

2007年:推出超快速模式(5Mbps)

1.2 IIC总线的基本特性
IIC总线具有以下显著特点:

两线制结构:仅需串行数据线(SDA)和串行时钟线(SCL)两根信号线

多主多从架构:支持多个主设备和多个从设备连接在同一总线上

地址寻址:每个从设备有唯一的7位或10位地址

半双工通信:同一时间只能进行发送或接收

速率可调:支持从标准模式到高速模式多种传输速率

硬件简单:无需复杂的接口电路,节省PCB空间和成本

1.3 IIC总线的电气特性
IIC总线采用开漏输出结构,需要外接上拉电阻(通常为4.7kΩ)。这种设计具有以下优势:

实现了"线与"逻辑,便于总线仲裁

允许不同电压等级的器件在同一总线上通信

提高了总线的抗干扰能力

工作电压范围通常为1.8V-5V,具体取决于器件规格。总线电容限制一般为400pF,超过此值需要考虑使用总线缓冲器。

二、EEPROM存储器技术
2.1 EEPROM的基本原理
EEPROM(Electrically Erasable Programmable Read-Only Memory)是一种非易失性存储器,其主要特点包括:

数据掉电不丢失

可以字节为单位进行擦写

擦写寿命有限(通常10万-100万次)

读取速度较快,写入速度相对较慢

EEPROM通过浮栅晶体管存储数据,利用F-N隧穿效应实现电子注入和释放,从而改变存储单元的阈值电压来表示"0"和"1"。

2.2 EEPROM的分类
根据接口类型,EEPROM主要分为:

并行EEPROM:数据总线宽度通常为8位,速度快但引脚多

串行EEPROM:包括IIC接口、SPI接口等,节省引脚资源

根据容量大小,常见的EEPROM有:

小容量:1Kbit-64Kbit(如24C01-24C64)

中容量:128Kbit-512Kbit

大容量:1Mbit及以上

2.3 IIC接口EEPROM的特点
IIC接口EEPROM具有以下优势:

引脚精简:通常只需SCL、SDA、VCC、GND和WP(写保护)五个引脚

易于扩展:通过地址引脚可扩展多个器件

低功耗:工作电流小,适合电池供电设备

标准化接口:与各种MCU兼容性好

常见的IIC EEPROM系列包括Microchip的24系列、ST的M24系列等,容量从1Kbit到1Mbit不等。

三、IIC总线协议详解(摘自正点原子)
stm32f407探索者开发板V3 — 正点原子资料下载中心 1.0.0 文档



起始信号:

当 SCL 为高电平期间, SDA 由高到低的跳变。起始信号是一种电平跳变时序信号,而不是
一个电平信号。该信号由主机发出,在起始信号产生后,总线就处于被占用状态,准备数据传
输。
停止信号:
当 SCL 为高电平期间, SDA 由低到高的跳变。停止信号也是一种电平跳变时序信号,而不
是一个电平信号。该信号由主机发出,在停止信号发出后,总线就处于空闲状态。
应答信号:
发送器每发送一个字节,就在时钟脉冲 9 期间释放数据线,由接收器反馈一个应答信号。
应答信号为低电平时,规定为有效应答位( ACK 简称应答位),表示接收器已经成功地接收了
该字节;应答信号为高电平时,规定为非应答位( NACK ),一般表示接收器接收该字节没有成
功。
观察上图标号③就可以发现,有效应答的要求是从机在第 9 个时钟脉冲之前的低电平期间
将 SDA 线拉低,并且确保在该时钟的高电平期间为稳定的低电平。如果接收器是主机,则在它
收到最后一个字节后,发送一个 NACK 信号,以通知被控发送器结束数据发送,并释放 SDA
线,以便主机接收器发送一个停止信号。
数据有效性:
IIC 总线进行数据传送时,时钟信号为高电平期间,数据线上的数据必须保持稳定,只有在
时钟线上的信号为低电平期间,数据线上的高电平或低电平状态才允许变化。数据在 SCL 的上
升沿到来之前就需准备好。并在下降沿到来之前必须稳定。





主机向从机读取数据的操作,一开始的操作与写操作有点相似,观察两个图也可以发现,
都是由主机发出起始信号,接着发送从机地址 +1( 读操作 ) 组成的 8bit 数据,从机接收到数据验
证是否是自身的地址。 那么在验证是自己的设备地址后,从机就会发出应答信号,并向主机返
回 8bit 数据,发送完之后从机就会等待主机的应答信号。假如主机一直返回应答信号,那么从
机可以一直发送数据,也就是图中的( n byte + 应答信号)情况,直到主机发出非应答信号,从
机才会停止发送数据。
四、IIC读取eeprom实战讲解
4.1 软件模拟IIC
       4.11 初始化IIC 的GPIO,既然是软件模拟IIC,开发板所有的引脚都能使用;

void I2C_EE_Init()
{
  GPIO_InitTypeDef  gpio_init_struct;
  __HAL_RCC_GPIOB_CLK_ENABLE();
  gpio_init_struct.Pin =  GPIO_PIN_8;
  gpio_init_struct.Mode = GPIO_MODE_OUTPUT_OD;//开漏
  gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;
  gpio_init_struct.Pull = GPIO_PULLUP;//上拉
  HAL_GPIO_Init(GPIOB,&gpio_init_struct);
  gpio_init_struct.Pin =  GPIO_PIN_9;
  HAL_GPIO_Init(GPIOB,&gpio_init_struct);
  IIC_Stop();
}
     4.12 IIC起始结束时序



void IIC_Start(void)
{
  SCL_HIGH;
  SDA_HIGH;
  Delay_I2c(4);
  SDA_LOW;
  Delay_I2c(4);
  SCL_LOW;
  Delay_I2c(4);//起始信号
}

void IIC_Stop(void)
{
  SDA_LOW;
  Delay_I2c(4);
  SCL_HIGH;
  Delay_I2c(4);
  SDA_HIGH;
  Delay_I2c(4);//停止信号
}

    4.13 IIC发送时序

             

void IIC_Send_Byte(uint8_t data)
{
  uint8_t i;
  for(i = 0 ;i < 8 ;i++)
  {
    if((data & 0x80) >> 7)//高位先发送
    {
      SDA_HIGH;
    }
    else
    {
      SDA_LOW;
    }
                Delay_I2c(4);
    SCL_HIGH;
    Delay_I2c(4);
    SCL_LOW;
    data <<= 1;//下一位
  }
  SDA_HIGH;
}

  4.14 IIC读时序

uint8_t IIC_Recevie_Byte(uint8_t ack)
{
  uint8_t i,data = 0;
  for(i = 0 ;i < 8 ;i++)
  {
    data <<= 1;//高位先输出
    SCL_HIGH;
    Delay_I2c(4);
    if(SDA_READ())
    {
      data ++;
    }

    SCL_LOW;
    Delay_I2c(4);
  }
  if(!ack)
  {
    IIC_noack();
  }
  else
  {
    IIC_ack();
  }
        return data;
}

  4.15 应答信号

    首先先释放 SDA ,把电平拉高,延时等待从机操作 SDA 线,然后主机拉高时 钟线并延时,确保有充足的时间让主机接收到从机发出的 SDA 信号,这里使用的是 IIC_READ_SDA 宏定义去读取,根据 IIC 协议,主机读取 SDA 的值为低电平,就表示“应答信 号”;读到 SDA 的值为高电平,就表示“非应答信号”。在这个等待读取的过程中加入了超时判 断,假如超过这个时间没有接收到数据,那么主机直接发出停止信号,跳出循环,返回等于 1 的变量。在正常等待到应答信号后,主机会把 SCL 时钟线拉低并延时,返回是否接收到应答信 号。
uint8_t IIC_Wait_ack(void)
{
  uint8_t waittime = 0;
  uint8_t rack = 0;
  SDA_HIGH;
  Delay_I2c(4);
  SCL_HIGH;
  Delay_I2c(4);

  while(SDA_READ())
  {
    waittime++;
    if(waittime > 250)
    {
      IIC_Stop();
      rack = 1;
      break;
    }
  }
  SCL_LOW;
  Delay_I2c(4);
  return rack;

}
void IIC_noack()//非应答
{
  SDA_HIGH;
  Delay_I2c(4);
  SCL_HIGH;
  Delay_I2c(4);
  SCL_LOW;
  Delay_I2c(4);
}

void IIC_ack()//应答
{
  SDA_LOW;
  Delay_I2c(4);
  SCL_HIGH;
  Delay_I2c(4);
  SCL_LOW;
  Delay_I2c(4);
  SDA_HIGH;
  Delay_I2c(4);
}

4.16 IIC读写EEPROM驱动代码

  4.161 IIC写操作

void IIC_Write_Byte(uint8_t addr,uint8_t data)
{
  IIC_Start();//发生起始信号

  IIC_Send_Byte(0xA0);//最低位0,表示写入
  IIC_Wait_ack();//每次发完等待应答
  IIC_Send_Byte(addr >> 8);//高字节
  IIC_Wait_ack();
  IIC_Send_Byte(addr % 256);//低字节
  IIC_Wait_ack();

  IIC_Send_Byte(data);//发生一字节
  IIC_Wait_ack();
  IIC_Stop();//停止条件
  HAL_Delay(10);

}

4.162 IIC读操作

uint8_t IIC_Read_Byte(uint8_t addr)
{
  uint8_t data = 0;
  IIC_Start();//起始信号

  IIC_Send_Byte(0xA0);//写入地址
  IIC_Wait_ack();
  IIC_Send_Byte(addr >> 8);
  IIC_Wait_ack();
  IIC_Send_Byte(addr % 256);
  IIC_Wait_ack();

  IIC_Start();
  IIC_Send_Byte(0xA1);//1代表读
  IIC_Wait_ack();
  data = IIC_Recevie_Byte(0);//接受一字节
  IIC_Stop();
  return data;
}

附.h代码:

#define  SCL_LOW  HAL_GPIO_WritePin(GPIOB,GPIO_PIN_8,GPIO_PIN_RESET)
#define  SCL_HIGH HAL_GPIO_WritePin(GPIOB,GPIO_PIN_8,GPIO_PIN_SET)
#define  SDA_LOW  HAL_GPIO_WritePin(GPIOB,GPIO_PIN_9,GPIO_PIN_RESET)
#define  SDA_HIGH HAL_GPIO_WritePin(GPIOB,GPIO_PIN_9,GPIO_PIN_SET)

#define SDA_READ() HAL_GPIO_ReadPin(GPIOB ,GPIO_PIN_9)
4.2 硬件IIC(参考野火)
22. I2C—读写EEPROM — [野火]STM32 HAL库开发实战指南——基于野火F4系列开发板 文档 (embedfire.com)

   硬件IIC与软件IIC不一样,必须用IIC引脚;

4.21 初始化

I2C_HandleTypeDef hi2c1;

/* I2C1 init function */
void MX_I2C1_Init(void)
{

  /* USER CODE BEGIN I2C1_Init 0 */

  /* USER CODE END I2C1_Init 0 */

  /* USER CODE BEGIN I2C1_Init 1 */

  /* USER CODE END I2C1_Init 1 */
  hi2c1.Instance = I2C1;//IIC1
  hi2c1.Init.ClockSpeed = 400000;//速率
  hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2;/*指定时钟占空比,可选low/high = 2:1及16:9模式*/
  hi2c1.Init.OwnAddress1 = 0; /*指定自身的I2C设备地址1,可以是7-bit或者10-bit*/
  hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;/*指定地址的长度模式,可以是7bit模式或者10bit模式 \*/
  hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;/*设置双地址模式 \*/
  hi2c1.Init.OwnAddress2 = 0;/*指定自身的I2C设备地址2,只能是 7-bit \*/
  hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;/*指定广播呼叫模式 \*/
  hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;/*指定禁止时钟延长模式*/
  HAL_I2C_Init(&hi2c1);

  /* USER CODE BEGIN I2C1_Init 2 */
  /* USER CODE END I2C1_Init 2 */

}

void HAL_I2C_MspInit(I2C_HandleTypeDef* i2cHandle)
{

  GPIO_InitTypeDef GPIO_InitStruct = {0};

  /* USER CODE BEGIN I2C1_MspInit 0 */

  /* USER CODE END I2C1_MspInit 0 */

    __HAL_RCC_GPIOB_CLK_ENABLE();
    /**I2C1 GPIO Configuration
    PB6     ------> I2C1_SCL
    PB7     ------> I2C1_SDA
    */
    GPIO_InitStruct.Pin = GPIO_PIN_6|GPIO_PIN_7;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_OD;//复用开漏
    GPIO_InitStruct.Pull = GPIO_NOPULL;//上拉
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF4_I2C1;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

    /* I2C1 clock enable */
    __HAL_RCC_I2C1_CLK_ENABLE();
  /* USER CODE BEGIN I2C1_MspInit 1 */
       
  /* USER CODE END I2C1_MspInit 1 */

}

4.22 向EEPROM写入一个字节的数据

uint32_t I2C_EE_ByteWrite(uint8_t* pBuffer, uint8_t WriteAddr)//数据,地址
{
     HAL_StatusTypeDef status = HAL_OK;

     status = HAL_I2C_Mem_Write(&hi2c1, 0xA0, (uint16_t)
             WriteAddr, I2C_MEMADD_SIZE_8BIT, pBuffer, 1, 100);//IIC1,写操作,地址,字节大小,数据,数据大小,超时时间

     /* Check the communication status */
     if (status != HAL_OK) {
         /* Execute user timeout callback */
         //I2Cx_Error(Addr);
     }
     while (HAL_I2C_GetState(&hi2c1) != HAL_I2C_STATE_READY) {

     }

     /* Check if the EEPROM is ready for a new operation */
     while (HAL_I2C_IsDeviceReady(&hi2c1, 0xA0,
             300, 300) == HAL_TIMEOUT);//等待IIC已经就绪

     /* Wait for the end of the transfer */
     while (HAL_I2C_GetState(&hi2c1) != HAL_I2C_STATE_READY) {

     }
     return status;
}

4.23  EEPROM的页写入

// 向 I2C EEPROM 进行页写入操作
// pBuffer: 指向要写入的数据缓冲区的指针
// WriteAddr: 要写入的起始地址
// NumByteToWrite: 要写入的字节数
// 返回值: HAL 状态类型,用于表示操作的状态
uint32_t I2C_EE_PageWrite(uint8_t* pBuffer, uint8_t WriteAddr,
uint8_t NumByteToWrite)
{
     // 初始化状态为 HAL_OK,表示操作成功
     HAL_StatusTypeDef status = HAL_OK;
     // 调用 HAL_I2C_Mem_Write 函数向 EEPROM 写入数据
     // &hi2c1 是 I2C 句柄,0XA0 是 EEPROM 的设备地址
     // WriteAddr 是要写入的起始地址,I2C_MEMADD_SIZE_8BIT 表示地址大小为 8 位
     // (uint8_t*)(pBuffer) 是要写入的数据缓冲区,NumByteToWrite 是要写入的字节数
     // 100 是超时时间
     status=HAL_I2C_Mem_Write(&hi2c1, 0XA0,WriteAddr,
I2C_MEMADD_SIZE_8BIT, (uint8_t*)(pBuffer),NumByteToWrite, 100);

     // 等待 I2C 总线准备好进行下一次操作
     while (HAL_I2C_GetState(&hi2c1) != HAL_I2C_STATE_READY) {

     }

     // 检查 EEPROM 是否准备好进行新的操作
     // HAL_I2C_IsDeviceReady 函数用于检查设备是否准备好
     // 0xA0 是 EEPROM 的设备地址,300 是重试次数,300 是每次重试的超时时间
     while (HAL_I2C_IsDeviceReady(&hi2c1, 0xA0,
300, 300) == HAL_TIMEOUT);

     // 等待传输结束,确保 I2C 总线处于就绪状态
     while (HAL_I2C_GetState(&hi2c1) != HAL_I2C_STATE_READY) {

     }
     // 返回操作的状态
     return status;
}

4.24 多字节写入

// 向 I2C EEPROM 进行缓冲区写入操作
// pBuffer: 指向要写入的数据缓冲区的指针
// WriteAddr: 要写入的起始地址
// NumByteToWrite: 要写入的字节数
void I2C_EE_BufferWrite(uint8_t* pBuffer, uint8_t WriteAddr,
uint16_t NumByteToWrite)
{
     // 定义变量用于存储页数、剩余字节数、地址偏移量和计数器
     uint8_t NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0;

     // 计算起始地址相对于页大小的偏移量
     Addr = WriteAddr % EEPROM_PAGESIZE;
     // 计算当前页剩余可写入的字节数
     count = EEPROM_PAGESIZE - Addr;
     // 计算要写入的总页数
     NumOfPage =  NumByteToWrite / EEPROM_PAGESIZE;
     // 计算写入所有页后剩余的字节数
     NumOfSingle = NumByteToWrite % EEPROM_PAGESIZE;

     // 如果起始地址是页大小对齐的
     if (Addr == 0) {
         // 如果要写入的字节数小于页大小
         if (NumOfPage == 0) {
             // 直接调用页写入函数写入剩余的字节数
             I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle);
         }
         // 如果要写入的字节数大于页大小
         else {
             // 循环写入所有页
             while (NumOfPage--) {
                 // 调用页写入函数写入一页数据
                 I2C_EE_PageWrite(pBuffer, WriteAddr, EEPROM_PAGESIZE);
                 // 更新写入地址
                 WriteAddr +=  EEPROM_PAGESIZE;
                 // 更新数据缓冲区指针
                 pBuffer += EEPROM_PAGESIZE;
             }

             // 如果还有剩余的字节数
             if (NumOfSingle!=0) {
                 // 调用页写入函数写入剩余的字节数
                 I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle);
             }
         }
     }
     // 如果起始地址不是页大小对齐的
     else {
         // 如果要写入的字节数小于页大小
         if (NumOfPage== 0) {
             // 直接调用页写入函数写入剩余的字节数
             I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle);
         }
         // 如果要写入的字节数大于页大小
         else {
             // 减去当前页可写入的字节数
             NumByteToWrite -= count;
             // 重新计算要写入的总页数
             NumOfPage =  NumByteToWrite / EEPROM_PAGESIZE;
             // 重新计算写入所有页后剩余的字节数
             NumOfSingle = NumByteToWrite % EEPROM_PAGESIZE;

             // 如果当前页还有可写入的字节数
             if (count != 0) {
                 // 调用页写入函数写入当前页剩余的字节数
                 I2C_EE_PageWrite(pBuffer, WriteAddr, count);
                 // 更新写入地址
                 WriteAddr += count;
                 // 更新数据缓冲区指针
                 pBuffer += count;
             }

             // 循环写入所有页
             while (NumOfPage--) {
                 // 调用页写入函数写入一页数据
                 I2C_EE_PageWrite(pBuffer, WriteAddr, EEPROM_PAGESIZE);
                 // 更新写入地址
                 WriteAddr +=  EEPROM_PAGESIZE;
                 // 更新数据缓冲区指针
                 pBuffer += EEPROM_PAGESIZE;
             }
             // 如果还有剩余的字节数
             if (NumOfSingle != 0) {
                 // 调用页写入函数写入剩余的字节数
                 I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle);
             }
         }
     }
}

附.h代码:

/* USER CODE BEGIN Includes */
#define EEPROM_PAGESIZE 8
/* USER CODE END Includes */

extern I2C_HandleTypeDef hi2c1;

/* USER CODE BEGIN Private defines */
void I2C_EE_Init(void);
uint8_t I2C_Test(void);
uint32_t I2C_EE_ByteWrite(uint8_t* pBuffer, uint8_t WriteAddr);

uint32_t I2C_EE_PageWrite(uint8_t* pBuffer, uint8_t WriteAddr,
uint8_t NumByteToWrite);


void I2C_EE_BufferWrite(uint8_t* pBuffer, uint8_t WriteAddr,
uint16_t NumByteToWrite);

uint32_t I2C_EE_BufferRead(uint8_t* pBuffer, uint8_t ReadAddr, uint16_t NumByteToRead);

五. 总结(IIC软件模拟与硬件IIC区别)
硬件电路
硬件 IIC:需要使用芯片内部专门的 IIC 硬件模块,该模块具有独立的时钟发生器、数据寄存器等硬件电路,通过特定的引脚(SCL 时钟线和 SDA 数据线)与外部设备连接。
软件模拟 IIC:利用单片机的普通 I/O 引脚来模拟 IIC 的通信时序,不需要特定的硬件模块,只需要将普通 I/O 引脚配置为输出模式来发送数据和时钟信号,或配置为输入模式来接收数据。
通信速度
硬件 IIC:通信速度通常较快,因为硬件模块是专门为 IIC 通信设计的,能够高效地处理数据的发送和接收,并且可以支持较高的时钟频率。
软件模拟 IIC:速度相对较慢,由于是通过软件程序来模拟通信时序,程序需要执行一系列的指令来控制 I/O 引脚的电平变化,这会花费一定的时间,限制了通信的速度。
代码复杂度
硬件 IIC:使用硬件 IIC 时,芯片厂商通常会提供相应的驱动库或寄存器操作手册,开发者只需要按照规定的方式配置寄存器,即可实现 IIC 通信。代码相对简洁,且不需要过多关注底层的通信时序细节。
软件模拟 IIC:需要开发者自己编写代码来模拟 IIC 的起始信号、停止信号、数据传输、应答信号等各种时序,代码量较大,且需要对 IIC 协议有深入的理解,以确保时序的正确性。
灵活性
硬件 IIC:硬件 IIC 的引脚通常是固定的,一旦确定了使用的硬件 IIC 模块,其对应的引脚就不能再用于其他功能,灵活性较差。如果硬件设计已经确定,后期很难更改 IIC 引脚的分配。
软件模拟 IIC:可以根据实际需求灵活选择任意的普通 I/O 引脚来模拟 IIC 通信,在硬件设计上更加灵活。如果需要更改引脚分配,只需要修改软件代码中的引脚定义即可,而不需要对硬件进行重新设计。
稳定性
硬件 IIC:由于是硬件电路实现,其稳定性较高,能够准确地按照 IIC 协议的规范进行通信,不容易受到软件干扰或其他因素的影响。在复杂的系统中,硬件 IIC 能够可靠地与多个 IIC 设备进行通信。
软件模拟 IIC:稳定性相对较差,容易受到程序中其他代码的影响,例如在模拟 IIC 时序的过程中,如果程序被其他中断打断,可能会导致时序出现错误,从而影响通信的稳定性。

在实际应用中,对于通信速度要求较高、对稳定性要求严格且 IIC 设备数量较多的情况,通常优先选择硬件 IIC;而对于资源有限、对灵活性要求较高或通信速度要求不高的场合,可以考虑使用软件模拟 IIC。


————————————————

                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/m0_75187370/article/details/147099306

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

37

主题

140

帖子

0

粉丝