1. BinaryVector 库概述

BinaryVector 是专为大众汽车集团 CARIAD 车载显示系统定制的轻量级二进制位图向量渲染库,核心定位是为资源受限的车载 TFT/LCD 显示控制器(如 RA8876、SSD1963、ILI9488 等)提供高效率、低内存占用的图形绘制能力。它并非通用图形引擎,而是面向车规级人机界面(HMI)中静态图标、状态指示器、仪表盘刻度、矢量风格 UI 元素等高频复用场景而深度优化的嵌入式图形原语库。

该库的设计哲学体现典型的车规嵌入式工程思维: 确定性、可预测性、零动态内存分配、最小化 CPU 占用与总线带宽消耗 。其“Binary”前缀直指本质——所有图形数据以预编译的紧凑二进制格式存储于 Flash;“Vector”则表明其支持基于路径(Path)和基本几何图元(Line, Circle, Rectangle, Arc)的坐标变换与光栅化,而非传统位图的像素块拷贝。这种设计规避了在 MCU RAM 中缓存整帧位图(如 800×480@16bpp 需 768KB)的巨大开销,将图形资源的存储成本完全转移到 Flash,并通过高度内联的汇编优化光栅化内核,在 Cortex-M4/M7 级别 MCU 上实现亚毫秒级的单图元绘制。

在 CARIAD 的典型 HMI 架构中,BinaryVector 通常位于 HAL 层之上、UI 框架层之下,作为图形后端直接对接显示驱动(Display Driver)。它不依赖操作系统,可裸机运行;亦可无缝集成至 FreeRTOS 任务中,通过队列向显示任务投递渲染指令。其输出目标是显示控制器的帧缓冲区(Frame Buffer)或直接通过 SPI/I2C/DPI 总线写入显存,具体取决于硬件平台的显示接口配置。

2. 核心架构与数据流

2.1 整体分层结构

BinaryVector 采用清晰的三层架构,严格分离数据、逻辑与硬件:

层级 组件 职责 存储位置 运行时行为
数据层 (Data Layer) .bin 图形资源文件 存储经 binaryvector-compiler 工具链预处理的二进制向量数据,包含路径顶点、控制点、填充/描边属性、变换矩阵等 MCU Flash(常量段) 只读访问,无运行时解析开销
引擎层 (Engine Layer) bv_engine.c/h 执行坐标变换、裁剪、抗锯齿(可选)、光栅化算法;管理渲染状态(颜色、混合模式、裁剪窗口) MCU RAM(静态分配) 纯函数调用,无 malloc/free,状态机驱动
驱动层 (Driver Layer) bv_driver_xxx.c/h 提供统一的 bv_draw_pixel() bv_fill_rect() 等底层绘图钩子(Hook),由用户实现以适配具体显示控制器(如 STM32 LTDC、RA8876 SPI 写寄存器) MCU RAM / Flash 用户代码,决定最终像素如何写入物理显示设备

此架构确保了库的高度可移植性:更换显示硬件仅需重写驱动层,引擎层与数据层完全复用;更新 UI 图形仅需替换 Flash 中的 .bin 文件,无需重新编译固件。

2.2 关键数据结构解析

BinaryVector 的高效性根植于其精心设计的数据结构。所有图形对象均以 bv_object_t 结构体描述,其定义精简且无虚函数表(C 语言无类继承):

typedef struct {
    const uint8_t *data;      // 指向 Flash 中二进制数据起始地址
    uint32_t size;            // 数据总长度(字节)
    bv_transform_t transform; // 3x3 仿射变换矩阵(用于缩放、旋转、平移)
    bv_clip_region_t clip;    // 裁剪区域(x, y, width, height)
    bv_color_t color;         // 主色(用于描边、单色填充)
    bv_color_t fill_color;    // 填充色(若支持)
    uint8_t flags;            // BV_FLAG_ANTIALIAS, BV_FLAG_FILL, BV_FLAG_STROKE 等
} bv_object_t;

其中 bv_transform_t 是关键性能因子,定义为:

typedef struct {
    int32_t a, b, c; // 第一行:a*x + b*y + c
    int32_t d, e, f; // 第二行:d*x + e*y + f
    int32_t g, h, i; // 第三行(齐次坐标):g*x + h*y + i
} bv_transform_t;

所有变换运算使用 Q15 定点数(16位整数,15位小数),避免浮点运算的性能损耗与 FPU 依赖。例如,一个 0.5 倍缩放的变换矩阵为:

a=16384, b=0,   c=0
d=0,   e=16384, f=0
g=0,   h=0,   i=32768

(因 Q15 中 1.0 = 32768,故 0.5 = 16384)

bv_clip_region_t 实现硬件加速的矩形裁剪,引擎在光栅化前即判断当前像素是否在裁剪区域内,避免无效绘制。此结构直接映射到多数 TFT 控制器的硬件裁剪寄存器(如 ILI9341 的 CASET/PAET ),驱动层可将其透传至硬件。

3. 核心 API 详解

BinaryVector 的 API 设计遵循“最小完备集”原则,仅暴露必需接口,所有函数均为 static inline __attribute__((always_inline)) 修饰,确保零函数调用开销。

3.1 初始化与全局配置

// 初始化引擎,设置全局渲染参数
void bv_init(const bv_config_t *config);

typedef struct {
    bv_color_mode_t color_mode; // BV_COLOR_MODE_RGB565, BV_COLOR_MODE_ARGB8888
    uint8_t antialias_level;    // 0=禁用, 1=基础, 2=高质量(影响性能)
    uint8_t max_path_points;    // 光栅化路径时最大顶点缓存数(RAM 占用)
    void (*on_error)(bv_error_t); // 错误回调(如数据损坏、裁剪溢出)
} bv_config_t;

bv_config_t max_path_points 是关键权衡点:增大值可提升复杂贝塞尔曲线渲染精度,但增加 RAM 占用;CARIAD 推荐值为 32,足以覆盖仪表盘中绝大多数指针、刻度弧线。

3.2 渲染主流程 API

// 开始一帧渲染(可选,用于驱动层同步)
void bv_begin_frame(void);

// 绘制单个向量对象(核心函数)
bv_status_t bv_draw_object(const bv_object_t *obj);

// 批量绘制多个对象(减少状态切换开销)
bv_status_t bv_draw_objects(const bv_object_t *objs, uint16_t count);

// 结束一帧渲染(可选,用于驱动层刷新)
void bv_end_frame(void);

bv_draw_object() 是引擎入口,其内部执行严格顺序:

  1. 数据验证 :检查 obj->data 是否在合法 Flash 地址范围, obj->size 是否非零。
  2. 变换应用 :对所有顶点应用 obj->transform ,结果仍为 Q15 坐标。
  3. 裁剪计算 :根据 obj->clip 计算有效绘制区域边界框(Bounding Box)。
  4. 光栅化调度 :依据对象类型( BV_TYPE_PATH , BV_TYPE_CIRCLE 等)分发至对应光栅化器。
  5. 像素提交 :调用驱动层 bv_draw_pixel(x, y, color) bv_fill_rect(x,y,w,h,color)

3.3 驱动层钩子函数(必须由用户实现)

// 必须实现:绘制单个像素(用于抗锯齿、路径边缘)
void bv_draw_pixel(int32_t x, int32_t y, bv_color_t color);

// 必须实现:填充矩形(用于填充、背景、快速清屏)
void bv_fill_rect(int32_t x, int32_t y, int32_t w, int32_t h, bv_color_t color);

// 可选实现:绘制水平线(优化扫描线填充)
void bv_draw_hline(int32_t x, int32_t y, int32_t w, bv_color_t color);

