使用字库 HZK16 来驱动oled0.96(ssd1306) 显示中文

硬件平台:友善之臂 Tiny4412
软件平台:Ubuntu16.04
源码位置: https://github.com/lian494362816/Tiny4412/tree/master/SourceCode/Driver/003_spi

本文的重点放在如何使用字库上,因此不过多的介绍OLED模块

文章目录

  • 1. OLED 模块基本介绍
    • 1. Pin脚介绍
    • 1.2 显示及数据排列
  • 2. 显示原理
  • 3. 字库的使用
    • 3.1 字符编码的转换 UTF8转GBK
    • 3.2 获取字模信息
    • 3.3 验证
  • 4. 数据转换和最终的效果
    • 4.1 数据的转换
    • 4.2 突如其来的问题
    • 4.3 成果展示
  • 5 小结

1. OLED 模块基本介绍

1. Pin脚介绍

使用字库 HZK16 来驱动oled0.96(ssd1306) 显示中文_第1张图片
使用字库 HZK16 来驱动oled0.96(ssd1306) 显示中文_第2张图片
OLED 连接到Tin4412 的SPI接口,
D/C 连接 GPB_5,当D/C 为高电平时传输的是数据,为低电平时传输的是命令
RESET 连接 GPB_4, 当RESET 为低电平时OLED复位, 为高电平取消复位状态
对OLED来说,使用的为4-wire SPI

1.2 显示及数据排列

OLED 总共有128x64 个像素点, 内部按Page 和 Seg 来区别, 总共有8个Page, 每个Page 有128个Seg。相当于分成8行、128列, 每行占8个像素点, 每列占1个像素点
使用字库 HZK16 来驱动oled0.96(ssd1306) 显示中文_第3张图片
地址的增长总共有3种模式:page addressing mode, horizontal addressingmode and vertical addressing mode
这里是介绍第1种page addressing mode。
page addressing mode: 从某个Page Seg的低地址到高地址增长, 到了最高地址后Page不变, Seg又变为低地址。

使用字库 HZK16 来驱动oled0.96(ssd1306) 显示中文_第4张图片

注意前面说的,一个Page 占8个像素点,因此一个byte的数据刚好可以写完一个Seg
当设置完了起始Page和Seg后,继续写数据Seg会自动增加
使用字库 HZK16 来驱动oled0.96(ssd1306) 显示中文_第5张图片

2. 显示原理

因为OLED模块不带字库,因此只能通过描点的方式来显示中文和英文,英文字母网上已经有一大堆16X16的字库,而中文的则没有,因此要在OLED上显示中文需要使用特定的字库或者自己使用取模软件。

要显示中文可以使用16X16的像素点来表示,当然是可以显示一些简单的文字,太复杂的文字还是显示不了的
根据前面对OLED模块的介绍,显示16X16需要使用到2个Page, 16个Seg。 16X16 bit = 2x16Bytes, 因此可以把数据分成2排,
每排占16个Bytes, 数据的排列方式如下

使用字库 HZK16 来驱动oled0.96(ssd1306) 显示中文_第6张图片

3. 字库的使用

字库的介绍不多说,只介绍一句 HZK16字库是符合GB2312国家标准的16×16点阵字库, 16x16是重点
字库的详细介绍:https://blog.twofei.com/505/

3.1 字符编码的转换 UTF8转GBK

在linux下使用的是UTF-8编码, 一个中文字符由个3Byte表示,而HZK16字库是GB2312表示,一个中文字符有2个Byte组成。因此就需要把UTF-8转化为GB2312
注: (代码实际用的GBK, GBK向下兼容GB2312)

UTF8转成GBK使用的是iconv库, 虚拟机中本身就有这个库和头文件,因此可以直接使用里面的函数。

/*
need include  
convert UTF-8 to GBK (GBK is compatible GB2313)
input_buf:UTF-8中文字符串,一个中文字符占3个字节
output_buf:将中文字符串转化成GBK编码后的输出, 一个中文字符占2个字节
*/
int UTF8_to_GBK(unsigned char *input_buf, long input_len, unsigned char *output_buf, long output_len)
{
    char *encTo = "GBK//IGNORE";
    char *encFrom = "UTF-8";
    char *srcstart = input_buf;
    char *tempoutbuf = output_buf;
    int i = 0;
    long ret = 0;

    iconv_t fd = iconv_open (encTo, encFrom);
    if (fd == (iconv_t)-1)
    {
      printf ("iconv_open fail \n");
      return -1;
    }

    memset(output_buf, 0, output_len);

    ret = iconv (fd, &srcstart, &input_len, &tempoutbuf, &output_len);
    if (ret == -1)
    {
      printf ("iconv fail \n");
    }

    iconv_close (fd);

    return 0;
}

3.2 获取字模信息

通过3.1 已经可以获取GKB编码的中文字符了, 接下来就是在字库HZK16中找到对应的32 Byte数据。(16x16 bit= 32byte)
原理很简单, 打开HZK16文件, 通过2字节的GBK数据算出中文字符对应位置,然后读到某个buff中,再把buff返回来即可。
计算位置的原理请看:https://blog.twofei.com/505/

/*
one_chinese_buf:input, only one chinese-character(UTF-8), 3 bytes
hex_buf:output, HZK16 code, 32 bytes
can use print_HZK16_string to print hex_buf result
*/
int Get_GBK_Code(unsigned char *one_chinese_buf, unsigned char *hex_buf)
{
    FILE* fphzk = NULL;
    unsigned char tmp_buf[10];
    int offset;

	//这里把前面的UTF8_to_GBK 函数也封装进来了,因此可以直接输出UTF8的中文字符
    //one chinese-character(UTF-8) is 3 bytes
    UTF8_to_GBK(one_chinese_buf, 3, tmp_buf, 10);

    fphzk = fopen("HZK16", "rb");
    if(fphzk == NULL)
    {
        fprintf(stderr, "error hzk16\n");
        return 1;
    }

    offset = (94*(unsigned int)(tmp_buf[0]-0xa0-1)+(tmp_buf[1]-0xa0-1))*32;

    fseek(fphzk, offset, SEEK_SET);
    fread(hex_buf, 1, 32, fphzk);

    //print_HZK16_string(hex_buf, 32);

    fclose(fphzk);

    return 0;
}

3.3 验证

3.1 和 3.2 已经获取了 GBK编码对应的32Byte数据, 接下来需要验证,看数据是否正确。
因此封装了print_HZK16_string 来打印获取到的数据。这里数据的打印顺序与前面说的OLED的排列顺序不一样。OLED的叫列行式, print_HZK16_string的叫逐行式。
使用字库 HZK16 来驱动oled0.96(ssd1306) 显示中文_第7张图片

/*
buf: HZK16 buf,
len: buf len,should be multiple of 32
*/
int print_HZK16_string(unsigned char *buf, int len)
{
    int i, j, k, n, z;
    int flag;
    unsigned char key[8] = {0x80,0x40,0x20,0x10,0x08,0x04,0x02,0x01};

    if (len < 32 || 0 != (len % 32))
    {
        printf("len is not multiple of 32 \n");
        return -1;
    }

    n = 0;
    do{
        for(k=0; k < 16; k++)
        {
            for(j=0; j < 2; j++)
            {
                for(i=0; i < 8; i++)
                {
                    flag = buf[k*2+j]&key[i];
                    printf("%s", flag?"●":"○");
                }
            }
            printf("\n");
        }
        for (i = 0; i <32; i++)
        {
            printf("0x%-2x ", buf[i]);
        }
        n++;
        buf += 32;
        printf(" \n");
        printf(" \n");
    }while(n < len / 32);

    return 0;
}

测试代码:https://github.com/lian494362816/C/tree/master/TEST/2019_05
直接编译即可运行
09_chinese_word.c, 直接编译即可运行
使用字库 HZK16 来驱动oled0.96(ssd1306) 显示中文_第8张图片
使用字库 HZK16 来驱动oled0.96(ssd1306) 显示中文_第9张图片

4. 数据转换和最终的效果

4.1 数据的转换

因为HZK16库中的数据是按逐行式排列的,而OLED需要使用行列式的数据排列,因此需要把逐行式的数据转换为行列式的数据。

原来的数据排列是逐行式,可以看做是16x16的矩阵, 现在要从这个矩阵里面去提取数据。按照从上往下,从左往右的顺序来获取每一个bit, 再组成一个byte.

先获取第一个byte的数据, 由Byte0的第7位, Byte2的第7位, Byte4的第7位, Byte6的第7位,Byte8的第7位,Byte10的第7位,Byte12的第7位,Byte14的第7位。

再获取第二个byte的数据, 由Byte0的第6位, Byte2的第6位, Byte4的第6位, Byte6的第6位,Byte8的第6位,Byte10的第6位,Byte12的第6位,Byte14的第6位。
依次类推
使用字库 HZK16 来驱动oled0.96(ssd1306) 显示中文_第10张图片
使用字库 HZK16 来驱动oled0.96(ssd1306) 显示中文_第11张图片

使用字库 HZK16 来驱动oled0.96(ssd1306) 显示中文_第12张图片

int change_arrangement_mode(unsigned char *pc_buf, unsigned char *oled_buf, int len)
{
    int i, j, k;

    if (len % 32)
    {
        printf("len is not multiple of 32 \n");
        return -1;
    }

    k = 0;
    do {
        for (i = 0; i < 8; i++)
        {
            for (j = 0; j < 8; j++)
            {
                oled_buf[i] |= ( ((pc_buf[j * 2] & (0x1 << 7 - (i % 8))) >> (7 - (i % 8)) ) << j );
                oled_buf[i + 8] |= ( ((pc_buf[j * 2 + 1] & (0x1 << 7 - (i % 8))) >> (7 - (i % 8)) ) << j );
                oled_buf[i + 16] |= ( ((pc_buf[j * 2 + 16] & (0x1 << 7 - (i % 8))) >> (7 - (i % 8)) ) << j );
                oled_buf[i + 24] |= ( ((pc_buf[j * 2 + 1 + 16] & (0x1 << 7 - (i % 8))) >> (7 - (i % 8)) ) << j 	);
                //printf("buf[%d] bit[%d] = byte[%d] & %0x << %d \n", i, j, j * 2, (0x1 << 7 - (i % 8)), j);
            }
         }
         k += 32;
         pc_buf += 32;
         oled_buf += 32;
     }while(k < len);

    pc_buf -= k;
    oled_buf -= k;

     return 0;
}

4.2 突如其来的问题

当我把所有程序在PC上调试过后,通过交叉编,放到开发板上执行时,程序报错。
提示我的参数有问题,不过我再虚拟机上测试过是ok的,然后把encTo 和 encFrom 各种修改,发现程序不支持GBK这个参数,
如果不转化成GBK, 后面的程序都无法执行, 真是恼火。
使用字库 HZK16 来驱动oled0.96(ssd1306) 显示中文_第13张图片
使用字库 HZK16 来驱动oled0.96(ssd1306) 显示中文_第14张图片
细想一下,虚拟机支持,但是交叉编译后不支持,应该是交叉编译器自带的库的问题。于是我找到了libiconv的网站,下载了libiconv的源码。
http://www.gnu.org/software/libiconv/#downloading

将源码通过交叉编译器编译成了静态库(我的交叉编译器无法编译动态库),最后使用静态库解决了代码运行的错误。

4.3 成果展示

上层的测试函数,中间有驱动层的代码,这里不做介绍
使用字库 HZK16 来驱动oled0.96(ssd1306) 显示中文_第15张图片
使用字库 HZK16 来驱动oled0.96(ssd1306) 显示中文_第16张图片

5 小结

本来使用OLED模块我只是想测试一下SPI总线的,可是OLED模块不带字库,无法显示中文,让我写测试很不爽。
只能显示英文字符,这不符合国情。随后便想到以前使用LCD12864模块时,模块是自带文字库的,因此想通过动态查找字库来在OLED模块上显示中文,继而找到了 HZK16字库。然后linux 的字符编码为UTF-8, 因此需要转换成GBK(GB2313), 结果交叉编译器自带的库又不支持GBK的转换,被迫自己去找libiconv的源码,自己编译静态库。最后HZK16字模的排列方式与OLED所需的不一样,因此又要转换排列方式,最后才成功的显示出中文。
过程如下:
1)点亮OLED
2)为显示中文,找到HZK16字库
3)linux使用UTF-8编码, 需转换成GBK(GB2313) 编码
4)交叉编译器自带的库无法正常运行,网上找到libiconv的源码, 手动编译静态库
5)HZK16字模排列顺序与OLED所需的不一样, 写代码转换。
本来只是想显示中文字符,没想到要经过这么多步骤,特写此文给后面的人,让后人少走点弯路。

你可能感兴趣的:(使用字库 HZK16 来驱动oled0.96(ssd1306) 显示中文)