2015年12月31日木曜日

Visual Studioのコメント



Visual StudioではCのソースファイルでもマウスオーバーでいろいろ表示できる。あとTODOリストとかも作れる。けどC#と違いsummaryの自動入力とかはできないみたい。それから戻り値はそもそもカーソルを合わせられないので、戻り値の説明をreturnsで書いてもマウスオーバーでは表示できない。
そもそも何も書かなくたってマウスオーバーで宣言が表示されるんだから変数名で説明しろとかいろいろ言いたいことはあるだろうけど、とりあえずこういう書き方もあるんだよって。

Visual Studio限定なので、Doxygenみたいなマルチプラットフォームとかはあんまり無い。
使い所が難しそうだけど、とりあえず/// <summary>とかで書いておけば最悪Doxygenに移行したい時には正規表現の置換とかでなんとかなりそうな気もするので、場当たり的にコメント書くよりはマシだと思う。

2015年12月23日水曜日

SDカードの速度



SDカードの速度を測ってみた。512バイトだけシングルブロックライト、それ以外はマルチブロックライトで一気に書き込んでいる。速度は毎回変わるし、精度もあまりないので正確じゃないけど。

512バイトを書いた時で60kByte/sec、5120byte書いた時で540kByte/secくらい出てる。大容量書いてもあんまり時間が変わらない。1024byteから512byte増えるごとに0.25msecくらい増えていく。



4KiBを書いた時のキャプチャがこんな感じ。マルチブロック書き込みで8ブロック分を書き込んでいる。その後のシングルブロック書き込み複数個でFATを操作しているっぽい。
マルチブロック書き込みでは最初の1ブロックを書いたあとにちょっと長いビジーが発生し、その後のビジーはあまり長くない。

SPIの速度を変更する

STM32F1のSPIはCR1のBRに設定する。これはStdPerphLibではSPI_Initのみで変更できる。ということで自前で関数を作る必要がある。
static uint16_t SPI_Baud = 0;

static void SPI_BaudLow(void) {
    SPI_Baud = SPI1->CR1 & 0x0038;

    SPI_Cmd(SPI1, DISABLE);
    SPI1->CR1 = (SPI1->CR1 & 0xFFC7) | 0x0038;
    SPI_Cmd(SPI1, ENABLE);
}

static void SPI_BaudHigh(void) {
    SPI_Cmd(SPI1, DISABLE);
    SPI1->CR1 = (SPI1->CR1 & 0xFFC7) | SPI_Baud;
    SPI_Cmd(SPI1, ENABLE);
}

SPI_BaudLowではSPIを最低の速度で駆動する。72MHzなら281.25kHzとなる。またSPI_Baudに速度をバックアップする。
SPI_BaudHighではバックアップされたボーレートをレジスタに書き戻す。これはSPI_Initで設定した値になる。またSPI_BaudLowを呼ばれていない場合はSPI_Baudには0が入っており、これは分周比2となる。

それとボーレートの設定は通信中に変更してはいけないらしいのでSPI_Cmdでペリフェラルを終了させている。
ちなみにSPI_Cmdの中ではCR1のSPEを変更するだけなので、本来のSPI停止方法ではない。STM32F1のリファレンスマニュアルによると、RXバッファに値が入るまで待つ→RXバッファを読む→TXバッファが空になるまで待つ→ビジー状態でなくなるまで待つ→SPIを無効にする、という手順を踏む必要がある。ただこの方法は結構手間なのと、そもそも通信中にSPIを停止させるための手段なので、非通信時に変更したいのでSPI_Cmdだけで問題ないと判断した。

SDカードをSPIで使用するには、初期化時には100-400kbaudにする必要があるらしい。とりあえず36MHzでも問題なく初期化できているが、どうせ初期化するには200msecほどかかるので280kbaudでも36Mbaudでも大して変わりはない。

2015年12月22日火曜日

ZEROPLUS-LAP-Cでパケットのデータを表示する

LAP-Cは結構前から使っていたけど、パケットのデータを表示する機能は使ったことがなかった。



