VO自定义时序计算以及MIPI屏幕调试

VO自定义时序计算以及MIPI屏幕调试 Sunshine 2025-09-24 16:07:28 236

前言

  • 海思平台VO自定义时序说明,下面以海鸥派SS928为例自定义MIPI屏时序。

  • MIPI屏幕型号:M101WXB40-01A(MIPI 800x1280@60fps)。

  • 文章中MIPI屏幕时序计算可通过海思SDK提供的ReleaseDoc\zh\02.only for reference\software\RGB_MIPI屏幕时钟时序计算器.xlsx计算。

一、定义时序信息

参考文档MPP 媒体处理软件 V5.0 开发参考.pdf, ot_vo_sync_info

根据上面的公式计算出像素时钟:(1280+24+20)x(800+40+40)x 60 = 69907200Hz ≈ 69908KHz。

二、用户接口时序信息

参考文档MPP 媒体处理软件 V5.0 开发参考.pdf, ot_vo_user_sync_info

使用下面的python脚本计算:

import itertools

#pixel_clk = (hbb+hact+hfb)*(vbb+vact+vfb) * frame_rate /1000000
pixel_clk = 70

FREF      = 24                        # FREF       PLL 输入参考时钟 固定输入24MHz

# 设定范围
g_fb_div    = range(0, 4095)          # fb_div      PLL 整数倍频系数,数值范围:[0,0xfff]。
g_frac      = range(0, 16777215)      # frac        PLL 小数分频系数,数值范围:[0,0xffffff]。
g_ref_div   = [1]                     # ref_div     PLL 参考时钟分频系数,SS928V100/SS626V100只能为1。
g_post_div1 = range(1, 7)             # post_div1   PLL 第一级输出分频系数,数值范围:(0,0x7]。
g_post_div2 = range(1, 7)             # post_div2   PLL 第二级输出分频系数,数值范围:(0,0x7]。
g_pre_div   = [1]                     # pre_div     设备前置分频,数值范围[1, 32],只有在SS528V100/SS625V100/SS524V100/SS522V100采用HDMI(含HDMI同源)接口输出时设备前置分频属性需要进行配置,其他接口输出时前置分频属性配置为1(不分频)
g_dev_div   = [1]                     # dev_div     设备的时钟分频比,数值范围:[1, 4], SS928V100 HDMI/VGA/BT1120/MIPI 固定为1

#要求
#FOUTVCO     = FREF x ( fb_div + frac/2^24) / ref_div      #PLL 工作频率,要求大于等于800MHz,且小于等于3.2GHz
#FOUTPOSTDIV = FOUTVCO / (post_div1 xpost_div2)            #post_div1 >= post_div2     [16.326531,594]MHz。
#pixel_clk   = FOUTPOSTDIV / (pre_div × dev_div)

def find_pll_parameters():
    """查找PLL参数配置"""
    results = []

    # 遍历所有可能的组合
    for ref_div, post_div1, post_div2, pre_div, dev_div in itertools.product(g_ref_div, g_post_div1, g_post_div2, g_pre_div, g_dev_div):
        # 检查 post_div1 >= post_div2
        if post_div1 < post_div2:
            continue

        # 计算总的分频比
        total_div = ref_div * post_div1 * post_div2 * pre_div * dev_div

        # 计算需要的FVCO
        required_fvco = pixel_clk * total_div

        # 检查FVCO范围 [800, 3200] MHz
        if not (800 <= required_fvco <= 3200):
            continue

        # 计算FOUTPOSTDIV并检查范围 [16.326531, 594] MHz
        required_foutpostdiv = pixel_clk * pre_div * dev_div
        if not (16.326531 <= required_foutpostdiv <= 594):
            continue

        # 计算分频比
        div_ratio = required_fvco / FREF
        g_fb_div = int(div_ratio)
        g_frac = int((div_ratio - g_fb_div) * (2 ** 24))

        # 检查参数范围
        if 1 <= g_fb_div < 4095 and 0 <= g_frac < 16777215:
            # 验证实际频率
            actual_fvco = FREF * (g_fb_div + g_frac / (2 ** 24))
            actual_foutpostdiv = actual_fvco / (post_div1 * post_div2)
            actual_pixel_clk = actual_foutpostdiv / (pre_div * dev_div)

            # 检查误差
            error = abs(actual_pixel_clk - pixel_clk)
            if error < 0.001:  # 允许1kHz误差
                results.append({
                    'fb_div': g_fb_div,
                    'frac': g_frac,
                    'ref_div': ref_div,
                    'post_div1': post_div1,
                    'post_div2': post_div2,
                    'pre_div': pre_div,
                    'dev_div': dev_div,
                    'total_div': total_div,
                    'fvco': actual_fvco,
                    'foutpostdiv': actual_foutpostdiv,
                    'pixel_clk': actual_pixel_clk,
                    'error': actual_pixel_clk - pixel_clk,
                    'error_ppm': (actual_pixel_clk - pixel_clk) / pixel_clk * 1e6
                })

    return results


# 更高效的搜索版本,按总分频比排序
def find_pll_parameters_sorted():
    """查找并排序PLL参数配置"""
    results = find_pll_parameters()

    # 按总分频比排序(较小的分频比通常更好)
    results.sort(key=lambda x: x['total_div'])

    return results

def select_best_solution(solutions):
    """选择最佳方案"""
    if not solutions:
        return None

    # 评分函数
    def score_solution(sol):
        score = 0

        # 1. 分频比最小化(权重最高)
        total_div = sol['total_div']
        score -= total_div * 1000  # 分频比越小越好

        # 2. 整数分频优先(frac=0)
        if sol['frac'] == 0:
            score += 5000  # 整数分频大幅加分

        # 3. FVCO接近理想值2000MHz
        fvco_ideal = 2000
        fvco_diff = abs(sol['fvco'] - fvco_ideal)
        score -= fvco_diff * 10  # 越接近理想值越好

        # 4. 分频系数简化
        # post_div1和post_div2接近
        div_ratio = sol['post_div1'] / sol['post_div2']
        if div_ratio == 1:  # 相等最好
            score += 100
        elif div_ratio == 2 or div_ratio == 0.5:  # 2倍关系较好
            score += 50

        # 5. 使用标准分频值加分
        standard_divs = [1, 2, 4, 8, 16]
        if sol['ref_div'] in standard_divs:
            score += 20
        if sol['post_div1'] in standard_divs:
            score += 20
        if sol['post_div2'] in standard_divs:
            score += 20

        return score

    # 按评分排序
    scored_solutions = [(sol, score_solution(sol)) for sol in solutions]
    scored_solutions.sort(key=lambda x: x[1], reverse=True)

    return scored_solutions[0][0]  # 返回评分最高的方案

def analyze_solutions(solutions):
    """分析并推荐最佳方案"""
    if not solutions:
        print("未找到合适的解决方案")
        return

    # 方案1:总分频比最小
    min_div_solution = min(solutions, key=lambda x: x['total_div'])

    # 方案2:整数分频优先
    integer_solutions = [sol for sol in solutions if sol['frac'] == 0]
    if integer_solutions:
        best_integer = min(integer_solutions, key=lambda x: x['total_div'])
    else:
        best_integer = None

    # 方案3:综合评分最佳
    best_overall = select_best_solution(solutions)

    # 显示各候选方案
    print("\n\n=== 候选方案分析 ===")
    print(f"1. 最小分频比方案 (total_div={min_div_solution['total_div']}):")
    print_solution_details(min_div_solution)

    if best_integer:
        print(f"\n2. 整数分频最佳方案 (frac=0):")
        print_solution_details(best_integer)
    else:
        print("\n2. 无整数分频方案")

    print(f"\n3. 综合最佳方案:")
    print_solution_details(best_overall)

def print_solution_details(sol):
    """打印方案详情"""
    print(f"  fb_div    = {sol['fb_div']} (0x{sol['fb_div']:03x})")
    print(f"  frac      = {sol['frac']} (0x{sol['frac']:06x})")
    print(f"  ref_div   = {sol['ref_div']}")
    print(f"  post_div1 = {sol['post_div1']}")
    print(f"  post_div2 = {sol['post_div2']}")
    print(f"  pre_div   = {sol['pre_div']}")
    print(f"  dev_div   = {sol['dev_div']}")
    print(f"  总分频比   = {sol['total_div']}")
    print(f"  FVCO      = {sol['fvco']:.6f} MHz")
    print(f"  FOUTPOSTDIV = {sol['foutpostdiv']:.6f} MHz")
    print(f"  像素时钟   = {sol['pixel_clk']:.6f} MHz")
    print(f"  误差       = {sol['error']:.6f} MHz")
    print(f"  相对误差   = {sol['error_ppm']:.2f} ppm")

def main():
    # 执行搜索
    print("搜索PLL参数配置...")
    print(f"目标像素时钟: {pixel_clk} MHz")
    print(f"计算公式: 像素时钟 = FOUTPOSTDIV / (pre_div × dev_div)")
    print()

    solutions = find_pll_parameters_sorted()

    print(f"找到 {len(solutions)} 个解决方案:")
    for i, sol in enumerate(solutions):
        print(f"\n方案 {i + 1}:")
        print_solution_details(sol)
        # 验证计算
        calculated_foutpostdiv = sol['fvco'] / (sol['post_div1'] * sol['post_div2'])
        calculated_pixel_clk = calculated_foutpostdiv / (sol['pre_div'] * sol['dev_div'])
        print(f"  验证计算   = {calculated_pixel_clk:.6f} MHz")

    analyze_solutions(solutions)

