zlggui是一个简单的GUI,前后共花了半天时间来试验,有些心得;个人觉得这个UI用在一些需求一个简单的UI嵌入式系统中还是挺好的,移植方便,容易修改。其实现了基本的画线、画长方形、画圆/椭圆、画弧、曲线、填充、5*7\8*8字\24*32ASCII字符、简单窗体、菜单、按键等功能。在试验过程中,修改了一处BUG及增加了几个用来输出GB2312字库的函数。以下详细说明。
源程序中的各功能部分文件逻辑关系结构:
顶层:font_24_32 Font5_7 Font8_8 Menu Spline Windows Loadbit
中层:Gui_basic GUI_StockC ConvertColor
底层:需要由用户根据硬件编写LCD的驱动,并且要符合中层的函数声明要求
配置层:Gui_CONFIG.h,另外需要用户提供一个全局的配置文件(数据类型等)
在移植过程中,只需设计四个文件即可(文件名自定,文件数量也算定,只要功能实现即可):lcd_driver.h lcd_driver.c config.h。其中lcd_driver.c、lcd_driver.h和 config.h需要由用户提供。除了要符合中层Gui_BASIC.H所要求的函数定义外,在lcd_driver.c/h 中还需要定义两个宏(函数):GUI_CmpColor和GUI_CopyColor,注意这两个宏需要根据LCD的显示类型来定义,单色屏和彩色屏不同。
lcd_driver.h中的一些示例:
//这两个宏根据LCD的像素位数不同而不同,以 下是彩色屏定义
#define GUI_CmpColor(color1, color2) ((color1) == (color2))
#define GUI_CopyColor(color1, color2) ((*color1) = (color2))
//单色屏定义
#define GUI_CmpColor(color1, color2) ( (color1&0x01) == (color2&0x01) )
#define GUI_CopyColor(color1, color2) ((*color1) =( color2&0x01))
//定义一些颜色值
#define red 0x001f
#define blue 0xf800
#define green 0x07e0
#define black 0x0000
=====================================
lcd_driver.c中的一些示例:
在这个驱动文件中,需要定义几个函数:画点、取点、画水平线、画垂直线、画任意两点线四个函数,且要和Gui_basic.h中的函数声明符合。在我的移植中,我在lcd_driver.h中用宏来联系这些函数,这样就不用改变我lcd_driver.c中的函数名,如下:
#define GUI_Point set_pixel
#define GUI_HLine draw_hline
#define GUI_RLine draw_vline
#define GUI_ReadPoint get_pixel
config.h中的一些示例:
typedef unsigned char uint8; /* 无符号8位整型变量 */
typedef signed char int8; /* 有符号8位整型变量 */
typedef unsigned short uint16; /* 无符号16位整型变量 */
typedef signed short int16; /* 有符号16位整型变量 */
typedef unsigned int uint32; /* 无符号32位整型变量 */
typedef signed int int32; /* 有符号32位整型变量 */
typedef unsigned short TCOLOR;
//包含文件,根据工程的环境来包含
#include "GUI_BASIC.H"
#include "font5_7.h"
#include "font24_32.h"
#include "font8_8.h"
#include "font_macro.h"
#include "windows.h"
在移植过程中,发现zlggui的一个BUG。在font_24_32.c中,GUI_PutChar24_32函数设计有误,导致不能正确输出字体数据。按照我的理解逻辑,修改后如下:
uint8 GUI_PutChar24_32(uint32 x, uint32 y, uint8 ch)
{ uint8 font_dat;
uint8 i, j, k;
TCOLOR bakc;
/* 参数过滤 */
if( x>(GUI_LCM_XMAX-32) ) return(0);
if( y>(GUI_LCM_YMAX-32) ) return(0);
for(i=0; i<14; i++)
{ if(FONT24x32_TAB[i]==ch) break;
}
ch = i;
for(i=0; i<32; i++) // 显示共32行
{
for(j=0; j<3; j++) // 每行共3字节
{
font_dat = FONT24x32[ch][i*3+j];
/* 设置相应的点为color或为back_color */
for(k=0; k<8; k++)
{
if( (font_dat&DCB2HEX_TAB[k])==0 )
GUI_CopyColor(&bakc, back_color);
else
GUI_CopyColor(&bakc, disp_color);
GUI_Point(x, y, bakc);
x++;
}
}
y++;
// 指向下一行
x -= 24;
// 恢复x值
}
return(1);
}
==========================================================
原函数:
uint8 GUI_PutChar24_32(uint32 x, uint32 y, uint8 ch)
{ uint8 font_dat;
uint8 i, j;
TCOLOR bakc;
/* 参数过滤 */
if( x>(GUI_LCM_XMAX-32) ) return(0);
if( y>(GUI_LCM_YMAX-32) ) return(0);
for(i=0; i<14; i++)
{ if(FONT24x32_TAB[i]==ch) break;
}
ch = i;
for(i=0; i<32; i++) // 显示共32行
{
for(j=0; j<24; j++) // 每行共24点
{ /* 若当前点为0、8、16点,读取点阵数据 */
if( (j&0x07)==0 ) font_dat = FONT24x32[ch][i*3+j>>3];
/*设置相应的点为color或为back_color */
f( (font_dat&DCB2HEX_TAB[j])==0 ) GUI_CopyColor(&bakc, back_color);
else GUI_CopyColor(&bakc, disp_color);
GUI_Point(x, y, bakc);
x++;
}
y++;
// 指向下一行
x -= 24;
// 恢复x值
}
return(1);
}
===============================================
//另外,我设计一个可以输出24*32字符串的函数,如下:
uint8 GUI_PutString24_32(uint32 x, uint32 y, uint8 *str)
{
while(1)
{ if( (*str)=='\0' ) break;
if( GUI_PutChar24_32(x, y, *str++)==0 ) break;
x += 24;
// 下一个字符显示位置,y不变(即不换行)
}
return 1;
}
===============================================================================================================
===============================================================================================================
显示GB2312汉字。字库的生成由minigui提供的工具来生成,我生成的是16*16的字库。在实际应用中,显示的字体可能是12*12、16*16、24*32等。因此,需要设计一个通用的字体数据输出函数,能够自动识别字体的参数如高度、宽度等。在loadbit.c中有一个函数_GUI_PutHZ可以用来显示汉字,利用这个函数我设计了几个函数来输出不同宽高的GB2312字库。说明如下:
//这个函数可以显示任意高宽的字体数据。其中hz_addr=(uint8 *)HZ_FONT_ADDR是字
库的起始地址。hz_addr=(uint8 *)HZ_FONT_ADDR+128*hz_bytes1是汉字的起始地址。
重要的:tmp=(*chr)*hz_bytes1和tmp=((*(chr)-0xa0-1)*94+(*(chr+1)-0xa0-1))*hz_bytes2
,这个是根据GB2312的编码规则和生成字库的参数来计算汉字的点阵数据起始偏移量,
其中hz_bytes1是表示西文字体的一个字共需要的字节数;hz_bytes2是表示中文字体的
一个字共需要的字节数。94表示字库的区号。可以参考GPB2312规则来理解。
void GUI_PutHZ(uint32 x, uint32 y, uint8 *chr)
{
uint32 tmp;
if(!font_lib_valid_flag) //字库是否有效
return;
if(*chr<128)
{
hz_addr=(uint8 *)HZ_FONT_ADDR;
tmp=(*chr)*hz_bytes1; //西文字偏移量
_GUI_PutHZ(x, y, hz_addr+tmp, hz_width1, hz_height);//输出西文字
}
else
{
hz_addr=(uint8 *)HZ_FONT_ADDR+128*hz_bytes1;
tmp=((*(chr)-0xa0-1)*94+(*(chr+1)-0xa0-1))*hz_bytes2; //中文字偏移量
_GUI_PutHZ(x, y, hz_addr+tmp, hz_width2 ,hz_height); //输出中文字
}
}
//这个函数用来显示水平输出汉字字符串,可混合输出汉字和ASCIi字符,能自动识别字体的宽度
//调用示例: GUI_PutHZStringH(0,0,"山光物态弄春晖,莫为轻阴便拟归");
// GUI_PutHZStringH(0,100,"abcdef一二三四");
void GUI_PutHZStringH(uint32 x, uint32 y, uint8 *str)
{
if(!font_lib_valid_flag) //字库是否有效
return;
while(1)
{ if( (*str)=='\0' ) break;
if(*str > 127)
{
GUI_PutHZ(x, y, str);
str+=2; //每个中文字由两个字节数据组成
x+=hz_width2; //中文字体
}
else
{
GUI_PutHZ(x, y, str);
str+=1;
x+=hz_width1; //西文字体
}
}
}
//这个函数用来显示垂直输出汉字字符串,可混合输出汉字和ASCII字符,能自动识别字体的宽度
//调用示例: GUI_PutHZStringV(0,0,"山光物态弄春晖,莫为轻阴便拟归");
// GUI_PutHZStringV(0,100,"abcdef一二三四");
void GUI_PutHZStringV(uint32 x, uint32 y, uint8 *str)
{
if(!font_lib_valid_flag) //字库是否有效
return;
while(1)
{ if( (*str)=='\0' ) break;
if(*str > 127)
{
GUI_PutHZ(x, y, str);
str+=2;
y+=hz_height; //中文字体
}
else
{
GUI_PutHZ(x, y, str);
str+=1;
y+=hz_height; //西文字体
}
}
}
//字库参数读取及处理:字体统一高度、西文字体宽度、中文字体宽度、编码方式、校验字节数据
//在输出GB2312字库数据之前,必须先调用这个函数,仅需调用一次即可,用来读取字库的参数如:字体统一高度、宽度、编码方式(输出函数只支持GB2312)、校验等。
void GUI_HZParameterInit(void)
{
uint8 tmp;
hz_addr=(uint8 *)HZ_FONT_ADDR;
hz_height=*hz_addr; //字体统一高度
hz_width1=*(hz_addr+1); //西文字体宽度
hz_width2=*(hz_addr+2); //中文字体宽度
hz_code_mode=*(hz_addr+3); //字体编码方式
tmp=~(hz_height+hz_width1+hz_width2+hz_code_mode);//计算校验
if(*(hz_addr+4)==tmp) //比较校验是否有效
{
font_lib_valid_flag=0xff;
hz_bytes1=((hz_width1%8)? hz_width1/8+1:hz_width1/8)
*hz_height; //计算西文字体字节数
hz_bytes2=((hz_width2%8)? hz_width2/8+1:hz_width2/8)
*hz_height;//计算中文字体字节数
}
else
font_lib_valid_flag=0x00; //无效的字库
}
//一些用到的变量
#define HZ_FONT_ADDR 0x26000 //字库存放的起始地址,根据实际情况处理
uint8 *hz_addr; //访问字库数据地址指针
uint8 hz_width1,hz_width2; //西文字宽度,中文字宽度
uint8 hz_height; /字体统一高度
uint8 hz_code_mode; //编码方式
uint8 hz_bytes1,hz_bytes2; //西文字占用字节数,中文字字节数
uint8 font_lib_valid_flag; //字库有效性
//调用到的一些其他函数
/****************************************************************************
* 名称:GUI_LoadLine()
* 功能:输出单色图形的一行数据。
* 入口参数: x 指定显示位置,x坐标
* y 指定显示位置,y坐标
* dat 要输出显示的数据。
* no 要显示此行的点个数
* 出口参数:返回值为1时表示操作成功,为0时表示操作失败。
* 说明:操作失败原因是指定地址超出有效范围。
****************************************************************************/
uint8 GUI_LoadLine(uint32 x, uint32 y, uint8 *dat, uint32 no)
{ uint8 bit_dat;
uint8 i;
TCOLOR bakc;
/* 参数过滤 */
if(x>=GUI_LCM_XMAX) return(0);
if(y>=GUI_LCM_YMAX) return(0);
for(i=0; i
if( (i%8)==0 ) bit_dat = *dat++;
/* 设置相应的点为color或为back_color */
if( (bit_dat&DCB2HEX_TAB[i&0x07])==0 ) GUI_CopyColor(&bakc, back_color);
else GUI_CopyColor(&bakc, disp_color);
GUI_Point(x, y, bakc);
if( (++x)>=GUI_LCM_XMAX ) return(0);
}
return(1);
}
/****************************************************************************
* 名称:_GUI_PutHZ()
* 功能:显示汉字。
* 入口参数: x 指定显示位置,x坐标
* y 指定显示位置,y坐标
* dat 要输出显示的汉字点阵数据。
* hno 要显示此行的点个数
* lno 要显示此列的点个数
* 出口参数:无
* 说明:操作失败原因是指定地址超出有效范围。
****************************************************************************/
//这个函数我改了名字,_GUI_PutHZ用来输出字体点阵数据,GUI_PutHZ用来输出一个
汉字
void _GUI_PutHZ(uint32 x, uint32 y, uint8 *dat, uint8 hno, uint8 lno)
{ uint8 i;
for(i=0; i
// 输出一行数据
y++;
// 显示下一行
dat += (hno>>3);
// 计算下一行的数据
if( (hno&0x07)!=0 ) dat++;
}
}
====================================
关于GB2312字库的存放。16*16的GB2312字库共需要258KB的存储空间。可以根据硬
件环境把它存放在SD卡、外部Flash或者内部的Flash中。我使用的是LPC1768,共有
512KB内部Flash空间,因此就把它存放在了内部的Flash中的后半部分空间。如何把它烧
录进去呢?有几种方法:一是先把字库生成BIN文件,然后用烧录器烧录到指定空间;二
是在主程序中设计一个烧录字库的程序,使用IAP来烧录到指定空间 ;三是先把字库文件
和主程序一起编译,并把字库定义到指定空间,烧录一次,然后把字库文件从主程序剔除
,那么以后的的程序编译和烧录就只编译和烧录主程序了(这样可以加快编译和烧录速度)
,要注意的是主程序的需求空间不能覆盖到字体空间。因为是学习验证,我使用了最后一
种方法,方法的关键是把字库编译到指定空间:const unsigned char FONT[263744]
__attribute__((at(0x26000))),这是我使用的字库空间起始地址。烧录之后,字库的起始
地址就是0x26000了。字库数据一般也不会存放于内部的Flash空间,这是一种极度的浪
费(除非空间绰绰有余)。在实际产品中,字库一般存放于SD卡或者外部Flash中。
================================================================
关于GB2312的编码规则,在网上摘录一些重要信息如下:
GB 2312中对所收汉字进行"分区"处理,每区含有94个汉字/符号。这种表示方式也称为
区位码。
01-09区为特殊符号。
16-55区为一级汉字,按拼音排序。
56-87区为二级汉字,按部首/笔画排序。
10-15区及88-94区则未有编码。
举例来说,"啊"字是GB2312之中的第一个汉字,它的区位码就是1601。
每个汉字及符号以两个字节来表示。
第一个字节称为"高位字节"(也称区字节),第二个字节称为"低位字节"(也称位字节)。
"高位字节"使用了0xA1-0xF7(把01-87区的区号加上0xA0),"低位字节"使用了0xA1-0xFE(
把01-94加上 0xA0)。
由于一级汉字从16区起始,汉字区的"高位字节"的范围是0xB0-0xF7,"低位字节"的范围
是0xA1-0xFE,占用的码位是 72*94=6768。其中有5个空位是D7FA-D7FE。
例如"啊"字在大多数程序中,会以两个字节,0xB0(第一个字节) 0xA1(第二个字节)
储存。区位码=区字节+位字节(与区位码对比:0xB0=0xA0+16,0xA1=0xA0+1)。
==================================================================
================================================================end