「正点原子STM32Mini板资料连载」第三十五章 汉字显示实验
1)实验平台:正点原子STM32mini开发板2)摘自《正点原子 STM32 不完全手册(HAL 库版)》 关注官方微信号公众号,获取更多资料:正点原子
第三十五章 汉字显示实验
汉字显示在很多单片机系统都需要用到,少则几个字,多则整个汉字库的支持,更有甚者
还要支持多国字库,那就更麻烦了。本章,我们将向大家介绍,如何用 STM32 控制 LCD 显示
汉字。在本章中,我们将使用外部 FLASH 来存储字库,并可以通过 SD 卡更新字库。STM32
读取存在 FLASH 里面的字库,然后将汉字显示在 LCD 上面。本章分为如下几个部分:
35.1 汉字显示原理简介
35.2 硬件设计
35.3 软件设计
35.4 下载验证
35.1 汉字显示原理简介
常用的汉字内码系统有 GB2312,GB13000,GBK,BIG5(繁体)等几种,其中 GB2312
支持的汉字仅有几千个,很多时候不够用,而 GBK 内码不仅完全兼容 GB2312,还支持了繁体
字,总汉字数有 2 万多个,完全能满足我们一般应用的要求。
本实例我们将制作三个 GBK 字库,制作好的字库放在 SD 卡里面,然后通过 SD 卡,将字
库文件复制到外部 FLASH 芯片 W25Q64 里,这样,W25Q64 就相当于一个汉字字库芯片了。
汉字在液晶上的显示原理与前面显示字符的是一样的。汉字在液晶上的显示其实就是一些
点的显示与不显示,这就相当于我们的笔一样,有笔经过的地方就画出来,没经过的地方就不
画。所以要显示汉字,我们首先要知道汉字的点阵数据,这些数据可以由专门的软件来生成。
只要知道了一个汉字点阵的生成方法,那么我们在程序里面就可以把这个点阵数据解析成一个
汉字。
知道显示了一个汉字,就可以推及整个汉字库了。汉字在各种文件里面的存储不是以点阵
数据的形式存储的(否则那占用的空间就太大了),而是以内码的形式存储的,就是
GB2312/GBK/BIG5 等这几种的一种,每个汉字对应着一个内码,在知道了内码之后再去字库
里面查找这个汉字的点阵数据,然后在液晶上显示出来。这个过程我们是看不到,但是计算机
是要去执行的。
单片机要显示汉字也与此类似:汉字内码(GBK/GB2312)→查找点阵库→解析→显示。
所以只要我们有了整个汉字库的点阵,就可以把电脑上的文本信息在单片机上显示出来了。
这里我们要解决的最大问题就是制作一个与汉字内码对得上号的汉字点阵库。而且要方便单片
机的查找。每个 GBK 码由 2 个字节组成,第一个字节为 0X81~0XFE,第二个字节分为两部分,
一是 0X40~0X7E,二是 0X80~0XFE。其中与 GB2312 相同的区域,字完全相同。
我们把第一个字节代表的意义称为区,那么 GBK 里面总共有 126 个区(0XFE-0X81+1),
每个区内有 190 个汉字(0XFE-0X80+0X7E-0X40+2),总共就有 126*190=23940 个汉字。我
们的点阵库只要按照这个编码规则从 0X8140 开始,逐一建立,每个区的点阵大小为每个汉字
所用的字节数*190。这样,我们就可以得到在这个字库里面定位汉字的方法:
当 GBKL<0X7F 时:Hp=((GBKH-0x81)*190+GBKL-0X40)*csize;
当 GBKL>0X80 时:Hp=((GBKH-0x81)*190+GBKL-0X41)*csize;
其中 GBKH、GBKL 分别代表 GBK 的第一个字节和第二个字节(也就是高位和低位),Hp
为对应汉字点阵数据在字库里面的起始地址(假设是从 0 开始存放),csize 代表一个汉字点阵所
占的字节数。假定采用与 15.3 节 ASCII 字库一样的提取方法(从上到下,从左到右),可以得
出字体大小与点阵所占字节数的对应关系为:
csize=(size/8+((size%8)?1:0))*size;
size 为字体大小,比如 12(12*12)、16(16*16)、24(24*24)等。
这样我们只要得到了汉字的 GBK 码,就可以得到该汉字点阵在点阵库里面的位置,从而
获取其点阵数据,显示这个汉字了。
上一章,我们提到要用 cc936.c,以支持长文件名,但是 cc936.c 文件里面的两个数组太大
了(172KB),直接刷在单片机里面,太占用 flash 了,所以我们必须把这两个数组存放在外部
flash。cc936 里面包含的两个数组 oem2uni 和 uni2oem 存放 unicode 和 gbk 的互相转换对照表,
这两个数组很大,这里我们利用 ALIENTEK 提供的一个 C 语言数组转 BIN(二进制)的软件:
C2B 转换助手 V1.1.exe,将这两个数组转为 BIN 文件,我们将这两个数组拷贝出来存放为一个
新的文本文件,假设为 UNIGBK.TXT,然后用 C2B 转换助手打开这个文本文件,如图 35.1.1
所示:
图 35.1.1 C2B 转换助手
然后点击转换,就可以在当前目录下(文本文件所在目录下)得到一个 UNIGBK.bin 的文
件。这样就完成将 C 语言数组转换为.bin 文件,然后只需要将 UNIGBK.bin 保存到外部 FLASH
就实现了该数组的转移。
在 cc936.c 里面,主要是通过 ff_convert 调用这两个数组,实现 UNICODE 和 GBK 的互转,
该函数原代码如下:
WCHAR ff_convert ( /* Converted code, 0 means conversion error */
WCHAR src, /* Character code to be converted */
UINT
dir
/* 0: Unicode to OEMCP, 1: OEMCP to Unicode */
)
{
const WCHAR *p;
WCHAR c;
int i, n, li, hi;
if (src < 0x80) {
/* ASCII */
c = src;
} else {
if (dir) {
/* OEMCP to unicode */
p = oem2uni;
hi = sizeof(oem2uni) / 4 - 1;
} else {
/* Unicode to OEMCP */
p = uni2oem;
hi = sizeof(uni2oem) / 4 - 1;
}
li = 0;
for (n = 16; n; n--) {
i = li + (hi - li) / 2;
if (src == p[i * 2]) break;
if (src > p[i * 2]) li = i;
else hi = i;
}
c = n ? p[i * 2 + 1] : 0;
}
return c;
}
此段代码,通过二分法(16 阶)在数组里面查找 UNICODE(或 GBK)码对应的 GBK(或
UNICODE)码。当我们将数组存放在外部 flash 的时候,将该函数修改为:
WCHAR ff_convert ( /* Converted code, 0 means conversion error */
WCHAR src,
/* Character code to be converted */
UINT
dir
/* 0: Unicode to OEMCP, 1: OEMCP to Unicode */
)
{
WCHAR t[2];
WCHAR c;
u32 i, li, hi;
u16 n;
u32 gbk2uni_offset=0;
if (src < 0x80)c = src;//ASCII,直接不用转换.
else
{
if(dir) gbk2uni_offset=ftinfo.ugbksize/2;
//GBK 2 UNICODE
else gbk2uni_offset=0;
//UNICODE 2 GBK
/* Unicode to OEMCP */
hi=ftinfo.ugbksize/2;//对半开.
hi =hi / 4 - 1;
li = 0;
for (n = 16; n; n--)
{
i = li + (hi - li) / 2;
SPI_Flash_Read((u8*)&t,ftinfo.ugbkaddr+i*4+gbk2uni_offset,4);//读出 4 个字节
if (src == t[0]) break;
if (src > t[0])li = i;
else hi = i;
}
c = n ? t[1] : 0;
}
return c;
}
代码中的 ftinfo.ugbksize 为我们刚刚生成的 UNIGBK.bin 的大小,而 ftinfo.ugbkaddr 是我们
存放 UNIGBK.bin 文件的首地址。这里同样采用的是二分法查找,关于 cc936.c 的修改,我们就
介绍到这。
字库的生成,我们要用到一款软件,由易木雨软件工作室设计的点阵字库生成器 V3.8。该
软件可以在 WINDOWS 系统下生成任意点阵大小的 ASCII,GB2312(简体中文)、GBK(简体中
文)、BIG5(繁体中文)、HANGUL(韩文)、SJIS(日文)、Unicode 以及泰文,越南文、俄文、乌克
兰文,拉丁文,8859 系列等共二十几种编码的字库,不但支持生成二进制文件格式的文件,也
可以生成 BDF 文件,还支持生成图片功能,并支持横向,纵向等多种扫描方式,且扫描方式
可以根据用户的需求进行增加。该软件的界面如图 35.1.1 所示:
图 35.1.2 点阵字库生成器默认界面
本章,我们总共要生成 3 个字库:12*12 字库、16*16 字库和 24*24 字库。这里以 16*16
字库为例进行介绍,其他两个字库的制作方法类似。
要生成 16*16 的 GBK 字库,则选择:936 中文 PRC GBK,字宽和高均选择 16,字体大小
选择 12,然后模式选择纵向取模方式二(字节高位在前,低位在后),最后点击创建,就可以
开始生成我们需要的字库了(.DZK 文件)。具体设置如图 35.1.3 所示:
图 35.1.3 生成 GBK16*16 字库的设置方法
注意:电脑端的字体大小与我们生成点阵大小的关系为:
fsize=dsize*6/8
其中,fsize 是电脑端字体大小,dsize 是点阵大小(12、16、24 等)。所以 16*16 点阵大小
对应的是 12 字体。
生成完以后,我们把文件名和后缀改成:GBK16.FON。同样的方法,生成 12*12 的点阵库
(GBK12.FON)和 24*24 的点阵库(GBK24.FON),总共制作 3 个字库。
另外,该软件还可以生成其他很多字库,字体也可选,大家可以根据自己的需要按照上面
的方法生成即可。该软件的详细介绍请看软件自带的《点阵字库生成器说明书》,关于汉字显示
原理,我们就介绍到这。
35.2 硬件设计
本章实验功能简介:开机的时候先检测 W25Q64 中是否已经存在字库,如果存在,则按次
序显示汉字(两种字体都显示)。如果没有,则检测 SD 卡和文件系统,并查找 SYSTEM 文件夹
下的 FONT 文件夹,在该文件夹内查找 UNIGBK.BIN、GBK12.FON、GBK16.FON 和
GBK24.FON(这几个文件的由来,我们前面已经介绍了)。在检测到这些文件之后,就开始
更新字库,更新完毕才开始显示汉字。通过按按键 KEY0,可以强制更新字库。同样我们也是
用 DS0 来指示程序正在运行。
所要用到的硬件资源如下:
1) 指示灯 DS0
2) KEY0 按键
3) 串口
4) TFTLCD 模块
5) SD 卡
6) SPI FLASH
这几部分分,在之前的实例中都介绍过了,我们在此就不介绍了。
35.3 软件设计
打开上一章的工程,首先在 HARDWARE 文件夹所在的文件夹下新建一个 TEXT 的文件夹。
在 TEXT 文件夹下新建 fontupd.c、fontupd.h、text.c、text.h 这 4 个文件。并将该文件夹加入头
文件包含路径。
打开 fontupd.c,在该文件内输入如下代码:
//字库区域占用的总扇区数大小(3 个字库+unigbk 表+字库信息=3238700 字节,
//约占 791 个 W25QXX 扇区)
#define FONTSECSIZE
791
//字库存放起始地址
#define FONTINFOADDR 1024*1024*12
//Explorer STM32F4 是从 12M 地址以后开始存放字库前面 12M 被 fatfs 占用了.
//12M 以后紧跟 3 个字库+UNIGBK.BIN,总大小 3.09M,被字库占用了,不能动!
//15.10M 以后,用户可以自由使用.建议用最后的 100K 字节比较好.
//用来保存字库基本信息,地址,大小等
_font_info ftinfo;
//字库存放在 sd 卡中的路径
const u8 *GBK24_PATH="0:/SYSTEM/FONT/GBK24.FON"; //GBK24 的存放位置
const u8 *GBK16_PATH="0:/SYSTEM/FONT/GBK16.FON"; //GBK16 的存放位置
const u8 *GBK12_PATH="0:/SYSTEM/FONT/GBK12.FON"; //GBK12 的存放位置
const u8 *UNIGBK_PATH="0:/SYSTEM/FONT/UNIGBK.BIN";//UNIGBK.BIN 的存放位置
//显示当前字体更新进度
//x,y:坐标
//size:字体大小
//fsize:整个文件大小
//pos:当前文件指针位置
u32 fupd_prog(u16 x,u16 y,u8 size,u32 fsize,u32 pos)
{
float prog; u8 t=0XFF;
prog=(float)pos/fsize;
prog*=100;
if(t!=prog)
{
LCD_ShowString(x+3*size/2,y,240,320,size,"%");
t=prog;
if(t>100)t=100;
LCD_ShowNum(x,y,t,3,size);//显示数值
}
return 0;
}
//更新某一个
//x,y:坐标
//size:字体大小
//fxpath:路径
//fx:更新的内容 0,ungbk;1,gbk12;2,gbk16;3,gbk24;
//返回值:0,成功;其他,失败.
u8 updata_fontx(u16 x,u16 y,u8 size,u8 *fxpath,u8 fx)
{
u32 flashaddr=0; u16 bread; u32 offx=0;
FIL * fftemp;
u8 *tempbuf; u8 res; u8 rval=0;
fftemp=(FIL*)mymalloc(sizeof(FIL)); //分配内存
if(fftemp==NULL)rval=1;
tempbuf=mymalloc(4096); //分配 4096 个字节空间
if(tempbuf==NULL)rval=1;
res=f_open(fftemp,(const TCHAR*)fxpath,FA_READ);
if(res)rval=2;//打开文件失败
if(rval==0)
{
switch(fx)
{
case 0:
//更新 UNIGBK.BIN
ftinfo.ugbkaddr=FONTINFOADDR+sizeof(ftinfo);// UNIGBK 转换码表
ftinfo.ugbksize=fftemp->fsize;
//UNIGBK 大小
flashaddr=ftinfo.ugbkaddr;
break;
case 1:
ftinfo.f12addr=ftinfo.ugbkaddr+ftinfo.ugbksize; //GBK12 字库地址
ftinfo.gbk12size=fftemp->fsize;
//GBK12 字库大小
flashaddr=ftinfo.f12addr;
//GBK12 的起始地址
break;
case 2:
ftinfo.f16addr=ftinfo.f12addr+ftinfo.gbk12size; // GBK16 字库地址
ftinfo.gbk16size=fftemp->fsize;
//GBK16 字库大小
flashaddr=ftinfo.f16addr;
//GBK16 的起始地址
break;
case 3:
ftinfo.f24addr=ftinfo.f16addr+ftinfo.gbk16size; // GBK24 字库地址
ftinfo.gkb24size=fftemp->fsize;
//GBK24 字库大小
flashaddr=ftinfo.f24addr;
//GBK24 的起始地址
break;
}
while(res==FR_OK)//死循环执行
{
res=f_read(fftemp,tempbuf,4096,(UINT *)&bread); //读取数据
if(res!=FR_OK)break;
//执行错误
SPI_Flash_Write(tempbuf,offx+flashaddr,4096); //从 0 开始写入 4096 个数据
offx+=bread;
fupd_prog(x,y,size,fftemp->fsize,offx);
//进度显示
if(bread!=4096)break;
//读完了.
}
f_close(fftemp);
}
myfree(fftemp);
//释放内存
myfree(tempbuf); //释放内存
return res;
}
//更新字体文件,UNIGBK,GBK12,GBK16,GBK24 一起更新
//x,y:提示信息的显示地址
//size:字体大小
//提示信息字体大小
//返回值:0,更新成功;
//
其他,错误代码.
u8 update_font(u16 x,u16 y,u8 size)
{
u8 *gbk24_path=(u8*)GBK24_PATH;
u8 *gbk16_path=(u8*)GBK16_PATH;
u8 *gbk12_path=(u8*)GBK12_PATH;
u8 *unigbk_path=(u8*)UNIGBK_PATH;
u8 res;
res=0XFF;
ftinfo.fontok=0XFF;
SPI_Flash_Write((u8*)&ftinfo,FONTINFOADDR,sizeof(ftinfo));
//清除之前字库成功的标志.防止更新到一半重启,导致的字库部分数据丢失.
SPI_Flash_Read((u8*)&ftinfo,FONTINFOADDR,sizeof(ftinfo));
//重新读出 ftinfo 结构体数据
LCD_ShowString(x,y,240,320,size,"Updating UNIGBK.BIN");
res=updata_fontx(x+20*size/2,y,size,unigbk_path,0);
//更新 UNIGBK.BIN
if(res)return 1;
LCD_ShowString(x,y,240,320,size,"Updating GBK12.BIN ");
res=updata_fontx(x+20*size/2,y,size,gbk12_path,1);
//更新 GBK12.FON
if(res)return 2;
LCD_ShowString(x,y,240,320,size,"Updating GBK16.BIN ");
res=updata_fontx(x+20*size/2,y,size,gbk16_path,2);
//更新 GBK16.FON
if(res)return 3;
LCD_ShowString(x,y,240,320,size,"Updating GBK24.BIN ");
res=updata_fontx(x+20*size/2,y,size,gbk24_path,3);
//更新 GBK24.FON
if(res)return 4;
ftinfo.fontok=0XAA; //全部更新好了
SPI_Flash_Write((u8*)&ftinfo,FONTINFOADDR,sizeof(ftinfo));
//保存字库信息
return 0;//无错误.
}
//初始化字体
//返回值:0,字库完好.
//
其他,字库丢失
u8 font_init(void)
{
SPI_Flash_Init();
SPI_Flash_Read((u8*)&ftinfo,FONTINFOADDR,sizeof(ftinfo));//读出 ftinfo 结构体数据
if(ftinfo.fontok!=0XAA)return 1;
//字库错误.
return 0;
}
此部分代码主要用于字库的更新操作(包含 UNIGBK 的转换码表更新),其中 ftinfo 是我
们在 fontupd.h 里面定义的一个结构体,用于记录字库首地址及字库大小等信息。因为我们将
W25Q64 的前 4.8M 字节给 FATFS 管理(用做本地磁盘),然后又预留了 100K 字节给用户自己
使用,最后的 3.1M 字节(W25Q64 总共 8M 字节),才是 UNIGBK 码表和字库的存储空间,所
以,我们的存储地址是从(4916+100)*1024 处开始的。最开始的 33 个字节给 ftinfo 用,用于保
存 ftinfo 结构体数据,之后依次是:UNIGBK.BIN、GBK12.FON、GBK16.FON 和 GBK24.FON。
保存该部分代码,并在工程里面新建一个 TEXT 的组,把 fontupd.c 加入到这个组里面,然
后打开 fontupd.h 在该文件里面输入如下代码:
#ifndef __FONTUPD_H__
#define __FONTUPD_H__
#include <stm32f10x.h>
//前面 4.8M 被 fatfs 占用了.
//4.8M 以后紧跟的 100K 字节,用户可以随便用.
//4.8M+100K 字节以后的字节,被字库占用了,不能动!
//字体信息保存地址,占 33 个字节,第 1 个字节用于标记字库是否存在.后续每 8 个字节一组
//分别保存起始地址和文件大小
extern u32 FONTINFOADDR;
//字库信息结构体定义
//用来保存字库基本信息,地址,大小等
__packed typedef struct
{
u8 fontok;
//字库存在标志,0XAA,字库正常;其他,字库不存在
u32 ugbkaddr;
//unigbk 的地址
u32 ugbksize;
//unigbk 的大小
u32 f12addr;
//gbk12 地址
u32 gbk12size;
//gbk12 的大小
u32 f16addr;
//gbk16 地址
u32 gbk16size;
//gbk16 的大小
u32 f24addr;
//gbk24 地址
u32 gkb24size;
//gbk24 的大小
}_font_info;
extern _font_info ftinfo; //字库信息结构体
u32 fupd_prog(u16 x,u16 y,u8 size,u32 fsize,u32 pos);
//显示更新进度
u8 updata_fontx(u16 x,u16 y,u8 size,u8 *fxpath,u8 fx);
//更新指定字库
u8 update_font(u16 x,u16 y,u8 size);
//更新全部字库
u8 font_init(void);
#endif
这里,我们可以看到 ftinfo 的结构体定义,总共占用 25 个字节,第一个字节用来标识字库
是否 OK,其他的用来记录地址和文件大小。保存此部分代码,然后打开 text.c 文件,在该文件
里面输入如下代码:
#include "sys.h"
#include "fontupd.h"
#include "flash.h"
#include "lcd.h"
#include "text.h"
#include "string.h"
//code 字符指针开始
//从字库中查找出字模
//code 字符串的开始地址,GBK 码
//mat 数据存放地址 (size/8+((size%8)?1:0))*(size) bytes 大小
//size:字体大小
void Get_HzMat(unsigned char *code,unsigned char *mat,u8 size)
{
unsigned char qh,ql;
unsigned char i;
unsigned long foffset;
u8 csize=(size/8+((size%8)?1:0))*(size);//得到该字体一个汉字对应点阵集所占字节数
qh=*code;
ql=*(++code);
if(qh<0x81||ql<0x40||ql==0xff||qh==0xff)//非 常用汉字
{
for(i=0;i<csize;i++)*mat++=0x00;//填充满格
return; //结束访问
}
if(ql<0x7f)ql-=0x40;//注意!
else ql-=0x41;
qh-=0x81;
foffset=((unsigned long)190*qh+ql)*csize; //得到字库中的字节偏移量
switch(size)
{
case 12:SPI_Flash_Read(mat,foffset+ftinfo.f12addr,24);break;
case 16:SPI_Flash_Read(mat,foffset+ftinfo.f16addr,32);break;
case 24:SPI_Flash_Read(mat,foffset+ftinfo.f24addr,72);break;
}
}
//显示一个指定大小的汉字
//x,y :汉字的坐标
//font:汉字 GBK 码
//size:字体大小
//mode:0,正常显示,1,叠加显示
void Show_Font(u16 x,u16 y,u8 *font,u8 size,u8 mode)
{
u8 temp,t,t1;
u16 y0=y;
u8 dzk[72];
u8 csize=(size/8+((size%8)?1:0))*(size);//得到字体一个字符对应点阵集所占的字节数
if(size!=12&&size!=16&&size!=24)return; //不支持的 size
Get_HzMat(font,dzk,size); //得到相应大小的点阵数据
for(t=0;t<csize;t++)
{
temp=dzk[t];
//得到点阵数据
for(t1=0;t1<8;t1++)
{
if(temp&0x80)LCD_Fast_DrawPoint(x,y,POINT_COLOR);
else if(mode==0)LCD_Fast_DrawPoint(x,y,BACK_COLOR);
temp<<=1;
y++;
if((y-y0)==size) { y=y0; x++; break; }
}
}
}
//在指定位置开始显示一个字符串
//支持自动换行
//(x,y):起始坐标
//width,height:区域
//str :字符串
//size :字体大小
//mode:0,非叠加方式;1,叠加方式
void Show_Str(u16 x,u16 y,u16 width,u16 height,u8*str,u8 size,u8 mode)
{
……此处代码省略
}
//在指定宽度的中间显示字符串
//如果字符长度超过了 len,则用 Show_Str 显示
//len:指定要显示的宽度
void Show_Str_Mid(u16 x,u16 y,u8*str,u8 size,u8 len)
{
……//此处代码省略
}
此部分代码总共有 4 个函数,我们省略了两个函数(Show_Str_Mid 和 Show_Str)的代码,
另外两个函数,Get_HzMat 函数用于获取 GBK 码对应的汉字字库,通过我们 35.1 节介绍的办
法,在外部 flash 查找字库,然后返回对应的字库点阵。Show_Font 函数用于在指定地址显示一
个指定大小的汉字,采用的方法和 LCD_ShowChar 所采用的方法一样,都是画点显示,这里就
不细说了。保存此部分代码,并把 text.c 文件加入 TEXT 组下。text.h 里面都是一些函数申明,
这里我们就不贴出来了,详见光盘本例程源码。
前面提到我们队 cc936.c 文件做了修改,我们将其命名为 mycc936.c,并保存在 exfuns 文件
夹下,将工程 FATFS 组下的 cc936.c 删除,然后重新添加 mycc936.c 到 FATFS 组下,mycc936.c
的源码就不贴出来了,其实就是在 cc936.c 的基础上去掉了两个大数组,然后对 ff_convert 进行
了修改,详见光盘本例程源码。
最后,我们在 main.c 里面修改 main 函数如下:
int main(void)
{
u32 fontcnt;
u8 i,j,key,t;
u8 fontx[2];//gbk 码
HAL_Init();
//初始化 HAL 库
Stm32_Clock_Init(RCC_PLL_MUL9); //设置时钟,72M
delay_init(72);
//初始化延时函数
uart_init(115200);
//初始化串口
usmart_dev.init(84);
//初始化 USMART
LED_Init();
//初始化 LED
KEY_Init();
//初始化按键
LCD_Init();
//初始化 LCD
mem_init();
//初始化内存池
exfuns_init();
//为 fatfs 相关变量申请内存
f_mount(fs[0],"0:",1);
//挂载 SD 卡
f_mount(fs[1],"1:",1);
//挂载 FLASH.
while(font_init())
//检查字库
{
UPD:
LCD_Clear(WHITE);
//清屏
POINT_COLOR=RED;
//设置字体为红色
LCD_ShowString(60,50,200,16,16,"Mini STM32");
while(SD_Initialize())
//检测 SD 卡
{
LCD_ShowString(60,70,200,16,16,"SD Card Failed!");
delay_ms(200);
LCD_Fill(60,70,200+60,70+16,WHITE);
delay_ms(200);
}
LCD_ShowString(60,70,200,16,16,"SD Card OK");
LCD_ShowString(60,90,200,16,16,"Font Updating...");
key=update_font(20,110,16);//更新字库
while(key)//更新失败
{
LCD_ShowString(60,110,200,16,16,"Font Update Failed!"); delay_ms(200);
LCD_Fill(20,110,200+20,110+16,WHITE); delay_ms(200);
}
LCD_ShowString(60,110,200,16,16,"Font Update Success!");
delay_ms(1500);
LCD_Clear(WHITE);//清屏
}
POINT_COLOR=RED;
Show_Str(30,50,200,16,"Mini STM32 开发板",16,0);
Show_Str(30,70,200,16,"GBK 字库测试程序",16,0);
Show_Str(30,90,200,16,"正点原子@ALIENTEK",16,0);
Show_Str(30,110,200,16,"2019 年 11 月 18 日",16,0);
Show_Str(30,130,200,16,"按 KEY0,更新字库",16,0);
POINT_COLOR=BLUE;
Show_Str(30,150,200,16,"内码高字节:",16,0);
Show_Str(30,170,200,16,"内码低字节:",16,0);
Show_Str(30,190,200,16,"汉字计数器:",16,0);
Show_Str(30,220,200,24,"对应汉字为:",24,0);
Show_Str(30,244,200,16,"对应汉字(16*16)为:",16,0);
Show_Str(30,260,200,12,"对应汉字(12*12)为:",12,0);
while(1)
{
fontcnt=0;
for(i=0x81;i<0xff;i++)
{
fontx[0]=i;
LCD_ShowNum(148,150,i,3,16);
//显示内码高字节
for(j=0x40;j<0xfe;j++)
{
if(j==0x7f)continue;
fontcnt++;
LCD_ShowNum(148,170,j,3,16); //显示内码低字节
LCD_ShowNum(148,190,fontcnt,5,16);//汉字计数显示
fontx[1]=j;
Show_Font(60+132,220,fontx,24,0);
Show_Font(60+144,244,fontx,16,0);
Show_Font(60+108,260,fontx,12,0);
t=200;
while(t--)//延时,同时扫描按键
{
delay_ms(1);
key=KEY_Scan(0);
if(key==KEY0_PRES)goto UPD;
}
LED0=!LED0;
}
}
}
}
此部分代码就实现了我们在硬件描述部分所描述的功能,至此整个软件设计就完成了
35.4 下载验证
本例程支持 12*12、16*16 和 24*24 等三种字体的显示,在代码编译成功之后,我们通过下
载代码到 ALIENTEK MiniSTM32 开发板上,可以看到 LCD 开始显示三种大小的汉字及汉字内
码,如图 35.4.1 所示:
图 35.4.1 汉字显示实验显示效果
一开始就显示汉字,是因为 ALIENTEK MiniSTM32 开发板在出厂的时候都是测试过的,
里面刷了综合测试程序,已经把字库写入到了 W25Q64 里面,所以并不会提示更新字库。如果
你想要更新字库,那么则必须先找一张 SD 卡,把:光盘\5,SD 卡根目录文件 文件夹下面的
SYSTEM 文件夹拷贝到 SD 卡根目录下,插入开发板,并按复位,之后,在显示汉字的时候,
按下 KEY0,就可以开始更新字库了。字库更新界面如图 35.4.2 所示:
图 35.4.2 汉字字库更新界面
我们还可以通过 USMART 来测试该实验,我们可以通过 USMART 调用 Show_Str 函数,
来实现任意位置显示任何字符串,有兴趣的朋友可以测试一下。
单片机实例分享,自定义提醒闹钟
图14.1 电子钟
想起那时刚学习单片机,仅仅会写几段汇编代码。后来的几个月,自己也学写了一个电子钟程序,不过做得非常简陋,也没使用什么时钟芯片,仅仅通过单片机的定时器来累计计时,功能上实现了时、分、秒的显示以及简单的闹钟功能。不过,我还真是怀念以前学习单片机的美好时光,也很高兴那时自己能专心学习单片机。后面我也做了一款像样的电子钟(见图14.1),在下面的内容里,我会和大家分享制作它的过程。
零件清单
零件清单是制作这款电子钟的基本元件,还有一些边角料,不再列出。从零件列表(见表14.1)中大家可以看出,整体的成本不超过80元。不过,最近这段时期,AVR单片机涨价厉害,在网上也要18元左右才能买到。如果液晶屏在淘宝网上购买的话,建议以关键字ST7565搜索,这样才能搜索到ST7565控制器的128×64液晶屏。至于其他零件比较常用,一般电子市场都能购买得到。
图14.2 零件全家福
表14.1 材料表
电路原理
如图14.3所示,微控制器(MCU)我选择了AVR单片机的Atmega8,因为比较熟悉。它的程序存储器大小为8KB,数据RAM大小为1KB,工作电源电压范围为2.7~5.5V,最大工作温度为+85℃,最小工作温度为-40℃。时钟芯片选择DS1302,DS1302是美国DALLAS公司推出的一种高性能、低功耗、带RAM的实时时钟电路,它可以对年、月、日、周日、时、分、秒进行计时,具有闰年补偿功能,工作电压为2.5~5.5V。采用三线接口与MCU进行同步通信。最后,通过使用SD卡的SPI模式和单片机的SPI接口连接,进行数据交换。SD卡相当于容量很大的SPI接口的FLASH,在制作过程中,也可以替换成大容量的FLASH芯片。128×64液晶仅仅需要4根线和单片机连接。由于使用的控制器是ST7565,它不带中文、英文字库,因此需要自己建立字库。但这种液晶屏价格便宜,外观也很小巧。我购买的这款液晶屏,背光是橘黄色的,到了晚上会发出迷人的光泽。
图14.3 电路原理图
工作原理
在制作之前,我先介绍一下它的工作原理。控制芯片使用的是AVR的atmega8单片机,简称M8。大家也可以使用熟悉的51单片机(程序可以到本书配套光盘中下载,大家根据需要自己修改或移植)。程序通过读取SD卡内的TXT文件,显示每天需要提醒的内容。因此,可以通过电脑,方便地修改提醒的内容,如节日、生日、纪念日等。不必再为了修改液晶屏上的提醒内容而特意修改程序代码,仅仅通过编辑TXT文件即可。SD卡是通过它的SPI接口和单片机进行数据交换。液晶屏使用的是串口128×64的黑白液晶屏,控制器是ST7565,它和单片机连接也仅仅需要4个I/O口。时钟芯片使用的是DS1302,大家对它应该不陌生吧。时钟芯片通过3个I/O口和单片机连接,电源使用USB接口的5V电源,经过1117-3.3电源稳压芯片转换成3.3V电压,供给单片机、液晶和SD卡使用,大家也可以使用3.3V的电源直接供电。这款电子钟通过两个按钮实现时间和闹钟的设置。当时钟正常运行时,第2个按钮可以单独开启闹钟或关闭闹钟。
使用方法
(1)在计算机的WinXP系统下把SD卡格式化成FAT文件系统。
(2)先复制字体到SD卡内,这样才能在液晶上显示中文。
(3)在根目录下新建“提醒.txt”文本文件。
(4)在文件内写入一行内容,如下:****-02-13"明天是情人节",这样每年的2月13号,电子钟就会提醒你明天是情人节了。
大家会发现,这款电子钟没有农历的显示,如果要显示农历怎么办呢?如果朋友的生日是按农历来算的怎么办呢?其实也挺简单的,通过在文件中写入公历和农历的对应时间关系即可。如:2010-02-13"农历2009-12-30"。注意:*号是通配符,表示任意的意思。例如:2010-**-**"虎年",表示2010年的任意日期,都会显示虎年。
字库的制作及使用
这是本次制作的知识要点之一。GB2312是中国国家标准简体中文字符集,全称《信息交换用汉字编码字符集·基本集》,又称GB0,由中国国家标准总局发布,1981年5月1日实施。GB2312编码通行于中国大陆,新加坡等也采用此编码。中国大陆绝大多数中文系统和国际化的软件都支持GB2312。
GB2312标准共收录6763个汉字,其中一级汉字3755个,二级汉字3008个;同时收录了包括拉丁字母、希腊字母、日文平假名及片假名字母、俄语西里尔字母在内的682个字符。GB2312的出现,基本满足了汉字的计算机处理需要。GB2312中对所收汉字进行了“分区”处理,每区含有94个汉字/符号。这种表示方式也称为区位码。
01~09区为特殊符号。
16~55区为一级汉字,按拼音排序。
56~87区为二级汉字,按部首/笔画排序。
10~15区及88-94区则未有编码。
举例来说,“啊”字是GB2312之中的第一个汉字,它的区位码就是1601。
在电脑上的TXT文本文件中,每个汉字及符号以两个字节来表示。第一个字节称为“高位字节”,第二个字节称为“低位字节”。为了和原有的ASCII码兼容,“高位字节”使用了0xA1~0xF7(把01~87区的区号加上0xA0),“低位字节”使用了0xA1~0xFE(把01-94加上0xA0)。由于一级汉字从16区起始,汉字区的“高位字节”的范围是0xB0~0xF7,“低位字节”的范围是0xA1~0xFE,占用的码位是72×94=6768。其中,有5个空位是D7FA~D7FE。例如,“啊”字在文件中会以两个字节“0xB0(第一个字节)0xA1(第二个字节)”储存。
制作步骤
1 按图在洞洞板上安插好各个元件,并插上已经烧录好程序的芯片。
2 根据电路原理图,依次连接导线
3 将各个组件准备好了后,就可以组装起来。
4 最后插上USB电源,就可正常工作了。
DIY的过程不仅仅是制作过程,还是一个让作品更美好的过程,为此,我又开始了这个自定义提醒闹钟的美化过程。
将钢丝弯曲成如图的形状,在钢丝上拧上螺丝,在万用板上也拧上螺丝。
用内六角扳手、普通扳手拧紧螺丝。
那么如何定位字库中的点阵数据呢?文件编码的区码范围是从0xA1(十六进制)开始,对应区位码中区码的第一区,第二个字节为汉字的位码,范围也是从0xA1(十六进制)开始,对应某区中的第一个位码。就是说,将汉字编码减去0xA0A0就得到该汉字的区位码。例如,汉字“啊”的机内码是十六进制的“0xB0A1”,其中前两位“0xB0”表示编码的区码,后两位“0xA1”表示编码的位码。所以“啊”的区位码为0xB0A1-0xA0A0=0x1001,将区码和位码分别转换为十进制16和01,得到汉字“啊”位于第16区的第1个字的位置,那么点阵数据在文件中的位置为第“32×[(16-1)×94+(1-1)]=45120”以后的32个字节。这就是“啊”的显示点阵需要的字节数据了。其中,32为16×16点阵的取模字节数,表示32字节大小。单片机通过取这连续的32个字节,送到LCD的相应位置,就能正确显示汉字、图形符号了。
最后,使用字库生成工具,就能生成自己需要的字库了。这样的工具软件在网上有许多,请自行选择。我使用未注册的“汉字取模字库生成”小工具,使用次数有一定的限制,但偶尔用于生成字库还是够用的。由于这款液晶显示数据是1列(8个点)为一个地址单位的,而不是1行(8个点)或点地址为单位。因此,取模时需使用纵向(列)取模方式取模,这样方便后期程序的编写。当然也可以直接选择“@宋体”这类字体。通过工具预览后,你会发现,这种字体旋转了90°。点击生成字库,在弹出的菜单中输入的路径和文件名。按“确认”后就会生成需要的字库了,注意后缀名为.dot。程序读取经过旋转后的32字节字体数据,即点阵列数据,就能显示一个汉字了。
存储整个字库数据是个难点,GB2312汉字库有200多字节的大小,单片机的FLASH可是没有足够的空间用来保存它。那么怎么办呢?其实,方法也挺多的。有一种实用、简单、方便的方法就是外接FLASH存储芯片。如SST25VF020、AT45DB161等,它们都是串行接口,可以节省许多I/O,读取速度也够快,但增加了制作成本。还有一种方法,可以直接放在SD卡内,但程序会复杂很多。同时,显示字体的速度也没外接FLASH快。不过最后我还是选择了第2种方法,以后的小制作中再试试第1种方法。
为了让电脑和单片机互相交换数据容易些,需要把SD卡格式化成FAT文件系统,然后单片机解读SD卡格式化后FAT文件系统,在此基础上再读取txt文件,最后调用相应的字库数据在液晶屏上显示。具体如何实现,请读者朋友自行分析源代码。源代码可到本书配套光盘上下载。
编程说明
当我烧录完程序后,迫不及待地要运行这个电路了。把电子钟插上电源后,程序会先初始化硬件(液晶、SD卡和时钟芯片)。之后会读取单片机EEPROM里的闹钟信息,没有的话会新建初始化内容,并写入EEPROM。最后,液晶就会分4行显示时间、日期、星期和闹钟。当有提醒信息时,闹钟时间和提醒的内容会交替闪烁。在程序的循环体内,程序会定时读取SD卡内的TXT文件,如果TXT文件内定义的日期和时钟芯片的日期一致,那么单片机会读取文本文件内对应的显示内容,并在液晶的第4行显示。如果没有相等的日期,单片机会显示默认的字符串“MADEBYZBJ”,大家可以改成自己定义的字符串。
电子钟的右侧有4个按钮,但是本次制作只使用了上面2个按钮,另外2个按钮功能未用。这4个按钮的一端都连到了单片机的中断引脚,并把这个中断引脚设置为上拉,在程序中等待下降沿中断。按钮的另一端和单片机的4个普通I/O连接,这4个I/O设置为低电平。当按钮按下时,就会引发下降沿中断,此时程序修改中断,引为低电平,并把4个普通I/O口上拉,再分别读取4个引脚的电平状态。如果,某个引脚读到低电平,就可以判断对应的这个按钮按下了。最后,等待按钮的释放,不断循环此过程。
当时钟在运行状态时,按第1个按钮,将会进入时钟设置状态,再次按下第1个按钮,就会进入下一个设置选项,以此类推,直到退出最后一个选项(注意闹钟关闭状态,不会进入闹钟设置选项),这样电子钟就会退出设置状态,再次进入运行状态了。电子钟在设置状态下,设置的项目会反显,可以通过按第2个按钮,改变设置的数值。
当时钟在运行状态时,按下第2个按钮,闹钟将会开启或关闭,这取决于原来的状态。在液晶屏上会显示相应的闹钟状态信息。闹钟数据虽然保存在M8单片机的EEPROM中,但不会直接使用。当单片机上电运行时,会自动载入RAM中使用,这样是为了延长EEPROM的使用寿命。但当RAM中的闹钟数据改变时,修改的数据才会同步更新,写入EEPROM。程序会比较RAM中的闹钟时间和时钟芯片的时钟是否一致,当两者一致时,闹钟就会“嘀嘀”地叫了。至于鸣叫多久,大家可以根据自己的需要修改程序中的设置。
更多源程序可以到qq群657864614进行下载!
有没有因为想买一本书,因为价格偏高而犹豫?现在我来帮你解决这个问题,把书名作者直接告诉我,我来帮你找,通常价格均为1元,难一点的也就几块钱,先查询,后付款,诚信经营! 代找各种电子书电子图书教材文献查找代查代找中文pdf格式-淘宝网
相关问答
51 单片机 如何实现 汉字 显示,如“龙”字,能否给程序和电路图?方案一:含字库的液晶,单片机给出相应指令即可显示。效果最好,成本较高,程序量小,难度适中。方案二:点阵液晶或LED点阵,以16*16分辨率,用汉字取模软件取...方...
单片机 中8*8点阵中 汉字 的编码是怎么获得的?8*8造汉字很困难的,你可以找一个8*8的汉字库(如果有的话),用字模提取软件(网上很多的)获取点阵字库数据,之后写入这个8*8点阵就OK了。8*8造汉字很困难的,你可...
单片机 如何识别串口的数据是中文还是字母?串口有两种通讯模式,一种是十六进制模式,一种是文本模式。选择文本模式就可以发送中文。其实文本模式发送的ASCII码,接收时再转换回相应字符(包括中文)。串口...
chinese,除了中国人的意思以外,还有什么意思?一般表示“中文”、“汉语”用“Chinese”就可以了。其余还有”Chinesecharacters”、”[计]Chineseideograph”(计算机术语用词)以及简化汉字:”simplif...
单片机 光标显示問題 12864方面的-ZOL问答12864的操作单位是两个字符(一个汉字),也就是你说的一个地址。光标也只能这样...单片机控制12864液晶屏的光标显示通常涉及驱动寄存器操作。首先,你需要初始化...
求PIC 单片机 英文介绍和中文翻译.PIC16F877A型号的更好. 汉字 3...[最佳回答]我有些不错范文,采纳后发你.
C语言是什么意思? - 羽天 的回答 - 懂得首先,人类发明了计算机,2113需要与计算5261机“交流”,即写入4102和读出,而且硬件需要与软件相1653配才能发挥作用,这样必须发明一中语言让人类与机...
sr电容系列详解?可组屏安装,也可就地安装到开关柜。1.1特点a)装置硬件采用32位单片机及高精度A/D,具有完善的自诊断功能,汉字液晶显示操作提示及动作信息,操作简单方...
displaying是什么意思[回答]displaying意思是显示。displaying:v.陈列;展出;展示;显露,表现(特性或情感等);显示。display的现在分词。扩展资料Somemethodsfordisplaying...
世界上编程语言那么多,有中国哪些是原创的吗?CPU和指令集,不是自己的,源程序要变成机器指令才能运行。还有一层套一层的系统调用,各种库。编译器要适应这些系统调用。相对简单一些的,单片机C语言方言,...