« ARM STM32基板にSPIインターフェースを作る | トップページ | FPGAのSPIインターフェース遂に完成 »

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にデータを送るところまでは動いたようだ。このあとは、じっくり調べていくことにして、とりあえずはここまでの状況を報告しておくことにする。

|

« ARM STM32基板にSPIインターフェースを作る | トップページ | FPGAのSPIインターフェース遂に完成 »

電子工作」カテゴリの記事

ARM」カテゴリの記事

FPGA」カテゴリの記事

コメント

コメントを書く



(ウェブ上には掲載しません)


コメントは記事投稿者が公開するまで表示されません。



トラックバック


この記事へのトラックバック一覧です: ARM STM32基板のSPIでFPGAにデータが送れた:

« ARM STM32基板にSPIインターフェースを作る | トップページ | FPGAのSPIインターフェース遂に完成 »