Uma arquitetura PWM em VHDL

PWM em VHDL

Introdução

Neste artigo vamos implementar um PWM em VHDL. PWM é a abreviação de Pulse Width Modulation ou Modulação por Largura de Pulso. Este circuito é de grande importância e muito utilizado no nosso domínio de estudo, aplicando-se por exemplo para: controle de potência de motores e outras cargas, fontes chaveadas, modulação de sinais como IrDA (Infrared Data Association) e outras aplicações.

A implementação digital deste circuito é vantajosa em relação à implementação analógica devido à diminuição de ruído, tendo em vista que o circuito digital é menos afetado pelas variações de tensão, corrente, temperatura, entre outros fatores. Além disso, tem-se uma grande flexibilidade de especificar a frequência de operação, embarcá-lo e reprogramá-lo.

Em contrapartida, existem algumas limitações como a escolha do Duty Cycle, devido a não se ter uma variação continua deste e sim discreta devido a sua dependência em relação ao relógio (clock).

Princípio de funcionamento do PWM

Em uma frequência fixa pode-se variar o ciclo ativo (Duty Cycle) da onda e com isso tem-se um valor médio de tensão e uma potência entregue à carga controlados pela escolha do Duty Cycle. O valor do ciclo ativo e a tensão média são determinados como segue:

PWM em VHDL - ONDA PWM
Figura 1: Possível forma de onda de um PWM

Para a implementação deste projeto, deve-se pensar no relógio e com isso fazer uma análise discreta do PWM. Na figura abaixo pode-se ver um possível comportamento de um sinal PWM digital que nos ajuda a compreender o que deve ser feito no código.

PWM em VHDL - ONDA PWM DIGITAL
Figura 2: Possível saída de PWM digital

Observando a figura 2 e considerando a entrada TIMER carregada com o valor 15 (isto é, “1111”) e a variável CONTADOR contando as bordas de subida do relógio, então pode-se escolher um valor do Duty Cycle. Desta forma o CONTADOR vai contar até chegar ao valor CYCLE OFF e alterará o estado do PWM para alto, e quando chegar ao valor do TIMER alterará o estado do PWM para baixo e recomeçará a contar de zero. Pode-se notar então que, se o CONTADOR é alterado a cada período do clock não é possível obter uma faixa contínua de 0% à 100% do Duty Cycle. Para ilustrar, a tabela abaixo mostra os possíveis valores do ciclo ativo para esse caso:

Tabela 1: Duty Cycles possíveis para um TIMER de 4 bits

Poderá surgir a seguinte pergunta: “Não existe Duty Cycle em 0%?” A resposta é sim! O ciclo ativo é nulo quando a entrada ENABLE for zero e não quando DUTY=0. Foi escolhido projetar desta maneira para obter um total aproveitando das escolhas do ciclos ativo.

Exemplo: Ao carregar o TIMER com 1 e o Duty Cycle com zero, teremos a saída do PWM igual a uma onda com frequência metade da frequência do relógio e Duty Cycle 50%; se o Duty vale 1 a saída é 100%; se o ENABLE é ativo saída do PWM é zero. (Imagine agora esse mesmo exemplo com a condição em VHDL “se DUTY=0, saída igual a zero”).

Foi escolhido para o projeto um TIMER e um DUTY de 8 bits flexibilizando a escolha da frequência de trabalho e de Duty Cycles.

Diante do exposto, não é difícil ver que a frequência do PWM pode ser determinada pela fórmula abaixo, onde o valor do TIMER é somado mais 1, pois o TIMER começa em zero, logo conta-se mais 1 pulso do clock.

formula2

Circuito Prescaler

Para melhorar a flexibilidade da frequência do PWM foi implementado o circuito para dividir a frequência do clock, circuito denominado PRESCALER. Foi escolhida a possibilidade de escolher três PRESCALER’s 1, 4 e 16, que são, respectivamente, a frequência do clock original, a frequência dividida por 4 e a frequência dividida por 16. E assim a frequência do PWM pode ser reescrita como segue:

formula3

O esquema abaixo mostra como foi pensado e descrito o código VHDL. Pode-se ver que se o reset não estiver ativo, todos os sinais de clock estão disponíveis no MUX 4:1, onde a entrada SEL_PR de 2 bits seleciona o clock desejado na saída do MUX. Esta entrada determina o PRESCALER da seguinte maneira: SEL_PR = “00” implica PRESCALER = 1; SEL_PR = “01” implica PRESCALER = 4; SEL_PR = “10” implica PRESCALER = 16 e SEL_PR = “00” implica saída de clock zero (opção inválida).

