2018/05/23

Nucleo-H743ZIでFatFSを使う(SDカード・DMAモード)

STM32H7はじめました。

まずはHPA_Naviの全機能をNucleo-H743ZIに移植すべく、各種ペリフェラルのテストを進めています。
大抵のペリフェラルはそれほど苦労なく動かせましたが、FatFSをSDMMC1のDMAモードで動かすまでに少し時間がかかったので、詰まった点を書き残しておこうと思います。

Nucleo-H743ZIにはSDカードスロットがついていないので、手元にあった秋月のマイクロSDカードスロットDIP化キットを取り付けました。
micro SDカードの接続状況
あまり褒められた方法ではありませんが、プルアップ抵抗は省略し、STM32H7の内部プルアップを有効にしています。
144ピンのNucleoボードにはSDIOに高速信号を流した際に問題になるスタブを切り離すための0オーム抵抗(SB116, 117)があるので念のために取り外しましたが、必要はないかもしれません。

コードの雛型はSTM32CubeMX (Ver. 4.25.1) + STM32CubeH7(Ver. 1.2.0)で生成しました。
SDMMC、FatFS周りの設定はほぼデフォルトです。

基本的にはSTM32CubeMXで生成したプロジェクトを開いて(今回はSW4STM32を使いました)、FatFSのAPIを叩けば終わりなのですが、DMAを使いたい場合さらにコードに追記が必要です。

HALドライバを使った場合、SDMMCのDMAでのデータ転送完了後には、HAL_SD_TxCpltCallback/HAL_SD_RxCpltCallbackが呼ばれます。
しかし、STM32CubeMXで生成されるコードにはこれらの関数の実体が含まれておらず、FatFSのDMA Templateを有効にするとデータ転送でタイムアウトが起こってしまい、うまく動きません。
そこで、HAL_SD_TxCpltCallback/HAL_SD_RxCpltCallbackを適当なところに(今回はmain.cに入れました)実装し、データ転送を制御するフラグWriteStatus/ReadStatusをセットする関数BSP_SD_WriteCpltCallback/BSP_SD_ReadCpltCallback (sd_diskio.c)を呼び出す必要があります。

行う作業は極めて単純で、
void HAL_SD_TxCpltCallback(SD_HandleTypeDef *hsd)
{
  BSP_SD_WriteCpltCallback();
}

void HAL_SD_RxCpltCallback(SD_HandleTypeDef *hsd)
{
  BSP_SD_ReadCpltCallback();
}
を挿入するだけです。

STM32H743I-EVAL用のファイルstm32h743i_eval_sd.cには、上記の実装が行われているので、これはNucleo-H743ZIや自前のSTM32H7ボードを使う場合に起こり得る問題かと思います。

作成したサンプルプロジェクトはgithubにアップロードしてあります。
サンプルプロジェクトは準備中です。

これでHPA_Naviの機能を移植するために必要なペリフェラルのテストは終わりました。
最終目標はINS/GNSSによる姿勢角の推定ですが、まずはHPA_Naviが有する機能の動作を確認したいと思います。

2018/01/19

スカイスポーツシンポジウム概要・プレゼンテーション資料

製作した計器類の運用結果は毎年行われるスカイスポーツシンポジウムで発表していますが、日本航空宇宙学会講演原稿の著作権の取扱いに関する内規を読むと個人のwebページでの公開は問題ないようなので、これまでの概要・プレゼンテーション資料を公開しようと思います。

第19回 1-12 人力飛行機用計測システムの開発
概要
プレゼンテーション資料

第20回 1-15 鳥人間コンテスト滑空機の飛行データ計測

第21回 1-16 IMU/GNSSロガーを用いた鳥人間コンテスト滑空機・人力飛行機の飛行データ計測

第22回 1-14 Android 端末とIMU/GNSSロガーによる人力飛行機用計測システム

第23回 1-12 人力飛行機用地上機上総合飛行システムの開発

上記のリンクからダウンロードできる概要の著作権は日本航空宇宙学会にあります。

2017/12/12

[PSoC Advent Calendar 2017 12日目] PSoC 5LPでSWVを使う

PSoC 3/5LPのデバッグ機能の中でSWV(Serial Wire Viewer)の機能は使ったことがなかったので試してみました。

開発しているIMU/GPSロガーHPA_NaviではUSB付きのPSoC 5LPを利用しているのでデバッグ情報をUSBUARTで流すこともできますが、
  • データとデバッグ情報を異なるチャンネルに流せる
ことがメリットとして挙げられます。
また、デバッグ情報送信用にUARTを使った場合と比べると、
  • (ソフトウエアUARTでない場合)UDBを消費しない
  • (10ピンコネクタを使っている場合は)遊んでいるSWV端子が使える
  • USBシリアルアダプタが不要
等のメリットがあります。
未検証ですがクロック設定によってはUARTよりも高速、かつソフトウエアUARTより低CPU負荷かもしれません。

テストは開発中のIMU/GPS基板を用いてGitHubにあるサンプルプロジェクトを動かすことで行いました。
やることは基板に合わせて細かな設定を変える程度ですが、2014/10/18から更新がないので、利用するためにはPSoC Creatorのアップデートに伴う手直しが必要です。
(今回はPSoC Creator 4.1を用いました。)


古いプロジェクトファイルを開いたときの常ですが、まずは各種モジュールのアップデートを行います。
これだけで問題なくプロジェクトの移行が行える場合も多いのですが、PSoC Creatorのprintfがnewlib-nanoを用いるようになったためか、それに伴う問題がいくつか発生しました。
王様の耳はロバの耳!PSoC3/4/5LPでprintfの出力先をUARTにしたいとき」に関連する情報がまとまっています)
ダウンロードしたデモプロジェクトの設定ではheapサイズが不足してうまくpritntf出力が行えないので、十分大きなサイズまでheapを増やします。
今回は2048byteまで増やしてうまくいきました。


また、浮動小数点まわりのライブラリはデフォルトではリンクされないので、利用する場合はビルドオプション"Use newlib-nano Float Formatting"の有効化が必要です。


データの受信は専用のプログラムで行います。
これもGitHubにソースがあり、インストーラ付きバイナリもあるのでどちらかを用います。
受信用のプログラムは設定項目も少なく、ほぼ迷いなく使えましたが、ボーレートの設定がひとつずれている(?)ようで、6,000,000bpsをPSoC 5LP側で設定した場合には5,333,333bpsに設定するとうまくデータが受信できました。


以上で、SWVを用いたprintfデバッグが可能になりました。
デバッガとの併用でファームウエア開発がより効率的に行えそうです。

2017/09/03

クランクひずみによるパイロットパワーの計測3

前回に引き続きひずみゲージによるパイロットパワー計測の話です。
今回は取得したデータからパイロットパワーへの変換について書きます。

パワーはトルクと回転数の積から計算することができます。
取得した回転数、ひずみのデータと計算して求めたパワーのグラフを以下に示します。
今回は、左クランクのみにひずみゲージを取り付けたので、右足も同じパワーを出していると仮定して、パイロット出力は左足のものを2倍した値としました。
回転数、ひずみ、パイロット出力。パイロット出力は市販のパワーメータでも測定を行い、一番下のグラフの黒点で示した。
取得したひずみデータは、キャリブレーションから求めた係数を用いることで、トルクの値に変換することができます。
しかし、トルクの値はクランク1回転中に変化する振動を含んだデータなので何らかの形で平均を取ることが必要です。
平均を取る方法としては、移動平均、指数フィルタ等の各種IIR、FIRフィルタ等が使えますが、試してみたところこれらのフィルタでは時定数が長めのフィルタが必要なようなので、クランク1回転の平均を取ることにしました。

クランク1回転の平均値を計算するためには、クランクが1回転する時間の情報が必要になります。
1回転を検出する磁石式の回転数計の場合には直接この値が得られます。
しかし、今回用いた回転数計はフォトインタラプタ+スリットを用いたもので、1スリットが通過する時間を測定するため、直接的に1回転の検出はできません。
(スリットが通過する数を数えれば測定することはできます)
そこで、クランク回転検知は数値的に行いました。
回転数 [rpm]はその定義から回転回数 [回]の時間微分に相当します。
したがって、回転数を数値的に積分した値の整数部が変化する時刻がクランクが1回転した時刻と推定されます。
この方法で推定したクランク回転検知時刻を図中段の青印で示しました。
このデータでは後処理で回転検知を行いましたが、コックピット表示用にマイコン内でも同等の計算を行っています。

図下段のパワーのデータはクランク1周分の平均化を行ったトルクに回転数の値をかけて求めたパイロットパワーです。
薄青色の点は生データ、実線は弱い指数フィルタをかけたデータです。
1秒の平均時間で測定した市販のパワーメータ(ペダル式)の値と多少のずれはあるもののよく一致していることがわかります。

クランクひずみによるパイロットパワーの計測の話はここまでです。
現在はさらに詳細な解析として、クランク上下のひずみゲージの出力からペダリングのベクトルを求められないか試みています。
うまくいった場合にはさらに追加の記事を書きたいと思います。

2017/08/27

クランクひずみによるパイロットパワーの計測2

前回に引き続きひずみゲージによるパイロットパワー計測の話です。
今回はキャリブレーションについて書きます。

ひずみゲージの取り付け状況。左クランク上面、下面に1枚ずつ取り付けた
ひずみゲージは上の写真の通り、左クランクの上面、下面に1枚ずつ貼り付けました。
パイロットパワーを求めるのに必要なのは曲げひずみの値で、2ゲージ法を使うと圧縮ひずみの除去が可能です。
今回は、圧縮ひずみも含めて測定することでペダリングの向きも測ることを目論んで、上下のひずみゲージを独立したブリッジ回路に接続し、1ゲージ法x2で測定を行いました。

