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

Recientemente cambié a los MCU SM32H7 recién producidos y actualmente estoy en el proceso de migrar parte de mi código antiguo 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 de DMA, que estaba ausente en los F7, F4 o F3 más antiguos con los que solía trabajar. En los últimos modelos, el mapeo de canales DMA se logró ingresando 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 MCU F7, esto correspondería a la solicitud SPI1 TX DMA: ingrese la descripción de la imagen aquísegún tengo entendido, el mapeo de canal DMA a DMA ya no está "cableado" y puede ser reconfigurado manualmente en la serie H7. Como dice el manual, DMAMux1 debe usarse para enrutar una línea de solicitud de DMA al canal DMA . Desafortunadamente, la configuración de DMAMUX se describe bastante mal en el manual de referencia. No he logrado comprender cómo exactamente un flujo DMA, un periférico y un canal DMA están interconectados a través del multiplexor.Abajo hay un fragmento de código que debería, idealmente,

  1. Configurar SPI1.
  2. Configure el DMA.
  3. Habilite la transmisión 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. El código completo también tiene configuración de PLL, que no está incluida en el fragmento anterior (la inicialización de PLL funciona bien, ya que puedo sondear el reloj del sistema de 400 MHz en el pin MCO).

La configuración del multiplexor DMA está, obviamente, 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 ejecutar este MWE, seré más o menos capaz de 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 sobre DMA en los microcontroladores de la serie H7. Ahora puedo hacer que SPI funcione sin DMA en el H7:

#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 anterior esencialmente hace tres cosas:

  1. ConfigureHSIinicializa el reloj HSI (he reducido la velocidad del reloj para poder hacer algunas pruebas con un osciloscopio USB lento que actualmente tengo en mis manos).
  2. InitializeMCOmuestra la salida del reloj principal (solo para asegurarse de que el reloj está configurado correctamente).
  3. InitializeMasterTxSPIconfigura el SPI y envía un mensaje de tres bytes.

Definitivamente puedo ver que se envía ese mensaje en mi osciloscopio: ingrese la descripción de la imagen aquíla base de tiempo es 200 ns/div, como referencia.

Por otro lado, si trato de rehacer todo a través de DMA, no veo ningún resultado. Así es como se ve mi código SPI basado en DMA:

#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 InitializeDMAfunción y la transferencia DMA se inicia a través del DMA1_Stream0->CR |= DMA_SxCR_ENcomando (como fue el caso en la serie anterior de MCU). Entonces, lamentablemente, todavía no puedo iniciar el SPI a través de DMA en los H7. Cualquier ayuda sería muy apreciada.

¿Ha intentado usar el SPI en el H7 en modo esclavo con DMA? Modifiqué tu ejemplo publicado, pero no funciona. Saludos Karsten
Estimado Karsten. Intente ejecutar el código de mi propia respuesta a continuación. La configuración de DMA es muy similar a la de la serie STM32 anterior, con la única diferencia de que también debe configurar el canal DMAMUX adecuado de acuerdo con el manual de referencia.

Respuestas (2)

Entonces, en realidad logré hacer que SPI DMA se ejecutara. Publicando 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 DMAMUX1 0 a 7 están conectados a los canales DMA1 0 a 7
  • Los canales 8 a 15 de DMAMUX1 están conectados a los canales 0 a 7 de DMA2
  • Los canales DMAMUX2 del 0 al 7 están conectados a los canales BDMA del 0 al 7

Estos, junto con la asignación de entradas del multiplexor a las tablas de recursos, son las claves para que DMA funcione (al menos de la misma manera que en la serie anterior de MCUS). Por ejemplo, SPI1_TX está en la entrada MUX de solicitud de DMA número 38 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 quiera, solo necesitan seguir la regla:

  • DMA1_Transmisión_x -> DMAMUX1_Canal_x
  • DMA2_Stream_x -> DMAMUX1_Channel_(x+8)

Entonces, así es como esencialmente vincula un periférico a una transmisión DMA a través de un canal particular de DMAMUX.

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

  • No olvide configurar el SPI_CR1_CSTARTbit (esto es algo nuevo para los H7).
  • Cuidado con el SPI->CR2registro. Si le escribe un valor, la transferencia SPI se detendrá después de 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).

Si bien todo me parece un poco obvio ahora, aún diré que falta algo de información en la referencia. La operación DMA en los manuales de series anteriores se describía un poco 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 demás). 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 adecuado).

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

Salud.

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

ACTUALIZACIÓN 1: he inicializado con éxito DMA para el temporizador TIM2:

Configuración de TIM2 DMA para stm32h7

¿Podría dar más detalles sobre eso? ¿No es eso lo que // 5. Use DMAMux1 to route a DMA request line to the DMA channel. DMAMUX1_Channel0->CCR = (37u << DMAMUX_CxCR_DMAREQ_ID_Pos);hace la línea :?
@KR sí, lo es. DMAMUX no necesita ninguna configuración adicional. Si está interesado, puede consultar mi pregunta con la configuración DMA (editar respuesta)
Bien, intentaré reconstruir este proyecto tan pronto como pueda tener en mis manos el hardware. Mientras tanto, ¿podría aclarar acerca de los bits SYNC_ID en el registro DMAMUX-CxCR y el registro del generador de solicitudes (DMAMUX_RGxCR)? ¿Cuándo uno necesita usar esos? Además, ¿cómo se "adjunta" un flujo DMA a un periférico en particular? En mi ejemplo anterior, elegí DMA2_Stream3 y DMAMUX1_Channel0, pero no hay ningún código que los vincule.
@KR es solo dmamux1_channel0 conectado a dma1_stream0, dmamux1_channel8 a dma2_stream0 y así sucesivamente. Creo que necesitaban iniciar sincrónicamente más de un dma desde una fuente de eventos. Si te interesa mi código actualizado, púlsame con el palo. te lo enviaré