4、CPU侧控制逻辑:I2C/SPI驱动配置、寄存器读写、GPIO触发同步、电源管理序列
CPU 侧的控制逻辑,说白了就是嵌入式大脑怎么跟相机模组「对话」。我做了这么多年嵌入式视觉,发现很多新手把精力全放在图像算法上,结果板子一上电,传感器压根不工作——原因往往是 I2C 时序不对,或者电源序列搞反了。
这一章,我们就来拆解 CPU 侧最核心的四个控制动作:I2C/SPI 驱动配置、寄存器读写、GPIO 触发同步、电源管理序列。你想想看,相机模组本质上就是个复杂的模拟+数字混合器件,CPU 要让它干活,就得通过这几条「神经」去下指令。
4.1 I2C/SPI 驱动配置:选对总线,调好时序
相机传感器最常用的控制总线就两种:I2C 和 SPI。我个人的习惯是:帧率要求不高、引脚紧张的项目用 I2C;需要快速配置大量寄存器、或者传感器本身支持 SPI 时,优先用 SPI。
| 特性 | I2C | SPI |
|---|---|---|
| 引脚数 | 2 根(SCL, SDA) | 4 根(SCLK, MOSI, MISO, CS) |
| 速度 | 标准 100kHz~400kHz,快速 1MHz | 通常 10MHz~50MHz |
| 寻址方式 | 7 位/10 位地址,多设备共用 | 片选 CS 独立,一主多从 |
| 典型场景 | OV2640、MT9V034 等中低速传感器 | AR0234、IMX 系列高速传感器 |
4.1.1 I2C 驱动配置要点
配置 I2C 驱动时,我建议你关注这几个参数:
- 时钟频率:不要超过传感器 datasheet 标称最大值。有些传感器对时序抖动敏感,频率设太高反而容易丢数据。
- 从设备地址:注意 7 位地址和 8 位地址的区别。很多 Linux 驱动里用的是 7 位左移 1 位的格式,跟裸机驱动不一样。
- 传输模式:大部分传感器用标准模式(SMBus 兼容),但有些需要重复起始条件(Restart)才能连续读写。
// 以 STM32 HAL 库为例,配置 I2C 句柄
I2C_HandleTypeDef hi2c1;
hi2c1.Instance = I2C1;
hi2c1.Init.ClockSpeed = 400000; // 400kHz 快速模式
hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2;
hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
HAL_I2C_Init(&hi2c1);
4.1.2 SPI 驱动配置要点
SPI 配置比 I2C 稍微复杂一点,主要是极性和相位。我见过太多人 SPI 通信失败,就是因为 CPOL 和 CPHA 没配对。
- CPOL(时钟极性):空闲时时钟是高电平还是低电平。
- CPHA(时钟相位):数据是在第一个边沿采样,还是第二个边沿采样。
- 数据位宽:大部分传感器用 8 位,但也有 16 位或 32 位模式。
4.2 寄存器读写:传感器的「控制面板」
相机传感器内部有几十到几百个寄存器,控制着曝光时间、增益、帧率、窗口大小、输出格式等等。CPU 通过 I2C/SPI 读写这些寄存器,就像在操作一个远程控制面板。
我个人的习惯是:把寄存器操作封装成两层。底层是总线读写函数,上层是传感器专用 API。这样换传感器时,只需要改上层,底层不用动。
// 底层:I2C 寄存器读写(以 16 位地址为例)
int sensor_write_reg(uint16_t reg_addr, uint8_t value) {
uint8_t buf[3] = { (reg_addr >> 8) & 0xFF, reg_addr & 0xFF, value };
return HAL_I2C_Master_Transmit(&hi2c1, SENSOR_ADDR, buf, 3, 100);
}
int sensor_read_reg(uint16_t reg_addr, uint8_t *value) {
uint8_t addr_buf[2] = { (reg_addr >> 8) & 0xFF, reg_addr & 0xFF };
HAL_I2C_Master_Transmit(&hi2c1, SENSOR_ADDR, addr_buf, 2, 100);
return HAL_I2C_Master_Receive(&hi2c1, SENSOR_ADDR, value, 1, 100);
}
// 上层:设置曝光时间
void sensor_set_exposure(uint32_t exposure_lines) {
sensor_write_reg(0x3501, (exposure_lines >> 8) & 0xFF);
sensor_write_reg(0x3502, exposure_lines & 0xFF);
}
4.3 GPIO 触发同步:让多个传感器「齐步走」
在多目相机系统中,GPIO 触发同步是保证图像时间对齐的关键。我做过一个双目深度相机项目,两个传感器如果不同步,误差哪怕只有 1 毫秒,生成的深度图都会出现明显的边缘错位。
常用的同步方式有两种:
- 硬件触发(Hardware Trigger):CPU 输出一个 GPIO 脉冲,传感器检测到上升沿或下降沿后开始曝光。这种方式延迟最小,抖动通常在微秒级。
- 软件触发(Software Trigger):CPU 通过 I2C/SPI 写寄存器来触发曝光。这种方式简单,但延迟和抖动都比较大,不适合高精度同步。
我个人强烈建议:只要硬件支持,就用硬件触发。下面是一个典型的硬件触发时序:
// GPIO 触发脉冲生成(以 STM32 为例)
void trigger_sensor(void) {
HAL_GPIO_WritePin(TRIGGER_GPIO_Port, TRIGGER_Pin, GPIO_PIN_SET);
delay_us(10); // 脉冲宽度 10 微秒,具体值看传感器要求
HAL_GPIO_WritePin(TRIGGER_GPIO_Port, TRIGGER_Pin, GPIO_PIN_RESET);
}
4.4 电源管理序列:上电下电,顺序不能乱
相机传感器对电源序列非常敏感。上电时,如果核心电压(VDD)先于 I/O 电压(VDDIO)到达,或者模拟电压(AVDD)跟数字电压(DVDD)时序不对,轻则传感器不工作,重则直接烧毁。
我见过最惨的一次:一个同事把传感器上电顺序搞反了,结果芯片内部 ESD 保护二极管导通,大电流灌进去,传感器当场冒烟。嗯,从那以后,我每次画板子都会在电源序列上多花一倍时间。
典型的电源序列要求如下(以某款 500 万像素传感器为例):
| 步骤 | 动作 | 延时要求 | 说明 |
|---|---|---|---|
| 1 | VDDIO(1.8V)上电 | 0ms | I/O 电源先稳定 |
| 2 | AVDD(3.3V)上电 | ≥ 1ms | 模拟电源随后 |
| 3 | DVDD(1.2V)上电 | ≥ 1ms | 数字电源最后 |
| 4 | MCLK 提供时钟 | ≥ 5ms | 所有电源稳定后 |
| 5 | 复位释放(XSHUT 拉高) | ≥ 10ms | 时钟稳定后 |
| 6 | I2C/SPI 初始化 | ≥ 1ms | 复位释放后 |
4.5 知识体系总览
下面这张图把 CPU 侧控制逻辑的四个核心模块串起来了。你可以把它当作一个检查清单:做新项目时,对着这张图一项一项确认,基本不会漏东西。
好了,这一章的内容就是这些。从 I2C/SPI 驱动配置,到寄存器读写,再到 GPIO 触发同步和电源管理序列,每一步都是 CPU 跟相机传感器「沟通」的关键环节。你在实际项目中遇到问题时,不妨回头看看这张图,大概率能找到排查方向。