PICに内蔵されているUSB機能の実験パート4
(CDCクラスでパソコンとデータの送受信を行う:XC8コンパイラ編)

〔パート1〕 〔パート2〕 〔パート3〕   〔パート5〕 〔パート6〕 〔マイコンのトップに戻る〕


前回のパート2では、USBフレームワークの[microchip_solutions_v2013-02-15]ライブラリ内の、
18F14K50用 CDC - Basic Demo(USB CDCクラス) を使用してC18コンパイラで利用する方法を
記載しました。
今回はこれをXC8コンパイラで利用出来る様に変更改造したので紹介したいと思います。

ですので、まずはパート2から先に読むのをお勧めします。

"v2016-11-07"バージョン以降のUSBフレームワークではXC8に対応しました、
ですのでぇ、"v2016-11-07"バージョンでぇ動作実験を行った方が良いかもですぅ....
パート2にコンパイル方法を追記して置きました。 *2)

改造点:

・ コンパイル時のメモリーエリア割り付け方法の変更

・ 割り込み処理記述方法(main.c)の変更

・ プログラムサイズを小さくする為に、CDCクラスでは不要と思われる一部の機能やオプションを削除
  ちなみに同じプログラムを、"C18"でコンパイルした場合のコンパイルサイズは40%で、
  "XC8"では57.5%、だから更に機能を削除して現在は53.2%、PRO modeであれば35〜40%らしい。
  18F14K50はプログラムメモリ容量が16KBなので約8KBも使うって事よねぇ、でかい!
  オリジナルコードに変更しようかしら?

・ include用ヘッダーファイルは削除が面倒だったのでほとんどそのまま使用しています。
  "HardwareProfile - PIC18F14K50.h"は、18F14K50のデモボード用記述部分を削除
  "usb_hal_pic18.h"は、 XC8 Compiler用に変更

《ダウンロードプログラム》

このダウンロードプログラムは、[microchip_solutions_v2013-02-15]ライブラリの中の
USB”フォルダー内に有る”Device - CDC - Basic Demo\Firmware\USB Device - CDC - Basic
Demo - C18 - Low Pin Count USB Development Kit.mcp
”のプロジェクトファイルを流用しました
パート2のプログラムをXC8 C Compiler用に改造しています)

なのでぇ、プログラムライセンスは"Microchip Technology, Inc."です、
ってぇこんなに変更しておいて紹介しても大丈夫かいって言われそうな気がしなくもないのですがぁ..

パソコンとUSBで接続しデータの送受信を行う場合の雛型プログラムとして利用して下さい。

@ ここからダウンロードし解凍して下さい。

A 解凍すると"USB"フォルダーが出来ます、このフォルダーごと全て、
   適当なフォルダー下に移動させて下さい。

B 後は MPLAB IDE でプロジェクトファイル(USB\CDC - Basic\Firmware\CDC - Basic.mcp)を
   読み込みコンパイルし、18F14K50にプログラムを書き込みます。
   MPLAB IDE(V8.84)/MPLAB(R) XC8 C Compiler Version 1.00を使用しました。

   コンパイルすると"Warning [1311][1471] "が出ますが気にしなくてOKです。

 PS.
   MPLAB X(v4.10)/MPLAB(R) XC8 C Compiler Version 1.32等でコンパイルしたい場合は、
   "USB\CDC - Basic\Firmware\CDC - Basic.X"で行いましょう。

後の操作等はパート2と同じです、参照下さい。


