S-funkcja w języku C
Transkrypt
S-funkcja w języku C
Programowanie Systemów Sterowania Dr inż. Dariusz Bismor Gliwice, 2007 S-funkcje aSystem-functions – reprezentacja bloku Simulinka w języku programowania aS-funkcje stanowią wygodny mechanizm poszerzający możliwości Simulinka aZastosowanie s-funkcji obejmuje: `tworzenie nowych bloków funkcyjnych `tworzenie bloków sterowników urządzeń `wykorzystywanie istniejącego kodu w C, C++, Fortranie, Adzie `opis systemu jako układu równań `przyspieszanie czasu symulacji `dodawanie animacji do symulacji Jak działają S-funkcje? Blok w Simulinku: wejście u wyjście y stany x y = f o (t , x, u ) x&c = f d (t , x, u ) xd ,k +1 = f u (t , x, u ) x = xd + xc równanie wyjścia równanie różniczkowe równanie różnicowe Jak działają S-funkcje? Fazy symulacji: Inicjalizacja Obliczenie wyjść Aktualizacja stanów dyskretnych Obliczenie pochodnych Obliczenie wyjść Obliczenie pochodnych Lokalizacja przejść przez zero Całkowanie Pętla symulacji Obliczenie czasu następnej próbki Jak działają S-funkcje? Metody wywołania S-funkcji: aInicjalizacja: `utworzenie struktury SimStruct `ustawienie ilości i wymiarów wejść i wyjść `ustawienie okresu próbkowania dla bloku `przydzielenie pamięci dla stanów i tablicy „sizes” Jak działają S-funkcje? Metody wywołania S-funkcji (c.d): aObliczenie następnego kroku próbkowania (dla bloków ze zmiennym próbkowaniem) aObliczenie wyjść aAktualizacja stanów dyskretnych aCałkowanie równań ciągłych (z małym krokiem całkowania) aZakończenie symulacji S-funkcja jako „M-file” Składnia i parametry funkcji: [sys, x0, str, ts] = funkcja(t, x, u, flaga, p1, p2, ...) Wektor Wektor stanu wejść Bieżący funkcji czas Warunki Nieużywane, Wektor początkowe czasów []Nazwa próbkowania Metoda Parametry wywołaniawywołania Zwracana wartość Metody wywołania: • flaga = 0 - inicjalizacja • flaga = 1 - całkowanie równań ciągłych • flaga = 2 - obliczanie równań dyskretnych • flaga = 3 - obliczenie wyjść s-funkcji • flaga = 4 - obliczenie czasu następnej próbki • flaga = 9 - zakończenie symulacji S-funkcja jako „M-file” Przykładowa s-funkcja – wzmacniacz: function [sys, x0, str, ts] = pomnoz(t, x, u, flaga, p1) switch flaga, case 0 [sys, x0, str, ts] = inicjalizacja; case 3 sys = oblicz_wyjscie(t, x, u, p1 ); case {1, 2, 4, 9} sys = []; otherwise error([‘Nieobsługiwana flaga ’, num2str(flaga)]); end S-funkcja jako „M-file” Przykładowa s-funkcja – wzmacniacz: function [sys, x0, str, ts] = inicjalizacja mysizes = simsizes; mysizes.NumContStates = 0; mysizes.NumDiscStates = 0; mysizes.NumInputs = 1; mysizes.NumOutputs = 1; mysizes.DirFeedthrough = 1; mysizes.NumSampleTimes = 1; sys = simsizes(mysizes) x0 = []; str = []; ts = [-1, 0] %Dziedziczony czas próbkowania S-funkcja jako „M-file” Przykładowa s-funkcja – wzmacniacz: function sys = oblicz_wyjscie(t, x, u, mnoznik) sys = mnoznik.*u; S-funkcja dla obiektów ciągłych function [sys,x0,str,ts] = csfunc(t,x,u,flag) % Macierze modelu układu ciągłego w postaci równań stanu: A=[-0.09 -0.01; 1 0]; B=[ 1 -7; 0 -2]; C=[ 0 2; 1 -5]; D=[-3 0; 1 0]; % Obsługa flagi. switch flag, case 0 % Initializacja [sys,x0,str,ts]=mdlInitializeSizes(A,B,C,D); case 1 % Obliczenie pochodnych sys = mdlDerivatives(t,x,u,A,B,C,D); case 3 % Obliczenie wyjść sys = mdlOutputs(t,x,u,A,B,C,D); case { 2, 4, 9 } % Nieużywane flagi sys = []; otherwise % Obsługa błędów error(['Unhandled flag = ',num2str(flag)]); end S-funkcja dla obiektów ciągłych function [sys,x0,str,ts] = mdlInitializeSizes(A,B,C,D) sizes = simsizes; sizes.NumContStates = 2; sizes.NumDiscStates = 0; sizes.NumOutputs = 2; sizes.NumInputs = 2; sizes.DirFeedthrough = 1; % Ponieważ macierz D niezerowa sizes.NumSampleTimes = 1; sys = simsizes(sizes); % Zerowe warunki początkowe. x0 = zeros(2,1); str = []; % Inicjalizacja macierzy czasów próbkowania bloku [tp, off] % Dla układu ciągłego należy użyć wartości tp=0 i off=0 ts = [0 0]; S-funkcja dla obiektów ciągłych function sys = mdlDerivatives(t,x,u,A,B,C,D) % sys = A*x + B*u; function sys = mdlOutputs(t,x,u,A,B,C,D) % sys = C*x + D*u; S-funkcja dla obiektów dyskretnych function [sys,x0,str,ts] = dsfunc(t,x,u,flag) % Macierze modelu układu dyskretnego w postaci równań stanu A=[–1.3839 –0.5097; 1.0000 0]; B=[–2.5559 0; 0 4.2382]; C=[ 0 2.0761; 0 7.7891]; D=[ –0.8141 –2.9334; 1.2426 0]; switch flag, case 0 % Initializacja sys = mdlInitializeSizes(A,B,C,D); case 2 % Aktualizacja stanów dyskretnych sys = mdlUpdate(t,x,u,A,B,C,D); case 3 % Obliczanie wyjść sys = mdlOutputs(t,x,u,A,B,C,D); case {1, 4, 9} % Nieużywane flagi sys = []; otherwise % Obsługa błędów error(['unhandled flag = ',num2str(flag)]); end S-funkcja dla obiektów dyskretnych function [sys,x0,str,ts] = mdlInitializeSizes(A,B,C,D) sizes = simsizes; sizes.NumContStates = 0; sizes.NumDiscStates = 2; sizes.NumOutputs = 2; sizes.NumInputs = 2; sizes.DirFeedthrough = 1; sizes.NumSampleTimes = 1; sys = simsizes(sizes); x0 = ones(2,1); str = []; ts = [1 0]; % Ponieważ macierz D niezerowa % Zerowe warunki macierzowe. % Okres próbkowania: [tp, off] S-funkcja dla obiektów dyskretnych function sys = mdlUpdates(t,x,u,A,B,C,D) % sys = A*x + B*u; function sys = mdlOutputs(t,x,u,A,B,C,D) % sys = C*x + D*u; S-funkcja dla obiektów mieszanych function [sys,x0,str,ts] = mixedm(t,x,u,flag) % Element całkujący (1/s) połączony szeregowo z opóźnieniem jednostkowym (1/z). % Okres próbkowania i offset dla części dyskretnej: dperiod = 1; doffset = 0; switch flag, case 0 % Initializacja [sys,x0,str,ts] = mdlInitializeSizes(dperiod,doffset); case 1 % Obliczanie pochodnych sys = mdlDerivatives(t,x,u); case 2 % Aktualizacja stanów dyskretnych sys = mdlUpdate(t,x,u,dperiod,doffset); case 3 % Obliczanie wyjść sys = mdlOutputs(t,x,u,doffset,dperiod); case {4, 9} % Nieużywane flagi sys = []; otherwise % Obsługa błędów error(['unhandled flag = ',num2str(flag)]); end S-funkcja dla obiektów mieszanych function [sys,x0,str,ts] = mdlInitializeSizes(dperiod,doffset) sizes = simsizes; sizes.NumContStates = 1; sizes.NumDiscStates = 1; sizes.NumOutputs = 1; sizes.NumInputs = 1; sizes.DirFeedthrough = 0; sizes.NumSampleTimes = 2; sys = simsizes(sizes); x0 = ones(2,1); str = []; ts = [0, 0; dperiod, doffset]; % DWA czasy próbkowania S-funkcja dla obiektów mieszanych function sys = mdlDerivatives(t,x,u) sys = u; function sys = mdlUpdate(t,x,u,dperiod,doffset) % Funkcja aktualizująca stany dyskretne. Poniższy kod sprawdza, czy czas % "t" jest wielokrotnością dyskretnego okresu próbkowania, z dokładnością 1e-8. % Jeżeli tak jest, bieżąca wartość stanu ciągłego (x(1)) jest zwracana jako nowa % wartość stanu dyskretnego if abs(round((t-doffset)/dperiod)-(t-doffset)/dperiod) < 1e-8 sys = x(1); % Wielokrotność dyskr. okresu próbkowania % Przepisanie stanu ciągłego x(1) do stanu dyskretnego else sys = []; % Czas "t" nie jest wielokrotnością okresu próbkowania % Zwracana pusta macierz symbolizuje brak zmian % stanu dyskretnego end S-funkcja dla obiektów mieszanych function sys = mdlOutputs(t,x,u,doffset,dperiod) % Funkcja aktualizująca wyjście. Poniższy kod sprawdza, czy czas % "t" jest wielokrotnością dyskretnego okresu próbkowania, z dokładnością 1e-8 % (mdlOutputs jest także wywoływana w małym kroku symulacji) % Jeżeli tak jest, bieżąca wartość stanu dyskretnego (x(2)) jest przepisywana na % wyjście. W przeciwnym przypadku zwraca się pustą macierz symbolizującą % brak zmian na wyjściu. if abs(round((t-doffset)/dperiod)-(t-doffset)/dperiod) < 1e-8 sys = x(2); else sys = []; end S-funkcja ze zmiennym okresem próbkowania function [sys,x0,str,ts] = vsfunc(t,x,u,flag) % Przykład s-funkcji ze zmiennym czasem próbkowania. Realizowana funkcja to zmienne % opóźnienie - opóźnienie sygnału na pierwszym wejściu zależy od wartości sygnału na % drugim wejściu: dt = u(2); y(t+dt) = u(t) % switch flag, case 0 [sys,x0,str,ts] = mdlInitializeSizes; % Inicjalizacja case 2 sys = mdlUpdate(t,x,u); % Aktualiz. stanów dyskretnych case 3 sys = mdlOutputs(t,x,u); % Obliczanie wyjść case 4 sys = mdlGetTimeOfNextVarHit(t,x,u); % Obliczanie czasu nast. próbki case { 1, 9 } sys = []; % Pozostałe flagi otherwise error(['Unhandled flag = ',num2str(flag)]); % Ewentualny błąd end S-funkcja ze zmiennym okresem próbkowania function [sys,x0,str,ts] = mdlInitializeSizes % sizes = simsizes; sizes.NumContStates = 0; sizes.NumDiscStates = 1; sizes.NumOutputs = 1; sizes.NumInputs = 2; sizes.DirFeedthrough = 1; % flag=4 - wymaganie bezpośredniego sprzężenia sizes.NumSampleTimes = 1; % wejścia z wyjściem sys = simsizes(sizes); x0 = [0]; str = []; ts = [–2 0]; % Zerowe warunki początkowe % Zwracana macierz pusta (niewykorzystywany parametr) % Kod dla zmiennego czasu próbkowania S-funkcja ze zmiennym okresem próbkowania function sys = mdlUpdate(t,x,u) % Funkcja do aktualizacji stanów dyskretnych sys = u(1); function sys = mdlOutputs(t,x,u) % Funkcja do aktualizacji wyjść sys = x(1); function sys = mdlGetTimeOfNextVarHit(t,x,u) % Funkcja obliczająca czas następnego próbkowania bloku % Uwaga! Jest to czas bezwzględny, a nie przyrost czasu sys = t + u(2); Wersja 2 interfejsu s-funkcji aObecnie istnieje druga wersja interfejsu dla s-funkcji pisanych w języku Matlaba aWersja ta nie posiada ograniczeń wersji pierwszej, jak jedno wejście i jedno wyjście bloku aZapis kodu s-funkcji w wersji 2 interfejsu jest bardzo podobny do kodu w języku C aKod s-funkcji w wersji 2 interfejsu traci podstawową zaletę s-funkcji w wersji pierwszej prostotę S-funkcje w języku C aNajprostszym sposobem zapisania kodu s-funkcji w języku C jest wykorzystanie dostarczanego z Simulinkiem "kreatora" sfunkcji aS-funkcję można także zapisać samodzielnie i skompilować przy użyciu dostarczonego skryptu "mex" aTrzecim sposobem jest wykorzystanie narzędzi do dziedziczenia kodu aKażdy ze sposobów wymaga posiadania zewnętrznych kompilatorów S-funkcje w języku C a Interakcja "silnika" Simulinka z s-funkcją napisaną w języku C polega na wywoływaniu odpowiednich funkcji na każdym z etapów symulacji a Funkcje te nazywa się metodami – odpowiadają one funkcjom pomocniczym dla poszczególnych faz symulacji a Liczba metod, za pomocą których można sterować przebiegiem wykonania bloku, jest znacznie większa od faz symulacji s-funkcji w języku Matlaba a Każda s-funkcja musi co najmniej implementować metody dla fazy inicjalizacji rozmiarów, inicjalizacji czasów próbkowania, obliczania wyjść i zakończenia symulacji Wywołania metod dla języka C „Silnik” Simulinka mdlInitializeSizes mdlCheckParameters mdlSetInputPortFrameData mdlSetInputPortWidth/mdlSetOutputPortWidth mdlSetInputPortDimensionInfo/mdlSetOutputPortDimensionInfo mdlSetInputPortSampleTime/mdlSetOutputPortSampleTime mdlInitializeSampleTime Wywołania metod dla języka C „Silnik” Simulinka mdlSetInputPortDataType/mdlSetOutputPortDataType mdlSetDefaultPortDataTypes mdlSetInputPortComplexSignal/mdlSetOutputPortComplexSignal mdlSetDefaultPortComplexSignal mdlSetWorkWidths mdlStart mdlCheckParameters mdlProcessParameters mdlInitializeConditions mdlOutputs Wywołania metod dla języka C Duży krok symulacji mdlProcessParameters mdlGetTimeOfNextVarHit mdlInitializeConditions mdlOutputs mdlUpdate Wywołania metod dla języka C Całkowanie Mały krok symulacji mdlCheckParameters mdlDerivatives mdlOutputs mdlDerivatives mdlOutputs Detekcja przejść przez 0 mdlZeroCrossings mdlTerminate Struktura SimStruct a Wszystkie metody przyjmują jako parametr wskaźnik do struktury typu SimStruct a Struktura ta przechowuje informacje konfiguracyjne bloku a Struktura SimStruct jest zdefiniowana w pliku nagłówkowym "simstruct.h" znajdującym się w podkatalogu simulink/include głównego katalogu Matlaba a Plik "simstruct.h" zawiera także szereg makr do manipulacji strukturą SimStruct – makra te należy traktować jako interfejs struktury a Obecnie struktura zawiera 17 składowych takich, jak nazwa bloku, ścieżka do pliku bloku, stan błędu bloku czy wskaźniki do podsystemów bloku S-funkcje w języku C #define S_FUNCTION_NAME nazwa_funkcji #define S_FUNCTION_LEVEL 2 #include ”simstruc.h” static void mdlInitializeSizes(SimStruct *S){} static void mdlInitializeSampleTime(SimStruct *S){} static void mdlOutputs(SimStruct *S, int_T tid){} static void mdlTerminate(SimStruct *S){} /* Kod dodatkowych funkcji */ #ifdef MATLAB_MEX_FILE #include "simulink.c" /* interfejs dla plików MEX */ #else #include "cg_sfun.h" /* Funkcje dla generacji kodu */ #endif S-funkcja w języku C - przykład #define S_FUNCTION_NAME cpomnoz #define S_FUNCTION_LEVEL 2 #include "simstruc.h" /* Definicja dla łatwiejszego dostępu do parametru s-funkcji */ #define PARAM1(S) ssGetSFcnParam(S,0) /* Poniższe umożliwia wyłączenie kontroli poprawności zakresu parametru - w tym celu należy zmienić na #undef */ #define MDL_CHECK_PARAMETERS S-funkcja w języku C - przykład #if defined(MDL_CHECK_PARAMETERS) && defined(MATLAB_MEX_FILE) static void mdlCheckParameters( SimStruct *S ){ /* Sprawdzenie, czy parametr jest skalarny (wzmocnienie) */ if (mxGetNumberOfElements(PARAM1(S)) != 1) { ssSetErrorStatus(S,"Parametr tej S-funkcji ma być skalarem!"); return; } /* Wymuszenie dodatniego wzmocnienia */ if (mxGetPr(PARAM1(S))[0] < 0) { ssSetErrorStatus(S, "Parametr tej S-funkcji ma być nieujemny!"); return; } } #endif /* MDL_CHECK_PARAMETERS */ S-funkcja w języku C - przykład static void mdlInitializeSizes( SimStruct *S ){ ssSetNumSFcnParams(S, 1); if (ssGetNumSFcnParams(S) == ssGetSFcnParamsCount(S)) { mdlCheckParameters(S); if( ssGetErrorStatus(S) != NULL ){ return; /* Simulink ostrzeże o problemie z parametrami */ } } else { return; } if ( !ssSetNumInputPorts(S, 1) ) return; ssSetInputPortWidth( S, 0, DYNAMICALLY_SIZED ); ssSetInputPortDirectFeedThrough( S, 0, 1 ); S-funkcja w języku C - przykład if (!ssSetNumOutputPorts(S,1)) return; /* /* } ssSetOutputPortWidth( S, 0, DYNAMICALLY_SIZED ); ssSetNumSampleTimes( S, 1); Poniższą opcję włącza się po upewnieniu się, że kod nie generuje sytuacji wyjątkowych w rozumieniu Matlaba */ ssSetOptions(S, SS_OPTION_EXCEPTION_FREE_CODE); */ static void mdlInitializeSampleTimes( SimStruct *S ){ ssSetSampleTime( S, 0, INHERITED_SAMPLE_TIME ); ssSetOffsetTime( S, 0, 0.0 ); } S-funkcja w języku C - przykład static void mdlOutputs(SimStruct *S, int_T tid){ int_T i; real_T *mnoznik = mxGetPr( PARAM1(S) ); InputRealPtrsType uPtrs = ssGetInputPortRealSignalPtrs(S,0); real_T *y = ssGetOutputPortRealSignal(S,0); int_T width = ssGetOutputPortWidth(S,0); for (i=0; i<width; i++) { *y++ = (*mnoznik) *(*uPtrs[i]); } } static void mdlTerminate(SimStruct *S){} S-funkcja w języku C - przykład #ifdef MATLAB_MEX_FILE #include “simulink.c” #else #include “cg_sfun.h” #endif Zmienne bloku Simulinka Wejścia zewnętrzne (porty) Blok wejściowowyjściowy Wyjścia zewnętrzne (porty) Jednym z zadań metody mdlInitializeSizes jest ustawienie Blokzmiennych funkcyjny wykorzystywanych rozmiarów wszystkich Masa w Simulinku przed dany blok Simulinka Stany Parametry Dodatkowe zmienne wektorowe Tworzenie wejść bloku Składnia: boolean_T ssSetNumInputPorts( SimStruct *S, int_T ile); gdzie: S ile - wskaźnik do struktury SimSizes, - żądana liczba wejść bloku. Zazwyczaj: if( !ssSetNumInputPorts(S, ile) ) return; Wejścia bloku a Po zdefiniowaniu liczby wejść dla każdego wejścia należy określić: `wymiar wejścia lub dynamiczny dobór wymiaru, `właściwość „rozciągania” wielkości skalarnych (ssSetOption), `czy port uczestniczy bezpośrednio w obliczaniu wyjścia lub następnego okresu próbkowania za pomocą makra ssSetInputPortDirectFeedThrough `typ danych portu, jeśli jest to inny typ niż domyślny (double) `typ numeryki portu, jeśli port ma przyjmować dane zespolone Wejścia bloku - rozmiar a Dla portu jednowymiarowego o długości Dlugosc: void ssSetInputPortVectorDimension( SimStruct *S, int_T NumPortu, int_T Dlugosc); (lub ssSetInputPortWidth()) a Dla portu, którego sygnałem jest macierz o rozm. M x N: void ssSetInputPortMatrixDimensions( SimStruct *S, int_T NumPortu, int_T M, int_T N ); a W innym przypadku: void ssSetInputPortDimensionInfo( SimStruct *S, int_T NumPortu, DimsInfo_T *dimsI ); Wejścia bloku - rozmiar dynamiczny a Używany w przypadku, gdy rozmiar portu ma zależeć od rozmiaru sygnału podłączonego do portu a Można wyróżnić trzy przypadki: `jeśli port ma akceptować sygnały dowolnych rozmiarów, należy użyć funkcji: ssSetInputPortDimensionInfo( S, NumPortu, DYNAMIC_DIMENSION); `jeśli port ma akceptować jedynie wektory, lecz o dowolnym rozmiarze, należy użyć funkcji: ssSetInputPortWidth( S, NumPortu, DYNAMICALLY_SIZED); `jeśli port ma akceptować macierz, lecz ilość jej wierszy lub kolumn ma być dowolna, należy użyć funkcji ssSetInputPortMatrixDimensions( S, NumPortu, M, N ); gdzie M i/lub N może być zapisane jako DYNAMICALLY_SIZED Wejścia bloku - rozmiar dynamiczny aNależy zapewnić obsługę metody mdlSetInputPortDimensionInfo(), która dostosowuje rozmiar portu do sygnału do niego dołączonego aMożna także zapewnić obsługę metody mdlSetDefaultPortDimensionInfo(), która dobiera rozmiar portu, gdy nie można określić rozmiaru dołączonego do niego sygnału (np. niepodłączony port) Wejścia bloku – właściwość "rozszerzania" aPrzykład: blok sumatora z jednym sygnałem wektorowym a drugim skalarnym aUłatwienie: ustawienie, w mdlInitializeSizes(), opcji SS_OPTION_ALLOW_INPUT_SCALAR_EXPANSION aS-funkcja musi definiować wejścia jako DYNAMICALLY_SIZED aS-funkcja nie może implementować żadnej z metod mdlSetInputPortWidth(), mdlSetOutputPortWidth(), mdlSetInputPortDimesionInfo(), mdlSetOutputPortDimensionInfo(), mdlSetDefaultPortDimensionInfo() Wejścia bloku - bezpośredni wpływ na wyjście aFunkcja: ssSetInputPortDirectFeedThrough( SimStruct *S, int_T NumPortu, int_T Flaga); umożliwia powiadomienie Simulinka, że sygnał z danego portu jest wykorzystywany w metodach mdlOutput lub mdlGetTimeOfNextVarHit (wartość Flaga = 1) Wejścia bloku - typ danych aFunkcja: ssSetInputPortDataType( SimStruct S, int_T NumPortu, DTypeID Id ); umożliwia określenie innego niż domyślny typu danych akceptowanych przez port aOkreślenie typu jako DYNAMICALLY_TYPED umożliwia dobór typu na podstawie typu sygnału podłączonego do portu aWówczas powinno się zdefiniować metody mdlSetInputPortDataType() i mdlSetDefaultPortDataTypes() Wejścia bloku - dane zespolone aFunkcja: ssSetInputPortComplexSignal( SimStruct S, int_T NumPortu, CSignal_T sig ); umożliwia dobór numeryki portu (rzeczywista lub zespolona) aZmienna sig może przyjmować wartości: COMPLEX_NO dla danych rzeczywistych, COMPLEX_YES dla danych zespolonych i COMPLEX_INHERITED dla typu danych zależnego od typu sygnału podłączonego do portu Wejścia bloku - dane zespolone aW przypadku użycia typu COMPLEX_INHERITED należy zdefiniować metody mdlSetInputPortComplexSignal() i ewentualnie mdlSetDefaultPortComplexSignal() Dostęp do wejść przez wskaźniki W ogólnym przypadku przestrzeń wejściowo-wyjściowa bloku nie jest ciągła: Dostęp do wejść przez wskaźniki Składnia: InputRealPtrsType uPtrs = ssGetInputPortRealSignalPtrs( S, portIndex); gdzie: portIndex S uPtrs - numer wejścia układu, począwszy od 0, - wskaźnik do struktury SimSizes, - tablica wskaźników do sygnałów wejściowych. Odwołanie do konkretnego elementu: u1 = *uPtrs[ NrElementu ]; uPtrs to wskaźnik do tablicy! Dostęp do wejść przez wskaźniki przykład Wejście1 Wejście2 Blok funkcyjny Dostęp do elementów Wejścia1: InputRealPtrsType wskWe1 = ssGetInputPortRealSignalPtrs( S, 0 ); Dostęp do elementów Wejścia2: InputRealPtrsType wskWe2 = ssGetInputPortRealSignalPtrs( S, 1 ); Rozmiar tablicy Wejścia1: int_T rozmWe1 = ssGetInputPortWidth( S, 0 ); Testowanie poprawności obsługi wejść aWiele błędów programowych dotyczących obsługi wejść nie ujawnia się, gdy do wejścia podłączony jest sygnał skalarny aDlatego zaleca się testowanie poprawności obsługi wejść według następującego schematu: Dostęp do wejść przestrzeni ciągłej Wymuszenie ciągłości przestrzeni wejściowej – w metodzie mdlInitializeSizes(): void ssSetInputPortRequiredContiguous( S, portIndex, flaga ) gdzie: portIndex S flaga - numer wejścia układu, począwszy od 0, - wskaźnik do struktury SimSizes, - równa TRUE gdy wymagamy ciągłości. Odwołanie do wejścia: const real_T *u = ssGetInputPortRealSignal( S, portIndex); Odwołanie do konkretnego elementu: u1 = u[0]; Wymuszenie ciągłości wpływa na efektywność! Tworzenie wyjść bloku Składnia: boolean_T ssSetNumOutputPorts( SimStruct *S, int_T ile); gdzie: S ile - wskaźnik do struktury SimSizes, - żądana liczba wyjść bloku. Zazwyczaj: if( !ssSetNumOutputPorts(S, ile) ) return; Wyjścia bloku aPo określeniu liczby wyjść dla każdego portu wyjściowego należy określić: `wymiar wyjścia lub dynamiczny dobór wymiaru za pomocą funkcji ssSetOutputPortDimensionInfo(), ssSetOutputPortMatrixDimensions(), ssSetOutputPortVectorDimensions(), ssSetOutputPortWidth(); `w przypadku wyjść z dynamicznym rozmiarem należy zdefiniować metody mdlSetOutputPortDimensionInfo() i ewentualnie mdlSetDefaultPortDimensionInfo() Wyjścia bloku `typ danych portu za pomocą funkcji ssSetOutputPortDataType(); `jeśli typ jest zależny od połączeń bloku (DYNAMICALLY_TYPED), powinno się zdefiniować metodę mdlSetOutputPortDataType() i ewentualnie mdlSetDefaultPortDataTypes() `typ numeryki portu za pomocą funkcji ssSetOutputPortComplexSignal(); `jeśli typ jest zależny od połączeń bloku (COMPLEX_INHERITED), to należy zdefiniować metodę mdlSetOutputPortComplexSignal() i ewentualnie mdlSetDefaultPortComplexSignal() Dostęp do wyjść przez wskaźniki Składnia: real_T *y = ssGetOutputPortRealSignal( S, portIndex); gdzie: portIndex S y - numer wyjścia układu, począwszy od 0, - wskaźnik do struktury SimSizes, - wskaźnik do sygnałów wyjściowych. Odwołanie do konkretnego elementu: y[0] = 0; Zazwyczaj rozmiar portu wyjściowego jest powiązany z rozmiarem portu wejściowego Istnieją także makra ssGetOutputPortNumDimensions() i ssGetOutputPortDimensions() umożliwiające odczyt rozmiaru wyjścia Przykład kodu Poniższy kod przepisuje dane z portu wejściowego o numerze NrPortuWe do portu wyjściowego o numerze NrPortuWy int_T PozWektora; int_T RozmiarWe = ssGetInputPortWidth( S, NrPortuWe ); InputRealPtrsType WskWe = ssGetInputPortRealSignalPtrs( S, NrPortuWe ); real_T *y = ssGetOutputPortSignal( S, NrPortuWy ); for ( PozWektora=0; PozWektora < RozmiarWe; PozWektora++ ) { y[ PozWektora ] = *WskWe[ PozWektora ]; } Tworzenie stanów bloku Składnia: ssSetNumContStates( SimStruct *S, int_T ile); ssSetNumDiscStates( SimStruct *S, int_T ile); gdzie: S ile - wskaźnik do struktury SimSizes, - żądana liczba stanów bloku (lub DYNAMICALLY_SIZED) Jeżeli w metodzie mdlInitializeSizes() użyto DYNAMICALLY_SIZED, makra należy jeszcze raz wywołać w metodzie mdlSetWorkWidths()! Inicjalizacja stanów bloku W metodzie mdlInitializeConditions() lub mdlStart(): real_T *x0 = ssGetContStates(S); int_T lx = ssGetNumContStates(S); for(; lx > 0; lx--){ *x0++ = 0.0; } Jest to jedyne miejsce, gdzie stany zmienia się "ręcznie"! Uaktualnianie stanów bloku a Stany ciągłe uaktualnia się w metodzie mdlDerivatives() a Programista powinien obliczyć wektor pochodnych (na podstawie prawej strony równania stanu) i zapisać w miejscu wskazywanym przez wynik wywołania: real_T *dx = ssGetdX(S); a Stany dyskretne uaktualnia się w metodzie mdlUpdate() a Dostęp do bieżącego wektora stanów dyskretnym można uzyskać dzięki wywołaniu: real_T *x = ssGetDiscStates(S); a Zazwyczaj aktualizacja stanów wymaga alokacji dodatkowego wektora buforowego Parametry okna dialogowego a Należy zdecydować, w jakim porządku parametry będą wpisywane a W metodzie mdlInitializeSizes() należy wywołać makro ssSetNumSFcnParams() w celu ustawienia ilości obsługiwanych parametrów a Ustawioną liczbę parametrów zwraca makro: int_T ssGetNumSFcnParams(SimStruct* S); a Aktualną liczbę parametrów zwraca makro: int_T ssGetNumSFcnParamsCount(SimStruct *S); a Jeżeli programuje się metodę mdlCheckParams(), należy ją wywołać z metody mdlInitializeSizes() a Dostęp do parametrów następuje przez makro const mxArray *ssGetSFcnParams( S, index ) Parametry okna dialogowego a Tablica Matlaba mxArray może przechowywać dana różnych typów a W celu sprawdzenia typu danych w tablicy należy wywołać: DTypeID ssGetDTypeIdFromMxArray(const mxArray *m); a Inne funkcje przydatne do sprawdzania typu to mxIsComplex(), mxIsChar(), mxIsStruct() Parametry okna dialogowego Wartość Typ danych Matlaba Typ danych C SS_DOUBLE mxDOUBLE_CLASS real_T SS_SINGLE mxSINGLE_CLASS real32_T SS_INT8 mxINT8_CLASS int8_T SS_UINT8 mxUINT8_CLASS uint8_T SS_INT16 mxINT16_CLASS int16_T SS_UINT16 mxUINT16_CLASS uint16_T SS_INT32 mxINT32_CLASS int32_T SS_UINT32 mxUINT32_CLASS uint32_T SS_BOOLEAN mxUINT8_CLASS boolean_T INVALID_DTYPE_ID Parametry dostrajalne... ...to parametry, które można zmieniać podczas symulacji Dla każdego z parametrów dostrajalnych należy wywołać makro void ssSetFcnParamTunable( SimStruct *S, int_T Param, int_T Mode ) gdzie: S Param Mode - wskaźnik do struktury SimSizes, - numer parametru, - SS_PRM_TUNABLE (dostrajalny), SS_PRM_NOT_TUNABLE (nie dostrajalny), SS_PRM_SIM_ONLY_TUNABLE (dostrajalny w symul.) Uwaga! Po wykryciu zmiany parametru Simulink wywoła metodę mdlCheckParameters() Metoda mdlProcessParameters() pozwoli przeprowadzić konieczne po dostrojeniu parametru zmiany Parametry wykonania (run-time) a Stanowią wewnętrzną reprezentację parametrów dialogowych a Każdy parametr wykonania może odpowiadać jednemu lub większej liczbie parametrów dialogowych a Parametry wykonania mogą się różnić od parametrów dialogowych nie tylko wartością, ale i typem a Typowe zastosowania: `przeliczania, potencjalnie wielu, parametrów dialogowych na jeden parametr konfiguracyjny `konwersji skali bądź typu `w połączeniu z RTW Parametry wykonania (run-time) Aby zarejestrować parametr wykonania dla każdego parametru dialogowego, należy w metodzie mdlSetWorkWidths wykonać: void ssRegAllTunableParamsAsRunTimeParams( S, names) gdzie: S names - wskaźnik do struktury SimSizes, - wskaźnik do tablicy nazw parametrów dla RTW (const char_T *names[]) Simulink rozróżnia jedynie pierwsze cztery znaki każdej nazwy (muszą być niepowtarzalne)! Parametry wykonania (run-time) Aby zarejestrować pojedynczy parametr wykonania lub kilka takich parametrów należy, w metodzie mdlSetWorkWidths() wykonać: ssSetNumRunTimeParams( SimStruct *S, int_T num) gdzie: S num - wskaźnik do struktury SimSizes, - liczba parametrów wykonania Dalsze czynności zależą od typu relacji pomiędzy parametrem wykonania a parametrem dialogowym Parametry wykonania (run-time) Aby powiązać parametr wykonania z jednym i tylko jednym parametrem dialogowym, należy wykonać: void ssRegDlgParamAsRunTimeParam(SimStruct *S, int_T nrD, int_T nrW, const char_T *nazwa, DTypeId nazwaT); gdzie: S – wskaźnik do struktury SimStruct bloku, nrD – numer parametru dialogowego nrW – numer parametru wykonania nazwa – symboliczna nazwa parametru wykonania nazwaT – nazwa typu parametru wykonania Parametry mogą różnić się typem! Parametry wykonania (run-time) Aby powiązać parametr wykonania z kilkoma parametrami dialogowymi, należy wykonać: void ssSetRunTimeParamInfo(SimStruct *S, int_T nrW, ssParamRec *info); gdzie: S nrW info – wskaźnik do struktury SimStruct bloku, – numer parametru wykonania – struktura określająca powiązania pomiędzy parametrami Parametry mogą różnić się typem! Parametry wykonania (run-time) Aby uaktualnić parametry wykonania należy, w metodzie mdlProcessParametres(), wywołać jedną z funkcji: ssUpdateAllTunableParamsAsRunTimeParams(SimStruct *S) dla uaktualnienia wszystkich parametrów, ssUpdateRunTimeParamData( SimStruct *S, int_T nrW, void *dana); dla uaktualnienia pojedynczego parametru, utworzonego przez wywołanie ssRegDlgParamAsRunTimeParam() ("dana" to wskaźnik do nowej wartości parametru), ssUpdateDlgParamAsRunTimeParam(SimStruct *S, int_T nrW) dla uaktualnienia parametru utworzonego przez wywołanie ssSetRunTimeParamInfo(). Parametry wykonania (run-time) Aby posłużyć się wartością parametru wykonania należy wywołać makro: ssParamRec * ssGetRunTimeParamInfo(SimStruct *S, int_T nrW); którego drugim argumentem jest numer żądanego parametru wykonania. Zwrócona przez makro struktura posiada pole "data", którego jest wskaźnikiem do żądanej wartości parametru wykonania. Wskaźnik ten należy wcześniej skonwertować do żądanego typu, np.: real_T par1 = *( (real_T *)((ssGetRunTimeParamInfo(S,0)->data)); Czasy próbkowania a Czas próbkowania ustalany jest zazwyczaj w metodzie mdlInitializeSizes, najczęściej jako jeden dla całego bloku funkcyjnego (tzw. block-based sample times) a W takim rozwiązaniu należy wywołać funkcję void ssSetNumSampleTimes( SimStruct *S, int_T Num); gdzie: S - wskaźnik do struktury SimStruct bloku Num - ilość czasów próbkowania, > 0 a Simulink wywoła wtedy metodę mdlInitializeSampleTimes, w której czasy próbkowania powinny zostać ustawione Czasy próbkowania a Czasy próbkowania wpisuje się jako pary [czas_próbkowania, offset] za pomocą funkcji: ssSetSampleTime( SimStruct *S, st_Index, time_T Czas ); ssSetOffsetTime( SimStruct *S, st_Index, time_T Offset ); gdzie: S - wskaźnik do struktury SimStruct bloku, st_Index - indeks pary, dla której dokonuje się zmian, począwszy od 0, Czas, Offset - czas i offset dla danej pary Czasy próbkowania a Możliwe są następujące kombinacje: `[CONTINUOUS_SAMPLE_TIME, 0.0] - funkcja ciągła, która dokonuje zmian w małych krokach symulacji, `[CONTINUOUS_SAMPLE_TIME, FIXED_IN_MINOR_STEP_OFFSET] - funkcja ciągła, która nie dokonuje zmian wyjść w małym kroku symulacji, `[Tp, To ] - funkcja dyskretna, z określonym okresem próbkowania, Tp > 0 oraz 0 ≤ To ≤ Tp, `[VARIABLE_SAMPLE_TIME, 0.0] - funkcja, której czas próbkowania jest zmienny; funkcja taka powinna zdefiniować metodę mdlGetTimeOfNexVarHit(), Czasy próbkowania `[INHERITED_SAMPLE_TIME, 0.0] - funkcja, której czas próbkowania zależy od sygnału podłączonego do bloku `[INHERITED_SAMPLE_TIME, FIXED_IN_MINOR_STEP_OFFSET] funkcja, której czas próbkowania zależy od sygnału podłączonego do bloku, lecz funkcja nie zmienia swoich wartości wyjściowych podczas małego kroku symulacji a Dla sprawdzenia, czy bieżąca chwila jest chwilą dyskretnego próbkowania, bądź chwilą pracy małej pętli symulacji, należy wykorzystać makra: ssIsSampleHit( SimStructure *S, st_Index, tid ); ssIsContinuousTask( SimStructure *S, st_Index, tid ); Czasy próbkowania a Można także używać innych czasów próbkowania dla różnych portów - wówczas należy w metodzie mdlInitializeSizes() wykonać funkcję ssSetNumSampleTimes(S, PORT_BASED_SAMPLE_TIMES), a następnie: ssSetInputPortSampleTime( S, NumPortu, Czas1 ); ssSetInputPortOffsetTime( S, NumPortu, Offset1 ); ssSetOutputPortSampleTime( S, NumPortu, Czas2 ); ssSetOutputPortOffsetTime( S, NumPortu, Offset2 ); a S-funkcja wykorzystująca indywidualne czasy próbkowania nie powinna wywoływać makr ssSetSampleTime() i ssSetOffsetTime() a Możliwe jest także dziedziczenie czasu próbkowania, bądź użycie "stałego" czasu próbkowania dla portu Czasy próbkowania a S-funkcja, która posiada choć jeden port, który dziedziczy czas próbkowania, powinna zdefiniować metody mdlSetInputPortSampleTime() – jeżeli jest to port wejściowy, bądź mdlSetOutputPortSampleTime() – jeżeli jest to port wyjściowy, bądź obydwie te metody a Metody te mogą posłużyć się makrami: real_T ssGetInputPortSampleTime(SimStruct *S, int_T nr) real_T ssGetInputPortOffsetTime (SimStruct *S, int_T nr) real_T ssGetOutputPortSampleTime(SimStruct *S, int_T nr) real_T ssGetOutputPortOffsetTime(SimStruct *S, int_T nr) w celu odczytania aktualnego czasu próbkowania Czasy próbkowania a Przez "stały" czas próbkowania (ang. constant sample time) dokumentacja Simulinka rozumie sytuację, gdy wartość wejścia lub wyjścia s-funkcji nie ulega zmianie w trakcie symulacji a Aby zezwolić na wykorzystanie "stałego" czasu próbkowania, należy w metodzie mdlInitializeSizes() ustawić opcję SS_OPTION_ALLOW_CONSTANT_PORT_SAMPLE_TIME a "Stały" czas próbkowania uzyskuje się przez ustawienie czasu próbkowania portu równego nieskończoności, i offsetu równego zero, np.: ssSetInputPortSampleTime(S, 0, mxGetInf() ); ssSetInputPortOffsetTime(S, 0, 0); Czasy próbkowania a Jeżeli portem, dla którego ustala się "stały" czas próbkowania, jest port wyjściowy, należy w metodzie mdlOutput() sprawdzać, czy drugi parametr jej wywołania jest równy symbolicznej wartości CONSTANT_TID a Jeżeli tak jest, należy ustawić wartość portu o "stałym" czasie próbkowania Czasy próbkowania a Funkcja o zmiennym czasie próbkowania powinna implementować metodę mdlGetTimeOfNextVarHit() a W metodzie tej należy wywołać makro: ssSetTNext(SimStruct *S, time_T tnext); a Drugim parametrem powyższego makra jest bezwzględny czas symulacji, w którym powinno nastąpić następne przetwarzanie metod bloku a Bieżącą chwilę symulacji można określić za pomocą makra: time_T ssGetT(SimStruct *S) a Jeżeli metoda mdlGetTimeOfNextVarHit() korzysta z wartości któregoś z wejść, należy dla tego wejścia ustawić cechę bezpośredniego przejścia sygnału przez blok Wektory robocze aZmienne, których wartości mają być przechowywane pomiędzy kolejnymi wywołaniami, można zadeklarować jako statyczne zmienne języka C aWadą takiego rozwiązania jest nadpisywanie tych zmiennych przez kolejne instancje danego bloku aWektory robocze Simulinka są pozbawione tej wady - mogą one przechowywać zmienne typu integer, real, wskaźnik i typu ogólnego Wektory robocze a Do ustawiania rozmiarów wektorów roboczych służą następujące makra, wywoływane w mdlInitializeSizes(): `ssSetNumContStates - rozmiar wektorów stanów ciągłych `ssSetNumDiscStates - rozmiar wektorów stanów dyskretnych `ssSetNumDWork - rozmiar wektorów roboczych typu ogólnego `ssSetNumRWork - rozmiar wektorów roboczych typu real `ssSetNumIWork - rozmiar wektorów roboczych typu integer `ssSetNumPWork - rozmiar wektorów roboczych typu wskaźnikowego `ssSetNumModes - rozmiar wektora trybów `ssSetNumNonsampledZCs - ilość stanów, dla których wyznaczane są chwile przejść przez zero Wektory robocze a Makra te są dwuparametrowe – drugim parametrem jest żądana liczba wektorów roboczych danego typu a Drugi parametr może przyjmować następujące wielkości: `zero (domyślna wielkość), `wartość całkowita dodatnia, `DYNAMICALLY_SIZED dla wielkości zależnej od wielkości wyjścia bloku (dobór automatyczny). a Można zdefiniować metodę mdlSetWorkWidths, w której dobiera się rozmiary wektorów roboczych, a w której znane są już rozmiary wejść i wyjść Wektory robocze a W przypadku wektorów typu ogólnego należy dookreślić typ i rodzaj każdego pola wektora przez wywołania: void ssSetDWorkDataType(SimStruct *S, int_T pos, DTypeID typ); void ssSetDWorkWidth(SimStruct *S, int_T pos, int_T rozm) void ssSetDWorkComplexSignal(SimStruct *S, int_T pos, Csignal_T flaga); gdzie flata = COMPLEX_YES lub COMPLEX_NO Wektory robocze a W dowolnej z metod możliwe jest odczytanie rozmiaru każdego z wektorów roboczych przez wywołania makr: int_T int_T int_T int_T ssGetNumDWork(SimStruct *S); ssGetNumIWork(SimStruct *S); ssGetNumPWork(SimStruct *S); ssGetNumRWork(SimStruct *S); a Dostęp do wektorów zmiennych roboczych uzyskuje się za pomocą wywołań: void* ssGetDWork(SimStruct *S, int_T pozycja); int_T* ssGetIWork(SimStruct *S); void** ssGetPWork(SimStruct *S); real_T* ssGetRWork(SimStruct *S); Raportowanie błędów a Do zgłaszania Simulinkowi błędów, np. błędnie wpisanych parametrów, używa się funkcji: void ssSetErrorStatus( SimStruct *S, const char_T *msg ); gdzie: S - wskaźnik do struktury SimStruct, msg - wskaźnik do łańcucha z opisem błędu. a Łańcuch z opisem błędu powinien być na stałe w pamięci (static), nie może to być zmienna lokalna! a Do wyświetlenia okna dialogowego z ostrzeżeniem służy makro: void ssWarning( SimStruct *S, const char_T *msg ); Raportowanie błędów a Do wyświetlenia dowolnego komunikatu w głównym oknie Matlaba służy makro: int_T ssPrintf(const char_T *msg, ... ); a Wartością zwracaną jest liczba poprawnie wyświetlonych bajtów, lub zero w przypadku błędu Raportowanie błędów a Wyjątki w dokumentacji Simulinka są rozumiane jako "długie skoki" (ang. long jumps) a Wystąpienie tak rozumianych wyjątków jest możliwe przy wywołaniu wszystkich funkcji, których nazwy rozpoczynają się od "mex" a Również funkcje o nazwach rozpoczynających się od "mx" nie dają gwarancji braku wyjątków a Nigdy nie powodują wystąpienia wyjątku te z funkcji o nazwach rozpoczynających się od "mx", które zwracają wskaźnik lub rozmiar, np. mxGetPr(), mxGetData(), msGetM(), mxGetNumberOfElements() a Obsługa wyjątków pociąga za sobą niebagatelny koszt czasowy Raportowanie błędów a W celu wyłączenia obsługi błędów można wykorzystać makro: ssSetOptions( S, SS_OPTION_EXCEPTION_FREE_CODE ) a W przypadku błędów funkcja z włączoną powyższą opcją może spowodować niezdefiniowane zachowanie Simulinka (zwykle zawieszenie) a Opcja SS_OPTION_RUNTIME_EXCEPTION_FREE_CODE sygnalizuje, że wyjątków nie powoduje kod najczęściej wykonywanych metod mdlGetTimeOfNextVarHit(), mdlUpdate(), mdlDerivatives() i mdlOutputs() S-funkcje w C++ a Mechanizm Simulinka zakłada sposób wywołań s-funkcji właściwy dla języka C a W celu wykorzystania w Simulinku kodu z języka C++ należy wszystkie wywołania metod Simulinka objąć dyrektywą <extern ”C”>: #ifdef __cplusplus extern ”C”{ #endif ... #ifdef __cplusplus } #endif S-funkcje w C++ Przechowywanie obiektów języka C++ pomiędzy wywołaniami metod Simulinka wymaga wykorzystania wskaźnikowych wektorów roboczych. Sposób postępowania jest następujący: 1. static void mdlInitializeSizes( SimStruct *S ){ ... ssSetNumPWork( S, 1 ); //Utworzenie wektora wsk. } 2. static void mdlStart( SimStruct *S, int_T tid ){ ssGetPWork(S)[ 0 ] = (void *) new Obiekt; } S-funkcje w C++ 3. 4. Aby odwołać się do obiektu, należy odczytać wskaźnik: void mdlOutputs( SimStruc *S, int_T tid){ Obiekt *Ob1 = (Obiekt *) ssGetPWork(S)[ 0 ]; real_T *y = ssGetOutputPortRealSignal( S, 0 ); y[ 0 ] = Obiekt->wyjscie(); } Na koniec symulacji należy obiekt skasować: void mdlTerminate( SimStruct *S ){ Obiekt *Ob1 = (Obiekt *) ssGetPWork(S)[ 0 ]; delete Ob1; } S-funkcje w C++ Przykład: wzmacniacz w języku C++