3. 传感器寄存器配置
好,咱们进入第三章。这一章是真正动手操作传感器的基础。说白了,你拿到一颗图像传感器,它不会自己工作。你得通过一根线,跟它“对话”,告诉它怎么跑、跑多快、输出多大的画面。
这根线,就是 I2C 或者 SPI。我个人习惯把这一章叫做“驯服传感器”的第一步。你配置对了,画面就出来了;配错了,要么黑屏,要么花屏,要么干脆没反应。我在项目里见过太多人卡在这一步,所以咱们今天把这几个关键点掰开揉碎了讲清楚。
3.1 I2C 与 SPI:两条路,怎么选?
传感器跟主控芯片通信,最常见的就是 I2C 和 SPI。它们俩的区别,我直接给你列个表,你一看就明白。
| 特性 | I2C | SPI |
|---|---|---|
| 信号线数量 | 2 根(SCL, SDA) | 4 根(SCLK, MOSI, MISO, CS) |
| 通信方式 | 半双工,主从架构 | 全双工,主从架构 |
| 速度 | 标准 100kHz / 400kHz,快一点 1MHz | 轻松上 10MHz,甚至更高 |
| 地址 | 7 位或 10 位设备地址 | 通过片选 CS 引脚选择 |
| 总线占用 | 多设备共用,靠地址区分 | 每个设备独占一根 CS 线 |
| 适用场景 | 配置寄存器、低速控制 | 高速数据流、批量寄存器写入 |
你可能会问,那我该用哪个?我的经验是:配置寄存器用 I2C 就够了。大部分传感器的寄存器读写频率不高,I2C 的 400kHz 完全够用,而且省引脚。但如果你需要快速写入一长串初始化序列,或者传感器支持 SPI 批量写入模式,那 SPI 会快很多。
3.2 寄存器映射:传感器的“控制面板”
每个传感器内部都有一张寄存器映射表。你可以把它想象成一张控制面板,上面有几百个旋钮和开关。每个旋钮对应一个寄存器地址,你往里面写值,就相当于拧了一下旋钮。
举个例子,一个典型的传感器寄存器映射长这样:
// 伪代码:传感器寄存器映射示例
#define SENSOR_ADDR 0x30 // I2C 设备地址
#define REG_CHIP_ID 0x00 // 芯片 ID,只读
#define REG_MODE_SELECT 0x01 // 模式选择:0x00 待机,0x01 流模式
#define REG_PLL_CONFIG 0x02 // PLL 配置
#define REG_FRAME_LEN_H 0x03 // 帧长度高位
#define REG_FRAME_LEN_L 0x04 // 帧长度低位
#define REG_LINE_LEN_H 0x05 // 行长度高位
#define REG_LINE_LEN_L 0x06 // 行长度低位
#define REG_X_START 0x07 // 窗口起始 X
#define REG_Y_START 0x08 // 窗口起始 Y
#define REG_X_END 0x09 // 窗口结束 X
#define REG_Y_END 0x0A // 窗口结束 Y
#define REG_MIRROR_FLIP 0x0B // 镜像翻转控制
嗯,这里要注意:寄存器地址和位定义,一定要以 datasheet 为准。不同厂商、不同型号的传感器,寄存器布局千差万别。我曾经吃过一次亏,把 A 传感器的寄存器地址套到 B 传感器上,结果画面全是乱的,查了整整一天才发现是地址搞错了。
3.3 初始化序列:让传感器“醒过来”
传感器上电后,不会立刻输出图像。你得按顺序给它发一串命令,就像叫醒一个人,得先开灯、再摇一摇、等他睁眼。这个命令序列就是初始化序列。
一个典型的初始化流程是这样的:
- 上电等待:给传感器供电后,等待 1-10ms,让内部电源稳定。
- 复位:通过硬件复位引脚或软件复位寄存器,让传感器回到已知状态。
- 读取芯片 ID:验证 I2C/SPI 通信是否正常。如果读不到预期的 ID,说明没连上或者地址错了。
- 配置 PLL 和时钟:设置内部锁相环,生成传感器需要的像素时钟。
- 设置帧率和分辨率:配置帧长度、行长度、窗口大小。
- 配置其他功能:镜像翻转、增益、曝光模式等。
- 启动流模式:写入 0x01 到模式寄存器,传感器开始输出图像数据。
3.4 PLL 配置:给传感器一颗“稳定的心脏”
PLL,全称锁相环。它的作用是把外部输入的低频时钟,倍频成传感器内部需要的高频像素时钟。你可以把它理解成一个“时钟放大器”。
配置 PLL 时,你通常需要设置这几个参数:
- 输入时钟频率:外部晶振或主控提供的时钟,常见 24MHz、27MHz。
- 倍频系数 (Multiplier):乘以输入时钟,得到 VCO 频率。
- 分频系数 (Divider):对 VCO 频率分频,得到最终的像素时钟。
- 电荷泵电流:影响 PLL 锁定速度和稳定性。
公式很简单:
PCLK = (输入时钟 × 倍频系数) / 分频系数
举个例子,输入时钟 24MHz,倍频系数 40,分频系数 4,那么 PCLK = (24 × 40) / 4 = 240MHz。
3.5 帧率设置:算清楚这笔账
帧率,就是每秒输出多少帧图像。它跟像素时钟、帧长度、行长度直接相关。公式如下:
帧率 (FPS) = PCLK / (帧长度 × 行长度)
这里要注意,帧长度和行长度不是分辨率。帧长度是一帧图像包含的总行数(包括消隐区),行长度是一行图像包含的总像素数(包括消隐区)。
举个例子,假设 PCLK = 240MHz,帧长度 = 1125 行,行长度 = 2200 像素,那么:
帧率 = 240,000,000 / (1125 × 2200) ≈ 97 FPS
如果你想降低帧率,可以增大帧长度或行长度。反过来,想提高帧率,就减小它们。但要注意,帧长度不能小于有效行数,行长度不能小于有效像素数。
| 参数 | 说明 | 典型值 |
|---|---|---|
| PCLK | 像素时钟,由 PLL 生成 | 240 MHz |
| 帧长度 (VTS) | 一帧总行数 = 有效行 + 消隐行 | 1125 |
| 行长度 (HTS) | 一行总像素数 = 有效像素 + 消隐像素 | 2200 |
| 有效行数 | 实际图像的行数(分辨率高度) | 1080 |
| 有效像素 | 实际图像的列数(分辨率宽度) | 1920 |
3.6 分辨率设置与窗口裁剪
分辨率设置,说白了就是告诉传感器,你要输出多大一张图。但传感器内部的实际像素阵列通常比输出分辨率大。比如一个 500 万像素的传感器,内部有 2592×1944 个像素,但你只需要输出 1920×1080。
这时候就要用到窗口裁剪。你通过设置起始坐标和结束坐标,从像素阵列中“切”出一块区域来输出。
// 示例:设置 1920x1080 窗口,从中心裁剪
// 假设传感器总像素阵列 2592x1944
// 起始坐标 = (总宽度 - 输出宽度) / 2
// 起始 X = (2592 - 1920) / 2 = 336
// 起始 Y = (1944 - 1080) / 2 = 432
write_reg(REG_X_START, 336);
write_reg(REG_Y_START, 432);
write_reg(REG_X_END, 336 + 1920 - 1); // 2255
write_reg(REG_Y_END, 432 + 1080 - 1); // 1511
你想想看,如果你不裁剪,直接输出全分辨率,帧率会掉得很厉害。因为数据量大了,PCLK 不变的情况下,帧率自然就低了。所以,窗口裁剪是平衡分辨率和帧率的关键手段。
3.7 镜像翻转:别让画面是反的
镜像翻转,听起来简单,但实际项目中经常遇到。你装好镜头,发现画面是左右颠倒的,或者上下颠倒的。这时候不需要动硬件,改一个寄存器位就行。
通常传感器会提供两个控制位:
- 水平镜像 (Horizontal Mirror):左右翻转。
- 垂直翻转 (Vertical Flip):上下翻转。
组合起来有四种状态:
| 水平镜像 | 垂直翻转 | 效果 |
|---|---|---|
| 0 | 0 | 正常画面 |
| 1 | 0 | 左右镜像 |
| 0 | 1 | 上下翻转 |
| 1 | 1 | 180度旋转 |
3.8 知识结构总览
下面这张图,把本章的核心知识点串起来了。你可以把它当作一个检查清单,配置传感器时按这个流程走,基本不会漏。
好了,这一章的内容就这些。从通信协议到寄存器映射,从初始化序列到 PLL、帧率、分辨率、窗口裁剪、镜像翻转,每一步都是实际项目中绕不开的。你把这些吃透了,后面调图像质量的时候,心里就有底了。