Skip to content

Instantly share code, notes, and snippets.

@jjcarrier
Last active March 8, 2022 22:34
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jjcarrier/7631350 to your computer and use it in GitHub Desktop.
Save jjcarrier/7631350 to your computer and use it in GitHub Desktop.
A parameterized PWM module written in VHDL
-- Written By Jon Carrier
library IEEE;
use IEEE.STD_LOGIC_1164.all;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
use ieee.numeric_std.all;
entity PWM is
generic (
prescale: integer := 0; --frequency(PWM_clk)=frequency(i_CLK/(prescale+1))
quantas: integer := 8 --PWM stages, determines granularity
);
port (
i_CLK: in std_logic; --Main input clock signal
i_PWM: in std_logic_vector(31 downto 0); --Controls the Duty Cycle of o_PWM
o_PWM: out std_logic := '1' --The Pulse Width Modulated output signal
);
end PWM;
architecture PWM_arch of PWM is
signal PWM_clk : std_logic :='0';
signal PWM_acc : std_logic_vector(31 downto 0) := x"00000000";
begin
--PRESCALING PROCESS
--Generates the clock divided signal, frequency(PWM_clk)=frequency(i_CLK/(prescale+1))
--PWM_clk drives the rest of the PWM logic
prescale_proc: process(i_CLK)
variable prescale_cnt : std_logic_vector(31 downto 0) := x"00000000";
begin
if rising_edge(i_CLK) then
if prescale_cnt >= prescale then
prescale_cnt := x"00000000";
PWM_clk <= not PWM_clk;
else
prescale_cnt := prescale_cnt + 1;
end if;
end if;
end process;
--ACCUMULATOR PROCESS
--Generates the accumlation variable PWM_acc
--This variable is used in the PWM as a means to determine
--when the PWM signal should switch.
accumulate: process(PWM_clk)
begin
if falling_edge(PWM_clk) then
if PWM_acc >= std_logic_vector(to_unsigned(quantas - 1, PWM_acc'length)) then
PWM_acc <= x"00000000";
else
PWM_acc <= PWM_acc + 1;
end if;
end if;
end process;
--PWM PROCESS
--Generates the PWM output signal, o_PWM
--i_PWM controls the output and specifies how many Time Quantas
--out of a Total Quanta the output must be set HIGH
modulate: process(PWM_clk, i_PWM)
begin
if rising_edge(PWM_clk) then
if PWM_acc >= i_PWM then
o_PWM <= '0';
elsif PWM_acc = x"00000000" then
o_PWM <= '1';
end if;
end if;
end process;
end PWM_arch;
@jjcarrier
Copy link
Author

@Eguy358, I will skip on writing the VHDL/Verilog code as I do not have a dev environment setup for this right now, but I can help with the math.

A 50Hz PWM will have a period of 20ms. You can break this period into fractions of time using time quantas. To get a nice even 2ms pulse width you could use a value that is a multiple of 10. A higher value will give you better granularity if you need to provide a PWM output for a different pulse width or duty cycle. So we can go with quantas = 200.

With this, we can now calculate the prescaler to make this work which follows the form of:

PWM frequency = Input Clock Frequency / (prescaler + 1) / quantas
50Hz = 12MHz / (prescaler + 1) / 200

This results in:
prescaler = 1199

Then with this configuration, each unit of i_PWM is equal to:
i_PWM unit of time = (1/50)/200

or

i_PWM unit of time = 100us

So a 2ms pulse would be created using:
i_PWM = 2ms / 100us = 20

To summarize, to get a 2ms pulse width on a 50Hz PWM using a 12MHz clock, the following parameters could be used (there many possible solutions to this problem):

  • prescaler = 1199 (creates a PWM_clk of 10KHz)
  • quantas = 200 (200 quantas for a 10KHz PWM_clk = 20ms or 50Hz)
  • i_PWM = 20

Anyways hope that helps and I did not mix any math up.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment