STM32F4实现SD卡读写

更多交流欢迎关注作者抖音号:81849645041

目的

        熟悉SD卡和SDIO工作原理。掌握SD卡的读写。

原理

        大多单片机系统都需要大容量存储设备,以存储数据。目前常用的有 U 盘,FLASH 芯片, SD 卡等。他们各有优点,综合比较,最适合单片机系统的莫过于 SD 卡了,它不仅容量可以做到很大(32GB 以上),支持 SPI/SDIO 驱动,而且有多种体积的尺寸可供选择(标准的 SD 卡尺寸,以及 TF 卡尺寸等),能满足不同应用的要求。只需要少数几个 IO 口即可外扩一个高达 32GB 以上的外部存储器,容量从几十 M 到几十G 选择尺度很大,更换也很方便,编程也简单,是单片机大容量外部存储器的首选。

        控制器对 SD 卡进行读写通信操作一般有两种通信接口可选,一种是 SPI 接口,另外一种就是 SDIO 接口。

        SDIO 全称是安全数字输入/输出接口,多媒体卡(MMC)、SD 卡、SD I/O 卡都有 SDIO 接口。

        SD I/O 卡本身不是用于存储的卡,它是指利用 SDIO 传输协议的一种外设。比如 Wi-Fi Card,它主要是提供 Wi-Fi 功能,有些 Wi-Fi 模块是使用串口或者 SPI 接口进行通信的,但 Wi-Fi SDIO Card 是使用 SDIO 接口进行通信的。并且一般设计 SD I/O 卡是可以插入到 SD 的插槽。

SD 卡物理结构

        一张 SD 卡包括有存储单元、存储单元接口、电源检测、卡及接口控制器和接口驱动器 5 个部分。

        存储单元是存储数据部件,存储单元通过存储单元接口与卡控制单元进行数据传输;电源检测单元保证 SD 卡工作在合适的电压下,如出现掉电或上状态时,它会使控制单元和存储单元接口复位;卡及接口控制单元控制 SD 卡的运行状态,它包括有 8 个寄存器;接口驱动器控制 SD 卡引脚的输入输出。

         SD 卡总共有 8 个寄存器,用于设定或表示 SD 卡信息。这些寄存器只能通过对应的命令访问,对 SD 卡进行控制操作并不是像操作控制器 GPIO 相关寄存器那样一次读写一个寄存器的,它是通过命令来控制,SDIO 定义了 64 个命令,每个命令都有特殊意义,可以实现某一特定功能,SD 卡接收到命令后,根据命令要求对 SD 卡内部寄存器进行修改,程序控制中只需要发送组合命令就可以实现 SD 卡的控制以及读写操作。

SDIO 总线

        SD 卡总线拓扑如图。虽然可以共用总线,但不推荐多卡槽共用总线信号,要求一个单独 SD 总线应该连接一个单独的 SD 卡。

        SD 卡使用 9-pin 接口通信,其中 3 根电源线、1 根时钟线、1 根命令线和 4 根数据线,具体说明如下:

        CLK:时钟线,由 SDIO 主机产生,即由 STM32 控制器输出;

        CMD:命令控制线,SDIO 主机通过该线发送命令控制 SD 卡,如果命令要求 SD 卡提供应答(响应),SD 卡也是通过该线传输应答信息;

        D0-3:数据线,传输读写数据;SD 卡可将 D0 拉低表示忙状态;

        VDD、VSS1、VSS2:电源和地信号。

        SDIO 不管是从主机控制器向 SD 卡传输,还是 SD 卡向主机控制器传输都只以 CLK 时钟线的上升沿为有效。SD 卡操作过程会使用两种不同频率的时钟同步数据,一 个是识别卡阶段时钟频率 FOD,最高为 400kHz,另外一个是数据传输模式下时钟频率FPP,默认最高为 25MHz,如果通过相关寄存器配置使 SDIO 工作在高速模式,此时数据传输模式最高频率为 50MHz。

        SD 总线通信是基于命令和数据传输的。通讯由一个起始位(“0”),由一个停止位(“1”)终止。SD 通信一般是主机发送一个命令(Command),从设备在接收到命令后作出响应(Response),如有需要会有数据(Data)传输参与。

        SD 数据是以块(Black)形式传输的,SDHC 卡数据块长度一般为 512 字节,数据可以从主机到卡,也可以是从卡到主机。数据块需要 CRC 位来保证数据传输成功。CRC 位由 SD卡系统硬件生成。STM32 控制器可以控制使用单线或 4 线传输,本开发板设计使用 4 线传输。

命令格式

        SD 命令由主机发出,以广播命令和寻址命令为例,广播命令是针对与 SD 主机总线连接的所有从设备发送的,寻址命令是指定某个地址设备进行命令传输。

        SD 命令格式固定为 48bit,都是通过 CMD 线连续传输的(数据线不参与)。

SD 命令的组成如下:

        起始位和终止位:命令的主体包含在起始位与终止位之间,它们都只包含一个数据位,起始位为 0,终止位为 1。

        传输标志:用于区分传输方向,该位为 1 时表示命令,方向为主机传输到 SD 卡,该位为 0 时表示响应,方向为 SD 卡传输到主机。

        命令主体内容包括命令、地址信息/参数和 CRC 校验三个部分。

        命令号:它固定占用 6bit,所以总共有 64 个命令(代号:CMD0~CMD63),每个命令都有特定的用途,部分命令不适用于 SD 卡操作,只是专门用于 MMC 卡或者SD I/O 卡。

        地址/参数:每个命令有 32bit 地址信息/参数用于命令附加内容,例如,广播命令没有地址信息,这 32bit 用于指定参数,而寻址命令这 32bit 用于指定目标 SD 卡的地址。

        CRC7 校验:长度为 7bit 的校验位用于验证命令传输内容正确性,如果发生外部干扰导致传输数据个别位状态改变将导致校准失败,也意味着命令传输失败,SD卡不执行命令。

命令类型

        SD 命令有 4 种类型:

         无响应广播命令(bc),发送到所有卡,不返回任务响应;

        带响应广播命令(bcr),发送到所有卡,同时接收来自所有卡响应;

        寻址命令(ac),发送到选定卡,DAT 线无数据传输;

        寻址数据传输命令(adtc),发送到选定卡,DAT 线有数据传输。

命令描述

        SD 卡系统的命令被分为多个类,每个类支持一种“卡的功能设置”。

