配置 PWM ADC LCD

This commit is contained in:
2026-07-14 17:51:39 +08:00
parent 0a5c478a42
commit 2d0d000a38
67 changed files with 33357 additions and 911 deletions

394
tools/font_gen.py Normal file
View 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='CJKGB2312 全部汉字')
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
View 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()