基于S3C2440——SD/MMC

下面内容是转载一为网友的内容:http://blog.163.com/zhouhui_1102/blog/static/1504807482011513724255/

之前在http://blog.163.com/zhouhui_1102/blog/static/1504807482011429115933380/,也就是我的SD卡笔记上说了一些需要主要的地方。在这里关于SD和MMC的基础概念我就不提了。一般百科会有相关的知识,可以自行搜索。

这里就笔记上提出来的那两个编程流程图来进行编程。如果还不了解的可以自行查阅我的笔记,或者自己下载SD的datasheet。里面有很详细的说明。
这里说明下我的硬件配置是:2440板子,2G Kingston SD卡。
废话不多说,直接看源码。

###########################################
//------------------------SDI.h------------------------

#ifndef __SDI_H__
#define __SDI_H__

#define U32 unsigned int
#define U16 unsigned short
#define U8  unsigned char
#define PCLK  50000000
typedef struct SD_STRUCT
{
	U8 sdiWide; // 0:1bit, 1:4bit
	U8 sdiType;  // 0:SD  , 1:MMC
	U16 sdiRCA;
	U8 cCardCID[16]; // 卡的CID信息
	U32 lCardCSD[4]; // 卡的CSD信息
	U32 lSectorSize; /* 一次可擦除的块个数 */ 
	U32 lCardSize; //卡容量(单位:字节)
}sdi_set;  //卡的信息结构体

void test_sdi();
U8 sdi_init();
U32 SDI_Check_CMD_End(int cmd, int be_resp);
void CMD0(void);
U8 CMD1(void);
U8 CMD2(U8 *cCID_Info);
U8 CMD3(U16 iCardType,U16 *iRCA);
U8 CMD7(U8 cSorD,U16 iRCA);
U8 CMD9(U16 iRCA,U32 *lCSD);
U8 CMD12(void);
U16 CMD13(U16 iRCA);
U8 CMD17(U32 Addr);
U8 CMD18(U32 Addr);
U8 CMD24(U32 Addr);
U8 CMD25(U32 Addr);
U8 CMD55(U16 iRCA);
U8 ACMD6(U8 BusWidth,U16 iRCA);
U8 ACMD41(U16 iRCA);
U32 SDI_MMC_OCR(void);
U32 SDI_SD_OCR(void);
U8 select_or_deselect(U8 cSelDesel,U16 iCardRCA);
U8 Set_bus_Width(U8 cCardType,U8 cBusWidth,U16 iRCA);
U8 Read_Block(U32 Addr,U32* RxBuffer,U32 block_num);
U8 Write_Block(U32 Addr,U32* TxBuffer,U32 block_num);
void Delay(U32 i);
#endif

##############################################


########################################
//------------------------SDI.c------------------------

#define _SD_DEBUG_
#define SDCARD_BUFF_SIZE 512

U8 cTxBuffer[SDCARD_BUFF_SIZE];
U8 cRxBuffer[SDCARD_BUFF_SIZE];
sdi_set SDCard;

void test_sdi()  //测试函数
{
	U32 i;
	if(sdi_init())
	{
#ifdef _SD_DEBUG_
		printf("SDI初始化结束!\r\n");
#endif
	}
	else
	{
#ifdef _SD_DEBUG_
		printf("SDI初始化出错,终止!\r\n");
#endif
		return;
	}
	for(i=0;i<512;i++)
	{
		cTxBuffer[i]=i+1;
		cRxBuffer[i]=0;
	}

	if(Write_Block(0,(U32 *)cTxBuffer,1))
	{
#ifdef _SD_DEBUG_
		printf("\r\n写入SD卡0地址数据成功!");
#endif
	}
	else
	{
#ifdef _SD_DEBUG_
		printf("\r\n写入SD卡0地址数据出错,终止!");
#endif
		return;
	}
	if(Read_Block(0,(U32 *)cRxBuffer,1))
	{
#ifdef _SD_DEBUG_
		printf("\r\n读出SD卡0地址数据成功!");
#endif
		printf("\r\n读出的数据:\r\n");
		for(i=0;i<512;i+=4)
		{
			if(i%32)
				printf("\r\n");
			printf("0x%x ",cRxBuffer[i]);
			printf("0x%x ",cRxBuffer[i+1]);
			printf("0x%x ",cRxBuffer[i+2]);
			printf("0x%x ",cRxBuffer[i+3]);

		}
	}
	else
	{
#ifdef _SD_DEBUG_
		printf("\r\n读出SD卡0地址数据出错,终止!");
#endif
		return;
	}
	select_or_deselect(0,SDCard.sdiRCA);
}

