Configuración del multiplexor de solicitud DMA en una MCU STM32H7

2

Recientemente he cambiado a las MCU SM32H7 recién producidas y actualmente estoy en el proceso de migrar algunos de mis códigos antiguos a ellos (quiero ver cómo manejan algunas aplicaciones que requieren un poco más de velocidad). Los H7 cuentan con un multiplexor de solicitud DMA, que estaba ausente en los F7, F4 o F3 más antiguos con los que solía trabajar. En los últimos modelos, la asignación de canales DMA se realizó al ingresar un valor correcto en el registro de control DMA_x Stream_y. Por ejemplo

DMA2_Stream3->CR |= (0x3 << DMA_SxCR_CHSEL_Pos);

seleccionaría el tercer canal para DM2 Stream3 (en el caso de F7 MCU, esto correspondería a la solicitud de SPI1 DMA TX: Segúntengoentendido,laasignacióndecanalesDMAstream-to-DMAyanoestá"conectada" y se puede reconfigurar manualmente en la serie H7. Como indica el manual, DMAMux1 debe usarse para enrutar una línea de solicitud de DMA al canal de DMA . Desafortunadamente, la configuración de DMAMUX se describe bastante mal en el manual de referencia. No he logrado comprender cómo se interconectan exactamente un flujo DMA, un periférico y un canal DMA a través del multiplexor. A continuación se muestra un fragmento de código que, idealmente, debería

  1. Configurar SPI1.
  2. Configura el DMA.
  3. Habilite la secuencia DMA para una transferencia SPI TX.

    RCC->AHB4ENR |= RCC_AHB4ENR_GPIOAEN;   // Enable usage of GPIOA
    RCC->APB2ENR |= RCC_APB2ENR_SPI1EN;
    
    GPIOA->MODER &= ~GPIO_MODER_MODER5;
    GPIOA->MODER |= GPIO_MODER_MODER5_1;   // Alternate function for SPI1 SCK on PA5
    GPIOA->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR5;   // High Speed on PA5
    GPIOA->AFR[0] |= (0x05 << 5 * 4);   // AFRL selected AF5 (SPI1 SCK) for PA5
    
    GPIOA->MODER &= ~GPIO_MODER_MODER6;
    GPIOA->MODER |= GPIO_MODER_MODER6_1;   // Alternate function for SPI1 MISO on PA6
    GPIOA->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR6;   // High Speed on PA6
    GPIOA->AFR[0] |= (0x05 << 6 * 4);   // AFRL selected AF5 (SPI1 MISO) for PA6
    
    GPIOA->MODER &= ~GPIO_MODER_MODER7;
    GPIOA->MODER |= GPIO_MODER_MODER7_1;   // Alternate function for SPI1 MOSI on PA7
    GPIOA->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR7;   // High Speed on PA7
    GPIOA->AFR[0] |= (0x05 << 7 * 4);   // AFRL selected AF5 (SPI1 MOSI) for PA7
    
    GPIOA->MODER &= ~GPIO_MODER_MODER4;
    GPIOA->MODER |= GPIO_MODER_MODER4_1;   // Alternate function for SPI1 NSS on PA7
    GPIOA->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR4;   // High Speed on PA7
    GPIOA->AFR[0] |= (0x05 << 4 * 4);   // AFRL selected AF5 (SPI1 NSS) for PA7
    
    GPIOA->PUPDR |= GPIO_PUPDR_PUPDR4_0;  // Ensure all pull up pull down resistors are enabled
    GPIOA->PUPDR &= ~GPIO_PUPDR_PUPDR5;   // Ensure all pull up pull down resistors are disabled
    GPIOA->PUPDR &= ~GPIO_PUPDR_PUPDR6;   // Ensure all pull up pull down resistors are disabled
    GPIOA->PUPDR &= ~GPIO_PUPDR_PUPDR7;   // Ensure all pull up pull down resistors are disabled
    
    SPI1->CFG1 = (1u << SPI_CFG1_MBR_Pos) | // Master baud rate: master clock / 2
                     (7u << SPI_CFG1_CRCSIZE_Pos) | // Length of CRC frame
                     SPI_CFG1_TXDMAEN | SPI_CFG1_RXDMAEN | // Enable RX/TX DMA
                     (7u << SPI_CFG1_FTHLV_Pos) | // FIFO threshold level
                     (7u << SPI_CFG1_DSIZE_Pos) //Number of bits in at single SPI data frame
                     ;
    
    SPI1->CFG2 = SPI_CFG2_SSOE | // SS output enable
                 SPI_CFG2_MASTER // SPI Master
                 ;       
    
    RCC->AHB1ENR |= RCC_AHB1ENR_DMA2EN;   // DMA2 clock enable;
    
    DMA2_Stream3->CR = 0u;
    DMA2_Stream3->PAR = (uint32_t) &(SPI1->TXDR);
    DMA2_Stream3->M0AR = (uint32_t) &(Data_Buffer[0]);
    DMA2_Stream3->CR |= (1u << DMA_SxCR_DIR_Pos);
    DMA2_Stream3->CR |= DMA_SxCR_MINC; 
    DMA2_Stream3->CR |= DMA_SxCR_PL;
    DMA2_Stream3->NDTR = 1000;
    
    // 5. Use DMAMux1 to route a DMA request line to the DMA channel.
    DMAMUX1_Channel0->CCR  = (37u << DMAMUX_CxCR_DMAREQ_ID_Pos);
    
    SPI1->CR1 |= SPI_CR1_SPE;
    DMA2_Stream3->CR |= DMA_SxCR_EN;
    

Este código se compila y puedo cargarlo en un MCU STM32H753ZIT6. Además, el código completo tiene una configuración PLL, que no está incluida en el fragmento de código anterior (la inicialización de PLL funciona bien, ya que puedo probar el reloj del sistema de 400 MHz en el pin MCO).

La configuración del multiplexor DMA está, sin darse cuenta, incompleta. Solo he seleccionado una asignación adecuada (al menos creo que es adecuada) de entradas de multiplexor a recursos. Ni siquiera estoy seguro de si el canal del multiplexor es correcto (o si su elección es arbitraria en las MCU H7).

Entonces, mi pregunta es: ¿cuál sería la forma correcta de configurar el multiplexor DMA para la transferencia SPI TX? Si puedo hacer funcionar este MWE, podré más o menos terminar de migrar el resto de mi código.

Gracias de antemano.

ACTUALIZAR :

Entonces, he estado tratando de seguir el consejo del usuario 9403409, pero desafortunadamente no pude ir muy lejos. Todavía no puedo hacer que SPI funcione con DMA en los microcontroladores de la serie H7. Ahora puedo hacer que SPI funcione sin DMA en el H7, simplemente bien:

#include "stm32h7xx.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static void InitializeMCO(void);
static void ConfigureHSI(void);
static void InitializeMasterTxSPI(void);
uint8_t s_TransferBuffer[10];

int main()
{        
    s_TransferBuffer[0] = 0xAA;
    s_TransferBuffer[1] = 0xBB;
    s_TransferBuffer[2] = 0xCC;

    ConfigureHSI();
    InitializeMCO();
    InitializeMasterTxSPI();
    while(1){};
}

static void ConfigureHSI(void)
{
    PWR->CR3 |= PWR_CR3_SCUEN;
    PWR->D3CR |= (PWR_D3CR_VOS_1 | PWR_D3CR_VOS_0);
        while ((PWR->D3CR & PWR_D3CR_VOSRDY) != PWR_D3CR_VOSRDY) 
        {
        };

    FLASH->ACR = FLASH_ACR_LATENCY_2WS;

    RCC->CR |= RCC_CR_HSION;
    while ((RCC->CR & RCC_CR_HSIRDY) != RCC_CR_HSIRDY)
    {
    };

    RCC->PLLCKSELR = (4u << RCC_PLLCKSELR_DIVM1_Pos) |
                         (32u << RCC_PLLCKSELR_DIVM2_Pos) |
                         (32u << RCC_PLLCKSELR_DIVM3_Pos) |
                         RCC_PLLCKSELR_PLLSRC_HSI
                         ;

    RCC->PLLCFGR   =  RCC_PLLCFGR_DIVR1EN |
                      RCC_PLLCFGR_DIVQ1EN |
                      RCC_PLLCFGR_DIVP1EN |
                      (2u << RCC_PLLCFGR_PLL1RGE_Pos)  |
                      (1u << RCC_PLLCFGR_PLL1VCOSEL_Pos) 
                      ;

    RCC->PLL1DIVR = ((2u - 1u) << RCC_PLL1DIVR_R1_Pos) |          
        ((2u - 1u) << RCC_PLL1DIVR_Q1_Pos) |
        ((2u - 1u) << RCC_PLL1DIVR_P1_Pos) |
        ((10u - 1u) << RCC_PLL1DIVR_N1_Pos)  // Reducing the clock rate so I can probe it with my slow USB scope
        ;

    RCC->D1CFGR = RCC_D1CFGR_D1CPRE_DIV1;
    RCC->D1CFGR = RCC_D1CFGR_HPRE_DIV2 | 
                  RCC_D1CFGR_D1PPRE_DIV2;
    RCC->D2CFGR = RCC_D2CFGR_D2PPRE1_DIV2 |
                  RCC_D2CFGR_D2PPRE2_DIV2;
    RCC->D3CFGR = RCC_D3CFGR_D3PPRE_DIV2;

    RCC->CR |= RCC_CR_PLL1ON;
    while (!(RCC->CR & RCC_CR_PLLRDY))
    {
    };

    RCC->CFGR |= (1u << 25);
    RCC->CFGR |= RCC_CFGR_SW_PLL1;
    while (!(RCC->CFGR & RCC_CFGR_SWS_PLL1))
    {
    };
}

/* Displays MCO on PC9 */
static void InitializeMCO(void)
{
    RCC->CFGR |= RCC_CFGR_MCO2;
        RCC->CFGR |= (15 << 25); // Reducing the output so I can probe it with my slow USB scope

    RCC->AHB4ENR &= ~RCC_AHB4ENR_GPIOCEN;
    RCC->AHB4ENR |= RCC_AHB4ENR_GPIOCEN;

    GPIOC->MODER &= ~GPIO_MODER_MODER9;
    GPIOC->MODER |= GPIO_MODER_MODER9_1;

    GPIOC->OTYPER &= ~GPIO_OTYPER_OT_9;
    GPIOC->PUPDR &= ~GPIO_PUPDR_PUPDR9;

    GPIOC->OSPEEDR &= ~GPIO_OSPEEDER_OSPEEDR9;
    GPIOC->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR9;

    GPIOC->AFR[0] &= ~GPIO_AFRL_AFRL0;
}

static void InitializeMasterTxSPI(void)
{
    RCC->AHB4ENR |= RCC_AHB4ENR_GPIOAEN;   // Enable usage of GPIOA

    GPIOA->MODER &= ~GPIO_MODER_MODER5;
    GPIOA->MODER |= GPIO_MODER_MODER5_1;   // Alternate function for SPI1 SCK on PA5
    GPIOA->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR5;   // High Speed on PA5
    GPIOA->AFR[0] |= (0x05 << 5 * 4);   // AFRL selected AF5 (SPI1 SCK) for PA5

    GPIOA->MODER &= ~GPIO_MODER_MODER6;
    GPIOA->MODER |= GPIO_MODER_MODER6_1;   // Alternate function for SPI1 MISO on PA6
    GPIOA->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR6;   // High Speed on PA6
    GPIOA->AFR[0] |= (0x05 << 6 * 4);   // AFRL selected AF5 (SPI1 MISO) for PA6

    GPIOA->MODER &= ~GPIO_MODER_MODER7;
    GPIOA->MODER |= GPIO_MODER_MODER7_1;   // Alternate function for SPI1 MOSI on PA7
    GPIOA->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR7;   // High Speed on PA7
    GPIOA->AFR[0] |= (0x05 << 7 * 4);   // AFRL selected AF5 (SPI1 MOSI) for PA7

    GPIOA->MODER &= ~GPIO_MODER_MODER4;
    GPIOA->MODER |= GPIO_MODER_MODER4_1;   // Alternate function for SPI1 NSS on PA7
    GPIOA->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR4;   // High Speed on PA7
    GPIOA->AFR[0] |= (0x05 << 4 * 4);   // AFRL selected AF5 (SPI1 NSS) for PA7

    GPIOA->PUPDR &= ~GPIO_PUPDR_PUPDR4;  // Ensure all pull up pull down resistors are enabled
    GPIOA->PUPDR &= ~GPIO_PUPDR_PUPDR5;   // Ensure all pull up pull down resistors are disabled
    GPIOA->PUPDR &= ~GPIO_PUPDR_PUPDR6;   // Ensure all pull up pull down resistors are disabled
    GPIOA->PUPDR &= ~GPIO_PUPDR_PUPDR7;   // Ensure all pull up pull down resistors are disabled

    RCC->APB2ENR |= RCC_APB2ENR_SPI1EN;

    SPI1->CR1 = 0;

    SPI1->CFG1 = (3u << SPI_CFG1_MBR_Pos) |
                 (7u << SPI_CFG1_CRCSIZE_Pos) |
                 //SPI_CFG1_TXDMAEN | // SPI_CFG1_RXDMAEN |
                 (7u << SPI_CFG1_FTHLV_Pos) |
                 (7u << SPI_CFG1_DSIZE_Pos)
                 ;

    SPI1->CFG2 = SPI_CFG2_SSOE |
                 SPI_CFG2_MASTER 
                 ;      

    SPI1->CR2 |= 3;
    SPI1->CR1 |= SPI_CR1_SPE;
    SPI1->CR1 |= SPI_CR1_CSTART;

    for (uint32_t i=0; i<3; i++)
    {
            while ((SPI1->SR & SPI_SR_TXP) != SPI_SR_TXP){};
            *((__IO uint32_t *)&SPI1->TXDR) = *((uint32_t *)&s_TransferBuffer[i]);
    }
}

El código de arriba esencialmente hace tres cosas:

  1. ConfigureHSI inicializa el reloj HSI (he reducido la velocidad del reloj para poder hacer un sondeo con un alcance USB lento que actualmente tengo en mis manos).
  2. InitializeMCO muestra la salida del reloj principal (solo para asegurarse de que el reloj esté configurado correctamente).
  3. InitializeMasterTxSPI configura el SPI y envía un mensaje de tres bytes.

Definitivamente puedo ver ese mensaje enviado a mi alcance: Labasedetiempoes200ns/div,parareferencia.

Porotrolado,siintentorehacertodoatravésdeDMA,noveoningúnresultado.AsíescomosevemicódigoSPIbasadoenDMA:

#include"stm32h7xx.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static void InitializeMCO(void);
static void ConfigureHSI(void);
static void InitializeDMA(void);
static void InitializeMasterTxSPI(void);
uint8_t s_TransferBuffer[10];

int main()
{        
    s_TransferBuffer[0] = 0xAA;
    s_TransferBuffer[1] = 0xBB;
    s_TransferBuffer[2] = 0xCC;

    ConfigureHSI();
    InitializeMCO();
    InitializeDMA();
    InitializeMasterTxSPI();
    while(1){};
}

/* Initializes the MCU clock */
static void ConfigureHSI(void)
{
    PWR->CR3 |= PWR_CR3_SCUEN;
    PWR->D3CR |= (PWR_D3CR_VOS_1 | PWR_D3CR_VOS_0);
        while ((PWR->D3CR & PWR_D3CR_VOSRDY) != PWR_D3CR_VOSRDY) 
        {
        };

    FLASH->ACR = FLASH_ACR_LATENCY_2WS;

    RCC->CR |= RCC_CR_HSION;
    while ((RCC->CR & RCC_CR_HSIRDY) != RCC_CR_HSIRDY)
    {
    };

    RCC->PLLCKSELR = (4u << RCC_PLLCKSELR_DIVM1_Pos) |
                         (32u << RCC_PLLCKSELR_DIVM2_Pos) |
                         (32u << RCC_PLLCKSELR_DIVM3_Pos) |
                         RCC_PLLCKSELR_PLLSRC_HSI
                         ;

    RCC->PLLCFGR   =  RCC_PLLCFGR_DIVR1EN |
                      RCC_PLLCFGR_DIVQ1EN |
                      RCC_PLLCFGR_DIVP1EN |
                      (2u << RCC_PLLCFGR_PLL1RGE_Pos)  |
                      (1u << RCC_PLLCFGR_PLL1VCOSEL_Pos) 
                      ;

    RCC->PLL1DIVR = ((2u - 1u) << RCC_PLL1DIVR_R1_Pos) |          
        ((2u - 1u) << RCC_PLL1DIVR_Q1_Pos) |
        ((2u - 1u) << RCC_PLL1DIVR_P1_Pos) |
        ((10u - 1u) << RCC_PLL1DIVR_N1_Pos)  // Reducing the clock rate so I can probe it with my slow USB scope
        ;

    RCC->D1CFGR = RCC_D1CFGR_D1CPRE_DIV1;
    RCC->D1CFGR = RCC_D1CFGR_HPRE_DIV2 | 
                  RCC_D1CFGR_D1PPRE_DIV2;
    RCC->D2CFGR = RCC_D2CFGR_D2PPRE1_DIV2 |
                  RCC_D2CFGR_D2PPRE2_DIV2;
    RCC->D3CFGR = RCC_D3CFGR_D3PPRE_DIV2;

    RCC->CR |= RCC_CR_PLL1ON;
    while (!(RCC->CR & RCC_CR_PLLRDY))
    {
    };

    RCC->CFGR |= (1u << 25);
    RCC->CFGR |= RCC_CFGR_SW_PLL1;
    while (!(RCC->CFGR & RCC_CFGR_SWS_PLL1))
    {
    };
}

/* Displays MCO on PC9 */
static void InitializeMCO(void)
{
    RCC->CFGR |= RCC_CFGR_MCO2;
    RCC->CFGR |= (15 << 25); // Reducing the output so I can probe it with my slow USB scope

    RCC->AHB4ENR &= ~RCC_AHB4ENR_GPIOCEN;
    RCC->AHB4ENR |= RCC_AHB4ENR_GPIOCEN;

    GPIOC->MODER &= ~GPIO_MODER_MODER9;
    GPIOC->MODER |= GPIO_MODER_MODER9_1;

    GPIOC->OTYPER &= ~GPIO_OTYPER_OT_9;
    GPIOC->PUPDR &= ~GPIO_PUPDR_PUPDR9;

    GPIOC->OSPEEDR &= ~GPIO_OSPEEDER_OSPEEDR9;
    GPIOC->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR9;

    GPIOC->AFR[0] &= ~GPIO_AFRL_AFRL0;
}

static void InitializeDMA()
{
    RCC->AHB2ENR |= (0x7 << 29);  // Enable the SRAM
    RCC->AHB1ENR |= RCC_AHB1ENR_DMA1EN;   // DMA1 clock enable;

    // Set the peripheral and memory addresses:
    DMA1_Stream0->PAR = *((__IO uint32_t *)&SPI1->TXDR);
    DMA1_Stream0->M0AR = *((uint32_t *)&s_TransferBuffer[0]);

    DMA1_Stream0->CR = 0u;
    DMA1_Stream0->CR |= (1u << DMA_SxCR_DIR_Pos); // Memory to peripheral
    DMA1_Stream0->CR |= DMA_SxCR_MINC; // Memory increment mode
    DMA1_Stream0->CR |= (3u << DMA_SxCR_PL_Pos); // Very high priority

    DMA1_Stream0->NDTR = 3; // Number of data

    DMAMUX1_Channel0->CCR  = (38u << DMAMUX_CxCR_DMAREQ_ID_Pos);

}

static void InitializeMasterTxSPI(void)
{
    RCC->AHB4ENR |= RCC_AHB4ENR_GPIOAEN;   // Enable usage of GPIOA

    GPIOA->MODER &= ~GPIO_MODER_MODER5;
    GPIOA->MODER |= GPIO_MODER_MODER5_1;   // Alternate function for SPI1 SCK on PA5
    GPIOA->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR5;   // High Speed on PA5
    GPIOA->AFR[0] |= (0x05 << 5 * 4);   // AFRL selected AF5 (SPI1 SCK) for PA5

    GPIOA->MODER &= ~GPIO_MODER_MODER6;
    GPIOA->MODER |= GPIO_MODER_MODER6_1;   // Alternate function for SPI1 MISO on PA6
    GPIOA->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR6;   // High Speed on PA6
    GPIOA->AFR[0] |= (0x05 << 6 * 4);   // AFRL selected AF5 (SPI1 MISO) for PA6

    GPIOA->MODER &= ~GPIO_MODER_MODER7;
    GPIOA->MODER |= GPIO_MODER_MODER7_1;   // Alternate function for SPI1 MOSI on PA7
    GPIOA->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR7;   // High Speed on PA7
    GPIOA->AFR[0] |= (0x05 << 7 * 4);   // AFRL selected AF5 (SPI1 MOSI) for PA7

    GPIOA->MODER &= ~GPIO_MODER_MODER4;
    GPIOA->MODER |= GPIO_MODER_MODER4_1;   // Alternate function for SPI1 NSS on PA4
    GPIOA->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR4;   // High Speed on PA4
    GPIOA->AFR[0] |= (0x05 << 4 * 4);   // AFRL selected AF5 (SPI1 NSS) for PA4

    GPIOA->PUPDR &= ~GPIO_PUPDR_PUPDR4;  // Ensure all pull up pull down resistors are enabled
    GPIOA->PUPDR &= ~GPIO_PUPDR_PUPDR5;   // Ensure all pull up pull down resistors are disabled
    GPIOA->PUPDR &= ~GPIO_PUPDR_PUPDR6;   // Ensure all pull up pull down resistors are disabled
    GPIOA->PUPDR &= ~GPIO_PUPDR_PUPDR7;   // Ensure all pull up pull down resistors are disabled

    RCC->APB2ENR |= RCC_APB2ENR_SPI1EN;

    SPI1->CR1 = 0;
    SPI1->CFG1 = (3u << SPI_CFG1_MBR_Pos) |
                 (7u << SPI_CFG1_CRCSIZE_Pos) |
                 SPI_CFG1_TXDMAEN | // SPI_CFG1_RXDMAEN |
                 (7u << SPI_CFG1_FTHLV_Pos) |
                 (7u << SPI_CFG1_DSIZE_Pos)
                 ;
    SPI1->CFG2 = SPI_CFG2_SSOE |
                 SPI_CFG2_MASTER 
                 ;      

    //SPI1->CR2 |= 3;
    SPI1->CR1 |= SPI_CR1_SPE;
    SPI1->CR1 |= SPI_CR1_CSTART;

    DMA1_Stream0->CR |= DMA_SxCR_EN;
}

Esencialmente, es lo mismo, las únicas diferencias son que hay una función InitializeDMA y la transferencia DMA se inicia a través del comando DMA1_Stream0->CR |= DMA_SxCR_EN (como fue el caso en las series anteriores de MCU). Lamentablemente, todavía no puedo iniciar el SPI a través de DMA en los H7. Cualquier ayuda sería apreciada grandemente.

    
pregunta K.R.

2 respuestas

2

Por lo tanto, he logrado que SPI DMA se ejecute. Publicar mi código de trabajo a continuación:

#include "stm32h7xx.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static void InitializeMCO(void);
static void ConfigureHSI(void);
static void InitializeDMA(void);
static void InitializeMasterTxSPI(void);
const uint8_t aTxBuffer[] = "Simple SPI message";

int main()
{
    ConfigureHSI();
    InitializeMCO();
    InitializeDMA();
    InitializeMasterTxSPI();

    while (1)
    {
            /* Delay added to distinguish between the SPI messages: */
            while(DMA2_Stream4->NDTR != 0) asm("nop");
            for(uint32_t i=0; i<0xBF; i++) asm("nop");

            //DMA2_Stream4->CR &= ~DMA_SxCR_EN;
            DMA2->HIFCR |= DMA_HIFCR_CTCIF4 | DMA_HIFCR_CHTIF4 | DMA_HIFCR_CTEIF4 | DMA_HIFCR_CDMEIF4 | DMA_HIFCR_CFEIF4;
            //DMA2_Stream4->PAR = (uint32_t) &(SPI1->TXDR);
            DMA2_Stream4->M0AR = (uint32_t ) &(aTxBuffer[0]);
            DMA2_Stream4->NDTR = 0x12;

            DMA2_Stream4->CR |= DMA_SxCR_EN;
    };
}

/* Initializes the MCU clock */
static void ConfigureHSI(void)
{
    PWR->CR3 |= PWR_CR3_SCUEN;
    PWR->D3CR |= (PWR_D3CR_VOS_1 | PWR_D3CR_VOS_0);
    while ((PWR->D3CR & PWR_D3CR_VOSRDY) != PWR_D3CR_VOSRDY)
    {
    };

    FLASH->ACR = FLASH_ACR_LATENCY_2WS;

    RCC->CR |= RCC_CR_HSION;
    while ((RCC->CR & RCC_CR_HSIRDY) != RCC_CR_HSIRDY)
    {
    };

    RCC->PLLCKSELR = (4u << RCC_PLLCKSELR_DIVM1_Pos) |
                         (32u << RCC_PLLCKSELR_DIVM2_Pos) | 
                         (32u << RCC_PLLCKSELR_DIVM3_Pos) | 
                         RCC_PLLCKSELR_PLLSRC_HSI;

    RCC->PLLCFGR = RCC_PLLCFGR_DIVR1EN | 
                       RCC_PLLCFGR_DIVQ1EN | 
                       RCC_PLLCFGR_DIVP1EN | 
                       (2u << RCC_PLLCFGR_PLL1RGE_Pos) | 
                       (1u << RCC_PLLCFGR_PLL1VCOSEL_Pos);

    RCC->PLL1DIVR = ((2u - 1u) << RCC_PLL1DIVR_R1_Pos) | 
                        ((2u - 1u) << RCC_PLL1DIVR_Q1_Pos) | 
                        ((2u - 1u) << RCC_PLL1DIVR_P1_Pos) | 
                        ((10u - 1u) << RCC_PLL1DIVR_N1_Pos)   // Reducing the clock rate so I can probe it with my slow USB scope
            ;

    RCC->D1CFGR = RCC_D1CFGR_D1CPRE_DIV1;
    RCC->D1CFGR = RCC_D1CFGR_HPRE_DIV2 | RCC_D1CFGR_D1PPRE_DIV2;
    RCC->D2CFGR = RCC_D2CFGR_D2PPRE1_DIV2 | RCC_D2CFGR_D2PPRE2_DIV2;
    RCC->D3CFGR = RCC_D3CFGR_D3PPRE_DIV2;

    RCC->CR |= RCC_CR_PLL1ON;
    while (!(RCC->CR & RCC_CR_PLLRDY))
    {
    };

    RCC->CFGR |= (1u << 25);
    RCC->CFGR |= RCC_CFGR_SW_PLL1;
    while (!(RCC->CFGR & RCC_CFGR_SWS_PLL1))
    {
    };
}

/* Displays MCO on PC9 */
static void InitializeMCO(void)
{
    RCC->CFGR |= RCC_CFGR_MCO2;
    RCC->CFGR |= (15 << 25);   // Reducing the output so I can probe it with my slow USB scope

    RCC->AHB4ENR &= ~RCC_AHB4ENR_GPIOCEN;
    RCC->AHB4ENR |= RCC_AHB4ENR_GPIOCEN;

    GPIOC->MODER &= ~GPIO_MODER_MODER9;
    GPIOC->MODER |= GPIO_MODER_MODER9_1;

    GPIOC->OTYPER &= ~GPIO_OTYPER_OT_9;
    GPIOC->PUPDR &= ~GPIO_PUPDR_PUPDR9;

    GPIOC->OSPEEDR &= ~GPIO_OSPEEDER_OSPEEDR9;
    GPIOC->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR9;

    GPIOC->AFR[0] &= ~GPIO_AFRL_AFRL0;
}

static void InitializeDMA()
{
    RCC->AHB2ENR |= (RCC_AHB2ENR_D2SRAM1EN | RCC_AHB2ENR_D2SRAM2EN | RCC_AHB2ENR_D2SRAM3EN);   // Enable the SRAM
    RCC->AHB1ENR |= RCC_AHB1ENR_DMA2EN;   // DMA2 clock enable;

    // Set the peripheral and memory addresses:
    DMA2_Stream4->PAR = (uint32_t) &(SPI1->TXDR);
    DMA2_Stream4->M0AR = (uint32_t ) &(aTxBuffer[0]);

    DMA2_Stream4->CR = 0;
    DMA2_Stream4->CR |= (1u << DMA_SxCR_DIR_Pos);   // Memory to peripheral
    DMA2_Stream4->CR |= DMA_SxCR_MINC;   // Memory increment mode
    DMA2_Stream4->CR |= (3u << DMA_SxCR_PL_Pos);   // Very high priority

    DMA2_Stream4->NDTR = 0x12; //DMA transfer length

    DMA2_Stream4->CR |= DMA_SxCR_EN; // Enable DMA stream

    DMAMUX1_Channel12->CCR = 0x26;
}

static void InitializeMasterTxSPI(void)
{
    RCC->AHB4ENR |= RCC_AHB4ENR_GPIOAEN;   // Enable usage of GPIOA

    GPIOA->MODER &= ~GPIO_MODER_MODER5;
    GPIOA->MODER |= GPIO_MODER_MODER5_1;   // Alternate function for SPI1 SCK on PA5
    GPIOA->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR5;   // High Speed on PA5
    GPIOA->AFR[0] |= (0x05 << 5 * 4);   // AFRL selected AF5 (SPI1 SCK) for PA5

    GPIOA->MODER &= ~GPIO_MODER_MODER6;
    GPIOA->MODER |= GPIO_MODER_MODER6_1;   // Alternate function for SPI1 MISO on PA6
    GPIOA->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR6;   // High Speed on PA6
    GPIOA->AFR[0] |= (0x05 << 6 * 4);   // AFRL selected AF5 (SPI1 MISO) for PA6

    GPIOA->MODER &= ~GPIO_MODER_MODER7;
    GPIOA->MODER |= GPIO_MODER_MODER7_1;   // Alternate function for SPI1 MOSI on PA7
    GPIOA->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR7;   // High Speed on PA7
    GPIOA->AFR[0] |= (0x05 << 7 * 4);   // AFRL selected AF5 (SPI1 MOSI) for PA7

    GPIOA->MODER &= ~GPIO_MODER_MODER4;
    GPIOA->MODER |= GPIO_MODER_MODER4_1;   // Alternate function for SPI1 NSS on PA4
    GPIOA->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR4;   // High Speed on PA4
    GPIOA->AFR[0] |= (0x05 << 4 * 4);   // AFRL selected AF5 (SPI1 NSS) for PA4

    GPIOA->PUPDR |=  GPIO_PUPDR_PUPDR4;   // Ensure all pull up pull down resistors are enabled
    GPIOA->PUPDR &= ~GPIO_PUPDR_PUPDR5;   // Ensure all pull up pull down resistors are disabled
    GPIOA->PUPDR &= ~GPIO_PUPDR_PUPDR6;   // Ensure all pull up pull down resistors are disabled
    GPIOA->PUPDR &= ~GPIO_PUPDR_PUPDR7;   // Ensure all pull up pull down resistors are disabled

    RCC->APB2ENR |= RCC_APB2ENR_SPI1EN;

    SPI1->CR1 = SPI_CR1_SSI;

    SPI1->CFG1 = (2u << SPI_CFG1_MBR_Pos) | 
                     (7u << SPI_CFG1_CRCSIZE_Pos) |
                     SPI_CFG1_TXDMAEN | // SPI_CFG1_RXDMAEN |
                     (7u << SPI_CFG1_FTHLV_Pos) | 
                     (7u << SPI_CFG1_DSIZE_Pos);
    SPI1->CFG2 = SPI_CFG2_SSM | SPI_CFG2_MASTER;

    SPI1->CR1 |= SPI_CR1_SPE;
    SPI1->CR1 |= SPI_CR1_CSTART;
}

Ahora, la funcionalidad básica de DMAMUX no es demasiado difícil, considerando todo. El manual establece que:

  • Los canales 0 a 7 de DMAMUX1 están conectados a los canales 0 a 7 de DMA1
  • Los canales 8 a 15 de DMAMUX1 están conectados a los canales 0 a 7 de DMA2
  • Los canales DMAMUX2 0 a 7 están conectados a los canales BDMA 0 a 7

Estas, junto con la asignación de entradas de multiplexor a las tablas de recursos, son las claves para que DMA se ejecute (al menos se ejecuta de la misma manera que en la serie anterior de MCUS). Por ejemplo, SPI1_TX se encuentra en la 38ª entrada MUX de solicitud DMA de DMAMUX1 (consulte la tabla 110 en el manual de referencia). Esto significa que puedo emplear DMA1 o DMA2 (y no BDMA, ya que está vinculado a DMAMUX2). Puedo elegir cualquier transmisión que desee, solo deben seguir la regla:

  • DMA1_Stream_x - > DMAMUX1_Channel_x
  • DMA2_Stream_x - > DMAMUX1_Channel_ (x + 8)

Por lo tanto, esta es la forma en que esencialmente vincula un periférico a un flujo DMA a través de un canal en particular de DMAMUX.

Un par de cosas a tener en cuenta también:

  • No olvides configurar el bit SPI_CR1_CSTART (esto es algo nuevo para los H7).
  • Tenga cuidado con el registro SPI->CR2 . Si le escribe un valor, la transferencia de SPI se detendrá una vez que haya comenzado el número predefinido de transferencias de datos. Un bucle infinito, como se presenta en mi ejemplo, no funcionará si se establece CR2 (solo obtendremos una única transferencia SPI completa).

Aunque todo esto me parece un poco obvio ahora, todavía diré que la información en la referencia es algo deficiente. La operación DMA en los manuales de series anteriores se describió ligeramente mejor (al menos en mi opinión). Por ejemplo, todavía no sé cómo (y cuándo) utilizar la funcionalidad restante de DMAMUX (como los generadores de solicitudes y otras cosas). Además, no estoy muy seguro de cómo se implementan las transferencias de memoria a memoria (probablemente tendré que aprender eso cuando llegue el momento de la propiedad).

Espero que esto ayude a cualquiera que quiera profundizar en la programación ARM.

Saludos.

    
respondido por el K.R.
1

Su configuración de DMAMUX es correcta, solo necesita inicializar el registro DMAMUX1_CCR_DMAREQ_ID.

ACTUALIZACIÓN 1: He inicializado correctamente el DMA para el temporizador TIM2:

Configuración de DMA TIM2 para stm32h7

    
respondido por el stronk_kisik

Lea otras preguntas en las etiquetas