5.34 Graphic 设备
5.34 Graphic 设备
📚 本节导读
学习时长: 约 60 分钟
难度级别: ⭐⭐⭐⭐☆
前置知识: C 语言基础、LCD 显示原理、5.1 设备驱动框架、5.5 SPI 设备(部分屏走 SPI)
Graphic(图形/LCD 显示)设备是嵌入式 GUI 界面、仪表盘、工业屏的核心输出设备。OneOS 将显示设备抽象为 OS_DEVICE_TYPE_GRAPHIC 类型,提供 framebuffer 双缓冲、统一颜色格式、绘制函数库(点/线/矩形/圆/文字)与控制命令。Graphic 设备驱动位于 drivers/graphic/ 目录,核心文件为 graphic.h 与 graphic.c。
本节会带你:
- 看懂 Graphic 设备如何基于 5.1 设备框架实现统一显示接口;
- 掌握
os_graphic_device、os_graphic_info、os_graphic_ops、os_graphic_draw_ops、os_graphic_area等核心结构体的每个字段; - 掌握颜色格式(RGB565/RGB888/ARGB8888)与
os_color_t的位域/位运算演算; - 掌握 framebuffer 双缓冲机制(
FRAME_NEXT/FRAME_SET/FRAME_FILL/FRAME_FLUSH); - 逐个拆解
os_graphic_register、os_graphic_add_framebuffer、os_graphic_control的完整源码; - 通过多个可运行示例掌握初始化、显示信息获取、绘制图形文字、双缓冲刷屏的实际开发。
说明:本节所有源码均取自项目
drivers/graphic/graphic.h与drivers/graphic/graphic.c,与项目源码逐字一致(含原始英文注释)。
🎯 学习目标
- 理解 OneOS 中 Graphic 设备框架的设计理念与 framebuffer 双缓冲机制
- 掌握
os_graphic_device、os_graphic_info、os_graphic_ops、os_graphic_draw_ops各字段含义与来源 - 掌握颜色格式(RGB565/RGB888/ARGB8888)与
os_color_t的位域转换 - 掌握 Graphic 设备控制命令(电源/信息/framebuffer/绘制)
- 掌握
os_graphic_register、os_graphic_add_framebuffer、os_graphic_control的使用 - 理解更换 LCD 屏或 MCU 时如何移植 BSP 层 Graphic 驱动
- 能够编写完整的 LCD 初始化、绘制与刷屏程序
一、概述
Graphic(图形/LCD 显示)设备是嵌入式 GUI 界面、仪表盘、工业屏的核心输出设备。OneOS 将显示设备抽象为 OS_DEVICE_TYPE_GRAPHIC 类型,提供 framebuffer 双缓冲、统一颜色格式、绘制函数库与控制命令。Graphic 设备驱动位于 drivers/graphic/ 目录下,核心头文件为 graphic.h。
Graphic 设备框架的特点:
- framebuffer 双缓冲:支持 2 个帧缓冲,
FRAME_NEXT/FRAME_SET/FRAME_FLUSH实现无撕裂刷屏 - 统一颜色格式:
os_color_t抽象 RGB565/RGB888/ARGB8888,位域转换自动完成 - 绘制函数库:点、线、矩形、圆、实心图形、图像、字符、字符串
- 硬件加速接口:GPU 混叠(blend)/块搬移(blit)预留接口
- 多种屏驱动:ILI9341、ST7789VW、OTM8009A、HT240S030、ATK-TFLCD9341 等
1.1 Graphic 设备的分层架构
Graphic 设备基于 5.1 设备框架,通过 os_graphic_ops 与具体屏驱动解耦:
┌────────────────────────────────────────────────────────────┐
│ 应用层(Application) │
│ os_device_open_s / os_device_control(显示/绘制/刷屏) │
│ os_device_read_block(读 framebuffer) │
└──────────────────────────────┬─────────────────────────────┘
│ os_device_control
▼
┌────────────────────────────────────────────────────────────┐
│ Graphic 框架层(drivers/graphic/graphic.c) │
│ os_graphic_control:控制命令分发(电源/信息/framebuffer) │
│ os_graphic_frame_next/set/fill(双缓冲管理) │
│ 绘制函数库:draw_point/line/rect/circle/char/string │
│ os_graphic_register / os_graphic_add_framebuffer │
└──────────────────────────────┬─────────────────────────────┘
│ 调用 graphic->ops->display_on / frame_flush / display_area
▼
┌────────────────────────────────────────────────────────────┐
│ BSP 屏驱动(ili9341.c / st7789vw.c 等) │
│ 实现 struct os_graphic_ops 的 display_on/frame_flush/ │
│ display_area(通过 SPI/I2C/DSI 操作 LCD 寄存器) │
└──────────────────────────────┬─────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────────┐
│ 硬件(LCD 屏 + 背光 + 电源引脚) │
└────────────────────────────────────────────────────────────┘关键理解:Graphic 框架层不直接操作 LCD 寄存器。它负责"framebuffer 管理 + 控制命令分发 + 绘制函数库",真正写 LCD 寄存器(如 ILI9341 的 0x2C 写显存命令)的是 BSP 屏驱动实现的 struct os_graphic_ops。这让上层应用与具体屏(SPI 屏/I2C 屏/DSI 屏)解耦。
1.2 Graphic 设备使用场景
一句话核心
Graphic 设备适用于需要向 LCD/显示屏输出图形、文字、图像的人机交互场景,通过 framebuffer 缓存 + 统一绘制接口实现。
与其他输出设备的机制对比
| 维度 | Graphic(LCD) | Serial 串口终端 | 7 段数码管 | LED 灯 |
|---|---|---|---|---|
| 输出内容 | 图形、文字、图像 | 文本流 | 数字/字母 | 开关状态 |
| 交互性 | 高(可绘制 GUI) | 低(文本命令) | 低 | 极低 |
| framebuffer | 有(可回读) | 无 | 无 | 无 |
| 应用场景 | GUI、仪表、工业屏 | 调试、终端 | 数值显示 | 状态指示 |
为什么用 Graphic 而不是串口终端:需要显示图形界面、仪表盘、图片时,串口只能输出文本,无法表达图形化界面。Graphic 提供 framebuffer 和绘制函数库,可渲染复杂 GUI。
典型应用场景
场景 1:GUI 界面刷新(双缓冲防撕裂)
┌──────────────┐ framebuffer[0] ┌──────────────┐
│ 应用绘制任务 │ ────────────────▶│ 显存缓存 │
│ draw 按钮/文字│ │ FRAME_FLUSH │
└──────────────┘ └──────┬───────┘
│ 刷新到 LCD
▼
┌──────────┐
│ LCD 屏幕 │
└──────────┘为什么用 Graphic:GUI 需要把"按钮、文字、图片"等元素渲染到屏幕。应用先把所有元素画到 framebuffer(内存),再一次性 FRAME_FLUSH 刷到 LCD,避免逐元素刷新产生的闪烁/撕裂。
实际工作:任务调 os_device_control(dev, OS_GRAPHIC_CTRL_DRAW_STRING, "Hello") 把文字画到 framebuffer → 调 OS_GRAPHIC_CTRL_FRAME_FLUSH 刷到 LCD → 屏幕显示"Hello"。
场景 2:仪表盘实时刷新(多段数据更新)
为什么用 Graphic:仪表盘指针、数值每秒更新多次,用 draw_line/draw_solid_circle 重绘指针,FRAME_FLUSH 同步刷新。
实际工作:每 100ms 用 draw_solid_rect 清除旧指针区域,draw_line 画新角度指针,FRAME_FLUSH 刷新。
选型边界表(何时不该用 Graphic)
| 场景 | 该用什么 | 为什么 |
|---|---|---|
| 只需显示少量数字 | 7 段数码管 | Graphic 需要 LCD + 驱动,成本高 |
| 只需状态指示 | LED/GPIO | 无需图形渲染 |
| 只需文本日志 | Serial 终端 | 无需图形,串口更简单 |
| 极高帧率视频 | 专用 GPU/HDMI | MCU 的 framebuffer 带宽有限 |
结合源码的使用注意点
- 控制命令为主:Graphic 设备主要通过
os_device_control使用(os_graphic_ops只有.control,无 read/write),绘制和刷屏都走控制命令(见 3.3)。 - framebuffer 需先添加:使用
FRAME_FILL/FRAME_FLUSH前,若framebuffer_num == 0,需要先os_graphic_add_framebuffer添加缓冲(见 4.3)。 - 颜色格式由编译期决定:
os_color_t是 RGB565/RGB888/ARGB8888 之一,由OS_GRAPHIC_LCD_FORMAT_*宏决定(见 3.2)。 - 无缓冲时退化为直接写屏:绘制函数在
framebuffer_num == 0时,会退化为调用ops->display_area直接把像素写屏(见 4.2 的draw_point分步详解)。
二、核心数据结构(字段详解)
本节对照 drivers/graphic/graphic.h 讲解 Graphic 框架的核心结构体。来源:drivers/graphic/graphic.h。
2.1 颜色结构体(os_color16 / os_color24 / os_color32 / os_color_t)
struct os_color32
{
uint8_t b;
uint8_t g;
uint8_t r;
uint8_t a;
};
struct os_color24
{
uint8_t b;
uint8_t g;
uint8_t r;
};
struct os_color16
{
uint16_t b : 5;
uint16_t g : 6;
uint16_t r : 5;
};| 结构体 | 位数 | 字段 | 说明 |
|---|---|---|---|
os_color16 | 16 位 | b:5/g:6/r:5 | RGB565(蓝 5 位、绿 6 位、红 5 位),最常用 |
os_color24 | 24 位 | b/g/r | RGB888(各 8 位) |
os_color32 | 32 位 | b/g/r/a | ARGB8888(含 8 位透明度) |
os_color_t 类型由编译期宏决定:
#if defined(OS_GRAPHIC_LCD_FORMAT_RGB565)
typedef struct os_color16 os_color_t;
#define OS_COLOR(r, g, b) ((os_color_t){OS_COLOR_SET_B16(b), OS_COLOR_SET_G16(g), OS_COLOR_SET_R16(r)})
#elif defined(OS_GRAPHIC_LCD_FORMAT_RGB888)
typedef struct os_color24 os_color_t;
#define OS_COLOR(r, g, b) ((os_color_t){b, g, r})
#elif defined(OS_GRAPHIC_LCD_FORMAT_ARGB8888)
typedef struct os_color32 os_color_t;
#define OS_COLOR(r, g, b) ((os_color_t){b, g, r, 0xFF})
#else
#error Unsupported color format
#endif关键理解——OS_COLOR 宏自动做位域转换:OS_COLOR(r, g, b) 接收 8 位 R/G/B,自动按当前颜色格式转换。RGB565 时用 OS_COLOR_SET_R16/G16/B16 压缩到 5/6/5 位。
数值演算(RGB565 颜色转换):
OS_COLOR(0xFF, 0x80, 0x00)(红色 R=255、绿 G=128、蓝 B=0):
计算项 代入计算 结果 OS_COLOR_SET_R16(0xFF)(0xFF >> 3) & 0x1F0x1F(5 位)OS_COLOR_SET_G16(0x80)(0x80 >> 2) & 0x3F0x20(6 位)OS_COLOR_SET_B16(0x00)(0x00 >> 3) & 0x1F0x00(5 位)组成 os_color16{b=0, g=0x20, r=0x1F}红偏橙
2.2 显示设备信息 os_graphic_info
typedef struct os_graphic_info
{
uint8_t pixel_format; /* Graphic format. */
uint8_t bits_per_pixel; /* Bits per pixel. */
uint8_t bytes_per_pixel; /* Bytes per pixel. */
uint16_t bytes_per_line; /* Bytes per line. */
uint16_t width; /* Width of graphic device. */
uint16_t height; /* Height of graphic device. */
uint8_t *framebuffer[2]; /* Internal frame buffer pointer */
uint8_t framebuffer_num; /* Internal frame buffer number */
uint32_t framebuffer_size; /* Size of each frame buffer */
uint8_t *framebuffer_curr; /* Current frame buffer pointer */
} os_graphic_info_t;| 字段 | 类型 | 含义 |
|---|---|---|
pixel_format | uint8_t | 像素格式(OS_GRAPHIC_LCD_FORMAT_*) |
bits_per_pixel | uint8_t | 每像素位数(RGB565 为 16) |
bytes_per_pixel | uint8_t | 每像素字节数(RGB565 为 2) |
bytes_per_line | uint16_t | 每行字节数(= 宽度 × 每像素字节数) |
width/height | uint16_t | 屏幕宽/高(像素) |
framebuffer[2] | uint8_t *[2] | 两个帧缓冲指针(双缓冲) |
framebuffer_num | uint8_t | 已添加的帧缓冲个数(0/1/2) |
framebuffer_size | uint32_t | 每个帧缓冲大小 |
framebuffer_curr | uint8_t * | 当前正在使用的帧缓冲指针 |
关键理解:framebuffer[2] 支持双缓冲——一个缓冲被 FRAME_FLUSH 刷到屏幕时,应用可在另一个缓冲上继续绘制,实现无撕裂刷新。framebuffer_num 从 0 开始,os_graphic_add_framebuffer 每加一个递增(最多 2 个)。
2.3 显示设备结构体 os_graphic_device
typedef struct os_graphic_device
{
os_device_t parent;
struct os_graphic_info info;
const struct os_graphic_ops *ops;
const struct os_graphic_draw_ops *draw_ops;
} os_graphic_t;| 字段 | 类型 | 含义 |
|---|---|---|
parent | os_device_t | 内嵌的通用设备对象,用于接入 5.1 设备框架 |
info | struct os_graphic_info | 显示设备信息(分辨率/格式/framebuffer) |
ops | const struct os_graphic_ops * | 屏驱动底层操作表(display_on/frame_flush/display_area) |
draw_ops | const struct os_graphic_draw_ops * | 绘制函数表(OS_GRAPHIC_DRAW_ENABLE 时存在) |
关键理解:ops 是屏驱动(操作具体 LCD 寄存器)提供的;draw_ops 是框架内置的绘制函数库(graphic.c 里的 draw_ops,见 4.4),由 os_graphic_register 在 OS_GRAPHIC_DRAW_ENABLE 时自动挂载。
2.4 屏驱动操作表 os_graphic_ops
struct os_graphic_ops
{
void (*display_on)(struct os_device *dev, os_bool_t on_off);
void (*frame_flush)(struct os_device *dev);
void (*display_area)(struct os_device *dev, os_graphic_area_t *area);
#ifdef OS_GRAPHIC_GPU_ENABLE
void (*gpu_blend)(struct os_device *dev, struct os_device_gpu_info *gpu_info);
void (*gpu_alphamap_blend)(struct os_device *dev, struct os_device_gpu_info *gpu_info);
void (*gpu_blit)(struct os_device *dev, struct os_device_gpu_info *gpu_info);
void (*gpu_wait_finish)(struct os_device *dev);
#endif
};| 函数指针 | 说明 |
|---|---|
display_on | 开关显示(背光/显存),OS_TRUE 开、OS_FALSE 关 |
frame_flush | 把当前 framebuffer 数据刷到 LCD 屏幕 |
display_area | 把指定区域(os_graphic_area_t)绘制到屏幕 |
gpu_blend/gpu_blit/gpu_wait_finish | GPU 硬件加速(OS_GRAPHIC_GPU_ENABLE 时存在) |
关键理解:屏驱动只需实现 display_on/frame_flush/display_area 三个核心函数(GPU 相关可选)。框架的绘制函数库最终调用 display_area 或直接操作 framebuffer。
2.5 绘制操作表 os_graphic_draw_ops(OS_GRAPHIC_DRAW_ENABLE 时)
struct os_graphic_draw_ops
{
void (*draw_point)(os_graphic_t *graphic, os_graphic_point_t *point);
void (*draw_line)(os_graphic_t *graphic, os_graphic_line_t *line);
void (*draw_rect)(os_graphic_t *graphic, os_graphic_rect_t *rect);
void (*draw_circle)(os_graphic_t *graphic, os_graphic_circle_t *circle);
void (*draw_solid_rect)(os_graphic_t *graphic, os_graphic_rect_t *rect);
void (*draw_solid_circle)(os_graphic_t *graphic, os_graphic_circle_t *circle);
void (*draw_image)(os_graphic_t *graphic, os_graphic_area_t *circle);
void (*draw_char)(os_graphic_t *graphic, int32_t index);
void (*draw_string)(os_graphic_t *graphic, uint8_t *str);
void (*set_txt_cursor)(os_graphic_t *graphic, os_graphic_pos_t *pos);
void (*set_txt_color)(os_graphic_t *graphic, os_color_t *color);
os_err_t (*set_font)(os_graphic_t *graphic, char *name);
os_err_t (*add_font)(os_graphic_t *graphic, os_graphic_font_t *font);
void (*clear)(os_graphic_t *graphic, os_color_t *color);
};| 函数指针 | 说明 |
|---|---|
draw_point/draw_line/draw_rect/draw_circle | 基本图形 |
draw_solid_rect/draw_solid_circle | 实心图形 |
draw_image | 图像(区域绘制) |
draw_char/draw_string | 字符/字符串(配合字体) |
set_txt_cursor/set_txt_color | 设置文本光标/颜色 |
set_font/add_font | 设置/添加字体 |
clear | 清屏 |
绘制相关结构体(os_graphic_pos_t/point_t/line_t/rect_t/circle_t/font_t)定义于 graphic.h,这里不逐一展开(见 4.4 的完整源码)。
2.6 显示区域结构体 os_graphic_area
typedef struct os_graphic_area
{
uint16_t x; /* x coordinate. */
uint16_t y; /* y coordinate. */
uint16_t w; /* Width. */
uint16_t h; /* Height.*/
os_color_t color;
uint8_t *buffer;
} os_graphic_area_t;| 字段 | 类型 | 含义 |
|---|---|---|
x/y | uint16_t | 区域左上角坐标 |
w/h | uint16_t | 区域宽/高 |
color | os_color_t | 填充颜色(buffer 为空时用) |
buffer | uint8_t * | 图像数据指针(非空时用图像数据,空时用 color 填充) |
关键理解:os_graphic_area 既可用于"填充色块"(buffer = OS_NULL),也可用于"显示图像"(buffer 指向图像数据)。os_graphic_frame_fill 根据 buffer 是否为 0 决定用 memcpy 还是逐像素填色(见 4.2)。
三、控制命令与颜色宏
本节对照 drivers/graphic/graphic.h 讲解控制命令与颜色宏。来源:drivers/graphic/graphic.h。
3.1 控制命令枚举
enum
{
OS_GRAPHIC_CTRL_POWERON, /* power on graphic device */
OS_GRAPHIC_CTRL_POWEROFF, /* power off graphic device */
OS_GRAPHIC_CTRL_GET_INFO, /* get graphic device info */
OS_GRAPHIC_CTRL_DISP_AREA, /* display area */
OS_GRAPHIC_CTRL_FRAME_NEXT, /* get next frame buffer */
OS_GRAPHIC_CTRL_FRAME_SET, /* set current frame buffer */
OS_GRAPHIC_CTRL_FRAME_FILL, /* fill current frame buffer */
OS_GRAPHIC_CTRL_FRAME_FLUSH, /* flush frame buffer to display */
OS_GRAPHIC_CTRL_DRAW_POINT,
OS_GRAPHIC_CTRL_DRAW_LINE,
OS_GRAPHIC_CTRL_DRAW_RECT,
OS_GRAPHIC_CTRL_DRAW_CIRCLE,
OS_GRAPHIC_CTRL_DRAW_SOLID_RECT,
OS_GRAPHIC_CTRL_DRAW_SOLID_CIRCLE,
OS_GRAPHIC_CTRL_DRAW_IMAGE,
OS_GRAPHIC_CTRL_DRAW_CHAR,
OS_GRAPHIC_CTRL_DRAW_STRING,
OS_GRAPHIC_CTRL_DRAW_SET_TXT_CURSOR,
OS_GRAPHIC_CTRL_DRAW_SET_TXT_COLOR,
OS_GRAPHIC_CTRL_DRAW_ADD_FONT,
OS_GRAPHIC_CTRL_DRAW_SET_FONT,
OS_GRAPHIC_CTRL_DRAW_CLEAR,
OS_GRAPHIC_CTRL_GPU_BLEND, /* gpu blend */
OS_GRAPHIC_CTRL_GPU_ALPHAMAP_BLEND, /* gpu blend */
OS_GRAPHIC_CTRL_GPU_BLIT, /* gpu blit */
OS_GRAPHIC_CTRL_GPU_WAIT, /* gpu wait finish */
};| 命令 | 值 | 说明 |
|---|---|---|
OS_GRAPHIC_CTRL_POWERON | 0 | 开显示 |
OS_GRAPHIC_CTRL_POWEROFF | 1 | 关显示 |
OS_GRAPHIC_CTRL_GET_INFO | 2 | 获取显示信息(os_graphic_info_t *) |
OS_GRAPHIC_CTRL_DISP_AREA | 3 | 绘制指定区域 |
OS_GRAPHIC_CTRL_FRAME_NEXT | 4 | 获取下一个帧缓冲(uint8_t **) |
OS_GRAPHIC_CTRL_FRAME_SET | 5 | 设置当前帧缓冲(uint8_t *) |
OS_GRAPHIC_CTRL_FRAME_FILL | 6 | 填充当前帧缓冲(os_graphic_area_t *) |
OS_GRAPHIC_CTRL_FRAME_FLUSH | 7 | 刷屏(把当前帧缓冲刷到 LCD) |
OS_GRAPHIC_CTRL_DRAW_POINT | 8 | 画点 |
OS_GRAPHIC_CTRL_DRAW_LINE | 9 | 画线 |
OS_GRAPHIC_CTRL_DRAW_RECT | 10 | 画矩形 |
OS_GRAPHIC_CTRL_DRAW_CIRCLE | 11 | 画圆 |
OS_GRAPHIC_CTRL_DRAW_SOLID_RECT | 12 | 画实心矩形 |
OS_GRAPHIC_CTRL_DRAW_SOLID_CIRCLE | 13 | 画实心圆 |
OS_GRAPHIC_CTRL_DRAW_IMAGE | 14 | 画图像 |
OS_GRAPHIC_CTRL_DRAW_CHAR | 15 | 画字符 |
OS_GRAPHIC_CTRL_DRAW_STRING | 16 | 画字符串 |
OS_GRAPHIC_CTRL_DRAW_SET_TXT_CURSOR | 17 | 设置文本光标 |
OS_GRAPHIC_CTRL_DRAW_SET_TXT_COLOR | 18 | 设置文本颜色 |
OS_GRAPHIC_CTRL_DRAW_ADD_FONT | 19 | 添加字体 |
OS_GRAPHIC_CTRL_DRAW_SET_FONT | 20 | 设置字体 |
OS_GRAPHIC_CTRL_DRAW_CLEAR | 21 | 清屏 |
OS_GRAPHIC_CTRL_GPU_BLEND | 22 | GPU 混叠 |
OS_GRAPHIC_CTRL_GPU_ALPHAMAP_BLEND | 23 | GPU 透明度混叠 |
OS_GRAPHIC_CTRL_GPU_BLIT | 24 | GPU 块搬移 |
OS_GRAPHIC_CTRL_GPU_WAIT | 25 | GPU 等待完成 |
数值演算:控制命令是连续枚举,从
POWERON=0依次递增到GPU_WAIT=25。这些命令直接作为os_device_control的cmd传入(见 4.4 的os_graphic_controlswitch)。注意:DRAW_*系列命令在OS_GRAPHIC_DRAW_ENABLE宏下才被处理;GPU_*系列在OS_GRAPHIC_GPU_ENABLE宏下才被处理。
3.2 颜色宏
#define OS_COLOR_WHITE OS_COLOR(0xFF, 0xFF, 0xFF)
#define OS_COLOR_SILVER OS_COLOR(0xC0, 0xC0, 0xC0)
#define OS_COLOR_GRAY OS_COLOR(0x80, 0x80, 0x80)
#define OS_COLOR_BLACK OS_COLOR(0x00, 0x00, 0x00)
#define OS_COLOR_RED OS_COLOR(0xFF, 0x00, 0x00)
#define OS_COLOR_YELLOW OS_COLOR(0xFF, 0xFF, 0x00)
#define OS_COLOR_GREEN OS_COLOR(0x00, 0x80, 0x00)
#define OS_COLOR_BLUE OS_COLOR(0x00, 0x00, 0xFF)
#define OS_COLOR_PURPLE OS_COLOR(0x80, 0x00, 0x80)
#define OS_COLOR_ORANGE OS_COLOR(0xFF, 0xA5, 0x00)| 颜色宏 | RGB | RGB565 近似值 |
|---|---|---|
OS_COLOR_WHITE | FF,FF,FF | 0xFFFF |
OS_COLOR_BLACK | 00,00,00 | 0x0000 |
OS_COLOR_RED | FF,00,00 | 0xF800 |
OS_COLOR_GREEN | 00,80,00 | 0x0400 |
OS_COLOR_BLUE | 00,00,FF | 0x001F |
OS_COLOR_YELLOW | FF,FF,00 | 0xFFE0 |
数值演算(RGB565 红色):
OS_COLOR(0xFF, 0x00, 0x00):
计算项 代入计算 结果 OS_COLOR_SET_R16(0xFF)(0xFF >> 3) & 0x1F0x1FOS_COLOR_SET_G16(0x00)(0x00 >> 2) & 0x3F0x00OS_COLOR_SET_B16(0x00)(0x00 >> 3) & 0x1F0x00RGB565 值 r<<11 | g<<5 | b0x1F<<11=0xF800
位运算转换宏(RGB565 时使用):
#define OS_COLOR_SET_R16(rr) (uint16_t)((rr >> 3) & 0x1FU)
#define OS_COLOR_SET_G16(gg) (uint16_t)((gg >> 2) & 0x3FU)
#define OS_COLOR_SET_B16(bb) (uint16_t)((bb >> 3) & 0x1FU)
#define OS_COLOR_BIT_5TO8(x) ((x * 263 + 7) >> 5)
#define OS_COLOR_BIT_6TO8(x) ((x * 259 + 3) >> 6)| 宏 | 作用 |
|---|---|
OS_COLOR_SET_R16/G16/B16 | 8 位 → 5/6/5 位压缩(存帧缓冲) |
OS_COLOR_BIT_5TO8/6TO8 | 5/6 位 → 8 位扩展(读帧缓冲) |
数值演算(5→8 位扩展):
OS_COLOR_BIT_5TO8(0x1F)(5 位最大值):
计算项 代入计算 结果 0x1F * 263 + 731*263 + 78160>> 58160 / 322555 位
0x1F(全红)扩展回 8 位0xFF,无精度损失。
3.3 配置命令与 Kconfig
Graphic 设备的编译配置在 drivers/graphic/Kconfig(见第七章 7.1 的源码)。核心配置项:
| 配置 | 说明 | 默认值 |
|---|---|---|
OS_USING_GRAPHIC | 使能 Graphic 组件 | n |
OS_GRAPHIC_LCD_WIDTH/HEIGHT | 屏宽高 | 240/240 |
OS_GRAPHIC_LCD_FORMAT_* | 颜色格式(RGB565/RGB888/ARGB8888) | RGB565 |
OS_GRAPHIC_DRAW_ENABLE | 使能绘制函数库 | n |
OS_GRAPHIC_GPU_ENABLE | 使能 GPU 硬件加速 | n |
OS_USING_DSI | 使用 DSI 接口 | n |
BSP_USING_ILI9341 | ILI9341 屏驱动 | n |
OS_USING_ST7789VW | ST7789VW 屏驱动 | n |
OS_USING_HT240S030 | HT240S030 屏驱动 | n |
BSP_USING_OTM8009A | OTM8009A 屏驱动 | n |
关键理解:OS_GRAPHIC_LCD_FORMAT_* 决定 os_color_t 类型(见 2.1);OS_GRAPHIC_DRAW_ENABLE 决定是否编译绘制函数库(draw_ops);OS_GRAPHIC_GPU_ENABLE 决定是否编译 GPU 控制命令分支。
四、核心 API 详解
本节逐个拆解 drivers/graphic/graphic.c 中的核心函数。来源:drivers/graphic/graphic.c。
4.1 os_graphic_register —— 注册 Graphic 设备
函数签名
os_err_t os_graphic_register(const char *name, os_graphic_t *graphic)参数 / 返回值说明
| 参数 | 类型 | 说明 |
|---|---|---|
name | const char * | 设备名称,如 "lcd"、"graphic" |
graphic | os_graphic_t * | Graphic 设备结构体指针,必须已填充 ops |
| 返回值 | 说明 |
|---|---|
OS_SUCCESS | 注册成功 |
完整源码
os_err_t os_graphic_register(const char *name, os_graphic_t *graphic)
{
OS_ASSERT(graphic != OS_NULL);
OS_ASSERT(graphic->ops != OS_NULL);
graphic->parent.type = OS_DEVICE_TYPE_GRAPHIC;
graphic->parent.ops = &graphic_ops;
#ifdef OS_GRAPHIC_DRAW_ENABLE
graphic->draw_ops = &draw_ops;
#endif
graphic->info.width = OS_GRAPHIC_LCD_WIDTH;
graphic->info.height = OS_GRAPHIC_LCD_HEIGHT;
graphic->info.pixel_format = OS_GRAPHIC_LCD_FORMAT;
graphic->info.bits_per_pixel = OS_GRAPHIC_LCD_DEPTH;
graphic->info.bytes_per_pixel = OS_GRAPHIC_LCD_DEPTH / 8;
graphic->info.bytes_per_line = OS_GRAPHIC_LCD_WIDTH * (OS_GRAPHIC_LCD_DEPTH / 8);
graphic->info.framebuffer[0] = OS_NULL;
graphic->info.framebuffer[1] = OS_NULL;
graphic->info.framebuffer_curr = OS_NULL;
graphic->info.framebuffer_num = 0;
graphic->info.framebuffer_size = graphic->info.bytes_per_line * graphic->info.height;
os_device_register(&graphic->parent, name);
if (graphic->ops->display_on != OS_NULL)
graphic->ops->display_on(&graphic->parent, OS_TRUE);
return OS_SUCCESS;
}分步详解
第一步:参数校验(条件 graphic 与 ops 非空)
OS_ASSERT(graphic != OS_NULL);
OS_ASSERT(graphic->ops != OS_NULL);为什么断言 ops 非空:graphic->ops 是屏驱动提供的操作表(display_on/frame_flush/display_area),注册后 os_graphic_control 会访问它。若为空会空指针崩溃。
第二步:配置父设备与绘制函数表(无条件执行)
graphic->parent.type = OS_DEVICE_TYPE_GRAPHIC;
graphic->parent.ops = &graphic_ops;
#ifdef OS_GRAPHIC_DRAW_ENABLE
graphic->draw_ops = &draw_ops;
#endifparent.type = OS_DEVICE_TYPE_GRAPHIC:标记为图形设备;parent.ops = &graphic_ops:挂上框架层操作表(只有.control = os_graphic_control,见下方);OS_GRAPHIC_DRAW_ENABLE时,draw_ops = &draw_ops挂上框架内置绘制函数库(见 4.4)。
框架层操作表 graphic_ops:
const static struct os_device_ops graphic_ops = {
.control = os_graphic_control,
};注意:graphic_ops 只有 control,没有 init/deinit/read/write。即 Graphic 设备不支持 os_device_read/write,所有操作都走 os_device_control。
第三步:填充显示信息(无条件执行)
graphic->info.width = OS_GRAPHIC_LCD_WIDTH;
graphic->info.height = OS_GRAPHIC_LCD_HEIGHT;
graphic->info.pixel_format = OS_GRAPHIC_LCD_FORMAT;
graphic->info.bits_per_pixel = OS_GRAPHIC_LCD_DEPTH;
graphic->info.bytes_per_pixel = OS_GRAPHIC_LCD_DEPTH / 8;
graphic->info.bytes_per_line = OS_GRAPHIC_LCD_WIDTH * (OS_GRAPHIC_LCD_DEPTH / 8);从 Kconfig 宏填充宽高、格式、位深。bytes_per_line = 宽 × 每像素字节数。
数值演算:设
OS_GRAPHIC_LCD_WIDTH = 240、HEIGHT = 240、RGB565(DEPTH = 16):
计算项 代入计算 结果 bytes_per_pixel16 / 82bytes_per_line240 * 2480framebuffer_size480 * 240115200字节
第四步:初始化 framebuffer 并注册(无条件执行)
graphic->info.framebuffer[0] = OS_NULL;
graphic->info.framebuffer[1] = OS_NULL;
graphic->info.framebuffer_curr = OS_NULL;
graphic->info.framebuffer_num = 0;
graphic->info.framebuffer_size = graphic->info.bytes_per_line * graphic->info.height;
os_device_register(&graphic->parent, name);
if (graphic->ops->display_on != OS_NULL)
graphic->ops->display_on(&graphic->parent, OS_TRUE);
return OS_SUCCESS;framebuffer 全部清零(双缓冲为空,framebuffer_num = 0),计算 framebuffer_size,注册设备,最后调用 display_on(OS_TRUE) 点亮屏幕(若屏驱动实现了)。
条件走向总结
os_graphic_register() 条件走向总结:
├── OS_ASSERT(graphic != OS_NULL)(为空则断言失败)
├── OS_ASSERT(graphic->ops != OS_NULL)(为空则断言失败)
├── 配置 parent.type/ops
├── OS_GRAPHIC_DRAW_ENABLE 已定义
│ └── graphic->draw_ops = &draw_ops(挂绘制函数库)
├── 填充 info(宽高/格式/位深/每行字节数)
├── 初始化 framebuffer 全空、framebuffer_num = 0、算 size
├── os_device_register(&parent, name)
├── ops->display_on 非空
│ └── ops->display_on(&parent, OS_TRUE)(点亮屏幕)
└── 返回 OS_SUCCESS4.2 os_graphic_add_framebuffer —— 添加帧缓冲
函数签名
os_err_t os_graphic_add_framebuffer(struct os_device *dev, uint8_t *fb, uint32_t size)参数 / 返回值说明
| 参数 | 类型 | 说明 |
|---|---|---|
dev | struct os_device * | 设备指针 |
fb | uint8_t * | 帧缓冲内存指针(需调用者分配) |
size | uint32_t | 帧缓冲大小(必须等于 framebuffer_size) |
| 返回值 | 说明 |
|---|---|
OS_SUCCESS | 添加成功 |
OS_FAILURE | 已满 2 个缓冲或 size 不匹配 |
完整源码
os_err_t os_graphic_add_framebuffer(struct os_device *dev, uint8_t *fb, uint32_t size)
{
os_graphic_t *graphic = (os_graphic_t *)dev;
os_graphic_info_t *info = &graphic->info;
OS_ASSERT(fb != OS_NULL);
if (info->framebuffer_num == 2)
return OS_FAILURE;
// The size must be full screen
if (size != info->framebuffer_size)
return OS_FAILURE;
memset(fb, 0, size);
info->framebuffer[info->framebuffer_num] = fb;
info->framebuffer_num++;
// Set framebuffer_curr if add the first framebuffer.
if (info->framebuffer_num == 1)
info->framebuffer_curr = fb;
os_kprintf("Add framebuffer %d\r\n", info->framebuffer_num);
return OS_SUCCESS;
}分步详解
第一步:断言帧缓冲非空(无条件执行)
OS_ASSERT(fb != OS_NULL);第二步:校验数量与大小(条件 framebuffer_num == 2 或 size 不匹配)
if (info->framebuffer_num == 2)
return OS_FAILURE;
// The size must be full screen
if (size != info->framebuffer_size)
return OS_FAILURE;为什么限制 2 个:双缓冲机制最多 2 个(一个绘制、一个刷新)。为什么 size 必须等于 framebuffer_size(整屏):graphic.c 的 frame_fill/draw_* 函数直接按整屏行数/宽度寻址,若缓冲不满屏会越界。
第三步:清零并登记(无条件执行)
memset(fb, 0, size);
info->framebuffer[info->framebuffer_num] = fb;
info->framebuffer_num++;
// Set framebuffer_curr if add the first framebuffer.
if (info->framebuffer_num == 1)
info->framebuffer_curr = fb;
os_kprintf("Add framebuffer %d\r\n", info->framebuffer_num);
return OS_SUCCESS;清零新缓冲,登记到 framebuffer[num],num++;添加第一个缓冲时把它设为当前缓冲 framebuffer_curr。
条件走向总结
os_graphic_add_framebuffer() 条件走向总结:
├── OS_ASSERT(fb != OS_NULL)
├── framebuffer_num == 2(已满)→ 返回 OS_FAILURE
├── size != framebuffer_size(非整屏)→ 返回 OS_FAILURE
├── memset(fb, 0, size)(清零)
├── framebuffer[num] = fb;num++
├── num == 1(第一个)→ framebuffer_curr = fb
├── os_kprintf("Add framebuffer %d")
└── 返回 OS_SUCCESS4.3 framebuffer 内部辅助函数(os_graphic_frame_next/set/fill)
这三个 static 函数由 os_graphic_control 在 FRAME_* 命令时调用,是双缓冲机制的核心。
完整源码(os_graphic_frame_next / os_graphic_frame_set)
static void os_graphic_frame_next(struct os_device *dev, uint8_t **fb)
{
os_graphic_t *graphic = (os_graphic_t *)dev;
if (graphic->info.framebuffer_num == 2)
*fb = graphic->info.framebuffer[g_frame_id];
else if (graphic->info.framebuffer_num == 1)
*fb = graphic->info.framebuffer[0];
else
*fb = OS_NULL;
}
static void os_graphic_frame_set(struct os_device *dev, uint8_t *fb)
{
os_graphic_t *graphic = (os_graphic_t *)dev;
if (fb)
graphic->info.framebuffer_curr = fb;
}分步详解(os_graphic_frame_next)
第一步:按缓冲数量返回下一个缓冲(条件 framebuffer_num 的值)
if (graphic->info.framebuffer_num == 2)
*fb = graphic->info.framebuffer[g_frame_id];
else if (graphic->info.framebuffer_num == 1)
*fb = graphic->info.framebuffer[0];
else
*fb = OS_NULL;- 双缓冲(num==2):返回
framebuffer[g_frame_id](g_frame_id在FRAME_FLUSH后翻转,见 4.4)——这样应用拿到的是未在刷新的那个缓冲,可安全绘制; - 单缓冲(num==1):返回
framebuffer[0]; - 无缓冲(num==0):返回
OS_NULL。
分步详解(os_graphic_frame_set)
第一步:设置当前缓冲(条件 fb 非空)
if (fb)
graphic->info.framebuffer_curr = fb;把 framebuffer_curr 指向 fb,后续绘制/填充都写这个缓冲。
完整源码(os_graphic_frame_fill)
static void os_graphic_frame_fill(struct os_device *dev, os_graphic_area_t *area)
{
uint32_t i, j;
os_color_t *pos;
os_graphic_t *graphic = (os_graphic_t *)dev;
os_graphic_info_t *info = &(graphic->info);
uint32_t dst = (uint32_t)info->framebuffer_curr + area->y * graphic->info.bytes_per_line +
area->x * graphic->info.bytes_per_pixel;
uint32_t src = (uint32_t)area->buffer;
uint32_t dst_offset = info->bytes_per_pixel * info->width;
uint32_t src_offset = info->bytes_per_pixel * area->w;
if (src != 0)
{
for (i = 0; i < area->h; i++)
{
memcpy((void *)dst, (void *)src, src_offset);
dst += dst_offset;
src += src_offset;
}
}
else
{
for (i = 0; i < area->h; i++)
{
pos = (os_color_t *)(dst + i * graphic->info.bytes_per_line);
for (j = 0; j < area->w; j++)
{
*pos = area->color;
pos++;
}
}
}
os_clean_dcache(info->framebuffer_curr, graphic->info.framebuffer_size);
}分步详解(os_graphic_frame_fill)
第一步:计算目标/源地址偏移(无条件执行)
uint32_t dst = (uint32_t)info->framebuffer_curr + area->y * graphic->info.bytes_per_line +
area->x * graphic->info.bytes_per_pixel;
uint32_t src = (uint32_t)area->buffer;
uint32_t dst_offset = info->bytes_per_pixel * info->width;
uint32_t src_offset = info->bytes_per_pixel * area->w;计算目标区域起始地址(framebuffer_curr + y*每行字节 + x*每像素字节),以及行步长。
数值演算:设 RGB565、宽 240,
area = {x=10, y=20, w=100, h=50, color=红, buffer=图像}:
计算项 代入计算 结果 dstframebuffer_curr + 20*480 + 10*2+9620dst_offset2 * 240480src_offset2 * 100200
第二步:按"图像数据"填充(条件 src != 0)
if (src != 0)
{
for (i = 0; i < area->h; i++)
{
memcpy((void *)dst, (void *)src, src_offset);
dst += dst_offset;
src += src_offset;
}
}area->buffer 非空时,逐行 memcpy 图像数据到帧缓冲(每行复制 src_offset 字节,目标按 dst_offset 换行)。
第三步:按"纯色"填充(条件 src == 0)
else
{
for (i = 0; i < area->h; i++)
{
pos = (os_color_t *)(dst + i * graphic->info.bytes_per_line);
for (j = 0; j < area->w; j++)
{
*pos = area->color;
pos++;
}
}
}
os_clean_dcache(info->framebuffer_curr, graphic->info.framebuffer_size);area->buffer 为空时,逐像素填 area->color 形成色块。最后 os_clean_dcache 清理 D-Cache(保证 DMA/刷屏时数据一致)。
条件走向总结(os_graphic_frame_fill)
os_graphic_frame_fill() 条件走向总结:
├── 计算 dst/src/dst_offset/src_offset
├── src != 0(有图像数据)
│ └── 逐行 memcpy 图像到帧缓冲
├── src == 0(纯色填充)
│ └── 逐像素填 area->color
└── os_clean_dcache(framebuffer_curr, size)4.4 框架层控制入口(os_graphic_control)
函数签名
static os_err_t os_graphic_control(os_device_t *device, int cmd, void *args)参数 / 返回值说明
| 参数 | 类型 | 说明 |
|---|---|---|
device | os_device_t * | 设备指针 |
cmd | int | 控制命令(OS_GRAPHIC_CTRL_*,见 3.1) |
args | void * | 命令参数指针 |
| 返回值 | 说明 |
|---|---|
OS_SUCCESS | 命令处理成功 |
| 其他 | DRAW_ADD_FONT/SET_FONT 的返回值 |
完整源码
static os_err_t os_graphic_control(os_device_t *device, int cmd, void *args)
{
os_graphic_t *graphic;
os_err_t ret = OS_SUCCESS;
OS_ASSERT(device);
graphic = (os_graphic_t *)device;
switch (cmd)
{
case OS_GRAPHIC_CTRL_POWERON:
if (graphic->ops->display_on)
{
graphic->ops->display_on(device, OS_TRUE);
}
break;
case OS_GRAPHIC_CTRL_POWEROFF:
if (graphic->ops->display_on)
{
graphic->ops->display_on(device, OS_FALSE);
}
break;
case OS_GRAPHIC_CTRL_GET_INFO:
memcpy(args, &graphic->info, sizeof(os_graphic_info_t));
break;
case OS_GRAPHIC_CTRL_DISP_AREA:
if (graphic->ops->display_area)
{
graphic->ops->display_area(device, (os_graphic_area_t *)args);
}
break;
case OS_GRAPHIC_CTRL_FRAME_NEXT:
os_graphic_frame_next(device, (uint8_t **)args);
break;
case OS_GRAPHIC_CTRL_FRAME_SET:
os_graphic_frame_set(device, (uint8_t *)args);
break;
case OS_GRAPHIC_CTRL_FRAME_FILL:
os_graphic_frame_fill(device, (os_graphic_area_t *)args);
break;
case OS_GRAPHIC_CTRL_FRAME_FLUSH:
if (graphic->ops->frame_flush)
{
graphic->ops->frame_flush(device);
g_frame_id = 1 - g_frame_id;
}
break;
#ifdef OS_GRAPHIC_DRAW_ENABLE
case OS_GRAPHIC_CTRL_DRAW_POINT:
if (graphic->draw_ops->draw_point)
{
graphic->draw_ops->draw_point(graphic, (os_graphic_point_t *)args);
}
break;
case OS_GRAPHIC_CTRL_DRAW_LINE:
if (graphic->draw_ops->draw_line)
{
graphic->draw_ops->draw_line(graphic, (os_graphic_line_t *)args);
}
break;
case OS_GRAPHIC_CTRL_DRAW_RECT:
if (graphic->draw_ops->draw_rect)
{
graphic->draw_ops->draw_rect(graphic, (os_graphic_rect_t *)args);
}
break;
case OS_GRAPHIC_CTRL_DRAW_CIRCLE:
if (graphic->draw_ops->draw_circle)
{
graphic->draw_ops->draw_circle(graphic, (os_graphic_circle_t *)args);
}
break;
case OS_GRAPHIC_CTRL_DRAW_SOLID_RECT:
if (graphic->draw_ops->draw_solid_rect)
{
graphic->draw_ops->draw_solid_rect(graphic, (os_graphic_rect_t *)args);
}
break;
case OS_GRAPHIC_CTRL_DRAW_SOLID_CIRCLE:
if (graphic->draw_ops->draw_solid_circle)
{
graphic->draw_ops->draw_solid_circle(graphic, (os_graphic_circle_t *)args);
}
break;
case OS_GRAPHIC_CTRL_DRAW_IMAGE:
if (graphic->draw_ops->draw_image)
{
graphic->draw_ops->draw_image(graphic, (os_graphic_area_t *)args);
}
break;
case OS_GRAPHIC_CTRL_DRAW_CHAR:
if (graphic->draw_ops->draw_char)
{
graphic->draw_ops->draw_char(graphic, (uint32_t)args);
}
break;
case OS_GRAPHIC_CTRL_DRAW_STRING:
if (graphic->draw_ops->draw_string)
{
graphic->draw_ops->draw_string(graphic, (uint8_t *)args);
}
break;
case OS_GRAPHIC_CTRL_DRAW_SET_TXT_CURSOR:
if (graphic->draw_ops->set_txt_cursor)
{
graphic->draw_ops->set_txt_cursor(graphic, (os_graphic_pos_t *)args);
}
break;
case OS_GRAPHIC_CTRL_DRAW_SET_TXT_COLOR:
if (graphic->draw_ops->set_txt_color)
{
graphic->draw_ops->set_txt_color(graphic, (os_color_t *)args);
}
break;
case OS_GRAPHIC_CTRL_DRAW_ADD_FONT:
if (graphic->draw_ops->add_font)
{
ret = graphic->draw_ops->add_font(graphic, (os_graphic_font_t *)args);
}
break;
case OS_GRAPHIC_CTRL_DRAW_SET_FONT:
if (graphic->draw_ops->set_font)
{
ret = graphic->draw_ops->set_font(graphic, (char *)args);
}
break;
case OS_GRAPHIC_CTRL_DRAW_CLEAR:
if (graphic->draw_ops->clear)
{
graphic->draw_ops->clear(graphic, (os_color_t *)args);
}
break;
#endif
#ifdef OS_GRAPHIC_GPU_ENABLE
case OS_GRAPHIC_CTRL_GPU_BLEND:
if (graphic->ops->gpu_blend)
{
graphic->ops->gpu_blend(device, (struct os_device_gpu_info *)args);
}
break;
case OS_GRAPHIC_CTRL_GPU_ALPHAMAP_BLEND:
if (graphic->ops->gpu_alphamap_blend)
{
graphic->ops->gpu_alphamap_blend(device, (struct os_device_gpu_info *)args);
}
break;
case OS_GRAPHIC_CTRL_GPU_BLIT:
if (graphic->ops->gpu_blit)
{
graphic->ops->gpu_blit(device, (struct os_device_gpu_info *)args);
}
break;
case OS_GRAPHIC_CTRL_GPU_WAIT:
if (graphic->ops->gpu_wait_finish)
{
graphic->ops->gpu_wait_finish(device);
}
break;
#endif
default:
break;
}
return ret;
}分步详解
第一步:电源控制(条件 POWERON/POWEROFF)
case OS_GRAPHIC_CTRL_POWERON:
if (graphic->ops->display_on)
{
graphic->ops->display_on(device, OS_TRUE);
}
break;
case OS_GRAPHIC_CTRL_POWEROFF:
if (graphic->ops->display_on)
{
graphic->ops->display_on(device, OS_FALSE);
}
break;调用屏驱动的 display_on 开/关显示。OS_TRUE 开屏、OS_FALSE 关屏。
第二步:获取信息 / 显示区域(条件 GET_INFO/DISP_AREA)
case OS_GRAPHIC_CTRL_GET_INFO:
memcpy(args, &graphic->info, sizeof(os_graphic_info_t));
break;
case OS_GRAPHIC_CTRL_DISP_AREA:
if (graphic->ops->display_area)
{
graphic->ops->display_area(device, (os_graphic_area_t *)args);
}
break;GET_INFO 把 graphic->info 拷贝到 args(调用者提供 os_graphic_info_t)。DISP_AREA 调用屏驱动把指定区域画到屏幕(无缓冲时绘制走这里)。
第三步:framebuffer 操作(条件 FRAME_*)
case OS_GRAPHIC_CTRL_FRAME_NEXT:
os_graphic_frame_next(device, (uint8_t **)args);
break;
case OS_GRAPHIC_CTRL_FRAME_SET:
os_graphic_frame_set(device, (uint8_t *)args);
break;
case OS_GRAPHIC_CTRL_FRAME_FILL:
os_graphic_frame_fill(device, (os_graphic_area_t *)args);
break;
case OS_GRAPHIC_CTRL_FRAME_FLUSH:
if (graphic->ops->frame_flush)
{
graphic->ops->frame_flush(device);
g_frame_id = 1 - g_frame_id;
}
break;FRAME_NEXT:返回下一个可绘制的缓冲(args为uint8_t **);FRAME_SET:设置当前缓冲(args为uint8_t *);FRAME_FILL:填充当前缓冲(args为os_graphic_area_t *);FRAME_FLUSH:把当前缓冲刷到屏幕,并翻转g_frame_id(1 - g_frame_id)实现双缓冲切换。
数值演算(双缓冲翻转):
g_frame_id初始 0,每次FRAME_FLUSH后1 - g_frame_id:
次数 g_frame_id 下一次 FRAME_NEXT 返回 初始 0 framebuffer[0]1 次 FLUSH 后 1 framebuffer[1]2 次 FLUSH 后 0 framebuffer[0]
第四步:绘制命令(条件 OS_GRAPHIC_DRAW_ENABLE)
#ifdef OS_GRAPHIC_DRAW_ENABLE
case OS_GRAPHIC_CTRL_DRAW_POINT:
if (graphic->draw_ops->draw_point)
{
graphic->draw_ops->draw_point(graphic, (os_graphic_point_t *)args);
}
break;
#endifOS_GRAPHIC_DRAW_ENABLE 时,绘制命令调用 draw_ops 里对应的绘制函数。以 DRAW_POINT 为例,draw_ops->draw_point 的实现(graphic.c 的 os_graphic_draw_point)在 framebuffer_num > 0 时写帧缓冲,否则退化为 display_area(见 4.3 的说明)。
条件走向总结(os_graphic_control)
os_graphic_control() 条件走向总结:
├── switch(cmd)
│ ├── POWERON/POWEROFF → ops->display_on(OS_TRUE/FALSE)
│ ├── GET_INFO → memcpy(args, &info)
│ ├── DISP_AREA → ops->display_area(args)
│ ├── FRAME_NEXT → frame_next(args)
│ ├── FRAME_SET → frame_set(args)
│ ├── FRAME_FILL → frame_fill(args)
│ ├── FRAME_FLUSH → ops->frame_flush + g_frame_id = 1 - g_frame_id
│ ├── OS_GRAPHIC_DRAW_ENABLE 下 DRAW_* → draw_ops->xxx(args)
│ ├── OS_GRAPHIC_GPU_ENABLE 下 GPU_* → ops->gpu_xxx(args)
│ └── default → 不处理
└── 返回 ret4.5 绘制函数库(os_graphic_draw_*)
当 OS_GRAPHIC_DRAW_ENABLE 时,graphic.c 内置一套绘制函数库(draw_ops)。核心思想:有帧缓冲时直接写帧缓冲,无帧缓冲时退化为 display_area 逐区域写屏。
以 os_graphic_draw_point(画点) 为例(分步详解):
static void os_graphic_draw_point(os_graphic_t *graphic, os_graphic_point_t *point)
{
os_graphic_area_t area;
uint8_t *fb;
if (graphic->info.framebuffer_num > 0)
{
fb = graphic->info.framebuffer_curr;
OS_ASSERT(fb);
fb += point->y * graphic->info.bytes_per_line + point->x * graphic->info.bytes_per_pixel;
*(os_color_t *)fb = point->color;
}
else
{
area.x = point->x;
area.y = point->y;
area.w = 1;
area.h = 1;
area.color = point->color;
area.buffer = OS_NULL;
graphic->ops->display_area(&graphic->parent, &area);
}
}分步详解
第一步:有帧缓冲时直接写显存(条件 framebuffer_num > 0)
if (graphic->info.framebuffer_num > 0)
{
fb = graphic->info.framebuffer_curr;
OS_ASSERT(fb);
fb += point->y * graphic->info.bytes_per_line + point->x * graphic->info.bytes_per_pixel;
*(os_color_t *)fb = point->color;
}有帧缓冲:计算像素地址(framebuffer_curr + y*每行字节 + x*每像素字节),直接把颜色写入显存(后续靠 FRAME_FLUSH 统一刷屏)。
数值演算(像素地址):设 RGB565、宽 240(每行 480 字节),
point = {x=10, y=20, color=红}:
计算项 代入计算 结果 point->y * bytes_per_line20 * 4809600point->x * bytes_per_pixel10 * 220偏移 9600 + 209620写入 *(os_color_t*)(fb+9620) = 0xF800画红点
第二步:无帧缓冲时退化为显示区域(条件 framebuffer_num == 0)
else
{
area.x = point->x;
area.y = point->y;
area.w = 1;
area.h = 1;
area.color = point->color;
area.buffer = OS_NULL;
graphic->ops->display_area(&graphic->parent, &area);
}无帧缓冲:把"点"包装成 1×1 的 os_graphic_area,调 display_area 直接让屏驱动写屏(慢,但无需缓冲)。
再看一个算法型绘制函数:os_graphic_draw_line(Bresenham 直线)完整源码(graphic.c):
static void os_graphic_draw_line(os_graphic_t *graphic, os_graphic_line_t *line)
{
uint8_t *fb = 0;
int32_t i = 0, xerr = 0, yerr = 0;
int32_t delta_x, delta_y, distance, incx, incy, row, col;
int32_t len;
os_color_t *pos;
os_graphic_area_t area;
os_graphic_point_t point;
if (line->y1 == line->y2)
{
len = (line->x2 > line->x1) ? (line->x2 - line->x1) : (line->x1 - line->x2);
if (graphic->info.framebuffer_num > 0)
{
fb = graphic->info.framebuffer_curr;
OS_ASSERT(fb);
fb += line->y1 * graphic->info.bytes_per_line + line->x1 * graphic->info.bytes_per_pixel;
pos = (os_color_t *)fb;
for (i = 0; i < len; i++)
{
*pos = line->color;
pos++;
}
}
else
{
area.x = line->x1;
area.y = line->y1;
area.w = line->x2 - line->x1;
area.h = 1;
area.color = line->color;
area.buffer = OS_NULL;
graphic->ops->display_area(&graphic->parent, &area);
}
return;
}
delta_x = line->x2 - line->x1;
delta_y = line->y2 - line->y1;
row = line->x1;
col = line->y1;
if (delta_x > 0)
incx = 1;
else if (delta_x == 0)
incx = 0;
else
{
incx = -1;
delta_x = -delta_x;
}
if (delta_y > 0)
incy = 1;
else if (delta_y == 0)
incy = 0;
else
{
incy = -1;
delta_y = -delta_y;
}
if (delta_x > delta_y)
distance = delta_x;
else
distance = delta_y;
for (i = 0; i <= distance + 1; i++)
{
point.color = line->color;
point.x = row;
point.y = col;
os_graphic_draw_point(graphic, &point);
xerr += delta_x;
yerr += delta_y;
if (xerr > distance)
{
xerr -= distance;
row += incx;
}
if (yerr > distance)
{
yerr -= distance;
col += incy;
}
}
}分步详解(os_graphic_draw_line)
第一步:水平线快速路径(条件 line->y1 == line->y2)
水平线(y 相等)直接按行填充:有缓冲时用 pos 指针连续写 len 个像素;无缓冲时包装成 1×len 的 area 调 display_area。返回,不走 Bresenham。
第二步:计算增量与方向(条件:非水平线)
delta_x = line->x2 - line->x1;
delta_y = line->y2 - line->y1;
row = line->x1;
col = line->y1;row/col 是当前游标(起点),delta_x/delta_y 是总增量。
第三步:决定步进方向(条件 delta_x/delta_y 正负)
if (delta_x > 0)
incx = 1;
else if (delta_x == 0)
incx = 0;
else
{
incx = -1;
delta_x = -delta_x;
}- 增量>0 →
incx = 1(向右); - 增量为0 →
incx = 0(垂直); - 增量<0 →
incx = -1(向左)且delta_x取绝对值。incy同理决定 y 方向步进。
第四步:Bresenham 累计误差画线(无条件循环)
if (delta_x > delta_y)
distance = delta_x;
else
distance = delta_y;
for (i = 0; i <= distance + 1; i++)
{
point.color = line->color;
point.x = row;
point.y = col;
os_graphic_draw_point(graphic, &point);
xerr += delta_x;
yerr += delta_y;
if (xerr > distance)
{
xerr -= distance;
row += incx;
}
if (yerr > distance)
{
yerr -= distance;
col += incy;
}
}distance 取长轴(delta_x 与 delta_y 较大者),决定循环次数。每步把当前 (row, col) 画一个点,然后用"累计误差"(xerr/yerr 累加长轴增量)判断是否沿短轴进一位——这正是 Bresenham 用整数运算避免浮点除法的核心。
数值演算(Bresenham):设
line = {x1=0, y1=0, x2=4, y2=2},则delta_x=4、delta_y=2、distance=4、incx=1、incy=1。循环体顺序为先画点、再累加 xerr/yerr、最后判断进退:
迭代 画点 (row,col) xerr 累加后 yerr 累加后 xerr>4? yerr>4? 下一轮 (row,col) 初始 — — — — — (0,0) i=0 (0,0) 0+4=4 0+2=2 否 否 (0,0) i=1 (0,0) 4+4=8 2+2=4 是→xerr=4,row=1 否 (1,0) i=2 (1,0) 4+4=8 4+2=6 是→xerr=4,row=2 是→yerr=2,col=1 (2,1) i=3 (2,1) 4+4=8 2+2=4 是→xerr=4,row=3 否 (3,1) i=4 (3,1) 4+4=8 4+2=6 是→xerr=4,row=4 是→yerr=2,col=2 (4,2) i=5 (4,2) 4+4=8 2+2=4 是→xerr=4,row=5 否 (5,2) 画出的点序列为
(0,0), (1,0), (2,1), (3,1), (4,2)(i=0与i=1都落在起点(0,0))。整体沿长轴 x 每步进 1,短轴 y 在误差累积到distance=4时才进 1,最终形成从(0,0)到(4,2)、斜率为 1/2 的直线。
说明:
draw_circle(中点圆)、draw_rect(4 条直线)、draw_solid_*(逐行填充)、draw_char/draw_string(逐位字形点阵)等结构与上述两例类似,核心统一遵循"有缓冲直接写显存、无缓冲退化display_area"的双路径设计,此处不再逐一展开。
五、使用示例
以下示例基于项目头文件
drivers/graphic/graphic.h的接口,均含返回值检查。
示例 1:初始化 LCD 并点亮
#include <graphic/graphic.h>
#include <device.h>
#define GRAPHIC_DEV_NAME "lcd"
void graphic_init_demo(void)
{
os_device_t *dev;
/* 1. 打开显示设备 */
dev = os_device_open_s(GRAPHIC_DEV_NAME);
if (dev == OS_NULL)
{
os_kprintf("Failed to open graphic device\r\n");
return;
}
/* 2. 点亮屏幕 */
os_err_t ret = os_device_control(dev, OS_GRAPHIC_CTRL_POWERON, OS_NULL);
if (ret != OS_SUCCESS)
{
os_kprintf("Power on failed: %d\r\n", ret);
os_device_close(dev);
return;
}
os_kprintf("LCD powered on\r\n");
os_device_close(dev);
}实际效果:打开 "lcd" 后,os_device_control(dev, OS_GRAPHIC_CTRL_POWERON, OS_NULL) 调屏驱动的 display_on(OS_TRUE) 点亮屏幕。
示例 2:获取显示信息
#include <graphic/graphic.h>
#include <device.h>
void get_graphic_info_demo(void)
{
os_device_t *dev;
os_graphic_info_t info;
dev = os_device_open_s("lcd");
if (dev == OS_NULL) return;
/* 获取显示信息 */
os_err_t ret = os_device_control(dev, OS_GRAPHIC_CTRL_GET_INFO, &info);
if (ret != OS_SUCCESS)
{
os_kprintf("Get graphic info failed: %d\r\n", ret);
os_device_close(dev);
return;
}
os_kprintf("Graphic Info:\r\n");
os_kprintf(" Size: %d x %d\r\n", info.width, info.height);
os_kprintf(" Pixel fmt:%d\r\n", info.pixel_format);
os_kprintf(" Bpp: %d bits, %d bytes\r\n", info.bits_per_pixel, info.bytes_per_pixel);
os_kprintf(" Line: %d bytes\r\n", info.bytes_per_line);
os_kprintf(" Framebuf: %d x %d bytes\r\n", info.framebuffer_num, info.framebuffer_size);
os_device_close(dev);
}实际效果:os_device_control(dev, OS_GRAPHIC_CTRL_GET_INFO, &info) 把 graphic->info 拷贝到 info,打印分辨率、格式、位深、行字节数、帧缓冲信息。
示例 3:双缓冲绘制与刷屏(防撕裂)
#include <graphic/graphic.h>
#include <device.h>
#include <os_memory.h>
void double_buffer_demo(void)
{
os_device_t *dev;
os_graphic_info_t info;
uint8_t *fb;
os_graphic_rect_t rect;
dev = os_device_open_s("lcd");
if (dev == OS_NULL) return;
/* 1. 获取显示信息,得到帧缓冲大小 */
os_err_t ret = os_device_control(dev, OS_GRAPHIC_CTRL_GET_INFO, &info);
if (ret != OS_SUCCESS)
{
os_device_close(dev);
return;
}
/* 2. 分配并添加两个帧缓冲(双缓冲) */
fb = os_malloc(info.framebuffer_size);
if (fb == OS_NULL) { os_device_close(dev); return; }
ret = os_device_control(dev, OS_GRAPHIC_CTRL_FRAME_SET, fb);
if (ret != OS_SUCCESS) { os_free(fb); os_device_close(dev); return; }
/* 3. 绘制一个实心矩形(画到当前缓冲) */
rect.x = 10; rect.y = 10; rect.w = 100; rect.h = 50;
rect.color = OS_COLOR_RED;
rect.buffer = OS_NULL;
ret = os_device_control(dev, OS_GRAPHIC_CTRL_FRAME_FILL, &rect);
if (ret != OS_SUCCESS) { os_free(fb); os_device_close(dev); return; }
/* 4. 刷屏 */
ret = os_device_control(dev, OS_GRAPHIC_CTRL_FRAME_FLUSH, OS_NULL);
if (ret != OS_SUCCESS) { os_free(fb); os_device_close(dev); return; }
os_kprintf("Drew red rect and flushed\r\n");
os_free(fb);
os_device_close(dev);
}实际效果:分配帧缓冲 → FRAME_SET 设置当前缓冲 → FRAME_FILL 画红色实心矩形到缓冲 → FRAME_FLUSH 刷到屏幕。通过"先画缓冲再刷屏"避免逐像素刷新的闪烁。
示例 4:绘制文字(需启用绘制函数库)
#include <graphic/graphic.h>
#include <device.h>
void draw_text_demo(void)
{
os_device_t *dev;
os_graphic_pos_t pos;
os_color_t color = OS_COLOR_WHITE;
dev = os_device_open_s("lcd");
if (dev == OS_NULL) return;
/* 1. 设置文本光标与颜色 */
pos.x = 0;
pos.y = 0;
os_err_t ret = os_device_control(dev, OS_GRAPHIC_CTRL_DRAW_SET_TXT_CURSOR, &pos);
if (ret != OS_SUCCESS) { os_device_close(dev); return; }
ret = os_device_control(dev, OS_GRAPHIC_CTRL_DRAW_SET_TXT_COLOR, &color);
if (ret != OS_SUCCESS) { os_device_close(dev); return; }
/* 2. 绘制字符串(需要已添加字体,见 4.4 的 add_font) */
const char *str = "OneOS GUI";
ret = os_device_control(dev, OS_GRAPHIC_CTRL_DRAW_STRING, (void *)str);
if (ret != OS_SUCCESS) { os_device_close(dev); return; }
/* 3. 刷屏 */
os_device_control(dev, OS_GRAPHIC_CTRL_FRAME_FLUSH, OS_NULL);
os_kprintf("Drew text 'OneOS GUI'\r\n");
os_device_close(dev);
}实际效果:设置光标到 (0,0)、文字颜色白色,DRAW_STRING 绘制 "OneOS GUI",FRAME_FLUSH 刷屏。注意:绘制文字前需先通过 DRAW_ADD_FONT 添加字体并 DRAW_SET_FONT 设置(本示例未包含字体添加,实际使用需配合字体数据)。
示例 5:清屏
#include <graphic/graphic.h>
#include <device.h>
void clear_screen_demo(void)
{
os_device_t *dev;
os_color_t color = OS_COLOR_BLACK;
dev = os_device_open_s("lcd");
if (os_device_control(dev, OS_GRAPHIC_CTRL_DRAW_CLEAR, &color) != OS_SUCCESS)
{
os_kprintf("Clear screen failed\r\n");
os_device_close(dev);
return;
}
/* 刷屏显示全黑 */
os_device_control(dev, OS_GRAPHIC_CTRL_FRAME_FLUSH, OS_NULL);
os_kprintf("Screen cleared\r\n");
os_device_close(dev);
}实际效果:DRAW_CLEAR 用 OS_COLOR_BLACK 填充整屏为黑色,FRAME_FLUSH 刷屏显示全黑。
六、更换 LCD 屏(Graphic 驱动移植)—— 全局视角
本节从全局视角讲解:当你要换一块 LCD 屏(或换 MCU 导致屏驱动失效)时,OneOS 的 Graphic 驱动要做什么才能在新屏上跑起来。核心结论与 5.2~5.33 一致:应用层与框架层完全不用动,只需要新写/移植 BSP 屏驱动。
6.1 认识 BSP 层驱动:ili9341.c / st7789vw.c 等
BSP 层屏驱动位于 drivers/graphic/,每块屏一个 .c 文件(ili9341.c、st7789vw.c、otm8009a.c、ht240s030.c、atk_tflcd9341.c)。以 ILI9341(drivers/graphic/ili9341.c)为例,它实现了 struct os_graphic_ops:
static const struct os_graphic_ops ili9341_ops = {
.display_on = lcd_display_on,
.frame_flush = lcd_framebuffer_flush,
.display_area = lcd_display_area,
};lcd_display_on:控制背光/显存开关;lcd_framebuffer_flush:把 framebuffer 数据整体刷到 LCD(发 ILI9341 的写显存命令 + 传输数据);lcd_display_area:把指定区域写到 LCD(无缓冲模式用)。
init 注册(屏驱动初始化时调用):
os_graphic_t *graphic = os_calloc(1, sizeof(os_graphic_t));
graphic->ops = &ili9341_ops;
os_graphic_register("lcd", graphic);(1)"根据所选 LCD 屏决定"到底是怎么决定的?—— 三层选择机制
Graphic 驱动同样通过 Kconfig + weave.yaml 只编译所选屏的驱动:
第一层:Kconfig 选择屏型号(drivers/graphic/Kconfig,见 3.3)——如勾选 BSP_USING_ILI9341、OS_USING_ST7789VW、OS_USING_HT240S030 等。
第二层:weave.yaml 条件编译(drivers/graphic/weave.yaml):
# 组名
group_name: graphic
# 依赖宏控
depend_macro:
- OS_USING_GRAPHIC
# 源码
source_file:
- graphic.c
- dsi.c ? {is_define("OS_USING_DSI")}
- otm8009a.c ? {is_define("BSP_USING_OTM8009A")}
- ili9341.c ? {is_define("BSP_USING_ILI9341")}
- st7789vw.c ? {is_define("OS_USING_ST7789VW")}
- atk_tflcd9341.c ? {is_define("BSP_USING_ATKTFLCD9341")}
- ht240s030.c ? {is_define("OS_USING_HT240S030")}graphic.c(框架层)由 OS_USING_GRAPHIC 控制必编译;每块屏的 .c 由各自的宏控制。每块屏驱动平级、各按自己的宏条件编译——这正是"每个屏驱动平级实现、各自条件编译"的体现。
6.2 移植前的全局视角:分层全景图
回顾 Graphic 设备的完整分层(本书 1.1 节已详解):应用层 → Graphic 框架层(graphic.c)→ BSP 屏驱动(ili9341.c 等)→ 硬件层。
移植的关键洞察:
- 只有 BSP 屏驱动和具体屏强相关(依赖屏的寄存器命令、接口 SPI/I2C/DSI、背光/电源引脚)。
- 框架层和应用层与具体屏无关(只依赖
graphic.h定义的抽象接口)。 - 所以"换 LCD 屏"本质上 = 新写/移植一个 BSP 屏驱动(实现
struct os_graphic_ops三个函数 + init 注册)。
6.3 移植需要提供的"接口契约"
要移植,必须先满足 BSP 层与框架层之间的契约——也就是 struct os_graphic_ops 的三个核心函数(见 2.4 节):
| ops 函数 | 需实现的能力 | 新屏上对应什么 |
|---|---|---|
display_on | 开/关显示(背光/显存) | 拉高/拉低背光引脚、使能/失能显存 |
frame_flush | 把 framebuffer 整体刷到屏 | 发"写显存"命令 + 传输整屏数据(SPI/I2C/DSI) |
display_area | 把指定区域写到屏 | 设置窗口(set_column/set_page)+ 写区域数据 |
此外还需要:
- 屏初始化序列:上电时发送屏的初始化寄存器序列(如 ILI9341 的
0x36方向、0x2A/0x2B窗口、0x29开显示等)。 - 像素格式匹配:屏实际支持的像素格式必须与 Kconfig 的
OS_GRAPHIC_LCD_FORMAT_*一致(否则颜色错乱)。 - 通信接口:通过 SPI/I2C/DSI 操作屏寄存器(若走 SPI,参考 5.5 SPI 设备)。
6.4 移植的标准流程(5 步)
以"为一块 SPI 接口的 LCD 屏写 Graphic 驱动"为例:
第 1 步:实现屏驱动 ops 的三个函数
static void my_lcd_display_on(struct os_device *dev, os_bool_t on_off)
{
/* 控制背光引脚 */
if (on_off)
os_pin_write(MY_LCD_BL_PIN, PIN_HIGH);
else
os_pin_write(MY_LCD_BL_PIN, PIN_LOW);
}
static void my_lcd_framebuffer_flush(struct os_device *dev)
{
os_graphic_t *graphic = (os_graphic_t *)dev;
/* 发写显存命令 + 传输整屏 framebuffer 数据 */
my_lcd_set_window(0, 0, graphic->info.width, graphic->info.height);
my_lcd_write_data(graphic->info.framebuffer_curr, graphic->info.framebuffer_size);
}
static void my_lcd_display_area(struct os_device *dev, os_graphic_area_t *area)
{
/* 设置窗口并写区域数据 */
my_lcd_set_window(area->x, area->y, area->w, area->h);
if (area->buffer)
my_lcd_write_data(area->buffer, area->w * area->h * sizeof(os_color_t));
else
my_lcd_fill_color(area); /* 纯色填充 */
}第 2 步:定义并导出 ops 表
static const struct os_graphic_ops my_lcd_ops = {
.display_on = my_lcd_display_on,
.frame_flush = my_lcd_framebuffer_flush,
.display_area = my_lcd_display_area,
};第 3 步:写 init 并注册
static int os_hw_my_lcd_init(void)
{
os_graphic_t *graphic = os_calloc(1, sizeof(os_graphic_t));
OS_ASSERT(graphic);
graphic->ops = &my_lcd_ops;
my_lcd_hw_init(); /* 屏初始化序列 */
os_graphic_register("lcd", graphic);
return 0;
}
OS_INIT_CALL(os_hw_my_lcd_init, OS_INIT_LEVEL_DEVICE, OS_INIT_SUBLEVEL_HIGH);第 4 步:配置像素格式与宽高(Kconfig)
确保 OS_GRAPHIC_LCD_WIDTH/HEIGHT 与屏一致、OS_GRAPHIC_LCD_FORMAT_* 与屏支持格式一致。
第 5 步:验证
用本节"示例 2(获取信息)"确认宽高/格式正确,用"示例 3(画矩形刷屏)"确认 frame_flush 正常,用"示例 4(画文字)"确认绘制函数库正常。若颜色错乱,检查 OS_GRAPHIC_LCD_FORMAT_* 与屏实际格式。
6.5 移植检查清单(快速自查)
| 检查项 | 是否满足 |
|---|---|
struct os_graphic_ops 的 display_on/frame_flush/display_area 已实现并绑定 | ☐ |
| 屏初始化序列正确(方向/窗口/开显示命令) | ☐ |
OS_GRAPHIC_LCD_FORMAT_* 与屏实际像素格式一致 | ☐ |
os_graphic_register 前已填充 ops | ☐ |
| 新屏已在 weave.yaml 加条件编译、Kconfig 加选择项 | ☐ |
frame_flush 能正确刷整屏(含 D-Cache 一致性) | ☐ |
无缓冲时 display_area 能正常显示 | ☐ |
6.6 移植的价值与注意事项
价值:一旦 BSP 屏驱动移植完成,应用层代码一行不改即可在不同 LCD 屏(ILI9341/ST7789VW/OTM8009A 等)间复用。
注意事项:
- 像素格式必须匹配:
os_color_t类型由OS_GRAPHIC_LCD_FORMAT_*决定,若与屏实际格式不符,颜色错乱。 - D-Cache 一致性:刷屏涉及 DMA/CPU 访问 framebuffer,需
os_clean_dcache(graphic.c的frame_fill已做)。 - 整屏缓冲大小:
os_graphic_add_framebuffer要求size == framebuffer_size(整屏),别用局部小缓冲。
📝 本节小结
核心要点回顾
- 分层抽象设计:Graphic 设备基于 5.1 框架,
os_graphic_device内嵌os_device,通过os_graphic_ops与具体屏驱动解耦;os_device_ops只有.control,无 read/write。 - 双缓冲机制:
framebuffer[2]+FRAME_NEXT/FRAME_SET/FRAME_FILL/FRAME_FLUSH实现无撕裂刷屏,g_frame_id每次 FLUSH 翻转。 - 颜色格式:
os_color_t由OS_GRAPHIC_LCD_FORMAT_*决定(RGB565/RGB888/ARGB8888),OS_COLOR宏自动做位域转换。 - 绘制函数库:
OS_GRAPHIC_DRAW_ENABLE时内置draw_ops(点/线/矩形/圆/文字),有缓冲写显存、无缓冲退化display_area。 - 注册流程:
os_graphic_register配置设备类型/ops、填充 info、清空 framebuffer、注册设备并点亮屏幕。 - 控制命令:
os_graphic_control分发电源/信息/区域/framebuffer/绘制命令,DRAW_*和GPU_*命令受编译宏控制。 - 更换 LCD 屏移植(见第六章):移植核心 = 实现
struct os_graphic_ops三个函数 + init 注册;应用层与框架层不用改;换屏本质是换 weave.yaml 选择的那个屏驱动.c。
关键易错点速查
| 易错点 | 正确做法 |
|---|---|
期望 Graphic 设备用 os_device_read/write | os_graphic_ops 只有 .control,所有操作走 os_device_control |
os_graphic_add_framebuffer 传局部小缓冲 | 要求 size == framebuffer_size(整屏)→ 分配整屏大小 |
| 像素格式不匹配 | OS_GRAPHIC_LCD_FORMAT_* 必须与屏实际格式一致 → 否则颜色错乱 |
绘制前未加缓冲就用 FRAME_FILL | framebuffer_num == 0 时绘制退化写屏,但 FRAME_FLUSH 无缓冲无效 → 先 add_framebuffer |
| 画文字前没设置字体 | DRAW_STRING 依赖已 ADD_FONT/SET_FONT → 先添加并设置字体 |
双缓冲绘制后忘了 FRAME_FLUSH | 绘制只写内存,不刷屏看不到 → 必须 FRAME_FLUSH |
| 移植屏驱动没匹配像素格式 | 颜色错乱 → configure 阶段核对 OS_GRAPHIC_LCD_FORMAT_* |
| 刷屏没做 D-Cache 清理 | DMA/CPU 数据不一致 → graphic.c 已用 os_clean_dcache,移植时勿删 |
下一步
- 5.35 WLAN设备:了解 OneOS WLAN 无线设备的 STA/AP 模式、扫描/加入网络、事件回调与数据收发。