« 2010年7月 | トップページ | 2010年9月 »

2010年8月の3件の記事

2010年8月26日 (木)

エラーのないFPGAのUARTコードを自作する

 FPGAのフォトフレームプロジェクトは、いよいよ第一期完成フェーズにあたる第7ステップに進んだ。SDカードにあるビットマップ画像データを液晶モニターに表示する段階である。フォトフレームを作ると決めてブログで宣言したのが今年の2月末。ほぼ6ヶ月かけてここまできたことになる。

A8263052

 このあいだには、XilinxのFPGAチップと液晶モニターをプローブの誤接触で壊したり、HDL言語(VerilogHDL)の習得にてこずったり、今回も多くのドラマ(?)が生まれた。電子工作を始めて2年半、自分でも感心するほど多種多様な工作をしてきたが、ひとつのプロジェクトにこんなに長く(半年)かけたことはない。FPGAはやはりてごわい。

 これまで何度も書いているように、当研究所では電子工作には必ず何らかの実用的(こじつけでも良い)な目的を決めてから、工作を始めることにしている。定年後の楽しみに始めたアマチュアの電子工作である。何をやろうと、どこでやめようと誰もとがめる人はいないのだが、これだけ目的にこだわるのは理由がある。

 自分は飽きっぽい性格で、めげやすい。何の規制もせずにやりたいことだけやっていたら、いつまでたっても技術の向上は望めない。電子工作は幸い、初心者からプロまで、どんなレベルであろうと、それなりに夢中になり楽しめる世界だが、悔しいことに技術レベルが上がれば上がるほど楽しめる範囲が広くなっていくことは否定できない。

 何でも良いから具体的な目標(仕様)を設定しておくと、何とかそれを実現しようと、一生懸命に工夫、努力をする。飽きっぽい割には、一旦こだわると意地になる習性がある。しかし、これによって技術レベルは確実に上がる。遊びとは言いながら少しでも技術レベルを上げて今まで出来なかったことを可能にし、世界を広げたい。

 当研究所の所長は、この40年間、ほぼコンピューターのシステム開発を仕事にしていた(後半は企画と管理ばっかりだったが)。この世界ではコンピューターのハードウエアというものはいわば神が創ったものであり、これに注文をつけることはとんでもないことで、仕様どおり有難く使わせていただくものだった。

 PCやマイクロコンピューターでも似たようなものである。しかしFPGAはこういう今までの常識をくつがえすディバイスである。作ろうと思えば、自らが思いのままプロセッサーを自作できるのだ。何とかこのディバイスを自由自在に操れるようにしておきたい。

 フォトフレームプロジェクトは現物を作るだけでなくFPGAをマスターするということもプロジェクトの大きな目的にしている。そんなこともあってまた少し道草を食って、本来のフォトフレームとは関係ないFPGAのUARTにはまりこんでいた。

雑誌のサンプルUARTソースがうまく動いていない(8/16)
 SPIのバグ取りに散々苦労して、わかって見ればその原因はUARTの方だったという話は前回にした。このFPGAのUARTのVerilogHDLソースコードは、雑誌のデザインウエーブマガジン(その後季刊のデジタルデザインテクノロジーに改称)2008年10月号P86のサンプルソースをそっくり利用させてもらっている。

 雑誌のサンプルソースにバグはないはずなのだが、SRAMからのUART出力には明らかにエラーが出ている。エラーの頻度はそう高くないので、これまで気が付かなかった。これとは別のfpga4fun.comのUARTに較べれば、はるかに安定していると思っていた。

 しかし、詳しく調べると、1KBに数文字の字化け(エラー)があるようだ。特にリセット直後の数文字はたいていおかしい。フォトフレームプロジェクトではそろそろデジタル液晶の準備にかからなければいけない(ケース工作はほぼ完成)のだが、ここはそれ、しつこい性格である。気になると、他の事に手が付かなくなる。この際、徹底的にデバッグすることにした。

 折りしも、SPIのトラブルのとき勧められた、「HDLによる高性能デジタル回路設計」という本を入手し、FPGAを本格的に勉強しはじめたこともある。ロジアナを使って、雑誌サンプルソースの不具合の原因解明を始めた。

Dwv_uart

 まず、犯行現場の現場検証が重要だ。字化けの事実を検証する。テスト用にボーレートカウンターの動きをFPGA出力ピンに出すコードをソースに追加して、これをロジアナで拾い、少しづつデータを送ってはロジアナでタイミングチャートを分析する。

 その結果、どこで字化けが起きるかは、ほぼ確定できた。UARTのデータの送出順序はLSBファーストである。字化けは必ずMSBで起きている。データの最後の方を集中的に調べていくと、添付画像にあるように、8ビット目の最後がボーレートの半分で終わり、そのままストップビットになるところがいくつも見つかった。

 UART受信はボーレート期間中の多数決論理でHかLを決めているので、ボーレートが少しずれると半分の幅しかないMSBで0のデータが1になる可能性がある。これが前回の記事で1や2が半角のアやイになる理由である。スタートビットはこのあと正しく出ているので、エラーはここだけに止まる。

 字化けが起きる原因はつきとめられた。しかし何故、このUARTの最後のビットがボーレート通りの時間にならないかがわからない。ソースコードにはおかしなところはない。きちんとボーレートの時間通りウェイトが入っている(はずである)。

