首页 / 资讯中心 / 文章详情

GBK点阵字模:嵌入式显示与热敏打印的通用字库方案

GBK点阵字模:嵌入式显示与热敏打印的通用字库方案 ★ FEATURED ARTICLE
简介这是一份面向嵌入式显示与打印开发者的专业GBK汉字点阵字库资源针对打印机固件、LCD汉字显示等场景中字模数据难找、编码转换繁琐的问题提供可直接使用的字模文件与配套索引算法。包内共21个文件约3.41MB以bin字库、txt编码表、exe与bpl工具、c与h源码等为主GBK16.BIN与GBK24.BIN分别提供16×16和24×24点阵字模覆盖GBK1.0中22046个汉字不含用户自定义区两份编码表分别按分区和编码顺序排列便于快速定位。资源还附带字模提取与牧码字模等工具以及GBK转BIG5、GBK转Unicode的函数源码和字体获取索引函数可直接嵌入工程使用。目前已有1665人学习下载适合需要稳定字库与转码方案的开发者参考。1. GBK 点阵字模从嵌入式屏到热敏打印机的通用字库方案做嵌入式显示或者热敏打印的兄弟大概率都遇到过这个场景设备端要显示中文但主控 Flash 只剩几百 KB跑不动 FreeType也不想挂完整字库芯片。这时候 GBK 点阵字模就是最务实的选择——16×16 用于小屏状态栏和票据正文24×24 用于标题和打印抬头每个汉字占 32 字节或 72 字节按需取模RAM 里只缓存当前要画的那几行。这套资源把 GBK 全字符集的 16 点阵和 24 点阵字模数据、索引算法、取模工具打包在一起解决的就是「没有文件系统、没有矢量字体引擎也要把汉字稳定画出来」的问题。适合做 POS 机、电子秤、工控 HMI、热敏打印机固件的工程师也适合刚接触字库索引、想搞明白 GBK 区位码和点阵偏移怎么换算的新手。下面按「数据怎么组织 → 索引怎么算 → 怎么接进显示和打印 → 坑在哪」的顺序拆一遍。2. 字模数据组织与 GBK 索引算法区位码、偏移量和字节序2.1 为什么 GBK 点阵字模要按区位码而不是 Unicode 排GBK 是双字节编码第一字节 0x81–0xFE第二字节 0x40–0xFE剔除 0x7F。它的排列本身就有区位结构高字节对应区低字节对应位。点阵字模如果按 GBK 码位顺序线性排列索引就退化成一次减法加乘法不需要二分查找也不需要哈希表。常见做法是把 16 点阵和 24 点阵各存成一个连续二进制文件文件内第 N 个汉字的数据块就是N × 每字字节数的偏移。这样在 MCU 上取一个字模只需要读 GBK 双字节 → 算出该字在字库中的序号 → 乘以单字字节数 → 从 Flash 或外部存储读对应长度。整个过程没有查表没有字符串比较对资源紧张的设备非常友好。但这里有个容易翻车的点GBK 并不是所有码位都有字。0x80 不用0xFF 不用低字节 0x7F 也不用。如果直接按(高字节 - 0x81) × 190 (低字节 - 0x40)算序号遇到低字节大于 0x7F 的情况就会多算一个位置。正确做法是低字节先判断是否大于 0x7F大于则减 0x41否则减 0x40。这个细节在索引算法里必须体现否则从某个区开始所有汉字都会整体偏移一位显示出来全是乱码。2.2 索引算法的代码实现与参数说明下面这段 C 代码是索引算法的核心输入 GBK 双字节输出该字在字库文件中的字节偏移。假设 16 点阵每字 32 字节24 点阵每字 72 字节。#include stdint.h /* 字模参数按实际字库文件调整 */ #define FONT16_BYTES_PER_CHAR 32u /* 16*16/8 32 */ #define FONT24_BYTES_PER_CHAR 72u /* 24*24/8 72 */ /* 计算 GBK 字符在字库中的线性序号无效返回 0xFFFFFFFF */ uint32_t gbk_char_index(uint8_t high, uint8_t low) { /* 高字节有效范围 0x81-0xFE */ if (high 0x81u || high 0xFEu) { return 0xFFFFFFFFu; } /* 低字节有效范围 0x40-0xFE且排除 0x7F */ if (low 0x40u || low 0xFEu || low 0x7Fu) { return 0xFFFFFFFFu; } uint32_t high_idx (uint32_t)(high - 0x81u); /* 0 起算的区号 */ uint32_t low_idx; if (low 0x7Fu) { low_idx (uint32_t)(low - 0x41u); /* 跳过 0x7F */ } else { low_idx (uint32_t)(low - 0x40u); } /* 每区 190 个码位0x40-0x7E 共 63 个0x80-0xFE 共 127 个 */ return high_idx * 190u low_idx; } /* 获取 16 点阵字模在文件中的偏移失败返回 0xFFFFFFFF */ uint32_t gbk_font16_offset(uint8_t high, uint8_t low) { uint32_t idx gbk_char_index(high, low); if (idx 0xFFFFFFFFu) { return 0xFFFFFFFFu; } return idx * FONT16_BYTES_PER_CHAR; } /* 获取 24 点阵字模在文件中的偏移 */ uint32_t gbk_font24_offset(uint8_t high, uint8_t low) { uint32_t idx gbk_char_index(high, low); if (idx 0xFFFFFFFFu) { return 0xFFFFFFFFu; } return idx * FONT24_BYTES_PER_CHAR; }逻辑说明gbk_char_index先把高字节映射到 0–125 的区号再把低字节映射到 0–189 的位号最后用区号 × 190 位号得到线性序号。低字节大于 0x7F 时减 0x41 而不是 0x40就是为了跳过 0x7F 这个无效码位。gbk_font16_offset和gbk_font24_offset分别乘以单字字节数得到文件偏移。参数说明FONT16_BYTES_PER_CHAR和FONT24_BYTES_PER_CHAR必须和实际字库文件一致。有些字库工具生成的文件会带文件头或者按区补齐这时候偏移计算要加上文件头长度或者改用区索引表。如果字库是按「每区固定 190 字」排列的上面的公式直接可用如果工具生成时把无效码位也占了位置那就要以工具输出的索引表为准。2.3 字模字节序与扫描方向显示正常但打印反了的根源点阵字模的字节排列方式直接决定显示和打印是否正确。常见的有三种横向取模、纵向取模、行列式。16×16 汉字横向取模时每行 2 字节共 16 行总共 32 字节。纵向取模时每列 2 字节共 16 列。热敏打印机通常按行输出所以横向取模更顺手但有些 LCD 驱动 IC 的显存是按页8 行一页组织的纵向取模反而方便直接写 GRAM。这份资源里的字模数据如果和你手头的屏或打印机对不上先别怀疑索引算法优先检查取模方向。一个快速验证方法取「一」字横向取模时第一行应该是0x00 0x00中间某一行是0xFF 0xFF纵向取模时则是每列都有数据整体呈竖条状。如果显示出来是旋转 90 度或者镜像的基本就是取模方向或字节高低位反了。提示字模文件本身不带元数据取模方向、字节序、是否包含 ASCII 半角都要在接入前用工具确认。不要拿到文件直接写驱动先用 PC 端脚本渲染几个字看看。2.4 用 Python 快速验证字模文件是否正确在把字模烧进 Flash 之前我一般会在 PC 上用 Python 把二进制文件渲染成图片确认索引和取模方向都对。下面这段脚本读取 16 点阵文件按 GBK 编码取字并打印成点阵图。# -*- coding: utf-8 -*- # 验证 GBK 16x16 点阵字模文件按 GBK 编码取字渲染成文本点阵 FONT16_FILE gbk16.bin BYTES_PER_CHAR 32 def gbk_index(high, low): 与 C 版本一致的索引算法 if high 0x81 or high 0xFE: return None if low 0x40 or low 0xFE or low 0x7F: return None high_idx high - 0x81 low_idx low - 0x41 if low 0x7F else low - 0x40 return high_idx * 190 low_idx def render_char(ch): 渲染单个汉字为 16x16 文本点阵 data ch.encode(gbk) if len(data) ! 2: print(非 GBK 双字节字符) return idx gbk_index(data[0], data[1]) if idx is None: print(无效 GBK 码位) return offset idx * BYTES_PER_CHAR with open(FONT16_FILE, rb) as f: f.seek(offset) bitmap f.read(BYTES_PER_CHAR) if len(bitmap) ! BYTES_PER_CHAR: print(读取越界检查字库文件大小) return # 横向取模每行 2 字节高位在左 for row in range(16): byte1 bitmap[row * 2] byte2 bitmap[row * 2 1] line for bit in range(7, -1, -1): line ## if (byte1 bit) 1 else for bit in range(7, -1, -1): line ## if (byte2 bit) 1 else print(line) if __name__ __main__: render_char(中)逻辑说明gbk_index和 C 版本保持一致确保验证结果能直接反映嵌入式端的索引行为。render_char把汉字编码成 GBK 双字节算出偏移从文件读取 32 字节然后按横向取模逐位展开。每行两个字节第一个字节对应左 8 列第二个字节对应右 8 列高位在左。参数说明FONT16_FILE改成实际字库文件名BYTES_PER_CHAR如果是 24 点阵就改成 72渲染循环的行数和每行字节数也要相应调整。如果打印出来是空白或者全黑先检查文件路径和文件大小是否至少为190 × 126 × 32字节左右。如果字形上下颠倒说明取模方向是纵向或者字节序反了需要调整读取和展开逻辑。3. 把字模接进显示与打印驱动层取模、缓存与刷屏策略3.1 显示驱动里怎么按需取模避免整屏刷新卡顿在 MCU 上驱动 LCD 或 OLED 时最忌讳的是每次刷新都从 Flash 读整个字库。正确做法是只读当前要显示的那几个字在 RAM 里开一块行缓冲按行输出到屏。以 16 点阵为例一个汉字 32 字节一行 2 字节。如果屏宽 128 像素一行能放 8 个汉字那么行缓冲只需要 16 字节。每次刷新一行读 8 个汉字的对应行数据拼成 16 字节发给屏。这样 RAM 占用极小Flash 读取也是顺序的速度稳定。具体实现时先根据要显示的字符串算出每个字的 GBK 双字节和字库偏移存到一个临时数组里。然后按行循环对于第 r 行遍历每个字从它的 32 字节里取第 r×2 和 r×21 两个字节依次填入行缓冲。填满一行就调用屏的写行函数。这样一套流程下来显示 8 个汉字一屏只需要读 256 字节对 SPI 屏来说完全跟得上。如果用的是带 GRAM 的屏比如 ST7920 这类可以直接把字模按页写入 GRAM不需要行缓冲。但要注意 ST7920 的显存是纵向取模的每页 8 行写入时要把横向取模的字模转成纵向。转换逻辑不复杂但容易写错建议先用小数组在 PC 上模拟验证。3.2 热敏打印机走纸与点阵行输出的对齐热敏打印机和 LCD 不一样它是按点行输出的。一行 384 点48 毫米纸或者 576 点80 毫米纸每个点对应加热丝的通断。打印汉字时需要把 16 点阵或 24 点阵的每一行点数据对齐到打印头的点行上。常见做法是维护一个打印行缓冲宽度等于打印头点数比如 384 点就是 48 字节。要打印一行文字时先把行缓冲清零然后根据当前打印位置把每个汉字的对应行数据按位或进去。汉字之间留 1 到 2 列空白避免粘连。这里有个血泪经验热敏打印机的点行数据通常是高位在前但有些机芯是低位在前。如果打印出来文字是镜像的先检查位序。另外走纸速度和加热时间会影响浓度但那是打印机厂商的指令参数和字模本身无关。字模只负责点数据不负责浓度。24 点阵用于打印时每行 3 字节共 24 行。如果打印头一行 384 点那么一行能放 16 个 24 点阵汉字。行缓冲需要 48 字节每个汉字每行贡献 3 字节。按行循环 24 次每次把 16 个汉字的对应 3 字节填入行缓冲然后调用打印行指令。整个过程和显示类似只是数据宽度不同。3.3 字模缓存与 Flash 读取优化如果设备有外部 SPI Flash 存字库读取速度可能成为瓶颈。16 点阵全字库大约 190×126×32 ≈ 766 KB24 点阵大约 1.7 MB。SPI Flash 读一个字节大概几微秒读一个汉字 32 字节要上百微秒。如果一屏 8 个字刷新一屏要读 256 字节大概几毫秒可以接受。但如果频繁刷新比如滚动显示就会卡。优化方法有两种一是把常用字模缓存到 RAM比如最近用过的 64 个字用简单的 LRU 淘汰二是把字库放到 MCU 内部 Flash如果容量够的话。内部 Flash 读取是零等待的速度飞快。如果内部 Flash 不够可以考虑只把 16 点阵放内部24 点阵放外部因为 16 点阵用于频繁刷新的小字24 点阵用于偶尔打印的标题。还有一种做法是把字库压缩。点阵字模有很多连续 0用 RLE 压缩能压到一半左右。但解压需要 CPU 时间在低端 MCU 上可能得不偿失。我一般只在 Flash 实在放不下时才考虑压缩而且优先压缩 24 点阵因为 24 点阵用得少。3.4 打印和显示共用一套索引的工程组织如果同一个项目既要显示又要打印建议把索引算法和字模读取封装成一个独立模块显示和打印都调这个模块。模块对外提供两个函数font_get_char_bitmap(ch, size, buf)和font_get_char_width(size)。显示层拿到点阵后自己决定怎么刷屏打印层拿到点阵后自己决定怎么对齐点行。这样索引算法只写一遍避免显示和打印各写一套导致不一致。模块内部可以维护一个当前字库文件的句柄支持从内部 Flash 或外部 Flash 读取。如果字库文件有多个比如 16 点阵一个文件、24 点阵一个文件就用两个句柄。读取时根据 size 参数选择对应的句柄和每字字节数。这样上层完全不需要关心字库存在哪里、怎么索引只管拿点阵数据。注意如果显示和打印用的字模方向不同比如显示用横向、打印用纵向不要在索引模块里做转换而是在各自的驱动层转换。索引模块只负责按统一格式输出转换逻辑放在最接近硬件的地方方便单独调试。4. 避坑与排查索引偏移、取模方向、字库缺失的典型翻车现场4.1 现象所有汉字整体偏移一位显示成下一个字原因低字节索引没有跳过 0x7F。GBK 低字节范围是 0x40–0xFE但 0x7F 不是有效码位。如果直接用low - 0x40算位号当 low 大于 0x7F 时位号会比实际多 1导致从该区开始所有汉字都偏移一位。解决在索引算法里加判断low 0x7F时减 0x41否则减 0x40。这个判断在 2.2 节的代码里已经体现。验证方法取「啊」字GBK 编码 B0 A1看渲染出来是不是「啊」。如果显示成「阿」就是偏移了一位。4.2 现象部分汉字显示为空白或乱码但常用字正常原因字库文件不完整缺少某些区的数据。有些字库工具生成时只包含常用字区比如只生成 GB2312 范围内的字而 GBK 扩展区的字没有。如果设备输入了扩展区的汉字索引算出来的偏移超出了文件大小读出来就是空白或随机数据。解决先确认字库文件大小是否覆盖全 GBK 范围。16 点阵全字库大约 766 KB24 点阵大约 1.7 MB。如果文件明显偏小就是缺数据。另外在读取字模前加一个边界检查偏移加单字字节数不能超过文件大小超过就返回空白字模或者默认字模避免读越界导致 HardFault。4.3 现象显示正常但打印出来是镜像或旋转 90 度原因取模方向和输出设备不匹配。显示驱动可能做了行列转换打印驱动没做或者反过来。热敏打印机通常要求横向取模、高位在前但有些机芯是低位在前。解决先用「一」字和「十」字做测试。「一」字横向取模时中间一行是全 1纵向取模时每列都有数据。「十」字横向取模时中间一行和中间一列有数据。如果打印出来「一」字变成竖线就是取模方向反了。如果文字左右镜像就是位序反了把每个字节的位反转即可。4.4 现象24 点阵打印时行与行之间错位文字被拉长原因24 点阵每行 3 字节但打印行缓冲按 2 字节对齐导致每行多读或少读一个字节。或者打印头的点行宽度不是 8 的倍数行缓冲末尾有填充位没处理好。解决确认打印头一行多少点换算成字节数。384 点就是 48 字节576 点就是 72 字节。行缓冲按这个宽度分配每个汉字每行贡献 3 字节按顺序填入。填完后如果行缓冲没满剩余位补 0。不要用sizeof去算行缓冲要用打印头点数除以 8。4.5 现象字模文件烧进 Flash 后前几个字正常后面全是乱码原因烧录工具按 4 字节对齐或者加了文件头导致字库数据在 Flash 里的起始地址和索引算法假设的不一致。或者烧录时做了字节序转换把大端转成了小端。解决用烧录工具读回 Flash 的前 64 字节和原始字库文件的前 64 字节对比。如果不一样检查烧录工具的地址设置和字节序选项。常见做法是把字库文件当作二进制块烧到固定地址索引算法里的基地址要加上这个偏移。如果烧录工具自动加了文件头要么去掉文件头要么在索引算法里加上文件头长度。5. 进阶用工具批量生成字模并做完整性校验5.1 字模生成工具的选型与参数设置这份资源里带的工具可以批量生成 GBK 点阵字模。常见做法是选好字体比如宋体、仿宋、点阵大小16 或 24、取模方式横向/纵向、字节序高位在前/低位在前然后导出二进制文件。导出时一定要勾选「全 GBK 字符集」不要只选 GB2312否则扩展区汉字会缺失。另外如果工具支持生成索引表最好一起生成方便和线性索引算法做交叉验证。我一般会生成两份一份横向取模用于显示一份纵向取模用于打印。如果工具不支持同时生成两种就生成一份然后在驱动层转换。转换代码不复杂但要注意 24 点阵的转换比 16 点阵麻烦因为每行 3 字节不是 2 的幂位操作容易出错。建议先用小规模数据在 PC 上验证转换逻辑再放到 MCU 上跑。5.2 用校验和与抽样渲染验证字库完整性字库文件动辄几百 KB 到 1.7 MB烧录过程中可能出错。我习惯在生成字库后算一个 CRC32烧录后再从 Flash 读回算一次对比是否一致。如果不一致说明烧录有问题重新烧。另外抽样渲染几个关键汉字比如「中」「国」「一」「十」「啊」「龘」覆盖常用区和扩展区确认索引和取模都正确。下面这段 Python 脚本可以算字库文件的 CRC32 并抽样渲染。# -*- coding: utf-8 -*- # 字库完整性校验CRC32 抽样渲染 import zlib FONT_FILE gbk16.bin BYTES_PER_CHAR 32 def crc32_file(path): 计算文件 CRC32 crc 0 with open(path, rb) as f: while True: chunk f.read(4096) if not chunk: break crc zlib.crc32(chunk, crc) return crc 0xFFFFFFFF def sample_check(chars): 抽样检查字模是否存在且非全零 with open(FONT_FILE, rb) as f: for ch in chars: data ch.encode(gbk) if len(data) ! 2: print(f{ch}: 非 GBK 双字节) continue high, low data[0], data[1] if high 0x81 or high 0xFE or low 0x40 or low 0xFE or low 0x7F: print(f{ch}: 无效 GBK 码位) continue high_idx high - 0x81 low_idx low - 0x41 if low 0x7F else low - 0x40 idx high_idx * 190 low_idx offset idx * BYTES_PER_CHAR f.seek(0, 2) file_size f.tell() if offset BYTES_PER_CHAR file_size: print(f{ch}: 偏移越界字库可能不完整) continue f.seek(offset) bitmap f.read(BYTES_PER_CHAR) if all(b 0 for b in bitmap): print(f{ch}: 字模全零可能缺失) else: print(f{ch}: 字模存在非零字节数 {sum(1 for b in bitmap if b ! 0)}) if __name__ __main__: print(fCRC32: {crc32_file(FONT_FILE):08X}) sample_check([中, 国, 一, 十, 啊, 龘])逻辑说明crc32_file分块读取文件算 CRC32避免一次性加载大文件。sample_check对每个抽样汉字算索引偏移检查是否越界、字模是否全零。全零通常意味着该字缺失或者取模工具没生成。参数说明FONT_FILE改成实际字库文件名BYTES_PER_CHAR根据点阵大小调整。抽样汉字列表可以按项目实际用到的字调整但建议保留一个扩展区汉字比如「龘」用来验证字库是否覆盖全 GBK。5.3 把校验流程固化到构建脚本里每次重新生成字库后手动跑校验容易忘我后来把校验脚本挂到构建流程里。生成字库后自动算 CRC32和预期值对比不一致就报错停止构建。抽样渲染也可以自动化把渲染结果和预期点阵图对比虽然麻烦一点但能挡住大部分低级错误。从那以后我每次换字库或者改取模参数都强制走一遍 CRC32 加抽样渲染确认无误再烧录。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站