U8 sdi_init()
{
	int i;
#ifdef _SD_DEBUG_
	printf("\r\nSDI初始化开始!");
#endif
	GPEUP  = 0xf83f;     // The pull up  1111 1000 0011 1111  必须上拉
    GPECON = 0xaaaaaaaa;  // 1010 1010 1010 1010 1010 1010 1010 1010
    SDICSTA = 0xffff;   //SDI指令状态
    SDIDSTA = 0xffff;//SDI数据状态

	SDIPRE = 124; // 400KHz  波特率设置 频率 PCLK/400K -1
    SDICON=(1<<4)|1; // Type B,  clk enable SDI控制
    SDIFSTA|=1<<16; //FIFO reset
	SDIBSIZE=0x200; // 512byte(128word)  SDI块大小
	SDIDTIMER=0x7fffff; // Set timeout count 数据传输超时时间
	//等待74个CLK
    for(i=0;i<0x1000;i++);  
	
	CMD0(); //先执行CMD0,复位
	//判断卡的类型
	if(SDI_MMC_OCR()) 
	{
		SDCard.sdiType = 1;  //卡为MMC
	}
	else
	{
		SDCard.sdiType = 0;  //卡为SD
	}
	//检测SD卡
	if(SDI_SD_OCR())  //检测SD
    {
#ifdef _SD_DEBUG_
		printf("SD is ready\r\n");
#endif
	} else{
#ifdef _SD_DEBUG_
		printf("Initialize fail\r\nNo Card assertion\r\n");
#endif
        return 0;
    }     
	//读CID
	if(CMD2(SDCard.cCardCID))
	{
#ifdef _SD_DEBUG_
		printf("CID\r\n");
		printf("MID = %d\r\n",SDCard.cCardCID[0]);
		printf("OLD = %d\r\n",(SDCard.cCardCID[1]*0X100)+SDCard.cCardCID[2]);
		printf("生产厂家:%s\r\n",(SDCard.cCardCID+3));
		printf("生产日期:20%d,%d\r\n",((SDCard.cCardCID[13]&0x0f)<<4)+((SDCard.cCardCID[14]&0xf0)>>4),(SDCard.cCardCID[14]&0x0f));
#endif
	}
	else
	{
#ifdef _SD_DEBUG_
		printf("Read Card CID is fail!\r\n");
#endif
		return 0;
	}
	//设置RCA  MMC的RCA=1   SD的RCA=0
	if(SDCard.sdiType==1)  //MMC
	{
		if(CMD3(1,&SDCard.sdiRCA))
		{
			SDCard.sdiRCA = 1;
			SDIPRE = 2;  //16MHZ
#ifdef _SD_DEBUG_
			printf("MMC Card RCA = 0x%x\r\n",SDCard.sdiRCA);
			printf("MMC Frequency is %dHz\r\n",(PCLK/(SDIPRE+1)));
#endif
		}
		else
		{
#ifdef _SD_DEBUG_
			printf("Read MMC RCA is fail!\r\n");
#endif
			return 0;
		}
	}
	else   //SD
	{
		if(CMD3(0,&SDCard.sdiRCA))
		{
			SDIPRE = 1; // Normal clock=25MHz
#ifdef _SD_DEBUG_
			printf("SD Card RCA = 0x%x\r\n",SDCard.sdiRCA);
			printf("SD Frequency is %dHz\r\n",(PCLK/(SDIPRE+1)));
#endif
		}
		else
		{
#ifdef _SD_DEBUG_
			printf("Read SD RCA is fail!\r\n");
#endif
			return 0;
		}
	}
	//读CSD
	if(CMD9(SDCard.sdiRCA,SDCard.lCardCSD))
	{
		SDCard.lCardSize = (((SDCard.lCardCSD[1]&0x0000003f)<<16)+((SDCard.lCardCSD[2]&0xffff0000)>>16)+1)*512;
		SDCard.lSectorSize = ((SDCard.lCardCSD[2]>>6)&0x0000007f)+1;
#ifdef _SD_DEBUG_
		printf("Read Card CSD OK!\r\n");
		printf("0x%08x\r\n",SDCard.lCardCSD[0]);
		printf("0x%08x\r\n",SDCard.lCardCSD[1]);
		printf("0x%08x\r\n",SDCard.lCardCSD[2]);
		printf("0x%08x\r\n",SDCard.lCardCSD[3]);
		printf("卡容量为:%dKB,%dMB\r\n",SDCard.lCardSize,SDCard.lCardSize/1024);
#endif
	}
	else
	{
#ifdef _SD_DEBUG_
		printf("Read Card CSD Fail!\r\n");
#endif
		return 0;
	}
	//选中卡  CMD7  进入传输状态
	if(select_or_deselect(1,SDCard.sdiRCA))//1表示选中卡 
	{
#ifdef _SD_DEBUG_
		printf("Card sel desel OK!\r\n");
#endif
	}
	else
	{
#ifdef _SD_DEBUG_
		printf("Card sel desel fail!\r\n");
#endif
		return 0;
	}
	//CMD13 查询是否为传输状态
	while((CMD13(SDCard.sdiRCA)&0x1e00) != 0x800);
	//设置总线带宽 ACMD6
	if(Set_bus_Width(SDCard.sdiType,1,SDCard.sdiRCA))
	{
		SDCard.sdiWide = 1;
#ifdef _SD_DEBUG_
		printf("Bus Width is 4bit\r\n");
#endif
	}
	else
	{
		SDCard.sdiWide = 0;
#ifdef _SD_DEBUG_
		printf("Bus Width is 1bit\r\n");
#endif
	}
	return 1;
}


U32 SDI_Check_CMD_End(int cmd, int be_resp)  //检查CMD是否结束
{
    int finish0;

    if(!be_resp)    // No response   
    {
     finish0=SDICSTA;
while((finish0&0x800)!=0x800) // Check cmd end
   finish0=SDICSTA;

SDICSTA=finish0;// Clear cmd end state

#ifdef _SD_DEBUG_
printf("%x\r\n", finish0);
#endif

return 1;
    }
    else // With response
    {
     finish0=SDICSTA;
while( !( ((finish0&0x200)==0x200) | ((finish0&0x400)==0x400) ))    // Check cmd/rsp end
        finish0=SDICSTA;

#ifdef _SD_DEBUG_
printf("CMD%d:SDICSTA=0x%x, SDIRSP0=0x%x\r\n",cmd, SDICSTA, SDIRSP0);   
#endif
   

if(cmd==1 | cmd==9 | cmd==41) // CRC no check
{
   if( (finish0&0xf00) != 0xa00 )  // Check error
    {
SDICSTA=finish0;   // Clear error state 

if(((finish0&0x400)==0x400))
{
#ifdef _SD_DEBUG_
printf("CMD%d Time out!\r\n", cmd);
#endif
    return 0; // Timeout error     
    }
    
} 
   SDICSTA=finish0; // Clear cmd & rsp end state
   // printf("%x\r\n", finish0);
}
else // CRC check
{
    if( (finish0&0x1f00) != 0xa00 ) // Check error
   { 
SDICSTA=finish0;   // Clear error state

if(((finish0&0x400)==0x400))
{
#ifdef _SD_DEBUG_ 
printf("CMD%d Time out!\r\n", cmd);
#endif
    return 0; // Timeout error
}
}
     SDICSTA=finish0;
}
return 1;
    }
}

//复位,使卡进入IDEL状态
void CMD0(void)
{
SDICARG = 0x0; 
SDICCON = (1<<8)|0x40; // No_resp, start

SDI_Check_CMD_End(0, 0);
SDICSTA = 0x800; // Clear cmd_end(no rsp)
}

