2016/12/20

[PSoC Advent Calendar 2016 23日目] PSoC 3/5LPでDMA

PSoC 3/5LPには24チャンネルのDMAが搭載されていますが、それをSPI接続センサのデータ受信に使ってみました。
DMAに関する情報はアプリケーションノート
にありますが、今回はSPI通信への応用です。
PSoC 3/5LPでのDMAを利用したSPI通信についての情報は以前はExample Project EP562734で、現在はCode Example CE95376で提供されています。 
作製しているデータロガーHPA_Naviシリーズでは100spsでSPI接続の加速度、ジャイロ、地磁気、気圧センサの情報を取得していますが、この部分の処理に時間がかかってしまうと通信等の他の処理に影響が出てしまいます。
試作時にはSPIMモジュールのSPIM_PutArray関数を利用して通信を行っていましたが、ロジアナで確認した限りではバイト間に隙間ができるようで、フルスピードでの通信ができていない様子が確認できました。
そこで、DMAを用いたSPI通信の高速化を行いました。
 モジュールの配置は以下の図のようになります。
SPIモジュールとDMAの配置。3つのセンサを用いるのでChip Select信号とDMAの切り替えを行っている。
プログラムは以下のようになります。
FreeRTOSのタスクとしてSPIセンサの処理部分を実装しています。
計500行程度のプログラムですが、DMAの初期化部分が350行ほどで大部分を占め、センサの初期化部分は50行ほど、メインの処理は100行ほどです。
センサから取得したデータは、拡張Sylphideフォーマットに従って配列pageA, pageMに格納されます。
DMAの転送順を制御することで格納の順序をプログラムで交換する作業を最小限にしています。

はじめに加速度・ジャイロセンサMPU-6000のDMA設定を示します。
//******************************************************************************
//! @brief 地磁気センサの送信DMA設定
//******************************************************************************
#define DMA_TX_MAG_BYTES_PER_BURST       (1)
#define DMA_TX_MAG_REQUEST_PER_BURST     (1)
#define DMA_TX_MAG_SRC_BASE              (MAG_txBuffer)
#define DMA_TX_MAG_DST_BASE              (CYDEV_PERIPH_BASE)

uint8_t MAG_txBuffer[11] = {HMC5983_SPI_Read | HMC5983_SPI_Multiple | HMC5983_RA_DATAX_H, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00,
                            HMC5983_RA_MODE, HMC5983_MODE_SINGLE, 0x00};

static void DMA_TX_MAG_Configuration()
{
    uint8_t MAG_TxChannel;
    uint8_t MAG_TxTD[4];

    SPIM_SNS_TX_STATUS_MASK_REG &= (~SPIM_SNS_INT_ON_TX_EMPTY);    //SPIモジュールのTX_FIFO_EMPTY割り込みを止める

    MAG_txBuffer[7] = SPIM_SNS_TX_STATUS_MASK_REG;    //割り込みが止まった設定のレジスタをコピー
    MAG_txBuffer[10] = SPIM_SNS_TX_STATUS_MASK_REG;    //割り込みが止まった設定のレジスタをコピー
    
    MAG_TxChannel = DMA_TX_MAG_DmaInitialize(DMA_TX_MAG_BYTES_PER_BURST, DMA_TX_MAG_REQUEST_PER_BURST, 
                                        HI16(DMA_TX_MAG_SRC_BASE), HI16(DMA_TX_MAG_DST_BASE));

    MAG_TxTD[0] = CyDmaTdAllocate();
    MAG_TxTD[1] = CyDmaTdAllocate();
    MAG_TxTD[2] = CyDmaTdAllocate();
    MAG_TxTD[3] = CyDmaTdAllocate();

    CyDmaTdSetConfiguration(MAG_TxTD[0], 7, MAG_TxTD[1], TD_INC_SRC_ADR);
    CyDmaTdSetConfiguration(MAG_TxTD[1], 1, MAG_TxTD[2], DMA_TX_MAG__TD_TERMOUT_EN);
    CyDmaTdSetConfiguration(MAG_TxTD[2], 2, MAG_TxTD[3], TD_INC_SRC_ADR);
    CyDmaTdSetConfiguration(MAG_TxTD[3], 1, MAG_TxTD[0], DMA_TX_MAG__TD_TERMOUT_EN);
    
    CyDmaTdSetAddress(MAG_TxTD[0], LO16((uint32_t)MAG_txBuffer), LO16((uint32_t)SPIM_SNS_TXDATA_PTR));
    CyDmaTdSetAddress(MAG_TxTD[1], LO16((uint32_t)MAG_txBuffer+7), LO16((uint32_t)&SPIM_SNS_TX_STATUS_MASK_REG));
    CyDmaTdSetAddress(MAG_TxTD[2], LO16((uint32_t)MAG_txBuffer+8), LO16((uint32_t)SPIM_SNS_TXDATA_PTR));
    CyDmaTdSetAddress(MAG_TxTD[3], LO16((uint32_t)MAG_txBuffer+10), LO16((uint32_t)&SPIM_SNS_TX_STATUS_MASK_REG));

    CyDmaChSetInitialTd(MAG_TxChannel, MAG_TxTD[0]);
    
    CyDmaChEnable(MAG_TxChannel, 1);
}

//******************************************************************************
//! @brief 地磁気センサの受信DMA設定
//******************************************************************************
#define DMA_RX_MAG_BYTES_PER_BURST       (1)
#define DMA_RX_MAG_REQUEST_PER_BURST     (1)
#define DMA_RX_MAG_SRC_BASE              (CYDEV_PERIPH_BASE)
#define DMA_RX_MAG_DST_BASE              (pageM)

static void DMA_RX_MAG_Configuration()
{ 
    uint8_t MAG_RxChannel;
    uint8_t MAG_RxTD[36];
    uint8_t i;

    MAG_RxChannel = DMA_RX_MAG_DmaInitialize(DMA_RX_MAG_BYTES_PER_BURST, DMA_RX_MAG_REQUEST_PER_BURST,
                                     HI16(DMA_RX_MAG_SRC_BASE), HI16(DMA_RX_MAG_DST_BASE));
    
    for (i = 0; i < 36; i++) {
        MAG_RxTD[i] = CyDmaTdAllocate();
    }

    for (i = 0; i < 35; i ++) {
        CyDmaTdSetConfiguration(MAG_RxTD[i], 1u, MAG_RxTD[i + 1], TD_INC_DST_ADR);    
    }
    CyDmaTdSetConfiguration(MAG_RxTD[35], 1, MAG_RxTD[0], TD_INC_DST_ADR);

    for (i = 0; i < 4; i ++) {
        CyDmaTdSetAddress(MAG_RxTD[0 + 9 * i], LO16((uint32_t)SPIM_SNS_RXDATA_PTR), LO16((uint32_t)DMY_rxBuffer));
        CyDmaTdSetAddress(MAG_RxTD[1 + 9 * i], LO16((uint32_t)SPIM_SNS_RXDATA_PTR), LO16((uint32_t)(pageM + 10 + 6 * i)));    //センサX
        CyDmaTdSetAddress(MAG_RxTD[2 + 9 * i], LO16((uint32_t)SPIM_SNS_RXDATA_PTR), LO16((uint32_t)(pageM + 11 + 6 * i)));
        CyDmaTdSetAddress(MAG_RxTD[3 + 9 * i], LO16((uint32_t)SPIM_SNS_RXDATA_PTR), LO16((uint32_t)(pageM + 12 + 6 * i)));    //Z
        CyDmaTdSetAddress(MAG_RxTD[4 + 9 * i], LO16((uint32_t)SPIM_SNS_RXDATA_PTR), LO16((uint32_t)(pageM + 13 + 6 * i)));
        CyDmaTdSetAddress(MAG_RxTD[5 + 9 * i], LO16((uint32_t)SPIM_SNS_RXDATA_PTR), LO16((uint32_t)(pageM +  8 + 6 * i)));    //Y
        CyDmaTdSetAddress(MAG_RxTD[6 + 9 * i], LO16((uint32_t)SPIM_SNS_RXDATA_PTR), LO16((uint32_t)(pageM +  9 + 6 * i)));
        CyDmaTdSetAddress(MAG_RxTD[7 + 9 * i], LO16((uint32_t)SPIM_SNS_RXDATA_PTR), LO16((uint32_t)DMY_rxBuffer));
        CyDmaTdSetAddress(MAG_RxTD[8 + 9 * i], LO16((uint32_t)SPIM_SNS_RXDATA_PTR), LO16((uint32_t)DMY_rxBuffer));
    }

    CyDmaChSetInitialTd(MAG_RxChannel, MAG_RxTD[0]);

    CyDmaChEnable(MAG_RxChannel, 1);
}
こちらも基本的には加速度・ジャイロセンサの場合と同じですが、毎回測定開始命令を送る都合で、送信側で2度SPIの割り込みが止まるようになっています。
また、4回のデータを1つのページとして記録するので、受信側ではデータ転送先の異なる4組のDMA設定を行っています。

最後に気圧センサMS5611のDMA設定を示します。
//******************************************************************************
//! @brief SPIタスクの初期化関数
//! @memo HMC5983はcontモード+DRDY監視なしよりsingleでデータ取得したほうが取りこぼしが少ない
//! @memo MPU6000: 90 - 100kHz, 0.9 - 1.1MHz, 18 - 22MHz Typ
//! @memo HMC5983: 8 MHz Max
//! @memo MS5611: 20MHz Max
//! @maemo マスタークロックを分周して使う。16 MHzの倍数。
//******************************************************************************
#define SPI_DOF (0)
#define SPI_MAG (1)
#define SPI_PRS (2)

void TaskSPI_init()
{
    uint8_t init_DOF[][2] = {{0x6B, 0x80}, {0x6B, 0x00}, {0x19, 0x09}, {0x1A, 0x03}, {0x1B, 0x00}, {0x1C, 0x00}};
    uint8_t init_MAG[][2] = {{0x00, 0x9C}, {0x01, 0x00}, {0x02, 0x01}};
    uint8_t i;

    Clock_SPI_SetDividerValue(32); // 64 / 32 = 2 MHz SPI 1 Mbps
    SPIM_SNS_Start();
    
    //MPU-6000の初期化
    Control_Reg_SNS_Write(SPI_DOF);     //CS選択
    for (i = 0; i < 6; i ++) {
        SPIM_SNS_PutArray(init_DOF[i], sizeof(init_DOF[i]));
        while(!(SPIM_SNS_ReadTxStatus() & SPIM_SNS_STS_SPI_DONE));
        CyDelay(1);
    }
    
    //HMC5983の初期化
    Control_Reg_SNS_Write(SPI_MAG);     //CS選択
    for (i = 0; i < 3; i ++) {
        SPIM_SNS_PutArray(init_MAG[i], sizeof(init_MAG[i]));
        while(!(SPIM_SNS_ReadTxStatus() & SPIM_SNS_STS_SPI_DONE));
    }
   
    //MS5611の初期化
    Control_Reg_SNS_Write(SPI_PRS);     //CS選択
    MS561101BA_initialize();
    
    MS561101BA_startConversion(MS561101BA_D1 | MS561101BA_OSR_4096); //初回の読み込み用

    //クロック切り替え
    Clock_SPI_SetDividerValue(2); // 64 / 2 = 32 MHz SPI 16 Mbps

    //TX側DMAの初期化
    DMA_TX_DOF_Configuration();
    DMA_TX_MAG_Configuration();
    DMA_TX_PRS_Configuration();
    
    SPIM_SNS_ClearRxBuffer();    //初期化で少しゴミが受信バッファに入るのでそれを消しておく
    
    //RX側DMAの初期化
    DMA_RX_DOF_Configuration();
    DMA_RX_MAG_Configuration();
    DMA_RX_PRS_Configuration();    
}
初期化コマンドを配列に入れて一度にセンサに転送しています。
初期設定終了後、DMAの初期化を行います。

最後に、センサデータ取得部分を示します。
DMAにデータ転送に関する部分をかなり押し付けているので、センサデータ取得部分はDMA開始命令を送り、符号変換等の細かな処理を行うのみです。
取得したデータは、SDカード・USBUART等の処理を含む出力ストリーム関数に送ります。

