SSブログ

SilentCが使っていないモジュールを探せ(8) [ColdFire V2]このエントリーを含むはてなブックマーク#

1990557

モジュール探訪の旅。 今日は、Queued Serial Peripheral Interface (QSPI) を走ります。

QSPIは、いわゆるSPIなのですが、ちょっと違います。 キュー・バッファを持っていて、自動的にデータを送受信してくれます。 まずは、レジスタ・マップから

40000340  01 04 00 00 04 04 00 00  ........
40000348  00 00 00 00 00 00 00 00  ........
40000350  00 00 00 00 00 00 00 00  ........

32ビット境界に16ビットのレジスタが埋め込まれています。 何のために? いずれのレジスタも初期設定のままです。 ただし、EzPORTで他のMCF52233のFlashROMをプログラムするためにこのモジュールが使用されることが分かっています。

QSPIは、どこがSPIと違うのか

手持ちの1990年版MC68332ユーザーズ・マニュアルにQSPIというモジュールが登場しています。 今回、QSPIの仕様を詳しく読みましたが、MC68332の仕様とも違っているようです。 MC68332のQSPIの方が、使いやすそうなんだけどな。

まず、第一の特徴は、QSPIは、マスタ・モードでのみ使用できます。 仕様書の最初のほうに書いてありますが、マスタ・スレーブ切り替えレジスタ・ビット MSTR が存在しているにも関わらず、スレーブ・モードでは、使用できません。 これは、衝撃的な事実でした。 ちなみに、MC68332のQSPIは、スレーブ・モードでも使用できるようです。

次の特徴です。 通信の内容を「QSPI RAM」と呼ばれるキュー・バッファに入れておいて一括で処理することが出来ます。 80バイトの「QSPI RAM」は、送信すべきデータ、受信したデータ、チップセレクトの出し方やビット数などの通信方式を指定するコマンド、の三つの領域に分けられています。 ここまで見るとすごいのですが、メモリマップには、80バイトの領域が見当たりません。 はて?どうやってアクセスするんだろう。

ここで、第二の衝撃です。 ColdFireのアドレス空間は、32ビット・アドレス・バスを採用しているので、当然、4Gバイトの広大な空間になっているはずです。 ところが、「QSPI RAM」へのアクセスは、いわゆるバンク方式になっていて、アドレスを指定するQARレジスタと、「QSPI RAM」の中の特定の16ビットのデータが読み書きできるQDRレジスタという構成になっています。 HC08やRS08だったら、限られたアドレス空間を有効に使うために、こういう方式もあるでしょうが、いやしくも、32ビットMCUでこんな事をしているとは、思いもよりませんでした。 ちなみに、MC68332の場合、「QSPI RAM」へは直接アクセスが可能です。 いや、そうあるべきでしょう。

「QSPI RAM」がColdFireのアドレス空間に配置されていないことから、DMAでのアクセスも事実上不可能です。 可能だったとしても、DMAを3チャネル使用することになるので、もったいなくて。 そもそも、QSPIからDMAに転送要求を出す機能も無いので、大量のデータを自動的に送り出すことも出来ないでしょう。 割り込みなどを使用するにしても、必ず、ソフトウェアの介在が必要なので、SilentCとは、最悪の相性だと思います。 このモジュール、苦情は出なかったのかなあ。

QSPIで、LEDピカピカのハードウェア

max5353.png 2090522

QSPIを使って、毎度おなじみの「LEDピカピカ」をさせます。 このモジュールもI2Cと同様に通信相手になるデバイスが必要です。 昔、サンプルでもらったMAXIM社MAX5353というD/Aコンバータをつないでみました。 このD/Aコンバータは、DIP8に収まった12bit分解能のD/Aコンバータです。 出力には、FB端子付きのOPアンプが使われているので、色々と実験が出来ます。 惜しむらくは、DO端子が無いので、数珠つなぎにはできないところでしょうか。 このD/Aコンバータと2SC1815を使って、データシートに回路図がある可変電流源を構成して、LEDを光らせます。

