From 6fb74b976fbec4d156d0b50e5b3f506a813004dc Mon Sep 17 00:00:00 2001 From: Yann GASCUEL <34003959+lnv42@users.noreply.github.com> Date: Wed, 16 Mar 2022 15:04:07 +0100 Subject: [PATCH 1/5] iso15: fpga: add support for 2SC in READER_MODE_RECEIVE_AMPLITUDE FPGA_HF_READER_MODE_RECEIVE_AMPLITUDE | FPGA_HF_READER_2SUBCARRIERS_424_484_KHZ flags will return 2 fsk bits (same ones as in sniffing mode: '10' => 424kHz, '01' => 484kHz, '11' => unknown signal and '00' for no signal received) in addition to the signal amplitude (on 14 bits) on 424kHz. --- fpga-xc2s30/fpga_hf_15.bit | Bin 42178 -> 42178 bytes fpga-xc2s30/hi_reader_15.v | 19 ++++++++++++++----- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/fpga-xc2s30/fpga_hf_15.bit b/fpga-xc2s30/fpga_hf_15.bit index 678e746c48393129982605bf251edeb50880c306..c1ea7d0706540b02e4d9bf05a1fb4284742d650a 100644 GIT binary patch literal 42178 zcmZSJ51_fY4L`px&=9j$*C!NdC4gZNeo;@#l{9@g@y(O49N`K zMg~Sk`U=MShGq;Y44j50R;H#_#^wyE3=B&O{{R2~f7Q&;Gy{ePMg|53b_2!)5Y1u0 zsKCh3V6SVyZ~(+-fXZ_kFeo7N84Va57y=knk{N^q7;poY2Cy6vY8aWoDsd4IbBIwR zgv$t!ILtjDHZBZRBaBNPB+ddN82$+`HZUYGNHDN5Ffu?G9E_6~5*Q>E*c|MS8~`ag zz<7<}euMP^{Q~A1raKMB3Opy6jTp`}7&q{LU^ZYV0I6vZc*fGrpv1t@z|_Fd&7kyv zV*`^OLuZ551HKX7H65%^7-lkP9N<=9I>NAtLE``igOCbCCxe0mw*XTTBiNh+ zqU#tsIFuMTCNQmJU}R8Y(AdJH(a;&7<-#?QsemN`WDbMiF@_rltsV3)FxN2Lb`Uzi zvXfC$$?<@(2LBK402Z)04;Z&F{CBWx;7?%w!0?A@KLh`P=5Gvt8SE1HcQEf@0Gs0= zu#DlAgINeq1G5^#%LXGRzOBq_7=8xWIp_#*3$Q4F>^i}!&d|W1)WET!IfJ3A!Q=vO z2NUywogsRq95a}V7#u)q9c+FsQfM*J`79_qr1g9{xFen`0P~Z|_Xk}1j;3yE1VA#&2 zd!R#rNeB|o2N$~%gdp+$LUdQdE2d2ix(g0xFq~qrnZ&bV@+!u647LqC z3%IQyc6l&75O^VA&%p1%yn*2lgEfc#A+7?3AB=Vk`V7pM7{KX=Lm-XeHiv+Fi-ln&gPsG&1SSOr za7LG4RdJrlpmad{64MrjtoZwy7cY3=XvLt?qHG}Wg27Hpr>j|y86593WdHE| zTxgfUJBvB3;pajVhkhyHCk%fX><{o?V1C91POB2ER~Sw*SS9exU@mL8%V_Mt6Tqy( z@RQLl!Dogt8zjCg1RgMSF(@!_9OPQS(7>R@$lt)Ez_NoukAdR=8z02)9IQteTA4Im zGy<4hTpAe_61)VML>RX+=mv0vFfl>$mxSmNrcOp>2agL}OBfg#v=X={FqtsyWzcuv zxWvQ;iHijytqvz0tQ>eIG^a3}Y%nU2o1mo9@KeDqL}vn*1|)xJFf3vCIl~4N7stGQ zPPB92o6!A);pYUK1bzYLQ;__nA+Vs~hQlWgodeto3?~?h7_G(x7bJga zuvjQ`IVd$~JYce7=yOP1!SO)&1;Z`|y#|g2%v_MT&|vjvn0-Lu0>^?j-t@%@3Y-jk zxEL5WFz7I7FmQ3f@|OfdJBOkJM*CRN zhl5#);aS5%1)T~(A%-svwhBBR%vzBA#UZ;wz?#9%fxUt80mBUj8wdUg%ug78GuR#A z-@x1i$zKkvzZl*$STErD;cUlnzQJ$_PlmD?!_fxw1pXV$2O#maLU1O-6$V8Xg#zX~ z4r>(_-r)#j5^z|?VA{Z)!1M|d&I}AQ7?v<-HfTgJu`ow-2LlIF z1A_vCh5;7`(;|ii2NWi-d2oQ@l#>CLzZf_&7!EKfHRuR1FJO4VXyl+XL6C>#H)Gua z{tes=ko>hk_6EZq273nn3(OxF?l9Op@Lyow!SDwpazOb7B!3-X{lM^^!SH}j0kaUp znI=O}@-t(IXE09S`M_+z1kQsE0uLtKXi!q%=wQ-g=x$JYz_Ei#M*v)rePB|6gii>uz?;Qj&0Uk(B*8BZA61@vrTRuwpX??^M9@mlQ)wgKnxv71Jz+)&|7~oF_KPake)oKH{um@_@vt2Zx431EWoY4g*sM z!$Agf`O2RJ~zA@P~@UP%P)L$%LK&cK~ ze{tF~$RA*S!0>~?j)A>F7+!xpVYtb(!GmuCw;Dqv3%J@^!*r6tsDNLP8&-d@xG;1w zC?#-AV^U$*$)FR!F@Z^qp_5^eL-!P>1@QWdVJ3sJBi98dn})dz`c9f3n3gnvYCY~L zOo;l+p_M_=fg^xTgkdYFcn@G=ap)9CaNrhXYGMRO#tz=(1vge0Y58?cR^Yh7V8_sV zfO#d$sTZ3V`1{)7_1C|KpA7pP@+SyC;rPg42PzMrHT+y)@4$ZxQGb15c-di=z%xTw zh2tZGkwgCkZWEESON<@(Z?PfOUmXle3>^(j8Ui~Qlo+@V2q`dh1Sm0b_je)HUrQU7 z25^Kpf!wU338aRhkwM9kd!i7m{t|u4 z@RGsIfoFoV8pltDO%D7*!fH$>L5vwnu=Gu5oumE)?rw6iEO za5xA}5ZTM1*dQIibc12KgXRJ5f~FM^b5?LjF&$~xv4Bs4X%@qmmW>JQ5zPM>t~J;` z;J4sL)L#sr8SEPQCopF)`~+276PVvHyaF*cFdu@{Ukt3j82&U`C-j_P7GgLu$ykB^ zgz!Cqvkmr7_-`;WK+3}vf-@Mlvn+J;aP$%sXf;^a#O=s*gW;-x?g8#gOj?lghk;>+ z!v+D(0~!HL77lY*6dkxdm_9JHFz6nT4q#(|)L#lBCs;N&C_LbhWa6AK@qoe&Zl0zC z3=DAD6Z{*vwIF3e1H%i4-yAj^ z`iq#KF}xPAYvA9&{D9%71Grv>*Iz#rzA$cUSYyPzhk;MQ-ht;Q^S6fo4fYTCuORBL z42IpHc-X*X5j-+hXquTJz#zTsS6IU{$Y5- zV9dc&#B9TGoWuG6-vQWHgVZlWXLoNoFCWedvF9o&)hE<@@XkdWXUuzgTdDISYG%@Hf%mk%pfhHY?m7o^& z7AJW9^@d?pfRT$153`2D2?i?xz6Rzbg_lf44m?ZL;q_OK(ys=)1pW`q&lvtI*d6eB zApD2n9)o=Y{{?1v{k4YSHiN+do<`;brV|Wif;=mj6B$l37#-kQ#tg5&Y#5rAlumGL zRqA(WE?V(HV*?W}LxX~X0EbKyy#M9HFqvUNg9ZoF5vE2)l^!MmAzlTA1Pvh$876rD zOOC0%L2&^`1=B2rwh6ka93D2g7?387%qd{!@_)k;Q+%H0hJBHJPePR zESh)(7-9V{**y&Z8SIaM;)~%wgY`rH3(OlB?lPD=@IPRN_rKmayk;;upvS=6!0>>< zih*x}a}LK(1|tWa4Z^P=@#P^nhv72A!UG%yOfnqp42lOj61W5ywlOSh&~RY51WEG; z82T8NA5eV2<-s(GWl2Y-f>#Aw9K-eo#RnV}OdlZm%R}T0$nOU@?3j2vniCYhWEe6r zFl=y85a8fox&+BzFGN=`ylOC*ptq)ZCBrKR>ju6Tf*vfN1Pxkz7Bv5Y*yX|Sfa3>? z9TUF;^96=4O!kcY44`!AXfMEj0MY+?!f+DgiwVqX4ksCm(s(8?FM)|1g4or-;=GH5$!O<-Cgu$D#Jk>di> zF-Tbl>VLH|EOg)qV3Gm#PY-BGa!p}c&d_;4A%Ni!to}+`aAJj#mQGjqqD3cG?AGGr zYhJ+cg29S`=K%9NNc(Vx>=TCHOweZhPX-$Y-doHm3>O(pz!e#!eJH{DgyA5_Ujoca z7+!)H7r0Y6elme7JLO}L_yYC6ZZI%0a5ON3`UT1iTnnK6FYW_O=OBIu_rJh(F4GjY zrHqOW5&}XYp#GNwcL38NNc&JibPdByM&*Ex3rt@*<}xX}cuith0`7ltUtl^0iHik- zpm5%lpfiEFh2bT~h6J7o%qieXYW@URnV`Yg!|;*8CWU{3^OFW}y)5CJ!thhTE`fgn za{YC})rf)T0JkB-9f!>fIxUPF1pWxvv-BTSJ_YT6v1&4OF)V7}(g2NbbSO7;JYmve z*wvu7fO`egDM(ysu=*&2o4XcFJPeZ?6b^73aW#O-ItFeArc;pi;Q~e;hNTRO4qOgQ z0Sqk+iVWNVO>-2sGw2@VE?{~AF=qvboWijN!v#DQ%t8#;8w?frUocN%_}U1Qq!3`s*LV|3>47JQ3V-Oh+3wKH&MmY~}E; z(S8Ac1~UUBzE%j%Vc5!G>fw>VB*4(dpvb{hz$Fr}l|k2qJCF%lr!X+gVc5o?*Z}I9 zfzp^FhXRv?0k{pBzyzzm1kNyQU{GP;P+;O^Xkt*fq@}~e(y-xxjs~-26TJRf!|j2l3 zCJu!z2E7LEC6gY&>MtFJZbqep91UDL3>^$g2RIs;)-ZIj=rwq6V0r+F3l0_@hJ_3& z4jcwd3{1@o8Vp_xLJSOz3_1?n3{24c#lV}xaEf7515X1psQ+%nz|+9IhT$}%{yG53 zUkhY^aC~8?Wa$4OTmvc?53D`F{DBG5|6+#szdkX%X|PS;2emhR7|ajw9$>0z_%C4G zz<&ZUe)x`|yFo93dj^vlLq~(s2aXOVb%*^4N(tN>m=-|tR|o3>#zi0(g8H6~3@S&w z8Q6RnHajRB;AUWg*Iz3*x`gx`xFM5QYnTa?Z+bbBfv;@-jZj-j(b z=>YcvME}d3VPk{F1q}tJ158Z~8V{Hyh0bs^GAsz(xUi* z*@NjCLu-d(0{00-{l&oWA;7|c$AI|=!y$nk2lz#p&nO*aFmT{!W`@^aI~cxz`d=TI z|1kU$us^{6k@*Wl?F4%Ven&+8b%fy}gVBNh2Idvup^yFz%qk4O6AVBDiSYVs3Mk7Q z;C5iT!LXH8_aJuylMKgp4#fi;4ovX+YZt?m2E`BD6-?7W-L5Cx5^U2HHa93f-~crg zq5ZG349y7&54bIu&aiA&&^f?uAk@dOTR`Cn2Lq!1+TC%FVN(PD3+7!6{~YWZ{2z3$ zVEEOrcmc-?W<>qf@a%eepPi0bZ-01+S^$`6p_{jDVhK-ORZWe}(0t*~D1eu&*^;ZJJ4F)Sv zS@(e9#|ka2{;p=F1wUSF(&A}hhWEd|u>1wJ1}`vw;rPp72O8h_!ti&2y+b}q|LZIR zXn2I1h2bQFnFHShW*3IP48|Ee0o+LauP(^=yaK39VBkK$q`!Wl(nTzQUx#uo=`ooB|&| z6g|ptl0m_NXBx9=!$}S^2e~=SPZ+@M9}#AF|7$tZ&k6fLweS>BtRm{&IZV6Zu$kJA4-H^p9&|D^IKhJOv9{ui?m!~X{32Rt_r_1DCJ zRw3OZ+yzW`T(&YO9^fuul3>`%pm>DqBGW2JImW=SfvKH=w}Csw=_bQQ2E_*M0;UNG zTNo5Ca1_AnFOj1S4F_}_xdoVxv20>k;K0q$#UZeXLE(Uu1ycv4TwNi$f}vBw4pf`J zX!zM+)F9u&yolj9!-fSsQ<~xR*GY$8klxl4mQMk84*WZqAA$;p1paBv@cQcm(;WwU z7yb*}W(@ZjHYe~;VgAAJ1=OB@h3J3f2y~wSw=OgUW-=(XaIau`$uI*n;*o+FKh$BE z$)b0Fdn22U!|ns%qVfsDYzCzV8dKoohb$@#4GcO8+#O6z3>z2}6u1SL&ag~iSmeOb z03Sc(O<}mnV0WN@1G5gpZw4dK5bhI(3n2G2FvG_WFTiT64+1fu0h|NOe;B|Wu>;8U z*O>-m6`m8!ml*yu*emd!U@ihP_&t~lAnlC<0*^q$eW32-JC5BAdJnuo<^1jzy#w4E zxS;*76Ral~x;T_LI2f3Yz?x47zzi-1#QfI^fv$!{2Rb$|yxvRDM}yM?h7Jb30`3;3=L|a>tT?zQ!RoIU ztPTvD8Z-pBIG7GFGwj%v6k^ymN%2WXh0;BS_9op0vn!Zx zF-&jJeZZZ8sJ}QEez90F@G(p}!f-&q#)+Sa`3U0&1{(%`0d9ExwG%WV4eBrdVEE5w z51NAf!SG+e`Uw9+ME&)k;Rh&|A;DsIfOiA)8&Fz3z`ubp3ld)^1Sc?TXHaDDaA3N^ zu!TXFk=w!P0*JxD?H~jlKWt!_q_7P%6kNe{4^)3W-~=;TbRTe6I2FM9Uk5mLH7F=p z@G%`?*wCP(z|FJhfWw9ZdJ5c)i21Ko4FA3uZQ)tN{EFe%1G@#h>zH3K{OZzM)W1Tx z3X+#S7#_0xVA#*Xe~|fu!yit27WV_7{uiS@2Xg=GJ;P52gAAUD;NsNIgAd&Q;t+E1 zo8Szuzfw4MGAt_Sn8EZU12in(Jpt4y29?hfxDfMSBD)=woj5KqeSvj7-Z1nstajkK zfT+KC7&ZzhgXWU1Fl=Se1$AMr2(&U}x<~{vA?Clp!(u!Q(4kJg1I!B;zyktL5cSs+ zhOZ0kGCz-DGVP$ z!-f-_o-lMW7&&Ob`(GK1OE^H@0r`vJCx;ot%|98eKn!^OwV>gLf)RrZBz){a-O>*X ze^~4o`5Tn{nGZ4Ii6I1-rdFl^z_eE_OB7`C(IUf_;!fY)Dg4F4L; z7ieTSUt{>%X1kEj!}$`!mj>Af{283^@xwKsJa~|Q0rL}qA1pQwJfOVun!)Y>cLOuL z|8D-G zWC%!@IC(KlI-sDy!G#z3wE85T5fGB7EC>OlsE23YE^Iv}${xR4d;4gqoqcHG0aDQNU1FFaz;Pn^dC(vpQ1>PIXpBRpS)^EIE z-Uq6`9`Gb6!^aOF33P(yzg7snW9eW~Qs4m9U%MFe?{K_8Uca$`LFs@e1KSzF6%1+& zTnbEQ1U4|}9P(J;qy!n?0IlCx!J#C;(a`jUVFiPd64wT%w+*{M1FZ{~p!FAn=wXG& zOh#9Dw=$n}c)Y-<;iOU}j(`08LIjVBDc_e}X-O{|DwjEO!s=KcM@e z`48j&1$GDZA0)%a50^T;2rxRpH-%Y;L?#`F zP6d;L9J`p_Fmy5KAK*xa)n5--{TU296fSV7Fu6JiC@3^=C@`F1SjeDrfI~$I-v1Kg zncAeNz@5z$<}kfMRe_^|=@&zLgYJ_Kl<~t)3t!lQub zf6ZZd#$?aH@8moo;5~z_1OJ1`Hv;}J*dLdFpo|zlWcbZubij85a|**PCNt2`#~X#u z4QwxTR1)CpH|BurFYW>+5r%e9+pmb}0>gF&<-;5)O_Lz`>j1+%hJB3-A4uL{DreZ% z!uo)_g6SUP-UihdCo-7e>o?9ZZ1zyN#I47~!O-oY@POMy>0HAG575dGrb&?Y;S13n z3jaEdcksSpj$rtA!h90{0;ZP@{~XLfBNd4GFHmnwfW1{1JnhBA-@^QW`U?ww3W96A~F z9Jr@6A?mM&oefGZ-4mKrIKb0h6B?cfbTa5Ua!)|iUn~q8nLsOxgj5(B1wag?BMglK zIv(7}>o*vFFc^XQUkVH-95#U%4;W53*fH=Uuip^(&anPKe}MKChOZ0E9rz~*KVh<6 zXy=EteuMD}(^*CxP}fX^;VOfn1MdWeCmhz`fy5+O|BK-PM+YOg{!(D+V9;vdXkdE5 zFk=OHMhxEndcx4iqT|55K}dyRCWC?lrvMYU|K-4eJpaYe$)xAlJ<&~tp_5U`fnx&O z6ONe*N)8+woM7``BFh*~ve+f?&*)ZZxyfMUz&mB~Gl!oFn;di|Fl(@Y``sE0%Nl+< z*d5`Y!3>)A+UMXuq4^ERP4I#TZZAmN2DErBCQy3TJ;T8On_t#o5MnsmVEc$K zgZUoEkp{yAor>m5j^7*XU+`scd%?yJ7l1|!5AZHve#7*d0aPz5KWx|y8nbGEuip^; z$MC(u`~m+B=5GxDn=Bv5{A4x?0QbLMFb6=|hbtuKFl-M{Z18qqy6Lc=LGgfx3v+Dpq7O;E5zlZrX!w(Lt1H4<9^&0+j7$4wOfYp@^3=bWC zu!tSt-)Q`h;WtO!QN9VxZv}pH*d5?&LDXNLK<%g#JO`S8fSURb{69MXbJ!1>RBAx< zzY-F5wCFtG=wNyT>b4)?p2GIFVLzkQLGB(z{gne60Y1PngXx*TZU!wE?gdN_IKZ8e z6;29_;5HIy{BQ$VBpLFl?e?z6PTVr20s@t zy#Tkv{TsL$n89mB7RY`8bx#iPA7K6fa=Zh70`m`s?+n%;5!m=4xc;(N;6D-kNdVOU z;(5euz;eIA-h=l5V*KzCLw6(J1Mu+L?gqURyla^jF?2TQZ3NZAkZ|r`eE`Z|pnfi> z|CQs+v4DwzVIqTGfF}oH{l-G3E(X0r+#A?lFzj~FYv5F1TAL+PgsHG zm)|h#X4E;rhqQjfkzs?2PKvZ9(`g3dgM5wLDoke?Hah48bT=@;*KY`MY;VwAz#YSM zjbTfJW&(Fc(=CRrExHf5G+_D5gM)$LqoR!`zXJ0agUt#yXZRJE4}fMF0{9uYgCLRW zAiDw7|7zgB2tXwYHcW)TBTE9)?DYcTbJ666_f+om8$yuT2g;qZ&Yu8DUcsM`f{ z&jDpCmS2pU7x1Sr!~0(k81^veGjJbZ{s`)DGVnJtKXCZrXa}kkCPCK!a0onOI61{G z5j5w*@RLQ(lc$0CiNZ?;8|3weEGZ0~40o-)GT0y}A>cW7gEjU1R!9oTd2QEQ2r2f|l20I3B&{)$C z273XXuI5JzKMuXK~0uLB=K5fJ8)lM`@*o7G0&m<5>p8%G@PO*FttJ2Eek}SGW_JQa|GoJ zP@fURPy@LqfFD$Uf%BIJ;}V6RjNm@n6OdvCb^+#N3_o4uyx1o&!uwwvM9wf;Gq4|E z-pKHW!Ag)1RLFqGKpGJ1H#8W!nDnM_FJOA5v8zL0fdf=V?dsHP;8sA4A388i@X!F2 z8x4XR7&IC<8JR%iHaU#k28i)PJBH~Dx}agYI}FIm7#3OITilhIto2#&%l#KDgV4uK$g83K2{|5U7{4bd=37nf?pW^=mvHoyILYn}jZc5l9pxOfN54H-~ws99C z)*tR+Xy?+s#2vx}c5?$)LemF_<$}5{(gm>fhYBJG7#ccs6u33i4k~VVptFeELg*}L zuDyxdl<5Q{-a+-(i34^D{EL_$g9`i^{1ceoGQ8-}o525*8NUAT4F_li7dOZ|zXi-1 z_*o0Iw}%6!^jLjiI=K|ARAl%xVGu1?E2tf0^wUod2K5khv7Gi5@^^EG{Fa2bD|6zM`DJ}fANCrFVHv}xZBmh z{{Xyz*`9&_1HAvm`i5U|{O;*vz4Gg43`GzJ7iUXg=40 zV+oT&0*3<^6GsPA4?{PD)(MUci2Cas!{q};B|Hkuj2tH%Og`{*Ft1_w&tP|fcL&n= zp~IgE_6_^)d$I#ROY8EQ-a4c@f;NaN8 z!PD5ypmn5c4{ZEUfzgk;Xf2<^nuhEL9JiU|9F{jIKjE}Q8b4Gx5U@d@gOQoR zWdXyE13E^`JPfB9EDrD)BE}EruzVM?KfwNx`H#bO0YMf1Am%>`{{`$1^nXN*AD&}6 z2wGpdT-J_Tl-1-HQ?ip^S({;7xH1kkkd0geKv3k>ZH$_F|Mm}DB3Gnh7L znJ@`K#*q#%%wlOdkf_k)!Ssn?OM{{U*9)dThUpE;4>&5=;Qg;NEX^kpK5-cconcta zsC0qDgsF#NF~hP0T}bQaS2UbDV6>x8m-)29uMft{_@3~uV7SF#yv?V9+X}K4%Yy-u z4p|Q>e^lUMuovKOQ%nH$zd-fZC0PCS1hhgR1GH=dwCvEqZw@mH!^tMC1Re%%CWx8_ zmQ;p~ixeCo zt=M$ng_QtL3pafJ3p842=kR`mGN_W$%izDo^sMFQ0?`-zr;*nmo@Fw2;0<6FVK@qE z^BXdyIQ%?d6u`TQ*$vX3w-9(B&;efQ#H7H`!KkIh(Z!g=bl{7Eg6=scK8W8rSdTGv zHe`WzqqH$}gO*_jFoiU1Wl;6t3~6$LjBiMYE@|jy&~o6K#I%H=Q$xdnvw*3Fp_M_^ zf%67p{_7aSNroK>{ByXMFdaN#6r(>OIECRS16wHX1m+A#`q5xq!tztYEQNOhXkP#W zX#G6%6NZ-zR%q)tKr2Z3+n5(LoM12#=xtzDa5%wX#lY9V>;>7sqrqy$vW-E11$PdU zBtu_-QbWfA<_xA=4N6lykk-%pIBaI%ImB$r&<9?(oN3^s$JpGU@qlY1Qx~NFwSZBC zVLPMl0qze>GK%dCi47iKnBo|wcVs@|5J0ZKzJf;VE10J-9dA%Rz?;F$z;TShvVkw7 zdlk5=$G{=GfZ;cjT?6|D@Ur=Rpu+P3!*2#N2JT7B@bSZc4CfncRd^$qtr-3`7(eK} z!CZGBwn06G<)CoiSIE5iGzyq;|wB+wzmwE!{~$-$xkn*U<# zW?)hPFQn%JQB4d=jT{V2hXf&!!J8s<0@Rk+z^uS2B;iMz|N6yryutVZ{|`<(kN*wk3jEOdFVKF650L!TAn=R< zv}~Sxhmuai?gk|VZqR7`ZVr_uZiA)=kZ|r`J;Kn9bw$0kki3! z#PkS~zYd5lVd$1fy1>1GNrz!Ki;^UF1JeqDoeXLY+#8raK;qp&^sU2b2BU-g2F>~o zza7jz@Pit_KN;*8`?oM(faI?Sj9WPVH|#&c{|7WKs$l+re*<#~sONHk{}W>U;c|w< z4n`MvHZbcp{PwUDcM1}1%m%^i9Q92^Wv3?~*SY4Gkr zj32r%G&86i;4o?80WBS8U`q^_S8~4m(Gl8O+ZdPBz&&^b0Agfkza1 zCLqQSQye-wbRsyWusvn!^w9I*p1`HTuv0>;fORTj{e})hr-Hr%$2Fm^0{sGdF40%m zOdR(zD7$z}af0{1SQtS4FO2}ED-Nv;Iu6no_(T}D3aGkp3JM{u-#Ec&$H3FT^pK&4 zK@U{bDlq(TFrI+Ce&Y{l*P#Rd1m<@QpBZ!=_yw4sFjy|Id%+*Y4B!9tMaq}O7}VSP zzyKN!<(=UCgy9Z@u>j8jW~BaC7iigh1Jk314$ykz2Br@T4GR<$xw@GBAo&Zl{;-ol z;Q*%q(}Bex$8&-@j}tf)IJg=R>o=e~1tzi`W9Vey2Q8%k!qCi+;J`JZ$qf=03q+qV zoIGF@!8?ojDa*+NHV(2=nvbg7WZ2}uC*urXzp;$tqr<)w{t2LE*bD5^`lmEMVff)> zC&1r{vi{J)$bi3%`Juxf2YUt|P}%Z>(MUk2rJD;n4#~>V(RV<(f%^^9XNFw?dK0)- zFuh>d^T3>g;S8ex<;T!$u;2isLV*v_DOfTIvmf5|Z%Z!mn|51PjP2HEy?iQ)SJ(;qy)4#WFjJxs4b%QiMJ zzg_T?!REkuP~YKqhTScGr1cy37|w3ooWTEs@gK`y(B`O<%yta_8|okMRB|KgFNW3t zMKqC@O(gwAG&Zf9#8_UyLi*E!$D7wy8*Pm?ts|`-VMwN@b!mIoefGS zxEq+>IDob&N;l3^;n>Web;!d4G5@8)(8!>$fSZE}w18RX0QUwa4#v$4ItMr{VC$n8 zcvBcavt9fPlpsqW_&0FtF#HDXP}_i5zww3P8)$^{gD`0F&K}Ycvu7{|jkCeV4_QAn zoN2J<=szL+i32o#*i*!8;qZpRkb&m{V*Kzi!|fJ56YdVCXAC zLjuwNI^fg{>LCh+KSI2aZ%C@^#gHNoe<7BS3r&`SWDvztNh0oMj54uuX5 zr6!IE4AA*62hqn2Cl1&d@Xug=#PG|(W(Us(ZcT<)4h8}|3%KFy=eIEYaj|Cvt(F(~ zKfxa4ogWOpKFq9^n7byvO0M$o>=jH<$xpBPBa)L=*(bd zVmi%W^MGd?vyQ-N(0qylV*GFpLpy`wK@QNesCLlYu_0(v9;3nmj|4>hHO;WSLGuB} zk1jLtQWK5}rZ|T7ro@C9Nb_H344N0LxWZx6#+NXe0laNFhoO0b!U2v2i1iyg8O|-S z2F(Ing0?WM;CaBE!E_2V5BrW8W&TTy12pZ$@Pa{(L7#M)h7gqH$V#!RX}aX5ajh6EKK0lyaG(12?(77+(KPP7+M*@r44-iFloUF20bnQ z1WXgCWr ztO1(SG-lv$S<)Al#V*%4EgH3FU7I9}VzYyqR(QiOrf9Sxl zAwkCm)G`MxCQsnDVDb@|%%HP@12o16*$2cZz+l3l!@%Lfbb(%WN1vgBF)AV1A?U zlfmvF{}M$1>l4G#35HL2GP$Lgd_WER8_aeB=NkJmmWTQGd-;n98VlK%#(67PR<(0XJx~#!?2wBOED+^@k@ICps)S!C}P4 z!?4(4!47T(CLX4RpjL_kqW)UKaLdI=fv1I8vEc=SK>+^-ZX1S|3(_D87WxFkED`I{;3-(DB1N4Cgq&dx1?r%U>CI9x&T5oLpdh0I`0fks%k< zw_@ZL;8p?6e=%_IFo8y>lzwO}K+J!sD0eI9fmUd1upP#{`14?5LITj%5FBZmT z&~7gUCJvrm%!>|i2r%)0YP17f3b64F(E1JNYIz+{Gqpjd0puRgIMM?|{RJ)t5AuIt z-ox;n#lE560W{_b+7b2uY5b7!OM{_;P6e|a!|{fV4|HxYZ(=ypYN+6o#BAVD04fs> z2)yFi1s*?iVo>O4P-5WP;G_WBndrc=kO@>pF)%QkU_AiJgAE)8ObRXC3`_?&HVE~A zP8D$ASiq&!2%c{`Ai9PDw2hIYK?%HffPrg+Qjc;MgVH081xyi`1%d-J}D>uTihR*{w}a*;Qz^7)bJ1NFU0!!1r0wL zjgIgxU{(OFZ@Iv;f%&PxF9xFk9;Ep%9fsWj3qEknV9rfxWJqG*-5~IUVH3lm1H5mT ziy-yR16C&o&=Sy2K_*^M6fk&zPJY?Mpyj}=1RFnmz$oXquSHpb<0V^=LtjJk1I{Y8 zOAOm4C_nIi!Q=s1*A1Hg+Q4AYz$?Ja;L@nDfP=??`2@o+1_K9vr6wUZP%wk`FAIQn z#PdJk{1fmGv>hANo&3jOe?<2I^96SByxt1d!wffB3>6e11&&31**S5 zV=~}yJ|Q?qsU5VxsX)j;;yQ!kL9PNJ8KySSL~{z05TtF~z_3Yxw?X{@=Lx1s3~L&c z6}T#x>=^nQ6caeIVEM~K5GgGoFLlNA&WaP4r?b!Y;uT2z3U1KR&}D8OtA zuL^UAz$1sv4SK7%tr^ZOFy5i_0O|Yi!MP4-|ed>|^3b+Q0mU z;b)VX1J4X@HJ6hIj2wBu^_PQ@1CIc_{$faN*?C04L1Th&I>XI_W)2(^oFMzZK=ZJW zaFzg#&r75`N?hbJQS1dB+Ax7>2}2`jj1g)4kcDwC!(&kQq{Lw^gQ|;`pb(2gqkx8s zh9E3|fyWOS%@}kVm=&2$uo(;Vuy8MAI00ID$_KB%BtfP6J_mjQre_VH?Y+9Un6EW` zU10u#{}%Hj$o{VxtX~*Tf_8*mU@qV|%dp9XcM7vAsNx9VnS!Xl9x!w;Dub#mCeXSs z4UR5lriP9Mi!?P(!Pd`vupVRRXHa$Ep1|a=sF6Y0fir-~1hh-Sk>e7R5M+EqLUaj3 zFQc*p_XH&tCvde@2rOba!C(zqwy}`mhJqCXZv(U9 z4A9v<{09*8Ult9Z^EWsiF&8JaIVd!6EMVed=wec8;Lc&<0w;L}4OTy<1|^LH92`RZ z9FrL|K6F?x`GXd%J>XVh;)2BK0!9(eUPk2y91oac9QrvGFL0DF#VGVMC?4WYXySmV zS-}D7e_1}z$?%@1aJ9kufzA)^IHn^Fh70&#Fmo{pfNbNCZDIHfD%BO3--7nFf!Dk- zd|=quz`p=ie>t#z0b`IGzTqg+XOm@Ba!C@vjAi~Y6WQjvV-DDjzl3DhGn4h zZ337K8m@zO=LHHu*90;!Omx^UpnRY+fystpKErYb&WlVF7<(9$88{+f`71%>gurGd z&}yhA-iE~s)C4#rnHU&48Wu=!2q4yPtYP@wuz`Vh1vekVZ3nXxybGMyFm-nrUEopR zhRzo>Fg)k@Eo3LaztH)O%kKcI1N=*b-?02g}$2@_ym|4An~<8_7BH*CSC^q2h4XEzA)G_=sPff zU;v$jpnCzfSMdPj2Zb{Y#vD8q%oi4%Yp_q?|HN$9aJ<3zgw7kp{MSR!t|%^!9ZHWG zIzSu2{xa=n=xR~^zy(@22(I5dSPv{%#lpZNVZd~Tp@~5!f!lzM=fPrzB^Nk0F%?1b z*8$OG3adDjk~lUnffnTJfwqsWVOYVSc7SU}lK~`uIfy=Dc(O?Ez zBCEl0O2N*7-=KL7!|4M?4}2yd=D*Y#njG{zIHoYE9_UiAa`67bY{S&iu>AnX1SZe{ zWDE=p4_KWVniO<|G;Nz07@7{~BuE=@`G7YYaxgGi!TMi94DAzi6F7e{2{E@cXo5CO z@j0|@P<_CW!DIue2R%4cd=D|`F!&m9@h~1@usOtU!0qF5NWtI)PXl88;TBO)GoJ4g z`yZD*4E8Pj9~u9q{86w6t+#=%KZLaB`7M|g7(O@{J-cY;MxQUXVCh?y$y;FyldFx1h+IKKH#us;$vuQP<+AR z!K45wqdY|3GHjlx@PS(*iML_H1ce1025dYG4GangxH^#5A3FSVFkZvIg4=@OSA+2i z$Uz(mn;Li?G+9CH0-YcALt!6y{E*=X!#)N62IU8!@hArV4&?QRCk2cW_-8OboA4X5 z97ToUw18P6^7vsIL!*LH2=f%7=L#Dc7CCTFVNzjeWL%NLF#&P@hN=U2+2KT{Hw-%k zlt7yeSp+&cK#TE^)*m`GGAwZ6HgJM0;o}x+Vrgh((g5{kkj~#Y!C!k{4WT9Vfe{ndw_p};1iFJpuNeLn&IPzpfN^!2Y!$m z&;$gyUOvlU=%71+Sq)MiS_r&g*ulitz}>(E+Q-4iz}3L~fCJS3;$UEguipSURmXu_ z!08BMBcqOkga8u@s6ln0TYxDDlD|Op*G>oU`uS&|PN)ah1g0YlolJ{BfdF5>0Xhi^ zwEmF!S<^`dy8@7Vm_XwI{!_RS^Ir}>8SFgxCozNO_3RS*4Va%W{A{pu(wW443o@q( zT7P)M!;XOmyc7q%P?N=s!KVdLe_1m0HK;c*JYf3muunm81@|(h6-<2+$_)$)nBeDc z_;E}Y(0RmR1{!O6u%Ln42-H?@P?^B70X}}{$fnWCs9V5Mz;*?+#lFG2fN4%cJE*=W zV1l2&A;)mN!Ty0x1@kS=>kWnr{4Kco7>+iWF3_n)?EhK-n(cDn-@y0=bPz`a|EA_Q z3YQt|9P~HA>MsVye+=Iz7=yMggBBthgBQ{(oNqCFp!@SMy#AWXu*E@HfTMuvF2i;P z%?1t!racVpPKpj3#ZB<~YYM}5jzkBJ0;W3*`#H2hYsh0jowXwz1#B#k`~_OSv58}W z0fzw7G0;N91)Ks*ZMAa02H5diytYCiA@Uy|HNp}Nt4a3O}r2{-a zxHTa8tAXK(;tvNqC;m0ePZWMT$TjqBQC`FFJHZZR{l*uD_YLw4{6Coga{QlQyo&z= zvkk*p2IB)hADE$gpc+|{8GZ}sDR8b}dd{%fL8*aTiAjg4@t{G1p8@QAwg%9eHzvIV z?yYQZ79BM+C>3yQK=i*Dz&ocn4cLx=DrnH03+Mo!C7^jC==cT$ZvyCq zD+Yeh5biDpBhX$v8^Xc+I3w22b95e5+ThX2q~y?XKuLvT2A9QwRU&$` zI94EyA2KX=Lu()gjrUjh3A{12Fa z2>fHPdBFd}`B%fQ1$GYn2ZZ74HFr9#yM#CwAA_pE0r231aooT@Vjsh+ohGs?u5JOU79fR@# z4W#kIc?^>q6(2D8Fa<%*kE&tnZfIMf_`u@?V*UJCmS)BUk21jLM>Qy%;II)|!LX=7 z;R%NZV*Ief;S!U&)36FeCPVfx2A`91Tty z44`da5)DF3hdK@@X>c?k&foaLFqJ_SG=3Pu(8{0!YVI;HG$^ca-~zcBJWc|tzj`^q zd*4hLdIgjmxTdh_G&F+73PAh6!1)Vw{>H@vW&t{L*i$)9E--W85@A*YozPgM58wZ# z!MKFs=Y%~D`E!_`HT`U|bMT$Oe2C%41Tz($4bAZNhoIi5v7pZpZUcrhj7AJTEzJ)U zPH-6s=rl0H&)?tzjnXu5C@6bKwLzNkpP70Nm~${JXn>!;;m6p_u;2hF4>B|}DIDT7 zVLIc`d_m~}g9Qux{0&J^$5q|jmmjmlH#`9AQ zpYT+$+qv9tFn-YggZUrB`2)poc#+m0PGGoZpeWbjz#VX*B}nrEp99k!hIS_NBOC>Y z^@mfK+8Gr=IZi@hdWYfx?n00n0mT!X@b!lYA_obr{COpx&z{SyYj-k1A z!I1_7CiwcpwG5388x(kED5x>qXf%4kZ_@mx>2OEV0oEOe^@lGQekj--xX0AGK|3flXx#=td+DW$W6Wf23%MkXDG)l5nUIJO|p z-(X+?wQ@NWxHtrwK?fl&km6wjt&ZS;uRmu-^e456Hj|s=vS|N+FFOeqcDGVZVUqK=TKN{|v?|Js+5VFr8_z zSK$AMnE!g@(2=5-z|kV~*kNab83)%6rdJFd3OXF#n~>@+#zqbu2M(@92RJq{C~$BX zFuf3L;K@;zHu6Kve@S(==p}N@aC*$Ji$jfpYXj2@hAs!agWO2#4-YGxJYZ9y(*as9 z>R_b7vt{uMhSP`a82UFL&fi!9+758Q{{r(9hCdAZ4M58&8UBdqKjHs?JpXkv#VCTm z!TE{8fdIQE@IL5M4t5MW8xZ|3l>?n87JcNN#q^}1t3gM=doR;Xr!EG)0}LM!>o*)2 znhvOJ;AUt$AlS^Hw-lx}=z=x{ zd%)^17N=7I22A_{%txFKE7)}KD>9#OxXfh3z`KDNzJ7j>$Xfw>2LDfte?Z6fGVni8 z{=x8`!Tt!}2WI5;^PsFZgIR&$#{we;o(0S=K&@re^&6n|S`6F)LLv+sI20LpQ$PoC zfX-g~#2f@^vmIcV#;~nHSAp9?XrAKM22~}lETLPC+d(3gi2CcC!)7O)0B!@OUWLsK zY6jd2OeYvNH-IN(;p^vjGW=7pZ{&Z#yo2Ev$M#mf1;RfV{_)uF;9r0=|JCq=K~Ai> zWo9D7j|M9S))wZ63_lv=nD`qI_19B|k3FF2m}Kygst!Cd%qpOPanPn<#QtT5otKm< zxhFV1J+PBOFO_41kZQwACQv5}(foM? zxD7#Ds2X$vI1HFr6c!33xPZ=Q1C2B>Fznz>TJYnA5kq%h^P>en8tfQ2I+zt2emHCr z@bBu@gX~Y4A^Qh(+J+VJtiEo10r%!}ZfAohlFCxe~~*90cS`VFR& z4R&Gv6S$W#ojhb0zyqG-3bX+o3<973YG?R)!0rM6&ciMYKNY~oMm=Hp*#cfs53j!# zF#KTH#KGUdtpI9@Na%njUw$|lG4LbJe_1f>yQnmQdx?+*&%O@J1s+S8svUMQC^dL5 z5aNR53k_D^hRqBLA>I}YJPn&YbPjNWCjB=vECJ16Lic|yU=(55+NtOuT>?IWN%I0{ z0#k^__64e-6-pTpb3o&V|CIVM?XXyF846Oe^<7_GXKbZG<{GDL`fafH$mBaBP z#*cV@G{evDoxre_MbVMlg-IY`yMXQqj{@cZhU*OG2axNpISgAsM>Q5OP2kunsDD5r z!EFZPde9j-@cK(Z1avB(f)6(f6SKl*MjZzZ1<-~mFBMRW8eV^OJN#rY3gqu$Ud!;S z!N!Saf^rYT`v$`!JRh9l>*pUh{3x(~XUI70R=2V8+pz*^E3`pxYPO{iP z;h)p|$KiZ~y#vn-=6?<68H^9~6bLiG)*t3Dbezz8$UTMW3`6$;y$p_xOph2AGiV*) z+Q75`K7Q!X*`VhLTEAh?t)Q2{HO1)#q*sE}|6*ck0G-9e;BzU4e}%Mmw|&xK%iAFvwlt*@LLR_A&hB*zcrY z75qWr-+}#!{0EqSF#P8<2jv}j|7#7yO-8#0{w>_PETgQE*kfB7&>V$f;eHe+gHYC54%!Oha-$}stW zM*|lF^7&D1UAoV_E!gHc^foFk;;s-nmC)0yoxoAS20#Chm*Em<2dDveOTb|Ug9H3Z z&JGTj87vs|7=q#FZ_EL$qIBT@!d&6-g+ZQ${{rJ*hZ+IXBm6%R>krR>)=)L_Y-HA8 zImu$wz+>R8!gK?)UZMf1|HS}awoxP`!qUdj>A>N@B*6e3Z-MV$o(@`S@_?g?%Z{PF zK^?SsQiy5Eft3neNd2z^O5F~64>k5MDJwKQQBmOFaPnbT?4)s^%RmXfe|Z(dDgpZj zo`a2=O{Y4{I&_vXYcM==uxem?fK-1m{9&?V;crp?2tI$F??Ceg4ju+|2F?SB`s)b8 zONLDuG7TJR%%FK0o(aM#94|RQ>o=I2AY&;FEGYt=4oU?a6WCM|8l4mZJOr3n1#UW+ zIPeM})*qTMtaVUw@|dt`4MVR(a)87&HWkLT49YHCSD4x$WgQ2L2}7%Zf(r*D(-enZ zfyFMMGwL1Y3M_Zwya-z#wSzZ7^o@&wfCk^9g$xZ2N`gEGxVaZRaxj>{cdmIIWPAg3 z{^8FB_8Hu#gxwl`F0^?8UWm98bRvfoV*SP!2C#Q#bc2>j7&&lU;07_k2T38;A2M|? zECS6JXs~oJEM@>501n#7%)oH~Hva`$f7r^PoWKE|_F_?Vk4$2H14odL)ON!xtv+;wTAKY?G=NmRZ;ECY2V>;ik@d4itW_bOz z0<I?x|qEJ?Vc_O*@+@fZP!Zx(Vmj0?VS$nk2Mc2TMyG>!hu#7Hjm^1ChZD>$ z^zLDP&v5&I*#(`w+|aXv8yH?Nfp#nNgU?cioEiIq7-lo*9pIeE#G?SZUW8)@(;JRX(B>lnCiwb!4u(e1sT2xq3`|`N2@M<^Ob-~l z7*rTE6kz!abbjw`2D<~i4NRc(50${}a3)YkOs9bxzJC4&g zY!CJqaKqOheq;EPV64FNg4u%M4CsIn@S#kg{?{R9`1s*7h8+n?Od315vlv!1C@FY! zKo%ko37I96KA75;%4+WhkuRP-0lo z(DX!M2ZLS!cN2X7m*6v=9|w#!^5`^cGMoS%dA(O{Rr z-@*Kh;ok!L3H&#Z`(Hm9?Jn>vVb*jw;9%gu)8ecOnozsIFTe~RKh$B^>7duZv60!6 z;RfiK0?>LR5D31v`)}f;6@rh>ZHLk9x*%vSrf&{swLxhSLq3z}r9=PB$1W;915DKmQQC z{?LIt0krIpK@l`K!@;!rmn3Wh#fX?mdP&NV=di-6@@bi1G zfcjq<`T@*O8GgFi1@KQ{c*^jd!PdbaZT+D==-L!e{|mGn1+pOa06)t3;RXgP(DYaF11d^a;F9N<F9t zgT@!GI}FnqbPjM-Fv&3NKcIPmI|X_C@N37$1wIwrQVho%Y!~okFoQZ`hM;qI;p2yE z7=9|)fJQ3LfVM|9@GszgBJdg13xKcR0QJBAf%d~@aLYLyhpcD&#qzhoK7q#rasJ^P zhGz_lLA-^`Hx4WpP&~o`IuC!VgRTRI3u67@9EGhC$}AiSO);P|v_W%Z7eI%VF>rVw z>MzhqJ_-t)7ELFZ7BeV-Hi8{)*vOz`0GfP(wAogOt^=*#aM0PqodcQ>0Zr(iQTX-1 z<_6CO#QMWG9KSg1BKo&5KLz;|jthq|w2o%fQW`$KkPo zX+=XfgUTU}7TEqT2GMs6FW9Uad0HCv98NUYP12jdt;BMhVcG%S24-mg{Q=`1(D0fA z{|D_4O#db9nfMjC>p%^A2L2<6`fH8DX$HGPy0e(yf*M^8=RxP`b%Q1fr!ZfE%%7fM z)#qq#&@jOQ&Hzb_abHwh7JZZ&?>J_0^qYy9l|oUPn? zOdUcU3>qBX228&o^Is1b<(OJJ^cQGUGTmd`(v2LpyjkCDR{Bo8N;&hv6`T z4im2e(;0@(2{sM+90!=DFtjk~ zI&c>-eS(Z1HZaU%Sl__?K;uV{9bq!O9Cl6oOAz(fL#7`B_9FZT zl{X~(VXzb7KXMp!f~CCx^7&DR7*6uoCFx9Hdd5(>a8nYf{{`;3@NZ~F)L)66kZV(( zFm7y6D&w6AzGF_wgCDv6V(Vnk3jwVmci79Q;J_KeB*M_kknF%b1*!gWXk^d_%_V^r zr7O5-1~Q2_wF)S@a0?>lznm9zFxoNrG%&wl*u!EV;M2m*$a2DAvjBe|qW=2S@D(&R zD8LMw2(!=NpT+#H;pYOoOukvp(DfTLSU(*A9ee0AL0CoLB*SJFo`dWrOrR5e`6nRO zZ#+J*gF&I81GECUhe1h$r;9m(;RK6P178EI|K-7YfMFv8=x#426NX;UVZRJ)EDRGF zR6skL;p-3AFzi%V2wGPOR&#?xAc=*klR*tsZotlu5`4n&F~KN6XRflk1L*i@9Z+NY zCd0M>{t4aC@xu(p#R@+;>>T)KbU$PIIl)eWzX8+{W3U2k_(iPWVEDmcETD758MLL; zPKc+WIT19G!NK3s4DWv#Fzn+{YT!_4KEbg|K;MA(408sjTnwL7=PgZ(`+wrz0>#s{~hKFknyM$f^!mD1#}N+I5NpNv@t9^ z!d=j1J13DXo zZ9oHwpuX?}$oR$r$v@!!S3l_9iocB22be(>*?)$527jde%l{buHrPX^DZe!sC-59* z{s%ha_Xz(FX88W)cP!u&t{`hgln!caXL`r5pF{r+_YNkg-#b{LTWJiG&M@o(?F#@q zUO?x7w*eb`|MD7!-2%{Kdv`G`I>G@m5Na}f|1xM>E@YWFyJ`9_g7(n~7&oFFaQ24+B_7{V~1#Sb_ z`V9p}Inc7G2i%oR_ZapyEPTX~!3H{%N%4VK1!Dgf55sQ(g9H2q%ph}=4)8E2pJDh6 z5rMDY0PhPp!2dxQba18p0sbeTjx&e-CFK3f@KaWF!12yM4|IUDg53fB9mxB?z&AA% zILR<<10TG@9K*2PK=%al{^fZL+dDv~^)kt^Y-`eez#YLP$FO~Y?h9_D`zy~tXS=v` z7&bBJJm5B9@?ik&Lj)ZF4#{7j_4B_VEm#ePe++gj*q1PKfE)%|lWYaaUmgsgEkdCF z7kG&*h|vrexdc65Lf{E_wu>LUY@VSA)Xsdua1wHU6r%p(*y*4Yz&(XYm8sJq33UJ0 z6DH7(FpUY!NcVqjgsS0abWsT40I%PG^jr|@HyAcLEC}EhVPa)ybWjN35M(;S&?utg z!XW^=p9tLl0xhd!UTw zfb%lQ`gzd(DL);ITtLhG1VH^A9@zXB{{`gr8#@{nX=rqDDK&L0SgfJZ!1RD&0|WR( zM5O-LR?v#OfF==zr5uV*TmqoUJ6N9)Jgx;=zp+sPbgvnc2x!ld2ge4cCmcH&_zp-T z)^CVH!WrZq@O>_P;Jy_D_(Um0{|huv1UVP$Ca8i29a!;`$qv+KL>WH>ot6L&A7jw@ z380CvMW7S6;OjRuKt(|V#{wn|(D)&Qv5P@z0pA0p`%@UecVJmC@o_LWC@kQxU;`Z# zpm74N|K$K)HZKc0e|`c-4wDSS_6E%fTnWhiuYV1j7wEj;wqojQ*!)1pl3R}D`vJ=b z{29zh{V!-*eE~|M2l%&u)|qFRfzm3{`5S*5tP^-{Fn?n@3mS&H!TitRe}i!WPX_Y- zWk_6ry1)#&po7}(Fzi=Qbl^%r>VGlp;m~B@aA3Lsy7nu9JAmm9%XWrD2M(n3H$eBN zC^2v_FdYC@94Xu~O`uX#!GQ~D|JNFZ-wjrv^UU8snyK3u-*Ef~?UP!7IDZ3PCV-aB zgT3>HwmH2FzjOh@6P+du#Z6*w8;y! zxmXDlaq#mubU>pSpc`XB^UF#M+!MfOuq%OgFG9*p4i+BJX;BUukR+VN%UumL3-&{(dK13##I{?A|l8c2k%-}nPwEzf=ce4gHZ2mS+$AUE5CR@6iL zUkpbbnF}252$-|*9t1PgS$GdJ7C3@M4j4lEdPiMY3mIb^!UaT+ATb#3fJ6=$g4g^r zG%ztLGY3BS(XifgEOp1_uTK1{MZJFoThW zfsup3fq?_WcZ8}rn!v;XT6f<78exSq7`s4s1UW+OI_kihz!=F8#-Q2&VK|63u%|Fa zFoZFP!rkn^47%CFj6t*k%n)PX?O=q6@HT*lu^AYSI*AxG@+dfSa2#SWXyj3J<`6i< zVsHc`A^;1+1_n@CVPIxpH~?n|2r=-0+E}f-ncmAx0?UFsl(nplKasmyGD4+&_K_fUM;Qn%ANobT%aN}TXf-(e~ zSWFsu72G@o;c60CBN)RNLK|2USRf3M1V#x)Cx*}lQ3t494Qz~#4FL%v60DdENVk#! z2L%BR5m3VyRDrQHF|sr`DhPN$mo_sn9Cc$3Y`BtOD!~e4@VYSvHr`1vmEeWh zb<~ZuurVefTtf61oWTwif#n@nH|D~II|=3zyvLvnPLN`-$N}hCI<8pEVJU3j0GWJ1 z5R$(bF!Gm66F5?YI9MRJzcVl}urVe!L?ntyxVC{931SkgZH$SH5eZ@vj17#CICXVk zEntjc2xkyI0A>U;2pnK7VDJHpK<#pMVn}F^P+;R=YyvYFKq)t&fdeGc05OKa)rm!+ zkwd|WL!b%FaN`hcVo_+20Eslf{N=>z&={Z)!Xe58Vi<(-h&r)490*Zx;Sgnj+Qq=k z7;qrOK$M5o8Hv#lq9Dq_3LXIgB{L2NCk6p<_60FO84Z-bBpTQh7-4>Qbz%aI5h!qQ zFo75g6f{7L#tsG0ofuHNTtWFO0$l#ECNM@YxG@Meut6B2FgG8C^@7aeyU(fkVKVMc@F)y%21`z~g|Nzb-JSFmOO} z97F*FtDvEaPk^up%TY!{2MB}JkijEBSVRPFjxtNe!6<{U9>K+|Ack9y=wjB415yTI zJ)#KrfO@bFObnpSEubA)3Jen%R2VrJz#>q)I9!qhB^ulm1v@|t7B|Mg4wnQ$i553S zL4-N30fHeep^TyqAO?#lBddc$fM5tqC?ir_C^~U~8d0qrV1}qds|T1NqR|)PUCq%uqGp@L>*SJOUCy#5-dV1FSB9 zGT4h4Wk9t9B77t~jtLmH^du@n7(9u}0uBd+3|e}iB@_b#gZ+R1|Cj%N`TrM_0dfk_ zSm!pk+2OYvHs^pCht481;AR-x_?#F1eB{hWFvA#`0as&P=XZbd_9t(Df*6y`pY;B; zuJAY}yz$AKO1K(ErUHjK0_rTB2bl^SWMGUr0_-B3aKE!4SI3YFS`<`MiwlUt)nJqx zunJlNQEnjDr!0_alp%pZf`N?#p1&A4SU@4~t{`{9}I>Aw2V3cu8u*)1vc*_y_^^>!3wIoeGF(fufB(O;^wjnVZ!6MLb zW^helsbG|2aBpCI0A&b1V5wjLi3q~d1|wR%46mRO`P6}t30xgJaWFvYWk?0x0IsQ_ zc7f7{L!*F#3+UWT1fu~g0X@^E4U*$v^|FI& z0*eJBAA@s)zyml#1S$dz!vF?G1_K8k0cI8k(C9NN;{fFDJO>6=21C${uL#3Y217?4 z@X?bQjsTrUfNtK$xbByhdV2vg(g;1VDx!s^N>=-?6{Ai@G-I0Ohfu(&cZ zLesnh12~3};~d^PgZPSpfx!h-3p;>nVJ=3d7L*Dak-vPIgB`A1Fg4-DWFYJUR{`wc zx`0Wh0l89umOl&(pz2r%RxhJ493eqS<$4*lI)+uywQ##It7DkRU$`1jbqueUQ5kSG zOf4)688jTYM3`DQ7P4qKa0xWEXf$$w7;rTVEG*!zpo9Pm2e>OJA;2Ob$O7&q!WDsT zvgL4K5&>Ol%K~CFv?v&Ia5%6*YcJ4>V@N@$AR@uSBna-Z%wSPsWN34McF@3PHK-t` zEn{$-Pl162l*qvH(1Zut=)&LxPJsd(3``6P4dCV|L1lG<3MT{l^7ZtK!=inw&*t)gDe(eV3@+70JDvQfvbgSA;StMEdi?*rG*SD z9JB;>wK6SeSW%=U1FZ)+7%W)W7#TqQEe;kbMzFs;*!UREfO_lDG|$1H&;q)$-ib+o zp+&`zfy0SOprMsTp@GMNNdV>^0S0yx#xo3s3^Etk4H(ZbIDr|Q4GcvLGEI;a&A|ZL zvBUwo21)=L-~tm`R1`Tl9O33LfbKzRU{MeP1q*2B39|NlQYW6QsXGW7r7hls$GFu>L5--oJ^-~Sz# z8n|Nnb^qT(!vks(Lh8RgM7#Ze`+BhLP!*u|Jk$#N{r@3S_Wz*_ENcF}hnn;6{ccoq zkn%DEBvUK3%77V2QNqFj$;+Tj?ZDv3Ai%+*0Tw~15olonCs9ydHf-Q=QW9Zk6;Nd0 zaZp02fi!BI6ht^!SQr}^927(ZSV6e}H7_ec@*qb87mNX_D4C!so&l8Zz*!i~Pyl5` zP@+|60G&btONkr|5)EpQoXgR`X8_&OsHVWt!=!{zqrspAG0B2K3UoU&X!(i|Lvake;8Y4%8gMtJHiyoK(&!^x#$NX=z{9}A;?Tfhz{8=$ zG+{!6iU9*JEObD5c>-89BdALQ3U&oXq-=`F%b@l;NHr{;kraXT+t>&MKy$7DEDOUK zh`bERo{|j=%nghVk`0g+4Qg5il_>C>0%c&y)X*#}!5{%~k^qAOBvXT(1Fmync?XuM z?d_o=FeQk*jL3}p@u<1~9_&>6_x~XbZ05X&90BYV7vI@-mbGS3{U# zz@WegO11_JPM(BSkE$3Afzc2c4S~@R7!85Z5Eu=Ckrx8s_7W3lp4bW6%pG}QFzVyc z5Eu=C(GVC7fzc2c4S|sx0tO7Ag>mxP1`G|L=`>~o1_w@JLIbLv)qr6ENFQiL0V4y$ KLBzBa)DQri=u>t8 literal 42178 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 Date: Wed, 16 Mar 2022 13:58:04 +0100 Subject: [PATCH 2/5] iso15: move FSK code up (before all functions that may use it) --- armsrc/iso15693.c | 634 +++++++++++++++++++++++----------------------- 1 file changed, 317 insertions(+), 317 deletions(-) diff --git a/armsrc/iso15693.c b/armsrc/iso15693.c index ac5c0fdaa..d36f7f790 100644 --- a/armsrc/iso15693.c +++ b/armsrc/iso15693.c @@ -647,6 +647,323 @@ static void DecodeTagInit(DecodeTag_t *tag, uint8_t *data, uint16_t max_len) { DecodeTagReset(tag); } +//============================================================================= +// An ISO 15693 decoder for tag responses in FSK (two subcarriers) mode. +// Subcarriers frequencies are 424kHz and 484kHz (fc/32 and fc/28), +// LED handling: +// LED C -> ON once we have received the SOF and are expecting the rest. +// LED C -> OFF once we have received EOF or are unsynced +// +// Returns: true if we received a EOF +// false if we are still waiting for some more +//============================================================================= +//#define DEBUG 1 +#define FREQ_IS_484(f) ((f & 1) == 1) //(f >= 26 && f <= 30) +#define FREQ_IS_424(f) ((f & 2) == 2) //(f >= 30 && f <= 34) +#define FREQ_IS_0(f) ((f & 3) == 0) // (f <= 24 || f >= 36) +#define SEOF_COUNT(c, s) ((s) ? (c >= 11 && c <= 13) : (c >= 44 && c <= 52)) +#define LOGIC_COUNT(c, s) ((s) ? (c >= 3 && c <= 6) : (c >= 13 && c <= 21)) +#define MAX_COUNT(c, s) ((s) ? (c >= 13) : (c >= 52)) +#define MIN_COUNT(c, s) ((s) ? (c <= 2) : (c <= 4)) + +typedef struct DecodeTagFSK { + enum { + STATE_FSK_ERROR, + STATE_FSK_BEFORE_SOF, + STATE_FSK_SOF_484, + STATE_FSK_SOF_424, + STATE_FSK_SOF_END_484, + STATE_FSK_SOF_END_424, + STATE_FSK_RECEIVING_DATA_484, + STATE_FSK_RECEIVING_DATA_424, + STATE_FSK_EOF + } state; + enum { + LOGIC0_PART1, + LOGIC1_PART1, + LOGIC0_PART2, + LOGIC1_PART2, + SOF + } lastBit; + uint8_t count; + uint8_t bitCount; + uint8_t shiftReg; + uint16_t len; + uint16_t max_len; + uint8_t *output; +} DecodeTagFSK_t; + +static void DecodeTagFSKReset(DecodeTagFSK_t *DecodeTag) { + DecodeTag->state = STATE_FSK_BEFORE_SOF; + DecodeTag->bitCount = 0; + DecodeTag->len = 0; + DecodeTag->shiftReg = 0; +} + +static void DecodeTagFSKInit(DecodeTagFSK_t *DecodeTag, uint8_t *data, uint16_t max_len) { + DecodeTag->output = data; + DecodeTag->max_len = max_len; + DecodeTagFSKReset(DecodeTag); +} + +// Performances of this function are crutial for stability +// as it is called in real time for every samples +static int RAMFUNC Handle15693FSKSamplesFromTag(uint8_t freq, DecodeTagFSK_t *DecodeTag, bool recv_speed) +{ + switch(DecodeTag->state) { + case STATE_FSK_BEFORE_SOF: + if (FREQ_IS_484(freq)) + { // possible SOF starting + DecodeTag->state = STATE_FSK_SOF_484; + DecodeTag->lastBit = LOGIC0_PART1; + DecodeTag->count = 1; + } + break; + + case STATE_FSK_SOF_484: + //DbpString("STATE_FSK_SOF_484"); + if (FREQ_IS_424(freq) && SEOF_COUNT(DecodeTag->count, recv_speed)) + { // SOF part1 continue at 424 + DecodeTag->state = STATE_FSK_SOF_424; + DecodeTag->count = 1; + } + else if (FREQ_IS_484(freq) && !MAX_COUNT(DecodeTag->count, recv_speed)) // still in SOF at 484 + { + DecodeTag->count++; + } + else // SOF failed, roll back + { + DecodeTag->state = STATE_FSK_BEFORE_SOF; + } + break; + + case STATE_FSK_SOF_424: + //DbpString("STATE_FSK_SOF_424"); + if (FREQ_IS_484(freq) && SEOF_COUNT(DecodeTag->count, recv_speed)) + { // SOF part 1 finished + DecodeTag->state = STATE_FSK_SOF_END_484; + DecodeTag->count = 1; + } + else if (FREQ_IS_424(freq) && !MAX_COUNT(DecodeTag->count, recv_speed)) // still in SOF at 424 + DecodeTag->count++; + else // SOF failed, roll back + { +#ifdef DEBUG + if (DEBUG) + Dbprintf("SOF_424 failed: freq=%d, count=%d, recv_speed=%d", freq, DecodeTag->count, recv_speed); +#endif + DecodeTag->state = STATE_FSK_BEFORE_SOF; + } + break; + + case STATE_FSK_SOF_END_484: + if (FREQ_IS_424(freq) && LOGIC_COUNT(DecodeTag->count, recv_speed)) + { + DecodeTag->state = STATE_FSK_SOF_END_424; + DecodeTag->count = 1; + } + else if (FREQ_IS_484(freq) && !MAX_COUNT(DecodeTag->count, recv_speed)) // still in SOF_END_484 + DecodeTag->count++; + else // SOF failed, roll back + { +#ifdef DEBUG + if (DEBUG) + Dbprintf("SOF_END_484 failed: freq=%d, count=%d, recv_speed=%d", freq, DecodeTag->count, recv_speed); +#endif + DecodeTag->state = STATE_FSK_BEFORE_SOF; + } + break; + case STATE_FSK_SOF_END_424: + if (FREQ_IS_484(freq) && LOGIC_COUNT(DecodeTag->count, recv_speed)) + { // SOF finished at 484 + DecodeTag->count = 1; + DecodeTag->lastBit = SOF; + DecodeTag->state = STATE_FSK_RECEIVING_DATA_484; + LED_C_ON(); + } + else if (FREQ_IS_424(freq) && LOGIC_COUNT(DecodeTag->count-2, recv_speed)) + { // SOF finished at 424 (wait count+2 to be sure that next freq is 424) + DecodeTag->count = 2; + DecodeTag->lastBit = SOF; + DecodeTag->state = STATE_FSK_RECEIVING_DATA_424; + LED_C_ON(); + } + else if (FREQ_IS_424(freq) && !MAX_COUNT(DecodeTag->count, recv_speed)) // still in SOF_END_424 + DecodeTag->count++; + else // SOF failed, roll back + { +#ifdef DEBUG + if (DEBUG) + Dbprintf("SOF_END_424 failed: freq=%d, count=%d, recv_speed=%d", freq, DecodeTag->count, recv_speed); +#endif + DecodeTag->state = STATE_FSK_BEFORE_SOF; + } + break; + + + case STATE_FSK_RECEIVING_DATA_424: + if (FREQ_IS_484(freq) && LOGIC_COUNT(DecodeTag->count, recv_speed)) + { + if (DecodeTag->lastBit == LOGIC1_PART1) + { // logic 1 finished, goto 484 + DecodeTag->lastBit = LOGIC1_PART2; + + DecodeTag->shiftReg >>= 1; + DecodeTag->shiftReg |= 0x80; + DecodeTag->bitCount++; + if (DecodeTag->bitCount == 8) { + DecodeTag->output[DecodeTag->len++] = DecodeTag->shiftReg; + if (DecodeTag->len > DecodeTag->max_len) { + // buffer overflow, give up + LED_C_OFF(); + return true; + } + DecodeTag->bitCount = 0; + DecodeTag->shiftReg = 0; + } + } + else + { // end of LOGIC0_PART1 + DecodeTag->lastBit = LOGIC0_PART1; + } + DecodeTag->count = 1; + DecodeTag->state = STATE_FSK_RECEIVING_DATA_484; + } + else if (FREQ_IS_424(freq) && LOGIC_COUNT(DecodeTag->count-2, recv_speed) && + DecodeTag->lastBit == LOGIC1_PART1) + { // logic 1 finished, stay in 484 + DecodeTag->lastBit = LOGIC1_PART2; + + DecodeTag->shiftReg >>= 1; + DecodeTag->shiftReg |= 0x80; + DecodeTag->bitCount++; + if (DecodeTag->bitCount == 8) { + DecodeTag->output[DecodeTag->len++] = DecodeTag->shiftReg; + if (DecodeTag->len > DecodeTag->max_len) { + // buffer overflow, give up + LED_C_OFF(); + return true; + } + DecodeTag->bitCount = 0; + DecodeTag->shiftReg = 0; + } + DecodeTag->count = 2; + } + else if (FREQ_IS_424(freq) && !MAX_COUNT(DecodeTag->count, recv_speed)) // still at 424 + DecodeTag->count++; + + else if (FREQ_IS_484(freq) && DecodeTag->lastBit == LOGIC0_PART2 && + SEOF_COUNT(DecodeTag->count, recv_speed)) + { // EOF has started +#ifdef DEBUG + if (DEBUG) + Dbprintf("RECEIVING_DATA_424->EOF: freq=%d, count=%d, recv_speed=%d, lastbit=%d, state=%d", freq, DecodeTag->count, recv_speed, DecodeTag->lastBit, DecodeTag->state); +#endif + DecodeTag->count = 1; + DecodeTag->state = STATE_FSK_EOF; + LED_C_OFF(); + } + else // error + { +#ifdef DEBUG + if (DEBUG) + Dbprintf("RECEIVING_DATA_424 error: freq=%d, count=%d, recv_speed=%d, lastbit=%d, state=%d", freq, DecodeTag->count, recv_speed, DecodeTag->lastBit, DecodeTag->state); +#endif + DecodeTag->state = STATE_FSK_ERROR; + LED_C_OFF(); + return true; + } + break; + + case STATE_FSK_RECEIVING_DATA_484: + if (FREQ_IS_424(freq) && LOGIC_COUNT(DecodeTag->count, recv_speed)) + { + if (DecodeTag->lastBit == LOGIC0_PART1) + { // logic 0 finished, goto 424 + DecodeTag->lastBit = LOGIC0_PART2; + + DecodeTag->shiftReg >>= 1; + DecodeTag->bitCount++; + if (DecodeTag->bitCount == 8) { + DecodeTag->output[DecodeTag->len++] = DecodeTag->shiftReg; + if (DecodeTag->len > DecodeTag->max_len) { + // buffer overflow, give up + LED_C_OFF(); + return true; + } + DecodeTag->bitCount = 0; + DecodeTag->shiftReg = 0; + } + } + else + { // end of LOGIC1_PART1 + DecodeTag->lastBit = LOGIC1_PART1; + } + DecodeTag->count = 1; + DecodeTag->state = STATE_FSK_RECEIVING_DATA_424; + } + else if (FREQ_IS_484(freq) && LOGIC_COUNT(DecodeTag->count-2, recv_speed) && + DecodeTag->lastBit == LOGIC0_PART1) + { // logic 0 finished, stay in 424 + DecodeTag->lastBit = LOGIC0_PART2; + + DecodeTag->shiftReg >>= 1; + DecodeTag->bitCount++; + if (DecodeTag->bitCount == 8) { + DecodeTag->output[DecodeTag->len++] = DecodeTag->shiftReg; + if (DecodeTag->len > DecodeTag->max_len) { + // buffer overflow, give up + LED_C_OFF(); + return true; + } + DecodeTag->bitCount = 0; + DecodeTag->shiftReg = 0; + } + DecodeTag->count = 2; + } + else if (FREQ_IS_484(freq) && !MAX_COUNT(DecodeTag->count, recv_speed)) // still at 484 + DecodeTag->count++; + else // error + { +#ifdef DEBUG + if (DEBUG) + Dbprintf("RECEIVING_DATA_484 error: freq=%d, count=%d, recv_speed=%d, lastbit=%d, state=%d", freq, DecodeTag->count, recv_speed, DecodeTag->lastBit, DecodeTag->state); +#endif + LED_C_OFF(); + DecodeTag->state = STATE_FSK_ERROR; + return true; + } + break; + + case STATE_FSK_EOF: + if (FREQ_IS_484(freq) && !MAX_COUNT(DecodeTag->count, recv_speed)) // still at 484 + { + DecodeTag->count++; + if (SEOF_COUNT(DecodeTag->count, recv_speed)) + return true; // end of the transmission + } + else // error + { +#ifdef DEBUG + if (DEBUG) + Dbprintf("EOF error: freq=%d, count=%d, recv_speed=%d", freq, DecodeTag->count, recv_speed); +#endif + DecodeTag->state = STATE_FSK_ERROR; + return true; + } + break; + case STATE_FSK_ERROR: + LED_C_OFF(); +#ifdef DEBUG + if (DEBUG) + Dbprintf("FSK error: freq=%d, count=%d, recv_speed=%d", freq, DecodeTag->count, recv_speed); +#endif + return true; // error + break; + } + return false; +} + /* * Receive and decode the tag response, also log to tracebuffer */ @@ -1259,323 +1576,6 @@ void AcquireRawAdcSamplesIso15693(void) { LEDsoff(); } -//============================================================================= -// An ISO 15693 decoder for tag responses in FSK (two subcarriers) mode. -// Subcarriers frequencies are 424kHz and 484kHz (fc/32 and fc/28), -// LED handling: -// LED C -> ON once we have received the SOF and are expecting the rest. -// LED C -> OFF once we have received EOF or are unsynced -// -// Returns: true if we received a EOF -// false if we are still waiting for some more -//============================================================================= -//#define DEBUG 1 -#define FREQ_IS_484(f) ((f & 1) == 1) //(f >= 26 && f <= 30) -#define FREQ_IS_424(f) ((f & 2) == 2) //(f >= 30 && f <= 34) -#define FREQ_IS_0(f) ((f & 3) == 0) // (f <= 24 || f >= 36) -#define SEOF_COUNT(c, s) ((s) ? (c >= 11 && c <= 13) : (c >= 44 && c <= 52)) -#define LOGIC_COUNT(c, s) ((s) ? (c >= 3 && c <= 6) : (c >= 13 && c <= 21)) -#define MAX_COUNT(c, s) ((s) ? (c >= 13) : (c >= 52)) -#define MIN_COUNT(c, s) ((s) ? (c <= 2) : (c <= 4)) - -typedef struct DecodeTagFSK { - enum { - STATE_FSK_ERROR, - STATE_FSK_BEFORE_SOF, - STATE_FSK_SOF_484, - STATE_FSK_SOF_424, - STATE_FSK_SOF_END_484, - STATE_FSK_SOF_END_424, - STATE_FSK_RECEIVING_DATA_484, - STATE_FSK_RECEIVING_DATA_424, - STATE_FSK_EOF - } state; - enum { - LOGIC0_PART1, - LOGIC1_PART1, - LOGIC0_PART2, - LOGIC1_PART2, - SOF - } lastBit; - uint8_t count; - uint8_t bitCount; - uint8_t shiftReg; - uint16_t len; - uint16_t max_len; - uint8_t *output; -} DecodeTagFSK_t; - -static void DecodeTagFSKReset(DecodeTagFSK_t *DecodeTag) { - DecodeTag->state = STATE_FSK_BEFORE_SOF; - DecodeTag->bitCount = 0; - DecodeTag->len = 0; - DecodeTag->shiftReg = 0; -} - -static void DecodeTagFSKInit(DecodeTagFSK_t *DecodeTag, uint8_t *data, uint16_t max_len) { - DecodeTag->output = data; - DecodeTag->max_len = max_len; - DecodeTagFSKReset(DecodeTag); -} - -// Performances of this function are crutial for stability -// as it is called in real time for every samples -static int RAMFUNC Handle15693FSKSamplesFromTag(uint8_t freq, DecodeTagFSK_t *DecodeTag, bool recv_speed) -{ - switch(DecodeTag->state) { - case STATE_FSK_BEFORE_SOF: - if (FREQ_IS_484(freq)) - { // possible SOF starting - DecodeTag->state = STATE_FSK_SOF_484; - DecodeTag->lastBit = LOGIC0_PART1; - DecodeTag->count = 1; - } - break; - - case STATE_FSK_SOF_484: - //DbpString("STATE_FSK_SOF_484"); - if (FREQ_IS_424(freq) && SEOF_COUNT(DecodeTag->count, recv_speed)) - { // SOF part1 continue at 424 - DecodeTag->state = STATE_FSK_SOF_424; - DecodeTag->count = 1; - } - else if (FREQ_IS_484(freq) && !MAX_COUNT(DecodeTag->count, recv_speed)) // still in SOF at 484 - { - DecodeTag->count++; - } - else // SOF failed, roll back - { - DecodeTag->state = STATE_FSK_BEFORE_SOF; - } - break; - - case STATE_FSK_SOF_424: - //DbpString("STATE_FSK_SOF_424"); - if (FREQ_IS_484(freq) && SEOF_COUNT(DecodeTag->count, recv_speed)) - { // SOF part 1 finished - DecodeTag->state = STATE_FSK_SOF_END_484; - DecodeTag->count = 1; - } - else if (FREQ_IS_424(freq) && !MAX_COUNT(DecodeTag->count, recv_speed)) // still in SOF at 424 - DecodeTag->count++; - else // SOF failed, roll back - { -#ifdef DEBUG - if (DEBUG) - Dbprintf("SOF_424 failed: freq=%d, count=%d, recv_speed=%d", freq, DecodeTag->count, recv_speed); -#endif - DecodeTag->state = STATE_FSK_BEFORE_SOF; - } - break; - - case STATE_FSK_SOF_END_484: - if (FREQ_IS_424(freq) && LOGIC_COUNT(DecodeTag->count, recv_speed)) - { - DecodeTag->state = STATE_FSK_SOF_END_424; - DecodeTag->count = 1; - } - else if (FREQ_IS_484(freq) && !MAX_COUNT(DecodeTag->count, recv_speed)) // still in SOF_END_484 - DecodeTag->count++; - else // SOF failed, roll back - { -#ifdef DEBUG - if (DEBUG) - Dbprintf("SOF_END_484 failed: freq=%d, count=%d, recv_speed=%d", freq, DecodeTag->count, recv_speed); -#endif - DecodeTag->state = STATE_FSK_BEFORE_SOF; - } - break; - case STATE_FSK_SOF_END_424: - if (FREQ_IS_484(freq) && LOGIC_COUNT(DecodeTag->count, recv_speed)) - { // SOF finished at 484 - DecodeTag->count = 1; - DecodeTag->lastBit = SOF; - DecodeTag->state = STATE_FSK_RECEIVING_DATA_484; - LED_C_ON(); - } - else if (FREQ_IS_424(freq) && LOGIC_COUNT(DecodeTag->count-2, recv_speed)) - { // SOF finished at 424 (wait count+2 to be sure that next freq is 424) - DecodeTag->count = 2; - DecodeTag->lastBit = SOF; - DecodeTag->state = STATE_FSK_RECEIVING_DATA_424; - LED_C_ON(); - } - else if (FREQ_IS_424(freq) && !MAX_COUNT(DecodeTag->count, recv_speed)) // still in SOF_END_424 - DecodeTag->count++; - else // SOF failed, roll back - { -#ifdef DEBUG - if (DEBUG) - Dbprintf("SOF_END_424 failed: freq=%d, count=%d, recv_speed=%d", freq, DecodeTag->count, recv_speed); -#endif - DecodeTag->state = STATE_FSK_BEFORE_SOF; - } - break; - - - case STATE_FSK_RECEIVING_DATA_424: - if (FREQ_IS_484(freq) && LOGIC_COUNT(DecodeTag->count, recv_speed)) - { - if (DecodeTag->lastBit == LOGIC1_PART1) - { // logic 1 finished, goto 484 - DecodeTag->lastBit = LOGIC1_PART2; - - DecodeTag->shiftReg >>= 1; - DecodeTag->shiftReg |= 0x80; - DecodeTag->bitCount++; - if (DecodeTag->bitCount == 8) { - DecodeTag->output[DecodeTag->len++] = DecodeTag->shiftReg; - if (DecodeTag->len > DecodeTag->max_len) { - // buffer overflow, give up - LED_C_OFF(); - return true; - } - DecodeTag->bitCount = 0; - DecodeTag->shiftReg = 0; - } - } - else - { // end of LOGIC0_PART1 - DecodeTag->lastBit = LOGIC0_PART1; - } - DecodeTag->count = 1; - DecodeTag->state = STATE_FSK_RECEIVING_DATA_484; - } - else if (FREQ_IS_424(freq) && LOGIC_COUNT(DecodeTag->count-2, recv_speed) && - DecodeTag->lastBit == LOGIC1_PART1) - { // logic 1 finished, stay in 484 - DecodeTag->lastBit = LOGIC1_PART2; - - DecodeTag->shiftReg >>= 1; - DecodeTag->shiftReg |= 0x80; - DecodeTag->bitCount++; - if (DecodeTag->bitCount == 8) { - DecodeTag->output[DecodeTag->len++] = DecodeTag->shiftReg; - if (DecodeTag->len > DecodeTag->max_len) { - // buffer overflow, give up - LED_C_OFF(); - return true; - } - DecodeTag->bitCount = 0; - DecodeTag->shiftReg = 0; - } - DecodeTag->count = 2; - } - else if (FREQ_IS_424(freq) && !MAX_COUNT(DecodeTag->count, recv_speed)) // still at 424 - DecodeTag->count++; - - else if (FREQ_IS_484(freq) && DecodeTag->lastBit == LOGIC0_PART2 && - SEOF_COUNT(DecodeTag->count, recv_speed)) - { // EOF has started -#ifdef DEBUG - if (DEBUG) - Dbprintf("RECEIVING_DATA_424->EOF: freq=%d, count=%d, recv_speed=%d, lastbit=%d, state=%d", freq, DecodeTag->count, recv_speed, DecodeTag->lastBit, DecodeTag->state); -#endif - DecodeTag->count = 1; - DecodeTag->state = STATE_FSK_EOF; - LED_C_OFF(); - } - else // error - { -#ifdef DEBUG - if (DEBUG) - Dbprintf("RECEIVING_DATA_424 error: freq=%d, count=%d, recv_speed=%d, lastbit=%d, state=%d", freq, DecodeTag->count, recv_speed, DecodeTag->lastBit, DecodeTag->state); -#endif - DecodeTag->state = STATE_FSK_ERROR; - LED_C_OFF(); - return true; - } - break; - - case STATE_FSK_RECEIVING_DATA_484: - if (FREQ_IS_424(freq) && LOGIC_COUNT(DecodeTag->count, recv_speed)) - { - if (DecodeTag->lastBit == LOGIC0_PART1) - { // logic 0 finished, goto 424 - DecodeTag->lastBit = LOGIC0_PART2; - - DecodeTag->shiftReg >>= 1; - DecodeTag->bitCount++; - if (DecodeTag->bitCount == 8) { - DecodeTag->output[DecodeTag->len++] = DecodeTag->shiftReg; - if (DecodeTag->len > DecodeTag->max_len) { - // buffer overflow, give up - LED_C_OFF(); - return true; - } - DecodeTag->bitCount = 0; - DecodeTag->shiftReg = 0; - } - } - else - { // end of LOGIC1_PART1 - DecodeTag->lastBit = LOGIC1_PART1; - } - DecodeTag->count = 1; - DecodeTag->state = STATE_FSK_RECEIVING_DATA_424; - } - else if (FREQ_IS_484(freq) && LOGIC_COUNT(DecodeTag->count-2, recv_speed) && - DecodeTag->lastBit == LOGIC0_PART1) - { // logic 0 finished, stay in 424 - DecodeTag->lastBit = LOGIC0_PART2; - - DecodeTag->shiftReg >>= 1; - DecodeTag->bitCount++; - if (DecodeTag->bitCount == 8) { - DecodeTag->output[DecodeTag->len++] = DecodeTag->shiftReg; - if (DecodeTag->len > DecodeTag->max_len) { - // buffer overflow, give up - LED_C_OFF(); - return true; - } - DecodeTag->bitCount = 0; - DecodeTag->shiftReg = 0; - } - DecodeTag->count = 2; - } - else if (FREQ_IS_484(freq) && !MAX_COUNT(DecodeTag->count, recv_speed)) // still at 484 - DecodeTag->count++; - else // error - { -#ifdef DEBUG - if (DEBUG) - Dbprintf("RECEIVING_DATA_484 error: freq=%d, count=%d, recv_speed=%d, lastbit=%d, state=%d", freq, DecodeTag->count, recv_speed, DecodeTag->lastBit, DecodeTag->state); -#endif - LED_C_OFF(); - DecodeTag->state = STATE_FSK_ERROR; - return true; - } - break; - - case STATE_FSK_EOF: - if (FREQ_IS_484(freq) && !MAX_COUNT(DecodeTag->count, recv_speed)) // still at 484 - { - DecodeTag->count++; - if (SEOF_COUNT(DecodeTag->count, recv_speed)) - return true; // end of the transmission - } - else // error - { -#ifdef DEBUG - if (DEBUG) - Dbprintf("EOF error: freq=%d, count=%d, recv_speed=%d", freq, DecodeTag->count, recv_speed); -#endif - DecodeTag->state = STATE_FSK_ERROR; - return true; - } - break; - case STATE_FSK_ERROR: - LED_C_OFF(); -#ifdef DEBUG - if (DEBUG) - Dbprintf("FSK error: freq=%d, count=%d, recv_speed=%d", freq, DecodeTag->count, recv_speed); -#endif - return true; // error - break; - } - return false; -} - void SniffIso15693(uint8_t jam_search_len, uint8_t *jam_search_string, bool iclass) { LEDsoff(); From f65ca5f14c8b5480a57f1250abf87e003f44b1fa Mon Sep 17 00:00:00 2001 From: Yann GASCUEL <34003959+lnv42@users.noreply.github.com> Date: Wed, 16 Mar 2022 15:21:28 +0100 Subject: [PATCH 3/5] iso15 sniff: add support for slow answers with single subcarrier Before this commit, slow answers was only supported for dual subcarriers answers. --- armsrc/iso15693.c | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/armsrc/iso15693.c b/armsrc/iso15693.c index d36f7f790..a23e0805c 100644 --- a/armsrc/iso15693.c +++ b/armsrc/iso15693.c @@ -410,7 +410,7 @@ typedef struct { //----------------------------------------------------------------------------- // DEMODULATE tag answer //----------------------------------------------------------------------------- -static RAMFUNC int Handle15693SamplesFromTag(uint16_t amplitude, DecodeTag_t *tag) { +static RAMFUNC int Handle15693SamplesFromTag(uint16_t amplitude, DecodeTag_t *tag, bool recv_speed) { switch (tag->state) { @@ -454,7 +454,7 @@ static RAMFUNC int Handle15693SamplesFromTag(uint16_t amplitude, DecodeTag_t *ta if (tag->posCount > 2) { tag->threshold_half += amplitude; // keep track of average high value } - if (tag->posCount == 10) { + if (tag->posCount == (recv_speed?10:40)) { tag->threshold_half >>= 2; // (4 times 1/2 average) tag->state = STATE_TAG_SOF_HIGH_END; } @@ -468,7 +468,7 @@ static RAMFUNC int Handle15693SamplesFromTag(uint16_t amplitude, DecodeTag_t *ta case STATE_TAG_SOF_HIGH_END: { // check for falling edge - if (tag->posCount == 13 && amplitude < tag->threshold_sof) { + if (tag->posCount == (recv_speed?13:52) && amplitude < tag->threshold_sof) { tag->lastBit = SOF_PART1; // detected 1st part of SOF (12 samples low and 12 samples high) tag->shiftReg = 0; tag->bitCount = 0; @@ -480,7 +480,7 @@ static RAMFUNC int Handle15693SamplesFromTag(uint16_t amplitude, DecodeTag_t *ta LED_C_ON(); } else { tag->posCount++; - if (tag->posCount > 13) { // high phase too long + if (tag->posCount > (recv_speed?13:52)) { // high phase too long tag->posCount = 0; tag->previous_amplitude = amplitude; tag->state = STATE_TAG_SOF_LOW; @@ -496,13 +496,13 @@ static RAMFUNC int Handle15693SamplesFromTag(uint16_t amplitude, DecodeTag_t *ta tag->sum2 = 0; } - if (tag->posCount <= 4) { + if (tag->posCount <= (recv_speed?4:16)) { tag->sum1 += amplitude; } else { tag->sum2 += amplitude; } - if (tag->posCount == 8) { + if (tag->posCount == (recv_speed?8:32)) { if (tag->sum1 > tag->threshold_half && tag->sum2 > tag->threshold_half) { // modulation in both halves if (tag->lastBit == LOGIC0) { // this was already part of EOF tag->state = STATE_TAG_EOF; @@ -583,13 +583,13 @@ static RAMFUNC int Handle15693SamplesFromTag(uint16_t amplitude, DecodeTag_t *ta tag->sum2 = 0; } - if (tag->posCount <= 4) { + if (tag->posCount <= (recv_speed?4:16)) { tag->sum1 += amplitude; } else { tag->sum2 += amplitude; } - if (tag->posCount == 8) { + if (tag->posCount == (recv_speed?8:32)) { if (tag->sum1 > tag->threshold_half && tag->sum2 < tag->threshold_half) { // modulation in first half tag->posCount = 0; tag->state = STATE_TAG_EOF_TAIL; @@ -610,13 +610,13 @@ static RAMFUNC int Handle15693SamplesFromTag(uint16_t amplitude, DecodeTag_t *ta tag->sum2 = 0; } - if (tag->posCount <= 4) { + if (tag->posCount <= (recv_speed?4:16)) { tag->sum1 += amplitude; } else { tag->sum2 += amplitude; } - if (tag->posCount == 8) { + if (tag->posCount == (recv_speed?8:32)) { if (tag->sum1 < tag->threshold_half && tag->sum2 < tag->threshold_half) { // no modulation in both halves LED_C_OFF(); return true; @@ -1036,7 +1036,7 @@ int GetIso15693AnswerFromTag(uint8_t *response, uint16_t max_len, uint16_t timeo } } - if (Handle15693SamplesFromTag(tagdata, dt)) { + if (Handle15693SamplesFromTag(tagdata, dt, true)) { *eof_time = dma_start_time + (samples * 16) - DELAY_TAG_TO_ARM; // end of EOF @@ -1730,7 +1730,7 @@ void SniffIso15693(uint8_t jam_search_len, uint8_t *jam_search_string, bool icla if (!expect_fsk_answer) { - if (Handle15693SamplesFromTag((sniffdata >> 4) << 2, &dtag)) { + if (Handle15693SamplesFromTag((sniffdata >> 4) << 2, &dtag, expect_fast_answer)) { uint32_t eof_time = dma_start_time + (samples * 16) - DELAY_TAG_TO_ARM_SNIFF; // end of EOF if (dtag.lastBit == SOF_PART2) { @@ -1786,7 +1786,6 @@ void SniffIso15693(uint8_t jam_search_len, uint8_t *jam_search_string, bool icla } } } - } FpgaDisableTracing(); From abb840558f2cc3b89536cf9e4d6663681c1fa112 Mon Sep 17 00:00:00 2001 From: Yann GASCUEL <34003959+lnv42@users.noreply.github.com> Date: Wed, 16 Mar 2022 15:45:18 +0100 Subject: [PATCH 4/5] iso15: add support for slow and 2SC to GetIso15693AnswerFromTag() Allow running raw iso15 cmd expecting slow and/or dual subcarriers answers. E.g. sending slow 2SC inventory ("hf 15 raw -c -d 250100") is now working. --- armsrc/iclass.c | 18 +++---- armsrc/iso15693.c | 133 +++++++++++++++++++++++++++++++++------------- armsrc/iso15693.h | 4 +- 3 files changed, 107 insertions(+), 48 deletions(-) diff --git a/armsrc/iclass.c b/armsrc/iclass.c index e261efc59..cabec62ca 100644 --- a/armsrc/iclass.c +++ b/armsrc/iclass.c @@ -1264,7 +1264,7 @@ static bool iclass_send_cmd_with_retries(uint8_t *cmd, size_t cmdsize, uint8_t * return true; } - if (expected_size == GetIso15693AnswerFromTag(resp, max_resp_size, timeout, eof_time)) { + if (expected_size == GetIso15693AnswerFromTag(resp, max_resp_size, timeout, eof_time, false, true)) { return true; } } @@ -1296,7 +1296,7 @@ static bool select_iclass_tag_ex(picopass_hdr_t *hdr, bool use_credit_key, uint3 // wakeup uint32_t start_time = GetCountSspClk(); iclass_send_as_reader(act_all, 1, &start_time, eof_time); - int len = GetIso15693AnswerFromTag(resp, sizeof(resp), ICLASS_READER_TIMEOUT_ACTALL, eof_time); + int len = GetIso15693AnswerFromTag(resp, sizeof(resp), ICLASS_READER_TIMEOUT_ACTALL, eof_time, false, true); if (len < 0) return false; @@ -1305,7 +1305,7 @@ static bool select_iclass_tag_ex(picopass_hdr_t *hdr, bool use_credit_key, uint3 iclass_send_as_reader(identify, 1, &start_time, eof_time); // expect a 10-byte response here, 8 byte anticollision-CSN and 2 byte CRC - len = GetIso15693AnswerFromTag(resp, sizeof(resp), ICLASS_READER_TIMEOUT_OTHERS, eof_time); + len = GetIso15693AnswerFromTag(resp, sizeof(resp), ICLASS_READER_TIMEOUT_OTHERS, eof_time, false, true); if (len != 10) return false; @@ -1317,7 +1317,7 @@ static bool select_iclass_tag_ex(picopass_hdr_t *hdr, bool use_credit_key, uint3 iclass_send_as_reader(select, sizeof(select), &start_time, eof_time); // expect a 10-byte response here, 8 byte CSN and 2 byte CRC - len = GetIso15693AnswerFromTag(resp, sizeof(resp), ICLASS_READER_TIMEOUT_OTHERS, eof_time); + len = GetIso15693AnswerFromTag(resp, sizeof(resp), ICLASS_READER_TIMEOUT_OTHERS, eof_time, false, true); if (len != 10) return false; @@ -1329,7 +1329,7 @@ static bool select_iclass_tag_ex(picopass_hdr_t *hdr, bool use_credit_key, uint3 iclass_send_as_reader(read_conf, sizeof(read_conf), &start_time, eof_time); // expect a 8-byte response here - len = GetIso15693AnswerFromTag(resp, sizeof(resp), ICLASS_READER_TIMEOUT_OTHERS, eof_time); + len = GetIso15693AnswerFromTag(resp, sizeof(resp), ICLASS_READER_TIMEOUT_OTHERS, eof_time, false, true); if (len != 10) return false; @@ -1347,7 +1347,7 @@ static bool select_iclass_tag_ex(picopass_hdr_t *hdr, bool use_credit_key, uint3 iclass_send_as_reader(read_aia, sizeof(read_aia), &start_time, eof_time); // expect a 10-byte response here - len = GetIso15693AnswerFromTag(resp, sizeof(resp), ICLASS_READER_TIMEOUT_OTHERS, eof_time); + len = GetIso15693AnswerFromTag(resp, sizeof(resp), ICLASS_READER_TIMEOUT_OTHERS, eof_time, false, true); if (len != 10) return false; @@ -1361,7 +1361,7 @@ static bool select_iclass_tag_ex(picopass_hdr_t *hdr, bool use_credit_key, uint3 iclass_send_as_reader(read_check_cc, sizeof(read_check_cc), &start_time, eof_time); // expect a 8-byte response here - len = GetIso15693AnswerFromTag(resp, sizeof(resp), ICLASS_READER_TIMEOUT_OTHERS, eof_time); + len = GetIso15693AnswerFromTag(resp, sizeof(resp), ICLASS_READER_TIMEOUT_OTHERS, eof_time, false, true); if (len != 8) return false; @@ -1383,7 +1383,7 @@ static bool select_iclass_tag_ex(picopass_hdr_t *hdr, bool use_credit_key, uint3 iclass_send_as_reader(read_aia, sizeof(read_aia), &start_time, eof_time); // expect a 10-byte response here - len = GetIso15693AnswerFromTag(resp, sizeof(resp), ICLASS_READER_TIMEOUT_OTHERS, eof_time); + len = GetIso15693AnswerFromTag(resp, sizeof(resp), ICLASS_READER_TIMEOUT_OTHERS, eof_time, false, true); if (len != 10) return false; @@ -1870,7 +1870,7 @@ void iClass_WriteBlock(uint8_t *msg) { return; } else { - if (GetIso15693AnswerFromTag(resp, sizeof(resp), ICLASS_READER_TIMEOUT_UPDATE, &eof_time) == 10) { + if (GetIso15693AnswerFromTag(resp, sizeof(resp), ICLASS_READER_TIMEOUT_UPDATE, &eof_time, false, true) == 10) { res = true; break; } diff --git a/armsrc/iso15693.c b/armsrc/iso15693.c index a23e0805c..fd22ce953 100644 --- a/armsrc/iso15693.c +++ b/armsrc/iso15693.c @@ -967,20 +967,27 @@ static int RAMFUNC Handle15693FSKSamplesFromTag(uint8_t freq, DecodeTagFSK_t *De /* * Receive and decode the tag response, also log to tracebuffer */ -int GetIso15693AnswerFromTag(uint8_t *response, uint16_t max_len, uint16_t timeout, uint32_t *eof_time) { +int GetIso15693AnswerFromTag(uint8_t *response, uint16_t max_len, uint16_t timeout, uint32_t *eof_time, bool fsk, bool recv_speed) { int samples = 0, ret = 0; // the Decoder data structure DecodeTag_t dtm = { 0 }; DecodeTag_t *dt = &dtm; - DecodeTagInit(dt, response, max_len); + + DecodeTagFSK_t dtfm = { 0 }; + DecodeTagFSK_t *dtf = &dtfm; + + if (!fsk) + DecodeTagInit(dt, response, max_len); + else + DecodeTagFSKInit(dtf, response, max_len); // wait for last transfer to complete while (!(AT91C_BASE_SSC->SSC_SR & AT91C_SSC_TXEMPTY)); // And put the FPGA in the appropriate mode - FpgaWriteConfWord(FPGA_MAJOR_MODE_HF_READER | FPGA_HF_READER_SUBCARRIER_424_KHZ | FPGA_HF_READER_MODE_RECEIVE_AMPLITUDE); + FpgaWriteConfWord(FPGA_MAJOR_MODE_HF_READER | FPGA_HF_READER_2SUBCARRIERS_424_484_KHZ | FPGA_HF_READER_MODE_RECEIVE_AMPLITUDE); // Setup and start DMA. FpgaSetupSsc(FPGA_MAJOR_MODE_HF_READER); @@ -1036,56 +1043,102 @@ int GetIso15693AnswerFromTag(uint8_t *response, uint16_t max_len, uint16_t timeo } } - if (Handle15693SamplesFromTag(tagdata, dt, true)) { + if (!fsk) { + if (Handle15693SamplesFromTag(tagdata & 0x3FFF, dt, recv_speed)) { - *eof_time = dma_start_time + (samples * 16) - DELAY_TAG_TO_ARM; // end of EOF + *eof_time = dma_start_time + (samples * 16) - DELAY_TAG_TO_ARM; // end of EOF - if (dt->lastBit == SOF_PART2) { - *eof_time -= (8 * 16); // needed 8 additional samples to confirm single SOF (iCLASS) + if (dt->lastBit == SOF_PART2) { + *eof_time -= (8 * 16); // needed 8 additional samples to confirm single SOF (iCLASS) + } + if (dt->len > dt->max_len) { + ret = -2; // buffer overflow + Dbprintf("overflow (%d > %d", dt->len, dt->max_len); + } + break; } - if (dt->len > dt->max_len) { - ret = -2; // buffer overflow - Dbprintf("overflow (%d > %d", dt->len, dt->max_len); + + // timeout + if (samples > timeout && dt->state < STATE_TAG_RECEIVING_DATA) { + ret = -3; + break; } - break; } + else { + if (Handle15693FSKSamplesFromTag(tagdata >> 14, dtf, recv_speed)) { - // timeout - if (samples > timeout && dt->state < STATE_TAG_RECEIVING_DATA) { - ret = -3; - break; + *eof_time = dma_start_time + (samples * 16) - DELAY_TAG_TO_ARM; // end of EOF + + if (dtf->lastBit == SOF) { + *eof_time -= (8 * 16); // needed 8 additional samples to confirm single SOF (iCLASS) + } + if (dtf->len > dtf->max_len) { + ret = -2; // buffer overflow + Dbprintf("overflow (%d > %d", dtf->len, dtf->max_len); + } + break; + } + + // timeout + if (samples > timeout && dtf->state < STATE_FSK_RECEIVING_DATA_484) { + ret = -3; + break; + } } - } FpgaDisableSscDma(); FpgaDisableTracing(); - uint32_t sof_time = *eof_time - - (dt->len * 8 * 8 * 16) // time for byte transfers - - (32 * 16) // time for SOF transfer - - (dt->lastBit != SOF_PART2 ? (32 * 16) : 0); // time for EOF transfer + uint32_t sof_time = *eof_time - (32 * 16); // time for SOF transfer - if (g_dbglevel >= DBG_EXTENDED) { - Dbprintf("samples = %d, ret = %d, Decoder: state = %d, lastBit = %d, len = %d, bitCount = %d, posCount = %d, maxlen = %u", - samples, - ret, - dt->state, - dt->lastBit, - dt->len, - dt->bitCount, - dt->posCount, - dt->max_len + if (!fsk) { + sof_time -= (dt->len * 8 * 8 * 16) // time for byte transfers + + (dt->lastBit != SOF_PART2 ? (32 * 16) : 0); // time for EOF transfer + + if (g_dbglevel >= DBG_EXTENDED) { + Dbprintf("samples = %d, ret = %d, Decoder: state = %d, lastBit = %d, len = %d, bitCount = %d, posCount = %d, maxlen = %u", + samples, + ret, + dt->state, + dt->lastBit, + dt->len, + dt->bitCount, + dt->posCount, + dt->max_len ); - Dbprintf("timing: sof_time = %d, eof_time = %d", (sof_time * 4), (*eof_time * 4)); + Dbprintf("timing: sof_time = %d, eof_time = %d", (sof_time * 4), (*eof_time * 4)); + } + } + else { + sof_time -= (dtf->len * 8 * 8 * 16) // time for byte transfers + + (dtf->lastBit != SOF ? (32 * 16) : 0); // time for EOF transfer + + if (g_dbglevel >= DBG_EXTENDED) { + Dbprintf("samples = %d, ret = %d, FSK Decoder: state = %d, lastBit = %d, len = %d, bitCount = %d, count = %d, maxlen = %u", + samples, + ret, + dtf->state, + dtf->lastBit, + dtf->len, + dtf->bitCount, + dtf->count, + dtf->max_len + ); + Dbprintf("timing: sof_time = %d, eof_time = %d", (sof_time * 4), (*eof_time * 4)); + } } if (ret < 0) { return ret; } - LogTrace_ISO15693(dt->output, dt->len, (sof_time * 4), (*eof_time * 4), NULL, false); - return dt->len; + if (!fsk) { + LogTrace_ISO15693(dt->output, dt->len, (sof_time * 4), (*eof_time * 4), NULL, false); + return dt->len; + } + LogTrace_ISO15693(dtf->output, dtf->len, (sof_time * 4), (*eof_time * 4), NULL, false); + return dtf->len; } @@ -1869,6 +1922,9 @@ int SendDataTag(uint8_t *send, int sendlen, bool init, bool speed_fast, uint8_t start_time = GetCountSspClk(); } + bool fsk = send[0] & ISO15_REQ_SUBCARRIER_TWO; + bool recv_speed = send[0] & ISO15_REQ_DATARATE_HIGH; + if (speed_fast) { // high speed (1 out of 4) CodeIso15693AsReader(send, sendlen); @@ -1889,13 +1945,13 @@ int SendDataTag(uint8_t *send, int sendlen, bool init, bool speed_fast, uint8_t *eof_time = start_time + 32 * ((8 * ts->max) - 4); // subtract the 4 padding bits after EOF LogTrace_ISO15693(send, sendlen, (start_time * 4), (*eof_time * 4), NULL, true); if (recv != NULL) { - res = GetIso15693AnswerFromTag(recv, max_recv_len, timeout, eof_time); + res = GetIso15693AnswerFromTag(recv, max_recv_len, timeout, eof_time, fsk, recv_speed); } } return res; } -int SendDataTagEOF(uint8_t *recv, uint16_t max_recv_len, uint32_t start_time, uint16_t timeout, uint32_t *eof_time) { +int SendDataTagEOF(uint8_t *recv, uint16_t max_recv_len, uint32_t start_time, uint16_t timeout, uint32_t *eof_time, bool fsk, bool recv_speed) { CodeIso15693AsReaderEOF(); tosend_t *ts = get_tosend(); @@ -1905,7 +1961,7 @@ int SendDataTagEOF(uint8_t *recv, uint16_t max_recv_len, uint32_t start_time, ui int res = 0; if (recv != NULL) { - res = GetIso15693AnswerFromTag(recv, max_recv_len, timeout, eof_time); + res = GetIso15693AnswerFromTag(recv, max_recv_len, timeout, eof_time, fsk, recv_speed); } return res; } @@ -2335,10 +2391,13 @@ void DirectTag15693Command(uint32_t datalen, uint32_t speed, uint32_t recv, uint reply_mix(CMD_ACK, recvlen, 0, 0, NULL, 0); } else { + bool fsk = data[0] & ISO15_REQ_SUBCARRIER_TWO; + bool recv_speed = data[0] & ISO15_REQ_DATARATE_HIGH; + // send a single EOF to get the tag response if (request_answer) { start_time = eof_time + DELAY_ISO15693_VICC_TO_VCD_READER; - recvlen = SendDataTagEOF((recv ? recvbuf : NULL), sizeof(recvbuf), start_time, ISO15693_READER_TIMEOUT, &eof_time); + recvlen = SendDataTagEOF((recv ? recvbuf : NULL), sizeof(recvbuf), start_time, ISO15693_READER_TIMEOUT, &eof_time, fsk, recv_speed); } if (recv) { diff --git a/armsrc/iso15693.h b/armsrc/iso15693.h index 31ca6e6c1..dbb3ff28b 100644 --- a/armsrc/iso15693.h +++ b/armsrc/iso15693.h @@ -41,7 +41,7 @@ void CodeIso15693AsTag(const uint8_t *cmd, size_t len); void TransmitTo15693Reader(const uint8_t *cmd, size_t len, uint32_t *start_time, uint32_t slot_time, bool slow); int GetIso15693CommandFromReader(uint8_t *received, size_t max_len, uint32_t *eof_time); void TransmitTo15693Tag(const uint8_t *cmd, int len, uint32_t *start_time); -int GetIso15693AnswerFromTag(uint8_t *response, uint16_t max_len, uint16_t timeout, uint32_t *eof_time); +int GetIso15693AnswerFromTag(uint8_t *response, uint16_t max_len, uint16_t timeout, uint32_t *eof_time, bool fsk, bool recv_speed); //void RecordRawAdcSamplesIso15693(void); void AcquireRawAdcSamplesIso15693(void); @@ -55,7 +55,7 @@ void SniffIso15693(uint8_t jam_search_len, uint8_t *jam_search_string, bool icla int SendDataTag(uint8_t *send, int sendlen, bool init, bool speed_fast, uint8_t *recv, uint16_t max_recv_len, uint32_t start_time, uint16_t timeout, uint32_t *eof_time); -int SendDataTagEOF(uint8_t *recv, uint16_t max_recv_len, uint32_t start_time, uint16_t timeout, uint32_t *eof_time); +int SendDataTagEOF(uint8_t *recv, uint16_t max_recv_len, uint32_t start_time, uint16_t timeout, uint32_t *eof_time, bool fsk, bool recv_speed); void SetTag15693Uid(const uint8_t *uid); From d1e186dbe9249cf9c2b0d05f16e0b63eaa758e12 Mon Sep 17 00:00:00 2001 From: Yann GASCUEL <34003959+lnv42@users.noreply.github.com> Date: Wed, 16 Mar 2022 17:36:37 +0100 Subject: [PATCH 5/5] iso15: fix 2SC (FSK) slow decoding Previous decoding "count" constants have too large scale that may bug with some answer. With those new constants, this bug can't happend anymore. --- armsrc/iso15693.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/armsrc/iso15693.c b/armsrc/iso15693.c index fd22ce953..adba61be6 100644 --- a/armsrc/iso15693.c +++ b/armsrc/iso15693.c @@ -661,10 +661,9 @@ static void DecodeTagInit(DecodeTag_t *tag, uint8_t *data, uint16_t max_len) { #define FREQ_IS_484(f) ((f & 1) == 1) //(f >= 26 && f <= 30) #define FREQ_IS_424(f) ((f & 2) == 2) //(f >= 30 && f <= 34) #define FREQ_IS_0(f) ((f & 3) == 0) // (f <= 24 || f >= 36) -#define SEOF_COUNT(c, s) ((s) ? (c >= 11 && c <= 13) : (c >= 44 && c <= 52)) -#define LOGIC_COUNT(c, s) ((s) ? (c >= 3 && c <= 6) : (c >= 13 && c <= 21)) +#define SEOF_COUNT(c, s) ((s) ? (c >= 11 && c <= 13) : (c >= 45 && c <= 51)) +#define LOGIC_COUNT(c, s) ((s) ? (c >= 3 && c <= 6) : (c >= 14 && c <= 20)) #define MAX_COUNT(c, s) ((s) ? (c >= 13) : (c >= 52)) -#define MIN_COUNT(c, s) ((s) ? (c <= 2) : (c <= 4)) typedef struct DecodeTagFSK { enum {