Manufaturação industrial
Internet das coisas industrial | Materiais industriais | Manutenção e reparo de equipamentos | Programação industrial |
home  MfgRobots >> Manufaturação industrial >  >> Industrial programming >> VHDL

Como criar um controlador PWM em VHDL


A modulação por largura de pulso (PWM) é uma maneira eficiente de controlar a eletrônica analógica a partir de pinos FPGA puramente digitais. Em vez de tentar regular a tensão analógica, o PWM liga e desliga rapidamente a corrente de alimentação com potência total para o dispositivo analógico. Este método nos dá um controle preciso sobre a média móvel de energia fornecida ao dispositivo consumidor.

Exemplos de casos de uso que são bons candidatos para PWM são modulação de áudio (alto-falantes), controle de intensidade de luz (lâmpadas ou LEDs) e motores de indução. Este último inclui servomotores, ventiladores de computador, bombas, motores DC sem escova para carros elétricos, e a lista continua.

Veja também:
Controlador servo RC usando PWM de um pino FPGA

Como funciona o PWM


Ao ligar e desligar a fonte de alimentação de um dispositivo com alta frequência, podemos controlar com precisão a corrente média que flui através dele. A ilustração abaixo mostra o básico de como o PWM funciona. A saída PWM controla uma chave binária que pode definir a potência para 100% ou 0%. Ao alternar rapidamente entre os dois extremos, a média da janela deslizante será uma função do tempo gasto em cada um dos estados.


Ciclo de trabalho


O ciclo de trabalho é fundamental para controlar a potência fornecida ao dispositivo analógico em PWM. O termo ciclo de trabalho significa quanto tempo a saída PWM gasta na posição ON. É comum descrever o ciclo de trabalho como uma porcentagem, conforme mostrado na imagem abaixo. No entanto, no meu exemplo de VHDL, usarei um número binário não assinado posteriormente neste artigo. Faz mais sentido usarmos um número binário, que pode representar a resolução total do ciclo de trabalho em nossa implementação de VHDL.



Com um ciclo de trabalho de 0, a saída PWM permaneceria na posição DESLIGADA continuamente, enquanto em 100%, seria ininterrupta na posição LIGADA. O grau de precisão que o controlador PWM pode exercer no efeito da carga útil está diretamente relacionado ao comprimento do contador PWM. Veremos como isso funciona no código VHDL quando implementarmos um controlador PWM posteriormente neste artigo.

A fórmula para converter a representação binária do ciclo de trabalho em uma porcentagem é mostrada abaixo.
\mathit{duty\_cycle\_percentage} =\frac{\mathit{commanded\_duty\_cycle} * 100}{2^\mathit{pwm\_bits} - 1}

Frequência PWM


Ao falar sobre a frequência de comutação PWM, queremos dizer com que frequência a saída PWM alterna entre os estados ON e OFF, quanto tempo leva para o contador PWM quebrar. Como sempre, a frequência é o inverso do período PWM completo:
\mathit{pwm\_freq} =\frac{1}{\mathit{pwm\_period}}
A frequência PWM ideal depende do tipo de dispositivo que você está controlando. Qualquer número maior que algumas centenas de Hertz parecerá uma fonte de luz estável a olho nu se o consumidor for um LED. Para um motor CC sem escovas, o ponto ideal está na faixa de dezenas de quilohertz. Defina a frequência muito baixa e você pode sentir vibrações físicas. Com uma oscilação muito rápida, você está desperdiçando energia.



Uma questão a ter em mente é que a eletrônica de potência analógica não é tão rápida quanto o pino FPGA digital. Uma configuração típica de PWM usa MOSFETs de energia como interruptores para controlar a corrente que flui através do dispositivo analógico.

Considere o esquema mostrado na imagem. Faz parte do circuito de driver de LED usado no meu curso avançado de Dot Matrix VHDL. O pino FPGA controla a porta do MOSFET, atuando como um disjuntor para o LED em série. Com uma frequência de chaveamento mais alta, o transistor passará mais tempo não sendo totalmente aberto nem totalmente fechado. Isso se traduz em energia desperdiçada e produção de calor em excesso no MOSFET.

Módulo gerador de PWM


Vamos criar uma implementação padrão e genérica de um controlador PWM em VHDL. O que quero dizer com padrão é que isso está próximo do que os designers de VHDL mais experientes criariam se você lhes pedisse para escrever um controlador PWM em VHDL. É genérico no sentido de que a frequência PWM é personalizável para atender a maioria das aplicações.

