工作と競馬2

電子工作、プログラミング、木工といった工作の記録記事、競馬に関する考察記事を掲載するブログ

ESP32-WROVER-B/E(モジュール単体)へのプログラム書き込み失敗に対処する

概要

ESP32-WROVER-B/Eへのプログラム書き込みがうまくいかなかったため、原因を見つけて対処した。



背景と目的

以前、ESP32-WROVER-Eの開発ボードを使ってカメラを動かすことができた。そこで、次はESP32-WROVERの単体を使ってカメラを動かす回路を作ろうとしていたのだが、いつも通りプログラム書き込み用のシリアル通信ラインを接続して書き込もうとしたところ、うまくいかなかった。そこで、原因を探り書き込めるように対処する。



詳細

1. 環境/使用アイテム


2. 状況整理

まず、書き込みに関与するEN端子、IO0端子については、ダウンロードモード(ESP-WROVERの仕様書でいうところのBooting Mode=Download Boot)に入れるように正しく配線されている。シリアルについては、今までESP-WROOM-32のモジュール単体でうまくいっていた方法と同様、USBシリアル変換アダプターの端子と直結している。

この状態で、書き込みを行おうとするとArduino IDE上では以下のような表示になって書き込みができない。つまり、シリアルがうまく通信できていないらしい。

esptool.py v3.0-dev
Serial port COM5
Connecting........_____....._____....._____....._____....._____....._____....._____

A fatal error occurred: Failed to connect to ESP32: Invalid head of packet (0x00)
A fatal error occurred: Failed to connect to ESP32: Invalid head of packet (0x00)


3. 対処方法(いきなり結論)

ESP32-WROVER-B/EのTXD0端子に3.3Vラインへのプルアップ抵抗1kΩをつける

ことで、書き込みがうまくいく。


4. 調査メモ

3.1 調査

  • ESP32-WROVER-E開発ボード
  • ESP-WROOM-32モジュール
  • ESP32-WROVER-B/Eモジュール単体

の間で、シリアル通信ライン上の信号に違いがないかオシロスコープで、TXD0、RXD0端子を観測した。(ESP32-WROVER-Eについても調べたが、Bと同様だったのでBのみ載せる)

  • 赤: TXD0端子
  • 青: RXD0端子
  • TXD0がLOWに落ちたところでキャプチャ(t=0)

3.2.1 ESP32-WROVER-E開発ボード

TXD0、RXD0とも通常3.3Vで、書き込み中に信号に応じてLOWに落ちる動き。

f:id:dekuo-03:20210418144945p:plain

3.2.2 ESP-WROOM-32モジュール単体

手持ちのESP-WROOM-32モジュール単体でも、ESP32-WROVER-E開発ボードと同様。(書き込んだプログラムが異なるので波形は違う)

f:id:dekuo-03:20210418144954p:plain

3.2.3 ESP32-WROVER-Bモジュール単体

TXD0の電圧が1.2V程度となっており、明らかに低い。(※トリガレベルより低くて引っかからないので波形が描けていない)ここで、

  • TXD0はUSBシリアル変換アダプターのRX端子を繋がなければ、HIGH。内部でプルアップされている様子。
  • TXD0の接続相手であるUSBシリアル変換アダプターのRX端子は、TXD0を繋がない状態ではLOW。

だから、TXD0がUSBシリアルのRX端子を引っ張り上げ切れていないようだ。

f:id:dekuo-03:20210418145728p:plain

3.2 TXD0を無理やりプルアップしてみる

TXD0と3.3Vとの間に、1kΩのプルアップ抵抗を入れてみたところ、完全にHIGHになり切れていないが、電圧は少し上がった。この状態で、書き込みをやってみたところ、うまくいった。また、書き込んだプログラムのシリアル出力は、ArduinoIDEのシリアルモニターで見ることができるので通信はできてはいる。つまり、抵抗の付加は効果があったといえる。 とはいうものの、RXのラインがHIGH/LOWでしっかり振れているとは全く言えない状態なので、この方法だと不安ではある。他に方法がないか、後々探ってみたい。

f:id:dekuo-03:20210418150151p:plain

なお、さらに低抵抗(200Ωくらい)を入れてはみたが、電圧としてはあまり変わらないので1kΩでよさそう。



