SD卡读写

本文详细介绍了SD卡的协议,包括命令结构、响应模式和初始化步骤,并提供了FPGA实现SD卡读写操作的Verilog代码,涵盖了读写命令、数据传输等关键过程。

特别说明:以下所涉及代码参考网络、相关开发板厂家。只用于自己学习、理解和感悟。

SD存储卡( Secure Digital Memory Card)是一种基于半导体快闪存储器的新一代高速存储设备。SD 卡是现在嵌入式设备重要的存储模块。通常SD卡中都有文件系统,可以安装文件名和目录路径来读写文件。SD卡结构如下图所示:
在这里插入图片描述

  1. SD卡协议简介

在这里插入图片描述

数明:SD卡命令是6字节组成的命令包。
a) 命令码:
命令码由1个字节组成,第7位和第6位固定为“01”。
b) 校验与结束码:
校验与结束码由7位CRC校验和1位结束码组成。
c) 命令参数码:
2. 响应:
SD卡对每个命令多有响应,在SPI模式下有三种响应模式:R1、R2、R3。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  1. SD卡2.0版的初始化步骤:
    a.上电后延时至少 74clock,等待 SD 卡内部操作完成。
    b. 片选 CS 低电平选中 SD 卡。
    c. 发送 CMD0,需要返回 0x01,进入 Idle 状态。
    d. 为了区别 SD 卡是 2.0 还是 1.0,或是 MMC 卡,这里根据协议向上兼容的,首先发送只有 SD2.0 才有的命令 CMD8,如果 CMD8 返回无错误,则初步判断为 2.0 卡,进一步循环发送命令CMD55+ACMD41,直到返回 0x00,确定 SD2.0 卡。
    e. 如果 CMD8 返回错误则判断为 1.0 卡还是 MMC 卡,循环发送 CMD55+ACMD41,返回无错误,
    则为 SD1.0 卡,到此 SD1.0 卡初始成功,如果在一定的循环次数下,返回为错误,则进一步发
    送 CMD1 进行初始化,如果返回无错误,则确定为 MMC 卡,如果在一定的次数下,返回为错
    误,则不能识别该卡,初始化结束。(通过 CMD16 可以改变 SD 卡一次性读写的长度)。
    f. CS 拉高。

  2. SD卡的读步骤:
    a. 发送 CMD17(单块)或 CMD18(多块)读命令,返回 0X00。
    b. 接收数据开始令牌 fe(或 fc)+正式数据 512Bytes + CRC 校验 2Bytes 默认正式传输的数据长度是 512Bytes。

  3. SD 卡的写步骤:
    a 发送 CMD24(单块)或 CMD25(多块)写命令,返回 0X00。
    b. 发送数据开始令牌 fe(或 fc)+正式数据 512Bytes + CRC 校验 2Bytes。

