ÍNDICE DE CONTEÚDO
- Explorando o módulo OV7670-FIFO: Interface SCCB
- Explorando o módulo OV7670-FIFO: Video Frame Buffer
Olá, caro leitor. Este artigo apresenta o desenvolvimento dos módulos para configuração do OV7670. Para tal, são utilizados os módulos apresentados no primeiro artigo que tem função de comunicação via interface SCCB. O projeto pode ser acessado neste link.
Estrutura do módulo OV7670 e conexão com o buffer AL422
No primeiro artigo foi apresentado o método de acesso do módulo OV7670, utilizando a interface SCCB. Como destacado, a interface SCCB posibilita transferir informações e será utilizada para configurar o módulo OV7670. O diagrama de blocos do sensor é mostrado na Figura 1.
A partir desse diagrama é possível verificar a conexão entre a interface SCCB e o banco de registradores do módulo. Além disso, verifica-se que os registradores estão conectados com outros dois módulos: Video Timing Generator e Exposure/Gain Control.
O bloco Video Timing Generator tem como saída alguns sinais de controle que estão conectados diretamente no buffer AL422. Esse CI é uma memória DRAM de 3 Mbits (393.216 palavras de 8 bits) que tem função de fila (FIFO – First In First Out). Os terminais de acesso são ilustrados na Figura 2.
Segundo informações do datsheet “ching ling”, a conexão entre o módulo OV7670 e o buffer é a ilustrada na Figura 3. De modo geral, a sincronização entre os sinais gerados pela câmera e a captura pelo buffer são realizados diretamente. Isto é, a saída da câmera está ligada diretamente nas entradas do buffer.
No entanto, por se tratar de uma memória FIFO, são disponibilizados sinais de controle para determinar o momento em que as informações serão gravadas e também para controlar a posição de acesso. Cabe ressaltar que ao inserir um dado no buffer um ponteiro de escrita é incrementado. O mesmo vale para leitura e para o ponteiro de leitura.
A seguir são destacados os sinais de controle:
- WE (Write Enable): Habilita operação de escrita na FIFO. Observe que este sinal passa por uma porta NAND (CI 7400) para que o sinal WE do buffer seja ativado em zero somente quando WE e HREF estão em nível lógico 1;
- WRST (Write Reset): Posiciona o ponteiro de escrita no endereço zero. Esse sinal é ativado em zero;
- RRST (Read Reset): Posiciona o ponteiro de leitura no endereço zero. Esse sinal é ativado em zero;
- OE (Output Enable): Habilita saída do buffer;
- RCLK (Read clock): Sincronização para leitura de dados.
Video Frame Buffer
Embora tenha vários modos de operação, neste artigo será descrito apenas a configuração para que o sensor apresente como saída uma imagem no formato RGB565. A resolução da imagem será configurada como QVGA (320 x 240).
O início de um frame é sempre indicado pelo sinal VSYNC. Durante o período que o sinal VSYNC está ativado ocorre o processo de varredura horizontal, sendo indicado pelo sinal HREF. Como dito anteriormente, o HREF é um dos sinais de controle para gravar um dado na FIFO. Quando o sinal HREF está ativado, os bytes são transferidos na frequência no sinal PCLK. Isso é ilustrado na Figura 4.
O frame completo é ilustrado na Figura 5. Cabe ressaltar que será utilizado o padrão QVGA.
Por fim, cada byte transferido corresponde ao formato RBG565. Portanto, cada pixel é representado por dois bytes. As componentes R, G e B são ilustradas na Figura 6.
Capturando um Frame
Para controlar as operações na FIFO foi criado um módulo chamado Fifo.h. Esse módulo contém apenas três funções: Inicialização, Captura de frame e leitura da fifo.
1 2 3 4 5 6 7 8 9 10 11 |
#ifndef INCLUDES_FIFO_H_ #define INCLUDES_FIFO_H_ #include <stdint.h> void FIFO_Init(void); void FIFO_StartCapture(void); uint16_t FIFO_GetPixel16(void); #endif /* INCLUDES_FIFO_H_ */ |
A função de inicialização configura o estado inicial dos pinos de controle do buffer. Lembre-se que os sinais são ativados em zero, exceto o WE e RCLK.
1 2 3 4 5 6 7 8 9 |
void FIFO_Init(void) { //Startup condition of control signals Hw_RCLK_LOW(); Hw_WEN_DIS(); Hw_RRST_EN(); Hw_WRST_EN(); Hw_OE_EN(); } |
Para capturar um frame é necessário monitorar o sinal VSYNC. Isso pode ser feito via interrupção ou polling. Nesse caso, utilizou-se da segunda opção.
A função aguarda o sinal VSYNC tornar-se zero (devido à configuração). Em seguida, reinicia o ponteiro de escrita e habilita a operação de escrita no buffer. Essa condição deve ser mantida até que o frame seja gravado inteiramente no buffer. Após essa condição, a escrita no buffer é desativada.
Por fim, o ponteiro de leitura é posicionado em zero e um pulso de clock é dado para efetuar tal condição.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
void FIFO_StartCapture(void) { //Wait for the falling edge of the VSYNC signal while(Hw_READ_VSYNC()); //The frame capture starts here. //First, reset the fifo write pointer Hw_WRST_DIS(); //Enable the write signal allowing the data to be written into the fifo Hw_WEN_EN(); //Now waits for the end of frame while(!Hw_READ_VSYNC()); while(Hw_READ_VSYNC()); //Disable write signal Hw_WEN_DIS(); //Enable read_reset signal Hw_RRST_EN(); //Enable write_reset signal Hw_WRST_EN(); //Generate a clock pulse Hw_Delay(1); Hw_RCLK_LOW(); Hw_Delay(1); Hw_RCLK_HIGH(); Hw_Delay(1); //Disable read_reset signal Hw_RRST_DIS(); } |
Até esse ponto o frame está armazenado no buffer e o ponteiro de leitura está posicionado no início. Assim sendo, cada pixel pode ser obtido gerando pulsos no sinal RCLK e lendo o valor da saída. Para tal, criou-se uma função que faz a leitura de dois bytes. Para ler o frame completo basta realizar esse procedimento 76800 vezes (320×240).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
uint16_t FIFO_GetPixel16(void) { uint16_t result = 0; //Get the first byte from fifo buffer Hw_RCLK_HIGH(); //Turn on the clock line result = (Hw_READ_BYTE() << 8); //read one byte Hw_RCLK_LOW(); //Turn off the clock line //Get the second byte from fifo buffer Hw_RCLK_HIGH(); //Turn on the clock line result |= Hw_READ_BYTE(); //read one byte Hw_RCLK_LOW(); //Turn off the clock line return result; } |
Configuração do módulo OV7670
Para que o módulo opere do modo indicado neste artigo, é necessário realizar a configuração de uma série de registradores. Para facilitar as operações foi criada uma biblioteca ov7670.h. Algumas funções dessa biblioteca foram mostradas no artigo anterior: leitura e escrita de registradores.
1 2 3 4 5 6 7 8 9 10 11 |
#ifndef INCLUDES_OV7670_H_ #define INCLUDES_OV7670_H_ #include <stdint.h> int OV7670_Init(void); void OV7670_Setup_QVGA_RGB565(void); void OV7670_WriteRegister(uint8_t addr, uint8_t data); uint8_t OV7670_ReadRegister(uint8_t addr); #endif |
A função de inicialização verifica se o ID e a versão do módulo ov7670. Se o retorno corresponde às informações descritas no datasheet, a configuração para o modo QVGA/RGB565 é realizada.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
int OV7670_Init(void) { uint8_t tmp; SCCB_Init(); /*Check PID and Version*/ tmp = OV7670_ReadRegister(REG_PID); if(0x76 == tmp) { tmp = OV7670_ReadRegister(REG_VER); if(0x73 == tmp) { //Reset camera to default configuration OV7670_WriteRegister(REG_COM7, 0x80); //default value OV7670_WriteRegister(REG_CLKRC, 0x80); //output sequence: UYVY *** use with register COM13[1] OV7670_WriteRegister(REG_TSLB, TSLB_YLAST); //VSYNC Negative OV7670_WriteRegister(REG_COM10, COM10_VS_NEG); //White balance enable OV7670_WriteRegister(0x13, 0xe7); OV7670_WriteRegister(0x6f, 0x9f); return 0; } } return -1; } |
Para configurar o módulo na condição desejada é utilizada a seguinte função:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
void OV7670_Setup_QVGA_RGB565(void) { OV7670_WriteRegister(REG_COM7, COM7_RGB | COM7_QVGA); //QVGA and RGB format OV7670_WriteRegister(REG_COM15, COM15_RGB565 ); //RGB565 output //frame control: from linux sources OV7670_WriteRegister(REG_HSTART,0x15); OV7670_WriteRegister(REG_HSTOP, 0x03); OV7670_WriteRegister(REG_HREF, 0x00); OV7670_WriteRegister(REG_VSTART, 0x03); OV7670_WriteRegister(REG_VSTOP, 0x7B); OV7670_WriteRegister(REG_VREF, 0x00); //Scale mode enabled because COM7 is configured with pre-defined format (COM7_RGB) //then COM14_QVGA must be set OV7670_WriteRegister(REG_COM3, COM3_QVGA | COM3_DCWEN); OV7670_WriteRegister(REG_COM14, COM14_QVGA); OV7670_WriteRegister(REG_MVFP, MVFP_MIRROR); //Horizontal mirror //datasheet: Table 2.2 Resolution Register Settings OV7670_WriteRegister(REG_SCALING_DCWCTR, SCALING_DCWCTR_QVGA); //vertical/horizontal down sample by 2 OV7670_WriteRegister(REG_SCALING_PCLK_DELAY, SCALING_PCLK_DELAY_QVGA); //clock divider: divided by 2 OV7670_WriteRegister(REG_SCALING_PCLK_DIV, SCALING_PCLK_DIV_QVGA); OV7670_WriteRegister(REG_SCALING_XSC, SCALING_XSC_QVGA); //horizontal scale factor OV7670_WriteRegister(REG_SCALING_YSC, SCALING_YSC_QVGA); //vertical scale factor OV7670_WriteRegister(0xb0, 0x84); //Saturation matrix: from linux sources OV7670_WriteRegister( 0x4f, 0xb3 ); OV7670_WriteRegister( 0x50, 0xb3 ); OV7670_WriteRegister( 0x51, 0 ); OV7670_WriteRegister( 0x52, 0x3d ); OV7670_WriteRegister( 0x53, 0xa7 ); OV7670_WriteRegister( 0x54, 0xe4 ); } |
Vale também exibir novamente as funções de leitura e escrita dos registradores.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
uint8_t OV7670_ReadRegister(uint8_t addr) { uint8_t result; //Start of transmission SCCB_Start(); //ID Address + Sub-address SCCB_WriteByte(SCCB_ADDR_WR); SCCB_DELAY(); SCCB_WriteByte((uint8_t)addr); //End of transmission SCCB_Stop(); SCCB_DELAY(); //Start of transmission SCCB_Start(); //ID Address + Read Data SCCB_WriteByte(SCCB_ADDR_WR | 0x01); SCCB_ReadByte(&result); //End of transmission SCCB_Stop(); return result; } void OV7670_WriteRegister(uint8_t addr, uint8_t data) { //Start of transmission SCCB_Start(); SCCB_WriteByte(SCCB_ADDR_WR); SCCB_WriteByte((uint8_t)addr); SCCB_WriteByte((uint8_t)data); //End of transmission SCCB_Stop(); } |
Teste da Aplicação
Para testar o módulo foi criada uma aplicação em C#. O software envia um comando pela porta serial para receber uma imagem. O dispositivo recebe esse comando e inicia o procedimento para capturar a imagem e retornar para o cliente. No final do processo a imagem é mostrada no software (Figura 7).
A seguir é mostrado o código da aplicação do microcontrolador. Algumas funções não foram apresentadas, pois tem relação direta com os recursos do microcontrolador. No entanto, você pode consultar esses arquivos neste link, e também adaptar a parte de acesso ao hardware para atender o seu microcontrolador.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
#include "Application.h" #include "Fifo.h" #include "OV7670.h" #define QVGA_SIZE (320*240) int main(void) { int state = 0; int flag; uint16_t tmp; uint16_t x, y; uint8_t pbuff[32]; uint8_t cmd; //Hardware Init App_Init(); for (;;) { switch(state) { case 0: flag = OV7670_Init(); if(flag == 0){ OV7670_Setup_QVGA_RGB565(); state = 1; } break; case 1: flag = App_SerialRead((uint8_t *)&cmd); if(flag == 0){ if((uint8_t)cmd == 'e'){ state = 2; } } break; case 2: FIFO_StartCapture(); for(x = 0; x<(QVGA_SIZE/16); x++){ for(y = 0; y<16; y++){ tmp = FIFO_GetPixel16(); pbuff[2*y] = (uint8_t)tmp; pbuff[2*y + 1] = (uint8_t)(tmp >> 8); } App_SerialWrite(pbuff, 32); } state = 1; break; } } return 0; } |
No primeiro teste foi utilizado um modo de operação que exibe uma imagem de barras de cores. Tal configuração é realizada alterando os registradores COM7 e REG_SCALING_YSC. De modo geral, os bits ativados em cada registrador determinam o modo de teste e o tipo de saída.
1 2 |
OV7670_WriteRegister(REG_COM7, COM7_RGB | COM7_QVGA | 0x02); OV7670_WriteRegister(REG_SCALING_YSC, SCALING_YSC_QVGA | 0x80); |
O resultado dessa configuração é mostrado na Figura 8.
Agora, removendo as configurações anteriores, a imagem real pode ser capturada.
1 2 |
OV7670_WriteRegister(REG_COM7, COM7_RGB | COM7_QVGA); OV7670_WriteRegister(REG_SCALING_YSC, SCALING_YSC_QVGA); |
O resultado dessa configuração é mostrado na Figura 9.
Resultados e Discussões
Cabe ressaltar alguns pontos importantes. Primeiro, o datasheet não contém todas as informações necessárias. Não encontrei outra versão e a documentação tem alguns erros, como a descrição do registrador REG_SCALING_XSC. No entanto, consultando o fonte do linux e outros documentos foi possível configurar o módulo. Segundo, além das configurações realizadas a partir dos registradores, é necessário também ajustar o foco da câmera. Vale consultar este Application Note.
Para aqueles que já utilizaram esse módulo e desvendaram alguns mistérios, compartilhem suas experiências!
Saiba mais
Aplicação com câmera usando Qt5
Calibração de câmeras – Parte 1
Utilizando Câmeras em Sistemas Linux Embarcado
Referências
[1] Implementation Guide. [2] AVERLOGIC FIFO BUFFER. [3] Module schematic. [4] Advanced Information Preliminary Datasheet.Imagem destacada: https://en.wikipedia.org/wiki/Mode_13h#/media/File:VGA_palette_with_black_borders.svg
Obrigado pela informação, mas eu fiquei com a duvida sobre os valores dos registros do control da frame (REG_HSTART,REG_HSTOP, REG_HREF, REG_VSTART, REG_VgSTOP, REG_VREF), qual é o procedimento para calcular eles?.
Agradeço novamente pela informação.
Olá, está muito bem explanado, parabéns! Este FIFO também pode ser utilizado com outros dispositivos interessantes, como um ADC de alta velocidade, acredito que daria para ampliar limtada capacidade de ADC de um Arduino Mega, talvez com um TLC5510 (20MSPS, 8 bits) por exemplo. O fabricante do FIFO tem outros modelos de maior capacidade, vale apena ir lá conhecer: https://www.averlogic.com/product.htm
Parabéns, ótimo artigo.