まとめと今後の課題

ESP32-WROVER-B/E(モジュール単体)へのプログラム書き込み失敗に対処し、書き込みできるようになった。ただ、波形が開発ボードとは明らかに違うので、本当はもっと違うやり方があると思う。


Pythonのjsonschemaを試しに使う

Pythonのjsonschemaパッケージを使ってみた。


環境


インストール

pip install jsonschema


試用

汎用的に使える基底クラスを作ってみた。

import json
import jsonschema
import typing

class DefinedJson:

    _SCHEMA = {}

    def __init__(self):
        self._schema = self._SCHEMA

    @property
    def schema(self):
        return self._schema

    @schema.setter
    def schema(self, x: dict):
        self._schema = x

    def read_schema(self, filepath: str):
        """
        filepath: スキーマ定義ファイルパス
        """
        try:
            with open(filepath) as fo:
                self.schema = json.loads(fo)
            return True
        except:
            return False

    def validate(self, data: dict, on_invalid: (callable, typing.Any)=None):
        """
        Parameters
        --------------
        data: 検査対象
        on_invalid: 不正の場合のユーザー定義処理(func(message: str, args: any), args)
            message: エラーメッセージ
            args: ユーザー引数

        Returns
        --------------
        tf: boolean
            成否
        """
        try:
            jsonschema.validate(data, self._schema)
            return True
        except jsonschema.ValidationError as e:
            if on_invalid is not None:
                on_invalid[0](e.message, on_invalid[1])
            return False

スクリプト

サンプルのクラスを作成して、呼び出してみた。

class Sample(DefinedJson):

    _SCHEMA = {
        "required": [
            "name",
            "age"
        ],
        "type": "object",
        "properties": {
            "name": {
                "type": "string"
            },
            "age": {
                "type": "integer",
                "minimum": 0,
                "maximum": 100
                
            },
            "weight": {
                "type": "number",
                "minimum": 0
            },
            "height": {
                "type": "number",
                "minimum": 0
            },
            "country": {
                "type": "string",
                "enum": ["Japan", "America", "Others"]
            }
        }
    }

    def __init__(self):
        super().__init__()


if __name__ == "__main__":

    dj = Sample()

    data = {
        "name": "test",
        "age": 8,
        "weight": 6.7,
        "country": "China"
    }

    def on_invalid(msg, args):
        print("on_invalid", msg, args)

    print(dj.validate(data, (on_invalid, None)))

実行結果は以下。エラーを検出できている。

on_invalid 'China' is not one of ['Japan', 'America', 'Others'] None
False



まとめ

JSONスキーマの仕様を全部把握できていないが、とりあえずすぐに使う必要がありそうなものは試せた。


水田の水位をリモートで監視するシステムを作る(11) ソーラー発電対応(2)

概要

ソーラーパネルと充電回路、リチウムイオン電池をシステムに組み込み、動くことを確認した。



背景と目的

前回、ソーラーモジュールを防水施工したパネルを作成したので、今回はパネルをシステムに組み込む。



詳細

1. 構想

現行のリモート水位センサシステムに対して、

  • 乾電池を、リチウムイオン電池と充電回路に変更する
  • ソーラーモジュールの出力を充電回路に接続する
  • ソーラーモジュールは、本体を支持するフレームに取り付ける

としたい。

f:id:dekuo-03:20210404003129j:plain


2. フレームへの固定

2.1 固定金具の作成

固定にあたり、ホームセンターで売っていた金折れとアングルを使用して、以下のような金具を作成した。屋外で使用するので、耐食性を考慮してステンレス製にしたいが、高かったのでメッキ処理されたものを選んだ。

f:id:dekuo-03:20210404003456j:plain

2.2 固定

まず、フレーム上部の塩ビ管にネジ穴を空け、金具をタッピングネジで固定。

f:id:dekuo-03:20210404003429j:plain

ソーラーパネルをつけて完了。

f:id:dekuo-03:20210404003440j:plain


3. リチウムイオン電池と充電回路

3.1 充電回路の作成

以前作成したものと全く同じ材料を使って作る。

f:id:dekuo-03:20210411125527p:plain ※2021/04/11 間違いがあったので差し替えた

今回は、基板に電池も一緒に取り付けた。

f:id:dekuo-03:20210404002449j:plain

3.2 筐体への取り付け

現状は、電池ボックスが取り付けられている。

dekuo-03.hatenablog.jp

この電池ボックスを取り払って、作成した充電回路を取り付ける。これで、システムへの組み込みは完成。

f:id:dekuo-03:20210404002105j:plain


4. 動作テスト

ひとまず、充電できること、水位センサシステムが動作することを確認できた。田植えまで、約1か月あるので、屋外でしばらくテストを継続したいと思う。



まとめと今後の課題

リモート水位センサシステムのソーラー発電対応ができつつある。5月の現場導入に向けて、動作テストを進めたい。


水田の水位をリモートで監視するシステムを作る(10) ソーラー発電対応(1)

概要

リモート水位センサシステムをソーラー発電対応させるため、ソーラーモジュールの調達と防水加工を行った。



背景と目的

一昨年にから稼働しているリモート水位センサシステムの電源は、今まで電池を使用してきたのだが、いつかソーラー発電を導入したいと考えていた。今年は3シーズン目で、いよいよソーラー発電を導入するための作業時間が確保できたので、取り組むことにした。



詳細

1. 構想と設計

まず、屋外使用可能で、価格、発電容量の条件に見合うソーラーパネルを探したのだが、なかなか見つからなかった。結局、以下のソーラーモジュールを選定し、自前で防水加工をすることにした。

akizukidenshi.com

具体的には、以下のように、ソーラーモジュールの周りに防水用スポンジ(エプトシーラー)を配して、アクリル板で両面から挟み込む構造にした。耐候性に優れ、透明性も高い素材としてアクリルやポリカーボネートがあるが、今回は、たまたま入手しやすかったのでアクリルにした。 周囲の8個のネジで締め上げてエプトシーラーが圧縮されるようにする。配線は、ソーラーモジュールの背面の電極から、横に出す形。なので、あまり厚みが出ないように注意深くはんだ付けが必要。

f:id:dekuo-03:20210321112533p:plain


2. 材料調達

  • アクリル板

一部をカットして使う。

https://www.amazon.co.jp/gp/product/B00E84KFIQ/ref=ppx_yo_dt_b_asin_title_o00_s00?ie=UTF8&psc=1

  • エプトシーラー

https://www.amazon.co.jp/gp/product/B0795CJFBT/ref=ppx_yo_dt_b_asin_title_o00_s00?ie=UTF8&psc=1


3. 組み立て

2.1 配線

ソーラーモジュールの裏面は、こんな感じ。

f:id:dekuo-03:20210321122044j:plain

はんだ付けして、テープで押さえる。

f:id:dekuo-03:20210321122100j:plain

2.2 アクリル板切り出し

所定の寸法にカットし、周囲に穴あけ。

f:id:dekuo-03:20210328233039j:plain

2.3 ソーラーモジュール挟み込み

ソーラーモジュールを挟みこんで、ネジで締め上げる。といってもあまりに締めすぎるのは負担がかかるのでほどほどに。

f:id:dekuo-03:20210328233054j:plain

これで、外観は完成。


3. テスト

2.3に先立って、ソーラーモジュールを挟む前に、エプトシーラーで囲まれた領域に水が入らないか、テストした。ネジの締め付けは、ソーラーモジュール厚み程度にエプトシーラーが圧縮される強さにした。 方法は、約5cm程度の深さの水に、パネルを沈め、1時間ほど放置。

f:id:dekuo-03:20210328233047j:plain

結果、水の侵入がないことを確認できた。したがって、設計した構造で防水は問題なさそう。(日光による劣化の評価は時間がかかるのでまたあとで)



まとめと今後の課題

ソーラーモジュールの防水加工ができた。次回は、システムに組み込む。


windowsでlibusbを使う

概要

Windows PC上で、libusbを使ったC言語プログラムをビルドして、動かした。


背景と目的

Windows PC上で、libusbを使いたいので、方法をメモる。


詳細

参考

0. 環境


1. MinGWをダウンロード、インストール

https://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting%20Win32/Personal%20Builds/mingw-builds/installer/mingw-w64-install.exe/download