表示するにはWindowメニューのBus Packet Listをクリックすればいい。そうすると1トランザクションを一つのパケットとして表示する。複数のバスがある場合はバスごとに別れて表示される。データ量が多くなると右端で改行して次の行に表示されるようになる。データの位置(1始まりのカウント)はマウスオーバーをすると表示されるが、いまいち使いやすくない。16進数以外にもSetting...からASCIIや10進を選んで表示することができる。
I2Cのセンサを使っていた時にいちいち波形に表示されるデータを読んでいたが、Bus Packet Listなら一連のデータとして表示されるのでこっちのほうが簡単。

ところでLAPのソフトには幾つかのバージョンがあって、現在は3.12.xxが最新だと思う。しかし12系ではパケットキャプチャがインストールされない。そのため新規でLAPのソフトをインストールする場合は3.11系をインストールした後に、12系にアップデートした方がいい。過去のバージョンはメーカーのWebページからダウンロードすることができる。しかし台湾の方にあるサーバーで1個が50MB以上あるので、結構時間がかかる。いざ使いたい時に慌てないように。
UARTやSPIやI2Cは11系に無料で使えるキャプチャが入っているので、電子工作をする上では必須。他にもいろいろなパケットに対応していて、CANもあるのだが、大抵は有料で「ライセンス買ってね!」と表示されてぐぬぬ…となっているが。

プログラムの不具合はソースコードの中からおかしいところを探すほうがいいのだが、自分で書いたコードに入っている不具合は自分で発見することはかなり難しい。個人で開発する上ではロジック・アナライザは必須な機材であろう。

DMAでFatFs









上から
1) ソフトウェアでファイル読み込み
2) DMAでファイル読み込み
3) ソフトウェアでファイル書き込み
4) DMAでファイル書き込み
です。

1がおよそ11msec、2がおよそ3.5msec、3がおよそ22msec、4がおよそ12msec、という感じです。読み込みでは3倍、書き込みでは1.8倍 くらいでしょうか。書き込みではあまり高速化していませんが、1ブロック書き込むごとにビジーが2.7msecほどあって、これが2回あるので、ビジー待機だけで5msec以上になります。このビジー待機はおよそ1回の書き込みごとに1回の待機となりますから、1回に数kbyteを書くのも512byteを書くのも同じ程度の時間になります。つまり書き込みは一度に大量に書くほど早くなります。


ということでSDカードの書き込み動作もできました。あとはマルチブロックR/Wを作れば大容量を転送した場合のデータレートを計測できるようになります。
エラーチェックはほとんどやっていないし、挿抜検出も行っていないので実用には程遠いですが。
それと本来の目的はDMAによるバックグラウンド書き込みをやりたいので、スタート地点にも立っていないといえますが。


ところで、今回検証に使用しているメモリはTranscendの32GB/C10のmicroSDです。すぐ見つかる場所にあったのがこのカードだけなので、他のメーカーとの比較は行っていません。個人的には東芝のカードが早そうな気がするので試したいのですが、何かのついでに購入したあとになります。

STM32F1のSPIをDMAで双方向

STM32F1のSPI1を分周比2(バスクロック36MHz)で双方向通信した時のキャプチャです。



2バイトか3バイト程度を送ったところで少し隙間が空いてしまいます。これはDMAの帯域が足りないために、転送に遅延が発生していると推測できます。

F1のSPIはDMA1_3にTXが、DMA1_2にRXが設定されており、双方向で通信する場合にはDMA1の2chを使用することになります。36Mbit/secは4.5Mbyte/secですから、それが双方向で9Mbyte/secとなります。
一方、DMAは1転送に8クロック必要らしいので、72MHz動作では9Mbyte/secが限界となります。あれ、帯域足りてるじゃん。

おそらく1chだけを転送する場合は9Mbyte/secがフルに出るのですが、複数の転送を行う場合は次のデータを選ぶための比較が発生するためにフルで使用することができないのでしょう。

気になるデータの取りこぼしですが、先に書いたとおりSPIのTXが3、RXが2で、DMAは数字の小さいほうが優先されますから、受信のほうが優先度が高くなります。そのため受信データが転送されていない場合は送信が一時的に停止されるはずです。

ということで、「SPIをDMA転送したらきっちりSPI帯域使い切れるぜ!」と思ったらDMAの帯域が足りないというオチでした。それでもソフトウェアでフラグをポーリングして1バイトずつ送るよりは早いんですが。
もちろん送信だけの一方向通信なら受信データを読む必要はありませんから、SDカードへデータを書く場合は帯域をフルに使えます。バス速度的には 書き込み速度>読み込み速度 というちょっと不思議な感じになりそうです。

STM32F1のSPIをDMAで転送

今回は送信だけ、と受信だけ、の処理です。全二重の送受信処理は以下の関数を変形するだけで作れます。

送信は一方的に送りつけるだけなので楽ですが、受信する場合はデータを送信する必要がなくても送信用のDMAを設定する必要があります。何もデータを送る必要が無いのに大容量のバッファを確保して初期化するのも馬鹿らしいので、1バイトの配列に初期値(0xFF)を設定し、MemoryIncをDisableにして送信してやります。

それから転送が終わったら責任をもってDMA関連をDisableにしてやりましょう。さもないと次にSPIで通信を行った時に謎の動作をします(謎というか、ハングアップするだけですが)。

ソフトSPIとハードSPI

STM32F1のSPI1にmicroSDソケットを接続し、FatFsで読みだしてみました。分周比は2でバスクロックは36MHzです。





この画像はSDカードのシングルブロックリードコマンドの時のキャプチャです。上がDMA使用、下がソフトウェアで通信しています。
おおよその目安ですが、ソフトウェアでは1ブロックを読むのに1.8msec程必要です。対してDMAでは1ブロックの読み込みに460-600usec程度です。およそ3倍の速度差があります。前回のエントリではDMAとソフトでは10倍ほどの差がありましたが、今回はSDカードの内部処理待ちが含まれるために差が少なくなったのだと思われます。

今回はシングルブロックリードですが、マルチブロックでのR/Wであればだいぶパフォーマンスが向上すると思います。まだSDカードの書き込みコマンドや、マルチブロックの処理のコマンドは作っていないので、そのあたりを作ったらもう一度比較してみたいと思います。

2015年12月21日月曜日

STM32F1のSPI

STM32F1のStdPeriphDriverでは以下のコードでSPIの送受信と送信が可能です。

void SPI_WR(uint8_t *buff, uint16_t len) {
    do {
        while (!SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE));
        SPI_I2S_SendData(SPI1, *buff);

        while (!SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE));
        *buff = SPI_I2S_ReceiveData(SPI1);

        buff++;
    } while (--len);

    while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_BSY));
}

void SPI_W(const uint8_t*buff, uint16_t len) {
    do {
        while (!SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE));
        SPI_I2S_SendData(SPI1, *buff++);
    } while (--len);

    while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_BSY));
}

SPI_WR関数ではバッファのデータを送信しながら、受信したデータをバッファに保存します(与えた配列を破壊します)。
データを受信する必要が無い(送信だけすればいい)という場合はSPI_W関数を使用すれば受信処理を省くことができます。


ということでこのコードを使って36MHzで初期化したSPIにデータを送ってみましょう。



左がSPI_WRで右がSPI_Wです。送信専用では多少はマシとはいえ、バスの速度を十分に使用していません。これはソフトウェアでポーリングしているためで、バス帯域をフルに使用するにはハードウェアで送信する必要があります(DMAで転送する方法はこちらのエントリで)。

センサのデータを読み取りたいなど、数MHzで10バイト未満程度ならあまり問題になることはないかもしれませんが(*)、SDカードにアクセスする場合はこのバス使用率は問題です。DMAでハードウェア化した場合、受信では10倍程、送信でも4倍程の高速化が望めます。

*) 数バイト程度ならDMA初期化のオーバーヘッドがあるのでソフトウェアのほうが早い場合もあるので、各環境で用途に合わせて確認する必要があります。

/* 追記 */

SPIの転送にDMAを使うと右端のようになる。


左端のソフトウェアWR、中央のソフトウェアWに比べ、右端のハードウェアWではクロックの間に隙間がなく、バスの使用率はかなり高い。ただしCSをアサーとしてから最初のデータが送信されるまでにしばらく時間がかかっている。これはDMAの初期化を行っているため。でも2分周で10バイトくらい送るならDMAのほうが早そう。

void SPI_DMA_W(const uint8_t*buff, uint16_t len) {
    DMA_InitTypeDef DMA_InitStructure;

    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)(&SPI1->DR);
    DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)(buff);
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
    DMA_InitStructure.DMA_BufferSize = len;
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
    DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
    DMA_InitStructure.DMA_Priority = DMA_Priority_High;
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;

    DMA_DeInit(DMA1_Channel3);
    DMA_Init(DMA1_Channel3, &DMA_InitStructure);
    DMA_Cmd(DMA1_Channel3, ENABLE);
    SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Tx, ENABLE);

    while (DMA_GetCurrDataCounter(DMA1_Channel3));
    while (!SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE));
    while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_BSY));
}

