Introdução
Ov7670 é um módulo de câmera paralela de 8 bits com sensor CMOS capaz de gerar imagens na resolução VGA (640×480) a 30 fps (frames por segundo). Diferente das câmeras comuns de vídeo composto, este módulo oferece uma boa interface de comunicação, pois disponibiliza através de seus pinos, todos os sinais de sincronismo e os dados da imagem, o que o torna ideal para uso com projetos microcontrolados.
Neste artigo iremos aprender a configurar o módulo Ov7670 para trabalhar na resolução QQVGA (160×120), que permitirá capturar e armazenar uma imagem na memória RAM interna de um microcontrolador ARM Cortex-M3, que em seguida será transferida para o computador para visualização em formato bitmap.
Não iremos explorar muito os detalhes teóricos sobre o processamento digital de imagens (PDI), o objetivo maior é entender como funciona e como programar o módulo Ov7670.
O módulo de câmera Ov7670
Existem diferentes versões desse módulo, sendo a inicial fabricada pela OmniVision dispondo apenas do sensor CMOS e seu conector, conforme a figura 1, e versões mais recentes que inclui na própria placa do sensor, um buffer do tipo FIFO (First In, First Out) o AL422B, e um oscilador de 12MHz, figura 2, e outras de tamanho reduzido e com recursos de autofoco, figura 3.




Das diferentes versões, iremos utilizar neste primeiro artigo desta série, a versão da figura 1, que inclusive é a mais difícil de se trabalhar e, portanto, a mais indicada para o aprendizado. As versões das figuras 2 e 3, permite que a imagem seja armazenada no buffer AL422B de forma automática, e assim o microcontrolador só precisa ler os dados da saída do AL422B a qualquer momento, o que dá uma “folga” ao microcontrolador para executar outras tarefas, como por exemplo: armazenar as imagens lidas em um cartão de memória.
Principais características do Ov7670
- Alta sensibilidade em operação de baixa luminosidade
- Opera com baixa voltagem para aplicações embarcadas 3.3v
- Interface de comunicação padrão SCCB compatível com i2c
- Suporte aos formatos de saída RGB (GRB 4:2:2, RGB565/555/444), YUV (4:2:2) e YCbCr (4:2:2)
- Controle automático de ganho (AGC), de exposição (AEC) e balanço automático de branco (AWB)
- Controle de qualidade de imagem incluindo cor, saturação, gama, brilho e anti-florescente
- Correção de lente
- 30 fps – frames por segundo
Resolução do Ov7670
A resolução é medida em pixel por polegada (ppi), o pixel por sua vez é uma unidade que os computadores usam para representar cada minúsculo ponto de uma imagem do mundo real.
Algumas resoluções conhecidas que o Ov7670 suporta são:
- VGA (640 x 480 pixels)
- QVGA (320 x 240 pixels)
- CIF (352 x 240 pixels)
- QCIF (176 x 144 pixels)
- QQVGA(160 x 120 pixels)
- Manual (Possibilita customizar um tamanho diferente de resolução)
Formatos de saída suportados pelo Ov7670
- Monocromático
- RGB (GRB 4:2:2, RGB565/555/444)
- YUV (4:2:2)
- YCbCr (4:2:2)
Podemos entender o formato de saída como sendo, a forma como um pixel é representado, e também a forma como devemos armazená-lo em memória computacional, pois, ele define a quantidade e a ordem dos bits que são usados para representar cada intensidade de luz e cores de uma imagem.
O formato escolhido para nosso projeto foi o RGB565, esse formato é muito utilizado pela maioria dos dispositivos, tanto de captura (câmeras), como de visualização (displays) de imagens.
No RGB565 as cores são representadas utilizando apenas as três cores primárias, vermelho (R), verde (G) e azul (B), assim cada pixel de uma imagem é armazenado com essas três intensidades de cores.
Esse formato utiliza 5 bits para armazenar a cor vermelha, 6 bits para a cor verde e 5 bits para a cor azul, dando um total de 2 bytes para representar cada pixel conforme mostra a figura 4.

