From 847d67437334ccb1c7e2cc697c3dfa7cb898c6c5 Mon Sep 17 00:00:00 2001 From: Richard Eoin Meadows Date: Wed, 27 Aug 2014 19:57:03 +0100 Subject: [PATCH] CW output on a version fitted with a 16MHz crystal --- SAM_D20_Revision_Letter.JPG | Bin 0 -> 61463 bytes firmware/inc/gps.h | 43 +++ firmware/inc/hw_config.h | 47 ++-- firmware/inc/si4060.h | 221 +++++++++++++++ firmware/inc/si406x.h | 31 +++ firmware/inc/si406x_defs.h | 161 +++++++++++ firmware/inc/spi_bitbang.h | 33 +++ firmware/inc/system/conf_clocks.h | 2 +- firmware/inc/tc/tc_driver.h | 22 +- firmware/inc/timepulse.h | 31 +++ firmware/src/gps.c | 133 +++++++-- firmware/src/main.c | 112 ++++++-- firmware/src/si4060.c | 443 ++++++++++++++++++++++++++++++ firmware/src/si406x.c | 348 +++++++++++++++++++++++ firmware/src/spi_bitbang.c | 104 +++++++ firmware/src/system/clock.c | 4 +- firmware/src/tc/tc_driver.c | 19 +- firmware/src/timepulse.c | 215 +++++++++++++++ 18 files changed, 1892 insertions(+), 77 deletions(-) create mode 100644 SAM_D20_Revision_Letter.JPG create mode 100644 firmware/inc/si4060.h create mode 100644 firmware/inc/si406x.h create mode 100644 firmware/inc/si406x_defs.h create mode 100644 firmware/inc/spi_bitbang.h create mode 100644 firmware/inc/timepulse.h create mode 100644 firmware/src/si4060.c create mode 100644 firmware/src/si406x.c create mode 100644 firmware/src/spi_bitbang.c create mode 100644 firmware/src/timepulse.c diff --git a/SAM_D20_Revision_Letter.JPG b/SAM_D20_Revision_Letter.JPG new file mode 100644 index 0000000000000000000000000000000000000000..0db136cc1374e999e14592b2ba83bea836ed5a25 GIT binary patch literal 61463 zcmeFZ1z1%<+cvs3o0bmg?(XiEF6ofkgmft#igb53(hUj<2Hi-A5-N?LsI(w))<%ps zzVCehx&G@q_0DqE-1EfDGtbPLS!-tF#oLQT07pepSrLFhAixdq0xp&)`V@Q|Yym)7 znH4|<0N@&c4M6~qL6i(e91vrJX(kx)AW#4TrtnA#LAp#UKumUp8$ir_C4&HB9#Hoh zn6`mY48(9fc)&;aSq@w#^WGo2Lj>ZbOwXkh0LWRoxVrqjQ!8ldQVR%h%L8zuFQtB^ zO3Lb5)V!R+oPq#A4rJf^X%{J2c-|GJ1u<>pAKRI(a3b8GL=*rKOt^V@c_2wYF$8X3 zGWumLte`GJ^3O7PL5z4s7X>j&@}G8~U11!sEc7dRSx_b*83j-z0{|5eCnh5RPWRtFx(B$zv#$+VYnQe&kP4(fMdV{=@$yQ%>T+=r3|1H6yO1K7@%w6 zZH9Z;1-wD8@Es5%f|wSh2Z0zFl!A}4E)b)Dm>9&9AVvc*HHf!Bi~(xEYy0+`A_EhY zf)7!2&=8y}x*Gxzf`3&$IymeIujrskAqhx_>%t==h`|m3@IaaSe`J6ZkWLEHJwSRI zSg#_8AA&d?#PDAI4B`xUy`U|pAkMt1>l8FR3ncP_GKipHAxOsp@d${kKn#D9K7zOf z#J_BK^;f-%ARQN!*#~7H(ojGYY}0j+fC^&xXo&_#584$@1Tl{EPoJcN7*`qvI0a=M zff(Rr))YW2}>sr{0X2f-q+ z3YJlGNrKmj3SK$C1i%z-11JSb=v<=jSM~g|;c~wCYo<_i@Nm9s=>enGhk1B9xVljD zaB_2s0DuUzHBpMn!9 z7=F3xHwPqPz%y!v;3&PI|4GLf8G#GJpB{KH{F(e}*kw+O;1}^H`Iq0}dcPuE;-@cu z%7$L$f5~1}{F4O^E7x1U;Qy5rK3e{(0IyFPv^NA3Tn|tIzz%E}{E2S{m0p2y@h>BP z<@0)XE-X;;5jCLe5F^MspaI>0oQ}l&LwkO;;~(lpK!;Drzf1htl>+?C+&nCBxqs}_ zXFwSKGhhUaSDpvrK^dE?2$x3zA7prd(_mLA*o4a!@K^e~GLZgXg;@xI2q5PFcW(L@ z)&Do@{M{#i5B9ImYTv^nypex9;cq%PBz}j#>A#5}_@B_>_5QOQylglfp8j1AnEiJT zC7k?k>7fAp`ZaU?no%yr|NRZ@^~>wubN^8PA0p>I_e~exhrfLE&nd_P*Pow(l@@$1 zT7TvK|Ckm4D1aMa0X7{Cyrc<2FyZm%7-|2q`?CQ zP;~ju)&^Ei3oH_JV8UN3Gr_vv5IhFoWdAXMmBBw>5q%(d2=Eje!T(Zw1K}m~0Pz@c z1ab|r1EmXP4cZKeM2te@2VHh?0R{J0kU_&Pkjt_2v6Inwq1Pa&5C(9KID=k5b&=hX zBd$eX<3u<|*2BJq&4xw?JqJ#}chz#>HS{eM5!n~n{@UGZEC``!@H(#}S6u&tg3->Q z5JWRJfcRvKGOIOAP}gLo%sL)QjXsM2rmQI--SOWWhHV5Z|LVWBt+iS#lea z4)Gt_{}g?_^bIy*EB5~sHJ;ywL_kwnht9nrc2FFq{qsl&FVu;e`l0}GfnZ3cckvX` zhu}u!elZNGMpz_FxOfkVN6;d8e(@b*ieQR&bU}f@hG37w1t=i^2yd}002hQgNDXE- zP>9eB3Becw1`(1VZP)gJ6NEs>H*`x#AXEinfwm99L8XCzQ2x9wUH7N6IWF_~0L_(4 zaloAbJb(fq0f+!HFj9cK24ny$Kmg!h?h%jy*Fj7LUc{gdB|r`CkHE(q__cj`rw14S zHt=EuISgRGvVuR`5=DDFcAozk>QDpa6co5ikOhpoRh<0#Zc4ofI(;^MX4P zRA4P|EqOo?-~yCEiU4?*1!MpXfbX&l#>@LHz!Go)>;Qd`s|#p?Uz$dM8DIkLov8!b zziatvFFESC-} zkrDJR9q3JF?WZ+N~hw0Ck%Vk0Tx&6lj|M9?o zJn-8CLB{~NT1>(x$DbECtNzKsR~V*CMlVQ31?Navzyt6BlECfW3ZVXXDgQGd2Clnz zfkf~`pcJV5pQ-$d)UUm$e_{NG@W18(9It}Rm%~3PD)jR7 z>gGS}&4Y6AMN;xILPNj=TwZ?%QcWNV&hFp`bir2z_&F*369fQsM1(=~=bR9(aj}H_ z$llA#O_Ynv#gh{}*|36fTDv-P`B=JffqUg#fVh;8o29iQ%!}FzX6N7{!Swa%b0%sB z8wn-@0d;P5H#wNS!wo+Vn6973O=~|#YY`hJDM@s3A5kA?H)oiaCAE*UlZ&UQj|9_Y z;i4dhlew5cArBi{Q5|{3D-kd!!E|NI-Me==@A7fFdf0LCh=_=Aar1KV@^XL@9G<=| zUY0%_E}nE?3Fy?9Wyr%ktvwvvyx?awKdXa*N7KIx`-g_XIhQR1)v2k);aO32OGlXG zgfP)ii3-tq?{`Un1R_` zmGLk9-(|_Uy7^xENzz2a)dglo&8!7l;cDk$>1Ge}V4>!q{zX@?aqx2WV7V&skKXU1 z6nxxVJ-t0(HyxZ|l001e+|-^hYtAeAe_;MrQPl8uwt{&`+H!IKZtpMJZ=qkRS{`t- zoF&yQUA!%w{;Aww%->qdx|+IfFqn-FI3!%`JmJ3jIbwBT?w6ULIVT56es0j!@aF-< zmr|FRE3vC;{!M8=$FBl7h_B8nCE@t@!6h#zFVAlq|H%BdPtMiZ4RpARm#3Voi>-s5 zx5v-1`(N8>L@j4UE$2ba@qcRVe^d5Pt99+Yy}$$2O9$y&I(fq+1vvSHsTK68nZb_Z zx$L-KJ^in;zuO?=2J`R&d;Dh+ygd9u{K9;K{K8l5{zvw2tzZ2q>tN~mQxoibZm{y- z?)o{7{_uaRs>6?g!R}NCpM6O=(0Lv(j=M1UV-B;Sw!8!Lu(X3+mGTdwzYEZE@PRoQ z%7Y`+1%A9N$P?~VLvIJh8yO9R*M1q96eUV>mQ z;JV`hUtlig0XYDQfB^po21M{55-JiBA|etx3JNkR208}DHS}xOFtPA(FtKp4u3f{q zj)RL&KuAc4flWkwoq!mRfRF%Q2m}i1AR?h5A)ygqUc)5#Uk(>t04^#d9nt}X-~tG^ z5GXF>q6faK13`lSTIzRzLJ$yPG9s}TIUc=$O zx23EPwTe`+AvGj4TxQ`Nddy$1ZDKTPXz#vPJ0>c2@FRk$d8SRwjFXK#_xh)n@%8v~ z)w_F1n{4h8=ymv$_;b%5#c$UZir}S@e1r;z4Rac|aWU7ba{5&r;i9GMCAT4Vc`{c< zhpo3|F=*(&)E6(yu&IIE+)WgTex>)&M%Sn90Jl4@?tR;tWJ!dqy4`xIkeROR3e6+l z=Hu)q()^M8k*2!EbTf62VmC{^`CvILCq|BG*z2j$vZbURsH~Hqu*y!Lg)Pe{WbbL2 zDs#$~5}+i-h)*CuM`{Q3Wn^iWp6+?Q0_sZshoD#SWpk-aM{)C~)-L$T-r|K|lVHBYElCD&Ou$2mVsB^{1R?_+K?{ z#cfQ*sv!^~^Y7ePL?oEdrOPbsC}!1G@=a_aSW@&*(7{(M7G`Zn@0eD~8DgJ$K++vS zW73P;#lJ+NJo5E=AdTa=mDMz2k*IZbe_b;+7UIx7UY18+>nHu^lUV^sEPf6%rT`8S zi-5EI1pjTQ!%#jA^hFU(occYB-1&$_)DBDpR63vcA<*9S$Oy#B?vj=DJNX5w58QkS z(jsT#YF7=sm>$LXk}#FZh7YWt*||J5nTj8kw^qavW~S~y#j;3TCCs--Ty*1>CzG~V z$HhQ$Kv`u*#6U-7Oz8H(&_qDBW|nl{BJn-@8mzQlhb_kPNak+Cyi0lg9lu-b>wCLI za?VwqLNkXmD@wy9hLy+lG$vM*Oi;s_M2wuo>o=rd#<5`u#?t!aQbO*bq)6vyA`VFd zST`0JQD3}_L(fW`CGWfd%z`{tW7iX3?qjOg`R}r)C+)1TTdJ`Qo#?;L7(wz9zl-`x zwWv%+DOBq)yPq~-Cf(dGZ8~+oiq*KAab|(}qYt%j8IlZ2bKhpAmIJ4CXw-LeY^aRP zU`zI?mCJ3+wj)~iDM90a$4j&Q(sia{$eHhs9jpdC_2;&iJ`6$xZWG=^ptewvk7yYx zj3vZ-eQ(BPS-z9K0iBPXzO?vB+QRGHwpy*$$3xBf72fHOyad1(a)OX}bbO`BhH7+H z?M&^|c#aRUo%#6!xz76=T{LC#}LO{K5|Na%JXsx-|>Dwxes%HL2`zi|H=!?0$~X z=3<-OJ8UL8k%^f_(bgZ{PwJ%v@*XZ~;Sdn>^7A+_e&+Q`Eu5TC=&4NORPoBzsAe7e zSQ@<|+V_b9DmK(k|2nP^6|<3zxMO{Gny*WXn7|1MSMimH7Iy^AZL0-2I?Gnq>$#dl z`>Z={RqvY)Nhj7S)ePM0;+$W+A{Hi0UUS{+@Mf(}5h@%jYOgEa5UZuM3k3rRjsalT-D8BtQOU8^ka-sB`j#iXD@Ei(lWZ`Sv!^+yIn0@ z`4M$lz>3|x>We0Wf~!MLC3-EBSxTRMtH_M5d@TTzZFyzSY#1?-X(s-}FMEa3n^Ip> z<6Eb6eaCihA1|lp>V(D#=9#eihLV9Y$=y8TZa3S*=$B4bO*7$SXuMIMaZ_6ZF<%l^ zqNSVOQMFXanz+?H-YV`Cp;y{h)-pONYvGo1d zmtDTEYdl5>G5t8JVsp0z_(~9f7a`QbNC_6nKK$ebVfXC$$wdGhnKpSm9_dIZdS9Uh z@(p!eX)Pe14!CC$C`A^&?r-=uKj>K9*ddPegW;QukiF4dPYn&u{50K0+HMsA0ht%< z9>jfxvDTRMNq{>s2(0LC-QXrOhqR|RE&70oVzkSJtzwDFnVC2&-NbqG5Sb& zilA8&pI0LBROZrVGjoNoibi&5OYt{qYGG7N%L$#wprb-*n(wUTX4bN-t@VXSQD(CLiQYEj0Eg zu)sqtxhW9Igefy1pZy{1=6UkCvFSP?O~-M(q#~Dj+jybMvlnm1RsuSRIs8i~@;FbH znCWu$i|^E_{lh|+hoh28NrTiJm(-H?tx1}W2h z9DNKIxtE5Min=}ATJ|E5VY2r>;)zc%lvoqCt)h33LgkFL3z@^v!!bV#hmID6F(QuW z#qF4E`&aJP4ZiN(y#Oe^X9Q;VszoP-2OpL>LyW8$U`$9SRtjS5T@wWCVbeu%5bIul zny#p%IqJUGrv^zfx=)8b&U&%hhQDaC@)F{iphm_b3xzt~moaW_ z$B{?reE7a2!y9dCp~ReWLrmVfHBP)^-k6!$sU?Hg@ImSV zLgF>??H_4o9-gSY&cAtZbFMFFDTJGwj<_q~BfHJ+s_C0PV)6h}?|nP(LsZ8@#q(gK z#oa0tii6va4=nZGV2j98Kgyoi(9SBDVH^&X|E@RVMBS%dl9Am?Qq-_ym#2z;I|p~7 zrKaJYN)g(U0ILdDO5_wic|w`0+}l^wkx&GvZ(|JeYkGGQ&)b~d#(uTNcg>C^)mAo& zoFAk%k)q!5yFFKo+VmB_q}7sPzs)jt!dNal6xmbW_8w2PS|_BgXAr z&6{s(_KbWwHa$Q3pT!0XDa~|x?irw@Zm!pea_%46Z9j4`_lw*&ym5Gw2d^{3CcPfe_Gd zbo0vcsMvHUTjQ5jo>tcAO=H$yRhfnv)e@*C#>wE;4X)SBY#7B6ekK$W+A(|wizDGd zdC<^RlJy`dj3~|2BM@`m?O15*UCi1uN^wF5!jFTqTjk~tJF!*fpuEW`_%q!esG_%X zSgc{GGeOJmNH}mpi-L1xx?0gwztfNMR8^;0gOAyRZVI!CR#EaQeBFt-*OJKyq0o*! zXMZ;yJInOlW#XN?7l29&dG;qi%_QOWCW3{Q&Bw11J9?&7u~mBckn{$$tHvrTjW?Vs zGo!@}j?Iu$?lJ}|<0H)7msx@#g{&#xWc4rLiKTYr?dZIbMeV2dUR9xVWcXwFbRU39 z@q^GU%s1PrfRfT#Tq0)bpvA|_&U3eIhTN?Rrm1A|VYc#336Rgb;O236wy7z*So%{X zsR?On9*kv2ds{ujl3r9VHTOlS;s^HR)#9qPxEsWVHtHIk_1v4HxAApxaq}HMdywLb z9Ahj`=dN26NcIW)-?A$!GrIsJ+yhp512(Ubem>0eHt4jSFMB2P;-&zWRfF`R3^83O z%AyZ9fjS<|c7qLaL>SEtL?9x3@mm@yBI<}J`OCscKyEyt_-lsmvw|$)Z$k@evFGty zZ%UNxdJT_bSqf`ogN)}kI}94Dm7p?OAxpmD5R~v2-^zfOA=GXJ;;4v_Pyl??xYZ$8 z1KME|nb!D_l3h(?gfQ~9CyMQ7gynX_0ftuIgPTWZ)y?Bdy|`;##)Z>3gpS3}o=hnd zsab?+2`@#2R+1FOAulpx$Pt8C(7M{)@3Ig^=ELl=NJPZ||5W9^wzxGSu2!t@P1xWh z7gaI;=i{6ha>Awcn&!zNQ;zS|m>sD62(|u5N0zrM$>x1nK zPn9E?@0uFJLhttXoBT5I(0R}bH!5}QGv6d_F^W3i*qS<6bO{*n?v{!A6f$R5z0WkA zlEpQc?o(&aSJ-Qu9PjDOW_z5Z9o_grOsfHt(v+y~@#A+d0?x?;_d?l}SF#%iyiQW< z^<#JP$ECz{uRS_AgPQxexQaYl*iP=H>wc-uOrN{gsy`W@q6i zY>u2rjY3G;vIY2+NJl1mZlD*U~>#<$31rGH*nw)=_x0jK{1Dkp;d!5YJZ< z@0_oheVUp*RCb>WWt?YJ&U1s8z`kiM!C&WHHk||5^*2*C14X8`Hod)Y^Wt7dv)~^O z=U|E-Il3C#wB_u2u|2M!&|cW_9PAJiE%WYAiJ){YnJ<$Z#8{qs$+J0iOFhdp+}B8V zy1+Ee^pVJIPSFEzjqH{7U8wTsgeu2?0?ONWALO?4$d>7ihDVGex;Z9Dgo0XTW(DZ4e@! zVC*+xmEM$*hVcSRO6TraiK7|)ZHK{%69otV332}0a>#bfeTFOP66GMUw32gO$(PlLti)ZL!kc`dPSFFo?u+gAzpEM5PWqoC-olNZ(La->=E9 zj(N0T)%$hfX21Fog5~*$PVLag;j-JP6 ziXSg758Z*4hZ~3aRvkJXgor=5GiH(!E}Cw^J8l=Zkl$11uN!B$^182zrwB^!`006Z zfJH#YCRHEXXXWF8XM7E`w@R4|Ya6*H*|C#Pmj<5kc*wPipuJ>lvi*!DALPFp(I9+( z9dlKi$0Q`GlV-kX!6rSP%cDp9Ii=nQ20E3r!zh=}4s;$o)*jj`9@pXm$OTnxl6m@P zN9|1Cyb9tuk9OCPdKE<2q*-hc;MtgTM)u7^s>D@^PUI|CgzD7=K=p;YP@Cm_thO>X zAF--=hQ9oscozNBv64ZWxY$TTwJ+^U9==M`=W-AB9&ru3FIe#l3RfJsv?=n$vhp14 zpN9)gxLKd`xLH%hYL*1?NJeZ@+Izdc>^Z^z7R>DFdjT-*GSK0@)YZ&MhSzq-SU+ z!G@ZBT#MF00{1^EI^utnp)Tr05y%$Za=*DTW}nOKjwaG_9wmrtdl8DZbxT6D;j-rbBfaycVz5w8p^ugMa*X*7merm`l>ab(C4D z;eBFjxqAY0Ju&$LpSItKt2W~ua$~YmQ)7CW5%=LhP=%&&2_aCRHv$hg?{1!f=qYR6 z0O=_u2u;**}3a+fyFeExI4qwjBf9)|hY_s8^Cr!Sgd zqaP2x!%=2p-N0sIq*Rf+_bGkp)}a2-S-R5KyTG$>?4eJ=^MaAHqljH2N`Z?^=yz!R z@Q8E2kK#U%!(Jv^K^f!x7Q%O~M;xN)>NDn_^44B>E|#l9iR7Dmcw>;vwN@?9Cz*?> zbSJi7pLX)zzm>UaOft^?-Pdkh^X-xD6WZ!g6c_s~tDx%BHWKMt*Tgvam8bUBa&N?g zAqOG25BQ~4vE#m?TT-FMzD!D?t!K}oS0{P@?JM#P!5+*$A`(S=OIhIa+o#*lQ&-A! z)p25oZiK4ZZ4d`whMu}nBpiR3BaEl{?1LpYYg+I+?SY2*hqi)8t`e-DIsEQ@PU<0aTps=rC6Xjjbnq=`I(CNx9Q>VpyGOlW%8_m98dyD@PlXtbf{Q*&|}B@x8Zd z=g4R+%=@P98}!`c_lLCwxl*BTH(bNs2TwJ>q~h3p#~rk^6;hCx@P=$n`>iDf*1n}Rt5g9o}JP{tG-mJM8b@g2@4veMB|JSKgL=W<1v z)(pO{pVP&R)3NR(c2j2W2PxNbt4tLya8Si9C5-e(LTr|ozxDP=2Bm20_mf+Vo$;2F ztYIt!DD@i;E1P{9v1AYt&Qv|adTRI*f*S6jJP#xujMB%c=K^ZsPk%=}5uizK-eh8pp($o=NbEUR%u~^Ds)B%DBD#2==y5N-A_O=Tq1xG5l=Ol|{mp zXlssqY1v`YjtLJH!dG4;0a+y^D6sk=3JK1-C?m(01YvK}+qLnY=L-ir?S5?MqE5G> z6gH|2@m1JT#W*K9JLqz^feLkeZl4C8-_Z`ne00E$f>KS(cuT+cndbDirESO9`}CaF zdTpw2Yr)UcBhl8|60LXRq6Qm$d?V+C+M(Ax`yf)(Nnvpb!=KP|S)~zUSD5*?-FTWg z9%0hgGTtb8c?TOy!PUD2_r}a^R>^^wbcxcX*T}|c<}Ln5h$1|i%dKm8#%GdfXaZX{ zyVFT@*IcLw{2n7}KR!BDmLfLuy4C2-cF*w?n>V8A$!keYu@ABsEb;4)K(zO2@DZq!kb7Y+l{hO7PXp_Ep~qJ zq{l38V`zkk(@>}Jxw7qQ3GN`5?cP$8Xltg|_5$*!B=e7q4R*lwzIh8_U1>#n^d|`F zYyqhx2AZ}~8qCUV6~u2BjroEy9x0Lzz<63?#(7inOGzCrfOJP6=I4fO=WlO62+BS| zBI(#dX8w%5gIVLCJeq5I8hOsQ^FSlQ0w*)Aj1IyPl9U(K-S8s$nk#Z9z0=#BQ&++k zz0iSa=Q8c=_g)iQHWEO0@sPOh?lH*_ncz%Z0oCS|Lfo~X)7Rc9Tsk{xfaSzk))r5B z<7t{0>GYjfHz%Hy@;TWpyg0?nK@5o8dj{=b)^L@3ZhrxES{H<7J6jD-iKY~cV|Oj* zx7U?ylkC(kVooRIae5!KBpg|)Tb;G2g&u0iCr-h-s&hbT~b@_iuc@lL;GiA;#SBrqv#Wl zlr(a38YWHI_H<+L3QN>#pGcD58oj-3<-6sxXFR0U&uSz?o>qK>?HQ+{s*h+mqcZnc z2dznd#=XODfOORCT|G(HoVc_3G5vI|N$rZP?4lTriRh6l3BY7(;pyK)qujLgAY9m1 z_=`tvS9p_>Xp4=dOZ(P&S9g$oFxmEW3>NpCXB-U!`N2dlh8o?R-cu?ozs0N+TCqwt zS2{AgJIN>pk;69PNH@kP@gf^aWOdht2P<9Zw!aSuQLImM_nxy+)y48kZ7#87}`5ibxvOw*W~duo+ZZZetkIkj7mc&{6H6-o7Jji zV%7A0;vQgEgY}te6uJ}ON)+o+-`BJ%Yc4lFs#Zlc6Fkv4JD0=qpBGb(4#< z#!}PHvPpQCVMPgoD;|x0oYE)E?@xyN9}sRx8sc_#nz-|5mCq_4N#Q*xTP2{Yu=7i$ z4dr^fC+@d>N7|8f^P!ssUFCu22xV#ev37@qP|Y;XKAN8IP4*8n+% z*YN=xHT-tLqZrW*&6bO}s}o+=rCF=0=7Ame=H=Ol%16ZWXIaQP3AG}Uq*5F99Fb{e z=IS!WPl%dqcHZWHZ_G#{<>ZujbDYh8);uueC!bS%U}{HUFkdzZ9aTVdGj>?(GhjQy zP?kT?37Z@oWbMWZuv<%wpqi7UO0I6cx!-&;japFm#?HmBL)C8j_7@KNV-Kt~mcaKz z3NZ}#1_sdS`!|jFo`IzRMSH#xa1yC`o z{L$j?1b2>gZZ$V2zPlq9fcs7PMSr>Jtyqo%VPwG;NeU$g{Hesuvi%-MK0lWyFGt*- zjoO6O8MM5D1z8($aprZ})efIlTmWn(4dS}r-&eDte|DF06}#S>V7Z*4j{gZV3$E$q zW=*dHInY!FF+%z85E=TD>upC%Zjg=Q+<5t;GG{!E$tQ<6IJtdnzWWg8!?k&S&%uzx z{&7Z3D+G^rHY=-}G6YQo#cP~e={H=p(3xtNosP#1`*=0iRF8eSpN{A<-XAfh=DoG? zMzseka-q8KLlyGiv-Nw=vo$gOj;$4?g_3T4>&wl^9C<(2a=%%pYI5Y$7yNZ`CYw{j z(<73;3Y_7o;l4i7arS}9fCeh+fR)OZ4_cI=BrmljMX@?nR%1<&8)y=VL_?qZAFxg+ zoJ>1MOyeA007J{w>Fo)g603MM0Y+}8<_R^LvqF1?lAH7WiSO#PPd(Usb;-kno{lj; zk6yE#t=sx=Ko}l79n<20RoCgc5fC8p*+Yjv;q~L*>rre-UaeH|9#9){sU)hHFLu@2 zAEGvwTDph(sdjzo4vX)g_|6*!N2^(P&nfO4F&J1r>prqX*}VQnJi@pqqE6LAqiZB< zBe321F7-~zQHF|Y|B>H-i->`TcKx)63&L^axYr8l9jBNzp`BhlTE}PXjb0khiKF&p z{H8O{w+g~ZRPZTO@$hLDvdGye>}l+kXxn$)T|~p%#wLYQcPh{MKUE6Pun5sGdDe$1 zz;GH~)|{@mR%$-@;kg$Q*0<%3V!g^-TWjxdM~yNQPmb#0@+Z4{u@YbC=KE=JEHipu z?I}qa$4HnF*;|+WNV&nr5TzDJ$incl?}mqqNXfudO>mF<_#{&~-}260o!SF5#YE}i zP9yO1YpNq7^CbjnCcFA=#~DSIP3cl18Ci=h86E{<0DzAK|xOG@7Mc@4YiP zf6gRtwdr}rINkN<`ms4dB{l7iiP6(X)))=$w$ck0Y6pTvwF7UtSi}Zwb~-6# zNi5QDlsY1FPC?%hDP5z0l%exK8JizHu>DSxKOz$fF5c z8y?(5ZHN9%{3LdDG{d=oG;u@XN6NSfgwHgRlm=dr)UJnL02~aDm5z3Czfy_U&8}Id zt{q`wSkk6*j&B`G&dr{U7i$bA?P?M`esQ5h9Yd(h|8^_p zjQEW^%^E7JoPs6eW6tq+5M}!K=ydMuys-jS7oBz2=@T zay=L>>=^gsf7q^c(UpYO^3YiyQP3H;e6BcrCp{*P9R8BFpJ3vmz5h$ar|9Ze#*{{uVn+%E8Gw1Bd z%rR4-Z00|AI$G z%gz`7YKT?&_CT7y_lpzse%QG4>DR{hBac?~kCHJ=JjfmBY1o?tM6RzGlp>txmPp7e zHLT_y$K-x-GlGr#eLsA+%d|lE74_Y<=T7=@w06hkTn|cw=INHL_HqH3) zECyfDA5D}va$wx>0w|R3YGZUM3Ptr>;c-HIuszMj-StE4yG+M+O*P)|y&7wAmU?GO zf3I-_$acwKWOT2x0jVz$-rmEYDlZPcrKDF3-(m!2%{pf-H+EJJekSipMLQ74jWHHf)f3Y**5^;Px*5yOU$8`X786d;{I$1*B|o zhw<;COC)`4h-LInS7P6@Zlq>lcu({0Q};ic{;YSHf7m!I-Zhx)N%+L0Gu83$0Mwdb@fw2PgTYu)1UKIiN9{S~_VqdN8x zF){i^F5yS`FRA0#0(<+J55Hh%kK2S4r0hMwO0JoKEZcpeS0@PDEWT%=j;K9j!po*2 z2L2?8B3S|necY^=i`~tqvJ2(OWBie%$=tP6oEc|MM@ICRof5pvIooCc{bw4c_H!^rA>vcYJZ%(D5wly??j_Fk^T|e`s4h-^>gj53vv} z*KzxfGc)WyU@X&SnPfB65SUU|-da}seLP%h!Qe*Rmw{y?@wT9mds97WPlpA*zi=lL zWZ9BhQ*7cWV}!JcD0thWAMF{iRkEth@7%1{ho>hoCZ_VSTYZB@d^RP&RsoTI3nNXem=>kiU*= z*DibiGNFj{g>v7k(w;l727=!W3_Y9U^%xobJgf0?%MV#Ij;Y&ySB`?~t~t+1o+9Ru z&KtUM1qYX#_>apzYr=A^+eVoe-L5Xdn)DWV*SFHRvaAJvcnqTfmx<*Ggm&MYQo)?MR8z z*C{WsAG?ov=B`0+UKrN#cSNNA#76@Mh;gS^+yBJSF7|_ zT;g^Z{esMT`2u%6i$d0sgPPh-$AVY!IHf99VL`9~hmp4^POh;D7QBM3d6G~}N9z;Q zH*!%9cPgk`@!@A$V1VN*EnjfkMzp?G@X#E z6-H=Pb6+%e?p{Ah1J#q@!xMp%1Qfy`h`-dGMB$aSjKj(0O89 zCt>W&vrfhtWWbwFN~AtC`vhSy?6CJcwbFz}?&h~dqVH@%rgvq>Zn9BD{P6t2dxwK2 zMeHoPG^Hq(XO$ynF9ZP^_>{t%28l<>$u-w{o=~~Rj9&cqC>2!Apn2-BJxIc)U_Wzj z|D&R2UkQ7w5#`4p=S8Y8KbP??x0=S2r?IYgDTEd}oWmGX`%$Q9dP=ur`UVTihg5d< z@NSFrmWa3Is;4A$j}g6XTAucG_i-3j?;grzbEGSwA3edWaUr#zsAjD-w!N8g-vS$- zrW?_HRST_P9~rm*r7EMUmb~@Dlz~7I*2ZOCdmTPRTySiE-vj6G{`k)d1Xkq)OxHG5 zk&5?ia?H`~Q{q(rXl*>&Ay_XPCXsvox-JKVpy`?QP35a_>+k`NsES|qaO&`&5Sw`>mWpS``o zUA^rR+k9g#C z?w!*E7y_F2QYzFxu31G`dr`O^jq@9$`tZ#N5o|sEI1#q|b?5b65tYpL>u5e-)Z+Q* zv@2JxbJn_3(e}A&?rJG3njG949@fFe9IC(9Yr(ZUy_k@(`tot#-mclWlvBs~?W2e< zetxn|`EvbIG`0q1!DqS9gKP)CdWMIUo@cpoO;3h2Oc7r{Js)E^;Ce?}gPyp%IoVI( z%_>hpKp2tb{I)pwPR2aZho#lv`F;w+azk|t?E$*A3*h#U2cA;Y*ihTEqD-5P)A3{v z9Z87{C#DNPW0pQ|#662NkMrKha*L;mY534-ZX)vfI^6+FW<8s@@5$+`5C?naE|EC$ zoOh|y%YjMor<&M@-z3xye3C4ESEx$sBszO2rI^D8vP*K0S?DF0E0GlyIJfc~v!f^5#b3GThr+=4ks&W};4_8M+ zbsE!>ILo-qC9@;6BsW4#V#M}~vb(%}U{l7yT~wyQ+L6q$pP4UIW76INCU+ zPms9pa!+0Rl9`VI?Mg}P*!wv#-R1_0VZE&<$My0<)QhKCoALa*ng#(c=_Se{Lbyo% zRGZ!{ot1=xo@BmQ?;?$gVcTL;)MqM4;?}*_Wz=+tf>K-dqbbw#(+lJ#rzWT5uIFbn#ionea`upn=!lTV z%OzR&jibfjG{z7&MkDA=w)AjFMwFGm+HZkhhZFpfi6pR$30VaKdU|aI0tv7_$%rfN ze#-$YS*h?LOK#FYBkvI`osok>q81e464~_Pd>BEYZAX__YBaF!X(#AuV>2LeD=+Z6 z5$uO}T@psz!1GOBm_t6>bwyd{=-HlSW^!x{E!t;kNya=Ym3P@1#RbO^4R6SX8JVFf z#B5@mFDDMoy>Th!;_bY*N7A$GPVT5WP;_lf?wz-`f&=zQam%1OZng+{tDmd_;aXjo z1kDdvd8Ho-=ZHh<$A>Cj;^ncHdB-tD80NBvnvI~F$Rh}PNpa;LT)R$-yKDqIW;0lo zV)59`mbhIqN3&VNxLA&Hhvf}OEN^(+gg^e=Ri&c!#KWJacQG5kRJTv3#l`SiN^ zZsv{R=o4$kAJ-UE+bP!wJ04<1OrGW7oaSsbCk^B@IqXVOe9+LnhR^=o(y!^nRsUA* z5OYZsyF=!RnTVPdyX=WNf!3?l8ijNT7!Mk`hLnm)&S#AP8|w^u{j!>H+-sxtfdif* zn6uf}EpX*qQyOD68(|sy+04b!bfniy1#RwF89%o<8wCsbC~HSYSL};AZ$qoyQ*Rlx zb;R^GU2}_ujk$!6WiWG5#+&v$ZeUdeKPl}lw_uNUGVMKCB1=_+iCc>H#qsooS*TjA zl3~O6L`k<~mbK;NBA=owq|D(7r|pwcCHXh``X{ADIkH#yR_sK=9w``an(sJoowSoj z@Ott-wndx8iyi8ZYqa9&iX3i){ixn*I!%vecIUO`>B^!~w_z^Tioqw!F#i#yK&~8x zYe(x3jzTV}a}n?tg5lpU0HX6Am#U}FkIu1&Kd}F})$u%K_h?x1%~!6~)6U)T)*r*i zQd|Me+d;eMn*S_tJl?(h{Z{;71bRi)1+aWLGAwEXv+_&wQf0sybV~64+;FPl8u1+2 zMd+M@Dk{MBL&lcf@-4EXvXiXu8GUERK@Wo%cAf{_sx0`$l)7YQ(C?|{E4iM1IeU;B zFaIz=jwa&=@zYOC*7OtViT9f+^y@b@y)-G3`+GA5(hkX;6GMtQyAmIKp=w;zmWy0g zxo&mSgV7x)nn1oY`^YTn4tWZ-J1<3i7n|#XWck{z2$oz{h6wq)5zd*W0GXBMep~i0 z12#Iwo%K#9zxOJ1IH!Gv9SiJ^3a)yUq9xBxE+Jy(GExZppT2+5>_8l-({PfqhX*je#R-fqmgW zGQ*=kYfkr9(OdhK&>#!FEtW^B#8se;7Q-z1qOJB#1y@8{WERyZo!SX3VG9AAM{n$;P0bTmfvdLuhJ&Z;| z>nh5$zU;9gxcz24A+Nox?P%4Kh~#ZFZmnoC;fZ{9>7T_P?di>G&SKGKuLI{+JeytO zAE28eJ(c(qNGL_cZ?cp>0iWaETr3o5j|UwdZjslE0$J-E5%Yy?k;c0W2*MLLVQEyy zd>G#8PW^}jeXSz6`U8D2OoBeK>tjyq{emJqgSoR9ao6a0#PFEw>cPc?8NQf65Wxuk z!Pw*1e+bBnTtxW1dh{|f?|0-4^JFwE9TSbVJOIPa|AhOtk_bJ*i=-MKy~TdzVrPb z0LVZ$zX6NJ*>b$BURyD0a5WJ~%ZhYeiX1LhHwqM4WtLc+HdvWs7med)(vpux<4+?| zh{&?bg^Oiyu5HR3A+d0>=EWHJ{*A89g$!uZMyib2WthT#HsL}Tp^JmzP|=5y&B)w` zm6U0tRx=VPqS4XUg}6wlLtGH$^Fr}4lx;;N8fK`Bgo-Lqxwxrfij@_@iiC~9hQqjcM2%f5X2&mxLmTu7GE@2 zoTm-N!(wIl=%j5ZsntRlqWIXH(2&L@EKV4>B?**Q3h^~($Jta-WtLoOh-ICW;X~x1 zqq6uR%7igPVqRu;EyFBT6l$RkZ-ay>L*|Dn6BvfY-wW}xqj24nDb*0g6i}jy8sy<9 zqKYV@iYTU25Ya-KVqPZ{Q9?x>nvnRf9e#Bi4TVuDo(s9YJ37ExRf$|$b`M$>7q+G3^pKFTPf;YC?e;X^JL26{UZ@!yG~ zX-w@+n~?n~mKUb~&pQ_gW&(&*xXX^F8v-Nu4+5Z4O zZ#^}ZQGDtbfwpdCv5nB2QS>+2WthtI<%T)_LA;GWIVPixr7YI9yxo+-_#(HtVr_G5w}q#nK%TxL{7Bd`??4qZ$u}ujT6%7>mQAX0Mjc};0O$$xrJt{UMVvHX{HnV9= z+R9>VwqpMPiqkThF97D(J$EgOx??7R!d=-iFV?A?iL=-h5a zqS3puiYRy(;YMDDyC~XGd>brgnU*#@GB#vpA3`YTzv#vGElf@5-DAxURS#0cR5VbbvBO55X?s3gPmvq(X6#RqBUAqXLUHU+ z%M>_58iY|r3K}YE6j47+|P+cF&0;YN5J{C}Jp~xKPc; z;>#@DZY)f(cz^%I067o<0s;X71_1&D1O@>F0{{R30Rj;r5-|f1B0*7MGI4Lt?EBlRV z`wb?(!f|v`%@(B?{{Zv-vee(=!;h zH%G+ypA%vKT>hiG_4%EWES;M*qLyM0c?w)!3p!p7XQxv8uk z4bx>(6(g(mI!vsIa-_0&5`D-x?ltI^4~g+9MEIW)UF2?jneu1Ih{<+Crt8_#@174n z&Ce{(QMGd4COVm%vbHdKI$Jl-gL{0M_?ws;ueq_2f61ChiWiPqR&04Y9T-S$%Oz=7 z821 z@@6z;i#=Sgv8PrJ)V%W7STxEs$4O*z*>YI@4f6XJ+`|2h%1iE2j-lZ!5u5DCPUTw? zetFz4|>4ipi4sK1`R_^3r?q zyB}hFIO0sxD@Wn{jI#zGEE;2$E*}YM;z@2_6XeK75u=C0Z&Z<|hrC9oYJ{T~DHq4% zY4m>&jGH>;ranyZx`yEO#H}{Xe0QP(f`{UJ8i(+u?5FyiKC+DLEI+XC?2gU)H7=Bn z=Y%CEe?tqBinis7KZ_{Fx4p@9Ij>?-RnwXpg}J9#*x1f2)5>?f{!L9|VY{wN23d?9 zQl;dz`kQ3(T3o8Eii%&2BUGiTM+=E4?kOimx%D^Tml3@SvNdjNN$X>LS!@Etu0?DLiwOXO;w?#;d{q0H<`gdbQPN_C0g+ z%em~nNc>(eZISqyq+R6U#lDrY6jdq|)O8THXJdu@rrJ@&N!Qeu6S@<=-IO^BsAH>UIi2nc*O+-?I)dPd! z7ucJgR^w}?J_z`-?v)bNlx*~P6s5$M4Zg!sisE}?)4!2LNk&V_r;RO(gl!ue8*=#a zSTybSDzVGC+~1sYMOe-w*ngQGjfVNz1ucycYS&~{8y!aqF{Kn<==5!VnUs_8aP46h z>WY)A@Gcy77JH73s8;OKo)3;pVvH(d*A>LabF20*vNMv48%nFV^?i*v`kgsONGG#< z_#-JrILj%lpN9?;*LBnT8(Q*>E**FB#x^Z-zFo@H ze=%2nxoULm-v)A7?As@`WyX^`V>+cZQp}VS(J80P8x%! zs|S5Pg`2F=6)jCQl!$aw*`c+ulWts2XpXq*U96R@{{UkfcrQ*lEvwPFc3HLaT@<{T z%C6k1k-kp!WVIE3&a5LGl@B#%{1($Qo?p@`Jo3KAIqL3TD%EBkl}8F+1f^n*zMl5S z*By)3Z_+rO5vk*Bqsf79Y{W0@P3_9azuk$$u=Oi``7R!=o3xRaM@~oC6(we#!<5^j zX;+fCY1vmssXme1s=L{%S4o7v;)2_|8;4RnjqzRvmcrr3yBA%bi72PwU&D>D=#5Cy zIq`lJX;4`7YT(9~fe(wsjm3F1z8X)_sy_x{1Fn-B39a7cY87Ss7Y;8<^k(bW)69+RpApL&63<_e_M;t{ z!<`f2JbeiFy{wF|+C%MYG^HD(rqU`wNG|j;hWIr+x^h z8kE*W7_Q~<9l>o8aP%yd8f|zclx>YllZc@5niv$r!hEcjc8vnL1xX1sO+!hW`MlRVc3|t|E`r{{T~0q+8*}OT>&|N-Hd> zVBU?ZoMb2ao>`>E?~_Qj{>E2HXv->c`wjlbPUhvl68e&t>1VbksUF82Ib(&NQv4rM zF@22=81iWJC3!e}Ib}uJwbx>_su-hhNR%7E+?mv^3#BgxOh(GOF}Yh9E%Myly7I7W$bw`VccxN?7fGZ<*`>td1mx^HvJ4>!oOl2 z}h6p z=kJ_H(WX6{6^FY#2uqi}pCRk0bD1RY#ETd)i?0FOI%EY_!-J6J_U)XVzy6`nD9*k}5 zlbpJIc`ab~%Kkcs{{SZBcyX6g-4l)1oQg_6acPYSH~I@TLe%V<(o4~Gcq+MEnD(z` ze?t*J;T5rxk<_!`&r3R;A*AgJi?JVxt5z+)4AkwV;R{RYmu~27Jyg4Lv#Te@U;Tv3 zXS2&NnXvsIFR_wNtb9nTvoOh}tc$;-b;ql~_?2tfb%8-W#SKg7i^5(=CxvChJmD5}Aoho?NVZ_$t3Zydkm)bh@w2-^LYh}$@D(N2{& zu_d9(RhKTp=}te~y&7*VRu5%u&r3t{TM{D}!%M}?uYulHZIoLcm4kdUbx7WYO1iX56rOg-a%bSlY3Y>KmUb2}TG^1>=M%+Vv)TBQ z_{5t^qkEnQ#ND4ivO1otB%j!fV;r$2-u;Zb@O-+w8!x!*X{%zzcO}*8(WXxVf?X@P z-*T4y`7K{5WL!AMv8QHm+jNCDJ;fN}E3wAgZHXsJLeE<%;J2S#t6}2JsT+xqY9H;- zlU$^i5$8mRxxu*VyYe{7I%2#bwZAR-Ne?FG;+$brybDcBIZS=q?u|YSVABKNbj7yY z8<SCL&cCwK1~m0kD>T{FB`K9 zsfkNC*wHm;PB@7*I+Cu(Og8dEgCDyi!~Xzk?qM2hlGL1RrI?t<)QcdSjuPDYrpJ6p zt0{H)>`CyJi2nd7YjbW=eTTX6D@oJ0z0CCwQ`7YxnI?HYHqobuv}qQc_ANXQbZN3v zX8k2cG_Y1%KM}*|Jk^-wkge%8UZF21%Gu+Uv;K^v>_&z9vr@Y%Q|++R$m^v@zmxK8 zVE4+LtlIh4EzQly#qM{{lNkGxvHnk!GVEA4qZphw0vYVMU+8=pQG~&qP z$Duuj?vc5ilrJLLS*1!Z+;Ti^;hS=@xQ(+Df1y6(=dk6a(&U!rP^vbJ8@5+-Yvpfd zIbg?fnzUmBbSx8cN5rbZ@%_!ea;x_~B$8M{eTTMNYhn zR~Ej8s7l|nswI3^mS$Fwg@b4HHLs^Fv*d%ETiT|(k3y|&tK5t!ZNGo1Z-%0~HQ(7c z6|?1zQ_{N+o>jZX1<|HHj41gYXi6u8496-xa@D)+g>{}@$E3=-I$IUWOYUM){Y=hF z?qA|czNPDz>R;kJ`kOZ*-Z^>nb#&Q@WZcEF^s}lww>K$7u zr*wZx67A@J&NVH`Hm#a31*q0H<(uVi^klsFEA|$++VE+LN+y-UbM|e5qPI0mCB2GB zQdnbJ7u1u{)%?brUNS}s73@uI?9Dgi#R)x(W0egi&yzPdHx>N3GF~Wbe6Z-=%F`dv zx68=fqR@}{BjpJtl1p}Pc1B6*WM4f z@n>9D*lG%wvZIe>iKRovsSvjGuLD^lt+@9SzT>#$U&2h&D6L0fV?IQyCdW&4>|f#? zEnTl9__}&xtZs!GyBbisj=#5_Ry@%sR!j2g8M%dG)wzSoYd(s2uOy`p_jeSeMt0jn z*PKyKRUOBzI`WJxnRU*ixmuliH@1H;^QXY&DPNOX!e$1Jt;-`lUt-iLmff5+*l#$+ z4Xu!+C2}|W9!#wHR4D~S*G-U@NQJ`oh2B5nLn=|cQvq1XQaskd<#Ey?`}a!BjH zvM#aj{s&hT;h7ys#mCu;neb@XUOgAtGA*R+j;wkY54nh=PL*ZNy(_Xea+B;{mS!zq zaqf2=8u3S4$sfX+x7d{Uwe%&`XvP<_9Y#uD5;fdv#3xj*bEdLZ$b#+U^fJ=>zFYmy z7k8x8>wCz$qLHd{i;`4693|+rxfRL6GQoVKD!-_nJknHjrIGhuM-Ijod3289s!X*M zzJrMDqBkqgb5Hg>BTmfQB9rVXU)Zh6O9qo{pUj7$V2r*kA37*`vr1-ihl=&fY?+Qo z{{Y#34d|=;49V!zMMTr7;82aW&ZNwW@j^e>>3_~aN=lD`|f2kkW^Yq2Mb{f-> zDd59x6l3B=K|f}Byj%166&cvr?3W}`Y+JK@i6!?gYgZ+I7Cd9n^)u1busY?JNXC7N z_Au+}c}8$`HfI;#!CW_E7cM8)o(LT{R&Etr!DCZKSHJ0QPu3u$ITgZTD>U4D9gyC_amp2 z@5!<8FsY4xWr81x7WGF}BKT01KKqsUPf?y77SltPiC570G2G(j1OZVi1#rCPqFLZ8sRI_;+v7g4m*|(4{_8jB9eRZdA7}za|Wb zUe86=PmP}f++7-6I+9V0dkOvux8t#?r6*moIY;9mX=S92y{zG-IH7gN^*&KEIHUbojVG!4B`+`XZ}}{xnF+ZZ zKjI}zyEDa)>Q<D+RU4eh8~8EFSASR}^){-1#%)&yzPd{Pb;-m&kmu&yl+~XUUmnM3&-= z5|7r&-Uel*;>fo3dnF4|kFkmxt5ycv){ZLK*3>a&rQVC)`}iiF>-89qUJ&c_@JqQC zEfFv^Y(~9nYuuEi?mO;EUVqKDd;HfukfjA)za%F(sYP~T z>(7Ew)pD#PF)>Go(x!eN4tCz-FevdIp{^Qr@0O>q^?ZB|JSgaox60n;nL|;c3OqRf z08cAr#nU?YjjtT=+9}Dk&kdB@X5X82&oVbRFj()E-z&29qVC%x$}-lnU*aN^(eo== z8{#Kt%xO$faububVn}^Nq@|V4yK++QV0@H|Gs@mgkBPCdv9YnSOx)l6pZ~-FCJ+Gt z0s;a80s;a90RR91000315g{=_QDJd`kr1J=@WIjH@&DQY2mt{A0Y4#YD-1BS6!j{t zzYN;}-R5Uo=a}lnV*W?67;PQE3c#73{{R;aaXd3_O|r%T#WoChk2bMXK?oIFQ*4;k zm*p=~<2#Exu*OqT_{BFBWh-!3;$2HGl(zZ&U^It3;#pbzCah%BrZF@e7JcZCa9;>? zu{^$};4Yv&1@N(t)@xVX7g}^9%n_^QE@i@bL*9QFDlEUiWd*yI6u@1Pa}YAXVTUin zD6;gMYw>>ODFPh+G{oW5!FO|LY6G@mynM+d{ij#p_ z3d7I5KxN}PvZ^pWn zSBbQtmauU*n6u4(0J$-OVL}m6X)?`CK^P@oL*MtzZ5Y;@NM`3EvrOOkCHvj=O$RsC z?i3p4Kd%~Po?u&dc!Xwy&f>wAhREhw5#}qoj79h{xCpHcAfP4*%(@X%HIDxP21O44 z00H+kBNk?&hjBwQSnh5_KY}Zr;+I|~=i;hqsdoyakb{v-A+^I1Q!8_#DC#m@JGF*Z z1Qg_brU)Vho2U9h4mb=m4NNxS&tL8W@`mW1VADOvadRGs#p{PLZR(|OKJHw~BH3_; zT?s5k@LI&R>J;JwdW@Khy+GclI+_%*RLk0Bt7x_Z8}^3bxm}SKPg35~hN3%D6AHUb zl0U8jFgEpmU;E zZa!jY73LuCBgC*{{R#tNG9sb z+WUfdLV!O}8pSZp3FSE>;;>$JlI)690bW~g&I;!|sXiGr$k+*0#nW@cV; z8&Dgfa?PvD17WXXQa=ER)L`S8&8H%8wGL+aOU--tsc}SI5fn5WK?L(JQ8g*RM2a2E z09C^P1@ZVyQcO}hIxncztD_SC08oZ_{$d-b5VN4Uc znTPQFA*wF%m^O$Wp)QV7Nl=8}#I@Nb09U~&K2`&5=Kvki0d%#7W2pO*;Rb@7CmeQjwd%rUMWnB?!3JwyBl-6053p(*Jo171z zFb3lp445t|afmgRZUp6w`wnU$?G<7(b)X8JOCk#V_E+E-; z6}ET5*D};t@FY0MZ>Z59Myd=}R0~yEqOs*!gI-8xT)UXDPcwLrB} z_XBMhEK zN>1%P2#J4a!K68ZKPhVDg%Y*n%mA^!X|y62EHNT(cGPSZ23)d*Hg^#mzoJp0J9dC! ztIp%hXT;I448@X<8@fMGumEy79s`~;i0V)sX1}?J9epxE&Bop3G|*JSw#O)~+5D-&WA2E)YdXmKr97@vD9cWJFt@a`c z4H_#w^X^b%?LzUY@beBdou3(~94TupKeZoeXE>A{=_v6Xvy4x9giu0!M$m?`keM2& z6l=l$U`lGjaDRjgT#gQA8ut^e__(b!?i@IdK_1Mx)L(R=K-^46Je3y#V&+1_f;czm zLkQ+pUD7ROWp`afnlY-u1!C55<}{FB9K%yLG()0{L+98Vmi3xb2?>Dcx-nGm9*f!>%B!nmOV?Bb{6*@%3&}pQt7DG zK_aY9N=+X1DW^TfLsjI~ma7dEncg&OxOtS^a9hx&piXxb?p53-?_?;4H~ z7=O%hE6)Mm7TI*!^V9lDIq`Bw6}}v?Z~?%NR8yv8jexyQIIc{)2?ax&hF;~aC58lO zFmNcELC_D*0Bz-V!7qoH8nDK?LP&-{4>Z-}ws)VZh& z1WX{+#95HXlwg<<4$N;6^f~1H6F6nq!$r}>#I3(j2mDdB(djoXnr6Z1#5VSl-I~-> zH+_cY?}RQ+803ZSZeh`zsbQDWT_Op@=?>mWk|>}&9kDuOIpS9BVjLu&nVx0^RXBkP z9yhr4r?V|+yoABLq#K1Segto(2wnk#3NJ8?h06#PXWIZ|*fo!?16;E{4?IkkPt-Wb zE#^ICnli=KE&lTWCf_Bwz0wN!h+s1lbIlol{h-$EsS3T4yMkN?GsMWmqO9f|xsA5OTWYyr{o0~1B8G?Mk zo25T-MUtv0^-2q+y-W;cfgCy01-MG&Odg)u&KEeD_?%)089YlzL1I@cKz{G$Q>POP zF-4VeZXWn~tL|BnjtOIYGj#Voq*jTxgIu|p z;XMj;r}HoY?6EAm_fgk_%wD0=4^Hq)2|&%I;0KlFE=C;}FJ`&KMQ0*cGs)b|<4P%8 zKnf_^)XR#z=rJwkDSKnoQJL8AO!5xva7Cvf;uh}SQYy~(EVOaECNM>aV*dc_mMtv1 zKn6<8A;T^Vp~Jx38X(}5FU*H%(qOI46}{YPFC#7tK2&_1oD%I`FLHvF5Jbb=s3^Eb zS~9KE0N9u)SImo!8nc>;P=yVEW$H!s4h!m{w=;g@Q0i+Ia-X?wTZ#^^m~c6mg8fXD z+iXKUKGSaFuH^umaV!`HT+N}}Lk_45)aLSz1RDi8c$&)`;ttF*ApI<_q-w{pXEg33 zQC6y@^r?&535(?utRAQbHasa~RG)z3hi;x9*Jux#lo!mkplfW(9ltif}ZoWi&lIMC6)~ z0BK}l8QN2p!|&V`D=eXa=HjkgKXTn_CT17VtBLR|=sgWZ22QtS9j;YT;S6mo8H3NH3aEBuxCu;HbV9It=$EMmjK;{{ zF+$JfnE;v(5~9*(ff|%DX%_1<4X_~!aZ;yA7E;vZL&;R5W5g!iBmh_&lvdc~sd%oj zolKVF6J$|sis6do63ZyDvb;OkFWWDVGNWca#mMYs#G&F)A0sd^mvBOhh~`5y z(z!tvhdnxo+&_|2{{Rt4QxBGg-q=v^H4x-@W_HpjuZ*JVR`VY~;-QTeXhim0xgNN! zdyfHyfp8ZeN+yki`pfS`;rAEe6U_t^HtCPf2}Q9ZR%=~Nu|u5+x;hG&Co?dfMrSjx z<~E)$n8=1lj$(q!Y=t2EfES6wXehAjEJVqB@i5@7>B|G5%H&&+o3QJY&`%um7k4Tq zEQ@7aFScgi ztxHgs?kWs5$5PH?XIX8uX6HGG@C|cv;b1&OB_*5(;g;8IR{$E7ih=UXtf=HxI@UD; zUq+5kA5cn1Im6?gVtYzalJ$v5auWAm(Oxc80VvljH2(k?4Z-G8w(yxld-R(B0Les# z)t!WhO2;^WC8D|^%t11=9YT`W@n&U=N~@{704*}JJ3JeI z)V8h@I?<_kv!Ir5!z%-|OoT0B38k$hxC(rkiFhp7;$QNA34Es?GZ!k$EWn}LK3{O* z8n?K_TYZsWKohx<@U;@tcCbNZHf<%+XiV@AL=|Dn(Ja`f1kTY+P>IakEUagEG3G1M zGRm3Z5UY2}RPo4+ST>@yEols(%c>fbi)|#uwmrwXrH%-IHj6{EP%LHU6PAIQ@I8@V zYq*dvDC`aNVqqFi+(T*%*%UKwqoDgD#B4%f;o@f4qakFe)YzlBo0Q#85y=od9^$!| zgOoe7eO%^drB0SM)nkr69_0#$)nnr}mQ~zq@Gkb3u)r9G@iB6h;xxN5s!hV?44{y^ zuGwQ3UR#z21;%n15i!UdNJ0rUfyc=VY|#J2{9AxktQ8VcJv$8MiXBxZk;zN_m%6 zY~~;scD=;-#6gdHh`tH98x)u*UL{UQz*E{%ZMJUZyF=VDnqIB~%^{?lu6Rq&E4bxY zeL^f5?E}hSg3TU@M5^@yygx)*0Iac&Wu!{3U1C;o%HjH&oK~|=zeER-n8@;cN?vFb z2I%z>fm|hoK-6w4xHtjaXnhEUEtdiw+9jI>saku1Rzs@I`Ip&2pD?24M=7s}OILOw zUjWqHAZ8$@3WLef3LKxdGzpXyxPkySPAPsbmTYA`5swiKowWlfdbM(ncf;*k8n2EVWA8{94ic(w&oibUBFMYi(FS*ThA zqAqY9d6q~-?yJnr<^uI(N(HfI#^vbUE?^aw{Q36_Fr%(z@zgHQa^;#^sQ4Q*k}co9 z@fP?+z>xt07M80_8snsI~Dli4ZKZ36!IhFa@V4^4#(i+@N#0 z@=8*e;W?gUZZF_54-XO1FTonk48mXTE^jI?^Mh??DgzKfg9kT_>*8Kj92frpvYH6{ zV=kt^JxVAKWGs|CU`w5z(DiHF3psT>hk{|A^f{?z-#%f%ojZgEG`ftVPM*;N=xSrI z?}9WymAmxeD$36TEr8oiROYGuqD+v41=f!-69ml^4 zRlG`vL|LcS7Un>W^wor*E^n3%h}LG2V~s+A;Rf0=RMI0UfW8YmxsBxI?onS%Lpa%u zxP}(HZ@BfNr)w@KT|-QZ;=j8kyswNos`HqBx-{(lY5vXiQc; zq{lI(LHg~a4YIUYWjSXL1L79JV9ut8OqGM;%fN^HnHIDf@`LrEB?3NY5m;jC@WYjl z0#%LWbsbaFGN6T1G*_Ivp(xp4TtCW%U1J4XFrZ7+s1U1nN(dZ((MiREFiBCJ4>#n7k5^~4OLr>rz}8U!a*Vr$_N?g68UF&8C|0>91KSw zFtZYcv%8tR^BQKtiUM<$fa*6WD?&B3xMRvW%!xD@?Mft-k|ZBqP) z48tIsqB@!^No8-pT~9azcJ(aFe1i{C)~2m>9vw=zkb0aY{6}x^531J-RG*msf#&6< z6tEY={6q>?XEmD8jKaD|{NWp@7C3z``z?!RNw*_udf}~X0SdPvP z-cr7!w-A%R1xwZ_`)VcC4-f$;+RVOys>H?#D1vCN)X4nI%$R3#^7iT;nwg>{3y2&{ zhgbCsoE-#VZDJ_eMyfAKK4s@=xRa@ZpjTrSLkAQJ*AS6cG46(8U^)}r*gn$Cm94vm z;a#Q&FQv~hdApa>#~8sb%?q@f@%EQ$44gvaLVzJ*_zq^4^WQLeo+XUnmOQv60nQXA z9*DL$z6n6@FSrLGAuE-5?omWcSxu6qSJql;;%ix3Dr%dGW-8IXqCH{OpwLs(6w60x z3_^0woI0sxOdL6lgNP|nO_&B?^qv`Hf*7f83#*BW+Bg!JKpD4ixv5M40Dg%|PMnnW z{q62F(!u4UcfaH)aFDS5T-GLWx2|}xPYz( zB_U^GE!V+pvS`+2pdAM=?<^MpTn(SXf~uz4n25$MWvAX|3shdBc4zIFzqG*5_DnT@ zv?vulr}r%2K%n(9GliEu5j#YjJ_1`#PCA4(!{wF)tkL2Vf2Kq;sidV*A$Q0zXbdam zF*w7*#*|gVeTWn`ZVP~`!E-M{ku)sdgl;UuLyGffskh|ZvEbrXq}fS_aFocPWjw2o z6Nac3RmCDyPblSED|sQuT+4=~!v6qKeK`TW`{;ry5u<8z1mRm+s)v$`;u_VwtgwS) zxw24qGS0um&NcNg_IQOTy5zF!1rSScZ0&{7#H%kX4&h5A-LU3~k1NjsCEd z+>KSI!b0%lTjfhMjxQ}2YrbMkyhEs?c2pa~P$Is#8v*2l zebwFc0Rr1kfH9T9Bhsz@pqyR@UWc0K&KY6T=N-nB5%+k;uHji%Iho#>2!K zngm6D1DK&-v(S(9q&#rTdN5+znggbu2fKt=t1$ z8SxcGCCt-3z}3bgro%TCp1flan1)_ifj}LQsA1YYTCWoaBBx`0G#V!Mk_P((CD zR*TfOYYhT;CiCV3Tf9$lGTIVAB)B9F?et%{)q!f0ihrlSrq~ErL6PX;7&l zsd^RqA&-g<;20OcLOu_~%ChK2+70tl%M}OS4YJXxwMuQi<_n|sl`Bq!sC#nDE9u%0fg>QnGqZ}$^u58KlpG22KiTUfr_BD3Y0 zi03gEOr)Ecm}3qF>QZ9H%p2$arSnmpn1!TfP!zpH&cOs1gDq|%Yt$h!L8yt^5!^L7 zmsN`u7&<68n7|y`#YR~f%L8W{XAqx)6Kuu?W2~jt@=A5LFhzpvxs{NcZlY*tJwqU~ z&So7n_?Ce^B47Z~hY*%_K2pHeTAV<}*@PI37WM4|5M_&pIg5sNO{e(?lFHjjf~ew= z1xK9LF~rm}m}Oh6^Bavm%)a`~lQlHD?kKbBH3Z$!fFV3POSlVZm!Q6DNpb@C zM7HSUm%8ZGW1BjK@%my8Z&)f8?dBU0d6cLFxT>vHa4ZDOp`@=^F;pc(f_}X1pE1iX z%>Mw;f>dHM2Z>G3$rKylw%Y{!m0?C$z|hmx%KrcaR5GRi0F=VjI^JhAciciJkp%H~ z5uGBdrIbqr(ac>eQRBFBaOz@nX~e+D?o_KTE-Nl^Qp42ANv{#8%KG9tc|1U{t3|&~m&AJ};#;_PMZB>vV1;&jok36=rokXs z(9xNFN-Z^%!hE61J8#6Zy9S{*cN~!m z1sD>ITzQvSUxp`uUL}qJ=2li+xa16n5QbFYSb_jvp?Z}?-Ah>nc#Q1K>Qdc}k0fxV zI0LCbS#rw+q)XVFYL96{lJO9rI#jC=S5n#QF!hkFm_iW%4OXGk(_=v z4M{;Bilsv>k3O zFG9@Brl~P>wQ5-P^iLZZnca}kWcoxXGTJCZ*Yg@Rs#uJz#wjsz4`^j4)qe2Cq|2J;IRSaTIBprb}PPm?6(k1-K;txUX= zH4Ngb#HBQmon7=m=QhS!fo#hO^G^giF`b#2pbQjCDtS!49dPafy$FV)wC)5e#@M5T ztkeTrF^3zonX!|Jf@a_?S!mqaDl9Fq`6@pfZ3y}nH~6VQHR1rHxqpmcO8xtkWoQ>( ze17E{*4|cq%I4$5w>>(D)3t(91Y**<%tE1P{Yy)6H8AE*cpG+0i)t@Pwx%Oiir!+J zUvms9_?nJu5d1*!VY>@r;@Tm?nG1LbAa~WWoNwfSAkv(%;KN#{sd+|AZ9w6gOOBWd zXJfW`h-p}qq5;98YT8KO&m%o#8GtV zgZPPA4+9A*QVPJPt=47?a~#l2r|ttOvs8cP;?86WTC(Fa#mzGf7Dngn3}L-PR}{*k z^Yb>3f!3-3j~95a3C1W}%(c2R5-}mo4_-B0c% z%Pp~~RYA-nli?~tdSUp^)>&^li+Zm~#$q)V%RpJw6ftR0+%NY+*~;b3Y}3ggNQ}9WnZsp;2X8!-I$`o#<8Uns~j6)kP&Lm1;F?(X0JB z)K_r;utaUlsN$Bw(ybJu_SaF$ps88;MFpETW@0H>% z9xDhTi$)0Ehau$j$l@R$&FkEWk^M>O3 z0wQpB&RkVJ@_Mpz~+$2e|=^<+GXjkd0_ zH=O-X>?MF?gJ1oVfTu~l+66o2ZT1xFyvV);AtLLNdN4O_f1b*K;J9J3?1jyCM1QEIMZ6-jSO7(bbW zzooTB_(u3+Ka-Y)yA|#cz1=}4sYkZ?=nW;&(tTNEmYiMRfBoMrpYKUF*=V{~zOXw9 z#1fjV&-woVo|@ZAAm>`H$*CsD=-p@lLc`wN&W#k?O*D7@qmPB-XK`lr>VQmOq~l4*C%X<)_guZ4{Qcn%Q5Rcp(BqcJT3O_ynQ~~ zj0kWhqqT>jQ9^Bbpo98?`&_r(_bw6N{>u5=*JP1qZ}%LGi=8e+3xIvM5M)7Wr0ClY z?gN3?NP_b|0@45vXb1|x$^^v}JfJW(;)c4nSbO4XE;HM2js(Df<$Ebhc#!GJz4*q{ zH86CXu}K*J0IMJ=$s+F5%Tm8~xI_7oNKTbZ9)wEW!A^FWsy3=mdgXbVQh3r))>^n1 zf(8`o2-q_pFaCVpSc)`;4r08hZ)=NSsv#j+_ZcQYHVm-L0A-{Ukp!2~1-98sZu)zZ zY2;&w%@S$lqEShaV@&|rOqPylH9jTpC;tEf*&`%DYdVW*mfJ~?8_OS6$CITgaE_l* z#EgKU4KOuX3-XiMoe}l9rJD6I)M6wjYk9HZq9#!6Tk)(BYz6i{j!75jZ}?xQfL3a~ zrr>-`@e}AXs-5YM^bL^Nt{S32foi@k_R6jjhVJ&G7fxbQ%C}%Qs5qi_V-V$5UFv!6 zJT>!|jX0M-8+0ubQU-GQQ!%BL=S+-#f5q7rG#n;o2Xm%U|HJ?z5di@K0RaI40RRI4 z0|5a50096I5Fs%jK`>Eaak22hf&bb72mu2D0Y4BXGxLx4T3SI7X4u%ru;szQTr6d< z{;xJQZ~0?B*}Mcq7Bax}8TJtdL-q`@`2&6>IDl>Q8`VV3}WhmgEp;x7ERk12d~2vd;1u8{r3_5r?cywAYOs#(M z$MVcMdScQ62F!aOBc#r-H;#L=*%tBhf^1^h_C`FEl!83ATtR6*$lf6(+a$4-nD!rX z_}K^cN&SNh{fxT7$$bw=49V;}Q<%PZzws0VWWk=X&4nLreIKyFPLBd~bvxf&3bU#VA z$Zd~rh?!(IGm;q@WsmcYnYo zZt_1FjI>G-;S(+9Sv)xKY!}ITc$ju4pDp%YOYO4^!zW%Gdy?Xo=YBG~67n8T zz7^~9Hpt#COT$M>8_ET6$=SUHwi#$fCI0m~$u zmHA=v^cEW+j{LAJS%%o-Fz@d#pLiBY%xq%G&Su#*UpbO+B1O4up~UV4p3wi3b81GsllN zI34^hbK*^#$l8=+XOZ&xZnkXLQU!_kn=CWU>7U!nPA>z~d8DO@V;8AOw$feZS!MFu zZM{mr>US}V_~A0rNb$b!ES$9^{{R`bIQ|1~kn#Tj5%_-rx6JU!ya@-$;o{5GHN0cQ zVhsNP9(6w-ExfjQmVV26{s$QuJilv`Fm?X` z0Y8xswoV(KD$?2GdLysje{&#n{ED!&+b@iJe48xh+rOQ&Wk1N&f(rMV`TA=JOjb{8V-ZRaIY_iKN=2>S;Cx^4HTEakBoGW%Sg^E$1mMw2@V{M(b(*FSX-;CdXM*Keh z#S~FRm*T_?T-JS6`oGvL3okD(ESQ`pt?X@)36sTyc`PGsqu6<4TMCXDrHAqfGxEpp zEX(*tZko@ViMg&v{{Soh0K5MHo&Ny#fB9ei-~6xt0I~l7%KrfF{{ZiQ{gSo*saHHT zefi`|#_xBVZt?u>v6}>v47YTgV$Sme*iLP?$bpas!VwpLEDg8bV`YyYcw2qpd3j{# zay-0?JNJ(ZB=S!Ad;ODTq(|9xCZ<6n8gj6dNiRgt2|bUTCOY((;>0_%>`YIb`NYob zzsU)=r=JU0cxo7#?2Xuj7vy8?kIO%@2R9~r<($~|%Nr+|f7Ilk>c}E@n`|UbGTyv- z@8dm($;(Se*#m6=<@EvxOs|kL*w5$S8zx@IHW~KFx5;K>^_*PGEQv#W{4*T+d9c{= zL&+KGOYBd^OZ&&@H{3!WFBb0>S~HUm`{uW{+Ybc(&QBhepNE~>HpVv7BHo;HJ(zrz zX@^ed_b&vf0PWd5x0<@e}Q~4J=LU ztvF_xt^U|P#`xpE$p*KPGeSqxBK^PVKkkp}y-(sS`c5pzzb8q@*c+Gp#=Q&bTKN|D zCoV&_PTnm3EQ=kog+thJ?1CTW-cFoeZ~P|=y^B@+VRAMcm(gM5GWfP3I>HNmOX~7R z*myBuIN48^wd_mBvOS9SGw~ok%pOUT6W7>Ma~4FuU=q)_m&p~h$kpMpU}vM)4~3Eumc1+QG~{>sqQ;G` zPxO>o!+f!~mikKxn|oX3{Jy8{;eF#r$buN#_G9gmS!9_J_SuB1VG*P3Ah!LAD|5f> z4cq8Kme`I*dM5)Qi?NeodA8`t9~bpRyuQmL<0CCtFI!Ir(3wmd1>@zTJclP1mfDuK zwk?)#%OB1*;WF2hFt#jN5_iMca>cQckkQYv ztZ!t+7QT>%%a+W&79r%bK1b~s@^KsFyVycU;UQ_#{l=DTmR?EU|HJ?#5di@K00RL5 z00II70RaI30096I5Fs%^ATUv3(Q)wcK(XQf+5iXv0s#R(5McLZ_WuAa{i$Q7bpFwk z+%it>wj`Jsi6qz1ZIcgTH zp#$xomuFK*IhY_$@zIfD+} zi5Wirz2)G&k+J2mf#Dk%9ur`WGDZdo85kHF8yvB_gYkI~_a#Kq2Lk1*cME5CndAkB zAdjDu&ma$MBtCxt03O&_SXgkv!o!2Wa3zrJ%hK-_Yikb(P9)D_<_rbnxgoaA;hAB1 zY;d`24c+gy7JpZFf3IvLEG+)6?A{rdcYmYWBT!~Q*@L(}T}QZS!!wC7*$$;2I2^aR zckpx6w%ft{O`HvX3&83^?tTahwj4Taggd((7TXQHx43Y&Fy7gm9Dm0kcHRdY&X(G8 z_dSxvan9Rqw$Bfe*e{|G@*R({4{?Msx39)4vhh9e*hKyuS~m4NdzHI5o;Qn5G3F8X z#Nqw~JwYDWvJaE9t7~m8t?U5q#hD%mLmVdFpw4n72%y`d&FVLSL2P)&^>~+9x?U&Q zY+FCU^(pER>}DEIvuuz#XDuyDS@K}DSacuENyHX#{sdWudwVH!r^-4 zO`hBWUBQrW_y=VvV%tJYZ6{Yq_S`47Fh{q~!T84u>yP`wJ>k+0T1&gf4xL7$>3GX| zCvcH$`zberqXPM}-JAqDE%zSYIbH;u%k7$3+zwgEZMM&4q2Z^k@y|nu$%Tu^GY$59R4P}L}iIzgj#PxcV$&=J-E|JLPyUQi9{BPqJ z?VY}^C$CZ~fZ+OGHun){cV9Rr^#a1`>{*TDt}?`Rytc!PwCeInVja3ql;yHNJ3o&W zHUt=~5!ozwJj=1X;NA>%fwlL7PX7P}vB`RuZ0>nI&Z8D(?(@oj7Jgf@1?qZ^2WxMH zKOsFpw&6pKjK8@{+j3s#Y`TuvI^YesGAE4LpoHn_+{$+F?yEr z^$aqWEIeSd0@>#$Fk40@*+2BwpX)5N8BL zyNefZE{r`-Zx#JT?Upj?53VL7lJAnn9=ews%qrSp$Ul6w6WV;2_++t$5@JQ-!HQ}f zOmf_o-lNV3tD|=;?pK_L46m+9ms8XiLK|irdP>0`$;VmTLni8lEp=#)?1DOzVg@l} zcVuVI9$mzFbde6>h2GiA#zb-Lo9lrXk0Xd6yWN*)^4V%T_7O1+k@0qz_9e4y%WXc|T6~t$&7^)_F-u0%d+k}_k-7#ulf8uZWin1e|6)zA8=%gVrB|>-thDH zorcabTXrm)0|!5|Z~Q|40LCx;V*dcfC;tHBAL{=A%p-Z*%3JGOeX9pBwy?#UDUcM-{%-QCQ>i9bU0Zv^bzf?oJ1V|})hrIJamxNP=C zVC#k+&u*;hUcuDAF4!NjExbCG!`|cNWN`2d;ouUi$m3_pm#f&4d>9E4;v;P>5yS-o zAs=|nvP|IqT>fn3;p61{eMhM~v#Tq{?7CS6mK*_=55r~W@)>pi0L1Z@Ido;daW*X^ zMjIAO?EYPW$fvRUKY*U&{{WHUWT)-immU{TZA%>Nv&(-j{=sf@whP#E+j!nK-cI@0 z@9=ld+s1HrZLsei@!G;=ZA9&&*U2}y;&QfkYX{p$TVnfQ`^aD4W1%KkAZ#ogPtqUn zC_3RDhSTkbfsKZcAja@%gBim5;6^m=>x2}$Vmix@?-JTBxRHlhjxBNL8A^P$ro%}* zTB5Xp7o_Sj1)s9tCw{NGMDBHZhn;}+oH}9z^=S399?pC6?1e@h$XXtkj;&U8W9d2K z^)pAU;sK_?gMXUO)E`)XY&}PHcw{zZvtccQ@`;ZIeU@K%*;-5(%H(_4S&Y&Wa6P== zIF9%esNTA_7#q}f!8jo9#kD33d*i*-$F{sr_RqXL-F3AoZ z%V|qZ)@J?uw_R@!EAr^K_cxYk9t zL$2Js6&YH?LJygCK1k=lM>gHYAr9!cqSu~F5ckX;&Uu?0#N6J*U1I7X<`x>~P;!N# zPjZ6VPW;NE&6HIkbxsy_H@D)m5PuS?z<)7{waJcj<%~c-L?HMhU1ehZF-nj*`f#6f zlPy3tTI0Y6;E7mVj9c&Kr6q}UEb~?FBF$7Zwb|j}yW$?88a)f!PBE7jPJkS>&vNm! zVYd)`o%nz&8ETKjuLF4qU|g-j0O#)sd5hmN#_G5Vv-Jyc7~m|MPsUtg^L)c*ag$RK zir@@Fmn?KVhnRc^%rS!>5%m#8bt(m}0pc5BkUCZ?qxRT4hJj4>$ zIog+J!72GcS9JWuJOC}Mxi!s&c&*EqV^3CbD?4j$^5x6-;#3Pki&&xJDh+&+?`xFE zYF~B)fE+Dt|rFC>C8~!@{SfO z)T2j|BV{{T@;zFb5UA5m|DCJlleub9r|4-1t>>M^q4zT(%T75(L0p}CVye9l4e zk5R4|2}^6#;QHHwpPQBzuDY0|i1ctnR!v{isy1)}*r2DX~$jsqen$`IbV3`2qxL(tt%7bS`)F8P8mGD|E4?z-KV(3UeR9>vdDG@69bnC~P^gslcD$`VOBU^PtX3SRh*skM+qSCVoWe^m|0@c%0 zKODdt4g`d3JS}s@c(DVwer7gXqih^&#}eSk@GjQ+*Qmn#rrVPBnjcVvOMbn?u!N#; z?f8r}`kj|vpu1339JB;1{L2K4mMLt8#kDERwc-T_>Lu{s65vFM4mIqV#Cxc+q{{0- z=A*?d>Y^H2^bop3;-v=v021Hosi3RSYOPj_aEXEv@g>@ztSQT1cP*Zx2in=kPh2pyBKJD=h! zR{TSXW6Q!Fq~B1Z5mZqQR^9jN1@YLW<6u+8R?*hRuL*fhz)XVG%nqSMSmEVJp|N|# zP&LQN(grRY*++b7Ywi;inMPcAt#&g06BLV=1H{w?*Js42EUV6;GNH9p*q;DePbM|T z-bz_Vo;NMtCu!VcC6-enZNP8C_Y)CXqf^Xnb<$ePQFs8dB{@+`j9L!&ndyxd*NC1P zJfD~bR@@vB5+1Lwwh+t}+WVI#b4u1#(kpC;Wm?_%AY@fm4jMQZqER?f>+WPWTr?+h zQjWq9@OciP){!;&jJ*og_CNv^rw+|nYGgfxJ}Cp#8vrWv%x(Z3K#U!4NZAilL^Qg- zV*yL0iHNJM!&x-gb~4cZB6NdO^AiDiE*_uw^%(P3JjgN`hf%qO6UIo5o)2vU(pjm7uZ=20rXMz4cCa)FT@r% zhzbapr&8Y~L#n)>3j&Q%YL953A!zNuYUp9rOGZ{FC>qw>uh~9O;ISP6$fhL#0C#TL zOqFG;gn}D%Ky^KXv0+=-rpbG?Z6Bz0g9|;!+7|c->?-pH`8WqH#(yGe82l)ED%`v0 zVlDlD_!TN%d^b>^RIBO(L5W8CmK8}*RY1`LfJB-!Ez%tk^}C6D?YT~fhy?L^_=QB~ z)hDQ36$@+B5n$r`^vnReubFKv_M6BuxZe;N$E}=>Se_g){d<$!^EN!&3XosO0u-Pd zAMiw=RA!G7lnX-9W+(`}7DC+>XNRaaG0l&Fi3(p=!ITAtmBuhCUkxkFPXreEXP=mQ zbG1Y>*02LmP7)=L2;T|S$?Srqg;r!s9UACZMkgCo#mb(vQ_aAu3wSl$RT7j~er^fL zzIBOPePi`46kh=M1Ccm84>NKA`4O_O0|cGQUFCH7V5&RROKjkDcyLY}9CEW+^V?-k zK&thzw^3|Wa1WQ6ie!}LGB3!Sc+13x{ycb=wpW#^exVO4D535nWjV3R9%tv5li|Cqz1Os!#TC{T`emp@R3!qmi}lBNODe1f%x?HD$MXu)lC6 zDvNY8lRIUGc=?DhN(wjS7>y`$6}KLt*oHL^x{5xKv`!|fDWqb^Qud!yv&~U)PamZ) zTR|*6F=$cfR6gp%epwVQt<$aXE`n9iappK1o<>AKEG{E&nP-@PcxVbifC&R-vd#2W zY~+&#l9uiuj<;t3xP-T;uHC_+kon5Oi{_X|GLO;(Go3Fhj}Qif%Lu?40A_8Q=u7hCU}GbC3Y~mZe*>2e5=l+AV>c+4d5b#K_9TG|6nh@{foN z81UhJOAY!%`ii(UmcKgyo~k=sT!`(heNAULC0DjwZVRL;0*v-~s6fRmR|Hl%uA)(Aa%)#GrS=?Sjis8!xse5EPQEs1wUxN?}BcCxPx%xY&!|6Ut4IZ!oY$fb;o- zEJelzRm7}5Z@8F7Q5v2mmPFk;h|Q9roic6JwU&8{p8Bv6ffuxP)S%-M1E5EogHZR#($}#) z$Xxh>!kUWUu%-wHbm2ILoa}NJ^EolxHi4mISI2(@DCu=rP*bXD29MYVX+_O60H*&;B zL38dPV~NXrEFCFq82S7`tPP8H-(C87?RWrizQ z955c`4YtDD=#OW}Ts?gAD{3luCDlbt9l)$MtF8K-u(Tscc&HXX*ssL9hd9v))O|Wj zqGD27ej%0RhYu9ZuL@WU)C-i?f2mwFuH8F!)|H;6AI=mK+h}gTGdiFy$#rf+&yH$S zSTg#h`BpL}EVaI2b`t^lf&~gQ+|r7d9bC}Z9un357*CYd;6D#6gRcgSz@e>PC2#iJmy}Qd& z%t63!h~s4JKB9P$V~eVvkX#bAgQ7h_s!kcB$pXo~DbN1JNQyKkA8_Iebx^7F`j`6;XE57jr3;In7cN2R z92ZrZGP$es8RG+Awk;H=$o%}u;!mKd)k333Kvf5{7Q@~R}mCi zn<;&GiZb* z9|O1C4BdKo`t?71>AC&B<&K(xtqc8$oxHG@e<>`H09Z7cW}A0e;`& zVj)HaqXY7O$!OlvxHnW=eIIKPi&U;w`GTHx3yQX>$qSbh0iwC2tMLi;l@T!8Uuv(f z5jM{3fxM@_8+^h&_J+gPI}QzPC1&I<`M6r537l77r~(dXW&F(+T6jMZD0xY${LO|7 z3drRzK>2~4fL$0hs#E3nP|(F;P=|LE!CjQ+u+qHCTnx%klEg%I9Hz;q}2r z`t>r916pHy6<6jjgaX#OxTj~dVCUzODSaKh&X}x&-9NGlYN=IKK%y42*JO3Jmzm99 zQGQe>$RAV*Bv=fLDAJ9K_&KOV0r9~M4xvG7`w{h)r9O+EcD4^$rU9S?Ac3$Cz>dgP zTrZ25oU)tV;{A_c`i!ipYWBeZI)^e~R0pKJvs{~*b$$pwhV7VEJVt9Tk@Lmn&(#Hu7w%q*~wctBvU zX537%Jk>3gnwu;|bkl%TcO@^)MKnNgRu01HAQykv_1w8T_nrA7*E%TX3KZ@R)Ex+_ zh1}(u++nDy0Yi4ZxiT>ZG<54Iy{d|W18cNkNge_}pN&pIY3R6ERPKDEAain{c)91O zxYg~#v^J)8_k^8O>q zYU?aQF4XqnV;ClFiRPg-hlnjjJ2mSM4NBe;yZ-=5#^pK^EU-Kst~|n*LW39T5SRY| zna^lAfO}%z7Q*Y`hN_FOW`>3SOA%vmamK~{3|jvHxRlAD(ua%Y9PP8R9a{sF2P=Gd zm6aS={Z2Xf5w7E)lUu}FtJ*O6A+S?O`ye_=2Z8Dv@-}G=<8ygVp%VV}5n59h11AKa zih1ajFbB%Sip{XeSr0%Hm|i5tQNaPxcjtbIv^=?=oUmi*Gcd;LR|VDT!c>M<94_b7@6 zbHw2T?p19Z)&lY^`SB~b8>FnCVaYxSjBJEbT2Olb0106F3AHHb`9&-i8?k9jP}=hq zZ&OQO{hUP|u@W!^)4+zA6yHneh?Xu?=kpTMmkYa(V$*yjt?faX^BxONx6Q^UunrMp z(O@LaMGPT$};-4$8I4|UB!E_f;> z!^mEjQDBVAq4+*z6S zsQw|!CA}I=#wz@!;NaFirGsAWT8E;6NK*TVfJLyHhFWxZi1=+MsC$Z5-v`t-8s{K< zd`eoqm9J4Oia4QAhQxX5Ap}LC;%~1gzr+-vZd8a|oCBbmV5-Vd8B}uJc$b;ZPLQ{( zXnp|@opc~S5w#P`o8L0TWPA$~Y_wX#G%pgM1fjDoqgn~JSu9&Zcwb~O7fU#?U~5X6 zIb9avu3CJtta-6vmu^b}hNeZ3b2Rw@c@`Tu^Axm!$Aw<4ch$s}YH?J<8g`&_0Ha0a z#kEn6$~EIiIf3|>8 z6;N=dP1a`tvb0yELnYbJX+#)W>TJKnbqICjAJj8G!SCF7HNHL=0>@rZMUZ$%lZ)`9 zMFxs#>M>eiJeL1aiVQH~3@A}bqkO%yd@hQ?uDdOzmMLffO7~g# zC8sj>72nK0m@S}t7RR9wQA6UHiKShiJVpeYeMIu)>_LTnOF^691@qF|o(RnOD^k5U z$333lUNve6J5RZ^XoXkzFo}XG|ggH*ESLd(^8nU0*g zsI9F97)P6=eq=l|wC+{CRh2v{%Ns;3wAmfYW$3H-1~n1pv(yPy_a8h7Q|6zzEfuj@ zpk6TW@p0E|+K?XjU-_7zXm z(or-4XoivI9kDXnPlzt_@=Eso!vGVNckT+Ri!FgD8N)$hJo|xZ>*jYbaQ#lE_}cst z&<>zsJ2Z>>30GAm9TyzbcsXIfGXbi8AO*0xAHrTiaxL`>CBRXm&Bt`xRm+M16^B*| z!b0M_K(PT8+2(B&c(Dfxtc$AxUOW`TQze8hO#%sfd5)m8fpSpVKbebAJ|ofuD>RK+ zJSjs)kq}HcK(@D462c}K9Zn2mO^5#RG*^4f2`N-;=ZSPT3Q>EH;ru|-P!xRnhg8x5 z$ilNowHr|@Pfmd%3AI!d{KlqpGpOFvt4$BMb^%tcfA=Axnw2^6Nm}YO*KffFc9a6X zq3Q%}z?GJ$Cqznl%S`cH%Gu(xNFZK-q#qE3m<-|+a;pVrii(tQUL_QU){e@Y6rrm2 zz$~t+wIU40Y;(&4i0W?Kvc8`Ls`w_Q+#DX|&pU#`(7=npy zF?DqCz-1MB0;%xBGS)6r=$Ya9N-!zTSmP@< zzAh@ZhWZQSt%B*;z5E+4URZ;OR%+$uQo|~-?keyK{{Xl)SR^~{H30SH^8#E|LfhOr z-Hk5T*jqy{#1+Y~pf5b!t<-hhR3U4npSi4DEiS$ww=245h|KNQHuOQPsYB5cON&rB zj)abb7BHDQC_oC^F5p3Zp+u$#a#G-zjU3)$VJ|ka$xs#@A;3dRZNd2vE&7)rYVgYY zhqHJEx9&I#k_vt!*_3bmmMM7!-kX5!$M(PO##YuqgWEiAqbUcQ5g-|va& zg=jk;0TT6*S3STp;I^*>a<-wjg76QY3;DS_;Fcqw+v-vFr0f3xQlY@BrS04cAuBZ_rS)a`CPe~; z^1~-Yc^XzLg@WODD_Ya3K>!RC{^HUp%L1-Z2?`>aauVQhmAMzoA(OkKU@*hUqyaYt zdw>F#=;AP;+O0Sl!fvK_^K#_S;BWr`V5fo#u$lZH(zyYmOxZ^Ie3mcWmwVC_dm`0(bY}-_O{{XV*gXN1?9+(!v9!48s zVv$AnNryjB{&1qDIdNOHdpm}sLF{4Rr5?Kd0^(Ye%U}NNH=9#61g(3Nk*=!#P~bk= zzW_`0ep_o9x?Ht!@enR>rxB-RaBxBOQ07OJt?8yx<5w~RV}uuCj;?U4cPa8#8hzC@ zkA7kEqPXYA#fj-iRZG=k+SVXe#jBz2Qh|;6Oi2#FHBCTD3K;`B6lXqB)mbK^`kgMp zbv`m&VKl6xL0rMX3ktQZ4{*yFSCHG$SIVNGEnL^pEu&j;s~v{vCP!*N1U;~oatTXN z@G1*%dx@J=H?R@HQli0ptgv$BZSX!IORF8VoP$^@Dl`|-)%OoEr-XUEIy`(rNzmmc z2{0aIeXlhELH_`N zk$B4&1n@;Q1DuE4tT7eK@~~;kV;?bwJkS)Cb!v?$hs%sDZ>0N#4OAXavS0w!XC}f8 zaz0_#HCLhr>I}nRr$BJ~P0u~Zd58#Y3OC~5rF^K$`GN!m<)6$4D=kL?1i~`hzcDk0 zSr=(_ID>lbeEmnMmIAk*9Y9V-wk2s!7K@_1Fl|WM5OC-)HtVUFn+!^=9otX9U(5Y! ziZD+oHE=NjW4-1JBV|6L8&%kshRjeb^KVdCCiF{%{)nijmX-Gw!a#E|Nkv)tBgib9 zbRGd`4)aQvV~zyuQJ>Tcfx#~S05Zc%>X~P09s{~7UQxX@K;`6|D)7T=sd)^w%JxJW zEgusZ#a#mCEd1{JhsR3KXuke#8Y`e0`@;q3_$N_yC`vwZ0Q9j%iLnrSapFQOS-}dR2EnVH3i=m03L2<3ocUE z<{}2&!e^!{buOop=h(XfFe19zFDp2O=aN^FU9bsl(R}%3d`+FN)UFUk75Pp1b&1aT**c29`Xo!9CjB?XCZlp7WTR(V*?^IF+FN&%?p zUCR$1M+7%9ZD#&wvW>oF13bj%+Qu6+D!43?DEH-#x-1Jw zuxtzgS6)K*1%y-_?c8?z3q#aF%Af-IM}uTW64RUj2j(+j;mw{Ioe@)c<}_TZZyuuL zcLVf;0*9eLQ>ypJ^XgIvowI@ryw!tcKu#f|kIbNjmD2tZ6)0y4TX=S4DYNEk6Smmb z5q+)#6c0{L6)5@RH3?8GnOEZ9o0ll7bg%MyLoCq;>ZgNE;^Ic!5bp-B%6;xCaC?jqo=Lu0rij>S==2 z2tFasgsRZ-Z~#J}b^%7+f{>IFt6dcJ?p?ZJqWtff$Mv?ip!$c+HiH210Q5m-kg8c~ zBfttaYBs{#Rr>V-i>=w&Uf@N%=Nu7|-MLkJVFB0z-;0H~#l>mdB`5`2vax7*C6ysX zHAmF6NNzO&AiHYeVphvRTHli8_msJ5d9Gd>>XNlhLJMkz8ZPtVBdDgw>OLzS5+)?E z8kV4T_-*IBi*Y43Kg>f9SzLRbX{r3sgWO`!U2LlLR&cwo;%j&{u()8g@gL$1r6pfo z2bURJYTdkoR2_jtNC4MujVSr(zG@JRmb(CBOYBB`ZYo`TN>W`@SumydUDu0`ELgq- zvvfR_Pyp2A)v*Bb0TkQe_=U^{#c$=FGF={Sdz2!l{C5Kw4hQ|=L~_yokw)9T+xwMt zAXV}mzjqdmz!dxtO1>Z$XSfQ1S%zg*T_4V4S1Bz!F1wd9KwaoT-!bUAg%SXEsh&d9 zEqH^t*0D#lh0J%He8oGjM-eY{5o)mYDoFG^`l74ure?^k$l6L zsiw75;HypLkukz9g4T;xl=0)ZXue9QH&LKab-rUuxx$MJ1vshWrcyxR0lsVck97?t zKvdGYC52%aNl4!n28nz;%N(bQ{{Zpra)e(0C z4(Pm`R3K9m(bNcPC?F3p5xZO1^pTR!FfICtf@A?E64~u0{@~*K-PjMzDFERA07!`& zXwdy!3lN=vY~b$^M;VaAXa>lS)N6jaP>;wjXq0|cyX+Zy!%hK_%tP}G&GY#4kEAkYh3 z?T9p>J*#|w+{I7|(!5k%=KlaNQ7rdD^6$QQT%x6`5~2fXx64c2%Z}bs^IJ^`Z_U6l zKYl)t2B*mG8HKlhq)3$7c$O&~$JD6m=&_*_dp`twz?=eHR}dYXuiqX0OL1die{OmD z<`>Z~Hx}IO`SS&p4*vlAmI9pkFh|VCyu`$gj0F@QT)2Q5doo@Hp9WeFBwj}&U|ld$ z%Nj-}A`+VY%w}you(wfdcftM5w-%gTMloR7Uf1&~0}WGTA;~8|g^(;MtgK}k;JLL` ztv2=IbFatA+($&aHYb&qf`2gd%eNf_uQ4roR$o%;V#KvRW6Z1v;&seK;|CY@3jsBk zNUlkUSoa5Tw?a0J*0o>=ddL36oxt0G(Nyjd#Esq0=nQ5wQui1e>-@6vwprY=6KQubdaCB- zONGC3=+7#n1rV#J`7T;o4cE+S_`3=KtGf;fQ>jirMA=FN(ki3QxQ9N4HZRe-xGqyT znnYD9vNQ00Asb~8!n{r%qkt6~cT-#0gO-->(ldEsoSa;3B0*!*53~!)`j>10$Cg&Q za1!g>S|rc`RBi8v?P4q(S9E%1>5vx4$STTmHRcf{JrRewQzGj7eXqExrxgIO{{ZZ9 z0%=vjnNdr+_SaF+@uxkKB2_dQ3Pi*fzflKUXzhG4n5Biz-YmXFG?+ZxKDE8%A=Idw zK^v@Nm8x#5?G3M}xCLyFs2+{A={_Jqf#|;k0|AEFZ-V`8`8>-+`lwLV>->6$X1ap# zz#q4XP{`;2T3;0A_>D@1G=<>h@l$btQhdW?XPwA@;PT^G)x%}#R5HbQ#BIe(xYmgV zLiggifB@ms@9Z8>q@W38AFAR`|AK>Lu3*EoIMR79er2!xbxTIvx9z z55OixkLZU}c~_UOeZ_P%_`zT*6~Fl}!2*pS_2Gh795yOiFl_@z92cDIkLPos0FMo8)?ey|{M+LEwr(fk?`Oh~Jv` z7R57?Tcwm3v>z-NfH|BQ6^l+J(%@BUxS6O;B7ttX zt#3uWLb}>qD_@W*y`3YIO({Z{K3aK-F!p(6frkLZIpQE4xF?2FqqglaED*J8sZD3n zH3TDC@}4Sb@U6+}%!$k3nS>;s`Kl!aoA26h(7nzPqqi9;WM2UjG2*8WsQ*fV#&g z@l%M?MX@qfWaxzU8@1U3i&m=v`BEIOh^#GPoUw6>gUjMnUfE@Q!;QoMr2bAuYE>E-Q9)m?%32SQuZGygJBw(8D zdQQlPWiuQo4!pKs5}8U}6!&ZA>m^L;Bk1GEecTmck{gQyZyTMcG#C z?kuZlfK$~*D*z@ah61vk@HZPx=B;i9MC(i~HLcJ+`k5d!S$@4nk?iZX1P%dd9@y+# z*>G4QO47f$BHpG1Vmg9U{0KE=0);1^i1sel73F33N;k`dB-dkX_Z3UfX&&p;SuT;n zzcYSXCB+a`FLqG(0bKB=Faioog?S~UdXvYi@ey)5Bf$X`{{T4{Krdpv!t3iz!U_Py zxqHcTu+rc|oeEo*@dn*LOH%!MuUP=>1t7E=A$Dz1q%;uBK-Mk<0D_+qkfK?!Xj~_# zsDx|GGaI>170UA~l)E-Lj|jTbyMSBy*yqW7sCczp>r?B>asI#-3fE$D0l`YNf7n5` z;B_$eIACWnn8TOm93Uj?mrH2A;iZ5$HTIX}hb0nfBk3Nn>p8sQC%J;E7Ku3!9?JjVw|f(1+V%jJ~WDzz*=pcsw4;*qJxL_yT> zZ}5PPDDa~`WiAqIt@RXbwXXV)1bO#plo=OB;HtryY=460@b?R#S-_t#J5;Q>U{Gip z$}z)Sx_XAP!Vhd-(zw~o$p>Y@v9+mHp`>WwYkk-EGqkhBYf+`We-TM)znJwvM=^n| zC_o8mb%s?cw&UpFnxzCmtt}irrHR=u*AbpL@`3$8sNXr~sX+E^I`I}YI`8Q#%aO$Y z0NIJT2d@&$mBi+K#67lKXn^wj(Qhn9p;gO%GGRPg*LCh53LDM8i2i_OTx#L3iYvt7 z1*!Au;uDfQ9yW0A)X=aEa^X-d^(ZaW6?F$GZS;*e3{tT@^ozhTxf;Nu|tJOd_&V@z@Ft& z&0V1eitQ=q<~@K+qgWs)X{VXEwQPOBNLhRT03kLq2d$L1%w^)CLugADrMW1!Gi*$R z75BvF!deBft`euu{>?WwxC>nI-17t)mr^(jkUd3^(Qp*jhd+}DR)8=cUx`bMS06;V z(l}l(QBG3yJB)WKx)I9l^~J@brRB?$XNC(d28IfRz#@(oX%>@mfgiZQ;R=UIc@X3a z5V|{VF(@i>y~KJ3tJ5ui1{ANaQz-Y);7^equegP)0n^j&a>NU@QSjY@(7{s77jKv( zjzw7d^$knRJOuH#HXAXH+)4`W;Nwk?zc6iq-m58=6b~2NF*$G39|$dm)9^*Lb<@hVR!9;BvP+33m($n-!Ot9>7tdI?2m-EzX@0IW~MZ(2jO z^&EoBVZ#c*Xmd|&M$*Y$k!eBvNTSd!*H_2HC2%m6Pm7w|N?so^CZLwm&rw_{)?yq+ zV%GwyCDL3%Kze|Mv~3jbTU{J=sdm4+9{gE_XxS+Z193? zZ4cW~_!_a{VO40mmFiJMsaE_Yg^fiz{v|j(&FgUxMFaqK4dhT)L+(3duFr8BDl78~ zYLX410ow}9OBh14?49M1@(8XbubB0g{1Tj{oFNf#p>MUdUQS9z-rbpQ?(HlaUI@0C z!l`}5wjn{jeAGbnTGr1Hynq{BK?z#c!|nkxy6AUt;MKHs>M09H5GQvB0?WuRTMY`} zy5pPi9`SBI%fuc9V-@ut1$UL?EF%?8^IzaZYXP{{Uj3R)F*EX5bPn#3+Q1T0-IIisHVi2}<_;j7L)C4Q{i3W&jd8~ATM zG@)iJ8U!9_FAum#8KYRswYjE^D_<4vU!e&CwU%|*n!lNLn%~5tJg>g(jKazTquezW z*-l&;;_*<^B*y9&nF{ZVbwB8yLZ3O+zxYEi*vx_(HI5F)Zh*Yh4Bk}kD~0b z;j=>0V!?Pq`N+$=x_I}`{cN?#;=m`eD^KT{K_qQM97?nW*8<;C_?|;5_$3IXCO%@d z$gn@sS_oneM(tYZ!`FZv+b-wUP`ySGpgC2t^HLRa_Z$RUsba+Lp@kG3F_w6JM;xov z2za$);sHt2YUm2MxF1kPr{Vxy1)ZwOe=IOXePyxp>Hh#>`UAM2f3m=@#1u*nSYx1O zxw_(CruqQbX~M$rIjUnPSQQHuUmBEGHQcHvl&G!vMr9bnO(Z2UjlFMy4ieLhvf zg8Z=8fuUi@u7B=eH!u{TFD~sXe7W4rK`9-iJ9qR-T9Xzx#(9cli3=|J`j3#_$rY3r zjjkbuD>5EI)qGzIQ$mm-zcWz9U!%V={2uKnp>1XZ(m$c>vej#JciM3>!YsTk*1YpC zW(TIF6%a8Rl_~F90E@G?7UX=&Qit3|)xUG{Ld6kjg#iRY*1lz`im;c89H|#nA@qA< zl~Pq*xCG*OV1v&MT=kEOiYalx!UaEf0Iiu^h%meN9X)Q~X97b10I*?424@Hg=A?OT zqavKx3D~ZM3eX+;hyjKF0B{EKO0ZFW<01}_6|2~xSAPbSx@{J^wJUXY zp{uG|@yg7J$iF>BB(3sjp^N2+sS zqi&9Ce=(eeD4_A%#|IMqw$%i$x*WNt3N9q;lAhM_$0v!11}M-RcmoT3#$hXN!g8P% z{{Zf0u*L|Xpy!*VJrQkd2Eg(+c(w21Gq$_!xl@-mUfxR`%dx)HYRw!)P4Nf1i1|-g zomc8sduNU!7(OfcMSl9HWwpe-mX+HB%DyA&Bq7eA>vTtwaoV1vb^gNM{w1J70V|;2 zS63KmV*q%=1J|YHmvcntf|k0K8rj2g)Nldja+o5Z8msQ(P*4L$x96!qBJ^B&(7NJR z#WXag{{V4^O?k`yM2{`QdF0Y4<$XQ6(x*8ym)+t3lF*OIG;`)y_FF`AFDg=il+!+VNCG026Rs-0PGkTtnmMrBv zu$iLv#zQXQ6;;}`5f7bmoVNb}g-aB(d=BL>Q?lg>u7*#-f}g3ik#%qKID@cAg0Rk9 zg(}`g92knK{{V@@QEcrIFI7Y!$OWZf#R^U>mL^?Q4H`HrA^fwl;rbd4*dSaWA%VsR zsY<_jW7uUZ;#-2eBC=JB5i0qN7?%p+gf2n><}#+(P`7ahmUxN^(DAk4fMAcPCTa01 zc`wAhO-|^P4i}^w0PhgE(&JkUwpH;^nFU9JQaruOjWPvN%X>TyA`P5Ez?6?$OXh$7 E*$u$4UjP6A literal 0 HcmV?d00001 diff --git a/firmware/inc/gps.h b/firmware/inc/gps.h index b3596f8..5af9eac 100644 --- a/firmware/inc/gps.h +++ b/firmware/inc/gps.h @@ -56,6 +56,36 @@ volatile struct ubx_cfg_nav5 { uint32_t res3; uint32_t res4; } __PACKED__ ubx_cfg_nav5; +/** + * UBX CFG TP TimePulse Parameters + */ +volatile struct ubx_cfg_tp { + uint32_t interval; + uint32_t length; + int8_t status; + uint8_t timeRef; + uint8_t flags; + uint8_t res; + int16_t antennaCableDelay; + int16_t rfGroupDelay; + int32_t userDelay; +} __PACKED__ ubx_cfg_tp; +/** + * UBX CFG TP5 TimePulse Parameters + */ +volatile struct ubx_cfg_tp5 { + uint8_t tpIdx; + uint8_t res0; + uint16_t res1; + int16_t antCableDelay; + int16_t rfGroupDelay; + uint32_t freqPeriod; + uint32_t freqPeriodLoc; + uint32_t pulseLenRatio; + uint32_t pulseLenRatioLock; + int32_t userConfigDelay; + uint32_t flags; +} __PACKED__ ubx_cfg_tp5; /** * UBX NAV POSLLH Geodetic Position Solution */ @@ -106,6 +136,19 @@ volatile struct ubx_nav_sol { uint32_t res2; } __PACKED__ ubx_nav_sol; +/** + * UBX Dynamic Platform Model + */ +enum { + UBX_PLATFORM_MODEL_PORTABLE = 0, + UBX_PLATFORM_MODEL_STATIONARY = 2, + UBX_PLATFORM_MODEL_PEDESTRIAN = 3, + UBX_PLATFORM_MODEL_AUTOMOTIVE = 4, + UBX_PLATFORM_MODEL_SEA = 5, + UBX_PLATFORM_MODEL_AIRBORNE_1G = 6, + UBX_PLATFORM_MODEL_AIRBORNE_2G = 7, + UBX_PLATFORM_MODEL_AIRBORNE_4G = 8, +}; void usart_loopback_test(void); diff --git a/firmware/inc/hw_config.h b/firmware/inc/hw_config.h index 01b6eaf..5870f67 100644 --- a/firmware/inc/hw_config.h +++ b/firmware/inc/hw_config.h @@ -76,8 +76,19 @@ #define GPS_TIME_PIN PIN_PA28 #define GPS_TIME_PINMUX PINMUX_PA28H_GCLK_IO0 #define GPS_SERCOM_MUX USART_RX_1_TX_0_XCK_1 +#define GPS_TIMEPULSE_FREQ 24000000 +// 32768 +#define GPS_PLATFORM_MODEL UBX_PLATFORM_MODEL_AIRBORNE_1G -/* Loopback Testing */ +/** + * DFLL48 + */ +#define DFLL48M_GCLK GCLK_GENERATOR_0 +#define DFLL48M_CLK 48000000 + +/** + * USART Loopback Testing + */ #define USART_MUX_LOOPBACK USART_RX_0_TX_0_XCK_1 /** @@ -102,21 +113,25 @@ /** * Radio */ -#define RADIO_SERCOM (SercomSpi*)SERCOM3 -#define RADIO_SERCOM_MOSI_PIN PIN_PA19 -#define RADIO_SERCOM_MOSI_PINMUX PINMUX_PA19D_SERCOM3_PAD3 -#define RADIO_SERCOM_MISO_PIN PIN_PA22 -#define RADIO_SERCOM_MISO_PINMUX PINMUX_PA22C_SERCOM3_PAD0 -#define RADIO_SERCOM_SCK_PIN PIN_PA23 -#define RADIO_SERCOM_SCK_PINMUX PINMUX_PA23C_SERCOM3_PAD1 -#define RADIO_SEL_PIN PIN_PA18 -#define RADIO_IRQ_PIN PIN_PA24 -#define RADIO_IRQ_PINMUX PINMUX_PA24A_EIC_EXTINT12 -#define RADIO_HF_CLK_PIN PIN_PA17 -#define RADIO_HF_CLK_PINMUX PINMUX_PA17H_GCLK_IO3 -#define RADIO_SDN_PIN PIN_PA16 -#define RADIO_GPIO0_PIN PIN_PA27 -#define RADIO_GPIO1_PIN PIN_PA25 /* Shared with LED */ +#define SI406X_SERCOM (SercomSpi*)SERCOM3 +#define SI406X_SERCOM_MOSI_PIN PIN_PA19 +#define SI406X_SERCOM_MOSI_PINMUX PINMUX_PA19D_SERCOM3_PAD3 +#define SI406X_SERCOM_MISO_PIN PIN_PA22 +#define SI406X_SERCOM_MISO_PINMUX PINMUX_PA22C_SERCOM3_PAD0 +#define SI406X_SERCOM_SCK_PIN PIN_PA23 +#define SI406X_SERCOM_SCK_PINMUX PINMUX_PA23C_SERCOM3_PAD1 +#define SI406X_SERCOM_MUX SPI_SIGNAL_MUX_SETTING_D +#define SI406X_SEL_PIN PIN_PA18 +#define SI406X_IRQ_PIN PIN_PA24 +#define SI406X_IRQ_PINMUX PINMUX_PA24A_EIC_EXTINT12 +#define SI406X_HF_GCLK GCLK_GENERATOR_3 +#define SI406X_HF_CLK_PIN PIN_PA17 +#define SI406X_HF_CLK_PINMUX PINMUX_PA17H_GCLK_IO3 +/* Currently half GPS TIMEPULSE */ +#define SI406X_HF_FREQUENCY (GPS_TIMEPULSE_FREQ / 2) +#define SI406X_SDN_PIN PIN_PA16 +#define SI406X_GPIO0_PIN PIN_PA27 +#define SI406X_GPIO1_PIN PIN_PA25 /* Shared with LED */ /** * SWD diff --git a/firmware/inc/si4060.h b/firmware/inc/si4060.h new file mode 100644 index 0000000..96bfd61 --- /dev/null +++ b/firmware/inc/si4060.h @@ -0,0 +1,221 @@ +/* + * si4060 software library + * + * Stefan Biereigel + * + */ +#ifndef SI4060_H_ +#define SI4060_H_ + +#include +#include "samd20.h" +#include "system/port.h" +#include "hw_config.h" + + +/** + * Chip Select. Active Low (High = Inactive, Low = Active) + */ +#define _si406x_cs_enable() \ + port_pin_set_output_level(SI406X_SEL_PIN, 0) +#define _si406x_cs_disable() \ + port_pin_set_output_level(SI406X_SEL_PIN, 1) + +#define spi_select _si406x_cs_enable +#define spi_deselect _si406x_cs_disable +#define spi_write spi_bitbang_transfer +#define spi_read() spi_bitbang_transfer(0xff) + +#define __delay_cycles(n) do { \ + for (int cyc = 0; cyc < n; cyc++) { __NOP(); } \ + } while(0) + +void si4060_shutdown(void); + + +#define XO_FREQ 16000000UL +#define RF_FREQ_HZ 434600000.0f +#define RF_DEV_HZ 100.0f + +#define F_INT (2 * XO_FREQ / 8) +#define FDIV_INTE ( (RF_FREQ_HZ / F_INT) - 1) +#define FDIV_FRAC ( (RF_FREQ_HZ - F_INT*(int)FDIV_INTE) * ((uint32_t)1 << 19) ) / F_INT +#define FDEV ( ( ( (uint32_t)1 << 19) * 8 * RF_DEV_HZ)/ (2*XO_FREQ)) + +/* function prototypes */ +void si4060_shutdown (void); +void si4060_wakeup (void); +void si4060_reset (void); +void si4060_power_up (void); +void si4060_nop (void); +uint8_t si4060_get_state(void); +void si4060_get_freq(void); + +void si4060_start_tx (uint8_t channel); +void si4060_stop_tx (void); +void si4060_setup (uint8_t mod_type); +uint8_t si4060_get_property_8 (uint8_t group, uint8_t prop); +uint16_t si4060_part_info (void); + +/* ===== command definitions ===== */ +#define CMD_NOP 0x00 +#define CMD_PART_INFO 0x01 +#define CMD_POWER_UP 0x02 +#define CMD_SET_PROPERTY 0x11 +#define CMD_GET_PROPERTY 0x12 +#define CMD_GPIO_PIN_CFG 0x13 +#define CMD_START_TX 0x31 +#define CMD_REQUEST_STATE 0x33 +#define CMD_CHANGE_STATE 0x34 +#define CMD_READ_CMD_BUF 0x44 + +/* ===== device states ===== */ +#define STATE_NOCHANGE 0x00 +#define STATE_SLEEP 0x01 +#define STATE_SPI_ACTIVE 0x02 +#define STATE_READY 0x03 +#define STATE_TX_TUNE 0x05 +#define STATE_TXA 0x07 + +/* ===== property group definitions ===== */ +#define PROP_GLOBAL 0x00 +#define PROP_INT_CTL 0x01 +#define PROP_FRR_CTL 0x02 +#define PROP_PREAMBLE 0x10 +#define PROP_SYNC 0x11 +#define PROP_PKT 0x12 +#define PROP_MODEM 0x20 +#define PROP_PA 0x22 +#define PROP_SYNTH 0x23 +#define PROP_FREQ_CONTROL 0x40 + +/* ===== property definitions ===== */ +/* global properties */ +#define GLOBAL_XO_TUNE 0x00 +#define GLOBAL_CONFIG 0x03 +/* preamble properties */ +#define PREAMBLE_TX_LENGTH 0x00 +/* sync properties */ +#define SYNC_CONFIG 0x11 +/* modem properties */ +#define MODEM_MOD_TYPE 0x00 +#define MODEM_FREQ_DEV 0x0a +#define MODEM_FREQ_OFFSET 0x0d +#define MODEM_CLKGEN_BAND 0x51 +/* PA properties */ +#define PA_MODE 0x00 +#define PA_PWR_LVL 0x01 +#define PA_BIAS_CLKDUTY 0x02 +/* synthesizer properties */ +#define SYNTH_PFDCP_CPFF 0x00 +#define SYNTH_PFDCP_CPINT 0x01 +#define SYNTH_VCO_KV 0x02 +#define SYNTH_LPFILT3 0x03 +#define SYNTH_LPFILT2 0x04 +#define SYNTH_LPFILT1 0x05 +#define SYNTH_LPFILT0 0x06 +#define SYNTH_VCO_KVCAL 0x07 +/* frequency control properties */ +/* INTE shall be decreased by 1, because FRAC shall be between 1 and 2 */ +#define FREQ_CONTROL_INTE 0x00 +/* FRAC shall be added to 2**19, to ensure MSB is set! */ +#define FREQ_CONTROL_FRAC 0x01 +#define FREQ_CONTROL_CHANNEL_STEP_SIZE 0x04 +#define FREQ_CONTROL_W_SIZE 0x06 + +/* ===== command arguments ===== */ +/* POWER_UP arguments */ +/* byte 1 */ +#define PATCH ( 0x01 << 7) /* set patch mode */ +#define FUNC 0x01 /* power on device */ +/* byte 2 */ +#define TCXO 0x01/* select if TCXO (1) or crystal (0) is used */ + +/* GPIO_PIN_CFG arguments */ +/* bytes 1 .. 6 */ +#define PULL_CTL 0x40 /* enable or disable pull-up resistor */ +/* bytes 1 .. 4 */ +#define GPIO_MODE_DONOTHING 0x00/* pin behaviour is not changed */ +#define GPIO_MODE_TRISTATE 0x01/* input and output drivers are disabled */ +#define GPIO_MODE_DRIVE0 0x02/* CMOS output "low" */ +#define GPIO_MODE_DRIVE1 0x03/* CMOS output "high" */ +#define GPIO_MODE_INPUT 0x04/* GPIO is input, for TXDATA etc, function is not configured here */ +#define GPIO_MODE_32K_CLK 0x05/* outputs the 32kHz CLK when selected in CLK32_CLK_SEL */ +#define GPIO_MODE_BOOT_CLK 0x06/* outputs boot clock when SPI_ACTIVE */ +#define GPIO_MODE_DIV_CLK 0x07/* outputs divided xtal clk */ +#define GPIO_MODE_CTS 0x08/* output, '1' when device is ready to accept new command */ +#define GPIO_MODE_INV_CNT 0x09/* output, inverted CTS */ +#define GPIO_MODE_CMD_OVERLAP 0x0a/* output, '1' if a command was issued while not ready */ +#define GPIO_MODE_SDO 0x0b/* output, serial data out for SPI */ +#define GPIO_MODE_POR 0x0c/* output, '0' while in POR state */ +#define GPIO_MODE_CAL_WUT 0x0d/* output, '1' on expiration of wake up timer */ +#define GPIO_MODE_WUT 0x0e/* wake up timer output */ +#define GPIO_MODE_EN_PA 0x0f/* output, '1' when PA is enabled */ +#define GPIO_MODE_TX_DATA_CLK 0x10/* data clock output, for TX direct sync mode */ +#define GPIO_MODE_TX_DATA 0x11/* data output from TX FIFO, for debugging purposes */ +#define GPIO_MODE_IN_SLEEP 0x12/* output, '0' when in sleep state */ +#define GPIO_MODE_TX_STATE 0x13/* output, '1' when in TX state */ +#define GPIO_MODE_TX_FIFO_EMPTY 0x14/* output, '1' when FIFO is empty */ +#define GPIO_MODE_LOW_BATT 0x15/* output, '1' if low battery is detected */ +/* byte 5 omitted - no IRQ support */ +#define NIRQ_MODE_DONOTHING 0x00 +/* byte 6 omitted - no SDO reconfiguration support */ +#define SDO_MODE_DONOTHING 0x00 +/* byte 7 */ +#define DRV_STRENGTH_HIGH ( 0x00 << 5) +#define DRV_STRENGTH_MED_HIGH ( 0x01 << 5) +#define DRV_STRENGTH_MED_LOW ( 0x02 << 5) +#define DRV_STRENGTH_LOW ( 0x03 << 5) + +/* START_TX arguments */ +/* byte 2 */ +#define START_TX_TXC_STATE_NOCHANGE ( 0x00 << 4) +#define START_TX_TXC_STATE_SLEEP ( 0x01 << 4) +#define START_TX_TXC_STATE_SPI_ACTIVE ( 0x02 << 4) +#define START_TX_TXC_STATE_READY ( 0x03 << 4) +#define START_TX_RETRANSMIT_0 ( 0x00 << 2)/* send data that has been written to the TX FIFO */ +#define START_TX_START_IMM ( 0x00 << 0)/* start transmission immediately */ + +/* ===== property values ===== */ +/* GLOBAL_CONFIG values */ +#define GLOBAL_RESERVED ( 0x01 << 6) /* shall be set to 1 */ +#define POWER_MODE_LOW_POWER 0x00/* default */ +#define POWER_MODE_HIGH_PERF 0x01 +#define SEQUENCER_MODE_FAST ( 0x00 << 5)/* default */ +#define SEQUENCER_MODE_GUARANT ( 0x01 << 5) +/* SYNC_CONFIG values */ +#define SYNC_XMIT ( 0x00 << 7)/* default */ +#define SYNC_NO_XMIT ( 0x01 << 7) +/* MODEM_MOD_TYPE values */ +#define MOD_TYPE_CW 0x00 +#define MOD_TYPE_OOK 0x01 +#define MOD_TYPE_2FSK 0x02/* default */ +#define MOD_TYPE_2GFSK 0x03 +#define MOD_TYPE_4FSK 0x04 +#define MOD_TYPE_4GFSK 0x05 +#define MOD_SOURCE_PACKET ( 0x00 << 3) /* default */ +#define MOD_SOURCE_DIRECT ( 0x01 << 3) +#define MOD_SOURCE_PSEUDO ( 0x02 << 3) +#define MOD_GPIO_0 ( 0x00 << 5) /* default */ +#define MOD_GPIO_1 ( 0x01 << 5) +#define MOD_GPIO_2 ( 0x02 << 5) +#define MOD_GPIO_3 ( 0x03 << 5) +#define MOD_DIRECT_MODE_SYNC ( 0x00 << 7) /* default */ +#define MOD_DIRECT_MODE_ASYNC ( 0x01 << 7) +/* MODEM_CLKGEN_BAND values */ +#define SY_SEL_0 ( 0x00 << 3) /* low power */ +#define SY_SEL_1 ( 0x01 << 3) /* default */ +#define FVCO_DIV_4 0x00 /* default */ +#define FVCO_DIV_6 0x01 +#define FVCO_DIV_8 0x02 /* for 70cm ISM band */ +#define FVCO_DIV_12 0x03 +#define FVCO_DIV_16 0x04 +#define FVCO_DIV_24 0x05 +#define FVCO_DIV_24_2 0x06 +#define FVCO_DIV_24_3 0x07 +/* PA_MODE values*/ +/* PA_BIAS_CLKDUTY values */ +#define PA_BIAS_CLKDUTY_SIN_25 (0x03 << 6) /* for si4060 */ +#define PA_BIAS_CLKDUTY_DIFF_50 (0x00 << 6) /* for si4063 */ + +#endif diff --git a/firmware/inc/si406x.h b/firmware/inc/si406x.h new file mode 100644 index 0000000..5b10f06 --- /dev/null +++ b/firmware/inc/si406x.h @@ -0,0 +1,31 @@ +/* + * + * Copyright (C) 2014 Richard Meadows + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef SI406X_H +#define SI406X_H + +void si406x_init(void); +void spi_loopback_test(void); + +#endif /* SI406X_H */ diff --git a/firmware/inc/si406x_defs.h b/firmware/inc/si406x_defs.h new file mode 100644 index 0000000..762fe97 --- /dev/null +++ b/firmware/inc/si406x_defs.h @@ -0,0 +1,161 @@ +/* + * Definitions and macros for the Si406x + * Copyright (C) 2014 Richard Meadows + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef SI406X_DEFS_H +#define SI406X_DEFS_H + +/** + * Si406x Boot Commands + */ +enum { + SI_CMD_POWER_UP = 0x02, +}; +/** + * Si406x Common Commands + */ +enum { + SI_CMD_NOP = 0x00, + SI_CMD_PART_INFO = 0x01, + SI_CMD_FUNC_INFO = 0x10, + SI_CMD_SET_PROPERTY = 0x11, + SI_CMD_GET_PROPERTY = 0x12, + SI_CMD_GPIO_PIN_CFG = 0x13, + SI_CMD_FIFO_INFO = 0x15, + SI_CMD_GET_INT_STATUS = 0x20, + SI_CMD_REQUEST_DEVICE_STATE = 0x33, + SI_CMD_CHANGE_STATE = 0x34, + SI_CMD_READ_CMD_BUFF = 0x44, + SI_CMD_FRR_A_READ = 0x50, + SI_CMD_FRR_B_READ = 0x51, + SI_CMD_FRR_C_READ = 0x53, + SI_CMD_FRR_D_READ = 0x57, +}; +/** + * Si406x Tx Commands + */ +enum { + SI_CMD_START_TX = 0x31, + SI_CMD_WRITE_TX_FIFO = 0x66, +}; +/** + * Si406x Rx Commands + */ +enum { + SI_CMD_PACKET_INFO = 0x16, + SI_CMD_GET_MODEM_STATUS = 0x22, + SI_CMD_START_RX = 0x32, + SI_CMD_RX_HOP = 0x36, + SI_CMD_READ_RX_FIFO = 0x77, +}; +/** + * Si406x Advanced Commands + */ +enum { + SI_CMD_GET_ADC_READING = 0x14, + SI_CMD_PROTOCOL_CFG = 0x18, + SI_CMD_GET_PH_STATUS = 0x21, + SI_CMD_GET_CHIP_STATUS = 0x23, +}; + +/** + * Si406x State Change Commands + */ +enum { + SI_STATE_CHANGE_NOCHANGE = (0 << 8) | SI_CMD_CHANGE_STATE, + SI_STATE_CHANGE_SLEEP = (1 << 8) | SI_CMD_CHANGE_STATE, + SI_STATE_CHANGE_SPI_ACTIVE = (2 << 8) | SI_CMD_CHANGE_STATE, + SI_STATE_CHANGE_READY = (3 << 8) | SI_CMD_CHANGE_STATE, + SI_STATE_CHANGE_TX_TUNE = (5 << 8) | SI_CMD_CHANGE_STATE, + SI_STATE_CHANGE_RX_TUNE = (6 << 8) | SI_CMD_CHANGE_STATE, + SI_STATE_CHANGE_TX = (7 << 8) | SI_CMD_CHANGE_STATE, + SI_STATE_CHANGE_RX = (8 << 8) | SI_CMD_CHANGE_STATE, +}; + +/** + * Generic SPI Send / Receive for the Si406x + */ +void _si406x_transfer(int tx_count, int rx_count, const uint8_t *data); + +/** + * Chip Select. Active Low (High = Inactive, Low = Active) + */ +#define _si406x_cs_enable() \ + port_pin_set_output_level(SI406X_SEL_PIN, 0) +#define _si406x_cs_disable() \ + port_pin_set_output_level(SI406X_SEL_PIN, 1) + +/** + * Shutdown. Active High (High = Shutdown, Low = Run) + */ +#define _si406x_sdn_enable() \ + port_pin_set_output_level(SI406X_SDN_PIN, 1) +#define _si406x_sdn_disable() \ + port_pin_set_output_level(SI406X_SDN_PIN, 0) + +/** + * HF Clock + */ +#define _si406x_hf_clock_enable(void) \ + /* TODO: Clock is always enabled */ +#define _si406x_hf_clock_disable(void) \ + /* TODO: Clock is always enabled */ + + +/** + * Convenience transfer functions + */ +static void _si406x_transfer_uint16(uint16_t value) +{ + _si406x_transfer(2, 0, (uint8_t*)&value); +} + +/** + * State changes + */ +#define si406x_state_ready() \ + _si406x_transfer_uint16(SI_STATE_CHANGE_READY) +/** + * Change to TX tune state + */ +#define si406x_state_tx_tune() \ + _si406x_transfer_uint16(SI_STATE_CHANGE_TX_TUNE) +/** + * Change to RX tune state + */ +#define si406x_state_rx_tune() \ + _si406x_transfer_uint16(SI_STATE_CHANGE_RX_TUNE) +/** + * Change to TX state + */ +#define si406x_state_tx() \ + _si406x_transfer_uint16(SI_STATE_CHANGE_TX) +/** + * Change to RX state + */ +#define si406x_state_rx() \ + _si406x_transfer_uint16(SI_STATE_CHANGE_RX) + + + +#endif /* SI406X_DEFS_H */ diff --git a/firmware/inc/spi_bitbang.h b/firmware/inc/spi_bitbang.h new file mode 100644 index 0000000..90b3fa6 --- /dev/null +++ b/firmware/inc/spi_bitbang.h @@ -0,0 +1,33 @@ +/* + * SPI bit-banging! Yay + * Copyright (C) 2014 Richard Meadows + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef SPI_BITBANG_H +#define SPI_BITBANG_H + +void spi_bitbang_init(const uint8_t mosi, + const uint8_t miso, + const uint8_t sck); +uint8_t spi_bitbang_transfer(const uint8_t byte); + +#endif /* SPI_BITBANG_H */ diff --git a/firmware/inc/system/conf_clocks.h b/firmware/inc/system/conf_clocks.h index 7240158..9c4e2bd 100644 --- a/firmware/inc/system/conf_clocks.h +++ b/firmware/inc/system/conf_clocks.h @@ -82,7 +82,7 @@ # define CONF_CLOCK_OSC32K_RUN_IN_STANDBY false /* SYSTEM_CLOCK_SOURCE_DFLL configuration - Digital Frequency Locked Loop */ -# define CONF_CLOCK_DFLL_ENABLE true +# define CONF_CLOCK_DFLL_ENABLE false # define CONF_CLOCK_DFLL_LOOP_MODE SYSTEM_CLOCK_DFLL_LOOP_MODE_OPEN # define CONF_CLOCK_DFLL_ON_DEMAND false diff --git a/firmware/inc/tc/tc_driver.h b/firmware/inc/tc/tc_driver.h index 569bb22..1c4311a 100644 --- a/firmware/inc/tc/tc_driver.h +++ b/firmware/inc/tc/tc_driver.h @@ -272,10 +272,6 @@ * device is two when running in 32-bit mode and four in 8-, and 16-bit modes. */ -//#include -//#include -//#include - #include "samd20.h" #include "tc.h" #include @@ -522,15 +518,15 @@ struct tc_pwm_channel { }; -static inline void tc_enable(Tc* const hw); -static inline void tc_disable(Tc* const hw); +void tc_enable(Tc* const hw); +void tc_disable(Tc* const hw); -static inline void tc_start_counter(Tc* const hw); -static inline void tc_stop_counter(Tc* const hw); +void tc_start_counter(Tc* const hw); +void tc_stop_counter(Tc* const hw); -static inline uint32_t tc_get_status(Tc* const hw); -static inline void tc_clear_status(Tc* const hw, - const uint32_t status_flags); +uint32_t tc_get_status(Tc* const hw); +void tc_clear_status(Tc* const hw, + const uint32_t status_flags); void tc_set_count_value(Tc* const hw, const uint32_t count); uint32_t tc_get_count_value(Tc* const hw); @@ -546,9 +542,9 @@ void tc_reset(Tc* const hw); void tc_set_top_value (Tc* const hw, const uint32_t top_value); -static inline void tc_enable_events(Tc* const hw, +void tc_enable_events(Tc* const hw, struct tc_events *const events); -static inline void tc_disable_events(Tc* const hw, +void tc_disable_events(Tc* const hw, struct tc_events *const events); enum tc_status_t tc_init(Tc* const hw, diff --git a/firmware/inc/timepulse.h b/firmware/inc/timepulse.h new file mode 100644 index 0000000..4882af9 --- /dev/null +++ b/firmware/inc/timepulse.h @@ -0,0 +1,31 @@ +/* + * Functions for turning the GPS timepulse into a HF Clock + * Copyright (C) 2014 Richard Meadows + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef TIMEPULSE_H +#define TIMEPULSE_H + +void timepulse_init(void); +void switch_gclk_main_to_timepulse(void); + +#endif /* TIMEPULSE_H */ diff --git a/firmware/src/gps.c b/firmware/src/gps.c index dea9ae1..f810910 100644 --- a/firmware/src/gps.c +++ b/firmware/src/gps.c @@ -67,8 +67,10 @@ enum { */ enum { UBX_CFG_PRT = (UBX_CFG | (0x00 << 8)), + UBX_CFG_TP = (UBX_CFG | (0x07 << 8)), UBX_CFG_ANT = (UBX_CFG | (0x13 << 8)), UBX_CFG_NAV5 = (UBX_CFG | (0x24 << 8)), + UBX_CFG_TP5 = (UBX_CFG | (0x31 << 8)), }; /** * UBX ACK Message Types @@ -77,19 +79,6 @@ enum { UBX_ACK_NACK = (UBX_ACK | (0x00 << 8)), UBX_ACK_ACK = (UBX_ACK | (0x01 << 8)), }; -/** - * UBX Dynamic Platform Model - */ -enum { - UBX_PLATFORM_MODEL_PORTABLE = 0, - UBX_PLATFORM_MODEL_STATIONARY = 2, - UBX_PLATFORM_MODEL_PEDESTRIAN = 3, - UBX_PLATFORM_MODEL_AUTOMOTIVE = 4, - UBX_PLATFORM_MODEL_SEA = 5, - UBX_PLATFORM_MODEL_AIRBORNE_1G = 6, - UBX_PLATFORM_MODEL_AIRBORNE_2G = 7, - UBX_PLATFORM_MODEL_AIRBORNE_4G = 8, -}; #define UBX_BUFFER_LEN 0x80 @@ -110,7 +99,33 @@ uint8_t ubx_irq_buffer[UBX_BUFFER_LEN]; #define _get_buffer(rx_data, length) \ usart_read_buffer_wait(GPS_SERCOM, rx_data, length) +/** + * Flags for pending ubx pakcets + */ +enum ubx_packet_state { + UBX_PACKET_WAITING, + UBX_PACKET_ACK, + UBX_PACKET_NACK, +}; +enum ubx_packet_state _ubx_cfg_tp_state; +enum ubx_packet_state _ubx_cfg_tp5_state; +/** + * Processes UBX ack/nack packets + */ +void ubx_process_ack(uint16_t message, enum ubx_packet_state state) +{ + switch (message) { + case UBX_CFG_TP: + _ubx_cfg_tp_state = state; + break; + case UBX_CFG_TP5: + _ubx_cfg_tp5_state = state; + break; + default: + break; + } +} /** * Macro for the function below */ @@ -118,7 +133,6 @@ uint8_t ubx_irq_buffer[UBX_BUFFER_LEN]; if (payload_length == sizeof(ubx_type)) { \ memcpy((void*)&ubx_type, frame + 4, payload_length); \ } - /** * Process a single ubx frame. Runs in the IRQ so should be short and sweet. */ @@ -150,14 +164,25 @@ void ubx_process_frame(uint8_t* frame) break; case UBX_CFG_ANT: /* Antenna Control Settings */ UBX_POPULATE_STRUCT(ubx_cfg_ant); + break; + case UBX_CFG_TP: /* TimePulse Parameters */ + UBX_POPULATE_STRUCT(ubx_cfg_tp); + break; + case UBX_CFG_TP5: /* TimePulse Parameters */ + UBX_POPULATE_STRUCT(ubx_cfg_tp5); + break; } break; case UBX_ACK: - switch (frame16[0]) { - case UBX_ACK_ACK: - break; - case UBX_ACK_NACK: - break; + if (payload_length == 2) { /* All ACK packets should have a payload len of 2 */ + switch (frame16[0]) { + case UBX_ACK_ACK: + ubx_process_ack(frame16[2], UBX_PACKET_ACK); + break; + case UBX_ACK_NACK: + ubx_process_ack(frame16[2], UBX_PACKET_NACK); + break; + } } break; default: @@ -293,6 +318,67 @@ void gps_check_nav(void) _ubx_poll(UBX_CFG_NAV5); } +/** + * Set the GPS timepulse settings using the CFG_TP message + */ +void gps_set_timepulse(void) +{ + /* Clear the packet state */ + _ubx_cfg_tp_state = UBX_PACKET_WAITING; + + /* Send the Request */ + _ubx_poll(UBX_CFG_TP); + + /* Define the settings we want */ + struct ubx_cfg_tp timepulse; + memset(&timepulse, 0, sizeof(ubx_cfg_tp)); + timepulse.interval = 2; /* 2µS */ + timepulse.length = 1; /* 1µS */ + timepulse.status = 1; /* On, Positive */ + timepulse.timeRef = 1; /* Align GPS time */ + timepulse.flags = 0x1; /* Run outside lock */ + timepulse.antennaCableDelay = 50; /* 50 nS */ + + /* Wait for acknoledge */ + while (_ubx_cfg_tp_state == UBX_PACKET_WAITING); + + /* Compare with current settings */ + if (memcmp((void*)&ubx_cfg_tp, &timepulse, sizeof(ubx_cfg_tp)) != 0) { + /* Write the new settings */ + _ubx_send_message(UBX_CFG_TP, (uint8_t*)&timepulse, sizeof(ubx_cfg_tp)); + } +} +/** + * Set the GPS timepulse settings using the CFG_TP5 message + */ +void gps_set_timepulse_five(uint32_t frequency) +{ + /* Clear the packet state */ + _ubx_cfg_tp5_state = UBX_PACKET_WAITING; + + /* Send the Request */ + _ubx_poll(UBX_CFG_TP5); + + /* Define the settings we want */ + struct ubx_cfg_tp5 timepulse5; + memset(&timepulse5, 0, sizeof(ubx_cfg_tp5)); + timepulse5.tpIdx = 0; + timepulse5.antCableDelay = 50; /* 50 nS */ + /* GPS time, duty cyle, frequency, lock to GPS, active */ + timepulse5.flags = 0x80 | 0x8 | 0x3; + timepulse5.freqPeriod = frequency; + timepulse5.pulseLenRatio = 0x80000000; /* 50 % duty cycle*/ + + /* Wait for acknoledge */ + while (_ubx_cfg_tp5_state == UBX_PACKET_WAITING); + + /* Compare with current settings */ + if (memcmp((void*)&ubx_cfg_tp5, &timepulse5, sizeof(ubx_cfg_tp5)) != 0) { + /* Write the new settings */ + _ubx_send_message(UBX_CFG_TP5, (uint8_t*)&timepulse5, sizeof(ubx_cfg_tp5)); + } +} + /** * Init */ @@ -334,9 +420,16 @@ void gps_init(void) /* Incoming ubx messages are handled in an irq */ usart_register_rx_callback(GPS_SERCOM, gps_rx_callback, 0); - gps_check_nav(); + /* Fill some configuration structures */ + gps_check_lock(); _ubx_poll(UBX_CFG_ANT); + + /* */ + gps_check_nav(); + + /* Set the timepulse */ + gps_set_timepulse_five(GPS_TIMEPULSE_FREQ); } diff --git a/firmware/src/main.c b/firmware/src/main.c index 19b3055..ae7b0a5 100644 --- a/firmware/src/main.c +++ b/firmware/src/main.c @@ -30,49 +30,129 @@ #include "hw_config.h" #include "system/system.h" #include "sercom/usart.h" +#include "system/port.h" #include "gps.h" +#include "timepulse.h" +//#include "si406x.h" +#include "si4060.h" +#include "spi_bitbang.h" + +void si4060_hw_init(void) +{ + /* Configure the SDN pin */ + port_pin_set_config(SI406X_SDN_PIN, + PORT_PIN_DIR_OUTPUT, /* Direction */ + PORT_PIN_PULL_NONE, /* Pull */ + false); /* Powersave */ + + /* Put the SI406x in shutdown */ + //_si406x_sdn_enable(); + si4060_shutdown(); + + /* Configure the SDN pin */ + port_pin_set_config(SI406X_SEL_PIN, + PORT_PIN_DIR_OUTPUT, /* Direction */ + PORT_PIN_PULL_NONE, /* Pull */ + false); /* Powersave */ + + /* Put the SEL pin in reset */ + _si406x_cs_disable(); + + /* Configure the GPIO and IRQ pins */ + port_pin_set_config(SI406X_GPIO0_PIN, + PORT_PIN_DIR_OUTPUT, /* Direction */ + PORT_PIN_PULL_NONE, /* Pull */ + false); /* Powersave */ + port_pin_set_output_level(SI406X_GPIO0_PIN, 0); + + /* Configure the serial port */ + spi_bitbang_init(SI406X_SERCOM_MOSI_PIN, + SI406X_SERCOM_MISO_PIN, + SI406X_SERCOM_SCK_PIN); +} int main(void) { - SystemInit(); + /* Clock up to 28MHz with 1 wait state */ + system_flash_set_waitstates(1); - system_clock_source_osc8m_set_config(SYSTEM_OSC8M_DIV_1, /* Prescaler */ + /* Up the clock rate to 4MHz */ + system_clock_source_osc8m_set_config(SYSTEM_OSC8M_DIV_2, /* Prescaler */ false, /* Run in Standby */ false); /* Run on Demand */ - /* Update the value of SystemCoreClock */ - SystemCoreClockUpdate(); + /* Restart the GCLK Module */ + system_gclk_init(); + /* Get the current CPU Clock */ + SystemCoreClock = system_cpu_clock_get_hz(); /* Set LED0 as output */ - PORTA.DIRSET.reg = (1 << LED0_PIN); + //PORTA.DIRSET.reg = (1 << SI406X_HF_CLK_PIN); - /* Configure the SysTick for 50ms interrupts */ - SysTick_Config(SystemCoreClock / 20); + /* Configure the SysTick for cpu/1000 output*//*for 50ms interrupts */ + //SysTick_Config(500); //SystemCoreClock / 20); /* Configure Sleep Mode */ system_set_sleepmode(SYSTEM_SLEEPMODE_IDLE_0); //TODO: system_set_sleepmode(SYSTEM_SLEEPMODE_STANDBY); - volatile double d, dd; + semihost_printf("Hello World %fHz\n", RF_FREQ_HZ); - d = 2; - dd = sqrt(d); - - semihost_printf("Hello World %f\n", dd); + /* Initialise GPS */ gps_init(); + /* Wait for GPS timepulse to stabilise */ + for (int i = 0; i < 1000*100; i++); + + + /* For the moment output GCLK_MAIN / 2 on HF CLK */ + switch_gclk_main_to_timepulse(); + //half_glck_main_on_hf_clk(); + /* Wait for HF CLK to stabilise */ + for (int i = 0; i < 1000*100; i++); + + semihost_printf("GCLK_MAIN = %d\n", gclk_main_frequency()); + + /* Drop the CPU clock to 1.5Mhz */ + system_cpu_clock_set_divider(SYSTEM_MAIN_CLOCK_DIV_16); + + /* Initialise Si4060 */ + si4060_hw_init(); + + /* reset the radio chip from shutdown */ + si4060_reset(); + + /* check radio communication */ + int i = si4060_part_info(); + if (i != 0x4063) { + while(1); + } + + si4060_power_up(); + si4060_setup(MOD_TYPE_CW); + si4060_start_tx(0); + + uint32_t ll = 0; while (1) { - //system_sleep(); + semihost_printf("State is %d %d\n", si4060_get_state(), ll++); + si4060_get_freq(); + for (int i = 0; i < 1000*10; i++); + port_pin_set_output_level(SI406X_GPIO0_PIN, 0); + //si4060_start_tx(0); + for (int i = 0; i < 1000*10; i++); + port_pin_set_output_level(SI406X_GPIO0_PIN, 1); - for (int i = 0; i < 1000*1000; i++); - gps_check_lock(); + + + + //system_sleep(); } } void SysTick_Handler(void) { /* Toggle LED0 */ - PORTA.OUTTGL.reg = (1 << LED0_PIN); + //PORTA.OUTTGL.reg = (1 << SI406X_HF_CLK_PIN); } diff --git a/firmware/src/si4060.c b/firmware/src/si4060.c new file mode 100644 index 0000000..da4985b --- /dev/null +++ b/firmware/src/si4060.c @@ -0,0 +1,443 @@ +/* + * si4060 software library + * + * API description: see EZRadioPRO-API-v1.1.2.zip + * + * Stefan Biereigel + * + */ + +#include +#include "spi_bitbang.h" +#include "si4060.h" +#include "samd20.h" +#include "semihosting.h" +#include "hw_config.h" +#include "system/port.h" + +/* + * si4060_shutdown + * + * makes the Si4060 go to shutdown state. + * all register content is lost. + */ +void si4060_shutdown(void) { + //P1OUT |= SI_SHDN; + port_pin_set_output_level(SI406X_SDN_PIN, 1); + /* wait 10us */ + __delay_cycles(2000); +} + +/* + * si4060_wakeup + * + * wakes up the Si4060 from shutdown state. + * si4060_power_up and si4060_setup have to be called afterwards + */ +void si4060_wakeup(void) { + //P1OUT &= ~SI_SHDN; + port_pin_set_output_level(SI406X_SDN_PIN, 0); + /* wait 20ms */ + __delay_cycles(20000); +} + +/* + * si4060_reset + * + * cleanly does the POR as specified in datasheet + */ +void si4060_reset(void) { + si4060_shutdown(); + si4060_wakeup(); +} + +/* + * si4060_read_cmd_buf + * + * reads the Si4060 command buffer from via SPI + * + * deselect: whether to deselect the slave after reading the response or not. + * any command reading subsequent bytes after the CTS should use this + * function to get CTS and continue doing spi_read afterwards + * and finally deselecting the slave. + * + * returns:the value of the first command buffer byte (i.e. CTS or not) + */ +uint8_t si4060_read_cmd_buf(uint8_t deselect) { + uint8_t ret; + spi_select(); + spi_write(CMD_READ_CMD_BUF); + ret = spi_read(); + if (deselect) { + spi_deselect(); + } + return ret; +} + +/* + * si4060_power_up + * + * powers up the Si4060 by issuing the POWER_UP command + * + * warning: the si4060 can lock after issuing this command if input clock + * is not available for the internal RC oscillator calibration. + */ +void si4060_power_up(void) { + /* wait for CTS */ + while (si4060_read_cmd_buf(1) != 0xff); + spi_select(); + spi_write(CMD_POWER_UP); + spi_write(FUNC); + spi_write(0x00);/* TCXO not used */ + spi_write((uint8_t) (XO_FREQ >> 24)); + spi_write((uint8_t) (XO_FREQ >> 16)); + spi_write((uint8_t) (XO_FREQ >> 8)); + spi_write((uint8_t) XO_FREQ); + spi_deselect(); + /* wait for CTS */ + while (si4060_read_cmd_buf(1) != 0xff); +} + +/* + * si4060_change_state + * + * changes the internal state machine state of the Si4060 + */ +void si4060_change_state(uint8_t state) { + spi_select(); + spi_write(CMD_CHANGE_STATE); + spi_write(state); + spi_deselect(); + while (si4060_read_cmd_buf(1) != 0xff); + +} + +/* + * si4060_nop + * + * implements the NOP command on the Si4060 + */ +void si4060_nop(void) { + spi_select(); + spi_write(CMD_NOP); + spi_deselect(); + while (si4060_read_cmd_buf(1) != 0xff); +} + +/* + * si4060_set_property_8 + * + * sets an 8 bit (1 byte) property in the Si4060 + * + * group:the group number of the property + * prop:the number (index) of the property + * val:the value to set + */ +void si4060_set_property_8(uint8_t group, uint8_t prop, uint8_t val) { + spi_select(); + spi_write(CMD_SET_PROPERTY); + spi_write(group); + spi_write(1); + spi_write(prop); + spi_write(val); + spi_deselect(); + while (si4060_read_cmd_buf(1) != 0xff); +} + +/* + * si4060_get_property_8 + * + * gets an 8 bit (1 byte) property in the Si4060 + * + * group:the group number of the property + * prop:the number (index) of the property + * + * returns:the value of the property + */ +uint8_t si4060_get_property_8(uint8_t group, uint8_t prop) { + uint8_t temp = 0; + spi_select(); + spi_write(CMD_GET_PROPERTY); + spi_write(group); + spi_write(1); + spi_write(prop); + spi_deselect(); + while (temp != 0xff) { + temp = si4060_read_cmd_buf(0); + if (temp != 0xff) { + spi_deselect(); + } + } + temp = spi_read(); /* read property */ + spi_deselect(); + return temp; +} + +/* + * si4060_set_property_16 + * + * sets an 16 bit (2 byte) property in the Si4060 + * + * group:the group number of the property + * prop:the number (index) of the property + * val:the value to set + */ +void si4060_set_property_16(uint8_t group, uint8_t prop, uint16_t val) { + spi_select(); + spi_write(CMD_SET_PROPERTY); + spi_write(group); + spi_write(2); + spi_write(prop); + spi_write(val >> 8); + spi_write(val); + spi_deselect(); + while (si4060_read_cmd_buf(1) != 0xff); +} + +/* + * si4060_set_property_24 + * + * sets an 24 bit (3 byte) property in the Si4060 + * + * group:the group number of the property + * prop:the number (index) of the property + * val:the value to set + */ +void si4060_set_property_24(uint8_t group, uint8_t prop, uint32_t val) { + spi_select(); + spi_write(CMD_SET_PROPERTY); + spi_write(group); + spi_write(3); + spi_write(prop); + spi_write(val >> 16); + spi_write(val >> 8); + spi_write(val); + spi_deselect(); + while (si4060_read_cmd_buf(1) != 0xff); +} + +/* + * si4060_set_property_32 + * + * sets an 32 bit (4 byte) property in the Si4060 + * + * group:the group number of the property + * prop:the number (index) of the property + * val:the value to set + */ +void si4060_set_property_32(uint8_t group, uint8_t prop, uint32_t val) { + spi_select(); + spi_write(CMD_SET_PROPERTY); + spi_write(group); + spi_write(4); + spi_write(prop); + spi_write(val >> 24); + spi_write(val >> 16); + spi_write(val >> 8); + spi_write(val); + spi_deselect(); + while (si4060_read_cmd_buf(1) != 0xff); +} + +/* + * si4060_gpio_pin_cfg + * + * configures the GPIOs on the Si4060 + * see the GPIO_*-defines for reference + * + * gpio(0..3):setting flags for respective GPIOs + * drvstrength:the driver strength + */ +void si4060_gpio_pin_cfg(uint8_t gpio0, uint8_t gpio1, uint8_t gpio2, uint8_t gpio3, uint8_t drvstrength) { + spi_select(); + spi_write(CMD_GPIO_PIN_CFG); + spi_write(gpio0); + spi_write(gpio1); + spi_write(gpio2); + spi_write(gpio3); + spi_write(NIRQ_MODE_DONOTHING); + spi_write(SDO_MODE_DONOTHING); + spi_write(drvstrength); + spi_deselect(); + while (si4060_read_cmd_buf(1) != 0xff); +} + +/* + * si4060_part_info + * + * gets the PART_ID from the Si4060 + * this can be used to check for successful communication. + * as the SPI bus returns 0xFF (MISO=high) when no slave is connected, + * reading CTS can not verify communication to the Si4060. + * + * returns:the PART_ID - should be 0x4060 + */ +uint16_t si4060_part_info(void) { + uint16_t temp; + + temp = 0; + spi_select(); + spi_write(CMD_PART_INFO); + spi_deselect(); + /* do not deselect after reading CTS */ + while (temp != 0xff) { + temp = si4060_read_cmd_buf(0); + if (temp != 0xff) { + spi_deselect(); + } + } + spi_read(); /* ignore CHIPREV */ + temp = spi_read(); /* read PART[0] */ + temp = temp << 8; + temp |= spi_read(); /* read PART[1] */ + spi_deselect(); + return temp; +} + +/** + * si4060_get_state + * + * gets the current state of the Si4060 + */ +uint8_t si4060_get_state(void) +{ + uint16_t temp; + + temp = 0; + spi_select(); + spi_write(CMD_REQUEST_STATE); + spi_deselect(); + /* do not deselect after reading CTS */ + while (temp != 0xff) { + temp = si4060_read_cmd_buf(0); + if (temp != 0xff) { + spi_deselect(); + } + } + temp = spi_read() & 0x0F; /* Get MAIN_STATE */ + spi_deselect(); + return temp; +} +/** + * si4060_get_freq + */ +void si4060_get_freq(void) +{ + uint32_t inte, frac; + uint16_t temp; + + spi_select(); + spi_write(CMD_GET_PROPERTY); + spi_write(PROP_FREQ_CONTROL); + spi_write(0x4); + spi_write(FREQ_CONTROL_INTE); + spi_deselect(); + /* do not deselect after reading CTS */ + while (temp != 0xff) { + temp = si4060_read_cmd_buf(0); + if (temp != 0xff) { + spi_deselect(); + } + } + + inte = spi_read(); /* Get FREQ_CONTROL_INTE */ + + frac = (spi_read() << 16); /* Get FREQ_CONTROL_FRAC MSB */ + frac |= (spi_read() << 8); /* Get FREQ_CONTROL_FRAC */ + frac |= (spi_read() << 0); /* Get FREQ_CONTROL_FRAC LSB */ + spi_deselect(); + + semihost_printf("INTE = 0x%02x, FRAC = 0x%06x\n", inte, frac); +} + +/* + * si4060_start_tx + * + * starts transmission by the Si4060. + * + * channel:the channel to start transmission on + */ +void si4060_start_tx(uint8_t channel) { + spi_select(); + spi_write(CMD_START_TX); + spi_write(channel); + spi_write(START_TX_TXC_STATE_SLEEP | START_TX_RETRANSMIT_0 | START_TX_START_IMM); + /* set length to 0 for direct mode (is this correct?) */ + spi_write(0x00); + spi_write(0x00); + spi_deselect(); + while (si4060_read_cmd_buf(1) != 0xff); +} + +/* + * si4060_stop_tx + * + * makes the Si4060 stop all transmissions by transistioning to SLEEP state + */ +void si4060_stop_tx(void) { + si4060_change_state(STATE_SLEEP); +} + +/* + * si4060_setup + * + * initializes the Si4060 by setting all neccesary internal registers. + * has to be called after si4060_power_up. + * + * mod_type:the type of modulation to use, use the MODEM_MOD_TYPE values (MOD_TYPE_*) + */ +void si4060_setup(uint8_t mod_type) { + + /* set high performance mode */ + si4060_set_property_8(PROP_GLOBAL, + GLOBAL_CONFIG, + GLOBAL_RESERVED | POWER_MODE_HIGH_PERF | SEQUENCER_MODE_FAST); + /* set up GPIOs */ + si4060_gpio_pin_cfg(GPIO_MODE_DONOTHING, + GPIO_MODE_DONOTHING, + GPIO_MODE_DONOTHING, + PULL_CTL + GPIO_MODE_INPUT, + DRV_STRENGTH_HIGH); + /* disable preamble */ + si4060_set_property_8(PROP_PREAMBLE, + PREAMBLE_TX_LENGTH, + 0); + /* do not transmit sync word */ + si4060_set_property_8(PROP_SYNC, + SYNC_CONFIG, + SYNC_NO_XMIT); + /* use 2FSK from async GPIO0 */ + si4060_set_property_8(PROP_MODEM, + MODEM_MOD_TYPE, + (mod_type & 0x07) | MOD_SOURCE_DIRECT | MOD_GPIO_0 | MOD_DIRECT_MODE_ASYNC); + /* setup frequency deviation */ + si4060_set_property_24(PROP_MODEM, + MODEM_FREQ_DEV, + (uint32_t)FDEV); + /* setup frequency deviation offset */ + si4060_set_property_16(PROP_MODEM, + MODEM_FREQ_OFFSET, + 0x0000); + /* setup divider to 8 (for 70cm ISM band */ + si4060_set_property_8(PROP_MODEM, + MODEM_CLKGEN_BAND, + SY_SEL_1 | FVCO_DIV_8); + /* set up the PA power level */ + si4060_set_property_8(PROP_PA, + PA_PWR_LVL, + 0x3f); + /* set up the PA duty cycle */ + si4060_set_property_8(PROP_PA, + PA_BIAS_CLKDUTY, + PA_BIAS_CLKDUTY_DIFF_50); + /* set up the integer divider */ + si4060_set_property_8(PROP_FREQ_CONTROL, + FREQ_CONTROL_INTE, + (uint8_t)(FDIV_INTE)); + /* set up the fractional divider */ + si4060_set_property_24(PROP_FREQ_CONTROL, + FREQ_CONTROL_FRAC, + (uint32_t)(FDIV_FRAC)); + /* TODO set the channel step size (if SPI frequency changing is used) */ + +} diff --git a/firmware/src/si406x.c b/firmware/src/si406x.c new file mode 100644 index 0000000..3c03705 --- /dev/null +++ b/firmware/src/si406x.c @@ -0,0 +1,348 @@ +/* + * Functions for controlling Si406x radios + * Copyright (C) 2014 Richard Meadows + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include "samd20.h" +#include "semihosting.h" +#include "system/port.h" +#include "spi_bitbang.h" +#include "si406x_defs.h" +#include "hw_config.h" + +#define RADIO_FREQ 434600000 +// Quite low power +#define RADIO_PWR 0x7f +#define VCXO_FREQ SI406X_HF_FREQUENCY + +uint32_t active_freq = RADIO_FREQ; +uint8_t active_pwr = RADIO_PWR; +uint16_t si446x_powerlevel = RADIO_PWR; + +/** + * Generic SPI Send / Receive for the Si406x + */ +void _si406x_transfer(int tx_count, int rx_count, const uint8_t *data) +{ + uint8_t response[100]; + + /* Send command */ + _si406x_cs_enable(); + + for (int i = 0; i < tx_count; i++) { + spi_bitbang_transfer(data[i]); + } + + _si406x_cs_disable(); + + /** + * Poll CTS. From the docs: + * + * READ_CMD_BUFF is used to poll the CTS signal via the SPI bus. The + * NSEL line should be pulled low, followed by sending the + * READ_CMD_BUFF command on SDI. While NSEL remains asserted low, an + * additional eight clock pulses are sent on SCLK and the CTS + * response byte is read on SDO. If the CTS response byte is not + * 0xFF, the host MCU should pull NSEL high and repeat the polling + * procedure. + */ + + for (int i = 0; i < 20000; i++); // 20µS + _si406x_cs_enable(); + + do { + /* Issue READ_CMD_BUFF */ + spi_bitbang_transfer(SI_CMD_READ_CMD_BUFF); + response[0] = spi_bitbang_transfer(0xFF); + + /* If the reply is 0xFF, read the response */ + if (response[0] == 0xFF) break; + + /* Otherwise repeat the procedure */ + _si406x_cs_disable(); + for (int i = 0; i < 200; i++); // 20µS + _si406x_cs_enable(); + + } while (1); /* TODO: Timeout? */ + + /** + * Read response. From the docs: + * + * If the CTS response byte is 0xFF, the host MCU should keep NSEL + * asserted low and provide additional clock cycles on SCLK to read + * out as many response bytes (on SDO) as necessary. The host MCU + * should pull NSEL high upon completion of reading the response + * stream. + */ + for (int i = 1; i < rx_count; i++) { + response[i] = spi_bitbang_transfer(0xFF); + } + + _si406x_cs_disable(); +} + +/** + * Set the Si406x synthesiser to the given frequency + */ +static void si406x_set_frequency(uint32_t frequency) +{ + uint8_t outdiv, band; + + if (frequency < 705000000UL) { + outdiv = 6; band = 1; + } + if (frequency < 525000000UL) { + if (VCXO_FREQ < 24000000) { // clock < 24mhz + outdiv = 4; band = 0; + } else { + outdiv = 8; band = 2; + } + } + if (frequency < 353000000UL) { + outdiv = 12; band = 3; + } + if (frequency < 239000000UL) { + outdiv = 16; band = 4; + } + if (frequency < 177000000UL) { + outdiv = 24; band = 5; + } + + uint32_t f_pfd = 2 * VCXO_FREQ / outdiv; + + uint16_t n = ((uint16_t)(frequency / f_pfd)) - 1; + + float ratio = (float)frequency / (float)f_pfd; + float rest = ratio - (float)n; + + uint32_t m = (uint32_t)(rest * 524288UL); // 2^19 + + // set the band parameter + uint8_t sy_sel = 8; + uint8_t set_band_property_command[] = {0x11, 0x20, 0x01, 0x51, (band + sy_sel)}; + // send parameters + _si406x_transfer(5, 1, set_band_property_command); + + // Set the pll parameters + uint16_t m2 = m / 0x10000; + uint16_t m1 = (m - m2 * 0x10000) / 0x100; + uint16_t m0 = (m - m2 * 0x10000 - m1 * 0x100); + + // assemble parameter string + const uint8_t set_frequency_control_inte[] = {0x11, 0x40, 0x04, 0x00, n, m2, m1, m0}; +// const uint8_t set_frequency_control_inte[] = { + // 0x11, 0x40, 0x08, 0x00, n, m2, m1, m0, 0x0B, 0x61, 0x20, 0xFA}; + //const uint8_t set_frequency_control_inte[] = {0x11, 0x40, 0x08, 0x00, n, m2, m1, m0, 0x0B, 0x61, 0x20, 0xFA}; + + // send parameters + _si406x_transfer(8, 1, set_frequency_control_inte); + + // Read parameters + const uint8_t get_frequency_control[] = { 0x12, 0x40, 0x04, 0x00 }; + _si406x_transfer(4, 5, get_frequency_control); +} + +void si406x_init(void) +{ + /* Configure the SDN pin */ + port_pin_set_config(SI406X_SDN_PIN, + PORT_PIN_DIR_OUTPUT, /* Direction */ + PORT_PIN_PULL_NONE, /* Pull */ + false); /* Powersave */ + + /* Put the SI406x in shutdown */ + _si406x_sdn_enable(); + + /* Configure the SDN pin */ + port_pin_set_config(SI406X_SEL_PIN, + PORT_PIN_DIR_OUTPUT, /* Direction */ + PORT_PIN_PULL_NONE, /* Pull */ + false); /* Powersave */ + + /* Put the SEL pin in reset */ + _si406x_cs_disable(); + + /* Configure the GPIO and IRQ pins */ + + /* Configure the serial port */ + spi_bitbang_init(SI406X_SERCOM_MOSI_PIN, + SI406X_SERCOM_MISO_PIN, + SI406X_SERCOM_SCK_PIN); + + /* Boot */ + for (int i = 0; i < 15*10000; i++); // 15ms + _si406x_sdn_disable(); + for (int i = 0; i < 15*10000; i++); // 15ms + + const uint8_t PART_INFO_command[] = {0x01}; // Part Info + _si406x_transfer(1, 9, PART_INFO_command); +} + + +/** + * Sets the modem into CW mode + */ +static void si446xSetModem(void) +{ + // Set to CW mode + const uint8_t set_modem_mod_type_command[] = {0x11, 0x20, 0x01, 0x00, 0x00}; + _si406x_transfer(5, 1, set_modem_mod_type_command); +} + +/** + * Sets the tx power + */ +void si446xSetPower(uint8_t pwr) +{ + // Set the Power +// uint8_t set_pa_pwr_lvl_property_command[] = {0x11, 0x22, 0x01, 0x01, pwr}; +// _si406x_transfer(5, 1, set_pa_pwr_lvl_property_command); +} + +// Config reset ---------------------------------------------------------- +static void si446xReset(void) +{ +// _si406x_hf_clock_enable(); + _si406x_sdn_enable(); // active high shutdown = reset + + for (int i = 0; i < 15*10000; i++); // 15ms + _si406x_sdn_disable(); // booting + for (int i = 0; i < 15*10000; i++); // 15ms + + + const uint8_t PART_INFO_command[] = {0x01}; // Part Info + _si406x_transfer(1, 9, PART_INFO_command); + + //divide VCXO_FREQ into its bytes; MSB first + uint16_t x3 = VCXO_FREQ / 0x1000000; + uint16_t x2 = (VCXO_FREQ - x3 * 0x1000000) / 0x10000; + uint16_t x1 = (VCXO_FREQ - x3 * 0x1000000 - x2 * 0x10000) / 0x100; + uint16_t x0 = (VCXO_FREQ - x3 * 0x1000000 - x2 * 0x10000 - x1 * 0x100); + + /* ---- POWER_UP ---- */ + /* no patch, boot main app. img, FREQ_VCXO, return 1 byte */ + /* */ + const uint8_t init_command[] = {0x02, 0x01, 0x01, x3, x2, x1, x0}; + _si406x_transfer(7, 1 , init_command); + + + // Clear all pending interrupts and get the interrupt status back + const uint8_t get_int_status_command[] = {0x20, 0x00, 0x00, 0x00}; + _si406x_transfer(4, 9, get_int_status_command); + // cliPrint("Radio ready\n"); + + const uint8_t set_int_ctrl_enable[] = {0x11, 0x01, 0x01, 0x00, 0x00}; + _si406x_transfer(5, 1, set_int_ctrl_enable); + // cliPrint("Setting no Interrupts (see WDS)\n"); + + // Set all GPIOs LOW; Link NIRQ to CTS; Link SDO to MISO; Max drive strength + const uint8_t gpio_pin_cfg_command[] = {0x13, 0x02, 0x02, 0x02, 0x02, 0x08, 0x0B, 0x00}; + _si406x_transfer(8, 8, gpio_pin_cfg_command); + // cliPrint("LEDs should be switched off at this point\n"); + + //const uint8_t set_global_config1[] = {0x11, 0x00, 0x01, 0x03, 0x60}; + //_si406x_transfer(5, 1, set_global_config1); + // Sequencer Mode = Fast, Fifo = Half Duplex + // cliPrint("Setting special global Config 1 changes (see WDS)\n"); + + // const uint8_t set_global_xo_tune_command[] = {0x11, 0x00, 0x01, 0x00, 0x00}; + //_si406x_transfer(5, 1, set_global_xo_tune_command); + // cliPrint("Setting no additional capacitance on VXCO\n"); + + si406x_set_frequency(active_freq); + + si446xSetPower(active_pwr); + + si446xSetModem(); + // cliPrint("CW mode set\n"); + + si406x_state_tx_tune(); + // cliPrint("TX tune\n"); +} + +void si446xPTTOn(void) +{ + si446xReset(); + + // turn on the LED (GPIO0) to indicate TX + // Set GPIOs LOW; Link NIRQ to CTS; Link SDO to MISO; Max drive strength + const uint8_t gpio_pin_cfg_command2[] = {0x13, 0x02, 0x02, 0x02, 0x02, 0x08, 0x0B, 0x00}; + _si406x_transfer(8, 1, gpio_pin_cfg_command2); + + si406x_state_tx(); + + const uint8_t get_state[] = {0x33}; + _si406x_transfer(1, 3, get_state); +} + +void si446xPTTOff(void) +{ + si406x_state_ready(); + + // turn off the LED (GPIO1) + // Set GPIO0 HIGH + const uint8_t gpio_pin_cfg_command0[] = {0x13, 0x03, 0x0, 0x0, 0x0, 0x0, 0x0, 0x00}; + _si406x_transfer(8, 1, gpio_pin_cfg_command0); + + _si406x_sdn_enable(); // active high = shutdown + _si406x_hf_clock_disable(); // keep enabled for now +} + +/** + * Quick and dirty loopback test. Should print 0x34 + */ +void spi_loopback_test(void) +{ + /* spi_init(SI406X_SERCOM, */ + /* SPI_MODE_MASTER, /\** SPI mode *\/ */ + /* SPI_DATA_ORDER_MSB, /\** Data order *\/ */ + /* SPI_TRANSFER_MODE_0, /\** Transfer mode *\/ */ + /* SI406X_SERCOM_MUX, /\** Mux setting *\/ */ + /* SPI_CHARACTER_SIZE_8BIT, /\** SPI character size *\/ */ + /* false, /\** Enabled in sleep *\/ */ + /* true, /\** Enable receiver *\/ */ + /* 100000, /\** Master - Baud rate *\/ */ + /* 0, /\** Slave - Frame format *\/ */ + /* 0, /\** Slave - Address mode *\/ */ + /* 0, /\** Slave - Address *\/ */ + /* 0, /\** Slave - Address mask *\/ */ + /* false, /\** Slave - Preload data *\/ */ + /* GCLK_GENERATOR_0, /\** GCLK generator to use *\/ */ + /* SI406X_SERCOM_MOSI_PINMUX, /\** Pinmux *\/ */ + /* SI406X_SERCOM_MISO_PINMUX, /\** Pinmux *\/ */ + /* SI406X_SERCOM_SCK_PINMUX, /\** Pinmux *\/ */ + /* PINMUX_UNUSED); /\** Pinmux *\/ */ + + /* Init loopback */ + spi_bitbang_init(SI406X_SERCOM_MOSI_PIN, + SI406X_SERCOM_MOSI_PIN, + SI406X_SERCOM_SCK_PIN); + + /* Enable */ + + /* Test transfer */ + uint8_t data = spi_bitbang_transfer(0x34); + + /* Print result */ + semihost_printf("Rx'ed: 0x%02x\n", data); +} + diff --git a/firmware/src/spi_bitbang.c b/firmware/src/spi_bitbang.c new file mode 100644 index 0000000..bd70a60 --- /dev/null +++ b/firmware/src/spi_bitbang.c @@ -0,0 +1,104 @@ +/* + * SPI bit-banging! Yay + * Copyright (C) 2014 Richard Meadows + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include "samd20.h" +#include "system/port.h" + + +/** Should be at least 20ns or something. Why am I bit-banging this? */ +#define BIT_DELAY __NOP + +#define BIT_TOGGLE(bit) do { \ + PORTA.OUTTGL.reg = bit; } while (0) +#define BIT_SET(bit) do { \ + PORTA.OUTSET.reg = bit; } while (0) +#define BIT_CLEAR(bit) do { \ + PORTA.OUTCLR.reg = bit; } while (0) +#define BIT_READ(bit) (PORTA.IN.reg & bit) + +uint32_t mosi_mask = 0; +uint32_t miso_mask = 0; +uint32_t sck_mask = 0; + +void spi_bitbang_init(const uint8_t mosi, + const uint8_t miso, + const uint8_t sck) +{ + /* Set the masks */ + mosi_mask = (1 << mosi); + miso_mask = (1 << miso); + sck_mask = (1 << sck); + + /* Configure the output pins */ + if (mosi == miso) { /* Loopback */ + port_pin_set_config(mosi, + PORT_PIN_DIR_OUTPUT_WTH_READBACK,/* Direction */ + PORT_PIN_PULL_NONE, /* Pull */ + false); /* Powersave */ + } else { /* No loopback */ + port_pin_set_config(mosi, + PORT_PIN_DIR_OUTPUT, /* Direction */ + PORT_PIN_PULL_NONE, /* Pull */ + false); /* Powersave */ + port_pin_set_config(miso, + PORT_PIN_DIR_INPUT, /* Direction */ + PORT_PIN_PULL_UP, /* Pull */ + false); /* Powersave */ + } + port_pin_set_config(sck, + PORT_PIN_DIR_OUTPUT, /* Direction */ + PORT_PIN_PULL_NONE, /* Pull */ + false); /* Powersave */ + + /* Set output pins to default values */ + BIT_SET(mosi_mask); + BIT_CLEAR(sck_mask); +} +uint8_t spi_bitbang_transfer(uint8_t byte) +{ + for (uint8_t counter = 0; counter < 8; counter++) { + /* Set output data */ + if (byte & 0x80) { + BIT_SET(mosi_mask); + } else { + BIT_CLEAR(mosi_mask); + } + byte <<= 1; + BIT_DELAY(); + + /* Latch Data into Slave */ + BIT_TOGGLE(sck_mask); + BIT_DELAY(); + + /* Read Data */ + if (BIT_READ(miso_mask)) { + byte |= 0x1; + } + + /* Slave shifts out next data bit */ + BIT_TOGGLE(sck_mask); + } + + return byte; +} diff --git a/firmware/src/system/clock.c b/firmware/src/system/clock.c index 48b855c..3d00671 100644 --- a/firmware/src/system/clock.c +++ b/firmware/src/system/clock.c @@ -379,6 +379,7 @@ void system_clock_source_dfll_set_config( rev &= DSU_DID_REVISION_Msk; rev = rev >> DSU_DID_REVISION_Pos; + /* Calculate the Value register */ if (rev < _SYSTEM_MCU_REVISION_D) { val = _SYSTEM_OLD_DFLLVAL_COARSE(coarse_value) | @@ -389,6 +390,7 @@ void system_clock_source_dfll_set_config( _SYSTEM_NEW_DFLLVAL_FINE(fine_value); } + /* Calculate the Control register */ control = (uint32_t)wakeup_lock | (uint32_t)stable_tracking | @@ -396,8 +398,8 @@ void system_clock_source_dfll_set_config( (uint32_t)chill_cycle | ((uint32_t)on_demand << SYSCTRL_DFLLCTRL_ONDEMAND_Pos); + /* Set the Multiplication register for closed loop mode */ if (loop_mode == SYSTEM_CLOCK_DFLL_LOOP_MODE_CLOSED) { - if(rev < _SYSTEM_MCU_REVISION_D) { mul = _SYSTEM_OLD_DFLLMUL_CSTEP(coarse_max_step) | diff --git a/firmware/src/tc/tc_driver.c b/firmware/src/tc/tc_driver.c index 7911085..3dc0cb6 100644 --- a/firmware/src/tc/tc_driver.c +++ b/firmware/src/tc/tc_driver.c @@ -71,7 +71,7 @@ * \note When the counter is configured to re-trigger on an event, the counter * will not start until the start function is used. */ -static inline void tc_enable(Tc* const hw) +void tc_enable(Tc* const hw) { assert(hw); @@ -83,7 +83,7 @@ static inline void tc_enable(Tc* const hw) /** * Disables a TC module and stops the counter. */ -static inline void tc_disable(Tc* const hw) +void tc_disable(Tc* const hw) { assert(hw); @@ -96,7 +96,7 @@ static inline void tc_disable(Tc* const hw) /** * Starts or restarts an initialized TC module's counter. */ -static inline void tc_start_counter(Tc* const hw) +void tc_start_counter(Tc* const hw) { assert(hw); @@ -114,7 +114,7 @@ static inline void tc_start_counter(Tc* const hw) * counting up, or max or the top value if the counter was counting * down when stopped. */ -static inline void tc_stop_counter(Tc* const hw) +void tc_stop_counter(Tc* const hw) { assert(hw); @@ -135,7 +135,7 @@ static inline void tc_stop_counter(Tc* const hw) * \retval TC_STATUS_CAPTURE_OVERFLOW Timer capture data has overflowed * \retval TC_STATUS_COUNT_OVERFLOW Timer count value has overflowed */ -static inline uint32_t tc_get_status(Tc* const hw) +uint32_t tc_get_status(Tc* const hw) { assert(hw); @@ -174,8 +174,8 @@ static inline uint32_t tc_get_status(Tc* const hw) * * \param[in] status_flags Bitmask of \c TC_STATUS_* flags to clear */ -static inline void tc_clear_status(Tc* const hw, - const uint32_t status_flags) +void tc_clear_status(Tc* const hw, + const uint32_t status_flags) { assert(hw); @@ -336,7 +336,6 @@ void tc_set_compare_value(Tc* const hw, const uint32_t compare) { assert(hw); - assert(compare); WAIT_FOR_SYNC(hw); @@ -506,7 +505,7 @@ void tc_set_top_value(Tc* const hw, * * \param[in] events Struct containing flags of events to enable */ -static inline void tc_enable_events(Tc* const hw, +void tc_enable_events(Tc* const hw, struct tc_events *const events) { /* Sanity check arguments */ @@ -545,7 +544,7 @@ static inline void tc_enable_events(Tc* const hw, * * \param[in] events Struct containing flags of events to disable */ -static inline void tc_disable_events(Tc* const hw, +void tc_disable_events(Tc* const hw, struct tc_events *const events) { assert(hw); diff --git a/firmware/src/timepulse.c b/firmware/src/timepulse.c new file mode 100644 index 0000000..ac19ef7 --- /dev/null +++ b/firmware/src/timepulse.c @@ -0,0 +1,215 @@ +/* + * Functions for turning the GPS timepulse into a HF Clock + * Copyright (C) 2014 Richard Meadows + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include "samd20.h" +#include "system/clock.h" +#include "system/gclk.h" +#include "system/pinmux.h" +#include "tc/tc_driver.h" +#include "hw_config.h" + +#define DFLL48_MUL (DFLL48M_CLK / GPS_TIMEPULSE_FREQ) + +/* Check that DFLL48_MUL is an integer */ +//#if ((DFLL48M_CLK * 100000000) / GPS_TIMEPULSE_FREQ != (DFLL48_MUL * 100000000)) +//#error DFLL48M_CLK must be a integer multiple of GPS_TIMEPULSE_FREQ! +//#endif + +void timepulse_init(void) +{ + /* Set up the DFLL GCLK channel */ + system_gclk_chan_set_config(SYSCTRL_GCLK_ID_DFLL48, DFLL48M_GCLK); + system_gclk_chan_enable(SYSCTRL_GCLK_ID_DFLL48); + + /* Configure DFLL48 */ + system_clock_source_dfll_set_config( + SYSTEM_CLOCK_DFLL_LOOP_MODE_CLOSED, /* Loop Mode */ + false, /* On demand */ + SYSTEM_CLOCK_DFLL_QUICK_LOCK_DISABLE, /* Quick Lock */ + SYSTEM_CLOCK_DFLL_CHILL_CYCLE_ENABLE, /* Chill Cycle */ + SYSTEM_CLOCK_DFLL_WAKEUP_LOCK_KEEP, /* Lock during wakeup */ + SYSTEM_CLOCK_DFLL_STABLE_TRACKING_TRACK_AFTER_LOCK, + 0x1f / 4, /* Open Loop - Coarse calibration value */ + 0xff / 4, /* Open Loop - Fine calibration value */ + 1, /* Closed Loop - Coarse Maximum step */ + 1, /* Closed Loop - Fine Maximum step */ + DFLL48_MUL); /* Frequency Multiplication Factor */ + + /* Enable DFLL48 */ + system_clock_source_enable(SYSTEM_CLOCK_SOURCE_DFLL); + + /* Wait for it to be ready */ + while(!system_clock_source_is_ready(SYSTEM_CLOCK_SOURCE_DFLL)); + + /* system_clock_source_xosc_set_config(SYSTEM_CLOCK_EXTERNAL_CLOCK, */ + /* SYSTEM_XOSC_STARTUP_16384, */ + /* true, /\* Auto gain control *\/ */ + /* 16000000UL, /\* Frequency *\/ */ + /* true, /\* Run in Standby *\/ */ + /* false); /\* Run on demand *\/ */ + /* system_clock_source_enable(SYSTEM_CLOCK_SOURCE_XOSC); */ + + /* Configure the HF GCLK */ + system_gclk_gen_set_config(SI406X_HF_GCLK, + GCLK_SOURCE_DFLL48M, /* Source */ + false, /* High When Disabled */ + 48, /* Division Factor */ + false, /* Run in standby */ + true); /* Output Pin Enable */ + + /* Configure the output pin */ + system_pinmux_pin_set_config(SI406X_HF_CLK_PINMUX >> 16, /* GPIO Pin */ + SI406X_HF_CLK_PINMUX & 0xFFFF, /* Mux Position */ + SYSTEM_PINMUX_PIN_DIR_INPUT, /* Direction */ + SYSTEM_PINMUX_PIN_PULL_NONE, /* Pull */ + false); /* Powersave */ + + /* Enable the HF GCLK */ + system_gclk_gen_enable(SI406X_HF_GCLK); +} + +/** + * Switches GCLK_MAIN (a.k.a. GCLK0) to the gps timepulse + */ +void switch_gclk_main_to_timepulse(void) +{ + /* Enable GCLK_IO[0] */ + system_pinmux_pin_set_config(GPS_TIME_PINMUX >> 16, /* GPIO Pin */ + GPS_TIME_PINMUX & 0xFFFF, /* Mux Position */ + SYSTEM_PINMUX_PIN_DIR_INPUT, /* Direction */ + SYSTEM_PINMUX_PIN_PULL_NONE, /* Pull */ + false); /* Powersave */ + + /* Switch GCLK_MAIN to GCLK_IO[0] */ + system_gclk_gen_set_config(GCLK_GENERATOR_0, /* GCLK 0 */ + GCLK_SOURCE_GCLKIN,/* Source from pin */ + false, /* High When Disabled */ + 1, /* Division Factor */ + true, /* Run in standby */ + true); /* Output Pin Enable */ + + /* Wait for switch? */ +} + +/** + * Outputs GCLK0 div 2 on the HF CLK pin + */ +void half_glck_main_on_hf_clk(void) +{ + bool capture_channel_enables[] = {true, true}; + uint32_t compare_channel_values[] = {0x0000, 0x0000}; + + tc_init(TC2, + GCLK_GENERATOR_0, + TC_COUNTER_SIZE_8BIT, + TC_CLOCK_PRESCALER_DIV1, + TC_WAVE_GENERATION_NORMAL_FREQ, + TC_RELOAD_ACTION_GCLK, + TC_COUNT_DIRECTION_UP, + TC_WAVEFORM_INVERT_OUTPUT_NONE, + false, /* Oneshot = false */ + false, /* Run in standby = false */ + 0x0000, /* Initial value */ + 0x0000, /* Top value */ + capture_channel_enables, /* Capture Channel Enables */ + compare_channel_values); /* Compare Channels Values */ + + /* Enable the output pin */ + system_pinmux_pin_set_config(PINMUX_PA17F_TC2_WO1 >> 16, /* GPIO Pin */ + PINMUX_PA17F_TC2_WO1 & 0xFFFF, /* Mux Position */ + SYSTEM_PINMUX_PIN_DIR_INPUT, /* Direction */ + SYSTEM_PINMUX_PIN_PULL_NONE, /* Pull */ + false); /* Powersave */ + + tc_enable(TC2); + tc_start_counter(TC2); +} + +/** + * Returns the current GCLK_MAIN frequency, as measured against OSC8M + */ +uint32_t gclk_main_frequency(void) +{ + uint32_t osc8m_frequency = 8000000UL >> SYSCTRL->OSC8M.bit.PRESC; + + /* Configure GCLK Gen 6 as reference */ + system_gclk_gen_set_config(GCLK_GENERATOR_6, + GCLK_SOURCE_OSC8M, /* Source */ + false, /* High When Disabled */ + 4, /* Division Factor */ + false, /* Run in standby */ + false); /* Output Pin Enable */ + /* Enable GCLK 6 */ + system_gclk_gen_enable(GCLK_GENERATOR_6); + + /* Timer 0 free runs on GLCK 0 */ + bool t0_capture_channel_enables[] = {false, false}; + uint32_t t0_compare_channel_values[] = {0x0000, 0x0000}; + + tc_init(TC0, + GCLK_GENERATOR_0, + TC_COUNTER_SIZE_32BIT, + TC_CLOCK_PRESCALER_DIV1, + TC_WAVE_GENERATION_NORMAL_FREQ, + TC_RELOAD_ACTION_GCLK, + TC_COUNT_DIRECTION_UP, + TC_WAVEFORM_INVERT_OUTPUT_NONE, + false, /* Oneshot */ + false, /* Run in standby */ + 0x0000, /* Initial value */ + 0xFFFFFFFF, /* Top value */ + t0_capture_channel_enables, /* Capture Channel Enables */ + t0_compare_channel_values); /* Compare Channels Values */ + + /* Timer 3 counts 10000 cycles of GLCK 6 */ + bool t1_capture_channel_enables[] = {false, false}; + uint32_t t1_compare_channel_values[] = {10000, 0x0000}; + + tc_init(TC3, + GCLK_GENERATOR_6, + TC_COUNTER_SIZE_16BIT, + TC_CLOCK_PRESCALER_DIV1, + TC_WAVE_GENERATION_NORMAL_FREQ, + TC_RELOAD_ACTION_GCLK, + TC_COUNT_DIRECTION_UP, + TC_WAVEFORM_INVERT_OUTPUT_NONE, + false, /* Oneshot */ + false, /* Run in standby */ + 0x0000, /* Initial value */ + 0xFFFF, /* Top value */ + t1_capture_channel_enables, /* Capture Channel Enables */ + t1_compare_channel_values); /* Compare Channels Values */ + + tc_enable(TC0); + tc_enable(TC3); + tc_start_counter(TC0); + tc_start_counter(TC3); + + /* Wait 10000 cycles of GCLK 6 */ + while (!(tc_get_status(TC3) & TC_STATUS_CHANNEL_0_MATCH)); + + uint32_t gclk_main_count = tc_get_count_value(TC0) - 50; + + return gclk_main_count / 10; +}