SPI + DMA

Let’s talk about what DMA is. DMA itself means Direct Memory Access. It can be seen that this is just an access method, or read and write method, or to put it bluntly, it means direct reading. Take it, it's too straightforward. I feel that the word DMA feels a bit SB in my mind. It's just such a tattered thing that is similar to encountering DNA.

What about direct access or direct reading and writing? Of course it’s the data. Where can I read it from, or where should I write it?

This question is good, we will talk about the reading position and writing position later.

The DMA mentioned today is not to explain the reading and writing method, in fact, there is no reading and writing method at all, just like ordinary SPI, I2C, I can also ask them to read and write directly

Someone must say that your explanation is wrong, well, it is wrong! Because it does not reflect the meaning of "Direct", it directly means that data can be read from a specific address without going through the CPU.

Damn it! Cognition is a bit weak, and I have been told that the CPU is the central nervous system of data operations from childhood to most! How did you achieve a vegetative approach that does not require a central nervous system or a brain? ! ! !

First of all, DMA is only a medium access method. In our MCU, the DMA controller can be used to achieve direct access. That is to say, the DMA controller can be used to achieve DMA operations.

The DMA controller provides a "hardware way" to transfer data between peripherals and memory or between memory and memory, without CPU intervention, thereby freeing up bandwidth (in fact, it releases the CPU, allowing the CPU to do other live)

 

If you make a driver for other transmission bus devices, such as I2C devices, SPI devices, you know that there are I2C controllers, SPI controllers, then here is a simple DMA controller

The work of the DMA controller, especially the content that needs to be implemented in the software, is simpler than the previous i2C and SPI, but the hard part is to understand (you will think this is nonsense if you taste this sentence carefully)

First post a picture that I don’t know why I’m going to post it at this time: (Did you see the DMA? Look carefully, just forget it if you don’t see it)

You can see that DMA is located in the AHB bus matrix. AHB, you can remember that it is a very important high-speed bus. Corresponding to the APB peripheral bus, its frequency may be a little lower than AHB. It is more peripheral

 

As you can see from the picture, I used a GD32E10X domestic MCU as an example to introduce. Its main features, I am too lazy to type, post a picture:

It can be seen that the maximum transmission data length is 65536, which is 2 to the sixteenth power, and the tenth power of two is 1K, so the sixteenth of 2 is 16K, sorry 64k

64k is enough for an embedded MCU, don’t let yourself go, [旺财]

It doesn't matter what the channel is, but I have to say that different channels correspond to different peripheral addresses. If you don't understand this sentence, just look down.

Then it's the source and destination. It is actually reading and writing addresses.

The latter is about the transmission mode, interrupt these, say it lazily, experience it for yourself, if you can’t experience it, leave a comment. (Actually, I may not reply to messages)

 

The following sentence is very classic after I read it. This is what SPEC said:

DMA transfer is divided into two steps: read data from the source address, and then store the read data to the destination address. . . . I remembered putting the elephant in the refrigerator for several steps

The writer of this SPEC must have talked about cross talk before.

The DMA controller calculates the source/destination address of the next operation based on the values ​​of the DMA_CHxPADDR, DMA_CHxMADDR, and DMA_CHxCTL registers.

The DMA_CHxCNT register is used to control the number of transfers.

The PWIDTH and MWIDTH bit fields of the DMA_CHxCTL register determine the number of bytes (bytes, halfwords, words) sent and received each time

These introductions to key registers are very good. If you carefully understand the meaning of it, you will have many questions and will lead you to think.

 

The CNT bit field of the DMA_CHxCNT register must be configured before the CHEN bit is set, and it controls the number of transfers. During the transfer, the value of the CNT bit field indicates how many more data transfers will be performed.

This sentence can be tasted, once you set the read address and then set the CNT (that is, the number of reads), then it will read the number of times you set.

But it must be set before CHEN is set. If the CHEN bit of the DMA_CHxCTL register is cleared, DMA transmission can be stopped.

 

Address generation

Both memory and peripherals independently support two address generation algorithms: fixed mode and incremental mode. The PNAGA and MNAGA bits of the register DMA_CHxCTL are used to set the address generation algorithm of the memory and peripherals.

In fixed mode, the address is always fixed to the initial base address (DAM_CHxPADDR, DMA_CHxMADDR).

In incremental mode, the address of the next data transmission is the current address plus 1 (or 2, 4), and this value depends on the data transmission width.

 

Loop mode

The loop mode is used to process continuous peripheral requests (such as ADC scan mode). Set the CMEN bit in the DMA_CHxCTL register to enable the loop mode.

In the cyclic mode, when each DMA transfer is completed, the CNT value will be reloaded, and the transfer completion flag will be set to 1. DMA will always respond to the request of the peripheral, knowing the channel enable bit (CHEN in the DMA_CHxCTL register) Bit) is cleared to 0.

 

Memory to memory mode

Set the M2M bit in the DMA_CHxCTL register to enable the memory to the memory mode. In this mode, the DMA channel does not rely on the request signal of the peripheral when transferring data. Once the CHEN bit of the DMA_CHxCTL register is set to 1, the DMA channel will leave home and start transmitting data. The DMA channel will not stop until the DMA_CHXCNT register reaches 0.

 

Channel configuration

To start a new DMA data transfer, it is recommended to follow the steps below:

1: Read the CHEN bit to determine whether the channel is enabled. If it is 1 (the channel has been enabled), clear it and change the bit. When CHEN is 0, please follow the steps below to configure DMA to start a new transfer.

