2015年12月21日月曜日

STM32F1のSPI

STM32F1のStdPeriphDriverでは以下のコードでSPIの送受信と送信が可能です。

void SPI_WR(uint8_t *buff, uint16_t len) {
    do {
        while (!SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE));
        SPI_I2S_SendData(SPI1, *buff);

        while (!SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE));
        *buff = SPI_I2S_ReceiveData(SPI1);

        buff++;
    } while (--len);

    while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_BSY));
}

void SPI_W(const uint8_t*buff, uint16_t len) {
    do {
        while (!SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE));
        SPI_I2S_SendData(SPI1, *buff++);
    } while (--len);

    while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_BSY));
}

SPI_WR関数ではバッファのデータを送信しながら、受信したデータをバッファに保存します(与えた配列を破壊します)。
データを受信する必要が無い(送信だけすればいい)という場合はSPI_W関数を使用すれば受信処理を省くことができます。


ということでこのコードを使って36MHzで初期化したSPIにデータを送ってみましょう。



左がSPI_WRで右がSPI_Wです。送信専用では多少はマシとはいえ、バスの速度を十分に使用していません。これはソフトウェアでポーリングしているためで、バス帯域をフルに使用するにはハードウェアで送信する必要があります(DMAで転送する方法はこちらのエントリで)。

センサのデータを読み取りたいなど、数MHzで10バイト未満程度ならあまり問題になることはないかもしれませんが(*)、SDカードにアクセスする場合はこのバス使用率は問題です。DMAでハードウェア化した場合、受信では10倍程、送信でも4倍程の高速化が望めます。

*) 数バイト程度ならDMA初期化のオーバーヘッドがあるのでソフトウェアのほうが早い場合もあるので、各環境で用途に合わせて確認する必要があります。

/* 追記 */

SPIの転送にDMAを使うと右端のようになる。


左端のソフトウェアWR、中央のソフトウェアWに比べ、右端のハードウェアWではクロックの間に隙間がなく、バスの使用率はかなり高い。ただしCSをアサーとしてから最初のデータが送信されるまでにしばらく時間がかかっている。これはDMAの初期化を行っているため。でも2分周で10バイトくらい送るならDMAのほうが早そう。

void SPI_DMA_W(const uint8_t*buff, uint16_t len) {
    DMA_InitTypeDef DMA_InitStructure;

    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)(&SPI1->DR);
    DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)(buff);
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
    DMA_InitStructure.DMA_BufferSize = len;
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
    DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
    DMA_InitStructure.DMA_Priority = DMA_Priority_High;
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;

    DMA_DeInit(DMA1_Channel3);
    DMA_Init(DMA1_Channel3, &DMA_InitStructure);
    DMA_Cmd(DMA1_Channel3, ENABLE);
    SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Tx, ENABLE);

    while (DMA_GetCurrDataCounter(DMA1_Channel3));
    while (!SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE));
    while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_BSY));
}

他に予めRCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);でDMAのクロックを有効にしておく必要がある。

0 件のコメント:

コメントを投稿