3. 摄像头模组上电与I2C初始化实战
摄像头模组要出图,第一步不是配置DCMI或CSI,而是让模组“活过来”。绝大多数CMOS摄像头(如OV5640、OV2640、GC2145等)都遵循同一套上电时序:先给模拟电源AVDD,再给数字核心电源DVDD,最后给I/O电源DOVDD,同时提供外部时钟XCLK,等待内部PLL锁定后,主机通过I2C(SCCB兼容)访问寄存器。本节以STM32N6搭配一颗典型DVP/MIPI摄像头模组为例,完成从CubeMX配置I2C到读出模组ID、释放复位、写入基础寄存器的完整闭环,最终让DCMI/CSI端能抓到第一帧原始数据。
一、上电时序与硬件连接确认
在写任何代码之前,先确认模组的电源域和引脚。典型模组有5根关键控制线:PWDN(掉电控制,低有效)、RESET(复位,低有效)、XCLK(主时钟输入)、SDA/SCL(I2C)。STM32N6的I2C外设建议使用I2C1或I2C2,映射到带外部上拉的引脚上。模组侧SDA/SCL通常需要4.7kΩ上拉到DOVDD(1.8V或2.8V,视模组而定),STM32N6的I/O电压必须与模组DOVDD一致,否则会出现电平不匹配导致ACK丢失。
上电顺序建议:拉高PWDN(使能模组)→ 依次上电AVDD、DVDD、DOVDD → 输出XCLK(典型24MHz)→ 延时至少1ms等待内部稳压和PLL稳定 → 释放RESET(拉高)→ 再延时20ms以上,之后才允许I2C通信。这个顺序如果颠倒,常见现象是I2C读ID返回0xFF或NACK。
二、CubeMX中配置I2C外设
打开STM32CubeMX,在Connectivity中选择I2C1,模式设为I2C。参数配置如下:Speed Mode选择Standard Mode(100kHz)或Fast Mode(400kHz),摄像头SCCB通常兼容100kHz,首次调试建议先用100kHz提高容错;Clock No Stretch Mode保持默认;Own Address1可填0x00(主机模式不使用)。GPIO引脚会自动分配,确认SDA/SCL的上下拉配置为“No pull-up and no pull-down”,因为外部已有上拉电阻,避免双重上拉影响上升沿。
时钟树中确认I2C1的时钟源频率,例如APB1为120MHz,则I2C的时钟分频寄存器需要根据目标速率计算。CubeMX会自动计算并填入I2C_TIMINGR寄存器值,无需手动计算,但建议在生成的代码中核对一次。另外,把控制PWDN和RESET的两个GPIO配置为推挽输出,初始电平设为低(PWDN低=掉电,RESET低=复位保持),这样上电后由软件控制释放顺序。
三、编写摄像头寄存器读写驱动
摄像头寄存器是8位地址+8位数据,部分模组(如OV5640)支持16位地址。以8位地址为例,写寄存器的流程是:发送从机地址(写方向)→ 发送寄存器地址 → 发送数据 → 停止。读寄存器则是:发送从机地址(写方向)→ 发送寄存器地址 → 重复起始 → 发送从机地址(读方向)→ 读取数据 → 停止。STM32 HAL库提供了HAL_I2C_Mem_Write和HAL_I2C_Mem_Read,可以直接完成上述时序。
#define CAM_I2C_ADDR (0x3C << 1) // 7位地址0x3C,左移后为HAL使用的8位格式
uint8_t CAM_WriteReg(uint16_t reg, uint8_t val)
{
return HAL_I2C_Mem_Write(&hi2c1, CAM_I2C_ADDR, reg,
I2C_MEMADD_SIZE_8BIT, &val, 1, 100);
}
uint8_t CAM_ReadReg(uint16_t reg, uint8_t *val)
{
return HAL_I2C_Mem_Read(&hi2c1, CAM_I2C_ADDR, reg,
I2C_MEMADD_SIZE_8BIT, val, 1, 100);
}
注意从机地址的写法:HAL库要求传入的是8位格式,即7位地址左移一位。如果模组手册给出的是0x3C(7位),则代码中写0x78;如果手册直接给出0x78(8位写地址),则直接使用。这个细节是初学者最容易出错的地方,表现为HAL_I2C_Mem_Read返回HAL_ERROR或HAL_TIMEOUT。
四、读取模组ID并判断通信是否成功
不同摄像头的ID寄存器地址不同。以OV5640为例,ID寄存器为0x300A(高字节)和0x300B(低字节),预期值分别为0x56和0x40。以GC2145为例,ID寄存器为0xF0和0xF1,预期值分别为0x21和0x45。读取ID是验证I2C链路是否打通的最直接手段。
uint8_t id_h = 0, id_l = 0;
CAM_ReadReg(0x300A, &id_h);
CAM_ReadReg(0x300B, &id_l);
printf("Camera ID: 0x%02X%02X\r\n", id_h, id_l);
// 预期输出:Camera ID: 0x5640
如果读回0x0000或0xFFFF,说明I2C没有收到有效应答。排查顺序:先用逻辑分析仪或示波器看SDA/SCL是否有波形;再确认从机地址是否正确;然后检查上拉电阻和电平匹配;最后确认模组是否已经完成上电和复位释放。如果读回的值与预期不符但非全0/全1,可能是地址位宽配置错误(8位 vs 16位),需要调整I2C_MEMADD_SIZE参数。
五、释放复位与基础寄存器配置
ID读取成功后,先执行软复位:向复位寄存器写入0x80(以OV5640为例,寄存器0x3008),延时至少5ms,再写入0x00退出复位。随后配置基础寄存器组,至少包括:时钟分频、PLL倍频、输出格式(RGB565/YUV422/RAW)、分辨率、帧率。以下是一组示例配置(参数为教学示例,实际应以模组手册为准):
CAM_WriteReg(0x3008, 0x80); // 软复位
HAL_Delay(10);
CAM_WriteReg(0x3008, 0x00); // 退出复位
CAM_WriteReg(0x3035, 0x11); // PLL控制
CAM_WriteReg(0x3036, 0x40); // PLL倍频
CAM_WriteReg(0x4300, 0x6F); // 输出格式:RGB565
CAM_WriteReg(0x3808, 0x02); // 水平高字节
CAM_WriteReg(0x3809, 0x80); // 水平低字节 → 640
CAM_WriteReg(0x380A, 0x01); // 垂直高字节
CAM_WriteReg(0x380B, 0xE0); // 垂直低字节 → 480
配置完成后,建议回读关键寄存器确认写入生效。例如回读0x3808和0x3809,应得到0x02和0x80。如果回读值与写入值不一致,说明I2C写操作没有真正到达模组,需要检查时序或电源稳定性。
六、点亮第一帧原始数据
寄存器配置完成后,模组开始输出像素数据。此时在STM32N6侧启动DCMI或CSI接收,配置为与模组输出格式匹配的模式(如RGB565、连续模式、硬件同步),并开启DMA将数据搬运到帧缓冲区。判断第一帧是否成功的依据:DMA传输完成中断触发,帧缓冲区中不是全0或全1,且数据具有一定的变化规律(例如灰度渐变或彩色条纹)。如果缓冲区全为0,检查DCMI的极性配置(HSYNC/VSYNC/PIXCLK的边沿);如果数据有规律但颜色异常,检查RGB565的字节序和DCMI的数据对齐方式。
到这一步,摄像头模组的上电、I2C初始化、ID识别、复位释放和基础配置已经形成完整闭环。后续的图像质量调优、自动曝光、白平衡等,都建立在这一帧原始数据能够稳定接收的基础之上。