LINUX_LCD编程 TFT LCD

1:LCD屏幕

1.1:LCDTTL时序信号

1.2:LCD数据显示

        

一帧数据由多行组成,一行数据由多个像素点组成,每个像素由若干个数据位组成。对于单色显示器,每个像素为1位,称为1bpp,对于256色显示器,每个像素为8位,为8bpp。

        显示是从左上方开始,一行一行的将每个像素的颜色显示出来,当一行显示数据到最右边是,自动跳至下一列最左边开始执行,当数据至右下角时,调至左上方显示下一帧数据。

        该图结合下面时序图一起理解

LCD时序图

其中,VSYNC表示每一帧数据的开始,HSYNC表示每一行数据的开始,VCLK表示传输一个像素的数据。xrse个像素构成一行数据,yrse行数据构成一帧数据。xrse和yrse为分辨率,也就是一行有xrse个像素点,一列由yrse个像素点。

时序图讲解

        1)当VSYNC有效时,一帧数据开始传输。

        2)VSPW表示VSYNC信号脉冲宽度为(VSPW+1)个HSYNC信号周期,这几行数据无效。

        3)在经过HBPD+1个HSYNC个信号后,有效的数据行开始出现。

        注:在上图,我们可以看到VSYNC出现高电平之后,需要经过(VSPW+1)+(VBPD+1)个信号后,LINEVA+1的有效行才开始出现。(上图上黑框)

        4)LINEVA+1的有效行开始发送数据

        5)随后发送VFPD+1个无效行,(上图下黑框)

一行数据讲解。

        1)HSYNC信号有效后,一行数据开始传输

        2)首先前(HSPW+1)+(HBPD+1)个数据无效,第一个有效行像素开始出现。(上图左黑框)

        3)发送完一行数据之后,经过HFPD+1个无效数据点,(上图右黑框)

        4)下一个HSYN信号出现,开始传输下一行数据。

        

由图中我们可以知道,VSYNC他的频率表示了数据的帧率。一秒钟发送多少个VSYNC,则显示多少帧图片。

        VCLK= HCLK/[CLKVAL+1*2]

VSYNC是帧频率。

将VSYNC、HSYNC、VCLK等时间参数设置好之后,将帧内存Framebuffer地址告诉LCD控制器,他即可自动发起DMA传输从Framebuffer中得到数据,在上述信号的作用下出现在数据总线VD[23:0]上,用户只需要把现实的图像数据写入帧内存即可。

        注意:

        Framebuffer这个地址中,可以按照下图理解,假设每个像素位置占据2个字节,那么(0,0)所在的内存地址即为FB+0, (1,0)为FB+2,以此类推,下一列地址(0,1)内存地址为(FB+ xres*2).

像素存储

对于 24 16 BPP来说

        他们的Framebuffer存放的就是颜色

对于8bpp来说

        8BPP访问256调色板中对应的地址,将改地址的颜色 通过VD[23:0]发送出去。

1:16M(24BPP)像素

        即通过24条数据线VD[23:0]发起数据传输,RGB三色每个占据8条数据线。为方便BMA传输,每个像素使用4个字节32位来存储数据。

        设置不同的寄存器格式,数据排列方式不同

2:64K,16BPP

16BPP代表使用16条数据线传输RGB三色,分为两种接线方式 RGB为 5:6:5  和 5:6:5:1。

1个四字节表示两个BPP像素,

3:256色,8BPP

在8PP中,8条数据线要分给RGB,因此在这里,设置8条数据线的作用不是显示颜色了。而是我们定义一个256的调色板,这8条数据线的作用就是访问这256调色板中对应的地址,每个地址存放一种颜色。

8BBPP访问256调色板中对应的地址,将改地址的颜色 通过VD[23:0]发送出去。

调色板的起始地址 需要根据手册来确定。

2:寄存器

那么我门TTF只需要设置上2组寄存器即可。

2.1:LCDCON1

用于设置像素时钟,使能LCD信号输出等

 CLKVAL  与我们在时序中的 VCLK相关,负责1s有多少帧数据

BPPMOODE    像素格式

ENVID    使能位

2.2LCDCON2

用于设置垂直方向的时间参数

即我们图中的上下黑框,不同的LCD参数不同