エミッタには、100Ωの電流検出抵抗を接続します。 これで、FB端子が0.0Vから1.0Vの範囲で出力の電流が0mAから10mAまで可変するはずです。 D/AコンバータのREF端子には、二本の4.7kΩ抵抗でVDD=3.3Vの半分の電圧(1.65V)を与えました。 D/Aコンバータの設定値としては、0から2480までの値を与えればよい計算になります。

この回路図には、パスコンが描かれていませんので、適宜入れてください。

QSPIが、動かない

レジスタの設定は、以下のようにしました。

QSPIモジュールのレジスタ設定
レジスタ備考
QMR [MSTR]1 こんな設定ビットがあるのですが、このモジュールは、マスタ専用です。
QMR [DOHIE]0

SPIを使用しないときにデータ出力を高インピーダンスにするかどうかを指定します。

複数のスレーブ・デバイスが同一のバスに接続される場合、チップ・セレクトで指定されないデバイスは、データ出力を高インピーダンス状態にして、明け渡すのが礼儀です。 ところが、このモジュールはマスタ専用なので、データ出力を高インピーダンスにする理由がありません。 よって、ここでは、常にデータ出力を行う設定にします。

QMR [BITS]0000 (16bit) MAX5353が受け付けるデータ形式は、16ビットです。
QMR [CPOL]0 (Low if inactive) SPIが動作していないときのクロックの位相を指定します。 MAX5353が期待しているのは、非動作時にLowになるクロックです。
QMR [CPHA]0 (capture-change) データを受け取るクロックエッジとデータを変化させるクロックエッジを指定します。 MAX5353では、クロックの前縁でデータを受け取り、後縁でデータを変化させます。
QMR [BAUD]0x1E (30) クロックの周波数を指定します。 MAX5353の最小クロック周期は、100n秒となっています。 ここでは、無理に高速なクロックを使う必要も無いので、1MHzのクロックを使用します。
QDLYR [SPE]0 (disable) → 1 (enable) 通信を開始するときには、このビットをセットします。 レジスタを初期設定する際には、クリアしておき、通信を開始するタイミングでセットします。
QDLYR [QCD]0x04 (67µsec) チップセレクトがアサートされてから最初のクロックエッジが発生するまでの時間をバス・クロック周期単位で設定します。 MAX5353の仕様書では、最小40n秒となっていますので、3以上の値であればよいはずです。 ここでは、初期状態のままの値を使用します。
QDLYR [DTL]0x04 (67µsec) 複数のSPI通信を連続して行うようにコマンドを組んだときに、二つのSPI通信の間の遅延時間をバス・クロック周期単位で設定します。 今回は、複数のコマンドを駆使するような使い方はしないので、初期状態のままの値を使用します。
QWR [HALT]0 (disable) このビットをセットすると、実行中の通信が中断されます。
QWR [WREN]0 (disable) コマンドの実行が終了したときに、再度、コマンドを最初から実行するかどうかを指定します。 今回は、単発での通信を考えているので、コマンド実行終了後は停止させます。
QWR [WRTO]0 Wrap機能は使わないので、0のままにしておきます。
QWR [CSIV]1 (active low) MAX5353のCS入力は、 active low 信号です。
QWR [ENDQP]0000 通信に使用する最後のワードのポインタを示します。 このアプリケーションでは、1ワードだけ送信したいので、終点は始点と同じ"0"とします。
QWR [NEWQP]0000 通信に使用する最初のワードのポインタを示します。 キュー・バッファの"0"番目から通信に使用します。
QIR [WCEFB, ABRTB, ABRTL]0 (disable) これらは、エラー処理をイネーブルするために使用されます。 とりあえずは、エラー処理無しで進めます。
QIR [WCEFE, ABRTE, SPIFE]0 (disable) エラーおよび通信終了時に割り込みをかけるかどうかを指定します。 このアプリケーションでは、割り込みは使いません。
QCR0 [CONT]0 (single) 1ワードごとにCS出力をネゲートするかどうかを指定します。 今回は、1ワードだけの通信を行いますので、どちらでもかまわないはずです。
QCR0 [BITSE]1 (specify) MAX5353との通信は、16ビット単位で行います。 そのため、このビットをセットして、データ長をQMR [BITS]レジスタで示す必要があります。
QCR0 [DT]0 (default) 複数のSPI通信を連続して行うようにコマンドを組んだときに、二つのSPI通信の間の遅延時間をバス・クロック周期単位で設定します。 デフォルトのままの値を使います。
QCR0 [DSCK]0 (default) チップセレクトがアサートされてから最初のクロックエッジが発生するまでの時間を指定します。 デフォルトのままの値を使います。
QCR0 [QSPI_CS]1110 チップ・セレクト出力のパターンを示します。 80ピンのMCF52233には、複数のチップ・セレクト出力端子があるわけではないので、CS0だけアサートします。
  int  *pqspar =0x4010006c;
  int  *qmr    =0x40000340;
  int  *qdlyr  =0x40000344;
  int  *qwr    =0x40000348;
  int  *qir    =0x4000034c;
  int  *qar    =0x40000350;
  int  *qdr    =0x40000354;