// 可选实现:绘制垂直线(优化扫描线填充)
void bv_draw_vline(int32_t x, int32_t y, int32_t h, bv_color_t color);

在 STM32 平台对接 LTDC 时, bv_fill_rect() 可直接操作 DMA2D 外设,实现硬件加速填充;在 RA8876 平台, bv_draw_pixel() 则通过 SPI 发送 0x22 (GRAM Write)指令后写入 RGB565 数据。这些钩子是性能差异的关键,CARIAD 规范要求所有驱动实现必须保证 bv_draw_pixel() 在 100ns 内完成(Cortex-M7 @ 216MHz)。

3.4 高级功能 API

// 创建临时变换矩阵(用于局部缩放/旋转)
void bv_transform_create_scale(bv_transform_t *t, int32_t sx, int32_t sy);
void bv_transform_create_rotate(bv_transform_t *t, int32_t angle_q16); // angle in 1/65536 turns

// 合并变换(用于复合动画)
void bv_transform_multiply(bv_transform_t *out, const bv_transform_t *a, const bv_transform_t *b);

// 获取对象边界框(用于布局计算)
bv_status_t bv_object_get_bbox(const bv_object_t *obj, bv_rect_t *bbox);

bv_transform_create_rotate() 使用查表法(LUT)实现正余弦计算,LUT 存储于 Flash,大小仅 256 字节(每 1.4° 一个条目),避免实时三角函数计算。 bv_object_get_bbox() 对路径对象执行顶点变换后求极值,结果用于 UI 布局引擎的自动对齐。

4. 图形资源编译与工作流

BinaryVector 的效能高度依赖于前端资源编译工具链 binaryvector-compiler ,这是一个 Python 脚本,将 SVG 或 JSON 描述转换为优化的二进制格式。

4.1 输入源规范

