目次
解説用の写真は一部を除き400×240でデザインしています。 ディスプレイに表示されるサイズの参考にしてください。
開発経緯
私の職場のPCにはディスプレイが2台付いています。デスクトップ領域が広いと何かと便利なのですが、自宅では置き場所が無いため1台しか置けません。
最近はUSB接続のサブモニタが販売されていて、解像度は小さいながらも場所を取らないよう工夫された製品が数多くあります。
気軽に使える安いサブモニタは無いものかと探していたところ、ちびモという自作の液晶ディスプレイを作ってる方を見つけました。
組立用の基板が販売されているのでこれは欲しい!と思ったのですが、ポチる前に落ち着いてデモの動画を見ていると、思ったよりリフレッシュレートが低いのが気になりました。
というのも、サブモニタはフライトシミュレータの計器表示に使いたかったこともあり、ある程度のリフレッシュレートが欲しかったからです。
AVR(Arduino)でできるならPICでもできるだろうと思い、自分で作ってみることにしました。
LCDの特性と通信インターフェースの検討
表示に使うLS027B4DH01は画素が400×240ドットで高精細、偏光板を使わないPNLCという方式で、一般的な反射型液晶に比べてコントラストが非常に高くて見やすいです。
フレーム周波数は最大20Hzまで引き出せるので応答速度も十分です。
身近なことろではセブンイレブンのコーヒーメーカーのディスプレイが画面サイズや表示濃度から考えて同じ物を使っていると思われます。
右の表はLCDのデータシートの抜粋です。
最大フレーム周波数の20Hzを得るためには2MHzの速度でデータを送らなければいけないため、FT232のようなUSB-シリアル変換ICでは速度が不足します。
身近な素子で高速通信ができるものという事で白羽の矢が立ったのがイーサネット用IC RTL8019ASでした。
以前PICNICのファームウエアを改造した経験があったので、応用すればなんとかなるだろうとそれほど悩むことなく採用しました。
秋月電子のサイトで注文しようとしていたところFT245というICを見つけました。FT232がシリアル変換なのに対し、FT245はパラレル変換です。 FT245ではシリアルポート互換のドライバを使うと300kB/s(約2.3Mbps)の速度が出せると書いてあったので、わざわざイーサネットを使わなくても何とかなりそうです。 本来ならイーサネットは対象から外すわけですが、チャレンジという事で2つの受信ICを切り替えて使う事にしました。
秋月電子のサイトで注文しようとしていたところFT245というICを見つけました。FT232がシリアル変換なのに対し、FT245はパラレル変換です。 FT245ではシリアルポート互換のドライバを使うと300kB/s(約2.3Mbps)の速度が出せると書いてあったので、わざわざイーサネットを使わなくても何とかなりそうです。 本来ならイーサネットは対象から外すわけですが、チャレンジという事で2つの受信ICを切り替えて使う事にしました。
ユニット構成図
MCUはおなじみのPICです。速度が速い16F1シリーズを使いたかったのですが、ライターが対応していなかったため、対応範囲内で最上位クラスの16F887を20MHzのクロックで使うことになりました。
LCDへの転送クロックは、定格内で動かすならPICのSPIモジュールの制限から1.25MHz(FOSC/16)となってしまいます。
これではフレームレートも下がってしまうため、ワンランク上の5MHz(FOSC/4)というオーバークロック状態で動作させています。
噂ではもう少し高い速度でも動かすことができるようですが、PICの限界なので仕方ないですね。
イーサネットに使うツイストペアケーブルの接続にはパルストランスが必要です。RTL8019ASに付属品が付いてきますが、実装面積を省くためにRJ45のコネクタに内蔵しているタイプのものを別途使用しています。 コネクタ内蔵のタイプにはLEDも付いているのでとても便利です。
FT245はUSB2.0に対応したFT245BLを使うことにしました。こちらは外付けで6MHzの水晶が必要になります。
イーサネットに使うツイストペアケーブルの接続にはパルストランスが必要です。RTL8019ASに付属品が付いてきますが、実装面積を省くためにRJ45のコネクタに内蔵しているタイプのものを別途使用しています。 コネクタ内蔵のタイプにはLEDも付いているのでとても便利です。
FT245はUSB2.0に対応したFT245BLを使うことにしました。こちらは外付けで6MHzの水晶が必要になります。
基板を作る
回路図と基板の設計はEagleを使いました。
RJ45コネクタのライブラリがEagleに無かったためインターネットで見つけたものを使っていたのですが、 回路図上はあっているのにパターンの方だけLEDのピン番号が誤って作られていました。 おかげでLINKランプの点灯状態が負論理になってしまいました。これは結構ショックです。
RJ45コネクタのライブラリがEagleに無かったためインターネットで見つけたものを使っていたのですが、 回路図上はあっているのにパターンの方だけLEDのピン番号が誤って作られていました。 おかげでLINKランプの点灯状態が負論理になってしまいました。これは結構ショックです。
基板は75×54mmのガラエポ両面です。デザインルールは最小パターン幅10mil/最小クリアランス7mil、ビアの数は97個です。ビアは抵抗の足を使い安価に仕上げています。
USBのコネクタはMicro USBを使いました。Mini USBの方がハンダ付けしやすそうですが、挿抜耐久がMicro USBの方が1万回と多いそうです。 PICの左下にあるヘッダピンは、PICをICSPで書き込む時にLCDがONになるのを防ぐためのジャンパです。 後の説明で出てきますが、ICSP中にはLCDの反転信号を作ることができないため、LCDの破損防止として取り付けました。 後に27kΩのプルダウン抵抗に置き換えています。
USBのコネクタはMicro USBを使いました。Mini USBの方がハンダ付けしやすそうですが、挿抜耐久がMicro USBの方が1万回と多いそうです。 PICの左下にあるヘッダピンは、PICをICSPで書き込む時にLCDがONになるのを防ぐためのジャンパです。 後の説明で出てきますが、ICSP中にはLCDの反転信号を作ることができないため、LCDの破損防止として取り付けました。 後に27kΩのプルダウン抵抗に置き換えています。
ファームウエアを作る
ソースリスト Ver.0.36
ファームウエアはいつもと同じようにアセンブラで書いてます。ネットワークの処理は高級言語を使ったほうが作業効率が良いのは目に見えているのですが、 これまでそういった手法を使っておらず環境を構築するのも面倒ですし、何より高速化が必要だったので今回もパスしました。
PICのファームウエアは大まかなブロックとしてLCD表示、USBパラレルからのデータ処理、イーサネットのデータ処理の3つに分かれます。 完全に独立したプログラムではないですが、各ブロックごとに解説します。
ファームウエアはいつもと同じようにアセンブラで書いてます。ネットワークの処理は高級言語を使ったほうが作業効率が良いのは目に見えているのですが、 これまでそういった手法を使っておらず環境を構築するのも面倒ですし、何より高速化が必要だったので今回もパスしました。
PICのファームウエアは大まかなブロックとしてLCD表示、USBパラレルからのデータ処理、イーサネットのデータ処理の3つに分かれます。 完全に独立したプログラムではないですが、各ブロックごとに解説します。
LCDの駆動
LS027B4DH01を動かすうえでポイントになるところは3点あります。
LCDの通信がLSBファーストになっている
独自規格をうたっている3線シリアルインターフェースは、タイミングはSPIと同じですが、LSBファーストでデータを送ることになっています。
PICのSPIモジュールはMSBファーストで転送されるため、ビットの順番を反転しなければいけません。これは画像データだけではなく、ラインアドレスの指定も含みます。
後になって判明したのですが、Windowsが白黒二値で扱うビットマップデータはMSBファーストでドットが打たれていたので、
実際にビット反転が必要なのはラインアドレスだけになります。
今回のプログラムでは速度が要求されるので、RETLWテーブルを使うことにより僅か6サイクルでビット順反転を実行しています。
COM反転信号を作る
液晶ディスプレイは内部の構造により一定時間ごとに反転動作というものが必要です。
これを怠ると液晶ディスプレイが壊れるか寿命が縮まるため、LCDによってはその動作が自動化されていて利用者が意識する必要が無いものもあります。
LS027B4DH01では最低でも1秒ごとに反転動作のタイミング信号を与えないといけないので、
PICのTMR1割り込みを使い、約0.5秒ごとに反転動作の指示を与えることにしました。
SSPBUFの書き込みタイミングの最適化
LCDへのデータ転送タイミングを限界まで高めようと考えて、ビジーフラグを見ずに8bit分の時間経過でSPI送信レジスタ(SSPBUF)に書き込んだところ、
送信データが壊れることが分かりました。
何度か試してみると、11bit分の時間を待たなければ送信データが壊れるようです。この事から、1バイトの送信ループは11bit分の時間以上で回転させることになります。
USB経由のデータ受信
FT245経由でデータが転送されてくるとRXF#ラインがアクティブ(=Low)になります。RXF#はPICのINT端子に接続されているので割り込みも使えますが、1バイトごとに割り込み転送すると遅いのでポーリングにしました。
FT245にはPCからパラレル側へのバッファとして128バイト(2.5ライン分のバッファサイズ)が確保されているので、データが一杯にならないうちに読み出します。
もしバッファが一杯になれば、Windowsが持っているソフトウエアベースのバッファに貯まっていき、そこもいっぱいになればアプリケーションの書き込み動作がブロックされます。
データフォーマットは右図の通りです。ラインアドレスの後にデータが続く平凡なもので、エラーチェックは行っていません。1ラインが51バイトで構成されているので、1画面分のデータ転送サイズは12,240バイトになります。 LCDへの書き込みはこのデータとは別に更新コマンドとデータ書き換え開始の2バイトのダミーが必要になるので、内部的なデータサイズは12,960バイトです。 このデータサイズから逆算して最高のフレームレートを計算すると、5,000,000 / (12,960 * 11) = 35.07fpsとなります。(11は1バイト書き込むために必要なビット数です)
先頭バイトのラインアドレスを受信したら、ここだけビット順反転してからLCDに書き込み、その後50バイトのデータが続くものとしてメモリにバッファリングせず直接LCDに書き込んでいきます。 LCDの特性として、データの書き込み中はCOM反転信号を受けても反転動作は行わず、書き込み完了後に反転するという特徴があります。 何らかの原因でPCからデータが途絶えた場合に反転動作ができなくなるのを防ぐため、TMR2で13msの短周期タイマーを動かし、 1バイトごとの間隔が13ms以上になればタイムアップ割り込みが発生してLCDへの書き込みを強制終了するようにしてあります。
データフォーマットは右図の通りです。ラインアドレスの後にデータが続く平凡なもので、エラーチェックは行っていません。1ラインが51バイトで構成されているので、1画面分のデータ転送サイズは12,240バイトになります。 LCDへの書き込みはこのデータとは別に更新コマンドとデータ書き換え開始の2バイトのダミーが必要になるので、内部的なデータサイズは12,960バイトです。 このデータサイズから逆算して最高のフレームレートを計算すると、5,000,000 / (12,960 * 11) = 35.07fpsとなります。(11は1バイト書き込むために必要なビット数です)
先頭バイトのラインアドレスを受信したら、ここだけビット順反転してからLCDに書き込み、その後50バイトのデータが続くものとしてメモリにバッファリングせず直接LCDに書き込んでいきます。 LCDの特性として、データの書き込み中はCOM反転信号を受けても反転動作は行わず、書き込み完了後に反転するという特徴があります。 何らかの原因でPCからデータが途絶えた場合に反転動作ができなくなるのを防ぐため、TMR2で13msの短周期タイマーを動かし、 1バイトごとの間隔が13ms以上になればタイムアップ割り込みが発生してLCDへの書き込みを強制終了するようにしてあります。
イーサネット経由のデータ受信
イーサネットの通信にはUDPを使い、IPアドレスはDHCPでアドレスを自動取得するものとし、アドレスを固定したい場合はDHCPサーバ側で固定します。
認識させるプロトコルは以下の5つとなります。
電源を投入するとネットワークのリンク状態をNICに問い合わせ、LANケーブルが未接続の場合はUSBモードへ移行します。 ケーブルが接続されているとネットワークの受信ループへ入るとともに、2秒間隔でDHCP DISCOVERを送信します。 DISCOVERに対するOFFERを受信すると、今度はDHCP REQUESTを2秒間隔で送信します。これに対するACKがあればアドレス取得完了とみなし、DHCPパケットの送信を停止します。
PCの場合、PCから能動的にデータを送信するためネットマスクやデフォルトゲートウェイを取得する必要がありますが、mAQUOSは受動デバイスのため、 IPアドレスが取得できてARPリクエストに対するMACアドレスの返信ができれば良いだけなので、ネットマスクやデフォルトゲートウェイを処理する必要はありません。
- IPv4
- UDP
- DHCPクライアント
- ARP
- ICMP(ping用のEcho RequestとEcho Reply)
電源を投入するとネットワークのリンク状態をNICに問い合わせ、LANケーブルが未接続の場合はUSBモードへ移行します。 ケーブルが接続されているとネットワークの受信ループへ入るとともに、2秒間隔でDHCP DISCOVERを送信します。 DISCOVERに対するOFFERを受信すると、今度はDHCP REQUESTを2秒間隔で送信します。これに対するACKがあればアドレス取得完了とみなし、DHCPパケットの送信を停止します。
PCの場合、PCから能動的にデータを送信するためネットマスクやデフォルトゲートウェイを取得する必要がありますが、mAQUOSは受動デバイスのため、 IPアドレスが取得できてARPリクエストに対するMACアドレスの返信ができれば良いだけなので、ネットマスクやデフォルトゲートウェイを処理する必要はありません。
プロトコルの処理方法
パケットの解析はスタック状にはなっておらず、単なるIF分岐の連続です。
各プロトコルの詳しい解説は省きますが、タイプフィールドを見て各プロトコル解析への分岐、データサイズフィールドから次のペイロードへのオフセット計算だけしか行っていません。
チェックサムの確認を行っていないため、不正なパケットが来た場合何かよく解らない状態になるはずですが、PCやルーターを経由してそのような不正パケットが出てくることは無いでしょう。
IPフラグメントも非対応なので、Windowsのアプリケーション側では常にDFビットをセットして送信しています。
イーサネットモードの画像データはUDPパケットにカプセル化される以外はUSBモードと同じです。
ただしイーサネット+IPv4+UDPのヘッダ42バイトが付いて非効率になるので、UDPのペイロードに入るだけラインデータを詰め込むようにしてあります。
これによりイーサネット1フレームあたり最大28ラインのデータ転送が可能です。
RTL8019ASのバッファ割り当てについて
イーサネット受信にはちょっとした問題を抱えています。
RTL8019ASには受信バッファが16kバイトありますが、実はそれをフルに使えるのはCPUとの通信に16bitバスを使用した時だけで、PICとの接続のように8bitのモードで使用すると半分の8kバイトに減ってしまいます。
全メモリを使おうとすると、メモリの半分を超えたところでバッファの先頭にデータが書き込まれたり、バッファのポインタレジスタが意味のないアドレスに飛んだりとめちゃくちゃな動きをします。
さらに、バッファメモリーは送信用と受信用を分割使用しているため、メモリーの割り当て方によっては、受信したデータが送信バッファに入ったり、送信すべきデータが受信バッファに入ったりします。
RTL8019ASのデータシートには、
RTL8019ASはバッファを256バイトごとのページ単位で管理していて、合計32ページを送信と受信に静的に割り当てます。 少ないバッファをやり繰りする工夫として、起動直後はDHCPの長いパケットを送信するために送信バッファを2ページ確保し、 DHCPの処理が終われば送信側は1ページ、受信側は31ページと動的に割り当てを変更しています。
In 8 bit mode the PSTOP register should not exceed to 0x60, in 16 bit mode the PSTOP register should not exceed to 0x80.と記載されています。しかし、受信と送信のスタートアドレスには何も記載されていませんので、この問題に直面した時はかなり困りました。
RTL8019ASはバッファを256バイトごとのページ単位で管理していて、合計32ページを送信と受信に静的に割り当てます。 少ないバッファをやり繰りする工夫として、起動直後はDHCPの長いパケットを送信するために送信バッファを2ページ確保し、 DHCPの処理が終われば送信側は1ページ、受信側は31ページと動的に割り当てを変更しています。
Windows側のソフトを作る
PICのファームウエアのプログラムと平行してWindows側のアプリケーションを作らないと動作確認ができません。
そういえばWindowsのアプリというよりはドライバのレベルで実装しないとあかんな~と思ってひとしきり調べたのですが、やはり難しいですね。
私のPCのグラフィックボードには使っていないVGA出力もあるので、ここにダミープラグを付けて別のディスプレイがつながっていると見せかけ、その領域をキャプチャする事にしました。
ダミープラグ内部はRGBの信号線を51Ωの抵抗で終端するだけの簡単な構造です。EDIDが取得できないので「汎用非 PnP モニター」として認識します。
画像送信プログラム 2018/08/22版 要.NET Framework 4.5.2以上
右の画像はデスクトップ領域を取り込み、mAQUOSに送信するためのタスクトレイ常駐アプリケーションです。 実機が無くてもプレビュー機能があるので、操作を試すことができます。 設計当初から全画面更新でも35fps程度の速度が出るのが分かっていた事と、ディザで表示した場合にはほぼ全画面で更新が発生するので、 差分更新は設けず全画面一括書き換えのみを実装しています。(後日差分に変更しました)
USB接続の場合はシリアルポートの扱いになり、Windows内のバッファの使用量が表示されます。 転送が追い付かずバッファが100%になれば、自動的にターゲットfpsを抑制するように働きます。たいていは33~34fps位で頭打ちになります。
イーサネットモードでは、mAQUOSの起動時にDHCPで取得したアドレスを表示するので、それを入力します。 LPPとはLines Per Packetの略で、イーサネットの1フレームに収めるライン数を設定します。 これはMTUの小さなネットワークを経由するときにIPパケットが分割されないよう調整するもので、Path MTU Discoveryの手動版といったところです。
右の画像はデスクトップ領域を取り込み、mAQUOSに送信するためのタスクトレイ常駐アプリケーションです。 実機が無くてもプレビュー機能があるので、操作を試すことができます。 設計当初から全画面更新でも35fps程度の速度が出るのが分かっていた事と、ディザで表示した場合にはほぼ全画面で更新が発生するので、 差分更新は設けず全画面一括書き換えのみを実装しています。(後日差分に変更しました)
USB接続の場合はシリアルポートの扱いになり、Windows内のバッファの使用量が表示されます。 転送が追い付かずバッファが100%になれば、自動的にターゲットfpsを抑制するように働きます。たいていは33~34fps位で頭打ちになります。
イーサネットモードでは、mAQUOSの起動時にDHCPで取得したアドレスを表示するので、それを入力します。 LPPとはLines Per Packetの略で、イーサネットの1フレームに収めるライン数を設定します。 これはMTUの小さなネットワークを経由するときにIPパケットが分割されないよう調整するもので、Path MTU Discoveryの手動版といったところです。
UDPはハンドシェイクやフロー制御を行っていないため、fpsに関わらずバッファオーバーフローが起こる可能性があります。
これは1画面分の画像データが12kバイトあるのに対して、バッファメモリが8kバイトしかない事に起因しています。
最大フレームレートが35fpsと仮定すると平均データ転送速度は約3Mbpsになりますが、1画面分のデータは一度に10Mbpsの速度で届くので、パケット間にタイマーを入れるなど微妙な調整をしない限りオーバーフローは必ず起こります。
しかしWindowsはリアルタイムOSではないため、ミリセカンド単位の細かいタイマーはとても不安定ですぐにタイミングが狂いますし、その分解能は他のアプリケーションとの兼ね合いでいくらでも変化します。
1画面丸々送るとオーバーフローするならば…という事で、画面更新をインターレースや2分割から選択できるようにしてあります。
データ量が半分になることでフレームレートを変化させること無くバッファオーバーフローも低減できます。動きの速い画像だと目立つので高めのfpsにセットする必要があります。
こちらは2画面目。キャプチャするエリアの指定や画像の補正と表現方法です。
キャプチャの位置は任意の指定場所・ウインドウの追跡・マウスポインタの追跡・ディスプレイ全体の4種類から選べ、キャプチャ領域が400×240以外の場合は自動的にリサイズします。
数値指定のほか「ピッカー」を使えばキャプチャ位置をマウスで指定するGUIが表示されます。
実際のキャプチャ状況はプレビューウインドウを使って確認できます。(液晶画面を見ればよいと思うが…)
画像メニューでは画像の表現方法(単純閾値、動的閾値、ディザのモード)や濃淡補正、マウスまでキャプチャに含めるかなどが選択できます。 ディザは実験的な要素もあり、選択できる種類はとても多いです。
画像メニューでは画像の表現方法(単純閾値、動的閾値、ディザのモード)や濃淡補正、マウスまでキャプチャに含めるかなどが選択できます。 ディザは実験的な要素もあり、選択できる種類はとても多いです。
使ってみて分かったこと
液晶表示に関すること
- バックライトが無いため、室内の照明を反射させるような角度を選んで使ったほうがコントラストが良くなる。
- 誤差拡散ディザは画像の右下に誤差を拡散させる特性があるため、左上が動かない動画などでは更新部分の右下だけがフリッカーを起こして目につく。(右の画像)
- ディザを使うとドットが点滅を繰り返すので、相対的に反射率が下がったように見えて視認性が悪い。
USBに関すること
- USB接続は高いリフレッシュレートでも安定しており動作は非常に良好である。
- USBケーブルを素早く抜き差ししたり通信中にうっかりケーブルを抜くと、ftdibus.sysが原因でブルースクリーンになることがある。
Ethernetに関すること
- Wi-Fiのコンバータを通すことによりワンタッチで無線化できる。速度低下は一切ないため、これはとても便利である。
- ルータ側でNAPTの設定をしておけば遠隔地の画面でも受信することができる。
- Ethernetでも給電はUSBから行うのでケーブルが邪魔。
差分更新に関すること
- その後、画面の更新を1ラインごとの差分で送るようにしました。 当初の懸念通り誤差拡散モードでは効果が出にくいですが、静止画や差分範囲が画面全体の2/3以下の動画の場合はイーサネットのオーバーフローが起きなくなりました。
製作過程で苦労した点
- 基板製作とはんだ付けだけで一週間もかかった。RTL8019ASはピンピッチが狭いうえに本数が多く、途中ハンダブリッジを起こして一度作り直しているので、二度とやりたくない。
- 適当にダウンロードしてきたRJ45のライブラリのせいでLEDが点かなかったこと。
- RTL8019ASのバッファサイズがアクセスモードによって半分になってしまうこと。データシートにはわかりやすい記載が無い。
- NICのバッファ不足は致命的で、画面更新とは別にパケット間にも数ミリ秒のウエイトを挿入して使わないとすぐにバッファがオーバーフローを起こす。インターレース転送で何とか収まった。
- WindowsはリアルタイムOSではないため、ミリ秒オーダーのタイマーの精度が悪い。timeBeginPeriod()を見つけるまでに時間がかかった。