●ESP32-CAMモジュール

ESP32-CAM-MBモジュールを使用しました。
使ったモジュールはこちらの物です。
ESP32-CAM カメラ開発ボード(ESP32-CAM-MB)


届いたモジュールは3個の部品でバラバラになっています。


まずは、カメラを接続します、モジュールのカメラ接続部分にあるコネクタのフタを持ち上げます。


そこに、カメラを上向き状態でコネクタの奥まで差し込みます。


フタを指で閉じたらカメラの接続完了です。


パソコンと通信する基盤に取り付けたら組み立て完了です。


▼パソコンとの接続

パソコンとマイクロUSBで接続しますが、100均などで売っている充電専用ケーブルでは接続できません。
そこで、通信もできるケーブルを使用する必要があります。
もし、パソコンに接続してドライバを要求された場合にはCH340のドライバを探してきてインストールします。
私は、秋月電子のCH340チップのページからドライバーソフトをダウンロードしてインストールしました。
秋月電子のCH340チップのページ
その後、正常に接続されているのかをデバイスマネージャで確認します。
windows10ではスタートメニューボタンの横の検索ボックスに dev と入力するとデバイスマネージャを検索することができ、
デバイスマネージャを起動させて、ポート(COM と LTP) の項目にUSB-SERIAL CH340 が追加されているのを確認できたら接続完了です。


▼ArduinoIDEの設定 Arduino IDE のバージョンが 1.*.* の場合 バージョン 2.*.* の場合はこちら


ArduinoIDEを起動させ、メニューのファイル→環境設定を開きます。
追加のボードマネージャのURLの項目に https://dl.espressif.com/dl/package_esp32_index.json を追加して画面を閉じます。
コピー&ペーストのメニューが出てこない為、Control + v のショートカットで張り付けるのがお勧めです。


次に、メニューのツール → ボード:"??????" → ボードマネージャ を開きます。
検索ボックスに esp32 と入力して検索して esp32 を探し出してインストールします。


ボードの種類の設定をします。
メニューの ツール → ESP32 Arduino → ESP32 Wrover Module を選択します。


次に、Partition Schemeの設定をします。
メニューの ツール → Partition Scheme:"??????" → HugeAPP(3MB No OTA/1MB SPIFFS) を選択します。
これで設定は完了しました。
さっそくカメラの動作確認をしましょう。


▼ArduinoIDEの設定 Arduino IDE のバージョンが 2.*.* の場合 バージョン 1.*.* の場合はこちら


ArduinoIDEを起動させ、メニューのファイル→基本設定....を開きます。
追加のボードマネージャのURLの項目に https://dl.espressif.com/dl/package_esp32_index.json を追加して画面を閉じます。


次に、メニューのツール → ボード:"??????" → ボードマネージャ を開きます。
検索ボックスに esp32 と入力して検索して esp32 を探し出してインストールします。


ボードの種類の設定をします。
メニューの ツール → ボード → ESP32 → ESP32 Wrover Module を選択します。


次に、Partition Schemeの設定をします。
メニューの ツール → Partition Scheme → HugeAPP(3MB No OTA/1MB SPIFFS) を選択します。
これで設定は完了しました。
さっそくカメラの動作確認をしましょう。


▼スケッチ例でのカメラの動作確認

さっそくカメラのサンプルを動作させてみるとします。

メニューを開いて ファイル → スケッチ例から ESP32 → Camera → CameraWebServer を開きます。
そのままでは動作しないので、ソースコードの上の方の10行目付近を編集します。

// Select camera model
//#define CAMERA_MODEL_WROVER_KIT // Has PSRAM
//#define CAMERA_MODEL_ESP_EYE // Has PSRAM
//#define CAMERA_MODEL_M5STACK_PSRAM // Has PSRAM
//#define CAMERA_MODEL_M5STACK_V2_PSRAM // M5Camera version B Has PSRAM
//#define CAMERA_MODEL_M5STACK_WIDE // Has PSRAM
//#define CAMERA_MODEL_M5STACK_ESP32CAM // No PSRAM
//#define CAMERA_MODEL_M5STACK_UNITCAM // No PSRAM
#define CAMERA_MODEL_AI_THINKER // Has PSRAM
//#define CAMERA_MODEL_TTGO_T_JOURNAL // No PSRAM