PWM em VHDL - PRESCALER
Figura 3: Esquema do circuito PRESCALER

VHDL PRESCALER

O código da implementação deste circuito foi feito como mostrado em seguida:

------------------------------
--     MODULO PRESCALER     -- 
------------------------------
-- 
-- EMBARCADOS.COM
--
-- Ao usar o projeto dê os créditos!
-- by Gabriel Villanova

library IEEE;
use	  IEEE.std_logic_1164.all;
use 	  IEEE.numeric_std.all;

entity PRESCALER is
	port ( s_CLK     : in  std_logic;
			 s_RST     : in  std_logic;
			 s_SEL_PR  : in  std_logic_vector (1 downto 0);
			 OUT_CLK   : out std_logic);
end PRESCALER;

architecture RTL of PRESCALER is
	
	signal CLK4  : std_logic;
 	signal CLK16 : std_logic;
	
	begin
	process(s_CLK,s_RST)
	variable CONT4  : integer range 0 to 2;
	variable CONT16 : integer range 0 to 8;
	begin
		if( s_RST = '1' ) then
			CLK4 	  <= '0';
			CLK16   <= '0';
			CONT4   :=  0;
			CONT16  :=  0;
		elsif( s_CLK = '1' and s_CLK'EVENT ) then

			-- GERAR AS DIVISOES DE FREQUENCIA /4 e /16
			-- OBS: VARIAVEL DO TIPO INTEIRA É SEQUENCIAL, OU SEJA, A ORDEM DESSE CÓDIGO IMPORTA

			CONT4  := CONT4  + 1;
			CONT16 := CONT16 + 1;
   		if( CONT4 = 2 ) then
					CLK4 <= not CLK4;
					CONT4 := 0;
			end if;
			if( CONT16 = 8 ) then
						CLK16 <= not CLK16;
						CONT16  := 0;
			end if;
			
		end if;
	end process;
	
	-- MUX 4:1
	-- SELECIONA O CLK ESCOLHIDO
	
	with s_SEL_PR select
	OUT_CLK <= s_CLK and (not s_RST) when "00",
				  CLK4  when "01",
				  CLK16 when "10",
				  '0'   when "11",
				  '0'   when others;
				  
end RTL;

TESTBENCH PRESCALER

A seguir tem-se o código do testbench feito usando um clock de entrada em 1MHz.

----------------------------------
--     TEST BENCH PRESCALER     --
----------------------------------
--
-- EMBARCADOS.COM
--
-- Ao usar o projeto dê os créditos!
-- by Gabriel Villanova

library IEEE;
use     IEEE.std_logic_1164.all;

entity TB_PRESCALER is end TB_PRESCALER;

architecture RTL of TB_PRESCALER is

  component PRESCALER
  port ( s_CLK     : in std_logic;
         s_RST     : in std_logic;
         s_SEL_PR  : in std_logic_vector (1 downto 0);
         OUT_CLK   : out std_logic);
  end component;

  signal sig_CLK : std_logic := '0';
  signal sig_RST : std_logic;
  signal sig_PR : std_logic_vector (1 downto 0);
  signal sig_OCLK : std_logic;

  begin
    U0: PRESCALER port map (sig_CLK,sig_RST,sig_PR,sig_OCLK );

    sig_CLK <= not sig_CLK after 500 ns; -- CLK = 1M Hz
    sig_RST <= '1', '0' after 25 ns;

    process
    begin
      wait for 25 ns;

      -- CLK/1
      sig_PR <= "00";
      wait for 16000 ns;
    
      -- CLK/4
      sig_PR <= "01";
      wait for 16000 ns;

      -- CLK/16
      sig_PR <= "10";
      wait for 32000 ns;
 
      -- CLK=0
      sig_PR <= "11";
      wait for 16000 ns;
    end process;
  end RTL;

Abaixo tem-se as formas de ondas encontradas, onde pode-se validar o bom funcionamento do nosso circuito:

PWM em VHDL - Wave Form PRESCALER
Figura 4: Forma de onda do PRESCALER

CIRCUITO PWM

O esquema do projeto final é mostrado abaixo, onde tem-se as entradas TIMER (8 bits), DUTY (8 bits), SEL_PR (2 bits), ENABLE CLK e RST (1 bit), e logicamente a saída do PWM (1 bit). Com o embasamento teórico feito até aqui, pode-se entender/descrever o código facilmente.

PWM em VHDL - ESQUEMA Projeto PWM
Figura 5: Esquema da arquitetura do PWM

VHDL

O código abaixo mostra o que foi feito:

------------------------------
--     MODULO PRESCALER     -- 
------------------------------
-- 
-- EMBARCADOS.COM
--
-- Ao usar o projeto dê os créditos!
-- by Gabriel Villanova

library IEEE;
use     IEEE.std_logic_1164.all;
use     IEEE.std_logic_unsigned.all;

entity PWM is 
	port ( CLK 	 	: in  std_logic;
			 RST 	 	: in  std_logic;
			 ENABLE  : in  std_logic;
			 SEL_PR	: in  std_logic_vector (1 downto 0);
			 TIMER 	: in  std_logic_vector (7 downto 0);
			 DUTY  	: in  std_logic_vector (7 downto 0);
			 PWM_OUT : out std_logic);
end entity;

architecture RTL of PWM is
	
	--				        Fclk
	-- Fpwm = ------------------------
	--         (TIMER + 1)*PRESCALER
	--
	-- TIMER 8bits -> 0 à 255
	-- PRESCALER   -> 1, 4 ou 16
	
	--
	-- DUTY X%   = Duty/TIMER*100%
	
	component PRESCALER
		port ( s_CLK     : in  std_logic;
				 s_RST     : in  std_logic;
				 s_SEL_PR  : in  std_logic_vector (1 downto 0);
				 OUT_CLK   : out std_logic);
	end component;
	
	signal s_OUT_CLK  : std_logic; 
	signal CYCLE_OFF  : std_logic_vector (7 downto 0);
	signal s_PWM_OUT  : std_logic;
	signal CONT       : std_logic_vector (7 downto 0);
	
	begin
	
		U0 : PRESCALER port map (CLK,RST,SEL_PR,s_OUT_CLK);
		
		CYCLE_OFF <= TIMER - DUTY - '1';
		
		-- CASO ESPECIAL:
		
		with TIMER select
			PWM_OUT <= s_OUT_CLK and ENABLE and (not RST) when "00000000",
						  s_PWM_OUT when others;
		
		process(s_OUT_CLK,RST)
		begin
			if( RST = '1' ) then
				s_PWM_OUT <= '0';
				CONT      <= "00000000";
			elsif( s_OUT_CLK = '1' and s_OUT_CLK'EVENT ) then
				
				if( ENABLE = '1' ) then
					
					-- CONTA PULSOS DO CLOCK
					CONT <= CONT + '1';
				
					if ( DUTY >= TIMER ) then
						s_PWM_OUT <= '1';
						CONT      <= "00000000";
					elsif ( CONT = CYCLE_OFF ) then
						s_PWM_OUT <= '1';
					elsif ( CONT = TIMER ) then
						s_PWM_OUT <= '0';
						CONT      <= "00000000";
					end if;
				
				else
					s_PWM_OUT <= '0'; 
					CONT      <= "00000000";
				end if;
			end if;
		end process;
	end RTL;

TESTBENCH

Foram feitos os testes, porém devido à gama muito grande de resultados, vamos fazer um exemplo prático para demonstração.

Iremos usar o circuito para gerar um PWM em 38kHz, frequência geralmente usada para modulação de controles remotos em infravermelho, TV, DVD, etc. Portanto, precisamos escolher o PRESCALER e o TIMER.

Considerando um clock de 1MHz e usando o PRESCALER = 1, achamos o valor do TIMER =25.31, seguindo a formula:

formula4

Da mesma maneira se usarmos PRESCALER = 4, achamos o valor do TIMER = 5.57 e para PRESCALER = 16 temos um TIMER = 0.64. Dessa forma, a melhor escolha é o PRESCALER = 1 e carregar o TIMER com 25 (maior resolução).

Como o TIMER deve ser arredondado para um inteiro mais próximo temos um erro na frequência. Ou seja:

formula5

De qualquer maneira, mesmo não tendo a frequência perfeitamente em 38kHz, esse resultado é bem aceitável para aplicações práticas, como a do nosso exemplo de modular sinal de controle remoto nessa frequência.

O test bench feito para esse exemplo é mostrado abaixo, onde foi variado o Duty Cycle a cada período do PWM, exemplificando todos os casos de Duty.

-- MODULO PRESCALER 
-- 
-- Ao usar o projeto dê os créditos!
-- by Gabriel Villanova

library IEEE;
use     IEEE.std_logic_1164.all;
use     IEEE.std_logic_unsigned.all;

entity TB_PWM is end TB_PWM;

architecture RTL of TB_PWM is
	
	component PWM
	port ( CLK 	 	: in  std_logic;
			 RST 	 	: in  std_logic;
			 ENABLE  : in  std_logic;
			 SEL_PR	: in  std_logic_vector (1 downto 0);
			 TIMER 	: in  std_logic_vector (7 downto 0);
			 DUTY  	: in  std_logic_vector (7 downto 0);
			 PWM_OUT : out std_logic);
	end component;
	
	signal sig_CLK 	 : std_logic := '0';
	signal sig_RST 	 : std_logic;
	signal sig_ENABLE  : std_logic;
	signal sig_SEL_PR  : std_logic_vector (1 downto 0);
	signal sig_TIMER   : std_logic_vector (7 downto 0);
	signal sig_DUTY    : std_logic_vector (7 downto 0) := "00000000";
	signal sig_PWM_OUT : std_logic;

	begin
		M1: PWM port map (sig_CLK,sig_RST,sig_ENABLE,sig_SEL_PR,sig_TIMER,sig_DUTY,sig_PWM_OUT);
		
		sig_CLK 	  <= not sig_CLK after 500 ns; -- Fclk = 1M Hz.
		sig_RST 	  <= '1','0' after 750 ns;
		sig_ENABLE <= '0','1' after 1250 ns;
		
		process
		variable i : integer range 0 to 255;
		begin
			wait for 1250 ns;
			
			sig_SEL_PR <= "00";
			sig_TIMER  <= "00011001";

			for i in 0 to 25 loop
				sig_DUTY <= "00000000" + i;
				wait for 25 us;
			end loop;
		end process;
	end RTL;
	
			

Pode-se ver na figura gerada o círculo vermelho mostrando o período de PWM esperado. Além disso, a modulação do sinal.

PWM em VHDL - Forma de onda do PWM em 38kHz variando Duty Cycle
Figura 6: Forma de onda do PWM em 38kHz variando Duty Cycle

INTERESSANTE

Abaixo é mostrado um esquema de PWM retirado do datasheet do microcontrolador PIC16F877A. Pode-se ver registradores para Duty Cycle, o TIMER (TMR2) e comparadores, que de modo geral é similar ao que foi feito nesse projeto.

Dessa forma podemos ter uma ideia do que acontece “por trás” do código C, quando necessitamos de usar uma saída PWM de um microcontrolador por exemplo, ganhando confiança ao programá-lo e ficando sensíveis aos possíveis erros de programação. Vamos estudar HDL!

Diagrama simplificado de PWM PIC16F877A
Figura 7: Diagrama simplificado de PWM PIC16F877A

Espero que tenham gostado do artigo! Até a próxima!

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
13 Comentários
recentes
antigos mais votados
Inline Feedbacks
View all comments
Doulgas
Doulgas
15/01/2021 21:00

@Grabiel
tentei rodar o seu código, e apareceu o seguinte erro:
Error (10533): VHDL Wait Statement error at TB_PWM.vhd(40): Wait Statement must contain condition clause with UNTIL keyword
a linha 40 no código é: wait for 1250 ns;
O que pode ser isso?

cleiton dal agnol
cleiton dal agnol
29/07/2018 22:33

Sou novo no assunto, porém estou me aprofundando o máximo que posso. Minha dificuldade foi em gerar um pwm de 50 KHz, ao compilar da um erro do TimeQuest e de design. Alguém poderia me dar algumas dicas?

Euripedes Rocha Filho
Euripedes Rocha Filho
29/02/2016 07:48

Pode ser um exercício interessante transformar esse testbench em um testbench automatizado.

Vinicius Lagrota
Vinicius Lagrota
25/02/2016 11:26

Ótima matéria! Trabalho com HDL desde o começo da minha graduação e continuo utilizando no meu mestrado e acho sempre interessante quando esse tipo de linguagem ganha espaço em um site grande como este! Parabéns aos envolvidos e que venham mais matérias como está!

André Castelan
24/02/2016 19:10

Eu utilizava muito o MyHDL, nao utilizo mais pois nao estou trabalhando com FPGA no momento.

Recomendo de mais.

Caio Alonso
Caio Alonso
24/02/2016 19:07

Obrigado por compartilhar os artigos André.
É interessante a abordagem da utilização de variáveis na solução do algoritmo do código gray. Eu, particularmente, tenho receio do que o compilador interpreta e transforma em circuito. Concordo que os compiladores estão muito mais eficientes hoje em dia e que talvez não fosse necessário se preocupar com isso, mas a pulga sempre fica atrás da orelha. Não conheço o MyHDL. Vocês utilizam essa linguagem?

André Castelan
24/02/2016 17:47

Usar variaveis de forma correta eh excelente, nao tenham medo da ferramenta. Otimos artigos: https://www.jandecaluwe.com/hdldesign/thinking-software-rtl.html https://www.jandecaluwe.com/hdldesign/the-case-for-a-better-hdl.html Variables are one of the best understood programming concepts, and they are supported by both Verilog and VHDL. Nevertheless, even though mainstream HDL design is more than 20 years old, HDL designers still haven’t figured out how to use variables properly. Instead of teaching how to use variables, mainstream Verilog guidelines ban them altogether (in clocked processes). Thus, the confusion is “solved” by brute force amputation. In reality, local variables work just fine in clocked processes and synthesis supports them without a problem. They… Leia mais »

Gabriel Villanova
Gabriel Villanova
24/02/2016 17:01

Caio,
Muito obrigado pelo feedback.
Eu estou de acordo, quando
escrevemos sinais ao invés de variáveis podemos ter mais controle do que
será sintetizado, além disso, meio que repetindo o que você já disse,
não temos necessidade de utilizar variáveis, afinal de contas tudo é
bit.
Bom, como o artigo está a nível de “tutorial” escolhi fazer dessa forma pra explorar a linguagem.
Estou aberto a discussões sobre o assunto!

Caio Alonso
Caio Alonso
24/02/2016 12:52

Parabéns pelo artigo Gabriel.
Só queria deixar uma observação quanto a utilização de variáveis em processos deve ser utilizada com cautela. Algumas ferramentas de síntese podem inferir um circuito não desejável.
Eu, particularmente, evito o máximo a utilização de variáveis para deixar o código RTL mais independente do tipo de software de síntese. Até o momento não tive que desenvolver algum código em que fui obrigado a utilizar variáveis. Aproveito para abrir uma discussão sobre a utilização de sinais e variáveis em processos de VHDL.
Quais são as opniões de vocês?

Euripedes Rocha Filho
Euripedes Rocha Filho
Reply to  Caio Alonso
29/02/2016 07:46

Como colocado pelo @andrecas:disqus, não vejo qualquer problema em usar variáveis desde que se saiba descrever o circuito. Utilização de variáveis no código não vai torná-lo mais dependente IMHO. Uma coisa que me irrita bem mais é o impacto do circuito de reset na hora de portar entre Xilinx e Altera por exemplo. Mas mesmo isso no fim das contas não deve ser uma barreira pra portabilidade entre os dois principais players.

Caio Alonso
Caio Alonso
Reply to  Euripedes Rocha Filho
29/02/2016 19:35

Obrigado por compartilhar sua opinião Euripedes! Um artigo sobre metodologias de reset em projetos de circuitos digitais (síncrono e assíncrono) seria bem interessante!

André Castelan
Reply to  Caio Alonso
29/02/2016 19:39

https://embarcados.com.br/arquitetura-reset-fpga/

😛

com certeza o Euripedes poderia escrever mais ( e melhor )

Caio Alonso
Caio Alonso
Reply to  André Castelan
29/02/2016 19:47

Desculpe-me o desconhecimento do artigo. Acho que estou precisando aprender a fazer buscas melhores no site. 😎

Home » Hardware » Sistemas Digitais » Uma arquitetura PWM em VHDL

EM DESTAQUE

WEBINARS

VEJA TAMBÉM

JUNTE-SE HOJE À COMUNIDADE EMBARCADOS

Talvez você goste: