Verilog: PWM prosty

PWM to skrót od Pulse Width Modulation, czyli modulacja szerokością impulsu. Można powiedzieć, że PWM to najprostszy z możliwych przetworników cyfrowo-analogowych. W bardzo prosty sposób pozwala regulować napięcie, prąd, moc, a w konsekwencji prędkość silnika, jasność lampy i wiele innych.

Idea działania modulatora PWM jest bardzo prosta - sygnał wyjściowy przybiera wartość 1 lub 0, co odpowiada najwyższemu lub najniższemu napięciu, jakie jest na wyjściu. Generowany jest sygnał prostokątny o stałej częstotliwości. Trik polega na zmianie współczynnika wypełnienia tego sygnału. Przykładowo, jeśli czas trwania jedynki i zera jest taki sam, wówczas współczynnik wypełnienia wynosi 50%. Jeśli jedynka trwa przez 100us, a zero przez 900us, to mamy współczynnik wypełnienia rzędu 10%. Po przepuszczeniu takiego sygnału przez filtr dolnoprzepustowy uzyskujemy sygnał analogowy o napięciu, które możemy płynnie regulować. Elementy takie jak żarówki, grzejniki czy silniki nie wymagają stosowania filtrów.

Budowa generatora PWM w układzie FPGA/CPLD jest trywialnie prosta. Potrzebujemy zaledwie jeden licznik, jeden przerzutnik i kilka prostych elementów. Przyjrzyjmy się bliżej schematowi.

Budowa PWM

Potrzebujemy źródło sygnału zegarowego, które łączymy z najzwyklejszym licznikiem, liczącym w górę. Kiedy licznik osiągnie najwyższą wartość, wtedy resetuje się i zaczyna liczyć od początku. Wartość "granica" wyznacza współczynnik wypełnienia generowanego sygnału. Nazywa się to również wartością progową lub threshold. Oprócz tego mamy dwa komparatory. Pierwszy z nich daje na wyjściu 1, kiedy wartość licznika jest równa 0 (de facto jest to bramka NOR). Drugi porównuje aktualną wartość licznika z wartością graniczną.

Przebiegi PWM

Na początku cyklu, licznik ma wartość 0. Wykrywa to pierwszy komparator (=0) i natychmiast wystawia jedynkę na swoim wyjściu, połączonym z wejściem SET przerzutnika. Powoduje to pojawienie się stanu wysokiego na wyjściu generatora. Ten stan utrzymuje się przez pewien czas, a licznik liczy w górę. Kiedy stan licznika zrówna się z wartością graniczną, uaktywnia się drugi komparator, który jest połączony z wejściem RESET przerzutnika. W wyniku tego uzyskujemy zero na wyjściu układu. Ten stan utrzymuje się aż do przepełnienia licznika i powrotu do wartości początkowej. Wtedy cykl się powtarza.

Implementacja w FPGA

Kod jest bardzo prosty i odpowiada powyższemu opisowi. Jedyna różnica polega na dodaniu wejścia resetującego, które zeruje licznik sterujący i ustawia wyjście w stan wysoki.

 
module pwm(
    input clk,
    input reset,
    input [7:0] granica,
    output reg wyjscie = 1'b1
);

    // licznik sterujący 
    reg [7:0] licznik = 8'h00;
    
    // generator pwm
    always @(posedge clk)
        if(reset)
            begin
            wyjscie = 1'b1;
            licznik = 8'h00;
            end
        else
            begin
            licznik = licznik + 1;
            if(licznik == granica)
                wyjscie = 1'b0;
            else if(licznik == 8'h00)
                wyjscie = 1'b1;
            end
    
endmodule 
 

Napiszmy jeszcze prosty moduł testowy.

 
`timescale 1 ns / 1 ns

module pwm_tb;
    
    reg clk;
    reg reset;
    reg [7:0] granica;
    wire wyjscie;
    
    // instancja modułu
    pwm modul(clk, reset, granica, wyjscie);
    
    // sygnał zegarowy
    always
        #1 clk = ~clk;
        
    // procedura testowa
    initial
        begin
        clk = 0;
        reset = 0;
        granica = 8'd255;
        
        #10000 granica = 8'd192;
        #10000 granica = 8'd128;
        #10000 granica = 8'd64;     
        #10000 granica = 8'd0;
        
        #10000 $finish;
        end
        
endmodule 
 

Po przeprowadzeniu symulacji dostajemy takie wykresiki. Po kliknięciu myszką powinny się powiększyć na cały ekran. Licznik i granica to wartości 8-bitowe, zatem maksymalna wartość jaką mogą przyjąć to 255. Kiedy próg przełączenia ustawiony jest na 255, wtedy sygnał wyjściowy ma wypełnienie 255/256 i widać krótkie szpilki. Współczynnik wypełnienia jest proporcjonalny do wartości granicznej. 

Symulacja PWM

Prezentowany generator PWM jest bardzo prosty, ale ma jeden poważny feler, co wyraźnie widać na poniższym obrazku. W chwili zaznaczonej czerwonym kursorem, wartość graniczna została zmieniona, jednak sygnał wyjściowy pozostał w stanie wysokim aż do zakończenia cyklu! Dlaczego tak się stało? Próg przełączenia został ustawiony w chwili, kiedy licznik miał wartość większą od granicznej. Zatem licznik i granica w tym cyklu nigdy się nie zrównały, przez co drugi komparator nie zresetował przerzutnika. Nie powinno się zmieniać zasad w trakcie gry :)

Wykresy PWM

Inny sposób został przedstawiony na stronie FPGA4FUN. Po syntezie ten układ zajmuje trochę mniej zasobów niż rozwiązanie z komparatorami.

 
module pwm(
    input clk,
    input [7:0] granica,
    output wyjscie 
);

    // licznik sterujący 
    reg [8:0] akumulator = 9'd0;
    
    // generator pwm
    always @(posedge clk)
        akumulator = akumulator[7:0] + granica;
    
    // wyjście
    assign wyjscie = akumulator[8];
    
endmodule