From 79cffdac4e8cdb1df4bedd2dd3ffaa6852099428 Mon Sep 17 00:00:00 2001 From: EmaMaker Date: Thu, 25 Apr 2024 21:19:13 +0200 Subject: [PATCH] initial velocity control loop Achieved with a PI controller checking the current velocity as output by the motors. Since I'm using stepper motors, the angular velocity is exact (if the motors are not skipping steps) and does not need encoders on the motors. TODO: add a little deadzone either on the setpoint or on the velocity, to prevent oscillations when around the equilibrium point. --- selfbalance-madgwick/imu.ino | 2 +- selfbalance-madgwick/selfbalance-madgwick.ino | 23 +++++++++++++++--- simulations/transfer_function.mlx | Bin 8217 -> 8213 bytes 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/selfbalance-madgwick/imu.ino b/selfbalance-madgwick/imu.ino index b64c472..0d3dd42 100644 --- a/selfbalance-madgwick/imu.ino +++ b/selfbalance-madgwick/imu.ino @@ -4,7 +4,7 @@ //#define PRINT_ANGLES //#define PRINT_QUAT -#define PERFORM_CALIBRATION //Comment to disable startup calibration +// #define PERFORM_CALIBRATION //Comment to disable startup calibration #define IMU_ADDRESS 0x68 //Change to the address of the IMU MPU6050 IMU; //Change to the name of any supported IMU! diff --git a/selfbalance-madgwick/selfbalance-madgwick.ino b/selfbalance-madgwick/selfbalance-madgwick.ino index 4eb0e2f..17394c3 100644 --- a/selfbalance-madgwick/selfbalance-madgwick.ino +++ b/selfbalance-madgwick/selfbalance-madgwick.ino @@ -22,18 +22,25 @@ constexpr double KP = 42; constexpr double KI = KP * 10.8852; constexpr double KD = 0.3; -// IMU little bit tilted -// TODO: Implement an outer control loop for angular velocity. But that requires encoders on the motors -// TODO: try to achieve it crudely by just using a PI controller on the velocity given by the PID balance controller +// PI constants for outer velocity control loop +constexpr double KP_vel = 0.0025; +constexpr double KI_vel = 0.0005; + float setpoint = 0.0; float output = 0; float input = 0; +float vel_setpoint = 0.0; +float vel_input = 0.0; + + float yaw{ 0 }, pitch{ 0 }, roll{ 0 }; QuickPID pitchCtrl(&input, &output, &setpoint, KP, KI, KD, pitchCtrl.pMode::pOnError, pitchCtrl.dMode::dOnMeas, pitchCtrl.iAwMode::iAwCondition, pitchCtrl.Action::reverse); +// TODO: a little deadzone when the input is almost 0, just to avoid unnecessary "nervous" control and eventual oscillations +QuickPID velCtrl(&output, &setpoint, &vel_setpoint, KP_vel, KI_vel, 0, velCtrl.pMode::pOnMeas, velCtrl.dMode::dOnMeas, velCtrl.iAwMode::iAwCondition, velCtrl.Action::direct); void setup() { Serial.begin(9600); @@ -57,6 +64,10 @@ void setup() { pitchCtrl.SetMode(pitchCtrl.Control::automatic); pitchCtrl.SetSampleTimeUs(1000); + velCtrl.SetOutputLimits(-0.15, 0.15); + velCtrl.SetMode(velCtrl.Control::automatic); + velCtrl.SetSampleTimeUs(10000); + digitalWrite(LED_BUILTIN, LOW); } @@ -117,13 +128,17 @@ uint32_t current_time = millis(), last_time = millis(); void loop() { update_imu(); + velCtrl.Compute(); + input = pitch; - //if(abs(input -setpoint) <= 0.01) input = setpoint; + + Serial.println(setpoint); // I also modified the ArduPID library to use compute as a boolean. If calculations were done, it returns true. If not enough time has elapsed, it returns false if(pitchCtrl.Compute()){ double tvelocity = output; + tvelocity = tvelocity / WHEEL_RADIUS * 180 / PI; frequency = tvelocity * 0.1125; half_period0 = 1000000 / (2*frequency); diff --git a/simulations/transfer_function.mlx b/simulations/transfer_function.mlx index c470831c1407305d9820ccb6da03f78b34edaa23..701fb552c13b5bd7016edb790f66618917acb596 100644 GIT binary patch delta 6023 zcmY*-byU<()cyjJ(kZnx(k0E3N=OUREJ!yA1Zbqt<2#06A(xS_6t=SaOzGfO?>NEvI8vB zvd_7#q0&zCCiz}o9i4bU#%DU5lwZ>|3CroMckM7zYMN|*C7d#cT-`n_UBkOvL}vEk za+_)coGK`$Y48i5s=9{uXbTvx@4#^QS6}q@<_VTszNt?>eF^5A~!|Vm~p1Uc%NVc`TR{R9bTw@ z^#sVXAr%m5x4!NsO-;UXde7~eAAya|cx{);i#XW^V*ZfQrij4Ag{+p`WD(UKvs~#S z2FWs(p=+&y5C>4cVSJD2cDXTT>gWm~qn=e6M{`lc8IE;;)N|)yXBz{to-f?Hloy%V zrRd4E2VPmp4yjG#DfmXf_iDWz#1_}Bj$5O7Q)6+OYF9gEpz@mnsN0~lNQI1X`?#i@ zkg|D0QCFGO2fSSj%poct!Ux~5aE7fj(8%XldQ6R1ITvzxv~>m1rLkjT-t_hx5k_fXEg!iF~vnDPlV*7GmyL0wgd(?HlKb#g@+;%7m7G` zuu{^jt-n>pzPEYZX+X>(jpuL2frAe1iG9d4%NI~3tH66lJU$S_l*Pzfo$(=py)%wP z9V&j4v`dh?C{~lcn$~&4uPlY^+>QU!`e|qPFkdUZqwLF~;pdLqlgCoEL&c_}sMmSy{RPeGx93fz{U7)Rx+2JNl&e?z5a&ODd`NTANqcqNU4qdL7Vf z<*j+Lw@7~=+X&5?EntZf`s?@Ygl7jq^hnNE=0g9OVw)epR~1_UN%ie`laTUFQ1tA% zjK&`J1X%Xw@d8qGTMYK@J|0(*p75X6ba#}E0#lwcq539IKTsslwnI}?D#*-jniP5;OHUU>lTlv%D0`bof-cmha=JhK!!{xF$q3 zgs)vp`uYM9Uvf0y9V$g-T0k$;#fu0K7^#&7V%#3fb8bcVKhde()95_{Uv9;yV(xwR z6z_j~`AMH1!|Y6gI%#u*TqKKfNY!8j2*(6Q>89gL;fb3$W}GvcqB9(rU>Y;~?z>#? zksy~t_l`!LhQ`E3$Fp0(i{tvcppai00jCd79EkJyqt9e>m~>5Q^@rEi+f3#Z z4&ujGT9!EPs8KO@;*b@_KxwJ}g@m zu2gsy>ARz-7ZmVP*~tm7ziu9Zu`>d9_w~#o#G0S%sh>AC&J72qlAYnX8R{9@t3s7> zbp;{g9AhC{I!#UKON^vWE*?DsG;QM}3RZbf+oD&Bg}r9F z_v%{bab4dH9rXhs(q#1wxl4$gm65GG9t~Kzs_JExiV?ss` zp=*qNIYXDU+*tgQm&cTu*Hqt8|QrANjq6y+Jtx1M4jiz!`7{Jq+~2`k^PS|7_^vEU{sJ!1aUl*IBC zRgfzf3uYeL;~!`M`7U!xzfD!Edy^P9Dq#k}IW9%Di1*U>xQdm|ZflO}2s4OkynCL% zno$#9*dD(Gu&kAYiD9nVKKVmgm~~xQ3Mh+zb+G)cDx5_#JvjYNU8`3loo?`h&k#-j z()gl-tFwZ+t*+nzi($-NU$8T z?%pKaY-GD!Msk~dE*gK*aPWbL_Znoy!gS1PZ!U5aPL>{MiH7(IME*8xZS0UOmO{j>8r|sdFy!X}&BoC3vO7tCLCG_Q;q5P$b@5h8ELJekb!;d z1##!agX1t&5!I3_&C_V;PrVR3>_IfoeNT)h_hjMf*Tz#Wn-==}RG}5^_6|Cp25Wr~ zhcgVzx!Rm%@|+YA2WpB!k)=2gzTnUspmb7GAhk38V z-IuIh>>2CPKg!B3Av`gCz;&wqlP8bUNT?)TE_kb`{jH#ayv2V8-&RfDV)FXYS%m|m zz?@GbwnF3qi}$;-=N`fjkP2oxoy5hZ6Tfcr9=|jSh9Kv;3I*QPVQ+uLiqp-yIcR6wV)TU0n z|4&Sh38^E72!CSU08f@|U7cb{`URA1$gljGlffVG{V-zF=Nli(qy}<-w0pNYZZ~5J zz0yUto5iyJ{J|hmLYX)ovurV&eUvs2B^E$VIdgVDAs!;z}#`_X{ zX=8hVx;LXge0>D7!d1dlDoki~we=_SbuPIS@%j9GUR2OW>E6%DB+Ox~V(@Ec8GlQ|#H zOr8_h7CBGDi@;y}mnL!=pe?#4K?X<$Hr2`=lawZqg+!4;A%AxP(eM zg^U>#J{;;g-9ojsttdp=%N|7%+>~BBfAc{7o*nT1d#eZnyZt*i%`qXDOusFRc`6!; zV+|%=cH?69i>A*Ws*Qg{vnm58eYcZGBj(Ova|+q!+wH4S7!nSgBcK}*{renE#9Gxx zs%>uNx{k%khxY`%`uqKkytX4Vk<$ifkJx55pPh$stHd%j9F#r0cQH_9U=s;BBBC-G z`2n0^Om0C~?1Zb8uD)>VAI4c~J=s?Wz0$hQtnm82k=FX5<_n6J%S@F+R*~e%2D`tp3>^nPF$C;~?WkFJAOZXd# z%E{jmv3H$qf^{2F?fKCUf~?~;*aPd^sDQshc;H6d(pRO?ny(?OWV?0&!zm-BN~1yC zuS0t{CJGoOIVKR!h5cHe!DPFiw}(n;ma3rrf*;mr8w@_(JH5zqvq#1$$c`vY-th#$ zRBu-poC8{@lkDBGK|>x;+F5ZA$dH?7zm+t7-SiKlrb@n!4KtTEE75K#hj0~O8hDQ~ z_5qDXrO|W6P#pYjIsyCxbsM98Rx-XHGe~r?HW&ZIJhH-uvSL;8j^dPkMM4~Dkd0kE z@08K2f)-L{>;0fXK=>$OR2ap?RxTCW zdIW_;vYl3rMrqu*`RteQN@Tf6oW1@rpEo%QJ6F9OJ@#WL+V|ZNB0o=30s zZI2pYbAFyWWYg$jaAyNq($B{ay06c=bfDR4^_8F2uoKA2wCf0cyr-~b`T1DQq2=}3 zrDeE47-~Hw0>uXs3vemMfo*xD@EqHzLxfgM`uop^Uk%yGc6}ri5mHteOoc=}`q9xg zAUv3;enw>cY{Xho(3v(BhM=!RrAbzTq*vM#wh5`ifhR@2QOX4yqu!>~i?>a>5H8_k zd5AK`eOgW+2TT(g_y9gG=_vX62zhq>CgNY!{hy+%_^;~9Uw&UWXdgYQ-TWo;ji)a6 zkAOek63@BGxVNNwU|JUH_kF)grMXjDt42WHZX$!3Xm9Dt>|5+WA=2e)bKlhRHk%mM zNkWb?0-GUmLveyBld=HcD(sqbC0W|Yz52G_8(~*EpsqBOhF?{bvhepnV#&FzFHkqm zT0j0V^rx@~5XyTse+KI)M9s*z8Ve_z6<9|fQb<{kPo8Otjzm7iqt1k=U?gN7cK&VFBX0VlwfGq7Q_Q^;l1S~i&^I9(*HDJeAc6pXE(=w;aJnUFzW~@v zwv@v9NFuikA)PNtP32z;q=lj)>3_>}_FFGGmEkyYtuXq7Tq1V|N%7;mR*|IX`zd8K z;7ugN?2gdeXYcw|k|mqIVgfBz*~i#6jN-Vx zc#`4~;Wi1$OdPu1mrNN$FI6!izH-J|GOL@3FlRS#%$FJvqa z6QwD;Nz_{v5=Qr7_3~6{Oqlft-<3?Q(BKCuNw>1iSDaM(NwWucbav^5scb`c)USkR zvMPwedc#xrt6Gu=YP@yo|(HPbC85NDKN9HNtu9yZ z_T=i_PU)biqm^olKykoe)8AMW_A3TNqPdZ;V<5Dpy&|{0f%FDdc;SP%j#(mCVHHzk zw_`Wju0EPPdpze~{}&y#DUE%!9B0q(%NR_&fx&Gy}P4^$a+ZGWb#iv#$nAk!mcK;lU$?xZ=3f`Fyw=NKH_N3Z=)9 zZIqiLJ*ID%qkxr8FGZM8N~n~aa7K+|%a4Wte^R%w$erP?H4FUysfdwKE^*GPEB!9l zdNX&L4ChBjsatPjr`fx{V#PG4c0S4uefcjeJg`;!+JS0lb&cBbw;AF)gV8K?Vo$%wY+FyB5t`y8b%@*56-Gves7H+F z6`z-yg;+p9==xRk(W4mg4S%K^7nT)4snZXD{y z@=WoX_;kzf+*u-zEJX7^E3!q#yHs5#sVK7Oo!@~5SJ$Z>r8Xx%50NSGh&?MD*wW^q zZ8kRBNDP%t|3`?`at@~D(cn&4p0TIK8CQgj6TwE3jax8RPJKatQ?brLkhSDb{KHRG znBa7$8m!T+A3p$WiGr6NeDnW|CiI*M%+d50EJ$liK2W;(n6PH7`*=94d^ALRiEr9x z=}r^xj0brGcKEpp*LRFt*I7&@jisb?yW$|vSSQuU9p~}h&40u665plIyp|>x0i9rk zs%~3cac-hNiYL###o9Sfh8BJ*8{V!up9Wjg@9J03%g9n*M`;LON&jEvLk3AEP78~s zWCMoOd-w?DFRyWiZhNb5HLDXnQ1)<`nL}ij41U$sN6`)m7vB3XjzyqCtLl!@3QiWy zn`gLhK-)St%BrW>0zk~Ukui&E<}3A_D#Z2`;Rc)Pp>6MTD<;LF zK<`6(Tjmesf?rm!N7$~d>v1rj>OKFpicZ^U2atgiKQHz4IlP=PPuZZ6N80y$ z`r``sFU8g7z7;=}vdSFcEwqvSmUy>Yqb!9|J46+Y_PSCYoI6vBlVHw8Tfici zZQ64M6FkA4;&*Yme*}{lnQU`mSB5kS$&RmfY%$SjnQoSnodrl--rUb$m36oYPwtk< zu9rnmreU@U8SEaJTex^p{dj727@bKten=beag?f#XZ*ys0ybiw>SYyNndFhh()OK- z;ta%QS30#)=YdX+#bNB-tmTV$gw z;NuvMj;Z%8E3jibLDnUYm)!Vlw^b}OL;x&6M z_pJXa9Vi};4+i@3K8hg-&5v^1b1 z=Rc~Hb`p_!X>i)!JtywxOGHJ zMo`7_b81J4O$hNeE?&R>;gj2w2ZVAdHo+S`3JGD)@`wHN-jd8{%l}fKtdIY}X;!}N z7f(E2;4X4v6mIVD=r=x^=Wttm_ZhduL%*>Ye~nFa4)8@v%WJ`v6gWPPkPZ>-aOm2T zrGjDi4>;PES92^6B?Z^9um{nekG`TLXnsp~CyIV)lfiCG*Uplke5W8LZ)z!wMXVbP ze)hf@cFHyek#6#`CPwpzoLQv=EQO3g_Iw_VjLrI02mreLO$|%{p`MlCsi>0%Mv;L8 z3##Z<#ppA&;e0p;z<8?Bj$yDC0i;J2ARHGl2^=!vjb~P?N@} z)-8GGZ;=E{((ePhEDIfNLsIy1tsB_#%2LZ_0tt%`MtNTtBspBRE2pkLTgnX@Grfba zb7VRhXmuRi`MDBjp|*_gxa4f035xwm zljO5SuEz|>H=Qj^P2&d{g)leR9Fj~E4M0#f)o=r_w)+-cQ)xvp)to~0@|XhU8aA$U zXK8QLg5Z&f%mH?6!WQ`i5o25#vZCqo(B@R|aYUi=4en*RBs>aemrmPTLy`&cy9!E# z;c7C`jt(XGqpc!+oyq&*t=~gURyf3bUU~!%lqOcQ~*8JotZ_RUp0 zYTs??EGaW$yESFLDn05mXTwzHN?n%UEPh$Rs3_Cx$7^-PH&xN8={u!p_cOt>rE}eZ z{PcHXn~yB3L}^#a&84Hg1;=V58TrcQWAVI=?zWz#U+!;g`JWyUWGFZG^JdeK#EtRw z*XT*`Zw0tdw()p|6{f9vkC@pj_QG~tXC~ri7Mtg{xOa~3HQNWX#CO%K#={-!;)|l7kM}-bR+Vl-N1v;$)NSULNlj`=sD<*hzChgX z7C|)UHW!Jn?E@l0K{#z{C%pp0xOcpLcVB|`SIfDB1kSvS?*Oa)O?#g6)e^`VVs`tk zr%m;f-xY>W@b$N|lpq}e^%rV&GSRn7m5Z}VyLQRY0k4!H4z*G1q{70mkOje;Zwl3mBVsQ=b zOpn?$Ddk_|<^sF^Z3aO*MZM4If`h6q7a>naVDyjhW4;jX^-&k_C`f4@L5to*ydopr zmTg}!JnArP&c&TX(fXP^sxJwdZo$p`;5HmOw)1>yaF@)vB>3V=%$6gZdMOGYktjsxJLIeQst@GlyKUxAWgE+XkNc@};{9(F>LB^zwvhMKR``bG2O zwozC{aABZTCEs0XteEly1qCF}Pw0qm$UWc_O}!_*4pqXa!DjvfRNSO~P5P4i0ol?4WU#pml*Z5p(9NQDuko!l0|FR!Jf-|ootpm`tCM&Po% z(T`XDjMH;tdX?AgoFA723z)!P)%I1TdW@(?=cCiOtdjS?1cAVyp@> zEls2+Xy52qP{Nc+p?)QDJ>$ZN>#TGg>vu=0y=?=p zD^hJ9ysdUupkxI!Rt|L60Fu?+cnS%3fWE&XB(Er}LMrp6&rQCwToNY=KV2?mF?S9a z{1Psora&=Oi`U$;gq4yS+kz^7H{bZkF>2*4sPj3VP_O4kPwQHwqbXe%7NNJzYu25U zUC%dukqN&ZB5PvI;(EsCC(Ug{KeAlujlW{6Z8h=>)DL!rey*JI_vVkT;P|{y3#9z# zTgTOEUb|5#ZbC~%npd_A<>%wpflb5nL=rE$NY%Z=>u$|Du# zkYUd)bkF@bP1iL=^YvP$yc=>bZ6@`bpnA@B6TVlR`B zTLg}`<|KH@?UuzyDSwa&<6syOZcjA*Xl-)QsCSI&~hvC$n8Ld^@8 z%q^$v<0bLCz!eDDqsz||u(fuwAiwB_aop}ym(UkH^7Q>PhQ>LdNSG+1Jo_sO-R9vl zw#9Y<7~t(A3>0t6UV6b$WBc0)ryY;ig_Uz<_FndXGI+zVF=qL?^L5OvC647tOj2Nr z*NR2nm7jzP?p@}GK=x#2&JJlo}}dAJPO8A5LwpgZI03|{-W^Ssvm!U#-AlF9nQ zddEMxd7+TWaZurxKKzbi(&76EHtL7U#Dps!x_|?{tlkX|@x2RwYP~{?JiKNd$4D$% zOD`@6Q#>D+?SkK>cMAJs5iDkK&T+JsRM}|Fo%RNDqG?5Kf-ph{5C)RXT=RVCS0moN z83d%jvjpFZl4sOi`MC!dfc1zzuO!tNA|F>iN0C}jNwcZNh?SwVzl`A`fpn`da88cs zg2nW5M@6EdR1G2h8B47(Mq`2 z!(~Zk%TK60N_^5{4;g5s<O}4~w|q(p4Buhz7Xm%eTXE53zq^f{P5!@|;_0k9I^%R#ssVJM}v^ z@2&Q~g)?9bNF6FV`wolkMra2qlwqV^*Zmxas%{!Wn?G))y>B$|Ni*aQ?rhf2#9J-* z797xwcZ5dil05@?2--EA-3c%00Q;XtboXz0WG3yRx5m3*RV>R?&G{_ilNiHbYj+6?>jppd~8~C?YN%^ivsi zwoE+aHL9Kg4;~vJy$JQmTX%MJI#c)f!Z!P9qu;REo;{Gxq?)xQM5|Y@ug>*b)>@1+ z2wxzDJZ>lFr!2u;X*%M5(3-Xtkk{hUhrCdL{j#Xh+{T-&X-Uq*q~EOM8$R7th@XRf zFL|Im)t!S{z70jvk6|3(JeJ_52mDnpF~>#^N1c6i>@tHBrJa~_XPAT=saL2d@BgZ> zXkID)BaYG_xvGrd|;JDDN#NsQH%cq&=9ddJ*Fk$HU z#zRorcC%Qu_m*$k`VO)`NZP(4j@jMJBB&R=;M?yW9ymdeeqRnOW9DMviKMyiwl}~e z+RGUwV{wLv9cvH_NYVhH(M|@u(nVr2%x8@L`iFZ>x7`P}R3|}UiL%Rjd`g6uRhd#e z*)qJCVel<-arod$nDb-Q1vm8vb9vBAwPM5liQ%$nFF(e2j=3X=dA^V?2@by}6I(Kf zM5+BBj%;1t8|ru{w^?W-N} z0H59%*HNq_#3sD(+>rS8dQejif<7sk*tX6d@=t@`_Zx+qtm_&BnE?#R+H)FQ>BFwu zwU@)b%{4E{S&FVGiu#Xa9;ijzt8s}5;4|A1n5}s0|Y%sFNB}pv?c6lDG8Xi?owQL?iX-@5e&P&u} zJ5aZh3z~~XJ%1z~UunO+{#N=s&i|2xw5Po{^&8X1n+l}-#kbktt;74Z>qEljT=lu< zObDELu2a)dA4!eC#B9*FU4L%n>0_|X7eL-&6vaZmV{&6r9@krV=ytijyJ&rrL-x*C zadECPW)e>aJ87JZMYknz`{P3PQ1U#5Cx%0^*57Z(X{C*!D?-JW^LO;{a@s@vNpy~7 zj7pSSv>^%qz?i*^r9QXxi#C*fRlQH%IcbG8ms6D*;w^=(MUb3IwnNw4^kUNJB^C`9 zLD7|arA1hBkus%)Fm7GeyARiwfv>Pb!>0$uonLP{*-oB*Bh+lTGS=b?bE$hh(>)?o z_hil%puPHfe&!;NJa*XNULT`>SLM6XV;qC$8@198RetKF1u}hV&vo4eYXc#`dzX4v3gFSbvWzK;p*)MkzOt4uD zKjqIei9;&6NR0&%Z1HFZIoggm-O*B{UB&xZJ2&NcsN!Il3fpl=f(}W?Le#MyW1r*` zf~_RgrLC93l=V?EF_*$3TucIxm^bCIV};OJqF)1=)xtgv@UZ-M=e*x510MtX)NTZT zci%_7SiolAkxApj9{r|bulL&sCJF?hy(HP2uWOob55vc}DA!J|tz8loHhN?@5zj^q zrrX(XFxP{$xbCBIsab`@|JY?c-NxzT`$L;J=w0T7&FGP5BU0{ab6j2>XmC8#=Pbd6 z2RlN`+rr2TUhV&wEd0K4C4)GU2l);FH!UN=g?!YQCaWQwFJ9G0vl3R5)_=x6yQP*j5;_JXCgWNig+&06481S~J z^@yWe%dT56y*=6bhP*|UjM{%qLP&{K8IwQ+gFPJeS_R0k;1Go_X zO)_WvU%5!ixTv$$H%visws~-xPWQmNG^h$9a0CcK1lFU8tcX=M=uXk3N&k!ShCrl{C#il-rpcPM+lp%yv8yFTP{P-=GSq#afSq!o>mKV`8H0XL!0}HY3>95> ze=~K&lk5v=?G-GRxpAi2FG1c8hZ@2mQYqw&v>My{FlhG&K$a1s+NMsSb%T#= zU;HIpEyu#NKZkcfdp5vA^E$H+aowMwe5PP1YrIZ9!0w9 z(QE%9?IQqar#sWHh?omD#V6_bso|IajXz3?xz%IDW8xctkAp z!E?KKEjkAxEgJB*agni@XH#bnD_)qubU@!n=5&r&V^1EX-ZVw;* z{xY`PNVl~{!7%G{-}%9wJGw;N*Lh(RaeRj8U)Mj|!YWS=tK?DFiItAYOKmPY)UAuI z;_=McO}hwtOK{L`QD0u5^Y|$)At{{4{2O*;sIvG|wFuz_I)x|-I;}a@ke`gkr1~C^ zoX^a_+Zf>mjNVb045jaa=Wws-W7KpMX`lXp%8BcZ@=pRxN$Af7@+__6LH2DQ+#wBm zG{sDq-u`n5R{kb$&rjVRYTW@Vo%bm%bBmH1W^o@E7F9r%%;9}u+4zKWaxtM{fqQVZ zoDL!Dx}el~^HI2Uq1A4Kvkar~62VZbbCNF=UkKlf0ZRbxYU!yHn@`y(B4< ziIHdjtt^=gLR+^^>x}h(BymFq5xW1?mH03~f90ir2jiceNUmUDq5HSszW@y;2t@b) sc%S6o3`&^&w8