Maciej G.

Maciej G. Projektant /
Programista, Famor
S.A.

Temat: Maszyna stanów - sterownik rolet

Witam wszystkich,

Zaprojektowałem (moją pierwszą w VHDL) maszynę stanów dla prostego sterownika rolet opartego o układ FPGA i mostek MOSFET do sterowania silniczkiem prądu stałego 12V.

Mostek MOSFET ma piny wejściowe:

pwm : wejście sygnału PWM (do sterowania prędkością silnika)

DIRPIN : kierunek obrotów silnika - gdy 0 to kierunek "do przodu"

SLPPIN : blokada mostka (silnika) - gdy 0 to mostek zablokowany

Mamy teraz rolety na oknie balkonowym sterowane silniczkiem 12V.

Są dwa przyciski (aktywowane sygnałem LOW - 0 logiczne)

bt1 - do zamykania rolet

bt2 - do otwierania rolet

oraz dwa wyłączniki krańcowe

kranc END: 0 -gdy rolety dotrą do końca okna (czyli stan 1 gdy koniec nie został osiągniety)

kranPOC : 0 - gdy rolety są w pełni otwarte (pozycja początkowa)

Mamy 4 stany maszyny:

1) S0 - otwarte
2) S1 - zamykanie (trwa jakiś czas - generowany jest sygnał PWM podawany na mostek)
3) S2 - zamkniete
4) S3 - otwieranie (też trwa jakiś czas - generowany jest sygnał PWM podawany na mostek, tylko kierunek jest odwrotny)

Przejścia:

1) z S0 do S1 gdy ((bt1 = '0') and (krancPOC = '0'))
2) z S1 do S2 gdy ((krancEND = '0') and (krancPOC = '1'))
(tzn. gdy roleta dotrze do końca zakresu - okna)
3) z S2 do S3 gdy ((bt2 = '0') and (krancEND = '0'))
4) z S3 do S0 gdy ((krancPOC = '0') and (krancEND = '1'))

A tak wygląda implementacja maszyny stanów w VHDL:



library IEEE;
use IEEE.STD_LOGIC_1164.ALL;

entity DMASM is
Port ( CLK : in STD_LOGIC;
RST : in STD_LOGIC;
bt1 : in STD_LOGIC;--btn zamykanie
bt2 : in STD_LOGIC;--btn otwieranie
krancEND : in STD_LOGIC;--active 0
krancPOC : in STD_LOGIC;--active 0
DIRPIN : out STD_LOGIC;--Direction Motor
SLPPIN : out STD_LOGIC;--SLEEP PIN Mostek
pb1_Zam : out STD_LOGIC;--gdy 0 zamykanie
pb2_Otw : out STD_LOGIC);--gdy 0 otwieranie
end DMASM;

