So-net無料ブログ作成
検索選択

ArduinoでCMOSカメラの画像を取得してみる [Arduino]

先日,ArduinoとCMOSカメラを接続して動かしてみたので,いよいよ画像を取得してみる.

というわけで,早速だがArduinoのスケッチを示そう.

ファイルは2つあって,ov7670.inoとov7670reg.hだ.
まずはov7670.ino
#include <Wire.h>
#include "ov7670reg.h"

// Pin
const int WEN   = 14; // PC0
const int RRST  = 15; // PC1
const int OE    = 16; // PC2
const int RCLK  = 17; // PC3
const int VSYNC =  2; // PD2 INT0
const int D0    =  8; // PB0
const int D1    =  9; // PB1
const int D2    = 10; // PB2
const int D3    = 11; // PB3
const int D4    =  4; // PD4
const int D5    =  5; // PD5
const int D6    =  6; // PD6
const int D7    =  7; // PD7

boolean isWriteEnable = false;


void setPinMode()
{
  pinMode(WEN,   OUTPUT);
  pinMode(RRST,  OUTPUT);
  pinMode(OE,    OUTPUT);
  pinMode(RCLK,  OUTPUT);
  pinMode(D0,    INPUT );
  pinMode(D1,    INPUT );
  pinMode(D2,    INPUT );
  pinMode(D3,    INPUT );
  pinMode(D4,    INPUT );
  pinMode(D5,    INPUT );
  pinMode(D6,    INPUT );
  pinMode(D7,    INPUT );

  attachInterrupt(0, setWriteEnable, FALLING);

  digitalWrite(WEN,  LOW);
  digitalWrite(RRST, HIGH);
  digitalWrite(OE,   HIGH);
  digitalWrite(RCLK, HIGH);
}

void setWriteEnable()
{
  if (isWriteEnable) {
    digitalWrite(WEN, HIGH);
    isWriteEnable = false;
  } else {
    digitalWrite(WEN, LOW);
  }
}

void initFifo()
{
  digitalWrite(RCLK, HIGH); digitalWrite(RRST, HIGH); digitalWrite(OE, HIGH);
  digitalWrite(RCLK, HIGH); digitalWrite(RRST, LOW ); digitalWrite(OE, HIGH);
  digitalWrite(RCLK, LOW ); digitalWrite(RRST, LOW ); digitalWrite(OE, HIGH);
  digitalWrite(RCLK, HIGH); digitalWrite(RRST, LOW ); digitalWrite(OE, HIGH);
  digitalWrite(RCLK, LOW ); digitalWrite(RRST, LOW ); digitalWrite(OE, HIGH);
  digitalWrite(RCLK, LOW ); digitalWrite(RRST, HIGH); digitalWrite(OE, HIGH);
  digitalWrite(RCLK, HIGH); digitalWrite(RRST, HIGH); digitalWrite(OE, HIGH);
  digitalWrite(RCLK, HIGH); digitalWrite(RRST, HIGH); digitalWrite(OE, LOW );
  digitalWrite(RCLK, LOW ); digitalWrite(RRST, HIGH); digitalWrite(OE, LOW );
  digitalWrite(RCLK, HIGH); digitalWrite(RRST, HIGH); digitalWrite(OE, LOW );
}

void readFifo()
{
  const int HSIZE = 160;
  const int VSIZE = 120;
  const int DATABYTE = 2;

  int d0, d1, d2, d3, d4, d5, d6, d7;
  byte data;

  for (int v = 0; v < VSIZE; v++) {
    for (int h = 0; h < HSIZE; h++) {
      for (int d = 0; d < DATABYTE; d++) {
        digitalWrite(RCLK, HIGH);
        d0 = digitalRead(D0);
        d1 = digitalRead(D1);
        d2 = digitalRead(D2);
        d3 = digitalRead(D3);
        d4 = digitalRead(D4);
        d5 = digitalRead(D5);
        d6 = digitalRead(D6);
        d7 = digitalRead(D7);
        data = pinLevel2Byte(d0, 0)
          + pinLevel2Byte(d1, 1)
          + pinLevel2Byte(d2, 2)
          + pinLevel2Byte(d3, 3)
          + pinLevel2Byte(d4, 4)
          + pinLevel2Byte(d5, 5)
          + pinLevel2Byte(d6, 6)
          + pinLevel2Byte(d7, 7);
        digitalWrite(RCLK, LOW);

        char temp[3];
        snprintf(temp, sizeof(temp), "%02X", data);
        Serial.print(temp);
      }
    }
    Serial.println("");
  }

  // read end
  digitalWrite(RCLK, LOW ); digitalWrite(OE, HIGH);
  digitalWrite(RCLK, HIGH); digitalWrite(OE, HIGH);
}

byte pinLevel2Byte(int level, int shift)
{
  byte result = (level == HIGH) ? 1 : 0;
  return result << shift;
}

void writeRegister(int address, int data)
{
  const int I2C_WRITE_ADDR = 0x42;

  Wire.beginTransmission(I2C_WRITE_ADDR >> 1);
  Wire.write(address);
  Wire.write(data);
  Wire.endTransmission();
}

int readRegister(int address)
{
  const int I2C_READ_ADDR = 0x43;

  Wire.beginTransmission(I2C_READ_ADDR >> 1);
  Wire.write(address);
  Wire.endTransmission();

  Wire.requestFrom(I2C_READ_ADDR >> 1, 1);
  return Wire.read();
}

void resetCamera()
{
  writeRegister(0x12, 0x80);
  delay(200);
}

void initRegister()
{
  // QQVGA, RGB444
  writeRegister(REG_CLKRC,  0x80);
  writeRegister(REG_COM11,  0x0A);
  writeRegister(REG_TSLB,   0x04);
  writeRegister(REG_COM7,   0x04);
  writeRegister(REG_RGB444, 0x02);
  writeRegister(REG_COM15,  0xD0);
  writeRegister(REG_HSTART, 0x16);
  writeRegister(REG_HSTOP,  0x04);
  writeRegister(REG_HREF,   0x24);
  writeRegister(REG_VSTART, 0x02);
  writeRegister(REG_VSTOP,  0x7A);
  writeRegister(REG_VREF,   0x0A);
  writeRegister(REG_COM10,  0x02);
  writeRegister(REG_COM3,   0x04);
  writeRegister(REG_COM14,  0x1A);
  writeRegister(0x72,       0x22);
  writeRegister(0x73,       0xF2);

  // COLOR SETTING
  writeRegister(0x4F, 0x80);
  writeRegister(0x50, 0x80);
  writeRegister(0x51, 0x00);
  writeRegister(0x52, 0x22);
  writeRegister(0x53, 0x5E);
  writeRegister(0x54, 0x80);
  writeRegister(0x56, 0x40);
  writeRegister(0x58, 0x9E);
  writeRegister(0x59, 0x88);
  writeRegister(0x5A, 0x88);
  writeRegister(0x5B, 0x44);
  writeRegister(0x5C, 0x67);
  writeRegister(0x5D, 0x49);
  writeRegister(0x5E, 0x0E);
  writeRegister(0x69, 0x00);
  writeRegister(0x6A, 0x40);
  writeRegister(0x6B, 0x0A);
  writeRegister(0x6C, 0x0A);
  writeRegister(0x6D, 0x55);
  writeRegister(0x6E, 0x11);
  writeRegister(0x6F, 0x9F);

  writeRegister(0xB0, 0x84);
}

void printData(int address, int data)
{
  char temp[16];
  snprintf(temp, sizeof(temp), "addr=%02X data=%02X", address, data);
  Serial.println(temp);
}

void receiveCommand(char *buff)
{
  char c = '\0';
  while (c != '\r' ) {
    if (Serial.available() > 0) {
      c = Serial.read();
      Serial.print(c);
      *buff++ = c;
    }
  }
  *buff = '\0';
  Serial.println("");
}

void setup()
{
  Serial.begin(115200);
  Wire.begin(); 

  printMenu();

  setPinMode();
  resetCamera();
  initRegister();
}

void loop()
{
  int address  = 0;
  int data = 0;

  char buff[256];
  char *buff_ptr;
  char *cmd_ptr;
  char *arg1_ptr;
  char *arg2_ptr;
  const char DELIMITTER[] = " \t";

  Serial.print("> ");
  receiveCommand(buff);

  //  trim white space
  buff_ptr = buff;
  while ( (*buff_ptr == ' ') || (*buff_ptr == '\t') || (*buff_ptr == '\n') ) {
    buff_ptr++;
  }

  cmd_ptr = strtok(buff_ptr, DELIMITTER);

  switch (tolower(*cmd_ptr)) {
    case 'w':
      arg1_ptr = strtok(NULL, DELIMITTER);
      arg2_ptr = strtok(NULL, DELIMITTER);

      if ( (arg1_ptr != NULL) && (arg2_ptr != NULL) ) {
        address = hex2dec(arg1_ptr);
        data    = hex2dec(arg2_ptr);

        printData(address, data);
        writeRegister(address, data);

        delay(10);

      } else {
        Serial.println("Syntax Error");
      }

      break;
    case 'r':
      arg1_ptr = strtok(NULL, DELIMITTER);

      if (arg1_ptr != NULL) {
        address = hex2dec(arg1_ptr);
        data = readRegister(address);

        printData(address, data);

        delay(50);

      } else {
        Serial.println("Syntax Error");
      }
      break;

    case 'd':
      Serial.println("Dump");
      initFifo();
      readFifo();
      break;

    case 's':
      Serial.println("Start");
      isWriteEnable = true;
      break;

    case 'h':
      printHelp();
      break;

    default:
      Serial.println("Syntax Error");
      break;
  }

  Serial.println("");
}

void printMenu()
{
  Serial.println("------------------------------");
  Serial.println(" OV7670 Camera Debugger");
  Serial.println("------------------------------");
  Serial.println("");
}

void printHelp()
{
  Serial.println("WRITE :w [address] [data]");
  Serial.println("READ  :r [address]");
  Serial.println("START :s");
  Serial.println("DUMP  :d");
  Serial.println("HELP  :h");
}

int hex2dec(char *str)
{
  return strtol(str, NULL, 16);
}


次にov7670reg.h
#define REG_GAIN        0x00    /* Gain lower 8 bits (rest in vref) */
#define REG_BLUE        0x01    /* blue gain */
#define REG_RED         0x02    /* red gain */
#define REG_VREF        0x03    /* Pieces of GAIN, VSTART, VSTOP */
#define REG_COM1        0x04    /* Control 1 */
#define COM1_CCIR656    0x40    /* CCIR656 enable */
#define REG_BAVE        0x05    /* U/B Average level */
#define REG_GbAVE       0x06    /* Y/Gb Average level */
#define REG_AECHH       0x07    /* AEC MS 5 bits */
#define REG_RAVE        0x08    /* V/R Average level */
#define REG_COM2        0x09    /* Control 2 */
#define COM2_SSLEEP     0x10    /* Soft sleep mode */
#define REG_PID         0x0a    /* Product ID MSB */
#define REG_VER         0x0b    /* Product ID LSB */
#define REG_COM3        0x0c    /* Control 3 */
#define COM3_SWAP       0x40    /* Byte swap */
#define COM3_SCALEEN    0x08    /* Enable scaling */
#define COM3_DCWEN      0x04    /* Enable downsamp/crop/window */
#define REG_COM4        0x0d    /* Control 4 */
#define REG_COM5        0x0e    /* All "reserved" */
#define REG_COM6        0x0f    /* Control 6 */
#define REG_AECH        0x10    /* More bits of AEC value */
#define REG_CLKRC       0x11    /* Clocl control */
#define CLK_EXT         0x40    /* Use external clock directly */
#define CLK_SCALE       0x3f    /* Mask for internal clock scale */
#define REG_COM7        0x12    /* Control 7 */
#define COM7_RESET      0x80    /* Register reset */
#define COM7_FMT_MASK   0x38
#define COM7_FMT_VGA    0x00
#define COM7_FMT_CIF    0x20    /* CIF format */
#define COM7_FMT_QVGA   0x10    /* QVGA format */
#define COM7_FMT_QCIF   0x08    /* QCIF format */
#define COM7_RGB        0x04    /* bits 0 and 2 - RGB format */
#define COM7_YUV        0x00    /* YUV */
#define COM7_BAYER      0x01    /* Bayer format */
#define COM7_PBAYER     0x05    /* "Processed bayer" */
#define REG_COM8        0x13    /* Control 8 */
#define COM8_FASTAEC    0x80    /* Enable fast AGC/AEC */
#define COM8_AECSTEP    0x40    /* Unlimited AEC step size */
#define COM8_BFILT      0x20    /* Band filter enable */
#define COM8_AGC        0x04    /* Auto gain enable */
#define COM8_AWB        0x02    /* White balance enable */
#define COM8_AEC        0x01    /* Auto exposure enable */
#define REG_COM9        0x14    /* Control 9  - gain ceiling */
#define REG_COM10       0x15    /* Control 10 */
#define COM10_HSYNC     0x40    /* HSYNC instead of HREF */
#define COM10_PCLK_HB   0x20    /* Suppress PCLK on horiz blank */
#define COM10_HREF_REV  0x08    /* Reverse HREF */
#define COM10_VS_LEAD   0x04    /* VSYNC on clock leading edge */
#define COM10_VS_NEG    0x02    /* VSYNC negative */
#define COM10_HS_NEG    0x01    /* HSYNC negative */
#define REG_HSTART      0x17    /* Horiz start high bits */
#define REG_HSTOP       0x18    /* Horiz stop high bits */
#define REG_VSTART      0x19    /* Vert start high bits */
#define REG_VSTOP       0x1a    /* Vert stop high bits */
#define REG_PSHFT       0x1b    /* Pixel delay after HREF */
#define REG_MIDH        0x1c    /* Manuf. ID high */
#define REG_MIDL        0x1d    /* Manuf. ID low */
#define REG_MVFP        0x1e    /* Mirror / vflip */
#define MVFP_MIRROR     0x20    /* Mirror image */
#define MVFP_FLIP       0x10    /* Vertical flip */
#define REG_AEW         0x24    /* AGC upper limit */
#define REG_AEB         0x25    /* AGC lower limit */
#define REG_VPT         0x26    /* AGC/AEC fast mode op region */
#define REG_HSYST       0x30    /* HSYNC rising edge delay */
#define REG_HSYEN       0x31    /* HSYNC falling edge delay */
#define REG_HREF        0x32    /* HREF pieces */
#define REG_TSLB        0x3a    /* lots of stuff */
#define TSLB_YLAST      0x04    /* UYVY or VYUY - see com13 */
#define REG_COM11       0x3b    /* Control 11 */
#define COM11_NIGHT     0x80    /* NIght mode enable */
#define COM11_NMFR      0x60    /* Two bit NM frame rate */
#define COM11_HZAUTO    0x10    /* Auto detect 50/60 Hz */
#define COM11_50HZ      0x08    /* Manual 50Hz select */
#define COM11_EXP       0x02
#define REG_COM12       0x3c    /* Control 12 */
#define COM12_HREF      0x80    /* HREF always */
#define REG_COM13       0x3d    /* Control 13 */
#define COM13_GAMMA     0x80    /* Gamma enable */
#define COM13_UVSAT     0x40    /* UV saturation auto adjustment */
#define COM13_UVSWAP    0x01    /* V before U - w/TSLB */
#define REG_COM14       0x3e    /* Control 14 */
#define COM14_DCWEN     0x10    /* DCW/PCLK-scale enable */
#define REG_EDGE        0x3f    /* Edge enhancement factor */
#define REG_COM15       0x40    /* Control 15 */
#define COM15_R10F0     0x00    /* Data range 10 to F0 */
#define COM15_R01FE     0x80    /*            01 to FE */
#define COM15_R00FF     0xc0    /*            00 to FF */
#define COM15_RGB565    0x10    /* RGB565 output */
#define COM15_RGB555    0x30    /* RGB555 output */
#define REG_COM16       0x41    /* Control 16 */
#define COM16_AWBGAIN   0x08    /* AWB gain enable */
#define REG_COM17       0x42    /* Control 17 */
#define COM17_AECWIN    0xc0    /* AEC window - must match COM4 */
#define COM17_CBAR      0x08    /* DSP Color bar */
#define REG_CMATRIX_BASE 0x4f
#define CMATRIX_LEN 6
#define REG_CMATRIX_SIGN 0x58
#define REG_BRIGHT      0x55    /* Brightness */
#define REG_CONTRAS     0x56    /* Contrast control */
#define REG_GFIX        0x69    /* Fix gain control */
#define REG_REG76       0x76    /* OV's name */
#define R76_BLKPCOR     0x80    /* Black pixel correction enable */
#define R76_WHTPCOR     0x40    /* White pixel correction enable */
#define REG_RGB444      0x8c    /* RGB 444 control */
#define R444_ENABLE     0x02    /* Turn on RGB444, overrides 5x5 */
#define R444_RGBX       0x01    /* Empty nibble at end */
#define REG_HAECC1      0x9f    /* Hist AEC/AGC control 1 */
#define REG_HAECC2      0xa0    /* Hist AEC/AGC control 2 */
#define REG_BD50MAX     0xa5    /* 50hz banding step limit */
#define REG_HAECC3      0xa6    /* Hist AEC/AGC control 3 */
#define REG_HAECC4      0xa7    /* Hist AEC/AGC control 4 */
#define REG_HAECC5      0xa8    /* Hist AEC/AGC control 5 */
#define REG_HAECC6      0xa9    /* Hist AEC/AGC control 6 */
#define REG_HAECC7      0xaa    /* Hist AEC/AGC control 7 */
#define REG_BD60MAX     0xab    /* 60hz banding step limit */