//设置工作电压是根据SD的OCR寄存器来设置
U8 CMD1(void)
{
SDICARG = 0xff8000; //(SD OCR:2.7V~3.6V)
SDICCON = (0x1<<9)|(0x1<<8)|0x41; //sht_resp, wait_resp, start, 

if(SDI_Check_CMD_End(1, 1)) //[31]:Card Power up status bit (busy)
{
if((SDIRSP0>>16)==0x80ff)
{
SDICSTA = 0xa00; // Clear cmd_end(with rsp)
return 1; // Success
}
else
return 0;
}
return 0;
}
//请求设备在CMD上传送CID
U8 CMD2(U8 *cCID_Info) 
{
SDICARG = 0x0;
SDICCON = (0x1<<10)|(0x1<<9)|(0x1<<8)|0x42; //lng_resp, wait_resp, start

if(!SDI_Check_CMD_End(2, 1)) 
return 0;
*(cCID_Info+0) = SDIRSP0>>24;
*(cCID_Info+1) = SDIRSP0>>16;
*(cCID_Info+2) = SDIRSP0>>8;
*(cCID_Info+3) = SDIRSP0;
*(cCID_Info+4) = SDIRSP1>>24;
*(cCID_Info+5) = SDIRSP1>>16;
*(cCID_Info+6) = SDIRSP1>>8;
*(cCID_Info+7) = SDIRSP1;
*(cCID_Info+8) = SDIRSP2>>24;
*(cCID_Info+9) = SDIRSP2>>16;
*(cCID_Info+10) = SDIRSP2>>8;
*(cCID_Info+11) = SDIRSP2;
*(cCID_Info+12) = SDIRSP3>>24;
*(cCID_Info+13) = SDIRSP3>>16;
*(cCID_Info+14) = SDIRSP3>>8;
*(cCID_Info+15) = SDIRSP3;
SDICSTA = 0xa00; // Clear cmd_end(with rsp)
return 1;
}
//给SD卡设定一个相对地址,也就是寻址的地址 = 0:SD卡,=1:MMC卡 =0 失败 =1 成功
U8 CMD3(U16 iCardType,U16 *iRCA) 
{
SDICARG = iCardType<<16;     // (MMC:Set RCA, SD:Ask RCA-->SBZ)
SDICCON = (0x1<<9)|(0x1<<8)|0x43; // sht_resp, wait_resp, start

if(!SDI_Check_CMD_End(3, 1)) 
return 0;
SDICSTA=0xa00; // Clear cmd_end(with rsp)

if(iCardType)
{
*iRCA = 1;
}
else 
    {
*iRCA =( SDIRSP0 & 0xffff0000 )>>16;
}
if( SDIRSP0 & 0x1e00!=0x600 )   // CURRENT_STATE check
return 0;
else
return 1;
}

//选中卡或者解除选中 cSorD=1为选中 为0则解除选中
U8 CMD7(U8 cSorD,U16 iRCA) 
{
if(cSorD)
{
SDICARG = iRCA<<16; // (RCA,stuff bit)
SDICCON = (0x1<<9)|(0x1<<8)|0x47;   // sht_resp, wait_resp, start
if(!SDI_Check_CMD_End(7, 1))
return 0;
SDICSTA = 0xa00; // Clear cmd_end(with rsp)
//--State(transfer) check
if( SDIRSP0 & 0x1e00!=0x800 )
return 0;
else
return 1;
}
else
{
SDICARG = 0<<16; //(RCA,stuff bit)
SDICCON = (0x1<<8)|0x47; //no_resp, start

if(!SDI_Check_CMD_End(7, 0))
return 0;
SDICSTA = 0x800; //Clear cmd_end(no rsp)
return 1;
}
}
//获取卡的CSD寄存器的值
U8 CMD9(U16 iRCA,U32 *lCSD) 
{
SDICARG = iRCA<<16; // (RCA,stuff bit)
SDICCON = (0x1<<10)|(0x1<<9)|(0x1<<8)|0x49; // long_resp, wait_resp, start

if(!SDI_Check_CMD_End(9, 1)) 
return 0;

*(lCSD+0) = SDIRSP0;
*(lCSD+1) = SDIRSP1;
*(lCSD+2) = SDIRSP2;
*(lCSD+3) = SDIRSP3;
return 1;
}

