0.96寸TFT横屏驱动ST7735S

前几年买的0.96寸TFT小屏幕,实在是太小了,还没手指头大,当时没派上用场,现在有空了来点亮一下。
image
image
还好当时保存了卖家给的资料,这个屏外围电路也简单,啥都不要,直接接线就行了。
image
示例代码是可以横竖屏切换的,但实测下来,无论哪种模式屏幕都有一小块区域刷不到,为花屏状态。
通过下列示例代码段的X轴起始地址和结束地址都加了点偏移量,就可推断出驱动IC的一些Source Line或者Gate Line是没接的,因为这个屏幕虽然小,但边框真的很窄,留给面板走线的空间不多,可能就从IC驱动线的中间开始用起了。

LCD_WR_DATA(x1+24);
LCD_WR_DATA(x2+24);
#define USE_HORIZONTAL 2  //设置横屏或者竖屏显示 0或1为竖屏 2或3为横屏
#if USE_HORIZONTAL==0||USE_HORIZONTAL==1
#define LCD_W 80
#define LCD_H 160
#else
#define LCD_W 160
#define LCD_H 80
#endif

/******************************************************************************
      函数说明:设置起始和结束地址
      入口数据:x1,x2 设置列的起始和结束地址
                y1,y2 设置行的起始和结束地址
      返回值:  无
******************************************************************************/
void LCD_Address_Set(u16 x1,u16 y1,u16 x2,u16 y2)
{
	if(USE_HORIZONTAL==0)
	{
		LCD_WR_REG(0x2a);//列地址设置
		LCD_WR_DATA(x1+24);
		LCD_WR_DATA(x2+24);
		LCD_WR_REG(0x2b);//行地址设置
		LCD_WR_DATA(y1);
		LCD_WR_DATA(y2);
		LCD_WR_REG(0x2c);//储存器写
	}
	else if(USE_HORIZONTAL==1)
	{
		LCD_WR_REG(0x2a);//列地址设置
		LCD_WR_DATA(x1+24);
		LCD_WR_DATA(x2+24);
		LCD_WR_REG(0x2b);//行地址设置
		LCD_WR_DATA(y1);
		LCD_WR_DATA(y2);
		LCD_WR_REG(0x2c);//储存器写
	}
	else if(USE_HORIZONTAL==2)
	{
		LCD_WR_REG(0x2a);//列地址设置
		LCD_WR_DATA(x1);
		LCD_WR_DATA(x2);
		LCD_WR_REG(0x2b);//行地址设置
		LCD_WR_DATA(y1+24);
		LCD_WR_DATA(y2+24);
		LCD_WR_REG(0x2c);//储存器写
	}
	else
	{
		LCD_WR_REG(0x2a);//列地址设置
		LCD_WR_DATA(x1);
		LCD_WR_DATA(x2);
		LCD_WR_REG(0x2b);//行地址设置
		LCD_WR_DATA(y1+24);
		LCD_WR_DATA(y2+24);
		LCD_WR_REG(0x2c);//储存器写
	}
}

按照使用需求定义下屏幕原点,这种自带控制器的屏幕可以旋转显示方向,至于显存位置和面板实际走线的对应关系,可以直接在示例代码基础上进行加减调整,一般偏差不会太大。
image

先写个SPI驱动,测试屏幕就先不弄DMA了,轮询写SPI寄存器。

#include "drv_spi0.h"
#include "gd32f30x_spi.h"
#include "gd32f30x_gpio.h"
#include "gd32f30x_misc.h"

#define SPI0_CS_PIN     GPIO_PIN_4
#define SPI0_SCLK_PIN   GPIO_PIN_5
#define SPI0_MOSI_PIN   GPIO_PIN_7

