基于GC9A01A的1.28寸圆屏驱动与移植LVGL和emWin

文摘   2024-07-09 15:41   湖南  

一. 前言

前面我们分享了驱动ST7789屏并且移植LVGLemWin的过程,我们现在来分享驱动另外一块屏基于GC9A01A1.28寸圆形屏,并移植LVGLemWin

二. 屏介绍

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型有DODI两个引脚分别收发,而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.cgc9a01a.h完全可移植

初始化列表进行初始化序列(可按需修改)

gc9a01a.c

#include "gc9a01a.h"
/** * \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 GC9A01A_CHECK_PARAM 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; }#endif 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 GC9A01A_CHECK_PARAM 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; }#endif 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 GC9A01A_CHECK_PARAM if(dev == (gc9a01a_dev_st*)0) { return -1; }#endif 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 GC9A01A_CHECK_PARAM if(dev == (gc9a01a_dev_st*)0) { return -1; }#endif
/* @todo 添加IO等解除初始化配置 */
if(dev->deinit != 0) { dev->deinit(); } return 0;}

gc9a01a.h

#ifndef GC9A01A_H#define GC9A01A_H
#ifdef __cplusplus extern "C"{#endif
#include <stdint.h>
#define GC9A01A_CHECK_PARAM 1
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); /**< 解除初始化接口 */
#define GC9A01A_CMD_SLPOUT 0x11 /* 退出SLEEP模式 */#define GC9A01A_CMD_INVON 0x21 /* 显示反转 */#define GC9A01A_CMD_DISPON 0x29 /* 打开显示 */#define GC9A01A_CMD_CASET 0x2A /* 设置列地址 */#define GC9A01A_CMD_RASET 0x2B /* 设置行地址 */#define GC9A01A_CMD_RAMWR 0x2C /* 写数据 */#define GC9A01A_CMD_MADCTL 0x36 /* 显存访问控制 */#define GC9A01A_CMD_COLMOD 0x3A /* 点格式 */#define GC9A01A_CMD_IRE1 0xFE /* Inter Register Enable1 */#define GC9A01A_CMD_IRE2 0xEF /* Inter Register Enable2 */#define GC9A01A_CMD_DFC 0xB6 /* Display Function Control */#define GC9A01A_CMD_PC2 0xC3 /* Power Control 2 */#define GC9A01A_CMD_PC3 0xC4 /* Power Control 3 */#define GC9A01A_CMD_PC4 0xC9 /* Power Control 4 */#define GC9A01A_CMD_TELON 0x35 /* Tearing Effect Line ON */#define GC9A01A_CMD_SETGAMMA1 0xF0 /*SET_GAMMA1 */#define GC9A01A_CMD_SETGAMMA2 0xF1 /*SET_GAMMA2 */#define GC9A01A_CMD_SETGAMMA3 0xF2 /*SET_GAMMA3 */#define GC9A01A_CMD_SETGAMMA4 0xF3 /*SET_GAMMA4 */
/** * \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);
#ifdef __cplusplus }#endif
#endif

gc9a01a_test.c


#include <stdio.h>#include <stdlib.h>#include <maths.h>#include "timer.h"#include "os_utils.h"#include "gc9a01a_itf.h"#include "gc9a01a_test.h"
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

#ifndef GC9A01A_TEST_H#define GC9A01A_TEST_H
#ifdef __cplusplus extern "C"{#endif
#include <stdint.h>
int gc9a01a_test(void);
#ifdef __cplusplus }#endif
#endif

gc9a01a_itf.c

#include <stdint.h>#include "resource.h"#include "gpio.h"#include "timer.h"#include "os_utils.h"#include "gc9a01a_itf.h"#include "gc9a01a.h"
#define USE_SPI_PORT SPI_PORT1#if 1#define CS_PIN GPIO_30 #define DC_PIN GPIO_20 #define SCL_PIN GPIO_29 #define SDA_PIN GPIO_39 #define RST_PIN GPIO_38 #else#define CS_PIN GPIO_05 #define DC_PIN GPIO_06 #define SCL_PIN GPIO_04 #define SDA_PIN GPIO_03 #define RST_PIN GPIO_00 #endif/****************************************************************************** * 以下是底层适配 * ******************************************************************************/
#if GC9A01A_ITF_SPI_IO/* 使用IO模拟SPI方式 */ #include "io_spi.h"
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);}
#else /* 使用硬件SPI方式 */ #include "spi.h"
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);} #endif
/****************************************************************************** * 以下是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){#if 0 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++; }#endif 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

#ifndef GC9A01A_ITF_H#define GC9A01A_ITF_H
#ifdef __cplusplus extern "C"{#endif
#include <stdint.h>
#define GC9A01A_ITF_SPI_IO 0 /**< 配置为1使用IO模拟, 配置为0使用硬件SPI */
#define GC9A01A_HSIZE 240#define GC9A01A_VSIZE 240
/** * \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);
#ifdef __cplusplus }#endif
#endif

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

#ifndef IO_SPI_H#define IO_SPI_H
#ifdef __cplusplus extern "C"{#endif
#include <stdint.h>
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);
#ifdef __cplusplus }#endif
#endif

四. 测试

先检查供电,比如IOVCC1.8VVCC2.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

显示效果见视频LVGLemWinDemo

https://mp.weixin.qq.com/s/SjvmB4dsMvdXkj8WGgJRqA 基于GC9A01A1.28存圆屏移植emWin

https://mp.weixin.qq.com/s/gtRr41XfvttNhsRhep2IGg 基于GC9A01A1.28存圆屏移植LVGL

五. 总结

调试屏幕时可以先用IO模拟SPI方式调通,然后再改为SPI模式,调整性能。

RESET一定要接,最好是软件控制进行复位。浮空肯定是不行的,固定上拉也不可靠,最好每次由软件初始化时进行复位操作。




嵌入式Lee
嵌入式软硬件技术:RTOS,GUI,FS,协议栈,ARM,总线,嵌入式C,开发环境 and blablaba....多年经验分享,非硬货不发,带你扒开每一个技术背后的根本原理。
 最新文章