Para testar nosso gerador PWM em um FPGA real, vamos precisar de mais alguns módulos além do controlador PWM. Vou apresentá-los posteriormente ao usar o módulo PWM para controlar a iluminação de um LED na placa de desenvolvimento Lattice iCEstick FPGA. Mas primeiro, vamos falar sobre o módulo gerador de PWM.

Entidade do módulo PWM


Para tornar o módulo personalizável, adicionei uma porta genérica que permite especificar duas constantes no momento da instanciação.

O primeiro, chamado pwm_bits , determina o comprimento do contador PWM interno. Essa constante define o comprimento do bit, não o valor máximo do contador. Você não poderá especificar a frequência PWM como um número específico de períodos de relógio. Mas geralmente não precisamos definir a frequência PWM com 100% de precisão. A frequência PWM ideal é uma faixa que funciona bem em vez de um número exato.

A outra constante genérica é chamada clk_cnt_len . Ele especifica o comprimento de um segundo contador que reduz efetivamente a frequência PWM. Ele atua como um divisor de clock, mas sem realmente criar um sinal de clock derivado. Observe que há um valor padrão de 1 atribuído a ele. Definir essa constante como 1 desabilita o divisor de clock e também elimina a lógica extra que o trata.

Vou explicar isso e apresentar a fórmula para calcular a frequência PWM exata mais adiante no artigo.
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;

Por se tratar de um módulo totalmente síncrono, os dois primeiros sinais são o clock e o reset.

A terceira entrada na lista de declaração de porta é o ciclo de trabalho. Como você pode ver no código VHDL acima, a duração do duty_cycle sinal segue os pwm_bits constante genérica. Isso significa que os pwm_bits constante governa com que precisão você pode regular a energia do dispositivo analógico.

O sinal final na entidade é pwm_out . Esse é o sinal de controle modulado PWM, aquele que você roteia para um pino FPGA e conecta ao portão do seu MOSFET.

Sinais internos do módulo PWM


O módulo PWM contém apenas dois sinais internos. O primeiro é o contador PWM, que é idêntico ao duty_cycle entrada. Assim como o último, o pwm_bits constante também determina o comprimento deste sinal.
signal pwm_cnt : unsigned(pwm_bits - 1 downto 0);
signal clk_cnt : integer range 0 to clk_cnt_len - 1;

O segundo sinal interno é denominado clk_cnt , e como o nome indica, é para contar ciclos de clock. É do tipo inteiro, e se você definir clk_cnt_len para 1, o intervalo de contagem será avaliado como (0 a 0) — apenas o número 0.

Processo do contador de ciclos de clock PWM


O processo que implementa o contador de relógio é direto. Se o módulo não estiver em reset, a lógica contará os ciclos de clock continuamente, voltando a zero no valor máximo que o clk_cnt inteiro pode conter.
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;

Observe que se você usou o valor padrão de 1 para o clk_cnt_len genérico, este processo deve evaporar durante a síntese. A instrução if interna sempre será falsa porque 0 < 1 - 1 é falso. O valor de clk_cnt é então sempre 0. A maioria das ferramentas de síntese reconhecerá isso e otimizará todo o processo.

Processo de saída PWM


O processo que define o sinal de saída PWM também controla o contador PWM. Ele incrementa o contador PWM quando o contador de ciclos de clock é 0. É assim que o mecanismo de limitação de frequência PWM funciona.

Inicialmente, pretendia escrever apenas if clk_cnt = 0 then na linha 9, mas descobri que a ferramenta de síntese não removeu toda a lógica relacionada ao contador de relógio quando usei o padrão clk_cnt_len valor de 1. No entanto, incluindo clk_cnt_len na instrução if fez o truque. Não deve ter efeitos adversos na síntese porque clk_cnt_len é uma constante. A ferramenta de síntese pode descobrir seu valor em tempo de compilação e então decidir se o conteúdo do processo é redundante ou não.
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;

Quando clk_cnt_len for maior que 1, o pwm_cnt sinal se comporta como um contador de execução livre, incrementando quando clk_cnt é 0. É um tipo sem sinal, que voltaria para 0 automaticamente quando transbordasse. Mas temos que ter certeza de que ele pula o valor mais alto antes de quebrar para zero.

Na linha 14 do código acima, estou verificando se o contador está no segundo valor mais alto. Se for, definimos como 0 neste ponto. Estou usando um truque que funcionará, não importa quanto tempo o pwm_cnt sinal é. Usando o to_signed função, estou criando uma nova constante assinada com o mesmo comprimento que pwm_cnt , mas com o valor -2.