响应

        响应由 SD 卡向主机发出,部分命令要求 SD 卡作出响应,这些响应多用于反馈 SD 卡的状态。SDIO 总共有 7 个响应类型(代号:R1~R7),其中 SD 卡没有 R4、R5 类型响应。特定的命令对应有特定的响应类型,比如当主机发送 CMD3 命令时,可以得到响应 R6。与命令一样,SD 卡的响应也是通过 CMD 线连续传输的。根据响应内容大小可以分为短响应和长响应。短响应是 48bit 长度,只有 R2 类型是长响应,其长度为 136bit。

 SD 卡初始化流程

         从图中,我们看到,不管什么卡(这里我们将卡分为 4 类:SD2.0 高容量卡(SDHC,最大32G),SD2.0 标准容量卡(SDSC,最大 2G),SD1.x 卡和 MMC 卡),首先我们要执行的是卡上电(需要设置 SDIO_POWER[1:0]=11),上电后发送 CMD0,对卡进行软复位,之后发送 CMD8命令,用于区分 SD 卡 2.0,只有 2.0 及以后的卡才支持 CMD8 命令,MMC 卡和 V1.x 的卡,是不支持该命令的。CMD8 的格式如表。

         我们需要在发送 CMD8 的时候,通过其带的参数我们可以设置 VHS 位,以告诉 SD卡,主机的供电情况,VHS 位定义如表:

        这里我们使用参数 0X1AA,即告诉 SD 卡,主机供电为 2.7~3.6V 之间,如果 SD 卡支持CMD8,且支持该电压范围,则会通过 CMD8 的响应(R7)将参数部分原本返回给主机,如果不支持 CMD8,或者不支持这个电压范围,则不响应。

        在发送 CMD8 后,发送 ACMD41(注意发送 ACMD41 之前要先发送 CMD55),来进一步 确认卡的操作电压范围,并通过 HCS 位来告诉 SD 卡,主机是不是支持高容量卡(SDHC)。 ACMD41 的命令格式如表:

         ACMD41 得到的响应(R3)包含 SD 卡 OCR 寄存器内容,OCR 寄存器内容定义如表:

        对于支持 CMD8 指令的卡,主机通过 ACMD41 的参数设置 HCS 位为 1,来告诉 SD 卡主 机支 SDHC 卡,如果设置为 0,则表示主机不支持 SDHC 卡,SDHC 卡如果接收到 HCS 为 0则永远不会反回卡就绪状态。对于不支持 CMD8 的卡,HCS 位设置为 0 即可。

        SD 卡在接收到 ACMD41 后,返回 OCR 寄存器内容,如果是 2.0 的卡,主机可以通过判断OCR 的 CCS 位来判断是 SDHC 还是 SDSC;如果是 1.x 的卡,则忽略该位。OCR 寄存器的最后一个位用于告诉主机 SD 卡是否上电完成,如果上电完成,该位将会被置 1。

        对于 MMC 卡,则不支持 ACMD41,不响应 CMD55,对 MMC 卡,我们只需要在发送 CMD0 后,在发送 CMD1(作用同 ACMD41),检查 MMC 卡的 OCR 寄存器,实现 MMC 卡的初始化。

        至此,我们便实现了对 SD 卡的类型区分,流程图中,最后发送了 CMD2 和 CMD3 命令,用于获得卡 CID 寄存器数据和卡相对地址(RCA)。

        CMD2,用于获得 CID 寄存器的数据,CID 寄存器数据各位定义如表:

        SD 卡在收到 CMD2 后,将返回 R2 长响应(136 位),其中包含 128 位有效数据(CID 寄存器内容),存放在 SDIO_RESP1~4 等 4 个寄存器里面。通过读取这四个寄存器,就可以获得SD 卡的 CID 信息。

        CMD3,用于设置卡相对地址(RCA,必须为非 0),对于 SD 卡(非 MMC 卡),在收到CMD3 后,将返回一个新的 RCA 给主机,方便主机寻址。RCA 的存在允许一个 SDIO 接口挂 多个 SD 卡,通过 RCA 来区分主机要操作的是哪个卡。而对于 MMC 卡,则不是由 SD 卡自动返回 RCA,而是主机主动设置 MMC 卡的 RCA,即通过 CMD3 带参数(高 16 位用于 RCA 设置),实现 RCA 设置。同样 MMC 卡也支持一个 SDIO 接口挂多个 MMC 卡,不同于 SD 卡的是所有的 RCA 都是由主机主动设置的,而 SD 卡的 RCA 则是 SD 卡发给主机的。

        在获得卡 RCA 之后,我们便可以发送 CMD9(带 RCA 参数),获得 SD 卡的 CSD 寄存器内容,从 CSD 寄存器,我们可以得到 SD 卡的容量和扇区大小等十分重要的信息。

        至此,我们的 SD 卡初始化基本就结束了,最后通过 CMD7 命令,选中我们要操作的 SD 卡,即可开始对 SD 卡的读写操作了。

准备

        MDK5 开发环境。

        STM32F4xx HAL库。

        STM32F407 开发板。

        STM32F4xx 参考手册。

        STM32F4xx 数据手册。

        STM32F407 开发板电路原理图。

步骤

  • 新建bsp_sd.h和bsp_sd.c。在bsp_sd.h中定义需要的宏和函数。
#ifndef __BSP_SD_H
#define __BSP_SD_H

#include "stm32f4xx.h"

// 支持的SD卡定义
#define SDIO_STD_CAPACITY_SD_CARD_V1_1             ((uint32_t)0x00000000)
#define SDIO_STD_CAPACITY_SD_CARD_V2_0             ((uint32_t)0x00000001)
#define SDIO_HIGH_CAPACITY_SD_CARD                 ((uint32_t)0x00000002)
#define SDIO_MULTIMEDIA_CARD                       ((uint32_t)0x00000003)
#define SDIO_SECURE_DIGITAL_IO_CARD                ((uint32_t)0x00000004)
#define SDIO_HIGH_SPEED_MULTIMEDIA_CARD            ((uint32_t)0x00000005)
#define SDIO_SECURE_DIGITAL_IO_COMBO_CARD          ((uint32_t)0x00000006)
#define SDIO_HIGH_CAPACITY_MMC_CARD                ((uint32_t)0x00000007)

