Forum: FPGA, VHDL & Co. Digitaler PID Regler in VHDL


von Ali Zafar (Gast)


Lesenswert?

Hallo allerseits,

Ich hab mal ein kleines Problem. Ich habe eine Aufgabe bekommen, einen 
PID Regler in VHDL zu programmieren, das eine kommende Amplitude sowohl 
eine Eigenfrequenz eines Systems regelt. Kann mir einer ne Idee geben 
wie ich so ein Problem angehe, was sollte ich bei der Programmierung 
beachten.

Wäre sehr dankbar für euere Hilfe.

MfG

von Joe G. (feinmechaniker) Benutzerseite


Lesenswert?

Sind die zu regelnde Amplitude und die Eigenfrequenz unabhängig?
Wenn nicht, wie hängen sie zusammen?
Wie wird die Eigenfrequenz beeinflußt?

von Ali Zafar (Gast)


Lesenswert?

Hallo,
Also, ich muss einen generischen Digitalen regler programmieren, die 
Eigenfrequenz und Amplitude sind unabhängig voneinander, und können 
entweder vorgegeben werden oder werden von einem System in den Regler 
eingeschleust. Für den Amplitudenregler wird eine Aplituden Rampe 
eingeschleust und fürden EF-Regler eine Impedance Phase Ramp.
ich weiß es gibt zwei Methoden einen Regler zu entwerfen, 1. man 
entwirft analog einen und diskretisiert ihn oder man entwirft gleich 
einen diskreten Regler. Danke.

von mki (Gast)


Angehängte Dateien:

Lesenswert?

Hi,

vielleicht hilft dir diese Arbeit. Sie beschreibt wie du mit Hilfe von 
Matlab/Simulink einen Regler entwirfst und dann in VHDL exportieren 
kannst.

Ansonsten schlage ich vor du gehst nach deiner zweiten Methode vor. Am 
besten fängst du mit einem einfachen P-Regler an. Der läst sich ganz 
einfach implementieren.

von Ali Zafar (Gast)


Angehängte Dateien:

Lesenswert?

Hallo,
danke für deine Hilfe, hilft mir wirklich sehr, hatte auch schon daran 
gedacht, erst in matlab zu programmieren und dnn in HDl umzuwandeln, da 
geht es einfacher, frag mich aber trotzdem wie ich direkt in VHDL 
implementieren soll.

Hab nun einen dieskreten PID Regler aufgestellt, was ist nun der nächste 
Schritt, wie beginne ich mit der Programmierung in VHDL.Danke.

MfG

von Sebastian (Gast)


Lesenswert?

Diese Seite beschreibt sehr gut die Umsetzung von analogen und digitalen 
Reglern (auch Quellcodebeispeile) Diese muss man dann in VHDL umsetzen

http://www.rn-wissen.de/index.php/Regelungstechnik#PID-Regler

von Ali Zafar (Gast)


Lesenswert?

hallo,
vielen dank, diese seite kenne ich bereits, hab meine anfänglichen 
versuche unternommen um einen pid regler zu programmien, wie siehts aus, 
ist es einigermaßen in Ordnung.Danke.
1
 
2
library IEEE;
3
use IEEE.STD_LOGIC_1164.ALL;
4
use IEEE.NUMERIC_STD.ALL;
5
6
7
entity PID_Regler is
8
   generic(
9
    data_width: integer := 14;
10
    intern_ data_width  := 28;
11
      
12
13
     );
14
   port (
15
    w : in std_logic_vector(data_width -1 downto 0) --:= (others => '0');
16
    x : in std_logic_vector(data_width -1 downto 0) --:= (others => '0');
17
    y : out std_logic_vector(data_width -1 downto 0) --:= (others => '0');
18
    k_p  : in std_logic_vector(intern_data_width-1 downto 0);
19
    k_i  : in std_logic_vector(intern_data_width-1 downto 0);
20
    k_d  : in std_logic_vector(intern_data_width-1 downto 0);
21
    clk_i : in  std_logic;
22
    rst_i : in  std_logic;
23
);
24
end PID_Regler;
25
26
architecture Behavioral of PID_Regler is
27
28
signal e : std_logic_vector(daten_breite-1 downto 0) := (others => '0');
29
30
begin
31
32
  Regler: process(clk_i)
33
34
variable yp: std_logic_vector(intern_data_width-1 downto 0);
35
variable yi: std_logic_vector(intern_data_width-1 downto 0);
36
variable yd: std_logic_vector(intern_data_width-1 downto 0);
37
 
38
if rst_i = '1' then
39
40
      yp := (others => '0');
41
      yi := (others => '0');
42
      yd := (others => '0');
43
      ealt := (others => '0');
44
      y <= (others => '0');
45
else
46
      e  <= std_logic_vector(signed(w) - signed(x));  
47
      yp := (k_p*e);
48
      yi := (yi_alt+(k_i*e*T)); - yi_alt=yi
49
      yd := (k_d*((e-ealt)/T)); -- ealt=e
50
51
      y<= (yp+yi+yd);
52
ealt := e;
53
yi_alt := yi;
54
end if;
55
end process Regler;
56
end Behavioral;


MfG

von mki (Gast)


Lesenswert?

Hi,

ich finde das sieht fürs erste mal ganz gut aus. Was noch fehlt (glaub 
ich) ist sowas wie "if(rising_edge(clk_i))". Ansonsten führt er den 
Process auch bei fallender Flanke aus oder macht sogar was ganz 
undefiniertes.

Was mir noch aufgefallen ist: Du hast bei
1
 yi := (yi_alt+(k_i*e*T));
kein T definiert. Und du brauchst das auch nicht wirklich. Deine Zeit 
ist ja in clk_i mit enthalten und dass der Process mit jedem Zeittakt 
neu ausgeführt wird. Das gleiche gilt auch für yd.

von mac4ever (Gast)


Lesenswert?

Falls dein Problem nur simuliert werden soll, kannst du es so lassen.

Ansonsten würde ich die Variablen durch Signale ersetzen. Bei diesen 
verschachtelten Anweisungen bekommst du sonst sicher Probleme mit der 
Laufzeit bzw. maximalen Taktrate.
Signale generieren dir eine Pipeline wodurch das Resultat zwar um einen 
Takt verzögert am Ausgang erscheint, du allerdings den Systemtakt stark 
erhöhen kannst. Das wiederum kann der Bandbreite des Reglers nur 
dienlich sein.

Btw.: Die Divisionsoperation ist nicht so einfach zu synthetisieren. Das 
wird dir jedes Tool um die Ohren hauen. Ich persönlich löse soetwas gern 
durch Multiplikation mit dem Reziprokwert aus einem ROM. Das geht 
schnell und braucht nicht viel Ressourcen. Dafür ist man aber auf eine 
max. Anzahl von Werte für T beschränkt.

von Ali Zafar (Gast)


Lesenswert?

Hallo,
danke für deine Antwort, kannt du mir erklären wie das mit dem 
reziprokwert funktioniert, das habe ich so nicht verstanden.Danke.

MfG

von Hans (Gast)


Lesenswert?

1/5 : division!
1/5 = 1 * (1/5) = 1 * 0.2 : multiplikation!

0.2 ist der reziproke wert zu 5, das muss in ner tabelle liegen.

von mac4ever (Gast)


Lesenswert?

Sagen wir, du möchtest mit Werten von 1-500 dividieren. Also nimmst du 
ein BlockRAM und hinterlegst an den Adressen 1-500 die entsprechenden 
(vorberechneten) Reziprokwerte. Jetzt "wählst" du dir den gewünschten 
Divisor durch anlegen der entsprechenden Adresse (also dem 
Divisionswert) an den RAM. Im nächsten Takt liegt der Reziprokwert am 
Ausgang des BlockRAMs und wird einem Multiplizierer zugeführt. Schwupps, 
Division erledigt.

Nachteil dieser Methode: Bei zu geringer Bitbreite des Divisors wird die 
Genauigkeit schlechter.

von Ali Zafar (Gast)


Lesenswert?

Hallo,
Ich hab die Variable yp durch signal ersetzt, gibt dann fehlermeldung

Signal "yp" cannot be target of variable assignment statement.
Signal declaration 'yp' not allowed in this region

dieser Code fonktioniert einwandfrei mit variable bei dem Obigen hatte 
ich paar Fehler drin:
1
library IEEE;
2
use IEEE.STD_LOGIC_1164.ALL;
3
use IEEE.NUMERIC_STD.ALL;
4
use ieee.math_real.all;
5
6
7
entity PID_Regler is
8
   generic(
9
    data_width: integer := 16
10
    --intern_data_width: integer  := 28;
11
    );
12
    
13
   port(
14
    w : in signed(data_width-1 downto 0); --:= (others => '0');
15
    x : in signed(data_width-1 downto 0); --:= (others => '0');
16
    y : out signed(data_width-1 downto 0); --:= (others => '0');
17
    --k_p  : in std_logic_vector(data_width-1 downto 0);
18
    --k_i  : in std_logic_vector(data_width-1 downto 0);
19
    --k_d  : in std_logic_vector(data_width-1 downto 0);
20
    clk_i : in  std_logic;
21
    rst_i : in  std_logic
22
);
23
end PID_Regler;
24
25
architecture Behavioral of PID_Regler is
26
27
constant k_p : integer := 10;
28
constant k_i : integer := 100;
29
constant k_d : integer := 1;
30
--constant T: real := 0.1;
31
32
signal e : signed(data_width-1 downto 0) := (others => '0');
33
34
begin
35
36
Regler: process(clk_i)
37
38
signal yp: signed(data_width-1 downto 0) := (others => '0');
39
variable yi: signed(data_width-1 downto 0) := (others => '0');
40
variable yd: signed(data_width-1 downto 0) := (others => '0');
41
variable ealt : signed(data_width-1 downto 0) := (others => '0');
42
variable yi_alt : signed(data_width-1 downto 0) := (others => '0');
43
 
44
begin
45
if rising_edge(clk_i) then
46
  if rst_i = '1' then
47
    yp := (others => '0');
48
    yi := (others => '0');
49
    yd := (others => '0');
50
    ealt := (others => '0');
51
    y <= (others => '0');
52
    else
53
    e  <= (signed(w) - signed(x));  
54
    yp := (k_p*e);
55
    yi := (yi_alt+(k_i*e/10)); --yi_alt=yi
56
    yd := (k_d*((e-ealt)*10)); -- ealt=e
57
58
    y <= (yp+yi+yd);
59
    --y <= resize((yp + yi + yd));
60
    ealt := e;
61
    yi_alt := yi;
62
    end if;
63
end if;
64
end process Regler;
65
end Behavioral;

von Mathi (Gast)


Lesenswert?

Ein Signal darf nicht innerhalb eines Prozesses deklariert werden.
Schreibe mal die Deklaration zwischen architecture und begin.

Außerdem muss es
1
yp <= (others => '0');
bzw.
1
yp <= (k_p*e);

von Der Profi (Gast)


Lesenswert?

>Ich persönlich löse soetwas gern
>durch Multiplikation mit dem Reziprokwert aus einem ROM.

Ja, habe ich auch immer gemacht. Bei höheren Genauigkeiten ist das aber 
untauglich. Es ist zweckmäßiger, die Division auf Binärteilung zu 
normieren und entsprechend zu multiplizieren, also die Restrechung zu 
skalieren.

>Also nimmst du ein BlockRAM und hinterlegst an den Adressen 1-500
> die entsprechenden (vorberechneten) Reziprokwerte.

Ja ist klar. Ein Blockram mit 512 Speicherzellen. In diese Architektur 
bekommt man aber auch ohne große Schwierigkeiten die Terme für eine 
binäre Division hinein.

>Nachteil dieser Methode: Bei zu geringer Bitbreite des Divisors wird die
>Genauigkeit schlechter.

Genau. Denn wenn man genauer werden will, muss man manuell dividieren, 
also den Rest nochmal prozessieren. Das sind dann 2x(2+1) Takt = 6 Takte 
+ handling drum herum.

Ein Core im Cyclone 2 macht das auch in 8-10 Takten voll gepipelined. 
Der nutzt dazu die sog. Softmultiplier, die er ins RAM stopft. Und er 
ist zudem auch noch "restwertfähig".

Als Beispiel habe ich eine Division über 20bit/10 Bit mit nochmaliger 
Restwertberechung Rest*10Bit/10Bit und bekomme ein 20Bit breites Ergbnis 
nach nur 20 Takten. Die Rechenpipeline ist so aufgezogen, daß alle 
Divisionen (durchaus ähnlich einem Regler mit " /T") einzeln vorliegen. 
Dann packt der Core 8 Kanäle und hat einen Durchsatz von 1/(80 clks) je 
Kanal -> 1 MHz. (immer noch "analog klein" gegen das Wandlereinlesen und 
-auslese z.B.).

von Ali (Gast)


Lesenswert?

Hallo,
Habs soweit einen konkreten PID regler aufgestellt, nun will ich ihn 
soweit erweitern, die eigentliche Aufgabe ist ja, dass der Regler extern 
Werte in Form Amplitude und Frequenz einer Spannung abbekommt, die 
werden dann durch einen A/D Wandler umgewandelt, die Auflösung des A/D 
wandlers ist 14 bit groß, das heißt doch, dasss ich mein w (Sollwert) 
und x(Istwert)
als std_logic_vector (13 downto 0) deklarieren muss.
Anschließend wird wieder D/A umgewandelt und die Daten an ein System 
gesendet.
Nochwas, die PID Anteile sind nicht konkret festgelegt, sondern sollen 
in ein Register festgelegt werden wobei die dann abgerufen werden, dass 
ist für mich der schwierige Teil.

Also muss ich doch alle signal und variablen usw. als std_logic_vector 
deklarieren oder.

So sieht mein gedankengang aus, hoffe ihr könnt mir weiterhelfen.Danke.
1
 
2
library IEEE;
3
use IEEE.STD_LOGIC_1164.ALL;
4
use IEEE.NUMERIC_STD.ALL;
5
use ieee.math_real.all;
6
7
8
entity PID_Regler is
9
   generic(
10
    data_width: integer := 14
11
    intern_data_width: integer  := 18;
12
    );
13
 
14
port(
15
      clk_i              :  in  std_logic;
16
      rst_i              :  in  std_logic;
17
      k_p          : in std_logic_vector(intern_data_width-1 downto 0);
18
      k_i          : in std_logic_vector(intern_data_width-1 downto 0);
19
      k_d          : in std_logic_vector(intern_data_width-1 downto 0);
20
      data_w_i        :  in std_logic_vector(data_width-1 downto 0);
21
      data_x_i        :  in std_logic_vector(data_width-1 downto 0);
22
      data_y_o        :  out std_logic_vector(data_width-1 downto 0)
23
  );
24
end PID_Regler
25
26
architecture Behavioral of PID_Regler is
27
28
signal  w    :  std_logic_vector(data_width-1 downto 0);
29
signal  x    :  std_logic_vector(data_width-1 downto 0);
30
signal  e    :   std_logic_vector(data_width-1 downto 0);
31
32
signal  y  : std_logic_vector(data_width-1 downto 0);
33
signal yp: std_logic_vector(intern_data_width-1 downto 0) := (others => '0');
34
signal yi: std_logic_vector(intern_data_width-1 downto 0) := (others => '0');
35
signal yd: std_logic_vector(intern_data_width-1 downto 0) := (others => '0');
36
signal ealt : std_logic_vector(intern_data_width-1 downto 0) := (others => '0');
37
signal yi_alt : std_logic_vector(intern_data_width-1 downto 0) := (others => '0');
38
39
begin
40
Regler: process(clk_i)
41
begin
42
--Mappen der Eingeänge
43
w  <= data_w_i;
44
x  <= data_x_i;
45
  
