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を使わないような工夫を行っています。

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へ
部品の実装とファームウエアの開発が済んだら、まずは可変ピッチプロペラの制御基板として利用してみる予定です。

2015/12/06

nRF24AP2による市販ANT+デバイスデータの読み込み

Team 'F'の世界記録挑戦用の機体Nextz Avantがロールアウトし、試験飛行が始まりました。

試験飛行再開は春先からになりますが、それまでにパイロットの要望でパワーメータを増設予定です。
増設するパワーメータは市販のもので、通信にはANT+規格を利用します。
そこで、データロガーHPA_Navi IIにANT+通信機能を増設すべく、市販のANT+デバイスとのシリアル通信の実験を行いました。

ANT+デバイスとの通信には自前の回路を利用することもできますが技適が必要なのでnRF24AP2を搭載した市販のモジュールBeatcraft BC-ANT-SERIAL-2を利用しました
また、ANT+デバイスとしてはスピード/ケイデンスセンサのCATEYE ICS-11を使いました。
実験風景。左側がUSBシリアル変換器、右側がANT+シリアルモジュール、手前がスピード/ケイデンスセンサ
実験はANT+テスト用ソフトウエアANTware IIを用いて行いました。
PCとはFT232Hモジュールを介して接続しましたが、正しく認識されるようです。

ANT+をマイコンから使うための情報はあまりweb上にはないようなのですが、Beatcraft ANT+デバイスの基礎が大変参考になりました。
ここで概要をつかんだ後公式ページに登録後利用できるドキュメントANT Message Protocol and Usageを読むのがよいように思います

市販のデバイスを使う際には、利用周波数の設定などを行う際にデバイスプロファイル情報が必須になります。
これも公式のドキュメントANT+ DEVICE PROFILESが整備されていますので、それをよく読んで設定します。

また、市販のデバイスを利用する際には、ネットワークの種類をManaged Networkに変更しなくてはなりません。
これには8ビットのネットワークキーが必要ですが、それもANT+公式サイトで公開されていますので、それを利用します。
ANTware IIでは下図の左側にある通り通信モジュールのオプションとして設定できます

これらの設定を行い、スピード/ケイデンスセンサから出力されるデータをシリアル通信で受信した様子が下図になります。

スピード/ケイデンスセンサとの通信の様子
ここまででANT+モジュールに流し込むべきコマンドが明らかになったので、残りはHPA_Navi IIに実装する作業だけです。
ANTware IIでは、通信のログをLog Filesメニューから参照できるので、その通りコマンドを実装します。
チェックサム等を含むANT+パケットの生成はBearcraftで公開しているライブラリを利用して行います。
ライブラリはLPC1114/PIC24F用に書かれていますが、マイコンに依存する部分はシリアル通信部のみなので、移植は容易だと思います。

パワーメータのプロファイルにはキャリブレーションコマンド等が含まれていて、スピード/ケイデンスセンサほど単純ではありませんが、春までには実用可能な形で実装を行うつもりです。