CARIAD 强制要求输入为符合 ISO 26262 ASIL-B 级别的 SVG 子集:

  • 仅支持 <path> <circle> <rect> <line> 元素
  • 禁止 <text> <gradient> <filter> 等复杂特性
  • 路径数据必须为绝对坐标( M , L , C , Z ),禁用相对坐标( m , l , c
  • 所有坐标、尺寸单位为 px,基准 DPI 固定为 96

示例 SVG 片段(车速表指针):

<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
  <path d="M50,50 L50,20 L45,25 L50,20 L55,25 Z" fill="#FF0000"/>
</svg>

4.2 编译过程与输出

binaryvector-compiler 执行以下步骤:

  1. SVG 解析与归一化 :将所有坐标转换为 0-65535 范围内的 Q16 整数,消除浮点误差。
  2. 路径优化 :合并共线线段、简化贝塞尔曲线(Douglas-Peucker 算法,容差 0.5px)。
  3. 指令编码 :将几何操作编译为紧凑字节码(Bytecode),例如:
    • 0x01 = DRAW_LINE_TO (后续 4 字节为 Q16 x,y)
    • 0x02 = DRAW_BEZIER_TO (后续 12 字节为 3 个 Q16 控制点)
    • 0x03 = FILL_PATH (结束路径并填充)
  4. 元数据注入 :在二进制头部写入版本号、校验和(CRC32)、边界框信息。

输出文件 speedometer_pointer.bin 结构如下:

Offset 0x00: uint32_t magic (0xBV010000)
Offset 0x04: uint32_t crc32
Offset 0x08: uint16_t version
Offset 0x0A: uint16_t bbox_x_min (Q16)
Offset 0x0C: uint16_t bbox_y_min (Q16)
Offset 0x0E: uint16_t bbox_width (Q16)
Offset 0x10: uint16_t bbox_height (Q16)
Offset 0x12: uint8_t bytecode[] (实际绘图指令)

此二进制格式被设计为可直接 memcpy 到 Flash 并作为 const uint8_t* 使用,无任何运行时解包开销。

5. 在 CARIAD HMI 中的典型集成实践

5.1 硬件平台适配案例:基于 STM32U585 的数字仪表盘

在 CARIAD 的某款数字仪表项目中,MCU 为 STM32U585AI(Cortex-M33 @ 160MHz),显示控制器为 RA8876(SPI 接口,800×480 RGB565)。集成步骤如下:

1. 驱动层实现 ( bv_driver_ra8876.c ):

// RA8876 寄存器映射
#define RA8876_REG_GRAM_WRITE 0x22

void bv_draw_pixel(int32_t x, int32_t y, bv_color_t color) {
    // 设置 GRAM 地址
    ra8876_write_reg(0x00, (uint8_t)(x >> 8)); // CASET MSB
    ra8876_write_reg(0x01, (uint8_t)x);          // CASET LSB
    ra8876_write_reg(0x02, (uint8_t)(y >> 8)); // PASET MSB
    ra8876_write_reg(0x03, (uint8_t)y);          // PASET LSB
    // 写入像素数据(RGB565)
    ra8876_write_cmd(RA8876_REG_GRAM_WRITE);
    ra8876_write_data((uint8_t)(color >> 8));
    ra8876_write_data((uint8_t)color);
}

void bv_fill_rect(int32_t x, int32_t y, int32_t w, int32_t h, bv_color_t color) {
    // 利用 RA8876 的硬件填充指令(0x44),比逐像素快 10x
    ra8876_fill_rect(x, y, w, h, color);
}

2. UI 任务中渲染循环:

void ui_task(void *pvParameters) {
    bv_config_t config = {
        .color_mode = BV_COLOR_MODE_RGB565,
        .antialias_level = 1,
        .max_path_points = 32,
        .on_error = ui_error_handler
    };
    bv_init(&config);

    const bv_object_t *speed_ptr = (const bv_object_t*) &speedometer_pointer_bin;
    bv_transform_t rotate;
    
    for(;;) {
        // 根据车速计算旋转角度(0-240km/h → 0-300°)
        int32_t angle_q16 = (speed_kmh * 65536 * 300) / 240; 
        bv_transform_create_rotate(&rotate, angle_q16);
        
        speed_ptr->transform = rotate;
        speed_ptr->clip = (bv_clip_region_t){0, 0, 800, 480};
        speed_ptr->color = BV_COLOR_RGB565(0xFF, 0x00, 0x00); // 红色
        
        bv_begin_frame();
        bv_draw_object(speed_ptr);
        bv_end_frame();
        
        vTaskDelay(pdMS_TO_TICKS(33)); // ~30fps
    }
}

此实现中,指针旋转完全由 BinaryVector 引擎在 CPU 上完成,无需 GPU 或额外帧缓冲,RAM 占用仅 256 字节(引擎状态+变换矩阵),Flash 占用 1.2KB( .bin 文件)。

5.2 与 FreeRTOS 的协同优化

在多任务 HMI 中,为避免显示任务被高优先级任务抢占导致画面撕裂,CARIAD 推荐以下模式:

// 创建专用显示任务(最高优先级)
xTaskCreate(ui_display_task, "UI_DISP", 512, NULL, configLIBRARY_MAX_PRIORITIES, NULL);

// 在其他任务中通过队列发送渲染请求
typedef struct {
    const bv_object_t *obj;
    bv_transform_t transform;
    bv_clip_region_t clip;
} render_cmd_t;

QueueHandle_t xRenderQueue;

void send_render_cmd(const bv_object_t *obj, ...) {
    render_cmd_t cmd = {.obj=obj, .transform=..., .clip=...};
    xQueueSend(xRenderQueue, &cmd, portMAX_DELAY);
}

// 显示任务主循环
void ui_display_task(void *pvParameters) {
    render_cmd_t cmd;
    bv_begin_frame();
    while(xQueueReceive(xRenderQueue, &cmd, portMAX_DELAY) == pdPASS) {
        bv_draw_object(&cmd); // 此处需扩展 bv_draw_object 支持传入临时变换
    }
    bv_end_frame();
}

此模式将图形计算(变换、裁剪)与像素提交(耗时 I/O)分离,确保显示任务能及时响应,同时允许业务逻辑任务异步提交 UI 更新。

6. 性能特征与车规约束

BinaryVector 的性能指标严格对标 CARIAD 的 ASIL-B 功能安全要求:

指标 测量条件 典型值 车规要求 实现机制
单线段绘制时间 Cortex-M7 @ 216MHz, RGB565 1.8μs < 5μs 纯汇编光栅化,无分支预测失败
圆弧光栅化吞吐 90°弧,半径 50px 120k pixels/sec > 100k px/sec Bresenham 优化算法,查表 sin/cos
Flash 占用 100 个图标(平均) 48KB < 64KB 二进制压缩,去重顶点
RAM 占用 引擎状态+缓存 384 bytes < 512 bytes 全局静态分配,无堆依赖
最坏情况执行时间 (WCET) bv_draw_object() 24ms < 30ms 路径顶点数硬限 256,裁剪早停

关键约束的工程实现:

  • 零动态内存 :所有 malloc 调用被 #define malloc __error_malloc_not_allowed 替换,编译期报错。
  • 确定性调度 bv_draw_object() 被标记为 __attribute__((section(".ramfunc"))) ,强制加载到 RAM 执行,规避 Flash 等待周期抖动。
  • 故障安全 bv_init() 执行 CRC32 校验,若失败则调用 NVIC_SystemReset() ,符合 ISO 26262 失效响应要求。

在实车 ECU 中,BinaryVector 已通过 CARIAD 的全部 EMC 测试(ISO 11452-2/4/8)与温度循环测试(-40°C 至 +105°C),其生成的 .bin 文件被纳入整车 OTA 更新包,与应用固件独立签名、独立校验,实现 UI 资源的敏捷迭代。

7. 调试与诊断支持

为满足车规开发的可追溯性要求,BinaryVector 内置轻量级诊断接口:

// 启用调试日志(仅 DEBUG 构建)
#define BV_DEBUG_LOG 1

// 日志级别
typedef enum {
    BV_LOG_LEVEL_ERROR = 0,
    BV_LOG_LEVEL_WARN  = 1,
    BV_LOG_LEVEL_INFO  = 2,
    BV_LOG_LEVEL_DEBUG = 3
} bv_log_level_t;

// 设置日志回调(可对接 CAN FD 或 UART)
void bv_set_log_callback(void (*cb)(bv_log_level_t, const char*, ...));

// 查询引擎状态(用于诊断服务)
bv_status_t bv_get_engine_stats(bv_stats_t *stats);

typedef struct {
    uint32_t draw_calls;     // 总绘制调用次数
    uint32_t pixel_count;    // 总绘制像素数
    uint32_t last_error;     // 最近错误码
    uint32_t crc_failures;   // CRC 校验失败次数
} bv_stats_t;

在量产固件中, BV_DEBUG_LOG 被禁用,但 bv_get_engine_stats() 仍保留,可通过 UDS 诊断服务($22 PID)读取,用于售后分析 UI 卡顿原因。例如, pixel_count 异常飙升可能指示裁剪失效导致全屏重绘。

此外,CARIAD 提供配套的 bv_inspector 工具,可加载 .bin 文件并可视化其路径顶点、变换矩阵、边界框,辅助 UI 设计师验证资源合规性,避免因 SVG 导出错误导致的 runtime 故障。

BinaryVector 的工程价值,在于它将车载 HMI 图形渲染这一传统上依赖庞大 GUI 框架(如 Qt for MCUs)的领域,拉回到嵌入式工程师熟悉的确定性、可验证、资源可控的范畴。其存在本身,就是对“在 512KB Flash、192KB RAM 的 MCU 上构建安全可靠数字座舱”这一车规级挑战的务实回应。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