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

原创 嵌入式Lee 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
#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 #include #include #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
int gc9a01a_test(void);
#ifdef __cplusplus }#endif
#endif

gc9a01a_itf.c

#include #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
#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 { /* 取待发送的值, 用户没有提供则发送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
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一定要接,最好是软件控制进行复位。浮空肯定是不行的,固定上拉也不可靠,最好每次由软件初始化时进行复位操作。




评论
  • 遇到部分串口工具不支持1500000波特率,这时候就需要进行修改,本文以触觉智能RK3562开发板修改系统波特率为115200为例,介绍瑞芯微方案主板Linux修改系统串口波特率教程。温馨提示:瑞芯微方案主板/开发板串口波特率只支持115200或1500000。修改Loader打印波特率查看对应芯片的MINIALL.ini确定要修改的bin文件#查看对应芯片的MINIALL.ini cat rkbin/RKBOOT/RK3562MINIALL.ini修改uart baudrate参数修改以下目
    Industio_触觉智能 2024-12-03 11:28 87浏览
  • 光伏逆变器是一种高效的能量转换设备,它能够将光伏太阳能板(PV)产生的不稳定的直流电压转换成与市电频率同步的交流电。这种转换后的电能不仅可以回馈至商用输电网络,还能供独立电网系统使用。光伏逆变器在商业光伏储能电站和家庭独立储能系统等应用领域中得到了广泛的应用。光耦合器,以其高速信号传输、出色的共模抑制比以及单向信号传输和光电隔离的特性,在光伏逆变器中扮演着至关重要的角色。它确保了系统的安全隔离、干扰的有效隔离以及通信信号的精准传输。光耦合器的使用不仅提高了系统的稳定性和安全性,而且由于其低功耗的
    晶台光耦 2024-12-02 10:40 120浏览
  • 概述 说明(三)探讨的是比较器一般带有滞回(Hysteresis)功能,为了解决输入信号转换速率不够的问题。前文还提到,即便使能滞回(Hysteresis)功能,还是无法解决SiPM读出测试系统需要解决的问题。本文在说明(三)的基础上,继续探讨为SiPM读出测试系统寻求合适的模拟脉冲检出方案。前四代SiPM使用的高速比较器指标缺陷 由于前端模拟信号属于典型的指数脉冲,所以下降沿转换速率(Slew Rate)过慢,导致比较器检出出现不必要的问题。尽管比较器可以使能滞回(Hysteresis)模块功
    coyoo 2024-12-03 12:20 111浏览
  • RDDI-DAP错误通常与调试接口相关,特别是在使用CMSIS-DAP协议进行嵌入式系统开发时。以下是一些可能的原因和解决方法: 1. 硬件连接问题:     检查调试器(如ST-Link)与目标板之间的连接是否牢固。     确保所有必要的引脚都已正确连接,没有松动或短路。 2. 电源问题:     确保目标板和调试器都有足够的电源供应。     检查电源电压是否符合目标板的规格要求。 3. 固件问题: &n
    丙丁先生 2024-12-01 17:37 100浏览
  • TOF多区传感器: ND06   ND06是一款微型多区高集成度ToF测距传感器,其支持24个区域(6 x 4)同步测距,测距范围远达5m,具有测距范围广、精度高、测距稳定等特点。适用于投影仪的无感自动对焦和梯形校正、AIoT、手势识别、智能面板和智能灯具等多种场景。                 如果用ND06进行手势识别,只需要经过三个步骤: 第一步&
    esad0 2024-12-04 11:20 52浏览
  • 当前,智能汽车产业迎来重大变局,随着人工智能、5G、大数据等新一代信息技术的迅猛发展,智能网联汽车正呈现强劲发展势头。11月26日,在2024紫光展锐全球合作伙伴大会汽车电子生态论坛上,紫光展锐与上汽海外出行联合发布搭载紫光展锐A7870的上汽海外MG量产车型,并发布A7710系列UWB数字钥匙解决方案平台,可应用于数字钥匙、活体检测、脚踢雷达、自动泊车等多种智能汽车场景。 联合发布量产车型,推动汽车智能化出海紫光展锐与上汽海外出行达成战略合作,联合发布搭载紫光展锐A7870的量产车型
    紫光展锐 2024-12-03 11:38 101浏览
  • 11-29学习笔记11-29学习笔记习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习笔记&记录学习习笔记&记学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&学习学习笔记&记录学习学习笔记&记录学习学习笔记&记
    youyeye 2024-12-02 23:58 73浏览
  • 最近几年,新能源汽车愈发受到消费者的青睐,其销量也是一路走高。据中汽协公布的数据显示,2024年10月,新能源汽车产销分别完成146.3万辆和143万辆,同比分别增长48%和49.6%。而结合各家新能源车企所公布的销量数据来看,比亚迪再度夺得了销冠宝座,其10月新能源汽车销量达到了502657辆,同比增长66.53%。众所周知,比亚迪是新能源汽车领域的重要参与者,其一举一动向来为外界所关注。日前,比亚迪汽车旗下品牌方程豹汽车推出了新车方程豹豹8,该款车型一上市就迅速吸引了消费者的目光,成为SUV
    刘旷 2024-12-02 09:32 119浏览
  • 《高速PCB设计经验规则应用实践》+PCB绘制学习与验证读书首先看目录,我感兴趣的是这一节;作者在书中列举了一条经典规则,然后进行详细分析,通过公式推导图表列举说明了传统的这一规则是受到电容加工特点影响的,在使用了MLCC陶瓷电容后这一条规则已经不再实用了。图书还列举了高速PCB设计需要的专业工具和仿真软件,当然由于篇幅所限,只是介绍了一点点设计步骤;我最感兴趣的部分还是元件布局的经验规则,在这里列举如下:在这里,演示一下,我根据书本知识进行电机驱动的布局:这也算知行合一吧。对于布局书中有一句:
    wuyu2009 2024-11-30 20:30 124浏览
  •         温度传感器的精度受哪些因素影响,要先看所用的温度传感器输出哪种信号,不同信号输出的温度传感器影响精度的因素也不同。        现在常用的温度传感器输出信号有以下几种:电阻信号、电流信号、电压信号、数字信号等。以输出电阻信号的温度传感器为例,还细分为正温度系数温度传感器和负温度系数温度传感器,常用的铂电阻PT100/1000温度传感器就是正温度系数,就是说随着温度的升高,输出的电阻值会增大。对于输出
    锦正茂科技 2024-12-03 11:50 111浏览
  • 作为优秀工程师的你,已身经百战、阅板无数!请先醒醒,新的项目来了,这是一个既要、又要、还要的产品需求,ARM核心板中一个处理器怎么能实现这么丰富的外围接口?踌躇之际,你偶阅此文。于是,“潘多拉”的魔盒打开了!没错,USB资源就是你打开新世界得钥匙,它能做哪些扩展呢?1.1  USB扩网口通用ARM处理器大多带两路网口,如果项目中有多路网路接口的需求,一般会选择在主板外部加交换机/路由器。当然,出于成本考虑,也可以将Switch芯片集成到ARM核心板或底板上,如KSZ9897、
    万象奥科 2024-12-03 10:24 68浏览
  • 戴上XR眼镜去“追龙”是种什么体验?2024年11月30日,由上海自然博物馆(上海科技馆分馆)与三湘印象联合出品、三湘印象旗下观印象艺术发展有限公司(下简称“观印象”)承制的《又见恐龙》XR嘉年华在上海自然博物馆重磅开幕。该体验项目将于12月1日正式对公众开放,持续至2025年3月30日。双向奔赴,恐龙IP撞上元宇宙不久前,上海市经济和信息化委员会等部门联合印发了《上海市超高清视听产业发展行动方案》,特别提到“支持博物馆、主题乐园等场所推动超高清视听技术应用,丰富线下文旅消费体验”。作为上海自然
    电子与消费 2024-11-30 22:03 98浏览
我要评论
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