Pular para o conteúdo principal

DMA - Direct Memory Access

O DMA (Direct Memory Access, ou Acesso Direto à Memória) no microcontrolador STM32F411CEU6 é um periférico projetado para transferir dados em alta velocidade entre memórias e periféricos sem a intervenção da CPU.

Para entender o impacto disso, pense na CPU como um professor altamente qualificado. Sem o DMA, cada byte que chega de uma leitura de sensor (SPI/I2C) ou que precisa ir para uma porta serial (UART) precisa ser pego pela CPU e movido manualmente. Se você estiver transferindo um buffer de 1 KB, a CPU ficará "ocupada" executando milhares de instruções apenas para mover dados.

Com o DMA, a CPU apenas configura a origem, o destino e o tamanho do pacote, e diz: "Transfira isso para mim". Enquanto o DMA move os dados em segundo plano, a CPU fica 100% livre para processar algoritmos pesados (como filtragem digital ou lógica de controle) ou pode entrar em modo de baixo consumo (Sleep).


Arquitetura do DMA no STM32F411​

O STM32F411CEU6 possui 2 controladores DMA (DMA1 e DMA2). Cada controlador possui:

  • 8 Streams (Canais de fluxo): Cada stream é totalmente independente e pode ser configurado para gerenciar transferências de dados.
  • 8 Canais por Stream: Funciona como um multiplexador. Embora um stream tenha 8 canais de periféricos conectados a ele, apenas um canal pode estar ativo por vez naquele stream.

Diferença Crítica entre DMA1 e DMA2 no STM32F411:​

  • DMA1: Está conectado ao barramento APB1 (periféricos de baixa velocidade). Ele não tem acesso à memória Flash interna. Portanto, o DMA1 não pode fazer transferências do tipo Memory-to-Memory (Memória para Memória). Ele faz apenas Peripheral-to-Memory e Memory-to-Peripheral.
  • DMA2: Está conectado ao barramento APB2 (periféricos de alta velocidade) e tem acesso à matriz de barramentos que alcança a Flash e a SRAM. Por isso, apenas o DMA2 pode realizar transferências Memória para Memória (por exemplo, copiar rapidamente uma array da Flash para a RAM).

Modos de Transferência Suportados​

O DMA do STM32F411 é extremamente flexível e opera em três direções principais:

  1. Peripheral-to-Memory (Periférico para Memória):
  • Exemplo: O ADC (Conversor Analógico-Digital) termina uma leitura e o DMA salva o resultado diretamente em uma array na SRAM.
  1. Memory-to-Peripheral (Memória para Periférico):
  • Exemplo: Você tem um buffer de texto na RAM e o envia para a UART para transmissão serial sem travar o código.
  1. Memory-to-Memory (Memória para Memória - Apenas DMA2):
  • Exemplo: Copiar blocos de dados ou inicializar buffers grandes instantaneamente.

Principais Recursos Técnicos​

  • Modo Circular (Circular Mode): Ideal para buffers contínuos (como amostragem de áudio ou leitura constante de sensores). Quando o DMA chega ao fim do buffer de memória, ele reinicia automaticamente do topo, criando um Ring Buffer em hardware, sem precisar que a CPU reconfigure os ponteiros.
  • Double Buffer Mode (Modo de Buffer Duplo): O hardware alterna automaticamente entre dois buffers de memória. Enquanto o DMA está preenchendo o "Buffer A" com dados do periférico, a CPU pode ler e processar o "Buffer B" com total segurança, sem risco de corrupção de dados (race conditions).
  • Incremento Automático de Endereço: Você pode configurar o DMA para incrementar o ponteiro de memória a cada transferência (para preencher uma array) enquanto mantém o endereço do periférico estático (já que o registrador de dados do periférico, como USART_DR, está sempre no mesmo endereço de memória mapeada).
  • Tamanho do Dado Configurável: O DMA pode mover dados em tamanhos de Byte (8 bits), Half-Word (16 bits) ou Word (32 bits) independentemente na origem e no destino. O próprio hardware faz o alinhamento se você transferir, por exemplo, de um periférico de 8 bits para uma memória de 16 bits.
  • FIFO (First-In, First-Out): Cada stream possui um buffer FIFO de 4 words. Isso permite gerenciar variações de velocidade entre os barramentos e suportar transferências em modo Burst (rajadas de dados).

Interrupções do DMA​

Para avisar a CPU sobre o status da transferência, o DMA gera três interrupções principais muito utilizadas no desenvolvimento com a HAL ou LL:

  • Transfer Complete (TC): O bloco inteiro de dados foi transferido.
  • Half Transfer (HT): Metade dos dados foi transferida. Isso é crucial no Modo Circular, pois avisa a CPU que ela já pode processar a primeira metade do buffer enquanto o DMA continua preenchendo a segunda metade.
  • Transfer Error (TE): Ocorreu um erro de barramento ou configuração durante a transferência.

Exemplo Prático de Aplicação (Contexto HAL)​

Se você estivesse configurando o ADC no STM32CubeIDE para ler múltiplos canais (como um joystick e um sensor de temperatura) usando o STM32F411CEU6, o fluxo do seu código seria:

uint32_t adc_buffer[2]; // Buffer na SRAM para armazenar as leituras

int main(void) {
// ... Inicializações de Clock, GPIO, ADC e DMA ...

// Inicia o ADC em modo DMA apontando para o nosso buffer, com tamanho 2
HAL_ADC_Start_DMA(&hadc1, adc_buffer, 2);

while (1) {
// A CPU está completamente livre aqui!
// Os valores em adc_buffer[0] e adc_buffer[1]
// são atualizados em background pelo DMA.
}
}

// Callback chamado automaticamente quando o DMA termina de mover os 2 valores
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) {
// Opcional: fazer algo assim que os dados novos chegarem
}

Em suma: DMA2_Stream0_IRQHandler() é a função de hardware de baixo nível (o ponto de entrada do vetor de interrupção), enquanto HAL_ADC_ConvCpltCallback() é uma função de alto nível fornecida pela ST (STMicroelectronics) para facilitar a vida do desenvolvedor.


1. DMA2_Stream0_IRQHandler() — O Nível do Hardware​

Esta função é o Handler de Interrupção (Interrupt Service Routine - ISR) propriamente dito. No STM32F411, o ADC1 geralmente está mapeado para o DMA2, Stream 0, Canal 0 (ou Stream 4, Canal 0).

  • Quem a chama? O próprio hardware do microcontrolador. Quando o periférico DMA termina de mover a quantidade de dados programada, ele levanta uma flag de hardware, interrompe a CPU e força o contador de programa (PCPC) a pular para o endereço desta função (mapeada na tabela de vetores no arquivo startup_stm32f411xe.s).
  • Onde ela fica? Geralmente gerada no arquivo stm32f4xx_it.c.
  • O que ela faz por padrão? Se você usa o STM32CubeIDE, dentro dela haverá apenas uma chamada para a biblioteca da ST:
void DMA2_Stream0_IRQHandler(void) {
HAL_DMA_IRQHandler(&hdma_adc1);
}

  • Características: Executa no contexto de interrupção com altíssima prioridade. O código aqui dentro deve ser o mais rápido possível.

2. HAL_ADC_ConvCpltCallback() — O Nível da Abstração (HAL)​

Esta é uma função de Callback (retorno de chamada). Ela faz parte da camada de software da HAL (Hardware Abstraction Layer).

  • Quem a chama? A própria função genérica da ST (HAL_DMA_IRQHandler), depois que ela tratou o hardware de baixo nível.
  • O que a HAL faz antes de chamar o seu Callback?
  1. Limpa os bits/flags de interrupção no registrador do DMA (para evitar que a interrupção fique travada em loop).
  2. Verifica se houve erros de transferência.
  3. Se tudo estiver correto, ela chama HAL_ADC_ConvCpltCallback().
  • Onde ela fica? Ela é declarada na biblioteca da ST como uma função __weak (fraca). Isso significa que você pode reescrevê-la em qualquer lugar do seu código (como no main.c), e o compilador usará a sua versão em vez da versão vazia da biblioteca.

O Fluxo de Execução​

Para visualizar como as duas interagem, imagine o fluxo temporal quando o ADC termina de preencher o buffer via DMA:

  1. Hardware: O DMA termina de transferir o último dado do ADC →\rightarrow O hardware gera a interrupção.
  2. Vetor de Interrupção: A CPU para o main() e pula para DMA2_Stream0_IRQHandler().
  3. Tratamento HAL: A função chama HAL_DMA_IRQHandler(), que limpa os registradores de status do DMA.
  4. Notificação do Usuário: A HAL identifica que o periférico de origem era o ADC e finalmente chama o seu HAL_ADC_ConvCpltCallback().
  5. Retorno: O código sai do callback, sai do handler e a CPU volta a executar o main() exatamente de onde parou.

Comparação Direta​

CaracterísticaDMA2_Stream0_IRQHandler()HAL_ADC_ConvCpltCallback()
CamadaHardware / Baixo Nível.Software (HAL) / Alto Nível.
PortabilidadePresa ao hardware específico (se mudar de chip ou mudar o Stream do DMA, o nome da função muda).Alta portabilidade (funciona em quase qualquer STM32 que use o ADC com HAL).
Gerenciamento de RegistradoresVocê precisa limpar manualmente as flags de interrupção (se não usar a HAL dentro dela).A HAL já limpou todas as flags antes de entregar o controle para você.
Múltiplos PeriféricosEspecífica daquele Stream do DMA.Se você tiver múltiplos ADCs, todos vão cair no mesmo callback, exigindo que você teste quem chamou (ex: if(hadc->Instance == ADC1)).

Qual você deve usar?​

Use sempre o HAL_ADC_ConvCpltCallback(). Ela foi feita justamente para isolar a sua aplicação dos detalhes de hardware do DMA. Se amanhã você precisar mudar o ADC do Stream 0 para o Stream 4 no CubeMX, o seu código dentro do Callback continuará funcionando perfeitamente sem alterar uma única linha, enquanto qualquer lógica colocada diretamente na IRQHandler seria perdida ou teria que ser movida de arquivo.