O número assinado -2 em VHDL, e computadores em geral, sempre será uma série de 1s e um 0 na posição mais à direita. Isso é por causa de como a extensão de sinal funciona. Leia mais sobre isso no meu tutorial anterior:

Como usar assinado e não assinado em VHDL

Por fim, convertendo o tipo com sinal em um sem sinal, obtemos o segundo maior valor que pwm_cnt pode segurar.

Na linha 18, estamos verificando se o contador PWM de execução livre é maior que a entrada do ciclo de trabalho. Se isso for verdade, definimos a saída PWM como '1' porque estamos no período ON do ciclo de trabalho.

É por isso que tivemos que envolver o contador PWM de volta a 0 em seu segundo valor mais alto. Se o contador PWM pudesse atingir o valor mais alto possível que o ciclo de trabalho pode ter, não seria possível definir o ciclo de trabalho para 100%. Os pwm_cnt < duty_cycle linha sempre seria falsa quando pwm_cnt estava em seu valor máximo.

Faz sentido porque temos que representar os estados totalmente DESLIGADO e LIGADO, além das etapas intermediárias do ciclo de trabalho. Imagine que pwm_bits é o conjunto para 2, e percorra toda a sequência de contagem como um exercício mental para ver o que quero dizer!
\mathit{pwm\_hz} =\frac{\mathit{clk\_hz}}{(2^\mathit{pwm\_bits} - 1) * \mathit{clk\_cnt\_len}}
Levando em conta esses fatos, podemos derivar a fórmula mostrada acima para calcular a frequência PWM precisa. Enquanto clk_hz é a frequência do clock do sistema FPGA, as outras duas variáveis ​​são as constantes de entrada genéricas.

Módulo principal


Para testar o módulo PWM em hardware real, criei uma implementação que regulará a iluminação do LED de ativação no Lattice iCEstick. Estou usando esta placa de desenvolvimento FPGA acessível tanto no meu curso para iniciantes em VHDL Fast-Track quanto no meu curso avançado de FPGA Dot Matrix.



A imagem acima mostra a frente do iCEstick conectável por USB com o LED de ativação indicado pela seta. Existem cinco LEDs no iCEstick dispostos em um padrão de estrela. O LED de ativação é verde, enquanto os demais emitem luz de cor vermelha. No iCEstick, há um pino FPGA dedicado para cada um dos LEDs. Consulte o manual do usuário do iCEstick para ver os números exatos dos pinos para controlá-los.

O diagrama abaixo mostra como os submódulos no módulo superior estão conectados. Já falamos sobre o módulo PWM, e descreverei brevemente os módulos de contador e redefinição mais adiante neste artigo.


Entidade do módulo principal


Em vez de codificar as constantes dentro do módulo superior, estou declarando-as como genéricas na entidade de nível superior. Em seguida, atribuo valores padrão adequados para o iCEstick. Uma vantagem dessa abordagem é que você pode substituir esses valores no testbench para acelerar a simulação. Não atribuo nada aos genéricos quando sintetizo o design. Assim, os valores padrão corretos acabarão no projeto roteado.

Vamos repassar pwm_bits e clk_cnt_len aos genéricos na entidade do módulo PWM com os mesmos nomes. A frequência de clock do oscilador iCEstick é de 12 Mhz. Usando a fórmula apresentada anteriormente, podemos inserir esses valores para calcular a frequência PWM:\frac{12e6}{(2^8 - 1) * 47} \approx 1 \mathit{kHz}
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; -- Pullup

    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;

Você deve ter notado que há uma terceira constante, cnt_bits , na declaração de genéricos no código acima. Ele controla o comprimento de um contador de dente de serra auto-embrulhante. Vamos usar este contador adicional para criar uma iluminação gradual do LED de ativação para que possamos observar o módulo PWM funcionando em tempo real.

Vamos conectar os bits altos deste novo contador à entrada do ciclo de trabalho do módulo PWM. Como esse contador contará os ciclos de clock, os cnt_bits generic determina a frequência de pulsação do LED de ativação. A fórmula, que é uma função da frequência do relógio e do comprimento do contador, é mostrada abaixo.
\frac{2^{\mathit{cnt\_bits}}}{\mathit{clk\_hz}} =\frac{2^{25}}{12e6} \approx 2.8 \mathit{Hz}
Na declaração da porta, corrigi a entrada de reset com _n , indicando que o reset externo tem polaridade negativa. Vamos configurar o Lattice FPGA para usar um resistor pull-up interno neste pino.