2.3LCDCON3

设置水平方向的参数

即我们图中的左右黑框,不同的LCD参数不同

2.4 LCDCON4

对于TFT,这个寄存器只设置HSYNC信号的脉冲宽度【7:0】为HSPW

2.5 LCDCON5

设置控制信号的极性,并读取一些状态信息

2.6 帧内存地址 LCDSADDR1-LCDSADDR3

ADDR1寄存器格式

ADDR2寄存器格式

ADDR3寄存器格式

2.7 调色板

3:代码

为了方便调用LCD代码,以及以后添加新的LCD屏幕参数,和不同控制芯片对LCD的控制,采用面向对象编程方式。

首先,定义control.c和control.h文件,

          作用:将LCD_4_3所对应的参数,例如时序,bpp等,写入到S3C2440_LCD_Control.c中。

LCD_4_3

        作用,主要是配置LCD屏幕的参数。

S3C2440_LCD_Control.c

        配置LCD寄存器。

3.1:LCD.h函数编写

在LCD.h中,我们需要定义LCD屏幕他们需要的参数,并将其封装为结构体,在4.3存屏幕参数设置中,我们可以直接调用该结构体赋值。


#ifndef _LCD_H
#define _LCD_H


enum {
	NORMAL = 0,
	INVERT = 1,
};

/* NORMAL : 正常极性
 * INVERT : 反转极性
 */
typedef struct pins_polarity {
	int de;    /* normal: 高电平时可以传输数据 */
	int pwren; /* normal: 高电平有效 */
	int vclk;  /* normal: 在下降沿获取数据 */
	int rgb;   /* normal: 高电平表示1 */
	int hsync; /* normal: 高脉冲 */
	int vsync; /* normal: 高脉冲 */
}pins_polarity, *p_pins_polarity;

typedef struct time_sequence {
	/* 垂直方向 */
	int tvp; /* vysnc脉冲宽度 */
	int tvb; /* 上边黑框, Vertical Back porch */
	int tvf; /* 下边黑框, Vertical Front porch */

	/* 水平方向 */
	int thp; /* hsync脉冲宽度 */
	int thb; /* 左边黑框, Horizontal Back porch */
	int thf; /* 右边黑框, Horizontal Front porch */

	int vclk;
}time_sequence, *p_time_sequence;


typedef struct lcd_params {
	char *name;
	
	/* 引脚极性 */
	pins_polarity pins_pol;
	
	/* 时序 */
	time_sequence time_seq;
	
	/* 分辨率, bpp */
	int xres;
	int yres;
	int bpp;
	
	/* framebuffer的地址 */
	unsigned int fb_base;
}lcd_params, *p_lcd_params;

void get_lcd_params(unsigned int *fb_base, int *xres, int *yres, int *bpp);

#endif /* _LCD_H */


3.2:LCD.c函数编写

        我们设置一个包含多种类型LCD屏幕参数结构体 的数组                                          p_lcd_params p_array_lcd[LCD_NUM];

        并设置一个选择函数,根据选择的LCD的名字,来给g_p_lcd_selected赋予不同的屏幕参数。

get_lcd_params函数的作用是,获取 所选择的LCD 屏幕的 Framebuffer xres yres bpp。

        LCD的初始化再函数lcd_init()中,再调用LCD时,一般只修改这里即可。就不需要涉及底层文件。

#include "lcd.h"
#include "lcd_controller.h"

#define LCD_NUM 10

static p_lcd_params p_array_lcd[LCD_NUM];
static p_lcd_params g_p_lcd_selected;

int register_lcd(p_lcd_params plcd)
{
	int i;
	for (i = 0; i < LCD_NUM; i++)
	{
		if (!p_array_lcd[i])
		{
			p_array_lcd[i] = plcd;
			return i;
		}
	}
	return -1;
}

int select_lcd(char *name)
{
	int i;
	for (i = 0; i < LCD_NUM; i++)
	{
		if (p_array_lcd[i] && !strcmp(p_array_lcd[i]->name, name))
		{
			g_p_lcd_selected = p_array_lcd[i];
			return i;
		}
	}
	return -1;
}