相关代码如下:

  1. spi_master.v
    module spi_master(
    input sys_clk,
    input rst,
    output nCS,
    output DCLK,
    output MOSI,
    input MISO,
    input CPOL,
    input CPHA,
    input nCS_ctrl,
    input[15:0] clk_div,
    input wr_req,
    output wr_ack,
    input[7:0] data_in,
    output[7:0] data_out
    );
    localparam IDLE = 0;
    localparam DCLK_EDGE = 1;
    localparam DCLK_IDLE = 2;
    localparam ACK = 3;
    localparam LAST_HALF_CYCLE = 4;
    localparam ACK_WAIT = 5;
    reg DCLK_reg;
    reg[7:0] MOSI_shift;
    reg[7:0] MISO_shift;
    reg[2:0] state;
    reg[2:0] next_state;
    reg[15:0] clk_cnt;
    reg[4:0] clk_edge_cnt;
    assign MOSI = MOSI_shift[7];
    assign DCLK = DCLK_reg;
    assign data_out = MISO_shift;
    assign wr_ack = (state == ACK);
    assign nCS = nCS_ctrl;
    always @(posedge sys_clk or posedge rst)
    begin
    if(rst)
    state <= IDLE;
    else
    state <= next_state;
    end
    always @(*)
    begin
    case(state)
    IDLE:
    if(wr_req == 1’b1)
    next_state <= DCLK_IDLE;
    else
    next_state <= IDLE;
    DCLK_IDLE:
    if(clk_cnt == clk_div)
    next_state <= DCLK_EDGE;
    else
    next_state <= DCLK_IDLE;
    DCLK_EDGE:
    if(clk_edge_cnt == 5’d15)
    next_state <= LAST_HALF_CYCLE;
    else
    next_state <= DCLK_IDLE;
    LAST_HALF_CYCLE:
    if(clk_cnt == clk_div)
    next_state <= ACK;
    else
    next_state <= LAST_HALF_CYCLE;
    ACK:
    next_state <= ACK_WAIT;
    ACK_WAIT:
    next_state <= IDLE;
    default:
    next_state <= IDLE;
    endcase
    end
    always @(posedge sys_clk or posedge rst)
    begin
    if(rst)
    DCLK_reg <= 1’b0;
    else if(state == IDLE)
    DCLK_reg <= CPOL;
    else if(state == DCLK_EDGE)
    DCLK_reg <= ~DCLK_reg;
    end
    always @(posedge sys_clk or posedge rst)
    begin
    if(rst)
    clk_cnt <= 16’d0;
    else if(state == DCLK_IDLE || state == LAST_HALF_CYCLE)
    clk_cnt <= clk_cnt + 16’d1;
    else
    clk_cnt <= 16’d0;
    end
    //SPI时钟边沿计数
    always @(posedge sys_clk or posedge rst)
    begin
    if(rst)
    clk_edge_cnt <= 5’d0;
    else if(state == DCLK_EDGE)
    clk_edge_cnt <= clk_edge_cnt + 5’d1;
    else if(state == IDLE)
    clk_edge_cnt <= 5’d0;
    end
    //SPI数据输出
    always @(posedge sys_clk or posedge rst)
    begin
    if(rst)
    MOSI_shift <= 8’d0;
    else if(state == IDLE && wr_req)
    MOSI_shift <= data_in;
    else if(state == DCLK_EDGE)
    if(CPHA == 1’b0 && clk_edge_cnt[0] == 1’b1)
    MOSI_shift <= {MOSI_shift[6:0],MOSI_shift[7]};
    else if(CPHA == 1’b1 && (clk_edge_cnt != 5’d0 && clk_edge_cnt[0] == 1’b0))
    MOSI_shift <= {MOSI_shift[6:0],MOSI_shift[7]};
    end
    //SPI数据输入
    always @(posedge sys_clk or posedge rst)
    begin
    if(rst)
    MISO_shift <= 8’d0;
    else if(state == IDLE && wr_req)
    MISO_shift <= 8’h00;
    else if(state == DCLK_EDGE)
    if(CPHA == 1’b0 && clk_edge_cnt[0] == 1’b0)
    MISO_shift <= {MISO_shift[6:0],MISO};
    else if(CPHA == 1’b1 && (clk_edge_cnt[0] == 1’b1))
    MISO_shift <= {MISO_shift[6:0],MISO};
    end

endmodule

7.sd_card_cmd.v
module sd_card_cmd(
input sys_clk, //时钟输入
input rst, //异步复位输入,高复位
input[15:0] spi_clk_div, //SPI时钟分频,SPI时钟频率=系统时钟/((spi_clk_div+2)*2)
input cmd_req, //SD卡命令请求
output cmd_req_ack, //SD卡命令请求应答
output reg cmd_req_error, //SD卡命令请求错误
input[47:0] cmd, //SD卡命令,命令+参数+CRC,一共 48bit
input[7:0] cmd_r1, //sd卡命令期待的 R1响应
input[15:0] cmd_data_len, //sd卡命令后读取的数据长度,大部分命令没有读取数据
input block_read_req, //块数据读取请求
output reg block_read_valid, //块数据读取数据有效
output reg[7:0] block_read_data, //块数据读取数据
output block_read_req_ack, //块数据读取请求应答
input block_write_req, //块数据写请求
input[7:0] block_write_data, //块数据写数据
output block_write_data_rd, //块数据写数据请求,提前 block_write_data一个时钟周期
output block_write_req_ack, //块数据写请求应答
output nCS_ctrl, //到 SPI master控制器,cs片选控制
output reg[15:0] clk_div, //到 SPI Master控制器,时钟分频参数
output reg spi_wr_req, //到 SPI Master控制器,写一个字节请求
input spi_wr_ack, //来自 SPI Master控制器,写请求应答
output[7:0] spi_data_in, //到 SPI Master控制器,写数据
input[7:0] spi_data_out //来自 SPI Master控制器,读数据
);
parameter S_IDLE = 0;
parameter S_WAIT = 1;
parameter S_INIT = 2;
parameter S_CMD_PRE = 3;
parameter S_CMD = 4;
parameter S_CMD_DATA = 5;
parameter S_READ_WAIT = 6;
parameter S_READ = 7;
parameter S_READ_ACK = 8;
parameter S_WRITE_TOKEN = 9;
parameter S_WRITE_DATA_0 = 10;
parameter S_WRITE_DATA_1 = 11;
parameter S_WRITE_CRC = 12;
parameter S_WRITE_SUC = 13;
parameter S_WRITE_BUSY = 14;
parameter S_WRITE_ACK = 15;
parameter S_ERR = 16;
parameter S_END = 17;

reg[4:0] state;
reg CS_reg;
reg[15:0] byte_cnt;
reg[7:0] send_data;
wire[7:0] data_recv;
reg[9:0] wr_data_cnt;
assign cmd_req_ack = (state == S_END);
assign block_read_req_ack = (state == S_READ_ACK);
assign block_write_req_ack = (state == S_WRITE_ACK);
assign block_write_data_rd = (state == S_WRITE_DATA_0);
assign spi_data_in = send_data;
assign data_recv = spi_data_out;
assign nCS_ctrl = CS_reg;

always @(posedge sys_clk or posedge rst)
begin
if(rst == 1’b1)
begin
CS_reg <= 1’b1;
spi_wr_req <= 1’b0;
byte_cnt <= 16’d0;
clk_div <= 16’d0;
send_data <= 8’hff;
state <= S_IDLE;
cmd_req_error <= 1’b0;
wr_data_cnt <= 10’d0;
end
else
case(state)
S_IDLE:
begin
state <= S_INIT;
clk_div <= spi_clk_div;
CS_reg <= 1’b1;
end
S_INIT:
begin
if(spi_wr_ack == 1’b1)
begin
if(byte_cnt >= 16’d10)
begin
byte_cnt <= 16’d0;
spi_wr_req <= 1’b0;
state <= S_WAIT;
end
begin
byte_cnt <= byte_cnt + 16’d1;
end
end
else
begin
spi_wr_req <= 1’b1;
send_data <= 8’hff;
end
end
S_WAIT:
begin
cmd_req_error <= 1’b0;
wr_data_cnt <= 10’d0;
if(cmd_req == 1’b1)
state <= S_CMD_PRE;
else if(block_read_req == 1’b1)
state <= S_READ_WAIT;
else if(block_write_req == 1’b1)
state <= S_WRITE_TOKEN;
clk_div <= spi_clk_div;
end
S_CMD_PRE:
begin
if(spi_wr_ack == 1’b1)
begin
state <= S_CMD;
spi_wr_req <= 1’b0;
byte_cnt <= 16’d0;
end
else
begin
spi_wr_req <= 1’b1;
CS_reg <= 1’b1;
send_data <= 8’hff;
end
end
S_CMD:
begin
if(spi_wr_ack == 1’b1)
begin
if((byte_cnt == 16’hffff) || (data_recv != cmd_r1 && data_recv[7] == 1’b0))
begin
state <= S_ERR;
spi_wr_req <= 1’b0;
byte_cnt <= 16’d0;
end
else if(data_recv == cmd_r1)
begin
spi_wr_req <= 1’b0;
if(cmd_data_len != 16’d0)
begin
state <= S_CMD_DATA;
byte_cnt <= 16’d0;
end
else
state <= S_END;
byte_cnt <= 16’d0;
end
else
byte_cnt <= byte_cnt + 16’d1;
end
else
begin
spi_wr_req <= 1’b1;
CS_reg <= 1’b0;
if(byte_cnt == 16’d0)
send_data <= (cmd[47:40] | 8’h40);
else if(byte_cnt == 16’d1)
send_data <= cmd[39:32];
else if(byte_cnt == 16’d2)
send_data <= cmd[31:24];
else if(byte_cnt == 16’d3)
send_data <= cmd[23:16];
else if(byte_cnt == 16’d4)
send_data <= cmd[15:8];
else if(byte_cnt == 16’d5)
send_data <= cmd[7:0];
else
send_data <= 8’hff;
end
end
S_CMD_DATA:
begin
if(spi_wr_ack == 1’b1)
begin
if(byte_cnt == cmd_data_len - 16’d1)
begin
state <= S_END;
spi_wr_req <= 1’b0;
byte_cnt <= 16’d0;
end
else
begin
byte_cnt <= byte_cnt + 16’d1;
end
end
else
begin
spi_wr_req <= 1’b1;
send_data <= 8’hff;
end
end
S_READ_WAIT:
begin
if(spi_wr_ack == 1’b1 && data_recv == 8’hfe)
begin
spi_wr_req <= 1’b0;
state <= S_READ;
byte_cnt <= 16’d0;
end
else
begin
spi_wr_req <= 1’b1;
send_data <= 8’hff;
end
end
S_READ:
begin
if(spi_wr_ack == 1’b1)
begin
if(byte_cnt == 16’d513)
begin
state <= S_READ_ACK;
spi_wr_req <= 1’b0;
byte_cnt <= 16’d0;
end
else
begin
byte_cnt <= byte_cnt + 16’d1;
end
end
else
begin
spi_wr_req <= 1’b1;
send_data <= 8’hff;
end
end
S_WRITE_TOKEN:
if(spi_wr_ack == 1’b1)
begin
state <= S_WRITE_DATA_0;
spi_wr_req <= 1’b1;
send_data <= 8’hfe;
end
S_WRITE_DATA_0:
begin
state <= S_WRITE_DATA_1;
wr_data_cnt <= wr_data_cnt + 10’d1;
end
S_WRITE_DATA_1:
begin
if(spi_wr_ack == 1’b1 && wr_data_cnt == 10’d512)
begin
state <= S_WRITE_CRC;
spi_wr_req <= 1’b0;
end
else
begin
if(spi_wr_ack == 1’b1)
begin
state <= S_WRITE_DATA_0;
spi_wr_req <= 1’b0;
end
else
begin
spi_wr_req <= 1’b1;
send_data <= block_write_data;
end
end

                end  
            S_WRITE_CRC:
                begin
                    if(spi_wr_ack == 1'b1)
                        begin
                            if(byte_cnt <= 16'd1)
                                begin
                                    state <= S_WRITE_SUC;
                                    spi_wr_req <= 1'b0;
                                    byte_cnt <= 16'd0;
                                end  
                            else
                                begin
                                    byte_cnt <= byte_cnt + 16'd1;
                            end 
                        end
                    else
                        begin
                            spi_wr_req <= 1'b1;
                            send_data <= 8'hff;
                        end                  
                end
            S_WRITE_SUC:
                begin
                    if(spi_wr_ack == 1'b1)
                        begin
                            if(data_recv[4:0] == 5'b00101)
                                begin
                                    state <= S_WRITE_BUSY;
                                    spi_wr_req <= 1'b0; 
                                end
                        end
                    else
                        begin
                            spi_wr_req <= 1'b1;
                            send_data <= 8'hff;
                        end           
                end         
            S_WRITE_BUSY:
                begin
                    if(spi_wr_ack == 1'b1)
                        begin
                            if(data_recv == 8'hff)
                                begin
                                    state <= S_WRITE_ACK;
                                    spi_wr_req <= 1'b0;
                                end
                        end
                    else
                        begin
                            spi_wr_req <= 1'b1;
                            send_data <= 8'hff;
                        end      
                end
            S_ERR:
                begin
                    state <= S_END;
                    cmd_req_error <= 1'b1;
                end                 
            S_READ_ACK,S_WRITE_ACK,S_END:
                begin
                    state <= S_WAIT;
                end       
            default:
                state <= S_IDLE;                                                       
        endcase             
end 

always @(posedge sys_clk or posedge rst)
begin
if(rst == 1’b1)
block_read_valid <= 1’b0;
else if(state == S_READ && byte_cnt <= 16’d512)
block_read_valid <= spi_wr_ack;
else
block_read_valid <= 1’b0;
end

always @(posedge sys_clk or posedge rst)
begin
if(rst == 1’b1)
block_read_data <= 8’d0;
else if(state == S_READ && spi_wr_ack ==1’b1)
block_read_data <= data_recv;
end

endmodule

8.sd_card_sec_read_write.v

module sd_card_sec_read_write
#(
parameter SPI_LOW_SPEED_DIV = 248,
parameter SPI_HIGH_SPEED_DIV = 0
)
(
input clk, //时钟输入
input rst, //异步复位输入,高复位
output reg sd_init_done, //SD卡初始化完成
input sd_sec_read, //SD卡扇区读请求
input [31:0] sd_sec_read_addr, //SD卡扇区读地址
output [7:0] sd_sec_read_data, //SD卡扇区读数据
output sd_sec_read_data_valid, //SD卡读出的数据有效
output sd_sec_read_end, //SD卡数据读完成
input sd_sec_write, //SD卡数据写请求
input [31:0] sd_sec_write_addr, //SD卡扇区写请求应答
input [7:0] sd_sec_write_data, //SD卡扇区写请求数据
output sd_sec_write_data_req, //SD卡扇区写请求数据读取,提前一个sd_sec_write_data一个时钟周期
output sd_sec_write_end, //SD卡扇区写请求完成

output reg [15:0] spi_clk_div,          //SPI时钟分频,SPI时钟频率=系统时钟/((spi_clk_div + 2)*2)
output reg cmd_req,                     //SD卡命令请求
input cmd_req_ack,                      //SD卡命令请求应答
input cmd_req_error,                    //SD卡命令请求错误
output reg[47:0] cmd,                   //SD卡命令,
output reg[7:0] cmd_r1,                 //SD卡命令期待的R1响应
output reg[15:0] cmd_data_len,          //SD卡所读取的数据长度,大部分命令没有读取数据
output reg  block_read_req,             //块数据读取请求
input block_read_valid,                 //块数据读取数据有效
input [7:0] block_read_data,            //块读取数据
input block_read_req_ack,               //块数据读取请求应答
output reg block_write_req,             //块写请求
output[7:0] block_write_data,           //块写数据
input block_write_data_rd,              //块写数据请求,提前block_write_data一个时钟周期
input block_write_req_ack               //块写请求应答

);

reg[7:0] read_data;
reg[31:0] timer;
localparam S_IDLE = 0;
localparam S_CMD0 = 1;
localparam S_CMD8 = 2;
localparam S_CMD55 = 3;
localparam S_CMD41 = 4;
localparam S_CMD17 = 5;
localparam S_READ = 6;
localparam S_CMD24 = 7;
localparam S_WRITE = 8;
localparam S_ERR = 14;
localparam S_WRITE_END = 15;
localparam S_READ_END = 16;
localparam S_WAIT_READ_WRITE = 17;
localparam S_CMD16 = 18;
reg[4:0] state;
reg[31:0] sec_addr;
assign sd_sec_read_data_valid = (state == S_READ) && block_read_valid;
assign sd_sec_read_data = block_read_data;
assign sd_sec_read_end = (state == S_READ_END);
assign sd_sec_write_data_req = (state == S_WRITE) && block_write_data_rd;
assign block_write_data = sd_sec_write_data;
assign sd_sec_write_end = (state == S_WRITE_END);

always @(posedge clk or posedge rst)
begin
if(rst)
begin
state <= S_IDLE;
cmd_req <= 1’b0;
cmd_data_len <= 16’d0;
cmd_r1 <= 8’d0;
cmd <= 48’d0;
spi_clk_div <= SPI_LOW_SPEED_DIV[15:0];
block_write_req <= 1’b0;
block_read_req <= 1’b0;
sec_addr <= 32’d0;
sd_init_done <= 1’b0;
end
else
case(state)
S_IDLE:
begin
state <= S_CMD0;
sd_init_done <= 1’b0;
spi_clk_div <= SPI_LOW_SPEED_DIV[15:0];
end
S_CMD0:
begin
if(cmd_req_ack & ~cmd_req_error)
begin
state <= S_CMD8;
cmd_req <= 1’b0;
end
else
begin
cmd_req <= 1’b1;
cmd_data_len <= 16’d0;
cmd_r1 <= 8’h01;
cmd <= {8’d0,8’h00,8’h00,8’h00,8’h00,8’h95};
end
end
S_CMD8:
begin
if(cmd_req_ack & ~cmd_req_error)
begin
state <= S_CMD55;
cmd_req <= 1’b0;
end
else
begin
cmd_req <= 1’b1;
cmd_data_len <= 16’d4;
cmd_r1 <= 8’h0;
cmd <= {8’d8,8’h00,8’h00,8’h01,8’haa,8’h87};
end
end
S_CMD55:
begin
if(cmd_req_ack & ~cmd_req_error)
begin
state <= S_CMD41;
cmd_req <= 1’b0;
end
else
begin
cmd_req <= 1’b1;
cmd_data_len <= 16’d0;
cmd_r1 <= 8’h01;
cmd <= {8’d55,8’h00,8’h00,8’h00,8’h00,8’hff};
end
end
S_CMD41:
begin
if(cmd_req_ack & ~ cmd_req_error)
begin
state <= S_CMD16;
cmd_req <= 1’b0;
sd_init_done <= 1’b1;
spi_clk_div <= SPI_HIGH_SPEED_DIV[15:0];
end
else if(cmd_req_ack)
begin
state <= S_CMD55;
end
else
begin
cmd_req <= 1’b1;
cmd_data_len <= 16’d0;
cmd_r1 <= 8’h00;
cmd <= {8’d41,8’h40,8’h00,8’h00,8’h00,8’hff};
end
end
S_CMD16:
begin
if(cmd_req_ack & ~cmd_req_error)
begin
state <= S_WAIT_READ_WRITE;
cmd_req <= 1’b0;
sd_init_done <= 1’b1;
spi_clk_div <= SPI_HIGH_SPEED_DIV[15:0];
end
else if(cmd_req_ack)
begin
state <= S_CMD55;
end
else
begin
cmd_req <= 1’b1;
cmd_data_len <= 16’d0;
cmd_r1 <= 8’h00;
cmd <= {8’d16,32’d512,8’hff};
end
end
S_WAIT_READ_WRITE:
begin
if(sd_sec_write == 1’b1)
begin
state <= S_CMD24;
sec_addr <= sd_sec_write_addr;
end
else if(sd_sec_read == 1’b1)
begin
state <= S_CMD17;
sec_addr <= sd_sec_read_addr;
end
spi_clk_div <= 16’d0;
end
S_CMD24:
begin
if(cmd_req_ack & ~cmd_req_error)
begin
state <= S_WRITE;
cmd_req <= 1’b0;
end
else
begin
cmd_req <= 1’b1;
cmd_data_len <= 16’d0;
cmd_r1 <= 8’h00;
cmd <= {8’d24,sec_addr,8’hff};

            end
        end
        S_WRITE:
        begin
            if(block_write_req_ack == 1'b1)
            begin
                block_write_req <= 1'b0;
                state <= S_WRITE_END;
            end
            else
                block_write_req <= 1'b1;
            end
        S_CMD17:
        begin
            if(cmd_req_ack & ~cmd_req_error)
            begin
                state <= S_READ;
                cmd_req <= 1'b0;
            end
            else
            begin
                cmd_req <= 1'b1;
                cmd_data_len <= 16'd0;
                cmd_r1 <= 8'h00;
                cmd <= {8'd17,sec_addr,8'hff};
            end
        end
        S_READ:
        begin
            if(block_read_req_ack)
            begin
                state <= S_READ_END;
                block_read_req <= 1'b0;
            end
            else
            begin
                block_read_req <= 1'b1;
            end
        end
        S_WRITE_END:
        begin
            state <= S_WAIT_READ_WRITE;
        end
        S_READ_END:
        begin
            state <= S_WAIT_READ_WRITE;
        end
        default:
            state <= S_IDLE;
    endcase

end

endmodule

评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值