您尚未登录。

#1 Re: 全志 SOC » 新人对F1C200S“裸机”使用8080接口16位LCD的几点感受,大伙轻喷 » 昨天 23:14:40

呱呱蛙 说:

插个眼,最近也是在调试屏幕,在st7789的手册时序里面也看到了8080。

8080的接口虽然古老,但是用着还行,自己也有RGB屏幕,但不想看着8080屏幕吃灰就用起来了。

#2 全志 SOC » T113-S3和Tina2.0使用8080接口16位并行LCD屏幕时一定要加上这条指令 » 2025-05-25 18:43:39

william9527
回复: 0

自从用F1C200S调试8080接口16位并行LCD屏幕成功后,就迫不及待的用T113-S3做实验,本来想着挺简单的,但是被一条指令搞了一天,就是它:

if (info->lcd_cpu_mode == LCD_CPU_AUTO_MODE)
{
	DBG_INFO(" cpu auto mode.\n");
	sunxi_lcd_cpu_set_auto_mode(sel);
}

不知道是不是我的LCD特殊的原因,看着别人的屏幕不用这条指令也能点亮,无语中,LCD屏幕是ST7796SV芯片,背光PWM用的是PWM4(PE10),屏幕RESET管脚是PG10,未开启“TE”模式。写一下配置,为后面的兄弟避坑了。
board.dts

&pio {
        ......
        rgb16_pins_a: rgb16@0 {
		allwinner,pins = "PD1", "PD2", "PD3", "PD4", "PD5", \
			"PD6", "PD7", "PD8", "PD10", "PD11", "PD12", \
			"PD13", "PD14", "PD15", "PD16", "PD17", \
			"PD18", "PD19", "PD20", "PD21";
		allwinner,pname = "lcdb0", "lcdb1", "lcdb2", "lcdb3", "lcdb4", \
			"lcdg0", "lcdg1", "lcdg2", "lcdg3", "lcdg4", "lcdg5", \
			"lcdr0", "lcdr1", "lcdr2", "lcdr3", "lcdr4", \
			"lcdwr", "lcdrs", "lcdrd", "lcdcs";
		allwinner,function = "lcd0";
		allwinner,muxsel = <2>;
		allwinner,drive = <3>;
		allwinner,pull = <0>;
	};

	rgb16_pins_b: rgb16@1 {
		allwinner,pins = "PD1", "PD2", "PD3", "PD4", "PD5", \
			"PD6", "PD7", "PD8", "PD10", "PD11", "PD12", \
			"PD13", "PD14", "PD15", "PD16", "PD17", \
			"PD18", "PD19", "PD20", "PD21";
		allwinner,pname = "lcdb0", "lcdb1", "lcdb2", "lcdb3", "lcdb4", \
			"lcdg0", "lcdg1", "lcdg2", "lcdg3", "lcdg4", "lcdg5", \
			"lcdr0", "lcdr1", "lcdr2", "lcdr3", "lcdr4", \
			"lcdwr", "lcdrs", "lcdrd", "lcdcs";
		allwinner,function = "io_disabled";
		allwinner,muxsel = <7>;
		allwinner,drive = <3>;
		allwinner,pull = <0>;
	};
}

/* 8080i 16bit */
&lcd0 {
	// part 1
	lcd_used            = <1>;
	lcd_driver_name     = "st7796sv_cpu";

	// part 2
	lcd_if              = <1>;
	lcd_cpu_if	      = <8>;

	// part 3
	lcd_x               = <480>;
	lcd_y               = <320>;
	lcd_width           = <84>;
	lcd_height          = <56>;
	lcd_dclk_freq       = <24>;

	lcd_hbp             = <100>;
	lcd_ht              = <618>;
	lcd_hspw            = <50>;
	lcd_vbp             = <8>;
	lcd_vt              = <336>;
	lcd_vspw            = <4>;

	// part 4
	lcd_pwm_used        = <1>;
	lcd_pwm_ch          = <4>;
	lcd_pwm_freq        = <50000>;
	lcd_pwm_pol         = <1>;
	lcd_pwm_max_limit   = <255>;
	lcd_backlight       = <150>;

	// part 5
	lcd_cpu_mode        = <0>;
	lcd_cpu_te          = <0>;

	// part 6
	lcd_frm             = <0>;
	lcd_gamma_en        = <0>;
	lcd_cmap_en         = <0>;
	lcd_rb_swap			= <0>;

	// part 7
	// reset pin
	lcd_gpio_0 = <&pio PG 10 1 0 3 1>;

	pinctrl-0 = <&rgb16_pins_a>;
	pinctrl-1 = <&rgb16_pins_b>;
};

st7796sv_cpu.c

#include "st7796sv_cpu.h"

// #define CPU_TRI_MODE

#define DBG_INFO(format, args...) (printk("[ST7796SV LCD INFO] LINE:%04d-->%s:"format, __LINE__, __func__, ##args))
#define DBG_ERR(format, args...) (printk("[ST7796SV LCD ERR] LINE:%04d-->%s:"format, __LINE__, __func__, ##args))
#define panel_reset(val) sunxi_lcd_gpio_set_value(sel, 0, val)
// #define lcd_cs(val)  sunxi_lcd_gpio_set_value(sel, 1, val)

#define LCD_WIDTH 480
#define LCD_HEIGHT 320

#define MADCTL_MY   0x80  // 纵向交换
#define MADCTL_MX   0x40  // 横向交换
#define MADCTL_MV   0x20  // 纵横交换
#define MADCTL_ML   0x10  // 从下到上刷新
#define MADCTL_RGB  0x00  // RGB
#define MADCTL_BGR  0x08  // BGR
#define MADCTL_MH   0x04  // 从右到左刷新

typedef enum
{
    LCD_DIRECTION_0,
    LCD_DIRECTION_90,
    LCD_DIRECTION_180,
    LCD_DIRECTION_270
} eLcdDirection;

static void lcd_panel_st7796sv_init(u32 sel, struct disp_panel_para *info);
static void LCD_power_on(u32 sel);
static void LCD_power_off(u32 sel);
static void LCD_bl_open(u32 sel);
static void LCD_bl_close(u32 sel);

static void LCD_panel_init(u32 sel);
static void LCD_panel_exit(u32 sel);

static void LCD_cfg_panel_info(struct panel_extend_para *info)
{
}

static s32 LCD_open_flow(u32 sel)
{
	LCD_OPEN_FUNC(sel, LCD_power_on, 120);
#ifdef CPU_TRI_MODE
	LCD_OPEN_FUNC(sel, LCD_panel_init, 100);
	LCD_OPEN_FUNC(sel, sunxi_lcd_tcon_enable, 50);
#else
	LCD_OPEN_FUNC(sel, sunxi_lcd_tcon_enable, 100);
	LCD_OPEN_FUNC(sel, LCD_panel_init, 50);
#endif
	LCD_OPEN_FUNC(sel, LCD_bl_open, 0);

	return 0;
}

static s32 LCD_close_flow(u32 sel)
{
	LCD_CLOSE_FUNC(sel, LCD_bl_close, 20);
#ifdef CPU_TRI_MODE
	LCD_CLOSE_FUNC(sel, sunxi_lcd_tcon_disable, 10);
	LCD_CLOSE_FUNC(sel, LCD_panel_exit, 50);
#else
	LCD_CLOSE_FUNC(sel, LCD_panel_exit, 10);
	LCD_CLOSE_FUNC(sel, sunxi_lcd_tcon_disable, 10);
#endif
	LCD_CLOSE_FUNC(sel, LCD_power_off, 0);

	return 0;
}

static void LCD_power_on(u32 sel)
{
	/*config lcd_power pin to open lcd power0 */
	sunxi_lcd_power_enable(sel, 0);
	sunxi_lcd_pin_cfg(sel, 1);
}

static void LCD_power_off(u32 sel)
{
	/*lcd_cs, active low */
	// lcd_cs(1);
	sunxi_lcd_delay_ms(10);
	/*lcd_rst, active hight */
	panel_reset(1);
	sunxi_lcd_delay_ms(10);

	sunxi_lcd_pin_cfg(sel, 0);
	/*config lcd_power pin to close lcd power0 */
	sunxi_lcd_power_disable(sel, 0);
}

static void LCD_bl_open(u32 sel)
{
	sunxi_lcd_pwm_enable(sel);
	/*config lcd_bl_en pin to open lcd backlight */
	sunxi_lcd_backlight_enable(sel);
}

static void LCD_bl_close(u32 sel)
{
	/*config lcd_bl_en pin to close lcd backlight */
	sunxi_lcd_backlight_disable(sel);
	sunxi_lcd_pwm_disable(sel);
}

/*static int bootup_flag = 0;*/
static void LCD_panel_init(u32 sel)
{
	struct disp_panel_para *info =
	    kmalloc(sizeof(struct disp_panel_para), GFP_KERNEL);

	DBG_INFO("\n");
	bsp_disp_get_panel_info(sel, info);
	lcd_panel_st7796sv_init(sel, info);

	kfree(info);
	return;
}

static void LCD_panel_exit(u32 sel)
{
	sunxi_lcd_cpu_write_index(sel, 0x28);
	sunxi_lcd_cpu_write_index(sel, 0x10);
}

void lcdSetWindows(u32 sel, u16 x, u16 y, u16 width, u16 height)
{
    u16 temp;

    // 横向
    temp = x + width - 1;
    if (temp >= LCD_WIDTH) {
        temp = LCD_WIDTH - 1;
    }

    sunxi_lcd_cpu_write_index(sel, 0x2a);
    sunxi_lcd_cpu_write_data(sel, (x >> 8) & 0xFF); 
    sunxi_lcd_cpu_write_data(sel, x & 0xFF);
    sunxi_lcd_cpu_write_data(sel, (temp >> 8) && 0xFF); 
    sunxi_lcd_cpu_write_data(sel, temp & 0xFF);

    // 纵向
    temp = y + height - 1;
    if (temp >= LCD_HEIGHT) {
        temp = LCD_HEIGHT - 1;
    }

    sunxi_lcd_cpu_write_index(sel, 0x2b);
    sunxi_lcd_cpu_write_data(sel, (y >> 8) && 0xFF); 
    sunxi_lcd_cpu_write_data(sel, y & 0xFF);
    sunxi_lcd_cpu_write_data(sel, (temp >> 8) && 0xFF); 
    sunxi_lcd_cpu_write_data(sel, temp & 0xFF);

    // 开始写入屏幕
    sunxi_lcd_cpu_write_index(sel, 0x2c);
}

void lcdSetDirection(u32 sel, eLcdDirection dir)
{
    u8 temp = 0;

    switch (dir) {
    case LCD_DIRECTION_0:
        temp = MADCTL_MX;
        break;

    case LCD_DIRECTION_90:
        temp = MADCTL_MV;
        break;

    case LCD_DIRECTION_180:
        temp = MADCTL_MY;
        break;

    case LCD_DIRECTION_270:
        temp = MADCTL_MY | MADCTL_MX | MADCTL_MV;
        break;

    default:
        break;
    }

    temp |= MADCTL_BGR;

    sunxi_lcd_cpu_write_index(sel, 0x36);
    sunxi_lcd_cpu_write_data(sel, temp);

    lcdSetWindows(sel, 0, 0, LCD_WIDTH, LCD_HEIGHT);
}

static void lcd_panel_st7796sv_init(u32 sel, struct disp_panel_para *info)
{
	u8 initCmd[] = {
        0x81, 0x11,
        2, 0xfb, 0xc3,
        2, 0xfb, 0x96,
        2, 0x36, 0x48,
        2, 0x3a, 0x55,
        2, 0xb4, 0x01,
        2, 0xb7, 0xc6,
        9, 0xe8, 0x40, 0x8a, 0x00, 0x00, 0x29, 0x19, 0xa5, 0x33,
        2, 0xc1, 0x06,
        2, 0xc2, 0xa7,
        2, 0xc5, 0x18,
        15, 0xe0, 0x0F, 0x09, 0x0b, 0x06, 0x04, 0x15, 0x2f, 0X54, 0x42, 0x3c, 0x17, 0x14, 0x18, 0x1b,
        15, 0xe1, 0xf0, 0x09, 0x0b, 0x06, 0x04, 0x03, 0x2d, 0x43, 0x42, 0x3b, 0x16, 0x14, 0x17, 0x1b,
        2, 0xeb, 0x3c,
        0x82, 0xeb, 0x69,
        1, 0x29,
        0x00
    };

    u8* cmdIndex = initCmd;
    u8 count, temp;
    u16 index;

    DBG_INFO(" %d, %d\n", info->lcd_x, info->lcd_y);

    /*lcd_cs, active low */
    // lcd_cs(0);

    // reset
    panel_reset(1);
    sunxi_lcd_delay_ms(10);
    panel_reset(0);
    sunxi_lcd_delay_ms(20);
    panel_reset(1);
    sunxi_lcd_delay_ms(150);

    // init lcd
    while (*cmdIndex) {
        temp = *cmdIndex++;
        count = (temp & 0x7F) - 1;

		sunxi_lcd_cpu_write_index(sel, *cmdIndex++);

        for (index = 0; index < count; ++index)
        {
            sunxi_lcd_cpu_write_data(sel, *cmdIndex++);
        }

        if (temp >= 0x80) {
            sunxi_lcd_delay_ms(150);
        }
    }

#if defined(CPU_TRI_MODE)
	/* enable te, mode 0 */

	sunxi_lcd_cpu_write_index(0, 0x35);
	sunxi_lcd_cpu_write_data(0, 0x00);

	sunxi_lcd_cpu_write_index(0, 0x44);
	sunxi_lcd_cpu_write_data(0, 0x00);
	sunxi_lcd_cpu_write_data(0, 0x80);
#endif

	lcdSetDirection(sel, LCD_DIRECTION_90);

	sunxi_lcd_cpu_write_index(sel, 0x2c);

	if (info->lcd_cpu_mode == LCD_CPU_AUTO_MODE)
	{
		DBG_INFO(" cpu auto mode.\n");
		sunxi_lcd_cpu_set_auto_mode(sel);
	}
}


/* panel driver name, must mach the name of lcd_drv_name in sys_config.fex */
struct __lcd_panel st7796sv_cpu_panel = {
	.name = "st7796sv_cpu",
	.func = {
		.cfg_panel_info = LCD_cfg_panel_info,
		.cfg_open_flow = LCD_open_flow,
		.cfg_close_flow = LCD_close_flow,
	},
};

t113UseTFT.png

#3 全志 SOC » 新人对F1C200S“裸机”使用8080接口16位LCD的几点感受,大伙轻喷 » 2025-05-24 18:03:20

william9527
回复: 3

有天逛gitee时发现有个叫“黑白菜”的UP的仓库里有F1C200S的一个库,看了下介绍,说是参照一些大佬的作品做出来的,感兴趣就试了试,的确挺好用的,地址在这里F1C200S,不知道这位兄弟在不在这个论坛,献上我对他和其他大佬们的敬意。
用了2天“黑白菜”的库后,我发现里面有初始化8080屏幕接口的函数,还有写入TCON0的0x64寄存器的函数。熟悉的小伙伴看见这个地址就知道这是专门发送8080命令给LCD用的,但是怎么用却没有提供例程,没办法自己摸索呗,以下是我这个初学者这几天摸索的结果,说的不对的地方请各位大佬指正。
一、TCON是自动按照时序发送命令给LCD的。
我第一次调通的时候也是很惊讶,当然这和我没有用过太多的SOC有关,初学者一枚。也就是说F1C200S和8080屏幕相连时,几个重要的管脚顺序如下(以RGB屏幕类比):

  • CS = VSYNC。

  • RD = HSYNC。

  • RS = DE。

  • WR = CLK。

这也就是用户手册中TCON0的0x68寄存器中提到的IO3~IO0几个管脚。其实对照T113或者D1H的手册中关于屏幕接线的图就不难发现的,估价大佬们都知道的,我是无意间发现,再通过实验证实的,这里就不长篇大论了,直接说结论:
举个例子我想发送一条指令给LCD,比如是设置屏幕横向尺寸的0x2A指令,一般情况下的指令序列用伪指令应该是。

CS = 0
WR = 0
RS = 0 // 发送指令为低,数据为高
D0~D15 = 0x2A
WR = 1
CS = 1

但是现在只需要给TCON0的0x64寄存器发送这个0x2A指令,接着设置0x60寄存器的第25位为低或者高,告诉TCON我们发送的是“命令”还是“数据”,那么TCON会按照上面伪指令的时序自动调整CS、WR管脚(读取的时候是RD管脚),比我们自己写指令方便多了,这在F1C200S的用户手册里没写,我是参照T113的用户手册看的,用“黑白菜”库里的代码如下:

void de_lcd_8080_write(uint16_t data, bool is_cmd) {
    while(read32(TCON_BASE + TCON0_CPU_INTF) & 0x00C00000)
        ;

    if(is_cmd) {
        clear32(TCON_BASE + TCON0_CPU_INTF, (1 << 25));
    } else {
        set32(TCON_BASE + TCON0_CPU_INTF, (1 << 25));
    }

    while(read32(TCON_BASE + TCON0_CPU_INTF) & 0x00C00000)
        ;

    uint32_t reg_data = ((data & 0xfc00) << 8) | ((data & 0x0300) << 6) | ((data & 0x00e0) << 5) | ((data & 0x001f) << 3);

    write32(TCON_BASE + TCON0_CPU_WR_DAT, reg_data);
}

我手头只有一台简单的示波器,没有逻辑分析仪,慢慢的一点点试出来的。
基于此,初始化屏幕就变得异常简单了,代码如下:

void lcdSetWindows(uint16_t x, uint16_t y, uint16_t width, uint16_t height)
{
    uint16_t temp;

    // 横向
    temp = x + width - 1;
    if (temp >= LCD_WIDTH)
    {
        temp = LCD_WIDTH - 1;
    }

    de_lcd_8080_write(0x2a, true);
    de_lcd_8080_write(x >> 8, false);
    de_lcd_8080_write(x & 0xFF, false);
    de_lcd_8080_write(temp >> 8, false);
    de_lcd_8080_write(temp & 0xFF, false);

    // 纵向
    temp = y + height - 1;
    if (temp >= LCD_HIGHT)
    {
        temp = LCD_HIGHT - 1;
    }

    de_lcd_8080_write(0x2b, true);
    de_lcd_8080_write(y >> 8, false);
    de_lcd_8080_write(y & 0xFF, false);
    de_lcd_8080_write(temp >> 8, false);
    de_lcd_8080_write(temp & 0xFF, false);

    // 开始写入屏幕
    de_lcd_8080_write(0x2c, true);
}

// 寄存器0x36掩码
#define MADCTL_MY 0x80  // 纵向交换
#define MADCTL_MX 0x40  // 横向交换
#define MADCTL_MV 0x20  // 纵横交换
#define MADCTL_ML 0x10  // 从下到上刷新
#define MADCTL_RGB 0x00 // RGB
#define MADCTL_BGR 0x08 // BGR
#define MADCTL_MH 0x04  // 从右到左刷新

void lcdSetDirection(eLcdDirection dir)
{
    uint8_t temp = 0;

    switch (dir)
    {
    case LCD_DIRECTION_0:
        temp = MADCTL_MX;
        break;

    case LCD_DIRECTION_90:
        temp = MADCTL_MV;
        break;

    case LCD_DIRECTION_180:
        temp = MADCTL_MY;
        break;

    case LCD_DIRECTION_270:
        temp = MADCTL_MY | MADCTL_MX | MADCTL_MV;
        break;

    default:
        break;
    }

    temp |= MADCTL_BGR;

    de_lcd_8080_write(0x36, true);
    de_lcd_8080_write(temp, false);

    lcdSetWindows(0, 0, LCD_WIDTH, LCD_HIGHT);
}