architecture BEHAVE of DMASM is
type STATE is (S0Otwarte, S1Zamykanie, S2Zamkniete, S3Otwieranie);
signal CURRENT_STATE, NEXT_STATE: STATE;
begin
SEQ: process (RST, CLK)
begin
if (RST = '0') then
CURRENT_STATE <= S0Otwarte;
elsif (CLK' event and CLK = '1' ) then
CURRENT_STATE <= NEXT_STATE;
end if;
end process;

COMB: process (CURRENT_STATE, bt1, bt2, krancEND, KrancPOC)
begin
DIRPin <= '0'; --Kierunek: FORWARD
SLPPin <= '0'; --Blokada mostka
pb1_Zam <= '1'; --klawisz nieaktywny
pb2_Otw <= '1'; --klawisz nieaktywny
case CURRENT_STATE is
when S0Otwarte => DIRPin <= '0';SLPPin <= '1';pb1_Zam <= '1';pb2_Otw <= '1';--blokada ALL
if ((bt1 = '0') and (krancPOC = '0')) then
NEXT_STATE <= S1Zamykanie;
else
NEXT_STATE <= S0Otwarte;
end if;
when S1Zamykanie => DIRPin <= '0';SLPPin <= '1';pb1_Zam <= '0';pb2_Otw <= '1';--FORWARD,MOSTEK OTWARTY
if ((krancEND = '0') and (krancPOC = '1')) then
NEXT_STATE <= S2Zamkniete;
else
NEXT_STATE <= S1Zamykanie;
end if;
when S2Zamkniete => DIRPin <= '1';SLPPin <= '0';pb1_Zam <= '1';pb2_Otw <= '1';--blokada ALL
if ((bt2 = '0') and (krancEND = '0'))then
NEXT_STATE <= S3Otwieranie;
else
NEXT_STATE <= S2Zamkniete;
end if;
when S3Otwieranie => DIRPin <= '1';SLPPin <= '1';pb1_Zam <= '1';pb2_Otw <= '0';--BACKWARD,MOSTEK OTWARTY
if ((krancPOC = '0') and (krancEND = '1')) then
NEXT_STATE <= S0Otwarte;
else
NEXT_STATE <= S3Otwieranie;
end if;
end case;
end process;
end BEHAVE;



Czy tak zaprojektowana maszyna stanów jest poprawna?

(Syntetyzuje się bez błędów, czy ostrzeżeń w "ISE WabPack Xilinx).

Gdyby ktoś miał jakieś uwagi dot. ulepszenia kodu byłbym wdzięczny.

Ta maszyna stanów jest częścią większego projektu sterownika rolet na CPLD/FPGA (który zamierzam praktycznie użyć)

PozdrawiamTen post został edytowany przez Autora dnia 13.09.17 o godzinie 16:03
Jakub Tyburski

Jakub Tyburski Asystent dydaktyczny
- Wojskowa Akademia
Techniczna w War...

Temat: Maszyna stanów - sterownik rolet

Maszyna jak najbardziej w porządku. Niemniej trzy rzeczy, żeby to wyglądało już perfecto:

1) Zamiast:
COMB: process (CURRENT_STATE, bt1, bt2, krancEND, KrancPOC)
begin
DIRPin <= '0'; --Kierunek: FORWARD
SLPPin <= '0'; --Blokada mostka
pb1_Zam <= '1'; --klawisz nieaktywny
pb2_Otw <= '1'; --klawisz nieaktywny

napisz:
COMB: process (CURRENT_STATE, bt1, bt2, krancEND, KrancPOC)
begin

jako, że i tak wartości, które na samym początku przypisujesz zostają nadpisane przez wartości, które są zawarte w poszczególnych stanach, które napisałeś w warunku "case" (będzie tak nawet wtedy, gdy całość wprowadzisz w stan resetu - wtedy też będzie nadpisywanie wartości przez wybrany stan). No chyba, że chciałeś, żeby po zdjęciu resetu takie wartości były. Jeśli tak to dopisz po prostu dodatkowy stan, w którym zawrzesz te przypisania i też sprawdzisz czy doszło do wyjścia z resetu. I już.

2) Mała uwaga co do "case-ów": zwykle pisze się w nich warunek alternatywny na samym końcu tj. w przypadku VHDL-a "when others => instrukcje", co się przydaje bo w ten sposób nie musisz wszystkich stanów wypisywać, a ponadto nie prowadzisz w ten sposób do możliwych zatrzasków (zawsze niepełne warunki "if" i "case" są ich przyczyną, choć zależy właśnie od kodu - jeśli w podglądzie RTL nie zostały stworzone to może tak zostać - no ale jak wiadomo - raz się człowiek przyzwyczai, zobaczy, że mu nie stworzyło czegoś takiego tj. udało mu się, to potem tak już bez pomyślunku leci i się potem dziwi, że są problemy). Krótko mówiąc: proponuję zamiast:

when S3Otwieranie => DIRPin <= '1';SLPPin <= '1';pb1_Zam <= '1';pb2_Otw <= '0';--BACKWARD,MOSTEK OTWARTY
if ((krancPOC = '0') and (krancEND = '1')) then
NEXT_STATE <= S0Otwarte;
else
NEXT_STATE <= S3Otwieranie;
end if;

napisać po prostu:

when others => DIRPin <= '1';SLPPin <= '1';pb1_Zam <= '1';pb2_Otw <= '0';--BACKWARD,MOSTEK OTWARTY
if ((krancPOC = '0') and (krancEND = '1')) then
NEXT_STATE <= S0Otwarte;
else
NEXT_STATE <= S3Otwieranie;
end if;