//停止数据传输
U8 CMD12(void) 
{
SDICARG = 0x0;    
SDICCON = (0x1<<9)|(0x1<<8)|0x4c; //sht_resp, wait_resp, start,

if(!SDI_Check_CMD_End(12, 1)) 
return 0;
else
SDICSTA = 0xa00; //Clear cmd_end(with rsp)
return 1;
}
//获取卡内状态
U16 CMD13(U16 iRCA) 
{
SDICARG = iRCA<<16; // (RCA,stuff bit)
SDICCON = (0x1<<9)|(0x1<<8)|0x4d; // sht_resp, wait_resp, start

if(!SDI_Check_CMD_End(13, 1)) 
return 0;

SDICSTA=0xa00; // Clear cmd_end(with rsp)
return SDIRSP0;
}

//读取一个数据块
U8 CMD17(U32 Addr) 
{
    //STEP1:发送指令 
    SDICARG = Addr; //设定指令参数 
    SDICCON = (1<<9)|(1<<8)|0x51; //发送CMD17指令
    
    if(SDI_Check_CMD_End(17,1))
     return 1;
    else
     return 0;
}

//读取多个数据块
U8 CMD18(U32 Addr) 
{
    //STEP1:发送指令 
    SDICARG = Addr; //设定指令参数 
    SDICCON = (1<<9)|(1<<8)|0x52; //发送CMD18指令
    
    if(SDI_Check_CMD_End(18,1))
     return 1;
    else
     return 0;
}

//写入一个数据块
U8 CMD24(U32 Addr) 
{
    //STEP1:发送指令 
    SDICARG = Addr; //设定指令参数 
    SDICCON = (1<<9)|(1<<8)|0x58; //发送CMD24指令
    
    if(SDI_Check_CMD_End(24,1))
     return 1;
    else
     return 0;
}
//写入多个数据块
U8 CMD25(U32 Addr) 
{
    //STEP1:发送指令 
    SDICARG = Addr; //设定指令参数 
    SDICCON = (1<<9)|(1<<8)|0x59; //发送CMD25指令
    
    if(SDI_Check_CMD_End(25,1))
     return 1;
    else
     return 0;
}
//检测是否有卡,执行ACMD必须先执行CMD55
U8 CMD55(U16 iRCA) 
{
SDICARG = iRCA<<16;
SDICCON = (0x1<<9)|(0x1<<8)|0x77; //sht_resp, wait_resp, start

if(!SDI_Check_CMD_End(55, 1)) 
return 0;
SDICSTA = 0xa00; // Clear cmd_end(with rsp)
return 1;
}
// ACMD6命令为设置总线带宽 [1:0] 00为1bit  10为4bit
U8 ACMD6(U8 BusWidth,U16 iRCA) 
{
if(!CMD55(iRCA))
return 0;

SDICARG = BusWidth<<1;     //Wide 0: 1bit, 1: 4bit
SDICCON = (0x1<<9)|(0x1<<8)|0x46; //sht_resp, wait_resp, start

if(!SDI_Check_CMD_End(6, 1)) 
return 0;
SDICSTA=0xa00;     // Clear cmd_end(with rsp)
return 1;
}

//检测是否为SD卡及类型 =0应答错误或者卡正忙  =1标准SD卡 =2SDHC V2.0
U8 ACMD41(U16 iRCA) 
{
U8 cReturn;
if(!CMD55(iRCA)) 
return 0;
SDICARG=0x40ff8000; //ACMD41(SD OCR:2.7V~3.6V)
SDICCON=(0x1<<9)|(0x1<<8)|0x69;//sht_resp, wait_resp, start, ACMD41

if(SDI_Check_CMD_End(41, 1)) 
{
if(SDIRSP0==0xc0ff8000)
cReturn = 2; //SDHC
else if(SDIRSP0==0x80ff8000)
cReturn = 1; //标准SD
else
cReturn = 0; //应答错误
SDICSTA = 0xa00; // Clear cmd_end(with rsp)
return cReturn; // Success    
}
SDICSTA = 0xa00; // Clear cmd_end(with rsp)
return 0;
}
//检测MMC卡
U32 SDI_MMC_OCR(void)
{
    int i;
    //-- Negotiate operating condition for MMC, it makes card ready state
    for(i=0;i<10;i++)
    {
if(CMD1())
return 1;
    }
    return 0; // Fail
}
//检测SD卡
U32 SDI_SD_OCR(void)
{
    int i;

    SDCard.sdiRCA = 0;
    for(i=0;i<50;i++)
    {
     if(ACMD41(SDCard.sdiRCA))
return 1;
Delay(1000);
    }
return 0;  //fail
}
//运用CMD7来选中或解除选中卡,返回1则成功,0失败
U8 select_or_deselect(U8 cSelDesel,U16 iCardRCA)
{
if(CMD7(cSelDesel,iCardRCA))
return 1;
else
return 0;
}
//设置总线带宽
U8 Set_bus_Width(U8 cCardType,U8 cBusWidth,U16 iRCA)
{
if(cCardType==1) //MMC,返回0不需要设置 默认为1bit总线带宽
return 0;
return ACMD6(cBusWidth,iRCA);
}


