4. 驱动初始化流程:上电时序控制、PLL配置、分辨率切换、Stream on/off流程实战
驱动初始化,说白了就是让 sensor 从「沉睡」到「干活」的过程。很多刚入行的朋友觉得这步简单,不就是写几个寄存器嘛?其实不然。我见过太多因为上电时序不对导致 sensor 烧坏的案例,也遇到过 PLL 配置差一个 bit 画面全是条纹的尴尬。
这一章,我们就把整个初始化流程拆开揉碎,从硬件时序到软件配置,一步步走通。
4.1 上电时序控制:sensor 的「生命线」
每个 sensor 都有自己严格的上电时序要求。你想想看,sensor 内部有模拟电路、数字电路、像素阵列,它们需要按照特定顺序「醒来」。搞反了或者时间不够,轻则 sensor 不工作,重则直接烧毁。
典型的 sensor 上电时序包含以下阶段:
| 阶段 | 信号 | 典型时序要求 | 说明 |
|---|---|---|---|
| 1 | AVDD (模拟电源) | 先于 DVDD 2-10ms | 模拟电路先稳定 |
| 2 | DVDD (数字电源) | AVDD 稳定后 | 数字逻辑上电 |
| 3 | IOVDD (IO 电源) | 与 DVDD 同时或稍后 | 通信接口供电 |
| 4 | MCLK (主时钟) | 电源稳定后 1-5ms | 提供工作时钟 |
| 5 | RESET (复位) | 时钟稳定后 1ms | 释放复位信号 |
嗯,这里要注意:不同 sensor 的时序参数差异很大。有的 sensor 要求 AVDD 和 DVDD 之间延迟 10ms,有的只需要 1ms。一定要以 datasheet 为准。
4.2 PLL 配置:让 sensor 跑在正确的节奏上
PLL 配置是初始化中最容易出错的环节。我个人的习惯是:先算后配,配完再验。
PLL 的核心公式其实很简单:
VCO = MCLK × PLL_M / PLL_N
PIX_CLK = VCO / PLL_P / PLL_DIV
但实际配置时,有几个坑需要避开:
- VCO 频率范围:每个 sensor 的 VCO 都有允许范围,比如 400MHz-800MHz。超出范围,PLL 无法锁定。
- PLL_M 和 PLL_N 的互质关系:有些 sensor 要求这两个值互质,否则会产生 jitter。
- PLL_P 的分频选择:通常有 1、2、4、8 等选项,需要根据目标像素时钟选择。
4.3 分辨率切换:从 1080p 到 4K 的「变形」
分辨率切换,本质上是重新配置 sensor 的读出窗口和时序。我把它分为三步:
- 停止输出:先发 Stream off 命令,让 sensor 停止输出数据。
- 修改寄存器:配置新的分辨率参数,包括窗口起始、窗口大小、行长度、帧长度等。
- 重新启动:发 Stream on 命令,等待 sensor 输出新分辨率的图像。
这里有个容易忽略的点:切换分辨率时,PLL 是否需要重新配置?
我的经验是:如果目标分辨率的像素时钟与当前一致,可以只改窗口参数。但如果帧率变化较大,建议重新配置 PLL,确保像素时钟在合理范围内。
| 分辨率 | 窗口起始 (X, Y) | 窗口大小 (W, H) | 行长度 (HTS) | 帧长度 (VTS) |
|---|---|---|---|---|
| 1920x1080 | (0, 0) | (1920, 1080) | 2200 | 1125 |
| 3840x2160 | (0, 0) | (3840, 2160) | 4400 | 2250 |
| 640x480 | (16, 12) | (640, 480) | 800 | 525 |
4.4 Stream on/off 流程:sensor 的「开关」
Stream on/off 是 sensor 驱动中最常用的操作。看似简单,但顺序错了就会出问题。
标准的 Stream on 流程:
1. 检查 sensor 状态(是否已初始化)
2. 配置输出格式(RAW、YUV、JPEG 等)
3. 配置输出分辨率
4. 配置帧率(通过调整 VTS 或 PLL)
5. 写入 Stream on 寄存器(通常为 0x0100 = 0x01)
6. 等待 VSYNC 信号(确认 sensor 开始输出)
7. 读取第一帧数据(丢弃或校验)
标准的 Stream off 流程:
1. 停止 DMA 传输(防止数据丢失)
2. 写入 Stream off 寄存器(0x0100 = 0x00)
3. 等待 sensor 进入 standby 状态
4. 关闭时钟(可选,用于省电)
4.5 完整初始化流程图
下面这张图,是我在实际项目中总结的 sensor 初始化流程。你可以把它当作 checklist 来用。
4.6 实战代码片段
最后,分享一段我实际项目中用的初始化代码框架。你可以直接拿来改。
int sensor_init(sensor_t *sensor) {
// 1. 上电时序
sensor_power_on(sensor);
mdelay(10); // 等待电源稳定
// 2. 释放复位
gpio_set_value(sensor->reset_pin, 1);
mdelay(5);
// 3. 初始化 I2C
i2c_init(sensor->i2c_bus);
// 4. 读取 Sensor ID
uint16_t id = sensor_read_reg(sensor, 0x0000);
if (id != sensor->expected_id) {
printf("Error: Sensor ID mismatch\n");
return -1;
}
// 5. 配置 PLL
sensor_pll_config(sensor, sensor->mclk_hz, sensor->target_fps);
// 6. 配置分辨率
sensor_resolution_config(sensor, sensor->width, sensor->height);
// 7. Stream on
sensor_write_reg(sensor, 0x0100, 0x01);
mdelay(100); // 等待 sensor 稳定
// 8. 等待 VSYNC
int ret = wait_for_vsync(sensor, 1000);
if (ret < 0) {
printf("Error: No VSYNC detected\n");
return -1;
}
return 0;
}
好了,这一章的内容就到这里。初始化流程看似繁琐,但只要你按照流程一步步来,把每个环节的时序和参数都确认清楚,就不会出大问题。记住:sensor 驱动没有捷径,只有细心和耐心。