STM32CubeMX学习笔记(25)——FatFs文件系统使用(操作SPI Flash)

   日期:2021-04-03     浏览:141    评论:0    
核心提示:一、FatFs简介FatFs 是面向小型嵌入式系统的一种通用的 FAT 文件系统。它完全是由 ANSI C 语言编写并且完全独立于底层的 I/O 介质。因此它可以很容易地不加修改地移植到其他的处理器当中,如 8051、PIC、AVR、SH、Z80、H8、ARM 等。FatFs 支持 FAT12、FAT16、FAT32 等格式,所以我们利用前面写好的 SPI Flash 芯片驱动,把 FatFs 文件系统代码移植到工程之中,就可以利用文件系统的各种函数,对 SPI Flash 芯片以“文件”格式进行读写操

一、FatFs简介

FatFs 是面向小型嵌入式系统的一种通用的 FAT 文件系统。它完全是由 ANSI C 语言编写并且完全独立于底层的 I/O 介质。因此它可以很容易地不加修改地移植到其他的处理器当中,如 8051、PIC、AVR、SH、Z80、H8、ARM 等。FatFs 支持 FAT12、FAT16、FAT32 等格式,所以我们利用前面写好的 SPI Flash 芯片驱动,把 FatFs 文件系统代码移植到工程之中,就可以利用文件系统的各种函数,对 SPI Flash 芯片以“文件”格式进行读写操作了。

FatFs 文件系统的源码可以从 fatfs 官网下载:
http://elm-chan.org/fsw/ff/00index_e.html

1.1 FatFs文件系统布局

簇是文件存储的最小单元,FAT32分区大小与对应簇空间大小关系如下表示:

分区空间大小 簇空间大小 每个簇包含的扇区数
< 8GB 4KB 8
[ 8GB, 16GB ) 8KB 16
[ 16GB, 32GB ) 16KB 32
>= 32GB 32KB 64

例如:创建一个50字节的test.txt文件,文件大小是50字节,但是占用磁盘空间为4096字节(一个簇)

1.2 FatFs层次结构

  • 最顶层是应用层:使用者只需要调用FATFS模块提供给用户的一系列应用接口函数(如f_open, f_read, f_write和f_close等),就可以像在PC上读写文件那样简单

  • 中间层FATFS模块:实现了FAT文件读写协议;它提供了ff.c和ff.h文件,一般情况下不用修改,使用时将头文件包含进去即可

  • 最底层是FATFS模块的底层接口:包括存储媒介读写接口和供给文件创建修改时间的实时时钟,需要在移植时编写对应的代码