/**********************************************************************************
功  能:该函数用于从SD卡中读出指定块起始地址的单个或多个数据块
参  数:
 U32  Addr  被读块的起始地址
 U32* RxBuffer 用于接收读出数据的缓冲区
 U32 block_num 读的块数
返回值:
 0 读块操作不成功
 1 读块操作成功
**********************************************************************************/
U8 Read_Block(U32 Addr,U32* RxBuffer,U32 block_num)
{
U32 i=0;
U32 status=0;

SDIDTIMER=0x7fffff; // Set timeout count
SDIBSIZE=0x200; // 512byte(128word)
SDIFSTA=SDIFSTA|(1<<16); // FIFO reset
SDIDCON=(block_num<<0)|(2<<12)|(1<<14)|(SDCard.sdiWide<<16)|(1<<17)|(1<<19)|(2<<22);
while(CMD18(Addr)!=1)//发送读多个块指令
{
SDICSTA=0xF<<9;
}
 
while(i<block_num*128)
{ //开始接收数据到缓冲区
if(SDIDSTA&0x60)
{ //检查是否超时和CRC校验是否出错
SDIDSTA=(0x3<<0x5); //清除超时标志和CRC错误标志
return 0;
}
status=SDIFSTA;
if((status&0x1000)==0x1000)
{ //如果接收FIFO中有数据
*RxBuffer=SDIDAT;
RxBuffer++;
i++;
}
}
 
SDIDCON=SDIDCON&~(7<<12);
SDIFSTA = SDIFSTA&0x200;//Clear Rx FIFO Last data Ready 
SDIDSTA = 0x10;//Clear data Tx/Rx end detect 
while(CMD12()!=1)//发送结束指令 
{
SDICSTA=0xF<<9;
}

return 1;
}

/**********************************************************************************
功  能:该函数用于向SD卡的一个或多个数据块写入数据
参  数:
 U32  Addr  被写块的起始地址
 U32* TxBuffer 用于发送数据的缓冲区
 U32 block_num  块数
返回值:
 0 数据写入操作失败
 1 数据写入操作成功
**********************************************************************************/
U8 Write_Block(U32 Addr,U32* TxBuffer,U32 block_num)
{
U16 i=0;
U32 status = 0;

SDIDTIMER=0x7fffff; // Set timeout count
SDIBSIZE=0x200; // 512byte(128word)
SDIFSTA = SDIFSTA|(1<<16); // FIFO reset
SDIDCON = (block_num<<0)|(3<<12)|(1<<14)|(1<<16)|(1<<17)|(1<<20)|(2<<22);

while(CMD25(Addr)!=1)//发送写多个块指令
{
SDICSTA=0xF<<9;
}
while(i<block_num*128)
{ //开始传递数据到缓冲区
status=SDIFSTA;
if((status&0x2000)==0x2000)
{ //如果发送FIFO可用,即FIFO未满
SDIDAT=*TxBuffer;
TxBuffer++;
i++;
}
}
SDIDCON = SDIDCON&~(7<<12);
 
while(CMD12()!=1)//发送结束指令 
{
SDICSTA=0xF<<9;
}
do
{ //等待数据发送结束
status=SDIDSTA;
}while((status&0x2)==0x2);
SDIDSTA = status;
 

SDIDSTA=0xf4;
   return 1;
}

void Delay(U32 i)
{
while(i--);
}

###########################################


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值