Por fim, você pode ver que listei todos os LEDs presentes no iCEstick na declaração da porta. Vamos usar apenas o LED número 5, mas temos que acionar os outros LEDs ativamente. Se eles forem deixados desconectados, eles acenderão em uma cor vermelha fraca.

Se você quiser dar uma olhada no código VHDL e nos arquivos de restrições, digite seu e-mail no formulário abaixo. Você receberá um arquivo Zip com o código completo com os projetos ModelSim e Lattice iCEcube2.




Sinais internos do módulo superior


Eu gosto de manter meus principais módulos livres de lógica RTL. É um conceito chamado de módulo estrutural . Na minha experiência, é mais fácil manter um projeto VHDL estruturado quando você separa a lógica RTL e interconecta. O código abaixo mostra as declarações de sinal no módulo superior e as atribuições de sinal simultâneas.
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;

Primeiro, declaramos um sinal de reset que será nossa versão síncrona não invertida do reset externo.

O segundo sinal declarado, chamado cnt , é o contador de ciclos de clock infinitamente empacotado. É um tipo sem sinal que manterá o estado de nossa onda dente de serra de intensidade de LED a qualquer momento.

O próximo é o pwm_out sinal. Poderíamos ter conectado o pwm_out sinal do módulo PWM diretamente para o led_5 saída, mas eu queria observar pwm_out no simulador. A ferramenta de síntese descobrirá que os dois sinais pertencem à mesma rede. Não custará recursos adicionais.

Finalmente vem a declaração do duty_cycle vector—desta vez, usei o alias palavra-chave em vez de criar um novo sinal. Os aliases de VHDL funcionam como macros em C. Quando usamos o duty_cycle nome a partir de agora, o compilador irá substituí-lo pelos bits altos do cnt vetor.

Após o início palavra-chave, atribuímos a pwm_out sinal para o led_5 resultado. Todos os outros LEDs são conectados em '0' para evitar que iluminem a luz vermelha.

Instanciações


Antes de usar sinais externos dentro do FPGA, devemos sempre sincronizá-los com o relógio interno do sistema. Caso contrário, podemos ter problemas de metaestabilidade, problemas difíceis de depurar.
RESET : entity work.reset(rtl)
  port map (
    clk => clk,
    rst_n => rst_n,
    rst => rst
  );

O reset externo não é exceção, mas como não estou permitindo nenhuma lógica RTL no módulo estrutural de nível superior, implementamos o sincronizador de reset como um módulo autônomo.

A próxima instanciação é o módulo PWM, conforme mostrado no trecho de código abaixo. Na instanciação do módulo PWM, estamos usando o duty_cycle alias para atribuir os bits mais significativos do cnt vetor para o duty_cycle entrada. Isso fará com que o brilho do LED se intensifique até que o contador atinja seu valor máximo. Quando cnt volta a zero, o LED se apaga brevemente e o ciclo se repete.
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
  );

A terceira e última instanciação no módulo superior é o contador de ciclos de clock, conforme mostrado abaixo. Para tornar este módulo mais genérico, incluí um count_enable sinal. Mas neste projeto, vamos defini-lo como uma constante '1' porque queremos contar cada ciclo de clock.
COUNTER : entity work.counter(rtl)
  generic map (
    counter_bits => cnt'length
  )
  port map (
    clk => clk,
    rst => rst,
    count_enable => '1',
    counter => cnt
  );

Deixe seu endereço de e-mail no formulário abaixo se precisar do código VHDL completo para este projeto.




Simulando a pulsação do LED PWM


Uma vantagem significativa de tornar os comprimentos dos contadores personalizáveis ​​por meio de genéricos é que permite acelerar a simulação. Na maioria das vezes, estamos interessados ​​em testar as transições e eventos em nossa lógica. Não gostamos muito de passar por um contador ultralongo enquanto nada mais acontece no design.

Com os genéricos, podemos alterar essas coisas de maneira não invasiva a partir do banco de testes. O código abaixo mostra os valores que atribuí ao mapa genérico ao instanciar o módulo PWM no testbench.
DUT : entity work.pwm_led(str)
  generic map (
    pwm_bits => 8,
    cnt_bits => 16,
    clk_cnt_len => 1
  )

Quando simulamos usando essas constantes no ModelSim, basta executar por 1400 microssegundos a 100 MHz para revelar dois ciclos completos de PWM. Se tivéssemos usado os valores reais, teríamos que simular cerca de 6 segundos. São 32 milhões de ciclos de clock de quase nada além de contar. Levaria uma eternidade no ModelSim.