i nie musisz pamiętać o wszystkich stanach tj. rozpisywać je (oszczędność czasu, kodu i twoich rąk na klawiaturze)

3) Generalnie porty można deklarować tak jak piszesz tj:

entity DMASM is
Port ( CLK : in STD_LOGIC;
RST : in STD_LOGIC;
bt1 : in STD_LOGIC;--btn zamykanie
bt2 : in STD_LOGIC;--btn otwieranie
krancEND : in STD_LOGIC;--active 0
krancPOC : in STD_LOGIC;--active 0
DIRPIN : out STD_LOGIC;--Direction Motor
SLPPIN : out STD_LOGIC;--SLEEP PIN Mostek
pb1_Zam : out STD_LOGIC;--gdy 0 zamykanie
pb2_Otw : out STD_LOGIC);--gdy 0 otwieranie
end DMASM;


ale dla prostoty można też tak:

entity DMASM is
Port (Wejscia :in std_logic_vector(0 to 5);
Wyjscia :out std_logic_vector(0 to 3);
end DMASM;


Wtedy zamiast pisać mnogą ilość nazw dla każdego z wejść czy wyjść lecisz prościej bitami (tj. dany bit odpowiada temu, a temu wejściu czy wyjściu). Mniej w ten sposób kodu piszesz. Można co prawda przyczepić się w ten sposób do czytelności kodu - nie szkodzi - idzie się przyzwyczaić. A jeśli nie to od czego są aliasy w VHDL-u, które definiujesz tak jak tutaj:
http://www.ics.uci.edu/~jmoorkan/vhdlref/aliasdec.html

I tyle :)Ten post został edytowany przez Autora dnia 14.09.17 o godzinie 10:39
Jakub Tyburski

Jakub Tyburski Asystent dydaktyczny
- Wojskowa Akademia
Techniczna w War...

Temat: Maszyna stanów - sterownik rolet

Generalnie to taka maszynę stanów to można i na warunku "if" zrobić z lenistwa (czego oczywiście nie zaleca się robić przy większych maszynach stanów tj. gdy więcej stanów jest, jako że wtedy syntezowany jest coraz to większy układ, a co za tym idzie odznaczający się coraz większymi opóźnieniami - niejako chodzi o budowę układu z bramek, który jednocześnie ma sprawdzać i stan układu i realizować funkcje dla każdego ze stanów.

Co innego warunek "case", w którym to sprawdzanie stanów jest "wyodrebnione", a sam układ budowany jest już pod kątem realizacji funkcji w każdym z stanów - starczy że spojrzysz na podgląd RTL takiej większej maszyny to zobaczysz, że przy "if"-ie masz jeden kompletny układ, a przy "case" masz bloczek, który sprawdza stan układu oraz układ po prostu podłączony pod ten bloczek). Można też taką maszynę stanów rozbić na komponenty będące mniejszymi maszynami stanów, dzięki czemu jeszcze równomierniej rozłożyć można opóźnienia, a przy okazji je kompensować jeśli takie komponenty działają na zegarze i dążyć tym samym do nawet budowy architektury potokowej jeśli to możliwe.
Ale to już tak na boku gdybyś chciał coś znacznie większego budować co np: zawiera z 500 stanów :)Ten post został edytowany przez Autora dnia 14.09.17 o godzinie 10:56
Jakub Tyburski

Jakub Tyburski Asystent dydaktyczny
- Wojskowa Akademia
Techniczna w War...

Temat: Maszyna stanów - sterownik rolet

Aaaa i ten - można też i "bezstanowo" napisać coś takiego, ale ponieważ ty pytasz o maszynę stanów to stąd też tego wątku nie rozwijam, jako że w końcu jak to w programowaniu - jeden woli taką drogę, a drugi inną. Ponadto w twym przypadku to by nie miało jakiegoś większego znaczenia bo ta maszyna stanów jest dosyć mała. Ale to też tak na boku :)
Maciej G.

Maciej G. Projektant /
Programista, Famor
S.A.

Temat: Maszyna stanów - sterownik rolet

