Arduinoにカメラモジュール(OV7670 FiFo無)をつないでみた 　［その４　VGAとQQVGA］

#やった事
　OV7670の初期化パメーターに追加を行い、VAGとQQVGAの撮影を可能とした。
　これに合わせて表示ソフトも変更を加えた。
#撮影例
　(1)ＶＧＡ(640x480)カラー
![VGA_Color.jpg](https://qiita-image-store.s3.amazonaws.com/0/159764/b3b05e30-10b2-0c15-4fdd-7d9797f8bba0.jpeg)
　 (2)ＱＱＶＧＡ(160x120)モノクロ
![QQVGA_Mono.jpg](https://qiita-image-store.s3.amazonaws.com/0/159764/74657fa0-ea1d-f74c-7071-111ef28ac84e.jpeg)

#撮影速度
　(1)ＶＧＡ
　　通常モード　モノクロ　４．１秒、カラー　７．９秒
　(2)ＱＶＧＡ
　　通常モード　モノクロ　２．１秒　カラー　４．１秒
　　高速モード　　　　　　１．２秒　　　　　２．１秒
　(3)ＱＱＶＧＡ
　　通常モード　モノクロ　１．２秒　カラー　２．１秒
　　高速モード　　　　　　０．４秒　カラー　０．６秒
#評価
　Arduinoでここまで出来る事は技術的には面白いが、ＶＧＡカラーでは８秒要しており、用途として微速度撮影ぐらいしか思いつかない。
　ＱＱＶＧＡ高速モードモノクロでは０．４秒(2.5fps)で撮影できており、簡易な画像センサーとして使えるかも知れない。
　いずれにせよ、高速(高価)なチップを使えば出来るのは当たり前であり、Arduinoの範疇で何か出来ないか考えたい。

#ソフト
「uno_ov7670.ino」ver1.2

　表示ソフトは　[［その２　画像の表示］](http://qiita.com/hi631/items/94fa041a13714377332f)

```
// Source code for application to transmit image from ov7670 to PC via USB
// By Siarhei Charkes in 2015
// http://privateblog.info 

#include <stdint.h>
#include <avr/io.h>
#include <util/twi.h>
#include <util/delay.h>
#include <avr/pgmspace.h>

#define camAddr_WR  0x42
#define camAddr_RD  0x43
#define REG_COM7  0x12
#define REG_COM10 0x15  // Control 10 //
#define REG_COM14 0x3e  // Control 14 //
#define REG_COM15 0x40  // Control 15 //

uint8_t cammd0=0, cammd1=0, cammd2=0;
struct regval_list{ uint8_t reg_num; uint16_t value; };
const struct regval_list ov7670_default_regs[] PROGMEM = {
  {0x3a,0x04},{0x40,0xd0},{0x12,0x14},{0x32,0x80},{0x17,0x16},{0x18,0x04},{0x19,0x02},{0x1a,0x7b},
  {0x03,0x06},{0x0c,0x00},{0x3e,0x00},{0x70,0x00},{0x71,0x00},{0x72,0x11},{0x73,0x00},{0xa2,0x02},
  {0x11,0x81},{0x7a,0x20},{0x7b,0x1c},{0x7c,0x28},{0x7d,0x3c},{0x7e,0x55},{0x7f,0x68},{0x80,0x76},
  {0x81,0x80},{0x82,0x88},{0x83,0x8f},{0x84,0x96},{0x85,0xa3},{0x86,0xaf},{0x87,0xc4},{0x88,0xd7},
  {0x89,0xe8},{0x13,0xe0},{0x00,0x00},{0x10,0x00},{0x0d,0x00},{0x14,0x28},{0xa5,0x05},{0xab,0x07},
  {0x24,0x75},{0x25,0x63},{0x26,0xA5},{0x9f,0x78},{0xa0,0x68},{0xa1,0x03},{0xa6,0xdf},{0xa7,0xdf},
  {0xa8,0xf0},{0xa9,0x90},{0xaa,0x94},{0x13,0xe5},{0x0e,0x61},{0x0f,0x4b},{0x16,0x02},{0x1e,0x37}, //{0x1e,0x17}
  {0x21,0x02},{0x22,0x91},{0x29,0x07},{0x33,0x0b},{0x35,0x0b},{0x37,0x1d},{0x38,0x71},{0x39,0x2a},
  {0x3c,0x78},{0x4d,0x40},{0x4e,0x20},{0x69,0x00},{0x6b,0x00},{0x74,0x19},{0x8d,0x4f},{0x8e,0x00},
  {0x8f,0x00},{0x90,0x00},{0x91,0x00},{0x92,0x00},{0x96,0x00},{0x9a,0x80},{0xb0,0x84},{0xb1,0x0c},
  {0xb2,0x0e},{0xb3,0x82},{0xb8,0x0a},{0x43,0x14},{0x44,0xf0},{0x45,0x34},{0x46,0x58},{0x47,0x28},
  {0x48,0x3a},{0x59,0x88},{0x5a,0x88},{0x5b,0x44},{0x5c,0x67},{0x5d,0x49},{0x5e,0x0e},{0x64,0x04},
  {0x65,0x20},{0x66,0x05},{0x94,0x04},{0x95,0x08},{0x6c,0x0a},{0x6d,0x55},{0x6e,0x11},{0x6f,0x9f},
  {0x6a,0x40},{0x01,0x40},{0x02,0x40},{0x13,0xe7},{0x15,0x02},{0x4f,0x80},{0x50,0x80},{0x51,0x00},
  {0x52,0x22},{0x53,0x5e},{0x54,0x80},{0x58,0x9e},{0x41,0x08},{0x3f,0x00},{0x75,0x05},{0x76,0xe1},
  {0x4c,0x00},{0x77,0x01},{0x3d,0xc2},{0x4b,0x09},{0xc9,0x60},{0x41,0x38},{0x56,0x40},{0x34,0x11},
  {0x3b,0x02},{0xa4,0x89},{0x96,0x00},{0x97,0x30},{0x98,0x20},{0x99,0x30},{0x9a,0x84},{0x9b,0x29},
  {0x9c,0x03},{0x9d,0x4c},{0x9e,0x3f},{0x78,0x04},{0x79,0x01},{0xc8,0xf0},{0x79,0x0f},{0xc8,0x00},
  {0x79,0x10},{0xc8,0x7e},{0x79,0x0a},{0xc8,0x80},{0x79,0x0b},{0xc8,0x01},{0x79,0x0c},{0xc8,0x0f},
  {0x79,0x0d},{0xc8,0x20},{0x79,0x09},{0xc8,0x80},{0x79,0x02},{0xc8,0xc0},{0x79,0x03},{0xc8,0x40},
  {0x79,0x05},{0xc8,0x30},{0x79,0x26},{0x09,0x03},{0x3b,0x42},{0xff,0xff},
};        

void error_led(void){
  DDRB |= 32;//make sure led is output
  while (1){//wait for reset
    PORTB ^= 32;// toggle led
    _delay_ms(100);
  }
}

void twiStart(void){
  TWCR = _BV(TWINT) | _BV(TWSTA) | _BV(TWEN);//send start
  while (!(TWCR & (1 << TWINT)));//wait for start to be transmitted
  if ((TWSR & 0xF8) != TW_START)
    error_led();
}

void twiWriteByte(uint8_t DATA, uint8_t type){
  TWDR = DATA;
  TWCR = _BV(TWINT) | _BV(TWEN);
  while (!(TWCR & (1 << TWINT))) {}
  if ((TWSR & 0xF8) != type)
    error_led();
}

void twiAddr(uint8_t addr, uint8_t typeTWI){
  TWDR = addr;//send address
  TWCR = _BV(TWINT) | _BV(TWEN);    /* clear interrupt to start transmission */
  while ((TWCR & _BV(TWINT)) == 0); /* wait for transmission */
  if ((TWSR & 0xF8) != typeTWI)
    error_led();
}

void wrReg(uint8_t reg, uint8_t dat){
  //send start condition
  twiStart();
  twiAddr(camAddr_WR, TW_MT_SLA_ACK);
  twiWriteByte(reg, TW_MT_DATA_ACK);
  twiWriteByte(dat, TW_MT_DATA_ACK);
  TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWSTO);//send stop
  _delay_ms(1);
}

static uint8_t twiRd(uint8_t nack){
  if (nack){
    TWCR = _BV(TWINT) | _BV(TWEN);
    while ((TWCR & _BV(TWINT)) == 0); /* wait for transmission */
    if ((TWSR & 0xF8) != TW_MR_DATA_NACK)
      error_led();
    return TWDR;
  }
  else{
    TWCR = _BV(TWINT) | _BV(TWEN) | _BV(TWEA);
    while ((TWCR & _BV(TWINT)) == 0); /* wait for transmission */
    if ((TWSR & 0xF8) != TW_MR_DATA_ACK)
      error_led();
    return TWDR;
  }
}

uint8_t rdReg(uint8_t reg){
  uint8_t dat;
  twiStart();
  twiAddr(camAddr_WR, TW_MT_SLA_ACK);
  twiWriteByte(reg, TW_MT_DATA_ACK);
  TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWSTO);//send stop
  _delay_ms(1);
  twiStart();
  twiAddr(camAddr_RD, TW_MR_SLA_ACK);
  dat = twiRd(1);
  TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWSTO);//send stop
  _delay_ms(1);
  return dat;
}

void wrSensorRegs8_8(const struct regval_list reglist[]){
  uint8_t reg_addr, reg_val;
  const struct regval_list *next = reglist;
  while ((reg_addr != 0xff) | (reg_val != 0xff)){
    reg_addr = pgm_read_byte(&next->reg_num);
    reg_val = pgm_read_byte(&next->value);
    wrReg(reg_addr, reg_val);
    next++;
  }
}

void arduinoUnoInut(void) {
  //cli();//disable interrupts
  
  /* Setup the 8mhz PWM clock  * This will be on pin 11*/
  DDRB |= (1 << 3);//pin 11
  ASSR &= ~(_BV(EXCLK) | _BV(AS2));
  TCCR2A = (1 << COM2A0) | (1 << WGM21) | (1 << WGM20);
  TCCR2B = (1 << WGM22) | (1 << CS20);
  OCR2A = 0;//(F_CPU)/(2*(X+1))
  DDRC &= ~15;//low d0-d3 camera
  DDRD &= ~252;//d7-d4 and interrupt pins
  
  //set up twi for 100khz
  TWSR &= ~3;//disable prescaler for TWI
  TWBR = 72;//set to 100khz
  
  //enable serial
  UBRR0H  = 0;
  UBRR0L  = 1; // 0 = 2M baud rate. 1 = 1M baud. 3 = 0.5M. 7 = 250k 207 is 9600 baud rate.
  UCSR0A |= 2;//double speed aysnc
  UCSR0B  = (1 << RXEN0) | (1 << TXEN0);//Enable receiver and transmitter
  UCSR0C  = 6;//async 1 stop bit 8bit char no parity bits
}

void camInit(void){
  wrReg(0x12, 0x80); delay(100);
  wrSensorRegs8_8(ov7670_default_regs);
  wrReg(REG_COM10, 32);//PCLK does not toggle on HBLANK.
  wrReg(0x13, 0x8f); // Set ACE 
}

void cammode_select(){
  uint8_t clkspm,clkspc,com7s;
  if(cammd2==2){ // QQVGA
      com7s = 0x00;
      wrReg(0x70, 0x3a); wrReg(0x71, 0x35); wrReg(0x72, 0x22); wrReg(0x73, 0xf2); 
      wrReg(0x0c, 0x04); wrReg(0x3e, 0x1a); wrReg(0xa2, 0x02);
  } else {
    if(cammd2==1){ // VGA
      com7s = 0x00;
      wrReg(0x70, 0x3a); wrReg(0x71, 0x35); wrReg(0x72, 0x11); wrReg(0x73, 0xf0); 
      wrReg(0x0c, 0x00);  wrReg(0x3e, 0x00); wrReg(0xa2, 0x02);
    } else { // QVGA
      com7s = 0x10;
      wrReg(0x70, 0x3a); wrReg(0x71, 0x35); wrReg(0x72, 0x11); wrReg(0x73, 0xf1); 
      wrReg(0x0c, 0x04);  wrReg(0x3e, 0x19); wrReg(0xa2, 0x02);
    }
  }

  if(cammd2==2) 
    if(cammd1) { clkspm = 1; clkspc =  2;}
    else       { clkspm = 5; clkspc = 10;}
  else {
    if(cammd1) { clkspm = 5; clkspc = 10;}// Speedup 
    else       { clkspm = 10; clkspc = 20;}// Normal
    if(cammd2 & 1) { clkspm = clkspm * 2; clkspc = clkspc * 2;} // if VGA
  }
  
  if(cammd0){ // Color 
    com7s = com7s + 4; // RGB565
    wrReg(REG_COM15, 0xd0); 
    wrReg(0x11, clkspc); // Coloe Mode & Slow Speed
  } else { // Mono
    com7s = com7s + 0; // YUYV
    wrReg(REG_COM15, 0xc0); 
    wrReg(0x11, clkspm); //Earlier it had the value: wrReg(0x11, 12); New version works better for me :) !!!!
  } 
  wrReg(REG_COM7 , com7s); 
  //wrReg(0x71, 0xB5); // color bar
}

uint8_t imgbf[320];
static void captureImg( uint16_t wg, uint16_t hg){
  uint16_t y, x;
  uint8_t rm0,rm1,*imgwp,*imgrp;
  uint8_t s0s,s0b,s1s,s1b,s2s,s2b,s3s,s3b,qqvm; 
  while (!(PIND & 8)); while ((PIND & 8)); // VSYNC Falling edge

  if(cammd2==2 && cammd1==1){ // QQVGA + SPD
    if(cammd0){ // Color
      s0b = 0; s1b = 1; s2b = 1;
      s0s = 1; s1s = 0; s2s = 0;
      qqvm = 0;
    } else{ // mono
      s0b = 1; s1b = 0; s2b = 1;
      s0s = 0; s1s = 0; s2s = 0;
      qqvm = 1;     
    }
  } else { 
    qqvm = 0;
    s0b = 0; s0s = 1;
    if(cammd0){
      if(cammd1) {s1b = 1; s1s = 0; }
      else       {s1b = 0; s1s = 1; }
    } else       {s1b = 0; s1s = 0; }
    if(cammd0 || !cammd1) {s2s = 1; s2b = 0;}
    else                  {s2s = 0; s2b = 1;}
  }

  UDR0 = 0xff; // Start Mark
  cli();
  y = hg;
  do{
    x = wg / 2;
    imgwp = &imgbf[0]; imgrp = &imgbf[0];
    do{
      while (!(PIND & 4)); while ((PIND & 4)); // PCLK Falling edge
        if(qqvm) {
          if((x & 1)==0) UDR0 = (PINC & 15) | (PIND & 240);
          else           *imgwp++ = (PINC & 15) | (PIND & 240);
        } else {
          if(s0s) UDR0 = (PINC & 15) | (PIND & 240);
          if(s0b) *imgwp++ = (PINC & 15) | (PIND & 240);
        }
      while (!(PIND & 4)); while ((PIND & 4)); // Falling edge
        if(s1s) UDR0 = (PINC & 15) | (PIND & 240);
        if(s1b) *imgwp++ = (PINC & 15) | (PIND & 240);
      while (!(PIND & 4)); while ((PIND & 4)); // Falling edge
        if(s2s) UDR0 = (PINC & 15) | (PIND & 240);
        if(s2b) *imgwp++ = (PINC & 15) | (PIND & 240);
      while (!(PIND & 4)); while ((PIND & 4)); // Falling edge
        if(s1s) UDR0 = (PINC & 15) | (PIND & 240);
        if(s1b) *imgwp++ = (PINC & 15) | (PIND & 240);
    yield();
    } while (--x);
    while(imgrp!=imgwp) {
      while (!(UCSR0A & (1 << UDRE0)));//wait for byte to transmit
      UDR0 = *imgrp++;
    yield();
    }
  } while (--y);

  for( x=0; x<8; x++){ // End Mark
    while (!(UCSR0A & (1 << UDRE0)));//wait for byte to transmit
      UDR0 = 0;
  }
  sei();
  //_delay_ms(100);
}

void wrOV7670Reg(unsigned char adr, unsigned char  dat){
  wrReg( adr, dat);
}

void setup(){
  arduinoUnoInut();
  camInit(); 
  cammode_select();
}

int s22hex(char * ha){
  int rc = 0,hi;
  for(int i=0; i<2; i++){
    char ch = *ha++;
    if(ch>'9') hi = (ch & 0xdf)-0x37; else hi = ch-0x30;
    rc = rc*16+hi;
  }
  return rc;
}
int rct = 0,repf = 0;
char rbf[16];
byte inChar = 0;
char * rbfp;
void docmd(){
  inChar = ' ';
  if(UCSR0A & (1<<RXC0)){
    byte kd = UDR0;
    if(kd>=0x20) { rbf[rct++] = kd; return;
    } else {
      if(kd==0x08 && rct!=0) { rct--; return; } 
      else{ rbf[rct] = 0; inChar = rbf[0]; rbfp = &rbf[2]; rct = 0;}
    }
  } else if(repf==1) inChar = 'c';

  if (inChar == 'c') {
    if(rbf[1]==0){ 
      if(cammd2==2)   captureImg( 160, 120); // QQVGA
      else
        if(cammd2==1) captureImg( 640, 480); // VGA
        else          captureImg( 320, 240); // QVGA
    } else { 
      //camcolor = rbf[1]; 
      cammd0 = rbf[1] & 1;
      cammd1 = (rbf[1]  >> 1) & 1;
      cammd2 = (rbf[1]  >> 2) & 3;
      cammode_select();  
    }
  }
  if (inChar == 'r') repf = 1-repf;
  if (inChar == 's') { // Setup ov7670 Regs
    byte adr = s22hex(&rbf[1]);
    byte dat = s22hex(&rbf[3]);
    if(adr==0xff) setup();
    else wrReg( adr, dat);
  }
}
void loop(){
  docmd();
}




```
## その他
　上記記載のハード、ソフトは無保証であり、各自の責任においてご利用願います。