「HDLによる高性能デジタル回路設計」で勉強する。(8/18)
 すんさんの掲示板で勧められ、絶版だと聞いていたが、秋葉原の書泉で探してみたら最後の一冊らしいカバーが少し汚れたものが見つかり早速手に入れた。

Hdl

 噂には聞いていたが、高度な内容である。非同期回路だけでなく、同期回路も安心ならないことを学ぶ。それ以外にも考慮しなければならないことが沢山ある。自分がいかに能天気にRTL設計をしていたかを思い知らされる。

 雑誌UARTのソースコード(動作環境 Altera QuartusⅡ)は、この本を読み進むうち、かなりな問題があることがわかってきた。まず、送信要求のビットはキーボード押下で発生する非同期パルスだが、その配慮がなされていないこと、状態遷移のレジスターが多段(5ビット)で、単なるバイナリーコードであるうえに、これをインクリメントするステップと、状態遷移をするロジックが同一クロック時に動いており、これが正しく評価されない可能性があることなどである。

 本に拠れば、非同期はもちろんであるが、同期回路といえども、ハザード(出力が不定になるところ)は、常に起きる可能性があり、その対処が必要としている。それから言えば、この雑誌のサンプルソースはあやしいところが沢山ある。

 それにこのソースには不可解なところがある。UARTは非同期通信なので、同期シリアル(USART)と違ってデータはクロックのエッジではなく、HighかLowでデータを採取する。それなのにサンプリング周波数はボーレートの2倍にとってある。少なくとも送信の場合はオーバーサンプリングしても無意味である。

 恐らく、受信側のモジュールとボーレートを共通にするためだと思うが、ソースコードを読めば読むほど、何かあやしくなってきた。具体的にどこでハザードが起き、それによってボーレートが最後だけ短くなるのかは、まだ経験が浅くてつきとめられない。しかし現実に字化けが起きていることは事実である。

 こうなったら、自前でUARTのソースコードを書いてみたくなってきた。参考書で学んだテクニックを応用する絶好の機会だ。

ここまで勉強したら自分で作ってみよう(8/21)
 擬似コーディングを念入りにやって、自前のUART送信コードを書き始めた。UARTは、クロックパルスを持たない非同期通信なので、スタートビットからの時間間隔(ボーレート)だけでデータをHかLに設定して送信する。雑誌の2倍サンプリングをやめて、ボーレートだけのステートマシンにする。

 自慢は、ステートレジスターのグレイコード化(1ビットづつ変化する遷移ビット)と、同時に多数のビットを変えないロジック、ボーレートカウンターのような多数のビットを変更するときは、ステートをあけて結果を利用するなどのテクニックだ。半日ばかりで出来た。おお、前よりステートメント数も少なくなっている。これで動けばいうことなしだ。

 期待に胸をふくらませて論理合成する。祈る気持ちでUART端末を立ち上げる。ARMからデータを送り、FPGA側のUARTのスペースキー(出力指示)を押した。やった、やったぞ、最初の文字から全く誤りなくテキスト文書が表示される。キーを何度も押す。すごい。全くエラーがない。このあいだのXbeeの電力ロガーのデータはテキストデータで定型なので、少しでもエラーがあると目立つのだが、どこまでも綺麗に出力される(前は、3回に一回はどこかしらデータの乱れがあった)。

Uart_ok

 いやあ満足、満足である。勢いに乗って消費リソースを調べてみた。今度のソースコード全体のスライスは366、前は382だった。差はわずかだが、このスライス量はすべてのルーチン(UART受信、SRAMアクセス、ビデオ信号、SPI、10進変換など)を含んでいる。送信UARTだけならスライス量は80程度と考えられ、それでこの差は大幅な削減だ。嬉しい。

 調子に乗って、またソースコードを公開してしまうことにした。送信の部分だけだが、Latticeの環境では、間違いなく雑誌のコードより信頼性の高いVerilogHDLソースである。なおコメントに日本語が入っているが、これは公開のために追加したもので、Latticeのエディターでは日本語は化けてしまうので注意されたい。

