From e1ff22e4d6ecc3f94cb5626c2ca78eb84d856cb5 Mon Sep 17 00:00:00 2001 From: LILYGO_L <135582120+Llgok@users.noreply.github.com> Date: Thu, 23 Jan 2025 20:58:14 +0800 Subject: [PATCH] Adapt for LilyGO-T-Circle-S3 device (#89) * Adapt for LilyGO-T-Circle-S3 device * Adapt for LilyGO-T-Circle-S3 device * Remove comments and modify the size of the lilygo-t-circle-s3 image * Modify the code style and format to Google C++ * Modify the code style and format to Google C++ --------- Co-authored-by: Xiaoxia --- README.md | 4 + README_en.md | 4 + README_ja.md | 4 + docs/lilygo-t-circle-s3.jpg | Bin 0 -> 48137 bytes main/CMakeLists.txt | 4 + main/Kconfig.projbuild | 2 + main/audio_codecs/tcircles3_audio_codec.cc | 140 +++++++ main/audio_codecs/tcircles3_audio_codec.h | 38 ++ main/boards/lilygo-t-circle-s3/config.h | 48 +++ .../lilygo-t-circle-s3/esp_lcd_gc9d01n.c | 353 ++++++++++++++++++ .../lilygo-t-circle-s3/esp_lcd_gc9d01n.h | 99 +++++ .../lilygo-t-circle-s3/lilygo-t-circle-s3.cc | 233 ++++++++++++ main/boards/lilygo-t-circle-s3/pin_config.h | 47 +++ 13 files changed, 976 insertions(+) create mode 100644 docs/lilygo-t-circle-s3.jpg create mode 100644 main/audio_codecs/tcircles3_audio_codec.cc create mode 100644 main/audio_codecs/tcircles3_audio_codec.h create mode 100644 main/boards/lilygo-t-circle-s3/config.h create mode 100644 main/boards/lilygo-t-circle-s3/esp_lcd_gc9d01n.c create mode 100644 main/boards/lilygo-t-circle-s3/esp_lcd_gc9d01n.h create mode 100644 main/boards/lilygo-t-circle-s3/lilygo-t-circle-s3.cc create mode 100644 main/boards/lilygo-t-circle-s3/pin_config.h diff --git a/README.md b/README.md index 447f262a..447ed72b 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,7 @@ - 神奇按钮 2.4 - 虾哥 Mini C3 - 微雪电子 ESP32-S3-Touch-AMOLED-1.8 +- LILYGO T-Circle-S3
@@ -73,6 +74,9 @@ + + +
## 固件部分 diff --git a/README_en.md b/README_en.md index aee74095..d3964727 100644 --- a/README_en.md +++ b/README_en.md @@ -53,6 +53,7 @@ Breadboard setup shown below: - MagiClick 2.4 - Xmini C3 - Waveshare ESP32-S3-Touch-AMOLED-1.8 +- LILYGO T-Circle-S3
@@ -73,6 +74,9 @@ Breadboard setup shown below: + + +
## Firmware Section diff --git a/README_ja.md b/README_ja.md index 93a0a8b0..a659df89 100644 --- a/README_ja.md +++ b/README_ja.md @@ -53,6 +53,7 @@ - MagiClick 2.4 - Xmini C3 - Waveshare ESP32-S3-Touch-AMOLED-1.8 +- LILYGO T-Circle-S3
@@ -73,6 +74,9 @@ + + +
## ファームウェアセクション diff --git a/docs/lilygo-t-circle-s3.jpg b/docs/lilygo-t-circle-s3.jpg new file mode 100644 index 0000000000000000000000000000000000000000..45985d878adda2598cfcded37f1fa32306a6d56b GIT binary patch literal 48137 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

hG_fjrV|#^vQ=YWF}g;sJb2cf zIahyJK&)x*jsL(oA7^ZxDv}~UkF3Os$7--{!gOOxfBap2Pe92yb3}(^O;%y?!|Kb& zkde^mBqFgZw>s5Jog8h2?}@tSaqg((vdPmlA&5y{lQMK^!Cp#H;+A~LUrZaodF)qaFQ1; zTkGH`B%x?l;puKu!9tq9g5ZlTt2Q^A>Y8XP?l@0aV3}z!kJ#Jak|+uEy<025;tN&D z7LRmjpKsp#FyA4G`|Vr=%+EujjLA~}ocOnUX{}uu@P5-(C@<E9x#emAK?tZ9CCBSvGsMv{Yrk;4tii2-UyMl8_&{ z^|0fkwuNNmK};6!l;x6GW)(%Lf==g_#Vwk!8^1qIRN`vt;#Iy7r!#{{qaMQ_h^j0* zbGMzFpEyjq&+NacS{l>PWQ;juIIUoRr>I`8HOn;Pp}i8@G)Mb1;6!;U!7js5ldEx% z#DCQ7DZSRwW-#*~FCXo1O*7)1i{u^taksfNJlLKtRJg}oRSgS;-xv9{mB_9~A+U;D zRLRR)#Flcqt+N-e9~NlGLnmoQ4zw1qk^?G>;Pk z>%GZs&5zL4rA7aE6m_G|_6#RV`H&5Xm3@hm-a-b!WRzY5`M$@>%y6iXI0Aw^zen!1 zp)$OSQYm@#8_Z*lo@4px-xH?!VDhjyR_?Jfw)gYjnuWS^-3b5W5?;lya?E-hJ(q&olc7%o4j{qd-TYxKC}J)Xu=_zj+xh#+XBU`Y{y?y zlg{n*<`*2&)-_bvYqjpOm;|`E6ZGB zXl0gM z0BK&*-I`LL28AFOpXvOS0T084r%u~@)iYc&wev^n9o**U&ATgISb2`5Pj>s2&C@2= zKuyNg`bFU@|7#%H^Zx3WCHfkABSO!ggG>*Us@jxaX0b0daR`z&>Q9_o)TqG<%|cu< z=bH=ldVhyzoHV!H^fRi1EV`tbN!9gpMW&_w z#Fm(q4;i~ZJ>!|$Yqc9#;*7fjzL#}kl1cyE+o{}G4>SDe+yDOLv~UNpefQ2MHOU3{CTt70kwB_{7K;7aQl8*&$pK2(Eu zdwUt&%(I_}w#tr8G@@AiYUR(KV?6zgskG zmkF7^?LEzbRdlsj;u3oxc33R&7O)d*jehXso~_@&F(dn({>V+4V&P~U8OoTkD{F)# zSybjY=RV{yH;Yd$*Nx%)NVKYBtm7+Aee!sbeX;~Ox{y1&B6Mx}nVpa0#ERtW+3zH~ zzYp_i-xZNfy#{{e{`joJ7hQI_bPU0HbC~RWe zU+Cl~Fv_*uYry2hYhU|mIj>puY*s%zl%Vr%E9<3TQ^wAlU!wi=o~)taD<9%I*@q@u zJ7rBL6sKFt){XOqG&{)^kg>MJYTjcHO@l_apLu)Ea(IOMknQ~%V%)V#mLw9rn|K#C zlJC~{*37Tp{FgCPAJdA+FY++{rYZF^;O2Id;16Zi%^a_b~<%c?82@!Jtk zse7ql`oim70@Zm9OLesF&-bEZIrR~>dyv2elM1gsrY&=^!EsHm*>f+&59bQoHEyy7 zX=VY>O@G~b^phLlNpQuK{xH%;xNg=RdM&;2I!Kt3*k0pN}o&!cqM zTI~grylVQYN#RRK*fpRVc+pT>o^ZQ-dfzW=llQxxkZYq9Rm_3i^RiE&Os{+0<=L`# zx*Si1wr)0VRXprYUwOlhkP*(xu0H2ESN}uP;ndX{>ni_a*(f1pDJ-*c^~>}|S;xtj z$dl}-PiZw`daNap&j}Fa^no*>^*=sXWU};;h;X+RZeIh;Xp!yRJWip=8^eDSl+hoB zU)d~je4YF3&b^&i^&;A{BdA_EWTkY4By0W}cy>l*G0SfL+YG<5F8kk&j^}nN;ckqz zYS~&2gMV9*)Y_~gfY*H->tlfw#gL}(zUgLU!n&5c?Rt}m^lo97f~?=dyIVbf7RN`X zsxr)w8^$D4DBz%D*y==tJ~fFQbnkfw;YUl6g&?ZFu~(1MO!6#GNAKB)a#gCWJMU2G zLmfh5T&cTQK(Vd?xvg#PnX4&%nfKflLSz*OY+Y|HJG=R^OSPS+-3U-#1izPTW!fFr zEwe~9Y<-rco~HZzXA%}M<3r1wr}LED+>zeUI^+mzdBqbnfuf~~L$aa}G>Kh$rSN=1 zRTdjhM*(Z&3p5Xk%Q|zib4^c<6l&MRCuPa^gb=vs&}WL!(^qj+^PE1zscW;M53xM3 zmyB3kqF7C9H-3wpSYNygAX`rWO)E+MnNdmORU9$axCY)Ex!UJ38SRE!&Dq}S^tcA5 z-Id+RqTbt2$*^c$TxB;^EuATRyAmr?kO_G5Xg~Yn8lZk3EqX;86f@)7va0^WX2pW6 zQRx>^{xl?2)kik&@E*mh;Lj9-U9uNmg4FXNTluZR1eUVO)H_}DBqWm3g!4LH3Ee^I zJ1^LGGd>UM{gRhykyR&L*l@A4k4Dp0%74i?vKbk#XSJ6oBnc_|C1&SHdkw6Nha9gp zn%B+XcW7Supb=SFsR|zh4@~yY={Z^a*7i5fFZYc4w675PuU_n51BCRw5_xH%UgwuV z!@Y$|FFD8-{u73G6G$}N}T0;CxT@PXZLsdlvusfhgWY`J()}!er4Q0 z<^FxDpqxsTLNVF(jl!XUuUqp@Z)120xzne{h6$UVgWZEYj^QN{ZYM8?0O5j}+obz- zn%rK`AF;O5Ef12!jfM(Iyj!a65%75I8|-0kfjd{9i06O9D!nrW^I#?Kx=*oURwr9I z;6G9w9QtlsW@-<5^0IY^wvK*AmsBij@FmNSp!n1yM4&s}Mq9Go=*cBTRS3beldVb_ zGm_ZpPicQN^goMRhnc)6QZ`uXwvIW21J($SfuzZ0rxSss-6=FsT;{j zd!w4)Nj}Q#YuBLcZ)n%bXQPrinfY8zVl2Af`!hke+a-U=b2Xwj1V)*FSwAEE_pYIGGET%H!M_jxH)kqc`408I5G01d_RIgzfJB(k_J(D`hv*`+4S}XYMv{8N!{o=xi!%@J9~cT^ZcXAye-;wnqwpqYJUtz_pwq1%WP!^lb%Z`dc~XYs>*bxE>V z!q{2TQ{;AgEd%4L7|)Wz>Tl>O!JMu>HuVAh%pyl8Tv4)UhgDUIVimGrAmA{VBrait25TL z)(l;7l>6LRn8`?G?tm0!(pcSqZ?FM@6IrFua^wi{^Y>8cCjl^Xm4QX9R#lUf3p2Wt zpSUM;<8f>D+e*17W(7-krzueTcppkFqnk3 z)=G!BtYXd^nh3IvVYEUN&upzD?ed^7uMzu*Y^@5b=I9vGKjc&I^tiq#KWn|{v~5ba>JbJBX`hVW9pH2|0X63hc7`e=>%Eu? zdpYj?GG)0fIqUHWWy^4fzR9caeN$WOK*M6teeX8ESRZAHn(pzzGuHQg`r|QvHQN## zjVB+C(oJsoTmyC09BIXe^)ypWVq{TyUusc#-a5SUroLz6(|lELg{SU6b^6@JZG$%# zNGx2>p1iDKTFU&i17%ghY4VIGM=!>C->3EzoD=OL^*`EO@x6L^nW30z@>Jm@z@d!} z)SPnrskDOL4YNyF&`Q%*L9&jrS`F%(?x)>GMM1S^5vjl#=gQXoN2E*dVTN8UDg8+2!R zagIKWVf6R{YPlL!rRhwIte>^7sOiML? zWd~ip>af!|-H9Rpt$B6on-u>ZZk@)e)2Q;|R==K5M^+5|i6d^?)>$;RlUxcT)KlYdVBuY)17gGqzRh-jr zgt#TL{i=W;6E;KixswL?MPZ$q?$(%Ft(F8yblBF+&k~W5_oTQLCxZExhiI2q(M&eH z2$Zx3>5Vu3+;rAP%CF2`xe(bcx9TcM74>`Kzu&!=s+w@M4q)_s#g*QY5Q(vJ-iNII z>A!~m>bJ>;*1+ba8$|l=Oi#2_azb)r9oC-k3D(yX>*y$4Mop=>gtpsS%1^$BW`A$u z7-3F-e3rK(yyJMeYeDmUb>tjla9HlqH9zOKy0ZK?A=|FDw!Y43_ohRDe6q`iO>ELy zd4BW$?ny{SK#fdjdDAn+awv+kn?OQ_yLMeJL2jWxz+NQ7=Sj~WTj$0lHs!bkWxct- zM&dS#U)2k=FytNKdN1~lO1Oj3d!l*3q25^9y=dD`{6+$Y#k)`C`tIjzdhgEW4TlKy zyCH5}CcbG}?_6yilIGTQOJoY)?G!q$y@+NfX}kH@-B)5p_dJ?*DG~4d+;}n&Zg$n! zcBwSLaZ94F=_+rs^`I#Hv_>nFs%0!zj;vYZxzjx*gzc^M5)QWD5wxVb}H-Mq!iq|`uo9|w_lI+ zZ^fh~PyZK@1>1x_*wW7marRxKOFvC6mMH#yI2BWLc*mkuYm#v>>3Zn z0ae_^_=3(e&0PpCO1ZIk$2E}SD}|MCBlcW;F*Gavrg$HTVo8P_uQ?7Al#b%U#BDUfw|q;yx>MgSz<@ z-JeFM3nnav4s^RtzV&@IWA>pNeVx-uJ|I^TgprmSRN#Gx-)K1(+yN<*oD80*S&Z-d zQ7BW81pJDdqGxBaQNr%+vsWG2%OnUZvbC z5wE8oc*(B$skj{ZGcj~$oeCI{Im0P`XF%dlSLq8dwAaT!#zGHzswEyGZ%$AuY<5hm ze%hBR58zsO!3CrIAXtiq;BW@oM%h9?{auq)qgq-w1y~$zB|o7ZauuO_ypueFOH1b- zsLVLSDH&;#@p0m8)ATrAr0P#)Do5pSgJvF?c#E*9nPTZ`V;^coz1&DI;z{)d^d9hw zATYbqj9AY`W^jq$+!*@juiO5#qKx>z6Vz*4igsqMQ-yjGl78Zs=n#1fN3e{NJs?g= z?c|RWd&ch_Z2unhKB^VzPfHTl!+OG1Z1`i7+$a94it1AAU&F}A=^)AdmDnTuo>StP z`y1!Cuh>FgGc?vODi&r5b$y@zNyY7cP<<)Iks5TRYwlErsIM8j@V9&MLPwAl_CT{7 z7Lbss^fPOS<_lY7O+{5==*b~#6oKbs+w70!MidNbx@PY-oeu9E^#=w1kr8J|dvx>d z_(-R-(@J5>yB0PV*Yj}Au+Cpe*MRM1)P51q%e=bg7kdFf#_k&TVMkz%clG$w@}016 zwBW8Vx3!i1Qf6k?CHqwcyx)7j=o_I`zH88Jy(dxkxsWwNyZ&Bht6%c&FbVm~C#|g* z6m!n|bhTXk4ZFORuXC`wm!DO|EgvS;?%@!o*uMH!Lf(9)BPH99=X*jTf{X z@>NNX_}|U&!r#giL}4Pzn+?~1<*7Que28h>FN=ecSI3J1{a(bHC6w`dznIQIv7{84 zA-6ZPM+2|)x)vAP*aG{X91bWvUJz;;pSYsw2$XR=9jE%X6d+S^V7!pV&m(e=oz*hP zt-8hS_s7F)pv3DU;Z;Xlypjw@z)6FGtY-0_*-nu{#k@uOzM{^PB<&UlP-If}Tkegq zX5=-nH1PB+C~#%wiwECtcZG0rL}>FJOH@)5(mm)Jpr30#H`**;3pMB3SHai7BEiJqiOY1x#e-Suw1!|Y-@=>}KuO_SsLT3R-*bm+ zAc2E^f2EJ2-0PGlw^q^hkZemz>$R&3_aBamIS-qK(6WY;fX-EpiN2OCK6$F2WxAYz&iD4R;%x=(N?&Q7zayNeA%R3_@Qc@MkSUU6ggc9r(;yx=7jF zySfRL_8RWZO#k>hxWs&v2Bvz$AWm9lg%=ldkFK>XI?)!7{z48Oc&%qoYWW!@K@*u%mNv0f?;2r5gdQ8&lVJb1qMYanBIkh^8Q=|^U& ze#24rmBZhE?(d)2Zol=|D{MXTxpX=czs!Osd{MVKz#MVB%HDc-XdB4JG5Mu3#^=(mYF0)EYBs?!=Sz9{=!n6wu-m3q&nv^9V+-`QwYQyw%Pd8W{ zcl%(MVI)M z@QT6aXYCDYzkj{I(8Agr30sIfZFs+Qe3JF6b|!>kvWuo62uxRFSvV3)@nf9TYu3d5 zmyOFjg)>`W?amXI!lvXqmUn+mm-!P*)1AoHuF5t9ng6QwjO$AEvzpp=d*vlE;*FY@&r%w>B5-<|VdZhm(X00P_yJ_~vQPY+T^|$J(8tNYg zAS@_0C9<2me!MVp(c9bmN8tG{(SarDldzszx{ep5b&EA)aj=!YsZ?@Si!XJN9uG5; zhPlU@L#}}r;VON9Jvxq7vIlSD-^A3TE3?%miIL#R`*6#LOt()%#-%P()`aNo2eJd> z@2`G+K0@x5IZ=+6S)dOPQQK0^oeQAFRw5$zYY(L zz0-_0B)1^er^e5fgbF#B7haF+zWp0a`8WbT+ho|27tumelpe>m5xa&^@};s+x5+El zBCd-;g{Ute-$shlS*dvOrpiiEGkuj8o9;eC(TWT!nZhG@J?M$%uvqJF(TARnv?7db zvdlh%z1W+6$-)Zp{z|^ISX)dOJmNh_{qRGY1mb4=$WU`WqFgW+UVPh-NymW3P*uke zI;yIT(=n93;q3b8+k48Uk%>}YdiM9MS(Do8gh`-QufnZ|0+nZTj+b#Kdhw>@+#NPP zvI*xMuB<*KQGMhq+W9>PT{ZC>F0G+OUKTW#D?6WJ)alE@e{r*3OpN0e`uf<$g+654 zT_!A1U1ruRsV{0WUic+ll=Spnj+QC!B=j^Nz<01!^JBkKtKU%1O4lLfpFO;Esp)9z z6RzxkV-oU?vLjR`J)5I|YVWE)t4*w*F+?{j21cl90oLGw;8n({g5&>XO2w z2RzYj_XQ(Vx>q6}J~-E1>a{qRpQkaC99!KTG$v4O>|}7OV?Dl1KW)B!g}A)EVz<`U zCUXsJ%!ZUXDFh5ZaX4q1yrNQ`wXS~pD(VU8`0ZIbMZ$rWzvvq6alIFpB!2_G*Dow4 zUri1!d#_2QZmhG+DeW}`C+uH@hqIMz`7V?WwJjq1W8n{if^7brEyivR3`sK^#BoM6 z9QReZ;m%o}#GAD`>&~U`2s!BpL=XwlC3Mol{Q*`hBZhRZtrCqw*t$z5 zE0iCmE{A+$D}MhsOyXp_BL2_zL04w#eUtB>KZp%BX#Kv>z5o2t)vWS6nb_tMjsYgM zi#o${ZTpl3<5KF`s`UK6-7)9J@-J^~bxG_(YEiN?`qXewraKvzE`vScX_=mxifknk zCb>1DqG4PWAH)+O4pM?Dqp{9KReC0K85O{O`wO<+%wcgY4>U{l9hlrx;@$L@P) zA>~^(anKPqY{=Qu+mV0iTg#lnB|kq@p^jqU6;|gXO%^BzE3~4)IM@3jj+rs;ATc-? zC)!oCyse`et~xNCe)LTkUNusmAoo(OvU0YZ9i z%A8Lz!z!w9#E3aRzoM$@&@pJNSYh1F{)bnmT(=*PeG=Azd8D)qRQZ$F;tyu7lh+sIj|UxEISDxr~D0Ys7eOUB&kd_{Po?l&#H9~{$@a&eAO#IiA`RWeCn9C zXl?`Iz7b*FZbIo?HDbUfpnXkXW7}xZ+PIKFg?`s1j#_P>OhWuAn>D?2dhP3?| z?(X7RQmwcaXhfKl(0J85?Q*T!9S%rU`qk{xE0gaRyo|O@dyfUCh4R&Hn_x~3F{Xhc04$}62k4bVkFCO97?Pr5>w%01=9YE>S0r2DljQLf{Tzavb za>psSE8aW7?JaI@Zg|CnP}#br%#S*Sgq=7HN!vQp8UFwf^Qo@aa!snoi}2g726)A< z5wx3!tZeOd9KoUrgz6=R-W?}D15?s&HN!kpywSxfJ3!2mgQ=5X#<))hwG;79DQRUI7}c8MWk49VkPc5XkOfH=-Fs<` zN;qr~*~ZjsRA(d*4(CuFr0zP^EU(Kx*)f?u&i$tu1REwe&p-ozD)sz4 zJo$9-{M<6ji&6Z|lg8&uI;)qOvwgem=R8=U@=O&ai5M$J9 zBRsCn9+9}s10oDjgn1GjGktKl%DW}<%)}=pkzP;9=}>_D&UNr zBjReRfOi3U0kutpI)s}!IQ?pkcEbz~GoE!2O_ELt$E`N6=#u{cb*5Fk%b zL$m(?VIDVs9GY${=9=0a_YWq8Pt4aIOgG7-msmbXO#8&-lf8W@H6C+?R!xsNe&o{a3Xawv4M18CM%~*dO2JUTJq7JyS?; zFY$C0D_fQM_0pw>wV8#I-dypyF9p!CJW4+l#%#$STA#=f}os@ZMUz&wJ%I%jlQEjwZ9wx0Bv|z73~A?duXDDYe*usk=f>w zUhT1#PzDwfcC63VE~ny_oL5F=F`CB5i|jzLvh76B-b$8diC-B?rIBWU+1%S5IF1psh=~|GX9Q-Pnas5((;Rqe zZr8oKg6cnKd_Eov#ZvCZUeYAj_YT{Q z34$fVrR!d}h=F5t;Y4U~ayJKj;|C*Z)*1NYQ$9IznNM#`!#F=4kB_Gm;`f{+K-@@P zVgUKp*239P7C7AC`I2#2^OibHY|C}zy`tHyKGI%Gi_QQiHYPxLlva@jj#&r;G6Gz$ zagAL8$6D9FQHyhy_U(HIfg<5NMJ=wZUI;Jbibb=E5{&GtF<~S_3;>r-I%6KSGn&1{ zsQiQN$HW_l{hPS-i=_9_ExrjqiNgMb*Sp}){;vN3 zQRVq}_)TB8^JY%D>r9=aG@P(2Es)iUq|3_!vsL;x4u!4z1Hxgz279JjBh9DO#K=SpVTgYRPPG}_HG)v~xT?QzK%Krd5D&mbdEZJqK6*1C$Jm?A*)oxf1 z5a&R(V9ny@IfK%H_Uxf@1~bZlTFK))|*{Cw{08$06%T(Ag9{>7wtM+c15y!3h7bt zSK2nzj@5BF5Mal79GD{q4+iJ!7w2C?yJd3^vJN5Gpyb7S#I5fC0QjQG ztNx;&O7!?oDO_#3)5<65U6?Uw$+2u|!nW8-(N2;h5X!)Vc9m?G06E=s?F)b;_KN98}*Zm@hb~k z=PfK#-Cai<@rG7Rp<)OC5(YLEE$ubX`#WRV>(0tuaIP7|mx$S1EV0~89rDb~(w1p6 zB9nr3lj)ICUe{e|mfd?J+1VtX=e4oLFA=p7thfAOb(oa4lNn>M1)FhGQr(*A7B0%$ zb_z%$?JcTstEtJ7`d%cGEUfZskxZ;`bsa#A@+1m)_tZ=2*5N#3gK-;uJR^+o%ftxE zg4XIe2Zsj(Nj(TS>5)^{OKc@#oDJs63 zbgQb)<2ME2U7nt9AI5EaO?AYhhT&e(-;TY+4JZtl15s9B8MfSyD$R;~U5DV7;^f;U z~owXGQ(;H%jx^sA!3hciM?0lRb zj7c1PGI^ZH{6_qq_Cqgw;*vv>OY|qwt}@%xo?60pS?qgN?B|9@1Qxxi;1-a;3~)f4 zS`|cA0BH%U9Mo;`t$sZ9=<=Sn?4AX|?v~iKuX$|+xQ-ifA^WFcPALT zxw==kh`}`0+)8K_Rx-_{^f(?8&(Cq1)V4E}m)lonxHk;omU5>I`@=_dD7bf;>E~%1 zN~mpa#PUTP@wHoRq{qf;H%La4;&abz)rWrHHhz{ zCq9`Y>0aN9)XOJ_pZW4(kNMl-3AxnC>x|NES!kB^tk#PPWtk!aCD;nhBlSD=e)F?7 zPbQ}~amdWaIS2tc&!u^P`>WCP&%-XY%Q+_{t|V;o&Rm+cC26@VB=jSC$P|zj!5IEj zP{3spYF4rC4kQ^Ky{*~qO#*4 zP)S=$V;SjCE!Yj0IW!d$kPb#YDPX4LNcwrz2W-lzxx)&SHRZ|3#Rv2lHg~TZqQh@h z1R9B?CW7@)UaBsWHI-JX2wJ5STA&?OD!`Vgp)c!MX*GJ#d2ZSh{h@20_=2wcTFvcd zP@Rls@&dPhnS7`1=F}e0&e{~E&kjj}AmxlQYHyx`ezor4y*Tnm<2*{-+fLA)aj{(b za|h{O>~7=Fmqh|O0k@rQFtx>0rp9W-X@RgsYBKwm{{Rh6+piMrClKsi%>MxSjuSWD zEzp8ViTUn)o1pVGf* zlz5>?ZlQaG5=(_9HIy?A#-pHaHs)*5!!W+_+m2pa>b>9D5&KJV*p4qf=WHx^=Dr~6 zm!EaGcX>OguuI5eVynwef#}ue?us#n8ri z=8+?eL2z`i5@(i>I*ivD{Hn>;o{~8@qkAdh&kdZW^|+;@|Mw{;s8s$jD zV3Wx2T{ZSzb9@13lMrTL_`0w~z?Rs~inSC#j|~ z-$uK0q)CEce@LEmpnBsO{!b{-YBF%l4}Mzkg_o)SHyAR+Zp6dOEuhCjlS6~xP7N+ zdu=QnR2i+f7`3^!v?Y6|mG$`DNXrx}mCnHKMn-EsS9&}rx1+kcwYTtGT|*gVV4>Us z+D>-QK7X-8O+FJT9pY}sJhudA>HFHLr{*2Ejnjtqnj8Kh9Miz|_fSCx-XKZ)#bMl- z;geG{Xdt%x@20xpuO}SlUL9T6Q`mFECuZi6;Nl!ViHH9H&v3iz+2?K^$HcKOdc+Zq zOrT_W;}p*S04GT`i`uc=j`4GnJ>l1{FZ=pbO8$TU0OBRvB<&}*!@@1()dvB$7RMMr z#Xcw8bNoJ7ITiGA$HR(G-)|2W^XAKw_0#8>jdHJMnVjVMRw|VBD!LtwvNyeMTw<^7W||5uIZ?mwrX5p=*G^^*tyqSd{L$+npr`3_!$2H~j?wBr0SF zm>nsxCxy&}suCzapqlruBcOqxAk-681l5v7N+3IKXfHJcCW2<5qG~RJO%yEEm4%bO zYbuIy)}raAfG&{sxZJ9}XotvF8-kY!R+Z!iAI7fJm^ zdpLc$az7vKPY-|W#km{HYRzkLkMSYEKQa2(w+pMM45s7Fp~~jF#}T34s#9Y)s}Sg$ zf1OmX(LT++zPR>x?OTR+g&n@`vUgW#55Eg+!|A`uvCD^+J$q{mwRL?zr(7oX(}{4a zej{aZ-&%WMBwCLN>1~^xvzeknMM}`)<Y-S*nuFHV=Vv*P{DvLP7I zsHu%O+o?TsU9siy`i%Hp?{2qXdmRsC=eU(SUD{qMlPasl#yE%GRStI3l?6`xf!{rL zu32T}^y!Y|?RI|8`#3D*;j=_8Et5=f#T<33<3{6t>7Y4?E>L9BuO+mOsWv6UJ4ZHji2-pJurHXJkFq)g{!gF(BzAf=eBM$-o#J{Hb^LnwZBsw!M_#J)*x` zjx9SQ*}~~3cW|f#PlhG3HX5V%w&Xyj`qKExcJyjousTR@4&$DB{PFZ2bZ0GZw*!iH zONHKE!+Zthz3se`OC8<4%OsvGj1$PNHURSUtxUHx&y1OEqlEBy_;viZLidQ>H2xPC zS0+J^r=Rr2IxbERw(i?m_5<066zw(Z5N)mTZiR?4&OudC*K>`>AP+oOZX7)F)6McZ zd@<|Y>HN9v9}~Oc{A%lpUrf^7UQY8(BXf6RSf8n{W_fP(aN*&Mj#S5bmgD$ z2KAcL+xsTr`YjFgqkD;bB#*$Glm2X1pPMhIUOD!tZklcv+72{H&F#Y3z(Wvp^E-9$ z;@_j^ZVbF^qxoqBNO{rd!4Q>IrWtJdOZAN>zsCnC^GzDk_Bk0C$rZ$UMyf z$uWFm-5-@_L6O`Cgs+!hI?y|JM9VVd<6-4l8E$Tei);++Jm|3Gm1IRAVB>#Elv&vFZtxYJkR1;kU*Fgi&Ub@JI=qp_oA$p1u^#D88Rsg+9)KGky%^p*? z$4$+vXCK8CpSGO;0BY~v_9{i#lQ>*D>nX8g;B z7;dfv5`_@)i*7-15@Z-*`2$|N`*C=FEomzF7YNnEK5L4S;cm{{RX4{{XFXu$ey^;gU7? zPwoCM6}J(&Uee28lHW;Ujc|BfnvlZY9YaWxO(#g$vt{-HR+n?yV>{@7X$!WK*sl_9 z7{+U>$OyAoF4{403Ny*$LIDC1EADV~0fKM~4SlC4UYaRb{{Xv)S;LO3M~MFXtI2nkPelGP#N$XTct-}{!a`(4Hx~(zwqdRv3~rr&5#VY6eG{Uw!a-c~M6qkaPpJK*Wsb z0~l%z5Tcl}ch8+bvkE&AfKMUmMHReyHaNz4dDNpyTadt!&wqtQHYisoCtx-Ph{-%k z8(=+XrokVGI8aIRplp#9h)jUzJ7>h4v#CPkpRejbCdD}wx9I9y8vr|UYUYa(V z>7_#TG$y}VXiasU39M8UUW>ICtf*P4Sg;J+^Qlz>R$6&4X>Xw|+c?Ccv6A0TH?;eN z9gSvYz)(8ZPo`t=-rR8ubmEeOU{at{HgURLX8VQl4Wxjd~nLyHyR0 zld$JjGIVd}RV$^y_?_1b;xOM?PVihn;p37v!z&HJ6>hB6Nx63)(9g0Lw_YFZFSGG* zo-I5!-KVla-$q*k@j(9Z0qf?!F}E)sSX?f?ow~|yKU%looG~Z6nrmBYfpqCMiDC?~ zm5n39?XbjRv;LN4uyB6K@C#1P#lahgT1{?2V^-D0P_} z5LreCmfWe-B-4Lp`!^2>o;jBC=HhZCONpfjt#jVWX3UxuM}?WffB;bK=S!8hqUp}z zp3l2I!;RG3K3+F$_tUl2$%~HOuX`)|&e)f8fH$kVeWaS58`=KGaIO#7tDeZ(Pj+}+ z$heLu<&ZmD$2P4UCJow-JyBiL^X>?YP6Y zU)ItgMskAH*akR6SWehmD=6gfb5zkjOs@4k(?atc0EC%arE5878{HfU9r&r z0IglIZ*P~r+`itS?Dq?}><47;6}Yv`W-cdl8PDAi{s4c*!2E~AXWPNWu-sb>2xPqcn}ZVX|GTHmOq*6gDBc>Rw3*N*nu-osgYE*cDzu=iMlmp?PN%BB*@wz9uYb0K|FxvMq{+nMs(@NK0~EtOD!g{0N8$Xp^-=496vG4`B7s449UJ5Gs02AD0Bi?NRR($5WN<(rX9ELsLAui3>PU6> zF3jV@fmyX)j@z@`S$Ja3ZI~$;$zFAjIkvoOlf;W#77j6;vs;Y%bK`w^XCx4I9+iD6 zHP>)6t3S?x*E~W?g;;ds10$t0F+I?YiuZcGYLC3k<2>tznn5`8riFLo&YBcw9(2%x zapzM)cjL~cgf|PEWK&BJ+z&dMHiqFxQBzB`ec^oSXbHJrI+g)zrT+jGXH5XzFP&z^ zh%VR8vs#Z6?T?5Ecsk3Ualx*6wEWxJ$7)jRu#E(Bg)X2r{DpSomg$=O2=T_2(X~EE zV!6$ITyfNSa>>MQB_upjJJ|4EC(b;_TI;7hy$24sP8-CMAi(hMECjHgi2G!EZ@1F1 zlxjHLy5OzD?hWKA{25P(55V8jx^d^es~O{`p9~4EyK^xP25P2_jgE0vA&A@6O6&Gd zj&VK%#_jm82)vfk^3}wVO$(J|Bd9dE%%-YKal4Pae#<{;eh=HLSUWq~+2Y~7p$a38 zR>H%4dTIXv3!m`)JcpM)JBRvqmSx=b`WYWD%zt^E*V3@EnHyy_0zJmxmO4_ky}`CM z9EBr1xpeaWRX&X_Rlz*+3G4Ew+e9$V3brxP*ABReB^r0%dfOqjOG4!FXg2yK$WCE-`I%b>MdM)#%hDh+R`hNNJrkTEg z(#IJt%mOP0%WME6AF*1Kew^-e9^Wl}tp3yXToT&nvOdo4qq*WmOgDU1G8!vyF%kT4 z=G=#=rUx>7bAo9{PS)R75AS_&D=iYNizXYMcPFg`xKwu32IFCllmcYRXRj^# z&>gWOIAU0zps5E&(%MZKRAyg?I)hjCRqTNnqK&e1$O_KaPt*H1#XB{^_+`DU@yFhv z8FDkL&b;|dt#y|f6SdvBvy#c|?BWQBGOj$Sj~{z7&-DD0kKZ-L#-?4f;T7nPRfUAI zt2k_9V^vCmDM9>0F1v{o(tp#?Pb6GQT3_f zc?9;d+x{^6)^06qx9x4df4v`C#l>YM`(FV60D3X;tXykbc>e%veWr@Lw2^OtISP|`&9Z#!UYMr+@}!}jB!Bg$aH+36l;wz1oUB>5e<9k)X zXW+a*t_#~a%x~%CUGUFw%Pe%~D-WNQblV0Y#`&!VhRs$?jrXdWYO}d$7Ph4@Uae9JoI5bex;(C)obVJ*oB| z+7*IL*{(fraWIUTEhIWFBF;7_clnHuTJzau?Yv{#r;AV{r**7i9DSm*SmXfhxK z0grf!GFFfirgW+2>qSoQG7R$9f(i2(sq6t?0MfhY>FK>U^$%UC^sJe{AU=QT-kSZU z`(|>%2s?N%eEd89#c7@N@P605uXYRBwX;duJ~art9w_*Ixe{E-w?rQ3$>zig>&L^E zUYv8~ll{Ih`$c`HJ6G)nIk-0m55V{Xu7-jsGd;@x0R7npHb3E94?Nee3^C*N-Ey+! z@6+&E<}H#=^vf%6Zjx!8+}0W;+Oultwl$knFvm5`!dt-`JhB}qqp7Uzbp1tv%FP%W% z9d{sBU3&w+twxPvB#=JAxdG)sz=ul~Y@Vip$cSUo6a$QpN`UQPMgZ#)n z*Z$VY{{Y>O^HlMRx`(yWWB&kHKhIh5hUsUur>)@s06k9_yAS(Z2Rp^j{`#IVc8}W? z>w3TEtoSa%-La4V0KH%H)bL%F{jrZf`sM!sJ!bK)!2Pm>4eyuy^^3<^@+JFYZbnV= z{{Wh?c&k%3E+N{>DdH(5=pg#mKMq@K@<--fsg~Y;D(`INl=D3++s7=&w+ydrmhYru zw#_&l>YyCNAH-g&e8;76vf+0fCaidl)bKm533^e%cnR=GIeZOw!#R_VSn1A3$mhLwmCRwj zl~Rpd)!G=6RWx*Rk;<;wvC!c>R`-Y9$8}|OHN5dQsN{*tvY#+U;;&cPH$6Xe_FML~ z_Mh2hj@OA^L)r^C@HBA{OuBr8tA_cus^xevzMGu4?fchae%jrGzlkI59{}RKDpnpQ zw<&WN`Q=+5D#iHueK+}WuhF;{+Dq8S6kyQy$_N`+Vz+TT7#|lpzP>()mSRdtvrp04*nPdpUqX+ls))mO3%3_;c5!{E|HH zXnxY3#rRAoyzPt^4dWVY;gWKGYNUR3x$(I@>6^=o^B&!OwEGoj9EsV!&T&2`E}smS zPL|Q<<*AR*S4@5{{{WR^E=zy4_#d@TwO6$c)-1Nxd_pb*WhSBA@Wm+P9$?BzfAW?1 zS6Ro$kEq4DUv>D85Wcv)xqFMLJ=ZA)g_ zv$ch>+j`BaTC9^yu|kaQ=L(84NvztkKT`H{*+IeLTf3;c-Xl1|s2T3_&<{HE=atI% z;_D>FZnz%OIGmhsj>W-vbD7pQ!@3-+H=*jI&yhZuuG0y}j=tQcS3AF^PS9LhaIO$I z6_e%p+(@I|Z0IwH-IHXTW* ztu|sb!jYCcd@3o@Kyl$?lg#3Qt8H+o8$O^^*g0ESRwo$1IO|Ysc~Hom(gk2JXr(>V zgQG2id3w~)TQHTgoVQ%`szI_1Jof!-Dj9$zU}O0Tu_EvuFb>MQd}^wUhe*}%K9v%L z_>i*d8+ZqqsxwNHYXp!0=0yRhE;G2s*rLBNJVx|?zHhC1W>w2kU)p!S@!#uDDtN^H z)4s>|@AawWQbasT^#1_AZ>?V{XieBT~<*)Vg{;3&nVUXgF`(Zo(FEheqgp#c<0TjofwXDDvH{?6ubmk*Bp&EvY%! zZl4hD+}=m{f!N0v{HLpPZbJj!?~W6W zf0qkyC%;Y-cO9AGnt?CHXHpw|q8zI}`*rF~Jk+l5Gdq&Sj!8XfP|I=)1;E_%>rq|X z5JC(Aw@S@v(zhX_A&xiPp0%2=$52jFnB|I!WHSNG^X5%KsvD5_1QEVeP-%kaOWlSpcvpi_D3#c`Os~)fZT>gI#>?~ zK2;4e&u=VF3C})Wbwtx4I&|Q$^W{MT84xC|xr63v0Jzs4{M#Fv0Yar0AOX-GzO)Ti zi3XMOfYpFI@99KFY@Cg~1qQwm#Af05B2)^>MjaSsSalRxT??CGD*pg#aATwo z6woVg8S@m=Qt9Y>C)(?I)opm2V3DM|57xM4g_m*CSx)UgW3BI0-SDXvH;`)54}ty_ zyg6*@x#fey@e3=v8QKfDUN>H(Y<>p46DfT;*rAXQ5L&8jbRgAt5n;V*?9#-Z zwAhA2o^^I<42Pkn#Tf{uz?Z484Am`N1<$T&vq3G+qL%Ik-gP!Ry_M{j6yr=KC~d8O z6tWB!9)qoN&yr%^I9mNXfbd=u+1?E$(P-^0Vi=zGbMG8`qpLTPuKxf5y>OF0TmBo% zEO`F_+rxI(wzoWNNy2yxQrlR$(<8U{*ItfCCEMH|GI5&p<1yo!#m38>-b_}p+IDKl zTu8zAl#7KxbKL{a=TqBy{XF&E%-2^9JkD9z?rBuKM7mxda1)xD*eC%40m2-eEzjy-sJEopG7jbz8X0Hy@sHD@kqconKHZ&(zm>Wpeb) ztobYk4=@KoMFq?FNI24eI-)#IVqH3OjrZg#P>@}A3a8^ler0nTYB(KhykVTi*~SfM zS9IP~7u`RV1Q5CDKxPKpV?_Hw1Nm~HV8yJ2>?n&Cu6j@s+CNc^s3+YVeiD8Z7xzae zU}y+gu;vJ$g3XRFI@JW0Oj`s}QKsIX6C&SC(MK^)=lZtRFpSxVCXw>1O zf8|)XO5D%(_E!93fcA#nVxPOzf89&5&!Mh*%Nx;kT&_FVO+}>PR{#UQcPB5Au9)+E zRxTf>lj2-6h;ZgBcV{AC`@m&>wc1&IIqI?#ft-Vyq6ZynY+ik8iqDk=hO4n!y=k=4 zi)XDiX+`(g)KwPg)X)PtXTpq=umEPIrk$<@*&aK^2rFuB;y>>u+w$jF%aa(~#@D~> zceDE&S(VHU#4b>3W?|rrd7glJn&ao5cUZN2aqhHsqqbe0?D{lT?;jYwLxBann6`-j z0M8)Z{t37I-7B^{5_g|&SC=fh!gT!KjqzU4cF&3{-OSR6!4X7@q8oMRg;BDP*RE?< zFQ{XVnq#}L?A^G65f$y*s4_7a2z?Jeq*gt)^OMaa^O08$`m%-wH~w*F7i3DM#qroDps8i8o;)-_?G@v17>9I$pbz@fiX=h zI8%UwW+s7>DJ5CCos7Zj zR~e|8S$#a^)GCOsg#Q4~{VPZUt2AJoYUnzOy8{63@|?o+K6L=xLd_;wakn5SJR;Z< z4p$@&mFU}^fc7d6AaRbIsjzFqwj|&Y&YKpN0r90e`BPzw!pKnA2beyT*dOfRa51qQ zjW!7I{R(_LV>uMqV%Vq|DUs$m(`%y^$~QPw8y>YbnhdrIfB*>J8}+N?x;85a^dmC) zgNke|t&yLQ-+BUTMDEV}k)0-pU)`5E)9be?yA5a&MhcC$$f5xiR1tvKINzmGg^+>? z$OCG-MhnQ+L^)l+*ov@o8!F{U+)z;@W`dQo&!3eMVY5R^49eP&kVhgqQCtQS_x zZN%-wm_8YoXI;NK3Y&z=iY4LGi>QwN0peeX=R}Qs54F5ohOmgtWsP?3rr#d}%vN&7 zRhGSP4eh@Oy#hP`Tv_~e&!4jWpork`$s7Lw zVF&rvrzStPd@DKNd~9btD&%|?ujI$}c-5x>D(B|7r{u@>c+=tRhiN7tFArn~ ztGFM|pOYWi;av+qXS`-oKJjyJ06s*qu6Lgp)Z?|?;NHzGWw=9faWpk+d=AyXd<+9t8)`E$st5zXM9cpZLcz+7h!j|@IhLOL9JqJ3L_OdnG zMjkLS0q7W3cCFAvVvLn+0C%ZY8Q!B~N&>@|!k{9=Nu|BqZb+uoM5?E|&yX9`1rCBv z8!Sfcu2C=vMH8G7l;Wk@7ObZ95S` zs!cOS6+)c~2eDxop3?>t@+t0PSiGrVPjr-_q87>#P66M2>VPGPe1ZTu6-GOOgN}JQs~K}IVmh@B zVyMfVu|cN6Y~EbnJc_EP8U{%{2L7~0V!)Hn8QOyNQB|5DwwF`d$|QnDMjb)tMRdQk z?5{vxC1?X|JLt9}<<_9D>~);#;kVIBF_5lwwLy{6{=!X96tNM>#z*H@P}`|x+HOB- z3domp7|F(D8r1!2UOD<-D(Tt#LE@30;=yk&df=%)onrAt^K@+K%X@o?#P@{oT@&*8tb&28->)mOvAB|jNJ{#Abs{hnu7`%&Q$Nt@oyPjUeF zSBVJ0{{a0&{&k%AGqb9*IX%PfeY9G_gm7Haj$Hi(D;N6YX@jg3%7BD2MLBdB*&5Nl}Y)NU2y)N!#(!SUn4u|;<3bdt>ZcS z&E(E<&cv}kq?+|($A%O5%{bic?$+eszjpv}Yvi_0ecWU_ezm=_CF1scIYRJzVzLkZ zqECO~Jg7%1^5){^QzY`Ul6sz1LbF8_vB?n{)UHNGeQJ6OC)S|tb~}be#CVixaQ)l> z=11#_nO#MZ+vC$j*MbXd7-I0oys)*1)nkT0q<{z>W{A*|6UY(=Kx$Qn+Uf%uhEFnT zDbq{*JRD~j0~8W8R*zXYeth{*62I{WBb~yYW`Kj@VoAnE{z8LIxkftGJOFKuwJOD= zvG>4(;?IeXK~QwAZ3wlBatfo2sMHB>Q$=PHyD<(yJy;%81swg{Cm@y2gdVj4+e`{J z0M(qIK|!X`-aEMDR01_4Vfd;J?-P4_$1|3VSb^XJ8a*gDS5U5kP~idCdHT>KfOiTt zV|MbOG!qg079l!jgUm+Suy8&jrFCT8A;uJfSYrpRP_zwh?W<<;BWi)*<(TOJ5D6lv z7m`MEq-Ps}P&p-O07E7hP{?jn2)bP|5>SQC*fa}}G2a2k4oEzyuxlw{vu&psJm@LL zwAqU7)NCjf6ly~;Ad)f&sw~D-5};%p;Et35a0;A*__yjRz&daNW#2!Ctx+O&4hX@c zCNngSb&q>&8U$P?b~NOI!Y6OGlR6=t> z0l}cG&|YXJMFdx6-AM<~&~LTld_n=IzcMExuec-1h|&G6V>l~$!`FVX9Xrs{gngcy zBu#a0dIAFqc4-@iaQ4ZEi>Vz0E-Gx$PY47ZJWkl}=S`YH!ZuR;QqTkBPDjfW*`z!! z$pJEPn701_0+X6-(g-*Nc{5$y%yY0n6cO~QXhVr`i*&`O307?E(y?#nQ?(-Rj`0bQ zMzSnO1^|t8;;84y?(eDCu{_B+prAzr)tU;<)Df7Gxy@uY-2(zi=U#U{@##@b z5=STif#BEyu{0E-PWbX9bJRHs10()j02~gws5QdcVeZb7PQ>!X79u1i!oGBJ+;cUP zO*6>hRTZ){jBk*lin|qvqcn^P5s{6H8UUTZkma`rlW&1RAVP#lB*J8Z2U>$#O@qx# z0gr&FBFNd=W2Lsi9L)nIN#O%pV3FYikf0wHgTAHfjk0I~rD)CpU^#*1LFdF_Uyy7b zVD+x7nH~}|fUUkVd5WMHB#lIkK z!wyvi%)q-G>D(QUYyNx!a*9g zATsncQl3NIXc%m5f;XU87&zQyX9txMt+qnLY~bW|ttQKCYzSPFos9vbVoVk|2OEk6 zUSqvYImR$*z!E|hF(Jcw`c+0LfM7|gjIyqF!R5-J!7`m$6bsE!2RczU$2y{CY6;eY z^FcF2N-6`s;({Ma3OUULIp$3Td}xcJt2@v_XeLELS*RvxCV9|B-(f+f(HlIdI<|U> zU}NLR)>Wwyifxhq04c1g@Z(4rA&A|D79DKEO9zcyejhq0!iS9E^qy^z%hI9_H2wyr zQWriPb*Q4ZK!BFXC$B9u4MW|ia!5?zFZerRl(7Wf_FZ3MfY+945X_LOU|HtS1!oOaM?Kbf!xppt^6g@PIHh5piyQgEEtpJ zLFdbO*yA}P7_O|H45gPw-V?rdq!iK+a6Bpw+1{w7Vh99s{{RmmKxhMjxH%i1qJZYe zL8S9s1tNiR23^=3=>(C;P%`3XXmEi}a7i4^0#Z^pBRIwZ9H<(f#A2*;{Cdz746yi` zGDbNoa{cW(4YqZdg;( zJJ2TKNhIn!0Cu1m4q-ZnmMXxv#)7Idk3N+~SyhwX1Z3nKbD}X#6BaC2Y|$AejH_aS zS;o{8HAKxQnW!dcCTi?V(_&|OY)sQ)W}6dNtu`w(*qPd!6ExVFs4G5n6H&zl6G1b+ z)EX^dc5l{$p=}nI2OA!uv$fN55sYaW=nlq;(+h-BrH(Q(PW6z<0_9{y&JNh9(6Ggd zf~503F;EdG4KhU{I)>b~`3GPq83G_0W_-p@Fm^0x86;Xr!nR28gT9)A zg04wL&PL=m2{ksYkTCnc8Bnn!NMJH3p_~9rYS>T-C(qJ?L#V2;MH)J1ode~SrcoMy z5wV6ipt~Z#_=b?6fV}hTN)%)+`i9S(GYzn7BSJ8AO>T7UrAX#z42KNNbpyiP*eVn$&iNH~4dD@ysNgaDG}t&-5{#8>o$HXS+CP$(G59xh~Nn+s^)Rvd-G zk&NUTY$(9Z5Og^tV3E$71tN~`Ng&~Kr1Pf2UvO6ptZ+s}HYBP_l0m>Dbv){dW@U`V z4nSaWL15v5eMmKPVa|f4m@N1!j2r@|W9w7`wxR(ee2k7_s%QX}RZuqNf;rI@+>kQk z1oFo!1zjj4c!>aS=Rl8jz~)HgXj0u?MJzM7Un;u^l@E4CIq#o3qaD;_vB$&;GQ~7y zN%ZGHqqPNQg6Jk_D>M@{6EqW51kNZRG!rxxnhB9X3G$$spsepeQK@5$eJC~B+waRh zlpPy50DN-lbJnua<{WOr2bzFr3K6OF>Icl4ls;53N;InkI|0t4O;(_~ohk_)^A43L zG+iyY%JtRFsskF!2FX|CJd{v9QWexa`#-Pw*DsT=R#&V}+^rphVMj0)YUGde8KxR(^5)Pnpp!A?CcVMNkRe6Fp z&!q$AM|e=^2P0$1iU&rK3lN4`0aLhb!Jx@H;!J5DBSGh-_d^xjjfk#{q(@eAs=%&& zb3k%x$pB!5&JRihg>sC}bdk*GAkYfFrzb{0!6j%k6*>S1CssjWfG88(NT?mB375u` z%uK?% zqK4Z$=8z&%pnyPQo?z`oB%HVd&)zjqI?5qc$OPbu1i2-HF$V(}@~f~9Z!FrRjSDKS zTLYC5j!1)NCz#k(Mq7vo+zx$cGdyKY024uZpsdhBXeMYTXe%@jA6f~T37QJc1kD8N zwF6^}oDw#m*>4Jh4gvmE28aLz;Oq@Wjd6Ho2XIF&qKeRCcNtyJ;$hCF!P}~20x(9w zNX0Q-&d)i*~IMA?~IVVZZm?w z@*lj=Vv-QP0L4{K0AZbH<3%zM@y1l-$Ol8R&^E%}JBxOcy9HEX$T(&vl}&`srKA`v zt<6=x4G`o5xg|U!@iLs`(Mv^C5EM7fjSe;gI%4x5=Kn&#vC>eJ?K1}#O1S$=_F-&3h2(3 zk%vsF8v;+A10V>>0|Syd9kW4E>Pd8r?uR6uhwM-gMpTh>j}m*zeShpIHbELBdfkMI z7g17hXfsZ#T*;RAhRxD}5;)%o!wfN}%hrL6Ttc8|bFV(28UQ-U)vE=L4za%~1D^{9 zg1FR9;A8gq)B`^4f?O zQU{4}LE9Z@Eg8{S5&7)hC^8fZ2E<_C`p{8DEHZg?$116&b8f5%Cv5xGQI+lY+v!1_ z(S~-A^TBq3K0FBsxTx!0N)K8*I+0KxHnN%Bu6I86?WmnA1ogWRkq9FWI&bM9Cm$F@Q2O z@2-?S85LNlh%?}5Amc#usH;eF2=BEj9FoJIr8XZNEYajd&4J)V-{xupvq2`P@TK`P z{{Vn_epM9qjT#`#3IK4Z!#ay4SJLlc7 z4p@<&l>o;o#Q1g3_!WO&(tx39BP55^M$Mjp)DM$G#sQ6hZIBK|fo_bf5U4D6E&dt- z5dsN`u$+i~Y-k%Al$BZ0wGoVT0)t!$sMMx|;e1BRCkV`CBY&2yE=eaZq z5SXF=0Gmf5b_dFcOR8&TAgR(0S8!-5VSvwtY!FvY$LI8*xr>!x3g;l4^q`J3snQu2 zJw|eTs1`f8RQHRif$)L+=ojBJ%f5FzmM_B!D3k?9u?AZF;EOqvXbcGmvv_x>IVj75$e{U4+=8u zeQ2h__c2J$gr|DGITvs`6=o+t=3Kq)xUoT|oZ&=5+=BM6Ci zQ;=Jrtb>y1Y7D`G2Fj=z)j)GwnOR+s7bC>Ta6tX$f~3SHuf~^R2EvSsU0g*wQx|O_1r9tU~LC>A# zk-=cVCNLY30=f*+JDD`BYzD2$^`JNvwSrY-(#Il+wvzZz`tTXZ(s8)O6=F6LpdhFO zjgCN|bUoX|qdp;=w~(OCD!Z7FHlzUfN!_TFE)fc4nHkWJH3vWGL7m8F0Fc8@?!3(q zS4Z5&J>Bv*Gy?Jm)DfE}e-|nRUu~%f-vEYF&X5MTa1K?N0fESYR?(wONowrU$Ry68 z{t$NO@~AV)2C!Llj$o(a{qI*|=U~SutINbZy=k$X!e}ZOj4=(Xk@!<#dJ3p^LZBQj zp0z;A8)*SoV%Wg^KfKWp41=md7gpeobP;Z)hBk^4>aVjXI}z(aoq-ME!((iUs7nRz z`H{c6YKm+oIcmpFlnQ1fU>uJ=rh#(g$EO>flna$H7&*x!ayd~nRHq;X{dv{dqRL>L z^YZelQs#^W<-HR{l-qxm05Z^4j1194k(}ohKEoC+x!W~R)QpU5M)fwjJ!AeM0|y+) zG&FQEjC`q68eJG;{qT9P@*&+u}WIp`^Ob%lj86;*IMI!}AodLlsB1>%g(H9KGhOI}^ zpc$;(4*6|~LxUPddV2m;7jZn8XrW~8(Ek7s6~P0RC>F7*=|LL>2T%m(RY3cA0a8{p zd6$_{jSKtD0H8pm=fDo(hV9OQ_7R4Oq-gZI_;mBY^PtT5CJ5Oi9TPY07W5b z0F#@55m-=ZwSY}HR&t8|^R5U4P<42x3X&)h!AC+o0~$}pq^qYo=@i03Nv1=)YTq7} zQJUIm5Un&g}$gLq}blmmnLBx_tUpjWc8bfjdx&w{^rP zF2whGl;G;}^`KZD7V=2K-mFKtOY*l{8S0<)n8CjF`d_^#*B+X$bcgjRbAK&9jF%#yD>#a@iuWA ze7evg<|!IbTOjGipmrOI1BN*Jk|tC-ml@TD&S)$vi+>3j0|GXnSMc&#)T=skL$IKR z77Lah7fnNb&gXy9fm7wA_km{oC#S6fuGAiE;~dX1RsbL>Gq6%RW35nZSjFB2Us2^j zmGBD{B}Y@|L{VHfiE=*(AFT=k1LZ;AJlmyRp$-DS!4fF_%dr2h4J* zXgQD#kT3_Cs2%rb0b9ESILHgmigm0joJPkWW%ZAD&ZVS{(5VrFqeOYgAbaMbh(^rE z!Wqfbpd4bL(=1u#FxD_#m~Eb;dTn&j7f*piiUtg7rzNx2pk=#CnTZrlEr_rbLbWa`J3>Q)p)hw(pi( z!6=Dh0}(&s&ebZw+!=%7Fxeg%Bu3c>{j))yMgf3fswOzYY*^490?v16-H_uOg`lfz zg^EoK3&cn992_zDepCcYK8A}74}~L@arK~N8lzV#_0qv_#V>5;k`y8KA`)s>Ox~+E9?KvGlHh934LR$=gr* z&;`MSAW{l~ySg3Q)7PZ}5nsdr&R05dgHS%+W-eo1e9o?v1nfW|iA)iKI@K2xX*7%p z&Nl!Y5kRRXe)ZKEl!gTB+a8oaY0;Df&i3_6hfsfMJa_ zg=WdY)Z?e`dbDg;Xt(be&2Mn+~)KY`TM|K1);tv6C5aNIqw!0_2PkHaO-n)`FfW z#j#Mhmrx_nP$huDzCop!&CYue83?Tp!J%KR#9(YDsDG;$4lMt9UXikn?77yxwC ztT)1_+*50&qiW)8BLk0l!Q52XWu>Qcb>BRyV*~>mem|W}t4wUD)j)Y~fR&m`pUjoS|+- z&Og?aQ%aLus+KDwj4W(62iBmG!6$awJGjBxS%&_*Q)0@QtxFxr4*!N!sM z#R6INvDEAmGrqC!eCQa>-NtmF$s1`tlos$gQu>1g{_(nzK*%OC1zA|29uTZIpeZ9* zW>Jijv2bz+@A*(_wYIa8ER_sF!D4Zm4?fI}GDAs|?`H=U&}p|qu_G`9fwCNP^34F1 z5lF7-rMKMfqxZc)r_~`cu`U9UoZ$Q_gR%&bfc_%U^723TpaoSkF)|UA$BS^Hf|oK& zBYWDDIaB-d0)f&*Q~(aPb=9SZQTsIlWsgDM7jGN1ui+9gI=0&s4Y-)D z{o!mM#xtkVh*E%95rzGi^g4wKp&p~d=wEY1MdyA{_-daWKB9%%I}8OIh^$R&}W=#8)ZN{DC_Z{WN4>RY%v2F z2Lya8K^_bZI?+KO1|Ifkno^-)Nn9}G2Pzsbx+5wEbJm7{S4P|5Yy)+xvr1^+NG*WF zZd|Hu7aUs_BP)$3)|(5GB_&4VKZg_sZM+?Ibt8}z2L-rb8MDZY9%mHV=^g@KKprOK zr;zy2(CwnZ(TzHW>^e}>pthQzKw>3Wkf)Jq%7I`hb%r=NaV8NW!`}!XOxJs4m(i8nmlMR2;icIrI3`7_Q??ERf1WAMaj8Brlj9DuXj> zwP2{}7{_VoKa-ivBq^95Ys0`^?CWS)<2T^G{`G9BuN>U<3dJwSg zmNTE3pb23*zl1}u-I>QvAwUttL@dl26hoC#avwX>VsRU3EreGlF)KC>sE@cjkFNg! zodhyPAZAGwqm2Im5J)gie7}8Y6lYs?9sDd9u~g425zc{;MolxT!x9ZBatT*3TUKBajfQF4j61Oe}7s5 zWjZoL1s?M+Q$R76U`f&dI2wV@C^Bk7GRRDY_bJG8{rsz9A;uYwtTr-vnxf}&)~d}Y zj4L)HVVwT(Dh#o!G|S#(WReQArb^*sQx3*82VJI%@{2jXTrzuW1sI8 zMu>))rUnR6i{f?SQa{pgPeF*_nN3O@q}gpuv2$a zx&7=Y6(Sb^;Yq>7OmIo_`kDqq6i};R$dfYeap7N|(txgCR3}O)3^E=wxjX*=QS_h( z8r7OyH;2N9r#xE(mBIICs;)05RzThd0u93S?1PQ5*7rZCkQ}{;pb{rw@qde3RmOTDd26^KoDem)~ z#s@#%D(qKu-ZHxrxhy@}Yz&b1VfB)${{ZG`u=5BO=)?lq454|b_5A6ucTxAcoGJpN zaz+I<0w@!^_gXa0+k4-=>TEGB%d-Nyo0^6EBAX40*~2!5SrvC;`9;JaMo|i9RH@H?n4rmf0z$EPgNGPs%I;gC{KW>FyLO)6EJ`zE63dW) zV-y*|jKh$+lniS_yAk^o1foWRzkup5;V8r$ZhCX+`OsaXAlI!K30yOC&fi};2|Af@ zb~(n44T3SxGzV^3bwms2-Yq8M;h*0s0hU3iwjC94z$w_CzrRWfxRFbO_bZa1?z9|X z{pPP44T_&X-u1W`tj{3?2RgHmebH1@vB^8Kqca2~l1ASz-%5yFYE)pOpwuzHKi+Dq zdWJd5Y^NX~*c0dVA6f}?t=%eyUC%S+zTcfbg0&c+jdijqlL1dNp1;332qX|@d=g8-!2AgvnOB%(fIn)9XpU>nf46!hUkl=|~NM{FO`^5$s6p2D% zN^ThSBlakPf?R2cHiaWdM*Kt9(_!_hF7FW)Q*ZoMCqYBu9#s^unK}j~Y@Kp)`h5*i z5t$}TxGHj@HtYVI3L=6@1jt!2E}{toA#whqf=5Sz#q6%pteP@e}3wQ){7t-Wc*(8C0srjE;QI(uRR0yvq)kLy?_Q4nvpv z)iiCiTCCzSDwtfA$@iFn=TeX|!~&pX0ndacc3+JR0iq3RST5w1l1RlZg zGK4Ax!xQWEsk2Jq(K25uOuDs#rZP^H7~AqRqg9QlQ9SYy8ln|uLxRVs{KZX+x`|}9 zhC!vy3JCLuC)esItk&8Ls5R)4L5J1ukDt=6$42HGH;8viSu~LHt^p^~pig$q-FQQ+ zz=&dfut!t!pld8~;y)Y`N;2|eA$s6`RRJQXlTt>n3#I`FcWzlCftD^|n?PrICmL2M z1_XQc`3i$TmKdfJM1@yIndJv6PC94hSLhEAmN~7W@Q06*0C!?M#URflo#zpZNX{iY z6p(mV%k&ij*|bQ@8n%{S9I@}LgPp%B21x`LW+@Cs&c#u-XFP^Q2AietP=#j>ajb4t zG1KxC8Eqy-GJ*=k%e%+Ti0Sp9&uZYylcr66um=yuq zv~x%yaIbB-2)hsol>p)j0T&vx( z%_NcJD-9)jpXWg7R^jbc6uJf)*s;_Yj$_h;HS`e6A(?QB>#Y_)KC}mCj1^a1ZR23W z-SnVj!AJ37jnz=+3%Gg=(<{#U#-8(-3$ajtVuL~h-7-9M~a=jNJ8W^EZk8h|x&1DO@EqSZ_Vq&^ZCI<(}I;J00IX@%euCpj${;j-av+ z!ZVC*KrCJ6)2)eC1df1hoKzc`LPo_S5dv2j=6`$C67|?DU__;ckbAIlbG}Ui?WB>5 z2&L3f^6bE5e)4D&I|$bSo-odIuU+|mr|VP%;2BSO33(XgEPRk1_dg1ONbRUZST-aD znTNZ}Kdn?ttgXRC1|$-LsD3Zr^iu&W?ClQ9z<77ZE`M66KvtB=B09E*$tP@Q8xO5P z9t9c0G+;PksApDr@AEZ7A-53+d1DVC@r6^exOLWE#L z$A|v_Hh=F;hC+q)G?wc4V9TWANcH;BPMG*+&_lXz?85;;#&PpB3W5+-dKBpO-dRxHhtxg`1Lmrd(4ZCz=_h7%>MqgF}4I@cvZJiZ&& znLXi1<6h<#RgP6Ls_&?gxg)PJQBC7$@ghKo9&Jk|pmCrbznvmhTX>0i83|YycJIas zK7{;vQ(^KAU;2nz706^rx6XIT`icd`Z{G1aB99X?olN=0f%`O3n@eoSvC6&FQtLh9 z`{3=iel*x++<N(18s@N#86bi=kE$oiCIZ5!)C$q$DyDZflG9D)g8LN zsQbkSya)CwAft~OjP_D2&8493PC|}(-{LBPxpN%7#iZgy$nT_(?Y`KepLO3s2YH5y zC>dJ`$@A2Yr9^gEic*4WlG8GQ+GhYUcUrnh9jm zeck3*afZr9hdIge`cOL3&17kw86%ce-^I3y2goB!P*Kckju_x)S;qME+JOrkf^g<7 zQZk2(A;3UJLB#`YPJ#D#lsVQ5Je;t4P&MRfA(9yrxkwJU)D4^;^u+=#lIK`3qE9Ne zrV4jO7#kmf6c4;~jyBNbAR)hqG8%&Tjl?qW)^~w0-r~j0K^*@8N(|`2QY|*R&HIRP{ z_{lrAel!A;wWYvDGN^DufHE|mzpVzFt7wEeNfLr`?x)0~#^%bB~Ie7|(E8y{DWDU;M zQlE0+B4k7;0vxg&7w7(hqOsg}&KJC0O2c4rl6U^KL$TmU;6}^rHEtI)*sF-sQ7s;j z6Aa3CbCdl*`A{%h86`scU0HGwHazzv{OS&blEV#~6kHPc3dbsgf8{}j?qKfjPj=2V zGmb|Gr{zJGd1PrE02~wH18+?JQ~@xOHEg*p%WBTn9uU5v6^tKB}OcZjFw(ZdH(=PsZA=z2gp`Z zGO7j(W0A+iiU%w%ZP+ZK8bPI0^HEKOO`SpnF34^71auD@b#D zZSfpU?-B_)J$j7MPT@)-)gLLPj}kMV3W3lcn59@7`)hlPxT8mQi@GY(DffJw%NeQitW5Rs6BkSECEfGr}B#uvNr71K1aoT(hb`S}Au zQX6A3Mj;W#K}3w76E~SB<5X&qRb`F)va=P?TRDu5p9+G{btFB=O;LbWSybSYoa6MM zO}l~r0G6LZsZktpINhRps;xU$qgrY!_vY!esPlnwnCF7}hl1P=U!xbZw6Z;~H zO5UZssKtUvz^rO=R1QP;=|E+jWHO>JblFLaZGsO!=021QxYKQY1hU9hECpEKuG@aU zl?BHFA(BUkg85c;tL@NFaMG+&ISvMp8+;6me<}l+-6U{O zlu_?QbEtvKAHH-0AC07)CRsb-g^7j;_5JzKE>KrnDP(8yJ98em{q>-}@#O)>azPs$ zbD-u$5)}ttwI?N)Zk|=4p)@381!N^j(0Sxz`Jc*xdqpI9)FqVx!N~*5^)v~&nTSsa zY>)2;K9mifRfmN~3MOCP@n=2CE3;zFTV6p~}BMOW&rKDx&3GcLh?zd4Zu33Q-C>tEYLFuVp+_AqrnGI z-MJiZ@Sv7!C-9l%RFO`UiHC>h^A$zgxzibF$zl9CWl_r;e<}sXKYSBQ0|a>)$BDf@ zgY&2zZ*M1s)RmElMTz}-pQQx|CYJUPi>Rq!&YYBk{*6J{TU9X!SoI7RT z?=$1iO!;mo8!1GH%ymVF4HSOzjPoZSEYLQ}Ik4p!CScg_(_*9J&&q)-JW#NRTnC01 zB|{D0K1bIB=~Y)ObL%&Zts1M78ZLC{Io|`|wFBjc?!r0J6=yPrHyIj&k$?xT!ib=+ z_qlE|(7_7^#Eb6&cE);gpjQ%K3%HL|h~L98a$Jsorrwl+c9ypgcx90!LPAS2xX2v> zEatD!G-YFJM zDr9DuV<3^0&+il%?TCemA>oKOh9%FBBRM1CiXiQx21!ieCXvT`8z(r_e0@lwrP&g9 zme9`08;wQWWHBCD$LvsOyOhafvPo=XQKg|+TRQzs5WDDIAs!@><8^`frY z-aC-#6ps?B_l(5*-ha-5O{U^b`K@)uoW&exnDqjPwY(%;M-xZiPlxfZ_gEX_$mH+v zqN$nTNu&kfj!ZtaITAN4ZSiUVx>;ODbvmT#>-;57X}`*G^7^Jm~Le8?|Q#0-X z0OWjm&|3t1QUq9q2x3tC=aK$&2Ja-6u1+S99a>yt3%-Ay0Py*hi^5&wS6xiQmZ9W+ zv?FEAX(gN&C1Y?$gHFwmPUGqMQ2G}TRFXD5@H4sxCPA>qay~Q(W`wS{Rz+pY6* zCcF%MLs<;tm>DDMk(#;)yHDed1=cM@4Qqn@!2GwU9|Zl-NW@MDdK*B_IUUYEBkNRb zzekq#H2^tZ6EY0x&-DIP0_TyQV6jQ9HF))NVZPa*sbqm!8N0H7O$ ziaS^jbY5{O4I-V;0D63J^q|LZrKBwZIuwxdK6Ac60)VE8Qq3QDqcW8UI_Eo|mMADA zt0;6=b!~!?ga^;+1p{MYqq>4X1aFxneeieuykC_BbVq^X1)_~uF<&$LGz(5G7sd3k(39x4m7wukRC7m?tK7u6Cj=0Zr{Z;7a`%1ff(!V!oe z+-Hy{IH);)c_WBQizZ$o8H|g_U?1y2B}8L7=_wH^b%VJe`g|xV<6LpOG-)vi8aUKr z0CoBUK##r&F6Kmb9qNr;^5;&UBmV%zP}@}6qYL2?$0`v0*W>cX#{!4a@hsiU$N0?Y zrBM_HAx1KOLW&5`y1XqZiy*r@41s~m{*gp32W5g#k-Sl=s(26g*xU60ilNim%Ns1R zqRQ_PbWHAmbtB-TYJjAeg>{9BJ0?!C;W!y1e}~eFyEI#i12xJrTf_?>L;c|%L*+yb z%(6I;s1q??;X^LPSEfhEeQ2!L-S^y@OL)!{>pYOi*iv z?>)=u1~}D}40xTre!o0W0D+4^c_XSxI~e1QjzW%DKAv9+3cZQ}b8i$;0UU%Z_Xya@ zJCX3^nh7KeY2HY(mRJc$p>@G+wmJFG1ZgLBuFWD`tA%icl~wp-Y;Qo@$>Mn9iZxlI zSr2_7*pbv=eDrxFpIm6EUZvSzWV1R6ZPMv1>IX1?{XEQl2UT<394&Z&ncMcM6@Tq6cJU&zy zd={6FBywnlNGgrEaC!cpod$@ODI;Q4#OO|e8+)VopREI-xQn?A^smv z&VYD~dL-7W>7Nn>-Aa+Z-(Rf&M5V*StzWx1X3hx*_I|V(%nuTYBy$)>!Xu1^8=U^M z4j>=`e-R@pV{8mi(*i^lRL80qvU8kd<29gi%L#o(HcY>PLCy!S%k`jJX`0{$le}Rj zd?#h|AEgGC2&%#s9c;K-;Y)_XjQ9Dqv**%$xdrO#sfKXoxVZx9oJr3%q>R2Z#FYG~zQG=SiN%NZa0O#ZAo0j~;Qc=%L~gOsF9?$8H+0a;v?DmjB6ju8XsjGY+(@%Zq=${j z)SK<(I_wwcnj<#~X#W7Sn=!X>6qxSj{B6@Y^T41BhIP_pjnd&?5cfN(06P6e1D0r` zxCD213QzH58xRiIAC(lT6(hHb;7icE@}PGR zo>}jjZ80>mjcSKrgBj23KoggO+2mPFdbgE`$aB1cqH!-EMcBi8CKh&pkDYCs>uH4(%kXffR!(I(>X2>qREgSlZ0Tohb2JlXXmDfXAYEiiX;m0 zMht5c3>P@YbM*MpS+ArP_x;vnjidu_cn$piLWn!y*W5W8WnSnVI?&@J5A|9u*-?p5 zF_uiK$1hMY0Vny<1z_0Irro2FjviGx7$kmNsI3;=?g&EyPLHwu+&SrR!_;d!uj;ERD{V0J+4}IQi1|oEb+nGHN<|s8pv8tHKXyrKo z?obcjC=)o>5;S^VJ%ojJZ2s{@8Z%s5t|ZALMu`^%PfY$)LZh9QHD9@`q+}M`&VwZK zj_{(qYyyF{+IAVO3z{2nV-OZmkkT_8>p7q+-sU(9G-Yz(kyEJsf@l`a-h03>QQ?pL zBOLSa6a{F}$2-U*(AWiwf^o6fe6c`E6tt1$gg7L+fD4`SG4cX~r)vx=-L&c+HbdRU zHfHC&2QKDEc96#|UDsuOz})OUU#$bUO}U!%+oP;O&x%ao0(1Ftpswwtn8*ltg6cp4 z1<20lGz4LPy~!QC<>e`ot?s7B^q@H{g4)gAIE+jPl2zr`^5iH1EmBBhBnOD8(!YtZ zk@}D*8wpof(O8*^l~|i4SIBMu0Jxu(274nc_pDGBbRWjJ&dN3)rm6HeOiA(A4&zhEDVn zNnny3f=Fg3AEuwqpxWW%OIuhZ(ICkYoz##={{Zv(nkq|hZ6b~|W8tErM1b>eMI?hmNI`icwU&u!mG+GV$nS6NasxsbL;mo32~)`4b8uP}k3U1T5Y8to(?x9*VW9frcmR=?dAEJ2ik*g3yB%xaKR&h@=@vOoc$;U@B7G;5NRVHOE zt^WWC<&FM4y(p#IhSM}`l12drFo!uOuj%xn83n}eG?B25N0Y=r*r@CK^q@-xMUh~U zp@~Q>k(?8^@8Lx?<7 zhK(eN6?|F_Lmo%@(FGuvx$hh#!c`241{rt%0KHMErBVQjP=(MuhC#7CKtD6|s26zX z6qb)nKwMRrb@uRuNmgA_p~l31cmQ3&&` zM(vM>(tv^=7Uo_Op%mKxnC5Z`?({R zz-EJ;BFEjSLzs@tFm;|sA45SnO30F<9IUJ{94CW)`&Z3ofhqstg;s*xg_Yx8w`K$#YIba;*hkLEbv4yLC?HS2mb)Es2ssmPc_tG z`|zeDglvj`p7a9V99`RG3WILfUS&CxwzNfOC%>xcy@@4<0sQi22T1LXSrC{yGddqNtFBN zpNBm+S|ZV;oej*PKIbZxLk%NE`%{Ln9?@KfNXMoH$3R9&?-H) zBPj@x4|z6_b|ZWbodY<<%$Gb4)54M~F<7J6G@UYk&-qam9;LZMBqTd0b|YhyUoZav zDk=ObMS?hHD4|)~zn(TAoDsO{IiIBjlkWHDRPgJyj%^B|aQQjFIrVAq|&8RiMcRZ7%Q$4g$oU=IELUtcqocuFE z9gxfXNY%u6bV25DbL+4>&?fC}<&TzcHiIz53K_m?&Y9t zYKc^t9Z!l<4&Tawk>&V|NhFAS-wbfb20`RA`A}kIme-0KV5S)+Qxu!{M&r+yPlW@m z<&s-atoq@%C0qfy&Q3l%kB%ra!EniVj!8qF>_p>XwlaM;KT094#@cyf3#+|+ODunc zXZ=soicb)dOIL-KK_$A$kX>-zdz0}v_)r?Ww$h7?yiVbe0k{D3$nxCK3_4!uh5)p0 z{8vNGPwl?6M)<6)9?hfHRik_}_u>qREkSjg^sSauB_rq-YgjrsolXocLPq&?I#B&?*CImSlA_wk@`HxPI& zrIaT;5uNBWUrO=VNiwn2Ur)ee{cAyxOzeTO131GGvG=t8lo>DQmP^R&or^=ig!SHo zO@T1M-^a5E!2_otd3RMh?t3 zJN+tEjw8xsQ!5449K{!HfWv=5MGS0LQ@4!kDhm!k>hh|{xQh23>=82}2Q7ekkUIYW zn9xohk=;Dq@+lFqD!9P+lz%ftIb>yy=IDU(CXl+f3*u3?$W*8;S&E)%BaMvhccvBDSuwn)!EprHpeuO!kV5K;(OB4A)(Ha|f`Ul3JZ9Dzm*kqowX z_ndA1QAWOja^_1Wgn$*}1duRE@$Xd+w6{jEM=Zy{>x}JytU&tIK8+v?7nMeqSjvof zWDn4qidDE#C7rt{bY&rnb338`0QNOeo-26ezlt^lM*;>^2f}irIsX7CqOS1wwldrm z3nVT4GQWpne^4kdj4`p`){sI0S>!s)?r?Lrpx9D-Po}OTZ5m#*QRXy*R+UqLa9f|B zDkvw4Ir})5ZlFtVdm=`_d!R~@<2B_xJpbP*AkpBQ7LNcQ?7E0>u%=37Q9O}v6^)ylO7{sPX8Qo9> zZwq52gxvoCn9yNK-dN_=7YoP%u-$0NaeT$YR&`^nk6iu{$gzugHbV^h+yW9!L0zo!_!xGv&1g zs9}zCIx^r}7BT7DmPq|5D9XY|yCGgsQw+m6$~6J;RV#5MYRnhKC!YEB=|k<3(e>Y5 zp;gcTA$oh*{&Yj7V;z+95F~aW>T#03Bg@M2T}Gn7k}i zlS=GYr~?P%MPuTly0Mx>_nfA4zweb0HYe{79kienz{wjEfPbK&1w29pmPI9uDphrE zFb3KB3N358w~PBbzD3Z|LKu}ikL(H>A_)u2ZyPF+f~4bfja>eMgGI{sTs^K4j-&-Z nCv0!FK0<>vxLKl%x{RG!Bd!Vlf{0!1)bYruk`MxnZ$barYj!tX literal 0 HcmV?d00001 diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 9cc39635..e194ee7e 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -3,12 +3,14 @@ set(SOURCES "audio_codecs/audio_codec.cc" "audio_codecs/box_audio_codec.cc" "audio_codecs/es8311_audio_codec.cc" "audio_codecs/cores3_audio_codec.cc" + "audio_codecs/tcircles3_audio_codec.cc" "led/single_led.cc" "led/circular_strip.cc" "display/display.cc" "display/no_display.cc" "display/lcd_display.cc" "display/ssd1306_display.cc" + "boards/lilygo-t-circle-s3/esp_lcd_gc9d01n.c" "protocols/protocol.cc" "protocols/mqtt_protocol.cc" "protocols/websocket_protocol.cc" @@ -68,6 +70,8 @@ elseif(CONFIG_BOARD_TYPE_ESP32S3_Touch_AMOLED_1_8) set(BOARD_TYPE "esp32-s3-touch-amoled-1.8") elseif(CONFIG_BOARD_TYPE_BREAD_COMPACT_WIFI_LCD) set(BOARD_TYPE "bread-compact-wifi-lcd") +elseif(CONFIG_BOARD_TYPE_LILYGO_T_CIRCLE_S3) + set(BOARD_TYPE "lilygo-t-circle-s3") endif() file(GLOB BOARD_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/boards/${BOARD_TYPE}/*.cc) list(APPEND SOURCES ${BOARD_SOURCES}) diff --git a/main/Kconfig.projbuild b/main/Kconfig.projbuild index 3b5bdfc7..71b84679 100644 --- a/main/Kconfig.projbuild +++ b/main/Kconfig.projbuild @@ -70,6 +70,8 @@ choice BOARD_TYPE bool "ESP-SparkBot开发板" config BOARD_TYPE_ESP32S3_Touch_AMOLED_1_8 bool "Waveshare ESP32-S3-Touch-AMOLED-1.8" + config BOARD_TYPE_LILYGO_T_CIRCLE_S3 + bool "LILYGO T-Circle-S3" endchoice choice DISPLAY_LCD_TYPE diff --git a/main/audio_codecs/tcircles3_audio_codec.cc b/main/audio_codecs/tcircles3_audio_codec.cc new file mode 100644 index 00000000..32ce43f4 --- /dev/null +++ b/main/audio_codecs/tcircles3_audio_codec.cc @@ -0,0 +1,140 @@ +#include "tcircles3_audio_codec.h" + +#include +#include +#include +#include +#include + +static const char TAG[] = "Tcircles3AudioCodec"; + +Tcircles3AudioCodec::Tcircles3AudioCodec(int input_sample_rate, int output_sample_rate, + gpio_num_t mic_bclk, gpio_num_t mic_ws, gpio_num_t mic_data, + gpio_num_t spkr_bclk, gpio_num_t spkr_lrclk, gpio_num_t spkr_data, + bool input_reference){ + duplex_ = true; // 是否双工 + input_reference_ = input_reference; // 是否使用参考输入,实现回声消除 + input_channels_ = input_reference_ ? 2 : 1; // 输入通道数 + input_sample_rate_ = input_sample_rate; + output_sample_rate_ = output_sample_rate; + + CreateVoiceHardware(mic_bclk, mic_ws, mic_data, spkr_bclk, spkr_lrclk, spkr_data); + + gpio_config_t config; + config.pin_bit_mask = BIT64(GPIO_NUM_45); + config.mode = GPIO_MODE_OUTPUT; + config.pull_up_en = GPIO_PULLUP_DISABLE; + config.pull_down_en = GPIO_PULLDOWN_ENABLE; + config.intr_type = GPIO_INTR_DISABLE; +#if SOC_GPIO_SUPPORT_PIN_HYS_FILTER + config.hys_ctrl_mode = GPIO_HYS_SOFT_ENABLE; +#endif + gpio_config(&config); + gpio_set_level(GPIO_NUM_45, 0); + ESP_LOGI(TAG, "Tcircles3AudioCodec initialized"); +} + +Tcircles3AudioCodec::~Tcircles3AudioCodec(){ + audio_codec_delete_codec_if(in_codec_if_); + audio_codec_delete_ctrl_if(in_ctrl_if_); + audio_codec_delete_codec_if(out_codec_if_); + audio_codec_delete_ctrl_if(out_ctrl_if_); + audio_codec_delete_gpio_if(gpio_if_); + audio_codec_delete_data_if(data_if_); +} + +void Tcircles3AudioCodec::CreateVoiceHardware(gpio_num_t mic_bclk, gpio_num_t mic_ws, gpio_num_t mic_data, + gpio_num_t spkr_bclk, gpio_num_t spkr_lrclk, gpio_num_t spkr_data){ + + i2s_chan_config_t mic_chan_config = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_0, I2S_ROLE_MASTER); + mic_chan_config.auto_clear = true; // Auto clear the legacy data in the DMA buffer + i2s_chan_config_t spkr_chan_config = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_1, I2S_ROLE_MASTER); + spkr_chan_config.auto_clear = true; // Auto clear the legacy data in the DMA buffer + + ESP_ERROR_CHECK(i2s_new_channel(&mic_chan_config, NULL, &rx_handle_)); + ESP_ERROR_CHECK(i2s_new_channel(&spkr_chan_config, &tx_handle_, NULL)); + + i2s_std_config_t mic_config ={ + .clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(static_cast(input_sample_rate_)), + .slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO), + .gpio_cfg ={ + .mclk = I2S_GPIO_UNUSED, + .bclk = mic_bclk, + .ws = mic_ws, + .dout = I2S_GPIO_UNUSED, + .din = mic_data, + .invert_flags ={ + .mclk_inv = false, + .bclk_inv = false, + .ws_inv = false, + } + } + }; + + i2s_std_config_t spkr_config ={ + .clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(static_cast(11025)), + .slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO), + .gpio_cfg ={ + .mclk = I2S_GPIO_UNUSED, + .bclk = spkr_bclk, + .ws = spkr_lrclk, + .dout = spkr_data, + .din = I2S_GPIO_UNUSED, + .invert_flags ={ + .mclk_inv = false, + .bclk_inv = false, + .ws_inv = false + } + } + }; + + ESP_ERROR_CHECK(i2s_channel_init_std_mode(rx_handle_, &mic_config)); + ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle_, &spkr_config)); + ESP_LOGI(TAG, "Voice hardware created"); +} + +void Tcircles3AudioCodec::SetOutputVolume(int volume){ + volume_ = volume; + AudioCodec::SetOutputVolume(volume); +} + +void Tcircles3AudioCodec::EnableInput(bool enable){ + if (enable){ + }else{ + } + AudioCodec::EnableInput(enable); +} + +void Tcircles3AudioCodec::EnableOutput(bool enable){ + if (enable){ + gpio_set_level(GPIO_NUM_45, 1); + }else{ + gpio_set_level(GPIO_NUM_45, 0); + } + AudioCodec::EnableOutput(enable); +} + +int Tcircles3AudioCodec::Read(int16_t *dest, int samples){ + if (input_enabled_){ + size_t bytes_read; + i2s_channel_read(rx_handle_, dest, samples * sizeof(int16_t), &bytes_read, portMAX_DELAY); + } + return samples; +} + +void AdjustVolume(const int16_t *input_data, int16_t *output_data, size_t samples, float volume){ + for (size_t i = 0; i < samples; i++){ + output_data[i] = (float)input_data[i] * volume; + } +} + +int Tcircles3AudioCodec::Write(const int16_t *data, int samples){ + if (output_enabled_){ + size_t bytes_read; + auto output_data = (int16_t *)malloc(samples * sizeof(int16_t)); + AdjustVolume(data, output_data, samples, (float)(volume_ / 100.0)); + i2s_channel_write(tx_handle_, output_data, samples * sizeof(int16_t), &bytes_read, portMAX_DELAY); + free(output_data); + } + return samples; +} diff --git a/main/audio_codecs/tcircles3_audio_codec.h b/main/audio_codecs/tcircles3_audio_codec.h new file mode 100644 index 00000000..3db28e71 --- /dev/null +++ b/main/audio_codecs/tcircles3_audio_codec.h @@ -0,0 +1,38 @@ +#ifndef _TCIRCLES3_AUDIO_CODEC_H +#define _TCIRCLES3_AUDIO_CODEC_H + +#include "audio_codec.h" + +#include +#include + +class Tcircles3AudioCodec : public AudioCodec +{ +private: + const audio_codec_data_if_t *data_if_ = nullptr; + const audio_codec_ctrl_if_t *out_ctrl_if_ = nullptr; + const audio_codec_if_t *out_codec_if_ = nullptr; + const audio_codec_ctrl_if_t *in_ctrl_if_ = nullptr; + const audio_codec_if_t *in_codec_if_ = nullptr; + const audio_codec_gpio_if_t *gpio_if_ = nullptr; + + uint32_t volume_ = 70; + + void CreateVoiceHardware(gpio_num_t mic_bclk, gpio_num_t mic_ws, gpio_num_t mic_data,gpio_num_t spkr_bclk, gpio_num_t spkr_lrclk, gpio_num_t spkr_data); + + virtual int Read(int16_t *dest, int samples) override; + virtual int Write(const int16_t *data, int samples) override; + +public: + Tcircles3AudioCodec(int input_sample_rate, int output_sample_rate, + gpio_num_t mic_bclk, gpio_num_t mic_ws, gpio_num_t mic_data, + gpio_num_t spkr_bclk, gpio_num_t spkr_lrclk, gpio_num_t spkr_data, + bool input_reference); + virtual ~Tcircles3AudioCodec(); + + virtual void SetOutputVolume(int volume) override; + virtual void EnableInput(bool enable) override; + virtual void EnableOutput(bool enable) override; +}; + +#endif // _BOX_AUDIO_CODEC_H diff --git a/main/boards/lilygo-t-circle-s3/config.h b/main/boards/lilygo-t-circle-s3/config.h new file mode 100644 index 00000000..e115c77a --- /dev/null +++ b/main/boards/lilygo-t-circle-s3/config.h @@ -0,0 +1,48 @@ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +// M5Stack CoreS3 Board configuration + +#include +#include "pin_config.h" + +#define AUDIO_INPUT_REFERENCE true +#define AUDIO_INPUT_SAMPLE_RATE 24000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 + +#define AUDIO_MIC_I2S_GPIO_BCLK static_cast(MSM261_BCLK) +#define AUDIO_MIC_I2S_GPIO_WS static_cast(MSM261_WS) +#define AUDIO_MIC_I2S_GPIO_DATA static_cast(MSM261_DATA) + +#define AUDIO_SPKR_I2S_GPIO_BCLK static_cast(MAX98357A_BCLK) +#define AUDIO_SPKR_I2S_GPIO_LRCLK static_cast(MAX98357A_LRCLK) +#define AUDIO_SPKR_I2S_GPIO_DATA static_cast(MAX98357A_DATA) +#define AUDIO_SPKR_ENABLE static_cast(MAX98357A_SD_MODE) + +#define TOUCH_I2C_SDA_PIN static_cast(TP_SDA) +#define TOUCH_I2C_SCL_PIN static_cast(TP_SCL) + +#define BUILTIN_LED_GPIO GPIO_NUM_NC +#define BOOT_BUTTON_GPIO GPIO_NUM_0 +#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC +#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC + +#define DISPLAY_WIDTH LCD_WIDTH +#define DISPLAY_HEIGHT LCD_HEIGHT +#define DISPLAY_MOSI LCD_MOSI +#define DISPLAY_SCLK LCD_SCLK +#define DISPLAY_DC LCD_DC +#define DISPLAY_RST LCD_RST +#define DISPLAY_CS LCD_CS +#define DISPLAY_BL static_cast(LCD_BL) +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY false + +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 + +#define DISPLAY_BACKLIGHT_PIN DISPLAY_BL +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false + +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/lilygo-t-circle-s3/esp_lcd_gc9d01n.c b/main/boards/lilygo-t-circle-s3/esp_lcd_gc9d01n.c new file mode 100644 index 00000000..25a78674 --- /dev/null +++ b/main/boards/lilygo-t-circle-s3/esp_lcd_gc9d01n.c @@ -0,0 +1,353 @@ +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_lcd_panel_interface.h" +#include "esp_lcd_panel_io.h" +#include "esp_lcd_panel_vendor.h" +#include "esp_lcd_panel_ops.h" +#include "esp_lcd_panel_commands.h" +#include "driver/gpio.h" +#include "esp_log.h" +#include "esp_check.h" + +#include "esp_lcd_gc9d01n.h" + +static const char *TAG = "gc9d01n"; + +static esp_err_t panel_gc9d01n_del(esp_lcd_panel_t *panel); +static esp_err_t panel_gc9d01n_reset(esp_lcd_panel_t *panel); +static esp_err_t panel_gc9d01n_init(esp_lcd_panel_t *panel); +static esp_err_t panel_gc9d01n_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, const void *color_data); +static esp_err_t panel_gc9d01n_invert_color(esp_lcd_panel_t *panel, bool invert_color_data); +static esp_err_t panel_gc9d01n_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y); +static esp_err_t panel_gc9d01n_swap_xy(esp_lcd_panel_t *panel, bool swap_axes); +static esp_err_t panel_gc9d01n_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap); +static esp_err_t panel_gc9d01n_disp_on_off(esp_lcd_panel_t *panel, bool off); + +typedef struct{ + esp_lcd_panel_t base; + esp_lcd_panel_io_handle_t io; + int reset_gpio_num; + bool reset_level; + int x_gap; + int y_gap; + uint8_t fb_bits_per_pixel; + uint8_t madctl_val; // save current value of LCD_CMD_MADCTL register + uint8_t colmod_val; // save current value of LCD_CMD_COLMOD register + const gc9d01n_lcd_init_cmd_t *init_cmds; + uint16_t init_cmds_size; +} gc9d01n_panel_t; + +esp_err_t esp_lcd_new_panel_gc9d01n(const esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *panel_dev_config, esp_lcd_panel_handle_t *ret_panel){ + esp_err_t ret = ESP_OK; + gc9d01n_panel_t *gc9d01n = NULL; + gpio_config_t io_conf = {0}; + + ESP_GOTO_ON_FALSE(io && panel_dev_config && ret_panel, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument"); + gc9d01n = (gc9d01n_panel_t *)calloc(1, sizeof(gc9d01n_panel_t)); + ESP_GOTO_ON_FALSE(gc9d01n, ESP_ERR_NO_MEM, err, TAG, "no mem for gc9d01n panel"); + + if (panel_dev_config->reset_gpio_num >= 0){ + io_conf.mode = GPIO_MODE_OUTPUT; + io_conf.pin_bit_mask = 1ULL << panel_dev_config->reset_gpio_num; + ESP_GOTO_ON_ERROR(gpio_config(&io_conf), err, TAG, "configure GPIO for RST line failed"); + } + +#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0) + switch (panel_dev_config->color_space){ + case ESP_LCD_COLOR_SPACE_RGB: + gc9d01n->madctl_val = 0; + break; + case ESP_LCD_COLOR_SPACE_BGR: + gc9d01n->madctl_val |= LCD_CMD_BGR_BIT; + break; + default: + ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported color space"); + break; + } +#else + switch (panel_dev_config->rgb_endian){ + case LCD_RGB_ENDIAN_RGB: + gc9d01n->madctl_val = 0; + break; + case LCD_RGB_ENDIAN_BGR: + gc9d01n->madctl_val |= LCD_CMD_BGR_BIT; + break; + default: + ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported rgb endian"); + break; + } +#endif + + switch (panel_dev_config->bits_per_pixel){ + case 16: // RGB565 + gc9d01n->colmod_val = 0x55; + gc9d01n->fb_bits_per_pixel = 16; + break; + case 18: // RGB666 + gc9d01n->colmod_val = 0x66; + // each color component (R/G/B) should occupy the 6 high bits of a byte, which means 3 full bytes are required for a pixel + gc9d01n->fb_bits_per_pixel = 24; + break; + default: + ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported pixel width"); + break; + } + + gc9d01n->io = io; + gc9d01n->reset_gpio_num = panel_dev_config->reset_gpio_num; + gc9d01n->reset_level = panel_dev_config->flags.reset_active_high; + if (panel_dev_config->vendor_config){ + gc9d01n->init_cmds = ((gc9d01n_vendor_config_t *)panel_dev_config->vendor_config)->init_cmds; + gc9d01n->init_cmds_size = ((gc9d01n_vendor_config_t *)panel_dev_config->vendor_config)->init_cmds_size; + } + gc9d01n->base.del = panel_gc9d01n_del; + gc9d01n->base.reset = panel_gc9d01n_reset; + gc9d01n->base.init = panel_gc9d01n_init; + gc9d01n->base.draw_bitmap = panel_gc9d01n_draw_bitmap; + gc9d01n->base.invert_color = panel_gc9d01n_invert_color; + gc9d01n->base.set_gap = panel_gc9d01n_set_gap; + gc9d01n->base.mirror = panel_gc9d01n_mirror; + gc9d01n->base.swap_xy = panel_gc9d01n_swap_xy; +#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0) + gc9d01n->base.disp_off = panel_gc9d01n_disp_on_off; +#else + gc9d01n->base.disp_on_off = panel_gc9d01n_disp_on_off; +#endif + *ret_panel = &(gc9d01n->base); + ESP_LOGD(TAG, "new gc9d01n panel @%p", gc9d01n); + + // ESP_LOGI(TAG, "LCD panel create success, version: %d.%d.%d", ESP_LCD_GC9D01N_VER_MAJOR, ESP_LCD_GC9D01N_VER_MINOR, + // ESP_LCD_GC9D01N_VER_PATCH); + + return ESP_OK; + +err: + if (gc9d01n){ + if (panel_dev_config->reset_gpio_num >= 0){ + gpio_reset_pin(panel_dev_config->reset_gpio_num); + } + free(gc9d01n); + } + return ret; +} + +static esp_err_t panel_gc9d01n_del(esp_lcd_panel_t *panel){ + gc9d01n_panel_t *gc9d01n = __containerof(panel, gc9d01n_panel_t, base); + + if (gc9d01n->reset_gpio_num >= 0){ + gpio_reset_pin(gc9d01n->reset_gpio_num); + } + ESP_LOGD(TAG, "del gc9d01n panel @%p", gc9d01n); + free(gc9d01n); + return ESP_OK; +} + +static esp_err_t panel_gc9d01n_reset(esp_lcd_panel_t *panel){ + gc9d01n_panel_t *gc9d01n = __containerof(panel, gc9d01n_panel_t, base); + esp_lcd_panel_io_handle_t io = gc9d01n->io; + + // perform hardware reset + if (gc9d01n->reset_gpio_num >= 0){ + gpio_set_level(gc9d01n->reset_gpio_num, gc9d01n->reset_level); + vTaskDelay(pdMS_TO_TICKS(10)); + gpio_set_level(gc9d01n->reset_gpio_num, !gc9d01n->reset_level); + vTaskDelay(pdMS_TO_TICKS(10)); + } + else{ // perform software reset + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_SWRESET, NULL, 0), TAG, "send command failed"); + vTaskDelay(pdMS_TO_TICKS(20)); // spec, wait at least 5ms before sending new command + } + + return ESP_OK; +} + +static const gc9d01n_lcd_init_cmd_t vendor_specific_init_default[] = { + // {cmd, { data }, data_size, delay_ms} + // Enable Inter Register + {0xFE, (uint8_t[]){0x00}, 0, 0}, + {0xEF, (uint8_t[]){0x00}, 0, 0}, + {0x80, (uint8_t[]){0xFF}, 1, 0}, + {0x81, (uint8_t[]){0xFF}, 1, 0}, + {0x82, (uint8_t[]){0xFF}, 1, 0}, + {0x84, (uint8_t[]){0xFF}, 1, 0}, + {0x85, (uint8_t[]){0xFF}, 1, 0}, + {0x86, (uint8_t[]){0xFF}, 1, 0}, + {0x87, (uint8_t[]){0xFF}, 1, 0}, + {0x88, (uint8_t[]){0xFF}, 1, 0}, + {0x89, (uint8_t[]){0xFF}, 1, 0}, + {0x8A, (uint8_t[]){0xFF}, 1, 0}, + {0x8B, (uint8_t[]){0xFF}, 1, 0}, + {0x8C, (uint8_t[]){0xFF}, 1, 0}, + {0x8D, (uint8_t[]){0xFF}, 1, 0}, + {0x8E, (uint8_t[]){0xFF}, 1, 0}, + {0x8F, (uint8_t[]){0xFF}, 1, 0}, + {0x3A, (uint8_t[]){0x05}, 1, 0}, + {0xEC, (uint8_t[]){0x01}, 1, 0}, + {0x74, (uint8_t[]){0x02, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00}, 7, 0}, + {0x98, (uint8_t[]){0x3E}, 1, 0}, + {0x99, (uint8_t[]){0x3E}, 1, 0}, + {0xB5, (uint8_t[]){0x0D, 0x0D}, 2, 0}, + {0x60, (uint8_t[]){0x38, 0x0F, 0x79, 0x67}, 4, 0}, + {0x61, (uint8_t[]){0x38, 0x11, 0x79, 0x67}, 4, 0}, + {0x64, (uint8_t[]){0x38, 0x17, 0x71, 0x5F, 0x79, 0x67}, 6, 0}, + {0x65, (uint8_t[]){0x38, 0x13, 0x71, 0x5B, 0x79, 0x67}, 6, 0}, + {0x6A, (uint8_t[]){0x00, 0x00}, 2, 0}, + {0x6C, (uint8_t[]){0x22, 0x02, 0x22, 0x02, 0x22, 0x22, 0x50}, 7, 0}, + {0x6E, (uint8_t[]){0x03, 0x03, 0x01, 0x01, 0x00, 0x00, 0x0F, 0x0F, 0x0D, 0x0D, 0x0B, 0x0B, 0x09, 0x09, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x0A, 0x0C, 0x0C, 0x0E, 0x0E, 0x10, 0x10, 0x00, 0x00, 0x02, 0x02, 0x04, 0x04}, 32, 0}, + {0xBF, (uint8_t[]){0x01}, 1, 0}, + {0xF9, (uint8_t[]){0x40}, 1, 0}, + {0x9B, (uint8_t[]){0x3B, 0x93, 0x33, 0x7F, 0x00}, 5, 0}, + {0x7E, (uint8_t[]){0x30}, 1, 0}, + {0x70, (uint8_t[]){0x0D, 0x02, 0x08, 0x0D, 0x02, 0x08}, 6, 0}, + {0x71, (uint8_t[]){0x0D, 0x02, 0x08}, 3, 0}, + {0x91, (uint8_t[]){0x0E, 0x09}, 2, 0}, + {0xC3, (uint8_t[]){0x19, 0xC4, 0x19, 0xC9, 0x3C}, 5, 0}, + {0xF0, (uint8_t[]){0x53, 0x15, 0x0A, 0x04, 0x00, 0x3E}, 6, 0}, + {0xF1, (uint8_t[]){0x56, 0xA8, 0x7F, 0x33, 0x34, 0x5F}, 6, 0}, + {0xF2, (uint8_t[]){0x53, 0x15, 0x0A, 0x04, 0x00, 0x3A}, 6, 0}, + {0xF3, (uint8_t[]){0x52, 0xA4, 0x7F, 0x33, 0x34, 0xDF}, 6, 0}, + + // {0x20, (uint8_t[]){0x00}, 0, 0}, + {0x36, (uint8_t[]){0x00}, 1, 0}, + {0x11, (uint8_t[]){0x00}, 0, 200}, + {0x29, (uint8_t[]){0x00}, 0, 0}, + {0x2C, (uint8_t[]){0x00}, 0, 20}, +}; + +static esp_err_t panel_gc9d01n_init(esp_lcd_panel_t *panel){ + gc9d01n_panel_t *gc9d01n = __containerof(panel, gc9d01n_panel_t, base); + esp_lcd_panel_io_handle_t io = gc9d01n->io; + + // LCD goes into sleep mode and display will be turned off after power on reset, exit sleep mode first + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_SLPOUT, NULL, 0), TAG, "send command failed"); + vTaskDelay(pdMS_TO_TICKS(100)); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t[]){gc9d01n->madctl_val,},1),TAG, "send command failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_COLMOD, (uint8_t[]){gc9d01n->colmod_val,},1),TAG, "send command failed"); + + const gc9d01n_lcd_init_cmd_t *init_cmds = NULL; + uint16_t init_cmds_size = 0; + if (gc9d01n->init_cmds){ + init_cmds = gc9d01n->init_cmds; + init_cmds_size = gc9d01n->init_cmds_size; + }else{ + init_cmds = vendor_specific_init_default; + init_cmds_size = sizeof(vendor_specific_init_default) / sizeof(gc9d01n_lcd_init_cmd_t); + } + + bool is_cmd_overwritten = false; + for (int i = 0; i < init_cmds_size; i++){ + // Check if the command has been used or conflicts with the internal + switch (init_cmds[i].cmd){ + case LCD_CMD_MADCTL: + is_cmd_overwritten = true; + gc9d01n->madctl_val = ((uint8_t *)init_cmds[i].data)[0]; + break; + case LCD_CMD_COLMOD: + is_cmd_overwritten = true; + gc9d01n->colmod_val = ((uint8_t *)init_cmds[i].data)[0]; + break; + default: + is_cmd_overwritten = false; + break; + } + + if (is_cmd_overwritten){ + ESP_LOGW(TAG, "The %02Xh command has been used and will be overwritten by external initialization sequence", init_cmds[i].cmd); + } + + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, init_cmds[i].cmd, init_cmds[i].data, init_cmds[i].data_bytes), TAG, "send command failed"); + vTaskDelay(pdMS_TO_TICKS(init_cmds[i].delay_ms)); + } + ESP_LOGD(TAG, "send init commands success"); + + return ESP_OK; +} + +static esp_err_t panel_gc9d01n_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, const void *color_data){ + gc9d01n_panel_t *gc9d01n = __containerof(panel, gc9d01n_panel_t, base); + assert((x_start < x_end) && (y_start < y_end) && "start position must be smaller than end position"); + esp_lcd_panel_io_handle_t io = gc9d01n->io; + + x_start += gc9d01n->x_gap; + x_end += gc9d01n->x_gap; + y_start += gc9d01n->y_gap; + y_end += gc9d01n->y_gap; + + // define an area of frame memory where MCU can access + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_CASET, (uint8_t[]){(x_start >> 8) & 0xFF,x_start & 0xFF,((x_end - 1) >> 8) & 0xFF,(x_end - 1) & 0xFF,},4),TAG, "send command failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_RASET, (uint8_t[]){(y_start >> 8) & 0xFF,y_start & 0xFF,((y_end - 1) >> 8) & 0xFF,(y_end - 1) & 0xFF,},4),TAG, "send command failed"); + // transfer frame buffer + size_t len = (x_end - x_start) * (y_end - y_start) * gc9d01n->fb_bits_per_pixel / 8; + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_color(io, LCD_CMD_RAMWR, color_data, len), TAG, "send color failed"); + + return ESP_OK; +} + +static esp_err_t panel_gc9d01n_invert_color(esp_lcd_panel_t *panel, bool invert_color_data){ + gc9d01n_panel_t *gc9d01n = __containerof(panel, gc9d01n_panel_t, base); + esp_lcd_panel_io_handle_t io = gc9d01n->io; + int command = 0; + if (invert_color_data){ + command = LCD_CMD_INVON; + }else{ + command = LCD_CMD_INVOFF; + } + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, command, NULL, 0), TAG, "send command failed"); + return ESP_OK; +} + +static esp_err_t panel_gc9d01n_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y){ + gc9d01n_panel_t *gc9d01n = __containerof(panel, gc9d01n_panel_t, base); + esp_lcd_panel_io_handle_t io = gc9d01n->io; + if (mirror_x){ + gc9d01n->madctl_val |= LCD_CMD_MX_BIT; + }else{ + gc9d01n->madctl_val &= ~LCD_CMD_MX_BIT; + } + if (mirror_y){ + gc9d01n->madctl_val |= LCD_CMD_MY_BIT; + }else{ + gc9d01n->madctl_val &= ~LCD_CMD_MY_BIT; + } + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t[]){gc9d01n->madctl_val}, 1), TAG, "send command failed"); + return ESP_OK; +} + +static esp_err_t panel_gc9d01n_swap_xy(esp_lcd_panel_t *panel, bool swap_axes){ + gc9d01n_panel_t *gc9d01n = __containerof(panel, gc9d01n_panel_t, base); + esp_lcd_panel_io_handle_t io = gc9d01n->io; + if (swap_axes){ + gc9d01n->madctl_val |= LCD_CMD_MV_BIT; + }else{ + gc9d01n->madctl_val &= ~LCD_CMD_MV_BIT; + } + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t[]){gc9d01n->madctl_val}, 1), TAG, "send command failed"); + return ESP_OK; +} + +static esp_err_t panel_gc9d01n_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap){ + gc9d01n_panel_t *gc9d01n = __containerof(panel, gc9d01n_panel_t, base); + gc9d01n->x_gap = x_gap; + gc9d01n->y_gap = y_gap; + return ESP_OK; +} + +static esp_err_t panel_gc9d01n_disp_on_off(esp_lcd_panel_t *panel, bool on_off){ + gc9d01n_panel_t *gc9d01n = __containerof(panel, gc9d01n_panel_t, base); + esp_lcd_panel_io_handle_t io = gc9d01n->io; + int command = 0; + +#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0) + on_off = !on_off; +#endif + + if (on_off){ + command = LCD_CMD_DISPON; + }else{ + command = LCD_CMD_DISPOFF; + } + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, command, NULL, 0), TAG, "send command failed"); + return ESP_OK; +} diff --git a/main/boards/lilygo-t-circle-s3/esp_lcd_gc9d01n.h b/main/boards/lilygo-t-circle-s3/esp_lcd_gc9d01n.h new file mode 100644 index 00000000..ec057cc2 --- /dev/null +++ b/main/boards/lilygo-t-circle-s3/esp_lcd_gc9d01n.h @@ -0,0 +1,99 @@ +#pragma once + +#include "esp_lcd_panel_vendor.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief LCD panel initialization commands. + * + */ +typedef struct { + int cmd; /* +#include +#include +#include +#include +#include "esp_lcd_gc9d01n.h" + +#define TAG "LilygoTCircleS3Board" + +class Cst816x : public I2cDevice{ +public: + struct TouchPoint_t{ + int num = 0; + int x = -1; + int y = -1; + }; + + Cst816x(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr){ + uint8_t chip_id = ReadReg(0xA7); + ESP_LOGI(TAG, "Get chip ID: 0x%02X", chip_id); + read_buffer_ = new uint8_t[6]; + } + + ~Cst816x(){ + delete[] read_buffer_; + } + + void UpdateTouchPoint(){ + ReadRegs(0x02, read_buffer_, 6); + tp_.num = read_buffer_[0] & 0x0F; + tp_.x = ((read_buffer_[1] & 0x0F) << 8) | read_buffer_[2]; + tp_.y = ((read_buffer_[3] & 0x0F) << 8) | read_buffer_[4]; + } + + const TouchPoint_t &GetTouchPoint(){ + return tp_; + } + +private: + uint8_t *read_buffer_ = nullptr; + TouchPoint_t tp_; +}; + +class LilygoTCircleS3Board : public WifiBoard{ +private: + i2c_master_bus_handle_t i2c_bus_; + Cst816x *cst816d_; + LcdDisplay *display_; + Button boot_button_; + + void InitI2c(){ + // Initialize I2C peripheral + i2c_master_bus_config_t i2c_bus_config = { + .i2c_port = I2C_NUM_0, + .sda_io_num = TOUCH_I2C_SDA_PIN, + .scl_io_num = TOUCH_I2C_SCL_PIN, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .intr_priority = 0, + .trans_queue_depth = 0, + .flags = { + .enable_internal_pullup = 1, + } + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_config, &i2c_bus_)); + } + + void I2cDetect(){ + uint8_t address; + printf(" 0 1 2 3 4 5 6 7 8 9 a b c d e f\r\n"); + for (int i = 0; i < 128; i += 16){ + printf("%02x: ", i); + for (int j = 0; j < 16; j++){ + fflush(stdout); + address = i + j; + esp_err_t ret = i2c_master_probe(i2c_bus_, address, pdMS_TO_TICKS(200)); + if (ret == ESP_OK){ + printf("%02x ", address); + }else if (ret == ESP_ERR_TIMEOUT){ + printf("UU "); + }else{ + printf("-- "); + } + } + printf("\r\n"); + } + } + + static void touchpad_daemon(void *param){ + vTaskDelay(pdMS_TO_TICKS(2000)); + auto &board = (LilygoTCircleS3Board&)Board::GetInstance(); + auto touchpad = board.GetTouchpad(); + bool was_touched = false; + while (1){ + touchpad->UpdateTouchPoint(); + if (touchpad->GetTouchPoint().num > 0){ + // On press + if (!was_touched){ + was_touched = true; + Application::GetInstance().ToggleChatState(); + } + } + // On release + else if (was_touched){ + was_touched = false; + } + vTaskDelay(pdMS_TO_TICKS(50)); + } + vTaskDelete(NULL); + } + + void InitCst816d(){ + ESP_LOGI(TAG, "Init CST816x"); + cst816d_ = new Cst816x(i2c_bus_, 0x15); + xTaskCreate(touchpad_daemon, "tp", 2048, NULL, 5, NULL); + } + + void InitSpi(){ + spi_bus_config_t buscfg = {}; + buscfg.mosi_io_num = DISPLAY_MOSI; + buscfg.miso_io_num = GPIO_NUM_NC; + buscfg.sclk_io_num = DISPLAY_SCLK; + buscfg.quadwp_io_num = GPIO_NUM_NC; + buscfg.quadhd_io_num = GPIO_NUM_NC; + buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); + ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); + } + + void InitGc9d01nDisplay(){ + ESP_LOGI(TAG, "Init GC9D01N"); + + esp_lcd_panel_io_handle_t panel_io = nullptr; + esp_lcd_panel_handle_t panel = nullptr; + + ESP_LOGD(TAG, "Install panel IO"); + esp_lcd_panel_io_spi_config_t io_config = {}; + io_config.cs_gpio_num = DISPLAY_CS; + io_config.dc_gpio_num = DISPLAY_DC; + io_config.spi_mode = 0; + io_config.pclk_hz = 40 * 1000 * 1000; + io_config.trans_queue_depth = 10; + io_config.lcd_cmd_bits = 8; + io_config.lcd_param_bits = 8; + ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io)); + + ESP_LOGD(TAG, "Install LCD driver"); + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = DISPLAY_RST; + panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB; + panel_config.bits_per_pixel = 16; + ESP_ERROR_CHECK(esp_lcd_new_panel_gc9d01n(panel_io, &panel_config, &panel)); + + esp_lcd_panel_reset(panel); + + esp_lcd_panel_init(panel); + esp_lcd_panel_invert_color(panel, false); + esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); + esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); + + display_ = new LcdDisplay(panel_io, panel, DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT, + DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, + DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); + + gpio_config_t config; + config.pin_bit_mask = BIT64(DISPLAY_BL); + config.mode = GPIO_MODE_OUTPUT; + config.pull_up_en = GPIO_PULLUP_DISABLE; + config.pull_down_en = GPIO_PULLDOWN_ENABLE; + config.intr_type = GPIO_INTR_DISABLE; +#if SOC_GPIO_SUPPORT_PIN_HYS_FILTER + config.hys_ctrl_mode = GPIO_HYS_SOFT_ENABLE; +#endif + gpio_config(&config); + gpio_set_level(DISPLAY_BL, 0); + } + + void InitializeButtons(){ + boot_button_.OnClick([this]() + { + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + ResetWifiConfiguration(); + } + app.ToggleChatState(); }); + } + + // 物联网初始化,添加对 AI 可见设备 + void InitializeIot(){ + auto &thing_manager = iot::ThingManager::GetInstance(); + thing_manager.AddThing(iot::CreateThing("Speaker")); + } + +public: + LilygoTCircleS3Board() : boot_button_(BOOT_BUTTON_GPIO){ + InitI2c(); + InitCst816d(); + I2cDetect(); + InitSpi(); + InitGc9d01nDisplay(); + InitializeButtons(); + InitializeIot(); + } + + virtual AudioCodec *GetAudioCodec() override{ + static Tcircles3AudioCodec *audio_codec = nullptr; + if (audio_codec == nullptr){ + audio_codec = new Tcircles3AudioCodec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_MIC_I2S_GPIO_BCLK, AUDIO_MIC_I2S_GPIO_WS, AUDIO_MIC_I2S_GPIO_DATA, + AUDIO_SPKR_I2S_GPIO_BCLK, AUDIO_SPKR_I2S_GPIO_LRCLK, AUDIO_SPKR_I2S_GPIO_DATA, + AUDIO_INPUT_REFERENCE); + } + return audio_codec; + } + + virtual Display *GetDisplay() override{ + return display_; + } + + Cst816x *GetTouchpad(){ + return cst816d_; + } +}; + +DECLARE_BOARD(LilygoTCircleS3Board); diff --git a/main/boards/lilygo-t-circle-s3/pin_config.h b/main/boards/lilygo-t-circle-s3/pin_config.h new file mode 100644 index 00000000..db428cac --- /dev/null +++ b/main/boards/lilygo-t-circle-s3/pin_config.h @@ -0,0 +1,47 @@ +/* + * @Description: None + * @Author: LILYGO_L + * @Date: 2023-08-16 14:24:03 + * @LastEditTime: 2025-01-20 10:11:16 + * @License: GPL 3.0 + */ +#pragma once + +// MAX98357A +#define MAX98357A_BCLK 5 +#define MAX98357A_LRCLK 4 +#define MAX98357A_DATA 6 +#define MAX98357A_SD_MODE 45 + +// MSM261 +#define MSM261_BCLK 7 +#define MSM261_WS 9 +#define MSM261_DATA 8 + +// APA102 +#define APA102_DATA 38 +#define APA102_CLOCK 39 + +// H0075Y002-V0 +#define LCD_WIDTH 160 +#define LCD_HEIGHT 160 +#define LCD_MOSI 17 +#define LCD_SCLK 15 +#define LCD_DC 16 +#define LCD_RST -1 +#define LCD_CS 13 +#define LCD_BL 18 + +// IIC +#define IIC_SDA 11 +#define IIC_SCL 14 + +// CST816D +#define TP_SDA 11 +#define TP_SCL 14 +#define TP_RST -1 +#define TP_INT 12 + +//Rotary Encoder +#define KNOB_DATA_A 47 +#define KNOB_DATA_B 48