ホストへデータを送受信する為の関数を説明 (ファイルは"usb_function_cdc.c"です)

 putUSBUSART(char *data, BYTE length)
  送信するデータの配列を指定バイト数だけセットする関数
  データをホストへ送信する時に使います、
  ホストからの[IN]パケットを受信した時にSIE(Serial Interface Engine)が自動的に送信する。
  ホストにデータのブロックを送信し続ける為に、CDCTxService( )関数を定期的に呼び出さなければ
  なりません。
   *data :送信するデータRAMの配列への格納先アドレスを指定します
   length:送信するデータのバイト数を指定します、(255未満でなければなりません)
  例)この例は送信1回で後は終わりなので例が悪いですけどぉ...
   void main(void)
   {
        char USB_In_Buffer[] = {"TEST"} ;

        putUSBUSART(USB_In_Buffer,4) ;
        while(1) {
             CDCTxService() ;
        }
   }
 putsUSBUSART(char *data)
  送信する文字列の配列を0x00までセットする関数
   *data :送信する文字列RAMの配列への格納先アドレスを指定します
        送信する文字列データの最大長は255未満でなければなりません。
        putsUSBUSART("TEST") ;

 ans = getsUSBUSART(char *buffer, BYTE len)
  指定した場所にOUTエンドポインを介して受信したバイトデータをコピーする関数
  利用可能なデータが存在しない場合は、'0 'を返します。
  USBUSARTIsTxTrfReady( )関数を実行していないといけません。
   *buffer :受信されたバイトデータを格納する場所へのポインタを指定します
   len   :エンドポイントの最大転送サイズ(64byte:CDC_DATA_OUT_EP_SIZE)
   ans   :受信したデータのバイト数をかえします
  例)
   void main(void)
   {
        char USB_Out_Buffer[64] ;
           BYTE len ;

        while(1) {
                if (USBUSARTIsTxTrfReady()) {
                     len = getsUSBUSART(USB_Out_Buffer,64) ;
                     if(len != 0) {
                          // 受信処理を行う
                     }
                }
        }
   }

《メモ》

今回USB規格内のCDCクラス(ホストとシリアル通信を行うUSB機器の事)について少し調べたので
覚書のメモとして残しておきます。

ディスクリプタ

これは全てのUSB機器が必ず持っていないとならない情報で、機器のクラス情報やベンダー情報に
通信する為の情報などです、接続時にホストから問い合わせ(GET_DESCRIPTOR)が有ります。

ここのプログラムでディスクリプタを記述してあるファイルは、"usb_descriptors.c"です。
内容については、「SyncHackのUSB/Descriptor」や こちらのPDF「4.USBデバイスの論理的構造とディスクリプタ 9頁」を見て下さい。
また、ディスクリプタに記述するCDCクラス固有のコードは、こちら「とりメモさんの 4. クラス固有コード」を参照下さい。

ディスクリプタ構成図

上の図は、CDCクラスのディスクリプタ構成図です。
新しい機能(例 マウス)を追加したい場合は、インターフェイスディスクリプタを追加する事になります、
また、通常はコンフィギュレーションディスクリプタは1個のみだが、
#2の構成が有る場合は[SET_CONFIGURATION]で切り替えたりが可能な様です。

尚、エンドポイントEP0は全てのUSB機器が実装している必要がある機能なので、
ディスクリプタには記述しません。

また、EP1 IN のインターラプト転送はこのソフトでは利用していないです。

USBのプロトコル

USBにて通信する際のデータフレーム構造やパケットの種類・論理プロトコルの話は、
[picfun(電子工作の実験室)]さんのこちら「USBの通信プロトコル」を見て下さい。

また、ホストとデバイス間のアプリレベルで通信される論理的な通信はエンドポイントパイプの概念で
行われます。この概念の話は、こちらを見て下さい。

下記の方に記載「コントロール転送時のプロトコル」での通信図の構成としては、

通信図の構成

データステージは、数トランザクションからなります、又はなしです。

USBの基本動作

USB機器をパソコンに接続した場合に、デバイスとホスト間でやり取りされる基本の動作
(Enumeration:エニュメレーション)です、これは全てのUSB機器で行われます、
こちら「USBのプラグ&プレイ」をまず読みましょう。
この通信はエンドポイント0(EP0 IN/OUT)を使い"コントロール転送"モードで行われる。

[USBデバイスのステート]