void get_lcd_params(unsigned int *fb_base, int *xres, int *yres, int *bpp)
{
	*fb_base = g_p_lcd_selected->fb_base;
	*xres = g_p_lcd_selected->xres;
	*yres = g_p_lcd_selected->yres;
	*bpp = g_p_lcd_selected->bpp;
}

void lcd_enable(void)
{
	lcd_controller_enable();
}

void lcd_disable(void)
{
	lcd_controller_disable();
}

int lcd_init(void)
{
	/* 注册LCD */
	lcd_4_3_add();

	/* 注册LCD控制器 */
	lcd_contoller_add();

	/* 选择某款LCD */
	select_lcd("lcd_4.3");

	/* 选择某款LCD控制器 */
	select_lcd_controller("s3c2440");

	/* 使用LCD的参数, 初始化LCD控制器 */
	lcd_controller_init(g_p_lcd_selected);
}

3.3 LCD_4_3.c设计

        这里我们所起的作用即将我们的4.3LCD的参数,写入到这个结构体中,为后续使用做铺垫

这里的参数现需要根据LCD手册来获取。


#include "lcd.h"

#define LCD_FB_BASE 0x33c00000

lcd_params lcd_4_3_params = {
	.name = "lcd_4.3",
	.pins_pol = {
		.de    = NORMAL,	/* normal: 高电平时可以传输数据 */
		.pwren = NORMAL,    /* normal: 高电平有效 */
		.vclk  = NORMAL,	/* normal: 在下降沿获取数据 */
		.rgb   = NORMAL,	/* normal: 高电平表示1 */
		.hsync = INVERT,    /* normal: 高脉冲 */
		.vsync = INVERT, 	/* normal: 高脉冲 */
	},
	.time_seq = {
		/* 垂直方向 */
		.tvp=	10, /* vysnc脉冲宽度 */
		.tvb=	2,  /* 上边黑框, Vertical Back porch */
		.tvf=	2,  /* 下边黑框, Vertical Front porch */

		/* 水平方向 */
		.thp=	41, /* hsync脉冲宽度 */
		.thb=	2,  /* 左边黑框, Horizontal Back porch */
		.thf=	2,  /* 右边黑框, Horizontal Front porch */

		.vclk=	9,  /* MHz */
	},
	.xres = 480,
	.yres = 272,
	.bpp  = 32,  /* 16, no 24bpp */
	.fb_base = LCD_FB_BASE,
};


void lcd_4_3_add(void)
{
	register_lcd(&lcd_4_3_params);
}

注:

3.1 3.2 3.3这三节是相互关联的。

3.1 设计参数结构体, 3.3 将4.3屏幕的参数写入结构体  3.2将屏幕参数写入数组,为后续写入寄存器做准备。

3.4 LCD_Controller.h

这里,我们实现了一个结构体,该结构体中 包含除了名字之外,剩下的四个均为 函数指针

我们需要这四个函数指针 分别指向 控制器初始化函数 、 使能函数 、 失能函数 、 调色板函数。


#ifndef _LCD_CONTROLLER_H
#define _LCD_CONTROLLER_H

#include "lcd.h"

typedef struct lcd_controller
{
	char *name;
	void (*init)(p_lcd_params plcdparams);
	void (*enable)(void);
	void (*disable)(void);
	void (*init_palette)(void);
} lcd_controller, *p_lcd_controller;

#endif /* _LCD_CONTROLLER_H */

3.5 LCD_Controller.c

      我们设置一个包含多种类型LCD屏幕控制器结构体的数组                                         static p_lcd_controller p_array_lcd_controller[LCD_CONTROLLER_NUM];

        并设置一个选择函数,根据选择的LCD的控制器的名字,来给g_p_lcd_controller_selected赋予不同的控制器函数。


#include "lcd_controller.h"

#define LCD_CONTROLLER_NUM 10

static p_lcd_controller p_array_lcd_controller[LCD_CONTROLLER_NUM];
static p_lcd_controller g_p_lcd_controller_selected;

int register_lcd_controller(p_lcd_controller plcdcon)
{
	int i;
	for (i = 0; i < LCD_CONTROLLER_NUM; i++)
	{
		if (!p_array_lcd_controller[i])
		{
			p_array_lcd_controller[i] = plcdcon;
			return i;
		}
	}
	return -1;
}