if __name__ == '__main__':
    main()

选择其中一个结果:

三、MIPI屏幕代码配置

1. sample_vo_mipi_tx_cfg 配置

/* VO: USER 800x1280_60, TX: USER 800x1280 */
static const sample_vo_mipi_tx_cfg g_vo_tx_cfg_800x1280_user = {
    .vo_config = {
        .vo_dev = SAMPLE_VO_DEV_UHD,
        .vo_intf_type = OT_VO_INTF_MIPI,
        .intf_sync = OT_VO_OUT_USER,
        .bg_color = COLOR_RGB_BLACK,
        .pix_format = OT_PIXEL_FORMAT_YVU_SEMIPLANAR_420,
        .disp_rect = {0, 0, 800, 1280},
        .image_size = {800, 1280},
        .vo_part_mode = OT_VO_PARTITION_MODE_SINGLE,
        .dis_buf_len = 3, /* 3: def buf len for single */
        .dst_dynamic_range = OT_DYNAMIC_RANGE_SDR8,
        .vo_mode = VO_MODE_1MUX,
        .compress_mode = OT_COMPRESS_MODE_NONE,

        .sync_info = {0, 1, 1, 1280, 24, 20, 800, 40, 40, 1, 1, 1, 1, 20, 4, 0, 0, 0},
        .user_sync = {
            .user_sync_attr = {
                .clk_src = OT_VO_CLK_SRC_PLL,
                .vo_pll = { /* if hdmi, set it by pixel clk and div mode */
                    .fb_div = 105, /* 105 fb div */
                    .frac = 0,
                    .ref_div = 1, /* 1 ref div */
                    .post_div1 = 6, /* 6 post div1 */
                    .post_div2 = 6, /* 6 post div2 */
                },
            },
            .pre_div = 1, /* if hdmi, set it by pixel clk */
            .dev_div = 1, /* if rgb, set it by serial mode */
            .clk_reverse_en = TD_FALSE,
        },
        .dev_frame_rate = 60,
    },
    .tx_config = {
        /* for combo dev config */
        .intf_sync = OT_MIPI_TX_OUT_USER,

        /* for screen cmd */
        .cmd_count = CMD_COUNT_800X1280,
        .cmd_info = g_cmd_info_800x1280,

        /* for user sync */
        .combo_dev_cfg = {
            .devno = 0,
            .lane_id = {0, 1, 2, 3},
            .out_mode = OUT_MODE_DSI_VIDEO,
            .out_format = OUT_FORMAT_RGB_24BIT,

            .video_mode =  BURST_MODE,
            .sync_info = {
                .hsa_pixels = 20, /* 20 pixel */
                .hbp_pixels = 20, /* 20 pixel */
                .hact_pixels = 800, /* 800 pixel */
                .hfp_pixels = 40, /* 40 pixel */
                .vsa_lines = 4, /* 4 line */
                .vbp_lines = 20, /* 20 line */
                .vact_lines = 1280, /* 1280 line */
                .vfp_lines = 20, /* 20 line */
            },
            .phy_data_rate = 999, /* 999 Mbps */
            .pixel_clk = 70000, /* 70000 KHz */
        },
    },
};

其中g_cmd_info_800x1280为mipi屏幕初始化控制,注意在最后加上下面两行:

    {{0, 0, 0, 0x23, 0x0011, NULL}, USLEEP_120000},   #退出睡眠模式
    {{0, 0, 0, 0x23, 0x0029, NULL}, USLEEP_60000}     #开启显示

2. vo配置mipi屏出图

参考例程sample_vdec.c中的sample_start_vo, 新增start_vo_mipi_tx

