API GUIDE / I2D / LL
G32S10 I2D LL 接口
LL 层接口:ll_i2d.h 全部是 static inline,第一个参数是寄存器实例 I2D。
1. 接口规范
1.1 LL 接口
ll_i2d.h 全部是 static inline,第一个参数是寄存器实例 I2D。LL 不做参数
合法性检查,只做字段截位;调用时序、时钟与 buffer 都由调用方负责。
1.1.1 实例与版本
| 函数 | 说明 |
|---|---|
ll_i2d_is_instance(i2dx) | 指针是否为唯一实例 I2D |
ll_i2d_get_version(i2dx) | 读 version 寄存器 |
1.1.2 Shadow、复位、启动与停止
| 函数 | 说明 |
|---|---|
ll_i2d_update_shadow(i2dx) | 写 SHD_CTRL = I2D_SHD_CTRL_UPDATE_ALL(0x3) |
ll_i2d_set_shadow_control(i2dx, value) | 写 SHD_CTRL 原始值 |
ll_i2d_reset(i2dx) | 置 CTRL.RST(bit16,模块复位) |
ll_i2d_safe_reset(i2dx) | 置 CTRL.SAFE_RST(bit4,不清配置) |
ll_i2d_start(i2dx) | 置 CTRL.START(bit0) |
ll_i2d_stop(i2dx) | 清 CTRL.START |
ll_i2d_get_control(i2dx) | 读 CTRL |
1.1.3 尺寸、模式与 outstanding
| 函数 | 说明 |
|---|---|
ll_i2d_set_image_size(i2dx, width, height) | 宽 [31:16]、高 [15:0],各自按 16 位截位 |
ll_i2d_get_image_size(i2dx) | 读 IMG_SIZE |
ll_i2d_set_mode(i2dx, data_type, rotate_mode, mirror, flip) | 整写 MODE |
ll_i2d_write_mode(i2dx, value) / ll_i2d_get_mode(i2dx) | 写/读 MODE 原始值 |
ll_i2d_set_outstanding_limit(i2dx, limit) | 按 8 位截位写入;不拒绝 0 |
ll_i2d_get_outstanding(i2dx) | 读 OUTSTANDING |
1.1.4 地址与 stride
| 函数 | 说明 |
|---|---|
ll_i2d_set_source_addr(i2dx, y_addr, uv_addr) | 写源 Y/UV 地址 |
ll_i2d_set_destination_addr(i2dx, y_addr, uv_addr) | 写目标 Y/UV 地址 |
ll_i2d_set_source_stride(i2dx, y_stride, uv_stride) | 两个 stride 都按 16 位截位 |
ll_i2d_set_destination_stride(i2dx, y_stride, uv_stride) | 同上 |
ll_i2d_get_source_y_addr() / ll_i2d_get_source_uv_addr() | 读源地址 |
ll_i2d_get_destination_y_addr() / ll_i2d_get_destination_uv_addr() | 读目标地址 |
1.1.5 中断
| 函数 | 说明 |
|---|---|
ll_i2d_get_irq_status(i2dx) | 读 irq_state(锁存,读不清除) |
ll_i2d_clear_irq(i2dx, irq_mask) | 写 irq_clear,写 1 清对应状态位 |
ll_i2d_set_irq_mask(i2dx, irq_mask) | 写 irq_mask 原始值(1 = 屏蔽) |
ll_i2d_enable_irq(i2dx, irq_mask) | 清对应 mask 位(放行) |
ll_i2d_disable_irq(i2dx, irq_mask) | 置对应 mask 位(屏蔽) |
ll_i2d_get_irq_mask(i2dx) | 读 irq_mask |
1.1.6 硬件超时与时钟门控
| 函数 | 说明 |
|---|---|
ll_i2d_set_timeout_value(i2dx, value) / ll_i2d_get_timeout_value(i2dx) | 写/读硬件超时上限 |
ll_i2d_enable_timeout_restart(i2dx) / ll_i2d_disable_timeout_restart(i2dx) | 置/清 TIMEOUT_MODE.RST |
ll_i2d_get_timeout_mode(i2dx) | 读 TIMEOUT_MODE |
ll_i2d_enable_clock_gate(i2dx) / ll_i2d_disable_clock_gate(i2dx) | 置/清 CLK_GATE.VALUE |
ll_i2d_get_clock_gate(i2dx) | 读 CLK_GATE |
2. 典型用法:从零开始
本节假设你手上有一块能跑 SDK 的板子,想把「内存里的图 → 旋转/镜像 → 内存里的图」 直接做在 HAL 或 LL 上。
2.1 LL 五步模型
LL 没有布局助手、没有状态机、没有等待,全部自己做:
| 步骤 | 做什么 |
|---|---|
| 1 | 打开 AON HP1 总线门与模块内部时钟 |
| 2 | 一次性配置:安全复位、outstanding、硬件超时、中断掩码、清中断 |
| 3 | 自己算布局:stride、平面大小、输出尺寸、NV12 UV 地址 |
| 4 | 每帧:写 MODE/IMG_SIZE/地址/stride → 提交 shadow → 清中断 → 安全复位 → 启动 |
| 5 | 轮询 irq_state(或注册 SoC 中断)→ 完成后 invalidate 目标 → 收尾关时钟 |
时钟的两级开关见 §6.8。下面的序列取自 code/examples/ll/i2d_ll_example.c:
/* 三个事件位;示例里叫 EXAMPLE_I2D_IRQ_ALL_MASK */ #define I2D_IRQ_ALL (I2D_IRQ_FRAME_DONE_MASK | I2D_IRQ_TIMEOUT_MASK | \ I2D_IRQ_OUTSTANDING_MASK) #define I2D_IRQ_WANT (I2D_IRQ_FRAME_DONE_MASK | I2D_IRQ_TIMEOUT_MASK) /* 1. 时钟 */ ll_aon_hpclkgr1_clear_stop_mask(AON_CLKGR_STOP_MASK(AON_HPCLKGR1_I2D_BIT)); ll_i2d_enable_clock_gate(I2D); /* 2. 一次性配置 */ ll_i2d_safe_reset(I2D); ll_i2d_set_outstanding_limit(I2D, I2D_OUTSTANDING_LIMIT_DEFAULT); /* 0x40 */ ll_i2d_set_timeout_value(I2D, UINT32_MAX); ll_i2d_enable_timeout_restart(I2D); ll_i2d_set_irq_mask(I2D, I2D_IRQ_ALL & ~I2D_IRQ_WANT); /* 1 = 屏蔽 */ ll_i2d_clear_irq(I2D, I2D_IRQ_ALL); /* 4. 每帧(src_y/src_uv/dst_y/dst_uv 与各 stride 由下面的 layout 计算得到) */ ll_i2d_set_mode(I2D, I2D_MODE_DATA_TYPE_ARGB8888, I2D_MODE_ROTATE_90, false, false); ll_i2d_set_image_size(I2D, width, height); /* 源尺寸 */ ll_i2d_set_source_addr(I2D, src_y, src_uv); ll_i2d_set_destination_addr(I2D, dst_y, dst_uv); ll_i2d_set_source_stride(I2D, src_stride_y, src_stride_uv); ll_i2d_set_destination_stride(I2D, dst_stride_y, dst_stride_uv); ll_i2d_update_shadow(I2D); /* 必须提交 */ ll_i2d_clear_irq(I2D, I2D_IRQ_ALL); /* 清掉上一帧的锁存 */ ll_i2d_safe_reset(I2D); ll_i2d_start(I2D);
布局要自己算。LL 示例的 example_i2d_layout_build() 就是一个完整实现:按格式取
bpp、按旋转换输出宽高、按 16 行对齐算 NV12 的 Y 平面、UV 地址紧随 Y 之后、stride
超 0xFFFF 直接报错。核心几行:
bpp = example_i2d_bytes_per_pixel(data_type);
out_w = is_rotated ? height : width;
out_h = is_rotated ? width : height;
src_rows = is_nv12 ? align_up(height, 16U) : height;
dst_rows = is_nv12 ? align_up(out_h, 16U) : out_h;
src_y_stride = width * bpp;
dst_y_stride = out_w * bpp;
src_uv_stride = is_nv12 ? width : 0U;
dst_uv_stride = is_nv12 ? out_w : 0U;
src_buffer_size = src_y_stride * src_rows;
dst_buffer_size = dst_y_stride * dst_rows;
if (is_nv12) {
src_uv_addr = src_y_addr + src_buffer_size;
dst_uv_addr = dst_y_addr + dst_buffer_size;
src_buffer_size += src_uv_stride * (height / 2U);
dst_buffer_size += dst_uv_stride * (out_h / 2U);
}buffer 的分配、64 字节对齐与 cache 维护和 HAL 完全一样(§7.5、§6.7),区别只在
layout 要自己算、常量要自己定义。注意 stride 超过 0xFFFF 时 LL 会静默截位,自己
的 layout 函数必须显式报错。
3. 示例程序解读
三个示例结构相同,只是「谁负责等待、谁负责时钟」不同:
| 文件 | 层级 | 等待方式 | 时钟 | 布局 | 入口 |
|---|---|---|---|---|---|
i2d_callback_transform_example.c | HAL | IRQ_I2D + HAL 回调 | HAL | HAL | example_i2d_callback_transform_run() |
i2d_ll_example.c | LL | 轮询 irq_state | 自己开/关 | 自己算 | example_i2d_ll_run() |
3.1 改参数做练习
只改各文件开头的 Tunables:
| 宏 | 可选值 |
|---|---|
EXAMPLE_I2D_WIDTH / _HEIGHT | 8x8 ~ 3840x2160,受格式对齐与内存限制 |
EXAMPLE_I2D_FORMAT / _DATA_TYPE | NV12 / RAW8 / RGB565 / ARGB8888 / YUV422 |
EXAMPLE_I2D_ROTATE | ROTATE_0/90/180/270(LL 用 I2D_MODE_ROTATE_*) |
EXAMPLE_I2D_HORIZONTAL_MIRROR / _VERTICAL_FLIP | true / false |
EXAMPLE_I2D_TIMEOUT_MS | 软件等待上限(毫秒) |
建议练习顺序:
原样跑一次,确认
verify=OK;改成
ROTATE_270,确认仍然verify=OK(校验公式跟着变);改成 640x480 +
YUV422,观察日志里的尺寸与 stride;改成 1920x1080 +
NV12+ROTATE_90,观察分配的帧大小是 3,125,760(源、目标 取较大者),而目标实际只有 3,110,400;打开
horizontal_mirror,确认校验仍然通过;LL 示例里把
outstanding改成 0(仅练习):ll_i2d_set_outstanding_limit不拦, 硬件会按 0 配置,用来体会「LL 不校验参数」。