Arduino
電子工作
USB

CH375 USBモジュールをArduinoで使ってみる


リポジトリ

今回使用したコードはこちら

https://github.com/kn1cht/CH375_Arduino_sample


はじめに

電子部品ショップのaitendoさんで、「USBバスモジュール」というものが売られています。

中国製のUSBチップであるCH375を使用し、USBホストやUSBデバイスになれるそうです。

ちょうどArduino互換のボードで使うコンパクトなUSBホストモジュールを探していたので、買って動かしてみました(しかし、USBデバイスの接続有無をチェックするところまでしかできていません……)。

ch375_gaikan.jpg


情報がない

しかし、CH375は動作報告はおろか、ライブラリさえもネット上にほとんど見当たりません。

必死にググった結果、prittyparakeet氏の「CH375のメモ」という一連の記事が見つかりました。

これらをよく読めばCH375を動かすことができます。

ただ、氏も冒頭に書かれているように自分用のメモといった感じなので、CH375をArduinoに繋いで動作させることを目標にやり方をまとめておきます。


環境


  • macOS 10.13

  • Arduino IDE 1.8.5

  • Arduino UNO(純正品)

  • CH375モジュール(aitendo)


CH375について

江苏沁恒股份有限公司が製造している「USBバスインターフェースチップ」です。

なぜかデータシートが2種類あり、2番目の方はググる限り中国語版のみのようです。

読みたい方はGoogle翻訳に突っ込んでください。

その他、ElectrodragonのWikiに様々な情報へのリンクがあります。


使ってみる: ハードウェア編

Interface USB Mouse to your Arduino using CH375Bという記事が、写真付きで繋ぎ方を紹介しています。

モジュールにはUSB端子の近くに3本のピンヘッダ、反対側に2列16本のピンヘッダが立っていると思います。

シリアル接続の場合、使うのはほとんど前者です。

買ってきた状態では、3本の方のピンヘッダにジャンパピンがついています。

これは使わないので取ってください。

ジャンパワイヤなどで、Arduinoとモジュールを以下のように繋ぎましょう。

5Vピンのみ、2列になっている方のピンヘッダにあります。

Arduino
CH375

5V
5V

GND
GND

D10(RX)
TXD

D11(TX)
RXD

実際につなぐとこんな感じになります(オスメスのジャンパがなく、その場のもので代用したので配線が汚いですが......)。

arduino-ch375.jpg

写真ではArduino UNOを使っていますけれども、シリアル通信線2本の接続なので他のボードでも特にやることは変わりません。


使ってみる: ソフトウェア編


9ビットのシリアル通信をできるようにする

初め通信ができず苦しんでいたところ、prittyparakeet氏の「CH375のメモ.5」をよく読むと解決しました。

CH375は、9ビットのデータを持つ珍しい方式のシリアル通信を使っているようです。

マイコンから送信する際に、9ビット目を0にするとデータ、1にするとコマンドと解釈されるとのこと。

従って、Arduinoの標準のシリアル通信では対応できず、専用のライブラリが必要です。

記事で紹介されていたSoftwareSerial9を使 えばいいのですが、実はこのライブラリそのままでは使えません います。

ソフトウェアシリアル(9ビット版)の追記によると、SoftwareSerial9.cpp164行目にミスがあるので修正が必要です。

自分で直しても構いませんが、すでに修正済みのフォークがあるのでそちらを導入する方が楽でしょう。

- peppers96/SoftwareSerial9

なお、このミスについては元のリポジトリにPull Requestが出されていますが、1年近く放置されています:sweat:

追記:2017年12月にPull Requestがマージされ、上記の問題は解消しました。


CH375と通信してみる

CH375との通信の流れは、大体


  1. コマンドを書き込む

  2. コマンドに付随して送るべきデータがあれば、それも書き込む

  3. 出力が送られてくるので読み取る

といった感じです。

使えるコマンドは、データシート1の3ページ目から一覧になっています(データシート2にも追加コマンドがあります。ややこしい......)。

GET_IC_VERコマンド(0x01)を使うコードは以下のようになります。

#include <SoftwareSerial9.h>


SoftwareSerial9 ch375(10, 11);

void setup() {
unsigned char res;
Serial.begin(9600);
ch375.begin(9600);

Serial.println("Sending GET_IC_VER Command...");
ch375.write9(0x101);

for(int i = 0; i < 1000; ++i) {
delay(1);
if(ch375.available()) {
res = ch375.read();
}
}
Serial.print("0x");
Serial.println(res, HEX);
}

void loop() {}

コマンドが0x01なのに0x101を送っています。

これは、前項でも述べたようにコマンドの場合は9ビット目を1としなければならないためです。

後はCH375から返答があるまで待機し、read()で出力を受け取れます。

なお、CH375からArduinoに送られるのはデータだけなので、読む際は9ビット目を気にする必要はありません。

プログラムを実行して、0xB7が表示されれば成功です。

なお、データシートによれば、実際のバージョン番号を得るにはbit7(8番目のビット)を取り除く必要があるようです。

従って、0xB7 - 0x80 = 0x37が本当のバージョン番号になります。


USBデバイス(USBメモリ)の接続を検出するサンプル

CH375にUSBデバイスを刺して無事認識されると、赤色LEDが点灯します。

USBデバイス接続の有無は、CH375にコマンドを送ることでも確認できます。

以下、冒頭に掲げたリポジトリのコードです。

コマンドの書き込み・データの書き込み・データの読み取りを簡単に行うため、CH375.hCH375.cppでクラスを定義しています。

CH375_Arduino_sample.inoでは、今回試したコマンドを順番に実行しています。

コメントの"5.4"などといった番号は、データシートの項目と対応します。


CH375_Arduino_sample.ino

#include <SoftwareSerial9.h>

#include "CH375.h"

CH375 ch375(10, 11);

void setup() {
unsigned char res;
Serial.begin(9600);

/*** 5.1 CMD_GET_IC_VER ***/
Serial.println("\n## 5.1 CMD_GET_IC_VER");
ch375.cmd(CMD_GET_IC_VER);
res = ch375.read();
Serial.print("CH375B Version: 0x");
Serial.println(res - 0x80, HEX); // remove the bit 7 according to datasheet

/*** 5.4 CMD_RESET_ALL ***/
Serial.println("\n## 5.4 CMD_RESET_ALL: Hard resetting CH375");
ch375.cmd(CMD_RESET_ALL);
delay(200); // wait until reset is done

/*** 5.9 CMD_SET_USB_MODE ***/
Serial.println("\n## 5.9 CMD_SET_USB_MODE: USB-HOST with SOF package");
ch375.cmd(CMD_SET_USB_MODE);
ch375.write(0x06);
res = ch375.read();
if (res == CMD_RET_SUCCESS) {
Serial.println("SET_USB_MODE success");
}
else {
Serial.println("SET_USB_MODE failed");
}

/*** 5.10 CMD_TEST_CONNECT ***/
Serial.println("\n## 5.10 CMD_TEST_CONNECT");
ch375.cmd(CMD_TEST_CONNECT);
handleInterruptionState(ch375.read());

/*** 5.12 CMD_GET_STATUS ***/
Serial.println("\n## 5.12 CMD_GET_STATUS");
ch375.cmd(CMD_GET_STATUS);
handleInterruptionState(ch375.read());

/*** DS2 1.11 CMD_GET_DESCR ***/
Serial.println("\n## DS2 1.11 CMD_GET_DESCR");
ch375.cmd(CMD_GET_DESCR);
ch375.write(0x01); // device descriptor
ch375.cmd(CMD_GET_STATUS);
handleInterruptionState(ch375.read());
}

void loop() {}

void handleInterruptionState(uint8_t c) {
switch(c) {
case USB_INT_SUCCESS:
Serial.println("Operation successful");
break;
case USB_INT_CONNECT:
Serial.println("USB device connected");
break;
case USB_INT_DISCONNECT:
Serial.println("USB device disconnected");
break;
case USB_INT_BUF_OVER:
Serial.println("Buffer overflow");
break;
case USB_INT_DISK_READ:
Serial.println("Reading from USB storage");
break;
case USB_INT_DISK_WRITE:
Serial.println("Writing to USB storage");
break;
case USB_INT_DISK_ERR:
Serial.println("USB storage error");
break;
default:
Serial.print("Error: operation failed with code 0x");
Serial.println(c, HEX);
break;
}
}


USBデバイスを接続した上で実行すると、シリアルモニタにこのような出力が出てきます。

ch375_result.png


おわりに

ここまでさも成功したかのように書いていますが、実はサンプルの最後のGET_DESCRコマンドはうまく動きません。

参考にしたサンプルにはUSBメモリのファイルを読み書きするコードもありますが、USBメモリをマウントする時点で失敗します。

現状できていることは、


  • 通信してバージョンを取得する

  • リセットする

  • USBモードを設定する

  • USBデバイスが繋がっているかどうか調べる

となります。

これでは実用からは程遠く、結局積み基板と化してしまいました。

まだ試していないコマンドやサンプルはたくさんあるので、機会があれば試そうと思います。

もしCH375を使いたいという方がいらっしゃるなら、この記事が参考になれば幸いです。