2: Configure the M2M and DIR bits of the DMA_CHxCTL register and select the transmission mode

3: Configure the CMEN bit of the DMA_CHxCTL register. Note that CMEN is not CHEN. Choose whether to enable the cycle mode.

4: Configure the PRIO bit of the DMA_CHxCTL register to select the software priority of the channel.

5: Configure the transfer width of the memory and peripherals and the memory and peripheral address generation algorithm through the DMA_CHxCTL register. Does the transmission width here have a big impact on the speed? The speed difference between the width of the next word and the width of a byte can be measured. Generate algorithm, reading is definitely a fixed algorithm, receiving is incremental algorithm, because when doing SPI reading, you can only read data through the SPI_DATA register of SPI. Writing is writing to a continuous area in the memory. The width is written into the corresponding address. Note that the address offset here is the corresponding transmission width.

6: Through the DMA_CHxCTL register to configure the transfer completion interrupt, the half transfer completion interrupt, the enable bit of the transmission error interrupt, the interrupt can be configured to see if the transfer completion interrupt has been called, and whether the half transfer completion interrupt is a transfer Half of the given interrupt, what does the transmission error interrupt look like

7: Configure the peripheral base address through the DMA_CHxPADDR register. In the case of SPI Flash, the peripheral's base address is the SPI_DATA data register. This register is 32 bits. This can consider the maximum number of bits to transmit.

8: Configure the memory base address through the DMA_CHxMADDR register.

9: Configure the design and total transfer amount through the DMA_CHxCNT register.

10: Set the CHEN bit of the DMA_CHxCTL register to 1 to enable the DMA channel.

 

Interrupt:

Each DMA channel has a dedicated interrupt. There are three types of interrupt events: transmission complete, half-transmission complete and transmission error. Each interrupt event has a dedicated flag bit in the DMA_INTF register, a dedicated clear bit in the DMA_INTC register, and a dedicated enable bit in the DMA_CHxCTL register.

Interruption is actually relatively easy to understand. After all, interruptions are common in embedded development.

If you want to use interrupts here,

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

 

Then implement the interrupt handler in 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);
    }
}

Pay attention to which channel you use to achieve which channle interrupt. Or you have to have the habit of rechecking after the interrupt code is written.

 

DMA request mapping

Multiple peripheral requests are mapped to the same DMA channel. These request signals enter DMA after logical OR. By configuring the register of the corresponding peripheral, each peripheral request can be turned on or off independently. The user must ensure that only one peripheral request is enabled on the same channel at the same time.

The screenshot here is to illustrate which peripheral DMA function you want to use, or which peripheral DMA controller is going to write or read, and what DMA and channel you are looking for that can be used for this peripheral.

For example, if I want to use DMA to read the data of SPI1, you need to use the DMA0 CH3 channel, because you can see that only DMA0 channel 3 supports SPI1 R.

Here is an example of the operation of SPI + DMA. Before it was pure SPI operation, now it is the operation of reading the nor flash of spi interface through SPI+DMA.

GD flash is used

The interface for reading flash data with pure SPI before is:

/*!
    \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;
}

It can be seen that after the read command and address are sent, there is a while loop, which is the while loop to read the data. It can be seen that such a while loop seems to take too much time.

However, technology can be more than just watching. You can read, say, 1M data in this way to see how much time it takes, and then use the following SPI + DMA method to read and compare:

The following function is a modification made by using the above function. Of course, the main modification is the while loop part. We used pure SPI to send the read command and send address.

Let's take a look while receiving data.

If we want to use SPI+DMA to read, we might as well take a look at SEPC and see if there is any introduction to DMA in the SPI introduction.

The above screenshot is the introduction to DMA in the description of the SPI interface

This mainly means that if you want to use DMA to transfer SPI TX and RX data, you need to enable DMA in SPI mode, as explained in the code below:

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();
}

Here, when SPI is initialized, DMA CH3 initialization, clock, and DMA0 CH3 IRQ will be called at the same time.

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,就可以传输数据了。
}

Okay, the initialization is over, let’s take a look at our read operation, here is to use DMA to read the data and then print the data directly.

/*!
    \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;
}

Mainly the following part:

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);

 

We see that the previous function will send 0xFF when reading, and then can get the data, here I read the introduction of netizens

Here you can set the SPI trans mode to RECEIVE ONLY mode, so you don't need to send 0xff, you can directly use DMA to read.

Really thank this netizen for his blog address https://blog.csdn.net/chenwei2002/article/details/49722373

Because he used STM32, I set it wrong when setting SPI trans mode, it was used

Think of this function as a setting for receiving only

/*!
    \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;
    }
}

Actually it’s not him, set to Receive only is

spi_init_struct.trans_mode           = SPI_TRANSMODE_RECEIVEONLY;

 

, And then write the corresponding SPI register

As for while(g_dmacomplete_flag == 0); this sentence, I set this variable to 1 in the interrupt

So here is to wait for the DMA operation to complete before pulling the chip select high, otherwise there will be a problem of reading errors

 

In general, the introduced examples are much shallower than the previous theory. In fact, in the process of implementing the examples, or in the process of exploring, I encountered more problems than imagined.

Fortunately, data transmission can now be achieved through DMA.

I will use SPI+DMA and pure SPI to make a comparison later, how much speed can be improved?

 

 

 

Guess you like

Origin blog.csdn.net/yangkunhenry/article/details/104741475
SPI