通常(USB規格)は@〜Eまでの状態がエニュメレーション状況に応じて推移する。
ここの"usb_device.c"ではPICが起動し初期化を行った状態時、或いはUSBバスに接続されていない
状態(プルアップ接続は無し)の時を"DETACHED_STATE"としている。

また、[SET_ADDRESS]要求パケットが来た時点では"ADR_PENDING_STATE"となっていて、
アドレスを設定した時点で"ADDRESS_STATE"へ推移します。

@ Attachedステート(USBバスに接続状態:ATTACHED_STATE)
A Poweredステート(電源投入状態:POWERED_STATE)
B Defaultステート(EP0 受信準備完了状態:DEFAULT_STATE)
C Addressステート(指定アドレス設定状態:ADDRESS_STATE)
D Configuredステート(コンフィグレーション完了状態:CONFIGURED_STATE)
E Suspendステート(サスペンド状態:省電力モードへ移行時)

Suspendステートが"usb_device.c"には無いが、
サスペンド状態の管理は"USBBusIsSuspended"フラグ変数で行っている様です。

標準デバイスリクエスト

ホストからのリクエストは、上記USBの基本動作時や設定の変更/読込み時などに、
"コントロール転送"によって行われます。

リクエストの種類は、「おなかすいたさんのwikiHP」やこちらの「5.USBシステムの動作概要12頁」
を見て下さい、今回はこの中から[GET_DESCRIPTOR] [SET_ADDRESS] [SET_CONFIGURATION]
[SET_FEATURE] [CLEAR_FEATURE]のみ実装しています。 *2)

CDCクラス固有のリクエスト

標準デバイスリクエスト以外にCDCクラス固有のリクエストが有ります、これらはシリアル通信を
エミュレートする際のボーレート等の通信情報やフロー制御情報等のやり取りに使用されるが、
今回はこの機能は削除(又はコメント)して未実装にしています。
内容は、こちら「とりメモさんの 6.Communications Class 固有のメッセージ」や
「ポチの玉手箱さんのこちらHP」を見ましょう。

コントロール転送時のプロトコル

上記USBの基本動作時に行われるプロトコルの概要を記載します。(但し、PICでの内容)
コントロール転送は、エンドポイント0(EP0のIN/OUT)に対して行う事になっています。
コントロール転送のデータは8バイトづつの転送(1トランザクションあたり)となります。

こちらの「AzusaTekunoさんのHP」に、USB機器とホストの接続時の通信内容ダンプが有ります、
参考になるので一見しましょう。
 *2)

[GET_DESCRIPTOR]

この要求の1回目は、デバイスディスクリプタ[DEVICE]の送信要求が行われます、
しかしこの時はデバイスディスクリプタの最初のデータ8バイトのみしかホストは受け取らず、
8バイト読んだらステータスステージに移ってしまいます。

2回目の要求時は、
デバイスディスクリプタ[DEVICE]とコンフィギュレーションディスクリプタ[CONFIGURATION]と
ストリングディスクリプタ[STRING]の3回にわたり全てのデータを受け取ります。
但し、ストリングディスクリプタはいらない場合が有るようです、また、要求されたストリング番号の
データのみ送る事になります。

GET_DESCRIPTOR通信図

@ PICはハンドシェークパケットの送受信完了時に割り込みが発生します、
  割り込みが発生するとUSTATレジスタにどこのエンドポイントで発生したか書かれているので
  それを読み取り該当するBDT(EP0 OUT)よりパケットのデータを得ます。
  (USTATレジスタは4バッファ有るので、4個とも参照する)

A パケットのデータが[SETUP]パケットで[GET_DESCRIPTOR][DEVICE]なら
  デバイスディスクリプタのデータ情報をBDT(EP0 IN)にセットします。

B ホストから[IN]パケットが来ればSIEが自動的にBDTの情報でデバイスディスクリプタのデータを
  送信します。

C ホストから[ACK]パケットが来た時点で次のデバイスディスクリプタのデータ8バイトをBDTにセット
  します、以降は繰り返し。(但し途中でステータスステージが来たら終了処理に移ります)

