Biblioteca PWM para FRDM-KL25Z

Este post faz parte da série Bibliotecas de Software para FRDM-KL25Z

Olá caro leitor, esse é mais um artigo da série Biblioteca de Software para a FreedomBoard KL25Z. Será apresentada a biblioteca de software para utilizar o periférico de PWM (PulseWidth Modulation Modulação por Largura de Pulso) e exemplo de aplicação utilizando Servomotor.

As bibliotecas aqui apresentadas são compatíveis com o Kinetis Design Studio IDE e CodeWarrior IDE. Também são facilmente portáveis para as demais Freedom Board.

O microcontrolador presente na Freedom Board KL25Z é o MKL25Z128VLK4. O PWM é um dos módulos pertencentes ao TPM (Timer/PWM Module), que se trata de um dos Timers existentes no microcontrolador. O microcontrolador presente na Freedom Board contém três periféricos de TPM (TPM0, TPM1 e TPM2), esse Timer possui um contador de 16 bits.

Demostrarei a seguir a biblioteca de software com as configurações mínimas para trabalhar com Timer/PWM Module, apresentando funções de inicialização dos periféricos, inicialização do canal de saída do PWM e a função de que ajusta o valor do Duty Cycle.

O PWM trata-se de uma técnica de Modulação por Largura de Pulso (MLP). Essa técnica permite a geração de onda quadrada, baseada na variação da largura de pulso de acordo com o valor do item chamado de Duty Cycle. Para obter mais informações detalhadas sobre PWM, recomendo conferir o artigo Arduino – Saídas PWM, produzido pelo Fábio Souza. Na figura a seguir temos uma imagem que ilustra bem o funcionamento do PWM.

Fonte da Imagem: https://protostack.com.au/2011/06/atmega168a-pulse-width-modulation-pwm/

Inicializando o módulo Timer/PWM

Configurando o Clock do Periférico

O primeiro item a ser configurado para inicializar o periférico Timer/PWM Module é o registrador System Clock Gating Control Register 6 (SIM_SCGC6).

Como dito anteriormente, o microcontrolador MKL25Z128VLK4 possui três TPM (TPM0, TPM1 e TPM2). Para cada módulo possui uma macro específica para escrever no registrador System Clock Gating Control Register 6 (SIM_SCGC6). Para o TPM0, utilizar a macro SIM_SCGC6_TPM0_MASKTPM1 temos o SIM_SCGC6_TPM1_MASK e por fim para o TPM2 usar a macro SIM_SCGC6_TPM2_MASK.

As figuras abaixo ilustram os itens pertencentes ao registrador System Clock Gating Control Register 6 (SIM_SCGC6).

Outro item importante a ser configurado para utilização do Timer/PWM Module é sua fonte de clock. Para essa tarefa deve-se configurar o registrador System Options Register 2 (SIM_SOPT2). Os módulos TPM possuem três fontes de clock, são elas; MCGFLLCLK clock or MCGPLLCLK/2, OSCERCLK clock e MCGIRCLK clock. As figuras a abaixo descrevem brevemente o que é cada item. Para maiores informação sobre as fontes de clock, sugiro consultar o Reference Manual.

Para realizar a seleção da fonte de clock deve-se utilizar a macro SIM_SOPT2_TPMSRC(x), onde x é o valor que corresponde ao número da opção da fonte de clock. A seguir temos as imagens que demonstram o registrador e o valor para cada opção de clock.

Configurando os Parâmetros Timer/PWM Module

O primeiro item a ser configurado é modo de contagem do TPM. A escolha do modo de contagem é feita através do bit CMOD (Clock Mode Selection) do registrador Status and Control (TPMx_SC). A figura abaixo exibe com maiores detalhes o registrador Status and Control (TPMx_SC) e o bit CMOD.

A frequência do sinal do PWM é determinada pelo o valor clock atribuído ao periférico, por meio do registrador System Options Register 2 (SIM_SOPT2), o valor salvo no registrador Modulo (TPMx_MOD) e pelo o valor configurado ao Prescale, através no registrador Status and Control (TPMx_SC). As figuras abaixo têm os valores destinados ao Prescale, e detalhes do registrador Modulo (TPMx_MOD).

O valor do sinal do PWM é expresso pela equação apresentada a abaixo:

  • f core – Frequência atribuída ao periférico pelo registrador SIM_SOPT2.
  • f pwm – Frequência do sinal do PWM
  • TPMxMOD – Valor atribuído ao TPMxMOD
  • Prescale – Prescale Factor Selection

Realizando algumas manipulações com a equação acima, é possível determinar o valor do TPMxMOD a partir da frequência desejada ao sinal PWM.

