Implementando um Controlador PWM em VHDL: Projeto, Simulação e Demo em FPGA
Modulação por largura de pulso (PWM) transforma um pino digital de FPGA em um poderoso controlador analógico. Alternando a alimentação entre ligado e desligado em alta velocidade, o PWM entrega a corrente média exata que um dispositivo precisa, sem nunca gerar uma tensão analógica verdadeira.
Aplicações típicas de PWM incluem saída de áudio para alto-falantes, dimerização de LEDs e controle de torque de motores de indução como servomotores, ventiladores de computador, bombas e motores DC brushless para veículos elétricos.
Veja também: Controlador de servo RC usando PWM de pino FPGA
Como o PWM Funciona
Quando uma fonte de alimentação é alternada entre 100% e 0% em alta frequência, a corrente média vista pela carga é diretamente proporcional à fração de tempo que a alimentação fica ligada. O diagrama a seguir ilustra o princípio:

Ciclo de Trabalho
O ciclo de trabalho define por quanto tempo a saída PWM permanece alta durante cada período. Expresso como porcentagem, é calculado a partir do valor do contador binário:

Em uma implementação VHDL, tratamos o ciclo de trabalho como um inteiro sem sinal, nos dando a resolução total do contador PWM. Um ciclo de trabalho de 0% desliga a saída permanentemente, enquanto 100% a mantém continuamente ligada. A resolução alcançável é determinada pela largura do contador.
Para converter o valor binário de ciclo de trabalho para porcentagem, use:
\mathit{duty\_cycle\_percentage} = \frac{\mathit{commanded\_duty\_cycle} * 100}{2^\mathit{pwm\_bits} - 1}
Frequência PWM
A frequência PWM é o inverso do período:
\mathit{pwm\_freq} = \frac{1}{\mathit{pwm\_period}}
Escolher a frequência certa depende do dispositivo alvo. Para LEDs, algumas centenas de hertz são suficientes para evitar flicker visível. Motores DC brushless, por outro lado, tipicamente operam na faixa de dezenas de quilohertz; frequência muito baixa pode produzir vibrações mecânicas, enquanto frequência muito alta desperdiça potência.

