Forum Discussion

Altera_Forum's avatar
Altera_Forum
Icon for Honored Contributor rankHonored Contributor
12 years ago

PWM signal via sine-triangle comparison

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; 

9 Replies

  • Altera_Forum's avatar
    Altera_Forum
    Icon for Honored Contributor rankHonored Contributor

    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.
  • Altera_Forum's avatar
    Altera_Forum
    Icon for Honored Contributor rankHonored Contributor

    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.
  • Altera_Forum's avatar
    Altera_Forum
    Icon for Honored Contributor rankHonored Contributor

    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;  
    
  • Altera_Forum's avatar
    Altera_Forum
    Icon for Honored Contributor rankHonored Contributor

    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)
    
  • Altera_Forum's avatar
    Altera_Forum
    Icon for Honored Contributor rankHonored Contributor

    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
  • Altera_Forum's avatar
    Altera_Forum
    Icon for Honored Contributor rankHonored Contributor

    --- 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
  • Altera_Forum's avatar
    Altera_Forum
    Icon for Honored Contributor rankHonored Contributor

    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 ...
  • Altera_Forum's avatar
    Altera_Forum
    Icon for Honored Contributor rankHonored Contributor

    Wow,

    thats super cool!

    i simulated the code and i dont understand many things but i will study it Thank you
  • Altera_Forum's avatar
    Altera_Forum
    Icon for Honored Contributor rankHonored Contributor

    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;