STM32F103移植UCGUI(µCGUI)(附示例工程)
使用环境(蓝色粗体字为特别注意内容)
1、软件环境:Keil MDK 5.15
2、硬件环境:STM32F103C8T6最小系统,ST7735 1.44寸TFT LCD
3、参考文献:http://blog.chinaunix.net/uid-361890-id-2981509.html
上一篇文章里面写了TFT彩色LCD液晶屏的驱动方法,在本文中顺便记录一下移植UC-GUI的过程。我们知道,UCGUI是一种嵌入式应用中的图形支持系统。它设计用于为任何使用LCD图形显示的应用提供高效的独立于处理器及LCD控制器的图形用户接口,它适用单任务或是多任务系统环境, 并适用于任意LCD控制器和CPU下任何尺寸的真实显示或虚拟显示。它的设计架构是模块化的,由不同的模块中的不同层组成,由一个LCD驱动层来包含所有对LCD的具体图形操作。 UCGUI可以在任何的CPU上运行,因为它是100%的标准C代码编写的。UCGUI能够适应大多数的使用黑白或彩色LCD的应用,它提供非常好的允许处理灰度的颜色管理。还提供一个可扩展的2D图形库及占用极少RAM的窗口管理体系。
往往做产品的时候,需要有良好的用户交互界面,而UC-GUI就给我们提供了很大方便。移植起来也非常的简单,整个图形库纯C语言编写,有着良好的可移植性,经过简单的配置,几乎能够运行于任何平台,而且不需要操作系统支持。首先大概介绍uc-GUI的基本工作原理:
说白了就是在液晶屏的驱动中的画点函数套了一层壳,这层壳能够方便的生成窗体、控件等桌面UI元素。所以移植UC-GUI首先要做的就是先把液晶屏驱动起来,能够实现在任意一个点绘制任意颜色。因为点能构成线,线能构成面,所以画点是最基础的,当然后面可以做一下优化,比如画线可以不用一个点一个点的画,这样想想就知道很浪费时间,如何优雅地绘制点、线、面这将在很大程度上影响整体的性能。虽然已经有更新的版本,比如UCGUI3.98、甚至4.04版本。但是目前来说只有UCGUI3.90a版本的代码是最全的,包括了JPEG , MULTILAYER , MEMDEV ,AntiAlias等模块。要做一个数码相册,JEPG模块自然少不了,可以试试移植这个版本,但是鉴于笔者没有找到该版本的资源下载,只找到了3.98版本,凑合着用吧~~~~。好了,废话少说,下面我们来看一下大概的移植过程。
基本流程:先修改(移植),后编译,一定要按照这个顺序,否则你会遇到各种编译报错,因为Config.h中有些开关,比如默认使用ucos、默认支持触屏等需要根据需要修改,不改的话可能会把相关模块编译进去,这时候就可能少东西了!本工程只是作为一个示例,移植了基本ucgui组件,不包含os以及触屏!
Step1.下载源码,认识UC-GUI
E:.
└─uC-GUI
├─Doc
├─Sample
│ ├─Application
│ │ ├─Dashboard
│ │ ├─NEC_BuildingManagement
│ │ └─NEC_Pingpong
│ ├─GUI
│ │ ├─VSCREEN_MultiPage
│ │ └─WIDGET_Checkbox
│ ├─GUIDemo
│ ├─GUI_X
│ ├─LCDConf
│ │ ├─LCD0323
│ │ ├─LCD07X1
│ │ ├─LCD1200
│ │ ├─LCD13701
│ │ ├─LCD1611
│ │ ├─LCD161620
│ │ ├─LCD1781
│ │ ├─LCD501
│ │ ├─LCD6331
│ │ ├─LCD66750
│ │ ├─LCD667XX
│ │ ├─LCDColorOnMono
│ │ ├─LCDFujitsu
│ │ ├─LCDLin
│ │ ├─LCDLin32
│ │ ├─LCDMem
│ │ ├─LCDMemC
│ │ ├─LCDPage1bpp
│ │ ├─LCDPage4bpp
│ │ ├─LCDSLin
│ │ ├─LCDVesa
│ │ └─LCDXylon
│ ├─LCD_X
│ └─MakeLib
│ ├─8051_Keil
│ ├─ARM_GNU
│ ├─ARM_IAR
│ ├─M16C_NC30
│ ├─M16C_TASKING
│ ├─M32C_NC308
│ ├─MC80_IAR
│ ├─MSP430_IAR
│ ├─V850_GHS
│ ├─WIN32_MSVC60
│ ├─WIN32_WATCOM
│ └─X86_WC16
├─Start
│ ├─Application
│ ├─Config
│ ├─GUI
│ │ ├─AntiAlias
│ │ ├─ConvertColor
│ │ ├─ConvertMono
│ │ ├─Core
│ │ ├─Font
│ │ ├─LCDDriver
│ │ ├─MemDev
│ │ ├─MultiLayer
│ │ ├─Widget
│ │ └─WM
│ └─System
│ └─Simulation
│ ├─Res
│ ├─SIM_GUI
│ │ └─Branding
│ └─WinMain
└─Tool
1)“tool文件夹”是用来使用一些uCgui的上位机程序,基本都是字体和模板查看之类的.
2)“sample文件夹”下面是已经别人帮你写好了很多有用的东西,像跟操作系统有关的GUI_X或者一些模板(后面我们会用到的自己定义的Demo),或者是gui配置.后面再一一详细叙说这个文件夹的功能.
3)“Start文件夹”里面,这是我们最主要的文件夹.里面就包含了uCGUI的源代码,uCGUI的作者把源代码放进vc里面进行编译了(当然,这是用标准C语言写的程序,所以我们可以放在任何C语言平台下编译而不会担心兼容性问题,这个uCGUI在这方面做的算是完美了),所以,我们可以在vc平台下写界面,然后再把代码拷进我们的下位机编译器进行编译,这样子效率就会非常高了。
Start文件夹”目录内容:
1)Config,配置文件;
2)GUI/AntiAlias,抗锯齿支持;
3)GUI/ConvertMono,用于B/W(黑白两色)及灰度显示的色彩转换程序;
4)GUI/ConvertColor,用于彩色显示的色彩转换的程序;
5)GUI/Core µC/GUI,内核文件;
6)GUI/Font,字体文件;
7)GUI/LCDDriver,LCD 驱动;
8)GUI/Mendev,存储器件支持;
9)GUI/Touch,触摸屏支持;
10)GUI/Widget,视窗控件库;
11)GUI/WM,视窗管理器;
需要修改三个地方:
1) GUI/Config/GUIConf.h
2) GUI/Config/LCDConf.c
3) GUI/LCDDriver/GUI_X.c(手动添加)
4) GUI/LCDx.c(x为Lin、Lin32、Mem、Null等,这些都是模板文件随便选一个即可,一般选择Win32即LCDWin32.c)
Step2.修改移植
①GUI/Config/GUIConf.h改动(红色)如下:
#ifndef GUICONF_H
#define GUICONF_H
#define GUI_OS (0) /* Compile with multitasking support,多任务支持 */
#define GUI_SUPPORT_TOUCH (0) /* Support a touch screen (req. win-manager) 触摸屏*/
#define GUI_SUPPORT_MOUSE (0) /* Support a mouse 鼠标*/
#define GUI_SUPPORT_UNICODE (1) /* Support mixed ASCII/UNICODE strings unicode字符集*/
#define GUI_DEFAULT_FONT &GUI_Font6x8 // 这里是设定默认字体的,我们可以在要写什么字的时候把该字号的字体.c包含进我们的主函数里面,所以这里不用改
#define GUI_ALLOC_SIZE 5000 /* Size of dynamic memory ... For WM and memory devices 动态内存*/
/*********************************************************************
*
* Configuration of available packages
*/
#define GUI_WINSUPPORT 1 /* Window manager package available */
#define GUI_SUPPORT_MEMDEV 1 /* Memory devices available */
#define GUI_SUPPORT_AA 0 /* Anti aliasing available */
#endif /* Avoid multiple inclusion */
②GUI/Config/LCDConf.h改动(红色)如下:
#define LCD_XSIZE (128) /* X-resolution of LCD, Logical coor. LCD屏幕尺寸*/
#define LCD_YSIZE (128) /* Y-resolution of LCD, Logical coor. LCD屏幕尺寸*/
#define LCD_BITSPERPIXEL (16) // 16Bpp TFT彩屏16位颜色深度
#define LCD_CONTROLLER 1375 /*lcd控制器的具体型号,改不改无所谓*/
#define LCD_FIXEDPALETTE (565) /*RGB颜色位数,若没有,则添加此定义*/
#define LCD_SWAP_RB (1) /*红蓝反色交换,若没有,则添加此定义,若无此定义可能出现GUI_RED显示蓝色,即红蓝颠倒*/
// #define LCD_INIT_CONTROLLER() TFT_Init(); /*底层初始化函数,自己写的,而非源码自带,也可以直接在main函数中调用自己的TFT_Init();*/
②GUI/LCDDriver/GUI_X.c改动(红色)如下:
从Sample/GUI_X中将GUI_X.c复制到LCDDriver/下面,并且添加到keil工程。
关于GUI_X.c的几个说明:
①GUI_X.C :无系统
②GUI_X_embOS.C :embOS系统
③GUI_X_uCOS.C :uCOS系统
根据不同条件添加不同文件,如果是无系统下添加文件①,否则根据系统添加文件,或编写文件。特别说明如下函数:
a.int GUI_X_GetTime(void);
此函数调用系统时间如果你的系统有实时时钟之类的可以将实时时钟驱动程序放在这里面。没有也没关系只是调用系统时间相关的函数不能用而已,可能这个功能很多人也不会用。
b.void GUI_X_Delay(int Period);
此函数用于GUI的延时,如果没有跑系统,就自己手动的写个延时程序,如果跑了操作系统,就调用系统的延时函数。
在uCGUI中有很多地方都调用了以上函数,在其它的GUIDEMO_XXXX.C中也有这样一些循环,你要是调试是发现液晶屏上的显示一直停在一个画面上很久的话,应该就是以上函数的问题,需要根据需要修改。
说明:有的函数可以不用修改,可以用它软件的实现,有的则要重写,有的则可以直接返回0,完全有你个人的硬件板所决定。
④GUI/LCDDriver/LCDWin.c改动(红色)如下(重要):
LCDx.c可以说是最终要的一个文件了,该文件为ucgui的上层文件提供基本的LCD操作函数,可以从LCDWin、LCDLin等文件中随便选一个,这里选择LCDWin.c作为说明。
1. 必须编写好LCD的驱动函数,当然如果uCGUI中已经包含了移植需要的LCD底层驱动,就不用再编写。如果没有就需要编写好LCD的底层驱动。自己写的LCD底层驱动需要包含如下函数:
①LCD_Init (): LCD初始化函数(必须有)。
②LCD_SetPixel(): LCD画点函数(必须有,否则无法画图)。
③LCD_GetPixel(): LCD读点颜色函数(好像没有也能画图,具体影响未知)。
有了基本函数之后,下面就开始改LCDWin.c了,
最前面的第一个define要注释掉,否则该文件不会被编译,改动如下:
//#if defined(WIN32) && !defined(LCD_SIMCONTROLLER)
最后一个endif也要注释掉,
//#endif /* defined(WIN32) && defined(LCD_USE_WINSIM) */
最后一个#else也要注释掉,
//#else
//#include <windows.h>//如果报错找不到windows.h的话注释掉。
添加#include "YourLCDDriver.h"
①添加画点函数:
void LCDSIM_SetPixelIndex(int x, int y, int PixelIndex,int layer) {
/* Write into hardware ... Adapt to your system */
{
LCD_SetPixel():/* ... */ 添加画点函数
}
}
②添加获取像素点颜色函数:
unsigned int LCDSIM_GetPixelIndex(int x, int y,int display_index) {
/* Read from hardware ... Adapt to your system */
{
PixelIndex = 0;
LCD_GetPixel(): /* ... */ 添加获取像素点颜色函数
}
return PixelIndex;
}
③添加初始化函数:
int LCD_L0_Init(void) {
LCD_INIT_CONTROLLER(); // 把这句改成LCD_Init ();
return 0;
}
当然也可以直接在LCDConf.c文件中,把宏改成如下:
#define LCD_INIT_CONTROLLER() TFTInit();
说明:不同的ucgui版本可能有所差异,但是基本修改原理是一样的。有的函数可以不用修改,可以用它软件的实现,有的则要重写,有的则可以直接返回0,完全有你个人的硬件板所决定。
3. 以上方法是利用样本(LCDx.c)函数,当然也可以自己编写连接代码。如果是直接编写,则需要手动编写如下函数:
函数 |
说明 |
初始化及显示控制组 | |
LCD_L0_Init () |
初始化显示屏。 |
LCD_L0_ReInit () |
不擦除内容而重新初始化LCD。 |
LCD_L0_Off () |
关闭 LCD。 |
LCD_L0_On () |
开启 LCD。 |
绘制组 | |
LCD_L0_DrawBitmap () |
通用绘制位图函数。 |
LCD_L0_DrawHLine () |
绘制一条水平线。 |
LCD_L0_DrawPixel () |
以当前前景色绘制一个像素。 |
LCD_L0_DrawVLine () |
绘制一条垂直线。 |
LCD_L0_FillRect () |
直译一个矩形区域。 |
LCD_L0_SetPixelIndex() |
以指定颜色绘制一个像素。 |
LCD_L0_XorPixel () |
反转一个像素。 |
“Get”组 | |
LCD_L0_GetPixelIndex() |
返回指定像素的颜色的索引。 |
“Set”组 | |
LCD_L0_SetOrg () |
不再使用,保留给将来使用(必须在驱动器中存在)。 |
查询表组 | |
LCD_L0_SetLUTEntry () |
修改LUT 的单个条目。 |
Misc.组(可选) | |
LCD_L0_ControlCache () |
锁/解锁/清除 LCD 高速缓存。 |
注意:红色为必须要实现的函数,其他函数可以为空,但必须保留函数体。
把需要的.c文件添加到keil工程,记得分一下组,不然很乱!,然后记得添加一下include路径。最终工程我放到文章末尾,需要的朋友可以下载,工程目录如下:
以下是测试函数:
void MsgDemo()
{
/*
BUTTON_Handle hButton;
GUI_SetFont(&GUI_Font8x16);
GUI_DispStringHCenterAt("Cli",160,0);
hButton = BUTTON_Create(110,20,100,40,GUI_ID_OK,WM_CF_SHOW);
BUTTON_SetBkColor(hButton, 0, GUI_GRAY);
BUTTON_SetText(hButton, "cc");
BUTTON_SetDefaultBkColor(GUI_GRAY, 0);
*/
GUI_MessageBox("This text is shown\nin a message box",
"Caption/Title", GUI_MESSAGEBOX_CF_MOVEABLE);
}
int main(void)
{
DisableJTAG();
LCD_GPIO_Conf();
LED_Init();
LCD_Dev_Init();
GUI_Init();
GUI_SetBkColor(GUI_WHITE); // 背景颜色
GUI_SetColor(GUI_RED); // 画笔颜色
GUI_Clear(); // 清屏
GUI_SetDrawMode(GUI_DRAWMODE_NORMAL);
GUI_SetFont(&GUI_Font32B_ASCII);
GUI_DispString("hello");
GUI_DrawCircle(50, 50, 20); // 画圆形
GUI_FillCircle(0, 80, 10); //填充圆形
GUI_FillRect(20, 80, 30, 90); //填充矩形
GUI_DrawRect(80, 80, 90, 90); //矩形
GUI_DrawHLine(0, 50, 100); //横线
GUI_DrawLine(50, 50, 100, 100 ); //两点之间直线
GUI_CURSOR_Show();
GUI_Clear();
MsgDemo();
while(1)
{
}
}
显示效果如下:
效果完美(屏幕贴了张膜)~~~~~
优化篇
下面说说主要的优化思路,
假设我们要绘制一个100*100的矩阵,那么就要调用10000次Darw_Point(画点函数),10000次Set_LCD_reg函数,10000Set_LCD_gram()函数,而每次调用一个函数的时候,要包括如下过程:
1.把要传递的参数压入堆栈中
2.把上一层程序的返回地址压入堆栈中
3.对程序的运行指针进行偏移操作,指向调用程序的入口地址
4.执行程序时候如果有需要要进行堆栈保护
5.执行程序完毕,堆栈要进行释放,还原给调用它的函数使用
6.弹出返回地址
7.返回,并继续执行上层程序.
而真正的,我们执行程序的时候,嵌套了那么多层的调用,执行的只有一个寄存器赋值,所以系统更多时候是在疯狂的进行压栈和出栈活动,
如果我们把这些时间都去掉,
效率就是呈现几何倍数增长,这种是毋庸置疑的.
出了通过对函数进行拆解,算法和硬件的结合也是非常重要的,像你画个矩形,一个通过点来填充,通过直线来填充,甚至,
为什么你没有想到通过矩阵来填充呢?有时候想法
就是这样,这在很多IC上是可以很轻松的实现的,前提是你得通读datasheet.
大家在进行写函数的时候,对管脚进行配置都是直接调用库函数的setbit或者resetbit来进行的,我们可以直接查询库函数,对寄存器进行操作,把对管脚置高置低进行的操作完全跟改成寄存器操作。
例如:
//将
/*#define SDA_H GPIO_SetBits(GPIO_TFT,SDA)
#define SDA_L GPIO_ResetBits(GPIO_TFT,SDA)
#define SCL_H GPIO_SetBits(GPIO_TFT,SCL)
#define SCL_L GPIO_ResetBits(GPIO_TFT,SCL)
#define CS_H GPIO_SetBits(GPIO_TFT,CS)
#define CS_L GPIO_ResetBits(GPIO_TFT,CS)
#define RESET_H GPIO_SetBits(GPIO_TFT,RESET)
#define RESET_L GPIO_ResetBits(GPIO_TFT,RESET)
#define RS_H GPIO_SetBits(GPIO_TFT,RS)
#define RS_L GPIO_ResetBits(GPIO_TFT,RS)
*/
//换成寄存器操作,提高速度
#define SDA_H GPIO_TFT->BSRR = SDA
#define SDA_L GPIO_TFT->BRR = SDA
#define SCL_H GPIO_TFT->BSRR = SCL
#define SCL_L GPIO_TFT->BRR = SCL
#define CS_H GPIO_TFT->BSRR = CS
#define CS_L GPIO_TFT->BRR = CS
#define RESET_H GPIO_TFT->BSRR = RESET
#define RESET_L GPIO_TFT->BRR = RESET
#define RS_H GPIO_TFT->BSRR = RS
#define RS_L GPIO_TFT->BRR = RS
对线条进行操作的相信大家应该非常了解了,这里详细解释下对窗口进行操作的一些细节:
窗口:也就是可以进行填充的区域,液晶驱动里面,每个物理像素对应的坐标已经是固定的,但是窗口可以不固定,窗口就是可以进行填充的区域,你如果要在窗口外面进行填充,是无法进行的,同样的,当你填充到窗口边缘的时候,会自动跳转到下一行进行填充,只要你设定的点正确,那么整个你设定的窗口区域都会被填充完毕,这段期间你要做的知识单纯的填充数据,不需要进行设定点的操作,也不需要换行,这样子屏幕填充矩阵操作看起来效果就不会有刷屏的感觉了.
填充行:对行进行填充,只需要在换行的时候进行坐标切换,我用整个函数,慢了30万个点每秒把.
在优化的时候,我只是抛砖引玉的给大家介绍下怎么用什么样的思路进行优化,细节性的东西还是要大家好好去琢磨的.
初稿到这边就差不多结束了,后面会陆续补充,只要大家想了解都可互相探讨.
相关问题
1)链接报错,函数重复定义了
..\OBJ\lcd.axf: Error: L6200E: Symbol LCD_Init multiply defined (by lcd_1.o and lcd.o).于是把液晶屏驱动的函数 重命名一下:
2)编译报错
..\OBJ\lcd.axf: Error: L6218E: Undefined symbol GUI_X_ErrorOut (referred from gui_errorout.o).
..\OBJ\lcd.axf: Error: L6218E: Undefined symbol GUI_X_Log (referred from gui_log.o).
..\OBJ\lcd.axf: Error: L6218E: Undefined symbol GUI_TOUCH_X_ActivateX (referred from gui_touch_driveranalog.o).
..\OBJ\lcd.axf: Error: L6218E: Undefined symbol GUI_TOUCH_X_ActivateY (referred from gui_touch_driveranalog.o).
..\OBJ\lcd.axf: Error: L6218E: Undefined symbol GUI_TOUCH_X_MeasureX (referred from gui_touch_driveranalog.o).
..\OBJ\lcd.axf: Error: L6218E: Undefined symbol GUI_TOUCH_X_MeasureY (referred from gui_touch_driveranalog.o).
..\OBJ\lcd.axf: Error: L6218E: Undefined symbol GUI_X_ExecIdle (referred from gui_waitevent.o).
..\OBJ\lcd.axf: Error: L6218E: Undefined symbol GUI_X_Warn (referred from gui_warn.o).
..\OBJ\lcd.axf: Error: L6218E: Undefined symbol GUI_X_Init (referred from guicore.o).
..\OBJ\lcd.axf: Error: L6218E: Undefined symbol GUI_X_GetTaskId (referred from guitask.o).
..\OBJ\lcd.axf: Error: L6218E: Undefined symbol GUI_X_InitOS (referred from guitask.o).
..\OBJ\lcd.axf: Error: L6218E: Undefined symbol GUI_X_Lock (referred from guitask.o).
..\OBJ\lcd.axf: Error: L6218E: Undefined symbol GUI_X_Unlock (referred from guitask.o).
..\OBJ\lcd.axf: Error: L6218E: Undefined symbol GUI_X_Delay (referred from guitime.o).
..\OBJ\lcd.axf: Error: L6218E: Undefined symbol GUI_X_GetTime (referred from guitime.o).
将Sample\GUI_X中的GUI_X_uCOS.c加入工程。
..\LIB\GUI\LCDDriver\GUI_X_uCOS.c(30): error: #5: cannot open source input file "ucos_ii.h": No such file or directory
注释掉。再把GUI_X_uCOS.c中报错的地方全删掉。 如果发现连接的时候出现找不到模块的问题就在GUI_X_uCOS.c加入空函数即可。
此时如果编译报错:
..\OBJ\lcd.axf: Error: L6200E: Symbol LCD_L0_SetLUTEntry multiply defined (by lcdwin.o and lcdlin.o).
..\OBJ\lcd.axf: Error: L6200E: Symbol LCD_L0_SetOrg multiply defined (by lcdwin.o and lcdlin.o).
..\OBJ\lcd.axf: Error: L6200E: Symbol LCD_L0_Init multiply defined (by lcdwin.o and lcdlin.o).
..\OBJ\lcd.axf: Error: L6200E: Symbol LCD_L0_On multiply defined (by lcdwin.o and lcdlin.o).
..\OBJ\lcd.axf: Error: L6200E: Symbol LCD_L0_DrawBitmap multiply defined (by lcdwin.o and lcdlin.o).
..\OBJ\lcd.axf: Error: L6200E: Symbol LCD_L0_DrawHLine multiply defined (by lcdwin.o and lcdlin.o).
..\OBJ\lcd.axf: Error: L6200E: Symbol LCD_L0_DrawVLine multiply defined (by lcdwin.o and lcdlin.o).
..\OBJ\lcd.axf: Error: L6200E: Symbol LCD_L0_FillRect multiply defined (by lcdwin.o and lcdlin.o).
..\OBJ\lcd.axf: Error: L6200E: Symbol LCD_L0_GetDevFunc multiply defined (by lcdwin.o and lcdlin.o).
..\OBJ\lcd.axf: Error: L6200E: Symbol LCD_L0_GetPixelIndex multiply defined (by lcdwin.o and lcdlin.o).
..\OBJ\lcd.axf: Error: L6200E: Symbol LCD_L0_SetPixelIndex multiply defined (by lcdwin.o and lcdlin.o).
..\OBJ\lcd.axf: Error: L6200E: Symbol LCD_L0_XorPixel multiply defined (by lcdwin.o and lcdlin.o).
..\OBJ\lcd.axf: Error: L6200E: Symbol LCD_L0_Off multiply defined (by lcdwin.o and lcdlin.o).
把LCDLin.c移除(因为我们使用的是LCDWin.c)
在使用uC/GUI的时候需要注意的是,uC/GUI默认是使用在uCOS操作系统中,GUI_Delay()函数用来进行延时和界面的刷新。在GUI_Delay()函数中调用了GUI_GetTime( )用来返回操作系统的当前时间,并结合延时时间确定延时是否完成。在单任务的前后台系统中如果调用GUI_Delay(),因为得不到GUI_GetTime( )调用的系统时间,程序就会进入死循环。
红蓝反色,见前文。
其他注意事项见参考文献,我就不搬过来了。。。。。
本文地址:https://blog.csdn.net/pang9998/article/details/85840541