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 (Pulse–Width 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.

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_MASK, TPM1 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 0 | Canal 1 | Canal 2 | Canal 3 | Canal 4 | Canal 5 |
| PTA3 | PTA4 | PTA5 | PTA0 | ||
| PTC1 | PTC2 | PTC3 | PTC4 | PTC8 | PTC9 |
| PTD0 | PTD1 | PTD2 | PTD3 | PTD4 | PTD5 |
| PTE24 | PTE25 | PTE29 | PTE30 | PTE31 | |
| TPM1 | |||||
| PTA12 | PTA13 | ||||
| PTB0 | PTB1 | ||||
| PTE20 | PTE21 | ||||
| TPM2 | |||||
| PTA1 | PTEA2 | ||||
| PTB2 | PTB3 | ||||
| PTB18 | PTB19 | ||||
| PTE22 | PTE23 |
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.

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





jorgemateus142@gmail.com, esse e o meu email
OK!
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 »
Ola boa noite jorge,
Segue alguns links, com material que vai te ajudar na montagem do seu circuito elétrico, para o controle de motor DC:
https://www.clubedaeletronica.com.br/Eletronica/PDF/Ponte%20H.pdf
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?
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 »