Intel® Quartus® Prime Software
Intel® Quartus® Prime Design Software, Design Entry, Synthesis, Simulation, Verification, Timing Analysis, System Design (Platform Designer, formerly Qsys)
16612 Discussions

PWM signal via sine-triangle comparison

Altera_Forum
Honored Contributor II
2,949 Views

Hello, 

right now i am stuck in aproblem in my project. I hope its not explained to complicated and someone can help me (-:  

I try to generate a pwm signal out of a sine with 50 Hz - 20ms. 

I have triangle function with a period time of 8 µs by counting from -99 to 99 and vice versa. 

I have a sine table with 500 values, so if i want to fit the whole sine into the 20ms period  

and i have the triangle with a period of 8µs (400* 20ns) i have to update the sine value every 40µs. 

Now i simulated everything and it compiles just fine but i think my design is full of logical errors which i right now cant see. 

In the modelsim analysis i see a close pwm signal but with the wrong amplitude, so that the sine function is much larger than the triangle. 

 

I hope someone can help me and thanks in advance for reading through my code!! 

 

 

Cheers 

Tim 

 

 

--------------------------------------------------------------------------------- -------------------------Timer Direction Function-------------------------------- --------------------------------------------------------------------------------- TimerDirection:process (CLOCK,RESET,TimerDir,Timer) begin if RESET = '0' then TimerDir <= '1'; elsif CLOCK'event and CLOCK ='1' then if (Timer =99) then TimerDir <= '0'; end if; if (Timer =-99) then TimerDir <= '1'; end if; end if; end process TimerDirection; CountingTimer: process (CLOCK,RESET,TimerDir,Timer) begin if RESET = '0' then --Timer<= (others => '0'); Timer <= 0; countSine <= 0; elsif CLOCK'event and CLOCK ='1' then if (TimerDir ='1') then Timer <=Timer + 1; end if; if (TimerDir ='0') then Timer <=Timer - 1; end if; countSine <= countSine +1; if (countSine = 2000) then countSine <= 0; end if; end if; end process CountingTimer;  

 

 

 

type table is array (0 to 499) of std_logic_vector(8 downto 0); function init_table return table is variable var: table; variable x: real; begin for i in 0 to 499 loop x:= SIN(real(i)*MATH_PI/real(500)); var(i):=std_logic_vector(to_signed(INTEGER(x*real(100)),9)); end loop; return var; end; constant sinTable : mem := init_mem;  

 

this function generates the pwm signal: 

modulate: process (CLOCK,RESET,TimerDir,Timer) begin if RESET = '0' then PWM_OUTPUT <= '1'; elsif CLOCK'event and CLOCK ='1' then if SinValue = std_logic_vector(to_signed(Timer,9)) and (Timerdir<='1')then PWM_OUTPUT <= '0'; end if; if SinValue = std_logic_vector(to_signed(Timer,9)) and (Timerdir<='0')then PWM_OUTPUT <= '1'; end if; end if; end process modulate; end pwm_process;
0 Kudos
9 Replies
Altera_Forum
Honored Contributor II
1,134 Views

I have come across a variety of ways to do pwm based on counters, sine lut, triangle ...etc. 

I have the following algorithm to add to the list: 

 

no need for sine table, just a triangular table say from 0~256~1 (512 points) implemented as rom lut 

run an accumulator to generate pointer to lut, the accumulator starts from 0 adds tuning word modulo 512 (same as nco idea) 

tuning word for accumulator in my example = 512*f/clk 

 

then select in logic your threshold, 

threshold = duty * 256/100 i.e. scale duty to 256  

 

if lut output stream is checked in logic above threshold so output high else low 

 

thus you control frequency and duty cycle. 

you can also add initial phase control by adding a constant at start of accumulator.
0 Kudos
Altera_Forum
Honored Contributor II
1,134 Views

The sinvalue generation is missing from the code snippets, so we can't see where it probably goes wrong. Without the code, I don't understand at first sight how 2000 (actually 2001) sine samples are using a 500 point half-sine table and how the negative halve is generated. 

 

A possible trapdoor is the PWM compare for equality which can cause dropouts when sinvalue changes immediately at the crosspoint. Generally you'll decide for natural versus regular sampling and then implement a method that doesn't lose PWM edges.
0 Kudos
Altera_Forum
Honored Contributor II
1,134 Views

Hi, thanks you two for your replies and your time !! 

Hey FvM, the idea was to use natural sampling, how could i loose those pwm edges?  

i checked my sine vector in modelsim and it really seems that i just have half a sine, buthow does this happen. 

I thought when the variable x gets calculated its range is from 1 to -1 and this value is casted and stored in the vector? 

I have the period of 20 ms in which i put 500 sine values. The triangular impulse has a period of 8µs.  

So after 5 triangle impulses i need a new sine value.  

So i have the countSine value run to 2000 and update the sinetabe 

The compare function gets a new sine value every 5 

Right now i dont know why i just didnt divide the sine in 5000 intervals seems much more  

intuitive... 

 

 

I post the complete code with some comments below: 

 

Cheers 

Tim 

 

 

library IEEE; use IEEE.STD_LOGIC_1164.ALL; --use IEEE.STD_LOGIC_ARITH.ALL; --bad --use IEEE.STD_LOGIC_UNSIGNED.ALL; use IEEE.STD_LOGIC_SIGNED.ALL; use IEEE.NUMERIC_STD.all; --good --to_unsigned to_signed <- integer to .. use ieee.math_real.all; entity PWM is Port ( CLOCK : in STD_LOGIC; RESET : in STD_LOGIC; PWM_OUTPUT : out STD_LOGIC ); end PWM; architecture pwm_process of PWM is SIGNAL Timer : integer range -2048 to 2047 := 0; SIGNAL countSine : integer range 0 to 4095 := 4095; SIGNAL incSineTable : integer range 0 to 500 := 500; SIGNAL TimerDir : STD_LOGIC :='1'; SIGNAL SinValue : std_logic_vector(8 downto 0) := (others =>'0'); type table is array (0 to 499) of std_logic_vector(8 downto 0); function init_table return table is variable x: real; variable var: table; begin for i in 0 to 499 loop x:= SIN(real(i)*MATH_PI/real(499)); var(i):=std_logic_vector(to_signed(INTEGER(x*real(100)),9)); end loop; return var; end; constant sinTable : table := init_table; begin --------------------------------------------------------------------------------- -------------------------Timer Direction Function-------------------------------- --------------------------------------------------------------------------------- --input clokc 50 MHz Timer counts to 200 -> 8µs period time TimerDirection:process (CLOCK,RESET,TimerDir,Timer) begin if RESET = '0' then TimerDir <= '1'; elsif CLOCK'event and CLOCK ='1' then if (Timer =99) then TimerDir <= '0'; end if; if (Timer =-99) then --1 TimerDir <= '1'; end if; end if; --end if; end process TimerDirection; ---------------------------------------------------------------------------------- --------------------------------Timer counting----------------------------------- ---------------------------------------------------------------------------------- CountingTimer: process (CLOCK,RESET,TimerDir,Timer) begin if RESET = '0' then --Timer<= (others => '0'); Timer <= 0; elsif CLOCK'event and CLOCK ='1' then if (TimerDir ='1') then Timer <=Timer + 1; end if; if (TimerDir ='0') then Timer <=Timer - 1; end if; end if; end process CountingTimer; --------------------------------------------------------------------------------- -------------------------------Update Sine Value -------------------------------- --------------------------------------------------------------------------------- -- Timer 2500 cycles in 20ms 500 sine values -- every 5 cycles new sine value -> countSine to 2000 UpdateSineValue : process (CLOCK, RESET) begin if RESET = '0' then incSineTable <= 0; countSine <= 0; elsif CLOCK'event and CLOCK ='1' then SinValue <= sintable(incSineTable); if incSineTable = 500 then incSineTable <= 0; end if; countSine <= countSine +1; if (countSine = 2000) then incSineTable <= incSineTable +1; end if; end if; end process UpdateSineValue; ------------------------------------------------------------------------------------- ---------------------------- TimerCompare Function---------------------------- ------------------------------------------------------------------------------------- TimerCompare: process (CLOCK,RESET,TimerDir,Timer) begin if RESET = '0' then PWM_OUTPUT <= '1'; elsif CLOCK'event and CLOCK ='1' then --if (SinValue = to_signed(Timer,8)) and (Timerdir<='1')then if SinValue = std_logic_vector(to_signed(Timer,9)) and (Timerdir<='1')then PWM_OUTPUT <= '0'; end if; if SinValue = std_logic_vector(to_signed(Timer,9)) and (Timerdir<='0')then PWM_OUTPUT <= '1'; end if; end if; end process TimerCompare; end pwm_process;
0 Kudos
Altera_Forum
Honored Contributor II
1,134 Views

The following code implements pwm based on 15 bits phase accumulator. 

You need to provide: 

frequency word as : round(2^14*freq/clk) 

duty cycle as: round(duty*2^14/100); 

 

it does not require actual LUT but is implied 

 

library ieee; use ieee.std_logic_1164.all; use IEEE.numeric_std.all; entity pwm is port( clk : in std_logic; freq_word : in std_logic_vector(13 downto 0) := "00011001100110"; duty_scaled : in std_logic_vector(13 downto 0) := "10000000000000"; pwm : out std_logic ); end entity; architecture rtl of pwm is signal ptr : unsigned(14 downto 0) := (others => '0'); begin process(clk) begin if(rising_edge(clk)) then ptr <= ptr + unsigned(freq_word); pwm <= '0'; if ptr(13 downto 0) < unsigned(duty_scaled) then pwm <= '1'; end if; end if; end process; end rtl;  

 

and here is a model in matlab 

clear all; close all; clc; clk = 100; %system clock f = 10; %output frequency duty = 50; %output duty cycle, 1 ~ 100 %%%%%%%%%%%%%%%%% LUT %%%%%%%%%%%%%%%%%%% n = floor(5000/f); %test length m = 15; %lut resolution tw = round(2^(m-1)*f/clk); %tuning word ptr = 0; %pointer to lut for i = 1:n ptr = floor(mod(ptr + tw, 2^(m-1))); y(i) = ptr; %compute lut output end %%%%%%%%%%%%%%% comparator %%%%%%%%%%%%%%% threshold = round(duty*2^(m-1)/100); out = zeros(1,n); for i = 1:n if y(i) < threshold, out(i) = 2^(m-1); end end %%%%%%%%%%%%%% check results %%%%%%%%%%%%% figure;plot(out,'.-');grid; d = diff(find(diff(out))); %locate 0/1 transitions for i = 1:2:length(d)-1 a = clk/(d(i)+d(i+1)); b = round(100*d(i+1)/(d(i)+d(i+1))); end achieved_freq = mean(a) achieved_duty = mean(b)
0 Kudos
Altera_Forum
Honored Contributor II
1,134 Views

hey kaz, 

 

I could implement a triangular LUT.  

But i dont understand whats the idea behind the pointer and the tuning word.  

 

--- Quote Start ---  

0 adds tuning word modulo 512 (same as nco idea) 

tuning word for accumulator in my example = 512*f/clk 

--- Quote End ---  

 

Could you please this a bit more in detail ?  

thanks a lot!! 

Tim
0 Kudos
Altera_Forum
Honored Contributor II
1,134 Views

 

--- Quote Start ---  

hey kaz, 

 

I could implement a triangular LUT.  

But i dont understand whats the idea behind the pointer and the tuning word.  

 

Could you please this a bit more in detail ?  

thanks a lot!! 

Tim 

--- Quote End ---  

 

 

The idea comes from NCO but instead of generating sine the same concept is applied to a triangular cycle. 

The nco is based on one cycle worth of sine data (half or quarter can be used but eventually read into 1 cycle). 

A pointer is used to address lut based on phase increment... you will need to research into say DDS tutorial from AD for full explanation. 

I applied same idea but instead used triangular cycle and then realised I don't even need the LUT but just visualise it conceptually to save memory and use high resolution. The triangular cycle is 0:2^14 then back to 1(2^15 points in effect)... look at matlab model and change variois parameters to see how it responds. 

Bear in mind that as you get close to clk speed the samples per cycle gets less and affects resolution of duty cycle
0 Kudos
Altera_Forum
Honored Contributor II
1,134 Views

Here you are generating a half sine wave. The range of a full wave would be 0 to 2*pi. I also think that the division factor should be 500.0 instead of 499.0. 

for i in 0 to 499 loop x:= SIN(real(i)*MATH_PI/real(499)); 

 

You want something like this 

for i in 0 to 499 loop x:= SIN(real(i)*2.0*MATH_PI/500.0); 

 

Secondly, your counters are counting one step too much, e.g. incSineTable counts 498, 499, 500, 0, 1 ...
0 Kudos
Altera_Forum
Honored Contributor II
1,134 Views

Wow, 

thats super cool! 

i simulated the code and i dont understand many things but i will study it Thank you
0 Kudos
Altera_Forum
Honored Contributor II
1,134 Views

I now realized why my pwm signal was so "strange" 

I did not scale the sine correct to the triangle counter. 

I switched back to count from 0 to 100.  

And the i multiplied each sine value with 50 and added an offset of 50 to it. Now i have the desired output signal. 

Thank you both for your help and time!! 

I did not follow the NCO idea because i did not fully understand the principals (-;  

 

Cheers 

Tim 

function init_table return table is variable x: real; variable var: table; begin for i in 0 to 499 loop x:= SIN(real(i)*2.0*MATH_PI/500.0); var(i):=std_logic_vector(to_signed(INTEGER(50.0 + x*50.0),10)); end loop; return var; end; constant sinTable : table := init_table;
0 Kudos
Reply