時々ハングするIoT機器を再起動させるUSBアダプタを作った

tl;dr

  • GoogleHomeから操作できる家電リモコンが時々ハングして、使いたい時に限って使えないことがあったよ。電源を挿し直せば直るよ
  • CH217 はスイッチ機能がついたUSB電源保護ICだよ。2Aくらい流せるのでWiFi機器でもいけそうだよ。
  • このICのENを、CH32V003で制御して、12時間ごとに再起動させるアダプタを作ったよ。
  • Kicadファイルとファームウェアのコードは GitHub で公開しているよ。

よくある素行の悪いIoT機器を再起動したい

お家ではとあるIoT機器を使って、GoogleHomeやスマートフォンから家電を操作できるようにしています。 しかし、それは1ヶ月くらい使っていると時々ハングアップするのかアクセスできない状態になってしまい、いざという時に使えません。 これを、自動で電源オンオフする仕組みさえ作れば改善できるのにと思っていました。

USB電源保護ICのCH217にスイッチ機能があることに気づく

前の記事では、USB電源保護ICであるCH217を紹介しました。

74th.hateblo.jp

Ch217 Usb Current Limiting Power Distribution Switch Chip Adjustable Current Limiting Threshold 10pcs/lot - Integrated Circuits - AliExpress

https://www.aliexpress.com/i/1005005392218373.html

このCH217には、スイッチ機能があり、ENをLOWにしたときだけ、VOUTに出力される様になっています。 この機能を使えば、USB電源のオンオフが可能なことに気づきました。

いや、普通にMOS FETだけでもできると思うのですが、USBの5V電源を供給する機能も必要なので、このIC一つでちょうどよさそうに思ったのです。

USB Type-C Plugが秋月でも買えるようになった

スイッチアダプタの片方の端子が USB Type-C Plugになっていたら、そのまま機器に接続できて便利です。

最近秋月で、基板取り付け用のUSB Type-C Plugが販売されるようになりました。

akizukidenshi.com

これのストックを持っていました。こちらを使うことにしました。

はんだ付けをしてみたところ、妙に傘になっている部分があり、そこに差し込む様にはんだごてを当てる必要があり、簡単ではありませんでした。

ですが、Boothでも販売している自作のUSBソケットテスターを使うことで、正しく実装できているか確認しながらできたので、最終的には問題なくうまくいきました。

74th.booth.pm

タイマー機能のためだけにCH32V003を使う

タイマーの制御は、CH32V003を使うことにしました。CH32V003は、今や秋月で40円で買える格安マイコンです。私はこのマイコンを既に150個購入しているので、贅沢に組み込めます。

電源を3.3Vだけでなく5Vでも良いため、レギュレータなしで組み込むことが来ます。今回はレギュレータなしにUSB5Vを直結させていました。

ファームウェアはch32v003funを使って実装しました。ch32v003funは直接レジスタ触るのを少しやりやすくしてある感じなのですが、GPIOの操作についてはサブライブラリ的なのがあります。これを使うとクロックの設定や、GPIOの有効化の設定、またIOの出力がやりやすくなっています。

github.com

以下が実際のコードです。

#define EN_PIN GPIOv_from_PORT_PIN(GPIO_port_C, 2)
#define FLAG_PIN GPIOv_from_PORT_PIN(GPIO_port_C, 1)
#define LED_PIN GPIOv_from_PORT_PIN(GPIO_port_A, 2)

void setup_gpio()
{
    GPIO_port_enable(GPIO_port_A);
    GPIO_port_enable(GPIO_port_C);
    GPIO_port_enable(GPIO_port_D);
    GPIO_pinMode(FLAG_PIN, GPIO_pinMode_I_pullUp, GPIO_Speed_10MHz);
    GPIO_pinMode(EN_PIN, GPIO_pinMode_O_pushPull, GPIO_Speed_10MHz);
    GPIO_pinMode(LED_PIN, GPIO_pinMode_O_pushPull, GPIO_Speed_10MHz);
}

#define LOOP_MS 1000

#define ACTIVE_SEC 12 * 60 * 60
#define INACTIVE_SEC 1

int main()
{
    SystemInit();
    init_rcc();
    setup_uart();
    printf("start\n\r");

    setup_gpio();

    while (1)
    {

        // オンにする
        printf("active\n\r");
        GPIO_digitalWrite_0(EN_PIN);
        GPIO_digitalWrite_1(LED_PIN);

        for (int i = 0; i < ACTIVE_SEC; i++)
        {
            printf("%d/%d\n\r", i, ACTIVE_SEC);
            Delay_Ms(LOOP_MS);
        }

        // オフにする
        printf("unactive\n\r");
        GPIO_digitalWrite_1(EN_PIN);
        GPIO_digitalWrite_0(LED_PIN);

        for (int i = 0; i < INACTIVE_SEC; i++)
            Delay_Ms(LOOP_MS);
    }
}

github.com

わりと一瞬で動くコードは仕上がりました。

最後におまけ機能として、UART TXで今のカウントダウンを見られるようにしていました。

基板の設計

回路図は簡単なものです。GitHub上にアップロードしてあります。

github.com

kicanvas

https://kicanvas.org/?github=https%3A%2F%2Fgithub.com%2F74th%2Fusb-rebooter%2Fblob%2Fv1.0.0%2Fpcb%2Fusb_auto_reboot.kicad_sch

ポイントは以下の通りです。

  • CH217を組み込んで、ENピンをCH32V003で制御させる。
  • CH32V003には
  • CH32V003の書き込み用に、SWIOとGNDのピンを持たせておく。
  • CH32V003のデバッグ用に、TXピンをPD6として出しておく。標準のPD5を使うと、CH32V003J4M6(8ピンのやつ)ではSWIOのPD1と兼用のため、書き込みが難しくなる。PD6はalternate機能としてUART1 TXに繋がる。

反省点

作って動くものになったのですが、いくつか反省点があります。

  • CH32V003自体を保護にするのに、CH213(理想ダイオード)を組み込んでもよかった。
  • よりCH32V003の消費電力を減らすために、クロックを下げたり、Low Power Modeを使ったり、まだ工夫の余地がありそう。
  • 使ったUSB Type-C Plugは秋月で買えるのは便利ですが、LCSCで安く買えるものにしておけばもっとストックを持っておけるので良かったかも。
  • CCはそのままReceptacle(メス)からPlug(オス)に流していたはずですが、PDのやりとりが伝播できてなさそう。なんでや。。。

回路図、ファームウェアのコードは公開中

USB Type-C Plugの実装が思いのほか大変だったため、電子工作キットにしようとは今のところ考えていません。

ファームウェアのコードとkicadファイルは以下に公開しています。

github.com

まとめ

CH217で気軽にUSB5Vをスイッチ制御できるのは、今後も何かと役に立ちそうです!

ぜひCH217とCH32V003を使ってみてください。

USBデバイスの電源保護に、CH217、CH213を使う

注意、この記事は工業製品の電子回路設計のプロではなく、アマチュアの電子工作エンジニアが執筆しています。世の中には紹介するICより良いICがあるかもしれませんが、私にとって良さそうなものを見つけたとして、記事にしています。

tl;dr

  • USBデバイスの工作をしていて、PC側やMCUの電源保護に、過電流防止にリセッタブルフィーズや、逆電流防止にダイオードを入れたりするよ。
  • CH213、CH217を使うと、過電流防止と、逆電流防止(CH213のみ)、過熱保護が1つの部品でできるよ。
  • CH213を使うと、500mAまで流せるよ。ESP32には足りないが、だいたいのMCUには足りそうだよ。
  • CH217を使うと、ISETに繋ぐ抵抗で流せる電流量を変えられて、さらにオンオフスイッチ機能も付いているよ。ただし、ENのプルダウンと、ISETの抵抗は必須だよ。

USBデバイスの電源保護