Outro item do registrador Status and Control (TPMx_SC) que deve ser configurado é o bit CPWMS (Center-aligned PWM Selec). É através desse bit que é determinado o alinhamento do sinal do PWM. Nas figuras abaixo são detalhados os recursos do bit CPWMS (Center-aligned PWM Selec).

Inicialização do canal de PWM

Para configurar o canal como saída do sinal do Timer/PWM Module, é através do registrador Channel (n) Status and Control (TPMx_CnSC). Para selecionar o canal é utilizada a macro TPM_CnSC_REG(base,index), onde base é módulo TPM (TPM0, TPM1 ou TPM2), e a escolha do canal é através da variável index.

O último ponto a ser configurado para utilizar o PWM é o pino de saída do sinal. Configurar o pino deveram inicializar o sinal do clock e definir a funcionalidade do pino.

Para configurar o clock do pino, deve-se utilizar o registrador System Clock Gating Control Register 5 (SIM_SCGC5). Através da macro SIM_SCGC5_PORTx_MASK, onde x é a letra que corresponde ao PORT que pertence ao pino.

A configuração da funcionalidade do pino é feita pelo Signal Multiplexing, os canais dos periféricos Timer/PWM Module pertencem toda à terceira alternativa da tabela KL25 Signal Multiplexing and Pin Assignments. A tabela a seguir apresenta os pinos que podem ser configurados com sinal do PWM.

TPM0
Canal 0Canal 1Canal 2Canal 3Canal 4Canal 5
PTA3PTA4PTA5  PTA0
PTC1PTC2PTC3PTC4PTC8PTC9
PTD0PTD1PTD2PTD3PTD4PTD5
PTE24PTE25PTE29PTE30PTE31 
TPM1
PTA12PTA13    
PTB0PTB1    
PTE20PTE21    
TPM2
PTA1PTEA2    
PTB2PTB3    
PTB18PTB19    
PTE22PTE23    

Valor do Duty Cycle

A determinação do valor da Duty Cycle é através do registrador Channel (n) Value (TPMx_CnV). Para escrever neste registrador deve ser utilizada a macro TPM_CnV_REG(base,index), onde base é TPM (TPM0, TPM1 ou TPM2) e index é o número do canal. Na figura abaixo são apresentados os detalhes do registrador Channel (n) Value (TPMx_CnV).

A seguir é apresentado o código fonte do arquivo pwm.h.

/*
 * pwm.h
 *
 * Created on: 07/01/2017
 * Author: Evandro
 */
#ifndef SOURCES_PWM_H_
#define SOURCES_PWM_H_
 
#include "MKL25Z4.h" 
//#include "externs.h"
#include "stdbool.h"
 
// TPM clock source select
// Selects the clock source for the TPM counter clock
#define TPM_CLK_DIS 	0 	// Clock disabled
#define TPM_PLLFLL 		1 	// MCGFLLCLK clock or MCGPLLCLK/2 clock
#define TPM_OSCERCLK 	2 	// OSCERCLK clock
#define TPM_MCGIRCLK 	3 	// MCGIRCLK clock
// counter is disabled
#define TPM_CNT_DIS 	0 
// counter increments on every LPTPM counter clock
#define TPM_CLK 		1
// counter increments on rising edge of LPTPM_EXTCLK synchronized to the LPTPM counter clock
#define TPM_EXT_CLK 	2 
 
#define PS_1 			0 // Prescale Factor Selection
#define PS_2 			1
#define PS_4 			2
#define PS_8 			3
#define PS_16 			4
#define PS_32 			5
#define PS_64 			6
#define PS_128 			7
 
#define TPM_OC_TOGGLE 	TPM_CnSC_MSA_MASK|TPM_CnSC_ELSA_MASK
#define TPM_OC_CLR 		TPM_CnSC_MSA_MASK|TPM_CnSC_ELSB_MASK
#define TPM_OC_SET 		TPM_CnSC_MSA_MASK|TPM_CnSC_ELSA_MASK|TPM_CnSC_ELSB_MASK
#define TPM_OC_OUTL 	TPM_CnSC_MSB_MASK|TPM_CnSC_MSA_MASK|TPM_CnSC_ELSB_MASK
#define TPM_OC_OUTH 	TPM_CnSC_MSB_MASK|TPM_CnSC_MSA_MASK|TPM_CnSC_ELSA_MASK
 
#define TPM_PWM_H 		TPM_CnSC_MSB_MASK|TPM_CnSC_ELSB_MASK
#define TPM_PWM_L 		TPM_CnSC_MSB_MASK|TPM_CnSC_ELSA_MASK
 
#define EDGE_PWM 		0
#define CENTER_PWM 		1
 
bool pwm_tpm_Init(TPM_MemMapPtr tpm, uint16_t clk, uint16_t module, 
uint8_t clock_mode,uint8_t ps, bool counting_mode);

//void pwm_tpm_Ch_Init(TPM_MemMapPtr tpm, uint16_t channel, uint8_t mode);

bool pwm_tpm_Ch_Init(TPM_MemMapPtr tpm, uint16_t channel, uint8_t mode,
GPIO_MemMapPtr gpio,uint8_t pin);

void pwm_tpm_CnV(TPM_MemMapPtr TPMx, uint16_t channel, uint16_t value);
 
#endif /* SOURCES_PWM_H_ */

A seguir é apresentado o código fonte do arquivo pwm.c.

/*
 * pwm.c
 *
 * Created on: 07/01/2017
 * Author: Evandro
 *
 */
#include "pwm.h"
 
bool pwm_tpm_Init(TPM_MemMapPtr tpm, uint16_t clk, uint16_t module, uint8_t clock_mode,
                  uint8_t ps, bool counting_mode)
{
	if(tpm == TPM0)
	{
		SIM_SCGC6 |= SIM_SCGC6_TPM0_MASK;
	}
	else if(tpm == TPM1)
	{
		SIM_SCGC6 |= SIM_SCGC6_TPM1_MASK;
	}
	else if(tpm == TPM2)
	{
		SIM_SCGC6 |= SIM_SCGC6_TPM2_MASK;
	}
	else
	{
		return false;
	}

	SIM_SOPT2 |= SIM_SOPT2_TPMSRC(clk);

	tpm->MOD = module;

	tpm->SC |= TPM_SC_CMOD(clock_mode) | TPM_SC_PS(ps);

	if(counting_mode == CENTER_PWM)
	{
		tpm->SC |= TPM_SC_CPWMS_MASK;
	}
	else if(counting_mode == EDGE_PWM)
	{
		tpm->SC &= ~TPM_SC_CPWMS_MASK;
	}
	else
	{
		return false;
	}
	return true;
}
/****************************************************************************************
*
*****************************************************************************************/
bool pwm_tpm_Ch_Init(TPM_MemMapPtr tpm, uint16_t channel, uint8_t mode,
                     GPIO_MemMapPtr gpio,uint8_t pin)
{
	if(tpm == TPM0)
	{
		// PTA3 - CH0
		// PTA4 - CH1
		// PTA5 - CH2
		// PTA0 - CH5
		if(gpio == GPIOA)
		{
			if((channel<=2)||(channel==5))
			{
				if((pin>=3) || (pin<=5))
				{
					SIM_SCGC5 |= SIM_SCGC5_PORTA_MASK;
					PORT_PCR_REG(PORTA_BASE_PTR,pin) = PORT_PCR_MUX(3);
				}
				else return false;
			}
			else return false;
		}
		// PTC1 - CH0
		// PTC2 - CH1
		// PTC3 - CH2
		// PTC4 - CH3
		// PTC8 - CH4
		// PTC9 - CH5
		else if(gpio == GPIOC)
		{
			if(channel<=5)
			{
				if((pin>=1 || pin<=4) || (pin==8) || (pin==9))
				{
					SIM_SCGC5 |= SIM_SCGC5_PORTC_MASK;
					PORT_PCR_REG(PORTC_BASE_PTR,pin) = PORT_PCR_MUX(3);
				}
				else return false;
			}
			else return false;
		}
		// PTD0 - CH0
		// PTD1 - CH1
		// PTD2 - CH2
		// PTD3 - CH3
		// PTD4 - CH4
		// PTD5 - CH5
		else if(gpio == GPIOD)
		{
			if(channel<=5)
			{
				if(pin<=5)
				{
					SIM_SCGC5 |= SIM_SCGC5_PORTD_MASK;
					PORT_PCR_REG(PORTD_BASE_PTR,pin) = PORT_PCR_MUX(3);
				}
				else return false;
			}
			else return false;
		}
		// PTE24 - CH0
		// PTE25 - CH1
		// PTE29 - CH2
		// PTE30 - CH3
		// PTE31 - CH4
		else if(gpio == GPIOE)
		{
			if(channel<=4)
			{
				if( (pin>=24 || pin<=25) || (pin>=29 || pin<=31) )
				{
					SIM_SCGC5 |= SIM_SCGC5_PORTE_MASK;
					PORT_PCR_REG(PORTE_BASE_PTR,pin) = PORT_PCR_MUX(3);
				}
				else return false;
			}
			else return false;
		}
		else return false;
	}
	else if(tpm == TPM1)
	{
		if(channel <= 1)
		{
			// PTA12 - CH0
			// PTA13 - CH1
			if(gpio == GPIOA)
			{
				if(pin>=12 || pin<=13)
				{
					SIM_SCGC5 |= SIM_SCGC5_PORTA_MASK;
					PORT_PCR_REG(PORTA_BASE_PTR,pin) = PORT_PCR_MUX(3);
				}
				else return false;
			}
			// PTB0 - CH0
			// PTB1 - CH1
			else if(gpio == GPIOB)
			{
				if(pin<=1)
				{
					SIM_SCGC5 |= SIM_SCGC5_PORTB_MASK;
					PORT_PCR_REG(PORTB_BASE_PTR,pin) = PORT_PCR_MUX(3);
				}
				else return false;
			}
			// PTE20 - CH0
			// PTE21 - CH1
			else if(gpio == GPIOE)
			{
				if(pin>=20 || pin<=21)
				{
					SIM_SCGC5 |= SIM_SCGC5_PORTE_MASK;
					PORT_PCR_REG(PORTE_BASE_PTR,pin) = PORT_PCR_MUX(3);
				}
				else return false;
			}
			else return false;
		}
		else return false;
	}
	else if(tpm == TPM2)
	{
		if(channel <= 1)
		{
			// PTA1 - CH0
			// PTA2 - CH1
			if(gpio == GPIOA)
			{
				if(pin>=1 || pin<=2)
				{
					SIM_SCGC5 |= SIM_SCGC5_PORTA_MASK;
					PORT_PCR_REG(PORTA_BASE_PTR,pin) = PORT_PCR_MUX(3);
				}
				else return false;
			}
			// PTB2 - CH0
			// PTB3 - CH1
			// PTB18 - CH0
			// PTB19 - CH1
			else if(gpio == GPIOB)
			{
				if((pin>=2||pin<=3) || (pin>=18||pin<19))
				{
					SIM_SCGC5 |= SIM_SCGC5_PORTB_MASK;
					PORT_PCR_REG(PORTB_BASE_PTR,pin) = PORT_PCR_MUX(3);
				}
				else return false;
			}
			// PTE22 - CH0
			// PTE23 - CH1
			else if(gpio == GPIOE)
			{
				if(pin>=22 || pin<=23)
				{
					SIM_SCGC5 |= SIM_SCGC5_PORTE_MASK;
					PORT_PCR_REG(PORTE_BASE_PTR,pin) = PORT_PCR_MUX(3);
				}
				else return false;
			}
			else return false;
		}
		else return false;
	}
	else return false;

	TPM_CnSC_REG(tpm, channel) |= mode;

	return true;
}
/****************************************************************************************
*
*****************************************************************************************/
void pwm_tpm_CnV(TPM_MemMapPtr tpm, uint16_t channel, uint16_t value)
{
	TPM_CnV_REG(tpm, channel) = value;
}
/***************************************************************************************/

