【デバイスプログラム】
前ページで紹介した「基本デバイスプログラム」と同じ機能のデバイスプログラム
をC言語で記述したものです。ただし液晶表示器の機能は省いています。
この基本デバイスプログラムは、CCS社のC言語で記述されています。
この基本デバイスプログラムをベースにすれば、デバイスの機能動作部分だけ
を、ユーザー部分として追加するだけで、多くのデバイスを完成させることが可
能となります。
基本デバイスプログラムは下記をダウンロードして解凍すれば、CCS社のコンパ
イラを統合したMPLABでコンパイル出来ます。
(10/13)
(1/17 バグ修正)
(2/14)
上記の基本デバイスプログラムを整理してもう少し使いやすくしました。
USBのデバイス制御の部分を独立して切り離し、ユーザ処理部分だけ独立で
出来るようにしました。
これでUSBそのものの動作を知らなくてもアプリケーションを作成することが
可能です。(極端ですが???)
★ 改良版 基本デバイスプログラム(VerUp 2/14、VerUp3/3))
(1/17) CCSの新しいバージョンでコンパイルすると動作しない不具合を修正
(2/14) SPI動作安定化のためクロックエッジ H_TO_L をL_TO_H に変更
(3/3) TX_2()のエラー処理のT1SIZE→T2SIZEに修正
【全体構成】
この基本デバイスプログラムの全体構成は、アセンブラ言語と同様に下図の
ようになっていますが、プログラムブロック数は少なくなっていて、全体は3つ
のモジュールから構成されています。 メインプログラムである usbn9603d.c
以外は定義だけですので、そのまま共通に使えます。
メインプログラムの中に実際の処理プログラムが全て含まれていて、実際に
デバイスを開発するときには、この中にある「ホストコマンド処理追加部」に各
パイプの「機能」を果たす部分を追加し、必要があれば「デバイスディスクリプタ
データ」(usbdef.h)を変更します。
この基本デバイスプログラムには、アセンブラベースと異なり、液晶表示器の
制御が組み込まれていません。必要であれば、次ページの計測ロガ-を参照
して組み込んで下さい。
【エンドポイントの定義】
基本デバイスプログラムとして定義されているエンドポイントは下表のように
なっていて、アセンブラベースのものと同じです。
USBN9603が、コントロール+IN3個+OUT3個の合計7個のエンドポイント
が構成出来るようになっていますので、全部を使えるように定義しています。
この定義は、ディスクリプタデータで行っていますので、これを変更するとき
には、ディスクリプタデータを修正する必要があります。
またエンドポイントはNo0以外は全て片方向となっていますので、使うときに
は注意が必要です。
パイプNo エンドポイント IN/OUT 転送サイズ ユーザー処理追加部 無し 0 IN/OUT 8バイト 無し 0 1 バルクIN 8バイト 1 2 バルクOUT 8バイト DO_RX1 2 3バルクIN 8バイト 3 4 バルクOUT 8バイト DO_RX2 4 5 バルクIN 16バイト 5 6 バルクOUT 16バイト DO_RX3
上表のように、エンドポイントはすべてバルク転送モードとして使うように設定
しています。
そしてパイプ番号も順番に付けていますが、エンドポイント0が異なり、番号が
ずれていますので使うときに間違えないよう注意が必要です。
【プログラム概要】 (2001/7/19)
このUSB基本ライブラリのUSB制御メインプログラムの概略フローは、
実際のUSBの処理は全て割込みで起動される割込み処理関数usb_isr()の
下図のようになります。全体は大きく3つの部分からなっていて、
初期化関数init_usb()は、PIC自身のSPI通信に関するポートの初期設定を
行ったあと、USBコントローラUSBN960xの初期化を行い、最後にPICの
ポートRB0のINT割込みを許可してリターンします。
独立した関数になっていてユーザーのアプリケーションプログラムで
最初に使います。
中で実行されます。ここで送信と受信、最初のコンフィギュレーション
などが全て実行されます。さらに、受信処理の中で、受信したデータの
処理に関する部分はユーザ関数として外部関数となっています。
従って、ユーザーのアプリケーションプログラムの中で、この部分を記述
し追加します。残りは、送信用の関数で、ユーザーが送信データを用意
してこの関数を呼ぶだけで、USBへの送信を実行し戻るようになってい
ます。この送信用関数はパイプ毎に3つ用意されています。
USBNの割込み処理のフローは全く同じです。
【ユーザー処理部】 (2001/7/19)
USB基本ライブラリを使ってユーザーアプリケーションを作る方法を
説明します。USB基本ライブラリにはUSBの通信に必要な全ての内容を
含んでいますので、ユーザーアプリケーションでは、受信したデータの
処理と、折り返しに必要なデータの送信処理のみ記述すれば良いように
なっています。ユーザーがUSBデバイスを開発するとき、このUSB基本ライブラリを
使ってデバイス機能を実現する方法についてために追加すべき部分は下記
のようになります。
(1) PIC初期化
PICデバイスのデバイスとしての機能にあわせて入出力の初期化部分を
記述する必要があります。
(2) メインループ内処理
割込み待ちのメインループでは、割込みの無い時に常時実行する機能、
大部分はデバイス自身が実行しなければならない機能ですが、それを
この部分に追加します。
(3) USB送受信処理
ホストからのポーリングに対して応答する形で、データを受信してデータ
を解析し、そのデータに従った処理を追加します。
このデータの内容フォーマットはホストプログラムとの間で任意に決める
ことが出来ますので自由に設定することが出来ます。
そして受信に応答する形で送信処理を追加します。
このユーザー処理追加部を雛型となるリストで説明します。まず初期化の
部分ですが、下記リストのような形で記述します。まずPICのデバイス設定とクロックの設定です。クロックはUSB内部で
ディレイ関数を使っていますので必須です。つづいてユーザーが新たに使う
定数や変数の定義を記述します。必要であれば関数プロトタイプの定義も
します。次にUSBのバッファサイズを設定します。送信バッファと受信バッファ
それぞれ3個ずつ必要で、それぞれ最大64バイトまでの大きさです。
PICの場合もともとどのPICもデータメモリが少ないので必要最小限のバッファ
サイズにした方が良いでしょう。最後にUSB基本ライブラリのメイン部分をインクルードします。これだけで、
USBが使えるようになります。USB基本ライブラリのリンクはこれで完了します。
次は、ユーザーアプリケーションのメイン関数の作り方ですが、これには
下図リストに示すような基本的な形があります。この基本形の順序が重要なの
で意識しておいて下さい。まず、ユーザーが使用するポートの入出力モードの
設定と、A/D変換その他の周辺モジュールの初期設定を行います。その最後にUSBの初期化関数(init_usb())を記述します。このUSB初期化関数の
中の最後で、グローバル割込みを許可していますので、全体の初期化の最後に
置くことが必要です。もし、USB以外の割込みを使う場合には、USB初期化
関数の前で各モジュールの割込みイネーブルの設定を済ませておく必要があり
ます。初期化が完了したらその後にメインループ処理が続きます。USB以外の処理
はこのループの中に記述する必要があります。このループの実行中にUSBの
割込みが受けつけられ処理されます。
次に必要なのは、USBの3つの受信パイプに対応した処理です。
このパイプの処理関数は名前があらかじめ決められていますので、その関数名
で記述することが必要です。基本のフォーマットはリスト4−4−3のように
します。つまり、do_rx1( )、do_rx2( )、do_rx3( )の3つの関数記述となります。
送信処理は、USBの場合、受信に応答する形での送信しかありませんので、
受信処理の中に送信処理が記述されることになります。
送受信処理に必要な受信データはバッファに格納されていますが、
パイプ番号と受信処理関数、受信データ名は、下表のように対応していて、
それぞれinteger型の配列に格納されています。
パイプ番号 EP 対応処理関数 対応バッファ配列名 受信 1 2 do_rx1() R1_DAT[i] 3 4 do_rx2() R2_DAT[i] 5 6 do_rx3() R3_DAT[i] 送信 0 1 usb_send1() T1_DAT[i] 2 3 usb_send2() T2_DAT[i] 4 5 usb_send3() T3_DAT[i]
さらにこの受信処理の記述方法をもう少し詳しく説明します。つまり
do_rx1( )からdo_rx3( )の関数の中で受信処理を記述しますが、その記述方法は
下記のようにします。まず、各受信処理関数は、実際にUSB経由でパソコン側からデータが送ら
れて来て、デバイス側の受信が完了すると自動的に呼び出されます。
従って受信処理の各関数が呼ばれた時には、データがバッファに格納された
状態ですので、単純にこのバッファのデータを参照すれば良いことになります。実際に例題で説明すると、受信したデータをPICの入出力ポートのデータとし
てポートに出力するには下記リストのようにします。R2_DAT[i]という受信
データを配列としてそのまま参照しています。
このリストのように、いきなりR2_DAT(i)の配列データを呼び出して使えば
良いのですが、どのデータに何が入っているのかということは、ホスト側の
パソコンのプログラムとの間でユーザーが任意に決めることになります。
例えばこの例題の場合には下記のようにデータの内容を取り決めています。R2_DAT(1) :ポートA用出力データ
R2_DAT(2) :ポートB用出力データ
次に受信に対する折り返しの送信出力をする場合のプログラムの作成の仕方は
下記リストのようにします。受信データの処理をしたあと、送信するデータを
配列に格納してから、それぞれのパイプ用の送信関数を呼びます。
送信関数としては下記の3つが用意されています。また、送信用にパイプ毎に
配列としてデータ名が決められていて上表のようになっています。
usb_send1( ) : パイプ1用送信関数
usb_send2( ) : パイプ2用送信関数
usb_send3( ) : パイプ3用送信関数
実際の記述方法は下記のようにします。