static td_s32 sample_start_vo(sample_vo_cfg *vo_config, td_u32 vpss_grp_num)
{
    td_s32 ret;
    vo_config->vo_dev            = SAMPLE_VO_DEV_UHD;
    vo_config->vo_intf_type      = g_vdec_display_cfg.intf_type;
    vo_config->intf_sync         = g_vdec_display_cfg.intf_sync;
    vo_config->pic_size          = g_vdec_display_cfg.pic_size;
    vo_config->bg_color          = COLOR_RGB_BLUE;
    vo_config->dis_buf_len       = 3; /* 3:buf length */
    vo_config->dst_dynamic_range = OT_DYNAMIC_RANGE_SDR8;
    vo_config->vo_mode           = VO_MODE_1MUX;
    vo_config->pix_format        = OT_PIXEL_FORMAT_YVU_SEMIPLANAR_420;
    vo_config->disp_rect.x       = 0;
    vo_config->disp_rect.y       = 0;
    vo_config->disp_rect.width   = 1920; 
    vo_config->disp_rect.height  = 1080; 
    vo_config->image_size.width  = 1920; 
    vo_config->image_size.height = 1080; 
    vo_config->vo_part_mode      = OT_VO_PARTITION_MODE_SINGLE;
    vo_config->compress_mode     = OT_COMPRESS_MODE_NONE;

    ret = sample_comm_vo_start_vo(vo_config);
    if (ret != TD_SUCCESS) {
        sample_print("start VO fail for %#x!\n", ret);
        sample_comm_vo_stop_vo(vo_config);
        return ret;
    }

    ret = sample_vpss_bind_vo(*vo_config, vpss_grp_num);
    if (ret != TD_SUCCESS) {
        sample_vpss_unbind_vo(vpss_grp_num, *vo_config);
        sample_comm_vo_stop_vo(vo_config);
    }

    return ret;
}

static td_s32 start_vo_mipi_tx(const sample_vo_mipi_tx_cfg *vo_tx_cfg, td_u32 vpss_grp_num)
{
    td_s32 ret;
    const sample_vo_cfg *vo_config = &vo_tx_cfg->vo_config;
    const sample_mipi_tx_config *tx_config = &vo_tx_cfg->tx_config;

    ret = sample_comm_vo_start_vo(vo_config);
    if (ret != TD_SUCCESS) {
        sample_print("start vo failed with 0x%x!\n", ret);
        return ret;
    }
    printf("start vo dhd%d.\n", vo_config->vo_dev);

    ret = sample_vpss_bind_vo(*vo_config, vpss_grp_num);
    if (ret != TD_SUCCESS) {
        sample_vpss_unbind_vo(vpss_grp_num, *vo_config);
        sample_comm_vo_stop_vo(vo_config);
    }

    if ((vo_config->vo_intf_type & OT_VO_INTF_MIPI) ||
        (vo_config->vo_intf_type & OT_VO_INTF_MIPI_SLAVE)) {

        ret = sample_comm_start_mipi_tx(tx_config);
        if (ret != TD_SUCCESS) {
            sample_print("start mipi tx failed with 0x%x!\n", ret);
            return ret;
        }
    }

    return TD_SUCCESS;
}

3. 板端测试

测试前注意检查mipi_tx管脚复用以及mipi屏幕背光和复位控制。

管脚复用一般在sysconfig.ko驱动代码里已经写好,load脚本加载时传递参数即可。

VO_INTF=mipi_tx

# sys config
insmod $ko_path/sys_config.ko sensors=sns0=$SNS_TYPE0,sns1=$SNS_TYPE1,sns2=$SNS_TYPE2,sns3=$SNS_TYPE3 vo_intf=$VO_INTF

可参考ReleaseDoc\zh\01.software\board\MPP\SYS_CONFIG 配置指南.pdf

声明:本文内容由易百纳平台入驻作者撰写,文章观点仅代表作者本人,不代表易百纳立场。如有内容侵权或者其他问题,请联系本站进行删除。
Sunshine
红包 2 收藏 评论 打赏
评论
0个
内容存在敏感词
手气红包
    易百纳技术社区暂无数据
相关专栏
置顶时间设置
结束时间
删除原因
  • 广告/SPAM
  • 恶意灌水
  • 违规内容
  • 文不对题
  • 重复发帖
打赏作者
易百纳技术社区
Sunshine
您的支持将鼓励我继续创作!
打赏金额:
¥1易百纳技术社区
¥5易百纳技术社区
¥10易百纳技术社区
¥50易百纳技术社区
¥100易百纳技术社区
支付方式:
微信支付
支付宝支付
易百纳技术社区微信支付
易百纳技术社区
打赏成功!

感谢您的打赏,如若您也想被打赏,可前往 发表专栏 哦~

举报反馈

举报类型

  • 内容涉黄/赌/毒
  • 内容侵权/抄袭
  • 政治相关
  • 涉嫌广告
  • 侮辱谩骂
  • 其他

详细说明

审核成功

发布时间设置
发布时间:
是否关联周任务-专栏模块

审核失败

失败原因
备注
拼手气红包 红包规则
祝福语
恭喜发财,大吉大利!
红包金额
红包最小金额不能低于5元
红包数量
红包数量范围10~50个
余额支付
当前余额:
可前往问答、专栏板块获取收益 去获取
取 消 确 定

小包子的红包

恭喜发财,大吉大利

已领取20/40,共1.6元 红包规则

    易百纳技术社区