データを送信する場合は、トランザクションごとに[DATA0] [DATA1] [DATA0]と交互に送る事に
なっているが、これはデータの欠落防止の為です。

例えばホストに30バイト送信する場合は、[8byte][8byte][8byte][6byte]と4回に分けて送信されるが、
24バイト等の場合は、[8byte][8byte][8byte]と丁度3パケットになる。
然し、この丁度の時は最後にデータ0の空パケット([SYNC][DATA0/1][CRC])を送信する事に
USB仕様がなっている様である、だから[8byte][8byte][8byte][0byte]です。

[SET_ADDRESS]

この要求はUSBデバイス機器に対してホストからアドレスが割り振られます。
この要求が来るまではアドレス番号0(ADR0)で通信していて、これ以降は割り振られた
アドレス(例えばADR2)で通信する事になります。

また、この要求にはデータステージは有りません。

SET_ADDRESS通信図


[SET_ADDRESS]は、[SETUP]パケットのステージ時にアドレス番号も伝えられますが、
実際にアドレス番号を設定するのは、ステータスステージが正常に終了した後になります。

[SET_CONFIGURATION]

この要求が来たら、通信する為のエンドポイント(EP1/2)の初期化やCDCクラス固有の初期化などを
行います。
これ以降が終了後、EP2によるシリアル通信(バルク転送)が出来る様になります。

尚、EP1はホストへのインターラプト転送で機器からの事象通知メッセージ送信用みたいらしいが
ここでは利用していない。

SET_CONFIGURATION通信図


シリアル通信(バルク転送)

1トランザクション辺り64バイト単位で転送し、最大255バイトまでのデータを連続転送可能です。

受信するアプリは、BDT(EP2 OUT)のSTATレジスタのUOWNビットを常に監視します、
で、SIEは受信されればBDTに情報をセットして、UOWNビットをCPU側(0)にするのでアプリ側は
受信したと判断します。
アプリは受信データを取り出し、次の受信の為にBDT(EP2 OUT)を準備し、UOWNビットを
SIE側(1)にして置きます。

バルク転送(送信)通信図


データを送信する場合は、putUSBUSART( )関数で行います、これを発行すると関数はBDT(EP2 IN)に
送信情報をセットし、UOWNビットをSIE側(1)にして置きます。
後は、ホストから[IN]パケットが送られて来ればSIEが自動的にBDT情報でデータを送信します、
送信後はUOWNビットをCPU側(0)にします。

[IN]パケットは”送信するデータは有りますかぁ?”ってな感じでホストから定期的(1ms)に送られて来る
ので、SIEBDTを見て送信データが無ければホストに[NAK]パケットを送信します。

ホストから[IN]パケットSOFトークンを定期的(1ms毎)に送ってきますがぁ、
  エニュメレーション終了以降はアプリケーション側はこれに応える必要はないと思われます、
  USBモジュールのハード(SIE)が行ってくれます。
  なのでぇ、アプリはデータが受信されたかを定期的にチェックし、後は他の仕事をこなし必要時に
  データ送信を行うで良い様に思います。

バルク転送(受信)通信図

BDT(バッファディスクリプタテーブル)

受信する場合は、EP(0/2) OUTに該当するBDTエリアへ受信データの格納先アドレスやデータサイズに
ステータス情報(UOWNビットはSIE側にする)をセットして置く必要が有ります。

送信する場合は、EP(0/2) INの該当するBDTエリアへ送信するデータの格納先アドレスやデータサイズ
ステータス情報(UOWNビットはSIE側にする)をセットして置く必要が有ります。

サスペンド

UIRbits.IDLEIFの割り込みレジスタビットがONすればホストがアイドル状態に移行したと判断します。
アイドル状態は、ホストからの何らかの通信がバス上で3ms以上途絶えた場合に発生します、
と言う事はホストからは定期的に何らかのパケットを送り続けると言う事です。
CDCクラスでは EP1 IN(2ms) / EP2 IN(1ms) にパケットが定期的に送られてきます。

IDLEIFの割り込み発生で、UCONbits.SUSPNDをONにするので、PICはUSBモジュールと
PIC内部周辺回路を省電力モードにします。
アイドル状態に移行した場合はUSB規格で0.5mA(100mA時)に消費電流を抑える必要が有る為、
バスパワーで動作しているユーザ回路は考慮が必要でしょう。
回路等を制御する場合は、USBCBSuspend( )関数に記述しましょう。

ホストをアイドル状態から復帰させる場合は、USBCBSendResume( )関数をコールします、
今回のプログラムでは何処からもコールされていない、ユーザが任意に利用するようである。

UIRbits.ACTVIFの割込みレジスタビットがONすればホストがアイドル状態から復帰したと判断します。
割込み発生でUCONbits.SUSPNDをOFFにしてUSBCBWakeFromSuspend( )関数が
コールされるので、ユーザ回路の復帰制御等を行う場合はこの関数に記述しましょう。

USBCBSendResume( )関数を利用するには、"USBGetRemoteWakeupStatus( ) == TRUE"で
  ないとダメで、"TRUE"にするにはホストから[SET_FEATURE]パケットを受信した時の様である。
  では、[SET_FEATURE]パケットを送るタイミングは?、
  PCのデバイスドライバのプロパティの「電源管理」タブ "このデバイスで、コンピュータのスタンバイ
  状態を元に戻す事が出来るようにする
" チェックボックスがチェックされた時と思われるが、
  「USB Serial Port」ドライバのプロパティに「電源管理」タブが見当たらない。

  コンフィギュレーションディスクリプタの設定に"_RWU"を追加すればリモートウエイクアップの
  機能をデバイスが持っている事をホストに知らせる事が出来るのだがぁ.....
  追加しても「電源管理」タブが現れない。他にやり方が有る様だがちょっとぉ不明です。

 PS.
  「電源管理」タブHIDクラスにのみ現れるのかも?
  また、”チェックボックスがチェックされた時”ではなくチェックされていれば、PCがスリープ状態に
  移行する時に[SET_FEATURE]パケットを送って来る事が判明した。
  尚、"_RWU"が指定してあれば、スリープ状態から復帰する時に[CLEAR_FEATURE]パケット
  送って来ます。
  それからPCがスリープ状態の時に、"TRUE"になっていなくても強制的にUSBCBSendResume( )
  関数を実行させて見た結果、PCは正常にウエイクアップした。  *1)

《その他》

その他の参考先HPとして、「HEROさんのPDFでUSB概説」も参考になります、見ておきましょう。

USB仕様はUSBインプリメンターズ・フォーラム(USB-IF)のこちらを参照下さい。
各デバイスクラスのドキュメントはUSB−IFのこちらを参照下さい。 *2)

18F14K50のデータシートはこちらです。

今回同じプログラムを"C18"と"XC8"でコンパイルさせたら"C18"がプログラムサイズの面で
有利かもって事が判った。
現在"XC8 C Compiler"では 53.2% だが、これは"USB_INTERRUPT"モードでコンパイルした場合で、
"USB_POLLING"モードでコンパイルすれば 48.4% になります。

USB電源を利用する場合で初期は100mAの電力負荷です、コンフィギュレーションディスクリプタにて
最大500mAまで変更可能ですが[GET_DESCRIPTOR][CONFIGURATION]を問い合わせ後にホストは
提供します、なのでぇ、USBを挿した直後は100mAで有る事に注意が必要かもね。

次回はHIDクラスのキーボードでも調べてみます。  *1)



"v2016-11-07"用記事とリンク切れ見直し(*2) 2017/01/22
追記(*1) 2013/09/19


【きむ茶工房ガレージハウス】
Copyright (C) 2006-2017 Shigehiro Kimura All Rights Reserved.