f:id:dekuo-03:20210313104320p:plain


2. libusbをダウンロード、インストール

2.1 ダウンロード

https://github.com/libusb/libusb

バイナリを落とす。githubソースコードではない。

2.2 インストール

VS2019のdllを使うので、VS2019/MS64/dll/libusb-1.0.dllを以下のフォルダにコピーする。 これで、出来上がった実行ファイルがlibusb-1.0.dllを参照できる。

C:\Windows\System32


3. コンパイルしてみる

3.1 ソースコード

https://sites.google.com/site/memomuteki/tinylinux/linuxdepentabumojuruwozuochengshiyou

上記を参考に、使うものに合わせて編集。 PC側からの送信に使うエンドポイントアドレスがあるデバイスなので、そのアドレスでlibusb_interrupt_transfer関数を使って送信。

#define VID 0x**** // 使用するものに合わせる
#define PID 0x**** // 使用するものに合わせる
#define INTF_NUM 0
#define EP_ADDR 0x81
#define EP_ADDR2 0x01 // PC側からの送信に使うエンドポイントアドレス
#define MAX_PACKET_SIZE 64 // 変更

:

        // libusb_set_auto_detach_kernel_driver() failed: LIBUSB_ERROR_NOT_SUPPORTEDと出るのでコメントアウト
    // r = libusb_set_auto_detach_kernel_driver(handle, TRUE);
    // if (r != LIBUSB_SUCCESS) {
    //  printf("libusb_set_auto_detach_kernel_driver() failed: %s\n", libusb_error_name(r));
    //  goto exit;
    // }

:

    // 送信用にコードを追加
    int transferred2 = 1;
    buffer2[0] = 0x52;
    libusb_interrupt_transfer(handle, EP_ADDR2, buffer2, sizeof(buffer2), &transferred2, 100);

3.2 コンパイル

include/libusb-1.0のlibusb.hとVS2019フォルダのdllを参照する。

gcc -I./include/libusb-1.0 -L./VS2019/MS64/dll -lusb-1.0 -o test test.c


4. 動かす

実行した結果、正しく通信できた。

52 02 00 4a 02 53 39 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00


まとめと今後の課題

Windows PC上で、libusbを使ったC言語プログラムをビルドして、動かすことができた。

マイナンバーカードを使って、国税庁の確定申告書作成コーナーの作成作業の開始にたどり着くまでにやったこと

0. 環境

1. インストールしたソフトウェア

  • NFCポートソフトウェア

www.sony.co.jp

インストールが完了すると、デバイスが認識される。

  • Google Chrome拡張機能 マイナポータルAP 事前準備セットアップでインストールせよと促される。

https://chrome.google.com/webstore/detail/%E3%83%9E%E3%82%A4%E3%83%8A%E3%83%9D%E3%83%BC%E3%82%BF%E3%83%ABap/fjjibalalngebiaophebgkkohaaggphf

chrome.google.com

  • JPKI利用者ソフト 事前準備セットアップでインストールされる。

  • デスクトップアプリのマイナポータルAP

事前準備セットアップでなぜか、インストールされなかったので、手動でインストール。

2. 手順メモ

【確定申告書等作成コーナー】-作成コーナートップ

別窓が開くので、マイナンバーカード方式を選択する。

事前準備が済んでいないと、表示されるので、事前準備。 事前準備セットアップボタンを押し、jizen_setup.exeをダウンロード、実行する。 JPKI利用者ソフトまでインストールが終わる。

再度、作成開始を押す。 ここで、なぜかデスクトップアプリのマイナポータルAPがないといわれるので、実行ファイルをダウンロードしてインストール。

やっと、作成開始にたどり着いた。

ESP32-WROVER-Eを使って、OV2640使用200万画素カメラを動かす

概要

ESP32-WROVER-Eを使って、OV2640使用200万画素カメラで撮ったUXGAの画像をクラウドストレージに送信できた。



背景と目的

以前、ESP-WROOM-32を使って、OV2640使用200万画素カメラを動かしたのだが、ESP-WROOM-32のRAM不足により最大解像度UXGAでの撮影がうまくいかなかった。そこで、RAMが増強されたESP32=WROVER-Eを使って、UXGAでの撮影を試す。



詳細

0. やること

UXGAで写真を撮影し、クラウド上にアップロードする。

1. ESP32-WROVER-E

ESP32-WROVER-Eは、こちらに書いた通り、ESP-WROOM-32にPSRAMと呼ばれる外付けのRAMが備わったもの。

2. 撮影

2.1 ピン設定で間違えてハマる

今回の撮影部分のプログラムは、以前の記事に挙げてあるesp_cameraのサンプルを少し修正したプログラムをそのまま使ったのだが、実はピン設定がおかしかったことに気づいたので修正。PWDN_GPIO_NUM、RESET_GPIO_NUMは接続なしとする必要があった。実は、この間違いに全く気付くまで、プログラムの途中でおかしな再起動が多発し、かなりの時間を費やしてしまった。再起動が起こったある箇所を修正すると、今度は影響がないはずの前方の箇所で再起動が発生するという不思議な現象が起きていた。結局、このあたりからヒントを得て、どうにか修正にたどり着いた。

// ピン設定
#define Y2_GPIO_NUM 32
#define Y3_GPIO_NUM 35
#define Y4_GPIO_NUM 34
#define Y5_GPIO_NUM 5
#define Y6_GPIO_NUM 39
#define Y7_GPIO_NUM 18
#define Y8_GPIO_NUM 36
#define Y9_GPIO_NUM 19
#define XCLK_GPIO_NUM 27
#define PCLK_GPIO_NUM 23
#define VSYNC_GPIO_NUM 25
#define HREF_GPIO_NUM 26
#define SIOD_GPIO_NUM 21
#define SIOC_GPIO_NUM 22
#define PWDN_GPIO_NUM -1 // 変更
#define RESET_GPIO_NUM -1 // 変更

2.2 カメラ設定

