Настройка мультиплексора запросов DMA на микроконтроллере STM32H7

Недавно я переключился на недавно выпущенные микроконтроллеры SM32H7 и в настоящее время переношу на них часть своего старого кода (я хочу посмотреть, как они справляются с некоторыми приложениями, требующими немного большей скорости). H7 оснащены мультиплексором запросов DMA, отсутствовавшим в старых F7, F4 или F3, с которыми мне доводилось работать. В последних моделях сопоставление каналов DMA выполнялось путем ввода правильного значения в управляющий регистр DMA_x Stream_y. Например

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

будет выбирать 3-й канал для DM2 Stream3 (в случае MCU F7 это будет соответствовать запросу SPI1 TX DMA: введите описание изображения здесьнасколько я понимаю, сопоставление потока DMA с каналом DMA больше не является «зашитым» и может В серии H7 может быть перенастроен вручную.Как указано в руководстве, DMAMux1 должен использоваться для маршрутизации строки запроса DMA в канал DMA.К сожалению, конфигурация DMAMUX довольно плохо описана в справочном руководстве.Мне не удалось понять, как именно поток DMA, периферийное устройство и канал DMA соединяются между собой через мультиплексор.Ниже приведен фрагмент кода, который в идеале должен

  1. Настройте SPI1.
  2. Настройте прямой доступ к памяти.
  3. Включите поток DMA для передачи 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;
    

Этот код компилируется, и я могу загрузить его в микроконтроллер STM32H753ZIT6. Полный код дополнительно имеет настройку PLL, которая не включена в приведенный выше фрагмент (инициализация PLL работает просто отлично, так как я могу проверить системные часы 400 МГц на выводе MCO).

Настройка мультиплексора прямого доступа к памяти, как ни странно, неполна. Я только выбрал правильное (по крайней мере, я думаю, что это правильное) назначение входов мультиплексора ресурсам. Я даже не уверен, правильный ли канал мультиплексора (или их выбор произволен в микроконтроллерах H7).

Итак, мой вопрос: как правильно настроить мультиплексор DMA для передачи SPI TX? Если я смогу запустить этот MWE, я более или менее смогу закончить миграцию остального кода.

Заранее спасибо.

ОБНОВЛЯТЬ:

Итак, я пытался следовать совету пользователя 9403409, но, к сожалению, мне не удалось далеко зайти. Я до сих пор не могу заставить SPI работать поверх DMA на микроконтроллерах серии H7. Теперь я могу заставить SPI работать без DMA на 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]);
    }
}

Приведенный выше код по существу делает три вещи:

  1. ConfigureHSIинициализирует часы HSI (я уменьшил тактовую частоту, чтобы иметь возможность выполнять некоторые измерения с медленным USB-прицелом, который у меня сейчас есть).
  2. InitializeMCOотображает основной вывод часов (просто чтобы убедиться, что часы настроены правильно).
  3. InitializeMasterTxSPIнастраивает SPI и отправляет трехбайтовое сообщение.

Я определенно вижу это сообщение, отправленное на моем осциллографе: введите описание изображения здесьБаза времени составляет 200 нс/дел, для справки.

С другой стороны, если я попытаюсь переделать все это через DMA, я не увижу никакого результата. Вот как выглядит мой код SPI на основе 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;
}

По сути, это одно и то же, с той лишь разницей, что есть функция InitializeDMAи передача DMA инициируется командой DMA1_Stream0->CR |= DMA_SxCR_EN(как это было в более ранних сериях MCU). Так что, к сожалению, я до сих пор не могу запустить SPI через DMA на H7. Любая помощь будет принята с благодарностью.

Вы пытались использовать SPI на H7 в ведомом режиме с DMA? Я изменил ваш опубликованный пример, но он не работает. С уважением Карстен Полезно Бесполезно
Уважаемый Карстен. Попробуйте запустить код из моего собственного ответа ниже. Конфигурация DMA действительно похожа на более старую серию STM32, с той лишь разницей, что вам также необходимо настроить правильный канал DMAMUX в соответствии с справочным руководством.

