SPI + DMA

DMAとは何かについて話しましょう。DMA自体はダイレクトメモリアクセスを意味します。これは単なるアクセス方法、または読み取りと書き込みの方法、または率直に言って、直接読み取りであることがわかります。それを理解すると、単純すぎます。 DMAという言葉は少しSBのように感じますが、DNAに出会うのと同じようにボロボロになっています。

直接アクセスまたは直接読み取りと書き込みはどうですか?もちろん、それはデータです。どこから読み取ることができますか、それともどこに書き込む必要がありますか?

この質問は良いです。後で読み取り位置と書き込み位置について説明します。

本日言及したDMAは、読み書きの方法を説明するものではなく、実際、通常のSPI、I2Cと同様に、読み書きの方法はまったくありません。直接読み書きを依頼することもできます。

誰かがあなたの説明が間違っていると言わなければなりません、まあ、それは間違っています!「直接」の意味を反映していないため、CPUを経由せずに特定のアドレスからデータを読み取ることができることを直接意味します。

畜生!認知力は少し弱く、CPUは子供の頃からほとんどのデータ操作の中枢神経系であると言われています!中枢神経系や脳を必要としない植生アプローチをどのように達成しましたか?

まず、DMAはメディアアクセス方式にすぎません。MCUでは、DMAコントローラーを使用してダイレクトアクセスを実現できます。つまり、DMAコントローラーを使用してDMA操作を実現できます。

DMAコントローラは、CPUの介入なしに、周辺機器とメモリ間、またはメモリとメモリ間でデータを転送する「ハードウェアの方法」を提供します。これにより、帯域幅が解放されます(実際、CPUが解放され、CPUが他のライブを実行できるようになります)。

 

I2Cデバイス、SPIデバイスなど、他の伝送バスデバイス用のドライバーを作成する場合、I2Cコントローラー、SPIコントローラーがあることがわかっているので、ここに単純なDMAコントローラーがあります。

DMAコントローラーの作業、特にソフトウェアに実装する必要のあるコンテンツは、以前のi2CおよびSPIよりも単純ですが、理解するのが難しい部分です(この文を注意深く味わうと、これはナンセンスだと思うでしょう)

最初に写真を投稿するなぜ今回投稿するのかわかりません:(DMAは表示されましたか?注意深く見てください。表示されない場合は忘れてください)

DMAがAHBバスマトリックスに配置されていることがわかります。AHB、非常に重要な高速バスであることを思い出してください。APBペリフェラルバスに対応して、その周波数はAHBより少し低い場合があります。よりペリフェラルです。

 

写真からわかるように、紹介する例としてGD32E10X国産MCUを使用しました。その主な機能は、入力するのが面倒で、写真を投稿することです。

最大伝送データ長は65536で2の16乗、2の10乗は1Kなので、2の16乗は16K、申し訳ありませんが64kであることがわかります。

組み込みMCUには64kで十分です。手放さないでください、[旺财]

チャネルが何であるかは関係ありませんが、異なるチャネルは異なる周辺アドレスに対応していると言わざるを得ません。この文がわからない場合は、見下ろしてください。

次に、それが送信元と宛先です。実際にはアドレスの読み取りと書き込みです。

後者は送信モードに関するものです。これらを中断し、怠惰に言って、自分で体験してください。体験できない場合は、コメントを残してください。(実際、メッセージに返信できない場合があります)

 

次の文章は私が読んだ後は非常に古典的です。これはSPECが言ったことです:

DMA転送は、送信元アドレスからデータを読み取り、読み取ったデータを宛先アドレスに保存するという2つのステップに分かれています。象を冷蔵庫に数歩入れたことを思い出しました

このSPECの作成者は、以前にクロストークについて話したことがあるはずです。

DMAコントローラーは、DMA_CHxPADDR、DMA_CHxMADDR、およびDMA_CHxCTLレジスターの値に基づいて、次の操作の送信元/宛先アドレスを計算します。

DMA_CHxCNTレジスタは、転送数を制御するために使用されます。

DMA_CHxCTLレジスタのPWIDTHおよびMWIDTHビットフィールドは、毎回送受信されるバイト数(バイト、ハーフワード、ワード)を決定します。

これらのキーレジスタの紹介はとても良いです。その意味を注意深く理解すると、多くの質問があり、考えるようになります。

 

DMA_CHxCNTレジスタのCNTビットフィールドは、CHENビットを設定する前に設定する必要があり、転送回数を制御します。転送中、CNTビットフィールドの値は、さらに何回データ転送が実行されるかを示します。