UXGAで撮るには、config.frame_sizeを変更。

  // if PSRAM IC present, init with UXGA resolution and higher JPEG quality
  //                      for larger pre-allocated frame buffer.
  if (psramFound()) {
    Serial.println("PSRAM found.");
    config.frame_size = FRAMESIZE_UXGA; // UXGAを選択
    config.jpeg_quality = 5;
    config.fb_count = 1;
  } else {
   :

2.3 撮影部分

esp_camera_fb_get関数で実行するだけ。pixel_formatをPIXFORMAT_JPEG(サンプルそのまま)なので、fb->bufには、jpgのバイナリデータが格納される。

  // 撮影
  camera_fb_t * fb = esp_camera_fb_get();
  if (!fb) {
    Serial.println("Camera capture failed");
    return fb;
  }


3. アップロード

今回の送信先は、AWSのAPIGatewayのエンドポイント。JSONで受けて、バックエンドのLambdaでS3に保存する。なので、送信元はjpgバイナリデータをいったんbase64エンコードしてテキストに変換しJSONに格納する。(本当はapplication/octet-streamでバイナリを送った方がデータ量が少なくて済むが、クラウド側ができてしまっているので今回は不採用)

3.1 base64エンコード

extern "C" {
#include "libb64/cdecode.h"
#include "libb64/cencode.h"
}

以下のような関数を用意して呼び出して使う。

// base64エンコード
void base64_encode(uint8_t * packet, int packet_len, char * encoded) {
  
  base64_encodestate _state;
  base64_init_encodestate(&_state);
  int len = base64_encode_block((const char *) &packet[0], packet_len, &encoded[0], &_state);
  len = base64_encode_blockend((encoded + len), &_state);  

}

なお、呼び出し側で用意するencodedというエンコード結果格納用配列は、ps_callocを使ってPSRAM上にメモリ確保している。(またはps_malloc)UXGAのjpgバイナリは200kB程度があり、ESP32内部のRAMでは確保できないためだ。PSRAMがあると、できることが増えるというのを実感した。

  char * encoded = (char *) ps_calloc(size, sizeof(char));
  if (encoded == NULL) {
    Serial.printf("encoded=NULL!\n");
    while (1) {}
  }
  encoded[0] = '\0';
  Serial.printf("ps_calloc encoded at %x, size=%lu\n", &encoded[0], (unsigned long) size);

3.2 POST

実は、ここでもかなり悩まされてしまった。 ESP32でJSONをPOSTするために以前より自作して使用していた関数を今回も使ったところ、JSON BodyをWiFiClientSecure.printlnした直後に固まってしまった。いろいろ試行錯誤した結果、どうやらBodyのサイズが大きいと固まるらしく、Bodyを分割して複数回WiFiClientSecure->writeするとうまくいった。(POST POST自体を複数回行うという手もやってみたが、あまりに送信に時間がかかるのでボツ)

なので、以下のように、Bodyを分割してwriteするように関数を修正した。 なお、WiFiClientSecureのインスタンス化は、インスタンスをPSRAM上に確保するためにややこしい処理になっている。clientという変数は、WiFiClientSecureインスタンスではなくそのポインタ。そのせいで、各呼び出しが、client.***ではなく、client->になっている。(上述のBody送信後フリーズはが、WiFiClientSecureのインスタンスがRAM不足に起因すると思ってPSRAM上に確保してみたのだが、WiFiClientSecureのインスタンス自体はInternal RAMでも実は問題ない気もする。。。あとで余裕があれば試す。)

#include <WiFiClientSecure.h> // Wi-Fiに接続して、HTTPS通信するためのライブラリ

const int TIMEOUT_IN_MSEC = 60000; // タイムアウト[msec]

// POSTリクエストを送信する
// payload: 送信したいJSON Bodyのテキスト
void post_request(const char* host, const char* path, const char* payload) {

  // PSRAM上にインスタンスを確保するためのps_mallocとnew
  char * ptr = (char *) ps_malloc(sizeof(WiFiClientSecure));
  WiFiClientSecure* client = new (ptr) WiFiClientSecure;
  Serial.printf("client at %x\n", client);
  
  Serial.printf("Trying to connect to %s...\n", host);
  if (!client->connect(host, 443)) { // 接続にトライ
    // 接続失敗
    Serial.println("Connection failed!");
  } else {
    // 接続成功
    Serial.println("Connection success!");

    size_t contentLength = strlen(payload);
    Serial.printf("Content-Length: %d, Body=\"%c ... %c\"\n", contentLength, payload[0], payload[contentLength - 1]);
    
    // ヘッダ
    client->printf("POST %s HTTP/1.1\n", path); // POSTメソッド
    client->printf("Host: %s\n", host); // HTTP1.1で必須のヘッダ, アクセス先サーバーとしておく(そうしないとクロスドメインアクセスになる)
    client->println("Connection: close");
    client->println("Content-Type: application/json");
    client->printf("Content-Length: %d\n", contentLength);
    client->println(); // ヘッダの最後は空行
    
    // ボディ
    // 一度に大きなデータをprintfしたりwriteするとうまくいかない
    // mバイトに分割してwriteする
    int m = 1024;
    int i, j = 0;
    uint8_t * p = (uint8_t *) ps_malloc(m);
    while (true) {
      for (i = 0; i < m; i++) {
        p[i] = payload[i + j * m];
        if (i + j * m == contentLength - 1) break;
      }
      Serial.printf("j=%d, length=%d\n", j, i);
      client->write(p, m);
      client->flush();
      if (i + j * m == contentLength - 1) break;
      j++;
    }
    client->println();
    client->println(); // 空行を最後に

    // レスポンス待ち
    Serial.println("Waiting for reponse...");
    unsigned long curtime = millis();
    while (client->available() == 0) {
      if (millis() - curtime > TIMEOUT_IN_MSEC) {
        // タイムアウトのときは終了
        Serial.println(">>> Client Timeout !");
        client->stop();
        return;
      }
    }
    
    // レスポンス受信
    if (client->connected()) {
      while (client->available()) {
        char c = client->read();
        Serial.write(c); // 1文字ずつ出力
      }
      Serial.println("");
    }
    
    // 接続終了
    client->stop();
  }

}


4. 動作確認

無事、S3上に保存できた。



まとめと今後の課題

とにかく、ピン設定のミスと、Bodyの分割writeでハマってしまい非常に苦労した。ESP32-CAMのような出来上がっている基板を買えばもっとずっと楽だっただろう。とはいえ、ここまでたどり着けたので良しとする。