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


von Tobias P. (hubertus)


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:
1
library ieee;
2
use ieee.std_logic_1164.all;
3
use ieee.numeric_std.all;
4
5
entity simple_pwm is
6
  port(
7
    clk : in std_logic;               --clock
8
9
    rel : in integer range 0 to 255;  --reload value
10
    pre : in integer range 0 to 255;  --prescaler value
11
12
    dcu : in integer range 0 to 255;  --duty cycle u phase
13
    dcv : in integer range 0 to 255;  --duty cycle v phase
14
    dcw : in integer range 0 to 255;  --duty cycle w phase
15
16
    hsu : out std_logic;              --high side u output
17
    lsu : out std_logic;              --low side u output
18
    hsv : out std_logic;              --high side v output
19
    lsv : out std_logic;              --low side v output
20
    hsw : out std_logic;              --high side w output
21
    lsw : out std_logic               --low side w output
22
  );
23
end simple_pwm;
24
25
architecture behv of simple_pwm is
26
  signal ctr : integer range 0 to 255;
27
  signal ps : integer range 0 to 255;
28
29
  signal dcu_reg : integer range 0 to 255;
30
  signal dcv_reg : integer range 0 to 255;
31
  signal dcw_reg : integer range 0 to 255;
32
  
33
  signal ctr_nxt : integer range 0 to 255;
34
  
35
  signal dir : std_logic;
36
  
37
begin
38
39
  process begin
40
    wait until rising_edge(clk);
41
    if ps > 0 then
42
      ps <= ps - 1;
43
    else
44
      ps <= pre;
45
      ctr <= ctr_nxt;
46
    end if;
47
    
48
    if ctr = 0 then
49
      dcu_reg <= dcu;
50
      dcv_reg <= dcv;
51
      dcw_reg <= dcw;
52
      dir <= '0';
53
    elsif ctr = rel then
54
      dcu_reg <= dcu;
55
      dcv_reg <= dcv;
56
      dcw_reg <= dcw;
57
      dir <= '1';
58
    end if;
59
60
    if dcu_reg < ctr then
61
      lsu <= '1';
62
      hsu <= '0';
63
    else
64
      lsu <= '0';
65
      hsu <= '1';
66
    end if;
67
    
68
    if dcv_reg < ctr then
69
      lsv <= '1';
70
      hsv <= '0';
71
    else
72
      lsv <= '0';
73
      hsv <= '1';
74
    end if;
75
    
76
    if dcw_reg < ctr then
77
      lsw <= '1';
78
      hsw <= '0';
79
    else
80
      lsw <= '0';
81
      hsw <= '1';
82
    end if;
83
84
  end process;
85
  
86
  ctr_nxt <= ctr + 1 when dir = '0' else ctr - 1;
87
88
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.

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

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

von Tobias P. (hubertus)


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.

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


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):
1
    if ps > 0 then
2
      ps <= ps - 1;
3
    else
4
      ps <= pre;
5
      if(dir ='1') then --- runter 
6
         if(ctr>0) then
7
             ctr <= ctr-1;
8
         else
9
             ctr <= 1;
10
             dir <= '1';
11
         end if;
12
      else              --- hoch
13
         if(ctr<rel) then
14
             ctr <= ctr+1;
15
         else
16
             ctr <= rel-1;
17
             dir <= '0';
18
         end if;
19
      end if;
20
    end if;

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

von KeinProblem (Gast)


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;

von Tobias P. (hubertus)


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 :-)

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


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-Flankenerkennung

von Tobias P. (hubertus)


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?
1
signal s_old : std_logic;
2
signal s_new : std_logic;
3
signal enable : std_logic;
4
signal count : integer range 0 to ...
5
process begin
6
  wait until rising_edge(clk);
7
  s_old <= s_new;
8
  s_new <= my_input;
9
  if (s_old = '0') and (s_new = '1') then
10
    enable <= '1';
11
    count <= 0;
12
  end if;
13
  if (s_old = '1') and (s_new = '0') then
14
    enable <= '0';
15
  end if;
16
end process;
17
18
process begin
19
  wait until rising_edge(clk);
20
  if enable = '1' then
21
    count <= count + 1;
22
  end if;
23
end process;

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


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-Division-in-VHDL.html
> 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:
1
signal s_old : std_logic;
2
signal s_new : std_logic;
3
signal enable : std_logic;
4
signal count : integer range 0 to ...
5
signal duration : integer range 0 to ...
6
  process begin
7
    wait until rising_edge(clk);
8
    s_old <= s_new;
9
    s_new <= my_input;
10
    if (s_old = '0') and (s_new = '1') then -- steigende Flanke 
11
      duration <= count;                    --> letzten Zählerwert übernehmen
12
    end if;
13
    if (s_old='1') then                     -- zählen, solange '1'
14
      count <= count + 1;
15
     end if;
16
    if (s_old = '1') and (s_new = '0') then -- fallende Flanke 
17
      duration <= 0;                        --> Zählerwert zurücksetzen
18
    end if;
19
  end process;

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

von Tobias P. (hubertus)


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:
1
  signal ct : integer range 0 to 255;
2
  signal s : integer range 0 to 255;
3
  signal ch0_old : std_logic;
4
  signal ch0_new : std_logic;
5
6
  process begin
7
    wait until rising_edge(clk);
8
    ch0_old <= ch0_new;
9
    ch0_new <= ch0;
10
    if (ch0_old = '0') and (ch0_new = '1') then
11
      ct <= 0;
12
    end if;
13
    if ch0_old = '1' then
14
      if s = pre then
15
        ct <= ct + 1;
16
        s <= 0;
17
      else
18
        s <= s + 1;
19
      end if;
20
    end if;
21
    if (ch0_old = '1') and (ch0_new = '0') then
22
      ctr <= ct;
23
    end if;
24
  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.

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


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...

von Tobias P. (hubertus)


Lesenswert?

Anstatt die Frequenz kann ich mich auch mit der Periodendauer zufrieden 
geben ;-)

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

Dann ist das so eine Art Dreizeiler...
Ich nehme mal deinen Code:
1
  signal ct : integer range 0 to 255;
2
  signal p : integer range 0 to 255;  -- periodendauer
3
  signal h : integer range 0 to 255;  -- dauer vom high
4
  signal ch0_old : std_logic;
5
  signal ch0_new : std_logic;
6
7
  process begin
8
    wait until rising_edge(clk);
9
    ch0_old <= ch0_new;
10
    ch0_new <= ch0;
11
    ct <= ct + 1;
12
    if (ch0_old = '0') and (ch0_new = '1') then -- steigende Flanke
13
      p  <= ct;                                 -- periodendauer
14
      ct <= 0;
15
    end if;
16
    if (ch0_old = '1') and (ch0_new = '0') then -- fallende Flanke
17
      h  <= ct;                                 -- dauer high pegel
18
    end if;
19
  end process;

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.