配置 PWM ADC LCD
This commit is contained in:
394
tools/font_gen.py
Normal file
394
tools/font_gen.py
Normal file
@@ -0,0 +1,394 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
STM32 ST7735S LCD 字模生成工具(Pillow 版)
|
||||
- ASCII 8×16:生成 lcd_data.c
|
||||
- CJK 16×16:生成 hz16_data.c + hz16_data.h(含二分查找函数)
|
||||
|
||||
用法:
|
||||
# ASCII
|
||||
python font_gen.py --font simsun --size 56 --threshold 64
|
||||
|
||||
# CJK 常用汉字(GB2312 一级,约 3755 字)
|
||||
python font_gen.py --cjk --font simsun --size 32 --threshold 64
|
||||
|
||||
# CJK + 标点
|
||||
python font_gen.py --cjk --punct
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import argparse
|
||||
import struct
|
||||
|
||||
try:
|
||||
from PIL import Image, ImageFont, ImageDraw
|
||||
except ImportError:
|
||||
print("请安装 Pillow: pip install Pillow", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# ── 通用渲染 ──
|
||||
|
||||
def render_char(font, char: str, w: int, h: int, scale: int = 1,
|
||||
threshold: int = 64) -> list:
|
||||
"""
|
||||
在 w*scale × h*scale 画布上渲染字符,缩放到 w×h
|
||||
返回逐行字节列表(MSB 优先)
|
||||
"""
|
||||
bw, bh = w * scale, h * scale
|
||||
img = Image.new("L", (bw, bh), 0)
|
||||
draw = ImageDraw.Draw(img)
|
||||
|
||||
bbox = draw.textbbox((0, 0), char, font=font)
|
||||
tw = bbox[2] - bbox[0]
|
||||
th = bbox[3] - bbox[1]
|
||||
cx = (bw - tw) // 2 - bbox[0]
|
||||
cy = (bh - th) // 2 - bbox[1]
|
||||
draw.text((cx, cy), char, font=font, fill=255)
|
||||
|
||||
small = img.resize((w, h), Image.LANCZOS)
|
||||
pixels = list(small.getdata())
|
||||
|
||||
bytes_per_row = (w + 7) // 8
|
||||
result = []
|
||||
for r in range(h):
|
||||
for br in range(bytes_per_row):
|
||||
byte_val = 0
|
||||
for c in range(8):
|
||||
col = br * 8 + c
|
||||
if col < w and pixels[r * w + col] >= threshold:
|
||||
byte_val |= (1 << (7 - c))
|
||||
result.append(byte_val)
|
||||
return result
|
||||
|
||||
|
||||
# ── ASCII 8×16 ──
|
||||
|
||||
def generate_ascii(output_path: str, font_path: str, size: int,
|
||||
threshold: int, scale: int = 4):
|
||||
font = ImageFont.truetype(font_path, size)
|
||||
chars = list(range(32, 127))
|
||||
N = len(chars)
|
||||
|
||||
lines = [
|
||||
'#include "lcd_data.h"',
|
||||
'/*ASCII字模数据*********************/',
|
||||
'',
|
||||
'/*宽8像素,高16像素*/',
|
||||
f'/* 字体: {os.path.basename(font_path)}, 字号: {size}, 阈值: {threshold} */',
|
||||
'const uint8_t LCD_F8x16[][16] =',
|
||||
'{',
|
||||
]
|
||||
|
||||
for idx, ch in enumerate(chars):
|
||||
if ch == ord('_'):
|
||||
bitmap = [0x00] * 15 + [0x7F]
|
||||
else:
|
||||
bitmap = render_char(font, chr(ch), 8, 16, scale, threshold)
|
||||
|
||||
show = chr(ch) if 32 <= ch <= 126 else '?'
|
||||
comma = ',' if idx < N - 1 else ''
|
||||
line1 = ', '.join(f'0x{b:02X}' for b in bitmap[:8])
|
||||
line2 = ', '.join(f'0x{b:02X}' for b in bitmap[8:])
|
||||
lines.append(f' {line1},')
|
||||
lines.append(f' {line2}{comma} // {show} {idx}')
|
||||
if (idx + 1) % 10 == 0:
|
||||
print(f" ASCII 进度: {idx+1}/{N}")
|
||||
|
||||
lines.append('};')
|
||||
lines.append('')
|
||||
lines.append('/*宽6像素,高8像素(未使用)*/')
|
||||
lines.append('const uint8_t LCD_F6x8[][6] =')
|
||||
lines.append('{')
|
||||
for idx, ch in enumerate(chars):
|
||||
show = chr(ch) if 32 <= ch <= 126 else '?'
|
||||
comma = ',' if idx < N - 1 else ''
|
||||
lines.append(f' 0x00,0x00,0x00,0x00,0x00,0x00{comma} // {show} {idx}')
|
||||
lines.append('};')
|
||||
lines.append('')
|
||||
|
||||
content = '\n'.join(lines)
|
||||
with open(output_path, 'w', encoding='utf-8', newline='\n') as f:
|
||||
f.write(content)
|
||||
print(f"已生成 ASCII 字模: {output_path}")
|
||||
|
||||
|
||||
# ── CJK 16×16 ──
|
||||
|
||||
CJK_W, CJK_H = 16, 16
|
||||
CJK_BYTES = CJK_H * ((CJK_W + 7) // 8) # 32
|
||||
|
||||
|
||||
def get_gb2312_level1_unicodes():
|
||||
"""GB2312 一级汉字(常用 ~3755 字)的 Unicode 码点"""
|
||||
out = []
|
||||
for u in range(0x4E00, 0x9FA6):
|
||||
try:
|
||||
b = chr(u).encode("gb2312")
|
||||
if len(b) == 2:
|
||||
qu = b[0] - 0xA1
|
||||
if 16 <= qu <= 55:
|
||||
out.append(u)
|
||||
except (UnicodeEncodeError, LookupError):
|
||||
pass
|
||||
return out
|
||||
|
||||
|
||||
def get_gb2312_all_unicodes():
|
||||
"""GB2312 全部汉字(一级+二级 ~6763 字)"""
|
||||
out = []
|
||||
for u in range(0x4E00, 0x9FA6):
|
||||
try:
|
||||
b = chr(u).encode("gb2312")
|
||||
if len(b) == 2:
|
||||
out.append(u)
|
||||
except (UnicodeEncodeError, LookupError):
|
||||
pass
|
||||
return out
|
||||
|
||||
|
||||
CJK_PUNCTUATION = [
|
||||
0x00B7, 0x2014, 0x2018, 0x2019, 0x201C, 0x201D, 0x2026,
|
||||
0x3000, 0x3001, 0x3002, 0x300A, 0x300B, 0x300C, 0x300D,
|
||||
0x300E, 0x300F, 0x3010, 0x3011, 0x3014, 0x3015,
|
||||
0xFF01, 0xFF02, 0xFF07, 0xFF08, 0xFF09, 0xFF0C, 0xFF0E,
|
||||
0xFF1A, 0xFF1B, 0xFF1F, 0xFF3B, 0xFF3D, 0xFF5B, 0xFF5D,
|
||||
]
|
||||
|
||||
CJK_FULLWIDTH_ASCII = list(range(0xFF01, 0xFF5F)) # 全角 ASCII
|
||||
|
||||
|
||||
def generate_cjk(base_path: str, font_path: str, size: int,
|
||||
threshold: int, scale: int = 2,
|
||||
mode: str = 'common', punct: bool = False):
|
||||
"""生成 CJK 16×16 字库 .c + .h"""
|
||||
|
||||
font = ImageFont.truetype(font_path, size)
|
||||
|
||||
if mode == 'common':
|
||||
unicodes = get_gb2312_level1_unicodes()
|
||||
print(f"CJK 范围: GB2312 一级汉字 ({len(unicodes)} 字)")
|
||||
elif mode == 'gb2312':
|
||||
unicodes = get_gb2312_all_unicodes()
|
||||
print(f"CJK 范围: GB2312 全部汉字 ({len(unicodes)} 字)")
|
||||
else:
|
||||
unicodes = []
|
||||
print("CJK 范围: 无")
|
||||
|
||||
if punct:
|
||||
extra = set(CJK_PUNCTUATION) | set(CJK_FULLWIDTH_ASCII)
|
||||
unicodes = sorted(set(unicodes) | extra)
|
||||
print(f"已加入 {len(extra)} 个中文标点/全角字符")
|
||||
|
||||
if not unicodes:
|
||||
print("未指定 CJK 范围,使用默认字符")
|
||||
unicodes = [ord(c) for c in "你好世界一二三"]
|
||||
|
||||
unicodes = sorted(set(unicodes))
|
||||
N = len(unicodes)
|
||||
|
||||
# 渲染
|
||||
bitmaps = []
|
||||
for i, u in enumerate(unicodes):
|
||||
ch = chr(u)
|
||||
bm = render_char(font, ch, CJK_W, CJK_H, scale, threshold)
|
||||
bitmaps.append(bm)
|
||||
if (i + 1) % 500 == 0:
|
||||
print(f" CJK 进度: {i+1}/{N}")
|
||||
|
||||
# ── 写 .c ──
|
||||
c_path = base_path + ".c"
|
||||
h_path = base_path + ".h"
|
||||
name_base = os.path.basename(base_path)
|
||||
guard = name_base.upper().replace(".", "_").replace("-", "_") + "_H"
|
||||
|
||||
with open(c_path, 'w', encoding='utf-8', newline='\n') as f:
|
||||
f.write(f'/* 16×16 CJK 字库,由 font_gen.py 生成 */\n\n')
|
||||
f.write(f'#include "{name_base}.h"\n\n')
|
||||
f.write(f'static const uint8_t cjk_bitmaps[][{CJK_BYTES}] = {{\n')
|
||||
for u, bm in zip(unicodes, bitmaps):
|
||||
hex_str = ', '.join(f'0x{x:02X}' for x in bm)
|
||||
try:
|
||||
comment = f' /* U+{u:04X} {chr(u)} */'
|
||||
except:
|
||||
comment = f' /* U+{u:04X} */'
|
||||
f.write(f' {{ {hex_str} }},{comment}\n')
|
||||
f.write('};\n\n')
|
||||
|
||||
f.write(f'static const uint16_t cjk_unicodes[{N}] = {{\n')
|
||||
for i in range(0, N, 16):
|
||||
chunk = unicodes[i:i+16]
|
||||
f.write(' ' + ', '.join(f'0x{u:04X}' for u in chunk) + ',\n')
|
||||
f.write('};\n\n')
|
||||
|
||||
f.write(f'const uint8_t* cjk_get_bitmap(uint16_t unicode)\n')
|
||||
f.write('{\n')
|
||||
f.write(f' int lo = 0, hi = {N};\n')
|
||||
f.write(' while (lo < hi) {\n')
|
||||
f.write(' int mid = (lo + hi) >> 1;\n')
|
||||
f.write(' if (cjk_unicodes[mid] < unicode) lo = mid + 1;\n')
|
||||
f.write(' else if (cjk_unicodes[mid] > unicode) hi = mid;\n')
|
||||
f.write(' else return cjk_bitmaps[mid];\n')
|
||||
f.write(' }\n')
|
||||
f.write(' return (const uint8_t*)0;\n')
|
||||
f.write('}\n')
|
||||
|
||||
print(f'已生成: {c_path}')
|
||||
|
||||
# ── 写 .h ──
|
||||
with open(h_path, 'w', encoding='utf-8', newline='\n') as f:
|
||||
f.write(f'/* 16×16 CJK 字库,由 font_gen.py 生成 */\n\n')
|
||||
f.write(f'#ifndef {guard}\n#define {guard}\n\n')
|
||||
f.write(f'#include <stdint.h>\n\n')
|
||||
f.write(f'#define CJK_BYTES_PER_CHAR {CJK_BYTES}\n')
|
||||
f.write(f'#define CJK_WIDTH {CJK_W}\n')
|
||||
f.write(f'#define CJK_HEIGHT {CJK_H}\n')
|
||||
f.write(f'#define CJK_NUM_CHARS {N}\n\n')
|
||||
f.write(f'/* 按 Unicode 码点查找 16×16 点阵,返回 {CJK_BYTES} 字节,未找到返回 NULL */\n')
|
||||
f.write(f'const uint8_t* cjk_get_bitmap(uint16_t unicode);\n\n')
|
||||
f.write(f'#endif /* {guard} */\n')
|
||||
|
||||
print(f'已生成: {h_path}')
|
||||
|
||||
|
||||
def generate_cjk_direct(base_path: str, font_path: str, size: int,
|
||||
threshold: int, scale: int,
|
||||
unicodes: list):
|
||||
"""用指定的 Unicode 列表生成 CJK 字库"""
|
||||
font = ImageFont.truetype(font_path, size)
|
||||
N = len(unicodes)
|
||||
|
||||
print(f"CJK 自定义字符: {N} 字: {''.join(chr(u) for u in unicodes[:50])}{'...' if N > 50 else ''}")
|
||||
|
||||
bitmaps = []
|
||||
for i, u in enumerate(unicodes):
|
||||
bm = render_char(font, chr(u), CJK_W, CJK_H, scale, threshold)
|
||||
bitmaps.append(bm)
|
||||
|
||||
# ── 写 .c ──
|
||||
c_path = base_path + ".c"
|
||||
h_path = base_path + ".h"
|
||||
name_base = os.path.basename(base_path)
|
||||
guard = name_base.upper().replace(".", "_").replace("-", "_") + "_H"
|
||||
|
||||
with open(c_path, 'w', encoding='utf-8', newline='\n') as f:
|
||||
f.write(f'/* 16×16 CJK 字库,由 font_gen.py 生成(自定义) */\n\n')
|
||||
f.write(f'#include "{name_base}.h"\n\n')
|
||||
f.write(f'static const uint8_t cjk_bitmaps[][{CJK_BYTES}] = {{\n')
|
||||
for u, bm in zip(unicodes, bitmaps):
|
||||
hex_str = ', '.join(f'0x{x:02X}' for x in bm)
|
||||
f.write(f' {{ {hex_str} }}, /* U+{u:04X} {chr(u)} */\n')
|
||||
f.write('};\n\n')
|
||||
|
||||
f.write(f'static const uint16_t cjk_unicodes[{N}] = {{\n')
|
||||
for i in range(0, N, 16):
|
||||
chunk = unicodes[i:i+16]
|
||||
f.write(' ' + ', '.join(f'0x{u:04X}' for u in chunk) + ',\n')
|
||||
f.write('};\n\n')
|
||||
|
||||
f.write(f'const uint8_t* cjk_get_bitmap(uint16_t unicode)\n')
|
||||
f.write('{\n')
|
||||
f.write(f' int lo = 0, hi = {N};\n')
|
||||
f.write(' while (lo < hi) {\n')
|
||||
f.write(' int mid = (lo + hi) >> 1;\n')
|
||||
f.write(' if (cjk_unicodes[mid] < unicode) lo = mid + 1;\n')
|
||||
f.write(' else if (cjk_unicodes[mid] > unicode) hi = mid;\n')
|
||||
f.write(' else return cjk_bitmaps[mid];\n')
|
||||
f.write(' }\n')
|
||||
f.write(' return (const uint8_t*)0;\n')
|
||||
f.write('}\n')
|
||||
|
||||
print(f'已生成: {c_path}')
|
||||
|
||||
# ── 写 .h ──
|
||||
with open(h_path, 'w', encoding='utf-8', newline='\n') as f:
|
||||
f.write(f'/* 16×16 CJK 字库,由 font_gen.py 生成(自定义) */\n\n')
|
||||
f.write(f'#ifndef {guard}\n#define {guard}\n\n')
|
||||
f.write(f'#include <stdint.h>\n\n')
|
||||
f.write(f'#define CJK_BYTES_PER_CHAR {CJK_BYTES}\n')
|
||||
f.write(f'#define CJK_WIDTH {CJK_W}\n')
|
||||
f.write(f'#define CJK_HEIGHT {CJK_H}\n')
|
||||
f.write(f'#define CJK_NUM_CHARS {N}\n\n')
|
||||
f.write(f'const uint8_t* cjk_get_bitmap(uint16_t unicode);\n\n')
|
||||
f.write(f'#endif /* {guard} */\n')
|
||||
|
||||
print(f'已生成: {h_path}')
|
||||
|
||||
|
||||
# ── 主入口 ──
|
||||
|
||||
def _resolve_font(name_or_path: str) -> str:
|
||||
if os.path.isfile(name_or_path):
|
||||
return name_or_path
|
||||
font_dir = r'C:\Windows\Fonts'
|
||||
key = name_or_path.lower()
|
||||
known = {
|
||||
'consolas': 'consola.ttf', 'arial': 'arial.ttf',
|
||||
'tahoma': 'tahoma.ttf', 'simsun': 'simsun.ttc',
|
||||
'songti': 'simsun.ttc', '宋体': 'simsun.ttc',
|
||||
'simhei': 'simhei.ttf', '黑体': 'simhei.ttf',
|
||||
'yahei': 'msyh.ttc', '微软雅黑': 'msyh.ttc',
|
||||
}
|
||||
if key in known:
|
||||
full = os.path.join(font_dir, known[key])
|
||||
if os.path.isfile(full):
|
||||
return full
|
||||
for f in os.listdir(font_dir):
|
||||
if f.lower().startswith(key) and f.lower().endswith(('.ttf', '.ttc')):
|
||||
return os.path.join(font_dir, f)
|
||||
# fallback
|
||||
fallback = os.path.join(font_dir, 'simsun.ttc')
|
||||
if os.path.isfile(fallback):
|
||||
return fallback
|
||||
return name_or_path
|
||||
|
||||
|
||||
def main():
|
||||
p = argparse.ArgumentParser(description='字模生成工具')
|
||||
p.add_argument('--font', default='simsun', help='字体名称或路径')
|
||||
p.add_argument('--size', type=int, default=64, help='渲染字号')
|
||||
p.add_argument('--threshold', type=int, default=50, help='二值化阈值(默认50)')
|
||||
p.add_argument('--scale', type=int, default=4, help='缩放倍率')
|
||||
|
||||
g = p.add_argument_group('ASCII 8×16')
|
||||
g.add_argument('--ascii', action='store_true', help='生成 ASCII 字模(默认)')
|
||||
|
||||
g = p.add_argument_group('CJK 16×16')
|
||||
g.add_argument('--cjk', action='store_true', help='生成 CJK 汉字字模')
|
||||
g.add_argument('--common', action='store_true', help='CJK:仅常用汉字(GB2312 一级)')
|
||||
g.add_argument('--gb2312', action='store_true', help='CJK:GB2312 全部汉字')
|
||||
g.add_argument('--punct', action='store_true', help='CJK:加入中文标点/全角字符')
|
||||
g.add_argument('--string', type=str, default=None, help='CJK:仅生成指定字符串中的字符(节省空间)')
|
||||
g.add_argument('--output', '-o', default=None, help='CJK 输出基名(不含扩展名)')
|
||||
|
||||
args = p.parse_args()
|
||||
|
||||
font_path = _resolve_font(args.font)
|
||||
print(f"字体: {font_path}")
|
||||
|
||||
proj_dir = os.path.abspath(os.path.join(
|
||||
os.path.dirname(__file__),
|
||||
'..', 'STM32F103C8T6', 'Drivers', 'BSP', 'LCD'))
|
||||
|
||||
if args.cjk:
|
||||
size = args.size or 48
|
||||
scale = args.scale or 3
|
||||
out_base = args.output or os.path.join(proj_dir, 'hz16_data')
|
||||
|
||||
if args.string is not None:
|
||||
unicodes = sorted(set(ord(c) for c in args.string))
|
||||
generate_cjk_direct(out_base, font_path, size, args.threshold, scale, unicodes)
|
||||
else:
|
||||
generate_cjk(out_base, font_path, size, args.threshold, scale,
|
||||
mode='common' if args.common else ('gb2312' if args.gb2312 else 'common'),
|
||||
punct=args.punct)
|
||||
else:
|
||||
# 默认 ASCII
|
||||
size = args.size or 56
|
||||
scale = args.scale or 4
|
||||
out = os.path.join(proj_dir, 'lcd_data.c')
|
||||
generate_ascii(out, font_path, size, args.threshold, scale)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
10
tools/to_escape.py
Normal file
10
tools/to_escape.py
Normal file
@@ -0,0 +1,10 @@
|
||||
#!/usr/bin/env python3
|
||||
import sys
|
||||
|
||||
def main():
|
||||
text = sys.argv[1] if len(sys.argv) > 1 else input("Enter Chinese: ")
|
||||
escaped = ''.join(f'\\x{b:02X}' for b in text.encode('utf-8'))
|
||||
print(escaped)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user