radio.c 35 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148
  1. /*!
  2. * \file radio.c
  3. *
  4. * \brief Radio driver API definition
  5. *
  6. * \copyright Revised BSD License, see section \ref LICENSE.
  7. *
  8. * \code
  9. * ______ _
  10. * / _____) _ | |
  11. * ( (____ _____ ____ _| |_ _____ ____| |__
  12. * \____ \| ___ | (_ _) ___ |/ ___) _ \
  13. * _____) ) ____| | | || |_| ____( (___| | | |
  14. * (______/|_____)_|_|_| \__)_____)\____)_| |_|
  15. * (C)2013-2017 Semtech
  16. *
  17. * \endcode
  18. *
  19. * \author Miguel Luis ( Semtech )
  20. *
  21. * \author Gregory Cristian ( Semtech )
  22. */
  23. #include <math.h>
  24. #include <string.h>
  25. #include <stdbool.h>
  26. #include "radio.h"
  27. #include "sx126x.h"
  28. #include "sx126x-board.h"
  29. /*!
  30. * \brief Initializes the radio
  31. *
  32. * \param [IN] events Structure containing the driver callback functions
  33. */
  34. void RadioInit( RadioEvents_t *events );
  35. /*!
  36. * Return current radio status
  37. *
  38. * \param status Radio status.[RF_IDLE, RF_RX_RUNNING, RF_TX_RUNNING]
  39. */
  40. RadioState_t RadioGetStatus( void );
  41. /*!
  42. * \brief Configures the radio with the given modem
  43. *
  44. * \param [IN] modem Modem to be used [0: FSK, 1: LoRa]
  45. */
  46. void RadioSetModem( RadioModems_t modem );
  47. /*!
  48. * \brief Sets the channel frequency
  49. *
  50. * \param [IN] freq Channel RF frequency
  51. */
  52. void RadioSetChannel( uint32_t freq );
  53. /*!
  54. * \brief Checks if the channel is free for the given time
  55. *
  56. * \param [IN] modem Radio modem to be used [0: FSK, 1: LoRa]
  57. * \param [IN] freq Channel RF frequency
  58. * \param [IN] rssiThresh RSSI threshold
  59. * \param [IN] maxCarrierSenseTime Max time while the RSSI is measured
  60. *
  61. * \retval isFree [true: Channel is free, false: Channel is not free]
  62. */
  63. bool RadioIsChannelFree( RadioModems_t modem, uint32_t freq, int16_t rssiThresh, uint32_t maxCarrierSenseTime );
  64. /*!
  65. * \brief Generates a 32 bits random value based on the RSSI readings
  66. *
  67. * \remark This function sets the radio in LoRa modem mode and disables
  68. * all interrupts.
  69. * After calling this function either Radio.SetRxConfig or
  70. * Radio.SetTxConfig functions must be called.
  71. *
  72. * \retval randomValue 32 bits random value
  73. */
  74. uint32_t RadioRandom( void );
  75. /*!
  76. * \brief Sets the reception parameters
  77. *
  78. * \param [IN] modem Radio modem to be used [0: FSK, 1: LoRa]
  79. * \param [IN] bandwidth Sets the bandwidth
  80. * FSK : >= 2600 and <= 250000 Hz
  81. * LoRa: [0: 125 kHz, 1: 250 kHz,
  82. * 2: 500 kHz, 3: Reserved]
  83. * \param [IN] datarate Sets the Datarate
  84. * FSK : 600..300000 bits/s
  85. * LoRa: [6: 64, 7: 128, 8: 256, 9: 512,
  86. * 10: 1024, 11: 2048, 12: 4096 chips]
  87. * \param [IN] coderate Sets the coding rate (LoRa only)
  88. * FSK : N/A ( set to 0 )
  89. * LoRa: [1: 4/5, 2: 4/6, 3: 4/7, 4: 4/8]
  90. * \param [IN] bandwidthAfc Sets the AFC Bandwidth (FSK only)
  91. * FSK : >= 2600 and <= 250000 Hz
  92. * LoRa: N/A ( set to 0 )
  93. * \param [IN] preambleLen Sets the Preamble length
  94. * FSK : Number of bytes
  95. * LoRa: Length in symbols (the hardware adds 4 more symbols)
  96. * \param [IN] symbTimeout Sets the RxSingle timeout value
  97. * FSK : timeout in number of bytes
  98. * LoRa: timeout in symbols
  99. * \param [IN] fixLen Fixed length packets [0: variable, 1: fixed]
  100. * \param [IN] payloadLen Sets payload length when fixed length is used
  101. * \param [IN] crcOn Enables/Disables the CRC [0: OFF, 1: ON]
  102. * \param [IN] FreqHopOn Enables disables the intra-packet frequency hopping
  103. * FSK : N/A ( set to 0 )
  104. * LoRa: [0: OFF, 1: ON]
  105. * \param [IN] HopPeriod Number of symbols between each hop
  106. * FSK : N/A ( set to 0 )
  107. * LoRa: Number of symbols
  108. * \param [IN] iqInverted Inverts IQ signals (LoRa only)
  109. * FSK : N/A ( set to 0 )
  110. * LoRa: [0: not inverted, 1: inverted]
  111. * \param [IN] rxContinuous Sets the reception in continuous mode
  112. * [false: single mode, true: continuous mode]
  113. */
  114. void RadioSetRxConfig( RadioModems_t modem, uint32_t bandwidth,
  115. uint32_t datarate, uint8_t coderate,
  116. uint32_t bandwidthAfc, uint16_t preambleLen,
  117. uint16_t symbTimeout, bool fixLen,
  118. uint8_t payloadLen,
  119. bool crcOn, bool FreqHopOn, uint8_t HopPeriod,
  120. bool iqInverted, bool rxContinuous ,bool LowDatarateOptimize);
  121. /*!
  122. * \brief Sets the transmission parameters
  123. *
  124. * \param [IN] modem Radio modem to be used [0: FSK, 1: LoRa]
  125. * \param [IN] power Sets the output power [dBm]
  126. * \param [IN] fdev Sets the frequency deviation (FSK only)
  127. * FSK : [Hz]
  128. * LoRa: 0
  129. * \param [IN] bandwidth Sets the bandwidth (LoRa only)
  130. * FSK : 0
  131. * LoRa: [0: 125 kHz, 1: 250 kHz,
  132. * 2: 500 kHz, 3: Reserved]
  133. * \param [IN] datarate Sets the Datarate
  134. * FSK : 600..300000 bits/s
  135. * LoRa: [6: 64, 7: 128, 8: 256, 9: 512,
  136. * 10: 1024, 11: 2048, 12: 4096 chips]
  137. * \param [IN] coderate Sets the coding rate (LoRa only)
  138. * FSK : N/A ( set to 0 )
  139. * LoRa: [1: 4/5, 2: 4/6, 3: 4/7, 4: 4/8]
  140. * \param [IN] preambleLen Sets the preamble length
  141. * FSK : Number of bytes
  142. * LoRa: Length in symbols (the hardware adds 4 more symbols)
  143. * \param [IN] fixLen Fixed length packets [0: variable, 1: fixed]
  144. * \param [IN] crcOn Enables disables the CRC [0: OFF, 1: ON]
  145. * \param [IN] FreqHopOn Enables disables the intra-packet frequency hopping
  146. * FSK : N/A ( set to 0 )
  147. * LoRa: [0: OFF, 1: ON]
  148. * \param [IN] HopPeriod Number of symbols between each hop
  149. * FSK : N/A ( set to 0 )
  150. * LoRa: Number of symbols
  151. * \param [IN] iqInverted Inverts IQ signals (LoRa only)
  152. * FSK : N/A ( set to 0 )
  153. * LoRa: [0: not inverted, 1: inverted]
  154. * \param [IN] timeout Transmission timeout [ms]
  155. */
  156. void RadioSetTxConfig( RadioModems_t modem, int8_t power, uint32_t fdev,
  157. uint32_t bandwidth, uint32_t datarate,
  158. uint8_t coderate, uint16_t preambleLen,
  159. bool fixLen, bool crcOn, bool FreqHopOn,
  160. uint8_t HopPeriod, bool iqInverted, uint32_t timeout ,bool LowDatarateOptimize);
  161. /*!
  162. * \brief Checks if the given RF frequency is supported by the hardware
  163. *
  164. * \param [IN] frequency RF frequency to be checked
  165. * \retval isSupported [true: supported, false: unsupported]
  166. */
  167. bool RadioCheckRfFrequency( uint32_t frequency );
  168. /*!
  169. * \brief Computes the packet time on air in ms for the given payload
  170. *
  171. * \Remark Can only be called once SetRxConfig or SetTxConfig have been called
  172. *
  173. * \param [IN] modem Radio modem to be used [0: FSK, 1: LoRa]
  174. * \param [IN] pktLen Packet payload length
  175. *
  176. * \retval airTime Computed airTime (ms) for the given packet payload length
  177. */
  178. uint32_t RadioTimeOnAir( RadioModems_t modem, uint8_t pktLen );
  179. /*!
  180. * \brief Sends the buffer of size. Prepares the packet to be sent and sets
  181. * the radio in transmission
  182. *
  183. * \param [IN]: buffer Buffer pointer
  184. * \param [IN]: size Buffer size
  185. */
  186. void RadioSend( uint8_t *buffer, uint8_t size );
  187. /*!
  188. * \brief Sets the radio in sleep mode
  189. */
  190. void RadioSleep( void );
  191. /*!
  192. * \brief Sets the radio in standby mode
  193. */
  194. void RadioStandby( void );
  195. /*!
  196. * \brief Sets the radio in reception mode for the given time
  197. * \param [IN] timeout Reception timeout [ms]
  198. * [0: continuous, others timeout]
  199. */
  200. void RadioRx( uint32_t timeout );
  201. /*!
  202. * \brief Start a Channel Activity Detection
  203. */
  204. void RadioStartCad( void );
  205. /*!
  206. * \brief Sets the radio in continuous wave transmission mode
  207. *
  208. * \param [IN]: freq Channel RF frequency
  209. * \param [IN]: power Sets the output power [dBm]
  210. * \param [IN]: time Transmission mode timeout [s]
  211. */
  212. void RadioSetTxContinuousWave( uint32_t freq, int8_t power, uint16_t time );
  213. /*!
  214. * \brief Reads the current RSSI value
  215. *
  216. * \retval rssiValue Current RSSI value in [dBm]
  217. */
  218. int16_t RadioRssi( RadioModems_t modem );
  219. /*!
  220. * \brief Writes the radio register at the specified address
  221. *
  222. * \param [IN]: addr Register address
  223. * \param [IN]: data New register value
  224. */
  225. void RadioWrite( uint16_t addr, uint8_t data );
  226. /*!
  227. * \brief Reads the radio register at the specified address
  228. *
  229. * \param [IN]: addr Register address
  230. * \retval data Register value
  231. */
  232. uint8_t RadioRead( uint16_t addr );
  233. /*!
  234. * \brief Writes multiple radio registers starting at address
  235. *
  236. * \param [IN] addr First Radio register address
  237. * \param [IN] buffer Buffer containing the new register's values
  238. * \param [IN] size Number of registers to be written
  239. */
  240. void RadioWriteBuffer( uint16_t addr, uint8_t *buffer, uint8_t size );
  241. /*!
  242. * \brief Reads multiple radio registers starting at address
  243. *
  244. * \param [IN] addr First Radio register address
  245. * \param [OUT] buffer Buffer where to copy the registers data
  246. * \param [IN] size Number of registers to be read
  247. */
  248. void RadioReadBuffer( uint16_t addr, uint8_t *buffer, uint8_t size );
  249. /*!
  250. * \brief Sets the maximum payload length.
  251. *
  252. * \param [IN] modem Radio modem to be used [0: FSK, 1: LoRa]
  253. * \param [IN] max Maximum payload length in bytes
  254. */
  255. void RadioSetMaxPayloadLength( RadioModems_t modem, uint8_t max );
  256. /*!
  257. * \brief Sets the network to public or private. Updates the sync byte.
  258. *
  259. * \remark Applies to LoRa modem only
  260. *
  261. * \param [IN] enable if true, it enables a public network
  262. */
  263. void RadioSetPublicNetwork( bool enable );
  264. /*!
  265. * \brief Gets the time required for the board plus radio to get out of sleep.[ms]
  266. *
  267. * \retval time Radio plus board wakeup time in ms.
  268. */
  269. uint32_t RadioGetWakeupTime( void );
  270. /*!
  271. * \brief Process radio irq
  272. */
  273. void RadioIrqProcess( void );
  274. /*!
  275. * \brief Sets the radio in reception mode with Max LNA gain for the given time
  276. * \param [IN] timeout Reception timeout [ms]
  277. * [0: continuous, others timeout]
  278. */
  279. void RadioRxBoosted( uint32_t timeout );
  280. /*!
  281. * \brief Sets the Rx duty cycle management parameters
  282. *
  283. * \param [in] rxTime Structure describing reception timeout value
  284. * \param [in] sleepTime Structure describing sleep timeout value
  285. */
  286. void RadioSetRxDutyCycle( uint32_t rxTime, uint32_t sleepTime );
  287. /*!
  288. * Radio driver structure initialization
  289. */
  290. const struct Radio_s Radio =
  291. {
  292. RadioInit,
  293. RadioGetStatus,
  294. RadioSetModem,
  295. RadioSetChannel,
  296. RadioIsChannelFree,
  297. RadioRandom,
  298. RadioSetRxConfig,
  299. RadioSetTxConfig,
  300. RadioCheckRfFrequency,
  301. RadioTimeOnAir,
  302. RadioSend,
  303. RadioSleep,
  304. RadioStandby,
  305. RadioRx,
  306. RadioStartCad,
  307. RadioSetTxContinuousWave,
  308. RadioRssi,
  309. RadioWrite,
  310. RadioRead,
  311. RadioWriteBuffer,
  312. RadioReadBuffer,
  313. RadioSetMaxPayloadLength,
  314. RadioSetPublicNetwork,
  315. RadioGetWakeupTime,
  316. RadioIrqProcess,
  317. // Available on SX126x only
  318. RadioRxBoosted,
  319. RadioSetRxDutyCycle
  320. };
  321. /*
  322. * Local types definition
  323. */
  324. /*!
  325. * FSK bandwidth definition
  326. */
  327. typedef struct
  328. {
  329. uint32_t bandwidth;
  330. uint8_t RegValue;
  331. }FskBandwidth_t;
  332. /*!
  333. * Precomputed FSK bandwidth registers values
  334. */
  335. static const FskBandwidth_t FskBandwidths[] =
  336. {
  337. { 4800 , 0x1F },
  338. { 5800 , 0x17 },
  339. { 7300 , 0x0F },
  340. { 9700 , 0x1E },
  341. { 11700 , 0x16 },
  342. { 14600 , 0x0E },
  343. { 19500 , 0x1D },
  344. { 23400 , 0x15 },
  345. { 29300 , 0x0D },
  346. { 39000 , 0x1C },
  347. { 46900 , 0x14 },
  348. { 58600 , 0x0C },
  349. { 78200 , 0x1B },
  350. { 93800 , 0x13 },
  351. { 117300, 0x0B },
  352. { 156200, 0x1A },
  353. { 187200, 0x12 },
  354. { 234300, 0x0A },
  355. { 312000, 0x19 },
  356. { 373600, 0x11 },
  357. { 467000, 0x09 },
  358. { 500000, 0x00 }, // Invalid Bandwidth
  359. };
  360. static const RadioLoRaBandwidths_t Bandwidths[] = { LORA_BW_125, LORA_BW_250, LORA_BW_500 };
  361. // SF12 SF11 SF10 SF9 SF8 SF7
  362. static double RadioLoRaSymbTime[3][6] = {{ 32.768, 16.384, 8.192, 4.096, 2.048, 1.024 }, // 125 KHz
  363. { 16.384, 8.192, 4.096, 2.048, 1.024, 0.512 }, // 250 KHz
  364. { 8.192, 4.096, 2.048, 1.024, 0.512, 0.256 }}; // 500 KHz
  365. uint8_t MaxPayloadLength = 0xFF;
  366. uint32_t TxTimeout = 0;
  367. uint32_t RxTimeout = 0;
  368. bool RxContinuous = false;
  369. PacketStatus_t RadioPktStatus;
  370. uint8_t RadioRxPayload[255];
  371. /*
  372. * SX126x DIO IRQ callback functions prototype
  373. */
  374. /*!
  375. * \brief DIO 0 IRQ callback
  376. */
  377. void RadioOnDioIrq( void );
  378. /*!
  379. * \brief Tx timeout timer callback
  380. */
  381. void RadioOnTxTimeoutIrq( void );
  382. /*!
  383. * \brief Rx timeout timer callback
  384. */
  385. void RadioOnRxTimeoutIrq( void );
  386. /*
  387. * Private global variables
  388. */
  389. /*!
  390. * Holds the current network type for the radio
  391. */
  392. typedef struct
  393. {
  394. bool Previous;
  395. bool Current;
  396. }RadioPublicNetwork_t;
  397. static RadioPublicNetwork_t RadioPublicNetwork = { false };
  398. /*!
  399. * Radio callbacks variable
  400. */
  401. RadioEvents_t* RadioEvents;
  402. /*
  403. * Public global variables
  404. */
  405. /*!
  406. * Radio hardware and global parameters
  407. */
  408. SX126x_t SX126x;
  409. /*!
  410. * Tx and Rx timers
  411. */
  412. //TimerEvent_t TxTimeoutTimer;
  413. //TimerEvent_t RxTimeoutTimer;
  414. /*!
  415. * Returns the known FSK bandwidth registers value
  416. *
  417. * \param [IN] bandwidth Bandwidth value in Hz
  418. * \retval regValue Bandwidth register value.
  419. */
  420. static uint8_t RadioGetFskBandwidthRegValue( uint32_t bandwidth )
  421. {
  422. uint8_t i;
  423. if( bandwidth == 0 )
  424. {
  425. return( 0x1F );
  426. }
  427. for( i = 0; i < ( sizeof( FskBandwidths ) / sizeof( FskBandwidth_t ) ) - 1; i++ )
  428. {
  429. if( ( bandwidth >= FskBandwidths[i].bandwidth ) && ( bandwidth < FskBandwidths[i + 1].bandwidth ) )
  430. {
  431. return FskBandwidths[i+1].RegValue;
  432. }
  433. }
  434. // ERROR: Value not found
  435. while( 1 );
  436. }
  437. void RadioEventsInit(RadioEvents_t *events){
  438. RadioEvents = events;
  439. }
  440. void RadioInit( RadioEvents_t *events )
  441. {
  442. RadioEvents = events;
  443. SX126xInit( RadioOnDioIrq );
  444. SX126xSetStandby( STDBY_RC );
  445. SX126xSetRegulatorMode( USE_DCDC );
  446. SX126xSetBufferBaseAddress( 0x00, 0x00 );
  447. SX126xSetTxParams( 0, RADIO_RAMP_200_US );
  448. SX126xSetDioIrqParams( IRQ_RADIO_ALL, IRQ_RADIO_ALL, IRQ_RADIO_NONE, IRQ_RADIO_NONE );
  449. }
  450. RadioState_t RadioGetStatus( void )
  451. {
  452. switch( SX126xGetOperatingMode( ) )
  453. {
  454. case MODE_TX:
  455. return RF_TX_RUNNING;
  456. case MODE_RX:
  457. return RF_RX_RUNNING;
  458. case RF_CAD:
  459. return RF_CAD;
  460. default:
  461. return RF_IDLE;
  462. }
  463. }
  464. void RadioSetModem( RadioModems_t modem )
  465. {
  466. switch( modem )
  467. {
  468. default:
  469. case MODEM_FSK:
  470. SX126xSetPacketType( PACKET_TYPE_GFSK );
  471. // When switching to GFSK mode the LoRa SyncWord register value is reset
  472. // Thus, we also reset the RadioPublicNetwork variable
  473. RadioPublicNetwork.Current = false;
  474. break;
  475. case MODEM_LORA:
  476. SX126xSetPacketType( PACKET_TYPE_LORA );
  477. // Public/Private network register is reset when switching modems
  478. if( RadioPublicNetwork.Current != RadioPublicNetwork.Previous )
  479. {
  480. RadioPublicNetwork.Current = RadioPublicNetwork.Previous;
  481. RadioSetPublicNetwork( RadioPublicNetwork.Current );
  482. }
  483. break;
  484. }
  485. }
  486. void RadioSetChannel( uint32_t freq )
  487. {
  488. SX126xSetRfFrequency( freq );
  489. }
  490. bool RadioIsChannelFree( RadioModems_t modem, uint32_t freq, int16_t rssiThresh, uint32_t maxCarrierSenseTime )
  491. {
  492. bool status = true;
  493. // int16_t rssi = 0;
  494. // uint32_t carrierSenseTime = 0;
  495. RadioSetModem( modem );
  496. RadioSetChannel( freq );
  497. RadioRx( 0 );
  498. SX126xDelayMs( 1 );
  499. //carrierSenseTime = TimerGetCurrentTime( );
  500. //Perform carrier sense for maxCarrierSenseTime
  501. // while( TimerGetElapsedTime( carrierSenseTime ) < maxCarrierSenseTime )
  502. // {
  503. // rssi = RadioRssi( modem );
  504. //
  505. // if( rssi > rssiThresh )
  506. // {
  507. // status = false;
  508. // break;
  509. // }
  510. // }
  511. RadioSleep( );
  512. return status;
  513. }
  514. uint32_t RadioRandom( void )
  515. {
  516. uint8_t i;
  517. uint32_t rnd = 0;
  518. /*
  519. * Radio setup for random number generation
  520. */
  521. // Set LoRa modem ON
  522. RadioSetModem( MODEM_LORA );
  523. // Set radio in continuous reception
  524. SX126xSetRx( 0 );
  525. for( i = 0; i < 32; i++ )
  526. {
  527. SX126xDelayMs( 1 );
  528. // Unfiltered RSSI value reading. Only takes the LSB value
  529. rnd |= ( ( uint32_t )SX126xGetRssiInst( ) & 0x01 ) << i;
  530. }
  531. RadioSleep( );
  532. return rnd;
  533. }
  534. void RadioSetRxConfig( RadioModems_t modem, uint32_t bandwidth,
  535. uint32_t datarate, uint8_t coderate,
  536. uint32_t bandwidthAfc, uint16_t preambleLen,
  537. uint16_t symbTimeout, bool fixLen,
  538. uint8_t payloadLen,
  539. bool crcOn, bool freqHopOn, uint8_t hopPeriod,
  540. bool iqInverted, bool rxContinuous ,bool LowDatarateOptimize)
  541. {
  542. RxContinuous = rxContinuous;
  543. if( fixLen == true )
  544. {
  545. MaxPayloadLength = payloadLen;
  546. }
  547. else
  548. {
  549. MaxPayloadLength = 0xFF;
  550. }
  551. switch( modem )
  552. {
  553. case MODEM_FSK:
  554. SX126xSetStopRxTimerOnPreambleDetect( false );
  555. SX126x.ModulationParams.PacketType = PACKET_TYPE_GFSK;
  556. SX126x.ModulationParams.Params.Gfsk.BitRate = datarate;
  557. SX126x.ModulationParams.Params.Gfsk.ModulationShaping = MOD_SHAPING_G_BT_1;
  558. SX126x.ModulationParams.Params.Gfsk.Bandwidth = RadioGetFskBandwidthRegValue( bandwidth );
  559. SX126x.PacketParams.PacketType = PACKET_TYPE_GFSK;
  560. SX126x.PacketParams.Params.Gfsk.PreambleLength = ( preambleLen << 3 ); // convert byte into bit
  561. SX126x.PacketParams.Params.Gfsk.PreambleMinDetect = RADIO_PREAMBLE_DETECTOR_08_BITS;
  562. SX126x.PacketParams.Params.Gfsk.SyncWordLength = 3 << 3; // convert byte into bit
  563. SX126x.PacketParams.Params.Gfsk.AddrComp = RADIO_ADDRESSCOMP_FILT_OFF;
  564. SX126x.PacketParams.Params.Gfsk.HeaderType = ( fixLen == true ) ? RADIO_PACKET_FIXED_LENGTH : RADIO_PACKET_VARIABLE_LENGTH;
  565. SX126x.PacketParams.Params.Gfsk.PayloadLength = MaxPayloadLength;
  566. if( crcOn == true )
  567. {
  568. SX126x.PacketParams.Params.Gfsk.CrcLength = RADIO_CRC_2_BYTES_CCIT;
  569. }
  570. else
  571. {
  572. SX126x.PacketParams.Params.Gfsk.CrcLength = RADIO_CRC_OFF;
  573. }
  574. SX126x.PacketParams.Params.Gfsk.DcFree = RADIO_DC_FREE_OFF;
  575. RadioStandby( );
  576. RadioSetModem( ( SX126x.ModulationParams.PacketType == PACKET_TYPE_GFSK ) ? MODEM_FSK : MODEM_LORA );
  577. SX126xSetModulationParams( &SX126x.ModulationParams );
  578. SX126xSetPacketParams( &SX126x.PacketParams );
  579. SX126xSetSyncWord( ( uint8_t[] ){ 0xC1, 0x94, 0xC1, 0x00, 0x00, 0x00, 0x00, 0x00 } );
  580. SX126xSetWhiteningSeed( 0x01FF );
  581. RxTimeout = ( uint32_t )( symbTimeout * ( ( 1.0 / ( double )datarate ) * 8.0 ) * 1000 );
  582. break;
  583. case MODEM_LORA:
  584. SX126xSetStopRxTimerOnPreambleDetect( false );
  585. SX126xSetLoRaSymbNumTimeout( symbTimeout );
  586. SX126x.ModulationParams.PacketType = PACKET_TYPE_LORA;
  587. SX126x.ModulationParams.Params.LoRa.SpreadingFactor = ( RadioLoRaSpreadingFactors_t )datarate;
  588. SX126x.ModulationParams.Params.LoRa.Bandwidth = Bandwidths[bandwidth];
  589. SX126x.ModulationParams.Params.LoRa.CodingRate = ( RadioLoRaCodingRates_t )coderate;
  590. if (LowDatarateOptimize){
  591. SX126x.ModulationParams.Params.LoRa.LowDatarateOptimize = 0x01;
  592. }else{
  593. if( ( ( bandwidth == 0 ) && ( ( datarate == 11 ) || ( datarate == 12 ) ) ) ||
  594. ( ( bandwidth == 1 ) && ( datarate == 12 ) ) )
  595. {
  596. SX126x.ModulationParams.Params.LoRa.LowDatarateOptimize = 0x01;
  597. }
  598. else
  599. {
  600. SX126x.ModulationParams.Params.LoRa.LowDatarateOptimize = 0x00;
  601. }
  602. }
  603. SX126x.PacketParams.PacketType = PACKET_TYPE_LORA;
  604. if( ( SX126x.ModulationParams.Params.LoRa.SpreadingFactor == LORA_SF5 ) ||
  605. ( SX126x.ModulationParams.Params.LoRa.SpreadingFactor == LORA_SF6 ) )
  606. {
  607. if( preambleLen < 12 )
  608. {
  609. SX126x.PacketParams.Params.LoRa.PreambleLength = 12;
  610. }
  611. else
  612. {
  613. SX126x.PacketParams.Params.LoRa.PreambleLength = preambleLen;
  614. }
  615. }
  616. else
  617. {
  618. SX126x.PacketParams.Params.LoRa.PreambleLength = preambleLen;
  619. }
  620. SX126x.PacketParams.Params.LoRa.HeaderType = ( RadioLoRaPacketLengthsMode_t )fixLen;
  621. SX126x.PacketParams.Params.LoRa.PayloadLength = MaxPayloadLength;
  622. SX126x.PacketParams.Params.LoRa.CrcMode = ( RadioLoRaCrcModes_t )crcOn;
  623. SX126x.PacketParams.Params.LoRa.InvertIQ = ( RadioLoRaIQModes_t )iqInverted;
  624. RadioSetModem( ( SX126x.ModulationParams.PacketType == PACKET_TYPE_GFSK ) ? MODEM_FSK : MODEM_LORA );
  625. SX126xSetModulationParams( &SX126x.ModulationParams );
  626. SX126xSetPacketParams( &SX126x.PacketParams );
  627. // Timeout Max, Timeout handled directly in SetRx function
  628. RxTimeout = 0xFFFF;
  629. break;
  630. }
  631. }
  632. void RadioSetTxConfig( RadioModems_t modem, int8_t power, uint32_t fdev,
  633. uint32_t bandwidth, uint32_t datarate,
  634. uint8_t coderate, uint16_t preambleLen,
  635. bool fixLen, bool crcOn, bool freqHopOn,
  636. uint8_t hopPeriod, bool iqInverted, uint32_t timeout ,bool LowDatarateOptimize)
  637. {
  638. switch( modem )
  639. {
  640. case MODEM_FSK:
  641. SX126x.ModulationParams.PacketType = PACKET_TYPE_GFSK;
  642. SX126x.ModulationParams.Params.Gfsk.BitRate = datarate;
  643. SX126x.ModulationParams.Params.Gfsk.ModulationShaping = MOD_SHAPING_G_BT_1;
  644. SX126x.ModulationParams.Params.Gfsk.Bandwidth = RadioGetFskBandwidthRegValue( bandwidth );
  645. SX126x.ModulationParams.Params.Gfsk.Fdev = fdev;
  646. SX126x.PacketParams.PacketType = PACKET_TYPE_GFSK;
  647. SX126x.PacketParams.Params.Gfsk.PreambleLength = ( preambleLen << 3 ); // convert byte into bit
  648. SX126x.PacketParams.Params.Gfsk.PreambleMinDetect = RADIO_PREAMBLE_DETECTOR_08_BITS;
  649. SX126x.PacketParams.Params.Gfsk.SyncWordLength = 3 << 3 ; // convert byte into bit
  650. SX126x.PacketParams.Params.Gfsk.AddrComp = RADIO_ADDRESSCOMP_FILT_OFF;
  651. SX126x.PacketParams.Params.Gfsk.HeaderType = ( fixLen == true ) ? RADIO_PACKET_FIXED_LENGTH : RADIO_PACKET_VARIABLE_LENGTH;
  652. if( crcOn == true )
  653. {
  654. SX126x.PacketParams.Params.Gfsk.CrcLength = RADIO_CRC_2_BYTES_CCIT;
  655. }
  656. else
  657. {
  658. SX126x.PacketParams.Params.Gfsk.CrcLength = RADIO_CRC_OFF;
  659. }
  660. SX126x.PacketParams.Params.Gfsk.DcFree = RADIO_DC_FREEWHITENING;
  661. RadioStandby( );
  662. RadioSetModem( ( SX126x.ModulationParams.PacketType == PACKET_TYPE_GFSK ) ? MODEM_FSK : MODEM_LORA );
  663. SX126xSetModulationParams( &SX126x.ModulationParams );
  664. SX126xSetPacketParams( &SX126x.PacketParams );
  665. SX126xSetSyncWord( ( uint8_t[] ){ 0xC1, 0x94, 0xC1, 0x00, 0x00, 0x00, 0x00, 0x00 } );
  666. SX126xSetWhiteningSeed( 0x01FF );
  667. break;
  668. case MODEM_LORA:
  669. SX126x.ModulationParams.PacketType = PACKET_TYPE_LORA;
  670. SX126x.ModulationParams.Params.LoRa.SpreadingFactor = ( RadioLoRaSpreadingFactors_t ) datarate;
  671. SX126x.ModulationParams.Params.LoRa.Bandwidth = Bandwidths[bandwidth];
  672. SX126x.ModulationParams.Params.LoRa.CodingRate= ( RadioLoRaCodingRates_t )coderate;
  673. if (LowDatarateOptimize){
  674. SX126x.ModulationParams.Params.LoRa.LowDatarateOptimize = 0x01;
  675. }else{
  676. if( ( ( bandwidth == 0 ) && ( ( datarate == 11 ) || ( datarate == 12 ) ) ) ||
  677. ( ( bandwidth == 1 ) && ( datarate == 12 ) ) )
  678. {
  679. SX126x.ModulationParams.Params.LoRa.LowDatarateOptimize = 0x01;
  680. }
  681. else
  682. {
  683. SX126x.ModulationParams.Params.LoRa.LowDatarateOptimize = 0x00;
  684. }
  685. }
  686. SX126x.PacketParams.PacketType = PACKET_TYPE_LORA;
  687. if( ( SX126x.ModulationParams.Params.LoRa.SpreadingFactor == LORA_SF5 ) ||
  688. ( SX126x.ModulationParams.Params.LoRa.SpreadingFactor == LORA_SF6 ) )
  689. {
  690. if( preambleLen < 12 )
  691. {
  692. SX126x.PacketParams.Params.LoRa.PreambleLength = 12;
  693. }
  694. else
  695. {
  696. SX126x.PacketParams.Params.LoRa.PreambleLength = preambleLen;
  697. }
  698. }
  699. else
  700. {
  701. SX126x.PacketParams.Params.LoRa.PreambleLength = preambleLen;
  702. }
  703. SX126x.PacketParams.Params.LoRa.HeaderType = ( RadioLoRaPacketLengthsMode_t )fixLen;
  704. SX126x.PacketParams.Params.LoRa.PayloadLength = MaxPayloadLength;
  705. SX126x.PacketParams.Params.LoRa.CrcMode = ( RadioLoRaCrcModes_t )crcOn;
  706. SX126x.PacketParams.Params.LoRa.InvertIQ = ( RadioLoRaIQModes_t )iqInverted;
  707. RadioStandby( );
  708. RadioSetModem( ( SX126x.ModulationParams.PacketType == PACKET_TYPE_GFSK ) ? MODEM_FSK : MODEM_LORA );
  709. SX126xSetModulationParams( &SX126x.ModulationParams );
  710. SX126xSetPacketParams( &SX126x.PacketParams );
  711. break;
  712. }
  713. SX126xSetRfTxPower( power );
  714. TxTimeout = timeout;
  715. }
  716. bool RadioCheckRfFrequency( uint32_t frequency )
  717. {
  718. return true;
  719. }
  720. uint32_t RadioTimeOnAir( RadioModems_t modem, uint8_t pktLen )
  721. {
  722. uint32_t airTime = 0;
  723. switch( modem )
  724. {
  725. case MODEM_FSK:
  726. {
  727. airTime = rint( ( 8 * ( SX126x.PacketParams.Params.Gfsk.PreambleLength +
  728. ( SX126x.PacketParams.Params.Gfsk.SyncWordLength >> 3 ) +
  729. ( ( SX126x.PacketParams.Params.Gfsk.HeaderType == RADIO_PACKET_FIXED_LENGTH ) ? 0.0 : 1.0 ) +
  730. pktLen +
  731. ( ( SX126x.PacketParams.Params.Gfsk.CrcLength == RADIO_CRC_2_BYTES ) ? 2.0 : 0 ) ) /
  732. SX126x.ModulationParams.Params.Gfsk.BitRate ) * 1e3 );
  733. }
  734. break;
  735. case MODEM_LORA:
  736. {
  737. double ts = RadioLoRaSymbTime[SX126x.ModulationParams.Params.LoRa.Bandwidth - 4][12 - SX126x.ModulationParams.Params.LoRa.SpreadingFactor];
  738. // time of preamble
  739. double tPreamble = ( SX126x.PacketParams.Params.LoRa.PreambleLength + 4.25 ) * ts;
  740. // Symbol length of payload and time
  741. double tmp = ceil( ( 8 * pktLen - 4 * SX126x.ModulationParams.Params.LoRa.SpreadingFactor +
  742. 28 + 16 * SX126x.PacketParams.Params.LoRa.CrcMode -
  743. ( ( SX126x.PacketParams.Params.LoRa.HeaderType == LORA_PACKET_FIXED_LENGTH ) ? 20 : 0 ) ) /
  744. ( double )( 4 * ( SX126x.ModulationParams.Params.LoRa.SpreadingFactor -
  745. ( ( SX126x.ModulationParams.Params.LoRa.LowDatarateOptimize > 0 ) ? 2 : 0 ) ) ) ) *
  746. ( ( SX126x.ModulationParams.Params.LoRa.CodingRate % 4 ) + 4 );
  747. double nPayload = 8 + ( ( tmp > 0 ) ? tmp : 0 );
  748. double tPayload = nPayload * ts;
  749. // Time on air
  750. double tOnAir = tPreamble + tPayload;
  751. // return milli seconds
  752. airTime = floor( tOnAir + 0.999 );
  753. }
  754. break;
  755. }
  756. return airTime;
  757. }
  758. void RadioSend( uint8_t *buffer, uint8_t size )
  759. {
  760. SX126xSetDioIrqParams( IRQ_TX_DONE | IRQ_RX_TX_TIMEOUT,
  761. IRQ_TX_DONE | IRQ_RX_TX_TIMEOUT,
  762. IRQ_RADIO_NONE,
  763. IRQ_RADIO_NONE );
  764. if( SX126xGetPacketType( ) == PACKET_TYPE_LORA )
  765. {
  766. SX126x.PacketParams.Params.LoRa.PayloadLength = size;
  767. }
  768. else
  769. {
  770. SX126x.PacketParams.Params.Gfsk.PayloadLength = size;
  771. }
  772. SX126xSetPacketParams( &SX126x.PacketParams );
  773. SX126xSendPayload( buffer, size, 0 );
  774. // TimerSetValue( &TxTimeoutTimer, TxTimeout );
  775. // TimerStart( &TxTimeoutTimer );
  776. }
  777. void RadioSleep( void )
  778. {
  779. SleepParams_t params = { 0 };
  780. params.Fields.WarmStart = 1;
  781. SX126xSetSleep( params );
  782. SX126xDelayMs( 2 );
  783. }
  784. void RadioStandby( void )
  785. {
  786. SX126xSetStandby( STDBY_RC );
  787. }
  788. void RadioRx( uint32_t timeout )
  789. {
  790. SX126xSetDioIrqParams( IRQ_RADIO_ALL, //IRQ_RX_DONE | IRQ_RX_TX_TIMEOUT,
  791. IRQ_RADIO_ALL, //IRQ_RX_DONE | IRQ_RX_TX_TIMEOUT,
  792. IRQ_RADIO_NONE,
  793. IRQ_RADIO_NONE );
  794. if( RxContinuous == true )
  795. {
  796. SX126xSetRx( 0xFFFFFF ); // Rx Continuous
  797. }
  798. else
  799. {
  800. SX126xSetRx( timeout << 6 );
  801. }
  802. }
  803. void RadioRxBoosted( uint32_t timeout )
  804. {
  805. SX126xSetDioIrqParams( IRQ_RADIO_ALL, //IRQ_RX_DONE | IRQ_RX_TX_TIMEOUT,
  806. IRQ_RADIO_ALL, //IRQ_RX_DONE | IRQ_RX_TX_TIMEOUT,
  807. IRQ_RADIO_NONE,
  808. IRQ_RADIO_NONE );
  809. if( RxContinuous == true )
  810. {
  811. SX126xSetRxBoosted( 0xFFFFFF ); // Rx Continuous
  812. }
  813. else
  814. {
  815. SX126xSetRxBoosted( timeout << 6 );
  816. }
  817. }
  818. void RadioSetRxDutyCycle( uint32_t rxTime, uint32_t sleepTime )
  819. {
  820. SX126xSetRxDutyCycle( rxTime, sleepTime );
  821. }
  822. void RadioStartCad( void )
  823. {
  824. SX126xSetCad( );
  825. }
  826. void RadioTx( uint32_t timeout )
  827. {
  828. SX126xSetTx( timeout << 6 );
  829. }
  830. void RadioSetTxContinuousWave( uint32_t freq, int8_t power, uint16_t time )
  831. {
  832. SX126xSetRfFrequency( freq );
  833. SX126xSetRfTxPower( power );
  834. SX126xSetTxContinuousWave( );
  835. // TimerSetValue( &RxTimeoutTimer, time * 1e3 );
  836. // TimerStart( &RxTimeoutTimer );
  837. }
  838. int16_t RadioRssi( RadioModems_t modem )
  839. {
  840. return SX126xGetRssiInst( );
  841. }
  842. void RadioWrite( uint16_t addr, uint8_t data )
  843. {
  844. SX126xWriteRegister( addr, data );
  845. }
  846. uint8_t RadioRead( uint16_t addr )
  847. {
  848. return SX126xReadRegister( addr );
  849. }
  850. void RadioWriteBuffer( uint16_t addr, uint8_t *buffer, uint8_t size )
  851. {
  852. SX126xWriteRegisters( addr, buffer, size );
  853. }
  854. void RadioReadBuffer( uint16_t addr, uint8_t *buffer, uint8_t size )
  855. {
  856. SX126xReadRegisters( addr, buffer, size );
  857. }
  858. void RadioWriteFifo( uint8_t *buffer, uint8_t size )
  859. {
  860. SX126xWriteBuffer( 0, buffer, size );
  861. }
  862. void RadioReadFifo( uint8_t *buffer, uint8_t size )
  863. {
  864. SX126xReadBuffer( 0, buffer, size );
  865. }
  866. void RadioSetMaxPayloadLength( RadioModems_t modem, uint8_t max )
  867. {
  868. if( modem == MODEM_LORA )
  869. {
  870. SX126x.PacketParams.Params.LoRa.PayloadLength = MaxPayloadLength = max;
  871. SX126xSetPacketParams( &SX126x.PacketParams );
  872. }
  873. else
  874. {
  875. if( SX126x.PacketParams.Params.Gfsk.HeaderType == RADIO_PACKET_VARIABLE_LENGTH )
  876. {
  877. SX126x.PacketParams.Params.Gfsk.PayloadLength = MaxPayloadLength = max;
  878. SX126xSetPacketParams( &SX126x.PacketParams );
  879. }
  880. }
  881. }
  882. void RadioSetPublicNetwork( bool enable )
  883. {
  884. RadioPublicNetwork.Current = RadioPublicNetwork.Previous = enable;
  885. RadioSetModem( MODEM_LORA );
  886. if( enable == true )
  887. {
  888. // Change LoRa modem SyncWord
  889. SX126xWriteRegister( REG_LR_SYNCWORD, ( LORA_MAC_PUBLIC_SYNCWORD >> 8 ) & 0xFF );
  890. SX126xWriteRegister( REG_LR_SYNCWORD + 1, LORA_MAC_PUBLIC_SYNCWORD & 0xFF );
  891. }
  892. else
  893. {
  894. // Change LoRa modem SyncWord
  895. SX126xWriteRegister( REG_LR_SYNCWORD, ( LORA_MAC_PRIVATE_SYNCWORD >> 8 ) & 0xFF );
  896. SX126xWriteRegister( REG_LR_SYNCWORD + 1, LORA_MAC_PRIVATE_SYNCWORD & 0xFF );
  897. }
  898. }
  899. uint32_t RadioGetWakeupTime( void )
  900. {
  901. return( RADIO_TCXO_SETUP_TIME + RADIO_WAKEUP_TIME );
  902. }
  903. void RadioOnTxTimeoutIrq( void )
  904. {
  905. if( ( RadioEvents != NULL ) && ( RadioEvents->TxTimeout != NULL ) )
  906. {
  907. RadioEvents->TxTimeout( );
  908. }
  909. }
  910. void RadioOnRxTimeoutIrq( void )
  911. {
  912. if( ( RadioEvents != NULL ) && ( RadioEvents->RxTimeout != NULL ) )
  913. {
  914. RadioEvents->RxTimeout( );
  915. }
  916. }
  917. void RadioOnDioIrq( void )
  918. {
  919. }
  920. void RadioIrqProcess( void )
  921. {
  922. if(SX126xGetIrqFired()==1)
  923. {
  924. uint16_t irqRegs = SX126xGetIrqStatus( );
  925. SX126xClearIrqStatus( IRQ_RADIO_ALL );
  926. if( ( irqRegs & IRQ_TX_DONE ) == IRQ_TX_DONE )
  927. {
  928. if( ( RadioEvents != NULL ) && ( RadioEvents->TxDone != NULL ) )
  929. {
  930. RadioEvents->TxDone( );
  931. }
  932. }
  933. if( ( irqRegs & IRQ_RX_DONE ) == IRQ_RX_DONE )
  934. {
  935. uint8_t size;
  936. SX126xGetPayload( RadioRxPayload, &size , 255 );
  937. SX126xGetPacketStatus( &RadioPktStatus );
  938. if( ( RadioEvents != NULL ) && ( RadioEvents->RxDone != NULL ) )
  939. {
  940. RadioEvents->RxDone( RadioRxPayload, size, RadioPktStatus.Params.LoRa.RssiPkt, RadioPktStatus.Params.LoRa.SnrPkt );
  941. }
  942. }
  943. if( ( irqRegs & IRQ_CRC_ERROR ) == IRQ_CRC_ERROR )
  944. {
  945. if( ( RadioEvents != NULL ) && ( RadioEvents->RxError ) )
  946. {
  947. RadioEvents->RxError( );
  948. }
  949. }
  950. if( ( irqRegs & IRQ_CAD_DONE ) == IRQ_CAD_DONE )
  951. {
  952. if( ( RadioEvents != NULL ) && ( RadioEvents->CadDone != NULL ) )
  953. {
  954. RadioEvents->CadDone( ( ( irqRegs & IRQ_CAD_ACTIVITY_DETECTED ) == IRQ_CAD_ACTIVITY_DETECTED ) );
  955. }
  956. }
  957. if( ( irqRegs & IRQ_RX_TX_TIMEOUT ) == IRQ_RX_TX_TIMEOUT )
  958. {
  959. if( SX126xGetOperatingMode( ) == MODE_TX )
  960. {
  961. if( ( RadioEvents != NULL ) && ( RadioEvents->TxTimeout != NULL ) )
  962. {
  963. RadioEvents->TxTimeout( );
  964. }
  965. }
  966. else if( SX126xGetOperatingMode( ) == MODE_RX )
  967. {
  968. if( ( RadioEvents != NULL ) && ( RadioEvents->RxTimeout != NULL ) )
  969. {
  970. RadioEvents->RxTimeout( );
  971. }
  972. }
  973. }
  974. if( ( irqRegs & IRQ_PREAMBLE_DETECTED ) == IRQ_PREAMBLE_DETECTED )
  975. {
  976. //__NOP( );
  977. }
  978. if( ( irqRegs & IRQ_SYNCWORD_VALID ) == IRQ_SYNCWORD_VALID )
  979. {
  980. //__NOP( );
  981. }
  982. if( ( irqRegs & IRQ_HEADER_VALID ) == IRQ_HEADER_VALID )
  983. {
  984. //__NOP( );
  985. }
  986. if( ( irqRegs & IRQ_HEADER_ERROR ) == IRQ_HEADER_ERROR )
  987. {
  988. if( ( RadioEvents != NULL ) && ( RadioEvents->RxTimeout != NULL ) )
  989. {
  990. RadioEvents->RxTimeout( );
  991. }
  992. }
  993. }
  994. }