From e792d0b0280e9c76394454cd8ae95794bb98fd8d Mon Sep 17 00:00:00 2001 From: Thierry Zamofing Date: Fri, 15 Feb 2019 10:46:29 +0100 Subject: [PATCH] major renaming of transfer functions --- MXfastStageDoc/MXfastStage.tex | 126 ++++++++------ PBInspect1.pbi | 280 ++++++++++++++++++------------- matlab/StateSpaceControlDesign.m | 108 +++++++----- matlab/documentFunctions.m | 10 +- matlab/identifyFxFyStage.m | 146 ++++++++-------- matlab/observer.slx | Bin 35281 -> 35376 bytes matlab/simFxFyStage.m | 23 +-- matlab/stage_closed_loop.slx | Bin 35319 -> 35124 bytes matlab/testObserver.m | 252 ++++++++-------------------- python/MXTuning.py | 225 +++++++++++++------------ python/usr_code/Makefile | 2 +- 11 files changed, 592 insertions(+), 580 deletions(-) diff --git a/MXfastStageDoc/MXfastStage.tex b/MXfastStageDoc/MXfastStage.tex index 2067ccc..27e66d8 100644 --- a/MXfastStageDoc/MXfastStage.tex +++ b/MXfastStageDoc/MXfastStage.tex @@ -128,6 +128,13 @@ Out of the bode plot we can read approx.:\\ Motor 1: -33dB at 130Hz\\ Motor 2: -33dB at 84Hz\\ +The chirp parameters for low frequencies bode measurements (yellow) are: \verb|minFrq=1, maxFrq=15, amp=1000|.\\ +In this regime of 0 to 10Hz the bode amplitude is flat, which is mainly the result of the friction:\\ +Motor 1 has 9.63dB($\approx$ factor 10) at 10 Hz. This means that an amplitude of 1000um needs roughly 100 \verb|curr_bits|. +The friction as measured in figure \ref{fig:mot1_frict} is around 100 \verb|curr_bits|. At lower frequencies than 10 Hz with an amplitude of 1000um, the friction free motion consumes much less current (-40dB) than the friction. +Therefore the whole current is used to overcome the friction and the bode amplitude remains constant. + + %n times higher mass $\rightarrow$ n times lower frequency for same amplitude response\\ %n times higher frequency $\rightarrow$ n times higher velocity $\rightarrow$ $n^2$ times more acceleration==current %1um at 12Hz with 1 mA $\rightarrow$ with 2000mA $\rightarrow$ sqrt(2000)*12Hz=540Hz @@ -385,15 +392,15 @@ springs: $k=k_1+k_2+\ldots+k_n$\\ \begin{equation} \begin{aligned} -\dot{x} = Ax + Bu\\ -y = Cx + Du +\mathbf{\dot{x} = Ax + Bu}\\ +\mathbf{y = Cx + Du} \end{aligned}\label{eq:mech2} \end{equation} \eqref{eq:mech2} are the general input output equations in matrix form with x and A defines as \eqref{eq:mech3}: \begin{equation} -x= +\mathbf{x}= \begin{bmatrix} x_1\\ \dot{x}_1\\ @@ -404,7 +411,7 @@ x_3\\ x_4\\ \dot{x}_4\\ \end{bmatrix},\quad -\dot{x}= +\mathbf{\dot{x}}= \begin{bmatrix} \dot{x}_1\\ \ddot{x}_1\\ @@ -415,7 +422,7 @@ x_4\\ \dot{x}_4\\ \ddot{x}_4\\ \end{bmatrix},\quad -A= +\mathbf{A}= \begin{bmatrix} 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0\\ -\frac{k}{m_1} & -\frac{d}{m_1} & \frac{k_2}{m_1} & \frac{d_2}{m_1} & \frac{k_3}{m_1} & \frac{d_3}{m_1} & \frac{k_4}{m_1} & \frac{d_4}{m_1}\\ @@ -440,7 +447,14 @@ B=\begin{bmatrix} \label{eq:mech3} \end{equation} +\begin{tcolorbox}[colback=red!5!white,colframe=red!75!black,colbacktitle=red!50,coltitle=black,title=TODO] +$k_1=0$ The non linear friction has to be compensate outside of the control loop.\\ +$d_1$ seems to be negligable (section \ref{sec:stageData}).\\ +But the system is not observable. $\rightarrow$ Perhaps the integrators of the system can be removed. having only $\mathbf{x}=[ \dot{x}_1 \dot{x}_2 \dot{x}_3 \dot{x}_4]$ as the state vector +\end{tcolorbox} + \subsection{Stage data} +\label{sec:stageData} This data comes from datasheets and construction information \cite{DynParker}. @@ -469,16 +483,19 @@ Inductance &L& 2.4mH\\ \vspace{1pc} -The data in the data sheet are quite confusing. But lets check the motor Konstant Km.\\ +The data sheet for the current are quite confusing (sine, trap, rms). But lets check the motor Konstant Km.\\ The data sheet says:\\ -Stall Current Continous 0.92A, Stall force Continous 4N - +Stall Current Continous 0.92A (trap, DC), Stall force Continous 4N \[ U=R\cdot I \qquad P=U \cdot I \quad \rightarrow \quad P=R \cdot I^2\\ \] - at a constant current of 0.92A we have $ 8.8 \cdot 0.92^2 = 7.44 $W the resulting force will be:\\ -$1.46N \cdot \sqrt{7.44} = 3.98 N$ +$1.46N \cdot \sqrt{7.44} = 3.98 N$\\ + +Lets have a look at the viscose damping:\\ +our speed is roughly 50$\mu$m/10ms. this is 5mm/s. The damping force at that speed is: $0.5\frac{Ns}{m}\cdot 5\frac{mm}{s}=2\cdot10^{-3}N$. +The used current for that force is negligable, and theerefore the viscose damopling is negligable. + \subsection{identification of stages} @@ -500,20 +517,26 @@ The approximated transfer functions can be tweaked and edited with: e.g. \verb|c The full transfer function is then split in individual parts that are put back into \verb|MXTuning.py|.\\ the transfer functions for each two motors are separated atomic transfer function as stated in table \ref{tab:trfFunc1}\\ +tfc = transfer function current, tfp = transfer function position. Consecutive letters are simplified transfer functions. tf[1-9]=transfer function resonance. \begin{table}[h!] \center \begin{tabular}{|l|l|l|l|} \hline -key & description & motor1 (fy) & motor2 (fx) \\ +key & description & motor1 (fy) & motor2 (fx) \\ \hline -tfc & curren loop tf & f=694 ? 1389 , d=0.75 & same \\ -tf1 & \vtop{\hbox{\strut main mechanical tf} \hbox{\strut with -40dB/dec }} - & mag=6dB f=7.96Hz d=0.6 & mag=12dB f=3.34 d=0.4 \\ -tf2 & mechanical resonance tf & f=[197,199] d=[0.02,0.02] & f=[55|61] d=[0.2|0.2] \\ -tf3 & mechanical resonance tf & & f=[128|137] d=[0.05|0.05] \\ -tf4 & mechanical resonance tf & & f=[410|417] d=[0.015|0.015] \\ -tf5 & mechanical resonance tf & & f=[230|233] d=[0.04|0.04] \\ +tfc & current loop tf & f=694 ? 1389 , d=0.75 & same \\ +tfd & simplified cur.loop tf $PT_1$ & f-3dB, 45\deg at 400Hz & same \\ +\hline +tfp & \vtop{\hbox{\strut main mechanical tf} \hbox{\strut with -40dB/dec }} + & mag=6dB f=7.96Hz d=0.6 & mag=12dB f=3.34 d=0.4 \\ +tfq & \vtop{\hbox{\strut simplified mechanical tf} \hbox{\strut 2 integrators with -40dB/dec }} + & 0dB at 19.8Hz & 0dB at 11.84Hz\\ +\hline +tf1 & mechanical resonance tf & f=[197,199] d=[0.02,0.02] & f=[55|61] d=[0.2|0.2] \\ +tf2 & mechanical resonance tf & & f=[128|137] d=[0.05|0.05] \\ +tf3 & mechanical resonance tf & & f=[410|417] d=[0.015|0.015] \\ +tf4 & mechanical resonance tf & & f=[230|233] d=[0.04|0.04] \\ \hline \end{tabular} \caption{plant partial transfer functions} @@ -521,7 +544,7 @@ tf5 & mechanical resonance tf & & f=[23 \end{table} %\vspace{1pc} -The black line on figure \ref{fig:mot_open} shows the concatenate transfer function $tf1 \cdot tf2 \cdot tf3 \cdot tf4 \cdot tf5 \cdot tfc$ . +The black line on figure \ref{fig:mot_open} shows the concatenate transfer function $tfc \cdot tfp \cdot tf1 \cdot tf2 \cdot tf3 \cdot tf4$.\\ \vspace{1pc} \fbox{\parbox[t]{15cm}{The current loop frequency from the MATLAB identification looks different than the used one. Matlab identifies $w_0=8727rad/sec = f0=1389Hz$ but to match the bode plot a value of half frequency need to be taken:$f_0=694$. The reason could be discretization and time delay because the servo loop is processed after the phase loop.}} @@ -584,29 +607,17 @@ opposite to the PID cpntroller which is a ‘black-box design’, the state spa \end{figure} -To implement a state space controller with observer the model has to be observable and controllable. The full model of the tranfer function $tf1 \cdot tf2 \cdot tf3 \cdot tf4 \cdot tf5 \cdot tfc$ is not controllable due to the various resonance frequencies. Therefore for the observer design the state space model has to be simplified. +To implement a state space controller with observer the model has to be observable and controllable. The full model of the tranfer function $tfc \cdot tfp \cdot tf1 \cdot tf2 \cdot tf3 \cdot tf4$ is not controllable due to the various resonance frequencies. Therefore for the observer design the state space model has to be simplified. (see simplified transfer function in table \ref{tab:trfFunc1}\\ -In addition to the transferfunction in table \ref{tab:trfFunc1} following simplified transfer functions have been added:\\ - -\begin{tabular}{|l|l|l|l|} -\hline -key & description & motor1 (fy) & motor2 (fx) \\ -\hline -tfd & simplified loop tf $PT_1$ & f-3dB, 45\deg at 400Hz & same \\ -tf0 & \vtop{\hbox{\strut simplified mechanical tf} \hbox{\strut 2 integrators with -40dB/dec }} - & 0dB at 19.8Hz & 0dB at 11.84Hz\\ -\hline -\end{tabular} -\vspace{1pc} Figure \ref{fig:mdl_bode1} shows the bode plots of the best model to the most simplified model:\\ \begin{tabular}{ll} -plant & best model tranfer function: $tf1 \cdot tf2 \cdot tf3 \cdot tf4 \cdot tf5 \cdot tfc$\\ -mdl1c & main mechanical with second order current loop: $tf1 \cdot tfc$\\ -mdl1d & main mechanical with $PT_1$ current loop (first order): $tf1 \cdot tfd$\\ -mdl1 & main mechanical (incl. 'viscose friction'): $tf1$\\ -mdl0 & only 2 integrators: $tf0$\\ +plant & best model tranfer function: $tfc \cdot tfp \cdot tf1 \cdot tf2 \cdot tf3 \cdot tf4$\\ +mdlcp & main mechanical with second order current loop: $tfc \cdot tfp$\\ +mdldp & main mechanical with $PT_1$ current loop (first order): $tfd \cdot tfp$\\ +mdlp & main mechanical (incl. 'viscose friction'): $tfp$\\ +mdlq & only 2 integrators: $tfq$\\ \end{tabular} \vspace{1pc} @@ -627,10 +638,10 @@ The matlab models are:\\ \begin{tabular}{ll} \texttt{ss\_plt:} & best approach of the plant with mechanics, resonance, current loop etc.\\ -\texttt{ss\_c1:} & model without resonance (only current and main mechanical)\\ -\texttt{ss\_d1:} & model without resonance (simplified current and main mechanical)\\ -\texttt{ss\_1:} & model without current loop, no resonance (only main mechanical)\\ -\texttt{ss\_0:} & simplified mechanical, no current loop, no resonance\\ +\texttt{ss\_cp:} & model without resonance (only current and main mechanical)\\ +\texttt{ss\_dp:} & model without resonance (simplified current and main mechanical)\\ +\texttt{ss\_p:} & model without current loop, no resonance (only main mechanical)\\ +\texttt{ss\_q:} & simplified mechanical, no current loop, no resonance\\ \end{tabular}\\ \vspace{1pc} @@ -639,13 +650,18 @@ Following code calculates parameters for a observer controller, does a simulatio \begin{verbatim} clear;clear global;close all; mot=identifyFxFyStage(7); -sscType=0 -for k =1:2 - [ssc]=StateSpaceControlDesign(mot{k},sscType);sim('observer'); - f=figure(); h=plot(desPos_actPos.Time,desPos_actPos.Data,'g'); - set(h(1),'color','b'); set(h(2),'color',[0 0.5 0]); - print(f,sprintf('figures/sim_cl_observer_%d',mot{k}.id),'-depsc'); +for sscType=0:1 + for k=1:2 + [ssc]=StateSpaceControlDesign(mot{k},sscType);sim('observer'); + f=figure(); h=plot(desPos_actPos.Time,desPos_actPos.Data,'g');grid on; + set(h(1),'color','b'); set(h(2),'color',[0 0.5 0]); + print(f,sprintf('figures/sim_cl_obs_%d_%d',sscType,mot{k}.id),'-depsc'); + f=bodeSamples(desPos_actPos); + print(f,sprintf('figures/sim_cl_obs_bode%d_%d',sscType,mot{k}.id),'-depsc'); + end end + + \end{verbatim} @@ -659,10 +675,10 @@ end \begin{figure}[h!] \center -\includegraphics[scale=.45]{../matlab/figures/sim_cl_observer_1.eps} -\includegraphics[scale=.45]{../matlab/figures/sim_cl_observer_bode1.eps}\\ -\includegraphics[scale=.45]{../matlab/figures/sim_cl_observer_2.eps} -\includegraphics[scale=.45]{../matlab/figures/sim_cl_observer_bode2.eps} +\includegraphics[scale=.45]{../matlab/figures/sim_cl_obs_0_1.eps} +\includegraphics[scale=.45]{../matlab/figures/sim_cl_obs_bode0_1.eps}\\ +\includegraphics[scale=.45]{../matlab/figures/sim_cl_obs_0_2.eps} +\includegraphics[scale=.45]{../matlab/figures/sim_cl_obs_bode0_2.eps} \caption{Observer sim: Motor 1 Motor 2} \label{fig:mot_observer_sim} \end{figure} @@ -675,10 +691,10 @@ With an additional prefilter resonance and reductions can be suppressed a bit.\\ \begin{figure}[h!] \center -\includegraphics[scale=.45]{../matlab/figures/sim_cl_obs_pf_1.eps} -\includegraphics[scale=.45]{../matlab/figures/sim_cl_obs_pf_bode1.eps}\\ -\includegraphics[scale=.45]{../matlab/figures/sim_cl_obs_pf_2.eps} -\includegraphics[scale=.45]{../matlab/figures/sim_cl_obs_pf_bode2.eps} +\includegraphics[scale=.45]{../matlab/figures/sim_cl_obs_1_1.eps} +\includegraphics[scale=.45]{../matlab/figures/sim_cl_obs_bode1_1.eps}\\ +\includegraphics[scale=.45]{../matlab/figures/sim_cl_obs_1_2.eps} +\includegraphics[scale=.45]{../matlab/figures/sim_cl_obs_bode1_2.eps} \caption{Observer with prefilter sim: Motor 1 Motor 2} \label{fig:mot_observer_sim} \end{figure} diff --git a/PBInspect1.pbi b/PBInspect1.pbi index a94c14f..34aa3ce 100644 --- a/PBInspect1.pbi +++ b/PBInspect1.pbi @@ -1,4 +1,9 @@ [ + [ + "StatusGblListCtrl", + [], + "name=g;caption=global;state=67377148;dir=4;layer=0;row=0;pos=0;prop=100000;bestw=200;besth=369;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=41;floaty=486;floatw=208;floath=377;notebookid=-1;transparent=255" + ], [ "MotorListCtrl", [ @@ -24,160 +29,207 @@ [ "WatchListCtrl", [ + [ + "EncTable[9].PrevEnc", + null + ], + [ + "EncTable[10].PrevEnc", + null + ], + [ + "EncTable[11].PrevEnc", + null + ], + [ + "EncTable[12].PrevEnc", + null + ], + [ + "EncTable[13].PrevEnc", + null + ], + [ + "EncTable[14].PrevEnc", + null + ], + [ + "EncTable[15].PrevEnc", + null + ], + [ + "EncTable[16].PrevEnc", + null + ], [ "Motor[1].idCmd", - "lambda v: '%.8g'%float(v)" - ], - [ - "Motor[1].iqCmd", - "lambda v: '%.8g'%float(v)" - ], - [ - "Motor[1].ServoOut", - "lambda v: '%.8g'%float(v)" - ], - [ - "Motor[2].iqCmd", - "lambda v: '%.8g'%float(v)" + null ], [ "Motor[2].idCmd", - "lambda v: '%.8g'%float(v)" - ], - [ - "Motor[2].ServoOut", - "lambda v: '%.8g'%float(v)" - ], - [ - "Motor[3].iqCmd", - "lambda v: '%.8g'%float(v)" + null ], [ "Motor[3].idCmd", - "lambda v: '%.8g'%float(v)" - ], - [ - "Motor[3].ServoOut", - "lambda v: '%.8g'%float(v)" - ], - [ - "Motor[4].iqCmd", - "lambda v: '%.8g'%float(v)" + null ], [ "Motor[4].idCmd", - "lambda v: '%.8g'%float(v)" - ], - [ - "Motor[4].ServoOut", - "lambda v: '%.8g'%float(v)" - ], - [ - "Motor[5].iqCmd", - "lambda v: '%.8g'%float(v)" + null ], [ "Motor[5].idCmd", - "lambda v: '%.8g'%float(v)" - ], - [ - "Motor[5].ServoOut", - "lambda v: '%.8g'%float(v)" - ], - [ - "Motor[6].iqCmd", - "lambda v: '%.8g'%float(v)" + null ], [ "Motor[6].idCmd", - "lambda v: '%.8g'%float(v)" - ], - [ - "Motor[6].ServoOut", - "lambda v: '%.8g'%float(v)" - ], - [ - "Motor[7].iqCmd", - "lambda v: '%.8g'%float(v)" + null ], [ "Motor[7].idCmd", - "lambda v: '%.8g'%float(v)" - ], - [ - "Motor[7].iqCmd", - "lambda v: '%.8g'%float(v)" - ], - [ - "P1000", null ], [ - "P2000", + "Motor[8].idCmd", + null + ], + [ + "Motor[1].ServoOut", + null + ], + [ + "Motor[2].ServoOut", + null + ], + [ + "Motor[3].ServoOut", + null + ], + [ + "Motor[4].ServoOut", + null + ], + [ + "Motor[5].ServoOut", + null + ], + [ + "Motor[6].ServoOut", + null + ], + [ + "Motor[7].ServoOut", + null + ], + [ + "Motor[8].ServoOut", + null + ], + [ + "Gate3[1].Chan[0].UserFlag", null ] ], - "name=w;caption=watch;state=67377148;dir=4;layer=0;row=1;pos=0;prop=100000;bestw=200;besth=369;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=221;floaty=858;floatw=208;floath=377;notebookid=-1;transparent=255" + "name=w;caption=watch;state=67377148;dir=4;layer=0;row=2;pos=0;prop=100000;bestw=200;besth=369;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=221;floaty=858;floatw=208;floath=377;notebookid=-1;transparent=255" ], [ "StatusMotorListCtrl", [ - 3 + 1 ], - "name=m3;caption=motor3;state=67377148;dir=4;layer=0;row=6;pos=0;prop=100000;bestw=200;besth=669;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=694;floaty=941;floatw=208;floath=677;notebookid=-1;transparent=255" - ], - [ - "StatusGblListCtrl", - [], - "name=g;caption=global;state=67377148;dir=4;layer=0;row=0;pos=0;prop=100000;bestw=200;besth=369;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=2058;floaty=457;floatw=208;floath=377;notebookid=-1;transparent=255" + "name=m1;caption=motor1;state=67377148;dir=4;layer=0;row=3;pos=0;prop=100000;bestw=200;besth=669;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=485;floaty=810;floatw=208;floath=677;notebookid=-1;transparent=255" ], [ "StatusMotorListCtrl", [ 2 ], - "name=m2;caption=motor2;state=67377148;dir=4;layer=0;row=5;pos=1;prop=100000;bestw=200;besth=669;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=2588;floaty=946;floatw=208;floath=677;notebookid=-1;transparent=255" + "name=m2;caption=motor2;state=67377148;dir=4;layer=0;row=3;pos=1;prop=100000;bestw=200;besth=669;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=520;floaty=734;floatw=208;floath=677;notebookid=-1;transparent=255" ], [ - "StatusMotorListCtrl", + "WatchListCtrl", [ - 4 + [ + "P2010", + "lambda v: '%.5g'%float(v)" + ], + [ + "P2011", + "lambda v: '%.5g'%float(v)" + ], + [ + "P2012", + "lambda v: '%.5g'%float(v)" + ], + [ + "P2013", + "lambda v: '%.5g'%float(v)" + ], + [ + "P2014", + "lambda v: '%.5g'%float(v)" + ], + [ + "P2015", + "lambda v: '%.5g'%float(v)" + ], + [ + "P2016", + "lambda v: '%.5g'%float(v)" + ], + [ + "P2017", + "lambda v: '%.5g'%float(v)" + ], + [ + "P2018", + "lambda v: '%.5g'%float(v)" + ], + [ + "P2019", + "lambda v: '%.5g'%float(v)" + ], + [ + "P2020", + "lambda v: '%.5g'%float(v)" + ], + [ + "P2021", + "lambda v: '%.5g'%float(v)" + ], + [ + "P2022", + "lambda v: '%.5g'%float(v)" + ], + [ + "P2023", + "lambda v: '%.5g'%float(v)" + ], + [ + "P2024", + "lambda v: '%.5g'%float(v)" + ], + [ + "P2025", + "lambda v: '%.5g'%float(v)" + ], + [ + "P2026", + "lambda v: '%.5g'%float(v)" + ], + [ + "P2027", + "lambda v: '%.5g'%float(v)" + ], + [ + "P2028", + "lambda v: '%.5g'%float(v)" + ], + [ + "P2029", + "lambda v: '%.5g'%float(v)" + ] ], - "name=m4;caption=motor4;state=67377148;dir=4;layer=0;row=7;pos=0;prop=100000;bestw=200;besth=669;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=905;floaty=792;floatw=208;floath=677;notebookid=-1;transparent=255" - ], - [ - "StatusMotorListCtrl", - [ - 5 - ], - "name=m5;caption=motor5;state=67377148;dir=4;layer=0;row=7;pos=1;prop=100000;bestw=200;besth=669;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=1139;floaty=836;floatw=208;floath=677;notebookid=-1;transparent=255" - ], - [ - "StatusMotorListCtrl", - [ - 7 - ], - "name=m7;caption=motor7;state=67377148;dir=4;layer=0;row=8;pos=1;prop=100000;bestw=200;besth=669;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=1353;floaty=657;floatw=208;floath=677;notebookid=-1;transparent=255" - ], - [ - "StatusMotorListCtrl", - [ - 6 - ], - "name=m6;caption=motor6;state=67377148;dir=4;layer=0;row=8;pos=0;prop=100000;bestw=200;besth=669;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=1276;floaty=771;floatw=208;floath=677;notebookid=-1;transparent=255" - ], - [ - "StatusMotorListCtrl", - [ - 1 - ], - "name=m1;caption=motor1;state=67377148;dir=4;layer=0;row=5;pos=0;prop=100000;bestw=200;besth=669;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=669;floaty=892;floatw=208;floath=677;notebookid=-1;transparent=255" - ], - [ - "StatusCoordListCtrl", - [ - 1 - ], - "name=c1;caption=coord1;state=67377148;dir=4;layer=0;row=2;pos=0;prop=100000;bestw=200;besth=669;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=527;floaty=439;floatw=208;floath=677;notebookid=-1;transparent=255" + "name=w-1;caption=watch;state=67377148;dir=4;layer=0;row=1;pos=0;prop=129863;bestw=200;besth=369;minw=-1;minh=-1;maxw=-1;maxh=-1;floatx=464;floaty=580;floatw=208;floath=377;notebookid=-1;transparent=255" ] ] \ No newline at end of file diff --git a/matlab/StateSpaceControlDesign.m b/matlab/StateSpaceControlDesign.m index 49010fd..09c14d3 100644 --- a/matlab/StateSpaceControlDesign.m +++ b/matlab/StateSpaceControlDesign.m @@ -39,43 +39,67 @@ function [ssc]=StateSpaceControlDesign(mot,mode) %use_lqr: use lqr instead of pole placement - verb=1; + t=0:1E-4:.5; %time vector for simulations + verb=0; use_lqr=0; MaxDac=2011.968; - filt_pos_err=Prefilt(mot,2); + tfDesPos=tf(1,1); + tfPosErr=tf(1,1); + ssc=struct(); + ssc.sel1={3,[3]}; %locate poles: 2500rad/s = 397Hz, 6300rad/s = 1027Hz switch mode case -1 %TESTING + + case 0 %for document: best observer without prefilter ss_plt=mot.ss_plt; %ss_plt ss_c1 ss_d1 ss_1 ss_0 - ss_mdl=mot.ss_d1; %ss_plt ss_c1 ss_d1 ss_1 ss_0 - if mot.id==1 - pl=[-2200 -2100 -2000]; % stable with scaling of .05 .. 1.0; - else - pl=[-2500 -900 -800]; - end - plObs=2*pl; - case 0 - ss_plt=mot.ss_plt; %ss_plt ss_c1 ss_d1 ss_1 ss_0 - ss_mdl=mot.ss_c1; %ss_plt ss_c1 ss_d1 ss_1 ss_0 + ss_mdl=mot.ss_cp; %ss_plt ss_c1 ss_d1 ss_1 ss_0 if mot.id==1 pl=[-3300 -3200 -2900 -2800]; else pl=[-3300 -3200 -2700 -2600]; end plObs=2*pl; - case 1 + case 1 %for document: best observer without prefilter ss_plt=mot.ss_plt; %ss_plt ss_c1 ss_d1 ss_1 ss_0 - ss_mdl=mot.ss_d1; %ss_plt ss_c1 ss_d1 ss_1 ss_0 + ss_mdl=mot.ss_cp; %ss_plt ss_c1 ss_d1 ss_1 ss_0 + if mot.id==1 + pl=[-3300 -3200 -2900 -2800]; + else + pl=[-3300 -3200 -2700 -2600]; + end + plObs=2*pl; + tfDesPos=Prefilt(mot,2);%user designed envelope + tfPosErr=Prefilt(mot,1);%inverse resonance + case 2 %for document: best observer with prefilter + ss_plt=mot.ss_plt; %ss_plt ss_c1 ss_d1 ss_1 ss_0 + ss_mdl=mot.ss_dp; %ss_plt ss_c1 ss_d1 ss_1 ss_0 if mot.id==1 pl=[-2200 -2100 -2000]; % stable with scaling of .05 .. 1.0; else pl=[-2500 -900 -800]; end plObs=2*pl; - case 2 + case 3 ss_plt=mot.ss_plt; %ss_plt ss_c1 ss_d1 ss_1 ss_0 - ss_mdl=mot.ss_c1; %ss_plt ss_c1 ss_d1 ss_1 ss_0 - use_lqr=1 + ss_mdl=mot.ss_dp; %ss_plt ss_c1 ss_d1 ss_1 ss_0 + if mot.id==1 + pl=[-2200 -2100 -2000]; % stable with scaling of .05 .. 1.0; + else + pl=[-2500 -900 -800]; + end + plObs=2*pl; + case 4 + %this is the hovering ball model + A = [ 0 1 0; 980 0 -2.8;0 0 -100 ]; + B = [ 0 0 100 ]'; + C = [ 1 0 0 ]; + D = 0; + ssBall=ss(A,B,C,D,'InputName','iCmd','OutputName','actPos'); + ss_plt=ssBall; %ss_plt ss_c1 ss_d1 ss_1 ss_0 + ss_mdl=ssBall; %ss_plt ss_c1 ss_d1 ss_1 ss_0 + pl=[-10+10i -10-10i -50]; + plObs=[-100 -101 -102]; end [Am,Bm,Cm,Dm]=ssdata(ss_mdl); @@ -100,12 +124,14 @@ function [ssc]=StateSpaceControlDesign(mot,mode) if bitand(verb,4) % step answer on open loop: - t = 0:1E-4:.5; u = ones(size(t)); [yp,t,x] = lsim(ss_plt,u,t,xp0); [ym,t,x] = lsim(ss_mdl,u,t,xm0); - figure();plot(t,yp,t,ym,'--');title('step on open loop (plant and model)'); - legend('plt.iqMeas','plt.iqVolts','plt.actPos','mdl.iqMeas','mdl.iqVolts','mdl.actPos') + figure();hold on; plot(t,yp,'DisplayName',ss_plt.OutputName{1}) + plot(t,ym,'--','DisplayName',ss_plt.OutputName{1}); + title('step on open loop (plant and model)'); + %legend('plt.iqMeas','plt.iqVolts','plt.actPos','mdl.iqMeas','mdl.iqVolts','mdl.actPos') + legend('location','best') end %w0=abs(poles); %ang=angle(-poles); @@ -126,13 +152,13 @@ function [ssc]=StateSpaceControlDesign(mot,mode) V=-1./(Cm*(Am-Bm*K)^-1*Bm); %(from Lineare Regelsysteme2 (Glattfelder) page:173 ) if length(V)>1 - V=V(3); % only the position scaling needed + disp(['scaling (should be actPos): ' ss_mdl.OutputName{end}]) + V=V(end); % only the position scaling needed end ss_cl = ss(Am-Bm*K,Bm*V,Cm,0,'Name','space state controller','InputName',ss_mdl.InputName,'OutputName',ss_mdl.OutputName); if bitand(verb,8) % step answer on closed loop with space state controller: - t = 0:1E-4:.5; [y,t,x]=lsim(ss_cl,V*u,t,xm0); figure();plot(t,y);title('step on closed loop'); end @@ -206,7 +232,8 @@ function [ssc]=StateSpaceControlDesign(mot,mode) ss_oz = c2d(ss_o,Ts); %discrete prefilter - filt_pos_err_z=c2d(filt_pos_err,Ts); + tfDesPos_z=c2d(tfDesPos,Ts); + tfPosErr_z=c2d(tfPosErr,Ts); if bitand(verb,128) h=bodeplot(filt_pos_err,filt_pos_err_z); @@ -214,11 +241,10 @@ function [ssc]=StateSpaceControlDesign(mot,mode) end %state space controller - ssc=struct(); - for k=["Ts","ss_plt","ss_o","ss_oz","filt_pos_err","filt_pos_err_z","V","MaxDac"] + for k=["Ts","ss_plt","ss_o","ss_oz","tfDesPos","tfDesPos_z","tfPosErr","tfPosErr_z","V","MaxDac"] ssc=setfield(ssc,k,eval(k)); end - + mat2py=struct(); %[ozA,ozB,ozC,ozD]=ssdata(ss_oz); %[pos_err_num,pos_err_den]=tfdata(filt_pos_err_z); @@ -230,12 +256,9 @@ function [ssc]=StateSpaceControlDesign(mot,mode) mat2py.ozB=ss_oz.B; mat2py.ozC=ss_oz.C; mat2py.ozD=ss_oz.D; - mat2py.ozInpName=ss_oz.InputName - mat2py.ozOutName=ss_oz.OutputName - - mat2py.pos_err_num=filt_pos_err_z.Numerator{1}; - mat2py.pos_err_den=filt_pos_err_z.Denominator{1}; - + mat2py.ozInpName=ss_oz.InputName; + mat2py.ozOutName=ss_oz.OutputName; + fn=[pwd '/' sprintf( 'ssc%d.mat',mot.id)]; save(fn,'-struct','mat2py'); disp(['saved ' fn]); @@ -247,23 +270,26 @@ function pf=Prefilt(mot,mode) pf=tf(1,1); case 1 %inverse resonance if mot.id==1 - den=mot.mdl.num2;%num=1; - num=mot.mdl.den2;%den=[1 0 0]; + den=mot.mdl.num1;%num=1; + num=mot.mdl.den1;%den=[1 0 0]; pf=tf(num,den); else - den=conv(conv(conv(mot.mdl.num2,mot.mdl.num3),mot.mdl.num4),mot.mdl.num5);%num=1; - num=conv(conv(conv(mot.mdl.den2,mot.mdl.den3),mot.mdl.den4),mot.mdl.den5);%den=[1 0 0]; + den=conv(conv(conv(mot.mdl.num1,mot.mdl.num2),mot.mdl.num3),mot.mdl.num4);%num=1; + num=conv(conv(conv(mot.mdl.den1,mot.mdl.den2),mot.mdl.den3),mot.mdl.den4);%den=[1 0 0]; pf=tf(num,den); end case 2 if mot.id==1 - f=200;w0=f*2*pi; num1=[1 300 w0^2]; den1=[1 200 w0^2]; - numV=num1; - denV=den1; - pf=tf(numV,denV); + %f=200;w0=f*2*pi; num1=[1 300 w0^2]; den1=[1 200 w0^2]; + %numV=num1; + %denV=den1; + %pf=tf(numV,denV); + %Lag + f=[200 300]; w=f*2*pi; T=1./w; + pf=tf([T(1) 1],[T(2) 1]); else %Lag - f=[100 200]; w=f*2*pi; T=1./w; + f=[200 400]; w=f*2*pi; T=1./w; tf1=tf([T(1) 1],[T(2) 1]); %bo = bodeoptions; %bo.FreqUnits = 'Hz'; bo.MagUnits='abs'; bo.Grid='on'; diff --git a/matlab/documentFunctions.m b/matlab/documentFunctions.m index 670656c..5ccfa9b 100644 --- a/matlab/documentFunctions.m +++ b/matlab/documentFunctions.m @@ -8,7 +8,7 @@ mot=identifyFxFyStage(7); close all;disp('simulate stage closed loop...'); for k =1:2 [pb]=simFxFyStage(mot{k});sim('stage_closed_loop'); - f=figure(); h=plot(desPos_actPos.Time,desPos_actPos.Data,'g'); + f=figure(); h=plot(desPos_actPos.Time,desPos_actPos.Data,'g');grid on; set(h(1),'color','b'); set(h(2),'color',[0 0.5 0]); print(f,sprintf('figures/sim_cl_DTGz_%d',mot{k}.id),'-depsc'); f=bodeSamples(desPos_actPos); @@ -16,14 +16,14 @@ for k =1:2 end close all;disp('simulate observer...'); -for sscType=0%0:1 +for sscType=1%0:1 for k=1:2 [ssc]=StateSpaceControlDesign(mot{k},sscType);sim('observer'); - f=figure(); h=plot(desPos_actPos.Time,desPos_actPos.Data,'g'); + f=figure(); h=plot(desPos_actPos.Time,desPos_actPos.Data,'g');grid on; set(h(1),'color','b'); set(h(2),'color',[0 0.5 0]); - print(f,sprintf('figures/sim_cl_obs_%d_%d',m,mot{k}.id),'-depsc'); + print(f,sprintf('figures/sim_cl_obs_%d_%d',sscType,mot{k}.id),'-depsc'); f=bodeSamples(desPos_actPos); - print(f,sprintf('figures/sim_cl_obs_bode%d_%d',m,mot{k}.id),'-depsc'); + print(f,sprintf('figures/sim_cl_obs_bode%d_%d',sscType,mot{k}.id),'-depsc'); end end disp('document figure generation done');close all; diff --git a/matlab/identifyFxFyStage.m b/matlab/identifyFxFyStage.m index d6ffc1a..d4d3236 100644 --- a/matlab/identifyFxFyStage.m +++ b/matlab/identifyFxFyStage.m @@ -163,11 +163,11 @@ function motCell=identifyFxFyStage(mode) catch return end - h=bodeplot(mot.meas,'r',mot.ss_plt(3,1),'g',mot.ss_c1(3,1),'b',mot.ss_d1(3,1),'m',mot.ss_1(2,1),'c',mot.ss_0(2,1),'k',mot.w); + h=bodeplot(mot.meas,'r',mot.ss_plt(3,1),'g',mot.ss_cp(3,1),'b',mot.ss_dp(3,1),'m',mot.ss_p(2,1),'c',mot.ss_q(2,1),'k',mot.w); setoptions(h,'FreqUnits','Hz','Grid','on'); p=getoptions(h);p.YLim{2}=[-360 90];p.YLimMode='manual';setoptions(h,p); ax=h.getaxes(); - legend(ax(1),'Location','sw',{'real','plant','c1','d1','1','0'}); + legend(ax(1),'Location','sw',{'real','plant','cp','dp','p','q'}); print(gcf,sprintf('figures/plotBode_%d',mot.id),'-depsc'); end @@ -175,24 +175,24 @@ function motCell=identifyFxFyStage(mode) %current loop iqCmd->iqMeas tfc=tf(mot.mdl.numc,mot.mdl.denc,'InputName','iqCmd','OutputName','iqMeas'); - %simplified current loop iqCmd->iqMeas (first order tf) tfd=tf(mot.mdl.numd,mot.mdl.dend,'InputName','iqCmd','OutputName','iqMeas'); + %force(=current) to velocity and position iqForce->(actVel,actPos), actVel=s*actPos + tfp=tf({[mot.mdl.nump 0];mot.mdl.nump},mot.mdl.denp,'InputName','iqForce','OutputName',{'actVel','actPos'}); + %simplified force(=current) to velocity and position iqForce->(actVel,actPos), actVel=s*actPos + tfq=tf({[mot.mdl.numq 0];mot.mdl.numq},mot.mdl.denq,'InputName','iqForce','OutputName',{'actVel','actPos'}); + %simplified force(=current) to velocity iqForce->(actVel) + tfv=tf(mot.mdl.numv,mot.mdl.denv,'InputName','iqForce','OutputName',{'actVel'}); + %resonance iqMeas->iqForce - tf2=tf(mot.mdl.num2,mot.mdl.den2,'InputName','iqMeas','OutputName','iqForce'); + tf1=tf(mot.mdl.num1,mot.mdl.den1,'InputName','iqMeas','OutputName','iqForce'); %current to position iqForce->actPos - tf1_=tf(mot.mdl.num1,mot.mdl.den1,'InputName','iqForce','OutputName','actPos'); - - %force(=current) to velocity and position iqForce->(actVel,actPos), actVel=s*actPos - tf1=tf({[mot.mdl.num1 0];mot.mdl.num1},mot.mdl.den1,'InputName','iqForce','OutputName',{'actVel','actPos'}); - - %simplified force(=current) to velocity and position iqForce->(actVel,actPos), actVel=s*actPos - tf0=tf({[mot.mdl.num0 0];mot.mdl.num0},mot.mdl.den0,'InputName','iqForce','OutputName',{'actVel','actPos'}); - + %tf1_=tf(mot.mdl.num1,mot.mdl.den1,'InputName','iqForce','OutputName','actPos'); + %check observable/controlable of transfer functions - ssLst=["tfc","tfd","tf0","tf1","tf2","tfc*tf1*tf2","tfc*tf1","tfd*tf1","tf1*tf2"]; + ssLst=["tfc","tfd","tfp","tfq","tfv","tf1","tfc*tfp*tf1","tfc*tfp","tfd*tfp","tfp*tf1","tfc*tfv","tfd*tfv","tfd*tfv*tf1"]; sys=[]; for s = ssLst eval('sys=ss('+s+');') @@ -201,23 +201,32 @@ function motCell=identifyFxFyStage(mode) % sample code: %tfc iqCmd-> iqMeas - %tf2 resonance iqMeas->iqForce - %tf1 iqForce->(actVel,actPos) + %tfp iqForce->(actVel,actPos) + %tf1 resonance iqMeas->iqForce %connect(tfc,tf2,'iqCmd','iqForce'); %connect(tfc,tf2,'iqCmd',{'iqMeas','iqForce'}); %connect(tfc,tf2,tf1_,'iqCmd',{'iqMeas','iqForce','actPos'}); %connect(tfc,tf2,tf1_,'iqCmd',{'iqMeas','actPos'}); - % best plant approximation % u +-----------+ y %iqCmd------->|1 1|-------> iqMeas % | 2|-------> actVel % | 3|-------> actPos % +-----------+ - mot.ss_plt=connect(tfc,tf1,tf2,'iqCmd',{'iqMeas','actVel','actPos'}); + mot.ss_plt=connect(tfc,tfp,tf1,'iqCmd',{'iqMeas','actVel','actPos'}); mot.ss_plt.Name='best plant approximation'; chkCtrlObsv(mot.ss_plt,'ss_plt fyStage'); + + % best plant approximation without friction (always -40dB) + % u +-----------+ y + %iqCmd------->|1 1|-------> iqMeas + % | 2|-------> actVel + % | 3|-------> actPos + % +-----------+ + mot.ss_cqr=connect(tfc,tfq,tf1,'iqCmd',{'iqMeas','actVel','actPos'}); + mot.ss_cqr.Name='plant no friction'; + chkCtrlObsv(mot.ss_plt,'ss_cqr fyStage'); %without resonance % u +-----------+ y @@ -225,44 +234,41 @@ function motCell=identifyFxFyStage(mode) % | 2|-------> actVel % | 3|-------> actPos % +-----------+ - s=tf1.InputName{1};tf1.InputName{1}='iqMeas'; - mot.ss_c1=connect(tfc,tf1,'iqCmd',{'iqMeas','actVel','actPos'}); - mot.ss_c1.Name='without resonance'; - chkCtrlObsv(mot.ss_c1,'ss_c1 fyStage'); - tf1.InputName{1}=s;%restore + s=tfp.InputName{1};tfp.InputName{1}='iqMeas'; + mot.ss_cp=connect(tfc,tfp,'iqCmd',{'iqMeas','actVel','actPos'}); + mot.ss_cp.Name='without resonance'; + chkCtrlObsv(mot.ss_cp,'ss_cp fyStage'); + tfp.InputName{1}=s;%restore - %simplified current, without resonance % u +-----------+ y %iqCmd------->|1 1|-------> iqMeas % | 2|-------> actVel % | 3|-------> actPos % +-----------+ - s=tf1.InputName{1};tf1.InputName{1}='iqMeas'; - mot.ss_d1=connect(tfd,tf1,'iqCmd',{'iqMeas','actVel','actPos'}); - mot.ss_d1.Name='simplified current, without resonance'; - chkCtrlObsv(mot.ss_d1,'ss_d1 fyStage'); - tf1.InputName{1}=s;%restore - + s=tfp.InputName{1};tfp.InputName{1}='iqMeas'; + mot.ss_dp=connect(tfd,tfp,'iqCmd',{'iqMeas','actVel','actPos'}); + mot.ss_dp.Name='simplified current, without resonance'; + chkCtrlObsv(mot.ss_dp,'ss_dp fyStage'); + tfp.InputName{1}=s;%restore %no current loop, no resonance % u +-----------+ y %iqCmd------->|1 1|-------> actVel % | 2|-------> actPos % +-----------+ - mot.ss_1=ss(tf1); - mot.ss_1.Name='no current loop, no resonance'; - chkCtrlObsv(mot.ss_1,'ss_1 fyStage'); - + mot.ss_p=ss(tfp); + mot.ss_p.Name='no current loop, no resonance'; + chkCtrlObsv(mot.ss_p,'ss_p fyStage'); %simplified mechanics, no current loop, no resonance % u +-----------+ y %iqCmd------->|1 1|-------> actVel % | 2|-------> actPos % +-----------+ - mot.ss_0=ss(tf0); - mot.ss_0.Name='simplified mechanics, no current loop, no resonance'; - chkCtrlObsv(mot.ss_0,'ss_0 fyStage'); + mot.ss_q=ss(tfq); + mot.ss_q.Name='simplified mechanics, no current loop, no resonance'; + chkCtrlObsv(mot.ss_q,'ss_q fyStage'); %h=bodeplot(mot.meas,'r',mot.tf4_2,'b',mot.tf6_4,'g'); %h=bodeplot(mot.meas,'r',mot.tf2_0,'b',mot.tf_mdl,'g',mot.w); @@ -272,58 +278,64 @@ function motCell=identifyFxFyStage(mode) function mot=fxStage(mot) %current loop iqCmd->iqMeas tfc=tf(mot.mdl.numc,mot.mdl.denc,'InputName','iqCmd','OutputName','iqMeas'); - %simplified current loop iqCmd->iqMeas (first order tf) tfd=tf(mot.mdl.numd,mot.mdl.dend,'InputName','iqCmd','OutputName','iqMeas'); + %force(=current) to velocity and position iqForce->(actVel,actPos), actVel=s*actPos + tfp=tf({[mot.mdl.nump 0];mot.mdl.nump},mot.mdl.denp,'InputName','iqForce','OutputName',{'actVel','actPos'}); + %simplified force(=current) to velocity and position iqForce->(actVel,actPos), actVel=s*actPos + tfq=tf({[mot.mdl.numq 0];mot.mdl.numq},mot.mdl.denq,'InputName','iqForce','OutputName',{'actVel','actPos'}); + %resonance iqMeas->iqForce - tf2=tf(mot.mdl.num2,mot.mdl.den2,'InputName','iqMeas','OutputName','iqF1'); + tf1=tf(mot.mdl.num1,mot.mdl.den1,'InputName','iqMeas','OutputName','iqF1'); %resonance iqMeas->iqForce - tf3=tf(mot.mdl.num3,mot.mdl.den3,'InputName','iqF1','OutputName','iqF2'); + tf2=tf(mot.mdl.num2,mot.mdl.den2,'InputName','iqF1','OutputName','iqF2'); %resonance iqMeas->iqForce - tf4=tf(mot.mdl.num4,mot.mdl.den4,'InputName','iqF2','OutputName','iqF3'); + tf3=tf(mot.mdl.num3,mot.mdl.den3,'InputName','iqF2','OutputName','iqF3'); %resonance iqMeas->iqForce - tf5=tf(mot.mdl.num5,mot.mdl.den5,'InputName','iqF3','OutputName','iqForce'); + tf4=tf(mot.mdl.num4,mot.mdl.den4,'InputName','iqF3','OutputName','iqForce'); %current to position iqForce->actPos - tf1_=tf(mot.mdl.num1,mot.mdl.den1,'InputName','iqForce','OutputName','actPos'); - - %force(=current) to velocity and position iqForce->(actVel,actPos), actVel=s*actPos - tf1=tf({[mot.mdl.num1 0];mot.mdl.num1},mot.mdl.den1,'InputName','iqForce','OutputName',{'actVel','actPos'}); - - %simplified force(=current) to velocity and position iqForce->(actVel,actPos), actVel=s*actPos - tf0=tf({[mot.mdl.num0 0];mot.mdl.num0},mot.mdl.den0,'InputName','iqForce','OutputName',{'actVel','actPos'}); + %tf1_=tf(mot.mdl.num1,mot.mdl.den1,'InputName','iqForce','OutputName','actPos'); %check observable/controlable of transfer functions - ssLst=["tfc","tfd","tf0","tf1","tf2","tf3","tf4","tf5",... - "tfc*tf1*tf2","tfc*tf1","tfd*tf1","tf1*tf2","tf1*tf2*tf3"]; + ssLst=["tfc","tfd","tfp","tfq","tf1","tf2","tf3","tf4",... + "tfc*tfp*tf1","tfc*tfp","tfd*tfp","tfp*tf1","tfd*tfp*tf1","tfc*tf1","tfd*tf1","tfc*tf1*tf2","tfd*tf1*tf2"]; sys=[]; for s = ssLst eval('sys=ss('+s+');') chkCtrlObsv(sys,char(s)); end - % best plant approximation % u +-----------+ y %iqCmd------->|1 1|-------> iqMeas % | 2|-------> actVel % | 3|-------> actPos % +-----------+ - mot.ss_plt=connect(tfc,tf1,tf2,tf3,tf4,tf5,'iqCmd',{'iqMeas','actVel','actPos'}); + mot.ss_plt=connect(tfc,tfp,tf1,tf2,tf3,tf4,'iqCmd',{'iqMeas','actVel','actPos'}); chkCtrlObsv(mot.ss_plt,'ss_plt fxStage'); + % best plant approximation without friction (always -40dB) + % u +-----------+ y + %iqCmd------->|1 1|-------> iqMeas + % | 2|-------> actVel + % | 3|-------> actPos + % +-----------+ + mot.ss_cqr=connect(tfc,tfq,tf1,tf2,tf3,tf4,'iqCmd',{'iqMeas','actVel','actPos'}); + mot.ss_cqr.Name='plant no friction'; + chkCtrlObsv(mot.ss_cqr,'ss_plt0 fxStage'); + %without resonance % u +-----------+ y %iqCmd------->|1 1|-------> iqMeas % | 2|-------> actVel % | 3|-------> actPos % +-----------+ - s=tf1.InputName{1};tf1.InputName{1}='iqMeas'; - mot.ss_c1=connect(tfc,tf1,'iqCmd',{'iqMeas','actVel','actPos'}); - chkCtrlObsv(mot.ss_c1,'ss_c1 fxStage'); - tf1.InputName{1}=s;%restore - + s=tfp.InputName{1};tfp.InputName{1}='iqMeas'; + mot.ss_cp=connect(tfc,tfp,'iqCmd',{'iqMeas','actVel','actPos'}); + chkCtrlObsv(mot.ss_cp,'ss_cp fxStage'); + tfp.InputName{1}=s;%restore %simplified current, without resonance % u +-----------+ y @@ -331,28 +343,26 @@ function motCell=identifyFxFyStage(mode) % | 2|-------> actVel % | 3|-------> actPos % +-----------+ - s=tf1.InputName{1};tf1.InputName{1}='iqMeas'; - mot.ss_d1=connect(tfd,tf1,'iqCmd',{'iqMeas','actVel','actPos'}); - chkCtrlObsv(mot.ss_d1,'ss_d1 fxStage'); - tf1.InputName{1}=s;%restore - - + s=tfp.InputName{1};tfp.InputName{1}='iqMeas'; + mot.ss_dp=connect(tfd,tfp,'iqCmd',{'iqMeas','actVel','actPos'}); + chkCtrlObsv(mot.ss_dp,'ss_dp fxStage'); + tfp.InputName{1}=s;%restore + %no current loop, no resonance % u +-----------+ y %iqCmd------->|1 1|-------> actVel % | 2|-------> actPos % +-----------+ - mot.ss_1=ss(tf1); - chkCtrlObsv(mot.ss_1,'ss_1 fxStage'); + mot.ss_p=ss(tfp); + chkCtrlObsv(mot.ss_p,'ss_p fxStage'); - %simplified mechanics, no current loop, no resonance % u +-----------+ y %iqCmd------->|1 1|-------> actVel % | 2|-------> actPos % +-----------+ - mot.ss_0=ss(tf0); - chkCtrlObsv(mot.ss_0,'ss_0 fxStage'); + mot.ss_q=ss(tfq); + chkCtrlObsv(mot.ss_q,'ss_q fxStage'); plotBode(mot) end diff --git a/matlab/observer.slx b/matlab/observer.slx index a199d8bc50115c10e516bff1bbe236e887903022..97d75846d665967a902c1f7e5db1466332834a28 100644 GIT binary patch delta 13308 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

y~_8iF{X)o6t^SeZNf@|TFj`0U^y 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 delta 13730 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 diff --git a/matlab/testObserver.m b/matlab/testObserver.m index 93d97f0..777cb23 100644 --- a/matlab/testObserver.m +++ b/matlab/testObserver.m @@ -1,50 +1,16 @@ -function [ssc]=testObserver(mot) +function [ssc]=testObserver() %http://ctms.engin.umich.edu/CTMS/index.php?example=Introduction§ion=ControlStateSpace mdl=1; % 0:hovering ball 1:fast stage - if mdl==0 - A = [ 0 1 0 - 980 0 -2.8 - 100 - 0 0 -100 ]; + A = [ 0 1 0 + 980 0 -2.8 + 0 0 -100 ]; - B = [ 0 - 0]; + B = [ 0 + 0 + 100 ]; - C = [ 1 0 0 ]; - D=0; - else - [A,B,C,D]=ssdata(mot.ssMdl); - C=C(3,:);D=D(3); - numc=(mot.mdl.numc); - denc=(mot.mdl.denc); - num1=(mot.mdl.num1); - den1=(mot.mdl.den1); - - g1=tf(numc,denc); % iqCmd->iqMeas - s1=ss(g1); - s1.C=[s1.C; 1E5* 2.4E-3 1E-3*s1.C(2)*8.8]; % add output iqVolts: iqVolts= i_meas*R+i_meas'*L 2.4mH 8.8Ohm (took random scaling values) - g2=tf(num1,den1); %iqMeas->ActPos without resonance frequencies - s2=ss(g2); - s3=append(s1,s2); - s3.A(3,2)=s3.C(1,2)*s3.B(3,2); - - %d=.77;w0=9100; %1.44kHz -> T0=6.9046e-04 - %numc=[w0^2]; - %denc=[1 d*2*w0 w0^2]; - %T0=6E-4; %w=1/1E-3=1E3 -> f=w/(2pi)=1000/6.2=159Hz - %numc=[1]; - %denc=[T0^2 d*2*T0 1]; - %bode(numc,denc) - - num=conv(numc,num1);%num=1; - den=conv(denc,den1);%den=[1 0 0]; - g4=tf(num,den); %iqMeas->ActPos - s4=ss(g4); - mot.ssMdl - s3 - s4 - [A,B,C,D]=ssdata(s4); - end + C = [ 1 0 0 ]; + D=0; Ap=A;Am=A; Bp=B;Bm=B; @@ -52,159 +18,79 @@ function [ssc]=testObserver(mot) Dp=D;Dm=D;Dt=D; poles = eig(A); - disp(poles); + poles - if mdl==0 - t = 0:0.01:2; - u = zeros(size(t)); - x0 = [0.01 0 0]; + t = 0:0.01:2; + u = zeros(size(t)); + x0 = [0.01 0 0]; - sys = ss(A,B,C,D); + sys = ss(A,B,C,D); - [y,t,x] = lsim(sys,u,t,x0); - plot(t,y) - title('Open-Loop Response to Non-Zero Initial Condition') - xlabel('Time (sec)') - ylabel('Ball Position (m)') + [y,t,x] = lsim(sys,u,t,x0); + plot(t,y) + title('Open-Loop Response to Non-Zero Initial Condition') + xlabel('Time (sec)') + ylabel('Ball Position (m)') - p1 = -10 + 10i; p2 = -10 - 10i; p3 = -50; - K = place(A,B,[p1 p2 p3]); - sys_cl = ss(A-B*K,B,C,0); - lsim(sys_cl,u,t,x0); - xlabel('Time (sec)') - ylabel('Ball Position (m)') + p1 = -10 + 10i; p2 = -10 - 10i; p3 = -50; + K = place(A,B,[p1 p2 p3]); + sys_cl = ss(A-B*K,B,C,0); + lsim(sys_cl,u,t,x0); + xlabel('Time (sec)') + ylabel('Ball Position (m)') - p1 = -20 + 20i; p2 = -20 - 20i; p3 = -100; - P=[p1 p2 p3]; - K = place(A,B,P); - sys_cl = ss(A-B*K,B,C,0); - lsim(sys_cl,u,t,x0); - xlabel('Time (sec)') - ylabel('Ball Position (m)') - - t = 0:0.01:2; - u = 0.001*ones(size(t)); + t = 0:0.01:2; + u = 0.001*ones(size(t)); - sys_cl = ss(A-B*K,B,C,0); + sys_cl = ss(A-B*K,B,C,0); - lsim(sys_cl,u,t); - xlabel('Time (sec)') - ylabel('Ball Position (m)') - axis([0 2 -4E-6 0]) + lsim(sys_cl,u,t); + xlabel('Time (sec)') + ylabel('Ball Position (m)') + axis([0 2 -4E-6 0]) - V=-1./(Cm*(Am-Bm*K)^-1*Bm); %(from Lineare Regelsysteme2 (Glattfelder) page:173 ) - lsim(sys_cl,V*u,t) - title('Linear Simulation Results (with Nbar)') - xlabel('Time (sec)') - ylabel('Ball Position (m)') - axis([0 2 0 1.2*10^-3]) - else - x0 = [0 0 0 0]; - p1=-63+2.80i; - p2=-62+1.50i; - P=[p1 p1' p2 p2']; - P=[-4000+100j -4000-100j -500+10j -500-10j]; - K = place(A,B,P); - Ts=1/5000 - t = 0:Ts:2; - u = 0.001*ones(size(t)); - V=-1./(Cm*(Am-Bm*K)^-1*Bm); %(from Lineare Regelsysteme2 (Glattfelder) page:173 ) - sys_cl = ss(A-B*K,B*V,C,0); + V=-1./(Cm*(Am-Bm*K)^-1*Bm); %(from Lineare Regelsysteme2 (Glattfelder) page:173 ) + lsim(sys_cl,V*u,t) + title('Linear Simulation Results (with Nbar)') + xlabel('Time (sec)') + ylabel('Ball Position (m)') + axis([0 2 0 1.2*10^-3]) - lsim(sys_cl,u,t,x0); - xlabel('Time (sec)') - ylabel('Motor Position (m)') - axis([0 2 0 1.2E-3]) - end + op1 = -100; + op2 = -101; + op3 = -102; + OP=[op1,op2,op3]; + %OP=P*2; + L = place(A',C',OP)'; + At = [ A-B*K B*K + zeros(size(A)) A-L*C ]; + Bt = [ B*V + zeros(size(B)) ]; + Ct = [ C zeros(size(C)) ]; - if mdl==0 - op1 = -100; - op2 = -101; - op3 = -102; - OP=[op1,op2,op3]; - OP=P*2; - L = place(A',C',OP)'; - At = [ A-B*K B*K - zeros(size(A)) A-L*C ]; - Bt = [ B*V - zeros(size(B)) ]; - Ct = [ C zeros(size(C)) ]; + sys = ss(At,Bt,Ct,0); + %lsim(sys,zeros(size(t)),t,[x0 x0]); + lsim(sys,u,t,[x0 x0]); + title('Linear Simulation Results (with observer)') + xlabel('Time (sec)') + ylabel('Ball Position (m)') - sys = ss(At,Bt,Ct,0); - %lsim(sys,zeros(size(t)),t,[x0 x0]); - lsim(sys,u,t,[x0 x0]*0); - title('Linear Simulation Results (with observer)') - xlabel('Time (sec)') - ylabel('Ball Position (m)') - else - OP=P*2; - L = place(A',C',OP)'; - At = [ A-B*K B*K - zeros(size(A)) A-L*C ]; - Bt = [ B*V - zeros(size(B)) ]; - Ct = [ C zeros(size(C)) ]; + t = 0:1E-6:0.1; + x0 = [0.01 0.5 -5]; + [y,t,x] = lsim(sys,zeros(size(t)),t,[x0 x0]); - sys = ss(At,Bt,Ct,0); - lsim(sys,u,t,[x0 x0]*0); - title('Linear Simulation Results (with observer)') - xlabel('Time (sec)') - ylabel('Motor Position (m)') - axis([0 2 0 1.2E-3]) - end - - if mdl==0 - t = 0:1E-6:0.1; - x0 = [0.01 0.5 -5]; - [y,t,x] = lsim(sys,zeros(size(t)),t,[x0 x0]); + n = 3; + e = x(:,n+1:end); + x = x(:,1:n); + x_est = x - e; - n = 3; - e = x(:,n+1:end); - x = x(:,1:n); - x_est = x - e; + % Save state variables explicitly to aid in plotting + h = x(:,1); h_dot = x(:,2); i = x(:,3); + h_est = x_est(:,1); h_dot_est = x_est(:,2); i_est = x_est(:,3); - % Save state variables explicitly to aid in plotting - h = x(:,1); h_dot = x(:,2); i = x(:,3); - h_est = x_est(:,1); h_dot_est = x_est(:,2); i_est = x_est(:,3); - - plot(t,h,'-r',t,h_est,':r',t,h_dot,'-b',t,h_dot_est,':b',t,i,'-g',t,i_est,':g') - legend('h','h_{est}','hdot','hdot_{est}','i','i_{est}') - xlabel('Time (sec)') - end - - - %discrete - Ts=1/5000; % deltatau std. frq. is 5kHz - %The discrete system works with sampling >100Hz,. the bigger the frequency the better the result - %With sampling = 80Hz hte system already becomes instable. - ss_t=ss(At,Bt,Ct,Dt); - %figure();pzplot(ss_t) - ss_tz=c2d(ss_t,Ts); - %figure();pzplot(ss_tz) - [Atz,Btz,Ctz,Dtz]=ssdata(ss_tz); - - - [Apz,Bpz,Cpz,Dpz]=ssdata(c2d(ss(Ap,Bp,Cp,Dp),Ts)); - [Amz,Bmz,Cmz,Dmz]=ssdata(c2d(ss(Am,Bm,Cm,Dm),Ts)); - - - Ao=Am-L*Cm; - Bo=[Bm L]; - Co=K; - Do=zeros(size(Co,1),size(Bo,2)); - - [Aoz,Boz,Coz,Doz]=ssdata(c2d(ss(Ao,Bo,Co,Do),Ts)); - - - %state space controller - ssc=struct(); - for k=["Ts","At","Bt","Ct","Dt","Atz","Btz","Ctz","Dtz","Ap","Bp","Cp","Dp","Am","Bm","Cm","Dm","Ao","Bo","Co","Do","Apz","Bpz","Cpz","Dpz","Aoz","Boz","Coz","Doz","V","K","L"] - ssc=setfield(ssc,k,eval(k)); - end - mdlName='testObserverSim'; - open(mdlName); - - - + plot(t,h,'-r',t,h_est,':r',t,h_dot,'-b',t,h_dot_est,':b',t,i,'-g',t,i_est,':g') + legend('h','h_{est}','hdot','hdot_{est}','i','i_{est}') + xlabel('Time (sec)') + K,L,V end diff --git a/python/MXTuning.py b/python/MXTuning.py index e66fa84..ee889e2 100755 --- a/python/MXTuning.py +++ b/python/MXTuning.py @@ -95,31 +95,82 @@ class MXTuning(Tuning): # Hz -> kHz # rad/s -> rad/ms if mot==1: + #current loop 2nd order approx + #identification with matlab: k=1, w0=8725, d=0.75 + dc=.75 # daempfung =1 -> keine resonanz -> den1= np.poly1d([T1,1])**2 + wc=8725.*_N # rad/sec + #...but phase lag seems to have earlier effect -> reduce wc + wc*=.5 # rad/sec + fc=wc/2/np.pi # ca 1388Hz + Tc=1/wc + numc = np.poly1d([1.]) + denc = np.poly1d([Tc**2,2*Tc*dc,1]) + + + #simplified current loop 1st order approx + wd=2*np.pi*400. #45deg at 360Hz + Td=1/wd + numd = np.poly1d([1.]) + dend = np.poly1d([Td,1]) + + #force(idMeas)->position #identify matlab: k:0.671226 w0:134.705 damp:0.191004 - mag1=6 #10**(db_mag1/20) - db_mag1=20*np.log10(mag1)#dB - w1=50.*_N #rad/sec - f1=w1/2/np.pi; # ca. 6.5Hz - T1=1/w1 - d1=0.6 # daempfung =1 -> keine resonanz -> den1= np.poly1d([T1,1])**2 - num1=np.poly1d([mag1]) - den1 = np.poly1d([T1**2,2*T1*d1,1]) + magp=6 #10**(db_mag1/20) + db_magp=20*np.log10(magp)#dB + wp=50.*_N #rad/sec + fp=wp/2/np.pi; # ca. 6.5Hz + Tp=1/wp + dp=0.6 # daempfung =1 -> keine resonanz -> den1= np.poly1d([T1,1])**2 + nump=np.poly1d([magp]) + denp = np.poly1d([Tp**2,2*Tp*dp,1]) + #force->position (simplified) + #reiner integrator: 19.8Hz=0dB -> k=frq*2*pi=180 + numq=np.poly1d([(19.8*2*np.pi)**2]) + denq=np.poly1d([1,0,0]) - #reiner integrator: 19.8Hz=0dB -> k=30*2*pi=180 - num0=np.poly1d([(19.8*2*np.pi)**2]) - den0=np.poly1d([1,0,0]) + #force->velocity (simplified) + #reiner integrator: 19.8Hz=0dB -> k=frq*2*pi + numv=np.poly1d([19.8*2*np.pi]) + denv=np.poly1d([1,0]) #first resonance frequency - f2=np.array([197,199]) - d2=np.array([.02,.02])#daempfung - w2=f2*2*np.pi*_N #rad/sec - T2=1/w2 - num2 = np.poly1d([T2[0]**2,2*T2[0]*d2[0],1]) - den2 = np.poly1d([T2[1]**2,2*T2[1]*d2[1],1]) + f1=np.array([197,199]) + d1=np.array([.02,.02])#daempfung + w1=f1*2*np.pi*_N #rad/sec + T1=1/w1 + num1 = np.poly1d([T1[0]**2,2*T1[0]*d1[0],1]) + den1 = np.poly1d([T1[1]**2,2*T1[1]*d1[1],1]) + num=numc*nump*num1#*num3 + den=denc*denp*den1#*den3 + mdl= signal.lti(num, den) #num denum + #print(num);print(den);print(mdl) + + w=np.logspace(0, np.log10(2000), 1000)*2*np.pi + + mdlcp= signal.lti(numc*nump, denc*denp) + mdldp= signal.lti(numd*nump, dend*denp) + mdlp= signal.lti(nump, denp) + mdlq= signal.lti(numq, denq) + + bode(((mdl,'plant'), + (mdlcp,'mdl_cp'), + (mdldp,'mdl_dp'), + (mdlp,'mdl_p'), + (mdlq,'mdl_q'), + ),w) + + d={'num':num.coeffs,'numc':numc.coeffs,'numd':numd.coeffs,'nump':nump.coeffs,'numq':numq.coeffs,'numv':numv.coeffs,'num1':num1.coeffs, + 'den':den.coeffs,'denc':denc.coeffs,'dend':dend.coeffs,'denp':denp.coeffs,'denq':denq.coeffs,'denv':denv.coeffs,'den1':den1.coeffs} + fn=os.path.join(self.baseDir,'model%d.mat'%mot) + import scipy.io + scipy.io.savemat(fn, mdict=d) + print('save to matlab file:'+fn) + + elif mot==2: #current loop 2nd order approx #identification with matlab: k=1, w0=8725, d=0.75 dc=.75 # daempfung =1 -> keine resonanz -> den1= np.poly1d([T1,1])**2 @@ -139,52 +190,39 @@ class MXTuning(Tuning): dend = np.poly1d([Td,1]) - num=num1*num2*numc#*num3 - den=den1*den2*denc#*den3 - mdl= signal.lti(num, den) #num denum - #print(num);print(den);print(mdl) - - w=np.logspace(0, np.log10(2000), 1000)*2*np.pi - - mdl1c= signal.lti(num1*numc, den1*denc) - mdl1d= signal.lti(num1*numd, den1*dend) - mdl1= signal.lti(num1, den1) - mdl0= signal.lti(num0, den0) - - bode(((mdl,'plant'), - (mdl1c,'mdl1c'), - (mdl1d,'mdl1d'), - (mdl1,'mdl1'), - (mdl0,'mdl0'), - ),w) - - d={'num':num.coeffs,'num1':num1.coeffs,'num2':num2.coeffs,'numc':numc.coeffs,'num0':num0.coeffs,'numd':numd.coeffs, - 'den':den.coeffs,'den1':den1.coeffs,'den2':den2.coeffs,'denc':denc.coeffs,'den0':den0.coeffs,'dend':dend.coeffs} - fn=os.path.join(self.baseDir,'model%d.mat'%mot) - import scipy.io - scipy.io.savemat(fn, mdict=d) - print('save to matlab file:'+fn) - - elif mot==2: + #force(idMeas)->position #identify matlab: k:1.7282 w0:51.069 damp:0.327613 - mag1=12. #10**(db_mag1/20) - db_mag1=20*np.log10(mag1)#dB - w1=21.*_N #rad/sec - f1=w1/2/np.pi; # ca. 6.5Hz - T1=1/w1 - d1=0.4 # daempfung =1 -> keine resonanz -> den1= np.poly1d([T1,1])**2 - num1=np.poly1d([mag1]) - den1 = np.poly1d([T1**2,2*T1*d1,1]) - + magp=12. #10**(db_mag1/20) + db_magp=20*np.log10(magp)#dB + wp=21.*_N #rad/sec + fp=wp/2/np.pi; # ca. 6.5Hz + Tp=1/wp + dp=0.4 # daempfung =1 -> keine resonanz -> den1= np.poly1d([T1,1])**2 + nump=np.poly1d([magp]) + denp = np.poly1d([Tp**2,2*Tp*dp,1]) #reiner integrator: 11.84Hz=0dB -> k=30*2*pi=180 - num0=np.poly1d([(11.84*2*np.pi)**2]) - den0=np.poly1d([1,0,0]) + numq=np.poly1d([(11.84*2*np.pi)**2]) + denq=np.poly1d([1,0,0]) + + #force->velocity (simplified) + #reiner integrator: 19.8Hz=0dB -> k=frq*2*pi + numv=np.poly1d([19.8*2*np.pi]) + denv=np.poly1d([1,0]) #resonance frequency - f2=np.array([55.,61.]) - d2=np.array([.2,.2])#daempfung + f1=np.array([55.,61.]) + d1=np.array([.2,.2])#daempfung + w1=f1*2*np.pi #rad/sec + T1=1/w1 + num1 = np.poly1d([T1[0]**2,2*T1[0]*d1[0],1]) + den1 = np.poly1d([T1[1]**2,2*T1[1]*d1[1],1]) + + + #resonance frequency + f2=np.array([128,137]) + d2=np.array([.05,.05])#daempfung w2=f2*2*np.pi #rad/sec T2=1/w2 num2 = np.poly1d([T2[0]**2,2*T2[0]*d2[0],1]) @@ -192,8 +230,8 @@ class MXTuning(Tuning): #resonance frequency - f3=np.array([128,137]) - d3=np.array([.05,.05])#daempfung + f3=np.array([410,417]) + d3=np.array([.015,.015])#daempfung w3=f3*2*np.pi #rad/sec T3=1/w3 num3 = np.poly1d([T3[0]**2,2*T3[0]*d3[0],1]) @@ -201,64 +239,36 @@ class MXTuning(Tuning): #resonance frequency - f4=np.array([410,417]) - d4=np.array([.015,.015])#daempfung + f4=np.array([230,233]) + d4=np.array([.04,.04])#daempfung w4=f4*2*np.pi #rad/sec T4=1/w4 num4 = np.poly1d([T4[0]**2,2*T4[0]*d4[0],1]) den4 = np.poly1d([T4[1]**2,2*T4[1]*d4[1],1]) - #resonance frequency - f5=np.array([230,233]) - d5=np.array([.04,.04])#daempfung - w5=f5*2*np.pi #rad/sec - T5=1/w5 - num5 = np.poly1d([T5[0]**2,2*T5[0]*d5[0],1]) - den5 = np.poly1d([T5[1]**2,2*T5[1]*d5[1],1]) - - - #current loop 2nd order approx - #identification with matlab: k=1, w0=8725, d=0.75 - dc=.75 # daempfung =1 -> keine resonanz -> den1= np.poly1d([T1,1])**2 - wc=8725.*_N # rad/sec - #...but phase lag seems to have earlier effect -> reduce wc - wc*=.5 # rad/sec - fc=wc/2/np.pi # ca 1388Hz - Tc=1/wc - numc = np.poly1d([1.]) - denc = np.poly1d([Tc**2,2*Tc*dc,1]) - - - #simplified current loop 1st order approx - wd=2*np.pi*400. #45deg at 360Hz - Td=1/wd - numd = np.poly1d([1.]) - dend = np.poly1d([Td,1]) - - - num=num1*num2*num3*num4*num5*numc - den=den1*den2*den3*den4*den5*denc + num=numc*nump*num1*num2*num3*num4 + den=denc*denp*den1*den2*den3*den4 mdl= signal.lti(num, den) #num denum #print(num);print(den);print(mdl) w=np.logspace(0, np.log10(2000), 1000)*2*np.pi - mdl1c= signal.lti(num1*numc, den1*denc) - mdl1d= signal.lti(num1*numd, den1*dend) - mdl1= signal.lti(num1, den1) - mdl0= signal.lti(num0, den0) + mdlcp= signal.lti(numc*nump, denc*denp) + mdldp= signal.lti(numd*nump, dend*denp) + mdlp= signal.lti(nump, denp) + mdlq= signal.lti(numq, denq) bode(((mdl,'plant'), - (mdl1c,'mdl1c'), - (mdl1d,'mdl1d'), - (mdl1,'mdl1'), - (mdl0,'mdl0'), + (mdlcp,'mdlcp'), + (mdldp,'mdldp'), + (mdlp,'mdlp'), + (mdlq,'mdlq'), ),w) - d={'num':num.coeffs,'num1':num1.coeffs,'num2':num2.coeffs,'num3':num3.coeffs,'num4':num4.coeffs,'num5':num5.coeffs,'numc':numc.coeffs,'num0':num0.coeffs,'numd':numd.coeffs, - 'den':den.coeffs,'den1':den1.coeffs,'den2':den2.coeffs,'den3':den3.coeffs,'den4':den4.coeffs,'den5':den5.coeffs,'denc':denc.coeffs,'den0':den0.coeffs,'dend':dend.coeffs} + d={'num':num.coeffs,'numc':numc.coeffs,'numd':numd.coeffs,'nump':nump.coeffs,'numq':numq.coeffs,'numv':numv.coeffs,'num1':num1.coeffs,'num2':num2.coeffs,'num3':num3.coeffs,'num4':num4.coeffs, + 'den':den.coeffs,'denc':denc.coeffs,'dend':dend.coeffs,'denp':denp.coeffs,'denq':denq.coeffs,'denv':denv.coeffs,'den1':den1.coeffs,'den2':den2.coeffs,'den3':den3.coeffs,'den4':den4.coeffs} fn=os.path.join(self.baseDir,'model%d.mat'%mot) import scipy.io scipy.io.savemat(fn, mdict=d) @@ -357,7 +367,16 @@ class MXTuning(Tuning): prog+=''' //set output iqCmd={u}*{V}-{y}; //return iqCmd; - pshm->P[200{motid}]=iqCmd; + pshm->P[20{motid}0]=DesPos; + pshm->P[20{motid}1]=IqMeas; + pshm->P[20{motid}2]=ActVel; + pshm->P[20{motid}3]=ActPos; + pshm->P[20{motid}4]=_x[0]; + pshm->P[20{motid}5]=_x[1]; + pshm->P[20{motid}6]=_x[2]; + pshm->P[20{motid}7]=_x[3]; + pshm->P[20{motid}8]=iqCmd; + return pshm->ServoCtrl(Mptr); }} else @@ -723,7 +742,7 @@ EXPORT_SYMBOL(obsvr_servo_ctrl_{motid});'''.format(motid=motid) bm+=32 #amplitude linear (not dB) self.bode_plot(data, mode=bm, kwargs=meta) plt.show(block=False) - save_figs(fn) + #save_figs(fn) #wait_key() raw_input('press return') diff --git a/python/usr_code/Makefile b/python/usr_code/Makefile index aa76b5a..398d002 100644 --- a/python/usr_code/Makefile +++ b/python/usr_code/Makefile @@ -112,7 +112,7 @@ tags: Debug: all -../../matlab/ssc%.mat: ../../matlab/StateSpaceControlDesign.m ../../matlab/identifyFxFyStage.m +../../matlab/ssc%.mat: ../../matlab/StateSpaceControlDesign.m ../../matlab/identifyFxFyStage.m Makefile cd ../../matlab; /afs/psi.ch/sys/psi.x86_64_slp6/Programming/matlab/2018a/bin/matlab -nodisplay -nojvm -nosplash -nodesktop -r \ "clear;clear global;close all;\ mot=identifyFxFyStage(7);\