From 749abc80c41abe3939ab1e313ee98d480d4a019c Mon Sep 17 00:00:00 2001 From: Michael Coracin Date: Mon, 6 Jun 2016 12:11:45 +0200 Subject: [PATCH] v4.0.0 * HAL: Added "Listen-Before-Talk" support for Semtech SX1301AP2 Ref Design. A description of the feature implementation can be found in libloragw/readme.md. * HAL: Updated FSK RSSI calculation for better linearization * util_lbt_test: New utility provided for basic "Listen-Before-Talk" testing. * util_tx_test: Extended to configure and test "LBT" through the HAL. * Added a reset_lgw.sh script to be used with IoT Starter Kit (v1.0) to reset the concentrator through the HOST GPIO pin. --- Makefile | 2 + VERSION | 2 +- .../SX1301_FPGA_125K_NOTCH_LBT_bitmap_v27.bin | Bin 0 -> 32299 bytes ...GA_125K_NOTCH_SPECTRAL_SCAN_bitmap_v27.bin | Bin 0 -> 32299 bytes fpga/readme.md | 63 + libloragw/Makefile | 3 +- libloragw/inc/loragw_aux.h | 16 +- .../inc/loragw_fpga.h | 66 +- libloragw/inc/loragw_gps.h | 97 +- libloragw/inc/loragw_hal.h | 315 +- libloragw/inc/loragw_lbt.h | 68 + libloragw/inc/loragw_radio.h | 45 + libloragw/inc/loragw_reg.h | 37 +- libloragw/inc/loragw_spi.h | 22 +- libloragw/inc/loragw_sx125x.h | 49 + libloragw/inc/loragw_sx1272_fsk.h | 113 + libloragw/inc/loragw_sx1272_lora.h | 89 + libloragw/inc/loragw_sx1276_fsk.h | 112 + libloragw/inc/loragw_sx1276_lora.h | 94 + libloragw/library.cfg | 1 + libloragw/readme.md | 115 +- libloragw/src/loragw_aux.c | 44 +- .../src/loragw_fpga.c | 256 +- libloragw/src/loragw_gps.c | 830 ++--- libloragw/src/loragw_hal.c | 2922 ++++++++--------- libloragw/src/loragw_lbt.c | 345 ++ libloragw/src/loragw_radio.c | 653 ++++ libloragw/src/loragw_reg.c | 1256 +++---- libloragw/tst/test_loragw_cal.c | 1331 ++++---- libloragw/tst/test_loragw_gps.c | 248 +- libloragw/tst/test_loragw_hal.c | 694 ++-- libloragw/tst/test_loragw_reg.c | 204 +- libloragw/tst/test_loragw_spi.c | 86 +- readme.md | 31 +- reset_lgw.sh | 55 + util_lbt_test/Makefile | 65 + util_lbt_test/readme.md | 48 + util_lbt_test/src/util_lbt_test.c | 293 ++ util_pkt_logger/Makefile | 3 +- util_pkt_logger/global_conf.json | 112 - util_pkt_logger/src/util_pkt_logger.c | 1048 +++--- util_spectral_scan/Makefile | 34 +- util_spectral_scan/inc/loragw_fpga_aux.h | 34 - util_spectral_scan/inc/loragw_fpga_spi.h | 99 - util_spectral_scan/readme.md | 6 +- util_spectral_scan/src/loragw_fpga_aux.c | 61 - util_spectral_scan/src/loragw_fpga_spi.c | 352 -- util_spectral_scan/src/util_spectral_scan.c | 114 +- util_spi_stress/Makefile | 2 +- util_spi_stress/src/util_spi_stress.c | 438 +-- util_tx_continuous/Makefile | 2 +- util_tx_continuous/src/util_tx_continuous.c | 6 - util_tx_test/Makefile | 2 +- util_tx_test/src/util_tx_test.c | 202 +- 54 files changed, 7360 insertions(+), 5825 deletions(-) create mode 100644 fpga/SX1301_FPGA_125K_NOTCH_LBT_bitmap_v27.bin create mode 100644 fpga/SX1301_FPGA_125K_NOTCH_SPECTRAL_SCAN_bitmap_v27.bin create mode 100644 fpga/readme.md rename util_spectral_scan/inc/loragw_fpga_reg.h => libloragw/inc/loragw_fpga.h (72%) create mode 100644 libloragw/inc/loragw_lbt.h create mode 100644 libloragw/inc/loragw_radio.h create mode 100644 libloragw/inc/loragw_sx125x.h create mode 100644 libloragw/inc/loragw_sx1272_fsk.h create mode 100644 libloragw/inc/loragw_sx1272_lora.h create mode 100644 libloragw/inc/loragw_sx1276_fsk.h create mode 100644 libloragw/inc/loragw_sx1276_lora.h rename util_spectral_scan/src/loragw_fpga_reg.c => libloragw/src/loragw_fpga.c (50%) create mode 100644 libloragw/src/loragw_lbt.c create mode 100644 libloragw/src/loragw_radio.c create mode 100755 reset_lgw.sh create mode 100644 util_lbt_test/Makefile create mode 100644 util_lbt_test/readme.md create mode 100644 util_lbt_test/src/util_lbt_test.c delete mode 100644 util_spectral_scan/inc/loragw_fpga_aux.h delete mode 100644 util_spectral_scan/inc/loragw_fpga_spi.h delete mode 100644 util_spectral_scan/src/loragw_fpga_aux.c delete mode 100644 util_spectral_scan/src/loragw_fpga_spi.c diff --git a/Makefile b/Makefile index c9bc6371..5022ac6b 100644 --- a/Makefile +++ b/Makefile @@ -10,6 +10,7 @@ all: $(MAKE) all -e -C libloragw $(MAKE) all -e -C util_pkt_logger $(MAKE) all -e -C util_spi_stress + $(MAKE) all -e -C util_lbt_test $(MAKE) all -e -C util_tx_test $(MAKE) all -e -C util_tx_continuous $(MAKE) all -e -C util_spectral_scan @@ -18,6 +19,7 @@ clean: $(MAKE) clean -e -C libloragw $(MAKE) clean -e -C util_pkt_logger $(MAKE) clean -e -C util_spi_stress + $(MAKE) clean -e -C util_lbt_test $(MAKE) clean -e -C util_tx_test $(MAKE) clean -e -C util_tx_continuous $(MAKE) clean -e -C util_spectral_scan diff --git a/VERSION b/VERSION index e4604e3a..fcdb2e10 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.2.1 +4.0.0 diff --git a/fpga/SX1301_FPGA_125K_NOTCH_LBT_bitmap_v27.bin b/fpga/SX1301_FPGA_125K_NOTCH_LBT_bitmap_v27.bin new file mode 100644 index 0000000000000000000000000000000000000000..f96b4998c0d68364ac0c42d863261e759392fe47 GIT binary patch literal 32299 zcmeIaeVi1<@jqTYGrK#t_cY*whaNZ_2QJ5ni=J@G0ewIe4dUkGsfdURA_0SXKBJ=c z9$>%+SRyJY;u1g+VmPBw6G7)d1rgy$d=_DS#79`71VohYyL$Ex^7MWH4`qrgeDC7$;H=>x#R@*gjlbWdd8}Hp3tW%b~43BUvRdq_Q*eP_Nsu$g|Y~C$DBN6x& zxnsmnucA}~Q5zD1{ynIG`u9XWh5aK5<)L)$p_Tu*tcZ*s{jigxwETOnD!F!Ox%kJZ zN3&4+%9nq`lFU}h|6bw8@nqw~{(GG=r`%xwn@kUJp?Kg4k6BlK9&hcT{fas+pE93O z<}Vs0*9ukK^oXh`o+Z*4B9V#LqA9eZ30ErL6v4HGROreZC}B`=hvPDy#uA;nvrZ_R z{(Mh7NUnH$iDbN>QKrKKbl&GGK+%9qToPsTr!dQbuYZnb;Vyf>sGNXK2klw^a1;nY z`(XQ>kg|9lSEM_7#BKpuN#y@AKQ)vL*V`bu1z?%=%(A3W*JNpN)?qK66v|FuckcO} z-+=Nb<9Oh9$&L5KF&ZPxk73|+(N9w><@&*z1LPjuPqTEW=^WG&9m53{H=Vz*hX)}f zrL#FoqCU*hrFR}3F404$b8vOhSjV~yl2eSb574DVDKzLau3O>7e8bppB%6dR|4q!* zb<~dKfz#3b=QuL$ntKCMH`oye_uj($DHOpV$n;sNrdrDqUTHBu2K=H5Si%_dI=%>~rSOymY$AgN1zTDIZVCSn#SX*lAx5x5&`XC(YOt~Jw# z5(PTk`%E(#5p&dbSGVMaKUOGQe146t^2LyC2f~mmdKV8SGtusK#sLClBPbyTKE26T z1SK>}LlugB`!Vo{wRefjbdco|1umC1r-$e&T<>_z}m)5X!-8AY~hI|F*p;kmS6TC@+7BDge)YznA(+(3d6Ja=G~D#V&#vl)^3}T_X0XK8^lor3t zP;X{wi=!^|z*217&2)fwPvq;E_%gmRl$x4lY}QTBphIaCLj*vzmX$u}`a%m!BhjB( zn#p%3&4phkZroF*DZ~0|49d2~WXsS4w`W~nh_-G+i?f3K()V(?Mq2Db>w4Ta9t(!*UeUja56D#`dPD617; zlK-ryd4HBbE5H$%-jCFqaH1RgNk)uZQR};BiRrPmUOpIh#LgY$Qe)$w*SM?VKmpSe zJ#y((3?$L-bH9pf=ExQGQQY5s?Ek#ZRIzoh)i3gu0RWb~4Cc&H_%npF^rArnHny2bMJsd=yjz6<13G zi*a?Rq_Cbt#fKet#u?2pFqz2;=(OQE6`-K;EjfzVy?-?weu-yVW)qi-$#LQ#3>;6l z`x7t(cUaP(q1EiBDbZ{&xR9m6^}dvaVSkSQqCU${0*qjncv`~3;)@B% zFm>5hyg0@9xOf=x(oFQZBWLl1L0y;C8Ph5Jmrv`0WCaVRxJ*d5hr24oQX!}7Dp7%D z#?O&?B~;k|b&sgaldr}677oFl7?R^ko3V%meolzYM@O3`hJLERA68%L!N4ZTk5&v+8I($@WVfDspd-OMrJ3 z*%-J%)`{_)Um8T$V>!!%Cr|l;TpCoBi$qthSiAv`1Uv0y`)e2%f|QikVc%E7;o9Ra z`_Mgm;mCj>*mazm{Y81gl6uq6M{4zT`WT_pp0{`P|* z7-{H_6AXvw$oW;er9SlR&8&#doBRh+H0JMv1f9iImt}?MHsJFmwIEPLlY2KC=)&8l z9|X&qV|RWa?ZEKm>C-6|_+;d_=jfKXp-QZnrgv;>yfhPuFY6J)^7eJDhp{f%wz-xm z1N7Gk&4^&+1$>rJLXT~)V4yclfi5A*xp(?fzIAu?JxtR1jZ_O|=t2ZKv-{kAqhs*4 zu4iu6fkN2Vy^@lUa^1Zxgyq$--4HhZaUbZ?8CatS?$BFD|1can@~m*3PbtNzPktWn zLt?=@6B@0uhw0T15*P+e37 zRhxv{!jdS_e*HB>#~t%4_$77w`70kwP%+dvL=D~L2q|@y4DtO4X;>w9xu>IL&F;5+ zznuFFvf>1hY*{E!9=X{PDlf@?S>|3ld9&sqW@I_h2H%_$q1^l3610`f>wG>{CQj{h z=Sje|fEYJ*eXi^obnc6mIV3Rm1olf3ulP}&@-;!ZY_WI&m3Pf9ktb1J;1SlPx&04y ziWL*j`*1&KGvcwfu6_oW3zbFDP^W#}8yyksJbrV+Z}`UaU{@hO@T<7?ZVpRyYCeT{ zS3rH{_x4)~#|;PTMDeE_f&qpYBUaSG9tCYL%c9=p{55bQ7o|Tw0sUZcNOMI)*XDf^ zEDPP!x!PxDxyF(`I%6A@v6lH&zIL1*!R$%0U&7WmX5x&@%lqayawU2{xI&6in}-mk zlF%h&>Gu3oCL~q-@nYOe_cvG-G?;4#zhj~A>Azwr)5i|?bMrh}V0w~gMT^#{{e+j9 zCFNC}uE;v|*xf8;4D}D;l{9eN(i}@936K);a{6#`3y_0PVyOh89(0LyJdu^C01!LP zi#FFfx&jvNabdq$A}W&b+=P6ry^kRC)P7hY9h*+5ira3t(PxqLTeAIF1O3nZK;|%( zkxffl>WJRQ#VljZ#uqFKa=fGyo7gW4ZpZ9Vs3vR*tgqwH88SWCH?h%`Km!nh`BuLy z!8Zn>*B%sVT-SUd^2AghJFD;O0xM+EPOxO~=z}LWVZ)~<9S!VH!+n+kdNS%z7#7zj z1w8Qf6`3^RrG_vKdiCzXFcQ(cDa&6+A?Q9EAr5*_Zl-X39l4s2y=T48QqX?h>RMvt zRgxcsj1j^*?kg5kUDo8JBV?vEM_Sg^1pbUY$Mb8JFwBPdR(zrM}mV5;8bI)FJ*DwaF()@xdBr8 zV7X2J;<)BxfYJuXcJiD(O?R2DU- zYc(Mzr+9CyM7j7{tcWB#zFCez!!M^-z&ysdf)-t(>w;zcOou0y;+mj>MUtz6w0SJE zxz#)dxoK|NHRUyzY}xOgDxzypzvCC|>QXF+WDFkBSv%s@B*ckNpxiSs9_BF9a6Iiq z1?JGJBr!l6otu|%A$c;?AwK-7xXv1^TQ1d3bX=>HA~WnA$Fbw|t5p&Nzj!OFC%+)p zrI11%l(Ns>&FI=`8mJ6VmIuU_IOm}EsfKT7o6$~fYs@z!q8 z!((iO4Xopxv$+!(n5S>^K1^=P`vB)eL|p@mfOW^<3!hdUq|3n1&;~vsvIbHfs{H-8|a#5}B5j+Hqho=lo!VpG4hdeZSWE>OF z>*O~&@OII`0%1n9UlqsDtPvbV0R>bPn7oA7$qtw^9#Ly7zpJ(u?Z!z6c|Mq{PvTfb z_PM1g+*+ogNItzPjxA?!)NW}gP1-GZ%;OR>yW~a=7KxUb?G*QVncMrfvLxdNE(?nD z%q~9|EN=S^Sd#HhPD(EChpkdDXqhw#gK34lBMGiVqOW8M%J8_Ec2rCjxNv3vhALYBcP5~kR6+cvIbciZ>p@t~A`Sldl>c+J@d?{18Iev&P$=9Wm_2>9oET#6#FSTE4 z_Xdjn5@q=1BVd`JP`BJ3Eq;h!S|Eb5*hdu#^~|!SepK$LicZl|3OxzY%;1-`U(&d9 z?qpr#nL7|KJt{jiULLFc(uQAJ@PD2AVuO`Lyo4{d#!GBs%T)16I$lP8>6;6b z#>)Fp&Lf?#Rf?el@fgD|ITDd^lpoL-$g*N*7p3K2@OlvH(4N^^lO&uV5} zYAN|fMSi@bmI(<|@J5!h-ei^}+by&715V&t9WVPqN+ehSTSAxYm%M=rmqV8j&P8zZ z<7FcJxD=X@F3-iwDLq)qLFwClT{?x(rG?zMRZ3fGT?)D;;uMI2YbY&sPdZ+@e!L85 zo^IlDmQrWv@{B>RFM~B1{$7{B*03(g*Co~deY`{l&#e?xG|Bf%Q^(6ti^bzwm%`J! z)T+QHDgY@8$=7AC*4&rlr7AbhfMhvBx)iG=q9zX<8%1q#H09#uF|0oZNk&m|9SH3O z6H=&GG=GAt3FRVQT3*mM2~Y(HbQ5{MEAD&v9@Mwr@}(2Qy)|A!mx`+Zy{0$B?JfQ4 z$taz4)>w`G$9U=Lcxg0H$9+jWHD*{Y1ZQ(|JklD8Oi#~os5F933t|&jYnFL_yhJY2 z>X(*Z?(h|dU^;Eo4`aX?Sv1E>_Yl8y@roAKSAyPnesvi%G|7)dE%8$PL%f`8-ns>D zW)%KSyp*zEtCaBBwdJI!!~Xt_6s{cf_8O890_(Vr>WPE#()D9!hkKS`;EB*le-|$g z`ekeE>{JscdeYeP0T}W+{;%Stm~l!I2xOo89a}M})}>ti+CeEfUK$jls&@5c4K_D^ z3@QOy6Y(q>)M0(#B*E;J5qyTM8<4-M>3sA-p5g0$4C|s&=N0? zys-&=U;I_>iwUQ=tB#j7zbyCZaLvteTS)j>=fpk_cR((`%q4(~@z-&&1X5yubNw=? zSNmmB`(^7o?&6aW-!H8m&5#nOu#z7q!i2=t&@oaYa%C0x%PO#x#VZE8jCTT;=y=)g zpp>#5A`x8u$w&-uc}SnF>UEr>`afC6)u%YTLw-rQ19}}V;Z&`pJcFY*2e`g8j2+{49 zqfqtpOJm^iEg_i9)pNMJJo?ovG?$}4DN$&Nm;Txf1i*afd0bT>@eNlNhwJn#^Ycq% z%`FF|r2Y?qrJ%OYtab~!l@A*zEsyJde(9&FV8N4h;`g{I^WqI%Y#c`M=QnJGTz;wN zHPewHMRCl6lyP07+zQzeFU@y$OT{e=oxTPOC5$=f(hIC_S;zf-Piw9YpY8upJ=0&) ze;wcS*{ahLFUu1xVS`OK@c>gS{{pYKNQ%$3@6kWxmq4Gybb|gA#FAACDQmgj8ZS5Q zhPix5{18Cl?WUSJ!7ZotcYp_+-!0TxmD>EXB~i3@;d&`QV08<*8CDhYa%a{C?*6*(vyR2(O_u(UBq0aZTPO>4ZE$NL^}YOqu5qFW?}?MB=1 z6s7Ds3f}g)c1xP8*B;?OJRW$4_^nOMO|v2ayzhar;g`A!R}Xy2Ql9+9rL%2( z%TcNiKRI5qU#8^%ma>JmxL(2H-#zc)0qn9NfSh8p~J{LpyW{Y!Lb&=Hp^5M+wh$8mEIFIOX8{>^?o zfd&EXHM+cRmtD3QHetVnd8~9W*b*-b;0=Hnz8w*H(Y>{yzf%}-=vBP7M4=_WG!`7_7{6S3Mx?getGDA{Ni;yaB)ZDG`vmS z{?unPs1;2;_xzr5J$+gkhgi>EM-)SCB2| zoJ6w(n%d7RBk5A#*M7;*X{hSDm+wnX;Irhw;$b<@oN!H~3X$piO*uyf8}r8cMo`8x z#;4>SRm-pG0PW}D<2G{ozO>|a^v>Y)8upQq!i7lqqWFSut%N&iI_mK;>;_1AO zo9J^*mTQI^F3A<65l5uSt!RAGXhPESRWtGrK+)IBKhcGQjJsciA*$ORdFr>73Ci!Z zV;(CH3CmO6#_|(XpVl{AqQyws-3hDu41{!zx9;)-cz8E9=y;vH;TdgB6dl`A9Tmpc zy9y%9$g3&g0ZkVf<+X6{1Fyun#vr^3@v%+e8%`dBT92lkvm~R7zJ5Q0El{-MBE?mJ z)9bV~b785#jSo)(i_5pAGJ4<#9^jiuBMkiWQH6w!dy z^JXxf+_Pi66;8j`T!4>o7@Y1GW{YU<;L71-snO<^b#G^cLVmjE z=zeMP{btnFf%7PaVABr2v(Ul-_xQ$ol2n*|vRjH88j`u?xgyXAbJ3%xJ>j#2RmI38 z2`LwzA_+ge*$=Y=TqBL{15|k5M#^doB*GDhZR2bHZ#`7{eR5aMFGC4*MBO(|_vvsI zqp+pG+vDoN(1ZEj7Qew7VJbv*G58n0dC<3v7RA6mk*=u#O?A`V%n6BUXk|!Ar)<=p z?}9IC=svQ!SYe?5r(8&$98Vb@sJ+CXYYZ|=J0g<%AnH2N9E6SUCs~xBqGK~UO4wmD zvz^Q~?#Q`CrM`;VwmI#sj#RMjT?1!Joz3~T))QNtAmwDEQA|E&bIRj7hEp@3F=eZ> zvQbF}m90?<<0s)RI^~mE5)04SmNO4t`M$nODP=tN*Whu6o&Ve!&21EP**0u-s*yCI zbVxP>b_{m3BVqqpzC2MDb?EswW)-*^{|J;4By(d1??{2bNlK4xzf4lZD89aKYCLYN z8X}aIGGI(buWqD2rKElmo6}18sH}c^tP!-}>VCAe0$or3@I{0AzOhU3Y{CE>cWA2k zPNIcv+hcoeuDm(!4Dm1n(uSN&G4sHX;T|baVu+Ku7&O44VsEG3m*jpuPF6c`rxn`Ot^Cj*L%SW26wgTG3QJM{+nS3Mbor}Mqa~uv zK1?5WFDE?zdZ4ivxt*_oBfK6=AAWqPxeg&1D#mFdesFO0V(dTB9-1N8JjkTV-gqLf z=1y-$pF8WRnxdaUdXGp=3)RrIEPtMF9-P}*6bdcSRF;k-Iytc z6XL5lG09Yc;OtG=4h5_tNZ$RxUXg?;&Xtxi^n{h`*!RHl#iNlN#)}JvQi|(TPjt!1 z`WL6taVLJi=C*dDdw3?kZAL@Ipqep?#G>6VE2aZDW#_D#SqEMAwK~&a65~r)XbO*g zdj92D6i?oXZ%H*6W@X;De?ct1vfPbQ+75VkVzOXh=RLXxF0yF%u33650`bWk-+U0# z&Ny0D8ASK|G&j&?VXDQbs&|Mab2I|@JmZXAo`PTYT8$i~+t#%hIH2JAWu}LD{pKEB zrS1^qJWsediW}u!LZ)%odoiAY$1AYvno__W4jXE{@G!2A7VbE%|~rqpyx zLTLM>{t3JYp8ZwJ?7=UW>fY+uQ5uiknyJSJ04w*EEYnJU06zmHE5Bf(*rj}yX6Y%s zzDCbq;XxOeka@LW*`a5TpRfHHspVU@E%51J@mrfZ;prFqd(|#N!)3{jSS(}U6H~`< z^r?Ye7$huyxiB0mm{)C~-lOS};xspe&--+HqD(A*&R38_ zfkl(kwu_JAI_2gZ{FMd zkOU!S0p*>!1{RO^yxLPCLG8{GX>KsWp$!79k-S|Wa2z|sg7Z~mHNYjGL4~~SgRP431%HU zO*koFo%h#I$eRCAW=up^*srFF3*RAK~jM|nj>M|vmWmqj-X zZ&aENIZpeVdE5a6V9zCEX0yiA(gR&RSw+b$>5@X`i8Vg&y=U@yzTGOJQDgqw$rO_s zMSqet!MJ_PmUy-hEg}dD4u8Dav!yeh=Th+cyGq)j!H(`pgmCZj`Xc}LeF6NC@7%kU zREjRbXoPXsMPi{xuxnv|*no`g>#l54s8bc06Z=mxoHmrMf?lY6^%E?UAu6wS6O-Mx zWf%RPf5X90X54cRrHv9uS(6e}=yjMXfo{9{?#gQH$4ig08@R4lzM8$35@8x2ile@o zQcxj&GJnHoKTjZp*S%?zi#pVsH}cJyuxH>pb&bM<>ryPGYp&^QOh%e^)8w4RA<)Dk zt;?)yg3$SLIlD7-cc2k!qvRWDZpEWWVAf`^_y>#0jp4Be-(d;By69|z5cVzaYI~zk z_+%3HnP$-}`SLz)_pl>OjGVZ#tjmKf#3>Uuy zWtvAJFgTi!%Ai7g1RBUjyLiy$nq(>|(HLFT5G12b*=b+j?)xRVJvPlMAtBdZr(#Ag z=+b{aeA)wp@zw&7+$LLToUs!BwgV_Hq)z zaP8UszmJi9-=*^uC+Yv_7X=qO3ePhXE}fK}N}{s!r>92A`fUI2eYAV_6Q`+w znEl3|lM2y+;n5$U*GSu0*Qv4ijZNvT7@EY-H!lkJuEuxJ1sBp(fjYpx=Q6zNlJ2{e zW&->17dI8?6Q$4hjAgf;z1Ay*@--y)dnNc`%@U*24H(d>AtfoE{$@BI|KNb~i*ANn zYp@C?PbS#9`-9!ay4c*v^mNz^FLIujZxL3OM6h3}i5~>2} z9lt6)&ZYdIPFjEnU}JqIHDmnGKQ;*UDcjZ-Fd>C^e`HE>XYa(?BQ-`CQ$rmf`HT_6 zP?r(^%*~}hP=1O1dBKIb-Y_H;fal^5&lAqclmCX3lDZOngux~?CACUf`y;!SB@hK@S_k}aBlH1L_>j{x&u z(6BcHT)yPWTm<}W&By(y>!B=tkENLZpwzd2CCZOt-a?e)i0Z#lmHgjbDM*lbnoelDc54&h7W5>_fQdNkeV_u;rk zL&HKsjuI}yoWUy7Nu(eNwG9mLpgCjC5XIe1IG|T(bE&=@ zx2hfJF9(PMC>g`<_84)=_p_V%ZUp*lP2fY|nYEJ?*8$;G2zU75<9k!N*8R;-v1CCD zX{tfg@%O*n0BBIp<7$M13O6x$i^#D&#e*?ynk+KFGlW-7T3S?+gu7_*JYi?0)6q_M08U6_9Q?aFJJyWL31h^kkMpJEX9Bzh{k;2rHYW%mN|V z(4|RTC|R2#{LC02aXOV@mf<4L#V=du_m43e10OJq0&WRij*7oviy1Z(wBDW#=O9h06ztiQbd1*$&;@Ds%Z1LG!3u_f)m?eo?72Ss62dVoT@AH>z zY@QmvC2Q~VJpknf)re&oxv%1u3t#aU>0qG0MAgO-dy9&~tIp(dh5nY^%G)!A>4_3? zuq;C%A4j!};H0+k7?!G-VhPu#9+csM8Z=@iSuWb2N#g=-o|^MAp{PlW!; ze3Iub^wE$KDmH@G?s_OkCD`MSYPU%DMHqr1QQHID(a1=HaB$ESm3*jR0-#whj7OL3DyXYV*gx#4M7 zkR_QCPBd+&!z|5fGEP2pnY6WQyYZE#hhM%nJ&Q&kVlQql!lx*(IGn+zd?8qVgKkk) zY&3R5N(rSYyO(~q*AZ#goJg{pk*v$wVmKOL@sBJdBtL&Q$5LUkHWrZzhQpqrSeGfC zJmO1@AL_ARedr2*ICwTZ?F(k^2EKvrMDn~$i_dU}!kF^Now;}^I(VCop(GvI)~Lkc z zVu<`o-xn}$@so9ahKsiL6F|Lk;;uA|!C-c}S*7r)-K`iVOt9(Wxw;{6>qn?cBe{an z07=g4FVBD~mgH|hDok@<_3;$CU~7z}9F|P`7+Wm(VQvcJ-;<^Baq9l1vO7yMsNs1R zLQ-;2O8JBi1#eah*y6?C5Hiqc)K-ug-^HA}02{&Zs(0J?lm@kZ=&KIzuY)f8W9J}} zb?rncH%+^8&xew%WbO0Cq)A;mp(&LQMFonzZWwvQr3@_YQt0dj*C<4vt$T%s2Pq)h zcKrd9iri2(1PXQ{;i5s@`rRN8=Bk}n;@YU=+2F0y^y~@kmkBp7Z^)#u;i2K!5nC;c za@DIMt2>Xu`rj*lBJf@Wfx4s)|dK}4LM#YD2YZ|dRWhv`fN~b=p@jrQB z0kd5Bt;h()n1E3*svbIVLyaNXjgqGUTLOIO`e?V+kb2+)x@(HYp-Wf?x(sbW;IX5& z?=sGgzI9QS@_+(ob|FZ)sU9D2x}oQS7+msPM{ooFD@WEmt%3&Z{Bto)*BA<{eG}3d zaP=7f*CpP}OZ2hg0&grP-^#c5a;(rWf88?_@mw($KWui5sf}3vfe)uAa_a!^*_5U- z$qKxhL8thg8$ySH<3hYK=B6b#V(kf-E5CR=qbYHD@KQ0Ab-6!dx(V@C!EWL3>gs`1Nrf>>NwEBI z#Flz1ZnqnvEZ;Aogj;uc7(ig`X>u4n?|q6_BVbW|&Qb&lLp{};$5rMF_ML=8w(%QJ zQPI%mECvsjkKO+;#KuuFQ)z&d^(otR10RI&ZRg?*YgQI0I4oZMu1RB$y5>z-Spzm( z4?KYXzBIvtUdw3g#=J)Dhbzuj7Iii5;sNlz#;~t8i=pI>cR7c+f+5Cep33g2e|O6oqu>=K}tvu zBOICH8*-#0#$t|8h03yJ81PErp%VNfg$b60Y{-t3%$~z&=!-wg%N`!+PiB zj7H%9EUkRInFLr?^X^pzu~wI$DZ0CgokG-pO~3Ieq04zQg)Eyu1tNiX*`3nd(6;c+ z`G&#@dCOeOCuEkB#9J8nDU49Rz`%hv7CtaZo@?f5yk2l@!+rNK64FQXCHFh9w_YA`DBnlu;Z@&xD zu*F@d^!A$?VO%(5=k|z$IKaONPRj^nxwCfsOtE=jl7!6vNHr;=dja>>H5>nDMi@JY*g zb2EN(g#x7f!?5HXcto>Qb|hA>(Prl(6}gn(el)D>L%7{OsINNbm&pq=dOkwRiBb^F zsePy*stM!N@WVYg?d1sxOWOECqY+AW&CYEfP6R~wp7Hdg@>!NVfBL)>IoHjO1~Ge* zqo@>zEv(B+B)aYFt@X(uNP4I8Sy}|YjJR(RO_dn<5x3!JQdEx4F%)$4UHeKLW?7K{ zi{ynLV>ts2bZL5!evmIT^KIClMD)F#2aaGCJwB^Y5@3nAI@+B2<&}RCz?fK&pMUx7 zM$yQAi9@60$l`_Umlkv>VWsv{zSTZNv+jETDXp{_#FX%3>26kGUyj_ zGYa$Fg?Tu?WK@fLnOtF4&q22}PZyOMZ8b~V+;4xIZfW7?B5%cnrxB)6pq=PC?Uzp9 z*Qd0ev~|ILo`lD5Wi?>N7W`Y%YfTve1H7oJb>JJK?2r^ z(|X}c30t)LvVQ69^c4IOui=dcm9`ia?|J$(N%V%wI6gykQ@eeZugm@o)Om7o-fWMX zHXJ3RF@z04$)sWg)}E2fUB}B)smB&`o{pEryKWuhk^31|%^bMyHz`_JTgI-A$5uMF zmde`s+-uNnc#bWUZb9Duf|^9M?|1`CWIMS+ zpLHOLQEDeEL9dWdAr`*&c!}vFM(v)C=fLub>OmNo zsQv0rCgZ^O@NaPrD|3EXJHKcbEN&{Jc*MiOsfMv~W2#NFPkPin=PQ(+;&_Q|%GPFh z9sIIuW17!72w3fpfg19xu$ITAm)zt`5c2)}JBQAmu3W6+KbI!;cw?>qre!x0>tEt`gZFxD3d|N*{L2TyChR+r z9T`CHW3k4Y3&bq^0+Q!=o&u#na2q)#m6y(~dM|(vLwBNR%>1f;lic*^% z$7?KOLY%91q$jLuL8=e0!tW<3Jpc1~8Obei)@7=lO;4rp3L77Jy|`AZZVU~$3a4Py zfL?(<42LyvSOfpN8fYMOI1K#nn*Z>~4{Lxm&_*222M&9}VGaDxYv6D`@IUVYhZS&G z18s16bNGDVupb=O!2h@g4(9{^;|_3m_=h#X-{c?82M+teVGaDxYv6D`@IUVYhZS&G M17&AFhF=o@Ke+W4djJ3c literal 0 HcmV?d00001 diff --git a/fpga/SX1301_FPGA_125K_NOTCH_SPECTRAL_SCAN_bitmap_v27.bin b/fpga/SX1301_FPGA_125K_NOTCH_SPECTRAL_SCAN_bitmap_v27.bin new file mode 100644 index 0000000000000000000000000000000000000000..f986d448abb4a381e57dd91ff3022a691736e647 GIT binary patch literal 32299 zcmeIadz=)-)i-{syJx0%_A(6)u;{Whu;OwN2Sr?9fnq^HgD_FRhzO$~qA_Mwj4R<% z!=fTySQG?|m%)GnYFHAZCPM556@nUd-m=d>)DqM_Bw@Tu3kFx=OhBR zk$a)|*_Bi`o2Uf|K|c)Yhe`go6!L#N;D_mzO~wyXX^I%dvXquzI({L!IJ{0tzvG9m zIfg_(#M1m8X2I{l{}}X-@gk88R_>2s_v2}@%KrEPKTOF|0@w}lyaWF-$Owwm7aB*( zpwhfcp-J1`PkKc1qRaP4?kQBK+~+tHbtxiAW2mIkz5_k+6`j_B-nb>Q2d!d?N4}gy z36gE@M3bQ!9$R{;P!XElN(O0w@j*u8BkHr0Q0ZX|t8+DisFF@#ma$0HN+L5IBBqRy zimZ%r?49sYmJUVhrBh5Aedp0#WC8W2Gp<8q3j9}7fWBUoVOap|wr+P&%%Q@;9(h6< zcAUV8ip4MAf*lgpCvlev0YgidP9d|9UhE>_#o&ieXe=)M8;J_YeLB~!oZg+KEEZr% zRJCW0L;-03pocqpm8+XWoTVh{a93!BQsi1Q3vg$bsHnySr9oa+N{pq*9S(BE`a#Q~ z_{M+aLO9fS4x|iHvy@`;{kQjox$V#ZN5QZK&tAx*^T;ZfL}lb{;id+0=2n#Pi1`9o zR`oBKsE*Ap$fQ!C7;pOSZUv&7AAAI4;7Pr8V6dom1Po zD18k&;tvnXiQ|(myMHW#YI5?%gx|*X;@`(y5ula}n=z;ajtQ?&9GP!k(v0p|y%grP zpRB^{$pTCpH#?7oyAq|#$u*S5CoW!<0IKah1)ic(RDHO~V8u(j%WNA(r;jVSarfX@ zg3BzZ02GItF8Xs0V^I4oaTl#1Y?%X>ZF#JcRMzp*YB!6GmNy=+tFkhO(oIo@*fJ8w z)&_Iv;9c9aUGLE9RA?%q5S{foszoCHXPHI8-8$?GB@4?FAq(aG5hL%MGcV^JAy58T zy5;6wx2?>PWJ|LVH@~<#(~LH%D@jmZKyox?A?BB!@cPeRk;NJ2zuw)z^|Mzf#ICU~ zC2>!`(;`Eh{!PMfP_*moJjbcfj3Kn?gvh8UH%09%C|;Ew|1P9-7=xrWcQ`ucKDiQY z*WCK9)L44;Bo7AeHtQjxEXMR&R)I3G@nulRQ|7S##iRmnOB=NgA7rhb5E3i}R@-CN z#|`R1XQa@Lf%)UQb7?mIj0-?PXOb-?QDZ<1q7)_CMIaUOrMP$13t$;sv^wSA+oRxA zNGa^%H`jxtbmG>hJAx4v+I3wZHFb}zhBZ*J5JYKa>4*(Uv|XzJSM-SYCu*;iJ?sLZ zVKTF%NODXUuq+yQ-g4>VKbz5r$_c+M^_#f53C%K@9F4|tLl2Bn+~wGuXo-fr8*{m2 z7{Dwc`6tY>lZ$+OECwa`03OoEA*RqqfEkPJ~q8-~Z(8S94(3@G> z#rLw}ZuNmrC8GmjyJsU;;t{_3plun0EfEpV%zP8J0JPoP^%jfl$wLAuZUT&iW+{@p z*y9v>BgI&xjoTN3rQy<87J<;2ERG%?F2}*VysA$)#zwA)(;fyzt%sR1lDOjy`U^N7<` z+d21_9LDng8>%@eUAdCNLiXH}!Z3y}1}I)38~v>U5YcYu@1}B!1vY+wH0HI6q<}=Q z=l7A&I~Z*mrQ5iKz>?iF;ZL3-_kw-fe8;Ir+IVDHMm#l)Q@4gUccXBc%&3CJN($l* z$0hx#3AQvQTWgr5#2?W`wNTtHIUAvEgv8$L!jN|WShyq;mrMjG>ARB^Z;|)pV%W91-gUUcX7bdXuX^jrd#7oXW|OC#8j%GeaLXC-il68bhp3altxa}ut-E@(WYICgQ| zb8z4X;B1_)(+1k+?q9HQ=V)6pfd(w;#lP)xTr1ra8usUk13nQL z1o}dCx{Yuv*o6+xmoyP94U+Rns8FQFNl-{MrSj^iXdhS-RqRf|1vUl14O`aAfsJus z^Zuc5MT`=uPxI(>6c*2z!=dye=nx^t@UR;KdvZ!yO59B(HJz)kxHu_X_U0J{5GR(R zSVwG3z%oGvk90sR=}oVu6(6A?XfGPF1K68p(O+3{HRS-a04RN>fgYqZC<5}RAyE(H z<*C!DWfTtdDg3KQv<4fntikn}UGs3gZr*IL49g%`A|w=0EV6`Th`PO|VT6rxNNn4p zu<~?rZ8H-2xhBe81ha|j-8Q-%GL?9PrIg*rrE)a7G})F$M$AUbII{}Z`c+We2yawj zNY3EstC3(S>6i>|I#J9sP(Pa8V+}BhwUnGNG*raFl4upP>XZ!oG{rYO z_%donlw&cZs)>dfb5du)>i00qWfYiTN=@qQEoY7NgcKA?N+D&81rrC==`F}MpS$yF z5Fkl~`n^mEQ}&-XN+?**=wH1ica&h8;2 zuH%x7(G3BJMU=b_ERn;u>l9p6Zv!hnGT6|FOUvqDY(W+T2>ODhwA>7*U=d<-9}goq z#S~GeTavKX!YH7Z-qLpwD#6xB8yN98C)biK%%bgBC{CqDX^Q!Sd|j(JSQ^zSiJX78 zVkD*Tm$wQmQ3kYoK)K?+B${+Arwws;1#)ac2NV_E*&#Lr4|V(B!L3}7e9<2)%S1XZ z5x=`2PMPrH1ojcQN1E?8>|`o%ng2^xoJ!d}B3aM4c8F1vijHO}t!W2*DT#g&bzCYi z;!wPz_+D1rml9?^U0`7to=tAWpZ^vtX)?2PL-*E!ujfliw{?gKDy`5%aVh>*dpwrj z!|u74BuHUP080s(xvJx`)n6BpM~Ppp8D&zaNJ}X>E{`kO*P+;=(!S59KnQc%C9UGn zq(eL|9cuBul0@ZR+>Naeh3Y!;$~umS#1%*_TG``iR=m{~i;&nfoP2Y&P2$#;T0rsb z9*uPA#S-XX4Zu2XZ1M@qMr_2r@*=!3|;-rCPGSxqmC#q;=yNe-f^AB}=b7>uA>G!@p;Qga~XGFnhce?+L&sd{vR+koyg;?18MON2RiJM^?Hb`JzyI{!k%rFmMfc?cp6 zPP*4Nu0th2=Sjm;q)-u5!EqThGouHwf(#;)8idrI){UydNJ>{IeK~CDVNWy$3C%}M z*a{7gQ#)6ZEE*7alhQMHSC_LOu!?;UAe$9*!Ve};QW7XGa>Dlwu~?{Hkwn4FYb9N9)e z7(DmXON75w!cqzhTH?oLCaZV>-P#BF67cE40#}+FJxB>sgnlpP+Lov=^xeU>bgoOCj^)3qO`367#~-BrYGzp=t6@vUgQa0!z7hIVe|r46Xd16Vlgp+cU*0ecwlsq6 znxzz*yi^7o>APP%elLmN7~vF5&#G6#mPRFlNWu&nDtP6m%!=ih<9TN<3;=0@QguZT zA-o@Mouw1r)YWlC`AhCRwuTCNTbHJEM}W?`s}3GFe({df2#o5VUJV7}frlZbsJbVz z%y&Xadz?Bv5#>c!Luef|ByrZSGEjWlmKU&dO(EU%Vvmz~=9R;IGAbFMZAsheA*Ev- zH`gKE71{^Iv1x%VeK6>FW*HIF*p@yhE~aXhWrsq&(){&C__+AG_@DWI9cwCh+hnBu}>C+&* zcqA_r42Z~a^Wq&~Zj_4?_G?K!- zTZr#DU1z!edjLCXSr6kV53B$GuKpLKgpn9n!Wad-Tpgw4dLbhCKLB3V8hxijHMb69 znZw2Wip;hD8)cSfmX-P*mj8jzO7}hFKL#!Jqt6dgN~ZXZopOod--kM+{vD;;(9pzD zk4GH``rA2niZNh&85r5U%AEo#?SKa;9q?*Aw@W7N+=Dg%Y6jnaUxLL^*a>;2cTsN6 z_=hM)k4SE&lW}~QtzvKIEJ>ZP$3THy(|LR@6AXN-_iu~}hQ>4d;$!s8PRwe`viSXA z&h6Cd>0Vqg7`s_wlRoElvBdPU(@@M@iCYAz@b^-a;d0?VW~pq7!d?|_a4dn@xD(}0>@ujmQKDH4J4I-4I)QF2}_BGO!j!xM{zQKi9K%7 z8ITek8uD36GI{leUo<%6+@RyqXq8jkBd%keRG`3CubWof#L@WJm556#sJ)Cn3qdq? z590E1X62Fmb=H=d8?-HrLd2rWePnKEr`cz-9G?|1S|qPwTLyy%nr+!R=*Oimb0Qb& z)gdDZsS}ksDKR?8p&s!S=i}2XjA52V#=bp+au_jFv(NwdkmArk!w02+!e;ltetP!M z*&IZMnT<=LuXJ3RhsC9ML$kDbS1HnkS;ga_Yjs>6dq~PefLRI}#qP0q(@(I8Sfb;y z^MyJVwVXW?mom#G`46&`!4X-Oj*0U^j>}Ej%S@U$nb`|^Rfjrdd|T!(`3+AdKvOm@1ASOZi)~r12~C-`tKO&{7MG@u%W}k`rV9!wz!Gt3K7sw( zp}54>PusEtQbINcmtz!gRY{%1em9pk=re6g%3qrZD(Ueoh2ojuWf0x^1aMx zBZN8*h=!P<=n$4G*kT2p*u*9nhOu`(_I%e&# zL-{g?b@Df|l!xU@lKsIRJ0_hk4KtDQI4<*+yu^wRxZqH}bah-he!iqc^rJ5{S0|!b zCR*p*1NQA$O49j~EL}y*F8+!YFS}riR+b5}`BLb7X#^$X0DH|O2a#try9e)2A*F3n ztlVMCP^5v)Ak;VOIbV_vZf8o!8P=AEFkiA%0pc#7IQ07p8 zu5#*--h|xeX)GE1nxX9Qd};XkGT*!#aY8*-TOhMe0InZ;QY)%_R z)S)pB;lMFDOXthbfkPfA4k#*a{|$RM@vA0(w#4T195=m6NST8Ij>`zeIn?oTILkul z_baZ^UM~(z8#I0I>wJm%`4Obzhp!OkxQhp+Kz2g9QHsL*NRpYg-LML!8{Jga%+9Bz zy2?7RkiX2bOg;hkz&Jd6AapZ0ly_+Z&JHeaf3O-_Auc0>dCr?(u#_(7T-WrNT-c)w z1v>~2MqntIz?leddULjA{tU_~k8)|HGy`&8ZWx57Yy~Ox3h8n$=!kBMH-cNY#sHcw z_Lq7&>wF>NQZPj`w7qfSE!?RR}bXbkTk%s5m5*u~fk zB1 z*7?%RrQooqXvFG4<&=E4Z<1L|Xu>TS*p{$R8Eok&MvyT+f(11|1Ky~kDrA&>8jAxZ zZA*(cFv>>-&R7k3UP%675#m{P*v2d!B7c8r{vnq(6d40|M0H&H;~Eb_K@;x>;2!KR z&FR@t$8D|iH(`I7ou1TYUWV^wWNJ5RDI+{%TxQ}J_eREHD>pazml(N1RF7mV{X991 z(?&4F3oUz$=#Id6)G6NLn+0U0L-#Mg(q-FKM|eUSLwKRkfMxZSB3+MjsFN}7AX$>T z9@P`lf7apl{WNLp7geYfUaD=`=B5nMiM+q`xBoiSnGj4C@o^+x-F1hv4RHy(N8;~# z_Bi>^I;b!d?ZL|{9PmGFdqS4&x>6&Z$RJ2bRm~F;jj{j~7yj!SV+!6lvbpk>oXApk zo^)t`85EbZU6+`B73izZ;o}=Xc7G|`A=Dj>F)utWPoKhB1sNb_HBQxNyV&1f^0SVm z80AI-BUV81Vybu!HVh4&C(DbM&01l9c^&4s#JOC~<5E0@4kM<`fY|G}^9_j` zV3HqLAICZ#%x$yKR}IRZL@5-K5|tky4k1czf0^9}0f$A=&+1&4b2_BsgA{7n{Ush1 z*Gf)N483MzCgnu_=fBo4OMs~TkPEh@4|tS{EYH@#pGICJxn|PgMln`TTj_?G?zPuf+zHLV z1_oQ!+;ZlTF`Lk*0|l<`Tpjm#j9rXH7rgyv9UuzeCWym)+U^xhpm>W%^O7d4K8BFn*N)2vV=^v);)*EYWI`o#&77XN#XNPy3?TmO=*B zWH3Z!20VhB&-i=BV-7?wv3r7D!-s5{xb!Pbk9b~J*pjMFTi*(6Fldrk$0d)*!c$0S z0gD}HmqWSUWpCBP*n<#6IMhKbYG98Ss3I{g&iS%-wKA%V^a*RCq|l7tay8r1^fW0h z77ei?U!bBMiE?Yu{*9+AG(|F`GZCseIPOxpLK0oExuY^@(By}AcAl%}bcEL)13g&? z<4e8<4ybTJIb^Mz=%u9PvAa&u`7%exWnju2iI;Up?NbnH)8wX6h!mhd=0|)4ax<9! zj6U~X7|lm`xrn-#+$nXR_>E?Xh|59IWt$&MLsR=g1(ebB1_A}Zaj7fK7ms>0lqob} zOAwmrlmr=q=#T#{Wl#*80U`hHL=VM&D# zT>R&r99+# z^!`8ZrlTHt<}jjcU1^kYSjUg79?mj5kzIeN(#2s@!AF1JReoQ`|2sT?-1DO#YXqM* z@uP?gvXS?voxU$W6qXjIR$88>x z=G~4D7ILwklYY~nt#8zM*}g7r5v8+r6yi{oBuiPBLx{~oRweyS5>VJKRjZtw3_ zJSIG!HqR|_n)`edKAz@E&;!fBy}~68V7W3UBeTq{5a=w&-_~8Pd72LA?{4Yg6XS3x zG43iWotNXY`P6iF42tZg&Yub0fp~tL$aS}M3&;a12YI=Qo`Y8fGWl7AvH8^0p*~0% zZ*Cb1QoiYLXo1XwEe`{cwNH3Y3Rrnn;3bvq6=0bY_>h!1PV*Ds^FOQO5da&u{En19 zx}a!JJ=dWUEbhQWd9U&%)N~k_>}IhYu@#6e06MN zY1GFc)c0az&yDIVOI|CE8rfz?1#b1+d8GA1cb;2VP1~7}ZE_o>{OIW3;*o?a!7`IG zHEg+7Y2AX|$u5|42Dp1z+c!NXg#q^rU&~Aw@?3s}rm*UqhVnMCAP+nZO1xDaMEj~T z&rYEPK+4D4qHO9n3ULUQNyTV$WT~VCLCV-8+y>;i?XXHw zwX4dW{+*8?+I%Woh}Mj)#I<0~c#U_D8rpA21*%)e#nT#t12;%1Ab+N6qhiJDw8*wQ z<4e9pM*G&&U6jv_jAyb|Z|fGa?;{!4l#*@fze?=h z?>(*fv2$~_L=e@t9YkK!i3;>!*#dVbYBTgG$>r8zS8&0Q=e8$hgXO~>TDOThKe*g$ zzYJkpbX6I?T9D`f%sq9{eBXC)Bor@p*ge7a?J?V9kg_`E4~63GY(HkPn7FqI;L#(> zPsSz|-(#M--rt}A*KHSP#1>nR`dbaN%*EwPN3-Iyci8pBAV3)%h!0vUGj^$mz~>;+ z;9fnGWWee73L6?wj1#FH?<;&fzV(jX6>+e{Gr3Us@xcja?davk&FNj@XaNvtC-1^n zO=R}$_Ee1B<7qtgIQgN;lB+z-hrf*WL>Y;#t*xmn`~%mzTN7pa|Z-=o0tcI7h5p9u_u$4?mlB?|M~ z%gMK;qR6c?uCRdWImJ}W((_gC(f=XNtOLF5KEr@5pL*8DW5m{$5`Jrr%lyH8Kf%oF z^ey=2p#1n2kiqOzhB+g1wshR9cV|Ba@wG9@={f%WBn+|jHV>2qbIwzWS+qQ1%u^lj z%Q4fXiLa^}JV*&QO4zc&@9oil6`Vo+_ISlv3&r}p{A61^TWmdQ`y`j_Ip<%ykV$6` zexgc3%Yz^0+<=AS=!y(befka^Le?^Boa=&++$;XPi8?y{b|4ujMvz*ye~rSS`qq|- z$ru2rVhT|4zPK~b@F@%0rg6}1{h(RScqj67vsCBF2YwX-ITCPUtUH!^84$Q)S1ha=rK zyl*et4s_RD6_fvfn+*LF$}*ix4~}_z3BKmrx;TYBM9W*6iqi^s9{7f|A_I zqP!9`VHk2w+V*vk%Zg8)1j|I5aIr7+=xJk444*W3^tC2r+UW663M~6hP9ULfcpy(A zGS*offv|Y7Z*%|S4%&EF+4DsZ^#6dvH%ZNIZ za_BMMwK&nTcJC!af#pkgs7`QFt6ACEgEQk%h4l7Zu&n2S-I{JvOL!eGU;kGV1CrZ6 zN1q1AJ#~-3=ZR~2FGSn!e|g)qLkY^?`1v}Cpqp_?-WNV0-ud((7$K6G#P_^mTfL(@ zy3g7j#CmYwfjS**kkaWmMj!01Y46SfW%RNoDRjBUhStkX_r@%`Lg-)5XBIw(*idm)0$PoO#; zXr#zk*Y|S^)p&&|QT%hQc8_IU0e2zoj{X2a&fvt{9lz&1ffv<=GJn?YZHHZelbbM> zT*R&2!l%SR(q%n*D(*5T;_}B0k8z!>qO1P&zGpgpo?Fa&B$ko^OTLwfmB7~F6J{kh zYxx7glBptw-<^rMI!=|Q%tVXl6GDO1veUkCWPya@=X5v?UhejMU3dx8K%xb&Y;G$g zt?74v0_#I?m6U9)x`U+UWxoAT?H0c5Qxs*t z$9V{T7|>MDE-)?ot7StKIh}U?gN4 z^XaWrXsmcL;Zj9-{WdzE}L#iP#*GR5rK%9b43*15}roPG9x zI5&5XE)yP@=Mt?LkMCEozMN65^``lM9S(ZqrmtK0?I|2kdCOs&)D(2c8^?UDP-4+N zyHkP!Ez7t4-KK?T!zf}*9rvs``dg6N{oe#eg4gI^_X>RJesh<$)PmazcEub(HU81X z9?^YY)a&6?{%ZN;6!PWE*}Zx>Qe;zxAFCdIbSqefk3Vk-NMV>;))fqLac0=H1veRt z&~U*lYggjev{c{p06WW|uA|E*{}Nu_PVI&(U_yw|tq;t@-RJ+NHA2{KH)1d2$ygoq zIw}hH7_oL9RMDvNqCDKrPCc=4SX|z)>q+G@y6c|L%v!P@d_{4K!Ra`B6U-`aoHh>0 zz+OB-wL}cMM!>wR0^`fWT4<&;OSIDK$uz2GLmaYufD zOeB*N5_s-0)@OfdXN1iH3Q&18-}%5Ngf08j;Tb5LY1-gPGHNpgQlhKtSAPtxv}wWn z%3sG_a>}2o!!|+ImOgJ;!fds3!2(E0Mj(I4pbl7s6Hc9nHYTevyoXR&m+Pl9L5vne z*qD%VHC`!Gm*;LTmNwdL7-^PBE^reeT8*#J`%5F7kKl3X;)69|J%1FF*!y@8b>hE< zy1D*N4GkB`ZfHh_3Yn6+ypo}mOM$V}0k09)jjIMp7F-eoFCjhA3_;@5bF1sn6IbSJ zZ1*?T`-dChuO`9EvcuGNrT#NG+%x8}q!>j3BwRU&qSwZ-_9T!mL!)BYgmihXhJ%;E zEa_XFLCmH)O0uZWhCk(#V*EK%gEHQ+%gA>zuYFk6KZ$x(_!JpbaL=wj8#I1Gkyj4q zm3OuMf!JS8e2aU^+`SN)#Go{9mkrBq@!`;Ju;!u4k@!S&02^`|Aj&^<2QbjK2~ zuQFy$o`dE8$+7XY?pPD)EtSgutN}2b*2uka%kWub=t$om(`|{Tuj+iECY!yhvdjewLY*D72-9#t%S>;5C4iQL)^hp=}J5`=WOrhsJg zXmXhk?qgSa#aYFv-SRV*&c?Rn^VGycgiNR5z9!Y|FlNb$8&Y?D=VI6ccjOizX1N4X zb{+iqcWAhQU#TwG-q?(W;_RwGb}BXFb;Ta|cSx&rz1{K=x_LWb+0_pjXw+_i_CC_c2GLFJcevP`h0m>KylX^Wy53@zvLcbX%%opyS z&g`GRcuSWsC8RVxm|($ydc{qEn`Jq4fONsK?=K1C*p?NYQP};rL46;<5=?COU`;~e zAbFf#Ch=7IaCIkz9sJWx_=%%Wd8aP1^crOtfYn=vtg+(CQhdp14}2q8ColqVydRhD zS1N(nu6;g`@ftcegr#Z=JprWc=bsUsflKn088yHHQt@-j%RfYWi= zwvoKUj|4Xipwe3AgzlzdOTgynknqcwDQ}`5^)(N z#HH~J9;mlxpLT+E97J-#7nx?ttSyIr=0WpL2VeI&h)Y`X%4rY8(*>kuXs7cf;_@Fu zB@UIhD@Ga=tm6`Myzu6%Ix^=TZHHkrO%sr~q>VxibNo|Crr}Urau6k>N@E2pjt8v; zJ3sXm2UZ7>FosUsfkt@ox%yTvSn>#a;P9oq<>jg$m*noB%q$-smC}(7mj3MVvVo5L zr&&tOo)Qq|P05d^P_mREGlEIHlaz9rIm|~R_zvB^5TynASQ4dFuK4cYt9{N=N4fj&1B;fc&VO5J7Ix;#A-cc$f;rB0 zV=O;m0^0I3E#mCD0qJV40H7WN$ECue8>gU$sV$%O-$exuCN>iETbK?@lnlNtXAgZ* zPR9wW{ifr`*#r9A*JypgxwCzbXX6qcPr*iV?f%Wh7;OmqiAy(nR^o|<4B3w6=(BW? zHY`M!n9|nvUzDtPas2m3_VrP0J7M1M`K=rMLeZT=bdFkM7?qAK zjqy&S?y(hW)(XbgNee=cgFqdbG?ypn6hiyH5oB@uMy-M_8Ll9ADAWJOE33uZZbCyiNkzKZ^qd}*kRZUGe>d3%! zv^uQ1=mSbH8i8S+=Ax1x?!axpDH|djqZ15vc;2vXnZGhtnsK@?z_q&Ne8sdjXN+U3 zNA`3^tIeuSuW2*|m#vVd+wrjt$Ra$?ESCoEdP|ikvS$+R?&1vFs$$x)FH!kuaYm~T zmG7PanLFRyORC%pEH|$wVceQ2QZ5LK2G^LlRWymiT0bt;z}@vYB(>(Y`F5HY>^c2} zYHbRVuq6ftW~!7H7NNWUHSrlzZin|m5VqS+yV!q;k#`9wiOyWWmekN~V?D|xG^T0> zaErJ32R0;5OeGY)#hymWr?ZaQzeU%soAGW6mb+_FDGCO$Q6aIm+vF!ZAU4!~dJEaJ zM@|?buoYU@))syo@_|%Gk+&B|T8al!c>R=XaX96^j3WiST{&&-t!z6Hri~J3uMcj_ z_{q+Zv}o|Xpf|2NcsY8Eb^RVk3x-HtsixuQ4bcgh;|z1~!mf!cU0<5{~Nj_)_S3t8bwg=-rn)WN$dW`ln- zv<`;SU@UwcM~+VU*U2IWS^blYlbT9lK=G=pA1(~^;|K=hVvyF(s@qzxN6NQ z`0A02g0B%oJ{ap}w#`6-G=*lpj5&*_8ZXvmS>-s7>2$2E5B0;M#?*O#s{VfsK+*9 zqn*u{9g#w$Lz{x{bygB%?KG?*!Veroh`kt}V_Lw3+Gkmq zn>>fwHZ>|B0SfQh4svnYJwsebUyh9`@c`VikBfz3izDXfCW(@NEy2#PU{+Tc2r>AA z@^ptIO@+!%gp|&gl2%>~S81pA*M86^l?aY&nBFP!a8>xj0d)Sy8GmxplsGV0u{1== zLb!)98_H$j`n%V*#n{{RhZ4rZ{On#!q4T=op)ZmmK} zGqK#J!AQXyJ^kFMnJ$}NcQYz$!c`T*GVkia0^qJ^ofBlqD$_zKw4p$r*>pvnjrS*i zmgG7uHtE>{{QTo&j5=@h0!*i&E}1Tc3OHhfR8(GA;p;PYf-;l=mV2{wqPYBPO5<$e z_IE-Ovt`@YO7|4(sWFt~r?Vvjcnw~JV|EShXIV1&wZ_Hhmh1j%PZv2{wH{cz>msvVsJ8#G!6lt~TQNlJ4{Mr{%o_fxR?nJ}-zlldt z4Z5(rTY@i;0HYhb3bsjotT zXGmJxz%?9`-*sL!wUXga>o+R0x=dK2-&;^{;BiCHy55#>o+JNx->8}yZbw@uGtU6nc9g-@cn}Z~_(0cA) zG-xRk@vUk0>=$rTV^0Vvn8b2tfzmy6&0Q$Ni{ND)ehzhA|8d@~oaybCrGEG2zuL0Sq|(*SIt+Km)xpQU9-btK{- zf=7-$HBE{ufeg-#;sH^G7uOmAS3O^che(`lC^s=1wrmvWh|!wrpfIT8y4{pq$RlBd z6J*=jNN#Z}A#JequxH>rk*YLtT_o$U&;oG7#a?4%2~w~%V2}YRAs}mP1+WukTYiEW z6D9q8oHjyrSioK3jA53C_3TxTR<2ND@rK4 zXSzjr)_~F`+=Xf|Bq;Y!~(EDN9$1$r2hjz Cn%{>2 literal 0 HcmV?d00001 diff --git a/fpga/readme.md b/fpga/readme.md new file mode 100644 index 00000000..870248fe --- /dev/null +++ b/fpga/readme.md @@ -0,0 +1,63 @@ + / _____) _ | | + ( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | + (______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2013 Semtech-Cycleo + +FPGA images for LoRa Gateway SX1301AP2-PCB_E336 +=============================================== + +1. Content +---------- + +This directory contains the FPGA images to be programmed in the Semtech's +Reference Design board (SX1301AP2-PCB_E336) flash memory. + +The different images contain the following features: + +* SX1301_FPGA_125K_NOTCH_LBT_bitmap_v27.bin: + - 125K Notch filter for TX + - Listen-Before-Talk + +* SX1301_FPGA_125K_NOTCH_SPECTRAL_SCAN_bitmap_v27.bin: + - 125K Notch filter for TX + - Background Spectral Scan + +2. Usage +-------- + +The following parameters have to be set when using the Lattice Diamond +Programmer software: + +Device Family -> iCE40 +Device -> iCE40LP1K +Operation -> SPI Flash Programming + -> Programming file: select one of the provided bin image + -> SPI Vendor: Micron + -> SPI Device: SPI-M25P10-A + +3. Legal notice +---------------- + +The information presented in this project documentation does not form part of +any quotation or contract, is believed to be accurate and reliable and may be +changed without notice. No liability will be accepted by the publisher for any +consequence of its use. Publication thereof does not convey nor imply any +license under patent or other industrial or intellectual property rights. +Semtech assumes no responsibility or liability whatsoever for any failure or +unexpected operation resulting from misuse, neglect improper installation, +repair or improper handling or unusual physical or electrical stress +including, but not limited to, exposure to parameters beyond the specified +maximum ratings or operation outside the specified range. + +SEMTECH PRODUCTS ARE NOT DESIGNED, INTENDED, AUTHORIZED OR WARRANTED TO BE +SUITABLE FOR USE IN LIFE-SUPPORT APPLICATIONS, DEVICES OR SYSTEMS OR OTHER +CRITICAL APPLICATIONS. INCLUSION OF SEMTECH PRODUCTS IN SUCH APPLICATIONS IS +UNDERSTOOD TO BE UNDERTAKEN SOLELY AT THE CUSTOMER'S OWN RISK. Should a +customer purchase or use Semtech products for any such unauthorized +application, the customer shall indemnify and hold Semtech and its officers, +employees, subsidiaries, affiliates, and distributors harmless against all +claims, costs damages and attorney fees which could arise. + +*EOF* diff --git a/libloragw/Makefile b/libloragw/Makefile index e5419599..53c33d90 100644 --- a/libloragw/Makefile +++ b/libloragw/Makefile @@ -47,6 +47,7 @@ inc/config.h: ../VERSION library.cfg @echo " #define DEBUG_HAL $(DEBUG_HAL)" >> $@ @echo " #define DEBUG_GPS $(DEBUG_GPS)" >> $@ @echo " #define DEBUG_GPIO $(DEBUG_GPIO)" >> $@ + @echo " #define DEBUG_LBT $(DEBUG_LBT)" >> $@ # end of file @echo "#endif" >> $@ @echo "*** Configuration seems ok ***" @@ -67,7 +68,7 @@ $(OBJDIR)/loragw_hal.o: src/loragw_hal.c $(INCLUDES) src/arb_fw.var src/agc_fw.v ### static library -libloragw.a: $(OBJDIR)/loragw_hal.o $(OBJDIR)/loragw_gps.o $(OBJDIR)/loragw_reg.o $(OBJDIR)/loragw_spi.o $(OBJDIR)/loragw_aux.o +libloragw.a: $(OBJDIR)/loragw_hal.o $(OBJDIR)/loragw_gps.o $(OBJDIR)/loragw_reg.o $(OBJDIR)/loragw_spi.o $(OBJDIR)/loragw_aux.o $(OBJDIR)/loragw_radio.o $(OBJDIR)/loragw_fpga.o $(OBJDIR)/loragw_lbt.o $(AR) rcs $@ $^ ### test programs diff --git a/libloragw/inc/loragw_aux.h b/libloragw/inc/loragw_aux.h index feb0594b..35b39aba 100644 --- a/libloragw/inc/loragw_aux.h +++ b/libloragw/inc/loragw_aux.h @@ -7,7 +7,7 @@ (C)2013 Semtech-Cycleo Description: - LoRa concentrator HAL common auxiliary functions + LoRa concentrator HAL common auxiliary functions License: Revised BSD License, see LICENSE.TXT file include in the project Maintainer: Sylvain Miermont @@ -20,7 +20,19 @@ Maintainer: Sylvain Miermont /* -------------------------------------------------------------------------- */ /* --- DEPENDANCIES --------------------------------------------------------- */ -#include "config.h" /* library configuration options (dynamically generated) */ +#include "config.h" /* library configuration options (dynamically generated) */ + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC MACROS -------------------------------------------------------- */ + +/** +@brief Get a particular bit value from a byte +@param b [in] Any byte from which we want a bit value +@param p [in] Position of the bit in the byte [0..7] +@param n [in] Number of bits we want to get +@return The value corresponding the requested bits +*/ +#define TAKE_N_BITS_FROM(b, p, n) (((b) >> (p)) & ((1 << (n)) - 1)) /* -------------------------------------------------------------------------- */ /* --- PUBLIC FUNCTIONS PROTOTYPES ------------------------------------------ */ diff --git a/util_spectral_scan/inc/loragw_fpga_reg.h b/libloragw/inc/loragw_fpga.h similarity index 72% rename from util_spectral_scan/inc/loragw_fpga_reg.h rename to libloragw/inc/loragw_fpga.h index 375c2124..358607fe 100644 --- a/util_spectral_scan/inc/loragw_fpga_reg.h +++ b/libloragw/inc/loragw_fpga.h @@ -7,23 +7,22 @@ (C)2013 Semtech-Cycleo Description: - Functions used to handle a single LoRa concentrator. + Functions used to handle FPGA register access for LoRa concentrator. Registers are addressed by name. Multi-bytes registers are handled automatically. Read-modify-write is handled automatically. License: Revised BSD License, see LICENSE.TXT file include in the project -Maintainer: Matthieu Leurent +Maintainer: Michael Coracin */ - -#ifndef _LORAGW_REG_H -#define _LORAGW_REG_H +#ifndef _LORAGW_FPGA_REG_H +#define _LORAGW_FPGA_REG_H /* -------------------------------------------------------------------------- */ /* --- DEPENDANCIES --------------------------------------------------------- */ -#include /* C99 types */ +#include /* C99 types */ #include /* bool type */ /* -------------------------------------------------------------------------- */ @@ -38,35 +37,35 @@ this file contains autogenerated C struct used to access the FPGA registers this file is autogenerated from registers description */ #define LGW_FPGA_SOFT_RESET 0 -#define LGW_FPGA_VERSION 1 -#define LGW_FPGA_FPGA_STATUS 2 -#define LGW_FPGA_FPGA_CTRL 3 -#define LGW_FPGA_HISTO_RAM_ADDR 4 -#define LGW_FPGA_HISTO_RAM_DATA 5 -#define LGW_FPGA_HISTO_TEMPO 6 -#define LGW_FPGA_HISTO_NB_READ 7 -#define LGW_FPGA_TIMESTAMP 8 -#define LGW_FPGA_SPI_MUX_CTRL 9 -#define LGW_FPGA_TOTALREGS 10 - -#define FPGA_SPI_MUX_REG 1 -#define FPGA_SPI_MUX_RADIO 3 -#define FPGA_SPI_MUX_REG_HISTO 5 +#define LGW_FPGA_FPGA_FEATURE 1 +#define LGW_FPGA_VERSION 2 +#define LGW_FPGA_FPGA_STATUS 3 +#define LGW_FPGA_CTRL_FEATURE_START 4 +#define LGW_FPGA_CTRL_RADIO_RESET 5 +#define LGW_FPGA_CTRL_INPUT_SYNC_I 6 +#define LGW_FPGA_CTRL_INPUT_SYNC_Q 7 +#define LGW_FPGA_CTRL_OUTPUT_SYNC 8 +#define LGW_FPGA_CTRL_INVERT_IQ 9 +#define LGW_FPGA_HISTO_RAM_ADDR 10 +#define LGW_FPGA_HISTO_RAM_DATA 11 +#define LGW_FPGA_HISTO_TEMPO 12 +#define LGW_FPGA_HISTO_NB_READ 13 +#define LGW_FPGA_TIMESTAMP 14 +#define LGW_FPGA_LBT_TIMESTAMP_CH 15 +#define LGW_FPGA_LBT_TIMESTAMP_SELECT_CH 16 +#define LGW_FPGA_LBT_TIMESTAMP_NB_CH 17 +#define LGW_FPGA_SPI_MASTER_SPEED_DIVIDER 18 +#define LGW_FPGA_NB_READ_RSSI 19 +#define LGW_FPGA_PLL_LOCK_TIME 20 +#define LGW_FPGA_RSSI_TARGET 21 +#define LGW_FPGA_LSB_START_FREQ 22 +#define LGW_FPGA_SPI_MUX_CTRL 23 +#define LGW_FPGA_TOTALREGS 24 /* -------------------------------------------------------------------------- */ /* --- PUBLIC FUNCTIONS PROTOTYPES ------------------------------------------ */ -/** -@brief Connect LoRa concentrator FPGA by opening SPI link -@return status of register operation (LGW_REG_SUCCESS/LGW_REG_ERROR) -*/ -int lgw_fpga_connect(void); - -/** -@brief Disconnect LoRa concentrator FPGA by closing SPI link -@return status of register operation (LGW_REG_SUCCESS/LGW_REG_ERROR) -*/ -int lgw_fpga_disconnect(void); +int lgw_fpga_configure(void); /** @brief LoRa concentrator FPGA register write @@ -102,10 +101,5 @@ int lgw_fpga_reg_wb(uint16_t register_id, uint8_t *data, uint16_t size); */ int lgw_fpga_reg_rb(uint16_t register_id, uint8_t *data, uint16_t size); -int lgw_sx1272_reg_w(uint8_t address, uint8_t reg_value); - -int lgw_sx1272_reg_r(uint8_t address, uint8_t *reg_value); - #endif - /* --- EOF ------------------------------------------------------------------ */ diff --git a/libloragw/inc/loragw_gps.h b/libloragw/inc/loragw_gps.h index 3f771e49..dd3b4431 100644 --- a/libloragw/inc/loragw_gps.h +++ b/libloragw/inc/loragw_gps.h @@ -7,9 +7,9 @@ (C)2013 Semtech-Cycleo Description: - Library of functions to manage a GNSS module (typically GPS) for accurate - timestamping of packets and synchronisation of gateways. - A limited set of module brands/models are supported. + Library of functions to manage a GNSS module (typically GPS) for accurate + timestamping of packets and synchronisation of gateways. + A limited set of module brands/models are supported. License: Revised BSD License, see LICENSE.TXT file include in the project Maintainer: Sylvain Miermont @@ -23,17 +23,12 @@ Maintainer: Sylvain Miermont /* --- DEPENDANCIES --------------------------------------------------------- */ /* fix an issue between POSIX and C99 */ -#if __STDC_VERSION__ >= 199901L - #define _XOPEN_SOURCE 600 -#else - #define _XOPEN_SOURCE 500 -#endif - -#include /* C99 types */ -#include /* time library */ -#include /* speed_t */ +#define _GNU_SOURCE +#include /* C99 types */ +#include /* time library */ +#include /* speed_t */ -#include "config.h" /* library configuration options (dynamically generated) */ +#include "config.h" /* library configuration options (dynamically generated) */ /* -------------------------------------------------------------------------- */ /* --- PUBLIC TYPES --------------------------------------------------------- */ @@ -43,10 +38,10 @@ Maintainer: Sylvain Miermont @brief Time solution required for timestamp to absolute time conversion */ struct tref { - time_t systime; /*!> system time when solution was calculated */ - uint32_t count_us; /*!> reference concentrator internal timestamp */ - struct timespec utc; /*!> reference UTC time (from GPS) */ - double xtal_err; /*!> raw clock error (eg. <1 'slow' XTAL) */ + time_t systime; /*!> system time when solution was calculated */ + uint32_t count_us; /*!> reference concentrator internal timestamp */ + struct timespec utc; /*!> reference UTC time (from GPS) */ + double xtal_err; /*!> raw clock error (eg. <1 'slow' XTAL) */ }; /** @@ -54,9 +49,9 @@ struct tref { @brief Geodesic coordinates */ struct coord_s { - double lat; /*!> latitude [-90,90] (North +, South -) */ - double lon; /*!> longitude [-180,180] (East +, West -)*/ - short alt; /*!> altitude in meters (WGS 84 geoid ref.) */ + double lat; /*!> latitude [-90,90] (North +, South -) */ + double lon; /*!> longitude [-180,180] (East +, West -)*/ + short alt; /*!> altitude in meters (WGS 84 geoid ref.) */ }; /** @@ -64,33 +59,33 @@ struct coord_s { @brief Type of GPS (and other GNSS) sentences */ enum gps_msg { - UNKNOWN, /*!> neutral value */ - IGNORED, /*!> frame was not parsed by the system */ - INVALID, /*!> system try to parse frame but failed */ - /* NMEA messages of interest */ - NMEA_RMC, /*!> Recommended Minimum data (time + date) */ - NMEA_GGA, /*!> Global positioning system fix data (pos + alt) */ - NMEA_GNS, /*!> GNSS fix data (pos + alt, sat number) */ - NMEA_ZDA, /*!> Time and Date */ - /* NMEA message useful for time reference quality assessment */ - NMEA_GBS, /*!> GNSS Satellite Fault Detection */ - NMEA_GST, /*!> GNSS Pseudo Range Error Statistics */ - NMEA_GSA, /*!> GNSS DOP and Active Satellites (sat number) */ - NMEA_GSV, /*!> GNSS Satellites in View (sat SNR) */ - /* Misc. NMEA messages */ - NMEA_GLL, /*!> Latitude and longitude, with time fix and status */ - NMEA_TXT, /*!> Text Transmission */ - NMEA_VTG, /*!> Course over ground and Ground speed */ - /* uBlox proprietary NMEA messages of interest */ - UBX_POSITION, /*!> */ - UBX_TIME /*!> */ + UNKNOWN, /*!> neutral value */ + IGNORED, /*!> frame was not parsed by the system */ + INVALID, /*!> system try to parse frame but failed */ + /* NMEA messages of interest */ + NMEA_RMC, /*!> Recommended Minimum data (time + date) */ + NMEA_GGA, /*!> Global positioning system fix data (pos + alt) */ + NMEA_GNS, /*!> GNSS fix data (pos + alt, sat number) */ + NMEA_ZDA, /*!> Time and Date */ + /* NMEA message useful for time reference quality assessment */ + NMEA_GBS, /*!> GNSS Satellite Fault Detection */ + NMEA_GST, /*!> GNSS Pseudo Range Error Statistics */ + NMEA_GSA, /*!> GNSS DOP and Active Satellites (sat number) */ + NMEA_GSV, /*!> GNSS Satellites in View (sat SNR) */ + /* Misc. NMEA messages */ + NMEA_GLL, /*!> Latitude and longitude, with time fix and status */ + NMEA_TXT, /*!> Text Transmission */ + NMEA_VTG, /*!> Course over ground and Ground speed */ + /* uBlox proprietary NMEA messages of interest */ + UBX_POSITION, /*!> */ + UBX_TIME /*!> */ }; /* -------------------------------------------------------------------------- */ /* --- PUBLIC CONSTANTS ----------------------------------------------------- */ -#define LGW_GPS_SUCCESS 0 -#define LGW_GPS_ERROR -1 +#define LGW_GPS_SUCCESS 0 +#define LGW_GPS_ERROR -1 /* -------------------------------------------------------------------------- */ /* --- PUBLIC FUNCTIONS PROTOTYPES ------------------------------------------ */ @@ -113,9 +108,9 @@ int lgw_gps_enable(char* tty_path, char* gps_familly, speed_t target_brate, int* @param buff_size maximum string lengths for NMEA parsing (incl. null char) @return type of frame parsed -The RAW NMEA sentences are parsed to a global set of variables shared with the +The RAW NMEA sentences are parsed to a global set of variables shared with the lgw_gps_get function. -If the lgw_parse_nmea and lgw_gps_get are used in different threads, a mutex +If the lgw_parse_nmea and lgw_gps_get are used in different threads, a mutex lock must be acquired before calling either function. */ enum gps_msg lgw_parse_nmea(char* serial_buff, int buff_size); @@ -128,10 +123,10 @@ enum gps_msg lgw_parse_nmea(char* serial_buff, int buff_size); @param err pointer to store coordinates standard deviation (NULL to ignore) @return success if the chosen elements could be returned -This function read the global variables generated by the NMEA parsing function -lgw_parse_nmea. It returns time and location data in a format that is +This function read the global variables generated by the NMEA parsing function +lgw_parse_nmea. It returns time and location data in a format that is exploitable by other functions in that library sub-module. -If the lgw_parse_nmea and lgw_gps_get are used in different threads, a mutex +If the lgw_parse_nmea and lgw_gps_get are used in different threads, a mutex lock must be acquired before calling either function. */ int lgw_gps_get(struct timespec* utc, struct coord_s* loc, struct coord_s* err); @@ -156,8 +151,8 @@ int lgw_gps_sync(struct tref* ref, uint32_t count_us, struct timespec utc); @param utc pointer to store UTC time, with ns precision (leap seconds ignored) @return success if the function was able to convert timestamp to UTC -This function is typically used when a packet is received to transform the -internal counter-based timestamp in an absolute timestamp with an accuracy in +This function is typically used when a packet is received to transform the +internal counter-based timestamp in an absolute timestamp with an accuracy in the order of a couple microseconds (ns resolution). */ int lgw_cnt2utc(struct tref ref, uint32_t count_us, struct timespec* utc); @@ -170,8 +165,8 @@ int lgw_cnt2utc(struct tref ref, uint32_t count_us, struct timespec* utc); @param count_us pointer to store internal timestamp counter of LoRa concentrator @return success if the function was able to convert UTC to timestamp -This function is typically used when a packet must be sent at an accurate time -(eg. to send a piggy-back response after receiving a packet from a node) to +This function is typically used when a packet must be sent at an accurate time +(eg. to send a piggy-back response after receiving a packet from a node) to transform an absolute UTC time into a matching internal concentrator timestamp. */ int lgw_utc2cnt(struct tref ref,struct timespec utc, uint32_t* count_us); diff --git a/libloragw/inc/loragw_hal.h b/libloragw/inc/loragw_hal.h index 54251f89..5547dba1 100644 --- a/libloragw/inc/loragw_hal.h +++ b/libloragw/inc/loragw_hal.h @@ -7,7 +7,7 @@ (C)2013 Semtech-Cycleo Description: - LoRa concentrator Hardware Abstraction Layer + LoRa concentrator Hardware Abstraction Layer License: Revised BSD License, see LICENSE.TXT file include in the project Maintainer: Sylvain Miermont @@ -20,133 +20,134 @@ Maintainer: Sylvain Miermont /* -------------------------------------------------------------------------- */ /* --- DEPENDANCIES --------------------------------------------------------- */ -#include /* C99 types */ -#include /* bool type */ +#include /* C99 types */ +#include /* bool type */ -#include "config.h" /* library configuration options (dynamically generated) */ +#include "config.h" /* library configuration options (dynamically generated) */ /* -------------------------------------------------------------------------- */ /* --- PUBLIC MACROS -------------------------------------------------------- */ -#define IS_LORA_BW(bw) ((bw == BW_125KHZ) || (bw == BW_250KHZ) || (bw == BW_500KHZ)) -#define IS_LORA_STD_DR(dr) ((dr == DR_LORA_SF7) || (dr == DR_LORA_SF8) || (dr == DR_LORA_SF9) || (dr == DR_LORA_SF10) || (dr == DR_LORA_SF11) || (dr == DR_LORA_SF12)) -#define IS_LORA_MULTI_DR(dr) ((dr & ~DR_LORA_MULTI) == 0) /* ones outside of DR_LORA_MULTI bitmask -> not a combination of LoRa datarates */ -#define IS_LORA_CR(cr) ((cr == CR_LORA_4_5) || (cr == CR_LORA_4_6) || (cr == CR_LORA_4_7) || (cr == CR_LORA_4_8)) +#define IS_LORA_BW(bw) ((bw == BW_125KHZ) || (bw == BW_250KHZ) || (bw == BW_500KHZ)) +#define IS_LORA_STD_DR(dr) ((dr == DR_LORA_SF7) || (dr == DR_LORA_SF8) || (dr == DR_LORA_SF9) || (dr == DR_LORA_SF10) || (dr == DR_LORA_SF11) || (dr == DR_LORA_SF12)) +#define IS_LORA_MULTI_DR(dr) ((dr & ~DR_LORA_MULTI) == 0) /* ones outside of DR_LORA_MULTI bitmask -> not a combination of LoRa datarates */ +#define IS_LORA_CR(cr) ((cr == CR_LORA_4_5) || (cr == CR_LORA_4_6) || (cr == CR_LORA_4_7) || (cr == CR_LORA_4_8)) -#define IS_FSK_BW(bw) ((bw >= 1) && (bw <= 7)) -#define IS_FSK_DR(dr) ((dr >= DR_FSK_MIN) && (dr <= DR_FSK_MAX)) +#define IS_FSK_BW(bw) ((bw >= 1) && (bw <= 7)) +#define IS_FSK_DR(dr) ((dr >= DR_FSK_MIN) && (dr <= DR_FSK_MAX)) -#define IS_TX_MODE(mode) ((mode == IMMEDIATE) || (mode == TIMESTAMPED) || (mode == ON_GPS)) +#define IS_TX_MODE(mode) ((mode == IMMEDIATE) || (mode == TIMESTAMPED) || (mode == ON_GPS)) /* -------------------------------------------------------------------------- */ /* --- PUBLIC CONSTANTS ----------------------------------------------------- */ /* return status code */ -#define LGW_HAL_SUCCESS 0 -#define LGW_HAL_ERROR -1 +#define LGW_HAL_SUCCESS 0 +#define LGW_HAL_ERROR -1 +#define LGW_LBT_ISSUE 1 /* radio-specific parameters */ -#define LGW_XTAL_FREQU 32000000 /* frequency of the RF reference oscillator */ -#define LGW_RF_CHAIN_NB 2 /* number of RF chains */ -#define LGW_RF_RX_BANDWIDTH { 1000000, 1000000} /* bandwidth of the radios */ +#define LGW_XTAL_FREQU 32000000 /* frequency of the RF reference oscillator */ +#define LGW_RF_CHAIN_NB 2 /* number of RF chains */ +#define LGW_RF_RX_BANDWIDTH {1000000, 1000000} /* bandwidth of the radios */ /* type of if_chain + modem */ -#define IF_UNDEFINED 0 -#define IF_LORA_STD 0x10 /* if + standard single-SF LoRa modem */ -#define IF_LORA_MULTI 0x11 /* if + LoRa receiver with multi-SF capability */ -#define IF_FSK_STD 0x20 /* if + standard FSK modem */ +#define IF_UNDEFINED 0 +#define IF_LORA_STD 0x10 /* if + standard single-SF LoRa modem */ +#define IF_LORA_MULTI 0x11 /* if + LoRa receiver with multi-SF capability */ +#define IF_FSK_STD 0x20 /* if + standard FSK modem */ /* concentrator chipset-specific parameters */ /* to use array parameters, declare a local const and use 'if_chain' as index */ -#define LGW_IF_CHAIN_NB 10 /* number of IF+modem RX chains */ -#define LGW_PKT_FIFO_SIZE 8 /* depth of the RX packet FIFO */ -#define LGW_DATABUFF_SIZE 1024 /* size in bytes of the RX data buffer (contains payload & metadata) */ -#define LGW_REF_BW 125000 /* typical bandwidth of data channel */ -#define LGW_MULTI_NB 8 /* number of LoRa 'multi SF' chains */ +#define LGW_IF_CHAIN_NB 10 /* number of IF+modem RX chains */ +#define LGW_PKT_FIFO_SIZE 8 /* depth of the RX packet FIFO */ +#define LGW_DATABUFF_SIZE 1024 /* size in bytes of the RX data buffer (contains payload & metadata) */ +#define LGW_REF_BW 125000 /* typical bandwidth of data channel */ +#define LGW_MULTI_NB 8 /* number of LoRa 'multi SF' chains */ #define LGW_IFMODEM_CONFIG {\ - IF_LORA_MULTI, \ - IF_LORA_MULTI, \ - IF_LORA_MULTI, \ - IF_LORA_MULTI, \ - IF_LORA_MULTI, \ - IF_LORA_MULTI, \ - IF_LORA_MULTI, \ - IF_LORA_MULTI, \ - IF_LORA_STD, \ - IF_FSK_STD } /* configuration of available IF chains and modems on the hardware */ + IF_LORA_MULTI, \ + IF_LORA_MULTI, \ + IF_LORA_MULTI, \ + IF_LORA_MULTI, \ + IF_LORA_MULTI, \ + IF_LORA_MULTI, \ + IF_LORA_MULTI, \ + IF_LORA_MULTI, \ + IF_LORA_STD, \ + IF_FSK_STD } /* configuration of available IF chains and modems on the hardware */ /* values available for the 'modulation' parameters */ /* NOTE: arbitrary values */ -#define MOD_UNDEFINED 0 -#define MOD_LORA 0x10 -#define MOD_FSK 0x20 +#define MOD_UNDEFINED 0 +#define MOD_LORA 0x10 +#define MOD_FSK 0x20 /* values available for the 'bandwidth' parameters (LoRa & FSK) */ /* NOTE: directly encode FSK RX bandwidth, do not change */ -#define BW_UNDEFINED 0 -#define BW_500KHZ 0x01 -#define BW_250KHZ 0x02 -#define BW_125KHZ 0x03 -#define BW_62K5HZ 0x04 -#define BW_31K2HZ 0x05 -#define BW_15K6HZ 0x06 -#define BW_7K8HZ 0x07 +#define BW_UNDEFINED 0 +#define BW_500KHZ 0x01 +#define BW_250KHZ 0x02 +#define BW_125KHZ 0x03 +#define BW_62K5HZ 0x04 +#define BW_31K2HZ 0x05 +#define BW_15K6HZ 0x06 +#define BW_7K8HZ 0x07 /* values available for the 'datarate' parameters */ /* NOTE: LoRa values used directly to code SF bitmask in 'multi' modem, do not change */ -#define DR_UNDEFINED 0 -#define DR_LORA_SF7 0x02 -#define DR_LORA_SF8 0x04 -#define DR_LORA_SF9 0x08 -#define DR_LORA_SF10 0x10 -#define DR_LORA_SF11 0x20 -#define DR_LORA_SF12 0x40 -#define DR_LORA_MULTI 0x7E +#define DR_UNDEFINED 0 +#define DR_LORA_SF7 0x02 +#define DR_LORA_SF8 0x04 +#define DR_LORA_SF9 0x08 +#define DR_LORA_SF10 0x10 +#define DR_LORA_SF11 0x20 +#define DR_LORA_SF12 0x40 +#define DR_LORA_MULTI 0x7E /* NOTE: for FSK directly use baudrate between 500 bauds and 250 kbauds */ -#define DR_FSK_MIN 500 -#define DR_FSK_MAX 250000 +#define DR_FSK_MIN 500 +#define DR_FSK_MAX 250000 /* values available for the 'coderate' parameters (LoRa only) */ /* NOTE: arbitrary values */ -#define CR_UNDEFINED 0 -#define CR_LORA_4_5 0x01 -#define CR_LORA_4_6 0x02 -#define CR_LORA_4_7 0x03 -#define CR_LORA_4_8 0x04 +#define CR_UNDEFINED 0 +#define CR_LORA_4_5 0x01 +#define CR_LORA_4_6 0x02 +#define CR_LORA_4_7 0x03 +#define CR_LORA_4_8 0x04 /* values available for the 'status' parameter */ /* NOTE: values according to hardware specification */ -#define STAT_UNDEFINED 0x00 -#define STAT_NO_CRC 0x01 -#define STAT_CRC_BAD 0x11 -#define STAT_CRC_OK 0x10 +#define STAT_UNDEFINED 0x00 +#define STAT_NO_CRC 0x01 +#define STAT_CRC_BAD 0x11 +#define STAT_CRC_OK 0x10 /* values available for the 'tx_mode' parameter */ -#define IMMEDIATE 0 -#define TIMESTAMPED 1 -#define ON_GPS 2 -//#define ON_EVENT 3 -//#define GPS_DELAYED 4 -//#define EVENT_DELAYED 5 +#define IMMEDIATE 0 +#define TIMESTAMPED 1 +#define ON_GPS 2 +//#define ON_EVENT 3 +//#define GPS_DELAYED 4 +//#define EVENT_DELAYED 5 /* values available for 'select' in the status function */ -#define TX_STATUS 1 -#define RX_STATUS 2 +#define TX_STATUS 1 +#define RX_STATUS 2 /* status code for TX_STATUS */ /* NOTE: arbitrary values */ -#define TX_STATUS_UNKNOWN 0 -#define TX_OFF 1 /* TX modem disabled, it will ignore commands */ -#define TX_FREE 2 /* TX modem is free, ready to receive a command */ -#define TX_SCHEDULED 3 /* TX modem is loaded, ready to send the packet after an event and/or delay */ -#define TX_EMITTING 4 /* TX modem is emitting */ +#define TX_STATUS_UNKNOWN 0 +#define TX_OFF 1 /* TX modem disabled, it will ignore commands */ +#define TX_FREE 2 /* TX modem is free, ready to receive a command */ +#define TX_SCHEDULED 3 /* TX modem is loaded, ready to send the packet after an event and/or delay */ +#define TX_EMITTING 4 /* TX modem is emitting */ /* status code for RX_STATUS */ /* NOTE: arbitrary values */ -#define RX_STATUS_UNKNOWN 0 -#define RX_OFF 1 /* RX modem is disabled, it will ignore commands */ -#define RX_ON 2 /* RX modem is receiving */ -#define RX_SUSPENDED 3 /* RX is suspended while a TX is ongoing */ +#define RX_STATUS_UNKNOWN 0 +#define RX_OFF 1 /* RX modem is disabled, it will ignore commands */ +#define RX_ON 2 /* RX modem is receiving */ +#define RX_SUSPENDED 3 /* RX is suspended while a TX is ongoing */ /* Maximum size of Tx gain LUT */ #define TX_GAIN_LUT_SIZE_MAX 16 @@ -154,10 +155,16 @@ Maintainer: Sylvain Miermont /* -------------------------------------------------------------------------- */ /* --- PUBLIC TYPES --------------------------------------------------------- */ +/** +@enum lgw_radio_type_e +@brief Radio types that can be found on the LoRa Gateway +*/ enum lgw_radio_type_e { - LGW_RADIO_TYPE_NONE, - LGW_RADIO_TYPE_SX1255, - LGW_RADIO_TYPE_SX1257 + LGW_RADIO_TYPE_NONE, + LGW_RADIO_TYPE_SX1255, + LGW_RADIO_TYPE_SX1257, + LGW_RADIO_TYPE_SX1272, + LGW_RADIO_TYPE_SX1276 }; /** @@ -165,8 +172,22 @@ enum lgw_radio_type_e { @brief Configuration structure for board specificities */ struct lgw_conf_board_s { - bool lorawan_public; /*!> Enable ONLY for *public* networks using the LoRa MAC protocol */ - uint8_t clksrc; /*!> Index of RF chain which provides clock to concentrator */ + bool lorawan_public; /*!> Enable ONLY for *public* networks using the LoRa MAC protocol */ + uint8_t clksrc; /*!> Index of RF chain which provides clock to concentrator */ +}; + +/** +@struct lgw_conf_lbt_s +@brief Configuration structure for LBT specificities +*/ +struct lgw_conf_lbt_s { + bool enable; /*!> enable or disable LBT */ + uint8_t rssi_target; /*!> RSSI threshold to detect if channel is busy or not */ + uint8_t nb_channel; /*!> number of LBT channels */ + uint16_t scan_time_us; /*!> channel activity scan duration, in microseconds */ + uint32_t start_freq; /*!> first LBT channel frequency */ + uint32_t tx_delay_1ch_us; /*!> maximum time allowed to send a packet since channel was free, when TX is on one channel only */ + uint32_t tx_delay_2ch_us; /*!> maximum time allowed to send a packet since channel was free, when TX is on two channels */ }; /** @@ -174,11 +195,11 @@ struct lgw_conf_board_s { @brief Configuration structure for a RF chain */ struct lgw_conf_rxrf_s { - bool enable; /*!> enable or disable that RF chain */ - uint32_t freq_hz; /*!> center frequency of the radio in Hz */ - float rssi_offset; /*!> Board-specific RSSI correction factor */ - enum lgw_radio_type_e type; /*!> Radio type for that RF chain (SX1255, SX1257....) */ - bool tx_enable; /*!> enable or disable TX on that RF chain */ + bool enable; /*!> enable or disable that RF chain */ + uint32_t freq_hz; /*!> center frequency of the radio in Hz */ + float rssi_offset; /*!> Board-specific RSSI correction factor */ + enum lgw_radio_type_e type; /*!> Radio type for that RF chain (SX1255, SX1257....) */ + bool tx_enable; /*!> enable or disable TX on that RF chain */ }; /** @@ -186,13 +207,13 @@ struct lgw_conf_rxrf_s { @brief Configuration structure for an IF chain */ struct lgw_conf_rxif_s { - bool enable; /*!> enable or disable that IF chain */ - uint8_t rf_chain; /*!> to which RF chain is that IF chain associated */ - int32_t freq_hz; /*!> center frequ of the IF chain, relative to RF chain frequency */ - uint8_t bandwidth; /*!> RX bandwidth, 0 for default */ - uint32_t datarate; /*!> RX datarate, 0 for default */ - uint8_t sync_word_size; /*!> size of FSK sync word (number of bytes, 0 for default) */ - uint64_t sync_word; /*!> FSK sync word (ALIGN RIGHT, eg. 0xC194C1) */ + bool enable; /*!> enable or disable that IF chain */ + uint8_t rf_chain; /*!> to which RF chain is that IF chain associated */ + int32_t freq_hz; /*!> center frequ of the IF chain, relative to RF chain frequency */ + uint8_t bandwidth; /*!> RX bandwidth, 0 for default */ + uint32_t datarate; /*!> RX datarate, 0 for default */ + uint8_t sync_word_size; /*!> size of FSK sync word (number of bytes, 0 for default) */ + uint64_t sync_word; /*!> FSK sync word (ALIGN RIGHT, eg. 0xC194C1) */ }; /** @@ -200,22 +221,22 @@ struct lgw_conf_rxif_s { @brief Structure containing the metadata of a packet that was received and a pointer to the payload */ struct lgw_pkt_rx_s { - uint32_t freq_hz; /*!> central frequency of the IF chain */ - uint8_t if_chain; /*!> by which IF chain was packet received */ - uint8_t status; /*!> status of the received packet */ - uint32_t count_us; /*!> internal concentrator counter for timestamping, 1 microsecond resolution */ - uint8_t rf_chain; /*!> through which RF chain the packet was received */ - uint8_t modulation; /*!> modulation used by the packet */ - uint8_t bandwidth; /*!> modulation bandwidth (LoRa only) */ - uint32_t datarate; /*!> RX datarate of the packet (SF for LoRa) */ - uint8_t coderate; /*!> error-correcting code of the packet (LoRa only) */ - float rssi; /*!> average packet RSSI in dB */ - float snr; /*!> average packet SNR, in dB (LoRa only) */ - float snr_min; /*!> minimum packet SNR, in dB (LoRa only) */ - float snr_max; /*!> maximum packet SNR, in dB (LoRa only) */ - uint16_t crc; /*!> CRC that was received in the payload */ - uint16_t size; /*!> payload size in bytes */ - uint8_t payload[256]; /*!> buffer containing the payload */ + uint32_t freq_hz; /*!> central frequency of the IF chain */ + uint8_t if_chain; /*!> by which IF chain was packet received */ + uint8_t status; /*!> status of the received packet */ + uint32_t count_us; /*!> internal concentrator counter for timestamping, 1 microsecond resolution */ + uint8_t rf_chain; /*!> through which RF chain the packet was received */ + uint8_t modulation; /*!> modulation used by the packet */ + uint8_t bandwidth; /*!> modulation bandwidth (LoRa only) */ + uint32_t datarate; /*!> RX datarate of the packet (SF for LoRa) */ + uint8_t coderate; /*!> error-correcting code of the packet (LoRa only) */ + float rssi; /*!> average packet RSSI in dB */ + float snr; /*!> average packet SNR, in dB (LoRa only) */ + float snr_min; /*!> minimum packet SNR, in dB (LoRa only) */ + float snr_max; /*!> maximum packet SNR, in dB (LoRa only) */ + uint16_t crc; /*!> CRC that was received in the payload */ + uint16_t size; /*!> payload size in bytes */ + uint8_t payload[256]; /*!> buffer containing the payload */ }; /** @@ -223,22 +244,22 @@ struct lgw_pkt_rx_s { @brief Structure containing the configuration of a packet to send and a pointer to the payload */ struct lgw_pkt_tx_s { - uint32_t freq_hz; /*!> center frequency of TX */ - uint8_t tx_mode; /*!> select on what event/time the TX is triggered */ - uint32_t count_us; /*!> timestamp or delay in microseconds for TX trigger */ - uint8_t rf_chain; /*!> through which RF chain will the packet be sent */ - int8_t rf_power; /*!> TX power, in dBm */ - uint8_t modulation; /*!> modulation to use for the packet */ - uint8_t bandwidth; /*!> modulation bandwidth (LoRa only) */ - uint32_t datarate; /*!> TX datarate (baudrate for FSK, SF for LoRa) */ - uint8_t coderate; /*!> error-correcting code of the packet (LoRa only) */ - bool invert_pol; /*!> invert signal polarity, for orthogonal downlinks (LoRa only) */ - uint8_t f_dev; /*!> frequency deviation, in kHz (FSK only) */ - uint16_t preamble; /*!> set the preamble length, 0 for default */ - bool no_crc; /*!> if true, do not send a CRC in the packet */ - bool no_header; /*!> if true, enable implicit header mode (LoRa), fixed length (FSK) */ - uint16_t size; /*!> payload size in bytes */ - uint8_t payload[256]; /*!> buffer containing the payload */ + uint32_t freq_hz; /*!> center frequency of TX */ + uint8_t tx_mode; /*!> select on what event/time the TX is triggered */ + uint32_t count_us; /*!> timestamp or delay in microseconds for TX trigger */ + uint8_t rf_chain; /*!> through which RF chain will the packet be sent */ + int8_t rf_power; /*!> TX power, in dBm */ + uint8_t modulation; /*!> modulation to use for the packet */ + uint8_t bandwidth; /*!> modulation bandwidth (LoRa only) */ + uint32_t datarate; /*!> TX datarate (baudrate for FSK, SF for LoRa) */ + uint8_t coderate; /*!> error-correcting code of the packet (LoRa only) */ + bool invert_pol; /*!> invert signal polarity, for orthogonal downlinks (LoRa only) */ + uint8_t f_dev; /*!> frequency deviation, in kHz (FSK only) */ + uint16_t preamble; /*!> set the preamble length, 0 for default */ + bool no_crc; /*!> if true, do not send a CRC in the packet */ + bool no_header; /*!> if true, enable implicit header mode (LoRa), fixed length (FSK) */ + uint16_t size; /*!> payload size in bytes */ + uint8_t payload[256]; /*!> buffer containing the payload */ }; /** @@ -246,11 +267,11 @@ struct lgw_pkt_tx_s { @brief Structure containing all gains of Tx chain */ struct lgw_tx_gain_s { - uint8_t dig_gain; /*!> 2 bits, control of the digital gain of SX1301 */ - uint8_t pa_gain; /*!> 2 bits, control of the external PA (SX1301 I/O) */ - uint8_t dac_gain; /*!> 2 bits, control of the radio DAC */ - uint8_t mix_gain; /*!> 4 bits, control of the radio mixer */ - int8_t rf_power; /*!> measured TX power at the board connector, in dBm */ + uint8_t dig_gain; /*!> 2 bits, control of the digital gain of SX1301 */ + uint8_t pa_gain; /*!> 2 bits, control of the external PA (SX1301 I/O) */ + uint8_t dac_gain; /*!> 2 bits, control of the radio DAC */ + uint8_t mix_gain; /*!> 4 bits, control of the radio mixer */ + int8_t rf_power; /*!> measured TX power at the board connector, in dBm */ }; /** @@ -258,11 +279,10 @@ struct lgw_tx_gain_s { @brief Structure defining the Tx gain LUT */ struct lgw_tx_gain_lut_s { - struct lgw_tx_gain_s lut[TX_GAIN_LUT_SIZE_MAX]; /*!> Array of Tx gain struct */ - uint8_t size; /*!> Number of LUT indexes */ + struct lgw_tx_gain_s lut[TX_GAIN_LUT_SIZE_MAX]; /*!> Array of Tx gain struct */ + uint8_t size; /*!> Number of LUT indexes */ }; - /* -------------------------------------------------------------------------- */ /* --- PUBLIC FUNCTIONS PROTOTYPES ------------------------------------------ */ @@ -273,6 +293,13 @@ struct lgw_tx_gain_lut_s { */ int lgw_board_setconf(struct lgw_conf_board_s conf); +/** +@brief Configure the gateway lbt function +@param conf structure containing the configuration parameters +@return LGW_HAL_ERROR id the operation failed, LGW_HAL_SUCCESS else +*/ +int lgw_lbt_setconf(struct lgw_conf_lbt_s conf); + /** @brief Configure an RF chain (must configure before start) @param rf_chain number of the RF chain to configure [0, LGW_RF_CHAIN_NB - 1] @@ -330,7 +357,7 @@ int lgw_send(struct lgw_pkt_tx_s pkt_data); /** @brief Give the the status of different part of the LoRa concentrator -@param select is used to select what status we want to know +@param select is used to select what status we want to know @param code is used to return the status code @return LGW_HAL_ERROR id the operation failed, LGW_HAL_SUCCESS else */ @@ -355,6 +382,14 @@ int lgw_get_trigcnt(uint32_t* trig_cnt_us); */ const char* lgw_version_info(void); +/** +@brief Return time on air of given packet, in milliseconds +@param packet is a pointer to the packet structure +@param isBeacon indicates if the packet is a beacon or not +@return the packet time on air in milliseconds +*/ +uint32_t lgw_time_on_air(struct lgw_pkt_tx_s *packet, bool isBeacon); + #endif /* --- EOF ------------------------------------------------------------------ */ diff --git a/libloragw/inc/loragw_lbt.h b/libloragw/inc/loragw_lbt.h new file mode 100644 index 00000000..48ae4601 --- /dev/null +++ b/libloragw/inc/loragw_lbt.h @@ -0,0 +1,68 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2013 Semtech-Cycleo + +Description: + Functions used to handle the Listen Before Talk feature + +License: Revised BSD License, see LICENSE.TXT file include in the project +Maintainer: Michael Coracin +*/ + +#ifndef _LORAGW_LBT_H +#define _LORAGW_LBT_H + +/* -------------------------------------------------------------------------- */ +/* --- DEPENDANCIES --------------------------------------------------------- */ + +#include /* C99 types */ +#include /* bool type */ + +#include "loragw_hal.h" + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC CONSTANTS ----------------------------------------------------- */ + +#define LGW_LBT_SUCCESS 0 +#define LGW_LBT_ERROR -1 + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC FUNCTIONS PROTOTYPES ------------------------------------------ */ + +/** +@brief Set the configuration parameters for LBT feature +@param conf structure containing the configuration parameters +@return LGW_LBT_ERROR id the operation failed, LGW_LBT_SUCCESS else +*/ +int lbt_setconf(struct lgw_conf_lbt_s * conf); + +/** +@brief Configure the concentrator for LBT feature +@param rf_freq frequency in Hz of the first LBT channel +@param rssi_target RSSI threshold used to determine if LBT channel is busy or not +@param scan_time_us duration of channel activity scanning, in microseconds +@param nb_channel number of LBT channels +@return LGW_LBT_ERROR id the operation failed, LGW_LBT_SUCCESS else +*/ +int lbt_setup(uint32_t rf_freq, uint8_t rssi_target, uint16_t scan_time_us, uint8_t nb_channel); + +/** +@brief Start the LBT FSM +@return LGW_LBT_ERROR id the operation failed, LGW_LBT_SUCCESS else +*/ +int lbt_start(void); + +/** +@brief Configure the concentrator for LBT feature +@param pkt_data pointer to downlink packet to be trabsmitted +@param tx_allowed pointer to receive permission for transmission +@return LGW_LBT_ERROR id the operation failed, LGW_LBT_SUCCESS else +*/ +int lbt_is_channel_free(struct lgw_pkt_tx_s * pkt_data, bool * tx_allowed); + +#endif +/* --- EOF ------------------------------------------------------------------ */ diff --git a/libloragw/inc/loragw_radio.h b/libloragw/inc/loragw_radio.h new file mode 100644 index 00000000..30831ce2 --- /dev/null +++ b/libloragw/inc/loragw_radio.h @@ -0,0 +1,45 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2013 Semtech-Cycleo + +Description: + Functions used to handle LoRa concentrator radios. + +License: Revised BSD License, see LICENSE.TXT file include in the project +Maintainer: Michael Coracin +*/ + +#ifndef _LORAGW_RADIO_H +#define _LORAGW_RADIO_H + +/* -------------------------------------------------------------------------- */ +/* --- DEPENDANCIES --------------------------------------------------------- */ + +#include /* C99 types */ +#include /* bool type */ + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC CONSTANTS ----------------------------------------------------- */ + +#define LGW_REG_SUCCESS 0 +#define LGW_REG_ERROR -1 + +#define SX125x_32MHz_FRAC 15625 /* irreductible fraction for PLL register caculation */ + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC FUNCTIONS PROTOTYPES ------------------------------------------ */ + +int setup_sx125x(uint8_t rf_chain, uint8_t rf_clkout, bool rf_enable, uint8_t rf_radio_type, uint32_t freq_hz); + +int lgw_sx127x_reg_w(uint8_t address, uint8_t reg_value); + +int lgw_sx127x_reg_r(uint8_t address, uint8_t *reg_value); + +int lgw_setup_sx127x(uint32_t frequency, uint8_t modulation); + +#endif +/* --- EOF ------------------------------------------------------------------ */ diff --git a/libloragw/inc/loragw_reg.h b/libloragw/inc/loragw_reg.h index 49a03c94..d034b07e 100644 --- a/libloragw/inc/loragw_reg.h +++ b/libloragw/inc/loragw_reg.h @@ -7,10 +7,10 @@ (C)2013 Semtech-Cycleo Description: - Functions used to handle a single LoRa concentrator. - Registers are addressed by name. - Multi-bytes registers are handled automatically. - Read-modify-write is handled automatically. + Functions used to handle a single LoRa concentrator. + Registers are addressed by name. + Multi-bytes registers are handled automatically. + Read-modify-write is handled automatically. License: Revised BSD License, see LICENSE.TXT file include in the project Maintainer: Sylvain Miermont @@ -23,16 +23,35 @@ Maintainer: Sylvain Miermont /* -------------------------------------------------------------------------- */ /* --- DEPENDANCIES --------------------------------------------------------- */ -#include /* C99 types */ -#include /* bool type */ +#include /* C99 types */ +#include /* bool type */ -#include "config.h" /* library configuration options (dynamically generated) */ +#include "config.h" /* library configuration options (dynamically generated) */ + +/* -------------------------------------------------------------------------- */ +/* --- INTERNAL SHARED TYPES ------------------------------------------------ */ + +struct lgw_reg_s { + int8_t page; /*!< page containing the register (-1 for all pages) */ + uint8_t addr; /*!< base address of the register (7 bit) */ + uint8_t offs; /*!< position of the register LSB (between 0 to 7) */ + bool sign; /*!< 1 indicates the register is signed (2 complem.) */ + uint8_t leng; /*!< number of bits in the register */ + bool rdon; /*!< 1 indicates a read-only register */ + int32_t dflt; /*!< register default value */ +}; + +/* -------------------------------------------------------------------------- */ +/* --- INTERNAL SHARED FUNCTIONS -------------------------------------------- */ + +int reg_w_align32(void *spi_target, uint8_t spi_mux_mode, uint8_t spi_mux_target, struct lgw_reg_s r, int32_t reg_value); +int reg_r_align32(void *spi_target, uint8_t spi_mux_mode, uint8_t spi_mux_target, struct lgw_reg_s r, int32_t *reg_value); /* -------------------------------------------------------------------------- */ /* --- PUBLIC CONSTANTS ----------------------------------------------------- */ -#define LGW_REG_SUCCESS 0 -#define LGW_REG_ERROR -1 +#define LGW_REG_SUCCESS 0 +#define LGW_REG_ERROR -1 /* auto generated register mapping for C code : 11-Jul-2013 13:20:40 diff --git a/libloragw/inc/loragw_spi.h b/libloragw/inc/loragw_spi.h index 8ce42dfa..fef1f48d 100644 --- a/libloragw/inc/loragw_spi.h +++ b/libloragw/inc/loragw_spi.h @@ -7,11 +7,11 @@ (C)2013 Semtech-Cycleo Description: - Host specific functions to address the LoRa concentrator registers through a - SPI interface. - Single-byte read/write and burst read/write. - Does not handle pagination. - Could be used with multiple SPI ports in parallel (explicit file descriptor) + Host specific functions to address the LoRa concentrator registers through a + SPI interface. + Single-byte read/write and burst read/write. + Does not handle pagination. + Could be used with multiple SPI ports in parallel (explicit file descriptor) License: Revised BSD License, see LICENSE.TXT file include in the project Maintainer: Sylvain Miermont @@ -24,16 +24,16 @@ Maintainer: Sylvain Miermont /* -------------------------------------------------------------------------- */ /* --- DEPENDANCIES --------------------------------------------------------- */ -#include /* C99 types*/ +#include /* C99 types*/ -#include "config.h" /* library configuration options (dynamically generated) */ +#include "config.h" /* library configuration options (dynamically generated) */ /* -------------------------------------------------------------------------- */ /* --- PUBLIC CONSTANTS ----------------------------------------------------- */ -#define LGW_SPI_SUCCESS 0 -#define LGW_SPI_ERROR -1 -#define LGW_BURST_CHUNK 1024 +#define LGW_SPI_SUCCESS 0 +#define LGW_SPI_ERROR -1 +#define LGW_BURST_CHUNK 1024 #define LGW_SPI_MUX_MODE0 0x0 /* No FPGA */ #define LGW_SPI_MUX_MODE1 0x1 /* FPGA, with spi mux header */ @@ -41,7 +41,7 @@ Maintainer: Sylvain Miermont #define LGW_SPI_MUX_TARGET_SX1301 0x0 #define LGW_SPI_MUX_TARGET_FPGA 0x1 #define LGW_SPI_MUX_TARGET_EEPROM 0x2 -#define LGW_SPI_MUX_TARGET_SX1272 0x3 +#define LGW_SPI_MUX_TARGET_SX127X 0x3 /* -------------------------------------------------------------------------- */ /* --- PUBLIC FUNCTIONS PROTOTYPES ------------------------------------------ */ diff --git a/libloragw/inc/loragw_sx125x.h b/libloragw/inc/loragw_sx125x.h new file mode 100644 index 00000000..120ee680 --- /dev/null +++ b/libloragw/inc/loragw_sx125x.h @@ -0,0 +1,49 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2013 Semtech + +Description: SX125x radio registers and constant definitions + +License: Revised BSD License, see LICENSE.TXT file include in the project + +Maintainer: Michael Coracin +*/ +#ifndef __SX125X_REGS_H__ +#define __SX125X_REGS_H__ + +/* +SX1257 frequency setting : +F_register(24bit) = F_rf (Hz) / F_step(Hz) + = F_rf (Hz) * 2^19 / F_xtal(Hz) + = F_rf (Hz) * 2^19 / 32e6 + = F_rf (Hz) * 256/15625 + +SX1255 frequency setting : +F_register(24bit) = F_rf (Hz) / F_step(Hz) + = F_rf (Hz) * 2^20 / F_xtal(Hz) + = F_rf (Hz) * 2^20 / 32e6 + = F_rf (Hz) * 512/15625 +*/ + +#define SX125x_TX_DAC_CLK_SEL 1 /* 0:int, 1:ext */ +#define SX125x_TX_DAC_GAIN 2 /* 3:0, 2:-3, 1:-6, 0:-9 dBFS (default 2) */ +#define SX125x_TX_MIX_GAIN 14 /* -38 + 2*TxMixGain dB (default 14) */ +#define SX125x_TX_PLL_BW 1 /* 0:75, 1:150, 2:225, 3:300 kHz (default 3) */ +#define SX125x_TX_ANA_BW 0 /* 17.5 / 2*(41-TxAnaBw) MHz (default 0) */ +#define SX125x_TX_DAC_BW 5 /* 24 + 8*TxDacBw Nb FIR taps (default 2) */ +#define SX125x_RX_LNA_GAIN 1 /* 1 to 6, 1 highest gain */ +#define SX125x_RX_BB_GAIN 12 /* 0 to 15 , 15 highest gain */ +#define SX125x_LNA_ZIN 1 /* 0:50, 1:200 Ohms (default 1) */ +#define SX125x_RX_ADC_BW 7 /* 0 to 7, 2:100| read rssi | | + | +-----------+ | + The number of | | | + read defines the | v | The delay between 2 channel + CHANNEL_SCAN_TIME | +------------+ | scan is equal to + | | compare to | | PLL_LOCK_TIME + CHANNEL_SCAN_TIME + +---+ | | + | threshold | | + +------------+ | + | | + v | + +------------+ | + | update CH_n| | + | timestamp | | + +----------- + | + | | + v | + +-----------+ | + | update | | + | rf freq | | + +-----+-----+ | + | | + +----------+ + +In order to configure the LBT, the following parameters have to be set: + +SPI_MASTER_SPEED_DIVIDER: defines the internal SPI_MASTER SPI clock speed. + SPI_clock_freq = 32MHz / (SPI_MASTER_SPEED_DIVIDER*2) + +NB_RSSI_READ: defines the number of SPI reads of SX127x RSSI value + register. + +PLL_LOCK_TIME: defines the delay in 8µs step between frequency + programming and RX ready. + +RSSI_TARGET: defines the signal strength target used to detect if + the channel is busy or not. + RSSI_TARGET_dBm = -RSSI_TARGET / 2 + +Based on those parameters we have: + +CHANNEL_SCAN_TIME (µs) = (NB_RSSI_READ + 1) * Tspi + with Tspi (µs) = (16*(2*(SPI_MASTER_SPEED_DIVIDER+1))/32) + 2 + +With this FSM, the FPGA keeps the last instant when each channel was free during +more than CHANNEL_SCAN_TIME µs. + +Then, the HAL, when receiving a downlink request, will first determine on which +LBT channel this downlink is supposed to be sent and then checks if the channel +is busy or if downlink is allowed. + +In order to determine if a downlink is allowed or not, the HAL does: +- read the LBT_TIMESTAMP_CH of the channel on which downlink is requested. This + gives the last time when channel was free (LBT_TIME). +- compute the time on air of the downlink packet to determine the end time of + the packet emission (PKT_END_TIME). +- if ((PKT_END_TIME - LBT_TIME) < TX_MAX_TIME) + ALLOWED = TRUE + else + ALLOWED = FALSE + endif + where TX_MAX_TIME is the maximum time allowed to send a packet since the + last channel free time (this is given to the HAL as a configuration + parameter). + + 3. Software build process -------------------------- @@ -276,7 +386,6 @@ For a typical application you need to: * include loragw_hal.h in your program source * link to the libloragw.a static library during compilation * link to the librt library due to loragw_aux dependencies (timing functions) -* link to the libmpsse library if you use a FTDI SPI-over-USB bridge For an application that will also access the concentrator configuration registers directly (eg. for advanced configuration) you also need to: diff --git a/libloragw/src/loragw_aux.c b/libloragw/src/loragw_aux.c index 4958fa3e..c9212966 100644 --- a/libloragw/src/loragw_aux.c +++ b/libloragw/src/loragw_aux.c @@ -7,7 +7,7 @@ (C)2013 Semtech-Cycleo Description: - LoRa concentrator HAL auxiliary functions + LoRa concentrator HAL auxiliary functions License: Revised BSD License, see LICENSE.TXT file include in the project Maintainer: Sylvain Miermont @@ -19,23 +19,23 @@ Maintainer: Sylvain Miermont /* fix an issue between POSIX and C99 */ #if __STDC_VERSION__ >= 199901L - #define _XOPEN_SOURCE 600 + #define _XOPEN_SOURCE 600 #else - #define _XOPEN_SOURCE 500 + #define _XOPEN_SOURCE 500 #endif -#include /* printf fprintf */ -#include /* clock_nanosleep */ +#include /* printf fprintf */ +#include /* clock_nanosleep */ /* -------------------------------------------------------------------------- */ /* --- PRIVATE MACROS ------------------------------------------------------- */ #if DEBUG_AUX == 1 - #define DEBUG_MSG(str) fprintf(stderr, str) - #define DEBUG_PRINTF(fmt, args...) fprintf(stderr,"%s:%d: "fmt, __FUNCTION__, __LINE__, args) + #define DEBUG_MSG(str) fprintf(stderr, str) + #define DEBUG_PRINTF(fmt, args...) fprintf(stderr,"%s:%d: "fmt, __FUNCTION__, __LINE__, args) #else - #define DEBUG_MSG(str) - #define DEBUG_PRINTF(fmt, args...) + #define DEBUG_MSG(str) + #define DEBUG_PRINTF(fmt, args...) #endif /* -------------------------------------------------------------------------- */ @@ -43,19 +43,19 @@ Maintainer: Sylvain Miermont /* This implementation is POSIX-pecific and require a fix to be compatible with C99 */ void wait_ms(unsigned long a) { - struct timespec dly; - struct timespec rem; - - dly.tv_sec = a / 1000; - dly.tv_nsec = ((long)a % 1000) * 1000000; - - DEBUG_PRINTF("NOTE dly: %ld sec %ld ns\n", dly.tv_sec, dly.tv_nsec); - - if((dly.tv_sec > 0) || ((dly.tv_sec == 0) && (dly.tv_nsec > 100000))) { - clock_nanosleep(CLOCK_MONOTONIC, 0, &dly, &rem); - DEBUG_PRINTF("NOTE remain: %ld sec %ld ns\n", rem.tv_sec, rem.tv_nsec); - } - return; + struct timespec dly; + struct timespec rem; + + dly.tv_sec = a / 1000; + dly.tv_nsec = ((long)a % 1000) * 1000000; + + DEBUG_PRINTF("NOTE dly: %ld sec %ld ns\n", dly.tv_sec, dly.tv_nsec); + + if((dly.tv_sec > 0) || ((dly.tv_sec == 0) && (dly.tv_nsec > 100000))) { + clock_nanosleep(CLOCK_MONOTONIC, 0, &dly, &rem); + DEBUG_PRINTF("NOTE remain: %ld sec %ld ns\n", rem.tv_sec, rem.tv_nsec); + } + return; } /* --- EOF ------------------------------------------------------------------ */ diff --git a/util_spectral_scan/src/loragw_fpga_reg.c b/libloragw/src/loragw_fpga.c similarity index 50% rename from util_spectral_scan/src/loragw_fpga_reg.c rename to libloragw/src/loragw_fpga.c index 1c7e1bf7..717639b4 100644 --- a/util_spectral_scan/src/loragw_fpga_reg.c +++ b/libloragw/src/loragw_fpga.c @@ -7,16 +7,15 @@ (C)2013 Semtech-Cycleo Description: - Functions used to handle a single LoRa concentrator. + Functions used to handle FPGA register access for LoRa concentrator. Registers are addressed by name. Multi-bytes registers are handled automatically. Read-modify-write is handled automatically. License: Revised BSD License, see LICENSE.TXT file include in the project -Maintainer: Matthieu Leurent +Maintainer: Michael Coracin */ - /* -------------------------------------------------------------------------- */ /* --- DEPENDANCIES --------------------------------------------------------- */ @@ -24,8 +23,11 @@ Maintainer: Matthieu Leurent #include /* bool type */ #include /* printf fprintf */ -#include "loragw_fpga_spi.h" -#include "loragw_fpga_reg.h" +#include "loragw_spi.h" +#include "loragw_aux.h" +#include "loragw_hal.h" +#include "loragw_reg.h" +#include "loragw_fpga.h" /* -------------------------------------------------------------------------- */ /* --- PRIVATE MACROS ------------------------------------------------------- */ @@ -41,31 +43,9 @@ Maintainer: Matthieu Leurent #define CHECK_NULL(a) if(a==NULL){return LGW_REG_ERROR;} #endif -/* -------------------------------------------------------------------------- */ -/* --- PRIVATE TYPES -------------------------------------------------------- */ - -struct lgw_reg_s { - int8_t page; /*!< page containing the register (-1 for all pages) */ - uint8_t addr; /*!< base address of the register (7 bit) */ - uint8_t offs; /*!< position of the register LSB (between 0 to 7) */ - bool sign; /*!< 1 indicates the register is signed (2 complem.) */ - uint8_t leng; /*!< number of bits in the register */ - bool rdon; /*!< 1 indicates a read-only register */ - int32_t dflt; /*!< register default value */ -}; - /* -------------------------------------------------------------------------- */ /* --- PRIVATE CONSTANTS ---------------------------------------------------- */ -#define PAGE_ADDR 0x00 -#define PAGE_MASK 0x03 - -/* FPGA SPI Mux device IDs */ -#define FPGA_SPI_MUX_SX1301 0x00 /* header to access SX1301 */ -#define FPGA_SPI_MUX_FPGA_REG 0x01 /* header to access FPGA registers */ -#define FPGA_SPI_MUX_EEPROM 0x02 /* header to access EEPROM */ -#define FPGA_SPI_MUX_SX1272 0x03 /* header to access SX1272 radio */ - /* auto generated register mapping for C code : 11-Jul-2013 13:20:40 this file contains autogenerated C struct used to access the LoRa register from the Primer firmware @@ -74,75 +54,79 @@ this file is autogenerated from registers description */ const struct lgw_reg_s fpga_regs[LGW_FPGA_TOTALREGS] = { {-1,0,0,0,1,0,0}, /* SOFT_RESET */ - {-1,1,0,0,8,1,18}, /* VERSION */ + {-1,0,1,0,7,1,0}, /* FPGA_FEATURE */ + {-1,1,0,0,8,1,0}, /* VERSION */ {-1,2,0,0,8,1,0}, /* FPGA_STATUS */ - {-1,3,0,0,8,0,0}, /* FPGA_CTRL */ + {-1,3,0,0,1,0,0}, /* FPGA_CTRL_FEATURE_START */ + {-1,3,1,0,1,0,0}, /* FPGA_CTRL_RADIO_RESET */ + {-1,3,2,0,1,0,1}, /* FPGA_CTRL_INPUT_SYNC_I */ + {-1,3,3,0,1,0,1}, /* FPGA_CTRL_INPUT_SYNC_Q */ + {-1,3,4,0,1,0,0}, /* FPGA_CTRL_OUTPUT_SYNC */ + {-1,3,5,0,1,0,1}, /* FPGA_CTRL_INVERT_IQ */ {-1,4,0,0,8,0,0}, /* HISTO_RAM_ADDR */ {-1,5,0,0,8,1,0}, /* HISTO_RAM_DATA */ {-1,6,0,0,16,0,32000}, /* HISTO_TEMPO */ {-1,8,0,0,16,0,1000}, /* HISTO_NB_READ */ {-1,10,0,0,32,1,0}, /* TIMESTAMP */ + {-1,14,0,0,24,1,0}, /* LBT_TIMESTAMP_CH */ + {-1,17,0,0,8,0,0}, /* LBT_TIMESTAMP_SELECT_CH */ + {-1,18,0,0,8,0,8}, /* LBT_TIMESTAMP_NB_CH */ + {-1,19,0,0,8,0,7}, /* SPI_MASTER_SPEED_DIVIDER */ + {-1,20,0,0,8,0,10}, /* NB_READ_RSSI */ + {-1,21,0,0,8,0,10}, /* PLL_LOCK_TIME */ + {-1,22,0,0,8,0,160}, /* RSSI_TARGET */ + {-1,23,0,0,16,0,0}, /* LSB_START_FREQ */ {-1,127,0,0,8,0,0} /* SPI_MUX_CTRL */ }; /* -------------------------------------------------------------------------- */ /* --- PRIVATE VARIABLES ---------------------------------------------------- */ -void *spi_target = NULL; /*! generic pointer to the SPI device */ +extern void *lgw_spi_target; /*! generic pointer to the SPI device */ +extern uint8_t lgw_spi_mux_mode; /*! current SPI mux mode used */ /* -------------------------------------------------------------------------- */ /* --- PRIVATE FUNCTIONS ---------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE FUNCTIONS DEFINITION ----------------------------------------- */ + /* -------------------------------------------------------------------------- */ /* --- PUBLIC FUNCTIONS DEFINITION ------------------------------------------ */ -int lgw_fpga_connect(void) { - int spi_stat = LGW_SPI_SUCCESS; - uint8_t u = 0; - - if (spi_target != NULL) { - DEBUG_MSG("WARNING: FPGA was already connected\n"); - lgw_fpga_spi_close(spi_target); +int lgw_fpga_configure(void) { + int x; + int32_t val; + bool tx_filter_support, spectral_scan_support, lbt_support; + + /* Get supported FPGA features */ + printf("INFO: FPGA supported features:"); + lgw_fpga_reg_r(LGW_FPGA_FPGA_FEATURE, &val); + tx_filter_support = TAKE_N_BITS_FROM((uint8_t)val, 0, 1); + if (tx_filter_support) { + printf(" [TX filter] "); } - - /* open the SPI link */ - spi_stat = lgw_fpga_spi_open(&spi_target); - if (spi_stat != LGW_SPI_SUCCESS) { - DEBUG_MSG("ERROR CONNECTING FPGA\n"); - return LGW_REG_ERROR; + spectral_scan_support = TAKE_N_BITS_FROM((uint8_t)val, 1, 1); + if (spectral_scan_support) { + printf(" [Spectral Scan] "); } - - /* checking the version register */ - spi_stat = lgw_fpga_spi_r(spi_target, FPGA_SPI_MUX_FPGA_REG, fpga_regs[LGW_FPGA_VERSION].addr, &u); - if (spi_stat != LGW_SPI_SUCCESS) { - DEBUG_MSG("ERROR READING VERSION REGISTER\n"); - return LGW_REG_ERROR; - } - else if ((u == 0) || (u == 255)) { - DEBUG_MSG("ERROR: FPGA SEEMS DECONNECTED\n"); - return LGW_REG_ERROR; + lbt_support = TAKE_N_BITS_FROM((uint8_t)val, 2, 1); + if (lbt_support) { + printf(" [LBT] "); } - else if (u != fpga_regs[LGW_FPGA_VERSION].dflt) { - DEBUG_MSG("ERROR: NOT EXPECTED FPGA VERSION\n"); + printf("\n"); + + /* Configure TX filter */ + x = lgw_fpga_reg_w(LGW_FPGA_CTRL_INPUT_SYNC_I, 0); + x |= lgw_fpga_reg_w(LGW_FPGA_CTRL_INPUT_SYNC_Q, 0); + x |= lgw_fpga_reg_w(LGW_FPGA_CTRL_OUTPUT_SYNC, 1); + if( x != LGW_REG_SUCCESS ) + { + DEBUG_MSG("ERROR: Failed to configure FPGA TX filter\n"); return LGW_REG_ERROR; } - - DEBUG_MSG("Note: success connecting the FPGA\n"); - return LGW_REG_SUCCESS; -} - -/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ -int lgw_fpga_disconnect(void) { - if (spi_target != NULL) { - lgw_fpga_spi_close(spi_target); - spi_target = NULL; - DEBUG_MSG("Note: success disconnecting the concentrator\n"); - return LGW_REG_SUCCESS; - } else { - DEBUG_MSG("WARNING: concentrator was already disconnected\n"); - return LGW_REG_ERROR; - } + return LGW_REG_SUCCESS; } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ @@ -151,56 +135,30 @@ int lgw_fpga_disconnect(void) { int lgw_fpga_reg_w(uint16_t register_id, int32_t reg_value) { int spi_stat = LGW_SPI_SUCCESS; struct lgw_reg_s r; - uint8_t buf[4] = "\x00\x00\x00\x00"; - int i, size_byte; - + /* check input parameters */ if (register_id >= LGW_FPGA_TOTALREGS) { DEBUG_MSG("ERROR: REGISTER NUMBER OUT OF DEFINED RANGE\n"); return LGW_REG_ERROR; } - + /* check if SPI is initialised */ - if (spi_target == NULL) { + if (lgw_spi_target == NULL) { DEBUG_MSG("ERROR: CONCENTRATOR UNCONNECTED\n"); return LGW_REG_ERROR; } - + /* get register struct from the struct array */ r = fpga_regs[register_id]; - + /* reject write to read-only registers */ if (r.rdon == 1){ DEBUG_MSG("ERROR: TRYING TO WRITE A READ-ONLY REGISTER\n"); return LGW_REG_ERROR; } - - if ((r.leng == 8) && (r.offs == 0)) { - /* direct write */ - spi_stat += lgw_fpga_spi_w(spi_target, FPGA_SPI_MUX_FPGA_REG, r.addr, (uint8_t)reg_value); - } else if ((r.offs + r.leng) <= 8) { - /* single-byte read-modify-write, offs:[0-7], leng:[1-7] */ - spi_stat += lgw_fpga_spi_r(spi_target, FPGA_SPI_MUX_FPGA_REG, r.addr, &buf[0]); - buf[1] = ((1 << r.leng) - 1) << r.offs; /* bit mask */ - buf[2] = ((uint8_t)reg_value) << r.offs; /* new data offsetted */ - buf[3] = (~buf[1] & buf[0]) | (buf[1] & buf[2]); /* mixing old & new data */ - spi_stat += lgw_fpga_spi_w(spi_target, FPGA_SPI_MUX_FPGA_REG, r.addr, buf[3]); - } else if ((r.offs == 0) && (r.leng > 0) && (r.leng <= 32)) { - /* multi-byte direct write routine */ - size_byte = (r.leng + 7) / 8; /* add a byte if it's not an exact multiple of 8 */ - for (i=0; i> 8); - } - spi_stat += lgw_fpga_spi_wb(spi_target, FPGA_SPI_MUX_FPGA_REG, r.addr, buf, size_byte); /* write the register in one burst */ - } else { - /* register spanning multiple memory bytes but with an offset */ - DEBUG_MSG("ERROR: REGISTER SIZE AND OFFSET ARE NOT SUPPORTED\n"); - return LGW_REG_ERROR; - } - + + spi_stat += reg_w_align32(lgw_spi_target, LGW_SPI_MUX_MODE1, LGW_SPI_MUX_TARGET_FPGA, r, reg_value); + if (spi_stat != LGW_SPI_SUCCESS) { DEBUG_MSG("ERROR: SPI ERROR DURING REGISTER WRITE\n"); return LGW_REG_ERROR; @@ -215,57 +173,25 @@ int lgw_fpga_reg_w(uint16_t register_id, int32_t reg_value) { int lgw_fpga_reg_r(uint16_t register_id, int32_t *reg_value) { int spi_stat = LGW_SPI_SUCCESS; struct lgw_reg_s r; - uint8_t bufu[4] = "\x00\x00\x00\x00"; - int8_t *bufs = (int8_t *)bufu; - int i, size_byte; - uint32_t u = 0; - + /* check input parameters */ CHECK_NULL(reg_value); if (register_id >= LGW_FPGA_TOTALREGS) { DEBUG_MSG("ERROR: REGISTER NUMBER OUT OF DEFINED RANGE\n"); return LGW_REG_ERROR; } - + /* check if SPI is initialised */ - if (spi_target == NULL) { + if (lgw_spi_target == NULL) { DEBUG_MSG("ERROR: CONCENTRATOR UNCONNECTED\n"); return LGW_REG_ERROR; } - + /* get register struct from the struct array */ r = fpga_regs[register_id]; - - if ((r.offs + r.leng) <= 8) { - /* read one byte, then shift and mask bits to get reg value with sign extension if needed */ - spi_stat += lgw_fpga_spi_r(spi_target, FPGA_SPI_MUX_FPGA_REG, r.addr, &bufu[0]); - bufu[1] = bufu[0] << (8 - r.leng - r.offs); /* left-align the data */ - if (r.sign == true) { - bufs[2] = bufs[1] >> (8 - r.leng); /* right align the data with sign extension (ARITHMETIC right shift) */ - *reg_value = (int32_t)bufs[2]; /* signed pointer -> 32b sign extension */ - } else { - bufu[2] = bufu[1] >> (8 - r.leng); /* right align the data, no sign extension */ - *reg_value = (int32_t)bufu[2]; /* unsigned pointer -> no sign extension */ - } - } else if ((r.offs == 0) && (r.leng > 0) && (r.leng <= 32)) { - size_byte = (r.leng + 7) / 8; /* add a byte if it's not an exact multiple of 8 */ - spi_stat += lgw_fpga_spi_rb(spi_target, FPGA_SPI_MUX_FPGA_REG, r.addr, bufu, size_byte); - u = 0; - for (i=(size_byte-1); i>=0; --i) { - u = (uint32_t)bufu[i] + (u << 8); /* transform a 4-byte array into a 32 bit word */ - } - if (r.sign == true) { - u = u << (32 - r.leng); /* left-align the data */ - *reg_value = (int32_t)u >> (32 - r.leng); /* right-align the data with sign extension (ARITHMETIC right shift) */ - } else { - *reg_value = (int32_t)u; /* unsigned value -> return 'as is' */ - } - } else { - /* register spanning multiple memory bytes but with an offset */ - DEBUG_MSG("ERROR: REGISTER SIZE AND OFFSET ARE NOT SUPPORTED\n"); - return LGW_REG_ERROR; - } - + + spi_stat += reg_r_align32(lgw_spi_target, LGW_SPI_MUX_MODE1, LGW_SPI_MUX_TARGET_FPGA, r, reg_value); + if (spi_stat != LGW_SPI_SUCCESS) { DEBUG_MSG("ERROR: SPI ERROR DURING REGISTER WRITE\n"); return LGW_REG_ERROR; @@ -280,7 +206,7 @@ int lgw_fpga_reg_r(uint16_t register_id, int32_t *reg_value) { int lgw_fpga_reg_wb(uint16_t register_id, uint8_t *data, uint16_t size) { int spi_stat = LGW_SPI_SUCCESS; struct lgw_reg_s r; - + /* check input parameters */ CHECK_NULL(data); if (size == 0) { @@ -291,25 +217,25 @@ int lgw_fpga_reg_wb(uint16_t register_id, uint8_t *data, uint16_t size) { DEBUG_MSG("ERROR: REGISTER NUMBER OUT OF DEFINED RANGE\n"); return LGW_REG_ERROR; } - + /* check if SPI is initialised */ - if (spi_target == NULL) { + if (lgw_spi_target == NULL) { DEBUG_MSG("ERROR: CONCENTRATOR UNCONNECTED\n"); return LGW_REG_ERROR; } - + /* get register struct from the struct array */ r = fpga_regs[register_id]; - + /* reject write to read-only registers */ if (r.rdon == 1){ DEBUG_MSG("ERROR: TRYING TO BURST WRITE A READ-ONLY REGISTER\n"); return LGW_REG_ERROR; } - + /* do the burst write */ - spi_stat += lgw_fpga_spi_wb(spi_target, FPGA_SPI_MUX_FPGA_REG, r.addr, data, size); - + spi_stat += lgw_spi_wb(lgw_spi_target, LGW_SPI_MUX_MODE1, LGW_SPI_MUX_TARGET_FPGA, r.addr, data, size); + if (spi_stat != LGW_SPI_SUCCESS) { DEBUG_MSG("ERROR: SPI ERROR DURING REGISTER BURST WRITE\n"); return LGW_REG_ERROR; @@ -324,7 +250,7 @@ int lgw_fpga_reg_wb(uint16_t register_id, uint8_t *data, uint16_t size) { int lgw_fpga_reg_rb(uint16_t register_id, uint8_t *data, uint16_t size) { int spi_stat = LGW_SPI_SUCCESS; struct lgw_reg_s r; - + /* check input parameters */ CHECK_NULL(data); if (size == 0) { @@ -335,19 +261,19 @@ int lgw_fpga_reg_rb(uint16_t register_id, uint8_t *data, uint16_t size) { DEBUG_MSG("ERROR: REGISTER NUMBER OUT OF DEFINED RANGE\n"); return LGW_REG_ERROR; } - + /* check if SPI is initialised */ - if (spi_target == NULL) { + if (lgw_spi_target == NULL) { DEBUG_MSG("ERROR: CONCENTRATOR UNCONNECTED\n"); return LGW_REG_ERROR; } - + /* get register struct from the struct array */ r = fpga_regs[register_id]; - + /* do the burst read */ - spi_stat += lgw_fpga_spi_rb(spi_target, FPGA_SPI_MUX_FPGA_REG, r.addr, data, size); - + spi_stat += lgw_spi_rb(lgw_spi_target, LGW_SPI_MUX_MODE1, LGW_SPI_MUX_TARGET_FPGA, r.addr, data, size); + if (spi_stat != LGW_SPI_SUCCESS) { DEBUG_MSG("ERROR: SPI ERROR DURING REGISTER BURST READ\n"); return LGW_REG_ERROR; @@ -356,16 +282,4 @@ int lgw_fpga_reg_rb(uint16_t register_id, uint8_t *data, uint16_t size) { } } -/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ - -int lgw_sx1272_reg_w(uint8_t address, uint8_t reg_value) { - return lgw_fpga_spi_w(spi_target, FPGA_SPI_MUX_SX1272, address, reg_value); -} - -/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ - -int lgw_sx1272_reg_r(uint8_t address, uint8_t *reg_value) { - return lgw_fpga_spi_r(spi_target, FPGA_SPI_MUX_SX1272, address, reg_value); -} - /* --- EOF ------------------------------------------------------------------ */ diff --git a/libloragw/src/loragw_gps.c b/libloragw/src/loragw_gps.c index a07e3b8c..7e20b613 100644 --- a/libloragw/src/loragw_gps.c +++ b/libloragw/src/loragw_gps.c @@ -7,9 +7,9 @@ (C)2013 Semtech-Cycleo Description: - Library of functions to manage a GNSS module (typically GPS) for accurate - timestamping of packets and synchronisation of gateways. - A limited set of module brands/models are supported. + Library of functions to manage a GNSS module (typically GPS) for accurate + timestamping of packets and synchronisation of gateways. + A limited set of module brands/models are supported. License: Revised BSD License, see LICENSE.TXT file include in the project Maintainer: Sylvain Miermont @@ -21,22 +21,22 @@ Maintainer: Sylvain Miermont /* fix an issue between POSIX and C99 */ #if __STDC_VERSION__ >= 199901L - #define _XOPEN_SOURCE 600 + #define _XOPEN_SOURCE 600 #else - #define _XOPEN_SOURCE 500 + #define _XOPEN_SOURCE 500 #endif -#include /* C99 types */ -#include /* bool type */ -#include /* printf fprintf */ -#include /* memcpy */ +#include /* C99 types */ +#include /* bool type */ +#include /* printf fprintf */ +#include /* memcpy */ -#include /* struct timespec */ -#include /* open */ -#include /* tcflush */ +#include /* struct timespec */ +#include /* open */ +#include /* tcflush */ #include /* modf */ -#include // DEBUG +#include #include "loragw_gps.h" @@ -45,23 +45,23 @@ Maintainer: Sylvain Miermont #define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) #if DEBUG_GPS == 1 - #define DEBUG_MSG(args...) fprintf(stderr, args) - #define DEBUG_ARRAY(a,b,c) for(a=0;a= buff_size) { - DEBUG_MSG("Maximum length reached for nmea_checksum\n"); - return -1; - } - } - - /* Convert checksum value to 2 hexadecimal characters */ - checksum[0] = nibble_to_hexchar(check_num / 16); /* upper nibble */ - checksum[1] = nibble_to_hexchar(check_num % 16); /* lower nibble */ - - return i + 1; + int i = 0; + uint8_t check_num = 0; + + /* check input parameters */ + if ((nmea_string == NULL) || (checksum == NULL) || (buff_size <= 1)) { + DEBUG_MSG("Invalid parameters for nmea_checksum\n"); + return -1; + } + + /* skip the first '$' if necessary */ + if (nmea_string[i] == '$') { + i += 1; + } + + /* xor until '*' or max length is reached */ + while (nmea_string[i] != '*') { + check_num ^= nmea_string[i]; + i += 1; + if (i >= buff_size) { + DEBUG_MSG("Maximum length reached for nmea_checksum\n"); + return -1; + } + } + + /* Convert checksum value to 2 hexadecimal characters */ + checksum[0] = nibble_to_hexchar(check_num / 16); /* upper nibble */ + checksum[1] = nibble_to_hexchar(check_num % 16); /* lower nibble */ + + return i + 1; } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ char nibble_to_hexchar(uint8_t a) { - if (a < 10) { - return '0' + a; - } else if (a < 16) { - return 'A' + (a-10); - } else { - return '?'; - } + if (a < 10) { + return '0' + a; + } else if (a < 16) { + return 'A' + (a-10); + } else { + return '?'; + } } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ @@ -164,30 +164,30 @@ present at the end of it. Return true if it matches */ bool validate_nmea_checksum(char *serial_buff, int buff_size) { - int checksum_index; - char checksum[2]; /* 2 characters to calculate NMEA checksum */ - - checksum_index = nmea_checksum(serial_buff, buff_size, checksum); - - /* could we calculate a verification checksum ? */ - if (checksum_index < 0) { - DEBUG_MSG("ERROR: IMPOSSIBLE TO PARSE NMEA SENTENCE\n"); - return false; - } - - /* check if there are enough char in the serial buffer to read checksum */ - if (checksum_index >= (buff_size - 2)) { - DEBUG_MSG("ERROR: IMPOSSIBLE TO READ NMEA SENTENCE CHECKSUM\n"); - return false; - } - - /* check the checksum per se */ - if ((serial_buff[checksum_index] == checksum[0]) && (serial_buff[checksum_index+1] == checksum[1])) { - return true; - } else { - DEBUG_MSG("ERROR: NMEA CHECKSUM %c%c DOESN'T MATCH VERIFICATION CHECKSUM %c%c\n", serial_buff[checksum_index], serial_buff[checksum_index+1], checksum[0], checksum[1]); - return false; - } + int checksum_index; + char checksum[2]; /* 2 characters to calculate NMEA checksum */ + + checksum_index = nmea_checksum(serial_buff, buff_size, checksum); + + /* could we calculate a verification checksum ? */ + if (checksum_index < 0) { + DEBUG_MSG("ERROR: IMPOSSIBLE TO PARSE NMEA SENTENCE\n"); + return false; + } + + /* check if there are enough char in the serial buffer to read checksum */ + if (checksum_index >= (buff_size - 2)) { + DEBUG_MSG("ERROR: IMPOSSIBLE TO READ NMEA SENTENCE CHECKSUM\n"); + return false; + } + + /* check the checksum per se */ + if ((serial_buff[checksum_index] == checksum[0]) && (serial_buff[checksum_index+1] == checksum[1])) { + return true; + } else { + DEBUG_MSG("ERROR: NMEA CHECKSUM %c%c DOESN'T MATCH VERIFICATION CHECKSUM %c%c\n", serial_buff[checksum_index], serial_buff[checksum_index+1], checksum[0], checksum[1]); + return false; + } } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ @@ -197,13 +197,13 @@ Return true if the "label" string (can contain wildcard characters) matches the begining of the "s" string */ bool match_label(char *s, char *label, int size, char wildcard) { - int i; - - for (i=0; i < size; i++) { - if (label[i] == wildcard) continue; - if (label[i] != s[i]) return false; - } - return true; + int i; + + for (i=0; i < size; i++) { + if (label[i] == wildcard) continue; + if (label[i] != s[i]) return false; + } + return true; } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ @@ -218,361 +218,361 @@ buff_size and max_idx are there to prevent segfaults. Return the number of token found (number of idx_ary filled). */ int str_chop(char *s, int buff_size, char separator, int *idx_ary, int max_idx) { - int i = 0; /* index in the string */ - int j = 0; /* index in the result array */ - - if ((s == NULL) || (buff_size < 0) || (separator == 0) || (idx_ary == NULL) || (max_idx < 0)) { - /* unsafe to do anything */ - return -1; - } - if ((buff_size == 0) || (max_idx == 0)) { - /* nothing to do */ - return 0; - } - s[buff_size - 1] = 0; /* add string terminator at the end of the buffer, just to be sure */ - idx_ary[j] = 0; - j += 1; - /* loop until string terminator is reached */ - while (s[i] != 0) { - if (s[i] == separator) { - s[i] = 0; /* replace separator by string terminator */ - if (j >= max_idx) { /* no more room in the index array */ - return j; - } - idx_ary[j] = i+1; /* next token start after replaced separator */ - ++j; - } - ++i; - } - return j; + int i = 0; /* index in the string */ + int j = 0; /* index in the result array */ + + if ((s == NULL) || (buff_size < 0) || (separator == 0) || (idx_ary == NULL) || (max_idx < 0)) { + /* unsafe to do anything */ + return -1; + } + if ((buff_size == 0) || (max_idx == 0)) { + /* nothing to do */ + return 0; + } + s[buff_size - 1] = 0; /* add string terminator at the end of the buffer, just to be sure */ + idx_ary[j] = 0; + j += 1; + /* loop until string terminator is reached */ + while (s[i] != 0) { + if (s[i] == separator) { + s[i] = 0; /* replace separator by string terminator */ + if (j >= max_idx) { /* no more room in the index array */ + return j; + } + idx_ary[j] = i+1; /* next token start after replaced separator */ + ++j; + } + ++i; + } + return j; } /* -------------------------------------------------------------------------- */ /* --- PUBLIC FUNCTIONS DEFINITION ------------------------------------------ */ int lgw_gps_enable(char *tty_path, char *gps_familly, speed_t target_brate, int *fd_ptr) { - int i; - struct termios ttyopt; /* serial port options */ - int gps_tty_dev; /* file descriptor to the serial port of the GNSS module */ - - /* check input parameters */ - CHECK_NULL(tty_path); - CHECK_NULL(fd_ptr); - - /* open TTY device */ - gps_tty_dev = open(tty_path, O_RDWR | O_NOCTTY); - if (gps_tty_dev <= 0) { - DEBUG_MSG("ERROR: TTY PORT FAIL TO OPEN, CHECK PATH AND ACCESS RIGHTS\n"); - return LGW_GPS_ERROR; - } - *fd_ptr = gps_tty_dev; - - /* manage the different GPS modules families */ - if (gps_familly != NULL) { - DEBUG_MSG("WARNING: gps_familly parameter ignored for now\n"); // TODO - } - - /* manage the target bitrate */ - if (target_brate != 0) { - DEBUG_MSG("WARNING: target_brate parameter ignored for now\n"); // TODO - } - - /* get actual serial port configuration */ - i = tcgetattr(gps_tty_dev, &ttyopt); - if (i != 0) { - DEBUG_MSG("ERROR: IMPOSSIBLE TO GET TTY PORT CONFIGURATION\n"); - return LGW_GPS_ERROR; - } - - /* update baudrates */ - cfsetispeed(&ttyopt, DEFAULT_BAUDRATE); - cfsetospeed(&ttyopt, DEFAULT_BAUDRATE); - - /* update terminal parameters */ - ttyopt.c_cflag |= CLOCAL; /* local connection, no modem control */ - ttyopt.c_cflag |= CREAD; /* enable receiving characters */ - ttyopt.c_cflag |= CS8; /* 8 bit frames */ - ttyopt.c_cflag &= ~PARENB; /* no parity */ - ttyopt.c_cflag &= ~CSTOPB; /* one stop bit */ - ttyopt.c_iflag |= IGNPAR; /* ignore bytes with parity errors */ - ttyopt.c_iflag |= ICRNL; /* map CR to NL */ - ttyopt.c_iflag |= IGNCR; /* Ignore carriage return on input */ - ttyopt.c_lflag |= ICANON; /* enable canonical input */ - - /* set new serial ports parameters */ - i = tcsetattr(gps_tty_dev, TCSANOW, &ttyopt); - if (i != 0){ - DEBUG_MSG("ERROR: IMPOSSIBLE TO UPDATE TTY PORT CONFIGURATION\n"); - return LGW_GPS_ERROR; - } - tcflush(gps_tty_dev, TCIOFLUSH); - - /* get timezone info */ - tzset(); - - /* initialize global variables */ - gps_time_ok = false; - gps_pos_ok = false; - gps_mod = 'N'; - - return LGW_GPS_SUCCESS; + int i; + struct termios ttyopt; /* serial port options */ + int gps_tty_dev; /* file descriptor to the serial port of the GNSS module */ + + /* check input parameters */ + CHECK_NULL(tty_path); + CHECK_NULL(fd_ptr); + + /* open TTY device */ + gps_tty_dev = open(tty_path, O_RDWR | O_NOCTTY); + if (gps_tty_dev <= 0) { + DEBUG_MSG("ERROR: TTY PORT FAIL TO OPEN, CHECK PATH AND ACCESS RIGHTS\n"); + return LGW_GPS_ERROR; + } + *fd_ptr = gps_tty_dev; + + /* manage the different GPS modules families */ + if (gps_familly != NULL) { + DEBUG_MSG("WARNING: gps_familly parameter ignored for now\n"); // TODO + } + + /* manage the target bitrate */ + if (target_brate != 0) { + DEBUG_MSG("WARNING: target_brate parameter ignored for now\n"); // TODO + } + + /* get actual serial port configuration */ + i = tcgetattr(gps_tty_dev, &ttyopt); + if (i != 0) { + DEBUG_MSG("ERROR: IMPOSSIBLE TO GET TTY PORT CONFIGURATION\n"); + return LGW_GPS_ERROR; + } + + /* update baudrates */ + cfsetispeed(&ttyopt, DEFAULT_BAUDRATE); + cfsetospeed(&ttyopt, DEFAULT_BAUDRATE); + + /* update terminal parameters */ + ttyopt.c_cflag |= CLOCAL; /* local connection, no modem control */ + ttyopt.c_cflag |= CREAD; /* enable receiving characters */ + ttyopt.c_cflag |= CS8; /* 8 bit frames */ + ttyopt.c_cflag &= ~PARENB; /* no parity */ + ttyopt.c_cflag &= ~CSTOPB; /* one stop bit */ + ttyopt.c_iflag |= IGNPAR; /* ignore bytes with parity errors */ + ttyopt.c_iflag |= ICRNL; /* map CR to NL */ + ttyopt.c_iflag |= IGNCR; /* Ignore carriage return on input */ + ttyopt.c_lflag |= ICANON; /* enable canonical input */ + + /* set new serial ports parameters */ + i = tcsetattr(gps_tty_dev, TCSANOW, &ttyopt); + if (i != 0){ + DEBUG_MSG("ERROR: IMPOSSIBLE TO UPDATE TTY PORT CONFIGURATION\n"); + return LGW_GPS_ERROR; + } + tcflush(gps_tty_dev, TCIOFLUSH); + + /* get timezone info */ + tzset(); + + /* initialize global variables */ + gps_time_ok = false; + gps_pos_ok = false; + gps_mod = 'N'; + + return LGW_GPS_SUCCESS; } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ enum gps_msg lgw_parse_nmea(char *serial_buff, int buff_size) { - int i, j, k; - int str_index[30]; /* string index from the string chopping */ - int nb_fields; /* number of strings detected by string chopping */ - - /* check input parameters */ - if (serial_buff == NULL) { - return UNKNOWN; - } - - /* display received serial data and checksum */ - DEBUG_MSG("Note: parsing NMEA frame> %s", serial_buff); - - /* look for some NMEA sentences in particular */ - if (buff_size < 8) { - DEBUG_MSG("ERROR: TOO SHORT TO BE A VALID NMEA SENTENCE\n"); - return UNKNOWN; - } else if (match_label(serial_buff, "$G?RMC", 6, '?')) { - /* - NMEA sentence format: $xxRMC,time,status,lat,NS,long,EW,spd,cog,date,mv,mvEW,posMode*cs - Valid fix: $GPRMC,083559.34,A,4717.11437,N,00833.91522,E,0.004,77.52,091202,,,A*00 - No fix: $GPRMC,,V,,,,,,,,,,N*00 - */ - if (!validate_nmea_checksum(serial_buff, buff_size)) { - DEBUG_MSG("Warning: invalid RMC sentence (bad checksum)\n"); - return INVALID; - } - nb_fields = str_chop(serial_buff, buff_size, ',', str_index, ARRAY_SIZE(str_index)); - if (nb_fields != 13) { - DEBUG_MSG("Warning: invalid RMC sentence (number of fields)\n"); - return INVALID; - } - /* parse GPS status */ - gps_mod = *(serial_buff + str_index[12]); /* get first character, no need to bother with sscanf */ - if ((gps_mod != 'N') && (gps_mod != 'A') && (gps_mod != 'D')) { - gps_mod = 'N'; - } - /* parse complete time */ - i = sscanf(serial_buff + str_index[1], "%2hd%2hd%2hd%4f", &gps_hou, &gps_min, &gps_sec, &gps_fra); - j = sscanf(serial_buff + str_index[9], "%2hd%2hd%2hd", &gps_day, &gps_mon, &gps_yea); - if ((i == 4) && (j == 3)) { - if ((gps_mod == 'A') || (gps_mod == 'D')) { - gps_time_ok = true; - DEBUG_MSG("Note: Valid RMC sentence, GPS locked, date: 20%02d-%02d-%02dT%02d:%02d:%06.3fZ\n", gps_yea, gps_mon, gps_day, gps_hou, gps_min, gps_fra + (float)gps_sec); - } else { - gps_time_ok = false; - DEBUG_MSG("Note: Valid RMC sentence, no satellite fix, estimated date: 20%02d-%02d-%02dT%02d:%02d:%06.3fZ\n", gps_yea, gps_mon, gps_day, gps_hou, gps_min, gps_fra + (float)gps_sec); - } - } else { - /* could not get a valid hour AND date */ - gps_time_ok = false; - DEBUG_MSG("Note: Valid RMC sentence, mode %c, no date\n", gps_mod); - } - return NMEA_RMC; - } else if (match_label(serial_buff, "$G?GGA", 6, '?')) { - /* - NMEA sentence format: $xxGGA,time,lat,NS,long,EW,quality,numSV,HDOP,alt,M,sep,M,diffAge,diffStation*cs - Valid fix: $GPGGA,092725.00,4717.11399,N,00833.91590,E,1,08,1.01,499.6,M,48.0,M,,*5B - */ - if (!validate_nmea_checksum(serial_buff, buff_size)) { - DEBUG_MSG("Warning: invalid GGA sentence (bad checksum)\n"); - return INVALID; - } - nb_fields = str_chop(serial_buff, buff_size, ',', str_index, ARRAY_SIZE(str_index)); - if (nb_fields != 15) { - DEBUG_MSG("Warning: invalid GGA sentence (number of fields)\n"); - return INVALID; - } - /* parse number of satellites used for fix */ - sscanf(serial_buff + str_index[7], "%hd", &gps_sat); - /* parse 3D coordinates */ - i = sscanf(serial_buff + str_index[2], "%2hd%10lf", &gps_dla, &gps_mla); - gps_ola = *(serial_buff + str_index[3]); - j = sscanf(serial_buff + str_index[4], "%3hd%10lf", &gps_dlo, &gps_mlo); - gps_olo = *(serial_buff + str_index[5]); - k = sscanf(serial_buff + str_index[9], "%hd", &gps_alt); - if ((i == 2) && (j == 2) && (k == 1) && ((gps_ola=='N')||(gps_ola=='S')) && ((gps_olo=='E')||(gps_olo=='W'))) { - gps_pos_ok = true; - DEBUG_MSG("Note: Valid GGA sentence, %d sat, lat %02ddeg %06.3fmin %c, lon %03ddeg%06.3fmin %c, alt %d\n", gps_sat, gps_dla, gps_mla, gps_ola, gps_dlo, gps_mlo, gps_olo, gps_alt); - } else { - /* could not get a valid latitude, longitude AND altitude */ - gps_pos_ok = false; - DEBUG_MSG("Note: Valid GGA sentence, %d sat, no coordinates\n", gps_sat); - } - return NMEA_GGA; - } else { - DEBUG_MSG("Note: ignored NMEA sentence\n"); /* quite verbose */ - return INVALID; - } + int i, j, k; + int str_index[30]; /* string index from the string chopping */ + int nb_fields; /* number of strings detected by string chopping */ + + /* check input parameters */ + if (serial_buff == NULL) { + return UNKNOWN; + } + + /* display received serial data and checksum */ + DEBUG_MSG("Note: parsing NMEA frame> %s", serial_buff); + + /* look for some NMEA sentences in particular */ + if (buff_size < 8) { + DEBUG_MSG("ERROR: TOO SHORT TO BE A VALID NMEA SENTENCE\n"); + return UNKNOWN; + } else if (match_label(serial_buff, "$G?RMC", 6, '?')) { + /* + NMEA sentence format: $xxRMC,time,status,lat,NS,long,EW,spd,cog,date,mv,mvEW,posMode*cs + Valid fix: $GPRMC,083559.34,A,4717.11437,N,00833.91522,E,0.004,77.52,091202,,,A*00 + No fix: $GPRMC,,V,,,,,,,,,,N*00 + */ + if (!validate_nmea_checksum(serial_buff, buff_size)) { + DEBUG_MSG("Warning: invalid RMC sentence (bad checksum)\n"); + return INVALID; + } + nb_fields = str_chop(serial_buff, buff_size, ',', str_index, ARRAY_SIZE(str_index)); + if (nb_fields != 13) { + DEBUG_MSG("Warning: invalid RMC sentence (number of fields)\n"); + return INVALID; + } + /* parse GPS status */ + gps_mod = *(serial_buff + str_index[12]); /* get first character, no need to bother with sscanf */ + if ((gps_mod != 'N') && (gps_mod != 'A') && (gps_mod != 'D')) { + gps_mod = 'N'; + } + /* parse complete time */ + i = sscanf(serial_buff + str_index[1], "%2hd%2hd%2hd%4f", &gps_hou, &gps_min, &gps_sec, &gps_fra); + j = sscanf(serial_buff + str_index[9], "%2hd%2hd%2hd", &gps_day, &gps_mon, &gps_yea); + if ((i == 4) && (j == 3)) { + if ((gps_mod == 'A') || (gps_mod == 'D')) { + gps_time_ok = true; + DEBUG_MSG("Note: Valid RMC sentence, GPS locked, date: 20%02d-%02d-%02dT%02d:%02d:%06.3fZ\n", gps_yea, gps_mon, gps_day, gps_hou, gps_min, gps_fra + (float)gps_sec); + } else { + gps_time_ok = false; + DEBUG_MSG("Note: Valid RMC sentence, no satellite fix, estimated date: 20%02d-%02d-%02dT%02d:%02d:%06.3fZ\n", gps_yea, gps_mon, gps_day, gps_hou, gps_min, gps_fra + (float)gps_sec); + } + } else { + /* could not get a valid hour AND date */ + gps_time_ok = false; + DEBUG_MSG("Note: Valid RMC sentence, mode %c, no date\n", gps_mod); + } + return NMEA_RMC; + } else if (match_label(serial_buff, "$G?GGA", 6, '?')) { + /* + NMEA sentence format: $xxGGA,time,lat,NS,long,EW,quality,numSV,HDOP,alt,M,sep,M,diffAge,diffStation*cs + Valid fix: $GPGGA,092725.00,4717.11399,N,00833.91590,E,1,08,1.01,499.6,M,48.0,M,,*5B + */ + if (!validate_nmea_checksum(serial_buff, buff_size)) { + DEBUG_MSG("Warning: invalid GGA sentence (bad checksum)\n"); + return INVALID; + } + nb_fields = str_chop(serial_buff, buff_size, ',', str_index, ARRAY_SIZE(str_index)); + if (nb_fields != 15) { + DEBUG_MSG("Warning: invalid GGA sentence (number of fields)\n"); + return INVALID; + } + /* parse number of satellites used for fix */ + sscanf(serial_buff + str_index[7], "%hd", &gps_sat); + /* parse 3D coordinates */ + i = sscanf(serial_buff + str_index[2], "%2hd%10lf", &gps_dla, &gps_mla); + gps_ola = *(serial_buff + str_index[3]); + j = sscanf(serial_buff + str_index[4], "%3hd%10lf", &gps_dlo, &gps_mlo); + gps_olo = *(serial_buff + str_index[5]); + k = sscanf(serial_buff + str_index[9], "%hd", &gps_alt); + if ((i == 2) && (j == 2) && (k == 1) && ((gps_ola=='N')||(gps_ola=='S')) && ((gps_olo=='E')||(gps_olo=='W'))) { + gps_pos_ok = true; + DEBUG_MSG("Note: Valid GGA sentence, %d sat, lat %02ddeg %06.3fmin %c, lon %03ddeg%06.3fmin %c, alt %d\n", gps_sat, gps_dla, gps_mla, gps_ola, gps_dlo, gps_mlo, gps_olo, gps_alt); + } else { + /* could not get a valid latitude, longitude AND altitude */ + gps_pos_ok = false; + DEBUG_MSG("Note: Valid GGA sentence, %d sat, no coordinates\n", gps_sat); + } + return NMEA_GGA; + } else { + DEBUG_MSG("Note: ignored NMEA sentence\n"); /* quite verbose */ + return INVALID; + } } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ int lgw_gps_get(struct timespec *utc, struct coord_s *loc, struct coord_s *err) { - struct tm x; - time_t y; - - if (utc != NULL) { - if (!gps_time_ok) { - DEBUG_MSG("ERROR: NO VALID TIME TO RETURN\n"); - return LGW_GPS_ERROR; - } - memset(&x, 0, sizeof(x)); - if (gps_yea < 100) { /* 2-digits year, 20xx */ - x.tm_year = gps_yea + 100; /* 100 years offset to 1900 */ - } else { /* 4-digits year, Gregorian calendar */ - x.tm_year = gps_yea - 1900; - } - x.tm_mon = gps_mon - 1; /* tm_mon is [0,11], gps_mon is [1,12] */ - x.tm_mday = gps_day; - x.tm_hour = gps_hou; - x.tm_min = gps_min; - x.tm_sec = gps_sec; - y = mktime(&x) - timezone; /* need to substract timezone bc mktime assumes time vector is local time */ - if (y == (time_t)(-1)) { - DEBUG_MSG("ERROR: FAILED TO CONVERT BROKEN-DOWN TIME\n"); - return LGW_GPS_ERROR; - } - utc->tv_sec = y; - utc->tv_nsec = (int32_t)(gps_fra * 1e9); - } - if (loc != NULL) { - if (!gps_pos_ok) { - DEBUG_MSG("ERROR: NO VALID POSITION TO RETURN\n"); - return LGW_GPS_ERROR; - } - loc->lat = ((double)gps_dla + (gps_mla/60.0)) * ((gps_ola == 'N')?1.0:-1.0); - loc->lon = ((double)gps_dlo + (gps_mlo/60.0)) * ((gps_olo == 'E')?1.0:-1.0); - loc->alt = gps_alt; - } - if (err != NULL) { - DEBUG_MSG("Warning: localization error processing not implemented yet\n"); - err->lat = 0.0; - err->lon = 0.0; - err->alt = 0; - } - - return LGW_GPS_SUCCESS; + struct tm x; + time_t y; + + if (utc != NULL) { + if (!gps_time_ok) { + DEBUG_MSG("ERROR: NO VALID TIME TO RETURN\n"); + return LGW_GPS_ERROR; + } + memset(&x, 0, sizeof(x)); + if (gps_yea < 100) { /* 2-digits year, 20xx */ + x.tm_year = gps_yea + 100; /* 100 years offset to 1900 */ + } else { /* 4-digits year, Gregorian calendar */ + x.tm_year = gps_yea - 1900; + } + x.tm_mon = gps_mon - 1; /* tm_mon is [0,11], gps_mon is [1,12] */ + x.tm_mday = gps_day; + x.tm_hour = gps_hou; + x.tm_min = gps_min; + x.tm_sec = gps_sec; + y = mktime(&x) - timezone; /* need to substract timezone bc mktime assumes time vector is local time */ + if (y == (time_t)(-1)) { + DEBUG_MSG("ERROR: FAILED TO CONVERT BROKEN-DOWN TIME\n"); + return LGW_GPS_ERROR; + } + utc->tv_sec = y; + utc->tv_nsec = (int32_t)(gps_fra * 1e9); + } + if (loc != NULL) { + if (!gps_pos_ok) { + DEBUG_MSG("ERROR: NO VALID POSITION TO RETURN\n"); + return LGW_GPS_ERROR; + } + loc->lat = ((double)gps_dla + (gps_mla/60.0)) * ((gps_ola == 'N')?1.0:-1.0); + loc->lon = ((double)gps_dlo + (gps_mlo/60.0)) * ((gps_olo == 'E')?1.0:-1.0); + loc->alt = gps_alt; + } + if (err != NULL) { + DEBUG_MSG("Warning: localization error processing not implemented yet\n"); + err->lat = 0.0; + err->lon = 0.0; + err->alt = 0; + } + + return LGW_GPS_SUCCESS; } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ int lgw_gps_sync(struct tref *ref, uint32_t count_us, struct timespec utc) { - double cnt_diff; /* internal concentrator time difference (in seconds) */ - double utc_diff; /* UTC time difference (in seconds) */ - double slope; /* time slope between new reference and old reference (for sanity check) */ - - bool aber_n0; /* is the update value for synchronization aberrant or not ? */ - static bool aber_min1 = false; /* keep track of whether value at sync N-1 was aberrant or not */ - static bool aber_min2 = false; /* keep track of whether value at sync N-2 was aberrant or not */ - - CHECK_NULL(ref); - - /* calculate the slope */ - cnt_diff = (double)(count_us - ref->count_us) / (double)(TS_CPS); /* uncorrected by xtal_err */ - utc_diff = (double)(utc.tv_sec - (ref->utc).tv_sec) + (1E-9 * (double)(utc.tv_nsec - (ref->utc).tv_nsec)); - - /* detect aberrant points by measuring if slope limits are exceeded */ - if (utc_diff != 0) { // prevent divide by zero - slope = cnt_diff/utc_diff; - if ((slope > PLUS_10PPM) || (slope < MINUS_10PPM)) { - DEBUG_MSG("Warning: correction range exceeded\n"); - aber_n0 = true; - } else { - aber_n0 = false; - } - } else { - DEBUG_MSG("Warning: aberrant UTC value for synchronization\n"); - aber_n0 = true; - } - - /* watch if the 3 latest sync point were aberrant or not */ - if (aber_n0 == false) { - /* value no aberrant -> sync with smoothed slope */ - ref->systime = time(NULL); - ref->count_us = count_us; - ref->utc = utc; - ref->xtal_err = slope; - aber_min2 = aber_min1; - aber_min1 = aber_n0; - return LGW_GPS_SUCCESS; - } else if (aber_n0 && aber_min1 && aber_min2) { - /* 3 successive aberrant values -> sync reset (keep xtal_err) */ - ref->systime = time(NULL); - ref->count_us = count_us; - ref->utc = utc; - /* reset xtal_err only if the present value is out of range */ - if ((ref->xtal_err > PLUS_10PPM) || (ref->xtal_err < MINUS_10PPM)) { - ref->xtal_err = 1.0; - } - DEBUG_MSG("Warning: 3 successive aberrant sync attempts, sync reset\n"); - aber_min2 = aber_min1; - aber_min1 = aber_n0; - return LGW_GPS_SUCCESS; - } else { - /* only 1 or 2 successive aberrant values -> ignore and return an error */ - aber_min2 = aber_min1; - aber_min1 = aber_n0; - return LGW_GPS_ERROR; - } - - return LGW_GPS_SUCCESS; + double cnt_diff; /* internal concentrator time difference (in seconds) */ + double utc_diff; /* UTC time difference (in seconds) */ + double slope; /* time slope between new reference and old reference (for sanity check) */ + + bool aber_n0; /* is the update value for synchronization aberrant or not ? */ + static bool aber_min1 = false; /* keep track of whether value at sync N-1 was aberrant or not */ + static bool aber_min2 = false; /* keep track of whether value at sync N-2 was aberrant or not */ + + CHECK_NULL(ref); + + /* calculate the slope */ + cnt_diff = (double)(count_us - ref->count_us) / (double)(TS_CPS); /* uncorrected by xtal_err */ + utc_diff = (double)(utc.tv_sec - (ref->utc).tv_sec) + (1E-9 * (double)(utc.tv_nsec - (ref->utc).tv_nsec)); + + /* detect aberrant points by measuring if slope limits are exceeded */ + if (utc_diff != 0) { // prevent divide by zero + slope = cnt_diff/utc_diff; + if ((slope > PLUS_10PPM) || (slope < MINUS_10PPM)) { + DEBUG_MSG("Warning: correction range exceeded\n"); + aber_n0 = true; + } else { + aber_n0 = false; + } + } else { + DEBUG_MSG("Warning: aberrant UTC value for synchronization\n"); + aber_n0 = true; + } + + /* watch if the 3 latest sync point were aberrant or not */ + if (aber_n0 == false) { + /* value no aberrant -> sync with smoothed slope */ + ref->systime = time(NULL); + ref->count_us = count_us; + ref->utc = utc; + ref->xtal_err = slope; + aber_min2 = aber_min1; + aber_min1 = aber_n0; + return LGW_GPS_SUCCESS; + } else if (aber_n0 && aber_min1 && aber_min2) { + /* 3 successive aberrant values -> sync reset (keep xtal_err) */ + ref->systime = time(NULL); + ref->count_us = count_us; + ref->utc = utc; + /* reset xtal_err only if the present value is out of range */ + if ((ref->xtal_err > PLUS_10PPM) || (ref->xtal_err < MINUS_10PPM)) { + ref->xtal_err = 1.0; + } + DEBUG_MSG("Warning: 3 successive aberrant sync attempts, sync reset\n"); + aber_min2 = aber_min1; + aber_min1 = aber_n0; + return LGW_GPS_SUCCESS; + } else { + /* only 1 or 2 successive aberrant values -> ignore and return an error */ + aber_min2 = aber_min1; + aber_min1 = aber_n0; + return LGW_GPS_ERROR; + } + + return LGW_GPS_SUCCESS; } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ int lgw_cnt2utc(struct tref ref, uint32_t count_us, struct timespec *utc) { - double delta_sec; - double intpart, fractpart; - long tmp; - - CHECK_NULL(utc); - if ((ref.systime == 0) || (ref.xtal_err > PLUS_10PPM) || (ref.xtal_err < MINUS_10PPM)) { - DEBUG_MSG("ERROR: INVALID REFERENCE FOR CNT -> UTC CONVERSION\n"); - return LGW_GPS_ERROR; - } - - /* calculate delta in seconds between reference count_us and target count_us */ - delta_sec = (double)(count_us - ref.count_us) / (TS_CPS * ref.xtal_err); - - /* now add that delta to reference UTC time */ - fractpart = modf (delta_sec , &intpart); - tmp = ref.utc.tv_nsec + (long)(fractpart * 1E9); - if (tmp < (long)1E9) { /* the nanosecond part doesn't overflow */ - utc->tv_sec = ref.utc.tv_sec + (time_t)intpart; - utc->tv_nsec = tmp; - } else { /* must carry one second */ - utc->tv_sec = ref.utc.tv_sec + (time_t)intpart + 1; - utc->tv_nsec = tmp - (long)1E9; - } - - return LGW_GPS_SUCCESS; + double delta_sec; + double intpart, fractpart; + long tmp; + + CHECK_NULL(utc); + if ((ref.systime == 0) || (ref.xtal_err > PLUS_10PPM) || (ref.xtal_err < MINUS_10PPM)) { + DEBUG_MSG("ERROR: INVALID REFERENCE FOR CNT -> UTC CONVERSION\n"); + return LGW_GPS_ERROR; + } + + /* calculate delta in seconds between reference count_us and target count_us */ + delta_sec = (double)(count_us - ref.count_us) / (TS_CPS * ref.xtal_err); + + /* now add that delta to reference UTC time */ + fractpart = modf (delta_sec , &intpart); + tmp = ref.utc.tv_nsec + (long)(fractpart * 1E9); + if (tmp < (long)1E9) { /* the nanosecond part doesn't overflow */ + utc->tv_sec = ref.utc.tv_sec + (time_t)intpart; + utc->tv_nsec = tmp; + } else { /* must carry one second */ + utc->tv_sec = ref.utc.tv_sec + (time_t)intpart + 1; + utc->tv_nsec = tmp - (long)1E9; + } + + return LGW_GPS_SUCCESS; } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ int lgw_utc2cnt(struct tref ref, struct timespec utc, uint32_t *count_us) { - double delta_sec; - - CHECK_NULL(count_us); - if ((ref.systime == 0) || (ref.xtal_err > PLUS_10PPM) || (ref.xtal_err < MINUS_10PPM)) { - DEBUG_MSG("ERROR: INVALID REFERENCE FOR UTC -> CNT CONVERSION\n"); - return LGW_GPS_ERROR; - } - - /* calculate delta in seconds between reference utc and target utc */ - delta_sec = (double)(utc.tv_sec - ref.utc.tv_sec); - delta_sec += 1E-9 * (double)(utc.tv_nsec - ref.utc.tv_nsec); - - /* now convert that to internal counter tics and add that to reference counter value */ - *count_us = ref.count_us + (uint32_t)(delta_sec * TS_CPS * ref.xtal_err); - - return LGW_GPS_SUCCESS; + double delta_sec; + + CHECK_NULL(count_us); + if ((ref.systime == 0) || (ref.xtal_err > PLUS_10PPM) || (ref.xtal_err < MINUS_10PPM)) { + DEBUG_MSG("ERROR: INVALID REFERENCE FOR UTC -> CNT CONVERSION\n"); + return LGW_GPS_ERROR; + } + + /* calculate delta in seconds between reference utc and target utc */ + delta_sec = (double)(utc.tv_sec - ref.utc.tv_sec); + delta_sec += 1E-9 * (double)(utc.tv_nsec - ref.utc.tv_nsec); + + /* now convert that to internal counter tics and add that to reference counter value */ + *count_us = ref.count_us + (uint32_t)(delta_sec * TS_CPS * ref.xtal_err); + + return LGW_GPS_SUCCESS; } /* --- EOF ------------------------------------------------------------------ */ diff --git a/libloragw/src/loragw_hal.c b/libloragw/src/loragw_hal.c index 433160f5..e5f19286 100644 --- a/libloragw/src/loragw_hal.c +++ b/libloragw/src/loragw_hal.c @@ -7,7 +7,7 @@ (C)2013 Semtech-Cycleo Description: - LoRa concentrator Hardware Abstraction Layer + LoRa concentrator Hardware Abstraction Layer License: Revised BSD License, see LICENSE.TXT file include in the project Maintainer: Sylvain Miermont @@ -17,100 +17,71 @@ Maintainer: Sylvain Miermont /* -------------------------------------------------------------------------- */ /* --- DEPENDANCIES --------------------------------------------------------- */ -#include /* C99 types */ -#include /* bool type */ -#include /* printf fprintf */ -#include /* memcpy */ +#include /* C99 types */ +#include /* bool type */ +#include /* printf fprintf */ +#include /* memcpy */ +#include /* pow, cell */ #include "loragw_reg.h" #include "loragw_hal.h" #include "loragw_aux.h" +#include "loragw_spi.h" +#include "loragw_radio.h" +#include "loragw_fpga.h" +#include "loragw_lbt.h" /* -------------------------------------------------------------------------- */ /* --- PRIVATE MACROS ------------------------------------------------------- */ #define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) #if DEBUG_HAL == 1 - #define DEBUG_MSG(str) fprintf(stderr, str) - #define DEBUG_PRINTF(fmt, args...) fprintf(stderr,"%s:%d: "fmt, __FUNCTION__, __LINE__, args) - #define DEBUG_ARRAY(a,b,c) for(a=0;a= LGW_RF_CHAIN_NB) { - DEBUG_MSG("ERROR: INVALID RF_CHAIN\n"); - return; - } - if (addr >= 0x7F) { - DEBUG_MSG("ERROR: ADDRESS OUT OF RANGE\n"); - return; - } - - /* selecting the target radio */ - switch (channel) { - case 0: - reg_add = LGW_SPI_RADIO_A__ADDR; - reg_dat = LGW_SPI_RADIO_A__DATA; - reg_cs = LGW_SPI_RADIO_A__CS; - break; - - case 1: - reg_add = LGW_SPI_RADIO_B__ADDR; - reg_dat = LGW_SPI_RADIO_B__DATA; - reg_cs = LGW_SPI_RADIO_B__CS; - break; - - default: - DEBUG_PRINTF("ERROR: UNEXPECTED VALUE %d IN SWITCH STATEMENT\n", channel); - return; - } - - /* SPI master data write procedure */ - lgw_reg_w(reg_cs, 0); - lgw_reg_w(reg_add, 0x80 | addr); /* MSB at 1 for write operation */ - lgw_reg_w(reg_dat, data); - lgw_reg_w(reg_cs, 1); - lgw_reg_w(reg_cs, 0); - - return; -} +void lgw_constant_adjust(void) { -/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + /* I/Q path setup */ + // lgw_reg_w(LGW_RX_INVERT_IQ,0); /* default 0 */ + // lgw_reg_w(LGW_MODEM_INVERT_IQ,1); /* default 1 */ + // lgw_reg_w(LGW_CHIRP_INVERT_RX,1); /* default 1 */ + // lgw_reg_w(LGW_RX_EDGE_SELECT,0); /* default 0 */ + // lgw_reg_w(LGW_MBWSSF_MODEM_INVERT_IQ,0); /* default 0 */ + // lgw_reg_w(LGW_DC_NOTCH_EN,1); /* default 1 */ + lgw_reg_w(LGW_RSSI_BB_FILTER_ALPHA,6); /* default 7 */ + lgw_reg_w(LGW_RSSI_DEC_FILTER_ALPHA,7); /* default 5 */ + lgw_reg_w(LGW_RSSI_CHANN_FILTER_ALPHA,7); /* default 8 */ + lgw_reg_w(LGW_RSSI_BB_DEFAULT_VALUE,23); /* default 32 */ + lgw_reg_w(LGW_RSSI_CHANN_DEFAULT_VALUE,85); /* default 100 */ + lgw_reg_w(LGW_RSSI_DEC_DEFAULT_VALUE,66); /* default 100 */ + lgw_reg_w(LGW_DEC_GAIN_OFFSET,7); /* default 8 */ + lgw_reg_w(LGW_CHAN_GAIN_OFFSET,6); /* default 7 */ + + /* Correlator setup */ + // lgw_reg_w(LGW_CORR_DETECT_EN,126); /* default 126 */ + // lgw_reg_w(LGW_CORR_NUM_SAME_PEAK,4); /* default 4 */ + // lgw_reg_w(LGW_CORR_MAC_GAIN,5); /* default 5 */ + // lgw_reg_w(LGW_CORR_SAME_PEAKS_OPTION_SF6,0); /* default 0 */ + // lgw_reg_w(LGW_CORR_SAME_PEAKS_OPTION_SF7,1); /* default 1 */ + // lgw_reg_w(LGW_CORR_SAME_PEAKS_OPTION_SF8,1); /* default 1 */ + // lgw_reg_w(LGW_CORR_SAME_PEAKS_OPTION_SF9,1); /* default 1 */ + // lgw_reg_w(LGW_CORR_SAME_PEAKS_OPTION_SF10,1); /* default 1 */ + // lgw_reg_w(LGW_CORR_SAME_PEAKS_OPTION_SF11,1); /* default 1 */ + // lgw_reg_w(LGW_CORR_SAME_PEAKS_OPTION_SF12,1); /* default 1 */ + // lgw_reg_w(LGW_CORR_SIG_NOISE_RATIO_SF6,4); /* default 4 */ + // lgw_reg_w(LGW_CORR_SIG_NOISE_RATIO_SF7,4); /* default 4 */ + // lgw_reg_w(LGW_CORR_SIG_NOISE_RATIO_SF8,4); /* default 4 */ + // lgw_reg_w(LGW_CORR_SIG_NOISE_RATIO_SF9,4); /* default 4 */ + // lgw_reg_w(LGW_CORR_SIG_NOISE_RATIO_SF10,4); /* default 4 */ + // lgw_reg_w(LGW_CORR_SIG_NOISE_RATIO_SF11,4); /* default 4 */ + // lgw_reg_w(LGW_CORR_SIG_NOISE_RATIO_SF12,4); /* default 4 */ + + /* LoRa 'multi' demodulators setup */ + // lgw_reg_w(LGW_PREAMBLE_SYMB1_NB,10); /* default 10 */ + // lgw_reg_w(LGW_FREQ_TO_TIME_INVERT,29); /* default 29 */ + // lgw_reg_w(LGW_FRAME_SYNCH_GAIN,1); /* default 1 */ + // lgw_reg_w(LGW_SYNCH_DETECT_TH,1); /* default 1 */ + // lgw_reg_w(LGW_ZERO_PAD,0); /* default 0 */ + lgw_reg_w(LGW_SNR_AVG_CST,3); /* default 2 */ + if (lorawan_public) { /* LoRa network */ + lgw_reg_w(LGW_FRAME_SYNCH_PEAK1_POS,3); /* default 1 */ + lgw_reg_w(LGW_FRAME_SYNCH_PEAK2_POS,4); /* default 2 */ + } else { /* private network */ + lgw_reg_w(LGW_FRAME_SYNCH_PEAK1_POS,1); /* default 1 */ + lgw_reg_w(LGW_FRAME_SYNCH_PEAK2_POS,2); /* default 2 */ + } + + // lgw_reg_w(LGW_PREAMBLE_FINE_TIMING_GAIN,1); /* default 1 */ + // lgw_reg_w(LGW_ONLY_CRC_EN,1); /* default 1 */ + // lgw_reg_w(LGW_PAYLOAD_FINE_TIMING_GAIN,2); /* default 2 */ + // lgw_reg_w(LGW_TRACKING_INTEGRAL,0); /* default 0 */ + // lgw_reg_w(LGW_ADJUST_MODEM_START_OFFSET_RDX8,0); /* default 0 */ + // lgw_reg_w(LGW_ADJUST_MODEM_START_OFFSET_SF12_RDX4,4092); /* default 4092 */ + // lgw_reg_w(LGW_MAX_PAYLOAD_LEN,255); /* default 255 */ + + /* LoRa standalone 'MBWSSF' demodulator setup */ + // lgw_reg_w(LGW_MBWSSF_PREAMBLE_SYMB1_NB,10); /* default 10 */ + // lgw_reg_w(LGW_MBWSSF_FREQ_TO_TIME_INVERT,29); /* default 29 */ + // lgw_reg_w(LGW_MBWSSF_FRAME_SYNCH_GAIN,1); /* default 1 */ + // lgw_reg_w(LGW_MBWSSF_SYNCH_DETECT_TH,1); /* default 1 */ + // lgw_reg_w(LGW_MBWSSF_ZERO_PAD,0); /* default 0 */ + if (lorawan_public) { /* LoRa network */ + lgw_reg_w(LGW_MBWSSF_FRAME_SYNCH_PEAK1_POS,3); /* default 1 */ + lgw_reg_w(LGW_MBWSSF_FRAME_SYNCH_PEAK2_POS,4); /* default 2 */ + } else { + lgw_reg_w(LGW_MBWSSF_FRAME_SYNCH_PEAK1_POS,1); /* default 1 */ + lgw_reg_w(LGW_MBWSSF_FRAME_SYNCH_PEAK2_POS,2); /* default 2 */ + } + // lgw_reg_w(LGW_MBWSSF_ONLY_CRC_EN,1); /* default 1 */ + // lgw_reg_w(LGW_MBWSSF_PAYLOAD_FINE_TIMING_GAIN,2); /* default 2 */ + // lgw_reg_w(LGW_MBWSSF_PREAMBLE_FINE_TIMING_GAIN,1); /* default 1 */ + // lgw_reg_w(LGW_MBWSSF_TRACKING_INTEGRAL,0); /* default 0 */ + // lgw_reg_w(LGW_MBWSSF_AGC_FREEZE_ON_DETECT,1); /* default 1 */ + + /* FSK datapath setup */ + lgw_reg_w(LGW_FSK_RX_INVERT,1); /* default 0 */ + lgw_reg_w(LGW_FSK_MODEM_INVERT_IQ,1); /* default 0 */ + + /* FSK demodulator setup */ + lgw_reg_w(LGW_FSK_RSSI_LENGTH,4); /* default 0 */ + lgw_reg_w(LGW_FSK_PKT_MODE,1); /* variable length, default 0 */ + lgw_reg_w(LGW_FSK_CRC_EN,1); /* default 0 */ + lgw_reg_w(LGW_FSK_DCFREE_ENC,2); /* default 0 */ + // lgw_reg_w(LGW_FSK_CRC_IBM,0); /* default 0 */ + lgw_reg_w(LGW_FSK_ERROR_OSR_TOL,10); /* default 0 */ + lgw_reg_w(LGW_FSK_PKT_LENGTH,255); /* max packet length in variable length mode */ + // lgw_reg_w(LGW_FSK_NODE_ADRS,0); /* default 0 */ + // lgw_reg_w(LGW_FSK_BROADCAST,0); /* default 0 */ + // lgw_reg_w(LGW_FSK_AUTO_AFC_ON,0); /* default 0 */ + lgw_reg_w(LGW_FSK_PATTERN_TIMEOUT_CFG,128); /* sync timeout (allow 8 bytes preamble + 8 bytes sync word, default 0 */ + + /* TX general parameters */ + lgw_reg_w(LGW_TX_START_DELAY, TX_START_DELAY); /* default 0 */ + + /* TX LoRa */ + // lgw_reg_w(LGW_TX_MODE,0); /* default 0 */ + lgw_reg_w(LGW_TX_SWAP_IQ,1); /* "normal" polarity; default 0 */ + if (lorawan_public) { /* LoRa network */ + lgw_reg_w(LGW_TX_FRAME_SYNCH_PEAK1_POS,3); /* default 1 */ + lgw_reg_w(LGW_TX_FRAME_SYNCH_PEAK2_POS,4); /* default 2 */ + } else { /* Private network */ + lgw_reg_w(LGW_TX_FRAME_SYNCH_PEAK1_POS,1); /* default 1 */ + lgw_reg_w(LGW_TX_FRAME_SYNCH_PEAK2_POS,2); /* default 2 */ + } + + /* TX FSK */ + // lgw_reg_w(LGW_FSK_TX_GAUSSIAN_EN,1); /* default 1 */ + lgw_reg_w(LGW_FSK_TX_GAUSSIAN_SELECT_BT,2); /* Gaussian filter always on TX, default 0 */ + // lgw_reg_w(LGW_FSK_TX_PATTERN_EN,1); /* default 1 */ + // lgw_reg_w(LGW_FSK_TX_PREAMBLE_SEQ,0); /* default 0 */ -uint8_t sx125x_read(uint8_t channel, uint8_t addr) { - int reg_add, reg_dat, reg_cs, reg_rb; - int32_t read_value; - - /* checking input parameters */ - if (channel >= LGW_RF_CHAIN_NB) { - DEBUG_MSG("ERROR: INVALID RF_CHAIN\n"); - return 0; - } - if (addr >= 0x7F) { - DEBUG_MSG("ERROR: ADDRESS OUT OF RANGE\n"); - return 0; - } - - /* selecting the target radio */ - switch (channel) { - case 0: - reg_add = LGW_SPI_RADIO_A__ADDR; - reg_dat = LGW_SPI_RADIO_A__DATA; - reg_cs = LGW_SPI_RADIO_A__CS; - reg_rb = LGW_SPI_RADIO_A__DATA_READBACK; - break; - - case 1: - reg_add = LGW_SPI_RADIO_B__ADDR; - reg_dat = LGW_SPI_RADIO_B__DATA; - reg_cs = LGW_SPI_RADIO_B__CS; - reg_rb = LGW_SPI_RADIO_B__DATA_READBACK; - break; - - default: - DEBUG_PRINTF("ERROR: UNEXPECTED VALUE %d IN SWITCH STATEMENT\n", channel); - return 0; - } - - /* SPI master data read procedure */ - lgw_reg_w(reg_cs, 0); - lgw_reg_w(reg_add, addr); /* MSB at 0 for read operation */ - lgw_reg_w(reg_dat, 0); - lgw_reg_w(reg_cs, 1); - lgw_reg_w(reg_cs, 0); - lgw_reg_r(reg_rb, &read_value); - - return (uint8_t)read_value; + return; } -/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC FUNCTIONS DEFINITION ------------------------------------------ */ + +int lgw_board_setconf(struct lgw_conf_board_s conf) { -int setup_sx125x(uint8_t rf_chain, uint32_t freq_hz) { - uint32_t part_int = 0; - uint32_t part_frac = 0; - int cpt_attempts = 0; - - if (rf_chain >= LGW_RF_CHAIN_NB) { - DEBUG_MSG("ERROR: INVALID RF_CHAIN\n"); - return -1; - } - - /* Get version to identify SX1255/57 silicon revision */ - DEBUG_PRINTF("Note: SX125x #%d version register returned 0x%02x\n", rf_chain, sx125x_read(rf_chain, 0x07)); - - /* General radio setup */ - if (rf_clkout == rf_chain) { - sx125x_write(rf_chain, 0x10, SX125x_TX_DAC_CLK_SEL + 2); - DEBUG_PRINTF("Note: SX125x #%d clock output enabled\n", rf_chain); - } else { - sx125x_write(rf_chain, 0x10, SX125x_TX_DAC_CLK_SEL); - DEBUG_PRINTF("Note: SX125x #%d clock output disabled\n", rf_chain); - } - - switch (rf_radio_type[rf_chain]) { - case LGW_RADIO_TYPE_SX1255: - sx125x_write(rf_chain, 0x28, SX125x_XOSC_GM_STARTUP + SX125x_XOSC_DISABLE*16); - break; - case LGW_RADIO_TYPE_SX1257: - sx125x_write(rf_chain, 0x26, SX125x_XOSC_GM_STARTUP + SX125x_XOSC_DISABLE*16); - break; - default: - DEBUG_PRINTF("ERROR: UNEXPECTED VALUE %d FOR RADIO TYPE\n", rf_radio_type[rf_chain]); - break; - } - - if (rf_enable[rf_chain] == true) { - /* Tx gain and trim */ - sx125x_write(rf_chain, 0x08, SX125x_TX_MIX_GAIN + SX125x_TX_DAC_GAIN*16); - sx125x_write(rf_chain, 0x0A, SX125x_TX_ANA_BW + SX125x_TX_PLL_BW*32); - sx125x_write(rf_chain, 0x0B, SX125x_TX_DAC_BW); - - /* Rx gain and trim */ - sx125x_write(rf_chain, 0x0C, SX125x_LNA_ZIN + SX125x_RX_BB_GAIN*2 + SX125x_RX_LNA_GAIN*32); - sx125x_write(rf_chain, 0x0D, SX125x_RX_BB_BW + SX125x_RX_ADC_TRIM*4 + SX125x_RX_ADC_BW*32); - sx125x_write(rf_chain, 0x0E, SX125x_ADC_TEMP + SX125x_RX_PLL_BW*2); - - /* set RX PLL frequency */ - switch (rf_radio_type[rf_chain]) { - case LGW_RADIO_TYPE_SX1255: - part_int = freq_hz / (SX125x_32MHz_FRAC << 7); /* integer part, gives the MSB */ - part_frac = ((freq_hz % (SX125x_32MHz_FRAC << 7)) << 9) / SX125x_32MHz_FRAC; /* fractional part, gives middle part and LSB */ - break; - case LGW_RADIO_TYPE_SX1257: - part_int = freq_hz / (SX125x_32MHz_FRAC << 8); /* integer part, gives the MSB */ - part_frac = ((freq_hz % (SX125x_32MHz_FRAC << 8)) << 8) / SX125x_32MHz_FRAC; /* fractional part, gives middle part and LSB */ - break; - default: - DEBUG_PRINTF("ERROR: UNEXPECTED VALUE %d FOR RADIO TYPE\n", rf_radio_type[rf_chain]); - break; - } - - sx125x_write(rf_chain, 0x01,0xFF & part_int); /* Most Significant Byte */ - sx125x_write(rf_chain, 0x02,0xFF & (part_frac >> 8)); /* middle byte */ - sx125x_write(rf_chain, 0x03,0xFF & part_frac); /* Least Significant Byte */ - - /* start and PLL lock */ - do { - if (cpt_attempts >= PLL_LOCK_MAX_ATTEMPTS) { - DEBUG_MSG("ERROR: FAIL TO LOCK PLL\n"); - return -1; - } - sx125x_write(rf_chain, 0x00, 1); /* enable Xtal oscillator */ - sx125x_write(rf_chain, 0x00, 3); /* Enable RX (PLL+FE) */ - ++cpt_attempts; - DEBUG_PRINTF("Note: SX125x #%d PLL start (attempt %d)\n", rf_chain, cpt_attempts); - wait_ms(1); - } while((sx125x_read(rf_chain, 0x11) & 0x02) == 0); - } else { - DEBUG_PRINTF("Note: SX125x #%d kept in standby mode\n", rf_chain); - } - - return 0; + /* check if the concentrator is running */ + if (lgw_is_started == true) { + DEBUG_MSG("ERROR: CONCENTRATOR IS RUNNING, STOP IT BEFORE TOUCHING CONFIGURATION\n"); + return LGW_HAL_ERROR; + } + + /* set internal config according to parameters */ + lorawan_public = conf.lorawan_public; + rf_clkout = conf.clksrc; + + DEBUG_PRINTF("Note: board configuration; lorawan_public:%d, clksrc:%d\n", lorawan_public, rf_clkout); + + return LGW_HAL_SUCCESS; } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ -void lgw_constant_adjust(void) { +int lgw_lbt_setconf(struct lgw_conf_lbt_s conf) { + int x; - /* I/Q path setup */ - // lgw_reg_w(LGW_RX_INVERT_IQ,0); /* default 0 */ - // lgw_reg_w(LGW_MODEM_INVERT_IQ,1); /* default 1 */ - // lgw_reg_w(LGW_CHIRP_INVERT_RX,1); /* default 1 */ - // lgw_reg_w(LGW_RX_EDGE_SELECT,0); /* default 0 */ - // lgw_reg_w(LGW_MBWSSF_MODEM_INVERT_IQ,0); /* default 0 */ - // lgw_reg_w(LGW_DC_NOTCH_EN,1); /* default 1 */ - lgw_reg_w(LGW_RSSI_BB_FILTER_ALPHA,6); /* default 7 */ - lgw_reg_w(LGW_RSSI_DEC_FILTER_ALPHA,7); /* default 5 */ - lgw_reg_w(LGW_RSSI_CHANN_FILTER_ALPHA,7); /* default 8 */ - lgw_reg_w(LGW_RSSI_BB_DEFAULT_VALUE,23); /* default 32 */ - lgw_reg_w(LGW_RSSI_CHANN_DEFAULT_VALUE,85); /* default 100 */ - lgw_reg_w(LGW_RSSI_DEC_DEFAULT_VALUE,66); /* default 100 */ - lgw_reg_w(LGW_DEC_GAIN_OFFSET,7); /* default 8 */ - lgw_reg_w(LGW_CHAN_GAIN_OFFSET,6); /* default 7 */ - - /* Correlator setup */ - // lgw_reg_w(LGW_CORR_DETECT_EN,126); /* default 126 */ - // lgw_reg_w(LGW_CORR_NUM_SAME_PEAK,4); /* default 4 */ - // lgw_reg_w(LGW_CORR_MAC_GAIN,5); /* default 5 */ - // lgw_reg_w(LGW_CORR_SAME_PEAKS_OPTION_SF6,0); /* default 0 */ - // lgw_reg_w(LGW_CORR_SAME_PEAKS_OPTION_SF7,1); /* default 1 */ - // lgw_reg_w(LGW_CORR_SAME_PEAKS_OPTION_SF8,1); /* default 1 */ - // lgw_reg_w(LGW_CORR_SAME_PEAKS_OPTION_SF9,1); /* default 1 */ - // lgw_reg_w(LGW_CORR_SAME_PEAKS_OPTION_SF10,1); /* default 1 */ - // lgw_reg_w(LGW_CORR_SAME_PEAKS_OPTION_SF11,1); /* default 1 */ - // lgw_reg_w(LGW_CORR_SAME_PEAKS_OPTION_SF12,1); /* default 1 */ - // lgw_reg_w(LGW_CORR_SIG_NOISE_RATIO_SF6,4); /* default 4 */ - // lgw_reg_w(LGW_CORR_SIG_NOISE_RATIO_SF7,4); /* default 4 */ - // lgw_reg_w(LGW_CORR_SIG_NOISE_RATIO_SF8,4); /* default 4 */ - // lgw_reg_w(LGW_CORR_SIG_NOISE_RATIO_SF9,4); /* default 4 */ - // lgw_reg_w(LGW_CORR_SIG_NOISE_RATIO_SF10,4); /* default 4 */ - // lgw_reg_w(LGW_CORR_SIG_NOISE_RATIO_SF11,4); /* default 4 */ - // lgw_reg_w(LGW_CORR_SIG_NOISE_RATIO_SF12,4); /* default 4 */ - - /* LoRa 'multi' demodulators setup */ - // lgw_reg_w(LGW_PREAMBLE_SYMB1_NB,10); /* default 10 */ - // lgw_reg_w(LGW_FREQ_TO_TIME_INVERT,29); /* default 29 */ - // lgw_reg_w(LGW_FRAME_SYNCH_GAIN,1); /* default 1 */ - // lgw_reg_w(LGW_SYNCH_DETECT_TH,1); /* default 1 */ - // lgw_reg_w(LGW_ZERO_PAD,0); /* default 0 */ - lgw_reg_w(LGW_SNR_AVG_CST,3); /* default 2 */ - if (lorawan_public) { /* LoRa network */ - lgw_reg_w(LGW_FRAME_SYNCH_PEAK1_POS,3); /* default 1 */ - lgw_reg_w(LGW_FRAME_SYNCH_PEAK2_POS,4); /* default 2 */ - } else { /* private network */ - lgw_reg_w(LGW_FRAME_SYNCH_PEAK1_POS,1); /* default 1 */ - lgw_reg_w(LGW_FRAME_SYNCH_PEAK2_POS,2); /* default 2 */ - } - - // lgw_reg_w(LGW_PREAMBLE_FINE_TIMING_GAIN,1); /* default 1 */ - // lgw_reg_w(LGW_ONLY_CRC_EN,1); /* default 1 */ - // lgw_reg_w(LGW_PAYLOAD_FINE_TIMING_GAIN,2); /* default 2 */ - // lgw_reg_w(LGW_TRACKING_INTEGRAL,0); /* default 0 */ - // lgw_reg_w(LGW_ADJUST_MODEM_START_OFFSET_RDX8,0); /* default 0 */ - // lgw_reg_w(LGW_ADJUST_MODEM_START_OFFSET_SF12_RDX4,4092); /* default 4092 */ - // lgw_reg_w(LGW_MAX_PAYLOAD_LEN,255); /* default 255 */ - - /* LoRa standalone 'MBWSSF' demodulator setup */ - // lgw_reg_w(LGW_MBWSSF_PREAMBLE_SYMB1_NB,10); /* default 10 */ - // lgw_reg_w(LGW_MBWSSF_FREQ_TO_TIME_INVERT,29); /* default 29 */ - // lgw_reg_w(LGW_MBWSSF_FRAME_SYNCH_GAIN,1); /* default 1 */ - // lgw_reg_w(LGW_MBWSSF_SYNCH_DETECT_TH,1); /* default 1 */ - // lgw_reg_w(LGW_MBWSSF_ZERO_PAD,0); /* default 0 */ - if (lorawan_public) { /* LoRa network */ - lgw_reg_w(LGW_MBWSSF_FRAME_SYNCH_PEAK1_POS,3); /* default 1 */ - lgw_reg_w(LGW_MBWSSF_FRAME_SYNCH_PEAK2_POS,4); /* default 2 */ - } else { - lgw_reg_w(LGW_MBWSSF_FRAME_SYNCH_PEAK1_POS,1); /* default 1 */ - lgw_reg_w(LGW_MBWSSF_FRAME_SYNCH_PEAK2_POS,2); /* default 2 */ - } - // lgw_reg_w(LGW_MBWSSF_ONLY_CRC_EN,1); /* default 1 */ - // lgw_reg_w(LGW_MBWSSF_PAYLOAD_FINE_TIMING_GAIN,2); /* default 2 */ - // lgw_reg_w(LGW_MBWSSF_PREAMBLE_FINE_TIMING_GAIN,1); /* default 1 */ - // lgw_reg_w(LGW_MBWSSF_TRACKING_INTEGRAL,0); /* default 0 */ - // lgw_reg_w(LGW_MBWSSF_AGC_FREEZE_ON_DETECT,1); /* default 1 */ - - /* FSK datapath setup */ - lgw_reg_w(LGW_FSK_RX_INVERT,1); /* default 0 */ - lgw_reg_w(LGW_FSK_MODEM_INVERT_IQ,1); /* default 0 */ - - /* FSK demodulator setup */ - lgw_reg_w(LGW_FSK_RSSI_LENGTH,4); /* default 0 */ - lgw_reg_w(LGW_FSK_PKT_MODE,1); /* variable length, default 0 */ - lgw_reg_w(LGW_FSK_CRC_EN,1); /* default 0 */ - lgw_reg_w(LGW_FSK_DCFREE_ENC,2); /* default 0 */ - // lgw_reg_w(LGW_FSK_CRC_IBM,0); /* default 0 */ - lgw_reg_w(LGW_FSK_ERROR_OSR_TOL,10); /* default 0 */ - lgw_reg_w(LGW_FSK_PKT_LENGTH,255); /* max packet length in variable length mode */ - // lgw_reg_w(LGW_FSK_NODE_ADRS,0); /* default 0 */ - // lgw_reg_w(LGW_FSK_BROADCAST,0); /* default 0 */ - // lgw_reg_w(LGW_FSK_AUTO_AFC_ON,0); /* default 0 */ - lgw_reg_w(LGW_FSK_PATTERN_TIMEOUT_CFG,128); /* sync timeout (allow 8 bytes preamble + 8 bytes sync word, default 0 */ - - /* TX general parameters */ - lgw_reg_w(LGW_TX_START_DELAY, TX_START_DELAY); /* default 0 */ - - /* TX LoRa */ - // lgw_reg_w(LGW_TX_MODE,0); /* default 0 */ - lgw_reg_w(LGW_TX_SWAP_IQ,1); /* "normal" polarity; default 0 */ - if (lorawan_public) { /* LoRa network */ - lgw_reg_w(LGW_TX_FRAME_SYNCH_PEAK1_POS,3); /* default 1 */ - lgw_reg_w(LGW_TX_FRAME_SYNCH_PEAK2_POS,4); /* default 2 */ - } else { /* Private network */ - lgw_reg_w(LGW_TX_FRAME_SYNCH_PEAK1_POS,1); /* default 1 */ - lgw_reg_w(LGW_TX_FRAME_SYNCH_PEAK2_POS,2); /* default 2 */ - } - - /* TX FSK */ - // lgw_reg_w(LGW_FSK_TX_GAUSSIAN_EN,1); /* default 1 */ - lgw_reg_w(LGW_FSK_TX_GAUSSIAN_SELECT_BT,2); /* Gaussian filter always on TX, default 0 */ - // lgw_reg_w(LGW_FSK_TX_PATTERN_EN,1); /* default 1 */ - // lgw_reg_w(LGW_FSK_TX_PREAMBLE_SEQ,0); /* default 0 */ - - return; + /* check if the concentrator is running */ + if (lgw_is_started == true) { + DEBUG_MSG("ERROR: CONCENTRATOR IS RUNNING, STOP IT BEFORE TOUCHING CONFIGURATION\n"); + return LGW_HAL_ERROR; + } + + x = lbt_setconf(&conf); + if (x != LGW_LBT_SUCCESS) { + DEBUG_MSG("ERROR: Failed to configure concentrator for LBT\n"); + return LGW_HAL_ERROR; + } + + return LGW_HAL_SUCCESS; } -/* -------------------------------------------------------------------------- */ -/* --- PUBLIC FUNCTIONS DEFINITION ------------------------------------------ */ +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ -int lgw_board_setconf(struct lgw_conf_board_s conf) { +int lgw_rxrf_setconf(uint8_t rf_chain, struct lgw_conf_rxrf_s conf) { - /* check if the concentrator is running */ - if (lgw_is_started == true) { - DEBUG_MSG("ERROR: CONCENTRATOR IS RUNNING, STOP IT BEFORE TOUCHING CONFIGURATION\n"); - return LGW_HAL_ERROR; - } + /* check if the concentrator is running */ + if (lgw_is_started == true) { + DEBUG_MSG("ERROR: CONCENTRATOR IS RUNNING, STOP IT BEFORE TOUCHING CONFIGURATION\n"); + return LGW_HAL_ERROR; + } - /* set internal config according to parameters */ - lorawan_public = conf.lorawan_public; - rf_clkout = conf.clksrc; + /* check input range (segfault prevention) */ + if (rf_chain >= LGW_RF_CHAIN_NB) { + DEBUG_MSG("ERROR: NOT A VALID RF_CHAIN NUMBER\n"); + return LGW_HAL_ERROR; + } - DEBUG_PRINTF("Note: board configuration; lorawan_public:%d, clksrc:%d\n", lorawan_public, rf_clkout); + /* check if radio type is supported */ + if ((conf.type != LGW_RADIO_TYPE_SX1255) && (conf.type != LGW_RADIO_TYPE_SX1257)) { + DEBUG_MSG("ERROR: NOT A VALID RADIO TYPE\n"); + return LGW_HAL_ERROR; + } - return LGW_HAL_SUCCESS; -} + /* set internal config according to parameters */ + rf_enable[rf_chain] = conf.enable; + rf_rx_freq[rf_chain] = conf.freq_hz; + rf_rssi_offset[rf_chain] = conf.rssi_offset; + rf_radio_type[rf_chain] = conf.type; + rf_tx_enable[rf_chain] = conf.tx_enable; -int lgw_rxrf_setconf(uint8_t rf_chain, struct lgw_conf_rxrf_s conf) { + DEBUG_PRINTF("Note: rf_chain %d configuration; en:%d freq:%d rssi_offset:%f radio_type:%d tx_enable:%d\n", rf_chain, rf_enable[rf_chain], rf_rx_freq[rf_chain], rf_rssi_offset[rf_chain], rf_radio_type[rf_chain], rf_tx_enable[rf_chain]); - /* check if the concentrator is running */ - if (lgw_is_started == true) { - DEBUG_MSG("ERROR: CONCENTRATOR IS RUNNING, STOP IT BEFORE TOUCHING CONFIGURATION\n"); - return LGW_HAL_ERROR; - } - - /* check input range (segfault prevention) */ - if (rf_chain >= LGW_RF_CHAIN_NB) { - DEBUG_MSG("ERROR: NOT A VALID RF_CHAIN NUMBER\n"); - return LGW_HAL_ERROR; - } - - /* check if radio type is supported */ - if ((conf.type != LGW_RADIO_TYPE_SX1255) && (conf.type != LGW_RADIO_TYPE_SX1257)) { - DEBUG_MSG("ERROR: NOT A VALID RADIO TYPE\n"); - return LGW_HAL_ERROR; - } - - /* set internal config according to parameters */ - rf_enable[rf_chain] = conf.enable; - rf_rx_freq[rf_chain] = conf.freq_hz; - rf_rssi_offset[rf_chain] = conf.rssi_offset; - rf_radio_type[rf_chain] = conf.type; - rf_tx_enable[rf_chain] = conf.tx_enable; - - DEBUG_PRINTF("Note: rf_chain %d configuration; en:%d freq:%d rssi_offset:%f radio_type:%d tx_enable:%d\n", rf_chain, rf_enable[rf_chain], rf_rx_freq[rf_chain], rf_rssi_offset[rf_chain], rf_radio_type[rf_chain], rf_tx_enable[rf_chain]); - - return LGW_HAL_SUCCESS; + return LGW_HAL_SUCCESS; } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ int lgw_rxif_setconf(uint8_t if_chain, struct lgw_conf_rxif_s conf) { - /* check if the concentrator is running */ - if (lgw_is_started == true) { - DEBUG_MSG("ERROR: CONCENTRATOR IS RUNNING, STOP IT BEFORE TOUCHING CONFIGURATION\n"); - return LGW_HAL_ERROR; - } - - /* check input range (segfault prevention) */ - if (if_chain >= LGW_IF_CHAIN_NB) { - DEBUG_PRINTF("ERROR: %d NOT A VALID IF_CHAIN NUMBER\n", if_chain); - return LGW_HAL_ERROR; - } - - /* if chain is disabled, don't care about most parameters */ - if (conf.enable == false) { - if_enable[if_chain] = false; - if_freq[if_chain] = 0; - DEBUG_PRINTF("Note: if_chain %d disabled\n", if_chain); - return LGW_HAL_SUCCESS; - } - - /* check 'general' parameters */ - if (ifmod_config[if_chain] == IF_UNDEFINED) { - DEBUG_PRINTF("ERROR: IF CHAIN %d NOT CONFIGURABLE\n", if_chain); - } - if (conf.rf_chain >= LGW_RF_CHAIN_NB) { - DEBUG_MSG("ERROR: INVALID RF_CHAIN TO ASSOCIATE WITH A LORA_STD IF CHAIN\n"); - return LGW_HAL_ERROR; - } - if ((conf.freq_hz + LGW_REF_BW/2) > ((int32_t)rf_rx_bandwidth[conf.rf_chain] / 2)) { - DEBUG_PRINTF("ERROR: IF FREQUENCY %d TOO HIGH\n", conf.freq_hz); - return LGW_HAL_ERROR; - } else if ((conf.freq_hz - LGW_REF_BW/2) < -((int32_t)rf_rx_bandwidth[conf.rf_chain] / 2)) { - DEBUG_PRINTF("ERROR: IF FREQUENCY %d TOO LOW\n", conf.freq_hz); - return LGW_HAL_ERROR; - } - /* WARNING: if the channel is 250 or 500kHz wide, that check is insufficient */ - - /* check parameters according to the type of IF chain + modem, - fill default if necessary, and commit configuration if everything is OK */ - switch (ifmod_config[if_chain]) { - case IF_LORA_STD: - /* fill default parameters if needed */ - if (conf.bandwidth == BW_UNDEFINED) { - conf.bandwidth = BW_250KHZ; - } - if (conf.datarate == DR_UNDEFINED) { - conf.datarate = DR_LORA_SF9; - } - /* check BW & DR */ - if (!IS_LORA_BW(conf.bandwidth)) { - DEBUG_MSG("ERROR: BANDWIDTH NOT SUPPORTED BY LORA_STD IF CHAIN\n"); - return LGW_HAL_ERROR; - } - if (!IS_LORA_STD_DR(conf.datarate)) { - DEBUG_MSG("ERROR: DATARATE NOT SUPPORTED BY LORA_STD IF CHAIN\n"); - return LGW_HAL_ERROR; - } - /* set internal configuration */ - if_enable[if_chain] = conf.enable; - if_rf_chain[if_chain] = conf.rf_chain; - if_freq[if_chain] = conf.freq_hz; - lora_rx_bw = conf.bandwidth; - lora_rx_sf = (uint8_t)(DR_LORA_MULTI & conf.datarate); /* filter SF out of the 7-12 range */ - if (SET_PPM_ON(conf.bandwidth, conf.datarate)) { - lora_rx_ppm_offset = true; - } else { - lora_rx_ppm_offset = false; - } - - DEBUG_PRINTF("Note: LoRa 'std' if_chain %d configuration; en:%d freq:%d bw:%d dr:%d\n", if_chain, if_enable[if_chain], if_freq[if_chain], lora_rx_bw, lora_rx_sf); - break; - - case IF_LORA_MULTI: - /* fill default parameters if needed */ - if (conf.bandwidth == BW_UNDEFINED) { - conf.bandwidth = BW_125KHZ; - } - if (conf.datarate == DR_UNDEFINED) { - conf.datarate = DR_LORA_MULTI; - } - /* check BW & DR */ - if (conf.bandwidth != BW_125KHZ) { - DEBUG_MSG("ERROR: BANDWIDTH NOT SUPPORTED BY LORA_MULTI IF CHAIN\n"); - return LGW_HAL_ERROR; - } - if (!IS_LORA_MULTI_DR(conf.datarate)) { - DEBUG_MSG("ERROR: DATARATE(S) NOT SUPPORTED BY LORA_MULTI IF CHAIN\n"); - return LGW_HAL_ERROR; - } - /* set internal configuration */ - if_enable[if_chain] = conf.enable; - if_rf_chain[if_chain] = conf.rf_chain; - if_freq[if_chain] = conf.freq_hz; - lora_multi_sfmask[if_chain] = (uint8_t)(DR_LORA_MULTI & conf.datarate); /* filter SF out of the 7-12 range */ - - DEBUG_PRINTF("Note: LoRa 'multi' if_chain %d configuration; en:%d freq:%d SF_mask:0x%02x\n", if_chain, if_enable[if_chain], if_freq[if_chain], lora_multi_sfmask[if_chain]); - break; - - case IF_FSK_STD: - /* fill default parameters if needed */ - if (conf.bandwidth == BW_UNDEFINED) { - conf.bandwidth = BW_250KHZ; - } - if (conf.datarate == DR_UNDEFINED) { - conf.datarate = 64000; /* default datarate */ - } - /* check BW & DR */ - if(!IS_FSK_BW(conf.bandwidth)) { - DEBUG_MSG("ERROR: BANDWIDTH NOT SUPPORTED BY FSK IF CHAIN\n"); - return LGW_HAL_ERROR; - } - if(!IS_FSK_DR(conf.datarate)) { - DEBUG_MSG("ERROR: DATARATE NOT SUPPORTED BY FSK IF CHAIN\n"); - return LGW_HAL_ERROR; - } - /* set internal configuration */ - if_enable[if_chain] = conf.enable; - if_rf_chain[if_chain] = conf.rf_chain; - if_freq[if_chain] = conf.freq_hz; - fsk_rx_bw = conf.bandwidth; - fsk_rx_dr = conf.datarate; - if (conf.sync_word > 0) { - fsk_sync_word_size = conf.sync_word_size; - fsk_sync_word = conf.sync_word; - } - DEBUG_PRINTF("Note: FSK if_chain %d configuration; en:%d freq:%d bw:%d dr:%d (%d real dr) sync:0x%0*llX\n", if_chain, if_enable[if_chain], if_freq[if_chain], fsk_rx_bw, fsk_rx_dr, LGW_XTAL_FREQU/(LGW_XTAL_FREQU/fsk_rx_dr), 2*fsk_sync_word_size, fsk_sync_word); - break; - - default: - DEBUG_PRINTF("ERROR: IF CHAIN %d TYPE NOT SUPPORTED\n", if_chain); - return LGW_HAL_ERROR; - } - - return LGW_HAL_SUCCESS; + /* check if the concentrator is running */ + if (lgw_is_started == true) { + DEBUG_MSG("ERROR: CONCENTRATOR IS RUNNING, STOP IT BEFORE TOUCHING CONFIGURATION\n"); + return LGW_HAL_ERROR; + } + + /* check input range (segfault prevention) */ + if (if_chain >= LGW_IF_CHAIN_NB) { + DEBUG_PRINTF("ERROR: %d NOT A VALID IF_CHAIN NUMBER\n", if_chain); + return LGW_HAL_ERROR; + } + + /* if chain is disabled, don't care about most parameters */ + if (conf.enable == false) { + if_enable[if_chain] = false; + if_freq[if_chain] = 0; + DEBUG_PRINTF("Note: if_chain %d disabled\n", if_chain); + return LGW_HAL_SUCCESS; + } + + /* check 'general' parameters */ + if (ifmod_config[if_chain] == IF_UNDEFINED) { + DEBUG_PRINTF("ERROR: IF CHAIN %d NOT CONFIGURABLE\n", if_chain); + } + if (conf.rf_chain >= LGW_RF_CHAIN_NB) { + DEBUG_MSG("ERROR: INVALID RF_CHAIN TO ASSOCIATE WITH A LORA_STD IF CHAIN\n"); + return LGW_HAL_ERROR; + } + if ((conf.freq_hz + LGW_REF_BW/2) > ((int32_t)rf_rx_bandwidth[conf.rf_chain] / 2)) { + DEBUG_PRINTF("ERROR: IF FREQUENCY %d TOO HIGH\n", conf.freq_hz); + return LGW_HAL_ERROR; + } else if ((conf.freq_hz - LGW_REF_BW/2) < -((int32_t)rf_rx_bandwidth[conf.rf_chain] / 2)) { + DEBUG_PRINTF("ERROR: IF FREQUENCY %d TOO LOW\n", conf.freq_hz); + return LGW_HAL_ERROR; + } + /* WARNING: if the channel is 250 or 500kHz wide, that check is insufficient */ + + /* check parameters according to the type of IF chain + modem, + fill default if necessary, and commit configuration if everything is OK */ + switch (ifmod_config[if_chain]) { + case IF_LORA_STD: + /* fill default parameters if needed */ + if (conf.bandwidth == BW_UNDEFINED) { + conf.bandwidth = BW_250KHZ; + } + if (conf.datarate == DR_UNDEFINED) { + conf.datarate = DR_LORA_SF9; + } + /* check BW & DR */ + if (!IS_LORA_BW(conf.bandwidth)) { + DEBUG_MSG("ERROR: BANDWIDTH NOT SUPPORTED BY LORA_STD IF CHAIN\n"); + return LGW_HAL_ERROR; + } + if (!IS_LORA_STD_DR(conf.datarate)) { + DEBUG_MSG("ERROR: DATARATE NOT SUPPORTED BY LORA_STD IF CHAIN\n"); + return LGW_HAL_ERROR; + } + /* set internal configuration */ + if_enable[if_chain] = conf.enable; + if_rf_chain[if_chain] = conf.rf_chain; + if_freq[if_chain] = conf.freq_hz; + lora_rx_bw = conf.bandwidth; + lora_rx_sf = (uint8_t)(DR_LORA_MULTI & conf.datarate); /* filter SF out of the 7-12 range */ + if (SET_PPM_ON(conf.bandwidth, conf.datarate)) { + lora_rx_ppm_offset = true; + } else { + lora_rx_ppm_offset = false; + } + + DEBUG_PRINTF("Note: LoRa 'std' if_chain %d configuration; en:%d freq:%d bw:%d dr:%d\n", if_chain, if_enable[if_chain], if_freq[if_chain], lora_rx_bw, lora_rx_sf); + break; + + case IF_LORA_MULTI: + /* fill default parameters if needed */ + if (conf.bandwidth == BW_UNDEFINED) { + conf.bandwidth = BW_125KHZ; + } + if (conf.datarate == DR_UNDEFINED) { + conf.datarate = DR_LORA_MULTI; + } + /* check BW & DR */ + if (conf.bandwidth != BW_125KHZ) { + DEBUG_MSG("ERROR: BANDWIDTH NOT SUPPORTED BY LORA_MULTI IF CHAIN\n"); + return LGW_HAL_ERROR; + } + if (!IS_LORA_MULTI_DR(conf.datarate)) { + DEBUG_MSG("ERROR: DATARATE(S) NOT SUPPORTED BY LORA_MULTI IF CHAIN\n"); + return LGW_HAL_ERROR; + } + /* set internal configuration */ + if_enable[if_chain] = conf.enable; + if_rf_chain[if_chain] = conf.rf_chain; + if_freq[if_chain] = conf.freq_hz; + lora_multi_sfmask[if_chain] = (uint8_t)(DR_LORA_MULTI & conf.datarate); /* filter SF out of the 7-12 range */ + + DEBUG_PRINTF("Note: LoRa 'multi' if_chain %d configuration; en:%d freq:%d SF_mask:0x%02x\n", if_chain, if_enable[if_chain], if_freq[if_chain], lora_multi_sfmask[if_chain]); + break; + + case IF_FSK_STD: + /* fill default parameters if needed */ + if (conf.bandwidth == BW_UNDEFINED) { + conf.bandwidth = BW_250KHZ; + } + if (conf.datarate == DR_UNDEFINED) { + conf.datarate = 64000; /* default datarate */ + } + /* check BW & DR */ + if(!IS_FSK_BW(conf.bandwidth)) { + DEBUG_MSG("ERROR: BANDWIDTH NOT SUPPORTED BY FSK IF CHAIN\n"); + return LGW_HAL_ERROR; + } + if(!IS_FSK_DR(conf.datarate)) { + DEBUG_MSG("ERROR: DATARATE NOT SUPPORTED BY FSK IF CHAIN\n"); + return LGW_HAL_ERROR; + } + /* set internal configuration */ + if_enable[if_chain] = conf.enable; + if_rf_chain[if_chain] = conf.rf_chain; + if_freq[if_chain] = conf.freq_hz; + fsk_rx_bw = conf.bandwidth; + fsk_rx_dr = conf.datarate; + if (conf.sync_word > 0) { + fsk_sync_word_size = conf.sync_word_size; + fsk_sync_word = conf.sync_word; + } + DEBUG_PRINTF("Note: FSK if_chain %d configuration; en:%d freq:%d bw:%d dr:%d (%d real dr) sync:0x%0*llX\n", if_chain, if_enable[if_chain], if_freq[if_chain], fsk_rx_bw, fsk_rx_dr, LGW_XTAL_FREQU/(LGW_XTAL_FREQU/fsk_rx_dr), 2*fsk_sync_word_size, fsk_sync_word); + break; + + default: + DEBUG_PRINTF("ERROR: IF CHAIN %d TYPE NOT SUPPORTED\n", if_chain); + return LGW_HAL_ERROR; + } + + return LGW_HAL_SUCCESS; } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ int lgw_txgain_setconf(struct lgw_tx_gain_lut_s *conf) { - int i; - - /* Check LUT size */ - if ((conf->size < 1) || (conf->size > TX_GAIN_LUT_SIZE_MAX)) { - DEBUG_PRINTF("ERROR: TX gain LUT must have at least one entry and maximum %d entries\n", TX_GAIN_LUT_SIZE_MAX); - return LGW_HAL_ERROR; - } - - txgain_lut.size = conf->size; - - for (i = 0; i < txgain_lut.size; i++) { - /* Check gain range */ - if (conf->lut[i].dig_gain > 3) { - DEBUG_MSG("ERROR: TX gain LUT: SX1301 digital gain must be between 0 and 3\n"); - return LGW_HAL_ERROR; - } - if (conf->lut[i].dac_gain != 3) { - DEBUG_MSG("ERROR: TX gain LUT: SX1257 DAC gains != 3 are not supported\n"); - return LGW_HAL_ERROR; - } - if (conf->lut[i].mix_gain > 15) { - DEBUG_MSG("ERROR: TX gain LUT: SX1257 mixer gain must not exceed 15\n"); - return LGW_HAL_ERROR; - } else if (conf->lut[i].mix_gain < 8) { - DEBUG_MSG("ERROR: TX gain LUT: SX1257 mixer gains < 8 are not supported\n"); - return LGW_HAL_ERROR; - } - if (conf->lut[i].pa_gain > 3) { - DEBUG_MSG("ERROR: TX gain LUT: External PA gain must not exceed 3\n"); - return LGW_HAL_ERROR; - } - - /* Set internal LUT */ - txgain_lut.lut[i].dig_gain = conf->lut[i].dig_gain; - txgain_lut.lut[i].dac_gain = conf->lut[i].dac_gain; - txgain_lut.lut[i].mix_gain = conf->lut[i].mix_gain; - txgain_lut.lut[i].pa_gain = conf->lut[i].pa_gain; - txgain_lut.lut[i].rf_power = conf->lut[i].rf_power; - } - - return LGW_HAL_SUCCESS; + int i; + + /* Check LUT size */ + if ((conf->size < 1) || (conf->size > TX_GAIN_LUT_SIZE_MAX)) { + DEBUG_PRINTF("ERROR: TX gain LUT must have at least one entry and maximum %d entries\n", TX_GAIN_LUT_SIZE_MAX); + return LGW_HAL_ERROR; + } + + txgain_lut.size = conf->size; + + for (i = 0; i < txgain_lut.size; i++) { + /* Check gain range */ + if (conf->lut[i].dig_gain > 3) { + DEBUG_MSG("ERROR: TX gain LUT: SX1301 digital gain must be between 0 and 3\n"); + return LGW_HAL_ERROR; + } + if (conf->lut[i].dac_gain != 3) { + DEBUG_MSG("ERROR: TX gain LUT: SX1257 DAC gains != 3 are not supported\n"); + return LGW_HAL_ERROR; + } + if (conf->lut[i].mix_gain > 15) { + DEBUG_MSG("ERROR: TX gain LUT: SX1257 mixer gain must not exceed 15\n"); + return LGW_HAL_ERROR; + } else if (conf->lut[i].mix_gain < 8) { + DEBUG_MSG("ERROR: TX gain LUT: SX1257 mixer gains < 8 are not supported\n"); + return LGW_HAL_ERROR; + } + if (conf->lut[i].pa_gain > 3) { + DEBUG_MSG("ERROR: TX gain LUT: External PA gain must not exceed 3\n"); + return LGW_HAL_ERROR; + } + + /* Set internal LUT */ + txgain_lut.lut[i].dig_gain = conf->lut[i].dig_gain; + txgain_lut.lut[i].dac_gain = conf->lut[i].dac_gain; + txgain_lut.lut[i].mix_gain = conf->lut[i].mix_gain; + txgain_lut.lut[i].pa_gain = conf->lut[i].pa_gain; + txgain_lut.lut[i].rf_power = conf->lut[i].rf_power; + } + + return LGW_HAL_SUCCESS; } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ int lgw_start(void) { - int i; - int reg_stat; - unsigned x; - uint8_t radio_select; - int32_t read_val; - uint8_t load_val; - uint8_t fw_version; - uint8_t cal_cmd; - uint16_t cal_time; - uint8_t cal_status; - - uint64_t fsk_sync_word_reg; - - if (lgw_is_started == true) { - DEBUG_MSG("Note: LoRa concentrator already started, restarting it now\n"); - } - - reg_stat = lgw_connect(); - if (reg_stat == LGW_REG_ERROR) { - DEBUG_MSG("ERROR: FAIL TO CONNECT BOARD\n"); - return LGW_HAL_ERROR; - } - - /* reset the registers (also shuts the radios down) */ - lgw_soft_reset(); - - /* ungate clocks (gated by default) */ - lgw_reg_w(LGW_GLOBAL_EN, 1); - - /* switch on and reset the radios (also starts the 32 MHz XTAL) */ - lgw_reg_w(LGW_RADIO_A_EN,1); - lgw_reg_w(LGW_RADIO_B_EN,1); - wait_ms(500); /* TODO: optimize */ - lgw_reg_w(LGW_RADIO_RST,1); - wait_ms(5); - lgw_reg_w(LGW_RADIO_RST,0); - - /* setup the radios */ - setup_sx125x(0, rf_rx_freq[0]); - setup_sx125x(1, rf_rx_freq[1]); - - /* gives AGC control of GPIOs to enable Tx external digital filter */ - lgw_reg_w(LGW_GPIO_MODE,31); /* Set all GPIOs as output */ - lgw_reg_w(LGW_GPIO_SELECT_OUTPUT,2); - - /* GPIOs table : - DGPIO0 -> N/A - DGPIO1 -> N/A - DGPIO2 -> N/A - DGPIO3 -> TX digital filter ON - DGPIO4 -> TX ON - */ - - /* select calibration command */ - cal_cmd = 0; - cal_cmd |= rf_enable[0] ? 0x01 : 0x00; /* Bit 0: Calibrate Rx IQ mismatch compensation on radio A */ - cal_cmd |= rf_enable[1] ? 0x02 : 0x00; /* Bit 1: Calibrate Rx IQ mismatch compensation on radio B */ - cal_cmd |= (rf_enable[0] && rf_tx_enable[0]) ? 0x04 : 0x00; /* Bit 2: Calibrate Tx DC offset on radio A */ - cal_cmd |= (rf_enable[1] && rf_tx_enable[1]) ? 0x08 : 0x00; /* Bit 3: Calibrate Tx DC offset on radio B */ - cal_cmd |= 0x10; /* Bit 4: 0: calibrate with DAC gain=2, 1: with DAC gain=3 (use 3) */ - - switch (rf_radio_type[0]) { /* we assume that there is only one radio type on the board */ - case LGW_RADIO_TYPE_SX1255: - cal_cmd |= 0x20; /* Bit 5: 0: SX1257, 1: SX1255 */ - break; - case LGW_RADIO_TYPE_SX1257: - cal_cmd |= 0x00; /* Bit 5: 0: SX1257, 1: SX1255 */ - break; - default: - DEBUG_PRINTF("ERROR: UNEXPECTED VALUE %d FOR RADIO TYPE\n", rf_radio_type[0]); - break; - } - - cal_cmd |= 0x00; /* Bit 6-7: Board type 0: ref, 1: FPGA, 3: board X */ - cal_time = 2300; /* measured between 2.1 and 2.2 sec, because 1 TX only */ - - /* Load the calibration firmware */ - load_firmware(MCU_AGC, cal_firmware, MCU_AGC_FW_BYTE); - lgw_reg_w(LGW_FORCE_HOST_RADIO_CTRL,0); /* gives to AGC MCU the control of the radios */ - lgw_reg_w(LGW_RADIO_SELECT,cal_cmd); /* send calibration configuration word */ - lgw_reg_w(LGW_MCU_RST_1,0); - - /* Check firmware version */ - lgw_reg_w(LGW_DBG_AGC_MCU_RAM_ADDR, FW_VERSION_ADDR); - lgw_reg_r(LGW_DBG_AGC_MCU_RAM_DATA, &read_val); - fw_version = (uint8_t)read_val; - if (fw_version != FW_VERSION_CAL) { - printf("ERROR: Version of calibration firmware not expected, actual:%d expected:%d\n", fw_version, FW_VERSION_CAL); - return -1; - } - - lgw_reg_w(LGW_PAGE_REG,3); /* Calibration will start on this condition as soon as MCU can talk to concentrator registers */ - lgw_reg_w(LGW_EMERGENCY_FORCE_HOST_CTRL,0); /* Give control of concentrator registers to MCU */ - - /* Wait for calibration to end */ - DEBUG_PRINTF("Note: calibration started (time: %u ms)\n", cal_time); - wait_ms(cal_time); /* Wait for end of calibration */ - lgw_reg_w(LGW_EMERGENCY_FORCE_HOST_CTRL,1); /* Take back control */ - - /* Get calibration status */ - lgw_reg_r(LGW_MCU_AGC_STATUS, &read_val); - cal_status = (uint8_t)read_val; - /* - bit 7: calibration finished - bit 0: could access SX1301 registers - bit 1: could access radio A registers - bit 2: could access radio B registers - bit 3: radio A RX image rejection successful - bit 4: radio B RX image rejection successful - bit 5: radio A TX imbalance correction successful - bit 6: radio B TX imbalance correction successful - */ - if ((cal_status & 0x81) != 0x81) { - DEBUG_PRINTF("ERROR: CALIBRATION FAILURE (STATUS = %u)\n", cal_status); - return LGW_HAL_ERROR; - } else { - DEBUG_PRINTF("Note: calibration finished (status = %u)\n", cal_status); - } - if (rf_enable[0] && ((cal_status & 0x02) == 0)) { - DEBUG_MSG("WARNING: calibration could not access radio A\n"); - } - if (rf_enable[1] && ((cal_status & 0x04) == 0)) { - DEBUG_MSG("WARNING: calibration could not access radio B\n"); - } - if (rf_enable[0] && ((cal_status & 0x08) == 0)) { - DEBUG_MSG("WARNING: problem in calibration of radio A for image rejection\n"); - } - if (rf_enable[1] && ((cal_status & 0x10) == 0)) { - DEBUG_MSG("WARNING: problem in calibration of radio B for image rejection\n"); - } - if (rf_enable[0] && rf_tx_enable[0] && ((cal_status & 0x20) == 0)) { - DEBUG_MSG("WARNING: problem in calibration of radio A for TX imbalance\n"); - } - if (rf_enable[1] && rf_tx_enable[1] && ((cal_status & 0x40) == 0)) { - DEBUG_MSG("WARNING: problem in calibration of radio B for TX imbalance\n"); - } - - /* Get TX DC offset values */ - for(i=0; i<=7; ++i) { - lgw_reg_w(LGW_DBG_AGC_MCU_RAM_ADDR, 0xA0+i); - lgw_reg_r(LGW_DBG_AGC_MCU_RAM_DATA, &read_val); - cal_offset_a_i[i] = (int8_t)read_val; - lgw_reg_w(LGW_DBG_AGC_MCU_RAM_ADDR, 0xA8+i); - lgw_reg_r(LGW_DBG_AGC_MCU_RAM_DATA, &read_val); - cal_offset_a_q[i] = (int8_t)read_val; - lgw_reg_w(LGW_DBG_AGC_MCU_RAM_ADDR, 0xB0+i); - lgw_reg_r(LGW_DBG_AGC_MCU_RAM_DATA, &read_val); - cal_offset_b_i[i] = (int8_t)read_val; - lgw_reg_w(LGW_DBG_AGC_MCU_RAM_ADDR, 0xB8+i); - lgw_reg_r(LGW_DBG_AGC_MCU_RAM_DATA, &read_val); - cal_offset_b_q[i] = (int8_t)read_val; - } - - /* load adjusted parameters */ - lgw_constant_adjust(); - - /* Freq-to-time-drift calculation */ - x = 4096000000 / (rf_rx_freq[0] >> 1); /* dividend: (4*2048*1000000) >> 1, rescaled to avoid 32b overflow */ - x = ( x > 63 ) ? 63 : x; /* saturation */ - lgw_reg_w(LGW_FREQ_TO_TIME_DRIFT, x); /* default 9 */ - - x = 4096000000 / (rf_rx_freq[0] >> 3); /* dividend: (16*2048*1000000) >> 3, rescaled to avoid 32b overflow */ - x = ( x > 63 ) ? 63 : x; /* saturation */ - lgw_reg_w(LGW_MBWSSF_FREQ_TO_TIME_DRIFT, x); /* default 36 */ - - /* configure LoRa 'multi' demodulators aka. LoRa 'sensor' channels (IF0-3) */ - radio_select = 0; /* IF mapping to radio A/B (per bit, 0=A, 1=B) */ - for(i=0; i> 32))); - if (if_enable[9] == true) { - lgw_reg_w(LGW_FSK_RADIO_SELECT, if_rf_chain[9]); - lgw_reg_w(LGW_FSK_BR_RATIO,LGW_XTAL_FREQU/fsk_rx_dr); /* setting the dividing ratio for datarate */ - lgw_reg_w(LGW_FSK_CH_BW_EXPO,fsk_rx_bw); - lgw_reg_w(LGW_FSK_MODEM_ENABLE,1); /* default 0 */ - } else { - lgw_reg_w(LGW_FSK_MODEM_ENABLE,0); - } - - /* Load firmware */ - load_firmware(MCU_ARB, arb_firmware, MCU_ARB_FW_BYTE); - load_firmware(MCU_AGC, agc_firmware, MCU_AGC_FW_BYTE); - - /* gives the AGC MCU control over radio, RF front-end and filter gain */ - lgw_reg_w(LGW_FORCE_HOST_RADIO_CTRL,0); - lgw_reg_w(LGW_FORCE_HOST_FE_CTRL,0); - lgw_reg_w(LGW_FORCE_DEC_FILTER_GAIN,0); - - /* Get MCUs out of reset */ - lgw_reg_w(LGW_RADIO_SELECT, 0); /* MUST not be = to 1 or 2 at firmware init */ - lgw_reg_w(LGW_MCU_RST_0, 0); - lgw_reg_w(LGW_MCU_RST_1, 0); - - /* Check firmware version */ - lgw_reg_w(LGW_DBG_AGC_MCU_RAM_ADDR, FW_VERSION_ADDR); - lgw_reg_r(LGW_DBG_AGC_MCU_RAM_DATA, &read_val); - fw_version = (uint8_t)read_val; - if (fw_version != FW_VERSION_AGC) { - DEBUG_PRINTF("ERROR: Version of AGC firmware not expected, actual:%d expected:%d\n", fw_version, FW_VERSION_AGC); - return LGW_HAL_ERROR; - } - lgw_reg_w(LGW_DBG_ARB_MCU_RAM_ADDR, FW_VERSION_ADDR); - lgw_reg_r(LGW_DBG_ARB_MCU_RAM_DATA, &read_val); - fw_version = (uint8_t)read_val; - if (fw_version != FW_VERSION_ARB) { - DEBUG_PRINTF("ERROR: Version of arbiter firmware not expected, actual:%d expected:%d\n", fw_version, FW_VERSION_ARB); - return LGW_HAL_ERROR; - } - - DEBUG_MSG("Info: Initialising AGC firmware...\n"); - wait_ms(1); - - lgw_reg_r(LGW_MCU_AGC_STATUS, &read_val); - if (read_val != 0x10) { - DEBUG_PRINTF("ERROR: AGC FIRMWARE INITIALIZATION FAILURE, STATUS 0x%02X\n", (uint8_t)read_val); - return LGW_HAL_ERROR; - } - - /* Update Tx gain LUT and start AGC */ - for (i = 0; i < txgain_lut.size; ++i) { - lgw_reg_w(LGW_RADIO_SELECT, AGC_CMD_WAIT); /* start a transaction */ - wait_ms(1); - load_val = txgain_lut.lut[i].mix_gain + (16 * txgain_lut.lut[i].dac_gain) + (64 * txgain_lut.lut[i].pa_gain); - lgw_reg_w(LGW_RADIO_SELECT, load_val); - wait_ms(1); - lgw_reg_r(LGW_MCU_AGC_STATUS, &read_val); - if (read_val != (0x30 + i)) { - DEBUG_PRINTF("ERROR: AGC FIRMWARE INITIALIZATION FAILURE, STATUS 0x%02X\n", (uint8_t)read_val); - return LGW_HAL_ERROR; - } - } - /* As the AGC fw is waiting for 16 entries, we need to abort the transaction if we get less entries */ - if (txgain_lut.size < TX_GAIN_LUT_SIZE_MAX) { - lgw_reg_w(LGW_RADIO_SELECT, AGC_CMD_WAIT); - wait_ms(1); - load_val = AGC_CMD_ABORT; - lgw_reg_w(LGW_RADIO_SELECT, load_val); - wait_ms(1); - lgw_reg_r(LGW_MCU_AGC_STATUS, &read_val); - if (read_val != 0x30) { - DEBUG_PRINTF("ERROR: AGC FIRMWARE INITIALIZATION FAILURE, STATUS 0x%02X\n", (uint8_t)read_val); - return LGW_HAL_ERROR; - } - } - - /* Load Tx freq MSBs (always 3 if f > 768 for SX1257 or f > 384 for SX1255 */ - lgw_reg_w(LGW_RADIO_SELECT, AGC_CMD_WAIT); - wait_ms(1); - lgw_reg_w(LGW_RADIO_SELECT, 3); - wait_ms(1); - lgw_reg_r(LGW_MCU_AGC_STATUS, &read_val); - if (read_val != 0x33) { - DEBUG_PRINTF("ERROR: AGC FIRMWARE INITIALIZATION FAILURE, STATUS 0x%02X\n", (uint8_t)read_val); - return LGW_HAL_ERROR; - } - - /* Load chan_select firmware option */ - lgw_reg_w(LGW_RADIO_SELECT, AGC_CMD_WAIT); - wait_ms(1); - lgw_reg_w(LGW_RADIO_SELECT, 0); - wait_ms(1); - lgw_reg_r(LGW_MCU_AGC_STATUS, &read_val); - if (read_val != 0x30) { - DEBUG_PRINTF("ERROR: AGC FIRMWARE INITIALIZATION FAILURE, STATUS 0x%02X\n", (uint8_t)read_val); - return LGW_HAL_ERROR; - } - - /* End AGC firmware init and check status */ - lgw_reg_w(LGW_RADIO_SELECT, AGC_CMD_WAIT); - wait_ms(1); - lgw_reg_w(LGW_RADIO_SELECT, radio_select); /* Load intended value of RADIO_SELECT */ - wait_ms(1); - DEBUG_MSG("Info: putting back original RADIO_SELECT value\n"); - lgw_reg_r(LGW_MCU_AGC_STATUS, &read_val); - if (read_val != 0x40) { - DEBUG_PRINTF("ERROR: AGC FIRMWARE INITIALIZATION FAILURE, STATUS 0x%02X\n", (uint8_t)read_val); - return LGW_HAL_ERROR; - } - - /* enable GPS event capture */ - lgw_reg_w(LGW_GPS_EN,1); - - lgw_is_started = true; - return LGW_HAL_SUCCESS; + int i; + int reg_stat; + unsigned x; + uint8_t radio_select; + int32_t read_val; + uint8_t load_val; + uint8_t fw_version; + uint8_t cal_cmd; + uint16_t cal_time; + uint8_t cal_status; + + uint64_t fsk_sync_word_reg; + + if (lgw_is_started == true) { + DEBUG_MSG("Note: LoRa concentrator already started, restarting it now\n"); + } + + reg_stat = lgw_connect(); + if (reg_stat == LGW_REG_ERROR) { + DEBUG_MSG("ERROR: FAIL TO CONNECT BOARD\n"); + return LGW_HAL_ERROR; + } + + /* reset the registers (also shuts the radios down) */ + lgw_soft_reset(); + + /* gate clocks */ + lgw_reg_w(LGW_GLOBAL_EN, 0); + lgw_reg_w(LGW_CLK32M_EN, 0); + + /* switch on and reset the radios (also starts the 32 MHz XTAL) */ + lgw_reg_w(LGW_RADIO_A_EN,1); + lgw_reg_w(LGW_RADIO_B_EN,1); + wait_ms(500); /* TODO: optimize */ + lgw_reg_w(LGW_RADIO_RST,1); + wait_ms(5); + lgw_reg_w(LGW_RADIO_RST,0); + + /* setup the radios */ + setup_sx125x(0, rf_clkout, rf_enable[0], rf_radio_type[0], rf_rx_freq[0]); + setup_sx125x(1, rf_clkout, rf_enable[1], rf_radio_type[1], rf_rx_freq[1]); + + /* gives AGC control of GPIOs to enable Tx external digital filter */ + lgw_reg_w(LGW_GPIO_MODE,31); /* Set all GPIOs as output */ + lgw_reg_w(LGW_GPIO_SELECT_OUTPUT,2); + + /* Configure LBT */ + if (lbt_enable == true) { + lgw_reg_w(LGW_CLK32M_EN, 1); + i = lbt_setup(lbt_first_channel_freq, lbt_rssi_target, lbt_scan_time_us, lbt_nb_channel); + if (i != LGW_LBT_SUCCESS) { + DEBUG_MSG("ERROR: lbt_setup() did not return SUCCESS\n"); + return LGW_HAL_ERROR; + } + + lgw_reg_w(LGW_CLK32M_EN, 0); + i = lbt_start(); + if (i != LGW_LBT_SUCCESS) { + DEBUG_MSG("ERROR: lbt_start() did not return SUCCESS\n"); + return LGW_HAL_ERROR; + } + } + + /* Enable clocks */ + lgw_reg_w(LGW_GLOBAL_EN, 1); + lgw_reg_w(LGW_CLK32M_EN, 1); + + /* GPIOs table : + DGPIO0 -> N/A + DGPIO1 -> N/A + DGPIO2 -> N/A + DGPIO3 -> TX digital filter ON + DGPIO4 -> TX ON + */ + + /* select calibration command */ + cal_cmd = 0; + cal_cmd |= rf_enable[0] ? 0x01 : 0x00; /* Bit 0: Calibrate Rx IQ mismatch compensation on radio A */ + cal_cmd |= rf_enable[1] ? 0x02 : 0x00; /* Bit 1: Calibrate Rx IQ mismatch compensation on radio B */ + cal_cmd |= (rf_enable[0] && rf_tx_enable[0]) ? 0x04 : 0x00; /* Bit 2: Calibrate Tx DC offset on radio A */ + cal_cmd |= (rf_enable[1] && rf_tx_enable[1]) ? 0x08 : 0x00; /* Bit 3: Calibrate Tx DC offset on radio B */ + cal_cmd |= 0x10; /* Bit 4: 0: calibrate with DAC gain=2, 1: with DAC gain=3 (use 3) */ + + switch (rf_radio_type[0]) { /* we assume that there is only one radio type on the board */ + case LGW_RADIO_TYPE_SX1255: + cal_cmd |= 0x20; /* Bit 5: 0: SX1257, 1: SX1255 */ + break; + case LGW_RADIO_TYPE_SX1257: + cal_cmd |= 0x00; /* Bit 5: 0: SX1257, 1: SX1255 */ + break; + default: + DEBUG_PRINTF("ERROR: UNEXPECTED VALUE %d FOR RADIO TYPE\n", rf_radio_type[0]); + break; + } + + cal_cmd |= 0x00; /* Bit 6-7: Board type 0: ref, 1: FPGA, 3: board X */ + cal_time = 2300; /* measured between 2.1 and 2.2 sec, because 1 TX only */ + + /* Load the calibration firmware */ + load_firmware(MCU_AGC, cal_firmware, MCU_AGC_FW_BYTE); + lgw_reg_w(LGW_FORCE_HOST_RADIO_CTRL,0); /* gives to AGC MCU the control of the radios */ + lgw_reg_w(LGW_RADIO_SELECT,cal_cmd); /* send calibration configuration word */ + lgw_reg_w(LGW_MCU_RST_1,0); + + /* Check firmware version */ + lgw_reg_w(LGW_DBG_AGC_MCU_RAM_ADDR, FW_VERSION_ADDR); + lgw_reg_r(LGW_DBG_AGC_MCU_RAM_DATA, &read_val); + fw_version = (uint8_t)read_val; + if (fw_version != FW_VERSION_CAL) { + printf("ERROR: Version of calibration firmware not expected, actual:%d expected:%d\n", fw_version, FW_VERSION_CAL); + return -1; + } + + lgw_reg_w(LGW_PAGE_REG,3); /* Calibration will start on this condition as soon as MCU can talk to concentrator registers */ + lgw_reg_w(LGW_EMERGENCY_FORCE_HOST_CTRL,0); /* Give control of concentrator registers to MCU */ + + /* Wait for calibration to end */ + DEBUG_PRINTF("Note: calibration started (time: %u ms)\n", cal_time); + wait_ms(cal_time); /* Wait for end of calibration */ + lgw_reg_w(LGW_EMERGENCY_FORCE_HOST_CTRL,1); /* Take back control */ + + /* Get calibration status */ + lgw_reg_r(LGW_MCU_AGC_STATUS, &read_val); + cal_status = (uint8_t)read_val; + /* + bit 7: calibration finished + bit 0: could access SX1301 registers + bit 1: could access radio A registers + bit 2: could access radio B registers + bit 3: radio A RX image rejection successful + bit 4: radio B RX image rejection successful + bit 5: radio A TX imbalance correction successful + bit 6: radio B TX imbalance correction successful + */ + if ((cal_status & 0x81) != 0x81) { + DEBUG_PRINTF("ERROR: CALIBRATION FAILURE (STATUS = %u)\n", cal_status); + return LGW_HAL_ERROR; + } else { + DEBUG_PRINTF("Note: calibration finished (status = %u)\n", cal_status); + } + if (rf_enable[0] && ((cal_status & 0x02) == 0)) { + DEBUG_MSG("WARNING: calibration could not access radio A\n"); + } + if (rf_enable[1] && ((cal_status & 0x04) == 0)) { + DEBUG_MSG("WARNING: calibration could not access radio B\n"); + } + if (rf_enable[0] && ((cal_status & 0x08) == 0)) { + DEBUG_MSG("WARNING: problem in calibration of radio A for image rejection\n"); + } + if (rf_enable[1] && ((cal_status & 0x10) == 0)) { + DEBUG_MSG("WARNING: problem in calibration of radio B for image rejection\n"); + } + if (rf_enable[0] && rf_tx_enable[0] && ((cal_status & 0x20) == 0)) { + DEBUG_MSG("WARNING: problem in calibration of radio A for TX imbalance\n"); + } + if (rf_enable[1] && rf_tx_enable[1] && ((cal_status & 0x40) == 0)) { + DEBUG_MSG("WARNING: problem in calibration of radio B for TX imbalance\n"); + } + + /* Get TX DC offset values */ + for(i=0; i<=7; ++i) { + lgw_reg_w(LGW_DBG_AGC_MCU_RAM_ADDR, 0xA0+i); + lgw_reg_r(LGW_DBG_AGC_MCU_RAM_DATA, &read_val); + cal_offset_a_i[i] = (int8_t)read_val; + lgw_reg_w(LGW_DBG_AGC_MCU_RAM_ADDR, 0xA8+i); + lgw_reg_r(LGW_DBG_AGC_MCU_RAM_DATA, &read_val); + cal_offset_a_q[i] = (int8_t)read_val; + lgw_reg_w(LGW_DBG_AGC_MCU_RAM_ADDR, 0xB0+i); + lgw_reg_r(LGW_DBG_AGC_MCU_RAM_DATA, &read_val); + cal_offset_b_i[i] = (int8_t)read_val; + lgw_reg_w(LGW_DBG_AGC_MCU_RAM_ADDR, 0xB8+i); + lgw_reg_r(LGW_DBG_AGC_MCU_RAM_DATA, &read_val); + cal_offset_b_q[i] = (int8_t)read_val; + } + + /* load adjusted parameters */ + lgw_constant_adjust(); + + /* Freq-to-time-drift calculation */ + x = 4096000000 / (rf_rx_freq[0] >> 1); /* dividend: (4*2048*1000000) >> 1, rescaled to avoid 32b overflow */ + x = ( x > 63 ) ? 63 : x; /* saturation */ + lgw_reg_w(LGW_FREQ_TO_TIME_DRIFT, x); /* default 9 */ + + x = 4096000000 / (rf_rx_freq[0] >> 3); /* dividend: (16*2048*1000000) >> 3, rescaled to avoid 32b overflow */ + x = ( x > 63 ) ? 63 : x; /* saturation */ + lgw_reg_w(LGW_MBWSSF_FREQ_TO_TIME_DRIFT, x); /* default 36 */ + + /* configure LoRa 'multi' demodulators aka. LoRa 'sensor' channels (IF0-3) */ + radio_select = 0; /* IF mapping to radio A/B (per bit, 0=A, 1=B) */ + for(i=0; i> 32))); + if (if_enable[9] == true) { + lgw_reg_w(LGW_FSK_RADIO_SELECT, if_rf_chain[9]); + lgw_reg_w(LGW_FSK_BR_RATIO,LGW_XTAL_FREQU/fsk_rx_dr); /* setting the dividing ratio for datarate */ + lgw_reg_w(LGW_FSK_CH_BW_EXPO,fsk_rx_bw); + lgw_reg_w(LGW_FSK_MODEM_ENABLE,1); /* default 0 */ + } else { + lgw_reg_w(LGW_FSK_MODEM_ENABLE,0); + } + + /* Load firmware */ + load_firmware(MCU_ARB, arb_firmware, MCU_ARB_FW_BYTE); + load_firmware(MCU_AGC, agc_firmware, MCU_AGC_FW_BYTE); + + /* gives the AGC MCU control over radio, RF front-end and filter gain */ + lgw_reg_w(LGW_FORCE_HOST_RADIO_CTRL,0); + lgw_reg_w(LGW_FORCE_HOST_FE_CTRL,0); + lgw_reg_w(LGW_FORCE_DEC_FILTER_GAIN,0); + + /* Get MCUs out of reset */ + lgw_reg_w(LGW_RADIO_SELECT, 0); /* MUST not be = to 1 or 2 at firmware init */ + lgw_reg_w(LGW_MCU_RST_0, 0); + lgw_reg_w(LGW_MCU_RST_1, 0); + + /* Check firmware version */ + lgw_reg_w(LGW_DBG_AGC_MCU_RAM_ADDR, FW_VERSION_ADDR); + lgw_reg_r(LGW_DBG_AGC_MCU_RAM_DATA, &read_val); + fw_version = (uint8_t)read_val; + if (fw_version != FW_VERSION_AGC) { + DEBUG_PRINTF("ERROR: Version of AGC firmware not expected, actual:%d expected:%d\n", fw_version, FW_VERSION_AGC); + return LGW_HAL_ERROR; + } + lgw_reg_w(LGW_DBG_ARB_MCU_RAM_ADDR, FW_VERSION_ADDR); + lgw_reg_r(LGW_DBG_ARB_MCU_RAM_DATA, &read_val); + fw_version = (uint8_t)read_val; + if (fw_version != FW_VERSION_ARB) { + DEBUG_PRINTF("ERROR: Version of arbiter firmware not expected, actual:%d expected:%d\n", fw_version, FW_VERSION_ARB); + return LGW_HAL_ERROR; + } + + DEBUG_MSG("Info: Initialising AGC firmware...\n"); + wait_ms(1); + + lgw_reg_r(LGW_MCU_AGC_STATUS, &read_val); + if (read_val != 0x10) { + DEBUG_PRINTF("ERROR: AGC FIRMWARE INITIALIZATION FAILURE, STATUS 0x%02X\n", (uint8_t)read_val); + return LGW_HAL_ERROR; + } + + /* Update Tx gain LUT and start AGC */ + for (i = 0; i < txgain_lut.size; ++i) { + lgw_reg_w(LGW_RADIO_SELECT, AGC_CMD_WAIT); /* start a transaction */ + wait_ms(1); + load_val = txgain_lut.lut[i].mix_gain + (16 * txgain_lut.lut[i].dac_gain) + (64 * txgain_lut.lut[i].pa_gain); + lgw_reg_w(LGW_RADIO_SELECT, load_val); + wait_ms(1); + lgw_reg_r(LGW_MCU_AGC_STATUS, &read_val); + if (read_val != (0x30 + i)) { + DEBUG_PRINTF("ERROR: AGC FIRMWARE INITIALIZATION FAILURE, STATUS 0x%02X\n", (uint8_t)read_val); + return LGW_HAL_ERROR; + } + } + /* As the AGC fw is waiting for 16 entries, we need to abort the transaction if we get less entries */ + if (txgain_lut.size < TX_GAIN_LUT_SIZE_MAX) { + lgw_reg_w(LGW_RADIO_SELECT, AGC_CMD_WAIT); + wait_ms(1); + load_val = AGC_CMD_ABORT; + lgw_reg_w(LGW_RADIO_SELECT, load_val); + wait_ms(1); + lgw_reg_r(LGW_MCU_AGC_STATUS, &read_val); + if (read_val != 0x30) { + DEBUG_PRINTF("ERROR: AGC FIRMWARE INITIALIZATION FAILURE, STATUS 0x%02X\n", (uint8_t)read_val); + return LGW_HAL_ERROR; + } + } + + /* Load Tx freq MSBs (always 3 if f > 768 for SX1257 or f > 384 for SX1255 */ + lgw_reg_w(LGW_RADIO_SELECT, AGC_CMD_WAIT); + wait_ms(1); + lgw_reg_w(LGW_RADIO_SELECT, 3); + wait_ms(1); + lgw_reg_r(LGW_MCU_AGC_STATUS, &read_val); + if (read_val != 0x33) { + DEBUG_PRINTF("ERROR: AGC FIRMWARE INITIALIZATION FAILURE, STATUS 0x%02X\n", (uint8_t)read_val); + return LGW_HAL_ERROR; + } + + /* Load chan_select firmware option */ + lgw_reg_w(LGW_RADIO_SELECT, AGC_CMD_WAIT); + wait_ms(1); + lgw_reg_w(LGW_RADIO_SELECT, 0); + wait_ms(1); + lgw_reg_r(LGW_MCU_AGC_STATUS, &read_val); + if (read_val != 0x30) { + DEBUG_PRINTF("ERROR: AGC FIRMWARE INITIALIZATION FAILURE, STATUS 0x%02X\n", (uint8_t)read_val); + return LGW_HAL_ERROR; + } + + /* End AGC firmware init and check status */ + lgw_reg_w(LGW_RADIO_SELECT, AGC_CMD_WAIT); + wait_ms(1); + lgw_reg_w(LGW_RADIO_SELECT, radio_select); /* Load intended value of RADIO_SELECT */ + wait_ms(1); + DEBUG_MSG("Info: putting back original RADIO_SELECT value\n"); + lgw_reg_r(LGW_MCU_AGC_STATUS, &read_val); + if (read_val != 0x40) { + DEBUG_PRINTF("ERROR: AGC FIRMWARE INITIALIZATION FAILURE, STATUS 0x%02X\n", (uint8_t)read_val); + return LGW_HAL_ERROR; + } + + /* enable GPS event capture */ + lgw_reg_w(LGW_GPS_EN, 1); + + /* */ + if (lbt_enable) { + printf("INFO: Configuring LBT, this may take few seconds, please wait...\n"); + wait_ms(8400); + } + + lgw_is_started = true; + return LGW_HAL_SUCCESS; } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ int lgw_stop(void) { - lgw_soft_reset(); - lgw_disconnect(); + lgw_soft_reset(); + lgw_disconnect(); - lgw_is_started = false; - return LGW_HAL_SUCCESS; + lgw_is_started = false; + return LGW_HAL_SUCCESS; } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ int lgw_receive(uint8_t max_pkt, struct lgw_pkt_rx_s *pkt_data) { - int nb_pkt_fetch; /* loop variable and return value */ - struct lgw_pkt_rx_s *p; /* pointer to the current structure in the struct array */ - uint8_t buff[255+RX_METADATA_NB]; /* buffer to store the result of SPI read bursts */ - unsigned sz; /* size of the payload, uses to address metadata */ - int ifmod; /* type of if_chain/modem a packet was received by */ - int stat_fifo; /* the packet status as indicated in the FIFO */ - uint32_t raw_timestamp; /* timestamp when internal 'RX finished' was triggered */ - uint32_t delay_x, delay_y, delay_z; /* temporary variable for timestamp offset calculation */ - uint32_t timestamp_correction; /* correction to account for processing delay */ - uint32_t sf, cr, bw_pow, crc_en, ppm; /* used to calculate timestamp correction */ - - /* check if the concentrator is running */ - if (lgw_is_started == false) { - DEBUG_MSG("ERROR: CONCENTRATOR IS NOT RUNNING, START IT BEFORE RECEIVING\n"); - return LGW_HAL_ERROR; - } - - /* check input variables */ - if (max_pkt <= 0) { - DEBUG_PRINTF("ERROR: %d = INVALID MAX NUMBER OF PACKETS TO FETCH\n", max_pkt); - return LGW_HAL_ERROR; - } - CHECK_NULL(pkt_data); - - /* Initialize buffer */ - memset (buff, 0, sizeof buff); - - /* iterate max_pkt times at most */ - for (nb_pkt_fetch = 0; nb_pkt_fetch < max_pkt; ++nb_pkt_fetch) { - - /* point to the proper struct in the struct array */ - p = &pkt_data[nb_pkt_fetch]; - - /* fetch all the RX FIFO data */ - lgw_reg_rb(LGW_RX_PACKET_DATA_FIFO_NUM_STORED, buff, 5); - - /* how many packets are in the RX buffer ? Break if zero */ - if (buff[0] == 0) { - break; /* no more packets to fetch, exit out of FOR loop */ - } - - DEBUG_PRINTF("FIFO content: %x %x %x %x %x\n",buff[0],buff[1],buff[2],buff[3],buff[4]); - - p->size = buff[4]; - sz = p->size; - stat_fifo = buff[3]; /* will be used later, need to save it before overwriting buff */ - - /* get payload + metadata */ - lgw_reg_rb(LGW_RX_DATA_BUF_DATA, buff, sz+RX_METADATA_NB); - - /* copy payload to result struct */ - memcpy((void *)p->payload, (void *)buff, sz); - - /* process metadata */ - p->if_chain = buff[sz+0]; - ifmod = ifmod_config[p->if_chain]; - DEBUG_PRINTF("[%d %d]\n", p->if_chain, ifmod); - - p->rf_chain = (uint8_t)if_rf_chain[p->if_chain]; - p->freq_hz = (uint32_t)((int32_t)rf_rx_freq[p->rf_chain] + if_freq[p->if_chain]); - p->rssi = (float)buff[sz+5] + rf_rssi_offset[p->rf_chain]; - - if ((ifmod == IF_LORA_MULTI) || (ifmod == IF_LORA_STD)) { - DEBUG_MSG("Note: LoRa packet\n"); - switch(stat_fifo & 0x07) { - case 5: - p->status = STAT_CRC_OK; - crc_en = 1; - break; - case 7: - p->status = STAT_CRC_BAD; - crc_en = 1; - break; - case 1: - p->status = STAT_NO_CRC; - crc_en = 0; - break; - default: - p->status = STAT_UNDEFINED; - crc_en = 0; - } - p->modulation = MOD_LORA; - p->snr = ((float)((int8_t)buff[sz+2]))/4; - p->snr_min = ((float)((int8_t)buff[sz+3]))/4; - p->snr_max = ((float)((int8_t)buff[sz+4]))/4; - if (ifmod == IF_LORA_MULTI) { - p->bandwidth = BW_125KHZ; /* fixed in hardware */ - } else { - p->bandwidth = lora_rx_bw; /* get the parameter from the config variable */ - } - sf = (buff[sz+1] >> 4) & 0x0F; - switch (sf) { - case 7: p->datarate = DR_LORA_SF7; break; - case 8: p->datarate = DR_LORA_SF8; break; - case 9: p->datarate = DR_LORA_SF9; break; - case 10: p->datarate = DR_LORA_SF10; break; - case 11: p->datarate = DR_LORA_SF11; break; - case 12: p->datarate = DR_LORA_SF12; break; - default: p->datarate = DR_UNDEFINED; - } - cr = (buff[sz+1] >> 1) & 0x07; - switch (cr) { - case 1: p->coderate = CR_LORA_4_5; break; - case 2: p->coderate = CR_LORA_4_6; break; - case 3: p->coderate = CR_LORA_4_7; break; - case 4: p->coderate = CR_LORA_4_8; break; - default: p->coderate = CR_UNDEFINED; - } - - /* determine if 'PPM mode' is on, needed for timestamp correction */ - if (SET_PPM_ON(p->bandwidth,p->datarate)) { - ppm = 1; - } else { - ppm = 0; - } - - /* timestamp correction code, base delay */ - if (ifmod == IF_LORA_STD) { /* if packet was received on the stand-alone LoRa modem */ - switch (lora_rx_bw) { - case BW_125KHZ: - delay_x = 64; - bw_pow = 1; - break; - case BW_250KHZ: - delay_x = 32; - bw_pow = 2; - break; - case BW_500KHZ: - delay_x = 16; - bw_pow = 4; - break; - default: - DEBUG_PRINTF("ERROR: UNEXPECTED VALUE %d IN SWITCH STATEMENT\n", p->bandwidth); - delay_x = 0; - bw_pow = 0; - } - } else { /* packet was received on one of the sensor channels = 125kHz */ - delay_x = 114; - bw_pow = 1; - } - - /* timestamp correction code, variable delay */ - if ((sf >= 6) && (sf <= 12) && (bw_pow > 0)) { - if ((2*(sz + 2*crc_en) - (sf-7)) <= 0) { /* payload fits entirely in first 8 symbols */ - delay_y = ( ((1<<(sf-1)) * (sf+1)) + (3 * (1<<(sf-4))) ) / bw_pow; - delay_z = 32 * (2*(sz+2*crc_en) + 5) / bw_pow; - } else { - delay_y = ( ((1<<(sf-1)) * (sf+1)) + ((4 - ppm) * (1<<(sf-4))) ) / bw_pow; - delay_z = (16 + 4*cr) * (((2*(sz+2*crc_en)-sf+6) % (sf - 2*ppm)) + 1) / bw_pow; - } - timestamp_correction = delay_x + delay_y + delay_z; - } else { - timestamp_correction = 0; - DEBUG_MSG("WARNING: invalid packet, no timestamp correction\n"); - } - - /* RSSI correction */ - if (ifmod == IF_LORA_MULTI) { - p->rssi -= RSSI_MULTI_BIAS; - } - - } else if (ifmod == IF_FSK_STD) { - DEBUG_MSG("Note: FSK packet\n"); - switch(stat_fifo & 0x07) { - case 5: p->status = STAT_CRC_OK; break; - case 7: p->status = STAT_CRC_BAD; break; - case 1: p->status = STAT_NO_CRC; break; - default: p->status = STAT_UNDEFINED; - } - p->modulation = MOD_FSK; - p->snr = -128.0; - p->snr_min = -128.0; - p->snr_max = -128.0; - p->bandwidth = fsk_rx_bw; - p->datarate = fsk_rx_dr; - p->coderate = CR_UNDEFINED; - timestamp_correction = ((uint32_t)680000 / fsk_rx_dr) - 20; - - /* RSSI correction */ - p->rssi -= RSSI_FSK_BIAS; - p->rssi = ((p->rssi - RSSI_FSK_REF) * RSSI_FSK_SLOPE) + RSSI_FSK_REF; - } else { - DEBUG_MSG("ERROR: UNEXPECTED PACKET ORIGIN\n"); - p->status = STAT_UNDEFINED; - p->modulation = MOD_UNDEFINED; - p->rssi = -128.0; - p->snr = -128.0; - p->snr_min = -128.0; - p->snr_max = -128.0; - p->bandwidth = BW_UNDEFINED; - p->datarate = DR_UNDEFINED; - p->coderate = CR_UNDEFINED; - timestamp_correction = 0; - } - - raw_timestamp = (uint32_t)buff[sz+6] + ((uint32_t)buff[sz+7] << 8) + ((uint32_t)buff[sz+8] << 16) + ((uint32_t)buff[sz+9] << 24); - p->count_us = raw_timestamp - timestamp_correction; - p->crc = (uint16_t)buff[sz+10] + ((uint16_t)buff[sz+11] << 8); - - /* advance packet FIFO */ - lgw_reg_w(LGW_RX_PACKET_DATA_FIFO_NUM_STORED, 0); - } - - return nb_pkt_fetch; + int nb_pkt_fetch; /* loop variable and return value */ + struct lgw_pkt_rx_s *p; /* pointer to the current structure in the struct array */ + uint8_t buff[255+RX_METADATA_NB]; /* buffer to store the result of SPI read bursts */ + unsigned sz; /* size of the payload, uses to address metadata */ + int ifmod; /* type of if_chain/modem a packet was received by */ + int stat_fifo; /* the packet status as indicated in the FIFO */ + uint32_t raw_timestamp; /* timestamp when internal 'RX finished' was triggered */ + uint32_t delay_x, delay_y, delay_z; /* temporary variable for timestamp offset calculation */ + uint32_t timestamp_correction; /* correction to account for processing delay */ + uint32_t sf, cr, bw_pow, crc_en, ppm; /* used to calculate timestamp correction */ + + /* check if the concentrator is running */ + if (lgw_is_started == false) { + DEBUG_MSG("ERROR: CONCENTRATOR IS NOT RUNNING, START IT BEFORE RECEIVING\n"); + return LGW_HAL_ERROR; + } + + /* check input variables */ + if (max_pkt <= 0) { + DEBUG_PRINTF("ERROR: %d = INVALID MAX NUMBER OF PACKETS TO FETCH\n", max_pkt); + return LGW_HAL_ERROR; + } + CHECK_NULL(pkt_data); + + /* Initialize buffer */ + memset (buff, 0, sizeof buff); + + /* iterate max_pkt times at most */ + for (nb_pkt_fetch = 0; nb_pkt_fetch < max_pkt; ++nb_pkt_fetch) { + + /* point to the proper struct in the struct array */ + p = &pkt_data[nb_pkt_fetch]; + + /* fetch all the RX FIFO data */ + lgw_reg_rb(LGW_RX_PACKET_DATA_FIFO_NUM_STORED, buff, 5); + + /* how many packets are in the RX buffer ? Break if zero */ + if (buff[0] == 0) { + break; /* no more packets to fetch, exit out of FOR loop */ + } + + DEBUG_PRINTF("FIFO content: %x %x %x %x %x\n",buff[0],buff[1],buff[2],buff[3],buff[4]); + + p->size = buff[4]; + sz = p->size; + stat_fifo = buff[3]; /* will be used later, need to save it before overwriting buff */ + + /* get payload + metadata */ + lgw_reg_rb(LGW_RX_DATA_BUF_DATA, buff, sz+RX_METADATA_NB); + + /* copy payload to result struct */ + memcpy((void *)p->payload, (void *)buff, sz); + + /* process metadata */ + p->if_chain = buff[sz+0]; + ifmod = ifmod_config[p->if_chain]; + DEBUG_PRINTF("[%d %d]\n", p->if_chain, ifmod); + + p->rf_chain = (uint8_t)if_rf_chain[p->if_chain]; + p->freq_hz = (uint32_t)((int32_t)rf_rx_freq[p->rf_chain] + if_freq[p->if_chain]); + p->rssi = (float)buff[sz+5] + rf_rssi_offset[p->rf_chain]; + + if ((ifmod == IF_LORA_MULTI) || (ifmod == IF_LORA_STD)) { + DEBUG_MSG("Note: LoRa packet\n"); + switch(stat_fifo & 0x07) { + case 5: + p->status = STAT_CRC_OK; + crc_en = 1; + break; + case 7: + p->status = STAT_CRC_BAD; + crc_en = 1; + break; + case 1: + p->status = STAT_NO_CRC; + crc_en = 0; + break; + default: + p->status = STAT_UNDEFINED; + crc_en = 0; + } + p->modulation = MOD_LORA; + p->snr = ((float)((int8_t)buff[sz+2]))/4; + p->snr_min = ((float)((int8_t)buff[sz+3]))/4; + p->snr_max = ((float)((int8_t)buff[sz+4]))/4; + if (ifmod == IF_LORA_MULTI) { + p->bandwidth = BW_125KHZ; /* fixed in hardware */ + } else { + p->bandwidth = lora_rx_bw; /* get the parameter from the config variable */ + } + sf = (buff[sz+1] >> 4) & 0x0F; + switch (sf) { + case 7: p->datarate = DR_LORA_SF7; break; + case 8: p->datarate = DR_LORA_SF8; break; + case 9: p->datarate = DR_LORA_SF9; break; + case 10: p->datarate = DR_LORA_SF10; break; + case 11: p->datarate = DR_LORA_SF11; break; + case 12: p->datarate = DR_LORA_SF12; break; + default: p->datarate = DR_UNDEFINED; + } + cr = (buff[sz+1] >> 1) & 0x07; + switch (cr) { + case 1: p->coderate = CR_LORA_4_5; break; + case 2: p->coderate = CR_LORA_4_6; break; + case 3: p->coderate = CR_LORA_4_7; break; + case 4: p->coderate = CR_LORA_4_8; break; + default: p->coderate = CR_UNDEFINED; + } + + /* determine if 'PPM mode' is on, needed for timestamp correction */ + if (SET_PPM_ON(p->bandwidth,p->datarate)) { + ppm = 1; + } else { + ppm = 0; + } + + /* timestamp correction code, base delay */ + if (ifmod == IF_LORA_STD) { /* if packet was received on the stand-alone LoRa modem */ + switch (lora_rx_bw) { + case BW_125KHZ: + delay_x = 64; + bw_pow = 1; + break; + case BW_250KHZ: + delay_x = 32; + bw_pow = 2; + break; + case BW_500KHZ: + delay_x = 16; + bw_pow = 4; + break; + default: + DEBUG_PRINTF("ERROR: UNEXPECTED VALUE %d IN SWITCH STATEMENT\n", p->bandwidth); + delay_x = 0; + bw_pow = 0; + } + } else { /* packet was received on one of the sensor channels = 125kHz */ + delay_x = 114; + bw_pow = 1; + } + + /* timestamp correction code, variable delay */ + if ((sf >= 6) && (sf <= 12) && (bw_pow > 0)) { + if ((2*(sz + 2*crc_en) - (sf-7)) <= 0) { /* payload fits entirely in first 8 symbols */ + delay_y = ( ((1<<(sf-1)) * (sf+1)) + (3 * (1<<(sf-4))) ) / bw_pow; + delay_z = 32 * (2*(sz+2*crc_en) + 5) / bw_pow; + } else { + delay_y = ( ((1<<(sf-1)) * (sf+1)) + ((4 - ppm) * (1<<(sf-4))) ) / bw_pow; + delay_z = (16 + 4*cr) * (((2*(sz+2*crc_en)-sf+6) % (sf - 2*ppm)) + 1) / bw_pow; + } + timestamp_correction = delay_x + delay_y + delay_z; + } else { + timestamp_correction = 0; + DEBUG_MSG("WARNING: invalid packet, no timestamp correction\n"); + } + + /* RSSI correction */ + if (ifmod == IF_LORA_MULTI) { + p->rssi -= RSSI_MULTI_BIAS; + } + + } else if (ifmod == IF_FSK_STD) { + DEBUG_MSG("Note: FSK packet\n"); + switch(stat_fifo & 0x07) { + case 5: p->status = STAT_CRC_OK; break; + case 7: p->status = STAT_CRC_BAD; break; + case 1: p->status = STAT_NO_CRC; break; + default: p->status = STAT_UNDEFINED; + } + p->modulation = MOD_FSK; + p->snr = -128.0; + p->snr_min = -128.0; + p->snr_max = -128.0; + p->bandwidth = fsk_rx_bw; + p->datarate = fsk_rx_dr; + p->coderate = CR_UNDEFINED; + timestamp_correction = ((uint32_t)680000 / fsk_rx_dr) - 20; + + /* RSSI correction */ + p->rssi = RSSI_FSK_POLY_0 + RSSI_FSK_POLY_1 * p->rssi + RSSI_FSK_POLY_2 * pow(p->rssi, 2); + } else { + DEBUG_MSG("ERROR: UNEXPECTED PACKET ORIGIN\n"); + p->status = STAT_UNDEFINED; + p->modulation = MOD_UNDEFINED; + p->rssi = -128.0; + p->snr = -128.0; + p->snr_min = -128.0; + p->snr_max = -128.0; + p->bandwidth = BW_UNDEFINED; + p->datarate = DR_UNDEFINED; + p->coderate = CR_UNDEFINED; + timestamp_correction = 0; + } + + raw_timestamp = (uint32_t)buff[sz+6] + ((uint32_t)buff[sz+7] << 8) + ((uint32_t)buff[sz+8] << 16) + ((uint32_t)buff[sz+9] << 24); + p->count_us = raw_timestamp - timestamp_correction; + p->crc = (uint16_t)buff[sz+10] + ((uint16_t)buff[sz+11] << 8); + + /* advance packet FIFO */ + lgw_reg_w(LGW_RX_PACKET_DATA_FIFO_NUM_STORED, 0); + } + + return nb_pkt_fetch; } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ int lgw_send(struct lgw_pkt_tx_s pkt_data) { - int i; - uint8_t buff[256+TX_METADATA_NB]; /* buffer to prepare the packet to send + metadata before SPI write burst */ - uint32_t part_int = 0; /* integer part for PLL register value calculation */ - uint32_t part_frac = 0; /* fractional part for PLL register value calculation */ - uint16_t fsk_dr_div; /* divider to configure for target datarate */ - int transfer_size = 0; /* data to transfer from host to TX databuffer */ - int payload_offset = 0; /* start of the payload content in the databuffer */ - uint8_t pow_index = 0; /* 4-bit value to set the firmware TX power */ - uint8_t target_mix_gain = 0; /* used to select the proper I/Q offset correction */ - uint32_t count_trig; /* timestamp value in trigger mode corrected for TX start delay */ - - /* check if the concentrator is running */ - if (lgw_is_started == false) { - DEBUG_MSG("ERROR: CONCENTRATOR IS NOT RUNNING, START IT BEFORE SENDING\n"); - return LGW_HAL_ERROR; - } - - /* check input range (segfault prevention) */ - if (pkt_data.rf_chain >= LGW_RF_CHAIN_NB) { - DEBUG_MSG("ERROR: INVALID RF_CHAIN TO SEND PACKETS\n"); - return LGW_HAL_ERROR; - } - - /* check input variables */ - if (rf_tx_enable[pkt_data.rf_chain] == false) { - DEBUG_MSG("ERROR: SELECTED RF_CHAIN IS DISABLED FOR TX ON SELECTED BOARD\n"); - return LGW_HAL_ERROR; - } - if (rf_enable[pkt_data.rf_chain] == false) { - DEBUG_MSG("ERROR: SELECTED RF_CHAIN IS DISABLED\n"); - return LGW_HAL_ERROR; - } - if (!IS_TX_MODE(pkt_data.tx_mode)) { - DEBUG_MSG("ERROR: TX_MODE NOT SUPPORTED\n"); - return LGW_HAL_ERROR; - } - if (pkt_data.modulation == MOD_LORA) { - if (!IS_LORA_BW(pkt_data.bandwidth)) { - DEBUG_MSG("ERROR: BANDWIDTH NOT SUPPORTED BY LORA TX\n"); - return LGW_HAL_ERROR; - } - if (!IS_LORA_STD_DR(pkt_data.datarate)) { - DEBUG_MSG("ERROR: DATARATE NOT SUPPORTED BY LORA TX\n"); - return LGW_HAL_ERROR; - } - if (!IS_LORA_CR(pkt_data.coderate)) { - DEBUG_MSG("ERROR: CODERATE NOT SUPPORTED BY LORA TX\n"); - return LGW_HAL_ERROR; - } - if (pkt_data.size > 255) { - DEBUG_MSG("ERROR: PAYLOAD LENGTH TOO BIG FOR LORA TX\n"); - return LGW_HAL_ERROR; - } - } else if (pkt_data.modulation == MOD_FSK) { - if((pkt_data.f_dev < 1) || (pkt_data.f_dev > 200)) { - DEBUG_MSG("ERROR: TX FREQUENCY DEVIATION OUT OF ACCEPTABLE RANGE\n"); - return LGW_HAL_ERROR; - } - if(!IS_FSK_DR(pkt_data.datarate)) { - DEBUG_MSG("ERROR: DATARATE NOT SUPPORTED BY FSK IF CHAIN\n"); - return LGW_HAL_ERROR; - } - if (pkt_data.size > 255) { - DEBUG_MSG("ERROR: PAYLOAD LENGTH TOO BIG FOR FSK TX\n"); - return LGW_HAL_ERROR; - } - } else { - DEBUG_MSG("ERROR: INVALID TX MODULATION\n"); - return LGW_HAL_ERROR; - } - - /* interpretation of TX power */ - for (pow_index = txgain_lut.size-1; pow_index > 0; pow_index--) { - if (txgain_lut.lut[pow_index].rf_power <= pkt_data.rf_power) { - break; - } - } - - /* loading TX imbalance correction */ - target_mix_gain = txgain_lut.lut[pow_index].mix_gain; - if (pkt_data.rf_chain == 0) { /* use radio A calibration table */ - lgw_reg_w(LGW_TX_OFFSET_I, cal_offset_a_i[target_mix_gain - 8]); - lgw_reg_w(LGW_TX_OFFSET_Q, cal_offset_a_q[target_mix_gain - 8]); - } else { /* use radio B calibration table */ - lgw_reg_w(LGW_TX_OFFSET_I, cal_offset_b_i[target_mix_gain - 8]); - lgw_reg_w(LGW_TX_OFFSET_Q, cal_offset_b_q[target_mix_gain - 8]); - } - - /* Set digital gain from LUT */ - lgw_reg_w(LGW_TX_GAIN, txgain_lut.lut[pow_index].dig_gain); - - /* fixed metadata, useful payload and misc metadata compositing */ - transfer_size = TX_METADATA_NB + pkt_data.size; /* */ - payload_offset = TX_METADATA_NB; /* start the payload just after the metadata */ - - /* metadata 0 to 2, TX PLL frequency */ - switch (rf_radio_type[0]) { /* we assume that there is only one radio type on the board */ - case LGW_RADIO_TYPE_SX1255: - part_int = pkt_data.freq_hz / (SX125x_32MHz_FRAC << 7); /* integer part, gives the MSB */ - part_frac = ((pkt_data.freq_hz % (SX125x_32MHz_FRAC << 7)) << 9) / SX125x_32MHz_FRAC; /* fractional part, gives middle part and LSB */ - break; - case LGW_RADIO_TYPE_SX1257: - part_int = pkt_data.freq_hz / (SX125x_32MHz_FRAC << 8); /* integer part, gives the MSB */ - part_frac = ((pkt_data.freq_hz % (SX125x_32MHz_FRAC << 8)) << 8) / SX125x_32MHz_FRAC; /* fractional part, gives middle part and LSB */ - break; - default: - DEBUG_PRINTF("ERROR: UNEXPECTED VALUE %d FOR RADIO TYPE\n", rf_radio_type[0]); - break; - } - - buff[0] = 0xFF & part_int; /* Most Significant Byte */ - buff[1] = 0xFF & (part_frac >> 8); /* middle byte */ - buff[2] = 0xFF & part_frac; /* Least Significant Byte */ - - /* metadata 3 to 6, timestamp trigger value */ - /* TX state machine must be triggered at T0 - TX_START_DELAY for packet to start being emitted at T0 */ - if (pkt_data.tx_mode == TIMESTAMPED) - { - count_trig = pkt_data.count_us - TX_START_DELAY; - buff[3] = 0xFF & (count_trig >> 24); - buff[4] = 0xFF & (count_trig >> 16); - buff[5] = 0xFF & (count_trig >> 8); - buff[6] = 0xFF & count_trig; - } - - /* parameters depending on modulation */ - if (pkt_data.modulation == MOD_LORA) { - /* metadata 7, modulation type, radio chain selection and TX power */ - buff[7] = (0x20 & (pkt_data.rf_chain << 5)) | (0x0F & pow_index); /* bit 4 is 0 -> LoRa modulation */ - - buff[8] = 0; /* metadata 8, not used */ - - /* metadata 9, CRC, LoRa CR & SF */ - switch (pkt_data.datarate) { - case DR_LORA_SF7: buff[9] = 7; break; - case DR_LORA_SF8: buff[9] = 8; break; - case DR_LORA_SF9: buff[9] = 9; break; - case DR_LORA_SF10: buff[9] = 10; break; - case DR_LORA_SF11: buff[9] = 11; break; - case DR_LORA_SF12: buff[9] = 12; break; - default: DEBUG_PRINTF("ERROR: UNEXPECTED VALUE %d IN SWITCH STATEMENT\n", pkt_data.datarate); - } - switch (pkt_data.coderate) { - case CR_LORA_4_5: buff[9] |= 1 << 4; break; - case CR_LORA_4_6: buff[9] |= 2 << 4; break; - case CR_LORA_4_7: buff[9] |= 3 << 4; break; - case CR_LORA_4_8: buff[9] |= 4 << 4; break; - default: DEBUG_PRINTF("ERROR: UNEXPECTED VALUE %d IN SWITCH STATEMENT\n", pkt_data.coderate); - } - if (pkt_data.no_crc == false) { - buff[9] |= 0x80; /* set 'CRC enable' bit */ - } else { - DEBUG_MSG("Info: packet will be sent without CRC\n"); - } - - /* metadata 10, payload size */ - buff[10] = pkt_data.size; - - /* metadata 11, implicit header, modulation bandwidth, PPM offset & polarity */ - switch (pkt_data.bandwidth) { - case BW_125KHZ: buff[11] = 0; break; - case BW_250KHZ: buff[11] = 1; break; - case BW_500KHZ: buff[11] = 2; break; - default: DEBUG_PRINTF("ERROR: UNEXPECTED VALUE %d IN SWITCH STATEMENT\n", pkt_data.bandwidth); - } - if (pkt_data.no_header == true) { - buff[11] |= 0x04; /* set 'implicit header' bit */ - } - if (SET_PPM_ON(pkt_data.bandwidth,pkt_data.datarate)) { - buff[11] |= 0x08; /* set 'PPM offset' bit at 1 */ - } - if (pkt_data.invert_pol == true) { - buff[11] |= 0x10; /* set 'TX polarity' bit at 1 */ - } - - /* metadata 12 & 13, LoRa preamble size */ - if (pkt_data.preamble == 0) { /* if not explicit, use recommended LoRa preamble size */ - pkt_data.preamble = STD_LORA_PREAMBLE; - } else if (pkt_data.preamble < MIN_LORA_PREAMBLE) { /* enforce minimum preamble size */ - pkt_data.preamble = MIN_LORA_PREAMBLE; - DEBUG_MSG("Note: preamble length adjusted to respect minimum LoRa preamble size\n"); - } - buff[12] = 0xFF & (pkt_data.preamble >> 8); - buff[13] = 0xFF & pkt_data.preamble; - - /* metadata 14 & 15, not used */ - buff[14] = 0; - buff[15] = 0; - - /* MSB of RF frequency is now used in AGC firmware to implement large/narrow filtering in SX1257/55 */ - buff[0] &= 0x3F; /* Unset 2 MSBs of frequency code */ - if (pkt_data.bandwidth == BW_500KHZ) { - buff[0] |= 0x80; /* Set MSB bit to enlarge analog filter for 500kHz BW */ - } + int i, x; + uint8_t buff[256+TX_METADATA_NB]; /* buffer to prepare the packet to send + metadata before SPI write burst */ + uint32_t part_int = 0; /* integer part for PLL register value calculation */ + uint32_t part_frac = 0; /* fractional part for PLL register value calculation */ + uint16_t fsk_dr_div; /* divider to configure for target datarate */ + int transfer_size = 0; /* data to transfer from host to TX databuffer */ + int payload_offset = 0; /* start of the payload content in the databuffer */ + uint8_t pow_index = 0; /* 4-bit value to set the firmware TX power */ + uint8_t target_mix_gain = 0; /* used to select the proper I/Q offset correction */ + uint32_t count_trig = 0; /* timestamp value in trigger mode corrected for TX start delay */ + bool tx_allowed = false; + + /* check if the concentrator is running */ + if (lgw_is_started == false) { + DEBUG_MSG("ERROR: CONCENTRATOR IS NOT RUNNING, START IT BEFORE SENDING\n"); + return LGW_HAL_ERROR; + } + + /* check input range (segfault prevention) */ + if (pkt_data.rf_chain >= LGW_RF_CHAIN_NB) { + DEBUG_MSG("ERROR: INVALID RF_CHAIN TO SEND PACKETS\n"); + return LGW_HAL_ERROR; + } + + /* check input variables */ + if (rf_tx_enable[pkt_data.rf_chain] == false) { + DEBUG_MSG("ERROR: SELECTED RF_CHAIN IS DISABLED FOR TX ON SELECTED BOARD\n"); + return LGW_HAL_ERROR; + } + if (rf_enable[pkt_data.rf_chain] == false) { + DEBUG_MSG("ERROR: SELECTED RF_CHAIN IS DISABLED\n"); + return LGW_HAL_ERROR; + } + if (!IS_TX_MODE(pkt_data.tx_mode)) { + DEBUG_MSG("ERROR: TX_MODE NOT SUPPORTED\n"); + return LGW_HAL_ERROR; + } + if (pkt_data.modulation == MOD_LORA) { + if (!IS_LORA_BW(pkt_data.bandwidth)) { + DEBUG_MSG("ERROR: BANDWIDTH NOT SUPPORTED BY LORA TX\n"); + return LGW_HAL_ERROR; + } + if (!IS_LORA_STD_DR(pkt_data.datarate)) { + DEBUG_MSG("ERROR: DATARATE NOT SUPPORTED BY LORA TX\n"); + return LGW_HAL_ERROR; + } + if (!IS_LORA_CR(pkt_data.coderate)) { + DEBUG_MSG("ERROR: CODERATE NOT SUPPORTED BY LORA TX\n"); + return LGW_HAL_ERROR; + } + if (pkt_data.size > 255) { + DEBUG_MSG("ERROR: PAYLOAD LENGTH TOO BIG FOR LORA TX\n"); + return LGW_HAL_ERROR; + } + } else if (pkt_data.modulation == MOD_FSK) { + if((pkt_data.f_dev < 1) || (pkt_data.f_dev > 200)) { + DEBUG_MSG("ERROR: TX FREQUENCY DEVIATION OUT OF ACCEPTABLE RANGE\n"); + return LGW_HAL_ERROR; + } + if(!IS_FSK_DR(pkt_data.datarate)) { + DEBUG_MSG("ERROR: DATARATE NOT SUPPORTED BY FSK IF CHAIN\n"); + return LGW_HAL_ERROR; + } + if (pkt_data.size > 255) { + DEBUG_MSG("ERROR: PAYLOAD LENGTH TOO BIG FOR FSK TX\n"); + return LGW_HAL_ERROR; + } + } else { + DEBUG_MSG("ERROR: INVALID TX MODULATION\n"); + return LGW_HAL_ERROR; + } + + /* interpretation of TX power */ + for (pow_index = txgain_lut.size-1; pow_index > 0; pow_index--) { + if (txgain_lut.lut[pow_index].rf_power <= pkt_data.rf_power) { + break; + } + } + + /* loading TX imbalance correction */ + target_mix_gain = txgain_lut.lut[pow_index].mix_gain; + if (pkt_data.rf_chain == 0) { /* use radio A calibration table */ + lgw_reg_w(LGW_TX_OFFSET_I, cal_offset_a_i[target_mix_gain - 8]); + lgw_reg_w(LGW_TX_OFFSET_Q, cal_offset_a_q[target_mix_gain - 8]); + } else { /* use radio B calibration table */ + lgw_reg_w(LGW_TX_OFFSET_I, cal_offset_b_i[target_mix_gain - 8]); + lgw_reg_w(LGW_TX_OFFSET_Q, cal_offset_b_q[target_mix_gain - 8]); + } + + /* Set digital gain from LUT */ + lgw_reg_w(LGW_TX_GAIN, txgain_lut.lut[pow_index].dig_gain); + + /* fixed metadata, useful payload and misc metadata compositing */ + transfer_size = TX_METADATA_NB + pkt_data.size; /* */ + payload_offset = TX_METADATA_NB; /* start the payload just after the metadata */ + + /* metadata 0 to 2, TX PLL frequency */ + switch (rf_radio_type[0]) { /* we assume that there is only one radio type on the board */ + case LGW_RADIO_TYPE_SX1255: + part_int = pkt_data.freq_hz / (SX125x_32MHz_FRAC << 7); /* integer part, gives the MSB */ + part_frac = ((pkt_data.freq_hz % (SX125x_32MHz_FRAC << 7)) << 9) / SX125x_32MHz_FRAC; /* fractional part, gives middle part and LSB */ + break; + case LGW_RADIO_TYPE_SX1257: + part_int = pkt_data.freq_hz / (SX125x_32MHz_FRAC << 8); /* integer part, gives the MSB */ + part_frac = ((pkt_data.freq_hz % (SX125x_32MHz_FRAC << 8)) << 8) / SX125x_32MHz_FRAC; /* fractional part, gives middle part and LSB */ + break; + default: + DEBUG_PRINTF("ERROR: UNEXPECTED VALUE %d FOR RADIO TYPE\n", rf_radio_type[0]); + break; + } + + buff[0] = 0xFF & part_int; /* Most Significant Byte */ + buff[1] = 0xFF & (part_frac >> 8); /* middle byte */ + buff[2] = 0xFF & part_frac; /* Least Significant Byte */ + + /* metadata 3 to 6, timestamp trigger value */ + /* TX state machine must be triggered at T0 - TX_START_DELAY for packet to start being emitted at T0 */ + if (pkt_data.tx_mode == TIMESTAMPED) + { + count_trig = pkt_data.count_us - TX_START_DELAY; + buff[3] = 0xFF & (count_trig >> 24); + buff[4] = 0xFF & (count_trig >> 16); + buff[5] = 0xFF & (count_trig >> 8); + buff[6] = 0xFF & count_trig; + } + + /* parameters depending on modulation */ + if (pkt_data.modulation == MOD_LORA) { + /* metadata 7, modulation type, radio chain selection and TX power */ + buff[7] = (0x20 & (pkt_data.rf_chain << 5)) | (0x0F & pow_index); /* bit 4 is 0 -> LoRa modulation */ + + buff[8] = 0; /* metadata 8, not used */ + + /* metadata 9, CRC, LoRa CR & SF */ + switch (pkt_data.datarate) { + case DR_LORA_SF7: buff[9] = 7; break; + case DR_LORA_SF8: buff[9] = 8; break; + case DR_LORA_SF9: buff[9] = 9; break; + case DR_LORA_SF10: buff[9] = 10; break; + case DR_LORA_SF11: buff[9] = 11; break; + case DR_LORA_SF12: buff[9] = 12; break; + default: DEBUG_PRINTF("ERROR: UNEXPECTED VALUE %d IN SWITCH STATEMENT\n", pkt_data.datarate); + } + switch (pkt_data.coderate) { + case CR_LORA_4_5: buff[9] |= 1 << 4; break; + case CR_LORA_4_6: buff[9] |= 2 << 4; break; + case CR_LORA_4_7: buff[9] |= 3 << 4; break; + case CR_LORA_4_8: buff[9] |= 4 << 4; break; + default: DEBUG_PRINTF("ERROR: UNEXPECTED VALUE %d IN SWITCH STATEMENT\n", pkt_data.coderate); + } + if (pkt_data.no_crc == false) { + buff[9] |= 0x80; /* set 'CRC enable' bit */ + } else { + DEBUG_MSG("Info: packet will be sent without CRC\n"); + } + + /* metadata 10, payload size */ + buff[10] = pkt_data.size; + + /* metadata 11, implicit header, modulation bandwidth, PPM offset & polarity */ + switch (pkt_data.bandwidth) { + case BW_125KHZ: buff[11] = 0; break; + case BW_250KHZ: buff[11] = 1; break; + case BW_500KHZ: buff[11] = 2; break; + default: DEBUG_PRINTF("ERROR: UNEXPECTED VALUE %d IN SWITCH STATEMENT\n", pkt_data.bandwidth); + } + if (pkt_data.no_header == true) { + buff[11] |= 0x04; /* set 'implicit header' bit */ + } + if (SET_PPM_ON(pkt_data.bandwidth,pkt_data.datarate)) { + buff[11] |= 0x08; /* set 'PPM offset' bit at 1 */ + } + if (pkt_data.invert_pol == true) { + buff[11] |= 0x10; /* set 'TX polarity' bit at 1 */ + } + + /* metadata 12 & 13, LoRa preamble size */ + if (pkt_data.preamble == 0) { /* if not explicit, use recommended LoRa preamble size */ + pkt_data.preamble = STD_LORA_PREAMBLE; + } else if (pkt_data.preamble < MIN_LORA_PREAMBLE) { /* enforce minimum preamble size */ + pkt_data.preamble = MIN_LORA_PREAMBLE; + DEBUG_MSG("Note: preamble length adjusted to respect minimum LoRa preamble size\n"); + } + buff[12] = 0xFF & (pkt_data.preamble >> 8); + buff[13] = 0xFF & pkt_data.preamble; + + /* metadata 14 & 15, not used */ + buff[14] = 0; + buff[15] = 0; + + /* MSB of RF frequency is now used in AGC firmware to implement large/narrow filtering in SX1257/55 */ + buff[0] &= 0x3F; /* Unset 2 MSBs of frequency code */ + if (pkt_data.bandwidth == BW_500KHZ) { + buff[0] |= 0x80; /* Set MSB bit to enlarge analog filter for 500kHz BW */ + } else if (pkt_data.bandwidth == BW_125KHZ){ - buff[0] |= 0x40; /* Set MSB-1 bit to enable digital filter for 125kHz BW */ - } - - } else if (pkt_data.modulation == MOD_FSK) { - /* metadata 7, modulation type, radio chain selection and TX power */ - buff[7] = (0x20 & (pkt_data.rf_chain << 5)) | 0x10 | (0x0F & pow_index); /* bit 4 is 1 -> FSK modulation */ - - buff[8] = 0; /* metadata 8, not used */ - - /* metadata 9, frequency deviation */ - buff[9] = pkt_data.f_dev; - - /* metadata 10, payload size */ - buff[10] = pkt_data.size; - /* TODO: how to handle 255 bytes packets ?!? */ - - /* metadata 11, packet mode, CRC, encoding */ - buff[11] = 0x01 | (pkt_data.no_crc?0:0x02) | (0x02 << 2); /* always in variable length packet mode, whitening, and CCITT CRC if CRC is not disabled */ - - /* metadata 12 & 13, FSK preamble size */ - if (pkt_data.preamble == 0) { /* if not explicit, use LoRa MAC preamble size */ - pkt_data.preamble = STD_FSK_PREAMBLE; - } else if (pkt_data.preamble < MIN_FSK_PREAMBLE) { /* enforce minimum preamble size */ - pkt_data.preamble = MIN_FSK_PREAMBLE; - DEBUG_MSG("Note: preamble length adjusted to respect minimum FSK preamble size\n"); - } - buff[12] = 0xFF & (pkt_data.preamble >> 8); - buff[13] = 0xFF & pkt_data.preamble; - - /* metadata 14 & 15, FSK baudrate */ - fsk_dr_div = (uint16_t)((uint32_t)LGW_XTAL_FREQU / pkt_data.datarate); /* Ok for datarate between 500bps and 250kbps */ - buff[14] = 0xFF & (fsk_dr_div >> 8); - buff[15] = 0xFF & fsk_dr_div; - - /* insert payload size in the packet for variable mode */ - buff[16] = pkt_data.size; - ++transfer_size; /* one more byte to transfer to the TX modem */ - ++payload_offset; /* start the payload with one more byte of offset */ - - /* MSB of RF frequency is now used in AGC firmware to implement large/narrow filtering in SX1257/55 */ - buff[0] &= 0x7F; /* Always use narrow band for FSK (force MSB to 0) */ - - } else { - DEBUG_MSG("ERROR: INVALID TX MODULATION..\n"); - return LGW_HAL_ERROR; - } - - /* copy payload from user struct to buffer containing metadata */ - memcpy((void *)(buff + payload_offset), (void *)(pkt_data.payload), pkt_data.size); - - /* reset TX command flags */ - lgw_abort_tx(); - - /* put metadata + payload in the TX data buffer */ - lgw_reg_w(LGW_TX_DATA_BUF_ADDR, 0); - lgw_reg_wb(LGW_TX_DATA_BUF_DATA, buff, transfer_size); - DEBUG_ARRAY(i, transfer_size, buff); - - /* send data */ - switch(pkt_data.tx_mode) { - case IMMEDIATE: - lgw_reg_w(LGW_TX_TRIG_IMMEDIATE, 1); - break; - - case TIMESTAMPED: - lgw_reg_w(LGW_TX_TRIG_DELAYED, 1); - break; - - case ON_GPS: - lgw_reg_w(LGW_TX_TRIG_GPS, 1); - break; - - default: - DEBUG_PRINTF("ERROR: UNEXPECTED VALUE %d IN SWITCH STATEMENT\n", pkt_data.tx_mode); - return LGW_HAL_ERROR; - } - - return LGW_HAL_SUCCESS; + buff[0] |= 0x40; /* Set MSB-1 bit to enable digital filter for 125kHz BW */ + } + + } else if (pkt_data.modulation == MOD_FSK) { + /* metadata 7, modulation type, radio chain selection and TX power */ + buff[7] = (0x20 & (pkt_data.rf_chain << 5)) | 0x10 | (0x0F & pow_index); /* bit 4 is 1 -> FSK modulation */ + + buff[8] = 0; /* metadata 8, not used */ + + /* metadata 9, frequency deviation */ + buff[9] = pkt_data.f_dev; + + /* metadata 10, payload size */ + buff[10] = pkt_data.size; + /* TODO: how to handle 255 bytes packets ?!? */ + + /* metadata 11, packet mode, CRC, encoding */ + buff[11] = 0x01 | (pkt_data.no_crc?0:0x02) | (0x02 << 2); /* always in variable length packet mode, whitening, and CCITT CRC if CRC is not disabled */ + + /* metadata 12 & 13, FSK preamble size */ + if (pkt_data.preamble == 0) { /* if not explicit, use LoRa MAC preamble size */ + pkt_data.preamble = STD_FSK_PREAMBLE; + } else if (pkt_data.preamble < MIN_FSK_PREAMBLE) { /* enforce minimum preamble size */ + pkt_data.preamble = MIN_FSK_PREAMBLE; + DEBUG_MSG("Note: preamble length adjusted to respect minimum FSK preamble size\n"); + } + buff[12] = 0xFF & (pkt_data.preamble >> 8); + buff[13] = 0xFF & pkt_data.preamble; + + /* metadata 14 & 15, FSK baudrate */ + fsk_dr_div = (uint16_t)((uint32_t)LGW_XTAL_FREQU / pkt_data.datarate); /* Ok for datarate between 500bps and 250kbps */ + buff[14] = 0xFF & (fsk_dr_div >> 8); + buff[15] = 0xFF & fsk_dr_div; + + /* insert payload size in the packet for variable mode */ + buff[16] = pkt_data.size; + ++transfer_size; /* one more byte to transfer to the TX modem */ + ++payload_offset; /* start the payload with one more byte of offset */ + + /* MSB of RF frequency is now used in AGC firmware to implement large/narrow filtering in SX1257/55 */ + buff[0] &= 0x7F; /* Always use narrow band for FSK (force MSB to 0) */ + + } else { + DEBUG_MSG("ERROR: INVALID TX MODULATION..\n"); + return LGW_HAL_ERROR; + } + + /* copy payload from user struct to buffer containing metadata */ + memcpy((void *)(buff + payload_offset), (void *)(pkt_data.payload), pkt_data.size); + + /* reset TX command flags */ + lgw_abort_tx(); + + /* put metadata + payload in the TX data buffer */ + lgw_reg_w(LGW_TX_DATA_BUF_ADDR, 0); + lgw_reg_wb(LGW_TX_DATA_BUF_DATA, buff, transfer_size); + DEBUG_ARRAY(i, transfer_size, buff); + + x = lbt_is_channel_free(&pkt_data, &tx_allowed); + if (x != LGW_LBT_SUCCESS) { + DEBUG_MSG("ERROR: Failed to check channel availability for TX\n"); + return LGW_HAL_ERROR; + } + if (tx_allowed == true) { + switch(pkt_data.tx_mode) { + case IMMEDIATE: + lgw_reg_w(LGW_TX_TRIG_IMMEDIATE, 1); + break; + + case TIMESTAMPED: + lgw_reg_w(LGW_TX_TRIG_DELAYED, 1); + break; + + case ON_GPS: + lgw_reg_w(LGW_TX_TRIG_GPS, 1); + break; + + default: + DEBUG_PRINTF("ERROR: UNEXPECTED VALUE %d IN SWITCH STATEMENT\n", pkt_data.tx_mode); + return LGW_HAL_ERROR; + } + } else { + DEBUG_MSG("ERROR: Cannot send packet, channel is busy (LBT)\n"); + return LGW_LBT_ISSUE; + } + + return LGW_HAL_SUCCESS; } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ int lgw_status(uint8_t select, uint8_t *code) { - int32_t read_value; - - /* check input variables */ - CHECK_NULL(code); - - if (select == TX_STATUS) { - lgw_reg_r(LGW_TX_STATUS, &read_value); - if (lgw_is_started == false) { - *code = TX_OFF; - } else if ((read_value & 0x10) == 0) { /* bit 4 @1: TX programmed */ - *code = TX_FREE; - } else if ((read_value & 0x60) != 0) { /* bit 5 or 6 @1: TX sequence */ - *code = TX_EMITTING; - } else { - *code = TX_SCHEDULED; - } - return LGW_HAL_SUCCESS; - - } else if (select == RX_STATUS) { - *code = RX_STATUS_UNKNOWN; /* todo */ - return LGW_HAL_SUCCESS; - - } else { - DEBUG_MSG("ERROR: SELECTION INVALID, NO STATUS TO RETURN\n"); - return LGW_HAL_ERROR; - } + int32_t read_value; + + /* check input variables */ + CHECK_NULL(code); + + if (select == TX_STATUS) { + lgw_reg_r(LGW_TX_STATUS, &read_value); + if (lgw_is_started == false) { + *code = TX_OFF; + } else if ((read_value & 0x10) == 0) { /* bit 4 @1: TX programmed */ + *code = TX_FREE; + } else if ((read_value & 0x60) != 0) { /* bit 5 or 6 @1: TX sequence */ + *code = TX_EMITTING; + } else { + *code = TX_SCHEDULED; + } + return LGW_HAL_SUCCESS; + + } else if (select == RX_STATUS) { + *code = RX_STATUS_UNKNOWN; /* todo */ + return LGW_HAL_SUCCESS; + + } else { + DEBUG_MSG("ERROR: SELECTION INVALID, NO STATUS TO RETURN\n"); + return LGW_HAL_ERROR; + } } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ int lgw_abort_tx(void) { - int i; + int i; - i = lgw_reg_w(LGW_TX_TRIG_ALL, 0); + i = lgw_reg_w(LGW_TX_TRIG_ALL, 0); - if (i == LGW_REG_SUCCESS) return LGW_HAL_SUCCESS; - else return LGW_HAL_ERROR; + if (i == LGW_REG_SUCCESS) return LGW_HAL_SUCCESS; + else return LGW_HAL_ERROR; } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ int lgw_get_trigcnt(uint32_t* trig_cnt_us) { - int i; - int32_t val; - - i = lgw_reg_r(LGW_TIMESTAMP, &val); - if (i == LGW_REG_SUCCESS) { - *trig_cnt_us = (uint32_t)val; - return LGW_HAL_SUCCESS; - } else { - return LGW_HAL_ERROR; - } + int i; + int32_t val; + + i = lgw_reg_r(LGW_TIMESTAMP, &val); + if (i == LGW_REG_SUCCESS) { + *trig_cnt_us = (uint32_t)val; + return LGW_HAL_SUCCESS; + } else { + return LGW_HAL_ERROR; + } } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ const char* lgw_version_info() { - return lgw_version_string; + return lgw_version_string; } +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +uint32_t lgw_time_on_air(struct lgw_pkt_tx_s *packet, bool isBeacon) { + uint8_t SF, H, DE; + uint16_t BW; + uint32_t payloadSymbNb, Tpacket; + double Tsym, Tpreamble, Tpayload; + + if (packet == NULL) { + DEBUG_MSG("ERROR: Failed to compute time on air, wrong parameter\n"); + return 0; + } + + switch (packet->bandwidth) { + case BW_125KHZ: + BW = 125; + break; + case BW_250KHZ: + BW = 250; + break; + case BW_500KHZ: + BW = 500; + break; + default: + DEBUG_PRINTF("ERROR: Cannot compute time on air for this packet, unsupported bandwidth (%u)\n", packet->bandwidth); + return 0; + } + + switch (packet->datarate) { + case DR_LORA_SF7: + SF = 7; + break; + case DR_LORA_SF8: + SF = 8; + break; + case DR_LORA_SF9: + SF = 9; + break; + case DR_LORA_SF10: + SF = 10; + break; + case DR_LORA_SF11: + SF = 11; + break; + case DR_LORA_SF12: + SF = 12; + break; + default: + DEBUG_PRINTF("ERROR: Cannot compute time on air for this packet, unsupported datarate (%u)\n", packet->datarate); + return 0; + } + + /* Duration of 1 symbol */ + Tsym = pow(2, SF) / BW; + + /* Duration of preamble */ + Tpreamble = (8 + 4.25) * Tsym; /* 8 programmed symbols in preamble */ + + /* Duration of payload */ + H = (isBeacon==false) ? 0 : 1; /* header is always enabled, except for beacons */ + DE = (SF >= 11) ? 1 : 0; /* Low datarate optimization enabled for SF11 and SF12 */ + + payloadSymbNb = 8 + (ceil((double)(8*packet->size - 4*SF + 28 + 16 - 20*H) / (double)(4*(SF - 2*DE))) * (packet->coderate + 4)); /* Explicitely cast to double to keep precision of the division */ + + Tpayload = payloadSymbNb * Tsym; + + Tpacket = Tpreamble + Tpayload; + + return Tpacket; +} /* --- EOF ------------------------------------------------------------------ */ diff --git a/libloragw/src/loragw_lbt.c b/libloragw/src/loragw_lbt.c new file mode 100644 index 00000000..ff726f65 --- /dev/null +++ b/libloragw/src/loragw_lbt.c @@ -0,0 +1,345 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2013 Semtech-Cycleo + +Description: + Functions used to handle the Listen Before Talk feature + +License: Revised BSD License, see LICENSE.TXT file include in the project +Maintainer: Michael Coracin +*/ + +/* -------------------------------------------------------------------------- */ +/* --- DEPENDANCIES --------------------------------------------------------- */ + +#include /* C99 types */ +#include /* bool type */ +#include /* printf fprintf */ +#include /* abs, labs, llabs */ + +#include "loragw_radio.h" +#include "loragw_aux.h" +#include "loragw_hal.h" +#include "loragw_lbt.h" +#include "loragw_fpga.h" + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE MACROS ------------------------------------------------------- */ + +#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) +#if DEBUG_LBT == 1 + #define DEBUG_MSG(str) fprintf(stderr, str) + #define DEBUG_PRINTF(fmt, args...) fprintf(stderr,"%s:%d: "fmt, __FUNCTION__, __LINE__, args) + #define CHECK_NULL(a) if(a==NULL){fprintf(stderr,"%s:%d: ERROR: NULL POINTER AS ARGUMENT\n", __FUNCTION__, __LINE__);return LGW_REG_ERROR;} +#else + #define DEBUG_MSG(str) + #define DEBUG_PRINTF(fmt, args...) + #define CHECK_NULL(a) if(a==NULL){return LGW_REG_ERROR;} +#endif + +#define TX_START_DELAY 1500 +#define LBT_TIMESTAMP_MASK 0x007FFC00 + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE TYPES -------------------------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE CONSTANTS ---------------------------------------------------- */ + +#define LBT_CHANNEL_FREQ_NB 10 /* Number of LBT channels */ +#define LBT_CHANNEL_DELTA 200000 /* frequency delta between LBT channels, in Hz */ + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE VARIABLES ---------------------------------------------------- */ + +extern void *lgw_spi_target; /*! generic pointer to the SPI device */ +extern uint8_t lgw_spi_mux_mode; /*! current SPI mux mode used */ + +/* LBT variables shared with loragw_hal module */ +bool lbt_enable; +uint8_t lbt_rssi_target = 160; /* -80 dBm */ +uint8_t lbt_nb_channel = 6; +uint32_t lbt_first_channel_freq = 863000000; +uint16_t lbt_scan_time_us = 220; + +/* LBT local variables */ +static uint32_t lbt_end_tx_delay_1ch_us = 400000; +static uint32_t lbt_end_tx_delay_2ch_us = 200000; +static uint32_t lbt_channel_freq[LBT_CHANNEL_FREQ_NB]; /* absolute, in Hz */ + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE FUNCTIONS ---------------------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE FUNCTIONS DEFINITION ----------------------------------------- */ + +/* As given frequencies have been converted from float to integer, some aliasing +issues can appear, so we can't simply check for equality, but have to take some +margin */ +bool is_equal_freq(uint32_t a, uint32_t b) { + int64_t diff; + int64_t a64 = (int64_t)a; + int64_t b64 = (int64_t)b; + + /* Calculate the difference */ + diff = llabs(a64 - b64); + + /* Check for acceptable diff range */ + if( diff <= 10000 ) + { + return true; + } + + return false; +} + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC FUNCTIONS DEFINITION ------------------------------------------ */ + +int lbt_setconf(struct lgw_conf_lbt_s * conf) { + int i; + + /* Check input parameters */ + if (conf == NULL ) { + return LGW_LBT_ERROR; + } + + /* set internal config according to parameters */ + lbt_enable = conf->enable; + lbt_rssi_target = conf->rssi_target; + lbt_scan_time_us = conf->scan_time_us; + lbt_nb_channel = conf->nb_channel; + lbt_end_tx_delay_1ch_us = conf->tx_delay_1ch_us; + lbt_end_tx_delay_2ch_us = conf->tx_delay_2ch_us; + lbt_first_channel_freq = conf->start_freq; + + /* set derivated parameters */ + lbt_channel_freq[0] = lbt_first_channel_freq; + for (i=1; imodulation != MOD_LORA) { + *tx_allowed = false; + DEBUG_PRINTF("INFO: TX is not allowed for this modulation (%x)\n", pkt_data->modulation); + return LGW_LBT_SUCCESS; + } + + /* Get current FPGA time */ + lgw_fpga_reg_r(LGW_FPGA_TIMESTAMP, &val); + fpga_time = (uint32_t)val; + /* Get SX1301 time at last PPS */ + lgw_get_trigcnt(&sx1301_time); + + DEBUG_MSG("################################\n"); + switch(pkt_data->tx_mode) { + case IMMEDIATE: + DEBUG_MSG("tx_mode = IMMEDIATE\n"); + tx_start_time = (fpga_time + TX_START_DELAY) & LBT_TIMESTAMP_MASK; /* 0x007FFC00: to align on LBT time format (TIMESTAMP_CH) */ + break; + case TIMESTAMPED: + DEBUG_MSG("tx_mode = TIMESTAMPED\n"); + tx_start_time = pkt_data->count_us & LBT_TIMESTAMP_MASK; + break; + case ON_GPS: + DEBUG_MSG("tx_mode = ON_GPS\n"); + tx_start_time = (sx1301_time + TX_START_DELAY + 1000000) & LBT_TIMESTAMP_MASK; + break; + default: + return LGW_LBT_ERROR; + } + + /* Select LBT Channel corresponding to required TX frequency */ + if (pkt_data->bandwidth == BW_125KHZ){ + tx_max_time = lbt_end_tx_delay_1ch_us; + lbt_channel_decod_1 = -1; + lbt_channel_decod_2 = -1; + for (i=0; ifreq_hz, lbt_channel_freq[i]) == true) { + DEBUG_PRINTF("LBT: select channel %d (%u Hz)\n", i, lbt_channel_freq[i]); + lbt_channel_decod_1 = i; + lbt_channel_decod_2 = i; + break; + } + } + } else if (pkt_data->bandwidth == BW_250KHZ) { + tx_max_time = lbt_end_tx_delay_2ch_us; + + /* In case of 250KHz, the TX freq has to be in between 2 channels of 200KHz BW. The TX can only be over 2 channels, not more */ + lbt_channel_decod_1 = -1; + lbt_channel_decod_2 = -1; + for (i=0; i<(LBT_CHANNEL_FREQ_NB-1); i++) { + if (is_equal_freq(pkt_data->freq_hz, (lbt_channel_freq[i]+lbt_channel_freq[i+1])/2) == true) { + DEBUG_PRINTF("LBT: select channels %d,%d (%u Hz)\n", i, i+1, (lbt_channel_freq[i]+lbt_channel_freq[i+1])/2); + lbt_channel_decod_1 = i; + lbt_channel_decod_2 = i+1; + break; + } + } + } else { + lbt_channel_decod_1 = -1; + lbt_channel_decod_2 = -1; + } + + /* Get last time when selected channel was free */ + if ((lbt_channel_decod_1 >= 0) && (lbt_channel_decod_2 >= 0)) { + lgw_fpga_reg_w(LGW_FPGA_LBT_TIMESTAMP_SELECT_CH, (int32_t)lbt_channel_decod_1); + lgw_fpga_reg_r(LGW_FPGA_LBT_TIMESTAMP_CH, &val); + lbt_time = lbt_time1 = (uint32_t)(val & 0x00FFFFFF) * 256; /* 24bits (1LSB = 256µs) */ + + if (lbt_channel_decod_1 != lbt_channel_decod_2 ) { + lgw_fpga_reg_w(LGW_FPGA_LBT_TIMESTAMP_SELECT_CH, (int32_t)lbt_channel_decod_2); + lgw_fpga_reg_r(LGW_FPGA_LBT_TIMESTAMP_CH, &val); + lbt_time2 = (uint32_t)(val & 0x00FFFFFF) * 256; /* 24bits (1LSB = 256µs) */ + + if (lbt_time2 < lbt_time1) { + lbt_time = lbt_time2; + } + } + } else { + lbt_time = 0; + } + + packet_duration = lgw_time_on_air(pkt_data, pkt_data->no_header) * 1000UL; + tx_end_time = (tx_start_time + packet_duration) & LBT_TIMESTAMP_MASK; + if (lbt_time < tx_end_time) { + delta_time = tx_end_time - lbt_time; + } else { + /* It means LBT counter has wrapped */ + printf("LBT: lbt counter has wrapped\n"); + delta_time = (LBT_TIMESTAMP_MASK - lbt_time) + tx_end_time; + } + + DEBUG_PRINTF("sx1301_time = %u\n", sx1301_time & LBT_TIMESTAMP_MASK); + DEBUG_PRINTF("fpga_time = %u\n", fpga_time & LBT_TIMESTAMP_MASK); + DEBUG_PRINTF("tx_freq = %u\n", pkt_data->freq_hz); + DEBUG_MSG("------------------------------------------------\n"); + DEBUG_PRINTF("packet_duration = %u\n", packet_duration); + DEBUG_PRINTF("tx_start_time = %u\n", tx_start_time); + DEBUG_PRINTF("lbt_time1 = %u\n", lbt_time1); + DEBUG_PRINTF("lbt_time2 = %u\n", lbt_time2); + DEBUG_PRINTF("lbt_time = %u\n", lbt_time); + DEBUG_PRINTF("delta_time = %u\n", delta_time); + DEBUG_MSG("------------------------------------------------\n"); + + /* send data if allowed */ + /* lbt_time: last time when channel was free */ + /* tx_max_time: maximum time allowed to send packet since last free time */ + /* 2048: some margin */ + if (((delta_time < (tx_max_time - 2048)) && (lbt_time != 0)) || (lbt_enable == false)) { + *tx_allowed = true; + } else { + DEBUG_MSG("ERROR: TX request rejected (LBT)\n"); + *tx_allowed = false; + } + } else { + /* Always allow if LBT is disabled */ + *tx_allowed = true; + } + + return LGW_LBT_SUCCESS; +} + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/libloragw/src/loragw_radio.c b/libloragw/src/loragw_radio.c new file mode 100644 index 00000000..90e4cfa9 --- /dev/null +++ b/libloragw/src/loragw_radio.c @@ -0,0 +1,653 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2013 Semtech-Cycleo + +Description: + Functions used to handle LoRa concentrator radios. + +License: Revised BSD License, see LICENSE.TXT file include in the project +Maintainer: Michael Coracin +*/ + +/* -------------------------------------------------------------------------- */ +/* --- DEPENDANCIES --------------------------------------------------------- */ + +#include /* C99 types */ +#include /* bool type */ +#include /* printf fprintf */ + +#include "loragw_sx125x.h" +#include "loragw_sx1272_fsk.h" +#include "loragw_sx1272_lora.h" +#include "loragw_sx1276_fsk.h" +#include "loragw_sx1276_lora.h" +#include "loragw_spi.h" +#include "loragw_aux.h" +#include "loragw_reg.h" +#include "loragw_hal.h" +#include "loragw_radio.h" +#include "loragw_fpga.h" + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE MACROS ------------------------------------------------------- */ + +#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) +#if DEBUG_REG == 1 + #define DEBUG_MSG(str) fprintf(stderr, str) + #define DEBUG_PRINTF(fmt, args...) fprintf(stderr,"%s:%d: "fmt, __FUNCTION__, __LINE__, args) + #define CHECK_NULL(a) if(a==NULL){fprintf(stderr,"%s:%d: ERROR: NULL POINTER AS ARGUMENT\n", __FUNCTION__, __LINE__);return LGW_REG_ERROR;} +#else + #define DEBUG_MSG(str) + #define DEBUG_PRINTF(fmt, args...) + #define CHECK_NULL(a) if(a==NULL){return LGW_REG_ERROR;} +#endif + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE TYPES -------------------------------------------------------- */ + +/** +@struct lgw_radio_type_version_s +@brief Associate a radio type with its corresponding expected version value + read in the radio version register. +*/ +struct lgw_radio_type_version_s { + enum lgw_radio_type_e type; + uint8_t reg_version; +}; + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE CONSTANTS ---------------------------------------------------- */ + +#define PLL_LOCK_MAX_ATTEMPTS 5 + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE VARIABLES ---------------------------------------------------- */ + +extern void *lgw_spi_target; /*! generic pointer to the SPI device */ + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE FUNCTIONS ---------------------------------------------------- */ + +void sx125x_write(uint8_t channel, uint8_t addr, uint8_t data); + +uint8_t sx125x_read(uint8_t channel, uint8_t addr); + +int setup_sx1272_FSK(uint32_t frequency); + +int setup_sx1272_LoRa(uint32_t frequency); + +int setup_sx1276_FSK(uint32_t frequency); + +int reset_sx127x(enum lgw_radio_type_e radio_type); + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE FUNCTIONS DEFINITION ----------------------------------------- */ + +void sx125x_write(uint8_t channel, uint8_t addr, uint8_t data) { + int reg_add, reg_dat, reg_cs; + + /* checking input parameters */ + if (channel >= LGW_RF_CHAIN_NB) { + DEBUG_MSG("ERROR: INVALID RF_CHAIN\n"); + return; + } + if (addr >= 0x7F) { + DEBUG_MSG("ERROR: ADDRESS OUT OF RANGE\n"); + return; + } + + /* selecting the target radio */ + switch (channel) { + case 0: + reg_add = LGW_SPI_RADIO_A__ADDR; + reg_dat = LGW_SPI_RADIO_A__DATA; + reg_cs = LGW_SPI_RADIO_A__CS; + break; + + case 1: + reg_add = LGW_SPI_RADIO_B__ADDR; + reg_dat = LGW_SPI_RADIO_B__DATA; + reg_cs = LGW_SPI_RADIO_B__CS; + break; + + default: + DEBUG_PRINTF("ERROR: UNEXPECTED VALUE %d IN SWITCH STATEMENT\n", channel); + return; + } + + /* SPI master data write procedure */ + lgw_reg_w(reg_cs, 0); + lgw_reg_w(reg_add, 0x80 | addr); /* MSB at 1 for write operation */ + lgw_reg_w(reg_dat, data); + lgw_reg_w(reg_cs, 1); + lgw_reg_w(reg_cs, 0); + + return; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +uint8_t sx125x_read(uint8_t channel, uint8_t addr) { + int reg_add, reg_dat, reg_cs, reg_rb; + int32_t read_value; + + /* checking input parameters */ + if (channel >= LGW_RF_CHAIN_NB) { + DEBUG_MSG("ERROR: INVALID RF_CHAIN\n"); + return 0; + } + if (addr >= 0x7F) { + DEBUG_MSG("ERROR: ADDRESS OUT OF RANGE\n"); + return 0; + } + + /* selecting the target radio */ + switch (channel) { + case 0: + reg_add = LGW_SPI_RADIO_A__ADDR; + reg_dat = LGW_SPI_RADIO_A__DATA; + reg_cs = LGW_SPI_RADIO_A__CS; + reg_rb = LGW_SPI_RADIO_A__DATA_READBACK; + break; + + case 1: + reg_add = LGW_SPI_RADIO_B__ADDR; + reg_dat = LGW_SPI_RADIO_B__DATA; + reg_cs = LGW_SPI_RADIO_B__CS; + reg_rb = LGW_SPI_RADIO_B__DATA_READBACK; + break; + + default: + DEBUG_PRINTF("ERROR: UNEXPECTED VALUE %d IN SWITCH STATEMENT\n", channel); + return 0; + } + + /* SPI master data read procedure */ + lgw_reg_w(reg_cs, 0); + lgw_reg_w(reg_add, addr); /* MSB at 0 for read operation */ + lgw_reg_w(reg_dat, 0); + lgw_reg_w(reg_cs, 1); + lgw_reg_w(reg_cs, 0); + lgw_reg_r(reg_rb, &read_value); + + return (uint8_t)read_value; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int setup_sx1272_FSK(uint32_t frequency) { + uint64_t freq_reg; + uint8_t ModulationShaping = 0; + uint8_t PllHop = 1; + uint8_t LnaGain = 1; + uint8_t LnaBoost = 3; + uint8_t AdcBwAuto = 0; + uint8_t AdcBw = 7; + uint8_t AdcLowPwr = 0; + uint8_t AdcTrim = 6; + uint8_t AdcTest = 0; + uint8_t RxBwExp = 2; + uint8_t RxBwMant = 1; + uint8_t RssiSmoothing = 5; + uint8_t RssiOffset = 3; + uint8_t reg_val; + int x; + + /* Set in FSK mode */ + x = lgw_sx127x_reg_w(SX1272_REG_OPMODE, 0); + wait_ms(100); + x |= lgw_sx127x_reg_w(SX1272_REG_OPMODE, 0 | (ModulationShaping << 3)); /* Sleep mode, no FSK shaping */ + wait_ms(100); + x |= lgw_sx127x_reg_w(SX1272_REG_OPMODE, 1 | (ModulationShaping << 3)); /* Standby mode, no FSK shaping */ + wait_ms(100); + + /* Set RF carrier frequency */ + x |= lgw_sx127x_reg_w(SX1272_REG_PLLHOP, PllHop << 7); + freq_reg = ((uint64_t)frequency << 19) / (uint64_t)32000000; + x |= lgw_sx127x_reg_w(SX1272_REG_FRFMSB, (freq_reg >> 16) & 0xFF); + x |= lgw_sx127x_reg_w(SX1272_REG_FRFMID, (freq_reg >> 8) & 0xFF); + x |= lgw_sx127x_reg_w(SX1272_REG_FRFLSB, (freq_reg >> 0) & 0xFF); + + /* Config */ + x |= lgw_sx127x_reg_w(SX1272_REG_LNA, LnaBoost | (LnaGain << 5)); /* Improved sensitivity, highest gain */ + x |= lgw_sx127x_reg_w(0x68, AdcBw | (AdcBwAuto << 3)); + x |= lgw_sx127x_reg_w(0x69, AdcTest | (AdcTrim << 4) | (AdcLowPwr << 7)); + + /* set BR and FDEV for 200 kHz bandwidth*/ + x |= lgw_sx127x_reg_w(SX1272_REG_BITRATEMSB, 125); + x |= lgw_sx127x_reg_w(SX1272_REG_BITRATELSB, 0); + x |= lgw_sx127x_reg_w(SX1272_REG_FDEVMSB, 2); + x |= lgw_sx127x_reg_w(SX1272_REG_FDEVLSB, 225); + + /* Config continues... */ + x |= lgw_sx127x_reg_w(SX1272_REG_RXCONFIG, 0); /* Disable AGC */ + x |= lgw_sx127x_reg_w(SX1272_REG_RSSICONFIG, RssiSmoothing | (RssiOffset << 3)); /* Set RSSI smoothing to 64 samples, RSSI offset 3dB */ + x |= lgw_sx127x_reg_w(SX1272_REG_RXBW, RxBwExp | (RxBwMant << 3)); /* RX BW = 100kHz, Mant=20, Exp=2 */ + x |= lgw_sx127x_reg_w(SX1272_REG_RXDELAY, 2); + x |= lgw_sx127x_reg_w(SX1272_REG_PLL, 0x10); /* PLL BW set to 75 KHz */ + x |= lgw_sx127x_reg_w(0x47, 1); /* optimize PLL start-up time */ + + if (x != LGW_REG_SUCCESS) { + DEBUG_MSG("ERROR: Failed to configure SX1272\n"); + return x; + } + + /* set Rx continuous mode */ + x = lgw_sx127x_reg_w(SX1272_REG_OPMODE, 5 | (ModulationShaping << 3)); /* Receiver Mode, no FSK shaping */ + wait_ms(500); + x |= lgw_sx127x_reg_r(SX1272_REG_IRQFLAGS1, ®_val); + /* Check if RxReady and ModeReady */ + if ((TAKE_N_BITS_FROM(reg_val, 6, 1) == 0) || (TAKE_N_BITS_FROM(reg_val, 7, 1) == 0) || (x != LGW_REG_SUCCESS)) { + DEBUG_MSG("ERROR: SX1272 failed to enter RX continuous mode\n"); + return LGW_REG_ERROR; + } + wait_ms(500); + + DEBUG_MSG("INFO: Successfully configured SX1272 for FSK modulation\n"); + + return LGW_REG_SUCCESS; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int setup_sx1272_LoRa(uint32_t frequency) { + uint64_t freq_reg; + uint8_t LoRaMode = 1; + uint8_t bw = 0; + uint8_t LowZin = 1; + uint8_t sf = 7; + uint8_t AgcAuto = 1; + uint8_t LnaGain = 1; + uint8_t TrimRxCrFo = 0; + uint8_t LnaBoost = 3; + uint8_t AdcBwAuto = 0; + uint8_t AdcBw = 7; + uint8_t AdcLowPwr = 0; + uint8_t AdcTrim = 6; + uint8_t AdcTest = 0; + uint8_t reg_val; + int x; + + /* Set in LoRa mode */ + x = lgw_sx127x_reg_w(SX1272_REG_LR_OPMODE, 0); + wait_ms(100); + x |= lgw_sx127x_reg_w(SX1272_REG_LR_OPMODE, 0 | (LoRaMode << 7)); /* Sleep mode, LoRa Mode */ + wait_ms(100); + x |= lgw_sx127x_reg_w(SX1272_REG_LR_OPMODE, 1 | (LoRaMode << 7)); /* Standby mode, LoRa Mode */ + wait_ms(100); + + /* Set RF carrier frequency */ + freq_reg = ((uint64_t)frequency << 19) / (uint64_t)32000000; + x |= lgw_sx127x_reg_w(SX1272_REG_LR_FRFMSB, (freq_reg >> 16) & 0xFF); + x |= lgw_sx127x_reg_w(SX1272_REG_LR_FRFMID, (freq_reg >> 8) & 0xFF); + x |= lgw_sx127x_reg_w(SX1272_REG_LR_FRFLSB, freq_reg & 0xFF); + + /* Config */ + x |= lgw_sx127x_reg_w(SX1272_REG_LR_MODEMCONFIG1, bw << 6); /* 125 KHz */ + x |= lgw_sx127x_reg_w(0x50, LowZin); + x |= lgw_sx127x_reg_w(SX1272_REG_LR_MODEMCONFIG2, (sf << 4) | (AgcAuto << 2)); + x |= lgw_sx127x_reg_w(SX1272_REG_LR_LNA, LnaBoost | (TrimRxCrFo << 3) | (LnaGain << 5)); + x |= lgw_sx127x_reg_w(0x68, AdcBw | (AdcBwAuto << 3)); + x |= lgw_sx127x_reg_w(0x69, AdcTest | (AdcTrim << 4) | (AdcLowPwr << 7)); + + if (x != LGW_REG_SUCCESS) { + DEBUG_MSG("ERROR: Failed to configure SX1272\n"); + return x; + } + + /* Set in Rx continuous mode */ + x = lgw_sx127x_reg_w(SX1272_REG_LR_OPMODE, 5 | (LoRaMode << 7)); /* Receiver mode, LoRa Mode */ + wait_ms(100); + x |= lgw_sx127x_reg_r(SX1272_REG_LR_OPMODE, ®_val); + if ((reg_val != (5 | (LoRaMode << 7))) || (x != LGW_REG_SUCCESS)) { + DEBUG_MSG("ERROR: SX1272 failed to enter RX continuous mode\n"); + return x; + } + + DEBUG_MSG("INFO: Successfully configured SX1272 for LoRa modulation\n"); + + return LGW_REG_SUCCESS; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int setup_sx1276_FSK(uint32_t frequency) { + uint64_t freq_reg; + uint8_t ModulationShaping = 0; + uint8_t PllHop = 1; + uint8_t LnaGain = 1; + uint8_t LnaBoost = 3; + uint8_t AdcBwAuto = 0; + uint8_t AdcBw = 7; + uint8_t AdcLowPwr = 0; + uint8_t AdcTrim = 6; + uint8_t AdcTest = 0; + uint8_t RxBwExp = 2; + uint8_t RxBwMant = 1; + uint8_t RssiSmoothing = 5; + uint8_t RssiOffset = 3; + uint8_t reg_val; + int x; + + /* Set in FSK mode */ + x = lgw_sx127x_reg_w(SX1276_REG_OPMODE, 0); + wait_ms(100); + x |= lgw_sx127x_reg_w(SX1276_REG_OPMODE, 0 | (ModulationShaping << 3)); /* Sleep mode, no FSK shaping */ + wait_ms(100); + x |= lgw_sx127x_reg_w(SX1276_REG_OPMODE, 1 | (ModulationShaping << 3)); /* Standby mode, no FSK shaping */ + wait_ms(100); + + /* Set RF carrier frequency */ + x |= lgw_sx127x_reg_w(SX1276_REG_PLLHOP, PllHop << 7); + freq_reg = ((uint64_t)frequency << 19) / (uint64_t)32000000; + x |= lgw_sx127x_reg_w(SX1276_REG_FRFMSB, (freq_reg >> 16) & 0xFF); + x |= lgw_sx127x_reg_w(SX1276_REG_FRFMID, (freq_reg >> 8) & 0xFF); + x |= lgw_sx127x_reg_w(SX1276_REG_FRFLSB, (freq_reg >> 0) & 0xFF); + + /* Config */ + x |= lgw_sx127x_reg_w(SX1276_REG_LNA, LnaBoost | (LnaGain << 5)); /* Improved sensitivity, highest gain */ + x |= lgw_sx127x_reg_w(0x57, AdcBw | (AdcBwAuto << 3)); + x |= lgw_sx127x_reg_w(0x58, AdcTest | (AdcTrim << 4) | (AdcLowPwr << 7)); + + /* set BR and FDEV for 200 kHz bandwidth*/ + x |= lgw_sx127x_reg_w(SX1276_REG_BITRATEMSB, 125); + x |= lgw_sx127x_reg_w(SX1276_REG_BITRATELSB, 0); + x |= lgw_sx127x_reg_w(SX1276_REG_FDEVMSB, 2); + x |= lgw_sx127x_reg_w(SX1276_REG_FDEVLSB, 225); + + /* Config continues... */ + x |= lgw_sx127x_reg_w(SX1276_REG_RXCONFIG, 0); /* Disable AGC */ + x |= lgw_sx127x_reg_w(SX1276_REG_RSSICONFIG, RssiSmoothing | (RssiOffset << 3)); /* Set RSSI smoothing to 64 samples, RSSI offset 3dB */ + x |= lgw_sx127x_reg_w(SX1276_REG_RXBW, RxBwExp | (RxBwMant << 3)); /* RX BW = 100kHz, Mant=20, Exp=2 */ + x |= lgw_sx127x_reg_w(SX1276_REG_RXDELAY, 2); + x |= lgw_sx127x_reg_w(SX1276_REG_PLL, 0x10); /* PLL BW set to 75 KHz */ + x |= lgw_sx127x_reg_w(0x43, 1); /* optimize PLL start-up time */ + + if (x != LGW_REG_SUCCESS) { + DEBUG_MSG("ERROR: Failed to configure SX1276\n"); + return x; + } + + /* set Rx continuous mode */ + x = lgw_sx127x_reg_w(SX1276_REG_OPMODE, 5 | (ModulationShaping << 3)); /* Receiver Mode, no FSK shaping */ + wait_ms(500); + x |= lgw_sx127x_reg_r(SX1276_REG_IRQFLAGS1, ®_val); + /* Check if RxReady and ModeReady */ + if ((TAKE_N_BITS_FROM(reg_val, 6, 1) == 0) || (TAKE_N_BITS_FROM(reg_val, 7, 1) == 0) || (x != LGW_REG_SUCCESS)) { + DEBUG_MSG("ERROR: SX1276 failed to enter RX continuous mode\n"); + return LGW_REG_ERROR; + } + wait_ms(500); + + DEBUG_MSG("INFO: Successfully configured SX1276 for FSK modulation\n"); + + return LGW_REG_SUCCESS; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int setup_sx1276_LoRa(uint32_t frequency) { + uint64_t freq_reg; + uint8_t LoRaMode = 1; + uint8_t bw = 7; + uint8_t codingRate = 1; + uint8_t LowZin = 1; + uint8_t sf = 7; + uint8_t AgcAuto = 1; + uint8_t LnaGain = 1; + uint8_t LnaBoost = 3; + uint8_t AdcBwAuto = 0; + uint8_t AdcBw = 7; + uint8_t AdcLowPwr = 0; + uint8_t AdcTrim = 6; + uint8_t AdcTest = 0; + uint8_t reg_val; + int x; + + /* Set in LoRa mode */ + x = lgw_sx127x_reg_w(SX1276_REG_LR_OPMODE, 0); + wait_ms(100); + x |= lgw_sx127x_reg_w(SX1276_REG_LR_OPMODE, 0 | (LoRaMode << 7)); /* Sleep mode, LoRa Mode */ + wait_ms(100); + x |= lgw_sx127x_reg_w(SX1276_REG_LR_OPMODE, 1 | (LoRaMode << 7)); /* Standby mode, LoRa Mode */ + wait_ms(100); + + /* Set RF carrier frequency */ + freq_reg = ((uint64_t)frequency << 19) / (uint64_t)32000000; + x |= lgw_sx127x_reg_w(SX1276_REG_LR_FRFMSB, (freq_reg >> 16) & 0xFF); + x |= lgw_sx127x_reg_w(SX1276_REG_LR_FRFMID, (freq_reg >> 8) & 0xFF); + x |= lgw_sx127x_reg_w(SX1276_REG_LR_FRFLSB, freq_reg & 0xFF); + + /* Config */ + x |= lgw_sx127x_reg_w(SX1276_REG_LR_MODEMCONFIG1, 0 | (codingRate << 1) | (bw << 4)); /* 125 KHz, 4/5 */ + x |= lgw_sx127x_reg_w(0x69, LowZin); + x |= lgw_sx127x_reg_w(SX1276_REG_LR_MODEMCONFIG2, sf << 4); + x |= lgw_sx127x_reg_w(SX1276_REG_LR_MODEMCONFIG3, AgcAuto << 2); + x |= lgw_sx127x_reg_w(SX1276_REG_LR_LNA, LnaBoost | (LnaGain << 5)); + x |= lgw_sx127x_reg_w(0x57, AdcBw | (AdcBwAuto << 3)); + x |= lgw_sx127x_reg_w(0x57, AdcTest | (AdcTrim << 4) | (AdcLowPwr << 7)); + + if (x != LGW_REG_SUCCESS) { + DEBUG_MSG("ERROR: Failed to configure SX1276\n"); + return x; + } + + /* Set in Rx continuous mode */ + x = lgw_sx127x_reg_w(SX1276_REG_LR_OPMODE, 5 | (LoRaMode << 7)); /* Receiver mode, LoRa Mode */ + wait_ms(100); + x |= lgw_sx127x_reg_r(SX1276_REG_LR_OPMODE, ®_val); + if ((reg_val != (5 | (LoRaMode << 7))) || (x != LGW_REG_SUCCESS)) { + DEBUG_MSG("ERROR: SX1276 failed to enter RX continuous mode\n"); + return x; + } + + DEBUG_MSG("INFO: Successfully configured SX1276 for LoRa modulation\n"); + + return LGW_REG_SUCCESS; +} + +int reset_sx127x(enum lgw_radio_type_e radio_type) { + int x; + + switch(radio_type) { + case LGW_RADIO_TYPE_SX1276: + x = lgw_fpga_reg_w(LGW_FPGA_CTRL_RADIO_RESET, 0); + x |= lgw_fpga_reg_w(LGW_FPGA_CTRL_RADIO_RESET, 1); + if (x != LGW_SPI_SUCCESS) { + DEBUG_MSG("ERROR: Failed to reset sx127x\n"); + return x; + } + break; + case LGW_RADIO_TYPE_SX1272: + x = lgw_fpga_reg_w(LGW_FPGA_CTRL_RADIO_RESET, 1); + x |= lgw_fpga_reg_w(LGW_FPGA_CTRL_RADIO_RESET, 0); + if (x != LGW_SPI_SUCCESS) { + DEBUG_MSG("ERROR: Failed to reset sx127x\n"); + return x; + } + break; + default: + DEBUG_PRINTF("ERROR: Failed to reset sx127x, not supported (%d)\n", radio_type); + return LGW_REG_ERROR; + } + + return LGW_REG_SUCCESS; +} + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC FUNCTIONS DEFINITION ------------------------------------------ */ + +int setup_sx125x(uint8_t rf_chain, uint8_t rf_clkout, bool rf_enable, uint8_t rf_radio_type, uint32_t freq_hz) { + uint32_t part_int = 0; + uint32_t part_frac = 0; + int cpt_attempts = 0; + + if (rf_chain >= LGW_RF_CHAIN_NB) { + DEBUG_MSG("ERROR: INVALID RF_CHAIN\n"); + return -1; + } + + /* Get version to identify SX1255/57 silicon revision */ + DEBUG_PRINTF("Note: SX125x #%d version register returned 0x%02x\n", rf_chain, sx125x_read(rf_chain, 0x07)); + + /* General radio setup */ + if (rf_clkout == rf_chain) { + sx125x_write(rf_chain, 0x10, SX125x_TX_DAC_CLK_SEL + 2); + DEBUG_PRINTF("Note: SX125x #%d clock output enabled\n", rf_chain); + } else { + sx125x_write(rf_chain, 0x10, SX125x_TX_DAC_CLK_SEL); + DEBUG_PRINTF("Note: SX125x #%d clock output disabled\n", rf_chain); + } + + switch (rf_radio_type) { + case LGW_RADIO_TYPE_SX1255: + sx125x_write(rf_chain, 0x28, SX125x_XOSC_GM_STARTUP + SX125x_XOSC_DISABLE*16); + break; + case LGW_RADIO_TYPE_SX1257: + sx125x_write(rf_chain, 0x26, SX125x_XOSC_GM_STARTUP + SX125x_XOSC_DISABLE*16); + break; + default: + DEBUG_PRINTF("ERROR: UNEXPECTED VALUE %d FOR RADIO TYPE\n", rf_radio_type); + break; + } + + if (rf_enable == true) { + /* Tx gain and trim */ + sx125x_write(rf_chain, 0x08, SX125x_TX_MIX_GAIN + SX125x_TX_DAC_GAIN*16); + sx125x_write(rf_chain, 0x0A, SX125x_TX_ANA_BW + SX125x_TX_PLL_BW*32); + sx125x_write(rf_chain, 0x0B, SX125x_TX_DAC_BW); + + /* Rx gain and trim */ + sx125x_write(rf_chain, 0x0C, SX125x_LNA_ZIN + SX125x_RX_BB_GAIN*2 + SX125x_RX_LNA_GAIN*32); + sx125x_write(rf_chain, 0x0D, SX125x_RX_BB_BW + SX125x_RX_ADC_TRIM*4 + SX125x_RX_ADC_BW*32); + sx125x_write(rf_chain, 0x0E, SX125x_ADC_TEMP + SX125x_RX_PLL_BW*2); + + /* set RX PLL frequency */ + switch (rf_radio_type) { + case LGW_RADIO_TYPE_SX1255: + part_int = freq_hz / (SX125x_32MHz_FRAC << 7); /* integer part, gives the MSB */ + part_frac = ((freq_hz % (SX125x_32MHz_FRAC << 7)) << 9) / SX125x_32MHz_FRAC; /* fractional part, gives middle part and LSB */ + break; + case LGW_RADIO_TYPE_SX1257: + part_int = freq_hz / (SX125x_32MHz_FRAC << 8); /* integer part, gives the MSB */ + part_frac = ((freq_hz % (SX125x_32MHz_FRAC << 8)) << 8) / SX125x_32MHz_FRAC; /* fractional part, gives middle part and LSB */ + break; + default: + DEBUG_PRINTF("ERROR: UNEXPECTED VALUE %d FOR RADIO TYPE\n", rf_radio_type); + break; + } + + sx125x_write(rf_chain, 0x01,0xFF & part_int); /* Most Significant Byte */ + sx125x_write(rf_chain, 0x02,0xFF & (part_frac >> 8)); /* middle byte */ + sx125x_write(rf_chain, 0x03,0xFF & part_frac); /* Least Significant Byte */ + + /* start and PLL lock */ + do { + if (cpt_attempts >= PLL_LOCK_MAX_ATTEMPTS) { + DEBUG_MSG("ERROR: FAIL TO LOCK PLL\n"); + return -1; + } + sx125x_write(rf_chain, 0x00, 1); /* enable Xtal oscillator */ + sx125x_write(rf_chain, 0x00, 3); /* Enable RX (PLL+FE) */ + ++cpt_attempts; + DEBUG_PRINTF("Note: SX125x #%d PLL start (attempt %d)\n", rf_chain, cpt_attempts); + wait_ms(1); + } while((sx125x_read(rf_chain, 0x11) & 0x02) == 0); + } else { + DEBUG_PRINTF("Note: SX125x #%d kept in standby mode\n", rf_chain); + } + + return 0; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int lgw_sx127x_reg_w(uint8_t address, uint8_t reg_value) { + return lgw_spi_w(lgw_spi_target, LGW_SPI_MUX_MODE1, LGW_SPI_MUX_TARGET_SX127X, address, reg_value); +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int lgw_sx127x_reg_r(uint8_t address, uint8_t *reg_value) { + return lgw_spi_r(lgw_spi_target, LGW_SPI_MUX_MODE1, LGW_SPI_MUX_TARGET_SX127X, address, reg_value); +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int lgw_setup_sx127x(uint32_t frequency, uint8_t modulation) { + int x, i; + uint8_t version; + enum lgw_radio_type_e radio_type = LGW_RADIO_TYPE_NONE; + struct lgw_radio_type_version_s supported_radio_type[2] = { + {LGW_RADIO_TYPE_SX1272, 0x22}, + {LGW_RADIO_TYPE_SX1276, 0x12} + }; + + /* Check parameters */ + if ((modulation != MOD_FSK) && (modulation != MOD_LORA)) { + DEBUG_PRINTF("ERROR: modulation not supported for SX127x (%u)\n", modulation); + return LGW_REG_ERROR; + } + + /* Probing radio type */ + for (i = 0; i < (int)(sizeof supported_radio_type); i++) { + /* Reset the radio */ + x = reset_sx127x(supported_radio_type[i].type); + if (x != LGW_SPI_SUCCESS) { + DEBUG_MSG("ERROR: Failed to reset sx127x\n"); + return x; + } + /* Read version register */ + x = lgw_sx127x_reg_r(0x42, &version); + if (x != LGW_SPI_SUCCESS) { + DEBUG_MSG("ERROR: Failed to read sx127x version register\n"); + return x; + } + /* Check if we got the expected version */ + if (version != supported_radio_type[i].reg_version) { + DEBUG_PRINTF("INFO: sx127x version register - read:0x%02x, expected:0x%02x\n", version, supported_radio_type[i].reg_version); + continue; + } else { + DEBUG_PRINTF("INFO: sx127x radio has been found (type:%d, version:0x%02x)\n", supported_radio_type[i].type, version); + radio_type = supported_radio_type[i].type; + break; + } + } + if (radio_type == LGW_RADIO_TYPE_NONE) { + DEBUG_MSG("ERROR: sx127x radio has not been found\n"); + return LGW_REG_ERROR; + } + + /* Setup the radio */ + switch (modulation) { + case MOD_LORA: + if (radio_type == LGW_RADIO_TYPE_SX1272) { + x = setup_sx1272_LoRa(frequency); + } else { + x = setup_sx1276_LoRa(frequency); + } + break; + case MOD_FSK: + if (radio_type == LGW_RADIO_TYPE_SX1272) { + x = setup_sx1272_FSK(frequency); + } else { + x = setup_sx1276_FSK(frequency); + } + break; + default: + /* Should not happen */ + break; + } + if (x != LGW_REG_SUCCESS) { + DEBUG_MSG("ERROR: failed to setup SX127x\n"); + return x; + } + + return LGW_REG_SUCCESS; +} + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/libloragw/src/loragw_reg.c b/libloragw/src/loragw_reg.c index efc65ccf..3f3c7e26 100644 --- a/libloragw/src/loragw_reg.c +++ b/libloragw/src/loragw_reg.c @@ -7,10 +7,10 @@ (C)2013 Semtech-Cycleo Description: - Functions used to handle a single LoRa concentrator. - Registers are addressed by name. - Multi-bytes registers are handled automatically. - Read-modify-write is handled automatically. + Functions used to handle a single LoRa concentrator. + Registers are addressed by name. + Multi-bytes registers are handled automatically. + Read-modify-write is handled automatically. License: Revised BSD License, see LICENSE.TXT file include in the project Maintainer: Sylvain Miermont @@ -20,47 +20,35 @@ Maintainer: Sylvain Miermont /* -------------------------------------------------------------------------- */ /* --- DEPENDANCIES --------------------------------------------------------- */ -#include /* C99 types */ -#include /* bool type */ -#include /* printf fprintf */ +#include /* C99 types */ +#include /* bool type */ +#include /* printf fprintf */ #include "loragw_spi.h" #include "loragw_reg.h" +#include "loragw_fpga.h" /* -------------------------------------------------------------------------- */ /* --- PRIVATE MACROS ------------------------------------------------------- */ #define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) #if DEBUG_REG == 1 - #define DEBUG_MSG(str) fprintf(stderr, str) - #define DEBUG_PRINTF(fmt, args...) fprintf(stderr,"%s:%d: "fmt, __FUNCTION__, __LINE__, args) - #define CHECK_NULL(a) if(a==NULL){fprintf(stderr,"%s:%d: ERROR: NULL POINTER AS ARGUMENT\n", __FUNCTION__, __LINE__);return LGW_REG_ERROR;} + #define DEBUG_MSG(str) fprintf(stderr, str) + #define DEBUG_PRINTF(fmt, args...) fprintf(stderr,"%s:%d: "fmt, __FUNCTION__, __LINE__, args) + #define CHECK_NULL(a) if(a==NULL){fprintf(stderr,"%s:%d: ERROR: NULL POINTER AS ARGUMENT\n", __FUNCTION__, __LINE__);return LGW_REG_ERROR;} #else - #define DEBUG_MSG(str) - #define DEBUG_PRINTF(fmt, args...) - #define CHECK_NULL(a) if(a==NULL){return LGW_REG_ERROR;} + #define DEBUG_MSG(str) + #define DEBUG_PRINTF(fmt, args...) + #define CHECK_NULL(a) if(a==NULL){return LGW_REG_ERROR;} #endif -/* -------------------------------------------------------------------------- */ -/* --- PRIVATE TYPES -------------------------------------------------------- */ - -struct lgw_reg_s { - int8_t page; /*!< page containing the register (-1 for all pages) */ - uint8_t addr; /*!< base address of the register (7 bit) */ - uint8_t offs; /*!< position of the register LSB (between 0 to 7) */ - bool sign; /*!< 1 indicates the register is signed (2 complem.) */ - uint8_t leng; /*!< number of bits in the register */ - bool rdon; /*!< 1 indicates a read-only register */ - int32_t dflt; /*!< register default value */ -}; - /* -------------------------------------------------------------------------- */ /* --- PRIVATE CONSTANTS ---------------------------------------------------- */ -#define PAGE_ADDR 0x00 -#define PAGE_MASK 0x03 +#define PAGE_ADDR 0x00 +#define PAGE_MASK 0x03 -const uint8_t FPGA_VERSION[] = { 18, 19 }; /* several versions supported */ +const uint8_t FPGA_VERSION[] = { 18, 19, 27 }; /* several versions supported */ /* auto generated register mapping for C code : 11-Jul-2013 13:20:40 @@ -69,332 +57,332 @@ this file is autogenerated from registers description 293 registers are defined */ const struct lgw_reg_s loregs[LGW_TOTALREGS] = { - {-1,0,0,0,2,0,0}, /* PAGE_REG */ - {-1,0,7,0,1,0,0}, /* SOFT_RESET */ - {-1,1,0,0,8,1,103}, /* VERSION */ - {-1,2,0,0,16,0,0}, /* RX_DATA_BUF_ADDR */ - {-1,4,0,0,8,0,0}, /* RX_DATA_BUF_DATA */ - {-1,5,0,0,8,0,0}, /* TX_DATA_BUF_ADDR */ - {-1,6,0,0,8,0,0}, /* TX_DATA_BUF_DATA */ - {-1,7,0,0,8,0,0}, /* CAPTURE_RAM_ADDR */ - {-1,8,0,0,8,1,0}, /* CAPTURE_RAM_DATA */ - {-1,9,0,0,8,0,0}, /* MCU_PROM_ADDR */ - {-1,10,0,0,8,0,0}, /* MCU_PROM_DATA */ - {-1,11,0,0,8,0,0}, /* RX_PACKET_DATA_FIFO_NUM_STORED */ - {-1,12,0,0,16,1,0}, /* RX_PACKET_DATA_FIFO_ADDR_POINTER */ - {-1,14,0,0,8,1,0}, /* RX_PACKET_DATA_FIFO_STATUS */ - {-1,15,0,0,8,1,0}, /* RX_PACKET_DATA_FIFO_PAYLOAD_SIZE */ - {-1,16,0,0,1,0,0}, /* MBWSSF_MODEM_ENABLE */ - {-1,16,1,0,1,0,0}, /* CONCENTRATOR_MODEM_ENABLE */ - {-1,16,2,0,1,0,0}, /* FSK_MODEM_ENABLE */ - {-1,16,3,0,1,0,0}, /* GLOBAL_EN */ - {-1,17,0,0,1,0,1}, /* CLK32M_EN */ - {-1,17,1,0,1,0,1}, /* CLKHS_EN */ - {-1,18,0,0,1,0,0}, /* START_BIST0 */ - {-1,18,1,0,1,0,0}, /* START_BIST1 */ - {-1,18,2,0,1,0,0}, /* CLEAR_BIST0 */ - {-1,18,3,0,1,0,0}, /* CLEAR_BIST1 */ - {-1,19,0,0,1,1,0}, /* BIST0_FINISHED */ - {-1,19,1,0,1,1,0}, /* BIST1_FINISHED */ - {-1,20,0,0,1,1,0}, /* MCU_AGC_PROG_RAM_BIST_STATUS */ - {-1,20,1,0,1,1,0}, /* MCU_ARB_PROG_RAM_BIST_STATUS */ - {-1,20,2,0,1,1,0}, /* CAPTURE_RAM_BIST_STATUS */ - {-1,20,3,0,1,1,0}, /* CHAN_FIR_RAM0_BIST_STATUS */ - {-1,20,4,0,1,1,0}, /* CHAN_FIR_RAM1_BIST_STATUS */ - {-1,21,0,0,1,1,0}, /* CORR0_RAM_BIST_STATUS */ - {-1,21,1,0,1,1,0}, /* CORR1_RAM_BIST_STATUS */ - {-1,21,2,0,1,1,0}, /* CORR2_RAM_BIST_STATUS */ - {-1,21,3,0,1,1,0}, /* CORR3_RAM_BIST_STATUS */ - {-1,21,4,0,1,1,0}, /* CORR4_RAM_BIST_STATUS */ - {-1,21,5,0,1,1,0}, /* CORR5_RAM_BIST_STATUS */ - {-1,21,6,0,1,1,0}, /* CORR6_RAM_BIST_STATUS */ - {-1,21,7,0,1,1,0}, /* CORR7_RAM_BIST_STATUS */ - {-1,22,0,0,1,1,0}, /* MODEM0_RAM0_BIST_STATUS */ - {-1,22,1,0,1,1,0}, /* MODEM1_RAM0_BIST_STATUS */ - {-1,22,2,0,1,1,0}, /* MODEM2_RAM0_BIST_STATUS */ - {-1,22,3,0,1,1,0}, /* MODEM3_RAM0_BIST_STATUS */ - {-1,22,4,0,1,1,0}, /* MODEM4_RAM0_BIST_STATUS */ - {-1,22,5,0,1,1,0}, /* MODEM5_RAM0_BIST_STATUS */ - {-1,22,6,0,1,1,0}, /* MODEM6_RAM0_BIST_STATUS */ - {-1,22,7,0,1,1,0}, /* MODEM7_RAM0_BIST_STATUS */ - {-1,23,0,0,1,1,0}, /* MODEM0_RAM1_BIST_STATUS */ - {-1,23,1,0,1,1,0}, /* MODEM1_RAM1_BIST_STATUS */ - {-1,23,2,0,1,1,0}, /* MODEM2_RAM1_BIST_STATUS */ - {-1,23,3,0,1,1,0}, /* MODEM3_RAM1_BIST_STATUS */ - {-1,23,4,0,1,1,0}, /* MODEM4_RAM1_BIST_STATUS */ - {-1,23,5,0,1,1,0}, /* MODEM5_RAM1_BIST_STATUS */ - {-1,23,6,0,1,1,0}, /* MODEM6_RAM1_BIST_STATUS */ - {-1,23,7,0,1,1,0}, /* MODEM7_RAM1_BIST_STATUS */ - {-1,24,0,0,1,1,0}, /* MODEM0_RAM2_BIST_STATUS */ - {-1,24,1,0,1,1,0}, /* MODEM1_RAM2_BIST_STATUS */ - {-1,24,2,0,1,1,0}, /* MODEM2_RAM2_BIST_STATUS */ - {-1,24,3,0,1,1,0}, /* MODEM3_RAM2_BIST_STATUS */ - {-1,24,4,0,1,1,0}, /* MODEM4_RAM2_BIST_STATUS */ - {-1,24,5,0,1,1,0}, /* MODEM5_RAM2_BIST_STATUS */ - {-1,24,6,0,1,1,0}, /* MODEM6_RAM2_BIST_STATUS */ - {-1,24,7,0,1,1,0}, /* MODEM7_RAM2_BIST_STATUS */ - {-1,25,0,0,1,1,0}, /* MODEM_MBWSSF_RAM0_BIST_STATUS */ - {-1,25,1,0,1,1,0}, /* MODEM_MBWSSF_RAM1_BIST_STATUS */ - {-1,25,2,0,1,1,0}, /* MODEM_MBWSSF_RAM2_BIST_STATUS */ - {-1,26,0,0,1,1,0}, /* MCU_AGC_DATA_RAM_BIST0_STATUS */ - {-1,26,1,0,1,1,0}, /* MCU_AGC_DATA_RAM_BIST1_STATUS */ - {-1,26,2,0,1,1,0}, /* MCU_ARB_DATA_RAM_BIST0_STATUS */ - {-1,26,3,0,1,1,0}, /* MCU_ARB_DATA_RAM_BIST1_STATUS */ - {-1,26,4,0,1,1,0}, /* TX_TOP_RAM_BIST0_STATUS */ - {-1,26,5,0,1,1,0}, /* TX_TOP_RAM_BIST1_STATUS */ - {-1,26,6,0,1,1,0}, /* DATA_MNGT_RAM_BIST0_STATUS */ - {-1,26,7,0,1,1,0}, /* DATA_MNGT_RAM_BIST1_STATUS */ - {-1,27,0,0,4,0,0}, /* GPIO_SELECT_INPUT */ - {-1,28,0,0,4,0,0}, /* GPIO_SELECT_OUTPUT */ - {-1,29,0,0,5,0,0}, /* GPIO_MODE */ - {-1,30,0,0,5,1,0}, /* GPIO_PIN_REG_IN */ - {-1,31,0,0,5,0,0}, /* GPIO_PIN_REG_OUT */ - {-1,32,0,0,8,1,0}, /* MCU_AGC_STATUS */ - {-1,125,0,0,8,1,0}, /* MCU_ARB_STATUS */ - {-1,126,0,0,8,1,1}, /* CHIP_ID */ - {-1,127,0,0,1,0,1}, /* EMERGENCY_FORCE_HOST_CTRL */ - {0,33,0,0,1,0,0}, /* RX_INVERT_IQ */ - {0,33,1,0,1,0,1}, /* MODEM_INVERT_IQ */ - {0,33,2,0,1,0,0}, /* MBWSSF_MODEM_INVERT_IQ */ - {0,33,3,0,1,0,0}, /* RX_EDGE_SELECT */ - {0,33,4,0,1,0,0}, /* MISC_RADIO_EN */ - {0,33,5,0,1,0,0}, /* FSK_MODEM_INVERT_IQ */ - {0,34,0,0,4,0,7}, /* FILTER_GAIN */ - {0,35,0,0,8,0,240}, /* RADIO_SELECT */ - {0,36,0,1,13,0,-384}, /* IF_FREQ_0 */ - {0,38,0,1,13,0,-128}, /* IF_FREQ_1 */ - {0,40,0,1,13,0,128}, /* IF_FREQ_2 */ - {0,42,0,1,13,0,384}, /* IF_FREQ_3 */ - {0,44,0,1,13,0,-384}, /* IF_FREQ_4 */ - {0,46,0,1,13,0,-128}, /* IF_FREQ_5 */ - {0,48,0,1,13,0,128}, /* IF_FREQ_6 */ - {0,50,0,1,13,0,384}, /* IF_FREQ_7 */ - {0,52,0,1,13,0,0}, /* IF_FREQ_8 */ - {0,54,0,1,13,0,0}, /* IF_FREQ_9 */ - {0,64,0,0,1,0,0}, /* CHANN_OVERRIDE_AGC_GAIN */ - {0,64,1,0,4,0,7}, /* CHANN_AGC_GAIN */ - {0,65,0,0,7,0,0}, /* CORR0_DETECT_EN */ - {0,66,0,0,7,0,0}, /* CORR1_DETECT_EN */ - {0,67,0,0,7,0,0}, /* CORR2_DETECT_EN */ - {0,68,0,0,7,0,0}, /* CORR3_DETECT_EN */ - {0,69,0,0,7,0,0}, /* CORR4_DETECT_EN */ - {0,70,0,0,7,0,0}, /* CORR5_DETECT_EN */ - {0,71,0,0,7,0,0}, /* CORR6_DETECT_EN */ - {0,72,0,0,7,0,0}, /* CORR7_DETECT_EN */ - {0,73,0,0,1,0,0}, /* CORR_SAME_PEAKS_OPTION_SF6 */ - {0,73,1,0,1,0,1}, /* CORR_SAME_PEAKS_OPTION_SF7 */ - {0,73,2,0,1,0,1}, /* CORR_SAME_PEAKS_OPTION_SF8 */ - {0,73,3,0,1,0,1}, /* CORR_SAME_PEAKS_OPTION_SF9 */ - {0,73,4,0,1,0,1}, /* CORR_SAME_PEAKS_OPTION_SF10 */ - {0,73,5,0,1,0,1}, /* CORR_SAME_PEAKS_OPTION_SF11 */ - {0,73,6,0,1,0,1}, /* CORR_SAME_PEAKS_OPTION_SF12 */ - {0,74,0,0,4,0,4}, /* CORR_SIG_NOISE_RATIO_SF6 */ - {0,74,4,0,4,0,4}, /* CORR_SIG_NOISE_RATIO_SF7 */ - {0,75,0,0,4,0,4}, /* CORR_SIG_NOISE_RATIO_SF8 */ - {0,75,4,0,4,0,4}, /* CORR_SIG_NOISE_RATIO_SF9 */ - {0,76,0,0,4,0,4}, /* CORR_SIG_NOISE_RATIO_SF10 */ - {0,76,4,0,4,0,4}, /* CORR_SIG_NOISE_RATIO_SF11 */ - {0,77,0,0,4,0,4}, /* CORR_SIG_NOISE_RATIO_SF12 */ - {0,78,0,0,4,0,4}, /* CORR_NUM_SAME_PEAK */ - {0,78,4,0,3,0,5}, /* CORR_MAC_GAIN */ - {0,81,0,0,12,0,0}, /* ADJUST_MODEM_START_OFFSET_RDX4 */ - {0,83,0,0,12,0,4092}, /* ADJUST_MODEM_START_OFFSET_SF12_RDX4 */ - {0,85,0,0,8,0,7}, /* DBG_CORR_SELECT_SF */ - {0,86,0,0,8,0,0}, /* DBG_CORR_SELECT_CHANNEL */ - {0,87,0,0,8,1,0}, /* DBG_DETECT_CPT */ - {0,88,0,0,8,1,0}, /* DBG_SYMB_CPT */ - {0,89,0,0,1,0,1}, /* CHIRP_INVERT_RX */ - {0,89,1,0,1,0,1}, /* DC_NOTCH_EN */ - {0,90,0,0,1,0,0}, /* IMPLICIT_CRC_EN */ - {0,90,1,0,3,0,0}, /* IMPLICIT_CODING_RATE */ - {0,91,0,0,8,0,0}, /* IMPLICIT_PAYLOAD_LENGHT */ - {0,92,0,0,8,0,29}, /* FREQ_TO_TIME_INVERT */ - {0,93,0,0,6,0,9}, /* FREQ_TO_TIME_DRIFT */ - {0,94,0,0,2,0,2}, /* PAYLOAD_FINE_TIMING_GAIN */ - {0,94,2,0,2,0,1}, /* PREAMBLE_FINE_TIMING_GAIN */ - {0,94,4,0,2,0,0}, /* TRACKING_INTEGRAL */ - {0,95,0,0,4,0,1}, /* FRAME_SYNCH_PEAK1_POS */ - {0,95,4,0,4,0,2}, /* FRAME_SYNCH_PEAK2_POS */ - {0,96,0,0,16,0,10}, /* PREAMBLE_SYMB1_NB */ - {0,98,0,0,1,0,1}, /* FRAME_SYNCH_GAIN */ - {0,98,1,0,1,0,1}, /* SYNCH_DETECT_TH */ - {0,99,0,0,4,0,8}, /* LLR_SCALE */ - {0,99,4,0,2,0,2}, /* SNR_AVG_CST */ - {0,100,0,0,7,0,0}, /* PPM_OFFSET */ - {0,101,0,0,8,0,255}, /* MAX_PAYLOAD_LEN */ - {0,102,0,0,1,0,1}, /* ONLY_CRC_EN */ - {0,103,0,0,8,0,0}, /* ZERO_PAD */ - {0,104,0,0,4,0,8}, /* DEC_GAIN_OFFSET */ - {0,104,4,0,4,0,7}, /* CHAN_GAIN_OFFSET */ - {0,105,1,0,1,0,1}, /* FORCE_HOST_RADIO_CTRL */ - {0,105,2,0,1,0,1}, /* FORCE_HOST_FE_CTRL */ - {0,105,3,0,1,0,1}, /* FORCE_DEC_FILTER_GAIN */ - {0,106,0,0,1,0,1}, /* MCU_RST_0 */ - {0,106,1,0,1,0,1}, /* MCU_RST_1 */ - {0,106,2,0,1,0,0}, /* MCU_SELECT_MUX_0 */ - {0,106,3,0,1,0,0}, /* MCU_SELECT_MUX_1 */ - {0,106,4,0,1,1,0}, /* MCU_CORRUPTION_DETECTED_0 */ - {0,106,5,0,1,1,0}, /* MCU_CORRUPTION_DETECTED_1 */ - {0,106,6,0,1,0,0}, /* MCU_SELECT_EDGE_0 */ - {0,106,7,0,1,0,0}, /* MCU_SELECT_EDGE_1 */ - {0,107,0,0,8,0,1}, /* CHANN_SELECT_RSSI */ - {0,108,0,0,8,0,32}, /* RSSI_BB_DEFAULT_VALUE */ - {0,109,0,0,8,0,100}, /* RSSI_DEC_DEFAULT_VALUE */ - {0,110,0,0,8,0,100}, /* RSSI_CHANN_DEFAULT_VALUE */ - {0,111,0,0,5,0,7}, /* RSSI_BB_FILTER_ALPHA */ - {0,112,0,0,5,0,5}, /* RSSI_DEC_FILTER_ALPHA */ - {0,113,0,0,5,0,8}, /* RSSI_CHANN_FILTER_ALPHA */ - {0,114,0,0,6,0,0}, /* IQ_MISMATCH_A_AMP_COEFF */ - {0,115,0,0,6,0,0}, /* IQ_MISMATCH_A_PHI_COEFF */ - {0,116,0,0,6,0,0}, /* IQ_MISMATCH_B_AMP_COEFF */ - {0,116,6,0,1,0,0}, /* IQ_MISMATCH_B_SEL_I */ - {0,117,0,0,6,0,0}, /* IQ_MISMATCH_B_PHI_COEFF */ - {1,33,0,0,1,0,0}, /* TX_TRIG_IMMEDIATE */ - {1,33,1,0,1,0,0}, /* TX_TRIG_DELAYED */ - {1,33,2,0,1,0,0}, /* TX_TRIG_GPS */ - {1,34,0,0,16,0,0}, /* TX_START_DELAY */ - {1,36,0,0,4,0,1}, /* TX_FRAME_SYNCH_PEAK1_POS */ - {1,36,4,0,4,0,2}, /* TX_FRAME_SYNCH_PEAK2_POS */ - {1,37,0,0,3,0,0}, /* TX_RAMP_DURATION */ - {1,39,0,1,8,0,0}, /* TX_OFFSET_I */ - {1,40,0,1,8,0,0}, /* TX_OFFSET_Q */ - {1,41,0,0,1,0,0}, /* TX_MODE */ - {1,41,1,0,4,0,0}, /* TX_ZERO_PAD */ - {1,41,5,0,1,0,0}, /* TX_EDGE_SELECT */ - {1,41,6,0,1,0,0}, /* TX_EDGE_SELECT_TOP */ - {1,42,0,0,2,0,0}, /* TX_GAIN */ - {1,42,2,0,3,0,5}, /* TX_CHIRP_LOW_PASS */ - {1,42,5,0,2,0,0}, /* TX_FCC_WIDEBAND */ - {1,42,7,0,1,0,1}, /* TX_SWAP_IQ */ - {1,43,0,0,1,0,0}, /* MBWSSF_IMPLICIT_HEADER */ - {1,43,1,0,1,0,0}, /* MBWSSF_IMPLICIT_CRC_EN */ - {1,43,2,0,3,0,0}, /* MBWSSF_IMPLICIT_CODING_RATE */ - {1,44,0,0,8,0,0}, /* MBWSSF_IMPLICIT_PAYLOAD_LENGHT */ - {1,45,0,0,1,0,1}, /* MBWSSF_AGC_FREEZE_ON_DETECT */ - {1,46,0,0,4,0,1}, /* MBWSSF_FRAME_SYNCH_PEAK1_POS */ - {1,46,4,0,4,0,2}, /* MBWSSF_FRAME_SYNCH_PEAK2_POS */ - {1,47,0,0,16,0,10}, /* MBWSSF_PREAMBLE_SYMB1_NB */ - {1,49,0,0,1,0,1}, /* MBWSSF_FRAME_SYNCH_GAIN */ - {1,49,1,0,1,0,1}, /* MBWSSF_SYNCH_DETECT_TH */ - {1,50,0,0,8,0,10}, /* MBWSSF_DETECT_MIN_SINGLE_PEAK */ - {1,51,0,0,3,0,3}, /* MBWSSF_DETECT_TRIG_SAME_PEAK_NB */ - {1,52,0,0,8,0,29}, /* MBWSSF_FREQ_TO_TIME_INVERT */ - {1,53,0,0,6,0,36}, /* MBWSSF_FREQ_TO_TIME_DRIFT */ - {1,54,0,0,12,0,0}, /* MBWSSF_PPM_CORRECTION */ - {1,56,0,0,2,0,2}, /* MBWSSF_PAYLOAD_FINE_TIMING_GAIN */ - {1,56,2,0,2,0,1}, /* MBWSSF_PREAMBLE_FINE_TIMING_GAIN */ - {1,56,4,0,2,0,0}, /* MBWSSF_TRACKING_INTEGRAL */ - {1,57,0,0,8,0,0}, /* MBWSSF_ZERO_PAD */ - {1,58,0,0,2,0,0}, /* MBWSSF_MODEM_BW */ - {1,58,2,0,1,0,0}, /* MBWSSF_RADIO_SELECT */ - {1,58,3,0,1,0,1}, /* MBWSSF_RX_CHIRP_INVERT */ - {1,59,0,0,4,0,8}, /* MBWSSF_LLR_SCALE */ - {1,59,4,0,2,0,3}, /* MBWSSF_SNR_AVG_CST */ - {1,59,6,0,1,0,0}, /* MBWSSF_PPM_OFFSET */ - {1,60,0,0,4,0,7}, /* MBWSSF_RATE_SF */ - {1,60,4,0,1,0,1}, /* MBWSSF_ONLY_CRC_EN */ - {1,61,0,0,8,0,255}, /* MBWSSF_MAX_PAYLOAD_LEN */ - {1,62,0,0,8,1,128}, /* TX_STATUS */ - {1,63,0,0,3,0,0}, /* FSK_CH_BW_EXPO */ - {1,63,3,0,3,0,0}, /* FSK_RSSI_LENGTH */ - {1,63,6,0,1,0,0}, /* FSK_RX_INVERT */ - {1,63,7,0,1,0,0}, /* FSK_PKT_MODE */ - {1,64,0,0,3,0,0}, /* FSK_PSIZE */ - {1,64,3,0,1,0,0}, /* FSK_CRC_EN */ - {1,64,4,0,2,0,0}, /* FSK_DCFREE_ENC */ - {1,64,6,0,1,0,0}, /* FSK_CRC_IBM */ - {1,65,0,0,5,0,0}, /* FSK_ERROR_OSR_TOL */ - {1,65,7,0,1,0,0}, /* FSK_RADIO_SELECT */ - {1,66,0,0,16,0,0}, /* FSK_BR_RATIO */ - {1,68,0,0,32,0,0}, /* FSK_REF_PATTERN_LSB */ - {1,72,0,0,32,0,0}, /* FSK_REF_PATTERN_MSB */ - {1,76,0,0,8,0,0}, /* FSK_PKT_LENGTH */ - {1,77,0,0,1,0,1}, /* FSK_TX_GAUSSIAN_EN */ - {1,77,1,0,2,0,0}, /* FSK_TX_GAUSSIAN_SELECT_BT */ - {1,77,3,0,1,0,1}, /* FSK_TX_PATTERN_EN */ - {1,77,4,0,1,0,0}, /* FSK_TX_PREAMBLE_SEQ */ - {1,77,5,0,3,0,0}, /* FSK_TX_PSIZE */ - {1,80,0,0,8,0,0}, /* FSK_NODE_ADRS */ - {1,81,0,0,8,0,0}, /* FSK_BROADCAST */ - {1,82,0,0,1,0,1}, /* FSK_AUTO_AFC_ON */ - {1,83,0,0,10,0,0}, /* FSK_PATTERN_TIMEOUT_CFG */ - {2,33,0,0,8,0,0}, /* SPI_RADIO_A__DATA */ - {2,34,0,0,8,1,0}, /* SPI_RADIO_A__DATA_READBACK */ - {2,35,0,0,8,0,0}, /* SPI_RADIO_A__ADDR */ - {2,37,0,0,1,0,0}, /* SPI_RADIO_A__CS */ - {2,38,0,0,8,0,0}, /* SPI_RADIO_B__DATA */ - {2,39,0,0,8,1,0}, /* SPI_RADIO_B__DATA_READBACK */ - {2,40,0,0,8,0,0}, /* SPI_RADIO_B__ADDR */ - {2,42,0,0,1,0,0}, /* SPI_RADIO_B__CS */ - {2,43,0,0,1,0,0}, /* RADIO_A_EN */ - {2,43,1,0,1,0,0}, /* RADIO_B_EN */ - {2,43,2,0,1,0,1}, /* RADIO_RST */ - {2,43,3,0,1,0,0}, /* LNA_A_EN */ - {2,43,4,0,1,0,0}, /* PA_A_EN */ - {2,43,5,0,1,0,0}, /* LNA_B_EN */ - {2,43,6,0,1,0,0}, /* PA_B_EN */ - {2,44,0,0,2,0,0}, /* PA_GAIN */ - {2,45,0,0,4,0,2}, /* LNA_A_CTRL_LUT */ - {2,45,4,0,4,0,4}, /* PA_A_CTRL_LUT */ - {2,46,0,0,4,0,2}, /* LNA_B_CTRL_LUT */ - {2,46,4,0,4,0,4}, /* PA_B_CTRL_LUT */ - {2,47,0,0,5,0,0}, /* CAPTURE_SOURCE */ - {2,47,5,0,1,0,0}, /* CAPTURE_START */ - {2,47,6,0,1,0,0}, /* CAPTURE_FORCE_TRIGGER */ - {2,47,7,0,1,0,0}, /* CAPTURE_WRAP */ - {2,48,0,0,16,0,0}, /* CAPTURE_PERIOD */ - {2,51,0,0,8,1,0}, /* MODEM_STATUS */ - {2,52,0,0,8,1,0}, /* VALID_HEADER_COUNTER_0 */ - {2,54,0,0,8,1,0}, /* VALID_PACKET_COUNTER_0 */ - {2,56,0,0,8,1,0}, /* VALID_HEADER_COUNTER_MBWSSF */ - {2,57,0,0,8,1,0}, /* VALID_HEADER_COUNTER_FSK */ - {2,58,0,0,8,1,0}, /* VALID_PACKET_COUNTER_MBWSSF */ - {2,59,0,0,8,1,0}, /* VALID_PACKET_COUNTER_FSK */ - {2,60,0,0,8,1,0}, /* CHANN_RSSI */ - {2,61,0,0,8,1,0}, /* BB_RSSI */ - {2,62,0,0,8,1,0}, /* DEC_RSSI */ - {2,63,0,0,8,1,0}, /* DBG_MCU_DATA */ - {2,64,0,0,8,1,0}, /* DBG_ARB_MCU_RAM_DATA */ - {2,65,0,0,8,1,0}, /* DBG_AGC_MCU_RAM_DATA */ - {2,66,0,0,16,1,0}, /* NEXT_PACKET_CNT */ - {2,68,0,0,16,1,0}, /* ADDR_CAPTURE_COUNT */ - {2,70,0,0,32,1,0}, /* TIMESTAMP */ - {2,74,0,0,4,1,0}, /* DBG_CHANN0_GAIN */ - {2,74,4,0,4,1,0}, /* DBG_CHANN1_GAIN */ - {2,75,0,0,4,1,0}, /* DBG_CHANN2_GAIN */ - {2,75,4,0,4,1,0}, /* DBG_CHANN3_GAIN */ - {2,76,0,0,4,1,0}, /* DBG_CHANN4_GAIN */ - {2,76,4,0,4,1,0}, /* DBG_CHANN5_GAIN */ - {2,77,0,0,4,1,0}, /* DBG_CHANN6_GAIN */ - {2,77,4,0,4,1,0}, /* DBG_CHANN7_GAIN */ - {2,78,0,0,4,1,0}, /* DBG_DEC_FILT_GAIN */ - {2,79,0,0,3,1,0}, /* SPI_DATA_FIFO_PTR */ - {2,79,3,0,3,1,0}, /* PACKET_DATA_FIFO_PTR */ - {2,80,0,0,8,0,0}, /* DBG_ARB_MCU_RAM_ADDR */ - {2,81,0,0,8,0,0}, /* DBG_AGC_MCU_RAM_ADDR */ - {2,82,0,0,1,0,0}, /* SPI_MASTER_CHIP_SELECT_POLARITY */ - {2,82,1,0,1,0,0}, /* SPI_MASTER_CPOL */ - {2,82,2,0,1,0,0}, /* SPI_MASTER_CPHA */ - {2,83,0,0,1,0,0}, /* SIG_GEN_ANALYSER_MUX_SEL */ - {2,84,0,0,1,0,0}, /* SIG_GEN_EN */ - {2,84,1,0,1,0,0}, /* SIG_ANALYSER_EN */ - {2,84,2,0,2,0,0}, /* SIG_ANALYSER_AVG_LEN */ - {2,84,4,0,3,0,0}, /* SIG_ANALYSER_PRECISION */ - {2,84,7,0,1,1,0}, /* SIG_ANALYSER_VALID_OUT */ - {2,85,0,0,8,0,0}, /* SIG_GEN_FREQ */ - {2,86,0,0,8,0,0}, /* SIG_ANALYSER_FREQ */ - {2,87,0,0,8,1,0}, /* SIG_ANALYSER_I_OUT */ - {2,88,0,0,8,1,0}, /* SIG_ANALYSER_Q_OUT */ - {2,89,0,0,1,0,0}, /* GPS_EN */ - {2,89,1,0,1,0,1}, /* GPS_POL */ - {2,90,0,1,8,0,0}, /* SW_TEST_REG1 */ - {2,91,2,1,6,0,0}, /* SW_TEST_REG2 */ - {2,92,0,1,16,0,0}, /* SW_TEST_REG3 */ - {2,94,0,0,4,1,0}, /* DATA_MNGT_STATUS */ - {2,95,0,0,5,1,0}, /* DATA_MNGT_CPT_FRAME_ALLOCATED */ - {2,96,0,0,5,1,0}, /* DATA_MNGT_CPT_FRAME_FINISHED */ - {2,97,0,0,5,1,0}, /* DATA_MNGT_CPT_FRAME_READEN */ - {1,33,0,0,8,0,0} /* TX_TRIG_ALL (alias) */ + {-1,0,0,0,2,0,0}, /* PAGE_REG */ + {-1,0,7,0,1,0,0}, /* SOFT_RESET */ + {-1,1,0,0,8,1,103}, /* VERSION */ + {-1,2,0,0,16,0,0}, /* RX_DATA_BUF_ADDR */ + {-1,4,0,0,8,0,0}, /* RX_DATA_BUF_DATA */ + {-1,5,0,0,8,0,0}, /* TX_DATA_BUF_ADDR */ + {-1,6,0,0,8,0,0}, /* TX_DATA_BUF_DATA */ + {-1,7,0,0,8,0,0}, /* CAPTURE_RAM_ADDR */ + {-1,8,0,0,8,1,0}, /* CAPTURE_RAM_DATA */ + {-1,9,0,0,8,0,0}, /* MCU_PROM_ADDR */ + {-1,10,0,0,8,0,0}, /* MCU_PROM_DATA */ + {-1,11,0,0,8,0,0}, /* RX_PACKET_DATA_FIFO_NUM_STORED */ + {-1,12,0,0,16,1,0}, /* RX_PACKET_DATA_FIFO_ADDR_POINTER */ + {-1,14,0,0,8,1,0}, /* RX_PACKET_DATA_FIFO_STATUS */ + {-1,15,0,0,8,1,0}, /* RX_PACKET_DATA_FIFO_PAYLOAD_SIZE */ + {-1,16,0,0,1,0,0}, /* MBWSSF_MODEM_ENABLE */ + {-1,16,1,0,1,0,0}, /* CONCENTRATOR_MODEM_ENABLE */ + {-1,16,2,0,1,0,0}, /* FSK_MODEM_ENABLE */ + {-1,16,3,0,1,0,0}, /* GLOBAL_EN */ + {-1,17,0,0,1,0,1}, /* CLK32M_EN */ + {-1,17,1,0,1,0,1}, /* CLKHS_EN */ + {-1,18,0,0,1,0,0}, /* START_BIST0 */ + {-1,18,1,0,1,0,0}, /* START_BIST1 */ + {-1,18,2,0,1,0,0}, /* CLEAR_BIST0 */ + {-1,18,3,0,1,0,0}, /* CLEAR_BIST1 */ + {-1,19,0,0,1,1,0}, /* BIST0_FINISHED */ + {-1,19,1,0,1,1,0}, /* BIST1_FINISHED */ + {-1,20,0,0,1,1,0}, /* MCU_AGC_PROG_RAM_BIST_STATUS */ + {-1,20,1,0,1,1,0}, /* MCU_ARB_PROG_RAM_BIST_STATUS */ + {-1,20,2,0,1,1,0}, /* CAPTURE_RAM_BIST_STATUS */ + {-1,20,3,0,1,1,0}, /* CHAN_FIR_RAM0_BIST_STATUS */ + {-1,20,4,0,1,1,0}, /* CHAN_FIR_RAM1_BIST_STATUS */ + {-1,21,0,0,1,1,0}, /* CORR0_RAM_BIST_STATUS */ + {-1,21,1,0,1,1,0}, /* CORR1_RAM_BIST_STATUS */ + {-1,21,2,0,1,1,0}, /* CORR2_RAM_BIST_STATUS */ + {-1,21,3,0,1,1,0}, /* CORR3_RAM_BIST_STATUS */ + {-1,21,4,0,1,1,0}, /* CORR4_RAM_BIST_STATUS */ + {-1,21,5,0,1,1,0}, /* CORR5_RAM_BIST_STATUS */ + {-1,21,6,0,1,1,0}, /* CORR6_RAM_BIST_STATUS */ + {-1,21,7,0,1,1,0}, /* CORR7_RAM_BIST_STATUS */ + {-1,22,0,0,1,1,0}, /* MODEM0_RAM0_BIST_STATUS */ + {-1,22,1,0,1,1,0}, /* MODEM1_RAM0_BIST_STATUS */ + {-1,22,2,0,1,1,0}, /* MODEM2_RAM0_BIST_STATUS */ + {-1,22,3,0,1,1,0}, /* MODEM3_RAM0_BIST_STATUS */ + {-1,22,4,0,1,1,0}, /* MODEM4_RAM0_BIST_STATUS */ + {-1,22,5,0,1,1,0}, /* MODEM5_RAM0_BIST_STATUS */ + {-1,22,6,0,1,1,0}, /* MODEM6_RAM0_BIST_STATUS */ + {-1,22,7,0,1,1,0}, /* MODEM7_RAM0_BIST_STATUS */ + {-1,23,0,0,1,1,0}, /* MODEM0_RAM1_BIST_STATUS */ + {-1,23,1,0,1,1,0}, /* MODEM1_RAM1_BIST_STATUS */ + {-1,23,2,0,1,1,0}, /* MODEM2_RAM1_BIST_STATUS */ + {-1,23,3,0,1,1,0}, /* MODEM3_RAM1_BIST_STATUS */ + {-1,23,4,0,1,1,0}, /* MODEM4_RAM1_BIST_STATUS */ + {-1,23,5,0,1,1,0}, /* MODEM5_RAM1_BIST_STATUS */ + {-1,23,6,0,1,1,0}, /* MODEM6_RAM1_BIST_STATUS */ + {-1,23,7,0,1,1,0}, /* MODEM7_RAM1_BIST_STATUS */ + {-1,24,0,0,1,1,0}, /* MODEM0_RAM2_BIST_STATUS */ + {-1,24,1,0,1,1,0}, /* MODEM1_RAM2_BIST_STATUS */ + {-1,24,2,0,1,1,0}, /* MODEM2_RAM2_BIST_STATUS */ + {-1,24,3,0,1,1,0}, /* MODEM3_RAM2_BIST_STATUS */ + {-1,24,4,0,1,1,0}, /* MODEM4_RAM2_BIST_STATUS */ + {-1,24,5,0,1,1,0}, /* MODEM5_RAM2_BIST_STATUS */ + {-1,24,6,0,1,1,0}, /* MODEM6_RAM2_BIST_STATUS */ + {-1,24,7,0,1,1,0}, /* MODEM7_RAM2_BIST_STATUS */ + {-1,25,0,0,1,1,0}, /* MODEM_MBWSSF_RAM0_BIST_STATUS */ + {-1,25,1,0,1,1,0}, /* MODEM_MBWSSF_RAM1_BIST_STATUS */ + {-1,25,2,0,1,1,0}, /* MODEM_MBWSSF_RAM2_BIST_STATUS */ + {-1,26,0,0,1,1,0}, /* MCU_AGC_DATA_RAM_BIST0_STATUS */ + {-1,26,1,0,1,1,0}, /* MCU_AGC_DATA_RAM_BIST1_STATUS */ + {-1,26,2,0,1,1,0}, /* MCU_ARB_DATA_RAM_BIST0_STATUS */ + {-1,26,3,0,1,1,0}, /* MCU_ARB_DATA_RAM_BIST1_STATUS */ + {-1,26,4,0,1,1,0}, /* TX_TOP_RAM_BIST0_STATUS */ + {-1,26,5,0,1,1,0}, /* TX_TOP_RAM_BIST1_STATUS */ + {-1,26,6,0,1,1,0}, /* DATA_MNGT_RAM_BIST0_STATUS */ + {-1,26,7,0,1,1,0}, /* DATA_MNGT_RAM_BIST1_STATUS */ + {-1,27,0,0,4,0,0}, /* GPIO_SELECT_INPUT */ + {-1,28,0,0,4,0,0}, /* GPIO_SELECT_OUTPUT */ + {-1,29,0,0,5,0,0}, /* GPIO_MODE */ + {-1,30,0,0,5,1,0}, /* GPIO_PIN_REG_IN */ + {-1,31,0,0,5,0,0}, /* GPIO_PIN_REG_OUT */ + {-1,32,0,0,8,1,0}, /* MCU_AGC_STATUS */ + {-1,125,0,0,8,1,0}, /* MCU_ARB_STATUS */ + {-1,126,0,0,8,1,1}, /* CHIP_ID */ + {-1,127,0,0,1,0,1}, /* EMERGENCY_FORCE_HOST_CTRL */ + {0,33,0,0,1,0,0}, /* RX_INVERT_IQ */ + {0,33,1,0,1,0,1}, /* MODEM_INVERT_IQ */ + {0,33,2,0,1,0,0}, /* MBWSSF_MODEM_INVERT_IQ */ + {0,33,3,0,1,0,0}, /* RX_EDGE_SELECT */ + {0,33,4,0,1,0,0}, /* MISC_RADIO_EN */ + {0,33,5,0,1,0,0}, /* FSK_MODEM_INVERT_IQ */ + {0,34,0,0,4,0,7}, /* FILTER_GAIN */ + {0,35,0,0,8,0,240}, /* RADIO_SELECT */ + {0,36,0,1,13,0,-384}, /* IF_FREQ_0 */ + {0,38,0,1,13,0,-128}, /* IF_FREQ_1 */ + {0,40,0,1,13,0,128}, /* IF_FREQ_2 */ + {0,42,0,1,13,0,384}, /* IF_FREQ_3 */ + {0,44,0,1,13,0,-384}, /* IF_FREQ_4 */ + {0,46,0,1,13,0,-128}, /* IF_FREQ_5 */ + {0,48,0,1,13,0,128}, /* IF_FREQ_6 */ + {0,50,0,1,13,0,384}, /* IF_FREQ_7 */ + {0,52,0,1,13,0,0}, /* IF_FREQ_8 */ + {0,54,0,1,13,0,0}, /* IF_FREQ_9 */ + {0,64,0,0,1,0,0}, /* CHANN_OVERRIDE_AGC_GAIN */ + {0,64,1,0,4,0,7}, /* CHANN_AGC_GAIN */ + {0,65,0,0,7,0,0}, /* CORR0_DETECT_EN */ + {0,66,0,0,7,0,0}, /* CORR1_DETECT_EN */ + {0,67,0,0,7,0,0}, /* CORR2_DETECT_EN */ + {0,68,0,0,7,0,0}, /* CORR3_DETECT_EN */ + {0,69,0,0,7,0,0}, /* CORR4_DETECT_EN */ + {0,70,0,0,7,0,0}, /* CORR5_DETECT_EN */ + {0,71,0,0,7,0,0}, /* CORR6_DETECT_EN */ + {0,72,0,0,7,0,0}, /* CORR7_DETECT_EN */ + {0,73,0,0,1,0,0}, /* CORR_SAME_PEAKS_OPTION_SF6 */ + {0,73,1,0,1,0,1}, /* CORR_SAME_PEAKS_OPTION_SF7 */ + {0,73,2,0,1,0,1}, /* CORR_SAME_PEAKS_OPTION_SF8 */ + {0,73,3,0,1,0,1}, /* CORR_SAME_PEAKS_OPTION_SF9 */ + {0,73,4,0,1,0,1}, /* CORR_SAME_PEAKS_OPTION_SF10 */ + {0,73,5,0,1,0,1}, /* CORR_SAME_PEAKS_OPTION_SF11 */ + {0,73,6,0,1,0,1}, /* CORR_SAME_PEAKS_OPTION_SF12 */ + {0,74,0,0,4,0,4}, /* CORR_SIG_NOISE_RATIO_SF6 */ + {0,74,4,0,4,0,4}, /* CORR_SIG_NOISE_RATIO_SF7 */ + {0,75,0,0,4,0,4}, /* CORR_SIG_NOISE_RATIO_SF8 */ + {0,75,4,0,4,0,4}, /* CORR_SIG_NOISE_RATIO_SF9 */ + {0,76,0,0,4,0,4}, /* CORR_SIG_NOISE_RATIO_SF10 */ + {0,76,4,0,4,0,4}, /* CORR_SIG_NOISE_RATIO_SF11 */ + {0,77,0,0,4,0,4}, /* CORR_SIG_NOISE_RATIO_SF12 */ + {0,78,0,0,4,0,4}, /* CORR_NUM_SAME_PEAK */ + {0,78,4,0,3,0,5}, /* CORR_MAC_GAIN */ + {0,81,0,0,12,0,0}, /* ADJUST_MODEM_START_OFFSET_RDX4 */ + {0,83,0,0,12,0,4092}, /* ADJUST_MODEM_START_OFFSET_SF12_RDX4 */ + {0,85,0,0,8,0,7}, /* DBG_CORR_SELECT_SF */ + {0,86,0,0,8,0,0}, /* DBG_CORR_SELECT_CHANNEL */ + {0,87,0,0,8,1,0}, /* DBG_DETECT_CPT */ + {0,88,0,0,8,1,0}, /* DBG_SYMB_CPT */ + {0,89,0,0,1,0,1}, /* CHIRP_INVERT_RX */ + {0,89,1,0,1,0,1}, /* DC_NOTCH_EN */ + {0,90,0,0,1,0,0}, /* IMPLICIT_CRC_EN */ + {0,90,1,0,3,0,0}, /* IMPLICIT_CODING_RATE */ + {0,91,0,0,8,0,0}, /* IMPLICIT_PAYLOAD_LENGHT */ + {0,92,0,0,8,0,29}, /* FREQ_TO_TIME_INVERT */ + {0,93,0,0,6,0,9}, /* FREQ_TO_TIME_DRIFT */ + {0,94,0,0,2,0,2}, /* PAYLOAD_FINE_TIMING_GAIN */ + {0,94,2,0,2,0,1}, /* PREAMBLE_FINE_TIMING_GAIN */ + {0,94,4,0,2,0,0}, /* TRACKING_INTEGRAL */ + {0,95,0,0,4,0,1}, /* FRAME_SYNCH_PEAK1_POS */ + {0,95,4,0,4,0,2}, /* FRAME_SYNCH_PEAK2_POS */ + {0,96,0,0,16,0,10}, /* PREAMBLE_SYMB1_NB */ + {0,98,0,0,1,0,1}, /* FRAME_SYNCH_GAIN */ + {0,98,1,0,1,0,1}, /* SYNCH_DETECT_TH */ + {0,99,0,0,4,0,8}, /* LLR_SCALE */ + {0,99,4,0,2,0,2}, /* SNR_AVG_CST */ + {0,100,0,0,7,0,0}, /* PPM_OFFSET */ + {0,101,0,0,8,0,255}, /* MAX_PAYLOAD_LEN */ + {0,102,0,0,1,0,1}, /* ONLY_CRC_EN */ + {0,103,0,0,8,0,0}, /* ZERO_PAD */ + {0,104,0,0,4,0,8}, /* DEC_GAIN_OFFSET */ + {0,104,4,0,4,0,7}, /* CHAN_GAIN_OFFSET */ + {0,105,1,0,1,0,1}, /* FORCE_HOST_RADIO_CTRL */ + {0,105,2,0,1,0,1}, /* FORCE_HOST_FE_CTRL */ + {0,105,3,0,1,0,1}, /* FORCE_DEC_FILTER_GAIN */ + {0,106,0,0,1,0,1}, /* MCU_RST_0 */ + {0,106,1,0,1,0,1}, /* MCU_RST_1 */ + {0,106,2,0,1,0,0}, /* MCU_SELECT_MUX_0 */ + {0,106,3,0,1,0,0}, /* MCU_SELECT_MUX_1 */ + {0,106,4,0,1,1,0}, /* MCU_CORRUPTION_DETECTED_0 */ + {0,106,5,0,1,1,0}, /* MCU_CORRUPTION_DETECTED_1 */ + {0,106,6,0,1,0,0}, /* MCU_SELECT_EDGE_0 */ + {0,106,7,0,1,0,0}, /* MCU_SELECT_EDGE_1 */ + {0,107,0,0,8,0,1}, /* CHANN_SELECT_RSSI */ + {0,108,0,0,8,0,32}, /* RSSI_BB_DEFAULT_VALUE */ + {0,109,0,0,8,0,100}, /* RSSI_DEC_DEFAULT_VALUE */ + {0,110,0,0,8,0,100}, /* RSSI_CHANN_DEFAULT_VALUE */ + {0,111,0,0,5,0,7}, /* RSSI_BB_FILTER_ALPHA */ + {0,112,0,0,5,0,5}, /* RSSI_DEC_FILTER_ALPHA */ + {0,113,0,0,5,0,8}, /* RSSI_CHANN_FILTER_ALPHA */ + {0,114,0,0,6,0,0}, /* IQ_MISMATCH_A_AMP_COEFF */ + {0,115,0,0,6,0,0}, /* IQ_MISMATCH_A_PHI_COEFF */ + {0,116,0,0,6,0,0}, /* IQ_MISMATCH_B_AMP_COEFF */ + {0,116,6,0,1,0,0}, /* IQ_MISMATCH_B_SEL_I */ + {0,117,0,0,6,0,0}, /* IQ_MISMATCH_B_PHI_COEFF */ + {1,33,0,0,1,0,0}, /* TX_TRIG_IMMEDIATE */ + {1,33,1,0,1,0,0}, /* TX_TRIG_DELAYED */ + {1,33,2,0,1,0,0}, /* TX_TRIG_GPS */ + {1,34,0,0,16,0,0}, /* TX_START_DELAY */ + {1,36,0,0,4,0,1}, /* TX_FRAME_SYNCH_PEAK1_POS */ + {1,36,4,0,4,0,2}, /* TX_FRAME_SYNCH_PEAK2_POS */ + {1,37,0,0,3,0,0}, /* TX_RAMP_DURATION */ + {1,39,0,1,8,0,0}, /* TX_OFFSET_I */ + {1,40,0,1,8,0,0}, /* TX_OFFSET_Q */ + {1,41,0,0,1,0,0}, /* TX_MODE */ + {1,41,1,0,4,0,0}, /* TX_ZERO_PAD */ + {1,41,5,0,1,0,0}, /* TX_EDGE_SELECT */ + {1,41,6,0,1,0,0}, /* TX_EDGE_SELECT_TOP */ + {1,42,0,0,2,0,0}, /* TX_GAIN */ + {1,42,2,0,3,0,5}, /* TX_CHIRP_LOW_PASS */ + {1,42,5,0,2,0,0}, /* TX_FCC_WIDEBAND */ + {1,42,7,0,1,0,1}, /* TX_SWAP_IQ */ + {1,43,0,0,1,0,0}, /* MBWSSF_IMPLICIT_HEADER */ + {1,43,1,0,1,0,0}, /* MBWSSF_IMPLICIT_CRC_EN */ + {1,43,2,0,3,0,0}, /* MBWSSF_IMPLICIT_CODING_RATE */ + {1,44,0,0,8,0,0}, /* MBWSSF_IMPLICIT_PAYLOAD_LENGHT */ + {1,45,0,0,1,0,1}, /* MBWSSF_AGC_FREEZE_ON_DETECT */ + {1,46,0,0,4,0,1}, /* MBWSSF_FRAME_SYNCH_PEAK1_POS */ + {1,46,4,0,4,0,2}, /* MBWSSF_FRAME_SYNCH_PEAK2_POS */ + {1,47,0,0,16,0,10}, /* MBWSSF_PREAMBLE_SYMB1_NB */ + {1,49,0,0,1,0,1}, /* MBWSSF_FRAME_SYNCH_GAIN */ + {1,49,1,0,1,0,1}, /* MBWSSF_SYNCH_DETECT_TH */ + {1,50,0,0,8,0,10}, /* MBWSSF_DETECT_MIN_SINGLE_PEAK */ + {1,51,0,0,3,0,3}, /* MBWSSF_DETECT_TRIG_SAME_PEAK_NB */ + {1,52,0,0,8,0,29}, /* MBWSSF_FREQ_TO_TIME_INVERT */ + {1,53,0,0,6,0,36}, /* MBWSSF_FREQ_TO_TIME_DRIFT */ + {1,54,0,0,12,0,0}, /* MBWSSF_PPM_CORRECTION */ + {1,56,0,0,2,0,2}, /* MBWSSF_PAYLOAD_FINE_TIMING_GAIN */ + {1,56,2,0,2,0,1}, /* MBWSSF_PREAMBLE_FINE_TIMING_GAIN */ + {1,56,4,0,2,0,0}, /* MBWSSF_TRACKING_INTEGRAL */ + {1,57,0,0,8,0,0}, /* MBWSSF_ZERO_PAD */ + {1,58,0,0,2,0,0}, /* MBWSSF_MODEM_BW */ + {1,58,2,0,1,0,0}, /* MBWSSF_RADIO_SELECT */ + {1,58,3,0,1,0,1}, /* MBWSSF_RX_CHIRP_INVERT */ + {1,59,0,0,4,0,8}, /* MBWSSF_LLR_SCALE */ + {1,59,4,0,2,0,3}, /* MBWSSF_SNR_AVG_CST */ + {1,59,6,0,1,0,0}, /* MBWSSF_PPM_OFFSET */ + {1,60,0,0,4,0,7}, /* MBWSSF_RATE_SF */ + {1,60,4,0,1,0,1}, /* MBWSSF_ONLY_CRC_EN */ + {1,61,0,0,8,0,255}, /* MBWSSF_MAX_PAYLOAD_LEN */ + {1,62,0,0,8,1,128}, /* TX_STATUS */ + {1,63,0,0,3,0,0}, /* FSK_CH_BW_EXPO */ + {1,63,3,0,3,0,0}, /* FSK_RSSI_LENGTH */ + {1,63,6,0,1,0,0}, /* FSK_RX_INVERT */ + {1,63,7,0,1,0,0}, /* FSK_PKT_MODE */ + {1,64,0,0,3,0,0}, /* FSK_PSIZE */ + {1,64,3,0,1,0,0}, /* FSK_CRC_EN */ + {1,64,4,0,2,0,0}, /* FSK_DCFREE_ENC */ + {1,64,6,0,1,0,0}, /* FSK_CRC_IBM */ + {1,65,0,0,5,0,0}, /* FSK_ERROR_OSR_TOL */ + {1,65,7,0,1,0,0}, /* FSK_RADIO_SELECT */ + {1,66,0,0,16,0,0}, /* FSK_BR_RATIO */ + {1,68,0,0,32,0,0}, /* FSK_REF_PATTERN_LSB */ + {1,72,0,0,32,0,0}, /* FSK_REF_PATTERN_MSB */ + {1,76,0,0,8,0,0}, /* FSK_PKT_LENGTH */ + {1,77,0,0,1,0,1}, /* FSK_TX_GAUSSIAN_EN */ + {1,77,1,0,2,0,0}, /* FSK_TX_GAUSSIAN_SELECT_BT */ + {1,77,3,0,1,0,1}, /* FSK_TX_PATTERN_EN */ + {1,77,4,0,1,0,0}, /* FSK_TX_PREAMBLE_SEQ */ + {1,77,5,0,3,0,0}, /* FSK_TX_PSIZE */ + {1,80,0,0,8,0,0}, /* FSK_NODE_ADRS */ + {1,81,0,0,8,0,0}, /* FSK_BROADCAST */ + {1,82,0,0,1,0,1}, /* FSK_AUTO_AFC_ON */ + {1,83,0,0,10,0,0}, /* FSK_PATTERN_TIMEOUT_CFG */ + {2,33,0,0,8,0,0}, /* SPI_RADIO_A__DATA */ + {2,34,0,0,8,1,0}, /* SPI_RADIO_A__DATA_READBACK */ + {2,35,0,0,8,0,0}, /* SPI_RADIO_A__ADDR */ + {2,37,0,0,1,0,0}, /* SPI_RADIO_A__CS */ + {2,38,0,0,8,0,0}, /* SPI_RADIO_B__DATA */ + {2,39,0,0,8,1,0}, /* SPI_RADIO_B__DATA_READBACK */ + {2,40,0,0,8,0,0}, /* SPI_RADIO_B__ADDR */ + {2,42,0,0,1,0,0}, /* SPI_RADIO_B__CS */ + {2,43,0,0,1,0,0}, /* RADIO_A_EN */ + {2,43,1,0,1,0,0}, /* RADIO_B_EN */ + {2,43,2,0,1,0,1}, /* RADIO_RST */ + {2,43,3,0,1,0,0}, /* LNA_A_EN */ + {2,43,4,0,1,0,0}, /* PA_A_EN */ + {2,43,5,0,1,0,0}, /* LNA_B_EN */ + {2,43,6,0,1,0,0}, /* PA_B_EN */ + {2,44,0,0,2,0,0}, /* PA_GAIN */ + {2,45,0,0,4,0,2}, /* LNA_A_CTRL_LUT */ + {2,45,4,0,4,0,4}, /* PA_A_CTRL_LUT */ + {2,46,0,0,4,0,2}, /* LNA_B_CTRL_LUT */ + {2,46,4,0,4,0,4}, /* PA_B_CTRL_LUT */ + {2,47,0,0,5,0,0}, /* CAPTURE_SOURCE */ + {2,47,5,0,1,0,0}, /* CAPTURE_START */ + {2,47,6,0,1,0,0}, /* CAPTURE_FORCE_TRIGGER */ + {2,47,7,0,1,0,0}, /* CAPTURE_WRAP */ + {2,48,0,0,16,0,0}, /* CAPTURE_PERIOD */ + {2,51,0,0,8,1,0}, /* MODEM_STATUS */ + {2,52,0,0,8,1,0}, /* VALID_HEADER_COUNTER_0 */ + {2,54,0,0,8,1,0}, /* VALID_PACKET_COUNTER_0 */ + {2,56,0,0,8,1,0}, /* VALID_HEADER_COUNTER_MBWSSF */ + {2,57,0,0,8,1,0}, /* VALID_HEADER_COUNTER_FSK */ + {2,58,0,0,8,1,0}, /* VALID_PACKET_COUNTER_MBWSSF */ + {2,59,0,0,8,1,0}, /* VALID_PACKET_COUNTER_FSK */ + {2,60,0,0,8,1,0}, /* CHANN_RSSI */ + {2,61,0,0,8,1,0}, /* BB_RSSI */ + {2,62,0,0,8,1,0}, /* DEC_RSSI */ + {2,63,0,0,8,1,0}, /* DBG_MCU_DATA */ + {2,64,0,0,8,1,0}, /* DBG_ARB_MCU_RAM_DATA */ + {2,65,0,0,8,1,0}, /* DBG_AGC_MCU_RAM_DATA */ + {2,66,0,0,16,1,0}, /* NEXT_PACKET_CNT */ + {2,68,0,0,16,1,0}, /* ADDR_CAPTURE_COUNT */ + {2,70,0,0,32,1,0}, /* TIMESTAMP */ + {2,74,0,0,4,1,0}, /* DBG_CHANN0_GAIN */ + {2,74,4,0,4,1,0}, /* DBG_CHANN1_GAIN */ + {2,75,0,0,4,1,0}, /* DBG_CHANN2_GAIN */ + {2,75,4,0,4,1,0}, /* DBG_CHANN3_GAIN */ + {2,76,0,0,4,1,0}, /* DBG_CHANN4_GAIN */ + {2,76,4,0,4,1,0}, /* DBG_CHANN5_GAIN */ + {2,77,0,0,4,1,0}, /* DBG_CHANN6_GAIN */ + {2,77,4,0,4,1,0}, /* DBG_CHANN7_GAIN */ + {2,78,0,0,4,1,0}, /* DBG_DEC_FILT_GAIN */ + {2,79,0,0,3,1,0}, /* SPI_DATA_FIFO_PTR */ + {2,79,3,0,3,1,0}, /* PACKET_DATA_FIFO_PTR */ + {2,80,0,0,8,0,0}, /* DBG_ARB_MCU_RAM_ADDR */ + {2,81,0,0,8,0,0}, /* DBG_AGC_MCU_RAM_ADDR */ + {2,82,0,0,1,0,0}, /* SPI_MASTER_CHIP_SELECT_POLARITY */ + {2,82,1,0,1,0,0}, /* SPI_MASTER_CPOL */ + {2,82,2,0,1,0,0}, /* SPI_MASTER_CPHA */ + {2,83,0,0,1,0,0}, /* SIG_GEN_ANALYSER_MUX_SEL */ + {2,84,0,0,1,0,0}, /* SIG_GEN_EN */ + {2,84,1,0,1,0,0}, /* SIG_ANALYSER_EN */ + {2,84,2,0,2,0,0}, /* SIG_ANALYSER_AVG_LEN */ + {2,84,4,0,3,0,0}, /* SIG_ANALYSER_PRECISION */ + {2,84,7,0,1,1,0}, /* SIG_ANALYSER_VALID_OUT */ + {2,85,0,0,8,0,0}, /* SIG_GEN_FREQ */ + {2,86,0,0,8,0,0}, /* SIG_ANALYSER_FREQ */ + {2,87,0,0,8,1,0}, /* SIG_ANALYSER_I_OUT */ + {2,88,0,0,8,1,0}, /* SIG_ANALYSER_Q_OUT */ + {2,89,0,0,1,0,0}, /* GPS_EN */ + {2,89,1,0,1,0,1}, /* GPS_POL */ + {2,90,0,1,8,0,0}, /* SW_TEST_REG1 */ + {2,91,2,1,6,0,0}, /* SW_TEST_REG2 */ + {2,92,0,1,16,0,0}, /* SW_TEST_REG3 */ + {2,94,0,0,4,1,0}, /* DATA_MNGT_STATUS */ + {2,95,0,0,5,1,0}, /* DATA_MNGT_CPT_FRAME_ALLOCATED */ + {2,96,0,0,5,1,0}, /* DATA_MNGT_CPT_FRAME_FINISHED */ + {2,97,0,0,5,1,0}, /* DATA_MNGT_CPT_FRAME_READEN */ + {1,33,0,0,8,0,0} /* TX_TRIG_ALL (alias) */ }; /* -------------------------------------------------------------------------- */ @@ -402,17 +390,19 @@ const struct lgw_reg_s loregs[LGW_TOTALREGS] = { void *lgw_spi_target = NULL; /*! generic pointer to the SPI device */ static int lgw_regpage = -1; /*! keep the value of the register page selected */ -static uint8_t lgw_spi_mux_mode = 0; /*! current SPI mux mode used */ +uint8_t lgw_spi_mux_mode = 0; /*! current SPI mux mode used */ /* -------------------------------------------------------------------------- */ /* --- PRIVATE FUNCTIONS ---------------------------------------------------- */ int page_switch(uint8_t target) { - lgw_regpage = PAGE_MASK & target; - lgw_spi_w(lgw_spi_target, lgw_spi_mux_mode, LGW_SPI_MUX_TARGET_SX1301, PAGE_ADDR, (uint8_t)lgw_regpage); - return LGW_REG_SUCCESS; + lgw_regpage = PAGE_MASK & target; + lgw_spi_w(lgw_spi_target, lgw_spi_mux_mode, LGW_SPI_MUX_TARGET_SX1301, PAGE_ADDR, (uint8_t)lgw_regpage); + return LGW_REG_SUCCESS; } +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + bool check_fpga_version(uint8_t version) { int i; @@ -425,6 +415,84 @@ bool check_fpga_version(uint8_t version) { return false; } +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int reg_w_align32(void *spi_target, uint8_t spi_mux_mode, uint8_t spi_mux_target, struct lgw_reg_s r, int32_t reg_value) { + int spi_stat = LGW_REG_SUCCESS; + int i, size_byte; + uint8_t buf[4] = "\x00\x00\x00\x00"; + + if ((r.leng == 8) && (r.offs == 0)) { + /* direct write */ + spi_stat += lgw_spi_w(spi_target, spi_mux_mode, spi_mux_target, r.addr, (uint8_t)reg_value); + } else if ((r.offs + r.leng) <= 8) { + /* single-byte read-modify-write, offs:[0-7], leng:[1-7] */ + spi_stat += lgw_spi_r(spi_target, spi_mux_mode, spi_mux_target, r.addr, &buf[0]); + buf[1] = ((1 << r.leng) - 1) << r.offs; /* bit mask */ + buf[2] = ((uint8_t)reg_value) << r.offs; /* new data offsetted */ + buf[3] = (~buf[1] & buf[0]) | (buf[1] & buf[2]); /* mixing old & new data */ + spi_stat += lgw_spi_w(spi_target, spi_mux_mode, spi_mux_target, r.addr, buf[3]); + } else if ((r.offs == 0) && (r.leng > 0) && (r.leng <= 32)) { + /* multi-byte direct write routine */ + size_byte = (r.leng + 7) / 8; /* add a byte if it's not an exact multiple of 8 */ + for (i=0; i> 8); + } + spi_stat += lgw_spi_wb(spi_target, spi_mux_mode, spi_mux_target, r.addr, buf, size_byte); /* write the register in one burst */ + } else { + /* register spanning multiple memory bytes but with an offset */ + DEBUG_MSG("ERROR: REGISTER SIZE AND OFFSET ARE NOT SUPPORTED\n"); + return LGW_REG_ERROR; + } + + return spi_stat; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int reg_r_align32(void *spi_target, uint8_t spi_mux_mode, uint8_t spi_mux_target, struct lgw_reg_s r, int32_t *reg_value) { + int spi_stat = LGW_SPI_SUCCESS; + uint8_t bufu[4] = "\x00\x00\x00\x00"; + int8_t *bufs = (int8_t *)bufu; + int i, size_byte; + uint32_t u = 0; + + if ((r.offs + r.leng) <= 8) { + /* read one byte, then shift and mask bits to get reg value with sign extension if needed */ + spi_stat += lgw_spi_r(spi_target, spi_mux_mode, spi_mux_target, r.addr, &bufu[0]); + bufu[1] = bufu[0] << (8 - r.leng - r.offs); /* left-align the data */ + if (r.sign == true) { + bufs[2] = bufs[1] >> (8 - r.leng); /* right align the data with sign extension (ARITHMETIC right shift) */ + *reg_value = (int32_t)bufs[2]; /* signed pointer -> 32b sign extension */ + } else { + bufu[2] = bufu[1] >> (8 - r.leng); /* right align the data, no sign extension */ + *reg_value = (int32_t)bufu[2]; /* unsigned pointer -> no sign extension */ + } + } else if ((r.offs == 0) && (r.leng > 0) && (r.leng <= 32)) { + size_byte = (r.leng + 7) / 8; /* add a byte if it's not an exact multiple of 8 */ + spi_stat += lgw_spi_rb(spi_target, spi_mux_mode, spi_mux_target, r.addr, bufu, size_byte); + u = 0; + for (i=(size_byte-1); i>=0; --i) { + u = (uint32_t)bufu[i] + (u << 8); /* transform a 4-byte array into a 32 bit word */ + } + if (r.sign == true) { + u = u << (32 - r.leng); /* left-align the data */ + *reg_value = (int32_t)u >> (32 - r.leng); /* right-align the data with sign extension (ARITHMETIC right shift) */ + } else { + *reg_value = (int32_t)u; /* unsigned value -> return 'as is' */ + } + } else { + /* register spanning multiple memory bytes but with an offset */ + DEBUG_MSG("ERROR: REGISTER SIZE AND OFFSET ARE NOT SUPPORTED\n"); + return LGW_REG_ERROR; + } + + return spi_stat; +} + /* -------------------------------------------------------------------------- */ /* --- PUBLIC FUNCTIONS DEFINITION ------------------------------------------ */ @@ -456,11 +524,13 @@ int lgw_connect(void) { DEBUG_MSG("INFO: no FPGA detected\n"); lgw_spi_mux_mode = LGW_SPI_MUX_MODE0; } else { - DEBUG_MSG("INFO: detected FPGA with SPI mux header\n"); + DEBUG_PRINTF("INFO: detected FPGA with SPI mux header (v%u)\n", u); lgw_spi_mux_mode = LGW_SPI_MUX_MODE1; /* FPGA Soft Reset */ lgw_spi_w(lgw_spi_target, lgw_spi_mux_mode, LGW_SPI_MUX_TARGET_FPGA, 0, 1); lgw_spi_w(lgw_spi_target, lgw_spi_mux_mode, LGW_SPI_MUX_TARGET_FPGA, 0, 0); + /* FPGA configure */ + lgw_fpga_configure(); } /* check SX1301 version */ @@ -487,304 +557,246 @@ int lgw_connect(void) { /* Concentrator disconnect */ int lgw_disconnect(void) { - if (lgw_spi_target != NULL) { - lgw_spi_close(lgw_spi_target); - lgw_spi_target = NULL; - DEBUG_MSG("Note: success disconnecting the concentrator\n"); - return LGW_REG_SUCCESS; - } else { - DEBUG_MSG("WARNING: concentrator was already disconnected\n"); - return LGW_REG_ERROR; - } + if (lgw_spi_target != NULL) { + lgw_spi_close(lgw_spi_target); + lgw_spi_target = NULL; + DEBUG_MSG("Note: success disconnecting the concentrator\n"); + return LGW_REG_SUCCESS; + } else { + DEBUG_MSG("WARNING: concentrator was already disconnected\n"); + return LGW_REG_ERROR; + } } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ /* soft-reset function */ int lgw_soft_reset(void) { - /* check if SPI is initialised */ - if ((lgw_spi_target == NULL) || (lgw_regpage < 0)) { - DEBUG_MSG("ERROR: CONCENTRATOR UNCONNECTED\n"); - return LGW_REG_ERROR; - } - lgw_spi_w(lgw_spi_target, lgw_spi_mux_mode, LGW_SPI_MUX_TARGET_SX1301, 0, 0x80); /* 1 -> SOFT_RESET bit */ - lgw_regpage = 0; /* reset the paging static variable */ - return LGW_REG_SUCCESS; + /* check if SPI is initialised */ + if ((lgw_spi_target == NULL) || (lgw_regpage < 0)) { + DEBUG_MSG("ERROR: CONCENTRATOR UNCONNECTED\n"); + return LGW_REG_ERROR; + } + lgw_spi_w(lgw_spi_target, lgw_spi_mux_mode, LGW_SPI_MUX_TARGET_SX1301, 0, 0x80); /* 1 -> SOFT_RESET bit */ + lgw_regpage = 0; /* reset the paging static variable */ + return LGW_REG_SUCCESS; } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ /* register verification */ int lgw_reg_check(FILE *f) { - struct lgw_reg_s r; - int32_t read_value; - char ok_msg[] = "+++MATCH+++"; - char notok_msg[] = "###MISMATCH###"; - char *ptr; - int i; - - /* check if SPI is initialised */ - if ((lgw_spi_target == NULL) || (lgw_regpage < 0)) { - DEBUG_MSG("ERROR: CONCENTRATOR UNCONNECTED\n"); - fprintf(f, "ERROR: CONCENTRATOR UNCONNECTED\n"); - return LGW_REG_ERROR; - } - - fprintf(f, "Start of register verification\n"); - for (i=0; i= LGW_TOTALREGS) { - DEBUG_MSG("ERROR: REGISTER NUMBER OUT OF DEFINED RANGE\n"); - return LGW_REG_ERROR; - } - - /* check if SPI is initialised */ - if ((lgw_spi_target == NULL) || (lgw_regpage < 0)) { - DEBUG_MSG("ERROR: CONCENTRATOR UNCONNECTED\n"); - return LGW_REG_ERROR; - } - - /* intercept direct access to PAGE_REG & SOFT_RESET */ - if (register_id == LGW_PAGE_REG) { - page_switch(reg_value); - return LGW_REG_SUCCESS; - } else if (register_id == LGW_SOFT_RESET) { - /* only reset if lsb is 1 */ - if ((reg_value & 0x01) != 0) - lgw_soft_reset(); - return LGW_REG_SUCCESS; - } - - /* get register struct from the struct array */ - r = loregs[register_id]; - - /* reject write to read-only registers */ - if (r.rdon == 1){ - DEBUG_MSG("ERROR: TRYING TO WRITE A READ-ONLY REGISTER\n"); - return LGW_REG_ERROR; - } - - /* select proper register page if needed */ - if ((r.page != -1) && (r.page != lgw_regpage)) { - spi_stat += page_switch(r.page); - } - - if ((r.leng == 8) && (r.offs == 0)) { - /* direct write */ - spi_stat += lgw_spi_w(lgw_spi_target, lgw_spi_mux_mode, LGW_SPI_MUX_TARGET_SX1301, r.addr, (uint8_t)reg_value); - } else if ((r.offs + r.leng) <= 8) { - /* single-byte read-modify-write, offs:[0-7], leng:[1-7] */ - spi_stat += lgw_spi_r(lgw_spi_target, lgw_spi_mux_mode, LGW_SPI_MUX_TARGET_SX1301, r.addr, &buf[0]); - buf[1] = ((1 << r.leng) - 1) << r.offs; /* bit mask */ - buf[2] = ((uint8_t)reg_value) << r.offs; /* new data offsetted */ - buf[3] = (~buf[1] & buf[0]) | (buf[1] & buf[2]); /* mixing old & new data */ - spi_stat += lgw_spi_w(lgw_spi_target, lgw_spi_mux_mode, LGW_SPI_MUX_TARGET_SX1301, r.addr, buf[3]); - } else if ((r.offs == 0) && (r.leng > 0) && (r.leng <= 32)) { - /* multi-byte direct write routine */ - size_byte = (r.leng + 7) / 8; /* add a byte if it's not an exact multiple of 8 */ - for (i=0; i> 8); - } - spi_stat += lgw_spi_wb(lgw_spi_target, lgw_spi_mux_mode, LGW_SPI_MUX_TARGET_SX1301, r.addr, buf, size_byte); /* write the register in one burst */ - } else { - /* register spanning multiple memory bytes but with an offset */ - DEBUG_MSG("ERROR: REGISTER SIZE AND OFFSET ARE NOT SUPPORTED\n"); - return LGW_REG_ERROR; - } - - if (spi_stat != LGW_SPI_SUCCESS) { - DEBUG_MSG("ERROR: SPI ERROR DURING REGISTER WRITE\n"); - return LGW_REG_ERROR; - } else { - return LGW_REG_SUCCESS; - } + int spi_stat = LGW_SPI_SUCCESS; + struct lgw_reg_s r; + + /* check input parameters */ + if (register_id >= LGW_TOTALREGS) { + DEBUG_MSG("ERROR: REGISTER NUMBER OUT OF DEFINED RANGE\n"); + return LGW_REG_ERROR; + } + + /* check if SPI is initialised */ + if ((lgw_spi_target == NULL) || (lgw_regpage < 0)) { + DEBUG_MSG("ERROR: CONCENTRATOR UNCONNECTED\n"); + return LGW_REG_ERROR; + } + + /* intercept direct access to PAGE_REG & SOFT_RESET */ + if (register_id == LGW_PAGE_REG) { + page_switch(reg_value); + return LGW_REG_SUCCESS; + } else if (register_id == LGW_SOFT_RESET) { + /* only reset if lsb is 1 */ + if ((reg_value & 0x01) != 0) + lgw_soft_reset(); + return LGW_REG_SUCCESS; + } + + /* get register struct from the struct array */ + r = loregs[register_id]; + + /* reject write to read-only registers */ + if (r.rdon == 1){ + DEBUG_MSG("ERROR: TRYING TO WRITE A READ-ONLY REGISTER\n"); + return LGW_REG_ERROR; + } + + /* select proper register page if needed */ + if ((r.page != -1) && (r.page != lgw_regpage)) { + spi_stat += page_switch(r.page); + } + + spi_stat += reg_w_align32(lgw_spi_target, lgw_spi_mux_mode, LGW_SPI_MUX_TARGET_SX1301, r, reg_value); + + if (spi_stat != LGW_SPI_SUCCESS) { + DEBUG_MSG("ERROR: SPI ERROR DURING REGISTER WRITE\n"); + return LGW_REG_ERROR; + } else { + return LGW_REG_SUCCESS; + } } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ /* Read to a register addressed by name */ int lgw_reg_r(uint16_t register_id, int32_t *reg_value) { - int spi_stat = LGW_SPI_SUCCESS; - struct lgw_reg_s r; - uint8_t bufu[4] = "\x00\x00\x00\x00"; - int8_t *bufs = (int8_t *)bufu; - int i, size_byte; - uint32_t u = 0; - - /* check input parameters */ - CHECK_NULL(reg_value); - if (register_id >= LGW_TOTALREGS) { - DEBUG_MSG("ERROR: REGISTER NUMBER OUT OF DEFINED RANGE\n"); - return LGW_REG_ERROR; - } - - /* check if SPI is initialised */ - if ((lgw_spi_target == NULL) || (lgw_regpage < 0)) { - DEBUG_MSG("ERROR: CONCENTRATOR UNCONNECTED\n"); - return LGW_REG_ERROR; - } - - /* get register struct from the struct array */ - r = loregs[register_id]; - - /* select proper register page if needed */ - if ((r.page != -1) && (r.page != lgw_regpage)) { - spi_stat += page_switch(r.page); - } - - if ((r.offs + r.leng) <= 8) { - /* read one byte, then shift and mask bits to get reg value with sign extension if needed */ - spi_stat += lgw_spi_r(lgw_spi_target, lgw_spi_mux_mode, LGW_SPI_MUX_TARGET_SX1301, r.addr, &bufu[0]); - bufu[1] = bufu[0] << (8 - r.leng - r.offs); /* left-align the data */ - if (r.sign == true) { - bufs[2] = bufs[1] >> (8 - r.leng); /* right align the data with sign extension (ARITHMETIC right shift) */ - *reg_value = (int32_t)bufs[2]; /* signed pointer -> 32b sign extension */ - } else { - bufu[2] = bufu[1] >> (8 - r.leng); /* right align the data, no sign extension */ - *reg_value = (int32_t)bufu[2]; /* unsigned pointer -> no sign extension */ - } - } else if ((r.offs == 0) && (r.leng > 0) && (r.leng <= 32)) { - size_byte = (r.leng + 7) / 8; /* add a byte if it's not an exact multiple of 8 */ - spi_stat += lgw_spi_rb(lgw_spi_target, lgw_spi_mux_mode, LGW_SPI_MUX_TARGET_SX1301, r.addr, bufu, size_byte); - u = 0; - for (i=(size_byte-1); i>=0; --i) { - u = (uint32_t)bufu[i] + (u << 8); /* transform a 4-byte array into a 32 bit word */ - } - if (r.sign == true) { - u = u << (32 - r.leng); /* left-align the data */ - *reg_value = (int32_t)u >> (32 - r.leng); /* right-align the data with sign extension (ARITHMETIC right shift) */ - } else { - *reg_value = (int32_t)u; /* unsigned value -> return 'as is' */ - } - } else { - /* register spanning multiple memory bytes but with an offset */ - DEBUG_MSG("ERROR: REGISTER SIZE AND OFFSET ARE NOT SUPPORTED\n"); - return LGW_REG_ERROR; - } - - if (spi_stat != LGW_SPI_SUCCESS) { - DEBUG_MSG("ERROR: SPI ERROR DURING REGISTER WRITE\n"); - return LGW_REG_ERROR; - } else { - return LGW_REG_SUCCESS; - } + int spi_stat = LGW_SPI_SUCCESS; + struct lgw_reg_s r; + + /* check input parameters */ + CHECK_NULL(reg_value); + if (register_id >= LGW_TOTALREGS) { + DEBUG_MSG("ERROR: REGISTER NUMBER OUT OF DEFINED RANGE\n"); + return LGW_REG_ERROR; + } + + /* check if SPI is initialised */ + if ((lgw_spi_target == NULL) || (lgw_regpage < 0)) { + DEBUG_MSG("ERROR: CONCENTRATOR UNCONNECTED\n"); + return LGW_REG_ERROR; + } + + /* get register struct from the struct array */ + r = loregs[register_id]; + + /* select proper register page if needed */ + if ((r.page != -1) && (r.page != lgw_regpage)) { + spi_stat += page_switch(r.page); + } + + spi_stat += reg_r_align32(lgw_spi_target, lgw_spi_mux_mode, LGW_SPI_MUX_TARGET_SX1301, r, reg_value); + + if (spi_stat != LGW_SPI_SUCCESS) { + DEBUG_MSG("ERROR: SPI ERROR DURING REGISTER WRITE\n"); + return LGW_REG_ERROR; + } else { + return LGW_REG_SUCCESS; + } } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ /* Point to a register by name and do a burst write */ int lgw_reg_wb(uint16_t register_id, uint8_t *data, uint16_t size) { - int spi_stat = LGW_SPI_SUCCESS; - struct lgw_reg_s r; - - /* check input parameters */ - CHECK_NULL(data); - if (size == 0) { - DEBUG_MSG("ERROR: BURST OF NULL LENGTH\n"); - return LGW_REG_ERROR; - } - if (register_id >= LGW_TOTALREGS) { - DEBUG_MSG("ERROR: REGISTER NUMBER OUT OF DEFINED RANGE\n"); - return LGW_REG_ERROR; - } - - /* check if SPI is initialised */ - if ((lgw_spi_target == NULL) || (lgw_regpage < 0)) { - DEBUG_MSG("ERROR: CONCENTRATOR UNCONNECTED\n"); - return LGW_REG_ERROR; - } - - /* get register struct from the struct array */ - r = loregs[register_id]; - - /* reject write to read-only registers */ - if (r.rdon == 1){ - DEBUG_MSG("ERROR: TRYING TO BURST WRITE A READ-ONLY REGISTER\n"); - return LGW_REG_ERROR; - } - - /* select proper register page if needed */ - if ((r.page != -1) && (r.page != lgw_regpage)) { - spi_stat += page_switch(r.page); - } - - /* do the burst write */ - spi_stat += lgw_spi_wb(lgw_spi_target, lgw_spi_mux_mode, LGW_SPI_MUX_TARGET_SX1301, r.addr, data, size); - - if (spi_stat != LGW_SPI_SUCCESS) { - DEBUG_MSG("ERROR: SPI ERROR DURING REGISTER BURST WRITE\n"); - return LGW_REG_ERROR; - } else { - return LGW_REG_SUCCESS; - } + int spi_stat = LGW_SPI_SUCCESS; + struct lgw_reg_s r; + + /* check input parameters */ + CHECK_NULL(data); + if (size == 0) { + DEBUG_MSG("ERROR: BURST OF NULL LENGTH\n"); + return LGW_REG_ERROR; + } + if (register_id >= LGW_TOTALREGS) { + DEBUG_MSG("ERROR: REGISTER NUMBER OUT OF DEFINED RANGE\n"); + return LGW_REG_ERROR; + } + + /* check if SPI is initialised */ + if ((lgw_spi_target == NULL) || (lgw_regpage < 0)) { + DEBUG_MSG("ERROR: CONCENTRATOR UNCONNECTED\n"); + return LGW_REG_ERROR; + } + + /* get register struct from the struct array */ + r = loregs[register_id]; + + /* reject write to read-only registers */ + if (r.rdon == 1){ + DEBUG_MSG("ERROR: TRYING TO BURST WRITE A READ-ONLY REGISTER\n"); + return LGW_REG_ERROR; + } + + /* select proper register page if needed */ + if ((r.page != -1) && (r.page != lgw_regpage)) { + spi_stat += page_switch(r.page); + } + + /* do the burst write */ + spi_stat += lgw_spi_wb(lgw_spi_target, lgw_spi_mux_mode, LGW_SPI_MUX_TARGET_SX1301, r.addr, data, size); + + if (spi_stat != LGW_SPI_SUCCESS) { + DEBUG_MSG("ERROR: SPI ERROR DURING REGISTER BURST WRITE\n"); + return LGW_REG_ERROR; + } else { + return LGW_REG_SUCCESS; + } } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ /* Point to a register by name and do a burst read */ int lgw_reg_rb(uint16_t register_id, uint8_t *data, uint16_t size) { - int spi_stat = LGW_SPI_SUCCESS; - struct lgw_reg_s r; - - /* check input parameters */ - CHECK_NULL(data); - if (size == 0) { - DEBUG_MSG("ERROR: BURST OF NULL LENGTH\n"); - return LGW_REG_ERROR; - } - if (register_id >= LGW_TOTALREGS) { - DEBUG_MSG("ERROR: REGISTER NUMBER OUT OF DEFINED RANGE\n"); - return LGW_REG_ERROR; - } - - /* check if SPI is initialised */ - if ((lgw_spi_target == NULL) || (lgw_regpage < 0)) { - DEBUG_MSG("ERROR: CONCENTRATOR UNCONNECTED\n"); - return LGW_REG_ERROR; - } - - /* get register struct from the struct array */ - r = loregs[register_id]; - - /* select proper register page if needed */ - if ((r.page != -1) && (r.page != lgw_regpage)) { - spi_stat += page_switch(r.page); - } - - /* do the burst read */ - spi_stat += lgw_spi_rb(lgw_spi_target, lgw_spi_mux_mode, LGW_SPI_MUX_TARGET_SX1301, r.addr, data, size); - - if (spi_stat != LGW_SPI_SUCCESS) { - DEBUG_MSG("ERROR: SPI ERROR DURING REGISTER BURST READ\n"); - return LGW_REG_ERROR; - } else { - return LGW_REG_SUCCESS; - } + int spi_stat = LGW_SPI_SUCCESS; + struct lgw_reg_s r; + + /* check input parameters */ + CHECK_NULL(data); + if (size == 0) { + DEBUG_MSG("ERROR: BURST OF NULL LENGTH\n"); + return LGW_REG_ERROR; + } + if (register_id >= LGW_TOTALREGS) { + DEBUG_MSG("ERROR: REGISTER NUMBER OUT OF DEFINED RANGE\n"); + return LGW_REG_ERROR; + } + + /* check if SPI is initialised */ + if ((lgw_spi_target == NULL) || (lgw_regpage < 0)) { + DEBUG_MSG("ERROR: CONCENTRATOR UNCONNECTED\n"); + return LGW_REG_ERROR; + } + + /* get register struct from the struct array */ + r = loregs[register_id]; + + /* select proper register page if needed */ + if ((r.page != -1) && (r.page != lgw_regpage)) { + spi_stat += page_switch(r.page); + } + + /* do the burst read */ + spi_stat += lgw_spi_rb(lgw_spi_target, lgw_spi_mux_mode, LGW_SPI_MUX_TARGET_SX1301, r.addr, data, size); + + if (spi_stat != LGW_SPI_SUCCESS) { + DEBUG_MSG("ERROR: SPI ERROR DURING REGISTER BURST READ\n"); + return LGW_REG_ERROR; + } else { + return LGW_REG_SUCCESS; + } } /* --- EOF ------------------------------------------------------------------ */ diff --git a/libloragw/tst/test_loragw_cal.c b/libloragw/tst/test_loragw_cal.c index fe8c1401..5853bb46 100644 --- a/libloragw/tst/test_loragw_cal.c +++ b/libloragw/tst/test_loragw_cal.c @@ -7,7 +7,7 @@ (C)2013 Semtech-Cycleo Description: - Minimum test program for the loragw_hal 'library' + Minimum test program for the loragw_hal 'library' License: Revised BSD License, see LICENSE.TXT file include in the project Maintainer: Sylvain Miermont @@ -19,22 +19,23 @@ Maintainer: Sylvain Miermont /* fix an issue between POSIX and C99 */ #if __STDC_VERSION__ >= 199901L - #define _XOPEN_SOURCE 600 + #define _XOPEN_SOURCE 600 #else - #define _XOPEN_SOURCE 500 + #define _XOPEN_SOURCE 500 #endif -#include /* C99 types */ -#include /* bool type */ -#include /* printf */ -#include /* memset */ -#include /* sigaction */ -#include /* cos */ -#include /* getopt access */ +#include /* C99 types */ +#include /* bool type */ +#include /* printf */ +#include /* memset */ +#include /* sigaction */ +#include /* cos */ +#include /* getopt access */ #include "loragw_hal.h" #include "loragw_reg.h" #include "loragw_aux.h" +#include "loragw_radio.h" /* -------------------------------------------------------------------------- */ /* --- PRIVATE MACROS ------------------------------------------------------- */ @@ -44,14 +45,14 @@ Maintainer: Sylvain Miermont /* -------------------------------------------------------------------------- */ /* --- PRIVATE CONSTANTS ---------------------------------------------------- */ -#define DEFAULT_RSSI_OFFSET 0.0 -#define NB_CAL_MAX 100 -#define MCU_AGC 1 -#define MCU_AGC_FW_BYTE 8192 /* size of the firmware IN BYTES (= twice the number of 14b words) */ -#define FW_VERSION_ADDR 0x20 -#define FW_VERSION_CAL 2 -#define RAM_SIZE 4096 -#define FREQ_SIG_NORM 0.078125 +#define DEFAULT_RSSI_OFFSET 0.0 +#define NB_CAL_MAX 100 +#define MCU_AGC 1 +#define MCU_AGC_FW_BYTE 8192 /* size of the firmware IN BYTES (= twice the number of 14b words) */ +#define FW_VERSION_ADDR 0x20 +#define FW_VERSION_CAL 2 +#define RAM_SIZE 4096 +#define FREQ_SIG_NORM 0.078125 /* -------------------------------------------------------------------------- */ /* --- PRIVATE VARIABLES ---------------------------------------------------- */ @@ -62,19 +63,19 @@ Maintainer: Sylvain Miermont /* --- PRIVATE TYPES --------------------------------------------------------- */ struct cal_res_s { - int8_t amp_a; - int8_t phi_a; - int8_t amp_b; - int8_t phi_b; - int8_t offset_i_a [8]; - int8_t offset_q_a [8]; - int8_t offset_i_b [8]; - int8_t offset_q_b [8]; - uint8_t img_rej_a; - uint8_t img_rej_b; - uint8_t offset_rej_a [8]; - uint8_t offset_rej_b [8]; - uint8_t debug [8]; + int8_t amp_a; + int8_t phi_a; + int8_t amp_b; + int8_t phi_b; + int8_t offset_i_a [8]; + int8_t offset_q_a [8]; + int8_t offset_i_b [8]; + int8_t offset_q_b [8]; + uint8_t img_rej_a; + uint8_t img_rej_b; + uint8_t offset_rej_a [8]; + uint8_t offset_rej_b [8]; + uint8_t debug [8]; }; /* -------------------------------------------------------------------------- */ @@ -82,12 +83,6 @@ struct cal_res_s { int load_firmware(uint8_t target, uint8_t *firmware, uint16_t size); /* defined in loragw_hal.c */ -void sx125x_write(uint8_t channel, uint8_t addr, uint8_t data); /* defined in loragw_hal.c */ - -uint8_t sx125x_read(uint8_t channel, uint8_t addr); /* defined in loragw_hal.c */ - -int setup_sx125x(uint8_t rf_chain, uint32_t freq_hz); /* defined in loragw_hal.c */ - uint8_t sx125x_cal(uint8_t cal_cmd, struct cal_res_s *cal_res); int read_capture(int16_t *i, int16_t *q, int nb_samp); @@ -101,659 +96,659 @@ void usage (void); /* describe command line options */ void usage(void) { - printf("Library version information: %s\n", lgw_version_info()); - printf( "Available options:\n"); - printf( " -h print this help\n"); - printf( " -a Radio A frequency in MHz\n"); - printf( " -b Radio B frequency in MHz\n"); - printf( " -r Radio type (SX1255:1255, SX1257:1257)\n"); - printf( " -n Number of calibration iterations\n"); - printf( " -k Concentrator clock source (0:radio_A, 1:radio_B(default))\n"); - printf( " -t Radio to run TX calibration on (0:None(default), 1:radio_A, 2:radio_B, 3:both)\n"); + printf("Library version information: %s\n", lgw_version_info()); + printf( "Available options:\n"); + printf( " -h print this help\n"); + printf( " -a Radio A frequency in MHz\n"); + printf( " -b Radio B frequency in MHz\n"); + printf( " -r Radio type (SX1255:1255, SX1257:1257)\n"); + printf( " -n Number of calibration iterations\n"); + printf( " -k Concentrator clock source (0:radio_A, 1:radio_B(default))\n"); + printf( " -t Radio to run TX calibration on (0:None(default), 1:radio_A, 2:radio_B, 3:both)\n"); } /* -------------------------------------------------------------------------- */ /* --- MAIN FUNCTION -------------------------------------------------------- */ int main(int argc, char **argv) -{ - int i, j, x; - int32_t read_val; - struct lgw_conf_board_s boardconf; - struct lgw_conf_rxrf_s rfconf; - uint8_t fw_version; - uint8_t cal_cmd; - uint8_t cal_status; - struct cal_res_s cal_res [NB_CAL_MAX]; - struct cal_res_s cal_res_max; - struct cal_res_s cal_res_min; - int16_t sig_i [RAM_SIZE]; - int16_t sig_q [RAM_SIZE]; - uint8_t img_rej_a [NB_CAL_MAX]; - uint8_t img_rej_b [NB_CAL_MAX]; - uint8_t img_rej_a_max; - uint8_t img_rej_a_min; - uint8_t img_rej_b_max; - uint8_t img_rej_b_min; - //FILE *file; - - /* command line options */ - int xi = 0; - double xd = 0.0; - uint32_t fa = 0, fb = 0; - enum lgw_radio_type_e radio_type = LGW_RADIO_TYPE_NONE; - uint8_t clocksource = 1; /* Radio B is source by default */ - uint8_t tx_enable = 0; - int nb_cal = 5; - - /* parse command line options */ - while ((i = getopt (argc, argv, "ha:b:r:n:k:t:")) != -1) { - switch (i) { - case 'h': - usage(); - return -1; - break; - case 'a': /* Radio A frequency in MHz */ - sscanf(optarg, "%lf", &xd); - fa = (uint32_t)((xd*1e6) + 0.5); /* .5 Hz offset to get rounding instead of truncating */ - break; - case 'b': /* Radio B frequency in MHz */ - sscanf(optarg, "%lf", &xd); - fb = (uint32_t)((xd*1e6) + 0.5); /* .5 Hz offset to get rounding instead of truncating */ - break; - case 'r': /* Radio type (1255, 1257) */ - sscanf(optarg, "%i", &xi); - switch (xi) { - case 1255: - radio_type = LGW_RADIO_TYPE_SX1255; - break; - case 1257: - radio_type = LGW_RADIO_TYPE_SX1257; - break; - default: - printf("ERROR: invalid radio type\n"); - usage(); - return -1; - } - break; - case 'n': /* Number of calibration iterations */ - i = sscanf(optarg, "%i", &xi); - if ((i != 1) || (xi > NB_CAL_MAX)) { - printf("ERROR: invalid number of calibration iterations (MAX %d)\n",NB_CAL_MAX); - usage(); - return -1; - } else { - nb_cal = xi; - } - break; - case 'k': /* Concentrator clock source (Radio A or Radio B) */ - sscanf(optarg, "%i", &xi); - clocksource = (uint8_t)xi; - break; - case 't': /* Radio to run TX calibration on */ - sscanf(optarg, "%i", &xi); - tx_enable = (uint8_t)xi; - break; - default: - printf("ERROR: argument parsing\n"); - usage(); - return -1; - } - } - - /* check input parameters */ - if ((fa == 0) || (fb == 0)) { - printf("ERROR: missing frequency input parameter:\n"); - printf(" Radio A RX: %u\n", fa); - printf(" Radio B RX: %u\n", fb); - usage(); - return -1; - } - - if (radio_type == LGW_RADIO_TYPE_NONE) { - printf("ERROR: missing radio type parameter:\n"); - usage(); - return -1; - } - - /* starting the concentrator */ - /* board config */ - memset(&boardconf, 0, sizeof(boardconf)); - - boardconf.lorawan_public = true; - boardconf.clksrc = clocksource; - lgw_board_setconf(boardconf); - - /* RF config */ - memset(&rfconf, 0, sizeof(rfconf)); - - rfconf.enable = true; - rfconf.freq_hz = fa; - rfconf.rssi_offset = DEFAULT_RSSI_OFFSET; - rfconf.type = radio_type; - rfconf.tx_enable = false; /* ignored */ - lgw_rxrf_setconf(0, rfconf); - - rfconf.freq_hz = fb; - rfconf.tx_enable = false; /* ignored */ - lgw_rxrf_setconf(1, rfconf); - - /* Calibration command */ - cal_cmd = 0; - //cal_cmd |= 0x01; /* Bit 0: Calibrate Rx IQ mismatch compensation on radio A */ - //cal_cmd |= 0x02; /* Bit 1: Calibrate Rx IQ mismatch compensation on radio B */ - //cal_cmd |= 0x04; /* Bit 2: Calibrate Tx DC offset on radio A */ - //cal_cmd |= 0x08; /* Bit 3: Calibrate Tx DC offset on radio B */ - cal_cmd |= 0x10; /* Bit 4: 0: calibrate with DAC gain=2, 1: with DAC gain=3 (use 3) */ - - switch (radio_type) { - case LGW_RADIO_TYPE_SX1255: - cal_cmd |= 0x20; /* Bit 5: 0: SX1257, 1: SX1255 */ - break; - case LGW_RADIO_TYPE_SX1257: - cal_cmd |= 0x00; /* Bit 5: 0: SX1257, 1: SX1255 */ - break; - default: - break; - } - - cal_cmd |= 0x00; /* Bit 6-7: Board type 0: ref, 1: FPGA, 3: board X */ - - /* Recap parameters*/ - printf("Library version information: %s\n", lgw_version_info()); - printf("Radio type: %d\n",radio_type); - printf("Radio A frequency: %f MHz\n",fa/1e6); - printf("Radio B frequency: %f MHz\n",fb/1e6); - printf("Number of calibration iterations: %d\n",nb_cal); - printf("Calibration command: brd: %d, chip: %d, dac: %d\n\n", cal_cmd >> 6, 1257-2*((cal_cmd & 0x20) >> 5), 2+((cal_cmd & 0x10) >> 4)); - - x = lgw_connect(); - if (x == -1) { - printf("ERROR: FAIL TO CONNECT BOARD\n"); - return -1; - } - - /* reset the registers (also shuts the radios down) */ - lgw_soft_reset(); - - /* ungate clocks (gated by default) */ - lgw_reg_w(LGW_GLOBAL_EN, 1); - - /* switch on and reset the radios (also starts the 32 MHz XTAL) */ - lgw_reg_w(LGW_RADIO_A_EN,1); - lgw_reg_w(LGW_RADIO_B_EN,1); - wait_ms(500); /* TODO: optimize */ - lgw_reg_w(LGW_RADIO_RST,1); - wait_ms(5); - lgw_reg_w(LGW_RADIO_RST,0); - - /* setup the radios */ - setup_sx125x(0, fa); - setup_sx125x(1, fb); - - /* Set GPIO 4 high for calibration */ - lgw_reg_w(LGW_GPIO_MODE,31); /* Set all GPIOs as output */ - lgw_reg_w(LGW_GPIO_SELECT_OUTPUT,2); /* AGC MCU drives GPIOs */ - - /* Load the calibration firmware */ - load_firmware(MCU_AGC, cal_firmware, MCU_AGC_FW_BYTE); - lgw_reg_w(LGW_MCU_RST_1,0); - lgw_reg_w(LGW_DBG_AGC_MCU_RAM_ADDR, FW_VERSION_ADDR); - lgw_reg_r(LGW_DBG_AGC_MCU_RAM_DATA, &read_val); - fw_version = (uint8_t)read_val; - if (fw_version != FW_VERSION_CAL) { - printf("ERROR: Version of calibration firmware not expected, actual:%d expected:%d\n", fw_version, FW_VERSION_CAL); - return -1; - } - - /* Run Rx A IQ mismatch calibration only */ - for (i=0; i> 4); - } - printf("\n"); - printf("Tx A DC debug Dec:"); - for (j=0; j<8; j++) { - printf(" %3d", cal_res[i].debug[j] & 0x0F); - } - printf("\n"); - printf("Tx A DC Status : %3d\n", cal_status); - } - } else { - printf("Tx A calibration bypassed\n"); - } - - /* Run Tx B DC offset calibation only */ - printf("\n"); - if ((tx_enable == 2) || (tx_enable == 3)) { - for (i=0; i> 4); - } - printf("\n"); - printf("Tx B DC debug Dec:"); - for (j=0; j<8; j++) { - printf(" %3d", cal_res[i].debug[j] & 0x0F); - } - printf("\n"); - printf("Tx B DC Status : %3d\n", cal_status); - } - } else { - printf("Tx B calibration bypassed\n"); - } - - /* Compute statistics */ - cal_res_max.amp_a = -128; - cal_res_max.phi_a = -128; - cal_res_max.amp_b = -128; - cal_res_max.phi_b = -128; - cal_res_max.img_rej_a = 0; - cal_res_max.img_rej_b = 0; - for (j=0; j<8; j++) { - cal_res_max.offset_i_a[j] = -128; - cal_res_max.offset_q_a[j] = -128; - cal_res_max.offset_i_b[j] = -128; - cal_res_max.offset_q_b[j] = -128; - cal_res_max.offset_rej_a[j] = 0; - cal_res_max.offset_rej_b[j] = 0; - } - - cal_res_min.amp_a = 127; - cal_res_min.phi_a = 127; - cal_res_min.amp_b = 127; - cal_res_min.phi_b = 127; - cal_res_min.img_rej_a = 255; - cal_res_min.img_rej_b = 255; - for (j=0; j<8; j++) { - cal_res_min.offset_i_a[j] = 127; - cal_res_min.offset_q_a[j] = 127; - cal_res_min.offset_i_b[j] = 127; - cal_res_min.offset_q_b[j] = 127; - cal_res_min.offset_rej_a[j] = 255; - cal_res_min.offset_rej_b[j] = 255; - } - - img_rej_a_max = 0; - img_rej_a_min = 255; - img_rej_b_max = 0; - img_rej_b_min = 255; - - for (i=0; i cal_res_max.amp_a) { - cal_res_max.amp_a = cal_res[i].amp_a; - } - if (cal_res[i].phi_a > cal_res_max.phi_a) { - cal_res_max.phi_a = cal_res[i].phi_a; - } - if (cal_res[i].amp_b > cal_res_max.amp_b) { - cal_res_max.amp_b = cal_res[i].amp_b; - } - if (cal_res[i].phi_b > cal_res_max.phi_b) { - cal_res_max.phi_b = cal_res[i].phi_b; - } - if (cal_res[i].phi_b > cal_res_max.phi_b) { - cal_res_max.phi_b = cal_res[i].phi_b; - } - if (cal_res[i].img_rej_a > cal_res_max.img_rej_a) { - cal_res_max.img_rej_a = cal_res[i].img_rej_a; - } - if (cal_res[i].img_rej_b > cal_res_max.img_rej_b) { - cal_res_max.img_rej_b = cal_res[i].img_rej_b; - } - for (j=0; j<8; j++) { - if (cal_res[i].offset_i_a[j] > cal_res_max.offset_i_a[j]) { - cal_res_max.offset_i_a[j] = cal_res[i].offset_i_a[j]; - } - if (cal_res[i].offset_q_a[j] > cal_res_max.offset_q_a[j]) { - cal_res_max.offset_q_a[j] = cal_res[i].offset_q_a[j]; - } - if (cal_res[i].offset_i_b[j] > cal_res_max.offset_i_b[j]) { - cal_res_max.offset_i_b[j] = cal_res[i].offset_i_b[j]; - } - if (cal_res[i].offset_q_b[j] > cal_res_max.offset_q_b[j]) { - cal_res_max.offset_q_b[j] = cal_res[i].offset_q_b[j]; - } - if (cal_res[i].offset_rej_a[j] > cal_res_max.offset_rej_a[j]) { - cal_res_max.offset_rej_a[j] = cal_res[i].offset_rej_a[j]; - } - if (cal_res[i].offset_rej_b[j] > cal_res_max.offset_rej_b[j]) { - cal_res_max.offset_rej_b[j] = cal_res[i].offset_rej_b[j]; - } - } - - if (cal_res[i].amp_a < cal_res_min.amp_a) { - cal_res_min.amp_a = cal_res[i].amp_a; - } - if (cal_res[i].phi_a < cal_res_min.phi_a) { - cal_res_min.phi_a = cal_res[i].phi_a; - } - if (cal_res[i].amp_b < cal_res_min.amp_b) { - cal_res_min.amp_b = cal_res[i].amp_b; - } - if (cal_res[i].phi_b < cal_res_min.phi_b) { - cal_res_min.phi_b = cal_res[i].phi_b; - } - if (cal_res[i].phi_b < cal_res_min.phi_b) { - cal_res_min.phi_b = cal_res[i].phi_b; - } - if (cal_res[i].img_rej_a < cal_res_min.img_rej_a) { - cal_res_min.img_rej_a = cal_res[i].img_rej_a; - } - if (cal_res[i].img_rej_b < cal_res_min.img_rej_b) { - cal_res_min.img_rej_b = cal_res[i].img_rej_b; - } - for (j=0; j<8; j++) { - if (cal_res[i].offset_i_a[j] < cal_res_min.offset_i_a[j]) { - cal_res_min.offset_i_a[j] = cal_res[i].offset_i_a[j]; - } - if (cal_res[i].offset_q_a[j] < cal_res_min.offset_q_a[j]) { - cal_res_min.offset_q_a[j] = cal_res[i].offset_q_a[j]; - } - if (cal_res[i].offset_i_b[j] < cal_res_min.offset_i_b[j]) { - cal_res_min.offset_i_b[j] = cal_res[i].offset_i_b[j]; - } - if (cal_res[i].offset_q_b[j] < cal_res_min.offset_q_b[j]) { - cal_res_min.offset_q_b[j] = cal_res[i].offset_q_b[j]; - } - if (cal_res[i].offset_rej_a[j] < cal_res_min.offset_rej_a[j]) { - cal_res_min.offset_rej_a[j] = cal_res[i].offset_rej_a[j]; - } - if (cal_res[i].offset_rej_b[j] < cal_res_min.offset_rej_b[j]) { - cal_res_min.offset_rej_b[j] = cal_res[i].offset_rej_b[j]; - } - } - - if (img_rej_a[i] > img_rej_a_max) { - img_rej_a_max = img_rej_a[i]; - } - if (img_rej_a[i] < img_rej_a_min) { - img_rej_a_min = img_rej_a[i]; - } - if (img_rej_b[i] > img_rej_b_max) { - img_rej_b_max = img_rej_b[i]; - } - if (img_rej_b[i] < img_rej_b_min) { - img_rej_b_min = img_rej_b[i]; - } - } - - /* Print statistics */ - printf("\n"); - printf("Rx A IQ mismatch calibration statistics on %3d iterations (min, max):\n", nb_cal); - printf("Amp: %3d %3d Phi: %3d %3d Rej: %2d %2d dB (capt.: %2d %2d dB)\n", cal_res_min.amp_a, cal_res_max.amp_a, cal_res_min.phi_a, cal_res_max.phi_a, cal_res_min.img_rej_a, cal_res_max.img_rej_a, img_rej_a_min, img_rej_a_max); - - printf("\n"); - printf("Rx B IQ mismatch calibration statistics on %3d iterations (min, max):\n", nb_cal); - printf("Amp: %3d %3d Phi: %3d %3d Rej: %2d %2d dB (capt.: %2d %2d dB)\n", cal_res_min.amp_b, cal_res_max.amp_b, cal_res_min.phi_b, cal_res_max.phi_b, cal_res_min.img_rej_b, cal_res_max.img_rej_b, img_rej_b_min, img_rej_b_max); - - if ((tx_enable == 1) || (tx_enable == 3)) { - printf("\n"); - printf("Tx A DC offset calibration statistics on %3d iterations (min, max):\n", nb_cal); - for (j=0; j<8; j++) { - printf(" Mix gain %2d: I: %3d %3d Q: %3d %3d Rej: %2d %2d dB\n", 8+j, cal_res_min.offset_i_a[j], cal_res_max.offset_i_a[j], cal_res_min.offset_q_a[j], cal_res_max.offset_q_a[j], cal_res_min.offset_rej_a[j], cal_res_max.offset_rej_a[j]); - } - } - - if ((tx_enable == 2) || (tx_enable == 3)) { - printf("\n"); - printf("Tx B DC offset calibration statistics on %3d iterations (min, max):\n", nb_cal); - for (j=0; j<8; j++) { - printf(" Mix gain %2d: I: %3d %3d Q: %3d %3d Rej: %2d %2d dB\n", 8+j, cal_res_min.offset_i_b[j], cal_res_max.offset_i_b[j], cal_res_min.offset_q_b[j], cal_res_max.offset_q_b[j], cal_res_min.offset_rej_b[j], cal_res_max.offset_rej_b[j]); - } - } - - lgw_stop(); - - printf("\nEnd of radio calibration test\n"); - - return 0; +{ + int i, j, x; + int32_t read_val; + struct lgw_conf_board_s boardconf; + struct lgw_conf_rxrf_s rfconf; + uint8_t fw_version; + uint8_t cal_cmd; + uint8_t cal_status; + struct cal_res_s cal_res [NB_CAL_MAX]; + struct cal_res_s cal_res_max; + struct cal_res_s cal_res_min; + int16_t sig_i [RAM_SIZE]; + int16_t sig_q [RAM_SIZE]; + uint8_t img_rej_a [NB_CAL_MAX]; + uint8_t img_rej_b [NB_CAL_MAX]; + uint8_t img_rej_a_max; + uint8_t img_rej_a_min; + uint8_t img_rej_b_max; + uint8_t img_rej_b_min; + //FILE *file; + + /* command line options */ + int xi = 0; + double xd = 0.0; + uint32_t fa = 0, fb = 0; + enum lgw_radio_type_e radio_type = LGW_RADIO_TYPE_NONE; + uint8_t clocksource = 1; /* Radio B is source by default */ + uint8_t tx_enable = 0; + int nb_cal = 5; + + /* parse command line options */ + while ((i = getopt (argc, argv, "ha:b:r:n:k:t:")) != -1) { + switch (i) { + case 'h': + usage(); + return -1; + break; + case 'a': /* Radio A frequency in MHz */ + sscanf(optarg, "%lf", &xd); + fa = (uint32_t)((xd*1e6) + 0.5); /* .5 Hz offset to get rounding instead of truncating */ + break; + case 'b': /* Radio B frequency in MHz */ + sscanf(optarg, "%lf", &xd); + fb = (uint32_t)((xd*1e6) + 0.5); /* .5 Hz offset to get rounding instead of truncating */ + break; + case 'r': /* Radio type (1255, 1257) */ + sscanf(optarg, "%i", &xi); + switch (xi) { + case 1255: + radio_type = LGW_RADIO_TYPE_SX1255; + break; + case 1257: + radio_type = LGW_RADIO_TYPE_SX1257; + break; + default: + printf("ERROR: invalid radio type\n"); + usage(); + return -1; + } + break; + case 'n': /* Number of calibration iterations */ + i = sscanf(optarg, "%i", &xi); + if ((i != 1) || (xi > NB_CAL_MAX)) { + printf("ERROR: invalid number of calibration iterations (MAX %d)\n",NB_CAL_MAX); + usage(); + return -1; + } else { + nb_cal = xi; + } + break; + case 'k': /* Concentrator clock source (Radio A or Radio B) */ + sscanf(optarg, "%i", &xi); + clocksource = (uint8_t)xi; + break; + case 't': /* Radio to run TX calibration on */ + sscanf(optarg, "%i", &xi); + tx_enable = (uint8_t)xi; + break; + default: + printf("ERROR: argument parsing\n"); + usage(); + return -1; + } + } + + /* check input parameters */ + if ((fa == 0) || (fb == 0)) { + printf("ERROR: missing frequency input parameter:\n"); + printf(" Radio A RX: %u\n", fa); + printf(" Radio B RX: %u\n", fb); + usage(); + return -1; + } + + if (radio_type == LGW_RADIO_TYPE_NONE) { + printf("ERROR: missing radio type parameter:\n"); + usage(); + return -1; + } + + /* starting the concentrator */ + /* board config */ + memset(&boardconf, 0, sizeof(boardconf)); + + boardconf.lorawan_public = true; + boardconf.clksrc = clocksource; + lgw_board_setconf(boardconf); + + /* RF config */ + memset(&rfconf, 0, sizeof(rfconf)); + + rfconf.enable = true; + rfconf.freq_hz = fa; + rfconf.rssi_offset = DEFAULT_RSSI_OFFSET; + rfconf.type = radio_type; + rfconf.tx_enable = false; /* ignored */ + lgw_rxrf_setconf(0, rfconf); + + rfconf.freq_hz = fb; + rfconf.tx_enable = false; /* ignored */ + lgw_rxrf_setconf(1, rfconf); + + /* Calibration command */ + cal_cmd = 0; + //cal_cmd |= 0x01; /* Bit 0: Calibrate Rx IQ mismatch compensation on radio A */ + //cal_cmd |= 0x02; /* Bit 1: Calibrate Rx IQ mismatch compensation on radio B */ + //cal_cmd |= 0x04; /* Bit 2: Calibrate Tx DC offset on radio A */ + //cal_cmd |= 0x08; /* Bit 3: Calibrate Tx DC offset on radio B */ + cal_cmd |= 0x10; /* Bit 4: 0: calibrate with DAC gain=2, 1: with DAC gain=3 (use 3) */ + + switch (radio_type) { + case LGW_RADIO_TYPE_SX1255: + cal_cmd |= 0x20; /* Bit 5: 0: SX1257, 1: SX1255 */ + break; + case LGW_RADIO_TYPE_SX1257: + cal_cmd |= 0x00; /* Bit 5: 0: SX1257, 1: SX1255 */ + break; + default: + break; + } + + cal_cmd |= 0x00; /* Bit 6-7: Board type 0: ref, 1: FPGA, 3: board X */ + + /* Recap parameters*/ + printf("Library version information: %s\n", lgw_version_info()); + printf("Radio type: %d\n",radio_type); + printf("Radio A frequency: %f MHz\n",fa/1e6); + printf("Radio B frequency: %f MHz\n",fb/1e6); + printf("Number of calibration iterations: %d\n",nb_cal); + printf("Calibration command: brd: %d, chip: %d, dac: %d\n\n", cal_cmd >> 6, 1257-2*((cal_cmd & 0x20) >> 5), 2+((cal_cmd & 0x10) >> 4)); + + x = lgw_connect(); + if (x == -1) { + printf("ERROR: FAIL TO CONNECT BOARD\n"); + return -1; + } + + /* reset the registers (also shuts the radios down) */ + lgw_soft_reset(); + + /* ungate clocks (gated by default) */ + lgw_reg_w(LGW_GLOBAL_EN, 1); + + /* switch on and reset the radios (also starts the 32 MHz XTAL) */ + lgw_reg_w(LGW_RADIO_A_EN,1); + lgw_reg_w(LGW_RADIO_B_EN,1); + wait_ms(500); /* TODO: optimize */ + lgw_reg_w(LGW_RADIO_RST,1); + wait_ms(5); + lgw_reg_w(LGW_RADIO_RST,0); + + /* setup the radios */ + setup_sx125x(0, clocksource, true, radio_type, fa); + setup_sx125x(1, clocksource, false, radio_type, fb); + + /* Set GPIO 4 high for calibration */ + lgw_reg_w(LGW_GPIO_MODE,31); /* Set all GPIOs as output */ + lgw_reg_w(LGW_GPIO_SELECT_OUTPUT,2); /* AGC MCU drives GPIOs */ + + /* Load the calibration firmware */ + load_firmware(MCU_AGC, cal_firmware, MCU_AGC_FW_BYTE); + lgw_reg_w(LGW_MCU_RST_1,0); + lgw_reg_w(LGW_DBG_AGC_MCU_RAM_ADDR, FW_VERSION_ADDR); + lgw_reg_r(LGW_DBG_AGC_MCU_RAM_DATA, &read_val); + fw_version = (uint8_t)read_val; + if (fw_version != FW_VERSION_CAL) { + printf("ERROR: Version of calibration firmware not expected, actual:%d expected:%d\n", fw_version, FW_VERSION_CAL); + return -1; + } + + /* Run Rx A IQ mismatch calibration only */ + for (i=0; i> 4); + } + printf("\n"); + printf("Tx A DC debug Dec:"); + for (j=0; j<8; j++) { + printf(" %3d", cal_res[i].debug[j] & 0x0F); + } + printf("\n"); + printf("Tx A DC Status : %3d\n", cal_status); + } + } else { + printf("Tx A calibration bypassed\n"); + } + + /* Run Tx B DC offset calibation only */ + printf("\n"); + if ((tx_enable == 2) || (tx_enable == 3)) { + for (i=0; i> 4); + } + printf("\n"); + printf("Tx B DC debug Dec:"); + for (j=0; j<8; j++) { + printf(" %3d", cal_res[i].debug[j] & 0x0F); + } + printf("\n"); + printf("Tx B DC Status : %3d\n", cal_status); + } + } else { + printf("Tx B calibration bypassed\n"); + } + + /* Compute statistics */ + cal_res_max.amp_a = -128; + cal_res_max.phi_a = -128; + cal_res_max.amp_b = -128; + cal_res_max.phi_b = -128; + cal_res_max.img_rej_a = 0; + cal_res_max.img_rej_b = 0; + for (j=0; j<8; j++) { + cal_res_max.offset_i_a[j] = -128; + cal_res_max.offset_q_a[j] = -128; + cal_res_max.offset_i_b[j] = -128; + cal_res_max.offset_q_b[j] = -128; + cal_res_max.offset_rej_a[j] = 0; + cal_res_max.offset_rej_b[j] = 0; + } + + cal_res_min.amp_a = 127; + cal_res_min.phi_a = 127; + cal_res_min.amp_b = 127; + cal_res_min.phi_b = 127; + cal_res_min.img_rej_a = 255; + cal_res_min.img_rej_b = 255; + for (j=0; j<8; j++) { + cal_res_min.offset_i_a[j] = 127; + cal_res_min.offset_q_a[j] = 127; + cal_res_min.offset_i_b[j] = 127; + cal_res_min.offset_q_b[j] = 127; + cal_res_min.offset_rej_a[j] = 255; + cal_res_min.offset_rej_b[j] = 255; + } + + img_rej_a_max = 0; + img_rej_a_min = 255; + img_rej_b_max = 0; + img_rej_b_min = 255; + + for (i=0; i cal_res_max.amp_a) { + cal_res_max.amp_a = cal_res[i].amp_a; + } + if (cal_res[i].phi_a > cal_res_max.phi_a) { + cal_res_max.phi_a = cal_res[i].phi_a; + } + if (cal_res[i].amp_b > cal_res_max.amp_b) { + cal_res_max.amp_b = cal_res[i].amp_b; + } + if (cal_res[i].phi_b > cal_res_max.phi_b) { + cal_res_max.phi_b = cal_res[i].phi_b; + } + if (cal_res[i].phi_b > cal_res_max.phi_b) { + cal_res_max.phi_b = cal_res[i].phi_b; + } + if (cal_res[i].img_rej_a > cal_res_max.img_rej_a) { + cal_res_max.img_rej_a = cal_res[i].img_rej_a; + } + if (cal_res[i].img_rej_b > cal_res_max.img_rej_b) { + cal_res_max.img_rej_b = cal_res[i].img_rej_b; + } + for (j=0; j<8; j++) { + if (cal_res[i].offset_i_a[j] > cal_res_max.offset_i_a[j]) { + cal_res_max.offset_i_a[j] = cal_res[i].offset_i_a[j]; + } + if (cal_res[i].offset_q_a[j] > cal_res_max.offset_q_a[j]) { + cal_res_max.offset_q_a[j] = cal_res[i].offset_q_a[j]; + } + if (cal_res[i].offset_i_b[j] > cal_res_max.offset_i_b[j]) { + cal_res_max.offset_i_b[j] = cal_res[i].offset_i_b[j]; + } + if (cal_res[i].offset_q_b[j] > cal_res_max.offset_q_b[j]) { + cal_res_max.offset_q_b[j] = cal_res[i].offset_q_b[j]; + } + if (cal_res[i].offset_rej_a[j] > cal_res_max.offset_rej_a[j]) { + cal_res_max.offset_rej_a[j] = cal_res[i].offset_rej_a[j]; + } + if (cal_res[i].offset_rej_b[j] > cal_res_max.offset_rej_b[j]) { + cal_res_max.offset_rej_b[j] = cal_res[i].offset_rej_b[j]; + } + } + + if (cal_res[i].amp_a < cal_res_min.amp_a) { + cal_res_min.amp_a = cal_res[i].amp_a; + } + if (cal_res[i].phi_a < cal_res_min.phi_a) { + cal_res_min.phi_a = cal_res[i].phi_a; + } + if (cal_res[i].amp_b < cal_res_min.amp_b) { + cal_res_min.amp_b = cal_res[i].amp_b; + } + if (cal_res[i].phi_b < cal_res_min.phi_b) { + cal_res_min.phi_b = cal_res[i].phi_b; + } + if (cal_res[i].phi_b < cal_res_min.phi_b) { + cal_res_min.phi_b = cal_res[i].phi_b; + } + if (cal_res[i].img_rej_a < cal_res_min.img_rej_a) { + cal_res_min.img_rej_a = cal_res[i].img_rej_a; + } + if (cal_res[i].img_rej_b < cal_res_min.img_rej_b) { + cal_res_min.img_rej_b = cal_res[i].img_rej_b; + } + for (j=0; j<8; j++) { + if (cal_res[i].offset_i_a[j] < cal_res_min.offset_i_a[j]) { + cal_res_min.offset_i_a[j] = cal_res[i].offset_i_a[j]; + } + if (cal_res[i].offset_q_a[j] < cal_res_min.offset_q_a[j]) { + cal_res_min.offset_q_a[j] = cal_res[i].offset_q_a[j]; + } + if (cal_res[i].offset_i_b[j] < cal_res_min.offset_i_b[j]) { + cal_res_min.offset_i_b[j] = cal_res[i].offset_i_b[j]; + } + if (cal_res[i].offset_q_b[j] < cal_res_min.offset_q_b[j]) { + cal_res_min.offset_q_b[j] = cal_res[i].offset_q_b[j]; + } + if (cal_res[i].offset_rej_a[j] < cal_res_min.offset_rej_a[j]) { + cal_res_min.offset_rej_a[j] = cal_res[i].offset_rej_a[j]; + } + if (cal_res[i].offset_rej_b[j] < cal_res_min.offset_rej_b[j]) { + cal_res_min.offset_rej_b[j] = cal_res[i].offset_rej_b[j]; + } + } + + if (img_rej_a[i] > img_rej_a_max) { + img_rej_a_max = img_rej_a[i]; + } + if (img_rej_a[i] < img_rej_a_min) { + img_rej_a_min = img_rej_a[i]; + } + if (img_rej_b[i] > img_rej_b_max) { + img_rej_b_max = img_rej_b[i]; + } + if (img_rej_b[i] < img_rej_b_min) { + img_rej_b_min = img_rej_b[i]; + } + } + + /* Print statistics */ + printf("\n"); + printf("Rx A IQ mismatch calibration statistics on %3d iterations (min, max):\n", nb_cal); + printf("Amp: %3d %3d Phi: %3d %3d Rej: %2d %2d dB (capt.: %2d %2d dB)\n", cal_res_min.amp_a, cal_res_max.amp_a, cal_res_min.phi_a, cal_res_max.phi_a, cal_res_min.img_rej_a, cal_res_max.img_rej_a, img_rej_a_min, img_rej_a_max); + + printf("\n"); + printf("Rx B IQ mismatch calibration statistics on %3d iterations (min, max):\n", nb_cal); + printf("Amp: %3d %3d Phi: %3d %3d Rej: %2d %2d dB (capt.: %2d %2d dB)\n", cal_res_min.amp_b, cal_res_max.amp_b, cal_res_min.phi_b, cal_res_max.phi_b, cal_res_min.img_rej_b, cal_res_max.img_rej_b, img_rej_b_min, img_rej_b_max); + + if ((tx_enable == 1) || (tx_enable == 3)) { + printf("\n"); + printf("Tx A DC offset calibration statistics on %3d iterations (min, max):\n", nb_cal); + for (j=0; j<8; j++) { + printf(" Mix gain %2d: I: %3d %3d Q: %3d %3d Rej: %2d %2d dB\n", 8+j, cal_res_min.offset_i_a[j], cal_res_max.offset_i_a[j], cal_res_min.offset_q_a[j], cal_res_max.offset_q_a[j], cal_res_min.offset_rej_a[j], cal_res_max.offset_rej_a[j]); + } + } + + if ((tx_enable == 2) || (tx_enable == 3)) { + printf("\n"); + printf("Tx B DC offset calibration statistics on %3d iterations (min, max):\n", nb_cal); + for (j=0; j<8; j++) { + printf(" Mix gain %2d: I: %3d %3d Q: %3d %3d Rej: %2d %2d dB\n", 8+j, cal_res_min.offset_i_b[j], cal_res_max.offset_i_b[j], cal_res_min.offset_q_b[j], cal_res_max.offset_q_b[j], cal_res_min.offset_rej_b[j], cal_res_max.offset_rej_b[j]); + } + } + + lgw_stop(); + + printf("\nEnd of radio calibration test\n"); + + return 0; } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ uint8_t sx125x_cal(uint8_t cal_cmd, struct cal_res_s *cal_res) { - int i; - int32_t read_val; - uint8_t cal_status; - - lgw_reg_w(LGW_FORCE_HOST_RADIO_CTRL,0); /* gives to AGC MCU the control of the radios */ - lgw_reg_w(LGW_RADIO_SELECT,cal_cmd); /* send calibration configuration word */ - lgw_reg_w(LGW_MCU_RST_1,1); - lgw_reg_w(LGW_MCU_RST_1,0); - lgw_reg_w(LGW_PAGE_REG,3); /* Calibration will start on this condition as soon as MCU can talk to concentrator registers */ - lgw_reg_w(LGW_EMERGENCY_FORCE_HOST_CTRL,0); /* Give control of concentrator registers to MCU */ - - wait_ms(2000); /* Wait for end of calibration */ - - lgw_reg_w(LGW_EMERGENCY_FORCE_HOST_CTRL,1); /* Take back control */ - - /* Get calibration status */ - lgw_reg_r(LGW_MCU_AGC_STATUS, &read_val); - cal_status = (uint8_t)read_val; - - /* Check calibration flags - bit 0: could access SX1301 registers - bit 1: could access radio A registers - bit 2: could access radio B registers - bit 3: radio A RX image rejection successful - bit 4: radio B RX image rejection successful - bit 5: radio A TX imbalance correction successful - bit 6: radio B TX imbalance correction successful - bit 7: calibration finished */ - - if ((cal_status & 0x01) == 0) { - printf("WARNING: calibration could not access SX1301 registers\n"); - } - if ((cal_status & 0x02) == 0) { - printf("WARNING: calibration could not access radio A\n"); - } - if ((cal_status & 0x04) == 0) { - printf("WARNING: calibration could not access radio B\n"); - } - if ((cal_cmd & 0x01) && ((cal_status & 0x08) == 0)) { - printf("WARNING: problem in calibration of radio A for image rejection\n"); - } - if ((cal_cmd & 0x02) && ((cal_status & 0x10) == 0)) { - printf("WARNING: problem in calibration of radio B for image rejection\n"); - } - if ((cal_cmd & 0x04) && ((cal_status & 0x20) == 0)) { - printf("WARNING: problem in calibration of radio A for TX imbalance\n"); - } - if ((cal_cmd & 0x08) && ((cal_status & 0x40) == 0)) { - printf("WARNING: problem in calibration of radio B for TX imbalance\n"); - } - if ((cal_status & 0x80) == 0) { - printf("WARNING: Calibration not finished\n"); - } - - /* Get calibration results */ - if (cal_cmd & 0x01) { - lgw_reg_r(LGW_IQ_MISMATCH_A_AMP_COEFF, &read_val); - (*cal_res).amp_a = (int8_t)((read_val > 31) ? read_val - 64 : read_val); - lgw_reg_r(LGW_IQ_MISMATCH_A_PHI_COEFF, &read_val); - (*cal_res).phi_a = (int8_t)((read_val > 31) ? read_val - 64 : read_val); - lgw_reg_w(LGW_DBG_AGC_MCU_RAM_ADDR, 0xD0); - lgw_reg_r(LGW_DBG_AGC_MCU_RAM_DATA, &read_val); - (*cal_res).img_rej_a = (uint8_t)read_val; - lgw_reg_w(LGW_DBG_AGC_MCU_RAM_ADDR, 0xD2); - lgw_reg_r(LGW_DBG_AGC_MCU_RAM_DATA, &read_val); - (*cal_res).debug[0] = (uint8_t)read_val; - lgw_reg_w(LGW_DBG_AGC_MCU_RAM_ADDR, 0xD3); - lgw_reg_r(LGW_DBG_AGC_MCU_RAM_DATA, &read_val); - (*cal_res).debug[1] = (uint8_t)read_val; - lgw_reg_w(LGW_DBG_AGC_MCU_RAM_ADDR, 0xD4); - lgw_reg_r(LGW_DBG_AGC_MCU_RAM_DATA, &read_val); - (*cal_res).debug[2] = (uint8_t)read_val; - } - if (cal_cmd & 0x02) { - lgw_reg_r(LGW_IQ_MISMATCH_B_AMP_COEFF, &read_val); - (*cal_res).amp_b = (int8_t)((read_val > 31) ? read_val - 64 : read_val); - lgw_reg_r(LGW_IQ_MISMATCH_B_PHI_COEFF, &read_val); - (*cal_res).phi_b = (int8_t)((read_val > 31) ? read_val - 64 : read_val); - lgw_reg_w(LGW_DBG_AGC_MCU_RAM_ADDR, 0xD1); - lgw_reg_r(LGW_DBG_AGC_MCU_RAM_DATA, &read_val); - (*cal_res).img_rej_b = (uint8_t)read_val; - lgw_reg_w(LGW_DBG_AGC_MCU_RAM_ADDR, 0xD2); - lgw_reg_r(LGW_DBG_AGC_MCU_RAM_DATA, &read_val); - (*cal_res).debug[0] = (uint8_t)read_val; - lgw_reg_w(LGW_DBG_AGC_MCU_RAM_ADDR, 0xD3); - lgw_reg_r(LGW_DBG_AGC_MCU_RAM_DATA, &read_val); - (*cal_res).debug[1] = (uint8_t)read_val; - lgw_reg_w(LGW_DBG_AGC_MCU_RAM_ADDR, 0xD4); - lgw_reg_r(LGW_DBG_AGC_MCU_RAM_DATA, &read_val); - (*cal_res).debug[2] = (uint8_t)read_val; - } - if (cal_cmd & 0x04) { - for (i=0; i<=7; ++i) { - lgw_reg_w(LGW_DBG_AGC_MCU_RAM_ADDR, 0xA0+i); - lgw_reg_r(LGW_DBG_AGC_MCU_RAM_DATA, &read_val); - (*cal_res).offset_i_a[i] = (int8_t)read_val; - lgw_reg_w(LGW_DBG_AGC_MCU_RAM_ADDR, 0xA8+i); - lgw_reg_r(LGW_DBG_AGC_MCU_RAM_DATA, &read_val); - (*cal_res).offset_q_a[i] = (int8_t)read_val; - lgw_reg_w(LGW_DBG_AGC_MCU_RAM_ADDR, 0xC0+i); - lgw_reg_r(LGW_DBG_AGC_MCU_RAM_DATA, &read_val); - (*cal_res).offset_rej_a[i] = (uint8_t)read_val; - lgw_reg_w(LGW_DBG_AGC_MCU_RAM_ADDR, 0xD2+i); - lgw_reg_r(LGW_DBG_AGC_MCU_RAM_DATA, &read_val); - (*cal_res).debug[i] = (uint8_t)read_val; - } - } - if (cal_cmd & 0x08) { - for (i=0; i<=7; ++i) { - lgw_reg_w(LGW_DBG_AGC_MCU_RAM_ADDR, 0xB0+i); - lgw_reg_r(LGW_DBG_AGC_MCU_RAM_DATA, &read_val); - (*cal_res).offset_i_b[i] = (int8_t)read_val; - lgw_reg_w(LGW_DBG_AGC_MCU_RAM_ADDR, 0xB8+i); - lgw_reg_r(LGW_DBG_AGC_MCU_RAM_DATA, &read_val); - (*cal_res).offset_q_b[i] = (int8_t)read_val; - lgw_reg_w(LGW_DBG_AGC_MCU_RAM_ADDR, 0xC8+i); - lgw_reg_r(LGW_DBG_AGC_MCU_RAM_DATA, &read_val); - (*cal_res).offset_rej_b[i] = (uint8_t)read_val; - lgw_reg_w(LGW_DBG_AGC_MCU_RAM_ADDR, 0xD2+i); - lgw_reg_r(LGW_DBG_AGC_MCU_RAM_DATA, &read_val); - (*cal_res).debug[i] = (uint8_t)read_val; - } - } - - return cal_status; + int i; + int32_t read_val; + uint8_t cal_status; + + lgw_reg_w(LGW_FORCE_HOST_RADIO_CTRL,0); /* gives to AGC MCU the control of the radios */ + lgw_reg_w(LGW_RADIO_SELECT,cal_cmd); /* send calibration configuration word */ + lgw_reg_w(LGW_MCU_RST_1,1); + lgw_reg_w(LGW_MCU_RST_1,0); + lgw_reg_w(LGW_PAGE_REG,3); /* Calibration will start on this condition as soon as MCU can talk to concentrator registers */ + lgw_reg_w(LGW_EMERGENCY_FORCE_HOST_CTRL,0); /* Give control of concentrator registers to MCU */ + + wait_ms(2000); /* Wait for end of calibration */ + + lgw_reg_w(LGW_EMERGENCY_FORCE_HOST_CTRL,1); /* Take back control */ + + /* Get calibration status */ + lgw_reg_r(LGW_MCU_AGC_STATUS, &read_val); + cal_status = (uint8_t)read_val; + + /* Check calibration flags + bit 0: could access SX1301 registers + bit 1: could access radio A registers + bit 2: could access radio B registers + bit 3: radio A RX image rejection successful + bit 4: radio B RX image rejection successful + bit 5: radio A TX imbalance correction successful + bit 6: radio B TX imbalance correction successful + bit 7: calibration finished */ + + if ((cal_status & 0x01) == 0) { + printf("WARNING: calibration could not access SX1301 registers\n"); + } + if ((cal_status & 0x02) == 0) { + printf("WARNING: calibration could not access radio A\n"); + } + if ((cal_status & 0x04) == 0) { + printf("WARNING: calibration could not access radio B\n"); + } + if ((cal_cmd & 0x01) && ((cal_status & 0x08) == 0)) { + printf("WARNING: problem in calibration of radio A for image rejection\n"); + } + if ((cal_cmd & 0x02) && ((cal_status & 0x10) == 0)) { + printf("WARNING: problem in calibration of radio B for image rejection\n"); + } + if ((cal_cmd & 0x04) && ((cal_status & 0x20) == 0)) { + printf("WARNING: problem in calibration of radio A for TX imbalance\n"); + } + if ((cal_cmd & 0x08) && ((cal_status & 0x40) == 0)) { + printf("WARNING: problem in calibration of radio B for TX imbalance\n"); + } + if ((cal_status & 0x80) == 0) { + printf("WARNING: Calibration not finished\n"); + } + + /* Get calibration results */ + if (cal_cmd & 0x01) { + lgw_reg_r(LGW_IQ_MISMATCH_A_AMP_COEFF, &read_val); + (*cal_res).amp_a = (int8_t)((read_val > 31) ? read_val - 64 : read_val); + lgw_reg_r(LGW_IQ_MISMATCH_A_PHI_COEFF, &read_val); + (*cal_res).phi_a = (int8_t)((read_val > 31) ? read_val - 64 : read_val); + lgw_reg_w(LGW_DBG_AGC_MCU_RAM_ADDR, 0xD0); + lgw_reg_r(LGW_DBG_AGC_MCU_RAM_DATA, &read_val); + (*cal_res).img_rej_a = (uint8_t)read_val; + lgw_reg_w(LGW_DBG_AGC_MCU_RAM_ADDR, 0xD2); + lgw_reg_r(LGW_DBG_AGC_MCU_RAM_DATA, &read_val); + (*cal_res).debug[0] = (uint8_t)read_val; + lgw_reg_w(LGW_DBG_AGC_MCU_RAM_ADDR, 0xD3); + lgw_reg_r(LGW_DBG_AGC_MCU_RAM_DATA, &read_val); + (*cal_res).debug[1] = (uint8_t)read_val; + lgw_reg_w(LGW_DBG_AGC_MCU_RAM_ADDR, 0xD4); + lgw_reg_r(LGW_DBG_AGC_MCU_RAM_DATA, &read_val); + (*cal_res).debug[2] = (uint8_t)read_val; + } + if (cal_cmd & 0x02) { + lgw_reg_r(LGW_IQ_MISMATCH_B_AMP_COEFF, &read_val); + (*cal_res).amp_b = (int8_t)((read_val > 31) ? read_val - 64 : read_val); + lgw_reg_r(LGW_IQ_MISMATCH_B_PHI_COEFF, &read_val); + (*cal_res).phi_b = (int8_t)((read_val > 31) ? read_val - 64 : read_val); + lgw_reg_w(LGW_DBG_AGC_MCU_RAM_ADDR, 0xD1); + lgw_reg_r(LGW_DBG_AGC_MCU_RAM_DATA, &read_val); + (*cal_res).img_rej_b = (uint8_t)read_val; + lgw_reg_w(LGW_DBG_AGC_MCU_RAM_ADDR, 0xD2); + lgw_reg_r(LGW_DBG_AGC_MCU_RAM_DATA, &read_val); + (*cal_res).debug[0] = (uint8_t)read_val; + lgw_reg_w(LGW_DBG_AGC_MCU_RAM_ADDR, 0xD3); + lgw_reg_r(LGW_DBG_AGC_MCU_RAM_DATA, &read_val); + (*cal_res).debug[1] = (uint8_t)read_val; + lgw_reg_w(LGW_DBG_AGC_MCU_RAM_ADDR, 0xD4); + lgw_reg_r(LGW_DBG_AGC_MCU_RAM_DATA, &read_val); + (*cal_res).debug[2] = (uint8_t)read_val; + } + if (cal_cmd & 0x04) { + for (i=0; i<=7; ++i) { + lgw_reg_w(LGW_DBG_AGC_MCU_RAM_ADDR, 0xA0+i); + lgw_reg_r(LGW_DBG_AGC_MCU_RAM_DATA, &read_val); + (*cal_res).offset_i_a[i] = (int8_t)read_val; + lgw_reg_w(LGW_DBG_AGC_MCU_RAM_ADDR, 0xA8+i); + lgw_reg_r(LGW_DBG_AGC_MCU_RAM_DATA, &read_val); + (*cal_res).offset_q_a[i] = (int8_t)read_val; + lgw_reg_w(LGW_DBG_AGC_MCU_RAM_ADDR, 0xC0+i); + lgw_reg_r(LGW_DBG_AGC_MCU_RAM_DATA, &read_val); + (*cal_res).offset_rej_a[i] = (uint8_t)read_val; + lgw_reg_w(LGW_DBG_AGC_MCU_RAM_ADDR, 0xD2+i); + lgw_reg_r(LGW_DBG_AGC_MCU_RAM_DATA, &read_val); + (*cal_res).debug[i] = (uint8_t)read_val; + } + } + if (cal_cmd & 0x08) { + for (i=0; i<=7; ++i) { + lgw_reg_w(LGW_DBG_AGC_MCU_RAM_ADDR, 0xB0+i); + lgw_reg_r(LGW_DBG_AGC_MCU_RAM_DATA, &read_val); + (*cal_res).offset_i_b[i] = (int8_t)read_val; + lgw_reg_w(LGW_DBG_AGC_MCU_RAM_ADDR, 0xB8+i); + lgw_reg_r(LGW_DBG_AGC_MCU_RAM_DATA, &read_val); + (*cal_res).offset_q_b[i] = (int8_t)read_val; + lgw_reg_w(LGW_DBG_AGC_MCU_RAM_ADDR, 0xC8+i); + lgw_reg_r(LGW_DBG_AGC_MCU_RAM_DATA, &read_val); + (*cal_res).offset_rej_b[i] = (uint8_t)read_val; + lgw_reg_w(LGW_DBG_AGC_MCU_RAM_ADDR, 0xD2+i); + lgw_reg_r(LGW_DBG_AGC_MCU_RAM_DATA, &read_val); + (*cal_res).debug[i] = (uint8_t)read_val; + } + } + + return cal_status; } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ int read_capture(int16_t *sig_i, int16_t *sig_q, int nb_samp) { - - uint8_t read_burst[4]; - uint16_t data_i_c2; - uint16_t data_q_c2; - int i; - - lgw_reg_w(LGW_CAPTURE_RAM_ADDR, 0); - for (i=0 ; i> 4); - data_q_c2 = ((uint16_t)read_burst[1] << 4) + ((uint16_t)read_burst[0] >> 4); - sig_i[i] = (int16_t)((data_i_c2 > 2047) ? data_i_c2 - 4096 : data_i_c2); - sig_q[i] = (int16_t)((data_q_c2 > 2047) ? data_q_c2 - 4096 : data_q_c2); - } - - return 0; + + uint8_t read_burst[4]; + uint16_t data_i_c2; + uint16_t data_q_c2; + int i; + + lgw_reg_w(LGW_CAPTURE_RAM_ADDR, 0); + for (i=0 ; i> 4); + data_q_c2 = ((uint16_t)read_burst[1] << 4) + ((uint16_t)read_burst[0] >> 4); + sig_i[i] = (int16_t)((data_i_c2 > 2047) ? data_i_c2 - 4096 : data_i_c2); + sig_q[i] = (int16_t)((data_q_c2 > 2047) ? data_q_c2 - 4096 : data_q_c2); + } + + return 0; } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ uint8_t get_img_rej(int16_t *sig_i, int16_t *sig_q, int nb_samp, double f_sig_norm) { - - int i; - double phase; - double corr_sig_i, corr_sig_q, corr_sig_abs; - double corr_img_i, corr_img_q, corr_img_abs; - double img_rej; - - corr_sig_i = 0; - corr_sig_q = 0; - corr_img_i = 0; - corr_img_q = 0; - - for (i=0 ; i= 199901L - #define _XOPEN_SOURCE 600 + #define _XOPEN_SOURCE 600 #else - #define _XOPEN_SOURCE 500 + #define _XOPEN_SOURCE 500 #endif -#include /* C99 types */ -#include /* bool type */ -#include /* printf */ -#include /* memset */ -#include /* sigaction */ -#include /* exit */ -#include /* read */ +#include /* C99 types */ +#include /* bool type */ +#include /* printf */ +#include /* memset */ +#include /* sigaction */ +#include /* exit */ +#include /* read */ #include "loragw_hal.h" #include "loragw_gps.h" @@ -51,11 +51,11 @@ static void sig_handler(int sigio); /* --- PRIVATE FUNCTIONS DEFINITION ----------------------------------------- */ static void sig_handler(int sigio) { - if (sigio == SIGQUIT) { - quit_sig = 1;; - } else if ((sigio == SIGINT) || (sigio == SIGTERM)) { - exit_sig = 1; - } + if (sigio == SIGQUIT) { + quit_sig = 1;; + } else if ((sigio == SIGINT) || (sigio == SIGTERM)) { + exit_sig = 1; + } } /* -------------------------------------------------------------------------- */ @@ -63,115 +63,115 @@ static void sig_handler(int sigio) { int main() { - struct sigaction sigact; /* SIGQUIT&SIGINT&SIGTERM signal handling */ - - int i; - char tmp_str[80]; - - /* serial variables */ - char serial_buff[128]; /* buffer to receive GPS data */ - ssize_t nb_char; - int gps_tty_dev; /* file descriptor to the serial port of the GNSS module */ - - /* NMEA variables */ - enum gps_msg latest_msg; /* keep track of latest NMEA message parsed */ - - /* variables for PPM pulse GPS synchronization */ - uint32_t ppm_tstamp; - struct timespec ppm_utc; - struct tref ppm_ref; - - /* variables for timestamp <-> UTC conversions */ - uint32_t x, z; - struct timespec y; - - /* configure signal handling */ - sigemptyset(&sigact.sa_mask); - sigact.sa_flags = 0; - sigact.sa_handler = sig_handler; - sigaction(SIGQUIT, &sigact, NULL); - sigaction(SIGINT, &sigact, NULL); - sigaction(SIGTERM, &sigact, NULL); - - /* Intro message and library information */ - printf("Beginning of test for loragw_gps.c\n"); - printf("*** Library version information ***\n%s\n***\n", lgw_version_info()); - - /* Open and configure GPS */ - i = lgw_gps_enable("/dev/ttyAMA0", NULL, 0, &gps_tty_dev); - if (i != LGW_GPS_SUCCESS) { - printf("ERROR: IMPOSSIBLE TO ENABLE GPS\n"); - exit(EXIT_FAILURE); - } - - /* start concentrator */ - lgw_start(); - - /* initialize some variables before loop */ - memset(serial_buff, 0, sizeof serial_buff); - memset(&ppm_ref, 0, sizeof ppm_ref); - - /* loop until user action */ - while ((quit_sig != 1) && (exit_sig != 1)) { - /* blocking canonical read on serial port */ - nb_char = read(gps_tty_dev, serial_buff, sizeof(serial_buff)-1); - if (nb_char <= 0) { - printf("Warning: read() returned value <= 0\n"); - continue; - } else { - serial_buff[nb_char] = 0; - } - - /* parse the received NMEA */ - latest_msg = lgw_parse_nmea(serial_buff, sizeof(serial_buff)); - - if (latest_msg == NMEA_RMC) { - - printf("\n~~ RMC NMEA sentence, triggering synchronization attempt ~~\n"); - - /* get UTC time for synchronization */ - i = lgw_gps_get(&ppm_utc, NULL, NULL); - if (i != LGW_GPS_SUCCESS) { - printf(" No valid reference UTC time available, synchronization impossible.\n"); - continue; - } - /* get timestamp for synchronization */ - i = lgw_get_trigcnt(&ppm_tstamp); - if (i != LGW_HAL_SUCCESS) { - printf(" Failed to read timestamp, synchronization impossible.\n"); - continue; - } - /* try to update synchronize time reference with the new UTC & timestamp */ - i = lgw_gps_sync(&ppm_ref, ppm_tstamp, ppm_utc); - if (i != LGW_GPS_SUCCESS) { - printf(" Synchronization error.\n"); - continue; - } - /* display result */ - printf(" * Synchronization successful *\n"); - strftime(tmp_str, sizeof(tmp_str), "%F %T", gmtime(&(ppm_ref.utc.tv_sec))); - printf(" UTC reference time: %s.%09ldZ\n", tmp_str, ppm_ref.utc.tv_nsec); - printf(" Internal counter reference value: %u\n", ppm_ref.count_us); - printf(" Clock error: %.9f\n", ppm_ref.xtal_err); - - x = ppm_tstamp + 500000; - printf(" * Test of timestamp counter <-> UTC value conversion *\n"); - printf(" Test value: %u\n", x); - lgw_cnt2utc(ppm_ref, x, &y); - strftime(tmp_str, sizeof(tmp_str), "%F %T", gmtime(&(y.tv_sec))); - printf(" Conversion to UTC: %s.%09ldZ\n", tmp_str, y.tv_nsec); - lgw_utc2cnt(ppm_ref, y, &z); - printf(" Converted back: %u\n", z); - } - } - - /* clean up before leaving */ - if (exit_sig == 1) { - lgw_stop(); - } - - printf("\nEnd of test for loragw_gps.c\n"); - exit(EXIT_SUCCESS); + struct sigaction sigact; /* SIGQUIT&SIGINT&SIGTERM signal handling */ + + int i; + char tmp_str[80]; + + /* serial variables */ + char serial_buff[128]; /* buffer to receive GPS data */ + ssize_t nb_char; + int gps_tty_dev; /* file descriptor to the serial port of the GNSS module */ + + /* NMEA variables */ + enum gps_msg latest_msg; /* keep track of latest NMEA message parsed */ + + /* variables for PPM pulse GPS synchronization */ + uint32_t ppm_tstamp; + struct timespec ppm_utc; + struct tref ppm_ref; + + /* variables for timestamp <-> UTC conversions */ + uint32_t x, z; + struct timespec y; + + /* configure signal handling */ + sigemptyset(&sigact.sa_mask); + sigact.sa_flags = 0; + sigact.sa_handler = sig_handler; + sigaction(SIGQUIT, &sigact, NULL); + sigaction(SIGINT, &sigact, NULL); + sigaction(SIGTERM, &sigact, NULL); + + /* Intro message and library information */ + printf("Beginning of test for loragw_gps.c\n"); + printf("*** Library version information ***\n%s\n***\n", lgw_version_info()); + + /* Open and configure GPS */ + i = lgw_gps_enable("/dev/ttyAMA0", NULL, 0, &gps_tty_dev); + if (i != LGW_GPS_SUCCESS) { + printf("ERROR: IMPOSSIBLE TO ENABLE GPS\n"); + exit(EXIT_FAILURE); + } + + /* start concentrator */ + lgw_start(); + + /* initialize some variables before loop */ + memset(serial_buff, 0, sizeof serial_buff); + memset(&ppm_ref, 0, sizeof ppm_ref); + + /* loop until user action */ + while ((quit_sig != 1) && (exit_sig != 1)) { + /* blocking canonical read on serial port */ + nb_char = read(gps_tty_dev, serial_buff, sizeof(serial_buff)-1); + if (nb_char <= 0) { + printf("Warning: read() returned value <= 0\n"); + continue; + } else { + serial_buff[nb_char] = 0; + } + + /* parse the received NMEA */ + latest_msg = lgw_parse_nmea(serial_buff, sizeof(serial_buff)); + + if (latest_msg == NMEA_RMC) { + + printf("\n~~ RMC NMEA sentence, triggering synchronization attempt ~~\n"); + + /* get UTC time for synchronization */ + i = lgw_gps_get(&ppm_utc, NULL, NULL); + if (i != LGW_GPS_SUCCESS) { + printf(" No valid reference UTC time available, synchronization impossible.\n"); + continue; + } + /* get timestamp for synchronization */ + i = lgw_get_trigcnt(&ppm_tstamp); + if (i != LGW_HAL_SUCCESS) { + printf(" Failed to read timestamp, synchronization impossible.\n"); + continue; + } + /* try to update synchronize time reference with the new UTC & timestamp */ + i = lgw_gps_sync(&ppm_ref, ppm_tstamp, ppm_utc); + if (i != LGW_GPS_SUCCESS) { + printf(" Synchronization error.\n"); + continue; + } + /* display result */ + printf(" * Synchronization successful *\n"); + strftime(tmp_str, sizeof(tmp_str), "%F %T", gmtime(&(ppm_ref.utc.tv_sec))); + printf(" UTC reference time: %s.%09ldZ\n", tmp_str, ppm_ref.utc.tv_nsec); + printf(" Internal counter reference value: %u\n", ppm_ref.count_us); + printf(" Clock error: %.9f\n", ppm_ref.xtal_err); + + x = ppm_tstamp + 500000; + printf(" * Test of timestamp counter <-> UTC value conversion *\n"); + printf(" Test value: %u\n", x); + lgw_cnt2utc(ppm_ref, x, &y); + strftime(tmp_str, sizeof(tmp_str), "%F %T", gmtime(&(y.tv_sec))); + printf(" Conversion to UTC: %s.%09ldZ\n", tmp_str, y.tv_nsec); + lgw_utc2cnt(ppm_ref, y, &z); + printf(" Converted back: %u\n", z); + } + } + + /* clean up before leaving */ + if (exit_sig == 1) { + lgw_stop(); + } + + printf("\nEnd of test for loragw_gps.c\n"); + exit(EXIT_SUCCESS); } /* --- EOF ------------------------------------------------------------------ */ diff --git a/libloragw/tst/test_loragw_hal.c b/libloragw/tst/test_loragw_hal.c index 4857c956..2f337d55 100644 --- a/libloragw/tst/test_loragw_hal.c +++ b/libloragw/tst/test_loragw_hal.c @@ -7,7 +7,7 @@ (C)2013 Semtech-Cycleo Description: - Minimum test program for the loragw_hal 'library' + Minimum test program for the loragw_hal 'library' License: Revised BSD License, see LICENSE.TXT file include in the project Maintainer: Sylvain Miermont @@ -19,17 +19,17 @@ Maintainer: Sylvain Miermont /* fix an issue between POSIX and C99 */ #if __STDC_VERSION__ >= 199901L - #define _XOPEN_SOURCE 600 + #define _XOPEN_SOURCE 600 #else - #define _XOPEN_SOURCE 500 + #define _XOPEN_SOURCE 500 #endif -#include /* C99 types */ -#include /* bool type */ -#include /* printf */ -#include /* memset */ -#include /* sigaction */ -#include /* getopt access */ +#include /* C99 types */ +#include /* bool type */ +#include /* printf */ +#include /* memset */ +#include /* sigaction */ +#include /* getopt access */ #include "loragw_hal.h" #include "loragw_reg.h" @@ -60,23 +60,23 @@ static void sig_handler(int sigio); /* --- PRIVATE FUNCTIONS DEFINITION ----------------------------------------- */ static void sig_handler(int sigio) { - if (sigio == SIGQUIT) { - quit_sig = 1;; - } else if ((sigio == SIGINT) || (sigio == SIGTERM)) { - exit_sig = 1; - } + if (sigio == SIGQUIT) { + quit_sig = 1;; + } else if ((sigio == SIGINT) || (sigio == SIGTERM)) { + exit_sig = 1; + } } /* describe command line options */ void usage(void) { - printf("Library version information: %s\n", lgw_version_info()); - printf( "Available options:\n"); - printf( " -h print this help\n"); - printf( " -a Radio A RX frequency in MHz\n"); - printf( " -b Radio B RX frequency in MHz\n"); - printf( " -t Radio TX frequency in MHz\n"); - printf( " -r Radio type (SX1255:1255, SX1257:1257)\n"); - printf( " -k Concentrator clock source (0: radio_A, 1: radio_B(default))\n"); + printf("Library version information: %s\n", lgw_version_info()); + printf( "Available options:\n"); + printf( " -h print this help\n"); + printf( " -a Radio A RX frequency in MHz\n"); + printf( " -b Radio B RX frequency in MHz\n"); + printf( " -t Radio TX frequency in MHz\n"); + printf( " -r Radio type (SX1255:1255, SX1257:1257)\n"); + printf( " -k Concentrator clock source (0: radio_A, 1: radio_B(default))\n"); } /* -------------------------------------------------------------------------- */ @@ -84,331 +84,331 @@ void usage(void) { int main(int argc, char **argv) { - struct sigaction sigact; /* SIGQUIT&SIGINT&SIGTERM signal handling */ - - struct lgw_conf_board_s boardconf; - struct lgw_conf_rxrf_s rfconf; - struct lgw_conf_rxif_s ifconf; - - struct lgw_pkt_rx_s rxpkt[4]; /* array containing up to 4 inbound packets metadata */ - struct lgw_pkt_tx_s txpkt; /* configuration and metadata for an outbound packet */ - struct lgw_pkt_rx_s *p; /* pointer on a RX packet */ - - int i, j; - int nb_pkt; - uint32_t fa = 0, fb = 0, ft = 0; - enum lgw_radio_type_e radio_type = LGW_RADIO_TYPE_NONE; - uint8_t clocksource = 1; /* Radio B is source by default */ - - uint32_t tx_cnt = 0; - unsigned long loop_cnt = 0; - uint8_t status_var = 0; - double xd = 0.0; - int xi = 0; - - /* parse command line options */ - while ((i = getopt (argc, argv, "ha:b:t:r:k:")) != -1) { - switch (i) { - case 'h': - usage(); - return -1; - break; - case 'a': /* Radio A RX frequency in MHz */ - sscanf(optarg, "%lf", &xd); - fa = (uint32_t)((xd*1e6) + 0.5); /* .5 Hz offset to get rounding instead of truncating */ - break; - case 'b': /* Radio B RX frequency in MHz */ - sscanf(optarg, "%lf", &xd); - fb = (uint32_t)((xd*1e6) + 0.5); /* .5 Hz offset to get rounding instead of truncating */ - break; - case 't': /* Radio TX frequency in MHz */ - sscanf(optarg, "%lf", &xd); - ft = (uint32_t)((xd*1e6) + 0.5); /* .5 Hz offset to get rounding instead of truncating */ - break; - case 'r': /* Radio type (1255, 1257) */ - sscanf(optarg, "%i", &xi); - switch (xi) { - case 1255: - radio_type = LGW_RADIO_TYPE_SX1255; - break; - case 1257: - radio_type = LGW_RADIO_TYPE_SX1257; - break; - default: - printf("ERROR: invalid radio type\n"); - usage(); - return -1; - } - break; - case 'k': /* Concentrator clock source (Radio A or Radio B) */ - sscanf(optarg, "%i", &xi); - clocksource = (uint8_t)xi; - break; - default: - printf("ERROR: argument parsing\n"); - usage(); - return -1; - } - } - - /* check input parameters */ - if ((fa == 0) || (fb == 0) || (ft == 0)) { - printf("ERROR: missing frequency input parameter:\n"); - printf(" Radio A RX: %u\n", fa); - printf(" Radio B RX: %u\n", fb); - printf(" Radio TX: %u\n", ft); - usage(); - return -1; - } - - if (radio_type == LGW_RADIO_TYPE_NONE) { - printf("ERROR: missing radio type parameter:\n"); - usage(); - return -1; - } - - /* configure signal handling */ - sigemptyset(&sigact.sa_mask); - sigact.sa_flags = 0; - sigact.sa_handler = sig_handler; - sigaction(SIGQUIT, &sigact, NULL); - sigaction(SIGINT, &sigact, NULL); - sigaction(SIGTERM, &sigact, NULL); - - /* beginning of LoRa concentrator-specific code */ - printf("Beginning of test for loragw_hal.c\n"); - - printf("*** Library version information ***\n%s\n\n", lgw_version_info()); - - /* set configuration for board */ - memset(&boardconf, 0, sizeof(boardconf)); - - boardconf.lorawan_public = true; - boardconf.clksrc = clocksource; - lgw_board_setconf(boardconf); - - /* set configuration for RF chains */ - memset(&rfconf, 0, sizeof(rfconf)); - - rfconf.enable = true; - rfconf.freq_hz = fa; - rfconf.rssi_offset = DEFAULT_RSSI_OFFSET; - rfconf.type = radio_type; - rfconf.tx_enable = true; - lgw_rxrf_setconf(0, rfconf); /* radio A, f0 */ - - rfconf.enable = true; - rfconf.freq_hz = fb; - rfconf.rssi_offset = DEFAULT_RSSI_OFFSET; - rfconf.type = radio_type; - rfconf.tx_enable = false; - lgw_rxrf_setconf(1, rfconf); /* radio B, f1 */ - - /* set configuration for LoRa multi-SF channels (bandwidth cannot be set) */ - memset(&ifconf, 0, sizeof(ifconf)); - - ifconf.enable = true; - ifconf.rf_chain = 1; - ifconf.freq_hz = -400000; - ifconf.datarate = DR_LORA_MULTI; - lgw_rxif_setconf(0, ifconf); /* chain 0: LoRa 125kHz, all SF, on f1 - 0.4 MHz */ - - ifconf.enable = true; - ifconf.rf_chain = 1; - ifconf.freq_hz = -200000; - ifconf.datarate = DR_LORA_MULTI; - lgw_rxif_setconf(1, ifconf); /* chain 1: LoRa 125kHz, all SF, on f1 - 0.2 MHz */ - - ifconf.enable = true; - ifconf.rf_chain = 1; - ifconf.freq_hz = 0; - ifconf.datarate = DR_LORA_MULTI; - lgw_rxif_setconf(2, ifconf); /* chain 2: LoRa 125kHz, all SF, on f1 - 0.0 MHz */ - - ifconf.enable = true; - ifconf.rf_chain = 0; - ifconf.freq_hz = -400000; - ifconf.datarate = DR_LORA_MULTI; - lgw_rxif_setconf(3, ifconf); /* chain 3: LoRa 125kHz, all SF, on f0 - 0.4 MHz */ - - ifconf.enable = true; - ifconf.rf_chain = 0; - ifconf.freq_hz = -200000; - ifconf.datarate = DR_LORA_MULTI; - lgw_rxif_setconf(4, ifconf); /* chain 4: LoRa 125kHz, all SF, on f0 - 0.2 MHz */ - - ifconf.enable = true; - ifconf.rf_chain = 0; - ifconf.freq_hz = 0; - ifconf.datarate = DR_LORA_MULTI; - lgw_rxif_setconf(5, ifconf); /* chain 5: LoRa 125kHz, all SF, on f0 + 0.0 MHz */ - - ifconf.enable = true; - ifconf.rf_chain = 0; - ifconf.freq_hz = 200000; - ifconf.datarate = DR_LORA_MULTI; - lgw_rxif_setconf(6, ifconf); /* chain 6: LoRa 125kHz, all SF, on f0 + 0.2 MHz */ - - ifconf.enable = true; - ifconf.rf_chain = 0; - ifconf.freq_hz = 400000; - ifconf.datarate = DR_LORA_MULTI; - lgw_rxif_setconf(7, ifconf); /* chain 7: LoRa 125kHz, all SF, on f0 + 0.4 MHz */ - - /* set configuration for LoRa 'stand alone' channel */ - memset(&ifconf, 0, sizeof(ifconf)); - ifconf.enable = true; - ifconf.rf_chain = 0; - ifconf.freq_hz = 0; - ifconf.bandwidth = BW_250KHZ; - ifconf.datarate = DR_LORA_SF10; - lgw_rxif_setconf(8, ifconf); /* chain 8: LoRa 250kHz, SF10, on f0 MHz */ - - /* set configuration for FSK channel */ - memset(&ifconf, 0, sizeof(ifconf)); - ifconf.enable = true; - ifconf.rf_chain = 1; - ifconf.freq_hz = 0; - ifconf.bandwidth = BW_250KHZ; - ifconf.datarate = 64000; - lgw_rxif_setconf(9, ifconf); /* chain 9: FSK 64kbps, on f1 MHz */ - - /* set configuration for TX packet */ - memset(&txpkt, 0, sizeof(txpkt)); - txpkt.freq_hz = ft; - txpkt.tx_mode = IMMEDIATE; - txpkt.rf_power = 10; - txpkt.modulation = MOD_LORA; - txpkt.bandwidth = BW_125KHZ; - txpkt.datarate = DR_LORA_SF9; - txpkt.coderate = CR_LORA_4_5; - strcpy((char *)txpkt.payload, "TX.TEST.LORA.GW.????" ); - txpkt.size = 20; - txpkt.preamble = 6; - txpkt.rf_chain = 0; -/* - memset(&txpkt, 0, sizeof(txpkt)); - txpkt.freq_hz = F_TX; - txpkt.tx_mode = IMMEDIATE; - txpkt.rf_power = 10; - txpkt.modulation = MOD_FSK; - txpkt.f_dev = 50; - txpkt.datarate = 64000; - strcpy((char *)txpkt.payload, "TX.TEST.LORA.GW.????" ); - txpkt.size = 20; - txpkt.preamble = 4; - txpkt.rf_chain = 0; -*/ - - /* connect, configure and start the LoRa concentrator */ - i = lgw_start(); - if (i == LGW_HAL_SUCCESS) { - printf("*** Concentrator started ***\n"); - } else { - printf("*** Impossible to start concentrator ***\n"); - return -1; - } - - /* once configured, dump content of registers to a file, for reference */ - // FILE * reg_dump = NULL; - // reg_dump = fopen("reg_dump.log", "w"); - // if (reg_dump != NULL) { - // lgw_reg_check(reg_dump); - // fclose(reg_dump); - // } - - while ((quit_sig != 1) && (exit_sig != 1)) { - loop_cnt++; - - /* fetch N packets */ - nb_pkt = lgw_receive(ARRAY_SIZE(rxpkt), rxpkt); - - if (nb_pkt == 0) { - wait_ms(300); - } else { - /* display received packets */ - for(i=0; i < nb_pkt; ++i) { - p = &rxpkt[i]; - printf("---\nRcv pkt #%d >>", i+1); - if (p->status == STAT_CRC_OK) { - printf(" if_chain:%2d", p->if_chain); - printf(" tstamp:%010u", p->count_us); - printf(" size:%3u", p->size); - switch (p-> modulation) { - case MOD_LORA: printf(" LoRa"); break; - case MOD_FSK: printf(" FSK"); break; - default: printf(" modulation?"); - } - switch (p->datarate) { - case DR_LORA_SF7: printf(" SF7"); break; - case DR_LORA_SF8: printf(" SF8"); break; - case DR_LORA_SF9: printf(" SF9"); break; - case DR_LORA_SF10: printf(" SF10"); break; - case DR_LORA_SF11: printf(" SF11"); break; - case DR_LORA_SF12: printf(" SF12"); break; - default: printf(" datarate?"); - } - switch (p->coderate) { - case CR_LORA_4_5: printf(" CR1(4/5)"); break; - case CR_LORA_4_6: printf(" CR2(2/3)"); break; - case CR_LORA_4_7: printf(" CR3(4/7)"); break; - case CR_LORA_4_8: printf(" CR4(1/2)"); break; - default: printf(" coderate?"); - } - printf("\n"); - printf(" RSSI:%+6.1f SNR:%+5.1f (min:%+5.1f, max:%+5.1f) payload:\n", p->rssi, p->snr, p->snr_min, p->snr_max); - - for (j = 0; j < p->size; ++j) { - printf(" %02X", p->payload[j]); - } - printf(" #\n"); - } else if (p->status == STAT_CRC_BAD) { - printf(" if_chain:%2d", p->if_chain); - printf(" tstamp:%010u", p->count_us); - printf(" size:%3u\n", p->size); - printf(" CRC error, damaged packet\n\n"); - } else if (p->status == STAT_NO_CRC){ - printf(" if_chain:%2d", p->if_chain); - printf(" tstamp:%010u", p->count_us); - printf(" size:%3u\n", p->size); - printf(" no CRC\n\n"); - } else { - printf(" if_chain:%2d", p->if_chain); - printf(" tstamp:%010u", p->count_us); - printf(" size:%3u\n", p->size); - printf(" invalid status ?!?\n\n"); - } - } - } - - /* send a packet every X loop */ - if (loop_cnt%16 == 0) { - /* 32b counter in the payload, big endian */ - txpkt.payload[16] = 0xff & (tx_cnt >> 24); - txpkt.payload[17] = 0xff & (tx_cnt >> 16); - txpkt.payload[18] = 0xff & (tx_cnt >> 8); - txpkt.payload[19] = 0xff & tx_cnt; - i = lgw_send(txpkt); /* non-blocking scheduling of TX packet */ - j = 0; - printf("+++\nSending packet #%d, rf path %d, return %d\nstatus -> ", tx_cnt, txpkt.rf_chain, i); - do { - ++j; - wait_ms(100); - lgw_status(TX_STATUS, &status_var); /* get TX status */ - printf("%d:", status_var); - } while ((status_var != TX_FREE) && (j < 100)); - ++tx_cnt; - printf("\nTX finished\n"); - } - } - - if (exit_sig == 1) { - /* clean up before leaving */ - lgw_stop(); - } - - printf("\nEnd of test for loragw_hal.c\n"); - return 0; + struct sigaction sigact; /* SIGQUIT&SIGINT&SIGTERM signal handling */ + + struct lgw_conf_board_s boardconf; + struct lgw_conf_rxrf_s rfconf; + struct lgw_conf_rxif_s ifconf; + + struct lgw_pkt_rx_s rxpkt[4]; /* array containing up to 4 inbound packets metadata */ + struct lgw_pkt_tx_s txpkt; /* configuration and metadata for an outbound packet */ + struct lgw_pkt_rx_s *p; /* pointer on a RX packet */ + + int i, j; + int nb_pkt; + uint32_t fa = 0, fb = 0, ft = 0; + enum lgw_radio_type_e radio_type = LGW_RADIO_TYPE_NONE; + uint8_t clocksource = 1; /* Radio B is source by default */ + + uint32_t tx_cnt = 0; + unsigned long loop_cnt = 0; + uint8_t status_var = 0; + double xd = 0.0; + int xi = 0; + + /* parse command line options */ + while ((i = getopt (argc, argv, "ha:b:t:r:k:")) != -1) { + switch (i) { + case 'h': + usage(); + return -1; + break; + case 'a': /* Radio A RX frequency in MHz */ + sscanf(optarg, "%lf", &xd); + fa = (uint32_t)((xd*1e6) + 0.5); /* .5 Hz offset to get rounding instead of truncating */ + break; + case 'b': /* Radio B RX frequency in MHz */ + sscanf(optarg, "%lf", &xd); + fb = (uint32_t)((xd*1e6) + 0.5); /* .5 Hz offset to get rounding instead of truncating */ + break; + case 't': /* Radio TX frequency in MHz */ + sscanf(optarg, "%lf", &xd); + ft = (uint32_t)((xd*1e6) + 0.5); /* .5 Hz offset to get rounding instead of truncating */ + break; + case 'r': /* Radio type (1255, 1257) */ + sscanf(optarg, "%i", &xi); + switch (xi) { + case 1255: + radio_type = LGW_RADIO_TYPE_SX1255; + break; + case 1257: + radio_type = LGW_RADIO_TYPE_SX1257; + break; + default: + printf("ERROR: invalid radio type\n"); + usage(); + return -1; + } + break; + case 'k': /* Concentrator clock source (Radio A or Radio B) */ + sscanf(optarg, "%i", &xi); + clocksource = (uint8_t)xi; + break; + default: + printf("ERROR: argument parsing\n"); + usage(); + return -1; + } + } + + /* check input parameters */ + if ((fa == 0) || (fb == 0) || (ft == 0)) { + printf("ERROR: missing frequency input parameter:\n"); + printf(" Radio A RX: %u\n", fa); + printf(" Radio B RX: %u\n", fb); + printf(" Radio TX: %u\n", ft); + usage(); + return -1; + } + + if (radio_type == LGW_RADIO_TYPE_NONE) { + printf("ERROR: missing radio type parameter:\n"); + usage(); + return -1; + } + + /* configure signal handling */ + sigemptyset(&sigact.sa_mask); + sigact.sa_flags = 0; + sigact.sa_handler = sig_handler; + sigaction(SIGQUIT, &sigact, NULL); + sigaction(SIGINT, &sigact, NULL); + sigaction(SIGTERM, &sigact, NULL); + + /* beginning of LoRa concentrator-specific code */ + printf("Beginning of test for loragw_hal.c\n"); + + printf("*** Library version information ***\n%s\n\n", lgw_version_info()); + + /* set configuration for board */ + memset(&boardconf, 0, sizeof(boardconf)); + + boardconf.lorawan_public = true; + boardconf.clksrc = clocksource; + lgw_board_setconf(boardconf); + + /* set configuration for RF chains */ + memset(&rfconf, 0, sizeof(rfconf)); + + rfconf.enable = true; + rfconf.freq_hz = fa; + rfconf.rssi_offset = DEFAULT_RSSI_OFFSET; + rfconf.type = radio_type; + rfconf.tx_enable = true; + lgw_rxrf_setconf(0, rfconf); /* radio A, f0 */ + + rfconf.enable = true; + rfconf.freq_hz = fb; + rfconf.rssi_offset = DEFAULT_RSSI_OFFSET; + rfconf.type = radio_type; + rfconf.tx_enable = false; + lgw_rxrf_setconf(1, rfconf); /* radio B, f1 */ + + /* set configuration for LoRa multi-SF channels (bandwidth cannot be set) */ + memset(&ifconf, 0, sizeof(ifconf)); + + ifconf.enable = true; + ifconf.rf_chain = 1; + ifconf.freq_hz = -400000; + ifconf.datarate = DR_LORA_MULTI; + lgw_rxif_setconf(0, ifconf); /* chain 0: LoRa 125kHz, all SF, on f1 - 0.4 MHz */ + + ifconf.enable = true; + ifconf.rf_chain = 1; + ifconf.freq_hz = -200000; + ifconf.datarate = DR_LORA_MULTI; + lgw_rxif_setconf(1, ifconf); /* chain 1: LoRa 125kHz, all SF, on f1 - 0.2 MHz */ + + ifconf.enable = true; + ifconf.rf_chain = 1; + ifconf.freq_hz = 0; + ifconf.datarate = DR_LORA_MULTI; + lgw_rxif_setconf(2, ifconf); /* chain 2: LoRa 125kHz, all SF, on f1 - 0.0 MHz */ + + ifconf.enable = true; + ifconf.rf_chain = 0; + ifconf.freq_hz = -400000; + ifconf.datarate = DR_LORA_MULTI; + lgw_rxif_setconf(3, ifconf); /* chain 3: LoRa 125kHz, all SF, on f0 - 0.4 MHz */ + + ifconf.enable = true; + ifconf.rf_chain = 0; + ifconf.freq_hz = -200000; + ifconf.datarate = DR_LORA_MULTI; + lgw_rxif_setconf(4, ifconf); /* chain 4: LoRa 125kHz, all SF, on f0 - 0.2 MHz */ + + ifconf.enable = true; + ifconf.rf_chain = 0; + ifconf.freq_hz = 0; + ifconf.datarate = DR_LORA_MULTI; + lgw_rxif_setconf(5, ifconf); /* chain 5: LoRa 125kHz, all SF, on f0 + 0.0 MHz */ + + ifconf.enable = true; + ifconf.rf_chain = 0; + ifconf.freq_hz = 200000; + ifconf.datarate = DR_LORA_MULTI; + lgw_rxif_setconf(6, ifconf); /* chain 6: LoRa 125kHz, all SF, on f0 + 0.2 MHz */ + + ifconf.enable = true; + ifconf.rf_chain = 0; + ifconf.freq_hz = 400000; + ifconf.datarate = DR_LORA_MULTI; + lgw_rxif_setconf(7, ifconf); /* chain 7: LoRa 125kHz, all SF, on f0 + 0.4 MHz */ + + /* set configuration for LoRa 'stand alone' channel */ + memset(&ifconf, 0, sizeof(ifconf)); + ifconf.enable = true; + ifconf.rf_chain = 0; + ifconf.freq_hz = 0; + ifconf.bandwidth = BW_250KHZ; + ifconf.datarate = DR_LORA_SF10; + lgw_rxif_setconf(8, ifconf); /* chain 8: LoRa 250kHz, SF10, on f0 MHz */ + + /* set configuration for FSK channel */ + memset(&ifconf, 0, sizeof(ifconf)); + ifconf.enable = true; + ifconf.rf_chain = 1; + ifconf.freq_hz = 0; + ifconf.bandwidth = BW_250KHZ; + ifconf.datarate = 64000; + lgw_rxif_setconf(9, ifconf); /* chain 9: FSK 64kbps, on f1 MHz */ + + /* set configuration for TX packet */ + memset(&txpkt, 0, sizeof(txpkt)); + txpkt.freq_hz = ft; + txpkt.tx_mode = IMMEDIATE; + txpkt.rf_power = 10; + txpkt.modulation = MOD_LORA; + txpkt.bandwidth = BW_125KHZ; + txpkt.datarate = DR_LORA_SF9; + txpkt.coderate = CR_LORA_4_5; + strcpy((char *)txpkt.payload, "TX.TEST.LORA.GW.????" ); + txpkt.size = 20; + txpkt.preamble = 6; + txpkt.rf_chain = 0; +/* + memset(&txpkt, 0, sizeof(txpkt)); + txpkt.freq_hz = F_TX; + txpkt.tx_mode = IMMEDIATE; + txpkt.rf_power = 10; + txpkt.modulation = MOD_FSK; + txpkt.f_dev = 50; + txpkt.datarate = 64000; + strcpy((char *)txpkt.payload, "TX.TEST.LORA.GW.????" ); + txpkt.size = 20; + txpkt.preamble = 4; + txpkt.rf_chain = 0; +*/ + + /* connect, configure and start the LoRa concentrator */ + i = lgw_start(); + if (i == LGW_HAL_SUCCESS) { + printf("*** Concentrator started ***\n"); + } else { + printf("*** Impossible to start concentrator ***\n"); + return -1; + } + + /* once configured, dump content of registers to a file, for reference */ + // FILE * reg_dump = NULL; + // reg_dump = fopen("reg_dump.log", "w"); + // if (reg_dump != NULL) { + // lgw_reg_check(reg_dump); + // fclose(reg_dump); + // } + + while ((quit_sig != 1) && (exit_sig != 1)) { + loop_cnt++; + + /* fetch N packets */ + nb_pkt = lgw_receive(ARRAY_SIZE(rxpkt), rxpkt); + + if (nb_pkt == 0) { + wait_ms(300); + } else { + /* display received packets */ + for(i=0; i < nb_pkt; ++i) { + p = &rxpkt[i]; + printf("---\nRcv pkt #%d >>", i+1); + if (p->status == STAT_CRC_OK) { + printf(" if_chain:%2d", p->if_chain); + printf(" tstamp:%010u", p->count_us); + printf(" size:%3u", p->size); + switch (p-> modulation) { + case MOD_LORA: printf(" LoRa"); break; + case MOD_FSK: printf(" FSK"); break; + default: printf(" modulation?"); + } + switch (p->datarate) { + case DR_LORA_SF7: printf(" SF7"); break; + case DR_LORA_SF8: printf(" SF8"); break; + case DR_LORA_SF9: printf(" SF9"); break; + case DR_LORA_SF10: printf(" SF10"); break; + case DR_LORA_SF11: printf(" SF11"); break; + case DR_LORA_SF12: printf(" SF12"); break; + default: printf(" datarate?"); + } + switch (p->coderate) { + case CR_LORA_4_5: printf(" CR1(4/5)"); break; + case CR_LORA_4_6: printf(" CR2(2/3)"); break; + case CR_LORA_4_7: printf(" CR3(4/7)"); break; + case CR_LORA_4_8: printf(" CR4(1/2)"); break; + default: printf(" coderate?"); + } + printf("\n"); + printf(" RSSI:%+6.1f SNR:%+5.1f (min:%+5.1f, max:%+5.1f) payload:\n", p->rssi, p->snr, p->snr_min, p->snr_max); + + for (j = 0; j < p->size; ++j) { + printf(" %02X", p->payload[j]); + } + printf(" #\n"); + } else if (p->status == STAT_CRC_BAD) { + printf(" if_chain:%2d", p->if_chain); + printf(" tstamp:%010u", p->count_us); + printf(" size:%3u\n", p->size); + printf(" CRC error, damaged packet\n\n"); + } else if (p->status == STAT_NO_CRC){ + printf(" if_chain:%2d", p->if_chain); + printf(" tstamp:%010u", p->count_us); + printf(" size:%3u\n", p->size); + printf(" no CRC\n\n"); + } else { + printf(" if_chain:%2d", p->if_chain); + printf(" tstamp:%010u", p->count_us); + printf(" size:%3u\n", p->size); + printf(" invalid status ?!?\n\n"); + } + } + } + + /* send a packet every X loop */ + if (loop_cnt%16 == 0) { + /* 32b counter in the payload, big endian */ + txpkt.payload[16] = 0xff & (tx_cnt >> 24); + txpkt.payload[17] = 0xff & (tx_cnt >> 16); + txpkt.payload[18] = 0xff & (tx_cnt >> 8); + txpkt.payload[19] = 0xff & tx_cnt; + i = lgw_send(txpkt); /* non-blocking scheduling of TX packet */ + j = 0; + printf("+++\nSending packet #%d, rf path %d, return %d\nstatus -> ", tx_cnt, txpkt.rf_chain, i); + do { + ++j; + wait_ms(100); + lgw_status(TX_STATUS, &status_var); /* get TX status */ + printf("%d:", status_var); + } while ((status_var != TX_FREE) && (j < 100)); + ++tx_cnt; + printf("\nTX finished\n"); + } + } + + if (exit_sig == 1) { + /* clean up before leaving */ + lgw_stop(); + } + + printf("\nEnd of test for loragw_hal.c\n"); + return 0; } /* --- EOF ------------------------------------------------------------------ */ diff --git a/libloragw/tst/test_loragw_reg.c b/libloragw/tst/test_loragw_reg.c index 7b3e11b4..a43dc533 100644 --- a/libloragw/tst/test_loragw_reg.c +++ b/libloragw/tst/test_loragw_reg.c @@ -7,7 +7,7 @@ (C)2013 Semtech-Cycleo Description: - Minimum test program for the loragw_spi 'library' + Minimum test program for the loragw_spi 'library' License: Revised BSD License, see LICENSE.TXT file include in the project Maintainer: Sylvain Miermont @@ -25,110 +25,110 @@ Maintainer: Sylvain Miermont /* -------------------------------------------------------------------------- */ /* --- MAIN FUNCTION -------------------------------------------------------- */ -#define BURST_TEST_LENGTH 8192 +#define BURST_TEST_LENGTH 8192 int main() { - int32_t read_value, test_value; - uint16_t lfsr; - uint8_t burst_buffout[BURST_TEST_LENGTH]; - uint8_t burst_buffin[BURST_TEST_LENGTH]; - int i; - - printf("Beginning of test for loragw_reg.c\n"); - - lgw_connect(); - /* 2 SPI transactions: - -> 0x80 0x00 <- 0x00 0x00 forcing page 0 - -> 0x01 0x00 <- 0x00 0x64 checking version - */ - - /* --- READ TEST --- */ - - lgw_reg_w(LGW_SOFT_RESET, 1); - lgw_reg_check(stdout); - - /* --- READ/WRITE COHERENCY TEST --- */ - - /* 8b unsigned */ - test_value = 197; /* 11000101b */ - lgw_reg_w(LGW_IMPLICIT_PAYLOAD_LENGHT, test_value); - lgw_reg_r(LGW_IMPLICIT_PAYLOAD_LENGHT, &read_value); - printf("IMPLICIT_PAYLOAD_LENGHT = %d (should be %d)\n", read_value, test_value); - - /* 8b signed */ - /* NO SUCH REG AVAILABLE */ - // /* RADIO_SELECT is normally unsigned, modify it manually in loragw_reg.c */ - // test_value = -59; /* 11000101b */ - // lgw_reg_w(LGW_RADIO_SELECT, test_value); - // lgw_reg_r(LGW_RADIO_SELECT, &read_value); - // printf("RADIO_SELECT = %d (should be %d)\n", read_value, test_value); - - /* less than 8b, with offset, unsigned */ - test_value = 11; /* 1011b */ - lgw_reg_w(LGW_FRAME_SYNCH_PEAK2_POS, test_value); - lgw_reg_r(LGW_FRAME_SYNCH_PEAK2_POS, &read_value); - printf("FRAME_SYNCH_PEAK2_POS = %d (should be %d)\n", read_value, test_value); - - /* less than 8b, with offset, signed */ - /* NO SUCH REG AVAILABLE */ - // /* MBWSSF_FRAME_SYNCH_PEAK2_POS is normally unsigned, modify it manually in loragw_reg.c */ - // test_value = -5; /* 1011b */ - // lgw_reg_w(LGW_MBWSSF_FRAME_SYNCH_PEAK2_POS, test_value); - // lgw_reg_r(LGW_MBWSSF_FRAME_SYNCH_PEAK2_POS, &read_value); - // printf("MBWSSF_FRAME_SYNCH_PEAK2_POS = %d (should be %d)\n", read_value, test_value); - - /* 16b unsigned */ - test_value = 49253; /* 11000000 01100101b */ - lgw_reg_w(LGW_PREAMBLE_SYMB1_NB, test_value); - lgw_reg_r(LGW_PREAMBLE_SYMB1_NB, &read_value); - printf("PREAMBLE_SYMB1_NB = %d (should be %d)\n", read_value, test_value); - - /* 16b signed */ - /* NO SUCH REG AVAILABLE */ - // /* CAPTURE_PERIOD is normally unsigned, modify it manually in loragw_reg.c */ - // test_value = -16283; /* 11000000 01100101b */ - // lgw_reg_w(LGW_CAPTURE_PERIOD, test_value); - // lgw_reg_r(LGW_CAPTURE_PERIOD, &read_value); - // printf("CAPTURE_PERIOD = %d (should be %d)\n", read_value, test_value); - - /* between 8b and 16b, unsigned */ - test_value = 3173; /* 1100 01100101b */ - lgw_reg_w(LGW_ADJUST_MODEM_START_OFFSET_SF12_RDX4, test_value); - lgw_reg_r(LGW_ADJUST_MODEM_START_OFFSET_SF12_RDX4, &read_value); - printf("ADJUST_MODEM_START_OFFSET_SF12_RDX4 = %d (should be %d)\n", read_value, test_value); - - /* between 8b and 16b, signed */ - test_value = -1947; /* 11000 01100101b */ - lgw_reg_w(LGW_IF_FREQ_1, test_value); - lgw_reg_r(LGW_IF_FREQ_1, &read_value); - printf("IF_FREQ_1 = %d (should be %d)\n", read_value, test_value); - - /* --- BURST WRITE AND READ TEST --- */ - - /* initialize data for SPI test */ - lfsr = 0xFFFF; - for(i=0; i> 4)); - /* printf("%05d # 0x%04x 0x%02x\n", i, lfsr, burst_buffout[i]); */ - lfsr = (lfsr & 1) ? ((lfsr >> 1) ^ 0x8679) : (lfsr >> 1); - } - - lgw_reg_wb(LGW_TX_DATA_BUF_DATA, burst_buffout, 256); - lgw_reg_rb(LGW_RX_DATA_BUF_DATA, burst_buffin, 256); - - /* impossible to check in software, - RX_DATA_BUF_DATA is read-only, - TX_DATA_BUF_DATA is write only, - use a logic analyser */ - - /* --- END OF TEST --- */ - - lgw_disconnect(); - /* no SPI transaction */ - - printf("End of test for loragw_reg.c\n"); - return 0; + int32_t read_value, test_value; + uint16_t lfsr; + uint8_t burst_buffout[BURST_TEST_LENGTH]; + uint8_t burst_buffin[BURST_TEST_LENGTH]; + int i; + + printf("Beginning of test for loragw_reg.c\n"); + + lgw_connect(); + /* 2 SPI transactions: + -> 0x80 0x00 <- 0x00 0x00 forcing page 0 + -> 0x01 0x00 <- 0x00 0x64 checking version + */ + + /* --- READ TEST --- */ + + lgw_reg_w(LGW_SOFT_RESET, 1); + lgw_reg_check(stdout); + + /* --- READ/WRITE COHERENCY TEST --- */ + + /* 8b unsigned */ + test_value = 197; /* 11000101b */ + lgw_reg_w(LGW_IMPLICIT_PAYLOAD_LENGHT, test_value); + lgw_reg_r(LGW_IMPLICIT_PAYLOAD_LENGHT, &read_value); + printf("IMPLICIT_PAYLOAD_LENGHT = %d (should be %d)\n", read_value, test_value); + + /* 8b signed */ + /* NO SUCH REG AVAILABLE */ + // /* RADIO_SELECT is normally unsigned, modify it manually in loragw_reg.c */ + // test_value = -59; /* 11000101b */ + // lgw_reg_w(LGW_RADIO_SELECT, test_value); + // lgw_reg_r(LGW_RADIO_SELECT, &read_value); + // printf("RADIO_SELECT = %d (should be %d)\n", read_value, test_value); + + /* less than 8b, with offset, unsigned */ + test_value = 11; /* 1011b */ + lgw_reg_w(LGW_FRAME_SYNCH_PEAK2_POS, test_value); + lgw_reg_r(LGW_FRAME_SYNCH_PEAK2_POS, &read_value); + printf("FRAME_SYNCH_PEAK2_POS = %d (should be %d)\n", read_value, test_value); + + /* less than 8b, with offset, signed */ + /* NO SUCH REG AVAILABLE */ + // /* MBWSSF_FRAME_SYNCH_PEAK2_POS is normally unsigned, modify it manually in loragw_reg.c */ + // test_value = -5; /* 1011b */ + // lgw_reg_w(LGW_MBWSSF_FRAME_SYNCH_PEAK2_POS, test_value); + // lgw_reg_r(LGW_MBWSSF_FRAME_SYNCH_PEAK2_POS, &read_value); + // printf("MBWSSF_FRAME_SYNCH_PEAK2_POS = %d (should be %d)\n", read_value, test_value); + + /* 16b unsigned */ + test_value = 49253; /* 11000000 01100101b */ + lgw_reg_w(LGW_PREAMBLE_SYMB1_NB, test_value); + lgw_reg_r(LGW_PREAMBLE_SYMB1_NB, &read_value); + printf("PREAMBLE_SYMB1_NB = %d (should be %d)\n", read_value, test_value); + + /* 16b signed */ + /* NO SUCH REG AVAILABLE */ + // /* CAPTURE_PERIOD is normally unsigned, modify it manually in loragw_reg.c */ + // test_value = -16283; /* 11000000 01100101b */ + // lgw_reg_w(LGW_CAPTURE_PERIOD, test_value); + // lgw_reg_r(LGW_CAPTURE_PERIOD, &read_value); + // printf("CAPTURE_PERIOD = %d (should be %d)\n", read_value, test_value); + + /* between 8b and 16b, unsigned */ + test_value = 3173; /* 1100 01100101b */ + lgw_reg_w(LGW_ADJUST_MODEM_START_OFFSET_SF12_RDX4, test_value); + lgw_reg_r(LGW_ADJUST_MODEM_START_OFFSET_SF12_RDX4, &read_value); + printf("ADJUST_MODEM_START_OFFSET_SF12_RDX4 = %d (should be %d)\n", read_value, test_value); + + /* between 8b and 16b, signed */ + test_value = -1947; /* 11000 01100101b */ + lgw_reg_w(LGW_IF_FREQ_1, test_value); + lgw_reg_r(LGW_IF_FREQ_1, &read_value); + printf("IF_FREQ_1 = %d (should be %d)\n", read_value, test_value); + + /* --- BURST WRITE AND READ TEST --- */ + + /* initialize data for SPI test */ + lfsr = 0xFFFF; + for(i=0; i> 4)); + /* printf("%05d # 0x%04x 0x%02x\n", i, lfsr, burst_buffout[i]); */ + lfsr = (lfsr & 1) ? ((lfsr >> 1) ^ 0x8679) : (lfsr >> 1); + } + + lgw_reg_wb(LGW_TX_DATA_BUF_DATA, burst_buffout, 256); + lgw_reg_rb(LGW_RX_DATA_BUF_DATA, burst_buffin, 256); + + /* impossible to check in software, + RX_DATA_BUF_DATA is read-only, + TX_DATA_BUF_DATA is write only, + use a logic analyser */ + + /* --- END OF TEST --- */ + + lgw_disconnect(); + /* no SPI transaction */ + + printf("End of test for loragw_reg.c\n"); + return 0; } /* --- EOF ------------------------------------------------------------------ */ diff --git a/libloragw/tst/test_loragw_spi.c b/libloragw/tst/test_loragw_spi.c index 5eda6b5f..872a0754 100644 --- a/libloragw/tst/test_loragw_spi.c +++ b/libloragw/tst/test_loragw_spi.c @@ -7,8 +7,8 @@ (C)2013 Semtech-Cycleo Description: - Minimum test program for the loragw_spi 'library' - Use logic analyser to check the results. + Minimum test program for the loragw_spi 'library' + Use logic analyser to check the results. License: Revised BSD License, see LICENSE.TXT file include in the project Maintainer: Sylvain Miermont @@ -32,54 +32,54 @@ Maintainer: Sylvain Miermont /* --- PRIVATE CONSTANTS ---------------------------------------------------- */ #define BURST_TEST_SIZE 2500 /* >> LGW_BURST_CHUNK */ -#define TIMING_REPEAT 1 /* repeat transactions multiple times for timing characterisation */ +#define TIMING_REPEAT 1 /* repeat transactions multiple times for timing characterisation */ /* -------------------------------------------------------------------------- */ /* --- MAIN FUNCTION -------------------------------------------------------- */ int main() { - int i; - void *spi_target = NULL; - uint8_t data = 0; - uint8_t dataout[BURST_TEST_SIZE]; - uint8_t datain[BURST_TEST_SIZE]; + int i; + void *spi_target = NULL; + uint8_t data = 0; + uint8_t dataout[BURST_TEST_SIZE]; + uint8_t datain[BURST_TEST_SIZE]; uint8_t spi_mux_mode = LGW_SPI_MUX_MODE0; - - for (i = 0; i < BURST_TEST_SIZE; ++i) { - dataout[i] = 0x30 + (i % 10); /* ASCCI code for 0 -> 9 */ - datain[i] = 0x23; /* garbage data, to be overwritten by received data */ - } - - printf("Beginning of test for loragw_spi.c\n"); - lgw_spi_open(&spi_target); - - /* normal R/W test */ - for (i = 0; i < TIMING_REPEAT; ++i) - lgw_spi_w(spi_target, spi_mux_mode, LGW_SPI_MUX_TARGET_SX1301, 0xAA, 0x96); - for (i = 0; i < TIMING_REPEAT; ++i) - lgw_spi_r(spi_target, spi_mux_mode, LGW_SPI_MUX_TARGET_SX1301, 0x55, &data); - - /* burst R/W test, small bursts << LGW_BURST_CHUNK */ - for (i = 0; i < TIMING_REPEAT; ++i) - lgw_spi_wb(spi_target, spi_mux_mode, LGW_SPI_MUX_TARGET_SX1301, 0x55, dataout, 16); - for (i = 0; i < TIMING_REPEAT; ++i) - lgw_spi_rb(spi_target, spi_mux_mode, LGW_SPI_MUX_TARGET_SX1301, 0x55, datain, 16); - - /* burst R/W test, large bursts >> LGW_BURST_CHUNK */ - for (i = 0; i < TIMING_REPEAT; ++i) - lgw_spi_wb(spi_target, spi_mux_mode, LGW_SPI_MUX_TARGET_SX1301, 0x5A, dataout, ARRAY_SIZE(dataout)); - for (i = 0; i < TIMING_REPEAT; ++i) - lgw_spi_rb(spi_target, spi_mux_mode, LGW_SPI_MUX_TARGET_SX1301, 0x5A, datain, ARRAY_SIZE(datain)); - - /* last read (blocking), just to be sure no to quit before the FTDI buffer is flushed */ - lgw_spi_r(spi_target, spi_mux_mode, LGW_SPI_MUX_TARGET_SX1301, 0x55, &data); - printf("data received (simple read): %d\n",data); - - lgw_spi_close(spi_target); - printf("End of test for loragw_spi.c\n"); - - return 0; + + for (i = 0; i < BURST_TEST_SIZE; ++i) { + dataout[i] = 0x30 + (i % 10); /* ASCCI code for 0 -> 9 */ + datain[i] = 0x23; /* garbage data, to be overwritten by received data */ + } + + printf("Beginning of test for loragw_spi.c\n"); + lgw_spi_open(&spi_target); + + /* normal R/W test */ + for (i = 0; i < TIMING_REPEAT; ++i) + lgw_spi_w(spi_target, spi_mux_mode, LGW_SPI_MUX_TARGET_SX1301, 0xAA, 0x96); + for (i = 0; i < TIMING_REPEAT; ++i) + lgw_spi_r(spi_target, spi_mux_mode, LGW_SPI_MUX_TARGET_SX1301, 0x55, &data); + + /* burst R/W test, small bursts << LGW_BURST_CHUNK */ + for (i = 0; i < TIMING_REPEAT; ++i) + lgw_spi_wb(spi_target, spi_mux_mode, LGW_SPI_MUX_TARGET_SX1301, 0x55, dataout, 16); + for (i = 0; i < TIMING_REPEAT; ++i) + lgw_spi_rb(spi_target, spi_mux_mode, LGW_SPI_MUX_TARGET_SX1301, 0x55, datain, 16); + + /* burst R/W test, large bursts >> LGW_BURST_CHUNK */ + for (i = 0; i < TIMING_REPEAT; ++i) + lgw_spi_wb(spi_target, spi_mux_mode, LGW_SPI_MUX_TARGET_SX1301, 0x5A, dataout, ARRAY_SIZE(dataout)); + for (i = 0; i < TIMING_REPEAT; ++i) + lgw_spi_rb(spi_target, spi_mux_mode, LGW_SPI_MUX_TARGET_SX1301, 0x5A, datain, ARRAY_SIZE(datain)); + + /* last read (blocking), just to be sure no to quit before the FTDI buffer is flushed */ + lgw_spi_r(spi_target, spi_mux_mode, LGW_SPI_MUX_TARGET_SX1301, 0x55, &data); + printf("data received (simple read): %d\n",data); + + lgw_spi_close(spi_target); + printf("End of test for loragw_spi.c\n"); + + return 0; } /* --- EOF ------------------------------------------------------------------ */ diff --git a/readme.md b/readme.md index f7b24d59..68b9ddab 100644 --- a/readme.md +++ b/readme.md @@ -55,9 +55,32 @@ for spectral measurement. This software is used to scan the spectral band in background, where the LoRa gateway operates. -3. Changelog +### 2.6. util_lbt_test ### + +This software is used to test "Listen-Before-Talk" channels timestamps. + +3. Helper scripts +----------------- + +### 3.1. reset_lgw.sh + +This script must be launched on IoT Start Kit platform to reset concentrator +chip through GPIO, before starting any application using the concentrator. + +4. Changelog ------------- +### v4.0.0 ### + +* HAL: Added "Listen-Before-Talk" support for Semtech SX1301AP2 Ref Design. + A description of the feature implementation can be found in + libloragw/readme.md. +* HAL: Updated FSK RSSI calculation for better linearization +* util_lbt_test: New utility provided for basic "Listen-Before-Talk" testing. +* util_tx_test: Extended to configure and test "LBT" through the HAL. +* Added a reset_lgw.sh script to be used with IoT Starter Kit (v1.0) to reset +the concentrator through the HOST GPIO pin. + ### v3.2.1 ### * HAL: Fixed downlink support for SX1301AP2 reference design: soft reset of the @@ -69,11 +92,13 @@ FPGA was missing for proper IQ inversion configuration. * util_tx_continous: reworked to use HAL functions instead of 'manual' config, and use same SX1301 calibration firmware as the HAL. * Updated all makefiles to handle the creation of obj directory when necessary. +* Change cs_change usage policy in SPI module to let the driver handle the chip +select. ### v3.2.0 ### * Added support for SX1301AP2 reference design (with FPGA and additional -SX1272). When a FPGA is detected at startup, the HAL automatically adapts SPI +SX127x). When a FPGA is detected at startup, the HAL automatically adapts SPI communication requests (using SPI header or not). * Added util_spectral_scan diagnostic tool to scan the spectral band in background, where the LoRa gateway operates. (can only be used with SX1301AP2 @@ -280,7 +305,7 @@ SPI-over-USB bridge). * Remove the 500 kHz limit on radio bandwith, back to the nominal 800 kHz. * Renamed debug flags. -4. Legal notice +5. Legal notice ---------------- The information presented in this project documentation does not form part of diff --git a/reset_lgw.sh b/reset_lgw.sh new file mode 100755 index 00000000..64cb3d1b --- /dev/null +++ b/reset_lgw.sh @@ -0,0 +1,55 @@ +#!/bin/sh + +# This script is intended to be used on IoT Starter Kit platform only, it +# performs the following actions: +# - export/unpexort GPIO7 used to reset the SX1301 chip +# +# Usage examples: +# ./reset_lgw.sh stop +# ./reset_lgw.sh start + +# The reset pin of SX1301 is wired with RPi GPIO7 +IOT_SK_SX1301_RESET_PIN=7 + +WAIT_GPIO() { + sleep 0.1 +} + +iot_sk_init() { + # setup GPIO 7 + echo "$IOT_SK_SX1301_RESET_PIN" > /sys/class/gpio/export; WAIT_GPIO + + # set GPIO 7 as output + echo "out" > /sys/class/gpio/gpio$IOT_SK_SX1301_RESET_PIN/direction; WAIT_GPIO + + # write output for SX1301 reset + echo "1" > /sys/class/gpio/gpio$IOT_SK_SX1301_RESET_PIN/value; WAIT_GPIO + echo "0" > /sys/class/gpio/gpio$IOT_SK_SX1301_RESET_PIN/value; WAIT_GPIO + + # set GPIO 7 as input + echo "in" > /sys/class/gpio/gpio$IOT_SK_SX1301_RESET_PIN/direction; WAIT_GPIO +} + +iot_sk_term() { + # cleanup GPIO 7 + if [ -d /sys/class/gpio/gpio$IOT_SK_SX1301_RESET_PIN ] + then + echo "$IOT_SK_SX1301_RESET_PIN" > /sys/class/gpio/unexport; WAIT_GPIO + fi +} + +case "$1" in + start) + iot_sk_term + iot_sk_init + ;; + stop) + iot_sk_term + ;; + *) + echo "Usage: $0 {start|stop}" + exit 1 + ;; +esac + +exit 0 diff --git a/util_lbt_test/Makefile b/util_lbt_test/Makefile new file mode 100644 index 00000000..de937e40 --- /dev/null +++ b/util_lbt_test/Makefile @@ -0,0 +1,65 @@ +### Application-specific constants + +APP_NAME := util_lbt_test + +### Environment constants + +LGW_PATH ?= ../libloragw +ARCH ?= +CROSS_COMPILE ?= + +### External constant definitions + +include $(LGW_PATH)/library.cfg + +### Constant symbols + +CC := $(CROSS_COMPILE)gcc +AR := $(CROSS_COMPILE)ar + +CFLAGS=-O2 -Wall -Wextra -std=c99 -Iinc -I. + +OBJDIR = obj + +### Constants for LoRa concentrator HAL library +# List the library sub-modules that are used by the application + +LGW_INC = $(LGW_PATH)/inc/config.h +LGW_INC += $(LGW_PATH)/inc/loragw_reg.h +LGW_INC += $(LGW_PATH)/inc/loragw_fpga.h + +### Linking options + +LIBS := -lloragw -lrt -lm + +### General build targets + +all: $(APP_NAME) + +clean: + rm -f $(OBJDIR)/*.o + rm -f $(APP_NAME) + +### HAL library (do no force multiple library rebuild even with 'make -B') + +$(LGW_PATH)/inc/config.h: + @if test ! -f $@; then \ + $(MAKE) all -C $(LGW_PATH); \ + fi + +$(LGW_PATH)/libloragw.a: $(LGW_INC) + @if test ! -f $@; then \ + $(MAKE) all -C $(LGW_PATH); \ + fi + +### Main program compilation and assembly +$(OBJDIR): + mkdir -p $(OBJDIR) + +$(OBJDIR)/$(APP_NAME).o: src/$(APP_NAME).c $(LGW_INC) | $(OBJDIR) + $(CC) -c $(CFLAGS) -I$(LGW_PATH)/inc $< -o $@ + +$(APP_NAME): $(OBJDIR)/$(APP_NAME).o $(LGW_PATH)/libloragw.a + $(CC) -L$(LGW_PATH) $< -o $@ $(LIBS) + +### EOF diff --git a/util_lbt_test/readme.md b/util_lbt_test/readme.md new file mode 100644 index 00000000..81472870 --- /dev/null +++ b/util_lbt_test/readme.md @@ -0,0 +1,48 @@ + / _____) _ | | + ( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | + (______/|_____)_|_|_| \__)_____)\____)_| |_| + ©2013 Semtech-Cycleo + +Lora Gateway LBT basic test application +======================================= + +1. Introduction +---------------- + +This software configures the FPGA for "Liste-Before-Talk" feature and +continuously reads the LBT channels timestamps which indicate when was the last +instant when the channel was free. + +2. Dependencies +---------------- + +- A SX1301AP2 Ref Design board with its FPGA programmed with LBT feature + +3. Usage +--------- + +Before running the util_lbt_test application, the concentrator MUST be first +started with the HAL, using for example util_pkt_logger or the packet forwarder. + +For a description of the command line options available: +./util_lbt_test -h + +ex: +./util_lbt_test -f 867.1 -t 100 -n 14 -s 15 -p 50 -r 162 + +This will set 6 LBT channels, starting from 867.1 MHz, then each subsequent +channel being set to the frequency of the previous channel +200 KHz (867.3, +867.5, ...). + +The above test will run for 100 iterations, with a CHANNEL_SCAN_TIME of 270µs +and a target RSSI of -81dBm. + +Please refer to the lora_gateway library readme.md to get more details on the +LBT feature implementation and configuration. + +4. Changelog +------------- + +2016-03-03 v1.0 Initial version diff --git a/util_lbt_test/src/util_lbt_test.c b/util_lbt_test/src/util_lbt_test.c new file mode 100644 index 00000000..0f10e867 --- /dev/null +++ b/util_lbt_test/src/util_lbt_test.c @@ -0,0 +1,293 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2013 Semtech-Cycleo + +Description: + Listen Before Talk basic test application + +License: Revised BSD License, see LICENSE.TXT file include in the project +Maintainer: Michael Coracin +*/ + + +/* -------------------------------------------------------------------------- */ +/* --- DEPENDANCIES --------------------------------------------------------- */ + +/* fix an issue between POSIX and C99 */ +#if __STDC_VERSION__ >= 199901L + #define _XOPEN_SOURCE 600 +#else + #define _XOPEN_SOURCE 500 +#endif + +#include /* C99 types */ +#include /* bool type */ +#include /* printf fprintf sprintf fopen fputs */ + +#include /* sigaction */ +#include /* getopt access */ +#include /* rand */ + +#include "loragw_aux.h" +#include "loragw_reg.h" +#include "loragw_hal.h" +#include "loragw_radio.h" +#include "loragw_fpga.h" + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE MACROS ------------------------------------------------------- */ + +#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) +#define MSG(args...) fprintf(stderr, args) /* message that is destined to the user */ + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE VARIABLES (GLOBAL) ------------------------------------------- */ + +/* signal handling variables */ +struct sigaction sigact; /* SIGQUIT&SIGINT&SIGTERM signal handling */ +static int exit_sig = 0; /* 1 -> application terminates cleanly (shut down hardware, close open files, etc) */ +static int quit_sig = 0; /* 1 -> application terminates without shutting down the hardware */ + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE FUNCTIONS DECLARATION ---------------------------------------- */ + +static void sig_handler(int sigio); + +void usage (void); + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE FUNCTIONS DEFINITION ----------------------------------------- */ + +static void sig_handler(int sigio) { + if (sigio == SIGQUIT) { + quit_sig = 1;; + } else if ((sigio == SIGINT) || (sigio == SIGTERM)) { + exit_sig = 1; + } +} + +/* describe command line options */ +void usage(void) { + printf( "Available options:\n"); + printf( " -h print this help\n"); + printf( " -f start frequency in MHz\n"); + printf( " -t number of read loops [1..32000]\n"); + printf( " -n number of spi access to SX127x RSSI instant register [0..255]\n"); + printf( " -s spi speed divider [0..255]\n"); + printf( " -p pll lock time: delay in 8 µsec step between frequency programming and RX ready [0..255]\n"); + printf( " -r target RSSI: signal strength target used to detect if the channel is clear or not [0..255]\n"); +} + +/* -------------------------------------------------------------------------- */ +/* --- MAIN FUNCTION -------------------------------------------------------- */ + +int main(int argc, char **argv) +{ + int i; + int xi = 0; + + /* in/out variables */ + double f1 = 0.0; + uint32_t f_start = 0; /* in Hz */ + uint16_t loop_cnt = 0; + uint16_t tempo = 100; + uint16_t nb_point = 14; + uint8_t spi_speed_div = 15; + uint8_t rssi_target = 162; + uint8_t pll_lock_time = 50; + uint16_t lsb_start_freq_int; + uint32_t timestamp; + uint8_t rssi_value; + int32_t val; + int channel; + + /* parse command line options */ + while ((i = getopt (argc, argv, "h:f:t:n:s:p:r:o:")) != -1) { + switch (i) { + case 'h': + usage(); + return EXIT_FAILURE; + break; + + case 'f': + i = sscanf(optarg, "%lf", &f1); + if ((i != 1) || (f1 < 30.0) || (f1 > 3000.0)) { + MSG("ERROR: Invalid LBT start frequency\n"); + usage(); + return EXIT_FAILURE; + } else { + f_start = (uint32_t)((f1*1e6) + 0.5);/* .5 Hz offset to get rounding instead of truncating */ + } + break; + case 't': + i = sscanf(optarg, "%i", &xi); + if ((i != 1) || (xi < 1) || (xi > 32000)) { + MSG("ERROR: tempo must be b/w 1 & 32000 \n"); + usage(); + return EXIT_FAILURE; + } else { + tempo = xi; + } + break; + case 'n': + i = sscanf(optarg, "%i", &xi); + if ((i != 1) || (xi < 0) || (xi > 255)) { + MSG("ERROR: nb_point must be b/w 0 & 255 \n"); + usage(); + return EXIT_FAILURE; + } else { + nb_point = xi; + } + break; + case 's': + i = sscanf(optarg, "%i", &xi); + if ((i != 1) || (xi < 0) || (xi > 255)) { + MSG("ERROR: spi_speed_div must be b/w 0 & 255 \n"); + usage(); + return EXIT_FAILURE; + } else { + spi_speed_div = xi; + } + break; + case 'r': + i = sscanf(optarg, "%i", &xi); + if ((i != 1) || (xi < 0) || (xi > 255)) { + MSG("ERROR: rssi_target must be b/w 0 & 255 \n"); + usage(); + return EXIT_FAILURE; + } else { + rssi_target = xi; + } + break; + case 'p': + i = sscanf(optarg, "%i", &xi); + if ((i != 1) || (xi < 0) || (xi > 255)) { + MSG("ERROR: pll_lock_time must be b/w 1 & 256 \n"); + usage(); + return EXIT_FAILURE; + } else { + pll_lock_time = xi; + } + break; + default: + MSG("ERROR: argument parsing use -h option for help\n"); + usage(); + return EXIT_FAILURE; + } + } + + /* Sanity check */ + if (f_start == 0) { + MSG("ERROR: LBT start frequency must be set\n"); + usage(); + return EXIT_FAILURE; + } + + MSG("INFO: Starting LoRa Gateway v1.5 LBT test\n"); + + /* configure signal handling */ + sigemptyset(&sigact.sa_mask); + sigact.sa_flags = 0; + sigact.sa_handler = sig_handler; + sigaction(SIGQUIT, &sigact, NULL); + sigaction(SIGINT, &sigact, NULL); + sigaction(SIGTERM, &sigact, NULL); + + /* Connect to concentrator */ + i = lgw_connect(); + if (i != LGW_REG_SUCCESS) { + MSG("ERROR: lgw_connect() did not return SUCCESS\n"); + return EXIT_FAILURE; + } + + /* Check if FPGA supports LBT */ + lgw_fpga_reg_r(LGW_FPGA_FPGA_FEATURE, &val); + if (TAKE_N_BITS_FROM((uint8_t)val, 2, 1) != true) { + printf("ERROR: LBT is not supported (0x%x)\n", (uint8_t)val); + return EXIT_FAILURE; + } + + MSG("FREQ: %u\n", f_start); + + /* Configure SX127x and read few RSSI points */ + lgw_setup_sx127x(f_start, MOD_FSK); + for (i = 0; i < 100; i++) { + lgw_sx127x_reg_r(0x11, &rssi_value); /* 0x11: RegRssiValue */ + MSG("SX127x RSSI:-%u dBm\n", rssi_value>>1); + wait_ms(10); + } + + /* Configure LBT */ + lgw_fpga_reg_w(LGW_FPGA_SPI_MASTER_SPEED_DIVIDER, (int32_t)spi_speed_div); + lgw_fpga_reg_w(LGW_FPGA_NB_READ_RSSI, (int32_t)nb_point); + lgw_fpga_reg_w(LGW_FPGA_PLL_LOCK_TIME, (int32_t)pll_lock_time); + lgw_fpga_reg_w(LGW_FPGA_RSSI_TARGET, (int32_t)rssi_target); + lsb_start_freq_int = (((uint64_t)f_start<<19)/(uint64_t)32000000); + lgw_fpga_reg_w(LGW_FPGA_LSB_START_FREQ, (int32_t)lsb_start_freq_int); + lgw_fpga_reg_w(LGW_FPGA_LBT_TIMESTAMP_NB_CH, (6-1)); /* 6 channels */ + + /* Enable LBT FSM */ + lgw_fpga_reg_w(LGW_FPGA_CTRL_FEATURE_START, 1); + + /* Read back LBT config */ + lgw_fpga_reg_r(LGW_FPGA_SPI_MASTER_SPEED_DIVIDER, &val); + MSG("spi_speed_div = %d\n", val); + if (val != spi_speed_div) { + return EXIT_FAILURE; + } + lgw_fpga_reg_r(LGW_FPGA_NB_READ_RSSI, &val); + MSG("nb_point = %d\n", val); + if (val != nb_point) { + return EXIT_FAILURE; + } + lgw_fpga_reg_r(LGW_FPGA_PLL_LOCK_TIME, &val); + MSG("PLL_LOCK = %d\n", val); + if (val != pll_lock_time) { + return EXIT_FAILURE; + } + lgw_fpga_reg_r(LGW_FPGA_RSSI_TARGET, &val); + MSG("RSSI_TARGET = %d\n", val); + if (val != rssi_target) { + return EXIT_FAILURE; + } + lgw_fpga_reg_r(LGW_FPGA_VERSION, &val); + MSG("FPGA VERSION = %d\n", val); + + /* Start test */ + while (((quit_sig != 1) && (exit_sig != 1)) && (loop_cnt != tempo)) { + /* Get current FPGA timestamp (1MHz) */ + lgw_fpga_reg_r(LGW_FPGA_TIMESTAMP, &val); + timestamp = (uint32_t)val; + MSG(" TIMESTAMP = %u\n", timestamp); + + for (channel = 0; channel <= 5; channel++) { + /* Select LBT channel */ + lgw_fpga_reg_w(LGW_FPGA_LBT_TIMESTAMP_SELECT_CH, channel); + + /* Get last instant when the selected channel was free */ + lgw_fpga_reg_r(LGW_FPGA_LBT_TIMESTAMP_CH, &val); + timestamp = (uint32_t)(val & 0x00FFFFFF) * 256; /* 24bits (1LSB = 256µs) */ + MSG(" TIMESTAMP_CH%u = %u\n", channel, timestamp); + } + + loop_cnt += 1; + wait_ms(400); + } + + /* close SPI link */ + i = lgw_disconnect(); + if (i != LGW_REG_SUCCESS) { + MSG("ERROR: lgw_disconnect() did not return SUCCESS\n"); + return EXIT_FAILURE; + } + + MSG("INFO: Exiting LoRa Gateway v1.5 LBT test successfully\n"); + return EXIT_SUCCESS; +} + +/* --- EOF ------------------------------------------------------------------ */ + diff --git a/util_pkt_logger/Makefile b/util_pkt_logger/Makefile index cda773b2..264ccc19 100644 --- a/util_pkt_logger/Makefile +++ b/util_pkt_logger/Makefile @@ -9,7 +9,6 @@ ARCH ?= CROSS_COMPILE ?= ### External constant definitions -# must get library build option to know if mpsse must be linked or not include $(LGW_PATH)/library.cfg @@ -30,7 +29,7 @@ LGW_INC += $(LGW_PATH)/inc/loragw_hal.h ### Linking options -LIBS := -lloragw -lrt +LIBS := -lloragw -lrt -lm ### General build targets diff --git a/util_pkt_logger/global_conf.json b/util_pkt_logger/global_conf.json index 55b58e3f..1a841d46 100644 --- a/util_pkt_logger/global_conf.json +++ b/util_pkt_logger/global_conf.json @@ -79,118 +79,6 @@ "if": 300000, "bandwidth": 125000, "datarate": 50000 - }, - "tx_lut_0": { - /* TX gain table, index 0 */ - "pa_gain": 0, - "mix_gain": 8, - "rf_power": -6, - "dig_gain": 0 - }, - "tx_lut_1": { - /* TX gain table, index 1 */ - "pa_gain": 0, - "mix_gain": 10, - "rf_power": -3, - "dig_gain": 0 - }, - "tx_lut_2": { - /* TX gain table, index 2 */ - "pa_gain": 0, - "mix_gain": 12, - "rf_power": 0, - "dig_gain": 0 - }, - "tx_lut_3": { - /* TX gain table, index 3 */ - "pa_gain": 1, - "mix_gain": 8, - "rf_power": 3, - "dig_gain": 0 - }, - "tx_lut_4": { - /* TX gain table, index 4 */ - "pa_gain": 1, - "mix_gain": 10, - "rf_power": 6, - "dig_gain": 0 - }, - "tx_lut_5": { - /* TX gain table, index 5 */ - "pa_gain": 1, - "mix_gain": 12, - "rf_power": 10, - "dig_gain": 0 - }, - "tx_lut_6": { - /* TX gain table, index 6 */ - "pa_gain": 1, - "mix_gain": 13, - "rf_power": 11, - "dig_gain": 0 - }, - "tx_lut_7": { - /* TX gain table, index 7 */ - "pa_gain": 2, - "mix_gain": 9, - "rf_power": 12, - "dig_gain": 0 - }, - "tx_lut_8": { - /* TX gain table, index 8 */ - "pa_gain": 1, - "mix_gain": 15, - "rf_power": 13, - "dig_gain": 0 - }, - "tx_lut_9": { - /* TX gain table, index 9 */ - "pa_gain": 2, - "mix_gain": 10, - "rf_power": 14, - "dig_gain": 0 - }, - "tx_lut_10": { - /* TX gain table, index 10 */ - "pa_gain": 2, - "mix_gain": 11, - "rf_power": 16, - "dig_gain": 0 - }, - "tx_lut_11": { - /* TX gain table, index 11 */ - "pa_gain": 3, - "mix_gain": 9, - "rf_power": 20, - "dig_gain": 0 - }, - "tx_lut_12": { - /* TX gain table, index 12 */ - "pa_gain": 3, - "mix_gain": 10, - "rf_power": 23, - "dig_gain": 0 - }, - "tx_lut_13": { - /* TX gain table, index 13 */ - "pa_gain": 3, - "mix_gain": 11, - "rf_power": 25, - "dig_gain": 0 - }, - "tx_lut_14": { - /* TX gain table, index 14 */ - "pa_gain": 3, - "mix_gain": 12, - "rf_power": 26, - "dig_gain": 0 - }, - "tx_lut_15": { - /* TX gain table, index 15 */ - "pa_gain": 3, - "mix_gain": 14, - "rf_power": 27, - "dig_gain": 0 } }, "gateway_conf": { diff --git a/util_pkt_logger/src/util_pkt_logger.c b/util_pkt_logger/src/util_pkt_logger.c index 9e452812..d95b2e39 100644 --- a/util_pkt_logger/src/util_pkt_logger.c +++ b/util_pkt_logger/src/util_pkt_logger.c @@ -7,7 +7,7 @@ (C)2013 Semtech-Cycleo Description: - Configure LoRa concentrator and record received packets in a log file + Configure LoRa concentrator and record received packets in a log file License: Revised BSD License, see LICENSE.TXT file include in the project Maintainer: Sylvain Miermont @@ -19,20 +19,20 @@ Maintainer: Sylvain Miermont /* fix an issue between POSIX and C99 */ #if __STDC_VERSION__ >= 199901L - #define _XOPEN_SOURCE 600 + #define _XOPEN_SOURCE 600 #else - #define _XOPEN_SOURCE 500 + #define _XOPEN_SOURCE 500 #endif -#include /* C99 types */ -#include /* bool type */ -#include /* printf fprintf sprintf fopen fputs */ +#include /* C99 types */ +#include /* bool type */ +#include /* printf fprintf sprintf fopen fputs */ -#include /* memset */ -#include /* sigaction */ -#include /* time clock_gettime strftime gmtime clock_nanosleep*/ -#include /* getopt access */ -#include /* atoi */ +#include /* memset */ +#include /* sigaction */ +#include /* time clock_gettime strftime gmtime clock_nanosleep*/ +#include /* getopt access */ +#include /* atoi */ #include "parson.h" #include "loragw_hal.h" @@ -40,8 +40,8 @@ Maintainer: Sylvain Miermont /* -------------------------------------------------------------------------- */ /* --- PRIVATE MACROS ------------------------------------------------------- */ -#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) -#define MSG(args...) fprintf(stderr,"loragw_pkt_logger: " args) /* message that is destined to the user */ +#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) +#define MSG(args...) fprintf(stderr,"loragw_pkt_logger: " args) /* message that is destined to the user */ /* -------------------------------------------------------------------------- */ /* --- PRIVATE VARIABLES (GLOBAL) ------------------------------------------- */ @@ -78,288 +78,288 @@ void usage (void); /* --- PRIVATE FUNCTIONS DEFINITION ----------------------------------------- */ static void sig_handler(int sigio) { - if (sigio == SIGQUIT) { - quit_sig = 1;; - } else if ((sigio == SIGINT) || (sigio == SIGTERM)) { - exit_sig = 1; - } + if (sigio == SIGQUIT) { + quit_sig = 1;; + } else if ((sigio == SIGINT) || (sigio == SIGTERM)) { + exit_sig = 1; + } } int parse_SX1301_configuration(const char * conf_file) { - int i; - const char conf_obj[] = "SX1301_conf"; - char param_name[32]; /* used to generate variable parameter names */ - const char *str; /* used to store string value from JSON object */ - struct lgw_conf_board_s boardconf; - struct lgw_conf_rxrf_s rfconf; - struct lgw_conf_rxif_s ifconf; - JSON_Value *root_val; - JSON_Object *root = NULL; - JSON_Object *conf = NULL; - JSON_Value *val; - uint32_t sf, bw; - - /* try to parse JSON */ - root_val = json_parse_file_with_comments(conf_file); - root = json_value_get_object(root_val); - if (root == NULL) { - MSG("ERROR: %s id not a valid JSON file\n", conf_file); - exit(EXIT_FAILURE); - } - conf = json_object_get_object(root, conf_obj); - if (conf == NULL) { - MSG("INFO: %s does not contain a JSON object named %s\n", conf_file, conf_obj); - return -1; - } else { - MSG("INFO: %s does contain a JSON object named %s, parsing SX1301 parameters\n", conf_file, conf_obj); - } - - /* set board configuration */ - memset(&boardconf, 0, sizeof boardconf); /* initialize configuration structure */ - val = json_object_get_value(conf, "lorawan_public"); /* fetch value (if possible) */ - if (json_value_get_type(val) == JSONBoolean) { - boardconf.lorawan_public = (bool)json_value_get_boolean(val); - } else { - MSG("WARNING: Data type for lorawan_public seems wrong, please check\n"); - boardconf.lorawan_public = false; - } - val = json_object_get_value(conf, "clksrc"); /* fetch value (if possible) */ - if (json_value_get_type(val) == JSONNumber) { - boardconf.clksrc = (uint8_t)json_value_get_number(val); - } else { - MSG("WARNING: Data type for clksrc seems wrong, please check\n"); - boardconf.clksrc = 0; - } - MSG("INFO: lorawan_public %d, clksrc %d\n", boardconf.lorawan_public, boardconf.clksrc); - /* all parameters parsed, submitting configuration to the HAL */ + int i; + const char conf_obj[] = "SX1301_conf"; + char param_name[32]; /* used to generate variable parameter names */ + const char *str; /* used to store string value from JSON object */ + struct lgw_conf_board_s boardconf; + struct lgw_conf_rxrf_s rfconf; + struct lgw_conf_rxif_s ifconf; + JSON_Value *root_val; + JSON_Object *root = NULL; + JSON_Object *conf = NULL; + JSON_Value *val; + uint32_t sf, bw; + + /* try to parse JSON */ + root_val = json_parse_file_with_comments(conf_file); + root = json_value_get_object(root_val); + if (root == NULL) { + MSG("ERROR: %s id not a valid JSON file\n", conf_file); + exit(EXIT_FAILURE); + } + conf = json_object_get_object(root, conf_obj); + if (conf == NULL) { + MSG("INFO: %s does not contain a JSON object named %s\n", conf_file, conf_obj); + return -1; + } else { + MSG("INFO: %s does contain a JSON object named %s, parsing SX1301 parameters\n", conf_file, conf_obj); + } + + /* set board configuration */ + memset(&boardconf, 0, sizeof boardconf); /* initialize configuration structure */ + val = json_object_get_value(conf, "lorawan_public"); /* fetch value (if possible) */ + if (json_value_get_type(val) == JSONBoolean) { + boardconf.lorawan_public = (bool)json_value_get_boolean(val); + } else { + MSG("WARNING: Data type for lorawan_public seems wrong, please check\n"); + boardconf.lorawan_public = false; + } + val = json_object_get_value(conf, "clksrc"); /* fetch value (if possible) */ + if (json_value_get_type(val) == JSONNumber) { + boardconf.clksrc = (uint8_t)json_value_get_number(val); + } else { + MSG("WARNING: Data type for clksrc seems wrong, please check\n"); + boardconf.clksrc = 0; + } + MSG("INFO: lorawan_public %d, clksrc %d\n", boardconf.lorawan_public, boardconf.clksrc); + /* all parameters parsed, submitting configuration to the HAL */ if (lgw_board_setconf(boardconf) != LGW_HAL_SUCCESS) { MSG("WARNING: Failed to configure board\n"); - } - - /* set configuration for RF chains */ - for (i = 0; i < LGW_RF_CHAIN_NB; ++i) { - memset(&rfconf, 0, sizeof(rfconf)); /* initialize configuration structure */ - sprintf(param_name, "radio_%i", i); /* compose parameter path inside JSON structure */ - val = json_object_get_value(conf, param_name); /* fetch value (if possible) */ - if (json_value_get_type(val) != JSONObject) { - MSG("INFO: no configuration for radio %i\n", i); - continue; - } - /* there is an object to configure that radio, let's parse it */ - sprintf(param_name, "radio_%i.enable", i); - val = json_object_dotget_value(conf, param_name); - if (json_value_get_type(val) == JSONBoolean) { - rfconf.enable = (bool)json_value_get_boolean(val); - } else { - rfconf.enable = false; - } - if (rfconf.enable == false) { /* radio disabled, nothing else to parse */ - MSG("INFO: radio %i disabled\n", i); - } else { /* radio enabled, will parse the other parameters */ - snprintf(param_name, sizeof param_name, "radio_%i.freq", i); - rfconf.freq_hz = (uint32_t)json_object_dotget_number(conf, param_name); - snprintf(param_name, sizeof param_name, "radio_%i.rssi_offset", i); - rfconf.rssi_offset = (float)json_object_dotget_number(conf, param_name); - snprintf(param_name, sizeof param_name, "radio_%i.type", i); - str = json_object_dotget_string(conf, param_name); - if (!strncmp(str, "SX1255", 6)) { - rfconf.type = LGW_RADIO_TYPE_SX1255; - } else if (!strncmp(str, "SX1257", 6)) { - rfconf.type = LGW_RADIO_TYPE_SX1257; - } else { - MSG("WARNING: invalid radio type: %s (should be SX1255 or SX1257)\n", str); - } - snprintf(param_name, sizeof param_name, "radio_%i.tx_enable", i); - val = json_object_dotget_value(conf, param_name); - if (json_value_get_type(val) == JSONBoolean) { - rfconf.tx_enable = (bool)json_value_get_boolean(val); - } else { - rfconf.tx_enable = false; - } - MSG("INFO: radio %i enabled (type %s), center frequency %u, RSSI offset %f, tx enabled %d\n", i, str, rfconf.freq_hz, rfconf.rssi_offset, rfconf.tx_enable); - } - /* all parameters parsed, submitting configuration to the HAL */ - if (lgw_rxrf_setconf(i, rfconf) != LGW_HAL_SUCCESS) { - MSG("WARNING: invalid configuration for radio %i\n", i); - } - } - - /* set configuration for LoRa multi-SF channels (bandwidth cannot be set) */ - for (i = 0; i < LGW_MULTI_NB; ++i) { - memset(&ifconf, 0, sizeof(ifconf)); /* initialize configuration structure */ - sprintf(param_name, "chan_multiSF_%i", i); /* compose parameter path inside JSON structure */ - val = json_object_get_value(conf, param_name); /* fetch value (if possible) */ - if (json_value_get_type(val) != JSONObject) { - MSG("INFO: no configuration for LoRa multi-SF channel %i\n", i); - continue; - } - /* there is an object to configure that LoRa multi-SF channel, let's parse it */ - sprintf(param_name, "chan_multiSF_%i.enable", i); - val = json_object_dotget_value(conf, param_name); - if (json_value_get_type(val) == JSONBoolean) { - ifconf.enable = (bool)json_value_get_boolean(val); - } else { - ifconf.enable = false; - } - if (ifconf.enable == false) { /* LoRa multi-SF channel disabled, nothing else to parse */ - MSG("INFO: LoRa multi-SF channel %i disabled\n", i); - } else { /* LoRa multi-SF channel enabled, will parse the other parameters */ - sprintf(param_name, "chan_multiSF_%i.radio", i); - ifconf.rf_chain = (uint32_t)json_object_dotget_number(conf, param_name); - sprintf(param_name, "chan_multiSF_%i.if", i); - ifconf.freq_hz = (int32_t)json_object_dotget_number(conf, param_name); - // TODO: handle individual SF enabling and disabling (spread_factor) - MSG("INFO: LoRa multi-SF channel %i enabled, radio %i selected, IF %i Hz, 125 kHz bandwidth, SF 7 to 12\n", i, ifconf.rf_chain, ifconf.freq_hz); - } - /* all parameters parsed, submitting configuration to the HAL */ - if (lgw_rxif_setconf(i, ifconf) != LGW_HAL_SUCCESS) { - MSG("WARNING: invalid configuration for LoRa multi-SF channel %i\n", i); - } - } - - /* set configuration for LoRa standard channel */ - memset(&ifconf, 0, sizeof(ifconf)); /* initialize configuration structure */ - val = json_object_get_value(conf, "chan_Lora_std"); /* fetch value (if possible) */ - if (json_value_get_type(val) != JSONObject) { - MSG("INFO: no configuration for LoRa standard channel\n"); - } else { - val = json_object_dotget_value(conf, "chan_Lora_std.enable"); - if (json_value_get_type(val) == JSONBoolean) { - ifconf.enable = (bool)json_value_get_boolean(val); - } else { - ifconf.enable = false; - } - if (ifconf.enable == false) { - MSG("INFO: LoRa standard channel %i disabled\n", i); - } else { - ifconf.rf_chain = (uint32_t)json_object_dotget_number(conf, "chan_Lora_std.radio"); - ifconf.freq_hz = (int32_t)json_object_dotget_number(conf, "chan_Lora_std.if"); - bw = (uint32_t)json_object_dotget_number(conf, "chan_Lora_std.bandwidth"); - switch(bw) { - case 500000: ifconf.bandwidth = BW_500KHZ; break; - case 250000: ifconf.bandwidth = BW_250KHZ; break; - case 125000: ifconf.bandwidth = BW_125KHZ; break; - default: ifconf.bandwidth = BW_UNDEFINED; - } - sf = (uint32_t)json_object_dotget_number(conf, "chan_Lora_std.spread_factor"); - switch(sf) { - case 7: ifconf.datarate = DR_LORA_SF7; break; - case 8: ifconf.datarate = DR_LORA_SF8; break; - case 9: ifconf.datarate = DR_LORA_SF9; break; - case 10: ifconf.datarate = DR_LORA_SF10; break; - case 11: ifconf.datarate = DR_LORA_SF11; break; - case 12: ifconf.datarate = DR_LORA_SF12; break; - default: ifconf.datarate = DR_UNDEFINED; - } - MSG("INFO: LoRa standard channel enabled, radio %i selected, IF %i Hz, %u Hz bandwidth, SF %u\n", ifconf.rf_chain, ifconf.freq_hz, bw, sf); - } - if (lgw_rxif_setconf(8, ifconf) != LGW_HAL_SUCCESS) { - MSG("WARNING: invalid configuration for LoRa standard channel\n"); - } - } - - /* set configuration for FSK channel */ - memset(&ifconf, 0, sizeof(ifconf)); /* initialize configuration structure */ - val = json_object_get_value(conf, "chan_FSK"); /* fetch value (if possible) */ - if (json_value_get_type(val) != JSONObject) { - MSG("INFO: no configuration for FSK channel\n"); - } else { - val = json_object_dotget_value(conf, "chan_FSK.enable"); - if (json_value_get_type(val) == JSONBoolean) { - ifconf.enable = (bool)json_value_get_boolean(val); - } else { - ifconf.enable = false; - } - if (ifconf.enable == false) { - MSG("INFO: FSK channel %i disabled\n", i); - } else { - ifconf.rf_chain = (uint32_t)json_object_dotget_number(conf, "chan_FSK.radio"); - ifconf.freq_hz = (int32_t)json_object_dotget_number(conf, "chan_FSK.if"); - bw = (uint32_t)json_object_dotget_number(conf, "chan_FSK.bandwidth"); - if (bw <= 7800) ifconf.bandwidth = BW_7K8HZ; - else if (bw <= 15600) ifconf.bandwidth = BW_15K6HZ; - else if (bw <= 31200) ifconf.bandwidth = BW_31K2HZ; - else if (bw <= 62500) ifconf.bandwidth = BW_62K5HZ; - else if (bw <= 125000) ifconf.bandwidth = BW_125KHZ; - else if (bw <= 250000) ifconf.bandwidth = BW_250KHZ; - else if (bw <= 500000) ifconf.bandwidth = BW_500KHZ; - else ifconf.bandwidth = BW_UNDEFINED; - ifconf.datarate = (uint32_t)json_object_dotget_number(conf, "chan_FSK.datarate"); - MSG("INFO: FSK channel enabled, radio %i selected, IF %i Hz, %u Hz bandwidth, %u bps datarate\n", ifconf.rf_chain, ifconf.freq_hz, bw, ifconf.datarate); - } - if (lgw_rxif_setconf(9, ifconf) != LGW_HAL_SUCCESS) { - MSG("WARNING: invalid configuration for FSK channel\n"); - } - } - json_value_free(root_val); - return 0; + } + + /* set configuration for RF chains */ + for (i = 0; i < LGW_RF_CHAIN_NB; ++i) { + memset(&rfconf, 0, sizeof(rfconf)); /* initialize configuration structure */ + sprintf(param_name, "radio_%i", i); /* compose parameter path inside JSON structure */ + val = json_object_get_value(conf, param_name); /* fetch value (if possible) */ + if (json_value_get_type(val) != JSONObject) { + MSG("INFO: no configuration for radio %i\n", i); + continue; + } + /* there is an object to configure that radio, let's parse it */ + sprintf(param_name, "radio_%i.enable", i); + val = json_object_dotget_value(conf, param_name); + if (json_value_get_type(val) == JSONBoolean) { + rfconf.enable = (bool)json_value_get_boolean(val); + } else { + rfconf.enable = false; + } + if (rfconf.enable == false) { /* radio disabled, nothing else to parse */ + MSG("INFO: radio %i disabled\n", i); + } else { /* radio enabled, will parse the other parameters */ + snprintf(param_name, sizeof param_name, "radio_%i.freq", i); + rfconf.freq_hz = (uint32_t)json_object_dotget_number(conf, param_name); + snprintf(param_name, sizeof param_name, "radio_%i.rssi_offset", i); + rfconf.rssi_offset = (float)json_object_dotget_number(conf, param_name); + snprintf(param_name, sizeof param_name, "radio_%i.type", i); + str = json_object_dotget_string(conf, param_name); + if (!strncmp(str, "SX1255", 6)) { + rfconf.type = LGW_RADIO_TYPE_SX1255; + } else if (!strncmp(str, "SX1257", 6)) { + rfconf.type = LGW_RADIO_TYPE_SX1257; + } else { + MSG("WARNING: invalid radio type: %s (should be SX1255 or SX1257)\n", str); + } + snprintf(param_name, sizeof param_name, "radio_%i.tx_enable", i); + val = json_object_dotget_value(conf, param_name); + if (json_value_get_type(val) == JSONBoolean) { + rfconf.tx_enable = (bool)json_value_get_boolean(val); + } else { + rfconf.tx_enable = false; + } + MSG("INFO: radio %i enabled (type %s), center frequency %u, RSSI offset %f, tx enabled %d\n", i, str, rfconf.freq_hz, rfconf.rssi_offset, rfconf.tx_enable); + } + /* all parameters parsed, submitting configuration to the HAL */ + if (lgw_rxrf_setconf(i, rfconf) != LGW_HAL_SUCCESS) { + MSG("WARNING: invalid configuration for radio %i\n", i); + } + } + + /* set configuration for LoRa multi-SF channels (bandwidth cannot be set) */ + for (i = 0; i < LGW_MULTI_NB; ++i) { + memset(&ifconf, 0, sizeof(ifconf)); /* initialize configuration structure */ + sprintf(param_name, "chan_multiSF_%i", i); /* compose parameter path inside JSON structure */ + val = json_object_get_value(conf, param_name); /* fetch value (if possible) */ + if (json_value_get_type(val) != JSONObject) { + MSG("INFO: no configuration for LoRa multi-SF channel %i\n", i); + continue; + } + /* there is an object to configure that LoRa multi-SF channel, let's parse it */ + sprintf(param_name, "chan_multiSF_%i.enable", i); + val = json_object_dotget_value(conf, param_name); + if (json_value_get_type(val) == JSONBoolean) { + ifconf.enable = (bool)json_value_get_boolean(val); + } else { + ifconf.enable = false; + } + if (ifconf.enable == false) { /* LoRa multi-SF channel disabled, nothing else to parse */ + MSG("INFO: LoRa multi-SF channel %i disabled\n", i); + } else { /* LoRa multi-SF channel enabled, will parse the other parameters */ + sprintf(param_name, "chan_multiSF_%i.radio", i); + ifconf.rf_chain = (uint32_t)json_object_dotget_number(conf, param_name); + sprintf(param_name, "chan_multiSF_%i.if", i); + ifconf.freq_hz = (int32_t)json_object_dotget_number(conf, param_name); + // TODO: handle individual SF enabling and disabling (spread_factor) + MSG("INFO: LoRa multi-SF channel %i enabled, radio %i selected, IF %i Hz, 125 kHz bandwidth, SF 7 to 12\n", i, ifconf.rf_chain, ifconf.freq_hz); + } + /* all parameters parsed, submitting configuration to the HAL */ + if (lgw_rxif_setconf(i, ifconf) != LGW_HAL_SUCCESS) { + MSG("WARNING: invalid configuration for LoRa multi-SF channel %i\n", i); + } + } + + /* set configuration for LoRa standard channel */ + memset(&ifconf, 0, sizeof(ifconf)); /* initialize configuration structure */ + val = json_object_get_value(conf, "chan_Lora_std"); /* fetch value (if possible) */ + if (json_value_get_type(val) != JSONObject) { + MSG("INFO: no configuration for LoRa standard channel\n"); + } else { + val = json_object_dotget_value(conf, "chan_Lora_std.enable"); + if (json_value_get_type(val) == JSONBoolean) { + ifconf.enable = (bool)json_value_get_boolean(val); + } else { + ifconf.enable = false; + } + if (ifconf.enable == false) { + MSG("INFO: LoRa standard channel %i disabled\n", i); + } else { + ifconf.rf_chain = (uint32_t)json_object_dotget_number(conf, "chan_Lora_std.radio"); + ifconf.freq_hz = (int32_t)json_object_dotget_number(conf, "chan_Lora_std.if"); + bw = (uint32_t)json_object_dotget_number(conf, "chan_Lora_std.bandwidth"); + switch(bw) { + case 500000: ifconf.bandwidth = BW_500KHZ; break; + case 250000: ifconf.bandwidth = BW_250KHZ; break; + case 125000: ifconf.bandwidth = BW_125KHZ; break; + default: ifconf.bandwidth = BW_UNDEFINED; + } + sf = (uint32_t)json_object_dotget_number(conf, "chan_Lora_std.spread_factor"); + switch(sf) { + case 7: ifconf.datarate = DR_LORA_SF7; break; + case 8: ifconf.datarate = DR_LORA_SF8; break; + case 9: ifconf.datarate = DR_LORA_SF9; break; + case 10: ifconf.datarate = DR_LORA_SF10; break; + case 11: ifconf.datarate = DR_LORA_SF11; break; + case 12: ifconf.datarate = DR_LORA_SF12; break; + default: ifconf.datarate = DR_UNDEFINED; + } + MSG("INFO: LoRa standard channel enabled, radio %i selected, IF %i Hz, %u Hz bandwidth, SF %u\n", ifconf.rf_chain, ifconf.freq_hz, bw, sf); + } + if (lgw_rxif_setconf(8, ifconf) != LGW_HAL_SUCCESS) { + MSG("WARNING: invalid configuration for LoRa standard channel\n"); + } + } + + /* set configuration for FSK channel */ + memset(&ifconf, 0, sizeof(ifconf)); /* initialize configuration structure */ + val = json_object_get_value(conf, "chan_FSK"); /* fetch value (if possible) */ + if (json_value_get_type(val) != JSONObject) { + MSG("INFO: no configuration for FSK channel\n"); + } else { + val = json_object_dotget_value(conf, "chan_FSK.enable"); + if (json_value_get_type(val) == JSONBoolean) { + ifconf.enable = (bool)json_value_get_boolean(val); + } else { + ifconf.enable = false; + } + if (ifconf.enable == false) { + MSG("INFO: FSK channel %i disabled\n", i); + } else { + ifconf.rf_chain = (uint32_t)json_object_dotget_number(conf, "chan_FSK.radio"); + ifconf.freq_hz = (int32_t)json_object_dotget_number(conf, "chan_FSK.if"); + bw = (uint32_t)json_object_dotget_number(conf, "chan_FSK.bandwidth"); + if (bw <= 7800) ifconf.bandwidth = BW_7K8HZ; + else if (bw <= 15600) ifconf.bandwidth = BW_15K6HZ; + else if (bw <= 31200) ifconf.bandwidth = BW_31K2HZ; + else if (bw <= 62500) ifconf.bandwidth = BW_62K5HZ; + else if (bw <= 125000) ifconf.bandwidth = BW_125KHZ; + else if (bw <= 250000) ifconf.bandwidth = BW_250KHZ; + else if (bw <= 500000) ifconf.bandwidth = BW_500KHZ; + else ifconf.bandwidth = BW_UNDEFINED; + ifconf.datarate = (uint32_t)json_object_dotget_number(conf, "chan_FSK.datarate"); + MSG("INFO: FSK channel enabled, radio %i selected, IF %i Hz, %u Hz bandwidth, %u bps datarate\n", ifconf.rf_chain, ifconf.freq_hz, bw, ifconf.datarate); + } + if (lgw_rxif_setconf(9, ifconf) != LGW_HAL_SUCCESS) { + MSG("WARNING: invalid configuration for FSK channel\n"); + } + } + json_value_free(root_val); + return 0; } int parse_gateway_configuration(const char * conf_file) { - const char conf_obj[] = "gateway_conf"; - JSON_Value *root_val; - JSON_Object *root = NULL; - JSON_Object *conf = NULL; - const char *str; /* pointer to sub-strings in the JSON data */ - unsigned long long ull = 0; - - /* try to parse JSON */ - root_val = json_parse_file_with_comments(conf_file); - root = json_value_get_object(root_val); - if (root == NULL) { - MSG("ERROR: %s id not a valid JSON file\n", conf_file); - exit(EXIT_FAILURE); - } - conf = json_object_get_object(root, conf_obj); - if (conf == NULL) { - MSG("INFO: %s does not contain a JSON object named %s\n", conf_file, conf_obj); - return -1; - } else { - MSG("INFO: %s does contain a JSON object named %s, parsing gateway parameters\n", conf_file, conf_obj); - } - - /* getting network parameters (only those necessary for the packet logger) */ - str = json_object_get_string(conf, "gateway_ID"); - if (str != NULL) { - sscanf(str, "%llx", &ull); - lgwm = ull; - MSG("INFO: gateway MAC address is configured to %016llX\n", ull); - } - - json_value_free(root_val); - return 0; + const char conf_obj[] = "gateway_conf"; + JSON_Value *root_val; + JSON_Object *root = NULL; + JSON_Object *conf = NULL; + const char *str; /* pointer to sub-strings in the JSON data */ + unsigned long long ull = 0; + + /* try to parse JSON */ + root_val = json_parse_file_with_comments(conf_file); + root = json_value_get_object(root_val); + if (root == NULL) { + MSG("ERROR: %s id not a valid JSON file\n", conf_file); + exit(EXIT_FAILURE); + } + conf = json_object_get_object(root, conf_obj); + if (conf == NULL) { + MSG("INFO: %s does not contain a JSON object named %s\n", conf_file, conf_obj); + return -1; + } else { + MSG("INFO: %s does contain a JSON object named %s, parsing gateway parameters\n", conf_file, conf_obj); + } + + /* getting network parameters (only those necessary for the packet logger) */ + str = json_object_get_string(conf, "gateway_ID"); + if (str != NULL) { + sscanf(str, "%llx", &ull); + lgwm = ull; + MSG("INFO: gateway MAC address is configured to %016llX\n", ull); + } + + json_value_free(root_val); + return 0; } void open_log(void) { - int i; - char iso_date[20]; - - strftime(iso_date,ARRAY_SIZE(iso_date),"%Y%m%dT%H%M%SZ",gmtime(&now_time)); /* format yyyymmddThhmmssZ */ - log_start_time = now_time; /* keep track of when the log was started, for log rotation */ - - sprintf(log_file_name, "pktlog_%s_%s.csv", lgwm_str, iso_date); - log_file = fopen(log_file_name, "a"); /* create log file, append if file already exist */ - if (log_file == NULL) { - MSG("ERROR: impossible to create log file %s\n", log_file_name); - exit(EXIT_FAILURE); - } - - i = fprintf(log_file, "\"gateway ID\",\"node MAC\",\"UTC timestamp\",\"us count\",\"frequency\",\"RF chain\",\"RX chain\",\"status\",\"size\",\"modulation\",\"bandwidth\",\"datarate\",\"coderate\",\"RSSI\",\"SNR\",\"payload\"\n"); - if (i < 0) { - MSG("ERROR: impossible to write to log file %s\n", log_file_name); - exit(EXIT_FAILURE); - } - - MSG("INFO: Now writing to log file %s\n", log_file_name); - return; + int i; + char iso_date[20]; + + strftime(iso_date,ARRAY_SIZE(iso_date),"%Y%m%dT%H%M%SZ",gmtime(&now_time)); /* format yyyymmddThhmmssZ */ + log_start_time = now_time; /* keep track of when the log was started, for log rotation */ + + sprintf(log_file_name, "pktlog_%s_%s.csv", lgwm_str, iso_date); + log_file = fopen(log_file_name, "a"); /* create log file, append if file already exist */ + if (log_file == NULL) { + MSG("ERROR: impossible to create log file %s\n", log_file_name); + exit(EXIT_FAILURE); + } + + i = fprintf(log_file, "\"gateway ID\",\"node MAC\",\"UTC timestamp\",\"us count\",\"frequency\",\"RF chain\",\"RX chain\",\"status\",\"size\",\"modulation\",\"bandwidth\",\"datarate\",\"coderate\",\"RSSI\",\"SNR\",\"payload\"\n"); + if (i < 0) { + MSG("ERROR: impossible to write to log file %s\n", log_file_name); + exit(EXIT_FAILURE); + } + + MSG("INFO: Now writing to log file %s\n", log_file_name); + return; } /* describe command line options */ void usage(void) { - printf("*** Library version information ***\n%s\n\n", lgw_version_info()); - printf( "Available options:\n"); - printf( " -h print this help\n"); - printf( " -r rotate log file every N seconds (-1 disable log rotation)\n"); + printf("*** Library version information ***\n%s\n\n", lgw_version_info()); + printf( "Available options:\n"); + printf( " -h print this help\n"); + printf( " -r rotate log file every N seconds (-1 disable log rotation)\n"); } /* -------------------------------------------------------------------------- */ @@ -367,250 +367,250 @@ void usage(void) { int main(int argc, char **argv) { - int i, j; /* loop and temporary variables */ - struct timespec sleep_time = {0, 3000000}; /* 3 ms */ - - /* clock and log rotation management */ - int log_rotate_interval = 3600; /* by default, rotation every hour */ - int time_check = 0; /* variable used to limit the number of calls to time() function */ - unsigned long pkt_in_log = 0; /* count the number of packet written in each log file */ - - /* configuration file related */ - const char global_conf_fname[] = "global_conf.json"; /* contain global (typ. network-wide) configuration */ - const char local_conf_fname[] = "local_conf.json"; /* contain node specific configuration, overwrite global parameters for parameters that are defined in both */ - const char debug_conf_fname[] = "debug_conf.json"; /* if present, all other configuration files are ignored */ - - /* allocate memory for packet fetching and processing */ - struct lgw_pkt_rx_s rxpkt[16]; /* array containing up to 16 inbound packets metadata */ - struct lgw_pkt_rx_s *p; /* pointer on a RX packet */ - int nb_pkt; - - /* local timestamp variables until we get accurate GPS time */ - struct timespec fetch_time; - char fetch_timestamp[30]; - struct tm * x; - - /* parse command line options */ - while ((i = getopt (argc, argv, "hr:")) != -1) { - switch (i) { - case 'h': - usage(); - return EXIT_FAILURE; - break; - - case 'r': - log_rotate_interval = atoi(optarg); - if ((log_rotate_interval == 0) || (log_rotate_interval < -1)) { - MSG( "ERROR: Invalid argument for -r option\n"); - return EXIT_FAILURE; - } - break; - - default: - MSG("ERROR: argument parsing use -h option for help\n"); - usage(); - return EXIT_FAILURE; - } - } - - /* configure signal handling */ - sigemptyset(&sigact.sa_mask); - sigact.sa_flags = 0; - sigact.sa_handler = sig_handler; - sigaction(SIGQUIT, &sigact, NULL); - sigaction(SIGINT, &sigact, NULL); - sigaction(SIGTERM, &sigact, NULL); - - /* configuration files management */ - if (access(debug_conf_fname, R_OK) == 0) { - /* if there is a debug conf, parse only the debug conf */ - MSG("INFO: found debug configuration file %s, other configuration files will be ignored\n", debug_conf_fname); - parse_SX1301_configuration(debug_conf_fname); - parse_gateway_configuration(debug_conf_fname); - } else if (access(global_conf_fname, R_OK) == 0) { - /* if there is a global conf, parse it and then try to parse local conf */ - MSG("INFO: found global configuration file %s, trying to parse it\n", global_conf_fname); - parse_SX1301_configuration(global_conf_fname); - parse_gateway_configuration(global_conf_fname); - if (access(local_conf_fname, R_OK) == 0) { - MSG("INFO: found local configuration file %s, trying to parse it\n", local_conf_fname); - parse_SX1301_configuration(local_conf_fname); - parse_gateway_configuration(local_conf_fname); - } - } else if (access(local_conf_fname, R_OK) == 0) { - /* if there is only a local conf, parse it and that's all */ - MSG("INFO: found local configuration file %s, trying to parse it\n", local_conf_fname); - parse_SX1301_configuration(local_conf_fname); - parse_gateway_configuration(local_conf_fname); - } else { - MSG("ERROR: failed to find any configuration file named %s, %s or %s\n", global_conf_fname, local_conf_fname, debug_conf_fname); - return EXIT_FAILURE; - } - - /* starting the concentrator */ - i = lgw_start(); - if (i == LGW_HAL_SUCCESS) { - MSG("INFO: concentrator started, packet can now be received\n"); - } else { - MSG("ERROR: failed to start the concentrator\n"); - return EXIT_FAILURE; - } - - /* transform the MAC address into a string */ - sprintf(lgwm_str, "%08X%08X", (uint32_t)(lgwm >> 32), (uint32_t)(lgwm & 0xFFFFFFFF)); - - /* opening log file and writing CSV header*/ - time(&now_time); - open_log(); - - /* main loop */ - while ((quit_sig != 1) && (exit_sig != 1)) { - /* fetch packets */ - nb_pkt = lgw_receive(ARRAY_SIZE(rxpkt), rxpkt); - if (nb_pkt == LGW_HAL_ERROR) { - MSG("ERROR: failed packet fetch, exiting\n"); - return EXIT_FAILURE; - } else if (nb_pkt == 0) { - clock_nanosleep(CLOCK_MONOTONIC, 0, &sleep_time, NULL); /* wait a short time if no packets */ - } else { - /* local timestamp generation until we get accurate GPS time */ - clock_gettime(CLOCK_REALTIME, &fetch_time); - x = gmtime(&(fetch_time.tv_sec)); - sprintf(fetch_timestamp,"%04i-%02i-%02i %02i:%02i:%02i.%03liZ",(x->tm_year)+1900,(x->tm_mon)+1,x->tm_mday,x->tm_hour,x->tm_min,x->tm_sec,(fetch_time.tv_nsec)/1000000); /* ISO 8601 format */ - } - - /* log packets */ - for (i=0; i < nb_pkt; ++i) { - p = &rxpkt[i]; - - /* writing gateway ID */ - fprintf(log_file, "\"%08X%08X\",", (uint32_t)(lgwm >> 32), (uint32_t)(lgwm & 0xFFFFFFFF)); - - /* writing node MAC address */ - fputs("\"\",", log_file); // TODO: need to parse payload - - /* writing UTC timestamp*/ - fprintf(log_file, "\"%s\",", fetch_timestamp); - // TODO: replace with GPS time when available - - /* writing internal clock */ - fprintf(log_file, "%10u,", p->count_us); - - /* writing RX frequency */ - fprintf(log_file, "%10u,", p->freq_hz); - - /* writing RF chain */ - fprintf(log_file, "%u,", p->rf_chain); - - /* writing RX modem/IF chain */ - fprintf(log_file, "%2d,", p->if_chain); - - /* writing status */ - switch(p->status) { - case STAT_CRC_OK: fputs("\"CRC_OK\" ,", log_file); break; - case STAT_CRC_BAD: fputs("\"CRC_BAD\",", log_file); break; - case STAT_NO_CRC: fputs("\"NO_CRC\" ,", log_file); break; - case STAT_UNDEFINED:fputs("\"UNDEF\" ,", log_file); break; - default: fputs("\"ERR\" ,", log_file); - } - - /* writing payload size */ - fprintf(log_file, "%3u,", p->size); - - /* writing modulation */ - switch(p->modulation) { - case MOD_LORA: fputs("\"LORA\",", log_file); break; - case MOD_FSK: fputs("\"FSK\" ,", log_file); break; - default: fputs("\"ERR\" ,", log_file); - } - - /* writing bandwidth */ - switch(p->bandwidth) { - case BW_500KHZ: fputs("500000,", log_file); break; - case BW_250KHZ: fputs("250000,", log_file); break; - case BW_125KHZ: fputs("125000,", log_file); break; - case BW_62K5HZ: fputs("62500 ,", log_file); break; - case BW_31K2HZ: fputs("31200 ,", log_file); break; - case BW_15K6HZ: fputs("15600 ,", log_file); break; - case BW_7K8HZ: fputs("7800 ,", log_file); break; - case BW_UNDEFINED: fputs("0 ,", log_file); break; - default: fputs("-1 ,", log_file); - } - - /* writing datarate */ - if (p->modulation == MOD_LORA) { - switch (p->datarate) { - case DR_LORA_SF7: fputs("\"SF7\" ,", log_file); break; - case DR_LORA_SF8: fputs("\"SF8\" ,", log_file); break; - case DR_LORA_SF9: fputs("\"SF9\" ,", log_file); break; - case DR_LORA_SF10: fputs("\"SF10\" ,", log_file); break; - case DR_LORA_SF11: fputs("\"SF11\" ,", log_file); break; - case DR_LORA_SF12: fputs("\"SF12\" ,", log_file); break; - default: fputs("\"ERR\" ,", log_file); - } - } else if (p->modulation == MOD_FSK) { - fprintf(log_file, "\"%6u\",", p->datarate); - } else { - fputs("\"ERR\" ,", log_file); - } - - /* writing coderate */ - switch (p->coderate) { - case CR_LORA_4_5: fputs("\"4/5\",", log_file); break; - case CR_LORA_4_6: fputs("\"2/3\",", log_file); break; - case CR_LORA_4_7: fputs("\"4/7\",", log_file); break; - case CR_LORA_4_8: fputs("\"1/2\",", log_file); break; - case CR_UNDEFINED: fputs("\"\" ,", log_file); break; - default: fputs("\"ERR\",", log_file); - } - - /* writing packet RSSI */ - fprintf(log_file, "%+.0f,", p->rssi); - - /* writing packet average SNR */ - fprintf(log_file, "%+5.1f,", p->snr); - - /* writing hex-encoded payload (bundled in 32-bit words) */ - fputs("\"", log_file); - for (j = 0; j < p->size; ++j) { - if ((j > 0) && (j%4 == 0)) fputs("-", log_file); - fprintf(log_file, "%02X", p->payload[j]); - } - - /* end of log file line */ - fputs("\"\n", log_file); - fflush(log_file); - ++pkt_in_log; - } - - /* check time and rotate log file if necessary */ - ++time_check; - if (time_check >= 8) { - time_check = 0; - time(&now_time); - if (difftime(now_time, log_start_time) > log_rotate_interval) { - fclose(log_file); - MSG("INFO: log file %s closed, %lu packet(s) recorded\n", log_file_name, pkt_in_log); - pkt_in_log = 0; - open_log(); - } - } - } - - if (exit_sig == 1) { - /* clean up before leaving */ - i = lgw_stop(); - if (i == LGW_HAL_SUCCESS) { - MSG("INFO: concentrator stopped successfully\n"); - } else { - MSG("WARNING: failed to stop concentrator successfully\n"); - } - fclose(log_file); - MSG("INFO: log file %s closed, %lu packet(s) recorded\n", log_file_name, pkt_in_log); - } - - MSG("INFO: Exiting packet logger program\n"); - return EXIT_SUCCESS; + int i, j; /* loop and temporary variables */ + struct timespec sleep_time = {0, 3000000}; /* 3 ms */ + + /* clock and log rotation management */ + int log_rotate_interval = 3600; /* by default, rotation every hour */ + int time_check = 0; /* variable used to limit the number of calls to time() function */ + unsigned long pkt_in_log = 0; /* count the number of packet written in each log file */ + + /* configuration file related */ + const char global_conf_fname[] = "global_conf.json"; /* contain global (typ. network-wide) configuration */ + const char local_conf_fname[] = "local_conf.json"; /* contain node specific configuration, overwrite global parameters for parameters that are defined in both */ + const char debug_conf_fname[] = "debug_conf.json"; /* if present, all other configuration files are ignored */ + + /* allocate memory for packet fetching and processing */ + struct lgw_pkt_rx_s rxpkt[16]; /* array containing up to 16 inbound packets metadata */ + struct lgw_pkt_rx_s *p; /* pointer on a RX packet */ + int nb_pkt; + + /* local timestamp variables until we get accurate GPS time */ + struct timespec fetch_time; + char fetch_timestamp[30]; + struct tm * x; + + /* parse command line options */ + while ((i = getopt (argc, argv, "hr:")) != -1) { + switch (i) { + case 'h': + usage(); + return EXIT_FAILURE; + break; + + case 'r': + log_rotate_interval = atoi(optarg); + if ((log_rotate_interval == 0) || (log_rotate_interval < -1)) { + MSG( "ERROR: Invalid argument for -r option\n"); + return EXIT_FAILURE; + } + break; + + default: + MSG("ERROR: argument parsing use -h option for help\n"); + usage(); + return EXIT_FAILURE; + } + } + + /* configure signal handling */ + sigemptyset(&sigact.sa_mask); + sigact.sa_flags = 0; + sigact.sa_handler = sig_handler; + sigaction(SIGQUIT, &sigact, NULL); + sigaction(SIGINT, &sigact, NULL); + sigaction(SIGTERM, &sigact, NULL); + + /* configuration files management */ + if (access(debug_conf_fname, R_OK) == 0) { + /* if there is a debug conf, parse only the debug conf */ + MSG("INFO: found debug configuration file %s, other configuration files will be ignored\n", debug_conf_fname); + parse_SX1301_configuration(debug_conf_fname); + parse_gateway_configuration(debug_conf_fname); + } else if (access(global_conf_fname, R_OK) == 0) { + /* if there is a global conf, parse it and then try to parse local conf */ + MSG("INFO: found global configuration file %s, trying to parse it\n", global_conf_fname); + parse_SX1301_configuration(global_conf_fname); + parse_gateway_configuration(global_conf_fname); + if (access(local_conf_fname, R_OK) == 0) { + MSG("INFO: found local configuration file %s, trying to parse it\n", local_conf_fname); + parse_SX1301_configuration(local_conf_fname); + parse_gateway_configuration(local_conf_fname); + } + } else if (access(local_conf_fname, R_OK) == 0) { + /* if there is only a local conf, parse it and that's all */ + MSG("INFO: found local configuration file %s, trying to parse it\n", local_conf_fname); + parse_SX1301_configuration(local_conf_fname); + parse_gateway_configuration(local_conf_fname); + } else { + MSG("ERROR: failed to find any configuration file named %s, %s or %s\n", global_conf_fname, local_conf_fname, debug_conf_fname); + return EXIT_FAILURE; + } + + /* starting the concentrator */ + i = lgw_start(); + if (i == LGW_HAL_SUCCESS) { + MSG("INFO: concentrator started, packet can now be received\n"); + } else { + MSG("ERROR: failed to start the concentrator\n"); + return EXIT_FAILURE; + } + + /* transform the MAC address into a string */ + sprintf(lgwm_str, "%08X%08X", (uint32_t)(lgwm >> 32), (uint32_t)(lgwm & 0xFFFFFFFF)); + + /* opening log file and writing CSV header*/ + time(&now_time); + open_log(); + + /* main loop */ + while ((quit_sig != 1) && (exit_sig != 1)) { + /* fetch packets */ + nb_pkt = lgw_receive(ARRAY_SIZE(rxpkt), rxpkt); + if (nb_pkt == LGW_HAL_ERROR) { + MSG("ERROR: failed packet fetch, exiting\n"); + return EXIT_FAILURE; + } else if (nb_pkt == 0) { + clock_nanosleep(CLOCK_MONOTONIC, 0, &sleep_time, NULL); /* wait a short time if no packets */ + } else { + /* local timestamp generation until we get accurate GPS time */ + clock_gettime(CLOCK_REALTIME, &fetch_time); + x = gmtime(&(fetch_time.tv_sec)); + sprintf(fetch_timestamp,"%04i-%02i-%02i %02i:%02i:%02i.%03liZ",(x->tm_year)+1900,(x->tm_mon)+1,x->tm_mday,x->tm_hour,x->tm_min,x->tm_sec,(fetch_time.tv_nsec)/1000000); /* ISO 8601 format */ + } + + /* log packets */ + for (i=0; i < nb_pkt; ++i) { + p = &rxpkt[i]; + + /* writing gateway ID */ + fprintf(log_file, "\"%08X%08X\",", (uint32_t)(lgwm >> 32), (uint32_t)(lgwm & 0xFFFFFFFF)); + + /* writing node MAC address */ + fputs("\"\",", log_file); // TODO: need to parse payload + + /* writing UTC timestamp*/ + fprintf(log_file, "\"%s\",", fetch_timestamp); + // TODO: replace with GPS time when available + + /* writing internal clock */ + fprintf(log_file, "%10u,", p->count_us); + + /* writing RX frequency */ + fprintf(log_file, "%10u,", p->freq_hz); + + /* writing RF chain */ + fprintf(log_file, "%u,", p->rf_chain); + + /* writing RX modem/IF chain */ + fprintf(log_file, "%2d,", p->if_chain); + + /* writing status */ + switch(p->status) { + case STAT_CRC_OK: fputs("\"CRC_OK\" ,", log_file); break; + case STAT_CRC_BAD: fputs("\"CRC_BAD\",", log_file); break; + case STAT_NO_CRC: fputs("\"NO_CRC\" ,", log_file); break; + case STAT_UNDEFINED: fputs("\"UNDEF\" ,", log_file); break; + default: fputs("\"ERR\" ,", log_file); + } + + /* writing payload size */ + fprintf(log_file, "%3u,", p->size); + + /* writing modulation */ + switch(p->modulation) { + case MOD_LORA: fputs("\"LORA\",", log_file); break; + case MOD_FSK: fputs("\"FSK\" ,", log_file); break; + default: fputs("\"ERR\" ,", log_file); + } + + /* writing bandwidth */ + switch(p->bandwidth) { + case BW_500KHZ: fputs("500000,", log_file); break; + case BW_250KHZ: fputs("250000,", log_file); break; + case BW_125KHZ: fputs("125000,", log_file); break; + case BW_62K5HZ: fputs("62500 ,", log_file); break; + case BW_31K2HZ: fputs("31200 ,", log_file); break; + case BW_15K6HZ: fputs("15600 ,", log_file); break; + case BW_7K8HZ: fputs("7800 ,", log_file); break; + case BW_UNDEFINED: fputs("0 ,", log_file); break; + default: fputs("-1 ,", log_file); + } + + /* writing datarate */ + if (p->modulation == MOD_LORA) { + switch (p->datarate) { + case DR_LORA_SF7: fputs("\"SF7\" ,", log_file); break; + case DR_LORA_SF8: fputs("\"SF8\" ,", log_file); break; + case DR_LORA_SF9: fputs("\"SF9\" ,", log_file); break; + case DR_LORA_SF10: fputs("\"SF10\" ,", log_file); break; + case DR_LORA_SF11: fputs("\"SF11\" ,", log_file); break; + case DR_LORA_SF12: fputs("\"SF12\" ,", log_file); break; + default: fputs("\"ERR\" ,", log_file); + } + } else if (p->modulation == MOD_FSK) { + fprintf(log_file, "\"%6u\",", p->datarate); + } else { + fputs("\"ERR\" ,", log_file); + } + + /* writing coderate */ + switch (p->coderate) { + case CR_LORA_4_5: fputs("\"4/5\",", log_file); break; + case CR_LORA_4_6: fputs("\"2/3\",", log_file); break; + case CR_LORA_4_7: fputs("\"4/7\",", log_file); break; + case CR_LORA_4_8: fputs("\"1/2\",", log_file); break; + case CR_UNDEFINED: fputs("\"\" ,", log_file); break; + default: fputs("\"ERR\",", log_file); + } + + /* writing packet RSSI */ + fprintf(log_file, "%+.0f,", p->rssi); + + /* writing packet average SNR */ + fprintf(log_file, "%+5.1f,", p->snr); + + /* writing hex-encoded payload (bundled in 32-bit words) */ + fputs("\"", log_file); + for (j = 0; j < p->size; ++j) { + if ((j > 0) && (j%4 == 0)) fputs("-", log_file); + fprintf(log_file, "%02X", p->payload[j]); + } + + /* end of log file line */ + fputs("\"\n", log_file); + fflush(log_file); + ++pkt_in_log; + } + + /* check time and rotate log file if necessary */ + ++time_check; + if (time_check >= 8) { + time_check = 0; + time(&now_time); + if (difftime(now_time, log_start_time) > log_rotate_interval) { + fclose(log_file); + MSG("INFO: log file %s closed, %lu packet(s) recorded\n", log_file_name, pkt_in_log); + pkt_in_log = 0; + open_log(); + } + } + } + + if (exit_sig == 1) { + /* clean up before leaving */ + i = lgw_stop(); + if (i == LGW_HAL_SUCCESS) { + MSG("INFO: concentrator stopped successfully\n"); + } else { + MSG("WARNING: failed to stop concentrator successfully\n"); + } + fclose(log_file); + MSG("INFO: log file %s closed, %lu packet(s) recorded\n", log_file_name, pkt_in_log); + } + + MSG("INFO: Exiting packet logger program\n"); + return EXIT_SUCCESS; } /* --- EOF ------------------------------------------------------------------ */ diff --git a/util_spectral_scan/Makefile b/util_spectral_scan/Makefile index 8ce2058b..35d625fb 100644 --- a/util_spectral_scan/Makefile +++ b/util_spectral_scan/Makefile @@ -1,17 +1,32 @@ ### Environment constants +LGW_PATH ?= ../libloragw ARCH ?= CROSS_COMPILE ?= +### External constant definitions + +include $(LGW_PATH)/library.cfg + ### Constant symbols CC = $(CROSS_COMPILE)gcc AR = $(CROSS_COMPILE)ar -CFLAGS = -O2 -Wall -Wextra -std=c99 -I inc -D DEBUG_SPI=0 -D DEBUG_REG=0 +CFLAGS = -O2 -Wall -Wextra -std=c99 -I inc OBJDIR = obj INCLUDES = $(wildcard inc/*.h) +### Constants for LoRa concentrator HAL library +# List the library sub-modules that are used by the application + +LGW_INC = $(LGW_PATH)/inc/config.h +LGW_INC += $(LGW_PATH)/inc/loragw_hal.h + +### Linking options + +LIBS := -lloragw -lrt + ### General build targets all: util_spectral_scan @@ -20,6 +35,17 @@ clean: rm -f $(OBJDIR)/*.o rm -f util_spectral_scan +### HAL library (do no force multiple library rebuild even with 'make -B') + +$(LGW_PATH)/inc/config.h: + @if test ! -f $@; then \ + $(MAKE) all -C $(LGW_PATH); \ + fi + +$(LGW_PATH)/libloragw.a: $(LGW_INC) + @if test ! -f $@; then \ + $(MAKE) all -C $(LGW_PATH); \ + fi ### Sub-modules compilation @@ -27,11 +53,11 @@ $(OBJDIR): mkdir -p $(OBJDIR) $(OBJDIR)/%.o: src/%.c $(INCLUDES) | $(OBJDIR) - $(CC) -c $(CFLAGS) $< -o $@ + $(CC) -c $(CFLAGS) -I$(LGW_PATH)/inc $< -o $@ ### Main program assembly -util_spectral_scan: $(OBJDIR)/util_spectral_scan.o $(OBJDIR)/loragw_fpga_reg.o $(OBJDIR)/loragw_fpga_spi.o $(OBJDIR)/loragw_fpga_aux.o - $(CC) $^ -lrt -o $@ +util_spectral_scan: $(OBJDIR)/util_spectral_scan.o + $(CC) -L$(LGW_PATH) $^ $(LIBS) -o $@ ### EOF diff --git a/util_spectral_scan/inc/loragw_fpga_aux.h b/util_spectral_scan/inc/loragw_fpga_aux.h deleted file mode 100644 index dd6780bf..00000000 --- a/util_spectral_scan/inc/loragw_fpga_aux.h +++ /dev/null @@ -1,34 +0,0 @@ -/* - / _____) _ | | -( (____ _____ ____ _| |_ _____ ____| |__ - \____ \| ___ | (_ _) ___ |/ ___) _ \ - _____) ) ____| | | || |_| ____( (___| | | | -(______/|_____)_|_|_| \__)_____)\____)_| |_| - (C)2013 Semtech-Cycleo - -Description: - LoRa concentrator HAL common auxiliary functions - -License: Revised BSD License, see LICENSE.TXT file include in the project -Maintainer: Matthieu Leurent -*/ - - -#ifndef _LORAGW_AUX_H -#define _LORAGW_AUX_H - -/* -------------------------------------------------------------------------- */ -/* --- DEPENDANCIES --------------------------------------------------------- */ - -/* -------------------------------------------------------------------------- */ -/* --- PUBLIC FUNCTIONS PROTOTYPES ------------------------------------------ */ - -/** -@brief Wait for a certain time (millisecond accuracy) -@param t number of milliseconds to wait. -*/ -void wait_ms(unsigned long t); - -#endif - -/* --- EOF ------------------------------------------------------------------ */ diff --git a/util_spectral_scan/inc/loragw_fpga_spi.h b/util_spectral_scan/inc/loragw_fpga_spi.h deleted file mode 100644 index 7515aec0..00000000 --- a/util_spectral_scan/inc/loragw_fpga_spi.h +++ /dev/null @@ -1,99 +0,0 @@ -/* - / _____) _ | | -( (____ _____ ____ _| |_ _____ ____| |__ - \____ \| ___ | (_ _) ___ |/ ___) _ \ - _____) ) ____| | | || |_| ____( (___| | | | -(______/|_____)_|_|_| \__)_____)\____)_| |_| - (C)2013 Semtech-Cycleo - -Description: - Host specific functions to address the LoRa concentrator registers through a - SPI interface. - Single-byte read/write and burst read/write. - Does not handle pagination. - Could be used with multiple SPI ports in parallel (explicit file descriptor) - -License: Revised BSD License, see LICENSE.TXT file include in the project -Maintainer: Matthieu Leurent -*/ - - -#ifndef _LORAGW_SPI_H -#define _LORAGW_SPI_H - -/* -------------------------------------------------------------------------- */ -/* --- DEPENDANCIES --------------------------------------------------------- */ - -#include /* C99 types*/ - -/* -------------------------------------------------------------------------- */ -/* --- PUBLIC CONSTANTS ----------------------------------------------------- */ - -#define LGW_SPI_SUCCESS 0 -#define LGW_SPI_ERROR -1 -#define LGW_BURST_CHUNK 1024 - -/* -------------------------------------------------------------------------- */ -/* --- PUBLIC FUNCTIONS PROTOTYPES ------------------------------------------ */ - -/** -@brief LoRa concentrator FPGA SPI setup (configure I/O and peripherals) -@param spi_target_ptr pointer on a generic pointer to SPI target (implementation dependant) -@return status of register operation (LGW_SPI_SUCCESS/LGW_SPI_ERROR) -*/ - -int lgw_fpga_spi_open(void **spi_target_ptr); - -/** -@brief LoRa concentrator FPGA SPI close -@param spi_target generic pointer to SPI target (implementation dependant) -@return status of register operation (LGW_SPI_SUCCESS/LGW_SPI_ERROR) -*/ - -int lgw_fpga_spi_close(void *spi_target); - -/** -@brief LoRa concentrator SPI single-byte write -@param spi_target generic pointer to SPI target (implementation dependant) -@param spi_mux_dev device selection for SPI request (FPGA registers, SX1272 radio...) -@param address 7-bit register address -@param data data byte to write -@return status of register operation (LGW_SPI_SUCCESS/LGW_SPI_ERROR) -*/ -int lgw_fpga_spi_w(void *spi_target, uint8_t spi_mux_dev, uint8_t address, uint8_t data); - -/** -@brief LoRa concentrator SPI single-byte read -@param spi_target generic pointer to SPI target (implementation dependant) -@param spi_mux_dev device selection for SPI request (FPGA registers, SX1272 radio...) -@param address 7-bit register address -@param data data byte to write -@return status of register operation (LGW_SPI_SUCCESS/LGW_SPI_ERROR) -*/ -int lgw_fpga_spi_r(void *spi_target, uint8_t spi_mux_dev, uint8_t address, uint8_t *data); - -/** -@brief LoRa concentrator SPI burst (multiple-byte) write -@param spi_target generic pointer to SPI target (implementation dependant) -@param spi_mux_dev device selection for SPI request (FPGA registers, SX1272 radio...) -@param address 7-bit register address -@param data pointer to byte array that will be sent to the LoRa concentrator -@param size size of the transfer, in byte(s) -@return status of register operation (LGW_SPI_SUCCESS/LGW_SPI_ERROR) -*/ -int lgw_fpga_spi_wb(void *spi_target, uint8_t spi_mux_dev, uint8_t address, uint8_t *data, uint16_t size); - -/** -@brief LoRa concentrator SPI burst (multiple-byte) read -@param spi_target generic pointer to SPI target (implementation dependant) -@param spi_mux_dev device selection for SPI request (FPGA registers, SX1272 radio...) -@param address 7-bit register address -@param data pointer to byte array that will be written from the LoRa concentrator -@param size size of the transfer, in byte(s) -@return status of register operation (LGW_SPI_SUCCESS/LGW_SPI_ERROR) -*/ -int lgw_fpga_spi_rb(void *spi_target, uint8_t spi_mux_dev, uint8_t address, uint8_t *data, uint16_t size); - -#endif - -/* --- EOF ------------------------------------------------------------------ */ diff --git a/util_spectral_scan/readme.md b/util_spectral_scan/readme.md index 68f4398a..2befdb7a 100644 --- a/util_spectral_scan/readme.md +++ b/util_spectral_scan/readme.md @@ -19,12 +19,12 @@ detect occupied bands and get interferer profiles. It logs the histogram in a .csv file. This utility program is meant to run on the LoRa gateway reference design -SX1301AP2 (with FPGA and additionnal SX1272). +SX1301AP2 (with FPGA and additionnal SX127x). The background RSSI scan is a diagnostic tool and must be run on top of the gateway activity. Moreover the two SX1257 radios have to be configured in RX -mode to optimize the matching impedance with SX1272. The 32MHz clock provided -to the SX1272 is available once SX1301 has enabled the two SX1257 radios, so +mode to optimize the matching impedance with SX127x. The 32MHz clock provided +to the SX127x is available once SX1301 has enabled the two SX1257 radios, so the background RSSI scan must be launched after the packet forwarder. 2. Command line options diff --git a/util_spectral_scan/src/loragw_fpga_aux.c b/util_spectral_scan/src/loragw_fpga_aux.c deleted file mode 100644 index 1ca04635..00000000 --- a/util_spectral_scan/src/loragw_fpga_aux.c +++ /dev/null @@ -1,61 +0,0 @@ -/* - / _____) _ | | -( (____ _____ ____ _| |_ _____ ____| |__ - \____ \| ___ | (_ _) ___ |/ ___) _ \ - _____) ) ____| | | || |_| ____( (___| | | | -(______/|_____)_|_|_| \__)_____)\____)_| |_| - (C)2013 Semtech-Cycleo - -Description: - LoRa concentrator HAL auxiliary functions - -License: Revised BSD License, see LICENSE.TXT file include in the project -Maintainer: Matthieu Leurent -*/ - - -/* -------------------------------------------------------------------------- */ -/* --- DEPENDANCIES --------------------------------------------------------- */ - -/* fix an issue between POSIX and C99 */ -#if __STDC_VERSION__ >= 199901L - #define _XOPEN_SOURCE 600 -#else - #define _XOPEN_SOURCE 500 -#endif - -#include /* printf fprintf */ -#include /* clock_nanosleep */ - -/* -------------------------------------------------------------------------- */ -/* --- PRIVATE MACROS ------------------------------------------------------- */ - -#if DEBUG_AUX == 1 - #define DEBUG_MSG(str) fprintf(stderr, str) - #define DEBUG_PRINTF(fmt, args...) fprintf(stderr,"%s:%d: "fmt, __FUNCTION__, __LINE__, args) -#else - #define DEBUG_MSG(str) - #define DEBUG_PRINTF(fmt, args...) -#endif - -/* -------------------------------------------------------------------------- */ -/* --- PUBLIC FUNCTIONS DEFINITION ------------------------------------------ */ - -/* This implementation is POSIX-pecific and require a fix to be compatible with C99 */ -void wait_ms(unsigned long a) { - struct timespec dly; - struct timespec rem; - - dly.tv_sec = a / 1000; - dly.tv_nsec = ((long)a % 1000) * 1000000; - - DEBUG_PRINTF("NOTE dly: %ld sec %ld ns\n", dly.tv_sec, dly.tv_nsec); - - if((dly.tv_sec > 0) || ((dly.tv_sec == 0) && (dly.tv_nsec > 100000))) { - clock_nanosleep(CLOCK_MONOTONIC, 0, &dly, &rem); - DEBUG_PRINTF("NOTE remain: %ld sec %ld ns\n", rem.tv_sec, rem.tv_nsec); - } - return; -} - -/* --- EOF ------------------------------------------------------------------ */ diff --git a/util_spectral_scan/src/loragw_fpga_spi.c b/util_spectral_scan/src/loragw_fpga_spi.c deleted file mode 100644 index 3d8487c3..00000000 --- a/util_spectral_scan/src/loragw_fpga_spi.c +++ /dev/null @@ -1,352 +0,0 @@ -/* - / _____) _ | | -( (____ _____ ____ _| |_ _____ ____| |__ - \____ \| ___ | (_ _) ___ |/ ___) _ \ - _____) ) ____| | | || |_| ____( (___| | | | -(______/|_____)_|_|_| \__)_____)\____)_| |_| - (C)2013 Semtech-Cycleo - -Description: - Host specific functions to address the LoRa concentrator registers through - a SPI interface. - Single-byte read/write and burst read/write. - Does not handle pagination. - Could be used with multiple SPI ports in parallel (explicit file descriptor) - -License: Revised BSD License, see LICENSE.TXT file include in the project -Maintainer: Michael Coracin -*/ - - -/* -------------------------------------------------------------------------- */ -/* --- DEPENDANCIES --------------------------------------------------------- */ - -#include /* C99 types */ -#include /* printf fprintf */ -#include /* malloc free */ -#include /* lseek, close */ -#include /* open */ -#include /* memset */ - -#include -#include -#include "loragw_fpga_spi.h" - -/* -------------------------------------------------------------------------- */ -/* --- PRIVATE MACROS ------------------------------------------------------- */ - -#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) -#if DEBUG_SPI == 1 - #define DEBUG_MSG(str) fprintf(stderr, str) - #define DEBUG_PRINTF(fmt, args...) fprintf(stderr,"%s:%d: "fmt, __FUNCTION__, __LINE__, args) - #define CHECK_NULL(a) if(a==NULL){fprintf(stderr,"%s:%d: ERROR: NULL POINTER AS ARGUMENT\n", __FUNCTION__, __LINE__);return LGW_SPI_ERROR;} -#else - #define DEBUG_MSG(str) - #define DEBUG_PRINTF(fmt, args...) - #define CHECK_NULL(a) if(a==NULL){return LGW_SPI_ERROR;} -#endif - -/* -------------------------------------------------------------------------- */ -/* --- PRIVATE CONSTANTS ---------------------------------------------------- */ - -#define READ_ACCESS 0x00 -#define WRITE_ACCESS 0x80 -#define SPI_SPEED 8000000 -#define SPI_DEV_PATH "/dev/spidev0.0" - -/* -------------------------------------------------------------------------- */ -/* --- PUBLIC FUNCTIONS DEFINITION ------------------------------------------ */ - -/* SPI initialization and configuration */ -int lgw_fpga_spi_open(void **spi_target_ptr) { - int *spi_device = NULL; - int dev; - int a=0, b=0; - int i; - - /* check input variables */ - CHECK_NULL(spi_target_ptr); /* cannot be null, must point on a void pointer (*spi_target_ptr can be null) */ - - /* allocate memory for the device descriptor */ - spi_device = malloc(sizeof(int)); - if (spi_device == NULL) { - DEBUG_MSG("ERROR: MALLOC FAIL\n"); - return LGW_SPI_ERROR; - } - - /* open SPI device */ - dev = open(SPI_DEV_PATH, O_RDWR); - if (dev < 0) { - DEBUG_PRINTF("ERROR: failed to open SPI device %s\n", SPI_DEV_PATH); - return LGW_SPI_ERROR; - } - - /* setting SPI mode to 'mode 0' */ - i = SPI_MODE_0; - a = ioctl(dev, SPI_IOC_WR_MODE, &i); - b = ioctl(dev, SPI_IOC_RD_MODE, &i); - if ((a < 0) || (b < 0)) { - DEBUG_MSG("ERROR: SPI PORT FAIL TO SET IN MODE 0\n"); - close(dev); - free(spi_device); - return LGW_SPI_ERROR; - } - - /* setting SPI max clk (in Hz) */ - i = SPI_SPEED; - a = ioctl(dev, SPI_IOC_WR_MAX_SPEED_HZ, &i); - b = ioctl(dev, SPI_IOC_RD_MAX_SPEED_HZ, &i); - if ((a < 0) || (b < 0)) { - DEBUG_MSG("ERROR: SPI PORT FAIL TO SET MAX SPEED\n"); - close(dev); - free(spi_device); - return LGW_SPI_ERROR; - } - - /* setting SPI to MSB first */ - i = 0; - a = ioctl(dev, SPI_IOC_WR_LSB_FIRST, &i); - b = ioctl(dev, SPI_IOC_RD_LSB_FIRST, &i); - if ((a < 0) || (b < 0)) { - DEBUG_MSG("ERROR: SPI PORT FAIL TO SET MSB FIRST\n"); - close(dev); - free(spi_device); - return LGW_SPI_ERROR; - } - - /* setting SPI to 8 bits per word */ - i = 0; - a = ioctl(dev, SPI_IOC_WR_BITS_PER_WORD, &i); - b = ioctl(dev, SPI_IOC_RD_BITS_PER_WORD, &i); - if ((a < 0) || (b < 0)) { - DEBUG_MSG("ERROR: SPI PORT FAIL TO SET 8 BITS-PER-WORD\n"); - close(dev); - return LGW_SPI_ERROR; - } - - *spi_device = dev; - *spi_target_ptr = (void *)spi_device; - DEBUG_MSG("Note: SPI port opened and configured ok\n"); - return LGW_SPI_SUCCESS; -} - -/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ - -/* SPI release */ -int lgw_fpga_spi_close(void *spi_target) { - int spi_device; - int a; - - /* check input variables */ - CHECK_NULL(spi_target); - - /* close file & deallocate file descriptor */ - spi_device = *(int *)spi_target; /* must check that spi_target is not null beforehand */ - a = close(spi_device); - free(spi_target); - - /* determine return code */ - if (a < 0) { - DEBUG_MSG("ERROR: SPI PORT FAILED TO CLOSE\n"); - return LGW_SPI_ERROR; - } else { - DEBUG_MSG("Note: SPI port closed\n"); - return LGW_SPI_SUCCESS; - } -} - -/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ - -/* Simple write */ -int lgw_fpga_spi_w(void *spi_target, uint8_t spi_mux_dev, uint8_t address, uint8_t data) { - int spi_device; - uint8_t out_buf[3]; - struct spi_ioc_transfer k; - int a; - - /* check input variables */ - CHECK_NULL(spi_target); - if ((address & 0x80) != 0) { - DEBUG_MSG("WARNING: SPI address > 127\n"); - } - - spi_device = *(int *)spi_target; /* must check that spi_target is not null beforehand */ - - /* prepare frame to be sent */ - out_buf[0] = spi_mux_dev; - out_buf[1] = WRITE_ACCESS | (address & 0x7F); - out_buf[2] = data; - - /* I/O transaction */ - memset(&k, 0, sizeof(k)); /* clear k */ - k.tx_buf = (unsigned long) out_buf; - k.len = ARRAY_SIZE(out_buf); - k.speed_hz = SPI_SPEED; - k.cs_change = 1; - k.bits_per_word = 8; - a = ioctl(spi_device, SPI_IOC_MESSAGE(1), &k); - - /* determine return code */ - if (a != (int)k.len) { - DEBUG_MSG("ERROR: SPI WRITE FAILURE\n"); - return LGW_SPI_ERROR; - } else { - DEBUG_MSG("Note: SPI write success\n"); - return LGW_SPI_SUCCESS; - } -} - -/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ - -/* Simple read */ -int lgw_fpga_spi_r(void *spi_target, uint8_t spi_mux_dev, uint8_t address, uint8_t *data) { - int spi_device; - uint8_t out_buf[3]; - uint8_t in_buf[ARRAY_SIZE(out_buf)]; - struct spi_ioc_transfer k; - int a; - - /* check input variables */ - CHECK_NULL(spi_target); - if ((address & 0x80) != 0) { - DEBUG_MSG("WARNING: SPI address > 127\n"); - } - CHECK_NULL(data); - - spi_device = *(int *)spi_target; /* must check that spi_target is not null beforehand */ - - /* prepare frame to be sent */ - out_buf[0] = spi_mux_dev; - out_buf[1] = READ_ACCESS | (address & 0x7F); - out_buf[2] = 0x00; - - /* I/O transaction */ - memset(&k, 0, sizeof(k)); /* clear k */ - k.tx_buf = (unsigned long) out_buf; - k.rx_buf = (unsigned long) in_buf; - k.len = ARRAY_SIZE(out_buf); - k.cs_change = 1; - a = ioctl(spi_device, SPI_IOC_MESSAGE(1), &k); - - /* determine return code */ - if (a != (int)k.len) { - DEBUG_MSG("ERROR: SPI READ FAILURE\n"); - return LGW_SPI_ERROR; - } else { - DEBUG_MSG("Note: SPI read success\n"); - *data = in_buf[2]; - return LGW_SPI_SUCCESS; - } -} - -/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ - -/* Burst (multiple-byte) write */ -int lgw_fpga_spi_wb(void *spi_target, uint8_t spi_mux_dev, uint8_t address, uint8_t *data, uint16_t size) { - int spi_device; - uint8_t command[2]; - struct spi_ioc_transfer k[2]; - int size_to_do, chunk_size, offset; - int byte_transfered = 0; - int i; - - /* check input parameters */ - CHECK_NULL(spi_target); - if ((address & 0x80) != 0) { - DEBUG_MSG("WARNING: SPI address > 127\n"); - } - CHECK_NULL(data); - if (size == 0) { - DEBUG_MSG("ERROR: BURST OF NULL LENGTH\n"); - return LGW_SPI_ERROR; - } - - spi_device = *(int *)spi_target; /* must check that spi_target is not null beforehand */ - - /* prepare command byte */ - command[0] = spi_mux_dev; - command[1] = WRITE_ACCESS | (address & 0x7F); - size_to_do = size; - - /* I/O transaction */ - memset(&k, 0, sizeof(k)); /* clear k */ - k[0].tx_buf = (unsigned long) &command[0]; - k[0].len = 2; - k[0].cs_change = 0; - k[1].cs_change = 1; - for (i=0; size_to_do > 0; ++i) { - chunk_size = (size_to_do < LGW_BURST_CHUNK) ? size_to_do : LGW_BURST_CHUNK; - offset = i * LGW_BURST_CHUNK; - k[1].tx_buf = (unsigned long)(data + offset); - k[1].len = chunk_size; - byte_transfered += (ioctl(spi_device, SPI_IOC_MESSAGE(2), &k) - k[0].len ); - DEBUG_PRINTF("BURST WRITE: to trans %d # chunk %d # transferred %d \n", size_to_do, chunk_size, byte_transfered); - size_to_do -= chunk_size; /* subtract the quantity of data already transferred */ - } - - /* determine return code */ - if (byte_transfered != size) { - DEBUG_MSG("ERROR: SPI BURST WRITE FAILURE\n"); - return LGW_SPI_ERROR; - } else { - DEBUG_MSG("Note: SPI burst write success\n"); - return LGW_SPI_SUCCESS; - } -} - -/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ - -/* Burst (multiple-byte) read */ -int lgw_fpga_spi_rb(void *spi_target, uint8_t spi_mux_dev, uint8_t address, uint8_t *data, uint16_t size) { - int spi_device; - uint8_t command[2]; - struct spi_ioc_transfer k[2]; - int size_to_do, chunk_size, offset; - int byte_transfered = 0; - int i; - - /* check input parameters */ - CHECK_NULL(spi_target); - if ((address & 0x80) != 0) { - DEBUG_MSG("WARNING: SPI address > 127\n"); - } - CHECK_NULL(data); - if (size == 0) { - DEBUG_MSG("ERROR: BURST OF NULL LENGTH\n"); - return LGW_SPI_ERROR; - } - - spi_device = *(int *)spi_target; /* must check that spi_target is not null beforehand */ - - /* prepare command byte */ - command[0] = spi_mux_dev; - command[1] = READ_ACCESS | (address & 0x7F); - size_to_do = size; - - /* I/O transaction */ - memset(&k, 0, sizeof(k)); /* clear k */ - k[0].tx_buf = (unsigned long) &command[0]; - k[0].len = 2; - k[0].cs_change = 0; - k[1].cs_change = 1; - for (i=0; size_to_do > 0; ++i) { - chunk_size = (size_to_do < LGW_BURST_CHUNK) ? size_to_do : LGW_BURST_CHUNK; - offset = i * LGW_BURST_CHUNK; - k[1].rx_buf = (unsigned long)(data + offset); - k[1].len = chunk_size; - byte_transfered += (ioctl(spi_device, SPI_IOC_MESSAGE(2), &k) - k[0].len ); - DEBUG_PRINTF("BURST READ: to trans %d # chunk %d # transferred %d \n", size_to_do, chunk_size, byte_transfered); - size_to_do -= chunk_size; /* subtract the quantity of data already transferred */ - } - - /* determine return code */ - if (byte_transfered != size) { - DEBUG_MSG("ERROR: SPI BURST READ FAILURE\n"); - return LGW_SPI_ERROR; - } else { - DEBUG_MSG("Note: SPI burst read success\n"); - return LGW_SPI_SUCCESS; - } -} - -/* --- EOF ------------------------------------------------------------------ */ diff --git a/util_spectral_scan/src/util_spectral_scan.c b/util_spectral_scan/src/util_spectral_scan.c index 3da61ac1..71239ed2 100644 --- a/util_spectral_scan/src/util_spectral_scan.c +++ b/util_spectral_scan/src/util_spectral_scan.c @@ -31,9 +31,11 @@ Maintainer: Matthieu Leurent #include /* getopt */ #include -#include "loragw_fpga_spi.h" -#include "loragw_fpga_reg.h" -#include "loragw_fpga_aux.h" +#include "loragw_aux.h" +#include "loragw_reg.h" +#include "loragw_hal.h" +#include "loragw_radio.h" +#include "loragw_fpga.h" /* -------------------------------------------------------------------------- */ /* --- MACROS & CONSTANTS --------------------------------------------------- */ @@ -57,11 +59,6 @@ Maintainer: Matthieu Leurent /* -------------------------------------------------------------------------- */ /* --- GLOBAL VARIABLES ----------------------------------------------------- */ -/* -------------------------------------------------------------------------- */ -/* --- SUBFUNCTIONS DECLARATION --------------------------------------------- */ - -int setup_sx1272( uint32_t freq ); - /* -------------------------------------------------------------------------- */ /* --- MAIN FUNCTION -------------------------------------------------------- */ @@ -93,11 +90,6 @@ int main( int argc, char ** argv ) uint16_t rssi_cumu; float rssi_thresh[] = {0.1,0.3,0.5,0.8,1}; - /* FPGA settings */ - uint32_t input_sync_edge = 0; - uint32_t output_sync_edge = 0; - uint32_t filt_on = 1; - /* Parse command line options */ while( (i = getopt( argc, argv, "hud::f:n:r:l:" )) != -1 ) { @@ -177,17 +169,22 @@ int main( int argc, char ** argv ) /* Start message */ printf( "+++ Start spectral scan of LoRa gateway channels +++\n" ); - x = lgw_fpga_connect( ); + x = lgw_connect( ); if( x != 0 ) { printf( "ERROR: Failed to connect to FPGA\n" ); return EXIT_FAILURE; } + /* Check if FPGA supports Spectral Scan */ + lgw_fpga_reg_r(LGW_FPGA_FPGA_FEATURE, ®_val); + if (TAKE_N_BITS_FROM((uint8_t)reg_val, 1, 1) != true) { + printf("ERROR: Spectral Scan is not supported (0x%x)\n", (uint8_t)reg_val); + return EXIT_FAILURE; + } + /* Configure FPGA */ - x = lgw_fpga_reg_w(LGW_FPGA_FPGA_CTRL, (filt_on << 4) | (input_sync_edge << 2)| (output_sync_edge << 3) | (1 << 1)); /* Reset Radio */ - x |= lgw_fpga_reg_w(LGW_FPGA_FPGA_CTRL, (filt_on << 4) | (input_sync_edge << 2)| (output_sync_edge << 3)); - x |= lgw_fpga_reg_w(LGW_FPGA_HISTO_TEMPO, rssi_rate_div); + x = lgw_fpga_reg_w(LGW_FPGA_HISTO_TEMPO, rssi_rate_div); x |= lgw_fpga_reg_w(LGW_FPGA_HISTO_NB_READ, rssi_pts); if( x != LGW_REG_SUCCESS ) { @@ -216,16 +213,16 @@ int main( int argc, char ** argv ) freq = start_freq + j * step_freq; printf( "%d", freq ); - /* Set SX1272 */ - x = setup_sx1272( freq ); + /* Set SX127x */ + x = lgw_setup_sx127x( freq, MOD_LORA ); if( x != 0 ) { - printf( "ERROR: SX1272 setup failed\n" ); + printf( "ERROR: SX127x setup failed\n" ); return EXIT_FAILURE; } /* Start histogram */ - lgw_fpga_reg_w(LGW_FPGA_FPGA_CTRL, 1); + lgw_fpga_reg_w(LGW_FPGA_CTRL_FEATURE_START, 1); /* Wait until rssi_pts have been processed */ do @@ -236,7 +233,7 @@ int main( int argc, char ** argv ) while( (reg_val & 0x0F) != 8 ); /* Stop histogram */ - lgw_fpga_reg_w(LGW_FPGA_FPGA_CTRL, 0); + lgw_fpga_reg_w(LGW_FPGA_CTRL_FEATURE_START, 0); /* Read histogram */ lgw_fpga_reg_w(LGW_FPGA_HISTO_RAM_ADDR, 0); @@ -262,7 +259,7 @@ int main( int argc, char ** argv ) fclose( log_file ); /* Close SPI */ - x = lgw_fpga_disconnect( ); + x = lgw_disconnect( ); if( x != 0 ) { printf( "ERROR: Failed to disconnect FPGA\n" ); @@ -274,77 +271,6 @@ int main( int argc, char ** argv ) return EXIT_SUCCESS; } -/* -------------------------------------------------------------------------- */ -/* --- SUBFUNCTIONS DEFINITION ---------------------------------------------- */ - -int setup_sx1272( uint32_t freq ) -{ - uint64_t freq_reg; - uint8_t bw = 0; - uint8_t LowZin = 1; - uint8_t sf = 7; - uint8_t AgcAuto = 1; - uint8_t LnaGain = 1; - uint8_t TrimRxCrFo = 0; - uint8_t LnaBoost = 3; - uint8_t AdcBwAuto = 0; - uint8_t AdcBw = 7; - uint8_t AdcLowPwr = 0; - uint8_t AdcTrim = 6; - uint8_t AdcTest = 0; - uint8_t reg_val; - int x; - - x = lgw_sx1272_reg_r(0x42, ®_val); - if (x != LGW_REG_SUCCESS) { - printf("ERROR: Failed to read SX1272 version register\n"); - return EXIT_FAILURE; - } - if (reg_val != 0x22) { - printf("ERROR: Unexpected SX1272 version\n"); - return EXIT_FAILURE; - } - - /* Set in LoRa mode */ - x = lgw_sx1272_reg_w(0x01, 0); - wait_ms(100); - x |= lgw_sx1272_reg_w(0x01, 0 | (1<<7)); - wait_ms(100); - x |= lgw_sx1272_reg_w(0x01, 1 | (1<<7)); - wait_ms(100); - - /* Set PLL freq */ - freq_reg = ((uint64_t)freq << 19) / (uint64_t)32000000; - x |= lgw_sx1272_reg_w(6, (freq_reg >> 16) & 0xFF); - x |= lgw_sx1272_reg_w(7, (freq_reg >> 8) & 0xFF); - x |= lgw_sx1272_reg_w(8, freq_reg & 0xFF); - - /* Config */ - x |= lgw_sx1272_reg_w(0x1D, bw << 6); - x |= lgw_sx1272_reg_w(0x50, LowZin); - x |= lgw_sx1272_reg_w(0x1E, (sf << 4) | (AgcAuto << 2)); - x |= lgw_sx1272_reg_w(0x0C, LnaBoost | (TrimRxCrFo << 3) | (LnaGain << 5)); - x |= lgw_sx1272_reg_w(0x68, AdcBw | (AdcBwAuto << 3)); - x |= lgw_sx1272_reg_w(0x69, AdcTest | (AdcTrim << 4) | (AdcLowPwr << 7)); - - if (x != LGW_REG_SUCCESS) { - printf("ERROR: Failed to configure SX1272\n"); - return EXIT_FAILURE; - } - - /* Set in Rx continuous mode */ - x = lgw_sx1272_reg_w(0x01, 5 | (1<<7)); - wait_ms(100); - x |= lgw_sx1272_reg_r(0x01, ®_val); - if ((reg_val != (5 | (1<<7))) || (x != LGW_REG_SUCCESS)) { - printf("ERROR: SX1272 failed to enter RX continuous mode\n"); - return EXIT_FAILURE; - } - - //printf("INFO: Successfully configured SX1272\n"); - return EXIT_SUCCESS; -} - /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ /* --- EOF ------------------------------------------------------------------ */ diff --git a/util_spi_stress/Makefile b/util_spi_stress/Makefile index a512acd0..23e3c88d 100644 --- a/util_spi_stress/Makefile +++ b/util_spi_stress/Makefile @@ -30,7 +30,7 @@ LGW_INC += $(LGW_PATH)/inc/loragw_reg.h ### Linking options -LIBS := -lloragw -lrt +LIBS := -lloragw -lrt -lm ### General build targets diff --git a/util_spi_stress/src/util_spi_stress.c b/util_spi_stress/src/util_spi_stress.c index 57d9454b..ba0d811b 100644 --- a/util_spi_stress/src/util_spi_stress.c +++ b/util_spi_stress/src/util_spi_stress.c @@ -7,7 +7,7 @@ (C)2013 Semtech-Cycleo Description: - SPI stress test + SPI stress test License: Revised BSD License, see LICENSE.TXT file include in the project Maintainer: Sylvain Miermont @@ -19,26 +19,26 @@ Maintainer: Sylvain Miermont /* fix an issue between POSIX and C99 */ #if __STDC_VERSION__ >= 199901L - #define _XOPEN_SOURCE 600 + #define _XOPEN_SOURCE 600 #else - #define _XOPEN_SOURCE 500 + #define _XOPEN_SOURCE 500 #endif -#include /* C99 types */ -#include /* bool type */ -#include /* printf fprintf sprintf fopen fputs */ +#include /* C99 types */ +#include /* bool type */ +#include /* printf fprintf sprintf fopen fputs */ -#include /* sigaction */ -#include /* getopt access */ -#include /* rand */ +#include /* sigaction */ +#include /* getopt access */ +#include /* rand */ #include "loragw_reg.h" /* -------------------------------------------------------------------------- */ /* --- PRIVATE MACROS ------------------------------------------------------- */ -#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) -#define MSG(args...) fprintf(stderr, args) /* message that is destined to the user */ +#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) +#define MSG(args...) fprintf(stderr, args) /* message that is destined to the user */ /* -------------------------------------------------------------------------- */ /* --- PRIVATE CONSTANTS ---------------------------------------------------- */ @@ -66,18 +66,18 @@ void usage (void); /* --- PRIVATE FUNCTIONS DEFINITION ----------------------------------------- */ static void sig_handler(int sigio) { - if (sigio == SIGQUIT) { - quit_sig = 1;; - } else if ((sigio == SIGINT) || (sigio == SIGTERM)) { - exit_sig = 1; - } + if (sigio == SIGQUIT) { + quit_sig = 1;; + } else if ((sigio == SIGINT) || (sigio == SIGTERM)) { + exit_sig = 1; + } } /* describe command line options */ void usage(void) { - MSG( "Available options:\n"); - MSG( " -h print this help\n"); - MSG( " -t specify which test you want to run (1-4)\n"); + MSG( "Available options:\n"); + MSG( " -h print this help\n"); + MSG( " -t specify which test you want to run (1-4)\n"); } /* -------------------------------------------------------------------------- */ @@ -85,206 +85,206 @@ void usage(void) { int main(int argc, char **argv) { - int i; - int xi = 0; - - /* application option */ - int test_number = 1; - int cycle_number = 0; - int repeats_per_cycle = 1000; - bool error = false; - - /* in/out variables */ - int32_t test_value; - int32_t read_value; - int32_t rb1, rb2, rb3; /* interstitial readbacks, to flush buffers if needed */ - - /* data buffer */ - int32_t test_addr; - uint8_t test_buff[BUFF_SIZE]; - uint8_t read_buff[BUFF_SIZE]; - - /* parse command line options */ - while ((i = getopt (argc, argv, "ht:")) != -1) { - switch (i) { - case 'h': - usage(); - return EXIT_FAILURE; - break; - - case 't': - i = sscanf(optarg, "%i", &xi); - if ((i != 1) || (xi < 1) || (xi > 4)) { - MSG("ERROR: invalid test number\n"); - return EXIT_FAILURE; - } else { - test_number = xi; - } - break; - - default: - MSG("ERROR: argument parsing use -h option for help\n"); - usage(); - return EXIT_FAILURE; - } - } - MSG("INFO: Starting LoRa concentrator SPI stress-test number %i\n", test_number); - - /* configure signal handling */ - sigemptyset(&sigact.sa_mask); - sigact.sa_flags = 0; - sigact.sa_handler = sig_handler; - sigaction(SIGQUIT, &sigact, NULL); - sigaction(SIGINT, &sigact, NULL); - sigaction(SIGTERM, &sigact, NULL); - - /* start SPI link */ - i = lgw_connect(); - if (i != LGW_REG_SUCCESS) { - MSG("ERROR: lgw_connect() did not return SUCCESS"); - return EXIT_FAILURE; - } - - if (test_number == 1) { - /* single 8b register R/W stress test */ - while ((quit_sig != 1) && (exit_sig != 1)) { - printf("Cycle %i > ", cycle_number); - for (i=0; i ", cycle_number); - for (i=0; i ", cycle_number); - for (i=0; i ", cycle_number); - test_addr = rand() & 0xFFFF; - lgw_reg_w(LGW_RX_DATA_BUF_ADDR, test_addr); /* write at random offset in memory */ - lgw_reg_wb(LGW_RX_DATA_BUF_DATA, test_buff, BUFF_SIZE); - lgw_reg_w(LGW_RX_DATA_BUF_ADDR, test_addr); /* go back to start of segment */ - lgw_reg_rb(LGW_RX_DATA_BUF_DATA, read_buff, BUFF_SIZE); - for (i=0; ((i 4)) { + MSG("ERROR: invalid test number\n"); + return EXIT_FAILURE; + } else { + test_number = xi; + } + break; + + default: + MSG("ERROR: argument parsing use -h option for help\n"); + usage(); + return EXIT_FAILURE; + } + } + MSG("INFO: Starting LoRa concentrator SPI stress-test number %i\n", test_number); + + /* configure signal handling */ + sigemptyset(&sigact.sa_mask); + sigact.sa_flags = 0; + sigact.sa_handler = sig_handler; + sigaction(SIGQUIT, &sigact, NULL); + sigaction(SIGINT, &sigact, NULL); + sigaction(SIGTERM, &sigact, NULL); + + /* start SPI link */ + i = lgw_connect(); + if (i != LGW_REG_SUCCESS) { + MSG("ERROR: lgw_connect() did not return SUCCESS"); + return EXIT_FAILURE; + } + + if (test_number == 1) { + /* single 8b register R/W stress test */ + while ((quit_sig != 1) && (exit_sig != 1)) { + printf("Cycle %i > ", cycle_number); + for (i=0; i ", cycle_number); + for (i=0; i ", cycle_number); + for (i=0; i ", cycle_number); + test_addr = rand() & 0xFFFF; + lgw_reg_w(LGW_RX_DATA_BUF_ADDR, test_addr); /* write at random offset in memory */ + lgw_reg_wb(LGW_RX_DATA_BUF_DATA, test_buff, BUFF_SIZE); + lgw_reg_w(LGW_RX_DATA_BUF_ADDR, test_addr); /* go back to start of segment */ + lgw_reg_rb(LGW_RX_DATA_BUF_DATA, read_buff, BUFF_SIZE); + for (i=0; ((i application terminates without shutting down th static void sig_handler(int sigio); -void sx125x_write(uint8_t channel, uint8_t addr, uint8_t data); /* defined in loragw_hal.c */ - -uint8_t sx125x_read(uint8_t channel, uint8_t addr); /* defined in loragw_hal.c */ - -int load_firmware(uint8_t target, uint8_t *firmware, uint16_t size); /* defined in loragw_hal.c */ - /* -------------------------------------------------------------------------- */ /* --- MAIN FUNCTION -------------------------------------------------------- */ diff --git a/util_tx_test/Makefile b/util_tx_test/Makefile index 7c374f06..8c35229f 100644 --- a/util_tx_test/Makefile +++ b/util_tx_test/Makefile @@ -31,7 +31,7 @@ LGW_INC += $(LGW_PATH)/inc/loragw_aux.h ### Linking options -LIBS := -lloragw -lrt +LIBS := -lloragw -lrt -lm ### General build targets diff --git a/util_tx_test/src/util_tx_test.c b/util_tx_test/src/util_tx_test.c index 9dc5f1ca..e27b7872 100644 --- a/util_tx_test/src/util_tx_test.c +++ b/util_tx_test/src/util_tx_test.c @@ -32,6 +32,7 @@ Maintainer: Sylvain Miermont #include /* sigaction */ #include /* getopt access */ #include /* exit codes */ +#include /* getopt_long */ #include "loragw_hal.h" #include "loragw_aux.h" @@ -122,26 +123,31 @@ void usage(void) { printf("*** Library version information ***\n%s\n\n", lgw_version_info()); printf("Available options:\n"); - printf(" -h print this help\n"); - printf(" -r radio type (SX1255:1255, SX1257:1257)\n"); - printf(" -f target frequency in MHz\n"); - printf(" -k concentrator clock source (0:Radio A, 1:Radio B)\n"); - printf(" -m modulation type ['LORA', 'FSK']\n"); - printf(" -b LoRa bandwidth in kHz [125, 250, 500]\n"); - printf(" -s LoRa Spreading Factor [7-12]\n"); - printf(" -c LoRa Coding Rate [1-4]\n"); - printf(" -d FSK frequency deviation in kHz [1:250]\n"); - printf(" -q FSK bitrate in kbps [0.5:250]\n"); - printf(" -p RF power (dBm) [ "); + printf(" -h print this help\n"); + printf(" -r radio type (SX1255:1255, SX1257:1257)\n"); + printf(" -f target frequency in MHz\n"); + printf(" -k concentrator clock source (0:Radio A, 1:Radio B)\n"); + printf(" -m modulation type ['LORA', 'FSK']\n"); + printf(" -b LoRa bandwidth in kHz [125, 250, 500]\n"); + printf(" -s LoRa Spreading Factor [7-12]\n"); + printf(" -c LoRa Coding Rate [1-4]\n"); + printf(" -d FSK frequency deviation in kHz [1:250]\n"); + printf(" -q FSK bitrate in kbps [0.5:250]\n"); + printf(" -p RF power (dBm) [ "); for (i = 0; i < txgain_lut.size; i++) { printf("%ddBm ", txgain_lut.lut[i].rf_power); } printf("]\n"); - printf(" -l LoRa preamble length (symbols)\n"); - printf(" -z payload size (bytes, <256)\n"); - printf(" -t pause between packets (ms)\n"); - printf(" -x nb of times the sequence is repeated (-1 loop until stopped)\n"); - printf(" -i send packet using inverted modulation polarity\n"); + printf(" -l LoRa preamble length (symbols)\n"); + printf(" -z payload size (bytes, <256)\n"); + printf(" -i send packet using inverted modulation polarity\n"); + printf(" -t pause between packets (ms)\n"); + printf(" -x nb of times the sequence is repeated (-1 loop until stopped)\n"); + printf(" --lbt-freq lbt first channel frequency in MHz\n"); + printf(" --lbt-sctm lbt scan time in usec\n"); + printf(" --lbt-max lbt max tx duration in usec\n"); + printf(" --lbt-rssi lbt rssi target (to be divided by -2)\n"); + printf(" --lbt-nbch lbt nb channel\n"); } /* -------------------------------------------------------------------------- */ @@ -173,11 +179,18 @@ int main(int argc, char **argv) bool invert = false; float br_kbps = DEFAULT_BR_KBPS; uint8_t fdev_khz = DEFAULT_FDEV_KHZ; + bool lbt_enable = false; + uint32_t lbt_f_target = 0; + uint32_t lbt_tx_max_time = 4000000; + uint32_t lbt_sc_time = 5000; + uint8_t lbt_rssi_target = 160; + uint8_t lbt_nb_channel = 1; /* RF configuration (TX fail if RF chain is not enabled) */ enum lgw_radio_type_e radio_type = LGW_RADIO_TYPE_NONE; uint8_t clocksource = 1; /* Radio B is source by default */ struct lgw_conf_board_s boardconf; + struct lgw_conf_lbt_s lbtconf; struct lgw_conf_rxrf_s rfconf; /* allocate memory for packet sending */ @@ -186,15 +199,26 @@ int main(int argc, char **argv) /* loop variables (also use as counters in the packet payload) */ uint16_t cycle_count = 0; + /* Parameter parsing */ + int option_index = 0; + static struct option long_options[] = { + {"lbt-freq", required_argument, 0, 0}, + {"lbt-sctm", required_argument, 0, 0}, + {"lbt-max", required_argument, 0, 0}, + {"lbt-rssi", required_argument, 0, 0}, + {"lbt-nbch", required_argument, 0, 0}, + {0, 0, 0, 0} + }; + /* parse command line options */ - while ((i = getopt (argc, argv, "hif:m:b:s:c:p:l:z:t:x:r:k:d:q:")) != -1) { + while ((i = getopt_long (argc, argv, "hif:m:b:s:c:p:l:z:t:x:r:k:d:q:", long_options, &option_index)) != -1) { switch (i) { case 'h': usage(); return EXIT_FAILURE; break; - case 'f': /* -f Target frequency in MHz */ + case 'f': /* Target frequency in MHz */ i = sscanf(optarg, "%lf", &xd); if ((i != 1) || (xd < 30.0) || (xd > 3000.0)) { MSG("ERROR: invalid TX frequency\n"); @@ -205,7 +229,7 @@ int main(int argc, char **argv) } break; - case 'm': /* -m Modulation type */ + case 'm': /* Modulation type */ i = sscanf(optarg, "%s", arg_s); if ((i != 1) || ((strcmp(arg_s,"LORA") != 0) && (strcmp(arg_s,"FSK")))) { MSG("ERROR: invalid modulation type\n"); @@ -216,7 +240,7 @@ int main(int argc, char **argv) } break; - case 'b': /* -b Modulation bandwidth in kHz */ + case 'b': /* Modulation bandwidth in kHz */ i = sscanf(optarg, "%i", &xi); if ((i != 1) || ((xi != 125) && (xi != 250) && (xi != 500))) { MSG("ERROR: invalid LoRa bandwidth\n"); @@ -227,7 +251,7 @@ int main(int argc, char **argv) } break; - case 's': /* -s Spreading Factor */ + case 's': /* Spreading Factor */ i = sscanf(optarg, "%i", &xi); if ((i != 1) || (xi < 7) || (xi > 12)) { MSG("ERROR: invalid spreading factor\n"); @@ -238,7 +262,7 @@ int main(int argc, char **argv) } break; - case 'c': /* -c Coding Rate */ + case 'c': /* Coding Rate */ i = sscanf(optarg, "%i", &xi); if ((i != 1) || (xi < 1) || (xi > 4)) { MSG("ERROR: invalid coding rate\n"); @@ -249,7 +273,7 @@ int main(int argc, char **argv) } break; - case 'p': /* -p RF power */ + case 'p': /* RF power */ i = sscanf(optarg, "%i", &xi); if ((i != 1) || (xi < -60) || (xi > 60)) { MSG("ERROR: invalid RF power\n"); @@ -260,7 +284,7 @@ int main(int argc, char **argv) } break; - case 'd': /* -d FSK frequency deviation */ + case 'd': /* FSK frequency deviation */ i = sscanf(optarg, "%u", &xu); if ((i != 1) || (xu < 1) || (xu > 250)) { MSG("ERROR: invalid FSK frequency deviation\n"); @@ -271,7 +295,7 @@ int main(int argc, char **argv) } break; - case 'q': /* -q FSK bitrate */ + case 'q': /* FSK bitrate */ i = sscanf(optarg, "%f", &xf); if ((i != 1) || (xf < 0.5) || (xf > 250)) { MSG("ERROR: invalid FSK bitrate\n"); @@ -282,7 +306,7 @@ int main(int argc, char **argv) } break; - case 'l': /* -r preamble length (symbols) */ + case 'l': /* preamble length (symbols) */ i = sscanf(optarg, "%i", &xi); if ((i != 1) || (xi < 6)) { MSG("ERROR: preamble length must be >6 symbols \n"); @@ -293,7 +317,7 @@ int main(int argc, char **argv) } break; - case 'z': /* -z payload length (bytes) */ + case 'z': /* payload length (bytes) */ i = sscanf(optarg, "%i", &xi); if ((i != 1) || (xi <= 0)) { MSG("ERROR: invalid payload size\n"); @@ -304,7 +328,7 @@ int main(int argc, char **argv) } break; - case 't': /* -t pause between packets (ms) */ + case 't': /* pause between packets (ms) */ i = sscanf(optarg, "%i", &xi); if ((i != 1) || (xi < 0)) { MSG("ERROR: invalid time between packets\n"); @@ -315,7 +339,7 @@ int main(int argc, char **argv) } break; - case 'x': /* -x numbers of times the sequence is repeated */ + case 'x': /* numbers of times the sequence is repeated */ i = sscanf(optarg, "%i", &xi); if ((i != 1) || (xi < -1)) { MSG("ERROR: invalid number of repeats\n"); @@ -342,7 +366,7 @@ int main(int argc, char **argv) } break; - case 'i': /* -i send packet using inverted modulation polarity */ + case 'i': /* Send packet using inverted modulation polarity */ invert = true; break; @@ -357,6 +381,79 @@ int main(int argc, char **argv) } break; + case 0: + if( strcmp(long_options[option_index].name, "lbt-freq") == 0 ) { /* LBT first channel frequency in MHz */ + i = sscanf(optarg, "%lf", &xd); + if ((i != 1) || (xd < 30.0) || (xd > 3000.0)) { + MSG("ERROR: invalid LBT start frequency\n"); + usage(); + return EXIT_FAILURE; + } else { + lbt_f_target = (uint32_t)((xd*1e6) + 0.5); /* .5 Hz offset to get rounding instead of truncating */ + lbt_enable = true; + } + } else if( strcmp(long_options[option_index].name, "lbt-sctm") == 0 ) { /* LBT scan time in usec */ + if (lbt_enable == true) { + i = sscanf(optarg, "%i", &xi); + if ((i != 1) || (xi < 0)) { + MSG("ERROR: invalid LBT scan time\n"); + usage(); + return EXIT_FAILURE; + } else { + lbt_sc_time = xi; + } + } else { + MSG("ERROR: invalid parameter, LBT start frequency must be set\n"); + usage(); + return EXIT_FAILURE; + } + } else if( strcmp(long_options[option_index].name, "lbt-rssi") == 0 ) { /* LBT RSSI target */ + if (lbt_enable == true) { + i = sscanf(optarg, "%i", &xi); + if ((i != 1) || (xi < 0)) { + MSG("ERROR: invalid LBT RSSI target\n"); + usage(); + return EXIT_FAILURE; + } else { + lbt_rssi_target = xi; + } + } else { + MSG("ERROR: invalid parameter, LBT start frequency must be set\n"); + usage(); + return EXIT_FAILURE; + } + } else if( strcmp(long_options[option_index].name, "lbt-nbch") == 0 ) { /* LBT number of channels */ + if (lbt_enable == true) { + i = sscanf(optarg, "%i", &xi); + if ((i != 1) || (xi < 0)) { + MSG("ERROR: invalid LBT number of channels\n"); + usage(); + return EXIT_FAILURE; + } else { + lbt_nb_channel = xi; + } + } else { + MSG("ERROR: invalid parameter, LBT start frequency must be set\n"); + usage(); + return EXIT_FAILURE; + } + } else if( strcmp(long_options[option_index].name, "lbt-max") == 0 ) { /* LBT max tx duration in usec */ + if (lbt_enable == true) { + i = sscanf(optarg, "%i", &xi); + if ((i != 1) || (xi < 0)) { + MSG("ERROR: invalid LBT max TX duration\n"); + usage(); + return EXIT_FAILURE; + } else { + lbt_tx_max_time = xi; + } + } else { + MSG("ERROR: invalid parameter, LBT start frequency must be set\n"); + usage(); + return EXIT_FAILURE; + } + } + break; default: MSG("ERROR: argument parsing\n"); usage(); @@ -374,6 +471,7 @@ int main(int argc, char **argv) return EXIT_FAILURE; } + /* Summary of packet parameters */ if (strcmp(mod, "FSK") == 0) { printf("Sending %i FSK packets on %u Hz (FDev %u kHz, Bitrate %.2f, %i bytes payload, %i symbols preamble) at %i dBm, with %i ms between each\n", repeat, f_target, fdev_khz, br_kbps, pl_size, preamb, pow, delay); } else { @@ -391,24 +489,42 @@ int main(int argc, char **argv) /* starting the concentrator */ /* board config */ memset(&boardconf, 0, sizeof(boardconf)); - boardconf.lorawan_public = true; boardconf.clksrc = clocksource; lgw_board_setconf(boardconf); + /* LBT config */ + if (lbt_enable) { + memset(&lbtconf, 0, sizeof(lbtconf)); + lbtconf.enable = true; + lbtconf.rssi_target = lbt_rssi_target; + lbtconf.scan_time_us = lbt_sc_time; + lbtconf.nb_channel = lbt_nb_channel; + lbtconf.start_freq = lbt_f_target; + lbtconf.tx_delay_1ch_us = lbt_tx_max_time; + lbtconf.tx_delay_2ch_us = lbt_tx_max_time; + lgw_lbt_setconf(lbtconf); + } + /* RF config */ memset(&rfconf, 0, sizeof(rfconf)); - rfconf.enable = true; rfconf.freq_hz = f_target; rfconf.rssi_offset = DEFAULT_RSSI_OFFSET; rfconf.type = radio_type; - rfconf.tx_enable = true; - lgw_rxrf_setconf(TX_RF_CHAIN, rfconf); + for (i = 0; i < LGW_RF_CHAIN_NB; i++) { + if (i == TX_RF_CHAIN) { + rfconf.tx_enable = true; + } else { + rfconf.tx_enable = false; + } + lgw_rxrf_setconf(i, rfconf); + } /* TX gain config */ lgw_txgain_setconf(&txgain_lut); + /* Start concentrator */ i = lgw_start(); if (i == LGW_HAL_SUCCESS) { MSG("INFO: concentrator started, packet can be sent\n"); @@ -475,18 +591,20 @@ int main(int argc, char **argv) /* send packet */ printf("Sending packet number %u ...", cycle_count); i = lgw_send(txpkt); /* non-blocking scheduling of TX packet */ - if (i != LGW_HAL_SUCCESS) { + if (i == LGW_HAL_ERROR) { printf("ERROR\n"); return EXIT_FAILURE; + } else if (i == LGW_LBT_ISSUE ) { + printf("Failed: Not allowed (LBT)\n"); + } else { + /* wait for packet to finish sending */ + do { + wait_ms(5); + lgw_status(TX_STATUS, &status_var); /* get TX status */ + } while (status_var != TX_FREE); + printf("OK\n"); } - /* wait for packet to finish sending */ - do { - wait_ms(5); - lgw_status(TX_STATUS, &status_var); /* get TX status */ - } while (status_var != TX_FREE); - printf("OK\n"); - /* wait inter-packet delay */ wait_ms(delay);