Ответы (2)

Итак, мне действительно удалось запустить SPI 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);
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;
}

Теперь базовая функциональность DMAMUX не слишком сложна, учитывая все обстоятельства. В руководстве указано, что:

  • Каналы DMAMUX1 с 0 по 7 подключены к каналам DMA1 с 0 по 7
  • Каналы DMAMUX1 с 8 по 15 подключены к каналам DMA2 с 0 по 7.
  • Каналы DMAMUX2 с 0 по 7 подключены к каналам BDMA с 0 по 7

Это, наряду с назначением входов мультиплексора таблицам ресурсов, является ключом к запуску DMA (по крайней мере, так же, как в более старых сериях MCUS). Например, SPI1_TX находится на 38-м входе MUX запроса DMA DMAMUX1 (см. таблицу 110 в справочном руководстве). Это означает, что я могу использовать либо DMA1, либо DMA2 (но не BDMA, так как он связан с DMAMUX2). Я могу выбрать любой поток, который захочу, им нужно только следовать правилу:

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

Таким образом, вы, по сути, связываете периферийное устройство с потоком DMA через определенный канал DMAMUX.

Также стоит отметить пару моментов:

  • Не забудьте установить SPI_CR1_CSTARTбит (это новинка для H7).
  • Будьте осторожны с SPI->CR2реестром. Если вы запишете в него значение, передача SPI остановится после того, как начнется предопределенное количество передач данных. Бесконечный цикл, представленный в моем примере, не будет работать, если установлен CR2 (мы получим только одну полную передачу SPI).

Хотя сейчас все это кажется мне немного очевидным, я все же скажу, что информации в справке немного не хватает. Работа DMA в руководствах к старым сериям была описана несколько лучше (по крайней мере, на мой взгляд). Например, я до сих пор не знаю, как (и когда) использовать оставшуюся функциональность DMAMUX (например, генераторы запросов и тому подобное). Кроме того, я не совсем уверен, как реализована передача памяти в память (вероятно, мне придется узнать об этом, когда придет время).

Я надеюсь, что это поможет всем, кто хочет глубже погрузиться в программирование ARM.

Ваше здоровье.

Ваша конфигурация DMAMUX правильная, вам нужно только инициализировать регистр DMAMUX1_CCR_DMAREQ_ID.

ОБНОВЛЕНИЕ 1: я успешно инициализировал DMA для таймера TIM2:

Конфигурация DMA TIM2 для stm32h7

Не могли бы вы уточнить это? Разве это не то, что // 5. Use DMAMux1 to route a DMA request line to the DMA channel. DMAMUX1_Channel0->CCR = (37u << DMAMUX_CxCR_DMAREQ_ID_Pos);делает строка:?
@KR да, это так. DMAMUX не требует дополнительной настройки. Если вы заинтересованы, вы можете посмотреть на мой вопрос с конфигурацией DMA (отредактировать ответ)
Хорошо, я постараюсь пересобрать этот проект, как только доберусь до железа. А пока не могли бы вы уточнить биты SYNC_ID в регистре DMAMUX-CxCR и регистре генератора запросов (DMAMUX_RGxCR)? Когда нужно их использовать? Более того, как «привязать» поток DMA к определенному периферийному устройству? В приведенном выше примере я выбрал DMA2_Stream3 и DMAMUX1_Channel0, но нет фрагмента кода, который действительно связывает их вместе.
@KR это просто dmamux1_channel0, жестко подключенный к dma1_stream0, dmamux1_channel8 к dma2_stream0 и так далее. Я считаю, что им нужно было синхронно запускать более одной DMA из одного источника событий. Если вас заинтересует мой обновленный код, ткните меня палкой. я отправлю это тебе