ISP模块参数统一对外接口插值逻辑(二)

目录

[1. 两边变量原始类型](#1. 两边变量原始类型)

[2. 强制类型转换 (uint8_t*) 的作用](#2. 强制类型转换 (uint8_t*) 的作用)

[3. 为什么要统一用 uint8_t* 存储所有模块的标定参数池?](#3. 为什么要统一用 uint8_t* 存储所有模块的标定参数池?)

[4. 内存布局直观演示](#4. 内存布局直观演示)

[5. 对比其他模块,验证通用性](#5. 对比其他模块,验证通用性)

[6. 总结](#6. 总结)


在上一篇博文中ISP模块参数统一对外接口插值逻辑(一)重点讲解了整个ISP Pipeline中使用统一接口进行插值的原理。这篇博文主要讲解其中的一些细节。

在标定初始化如下代码段中:

cpp 复制代码
static uint32_t lsc_cal_gain_list[] = {100, 400, 800, 1600, 3200}; // 升序标定增益
static ISP_LSC_ATTR lsc_cal_param_pool[5]; // 5组LSC标定参数
ISP_CAL_HEAD_T g_lsc_cal_head = {
    .cal_cnt = 5,
    .p_gain_arr = lsc_cal_gain_list,
    .p_param_pool = (uint8_t*)lsc_cal_param_pool,
    .param_single_size = sizeof(ISP_LSC_ATTR),
    .enable_interp = 1, // LSC随增益插值
    .gain_sorted_ok = 1 // 增益数组升序校验通过
};
  • 工程启动时从 Flash / 配置文件填充lsc_cal_param_pool每组 ISO 对应的完整 LSC 参数;
  • DPC 模块配置enable_interp=0,静态坏点不随增益插值。

对其中.p_param_pool = (uint8_t*)lsc_cal_param_pool, 这个赋值的理解如下:

1. 两边变量原始类型

cpp 复制代码
// 标定表头结构体成员
uint8_t*  p_param_pool; 

// LSC标定参数数组(多组完整LSC结构体)
static ISP_LSC_ATTR lsc_cal_param_pool[];
  • lsc_cal_param_pool结构体数组,一块连续内存,存放 N 档 ISO 对应的整套 LSC 参数;
  • p_param_pool:结构体里的通用指针,设计为 uint8_t*(字节指针 / 无类型内存指针),目的是统一管理所有不同大小的 ISP 结构体(LSC/DRC/Gamma/DPC 尺寸全不一样)。

2. 强制类型转换 (uint8_t*) 的作用

场景矛盾

直接赋值会报类型不匹配:

cpp 复制代码
// 报错:AK_ISP_LSC_ATTR* 不能直接赋值给 uint8_t*
p_param_pool = lsc_cal_param_pool;

数组名 lsc_cal_param_pool 等价于 ISP_LSC_ATTR *,是带结构体类型的指针 ; 而 p_param_pool字节裸指针 uint8_t*,两者类型不同,编译器类型校验失败。

转换的本质

(uint8_t*)lsc_cal_param_pool: 把「指向 LSC 结构体的类型指针」强制转换成「按单字节寻址的通用内存指针」,只改变编译器对内存的解析方式,不改变内存地址本身

举个直观例子: 假设数组首地址是 0x20000000

  • ISP_LSC_ATTR *:每次指针 + 1,偏移 sizeof(ISP_LSC_ATTR) 字节(一整套 LSC 参数长度)
  • uint8_t *:每次指针 + 1,只偏移 1 个字节,纯内存块操作

3. 为什么要统一用 uint8_t* 存储所有模块的标定参数池?

工程里每个 ISP 模块结构体尺寸完全不同:

  • LSC:sizeof(ISP_LSC_ATTR) 几百字节(带二维网格)
  • DRC:sizeof(ISP_DRC_REG) 几十字节
  • YUV Gamma LUT:256 点数组,几百字节
  • DPC:小型结构体

如果给 ISP_CAL_HEAD_T 为每个模块单独定义结构体指针,会出现:

  1. 结构体重复定义,代码冗余爆炸;
  2. 二分后计算第 N 组参数地址逻辑无法通用。

统一字节指针的通用寻址公式(核心用途)

代码中获取第 left_idx 档标定参数时:

cpp 复制代码
// 计算第 left_idx 组参数起始地址
uint8_t* p_cal_low  = p_cal->p_param_pool + left_idx * p_cal->param_single_size;
  • param_single_size:当前模块单个结构体的字节长度 sizeof(ISP_LSC_ATTR)
  • p_param_pooluint8_t*,指针加法 =字节偏移,可以通过「下标 × 单组长度」精准跳转到任意一档标定参数的内存起始位置。

不管这个模块是 LSC、DRC 还是 Gamma,这套寻址公式完全通用,不需要为每种结构体写单独的数组下标访问逻辑。

4. 内存布局直观演示

cpp 复制代码
static ISP_LSC_ATTR lsc_cal_param_pool[5]; // 5档标定参数,连续内存
// 内存排布:
// [0x00 ~ SZ-1] 第0档ISO参数
// [SZ ~ 2*SZ-1] 第1档ISO参数
// [2*SZ ~ 3*SZ-1] 第2档ISO参数
// SZ = sizeof(AK_ISP_LSC_ATTR)

ISP_CAL_HEAD_T g_lsc_cal_head = {
    .cal_cnt = 5,
    .p_gain_arr = lsc_cal_gain_list,
    // 把结构体数组首地址转为字节指针,存入通用内存池指针
    .p_param_pool = (uint8_t*)lsc_cal_param_pool,
    .param_single_size = sizeof(ISP_LSC_ATTR),
    .enable_interp = 1,
    .gain_sorted_ok = 1
};

当代码需要取第 2 档标定参数:

cpp 复制代码
uint32_t SZ = g_lsc_cal_head.param_single_size;
uint8_t* p_2nd_param = g_lsc_cal_head.p_param_pool + 2 * SZ;
// p_2nd_param 直接指向 lsc_cal_param_pool[2] 的内存首地址
// 使用时再强转回结构体指针:
const ISP_LSC_ATTR *p_para = (const ISP_LSC_ATTR *)p_2nd_param;

5. 对比其他模块,验证通用性

DRC 模块标定定义:

cpp 复制代码
static ISP_DRC_ATTR drc_cal_param_pool[2];
ISP_CAL_HEAD_T g_drc_cal_head = {
    .cal_cnt = 2,
    .p_gain_arr = drc_cal_gain_list,
    // 同样强转 uint8_t*,和LSC共用一套寻址逻辑
    .p_param_pool = (uint8_t*)drc_cal_param_pool,
    .param_single_size = sizeof(ISP_DRC_ATTR),
    .enable_interp = 0,
    .gain_sorted_ok = 1
};

DRC、LSC 两套完全不同的结构体,都存入 uint8_t* p_param_pool,二分查找、参数拷贝、插值回调的上层代码不需要做任何区分,一套逻辑适配全部 ISP 模块。

6. 总结

  1. lsc_cal_param_pool结构体数组 ,自带类型 ISP_LSC_ATTR *
  2. p_param_pool 是通用字节裸指针 uint8_t*,用于统一管理所有尺寸不同的 ISP 标定参数内存;
  3. (uint8_t*) 强制转换消除类型不匹配,让代码可以通过「下标 × 单组长度」的通用字节偏移算法,任意跳转到任意一档标定参数,实现多模块统一调度。