Como as chaves MOSFET são mais lentas que a lógica do FPGA, operar o PWM muito rápido pode fazer o transistor passar tempo em um estado parcialmente condutor, dissipando calor extra.
Módulo Gerador PWM
Abaixo está uma implementação VHDL genérica de um controlador PWM que pode ser instanciado com qualquer largura de contador e fator de divisão de clock.
Declaração da Entidade
entity pwm is generic ( pwm_bits : integer; clk_cnt_len : positive := 1 ); port ( clk : in std_logic; rst : in std_logic; duty_cycle : in unsigned(pwm_bits - 1 downto 0); pwm_out : out std_logic ); end pwm;
As portas clk e rst fornecem controle síncrono. A entrada duty_cycle tem largura definida por pwm_bits, então a resolução do contador é diretamente configurável. O sinal pwm_out é roteado para o pino do FPGA que aciona o gate do MOSFET.
Sinais Internos
signal pwm_cnt : unsigned(pwm_bits - 1 downto 0); signal clk_cnt : integer range 0 to clk_cnt_len - 1;
O contador pwm_cnt espelha a largura do ciclo de trabalho. O contador clk_cnt implementa um divisor de clock opcional; definir clk_cnt_len como 1 desabilita o divisor e remove a lógica associada.
Processo Divisor de Clock
CLK_CNT_PROC : process(clk) begin if rising_edge(clk) then if rst = '1' then clk_cnt <= 0; else if clk_cnt < clk_cnt_len - 1 then clk_cnt <= clk_cnt + 1; else clk_cnt <= 0; end if; end if; end if; end process;
Com o padrão clk_cnt_len = 1, ferramentas de síntese eliminarão este processo automaticamente.
Processo de Saída PWM
PWM_PROC : process(clk) begin if rising_edge(clk) then if rst = '1' then pwm_cnt <= (others => '0'); pwm_out <= '0'; else if clk_cnt_len = 1 or clk_cnt = 0 then pwm_cnt <= pwm_cnt + 1; pwm_out <= '0'; if pwm_cnt = unsigned(to_signed(-2, pwm_cnt'length)) then pwm_cnt <= (others => '0'); end if; if pwm_cnt < duty_cycle then pwm_out <= '1'; end if; end if; end if; end if; end process;
O contador incrementa apenas quando clk_cnt é zero, efetivamente controlando a frequência PWM. A condicional que reseta pwm_cnt no seu segundo maior valor garante que um ciclo de trabalho de 100% seja alcançável.
\mathit{pwm\_hz} = \frac{\mathit{clk\_hz}}{(2^\mathit{pwm\_bits} - 1) * \mathit{clk\_cnt\_len}}
Projeto Top-Level
Para demonstrar o controlador PWM, o projeto aciona o LED de power-on em uma FPGA Lattice iCEstick. O oscilador de 12 MHz da placa alimenta um contador que controla a intensidade do LED em padrão dente de serra.

Declaração da Entidade
entity pwm_led is generic ( pwm_bits : integer := 8; cnt_bits : integer := 25; clk_cnt_len : positive := 47 ); port ( clk : in std_logic; rst_n : in std_logic; led_1 : out std_logic; led_2 : out std_logic; led_3 : out std_logic; led_4 : out std_logic; led_5 : out std_logic ); end pwm_led;
O clock de 12 MHz e os genéricos escolhidos produzem uma frequência PWM de aproximadamente 1 kHz:
\frac{12e6}{(2^8 - 1) * 47} \approx 1 \text{ kHz}
O genérico cnt_bits aciona um contador dente de serra de 25 bits que controla o ciclo de trabalho do LED. Seu período é cerca de 2.8 Hz:
\frac{2^{\mathit{cnt\_bits}}}{\mathit{clk\_hz}} = \frac{2^{25}}{12e6} \approx 2.8 \text{ Hz}
Apenas o LED 5 é usado; os outros LEDs são amarrados a baixo para evitar iluminação indesejada.
Sinais Internos e Conexões
architecture str of pwm_led is signal rst : std_logic; signal cnt : unsigned(cnt_bits - 1 downto 0); signal pwm_out : std_logic; alias duty_cycle is cnt(cnt'high downto cnt'length - pwm_bits); begin led_1 <= '0'; led_2 <= '0'; led_3 <= '0'; led_4 <= '0'; led_5 <= pwm_out;
O sinal rst é uma versão sincronizada, não invertida do reset externo. O alias duty_cycle mapeia os bits mais significativos de cnt para o controlador PWM.
Instanciações dos Módulos
RESET : entity work.reset(rtl) port map ( clk => clk, rst_n => rst_n, rst => rst ); PWM : entity work.pwm(rtl) generic map ( pwm_bits => pwm_bits, clk_cnt_len => clk_cnt_len ) port map ( clk => clk, rst => rst, duty_cycle => duty_cycle, pwm_out => pwm_out ); COUNTER : entity work.counter(rtl) generic map ( counter_bits => cnt'length ) port map ( clk => clk, rst => rst, count_enable => '1', counter => cnt );
Simulação
Genéricos facilitam acelerar a simulação. Reduzindo cnt_bits e clk_cnt_len, podemos observar ciclos PWM completos em alguns microssegundos em vez de vários segundos.
Instanciação do testbench:
DUT : entity work.pwm_led(str) generic map ( pwm_bits => 8, cnt_bits => 16, clk_cnt_len => 1 );
A forma de onda no ModelSim mostra um contador dente de serra controlando o brilho do LED. Um breve pulso "on" aparece na ponta de cada ciclo quando o ciclo de trabalho atinge seu valor máximo.

Implementação em FPGA
Após síntese com iCEcube2, o projeto consome apenas 5% dos 64 LUTs disponíveis na peça ice40hx1ktq144:
Resource Usage Report for pwm_led Mapping to part: ice40hx1ktq144 Cell usage: GND 3 uses SB_CARRY 31 uses SB_DFF 5 uses SB_DFFSR 39 uses SB_GB 1 use VCC 3 uses SB_LUT4 64 uses I/O ports: 7 I/O primitives: 7 SB_GB_IO 1 use SB_IO 6 uses I/O Register bits: 0 Register bits not including I/Os: 44 (3%) Total load per clock: pwm_led|clk: 1 @S |Mapping Summary: Total LUTs: 64 (5%)
Programar o FPGA via USB com o programador standalone Diamond foi simples. A animação LED resultante mostra uma rampa suave de brilho seguida por um breve escurecimento antes do ciclo repetir.

LED Respiratório com Ciclo de Trabalho de Onda Senoidal
Substituindo o contador dente de serra por uma tabela de lookup de onda senoidal produz um efeito "respiratório" natural, comumente visto em LEDs de notificação.

Em um artigo futuro, mostraremos como construir um gerador de onda senoidal usando block RAM e integrá-lo ao projeto pwm_led.
Leia o próximo post: Como criar um efeito LED respiratório usando uma onda senoidal armazenada em block RAM
Veja também: Controlador de servo RC usando PWM de pino FPGA
VHDL
- Controlador de Potência PWM: Construa um Driver de Lâmpada com Modulação de Largura de Pulso
- Criando Listas de Strings em VHDL: Boas Práticas e Exemplo
- Criando um Testbench Dirigido por Tcl para um Módulo Code-Lock VHDL
- Finalizando Graciosamente uma Simulação de Testbench VHDL
- Gerando Números Aleatórios em VHDL: De Uniforme a OSVVM
- Projetando um FIFO Ring Buffer Robusto em VHDL com Block RAM
- Como Construir um Testbench de Auto-Verificação em VHDL – Guia Prático
- Implementando Lista Encadeada Dinâmica em VHDL com Tipos Protegidos e Ponteiros de Acesso
- Aproveitando Procedimentos In-Process para Design de FSM em VHDL Mais Limpo
- Guia Passo a Passo para Carregar Capacitores com Segurança e Eficiência