M5Cameraのサンプルプログラムをビルドする(2020.02版 + 2021.05版追記)

# はじめに

[M5Stack社](https://m5stack.com/)から発売されているM5Cameraは、ESP32 WROVERにカメラ(OVA2640)とセンサーを搭載した小型のWiFiカメラで、お値段も2000円以下でお手頃です。日本では[スイッチサイエンスさん](https://www.switch-science.com/catalog/5207/)などから購入できます。技適も取得しています。（一部技適がないタイプもあるので注意。スイッチサイエンスさんのは技適あり）

長時間利用ではオーバーヒートするという課題もありますが、ちょっとした用途では便利なモジュールです。

M5Cameraは最初からWeb(HTTP)からカメラ画像を取得するプログラムが書き込まれているので、そのままビルドなしに使えます。

一方、自分でプログラムを書こうと思うと、~~開発環境がArduino IDEに対応しておらず~~（※）、ビルドが大変なのが課題です。メンテナンスやドキュメントも十分ではないようで、今回、いろいろハマった点をここで共有します。

＜追記＞
※ Arduino IDEで使う方法もあるとのこと。
- 「M5Cameraでスマホから操作できるWebカメラを作る（MacOS編）」 : https://qiita.com/nakazawaken1/items/57ea26d6981ff25912c9
- 「M5cameraでWebカメラを作る」 : https://ambidata.io/samples/m5stack/m5camera/


今回の対象は、

- https://github.com/m5stack/m5stack-cam-psram

の、Masterの以下のコミットまでを対象にしています。

```
Latest commit 1c0504d on 12 Aug 2019
```

＜2021/5/15 追記＞
久しぶりにビルドしたところ以下の変更点があります。ご注意ください。

- リポジトリ名が`m5stack-cam-psram`から`m5stack-cam`になり、M5Camera Xにも対応
- カメラのデフォルトが`M5-Camera B`から`M5-Camera X`になっているので、変更する必要あり
- 起動時にカメラが検出できない旨のエラーは発生しなくなっていますので、対応不要です。ただし、main.cのipアドレスの型定義修正はなおってないので修正必要です。また、`make menuconfig`の変更も依然必要です。

# ESP-IDFの準備

ソースコード一式はこちらから取得します。

- https://github.com/m5stack/m5stack-cam-psram/tree/master

このREADME.mdにある手順に沿ってすすめます。

まずは、ESP-IDFをインストールする必要があります。Arudino IDEでESP32を使用できるようにしている場合、既にESP-IDFはインストールされていると思いますが、コンソールから各コマンドを使用することもあり、それとは別にインストールしました。私の環境はMacのため、以降の説明はMac環境のターミナルを使用するイメージとなっています。

# make menuconfig

次に、`make menuconfig`を実行します。どこで実行するかがREADME.mdには書いていません。このリポジトリには複数のプロジェクトが含まれているのですが、購入時に入っているものは`wifi_ap`なのでそのディレクトリ内に移動します。

- https://github.com/m5stack/m5stack-cam-psram/tree/master/wifi/wifi_ap

```console
$ cd m5stack-cam-psram/wifi/wifi_ap/
```

ESP-IDFの環境設定を有効にしておきます。

```console
. $HOME/esp/esp-idf/export.sh
```

`make menuconfig`を実行します。

```console
$ make menuconfig
```
ここで、README.mdでは、カメラの選択とpsramの有効化をするように書かれているのです、今のリポジトリは既にカメラがM5-Camera B(スイッチサイエンスで買えるもの)でpsramが有効になっており、特に変更する必要はありませんでした。

README.mdには記載ないですが、書き込みの際のデバイス名が、Macの場合は`/dev/tty.SLAB_USBtoUART`なので、こちらの設定を変更しておきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51272/68ee42d5-e54e-9f4b-3abb-1f06a84f90d4.png)

ここで

```console
$ make
```

でうまくいけばよいのですが、残念ながら現実は残酷でした。


```
CC build/esp32-camera/driver/twi.o
/Users/XXX/m5stack-cam-psram/wifi/wifi_ap/components/esp32-camera/driver/twi.c: In function 'pinMode':
/Users/XXX/m5stack-cam-psram/wifi/wifi_ap/components/esp32-camera/driver/twi.c:61:24: error: 'rtc_gpio_desc' undeclared (first use in this function); did you mean 'rtc_io_desc'?
     uint32_t rtc_reg = rtc_gpio_desc[pin].reg;
                        ^~~~~~~~~~~~~
                        rtc_io_desc
/Users/XXX/m5stack-cam-psram/wifi/wifi_ap/components/esp32-camera/driver/twi.c:61:24: note: each undeclared identifier is reported only once for each function it appears in
make[1]: *** [driver/twi.o] Error 1
make: *** [component-esp32-camera-build] Error 2
```

というエラーが発生します。

以下にあるように

- https://github.com/maximkulkin/esp32-homekit-camera/issues/57

`make menuconfig`で、`rtc_gpio_desc`配列のサポートを有効にしておく必要があります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51272/475d7cc1-ece4-0fd5-b858-57e04ae757ab.png)

これでビルドできるかと思いきや、

```
/Users/XXX/m5stack-cam-psram/wifi/wifi_ap/main/main.c:232:17: error: incompatible types when assigning to type 'ip4_addr_t' {aka 'struct ip4_addr'} from type 'esp_ip4_addr_t' {aka 'struct esp_ip4_addr'}
       s_ip_addr = event->event_info.got_ip.ip_info.ip;
                 ^
```

というエラーが次に発生します。

- https://github.com/m5stack/m5stack-cam-psram/issues/23

ipアドレスの型定義が変わった模様。main.cの`ip4_addr_t`を`esp_ip4_addr_t`に変更します。

```c
static ip4_addr_t s_ip_addr;
　　　　　↓
static esp_ip4_addr_t s_ip_addr;
```

```c
  s_ip_addr = *(ip4_addr_t*)&addr;
　　　　　↓
  s_ip_addr = *(esp_ip4_addr_t*)&addr;
```

さて、これでコンパイルは通るようになるのですが、まだまだ問題が発生します。

次に、`make flash`でM5Cameraにイメージを書き込むのですが、

```
esptool write_flash: error: argument <address> <filename>: Detected overlap at address: 0x8000 for file: /Users/XXX/m5stack-cam-psram/wifi/wifi_ap/build/partitions_singleapp.bin
```

とエラーになり書き込みできません。

- https://github.com/espressif/esp-idf/issues/3013


`CONFIG_PARTITION_TABLE_OFFSET`を8000から10000にしておく必要があります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51272/28ad0ddf-4d43-8034-247b-366c433f024e.png)

これで書き込みはできるようになります。これで動くかと思いきや、Webでアクセスしても画像が表示されません。

ここで、`make monitor`を使ってデバッグ情報を表示します。

```
I (1666) spiram: Reserving pool of 32K of internal memory for DMA/internal allocations
E (1716) ledc: requested frequency and duty resolution can not be achieved, try reducing freq_hz or duty_resolution. div_param=0
E (1716) camera_xclk: ledc_timer_config failed, rc=ffffffff
I (1726) sccb: pin_sda 22 pin_scl 23

I (1726) gpio: GPIO[0]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:0 
I (1756) gpio: GPIO[15]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:0 
E (1806) camera: Detected camera not supported.
E (1806) camera: Camera probe failed with error 0x20004
E (1806) camera: Camera Init Failed
```

ということで、起動時にカメラが検出できない旨のエラーが発生しています。

これについての詳細は以下に記載があります。

- https://github.com/espressif/esp32-camera/issues/66

対応としては、

```
wifi_ap/components/esp32-camera/driver/xclk.c
```

の以下の関数を修正する必要があります。

```c
esp_err_t camera_enable_out_clock(camera_config_t* config)
{
    periph_module_enable(PERIPH_LEDC_MODULE);

    ledc_timer_config_t timer_conf;
    timer_conf.duty_resolution = 2;
    timer_conf.freq_hz = config->xclk_freq_hz;
    timer_conf.speed_mode = LEDC_HIGH_SPEED_MODE;
    timer_conf.timer_num = config->ledc_timer;
    esp_err_t err = ledc_timer_config(&timer_conf);
    if (err != ESP_OK) {
        ESP_LOGE(TAG, "ledc_timer_config failed, rc=%x", err);
        return err;
    }
```

この関数について、まずは、以下の設定を追加します。

```c
timer_conf.clk_cfg = LEDC_USE_APB_CLK;
```

また、以下の処理を呼び出す場所を、関数の最初から関数の最後に移動します。

```c
periph_module_enable(PERIPH_LEDC_MODULE);
```

この２つの処理を対応した結果が、以下となります。

```c
esp_err_t camera_enable_out_clock(camera_config_t* config)
{
    ledc_timer_config_t timer_conf;
    timer_conf.duty_resolution = 2;
    timer_conf.freq_hz = config->xclk_freq_hz;
    timer_conf.speed_mode = LEDC_HIGH_SPEED_MODE;
    timer_conf.timer_num = config->ledc_timer;
    timer_conf.clk_cfg = LEDC_USE_APB_CLK;
    esp_err_t err = ledc_timer_config(&timer_conf);
    if (err != ESP_OK) {
        ESP_LOGE(TAG, "ledc_timer_config failed, rc=%x", err);
        return err;
    }

    ledc_channel_config_t ch_conf;
    ch_conf.gpio_num = config->pin_xclk;
    ch_conf.speed_mode = LEDC_HIGH_SPEED_MODE;
    ch_conf.channel = config->ledc_channel;
    ch_conf.intr_type = LEDC_INTR_DISABLE;
    ch_conf.timer_sel = config->ledc_timer;
    ch_conf.duty = 2;
    ch_conf.hpoint = 0;
    err = ledc_channel_config(&ch_conf);
    if (err != ESP_OK) {
        ESP_LOGE(TAG, "ledc_channel_config failed, rc=%x", err);
        return err;
    }
    periph_module_enable(PERIPH_LEDC_MODULE);
    return ESP_OK;
}
```

これでようやく動作するイメージが作成できます。

これらの内容は後ほどプルリクしておく予定なので、最新では治ってるかもしれませんが、情報共有のためにここで残しておきます。