ArduinoUNOでGroveシリアルカメラ+SDカードシールドを動かす

Arduinoでカメラ使いたいと思って(２年前に)買ったGroveシリアルカメラ+SDカードシールドがようやく動いたのでメモ。原因は簡単なミス。

**SDカードシールドのパワー切り替えスイッチを5Vにすること**(デフォルトは3.3V)。
3.3Vだとカメラの電源に（SDカードも？）供給される電圧が3.3Vになり、真っ黒な写真しか取れない。


あとはほとんどseeedの[チュートリアル](http://wiki.seeedstudio.com/Grove-Serial_Camera_Kit/)のとおりに進めた。
ただし筆者が購入したSDカードシールドがv3.0であり、現在販売されている[v4.0](http://wiki.seeedstudio.com/SD_Card_shield_V4.0/)とは異なる。


##ハードウェア
* arduinoUNO R3
純正品。

* SDカードシールドv3.0

おそらくこれの互換品。右下に3.3Vと5Vの切り替えがある。ボードの裏面にmicroSDカード用のスロットもついていておそらくそれのため？
<img src=https://qiita-image-store.s3.amazonaws.com/0/296108/f9894f12-cc1f-59a0-34cf-8610cd739ea6.png width=400>

* Groveシリアルカメラキット  

<img src=https://github.com/SeeedDocument/Grove-Serial_Camera_Kit/raw/master/img/GSCK_Introduction.jpg width=400>
ひょっとすると現在のものとはモデルが違うのかもしれないが、問題なかった。使用したコードのHistoryを見ても特に変更されていなかった。

###組み立て
1. groveカメラの白い端子をSDシールドの２つある白い端子のうちUARTの方につなぐ（上の画像では左側のRX、TXのある方）。
2. SDカードシールドをArduino本体に接続する。
3. SDカードシールドの5VソケットからD7ソケットへジャンパ線をつなぐ（チュートリアルではボタンモジュールをつなぎ、スイッチ押下で撮影になっている。今回はD7に直結させて常に連続撮影するようにした。）

ちゃんと調べていないが、11、12、13(Megaは50、51、52)、10(Megaでは53)のピンはSDカードが使用するので使わない方がいいっぽい。

##コード
[Seeed-Studioのリポジトリ](https://github.com/Seeed-Studio/Grove_Serial_Camera_Kit/blob/master/SerialCameral_DemoCode_CJ_OV528/SerialCameral_DemoCode_CJ_OV528.ino)から。ソフトシリアルじゃない方。

```c
//  File SerialCamera_DemoCode_CJ-OV528.ino
//  8/8/2013 Jack Shao
//  Demo code for using seeeduino or Arduino board to cature jpg format
//  picture from seeed serial camera and save it into sd card. Push the
//  button to take the a picture .
//  For more details about the product please check http://www.seeedstudio.com/depot/

#include <SPI.h>
#include <arduino.h>
#include <SD.h>

#define PIC_PKT_LEN    128                  //data length of each read, dont set this too big because ram is limited
#define PIC_FMT_VGA    7
#define PIC_FMT_CIF    5
#define PIC_FMT_OCIF   3
#define CAM_ADDR       0
#define CAM_SERIAL     Serial

#define PIC_FMT        PIC_FMT_VGA

File myFile;

const byte cameraAddr = (CAM_ADDR << 5);  // addr
//const int buttonPin = A5;                 // 使わない。
const int buttonPin = 7; //代わりにD7を使う。
unsigned long picTotalLen = 0;            // picture length
int picNameNum = 0;

/*********************************************************************/
void setup()
{
    Serial.begin(115200);
    pinMode(buttonPin, INPUT);    // initialize the pushbutton pin as an input
    Serial.println("Initializing SD card....");
    //pinMode(10,OUTPUT);          // CS pin of SD Card Shield
    pinMode(4,OUTPUT);          // SDカードシールドの動作に必要。
    pinMode(SS, OUTPUT);        //SDカード。使わなくても常にOUTPUT必要らしい。SSは(多くのデバイスでは)10のこと。

    //if (!SD.begin(10)) 
    if (!SD.begin(4)) 
    {
        Serial.print("sd init failed");
        return;
    }
    Serial.println("sd init done.");
    initialize();
}
/*********************************************************************/
void loop()
{
    int n = 0;
    while(1){
        Serial.println("\r\nPress the button to take a picture");
        while (digitalRead(buttonPin) == LOW);      //wait for buttonPin status to HIGH
        if(digitalRead(buttonPin) == HIGH){
            delay(20);                               //Debounce
            if (digitalRead(buttonPin) == HIGH)
            {
                Serial.println("\r\nbegin to take picture");
                delay(200);
                if (n == 0) preCapture();
                Capture();
                GetData();
            }
            Serial.print("\r\nTaking pictures success ,number : ");
            Serial.println(n);
            n++ ;
        }
    }
}
/*********************************************************************/
void clearRxBuf()
{
    while (Serial.available())
    {
        Serial.read();
    }
}
/*********************************************************************/
void sendCmd(char cmd[], int cmd_len)
{
    for (char i = 0; i < cmd_len; i++) Serial.print(cmd[i]);
}
/*********************************************************************/
void initialize()
{
    char cmd[] = {0xaa,0x0d|cameraAddr,0x00,0x00,0x00,0x00} ;
    unsigned char resp[6];

    Serial.setTimeout(500);
    while (1)
    {
        //clearRxBuf();
        sendCmd(cmd,6);
        if (Serial.readBytes((char *)resp, 6) != 6)
        {
            continue;
        }
        if (resp[0] == 0xaa && resp[1] == (0x0e | cameraAddr) && resp[2] == 0x0d && resp[4] == 0 && resp[5] == 0)
        {
            if (Serial.readBytes((char *)resp, 6) != 6) continue;
            if (resp[0] == 0xaa && resp[1] == (0x0d | cameraAddr) && resp[2] == 0 && resp[3] == 0 && resp[4] == 0 && resp[5] == 0) break;
        }
    }
    cmd[1] = 0x0e | cameraAddr;
    cmd[2] = 0x0d;
    sendCmd(cmd, 6);
    Serial.println("\nCamera initialization done.");
}
/*********************************************************************/
void preCapture()
{
    char cmd[] = { 0xaa, 0x01 | cameraAddr, 0x00, 0x07, 0x00, PIC_FMT };
    unsigned char resp[6];

    Serial.setTimeout(100);
    while (1)
    {
        clearRxBuf();
        sendCmd(cmd, 6);
        if (Serial.readBytes((char *)resp, 6) != 6) continue;
        if (resp[0] == 0xaa && resp[1] == (0x0e | cameraAddr) && resp[2] == 0x01 && resp[4] == 0 && resp[5] == 0) break;
    }
}
void Capture()
{
    char cmd[] = { 0xaa, 0x06 | cameraAddr, 0x08, PIC_PKT_LEN & 0xff, (PIC_PKT_LEN>>8) & 0xff ,0};
    unsigned char resp[6];

    Serial.setTimeout(100);
    while (1)
    {
        clearRxBuf();
        sendCmd(cmd, 6);
        if (Serial.readBytes((char *)resp, 6) != 6) continue;
        if (resp[0] == 0xaa && resp[1] == (0x0e | cameraAddr) && resp[2] == 0x06 && resp[4] == 0 && resp[5] == 0) break;
    }
    cmd[1] = 0x05 | cameraAddr;
    cmd[2] = 0;
    cmd[3] = 0;
    cmd[4] = 0;
    cmd[5] = 0;
    while (1)
    {
        clearRxBuf();
        sendCmd(cmd, 6);
        if (Serial.readBytes((char *)resp, 6) != 6) continue;
        if (resp[0] == 0xaa && resp[1] == (0x0e | cameraAddr) && resp[2] == 0x05 && resp[4] == 0 && resp[5] == 0) break;
    }
    cmd[1] = 0x04 | cameraAddr;
    cmd[2] = 0x1;
    while (1)
    {
        clearRxBuf();
        sendCmd(cmd, 6);
        if (Serial.readBytes((char *)resp, 6) != 6) continue;
        if (resp[0] == 0xaa && resp[1] == (0x0e | cameraAddr) && resp[2] == 0x04 && resp[4] == 0 && resp[5] == 0)
        {
            Serial.setTimeout(1000);
            if (Serial.readBytes((char *)resp, 6) != 6)
            {
                continue;
            }
            if (resp[0] == 0xaa && resp[1] == (0x0a | cameraAddr) && resp[2] == 0x01)
            {
                picTotalLen = (resp[3]) | (resp[4] << 8) | (resp[5] << 16);
                Serial.print("picTotalLen:");
                Serial.println(picTotalLen);
                break;
            }
        }
    }

}
/*********************************************************************/
void GetData()
{
    unsigned int pktCnt = (picTotalLen) / (PIC_PKT_LEN - 6);
    if ((picTotalLen % (PIC_PKT_LEN-6)) != 0) pktCnt += 1;

    char cmd[] = { 0xaa, 0x0e | cameraAddr, 0x00, 0x00, 0x00, 0x00 };
    unsigned char pkt[PIC_PKT_LEN];

    char picName[] = "pic00.jpg";
    picName[3] = picNameNum/10 + '0';
    picName[4] = picNameNum%10 + '0';

    if (SD.exists(picName))
    {
        SD.remove(picName);
    }

    myFile = SD.open(picName, FILE_WRITE);
    if(!myFile){
        Serial.println("myFile open fail...");
    }
    else{
        Serial.setTimeout(1000);
        for (unsigned int i = 0; i < pktCnt; i++)
        {
            cmd[4] = i & 0xff;
            cmd[5] = (i >> 8) & 0xff;

            int retry_cnt = 0;
            retry:
            delay(10);
            clearRxBuf();
            sendCmd(cmd, 6);
            uint16_t cnt = Serial.readBytes((char *)pkt, PIC_PKT_LEN);

            unsigned char sum = 0;
            for (int y = 0; y < cnt - 2; y++)
            {
                sum += pkt[y];
            }
            if (sum != pkt[cnt-2])
            {
                if (++retry_cnt < 100) goto retry;
                else break;
            }

            myFile.write((const uint8_t *)&pkt[4], cnt-6);
            //if (cnt != PIC_PKT_LEN) break;
        }
        cmd[4] = 0xf0;
        cmd[5] = 0xf0;
        sendCmd(cmd, 6);
    }
    myFile.close();
    picNameNum ++;
}

```

Arduinoに書き込む。

##動作確認
<img src=https://qiita-image-store.s3.amazonaws.com/0/296108/54226f1d-b7e5-a51f-9b61-6853b5d9a40f.png width=400>
シリアルモニタを起動した画面。
上手くいくとこんな感じになって、

<img src=https://qiita-image-store.s3.amazonaws.com/0/296108/377e8866-4e3d-c93c-5f99-6c96a8a0a7a4.jpeg width=400>
写真が取れる。

<img src=https://qiita-image-store.s3.amazonaws.com/0/296108/4810ab27-605b-6d71-e69a-0d990ab75b8b.jpeg width=400>
手前のディップスイッチが5V側にセットされていることを注意する。

<img src=https://qiita-image-store.s3.amazonaws.com/0/296108/8fe3607d-ed3b-5ad3-edda-9fc81ed58ec7.jpeg width=200>
上手くいかないと常に真っ黒な画像が撮影されていた。
ちなみによく見ると完全な黒ではなく、
<img src=https://qiita-image-store.s3.amazonaws.com/0/296108/53a34486-76d7-e94a-3049-0fe7c5901b2b.jpeg width=200>
ノイズが見える。（わかりやすいようにGimpで補正をかけたもの）

###参考
[ArduinoでSDカードにデータを保存する](https://qiita.com/kat2014new/items/7e8431a783023b455fa3)