Olá caro leitor, tudo bem? Continuando com a série de artigos “Biblioteca de Software para a FRDM-KL25Z”, neste artigo serão apresentados o conceito básico sobre DMA (Direct Memory Access – Acesso direto à memória), o código fonte da biblioteca de software que desenvolvi, uma aplicação que exemplifica o conceito e a biblioteca.
As bibliotecas apresentadas são compatíveis com o Kinetis Design Studio IDE e CodeWarrior IDE. Também são facilmente portáveis para as demais placas Freedom Board.
Introdução a DMA
DMA é o recurso que permite o dispositivo de Entrada/Saída de transferir dados diretamente para a memória sem fazer uso da Central Processing Unit (CPU). Assim, permite que a CPU do hardware se encarregue apenas do processamento que é referente à aplicação.
A figura abaixo demonstra o fluxo de dados realizado por uma aplicação que não utiliza o recurso do DMA.
Na próxima imagem temos a ilustração do fluxo de dados em sistema que utiliza o DMA.
O DMA é especialmente útil para transportar grandes quantidades de dados em altas velocidades. Por exemplo, dados de uma unidade de armazenamento, áudio e vídeo. O uso do DMA também pode ser utilizado para manipular dados lentos, como, por exemplo, do UART (Universal Asynchronous Receiver/Transmitter), I2C (Inter-Integrated Circuit), entre outros.
Por exemplo, no uso do DMA para aquisição de dados de um barramento UART, a CPU não precisa parar o seu processamento para atender a cada solicitação do periférico. Ficando por conta do módulo de DMA receber e salvar os dados em determinado Buffer e, assim que a transferência dos dados estiver concluída, o DMA notifica a CPU por meio de uma única interrupção.
DMA para FRDM-KL25Z (Freedom Board KL25Z)
O módulo de DMA que está presente no microcontrolador da Freedom Board KL25Z é chamado de DMAMUX, por se tratar de um multiplexador de acesso direto à memória. Esse multiplexador contém 63 fontes de entradas e possui 4 saídas, que estão conectadas os 4 canais de DMA presentes no microcontrolador. Basicamente o DMAMUX encaminha as fontes de entradas para qualquer um dos 4 canais de DMA. A figura abaixo ilustra com maiores detalhes o funcionamento do DMAMUX.
O DMAMUX, pode ser utilizado em conjunto com diversos periféricos do microcontrolador, tais como:
- Analog-to-Digital Converter (ADC);
- Inter-Integrated Circuit (I2C);
- Universal Asynchronous Receiver Transmitter (UART);
- Digital-to-Analog Converter (DAC);
- Timer Pulse Module (TPM);
- Low-Power Timer (LPTMR);
- Serial Peripheral Interface (SPI);
- Comparator (CMP);
- Touch Sensing Input (TSI).
Configurando o DMA
O funcionamento do DMA é semelhante a qualquer outro periférico do microcontrolador, necessita de configuração e inicialização através de seus registradores.
O primeiro passo é especificar a fonte de Clock para o periférico. Essa operação é feita por meio dos registradores System Clock Gating Control Register 6 (SIM_SCGC6) e System Clock Gating Control Register 7 (SIM_SCGC7) conforme podemos observar nas figuras abaixo.
Como observado nas figuras acima, para o registrador System Clock Gating Control Register 6 o Bit 1 é referente ao DMAMUX. Para habilitar o mesmo deve-se utilizar a macro SIM_SCGC6_DMAMUX_MASK. No registrador System Clock Gating Control Register 7 o Bit 8 é referente ao módulo de DMA. Para habilitar a operação é utilizada a macro SIM_SCGC7_DMA_MASK.
Outro registrador que devemos configurar é Source Address Register (DMA_SAR0), é por meio deste que é informada a origem dos dados a serem transferidos. Por exemplo, para aquisição de sinais do conversor analógico-digital (ADC), deve ser informado o registrador que contém o resultado da conversão (ADC0_RA). A próxima figura ilustra com maior detalhes o registrador DMA_SAR0.
O registrador Destination Address Register (DMA_DARn) é utilizado para informar o endereço de memória onde o módulo DMA deve salvar os dados.
Nota: Essa operação pode ser feita utilizando operadores para ponteiro ‘&’ para obter endereço. Por exemplo, no uso de DMA para realizar aquisição de dados do barramento UART, onde os dados serão salvos em um determinado Buffer.
O próximo registrador a ser configurado é DMA Status Register / Byte Count Register (DMA_DSR_BCRn). Esse registrador é divido em dois blocos, o primeiro é DSRn, que corresponde aos 8 Bits mais significativos (31 a 24), que contêm as Flags de status canal. Os Bits restantes são o BCRn (23 a 0), campo responsável em informar o número de Bytes a ser transferido.
Nota: Por exemplo, a aquisição sinal do conversor analógico-digital configurado com resolução de 16 Bits, corresponde a 2 Bytes. Portanto em aplicação que a aquisição do ADC seja feita por meio do DMA, no registrador BCRn deve-se informado o valor 2.
Outro registrador que deve ser configurado é DMA Control Register (DMA_DCRn). Esse registrador contém as principais configurações do módulo de DMA do microcontrolador, tais como: habilitar interrupção, configuração do formato de dado, configurações sobre buffer de dados, entre outros. Para mais detalhes sobre este registrador deve-se consultar o Reference-Manual do microcontrolador.
Por último, porém não mesmo importante, temos o registrador Channel Configuration Register (DMAMUXx_CHCFGn), que é responsável por configurar os canais do DMA do microcontrolador. A figura a seguir ilustra com maior detalhes o registrador:
Como pode ser observado, a figura acima trata-se de um registrador de 8 Bits, sendo que o bit mais significativo é destinado para habilitar e desabilitar o canal de DMA. Essa operação é feita com a ajuda da macro DMAMUX_CHCFG_ENBL_MASK.
O Bit de número 06 do registrador é utilizado para configurar o “trigger” do canal de DMA. Essa configuração é feita com a macro DMAMUX_CHCFG_TRIG_MASK.
Os Bits restantes do registrador são para especificar a fonte de dados para o DMA. O microcontrolador presente na Freedom Board KL25Z permite até 63 sinais de solicitações de DMA.
No Reference-Manual existem todas as informações referentes às 63 fontes de sinais de entrada do DMAMUX.
A seguir é apresentado o código fonte da biblioteca de software desenvolvida para Freedom Board KL25Z.
/*
* dma.h
*
* Created on: 13/02/2018
* Author: Evandro Teixeira
*/
#ifndef SOURCES_DMA_H_
#define SOURCES_DMA_H_
#include "MKL25Z4.h"
#include <stdio.h>
#define NUMBER_CHANNEL 4
#define DMA_CHANNEL_0 0
#define DMA_CHANNEL_1 1
#define DMA_CHANNEL_2 2
#define DMA_CHANNEL_3 3
#define DMA_SIZE_32_BIT 0
#define DMA_SIZE_08_BIT 1
#define DMA_SIZE_16_BIT 2
#define DMA_SIZE_RESERVED 3
#define DMA_DESTINATION_INCREMENT 1
#define DMA_DESTINATION_NO_INCREMENT 0
#define DMA_BUFFER_DISABLED 0
#define DMA_BUFFER_SIZE_16_BYTE 1
#define DMA_BUFFER_SIZE_32_BYTE 2
#define DMA_BUFFER_SIZE_64_BYTE 3
#define DMA_BUFFER_SIZE_128_BYTE 4
#define DMA_BUFFER_SIZE_256_BYTE 5
#define DMA_BUFFER_SIZE_512_BYTE 6
#define DMA_BUFFER_SIZE_1_KBYTE 7
#define DMA_BUFFER_SIZE_2_KBYTE 8
#define DMA_BUFFER_SIZE_4_KBYTE 9
#define DMA_BUFFER_SIZE_8_KBYTE 10
#define DMA_BUFFER_SIZE_16_KBYTE 11
#define DMA_BUFFER_SIZE_32_KBYTE 12
#define DMA_BUFFER_SIZE_64_KBYTE 13
#define DMA_BUFFER_SIZE_128_KBYTE 14
#define DMA_BUFFER_SIZE_256_KBYTE 15
#define DMA_UART0_RECEIVE 2
#define DMA_UART0_TRANSMIT 3
#define DMA_UART1_RECEIVE 4
#define DMA_UART1_TRANSMIT 5
#define DMA_UART2_RECEIVE 6
#define DMA_UART2_TRANSMIT 7
#define DMA_SPI0_RECEIVE 16
#define DMA_SPI0_TRANSMIT 17
#define DMA_SPI1_RECEIVE 18
#define DMA_SPI1_TRANSMIT 19
#define DMA_I2C0 22
#define DMA_I2C1 23
#define DMA_TPM0_CHANNEL_0 24
#define DMA_TPM0_CHANNEL_1 25
#define DMA_TPM0_CHANNEL_2 26
#define DMA_TPM0_CHANNEL_3 27
#define DMA_TPM0_CHANNEL_4 28
#define DMA_TPM0_CHANNEL_5 29
#define DMA_TPM1_CHANNEL_0 32
#define DMA_TPM1_CHANNEL_1 33
#define DMA_TPM1_CHANNEL_2 34
#define DMA_TPM1_CHANNEL_3 35
#define DMA_ADC0 40
#define DMA_CMP0 42
#define DMA_DAC0 45
#define DMA_PTA 49
#define DMA_PTD 52
#define DMA_TPM0_OVERFLOW 54
#define DMA_TPM1_OVERFLOW 55
#define DMA_TPM2_OVERFLOW 56
#define DMA_TSI 57
#define DMA_CONTINUOUSLY_TRANSFERS 0
#define DMA_FORCES_SINGLE 1
typedef struct
{
uint8_t channel;
uint8_t number_byte;
uint8_t channel_source;
uint32_t *source_address;
uint32_t *destination_address;
uint8_t source_size;
uint8_t destination_size;
uint8_t destination_increment;
uint8_t destination_address_modulo;
uint8_t source_address_modulo;
uint8_t cycle_steal;
uint8_t peripheral_request;
uint8_t start_transfer;
}dma_config_t;
void dma_init(dma_config_t config);
void dma0_callback(void (*task)(void));
void dma1_callback(void (*task)(void));
void dma2_callback(void (*task)(void));
void dma3_callback(void (*task)(void));
#endif /* SOURCES_DMA_H_ */
/*
* dma.c
*
* Created on: 13/02/2018
* Author: Evandro Teixeira
*/
#include "dma.h"
/****************************************************************************************
*
*****************************************************************************************/
static void (*dma0_task_callback)(void);
static void (*dma1_task_callback)(void);
static void (*dma2_task_callback)(void);
static void (*dma3_task_callback)(void);
/****************************************************************************************
*
*****************************************************************************************/
const IRQn_Type dma_irq[NUMBER_CHANNEL] =
{
DMA0_IRQn,
DMA1_IRQn,
DMA2_IRQn,
DMA3_IRQn
};
uint8_t dma_number_byte[NUMBER_CHANNEL];
/****************************************************************************************
*
*****************************************************************************************/
void dma_init(dma_config_t config)
{
SIM_SCGC6 |= SIM_SCGC6_DMAMUX_MASK;
SIM_SCGC7 |= SIM_SCGC7_DMA_MASK;
DMA0->DMA[config.channel].DAR = (uint32_t)config.destination_address;
DMA0->DMA[config.channel].SAR = (uint32_t)config.source_address;
DMA0->DMA[config.channel].DSR_BCR = DMA_DSR_BCR_BCR( config.number_byte );
DMA0->DMA[config.channel].DCR = 0;
DMA0->DMA[config.channel].DCR |= DMA_DCR_EINT_MASK;
DMA0->DMA[config.channel].DCR |= DMA_DCR_ERQ( config.peripheral_request );
DMA0->DMA[config.channel].DCR |= DMA_DCR_CS( config.cycle_steal );
DMA0->DMA[config.channel].DCR |= DMA_DCR_SSIZE( config.source_size );
DMA0->DMA[config.channel].DCR |= DMA_DCR_DSIZE( config.destination_size );
DMA0->DMA[config.channel].DCR |= DMA_DCR_DINC( config.destination_increment );
DMA0->DMA[config.channel].DCR |= DMA_DCR_SMOD( config.source_address_modulo );
DMA0->DMA[config.channel].DCR |= DMA_DCR_DMOD( config.destination_address_modulo );
DMA0->DMA[config.channel].DCR |= DMA_DCR_START( config.start_transfer );
DMAMUX0->CHCFG[config.channel] |= DMAMUX_CHCFG_ENBL_MASK;
DMAMUX0->CHCFG[config.channel] |= DMAMUX_CHCFG_SOURCE( config.channel_source );
NVIC_EnableIRQ( dma_irq[config.channel] );
dma_number_byte[config.channel] = (uint8_t)config.number_byte;
}
/****************************************************************************************
*
*****************************************************************************************/
void dma0_callback(void (*task)(void))
{
if(task != NULL)
dma0_task_callback = task;
}
/****************************************************************************************
*
*****************************************************************************************/
void dma1_callback(void (*task)(void))
{
if(task != NULL)
dma1_task_callback = task;
}
/****************************************************************************************
*
*****************************************************************************************/
void dma2_callback(void (*task)(void))
{
if(task != NULL)
dma2_task_callback = task;
}
/****************************************************************************************
*
*****************************************************************************************/
void dma3_callback(void (*task)(void))
{
if(task != NULL)
dma3_task_callback = task;
}
/****************************************************************************************
*
*****************************************************************************************/
void DMA0_IRQHandler(void)
{
DMA0->DMA[0].DSR_BCR |= DMA_DSR_BCR_DONE_MASK;
DMA0->DMA[0].DSR_BCR |= DMA_DSR_BCR_BCR( dma_number_byte[0] );
dma0_task_callback();
}
/****************************************************************************************
*
*****************************************************************************************/
void DMA1_IRQHandler(void)
{
DMA0->DMA[1].DSR_BCR |= DMA_DSR_BCR_DONE_MASK;
DMA0->DMA[1].DSR_BCR |= DMA_DSR_BCR_BCR( dma_number_byte[1] );
dma1_task_callback();
}
/****************************************************************************************
*
*****************************************************************************************/
void DMA2_IRQHandler(void)
{
DMA0->DMA[2].DSR_BCR |= DMA_DSR_BCR_DONE_MASK;
DMA0->DMA[2].DSR_BCR |= DMA_DSR_BCR_BCR( dma_number_byte[2] );
dma2_task_callback();
}
/****************************************************************************************
*
*****************************************************************************************/
void DMA3_IRQHandler(void)
{
DMA0->DMA[3].DSR_BCR |= DMA_DSR_BCR_DONE_MASK;
DMA0->DMA[3].DSR_BCR |= DMA_DSR_BCR_BCR( dma_number_byte[3] );
dma3_task_callback();
}
/*****************************************************************************************/
Aplicação
A aplicação de demonstração do uso do DMA consiste em amostrar o sinal do conversor analógico-digital (ADC), onde o resultado da conversão do sinal é salvo em uma determinada posição de memória. Quando o número de amostragem determinado é alcançado, o DMA gera uma interrupção e, em seguida, é feito o processamento dos dados amostrados. Para melhor entendimento, a figura abaixo possui o fluxograma da aplicação.
A seguir temos o código fonte da aplicação desenvolvida para demonstração do uso da biblioteca de software.
/*
* main.c
*
* Created on: 13/02/2018
* Author: Evandro Teixeira
*/
#include "MKL25Z4.h"
#include "dma.h"
#include "adc.h"
uint16_t i = 0;
uint16_t vetor_adc[8];
uint32_t average_adc = 0;
void processes_adc_data(void);
int main(void)
{
dma_config_t config;
config.channel = DMA_CHANNEL_0;
config.source_address = (uint32_t *)&ADC0_RA;
config.destination_address = (uint32_t *)&vetor_adc;
config.number_byte = 16;
config.cycle_steal = DMA_FORCES_SINGLE;
config.destination_increment = DMA_DESTINATION_INCREMENT;
config.destination_size = DMA_SIZE_16_BIT;
config.source_size = DMA_SIZE_16_BIT;
config.destination_address_modulo = DMA_BUFFER_SIZE_16_BYTE;
config.source_address_modulo = DMA_BUFFER_SIZE_16_BYTE;
config.channel_source = DMA_ADC0;
config.peripheral_request = 1;
config.start_transfer = 0;
dma_init(config);
adc_init(_16BIT);
dma0_callback(processes_adc_data);
for (;;)
{
i++;
if(i>100)
{
i = 0;
//start conversion with channel 8 PTB0
ADC0_SC1A = (ADC_SC1_ADCH(ADC_4) | (ADC0_SC1A & (ADC_SC1_AIEN_MASK | ADC_SC1_DIFF_MASK)));
}
}
/* Never leave main */
return 0;
}
void processes_adc_data(void)
{
uint8_t x = 0;
for(x=0;x<8;x++)
{
average_adc += vetor_adc[x];
}
average_adc /= 8;
}
Conclusão
O acesso direto à memória DMA é um excelente recurso de hardware que, quando bem aplicado, aumenta a eficiência do processamento, pois tira da CPU a responsabilidade de processar tarefas que são periódicas e lentas, deixando-a responsável apenas por processar o algoritmo da aplicação.
Neste artigo foi apresentado mais uma biblioteca de software para a Freedom Board KL25Z, para o módulo de DMA. Os arquivos aqui apresentados estão disponíveis neste Github.
E fica aqui o meu convite a você caro leitor, que se interessou pelo assunto, a contribuir com o projeto, testando e aperfeiçoando a biblioteca de software apresentada.
Saiba mais
Scan Memory: Checando a integridade da memória Flash no PIC18F47K40
Ping-pong Buffer para sistemas embarcados
Referências
Using the Asynchronous DMA features of the Kinetis L Serie

















Parabéns Evandro! Excelente artigo! Explicou um assunto complexo de forma bem didática!
Valeu muito obrigado Lucas 😉