int select_lcd_controller(char *name)
{
	int i;
	for (i = 0; i < LCD_CONTROLLER_NUM; i++)
	{
		if (p_array_lcd_controller[i] && !strcmp(p_array_lcd_controller[i]->name, name))
		{
			g_p_lcd_controller_selected = p_array_lcd_controller[i];
			return i;
		}
	}
	return -1;
}

/* 向上: 接收不同LCD的参数
 * 向下: 使用这些参数设置对应的LCD控制器
 */

int lcd_controller_init(p_lcd_params plcdparams)
{
	/* 调用所选择的LCD控制器的初始化函数 */
	if (g_p_lcd_controller_selected)
	{
		g_p_lcd_controller_selected->init(plcdparams);
		g_p_lcd_controller_selected->init_palette();
		return 0;
	}
	return -1;
}

void lcd_controller_enable(void)
{
	if (g_p_lcd_controller_selected)
	{
		g_p_lcd_controller_selected->enable();
	}
}

void lcd_controller_disable(void)
{
	if (g_p_lcd_controller_selected)
	{
		g_p_lcd_controller_selected->disable();
	}
}

void lcd_contoller_add(void)
{
	s3c2440_lcd_contoller_add();
}

3.6 S3C2440_Controller.c

将LCD参数写入到寄存器中


#include "lcd.h"
#include "lcd_controller.h"
#include "../s3c2440_soc.h"

#define HCLK 100

void jz2440_lcd_pin_init(void)
{
	/* 初始化引脚 : 背光引脚 */
	GPBCON &= ~0x3;
	GPBCON |= 0x01;

	/* LCD专用引脚 */
	GPCCON = 0xaaaaaaaa;
	GPDCON = 0xaaaaaaaa;

	/* PWREN */
	GPGCON |= (3 << 8);
}

/* 根据传入的LCD参数设置LCD控制器 */
void s3c2440_lcd_controller_init(p_lcd_params plcdparams)
{
	int pixelplace;
	unsigned int addr;

	jz2440_lcd_pin_init();

	/* [17:8]: clkval, vclk = HCLK / [(CLKVAL+1) x 2]
	 *                   9   = 100M /[(CLKVAL+1) x 2], clkval = 4.5 = 5
	 *                 CLKVAL = 100/vclk/2-1
	 * [6:5]: 0b11, tft lcd
	 * [4:1]: bpp mode
	 * [0]  : LCD video output and the logic enable/disable
	 */
	int clkval = (float)HCLK / plcdparams->time_seq.vclk / 2 - 1 + 0.5;
	// int clkval = 5;
	int bppmode = plcdparams->bpp == 8 ? 0xb : plcdparams->bpp == 16 ? 0xc
																	 : 0xd; /* 0xd: 24,32bpp */
	LCDCON1 = (clkval << 8) | (3 << 5) | (bppmode << 1);

	/* [31:24] : VBPD    = tvb - 1
	 * [23:14] : LINEVAL = line - 1
	 * [13:6]  : VFPD    = tvf - 1
	 * [5:0]   : VSPW    = tvp - 1
	 */
	LCDCON2 = ((plcdparams->time_seq.tvb - 1) << 24) |
			  ((plcdparams->yres - 1) << 14) |
			  ((plcdparams->time_seq.tvf - 1) << 6) |
			  ((plcdparams->time_seq.tvp - 1) << 0);

	/* [25:19] : HBPD	 = thb - 1
	 * [18:8]  : HOZVAL  = 列 - 1
	 * [7:0]   : HFPD	 = thf - 1
	 */
	LCDCON3 = ((plcdparams->time_seq.thb - 1) << 19) |
			  ((plcdparams->xres - 1) << 8) |
			  ((plcdparams->time_seq.thf - 1) << 0);

	/*
	 * [7:0]   : HSPW	 = thp - 1
	 */
	LCDCON4 = ((plcdparams->time_seq.thp - 1) << 0);

	/* 用来设置引脚极性, 设置16bpp, 设置内存中象素存放的格式
	 * [12] : BPP24BL
	 * [11] : FRM565, 1-565
	 * [10] : INVVCLK, 0 = The video data is fetched at VCLK falling edge
	 * [9]  : HSYNC是否反转
	 * [8]  : VSYNC是否反转
	 * [7]  : INVVD, rgb是否反转
	 * [6]  : INVVDEN
	 * [5]  : INVPWREN
	 * [4]  : INVLEND
	 * [3]  : PWREN, LCD_PWREN output signal enable/disable
	 * [2]  : ENLEND
	 * [1]  : BSWP
	 * [0]  : HWSWP
	 */

	pixelplace = plcdparams->bpp == 32 ? (0) : plcdparams->bpp == 16 ? (1)
																	 : (1 << 1); /* 8bpp */

	LCDCON5 = (plcdparams->pins_pol.vclk << 10) |
			  (plcdparams->pins_pol.rgb << 7) |
			  (plcdparams->pins_pol.hsync << 9) |
			  (plcdparams->pins_pol.vsync << 8) |
			  (plcdparams->pins_pol.de << 6) |
			  (plcdparams->pins_pol.pwren << 5) |
			  (1 << 11) | pixelplace;

	/* framebuffer地址 */
	/*
	 * [29:21] : LCDBANK, A[30:22] of fb
	 * [20:0]  : LCDBASEU, A[21:1] of fb
	 */
	addr = plcdparams->fb_base & ~(1 << 31);
	LCDSADDR1 = (addr >> 1);

	/*
	 * [20:0] : LCDBASEL, A[21:1] of end addr
	 */
	addr = plcdparams->fb_base + plcdparams->xres * plcdparams->yres * plcdparams->bpp / 8;
	addr >>= 1;
	addr &= 0x1fffff;
	LCDSADDR2 = addr; //
}