この文は、読み取りアドレスを設定してからCNT(つまり読み取り回数)を設定すると、設定した回数だけ読み取られるので、味わうことができます。

ただし、CHENを設定する前に設定する必要があります。DMA_CHxCTLレジスタのCHENビットがクリアされると、DMA送信を停止できます。

 

アドレス生成

メモリと周辺機器の両方が、固定モードとインクリメンタルモードの2つのアドレス生成アルゴリズムを独立してサポートしています。レジスタDMA_CHxCTLのPNAGAビットとMNAGAビットは、メモリとペリフェラルのアドレス生成アルゴリズムを設定するために使用されます。

固定モードでは、アドレスは常に初期ベースアドレス(DAM_CHxPADDR、DMA_CHxMADDR)に固定されます。

インクリメンタルモードでは、次のデータ送信のアドレスは現在のアドレスに1(または2、4)を加えたものであり、この値はデータ送信幅によって異なります。

 

ループモード

ループモードは、継続的な周辺機器要求(ADCスキャンモードなど)を処理するために使用されます。DMA_CHxCTLレジスタのCMENビットをセットして、ループモードを有効にします。

サイクリックモードでは、各DMA転送が完了すると、CNT値がリロードされ、転送完了フラグが1に設定されます。DMAは、チャネルイネーブルビット(CHENのCHEN)を認識して、常にペリフェラルの要求に応答します。 DMA_CHxCTLレジスタ)ビット)は0にクリアされます。

 

メモリ間モード

DMA_CHxCTLレジスタのM2Mビットをセットして、メモリをメモリモードに切り替えます。このモードでは、DMAチャネルは、データを転送するときにペリフェラルの要求信号に依存しません。DMA_CHxCTLレジスタのCHENビットが1に設定されると、DMAチャネルはホームを離れてデータの送信を開始します。DMAチャネルは、DMA_CHXCNTレジスタが0に達するまで停止しません。

 

チャネル構成

新しいDMAデータ転送を開始するには、以下の手順に従うことをお勧めします。

1:CHENビットを読み取って、チャネルが有効かどうかを判断します。1(チャネルが有効になっている)の場合は、クリアしてビットを変更します。CHENが0の場合は、以下の手順に従って、新しい転送を開始するようにDMAを構成してください。

2:DMA_CHxCTLレジスタのM2MビットとDIRビットを設定し、送信モードを選択します

3:DMA_CHxCTLレジスタのCMENビットを設定します。CMENはCHENではないことに注意してください。サイクルモードを有効にするかどうかを選択します。

4:DMA_CHxCTLレジスタのPRIOビットを設定して、チャネルのソフトウェア優先度を選択します。

5:DMA_CHxCTLレジスタを介して、メモリとペリフェラルの転送幅、およびメモリとペリフェラルのアドレス生成アルゴリズムを設定します。ここでの伝送幅は速度に大きな影響を与えますか?次のワードの幅とバイトの幅の速度差を測定できます。アルゴリズムの生成、読み取りは間違いなく固定アルゴリズム、受信はインクリメンタルアルゴリズムです。これは、SPI読み取りを行う場合、SPIのSPI_DATAレジスタを介してのみデータを読み取ることができるためです。書き込みはメモリ内の連続領域に書き込みます。幅はメモリ内の連続領域に書き込まれます。対応するアドレス。ここでのアドレスオフセットは、対応する送信幅であることに注意してください。

6:DMA_CHxCTLレジスタを介して、転送完了割り込み、半転送完了割り込み、送信エラー割り込みのイネーブルビットを設定し、転送完了割り込みが呼び出されたかどうか、および半転送完了かどうかを確認するように割り込みを設定できます。割り込みは転送です指定された割り込みの半分、送信エラー割り込みはどのように見えますか

7:DMA_CHxPADDRレジスタを介してペリフェラルベースアドレスを設定します。SPIフラッシュの場合、ペリフェラルのベースアドレスはSPI_DATAデータレジスタです。このレジスタは32ビットです。これにより、送信する最大ビット数を考慮することができます。

8:DMA_CHxMADDRレジスタを介してメモリベースアドレスを設定します。

9:DMA_CHxCNTレジスタを介してデザインと総転送量を設定します。

10:DMA_CHxCTLレジスタのCHENビットを1に設定して、DMAチャネルを有効にします。

 

割り込み:

各DMAチャネルには専用の割り込みがあります。割り込みイベントには、送信完了、半送信完了、送信エラーの3種類があります。各割り込みイベントには、DMA_INTFレジスタに専用のフラグビット、DMA_INTCレジスタに専用のクリアビット、およびDMA_CHxCTLレジスタに専用のイネーブルビットがあります。

中断は実際には比較的理解しやすいです。結局のところ、組み込み開発では中断が一般的です。

ここで割り込みを使用したい場合は、

nvic_irq_enable(DMA0_Channel3_IRQn,0,0);
dma_interrupt_enable(DMA0, DMA_CH3, DMA_INT_FTF);

 

次に、gd32e10x_it.cに割り込みハンドラを実装します。

void DMA0_Channel3_IRQHandler(void)
{
    if(dma_interrupt_flag_get(DMA0, DMA_CH3, DMA_INT_FLAG_FTF)){     
        dma_interrupt_flag_clear(DMA0, DMA_CH3, DMA_INT_FLAG_G);
    }
}

どのチャネル割り込みを達成するためにどのチャネルを使用するかに注意してください。または、割り込みコードを書き込んだ後に再チェックする習慣が必要です。

 

DMA要求マッピング

複数のペリフェラルリクエストが同じDMAチャネルにマッピングされます。これらの要求信号は、論理ORの後にDMAに入ります。対応するペリフェラルのレジスタを設定することにより、各ペリフェラル要求を個別にオンまたはオフにすることができます。ユーザーは、同じチャネルで同時に1つの周辺機器要求のみが有効になっていることを確認する必要があります。

ここでのスクリーンショットは、使用するペリフェラルDMA機能、または書き込みまたは読み取りを行うペリフェラルDMAコントローラー、およびこのペリフェラルに使用できる探しているDMAとチャネルを示しています。

たとえば、DMAを使用してSPI1のデータを読み取る場合、DMA0チャネル3のみがSPI1 Rをサポートしていることがわかるため、DMA0CH3チャネルを使用する必要があります。

これはSPI + DMAの動作例です。以前は純粋なSPI動作でしたが、現在はSPI + DMAを介してspiインターフェイスのNORフラッシュを読み取る動作です。

GDフラッシュを使用

以前は純粋なSPIでフラッシュデータを読み取るためのインターフェイスは次のとおりです。

/*!
    \brief      read a block of data from the flash
    \param[in]  pbuffer: pointer to the buffer that receives the data read from the flash
    \param[in]  read_addr: flash's internal address to read from
    \param[in]  num_byte_to_read: number of bytes to read from the flash
    \param[out] none
    \retval     none
*/
spiflash_ret spiflash_buffer_read(uint8_t* pbuffer, uint32_t read_addr, uint16_t num_byte_to_read)
{
    spiflash_ret ret = spiflash_ret_success;
    /* select the flash: chip slect low */
    SPI_FLASH_CS_LOW();

    /* send "read from memory " instruction */
    spi_flash_send_byte(READ);

    /* send read_addr high nibble address byte to read from */
    spi_flash_send_byte((read_addr & 0xFF0000) >> 16);
    /* send read_addr medium nibble address byte to read from */
    spi_flash_send_byte((read_addr& 0xFF00) >> 8);
    /* send read_addr low nibble address byte to read from */
    spi_flash_send_byte(read_addr & 0xFF);

    /* while there is data to be read */
    while(num_byte_to_read--){
        /* read a byte from the flash */
        *pbuffer = spi_flash_send_byte(DUMMY_BYTE);
        /* point to the next location where the byte read will be saved */
        pbuffer++;
    }

    /* deselect the flash: chip select high */
    SPI_FLASH_CS_HIGH();
    
    return ret;
}

読み取りコマンドとアドレスを送信した後、データを読み取るためのwhileループであるwhileループがあり、このようなwhileループには時間がかかりすぎるように見えることがわかります。

ただし、テクノロジーは単なる監視以上のものである可能性があります。たとえば、この方法で1Mデータを読み取って所要時間を確認し、次のSPI + DMAメソッドを使用して読み取りと比較を行うことができます。

次の関数は、上記の関数を使用して行われた変更です。もちろん、主な変更はwhileループ部分です。純粋なSPIを使用して、読み取りコマンドと送信アドレスを送信しました。

データを受信しながら見てみましょう。

SPI + DMAを使用して読み取りたい場合は、SEPCを調べて、SPIの概要にDMAの概要があるかどうかを確認することもできます。

上のスクリーンショットは、SPIインターフェイスの説明におけるDMAの概要です。

これは主に、DMAを使用してSPI TXおよびRXデータを転送する場合、以下のコードで説明されているように、SPIモードでDMAを有効にする必要があることを意味します。

static void dma0_ch3_init(void)
{
	    /* enable DMA clock */
    rcu_periph_clock_enable(RCU_DMA0);
	
	nvic_irq_enable(DMA0_Channel3_IRQn,0,0);
	dma0_ch3_test_config();
}

ここで、SPIが初期化されると、DMA CH3初期化、クロック、およびDMA0 CH3IRQが同時に呼び出されます。

void dma0_ch3_test_config(void)
{
	memset(g_destbuf,0x00,sizeof(g_destbuf));
    dma_parameter_struct dma_init_struct;
    /* initialize DMA channel 3 */
    dma_deinit(DMA0, DMA_CH3);//先要deinit一下
    dma_struct_para_init(&dma_init_struct);//将这个结构体中的数据全部初始化为0,
    
    dma_init_struct.direction = DMA_PERIPHERAL_TO_MEMORY;//这里我们只是做个测试,从外设(SPI)读取数据到内存
    dma_init_struct.memory_addr = (uint32_t)g_destbuf;//这个是个数组,也就是前面说的内存

    dma_init_struct.memory_inc = DMA_MEMORY_INCREASE_ENABLE;//在内存中需要采用自动增长的方式
    dma_init_struct.memory_width = DMA_MEMORY_WIDTH_8BIT;//每次读取8bit,当然SPI 的DATA寄存器是32位的寄存器你可以读取32位
    dma_init_struct.number = TRANSFER_NUM;//这个就是你这次DAM传输需要传输的字节数
    dma_init_struct.periph_addr = (uint32_t)&SPI_DATA(SPI1);//这里就是外设的地址,DMA读取数据都是从外设读取的,这里相对于DMA来说,SPI就是外设,是ARM内核的外设
    dma_init_struct.periph_inc = DMA_PERIPH_INCREASE_DISABLE;//SPI的Data寄存器是数据的唯一读入地址,SPI的数据在传输的时候都会不断的写入到这个寄存器,所以我们读取数据都只读一个寄存器,地址并不会增加改变
    dma_init_struct.periph_width = DMA_PERIPHERAL_WIDTH_8BIT;//外设的读取位数也是8位
    dma_init_struct.priority = DMA_PRIORITY_ULTRA_HIGH;//优先级高,这个在多个外设都要用到DMA的时候会用到
    dma_init(DMA0, DMA_CH3, &dma_init_struct);//把我们的这些设置都写入到DMA的对应寄存器中
    /* DMA channel 0 mode configuration */
    dma_circulation_disable(DMA0, DMA_CH3);//不采用循环模式,读完就结束
    dma_memory_to_memory_disable(DMA0, DMA_CH3);//不是从memory到memory的读取,所以disable
    /* DMA channel 0 interrupt configuration */
    dma_interrupt_enable(DMA0, DMA_CH3, DMA_INT_FTF);//enable 中断,如果你需要用到中断的话
    /* enable DMA transfer */
    //dma_channel_enable(DMA0, DMA_CH3);//以为你这里只是初始化,先不要enable DMA,只有在真正用的时候单独调用DMA,就可以传输数据了。
}

さて、初期化は終わりました。読み取り操作を見てみましょう。ここでは、DMAを使用してデータを読み取り、データを直接印刷します。

/*!
    \brief      read a block of data from the flash
    \param[in]  pbuffer: pointer to the buffer that receives the data read from the flash
    \param[in]  read_addr: flash's internal address to read from
    \param[in]  num_byte_to_read: number of bytes to read from the flash
    \param[out] none
    \retval     none
*/
spiflash_ret spiflash_dma_read(uint8_t* pbuffer, uint32_t read_addr, uint16_t num_byte_to_read)
{
    spiflash_ret ret = spiflash_ret_success;
    /* select the flash: chip slect low */
    SPI_FLASH_CS_LOW();

    /* send "read from memory " instruction */
    spi_flash_send_byte(READ);

    /* send read_addr high nibble address byte to read from */
    spi_flash_send_byte((read_addr & 0xFF0000) >> 16);
    /* send read_addr medium nibble address byte to read from */
    spi_flash_send_byte((read_addr& 0xFF00) >> 8);
    /* send read_addr low nibble address byte to read from */
    spi_flash_send_byte(read_addr & 0xFF);

	spi_parameter_struct spi_init_struct;
	    /* SPI1 parameter config */
    spi_init_struct.trans_mode           = SPI_TRANSMODE_RECEIVEONLY;
    spi_init_struct.device_mode          = SPI_MASTER;
    spi_init_struct.frame_size           = SPI_FRAMESIZE_8BIT;
    spi_init_struct.clock_polarity_phase = SPI_CK_PL_LOW_PH_1EDGE;
    spi_init_struct.nss                  = SPI_NSS_SOFT;
    spi_init_struct.prescale             = SPI_PSC_8;
    spi_init_struct.endian               = SPI_ENDIAN_MSB;
    spi_init(SPI1, &spi_init_struct);
	dma_channel_enable(DMA0, DMA_CH3);
	while(g_dmacomplete_flag == 0);

    /* deselect the flash: chip select high */
    SPI_FLASH_CS_HIGH();
	uint8_t i = 0;
	for(i = 0; i < 8; i++)
		printf("[%d] = %d\r\n",i,g_destbuf[i]);
    printf("g_destbuf = %s strlen(g_destbuf) = %d\r\n",g_destbuf,strlen(g_destbuf));
    return ret;
}

主に次の部分:

spi_parameter_struct spi_init_struct;
	    /* SPI1 parameter config */
    spi_init_struct.trans_mode           = SPI_TRANSMODE_RECEIVEONLY;
    spi_init_struct.device_mode          = SPI_MASTER;
    spi_init_struct.frame_size           = SPI_FRAMESIZE_8BIT;
    spi_init_struct.clock_polarity_phase = SPI_CK_PL_LOW_PH_1EDGE;
    spi_init_struct.nss                  = SPI_NSS_SOFT;
    spi_init_struct.prescale             = SPI_PSC_8;
    spi_init_struct.endian               = SPI_ENDIAN_MSB;
    spi_init(SPI1, &spi_init_struct);
	dma_channel_enable(DMA0, DMA_CH3);
	while(g_dmacomplete_flag == 0);

 

前の関数は読み取り時に0xFFを送信し、データを取得できることがわかります。ここでは、ネチズンの紹介を読みました。

ここでは、SPIトランスモードを受信専用モードに設定できるため、0xffを送信する必要はなく、DMAを直接使用して読み取ることができます。

このネチズンのブログアドレスhttps://blog.csdn.net/chenwei2002/article/details/49722373に本当に感謝します

彼はSTM32を使用していたので、SPIトランスモードを設定するときに間違って設定しました。

この機能は、受信専用の設定と考えてください。

/*!
    \brief      configure SPI bidirectional transfer direction
    \param[in]  spi_periph: SPIx(x=0,1,2)
    \param[in]  transfer_direction: SPI transfer direction
                only one parameter can be selected which is shown as below:
      \arg        SPI_BIDIRECTIONAL_TRANSMIT: SPI work in transmit-only mode
      \arg        SPI_BIDIRECTIONAL_RECEIVE: SPI work in receive-only mode
    \param[out] none
    \retval     none
*/
void spi_bidirectional_transfer_config(uint32_t spi_periph, uint32_t transfer_direction)
{
    if(SPI_BIDIRECTIONAL_TRANSMIT == transfer_direction){
        /* set the transmit-only mode */
        SPI_CTL0(spi_periph) |= (uint32_t)SPI_BIDIRECTIONAL_TRANSMIT;
    }else{
        /* set the receive-only mode */
        SPI_CTL0(spi_periph) &= SPI_BIDIRECTIONAL_RECEIVE;
    }
}

実際には彼ではなく、受信のみに設定されています

spi_init_struct.trans_mode           = SPI_TRANSMODE_RECEIVEONLY;

 

、次に対応するSPIレジスタを書き込みます

while(g_dmacomplete_flag == 0);この文については、割り込みでこの変数を1に設定しました

したがって、ここではDMA動作が完了するのを待ってから、チップセレクトをハイにプルします。そうしないと、読み取りエラーの問題が発生します。

 

一般的に、紹介された例は以前の理論よりもはるかに浅いです。実際、例を実装する過程、または探索する過程で、私は想像以上に多くの問題に遭遇しました。

幸いなことに、データ転送はDMAを介して実現できるようになりました。

後で比較するためにSPI + DMAと純粋なSPIを使用しますが、どのくらいの速度を改善できますか?

 

 

 

おすすめ

転載: blog.csdn.net/yangkunhenry/article/details/104741475