www.mikrocontroller.net

Forum: FPGA, VHDL & Co. 3phasen PWM mit FPGA


Autor: Tobias Plüss (hubertus)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo Leute,
ich versuche, mit einem FPGA eine 3phasen PWM zu bewerkstelligen. Am 
Schluss soll man damit eine Raumzeigermodulation machen können, oder 
eine FOC oder was auch immer - halt alles, wozu man eine 3phasen PWM 
benutzen kann.
Ich will, dass die PWM's 'center aligned' sind. also nicht so:

                 ___________
Phase U ________|             |______
                       _____
Phase V _______________|      |______


sondern so:

                 ___________
Phase U ________|             |______
                    _____
Phase V ___________|      |__________


dies wird mit einem up/down Counter erreicht. Er zählt von 0 hoch bis 
zum erreichen des maximalwerts und zählt dann wieder runter bis auf 0.
Gleichzeitig wird dieser Counter mit dem PWM-Value verglichen, und wenn 
dieser grösser ist, dann soll der Ausgangspin eingeschaltet werden.

Soweit die Theorie.

Warum funktioniert mein Code:
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity simple_pwm is
  port(
    clk : in std_logic;               --clock

    rel : in integer range 0 to 255;  --reload value
    pre : in integer range 0 to 255;  --prescaler value

    dcu : in integer range 0 to 255;  --duty cycle u phase
    dcv : in integer range 0 to 255;  --duty cycle v phase
    dcw : in integer range 0 to 255;  --duty cycle w phase

    hsu : out std_logic;              --high side u output
    lsu : out std_logic;              --low side u output
    hsv : out std_logic;              --high side v output
    lsv : out std_logic;              --low side v output
    hsw : out std_logic;              --high side w output
    lsw : out std_logic               --low side w output
  );
end simple_pwm;

architecture behv of simple_pwm is
  signal ctr : integer range 0 to 255;
  signal ps : integer range 0 to 255;

  signal dcu_reg : integer range 0 to 255;
  signal dcv_reg : integer range 0 to 255;
  signal dcw_reg : integer range 0 to 255;
  
  signal ctr_nxt : integer range 0 to 255;
  
  signal dir : std_logic;
  
begin

  process begin
    wait until rising_edge(clk);
    if ps > 0 then
      ps <= ps - 1;
    else
      ps <= pre;
      ctr <= ctr_nxt;
    end if;
    
    if ctr = 0 then
      dcu_reg <= dcu;
      dcv_reg <= dcv;
      dcw_reg <= dcw;
      dir <= '0';
    elsif ctr = rel then
      dcu_reg <= dcu;
      dcv_reg <= dcv;
      dcw_reg <= dcw;
      dir <= '1';
    end if;

    if dcu_reg < ctr then
      lsu <= '1';
      hsu <= '0';
    else
      lsu <= '0';
      hsu <= '1';
    end if;
    
    if dcv_reg < ctr then
      lsv <= '1';
      hsv <= '0';
    else
      lsv <= '0';
      hsv <= '1';
    end if;
    
    if dcw_reg < ctr then
      lsw <= '1';
      hsw <= '0';
    else
      lsw <= '0';
      hsw <= '1';
    end if;

  end process;
  
  ctr_nxt <= ctr + 1 when dir = '0' else ctr - 1;

end architecture;


nicht?

Da ich den FPGA mit einem Microcontroller verbunden habe, kann dieser 
gleich die Werte in die Register schreiben, wie etwa den duty cycle usw.
Ich möchte mit dem FPGA am Schluss eine Art Peripheriebaustein 
realisieren, ähnlich der Capture/Compare Unit die der 80C517 von Siemens 
hatte. Ich muss nämlich noch andere Timingaufgaben erledigen, und dafür 
ist der FPGA (denke ich) wie geschaffen.

Es handelt sich übrigens um einen Altera Cyclone II EP2C8T144C8N, falls 
dies von Belang sein sollte.

Autor: Lothar Miller (lkmiller) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Was funktioniert an dem Code nicht?
Und wie stellst du das fest?

Autor: Tobias Plüss (hubertus)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Der Zähler zählt nicht bis zum Wert rel hoch, sondern bis zu rel + 1, 
und er zählt nicht bis 0 runter, sondern er zählt noch eins mehr runter, 
nämlich auf 255.

Beispiel: ich setze den rel - Eingang auf 10, dann zählt der Zähler so:

0 1 2 3 4 5 6 7 8 9 10 11 10 9 8 7 6 5 4 3 2 1 0 255 0 1 ...

Warum?
Und kann man das überhaupt so machen, oder ginge das noch eleganter? Und 
wie könnte ich möglichst ressourcenschonend eine dead time einbauen?

Wie ich festgestellt habe, dass das Teil nicht funktioniert:
Ich habe es im Simulator simuliert, da ich den FPGA grade nicht zur Hand 
habe.