void lcdFill(uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint16_t color)
{
    lcdSetWindows(x, y, width, height);

    for (int y = 0; y < width; y++)
    {
        for (int x = 0; x < height; x++)
        {
            de_lcd_8080_write(color, false);
        }
    }
}

void lcdInit()
{
    // 屏幕RESET端口为PE7
    gpio_pin_init(GPIOE, 7, GPIO_MODE_OUTPUT, GPIO_PULL_NONE, GPIO_DRV_3);

    gpio_pin_set(GPIOE, 7);
    delay_soft(2); // 2 * 7 = 14ms
    gpio_pin_clear(GPIOE, 7);
    delay_soft(3); // 3 * 7 = 21ms
    gpio_pin_set(GPIOE, 7);
    delay_soft(20); // 20 * 7 = 140ms

    // 发送初始化命令
    uint8_t initCmd[] = {
        0x81, 0x11,
        2, 0xfb, 0xc3,
        2, 0xfb, 0x96,
        2, 0x36, 0x48,
        2, 0x3a, 0x55,
        2, 0xb4, 0x01,
        2, 0xb7, 0xc6,
        9, 0xe8, 0x40, 0x8a, 0x00, 0x00, 0x29, 0x19, 0xa5, 0x33,
        2, 0xc1, 0x06,
        2, 0xc2, 0xa7,
        2, 0xc5, 0x18,
        15, 0xe0, 0x0F, 0x09, 0x0b, 0x06, 0x04, 0x15, 0x2f, 0X54, 0x42, 0x3c, 0x17, 0x14, 0x18, 0x1b,
        15, 0xe1, 0xf0, 0x09, 0x0b, 0x06, 0x04, 0x03, 0x2d, 0x43, 0x42, 0x3b, 0x16, 0x14, 0x17, 0x1b,
        2, 0xeb, 0x3c,
        0x82, 0xeb, 0x69,
        1, 0x29,
        0x00};

    const uint8_t *cmdIndex = initCmd;
    uint8_t count, temp;

    while (*cmdIndex)
    {
        temp = *cmdIndex++;
        count = (temp & 0x7F) - 1;

        de_lcd_8080_write(*cmdIndex++, true);

        for (size_t index = 0; index < count; ++index)
        {
            de_lcd_8080_write(*cmdIndex++, false);
        }

        if (temp >= 0x80)
        {
            delay_soft(20); // 20 * 7 = 140ms
        }
    }

    // 设置屏幕方向为横屏
    lcdSetDirection(LCD_DIRECTION_90);

    // 清除屏幕
    lcdFill(0, 0, LCD_WIDTH, LCD_HIGHT, COLOR565_BLACK);
}

二、TCON、DEFE、DEBE的关系。
这个很好理解,手册里也说了,DEFE和DEBE可以对送过来的图像进行处理,然后送给TCON发送到LCD,今天先说TCON和DEBE。

三、屏幕的颜色模式怎么选择。
我的屏幕是一块普通的8080接口的4寸屏幕,最大16位传输(18位被厂家屏蔽了),RGB565格式,当然通过屏幕的最后三个管脚M0~M2可以让它成为8位或者9位传输模式(RGB666或者RGB888模式)。为了最大传输效率,我选择了16位RGB565模式。
tftFront.jpg
开发板是自己设计并焊接的,用起来效果还行吧,原本开发板搭载的是RGB屏幕,但是为了8080屏幕,自己又设计了块转接板。
converter.png
那么如何让TCON知道我的屏幕模式呢?请看TCON0的0x60寄存器的第31~29位,这些位就是用来设置屏幕格式的,其中的18、9、8位模式都好理解,为啥16位要有4种颜色模式呢,我的屏幕到底是哪种呢?
这个问题也是无意间解决的,就在第二天我无意间翻阅了T113的“LCD开发指南”的43页,猜我发现了什么?

  1. 18bit/1cycle (RGB666)

  2. 16bit/3cycle (RGB666)

  3. 16bit/2cycle (RGB666)

  4. 16bit/2cycle (RGB666)

  5. 16bit/1cycle (RGB565)

  6. 9bit/1cycle (RGB666)

  7. 8bit/3cycle (RGB666)

  8. 8bit/2cycle (RGB565)

