一. 前言
前面我们分享了驱动ST7789屏并且移植LVGL和emWin的过程,我们现在来分享驱动另外一块屏基于GC9A01A的1.28寸圆形屏,并移植LVGL和emWin。
二. 屏介绍
2.1 接口
我这里选用的是TFT128QY15-V2这个模组,主控是GC9A01A,是一个240x240,最大支持18位,即262k色的TFT屏,一般使用16位色即可。
网上找到了主控的规格书,如下,手动加了书签方便阅读。
从模组规格书看引脚,从引脚的名字就可以知道是用的什么接口方式,这里可以看出用的是
4-line serial interface Ⅰ。
串口接口有所谓的三线和四线,I型和II型,组合4种,如下IM引脚配置。
三线和4线的区别是4线专门有D/CX引脚表示发送的是命令还是数据,
而三线则将8位数据扩充为9位,第9位来表示是数据还是命令即无D/CX引脚。
I型和II型的区别是, II型有DO和DI两个引脚分别收发,而I型只有一个引脚DI即可收又可发。
我这里通过模组引脚可以看出使用的是4线I型。
2.2读写
上述串行接口实际就是SPI接口,对应的是模式0。
高位在前,多字节数据使用大端模式。
一般只需要使用写即可,读不是必须的。
2.2.1 写
按照先发命令字节,D/C=0
然后发送数据D/C=1的方式进行,命令后可以无数据。
CS拉低表示一次传输的开始,命令和数据两个过程可以是两次独立的SPI传输,即CS中间可以拉高。当然中间CS也可以保持拉低,但是要注意D/C的切换。
编程角度来看,分为两次传输更合适,因为一次传输完CS自动拉高,并且也总是在传输前配置好D/C,如果是一次传输,命令切换到数据时,应用层无法知道,无法去切换D/C。
注意发送多个数据之时中间CS不能再拉高,即一个字节拉高一次的CS是不符合要求。
2.2.1 读
I型接口,没有MISO线,使用MOSI同一根线读,且只能在RD开头的命令使用。
2.3 写显存
TTF内部有一个240x240x18-bit 的显存,TFT再根据设定的帧率刷新显存显示。
所以操作TFT显示有两种方式,一种是直接写TFT内部的显存,这种效率低会有拉窗帘的感觉。
还有一种主控芯片里开辟同样的显存,需要刷新显示时再批量写入到TFT的显存中,这种显示效果更好。我们采用后者。
最常用的是配置为16位,RGB565,直接将565格式的数据通过SPI写入即可
三. 驱动
相关代码如下
- io_spi.c/h 完全可移植,无需任何修改,在GC9A01A_ITF_SPI_IO配置为1时使用,即IO方式模拟SPI。
- gc9a01a.c/h 完全可移植,无需任何修改,st7789的驱动代码。
- gc9a01a_itf.c 需要移植代码,适配SPI接口。对外提供接口。
- gc9a01a_test.c/h 测试代码
gc9a01a.c和gc9a01a.h完全可移植
初始化列表进行初始化序列(可按需修改)
gc9a01a.c
/**
* \struct gc9a01a_cmd_st
* 命令结构体
*/
typedef struct
{
uint8_t cmd; /**< 命令 */
uint8_t data[12]; /**< 参数,最多5个参数 */
uint8_t datalen; /**< 参数长度 */
uint16_t delay; /**< 延时时间 */
} gc9a01a_cmd_st;
static gc9a01a_cmd_st s_gc9a01a_cmd_init_list[]=
{
///{0xEF,{0},0,0},
///{0xEB,{0x14},1,0},
/* 很多寄存器访问都需要Inter_command为高(默认为低)所以先发FE和EF配置Inter_command为高 */
{GC9A01A_CMD_IRE1,{0},0,0},
{GC9A01A_CMD_IRE2,{0},0,0},
{0xEB,{0x14},1,0},
{0x84,{0x40},1,0},
{0x85,{0xFF},1,0},
{0x86,{0xFF},1,0},
{0x87,{0xFF},1,0},
{0x88,{0x0A},1,0},
{0x89,{0x21},1,0},
{0x8A,{0x00},1,0},
{0x8B,{0x80},1,0},
{0x8C,{0x01},1,0},
{0x8D,{0x01},1,0},
{0x8E,{0xFF},1,0},
{0x8F,{0xFF},1,0},
/* 设置GS SS
* 第一个参数为0,第二个参数有效
* GS bit6 0:G1->G32 1:G32->G1
* SS bit5 0:S1->S360 1:S360->S1
*/
{GC9A01A_CMD_DFC,{0x00,0x20},2,0},
/**
* Memory Access Control
* 7 6 5 4 3 2 1 0
* MY MX MV ML BGR MH 0 0
* Y反转 X反转 XY交换 垂直刷新方向 0-RGB 水平刷新方向
* 1-BGR
*/
{GC9A01A_CMD_MADCTL,{0x08},1,0},
/* Pixel Format Set
*7 【6 5 4】 3 【2 1 0】
* DPI DBI
* RGB接口 MCU接口
* 101 16位 011 12位
* 110 18位 101 16位
* 110 18位
*/
{GC9A01A_CMD_COLMOD,{0x55},1,0},
{0x90,{0x08,0x08,0x08,0x08},4,0},
{0xBD,{0x06},1,0},
{0xBC,{0x00},1,0},
{0xFF,{0x60,0x01,0x04},3,0},
/* 电源控制 */
{GC9A01A_CMD_PC2,{0x13},1,0}, //vbp
{GC9A01A_CMD_PC3,{0x13},1,0}, //vbn
{GC9A01A_CMD_PC4,{0x22},1,0}, //vrh
{0xBE,{0x11},1,0},
{0xE1,{0x10,0x0E},2,0},
{0xDF,{0x21,0x0C,0x02},3,0},
/* 设置gamma曲线 */
{GC9A01A_CMD_SETGAMMA1,{0x45,0x09,0x08,0x08,0x26,0x2A},6,0}, //默认值 80 03 08 06 05 2B
{GC9A01A_CMD_SETGAMMA2,{0x43,0x70,0x72,0x36,0x37,0x6F},6,0}, //默认值 41 97 98 13 17 CD
{GC9A01A_CMD_SETGAMMA3,{0x45,0x09,0x08,0x08,0x26,0x2A},6,0}, //默认值 40 03 08 0B 08 2E
{GC9A01A_CMD_SETGAMMA3,{0x43,0x70,0x72,0x36,0x37,0x6F},6,0}, //默认值 3F 98 B4 14 18 CD
{0xED,{0x1B,0x0B},2,0},
{0xAE,{0x77},1,0},
{0xED,{0x1B,0x0B},2,0},
{0xCD,{0x63},1,0},
{0x70,{0x07,0x07,0x04,0x0E,0x0F,0x09,0x07,0x08,0x03},9,0},
{0xEB,{0x34},1,0},
{0x62,{0x18,0x0D,0x71,0xED,0x70,0x70,0x18,0x0F,0x71,0xEF,0x70,0x70},12,0},
{0x63,{0x18,0x11,0x71,0xF1,0x70,0x70,0x18,0x13,0x71,0xF3,0x70,0x70},12,0},
{0x64,{0x28,0x29,0xF1,0x01,0xF1,0x00,0x07},7,0},
{0x66,{0x3C,0x00,0xCD,0x67,0x45,0x45,0x10,0x00,0x00,0x00},10,0},
{0x67,{0x00,0x3C,0x00,0x00,0x00,0x01,0x54,0x10,0x32,0x98},10,0},
{0x74,{0x10,0x85,0x80,0x00,0x00,0x4E,0x00},7,0},
{0x98,{0x3E,0x07},2,0},
{GC9A01A_CMD_TELON,{0},0,0}, /* Tearing Effect Line ON */
{GC9A01A_CMD_INVON, {0x00},0,0},
{GC9A01A_CMD_SLPOUT,{0 },0,120}, /**< SLPOUT (11h): Sleep Out */
{GC9A01A_CMD_DISPON,{0}, 0,20}, /**< DISPON (29h): Display On */
};
/**
* \fn gc9a01a_write_cmd
* 写命令
* \param[in] dev \ref gc9a01a_dev_st
* \param[in] cmd 命令字节
* \retval 0 成功
* \retval 其他值 失败
*/
static int gc9a01a_write_cmd(gc9a01a_dev_st* dev,uint8_t cmd)
{
uint8_t tmp;
if(dev == (gc9a01a_dev_st*)0)
{
return -1;
}
if(dev->set_dcx == (gc9a01a_set_dcx_pf)0)
{
return -1;
}
if(dev->write == (gc9a01a_spi_write_pf)0)
{
return -1;
}
tmp = cmd;
dev->enable(1);
dev->set_dcx(0);
dev->write(&tmp,1);
dev->enable(0);
return 0;
}
/**
* \fn gc9a01a_write_data
* 写数据
* \param[in] dev \ref gc9a01a_dev_st
* \param[in] data 待写入数据
* \param[in] len 待写入数据长度
* \retval 0 成功
* \retval 其他值 失败
*/
static int gc9a01a_write_data(gc9a01a_dev_st* dev,uint8_t* data, uint32_t len)
{
if(dev == (gc9a01a_dev_st*)0)
{
return -1;
}
if(dev->set_dcx == (gc9a01a_set_dcx_pf)0)
{
return -1;
}
if(dev->write == (gc9a01a_spi_write_pf)0)
{
return -1;
}
dev->enable(1);
dev->set_dcx(1);
dev->write(data,len);
dev->enable(0);
return 0;
}
/**
* \fn gc9a01a_set_windows
* 设置窗口范围(行列地址)
* \param[in] dev \ref gc9a01a_dev_st
* \param[in] data 待写入数据
* \param[in] len 待写入数据长度
* \retval 0 成功
* \retval 其他值 失败
*/
static int gc9a01a_set_windows(gc9a01a_dev_st* dev, uint16_t x0, uint16_t x1, uint16_t y0, uint16_t y1)
{
uint8_t data[4];
gc9a01a_write_cmd(dev, GC9A01A_CMD_CASET);
data[0] = (x0>>8) & 0xFF; /* 列开始地址 大端 */
data[1] = x0 & 0xFF;
data[2] = (x1>>8) & 0xFF; /* 列结束地址 大端 */
data[3] = x1 & 0xFF;
gc9a01a_write_data(dev, data, 4);
gc9a01a_write_cmd(dev, GC9A01A_CMD_RASET);
data[0] = (y0>>8) & 0xFF; /* 行开始地址 大端 */
data[1] = y0 & 0xFF;
data[2] = (y1>>8) & 0xFF; /* 行结束地址 大端 */
data[3] = y1 & 0xFF;
gc9a01a_write_data(dev, data, 4);
return 0;
}
/**
* \fn gc9a01a_sync
* 现存写入gc9a01a
* \param[in] dev \ref gc9a01a_dev_st
* \paran[in] x0 列开始地址
* \paran[in] x1 列结束地址
* \paran[in] y0 行开始地址
* \paran[in] y1 行结束地址
* \paran[in] buffer 待写入数据
* \paran[in] len 待写入数据长度
* \retval 0 成功
* \retval 其他值 失败
*/
int gc9a01a_sync(gc9a01a_dev_st* dev, uint16_t x0, uint16_t x1, uint16_t y0, uint16_t y1, uint16_t* buffer, uint32_t len)
{
(void)dev;
gc9a01a_set_windows(dev, x0, x1, y0, y1);
gc9a01a_write_cmd(dev,GC9A01A_CMD_RAMWR);
gc9a01a_write_data(dev, (uint8_t*)buffer, len);
return 0;
}
/**
* \fn gc9a01a_init
* 初始化
* \param[in] dev \ref gc9a01a_dev_st
* \retval 0 成功
* \retval 其他值 失败
*/
int gc9a01a_init(gc9a01a_dev_st* dev)
{
if(dev == (gc9a01a_dev_st*)0)
{
return -1;
}
if(dev->init_flag != 0)
{
return 0;
}
dev->init_flag = 1;
if(dev->init != 0)
{
dev->init();
}
dev->set_reset(1);
dev->delay(120);
dev->set_reset(0);
dev->delay(120);
dev->set_reset(1);
dev->delay(120);
/* 初始化序列 */
for(uint32_t i=0; i<sizeof(s_gc9a01a_cmd_init_list)/sizeof(s_gc9a01a_cmd_init_list[0]); i++)
{
gc9a01a_write_cmd(dev, s_gc9a01a_cmd_init_list[i].cmd);
if(s_gc9a01a_cmd_init_list[i].datalen > 0)
{
gc9a01a_write_data(dev, s_gc9a01a_cmd_init_list[i].data,s_gc9a01a_cmd_init_list[i].datalen);
if(s_gc9a01a_cmd_init_list[i].delay > 0)
{
dev->delay(s_gc9a01a_cmd_init_list[i].delay);
}
}
}
return 0;
}
/**
* \fn gc9a01a_deinit
* 解除初始化
* \param[in] dev \ref gc9a01a_dev_st
* \return 总是返回0
*/
int gc9a01a_deinit(gc9a01a_dev_st* dev)
{
if(dev == (gc9a01a_dev_st*)0)
{
return -1;
}
/* @todo 添加IO等解除初始化配置 */
if(dev->deinit != 0)
{
dev->deinit();
}
return 0;
}
gc9a01a.h
extern "C"{
typedef void (*gc9a01a_set_dcx_pf)(uint8_t val); /**< DCX引脚操作接口,val=1为数据和参数, val=0为命令 */
typedef void (*gc9a01a_set_reset_pf)(uint8_t val); /**< 复位引脚操作,val=1输出高,val=0输出低 */
typedef void (*gc9a01a_spi_write_pf)(uint8_t* buffer, uint32_t len); /**< MOSI写接口接口,buffer为待写数据,len为待写长度 */
typedef void (*gc9a01a_spi_enable_pf)(uint8_t val); /**< 使能接口 */
typedef void (*gc9a01a_spi_delay_ms_pf)(uint32_t t); /**< 延时接口 */
typedef void (*gc9a01a_init_pf)(void); /**< 初始化接口 */
typedef void (*gc9a01a_deinit_pf)(void); /**< 解除初始化接口 */
/**
* \struct gc9a01a_dev_st
* 设备接口结构体
*/
typedef struct
{
gc9a01a_set_dcx_pf set_dcx; /**< DCX写接口 */
gc9a01a_set_reset_pf set_reset; /**< RESET写接口 */
gc9a01a_spi_write_pf write; /**< 数据写接口 */
gc9a01a_spi_enable_pf enable; /**< 使能接口 */
gc9a01a_spi_delay_ms_pf delay; /**< 延时接口 */
gc9a01a_init_pf init; /**< 初始化接口 */
gc9a01a_deinit_pf deinit; /**< 解除初始化接口 */
uint16_t* buffer; /**< 显存,用户分配 */
int init_flag; /**< 初始化标志 */
} gc9a01a_dev_st;
/**
* \fn gc9a01a_sync
* 现存写入gc9a01a
* \param[in] dev \ref gc9a01a_dev_st
* \paran[in] x0 列开始地址
* \paran[in] x1 列结束地址
* \paran[in] y0 行开始地址
* \paran[in] y1 行结束地址
* \paran[in] buffer 待写入数据
* \paran[in] len 待写入数据长度
* \retval 0 成功
* \retval 其他值 失败
*/
int gc9a01a_sync(gc9a01a_dev_st* dev, uint16_t x0, uint16_t x1, uint16_t y0, uint16_t y1, uint16_t* buffer, uint32_t len);
/**
* \fn gc9a01a_init
* 初始化
* \param[in] dev \ref gc9a01a_dev_st
* \retval 0 成功
* \retval 其他值 失败
*/
int gc9a01a_init(gc9a01a_dev_st* dev);
/**
* \fn gc9a01a_deinit
* 解除初始化
* \param[in] dev \ref gc9a01a_dev_st
* \return 总是返回0
*/
int gc9a01a_deinit(gc9a01a_dev_st* dev);
}
gc9a01a_test.c
static void rgb_test(void)
{
for(int x=0;x<240;x++)
{
for(int y=0;y<320;y++)
{
gc9a01a_itf_set_pixel(x, y, 0xF800);
}
}
gc9a01a_itf_sync();
os_delay(1000);
for(int x=0;x<240;x++)
{
for(int y=0;y<320;y++)
{
gc9a01a_itf_set_pixel(x, y, 0x07E0);
}
}
gc9a01a_itf_sync();
os_delay(1000);
for(int x=0;x<240;x++)
{
for(int y=0;y<320;y++)
{
gc9a01a_itf_set_pixel(x, y, 0x001F);
}
}
gc9a01a_itf_sync();
os_delay(1000);
}
int gc9a01a_test(void)
{
printf("gc9a01a test\r\n");
gc9a01a_itf_init();
rgb_test();
uint32_t start;
uint32_t end;
uint32_t ftime = 0;
while(0)
{
start = timer_get_time();
for(int i=0;i<10;i++)
{
gc9a01a_itf_sync();
}
end = timer_get_time();
ftime = (end - start);
uint32_t fps = (ftime*2+100)/(100*2); /* 刷新一次的时间uS */
if(fps > 0)
{
printf("FPS:%d\r\n",1000000/fps);
}
else
{
printf("FPS:%d\r\n",0);
}
}
return 0;
}
gc9a01a_test.h
extern "C"{
int gc9a01a_test(void);
}
gc9a01a_itf.c
/******************************************************************************
* 以下是底层适配
*
******************************************************************************/
/* 使用IO模拟SPI方式 */
static void port_io_spi_cs_write(uint8_t val)
{
gpio_write(CS_PIN,val);
}
static void port_io_spi_sck_write(uint8_t val)
{
gpio_write(SCL_PIN,val);
}
static void port_io_spi_mosi_write(uint8_t val)
{
gpio_write(SDA_PIN,val);
}
static uint8_t port_io_spi_miso_read(void)
{
return 0;
}
static void port_io_spi_init(void)
{
gpio_open(CS_PIN, GPIO_DIRECTION_OUTPUT);
gpio_set_pull_mode(CS_PIN, GPIO_PULL_UP);
gpio_write(CS_PIN,1);
gpio_open(SCL_PIN, GPIO_DIRECTION_OUTPUT);
gpio_set_pull_mode(SCL_PIN, GPIO_PULL_UP);
gpio_write(SCL_PIN,1);
gpio_open(SDA_PIN, GPIO_DIRECTION_OUTPUT);
gpio_set_pull_mode(SDA_PIN, GPIO_PULL_UP);
gpio_write(SDA_PIN,1);
if(RST_PIN < GPIO_INVALID)
{
gpio_open(RST_PIN, GPIO_DIRECTION_OUTPUT);
gpio_set_pull_mode(RST_PIN, GPIO_PULL_UP);
gpio_write(RST_PIN,1);
}
}
static void port_io_spi_deinit(void)
{
gpio_close(CS_PIN);
gpio_close(SCL_PIN);
gpio_close(SDA_PIN);
}
/* IO模拟SPI设备实例 */
static io_spi_dev_st s_io_spi_dev =
{
.cs_write = port_io_spi_cs_write,
.sck_write = port_io_spi_sck_write,
.mosi_write = port_io_spi_mosi_write,
.miso_read = port_io_spi_miso_read,
.delay_pf = 0,
.init = port_io_spi_init,
.deinit = port_io_spi_deinit,
.delayns = 1,
.mode = 0,
.msb = 1,
};
static void port_gc9a01a_set_dcx(uint8_t val)
{
gpio_write(DC_PIN, val);
}
static void port_gc9a01a_set_reset(uint8_t val)
{
(void)val;
if(RST_PIN < GPIO_INVALID)
{
gpio_write(RST_PIN, val);
}
}
static void port_gc9a01a_spi_write(uint8_t* buffer, uint32_t len)
{
io_spi_trans(&s_io_spi_dev, buffer, 0, len);
}
static void port_gc9a01a_spi_enable(uint8_t val)
{
if(val)
{
io_spi_enable(&s_io_spi_dev);
}
else
{
io_spi_disable(&s_io_spi_dev);
}
}
static void port_gc9a01a_delay_ms(uint32_t t)
{
if(t > 0)
{
os_delay(t);
}
}
static void port_gc9a01a_init(void)
{
gpio_open(DC_PIN, GPIO_DIRECTION_OUTPUT);
gpio_set_pull_mode(DC_PIN, GPIO_PULL_UP);
gpio_write(DC_PIN,1);
io_spi_init(&s_io_spi_dev);
}
static void port_gc9a01a_deinit(void)
{
gpio_close(DC_PIN);
io_spi_deinit(&s_io_spi_dev);
}
/* 使用硬件SPI方式 */
static void port_gc9a01a_set_dcx(uint8_t val)
{
gpio_write(DC_PIN, val);
}
static void port_gc9a01a_set_reset(uint8_t val)
{
(void)val;
if(RST_PIN < GPIO_INVALID)
{
gpio_write(RST_PIN, val);
}
}
volatile uint32_t s_gc9a01a_spi_busy_flag = 0;
static void spi_dma_cb(void)
{
//printf("spi done\r\n");
s_gc9a01a_spi_busy_flag = 0;
timer_delay_us(2);
}
static void port_gc9a01a_spi_write(uint8_t* buffer, uint32_t len)
{
RET ret;
int timeout = 1000;
while(s_gc9a01a_spi_busy_flag != 0)
{
if(len > 1024ul)
{
/* 传输数据较多时os delay让出CPU给其他任务 */
os_delay(1);
timeout--;
if(timeout <= 0)
{
printf("spi busy\r\n");
return;
}
}
else
{
/* 传输数据较少时,直接等待传输完,没必要再主动delay让出CPU */
}
}
s_gc9a01a_spi_busy_flag = 1;
if(RET_OK != (ret = spi_dma_trans_direct(USE_SPI_PORT, buffer, 0, len, spi_dma_cb)))
{
printf("spi err %d\r\n", ret);
s_gc9a01a_spi_busy_flag = 0;
return;
}
}
static void port_gc9a01a_spi_enable(uint8_t val)
{
if(val)
{
//gpio_write(CS_PIN,0);
}
else
{
//gpio_write(CS_PIN,1);
}
}
static void port_gc9a01a_delay_ms(uint32_t t)
{
os_delay(t);
}
static void port_gc9a01a_init(void)
{
gpio_open(DC_PIN, GPIO_DIRECTION_OUTPUT);
gpio_set_pull_mode(DC_PIN, GPIO_PULL_UP);
gpio_write(DC_PIN,1);
gpio_open(GPIO_PMM00, GPIO_DIRECTION_OUTPUT);
gpio_set_pull_mode(GPIO_PMM00, GPIO_PULL_UP);
gpio_write(GPIO_PMM00,1);
gpio_open(GPIO_PMM01, GPIO_DIRECTION_OUTPUT);
gpio_set_pull_mode(GPIO_PMM01, GPIO_PULL_UP);
gpio_write(GPIO_PMM01,1);
//gpio_open(CS_PIN, GPIO_DIRECTION_OUTPUT);
//gpio_set_pull_mode(CS_PIN, GPIO_PULL_UP);
//gpio_write(CS_PIN,1);
if(RST_PIN < GPIO_INVALID)
{
gpio_open(RST_PIN, GPIO_DIRECTION_OUTPUT);
gpio_set_pull_mode(RST_PIN, GPIO_PULL_UP);
gpio_write(RST_PIN,1);
}
printf("io init\r\n");
/* SPI配置 */
spi_cfg_t spi_cfg;
spi_gpio_cfg_t gpio_cfg;
spi_cfg.frequency = 80ul * 1000ul * 1000ul; // SPI时钟源是350M,在此基础上再分频。本TFT支持最大100M. 设置60M实际是350/6=58.3MHz
gpio_cfg.auto_cs = false;
gpio_cfg.cs = CS_PIN;
gpio_cfg.clk = SCL_PIN;
gpio_cfg.miso = GPIO_INVALID;
gpio_cfg.mosi = SDA_PIN;
if(gpio_cfg.auto_cs == false)
{
gpio_open(gpio_cfg.cs, GPIO_DIRECTION_OUTPUT);
gpio_set_pull_mode(gpio_cfg.cs, GPIO_PULL_UP);
}
RET ret;
if(RET_OK != (ret = spi_init(USE_SPI_PORT)))
{
printf("spi init err %d\r\n", ret);
}
if(RET_OK != (ret = spi_open(USE_SPI_PORT, &spi_cfg, &gpio_cfg)))
{
printf("spi open err %d\r\n", ret);
}
spi_modify_CPOL_CPHA(USE_SPI_PORT, SPI_CLK_MODE_0);
printf("spi init\r\n");
}
static void port_gc9a01a_deinit(void)
{
gpio_close(DC_PIN);
if(RST_PIN < GPIO_INVALID)
{
gpio_close(RST_PIN);
}
spi_close(USE_SPI_PORT);
}
/******************************************************************************
* 以下是GC9A01A设备实例
*
******************************************************************************/
static uint16_t s_gc9a01a_itf_buffer[GC9A01A_HSIZE][GC9A01A_VSIZE]; /**< 显存 */
/* 设备实例 */
static gc9a01a_dev_st s_gc9a01a_itf_dev =
{
.set_dcx = port_gc9a01a_set_dcx,
.set_reset = port_gc9a01a_set_reset,
.write = port_gc9a01a_spi_write,
.enable = port_gc9a01a_spi_enable,
.delay = port_gc9a01a_delay_ms,
.init = port_gc9a01a_init,
.deinit = port_gc9a01a_deinit,
.buffer = (uint16_t*)s_gc9a01a_itf_buffer,
};
/******************************************************************************
* 以下是对外操作接口
*
******************************************************************************/
/**
* \fn gc9a01a_itf_init
* 初始化
* \retval 0 成功
* \retval 其他值 失败
*/
int gc9a01a_itf_init(void)
{
gpio_open(DC_PIN, GPIO_DIRECTION_OUTPUT);
gpio_set_pull_mode(DC_PIN, GPIO_PULL_UP);
gpio_write(DC_PIN,1);
gpio_open(CS_PIN, GPIO_DIRECTION_OUTPUT);
gpio_set_pull_mode(CS_PIN, GPIO_PULL_UP);
gpio_write(CS_PIN,1);
gpio_open(SCL_PIN, GPIO_DIRECTION_OUTPUT);
gpio_set_pull_mode(SCL_PIN, GPIO_PULL_UP);
gpio_write(SCL_PIN,1);
gpio_open(SDA_PIN, GPIO_DIRECTION_OUTPUT);
gpio_set_pull_mode(SDA_PIN, GPIO_PULL_UP);
gpio_write(SDA_PIN,1);
while(1)
{
static int s_cnt = 0;
if(s_cnt%1 == 0)
{
gpio_toggle(DC_PIN);
}
if(s_cnt%2 == 1)
{
gpio_toggle(CS_PIN);
}
if(s_cnt%4 == 2)
{
gpio_toggle(SCL_PIN);
}
if(s_cnt%8 == 3)
{
gpio_toggle(SDA_PIN);
}
os_delay(1);
s_cnt++;
}
return gc9a01a_init(&s_gc9a01a_itf_dev);
}
/**
* \fn gc9a01a_itf_deinit
* 解除初始化
* \retval 0 成功
* \retval 其他值 失败
*/
int gc9a01a_itf_deinit(void)
{
return gc9a01a_deinit(&s_gc9a01a_itf_dev);
}
/**
* \fn gc9a01a_itf_sync
* 刷新显示
* \retval 0 成功
* \retval 其他值 失败
*/
int gc9a01a_itf_sync(void)
{
return gc9a01a_sync(&s_gc9a01a_itf_dev, 0, GC9A01A_HSIZE-1, 0, GC9A01A_VSIZE-1, s_gc9a01a_itf_dev.buffer, GC9A01A_HSIZE*GC9A01A_VSIZE*2);
}
/**
* \fn gc9a01a_itf_set_pixel
* 写点
* \param[in] x x坐标位置
* \param[in] y y坐标位置
* \param[in] rgb565 颜色
*/
void gc9a01a_itf_set_pixel(uint16_t x, uint16_t y, uint16_t rgb565)
{
//if(x >= GC9A01A_HSIZE)
//{
// return -1;
//}
//if(y >= GC9A01A_VSIZE)
//{
// return -1;
//}
s_gc9a01a_itf_dev.buffer[y*GC9A01A_HSIZE + x] = (uint16_t)((rgb565>>8)&0xFF) | (uint16_t)((rgb565<<8) & 0xFF00);
}
/**
* \fn gc9a01a_itf_get_pixel
* 读点
* \param[in] x x坐标位置
* \param[in] y y坐标位置
* \return rgb565颜色
*/
uint16_t gc9a01a_itf_get_pixel(uint16_t x, uint16_t y)
{
uint16_t color = s_gc9a01a_itf_dev.buffer[y*GC9A01A_HSIZE + x];
return ((uint16_t)(color>>8) | (uint16_t)(color<<8));
}
gc9a01_itf.h
extern "C"{
/**
* \fn gc9a01a_itf_init
* 初始化
* \retval 0 成功
* \retval 其他值 失败
*/
int gc9a01a_itf_init(void);
/**
* \fn gc9a01a_itf_deinit
* 解除初始化
* \retval 0 成功
* \retval 其他值 失败
*/
int gc9a01a_itf_deinit(void);
/**
* \fn gc9a01a_itf_sync
* 刷新显示
* \retval 0 成功
* \retval 其他值 失败
*/
int gc9a01a_itf_sync(void);
/**
* \fn gc9a01a_itf_set_pixel
* 写点
* \param[in] x x坐标位置
* \param[in] y y坐标位置
* \param[in] rgb565 颜色
*/
void gc9a01a_itf_set_pixel(uint16_t x, uint16_t y, uint16_t rgb565);
/**
* \fn gc9a01a_itf_get_pixel
* 读点
* \param[in] x x坐标位置
* \param[in] y y坐标位置
* \return rgb565颜色
*/
uint16_t gc9a01a_itf_get_pixel(uint16_t x, uint16_t y);
}
io_spi.c
#include "io_spi.h"
void io_spi_enable(io_spi_dev_st* dev)
{
if((dev != 0) && (dev->cs_write != 0) && (dev->sck_write != 0))
{
/* 准备空闲时的SCK状态,在CS拉低之前准备好 */
dev->sck_write((dev->mode & 0x02) >> 1);
if(dev->delay_pf != 0)
{
dev->delay_pf(dev->delayns);
}
/* 拉低CS */
dev->cs_write(0);
/* (5) SCK电平保持 */
//if(dev->delay_pf != 0)
//{
// dev->delay_pf(dev->delayns);
//}
}
}
void io_spi_disable(io_spi_dev_st* dev)
{
if((dev != 0) && (dev->cs_write != 0))
{
dev->cs_write(1);
}
}
/**
* _____ _____
* CS |_____________________________________________________________|
* _____________ _________
* SCK(CPOL=0) xx__________| |___ xxx __________| |__________
* __________ ____xxx __________ __________
* SCK(CPOL=1) xx |_____________| |_________|
* (0)
* (1)
* (2)
* (3)(4)
* (5)
* (6)(7)
* MISO ^ ^
* MOSI ^
* (1) (2) (4) (6)
* (3) (5)
* 其中()表示行为,^表示MOSI/MISO的输出或者采样位置.
* (0) io_spi_enable 准备SCK空闲状态,拉低CS.
* (1) 准备SCK初始状态,和(0)时SCK初始状态一样,代码中执行这个操作的目的仅仅是初始化局部变量cpol而已.
* (2) 输出MOSI数据.
* (3) 反转SCK产生第1个边沿.
* (4) 如果CPHA=0 则第1个边沿采样,MISO在此采样.
* (5) SCK高/低电平保持时间.
* (6) 反转SCK产生第2个边沿.
* (7) 如果CPHA=1 则第2个边沿采样,MISO在此采样.
*/
int io_spi_trans(io_spi_dev_st* dev, uint8_t* tx, uint8_t* rx, uint32_t size)
{
uint32_t i = 0; /* 字节数循环 */
uint8_t j = 0; /* 位数循环 */
uint8_t msb = 0; /* MSB标志 */
uint8_t cpha = 0; /* 相位标志bit0 */
uint8_t cpol = 0; /* 极性标志bit1 */
uint8_t rx_val = 0; /* 发送字节缓存 */
uint8_t tx_val = 0; /* 接收字节缓存 */
if(dev == 0)
{
return -1;
}
if((dev->miso_read == 0) || (dev->mosi_write == 0) || (dev->sck_write == 0))
{
/* dev->delay_pf 可以不实现 */
return -1;
}
cpha = dev->mode & 0x01;
msb = dev->msb;
/* (1) 准备空闲时的SCK状态 */
cpol = (dev->mode & 0x02) >> 1;
/* 这一句其实可以不用,和io_spi_enable效果一样,这里仅需要初始化cpol局部变量即可
* 加上这一句可以在此确保SCK引脚状态初始化,可靠性角度来说加上提高冗余.
*/
dev->sck_write(cpol);
for(i=0; i<size; i++)
{
/* 取待发送的值, 用户没有提供则发送0xFF */
if(tx != 0)
{
tx_val = *tx++;
}
else
{
tx_val = 0xFF;
}
/* 接收到的值初始化 */
rx_val = 0;
for(j=0 ;j<8; j++)
{
/* (2)对于发送,不管对方哪个边沿采样,都是都在第一个边沿之前准备好MOSI就行
* 如果对于对方第一个边沿采样,这里修改MOSI之后最好有个数据建立时间
*/
if(msb)
{
dev->mosi_write(tx_val & 0x80); /* 高位在前,先发送高位,未发送数据再往高位移动 */
tx_val <<= 0x1; /* 注意写的时候是先写后移位 */
}
else
{
dev->mosi_write(tx_val & 0x01); /* 低位在前,先发送低位,未发送数据再往高位移动 */
tx_val >>= 0x1;
}
/* (3)反转产生第1个CLK边沿 */
cpol ^= 0x01;
dev->sck_write(cpol);
if(rx != 0)
{
if(cpha == 0)
{
/* (4)第一个边沿采样 */
if(msb)
{
rx_val <<= 0x1; /* 注意读的时候是先移位后读 */
rx_val |= dev->miso_read(); /* 高位在前,先读到低位,已接收数据再往高位移动 */
}
else
{
rx_val >>= 0x1;
rx_val |= dev->miso_read() <<7; /* 低位在前,先读到高位,已接收数据再往低位移动 */
}
}
}
/* (5) SCK电平保持 */
if(dev->delay_pf != 0)
{
dev->delay_pf(dev->delayns);
}
/* (6)反转产生第2个CLK边沿 */
cpol ^= 0x01;
dev->sck_write(cpol);
if(rx != 0)
{
if(cpha == 1)
{
/* (7) 第2个边沿采样 */
if(msb)
{
rx_val <<= 0x1;
rx_val |= dev->miso_read(); /* 高位在前,先读到低位再往高位移动 */
}
else
{
rx_val >>= 0x1;
rx_val |= dev->miso_read()<<7; /* 低位在前,先读到高位再往低位移动 */
}
}
}
/* (5) SCK电平保持 */
if(dev->delay_pf != 0)
{
dev->delay_pf(dev->delayns);
}
}
/* 存储读到的值 */
if(rx != 0)
{
*rx++ = rx_val;
}
}
return 0;
}
void io_spi_init(io_spi_dev_st* dev)
{
if((dev != 0) && (dev->init != 0))
{
dev->init();
}
}
void io_spi_deinit(io_spi_dev_st* dev)
{
if((dev != 0) && (dev->deinit != 0))
{
dev->deinit();
}
}
io_spi.h
extern "C"{
typedef void (*io_spi_cs_write_pf)(uint8_t val); /**< CS写接口 */
typedef void (*io_spi_sck_write_pf)(uint8_t val); /**< SCK写接口 */
typedef void (*io_spi_mosi_write_pf)(uint8_t val); /**< MOSI写接口 */
typedef uint8_t (*io_spi_miso_read_pf)(void); /**< MISO读接口 */
typedef void (*io_spi_delay_ns_pf)(uint32_t delay); /**< 延时接口 */
typedef void (*io_spi_init_pf)(void); /**< 初始化接口 */
typedef void (*io_spi_deinit_pf)(void); /**< 解除初始化接口 */
/**
* \struct io_spi_dev_st
* 接口结构体
*/
typedef struct
{
io_spi_cs_write_pf cs_write; /**< cs写接口 */
io_spi_sck_write_pf sck_write; /**< sck写接口 */
io_spi_mosi_write_pf mosi_write; /**< mosi写接口 */
io_spi_miso_read_pf miso_read; /**< miso读接口 */
io_spi_delay_ns_pf delay_pf; /**< 延时接口 */
io_spi_init_pf init; /**< 初始化接口 */
io_spi_deinit_pf deinit; /**< 解除初始化接口 */
uint32_t delayns; /**< 延迟时间 */
uint8_t mode; /**< 模式0~3 bit0 CPHA bit1 CPOL */
uint8_t msb; /**< 1高位在前 否则低位在前 */
} io_spi_dev_st;
/**
* \fn io_spi_enable
* 发送CS使能信号,拉低CS
* \param[in] dev \ref io_spi_dev_st
*/
void io_spi_enable(io_spi_dev_st* dev);
/**
* \fn io_spi_disable
* 拉高CS,取消片选
* \param[in] dev \ref io_spi_dev_st
*/
void io_spi_disable(io_spi_dev_st* dev);
/**
* \fn io_spi_trans
* 传输,发送的同时读
* \param[in] dev \ref io_spi_dev_st
* \param[in] tx 待发送的数据 如果tx为空则默认发送FF
* \param[out] rx 存储接收的数据 如果rx为空则不读
* \param[in] size 传输的字节数
* \retval 0 读成功
* \retval -1 参数错误
*/
int io_spi_trans(io_spi_dev_st* dev, uint8_t* tx, uint8_t* rx, uint32_t size);
/**
* \fn io_spi_init
* 初始化
* \param[in] dev \ref io_spi_dev_st
*/
void io_spi_init(io_spi_dev_st* dev);
/**
* \fn io_spi_deinit
* 解除初始化
* \param[in] dev \ref io_spi_dev_st
*/
void io_spi_deinit(io_spi_dev_st* dev);
}
四. 测试
先检查供电,比如IOVCC接1.8V,VCC接2.8V,背光是否正确,其他GND等引脚是否正确。
注意RESET一样要由MCU控制,初始化时复位才可靠。
然后再看信号引脚。
初始化序列,和s_gc9a01a_cmd_init_list对应
先逻辑分析仪确认发出的命令正确,比如
如下初始化序列对应逻辑分析仪抓取的信号如下,可以看到是正确的
{GC9A01A_CMD_IRE1,{0},0,0},
{GC9A01A_CMD_IRE2,{0},0,0},
{0xEB,{0x14},1,0},
整个波形见如下地址,使用DsView打开。
链接:https://pan.baidu.com/s/19yAHeiG-aPeWOh_ojErIqw?pwd=dj05
提取码:dj05
显示效果见视频LVGL和emWin的Demo;
https://mp.weixin.qq.com/s/SjvmB4dsMvdXkj8WGgJRqA 基于GC9A01A的1.28存圆屏移植emWin
https://mp.weixin.qq.com/s/gtRr41XfvttNhsRhep2IGg 基于GC9A01A的1.28存圆屏移植LVGL
五. 总结
调试屏幕时可以先用IO模拟SPI方式调通,然后再改为SPI模式,调整性能。
RESET一定要接,最好是软件控制进行复位。浮空肯定是不行的,固定上拉也不可靠,最好每次由软件初始化时进行复位操作。