#include "camera_pins.h"

const char* ssid = "あなたのWifiのID";
const char* password = "あなたのWifiのパスワード";
赤文字の部分の、カメラモードのコメントアウトを修正してモードを切り替えます。
自宅のWIFIのIDとパスワードを入力します。


次に、ボードに書き込みます、書き込みボタンを押してしばらくすると書き込み完了のメッセージが表示されます。


シリアルモニタを開いて、速度を115200 bpsを選択し、ボードのリセットボタンを押します。
うまくWifiルーターに接続された場合には、 Camera Ready! Use 'http://192.168.0.119' to connect
のような文字が表示されますので、表示されたアドレスをコピーしてブラウザで開きます。
アドレスは環境によって違いますし毎回変わる可能性もありますので、表示されたアドレスを使ってください。


表示されたアドレスをブラウザで開いた所です。
カメラの画像を表示したりできます。


■画像ファイルをSDカードに保存できるようにする

サンプルをいじって、WIFI機能を無効にし画像ファイルを連番でSDカードに保存できるようにします。
まずは、サンプルの93行目から106行のカメラサーバーの部分をコメントアウトします。

コメントアウトした部分の下に次のプログラムを追加します。
  delay(100);
  SPI.begin(sd_sck, sd_miso, sd_mosi, sd_ss);

  camera_fb_t * fb = NULL;
  fb = esp_camera_fb_get();
  if (!fb && !(fb->len)) {
    Serial.println("Camera capture failed");
    return;
  }
  char filename[10];
  if (SD.begin(sd_ss)) {
    unsigned long i = 0;
    while (1) {
      sprintf(filename, "/%ld.JPG", i++);
      if (!SD.exists(filename)) {
        break;
      }
    }

    File file = SD.open(filename, FILE_WRITE);

    if (file.write(fb->buf, fb->len)) {
      Serial.print(filename);
      Serial.println("  書き込み成功");
    }else{
      Serial.println("  書き込み失敗");
    }
    file.close();

  } else {
    Serial.println("SDカードが開けません");
  }
  esp_camera_fb_return(fb);
次に、先頭部分、#include <WiFi.h> の下に、次のコードを追加します。
#include <SD.h>
#define sd_sck  14
#define sd_mosi 15
#define sd_ss   13
#define sd_miso  2

サンプルの修正後、ESP32-CAMに書き込み、FAT16もしくはFAT32でフォーマットしたSDカードをカードスロットに差してリセットボタンを押すと、カメラの画像がJPEGファイルとして保存されます。
リセットボタンを押すたびにカメラから取得したJPEG画像がSDカードに連番で保存されてゆくため、画質の悪いおもちゃのカメラみたいです。
ファイル数が増えていくと連番を振るのに時間がかかるようになりますので、その辺は改良する必要がありますが....


■画像データにアクセスする

JPEGなどお呼びでない!私は画像データにアクセスしたいんだ!
と言う事で、なんとかしてアクセスする方法を考えます。
最初に各種ヘッダーファイルなどがパソコンのどこにあるのかわからなかったので、
手動でArduinoIDEにESP32-CAMの開発環境をインストールする時に使うファイルを
https://github.com/espressif/arduino-esp32 からダウンロードし、
ダウンロードしたZIPファイルを解凍してtoolsフォルダの中にあるget.exeを動かして、必要なファイルを揃えました。
このファイル群の中を探れば必要な情報が見つかるはずです。

まず、画像サイズとJPEGフォーマットをなんとかしたいということで、秀丸でgrepして定義箇所を探します。
typedef enum {
    PIXFORMAT_RGB565,    // 2BPP/RGB565
    PIXFORMAT_YUV422,    // 2BPP/YUV422
    PIXFORMAT_GRAYSCALE, // 1BPP/GRAYSCALE
    PIXFORMAT_JPEG,      // JPEG/COMPRESSED
    PIXFORMAT_RGB888,    // 3BPP/RGB888
    PIXFORMAT_RAW,       // RAW
    PIXFORMAT_RGB444,    // 3BP2P/RGB444
    PIXFORMAT_RGB555,    // 3BP2P/RGB555
} pixformat_t;

