aaaaaaaaaaa

u8g2 库移植与使用说明(STM32G431 + ST7735S)

本文档面向本工程 Moisture_Meter_g431_lcd,完整说明 u8g2 图形库是如何移植到 STM32G431 + ST7735S 彩色 TFT 上、如何使用、以及本工程实际用到的每一个 u8g2 函数的作用。


目录

  1. 背景与总体思路
  2. u8g2 库的分层架构
  3. 工程目录结构与编译范围
  4. 移植原理与数据流
  5. 初始化流程
  6. 使用流程(两种模式)
  7. 移植层自定义接口
  8. u8g2 标准 API 函数详解
  9. u8x8 底层函数详解
  10. 字体系统
  11. 关键数据结构
  12. 常用常量与宏
  13. 常见问题与注意事项

1. 背景与总体思路

1.1 u8g2 是什么

u8g2 是一个开源的嵌入式单色(1bpp)图形库,广泛用于 OLED / LCD 点阵屏。它提供了统一的绘图 API(画点、线、矩形、圆、圆弧、字符串、位图等)以及大量字体,底层通过一个"显示驱动回调"适配各种具体屏幕。

1.2 本工程的特殊之处

u8g2 只支持单色屏,而本工程的 ST7735S 是 RGB565 彩色屏。因此移植时没有使用 u8g2 自带的任何显示驱动(u8g2_d_setup.c / u8g2_d_memory.c 里的驱动都没编译),而是:

  1. 让 u8g2 画在内部一块 1bpp 缓冲区(128×160/8 = 2560 字节);
  2. 自己编写一个 u8x8 显示驱动回调 u8x8_d_st7735_128x160();
  3. 在回调里把 1bpp 像素展开成 RGB565(只用前景色 / 背景色两种颜色);
  4. 复用工程已有的 TFT_LCD 驱动(LCD_SetWindow() + SPI2)把数据刷到屏上。

所以 u8g2 画出来的画面只包含前景色、背景色两种颜色(默认白字黑底),可通过 U8G2_Port_SetColors() 改成任意 RGB565 颜色。


2. u8g2 库的分层架构

u8g2 是典型的三层结构,本工程只用到其中与"画图"和"刷屏"相关的部分:

┌─────────────────────────────────────────────────────┐
│  u8g2 层(高层绘图 API)                              │
│  u8g2_ClearBuffer / DrawStr / DrawLine / DrawCircle… │
│  负责把图形画进 1bpp 缓冲区                            │
└──────────────────────────┬──────────────────────────┘
                           │ 内部持有
┌──────────────────────────▼──────────────────────────┐
│  u8x8 层(低层 tile/文字/协议)                       │
│  u8x8_Setup / u8x8_InitDisplay / 消息分发(msg)       │
│  负责把 8x8 tile 数据交给"显示驱动回调"                │
└──────────────────────────┬──────────────────────────┘
                           │ 回调函数指针
┌──────────────────────────▼──────────────────────────┐
│  显示驱动回调(本工程自写)                            │
│  u8x8_d_st7735_128x160()                            │
│  负责把 tile 展开成 RGB565 并通过 SPI 写屏             │
└─────────────────────────────────────────────────────┘

关键点:u8g2_t 和 u8x8_t 实际上是同一块内存——u8g2_t 是 u8x8_t 的超集。宏定义:

#define u8g2_GetU8x8(u8g2) ((u8x8_t *)(u8g2))

即 &g_u8g2 既可以被当作 u8g2_t * 使用(画图),也可以被当作 u8x8_t * 使用(底层控制)。


3. 工程目录结构与编译范围

u8g2 原始仓库 u8g2-master 非常庞大(含所有平台适配、所有字体、工具等),本工程只挑了一小部分放进 Keil 工程。

BSP/Transplant/U8G2/
├── u8g2_port.c                 ★ 本工程自写的移植层
├── u8g2_port.h                 ★ 移植层头文件
├── u8g2-master/                u8g2 官方仓库完整克隆(仅作参考,大部分不参与编译)
│   └── csrc/
│       ├── u8g2.h              ★ 主头文件(所有 API 声明)
│       ├── u8x8.h              ★ 底层头文件
│       ├── u8g2_*.c            ★ 按需编译的核心绘图/缓冲源码
│       └── u8x8_*.c            ★ 按需编译的底层源码
└── fonts/                      ★ 按需拷贝出来的 4 个 ASCII 字体 + 中文字模生成脚本
    ├── u8g2_font_6x10_tr.c
    ├── u8g2_font_8x13_tr.c
    ├── u8g2_font_10x20_tr.c
    ├── u8g2_font_helvB12_tr.c
    └── gen_cn_glyphs.py        (生成中文点阵的工具,不参与编译)

3.1 实际参与编译的 u8g2 源码(对照 .o 文件)

源文件 提供的能力
u8g2_buffer.c u8g2_ClearBuffer / u8g2_SendBuffer / FirstPage / NextPage
u8g2_cleardisplay.c u8g2_ClearDisplay
u8g2_setup.c u8g2_SetupBuffer / 初始化流程
u8g2_font.c u8g2_SetFont / u8g2_DrawStr / 字体度量
u8g2_ll_hvline.c 低层水平/垂直刷像素 u8g2_ll_hvline_vertical_top_lsb
u8g2_hvline.c u8g2_DrawHLine / u8g2_DrawVLine / u8g2_DrawHVLine / u8g2_DrawPixel
u8g2_box.c u8g2_DrawBox / u8g2_DrawFrame / 圆角矩形
u8g2_line.c u8g2_DrawLine
u8g2_circle.c u8g2_DrawCircle / DrawDisc / 椭圆
u8g2_arc.c u8g2_DrawArc
u8g2_bitmap.c u8g2_DrawBitmap / u8g2_DrawXBM / u8g2_DrawXBMP
u8g2_polygon.c u8g2_DrawTriangle / u8g2_ClearPolygonXY / u8g2_DrawPolygon
u8g2_intersection.c u8g2_IsIntersection / u8g2_GetIntersection
u8g2_kerning.c 字体紧凑排布相关(SetFontRefHeight*)

3.2 实际参与编译的 u8x8 源码

源文件 提供的能力
u8x8_setup.c u8x8_Setup / u8x8_InitDisplay / u8x8_InitInterface
u8x8_display.c u8x8_d_helper_display_setup_memory / u8x8_dummy_cb / 显示信息
u8x8_byte.c 字节级通信接口(本工程空实现)
u8x8_cad.c 命令/参数/数据(CAD)协议(本工程空实现)
u8x8_gpio.c GPIO/延时回调(本工程空实现)
u8x8_8x8.c 内置 8x8 字体字形
u8x8_string.c 低层字符串绘制
u8x8_u8toa.c / u8x8_u16toa.c 数字转字符串
u8x8_capture.c 帧捕获(调试用)

3.3 明确未编译的部分(重要)

  • u8g2_d_setup.c / u8g2_d_memory.c:u8g2 自带的全部屏幕驱动,本工程不用。
  • u8g2_fonts.c(37MB 大字体总表):不用,只按需引入 4 个字体。
  • u8g2_selection_list.c / u8g2_input_value.c / u8g2_message.c / u8g2_button.c / mui_u8g2.c:菜单/输入控件,本工程不用。

4. 移植原理与数据流

核心代码位于 u8g2_port.c,数据流如下:

u8g2 画图 API(DrawStr/DrawLine/...)
        ↓ 写入
1bpp 全缓冲 s_u8g2_buf[2560](vertical_top_lsb 布局)
        ↓ u8g2_SendBuffer() 遍历每个 8x8 tile
自定义显示驱动 u8x8_d_st7735_128x160()
        ↓ 收到 U8X8_MSG_DISPLAY_DRAW_TILE
st7735_draw_tile_block()
        ↓ 每个像素按 前景色/背景色 展开成 RGB565
LCD_SetWindow() + HAL_SPI_Transmit(LCD_SPI, ...) 刷屏

4.1 缓冲区布局(vertical_top_lsb)

  • 缓冲区总大小 U8G2_PORT_TILE_W * U8G2_PORT_TILE_H * 8 = 16*20*8 = 2560 字节。
  • 每个 tile 为 8×8 像素,占 8 字节。
  • 每字节 = 一列(8 个像素),bit0 对应 tile 内最上面一行,bit7 对应最下面一行。
  • 因此 tile_ptr[col] 就是"第 col 列的 8 个像素",与 u8g2_ll_hvline_vertical_top_lsb 布局完全匹配。

4.2 自定义显示驱动回调

static uint8_t u8x8_d_st7735_128x160(u8x8_t *u8x8, uint8_t msg,
                                      uint8_t arg_int, void *arg_ptr);

它按消息(msg)分发处理,各消息含义:

msg 处理方式
U8X8_MSG_DISPLAY_SETUP_MEMORY 调用 u8x8_d_helper_display_setup_memory() 把 u8x8_display_info_st7735_128x160 挂上,告诉 u8g2 屏幕是 16×20 tile(128×160 像素)
U8X8_MSG_DISPLAY_INIT 调用已有的 LCD_Init() 初始化 ST7735S(不重复写复位序列)
U8X8_MSG_DISPLAY_DRAW_TILE 把 arg_ptr 指向的 u8x8_tile_t 展开成 RGB565 并写屏(核心)
U8X8_MSG_DISPLAY_SET_POWER_SAVE 故意不处理,避免与工程低功耗流程冲突
U8X8_MSG_DISPLAY_REFRESH 数据已在 DRAW_TILE 里直接写屏,无需额外操作
其它 返回 1

4.3 1bpp → RGB565 的展开逻辑

st7735_draw_tile_block() 一次处理一块 8 行高的 tile:

  1. 用 LCD_SetWindow(x, y, x+w-1, y+7) 开一个整块窗口;
  2. 拉低 CS、置 RS=1(数据模式);
  3. 逐行(row 0..7)扫描,mask = 1 << row:
    • 像素为 1 → 写入前景色 s_fg_h/s_fg_l;
    • 像素为 0 → 写入背景色 s_bg_h/s_bg_l;
  4. 每行用一次 HAL_SPI_Transmit() 流式发送,最后拉高 CS。

前景/背景色的高、低字节在 U8G2_Port_SetColors() 里预先拆好(s_fg_h/s_fg_l/s_bg_h/s_bg_l),避免在刷屏内循环做移位运算。


5. 初始化流程

对外入口是 U8G2_Port_Init(),内部调用 u8g2_port_setup(U8G2_PORT_TILE_H):

static uint8_t u8g2_port_setup(uint8_t tile_rows)
{
    memset(&g_u8g2, 0, sizeof(g_u8g2));
    memset(s_u8g2_buf, 0, sizeof(s_u8g2_buf));

    /* 1) 挂自定义显示驱动;cad/byte/gpio 全部空实现 */
    u8x8_Setup(u8g2_GetU8x8(&g_u8g2),
               u8x8_d_st7735_128x160,   /* 显示驱动 */
               u8x8_dummy_cb,           /* cad */
               u8x8_dummy_cb,           /* byte */
               u8x8_dummy_cb);          /* gpio+delay */

    /* 2) 绑定 1bpp 缓冲区 */
    u8g2_SetupBuffer(&g_u8g2, s_u8g2_buf, tile_rows,
                     u8g2_ll_hvline_vertical_top_lsb, U8G2_R0);

    /* 3) 初始化面板(回调里会调 LCD_Init()) */
    u8g2_InitDisplay(&g_u8g2);
    u8g2_SetPowerSave(&g_u8g2, 0);

    /* 4) 清空缓冲 + 默认字体 */
    u8g2_ClearBuffer(&g_u8g2);
    u8g2_SetFont(&g_u8g2, U8G2_FONT_6X10);
    return 1;
}

调用关系:

U8G2_Port_Init()
 └─ u8g2_port_setup(20)
     ├─ u8x8_Setup(...)        → 记录回调函数指针
     ├─ u8g2_SetupBuffer(...)  → 记录缓冲区 & 画点回调 & 旋转方向
     ├─ u8g2_InitDisplay(...)  → 触发 U8X8_MSG_DISPLAY_INIT → LCD_Init()
     ├─ u8g2_SetPowerSave(0)   → 正常显示
     └─ u8g2_ClearBuffer + u8g2_SetFont

注意:U8G2_Port_Init() 内部会调用 LCD_Init(),所以不必再单独调用 LCD_Init()。


6. 使用流程(两种模式)

6.1 全缓冲模式(默认,最简单)

三步:清缓冲 → 画图 → 一次性送显。中途都画在内存里,不会闪烁。

U8G2_Port_Init();                    /* 初始化(含 LCD_Init) */

u8g2_ClearBuffer(&g_u8g2);           /* 1. 清空 1bpp 缓冲 */
u8g2_SetFont(&g_u8g2, U8G2_FONT_10X20);
u8g2_DrawStr(&g_u8g2, 0, 20, "Hello");
u8g2_DrawCircle(&g_u8g2, 64, 64, 20, U8G2_DRAW_ALL);
u8g2_SendBuffer(&g_u8g2);            /* 2. 一次性刷到屏上 */

RAM 占用:2560B(G431RB 有 32KB RAM,余量充足)。

6.2 页缓冲模式(省 RAM)

用 U8G2_Port_InitPage(tile_rows) 初始化,tile_rows 为缓冲区行数(单位 8 像素),取值 1~20:

U8G2_Port_InitPage(1);               /* 只保留 128B 缓冲 */

u8g2_FirstPage(&g_u8g2);
do {
    /* 每一页都重新画一遍完整内容 */
    u8g2_DrawStr(&g_u8g2, 0, 20, "Page mode");
    /* ... 其它绘制 ... */
} while (u8g2_NextPage(&g_u8g2));
  • tile_rows = 20 时等价于全缓冲模式。
  • 每页内容要完整重画(因为缓冲被覆盖)。

7. 移植层自定义接口

以下函数由本工程 u8g2_port.h 定义、u8g2_port.c 实现。

7.1 uint8_t U8G2_Port_Init(void)

初始化 u8g2(全缓冲模式)。内部调用 LCD_Init(),并设置默认前景色白 / 背景色黑。

7.2 uint8_t U8G2_Port_InitPage(uint8_t tile_rows)

页缓冲模式初始化,tile_rows 取值 1~20。

7.3 void U8G2_Port_SetColors(uint16_t fg, uint16_t bg)

设置前景色/背景色(RGB565)。例如:

U8G2_Port_SetColors(LCD_COLOR_YELLOW, LCD_COLOR_BLUE);

7.4 void U8G2_Port_Clear(void)

清屏(清空缓冲 + 整屏刷成背景色)。

7.5 void U8G2_Port_Demo(void)

移植自检画面:几行文字 + 线框 + 圆 + 斜线,用于验证移植是否成功。

7.6 void U8G2_DrawCnStr(u8g2_t *u8g2, uint16_t x, uint16_t y, const char *str, const Font *font)

用工程自己的波特律动字库(font.h 里的 Font 结构,如 &font16x16)画中文/混合字符串。

  • 与 u8g2_DrawStr 的"基线坐标"不同,这里 x,y 是左上角坐标。
  • str 必须是 UTF-8 编码。
  • 字库里找不到的字符会跳过一个格子宽度(不会回退到 ASCII 字体)。
  • 颜色由 U8G2_Port_SetColors() 决定,为"实心"绘制(0 的像素刷成背景色)。

为什么不用 u8g2 自带中文字库?因为 wqy/unifont 等中文字库动辄几十 KB~几 MB,128KB Flash 放不下,所以中文走工程自己的小字库,英文/数字仍走 u8g2 自带字体,互不影响。


8. u8g2 标准 API 函数详解

说明:以下只列本工程实际编译进来、可用的函数。每个函数的第一个参数固定传 &g_u8g2。

8.1 初始化 / 设置类

函数 作用
u8g2_SetupBuffer(u8g2, buf, tile_buf_height, ll_hvline_cb, u8g2_cb) 绑定绘图缓冲区、指定"画点回调"(本工程用 u8g2_ll_hvline_vertical_top_lsb)和旋转方向(U8G2_R0)
u8g2_GetU8x8(u8g2) 返回 (u8x8_t *),用于底层控制
u8g2_InitDisplay(u8g2) 初始化屏幕(触发 U8X8_MSG_DISPLAY_INIT → 本工程 LCD_Init())
u8g2_SetPowerSave(u8g2, is_enable) 使能/关闭省电模式(本工程回调里被忽略)
u8g2_SetFlipMode(u8g2, mode) 设置翻转模式(0/1/2/3,本工程未用)
u8g2_SetContrast(u8g2, value) 设置对比度(本工程未用)
u8g2_SetDisplayRotation(u8g2, u8g2_cb) 切换屏幕旋转方向(U8G2_R0/R1/R2/R3)

8.2 缓冲区 / 送显类

函数 作用
void u8g2_ClearBuffer(u8g2) 把整个 1bpp 缓冲清零(不影响屏幕,需配合 SendBuffer)
void u8g2_SendBuffer(u8g2) 把缓冲区内容一次性发送到屏幕(遍历每个 tile,触发 DRAW_TILE)
void u8g2_ClearDisplay(u8g2) 清空缓冲 并立即 送显(= ClearBuffer + SendBuffer)
void u8g2_FirstPage(u8g2) 页缓冲模式:定位到第一页(配合 NextPage 循环)
uint8_t u8g2_NextPage(u8g2) 页缓冲模式:切换到下一页,返回 1 表示还有下一页;返回 0 表示结束
void u8g2_UpdateDisplayArea(u8g2, tx, ty, tw, th) 只刷新指定 tile 区域
void u8g2_UpdateDisplay(u8g2) 刷新整个显示(页缓冲模式用)
u8g2_GetBufferPtr(u8g2) 返回缓冲区指针
u8g2_GetBufferTileHeight(u8g2) 返回缓冲区 tile 行数
u8g2_GetBufferTileWidth(u8g2) 返回缓冲区 tile 列数
u8g2_GetBufferCurrTileRow(u8g2) 页缓冲模式:当前 tile 行(FirstPage 后有效)
u8g2_WriteBufferXBM / XBM2 / PBM / PBM2(u8g2, out) 把缓冲区以 XBM/PBM 文本格式导出(调试用)

8.3 画点 / 画线类

函数 作用
void u8g2_SetDrawColor(u8g2, color) 设置绘制颜色索引(0/1,本工程 1=前景色、0=背景色)
u8g2_GetDrawColor(u8g2) 返回当前绘制颜色
void u8g2_DrawPixel(u8g2, x, y) 画一个点
void u8g2_DrawHLine(u8g2, x, y, len) 画水平线,从 (x,y) 向右长 len 像素
void u8g2_DrawVLine(u8g2, x, y, len) 画垂直线,从 (x,y) 向下长 len 像素
void u8g2_DrawHVLine(u8g2, x, y, len, dir) 画水平或垂直线,dir=0 水平 / dir=1 垂直
void u8g2_DrawLine(u8g2, x1, y1, x2, y2) 画任意两点连线(Bresenham 算法)

8.4 矩形类

函数 作用
void u8g2_DrawBox(u8g2, x, y, w, h) 画实心矩形,左上角 (x,y),宽 w 高 h
void u8g2_DrawFrame(u8g2, x, y, w, h) 画矩形边框(1 像素宽)
void u8g2_DrawRBox(u8g2, x, y, w, h, r) 画圆角实心矩形,圆角半径 r
void u8g2_DrawRFrame(u8g2, x, y, w, h, r) 画圆角矩形边框

8.5 圆 / 椭圆 / 圆弧类

函数 作用
void u8g2_DrawCircle(u8g2, x0, y0, rad, option) 画圆边框,圆心 (x0,y0) 半径 rad;option 决定画哪些象限(见第 12 节)
void u8g2_DrawDisc(u8g2, x0, y0, rad, option) 画实心圆(圆盘)
void u8g2_DrawEllipse(u8g2, x0, y0, rx, ry, option) 画椭圆边框,横半径 rx 纵半径 ry
void u8g2_DrawFilledEllipse(u8g2, x0, y0, rx, ry, option) 画实心椭圆
void u8g2_DrawArc(u8g2, x0, y0, rad, start, end) 画圆弧。start/end 范围 0~255,每格 = 360/256 度,end 到达 255 即约整圆

本工程 main.c 里 u8g2_DrawArc(&g_u8g2, 64, 32, 10, 0, 255) 就是画一个以 (64,32) 为圆心、半径 10 的近似整圆。

8.6 多边形类

函数 作用
void u8g2_DrawTriangle(u8g2, x0,y0, x1,y1, x2,y2) 画实心三角形(三个顶点)
void u8g2_ClearPolygonXY(u8g2) 清空多边形顶点列表
void u8g2_DrawPolygon(u8g2) 用 AddPolygonXY 累积的顶点画填充多边形

8.7 位图类

函数 作用
void u8g2_SetBitmapMode(u8g2, is_transparent) 位图透明模式:1=透明(0 像素不画)、0=实心(0 像素画背景色)
void u8g2_DrawBitmap(u8g2, x, y, cnt, h, bitmap) 画位图,cnt=横向字节数(每字节一列 8 像素),h=高度
void u8g2_DrawXBM(u8g2, x, y, w, h, bitmap) 画 XBM 格式位图,w 宽 h 高
void u8g2_DrawXBMP(u8g2, x, y, w, h, bitmap) 同上,bitmap 假定在 PROGMEM(本工程与 XBM 相同)

8.8 文字 / 字体类

函数 作用
void u8g2_SetFont(u8g2, font) 选择字体(如 U8G2_FONT_6X10 / U8G2_FONT_10X20)
void u8g2_SetFontMode(u8g2, is_transparent) 字体透明模式:1=透明背景、0=实心背景
void u8g2_SetFontDirection(u8g2, dir) 文字方向(0/1/2/3,旋转 90° 递增)
u8g2_uint_t u8g2_DrawStr(u8g2, x, y, str) 画字符串,(x,y) 为基线坐标,返回实际像素宽度
u8g2_uint_t u8g2_DrawUTF8(u8g2, x, y, str) 画 UTF-8 字符串(需 U8G2_WITH_UNICODE,本工程已定义)
u8g2_uint_t u8g2_DrawGlyph(u8g2, x, y, encoding) 画单个字符字形
u8g2_uint_t u8g2_GetStrWidth(u8g2, s) 计算字符串像素宽度(不含实际绘制)
u8g2_uint_t u8g2_GetUTF8Width(u8g2, str) 计算 UTF-8 字符串像素宽度
u8g2_GetAscent(u8g2) 当前字体基线以上高度
u8g2_GetDescent(u8g2) 当前字体基线以下高度
u8g2_GetMaxCharWidth(u8g2) 当前字体最大字符宽度
u8g2_GetMaxCharHeight(u8g2) 当前字体最大字符高度
u8g2_SetFontPosBaseline / Bottom / Top / Center(u8g2) 设置文字基准位置(基线/底部/顶部/居中)
u8g2_SetFontRefHeightText(u8g2) 参考高度 = 文本高度(默认)
u8g2_SetFontRefHeightExtendedText(u8g2) 参考高度 = 含下延的扩展文本高度
u8g2_SetFontRefHeightAll(u8g2) 参考高度 = 完整字体高度

U8G2_FONT_6X10 等宏在本工程 u8g2_port.h 里定义,本质是 u8g2_font_xxx_tr 字库数组。

8.9 裁剪窗口类

函数 作用
void u8g2_SetClipWindow(u8g2, x0, y0, x1, y1) 设置裁剪窗口(超出部分不绘制)
void u8g2_SetMaxClipWindow(u8g2) 恢复裁剪窗口为整屏

8.10 相交检测类

函数 作用
uint8_t u8g2_IsIntersection(u8g2, x0, y0, x1, y1) 判断矩形区域是否与当前缓冲区/裁剪区相交(页缓冲模式加速用)

9. u8x8 底层函数详解

这些函数由 u8g2 内部使用,本工程移植层也直接调用,理解它们有助于理解移植。

函数 作用
void u8x8_Setup(u8x8, display_cb, cad_cb, byte_cb, gpio_and_delay_cb) 注册 4 个回调函数指针:显示驱动、CAD(命令/数据)、字节通信、GPIO/延时
uint8_t u8x8_dummy_cb(u8x8, msg, arg_int, arg_ptr) 空回调,什么都不做。本工程把 cad/byte/gpio 都设成它
void u8x8_d_helper_display_setup_memory(u8x8, display_info) 把 u8x8_display_info_t 挂到 u8x8->display_info,用于确定 tile 数/像素数
void u8x8_InitDisplay(u8x8) 触发 U8X8_MSG_DISPLAY_INIT
void u8x8_SetPowerSave(u8x8, is_enable) 触发 U8X8_MSG_DISPLAY_SET_POWER_SAVE
void u8x8_SetFlipMode(u8x8, mode) 触发翻转模式消息
void u8x8_SetContrast(u8x8, value) 触发对比度消息

u8x8 显示驱动回调的消息(msg)(u8x8.h 定义,本工程用到的):

宏 值 含义
U8X8_MSG_DISPLAY_SETUP_MEMORY 9 只设置内存信息,不与屏幕通信
U8X8_MSG_DISPLAY_INIT 10 初始化显示
U8X8_MSG_DISPLAY_SET_POWER_SAVE 11 进入/退出省电
U8X8_MSG_DISPLAY_DRAW_TILE 15 画 tile(核心消息)
U8X8_MSG_DISPLAY_REFRESH 16 刷新显示

10. 字体系统

10.1 本工程引入的 4 个 ASCII 字体

宏名 底层字体 说明
U8G2_FONT_6X10 u8g2_font_6x10_tr 6×10 等宽,最小
U8G2_FONT_8X13 u8g2_font_8x13_tr 8×13 等宽
U8G2_FONT_10X20 u8g2_font_10x20_tr 10×20 等宽,数字大
U8G2_FONT_HELVB12 u8g2_font_helvB12_tr Helvetica 粗体 12,比例字体

这些字体 .c 文件位于 BSP/Transplant/U8G2/fonts/。

10.2 u8g2 字体命名规则(u8g2_font_<字型><高度>_<后缀>.c)

  • t = transparent(透明);r = 基准在右上(reference);f = 完整(full);e = 扩展(extended);m = 中等;n = 不透明背景;B/O/b = 粗体/轮廓等变体。
  • 本工程用到的 _tr 表示"透明 + 右上基准"。

10.3 中文字库(工程自带,非 u8g2)

  • 定义于 font.c,Font 结构见 font.h。
  • font16x16 目前收录 11 个字符:波 特 律 动 ℃ 你 好 , 世 界 !。
  • 每条目格式:4 字节 UTF-8 头(不足补 0x00)+ 32 字节字模(16×16 点阵)。
  • 字模取模约定:idx = (j/8)*w + i,bit = 1 << (j%8),即"上半 16 字节 + 下半 16 字节",每字节一列,bit0 为段内最上行。
  • 需要新增汉字时,可用 BSP/Transplant/U8G2/fonts/gen_cn_glyphs.py 生成,再粘进 font.c 的 zh16x16 数组并把 font16x16.len 加 1。

10.4 中文绘制方式

U8G2_DrawCnStr(&g_u8g2, 8, 100, "你好,世界!", &font16x16);

U8G2_DrawCnStr 内部:解析 UTF-8 → 查 font->chars 字库 → 找到后逐像素调用 u8g2_SetDrawColor + u8g2_DrawPixel 画进 u8g2 缓冲。


11. 关键数据结构

11.1 u8g2_t(u8g2.h)

typedef struct u8g2_struct u8g2_t;
struct u8g2_struct {
    const u8g2_cb_t *cb;         /* 旋转/坐标变换回调(U8G2_R0 等) */
    u8x8_t u8x8;                 /* 内嵌的 u8x8 底层结构(所以二者内存重叠) */
    u8g2_uint_t width, height;   /* 屏幕像素尺寸 */
    uint8_t *tile_buf_ptr;       /* 1bpp 缓冲区指针 */
    uint8_t tile_buf_height;     /* 缓冲区 tile 行数 */
    uint8_t tile_curr_row;       /* 页缓冲模式当前行 */
    u8g2_uint_t pixel_buf_width, pixel_buf_height, pixel_curr_row;
    u8g2_uint_t buf_y0, buf_y1;  /* 缓冲区纵向范围 */
    u8g2_uint_t user_x0,x1,y0,y1;/* 用户坐标窗口 */
    u8g2_uint_t clip_x0,x1,y0,y1;/* 裁剪窗口 */
    uint8_t font_mode;           /* 字体透明模式 */
    uint8_t draw_color;          /* 当前绘制颜色(0/1) */
    uint8_t bitmap_transparency; /* 位图透明模式 */
    ...                            /* 字体信息、像素循环状态等 */
};

11.2 u8x8_display_info_t(u8x8.h)

本工程在 u8g2_port.c 里定义 u8x8_display_info_st7735_128x160,关键字段:

tile_width  = 16   /* 128/8 */
tile_height = 20   /* 160/8 */
pixel_width = 128
pixel_height= 160

11.3 u8x8_tile_t(u8x8.h)

typedef struct u8x8_tile_struct {
    uint8_t *tile_ptr;  /* 指向 1bpp 数据 */
    uint8_t cnt;        /* 横向 tile 个数 */
    uint8_t x_pos;      /* 起始 tile 列 */
    uint8_t y_pos;      /* 起始 tile 行 */
} u8x8_tile_t;

11.4 u8g2_cb_t(旋转回调)

  • U8G2_R0:0 度(默认,本工程使用)。
  • U8G2_R1/R2/R3:顺时针 90/180/270 度。

12. 常用常量与宏

12.1 画圆象限选项(u8g2.h)

#define U8G2_DRAW_UPPER_RIGHT 0x01
#define U8G2_DRAW_UPPER_LEFT  0x02
#define U8G2_DRAW_LOWER_LEFT  0x04
#define U8G2_DRAW_LOWER_RIGHT 0x08
#define U8G2_DRAW_ALL (UPPER_RIGHT|UPPER_LEFT|LOWER_RIGHT|LOWER_LEFT)  /* =0x0F 整圆 */

12.2 旋转方向

#define U8G2_R0 (&u8g2_cb_r0)  /* 本工程使用 */
#define U8G2_R1 (&u8g2_cb_r1)
#define U8G2_R2 (&u8g2_cb_r2)
#define U8G2_R3 (&u8g2_cb_r3)

12.3 本工程移植层宏(u8g2_port.h)

#define U8G2_PORT_WIDTH  LCD_Width     /* 128 */
#define U8G2_PORT_HEIGHT LCD_Height    /* 160 */
#define U8G2_PORT_TILE_W (WIDTH/8)     /* 16 */
#define U8G2_PORT_TILE_H (HEIGHT/8)    /* 20 */

13. 常见问题与注意事项

  1. u8g2 只能画两种颜色:因为 ST7735S 是彩屏而 u8g2 是单色库,画面只有前景色/背景色。想画多色图请用工程原有的 TFT_LCD 驱动或 g_lcd 对象接口。

  2. 覆盖式刷新:缓冲区内为 0 的像素会被刷成背景色,不要指望半透明叠加。

  3. 不要重复初始化面板:U8G2_Port_Init() 内部已调用 LCD_Init(),外部无需再调。

  4. 省电消息被忽略:U8X8_MSG_DISPLAY_SET_POWER_SAVE 在回调里故意不处理,休眠/唤醒请直接调 LCD_EnterSleep() / LCD_ExitSleep(),避免与工程低功耗流程打架。

  5. u8g2_DrawStr 的 y 是基线,而 U8G2_DrawCnStr 的 y 是左上角,两者语义不同。

  6. 中文只能显示字库里收录的字:font16x16 只有 11 个字,其它中文会跳格不显示,需要先用 gen_cn_glyphs.py 生成字模并加入 font.c。

  7. 页缓冲模式必须整页重画:FirstPage/NextPage 每次只保留一小块缓冲,所以每页都要把该页所有内容重画一遍。

  8. 缓冲区大小:全缓冲固定 2560 字节(静态数组 s_u8g2_buf),勿越界。

  9. 源码需 UTF-8 编码:U8G2_DrawCnStr 传入的中文字符串需要源文件保存为 UTF-8。

  10. SPI 超时:刷屏用 HAL_SPI_Transmit(..., 100ms),超时宁可丢一块也不卡死整机(U8G2_PORT_SPI_TIMEOUT)。


附录:main.c 中的完整示例

/* u8g2:初始化(内部会调 LCD_Init)→ 画图 → 一次性送显 */
U8G2_Port_Init();

u8g2_ClearBuffer(&g_u8g2);
u8g2_DrawArc(&g_u8g2, 64, 32, 10, 0, 255);   /* 角度 0~255,255≈整圆 */
u8g2_SetFont(&g_u8g2, U8G2_FONT_10X20);
u8g2_DrawStr(&g_u8g2, 5, 80, "Hello world!");
U8G2_DrawCnStr(&g_u8g2, 8, 100, "你好,世界!", &font16x16); /* 中文走工程字库 */
u8g2_SendBuffer(&g_u8g2);
posted @ 2026-11-13 10:42  拉格朗日_欧拉  阅读(0)  评论(0)    收藏  举报