格安USBホストマイコン CH559をいじってみた(大盛)

最近秋月電子で取り扱いが開始されたマイコン CH559L、安い 8 ビットマイコンですが、USB ホストになれるという特徴があります。

このマイコンを使って、USB キーボードの入力を ASCII テキストに変換してくれる便利モジュール「かんたん USB ホスト」を開発したので、その過程と、その過程でわかったことをまとめてみます。

最初に評価ボードを手に入れたのが 2020年5月で、それから 1 年以上いろいろいじったのでそれなりに知見がたまりました。それらを一通りまとめてみたので、なかなか大盛な記事になってしまいましたが、これから CH559 をいじってみたい、という方にも参考になれれば幸いです。

CH559って?

南京沁恒微电子(Nanjing Qinheng Microelectronics、以下 WCH)から出ているマイコンチップです。WCH は USB トランシーバー入りの 8 ビットマイコンチップを CH55x という名前でシリーズで発売しているようで、CH559 はそのいちばん大きいもののようです。以下、簡単にスペックと主観での良し悪しポイントをまとめます。

スペック(CH559L のもの。CH559T は以下からかなり削減されているようなので注意!):

  • E8051 コア
  • 12MHz オシレータ内蔵(PLL で逓倍可能)
    • 外部クロックで最大 56MHz 可能だが USB 使用時は 48MHz に固定?
  • SRAM: 6KB + 256Bytes
  • Flash ROM: 60KB プログラム + 1KB データ + 3KB ブートローダー
    • ブートローダーは出荷時に書き込まれている
  • USB トランシーバー×2
    • Host / Guest 切替可 ×1
    • Host 専用 ×1
    • Host 側は Root Hub が 2 つ入っていて、上記 2 つを切り替えて使える。
  • タイマー×4、PWM ×3、SPI ×2、UART ×2、ADC ×8
  • GPIO: 最大 45 ピン
  • 電源: 3.3V、5V → 3.3V に使える LDO 内蔵

良いところ:

  • USB トランシーバーが 2 つ入っていて USB ホストにもなれる
  • 他のペリフェラル(UART とか)もわりと豊富
  • コアが 8051 なので基本的なプログラミングの資料は豊富
  • とても安い: LCSC で 10個買うと 1個 US$1.47(2021年8月)
  • ブートローダーが最初から書き込まれていてプログラミングが簡単
  • まあ使えるくらいには速い
  • ほとんどのピンが 5V(正確には、5Vピンと同じ電圧まで)トレラント

良くないところ:

  • コアが 8051 なのでプログラムのバイナリが予想外に大きい
    • すなわちあまり大きいプログラムは入らない
  • データシートやサンプルコードが中国語
  • I2C が内蔵されていない

という感じで、とにかく安く USB Host が実装できるのが最大の特長です。

USB ホストを実装するには

CH559 は USB トランシーバー入りですが、それより上のレイヤーは入っていません。つまり、USB デバイスとの信号のやりとりは CH559 がやってくれますが、その USB デバイスがマウスなのかシリアル変換ケーブルなのか判定したり、USB キーボードとどういうやりとりをすれば打った文字がわかるのか、みたいな上位レイヤーの処理は、素の CH559 はやってくれません。このあたりはすべてファームウェアに書く必要があります。

I2C が入ったマイコンで対応のセンサー等を使いたいとき、マイコンは I2C のバスの制御しかしないので、初期化のシーケンスをマイコンのプログラムに書いたり、センサーからのデータの変換計算も同プログラムに書いたりするのと同様です。

かんたん USB ホスト」の場合を例にとります。USB キーボードとやりとりしてキーボードの入力を ASCII テキストに変換するために、最終的に、以下のような処理がファームウェアに入りました。USB 自体が多用途なバスということもあって、けっこう多いです。

  • USB デバイスの認識・初期化
    • CH559 に USB デバイスが接続されたことを検知する。*
    • USB デバイスにアドレスを付与して、初期化する。*
      • USB デバイスの通信速度や種類などの基本的な情報を取得する。*
  • USB キーボードとの通信
    • 接続された USB デバイスがキーボードであると識別し、デバイスアドレスを覚えておく。*
    • キーボードをポーリングして押されているキーの一覧を取得する。*
    • キーボードに LED の状態を変更するよう命令する。
  • USB ハブとの通信
    • 接続された USB デバイスがキーボードであると識別し、デバイスアドレスを覚えておく。
    • ハブをポーリングして、接続状態の変化があるかどうかチェックする。
    • 新しくつながったデバイスを初期化、外されたデバイスを破棄する。
  • 押されたキーから文字への
    • 変換キーコードからキーの文字の ASCII コードに変換する。
    • Shift キーなどが押されていたら、文字のコードも適宜変更する。
    • キーが押され続けていたら、直前の文字をリピートする。

(以上のうち「*」のついているものは、あとで紹介するオープンソースプロジェクトですでに実装されていたものです。それ以外は私が実装しました。)

キーボードは当然として、ハブも CH559 自体では処理してくれないので、自前で実装しなければいけない点に注意です。(なんでキーボードの変換のためにわざわざハブも実装したのかというと、「ハブ内蔵のキーボード」というのがけっこう存在していると思うので、それらにも対応したくてがんばったのです。)

上の箇条書きでわかる通り、ファームウェアのコード量はかなり多くなりました。このことは開発を始める前から予想はしていたので、実際には開発をしやすい環境を整えるところから始めました。

開発環境の用意

開発環境は、評価ボードを除いて無料で手に入ります。書き込みも PC からの USB 接続でいけます。快適に開発できるようになるまで若干の手間はかかりますが、ハードルは充分低いと思います。

評価ボード

Aliexpress で公式の評価ボードが手に入ります。「CH559 Board」とかで検索すると出てきます。

実は公式の評価ボードであることは知らないで、とりあえず開発環境を整えるのに評価ボード的なのがあると楽かなと思って適当に買ったのですが、あとから公式品であることがわかりました。回路図やサンプルコードが WCH からダウンロードできる(WCH の CH559 のページの CH559EVT.ZIP)ので、ひとまずこの評価ボードを入手するのが手っ取り早いと思います。実際わりと便利なボードで、開発に十分使えました。

評価ボードの各端子はこんな感じ:

(Micro USB はいちどもげたのではんだづけしなおしました。使う前に補強したほうがよさそうです。)

便利だったのが USB の 2 系統がちゃんと評価ボードに実装してある点で、USB Host の開発をしながら、USB Device 側を PC につなぎっぱなしにしておいて、随時ファームウェアのダウンロードができます。これが 1 系統しかないと、ダウンロードのたびに接続を切り替えたり、SPI 経由でプログラミングするようにしておかないといけないので、だいぶ面倒になりそうです。

UART が 1 系統独立したピンになっているのも、PC にデバッグ出力を送るのに便利でした。

コンパイラ・エディタ

公式の IDE もあるよう?ですが、Windows 環境で手軽に開発するには、以下の github レポジトリから始めるのがわりとハードルが低いです。

https://github.com/atc1441/CH559sdccUSBHost

このレポジトリは CH559 でキーボードとマウスを認識してその入力を(加工なしで) UART にダンプするものです。USB を扱う部分は WCH の公式サンプルコード(WCH の CH559 のページ の CH559EVT.ZIP)ほぼそのままのようですが、SDCC でコンパイルして動くようにしてあります。

このレポジトリには SDCC コンパイラも同梱されているので、compile.bat を実行するだけでコンパイルができます。(が、大きなプロジェクトを開発するのなら、ずっと下の「メモリ」の項で解説している通り、自前で SDCC を用意するのをおすすめします。また、CH559 への書き込みもこの compile.bat から実行するようにできるようですが、私はこれは試しませんでした。)