1ゲージ法での測定を行う場合には、各種の温度補償の恩恵を受けられないので、温度を含めたキャリブレーションが必要になります。
そこで、クランクに与えるトルクと環境温度の両方を変えながらキャリブレーションを行いました。

キャリブレーションの様子。クランクから奥に延びる青い配線は熱電対につながる
キャリブレーションの結果を下の図に示します。
使用した錘は[0, 2, 14, 20]kg、温度は[18, 24, 28]℃程度です。

キャリブレーション結果。上のグラフは温度を、下のグラフは荷重を固定したものを同じ色で示した
計36点のデータを取得し、
(ADC出力)=(オフセット)+(荷重比例部分)+(温度比例部分)
として重回帰分析を行い、校正係数を求めました。

荷重比例部分の校正係数は市販のパワーメータPolar Keo Powerとの比較も行い、誤差が1%以下であることを確認しました。

「温度」として採用するべきものは、キャリブレーション時に測定したひずみゲージ部分の温度なのですが、外付けの温度センサをつなぐ設計にはなっていないため代わりにADC内蔵温度計のものを採用することとします。
また、この校正試験時にはコネクタを介してひずみゲージを接続していたり、ひずみゲージとブリッジをつなぐケーブルが長かったりしたので、鳥コン本番で使った構成より温度係数が大きめに出ているようです。
このあたりは次に新しく基板を作る際には反映するつもりです。

次回は取得したひずみデータからパワーを計算する部分について書く予定です。

2017/08/18

クランクひずみによるパイロットパワーの計測1

以前の記事に書いた通り、ANT+モジュールにより市販パワーメータGarmin Vector 2Jのデータを受信しパイロットパワーを測定するつもりでしたが、どうもGarmin Vector 2Jではリカンベント式人力飛行機のパワーをうまく計測できないようなので、ひずみゲージを使って自前のパワーメータを作ることにしました。

パワーメータの作製は学生時代(~10年前)に行ったことがありますが、その際にはひずみゲージのゼロ点を半固定抵抗で補正しつつ10~12bitのマイコン内蔵ADCで読み込み、EEPROM/Flash ROMにデータを書き込む構成としていました。
最近は高分解能ADCや無線モジュールが利用しやすくなったこともあるので、クランクひずみの測定回路にはゼロ点調整等は設けず、ひずみゲージを含むブリッジ回路の出力電圧を適当に増幅した後にAD変換し無線で送信する構成としました。
ひずみゲージ用のブリッジ回路は(左/右)足 x (上/下)面用に4系統用意し、1~4ゲージ法のすべてに対応可能なようにしました。

製作したひずみゲージデータの送信機。無線モジュールのアンテナとアンチエイリアスフィルタのキャパシタは未実装。サイズは20 x 67.5mm、重量は5g
上の写真は製作したひずみゲージデータの送信機です。
設計時に何を考えて各種部品を選定したかなどを以下に記します。

ひずみゲージの選定

ひずみゲージは特定材料の線膨張係数の補償を行うように設計されているので、クランク材質に合わせたものを選びます。
今回の場合はアルミ用(東京測器研究所のものだと緑色のもの)です。
その他に決めなくてはならない仕様としてはゲージ長がありますが、今回はクランク歪みの測定で、局所的な情報が必要ないので簡単に手に入るものの中で最長の5mmを選びました。
また、基板との配線の手間を減らすためにリード付きのFLA-5-23-1LJCを選びました。
(余談ですが、東京測器研究所のひずみゲージは個人でも購入可能なので、e-ゲージショップに並んでいるものから選ぶ必要はありません。)

抵抗の選定

ひずみゲージの抵抗変化は抵抗ブリッジにより電圧変化に変換される場合が多いですが、ブリッジを構成する抵抗の良し悪しによっては、電圧変化に大きな温度依存が乗る場合があります。(ある意味ではセンサの一部と言えます)
温度係数の小さな抵抗は高価なので、その他の回路とのバランスを考えて25ppm/degCの薄膜抵抗 進工業製RR1220P-121-Dを選定しました。
また、許容電力にある程度の余裕を持たせるためにサイズは2012としています。

電源回路の検討

クランクひずみ送信機の電源には重量の観点からLiPoバッテリを利用します。
低ノイズの電源回路をコンパクトに作るにはリニアレギュレータを利用するのが簡単です。
また、基板専有面積を抑えるためにデジタル・アナログの電源は共有することにしました。
LiPoバッテリの容量をフルに活用するために、システムの電源電圧は2.85Vとしました。

AD変換器の選定