// GPIOC D0-D3  SCK
#define Pin_SDIO_D0_D3	 GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_11
#define Pin_SDIO_SCK 		 GPIO_PIN_12
// GPIOD CMD
#define Pin_SDIO_CMD		 GPIO_PIN_2

uint8_t SDCard_Init(void); // 初始化函数
uint8_t SD_GetCardInfo(HAL_SD_CardInfoTypeDef *cardinfo); // 获取sd卡信息 
void    SDCard_Show_Info(HAL_SD_CardInfoTypeDef *cardinfo);	// 打印sd卡信息
uint8_t SD_ReadDisk(uint8_t* buf, uint32_t sector, uint32_t cnt);	// 读取数据
uint8_t SD_WriteDisk(uint8_t *buf, uint32_t sector, uint32_t cnt); // 写入数据
#endif
  • 在bsp_sd.c中实现sd初始化和读写操作函数。
#include "bsp_sd.h"

SD_HandleTypeDef SD_Handle;

/**
*  函数名:SD_Init
*  描述:SD 卡初始化
*  输入:
*  输出:返回值:0 初始化正确;
*/
uint8_t SDCard_Init(void)
{
	uint8_t sta;
	
	__HAL_RCC_SDIO_CLK_ENABLE();
	__SDIO_CLK_ENABLE();
	
	SD_Handle.Instance = SDIO; 
	SD_Handle.Init.BusWide = SDIO_BUS_WIDE_1B; // 1 位数据线
	SD_Handle.Init.ClockBypass = SDIO_CLOCK_BYPASS_DISABLE; // 不使用 bypass 模式
	SD_Handle.Init.ClockDiv = SDIO_TRANSFER_DIR_TO_SDIO; // SD 传输时钟频率
	SD_Handle.Init.ClockEdge = SDIO_CLOCK_EDGE_RISING; // 上升沿
	SD_Handle.Init.ClockPowerSave = SDIO_CLOCK_POWER_SAVE_DISABLE; // 空闲时不关闭时钟电源
	SD_Handle.Init.HardwareFlowControl = SDIO_HARDWARE_FLOW_CONTROL_DISABLE; // 关闭硬件流控

	sta = HAL_SD_Init(&SD_Handle);
	
	HAL_SD_ConfigWideBusOperation(&SD_Handle,SDIO_BUS_WIDE_4B); // 使能4 位宽总线模式
	
	return sta;
}

/**
*  函数名:HAL_SD_MspInit
*  描述:SD 卡初始化回调函数 用于引脚初始化
*  输入:hsd句柄
*  输出:返回值:0 初始化正确;
*/
void HAL_SD_MspInit(SD_HandleTypeDef *hsd)
{
	GPIO_InitTypeDef  GPIO_InitStruct;
	
	__HAL_RCC_GPIOC_CLK_ENABLE();
	__HAL_RCC_GPIOD_CLK_ENABLE();
	
	GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
	GPIO_InitStruct.Pull = GPIO_PULLUP;
	GPIO_InitStruct.Speed = GPIO_SPEED_FAST;
	GPIO_InitStruct.Alternate = GPIO_AF12_SDIO;
	
	GPIO_InitStruct.Pin = Pin_SDIO_D0_D3 | Pin_SDIO_SCK; // D0~D3 SCK引脚
	HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
	GPIO_InitStruct.Pin = Pin_SDIO_CMD;	// CMD引脚
	HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);
}

/**
*  函数名:SD_GetCardInfo
*  描述:获取卡信息
*  输入:HAL_SD_CardInfoTypeDef
*  输出:状态值
*/
uint8_t SD_GetCardInfo(HAL_SD_CardInfoTypeDef *cardinfo)
{
	uint8_t sta;
	sta = HAL_SD_GetCardInfo(&SD_Handle, cardinfo); // 获取卡信息
	return sta;
}

// 打印SD卡信息
void SDCard_Show_Info(HAL_SD_CardInfoTypeDef *cardinfo)
{
	switch(cardinfo->CardType) //卡类型
	{
		case SDIO_STD_CAPACITY_SD_CARD_V1_1:
			printf("Card Type:SDSC V1.1\r\n");
		break;
		case SDIO_STD_CAPACITY_SD_CARD_V2_0:
			printf("Card Type:SDSC V2.0\r\n");
		break;
		case SDIO_HIGH_CAPACITY_SD_CARD:
			printf("Card Type:SDHC V2.0\r\n");
		break;
		case SDIO_MULTIMEDIA_CARD:
			printf("Card Type:MMC Card\r\n");
		break;
	}
	printf("Card CardVersion:%d\r\n", cardinfo->CardVersion); // 版本号
	printf("Card RelCardAdd:%d\r\n", cardinfo->RelCardAdd); // 卡相对地址
	printf("Card BlockNbr:%d\r\n", cardinfo->BlockNbr); // 显示块数量
	printf("Card BlockSize:%d\r\n", cardinfo->BlockSize); // 显示块大小
}

/**
*  函数名:SD_ReadDisk
*  描述:读 SD 卡
*  输入:buf:读数据缓存区 sector:扇区地址 cnt:扇区个数
*  输出:返回值:错误状态;0,正常;
*/
uint8_t SD_ReadDisk(uint8_t* buf, uint32_t sector, uint32_t cnt)
{
	uint8_t sta;

	sta = HAL_SD_ReadBlocks(&SD_Handle, buf, sector, cnt, 1000);

	return sta;
}

/**
*  函数名:SD_WriteDisk
*  描述:写 SD 卡
*  输入:buf:写数据缓存区 sector:扇区地址 cnt:扇区个数
*  输出:返回值:错误状态;0,正常;
*/
uint8_t SD_WriteDisk(uint8_t *buf, uint32_t sector, uint32_t cnt)
{
	uint8_t sta;

	sta = HAL_SD_WriteBlocks(&SD_Handle, buf, sector, cnt, 1000);

	return sta;
}
  • main.c中读写指定扇区数据
#include "bsp_clock.h"
#include "bsp_uart.h"
#include "bsp_key.h"
#include "bsp_sd.h" 
#include "bsp_led.h"

HAL_SD_CardInfoTypeDef pCardInfo;

int main(void)
{
	uint8_t sta,size;
	uint8_t txbuf[] = {"SD Card ReadWrite Test"};
	size = sizeof(txbuf);
	uint8_t rxbuf[size];
	
	CLOCLK_Init(); // 初始化系统时钟
	UART_Init(); // 串口初始化
	KEY_Init(); // 按键初始化
	LED_Init(); // LED初始化
	
	sta = SDCard_Init();// sd初始化 返回状态
	
	if(sta == HAL_OK) // 初始化成功 
	{
		LED1_ON;
		SD_GetCardInfo(&pCardInfo); // 获取sd卡设备信息
		HAL_Delay(50);
		SDCard_Show_Info(&pCardInfo); // 显示sd卡设备信
	}
	
	while(1)
	{
		uint8_t key = KEY_Scan(0);
		
		if( key == 1) // KEY0按下
		 { 
			 SD_WriteDisk(txbuf, 0, 1); // 发送测试数据
			 printf("Write buf: %s \n", txbuf);
		 }
		
		 if( key == 2) // KEY1按下
		 { 
			 SD_ReadDisk(rxbuf, 0, 1); // 读取
			 printf("Read  buf: %s \n", rxbuf);
		 }
		 
		 HAL_Delay(50);
	}
}

现象

        打开串口助手,把编译好的程序下载到开发板。首先打印SD卡信息。

        按下KEY0 写入数据。按下key1从地址读取数据。

  • 7
    点赞
  • 51
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: 在STM32F4系列中,使用SPI和DMA读写SD卡是相对较简单的操作。 首先,我们需要配置SPI和DMA。对于SPI,我们需要设置SPI的工作模式、数据大小、极性和相位等参数。我们还需要配置DMA来传输数据。DMA可以在SPI和SD卡之间进行数据传输,提高速度和效率。 然后,我们需要初始化SD卡。这包括发送一系列的命令和参数来配置SD卡,使其能够与MCU进行通信。这些命令可以通过SPI进行传输,并且可以使用DMA进行数据传输。 接下来,我们可以使用SPI和DMA来读写SD卡。通过发送读写命令和地址来指定要读写的数据块。通过SPI和DMA进行数据传输,以读取或写入数据。 同时要注意,对于读取数据,我们需要等待SD卡的响应,并确保数据正确接收到MCU。对于写入数据,我们也需要等待SD卡的响应,并检查是否成功写入。 最后,在使用完SD卡后,我们需要进行关闭和释放相关资源的操作。这包括关闭SPI和DMA的功能,并确保SD卡处于适当的状态。 综上所述,通过配置SPI和DMA,初始化SD卡并使用SPI和DMA进行数据传输,我们可以在STM32F4实现SD卡读写操作。这种方法能够提高效率和速度,并简化了操作过程。 ### 回答2: STM32F4 SPI+DMA方式读写SD卡的操作步骤如下: 1. 初始化SPI和DMA:首先需要初始化SPI和DMA模块,设置SPI相关参数,如数据位数、时钟分频等。同时,配置DMA的通道和相关参数,以便实现SPI数据的直接传输。 2. 初始化SD卡:根据SD卡规范,使用SPI发送命令和参数来初始化SD卡。初始化过程中,需要发送CMD0命令以及CMD8命令等,以及在响应中获取SD卡的OCR、CSD等信息。 3. 读写SD卡:使用SPI+DMA方式进行SD卡读写操作。对于读操作,先发送CMD17命令来指定要读取的块地址,然后启动DMA传输,将从SD卡读取的数据保存到目标内存中。对于写操作,先发送CMD24命令来指定要写入的块地址,然后启动DMA传输,将数据从源内存传输到SD卡。 4. 数据检验与校验:在读写操作完成后,需要进行数据的检验与校验。对于读操作,可以使用CRC校验码进行数据的完整性验证;对于写操作,可以使用CRC校验码来确保写入的数据正确。 5. 错误处理与重试:在读写操作中,可能会遇到SPI和DMA传输错误、SD卡响应错误等情况。在这种情况下,可以根据具体的错误类型进行相应的处理和重试操作,例如重新初始化SPI和DMA,重新发送命令等。 总结:通过SPI+DMA方式进行SD卡读写操作,可以提高数据传输的效率和速度,并且能够充分利用STM32F4的硬件资源。在实际操作过程中,需要注意配置SPI和DMA的相关参数,正确发送SD卡的命令和参数,以及处理可能出现的错误情况。 ### 回答3: STM32F4系列微控制器支持SPI(串行外设接口)和DMA(直接内存访问)功能,可以实现SD卡读写操作。 首先,配置SPI硬件资源。选择一个可用的SPI外设和对应的引脚,并配置SPI的时钟频率、数据位、极性、相位等参数。 其次,配置DMA传输。选择一个可用的DMA通道,并设置传输方向、数据宽度、传输数量等参数,以使得DMA能够自动地在SPI和内存之间传输数据。 然后,初始化SD卡。通过发送SD卡命令和接收响应来识别和初始化SD卡,包括设置SPI的工作模式、速度、起始块地址等。 接下来,进行数据传输操作。如果要读取SD卡上的数据,首先发送读数据命令和相应的地址;然后通过DMA启动数据传输,将SD卡中的数据读取到指定的内存地址;最后等待DMA传输完成,并检查传输数据的正确性。 如果要写入数据到SD卡,首先发送写数据命令和相应的地址;然后通过DMA启动数据传输,将指定内存地址的数据写入到SD卡中;最后等待DMA传输完成,并检查写入数据的正确性。 最后,进行数据处理和错误处理。对于读取操作,可以对传输的数据进行解析和处理,以满足应用的需求。对于写入操作,可以检查写入数据是否正确,并处理写入数据过程中可能出现的错误,如超时、电压不稳定等。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

奚海蛟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值