一、字符显示机制
1. 字符显示原理
要在LCD上显示一个字符,需要以下两步:
- ① 占据屏幕上的一块地方,大小由字体大小说了算;
- ② 在占据的地方上依次控制每个像素点是否显示。
比如中文字符24×24字体大小表示水平需要24个像素点、垂直需要24个像素点。
在这块24×24的地盘上,每个像素点是否显示由字模说了算,字模中的每1位数据表示一个像素点,如果该位为0则表示此处像素点不显示、为1则表示显示(阴码规则)。
所以,字符显示的函数只需要基于打点函数实现即可。在程序中逐位读取字模,如果该位为0则该点写入背景颜色,如果该位为1则写入前景颜色。
2. 字模生成(点阵字体)
正因为LCD显示字符的机制,所以该种字体被称为点阵字体。
为了方便大家理解原理,这里我首先使用小工具生成字模。
设置工具的字模生成规则如下,阴码、逐行式、顺向取模(图中有误)、C51格式:
接着生成汉字的字模:
接着复制生成数据,在程序中定义为一个二维数组作为字库,第一个值表示字库中的元素个数,可以由编译器自行判断,第二个值是每个元素的大小,必须要指明,这样我们就可以用 hz_16x16[0]
来找到汉字 ”春”在字库中的位置:
#ifndef _HZ_H_
#define _HZ_H_
const unsigned char hz_16x16[][32] = {
{0x01,0x00,0x01,0x00,0x7F,0xFC,0x01,0x00,0x3F,0xF8,0x02,0x00,0xFF,0xFE,0x08,0x20,
0x10,0x10,0x2F,0xE8,0xC8,0x26,0x08,0x20,0x0F,0xE0,0x08,0x20,0x08,0x20,0x0F,0xE0},/*"春",0*/
/* (16 X 16 , 宋体 )*/
};
#endif /* _HZ_H_*/
我们选择的字体是16x16,所以:
- 水平方向有16个像素点,每个像素点占1位,需要16bit,两个字节来表示一行;
- 垂直方向有16行,所以整体有2*16=32个字节;
此处需要注意,软件每16个字节生成一对花括号,这会影响二维数组取值,需要将中间多余的花括号去除。
二、如何将字符显示到LCD
1. 打点函数支持
字符显示需要打点函数的支持,这里我使用RGB-LCD的打点函数:
代码语言:javascript复制void lcd_draw_point(uint16_t x, uint16_t y, uint16_t color);
2. 字库支持
引入刚刚我们创建二维数组的头文件:
代码语言:javascript复制#include "hz.h"
3. 读取字模显示
代码语言:javascript复制void lcd_show_chinese(uint16_t x, uint16_t y, char ch, uint16_t back_color, uint16_t font_color, uint8_t font_size)
{
uint16_t i, j;
uint16_t x_pos, y_pos, size, font_width, font_height;
uint8_t *font_ptr;
uint8_t bit_width, temp;
if((x > (LCD_WIDTH - font_size)) || (y > (LCD_HEIGHT - font_size))) {
return;
}
x_pos = x;
y_pos = y;
font_height = font_size;
font_width = font_size;
bit_width = 8;
size = (font_width / 8 ((font_width % 8) ? 1 : 0)) * font_height;
font_ptr = (uint8_t*)&hz_16x16[ch];
for (i = 0; i < size; i ) {
temp = *(font_ptr i);
for (j = 0; j < bit_width; j ) {
if(temp & 0x80){
lcd_draw_point(x_pos, y_pos, font_color);
} else {
lcd_draw_point(x_pos, y_pos, back_color);
}
temp <<= 1;
x_pos ;
}
if (x_pos >= (x font_width)) {
y_pos ;
x_pos = x;
}
}
}
实现之后记得声明:
代码语言:javascript复制/**
* @brief Show a chinese char.
* @param[in] x1 horizontal start position.
* @param[in] y1 vertical start position.
* @param[in] x2 horizontal end position.
* @param[in] y2 vertical end position.
* @param[in] ch offset in hz library.
* @param[in] back_color rgb565
* @param[in] font_color rgb565
* @param[in] font_size support 16.
* @return None
* @note This function need hz library.
*/
void lcd_show_chinese(uint16_t x, uint16_t y, char ch, uint16_t back_color, uint16_t font_color, uint8_t font_size);
4. 测试显示
在main函数中调用:
代码语言:javascript复制lcd_show_chinese(0, 0, 0, BLACK, GREEN, 16);
编译、下载即可看到效果:
三、小字库的使用
1. 制作小字库
在项目中,我们需要显示一些中文,但也没必要将整个汉字库包含进来,所以经常是制作本项目专属小字库。
这里取模工具使用安富莱电子的MakeDot,非常方便:
这里选择了输入文字重排,可以去除重复汉字,减少字库体积。
将生成的整个数组复制作为之前我们创建数组 hz_16x16
的内容:
#ifndef _HZ_H_
#define _HZ_H_
const unsigned char hz_16x16[][32] = {
{0x00,0x00,0x10,0x00,0x10,0x00,0x10,0x00,0x10,0x00,0x10,0x00,0x10,0x00,0x10,0x00,// ! //
0x10,0x00,0x10,0x00,0x00,0x00,0x00,0x00,0x10,0x00,0x10,0x00,0x00,0x00,0x00,0x00},
{0x00,0x00,0x20,0x00,0x17,0xFE,0x10,0x08,0x80,0x08,0x43,0xC8,0x42,0x48,0x12,0x48,// 河 //
0x12,0x48,0x22,0x48,0xE3,0xC8,0x22,0x48,0x20,0x08,0x20,0x08,0x20,0x28,0x00,0x10},
{0x10,0x00,0x10,0x00,0x10,0x00,0x10,0x7C,0xFE,0x44,0x12,0x44,0x12,0x44,0x12,0x44,// 加 //
0x12,0x44,0x12,0x44,0x12,0x44,0x12,0x44,0x22,0x44,0x22,0x7C,0x4A,0x44,0x84,0x00},
{0x01,0x00,0x01,0x00,0xFF,0xFE,0x01,0x00,0x01,0x00,0x7F,0xFC,0x48,0x24,0x44,0x44,// 南 //
0x4F,0xE4,0x41,0x04,0x41,0x04,0x5F,0xF4,0x41,0x04,0x41,0x04,0x41,0x14,0x40,0x08},
{0x00,0x40,0x20,0x40,0x10,0x40,0x10,0x40,0x87,0xFC,0x44,0x44,0x44,0x44,0x14,0x44,// 油 //
0x14,0x44,0x27,0xFC,0xE4,0x44,0x24,0x44,0x24,0x44,0x24,0x44,0x27,0xFC,0x04,0x04},
};
#endif /* _HZ_H_*/
这样就完成了我们整个项目小字库的制作,同样的方法,我们再制作出hz_24x24字库,hz_32x32字库。
代码语言:javascript复制const unsigned char hz_24x24[][72] = {
//...
};
const unsigned char hz_32x32[][128] = {
};
2. 使用小字库
进一步完善中文显示函数,找到获取字模数据的代码:
代码语言:javascript复制font_ptr = (uint8_t*)&hz_16x16[ch]
优化为:
代码语言:javascript复制switch (font_size) {
case 16:
font_ptr = (uint8_t*)&hz_16x16[ch];
break;
case 24:
font_ptr = (uint8_t*)&hz_24x24[ch];
break;
case 32:
font_ptr = (uint8_t*)&hz_32x32[ch];
break;
default:
return;
}
在main函数中进一步添加测试程序:
代码语言:javascript复制lcd_show_chinese(0, 0, 1, BLACK, RED, 16); // 河
lcd_show_chinese(16, 0, 3, BLACK, RED, 16); // 南
lcd_show_chinese(32, 0, 2, BLACK, RED, 16); // 加
lcd_show_chinese(48, 0, 4, BLACK, RED, 16); // 油
lcd_show_chinese(64, 0, 0, BLACK, RED, 16); // !
lcd_show_chinese(0, 20, 1, BLACK, RED, 24);
lcd_show_chinese(24, 20, 3, BLACK, RED, 24);
lcd_show_chinese(48, 20, 2, BLACK, RED, 24);
lcd_show_chinese(72, 20, 4, BLACK, RED, 24);
lcd_show_chinese(96, 20, 0, BLACK, RED, 24);
lcd_show_chinese(0, 60, 1, BLACK, RED, 32);
lcd_show_chinese(32, 60, 3, BLACK, RED, 32);
lcd_show_chinese(64, 60, 2, BLACK, RED, 32);
lcd_show_chinese(96, 60, 4, BLACK, RED, 32);
lcd_show_chinese(128, 60, 0, BLACK, RED, 32);
显示效果如下:
四、进一步的优化
本文中讲述的仅仅是最基本的中文显示方法,还可以进一步进行优化。
① 字符偏移优化
这里使用小字库不太方便的点是,字符在字库中的偏移位置需要自己控制,如果想自动控制就涉及编码问题,实现后可以直接写汉字字符串来显示。
② 字库优化
项目中比较稳妥的办法是使用全字库,片内Flash肯定不够用,通常的做法是外挂一片SPI Flash,将整个全字库文件烧写到Flash里,使用的时候读出来即可。
③ 字体优化
本文中我们取模时都使用的是宋体,好不容易建立的字库,可能某天老板突然过来说要换成什么奇奇怪怪的字体,那不是完蛋了~
所以在实现的时候我们要考虑在SPI Flash上建立文件系统,直接使用文件,方便字库文件替换。
后续这些优化点我也会写文章分享,这个周末要出去逛吃逛吃啦~