4523582236233
GUI和LCD层这层主要有3个功能 : 「1、设备管理」 首先定义了一堆LCD参数结构体,结构体包含ID,像素。并且把这些结构体组合到一个list数组内。 /* 各种LCD的规格参数*/ _lcd_pra LCD_IIL9341 ={ .id = 0x9341, .width = 240, //LCD 宽度 .height = 320, //LCD 高度 }; ... /*各种LCD列表*/ _lcd_pra *LcdPraList[5]= { &LCD_IIL9341, &LCD_IIL9325, &LCD_R61408, &LCD_Cog12864, &LCD_Oled12864, };
然后定义了所有驱动list数组,数组内容就是驱动,在对应的驱动文件内实现。 /* 所有驱动列表 驱动列表*/ _lcd_drv *LcdDrvList[] = { &TftLcdILI9341Drv, &TftLcdILI9325Drv, &CogLcdST7565Drv, &OledLcdSSD1615rv,
定义了设备树,即是定义了系统有多少个LCD,接在哪个接口,什么驱动IC。如果是一个完整系统,可以做成一个类似LINUX的设备树。 /*设备树定义*/ #define DEV_LCD_C 3//系统存在3个LCD设备 LcdObj LcdObjList[DEV_LCD_C]= { {"oledlcd", LCD_BUS_VSPI, 0X1315}, {"coglcd", LCD_BUS_SPI, 0X7565}, {"tftlcd", LCD_BUS_8080, NULL}, };
「2 、接口封装」 void dev_lcd_setdir(DevLcd *obj, u8 dir, u8 scan_dir) s32 dev_lcd_init(void) DevLcd *dev_lcd_open(char *name) s32 dev_lcd_close(DevLcd *dev) s32 dev_lcd_drawpoint(DevLcd *lcd, u16 x, u16 y, u16 color) s32 dev_lcd_prepare_display(DevLcd *lcd, u16 sx, u16 ex, u16 sy, u16 ey) s32 dev_lcd_display_onoff(DevLcd *lcd, u8 sta) s32 dev_lcd_fill(DevLcd *lcd, u16 sx,u16 ex,u16 sy,u16 ey,u16 *color) s32 dev_lcd_color_fill(DevLcd *lcd, u16 sx,u16 ex,u16 sy,u16 ey,u16 color) s32 dev_lcd_backlight(DevLcd *lcd, u8 sta)
大部分接口都是对驱动IC接口的二次封装。有区别的是初始化和打开接口。初始化,就是根据前面定义的设备树,寻找对应驱动,找到对应设备参数,并完成设备初始化。打开函数,根据传入的设备名称,查找设备,找到后返回设备句柄,后续的操作全部需要这个设备句柄。 「3 、简易GUI层」 目前最重要就是显示字符函数。 s32 dev_lcd_put_string(DevLcd *lcd, FontType font, int x, int y, char *s, unsigned colidx)
其他划线画圆的函数目前只是测试,后续会完善。 驱动IC层驱动IC层分两部分: 「1 、封装LCD接口」 LCD有使用8080总线的,有使用SPI总线的,有使用VSPI总线的。这些总线的函数由单独文件实现。但是,除了这些通信信号外,LCD还会有复位信号,命令数据线信号,背光信号等。我们通过函数封装,将这些信号跟通信接口一起封装为「LCD通信总线」, 也就是buslcd。BUS_8080在dev_ILI9341.c文件中封装。BUS_LCD1和BUS_lcd2在dev_str7565.c 中封装。 「2 驱动实现」 实现_lcd_drv驱动结构体。每个驱动都实现一个,某些驱动可以共用函数。 _lcd_drv CogLcdST7565Drv = { .id = 0X7565,
.init = drv_ST7565_init, .draw_point = drv_ST7565_drawpoint, .color_fill = drv_ST7565_color_fill, .fill = drv_ST7565_fill, .onoff = drv_ST7565_display_onoff, .prepare_display = drv_ST7565_prepare_display, .set_dir = drv_ST7565_scan_dir, .backlight = drv_ST7565_lcd_bl };
接口层8080层比较简单,用的是官方接口。SPI接口提供下面操作函数,可以操作SPI,也可以操作VSPI。 extern s32 mcu_spi_init(void); extern s32 mcu_spi_open(SPI_DEV dev, SPI_MODE mode, u16 pre); extern s32 mcu_spi_close(SPI_DEV dev); extern s32 mcu_spi_transfer(SPI_DEV dev, u8 *snd, u8 *rsv, s32 len); extern s32 mcu_spi_cs(SPI_DEV dev, u8 sta);
至于SPI为什么这样写,会有一个单独文件说明。 总体流程前面说的几个模块时如何联系在一起的呢?请看下面结构体: /* 初始化的时候会根据设备数定义, 并且匹配驱动跟参数,并初始化变量。 打开的时候只是获取了一个指针 */ struct _strDevLcd { s32 gd;//句柄,控制是否可以打开
LcdObj *dev; /* LCD参数,固定,不可变*/ _lcd_pra *pra;
/* LCD驱动 */ _lcd_drv *drv;
/*驱动需要的变量*/ u8 dir; //横屏还是竖屏控制:0,竖屏;1,横屏。 u8 scandir;//扫描方向 u16 width; //LCD 宽度 u16 height; //LCD 高度
void *pri;//私有数据,黑白屏跟OLED屏在初始化的时候会开辟显存 };
每一个设备都会有一个这样的结构体,这个结构体在初始化LCD时初始化。 - 成员dev指向设备树,从这个成员可以知道设备名称,挂在哪个LCD总线,设备ID。
typedef struct { char *name;//设备名字 LcdBusType bus;//挂在那条LCD总线上 u16 id; }LcdObj;
typedef struct { u16 id; u16 width; //LCD 宽度 竖屏 u16 height; //LCD 高度 竖屏 }_lcd_pra;
typedef struct { u16 id;
s32 (*init)(DevLcd *lcd);
s32 (*draw_point)(DevLcd *lcd, u16 x, u16 y, u16 color); s32 (*color_fill)(DevLcd *lcd, u16 sx,u16 ex,u16 sy,u16 ey, u16 color); s32 (*fill)(DevLcd *lcd, u16 sx,u16 ex,u16 sy,u16 ey,u16 *color);
s32 (*prepare_display)(DevLcd *lcd, u16 sx, u16 ex, u16 sy, u16 ey);
s32 (*onoff)(DevLcd *lcd, u8 sta); void (*set_dir)(DevLcd *lcd, u8 scan_dir); void (*backlight)(DevLcd *lcd, u8 sta); }_lcd_drv;
- 成员dir、scandir、 width、 height是驱动要使用的通用变量。因为每个LCD都有一个结构体,一套驱动程序就能控制多个设备而互不干扰。
- 成员pri是一个私有指针,某些驱动可能需要有些比较特殊的变量,就全部用这个指针记录,通常这个指针指向一个结构体,结构体由驱动定义,并且在设备初始化时申请变量空间。目前主要用于COG LCD跟OLED LCD显示缓存。
整个LCD驱动,就通过这个结构体组合在一起。 1、初始化,根据设备树,找到驱动跟参数,然后初始化上面说的结构体。 2、要使用LCD前,调用dev_lcd_open函数。打开成功就返回一个上面的结构体指针。 3、显示字符,接口找到点阵后,通过上面结构体的drv,调用对应的驱动程序。 4、驱动程序根据这个结构体,决定操作哪个LCD总线,并且使用这个结构体的变量。 用法和好处
|