typedef enum {
    FRAMESIZE_96X96,    // 96x96
    FRAMESIZE_QQVGA,    // 160x120
    FRAMESIZE_QCIF,     // 176x144
    FRAMESIZE_HQVGA,    // 240x176
    FRAMESIZE_240X240,  // 240x240
    FRAMESIZE_QVGA,     // 320x240
    FRAMESIZE_CIF,      // 400x296
sensor.h の中に、解像度と画像フォーマットの定義を見つける事ができました。
画像データに直接アクセスするために、FRAMESIZE_96X96 のサイズで PIXFORMAT_RGB565 の画像フォーマットでプログラムを書き換える事にします。

そこで、サンプルプログラム CameraWebServer を魔改造したのが次です。
#include "esp_camera.h"
#define CAMERA_MODEL_AI_THINKER
#include "camera_pins.h"

void setup() {
  Serial.begin(115200);
  Serial.setDebugOutput(true);
  Serial.println();

  camera_config_t config;
  config.ledc_channel = LEDC_CHANNEL_0;
  config.ledc_timer = LEDC_TIMER_0;
  config.pin_d0 = Y2_GPIO_NUM;
  config.pin_d1 = Y3_GPIO_NUM;
  config.pin_d2 = Y4_GPIO_NUM;
  config.pin_d3 = Y5_GPIO_NUM;
  config.pin_d4 = Y6_GPIO_NUM;
  config.pin_d5 = Y7_GPIO_NUM;
  config.pin_d6 = Y8_GPIO_NUM;
  config.pin_d7 = Y9_GPIO_NUM;
  config.pin_xclk = XCLK_GPIO_NUM;
  config.pin_pclk = PCLK_GPIO_NUM;
  config.pin_vsync = VSYNC_GPIO_NUM;
  config.pin_href = HREF_GPIO_NUM;
  config.pin_sscb_sda = SIOD_GPIO_NUM;
  config.pin_sscb_scl = SIOC_GPIO_NUM;
  config.pin_pwdn = PWDN_GPIO_NUM;
  config.pin_reset = RESET_GPIO_NUM;
  config.xclk_freq_hz = 20000000;
  config.pixel_format = PIXFORMAT_RGB565;
  config.frame_size = FRAMESIZE_96X96;
  config.jpeg_quality = 12;
  config.fb_count = 1;

  esp_err_t err = esp_camera_init(&config);
  if (err != ESP_OK) {
    Serial.printf("Camera init failed with error 0x%x", err);
    return;
  }
}

void loop() {
  delay(1000);

  camera_fb_t * fb = NULL;
  fb = esp_camera_fb_get();
  if (!fb && !(fb->len)) {
    Serial.println("Camera capture failed");
    return;
  }
  Serial.println(fb->width);
  Serial.println(fb->height);
  Serial.println(fb->len);

  Serial.println(fb->buf[0]);
  Serial.println("");

  esp_camera_fb_return(fb);
}

とりあえず、取得するデータは最初の1バイト(赤文字部分)にしました。
ESP32-CAMに書き込み、シリアルモニタを開いて動作を見ます。

実行すると幅、高さ、長さ、画像データの順に値が表示されています。
カメラの前で物を動かすと画像データの値が動きます。
まずは、画像データにアクセスすることができたようです。


▼画像の読み込み

取得できる画像フォーマットであるRGB565とは、ビットマップで色を16ビットで表現する方法で、2バイトで一つのピクセルの色を表します。


16進数で表現すると、
赤色のマスクは0xF800
緑色のマスクは0x07E0
青色のマスクは0x001F
になります。
これを使って画像を読み解いて行こうとおもいます。

RGBからグレースケール画像の作成方法については、
簡易的に処理する方法として、色を見た時の目の認識はGの輝度が6割ほどなのでGのみの値を見て、閾値を決めて白黒画像を作成することにします。

気を付けないといけない点として、
ESP32を調べるとx86と同じでリトル・エンディアンでした。
ということは、バイト単位でアクセスすると下位バイト→上位バイトの順に取り出す事になるということです。
typedef struct {
    uint8_t * buf;              /*!< Pointer to the pixel data */
    size_t len;                 /*!< Length of the buffer in bytes */
    size_t width;               /*!< Width of the buffer in pixels */
    size_t height;              /*!< Height of the buffer in pixels */
    pixformat_t format;         /*!< Format of the pixel data */
    struct timeval timestamp;   /*!< Timestamp since boot of the first DMA buffer of the frame */
} camera_fb_t;
画像にアクセスする構造体を見ると、画像バッファであるbufは8ビット変数のポインタであることがわかります。
ということは、16ビット変数のポインタにキャストしてアクセスすれば動作するんじゃないかと思いました。


以上をふまえて画像を読み込んでみます
前回作成したプログラムの void loop() の内容を次のように書き換えて、簡易的に画像をシリアルモニタに表示するようにします。
void loop() {
  delay(1000);
  camera_fb_t * fb = NULL;
  fb = esp_camera_fb_get();
  if (!fb && !(fb->len)) {
    Serial.println("Camera capture failed");
    return;
  }
  Serial.println(fb->width);
  Serial.println(fb->height);
  Serial.println(fb->len);
  Serial.println(fb->buf[0]);
  Serial.println("");
  uint16_t*pos = (uint16_t*)fb->buf;
  bool i = 0;
  for (int x = 0; x < fb->width; x++) {
    for (int y = 0; y < fb->height; y++) {
      if (55<((*pos) & 0x07E0) >> 5) {
        if (i)Serial.print("0");
      } else {
        if (i)Serial.print(" ");
      }
      pos++;
    }
    if (i)Serial.println("");
    i = !i;
  }
  esp_camera_fb_return(fb);
}

このプログラムを走らせる前に、撮影した画像です。


上記プログラムをESP32-CAMに書き込んで、カメラ画像を文字に変換してシリアルモニタに表示させた所です。 とりあえず、画像データを取得できているようです。

プログラムをちょっといじってRGBから白黒の色を作成し、全体の平均を出して平均以上の部分を表示するようにしてみました。
void loop() {
  delay(500);
  static long average;
  camera_fb_t * fb = NULL;
  fb = esp_camera_fb_get();
  if (!fb && !(fb->len)) {
    Serial.println("Camera capture failed");
    return;
  }
  Serial.println(fb->width);
  Serial.println(fb->height);
  Serial.println(fb->len);
  Serial.println(average);
  Serial.println("");
  uint16_t*pos = (uint16_t*)fb->buf;
  bool i = 0;
  long average_pos = 0;

  for (int x = 0; x < fb->width; x++) {
    for (int y = 0; y < fb->height; y++) {
      unsigned int c = ((float)(((*pos) & 0xF800) >> 11) * 0.3)
                       + ((float)(((*pos) & 0x07E0) >> 5) * 0.6)
                       + ((float)(((*pos) & 0x001F)) * 0.1);
      average_pos += c;
      if (average < c) {
        if (i)Serial.print("0");
      } else {
        if (i)Serial.print(" ");
      }

      pos++;
    }
    if (i)Serial.println("");
    i = !i;
  }
  average = (average_pos / (fb->width * fb->height));
  esp_camera_fb_return(fb);
}
プログラムを実行して画像を表示してみました。
こちらがカメラが見ているはずの画像です。


シリアルモニタの出力結果です。。


とりあえず、形は識別できそうですが、なかなかうまくいきません。
まだまだ、ちょっと厳しいです。


■画像データをビットマップで保存できるようにする

取得できる画像データがビットマップ形式ですからヘッダを取り付けてSDカードに保存すればビットマップとして保存できるような気がします。
そこで、次のようにプログラムを改造してビットマップヘッダを手動で作成し、SDカードに保存できるようにしました。
#include "esp_camera.h"
#define CAMERA_MODEL_AI_THINKER
#include "camera_pins.h"
#include <SD.h>
#define sd_sck  14
#define sd_mosi 15
#define sd_ss   13
#define sd_miso  2

byte BITMAPFILEHEADER[] = {'B', 'M' ,//Type ファイルタイプ
                           0x36, 0x48, 0x00, 0x00,//Size ファイルサイズ  96*96*2+54=18486
                           0x00, 0x00, //Reserved1 予約サイズ
                           0x00, 0x00, //Reserved2 予約サイズ
                           0x36, 0x00, 0x00, 0x00 //OffBits ファイル先頭から画像データまでのオフセット
                          };

byte BITMAPINFOHEADER[] = {0x28, 0x00, 0x00, 0x00,//Size この構造体のサイズ
                           0x60, 0x00, 0x00, 0x00,//Width ビットマップの幅
                           0xA0, 0xFF, 0xFF, 0xFF,//Height ビットマップの高さ
                           0x01, 0x00,//Planes ターゲット・デバイスのプレーンの枚数
                           0x10, 0x00,//BitCount 1画素当たりのビット数
                           0x00, 0x00, 0x00, 0x00,//Compression 圧縮方式を指定
                           0x00, 0x00, 0x00, 0x00,//SizeImage ビットマップデータのサイズ (バイト数)
                           0x00, 0x00, 0x00, 0x00,//XPelsPerMeter 水平解像度
                           0x00, 0x00, 0x00, 0x00,//YPelsPerMeter 垂直解像度
                           0x00, 0x00, 0x00, 0x00,//ClrUsed カラーテーブルのエントリ数
                           0x00, 0x00, 0x00, 0x00//ClrImportant 表示するのに必要な色数
                          };

void setup() {
  Serial.begin(115200);
  Serial.setDebugOutput(true);
  Serial.println();
  pinMode(4, OUTPUT);    //LEDライト
  
  camera_config_t config;
  config.ledc_channel = LEDC_CHANNEL_0;
  config.ledc_timer = LEDC_TIMER_0;
  config.pin_d0 = Y2_GPIO_NUM;
  config.pin_d1 = Y3_GPIO_NUM;
  config.pin_d2 = Y4_GPIO_NUM;
  config.pin_d3 = Y5_GPIO_NUM;
  config.pin_d4 = Y6_GPIO_NUM;
  config.pin_d5 = Y7_GPIO_NUM;
  config.pin_d6 = Y8_GPIO_NUM;
  config.pin_d7 = Y9_GPIO_NUM;
  config.pin_xclk = XCLK_GPIO_NUM;
  config.pin_pclk = PCLK_GPIO_NUM;
  config.pin_vsync = VSYNC_GPIO_NUM;
  config.pin_href = HREF_GPIO_NUM;
  config.pin_sscb_sda = SIOD_GPIO_NUM;
  config.pin_sscb_scl = SIOC_GPIO_NUM;
  config.pin_pwdn = PWDN_GPIO_NUM;
  config.pin_reset = RESET_GPIO_NUM;
  config.xclk_freq_hz = 20000000;
  config.pixel_format = PIXFORMAT_RGB565;
  config.frame_size = FRAMESIZE_96X96;
  config.jpeg_quality = 12;
  config.fb_count = 1;

  esp_err_t err = esp_camera_init(&config);
  if (err != ESP_OK) {
    Serial.printf("Camera init failed with error 0x%x", err);
    return;
  }
  
digitalWrite(4, HIGH);  //LEDライト ON
  delay(100);
  SPI.begin(sd_sck, sd_miso, sd_mosi, sd_ss);

  camera_fb_t * fb = NULL;
  fb = esp_camera_fb_get();
  if (!fb && !(fb->len)) {
    Serial.println("Camera capture failed");
    return;
  }
  if (SD.begin(sd_ss)) {
    File file = SD.open("/TEST.BMP", FILE_WRITE);

    file.write(BITMAPFILEHEADER, sizeof(BITMAPFILEHEADER));
    file.write(BITMAPINFOHEADER, sizeof(BITMAPINFOHEADER));
    if (file.write(fb->buf, fb->len)) {
      Serial.println("  書き込み成功");
    } else {
      Serial.println("  書き込み失敗");
    }
    file.close();

  } else {
    Serial.println("SDカードが開けません");
  }
  esp_camera_fb_return(fb);
  digitalWrite(4, LOW);   //LEDライト OFF
}
void loop() {
}
赤文字部分がビットマップヘッダを手動で作成している部分であり、SDカードに書き込んでいる部分でもあります。
SDカードに書き込まれた画像とカメラが見ているであろう画像を比較してみるとこのようになりました。

取得した画像が鮮やかな世界になっています。
色の扱いがまだ間違っているようです。

ひょっとしたら色を表現する16ビットの値の前後の部分が反転しているのじゃないかと思い、SDカードに保存する部分を次のように書き換えてみました。
  if (SD.begin(sd_ss)) {
    File file = SD.open("/TEST.BMP", FILE_WRITE);

    file.write(BITMAPFILEHEADER, sizeof(BITMAPFILEHEADER));
    file.write(BITMAPINFOHEADER, sizeof(BITMAPINFOHEADER));

    for (unsigned int i = 0; i < fb->len; i=i+2) {
      if (!file.write(&fb->buf[i + 1], 1)) {
        Serial.println("  書き込み失敗");
        break;
      }
      if (!file.write(&fb->buf[i], 1)) {
        Serial.println("  書き込み失敗");
        break;
      }
    }
    Serial.println("  書き込み終了");
    file.close();

  } else {
    Serial.println("SDカードが開けません");
  }
このプログラムで画像をSDカードに保存してみました。

なんだか色合いが違いますが、かなり現実に近くなりました。


▼ SD.h のコンパイル時のエラーが出た場合



エラーを見ると、#error Architecture or board not supported.と出ており標準ライブラリのSD.hではサポートされていないようです。
といっても未使用のライブラリを選択する方法がわかりません。
しょうがないので使用済と出ている標準ライブラリのSDフォルダをデスクトップなどの他の場所に移動させると、未使用のライブラリが使用されて問題なくコンパイルできました。
方法としては正しくないと思いますが、どうなんでしょうかね。コンパイルが終わったら元に戻しておくのをお勧めします。


■ ビットマップデータをパソコンにシリアル通信で送信する

いままでのSDカードにビットマップ画像を保存するプログラムをちょっと改造します。
0x00がシリアル通信でうまく送れなかったため、値を文字に変換して送信しています。
ファイルの終端を表すために0x0Aを2回、\n\nとして出力することにしました。
#include "esp_camera.h"
#define CAMERA_MODEL_AI_THINKER
#include "camera_pins.h"

byte BITMAPFILEHEADER[] = {
  'B', 'M',                //Type ファイルタイプ
  0x36, 0x48, 0x00, 0x00,  //Size ファイルサイズ  96*96*2+54=18486
  0x00, 0x00,              //Reserved1 予約サイズ
  0x00, 0x00,              //Reserved2 予約サイズ
  0x36, 0x00, 0x00, 0x00   //OffBits ファイル先頭から画像データまでのオフセット
};

byte BITMAPINFOHEADER[] = {
  0x28, 0x00, 0x00, 0x00,  //Size この構造体のサイズ
  0x60, 0x00, 0x00, 0x00,  //Width ビットマップの幅
  0xA0, 0xFF, 0xFF, 0xFF,  //Height ビットマップの高さ
  0x01, 0x00,              //Planes ターゲット・デバイスのプレーンの枚数
  0x10, 0x00,              //BitCount 1画素当たりのビット数
  0x00, 0x00, 0x00, 0x00,  //Compression 圧縮方式を指定
  0x00, 0x00, 0x00, 0x00,  //SizeImage ビットマップデータのサイズ (バイト数)
  0x00, 0x00, 0x00, 0x00,  //XPelsPerMeter 水平解像度
  0x00, 0x00, 0x00, 0x00,  //YPelsPerMeter 垂直解像度
  0x00, 0x00, 0x00, 0x00,  //ClrUsed カラーテーブルのエントリ数
  0x00, 0x00, 0x00, 0x00   //ClrImportant 表示するのに必要な色数
};

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

  pinMode(4, OUTPUT);  //LEDライト

  camera_config_t config;
  config.ledc_channel = LEDC_CHANNEL_0;
  config.ledc_timer = LEDC_TIMER_0;
  config.pin_d0 = Y2_GPIO_NUM;
  config.pin_d1 = Y3_GPIO_NUM;
  config.pin_d2 = Y4_GPIO_NUM;
  config.pin_d3 = Y5_GPIO_NUM;
  config.pin_d4 = Y6_GPIO_NUM;
  config.pin_d5 = Y7_GPIO_NUM;
  config.pin_d6 = Y8_GPIO_NUM;
  config.pin_d7 = Y9_GPIO_NUM;
  config.pin_xclk = XCLK_GPIO_NUM;
  config.pin_pclk = PCLK_GPIO_NUM;
  config.pin_vsync = VSYNC_GPIO_NUM;
  config.pin_href = HREF_GPIO_NUM;
  config.pin_sscb_sda = SIOD_GPIO_NUM;
  config.pin_sscb_scl = SIOC_GPIO_NUM;
  config.pin_pwdn = PWDN_GPIO_NUM;
  config.pin_reset = RESET_GPIO_NUM;
  config.xclk_freq_hz = 20000000;
  config.pixel_format = PIXFORMAT_RGB565;
  config.frame_size = FRAMESIZE_96X96;
  config.jpeg_quality = 12;
  config.fb_count = 1;

  esp_err_t err = esp_camera_init(&config);
  if (err != ESP_OK) {
    Serial.printf("Camera init failed with error 0x%x", err);
    return;
  }
  sensor_t *sensor;
  sensor = esp_camera_sensor_get();
  //sensor->set_colorbar(sensor, 1);
  delay(100);
}

char buf[3];
void loop() {
  //delay(5000);
  digitalWrite(4, HIGH);  //LEDライト ON LEDを点滅させないと動作しているのか不明のため
  delay(1);
  digitalWrite(4, LOW);  //LEDライト OFF
  camera_fb_t* fb = NULL;
  fb = esp_camera_fb_get();
  if (!fb && !(fb->len)) {
    Serial.println("Camera capture failed");
    return;
  }

  //Serial.write(BITMAPFILEHEADER, sizeof(BITMAPFILEHEADER));
  for (int i = 0; i < sizeof(BITMAPFILEHEADER); i++) {
    sprintf(buf, "%02x", (byte)BITMAPFILEHEADER[i]);
    Serial.print(buf);
  }

  //Serial.write(BITMAPINFOHEADER, sizeof(BITMAPINFOHEADER));
  for (int i = 0; i < sizeof(BITMAPINFOHEADER); i++) {
    sprintf(buf, "%02x", (byte)BITMAPINFOHEADER[i]);
    Serial.print(buf);
  }

  for (unsigned int i = 0; i < fb->len; i = i + 2) {
    //Serial.write(&fb->buf[i + 1],1);
    sprintf(buf, "%02x", (byte)fb->buf[i + 1]);
    Serial.print(buf);
    //Serial.write(&fb->buf[i], 1);
    sprintf(buf, "%02x", (byte)fb->buf[i]);
    Serial.print(buf);
  }

  Serial.print("\n\n");//0x0A 0x0A

  esp_camera_fb_return(fb);
  //digitalWrite(4, LOW);  //LEDライト OFF
}


▼ パソコンに画像を保存するプログラムを作成

シリアル通信で送られてきたデータをそのままファイルに変換して保存します。
ファイルは受信した連番で名前が振られ、ビットマップ画像ですのでペイントなどで開く事もできます。
Win32で作成していますのでWindows限定になります。
VisualC++にてWin32コンソールアプリを選択し、次のコードをコピーして作成します。
だいぶ古い開発環境のVisualC++6.0で作成しましたので新しいVisualCだと警告などが出るかもしれません。
必要なヘッダーファイルは こちら ( RS232c.h ) で作成したものを使いました。
#include <stdio.h>
#include <windows.h>
#include "RS232c.h"

FILE * fp;
char buf[3];
char filename[10];
int num;

int main() {
  RS232c rs;
  rs.Connect("COM3", 115200);

  while (1) {
    if (rs.Read(buf, 2)) {
      if (buf[0] == 0x0A || fp == 0) {

        if (fp)fclose(fp);
        sprintf(filename, "%d.bmp", num++);
        fp = fopen(filename, "wb");
        printf("%s\n", filename);
      } else {
        if (buf[0] != 0) {
          byte b = (byte)strtol(buf, NULL, 16);
          unsigned int WriteLen = 1;
          if (WriteLen != fwrite(&b, sizeof(byte), WriteLen, fp)) {
            printf("書き込みエラー%dバイト書き込まれました", WriteLen);
          }
        }
      }
    }
  }
  return 0;
}
受信するポートは決め打ちでプログラムに書き込んでいます。
赤文字部分を使用しているポート番号にあわせて書き換えてください。
使用しているポート番号を調べるにはパソコンのスタートボタンの横にある【ここに入力して検索】のところに dev と入力するとデバイスマネージャが見つかります。
デバイスマネージャのポート(COMとLPT)の項目を開くと確認することができます。
他のアプリケーション ArduinoIDEなどで同一のポートを開いていると動きません、閉じてから試してみてください。


▼ パソコンに受信と同時に画像を表示するプログラムを作成

かなり前に作成したビットマップビュアーを改造して表示するようにします。
メモリに直接読み込んで処理するのが理想的ですが、改造が大掛かりになり面倒なので二つのファイルを作成し交互に書き込み→ビットマップビューアで表示を繰り返します。
RS232Cでの通信は遅くてプログラムが固まった状態に見えて操作が遅延しますので、プログラム内で別スレッドを作成し無限ループさせて送受信します。
スレッドの作成方法はこちらです、なんというかプログラム内で同時に実行される個所が増えるような感じです。

作成して追加した主な部分はこのようになります。
test.cpp
830:	#define COMPORT "COM3"
831:	#define COMSPEED 115200
832:	char *filename[]={"a.bmp","b.bmp"};
833:	
834:	void Thread(char*str){
835:		int flag - 0;
836:		FILE * fp=0;
837:		RS232c rs;
838:		char buf[3];
839:		rs.Connect(COMPORT, COMSPEED);
840:		while(true){
841:			if (rs.Read(buf, 2)) {
842:			  if (buf[0] == 0x0A || fp == 0) {
843:				if (fp){
844:				  fclose(fp);
845:				  //保存したファイルのロード
846:				  load(filename[flag]);	
847:				  //ウィンドウの再描画
848:				  InvalidateRect(hDlg, NULL,1);
849:				  UpdateWindow(hDlg);
850:				}
851:				if(flag) flag=0; else flag=1;
852:				fp = fopen(filename[flag], "wb");
853:			  } else {
854:				if (buf[0] != 0) {
855:				  byte b = (byte)strtol(buf, NULL, 16);
856:				  unsigned int WriteLen = 1;
857:				  if (WriteLen != fwrite(&b, sizeof(byte), WriteLen, fp)) {
858:					printf("書き込みエラー%dバイト書き込まれました", WriteLen);
859:				  }
860:				}
861:			  }
862:			}
863:		}
864:	}
test.cpp内の830行目付近で、開けるポート番号とスピードを決め打ちしています。
832行目で二つのファイル名を作成し、851行目でファイルを切り替えます。


