Maîtrise du Contrôleur DMA sur STM32 : Théorie et Implémentations Pratiques

Architecture et Principes du Contrôleur DMA

Le DMA (Direct Memory Access) est un mécanisme matériel permettant des transferts de données à haute vitesse entre différentes entités (périphériques, mémoires) sans solliciter le processeur central (CPU). En déléguant ces opérations de copie au contrôleur DMA, le CPU est libéré pour exécuter des tâches de calcul plus complexes, optimisant ainsi les performances globales du système embarqué.

Typologie des Transferts

Le contrôleur DMA gère trois types fondamentaux de flux de données :

Direction du Flux Cas d'Usage Typique
Mémoire vers Mémoire (M2M) Copie de blocs de données importants entre la SRAM et la Flash.
Mémoire vers Périphérique Envoi d'un tampon de données vers le register de transmission (TDR) d'un USART.
Périphérique vers Mémoire Stockage des données reçues du registre de réception (RDR) d'un ADC ou USART vers la RAM.

Caractéristiques Matérielles sur STM32F103

  • DMA1 : Dispose de 7 canaux indépendants.
  • DMA2 : Dispose de 5 canaux indépendants.

Chaque canal est dédié à une requête périphérique spécifique. En cas de requêtes simultanées, l'arbitrage s'effectue d'abord par priorité logicielle (configurable sur 4 niveaux dans le registre DMA_CCRx), puis par priorité matérielle (le canal avec l'index le plus bas est prioritaire à niveau égal).

Modes de Fonctionnement et Adressage

  • Mode Normal : Le transfert s'arrête après l'envoi du nombre de données spécifié.
  • Mode Circulaire : Le registre de compteur de données est rechargé automatiquement à la fin du cycle, permettant un transfert continu.
  • Incrémentation des Pointeurs : Les adresses source et destination peuvent être configurées pour s'incrémenter automatiquement après chaque transfert, ou rester fixes (uttile pour les registres de périphériques).

Cas Pratique 1 : Transfert Mémoire vers Mémoire (M2M)

Implémentation avec la Bibliothèque HAL

Après avoir configuré le canal DMA en mode M2M via STM32CubeMX, le transfert est initié par logiciel. Voici une approche utilisant le polling pour vérifier la fin du transfert.

// Définition des tampons
const uint32_t flash_source_data[16] = {
    0x00000000, 0x11111111, 0x22222222, 0x33333333,
    0x44444444, 0x55555555, 0x66666666, 0x77777777,
    0x88888888, 0x99999999, 0xAAAAAAAA, 0xBBBBBBBB,
    0xCCCCCCCC, 0xDDDDDDDD, 0xEEEEEEEE, 0xFFFFFFFF
};
uint32_t sram_dest_data[16];

void Execute_M2M_Transfer(void) {
    // Démarrage du transfert (16 mots de 32 bits)
    HAL_DMA_Start(&hdma_m2m_channel, (uint32_t)flash_source_data, (uint32_t)sram_dest_data, 16);

    // Attente active de la fin de transmission
    while (__HAL_DMA_GET_FLAG(&hdma_m2m_channel, DMA_FLAG_TC1) == RESET) {
        // Le CPU attend la fin du transfert matériel
    }
    
    // Nettoyage du drapeau de fin de transfert
    __HAL_DMA_CLEAR_FLAG(&hdma_m2m_channel, DMA_FLAG_TC1);
}

Implémentation avec la Bibliothèque Standard (SPL)

Pour les projets utilisant l'ancienne bibliothèque de périphériques, la configuration manuelle de la structure DMA est requise.

void Configure_M2M_DMA(void) {
    DMA_InitTypeDef dma_cfg;
    
    // Activation de l'horloge du bus AHB pour le DMA1
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
    
    dma_cfg.DMA_PeripheralBaseAddr = (uint32_t)flash_source_data;
    dma_cfg.DMA_MemoryBaseAddr = (uint32_t)sram_dest_data;
    dma_cfg.DMA_DIR = DMA_DIR_PeripheralSRC;
    dma_cfg.DMA_BufferSize = 16; 
    dma_cfg.DMA_PeripheralInc = DMA_PeripheralInc_Enable;
    dma_cfg.DMA_MemoryInc = DMA_MemoryInc_Enable;
    dma_cfg.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;
    dma_cfg.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;
    dma_cfg.DMA_Mode = DMA_Mode_Normal;
    dma_cfg.DMA_Priority = DMA_Priority_Medium;
    dma_cfg.DMA_M2M = DMA_M2M_Enable; // Activation spécifique du mode M2M
    
    DMA_Init(DMA1_Channel1, &dma_cfg);
    DMA_Cmd(DMA1_Channel1, ENABLE);
}

Cas Pratique 2 : Transmision UART via DMA (Mémoire vers Périphérique)