main(){
  int v;
  init();
  #stop 0
  v=0
  for(;;){
    if(Getc(0)=='q')break;
    SystemSleep();
    send((v<<1)&0x1FFE);
    v+=80;
    if (v>2480) v=0;
  }
}
init(){
  *pqspar=0x0051; // PQSPAR[3,2,0]=01 (QSPI)
  *qmr   =0x801E; // See table
  *qdlyr =0x0404; // See table
  *qir   =0x000F; // clear all flags
  *qar   =0x0020; // select command RAM
  *qdr   =0x4E00; // COMMAND code
  *qwr   =0x1000; // See table
}
send(int value){
  *qar   =0x0000; // select transmit RAM
  *qdr   =value; // DATA code
  *qdlyr|=0x8000; // SPE=1
  while (!(*qir&0x0001));// wait SPI completed
  *qir   =0x000F; // clear all flags
}

と、綿密にプログラムを書いたつもりなのに、まったく動く気配がありません。 設定を色々と変えてみましたが、進展しません。

ソフトウェアSPIを試してみる

全く、動かないので、ハードウェアに問題があるのかソフトウェアに問題があるのか判断がつきません。 そこで、ハードウェアはそのままで、GPIOをソフトウェアで制御するプログラムを作成しました。

  char *portqs =0x4010000c;
  char *ddrqs  =0x40100024;
  int  *pqspar =0x4010006c;
main(){
  int v;
  init();
  #stop 0
  v=0
  for(;;){
    if(Getc(0)=='q')break;
    SystemSleep();
    send((v<<1)&0x1FFE);
    v+=80;
    if (v>2480) v=0;
  }
}
init(){
  *pqspar =0x0000; // PQSPAR[6:0]=00 (GPIO)
  *portqs =0x08; // CS=1;SCK=0;DO=0
  *ddrqs  =0x0D; // PQS[3,2,0] as output
}
send(int value){
  int i;
  for (i=16;--i>=0;) {
    *portqs = (value&0x8000)?0x01:0x00; // CS=0;SCK=0;D(i)
    value=value*2;
    *portqs|= 0x04; // SCK=1
  }
  *portqs = 0x00; // CS=0;SCK=0;DO=0
  *portqs = 0x08; // CS=1;SCK=0;DO=0
}

すると、このプログラムなら、動作することがわかりました。 問題は、QSPIの使い方にありそうです。

キュー・バッファにアクセスできない?

様々なステータスを表示させてデバッグをしました。 その結果、ある事に気がつきました。 キュー・バッファのうち、データ部分は、QDRレジスタを通じて読み書きが出来ることになっているのですが、どうやっても前に書き込んだはずのデータが出てきません。

ここから先は想像でしかありません。 おそらく、QDRレジスタへの書き込みアクセスの過程で、SilentCの内部処理によりQDRレジスタへの読み出しアクセスが発生したものと思われます。 QDRレジスタに読み出し・書き込みアクセスが発生すると、QARレジスタがインクリメントされる仕掛けになっています。 そのため、目的とするキュー・バッファにコマンドと送信データが書き込まれなかったものと思われます。 現に*qdrへの書き込みの後、QARレジスタを読み出すと、+2インクリメントされています。

逆転の発想

もし、SilentCが余計な読み出しをしているのなら、QCR0に書き込みたかったコマンドは、QCR1に書き込まれているはずです。 また、送信すべきデータも0x0001に書き込まれるはずです。 ならば、QWRレジスタに、「1番地のバッファを使え」と指示したらどうなるでしょうか。

  int  *pqspar =0x4010006c;
  int  *qmr    =0x40000340;
  int  *qdlyr  =0x40000344;
  int  *qwr    =0x40000348;
  int  *qir    =0x4000034c;
  int  *qar    =0x40000350;
  int  *qdr    =0x40000354;
main(){
  int v;
  init();
  #stop 0
  v=0
  for(;;){
    if(Getc(0)=='q')break;
    SystemSleep();
    send((v<<1)&0x1FFE);
    v+=80;
    if (v>2480) v=0;
  }
}
init(){
  *pqspar=0x0051; // PQSPAR[3,2,0]=01 (QSPI)
  *qmr   =0x801E; // See table
  *qdlyr =0x0404; // See table
  *qir   =0x000F; // clear all flags
  *qar   =0x0020; // select command RAM
  *qdr   =0x4E00; // COMMAND code
  *qwr   =0x1101; // See table
}
send(int value){
  *qar   =0x0000; // select transmit RAM
  *qdr   =value; // DATA code
  *qdlyr|=0x8000; // SPE=1
  while (!(*qir&0x0001));// wait SPI completed
  *qir   =0x000F; // clear all flags
}

変更したのは、QWRレジスタの設定部分だけです。 なんと、LEDが点滅を始めてしまいました。 しかし、この方法は、再現性も無いし、必ず正しく動く保証も無いので、決してお勧めはできません。

本日の結論

というわけで、本日の結論は、

SilentCQSPIの組み合わせは、最悪であって、まともに使えるものではない。

でした。 深追いした割りに得るものは少ないな。

参考文献

Interface (インターフェース) 2008年 09月号 [雑誌]

Interface (インターフェース) 2008年 09月号 [雑誌]

  • 作者:
  • 出版社/メーカー: CQ出版
  • 発売日: 2008/07/25
  • メディア: 雑誌


nice!(0)  コメント(3)  トラックバック(0)  このエントリーを含むはてなブックマーク#

nice! 0

コメント 3

hamayan

うっかり大嵌りする前に、ここ読んで良かったー。
by hamayan (2008-09-08 10:28) 

noritan

お役に立てれば、さいわいです。
QSPIやI2Cの操作部分は、ぜひ某氏に機械語で書いてもらいましょう。

by noritan (2008-09-08 12:59) 

サイレントシステム

noritanさんはじめまして。サイレントシステムの中本です。
情報の少ない中でColdFire基板を使い込んでいるご様子ですね。
大変素晴らしい解析記事に感服しております。もしnoritanさんが
どうしても不明な点があればサイレント宛にメール下されば可能な
範囲でお答えします。
by サイレントシステム (2008-09-11 16:23) 

コメントを書く

お名前:
URL:
コメント:
画像認証:
下の画像に表示されている文字を入力してください。

トラックバック 0

トラックバックの受付は締め切りました