標題: 帶字庫(GT20L16S1Y)LCD12864取字錯誤偏移問題的思考 [打印本頁]

作者: OHHO    時間: 2020-7-8 23:04
標題: 帶字庫(GT20L16S1Y)LCD12864取字錯誤偏移問題的思考
      最近買了一個LCD12864的屏幕,驅(qū)動采用的是UC1701,SPI接口,字庫是高通的GT20L16S1Y。這里吐槽一下我拿到的官方字庫手冊,居然沒有指明各種樣式字體的起始地址。不過在廠家給的例程里倒是能估計出來。在使用字庫例程修改加上我自己的SPI驅(qū)動以后,便可以簡單的顯示漢字了。一開始以為就這樣就好了,然后我打算將之前寫的一個電子鐘模塊移植過來時,卻發(fā)現(xiàn)年,月,日這樣的字符都無法正常顯示:年是亂碼,月的字符偏移到了字庫里面“(六)”’該字符的位置,日的字符則是直接顯示“查”字。我一開始覺得是驅(qū)動有問題,但是仔細想想又說不通,畢竟ascii字符也是可以讀取出來的。使用邏輯分析儀查看除了返回字模錯亂,其他也是一切正常。如果驅(qū)動有問題,也沒可能那么準確的返回另一個字的字模。然后我就考慮是不是其他地方在copy的時候出現(xiàn)了錯誤,類型不對溢出等。因為之前吃過這個虧,移植了一個stm32的程序到51上,結(jié)果MDK開發(fā)包與c51開發(fā)包的unsigned int類型有差異,導致了程序無緣無故的錯誤。

      萬萬沒想到這次又吃了這個虧。
      因為尋址算法是官方例程提供的,我也沒有多考慮,debug的時候直接排除了這部分。并且在網(wǎng)上查找對比發(fā)現(xiàn)這套GB2312的計算方法確實是可行的。我把精力一直放在字庫偏移的問題上,進行測試發(fā)現(xiàn),GB2312庫使用我的程序,大約在“繭”這個字左右開始,之后的所有字,要么是亂碼的無法顯示,要么是直接偏移到了前面的區(qū)位段去。雖然出錯,但是讀出的又是完整的字。很明顯發(fā)生了類似的溢出行為,但我百思不得其解,我代碼段里變量都已經(jīng)修改為unsigned long了,怎么還能溢出呢?該段代碼如下:

/***************************************************
16 點GB2312 標準點陣字庫
參數(shù)說明:
GBCode表示漢字內(nèi)碼。
MSB 表示漢字內(nèi)碼GBCode 的高8bits。
LSB 表示漢字內(nèi)碼GBCode 的低8bits。
Address 表示漢字或ASCII字符點陣在芯片中的字節(jié)地址。
BaseAdd:說明點陣數(shù)據(jù)在字庫芯片中的起始地址。
r_dat_bat 是讀點陣數(shù)據(jù)函數(shù)。
DZ_Data是保存讀出的點陣數(shù)據(jù)的數(shù)組。

*****************************************************/
void gt_16_GetData (u8 MSB,u8 LSB)
{
u32 BaseAdd=0,Address;
if(MSB == 0xA9 && LSB >=0xA1)
   Address = (282 + (LSB - 0xA1))*32+BaseAdd;
else if(MSB >=0xA1 && MSB <= 0xA3 && LSB >=0xA1)
       Address =( (MSB - 0xA1) * 94 + (LSB - 0xA1))*32+ BaseAdd;
else if(MSB >=0xB0 && MSB <= 0xF7 && LSB >=0xA1)
        Address = ((MSB - 0xB0) * 94 + (LSB - 0xA1)+ 846)*32+ BaseAdd;
r_dat_bat(Address,32,DZ_Data);
}


      在某一次搜索中,發(fā)現(xiàn)了中景園的字庫例程,我簡單的對比了一下,基本上是相同的,但是在燒寫程序以后,中景園給的例程完全正常,除了臭名昭著的0xfd bug(具體可以在論壇或者網(wǎng)絡上搜索),顯示完全沒問題?戳艘谎鄢绦蜃⑨專腥淮笪。代碼如下(篇幅問題只放一部分作對比,具體可站內(nèi)搜索):if(((text>=0xb0) &&(text<=0xf7))&&(text[i+1]>=0xa1))
                {                                                
                        /*國標簡體(GB2312)漢字在晶聯(lián)訊字庫IC中的地址由以下公式來計算:*/
                        /*Address = ((MSB - 0xB0) * 94 + (LSB - 0xA1)+ 846)*32+ BaseAdd;BaseAdd=0*/
                        /*由于擔心8位單片機有乘法溢出問題,所以分三部取地址*/
                        fontaddr = (text- 0xb0)*94;
                        fontaddr += (text[i+1]-0xa1)+846;
                        fontaddr = (ulong)(fontaddr*32);
                        
                        addrHigh = (fontaddr&0xff0000)>>16;  /*地址的高8位,共24位*/
                        addrMid = (fontaddr&0xff00)>>8;      /*地址的中8位,共24位*/
                        addrLow = fontaddr&0xff;             /*地址的低8位,共24位*/
                        get_n_bytes_data_from_ROM(addrHigh,addrMid,addrLow,fontbuf,32 );/*取32個字節(jié)的數(shù)據(jù),存到"fontbuf[32]"*/
                        display_graphic_16x16(y,x,fontbuf);/*顯示漢字到LCD上,y為頁地址,x為列地址,fontbuf[]為數(shù)據(jù)*/
                        i+=2;
                        x+=16;
                }
      這里提了一個關鍵詞 乘法溢出。之前在C語言,C++里面,相信大家都學習過不少相關的知識。例如整型提升,強制類型轉(zhuǎn)換等。uchar溢出的問題在剛寫程序的時候也經(jīng)常會遇到,常常是定時器相關。這里引用一名為的“keil大常量計算問題”的博客來做解釋,我自己講的可能不太準確。
該博客提到:
      keil C51是與ANSI C兼容的編譯器,ANSI C規(guī)范規(guī)定十進制整數(shù)常量的默認數(shù)據(jù)類型是int、long int和unsigned long int的其中一種,對給定的常量是其中的哪一種要看這個常量的實際大小,如果常數(shù)在-32768~32767之間則按int類型處理,如果按int類型處理會溢出就考慮long int或更大的數(shù)據(jù)類型unsigned long int?傊,編譯器總是按盡可能的原則指定常量的類型。但這一原則并不總能奏效,當兩個常量做運算時就可能導致溢出。如:
    #define SYSCLK  22118400// SYSCLK in Hz (22.1184 MHz external crystal oscillator)
    #define SLIDER_REST_TIME  100// in ms,slider rest time
    #define REST_DELAY    SYSCLK * SLIDER_REST_TIME / (65536 * 1000)
    unsigned char i;
    i = REST_DELAY;
       在keil c51中運行i為0xE1,即225,并不是期望的結(jié)果22118400 * 100 / (65536 * 1000) = 33.75,取整為33。原因分析如下:
宏替換后為:i = 22118400 * 100 / (65536 * 1000);,編譯器首先為22118400定義類型,因為22118400不在int的表示范圍內(nèi),而在long int的范圍-2147483648~2147483647內(nèi),所以22118400按long int類型處理,在做乘積運算時100被自動按long int處理,22118400 * 100將按兩帶符號長整型常量進行運算,運算結(jié)果仍為帶符號長整型,結(jié)果寫成十六進制是0x83D60000,其十進制是-2083127296,顯然出現(xiàn)了溢出錯誤。keil編譯器并沒有給出任何錯誤或警告提示信息(VC++6.0還給出警告warning C4307: * : integral constant overflow),繼續(xù)進行下一個運算65536 * 1000,結(jié)果為帶符號長整型,十六進制為0x3E80000,十進制為65536000,最后按兩長整型除法計算-2083127296 / 65536000,結(jié)果為0xFFFFFFE1,由于i為字符類型,取0xFFFFFFE1的最低有效字節(jié)為0xE1賦值給i,i的最終值為0xE1。
      解決這種溢出錯誤的方法用C語言的一個術語就是“提升”(promotion),拿上例來說就是將22118400指定為無符號長整型,即:
         # define SYSCLK 22118400UL
       注:雖然只要將22118400、100、65536和1000四個常數(shù)中的一個指定為無符號長整型即可得到正確的結(jié)果,但考慮到可讀性及規(guī)范性,應選擇大整數(shù)指定其類型。-----------------------------
       通過以上說明我們可以看見,這一切起因是常量造成的整型提升。以我的字庫程序為例就是94,被默認成為了int,而繭對應的機內(nèi)碼為BCEB,MSB為BC,LSB為EB,經(jīng)過計算可以得到,該式子為(1128+74+846)*32,合并一下,即為2048*32,這個數(shù)字大家一定很熟悉吧,2的十五次,對應int類型,恰好發(fā)生溢出。因此,繭字以后發(fā)生溢出便得到了解釋。本身LSB和MSB屬于無符號char,但常數(shù)94等默認為有符號int,在一起計算的過程中uchar被提升,整體結(jié)果以int的形式呈現(xiàn),而這時中間結(jié)果發(fā)生了溢出,無法在正常通過賦值提升為ulong類型提供正確地址。因此,字庫顯示范圍永遠被現(xiàn)在那個大小。再看中景園的程序,實際上是提前進行ulong轉(zhuǎn)化的步驟,提前存儲中間結(jié)果,防止其在進行乘法時溢出。同時也可以想到常量后綴的方法,將94等修改為94L,或者使用(unsigned long)進行強制轉(zhuǎn)化,則程序也可以正常運行。


      因此,大家以后在做這種容易涉及溢出的問題的時候,一定要多留個心眼,例如及時將常量加上后綴,以防止意料之外的溢出問題產(chǎn)生。我這個源程序其實是沒問題的,MDK環(huán)境下能正常運行,因為int的位數(shù)問題,導致了stm32使用該程序時,字庫無論怎么取也不會出現(xiàn)這個溢出。因此大家在移植程序時也要在這方面多加考慮。

      總體看其實是一個相當愚蠢的問題,卻困擾了我好多天。。一直沒想到乘法溢出的問題,C語言功底還是不夠扎實?傊砸粔q長一智,希望大家能吸取我的經(jīng)驗教訓吧,也希望能幫到使用了該例程而感到困惑的朋友。

作者: OHHO    時間: 2020-7-8 23:27
網(wǎng)上常說的keil漢字編碼方式需要修改成GB2312我個人沒什么感覺,我使用的是ANSI C,也是可以正常編碼的,使用UTF-8還可以直接看到漢字的機內(nèi)碼?偠灾瑵h字在字符串里顯示正確一般是沒有什么問題的。
至于0xfd bug,有些版本的補丁貌似在keil 4,keil 5無法奏效了,顯示查找不到,我這里順便也上傳一下我驗證了可以正常使用的版本,我是keil5。

51黑論壇_KeilFDfix_b7(冠銘師兄版).rar

96.79 KB, 下載次數(shù): 32, 下載積分: 黑幣 -5

KeilFDfix_b7(冠銘師兄版)


作者: chengfgc    時間: 2022-12-27 16:35
學習一下,正使用這個芯片
作者: hi等你    時間: 2023-1-3 14:51
本人才疏學淺,只是直接利用字符表的地址串行輸入12864顯示漢字很正常,沒有樓主的不能使用的煩惱
作者: 1123213124    時間: 2023-3-23 17:07
請問怎么獲取該芯片字庫的lcd12864屏幕

作者: 鄭偉雄    時間: 2023-3-24 15:11
1123213124 發(fā)表于 2023-3-23 17:07
請問怎么獲取該芯片字庫的lcd12864屏幕

http://www.torrancerestoration.com/bbs/dpj-216347-1.html
作者: 3120683840    時間: 2023-3-29 13:52
謝謝大佬點醒夢中人(我=====小白)
作者: CHUANLIUBUXI    時間: 2024-10-31 12:52
感謝,遇到同樣的問題




歡迎光臨 (http://www.torrancerestoration.com/bbs/) Powered by Discuz! X3.1