動作させるとこのような画面が表示され、画像を受信するたびに画面が更新されます。
VisualC++6.0で作成したソースコードとプロジェクトは ソース VisualC++6.0 esp32cam_1.zip こちらからダウンロードできます。

その後、SSDのパソコンを使っていてファイルに保存して表示するのは気分が悪いのでメモリーから描画するようにしました。
しかし、画像のファイル換算サイズ32KB以上は対応しておりません、大きなデータを受け取ると落ちます。
大きな画像を扱うのなら、test.cpp の850行付近のバッファサイズを大きくしてください。

#define COMPORT "COM3"
#define COMSPEED 115200
unsigned char buff[32768];

プロジェクトは ソース VisualC++6.0 esp32cam_2.zip こちらからダウンロードできます。
手持ちの開発環境に合わせてコンバートしてください。


■色調の確認

変な色の画像が出てきた時に、カメラの設定がおかしいのか、それとも自作プログラムがおかしいのか確認する必要があります。
そこで、センサーからカラーバーを出して確認してみます。

sensor_t *sensor;
sensor = esp_camera_sensor_get();
sensor->set_colorbar(sensor, 1);


configの後に
このように追加します。
そうすると、センサーからカラーバーが出力されてデバッグがはかどります。


サンプルプログラムに入れてみた所です、このように表示されました。


現在作成中です。
時間ができたら更新します。


▲トップページ > マイコンなど