Jakub,

bardzo dziękuję za wszystkie uwagi i propozycje (wszystkie z nich uwzględnie w tym projekcie).

Pozdrawiam
Maciej G.

Maciej G. Projektant /
Programista, Famor
S.A.

Temat: Maszyna stanów - sterownik rolet

Cześć,

dodałem jeszcze jeden stan S4Awaria. Ma on być osiągnięty gdy sygnał wejściowy OVERRIDE ma wartość 0 (LOW). Jak już wspomniałem mostek MOSFET użyty do sterowania silnikiem ma wyjście napięciowe proporcjonalne do prądu płynącego przez silnik. Zrezygnowałem z użycia przetwornika A/C gdyż podrażało by to cenę układu. Zamiast tego użyję komparatora z układem napięcia odniesienia, który będzie generował stan niski O gdy będzie płynął przez silnik za duży prąd (sygnał wejściowy OVERRIDE).
Implementacja mojej maszyny stanów wygląda następująco:



library IEEE;
use IEEE.STD_LOGIC_1164.ALL;

entity DMASM is
Port ( CLK : in STD_LOGIC;
RST : in STD_LOGIC;
bt1 : in STD_LOGIC;--btn zamykanie
bt2 : in STD_LOGIC;--btn otwieranie
krancEND : in STD_LOGIC;--active 0
krancPOC : in STD_LOGIC;--active 0
OVERRIDE : in STD_LOGIC;--active 0 (przeciazenie silnika)
DIRPIN : out STD_LOGIC;--Direction Motor ( 0 - FORWARD)
SLPPIN : out STD_LOGIC;--SLEEP PIN Mostek (0 - mostek zablokowany)
pb1_Zam : out STD_LOGIC;--gdy 0 zamykanie
pb2_Otw : out STD_LOGIC;--gdy 0 otwieranie
AWARIA : out STD_LOGIC;--gdy 0 awaria ukladu
LEDZAMKNIETE : out STD_LOGIC;--gdy 1 LED zamkniete
LEDOTWARTE : out STD_LOGIC;--gdy 1 LED otwarte
LEDPRACA : out STD_LOGIC;--gdy 1 LED Zamykanie/otwieranie
LEDAWARIA : out STD_LOGIC);--gdy 1 LED awaria;
end DMASM;

