- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
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;
Link Copied
9 Replies
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
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.- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
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.- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
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 Timlibrary 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;
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
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)
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
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- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
--- 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
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
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 ...
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Wow,
thats super cool! i simulated the code and i dont understand many things but i will study it Thank you- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
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 Timfunction 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;
Reply
Topic Options
- Subscribe to RSS Feed
- Mark Topic as New
- Mark Topic as Read
- Float this Topic for Current User
- Bookmark
- Subscribe
- Printer Friendly Page