2014年1月29日水曜日

SPIをDMAで

DMAでSPIの出力です

送信するデータはconst uint8_t *Buffで与えられます
1回に512バイトをDMAで出力し、出力が終わるまでwhileでループします

エディタからソースコードをコピペしてきたので行番号がついてるのはご容赦ください
    173     DMA_InitTypeDef DMA;
    174
    175     DMA.DMA_PeripheralBaseAddr = (uint32_t)(&SPI1->DR);
    176     DMA.DMA_MemoryBaseAddr = (uint32_t)Buff;
    177     DMA.DMA_DIR = DMA_DIR_PeripheralDST;
    178     DMA.DMA_BufferSize = 512;
    179     DMA.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    180     DMA.DMA_MemoryInc = DMA_MemoryInc_Enable;
    181     DMA.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
    182     DMA.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
    183     DMA.DMA_Mode = DMA_Mode_Normal;
    184     DMA.DMA_Priority = DMA_Priority_Medium;
    185     DMA.DMA_M2M = DMA_M2M_Disable;
    186
    187     DMA_DeInit(DMA1_Channel3);
    188     DMA_Init(DMA1_Channel3, &DMA);
    189     DMA_Cmd(DMA1_Channel3, ENABLE);
    190     SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Tx, ENABLE);
    191
    192     while (DMA_GetCurrDataCounter(DMA1_Channel3));
    193     while (!SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE));

メモリ2メモリとほぼ同じ感じでできます
違いとしては
1. ペリフェラルアドレスを適切に設定する
2. ペリフェラルのインクリメントはdis
3. M2Mもdis
4. DMA_Cmdを実行した後にペリフェラルのDMACmdを実行
5. ウエイトはDMA_GetCurrDataCounterだけではダメ
こんな感じです

まずペリフェラルアドレスについて
STM32F1のSPIはSPI_I2S_SendData関数を使って出力しますが
DMAの場合はレジスタアドレスを指定する必要があります
レジスタはSPIx->DR(DataRegister)です

データレジスタは配列ではないのでインクリメントも無効にしておきます
メモリ間転送ではないのでM2MもDisableにします

あとはDMA_Initの後にDMA_Cmdですが、M2Mと違い自動的に始まるわけではありません
SPI_I2S_DMACmdでDMAのトリガーをかけてやる必要があります
このトリガーにより適切なタイミングでSPIのレジスタにデータが転送されるようになります

また、メモリ間転送の場合、転送終了はGetCurrDataCounterで検出できますが
SPIの場合はちょっと違っていて、転送が終了してもSPIの送信が終了しているとは限りません
そのため、ソフトウェアでCS端子を操作している場合は、SPIの転送が終わらないうちにCSがネゲートされてしまいます
それを防ぐためにSPI_I2S_GetFlagStatusで送信完了フラグを確認する必要があります


SPIでマスタから一方的にデータを送る場合は比較的簡単に実現できますが
Webの情報を見た感じ、SPIの受信は一工夫必要な感じがあるようです

DMAを使ったデータ転送はSDSPIライブラリではなく、SPIライブラリに書いたほうがいろいろ使い回しができて便利だったかもしれません
例えば送信と受信の双方をDMAで行う関数を作っておけばセンサからのデータを読む場合に簡単になります

低レベルのAPIを書き換えると高レベル側全てに影響するため、結構面倒そうですが 試してみる価値はあるかも


STM32F1のDMAは実質的にDMA1の7chしかなく、その7chも様々なペリフェラルが共存しているので、データ転送をDMAだけでやることはほぼ不可能でしょう
例えばSPI1の受信とUSART3の送信 SPI1の送信とUSART3の受信は同じチャネルを使っています
これからの基板設計はGPIOの配置だけではなく、DMAのことも考慮する必要がありそうです
(例えばSDカードはSPI1を使うためUSART3はDMAが使えない のような)
GPIOのリマップのようにDMAのチャネル入れ替えも欲しい機能ですね

もっとも、USARTは送信・受信割り込みを使った送受信バッファを作ってしまったので、DMAを使う必要はあまり感じておらず、その点ではSPI以外にDMAが必要なIOも無いので気にしなくてもいいという気もしますが

0 件のコメント:

コメントを投稿