象棋小子 1048272975
gui(圖形使用者界面)極大地友善了非專業使用者的使用,使用者無需記憶大量的指令,取而代之的是可以通過視窗、菜單、按鍵等方式進行操作。在某些場合,設計一款人機界面豐富友好的嵌入式産品能赢得更多的使用者。筆者此處就s3c2416基于ucgui圖形使用者界面的使用作一個簡單的介紹。
ucgui 3.98源碼,這個版本的ucgui是開放源碼的最高版本,之後版本隻提供庫檔案,不再開源。筆者以ucgui 3.98這個版本移植作為講解,請讀者自行下載下傳ucgui3.98的版本,其它版本檔案命名有些不一緻。關于ucgui概述、使用、移植等詳細内容,可以直接閱讀ucgui使用者手冊。
s3c2416啟動代碼工程,啟動代碼是s3c2416/50/51這系列arm9晶片在運作使用者c代碼main函數之前必須先運作的代碼,啟動代碼支援sd、nand啟動,為使用者設定系統時鐘,初始化記憶體,自動識别啟動裝置并搬移代碼到ram,mmu映射,中斷管理等,使用者隻需專注于用c開發其它功能函數即可。關于啟動代碼以及啟動代碼的實作過程,筆者前面章節有非常詳細的介紹。此處以gcc下移植ucgui為講解,下載下傳”gcc啟動代碼工程應用執行個體”中的啟動代碼源碼即可。如果在mdk下開發,下載下傳”mdk啟動代碼工程應用執行個體”中的啟動代碼源碼,mdk下開發設定均比較簡單,不再細分mdk下ucgui的移植。
使用者代碼,用c開發的所有功能代碼,其中,使用者代碼入口為main()函數,在這裡實作lcd屏、觸摸屏的子產品驅動以支援ucgui顯示以及觸控。
在linux作業系統下任一路徑下建立一個ucgui的工程目錄,下載下傳ucgui 3.98源碼并解壓,把start目錄下的gui、config這兩個目錄拷貝到ucgui目錄中,gui目錄下為ucgui源碼實作,config目錄下包括gui、lcd、觸摸屏的配置檔案。再把sample目錄下guidemo這個目錄拷貝到ucgui目錄中,guidemo為micrium公司編寫的測試代碼,用以告訴使用者ucgui可以怎樣應用。拷貝sample->gui_x下gui_x.c和gui_x_touch.c這兩個代碼檔案到ucgui目錄下的gui_x目錄中,gui_x.c為gui與系統相關的擴充部分,如實作延時,不涉及到作業系統。gui_x_touch.c為gui支援觸屏實作的接口部分。
把啟用代碼目錄start_code拷貝到ucgui目錄下,這部分代碼無需任何的修改。并保留其中的makefile這些檔案。gcc啟動代碼下的工程管理makefile提取自uboot,可以友善地增加源代碼以及代碼目錄。
在ucgui目錄下建立apps目錄,用來儲存應用相關的源碼。
最終的ucgui目錄内容如下:
圖2-1 linux作業系統下ucgui目錄内容
ucgui要用到lcd以及觸摸屏,需要根據我們實際的屏以及觸摸屏進行配置以及接口調用。
進入config目錄,打開guiconf.h對gui進行總體的配置,由于記憶體充足,可以設定較大的動态記憶體以及支援記憶體裝置,此處并不支援作業系統以及滑鼠。修改後的内容如下:
#ifndef guiconf_h
#define guiconf_h
#define gui_os (0) /* 不支援多任務 */
#definegui_support_touch (1) /* support a touch screen (req. win-manager)*/
#definegui_support_mouse (0) /* 不支援滑鼠 */
#definegui_support_unicode (1) /* support mixed ascii/unicode strings */
#definegui_default_font &gui_font6x8
#definegui_alloc_size (1024*1024) /* 動态記憶體1m*/
/*********************************************************************
*
* configuration of available packages
*/
#definegui_winsupport 1 /* window manager package available */
#definegui_support_memdev 1 /* memory devices available */
#definegui_support_aa 1 /* anti aliasing available */
#endif /* avoidmultiple inclusion */
打開lcdconf.h對lcd進行配置,筆者使用的是16位(r:5-g:6-b:5)色深800*480的rgb屏,清空lcdconf.h中的所有内容,因為這是其它lcd屏的配置,與所用屏完全不一緻,修改後的内容如下:
#ifndef lcdconf_h
#define lcdconf_h
* generalconfiguration of lcd
**********************************************************************
#define lcd_xsize (800) /* 屏x水準像素點 */
#define lcd_ysize (480) /* 屏y水準像素點 */
#define lcd_bitsperpixel (16) /* 16位色深*/
#define lcd_controller (-1) /* 宏開關,使用lcddriver下的模闆 */
#define lcd_fixedpalette (565) /* r:5-g:6-b:5 */
#define lcd_swap_rb (1) /*rb顔色調換 */
#define lcd_swap_xy (0) /* 屏x,y方向不調換 */
#define lcd_init_controller() lcd_rgb_init() /* 屏驅動初始化接口 */
#endif /* lcdconf_h */
打開guitouchconf.h對觸摸屏進行配置,筆者使用的是電容屏,驅動ic已處理好傳回的觸摸坐标值與屏像素坐标一一對應,也可以在移植後進行校準。
#ifndefguitouch_conf_h
#defineguitouch_conf_h
#definegui_touch_ad_left 0 /* 觸摸屏能傳回最左邊的值 */
#definegui_touch_ad_right 800 /* 觸摸屏能傳回最右邊的值 */
#definegui_touch_ad_top 0 /* 觸摸屏能傳回最上面的值 */
#definegui_touch_ad_bottom 480 /* 觸摸屏能傳回最下面的值 */
#definegui_touch_swap_xy 0 /* 觸摸屏x,y方向不調換 */
#definegui_touch_mirror_x 0 /* 觸摸屏x方向不鏡像調換*/
#definegui_touch_mirror_y 0 /* 觸摸屏y方向不鏡像調換*/
#endif /* guitouch_conf_h */
進入gui->lcddriver目錄,需修改ucgui關于實際lcd的底層接口調用。由于我們在lcdconf.h裡配置lcd_controller為-1,這個宏開關會選擇lcdtemplate.c這個模闆檔案進行編譯,其它的接口檔案不會被編譯。lcdtemplate.c裡面已經有相關的模闆代碼,隻需加入lcd_l0_setpixelindex()和lcd_l0_getpixelindex()的實作即可,lcd_l0_setpixelindex設定lcd某一坐标的像素值,lcd_l0_getpixelindex從lcd某一坐标讀出像素值,分别對應rgb屏驅動子產品底層函數lcd_setpixel()和lcd_getpixel()。加入這兩個子產品中的底層函數即可。
lcd_l0_setpixelindex()修改後代碼如下:
void lcd_l0_setpixelindex(int x, int y, int pixelindex) {
gui_use_para(x);
gui_use_para(y);
gui_use_para(pixelindex);
/* convert logical into physicalcoordinates (dep. on lcdconf.h) */
#if lcd_swap_xy | lcd_mirror_x|lcd_mirror_y
int xphys = log2phys_x(x, y);
int yphys = log2phys_y(x, y);
#else
#define xphys x
#define yphys y
#endif
/* write into hardware ... adapt toyour system */
{
lcd_setpixel(x,y, (unsigned short)pixelindex);
}
}
lcd_l0_getpixelindex()修改後的代碼如下:
unsigned int lcd_l0_getpixelindex(int x, int y) {
lcd_pixelindex pixelindex;
/* read from hardware ... adapt toyour system */
pixelindex =(lcd_pixelindex)(lcd_getpixel(x, y));
return pixelindex;
lcdtemplate.c是ucgui最底層的接口實作,将直接通路lcd,是以這些接口函數往往需要根據lcd屏的特點重新改寫,以達到最好的通路速度。例如lcd_l0_drawvline畫堅線函數、lcd_l0_fillrect矩形填充函數等,模闆的實作是調用lcd_l0_setpixelindex一個點一個點地寫屏,這對于i80接口的lcd是緻命的,因為每個點的通路都是需要先寫指令、位址,最後才是資料,屏通路速度會非常慢,是以應改寫為連續寫方式,即寫入連續寫指令後再連續送出資料。為進一步提高ucgui通路lcd的性能,通過減小函數嵌套調用的層次,減小不必要的底層代碼,甚至彙編實作等都可以嘗試。由于筆者采用的是rgb屏,屏顯存在主系統記憶體區,對屏的通路實際是對顯存的通路,ucgui其它接口函數采用模闆預設的實作函數也不會造成性能的明顯變差,其它接口函數不再改寫優化。
最後在lcdtemplate.c中加入lcd驅動接口通路的子產品頭檔案#include “lcd_rgb.h”即可。
gui_x目錄下儲存了ucgui擴充部分,gui_x.c無需作業系統的支援,隻需要系統時間os_timems一個變化的時間計數即可,ucgui的延時函數如gui_delay()等都是以這個計時為标準的,可在使用者代碼中實作定時器時間計數。gui啟動前,除了lcd可能還有其它需初始化的硬體裝置,例如ucgui要用到lcd以及觸摸屏,那麼在gui使用前就應先初始化這些裝置,在gui_x_init()函數中實作如下:
void gui_x_init(void)
{
#if gui_support_touch
tp_init();// 使用ucgui時先初始化觸摸屏
#endif
同時在此檔案中加入tp接口子產品通路的頭檔案#include"tp_ft5206.h"
gui_x_touch.c為ucgui觸摸屏通路接口,隻要實作gui_touch_x_measurex()和gui_touch_x_measurey()即可。gui_touch_x_measurex要求如果有觸按,應傳回觸摸屏x位置,否則傳回無效的位置值(如-1)。gui_touch_x_measurey要求如果有觸按,應傳回觸摸屏y位置,否則傳回無效的位置值(如-1)。這兩個函數分别與電容屏子產品獲得第一個觸摸點x,y位置的底層函數tp_getpoint1_x()與tp_getpoint1_y()對應。由于ucgui不支援多點觸摸,是以在電容屏驅動子產品中實作任何時候隻傳回第一個觸摸的點即可。加入電容屏子產品通路頭檔案#include
“tp_ft5206.h”即可通路電容屏子產品接口。gui_x_touch.c修改後的内容如下:
#include "gui.h"
#include "gui_x.h"
#include "tp_ft5206.h"
void gui_touch_x_activatex(void) {
void gui_touch_x_activatey(void) {
int gui_touch_x_measurex(void) {
return tp_getpoint1_x();
int gui_touch_x_measurey(void) {
return tp_getpoint1_y();
lcd驅動子產品需提供設定某一坐标像素顔色接口lcd_setpixel()和從某一坐标讀出像素顔色接口lcd_getpixel()。筆者用的rgb屏驅動子產品實作在前面章節有詳細的介紹,此處不再詳述。lcd_rgb.c子產品實作如下:
#include"s3c2416.h"
#include"lcd_rgb.h"
#define vbpd 15
#define vfpd 5
#define vspw 5
#define hbpd 25
#define hfpd 88
#define hspw 20
// 幀緩存資料通過lcd dma傳輸到接口,為保證資料的一緻性,cpu每次寫顯存應直接通路主存,
// 即通路顯存時應關cache或無效cache中的資料,配置設定no_cache段緩存并在mmu中關閉
// 對應區域記憶體的cache,以作dma傳輸用,如lcd顯存,iis 音頻dma傳輸等,聲明屬性section("no_cache")
static unsigned shortframebuffer[hsize*vsize] __attribute__((section("no_cache"),zero_init));
unsigned short*getframebuffer()
return framebuffer;
void lcd_enable(intenable)
if (enable) {
rvidcon0 |= (0x03 << 0);
} else {
rvidcon0 &= ~(0x03 << 0);
}
voidlcd_backlight(int on)
rgpbcon &= ~(0x3 << 0);
rgpbcon |= (0x1 << 0);
if (on) {
rgpbdat |= (0x1 << 0);
rgpbdat &= ~(0x1 << 0);
void lcd_rgb_init()
{
rgpccon = 0xaaaa02aa; // gpc配置為rgb資料[7:0]、控制功能
rgpdcon = 0xaaaaaaaa; // gpd配置為rgb[23:8]
lcd_enable(0);
// 16bpp (r5-g6-b5),第一像素在記憶體低位址,選擇buffer0
rwincon0 = (5<<2) | (1<<16) |(0<<23);
rwincon1 = (5<<2) | (1<<16) |(1<<6);
// 選擇hclk=100m,3分頻得到vclk=33.3m,rgb并口格式(rgb),暫不啟動控制邏輯
rvidcon0 = (0<<22) |(0<<13) | (0<<12) | (2<<6) |
(1<<5) | (1<<4) | (0<<2) |(0<<0);
// vclk下降沿鎖存資料,行場同步信号低激活,資料使能高有效
rvidcon1 = (0<<7) |(1<<6) | (1<<5) | (0<<4);
// 設定時序控制參數
rvidtcon0 =((vbpd-1)<<16) | ((vfpd-1)<<8) | ((vspw-1)<<0);
rvidtcon1 = ((hbpd-1)<<16)| ((hfpd-1)<<8) | ((hspw-1)<<0);
// 設定螢幕像素尺寸
rvidtcon2 =((vsize-1)<<11) | ((hsize-1)<<0);
// 設定osd圖像與螢幕尺寸一緻
rvidosd0a = (0<<11) |(0<<0);
rvidosd0b =((hsize-1)<<11) | ((vsize-1)<<0);
rvidosd1a =(0<<11) | (0<0);
rvidosd1b = ((hsize-1)<<11) |((vsize-1)<<0);
// alpha混合方式,基色比對時全透明,未比對部分完全不透明
rvidosd1c = 0xfff000;
// 設定幀緩存的位址
rvidw00add0b0 = (unsigned int)framebuffer;
rvidw00add1b0 = ((unsigned int)(framebuffer+ hsize*vsize) & 0xffffff);
// 不使用虛拟螢幕
rvidw00add2b0 = (00<<13) | ((hsize*2)<<0);
// 視窗0使用
rwincon0 |= (1 << 0);
lcd_enable(1);
lcd_backlight(1);
voidlcd_clearscreen(unsigned short backcolor)
unsigned int i;
for (i=0; i<hsize*vsize; i++) {
framebuffer[i] = backcolor;
}
voidlcd_setpixel(unsigned int x, unsigned int y, unsigned short color)
if ((x >= hsize) || (y >= vsize)) {
return;
framebuffer[y*hsize+x] = color;
unsignedshort lcd_getpixel(unsigned int x, unsigned int y)
return 0;
return framebuffer[y*hsize+x];
lcd_rgb.h子產品頭檔案如下:
#ifndef __lcd_rgb_h__
#define __lcd_rgb_h__
#ifdef__cplusplus
extern"c" {
#define hsize 800// 水準行像素點
#define vsize 480 // 垂直像素點
extern voidlcd_backlight(int on);
extern voidlcd_enable(int enable);
extern voidlcd_rgb_init(void);
extern voidlcd_clearscreen(unsigned short backcolor);
externunsigned short *getframebuffer(void);
externunsigned short lcd_getpixel(unsigned intx, unsigned int y);
extern voidlcd_setpixel(unsigned int x, unsigned int y, unsigned short color);
觸摸屏驅動子產品在有觸摸時,應傳回觸摸點的x位置以及觸摸點的y位置給ucgui,如果沒有觸摸,可直接傳回-1。這可通過擷取第一個觸摸點x位置函數tp_getpoint1_x()和y位置tp_getpoint1_y()實作。電容屏的驅動筆者在前面章節有詳細的介紹,此處不再詳述。由于ucgui隻支援單點觸摸,電容屏中斷輸出配置成查詢方式,通過查詢中斷線的電平狀态來确定有無觸摸事件。同時,電容屏基本都是iic接口的,需要iic驅動子產品的支援,iic驅動實作在前面章節有詳細的介紹,此處不再詳述。
電容屏tp_ft5206.c子產品實作如下:
#include"iic.h"
#include"tp_ft5206.h"
static voiddelay_ms(unsigned int ncount)
//延時1ms,共延時ncount(r0) ms
__asm__ __volatile__ (
"0:\n\t"
"ldr r1, =100000\n\t" // arm clock為400m
"1:\n\t"
"subs r1, r1, #1\n\t" // 一個arm clock
"bne 1b\n\t" // 跳轉會清流水線,3個armclock
"subs %0, %0, #1\n\t" // 調用者確定ncount不為0
"bne 0b\n\t"
: : "r"(ncount) :"r1"
);
voidtp_reset()
rgpfdat &= ~ tp_rst; //位低複位線gpf5
delay_ms(20);
rgpfdat |= tp_rst;
delay_ms(200);
int tp_getpoint1_x()
unsigned char point1_x[2];
if (!(rgpgdat &tp_int)) { // 觸屏按下時,int拉低
// 調用iic讀,對電容屏内部某一位址進行連續讀,獲得12位x軸坐标
iic_readbytes(tp_slaveaddr, touch1_xh, point1_x, 2);
if (!(rgpgdat &tp_int)) {
//獲得ad時應保持按下,不然ad值可能不準确,應丢棄
return((((int)(point1_x[0]&0xf)) << 8) | point1_x[1]);
}
return -1; //傳回無效值,表未按下或釋放了
int tp_getpoint1_y()
unsigned char point1_y[2];
// 調用iic讀,對電容屏内部某一位址進行連續讀,獲得12位y軸坐标
iic_readbytes(tp_slaveaddr, touch1_yh, point1_y, 2);
return((((int)(point1_y[0]&0xf)) << 8) | point1_y[1]);
int tp_writebytes(unsigned char writeaddr,unsigned char *pdata,int length)
int ret;
// 調用iic接口寫,對電容屏内部某一位址進行連續寫
ret =iic_writebytes(tp_slaveaddr, writeaddr, pdata, length);
return ret;
int tp_readbytes(unsignedchar readaddr, unsigned char *pdata, int length)
// 調用iic接口讀,對電容屏内部某一位址進行連續讀
ret =iic_readbytes(tp_slaveaddr, readaddr, pdata, length);
voidtp_init()
unsigned char interruptmode;
rgpgudp &= ~(0x3 << 8);
rgpgudp |= (0x2 << 8); // tp_int gpg4上拉
rgpgcon &= ~(0x3 << 8);
rgpgcon |= (0x0 << 8); // gpg4配置成io口輸入,不使用中斷方式
rgpfudp &= ~(0x3 << 10);
rgpfudp |= (0x2 << 10); // tp_rst gpf5上拉
rgpfcon &=~(0x3 << 10);
rgpfcon |= (0x1 << 10);// gpf5配置成輸出
tp_reset();
// 按住tp時,tp不應多點上報(ucgui會認為連續多點按下),應電平觸發
// tp設定成電平觸發模式,此時不能多點識别,ucgui也隻能單點
interruptmode = 0;
// 電容屏設定成電平觸發,一次按下産生一個中斷信号
tp_writebytes(id_g_mode,&interruptmode, 1);
tp_ft5206.h子產品頭檔案實作如下:
#ifndef __tp_ft5206_h__
#define __tp_ft5206_h__
#ifdef __cplusplus
// tp iic的7位從機位址
#define tp_slaveaddr 0x38
#define tp_int (1<<4) // gpg4
#define tp_rst (1<<5) // gpf5
#define touch1_xh 0x03 // 觸摸點1坐标x高4位寄存器位址
#define touch1_xl 0x04 // 觸摸點1坐标x低8位寄存器位址
#define touch1_yh 0x05 // 觸摸點1坐标y高4位寄存器位址
#define touch1_yl 0x06 // 觸摸點1坐标y低8位寄存器位址
#define id_g_mode 0xa4 // 中斷模式控制寄存器
extern void tp_reset(void);
extern void tp_init(void);
extern int tp_getpoint1_x(void);
extern int tp_getpoint1_y(void);
extern int tp_writebytes(unsigned char writeaddr,
unsigned char *pdata, int length);
extern inttp_readbytes(unsigned char readaddr,
#endif/*__tp_ft5206_h__*/
啟動代碼為使用者設定了必要的運作環境後,就會跳轉到main執行使用者的c代碼,ucgui所有的測試代碼在guidemo目錄中,main函數再跳轉到guidemo_main()即可開始示範guidemo下所有的例程。在前面提到ucgui延時實作需要一個計時标準,這裡,我們在main.c中使用s3c2416的定時器4中斷産生1ms的systemtick為計時。同時,必須保證周期性(約100hz)執行gui_touch_exec(),ucgui用來獲得使用者的觸摸屏輸入操作。
main.c的實作如下:
#include"exception.h"
#include"gui.h"
#include"guidemo.h"
static void timer4_irq(void)
extern volatile intos_timems;
static unsigned char tp_period = 0;
os_timems++; // 1ms計數,在gui_x.c中定義,用來ucgui延時計數
tp_period++;
if (tp_period >= 10) { // 每隔10ms檢查觸摸屏輸入
tp_period = 0;
irq_enable(); // 開啟中斷嵌套
gui_touch_exec();// 保證100hz的觸摸屏輸入檢查
rsrcpnd1 |= (0x01 << int_timer4); // write 1 to clear
rintpnd1 |= (0x01 << int_timer4); // write 1 to clear
void timer4_start()
rtcon |= (0x1 << 20); // 定時器開啟
void timer4_stop()
rtcon &= ~(0x1 << 20); // 定時器停止
void timer4_init()
// 定時器4時鐘頻率為pclk(66.66666m)/(0+1)/16=4.166mhz
rtcfg1 &= ~(0xf << 16);
rtcfg1 |= (0x3 << 16); // timer4 16分頻
rtcfg0 &= ~(0xff << 8);
rtcfg0 |= (0 << 8); // pclk預分頻為1
rtcntb4 = 4166; // system tick設1ms
rtcon |= (0x1 << 21); // 更新計數值
rtcon &= ~(0x1 << 21); // 清除
rtcon |= (0x1 << 22); // 自動重裝載
irq_register(int_timer4, timer4_irq); // 注冊timer4中斷函數
rintmod1 &= ~(1 << int_timer4); //timer4 irq 模式
rintmsk1 &= ~(1 << int_timer4); //timer4開中斷
int main()
timer4_init();
timer4_start();
iic_init();// 電容屏用到iic接口
gui_init();// gui初始化時會同時會初始化lcd屏和觸摸屏
while (1) {
guidemo_main(); // 調用ucgui demo
return 0;
修改實作所有ucgui移植接口後,最後就是加入工程中的源碼,進行頭檔案搜尋路徑的設定,以及其它編譯選擇,生成輸出檔案的設定。對于mdk等可視化內建編譯環境來說,以上設定都很容易操作,筆者不再細說mdk的工程設定,可以在文章末尾連結下載下傳完整的mdk工程。
mdk移植ucgui效果圖: