LCD中文字符显示机制

2021-08-20 10:35:00 浏览数 (1)

一、字符显示机制

1. 字符显示原理

要在LCD上显示一个字符,需要以下两步:

  • ① 占据屏幕上的一块地方,大小由字体大小说了算;
  • ② 在占据的地方上依次控制每个像素点是否显示。

比如中文字符24×24字体大小表示水平需要24个像素点、垂直需要24个像素点。

在这块24×24的地盘上,每个像素点是否显示由字模说了算,字模中的每1位数据表示一个像素点,如果该位为0则表示此处像素点不显示、为1则表示显示(阴码规则)。

所以,字符显示的函数只需要基于打点函数实现即可。在程序中逐位读取字模,如果该位为0则该点写入背景颜色,如果该位为1则写入前景颜色。

2. 字模生成(点阵字体)

正因为LCD显示字符的机制,所以该种字体被称为点阵字体。

为了方便大家理解原理,这里我首先使用小工具生成字模。

设置工具的字模生成规则如下,阴码、逐行式、顺向取模(图中有误)、C51格式:

接着生成汉字的字模:

接着复制生成数据,在程序中定义为一个二维数组作为字库,第一个值表示字库中的元素个数,可以由编译器自行判断,第二个值是每个元素的大小,必须要指明,这样我们就可以用 hz_16x16[0] 来找到汉字 ”春”在字库中的位置:

代码语言:javascript复制
#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 的内容:

代码语言:javascript复制
#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上建立文件系统,直接使用文件,方便字库文件替换。

后续这些优化点我也会写文章分享,这个周末要出去逛吃逛吃啦~

0 人点赞