Chapter Three
— 從“壁畫(huà)”記事到“甲骨文” —
不得不承認,到目前為止,似乎 如果我們要想做一個(gè)電話(huà)號碼記事本之類(lèi)的電子助手已經(jīng)萬(wàn)事俱備了,但真正開(kāi)始做的時(shí)候才發(fā)現,我們還沒(méi)有教會(huì )AVR如何去寫(xiě)字。如果說(shuō),我們前面已經(jīng)能在 LCD上畫(huà)出“壁畫(huà)”的話(huà),那么要想讓別人看懂你記錄的到底是什么鬼畫(huà)符還需要一點(diǎn)點(diǎn)關(guān)于“甲骨文”的掃盲。事實(shí)上,大家約定俗成的固定大小的圖片集或其 子集就是一個(gè)被尊稱(chēng)為字庫的神圣典籍。在這個(gè)圣經(jīng)里面記錄的是一種被稱(chēng)之為“字模碼”的東西,對于我們,這種信息可能相當抽象,但是借助LCD,那么字模 碼就是一個(gè)我們能看懂的字符在顯存中存在的模式。
關(guān)于這些字模碼是如何排列的,自古以來(lái)就有數不清的模式。終于有一天,一群中國人伴隨著(zhù)新中國 站了起來(lái),制定了一個(gè)叫做國標的標準(GB),根據這個(gè)標準,祖國大地的字模碼才有了統一的目錄,而查詢(xún)這個(gè)目錄的方法已經(jīng)逐漸被人們所淡忘,吹落那源自 電報碼的書(shū)籍紅色封皮上的滄桑,用手輕輕撫摸封皮上的的文字:“區位碼”,我們發(fā)現,其實(shí)他包含了6000個(gè)漢字的一級字庫其他一些由非常用字組成的多級 字庫。
在西方,埃尼阿克的故鄉,一群依靠技術(shù)侵略世界的瘋子根據自己半通不同的習慣制定了一個(gè)由128個(gè)字符組成的交換標準稱(chēng)之為ASCII碼,由于技術(shù)大潮的沖擊,世界妥協(xié)了。
……
這一切的一切都無(wú)法改變字庫只不過(guò)是圖片集和的本質(zhì)。所以,敢于抵抗強權的人們在自己的領(lǐng)土上堅持著(zhù)自己的信念——我們稱(chēng)之為“小字庫技術(shù)”。甚至有些人堅持使用圖片記事,那么自然的被視作是“無(wú)字庫技術(shù)”。
世界在前進(jìn),即便后來(lái)世界技術(shù)的格局發(fā)生了怎樣的變化,即便一些曾經(jīng)約定的不合理的東西也會(huì )作為最底層的協(xié)議支持者新世界,就像是烏龜駝著(zhù)的世界。任何觸動(dòng)這些底層的行為都會(huì )受到世界的背叛,所以,拋棄情感上的東西,我們來(lái)研究一下ACSII的構成原理和實(shí)現方法。
對不起大家,我寫(xiě)這些東西的目的就是面向初學(xué)者。事實(shí)上,如各位所說(shuō),我并非高手,所以很多地方漏出了類(lèi)似“內存 映射”之類(lèi)的馬腳。這里我只想做一點(diǎn)解釋?zhuān)挥形遗说臇|西,我才能用通俗的方法和大家解釋?zhuān)乙桓[不通的東西就只好原樣照搬打腫臉充胖子了。呵呵。還 請原諒。事實(shí)上,就拿“內存映射”這個(gè)問(wèn)題來(lái)說(shuō),我使用的大段大段的文字來(lái)解釋這個(gè)概念,因為即便是羅嗦,我也最多只能用“屏幕的一塊區域對應內存的一塊 區域”這樣仍然抽象的話(huà)語(yǔ)來(lái)解釋?zhuān)炊@得我騙稿費一般,所以不如先提出一個(gè)名詞,把解釋溶化在后面的文字中。
本來(lái),開(kāi)篇就說(shuō)得很清楚,我寫(xiě)這 些東西的目的不是等大家來(lái)喊牛,姑妄言之,姑妄聽(tīng)之,水平有限,沒(méi)有刻意去追求什么文本格式上的東西,自然可能不對大家胃口,我以后注意就是了。但是,說(shuō) 回來(lái),寫(xiě)這些東西的心情和大家寫(xiě)伯克的時(shí)候差不多,多半是吐吐心中不吐不快的東西罷了,所以,由著(zhù)性子,演繹也罷,說(shuō)明書(shū)也罷,文檔整理稿也罷,那要看那 一陣子我正在看什么書(shū)了,如果哪天我不幸開(kāi)始看小說(shuō),來(lái)一個(gè)欲知后事請聽(tīng)下回分解也說(shuō)不定。
我的專(zhuān)業(yè)本來(lái)就是軟件工程,所以寫(xiě)出這些文字,非常自然。
--------------------------------
以上,就是本人借著(zhù)大家的機會(huì ),公然在技術(shù)論壇上灌的水。大家五味自知哈。
最近有點(diǎn)忙,第三章可能會(huì )推后幾天……請原諒
對不起大家,最近剛剛忙完“挑戰杯”創(chuàng )業(yè)大賽的商業(yè)計劃書(shū)、學(xué)校的一套試驗系統剛剛設計交付使用、呵呵……對不住大家。我就盡力寫(xiě)一點(diǎn),可能不會(huì )向從前那樣一次寫(xiě)很多了,時(shí)間可能長(cháng)一點(diǎn),但是質(zhì)量絕對不減。??
3.1 ASCII字符集
ASCII(American Standard Code for Information InterChange)——美國通用信息交換編碼。他是現在流行的眾多編碼的榜樣,雖然使用僅僅7位二進(jìn)制表示(通常用一個(gè)字節表示),但是卻是眾多編 碼系統的基礎,比方說(shuō)16位二進(jìn)制為組成的Unicode編碼,證據就是,只要在A(yíng)SCII碼前面加9個(gè)零就成完成了轉換。當然,仍然有不聽(tīng)話(huà)的,比方說(shuō) IBM老大的EBCDIC碼(大型計算機系統上用的)。
大家都注意到了7位二進(jìn)制表示的編碼顯然只能有128個(gè)字符的容量,那么,用一個(gè)字節 256個(gè)字符的容量豈不是造成了浪費?于是,現在PC機上普遍通用的IBM擴展ASCII碼從128~255開(kāi)始擴展了128個(gè)字符——注意,這128個(gè) 字符并不是通用的,即便在我們能接觸的大部分場(chǎng)合他們都有效,但是記住他們的“非常任理事國”的身份是擁有重大意義的。
比方說(shuō),我們的顯示系統只 需要顯示E文字母和數字還有一些標點(diǎn)符號,那么,干什么要這些無(wú)用的字符充數呢?要知道,一個(gè)字母存儲起來(lái)需要至少8*7的點(diǎn)陣(7個(gè)字節)??!事實(shí)上, 由于幾乎所有通用單片機內部都不帶有ASCII字庫(字模碼),所以,我們必須把他們存儲起來(lái),并且還不能打破原有的存儲模式,不然通過(guò)ASCII碼作為 索引我們就找不到他們了。為了完成對字模庫的簡(jiǎn)化,我們需要知道他們的構成方式,然后再考慮如何去獲取一個(gè)已知的標準字模庫,并按照我們設計的方法去簡(jiǎn)化 他……可憐的AVR,存儲器又要吃緊了。
從古老的教科書(shū)上,很容易獲得一張ASCII編碼表。因為大家都是搞Embedded System開(kāi)發(fā)的(為了顯示大家工作的高深程度,請允許我掉書(shū)袋),所以,這里我更多的要講述一下ASCII編碼一些不太被人注意的特性,一些只有從二進(jìn)制角度才容易看出的特性。
1)ASCII碼由7位二進(jìn)制組成;
[6][5][4][3][2][1][0]
2)[6:5]為用來(lái)表示ASCII編碼的組分類(lèi)
控制字符組(顯示不需要顯示的東西)
數字字符和標點(diǎn)符號組
大寫(xiě)字符和特殊字符
小寫(xiě)字符和特殊字符
3)只要把大寫(xiě)字母的第6位也就是[5]置位就是現了到小寫(xiě)字母的轉換,反之亦然;
4)數字的ASCII碼[3:0]位的值與它要表示的數值相同;
例如:
“0”? ?? ?? ? 0x30
“1”? ?? ?? ? 0x31
依照上面的編碼規則,ASCII碼的字模碼文件存儲的方式為:
char c;
……
fAddress = c * 8 * 16;//超級簡(jiǎn)單哈,這是計算機系統上標準8*16的ASCII字符集
fAddress = c * 8 * 8; //這是計算機系統使用的8*8小字符集
聰明的大家已經(jīng)知道如何在存儲器中獲得字模碼了吧?
就是訪(fǎng)問(wèn)存儲的“基地址+fAddress”就可以了。
為了便于后面大家實(shí)現漢字顯示時(shí)候的代碼移植(回避全角和半角問(wèn)題),我們使用8*16的大字符集,至于你想使用小字符集,那么就看你的愛(ài)好了。
3.2 How to get them?
這里我們來(lái)順手說(shuō)說(shuō)字模碼獲得的問(wèn)題。
不可否認,現在網(wǎng)上很多兄弟寫(xiě)的字 模制作軟件水平之高,已經(jīng)到了令人嘆為觀(guān)止的地步,只可惜當時(shí)我學(xué)習字庫問(wèn)題的時(shí)候,尚且沒(méi)有解決溫飽,更不用說(shuō)上網(wǎng)了。而且,這些字模軟件無(wú)不在客觀(guān)上 支持了字模的“無(wú)政府主義”,小字庫和無(wú)字庫技術(shù)在一些不恰當的場(chǎng)合也被大量濫用,嚴重影響了接著(zhù)寫(xiě)你代碼同志的心情……鬼知道原來(lái)跳槽的家伙如何定義那 該死的字庫的,所以,提倡在何時(shí)的場(chǎng)合使用標準的字模庫還是非常有必要的。
首先說(shuō)說(shuō)一種簡(jiǎn)單的獲取字模的方法。
不知道還有多少人記得UCDOS,中國漢字操作系統的“希望”。金山WPS,CCED……TX.com,這些東西讓人難忘啊。我們的字模庫很容易從這樣具有我國獨立知識產(chǎn)權的系統中獲得。所有的東西都放在
UCDOS\
目錄下面。我們選取這次需要的文件“ASC16”。順手說(shuō)下,漢字庫也可以從里面獲得“HZK16”。其他的字模庫在
UCDOS\FNT
目錄下面。
還有一種BT的方法可以獲得8*8的字庫。大家記得BIOS吧……呵呵,利用TC寫(xiě)一個(gè)中斷程序,直接讀出來(lái)……哈哈。后面有機會(huì )我會(huì )附上代碼,如果我能記得地址的話(huà)。
------------------------------------------------------
(本章待續)接下來(lái),我將來(lái)聊聊對這些字庫減肥。
Gorgon Meducer 來(lái)點(diǎn)實(shí)際的吧。
我們這些學(xué)生都等不急了。在這里讀帖子的電子愛(ài)好者都不是閑著(zhù)沒(méi)事逛逛的。
我來(lái)加點(diǎn)東西這是鉆石生成圖:有大液晶的朋友可以看看。
//****************************************************************************
//測試橢圓的圖形驅動(dòng)函數
//***************************************************************************
//顯示漸漸形成的鉆石
void test_elli(void)
{
Uchar i,j;
for(j=0;j<0x5;j++)
{
ClrScr();
for(i=0;i<100;i=i+2)
{
ellispeMidpoint(220,120,100,i);
//ellispeMidpoint(220,120,i,100);
delay_nms(500);
}
}
}//******************************************************************************
//合并四分橢圓點(diǎn)
//入口參數:中心點(diǎn)xc,yc和長(cháng)短軸 rx,ry??
//*****************************************************************************
void ellipsePlotPoints(int xc,int yc,int x,int y)
{
setPixel(xc+x,yc+y);
setPixel(xc-x,yc+y);
setPixel(xc+x,yc-y);
setPixel(xc-x,yc-y);
}
//******************************************************************************
//中點(diǎn)橢圓算法
//入口參數:中心點(diǎn)xc,yc和長(cháng)短軸 rx,ry??
//*****************************************************************************
void ellispeMidpoint(int xc,int yc,int rx,int ry)
{
int rx2=rx*rx;
int ry2=ry*ry;
int tworx2=2*rx2;
int twory2=2*ry2;
int p;
int x=0;
int y=ry;
int px=0;
int py=tworx2*y;
void ellipsePlotPoints(int,int,int,int);
//plot the first set of points
ellipsePlotPoints(xc,yc,x,y);
//region1
p=round(ry2-(rx2*ry)+(0.25*rx2));
while(px<py)
{
x++;
px+=twory2;
if(p<0)
p+=ry2+px;
else
{
y--;
py-=tworx2;
p+=ry2+px-py;
}
ellipsePlotPoints(xc,yc,x,y);
}
//region2
p=round(ry2*(x+0.5)*(x+0.5)+rx2*(y-1)*(y-1)-rx2*ry2);
while(y>0)
{
y--;
py-=tworx2;
if(p>0)
p+=rx2-py;
else
{
x++;
px+=twory2;
p+=rx2-py+px;
}
ellipsePlotPoints(xc,yc,x,y);
}
}
__________________________
總是在東風(fēng)無(wú)力的時(shí)候...
16M晶振跑Gorgon Meducer 先生的窗口(windows)生成算法都顯太慢。填充->擦除->畫(huà)框在液晶上過(guò)程的足跡太明顯。
因為沒(méi)有實(shí)際的顯示緩沖區,沒(méi)有辦法的事情啊,如果你使用SRAM大的AVR芯片,那么應該可以避免畫(huà)圖過(guò)程明顯的弊端的。
我用Mega8L填充整個(gè)換面,沒(méi)有使用優(yōu)化算法,直接用點(diǎn)畫(huà)完只需要500ms左右,內部8M,所以你說(shuō)16M還是慢,我不是很理解。呵呵,沒(méi)有別的意思,全當交流。
還有,沒(méi)有任何人是學(xué)生。你只當我在寫(xiě)blog好么?我沒(méi)有義務(wù)給大家做任何的免費資料即便是垃圾——我只是在寫(xiě)Blog一樣的東西。
如果我寫(xiě)得東西能起到拋磚引玉的作用,那是最好的。
chenbin0011能給大家談?wù)勀愕南敕??不然真的沒(méi)有交流可言了。
我用的320*240點(diǎn)陣液晶。mega128
那個(gè)窗口函數很通用,但重復擦寫(xiě)占去了很多時(shí)間。??
這樣會(huì )好點(diǎn)吧:
Box(X-4,Y-4,X+Width-4,Y,2,1);
Box(X-4,Y-4,X,Y+Height-4,2,1);
Box(X,Y,Width+X,Height+Y,1,1);
另外:做圖時(shí)只用了一個(gè)底層借口SetPix();這和RAM大小有什么聯(lián)系呢。Gorgon Meducer有什么好的處理方法呢?
SetPix函數只有反顯時(shí)需要知道像素的當前狀態(tài),而擦初與顯示都不需理會(huì )。
基于這種考慮來(lái)實(shí)現圖形界面:針對擦除區域為8的整數倍的區域(字符一般為8的整數倍,這種情況也很多見(jiàn))重新編寫(xiě)函數,速度又可提升x倍(取決于總線(xiàn)寬度,和讀取速度)。
擦除的時(shí)候也許要知道當前狀態(tài)阿,不然會(huì )影響到同一個(gè)字節內的其他點(diǎn)的。
您說(shuō):
“
這樣會(huì )好點(diǎn)吧:
Box(X-4,Y-4,X+Width-4,Y,2,1);
Box(X-4,Y-4,X,Y+Height-4,2,1);
Box(X,Y,Width+X,Height+Y,1,1);
”
我考慮到還要覆蓋下面有圖片的情況,不只是下面是空白的情況。
對非8的整數倍變換需要取出一個(gè)字節,對相應位變換后再寫(xiě)入,如果水平位置大于一個(gè)像素可同時(shí)變換多位。對垂直方向只要設光標移動(dòng)方向向下,也可同樣處理。不用每次只變換一個(gè)Pix.
呵呵,通用和效率很難兼顧呀。
Gorgon Meducer :再請教一下啊,31樓的不畫(huà)邊框怎么理解呢?和什么都不執行有什么區別呢,能給出函數嗎?
to chenbin0011
你說(shuō):“
對非8的整數倍變換需要取出一個(gè)字節,對相應位變換后再寫(xiě)入,如果水平位置大于一個(gè)像素可同時(shí)變換多位。對垂直方向只要設光標移動(dòng)方向向下,也可同樣處理。不用每次只變換一個(gè)Pix.”
這種情況我在前文討論過(guò)了。你注意看一下。
“
還記得黑白點(diǎn)陣屏幕的顯存映射方式么?它簡(jiǎn)單的使用1個(gè)字節表示8個(gè)坐標點(diǎn),同時(shí)這1 個(gè)字節是沿著(zhù)屏幕的短邊方向映射的,所以當我們想畫(huà)一條垂直的直線(xiàn)時(shí),對于每一個(gè)牽涉到的字節都有可能要重復的操作8次之多,這種操作不是簡(jiǎn)單的畫(huà)線(xiàn),而 是要先讀取再計算最后再寫(xiě)這樣的復合操作,重復8次只是為了把整個(gè)字節變黑顯然是一種超級不可容忍的冗余——大家都知道,直接把這個(gè)字節讀取一次,計算一 次,再寫(xiě)一次就完成了?;谶@種思想,我們需要把這種特殊情況單獨提取出來(lái),重新優(yōu)化代碼……具體優(yōu)化代碼就留給大家做作業(yè)了哈。 ”
關(guān)于無(wú)邊框,是需要的。一下的就是部分偽碼。前面也是提供過(guò)的……chenbin0011可能太性急,沒(méi)有注意哈。
if (BoxModel == BoxModel_NoBox)
{
if (FillType == FillType_NoFill)
{
//運行到這里說(shuō)明我們被程序員耍了……什么不做到這里干什么???
return ;
}
BoxModel = FillType;
}
Line(Xbegin,Ybegin,Xend,Ybegin,BoxModel);
Line(Xbegin,Ybegin,Xbegin,Yend,BoxModel);
Line(Xbegin,Yend,Xend,Yend,BoxModel);
Line(Xend,Ybegin,Xend,Yend,BoxModel);
3.3 字庫減肥
查看一下Mega8的Datasheet,上面赫然寫(xiě)著(zhù)8K Flash,再看看ASC16的大小4K,我不知道有多少人不會(huì )打寒顫。更不用說(shuō)260多K的漢字庫了,看來(lái)要么外擴存儲器,要么只能選擇西文顯示,并且對ASC16庫進(jìn)行減肥。
其 實(shí),減肥并不是一個(gè)可以稱(chēng)之為技術(shù)的行為,總原則就是丟棄無(wú)用的部分,同時(shí)修改索引方式以保證外界使用減肥前方文字魔窟文件的索引方式不至于出錯就可以 了。就拿ASC16的減肥工作來(lái)說(shuō)吧,很顯然,我們并不需要擴展字符128~255的那個(gè)部分,可以減小大小為16*128 = 2K的大小,再加上基本字符基的四個(gè)區中,控制字符區顯然也是可以舍棄的,所以,還可以減少16*32 = 512B的大小,也就是說(shuō),剩下的文件只有1.5K大小了,哈哈,總算可以接受了。當然如果你選擇了8*8的字模,就更小了。
以上完成的只是第一步,借助類(lèi)似UltraEdit這樣的編輯工具都可以做到,然后我們再把字模庫寫(xiě)成數組的形式,比方說(shuō):
const char ASC_Lib[][16] = {
……
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
……
}
包含入頭文件,就可以了。
下面我們需要一個(gè)訪(fǎng)問(wèn)函數,用來(lái)安全的有效的訪(fǎng)問(wèn)到我們需要的字模庫:
char *getASCLIB(char ASC)
{
if ((ASC <32 ) || (ASC > 127))
{
return ASC_Lib[CharStringNULL]; //需要一個(gè)空字符串作為安全返回
}
return ASC_Lib[ASC];
}
配合12864頭文件中公版中都有的那個(gè)顯示圖片的函數,就可以顯示字符了。例如:
void DispBITMap(const char *String,char StringLength,char X,char Y,char DispModel);
我們還可以再對這個(gè)函數包裝一下,實(shí)現16 * 4的文本模式。使用CLocate(x,y)來(lái)定位字符,用Print("")來(lái)顯示字符串,用PrintN()來(lái)顯示數字……哈哈。這一部分就留給大家自己來(lái)做了,不過(guò)是幾個(gè)函數加宏定義罷了,相信自己簡(jiǎn)單的。
至于如何實(shí)現任意位置顯示字符,還有一些字符特效的實(shí)現,在下一節里面具體說(shuō)明。
這是我寫(xiě)的函數
/********************************************************
* 函數說(shuō)明:讀取顯示數據指令 *
* 輸出: 顯示數據 *
********************************************************/
char getLCD12864Data(void)
{
char TempData = 0;
char a = 0;
LCD12864_SetModel_Data;
LCD12864_SetModel_Read;
LCD12864_SetEnable;
LCD12864_SetDisable;
LCD12864_SetEnable;
asm("nop");
TempData = ReadDataPORT;
return TempData;
}