1、软件、硬件
接着上一篇博客的工程基础
STM32F4_V1.25.0固件库应用于STM32F407ZGT6(一)
2、硬件连接
唠叨几句,购买了一个在卡片电脑上专用的摄像头模块,排针和我的板子camara卡槽匹配不上,只能飞线。长下面这个样子,CAMERA是卡槽接口,背面有个CAMERB是插针。
商家只管卖,连个原理图都没有,板子上引脚名称也没有,询问商家的回复是这摄像头为某某板子专用,他们也没有原理图。网上查了那些板子camera接口的原理图,才知道摄像头上的三角符号一端要板子上三角符号对应。好吧,原理图上也没有标注三角符号那一端是地线还是排线顶端。
好不容易找到在Android系统上使用过该模块的人,给出的说明书如下。
2.1、CAMERA接口图
下图EINT19其实就是PWDN引脚
对应模块来看,CAMERA接口应该是下面这样的,和上图左右对应匹配
2.2、CAMERB接口图
看模块背面的CAMERB接口,实际上插针对应左右两边和上图相反。只要保证CAMERA接口认识正确,用万用表探CAMERA和CAMERB即可找到引脚。
为啥这么设计呢?带着疑问,我去看板子的硬件,也是这样的,板子上卡槽是按原理图的左右顺序预留的,那么插针只有左右顺序对调才能匹配板子。或者板子做预留的卡槽和摄像头插针左右相反,总之这是需要留意的点。
用公对公的杜邦线一个一个和板子引脚连接,这里先测试IIC通路,把5V、3.3V、GND、RST、PWDN、I2C_SCL、I2C_SDA对应连接到板子上。
板子camera接口原理图如下:
3、代码实现
OV3640的IIC设备地址是0x78,写数据时是0x78,读数据时是0x79。STM32的i2c驱动对于设备地址是直接传入0x78,根据读写再与或读写位(无需左移)。
从标准IIC波形上看摄像头的数据,设备地址的高7位是0X3C,那种需要左移或上读写位的驱动,传入的是0X3C,左移或上读写位即为0x78(写)、0x79(读)。
3.1、i2c.h文件
#ifndef __BSP_I2C_H__
#define __BSP_I2C_H__
/****************************** Includes *****************************/
#include "stm32f4xx.h"
#include "stm32f4xx_hal.h"
#include "bsp_debug_usart.h"
/****************************** Defines *******************************/
#define OV3640_DEVICE_ADDRESS 0x78 //0x78 & 1 = 0x78 写
//0x78 | 1 = 0x79 读
/*引脚定义*/
#define SENSORS_I2C_SCL_GPIO_PORT GPIOB
#define SENSORS_I2C_SCL_GPIO_CLK_ENABLE() __GPIOB_CLK_ENABLE()
#define SENSORS_I2C_SCL_GPIO_PIN GPIO_PIN_8
#define SENSORS_I2C_SDA_GPIO_PORT GPIOB
#define SENSORS_I2C_SDA_GPIO_CLK_ENABLE() __GPIOB_CLK_ENABLE()
#define SENSORS_I2C_SDA_GPIO_PIN GPIO_PIN_9
#define SENSORS_I2C_AF GPIO_AF4_I2C1
#define SENSORS_I2C I2C1
#define SENSORS_I2C_RCC_CLK_ENABLE() __HAL_RCC_I2C1_CLK_ENABLE()
#define SENSORS_I2C_FORCE_RESET() __HAL_RCC_I2C1_FORCE_RESET()
#define SENSORS_I2C_RELEASE_RESET() __HAL_RCC_I2C1_RELEASE_RESET()
void I2CMaster_Init(void);
uint8_t OV3640_WriteReg(uint16_t Addr, uint8_t Data);
uint8_t OV3640_ReadReg(uint16_t Addr);
#endif // __BSP_I2C_H__
3.2、i2c.c文件
OV3640的寄存器地址是16位,所以直接调用 HAL_I2C_Mem_Write、HAL_I2C_Mem_Read函数操作寄存器,第4个参数传入I2C_MEMADD_SIZE_16BIT即可。
#include "bsp_i2c.h"
#include <stdio.h>
I2C_HandleTypeDef I2C_Handle;
/******************************* Function ************************************/
void I2CMaster_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
/* 使能I2Cx时钟 */
SENSORS_I2C_RCC_CLK_ENABLE();
/* 使能I2C GPIO 时钟 */
SENSORS_I2C_SCL_GPIO_CLK_ENABLE();
SENSORS_I2C_SDA_GPIO_CLK_ENABLE();
/* 配置I2Cx引脚: SCL ----------------------------------------*/
GPIO_InitStructure.Pin = SENSORS_I2C_SCL_GPIO_PIN;
GPIO_InitStructure.Mode = GPIO_MODE_AF_OD;
GPIO_InitStructure.Speed = GPIO_SPEED_HIGH;
GPIO_InitStructure.Pull = GPIO_NOPULL;
GPIO_InitStructure.Alternate = SENSORS_I2C_AF;
HAL_GPIO_Init(SENSORS_I2C_SCL_GPIO_PORT, &GPIO_InitStructure);
/* 配置I2Cx引脚: SDA ----------------------------------------*/
GPIO_InitStructure.Pin = SENSORS_I2C_SDA_GPIO_PIN;
HAL_GPIO_Init(SENSORS_I2C_SDA_GPIO_PORT, &GPIO_InitStructure);
if(HAL_I2C_GetState(&I2C_Handle) == HAL_I2C_STATE_RESET)
{
/* 强制复位I2C外设时钟 */
SENSORS_I2C_FORCE_RESET();
/* 释放I2C外设时钟复位 */
SENSORS_I2C_RELEASE_RESET();
/* I2C 配置 */
I2C_Handle.Instance = SENSORS_I2C;
I2C_Handle.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
I2C_Handle.Init.ClockSpeed = 40000; //40K,低速率
I2C_Handle.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
I2C_Handle.Init.DutyCycle = I2C_DUTYCYCLE_2;
I2C_Handle.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
I2C_Handle.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
I2C_Handle.Init.OwnAddress1 = 0xFE ;
I2C_Handle.Init.OwnAddress2 = 0;
/* 初始化I2C */
HAL_I2C_Init(&I2C_Handle);
}
}
/**
* @brief Manages error callback by re-initializing I2C.
* @param Addr: I2C Address
* @retval None
*/
static void I2Cx_Error(void)
{
/* 恢复I2C寄存器为默认值 */
HAL_I2C_DeInit(&I2C_Handle);
/* 重新初始化I2C外设 */
I2CMaster_Init();
}
/**
* @brief 写一字节数据到OV3640寄存器
* @param Addr: OV3640 的寄存器地址
* @param Data: 要写入的数据
* @retval 返回0表示写入正常,0xFF表示错误
*/
uint8_t OV3640_WriteReg(uint16_t Addr, uint8_t Data)
{
HAL_StatusTypeDef status = HAL_OK;
status = HAL_I2C_Mem_Write(&I2C_Handle, OV3640_DEVICE_ADDRESS, (uint16_t)Addr, I2C_MEMADD_SIZE_16BIT, (uint8_t*)&Data, 1, 1000);
/* Check the communication status */
if(status != HAL_OK)
{
/* Re-Initiaize the I2C Bus */
printf("ERR write status[%d]\r\n",status);
I2Cx_Error();
}
return status;
}
/**
* @brief 从OV3640寄存器中读取一个字节的数据
* @param Addr: 寄存器地址ַ
* @retval 返回读取得的数据
*/
uint8_t OV3640_ReadReg(uint16_t Addr)
{
uint8_t Data = 0;
HAL_StatusTypeDef status = HAL_OK;
status = HAL_I2C_Mem_Read(&I2C_Handle, OV3640_DEVICE_ADDRESS, (uint16_t)Addr, I2C_MEMADD_SIZE_16BIT, (uint8_t*)&Data, 1, 1000);
/* Check the communication status */
if(status != HAL_OK)
{
/* I2C error occurred */
printf("ERR read status[%d]\r\n",status);
I2Cx_Error();
}
/* return the read data */
return Data;
}
3.3、ov3640.h文件
定义了摄像头所需的所有引脚
#include "stm32f4xx.h"
#include "stm32f4xx_hal.h"
// VSYNC
#define DCMI_VSYNC_GPIO_PORT GPIOB
#define DCMI_VSYNC_GPIO_CLK_ENABLE() __HAL_RCC_GPIOB_CLK_ENABLE()
#define DCMI_VSYNC_GPIO_PIN GPIO_PIN_7
#define DCMI_VSYNC_AF GPIO_AF13_DCMI
// HSYNC
#define DCMI_HSYNC_GPIO_PORT GPIOA
#define DCMI_HSYNC_GPIO_CLK_ENABLE() __HAL_RCC_GPIOA_CLK_ENABLE()
#define DCMI_HSYNC_GPIO_PIN GPIO_PIN_4
#define DCMI_HSYNC_AF GPIO_AF13_DCMI
// PIXCLK
#define DCMI_PIXCLK_GPIO_PORT GPIOA
#define DCMI_PIXCLK_GPIO_CLK_ENABLE() __HAL_RCC_GPIOA_CLK_ENABLE()
#define DCMI_PIXCLK_GPIO_PIN GPIO_PIN_6
#define DCMI_PIXCLK_AF GPIO_AF13_DCMI
// PWDN
#define DCMI_PWDN_GPIO_PORT GPIOC
#define DCMI_PWDN_GPIO_CLK_ENABLE() __HAL_RCC_GPIOC_CLK_ENABLE()
#define DCMI_PWDN_GPIO_PIN GPIO_PIN_0
// RST
#define DCMI_RST_GPIO_PORT GPIOF
#define DCMI_RST_GPIO_CLK_ENABLE() __HAL_RCC_GPIOF_CLK_ENABLE()
#define DCMI_RST_GPIO_PIN GPIO_PIN_10
// D0-D7
#define DCMI_D0_GPIO_PORT GPIOC
#define DCMI_D0_GPIO_CLK_ENABLE() __HAL_RCC_GPIOC_CLK_ENABLE()
#define DCMI_D0_GPIO_PIN GPIO_PIN_6
#define DCMI_D0_AF GPIO_AF13_DCMI
#define DCMI_D1_GPIO_PORT GPIOC
#define DCMI_D1_GPIO_CLK_ENABLE() __HAL_RCC_GPIOC_CLK_ENABLE()
#define DCMI_D1_GPIO_PIN GPIO_PIN_7
#define DCMI_D1_AF GPIO_AF13_DCMI
#define DCMI_D2_GPIO_PORT GPIOC
#define DCMI_D2_GPIO_CLK_ENABLE() __HAL_RCC_GPIOC_CLK_ENABLE()
#define DCMI_D2_GPIO_PIN GPIO_PIN_8
#define DCMI_D2_AF GPIO_AF13_DCMI
#define DCMI_D3_GPIO_PORT GPIOC
#define DCMI_D3_GPIO_CLK_ENABLE() __HAL_RCC_GPIOC_CLK_ENABLE()
#define DCMI_D3_GPIO_PIN GPIO_PIN_9
#define DCMI_D3_AF GPIO_AF13_DCMI
#define DCMI_D4_GPIO_PORT GPIOE
#define DCMI_D4_GPIO_CLK_ENABLE() __HAL_RCC_GPIOE_CLK_ENABLE()
#define DCMI_D4_GPIO_PIN GPIO_PIN_4
#define DCMI_D4_AF GPIO_AF13_DCMI
#define DCMI_D5_GPIO_PORT GPIOB
#define DCMI_D5_GPIO_CLK_ENABLE() __HAL_RCC_GPIOB_CLK_ENABLE()
#define DCMI_D5_GPIO_PIN GPIO_PIN_6
#define DCMI_D5_AF GPIO_AF13_DCMI
#define DCMI_D6_GPIO_PORT GPIOE
#define DCMI_D6_GPIO_CLK_ENABLE() __HAL_RCC_GPIOE_CLK_ENABLE()
#define DCMI_D6_GPIO_PIN GPIO_PIN_5
#define DCMI_D6_AF GPIO_AF13_DCMI
#define DCMI_D7_GPIO_PORT GPIOE
#define DCMI_D7_GPIO_CLK_ENABLE() __HAL_RCC_GPIOE_CLK_ENABLE()
#define DCMI_D7_GPIO_PIN GPIO_PIN_6
#define DCMI_D7_AF GPIO_AF13_DCMI
#define OV3640_PIDH 0x300A //产品ID高8位
#define OV3640_PIDL 0x300B //产品ID低8位
//产品ID结构体
typedef struct
{
uint8_t PIDH;
uint8_t PIDL;
}OV3640_IDTypeDef;
3.4、ov3640.c文件
初始化控制摄像头使用的GPIO
/**
* @brief 初始化控制摄像头使用的GPIO
* @param None
* @retval None
*/
void OV3640_HW_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
/***DCMI引脚配置***/
/* 使能DCMI时钟 */
DCMI_RST_GPIO_CLK_ENABLE();
DCMI_PWDN_GPIO_CLK_ENABLE();
DCMI_VSYNC_GPIO_CLK_ENABLE();
DCMI_HSYNC_GPIO_CLK_ENABLE();
DCMI_PIXCLK_GPIO_CLK_ENABLE();
DCMI_D0_GPIO_CLK_ENABLE();
DCMI_D1_GPIO_CLK_ENABLE();
DCMI_D2_GPIO_CLK_ENABLE();
DCMI_D3_GPIO_CLK_ENABLE();
DCMI_D4_GPIO_CLK_ENABLE();
DCMI_D5_GPIO_CLK_ENABLE();
DCMI_D6_GPIO_CLK_ENABLE();
DCMI_D7_GPIO_CLK_ENABLE();
/*控制/同步信号线*/
GPIO_InitStructure.Pin = DCMI_VSYNC_GPIO_PIN;
GPIO_InitStructure.Mode = GPIO_MODE_AF_PP;
GPIO_InitStructure.Speed = GPIO_SPEED_HIGH;
GPIO_InitStructure.Pull = GPIO_PULLUP ;
GPIO_InitStructure.Alternate = DCMI_VSYNC_AF;
HAL_GPIO_Init(DCMI_VSYNC_GPIO_PORT, &GPIO_InitStructure);
GPIO_InitStructure.Pin = DCMI_HSYNC_GPIO_PIN;
GPIO_InitStructure.Alternate = DCMI_HSYNC_AF;
HAL_GPIO_Init(DCMI_HSYNC_GPIO_PORT, &GPIO_InitStructure);
GPIO_InitStructure.Pin = DCMI_PIXCLK_GPIO_PIN;
GPIO_InitStructure.Alternate = DCMI_PIXCLK_AF;
HAL_GPIO_Init(DCMI_PIXCLK_GPIO_PORT, &GPIO_InitStructure);
/*数据信号*/
GPIO_InitStructure.Pin = DCMI_D0_GPIO_PIN;
GPIO_InitStructure.Alternate = DCMI_D0_AF;
HAL_GPIO_Init(DCMI_D0_GPIO_PORT, &GPIO_InitStructure);
GPIO_InitStructure.Pin = DCMI_D1_GPIO_PIN;
GPIO_InitStructure.Alternate = DCMI_D1_AF;
HAL_GPIO_Init(DCMI_D1_GPIO_PORT, &GPIO_InitStructure);
GPIO_InitStructure.Pin = DCMI_D2_GPIO_PIN;
GPIO_InitStructure.Alternate = DCMI_D2_AF;
HAL_GPIO_Init(DCMI_D2_GPIO_PORT, &GPIO_InitStructure);
GPIO_InitStructure.Pin = DCMI_D3_GPIO_PIN;
GPIO_InitStructure.Alternate = DCMI_D3_AF;
HAL_GPIO_Init(DCMI_D3_GPIO_PORT, &GPIO_InitStructure);
GPIO_InitStructure.Pin = DCMI_D4_GPIO_PIN;
GPIO_InitStructure.Alternate = DCMI_D4_AF;
HAL_GPIO_Init(DCMI_D4_GPIO_PORT, &GPIO_InitStructure);
GPIO_InitStructure.Pin = DCMI_D5_GPIO_PIN;
GPIO_InitStructure.Alternate = DCMI_D5_AF;
HAL_GPIO_Init(DCMI_D5_GPIO_PORT, &GPIO_InitStructure);
GPIO_InitStructure.Pin = DCMI_D6_GPIO_PIN;
GPIO_InitStructure.Alternate = DCMI_D6_AF;
HAL_GPIO_Init(DCMI_D6_GPIO_PORT, &GPIO_InitStructure);
GPIO_InitStructure.Pin = DCMI_D7_GPIO_PIN;
GPIO_InitStructure.Alternate = DCMI_D7_AF;
HAL_GPIO_Init(DCMI_D7_GPIO_PORT, &GPIO_InitStructure);
GPIO_InitStructure.Speed = GPIO_SPEED_HIGH;
GPIO_InitStructure.Pull = GPIO_PULLUP ;
GPIO_InitStructure.Pin = DCMI_PWDN_GPIO_PIN;
GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
HAL_GPIO_Init(DCMI_PWDN_GPIO_PORT, &GPIO_InitStructure);
GPIO_InitStructure.Pin = DCMI_RST_GPIO_PIN;
GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
HAL_GPIO_Init(DCMI_RST_GPIO_PORT, &GPIO_InitStructure);
/*PWDN引脚,高电平关闭电源,低电平供电,GPIO_PIN_RESET是0*/
HAL_GPIO_WritePin(DCMI_PWDN_GPIO_PORT,DCMI_PWDN_GPIO_PIN,GPIO_PIN_RESET);
}
/**
* @brief 读取摄像头的ID
* @param OV3640ID:存储ID的结构体
* @retval None
*/
void OV3640_ReadID(OV3640_IDTypeDef *OV3640ID)
{
/*读取寄存芯片ID*/
OV3640ID->PIDH = OV3640_ReadReg(OV3640_PIDH);
OV3640ID->PIDL = OV3640_ReadReg(OV3640_PIDL);
}
3.5、main函数
int main(void)
{
HAL_Init();
/* Configure the system clock to 168 MHz */
SystemClock_Config();
DEBUG_USART_Config();
HAL_Delay(1000);
//camera ID 结构体初始化
OV3640_IDTypeDef OV3640_Camera_ID;
//I2C初始化
I2CMaster_Init();
//摄像头硬件初始化
OV3640_HW_Init();
HAL_Delay(100);
//读取摄像头ID
OV3640_ReadID(&OV3640_Camera_ID);
printf("ID = %x%x\r\n",OV3640_Camera_ID.PIDH ,OV3640_Camera_ID.PIDL);
if(OV3640_Camera_ID.PIDH == 0x36)
{
printf("Right\r\n");
while(1);
}
else
{
printf("ERR\r\n");
while(1);
}
}
4、调试通路
4.1、抓取IIC波形失败
用逻辑分析仪抓取波形,发现在设备地址0x78后,收到摄像头回复的NACK,随后跟着Stop。怀疑是IIC驱动不对,因为有的OV摄像头默认回复的是NACK,而STM32F4的V1.25.0驱动在收到NACK后会报错退出。OV家的摄像头,使用SCCB协议,类似IIC,但和IIC还是有点区别,对第9位不关心。
查阅资料,这篇博客给出了解释,OV3640会回复ACK,所以不是驱动的问题。
Wince6.0 上增加ov3640摄像头
4.2、查找原因,解决问题
可能压根摄像头就没有工作,因为程序起来后,摄像头一点温度都没有,查阅资料发现别的模块外部都有一个24M的晶振,而我购买这一块摄像头没有外部时钟,草率了以为有来着,原来晶振是长那个样子。
好吧!查阅资料,寻求帮助,了解到摄像头的时钟可以由外部晶振提供,或者由主控芯片提供,这里使用PA8引脚(MCO1)直接将板子外部晶振的时钟输出给摄像头。
我手上的板子外部晶振是25M,刚好符合摄像头工作时钟(6-27M)
void ov3640_input_clk()
{
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.Pin = GPIO_PIN_8;
GPIO_InitStructure.Speed = GPIO_SPEED_HIGH;
GPIO_InitStructure.Mode = GPIO_MODE_AF_PP;
HAL_GPIO_Init(GPIOA, &GPIO_InitStructure);
//时钟源输出到MCO1引脚(PA8),选择HSE时钟作为MCO1源,1分频,那么输出25M时钟给摄像头
HAL_RCC_MCOConfig(RCC_MCO1, RCC_MCO1SOURCE_HSE,RCC_MCODIV_1);
}
用示波器探测PA8引脚,可以观察到25M稳定时钟输出,将PA8引脚接到摄像头的CAMCLK脚
提供时钟后,摄像头发烫了,但是IIC依然没有数据,害怕因为短路摄像头才发烫。
在同事帮助下,看芯片手册文档,上电要满足一定的时序,摄像头才会正常工作。对于硬件小白的我来说,以为芯片只要供电就能工作,没想到还有一定的时序要求。
(哎呦喂,一定要仔细看手册啊!!!!!!!)
如果1.8V用于I/O电源,则首选使用内部DVDD。 如果2.8V用于I/O电源,由于内部DVDD调节器的高压下降,存在潜在的热问题。 因此,对于2.8V的I/O电源,OmniVision建议使用外部DVDD源。
用万用表探测摄像头模块的电压,供电是5V,电源转换后DOVDD是2.79V,DVDD是1.53V,AVDD是2.79V,这也分不清该用啥电源啊?
随便试试哪种时序吧!
这个上电后,需要延时5ms再将PWDN脚拉低,加了延时也没有效果,依然IIC通信没有成功。
看到一篇博客,说OV5640和OV3640差不多,所以参照OV5640的初始化流程,发现OV5640的上电时序还对复位脚有操作。
查看OV5640的datasheet,无论外部电源还是内部电源,时序都差不多,在PWDN拉低后还有拉高RESET脚才能操作IIC,且保证时钟稳定输入。
修改代码
void OV3640_HW_Init(void)
{
/***省略代码部分***/
/*POWER 低*/
HAL_GPIO_WritePin(DCMI_PWDN_GPIO_PORT,DCMI_PWDN_GPIO_PIN,(GPIO_PinState)0);
/*RESET 高*/
HAL_GPIO_WritePin(DCMI_RST_GPIO_PORT,DCMI_RST_GPIO_PIN,(GPIO_PinState)1);
}
再次抓波形,成功读取到OV3640的产品ID,我的天哪!终于可以了,忙活了几天。
该产品ID和0V5640这些不一样,并不是型号ID,而是0X364C
5、总结
5.1、摄像头要么使用外部晶振时钟,要么使用主控板提供时钟,必须有时钟才能工作。
5.2、一定要仔细看手册,这里写了要把复位脚拉高后才能操作IIC,只看图的我忽略了那句话。
5.3、遇到问题,冷静分析。
别急,别急,别急
查硬件、查软件、查文档