LoginSignup
4
1

More than 5 years have passed since last update.

Arduinoにカメラモジュール(OV7670 FiFo無)をつないでみた  [その3 カラー化とスピードアップ]

Last updated at Posted at 2017-01-22

カラー化とスピードアップ

[その1]では元ネタに従い、Arduinoにカメラをつなぐのはうまくいったので、もっと機能を改善する。

何をするか

(1)カラー化
  元ネタはモノクロなのでこれをカラー化する事により、表現力が向上する。
(2)画像表示の高速化
  画像表示速度を早くする事により、移動する画像に多少強くなる。

1.カラー化

 カラー画像を撮影するためにカメラの画像フォーマットをRGB565に変更する。
 データが2バイト/1ピクセルと2倍になるので、PCLKの変化毎にデータを送信する。
 データを送り切れなくなるので、PCLKを半分に遅くする。
 ※ov7670レジスタ(0x11)でXCLKとPCLKの分周比を設定

 モノクロ時
 PCLKを5.5uとし2クロック毎に送信
sigmono.jpg
 カラー時
 PCLKを10.5uとし1クロック毎に送信
sigcol.jpg

 画像の例
testcolor.jpg
 カメラの初期化はネットの設定例を参考にしたが、改善の余地があるような

2.画像表示の高速化

 画像の表示速度はシリアル通信の速度で抑えられており、シリアルの高速化が必要であり、この方法として次の2点を検討。
(1)通信速度アップ
  最大2Mbps迄UP可能だが、当方の環境では2Mbpsに設定するとデータの取りこぼしが発生した。
  ノイズに起因しているように思われるが、解決に時間がかかりそうなので、この方法は却下。
(2)空き時間をつめる
  PCLKの波形を俯瞰して見ると、一行分のデータと次の画像データとの間隔がかなり空いている事が判る。
  これは、OV7670をQVGAに設定すると、必要な画像は一行おきに発生するが、データの発生間隔は変わらない為である。
  そこで空き時間に伝送する方法を考えた。
  常套手段としてはリングバッファが考えられるが、arduino(16M)では処理能力が足りず、処理落ちが発生した。
  そこで、1バイト目はデータの直接送信を、2バイト目はバッファリングする事により、2倍のPCLKでも周期内での送信処理が可能となった。
  バッファリングしたデータは空き時間に送信する。
  また、これにより送信データはシーケンシャルにならないが、これは表示ソフトで並び替える事にした。

 表示ソフトの説明
 [その2 画像の表示]

  これにより通信速度は約2倍となり、モノクロでは約1秒(1.1s)で画像の更新が可能となった。

 PCLKと同期して送信
 (CH0.PCLK CH1.シリアル送信)
sigslow.jpg
 バッファリングして、空き時間にも送信
sigfast.jpg

「uno_ov7670.ino」ver1.1

// 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;
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(0x70, 0x3a); wrReg(0x71, 0x35); wrReg(0x72, 0x11); wrReg(0x73, 0xf1); 
  wrReg(0x0c, 0x04);  wrReg(0x3e, 0x19); wrReg(0xa2, 0x02);
  wrReg(0x13, 0x8f); // 
  //wrReg(0x71, 0xB5); // color bar
}

void cammode_select(){
  int clksp;
  if(cammd1) clksp =  6; 
  else             clksp = 11;
  if(cammd0){ 
    wrReg(REG_COM7 , 0x14); wrReg(REG_COM15, 0xd0); // RGB565
    wrReg(0x11, clksp*2-1); // Coloe Mode & Slow Speed
  } else {
    wrReg(REG_COM7 , 0x10); wrReg(REG_COM15, 0xc0); // YUYV
    wrReg(0x11, clksp-1); //Earlier it had the value: wrReg(0x11, 12); New version works better for me :) !!!!
  }  
}

uint8_t imgbf[320];
static void captureImg(uint8_t rm0, uint8_t rm1, uint16_t wg, uint16_t hg){
  uint16_t y, x;
  uint8_t *imgwp,*imgrp; 
  while (!(PIND & 8));//wait for high
  while ((PIND & 8));//wait for low
  UDR0 = 0xff; // Start Mark
  cli();
  y = hg;
  do{
    x = wg / 2;
    imgwp = &imgbf[0]; imgrp = &imgbf[0];
    do{
      while (!(PIND & 4)); while ((PIND & 4)); // Falling edge
        UDR0 = (PINC & 15) | (PIND & 240);
      while (!(PIND & 4)); while ((PIND & 4)); // Falling edge
        if(rm0) 
          if(rm1) *imgwp++ = (PINC & 15) | (PIND & 240);
          else    UDR0 = (PINC & 15) | (PIND & 240);
      while (!(PIND & 4)); while ((PIND & 4)); // Falling edge
        if(rm0 || !rm1) UDR0 = (PINC & 15) | (PIND & 240);
        else            *imgwp++ = (PINC & 15) | (PIND & 240);
      while (!(PIND & 4)); while ((PIND & 4)); // Falling edge
        if(rm0) 
          if(rm1) *imgwp++ = (PINC & 15) | (PIND & 240);
          else    UDR0 = (PINC & 15) | (PIND & 240);
    } while (--x);
    while(imgrp!=imgwp) {
       if (UCSR0A & _BV(UDRE0)) UDR0 = *imgrp++;
    }
  } 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){ 
      captureImg(cammd0,cammd1, 320, 240);
    } else { 
      //camcolor = rbf[1]; 
      if((rbf[1] & 1)!=0) cammd0 = 1; else cammd0 = 0;
      if((rbf[1] & 2)!=0) cammd1 = 1; else cammd1 = 0;
      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();
}

その他

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

 

4
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
1