SPI + DMA

Hablemos de lo que es DMA. DMA en sí mismo significa acceso directo a la memoria. Se puede ver que esto es solo un método de acceso, o método de lectura y escritura, o para decirlo sin rodeos, es lectura directa. Tómalo, es demasiado sencillo. Siento que la palabra DMA se siente un poco SB en mi mente. Es algo tan andrajoso que es similar a encontrar ADN.

¿Qué pasa con el acceso directo o la lectura y escritura directa? Por supuesto que son los datos. ¿Dónde puedo leerlos o dónde debo escribirlos?

Esta pregunta es buena, hablaremos sobre la posición de lectura y la posición de escritura más adelante.

El DMA mencionado hoy no es para explicar el método de lectura y escritura, de hecho, no hay ningún método de lectura y escritura, al igual que el SPI ordinario, I2C, también puedo pedirles que lean y escriban directamente.

Alguien debe decir que tu explicación está mal, bueno, ¡está mal! Debido a que no refleja el significado de "Directo", significa directamente que los datos se pueden leer desde una dirección específica sin pasar por la CPU.

¡Maldición! La cognición es un poco débil, ¡y me han dicho que la CPU es el sistema nervioso central de las operaciones de datos desde la infancia hasta la mayoría! ¿Cómo lograste un abordaje vegetativo que no requiere un sistema nervioso central o un cerebro? ! ! !

En primer lugar, DMA es solo un método de acceso medio. En nuestra MCU, el controlador DMA puede usarse para lograr acceso directo. Es decir, el controlador DMA puede usarse para lograr operaciones DMA.

El controlador DMA proporciona una "forma de hardware" para transferir datos entre los periféricos y la memoria o entre la memoria y la memoria, sin la intervención de la CPU, liberando así ancho de banda (de hecho, libera la CPU, lo que permite que la CPU realice otras actividades en vivo)

 

Si crea un controlador para otros dispositivos de bus de transmisión, como dispositivos I2C, dispositivos SPI, sabe que hay controladores I2C, controladores SPI, entonces aquí hay un controlador DMA simple

El trabajo del controlador DMA, especialmente el contenido que debe implementarse en el software, es más simple que el i2C y el SPI anteriores, pero lo difícil es entenderlo (pensarás que esto es una tontería si pruebas esta oración con cuidado)

Primero publique una imagen. No sé por qué la voy a publicar en este momento: (¿Viste el DMA? Míralo con cuidado, olvídalo si no lo ves)

Puede ver que DMA está ubicado en la matriz de bus AHB. AHB, puede recordar que es un bus de alta velocidad muy importante. Correspondiente al bus periférico APB, su frecuencia puede ser un poco menor que AHB. Es más periférico

 

Como puede ver en la imagen, utilicé un MCU doméstico GD32E10X como ejemplo para presentar. Sus características principales, soy demasiado vago para escribir, publicar una imagen:

Se puede ver que la longitud máxima de transmisión de datos es 65536, que es 2 elevado a la decimosexta potencia, y la décima potencia de dos es 1K, por lo que la decimosexta de 2 es 16K, lo siento 64k

64k es suficiente para un MCU integrado, no te dejes llevar, [旺财]

No importa cuál sea el canal, pero debo decir que diferentes canales corresponden a diferentes direcciones periféricas, si no entiendes esta frase, mira hacia abajo.

Entonces es el origen y el destino. En realidad, se trata de leer y escribir direcciones.

Este último es sobre el modo de transmisión, interrumpa estos, dígalo con pereza, experimentelo usted mismo, si no puede experimentarlo, deje un comentario. (De hecho, es posible que no responda a los mensajes)

 

La siguiente oración es muy clásica después de leerla. Esto es lo que dijo SPEC:

La transferencia DMA se divide en dos pasos: leer los datos de la dirección de origen y luego almacenar los datos leídos en la dirección de destino. . . . Recordé haber puesto al elefante en el refrigerador por varios pasos.

El autor de este SPEC debe haber hablado antes de la conversación cruzada.

El controlador DMA calcula la dirección de origen / destino de la siguiente operación basándose en los valores de los registros DMA_CHxPADDR, DMA_CHxMADDR y DMA_CHxCTL.

El registro DMA_CHxCNT se utiliza para controlar el número de transferencias.

Los campos de bits PWIDTH y MWIDTH del registro DMA_CHxCTL determinan el número de bytes (bytes, medias palabras, palabras) enviados y recibidos cada vez

Estas introducciones a los registros clave son muy buenas, si comprendes detenidamente su significado, tendrás muchas preguntas y te harán pensar.

 

El campo de bit CNT del registro DMA_CHxCNT debe configurarse antes de que se establezca el bit CHEN y controla el número de transferencias. Durante la transferencia, el valor del campo de bits CNT indica cuántas transferencias de datos más se realizarán.

Esta oración se puede probar, una vez que configure la dirección de lectura y luego configure el CNT (es decir, el número de lecturas), leerá el número de veces que configuró.

Pero debe configurarse antes de configurar CHEN. Si se borra el bit CHEN del registro DMA_CHxCTL, se puede detener la transmisión DMA.

 

Generación de direcciones

Tanto la memoria como los periféricos admiten de forma independiente dos algoritmos de generación de direcciones: modo fijo y modo incremental. Los bits PNAGA y MNAGA del registro DMA_CHxCTL se utilizan para configurar el algoritmo de generación de direcciones de la memoria y los periféricos.

En modo fijo, la dirección siempre se fija a la dirección base inicial (DAM_CHxPADDR, DMA_CHxMADDR).

En el modo incremental, la dirección de la siguiente transmisión de datos es la dirección actual más 1 (o 2, 4), y este valor depende del ancho de transmisión de datos.

 

Modo cíclico

El modo de bucle se utiliza para procesar solicitudes continuas de periféricos (como el modo de escaneo ADC). Establezca el bit CMEN en el registro DMA_CHxCTL para habilitar el modo de bucle.

En el modo cíclico, cuando se completa cada transferencia DMA, el valor de CNT se recargará y la bandera de finalización de transferencia se establecerá en 1. DMA siempre responderá a la solicitud del periférico, conociendo el bit de habilitación del canal (CHEN en el DMA_CHxCTL register) Bit) se pone a 0.

 

Modo de memoria a memoria

Configure el bit M2M en el registro DMA_CHxCTL para habilitar la memoria en el modo de memoria. En este modo, el canal DMA no depende de la señal de solicitud del periférico cuando transfiere datos. Una vez que el bit CHEN del registro DMA_CHxCTL se establece en 1, el canal DMA saldrá de casa y comenzará a transmitir datos.El canal DMA no se detendrá hasta que el registro DMA_CHXCNT llegue a 0.

 

Configuración de canal

Para iniciar una nueva transferencia de datos DMA, se recomienda seguir los pasos a continuación:

1: Lea el bit CHEN para determinar si el canal está habilitado. Si es 1 (el canal ha sido habilitado), bórrelo y cambie el bit. Cuando CHEN es 0, siga los pasos a continuación para configurar DMA para iniciar una nueva transferencia.

2: Configure los bits M2M y DIR del registro DMA_CHxCTL y seleccione el modo de transmisión

3: Configure el bit CMEN del registro DMA_CHxCTL Tenga en cuenta que CMEN no es CHEN Elija si desea habilitar el modo de ciclo.

4: Configure el bit PRIO del registro DMA_CHxCTL para seleccionar la prioridad de software del canal.

5: Configure el ancho de transferencia de la memoria y los periféricos y el algoritmo de generación de direcciones de la memoria y los periféricos a través del registro DMA_CHxCTL. ¿El ancho de transmisión aquí tiene un gran impacto en la velocidad? Se puede medir la diferencia de velocidad entre el ancho de la siguiente palabra y el ancho de un byte. Generar algoritmo, la lectura es definitivamente un algoritmo fijo, la recepción es un algoritmo incremental, porque al hacer la lectura SPI, solo puede leer datos a través del registro SPI_DATA de SPI. Escribir es escribir en un área continua en la memoria. El ancho se escribe en el dirección correspondiente Tenga en cuenta que el desplazamiento de dirección aquí es el ancho de transmisión correspondiente.

6: A través del registro DMA_CHxCTL para configurar la interrupción de finalización de transferencia, la interrupción de finalización de la mitad de la transferencia, el bit de habilitación de la interrupción del error de transmisión, la interrupción se puede configurar para ver si se ha llamado a la interrupción de finalización de la transferencia y si la finalización de la mitad de la transferencia La interrupción es una transferencia La mitad de la interrupción dada, ¿cómo se ve la interrupción del error de transmisión?

7: Configure la dirección base del periférico a través del registro DMA_CHxPADDR. En el caso de SPI Flash, la dirección base del periférico es el registro de datos SPI_DATA, este registro es de 32 bits, esto puede considerar el número máximo de bits a transmitir.

8: Configure la dirección base de la memoria a través del registro DMA_CHxMADDR.

9: Configure el diseño y el monto total de la transferencia a través del registro DMA_CHxCNT.

10: Establezca el bit CHEN del registro DMA_CHxCTL en 1 para habilitar el canal DMA.

 

Interrumpir:

Cada canal DMA tiene una interrupción dedicada. Hay tres tipos de eventos de interrupción: transmisión completa, media transmisión completa y error de transmisión. Cada evento de interrupción tiene un bit de bandera dedicado en el registro DMA_INTF, un bit de borrado dedicado en el registro DMA_INTC y un bit de habilitación dedicado en el registro DMA_CHxCTL.

La interrupción es relativamente fácil de entender. Después de todo, las interrupciones son comunes en el desarrollo integrado.

Si desea utilizar interrupciones aquí,

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

 

Luego implemente el controlador de interrupciones en 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);
    }
}

Preste atención a qué canal usa para lograr qué canal de interrupción. O debe tener el hábito de volver a verificar después de escribir el código de interrupción.

 

Asignación de solicitudes de DMA

Varias solicitudes de periféricos se asignan al mismo canal DMA. Estas señales de solicitud entran en DMA después de la lógica OR. Configurando el registro del periférico correspondiente, cada solicitud de periférico se puede activar o desactivar de forma independiente. El usuario debe asegurarse de que solo se habilite una solicitud de periférico en el mismo canal al mismo tiempo.

La captura de pantalla aquí es para ilustrar qué función DMA periférica desea usar, o qué controlador DMA periférico va a escribir o leer, y qué DMA y canal está buscando que se pueda usar para este periférico.

Por ejemplo, si quiero usar DMA para leer los datos de SPI1, debe usar el canal DMA0 CH3, porque puede ver que solo el canal 3 DMA0 admite SPI1 R.

A continuación se muestra un ejemplo de la operación de SPI + DMA: antes era pura operación de SPI, ahora es la operación de leer el flash ni de la interfaz spi a través de SPI + DMA.

Se utiliza flash GD

La interfaz para leer datos flash con SPI puro antes es:

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

Se puede ver que después de que se envían el comando de lectura y la dirección, hay un ciclo while, que es el ciclo while para leer los datos. Se puede ver que dicho ciclo while parece llevar demasiado tiempo.

Sin embargo, la tecnología puede ser más que solo mirar. Puede leer, digamos, 1M de datos de esta manera para ver cuánto tiempo lleva y luego usar el siguiente método SPI + DMA para leer y comparar:

La siguiente función es una modificación realizada utilizando la función anterior. Por supuesto, la modificación principal es la parte del bucle while. Usamos SPI puro para enviar el comando de lectura y la dirección de envío.

Echemos un vistazo mientras recibimos datos.

Si queremos usar SPI + DMA para leer, también podríamos mirar SEPC y ver si hay alguna introducción a DMA en la introducción de SPI.

La captura de pantalla anterior es la introducción a DMA en la descripción de la interfaz SPI

Esto significa principalmente que si desea utilizar DMA para transferir datos SPI TX y RX, debe habilitar DMA en el modo SPI, como se explica en el siguiente código:

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

Aquí, cuando se inicializa SPI, la inicialización de DMA CH3, el reloj y la IRQ de DMA0 CH3 se llamarán al mismo tiempo.

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

Bien, la inicialización ha terminado, echemos un vistazo a nuestra operación de lectura, aquí es para usar DMA para leer los datos y luego imprimir los datos directamente.

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

Principalmente la siguiente parte:

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

 

Vemos que la función anterior enviará 0xFF al leer, y luego puedo obtener los datos, aquí leo la introducción de los internautas

Aquí puede configurar el modo SPI trans en modo RECIBIR SOLAMENTE, por lo que no necesita enviar 0xff, puede usar DMA directamente para leer.

Realmente agradezco a este internauta por la dirección de su blog https://blog.csdn.net/chenwei2002/article/details/49722373

Debido a que usó STM32, lo configuré mal al configurar el modo de transmisión SPI, se usó

Piense en esta función como un ajuste para recibir solo

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

En realidad, no es él, configurado en Recibir solo es

spi_init_struct.trans_mode           = SPI_TRANSMODE_RECEIVEONLY;

 

, Y luego escriba el registro SPI correspondiente

En cuanto a while (g_dmacomplete_flag == 0); esta oración, establezco esta variable en 1 en la interrupción

Así que aquí hay que esperar a que se complete la operación de DMA antes de tirar de la selección de chip hacia arriba, de lo contrario habrá un problema de errores de lectura

 

En general, los ejemplos introducidos son mucho más superficiales que la teoría anterior, de hecho, en el proceso de implementación de los ejemplos, o en el proceso de exploración, encontré más problemas de los que imaginaba.

Afortunadamente, la transmisión de datos ahora se puede lograr a través de DMA.

Usaré SPI + DMA y SPI puro para hacer una comparación más adelante, ¿cuánta velocidad se puede mejorar?

 

 

 

Supongo que te gusta

Origin blog.csdn.net/yangkunhenry/article/details/104741475
Recomendado
Clasificación