ひとまずこれで動作する hex ファイルができるので、以下の「ファームウェアの書き込み」に書いたように WCHISPTool で書き込んでやると動きます。これをスタート地点にして少しずつ拡張していくのがいろいろ楽だと思いますし、実際楽でした。

ソースの編集には Visual Studio Code を使いました。VS Code の C プラグインは SDCC 固有の修飾子に対応していないので、そのままではエラー表示で真っ赤になります。が、以下のようなヘッダファイルを用意して各ソースファイルから include すれば、編集時は全部スルーさせて普通に使えます。(これはネットのどこかの書き込みからアイディアを拝借しました。)

公式データシート・サンプルコード

公式の開発資料は WCH の CH559 のページの下のほうにあります。(英語ページだと出てこないので注意)

  • データシート(中国語)は、CH559DS1.PDF
  • サンプルコードや回路図は、CH559EVT.ZIP
    • その中の EXAM がサンプルコード
      • (Keil 用?そのままでは SDCC ではコンパイルできない。)
    • その中の PUB/CH559SCH.PDF が評価ボードの回路図
  • ファームウェア書き込みツールは、WCHISPTool_setup.exe

ファームウェアの書き込み

CH559 には最初からブートローダーが書き込まれているので、ファームウェアの書き込みはとても簡単です。

私は主に公式の書き込みツール WCHISPTool を使いました。WCH の CH559 のページ のいちばん下のリンクからダウンロードできます。

評価ボードの Hub0 側を PC に接続し、Download ボタンを押しながら(P4.6 ピンを GND に落としながら)電源を入れると、ブートローダーに入れます。すると PC から USB デバイスとして認識されるので、WCHISPTool から hex ファイルをダウンロードできます。

WCHISPTool での hex ファイル書き込み手順

ほかに SPI から書き込みをする方法があるようですが、上記の方法で充分なので、試していません。

開発でつまづいた・気になった点

ということで開発をスムーズに進める環境はできたのですが、実際にいろいろ動かすのはなかなか素直にはいきませんでした。ここからは、開発を進める上でつまづいたり気になったりした点をまとめます。

マイコンのコアは 8051(MCS-51)なので、割り込みやタイマーみたいな USB に関係ない部分は昔ながらの 8051 のノウハウでそのまま開発できます。8051 は古典的なマイコンなので、ネットを検索すると参考になる情報がたくさん出てきます。これは開発の初期のブーストにはとても助かりました。

しかし、CH559 は 8051 からだいぶ「建て増し」されているような感じです。なので、タイマー0 は 8051 まんまなのに タイマー2 は全然違う、など、同じペリフェラルでも番号によって勝手が全然違う、みたいなところがたくさんあります。なので開発を進めていくとだんだん薮に迷い込むことになりました。

まあ、これが中華マイコンの「醍醐味」のひとつでもあるのでしょう。データシートやサンプルコードの中国語をひたすら Google Translate にかけたり、いろいろ試したりして動かていきました。

以下、ひとつひとつ紹介します。

電源と I/O 電圧

電源は 3.3V で、I/O 電圧も 3.3V です。が、3.6V ~ 5V → 3.3V にしてくれる LDO が内蔵されているので、これを使って 5V の電源から動かすこともできます。

面白いのは、一部の GPIO ピンの耐圧が LDO の入力と同じになっている点です(過電圧防止ダイオードが LDO の入力ピンにつながっている、ようなことをデータシートに見かけたような記憶があります)。つまり、5V から LDO 経由で電源供給すると、一部の GPIO ピンが 5V トレラントになります。

これは CH559 を使って変換モジュールをつくりたい場合にとても便利な機能です。USB デバイスに電源を供給する目的がある以上、5V の電源は必ずあるわけです。それを変換モジュール(と CH559)の電源にすれば、CH559 は 5V トレラントになります。これなら通信相手の I/O 電圧が 3.3V であっても 5V であっても大丈夫なので、組み込める機器の幅が広がります。