void s3c2440_lcd_controller_enalbe(void)
{
	/* 背光引脚 : GPB0 */
	GPBDAT |= (1 << 0);

	/* pwren    : 给LCD提供AVDD  */
	LCDCON5 |= (1 << 3);

	/* LCDCON1'BIT 0 : 设置LCD控制器是否输出信号 */
	LCDCON1 |= (1 << 0);
}

void s3c2440_lcd_controller_disable(void)
{
	/* 背光引脚 : GPB0 */
	GPBDAT &= ~(1 << 0);

	/* pwren	: 给LCD提供AVDD  */
	LCDCON5 &= ~(1 << 3);

	/* LCDCON1'BIT 0 : 设置LCD控制器是否输出信号 */
	LCDCON1 &= ~(1 << 0);
}

// 设置调色板那之前先关闭LCD控制器
void s3c2440_lcd_controller_init_palette(void)
{

	int bit = LCDCON1 & (1 << 0);
	if (bit)
		LCDCON1 &= ~(1 << 0);

	// 4d000400-4d00041f 16色调色板
	volatile unsigned int *palette_base = (volatile unsigned int *)0x4d000400;
	int i;

	for (i = 0; i < 256; i++)
	{
		// 存放565格式  低16位
		*palette_base++ = i;
	}

	if (bit)
		LCDCON1 |= (1 << 0);
}

struct lcd_controller s3c2440_lcd_controller = {
	.name = "s3c2440",
	.init = s3c2440_lcd_controller_init,
	.enable = s3c2440_lcd_controller_enalbe,
	.disable = s3c2440_lcd_controller_disable,
	.init_palette = s3c2440_lcd_controller_init_palette,
};

void s3c2440_lcd_contoller_add(void)
{
	register_lcd_controller(&s3c2440_lcd_controller);
}

再所有设置结束后,我们看LCD.C的init函数


int lcd_init(void)
{
	/* 注册LCD */
	lcd_4_3_add();

	/* 注册LCD控制器 */
	lcd_contoller_add();

	/* 选择某款LCD */
	select_lcd("lcd_4.3");

	/* 选择某款LCD控制器 */
	select_lcd_controller("s3c2440");

	/* 使用LCD的参数, 初始化LCD控制器 */
	lcd_controller_init(g_p_lcd_selected);
}

       1) 首先将LCD4_3添加到 参数 数组中 ,将 S3C2440控制器的的结构体添加至 控制器数组中

lcd_4_3.c函数中
void lcd_4_3_add(void)

{

    register_lcd(&lcd_4_3_params);

}