クランクひずみによるブリッジ回路の電圧変化は小さいので、高分解能(24~16bit程度が目安)のものを使用します。
また、クランクが1周する間に30点程度のデータ(平均誤差1%に対応)を取得しないと、振動するデータを平均する際に誤差が乗るので、サンプリングレートが小さすぎると問題になります。
さらに、基板面積の節約のために、PGA内蔵のものが望ましいです。
これらに電源電圧の条件を加えて検討し、128倍PGA内蔵、8入力、24bit、2ksps Delta Sigma型ADCのTexas Instruments ADC1248を利用することにしました。
同社のADCにはビット数が16bitで他のスペックが同じものもありますが、値段があまり変わらないようなので24bitのものを使いました。
使用する条件(128倍増幅、320sps)だと実効ビット数は16.2bitなので、16bitのものを使っても問題ないとは思います。
ADCには基準電圧源が内蔵されていますが、今回はratiometricな測定を行いたいのでADCの基準電圧には外からフィルタをかけたシステム電圧を供給します。
ブリッジ回路とADCの間には簡単なアンチエイリアスフィルタを入れました。
アンチエイリアス回路に利用するキャパシタは、手持ちの部品の関係から低誘電率系(CH)のセラミックコンデンサとPMLCAPを利用しました。

無線モジュールの選定

基板サイズ・重量を考えるとADCと無線モジュールの間にはマイコンを挟みたくないので、プログラマブルなモジュールを使います。
手軽に入手でき、技適の問題もクリアしているものの候補としてはXBeeTWE-Liteがありますが、表面実装可能なことからTWE-Liteを使いました。
アンテナが基板中央に来る配置になっていて、アンテナの性能は十分に発揮できない配置ですが、受信側基板との距離が1mもないことからこの部分は妥協しています。

その他

回転数の測定用に裏面にジャイロのパターンを設けました。(実際には利用せず)
また、動作確認用に4色のLEDをTWE-Liteにつなげています。

この手のアナログ・デジタル混載回路では定石ですが、アナログ・デジタルグラウンドの分離やリターンパス等を意識して基板のパターンを引きました。

基板の組み立て後は、ブリッジ回路部を中心に念入りにフラックスを洗浄しました。
大抵の場合は大丈夫だとは思いますが、フラックスの絶縁抵抗が低く、かつその抵抗に温度・湿度等の依存性がある場合には測定結果が不安定になる要因となるので、念のために行っています。

ひずみゲージの接続用にコネクタを用意しましたが、接触抵抗の変化や異種金属接合部での起電力とその温度依存が問題になり得るので、コネクタは用いず、ひずみゲージから延びるケーブルを直接基板にはんだ付けしました。

TWE-Liteのファームウエアはソフトウエア開発環境 TWELITE NET SDKを用いて開発しました。
個人的にはわかりにくいと感じた資料だったのですが、SDK全部入りファイルをダウンロードすると開発に必要な情報は全て手に入りました。

測定した結果はデータロガーHPA_Navi IIIで受信しますが、受信側に用いるTWE-Liteにも専用のファームウエアを用意しました。

以上でひずみ測定部分が完成しました。
次回はひずみとトルクの換算係数を得るために行った荷重試験について書きたいと思います。


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 6軸センサの送信DMA設定
//******************************************************************************
#define DMA_TX_DOF_BYTES_PER_BURST       (1)
#define DMA_TX_DOF_REQUEST_PER_BURST     (1)
#define DMA_TX_DOF_SRC_BASE              (DOF_txBuffer)
#define DMA_TX_DOF_DST_BASE              (CYDEV_PERIPH_BASE)

uint8_t DOF_txBuffer[16] = {0x80 | 0x3B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};

static void DMA_TX_DOF_Configuration()
{
    uint8_t DOF_TxChannel;
    uint8_t DOF_TxTD[2];

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

    DOF_txBuffer[15] = SPIM_SNS_TX_STATUS_MASK_REG;    //割り込みが止まった設定のレジスタをコピー
        
    DOF_TxChannel = DMA_TX_DOF_DmaInitialize(DMA_TX_DOF_BYTES_PER_BURST, DMA_TX_DOF_REQUEST_PER_BURST, 
                                        HI16(DMA_TX_DOF_SRC_BASE), HI16(DMA_TX_DOF_DST_BASE));

    DOF_TxTD[0] = CyDmaTdAllocate();
    DOF_TxTD[1] = CyDmaTdAllocate();

    CyDmaTdSetConfiguration(DOF_TxTD[0], 15, DOF_TxTD[1], TD_INC_SRC_ADR);
    CyDmaTdSetConfiguration(DOF_TxTD[1], 1, DOF_TxTD[0], 0);
    
    CyDmaTdSetAddress(DOF_TxTD[0], LO16((uint32_t)DOF_txBuffer), LO16((uint32_t)SPIM_SNS_TXDATA_PTR));
    CyDmaTdSetAddress(DOF_TxTD[1], LO16((uint32_t)(DOF_txBuffer + 15)), LO16((uint32_t)&SPIM_SNS_TX_STATUS_MASK_REG));

    CyDmaChSetInitialTd(DOF_TxChannel, DOF_TxTD[0]);
    
    CyDmaChEnable(DOF_TxChannel, 1);
}

//******************************************************************************
//! @brief 6軸センサの受信DMA設定
//******************************************************************************
#define DMA_RX_DOF_BYTES_PER_BURST       (1)
#define DMA_RX_DOF_REQUEST_PER_BURST     (1)
#define DMA_RX_DOF_SRC_BASE              (CYDEV_PERIPH_BASE)
#define DMA_RX_DOF_DST_BASE              (pageA)

static void DMA_RX_DOF_Configuration()
{
    uint8_t DOF_RxChannel;
    uint8_t DOF_RxTD[15];
    uint8_t i;

    DOF_RxChannel = DMA_RX_DOF_DmaInitialize(DMA_RX_DOF_BYTES_PER_BURST, DMA_RX_DOF_REQUEST_PER_BURST,
                                     HI16(DMA_RX_DOF_SRC_BASE), HI16(DMA_RX_DOF_DST_BASE));

    for (i = 0; i < 15; i++) {
        DOF_RxTD[i] = CyDmaTdAllocate();
    }

    for (i = 0; i < 14; i ++) {
        CyDmaTdSetConfiguration(DOF_RxTD[i], 1, DOF_RxTD[i + 1], TD_INC_DST_ADR);
    }
    CyDmaTdSetConfiguration(DOF_RxTD[14], 1, DOF_RxTD[0], TD_INC_DST_ADR);

    CyDmaTdSetAddress(DOF_RxTD[0], LO16((uint32_t)SPIM_SNS_RXDATA_PTR), LO16((uint32_t)DMY_rxBuffer));
    CyDmaTdSetAddress(DOF_RxTD[1], LO16((uint32_t)SPIM_SNS_RXDATA_PTR), LO16((uint32_t)pageA+7));    //ACC
    CyDmaTdSetAddress(DOF_RxTD[2], LO16((uint32_t)SPIM_SNS_RXDATA_PTR), LO16((uint32_t)pageA+8));
    CyDmaTdSetAddress(DOF_RxTD[3], LO16((uint32_t)SPIM_SNS_RXDATA_PTR), LO16((uint32_t)pageA+10));    //
    CyDmaTdSetAddress(DOF_RxTD[4], LO16((uint32_t)SPIM_SNS_RXDATA_PTR), LO16((uint32_t)pageA+11));
    CyDmaTdSetAddress(DOF_RxTD[5], LO16((uint32_t)SPIM_SNS_RXDATA_PTR), LO16((uint32_t)pageA+13));    //
    CyDmaTdSetAddress(DOF_RxTD[6], LO16((uint32_t)SPIM_SNS_RXDATA_PTR), LO16((uint32_t)pageA+14));
    CyDmaTdSetAddress(DOF_RxTD[7], LO16((uint32_t)SPIM_SNS_RXDATA_PTR), LO16((uint32_t)pageA+31));    //TEMP
    CyDmaTdSetAddress(DOF_RxTD[8], LO16((uint32_t)SPIM_SNS_RXDATA_PTR), LO16((uint32_t)pageA+30));
    CyDmaTdSetAddress(DOF_RxTD[9], LO16((uint32_t)SPIM_SNS_RXDATA_PTR), LO16((uint32_t)pageA+16));    //GYRO
    CyDmaTdSetAddress(DOF_RxTD[10], LO16((uint32_t)SPIM_SNS_RXDATA_PTR), LO16((uint32_t)pageA+17));
    CyDmaTdSetAddress(DOF_RxTD[11], LO16((uint32_t)SPIM_SNS_RXDATA_PTR), LO16((uint32_t)pageA+19));    //
    CyDmaTdSetAddress(DOF_RxTD[12], LO16((uint32_t)SPIM_SNS_RXDATA_PTR), LO16((uint32_t)pageA+20));
    CyDmaTdSetAddress(DOF_RxTD[13], LO16((uint32_t)SPIM_SNS_RXDATA_PTR), LO16((uint32_t)pageA+22));    //
    CyDmaTdSetAddress(DOF_RxTD[14], LO16((uint32_t)SPIM_SNS_RXDATA_PTR), LO16((uint32_t)pageA+23));

    CyDmaChSetInitialTd(DOF_RxChannel, DOF_RxTD[0]);

    CyDmaChEnable(DOF_RxChannel, 1);
}
基本的には、PSoC CreatorのDMAウィザードで生成した設定を雛型として使いますが、必要な分の送信を行った後にSPI送信をストップするために、送信データの最後でSPIMモジュールに割り込み禁止の設定を送っています。
また、受信側では最初のバイトにゴミが入るのでそれをダミーバッファに送り込む、Sylphideフォーマットに合わせて転送順を変えるなどの処理を行っています。

次に地磁気センサHMC-5983の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 圧力センサの送信DMA設定
//******************************************************************************
#define DMA_TX_PRS_BYTES_PER_BURST       (1)
#define DMA_TX_PRS_REQUEST_PER_BURST     (1)
#define DMA_TX_PRS_SRC_BASE              (PRS_txBuffer)
#define DMA_TX_PRS_DST_BASE              (CYDEV_PERIPH_BASE)

uint8_t PRS_txBuffer[14] = {MS561101BA_ADC, 0xFF, 0xFF, 0xFF, 0x00,
                            MS561101BA_D2 | MS561101BA_OSR_4096, 0x00,
                            MS561101BA_ADC, 0xFF, 0xFF, 0xFF, 0x00,
                            MS561101BA_D1 | MS561101BA_OSR_4096, 0x00};    //D2が最初。変換コマンドなので。

static void DMA_TX_PRS_Configuration()
{
    uint8_t PRS_TxChannel;
    uint8_t PRS_TxTD[8];
    uint8_t i;
    
    SPIM_SNS_TX_STATUS_MASK_REG &= (~SPIM_SNS_INT_ON_TX_EMPTY);    //SPIモジュールのTX_FIFO_EMPTY割り込みを止める

    PRS_txBuffer[4] = SPIM_SNS_TX_STATUS_MASK_REG;    //割り込みが止まった設定のレジスタをコピー
    PRS_txBuffer[6] = SPIM_SNS_TX_STATUS_MASK_REG;    //割り込みが止まった設定のレジスタをコピー
    PRS_txBuffer[11] = SPIM_SNS_TX_STATUS_MASK_REG;    //割り込みが止まった設定のレジスタをコピー
    PRS_txBuffer[13] = SPIM_SNS_TX_STATUS_MASK_REG;    //割り込みが止まった設定のレジスタをコピー
   
    PRS_TxChannel = DMA_TX_PRS_DmaInitialize(DMA_TX_PRS_BYTES_PER_BURST, DMA_TX_PRS_REQUEST_PER_BURST, 
                                        HI16(DMA_TX_PRS_SRC_BASE), HI16(DMA_TX_PRS_DST_BASE));

    for (i = 0; i < 8; i ++) {
        PRS_TxTD[i] = CyDmaTdAllocate();
    }

    CyDmaTdSetConfiguration(PRS_TxTD[0], 4, PRS_TxTD[1], TD_INC_SRC_ADR);
    CyDmaTdSetConfiguration(PRS_TxTD[1], 1, PRS_TxTD[2], DMA_TX_PRS__TD_TERMOUT_EN);
    CyDmaTdSetConfiguration(PRS_TxTD[2], 1, PRS_TxTD[3], TD_INC_SRC_ADR);
    CyDmaTdSetConfiguration(PRS_TxTD[3], 1, PRS_TxTD[4], DMA_TX_PRS__TD_TERMOUT_EN);
    CyDmaTdSetConfiguration(PRS_TxTD[4], 4, PRS_TxTD[5], TD_INC_SRC_ADR);
    CyDmaTdSetConfiguration(PRS_TxTD[5], 1, PRS_TxTD[6], DMA_TX_PRS__TD_TERMOUT_EN);
    CyDmaTdSetConfiguration(PRS_TxTD[6], 1, PRS_TxTD[7], TD_INC_SRC_ADR);
    CyDmaTdSetConfiguration(PRS_TxTD[7], 1, PRS_TxTD[0], DMA_TX_PRS__TD_TERMOUT_EN);
    
    CyDmaTdSetAddress(PRS_TxTD[0], LO16((uint32_t)PRS_txBuffer), LO16((uint32_t)SPIM_SNS_TXDATA_PTR));
    CyDmaTdSetAddress(PRS_TxTD[1], LO16((uint32_t)(PRS_txBuffer+4)), LO16((uint32_t)&SPIM_SNS_TX_STATUS_MASK_REG));
    CyDmaTdSetAddress(PRS_TxTD[2], LO16((uint32_t)(PRS_txBuffer+5)), LO16((uint32_t)SPIM_SNS_TXDATA_PTR));
    CyDmaTdSetAddress(PRS_TxTD[3], LO16((uint32_t)(PRS_txBuffer+6)), LO16((uint32_t)&SPIM_SNS_TX_STATUS_MASK_REG));
    CyDmaTdSetAddress(PRS_TxTD[4], LO16((uint32_t)(PRS_txBuffer+7)), LO16((uint32_t)SPIM_SNS_TXDATA_PTR));
    CyDmaTdSetAddress(PRS_TxTD[5], LO16((uint32_t)(PRS_txBuffer+11)), LO16((uint32_t)&SPIM_SNS_TX_STATUS_MASK_REG));
    CyDmaTdSetAddress(PRS_TxTD[6], LO16((uint32_t)(PRS_txBuffer+12)), LO16((uint32_t)SPIM_SNS_TXDATA_PTR));
    CyDmaTdSetAddress(PRS_TxTD[7], LO16((uint32_t)(PRS_txBuffer+13)), LO16((uint32_t)&SPIM_SNS_TX_STATUS_MASK_REG));

    CyDmaChSetInitialTd(PRS_TxChannel, PRS_TxTD[0]);
    
    CyDmaChEnable(PRS_TxChannel, 1);
}

//******************************************************************************
//! @brief 圧力センサの受信DMA設定
//******************************************************************************
#define DMA_RX_PRS_BYTES_PER_BURST       (1)
#define DMA_RX_PRS_REQUEST_PER_BURST     (1)
#define DMA_RX_PRS_SRC_BASE              (CYDEV_PERIPH_BASE)
#define DMA_RX_PRS_DST_BASE              (PRS_rxBuffer)

uint32_t PRS_rxBuffer[2];

static void DMA_RX_PRS_Configuration()
{
    uint8_t PRS_RxChannel;
    uint8_t PRS_RxTD[10];
    uint8_t i;

    PRS_RxChannel = DMA_RX_PRS_DmaInitialize(DMA_RX_PRS_BYTES_PER_BURST, DMA_RX_PRS_REQUEST_PER_BURST,
                                     HI16(DMA_RX_PRS_SRC_BASE), HI16(DMA_RX_PRS_DST_BASE));
                                     
    for (i = 0; i < 10; i ++) {
        PRS_RxTD[i] = CyDmaTdAllocate();
    }

    CyDmaTdSetConfiguration(PRS_RxTD[0], 1, PRS_RxTD[1], TD_INC_DST_ADR);
    CyDmaTdSetConfiguration(PRS_RxTD[1], 1, PRS_RxTD[2], 0);
    CyDmaTdSetConfiguration(PRS_RxTD[2], 1, PRS_RxTD[3], 0);
    CyDmaTdSetConfiguration(PRS_RxTD[3], 1, PRS_RxTD[4], 0);
    CyDmaTdSetConfiguration(PRS_RxTD[4], 1, PRS_RxTD[5], TD_INC_DST_ADR);
    CyDmaTdSetConfiguration(PRS_RxTD[5], 1, PRS_RxTD[6], TD_INC_DST_ADR);
    CyDmaTdSetConfiguration(PRS_RxTD[6], 1, PRS_RxTD[7], 0);
    CyDmaTdSetConfiguration(PRS_RxTD[7], 1, PRS_RxTD[8], 0);
    CyDmaTdSetConfiguration(PRS_RxTD[8], 1, PRS_RxTD[9], 0);
    CyDmaTdSetConfiguration(PRS_RxTD[9], 1, PRS_RxTD[0], TD_INC_DST_ADR);

    CyDmaTdSetAddress(PRS_RxTD[0], LO16((uint32_t)SPIM_SNS_RXDATA_PTR), LO16((uint32_t)DMY_rxBuffer));
    CyDmaTdSetAddress(PRS_RxTD[1], LO16((uint32_t)SPIM_SNS_RXDATA_PTR), LO16((uint32_t)((uint8*)&PRS_rxBuffer[0] + 2)));
    CyDmaTdSetAddress(PRS_RxTD[2], LO16((uint32_t)SPIM_SNS_RXDATA_PTR), LO16((uint32_t)((uint8*)&PRS_rxBuffer[0] + 1)));
    CyDmaTdSetAddress(PRS_RxTD[3], LO16((uint32_t)SPIM_SNS_RXDATA_PTR), LO16((uint32_t)((uint8*)&PRS_rxBuffer[0])));
    CyDmaTdSetAddress(PRS_RxTD[4], LO16((uint32_t)SPIM_SNS_RXDATA_PTR), LO16((uint32_t)DMY_rxBuffer));
    CyDmaTdSetAddress(PRS_RxTD[5], LO16((uint32_t)SPIM_SNS_RXDATA_PTR), LO16((uint32_t)DMY_rxBuffer));
    CyDmaTdSetAddress(PRS_RxTD[6], LO16((uint32_t)SPIM_SNS_RXDATA_PTR), LO16((uint32_t)((uint8*)&PRS_rxBuffer[1] + 2)));
    CyDmaTdSetAddress(PRS_RxTD[7], LO16((uint32_t)SPIM_SNS_RXDATA_PTR), LO16((uint32_t)((uint8*)&PRS_rxBuffer[1] + 1)));
    CyDmaTdSetAddress(PRS_RxTD[8], LO16((uint32_t)SPIM_SNS_RXDATA_PTR), LO16((uint32_t)((uint8*)&PRS_rxBuffer[1])));
    CyDmaTdSetAddress(PRS_RxTD[9], LO16((uint32_t)SPIM_SNS_RXDATA_PTR), LO16((uint32_t)DMY_rxBuffer));

    CyDmaChSetInitialTd(PRS_RxChannel, PRS_RxTD[0]);

    CyDmaChEnable(PRS_RxChannel, 1);
}
こちらも基本的にはこれまでに示したセンサの設定と同じです。
送信側では、温度センサと気圧センサを交互に読み込むため、2種類の変換開始命令に対応して、割り込みストップが4回登場します。
受信側も、温度・気圧センサの2つに対応した2種類の転送先を設定しています。

ここまででDMAの設定が終わりました。
残りはセンサの初期設定部分と10msごとに行うデータ取得部分です。

センサの初期設定には時間がかかっても構わないので、SPIMモジュールのPutArray関数を用いて行います。
//******************************************************************************
//! @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等の処理を含む出力ストリーム関数に送ります。
//******************************************************************************
//! @breif SPI DMAの開始
//******************************************************************************
static void DMAStart( void )
{
    SPIM_SNS_TX_STATUS_MASK_REG |= (SPIM_SNS_INT_ON_TX_EMPTY);    //割り込みを有効にしてDMA転送を開始
    CyDelayUs(1); //CSのアサート前にピンの状態を読んでしまうことの対策
    while(!Status_Reg_CS_Read());
}

//******************************************************************************
//! @brief SPIタスク
//! @memo SPIM_PutArrayではフルスピードが出ない。DMAを使う
//******************************************************************************
void TaskSPI( void )
{
    extern uint32_t itow;
    uint8_t i;
    uint16_t temp;
    uint32_t temp32;
    static uint8_t LoopCount = 0;
    static uint32_t tempTemp = 0, tempPres = 0;
    
    LoopCount ++;

    //pageAログ整形
    pageA[1] = (uint8_t)(xTaskGetTickCount() / 10);    //内部時刻の書き込み
    *(uint32_t*)(pageA + 2) = itow;
    
    //MPU-6000処理
    Control_Reg_SNS_Write(SPI_DOF);    //CS選択
    DMAStart();
    for (i = 0; i < 6; i++) {
        pageA[7 + 3 * i] ^= 0x80;    //符号付きから符号なしへの変換。
    }
    
    //HMC5983処理
    Clock_SPI_SetDividerValue(4); // 64 / 4 = 16 MHz SPI 8 Mbps
    Control_Reg_SNS_Write(SPI_MAG);     //CS選択                
    DMAStart();  //データ読み込み
    DMAStart();  //single modeに入れる
    if ( !(LoopCount % 4) ) {    //4つたまったら処理
        *(pageM + 3) = (uint8_t)(xTaskGetTickCount() / 10);    //内部時刻の書き込み
        *(uint32_t*)(pageM + 4) = itow;
        for (i = 0; i < 4; i ++) {    //Z軸に負の符号をつける
            temp = *((uint16_t *)(pageM + 12 + 6 * i));
            temp = ~CYSWAP_ENDIAN16(temp) + 1;    //マクロなのでtempに退避
            *((uint16_t *)(pageM + 12 + 6 * i)) = CYSWAP_ENDIAN16(temp);    //マクロなのでtempに退避 
        }
        outStream(pageM);
    }

    //MS5611処理
    Clock_SPI_SetDividerValue(2); // 64 / 2 = 32 MHz SPI 16 Mbps
    Control_Reg_SNS_Write(SPI_PRS);     //CS選択
    DMAStart();  //データ読み込み
    DMAStart();  //AD変換開始

    if ( LoopCount % 2 ){
        temp32 = MS561101BA_Pressure(PRS_rxBuffer[0]);
        if ( (tempPres > temp32 && tempPres - temp32 < 10000) || 
        (tempPres < temp32 && temp32 - tempPres < 10000)) {
            pageA[24] = (uint8_t)(temp32 >> 16);
            pageA[25] = (uint8_t)(temp32 >> 8);
            pageA[26] = (uint8_t)temp32;
        }
        tempPres = temp32; 
    }
    else{
        temp32 = MS561101BA_Tempareture(PRS_rxBuffer[1]);
        temp32 += 4000;    //マイナスが出ないようにオフセット。+40 degree C
        if ( (tempTemp > temp32 && tempTemp - temp32 < 1000) || 
        (tempTemp < temp32 && temp32 - tempTemp < 1000)) {
            pageA[27] = (uint8_t)(temp32 >> 16);
            pageA[28] = (uint8_t)(temp32 >> 8);
            pageA[29] = (uint8_t)temp32;
        }
        tempTemp = temp32;
    }    
    
    outStream(pageA);
}
以上のプログラムで、SPI接続センサとの通信にかかる時間を最小限にすることができ、ロジアナでも歯抜けのない通信が確認できました。
ここで生み出した余裕はその他の計算やワイヤレスモジュールとの通信等に使えるようになり、同じロガーでも多くの機能を持たせることができるようになります。
この他にもHPA_Naviでは8チャンネル・16ビットのAD変換データをDMA転送するなどして、できる限りデータ転送にはCPUを使わないような工夫を行っています。