Como vamos utilizar a resolução QQVGA, que possui as dimensões 160×120, ou seja, cada frame (imagem) é composto de 160 linhas por 120 colunas, dando um total de 19200 pixels, aproximadamente 0.019 Megapixels. Deu pra perceber que nessa resolução estamos muito abaixo de 1 Megapixel?
Como o nosso formato de saída será o RGB565, temos que 1 pixel é armazenado em 2 bytes. Logo o tamanho de uma foto QQVGA em memória será de:
19200 ———— 38400 bytes
Logo, nosso microcontrolador deve ter um espaço de memória RAM livre, de aproximadamente 37,5KB para que isso seja possível. O microcontrolador escolhido para este projeto possui memória RAM suficiente para tal propósito.


Algumas observações importantes podem ser feitas aqui:
- O barramento de dados é de apenas 8 bits, e o formato escolhido RGB565 utiliza 16 bits, logo, será necessário fazer duas leituras para obter um pixel completo.
- Para a entrada de clock XCLK iremos usar a saída de clock do próprio microcontrolador, por ser mais fácil do que adquirir um oscilador para isso.
- É através da interface SCCB ou I2C que será feita a configuração da câmera, como: mudar resolução, formato de saída, ajustar brilho, contraste e etc.
- O pino PWDN possui um resistor interno de pull-down que garante o funcionamento normal do módulo, portanto não precisamos usar este pino.
- O reset através do pino RESET será usado para garantir que o módulo tenha uma inicialização correta, e após isso será usado também o reset via software.
Figura 7 – Esquema elétrico do projeto
Software
Neste primeiro artigo não iremos desenvolver nenhum software específico para a obtenção da imagem no PC, ao invés disso, vamos utilizar algumas ferramentas gratuitas onde você deverá baixar e instalar em seu computador. O uso dessas ferramentas será mostrado através de um vídeo na sessão de resultados.
Figura 8 – Arquitetura do sistema
A figura 8 mostra a arquitetura do sistema e o fluxo completo do processo de captura, onde através do software HTerm, iremos capturar uma foto do Ov7670 e armazená-lo em disco no formato RGB565 puro, ou seja, as 160 linhas e 120 colunas, em seguida, através do software RGB565ToBitmap iremos inserir um header do tipo bitmap que nos permitirá ver a imagem em algum visualizador do Windows, completando assim o sucesso do nosso projeto.
Baixe as ferramentas através dos links:
Firmware
Antes de começar a programar o módulo Ov7670, é necessário conhecer algumas etapas fundamentais para a captura da imagem, isso inclui conhecer sobre os sinais de sincronismo, HREF, PCLK, VSYNC e XCLK baseando-se no datasheet do módulo que pode ser baixado aqui.
Os sinais de sincronismo
Quando o Ov7670 é alimentado, o mesmo começa a produzir frames em seu barramento de dados, e ao mesmo tempo conduz seus sinais de sincronismo, que devem ser interpretados de forma a identificar o início e o fim de cada linha da imagem. Na figura 9 temos todos esses sinais agrupados para melhor entendimento, e juntamente com a explicação será mostrado partes do código fonte a seguir.
Figura 9 – Sinais de sincronismo do módulo
Quando o sinal de sincronismo vertical VSYNC vai a nível baixo, isso indica o início de uma imagem (frame), o microcontrolador deve agora, aguardar o sinal de sincronismo horizontal HREF ir a nível alto, o que indica o início de uma linha da imagem, e para capturar cada pixel da linha, deve-se aguardar o sinal de pixel clock PCLK ir a nível alto, e é nesse momento que deve ser feito a leitura do barramento de dados, armazenando os bytes em um buffer na RAM. O processamento total de um frame é dado quando o sinal de VSYNC vai a nível alto, e a quantidade de linhas varia de acordo com a resolução e o formato de saída escolhido. Para nosso projeto, devemos obter 120 linhas e para cada linha devemos ler 320 bytes, dois bytes por pixel devido o formato de saída escolhido rgb565. Conforme mostrado no final da figura 9, esses bytes podem ser armazenados em buffers diferentes, conforme o trecho de código abaixo.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
#define QQVGA_WIDTH 160 //Define a largura do frame em 160 pixels, resolução QQVGA #define QQVGA_HEIGHT 120 //Define a altura do frame em 120 pixels, resolução QQVGA uint8_t qqvgaframe1[QQVGA_HEIGHT * QQVGA_WIDTH];//Armazena o primeiro byte de cada pixel uint8_t qqvgaframe2[QQVGA_HEIGHT * QQVGA_WIDTH];//Armazena o segundo byte de cada pixel uint16_t i = 0; while ( ! PIN_VSYNC )// Enquanto o sinal de VSYNC estiver em nível baixo { while (PIN_HREF) // Aguarda o início de uma linha da imagem { //Prepara para ler o primeiro byte do pixel while ( ! PIN_PCLK); // Aguarda o sinal de PCLK ir a nível alto qqvgaframe1[i] = LPC_GPIO2->FIOPIN; //Lê o barramento de dados do Ov7670 while (PIN_PCLK); //Aguarda o sinal de PCLK ir a nível baixo //Prepara para ler o segundo byte do pixel while (!PIN_PCLK); // Aguarda o sinal de PCLK ir a nível alto (segunda vez) qqvgaframe2[i] = LPC_GPIO2->FIOPIN; //Lê o barramento de dados do Ov7670 while (PIN_PCLK); //Aguarda o sinal de PCLK ir a nível baixo i++;//Incrementa número de pixel lido } } |
O código acima executa a captura da imagem fazendo polling nos sinais de sincronismo, isto pode deixar o projeto um pouco inconsistente, devido o processador ficar “preso” nessa tarefa até que se conclua a captura. Nos próximos projetos iremos utilizar interrupção para monitorar esses sinais, deixando assim o programa mais livre de travamentos.
Configuração correta do oscilador e do fps
<<< POST EM ANDAMENTO >>>
Resultados obtidos
Conclusão
Referencias
Downloads
Cientista computacional e técnico em eletrônica com mais de 4 anos de experiência. Trabalhei 4 anos na área de reparação de produtos eletrônicos de consumo, e atualmente trabalho com P&D na área de sistemas embarcados desde 2015, projetando e desenvolvendo soluções eletrônicas completas utilizando microcontroladores e afins.







Oi, muito útil a informação.
Eu tenho uma curiosidade, é sobre o caso da figura 3, essa camera já tem o buffer incluso e os dados são armazenados automaticamente né?, mas como que se faz a leitura desses dados depois de terem sido armazenados?. Outra curiosidade é se a câmera está atualizando em todo momento os dados da imagem no buffer?. Ou há um jeito de só tirar uma foto (armazenar um frame para depois processar os dados da imagem)?.
Agradeço muito desde já pela informação.
Abraço
Olá, sim o buffer é preenchido automaticamente pelos sinais de sincronismo da câmera, então um microcontrolador deve habilitar a escrita dele e quando estiver cheio, desabilita a escrita e pode efetuar e leitura da imagem contida no buffer, fazer qualquer processamento (salvar em um sd-card etc) e em seguida habilitar para capturar outro frame.
A camêra sempre está produzindo frames em seu barramento de dados, conduzidos pelos seus sinais de sincronismo.
Oi,
Uma consulta, qual seria a configuração dos registros para usar a camera em resolução VGA, rgb565 e pclk 24 mhz.
Agradeço desde já pela informação.
Olá, assim que der eu termino este post e dou exemplos de como fazer isso de forma que funcione. Abs.
Parabéns pelo trabalho. Espero que você possa fazer a continuação pois realmente está bem explicado, desde a diferença entre as câmeras até como a sincronização deve ser executada. Gostaria de saber como os dados devem ser passados via serial para o Hterm? Seria simplesmente enviar os dois buffers em sequência ou é necessário acrescentar algum cabeçalho ou mudança nos dados para que ele entenda que é uma imagem?
Olá, nesse caso basta enviar na mesma sequência que foram lidos, ou seja:
UartSend(qqvgaframe1[i]);
UartSend(qqvgaframe2[i]);
Mas ao receber isso no PC, ele vai estar em RGB565 então é necessário converter para bitmap, jpeg ou png.
Parabéns pelo artigo. Está muito bem explicado. Gostaria de saber quais os registradores eu preciso configurar para saber se a câmera está configurada como QQVGA e também como são passados os dados para serial?
Olá, prometo que vou reescrever este artigo usando um microcontrolador mais adequado e aí eu deixo exemplos no código fonte.