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

# リポジトリ
今回使用したコードはこちら
https://github.com/kn1cht/CH375_Arduino_sample

# はじめに
電子部品ショップのaitendoさんで、「[USBバスモジュール](http://www.aitendo.com/product/10246)」というものが売られています。
中国製のUSBチップである**CH375**を使用し、USBホストやUSBデバイスになれるそうです。

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

![ch375_gaikan.jpg](https://qiita-image-store.s3.amazonaws.com/0/150557/9c242018-96b3-9de1-5b57-09dc241a286d.jpeg)

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

必死にググった結果、prittyparakeet氏の「[**CH375のメモ**](http://blog.livedoor.jp/prittyparakeet/archives/cat_1227869.html)」という一連の記事が見つかりました。
これらをよく読めばCH375を動かすことができます。
ただ、氏も冒頭に書かれているように自分用のメモといった感じなので、**CH375をArduinoに繋いで動作させる**ことを目標にやり方をまとめておきます。

## 環境
- macOS 10.13
- Arduino IDE 1.8.5
- Arduino UNO（純正品）
- CH375モジュール（aitendo）

# CH375について
江苏沁恒股份有限公司が製造している「USBバスインターフェースチップ」です。
なぜかデータシートが2種類あり、2番目の方はググる限り**中国語版のみ**のようです。
読みたい方はGoogle翻訳に突っ込んでください。

- [データシート1](ftp://imall.iteadstudio.com/Modules/IM131009001/DS_IM131009001.pdf)
- [データシート2](http://www.winchiphead.com/download/CH375/CH375DS2.PDF)

その他、[ElectrodragonのWiki](http://www.electrodragon.com/w/CH340)に様々な情報へのリンクがあります。

# 使ってみる: ハードウェア編
[Interface USB Mouse to your Arduino using CH375B](http://www.electronicsfaq.com/2016/11/interface-usb-mouse-to-your-arduino.html)という記事が、写真付きで繋ぎ方を紹介しています。

モジュールにはUSB端子の近くに3本のピンヘッダ、反対側に2列16本のピンヘッダが立っていると思います。
シリアル接続の場合、使うのはほとんど前者です。

買ってきた状態では、3本の方のピンヘッダにジャンパピンがついています。
これは使わないので取ってください。

ジャンパワイヤなどで、Arduinoとモジュールを以下のように繋ぎましょう。
5Vピンのみ、2列になっている方のピンヘッダにあります。

Arduino  | CH375
---------|--------
 5V      | 5V
 GND     | GND
 D10(RX) | TXD
 D11(TX) | RXD

実際につなぐとこんな感じになります(オスメスのジャンパがなく、その場のもので代用したので配線が汚いですが......)。
![arduino-ch375.jpg](https://qiita-image-store.s3.amazonaws.com/0/150557/7c879146-4040-9ef6-f05b-d4ed8b2264d8.jpeg)

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

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

## 9ビットのシリアル通信をできるようにする
初め通信ができず苦しんでいたところ、prittyparakeet氏の「[CH375のメモ．５](http://blog.livedoor.jp/prittyparakeet/archives/1061453619.html)」をよく読むと解決しました。

CH375は、**9ビットのデータ**を持つ珍しい方式のシリアル通信を使っているようです。
マイコンから送信する際に、**9ビット目を0にするとデータ、1にするとコマンド**と解釈されるとのこと。

従って、Arduinoの標準のシリアル通信では対応できず、専用のライブラリが必要です。
記事で紹介されていた[SoftwareSerial9](https://github.com/addibble/SoftwareSerial9)を使 ~~えばいいのですが、実はこのライブラリそのままでは使えません~~ います。

~~[ソフトウェアシリアル（９ビット版）](http://blog.livedoor.jp/prittyparakeet/archives/1061496841.html)の追記によると、`SoftwareSerial9.cpp`の**164行目にミスがあるので修正が必要**です。
自分で直しても構いませんが、すでに修正済みのフォークがあるのでそちらを導入する方が楽でしょう。~~

~~- [peppers96/SoftwareSerial9](https://github.com/peppers96/SoftwareSerial9)~~

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

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

## CH375と通信してみる
CH375との通信の流れは、大体

1. コマンドを書き込む
1. コマンドに付随して送るべきデータがあれば、それも書き込む
1. 出力が送られてくるので読み取る

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

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

```cpp
#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メモリ)の接続を検出するサンプル

![](arduino-ch375-usb.jpg)

CH375にUSBデバイスを刺して無事認識されると、赤色LEDが点灯します。
USBデバイス接続の有無は、CH375にコマンドを送ることでも確認できます。

以下、冒頭に掲げた[リポジトリ](https://github.com/kn1cht/CH375_Arduino_sample)のコードです。
コマンドの書き込み・データの書き込み・データの読み取りを簡単に行うため、`CH375.h`と`CH375.cpp`でクラスを定義しています。

`CH375_Arduino_sample.ino`では、今回試したコマンドを順番に実行しています。
コメントの"5.4"などといった番号は、データシートの項目と対応します。

```cpp: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](https://qiita-image-store.s3.amazonaws.com/0/150557/f4cd5795-361e-b941-e07e-b57bbc22b9f8.png)


# おわりに
ここまでさも成功したかのように書いていますが、実はサンプルの最後の`GET_DESCR`コマンドはうまく動きません。
参考にしたサンプルにはUSBメモリのファイルを読み書きするコードもありますが、USBメモリをマウントする時点で失敗します。

現状できていることは、

- 通信してバージョンを取得する
- リセットする
- USBモードを設定する
- USBデバイスが繋がっているかどうか調べる

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

まだ試していないコマンドやサンプルはたくさんあるので、機会があれば試そうと思います。
もし`CH375`を使いたいという方がいらっしゃるなら、この記事が参考になれば幸いです。