少しだけ残念な点は、LDO の最大出力電流がデータシートに見当たらないところでしょうか。まあ、他の回路の電源にするのはやめてほしい、ということでしょうね。

リセットピン

誰でも見てわかる特徴的な点。リセットピンが正論理で、ハイレベルでリセットがかかります。最近のマイコンは負論理が多いので、「リセットピンはプルアップ、リセットのときは GND につなぐ」とやってしまいがちですが、CH559 では逆になるので要注意です。

これは元の 8051 の仕様を踏襲しているようです。リセットピンは内部でプルダウンされているので、使わない場合は無接続で大丈夫です。

GPIO

ピン(GPIO)の入出力の指定が特徴的です。ピンの設定を間違ったために UART がうまく動かずにしばらくはまる、というようなことがありました。

CH559 は(8051 がそういう仕様なのか、)ピンの入出力方向の指定方法が最近一般的な方式とはけっこう違うようです。加えて、最近のマイコンでは例えば UART を有効にすると TX/RX ピンの入出力方向を自動的に設定してくれたりしますが、CH559 ではそういうことがなく、RX ピンを間違って出力の設定にするとそのまま動かなくなったりします。

各ピンのモード設定は、以下のようになっています:

  • まずバンク(P0 とか P1 とか)ごとにオープンコレクタかプッシュプルか選択できる。デフォルトはオープンコレクタ。
  • プッシュプルの場合、許容電流(?)も 5mA か 20mA か選択できる。
  • ピンごとにプルアップ抵抗のオンオフと入出力方向、出力レベルのハイ・ローを選択できる。
  • オープンコレクタで出力ピンに指定した場合、Hレベルにしたときに2クロックだけソース電流を流すモードがある。
  • オープンコレクタの場合は出力ピンに指定していても入力が可能。(原理的にそうなるのはよくわかりますが、では入力ピンとは…。)
  • バンクごとに用意されているレジスタが違う(P4 には IN レジスタがあるのに P3 にはない、とか。何故…?)
    • IN レジスタがないポートは入出力両方とも同じレジスタを使う。
    • 例:P3 ポートは入力も出力も P3 レジスタ、P4 ポートの出力は P4_OUT、入力は P4_IN。

という感じで、文字だけだとちょっと何言ってるのかわからない感じかと思います。データシートに等価回路図も載ってますが、どうもそれだけでは腑に落ちない挙動があるようにも思えて、私も完全には理解できていません。いろいろ触ってなんとなく納得した感じです。8051 を扱い慣れた方ならわかりやすいのかも…?

CH559 データシートより、GPIO ピンの等価回路図。

ひとまず簡単に扱うには、思い切って全部プッシュプルモードにしてしまうか、もしくは、デフォルトのオープンコレクタモードで何も設定しなければ入力も出力もできるので、FET をドライブしたり高速な信号を扱ったりしないのなら、ピン設定は触らない、という方法でも良い気がします。

一部のピンを除いて、ほとんどのピンは 5V トレラントにできます。(正確には、上の「電源と I/O 電圧」の項で説明している通り、「5V ピンに供給されている電源の電圧まで」トレラントです。)これは使いやすいです。

UART

これも UART0 と UART1 でけっこう勝手が異なります。UART0 のほうが 8051 互換のようです。

特に注意するべき点として、UART0 を使うと タイマー0 または タイマー1 が占有されます。ボーレートの生成のためにタイマーを使うというしくみです。UART1 はそんなことなくシステムクロックからボーレートを生成できるので、実際の使い勝手は UART1 のほうが良いように思います。

面白いのは、UART0、UART1 ともに、入出力として使えるピンが2組あり、どちらを使うか選択できる点です。基板デザインをすっきりさせるのに意外と役に立ったりします。ただし、UART1 で P2.6、P2.7 ピンを使う場合、P2 ポートをプッシュプルモードにしないとうまく動かないようです。(加えて、P2.7 ピンを出力、P2.6 ピンを入力に設定するのを忘れずに!)

SPI

これも SPI0 と SPI1 で勝手が違い、SPI0 はマスター・スレーブ両方可能、SPI1 はマスターのみです。

SPI0 をスレーブで使ってみましたが、FIFO の設定がよくわからなかった(オンにするとデータがうまく転送できなくなる?)以外は特におかしなところはなかったように思います。転送完了時の割り込みも OK です。

SPI0 の機能がある P1 ポートは 5V トレラントではないところは注意点ですね。

メモリ

開発でいちばん厄介だったのがメモリの制限です。コードを書いているとすぐ内蔵メモリ 256バイト の上限に達してしまい、書いたばかりのところをゴチャゴチャやって容量を最適化したりとかの作業に大いに時間をとられました。最終的に SDCC の xstack オプションでスタックを拡張メモリに追いやることで解決しましたが、そのために SDCC をソースからコンパイルしなおしたり、一筋縄ではいかない問題でした。

CH559 は 6KB の拡張メモリを積んでいるとはいえ、スタックなどは SDCC のデフォルトでは内蔵メモリに格納されます。変数を拡張メモリに追い出すのは __xdata などの修飾子を変数につけるだけでできるのでわりと簡単なのですが、スタックが内蔵メモリに乗ってしまうのが問題で、ある程度プログラムが大きくなってくると、関数呼び出しを追加したらスタックが大きくなって内蔵メモリに乗り切らなくなりコンパイルが通らなくなる、みたいなことに頻繁に遭遇するようになりました。

ここで厄介なのは、どの関数がどれだけスタックを消費しているみたいなことがよくわからない点です。SDCC は一応メモリマッピングが書かれたファイルを出力するのですが、これを見てもよくわかりません。なので回避しようと思うと、引数の多い関数を削ってみたり、inline にしてみたり、暗中模索状態になります。これが時間を食うのです。

SDCC にはスタックを拡張メモリに置く –xstack というオプションがあって、これをオンにするとスタックに使えるメモリ領域が拡がるので、上記のようなことをする必要は(ひとまず)なくなります。が、SDCC に標準で付属しているライブラリ(libc とか)が –xstack なしでコンパイルされているため、自分のソースのコンパイル時に単に –xstack を追加するだけではリンクができません。標準ライブラリを –xstack つきでソースからコンパイルして使う必要があります。

幸い、SDCC のソースにはライブラリを –xstack オプションつきでコンパイルする Makefile が入っているので、コンパイル自体は全く難しくありません。(なら最初からインストールされるようにしておいてよ!!)なので自前で SDCC をソースからコンパイルして環境をつくったのですが、SDCC 自体が依存しているライブラリやソフトウェアが多いため、コンパイル環境を整えるのにわりと時間がかかりました。(半日消費しました。)

ということで、CH559 で少しでも大きめのプログラムを書くときは、メモリの制限がいちばん厄介です。これから CH559 と SDCC で何かつくろう、という場合は、作り始める前に、–xstack つきでコンパイルができる環境を整えることを強くオススメします。

ウォッチドッグタイマー

CH559 のウォッチドッグカウンターは 262144 CPU クロックごとに 1 カウントし、256 カウントでオーバーフローしてウォッチドッグリセットがかかります。カウンターに代入する値を変えるとリセットまでの時間を変えられますが、カウントもとのクロックの設定は変更できません。

USB を使う場合は CPU クロックが 48MHz に固定されるため、ウォッチドッグリセットの間隔は最大で約 1.4 秒になります。充分といえば充分ですが、USB デバイスの認識にはわりと時間がかかることがあるので、ちょっと短く感じました。5 秒くらいに設定できると良いのですが…

ブートローダー

ブートローダーは、上の「ファームウェアの書き込み」で書いた通り、電源を入れるとき(リセットがかかるとき?)に P4.6 ピンを GND に接続しておくと実行できます。

ブートローダーはプログラムメモリの 0xF400 番地以降に書き込まれているので、ユーザープログラムを実行中でも、このアドレスに飛べばブートローダーに入れます。なので、P4.6 ピンをポーリングして、L レベルになったときにブートローダーに入るようにプログラムを組んでおくと、開発中にファームウェアを更新するのが楽になります。上に挙げた CH559sdccUSBHost にコード例があります。

要注意ポイントとして、ウォッチドッグタイマーがオンの状態で、ユーザープログラムから飛ぶ形でブートローダーに入ると、(ブートローダーがウォッチドッグタイマーを無効化しないので)ブートローダー内でもウォッチドッグリセットがかかります。そうするともちろんユーザープログラムに戻ってきてしまうので、場合によっては無限ループになります。

この場合は、P4.6 ピンを GND にプルダウンした状態で電源を入れる方法でブートローダーに入りましょう。これならウォッチドッグタイマーを有効にする前にブートローダーに入れるので、無限ループから脱して修正できます。

USB Host

これは私自身まだ完全に理解できていないのと、本当に詳しく書くととても長くなるのとで、ここでは詳細な解説は省きます。ご容赦ください。

ざっくりと、以下のような手順で USB デバイスにアクセスします:

  • 通信対象の USB デバイスのアドレスを選択する。
  • 同対象デバイスの USB バススピードを選択する。
    • 上 2 つの情報はデバイスの初期化のときに取得しておきます。
  • メモリ上に USB のパケットのデータを用意する。
  • 送信のトリガをかけると、CH559 がそのデータを送信して返答を受信してくれる。
    • 送受信に使うメモリはそれ専用に用意し、DMA で送受信しているようです。

肝はパケットのデータを構築するところなので、CH559 のデータシートよりは、USB-IF にある規格書や、他の USB チップ用(MAX3421E とか)のドライバーのソースコードを参照することが多かったです。

バイナリサイズ

これは 8051 の特徴らしいのですが、コンパイル後のバイナリのサイズがコード量に対して意外と大きくなります。

「かんたんUSBホスト」は USB ハブとキーボードの処理のみが入っている状態ですが、それでもバイナリのサイズは 53KB ほどあります。ユーザープログラムに使える領域は 60KB なので、すでに 88%。もうそんなに余裕がありません。

USB ホスト機能を持っているちょっとしたガジェットをつくりたいなと思ったとき、理想的にはワンチップで済ませたいところです。が、CH559 でそれはちょっと難しそうだなぁという印象です。機能の少ないガジェットなら、接続する USB デバイスを限定して USB ホスト部分を極限まで小さくすればなんとか…という感じでしょうか。

だったらいっそ、CH559 は USB ホストとプロトコル変換に徹してもらって、外にもうひとつマイコンを接続したほうが自由度が高いように思います。(ということで「かんたんUSBホスト」はひとつの製品にしました。)

まとめ

CH559、わりとクセはありますが、チップ単価が安くて開発環境も無料で構築できるし、秋月からも手に入るし、USB ホスト機能が欲しい場合(もそうでなくて単に興味があるだけのときでも)、いじってみて損はないと思います。

個人的には、USB デバイスが自分のドライバープログラムで動くようになっていくのが楽しかったです。特にハブのドライバーが動いて、USB デバイスがずらりと認識されたときは感動的でした。かなり面白いマイコンだと思うので、おすすめです。

付録:他の方の CH559 情報ブログ等

SDCCを使ってCH559の開発をした際のハマりポイント

  • 私と同じように開発でつまづいたりした点が挙げられています。似たような気付きがいくつかありますね。

CH559のデータシートを読んで見る+日本語化 その1 概要と特徴

  • CH559 のデータシートを日本語化されています。

格安USBホストIC(CH559)を試す

  • 最初に遭遇したページ。昨年この情報を見て CH559 の評価ボードを買いました。