46
--Regeldifferenz
47
e  <= std_logic_vector(signed(w) - signed(x));  
48
49
50
if rising_edge(clk_i) then
51
  if rst_i = '1' then
52
    yp <= (others => '0');
53
    yi <= (others => '0');
54
    yd <= (others => '0');
55
    ealt <= (others => '0');
56
    y <= (others => '0');
57
    else
58
    e  <= (w-x);
59
    yp <= (k_p*e);
60
    yi <= (yi_alt+(k_i*e/10)); --yi_alt=yi
61
    yd <= (k_d*((e-ealt)*10)); -- ealt=e
62
63
    y <= (yp+yi+yd);
64
    --y <= resize((yp + yi + yd));
65
    ealt <= e;
66
    yi_alt <= yi;
67
    end if;
68
end if;
69
end process Regler;
70
data_y_o  <= y;
71
end Behavioral;

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


Lesenswert?

Ali schrieb:
> use ieee.math_real.all;
Wofür?

> Also muss ich doch alle signal und variablen usw. als std_logic_vector
> deklarieren oder.
Wenn ich eine rechenlastige Aufgabe habe, dann werden die Signale an den 
Ports als std_logic_vector übergeben und in meinem Modul 
schnellstmöglich nach signed oder unsigned oder integer gewandelt. Es 
ist unsinnig, mit nackten Vektoren zu rechnen. Denn was gibt z.B. "111" 
+ "111"?

von mac4ever (Gast)


Lesenswert?

Der Profi schrieb:
> Ein Core im Cyclone 2 macht das auch in 8-10 Takten voll gepipelined.
> Der nutzt dazu die sog. Softmultiplier, die er ins RAM stopft. Und er
> ist zudem auch noch "restwertfähig".

Ich habe auch nie davon abgeraten einen IP-Core zu nutzen. Was du unter 
"restwertfähig" verstehst würde mich brennend interessieren. Die von mir 
vorgeschlagene Variante braucht keinen expliziten Rest, da das Ergebnis 
automatisch zu einer Festkommazahl mit x Nachkommastellen wird.

Der Profi schrieb:
> Ja ist klar. Ein Blockram mit 512 Speicherzellen.

Was willst du mir damit sagen? Also mehrere RAM-Blöcke zu verknoten, um 
512 32-Bit Werte zu speichern, sollte doch kein Problem darstellen.


@Ali
std_logic_vector ist die richtige Wahl. Für mathematische Operationen 
bieten sich aber auch unsinged/signed aus ieee.numeric_std an. 
Geschmacksache ...
Mit was ist das FPGA denn verbunden? Mikrocontroller oder PC? Dann würde 
ich auf I2C oder RS-232 zurückgreifen, um die Parameter zu übertragen. 
Dafür gibt es genügend Cores, etwa bei opencores.org

von Ali (Gast)


Lesenswert?

Hallo,
danke für deine Antwort, es ist mit einem Microcontroller verbunden, 
aber die Daten können über eine RS232 Schnittstelle auch mit dem 
computer verbunden werden, und über diese Schnittstelle kann man auf die 
Register zugreifen. Hauptsächlich werden die Parameter mit RS232 
übetragen.

opencores.org: was genau enthält diese website, was ist für mich 
brauchbar, hab diese seite noch nie besucht.
Danke.

von Ali (Gast)


Lesenswert?

Hallo,
Danke für deine Antwort,
wie genau wandle ich die Daten in signed um etwa so
1
e  <= std_logic_vector(signed(w) - signed(x));

Danke
MfG

von mac4ever (Gast)


Lesenswert?

Die Umwandlung ist so i.O. wenn e,w und x vom Type std_logic_vector 
sind.

Auf opencores.org gibt es verschiedenste IP-Cores. So z.B. 
http://opencores.org/project,mmuart ... eine RS-232 UART für die 
Verbindung von FPGA und PC/Mikrocontroller. Erspart einen die Arbeit das 
Rad neu zu erfinden :)

von Ali (Gast)


Lesenswert?

Hallo,
Vielen Dank nochmal.
Muss ich die Umwandlung auch für yp yi.... vornehmen, oder kann ich die 
so stehen lassen.Danke.

Mfg

von mac4ever (Gast)


Lesenswert?

Sinnvoll wäre es mit Sicherheit wenn Du die gesamte Rechnung über signed 
laufen lässt. Dafür kannst du bei der Signaldefinition anstatt 
std_logic_vector(x downto 0) -> signed(x downto 0) schreiben. Allerdings 
müssen k_(p|i|d) über einen Cast zu signed konvertiert werden.

von A. (Gast)


Lesenswert?

Hallo,
da bin ich wieder,
ich muss die Einstellungen für den P- I- und D-Anteil in einem Register 
ablegen wie genau realisiere ich das.Danke.


Hab mein Code soweit umgeändert:
1
library IEEE;
2
use IEEE.STD_LOGIC_1164.ALL;
3
use IEEE.NUMERIC_STD.ALL;
4
use ieee.math_real.all;
5
6
7
entity PID_Regler is
8
   generic(
9
    data_width: integer := 14;
10
    intern_data_width: integer := 18
11
    );
12
 
13
port(
14
      clk_i              :  in  std_logic;
15
      rst_i              :  in  std_logic;
16
      k_p          : in std_logic_vector(intern_data_width-1 downto 0);
17
      k_i          : in std_logic_vector(intern_data_width-1 downto 0);
18
      k_d          : in std_logic_vector(intern_data_width-1 downto 0);
19
      data_w_i        :  in std_logic_vector(data_width-1 downto 0);
20
      data_x_i        :  in std_logic_vector(data_width-1 downto 0);
21
      data_y_o        :  out std_logic_vector(data_width-1 downto 0)
22
  );
23
end PID_Regler;
24
25
architecture Behavioral of PID_Regler is
26
27
signal  w    :  std_logic_vector(data_width-1 downto 0);
28
signal  x    :  std_logic_vector(data_width-1 downto 0);
29
signal  e    :   std_logic_vector(data_width-1 downto 0);
30
31
signal  y  : std_logic_vector(data_width-1 downto 0);
32
signal yp: std_logic_vector(intern_data_width-1 downto 0) := (others => '0');
33
signal yi: std_logic_vector(intern_data_width-1 downto 0) := (others => '0');
34
signal yd: std_logic_vector(intern_data_width-1 downto 0) := (others => '0');
35
signal ealt : std_logic_vector(intern_data_width-1 downto 0) := (others => '0');
36
signal yi_alt : std_logic_vector(intern_data_width-1 downto 0) := (others => '0');
37
38
begin
39
Regler: process(clk_i)
40
begin
41
--Mappen der Eingeänge
42
w  <= data_w_i;
43
x  <= data_x_i;
44
  
45
--Regeldifferenz
46
e  <= std_logic_vector(signed(w) - signed(x));  
47
48
49
if rising_edge(clk_i) then
50
  if rst_i = '1' then
51
    yp <= (others => '0');
52
    yi <= (others => '0');
53
    yd <= (others => '0');
54
    ealt <= (others => '0');
55
    y <= (others => '0');
56
    else
57
    --e  <= std_logic_vector(signed(w) - signed(x));
58
    yp <= std_logic_vector(signed(k_p)*signed(e));
59
    yi <= std_logic_vector(signed(yi_alt)+(signed(k_i)*signed(e)/10)); --yi_alt=yi
60
    yd <= std_logic_vector(signed(k_d)*((signed(e)-signed(ealt))*10)); -- ealt=e
61
62
    y <= std_logic_vector(signed(yp)+signed(yi)+signed(yd));
63
    --y <= resize((yp + yi + yd));
64
    ealt <= e;
65
    yi_alt <= yi;
66
    end if;
67
end if;
68
end process Regler;
69
data_y_o  <= y;
70
end Behavioral;

von Dudhat M. (Firma: student) (dudhat01)


Lesenswert?

thank you guys for aour support in PID controller design!

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.