マイコン開発ボードや自作キーボードなどの、USBデバイスを作っていますが、公開されている回路図を見ると、USB電源保護の措置が取られていることがあります。 主に2つのことが行われているようにうかがえます。

  • 逆電流防止: USBデバイスからUSBホストに、誤って電流を流さないようにする。
  • 過電流防止: USBデバイスが過剰に電流をとってしまい、マイコンに過剰に電流を流してしまったり、USBホストに影響を与えないようにする。

例えば、Raspberry Pi PICOでは、USBのVBUSからレギュレータの間にはMBR120VLSFという1A流せるショットキーバリアダイオードが入っています。

Raspberry Pi Pico Datasheet

https://datasheets.raspberrypi.com/pico/pico-datasheet.pdf

また、元祖のSparkFun社のProMicroにはUSBのVBUSからMCUに繋がれるVCC、レギュレータの間には500mAのリセッタブルフィーズがはいっています。

Pro Micro 5V/16MHz — スイッチサイエンス

https://www.switch-science.com/products/1623

私の最近作る開発ボードにはフューズかダイオードを入れるようにしています。

ただ、ショットキーバリアダイオードを入れるにも手に入りやすいものだと電圧降下が0.5Vほどあって辛かったり、リセッタブルフィーズも1A流せるものはサイズが大きかったりします。

私の場合、ショットキーバリアダイオードには電圧降下が340mVで1A流せるPMEG2010ERを、リセッタブルフィーズにはなんかTaoBaoで安かった1.1A流せるKT6-1100SMDIを使っていました。

www.lcsc.com

自恢復保險絲KT6-1100SMDI 1812-110 1.1A 6V 20只-Taobao

https://world.taobao.com/item/wap/555353746767.htm

WCHの製品にも使われる CH213、CH217 を調べる

WCH-LinkEのクローン品を作ろうとWCH-LinkEの回路図を見ていたとき、CH217Kという部品が使われているのに気づきました。 どうやらUSB電源保護を行ってくれるICであることがわかりました。クローン品を作る時にはこの部品は省略することにしました。

現在のところ中国語のサイト上にしか紹介ページはありませんが、データシートは英語のものができています。

CH217 Datasheet

https://www.wch-ic.com/downloads/CH217DS1_PDF.html

CH217は、USB電流制限スイッチICとして以下の機能があるようです。

  • 400mA-2.7Aで設定可能な電流制限
  • 供給電圧が一定以下になったとき電源出力をシャットダウンして、低電圧による誤動作を防止
  • 熱くなったときに電源出力をシャットダウンする過熱保護
  • スイッチ制御

ダイオードとしての機能はないようです。試しにVINとGND、VOUTとGNDを逆に繋いでみたところ、非常に過熱しました。

また、近い機能のICにCH213があることを知りました。こちらは英語のサイトと、データシートがあります。

5V*0.5A ideal diode chip – CH213 - NanjingQinhengMicroelectronics

https://www.wch-ic.com/products/CH213.html

CH213は5V0.5Aの理想ダイオードとして、以下の機能があります。

  • 逆電流保護(ダイオード
    • 200mA で電源降下32mVと降下が低い
  • 熱くなったときに電源出力をシャットダウンする過熱保護
  • 1.3A の電流制限

CH334というUSB Hub ICのデータシート中に、CH217をDownstream(デバイス)側の電源保護に、CH213をUpstream(ホスト)側の電源保護に使う例が登場しています。

USB HUB Controller – CH334/5 - NanjingQinhengMicroelectronics

https://www.wch-ic.com/products/CH334.html

CH213、CH217 を購入する

CH213、CH217を購入する手段として、見つけた当初はTaoBaoしかなかったのですが、最近AliexpressのWCHのオフィシャルショップで取り扱われるようになりました。

100Pcs/Lot CH213 5V*0.5A Low Voltage Drop Diode Chip - AliExpress

https://www.aliexpress.com/item/1005005619632155.html

100/500Pcs/Lot CH217 USB Current limiting Power Distribution Switch Chip - AliExpress

https://www.aliexpress.com/item/1005005619823491.html

2023/08/13現在、別途送料が717円ほどかかりますが、CH217が861円/100個、CH213が574円/100個と、あまり高くない価格で買うことができます。 自分の作成するデバイスに組み込んでみたく、購入してみました。

CH213を使ってみる

CH213を単品で使ってみました。確かにMCUを起動する分には使えそうな500mAで、試しにUSBはんだごてを繋いでみると、電流制限されて動作しないのを確認できました。

しかし、これをESP32-C3-WROOM-02を使ったデバイスの電源に入れてみたところ、電源が不安定になることが確認されました。ESP32のWiFiを駆動するには500mAでは足りないと言うことなのかなと思っています。

CH32V003を駆動するために組み込んでみたところ、特に問題なく利用することができました。

CH217を使ってみる

CH217を単品で使ってみました。試しにUSBはんだごてを繋いでみると、電流制限されて動作しないのを確認できました。

(この時はダイオード機能があるとおもってツイートしていますが、後で調べるとなさそうでした)

最初ENと、ISETは何も接続しないフローティングの状態で使えたりしないかなと試してみましたが、使えませんでした。 ENは確実にプルダウンする必要があり、ISETはGNDとなんらかの抵抗を繋ぐ必要があります。

ISETに繋ぐ抵抗の値として、データシートには22kΩで最大の2.75A、30kΩで2.0A、45kΩで1.0Aと記載があり、これをプロットしてみると直線になりそうなので、このグラフを見て抵抗を決めれば良いことがわかります。

ISETの抵抗と流せる電流

感想としては、必ずISETの抵抗が必要なのが部品点数が増えて面倒な感じがしますが、

このスイッチとして使える特性は面白く、これを使って、USBデバイスの電源を自動でオンオフするようなものが使えそうと思いました。

まとめ

CH213、CH217はUSBデバイスの電源保護のために作られているのもあって、USBデバイスの製作に活躍しそうです。そして、お安いのも助かります。

このあと実際に、時々フリーズするIoT機器を12時間ごとに再起動させるCH217を使ったアダプタと、USBハブを製作してみています。これは後日記事にします。

M5StackC3 と IR Unit で、HTTPで赤外線を送受信できる自宅 Web API を作る

tl;dr

  • CircuitPython と ESP32 を使うと簡単に、HTTP Server を立てることができる。
  • CircuitPython の pulseio を使うと、制御が抽象化されて、簡単に信号制御ができる。
  • M5StampC3 と、IR Unit ならケースに入っているし、そのままお家にデプロイしても良さそう。
  • CircuitPython 上でアプリケーションを作り込むより、Raspberry Piなどの上でアプリケーションを作って、HTTP経由でMCUに制御させた方が、アプリケーションの作り込みがやりやすそう。

作ったもの 赤外線送受信 HTTP Server

自宅の WiFi に繋いで、HTTP経由で赤外線を送受信できる Web Server を M5StampC3 と IR Unit で作成しました。

POST /api/send_ir で以下のような JSON を送ります(ちなみに、わが家の SONY の電源ボタン)。

{
  "pulse": [
    2403, 642, 1170, 645, 588, 621, 1167, 648, 588, 621, 1191, 624, 588, 621,
    588, 621, 1167, 648, 588, 621, 588, 624, 585, 624, 588
  ]
}

すると、赤外線信号が送信されます。

また、POST /api/receive_ir を叩いて、赤外線ユニットに向けてリモコンノボタンを押すと以下のようにレスポンスします。

{
  "success": true,
  "data": {
    "pulses": [
      2403, 615, 594, 618, 1194, 621, 588, 621, 591, 621, 588, 621, 588, 624,
      612, 618, 1170, 645, 567, 645, 564, 645, 564, 645, 567, 27126, 2400, 621,
      588, 642, 1170, 645, 564, 645, 567, 645, 588, 621, 588, 621, 588, 624,
      1191, 621, 591, 621, 588, 621, 588, 621, 588, 27105, 2397, 645, 564, 645,
      1170, 645, 588, 621, 564, 645, 567, 645, 564, 645, 588, 621, 1170, 645,
      588, 621, 567, 642, 591, 621, 588, 27105, 2421, 621, 567, 642, 1170, 645,
      564, 645, 564, 645, 567, 645, 588, 621, 564, 645, 1194, 621, 588, 621,
      591, 618, 591, 621, 588
    ]
  }
}

これで、パソコンからでもHTTP操作で、リモコンの操作ができるようになりました。

M5StampC3 に CircuitPython をインストールする

今回の工作には、お安めの M5StampC3 (¥1,188)を使ってみることにしました。

M5Stamp C3 Matewww.switch-science.com

そしてファームウェアのランタイムには CircuitPython を選びました。これは、単にのちほど M5StampS3 を使って赤外線機能を持ったTV横USBキーボードデバイスを作ろうとしていて、同じCircuitPython上で実装したコードが使えることを狙ったためです。CircuitPythonのファームウェアは以下からダウンロードできます。

circuitpython.org

インストール方法は下記にあります。私は別途 ESP32 の開発環境が合ったため、 esptool.py を使いましたが、ここにはWebブラウザ経由でインストールする方法も書かれています。

learn.adafruit.com

CircuitPython はインストールすると、通常は USBメモリとして認識するドライブに、ファイルをコピーすることで使うことができます。M5StampC3 で使われている ESP32-C3 にはUSBデバイス機能はありません。では、どうやってファイルを送るかというと、M5StampC3 をUSBで繋ぐと出てくるシリアルコンソール上で、WiFi の設定を入力してM5StampC3 をWiFi につなぎ、すると WebServer が自動で立ち上がるようになるので、HTTP経由で送ります。コンソールに以下のようなPythonのコマンドを打ち込んで、設定ファイルを作ります。

learn.adafruit.com

f = open('settings.toml', 'w')
f.write('CIRCUITPY_WIFI_SSID = "wifissid"\n')
f.write('CIRCUITPY_WIFI_PASSWORD = "wifipassword"\n')
f.write('CIRCUITPY_WEB_API_PASSWORD = "webpassword"\n')
f.close()

そしてUSBの再接続すると、IP アドレスが書かれているので、そのIPにブラウザでアクセスすることができます。

私の場合は、固定IPにしたかったので、お家のWiFiルータの設定でDHCPでこのIPを割り当てた MACアドレスに固定IPを割り振るようにしました。

これで、curl でファイルを転送することができるようになりました。

curl -v -u :webpassword -T main.py -L --location-trusted http://192.168.1.19/fs/code.py

CircuitPython で WebServer を立てる

WiFi の設定をしただけで WebServer が立っている訳ですが、adafruit_httpserver を使うと、Flask と似た感じに API を実装することができます。

import wifi
import json
import socketpool
from adafruit_httpserver import Server, Request, Response

pool = socketpool.SocketPool(wifi.radio)
server = Server(pool, "/static", debug=True)

@server.route("/api/send_ir", methods="POST")
def send_ir_api(request: Request):
    print("access /api/send_ir")
    print(request.body)
    data = json.loads(request.body)
    pulse = data["pulse"]

    print(pulse)
    send_ir(pulse)

    res = {"success": True}
    return Response(request, json.dumps(res))

server.serve_forever(str(wifi.radio.ipv4_address))

adafruit_httpserver は、Adafruit CircuitPython Library Bundle に含まれています。以下のリポジトリからダウンロードし、libs/adafruit_httpserver ディレクトリを、CircuitPythonのlib ディレクトリに転送して、使えるようにしておきます。

github.com

M5Stack IR Unit を接続する

M5StampC3 には Grove ポートが付いており、Grove ポートで使えるようになる 赤外線モジュールがM5Stackから出ています。

M5Stack用赤外線送受信ユニット [U002]www.switch-science.com

お値段も ¥649 とお手頃です。M5Stack のモジュールはケースに入っているので、そのままお家にデプロイしても良さそうです。

CircuitPython の pulseio を使って、赤外線の信号を送受信する

CircuitPython にはこのような信号をやりとりする仕組みとして、pulseio があります。その裏は、MCUごとの機能を使って実装されているようです。

docs.circuitpython.org

まず受信の関数は以下のようにしました。len(pulsein) で受信したかどうかわかり、pulsein.popleft() で値を取ります。これを受信できるまでループさせるようにします。

import board
import pulseio
import time

pulsein = pulseio.PulseIn(board.G0, maxlen=120, idle_state=True)
pulsein.pause()

def receive_ir() -> list[int]:
    timeout = 10

    pulses = []

    start = time.monotonic()
    is_received = False
    pulsein.clear()
    pulsein.resume()
    while not is_received and (time.monotonic() - start) < timeout:
        # 値が取れるか、タイムアウトまでループ
        time.sleep(0.1)
        while pulsein:
            pulse = pulsein.popleft()
            pulses.append(pulse)
            is_received = True
    pulsein.pause()

    if pulses is None:
        print("cannot receive pulses")
    else:
        print("Heard", len(pulses), "Pulses:", pulses)

    return pulses

なお、pulseio.PulseIn に関しては、一度 pulseio.PulseIn() でインスタンスを作り deinit() でインスタンスを解除しても、再度 pulseio.PulseIn() でインスタンスを作ったとしても、使えなくなる問題が発生しました。これは最初の一度だけ初期化するようにします。また、一度初期化してしまうと CircuitPython のリセットを行っても再度使える状態には鳴りませんでした。その時は仕方なく、MCU レベルでリセットするようにしました。

import microcontroller; microcontroller.reset()

次に送信は以下のようにします。こちらは単純です。

def send_ir(pulse: list[int]):
    with pulseio.PulseOut(IR_SEND_PIN_NO) as pulseout:
        print("send")
        for _ in range(3):
            pulseout.send(array.array("H", data))
            time.sleep(0.025)
        print("send done")

こんな感じに配列を渡すだけで送信ができます。

この 2つの関数を WebServer から呼べるように実装しました。

これで赤外線の IO となる WebServer ができた

ほんの100行に満たないコードで、赤外線リモコンの IO となる Web API を構築することができました。

ここから更にやりたいことは以下のようなことです。

  • TVの電源オン、チェンネル1に変更、入力切り替えでTV横PCのHDMI に、1ボタンで切り替える
  • PS5 の起動後のAmazon Prime アプリを起動するまでの、シーケンスを、TVリモコン越しに行う

これらには複数のボタンを、時間間隔と一緒に制御する必要があります。

ですが、M5StampC3 で宅内 Web API になったことで、既にある Raspberry Pi 4 上に操作盤となる Webアプリを作り込めば、CircuitPython の小さな世界ではなく、Pythonなどの機能を存分に使って実装することができます。これに気づいたことから、マイコンでは、センサとの IO だけやるようにしようと思うようになりました。

今回作成したコードは以下のリポジトリで公開しています。

github.com

Appendix: CircutPython のプロジェクトで Python のコード補完を効かせる

VS Code で実装していると、コード補完機能には助けられます。CircuitPython用のコード補完用のモジュールは以下で公開されているため、これをインストールします。しかし、これを使っても完全に補完できる状態にはならないようです。

pypi.org

また、adafruit_httpserver のようなモジュールでは、libディレクトリに配置するのは mpy というバイナリ変換ファイルになります。バイナリファイルでは補完が効かないため、プロジェクトディレクトリにgit submodule で変換前のファイルを取得して参照するようにしました。

git submodule add https://github.com/adafruit/Adafruit_CircuitPython_HTTPServer.git ./imports/Adafruit_Circui
tPython_HTTPServer
ln -s imports/Adafruit_CircuitPython_HTTPServer/adafruit_httpserver ./