S3C2440_LCD_Controller.c函数中
struct lcd_controller s3c2440_lcd_controller = {
	.name = "s3c2440",
	.init = s3c2440_lcd_controller_init,
	.enable = s3c2440_lcd_controller_enalbe,
	.disable = s3c2440_lcd_controller_disable,
	.init_palette = s3c2440_lcd_controller_init_palette,
};

void s3c2440_lcd_contoller_add(void)
{
	register_lcd_controller(&s3c2440_lcd_controller);
}

2) 选择LCD参数,和控制器参数

        位于LCD.C和LCD_Controller.c中。

3)将所选的 LCD参数 放入 控制器 初始化函数中, 使用该参数对控制器进行初始化。

该函数位于LCD_controller.c函数中,根据传入的参数对选择的控制器进行in

int lcd_controller_init(p_lcd_params plcdparams)
{
	/* 调用所选择的LCD控制器的初始化函数 */
	if (g_p_lcd_controller_selected)
	{
		g_p_lcd_controller_selected->init(plcdparams);
		g_p_lcd_controller_selected->init_palette();
		return 0;
	}
	return -1;
}

4:测试

根据不同的BPP值,分别显示 红绿蓝。



void lcd_test(void)
{
	unsigned int fb_base;
	int xres, yres, bpp;
	int x, y;
	unsigned short *p;
	unsigned int *p2;
	unsigned char *p0;

	/* 初始化LCD */
	lcd_init();

	/* 使能LCD */
	lcd_enable();

	/* 获得LCD的参数: fb_base, xres, yres, bpp */
	get_lcd_params(&fb_base, &xres, &yres, &bpp);
	fb_get_lcd_params();
	font_init();

	/* 往framebuffer中写数据 */
	if (bpp == 16)
	{
		/* 让LCD输出整屏的红色 */

		/* 565: 0xf800 */

		p = (unsigned short *)fb_base;
		for (x = 0; x < xres; x++)
			for (y = 0; y < yres; y++)
				*p++ = 0xf800;

		/* green */
		p = (unsigned short *)fb_base;
		for (x = 0; x < xres; x++)
			for (y = 0; y < yres; y++)
				*p++ = 0x7e0;

		/* blue */
		p = (unsigned short *)fb_base;
		for (x = 0; x < xres; x++)
			for (y = 0; y < yres; y++)
				*p++ = 0x1f;

		/* black */
		p = (unsigned short *)fb_base;
		for (x = 0; x < xres; x++)
			for (y = 0; y < yres; y++)
				*p++ = 0;
	}
	else if (bpp == 32)
	{
		/* 让LCD输出整屏的红色 */

		/* 0xRRGGBB */

		p2 = (unsigned int *)fb_base;
		for (x = 0; x < xres; x++)
			for (y = 0; y < yres; y++)
				*p2++ = 0xff0000;

		/* green */
		p2 = (unsigned int *)fb_base;
		for (x = 0; x < xres; x++)
			for (y = 0; y < yres; y++)
				*p2++ = 0x00ff00;

		/* blue */
		p2 = (unsigned int *)fb_base;
		for (x = 0; x < xres; x++)
			for (y = 0; y < yres; y++)
				*p2++ = 0x0000ff;

		/* black */
		p2 = (unsigned int *)fb_base;
		for (x = 0; x < xres; x++)
			for (y = 0; y < yres; y++)
				*p2++ = 0;
	}
	else if (bpp == 8)
	{
		/* 让LCD输出整屏的红色 */

		/* bpp = 8: 调色板中某个数值*/

		p0 = (unsigned char *)fb_base;
		for (x = 0; x < xres; x++)
			for (y = 0; y < yres; y++)
				*p0++ = 0xf12;

		/* green */
		p0 = (unsigned char *)fb_base;
		for (x = 0; x < xres; x++)
			for (y = 0; y < yres; y++)
				*p0++ = 0x49;

		/* blue */
		p0 = (unsigned char *)fb_base;
		for (x = 0; x < xres; x++)
			for (y = 0; y < yres; y++)
				*p0++ = 0x1f;

		/* black */
		p0 = (unsigned char *)fb_base;
		for (x = 0; x < xres; x++)
			for (y = 0; y < yres; y++)
				*p0++ = 0;
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值