Biblioteca de Software de DMA para FRDM-KL25Z

Biblioteca de GPIO

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.

 

Fluxo de dados sem o uso de DMA
Fluxo de dados sem o uso de DMA

 

Na próxima imagem temos a ilustração do fluxo de dados em sistema que utiliza o DMA.

 

Fluxo de dados com o uso de DMA
Fluxo de dados com o uso de 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.

 

Diagrama de bloco do DMAMUX
Diagrama de bloco 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.

 

DMA para FRDM-KL25Z - Descrição Registrador System Clock Gating Control Register 6
Descrição Registrador System Clock Gating Control Register 6
Descrição do Bit DMAMUX
Descrição do registrador SIM_SCGC7
Descrição do registrador SIM_SCGC7

 

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.

 

Descrição do registrador Source Address Register
Descrição do registrador Source Address Register
Detalhes do registrador Source Address Register
Detalhes do registrador Source Address Register

 

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.

 

Detalhes do registrador Destination Address Register
Detalhes do registrador Destination Address Register
Detalhes do registrador Destination Address Register
Detalhes do registrador Destination Address Register

 

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.

 

Descrição do registrador Status Register / Byte Count Register
Descrição do registrador Status Register / Byte Count Register

 

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.

 

Detalhes do registrador DMA Control Register
Detalhes do registrador DMA Control Register

 

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:

 

Detalhes do registrador Channel Configuration Register
Detalhes do registrador Channel Configuration Register

 

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.

 

Informações do registrador Configuration Register
Informações do registrador Configuration Register

 

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.

 

Fluxograma da aplicaçã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

Conhecendo a KL05Z

Ping-pong Buffer para sistemas embarcados

 

 

Referências

 

Using the Asynchronous DMA features of the Kinetis L Serie

KL25 Sub-Family Reference Manual

FRDM-KL25Z User's Manual

Outros artigos da série

<< Biblioteca DAC para FRDM-KL25ZBiblioteca de Software de Watchdog Timer para FRDM-KL25Z >>
NEWSLETTER

Receba os melhores conteúdos sobre sistemas eletrônicos embarcados, dicas, tutoriais e promoções.

Obrigado! Sua inscrição foi um sucesso.

Ops, algo deu errado. Por favor tente novamente.

Licença Creative Commons Esta obra está licenciada com uma Licença Creative Commons Atribuição-CompartilhaIgual 4.0 Internacional.

Evandro Teixeira
Desenvolvedor de Sistemas Embarcados. Sou formado Técnico em Instrumentação e Automação Industrial/Mecatrônica pelo Colégio Salesiano Dom Bosco de Americana-SP, cursei o Engenharia Elétrica com Ênfase em Eletrônica pela UNISAL Centro Universitário Salesiano de São Paulo e atualmente estou cursando Superior de Tecnologia em Análise e Desenvolvimento de Sistemas pela UNIP Universidade Paulista.

2
Deixe um comentário

avatar
 
1 Comment threads
1 Thread replies
2 Followers
 
Most reacted comment
Hottest comment thread
2 Comment authors
Evandro TeixeiraLucas Zampar Bernardi Recent comment authors
  Notificações  
recentes antigos mais votados
Notificar
Lucas Zampar Bernardi
Membro
Lucas Zampar Bernardi

Parabéns Evandro! Excelente artigo! Explicou um assunto complexo de forma bem didática!

Evandro Teixeira
Visitante
Evandro Teixeira

Valeu muito obrigado Lucas 😉