architecture BEHAVE of DMASM is
type STATE is (S0Otwarte, S1Zamykanie, S2Zamkniete, S3Otwieranie, S4Awaria);
signal CURRENT_STATE, NEXT_STATE: STATE;
begin
SEQ: process (RST, CLK)
begin
if (RST = '0') then
CURRENT_STATE <= S0Otwarte;
elsif (CLK' event and CLK = '1' ) then
CURRENT_STATE <= NEXT_STATE;
end if;
end process;

COMB: process (CURRENT_STATE, RST, bt1, bt2, krancEND, KrancPOC, OVERRIDE)
begin
DIRPin <= '0'; --Kierunek: FORWARD
SLPPin <= '0'; --Blokada mostka
pb1_Zam <= '1'; --klawisz nieaktywny
pb2_Otw <= '1'; --klawisz nieaktywny
AWARIA <= '1'; --klawisz nieaktywny
LEDZAMKNIETE <= '0';
LEDOTWARTE <= '1';
LEDPRACA <= '0';
LEDAWARIA <= '0';
NEXT_STATE <= S0Otwarte;
case CURRENT_STATE is
when S0Otwarte => DIRPin <= '0';
SLPPin <= '0';--mostek zablokowany
pb1_Zam <= '1';
pb2_Otw <= '1';--blokada ALL
AWARIA <= '1'; --klawisz nieaktywny
LEDZAMKNIETE <= '0';
LEDOTWARTE <= '1';
LEDPRACA <= '0';
LEDAWARIA <= '0';
if ((bt1 = '0') and (krancPOC = '0')) then
NEXT_STATE <= S1Zamykanie;
else
NEXT_STATE <= S0Otwarte;
end if;
when S1Zamykanie => DIRPin <= '0';
SLPPin <= '1';--FORWARD,MOSTEK OTWARTY
pb1_Zam <= '0';
pb2_Otw <= '1';
AWARIA <= '1'; --klawisz nieaktywny
LEDZAMKNIETE <= '0';
LEDOTWARTE <= '1';
LEDPRACA <= '1';
LEDAWARIA <= '0';
if (OVERRIDE = '0') then
NEXT_STATE <= S4Awaria;
elsif ((krancEND = '0') and (krancPOC = '1')) then
NEXT_STATE <= S2Zamkniete;
else
NEXT_STATE <= S1Zamykanie;
end if;
when S2Zamkniete => DIRPin <= '1';
SLPPin <= '0';
pb1_Zam <= '1';
pb2_Otw <= '1';--blokada ALL
AWARIA <= '1'; --klawisz nieaktywny
LEDZAMKNIETE <= '1';
LEDOTWARTE <= '0';
LEDPRACA <= '0';
LEDAWARIA <= '0';
if ((bt2 = '0') and (krancEND = '0'))then
NEXT_STATE <= S3Otwieranie;
else
NEXT_STATE <= S2Zamkniete;
end if;
when S3Otwieranie => DIRPin <= '1';
SLPPin <= '1';
pb1_Zam <= '1';
pb2_Otw <= '0';--BACKWARD,MOSTEK OTWARTY
AWARIA <= '1'; --klawisz nieaktywny
LEDZAMKNIETE <= '1';
LEDOTWARTE <= '0';
LEDPRACA <= '1';
LEDAWARIA <= '0';
if ((krancPOC = '0') and (krancEND = '1')) then
NEXT_STATE <= S0Otwarte;
elsif (OVERRIDE = '0') then
NEXT_STATE <= S4Awaria;
else
NEXT_STATE <= S3Otwieranie;
end if;
when S4Awaria => DIRPin <= '1';--stan S4Awaria
SLPPin <= '0';--blokada mostka
pb1_Zam <= '1';
pb2_Otw <= '1';--klawisze nieaktywne
AWARIA <= '0'; --sygnal aktywny
LEDZAMKNIETE <= '0';
LEDOTWARTE <= '0';
LEDPRACA <= '0';
LEDAWARIA <= '1';
when others => DIRPin <= '1';--stan S4Awaria
SLPPin <= '0';--blokada mostka
pb1_Zam <= '1';
pb2_Otw <= '1';--klawisze nieaktywne
AWARIA <= '0'; --sygnal aktywny
LEDZAMKNIETE <= '0';
LEDOTWARTE <= '0';
LEDPRACA <= '0';
LEDAWARIA <= '1'; end case;
end process;
end BEHAVE;



A plik "user constraints" (dla Elbert v2) tak:



NET "CLK" LOC = P129;
NET "CLK" PERIOD = 10MHz;

NET "RST" PULLUP;
NET "RST" LOC = "P76";

NET "bt1" PULLUP;
NET "bt1" LOC = "P80";

NET "bt2" PULLUP;
NET "bt2" LOC = "P79";

NET "krancEND" PULLUP;
NET "krancEND" LOC = "P58";

NET "krancPOC" PULLUP;
NET "krancPOC" LOC = "P59";

NET "OVERRIDE" PULLUP;
NET "OVERRIDE" LOC = "P78";

NET "DIRPIN" PULLUP;
NET "DIRPIN" LOC = "P19";

NET "SLPPIN" PULLUP;
NET "SLPPIN" LOC = "P21";

NET "pb1_Zam" PULLUP;
NET "pb1_Zam" LOC = "P18";

NET "pb2_Otw" PULLUP;
NET "pb2_Otw" LOC = "P20";

NET "AWARIA" PULLUP;
NET "AWARIA" LOC = "P15";

NET "LEDZAMKNIETE" LOC = "P55"; //LED1
NET "LEDOTWARTE" LOC = "P54"; //LED2
NET "LEDPRACA" LOC = "P51"; //LED3
NET "LEDAWARIA" LOC = "P46"; //LED8



Przejścia pomiędzy stanami działają OK poza wchodzeniem w stan "S4Awaria" gdy sygnał OVERRIDE = '0' (przy zamykaniu, czy otwieraniu - wtedy kiedy może wystąpić przeciążenie silnika np. z powodu zacięcia)

Zamiast w stan S4Awaria wchodzi mi zawsze w S0Otwarte.

Co może być tego przyczyną?

Poza tym w fazie syntezy (map) mam ostrzeżenia dla sygnałów wyjściowych:

PhysDesignRules:781 - PULLUP on an active net. PULLUP of comp SLPPIN is set but the tri state is not configured.

Rozumiem z tego, że wyjścia trójstanowe nie są prawidłowo ustawione - ale nie wiem jak usunąć te ostrzeżenia?

Pozdrawiam.Ten post został edytowany przez Autora dnia 17.09.17 o godzinie 15:58
Jakub Tyburski

Jakub Tyburski Asystent dydaktyczny
- Wojskowa Akademia
Techniczna w War...

Temat: Maszyna stanów - sterownik rolet

Nie zadziała tak jak planujesz, ponieważ warunek:
if ((krancPOC = '0') and (krancEND = '1')) then

przesłania warunek:
elsif (OVERRIDE = '0') then

a więc jeśli tamto będzie spełnione to co by nie była za wartość OVERRIDE to i tak ten pierwszy warunek będzie spełniany. Doszliśmy tym samym do odkrycia cechy instrukcji "if" w VHDL-u tj. jej priorytetowości i przesłaniania. Zamiast tego napisz tak:
   if (OVERRIDE = '0') then  -- gdy jest awaria
NEXT_STATE <= S4Awaria;
else -- gdy nie ma awarii
if ((krancPOC = '0') and (krancEND = '1')) then
NEXT_STATE <= S0Otwarte; -- przejdz do nastepnego stanu, gdy zaknczylo sie zwijanie
rolet
else
NEXT_STATE <= S3Otwieranie; -- pozostan w dotychczasowym stanie gdy nie zakonczylo
end if; sie zwijanie rolet
end if;

Co do warninga - to tylko mówi o tym, że choć próbujesz użyć PULL-UP-a to i tak na tym pinie nic to nie da, ponieważ nie ma tam bufora trójstanowego (spójrz na to pod jaki pin układu FPGA podłączone jest to wyjście - zgodnie ze schematem nie jest to tradycyjny pin I/O ogólnego użytku z buforem trójstanowym, a pin I/O bardziej przeznaczony pod wprowadzanie sygnałów zegarowych, w wyniku czego zrezygnowano z użycia bufora trójstanowego). Krótko mówiąc: nie zachodzi tu sytuacja "rozwarcia" na tym wyprowadzeniu, a tym samym nie ma potrzeby podciągania wyprowadzenia do zasilania, żeby przeciwdziałać zakłóceniom (to jest jak z przyciskiem, do którego zwykle robi się takie podciąganie, żeby właśnie w sytuacji gdy rozwiera obwód to żeby na wyjściu była stabilna, stała wartość, a nie przypadkowe zmiany napięcia (zapewne domyślasz się o co chodzi)). A skoro nie zachodzi sytuacja "rozwarcia"to i też nie ma sensu stosować podciągania bo zawsze będzie albo wartość równa 1 albo wartość równa 0 na tym wyprowadzeniu, a zakłócenia nie będą odgrywały roli (z racji tego, że sygnał przechodzi przez bufory, które regenerują sygnał). Tak więc wyrzuć tego PULL-UP-a i ci zniknie ostrzeżenie. Tyle :)Ten post został edytowany przez Autora dnia 17.09.17 o godzinie 21:02
Maciej G.

Maciej G. Projektant /
Programista, Famor
S.A.

Temat: Maszyna stanów - sterownik rolet

Jakub,

przepraszam za odpowiedź z opóźnieniem ale miałem mnóstwo pracy w ostatnie dni. Dziękuję za odpowiedz.

Pozdrawiam
Jakub Tyburski

Jakub Tyburski Asystent dydaktyczny
- Wojskowa Akademia
Techniczna w War...

Temat: Maszyna stanów - sterownik rolet

Drobiazg :)

Następna dyskusja:

Sterownik rolet na CPLD/FPGA




Wyślij zaproszenie do