是不是和TCON0的0x60寄存器的第31~29位对应上了,其中cycle的意思是写入数据需要几个时钟周期,比如我的屏幕是一次可以写入16位数据,所以是“16bit/1cycle (RGB565)”这个模式,按照F1C200S的用户手册相关内容写入“100”给第31~29位,这样颜色模式的问题就解决了。幸好显示接口变化不大,要不然还真不好找呢。

四、TCON和DEBE。
不知道大伙还记不记得,DEBE有个设置图层(Layer)的功能,一共有4个图层,我们一般用Layer0即第一层,通过DEBE的相关功能可以设置这个图层的大小、位置等,最后再用它的0x850寄存器绑定一个我们自己定义的数组给它(暂时叫它Buffer0)。这样我们修改自己的数组Buffer0的内容,等于间接修改了Layer0的内容。但是到这里我就不太明白了,修改了图层后,图层怎么传给TCON呢?RGB屏幕的话我们就不用管理了,TCON会自动处理。到了8080接口这里是不是也是这样呢?通过实验,我发现TCON0的0x60寄存器的第28、27位比较可疑,当然手册里面也写了,但是写的很模糊。第28位是启动自动刷新,第27位则是启动直接刷新,抱着试试看的态度,我将Buffer0的所有数据填充成某种颜色(比如红色),然后通过TCON发送指令0x2A、0x2B和0x2C指令给LCD(即设置写入窗口大小并打开LCD的数据写入功能),再设置TCON0的0x60寄存器的第28或27位设为1时,TCON自动的将Buffer0里面的数据传送给了LCD。我继续让程序每秒动态修改Buffer0的里面的颜色值,随后在LCD上显示出来的也就是写入的颜色了,而且速度很快(不知道是不是内部用了DMA),超过了LCD的液晶的反应速度(不知道这样说对不对),以至于我不得不加入了每帧7ms的延时,就这样也超过了每秒百帧以上的速度(不用延时帧率更高)。不过出来的图像因为液晶速度的原因会产生“Tear Effect”现象,幸好我的这个屏幕提供了一个FMARK管脚,用于同步处理TE信号,这都是后面要研究的了,代码片段如下:

    void lcdDirectFlush(bool enabled)
    {
        if (enabled)
        {
            set32(TCON_BASE + TCON0_CPU_INTF, (1 << 27));
        }
        else
        {
            clear32(TCON_BASE + TCON0_CPU_INTF, (1 << 27));
        }
    }

    ...

    lcdSetWindows(0, 0, LCD_WIDTH, LCD_HIGHT);
    // de_lcd_8080_auto_mode(true);
    lcdDirectFlush(true);

    uint16_t rectX, rectY, rectWidth, rectHeight;

    for(index = 0; index < testCount; ++index)
    {
        rectX = rand() % (LCD_WIDTH >> 1);
        rectY = rand() % (LCD_HIGHT >> 1);
        
        rectWidth = rand() % LCD_WIDTH;
        if(rectX + rectWidth >= LCD_WIDTH)
        {
            rectWidth = LCD_WIDTH - rectX - 1;
        }

        rectHeight = rand() % LCD_HIGHT;
        if(rectY + rectHeight >= LCD_HIGHT)
        {
            rectHeight = LCD_HIGHT - rectY - 1;
        }

        disp_fill(rectX, rectY, rectWidth, rectHeight, colorList[index % colorCount]);

        delay_soft(1); // 1 * 7 = 7ms
    }

这就是我这几天的一点感受,虽然很累但是觉得很值得。再次感谢“黑白菜”和论坛里各位大佬的不懈努力。不知道我以上的表述的清楚不清楚,请各位大佬批评指正。
PS:我已经整理好例程,向“黑白菜”提出了Pull requests。如果着急的话可以直接下载如下的代码压缩包,代码使用方法,先下载“黑白菜”的库,烧写它的BootLoader,然后将我的代码解压到库的“examples”文件夹里,进入文件夹后make download即可,我用的是串口0,需要改变可以修改system.c文件,记得让开发板提前进入FEL模式。
display8080.tar.gz

#4 Re: 全志 SOC » 全志V821驱动8080接口8位并口驱动TFT屏画面割裂,求指导。 » 2025-05-24 01:09:38

1、你的配置中的ht和vt算下来差不多12mhz的,是不是按照V821的手册照抄的,这个应该按照你的lcd的手册配置的。lcd_cpu_mode=<0>是为了自动刷屏,lcd_cpu_te=<0>是不用te功能,因为你说没有引出te管脚。
2、cat /sys/class/disp/disp/attr/sys多次,看看显示的irq数字变化了没有,如果没有变化还是时序不对。
3、建议找找本论坛里的“Linux_LCD_开发指南”,我是在100ask下载的。

#5 Re: 全志 SOC » 全志V821驱动8080接口8位并口驱动TFT屏画面割裂,求指导。 » 2025-05-23 12:34:03

lcd_cpu_mode和lcd_cpu_te都改为0。如果还是不行,lcd_dclk_freq设为12试试。

#6 Re: 全志 SOC » 肝了几天,8080接口的16位并行屏幕终于能在F1C200S上裸机显示了! » 2025-05-22 22:40:58

nTliang 说:

可以分享一下你的驱动吗?你用的内核版本是多少?

裸机跑的,没用linux,等我整理一下再发出来。

#9 Re: 全志 SOC » 肝了几天,8080接口的16位并行屏幕终于能在F1C200S上裸机显示了! » 2025-05-21 23:15:54

@nTliang
这个我倒是没有测试,你的帖子我看了,但手头没有工具测试。不过我试了一下nes,声音和图像同步的。整屏发送后是让硬件去处理framebuffer,写到frambuffer的东西tcon会自动送给lcd,不知道硬件有没有用到dma,我觉得速度还可以。

#10 Re: 全志 SOC » 肝了几天,8080接口的16位并行屏幕终于能在F1C200S上裸机显示了! » 2025-05-20 16:13:35

nTliang 说:

不是每帧的间隔,是每行的间隔。我这边的现象是发送完一行数据后会有几十us的空闲

是整屏发送,不是每行发送。

#11 Re: 全志 SOC » 肝了几天,8080接口的16位并行屏幕终于能在F1C200S上裸机显示了! » 2025-05-20 12:35:14

nTliang 说:

你这个传输数据的时候中间会有间隔吗?

每帧大概停了7ms左右,要不然一下子就传过去图像屏幕还没有反应过来。

#12 Re: 全志 SOC » 肝了几天,8080接口的16位并行屏幕终于能在F1C200S上裸机显示了! » 2025-05-20 12:34:06

fxyc87 说:

现在类似于f1c200s的芯片很多了吧,也很便宜,这芯片还有优势么?还没资料

手头还剩下几片,放着也是浪费,用起来呗。

#13 全志 SOC » 肝了几天,8080接口的16位并行屏幕终于能在F1C200S上裸机显示了! » 2025-05-19 22:34:35

william9527
回复: 12

手里有块4寸的16位8080接口并行LCD屏幕,想用F1C200S芯片驱动起来。但在网上找了一圈没有相关显示并行屏幕的例子,gitee上有位叫“黑白菜”的兄台出了个裸机程序,虽然有8080初始化部分,只有rgb屏幕的程序和例程。索性就自己研究一下呗,经过几天的努力,今天终于可以显示内容了,速度方面还算可以,终于可以休息了。

页脚

工信部备案:粤ICP备20025096号 Powered by FluxBB

感谢为中文互联网持续输出优质内容的各位老铁们。 QQ: 516333132, 微信(wechat): whycan_cn (哇酷网/挖坑网/填坑网) service@whycan.cn