他に予めRCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);でDMAのクロックを有効にしておく必要がある。

2015年12月11日金曜日

Cesiumで視点設定


Cesiumで視点を設定してみる。今回はとりあえず大樹町多目的航空公園の滑走路を指定した。


ドキュメントに書いてあるそのままが使える(Exampleの2. Using HeadingPitchRange offset)。
centerには注視点を設定する。滑走路上には垂直な線を1本と、高度500mあたりにラベルを書いている。今回の注視点はLabelの位置。
headinには視線の方向を、pitchには視線の仰角を指定する。多目的航空公園の滑走路方位は73度くらい(Google Earthによる実測)なので、今回はその角度を指定した。滑走路に垂直に立っている線が綺麗に滑走路の中心に見えるので角度の指定はおおよそ正しいようだ。それからpitchは-45を指定することにより斜め上から見下ろす感じになる。
あとrangeには視点から注視点の距離を指定する。今回は2500なので、注視点から半径2.5kmの球の表面に視点が存在するようになるはず。

それとlockAtでcenterを指定するとそこに注視点が固定されるみたい。なのでマウスで操作しても注視点から移動することができない。ズームはできるのでレンジの変更はできる。それからShiftを押しながらだと注視点を画面上の任意の位置に移動できる模様。

Cesiumはグラフィックリソースの要求が凄いのと日本語情報があんまりないのが欠点だが、JavaScriptでいろいろ叩けるので楽しい。
あと線量マップとか半減期とか原子時計とかいっぱい出てくるのでもうちょっと違う名前つけて欲しかった。

2015年12月6日日曜日

再配布可能パッケージの0x80240017エラー

Apacheを使おうと思って、とりあえずApacheを解凍してみたが、DLLが見つからない旨のエラーが出た。ということでWeb上の手順に従いVS2012C++の再配布可能パッケージを入れたが、それでもまだDLLが見つからない。どうやら2012ではダメっぽい。ということでVS2015C++の再配布可能パッケージをインストールしようとしたところ、0x80240017エラーが発生。

そもそもApacheを入れようとしたのはVirtualBoxにクリーンインストールしたWin7x64で、ほぼOSを入れただけの状態。そしてVS15C++のシステム要件には「Win7SP1」と書いてある。つまりService Pack未適用のWin7ではダメ。Windows UpdateでSP1を適用するとあっさりとインストールが進む。もちろんApacheも起動するようになる。

とりあえずソフトウェアを入れる時は必須環境のバージョンとかを確認するように、という教訓。

せっかくOSを入れるならUbuntuとか突っ込んでおけばよかったかなとか思ったり。

***

それと、VirtualBoxでWin7x64を入れる際にもいろいろと不具合が起こった。というのもOSの選択肢に64bitが出てこないのだ。BIOSで仮想化に使用する機能を有効化しないとダメらしい。このマシンを前のOSで使用した時に設定は変えたはずなのだけど、戻ってしまったのかと思ってBIOSを確認。MBはB85PLUSなのでBIOSではなくUEFIなのだけど、Intel Virtualization Technologyを確認したところ、ちゃんと"有効"に設定されている。
そして次に確認したのがWindowsの機能の"Hyper-V"というモノ。Windowsの機能の有効化または無効化の中にあるHyper-Vのチェックを外して再起動。
コレでようやく64bitが選択肢に出てきたので、それを選んでWin7x64をインストール。
しかしここでもまたつまずいた。取るべき選択肢が表示されているがいまいち理解しづらい。
これについてはVirtualBoxの"仮想マシン"→"設定"の"システム"カテゴリ"マザーボード"タブの"I/O APICを有効化"のチェックを入れることにより対応。本来64bitを選択するとこのオプションにチェックが入っているのだが、今回は選択肢が無かったために32bitで設定を作成してから、後でOSを64bitに変更した。そのためにチェックが外れていた模様。


ということでいろんな障害を超えながらようやく最低限の機能が使えるようになってきた。
しばらくはHTMLで遊ぶことになりそうだ。