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

2016/12/20

PSoC 5LP PSoC3 製作

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等の処理を含む出力ストリーム関数に送ります。