Autor: Lothar Miller (lkmiller) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Mein Glückwunsch... ;-)
Du hast das entdeckt, was andere Latency nennen. Das tritt gerne im 
Zusammenhang mit getakteten Prozessen und Signalen auf...

Überleg dir mal, wie das hier zustande und wie du damit fertig werden 
könntest...

Probiers mal so in dieser Art (ungetestet als Denkanstoss):
    if ps > 0 then
      ps <= ps - 1;
    else
      ps <= pre;
      if(dir ='1') then --- runter 
         if(ctr>0) then
             ctr <= ctr-1;
         else
             ctr <= 1;
             dir <= '1';
         end if;
      else              --- hoch
         if(ctr<rel) then
             ctr <= ctr+1;
         else
             ctr <= rel-1;
             dir <= '0';
         end if;
      end if;
    end if;

> Wie ich festgestellt habe, dass das Teil nicht funktioniert:
> Ich habe es im Simulator simuliert
Das ist schon mal der richtige Weg  ;-)

Autor: KeinProblem (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Okay. Das nächste mal bitte Kommentare oder nur Kommentare dann siehst 
du es auch selbst !

Dein Fehler:

Überlauf:

; wenn ctr = 0 dann dir auf 0
if ctr = 0 then
      dcu_reg <= dcu;
      dcv_reg <= dcv;
      dcw_reg <= dcw;
      dir <= '0';

; wenn dir = 0 dann ctr_nxt auf ctr-1 bedeutet 255
ctr_nxt <= ctr + 1 when dir = '0' else ctr - 1;


Unterlauf:

; wenn ctr = rel dann dir auf 1
elsif ctr = rel then
      dcu_reg <= dcu;
      dcv_reg <= dcv;
      dcw_reg <= dcw;
      dir <= '1';
    end if;


; wenn dir = 1 dann ctr_nxt auf ctr+1 bedeutet rel+1
ctr_nxt <= ctr + 1 when dir = '0' else ctr - 1;

Autor: Tobias Plüss (hubertus)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hi Lothar, Hi keinproblem,
danke für eure ausführlichen Antworten. Besonderen Dank an dich, Lothar.
Ich werde deinen Code morgen Abend mal ausprobieren.
Aber Grundsätzlich: ist mein Ansatz, diese PWMs zu machen, richtig? das 
wird in uCs intern ja auch irgendwie so gemacht, nicht?
gibts noch bessere Lösungen? Wie machst du das jeweils?

Dann:
Wie könnte ich auf möglichst einfache Weise Frequenz und Pulsbreite 
eines Signals messen? Hast du dazu auch grade eine schlaue Idee?
Das Signal ist natürlich ein digitales Signal, welches bereits auf 
3.3Volt Pegel umgesetzt ist. Frequenz liegt zwischen einigen 100 Hz und 
ca. 100 kHz. Messen möchte ich, wie gesagt, die Frequenz, sowie die 
Dauer der Positiven Impulse.

Gruss Tobias

PS: ich habe meinen ursprünglichen Code, der ja nicht korrekt 
funktioniert, vorhin mal kurz auf den FPGA runtergeladen. Wie zu 
erwarten war, kommen auch PWM Signale raus, soweit funktioniert die 
Sache also schon. Nur halt dass der Zähler (noch) falsch zählt :-)

Autor: Lothar Miller (lkmiller) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Tobias Plüss schrieb:
> Aber Grundsätzlich: ist mein Ansatz, diese PWMs zu machen, richtig?
Tausend Wege führen nach Rom, deiner ist einer davon...

> Messen möchte ich, wie gesagt, die Frequenz,
Mit welcher Auflösung/Genauigkeit?
Wie schnell willst du das Ergebnis?
Zu den Grundlagen: eine Frequenz wird üblicherweise so gemessen, dass 
während einer Gate-Zeit (z.B. 1 sec) die ankommenden steigenden Flanken 
des Signals gezählt werden.

> sowie die Dauer der Positiven Impulse.
Ja, das ist einfach: eine steigende Flanke startet einen Zähler, die 
fallende Flanke übernimmt den aktuellen Zählerwert. Wobei mit steigender 
und fallender Flanke aber nicht rising_edge() und falling_edge() gemeint 
ist, sondern eine traditionelle Flankenerkennung auf ein 
einsynchronisiertes Signal. So wie das hier:
http://www.lothar-miller.de/s9y/categories/18-Flan...

Autor: Tobias Plüss (hubertus)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hi Lothar,
Als Auflösung wären ca. 10 Hz genügend.
In welcher Geschwindigkeit ich das Ergebnis will, kann ich aus dem 
Stegreif so nicht sagen, ich muss mir das erst überlegen.
Evtl. wäre es einfacher, die Periodendauer zu messen?
Wie wähle ich eine sinnvolle Gate-Zeit für die Frequenzmessung? Die soll 
ja einerseits möglichst lang sein, damit die Messung genau wird, aber 
andererseits muss sie ja möglichst kurz sein, damit ich den Messwert 
schnell bekomme.