//------------------------------------------------------------
// UART送信モジュール   8/25/2010 (C) LABO Gataro
//
//  CLKはマスタークロック、BAUDは受信モジュールと共通のパラメーター
// (受信モジュールは、DWM2008年10月月号P86を利用している)
//
//   メインで以下のステートメントが必要(38.4kbpsのとき)
//
//   parameter CLK  = 33000000;        // FPGA master clock 
//   parameter BAUD =  CLK/(38400*2);  // 38.4kbps 8bit 1start/stop
//   assign div = BAUD;
//
module UART_TX( so, pi, xmit, rdy, div, pCLK, pRST );
output     so ;      // transmitted out シリアル出力
input[7:0] pi;       // data to transmit 送信データ(8ビット)
input xmit;          //start transmit flag   (req)  送信要求
output rdy;          // rdy=0 : in send proc (busy) ビジーフラッグ
input[10:0] div;     // baud rate count from main module ボーレート
input pCLK;          // master clock
input pRST;          // master reset(XP2 is positive reset)

reg [7:0] ps8;
reg       so ;
reg [1:0] state;
reg [10:0] dc ;
reg       rdy ;

reg [4:0] bitctr;    // including start/stop bit
wire [10:0] div2;
assign div2 = {div[9:0],1'b0};  // multiply by 2 for TX

parameter IDLE = 2'b00;  //state reg in gray code
parameter DSET = 2'b01;
parameter WAIT = 2'b11;

always @(posedge pCLK or posedge pRST ) begin
  if(pRST) begin          // initial process if reset
   ps8 <= 8'h00;          // clear output reg
   state <= IDLE;         // initial state
   dc  <= div2;           // load baud rate couner
   rdy <= 1'b1;           // inform TX ready
   so  <= 1'b1;           // UART is negative logic(active=0)
   bitctr <= 4'h0;        // confirm initial status
  end
  else begin
   case(state)
    IDLE: if(xmit == 1'b1) begin    // if tx request
            ps8 <= pi;              // set 8bit tx data
            state <= DSET;          // move to next state
            bitctr <= 4'h0;         // initialize counter
            rdy <= 1'b0;            // set busy bit
          end

    DSET: begin
           if(bitctr == 4'h0)      so <= 1'b0; // start bit
           else if(bitctr == 4'h9) so <= 1'b1; // stop bit
                else begin
                  so <= ps8[0];            // put data on serial line
                  ps8[6:0] <= ps8[7:1];    // shift data reg
                end
           bitctr <= bitctr + 4'h1;       // shift 1 bit
           state <= WAIT;                 // go to wait state
          end

    WAIT: begin
           dc <= dc - 11'h1;            // decrement baud rate counter
           if(dc == 11'h0) begin        // refill baud rate value
             dc <= div2;
             if(bitctr > 4'h9) begin  // goto IDLE after stop bit sent
               state <= IDLE;
               rdy <= 1'b1;           // set ready bit
             end else
               state <= DSET;  //repeat 10 times(start + 8bits + stop)
           end
          end
    endcase               
  end  // end of else after Init
end   // end of always clause
endmodule  // UART_TX

| | コメント (2) | トラックバック (0)

2010年8月15日 (日)

FPGAのSPIインターフェース遂に完成

 ここ1週間ばかり、お盆休みをとっていたのだが、外にもでかけず(まあ猛暑だったこともあるが)FPGAのSPI(スレーブ)インターフェースのデバッグに夢中になっていた。先ほど、やっとのことでバグの原因が判明し、清々しい気分でこのブログを書いている。

 今度も、日頃からお世話になっているkugaさんのヒントで解決したのだが、最後の最後になっても細かいエラーが取れず、今回も苦労した。しかし、苦労が多ければ多いほどその喜びは大きい。人間というのは因果なものである。

なかなか正しいデータが受け取れない(8/8/2010)
 FPGAのLattice XP2基板のSPIインターフェースは、フォトフレームプロジェクトの第5ステップにあたり、送り側のARMプロセッサー(STM32F103)のSDカードデータを送るSPI(マスター)インターフェース(第6ステップ)は、既に完成している。

A8153045

 前にも書いたように、FPGAのSPIスレーブインターフェースは、fpga4fun.comから拝借したソースコードを使わせてもらっている。テスト環境ではARMからデータを送った後、FPGAのUARTのリターンキーを押せば、SRAMに蓄積されていたデータがUART端末に表示される。

 前記事にもあるように、30バイト程度の短いメッセージならほぼ正しく帰ってくるが、少しデータが増えると、カウンターがおかしくなるらしく、表示が止まらなくなる。いわゆる暴走である。UARTで結果が見えるように、テストデータはキャラクターで、SDカードの中のテキストファイルを選んで送信するのだが、派手に字化けする。

 データはともかく、カウンターに目茶目茶な数字がはいっているようだ。切れ切れに見えるメッセージもあちこちでデータが化け、特に日本語の2バイト文字は悲惨な結果になる。まあ、プログラムの最初はこんなものだ。少しづつ不具合を直していこう。

 データの中味をチェックする前に、先にデータカウンターの部分を直さないとテストがやりにくい。いちいちリセットしているのではたまらない。しかし、FPGAはいわゆるprintfデバッグ(変数を途中で出力させて調べること)が出来ない。カウンターの数字を知る方法は何かないかと考えていたら、以前7セグLEDテストのときにバイナリから十進4桁データに変換するモジュールを開発したのを思い出した。

 デバッグ対象のコードにさらに不確定要素を加えるのは、デバッグの常道にはずれるが、この際、背に腹は代えられない。何とか組んでカウンターの数字をUARTに出してみた。ASCIIの数字キャラクターは、バイナリに0x30を加えるだけなので楽である('1'=0x31,'2'=0x32...)。

 テストのためにまずカウンターを固定して、モジュールから数字を取り出す。あれえ、全く違う数字が出る。SPIが読んだ時の数字(でたらめだが)のようだ。やれやれ、こいつのデバッグもしなければいけないのか。ソースを見直す。うはあ、こいつはマスタークロックで何十回かループしないと結果がでないソースコードだった。10進数字を取り出す前に計算終了のフラグをテストするループを加えて、やっと決め打ちした数字がUARTにでた。

 このモジュールが出す読み込んだデータの数は予想通り支離滅裂な数字だった。unsigned変数は0から1を引くと、最大数になり、止まらなくなる。カウンターが0になったときに備えて、0からこれ以上引かないような姑息なロジックも入れるが、これも関係なし。リセットした直後は、まあまあのデータが出るが、続けるとまるで駄目である。

 SPIってこんなに不安定なのか。しかしおかしなことに8ビットフレームを1ビットでも間違えれば、あとはすべて狂うはずだが、奇妙なことに正しいデータが復活する時がある。STM32側のデータはロジアナで追って、バイト数が合っていることを確認した。中味は30バイトほど調べて少なくとも間違っていなかった。まあ、この不具合はすべてFPGA側が原因だろう。

 前にも書いたが、このfpga4funのコードは一風変わっている。各変数のあとにひとつづつクロック単位に動くalways文がついている。例えばSCKの立ち上がりは、3ビットのシフトレジスターを動かし、そのデータが01Xになった時、つまりSCKが立ち上がったときに1になるよう変数を定義し、その変数を使って8ビットデータを拾う。実に巧妙な方法だ。

 このコードは、SPIの入力をマスタークロックでサンプリングして、ビットの立ち上がりを調べている。SPIのSCKクロックで動かす方がより正確なデータがとれるような気がするのだが、人さまの書いたソースなので、原因究明の方法の糸口が見つけられない。

 FPGAのクロック33Mhzと、最初設定したSPIの速度9Mhzの差が近いので、サンプリングが上手く行っていないのかも知れないと、クロックを半分の4.5Mhz(計算ではSCKの1パルスに3回以上サンプリングできる)に落としたが、結果は変わらなかった。

 これ以上何をやって良いのか見当がつかなくなった。サンプリングだとデバッグしにくいので、むしろSCKのクロックに合わせた回路の方が安全のように思えてきた。考えた末、遂にfpga4funのソースをあきらめ、自前で作ることにする。

自前のSPIスレーブルーチンもエラー続出(8/10/2010)

 腰を据えて、自前のSPI スレーブコードを書き始めた。思ったより早くコードが完成した。おお、fpga4funのコードより消費スライスも少ない。良いぞ。テストする。最初、200バイトあまりのデータが正しく表示されたので小躍りして喜んだが、少しデータが増えると相変わらず字化けが起きる。ところどころでデータが欠落する。

 不思議に字化けの連続にはならない。8ビットフレームが守られているからか。相変わらず、文字カウントはリセット直後は良いが、あとはデタラメになる。???である。目を皿のようにしてリセットあたりのロジックを確かめるが原因がわからない。

 迷走している。ロジアナでSRAMのアドレスまで出して詳細に調べる。殆どの8ビットフレームはちゃんとSRAMにアクセスし、カウンターも上がっていくが、ところどころ、書き込まれずにスキップしたり、カウンターが変わっていない(または2以上カウントアップ)ところが出ている。

 40センチほどあるSPIの配線が長すぎるかと思ってジャンパー線を減らし短くしてみる。少し改善されたようには見えるが、相変わらず安定しない。カウンターはいまだにでたらめである。SPIは殆どが基板上のディバイス間でしか使われないので、こういうジャンパー線で20センチも延ばしてはいけないのかも知れない。プルアップも効果なし。

Spi811adr

 謎は深まるばかりである。ロジアナを見れば、SPIはほぼ所定のデータを読み、WE(WriteEnable)が8ビット単位に忠実にパルスが上がってSRAMに書き込みをしていることは間違いない。不審なところは、同じalwaysループ内で動いているSRAMアドレスの増え方である。

 アドレスは読み書きで同一のバスを使っているが、このassign文をやめて別個のregにして2重に持たせるようにした。しかしこれでも駄目。一体何だろう。アドレスがところどころで突然0に戻る。しかも、途中で書き込みが終わってしまうときもある。CSにノイズが入っているのか。そんなばかな。これまでにない難関にぶちあたってしまったようだ。少し頭を冷やそう。

やっぱり聞いてみるものだ(8/12/2010)

 この5日間、悩み続けたFPGAの謎は、「すん」さんの掲示板に上げた質問に、いつもお世話になっているkugaさんが助言してくれて、いっぺんに解決した。HDLはプログラムではない回路だと考えろなどと少し偉そうなことを書いていたが、所詮はソフト屋である。

 同じループ内にあれば、全てのステートメントは間違いなく実行されるものと信じていた。ところがループを駆動するタイミングと判断する信号の間が非同期の場合は、その動作は保証されないという、FPGAでは極く初歩(だろうと思う)の常識を知らなかった。

 SPIスレーブは相手マスターからの同期クロックで動くメイン側から見れば典型的な非同期回路である。これを気楽にアドレスカウンターのインクリメントのトリガーに使っていたために、アドレスやカウンターのインクリメントが不定になっていたのである。

 アドレスカウンターは256Kワードなので18ビットである。さらにデータカウンターも18ビット、計36ビットをSPIのデータレディをトリガーにすれば、マスタークロックのタイミングと必ずいずれどこかでぶつかって、そのときの値は不定になってしまう。

 夜中、恐る恐る「こんなことってあるのか」などと大層なタイトルで掲示板に上げたら、ものの1時間もしないうちにkugaさんから回答があった。最初、非同期ということがわからず、反論でもないけれどロジアナのチャートを見せて「どこかでカウンターをこわしているやつがいる」的な主張をしたら、kugaさんから「18段のDFF(D-FlipFlop)を同時に動かすとき」という言葉で目が覚めた。

 これまでのブログをお読みになれば分かるように、当研究所のFPGAの勉強は基礎からやっていない。7セグLEDやUART、カラーバーなどが思ったより順調に動いたものだから、つい甘く見ていた。基礎からの勉強の必要性を痛感した。疑っていたFPGAさんごめんなさい。

 コードの方は、
always @(posedge pCLK) spi_rdy <= ( byte_received );

spi_rdy      ...... メインモジュールでのレディ信号
byte_received ..... spi入力(スレーブ)モジュールでのレディ信号
pCLK      ..... メインモジュールのマスタークロック
の一行をspi入力モジュールにつけるだけで、ほぼ正しくデータがSRAMに入るようになった。ばんざい。いやあ、やっとトンネルを越えた。

813spi_ok

 しかし不思議なことがある。最初のfpga4funからもらったSPIはマスタークロックで動いていた。非同期回路ではない。それがどうしてうまく行かなかったのだろう。試しに、fpga4funのコードを入れた見た。ややや、ちゃんと動くぞ。これはどういうことだ。

 しかし、どちらのルーチンもまだ細かい誤データが出る。8ビットの頭のビット(MSB)を間違え、1が半角のア、2がイになる。つまり、0011->1011 となる。ビットマップの画像データは文字データほどシビアではないとはいえ、こんなところでゴミは入れたくない。戦いはまだ終われない。

最後のエラーはUART送信のバグだった(8/14/2010)
 ロジアナを入れっぱなしにし、ソースを少しづつ変えながらUART端末を2つ立ち上げて、ファイルを送っては、チャートを調べ、出力データをチェックする作業を続ける。もう一息なのだけれど、まだ完全にERRORなしのデータは送れない。

 fpga4funの方のソースのSCKの立ち上がりを2ビットでなく、3ビットまで読んで立ち上がりとみなすというロジックに換えて(01Xでなく011で立ち上がり)、かなりエラーは少なくなった。サンプリングするところがSCKの立ち上がりから1クロック分あとになる方がエラーが少なくなるようだ。

 ARMの方にも少し心配が残っている。CS(ChipSelect)を別個のGPIOで出しているのだが、データを送り終える前に、デアサートされてしまう。最後の1バイトはエラーになるし、受信側(FPGA)のレジスターは途中で中断され悪影響が心配である。CSをデアサートする前に、SPIのビジービットがリセットされるのを監視するロジックを加える。

 これでエラー状況が好転するかと淡い期待を持っていたが、残念ながら全く影響はなかった。データカウントがファイルのデータ長と一致したのが唯一の収穫。

Spi_screen

 万策がつきた。この程度のエラー率(1KB送って数バイト)を許容して先に進むかどうかである。QVGAの8ビットカラーなら77KB、16ビットなら154KB、画面が汚れるだろう。そうだ全体のエラーがどれくらいになるか、調べてみよう。

 FPGAのUARTの部分に機能を追加して、スペースキーを押せば、SRAMのデータを一定量づつ際限なく表示するロジックを加えた。ここの部分は、複数データを任意にUART出力できるように改善して大分モニターらしくなっている。簡単に機能が追加できた。

 テストしてみる。すると驚くべき事実が判明した。同じSRAMアドレス上のデータが、表示するたびに変わるのである。あーあーあ、何故これをもっと早くやらなかったのか。これまでSPIのエラーだと思っていたのは、すべてUART送信側のエラーだったのである。SPIはしっかりデータを送っていたのだ。これまで疑っていたSPIのみなさん大変失礼しました。

 これで、プロセッサーからFPGAに画像データを送るルートは確立された。次のステップはいよいよ液晶モニターに画像を表示するステップである。買ってきたデジタル液晶はワイド液晶なので、ピクセルクロックに30Mhz以上を要求されている。これは少しきついので、これまでのアナログ液晶に簡単なラダー抵抗のDACを加えて、とりあえず8ビットカラーにしてみようかとも考えている。フォトフレームプロジェクトは山場を迎える。

| | コメント (2) | トラックバック (0)

2010年8月 6日 (金)

ARM STM32基板のSPIでFPGAにデータが送れた

SPI動き出す(8/1/2010)
 CQ-STARM基板(STM32F103VB6)のSPIインターフェースである。だいぶ慣れてきたとはいえ、ARMのディバイスをちゃんと動かすのは8ビットマシンと違って設定が大変である。手元にはFatFSというお手本があるのだが、このSDカードのSPIはDMAを使った複雑な構成で、こちらはまだDMAを使うだけのスキルがない。このソースを簡単に流用出来ない。まず新しくSPIを定義するだけで大変だ(CQ-STARMはSPIを3つ持っている)。

 構造的には、AVRのものと変わらないが、設定するレジスターが半端な数ではない。あちこち叱られながら、日曜一日で、今までのコード例を参考に、やっと2つめのSPI2が動き始めた(SPI1がSDカードアクセス)。出力チェックは最初、オシロでやり、動作を確認してからロジアナに切り替えた。デバッグのためUARTと一緒に動かしているので飛び飛びのデータだが、それらしい波形が出てきた。

 まだ、CS(チップセレクト)が動いていないが、ロジアナで見たところでは、ほぼ想定どおりだ。簡単なファイルを読ませて、シリアルデータを追う。よし、前のAVRと違って、全く問題なくクロックとデータが一致している。あとは正確なCSが出れば、結合テストに進むことが出来る。

Stm32pass_c_2

 シリアルデータを可視化できるロジックアナライザーが欲しくなってきた。今使っているオプティマイズのロジアナは、100Mhz分解能で、32チャンネル、128Kビットのサンプリングが出来、安くてもハードとしては十分な性能だが、ソフトがいまいちでパラレルは16進に変換できてもシリアルは出来ない。秋月や、ストロベリーリナックスで出しているロジアナが良さそうだが、現在のロジアナのハード性能を上回る機種は4万近くになるので迷っている。

SPIのチップセレクトを作るのに成功した(8/2/2010)
 気持ちの良い達成感。やっとのことで画像ファイルを送るルーチンが完成した。

 最初、SPIのデータは出たが、ファイル転送のトリガーになるチップセレクトが言うことを聞かなかった。チップセレクトは、SPIのNSS(Negative Slave Select)ピンを使っている。このピンを出力にしておけば、マスターで送信するとき必ず0になり、いちいち設定する手間が省けるということだ。

 しかし、ロジアナで見ると、送信前から0になったままで変化しない。それならというので、SPI_SSOutputCmdというコマンドで、強制的に動かそうとしたが、これも変化がない。Webで調べると、この状況は、STマイクロのフォーラムでERRATAではないかと話題になっている問題であることが分かった。

 ここのサイトによれば、STM32F103のSPIにはデータを送るときや、マスタースレーブの切り替えで、自動的にチップセレクトをアサート(負論理なので0)する機能がついているはずなのだが、これが動かないらしい。

 こちらも動かそうといろいろやったが変化がない。途方に暮れたとき急に思いついた。これまでのFatFSのSDカードのSPIでは、この問題をどうやって解決したのだろう。あわてて、ソースを見てみた。何とこのピンを全く使わず別の普通のGPIOピンを使って制御していた。なーんだ。始めからこうすればよかったのだ。

Stm32_spi

 再配線が面倒なので、元のNSSピンを汎用のGPIOに設定し(だいぶSTMライブラリに慣れてきた)、送信の前後に、0(アサート)、1(デアサート)するステートメントを挟む。
よーし、ロジアナで送信の前後にちゃんとCSがでることを確認した。やれやれやっと動いた。

 フラッシュのサイズが80KB近い。LFNをはずすと、もう少し減るかもしれないが、JPEGライブラリが入るかどうか心配になってきた。

STマイクロのペリフェラルライブラリの使い方を簡単に紹介(8/3/2010)
 だいぶSTM32の汎用ピンの設定に慣れて、その構造がわかってきたので、これからSTM(マイクロ)の標準入出力ライブラリを使ってARMのペリフェラルを開発する人のために、使い方を簡単にまとめておこうと思う(自分のためでもある)。これを理解するまで大変だったのだ(ライブラリのバージョンは3.1.2)。

 STマイクロのライブラリの使い方は、日本語では、ここに結構詳しく載っているが、専門家向けすぎて良くわからないところがある。これから紹介する方法は、理論的でもなく一部の例だけなので全体を理解するのには役立たないかもしれないが、実践的なことが強みである。ひとつ通してしまえば他への応用が効くと思う。

 ARMのペリフェラル(ディバイス群のこと)や、GPIO(汎用入出力ピン)の設定は、AVRのように簡単ではない。特にGPIOは、単純なことをやりたいだけなのにとても面倒である。AVRなどではI/Oポートの設定は、入出力か、プルアップか、初期値が0か、くらいの設定しかなく、設定レジスターも2つしかないので初期化は気が抜けるほど簡単だが、ARMはそうはいかない。ARMは、ピンごとに詳細な仕様をことこまかに設定する必要がある。

 こうしたペリフェラルの設定は、ARMにもAVRと同じように各設定レジスターの全パラメータを定義したヘッダーファイル(stm32f10x.h)が用意されているので、これを使ってレジスター単位にひとつづつ設定していっても良い(もちろん直に即値を入れてもかまわない)が、ソースコードの視認性と移植性、メンテナンス性は格段に落ちるだろう。

 というのは、各機能ごとに膨大な数のレジスターがあり、特に別機種への移植を考えると、こうした情報隠蔽化や抽象化をしておかないととんでもない時間がかかってしまう。

 メーカーにとっては、同じソースコードを最小の変更で使い回しできるので断然有利だが、アマチュアにとってみれば、調べることがかえって増えるので、それほどのメリットはないかもしれない。

 いやそれにしてもLEDを点滅させるだけでも大変な手順を踏む必要がある。これに輪をかけてタイマーの設定は複雑だ。、今流用させてもらっているソースFatFSにも良い例(リエントラントで動くインターバルタイマー)があるので、これはあとでじっくり勉強する予定だ。

 さて、ARMのペリフェラルとGPIOの設定方法である。GPIOとSPIの例を下にあげる。他のペリフェラルも細かい設定は異なっても、手順は共通のようだ。

 ここでは開発環境については詳しく触れない。STマイクロから提供されるライブラリをコンパイルの時どこに置くかは、Makefileで定義するが、このあたりは先人の環境をそっくり真似て、そこへ新しいソースを置くか、これまでのmain.cに付け加える。最初のうちはこの方法が一番楽だ(今度も「ねむい」さんの環境をそっくり真似た)。

それでは、新しいディバイスを設定していこう。

1. まず、ARMではペリフェラルが使うピンだけではなく、それぞれのピンの属するバスを調べて、このバス単位に各ピンのクロックをENABLEにするところから始めなければならない。これは完全に機種依存で、それぞれのチップのデータシートで調べる。CQ-STARMのSTM32F103VB6は、SPI1は、APBバスの2番で、SPI2は、APBバスの1番だったりするので気をつけないといけない。

RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2| ..., ENABLE);

2. 次に、各ペリフェラルに固有のパラメーター設定のための構造体を定義する。例はGPIOで、構造体の定義は各ディバイス単位のヘッダーファイルstm32f10x_gpio.hにあるのでその型(GPIO_InitTypeDef)で、自分の構造体を作る。

  GPIO_InitTypeDef  GPIO_InitStructure; // インスタンスを作る

3. この構造体のメンバーに、それぞれのパラメーターを定義していく。パラメーター名はヘッダーファイルに#defineで出ている。下の例では、ピン13と15がGPIOピンに指定され、50MHzで駆動し、モードはAF(Alternative Function、SPIやUARTなどの特定機能)PP(プッシュプル、普通の出力ピン、他にオープンドレインやプルアップの入力などがある)にする。このように、STM32では、各ピン単位に相当きめ細かい設定が出来る。

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_15;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;

4. こうして設定されたパラメーターを次のコマンドで初期化する。これはGPIOB
  つまりポートBの初期化である。(ポートAならGPIOA)
GPIO_Init(GPIOB, &GPIO_InitStructure);

(このコマンドは、何度も実行可能。同一ピンに対しては最新が有効)

5.GPIOはこれでやっと下記の関数を使って、0、1にすることが出来る。

GPIO_SetBits(GPIOB, GPIO_Pin_12);   //pin12を1にする
GPIO_ResetBits(GPIOB, GPIO_Pin_12);   //pin12を0にする

6.SPIなどのペリフェラルは、さらに2と同じように初期化構造体をつくり、パラメーターを設定していく。まず、SPIなら構造体の型、SPI_InitTypeDefを使って、

SPI_InitTypeDef  SPI_InitStructure2;    // インスタンスを作る

SPI_InitStructure2がその構造体のインスタンスの名前。GPIOと同じようにここへパラメーターをメンバーとして定義していく。

/* SPI2 configuration   8/1/2010 Chekcked */
SPI_InitStructure2.SPI_Direction = SPI_Direction_1Line_Tx;
SPI_InitStructure2.SPI_Mode = SPI_Mode_Master;
SPI_InitStructure2.SPI_DataSize = SPI_DataSize_8b;
SPI_InitStructure2.SPI_CPOL = SPI_CPOL_Low;
SPI_InitStructure2.SPI_CPHA = SPI_CPHA_1Edge;
SPI_InitStructure2.SPI_NSS = SPI_NSS_Soft;
SPI_InitStructure2.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_8;
SPI_InitStructure2.SPI_FirstBit = SPI_FirstBit_MSB;
SPI_InitStructure2.SPI_CRCPolynomial = 7;   // Default=7 (not use)

7. もちろん、このパラメーターも、次の関数で初期化。

 SPI_Init(SPI2, &SPI_InitStructure2);

このSPI2というのは、実際のディバイスのI/Oレジスターの構造体名で、これらは既に定義済み(stm32f10x.h)で新たにユーザーで定義しようとするとエラーになる(PORTAみたいなもの)

8. これで終わりではない。さらに動作を始めるために、次の関数で有効にする。

 SPI_Cmd(SPI2, ENABLE);

9.これでやっと、定義と初期化が終わった。このあとディバイス所定の関数を使うとディバイスが動き始める。おつかれさま。

SPI_I2S_SendData(SPI2, c16);          //SPI2へ、c16にある16ビットデータを送る。SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE);   //送信終了チェック
  ・・・
  ・・・

 気をつけなければならないのは、この各ペリフェラルの設定だけでは動かないことだ。必ず、3.のように、そのペリフェラルが使うピンに対してもGPIOの設定をしなければならない。GPIOモードのAF_PP(汎用ピンとして使うなら、Out_PP)などである。こうしたピンは固定されているのでペリフェラルの定義だけで動きそうだが、これがないと実際の入出力をしてくれない。これを知らずにはまった人は沢山いるだろう(かくいう私もそうである)。

FPGAにデータは送れたが(8/6/2010)
 ARMでSDカードのファイルのデータをSPIで送るところまで成功した。次はいよいよFPGAがこのデータを正しく受け取り、SRAMに展開してくれるかをテストする段階である。

 FPGAのLatice XP2基板を取り出す。机の上には、ARM、Lattice XP2基板に加え、UARTモジュール2つ(秋月USB-UARTモジュールと、自作COM1-TTLアダプター)、Latticeのパラレルライターと基板5つが勢ぞろいし壮観である。

 XP2基板は、UARTでSRAMに読み書きするコードが書き込まれている。これを、SPIで読んだデータだけを単純に出力するコードに書き換える。ARMのCOM1端末から、ファイル名を指示し、送った後、XP2のもうひとつのUART端末でキーを押せば、入っただけのデータがこのUART画面に出力されるという勘定である。

A8063042

 デバッグのためオシロやロジアナで調べやすいようブレッドボードでSPIやUARTケーブルを中継する。すべての結線は済んだ。緊張の瞬間である。ARMでファイルを指定しキーを押す。何事もなく送信が終わった。

 次は、データが正しくSRAMに書かれたかどうかを確かめる。FPGA(XP2)のUARTのリターンキーを押した。っと、何も変化がない。高揚した気分が一気に坂道を転げ落ちる。何度やっても同じ。

 まあ、これはいつものことだ。何度かやるうち、送っているときにFPGAのLEDが点くのを発見した。FPGAのSPIコードにはLEDが点くステートメントが入っていたのを思い出した。おお、FPGAのSPIはデータは読んでいるようだ。少し希望の光が差した。

 気を取り直して、FPGAのソースをチェックする。チップセレクトが下がればSPIのクロックを監視することにして、上がれば受信を中止し、次の受信のためカウンターを0に戻している。おーっと、また初歩的ミスだ。これでは、折角受信をしても終了のチップセレクトが上がったとたんにカウンターが0になりデータがないことになってしまう。

 急いで、ここをなおし再度テスト。やった、やった。それらしいデータがあらわれた。LFとCRキャラクターの関係で行がずれたりしているけれど、確かにSDカードのデータだ。テストしたファイルは小さなファイルだったので、今度は少し大きめのファイルを送る。

 いけない、UARTが暴走を始めた。SRAMの中味ではないようだ。不思議なことに、一旦暴走してFPGAをリセットした後は、最初成功した小さなファイルも表示できなくなった。電源を切ってSRAMの中味が完全に消えると元に戻るが、どこが悪いか見当がつかない。

 FPGAのデバッグは難しい。送られたデータ数をカウントしている変数ctrの内容がわかれば、暴走の原因はつかめるのだが、プロセッサーと違って、FPGAの中の変数を簡単にソースコードを加えて表示することが出来ない。

 まあ、とにかくプロセッサーからFPGAにデータを送るところまでは動いたようだ。このあとは、じっくり調べていくことにして、とりあえずはここまでの状況を報告しておくことにする。

| | コメント (0) | トラックバック (0)

« 2010年7月 | トップページ | 2010年9月 »