Aplicação com Servomotor

Servomotores são dispositivos eletromecânicos, quando aplicado um sinal elétrico pode movimentar o seu eixo em uma determinada posição angular. Para realizar o posicionamento do eixo é utilizado o sinal de PWM, que deve possuir uma frequência de 50Hz. A posição angular é determinada pela largura do pulso. O servomotor utilizado neste artigo é o modelo HXT900, que possibilita movimentar o seu eixo da posição 0º a 180º. A figura abaixo ilustra com detalhes o controle da posição do eixo do servomotor.

Fonte da imagem: https://www.pictronics.com.br/downloads/apostilas/servomotores.pdf 

Para o desenvolvimento da aplicação com o PWM foi utilizado o pino PTB0 como saída do sinal. Como dito acima, o valor da frequência deve ser de 50Hz. Para configurá-la é necessário calcular o valor para o TPMxMOD. A fonte de clock utilizada é MCGFLLCLK clock or MCGPLLCLK/2, que por padrão o seu valor é 20971520 (encontrado no arquivo system_MKL25Z4.h) e fator de divisão Prescale utilizado é de 128. Utilizando a segunda equação, com esses valores apresentados, temos que o valor para o TPMxMOD é aproximadamente 3276.

#define DEFAULT_SYSTEM_CLOCK 20971520u /* Default System clock value */

Sabemos que para um sinal de 50Hz, temos um período de 20 milissegundos. Para calcular o valor mínimo (1 milissegundo) basta dividir o valor encontrado para o TPMxMOD por 20, que corresponde aproximadamente a 163 e para calcular o valor máximo (2 milissegundos) do Duty Cycle é usado o dobro do valor mínimo, que é aproximadamente 327.

