前ページまでは弊社が提供した汎用的ファームウェアsimpleio.iicを利用してシステムを完成させましたが、より複雑なシステムになるとファームウェアをシステムに特化させた方が、ウィンドウズアプリの負担が軽減されパフォーマンスが向上します。そこで、このページでは以下を目標にします。
1.ファームウェアに早押しボタンに必要な機能だけコードする。
2.ファームウェアに早押しボタンを押したときにPCに情報を送る割込み制御をコードする。
3.上記に対応したウィンドウズアプリを作成する。アプリは割込み信号を別スレッドを作って受ける。
USB通信機能を持ち、かつ個別のハードウェアを動作させるファームウェアを一から作ることは非常に困難ですが、Cypress社はUSB通信機能のコードをそのまま利用できるサンプルコードを提供してくれています。これをスケルトンとして活用し早押しボタンを動作させるためのコードを加えます。
1. C:\GigatecEZ-USB\Firmwareフォルダの下にButtonフォルダを作成します。ここに、C:\Cypress\USB\CY3684_EZ-USB_FX2LP_DVK\1.1\Firmware\Bulksrcフォルダにあるファイルbulksrc.c、dscr.a51、およびfw.cをコピーします。bulksrc.cをbutton.cに改名します(これらをスケルトンとして活用します)。
2. フォルダデスクトップ上にあるKeil uVision2アイコンをダブルクリックして、uVision2を立ち上げます。Projectメニュー → New Project...コマンドを実行し、C:\GigatecEZ-USB\Firmware\ButtonフォルダにButton.Uv2を作成します。
Select Device for Target 'Target1'ウィンドウが表示されたら、Cypress Semiconductor下のEZ-USB
FX2を選択してOKボタンで閉じます。
3. 左端ツリーのTarget 1の+をクリックするとSource Group 1が表示されますから、それを右クリックします。Add Files
to Group 'Source Group 1'コマンドを実行します。
まず、ファイルの場所をC:\GigatecEZ-USB\Firmware\Buttonフォルダにします。ファイルの種類をAll FilesにしてButton.c、fw.c、dscr.a51を選択してAddします。
次に、ファイルの場所をC:\Cypress\USB\CY3684_EZ-USB_FX2LP_DVK\1.1\Target\Lib\LPフォルダにします。EZUSB.libとUSBJmpTb.OBJを選択してAddします。Closeボタンで閉じます。
左端ツリーにAddした5つのファイルがリストされます。
4. Projectメニュー → Components, Environment, Books...コマンドを実行します。Folders/Extensionsタブを選択します。□
Use Settings from TOOLS.INIのチェックを外し、INC:にC:\Cypress\USB\CY3684_EZ-USB_FX2LP_DVK\1.1\Target\Inc\;C:\Keil\C51\INC\を入力します。OKボタンで閉じます。
5. 左端ツリーのTarget 1を左クリックして選択します。Projectメニュー → Options for Target 'Target
1'コマンドを実行します。
Outputタブをクリックします。□ Browse Informationのチェックを外し、□ Create HEX Fileにチェックを入れます。□
Run User Program #1にチェックを入れ、その右の空白にC:\Cypress\USB\CY3684_EZ-USB_FX2LP_DVK\1.1\bin\hex2bix
-i -f 0xC2 -o Button.iic Button.hexと書き込みます(プロジェクト名に応じてButton.iic/.hexの名前を変更します)。
BL51 Locateタブをクリックします。□ Use Memory Layout from Target Dialogのチェックを外し、Code
Rangeに0x80-0x0FFF、Xdata Rangeに0x1000と入力します。
OKボタンで閉じます。
6. Projectメニュー → Build Targetコマンド(F7)を実行します。ビルドの結果がuVisonの下端に表示されますが、最下行が0
Errors, 0 WarningsでC:\GigatecEZ-USB\Firmware\Buttonフォルダ内にButton.iicが作成されていれば、取り合えずButtonの開発環境は正しく整備されました。
button.cに含まれるbulksrc.c独自のコードを削除します。作業を失敗するリスクを考えてbutton.cのコピーを作っておきます。
次に、button.cの以下の行をわかりやすいように////でコメントアウトします。
23,24行
34行
66,67,68,71,72,74,75行
82,83行
89〜106行
コメントアウトが終わったら、一旦F7キーを押してでビルドしてみます。エラー/警告が出なければ////でコメントアウトした行をすべて削除し、更にTD_Poll()の中身をすべて削除します。もう一度F7キーを押してでビルドしてみます。エラー/警告が出なければOKで、先ほど作成したコピーファイルも消去します。
この状態のButtonプロジェクトは、今後独自の開発をする場合の出発点にできますから、スケルトンとしてフォルダごと保存しておきましょう。
ファームウェアは右図のような構造、すなわち初期化後はぐるぐると同じ処理を繰り返して回っている、という構造をとっています。そして、USB通信に必要なコードが上記スケルトンには既に記述されていて、独自のコードをTD_Init()に初期化時に必要な処理、TD_Poll()に通常処理を書き込めばファームウェアとして完成します。
1. 初期化処理TD_Init()
早押しボタンの初期化に必要な処理は、基本的には「PD0を出力に他を入力に方向付け、リセットを出力する」だけですが、プッシュボタンA,B押し下げ時に割込みを発生させるための初期化コードも必要です。
EZ-USBのPort A/B/Dの各ピンを方向付けるレジスタはそれぞれOEA/OEB/OEDです。OEはOutput Enableの略でこのレジスタの各ビットに1を書き込めばそのビットの出力が可能になる、すなわち出力に設定されます。0だと入力です。なので、PD0のみを出力に他を入力にするコードは、
OEA=OEB=0; OED=0x01; //Eq.1
です。リセット出力については、通常処理の時も行うので以下のように関数にしましょう。これをTD_Init()関数より前に書き込みます。ネットのページだとタブとか空白がうまく使えないので以下のコードをコピーしたらuVision2の上で成形してください。
void reset(void)
{
IOD=0;
SYNCDELAY;
IOD=1;
}
ここで、IOA/IOB/IODはそれぞれPortA/B/Dの内容を保持するレジスタで、Portに8ビット一気に出力する場合はIOx=y(x=A,B,D,
yは出力値)、Portから8ビット一気に入力するときはy=IOx(yは入力値)と書きます。 SYNCDELAY;はウェイトでIODをある程度の時間0でキープするためのおまじないです。尚、上記のコードではPortDのbit1から7にも同時に出力する形になりますが、それらは入力に方向付けられているので実際のPD1からPD7ピンにそれが出力されることはありません。
尚、各1ビットのみに入出力する書き方もあります。例えばPDx=y(x=0-7, y=0または1)とすれば指定されたビットに出力され、他のビットは変化しません。また、y=PDxとしてそのビット値のみを読み込むこともできます。
TD_Init()関数のEq.1の後にreset();と書いてリセット関数を呼び出し、初期化時のリセットを実行します。
次に、以下のように割り込み初期化コードを記述します。
(1) クローバル変数InterruptReasonを定義する(reset()関数の前)
BYTE InterruptReason;
(2) TD_Init()の最後に以下の5行のコードを付け加える。
IT0=IT1=1;
PX0=PX1=1;
TCON &= 0xF5;
EX0=EX1=1;
InterruptReason=0;
意味は難しいので説明しませんが、精確さを犠牲にして大雑把に言うと、PAnピン(INTn)(n=0または1)の割り込みを有効にしたければ、PXn,EXnを1にします。また、TCON&=0xFD;(INT0)またはTCON&=0xF7;(INT1)とします(つまり、TCONのbit1またはbit3を0に設定します)。今回は両方有効にするのでTCON&=0xF5;と記述します。ITnについては後述します。
2. TD_Poll()コード
通常動作として以下の機能を設定します。
● [リセット機能] PCからEndPoint 02に0が送信されたら、早押しボタンをリセットする。
● [割込み機能] 早押しボタンA(B)が押されたら、EndPoint 86に0(1)を書き込んでPCへ送信する。
(1) リセット機能
以下のコードをTD_Poll()内に書き込みます。
WORD count;
if (!(EP2468STAT & bmEP2EMPTY))
{
count = (EP2BCH << 8) + EP2BCL;
if (count==1 && EP2FIFOBUF[0]==0) reset();
SYNCDELAY;
EP2BCL = 0x80;
}
上記コードの内容は以下の通り。
[1行目] ローカル変数 countの定義
[3行目] EP2468STATレジスタはEndPoint 02,04,86,88の状態を保持していて、このコードによってPCがEndPoint
02にデータを書き込んだかどうかをチェックしている。データが書き込まれていれば以下の6行が実行されます。
[5行目] EP2BCHレジスタとEP2BCLレジスタはEndPoint 02に格納されたデータ数を格納するレジスタペアでそれぞれ上位バイトと下位バイトです。それを合わせて変数countにデータ数を入力しています。
[6行目] EP2FIFOBUFはEndPoint 02へ送られたデータを格納するバッファで、EP2FIFOBUF[0]はその先頭バイトです。PCから送られたデータ数が1バイトでその値が0のときreset()が実行されます。
[7,8行目] EP2BCLはその内容を読んだときは3行目のようにEndPoint 02のデータ数の下位バイトとですが、0x80を書き込むと「EndPoint
02のデータは処理しましたので、次のデータを書き込んで下さい」とPCに通知する意味となります。
(2) 割込み機能
以下のコードをTD_Poll()内、(1)の次に書き込みます。
if (InterruptReason)
{
EP6FIFOBUF[0] = InterruptReason-1;
InterruptReason=0;
SYNCDELAY;
EP6BCH=0;
SYNCDELAY;
EP6BCL=1;
}
さらに、C:\GigatecEZ-USB\Firmware\Button_Completedフォルダ内のisr.cをC:\GigatecEZ-USB\Firmware\Buttonフォルダにコピーします。Add
Files to Group 'Source Group 1'コマンドでisr.cをプロジェクトに加えます。
以上で一旦F7キーを押してノーエラーでビルドできることを確認しましょう。ビルドできたら、EZ-USB FX2 56pin [1]の要領でCyCotrol.exeを用いてC:\GigatecEZ-USB\Firmware\Button\Button.iicをEZ-USBに書き込んでください。EZ-USBには既にsimpleio.iicが書き込んであるため、それをButton.iicに書き換えなければなりません。従って、PROMジャンパを外してからEZ-USBをUSBケーブルに接続し、EZ-USBが...NO
EEPROM Deviceと認識させるようにしてから、PROMジャンパを戻すことを忘れずに。
右図にようにファームウェアはプッシュボタンの押し下げ時に反応して一旦通常処理のループを中断し、割込み処理ルーチン(isr.c)に飛びます。ボタンA(B)の押し下げならInterruptReasonに1(2)を入力し、通常処理に戻ります。通常処理では毎回InterruptReasonの値を監視していて、それが0以外になったときInterruptReason - 1の値をPCに送りInterruptReasonを0クリアします。
以下により詳しくコード内容を説明します。
TD_Poll():
[1行目] InterruptReasonが0以外になる、すなわち割込みが発生していたら、以下の8行のコードを実行します。
[3,4行目] EP6FIFOBUF[]はPCへUSBを介してデータを送るためのバッファでEP6FUFOBUF[0]はその先頭バイトです。それにInterruptReason-1を格納します。格納が終わったらInterruptReasonを0クリアします。
[5-8行目] EP6BCHレジスタとEP6BCLレジスタはEP6FIFOBUF[]内に格納したデータ数を指定するためのレジスタペアです。EP6BCH(BP6BCL)が上位バイト(下位バイト)です。これらの値を書き込む際にはSYNCDELAYでウェイトを挟みます。そして下位バイトのEP6BCLに0以外を書き込むことにより、実際にEP6FIFOBUF[]の内容がUSBを介してPCへ送られます。今回送るデータは1バイトだけなので、EP6BCH:EP6BCL=1と指定します。
isr.c
INT0(またはINT1)割込み(プッシュボタンA(またはB)の押し下げに対応)の処理は、void ISR_EXTR0(void) interrupt
0 {}関数(またはvoid ISR_EXTR1(void) interrupt 2 {}関数)の中に記述します。そしてこれらの関数はbutton.cの中に書いてはいけません。というのは、button.cの先頭に、
#pragma NOIV
という記述があるため、このファイルには通常の割込み処理用関数(「割込みベクトル」を生成するタイプの割込み処理関数)を書き込めないからです。button.cの中には、後の方にUSB関連の割込み処理関数が並んでいますが、これらは特殊な割込み関数で「割込みベクトル」を生成しません。
割込み関数
[1,4行目] EX0(EX1) を0にするとその割込みは禁止され処理されません。多重割込みを禁止するための措置です。割込み処理終了時に1に戻して割込み許可します。
[2行目] 対応するピンの値(PA0またはPA1)が0であることを確認して、正しい割込みであることを確認します。その上でInterruptReasonの値をセットします。
[3行目] INT0(INT1)割込みが発生すると自動的にTCONレジスタのbit1(bit3)が1にセットされ、その割込みが発生したことを記憶しますが、割込み処理が終わったら0クリアしておきます。ただし、この行がなくてもハードウェア的に自動的に0クリアされるようです。
ハードウェア割込みは対応する割込みピンの値が1から0に変化したときに発生します(右図)。ただし、発生のさせ方には1→0の変化を検出するもの(エッジトリガ割込)と、0という値を検出するもの(レベルトリガ割込)とがあります。そしてIT0またはIT1のビットを1にするとエッジトリガ、0にするとレベルトリガを選択できます。今回のシステムでは必ずエッジトリガを選択しなければなりません。というのも、1回のボタン押し下げで0に落ちた割込み信号がリセットがかかるまで永遠に0のままであるため、レベルトリガを選択すると1回のボタン押し下げに対して多数回の割込みを発生してしまうからです。
ただし、エッジトリガにも「ノイズに弱い」という欠点があります。即ち、正しい割込み要因が発生していなくても何らかのノイズが発生して割込み信号が急激に0に向かって変動し、誤った割込みを発生するかもしれません。そこで、ファームウェア的に割込みルーチンの中で、もう一度正しく割込みが発生したのかどうかを確認する方が安全です。今回の割込みルーチンでは2行目のPA0またはPA1が確かに0かどうか確認しているコードがそれに当たります。
また、割込み信号は0がアクティブで、今回のシステムの様に無駄に長い間アクティブ0にしておくのはよくありません。元々1→ 0のエッジで割込みを発生させたいのであれば、その信号を74HC423で受けるなどして0アクティブの(下向きの)パルス信号に変換してから割込み信号ピンに入力するようにし、レベルトリガで割込みを受けるようにするとシステムが安定します。
尚、今回のファームウェアで割込み処理を、割込み処理ルーチンとTD_Poll()の中の2か所で行っている理由は、割込み処理ルーチンはできるだけ短く記述するのが作法だからです。割込み処理ルーチンではやるべき作業がわかるように起こった割込みを記憶することと、その中で必ず処理しなければならない緊急事態の事柄のみを書くように心がけます。そうすることにより、無駄に長い割込み処理によってシステム中の他の重要な処理が滞ることの無いようにします。
今回作成したbutton.iicをダウンロードしたEZ-USBを制御するウィンドウズアプリButton2.exeが、C:\GigtecEZ-USB\VC++\Button2フォルダにプロジェクト全体を格納してあります。前ページで既にウィンドウズアプリの作成方法については実習しているはずですから、今回は出来上がったものをビルドしてファームウェア/アプリのテストを行ってください。そして、以下にButton2の説明を見て内容を理解するようにしましょう。
機 能 名 | 02へのコマンド出力 | 86へのレスポンス | 説 明 |
Reset | [00] | なし | 早押しボタンをリセットする |
PushedButton | なし | ボタンA → [00] ボタンB → [01] |
押されたボタンを通知する(割込処理) |
上表はButton.iicがPCに対して提供する制御コマンドです。Button.iicでファームウェアを早押しボタン用に特化させたため、simpleio.iicと比較して利用する機能が大幅に単純化されたことがわかります。また、初期化時にEZ-USBは自ら早押しボタンをリセットするため、ウィンドウズアプリが立ち上げ時にリセットする必要はありません。以下はすべてファイルButton2Dlg.cppに関する説明です。
Button2.exeは別スレッドを作成して早押しボタンを監視していますが、前ページと同様タイマーで監視することもできます。ここでは、別スレッドの作成スキルが今後も役に立つと思い導入しました。
(1) リセットボタン
リセットボタンのクリックによりOnClickedResetButton()関数が呼び出されますので、その中にReset機能を書き込みました。
(2)別スレッドInterruptThreadの作成と割込み処理
Button.exeではタイマを駆動して100ミリ秒ごとにEZ-USBのポートの状態を監視して、早押しボタンの押し下げを検知していましたが、今回はタイマの代わりに別スレッドInterruptThreadを作成して、そのスレッドに監視機能を任せます。
初期化関数OnInitDialog()の最後にAfxBeginThread(別スレッドの関数名、任意のポインタ);と記述してInterruptThreadを立ち上げています。任意のポインタには通常メインのスレッドから別スレッドへ引き渡したい変数をまとめて構造体にして宣言し(ButtonInt)、その実態(m_BI)へのポインタを渡します。これにより、メインのスレッドで利用していた変数が別スレッドでも共有できるようになり、構造体内の変数を使用することによりメインのスレッドから別スレッドの動作を制御できるようになります。一方、別スレッドの関数の実行が終了すれば自動的にスレッドも閉じられます。
InterruptThreadは常にEndPoint 86に値が送信されたかどうかの監視を行い、送信された(即ち早押しボタンが押し下げられた)らその値をメインスレッドにwParam=0のメッセージで送ります。また、メインスレッドによってm_BI.cancelの値が1に変更されたら、監視をやめてwParam=1のメッセージを送るとともにスレッドを終了します。
メインスレッドは、InterruptThreadから送られるメッセージをOnButtonInt()関数で受けます。wParam=0のメッセージは早押しボタンが押し下げられた通知なので、その結果を表示します。wParam=1のメッセージはアプリの終了を指示するものなので、自身にWM_CLOSEメッセージを送ります。するとOnBnClickedCancel()関数が呼ばれます。
アプリの終了時に呼ばれるOnBnClickedCancel()関数は、ユーザーによって閉じるボタンや右上隅のxをクリックされることにより呼ばれる場合と、アプリ内部で呼ばれる場合とがあります。前者の場合はInterruptThreadの動作中に突然終了命令が出ることになりますが、メインスレッドはInterruptThreadの終了が確認されてからでないと終了してはいけません。そこで、このような場合m_BI.cancelを1にしてInterruptThreadが終了するのを確認してからメインスレッドを終了します。前者と後者の区別はm_BI.bRunningという変数で行えます。前者でInterruptThreadが実行中はm_BI.bRunning=TRUEで、後者の場合はFALSEとなるからです。
InterruptThreadにおいては、len=1;pBI->IntEndpt->XferData(&buf, len);という関数を用いてUSBを介してPCに到来したデータを監視します。OnInitDialogにてm_BI.IntEndpt->TimeOut
= 0;と指定しているため、関数XferData()は呼ばれたときにデータが来てなくても待たずにlen=0として戻ります。呼ばれたときにデータがあればそれをbufに格納しlen=1として戻ります。従って、lenの値を確認すればデータが来たかどうかがわかります。
以上のように、USBをただPCとEZ-USBとの間の通信手段として利用しUSB通信自体をプログラミングするつもりがなければ、これ以上USBそのものについて学ぶ必要はありません。TD_Init()とTD_Poll()の中身を考えることに集中し、後はスケルトンに任せましょう。
ただ、本格的にファームウェアを作成するには上記の知識では不十分で、EZ-USBに内蔵されているCPU、8051について学ぶ必要があります。特に、8051が持ちEZ-USBで拡張されたSFR(Special
Function Register)の理解を深めることにより、EZ-USBの能力を最大限に引き出すことができるようになります。そのために、C:\Cypress\USB\CY3684_EZ-USB_FX2LP_DVK\1.1\DocumentationフォルダにあるEZ-USB(R)
Technical Reference Manual.pdfを参照してください。
ファームウェアはC言語で作成するのでその基本知識は必要ですが、本ページで分かるように単純なコードだけで事足リるのでCの技量は要求されません。
慣れてくるとファームウェアでできるだけたくさんの処理をさせたくなりますがそれは間違いです。EZ-USBは最大で48MHzで動作する8ビットCPUにすぎません。一方、PCは数GHz64bitで動作していて柔軟性も高いです。ですから、EZ-USBには必要最小限のタスクをやらせ、しんどい仕事はPCに回すというトレードオフを意識したシステム作りを行う必要があります。
また、EZ-USBを離れ一般的なハードウェア:デジタル回路の設計技術を学ぶ必要があります。それはネットで勉強するか専門書を購入してください。
> 次に進む