0.96寸TFT横屏驱动ST7735S
前几年买的0.96寸TFT小屏幕,实在是太小了,还没手指头大,当时没派上用场,现在有空了来点亮一下。


还好当时保存了卖家给的资料,这个屏外围电路也简单,啥都不要,直接接线就行了。

示例代码是可以横竖屏切换的,但实测下来,无论哪种模式屏幕都有一小块区域刷不到,为花屏状态。
通过下列示例代码段的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);//储存器写
}
}
按照使用需求定义下屏幕原点,这种自带控制器的屏幕可以旋转显示方向,至于显存位置和面板实际走线的对应关系,可以直接在示例代码基础上进行加减调整,一般偏差不会太大。

先写个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++;
}



浙公网安备 33010602011771号