A seguir é apresentado o código fonte da aplicação. Arquivo main.c.

/*
** Projeto: servomotor lib PWM
** Autor: Evandro Teixeira
*/
#include "pwm.h"
#include "MKL25Z4.h"
 
#define TPM_MODULE 3275
#define DUTY_MIN 165
#define DUTY_MAX 325
 
uint16_t i = 0;
uint16_t x = 0;
 
int main(void)
{
	x = DUTY_MIN;
	// Inicializa TPM
	pwm_tpm_Init(TPM1_BASE_PTR, TPM_PLLFLL, TPM_MODULE, TPM_CLK, PS_128, EDGE_PWM);
	// Inicializa I/O com o sinal do PWM
	pwm_tpm_Ch_Init(TPM1, 0, TPM_PWM_H,GPIOB,0);
	// Set valor Duty Cycle
	pwm_tpm_CnV(TPM1, 0, x);
 
	for (;;)
	{
		// delay
		for(i=0;i<10000;i++); 
		// Set valor Duty Cycle
		pwm_tpm_CnV(TPM1, 0,x);
		// Incrementa o valor de x
		x++;
		if(x >= DUTY_MAX) x = DUTY_MIN;
	}
	return 0;
}

Conclusão

Neste artigo foi apresentada mais uma biblioteca de software para a Freedom Board KL25Z e uma aplicação simples com servo motor, demonstrando o uso do PWM.

Nos próximos artigos vamos apresentar outras bibliotecas de software (Timer, UART e entre outras) para utilizar com FRDM-KL25. A biblioteca apresentada aqui está disponível no meu GitHub.

Referências

Modulação por largura de pulso – Wikipédia

PWM – Wikipédia

Servomotor – Wikipédia

Servomotores – Pictronics 

Github – Library-FRDM-KL25Z 

Reference Manuals KL25Z

Bibliotecas de Software para FRDM-KL25Z

Biblioteca I2C para FRDM-KL25Z PIT – Periodic Interrupt Timer para FRDM-KL25Z
Licença Creative Commons Esta obra está licenciada com uma Licença Creative Commons Atribuição-CompartilhaIgual 4.0 Internacional.
Comentários:
Notificações
Notificar
6 Comentários
recentes
antigos mais votados
Inline Feedbacks
View all comments
Jorge
Jorge
27/06/2018 18:11

jorgemateus142@gmail.com, esse e o meu email

Jorge
Jorge
27/06/2018 17:50

Olá Evandro, Eu sou novo na comunidade de microcontroladores, estou tentando controlar um motor DC usando a sua função PWM mas não consigo reduzir a velocidade do motor. Eu usei um transistor como foi sugerido por muitos para controlar a velocidade usando uma bateria externa, mas mesmo assim não obtivi bons resultados. Eu sei que o meu circuito está certo porque eu fui capaz de controlar a velocidade usando o mesmo circuito e um function generator, o que sugere que eu esteja possivelmente a configurar mal o microcontrolador. Agradecia muito se me podesse providenciar o seu número de telefone para… Leia mais »

Jorge
Jorge
Reply to  Evandro Teixeira
20/07/2018 19:34

Muito obrigado evandro! Desculpa a demora em responder o seu email, mas eu estava viajando. Eu já encomendei o H-bridge sugerido, mas o problema é que eu não entendo por completo como funciona o seu main para controlar o servo. Como eu faço se eu quiser aumentar o Duty cycle para 50%?
Você definiu um max duty cycle de 325, de onde é que você tirou esse valor? Em geral, como funciona o seu código?

Evandro Teixeira
Evandro Teixeira
Reply to  Jorge
23/07/2018 14:27

Antes de explicar o algoritmo apresentado no artigo, é importante lembrar do principio de funcionamento do servomotor. O ângulo do servomotor é determinado a partir da duração da largura de pulso (PWM) enviado à entrada sinal do servomotor. Este sinal deve ser de uma onda quadrada com o período de 20 ms (50Hz), e a largura do pulso deve variar de 1 ms a 2 ms, para ajusta o ângulo do servo motor. Sabendo do principio de funcionamento do servomotor, aplicação desenvolvida consistem em variar o ângulo de 0º a 90°, mudando apenas a largura do pulso (1ms a 2ms).… Leia mais »

Home » Software » Biblioteca PWM para FRDM-KL25Z

EM DESTAQUE

WEBINARS

VEJA TAMBÉM

JUNTE-SE HOJE À COMUNIDADE EMBARCADOS

Talvez você goste: