☰
JX-F23 sensor 驱动开发实战:从硬件时序到 V4L2 出图全流程
2026/10/2 22:17:37 网站建设 项目流程

简介:这份资源是面向嵌入式驱动开发者的 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 总是返回 -ENODEV

CMOS sensor 通常有多路供电:AVDD(模拟,常见 2.8V)、DVDD(数字核心,常见 1.2V)、DOVDD(IO,常见 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-f23@30 { 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 lane,SoC 端 DPHY 也得配 2 lane,少一条就出不了图。速率对不上,典型现象是 dmesg 里 CSI 报err_stat或者出图上半部分正常下半部分绿。

我一般会先确认 SoC 侧的 DPHY 配置,再回头核对 sensor 寄存器里的 MIPI 相关设置。RV1126B 这类平台,MIPI 接收端在设备树里通常长这样:

&csi_dphy { status = "okay"; }; &mipi_csi2 { status = "okay"; ports { port@0 { mipi_csi2_input: endpoint@1 { 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 扫不到 0x30,dmesg 无任何 sensor 相关日志

原因:硬件层面没通。最常见是供电没上、reset 没释放、I2C 上拉电阻缺失或阻值不对(一般 2.2K~4.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-video=width=1920,height=1080,pixelformat=RG10 # 抓 10 帧存成 raw 文件 v4l2-ctl -d /dev/video0 --stream-mmap --stream-count=10 --stream-to=f23_1080p.raw

逻辑说明:--list-formats-ext能看到驱动实际支持的格式和分辨率,如果这里没有你要的格式,说明set_fmt回调没实现全。--stream-to存的是 raw Bayer 数据,不是图片,要用工具转。参数说明:pixelformat=RG10对应 10bit RGGB,不同平台命名可能不同,以--list-formats-ext输出为准。

抓完 raw 文件,用 Python 简单验证一下数据是不是合理,比如看前几个字节有没有全 0 或全 FF:

import numpy as np # 读 raw 文件,10bit 数据通常按 16bit 存储 data = np.fromfile("f23_1080p.raw", dtype=np.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 接近 1023(10bit)。如果全是 0,sensor 没出流;全是 1023,曝光过度或寄存器配错。参数说明:dtype按实际存储格式改,有的平台 10bit 打包成 8bit 存储,那就用uint8。

5.2 三个必调参数:曝光、增益、VTS

F23 出图后,画面太暗、太亮、有横纹,基本都是这三个参数的事。它们都在 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-ctrl=exposure # 设置曝光为 1000 行 v4l2-ctl -d /dev/video0 --set-ctrl=exposure=1000 # 设置增益 v4l2-ctl -d /dev/video0 --set-ctrl=gain=64 # 设置 VTS(垂直消隐) v4l2-ctl -d /dev/video0 --set-ctrl=vertical_blanking=100

逻辑说明:曝光和增益要配合调,先加曝光到画面够亮,不够再加增益。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 先通,再谈出图,出图了再调画质。每一步都有明确的验证手段,别跳步。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询