资讯详情

GBK点阵字模:16×16与24×24字模索引算法及嵌入式显示打印实战

📅 2026/10/29 1:06:49 | 华诺云谱 👁 阅读
GBK点阵字模:16×16与24×24字模索引算法及嵌入式显示打印实战
简介这份资源是个人整理的GBK汉字点阵字库面向打印机固件开发、LCD汉字显示及嵌入式字模提取等场景解决专业字库难找、编码与字模对应关系不清晰的问题。包内包含GBK1.0中22046个汉字的16×16与24×24两套点阵字模并附两份GBK编码表分别按分区和编码顺序排列便于快速定位与校验。资源共21个文件以bin字库、txt编码表、exe字模工具、bpl与ptl组件、c与h转码源码等为主压缩包约3.41MB另含GBK转BIG5、GBK转Unicode函数及字模提取、牧码字模、PCtoLCD2002等工具可直接用于显示与打印开发。目前已有1665人学习下载。读者可据此获得完整字模数据、编码索引算法、转码函数与提取工具减少自行搜集与验证字库的时间成本。1. GBK 点阵字模从嵌入式屏到热敏打印的刚需做嵌入式显示或者小票打印的兄弟大概率都碰过同一个需求屏幕上要显示中文打印机要吐中文小票但主控 Flash 就那么点大跑不动 TrueType 字体渲染也不想挂一个完整的字库芯片。这时候 GBK 点阵字模就是最务实的选择——16×16 够屏幕用24×24 够打印用一个字模就是一段固定字节取出来直接往 LCD 或热敏头上怼没有抗锯齿、没有矢量轮廓、没有浮点运算。这个标题讲的就是这套东西GBK 编码下的 16×16 和 24×24 点阵字模数据怎么组织、怎么根据汉字区位码算出偏移、怎么用工具生成和验证。适合做 STM32 裸机 UI 的、做 POS 机打印的、做工业 HMI 的以及任何需要在资源受限环境里显示中文的人。索引算法是核心工具是加速器两者缺一不可。2. 字模数据怎么组织从 GBK 区位码到字节偏移2.1 GBK 编码与区位码的映射关系GBK 是 GB2312 的超集GB2312 本身是双字节编码第一字节范围 0xA1–0xF7第二字节范围 0xA1–0xFE。区位码的概念来自 GB2312区号 第一字节 - 0xA0位号 第二字节 - 0xA0。比如“中”字的 GBK 编码是 0xD6D0区号 0xD6 - 0xA0 0x36 54位号 0xD0 - 0xA0 0x30 48。GBK 扩展了更多汉字第一字节范围扩到 0x81–0xFE第二字节 0x40–0xFE跳过 0x7F。但做点阵字模时绝大多数方案只覆盖 GB2312 的 6763 个汉字因为 GBK 扩展区的字模数据量太大而且很多生僻字根本用不上。常见做法是字模文件按 GB2312 区位码顺序排列索引时只处理第一字节 0xA1–0xF7、第二字节 0xA1–0xFE 的范围。注意如果你的系统需要显示 GBK 扩展汉字比如“镕”“堃”要么单独做扩展字模要么在取模失败时回退到默认字符别让程序直接跑飞。2.2 16×16 与 24×24 字模的字节排列16×16 点阵每个汉字 16 行每行 16 个像素。按字节对齐每行 2 字节总共 32 字节。常见的排列方式是“横向取模、字节正序”第一字节是第 1 行左边 8 像素第二字节是第 1 行右边 8 像素第三字节是第 2 行左边 8 像素以此类推。每个字节的 bit7 对应最左边像素bit0 对应最右边。24×24 点阵每行 24 像素按字节对齐需要 3 字节总共 72 字节。排列逻辑一样每行 3 字节共 24 行。有些工具默认“纵向取模”或者“字节倒序”这会导致显示出来是乱码或者镜像必须和你的驱动代码匹配。点阵规格每行字节数总字节数常见取模方式16×16232横向取模字节正序24×24372横向取模字节正序32×324128横向取模字节正序2.3 索引算法用区位码算偏移假设字模文件按区位码顺序排列从区号 1 到 94位号 1 到 94。那么偏移公式是// 计算 GBK 汉字在字模文件中的偏移 // gbk_high: GBK 第一字节, gbk_low: GBK 第二字节 // font_size: 每个字模占用的字节数 (16x16 为 32, 24x24 为 72) uint32_t get_font_offset(uint8_t gbk_high, uint8_t gbk_low, uint16_t font_size) { // 区号: 0xA1 对应区 1, 所以减 0xA0 uint8_t qu gbk_high - 0xA0; uint8_t wei gbk_low - 0xA0; // 区位码从 1 开始, 所以减 1 // 每区 94 个位 uint32_t index (qu - 1) * 94 (wei - 1); return index * font_size; }这段代码的逻辑很直接先算出区号和位号然后转成从 0 开始的线性索引最后乘以每个字模的字节数。参数font_size根据点阵规格传入16×16 传 3224×24 传 72。如果你的字模文件只包含一级汉字区 16–55和二级汉字区 56–87那区号范围要相应调整偏移公式里的基数也要改。提示有些字模文件把符号区区 1–9也包含进去有些只从区 16 开始。拿到字模文件后先用十六进制编辑器看一眼开头确认起始区号否则所有汉字都会偏移。3. 生成字模的实操路径工具选择与参数配置3.1 用开源工具生成 GB2312 全字库常见的做法是用font2c或者PCtoLCD2002这类工具。PCtoLCD2002是 Windows 下的老牌工具支持 GB2312 全字库导出但界面比较老旧。更现代的方式是用 Python 脚本调用freetype库自己生成可控性更强。# 用 freetype-py 生成 GB2312 16x16 点阵字模 import freetype def generate_gb2312_font(font_path, font_size, output_path): face freetype.Face(font_path) face.set_pixel_sizes(0, font_size) # GB2312 区 16-87, 位 1-94 with open(output_path, wb) as f: for qu in range(16, 88): for wei in range(1, 95): # 构造 GBK 编码 gbk_high qu 0xA0 gbk_low wei 0xA0 gbk_bytes bytes([gbk_high, gbk_low]) try: char gbk_bytes.decode(gbk) except UnicodeDecodeError: # 无效编码, 填充空白 f.write(b\x00 * (font_size * font_size // 8)) continue face.load_char(char, freetype.FT_LOAD_RENDER | freetype.FT_LOAD_TARGET_MONO) bitmap face.glyph.bitmap # 按 16x16 横向取模, 每行 2 字节 for row in range(font_size): byte_val 0 for col in range(font_size): # 从 bitmap 取像素 pixel 0 if row bitmap.rows and col bitmap.width: byte_index row * bitmap.pitch col // 8 bit_index 7 - (col % 8) pixel (bitmap.buffer[byte_index] bit_index) 1 byte_val (byte_val 1) | pixel if (col 1) % 8 0: f.write(bytes([byte_val])) byte_val 0这段脚本的核心逻辑遍历 GB2312 的区 16 到 87、位 1 到 94构造 GBK 编码用 freetype 渲染成单色位图然后按横向取模、每 8 像素一个字节的方式写入文件。参数font_size控制点阵大小16 就是 16×1624 就是 24×24。FT_LOAD_TARGET_MONO确保渲染结果是单色位图不带灰度。注意freetype 渲染出来的字形可能有 1 像素的偏移需要根据实际字体调整face.glyph.bitmap_left和bitmap_top否则字会偏上或偏左。3.2 验证字模数据的三个方法生成完字模文件后别急着烧到板子上。先用这三个方法验证方法一用十六进制编辑器看开头。打开字模文件找到“啊”字的位置。“啊”的 GBK 编码是 0xB0A1区号 0xB0 - 0xA0 16位号 0xA1 - 0xA0 1。偏移 (16-1)*94 (1-1) 1410乘以 32 字节 45120 字节。跳到这个位置应该能看到“啊”的点阵数据。方法二写一个简单的 Python 脚本把字模渲染成图片。读 32 字节按 16×16 展开成黑白像素用 PIL 画出来。from PIL import Image def render_font(font_data, offset, width16, height16): img Image.new(1, (width, height), 0) pixels img.load() bytes_per_row width // 8 for row in range(height): for byte_idx in range(bytes_per_row): byte_val font_data[offset row * bytes_per_row byte_idx] for bit in range(8): col byte_idx * 8 bit if col width: pixel (byte_val (7 - bit)) 1 pixels[col, row] pixel return img方法三在目标硬件上跑一个测试循环。把字模文件放到 Flash 的固定地址写一个函数遍历所有汉字在 LCD 上逐个显示。如果某个字显示为空白或者乱码说明该位置的编码映射有问题。3.3 打印场景下的 24×24 字模特殊处理热敏打印和 LCD 显示不一样。LCD 是逐像素刷打印是逐行加热。24×24 字模在打印时通常需要按行取出每行 3 字节然后根据打印头的宽度拼接成一行数据。如果打印头是 384 点48 字节一行能放 16 个 24×24 汉字。// 打印一行 24x24 汉字 void print_font_line(uint8_t *font_data, uint8_t *print_buffer, uint16_t font_offset) { // font_data: 字模文件基地址 // print_buffer: 打印行缓冲区, 至少 48 字节 // font_offset: 当前字模的偏移 for (int row 0; row 24; row) { // 每个汉字每行 3 字节 uint8_t b1 font_data[font_offset row * 3]; uint8_t b2 font_data[font_offset row * 3 1]; uint8_t b3 font_data[font_offset row * 3 2]; // 拼接到打印缓冲区, 具体位置根据打印头排列调整 print_buffer[row * 48 0] b1; print_buffer[row * 48 1] b2; print_buffer[row * 48 2] b3; } }这段代码只是示意实际打印时还要考虑打印头的字节顺序、加热时间、走纸速度。但核心逻辑就是从字模文件按行取字节拼到打印缓冲区然后一次性推给打印头。4. 避坑与排查字模显示翻车的五个血泪经验4.1 现象所有汉字都显示为乱码或镜像原因取模方式和驱动代码不匹配。工具默认可能是“纵向取模”或者“字节倒序”而驱动代码按“横向取模、字节正序”解析。解决在取模工具里把取模方式改成“横向取模、字节正序”或者修改驱动代码的解析逻辑。最快的验证方法取“一”字GBK 0xD2BB的字模看第一行是不是 0x00 0x00第二行是不是 0xFF 0xFF。如果不是取模方式肯定不对。4.2 现象部分汉字显示为空白原因字模文件不包含该汉字或者索引算法算出的偏移超出了文件范围。常见于 GBK 扩展汉字或者符号区。解决在取模函数里加边界检查如果偏移超出文件大小返回默认字符比如“”的字模。同时确认字模文件是否包含符号区区 1–9如果不包含区号小于 16 的编码要特殊处理。4.3 现象24×24 字模在打印时错位原因打印头的字节排列和字模的字节排列不一致。有些打印头是每 8 点一个字节但字节顺序是反的有些是每行 48 字节但汉字之间需要插入间隔。解决先用一个简单的测试图案比如全黑方块确认打印头的字节顺序然后调整字模拼接时的字节顺序。如果打印头是 384 点一行 48 字节16 个汉字刚好填满不需要额外间隔。4.4 现象字模文件太大Flash 放不下原因GB2312 全字库 16×16 是 6763 × 32 216KB24×24 是 6763 × 72 486KB。如果主控 Flash 只有 512KB放完字库就没空间放代码了。解决只取常用字。一级汉字 3755 个16×16 是 120KB24×24 是 270KB。如果还不够可以只取前 1000 个常用字或者用外部 SPI Flash 存字库通过 SPI 读取。4.5 现象字模显示有锯齿或笔画断裂原因字体本身的问题。有些字体在 16×16 点阵下笔画会粘连或者断裂尤其是宋体。仿宋字体在 24×24 下表现更好但 16×16 下可能不如黑体。解决16×16 点阵优先用黑体或点阵专用字体24×24 可以用仿宋或宋体。如果必须用某个字体可以在取模工具里开启“抗锯齿”或者“笔画加粗”但点阵字模本身不支持灰度只能通过调整阈值来改善。5. 进阶技巧用 Python 做字模索引与动态加载5.1 在 PC 端做字模预览工具调试字模时每次烧到板子上看太慢。我一般会在 PC 端写一个 Python 脚本直接读字模文件输入汉字输出点阵图。这样改字体、改取模方式时几秒钟就能看到效果。import sys from PIL import Image def load_font(file_path): with open(file_path, rb) as f: return f.read() def get_offset(gbk_bytes, font_size): qu gbk_bytes[0] - 0xA0 wei gbk_bytes[1] - 0xA0 return ((qu - 1) * 94 (wei - 1)) * font_size def preview(char, font_data, font_size16): gbk_bytes char.encode(gbk) offset get_offset(gbk_bytes, font_size * font_size // 8) width height font_size bytes_per_row width // 8 img Image.new(1, (width, height), 0) pixels img.load() for row in range(height): for byte_idx in range(bytes_per_row): byte_val font_data[offset row * bytes_per_row byte_idx] for bit in range(8): col byte_idx * 8 bit if col width: pixels[col, row] (byte_val (7 - bit)) 1 img img.resize((width * 10, height * 10), Image.NEAREST) img.show() if __name__ __main__: font_data load_font(gb2312_16x16.fnt) preview(中, font_data, 16)这个脚本的逻辑读字模文件根据汉字 GBK 编码算偏移取 32 字节展开成 16×16 像素放大 10 倍显示。参数font_size控制点阵大小16 或 24 都支持。改字体或者取模方式时只需要重新生成字模文件脚本不用改。5.2 在嵌入式端做动态加载如果字模文件放在外部 SPI Flash可以用一个简单的缓存机制最近用过的 16 个汉字字模缓存在 RAM 里取字模时先查缓存命中就直接用不命中再从 Flash 读。这样在显示连续文本时能减少 80% 以上的 Flash 读取次数。#define CACHE_SIZE 16 #define FONT_SIZE_16 32 typedef struct { uint16_t gbk_code; uint8_t data[FONT_SIZE_16]; uint8_t valid; } FontCache; FontCache cache[CACHE_SIZE]; uint8_t cache_idx 0; uint8_t* get_font_cached(uint16_t gbk_code) { // 查缓存 for (int i 0; i CACHE_SIZE; i) { if (cache[i].valid cache[i].gbk_code gbk_code) { return cache[i].data; } } // 未命中, 从 Flash 读 uint32_t offset get_font_offset(gbk_code 8, gbk_code 0xFF, FONT_SIZE_16); spi_flash_read(FONT_BASE_ADDR offset, cache[cache_idx].data, FONT_SIZE_16); cache[cache_idx].gbk_code gbk_code; cache[cache_idx].valid 1; uint8_t* result cache[cache_idx].data; cache_idx (cache_idx 1) % CACHE_SIZE; return result; }这段代码实现了一个简单的 FIFO 缓存。参数CACHE_SIZE根据 RAM 大小调整16 个缓存占 16 × 32 512 字节对大多数 MCU 来说可以接受。FONT_BASE_ADDR是字模在 SPI Flash 中的起始地址。5.3 字模压缩把 486KB 压到 200KB 以内24×24 全字库 486KB 确实大。如果 Flash 紧张可以用简单的行程编码RLE压缩。点阵字模里连续的空行和连续的黑块很多RLE 能压到 60% 左右。解压时在 RAM 里还原一个字的字模再送去显示或打印。// 简单的 RLE 解压: 每个字节的高 4 位是计数, 低 4 位是数据 void rle_decompress(uint8_t *src, uint8_t *dst, uint16_t dst_len) { uint16_t src_idx 0, dst_idx 0; while (dst_idx dst_len) { uint8_t count (src[src_idx] 4) 1; uint8_t data src[src_idx] 0x0F; // 展开成完整字节 for (int i 0; i count dst_idx dst_len; i) { dst[dst_idx] (data 4) | data; } src_idx; } }这个 RLE 变种把每个字节拆成计数和数据适合点阵字模里大量重复的 0x00 和 0xFF。压缩率取决于字体黑体比宋体压缩率低因为黑体笔画粗连续黑块少。我自己的习惯是先在 PC 端用 Python 脚本把字模文件跑一遍 RLE 压缩看压缩率能不能接受。如果压到 200KB 以内就直接用压缩格式存 Flash解压代码也就几十行。如果压不下来就老老实实换更大 Flash 或者只取常用字。这个方案值不值得做取决于你的 Flash 余量和汉字数量需求——如果只需要 1000 个常用字不压缩也才 72KB没必要折腾。希望帮到你。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