JX-F23 sensor 驱动开发实战:从硬件时序到 V4L2 出图全流程

发布时间:2026/10/2 22:17:40
JX-F23 sensor 驱动开发实战:从硬件时序到 V4L2 出图全流程 简介这份资源是面向嵌入式驱动开发者的 JX-F23 图像传感器驱动源码包适用于摄像头模组调试、Linux 平台 sensor 适配及高清视频采集方案的学习与移植。包内共 6 个文件以 2 个 c 源文件、2 个 o 编译产物、1 个 h 头文件和 1 个 Makefile 为主整体约 42KB结构紧凑覆盖了 sensor 控制逻辑、CMOS 寄存器配置与编译构建脚本便于直接阅读驱动实现或集成到现有工程中。资源围绕 1920×1080 分辨率、30FPS 帧率的高清视频采集场景展开涉及数据传输、电源管理、中断处理与错误恢复等关键环节可帮助读者理解操作系统与硬件之间的驱动桥梁作用。目前已有 387 人学习下载适合需要快速上手 F23 sensor 驱动开发、对照源码排查兼容性与功耗问题的工程师参考。1. JX-F23 sensor 驱动到底在驱动什么从一颗 CMOS 到 /dev 节点手上拿到一块标着 soi_f23_JX-F23 的模组第一反应往往不是兴奋而是懵丝印上只有 F23 三个字排线是 24pin 还是 32pin 得数供电是 1.8V 还是 2.8V 得猜I2C 地址是 0x30 还是 0x36 得试。JX-F23 这类 CMOS sensor 驱动的本质是把一颗靠 I2C 配置寄存器、靠 MIPI 或 DVP 出流的图像传感器变成一个 Linux 内核里能 open、能 ioctl、能出帧的 /dev/videoX 设备。它解决的不是「拍得好看」而是「拍得到」——上电时序对不对、时钟给没给、寄存器写得进不进、第一帧出不出得来。适合谁看手里有 F23 模组、跑着 RV1126B 或类似 SoC、正在被 sensor 出图全流程卡住的嵌入式驱动工程师。这篇不讲空理论讲我实际调 F23 时怎么从零把驱动跑通。2. 先搞懂 JX-F23 的硬件脾气上电时序、I2C 与 MIPI 三件事调 sensor 驱动翻车十次有八次不在代码在硬件脾气没摸清。JX-F23 这类 sensor 对供电顺序、复位时序、时钟稳定度都有硬性要求先把这三件事吃透后面写代码才有底气。2.1 供电与复位时序为什么你的 probe 总是返回 -ENODEVCMOS sensor 通常有多路供电AVDD模拟常见 2.8V、DVDD数字核心常见 1.2V、DOVDDIO常见 1.8V。JX-F23 具体电压以你手上模组的规格书为准但顺序逻辑是通用的先供 AVDD 和 DOVDD再供 DVDD或者按规格书要求的顺序最后拉高 reset、再拉高 pwdn。顺序错了sensor 内部 PLL 锁不住I2C 从机地址根本不响应probe 里读 chip id 就会返回 -ENODEV 或读到 0x00。我一般会在设备树里把这几路电写成 regulator用regulator-boot-on和regulator-always-on先保证上电再在驱动 probe 里用gpiod控制 reset 和 pwdn 的时序。下面是一段典型的设备树片段参数按你的板子改i2c1 { status okay; clock-frequency 400000; jx_f23: jx-f2330 { compatible vendor,jx-f23; reg 0x30; clocks cru CLK_MIPI_CAMARAOUT_M1; clock-names xvclk; power-domains power RV1126_PD_VI; pinctrl-names default; pinctrl-0 mipim0_camera1_clk; reset-gpios gpio1 RK_PB0 GPIO_ACTIVE_LOW; pwdn-gpios gpio1 RK_PB1 GPIO_ACTIVE_HIGH; avdd-supply vcc_2v8; dovdd-supply vcc_1v8; dvdd-supply vcc_1v2; port { jx_f23_out: endpoint { remote-endpoint mipi_csi2_input; ># 列出 i2c1 总线上的所有设备 i2cdetect -y 1 # 期望看到 0x30 位置有设备显示为 30 # 0 1 2 3 4 5 6 7 8 9 a b c d e f # 00: -- -- -- -- -- -- -- -- # 30: 30 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- # 读 chip id 寄存器地址以规格书为准这里假设 0x0A/0x0B i2cget -y 1 0x30 0x0a i2cget -y 1 0x30 0x0b逻辑说明i2cdetect扫不到 0x30先查硬件——供电有没有、reset 有没有释放、上拉电阻有没有、SID 引脚电平对不对。扫到了但i2cget报Error: Read failed多半是寄存器地址不对或者 sensor 还在 standby。参数说明-y跳过交互确认-f强制访问已被驱动占用的地址调试时有用但正式跑要卸载驱动。提示如果 i2cdetect 显示的是UU说明该地址已被内核驱动占用不是坏事说明驱动已经绑上了这时候该去查 dmesg 而不是继续 i2cget。2.3 MIPI CSI-2 链路lane 数、速率与 SoC 接收端要配对F23 出流走 MIPI CSI-2 的话sensor 端的 lane 数、速率必须和 SoC 接收端RV1126B 的 MIPI CSI 控制器严格配对。sensor 端配了 2 laneSoC 端 DPHY 也得配 2 lane少一条就出不了图。速率对不上典型现象是 dmesg 里 CSI 报err_stat或者出图上半部分正常下半部分绿。我一般会先确认 SoC 侧的 DPHY 配置再回头核对 sensor 寄存器里的 MIPI 相关设置。RV1126B 这类平台MIPI 接收端在设备树里通常长这样csi_dphy { status okay; }; mipi_csi2 { status okay; ports { port0 { mipi_csi2_input: endpoint1 { remote-endpoint jx_f23_out; >static int jx_f23_probe(struct i2c_client *client) { struct jx_f23 *f23; int ret; f23 devm_kzalloc(client-dev, sizeof(*f23), GFP_KERNEL); if (!f23) return -ENOMEM; f23-client client; v4l2_i2c_subdev_init(f23-sd, client, jx_f23_subdev_ops); /* 1. 拿 GPIO */ f23-reset_gpio devm_gpiod_get(client-dev, reset, GPIOD_OUT_LOW); f23-pwdn_gpio devm_gpiod_get(client-dev, pwdn, GPIOD_OUT_LOW); /* 2. 拿 regulator 并上电 */ f23-avdd devm_regulator_get(client-dev, avdd); f23-dovdd devm_regulator_get(client-dev, dovdd); f23-dvdd devm_regulator_get(client-dev, dvdd); regulator_enable(f23-avdd); regulator_enable(f23-dovdd); regulator_enable(f23-dvdd); /* 3. 复位时序 */ gpiod_set_value(f23-reset_gpio, 0); usleep_range(1000, 2000); gpiod_set_value(f23-reset_gpio, 1); usleep_range(5000, 6000); /* 4. 读 chip id 验证 */ ret jx_f23_read_reg(f23, JX_F23_REG_CHIP_ID_H, f23-chip_id_h); if (ret || f23-chip_id_h ! 0x23) { dev_err(client-dev, chip id mismatch: 0x%02x\n, f23-chip_id_h); return -ENODEV; } return 0; }逻辑说明devm_gpiod_get的第二个参数要和设备树里reset-gpios的前缀对应写错就拿到 NULL。GPIOD_OUT_LOW是初始输出低配合GPIO_ACTIVE_LOW表示一开始处于复位态。上电顺序按 2.1 说的来先 AVDD/DOVDD 再 DVDD。chip id 读出来不是预期值直接返回 -ENODEV别硬往下走。参数说明usleep_range的延时值按规格书来F23 一般 reset 释放后要等 5ms 以上才能写寄存器。JX_F23_REG_CHIP_ID_H是高位寄存器地址具体值查规格书我见过 0x0A 和 0x00 两种。3.2 寄存器读写I2C 传输的两种写法和常见错误sensor 寄存器是 16 位地址、8 位数据I2C 传输要用i2c_transfer拼消息。这里给一个通用的读写函数F23 和大多数 sensor 都能用。static int jx_f23_read_reg(struct jx_f23 *f23, u16 reg, u8 *val) { struct i2c_client *client f23-client; struct i2c_msg msgs[2]; u8 buf[2]; int ret; buf[0] reg 8; buf[1] reg 0xff; msgs[0].addr client-addr; msgs[0].flags 0; msgs[0].len 2; msgs[0].buf buf; msgs[1].addr client-addr; msgs[1].flags I2C_M_RD; msgs[1].len 1; msgs[1].buf val; ret i2c_transfer(client-adapter, msgs, 2); if (ret 0) dev_err(client-dev, read reg 0x%04x failed: %d\n, reg, ret); return ret 0 ? ret : 0; } static int jx_f23_write_reg(struct jx_f23 *f23, u16 reg, u8 val) { u8 buf[3] { reg 8, reg 0xff, val }; struct i2c_msg msg { .addr f23-client-addr, .flags 0, .len 3, .buf buf, }; int ret i2c_transfer(f23-client-adapter, msg, 1); return ret 0 ? ret : 0; }逻辑说明读操作是「先写寄存器地址再读数据」的两条消息用i2c_transfer一次发出去中间不能有 stop否则 sensor 不认。写操作是三条字节地址高、地址低、数据。参数说明client-addr是 7 位地址i2c_transfer内部会处理读写位。常见错误是用i2c_smbus_read_byte_data它只支持 8 位寄存器地址F23 是 16 位用了就读错寄存器。3.3 出图配置分辨率、帧率与 MIPI 寄存器序列sensor 出图靠一长串寄存器初始化序列通常厂商会给一份「初始化表」几百行寄存器值。这份表不能照抄因为里面的 MIPI 速率、PLL 配置和你的 XVCLK、目标分辨率强相关。我一般会先确认三组参数输出分辨率、帧率、MIPI lane 速率再让厂商 FAE 给对应配置或者用 PLL 公式自己算。下面是一个设置格式的回调骨架把分辨率、帧率、MIPI 配置串起来static int jx_f23_set_fmt(struct v4l2_subdev *sd, struct v4l2_subdev_state *state, struct v4l2_subdev_format *fmt) { struct jx_f23 *f23 to_jx_f23(sd); u32 width fmt-format.width; u32 height fmt-format.height; /* 按分辨率选初始化序列 */ if (width 1920 height 1080) jx_f23_write_array(f23, jx_f23_1080p_regs, ARRAY_SIZE(jx_f23_1080p_regs)); else if (width 1280 height 720) jx_f23_write_array(f23, jx_f23_720p_regs, ARRAY_SIZE(jx_f23_720p_regs)); else return -EINVAL; fmt-format.code MEDIA_BUS_FMT_SRGGB10_1X10; fmt-format.field V4L2_FIELD_NONE; return 0; }逻辑说明jx_f23_write_array是遍历寄存器表逐条写写完要检查返回值中间某条写失败后面全乱。MEDIA_BUS_FMT_SRGGB10_1X10是 Bayer 格式F23 常见 RGGB 10bit具体看规格书写错颜色会偏。参数说明分辨率切换时除了写寄存器表还要重新配置 MIPI 速率因为不同分辨率下 PLL 分频不同。帧率通过 VTS/HTS 寄存器调改这两个值就能改帧率但要注意曝光时间不能超过 VTS。注意初始化序列里通常有一条stream on寄存器写完它 sensor 才开始出流。这条要放在最后前面所有配置写完再写它顺序反了出不了图。4. 避坑与排查F23 驱动调不通时先看这五条调 sensor 驱动dmesg 是你的黑匣子但有些坑 dmesg 不会直接告诉你。下面五条是我调 F23 时真实踩过的按「现象 → 原因 → 解决」写照着排查能省不少时间。4.1 现象i2cdetect 扫不到 0x30dmesg 无任何 sensor 相关日志原因硬件层面没通。最常见是供电没上、reset 没释放、I2C 上拉电阻缺失或阻值不对一般 2.2K4.7K或者 SID 引脚电平让地址变成了 0x36。解决先用万用表量 AVDD/DVDD/DOVDD 三路电压再量 reset 和 pwdn 引脚电平确认和规格书一致。上拉电阻用示波器看 I2C 波形上升沿太缓就是阻值太大。SID 引脚如果悬空地址不确定按规格书接高或接低。4.2 现象i2cdetect 能扫到但读 chip id 返回 0x00 或 0xFF原因sensor 还在 standby或者寄存器地址不对或者 I2C 速率太高400K 在某些板子上不稳。解决确认 reset 释放后延时够不够F23 一般要 5ms 以上。把 I2C 速率降到 100K 试。寄存器地址查规格书别用别的 sensor 的地址套。如果读出来一直是 0xFF多半是 I2C 数据线没接好或者 sensor 没供电。4.3 现象驱动 probe 成功但 /dev/videoX 出流时报 timeout原因MIPI 链路没通。sensor 端 lane 数和 SoC 端不匹配或者 link frequency 超了 DPHY 上限或者 sensor 的 stream on 寄存器没写。解决先确认两边># 列出所有 video 设备 v4l2-ctl --list-devices # 查看 /dev/video0 支持的格式 v4l2-ctl -d /dev/video0 --list-formats-ext # 设置格式为 1920x1080 RGGB10 v4l2-ctl -d /dev/video0 --set-fmt-videowidth1920,height1080,pixelformatRG10 # 抓 10 帧存成 raw 文件 v4l2-ctl -d /dev/video0 --stream-mmap --stream-count10 --stream-tof23_1080p.raw逻辑说明--list-formats-ext能看到驱动实际支持的格式和分辨率如果这里没有你要的格式说明set_fmt回调没实现全。--stream-to存的是 raw Bayer 数据不是图片要用工具转。参数说明pixelformatRG10对应 10bit RGGB不同平台命名可能不同以--list-formats-ext输出为准。抓完 raw 文件用 Python 简单验证一下数据是不是合理比如看前几个字节有没有全 0 或全 FFimport numpy as np # 读 raw 文件10bit 数据通常按 16bit 存储 data np.fromfile(f23_1080p.raw, dtypenp.uint16) print(total pixels:, data.size) print(first 16 values:, data[:16]) print(min/max:, data.min(), data.max()) # 如果全是 0 或全是 1023说明没出图或曝光异常 if data.max() 0 or data.min() 1023: print(WARNING: data looks invalid, check stream on and exposure)逻辑说明正常出图的 raw 数据应该有丰富的灰度分布min 接近 0、max 接近 102310bit。如果全是 0sensor 没出流全是 1023曝光过度或寄存器配错。参数说明dtype按实际存储格式改有的平台 10bit 打包成 8bit 存储那就用uint8。5.2 三个必调参数曝光、增益、VTSF23 出图后画面太暗、太亮、有横纹基本都是这三个参数的事。它们都在 sensor 寄存器里通过 V4L2 的V4L2_CID_EXPOSURE、V4L2_CID_GAIN和V4L2_CID_VBLANK控制。参数V4L2 CID作用调整方向注意曝光V4L2_CID_EXPOSURE控制积分时间画面暗就加大不能超过 VTS超了帧率掉增益V4L2_CID_GAIN控制模拟/数字增益曝光加到头还暗就加增益加太多噪点明显VTSV4L2_CID_VBLANK控制帧总行数帧率低就减小太小会导致曝光受限用 v4l2-ctl 调这三个参数# 查当前曝光值 v4l2-ctl -d /dev/video0 --get-ctrlexposure # 设置曝光为 1000 行 v4l2-ctl -d /dev/video0 --set-ctrlexposure1000 # 设置增益 v4l2-ctl -d /dev/video0 --set-ctrlgain64 # 设置 VTS垂直消隐 v4l2-ctl -d /dev/video0 --set-ctrlvertical_blanking100逻辑说明曝光和增益要配合调先加曝光到画面够亮不够再加增益。VTS 决定帧率VTS 越小帧率越高但曝光上限也越小。参数说明曝光单位是行具体一行多少时间看 sensor 的 HTS 和像素时钟。增益单位各平台不同有的是倍数有的是寄存器值以驱动实现为准。我调 F23 时最常遇到的是画面有横纹最后发现是曝光时间接近 VTS 上限把 VTS 加大就好了。这个坑 dmesg 不会报只能靠看画面。5.3 一个习惯每次改寄存器前先 dump 一份调 sensor 寄存器最怕改乱了回不去。我现在的习惯是每次动初始化序列之前先用i2cdump把当前寄存器状态存一份改完出问题就对比。# dump 整个 0x30 设备的寄存器16 位地址用 i2cdump 的 mode i2cdump -y 1 0x30 # 存到文件 i2cdump -y 1 0x30 f23_regs_before.txt逻辑说明i2cdump默认按 8 位地址扫F23 是 16 位地址扫出来的数据可能不全但作为「改之前」的基线够用。真要完整 dump得写个脚本用i2cget逐条读。参数说明-y跳过确认-f强制访问被驱动占用的地址需要先卸载驱动。这个习惯救过我好几次。有一次改 MIPI 速率改完花屏对比 dump 发现有一条 PLL 寄存器写错了改回来就好了。sensor 寄存器几百条靠脑子记不住留个后悔药比什么都强。调 F23 这类 sensor说到底是个耐心活硬件先确认I2C 先通再谈出图出图了再调画质。每一步都有明确的验证手段别跳步。希望帮到你。本文还有配套的精品资源点击获取