API GUIDE / I2D / Middleware
G32S10 I2D Middleware 接口
Middleware 层接口:中间件是阻塞式的:im_i2d_process() 占用调用任务,直到这次变换完成、超时或被 取消才返回。
1. 分层关系与依赖
1.1 分层与调用方向
对外隐藏硬件复位和错误恢复细节
提供面向图像处理流程的语义接口
中断路径:IRQ_I2D → PLIC → im_irq → HAL IRQ handler → MID event waiter
1.2 各层职责
| 层 | 负责 | 不负责 |
|---|---|---|
| 应用 / 示例 / 测试 | 提供源/目标 buffer,填写几何与格式;调用 im_i2d_process() 并处理返回码;buffer 的容量、对齐与生命周期 | 寄存器、中断 |
im_i2d | 单实例设备对象;公共枚举到 HAL 配置的映射;IRQ_I2D 的申请/使能;事件位图与等待;超时;按 manage_cache 维护 cache | 分配内存、像素格式转换、缩放 |
| HAL I2D | 参数校验、stride/平面大小推导、寄存器编程、启停、中断状态读取与清除 | cache 维护、SoC 中断申请 |
| LL I2D | 寄存器字段级读写、shadow 更新、安全复位、启动/停止、时钟门控 | 参数语义 |
中间件是阻塞式的:im_i2d_process() 占用调用任务,直到这次变换完成、超时或被
取消才返回。公共接口没有「启动后立即返回」的版本,也没有状态查询接口。
2. 构建配置与文件组织
2.1 Kconfig
code/middleware/i2d/Kconfig:
config MIDDLEWARE_I2D bool "I2D image conversion middleware" depends on DRIVER_I2D && OS_API
| 配置 | 作用 |
|---|---|
CONFIG_DRIVER_I2D | 编译 I2D LL/HAL(code/drivers/g32s10/i2d/) |
CONFIG_OS_API / CONFIG_OS | 任务/延时与 thread_waiter |
CONFIG_DRIVER_IRQ | im_irq_* 注册与分发框架 |
CONFIG_DRIVER_PLIC | IRQ_I2D 的 PLIC 使能(G32S10 上默认随 core 打开) |
CONFIG_MIDDLEWARE_I2D | 编译 im_i2d |
2.2 CMake
code/middleware/i2d/CMakeLists.txt 在 CONFIG_MIDDLEWARE_I2D 打开时注册组件im_i2d:
if(CONFIG_MIDDLEWARE_I2D)
imde_component_register(NAME im_i2d
SRCS "${CMAKE_CURRENT_LIST_DIR}/src/im_i2d.c"
INCLUDE_DIRS
"${CMAKE_CURRENT_LIST_DIR}/include")
endif()应用只需要 #include "im_i2d.h" 并打开 CONFIG_MIDDLEWARE_I2D。示例目录code/middleware/i2d/example/CMakeLists.txt 会在没有打开中间件时直接FATAL_ERROR,避免「配置忘开、构建却通过」。
2.3 文件职责
| 文件 | 职责 |
|---|---|
include/im_i2d.h | 对外唯一头文件:枚举、两个结构体、六个函数 |
src/im_i2d.c | 全部实现,struct im_i2d 只在这里可见 |
example/main/main.c | 示例入口,只调用 i2d_single_transform_run() |
example/main/i2d_single_transform.c | 示例本体:可改配置、缓冲、图案、一次变换、抽样校验 |
example/README.md | 示例的用户说明(构建、日志、改参数) |
example/i2d_example_defconfig | 示例的 defconfig(打开 I2D 驱动与中间件) |
3. 资源模型与公开类型
3.1 设备状态机
RESET → READY · im_i2d_init()
READY → RUNNING · im_i2d_process() begin + start
RUNNING → STOPPING · finish() 屏蔽中断并收尾
STOPPING → READY · IM_OK / 取消
STOPPING → TIMEOUT · 等待或硬件超时
STOPPING → ERROR · outstanding 或硬件错误
READY / TIMEOUT / ERROR → RESET · im_i2d_deinit()
| 状态 | 含义 | 可继续做的操作 |
|---|---|---|
RESET | 未初始化或已反初始化 | im_i2d_init();im_i2d_deinit() 返回 IM_OK;im_i2d_process() 返回 IM_ERR_NOT_READY;im_i2d_cancel() 返回 last_status |
READY | 空闲,可以启动下一次变换 | im_i2d_process()、im_i2d_deinit();im_i2d_cancel() 返回 last_status |
RUNNING | 已启动,等待完成事件 | 等待由 im_i2d_process() 内部完成;另一任务可 im_i2d_cancel() |
STOPPING | finish 的中间态,仅在函数内部短暂出现 | 应用不应观察到 |
TIMEOUT | 上一次变换超时 | 只能 im_i2d_deinit() 后重新初始化 |
ERROR | 上一次变换遇到硬件错误或缺少完成事件 | 只能 im_i2d_deinit() 后重新初始化 |
关键点:
TIMEOUT/ERROR是终态。 此时再调用im_i2d_process(),begin 阶段会因状态 不是READY而返回IM_ERR_BUSY。恢复方式是im_i2d_deinit()+im_i2d_init()。begin 阶段的参数/布局错误、start 阶段的 HAL 错误都不会进入
RUNNING,状态保持READY,修正参数或降低负载后可以直接重试。正常完成或
im_i2d_cancel()会把状态送回READY。
3.2 缓冲区所有权
| 对象 | 谁分配 | 谁释放 | 谁维护 cache |
|---|---|---|---|
im_i2d_t 设备对象 | 中间件(静态) | 中间件 | 不涉及 |
| 源 buffer | 调用方 | 调用方 | 默认中间件(manage_cache = true) |
| 目标 buffer | 调用方 | 调用方 | 默认中间件 |
中间件不持有 buffer,im_i2d_process() 返回后不会再用它们(除了 finish 阶段对目标
做一次 invalidate)。因此调用方可以在返回后立刻读取或释放目标 buffer。
3.3 公开类型
3.3.1 im_i2d_t
typedef struct im_i2d im_i2d_t;
只有前向声明,定义在 im_i2d.c 内部。应用只能持有指针并原样传回,不能
sizeof(struct im_i2d) 分配自己的实例、不能访问字段、不能在 im_i2d_deinit()
之后继续使用。
3.3.2 im_i2d_instance_t
| 枚举 | 值 | 说明 |
|---|---|---|
IM_I2D_INSTANCE_0 |
0 | 唯一支持、也是唯一合法的实例 |
3.3.3 im_i2d_format_t
| 枚举 | 值 | 每像素字节 | 宽高对齐 | 平面数 |
|---|---|---|---|---|
IM_I2D_FORMAT_NV12 |
0 | 1(Y 平面) | 8 | 2(Y + UV 半平面) |
IM_I2D_FORMAT_RAW8 |
1 | 1 | 8 | 1 |
IM_I2D_FORMAT_RGB565 |
2 | 2 | 4 | 1 |
IM_I2D_FORMAT_ARGB8888 |
3 | 4 | 2 | 1 |
IM_I2D_FORMAT_YUV422 |
4 | 2 | 4 | 1(打包) |
format 同时描述源和目标:I2D 不做像素格式转换、不做缩放、不做色彩空间转换。
源和目标必须是同一种格式。YUV422 是打包的单平面格式(每像素 2 字节),UV 字段必须
为 NULL,它的字节序由上游决定,I2D 只按字节搬运。
3.3.4 im_i2d_rotate_t
| 枚举 | 值 | 说明 |
|---|---|---|
IM_I2D_ROTATE_0 |
0 | 不旋转 |
IM_I2D_ROTATE_90 |
1 | 顺时针 90° |
IM_I2D_ROTATE_180 |
2 | 180° |
IM_I2D_ROTATE_270 |
3 | 顺时针 270°(= 逆时针 90°) |
方向约定由几何映射确定(§6.7):ROTATE_90 把源图左上角送到输出右上角。
test/g32s10/module/vic/slt_103/gc0308_dump_i2d 已在硬件上逐像素验证过这个方向。
3.3.5 im_i2d_plane_t
| 字段 | 类型 | 说明 |
|---|---|---|
y |
void * |
Y 平面或打包图像的基地址;必须非空 |
uv |
void * |
NV12 的 UV 平面地址;其余格式必须为 NULL |
stride_y |
uint32_t |
Y 平面或打包图像的行 stride(字节);0 表示由 HAL 推导 |
stride_uv |
uint32_t |
NV12 UV 平面 stride(字节);0 表示由 HAL 推导 |
3.3.6 im_i2d_transform_t
| 字段 | 类型 | 说明 |
|---|---|---|
src |
im_i2d_plane_t |
源平面信息 |
dst |
im_i2d_plane_t |
目标平面信息 |
width / height |
uint32_t |
源图宽高(像素) |
format |
im_i2d_format_t |
源和目标共用的像素格式 |
rotate |
im_i2d_rotate_t |
旋转角度 |
horizontal_mirror |
bool |
水平镜像(作用在输出坐标系) |
vertical_flip |
bool |
垂直翻转(作用在输出坐标系) |
平面规则:
- 打包格式(RAW8、RGB565、ARGB8888、YUV422)只有一个平面:
uv = NULL、stride_uv = 0。 - NV12 有两种写法:
- 连续布局:
uv = NULL,HAL 按y + stride_y * 按 16 对齐后的 Y 行数推导 UV 地址; - 分离布局:显式给出
uv,并可用stride_uv指定 UV 行 stride。
- 连续布局:
- stride 单位是字节,不是像素;非 0 时必须
<= 0xFFFF且不小于最小行字节数。 - 非 NV12 却给了 UV 指针或 UV stride,会被 HAL 拒绝(
IM_ERR_INVALID_PARAM)。
3.3.7 im_i2d_config_t
| 字段 | 类型 | 说明 |
|---|---|---|
instance |
im_i2d_instance_t |
必须为 IM_I2D_INSTANCE_0 |
hal_init |
hal_i2d_init_config_t |
原样传给 hal_i2d_init();其中 instance 会被强制改写为 I2D |
timeout_ms |
uint32_t |
默认等待超时;0 表示使用 IM_I2D_DEFAULT_TIMEOUT_MS |
manage_cache |
bool |
是否由中间件维护源/目标 buffer 的 cache |
hal_init 中与中间件行为关系最大的字段:
| 字段 | 中间件行为 |
|---|---|
outstanding_limit |
透传给 HAL;合法范围 1..255,默认 0x40 |
timeout_value |
透传给 HAL 的硬件超时上限;默认 UINT32_MAX |
timeout_restart |
透传给 HAL;默认 true(硬件超时后自动重启) |
frame_done_irq_enable / timeout_irq_enable |
只影响 hal_i2d_init() 写入的初始中断门;每次 im_i2d_process() 都会把外设中断门重写为「FRAME_DONE + TIMEOUT」,因此不能用这两个字段关闭一次变换的完成/超时事件 |
outstanding_irq_enable |
为 true 时,中间件在本次变换中额外放行 OUTSTANDING 中断,命中会以 IM_ERR_HARDWARE 返回 |
4. 接口规范
4.1 初始化与默认值
4.1.1 im_i2d_config_struct_init
void im_i2d_config_struct_init(im_i2d_config_t *config);
| 参数 | 方向 | 说明 |
|---|---|---|
config |
输出 | 待填写的初始化配置;NULL 时直接返回 |
填默认值(§4.3),应用在调用 im_i2d_init() 之前先调它,再覆盖需要改的字段。
不要在未初始化的情况下手填 im_i2d_config_t,否则 hal_init 里的其它字段是随机的。
4.1.2 im_i2d_transform_default
void im_i2d_transform_default(im_i2d_transform_t *transform);
| 参数 | 方向 | 说明 |
|---|---|---|
transform |
输出 | 待填写的变换对象;NULL 时直接返回 |
清零后格式为 NV12、旋转为 0°,地址/尺寸/stride 全为 0。每次填变换前都应该先调
它,这样未使用的字段(uv、stride_uv、镜像/翻转)一定是合法值:非 NV12 的
UV 字段如果是随机值,会被 HAL 判为非法参数。
4.1.3 im_i2d_init
error_code_t im_i2d_init(im_i2d_t **out_dev, const im_i2d_config_t *config);
| 参数 | 方向 | 说明 |
|---|---|---|
out_dev |
输出 | 成功后写入设备句柄 |
config |
输入 | 初始化配置,内部保存一份副本 |
执行顺序:
- 校验
out_dev、config非空; - 校验
config->instance == IM_I2D_INSTANCE_0; - 检查静态设备状态是否为
RESET,否则返回IM_ERR_BUSY; - 清零设备对象,保存配置副本;
- 把
config.hal_init.instance强制设为I2D; timeout_ms == 0时填入IM_I2D_DEFAULT_TIMEOUT_MS;- 初始化等待器,状态置
RESET,last_status = IM_ERR_NOT_READY; - 写入
*out_dev; hal_i2d_init():打开 CPM/模块时钟、安全复位、写 outstanding/超时/初始中断门;hal_i2d_callback_register(im_i2d_hal_event, dev);sys_irq_request(IRQ_I2D, im_i2d_irq_entry, "im_i2d", dev)+sys_irq_enable();- 状态置
READY,last_status = IM_OK。
返回码:IM_OK;IM_ERR_INVALID_PARAM;IM_ERR_BUSY;或 HAL/CPM 错误码。
失败回滚:
- 第 9 步失败:直接返回 HAL 错误码,状态保持
RESET,可以再次调用im_i2d_init()。 - 第 10/11 步失败:释放已申请的 IRQ,
hal_i2d_deinit()(其中会清空回调),返回错误码, 状态保持RESET。 - 只有参数校验失败时才保证
*out_dev不被改写;HAL 初始化失败时*out_dev已经指向 内部对象。失败时一律以返回值为准;该句柄处于RESET,对其调用im_i2d_deinit()是安全的空操作。
IRQ 默认优先级由 sys_irq_request() 的框架默认值决定;需要调整可在初始化后调用
sys_irq_set_priority(IRQ_I2D, ...)。
初始化代码:
#include "im_i2d.h"
static im_i2d_t *s_i2d;
static error_code_t i2d_module_init(void)
{
im_i2d_config_t cfg;
error_code_t ret;
im_i2d_config_struct_init(&cfg);
/* 默认超时 400 ms;大分辨率可以放宽。manage_cache 保持默认 true。 */
cfg.timeout_ms = 500U;
ret = im_i2d_init(&s_i2d, &cfg);
if (ret != IM_OK) {
return ret;
}
return IM_OK;
}
4.2 im_i2d_process
error_code_t im_i2d_process(im_i2d_t *dev,
const im_i2d_transform_t *transform,
uint32_t timeout_ms);
| 参数 | 方向 | 说明 |
|---|---|---|
dev |
输入 | im_i2d_init() 返回的句柄 |
transform |
输入 | 本次变换的完整描述(地址、尺寸、格式、旋转、镜像) |
timeout_ms |
输入 | 等待上限(毫秒);0 表示用 config.timeout_ms,再为 0 用 400 |
行为:
- begin:把
transform映射成 HAL 配置,调hal_i2d_layout_get()完成全部几何 校验并推导 stride/平面大小;准备 cache;状态置RUNNING。 - start:打开完成/超时事件,编程寄存器并启动硬件。
- wait:阻塞等待完成事件或被取消;超时则主动停止。
- finish:屏蔽中断、把事件位图映射成返回码、invalidate 目标 cache、停止硬件、 迁移状态。
返回码见 §5.6。要点:
- 这是阻塞接口,只能在任务上下文调用;不要在 ISR、定时器回调里调用。
- 每次调用都会重新解析布局,所以同一个
dev连续处理多帧时,分辨率/格式可以逐帧 不同(中间件不缓存上一帧的配置)。 - 一次变换失败后能否继续,取决于返回码:
IM_ERR_INVALID_PARAM等参数类错误可以直接 修正后重试;IM_ERR_TIMEOUT/IM_ERR_HARDWARE必须 deinit + init。
4.3 im_i2d_cancel
error_code_t im_i2d_cancel(im_i2d_t *dev);
只在
RUNNING时生效:置STOPPING,走 finish,停止硬件并唤醒等待者;正在阻塞的im_i2d_process()返回IM_ERR_INVALID_STATE。没有运行中的变换时,返回
last_status,不做任何事。
典型用法是另一个任务中止阻塞中的变换。取消后状态回 READY,可以继续启动下一帧。
不要用「cancel + 立刻 deinit」代替正常等待:取消路径同样会停止硬件并 invalidate 目标,
但应用拿不到完成的图像。
4.4 im_i2d_deinit
error_code_t im_i2d_deinit(im_i2d_t *dev);
执行顺序:
- 校验句柄;
- 状态为
RESET时直接返回IM_OK; - 状态为
RUNNING/STOPPING时,先以IM_ERR_INVALID_STATE走一次 finish:屏蔽 中断、对目标做 cache invalidate、停止硬件,状态回READY; - 屏蔽全部 I2D 外设中断;
- 按注册/使能状态关闭并释放
IRQ_I2D; - 注销 HAL 回调;
hal_i2d_deinit():停止硬件、屏蔽/清除中断、关闭模块时钟与 CPM 时钟;- 状态置
RESET,last_status置为hal_i2d_deinit()的返回值。
从 TIMEOUT/ERROR 状态调用时第 3 步不执行(没有运行中的变换),直接释放资源——这
正是错误后的标准恢复路径。
4.5 对外接口速查
| 函数 | 说明 | 上下文 |
|---|---|---|
im_i2d_config_struct_init() | 填默认配置 | 任意 |
im_i2d_transform_default() | 填默认变换(NV12 / 0°) | 任意 |
im_i2d_init() | 初始化模块、申请 IRQ、初始化 HAL | 任务/初始化上下文 |
im_i2d_process() | 启动一次变换并阻塞等待完成 | 任务上下文 |
im_i2d_cancel() | 中止正在运行的变换 | 任务上下文 |
im_i2d_deinit() | 停止并释放 IRQ/HAL/时钟 | 任务/关闭上下文 |
需要并发时,由一个任务串行执行变换,另一个任务用 im_i2d_cancel() 中止。
5. 行为约束与实现说明
5.1 连续处理多帧
static error_code_t i2d_run_frames(im_i2d_t **dev,
const im_i2d_config_t *cfg,
im_i2d_transform_t *t,
uint32_t count)
{
error_code_t ret = IM_OK;
for (uint32_t i = 0U; i < count; i++) {
ret = im_i2d_process(*dev, t, 500U);
if (ret != IM_OK) {
/* 只有 READY 状态能启动下一次变换:TIMEOUT/ERROR 必须先重开会话。 */
if ((ret == IM_ERR_TIMEOUT) || (ret == IM_ERR_HARDWARE) ||
(ret == IM_ERR_NOT_READY)) {
(void)im_i2d_deinit(*dev);
ret = im_i2d_init(dev, cfg);
if (ret != IM_OK) {
return ret;
}
} else {
return ret;
}
}
/* 正常路径下目标 buffer 已经可以直接读(cache 已 invalidate)。 */
}
return IM_OK;
}两帧之间可以改 t 的地址、尺寸、格式、旋转;下一次 begin 会重新解析布局。
6. 典型用法:从零开始
本节假设你手上有一块能跑 SDK 的板子,想用 im_i2d 做「内存里的图 → 旋转/镜像 →
内存里的图」。整个过程是固定的五步。
6.1 第一步:打开设备
#include "im_i2d.h"
static im_i2d_t *s_i2d;
static error_code_t open_i2d(void)
{
im_i2d_config_t cfg;
im_i2d_config_struct_init(&cfg); /* 默认 400 ms、manage_cache = true */
return im_i2d_init(&s_i2d, &cfg);
}整个系统只开一次;再次调用会返回
IM_ERR_BUSY。打开后
IRQ_I2D与 I2D 时钟都归中间件管,应用不要再申请/关闭。普通 cacheable 内存不要关
manage_cache。
6.2 第四步:填变换对象并执行
im_i2d_transform_t t;
error_code_t ret;
im_i2d_transform_default(&t); /* 先给所有字段一个合法默认值 */
t.src.y = src;
t.src.stride_y = layout.src_y_stride; /* 源行字节数;相机帧来自 line_length */
t.dst.y = dst;
t.dst.stride_y = layout.dst_y_stride; /* 输出行字节数,旋转后可能不同 */
t.width = 640U; /* 源尺寸 */
t.height = 480U;
t.format = IM_I2D_FORMAT_YUV422;
t.rotate = IM_I2D_ROTATE_90; /* 顺时针 */
t.horizontal_mirror = false;
t.vertical_flip = false;
ret = im_i2d_process(s_i2d, &t, 500U);
if (ret != IM_OK) {
/* 按 §5.6 处理:参数错修正后重试;超时/硬件错 deinit + init */
}
/* 到这里 dst 里就是旋转后的图像,cache 已被中间件 invalidate 过 */打包格式(RAW8 / RGB565 / ARGB8888 / YUV422)的 src.uv / dst.uv 保持默认的NULL。NV12 也可以用连续布局(uv = NULL),此时 UV 紧跟在对齐后的 Y 平面之后,
地址由 HAL 推导。
6.3 第五步:关闭
(void)im_i2d_deinit(s_i2d); /* 停硬件、释放 IRQ、关时钟 */ s_i2d = NULL;
发生超时/硬件错误后同样走这一步,再重新 im_i2d_init()。
6.4 连续处理多帧
同一 dev 可以反复调用 im_i2d_process(),帧间不需要 deinit/init。见 §6.5 的循环
写法。需要注意:
只有
IM_ERR_TIMEOUT/IM_ERR_HARDWARE/IM_ERR_NOT_READY需要重开会话;IM_ERR_INVALID_PARAM直接修正参数重试即可。每帧的
transform可以不同(分辨率、旋转都能变),但同一时刻只能有一次变换。双缓冲:目标 buffer 在
im_i2d_process()返回后就可以交给下游;下游还在读的时候, 下一帧要写到另一块 buffer,否则会撕裂。
6.5 把结果交给下游
I2D 只负责写内存,之后的目标 buffer 怎么送出去由下游决定:
| 下游 | 要注意的 |
|---|---|
| JPEGE | 只接受 NV12 / YUV422(YUYV) / 灰度(Y)等,具体以 im_jpege 的格式枚举为准;宽高填输出尺寸;stride 要等于 宽 × bpp,否则编码器会按连续布局读错 |
| 显示(DPU/framebuffer) | 宽高、stride、像素格式必须与显示配置一致;RGB565/ARGB8888 是常见选择 |
| USB/UVC | 同 JPEGE;另外要 flush_dcache_force()(或等价操作)再交给 USB DMA |
| 另一个 I2D | 直接传目标 buffer 即可,中间件会 clean 源 |
| 存文件 | 按 stride × 行数 写;若 stride 大于 宽 × bpp,文件里会带行尾填充 |
如果目标 stride 比 输出宽 × bpp 大(例如为了对齐),下游必须知道这个 stride;不支持
自定义 stride 的下游(例如某些 JPEG 编码配置)就要让 I2D 输出紧凑布局——把dst.stride_y 设为 0 或 输出宽 × bpp。
7. 示例程序解读
示例位于 code/middleware/i2d/example/。它把一张 1280x1280 ARGB8888 的图像顺时针
旋转 90°,抽样校验后打印结论,全程不接相机、不读 SD 卡、不显示。它是「最小可运行」
的 im_i2d 用法,也是 §7 那五步的完整落地。
7.1 内存账
示例同时持有源、目标两帧,参考板默认堆约 15.8 MB(其它板以实际堆大小为准):
| 配置 | 单帧 | 两帧 | 结果 |
|---|---|---|---|
| 1280x1280 ARGB8888(默认) | 6,553,600 | 13.1 MB | 放得下 |
| 640x480 YUV422 | 614,400 | 1.2 MB | 放得下 |
| 1920x1080 NV12 | 3,125,760 | 6.2 MB | 放得下 |
| 1920x1080 ARGB8888 | 8,294,400 | 16.6 MB | 大概率 IM_ERR_NO_MEMORY |
| 3840x2160 NV12 | 12,441,600 | 24.9 MB | IM_ERR_NO_MEMORY |
| 3840x2160 ARGB8888 | 33,177,600 | 66 MB | IM_ERR_NO_MEMORY |
分配失败是 heap 的限制,不是 I2D 的限制;示例会打印 alloc ... failed, ret=-... 后退出。
8. 把 sensor 画面旋转
真实工程里更常见的需求是:相机采到一帧,方向不对,转正后再送去编码/显示/USB。参考
用例是 test/g32s10/module/vic/slt_103/gc0308_i2d_jpeg_usb/(位于独立的 test/仓库)。它把 GC0308 的 640x480 YUV422 画面顺时针转 90°,编码成 MJPEG,通过 UVC
送给 PC;主机看到的是 480x640 的竖图。
8.1 旋转函数
用例里只有一个薄封装,核心就是填 im_i2d_transform_t:
static error_code_t gc0308_i2d_jpeg_usb_rotate_90cw(im_i2d_t *dev,
const void *src,
uint32_t src_stride,
void *dst,
uint32_t dst_stride,
uint32_t width,
uint32_t height)
{
im_i2d_transform_t transform;
im_i2d_transform_default(&transform);
transform.src.y = (void *)src;
transform.src.uv = NULL; /* packed 格式必须为 NULL */
transform.src.stride_y = src_stride;
transform.dst.y = dst;
transform.dst.uv = NULL;
transform.dst.stride_y = dst_stride;
transform.width = width; /* 源尺寸;输出尺寸由 HAL 按 rotate 推出 */
transform.height = height;
transform.format = IM_I2D_FORMAT_YUV422;
transform.rotate = IM_I2D_ROTATE_90; /* 顺时针 */
transform.horizontal_mirror = false;
transform.vertical_flip = false;
return im_i2d_process(dev, &transform, GC0308_I2D_JPEG_USB_ROTATE_TIMEOUT_MS);
}几点值得照抄:
im_i2d_transform_default()先清零,保证uv/stride_uv等未用字段是合法值;width/height是源尺寸,旋转后尺寸不填;打包 YUV422 的
uv = NULL;超时 500 ms(
ROTATE_TIMEOUT_MS),比默认 400 ms 略宽,给总线拥塞留余量;cache 用默认策略,函数里没有任何
im_cache_*调用。
8.2 迁移到你自己的工程:检查清单
先确认源格式:YUV422/NV12/RAW8……决定
uv是否为NULL、每像素几字节。camera_get_status()里的format/line_length是唯一可信来源。源 stride 用生产者给的值(相机的
line_length、解码器的行宽),不要用width × bpp硬算。目标 stride 按输出几何算:90°/270° 时输出宽 = 源高。
目标 buffer 至少是 layout 的
dst_buffer_size,并 64 字节对齐(后面还要接 JPEGE 时按它的 256 字节要求来)。cache:保持
manage_cache = true。如果源 buffer 由另一个 DMA 引擎写、且你 在外层已经 clean 过,中间件再 clean 一次是安全的。不要在im_i2d_process()返回后 再对目标做 clean——目标此时只有 DMA 写入的新数据,clean 会把 CPU 里的旧脏行 写回、盖掉结果;多一次 invalidate 倒不会出错,只是多余。超时:默认 400 ms;大分辨率或总线同时被 IPU/USB 占用时给到 500~1000 ms。
失败处理:单帧失败计数并丢帧;
IM_ERR_TIMEOUT/IM_ERR_HARDWARE连续出现时 重开会话(deinit + init)。下游尺寸:编码器/显示配置必须用输出尺寸和输出 stride,而不是源尺寸。
方向验证:第一次接入时跑一次 §9.5 的图案自测,确认顺时针/逆时针符合预期。
8.3 sensor 旋转特有现象
| 现象 | 常见原因 | 排查 |
|---|---|---|
| 画面转了但方向反 | ROTATE_90 是顺时针;想要逆时针用 ROTATE_270 | 用图案自测确认 |
| 画面上下颠倒 | 相机装反或需要 vertical_flip | 叠加 vertical_flip,不是换 rotate |
| 红蓝对调 | YUV 字节序(YVYU/UYVY)由上游决定 | 查相机中间件的 dump_data_order,不是 I2D |
| 每行错位/斜条纹 | dst_stride 用了源 stride | 用输出宽 × bpp |
| 输出整片 0 或旧数据 | 目标 buffer 太小、cache 未 invalidate、I2D 没启动 | 看自测的「目标非零」计数;检查返回码 |
| 第一帧正确、后续花 | 目标 buffer 被下游同时复用 | 双缓冲,或等下游读完再写 |
偶发 IM_ERR_TIMEOUT | 总线被 IPU/USB 抢、分辨率大 | 放宽超时;必要时调 outstanding_limit |
9. 调试与验证
9.1 可观测性
中间件本身没有任何日志,也没有状态查询接口。定位问题只能依靠:
im_i2d_process()/im_i2d_init()的返回码(§5.6);应用自己在调用前后打印的几何、stride、buffer 地址与耗时;
目标 buffer 的内容(图案自测、抽样比对);
直接读 I2D 寄存器(
VERSION、IRQ_STATE、IRQ_MASK、OUTSTANDING)。
9.2 上板诊断顺序
先跑
register,确认 I2D 寄存器窗口可访问;再跑
code/middleware/i2d/example,确认单帧旋转与抽样校验通过;再跑
function,确认所有格式与几何组合;出现
IM_ERR_TIMEOUT/IM_ERR_HARDWARE时,先降分辨率、降 outstanding 压力, 排除总线带宽因素;出现数据不一致时,优先检查 stride、NV12 的 UV 偏移、旋转后的 buffer 尺寸和 cache 维护;
需要判断是否是总线拥塞时,用
test/g32s10/module/i2d/outstanding的扫描方法, 而不是继续怀疑几何。