void rt_spi0_init(void) {
    spi_parameter_struct spi_param;
    
    rcu_periph_clock_enable(RCU_SPI0);
    
    GPIO_BC(GPIOA) = SPI0_SCLK_PIN;
    GPIO_BC(GPIOA) = SPI0_MOSI_PIN;
    // CS idle is HIGH
    GPIO_BOP(GPIOA) = SPI0_CS_PIN;
    // SPI0 GPIO config: SCK D0
    gpio_init(GPIOA, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, (SPI0_SCLK_PIN | SPI0_MOSI_PIN));
    gpio_init(GPIOA, GPIO_MODE_OUT_PP, GPIO_OSPEED_2MHZ, SPI0_CS_PIN);
    
    // SPI0 parameter config
    spi_disable(SPI0);
    spi_param.trans_mode           = SPI_TRANSMODE_FULLDUPLEX;
    spi_param.device_mode          = SPI_MASTER;
    spi_param.frame_size           = SPI_FRAMESIZE_8BIT;
    spi_param.clock_polarity_phase = SPI_CK_PL_LOW_PH_1EDGE;
    spi_param.nss                  = SPI_NSS_SOFT;
    spi_param.prescale             = SPI_PSC_8;
    spi_param.endian               = SPI_ENDIAN_MSB;
    spi_init(SPI0, &spi_param);
    spi_enable(SPI0);
}

// @param cs bit0:cs take, bit1:cs release
// @param fill fill mode, use buf[0]
void rt_spi0_write(uint8_t cs, const uint8_t *buf, uint32_t len, uint32_t fill) {
    FlagStatus sts;
    if(cs & 0x1) {
        GPIO_BC(GPIOA) = SPI0_CS_PIN;
    }
    if(fill) {
        for(uint32_t i = 0; i < len; i++) {
            do {
                sts = spi_i2s_flag_get(SPI0, SPI_FLAG_TBE);
            }while(sts == RESET);
            spi_i2s_data_transmit(SPI0, buf[0]);
        }
    }else {
        for(uint32_t i = 0; i < len; i++) {
            do {
                sts = spi_i2s_flag_get(SPI0, SPI_FLAG_TBE);
            }while(sts == RESET);
            spi_i2s_data_transmit(SPI0, buf[i]);
        }
    }
    // wait transmit done
    do {
        sts = spi_i2s_flag_get(SPI0, SPI_FLAG_TRANS);
    }while(sts == SET);
    if(cs & 0x2) {
        GPIO_BOP(GPIOA) = SPI0_CS_PIN;
    }
}

void rt_spi0_write_16bit(uint8_t cs, const uint16_t *buf, uint32_t len, uint32_t fill) {
    FlagStatus sts;
    if(cs & 0x1) {
        GPIO_BC(GPIOA) = SPI0_CS_PIN;
    }
    if(fill) {
        for(uint32_t i = 0; i < len; i++) {
            do {
                sts = spi_i2s_flag_get(SPI0, SPI_FLAG_TBE);
            }while(sts == RESET);
            spi_i2s_data_transmit(SPI0, buf[0]);
        }
    }else {
        for(uint32_t i = 0; i < len; i++) {
            do {
                sts = spi_i2s_flag_get(SPI0, SPI_FLAG_TBE);
            }while(sts == RESET);
            spi_i2s_data_transmit(SPI0, buf[i]);
        }
    }
    // wait transmit done
    do {
        sts = spi_i2s_flag_get(SPI0, SPI_FLAG_TRANS);
    }while(sts == SET);
    if(cs & 0x2) {
        GPIO_BOP(GPIOA) = SPI0_CS_PIN;
    }
}

void rt_spi0_bitwidth(uint8_t bwidth) {
    uint32_t reg;
    SPI_CTL0(SPI0) &= (uint32_t)(~SPI_CTL0_SPIEN);
    reg = SPI_CTL0(SPI0);
    if(bwidth == 8) {
        reg &= 0xFFFFF7FFu;
    }else {
        reg |= 0x800u;
    }
    SPI_CTL0(SPI0) = reg;
    SPI_CTL0(SPI0) |= (uint32_t)SPI_CTL0_SPIEN;
}

void rt_spi0_close(void) {
    spi_disable(SPI0);
    rcu_periph_clock_disable(RCU_SPI0);
}

经过尝试,在横屏模式下的显存位置,Y轴需要正偏26像素,X轴需要正偏1像素。还有一点关于填充屏幕为全白状态,也就是清屏,需要看面板是常黑还是常白,常黑的未打开显示前打开背光是不透光的,常白的面板则是透光的,因此常黑面板显示白色就得把所有像素都偏转让背光透过去,在RGB565模式下需要填充0xFFFF,常白面板则相反,需要不偏转所有像素保持默认透光状态,填充0x0000

#define ST7735S_RESET_PIN    GPIO_PIN_3
#define ST7735S_A0_PIN       GPIO_PIN_6
#define ST7735S_USE_RGB565    1
static void st7735s_write_init_param(uint8_t cmd, uint8_t *args, uint32_t len);

typedef struct {
    const uint16_t cmd;
    const uint16_t len;
    const char *param;
} lcd_init_param_t;

const lcd_init_param_t lcd_init_squence[22] = {
    [0] = {
        .cmd = 0xB1,
        .len = 3,
        .param = "\x05\x3C\x3C"
    },
    [1] = {
        .cmd = 0xB2,
        .len = 3,
        .param = "\x05\x3C\x3C"
    },
    [2] = {
        .cmd = 0xB3,
        .len = 6,
        .param = "\x05\x3C\x3C\x05\x3C\x3C"
    },
    [3] = {
        .cmd = 0xB4,
        .len = 1,
        .param = "\x03"
    },
    [4] = {
        .cmd = 0xC0,
        .len = 3,
        .param = "\x0E\x0E\x04"
    },
    [5] = {
        .cmd = 0xC1,
        .len = 1,
        .param = "\xC5"
    },
    [6] = {
        .cmd = 0xC2,
        .len = 2,
        .param = "\x0D\x00"
    },
    [7] = {
        .cmd = 0xC3,
        .len = 2,
        .param = "\x8D\x2A"
    },
    [8] = {
        .cmd = 0xC4,
        .len = 2,
        .param = "\x8D\xEE"
    },
    [9] = {
        //VCOM
        .cmd = 0xC5,
        .len = 1,
        .param = "\x06"
    },
    [10] = {
        // Memory Access Control @RGB
        .cmd = 0x36,
        .len = 1,
        .param = "\xA8"
    },
    [11] = {
        .cmd = 0x3A,
        .len = 1,
#if ST7735S_USE_RGB565
        .param = "\x55"
#else
        .param = "\x66"
#endif
    },
    [12] = {
        .cmd = 0xE0,
        .len = 16,
        .param = "\x0b\x17\x0a\x0d\x1a\x19\x16\x1d\x21\x26\x37\x3c\x00\x09\x05\x01"
    },
    [13] = {
        .cmd = 0xE1,
        .len = 16,
        .param = "\x0c\x19\x09\x0d\x1b\x19\x15\x1d\x21\x26\x39\x3E\x00\x09\x05\x10"
    },
};

void st7735s_init() {
    const lcd_init_param_t *init_param;
    
    // RESET idle is HIGH
    GPIO_BOP(GPIOA) = ST7735S_RESET_PIN;
    // L:command; H:data
    GPIO_BC(GPIOA) = ST7735S_A0_PIN;
    
    gpio_init(GPIOA, GPIO_MODE_OUT_PP, GPIO_OSPEED_2MHZ, ST7735S_RESET_PIN);
    gpio_init(GPIOA, GPIO_MODE_OUT_PP, GPIO_OSPEED_2MHZ, ST7735S_A0_PIN);
    
    rt_spi0_init();
    st7735s_hw_reset();
    
    st7735s_write_init_param(0x11, NULL, 0); // Sleep exit 
    dwt_delay_ms(120);
    for(int i = 0; i < 14; i++) {
        init_param = (lcd_init_squence + i);
        st7735s_write_init_param((uint8_t)init_param->cmd, (uint8_t *)init_param->param, init_param->len);
    }
    dwt_delay_ms(120);    
    st7735s_write_init_param(0x29, NULL, 0); // Display on
}

void st7735s_hw_reset() {
    GPIO_BC(GPIOA) = ST7735S_RESET_PIN;
    dwt_delay_ms(1);
    GPIO_BOP(GPIOA) = ST7735S_RESET_PIN;
    dwt_delay_ms(120);
}

void st7735s_clear_screen() {
    st3375s_set_region(0, 0, 79, 159);
    st3375s_fill_color(0xFFFF, ST7735S_WIDTH * ST7735S_HEIGHT);
}