FATFS源码相关文件介绍如下表示;移植FATFS模块时,一般只需要修改2个文件(即ffconf.hdiskio.c

与平台无关:

文件 说明
ffconf.h FATFS模块配置文件
ff.h FATFS和应用模块公用的包含文件
ff.c FATFS模块
diskio.h FATFS和disk I/O模块公用的包含文件
interger.h 数据类型定义
option 可选的外部功能(比如支持中文)

与平台相关:

文件 说明
diskio.c FATFS和disk I/O模块接口层文件

二、新建工程

1. 打开 STM32CubeMX 软件,点击“新建工程”

2. 选择 MCU 和封装

3. 配置时钟
RCC 设置,选择 HSE(外部高速时钟) 为 Crystal/Ceramic Resonator(晶振/陶瓷谐振器)

选择 Clock Configuration,配置系统时钟 SYSCLK 为 72MHz
修改 HCLK 的值为 72 后,输入回车,软件会自动修改所有配置

4. 配置调试模式
非常重要的一步,否则会造成第一次烧录程序后续无法识别调试器
SYS 设置,选择 Debug 为 Serial Wire

三、SPI1

3.1 参数配置

Connectivity 中选择 SPI1 设置,并选择 Full-Duplex Master 全双工主模式,不开启 NSS 即不使用硬件片选信号

原理图中虽然将 CS 片选接到了硬件 SPI1 的 NSS 引脚,因为硬件 NSS 使用比较麻烦,所以后面直接把 PA4 配置为普通 GPIO,手动控制片选信号。

在右边图中找到 SPI1 NSS 对应引脚,选择 GPIO_Output纠正:野火STM32F103指南者开发板SPI1 NSS须配置为PC0

修改输出高电平 High,标签为 W25Q64_CHIP_SELECT

SPI 为默认设置不作修改。只需注意一下,Prescaler 分频系数最低为 4,波特率 (Baud Rate) 为 18.0 MBits/s。这里被限制了,SPI1 最高通信速率可达 36Mbtis/s。

  • Clock Polarity(CPOL):SPI 通讯设备处于空闲状态时,SCK 信号线的电平信号(即 SPI 通讯开始前、 NSS 线为高电平时 SCK 的状态)。CPOL=0 时, SCK 在空闲状态时为低电平,CPOL=1 时,则相反。
  • Clock Phase(CPHA):指数据的采样的时刻,当 CPHA=0 时,MOSI 或 MISO 数据线上的信号将会在 SCK 时钟线的“奇数边沿”被采样。当 CPHA=1 时,数据线在 SCK 的“偶数边沿”采样。

    根据 FLASH 芯片的说明,它支持 SPI 模式0模式 3,支持双线全双工,使用 MSB 先行模式,数据帧长度为 8 位。

    所以这里配置 CPOL 为 Low,CPHA 为 1 Edge 即 SPI 模式0

四、FATFS

4.1 参数配置

Middleware 中选择 FATFS 设置,并勾选 User-defined 因为 SPI Flash 在上面没有

  • Function Parameters 跳过

  • Locale and Namespace Parameters:

    • CODE_PAGE(Code page on target): Simplified Chinese GBK(DBCS,OEM,Windows) 支持简体中文编码
    • USE_LFN(Use Long Filename): Enabled with dynamic working buffer on the STACK 支持长文件名,并指定使用栈空间为缓冲区

缓存工作区为什么放在栈?其实fatfs提供了三个选项:BSS,STACK , HEAP,根据个人情况选一个。
在BSS上启用带有静态工作缓冲区的LFN,不能动态分配。
如果选择了HEAP(堆)且自己有属于自己的malloc就去重写ff_memalloc  ff_memfree函数。如果是库的malloc就不需要。
一般都选择使用STACK(栈),能动态分配。
当使用堆栈作为工作缓冲区时,请注意堆栈溢出。

  • Physical Drive Parameters:
    • VOLUMES(Logical drivers): 2 指定物理设备数量,这里设置为 2,包括预留 SD 卡和 SPI Flash 芯片
    • MAX_SS(Maximum Sector Size): 4096 指定扇区大小的最大值。SD 卡扇区大小一般都为 512 字节,SPI Flash 芯片扇区大小一般设置为 4096 字节,所以需要把 _MAX_SS 改为 4096
    • MIN_SS(Minimum Sector Size): 512 指定扇区大小的最小值

4.2 增大栈空间

将最小栈空间改到 0x1000

4.3 生成代码

输入项目名和项目路径

选择应用的 IDE 开发环境 MDK-ARM V5

每个外设生成独立的 ’.c/.h’ 文件
不勾:所有初始化代码都生成在 main.c
勾选:初始化代码生成在对应的外设文件。 如 GPIO 初始化代码生成在 gpio.c 中。

点击 GENERATE CODE 生成代码

五、添加SPI Flash操作函数

user_diskio.c 中加入

//#define SPI_FLASH_PageSize 4096
#define SPI_FLASH_PageSize 256
#define SPI_FLASH_PerWritePageSize 256

#define ManufactDeviceID_CMD 0x90
#define READ_STATU_REGISTER_1 0x05
#define READ_STATU_REGISTER_2 0x35
#define READ_DATA_CMD 0x03
#define WRITE_ENABLE_CMD 0x06
#define WRITE_DISABLE_CMD 0x04
#define SECTOR_ERASE_CMD 0x20
#define CHIP_ERASE_CMD 0xc7
#define PAGE_PROGRAM_CMD 0x02
                  
#define SPI_FLASH_CS_LOW() HAL_GPIO_WritePin(W25Q64_CHIP_SELECT_GPIO_Port, W25Q64_CHIP_SELECT_Pin, GPIO_PIN_RESET);
#define SPI_FLASH_CS_HIGH() HAL_GPIO_WritePin(W25Q64_CHIP_SELECT_GPIO_Port, W25Q64_CHIP_SELECT_Pin, GPIO_PIN_SET);

extern SPI_HandleTypeDef hspi1; 


static HAL_StatusTypeDef SPI_Transmit(uint8_t* send_buf, uint16_t size)
{ 
    return HAL_SPI_Transmit(&hspi1, send_buf, size, 100);
}

static HAL_StatusTypeDef SPI_Receive(uint8_t* recv_buf, uint16_t size)
{ 
   return HAL_SPI_Receive(&hspi1, recv_buf, size, 100);
}

static HAL_StatusTypeDef SPI_TransmitReceive(uint8_t* send_buf, uint8_t* recv_buf, uint16_t size)
{ 
   return HAL_SPI_TransmitReceive(&hspi1, send_buf, recv_buf, size, 100);
}


#define SPIT_FLAG_TIMEOUT ((uint32_t)0x1000)
#define SPIT_LONG_TIMEOUT ((uint32_t)(10 * SPIT_FLAG_TIMEOUT))
static __IO uint32_t  SPITimeout = SPIT_LONG_TIMEOUT;   

static  uint16_t SPI_TIMEOUT_UserCallback(uint8_t errorCode)
{ 
  
  printf("SPI 等待超时!errorCode = %d",errorCode);
  return 0;
}
 
uint8_t SPI_FLASH_SendByte(uint8_t byte)
{ 
  SPITimeout = SPIT_FLAG_TIMEOUT;

  
  while (__HAL_SPI_GET_FLAG( &hspi1, SPI_FLAG_TXE ) == RESET)
   { 
    if((SPITimeout--) == 0) return SPI_TIMEOUT_UserCallback(0);
   }

  
  WRITE_REG(hspi1.Instance->DR, byte);

  SPITimeout = SPIT_FLAG_TIMEOUT;

  
  while (__HAL_SPI_GET_FLAG( &hspi1, SPI_FLAG_RXNE ) == RESET)
   { 
    if((SPITimeout--) == 0) return SPI_TIMEOUT_UserCallback(1);
   }

  
  return READ_REG(hspi1.Instance->DR);
}


uint16_t W25QXX_ReadID(void)
{ 
    uint8_t recv_buf[2] = { 0};    //recv_buf[0]存放Manufacture ID, recv_buf[1]存放Device ID
    uint16_t device_id = 0;
    uint8_t send_data[4] = { ManufactDeviceID_CMD,0x00,0x00,0x00};   //待发送数据,命令+地址
    
    
    SPI_FLASH_CS_LOW();
    
    
    if (HAL_OK == SPI_Transmit(send_data, 4)) 
    { 
        if (HAL_OK == SPI_Receive(recv_buf, 2)) 
        { 
            device_id = (recv_buf[0] << 8) | recv_buf[1];
        }
    }
    
    
    SPI_FLASH_CS_HIGH();
    
    return device_id;
}

static uint8_t W25QXX_ReadSR(uint8_t reg)
{ 
    uint8_t result = 0; 
    uint8_t send_buf[4] = { 0x00,0x00,0x00,0x00};
    switch(reg)
    { 
        case 1:
            send_buf[0] = READ_STATU_REGISTER_1;
        case 2:
            send_buf[0] = READ_STATU_REGISTER_2;
        case 0:
        default:
            send_buf[0] = READ_STATU_REGISTER_1;
    }
    
    
    SPI_FLASH_CS_LOW();
    
    if (HAL_OK == SPI_Transmit(send_buf, 4)) 
    { 
        if (HAL_OK == SPI_Receive(&result, 1)) 
        { 
            
            SPI_FLASH_CS_HIGH();
            
            return result;
        }
    }
    
    
    SPI_FLASH_CS_HIGH();

    return 0;
}

static void W25QXX_Wait_Busy(void)
{ 
    while((W25QXX_ReadSR(1) & 0x01) == 0x01); // 等待BUSY位清空
}

void W25QXX_Write_Enable(void)
{ 
    uint8_t cmd= WRITE_ENABLE_CMD;
    
    SPI_FLASH_CS_LOW();
    
    SPI_Transmit(&cmd, 1);
    
    SPI_FLASH_CS_HIGH();
    
    W25QXX_Wait_Busy();

}

void W25QXX_Write_Disable(void)
{ 
    uint8_t cmd = WRITE_DISABLE_CMD;

    SPI_FLASH_CS_LOW();
    
    SPI_Transmit(&cmd, 1);
    
    SPI_FLASH_CS_HIGH();
    
    W25QXX_Wait_Busy();
}

void W25QXX_Erase_Sector(uint32_t sector_addr)
{    
    W25QXX_Write_Enable();  //擦除操作即写入0xFF,需要开启写使能
    W25QXX_Wait_Busy();     //等待写使能完成
   
    
    SPI_FLASH_CS_LOW();
    
    
    SPI_FLASH_SendByte(SECTOR_ERASE_CMD);
    
    SPI_FLASH_SendByte((sector_addr & 0xFF0000) >> 16);
    
    SPI_FLASH_SendByte((sector_addr & 0xFF00) >> 8);
    
    SPI_FLASH_SendByte(sector_addr & 0xFF);
    
    
    SPI_FLASH_CS_HIGH();
    
    W25QXX_Wait_Busy();       //等待扇区擦除完成
}

void W25QXX_PageProgram(uint8_t* dat, uint32_t WriteAddr, uint16_t nbytes)
{ 
    uint8_t cmd = PAGE_PROGRAM_CMD;
    
// WriteAddr <<= 8;
    
    W25QXX_Write_Enable();
    
    
    SPI_FLASH_CS_LOW();
    
    SPI_Transmit(&cmd, 1);

// SPI_Transmit((uint8_t*)&WriteAddr, 3);
    uint8_t addr;
    HAL_StatusTypeDef status;
    
    addr = (WriteAddr & 0xFF0000) >> 16;
    status = SPI_Transmit(&addr, 1);
    
    addr = (WriteAddr & 0xFF00) >> 8;
    status = SPI_Transmit(&addr, 1);
    
    addr = WriteAddr & 0xFF;
    status = SPI_Transmit(&addr, 1);
    
    SPI_Transmit(dat, nbytes);
    
    
    SPI_FLASH_CS_HIGH();
    
    W25QXX_Wait_Busy();
}

void W25QXX_BufferWrite(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite)
{ 
    uint8_t NumOfPage = 0;
    uint8_t NumOfSingle = 0;
    uint8_t Addr = 0;
    uint8_t count = 0;
    uint8_t temp = 0;
    
    
    Addr = WriteAddr % SPI_FLASH_PageSize;
    
    
    count = SPI_FLASH_PageSize - Addr;  
    
    NumOfPage =  NumByteToWrite / SPI_FLASH_PageSize;
    
    NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;

    
    if(Addr == 0) 
    { 
        
        if(NumOfPage == 0) 
        { 
            W25QXX_PageProgram(pBuffer, WriteAddr, NumByteToWrite);
        }
        
        else
        { 
            
            while(NumOfPage--)
            { 
                W25QXX_PageProgram(pBuffer, WriteAddr, SPI_FLASH_PageSize);
                WriteAddr +=  SPI_FLASH_PageSize;
                pBuffer += SPI_FLASH_PageSize;
            }
            
            
            W25QXX_PageProgram(pBuffer, WriteAddr, NumOfSingle);
        }
    }
    
    else 
    { 
        
        if(NumOfPage == 0) 
        { 
            
            if(NumOfSingle > count) 
            { 
                temp = NumOfSingle - count;
                
                
                W25QXX_PageProgram(pBuffer, WriteAddr, count);
                WriteAddr += count;
                pBuffer += count;
                
                
                W25QXX_PageProgram(pBuffer, WriteAddr, temp);
            }
            
            else
            {                
                W25QXX_PageProgram(pBuffer, WriteAddr, NumByteToWrite);
            }
        }
        
        else
        { 
            
            NumByteToWrite -= count;
            NumOfPage =  NumByteToWrite / SPI_FLASH_PageSize;
            NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;

            W25QXX_PageProgram(pBuffer, WriteAddr, count);
            WriteAddr +=  count;
            pBuffer += count;
            
            
            while(NumOfPage--)
            { 
                W25QXX_PageProgram(pBuffer, WriteAddr, SPI_FLASH_PageSize);
                WriteAddr +=  SPI_FLASH_PageSize;
                pBuffer += SPI_FLASH_PageSize;
            }
            
            if(NumOfSingle != 0)
            { 
                W25QXX_PageProgram(pBuffer, WriteAddr, NumOfSingle);
            }
        }
    }
}
 
void W25QXX_BufferRead(uint8_t* pBuffer, uint32_t ReadAddr, uint16_t NumByteToRead)
{   
    W25QXX_Wait_Busy();
    
    
    SPI_FLASH_CS_LOW();

    
    uint8_t cmd = READ_DATA_CMD;
    SPI_Transmit(&cmd, 1);

// 不知道为什么连起来发不行
// ReadAddr = ReadAddr << 8;
// SPI_Transmit((uint8_t*)&ReadAddr, 3);
    
    uint8_t addr;
    HAL_StatusTypeDef status;
    
    addr = (ReadAddr & 0xFF0000) >> 16;
    status = SPI_Transmit(&addr, 1);
    
    addr = (ReadAddr& 0xFF00) >> 8;
    status = SPI_Transmit(&addr, 1);
    
    addr = ReadAddr & 0xFF;
    status = SPI_Transmit(&addr, 1);
    
    if(HAL_OK == status)
    { 
        SPI_Receive(pBuffer, NumByteToRead);
    }

    
    SPI_FLASH_CS_HIGH();
}

六、修改diskio接口函数

user_diskio.c 中修改以下几个函数:




DSTATUS USER_initialize (
	BYTE pdrv           
)
{ 
  
    
    uint16_t i;
    i = 500;
    while(--i);
    Stat = STA_NOINIT;
	if(W25QXX_ReadID() != 0)
    { 
        Stat &= ~STA_NOINIT;
    }		
    return Stat;
  
}


DSTATUS USER_status (
	BYTE pdrv       
)
{ 
  
    Stat &= ~STA_NOINIT;
    return Stat;
  
}


DRESULT USER_read (
	BYTE pdrv,      
	BYTE *buff,     
	DWORD sector,   
	UINT count      
)
{ 
  
	DRESULT status = RES_PARERR;
    if(!count)
    { 
        return RES_PARERR; //count不能等于0,否则返回参数错误
    }
   // 
   // sector += 512; 
    W25QXX_BufferRead(buff, sector << 12, count << 12);
    status = RES_OK;
	return status;
  
}


#if _USE_WRITE == 1
DRESULT USER_write (
	BYTE pdrv,          
	const BYTE *buff,   
	DWORD sector,       
	UINT count          
)
{ 
  
    uint32_t write_addr; 
	DRESULT status = RES_PARERR;
	if(!count) 
    { 
		return RES_PARERR;		
	}
    //
    //sector += 512;
    write_addr = sector << 12;    
    W25QXX_Erase_Sector(write_addr);
    W25QXX_BufferWrite((uint8_t *)buff, write_addr, count << 12);
    status = RES_OK;
	return status;
  
}
#endif 


#if _USE_IOCTL == 1
DRESULT USER_ioctl (
	BYTE pdrv,      
	BYTE cmd,       
	void *buff      
)
{ 
  
	DRESULT status = RES_OK;
    switch(cmd) 
    { 
        case CTRL_SYNC :
            break;
        
        case GET_SECTOR_COUNT:
          *(DWORD * )buff = 1536;		
            break;
        
        case GET_SECTOR_SIZE :
          *(WORD * )buff = 4096;
            break;
        
        case GET_BLOCK_SIZE :
          *(DWORD * )buff = 1;
            break;        
        case CTRL_TRIM:
            break;
		default:
			status = RES_PARERR;
            break;      
	}
	return status;
  
}
#endif 

七、修改main函数



UINT fnum;                          
BYTE ReadBuffer[1024] = { 0};        
BYTE WriteBuffer[]= "Hello World!\n";




int main(void)
{ 
  

  

  

  
  HAL_Init();

  

  

  
  SystemClock_Config();

  

  

  
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_USART1_UART_Init();
  MX_SPI1_Init();
  MX_FATFS_Init();
  
  printf("****** 这是一个SPI FLASH 文件系统实验 ******\r\n");
  
  // 在外部SPI Flash挂载文件系统,文件系统挂载时会对SPI设备初始化
  retUSER = f_mount(&USERFatFS, USERPath, 1);
	
    
  
  if(retUSER == FR_NO_FILESYSTEM)
  { 
    printf("》FLASH还没有文件系统,即将进行格式化...\r\n");
    
    retUSER = f_mkfs(USERPath, 0, 0);	    
		
    if(retUSER == FR_OK)
    { 
      printf("》FLASH已成功格式化文件系统。\r\n");
      
      retUSER = f_mount(NULL, USERPath, 1);			
      			
      retUSER = f_mount(&USERFatFS, USERPath, 1);
    }
    else
    { 
      printf("《《格式化失败。》》\r\n");
      while(1);
    }
  }
  else if(retUSER != FR_OK)
  { 
    printf("!!外部Flash挂载文件系统失败。(%d)\r\n", retUSER);
    printf("!!可能原因:SPI Flash初始化不成功。\r\n");
    while(1);
  }
  else
  { 
    printf("》文件系统挂载成功,可以进行读写测试\r\n");
  }
  
  
  
  printf("\r\n****** 即将进行文件写入测试... ******\r\n");	
  retUSER = f_open(&USERFile, "test.txt", FA_CREATE_ALWAYS | FA_WRITE);
  if(retUSER == FR_OK)
  { 
    printf("》打开/创建FatFs读写测试文件.txt文件成功,向文件写入数据。\r\n");
    
    retUSER = f_write(&USERFile, WriteBuffer, sizeof(WriteBuffer), &fnum);
    if(retUSER == FR_OK)
    { 
      printf("》文件写入成功,写入字节数据:%d\n", fnum);
      printf("》向文件写入的数据为:\r\n%s\r\n", WriteBuffer);
    }
    else
    { 
      printf("!!文件写入失败:(%d)\n", retUSER);
    }    
    
    f_close(&USERFile);
  }
  else
  { 	
    printf("!!打开/创建文件失败。\r\n");
  }
	

	printf("****** 即将进行文件读取测试... ******\r\n");
	retUSER = f_open(&USERFile, "test.txt",FA_OPEN_EXISTING | FA_READ); 	 
	if(retUSER == FR_OK)
	{ 
		printf("》打开文件成功。\r\n");
		retUSER = f_read(&USERFile, ReadBuffer, sizeof(ReadBuffer), &fnum); 
    if(retUSER==FR_OK)
    { 
      printf("》文件读取成功,读到字节数据:%d\r\n",fnum);
      printf("》读取得的文件数据为:\r\n%s \r\n", ReadBuffer);	
    }
    else
    { 
      printf("!!文件读取失败:(%d)\n",retUSER);
    }		
	}
	else
	{ 
		printf("!!打开文件失败。\r\n");
	}
	
	f_close(&USERFile);	
  
	
	f_mount(NULL,"1:",1);
  

  
  
  while (1)
  { 
    

    
  }
  
}

八、查看打印

串口打印功能查看 STM32CubeMX学习笔记(6)——USART串口使用

九、工程代码

链接:(https://pan.baidu.com/s/1WAfBj091e7IJVLwaL1HyHA)[https://pan.baidu.com/s/1WAfBj091e7IJVLwaL1HyHA] 提取码:w24p

十、注意事项

用户代码要加在 USER CODE BEGIN NUSER CODE END N 之间,否则下次使用 STM32CubeMX 重新生成代码后,会被删除。

• 由 Leung 写于 2021 年 4 月 2 日

• 参考:【STM32CubeMx你不知道的那些事】第九章:STM32CubeMx的SPI外置FLASH+文件系统(FATFS)
    STM32CubeMX系列|FATFS文件系统
    使用STM32CUBEMX生成FatFS代码,操作SPI FLASH
    STM32CUBEIDE之SPI读写FLASH进阶串行FLASH文件系统FatFs
    3.1、CUBEMX使用FATFS读写SPI_FLASH

 
打赏
 本文转载自:网络 
所有权利归属于原作者,如文章来源标示错误或侵犯了您的权利请联系微信13520258486
更多>最近资讯中心
更多>最新资讯中心
更多>相关资讯中心
0相关评论

推荐图文
推荐资讯中心
点击排行
最新信息
新手指南
采购商服务
供应商服务
交易安全
关注我们
手机网站:
新浪微博:
微信关注:

13520258486

周一至周五 9:00-18:00
(其他时间联系在线客服)

24小时在线客服