2016/12/16

[PSoC Advent Calendar 2016 16日目] SDカードライブラリの比較

SDカードへの書き込みはデータロガーに必須の機能かつ性能に深く関わる重要な要素であるので、これまでに何度かSDカード周りのテストを行い、関連記事も書いてきました。
PSoC 3/5/5LPではじめてSDカードを扱ったのはPSoC 3 ES3が出始まった時期で、その頃のPSoC Creatorには標準のSDカードライブラリがなかったため、FatFsを自前で移植して利用していました。
その後しばらくしてemFileが利用可能になりSDカードの扱いはかなり楽になりましたが、主に速度面での性能差が知りたかったため、emFileとFatFsの比較も行いました。

今回の話はその続編のようなものです。
その存在に気がついたのはつい最近なのですが、PSoC 3/5LPで利用可能な新手のSDカードライブラリNSDSPIが現れたので、emFileとの性能比較を行ってみることにしました。
NSDSPIはデータの転送にDMAを用いているため、速い読み書きが期待できます。

テストはCY8C5888AXI-LP096を搭載するHPA_Navi IIIを使って行いました。
前回のテストとの比較を容易にするため、CPUクロックは50MHz、SPIクロックは12.5MHzに設定しています。
使用したmicroSDカードはGigastone製の2GByteのものです。
最も単純な書き込みと読み込みのテストを行い、その速度を比較しました。
NSDSPIのテストにはNSDSPI付属のComponentTestのピン配置とクロック周りをハードウエアに合わせて変更したものを利用しました。
また、emFileのテストはNSDSPIのテストに用いたプロジェクトのSDカード関連関数をemFileのものに書き換えて行いました。

転送するデータサイズを変えて行った読み書きテストの結果を以下に示します。
データサイズは2^Nバイトとし、FatFsが内部で利用するバッファサイズの512byte以上かつ8192byte以下で行いました。
計測は1ms単位でしか行っていないので、速度には多少の誤差が含まれていると思います。
書き込み速度の比較
読み込み速度の比較
コンパイル条件が同じ場合にNSDSPIとemFileの速度を比較すると、読み書き共におおむね同程度のようです。
また、Debugモードでコンパイルを行った場合の結果から、DMAを有効にすることで読み書きの速度が大幅に向上することがわかりました。
さらに、コードの最適化という意味では不利な条件にもかかわらず、今回試した範囲では最も高速に読み書きできていることがわかります。

今回試した限りだと、NSDSPIのDMAモード動作にはどこか問題があるらしく、Debugモードでビルドした場合のみに動作が確認できました。
また、動作するクロックにも制限があるのか、CPUクロック50MHz、SPIクロック12.5MHzの場合に動作が確認できたものの、たとえばSPIクロックを10MHzに下げるなどするとうまく動かなくなってしまいました。

今回のテストで用いたプログラムでは利用範囲に制限がありましたが、DMAの利用でSDカードの読み書きがかなり高速化できることがわかりました。
HPA_Navi IIIでうまくNSDSPIが利用できればシステムの負荷が軽減できそうなので、どこに問題があるのかを調べてみたいと思います。

テストに利用したプロジェクト類はライセンス周りを確認の上、後日githubで公開しようと思います。

2016/03/12

JAXA小型低乱風洞によるエアデータセンサの校正3

間があきましたが、前回の続きです。

今回は5孔ピトー管の校正についてです。