void st3375s_set_region(uint16_t left, uint16_t top, uint16_t right, uint16_t bottom) {
    uint16_t buf[2];
    const uint8_t col_cmd = 0x2A;
    const uint8_t row_cmd = 0x2B;
    //列地址设置
    GPIO_BC(GPIOA) = ST7735S_A0_PIN;
    rt_spi0_write((SPI0_CS_TAKE), &col_cmd, 1, SPI0_NORMAL_MODE);
    GPIO_BOP(GPIOA) = ST7735S_A0_PIN;
    rt_spi0_bitwidth(16);
    buf[0] = left + 1 ;//+ 24;
    buf[1] = right + 1 ;//+ 24;
    rt_spi0_write_16bit(SPI0_CS_RELEASE, buf, 2, SPI0_NORMAL_MODE);
    //行地址设置
    rt_spi0_bitwidth(8);
    GPIO_BC(GPIOA) = ST7735S_A0_PIN;
    rt_spi0_write(SPI0_CS_TAKE, &row_cmd, 1, SPI0_NORMAL_MODE);
    GPIO_BOP(GPIOA) = ST7735S_A0_PIN;
    rt_spi0_bitwidth(16);
    buf[0] = top+ 26;
    buf[1] = bottom+ 26;
    rt_spi0_write_16bit(SPI0_CS_RELEASE, buf, 2, SPI0_NORMAL_MODE);
    rt_spi0_bitwidth(8);
}

void st3375s_fill_color(uint16_t color, uint32_t len) {
    const uint8_t cmd = 0x2C;
    GPIO_BC(GPIOA) = ST7735S_A0_PIN;
    rt_spi0_write((SPI0_CS_TAKE), &cmd, 1, SPI0_NORMAL_MODE);
    GPIO_BOP(GPIOA) = ST7735S_A0_PIN;
    rt_spi0_bitwidth(16);
    rt_spi0_write_16bit((SPI0_CS_RELEASE), &color, len, SPI0_FILL_MODE);
    rt_spi0_bitwidth(8);
}

void st3375s_write_color(uint16_t *buf, uint32_t len) {
    const uint8_t cmd = 0x2C;
    GPIO_BC(GPIOA) = ST7735S_A0_PIN;
    rt_spi0_write((SPI0_CS_TAKE), &cmd, 1, SPI0_NORMAL_MODE);
    rt_spi0_bitwidth(16);
    GPIO_BOP(GPIOA) = ST7735S_A0_PIN;
    rt_spi0_write_16bit((SPI0_CS_RELEASE), buf, len, SPI0_NORMAL_MODE);
    rt_spi0_bitwidth(8);
}

static void st7735s_write_init_param(uint8_t cmd, uint8_t *args, uint32_t len) {
    // A0=0 cmd
    GPIO_BC(GPIOA) = ST7735S_A0_PIN;
    rt_spi0_write((SPI0_CS_TAKE), &cmd, 1, SPI0_NORMAL_MODE);
    GPIO_BOP(GPIOA) = ST7735S_A0_PIN;
    rt_spi0_write((SPI0_CS_RELEASE), args, len, SPI0_NORMAL_MODE);
}

驱动搞定了,就可以轮播两张图片试试效果了,这个屏幕虽小,但面板是IPS的,色彩还原和可视角度都很不错。

    /* configure systick */
    SystemCoreClockUpdate();
    lv_timer_core_init();
    systick_config();
    dwt_init();
    rtt_init();
	
    rcu_periph_clock_enable(RCU_GPIOA);
    
    st7735s_init();
    rt_kprintf("st7735s_init\n");
    //st7565_clear_screen();
    st3375s_set_region(0, 0, 159, 79);
    st3375s_fill_color(0xFFFF, 160*80);
    
    lv_timer_create(&drawer_timer, drawer_timer_func, 500, NULL);
static void drawer_timer_func(lv_timer_t* tm) {
    st3375s_set_region(0, 0, 159,79 );
    if(cpos & 0x1) {
        st3375s_write_color((uint16_t *)gImage_aaa, 12800);
    }else {
        st3375s_write_color((uint16_t *)gImage_aaa2, 12800);
    }
    cpos++;
}

image
image

posted @ 2025-07-11 22:39  Yanye  阅读(256)  评论(0)    收藏  举报