找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 3442|回复: 6
打印 上一主题 下一主题
收起左侧

带字库(GT20L16S1Y)LCD12864取字错误偏移问题的思考

  [复制链接]
跳转到指定楼层
楼主
      最近买了一个LCD12864的屏幕,驱动采用的是UC1701,SPI接口,字库是高通的GT20L16S1Y。这里吐槽一下我拿到的官方字库手册,居然没有指明各种样式字体的起始地址。不过在厂家给的例程里倒是能估计出来。在使用字库例程修改加上我自己的SPI驱动以后,便可以简单的显示汉字了。一开始以为就这样就好了,然后我打算将之前写的一个电子钟模块移植过来时,却发现年,月,日这样的字符都无法正常显示:年是乱码,月的字符偏移到了字库里面“(六)”’该字符的位置,日的字符则是直接显示“查”字。我一开始觉得是驱动有问题,但是仔细想想又说不通,毕竟ascii字符也是可以读取出来的。使用逻辑分析仪查看除了返回字模错乱,其他也是一切正常。如果驱动有问题,也没可能那么准确的返回另一个字的字模。然后我就考虑是不是其他地方在copy的时候出现了错误,类型不对溢出等。因为之前吃过这个亏,移植了一个stm32的程序到51上,结果MDK开发包与c51开发包的unsigned int类型有差异,导致了程序无缘无故的错误。

      万万没想到这次又吃了这个亏。
      因为寻址算法是官方例程提供的,我也没有多考虑,debug的时候直接排除了这部分。并且在网上查找对比发现这套GB2312的计算方法确实是可行的。我把精力一直放在字库偏移的问题上,进行测试发现,GB2312库使用我的程序,大约在“茧”这个字左右开始,之后的所有字,要么是乱码的无法显示,要么是直接偏移到了前面的区位段去。虽然出错,但是读出的又是完整的字。很明显发生了类似的溢出行为,但我百思不得其解,我代码段里变量都已经修改为unsigned long了,怎么还能溢出呢?该段代码如下:

/***************************************************
16 点GB2312 标准点阵字库
参数说明:
GBCode表示汉字内码。
MSB 表示汉字内码GBCode 的高8bits。
LSB 表示汉字内码GBCode 的低8bits。
Address 表示汉字或ASCII字符点阵在芯片中的字节地址。
BaseAdd:说明点阵数据在字库芯片中的起始地址。
r_dat_bat 是读点阵数据函数。
DZ_Data是保存读出的点阵数据的数组。

*****************************************************/
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);
}


      在某一次搜索中,发现了中景园的字库例程,我简单的对比了一下,基本上是相同的,但是在烧写程序以后,中景园给的例程完全正常,除了臭名昭著的0xfd bug(具体可以在论坛或者网络上搜索),显示完全没问题。看了一眼程序注释,恍然大悟。代码如下(篇幅问题只放一部分作对比,具体可站内搜索):if(((text>=0xb0) &&(text<=0xf7))&&(text[i+1]>=0xa1))
                {                                                
                        /*国标简体(GB2312)汉字在晶联讯字库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个字节的数据,存到"fontbuf[32]"*/
                        display_graphic_16x16(y,x,fontbuf);/*显示汉字到LCD上,y为页地址,x为列地址,fontbuf[]为数据*/
                        i+=2;
                        x+=16;
                }
      这里提了一个关键词 乘法溢出。之前在C语言,C++里面,相信大家都学习过不少相关的知识。例如整型提升,强制类型转换等。uchar溢出的问题在刚写程序的时候也经常会遇到,常常是定时器相关。这里引用一名为的“keil大常量计算问题”的博客来做解释,我自己讲的可能不太准确。
该博客提到:
      keil C51是与ANSI C兼容的编译器,ANSI C规范规定十进制整数常量的默认数据类型是int、long int和unsigned long int的其中一种,对给定的常量是其中的哪一种要看这个常量的实际大小,如果常数在-32768~32767之间则按int类型处理,如果按int类型处理会溢出就考虑long int或更大的数据类型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,并不是期望的结果22118400 * 100 / (65536 * 1000) = 33.75,取整为33。原因分析如下:
宏替换后为:i = 22118400 * 100 / (65536 * 1000);,编译器首先为22118400定义类型,因为22118400不在int的表示范围内,而在long int的范围-2147483648~2147483647内,所以22118400按long int类型处理,在做乘积运算时100被自动按long int处理,22118400 * 100将按两带符号长整型常量进行运算,运算结果仍为带符号长整型,结果写成十六进制是0x83D60000,其十进制是-2083127296,显然出现了溢出错误。keil编译器并没有给出任何错误或警告提示信息(VC++6.0还给出警告warning C4307: * : integral constant overflow),继续进行下一个运算65536 * 1000,结果为带符号长整型,十六进制为0x3E80000,十进制为65536000,最后按两长整型除法计算-2083127296 / 65536000,结果为0xFFFFFFE1,由于i为字符类型,取0xFFFFFFE1的最低有效字节为0xE1赋值给i,i的最终值为0xE1。
      解决这种溢出错误的方法用C语言的一个术语就是“提升”(promotion),拿上例来说就是将22118400指定为无符号长整型,即:
         # define SYSCLK 22118400UL
       注:虽然只要将22118400、100、65536和1000四个常数中的一个指定为无符号长整型即可得到正确的结果,但考虑到可读性及规范性,应选择大整数指定其类型。-----------------------------
       通过以上说明我们可以看见,这一切起因是常量造成的整型提升。以我的字库程序为例就是94,被默认成为了int,而茧对应的机内码为BCEB,MSB为BC,LSB为EB,经过计算可以得到,该式子为(1128+74+846)*32,合并一下,即为2048*32,这个数字大家一定很熟悉吧,2的十五次,对应int类型,恰好发生溢出。因此,茧字以后发生溢出便得到了解释。本身LSB和MSB属于无符号char,但常数94等默认为有符号int,在一起计算的过程中uchar被提升,整体结果以int的形式呈现,而这时中间结果发生了溢出,无法在正常通过赋值提升为ulong类型提供正确地址。因此,字库显示范围永远被现在那个大小。再看中景园的程序,实际上是提前进行ulong转化的步骤,提前存储中间结果,防止其在进行乘法时溢出。同时也可以想到常量后缀的方法,将94等修改为94L,或者使用(unsigned long)进行强制转化,则程序也可以正常运行。


      因此,大家以后在做这种容易涉及溢出的问题的时候,一定要多留个心眼,例如及时将常量加上后缀,以防止意料之外的溢出问题产生。我这个源程序其实是没问题的,MDK环境下能正常运行,因为int的位数问题,导致了stm32使用该程序时,字库无论怎么取也不会出现这个溢出。因此大家在移植程序时也要在这方面多加考虑。

      总体看其实是一个相当愚蠢的问题,却困扰了我好多天。。一直没想到乘法溢出的问题,C语言功底还是不够扎实。总之吃一堑长一智,希望大家能吸取我的经验教训吧,也希望能帮到使用了该例程而感到困惑的朋友。

评分

参与人数 2黑币 +110 收起 理由
gjwhs + 10 很给力!
admin + 100 共享资料的黑币奖励!

查看全部评分

分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
收藏收藏4 分享淘帖 顶 踩
回复

使用道具 举报

沙发
ID:686782 发表于 2020-7-8 23:27 | 只看该作者
网上常说的keil汉字编码方式需要修改成GB2312我个人没什么感觉,我使用的是ANSI C,也是可以正常编码的,使用UTF-8还可以直接看到汉字的机内码。总而言之,汉字在字符串里显示正确一般是没有什么问题的。
至于0xfd bug,有些版本的补丁貌似在keil 4,keil 5无法奏效了,显示查找不到,我这里顺便也上传一下我验证了可以正常使用的版本,我是keil5。

51黑论坛_KeilFDfix_b7(冠铭师兄版).rar

96.79 KB, 下载次数: 27, 下载积分: 黑币 -5

KeilFDfix_b7(冠铭师兄版)

评分

参与人数 1黑币 +20 收起 理由
admin + 20 回帖助人的奖励!

查看全部评分

回复

使用道具 举报

板凳
ID:373 发表于 2022-12-27 16:35 | 只看该作者
学习一下,正使用这个芯片
回复

使用道具 举报

地板
ID:996773 发表于 2023-1-3 14:51 | 只看该作者
本人才疏学浅,只是直接利用字符表的地址串行输入12864显示汉字很正常,没有楼主的不能使用的烦恼
回复

使用道具 举报

5#
ID:994947 发表于 2023-3-23 17:07 | 只看该作者
请问怎么获取该芯片字库的lcd12864屏幕
回复

使用道具 举报

6#
ID:463392 发表于 2023-3-24 15:11 | 只看该作者
1123213124 发表于 2023-3-23 17:07
请问怎么获取该芯片字库的lcd12864屏幕

http://www.51hei.com/bbs/dpj-216347-1.html
回复

使用道具 举报

7#
ID:1068432 发表于 2023-3-29 13:52 | 只看该作者
谢谢大佬点醒梦中人(我=====小白)
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|小黑屋|51黑电子论坛 |51黑电子论坛6群 QQ 管理员QQ:125739409;技术交流QQ群281945664

Powered by 单片机教程网

快速回复 返回顶部 返回列表