L'utilisation du DMA pour l'UART permet d'envoyer de grandes chaînes de caractères sans bloquer le CPU dans des boucles d'attente de registre vide.

Approche HAL

uint8_t uart_tx_payload[] = "Telemetry data sent via DMA\r\n";

void Transmit_UART_DMA(void) {
    // Le CPU configure le DMA et rend la main immédiatement
    HAL_UART_Transmit_DMA(&huart1, uart_tx_payload, sizeof(uart_tx_payload) - 1);
}

Approche SPL

#define USART1_DR_ADDRESS (USART1_BASE + 0x04)
uint8_t tx_buffer[16] = "0123456789ABCDEF";

void Init_UART_DMA_Tx(void) {
    DMA_InitTypeDef dma_cfg;
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
    
    dma_cfg.DMA_MemoryBaseAddr = (uint32_t)tx_buffer;
    dma_cfg.DMA_PeripheralBaseAddr = USART1_DR_ADDRESS;
    dma_cfg.DMA_DIR = DMA_DIR_PeripheralDST;
    dma_cfg.DMA_BufferSize = 16;
    dma_cfg.DMA_PeripheralInc = DMA_PeripheralInc_Disable; // Registre fixe
    dma_cfg.DMA_MemoryInc = DMA_MemoryInc_Enable;
    dma_cfg.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
    dma_cfg.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
    dma_cfg.DMA_Mode = DMA_Mode_Normal;
    dma_cfg.DMA_Priority = DMA_Priority_Low;
    dma_cfg.DMA_M2M = DMA_M2M_Disable;
    
    DMA_Init(DMA1_Channel4, &dma_cfg);
    DMA_Cmd(DMA1_Channel4, ENABLE);
    
    // Liaison de la requête DMA au périphérique USART1
    USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE);
}

Cas Pratique 3 : Réception UART de Longueur Variable (Périphérique vers Mémoire)

Pour recevoir des trames de taille inconnue sans utiliser d'interruptions par caractère (qui surchargent le CPU), la méthode optimale consiste à combiner le DMA circulaire avec l'interruption IDLE de l'UART. L'IDLE se déclenche lorsque la ligne RX reste inactive pendant la durée d'une trame, signalant la fin du paquet.

Logique de Réception avec HAL

Le processus consiste à démarrer le DMA, attendre l'interruption IDLE, arrêter temporairement le DMA pour calculer la taille reçue, traiter les données, puis relancer le DMA.

#define RX_BUFFER_CAPACITY 128

uint8_t uart_rx_buffer[RX_BUFFER_CAPACITY];
volatile uint16_t processed_rx_length = 0;

void Init_UART_Reception(void) {
    // Activation de l'interruption de ligne inactive (IDLE)
    __HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);
    // Démarrage de la réception DMA
    HAL_UART_Receive_DMA(&huart1, uart_rx_buffer, RX_BUFFER_CAPACITY);
}

void USART1_IRQHandler(void) {
    HAL_UART_IRQHandler(&huart1);
    
    // Vérification du drapeau IDLE
    if (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE)) {
        __HAL_UART_CLEAR_IDLEFLAG(&huart1);
        
        // Arrêt du DMA pour figer le compteur
        HAL_UART_DMAStop(&huart1);
        
        // Calcul du nombre d'octets reçus
        uint32_t unread_bytes = __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);
        processed_rx_length = RX_BUFFER_CAPACITY - unread_bytes;
        
        // Traitement des données (ou signalisation via un flag pour le main)
        // Exemple: Analyser la commande reçue
        if (strstr((char*)uart_rx_buffer, "CMD_START") != NULL) {
            HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET);
        }
        
        // Relancement du DMA pour la prochaine trame
        HAL_UART_Receive_DMA(&huart1, uart_rx_buffer, RX_BUFFER_CAPACITY);
    }
}

Logique de Réception avec SPL

uint8_t rx_payload[RX_BUFFER_CAPACITY];

void USART1_IRQHandler(void) {
    if (USART_GetITStatus(USART1, USART_IT_IDLE) != RESET) {
        // Séquence de nettoyage matériel du flag IDLE (Lecture SR puis DR)
        volatile uint32_t temp_register;
        temp_register = USART1->SR;
        temp_register = USART1->DR;
        (void)temp_register; 
        
        // Désactivation du canal DMA
        DMA_Cmd(DMA1_Channel5, DISABLE);
        
        // Extraction de la longueur utile
        uint16_t frame_length = RX_BUFFER_CAPACITY - DMA_GetCurrDataCounter(DMA1_Channel5);
        
        // Traitement de rx_payload sur frame_length octets...
        
        // Réinitialisation du compteur et redémarrage
        DMA_SetCurrDataCounter(DMA1_Channel5, RX_BUFFER_CAPACITY);
        DMA_Cmd(DMA1_Channel5, ENABLE);
    }
}

Étiquettes: STM32 DMA HAL EmbeddedC UART

Publié le 7 juin à 21h28