5孔ピトー管は中央孔の圧力を用いて対気速度を測定できるほか、上下・左右の差圧を用いることで、迎角・横滑り角を測定することができます。
実験では、ピトー管を風洞に対して傾けることで迎角・横滑り角を変え、その際の圧力センサ出力を取得しました。その時系列データを下図に示します。
5孔ピトー管の校正を行った際の各圧力計の出力電圧
風洞の流速を10, 15, 20 m/sに固定し、迎角・横滑り角の順に角度を変えてデータを取得しました。
ここから、迎角・横滑り角に対する各流速での圧力センサ出力を取り出すと以下の3つのグラフのようになりました。
10 m/sでの圧力センサ出力の迎角・横滑り角依存性
15 m/sでの圧力センサ出力の迎角・横滑り角依存性
20 m/sでの圧力センサ出力の迎角・横滑り角依存性
さらに、流速によらない校正定数を求めるために、上の3つのグラフで得られた結果を中央孔の圧力で規格化し、まとめたものが下図になります。
中央孔圧力で規格化した、差圧出力の迎角・横滑り角依存性。図中の直線は1次関数によるフィット、曲線は3次関数によるフィット。グラフの右下には得られた回帰直線の傾きに対応する校正係数を示している
上の図の傾きを用いることで、規格化した圧力センサ出力から迎角・横滑り角を求めることができます。
迎角・横滑り角が±10度の範囲では直線近似がおおむねよく成り立ちますが、それ以上の角度では回帰直線からのずれが見られます。
精度の高いデータを得るためには3次の回帰曲線を用いて圧力センサ出力から迎角・横滑り角を求める必要がありそうです。

最後に、迎角と横滑り角の間のクロストーク、すなわち迎角(横滑り角)を変えた場合の左右(上下)の差圧出力を示します。
迎角・横滑り角測定のクロストーク
迎角を変えた場合に現れる横滑り角測定値の変化はおおむね0.5度以内に収まっていますが、横滑り角を変えた場合に現れる迎え角測定値の変化は、横滑り角におおむね比例し、最大2.5度ほどになっています。
これは、ピトー管を回転テーブルに取り付けた際の角度アライメントが悪いことに対応する結果であると考えられます。
同等の問題は機体にピトー管を取り付ける際にも発生するので、後に補正が可能なように、機体軸とピトー管軸のずれを、写真を取るなどして記録、測定する必要があります。

以上の校正作業で、圧力測定値から迎角・横滑り角を得ることができるようになりました。
春先から試験飛行が再開するので、実機に搭載しさらなる評価を進めていく予定です。

2016/03/09

TallysmanのGNSSアンテナ

性能が高いというTallysmanのGNSSアンテナTW2405を購入してみました。
よく使われているのは防水型のTW2410ですが、試験飛行と記録飛行で使う分には防水不要なので、OEM版のTW2405を選定しています。

TW2405のケーブルは短めでMCXコネクタがついたものです。
コックピットに搭載したGNSSモジュールとアンテナを接続するにはケーブルの延長が必要ですが、ケーブルは駆動系近くを通る予定でメンテナンスがしにくいこともあるので、シールドを外し適切な長さのSMAケーブルに取り替えました。
TW2405のシールドを外した様子
TW2405の基板には"TW2410"とシルクが入っていて、基板が防水版のTW2410と共通なことが伺えます。また、アンテナとLNAの間にはSAWフィルタ用のパターンもあり、追加フィルタありバージョンのTW2407とも共通の基板を使っていることが想像できます。

GNSSアンテナとして動作することはベランダに設置して確かめましたが、肝心のRTK性能については未確認です。
どこか見晴らしがよく電子基準点に近い場所でRAWデータを取って、どの程度Fix解が得られるかをテストしてみたいと思います。

2016/03/07

HPA_Navi III実装中

HPA_Navi IIの後継となるHPA_Navi IIIの製作を始めました。

HPA_Navi IIと製作中のHPA_Navi III

HPA_Navi IIをしばらく運用した上で問題点を洗い出した上小型化しセンサ類を最新版に更新しています

仕様はほぼHPA_Navi IIと同じですが、以下の点に差異があります。
  • 基板の小型化・軽量化
  • GNSS対応: 搭載GPSモジュールをublox6シリーズからublox8シリーズに変更
  • GNSSアンテナのコネクタを変更: SMAからu.FLへ
  • 慣性・地磁気センサ構成の変更: MPU-6000+HMC5983の2チップ構成からMPU-9250の1チップ構成へ
  • アナログポートへの供給電圧の変更: PSoC 5LP出力(標準仕様では1.024V)から5Vへ
  • 各種タクトスイッチの廃止
  • USBコネクタの変更: miniからmicroへ
部品の実装とファームウエアの開発が済んだら、まずは可変ピッチプロペラの制御基板として利用してみる予定です。