A imagem abaixo mostra a forma de onda da simulação PWM no ModelSim. Mudei o formato do duty_cycle sinal do tipo de número padrão para uma apresentação de onda analógica. Você pode fazer isso no ModelSim clicando com o botão direito do mouse no sinal na forma de onda e selecionando Format->Analog (custom)… e defina a altura do pixel e o intervalo de dados para corresponder aos valores mínimo e máximo do seu sinal.



Na forma de onda, podemos ver por que é chamado de sinal de dente de serra. O contador de embrulho de funcionamento livre se assemelha aos dentes de uma lâmina de serra.

Observe como a duração dos períodos altos da saída PWM (led_5 ) aumenta à medida que o ciclo de trabalho cresce. Também podemos ver que led_5 é um '1' contínuo muito brevemente na ponta do dente de serra. É quando o ciclo de trabalho é 255, o valor máximo.

Se não adicionamos a instrução if extra no módulo PWM, aquela que envolve o pwm_cnt sinal de volta a zero em seu segundo valor mais alto, não veríamos isso. Nunca seríamos capazes de atingir a potência máxima. É um erro comum ao implementar geradores PWM. Eu também fiz isso uma ou duas vezes.

A implementação do FPGA


Implementei o projeto no Lattice iCEstick usando o iCEcube2, o software de projeto da Lattice. A listagem abaixo mostra o uso de recursos informado após local e rota. Embora o FPGA iCE40 seja pequeno, o PWM e os módulos de suporte usam apenas 5% dos LUTs disponíveis.
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%)

Após gerar o bitstream de programação no iCEcube2, usei o programador autônomo Lattice Diamond para configurar o FPGA via USB.

A animação Gif abaixo mostra como o sinal do ciclo de trabalho da onda dente de serra faz com que o LED de ativação no iCEstick se comporte. Acende com intensidade crescente até que o cnt contra envoltórios. Então, o ciclo de trabalho se torna todo zero e o LED desliga brevemente. Depois disso, o ciclo se repete indefinidamente.



O iCEstick é uma placa de desenvolvimento FPGA barata e versátil. É bom para iniciantes, mas também é adequado para projetos incorporados avançados. Além disso, o software Lattice é descomplicado e fácil de usar. É por isso que estou usando o iCEstick tanto no meu curso de VHDL para iniciantes quanto no curso de FPGA avançado.

Se você já possui um iCEstick, pode usar o formulário abaixo para baixar o projeto iCEcube2.




Ciclo de trabalho de onda senoidal para efeito de respiração de LED


Agora você sabe como controlar a iluminação de um LED usando PWM.

O LED pulsando em um padrão de onda dente de serra é indiscutivelmente mais frio do que um simples aplicativo piscando ON/OFF. Essa é uma primeira tarefa típica para alunos de VHDL, e tenho certeza de que você piscou um LED em algum momento.

No entanto, o piscar do LED se torna ainda mais impressionante se você usar uma onda senoidal para controlar o ciclo de trabalho. A animação Gif abaixo mostra nosso módulo PWM pulsando o LED com uma variação de intensidade senoidal ao longo do tempo.



Tenho certeza de que você já viu esse tipo de efeito de “respiração” nos LEDs antes. É assim que o LED de notificação do meu celular se comporta, e acho que parece natural porque não há mudanças bruscas na intensidade da luz.

Na minha próxima postagem no blog, mostrarei como criar um gerador de onda senoidal usando RAM de bloco em FPGAs. E modificaremos o pwm_led módulo para pulsar o LED no iCEstick com intensidade de onda senoidal.

Clique aqui para acessar a próxima postagem do blog:
Como criar um efeito de LED de respiração usando uma onda senoidal armazenada no bloco de RAM


Veja também:
Controlador servo RC usando PWM de um pino FPGA


VHDL

  1. Controlador de energia PWM
  2. Como criar uma lista de strings em VHDL
  3. Como criar um testbench orientado por Tcl para um módulo de bloqueio de código VHDL
  4. Como parar a simulação em um testbench VHDL
  5. Como gerar números aleatórios em VHDL
  6. Como criar um buffer de anel FIFO em VHDL
  7. Como criar um testbench de autoverificação
  8. Como criar uma lista vinculada em VHDL
  9. Como usar um procedimento em um processo em VHDL
  10. Como carrego um capacitor?