これをArduinoに書き込めばOK.

いろいろwebで調べて,とりあえずQQVGAのRGB444で画像取得する例がいくつか見つかったので,それで試してる.

使い方は,シリアルモニタを開いて,
sを入力すると撮影,dで画像データのダンプが行われる.hで簡単なヘルプ(というほどのものでもないが)が表示される.なので,まぁスケッチを見ながらいろいろ試してみてくださいな.

画像データは,QQVGAのRGB444で動かしているので,ダンプすると160x120=19200画素のデータが,1画素あたり2バイト出力される.なので,トータルとしては38400バイトになる.
データの並びは,xBGRで出力される.なお,160画素ごとに改行を入れて出力される.改行を除去すると,1バイトあたり2文字でダンプされるので,結果として,76800文字のデータが得られることになる.

さて,データだけあってもつまらないので,Processingで画像を表示してみる.
Processingのコードは,こんな感じ.
int width = 160;
int height = 120;
String[] pixeldata;

void setup() {
  frameRate(1);
  size(width, height);
  noStroke();
  pixeldata = loadStrings("captureData.txt");
}

void draw() {
  for (int i = 0; i < width * height; i++) {
    int x = i % width;
    int y = i / width;

    fill(
    unhex(String.valueOf(pixeldata[0].charAt(4*i+3))) *16, 
    unhex(String.valueOf(pixeldata[0].charAt(4*i+2))) *16, 
    unhex(String.valueOf(pixeldata[0].charAt(4*i+1))) *16
      );    

    rect(x, y, 1, 1);
  }
}


ダンプしたデータは,captureData.txtとしてProcessingのコードと同じディレクトリに保存してくださいな.

で,表示されるサンプル画像はこんな感じ.スクリーンショット 2014-07-20 9.58.05.png
ま,QQVGなのと,RGB各色4ビットしかないので,画質はそれなりですな.
あと,水平方向の位相がちょっとずれてるようなのと,電源投入一発目の画像がおかしいことがあるんだけど...

ま,お試しあれ.

参考サイト
いっぱいあって,いっぱい参考にした.特に以下の2つを参考にしたけど,ほかにもGoogleでov7670で検索してヒットするサイトは軒並みチェックした.先人に感謝.
トラ技 頒布カメラB(OV7670 FIFO AL422B)の動作確認 | mbed
HR2_blog: mbedでトラ技3月号の頒布カメラBを使う


2014.12.15追記
サンプル画像の元データ(captureData.txt)はこんな感じ.captureData.txt.zip.jpg
So-netのブログはテキストファイルのアップロードができないので,ZIP圧縮して,拡張子に.jpgをつけて無理矢理アップロードしてる.なので,ダウンロードしたら.jpgを消してZIPファイルとして展開してくださいな.

nice!(1)  コメント(4)  トラックバック(0) 
共通テーマ:日記・雑感

nice! 1

コメント 4

NO NAME

いつも参考にさせていただいております。
一つご質問したいのですが、撮影してダンプしたデータをprocessingで見たところモザイクになってしまい、画像が見ることができません。
もしもでいいのですがどうしたよいかご助言頂けるとありがたいです。
by NO NAME (2014-12-15 13:08) 

s15silvia

とりあえずcaptureData.txtをアップロードしてみました.これがうまく表示できるか試してみてください.Processing側は問題ないと思いますが,念のため.
で,うまく表示できたらProcessing側は問題ないのでArduino側ということになりますが...
心当たりとしては,本文にも書きましたが,電源投入一発目の画像がおかしいことがあって,どうやら初期化処理に問題があるようです.そのときの画像がモザイクっぽかった気がします.
この場合,再度画像を取得(sを送ってからdでダンプする)してみるか,あとはArduino基板のリセットスイッチを押して再度初期化して画像を取得してみてください.これでまともな画像が得られたと思います.
これでうまくいくといいのですが...
by s15silvia (2014-12-15 23:05) 

NO NAME

ご返答ありがとうございます。どうやらピンの接触が悪かったようです。
また、テキストの改行を削除する際に不手際があったようでプログラムの中の改行をする部分を一旦削除した所正常に映りました。
ご丁寧にありがとうございました。
by NO NAME (2014-12-17 11:49) 

内緒

Processing側は、テスト用のデータで正常に表示できました。
Arduino側もコンパイルは通るのですが、いくつか疑問点があります

1.captureData.txt の作り方
  シリアルモニタでdを実行後、ダンプデータをメモ帳とにコピペすれば
  よいのでしょうか。
    サンプルデータでは、データの最後にのみ改行コードが
  入っていますが、こちらでコピペすると、一定のデータごとに
  改行コードが入ってしまいます。

2.ソースを追っかけましたが
 予約領域に書き込みがされています。問題ないのでしょうか。
 トラ技スペシャルの記事中で、予約部分は、元々のデータを
 上書きするか、書き込まないように書いてあります。

以上少し疑問に思ったのでコメントを書きました。
  
by 内緒 (2017-04-15 14:29) 

コメントを書く

お名前:
URL:
コメント:
画像認証:
下の画像に表示されている文字を入力してください。

トラックバック 0

この広告は前回の更新から一定期間経過したブログに表示されています。更新すると自動で解除されます。