Kann ich das mit der Flankenerkennung auch so machen?
signal s_old : std_logic;
signal s_new : std_logic;
signal enable : std_logic;
signal count : integer range 0 to ...
process begin
  wait until rising_edge(clk);
  s_old <= s_new;
  s_new <= my_input;
  if (s_old = '0') and (s_new = '1') then
    enable <= '1';
    count <= 0;
  end if;
  if (s_old = '1') and (s_new = '0') then
    enable <= '0';
  end if;
end process;

process begin
  wait until rising_edge(clk);
  if enable = '1' then
    count <= count + 1;
  end if;
end process;

Autor: Lothar Miller (lkmiller) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Tobias Plüss schrieb:
> Evtl. wäre es einfacher, die Periodendauer zu messen?
Und dann in VHDL einen Kehrwert bilden?
Viel Spass mit der Division. Aber auch da hätte ich was  ;-)
http://www.lothar-miller.de/s9y/archives/29-Divisi...
> Evtl. wäre es einfacher, die Periodendauer zu messen?
Fazit: garantiert nicht.

> Als Auflösung wären ca. 10 Hz genügend.
Dann nimm eine Gate-Zeit von 100ms und du bekommst 10 mal pro Sekunde 
eine aktuelle Frequenz.

> Kann ich das mit der Flankenerkennung auch so machen?
Prinzipiell schon,...
aber dein count hat so Multiple Drivers aus 2 Prozessen heraus... :-o

Du brauchst die Flanke eigentlich nur für die Datenübernahme. Den Zähler 
kannst du auch einfach auf das einsynchronisierte Signal laufen lassen:
signal s_old : std_logic;
signal s_new : std_logic;
signal enable : std_logic;
signal count : integer range 0 to ...
signal duration : integer range 0 to ...
  process begin
    wait until rising_edge(clk);
    s_old <= s_new;
    s_new <= my_input;
    if (s_old = '0') and (s_new = '1') then -- steigende Flanke 
      duration <= count;                    --> letzten Zählerwert übernehmen
    end if;
    if (s_old='1') then                     -- zählen, solange '1'
      count <= count + 1;
     end if;
    if (s_old = '1') and (s_new = '0') then -- fallende Flanke 
      duration <= 0;                        --> Zählerwert zurücksetzen
    end if;
  end process;

EDIT: Quelltext wegen eines Denkfehlers überarbeitet... ;-)

Autor: Tobias Plüss (hubertus)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hi Lothar,

> Und dann in VHDL einen Kehrwert bilden?

Nöö, ohne Kehrwert. Einfach die Periodendauer.

Die Messung der positiven Pulsbreite habe ich jetzt wie folgt 
realisiert:
  signal ct : integer range 0 to 255;
  signal s : integer range 0 to 255;
  signal ch0_old : std_logic;
  signal ch0_new : std_logic;

  process begin
    wait until rising_edge(clk);
    ch0_old <= ch0_new;
    ch0_new <= ch0;
    if (ch0_old = '0') and (ch0_new = '1') then
      ct <= 0;
    end if;
    if ch0_old = '1' then
      if s = pre then
        ct <= ct + 1;
        s <= 0;
      else
        s <= s + 1;
      end if;
    end if;
    if (ch0_old = '1') and (ch0_new = '0') then
      ctr <= ct;
    end if;
  end process;

das kann man schon so machen, oder? es scheint jedenfalls zu 
funktionieren. Nur wie man die Periode misst, weiss ich noch nicht so 
genau.

Autor: Lothar Miller (lkmiller) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Tobias Plüss schrieb:
> Nur wie man die Periode misst, weiss ich noch nicht so genau.
Einfach mal zählen, wie lange es von einer steigenden Flanke des Signals 
zu nächsten steigenden Flanke dauert.

>> Und dann in VHDL einen Kehrwert bilden?
> Nöö, ohne Kehrwert. Einfach die Periodendauer.
Aber wenn du eine Frequenz willst, dann ist das eben der Kehrwert der 
Periodendauer...

Autor: Tobias Plüss (hubertus)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Anstatt die Frequenz kann ich mich auch mit der Periodendauer zufrieden 
geben ;-)

Autor: Lothar Miller (lkmiller) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Dann ist das so eine Art Dreizeiler...
Ich nehme mal deinen Code:
  signal ct : integer range 0 to 255;
  signal p : integer range 0 to 255;  -- periodendauer
  signal h : integer range 0 to 255;  -- dauer vom high
  signal ch0_old : std_logic;
  signal ch0_new : std_logic;

  process begin
    wait until rising_edge(clk);
    ch0_old <= ch0_new;
    ch0_new <= ch0;
    ct <= ct + 1;
    if (ch0_old = '0') and (ch0_new = '1') then -- steigende Flanke
      p  <= ct;                                 -- periodendauer
      ct <= 0;
    end if;
    if (ch0_old = '1') and (ch0_new = '0') then -- fallende Flanke
      h  <= ct;                                 -- dauer high pegel
    end if;
  end process;

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [vhdl]VHDL-Code[/vhdl]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.