diff --git a/.gitignore b/.gitignore index 54dd494f..f691a611 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,6 @@ libloragw/inc/config.h libloragw/test_loragw_* packet_forwarder/lora_pkt_fwd util_chip_id/chip_id -util_net_downlink/net_downlink \ No newline at end of file +util_net_downlink/net_downlink +util_boot/boot +util_spectral_scan/spectral_scan \ No newline at end of file diff --git a/Makefile b/Makefile index b70428d8..687c12c4 100644 --- a/Makefile +++ b/Makefile @@ -6,9 +6,9 @@ export ### general build targets -.PHONY: all clean install install_conf libtools libloragw packet_forwarder util_net_downlink util_chip_id +.PHONY: all clean install install_conf libtools libloragw packet_forwarder util_net_downlink util_chip_id util_boot util_spectral_scan -all: libtools libloragw packet_forwarder util_net_downlink util_chip_id +all: libtools libloragw packet_forwarder util_net_downlink util_chip_id util_boot util_spectral_scan libtools: $(MAKE) all -e -C $@ @@ -25,18 +25,28 @@ util_net_downlink: libtools util_chip_id: libloragw $(MAKE) all -e -C $@ +util_boot: libloragw + $(MAKE) all -e -C $@ + +util_spectral_scan: libloragw + $(MAKE) all -e -C $@ + clean: $(MAKE) clean -e -C libtools $(MAKE) clean -e -C libloragw $(MAKE) clean -e -C packet_forwarder $(MAKE) clean -e -C util_net_downlink $(MAKE) clean -e -C util_chip_id + $(MAKE) clean -e -C util_boot + $(MAKE) clean -e -C util_spectral_scan install: $(MAKE) install -e -C libloragw $(MAKE) install -e -C packet_forwarder $(MAKE) install -e -C util_net_downlink $(MAKE) install -e -C util_chip_id + $(MAKE) install -e -C util_boot + $(MAKE) install -e -C util_spectral_scan install_conf: $(MAKE) install_conf -e -C packet_forwarder diff --git a/VERSION b/VERSION index 90a27f9c..227cea21 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.0.5 +2.0.0 diff --git a/libloragw/Makefile b/libloragw/Makefile index 262167aa..881442fa 100644 --- a/libloragw/Makefile +++ b/libloragw/Makefile @@ -22,7 +22,20 @@ LIBS := -lloragw -ltinymt32 -lrt -lm ### general build targets -all: libloragw.a test_loragw_spi test_loragw_i2c test_loragw_reg test_loragw_hal_tx test_loragw_hal_rx test_loragw_cal test_loragw_capture_ram test_loragw_spi_sx1250 test_loragw_counter test_loragw_gps +all: libloragw.a \ + test_loragw_com \ + test_loragw_i2c \ + test_loragw_reg \ + test_loragw_hal_tx \ + test_loragw_hal_rx \ + test_loragw_cal_sx125x \ + test_loragw_capture_ram \ + test_loragw_com_sx1250 \ + test_loragw_com_sx1261 \ + test_loragw_counter \ + test_loragw_gps \ + test_loragw_toa \ + test_loragw_sx1261_rssi clean: rm -f libloragw.a @@ -60,17 +73,19 @@ inc/config.h: ../VERSION library.cfg @echo "Release version : $(LIBLORAGW_VERSION)" @echo " #define LIBLORAGW_VERSION "\"$(LIBLORAGW_VERSION)\""" >> $@ # Debug options - @echo " #define DEBUG_AUX $(DEBUG_AUX)" >> $@ - @echo " #define DEBUG_SPI $(DEBUG_SPI)" >> $@ - @echo " #define DEBUG_I2C $(DEBUG_I2C)" >> $@ - @echo " #define DEBUG_REG $(DEBUG_REG)" >> $@ - @echo " #define DEBUG_HAL $(DEBUG_HAL)" >> $@ - @echo " #define DEBUG_GPS $(DEBUG_GPS)" >> $@ - @echo " #define DEBUG_GPIO $(DEBUG_GPIO)" >> $@ - @echo " #define DEBUG_LBT $(DEBUG_LBT)" >> $@ - @echo " #define DEBUG_RAD $(DEBUG_RAD)" >> $@ - @echo " #define DEBUG_CAL $(DEBUG_CAL)" >> $@ + @echo " #define DEBUG_AUX $(DEBUG_AUX)" >> $@ + @echo " #define DEBUG_COM $(DEBUG_COM)" >> $@ + @echo " #define DEBUG_MCU $(DEBUG_MCU)" >> $@ + @echo " #define DEBUG_I2C $(DEBUG_I2C)" >> $@ + @echo " #define DEBUG_REG $(DEBUG_REG)" >> $@ + @echo " #define DEBUG_HAL $(DEBUG_HAL)" >> $@ + @echo " #define DEBUG_GPS $(DEBUG_GPS)" >> $@ + @echo " #define DEBUG_GPIO $(DEBUG_GPIO)" >> $@ + @echo " #define DEBUG_LBT $(DEBUG_LBT)" >> $@ + @echo " #define DEBUG_RAD $(DEBUG_RAD)" >> $@ + @echo " #define DEBUG_CAL $(DEBUG_CAL)" >> $@ @echo " #define DEBUG_SX1302 $(DEBUG_SX1302)" >> $@ + @echo " #define DEBUG_FTIME $(DEBUG_FTIME)" >> $@ # end of file @echo "#endif" >> $@ @echo "*** Configuration seems ok ***" @@ -85,12 +100,39 @@ $(OBJDIR)/%.o: src/%.c $(INCLUDES) inc/config.h | $(OBJDIR) ### static library -libloragw.a: $(OBJDIR)/loragw_spi.o $(OBJDIR)/loragw_i2c.o $(OBJDIR)/loragw_aux.o $(OBJDIR)/loragw_reg.o $(OBJDIR)/loragw_sx1250.o $(OBJDIR)/loragw_sx125x.o $(OBJDIR)/loragw_sx1302.o $(OBJDIR)/loragw_cal.o $(OBJDIR)/loragw_debug.o $(OBJDIR)/loragw_hal.o $(OBJDIR)/loragw_stts751.o $(OBJDIR)/loragw_gps.o $(OBJDIR)/loragw_sx1302_timestamp.o $(OBJDIR)/loragw_sx1302_rx.o +libloragw.a: $(OBJDIR)/loragw_spi.o \ + $(OBJDIR)/loragw_usb.o \ + $(OBJDIR)/loragw_com.o \ + $(OBJDIR)/loragw_mcu.o \ + $(OBJDIR)/loragw_i2c.o \ + $(OBJDIR)/sx125x_spi.o \ + $(OBJDIR)/sx125x_com.o \ + $(OBJDIR)/sx1250_spi.o \ + $(OBJDIR)/sx1250_usb.o \ + $(OBJDIR)/sx1250_com.o \ + $(OBJDIR)/sx1261_spi.o \ + $(OBJDIR)/sx1261_usb.o \ + $(OBJDIR)/sx1261_com.o \ + $(OBJDIR)/loragw_aux.o \ + $(OBJDIR)/loragw_reg.o \ + $(OBJDIR)/loragw_sx1250.o \ + $(OBJDIR)/loragw_sx1261.o \ + $(OBJDIR)/loragw_sx125x.o \ + $(OBJDIR)/loragw_sx1302.o \ + $(OBJDIR)/loragw_cal.o \ + $(OBJDIR)/loragw_debug.o \ + $(OBJDIR)/loragw_hal.o \ + $(OBJDIR)/loragw_lbt.o \ + $(OBJDIR)/loragw_stts751.o \ + $(OBJDIR)/loragw_gps.o \ + $(OBJDIR)/loragw_sx1302_timestamp.o \ + $(OBJDIR)/loragw_sx1302_rx.o \ + $(OBJDIR)/loragw_ad5338r.o $(AR) rcs $@ $^ ### test programs -test_loragw_spi: tst/test_loragw_spi.c libloragw.a +test_loragw_com: tst/test_loragw_com.c libloragw.a $(CC) $(CFLAGS) -L. -L../libtools $< -o $@ $(LIBS) test_loragw_i2c: tst/test_loragw_i2c.c libloragw.a @@ -108,10 +150,13 @@ test_loragw_hal_rx: tst/test_loragw_hal_rx.c libloragw.a test_loragw_capture_ram: tst/test_loragw_capture_ram.c libloragw.a $(CC) $(CFLAGS) -L. -L../libtools $< -o $@ $(LIBS) -test_loragw_cal: tst/test_loragw_cal.c libloragw.a +test_loragw_cal_sx125x: tst/test_loragw_cal_sx125x.c libloragw.a $(CC) $(CFLAGS) -L. -L../libtools $< -o $@ $(LIBS) -test_loragw_spi_sx1250: tst/test_loragw_spi_sx1250.c libloragw.a +test_loragw_com_sx1250: tst/test_loragw_com_sx1250.c libloragw.a + $(CC) $(CFLAGS) -L. -L../libtools $< -o $@ $(LIBS) + +test_loragw_com_sx1261: tst/test_loragw_com_sx1261.c libloragw.a $(CC) $(CFLAGS) -L. -L../libtools $< -o $@ $(LIBS) test_loragw_counter: tst/test_loragw_counter.c libloragw.a @@ -120,4 +165,10 @@ test_loragw_counter: tst/test_loragw_counter.c libloragw.a test_loragw_gps: tst/test_loragw_gps.c libloragw.a $(CC) $(CFLAGS) -L. -L../libtools $< -o $@ $(LIBS) +test_loragw_toa: tst/test_loragw_toa.c libloragw.a + $(CC) $(CFLAGS) -L. -L../libtools $< -o $@ $(LIBS) + +test_loragw_sx1261_rssi: tst/test_loragw_sx1261_rssi.c libloragw.a + $(CC) $(CFLAGS) -L. -L../libtools $< -o $@ $(LIBS) + ### EOF diff --git a/libloragw/inc/loragw_ad5338r.h b/libloragw/inc/loragw_ad5338r.h new file mode 100644 index 00000000..aaecda58 --- /dev/null +++ b/libloragw/inc/loragw_ad5338r.h @@ -0,0 +1,43 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2020 Semtech + +Description: + Basic driver for Analog AD5338R DAC. + +License: Revised BSD License, see LICENSE.TXT file include in the project +*/ + + +#ifndef _LORAGW_AD5338R_H +#define _LORAGW_AD5338R_H + +#include /* C99 types */ +#include /* bool type */ + +#include "config.h" /* library configuration options (dynamically generated) */ + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE MACROS ------------------------------------------------------- */ + +#define VOLTAGE2HEX_H(a) ( (a)*1024/5/4 ) +#define VOLTAGE2HEX_L(a) ( (((int)((a)*1024/5)) & 0x3) * 64 ) + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE CONSTANTS ---------------------------------------------------- */ + +#define I2C_PORT_DAC_AD5338R 0x0C + +#define AD5338R_CMD_SIZE 3 + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC FUNCTIONS ----------------------------------------------------- */ + +int ad5338r_configure(int i2c_fd, uint8_t i2c_addr); +int ad5338r_write(int i2c_fd, uint8_t i2c_addr, uint8_t buf[static AD5338R_CMD_SIZE]); + +#endif diff --git a/libloragw/inc/loragw_aux.h b/libloragw/inc/loragw_aux.h index fac21fea..d9d91064 100644 --- a/libloragw/inc/loragw_aux.h +++ b/libloragw/inc/loragw_aux.h @@ -19,11 +19,23 @@ License: Revised BSD License, see LICENSE.TXT file include in the project /* -------------------------------------------------------------------------- */ /* --- DEPENDANCIES --------------------------------------------------------- */ -#include "config.h" /* library configuration options (dynamically generated) */ +#include /* C99 types */ +#include /* bool type */ +#include /* gettimeofday, structtimeval */ + +#include "config.h" /* library configuration options (dynamically generated) */ + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC CONSTANTS ----------------------------------------------------- */ + +#define DEBUG_PERF 0 /* Debug timing performances: level [0..4] */ /* -------------------------------------------------------------------------- */ /* --- PUBLIC MACROS -------------------------------------------------------- */ +#define MIN(a,b) (((a)<(b))?(a):(b)) +#define MAX(a,b) (((a)>(b))?(a):(b)) + /** @brief Get a particular bit value from a byte @param b [in] Any byte from which we want a bit value @@ -33,6 +45,22 @@ License: Revised BSD License, see LICENSE.TXT file include in the project */ #define TAKE_N_BITS_FROM(b, p, n) (((b) >> (p)) & ((1 << (n)) - 1)) +/** +@brief Substract struct timeval values +@param a [in] struct timeval a +@param b [in] struct timeval b +@param b [out] struct timeval resulting from (a - b) +*/ +#define TIMER_SUB(a, b, result) \ + do { \ + (result)->tv_sec = (a)->tv_sec - (b)->tv_sec; \ + (result)->tv_usec = (a)->tv_usec - (b)->tv_usec; \ + if ((result)->tv_usec < 0) { \ + --(result)->tv_sec; \ + (result)->tv_usec += 1000000; \ + } \ + } while (0) + /* -------------------------------------------------------------------------- */ /* --- PUBLIC FUNCTIONS PROTOTYPES ------------------------------------------ */ @@ -42,6 +70,65 @@ License: Revised BSD License, see LICENSE.TXT file include in the project */ void wait_ms(unsigned long t); +/** +@brief Wait for a certain time (microsencond accuracy) +@param t number of microseconds to wait. +*/ +void wait_us(unsigned long t); + +/** +@brief Calculate the time on air of a LoRa packet in microseconds +@param bw packet bandwidth +@param sf packet spreading factor +@param cr packet coding rate +@param n_symbol_preamble packet preamble length (number of symbols) +@param no_header true if packet has no header +@param no_crc true if packet has no CRC +@param size packet size in bytes +@param nb_symbols pointer to return the total number of symbols in packet +@param nb_symbols_payload pointer to return the number of symbols in packet payload +@param t_symbol_us pointer to return the duration of a symbol in microseconds +@return the packet time on air in microseconds +*/ +uint32_t lora_packet_time_on_air( const uint8_t bw, + const uint8_t sf, + const uint8_t cr, + const uint16_t n_symbol_preamble, + const bool no_header, + const bool no_crc, + const uint8_t size, + double * nb_symbols, + uint32_t * nb_symbols_payload, + uint16_t * t_symbol_us); + +/** +@brief Record the current time, for measure start +@param tm Pointer to the current time value +*/ +void _meas_time_start(struct timeval *tm); + +/** +@brief Measure the ellapsed time since given time +@param debug_level debug print debug level to be used +@param start_time start time of the measure to be used +@param str string to be used for debug print +*/ +void _meas_time_stop(int debug_level, struct timeval start_time, const char *str); + +/** +@brief Get the current time for later timeout check +@param start contains the current time to be used as start time for timeout +*/ +void timeout_start(struct timeval * start); + +/** +@brief Check if the given timeout time in milliseconds has ellapsed compared to start time +@param start reference start time +@param timeout_ms the timeout duration in milliseconds +@return -1 if the timeout has exceeded, 0 otherwise +*/ +int timeout_check(struct timeval start, uint32_t timeout_ms); + #endif /* --- EOF ------------------------------------------------------------------ */ diff --git a/libloragw/inc/loragw_com.h b/libloragw/inc/loragw_com.h new file mode 100644 index 00000000..437b1269 --- /dev/null +++ b/libloragw/inc/loragw_com.h @@ -0,0 +1,123 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2020 Semtech + +Description: + Functions to abstract the communication interface used to communicate with + the concentrator. + Single-byte read/write and burst read/write. + +License: Revised BSD License, see LICENSE.TXT file include in the project +*/ + + +#ifndef _LORAGW_COM_H +#define _LORAGW_COM_H + +/* -------------------------------------------------------------------------- */ +/* --- DEPENDANCIES --------------------------------------------------------- */ + +#include /* C99 types*/ + +#include "config.h" /* library configuration options (dynamically generated) */ + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC CONSTANTS ----------------------------------------------------- */ + +#define LGW_COM_SUCCESS 0 +#define LGW_COM_ERROR -1 + +#define LGW_SPI_MUX_TARGET_SX1302 0x00 +#define LGW_SPI_MUX_TARGET_RADIOA 0x01 +#define LGW_SPI_MUX_TARGET_RADIOB 0x02 + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC TYPES --------------------------------------------------------- */ + +typedef enum com_type_e { + LGW_COM_SPI, + LGW_COM_USB, + LGW_COM_UNKNOWN +} lgw_com_type_t; + +typedef enum com_write_mode_e { + LGW_COM_WRITE_MODE_SINGLE, + LGW_COM_WRITE_MODE_BULK, + LGW_COM_WRITE_MODE_UNKNOWN +} lgw_com_write_mode_t; + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC FUNCTIONS PROTOTYPES ------------------------------------------ */ + +/** + * +*/ +int lgw_com_open(lgw_com_type_t com_type, const char *com_path); + +/** + * +*/ +int lgw_com_close(void); + +/** + * +*/ +int lgw_com_w(uint8_t spi_mux_target, uint16_t address, uint8_t data); + +/** + * +*/ +int lgw_com_r(uint8_t spi_mux_target, uint16_t address, uint8_t *data); + +/** + * +*/ +int lgw_com_rmw(uint8_t spi_mux_target, uint16_t address, uint8_t offs, uint8_t leng, uint8_t data); + +/** + * +*/ +int lgw_com_wb(uint8_t spi_mux_target, uint16_t address, const uint8_t *data, uint16_t size); + +/** + * +*/ +int lgw_com_rb(uint8_t spi_mux_target, uint16_t address, uint8_t *data, uint16_t size); + +/** + * +*/ +int lgw_com_set_write_mode(lgw_com_write_mode_t write_mode); + +/** + * +*/ +int lgw_com_flush(void); + +/** + * +*/ +uint16_t lgw_com_chunk_size(void); + +/** + * + **/ +int lgw_com_get_temperature(float * temperature); + +/** + * + **/ +void* lgw_com_target(void); + +/** + * + **/ +lgw_com_type_t lgw_com_type(void); + +#endif + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/libloragw/inc/loragw_debug.h b/libloragw/inc/loragw_debug.h index 8b2c76b1..15d91aa1 100644 --- a/libloragw/inc/loragw_debug.h +++ b/libloragw/inc/loragw_debug.h @@ -57,18 +57,6 @@ void dbg_generate_random_payload(uint32_t pkt_cnt, uint8_t * buffer_expected, ui */ int dbg_check_payload(struct lgw_conf_debug_s * context, FILE * file, uint8_t * payload_received, uint8_t size, uint8_t ref_payload_idx, uint8_t sf); -/** -@brief -@param -*/ -void dbg_init_gpio(void); - -/** -@brief -@param -*/ -void dbg_toggle_gpio(void); - #endif /* --- EOF ------------------------------------------------------------------ */ diff --git a/libloragw/inc/loragw_hal.h b/libloragw/inc/loragw_hal.h index a3d2e663..6e153a3e 100644 --- a/libloragw/inc/loragw_hal.h +++ b/libloragw/inc/loragw_hal.h @@ -22,6 +22,8 @@ License: Revised BSD License, see LICENSE.TXT file include in the project #include /* C99 types */ #include /* bool type */ +#include "loragw_com.h" + #include "config.h" /* library configuration options (dynamically generated) */ /* -------------------------------------------------------------------------- */ @@ -42,7 +44,7 @@ License: Revised BSD License, see LICENSE.TXT file include in the project /* return status code */ #define LGW_HAL_SUCCESS 0 #define LGW_HAL_ERROR -1 -#define LGW_LBT_ISSUE 1 +#define LGW_LBT_NOT_ALLOWED 1 /* radio-specific parameters */ #define LGW_XTAL_FREQU 32000000 /* frequency of the RF reference oscillator */ @@ -50,10 +52,10 @@ License: Revised BSD License, see LICENSE.TXT file include in the project #define LGW_RF_RX_BANDWIDTH {1000000, 1000000} /* bandwidth of the radios */ /* 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_REF_BW 125000 /* typical bandwidth of data channel */ #define LGW_MULTI_NB 8 /* number of LoRa 'multi SF' chains */ +#define LGW_MULTI_SF_EN 0xFF /* bitmask to enable/disable SF for multi-sf correlators (12 11 10 9 8 7 6 5) */ /* values available for the 'modulation' parameters */ /* NOTE: arbitrary values */ @@ -86,7 +88,7 @@ License: Revised BSD License, see LICENSE.TXT file include in the project /* values available for the 'coderate' parameters (LoRa only) */ /* NOTE: arbitrary values */ -#define CR_UNDEFINED 0 +#define CR_UNDEFINED 0 /* CR0 exists but is not recommended, so consider it as invalid */ #define CR_LORA_4_5 0x01 #define CR_LORA_4_6 0x02 #define CR_LORA_4_7 0x03 @@ -126,6 +128,12 @@ License: Revised BSD License, see LICENSE.TXT file include in the project /* Maximum size of Tx gain LUT */ #define TX_GAIN_LUT_SIZE_MAX 16 +/* Listen-Before-Talk */ +#define LGW_LBT_CHANNEL_NB_MAX 16 /* Maximum number of LBT channels */ + +/* Spectral Scan */ +#define LGW_SPECTRAL_SCAN_RESULT_SIZE 33 /* The number of results returned by spectral scan function, to be used for memory allocation */ + /* -------------------------------------------------------------------------- */ /* --- PUBLIC TYPES --------------------------------------------------------- */ @@ -147,10 +155,11 @@ typedef enum { @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 full_duplex; /*!> Indicates if the gateway operates in full duplex mode or not */ - char spidev_path[64];/*!> Path to access the SPI device to connect to the SX1302 */ + 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 full_duplex; /*!> Indicates if the gateway operates in full duplex mode or not */ + lgw_com_type_t com_type; /*!> The COMmunication interface (SPI/USB) to connect to the SX1302 */ + char com_path[64]; /*!> Path to access the COM device to connect to the SX1302 */ }; /** @@ -197,6 +206,14 @@ struct lgw_conf_rxif_s { uint8_t implicit_coderate; /*!> LoRa Service implicit header coding rate */ }; +/** +@struct lgw_conf_demod_s +@brief Configuration structure for LoRa/FSK demodulators +*/ +struct lgw_conf_demod_s { + uint8_t multisf_datarate; /*!> bitmask to enable spreading-factors for correlators (SF12 - SF5) */ +}; + /** @struct lgw_pkt_rx_s @brief Structure containing the metadata of a packet that was received and a pointer to the payload @@ -221,6 +238,8 @@ struct lgw_pkt_rx_s { 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 */ + bool ftime_received; /*!> a fine timestamp has been received */ + uint32_t ftime; /*!> packet fine timestamp (nanoseconds since last PPS) */ }; /** @@ -288,13 +307,62 @@ struct lgw_conf_debug_s { }; /** -@struct lgw_conf_debug_s -@brief Configuration structure for debug +@enum lgw_ftime_mode_t +@brief Fine timestamping modes +*/ +typedef enum { + LGW_FTIME_MODE_HIGH_CAPACITY, /*!> fine timestamps for SF5 -> SF10 */ + LGW_FTIME_MODE_ALL_SF /*!> fine timestamps for SF5 -> SF12 */ +} lgw_ftime_mode_t; + +/** +@struct lgw_conf_ftime_s +@brief Configuration structure for fine timestamping +*/ +struct lgw_conf_ftime_s { + bool enable; /*!> Enable / Disable fine timestamping */ + lgw_ftime_mode_t mode; /*!> Fine timestamping mode */ +}; + +/** +@enum lgw_lbt_scan_time_t +@brief Radio types that can be found on the LoRa Gateway +*/ +typedef enum { + LGW_LBT_SCAN_TIME_128_US = 128, + LGW_LBT_SCAN_TIME_5000_US = 5000, +} lgw_lbt_scan_time_t; + +/** +@brief Structure containing a Listen-Before-Talk channel configuration +*/ +struct lgw_conf_chan_lbt_s{ + uint32_t freq_hz; /*!> LBT channel frequency */ + uint8_t bandwidth; /*!> LBT channel bandwidth */ + lgw_lbt_scan_time_t scan_time_us; /*!> LBT channel carrier sense time */ + uint16_t transmit_time_ms; /*!> LBT channel transmission duration when allowed */ +}; + +/** +@struct lgw_conf_lbt_s +@brief Configuration structure for listen-before-talk +*/ +struct lgw_conf_lbt_s { + bool enable; /*!> enable or disable LBT */ + int8_t rssi_target; /*!> RSSI threshold to detect if channel is busy or not (dBm) */ + uint8_t nb_channel; /*!> number of LBT channels */ + struct lgw_conf_chan_lbt_s channels[LGW_LBT_CHANNEL_NB_MAX]; /*!> LBT channels configuration */ +}; + +/** +@struct lgw_conf_sx1261_s +@brief Configuration structure for additional SX1261 radio used for LBT and Spectral Scan */ -struct lgw_conf_timestamp_s { - bool enable_precision_ts; - uint8_t max_ts_metrics; - uint8_t nb_symbols; +struct lgw_conf_sx1261_s { + bool enable; /*!> enable or disable SX1261 radio */ + char spi_path[64]; /*!> Path to access the SPI device to connect to the SX1261 (not used for USB com type) */ + int8_t rssi_offset; /*!> value to be applied to the sx1261 RSSI value (dBm) */ + struct lgw_conf_lbt_s lbt_conf; /*!> listen-before-talk configuration */ }; /** @@ -308,16 +376,30 @@ typedef struct lgw_context_s { /* RX context */ struct lgw_conf_rxrf_s rf_chain_cfg[LGW_RF_CHAIN_NB]; struct lgw_conf_rxif_s if_chain_cfg[LGW_IF_CHAIN_NB]; + struct lgw_conf_demod_s demod_cfg; struct lgw_conf_rxif_s lora_service_cfg; /* LoRa service channel config parameters */ struct lgw_conf_rxif_s fsk_cfg; /* FSK channel config parameters */ /* TX context */ struct lgw_tx_gain_lut_s tx_gain_lut[LGW_RF_CHAIN_NB]; /* Misc */ - struct lgw_conf_timestamp_s timestamp_cfg; + struct lgw_conf_ftime_s ftime_cfg; + struct lgw_conf_sx1261_s sx1261_cfg; /* Debug */ struct lgw_conf_debug_s debug_cfg; } lgw_context_t; +/** +@struct lgw_spectral_scan_status_t +@brief Spectral Scan status +*/ +typedef enum lgw_spectral_scan_status_e { + LGW_SPECTRAL_SCAN_STATUS_NONE, + LGW_SPECTRAL_SCAN_STATUS_ON_GOING, + LGW_SPECTRAL_SCAN_STATUS_ABORTED, + LGW_SPECTRAL_SCAN_STATUS_COMPLETED, + LGW_SPECTRAL_SCAN_STATUS_UNKNOWN +} lgw_spectral_scan_status_t; + /* -------------------------------------------------------------------------- */ /* --- PUBLIC FUNCTIONS PROTOTYPES ------------------------------------------ */ @@ -344,23 +426,37 @@ int lgw_rxrf_setconf(uint8_t rf_chain, struct lgw_conf_rxrf_s * conf); */ int lgw_rxif_setconf(uint8_t if_chain, struct lgw_conf_rxif_s * conf); +/** +@brief Configure LoRa/FSK demodulators +@param conf structure containing the configuration parameters +@return LGW_HAL_ERROR id the operation failed, LGW_HAL_SUCCESS else +*/ +int lgw_demod_setconf(struct lgw_conf_demod_s * conf); + /** @brief Configure the Tx gain LUT -@param pointer to structure defining the LUT +@param conf pointer to structure defining the LUT @return LGW_HAL_ERROR id the operation failed, LGW_HAL_SUCCESS else */ int lgw_txgain_setconf(uint8_t rf_chain, struct lgw_tx_gain_lut_s * conf); /** -@brief Configure the precision timestamp +@brief Configure the fine timestamping +@param conf pointer to structure defining the config to be applied +@return LGW_HAL_ERROR id the operation failed, LGW_HAL_SUCCESS else +*/ +int lgw_ftime_setconf(struct lgw_conf_ftime_s * conf); + +/* +@brief Configure the SX1261 radio for LBT/Spectral Scan @param pointer to structure defining the config to be applied @return LGW_HAL_ERROR id the operation failed, LGW_HAL_SUCCESS else */ -int lgw_timestamp_setconf(struct lgw_conf_timestamp_s * conf); +int lgw_sx1261_setconf(struct lgw_conf_sx1261_s * conf); /** @brief Configure the debug context -@param pointer to structure defining the config to be applied +@param conf pointer to structure defining the config to be applied @return LGW_HAL_ERROR id the operation failed, LGW_HAL_SUCCESS else */ int lgw_debug_setconf(struct lgw_conf_debug_s * conf); @@ -464,7 +560,36 @@ const char* lgw_version_info(void); @param packet is a pointer to the packet structure @return the packet time on air in milliseconds */ -uint32_t lgw_time_on_air(struct lgw_pkt_tx_s * packet); +uint32_t lgw_time_on_air(const struct lgw_pkt_tx_s * packet); + +/** +@brief Start scaning the channel centered on the given frequency +@param freq_hz channel center frequency +@param nb_scan number of measures to be done for the scan +@return LGW_HAL_ERROR id the operation failed, LGW_HAL_SUCCESS else +*/ +int lgw_spectral_scan_start(uint32_t freq_hz, uint16_t nb_scan); + +/** +@brief Get the current scan status +@param status a pointer to the returned status +@return LGW_HAL_ERROR id the operation failed, LGW_HAL_SUCCESS else +*/ +int lgw_spectral_scan_get_status(lgw_spectral_scan_status_t * status); + +/** +@brief Get the channel scan results +@param levels an array containing the power levels for which the scan results are given +@param values ar array containing the results of the scan for each power levels +@return LGW_HAL_ERROR id the operation failed, LGW_HAL_SUCCESS else +*/ +int lgw_spectral_scan_get_results(int16_t levels_dbm[static LGW_SPECTRAL_SCAN_RESULT_SIZE], uint16_t results[static LGW_SPECTRAL_SCAN_RESULT_SIZE]); + +/** +@brief Abort the current scan +@return LGW_HAL_ERROR id the operation failed, LGW_HAL_SUCCESS else +*/ +int lgw_spectral_scan_abort(); #endif diff --git a/libloragw/inc/loragw_i2c.h b/libloragw/inc/loragw_i2c.h index da2539b9..a35b772d 100644 --- a/libloragw/inc/loragw_i2c.h +++ b/libloragw/inc/loragw_i2c.h @@ -70,6 +70,16 @@ int i2c_linuxdev_read(int i2c_fd, uint8_t device_addr, uint8_t reg_addr, uint8_t */ int i2c_linuxdev_write(int i2c_fd, uint8_t device_addr, uint8_t reg_addr, uint8_t data); +/** +@brief Write a raw buffer to an I2C port +@param i2c_fd I2C port file descriptor index +@param device_addr I2C device address +@param buffer Buffer to be written to the device +@param size Size of the buffer to be written +@return 0 if I2C data write is successful, -1 else +*/ +int i2c_linuxdev_write_buffer(int i2c_fd, uint8_t device_addr, uint8_t *buffer, uint8_t size); + #endif /* --- EOF ------------------------------------------------------------------ */ diff --git a/libloragw/inc/loragw_lbt.h b/libloragw/inc/loragw_lbt.h new file mode 100644 index 00000000..df67db41 --- /dev/null +++ b/libloragw/inc/loragw_lbt.h @@ -0,0 +1,65 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2020 Semtech + +Description: + LoRa concentrator Listen-Before-Talk functions + +License: Revised BSD License, see LICENSE.TXT file include in the project +*/ + + +#ifndef _LORAGW_LBT_H +#define _LORAGW_LBT_H + +/* -------------------------------------------------------------------------- */ +/* --- DEPENDANCIES --------------------------------------------------------- */ + +#include /* C99 types */ +#include /* bool type */ + +#include "loragw_hal.h" + +#include "config.h" /* library configuration options (dynamically generated) */ + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC MACROS -------------------------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC CONSTANTS ----------------------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC TYPES --------------------------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC FUNCTIONS PROTOTYPES ------------------------------------------ */ + +/** +@brief Configure the SX1261 and start LBT channel scanning +@param sx1261_context the sx1261 radio parameters to take into account for scanning +@param pkt description of the packet to be transmitted +@return 0 for success, -1 for failure +*/ +int lgw_lbt_start(const struct lgw_conf_sx1261_s * sx1261_context, const struct lgw_pkt_tx_s * pkt); + +/** +@brief Stop LBT scanning +@return 0 for success, -1 for failure +*/ +int lgw_lbt_stop(void); + +/** +@brief Check if packet was allowed to be transmitted or not +@param rf_chain the TX path on which TX was requested +@param tx_ok pointer to return if the packet was allowed to be transmitted or not. +@return 0 for success, -1 for failure +*/ +int lgw_lbt_tx_status(uint8_t rf_chain, bool * tx_ok); + +#endif + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/libloragw/inc/loragw_mcu.h b/libloragw/inc/loragw_mcu.h new file mode 100644 index 00000000..b2388942 --- /dev/null +++ b/libloragw/inc/loragw_mcu.h @@ -0,0 +1,197 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2020 Semtech + +Description: + Host specific functions to address the LoRa concentrator MCU for USB + interface. + Single-byte read/write and burst read/write. + +License: Revised BSD License, see LICENSE.TXT file include in the project +*/ + + +#ifndef _LORAGW_MCU_H +#define _LORAGW_MCU_H + +/* -------------------------------------------------------------------------- */ +/* --- DEPENDANCIES --------------------------------------------------------- */ + +#include /* C99 types*/ + +#include "config.h" /* library configuration options (dynamically generated) */ + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC CONSTANTS ----------------------------------------------------- */ + +static const char mcu_version_string[] = "00.02.06"; + +#define MAX_SIZE_COMMAND ( 4200 ) +#define MAX_SPI_COMMAND ( MAX_SIZE_COMMAND - CMD_OFFSET__DATA - 1 ) + +#define LGW_USB_BURST_CHUNK ( 4096 ) + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC TYPES --------------------------------------------------------- */ + +typedef enum order_id_e +{ + ORDER_ID__REQ_PING = 0x00, + ORDER_ID__REQ_GET_STATUS = 0x01, + ORDER_ID__REQ_BOOTLOADER_MODE = 0x02, + ORDER_ID__REQ_RESET = 0x03, + ORDER_ID__REQ_WRITE_GPIO = 0x04, + ORDER_ID__REQ_SPI = 0x05, /* deprecated */ + ORDER_ID__REQ_MULTIPLE_SPI = 0x06, + + ORDER_ID__ACK_PING = 0x40, + ORDER_ID__ACK_GET_STATUS = 0x41, + ORDER_ID__ACK_BOOTLOADER_MODE = 0x42, + ORDER_ID__ACK_RESET = 0x43, + ORDER_ID__ACK_WRITE_GPIO = 0x44, + ORDER_ID__ACK_SPI = 0x45, /* deprecated */ + ORDER_ID__ACK_MULTIPLE_SPI = 0x46, + + ORDER_ID__UNKNOW_CMD = 0xFF +} order_id_t; + +typedef enum +{ + CMD_OFFSET__ID, + CMD_OFFSET__SIZE_MSB, + CMD_OFFSET__SIZE_LSB, + CMD_OFFSET__CMD, + CMD_OFFSET__DATA +} e_cmd_order_offset; + +typedef enum +{ + REQ_RESET__TYPE, + REQ_RESET_SIZE +} e_cmd_offset_req_reset; + +typedef enum +{ + REQ_WRITE_GPIO__PORT, + REQ_WRITE_GPIO__PIN, + REQ_WRITE_GPIO__STATE, + REQ_WRITE_GPIO_SIZE +} e_cmd_offset_req_write_gpio; + +typedef enum +{ + ACK_PING__UNIQUE_ID_0, ACK_PING__UNIQUE_ID_1, ACK_PING__UNIQUE_ID_2, ACK_PING__UNIQUE_ID_3, + ACK_PING__UNIQUE_ID_4, ACK_PING__UNIQUE_ID_5, ACK_PING__UNIQUE_ID_6, ACK_PING__UNIQUE_ID_7, + ACK_PING__UNIQUE_ID_8, ACK_PING__UNIQUE_ID_9, ACK_PING__UNIQUE_ID_10, ACK_PING__UNIQUE_ID_11, + ACK_PING__VERSION_0, ACK_PING__VERSION_1, ACK_PING__VERSION_2, ACK_PING__VERSION_3, ACK_PING__VERSION_4, + ACK_PING__VERSION_5, ACK_PING__VERSION_6, ACK_PING__VERSION_7, ACK_PING__VERSION_8, + ACK_PING_SIZE, +} e_cmd_offset_ack_ping; + +typedef enum +{ + ACK_GET_STATUS__SYSTEM_TIME_31_24, ACK_GET_STATUS__SYSTEM_TIME_23_16, ACK_GET_STATUS__SYSTEM_TIME_15_8, ACK_GET_STATUS__SYSTEM_TIME_7_0, + ACK_GET_STATUS__TEMPERATURE_15_8, ACK_GET_STATUS__TEMPERATURE_7_0, + ACK_GET_STATUS_SIZE +} e_cmd_offset_ack_get_status; + +typedef enum +{ + ACK_GPIO_WRITE__STATUS, + ACK_GPIO_WRITE_SIZE +} e_cmd_offset_ack_gpio_write; + +typedef enum +{ + ACK_RESET__STATUS, + ACK_RESET_SIZE +} e_cmd_offset_ack_reset; + +typedef enum +{ + MCU_SPI_TARGET_SX1302, /* SX1302 + SX1250 */ + MCU_SPI_TARGET_SX1261 /* LBT/Spectral Scan additional radio */ +} e_cmd_spi_target; + +typedef enum +{ + MCU_SPI_REQ_TYPE_READ_WRITE = 0x01, /* Read/Write SPI request */ + MCU_SPI_REQ_TYPE_READ_MODIFY_WRITE = 0x02 /* Read-Modify-Write SPI request */ +} e_cmd_spi_req_type; + +typedef enum +{ + RESET_TYPE__GTW +} e_reset_type; + +typedef enum +{ + SPI_STATUS_OK, + SPI_STATUS_FAIL, + SPI_STATUS_WRONG_PARAM, + SPI_STATUS_TIMEOUT +} e_spi_status; + +typedef struct { + uint32_t unique_id_high; + uint32_t unique_id_mid; + uint32_t unique_id_low; + char version[10]; /* format is V00.00.00\0 */ +} s_ping_info; + +typedef struct { + uint32_t system_time_ms; + float temperature; +} s_status; + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC FUNCTIONS PROTOTYPES ------------------------------------------ */ + +/** + * +*/ +int mcu_ping(int fd, s_ping_info * info); + +/** + * +*/ +int mcu_boot(int fd); + +/** + * +*/ +int mcu_get_status(int fd, s_status * status); + +/** + * +*/ +int mcu_gpio_write(int fd, uint8_t gpio_port, uint8_t gpio_id, uint8_t gpio_value); + +/** +@brief Send a SX1302 read/write SPI request to the MCU +@param fd File descriptor of the device used to access the MCU +@param in_out_buf The buffer containing the multiple requests to be sent to the +SX1302 with the SPI header (r/w, target mux). This buffer will also contain the +SPI answer when the function exits. +@param buf_size The size of the given input/output buffer +@return 0 for SUCCESS, -1 for failure +*/ +int mcu_spi_write(int fd, uint8_t * in_out_buf, size_t buf_size); + +/** + * +*/ +int mcu_spi_store(uint8_t * in_out_buf, size_t buf_size); + +/** + * +*/ +int mcu_spi_flush(int fd); + +#endif + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/libloragw/inc/loragw_reg.h b/libloragw/inc/loragw_reg.h index 6f3b5198..bdac677e 100644 --- a/libloragw/inc/loragw_reg.h +++ b/libloragw/inc/loragw_reg.h @@ -27,6 +27,8 @@ License: Revised BSD License, see LICENSE.TXT file include in the project #include "config.h" /* library configuration options (dynamically generated) */ +#include "loragw_com.h" + /* -------------------------------------------------------------------------- */ /* --- INTERNAL SHARED TYPES ------------------------------------------------ */ @@ -44,14 +46,12 @@ struct lgw_reg_s { /* -------------------------------------------------------------------------- */ /* --- INTERNAL SHARED FUNCTIONS -------------------------------------------- */ -int reg_w_align32(void *spi_target, 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_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_WARNING -2 #define SX1302_REG_COMMON_PAGE_PAGE 0 #define SX1302_REG_COMMON_CTRL0_CLK32_RIF_CTRL 1 @@ -1432,14 +1432,15 @@ int reg_r_align32(void *spi_target, uint8_t spi_mux_target, struct lgw_reg_s r, /* --- PUBLIC FUNCTIONS PROTOTYPES ------------------------------------------ */ /** -@brief Connect LoRa concentrator by opening SPI link -@param spidev_path path to the SPI device to be used to connect to the SX1302 +@brief Connect LoRa concentrator by opening COM link +@param com_type type of COM interface to be used (SPI or USB) +@param com_path path to the COM device to be used to connect to the SX1302 @return status of register operation (LGW_REG_SUCCESS/LGW_REG_ERROR) */ -int lgw_connect(const char * spidev_path); +int lgw_connect(const lgw_com_type_t com_type, const char * com_path); /** -@brief Disconnect LoRa concentrator by closing SPI link +@brief Disconnect LoRa concentrator by closing COM link @return status of register operation (LGW_REG_SUCCESS/LGW_REG_ERROR) */ int lgw_disconnect(void); @@ -1472,19 +1473,28 @@ int lgw_reg_wb(uint16_t register_id, uint8_t *data, uint16_t size); /** @brief LoRa concentrator register burst read @param register_id register number in the data structure describing registers -@param data pointer to byte array that will be written from the LoRa concentrator +@param data pointer to byte array to store the data read from the LoRa concentrator @param size size of the transfer, in byte(s) @return status of register operation (LGW_REG_SUCCESS/LGW_REG_ERROR) */ int lgw_reg_rb(uint16_t register_id, uint8_t *data, uint16_t size); /** -TODO +@brief LoRa concentrator memory burst write +@param mem_addr the address of the memory section to write to +@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_REG_SUCCESS/LGW_REG_ERROR) */ int lgw_mem_wb(uint16_t mem_addr, const uint8_t *data, uint16_t size); /** -TODO +@brief LoRa concentrator memory burst read +@param mem_addr the address of the memory section to read from +@param data pointer to byte array to store the data read from the LoRa concentrator +@param size size of the transfer, in byte(s) +@param fifo_mode the type of memory to read from +@return status of register operation (LGW_REG_SUCCESS/LGW_REG_ERROR) */ int lgw_mem_rb(uint16_t mem_addr, uint8_t *data, uint16_t size, bool fifo_mode); diff --git a/libloragw/inc/loragw_spi.h b/libloragw/inc/loragw_spi.h index c3f11ec5..88e45302 100644 --- a/libloragw/inc/loragw_spi.h +++ b/libloragw/inc/loragw_spi.h @@ -31,25 +31,20 @@ License: Revised BSD License, see LICENSE.TXT file include in the project #define LGW_SPI_SUCCESS 0 #define LGW_SPI_ERROR -1 -#define LGW_BURST_CHUNK 1024 #define SPI_SPEED 2000000 -#define LGW_SPI_MUX_TARGET_SX1302 0x00 -#define LGW_SPI_MUX_TARGET_RADIOA 0x01 -#define LGW_SPI_MUX_TARGET_RADIOB 0x02 - /* -------------------------------------------------------------------------- */ /* --- PUBLIC FUNCTIONS PROTOTYPES ------------------------------------------ */ /** @brief LoRa concentrator SPI setup (configure I/O and peripherals) -@param spidev_path path to the SPI device to be used to connect to the SX1302 +@param com_path path to the SPI device to be used to connect to the SX1302 @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_spi_open(const char * spidev_path, void **spi_target_ptr); +int lgw_spi_open(const char * com_path, void **com_target_ptr); /** @brief LoRa concentrator SPI close @@ -57,7 +52,7 @@ int lgw_spi_open(const char * spidev_path, void **spi_target_ptr); @return status of register operation (LGW_SPI_SUCCESS/LGW_SPI_ERROR) */ -int lgw_spi_close(void *spi_target); +int lgw_spi_close(void *com_target); /** @brief LoRa concentrator SPI single-byte write @@ -66,7 +61,7 @@ int lgw_spi_close(void *spi_target); @param data data byte to write @return status of register operation (LGW_SPI_SUCCESS/LGW_SPI_ERROR) */ -int lgw_spi_w(void *spi_target, uint8_t spi_mux_target, uint16_t address, uint8_t data); +int lgw_spi_w(void *com_target, uint8_t spi_mux_target, uint16_t address, uint8_t data); /** @brief LoRa concentrator SPI single-byte read @@ -75,7 +70,18 @@ int lgw_spi_w(void *spi_target, uint8_t spi_mux_target, uint16_t address, uint8_ @param data data byte to write @return status of register operation (LGW_SPI_SUCCESS/LGW_SPI_ERROR) */ -int lgw_spi_r(void *spi_target, uint8_t spi_mux_target, uint16_t address, uint8_t *data); +int lgw_spi_r(void *com_target, uint8_t spi_mux_target, uint16_t address, uint8_t *data); + +/** +@brief LoRa concentrator SPI single-byte read-modify-write +@param spi_target generic pointer to SPI target (implementation dependant) +@param address 7-bit register address +@param offs start offset of the bits to be modified +@param leng number of bits to be modified +@param data value to be written in the selected bits +@return status of register operation (LGW_SPI_SUCCESS/LGW_SPI_ERROR) +*/ +int lgw_spi_rmw(void *com_target, uint8_t spi_mux_target, uint16_t address, uint8_t offs, uint8_t leng, uint8_t data); /** @brief LoRa concentrator SPI burst (multiple-byte) write @@ -85,7 +91,7 @@ int lgw_spi_r(void *spi_target, uint8_t spi_mux_target, uint16_t address, uint8_ @param size size of the transfer, in byte(s) @return status of register operation (LGW_SPI_SUCCESS/LGW_SPI_ERROR) */ -int lgw_spi_wb(void *spi_target, uint8_t spi_mux_target, uint16_t address, const uint8_t *data, uint16_t size); +int lgw_spi_wb(void *com_target, uint8_t spi_mux_target, uint16_t address, const uint8_t *data, uint16_t size); /** @brief LoRa concentrator SPI burst (multiple-byte) read @@ -95,7 +101,12 @@ int lgw_spi_wb(void *spi_target, uint8_t spi_mux_target, uint16_t address, const @param size size of the transfer, in byte(s) @return status of register operation (LGW_SPI_SUCCESS/LGW_SPI_ERROR) */ -int lgw_spi_rb(void *spi_target, uint8_t spi_mux_target, uint16_t address, uint8_t *data, uint16_t size); +int lgw_spi_rb(void *com_target, uint8_t spi_mux_target, uint16_t address, uint8_t *data, uint16_t size); + +/** + * + **/ +uint16_t lgw_spi_chunk_size(void); #endif diff --git a/libloragw/inc/loragw_stts751.h b/libloragw/inc/loragw_stts751.h index b1a898a2..d7a2dcf2 100644 --- a/libloragw/inc/loragw_stts751.h +++ b/libloragw/inc/loragw_stts751.h @@ -33,23 +33,30 @@ License: Revised BSD License, see LICENSE.TXT file include in the project /* -------------------------------------------------------------------------- */ /* --- PUBLIC CONSTANTS ----------------------------------------------------- */ -#define I2C_PORT_TEMP_SENSOR_0 0x39 /* STTS751-0DP3F */ -#define I2C_PORT_TEMP_SENSOR_1 0x3B /* STTS751-1DP3F */ +/* + 0x39: STTS751-0DP3F + 0x3B: STTS751-1DP3F + 0x38: STTS751-0DP3F on full duplex CN490 ref design + */ +static const uint8_t I2C_PORT_TEMP_SENSOR[] = {0x39, 0x3B, 0x38}; /* -------------------------------------------------------------------------- */ /* --- PUBLIC FUNCTIONS ----------------------------------------------------- */ /** -@brief TODO -@param TODO -@return TODO +@brief Configure the temperature sensor (ST TS751) +@param i2c_fd file descriptor to access the sensor through I2C +@param i2c_addr the I2C device address of the sensor +@return LGW_I2C_ERROR if fails, LGW_I2C_SUCCESS otherwise */ int stts751_configure(int i2c_fd, uint8_t i2c_addr); /** -@brief TODO -@param TODO -@return TODO +@brief Get the temperature from the sensor +@param i2c_fd file descriptor to access the sensor through I2C +@param i2c_addr the I2C device address of the sensor +@param temperature pointer to store the temerature read from sensor +@return LGW_I2C_ERROR if fails, LGW_I2C_SUCCESS otherwise */ int stts751_get_temperature(int i2c_fd, uint8_t i2c_addr, float * temperature); diff --git a/libloragw/inc/loragw_sx1250.h b/libloragw/inc/loragw_sx1250.h index eb3db90d..0e710aee 100644 --- a/libloragw/inc/loragw_sx1250.h +++ b/libloragw/inc/loragw_sx1250.h @@ -20,83 +20,30 @@ License: Revised BSD License, see LICENSE.TXT file include in the project /* --- DEPENDANCIES --------------------------------------------------------- */ #include /* C99 types*/ +#include /* bool type */ + +#include "sx1250_defs.h" #include "config.h" /* library configuration options (dynamically generated) */ /* -------------------------------------------------------------------------- */ /* --- PUBLIC MACROS -------------------------------------------------------- */ -#define SX1250_FREQ_TO_REG(f) (uint32_t)((uint64_t)f * (1 << 25) / 32000000U) - /* -------------------------------------------------------------------------- */ /* --- PUBLIC CONSTANTS ----------------------------------------------------- */ /* -------------------------------------------------------------------------- */ /* --- PUBLIC TYPES --------------------------------------------------------- */ -typedef enum { - CALIBRATE = 0x89, - CALIBRATE_IMAGE = 0x98, - CLR_IRQ_STATUS = 0x02, - STOP_TIMER_ON_PREAMBLE = 0x9F, - SET_RFSWITCHMODE = 0x9D, - GET_IRQ_STATUS = 0x12, - GET_RX_BUFFER_STATUS = 0x13, - GET_PACKET_STATUS = 0x14, - READ_BUFFER = 0x1E, - READ_REGISTER = 0x1D, - SET_DIO_IRQ_PARAMS = 0x08, - SET_MODULATION_PARAMS = 0x8B, - SET_PA_CONFIG = 0x95, - SET_PACKET_PARAMS = 0x8C, - SET_PACKET_TYPE = 0x8A, - SET_RF_FREQUENCY = 0x86, - SET_BUFFER_BASE_ADDRESS = 0x8F, - SET_SLEEP = 0x84, - SET_STANDBY = 0x80, - SET_RX = 0x82, - SET_TX = 0x83, - SET_TX_PARAMS = 0x8E, - WRITE_BUFFER = 0x0E, - WRITE_REGISTER = 0x0D, - SET_TXCONTINUOUSWAVE = 0xD1, - SET_TXCONTINUOUSPREAMBLE= 0xD2, - GET_STATUS = 0xC0, - SET_REGULATORMODE = 0x96, - SET_FS = 0xC1, - GET_DEVICE_ERRORS = 0x17 -} sx1250_op_code_t; - -typedef enum { - STDBY_RC = 0x00, - STDBY_XOSC = 0x01 -} sx1250_standby_modes_t; - -typedef enum { - PACKET_TYPE_GFSK = 0x00, - PACKET_TYPE_LORA = 0x01 -} sx1250_packet_type_t; - -typedef enum { - SET_RAMP_10U = 0x00, - SET_RAMP_20U = 0x01, - SET_RAMP_40U = 0x02, - SET_RAMP_80U = 0x03, - SET_RAMP_200U = 0x04, - SET_RAMP_800U = 0x05, - SET_RAMP_1700U = 0x06, - SET_RAMP_3400U = 0x07 -} sx1250_ramp_time_t; - /* -------------------------------------------------------------------------- */ /* --- PUBLIC FUNCTIONS PROTOTYPES ------------------------------------------ */ -int sx1250_write_command(uint8_t rf_chain, sx1250_op_code_t op_code, uint8_t *data, uint16_t size); -int sx1250_read_command(uint8_t rf_chain, sx1250_op_code_t op_code, uint8_t *data, uint16_t size); - int sx1250_calibrate(uint8_t rf_chain, uint32_t freq_hz); int sx1250_setup(uint8_t rf_chain, uint32_t freq_hz, bool single_input_mode); +int sx1250_reg_w(sx1250_op_code_t op_code, uint8_t *data, uint16_t size, uint8_t rf_chain); +int sx1250_reg_r(sx1250_op_code_t op_code, uint8_t *data, uint16_t size, uint8_t rf_chain); + #endif /* --- EOF ------------------------------------------------------------------ */ diff --git a/libloragw/inc/loragw_sx125x.h b/libloragw/inc/loragw_sx125x.h index f67fb208..430bd6cd 100644 --- a/libloragw/inc/loragw_sx125x.h +++ b/libloragw/inc/loragw_sx125x.h @@ -141,8 +141,8 @@ F_register(24bit) = F_rf (Hz) / F_step(Hz) int sx125x_setup(uint8_t rf_chain, uint8_t rf_clkout, bool rf_enable, uint8_t rf_radio_type, uint32_t freq_hz); -int lgw_sx125x_reg_w(radio_reg_t idx, uint8_t data, uint8_t rf_chain); -int lgw_sx125x_reg_r(radio_reg_t idx, uint8_t *data, uint8_t rf_chain); +int sx125x_reg_w(radio_reg_t idx, uint8_t data, uint8_t rf_chain); +int sx125x_reg_r(radio_reg_t idx, uint8_t *data, uint8_t rf_chain); #endif /* --- EOF ------------------------------------------------------------------ */ diff --git a/libloragw/inc/loragw_sx1261.h b/libloragw/inc/loragw_sx1261.h new file mode 100644 index 00000000..5204e46b --- /dev/null +++ b/libloragw/inc/loragw_sx1261.h @@ -0,0 +1,66 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2019 Semtech + +Description: + Functions used to handle LoRa concentrator SX1261 radio used to handle LBT + and Spectral Scan. + +License: Revised BSD License, see LICENSE.TXT file include in the project +*/ + + +#ifndef _LORAGW_SX1261_H +#define _LORAGW_SX1261_H + +/* -------------------------------------------------------------------------- */ +/* --- DEPENDANCIES --------------------------------------------------------- */ + +#include /* C99 types*/ +#include /* bool type */ + +#include "loragw_hal.h" +#include "sx1261_defs.h" + +#include "config.h" /* library configuration options (dynamically generated) */ + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC MACROS -------------------------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC CONSTANTS ----------------------------------------------------- */ + +static const char sx1261_pram_version_string[] = "2D06"; + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC TYPES --------------------------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC FUNCTIONS PROTOTYPES ------------------------------------------ */ + +int sx1261_connect(lgw_com_type_t com_type, const char *com_path); +int sx1261_disconnect(void); + +int sx1261_reg_w(sx1261_op_code_t op_code, uint8_t *data, uint16_t size); +int sx1261_reg_r(sx1261_op_code_t op_code, uint8_t *data, uint16_t size); + +int sx1261_load_pram(void); +int sx1261_calibrate(uint32_t freq_hz); +int sx1261_setup(void); +int sx1261_set_rx_params(uint32_t freq_hz, uint8_t bandwidth); + +int sx1261_lbt_start(lgw_lbt_scan_time_t scan_time_us, int8_t threshold_dbm); +int sx1261_lbt_stop(void); + +int sx1261_spectral_scan_start(uint16_t nb_scan); +int sx1261_spectral_scan_status(lgw_spectral_scan_status_t * status); +int sx1261_spectral_scan_get_results(int8_t rssi_offset, int16_t * levels_dbm, uint16_t * results); +int sx1261_spectral_scan_abort(void); + +#endif + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/libloragw/inc/loragw_sx1302.h b/libloragw/inc/loragw_sx1302.h index f0a0a60a..9751621d 100644 --- a/libloragw/inc/loragw_sx1302.h +++ b/libloragw/inc/loragw_sx1302.h @@ -46,48 +46,56 @@ License: Revised BSD License, see LICENSE.TXT file include in the project /* --- PUBLIC TYPES --------------------------------------------------------- */ /** -@struct sx1302_if_cfg_t -@brief TODO +@enum sx1302_model_id_t +@brief */ -typedef struct { - bool if_enable; - bool if_rf_chain; /* for each IF, 0 -> radio A, 1 -> radio B */ - int32_t if_freq; /* relative to radio frequency, +/- in Hz */ -} sx1302_if_cfg_t; +typedef enum { + CHIP_MODEL_ID_SX1302 = 0x02, /* SX1302 can be 0x00 or 0x02 */ + CHIP_MODEL_ID_SX1303 = 0x03, + CHIP_MODEL_ID_UNKNOWN +} sx1302_model_id_t; /** -@struct sx1302_lora_service_cfg_t -@brief TODO +@enum sx1302_rx_frequency_tracking_t +@brief Frequency Tracking mode */ -typedef struct { - uint8_t lora_rx_bw; /* bandwidth setting for LoRa standalone modem */ - uint8_t lora_rx_sf; /* spreading factor setting for LoRa standalone modem */ - bool lora_rx_implicit_hdr; /* implicit header setting for LoRa standalone modem */ - uint8_t lora_rx_implicit_length; /* implicit header payload length setting for LoRa standalone modem */ - bool lora_rx_implicit_crc_en; /* implicit header payload crc enable setting for LoRa standalone modem */ - uint8_t lora_rx_implicit_coderate; /* implicit header payload coderate setting for LoRa standalone modem */ -} sx1302_lora_service_cfg_t; +typedef enum { + RX_FREQ_TRACK_OFF = 0x00, + RX_FREQ_TRACK_ON = 0x01, + RX_FREQ_TRACK_AUTO = 0x03 +} sx1302_rx_frequency_tracking_t; /** -@struct sx1302_fsk_cfg_t -@brief TODO +@enum sx1302_rx_fine_timing_mode_t +@brief Fine Timing mode +*/ +typedef enum { + RX_FINE_TIMING_MODE_ABS = 0x01, + RX_FINE_TIMING_MODE_LINEAR = 0x02, + RX_FINE_TIMING_MODE_AUTO = 0x03 +} sx1302_rx_fine_timing_mode_t; + +/** +@enum sx1302_rx_dft_peak_mode_t +@brief DFT peak mode */ -typedef struct { - uint8_t fsk_rx_bw; /* bandwidth setting of FSK modem */ - uint32_t fsk_rx_dr; /* FSK modem datarate in bauds */ - uint8_t fsk_sync_word_size; /* default number of bytes for FSK sync word */ - uint64_t fsk_sync_word; /* default FSK sync word (ALIGNED RIGHT, MSbit first) */ -} sx1302_fsk_cfg_t; +typedef enum { + RX_DFT_PEAK_MODE_DISABLED = 0x00, + RX_DFT_PEAK_MODE_FULL = 0x01, + RX_DFT_PEAK_MODE_TRACK = 0x02, + RX_DFT_PEAK_MODE_AUTO = 0x03 +} sx1302_rx_dft_peak_mode_t; + /* -------------------------------------------------------------------------- */ /* --- PUBLIC FUNCTIONS PROTOTYPES ------------------------------------------ */ /** -@brief TODO -@param TODO -@return TODO +@brief Initialize sx1302 for operating, and needed internal structures (rx_buffer,....) +@param conf a pointer to the fine timestamp configuration context +@return LGW_REG_SUCCESS if no error, LGW_REG_ERROR otherwise */ -void sx1302_init(struct lgw_conf_timestamp_s *conf); +int sx1302_init(const struct lgw_conf_ftime_s *conf); /** @brief Get the SX1302 unique identifier @@ -96,6 +104,13 @@ void sx1302_init(struct lgw_conf_timestamp_s *conf); */ int sx1302_get_eui(uint64_t * eui); +/** +@brief Get the SX1302/SX1303 Chip Model ID +@param model_id pointer to the memory holding the Chip Model ID +@return LGW_REG_SUCCESS if no error, LGW_REG_ERROR otherwise +*/ +int sx1302_get_model_id(sx1302_model_id_t * model_id); + /** @brief Check AGC & ARB MCUs parity error, and update timestamp counter wraping status @brief This function needs to be called regularly (every few seconds) by the upper layer @@ -145,10 +160,10 @@ int sx1302_radio_calibrate(struct lgw_conf_rxrf_s * context_rf_chain, uint8_t cl /** @brief Configure the PA and LNA LUTs -@param N/A +@param context_board A pointer to the current board configuration context @return LGW_REG_SUCCESS if success, LGW_REG_ERROR otherwise */ -int sx1302_pa_lna_lut_configure(void); +int sx1302_pa_lna_lut_configure(struct lgw_conf_board_s * context_board); /** @brief Configure the Radio Front-End stage of the SX1302 @@ -158,9 +173,9 @@ int sx1302_pa_lna_lut_configure(void); int sx1302_radio_fe_configure(void); /** -@brief TODO -@param TODO -@return TODO +@brief Returns the type of the given modem index (LoRa MultiSF, LoRa SingleSF, FSK) +@param if_chain the index if the IF chain +@return The IF chain type */ uint8_t sx1302_get_ifmod_config(uint8_t if_chain); @@ -174,10 +189,11 @@ int sx1302_channelizer_configure(struct lgw_conf_rxif_s * if_cfg, bool fix_gain) /** @brief Configure the correlator stage of the SX1302 LoRa multi-SF modems -@param N/A +@param if_cfg A pointer to the channels configuration +@param demod_cfg A pointer to the demodulators configuration @return LGW_REG_SUCCESS if success, LGW_REG_ERROR otherwise */ -int sx1302_lora_correlator_configure(void); +int sx1302_lora_correlator_configure(struct lgw_conf_rxif_s * if_cfg, struct lgw_conf_demod_s * demod_cfg); /** @brief Configure the correlator stage of the SX1302 LoRa single-SF modem @@ -238,16 +254,16 @@ int sx1302_gps_enable(bool enable); uint32_t sx1302_timestamp_counter(bool pps); /** -@brief TODO -@param TODO -@return TODO +@brief Load firmware to AGC MCU memory +@param firmware A pointer to the fw binary to be loaded +@return LGW_REG_SUCCESS if success, LGW_REG_ERROR otherwise */ int sx1302_agc_load_firmware(const uint8_t *firmware); /** -@brief TODO -@param TODO -@return TODO +@brief Read the AGC status register for current status +@param status A pointer to store the current status returned +@return LGW_REG_SUCCESS if success, LGW_REG_ERROR otherwise */ int sx1302_agc_status(uint8_t* status); @@ -277,7 +293,7 @@ int sx1302_agc_mailbox_write(uint8_t mailbox, uint8_t value); @param TODO @return TODO */ -int sx1302_agc_start(uint8_t version, lgw_radio_type_t radio_type, uint8_t ana_gain, uint8_t dec_gain, uint8_t fdd_mode); +int sx1302_agc_start(uint8_t version, lgw_radio_type_t radio_type, uint8_t ana_gain, uint8_t dec_gain, bool full_duplex, bool lbt_enable); /** @brief TODO @@ -319,7 +335,7 @@ int sx1302_arb_debug_write(uint8_t reg_id, uint8_t value); @param TODO @return TODO */ -int sx1302_arb_start(uint8_t version); +int sx1302_arb_start(uint8_t version, const struct lgw_conf_ftime_s * ftime_context); /** @brief TODO @@ -367,14 +383,15 @@ int sx1302_parse(lgw_context_t * context, struct lgw_pkt_rx_s * p); /** @brief Configure the delay to be applied by the SX1302 for TX to start -@param rf_chain The RF chain index to be configured -@param radio_type The type of radio for this RF chain -@param modulation The modulation used for the TX -@param bandwidth The bandwidth used for the TX -@param delay The TX start delay calculated and applied +@param rf_chain RF chain index to be configured +@param radio_type Type of radio for this RF chain +@param modulation Modulation used for the TX +@param bandwidth Bandwidth used for the TX +@param chirp_lowpass Chirp Low Pass filtering configuration +@param delay TX start delay calculated and applied @return LGW_REG_SUCCESS if success, LGW_REG_ERROR otherwise */ -int sx1302_tx_set_start_delay(uint8_t rf_chain, lgw_radio_type_t radio_type, uint8_t modulation, uint8_t bandwidth, uint16_t * delay); +int sx1302_tx_set_start_delay(uint8_t rf_chain, lgw_radio_type_t radio_type, uint8_t modulation, uint8_t bandwidth, uint8_t chirp_lowpass, uint16_t * delay); /** @brief Compute the offset to be applied on RSSI for temperature compensation @@ -420,6 +437,20 @@ int sx1302_tx_configure(lgw_radio_type_t radio_type); */ int sx1302_send(lgw_radio_type_t radio_type, struct lgw_tx_gain_lut_s * tx_lut, bool lwan_public, struct lgw_conf_rxif_s * context_fsk, struct lgw_pkt_tx_s * pkt_data); +/** +@brief TODO +@param TODO +@return TODO +*/ +int sx1302_set_gpio(uint8_t gpio_reg_val); + +/** +@brief TODO +@param TODO +@return TODO +*/ +double sx1302_dc_notch_delay(double if_freq_hz); + #endif /* --- EOF ------------------------------------------------------------------ */ diff --git a/libloragw/inc/loragw_sx1302_rx.h b/libloragw/inc/loragw_sx1302_rx.h index a8cca034..3d1163fb 100644 --- a/libloragw/inc/loragw_sx1302_rx.h +++ b/libloragw/inc/loragw_sx1302_rx.h @@ -59,8 +59,8 @@ typedef struct rx_packet_s { uint32_t timestamp_cnt; uint16_t rx_crc16_value; /* LoRa only */ uint8_t num_ts_metrics_stored; /* LoRa only */ - uint8_t timestamp_avg[255]; /* LoRa only */ - uint8_t timestamp_stddev[255]; /* LoRa only */ + int8_t timestamp_avg[255]; /* LoRa only */ + int8_t timestamp_stddev[255]; /* LoRa only */ uint8_t packet_checksum; } rx_packet_t; @@ -110,25 +110,10 @@ int rx_buffer_pop(rx_buffer_t * self, rx_packet_t * pkt); /* -------------------------------------------------------------------------- */ /* --- DEBUG FUNCTIONS PROTOTYPES ------------------------------------------- */ -/** -@brief TODO -@param TODO -@return TODO -*/ uint16_t rx_buffer_read_ptr_addr(void); -/** -@brief TODO -@param TODO -@return TODO -*/ uint16_t rx_buffer_write_ptr_addr(void); -/** -@brief TODO -@param TODO -@return TODO -*/ void rx_buffer_dump(FILE * file, uint16_t start_addr, uint16_t end_addr); #endif diff --git a/libloragw/inc/loragw_sx1302_timestamp.h b/libloragw/inc/loragw_sx1302_timestamp.h index aa6635be..4e4b5fd8 100644 --- a/libloragw/inc/loragw_sx1302_timestamp.h +++ b/libloragw/inc/loragw_sx1302_timestamp.h @@ -27,6 +27,8 @@ License: Revised BSD License, see LICENSE.TXT file include in the project #include /* C99 types*/ #include /* boolean type */ +#include "loragw_hal.h" +#include "loragw_sx1302.h" #include "config.h" /* library configuration options (dynamically generated) */ @@ -72,11 +74,11 @@ void timestamp_counter_delete(timestamp_counter_t * self); /** @brief Update the counter wrapping status based on given current counter @param self Pointer to the counter handler -@param pps Set to true to update the PPS trig counter status -@param cnt Current value of the counter to be used for the update +@param pps Current value of the pps counter to be used for the update +@param cnt Current value of the freerun counter to be used for the update @return N/A */ -void timestamp_counter_update(timestamp_counter_t * self, bool pps, uint32_t cnt); +void timestamp_counter_update(timestamp_counter_t * self, uint32_t pps, uint32_t cnt); /** @brief Convert the 27-bits counter given by the SX1302 to a 32-bits counter which wraps on a uint32_t. @@ -98,22 +100,24 @@ uint32_t timestamp_pkt_expand(timestamp_counter_t * self, uint32_t cnt_us); /** @brief Reads the SX1302 internal counter register, and return the 32-bits 1 MHz counter @param self Pointer to the counter handler -@param pps Set to true to expand the counter based on the PPS trig wrapping status -@return the current 32-bits counter +@param pps Current value of the freerun counter +@param pps Current value of the PPS counter +@return LGW_REG_SUCCESS if success, LGW_REG_ERROR otherwise */ -uint32_t timestamp_counter_get(timestamp_counter_t * self, bool pps); +int timestamp_counter_get(timestamp_counter_t * self, uint32_t * inst, uint32_t * pps); /** -@brief Get the timestamp correction to applied to the packet timestamp -@param ifmod modem type +@brief Get the correction to applied to the LoRa packet timestamp (count_us) +@param context gateway configuration context @param bandwidth modulation bandwidth @param datarate modulation datarate @param coderate modulation coding rate @param crc_en indicates if CRC is enabled or disabled @param payload_length payload length +@param dft_peak_mode DFT peak mode configuration of the modem @return The correction to be applied to the packet timestamp, in microseconds */ -uint32_t timestamp_counter_correction(int ifmod, uint8_t bandwidth, uint8_t datarate, uint8_t coderate, uint32_t crc_en, uint16_t payload_length); +int32_t timestamp_counter_correction(lgw_context_t * context, uint8_t bandwidth, uint8_t datarate, uint8_t coderate, bool crc_en, uint8_t payload_length, sx1302_rx_dft_peak_mode_t dft_peak_mode); /** @brief Configure the SX1302 to output legacy timestamp or precision timestamp @@ -125,7 +129,19 @@ uint32_t timestamp_counter_correction(int ifmod, uint8_t bandwidth, uint8_t data @param nb_symbols The sampling rate of timestamp metrics @return LGW_REG_SUCCESS if success, LGW_REG_ERROR otherwise */ -int timestamp_counter_mode(bool enable_precision_ts, uint8_t max_ts_metrics, uint8_t nb_symbols); +int timestamp_counter_mode(bool ftime_enable); + +/** +@brief Compute a precise timestamp (fine timestamp) based on given coarse timestamp, metrics given by sx1302 and current GW xtal drift +@param ts_metrics_nb The number of timestamp metrics given in ts_metrics array +@param ts_metrics An array containing timestamp metrics to compute fine timestamp +@param pkt_coarse_tmst The packet coarse timestamp +@param sf packet spreading factor, used to shift timestamp from end of header to end of preamble +@param if_freq_hz the IF frequency, to take into account DC noth delay +@param result_ftime A pointer to store the resulting fine timestamp +@return 0 if success, -1 otherwise +*/ +int precise_timestamp_calculate(uint8_t ts_metrics_nb, const int8_t * ts_metrics, uint32_t pkt_coarse_tmst, uint8_t sf, int32_t if_freq_hz, uint32_t * result_ftime); #endif diff --git a/libloragw/inc/loragw_usb.h b/libloragw/inc/loragw_usb.h new file mode 100644 index 00000000..0839a4ab --- /dev/null +++ b/libloragw/inc/loragw_usb.h @@ -0,0 +1,98 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2020 Semtech + +Description: + Host specific functions to address the LoRa concentrator registers through + a USB interface. + Single-byte read/write and burst read/write. + +License: Revised BSD License, see LICENSE.TXT file include in the project +*/ + + +#ifndef _LORAGW_USB_H +#define _LORAGW_USB_H + +/* -------------------------------------------------------------------------- */ +/* --- DEPENDANCIES --------------------------------------------------------- */ + +#include /* C99 types*/ + +#include "loragw_com.h" + +#include "config.h" /* library configuration options (dynamically generated) */ + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC CONSTANTS ----------------------------------------------------- */ + +#define LGW_USB_SUCCESS 0 +#define LGW_USB_ERROR -1 + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC FUNCTIONS PROTOTYPES ------------------------------------------ */ + +/** + * +*/ + +int lgw_usb_open(const char * com_path, void **com_target_ptr); + +/** + * +*/ + +int lgw_usb_close(void *com_target); + +/** + * +*/ +int lgw_usb_w(void *com_target, uint8_t spi_mux_target, uint16_t address, uint8_t data); + +/** + * +*/ +int lgw_usb_r(void *com_target, uint8_t spi_mux_target, uint16_t address, uint8_t *data); + +/** + * +*/ +int lgw_usb_wb(void *com_target, uint8_t spi_mux_target, uint16_t address, const uint8_t *data, uint16_t size); + +/** + * +*/ +int lgw_usb_rb(void *com_target, uint8_t spi_mux_target, uint16_t address, uint8_t *data, uint16_t size); + +/** + * +*/ +int lgw_usb_rmw(void *com_target, uint16_t address, uint8_t offs, uint8_t leng, uint8_t data); + +/** + * + **/ +int lgw_usb_set_write_mode(lgw_com_write_mode_t write_mode); + +/** + * + **/ +int lgw_usb_flush(void *com_target); + +/** + * + **/ +uint16_t lgw_usb_chunk_size(void); + +/** + * + **/ +int lgw_usb_get_temperature(void *com_target, float * temperature); + +#endif + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/libloragw/inc/sx1250_com.h b/libloragw/inc/sx1250_com.h new file mode 100644 index 00000000..033a0a0f --- /dev/null +++ b/libloragw/inc/sx1250_com.h @@ -0,0 +1,46 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2019 Semtech + +Description: + Functions used to handle LoRa concentrator SX1250 radios. + +License: Revised BSD License, see LICENSE.TXT file include in the project +*/ + + +#ifndef _SX1250_COM_H +#define _SX1250_COM_H + +/* -------------------------------------------------------------------------- */ +/* --- DEPENDANCIES --------------------------------------------------------- */ + +#include /* C99 types*/ + +#include "loragw_com.h" +#include "sx1250_defs.h" + +#include "config.h" /* library configuration options (dynamically generated) */ + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC MACROS -------------------------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC CONSTANTS ----------------------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC TYPES --------------------------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC FUNCTIONS PROTOTYPES ------------------------------------------ */ + +int sx1250_com_w(lgw_com_type_t com_type, void *com_target, uint8_t spi_mux_target, sx1250_op_code_t op_code, uint8_t *data, uint16_t size); +int sx1250_com_r(lgw_com_type_t com_type, void *com_target, uint8_t spi_mux_target, sx1250_op_code_t op_code, uint8_t *data, uint16_t size); + +#endif + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/libloragw/inc/sx1250_defs.h b/libloragw/inc/sx1250_defs.h new file mode 100644 index 00000000..cb31045f --- /dev/null +++ b/libloragw/inc/sx1250_defs.h @@ -0,0 +1,93 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2019 Semtech + +Description: + Functions used to handle LoRa concentrator SX1250 radios. + +License: Revised BSD License, see LICENSE.TXT file include in the project +*/ + + +#ifndef _SX1250_DEFS_H +#define _SX1250_DEFS_H + +/* -------------------------------------------------------------------------- */ +/* --- DEPENDANCIES --------------------------------------------------------- */ + +#include /* C99 types*/ + +#include "config.h" /* library configuration options (dynamically generated) */ + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC MACROS -------------------------------------------------------- */ + +#define SX1250_FREQ_TO_REG(f) (uint32_t)((uint64_t)f * (1 << 25) / 32000000U) + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC CONSTANTS ----------------------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC TYPES --------------------------------------------------------- */ + +typedef enum { + CALIBRATE = 0x89, + CALIBRATE_IMAGE = 0x98, + CLR_IRQ_STATUS = 0x02, + STOP_TIMER_ON_PREAMBLE = 0x9F, + SET_RFSWITCHMODE = 0x9D, + GET_IRQ_STATUS = 0x12, + GET_RX_BUFFER_STATUS = 0x13, + GET_PACKET_STATUS = 0x14, + READ_BUFFER = 0x1E, + READ_REGISTER = 0x1D, + SET_DIO_IRQ_PARAMS = 0x08, + SET_MODULATION_PARAMS = 0x8B, + SET_PA_CONFIG = 0x95, + SET_PACKET_PARAMS = 0x8C, + SET_PACKET_TYPE = 0x8A, + SET_RF_FREQUENCY = 0x86, + SET_BUFFER_BASE_ADDRESS = 0x8F, + SET_SLEEP = 0x84, + SET_STANDBY = 0x80, + SET_RX = 0x82, + SET_TX = 0x83, + SET_TX_PARAMS = 0x8E, + WRITE_BUFFER = 0x0E, + WRITE_REGISTER = 0x0D, + SET_TXCONTINUOUSWAVE = 0xD1, + SET_TXCONTINUOUSPREAMBLE= 0xD2, + GET_STATUS = 0xC0, + SET_REGULATORMODE = 0x96, + SET_FS = 0xC1, + GET_DEVICE_ERRORS = 0x17 +} sx1250_op_code_t; + +typedef enum { + STDBY_RC = 0x00, + STDBY_XOSC = 0x01 +} sx1250_standby_modes_t; + +typedef enum { + PACKET_TYPE_GFSK = 0x00, + PACKET_TYPE_LORA = 0x01 +} sx1250_packet_type_t; + +typedef enum { + SET_RAMP_10U = 0x00, + SET_RAMP_20U = 0x01, + SET_RAMP_40U = 0x02, + SET_RAMP_80U = 0x03, + SET_RAMP_200U = 0x04, + SET_RAMP_800U = 0x05, + SET_RAMP_1700U = 0x06, + SET_RAMP_3400U = 0x07 +} sx1250_ramp_time_t; + +#endif + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/libloragw/inc/sx1250_spi.h b/libloragw/inc/sx1250_spi.h new file mode 100644 index 00000000..a38e8533 --- /dev/null +++ b/libloragw/inc/sx1250_spi.h @@ -0,0 +1,45 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2019 Semtech + +Description: + Functions used to handle LoRa concentrator SX1250 radios. + +License: Revised BSD License, see LICENSE.TXT file include in the project +*/ + + +#ifndef _SX1250_SPI_H +#define _SX1250_SPI_H + +/* -------------------------------------------------------------------------- */ +/* --- DEPENDANCIES --------------------------------------------------------- */ + +#include /* C99 types*/ + +#include "sx1250_defs.h" + +#include "config.h" /* library configuration options (dynamically generated) */ + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC MACROS -------------------------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC CONSTANTS ----------------------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC TYPES --------------------------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC FUNCTIONS PROTOTYPES ------------------------------------------ */ + +int sx1250_spi_w(void *com_target, uint8_t spi_mux_target, sx1250_op_code_t op_code, uint8_t *data, uint16_t size); +int sx1250_spi_r(void *com_target, uint8_t spi_mux_target, sx1250_op_code_t op_code, uint8_t *data, uint16_t size); + +#endif + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/libloragw/inc/sx1250_usb.h b/libloragw/inc/sx1250_usb.h new file mode 100644 index 00000000..3b2629e5 --- /dev/null +++ b/libloragw/inc/sx1250_usb.h @@ -0,0 +1,45 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2019 Semtech + +Description: + Functions used to handle LoRa concentrator SX1250 radios. + +License: Revised BSD License, see LICENSE.TXT file include in the project +*/ + + +#ifndef _SX1250_USB_H +#define _SX1250_USB_H + +/* -------------------------------------------------------------------------- */ +/* --- DEPENDANCIES --------------------------------------------------------- */ + +#include /* C99 types*/ + +#include "sx1250_defs.h" + +#include "config.h" /* library configuration options (dynamically generated) */ + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC MACROS -------------------------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC CONSTANTS ----------------------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC TYPES --------------------------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC FUNCTIONS PROTOTYPES ------------------------------------------ */ + +int sx1250_usb_w(void *com_target, uint8_t spi_mux_target, sx1250_op_code_t op_code, uint8_t *data, uint16_t size); +int sx1250_usb_r(void *com_target, uint8_t spi_mux_target, sx1250_op_code_t op_code, uint8_t *data, uint16_t size); + +#endif + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/libloragw/inc/sx125x_com.h b/libloragw/inc/sx125x_com.h new file mode 100644 index 00000000..e43e9711 --- /dev/null +++ b/libloragw/inc/sx125x_com.h @@ -0,0 +1,45 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2019 Semtech + +Description: + Functions used to handle LoRa concentrator SX1255/SX1257 radios SPI access. + +License: Revised BSD License, see LICENSE.TXT file include in the project +*/ + +#ifndef _SX125X_COM_H +#define _SX125X_COM_H + +/* -------------------------------------------------------------------------- */ +/* --- DEPENDANCIES --------------------------------------------------------- */ + +#include /* C99 types */ +#include /* bool type */ + +#include "loragw_com.h" + +/* -------------------------------------------------------------------------- */ +/* --- INTERNAL SHARED TYPES ------------------------------------------------ */ + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC MACROS -------------------------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC CONSTANTS ----------------------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC CONSTANTS ----------------------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC FUNCTIONS PROTOTYPES ------------------------------------------ */ + +int sx125x_com_r(lgw_com_type_t com_type, void *com_target, uint8_t spi_mux_target, uint8_t address, uint8_t *data); +int sx125x_com_w(lgw_com_type_t com_type, void *com_target, uint8_t spi_mux_target, uint8_t address, uint8_t data); + +#endif +/* --- EOF ------------------------------------------------------------------ */ diff --git a/libloragw/inc/sx125x_spi.h b/libloragw/inc/sx125x_spi.h new file mode 100644 index 00000000..0d7ff4a6 --- /dev/null +++ b/libloragw/inc/sx125x_spi.h @@ -0,0 +1,43 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2019 Semtech + +Description: + Functions used to handle LoRa concentrator SX1255/SX1257 radios SPI access. + +License: Revised BSD License, see LICENSE.TXT file include in the project +*/ + +#ifndef _SX125X_SPI_H +#define _SX125X_SPI_H + +/* -------------------------------------------------------------------------- */ +/* --- DEPENDANCIES --------------------------------------------------------- */ + +#include /* C99 types */ +#include /* bool type */ + +/* -------------------------------------------------------------------------- */ +/* --- INTERNAL SHARED TYPES ------------------------------------------------ */ + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC MACROS -------------------------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC CONSTANTS ----------------------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC CONSTANTS ----------------------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC FUNCTIONS PROTOTYPES ------------------------------------------ */ + +int sx125x_spi_r(void *com_target, uint8_t spi_mux_target, uint8_t address, uint8_t *data); +int sx125x_spi_w(void *com_target, uint8_t spi_mux_target, uint8_t address, uint8_t data); + +#endif +/* --- EOF ------------------------------------------------------------------ */ diff --git a/libloragw/inc/sx1261_com.h b/libloragw/inc/sx1261_com.h new file mode 100644 index 00000000..7d61166f --- /dev/null +++ b/libloragw/inc/sx1261_com.h @@ -0,0 +1,73 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2019 Semtech + +Description: + Functions used to handle the sx1261 radio used for LBT/Spectral Scan. + +License: Revised BSD License, see LICENSE.TXT file include in the project +*/ + + +#ifndef _SX1261_COM_H +#define _SX1261_COM_H + +/* -------------------------------------------------------------------------- */ +/* --- DEPENDANCIES --------------------------------------------------------- */ + +#include /* C99 types*/ + +#include "loragw_com.h" +#include "sx1261_defs.h" + +#include "config.h" /* library configuration options (dynamically generated) */ + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC MACROS -------------------------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC CONSTANTS ----------------------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC TYPES --------------------------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC FUNCTIONS PROTOTYPES ------------------------------------------ */ + +/** + * +*/ +int sx1261_com_open(lgw_com_type_t com_type, const char *com_path); + +/** + * +*/ +int sx1261_com_close(void); + +/** + * +*/ +int sx1261_com_w(sx1261_op_code_t op_code, uint8_t *data, uint16_t size); + +/** + * +*/ +int sx1261_com_r(sx1261_op_code_t op_code, uint8_t *data, uint16_t size); + +/** + * +*/ +int sx1261_com_set_write_mode(lgw_com_write_mode_t write_mode); + +/** + * +*/ +int sx1261_com_flush(void); + +#endif + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/libloragw/inc/sx1261_defs.h b/libloragw/inc/sx1261_defs.h new file mode 100644 index 00000000..3c7a2eb8 --- /dev/null +++ b/libloragw/inc/sx1261_defs.h @@ -0,0 +1,109 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2019 Semtech + +Description: + Functions used to handle LoRa concentrator SX1261 radio. + +License: Revised BSD License, see LICENSE.TXT file include in the project +*/ + + +#ifndef _SX1261_DEFS_H +#define _SX1261_DEFS_H + +/* -------------------------------------------------------------------------- */ +/* --- DEPENDANCIES --------------------------------------------------------- */ + +#include /* C99 types*/ + +#include "config.h" /* library configuration options (dynamically generated) */ + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC MACROS -------------------------------------------------------- */ + +#define SX1261_FREQ_TO_REG(f) (uint32_t)((uint64_t)f * (1 << 25) / 32000000U) + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC CONSTANTS ----------------------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC TYPES --------------------------------------------------------- */ + +typedef enum { + SX1261_CALIBRATE_IMAGE = 0x98, + SX1261_CLR_IRQ_STATUS = 0x02, + SX1261_STOP_TIMER_ON_PREAMBLE = 0x9F, + SX1261_SET_RFSWITCHMODE = 0x9D, + SX1261_GET_IRQ_STATUS = 0x12, + SX1261_GET_RX_BUFFER_STATUS = 0x13, + SX1261_GET_PACKET_STATUS = 0x14, + SX1261_GET_RSSI_INST = 0x15, + SX1261_READ_BUFFER = 0x1E, + SX1261_READ_REGISTER = 0x1D, + SX1261_SET_DIO_IRQ_PARAMS = 0x08, + SX1261_SET_MODULATION_PARAMS = 0x8B, + SX1261_SET_PA_CONFIG = 0x95, + SX1261_SET_PACKET_PARAMS = 0x8C, + SX1261_SET_PACKET_TYPE = 0x8A, + SX1261_SET_RF_FREQUENCY = 0x86, + SX1261_SET_BUFFER_BASE_ADDRESS = 0x8F, + SX1261_SET_SLEEP = 0x84, + SX1261_SET_STANDBY = 0x80, + SX1261_SET_RX = 0x82, + SX1261_SET_TX = 0x83, + SX1261_SET_TX_PARAMS = 0x8E, + SX1261_WRITE_BUFFER = 0x0E, + SX1261_WRITE_REGISTER = 0x0D, + SX1261_SET_TXCONTINUOUSWAVE = 0xD1, + SX1261_SET_TXCONTINUOUSPREAMBLE = 0xD2, + SX1261_GET_STATUS = 0xC0, + SX1261_SET_REGULATORMODE = 0x96, + SX1261_SET_FS = 0xC1, + SX1261_GET_DEVICE_ERRORS = 0x17 +} sx1261_op_code_t; + +typedef enum { + SX1261_STDBY_RC = 0x00, + SX1261_STDBY_XOSC = 0x01 +} sx1261_standby_modes_t; + +typedef enum { + SX1261_PACKET_TYPE_GFSK = 0x00, + SX1261_PACKET_TYPE_LORA = 0x01 +} sx1261_packet_type_t; + +typedef enum { + SX1261_SET_RAMP_10U = 0x00, + SX1261_SET_RAMP_20U = 0x01, + SX1261_SET_RAMP_40U = 0x02, + SX1261_SET_RAMP_80U = 0x03, + SX1261_SET_RAMP_200U = 0x04, + SX1261_SET_RAMP_800U = 0x05, + SX1261_SET_RAMP_1700U = 0x06, + SX1261_SET_RAMP_3400U = 0x07 +} sx1261_ramp_time_t; + +typedef enum { + SX1261_STATUS_MODE_STBY_RC = 0x20, /* 0x02 - bits 6:4 */ + SX1261_STATUS_MODE_STBY_XOSC = 0x30, /* 0x03 - bits 6:4 */ + SX1261_STATUS_MODE_FS = 0x40, /* 0x04 - bits 6:4 */ + SX1261_STATUS_MODE_RX = 0x50, /* 0x05 - bits 6:4 */ + SX1261_STATUS_MODE_TX = 0x60 /* 0x06 - bits 6:4 */ +} sx1261_status_mode_t; + +typedef enum { + SX1261_STATUS_READY = 0x02, /* 0x02 - bits 3:1 */ + SX1261_STATUS_TIMEOUT = 0x03, /* 0x03 - bits 3:1 */ + SX1261_STATUS_PROCESSING_ERROR = 0x04, /* 0x04 - bits 3:1 */ + SX1261_STATUS_EXECUTION_FAILED = 0x05, /* 0x05 - bits 3:1 */ + SX1261_STATUS_TX_DONE = 0x06 /* 0x06 - bits 3:1 */ +} sx1261_status_command_status_t; + +#endif + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/libloragw/inc/sx1261_spi.h b/libloragw/inc/sx1261_spi.h new file mode 100644 index 00000000..55a7e197 --- /dev/null +++ b/libloragw/inc/sx1261_spi.h @@ -0,0 +1,46 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2019 Semtech + +Description: + Functions used to handle LoRa concentrator SX1261 radio through SPI + interface. + +License: Revised BSD License, see LICENSE.TXT file include in the project +*/ + + +#ifndef _SX1261_SPI_H +#define _SX1261_SPI_H + +/* -------------------------------------------------------------------------- */ +/* --- DEPENDANCIES --------------------------------------------------------- */ + +#include /* C99 types*/ + +#include "sx1261_defs.h" + +#include "config.h" /* library configuration options (dynamically generated) */ + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC MACROS -------------------------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC CONSTANTS ----------------------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC TYPES --------------------------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC FUNCTIONS PROTOTYPES ------------------------------------------ */ + +int sx1261_spi_w(void *com_target, sx1261_op_code_t op_code, uint8_t *data, uint16_t size); +int sx1261_spi_r(void *com_target, sx1261_op_code_t op_code, uint8_t *data, uint16_t size); + +#endif + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/libloragw/inc/sx1261_usb.h b/libloragw/inc/sx1261_usb.h new file mode 100644 index 00000000..d122b786 --- /dev/null +++ b/libloragw/inc/sx1261_usb.h @@ -0,0 +1,64 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2019 Semtech + +Description: + Functions used to handle LoRa concentrator SX1261 radio through USB + interface. + +License: Revised BSD License, see LICENSE.TXT file include in the project +*/ + + +#ifndef _SX1261_USB_H +#define _SX1261_USB_H + +/* -------------------------------------------------------------------------- */ +/* --- DEPENDANCIES --------------------------------------------------------- */ + +#include /* C99 types*/ + +#include "loragw_com.h" +#include "sx1261_defs.h" + +#include "config.h" /* library configuration options (dynamically generated) */ + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC MACROS -------------------------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC CONSTANTS ----------------------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC TYPES --------------------------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC FUNCTIONS PROTOTYPES ------------------------------------------ */ + +/** + * + **/ +int sx1261_usb_w(void *com_target, sx1261_op_code_t op_code, uint8_t *data, uint16_t size); + +/** + * + **/ +int sx1261_usb_r(void *com_target, sx1261_op_code_t op_code, uint8_t *data, uint16_t size); + +/** + * + **/ +int sx1261_usb_set_write_mode(lgw_com_write_mode_t write_mode); + +/** + * + **/ +int sx1261_usb_flush(void *com_target); + +#endif + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/libloragw/library.cfg b/libloragw/library.cfg index 461d7947..401c8524 100644 --- a/libloragw/library.cfg +++ b/libloragw/library.cfg @@ -5,7 +5,8 @@ # Warning: that makes the module *very verbose*, do not use for production DEBUG_AUX= 0 -DEBUG_SPI= 0 +DEBUG_COM= 0 +DEBUG_MCU= 0 DEBUG_I2C= 0 DEBUG_REG= 0 DEBUG_HAL= 0 @@ -13,4 +14,5 @@ DEBUG_LBT= 0 DEBUG_GPS= 0 DEBUG_RAD= 0 DEBUG_CAL= 0 -DEBUG_SX1302= 0 \ No newline at end of file +DEBUG_SX1302= 0 +DEBUG_FTIME= 0 \ No newline at end of file diff --git a/libloragw/readme.md b/libloragw/readme.md index 5bdc4b15..8da60e0c 100644 --- a/libloragw/readme.md +++ b/libloragw/readme.md @@ -3,7 +3,7 @@ \____ \| ___ | (_ _) ___ |/ ___) _ \ _____) ) ____| | | || |_| ____( (___| | | | (______/|_____)_|_|_| \__)_____)\____)_| |_| - (C)2019 Semtech + (C)2020 Semtech LoRa concentrator HAL user manual ================================= @@ -21,18 +21,46 @@ radio used to send and receive packets wirelessly using LoRa or FSK modulations. The library is composed of the following modules: -* loragw_hal -* loragw_reg -* loragw_spi -* loragw_i2c -* loragw_aux -* loragw_gps -* loragw_sx125x -* loragw_sx1250 -* loragw_sx1302 -* loragw_sx1302_rx -* loragw_sx1302_timestamp -* loragw_stts751 +1. abstraction layer + * loragw_hal + * loragw_reg + * loragw_aux + * loragw_cal + * loragw_lbt + * loragw_sx1302 + * loragw_sx1302_rx + * loragw_sx1302_timestamp + * loragw_sx125x + * loragw_sx1250 + * loragw_sx1261 + +2. communication layer for sx1302 + * loragw_com + * loragw_spi + * loragw_usb + +3. communication layer for sx1255/SX1257 radios + * sx125x_com + * sx125x_spi + +4. communication layer for sx1250 radios + * sx1250_com + * sx1250_spi + * sx1250_usb + +5. communication layer for STM32 MCU (USB) + * loragw_mcu + +6. communication layer for sx1261 radio (LBT / Spectral Scan) + * sx1261_com + * sx1261_spi + * sx1261_usb + +7. peripherals + * loragw_i2c + * loragw_gps + * loragw_stts751 + * loragw_ad5338r The library also contains basic test programs to demonstrate code use and check functionality. @@ -51,6 +79,15 @@ use the LoRa concentrator: * lgw_receive, to fetch packets if any was received * lgw_send, to send a single packet (non-blocking, see warning in usage section) * lgw_status, to check when a packet has effectively been sent +* lgw_get_trigcnt, to get the value of the sx1302 internal counter at last PPS +* lgw_get_instcnt, to get the value of the sx1302 internal counter +* lgw_get_eui, to get the sx1302 chip EUI +* lgw_get_temperature, to get the current temperature +* lgw_time_on_air, to get the Time On Air of a packet +* lgw_spectral_scan_start, to start scaning a particular channel +* lgw_spectral_scan_get_status, to get the status of the current scan +* lgw_spectral_scan_get_results, to get the results of the completed scan +* lgw_spectral_scan_abort, to abort curretn scan For an standard application, include only this module. The use of this module is detailed on the usage section. @@ -85,10 +122,12 @@ of by address: * lgw_reg_w, write a named register * lgw_reg_rb, read a name register in burst * lgw_reg_wb, write a named register in burst +* lgw_mem_rb, read from a memory section in burst +* lgw_mem_wb, write to a memory section in burst This module handles read-only registers protection, multi-byte registers management, signed registers management, read-modify-write routines for -sub-byte registers and read/write burst fragmentation to respect SPI maximum +sub-byte registers and read/write burst fragmentation to respect SPI/USB maximum burst length constraints. It make the code much easier to read and to debug. @@ -102,15 +141,21 @@ application. **/!\ Warning** please be sure to have a good understanding of the LoRa concentrator inner working before accessing the internal registers directly. -### 2.3. loragw_spi +### 2.3. loragw_com This module contains the functions to access the LoRa concentrator register -array through the SPI interface: +array through the SPI or USB interfaces: -* lgw_spi_r to read one byte -* lgw_spi_w to write one byte -* lgw_spi_rb to read two bytes or more -* lgw_spi_wb to write two bytes or more +* lgw_com_r to read one byte +* lgw_com_w to write one byte +* lgw_com_rb to read two bytes or more +* lgw_com_wb to write two bytes or more + +This modules is an abstract interface, it then relies on the following modules +to actually perform the interfacing: + +* loragw_spi : for SPI interface +* loragw_usb : for USB interface Please *do not* include that module directly into your application. @@ -171,11 +216,20 @@ also be converted to/from UTC time using lgw_cnt2utc/lgw_utc2cnt functions. ### 2.6. loragw_sx125x This module contains functions to handle the configuration of SX1255 and -SX1257 radios. +SX1257 radios. In order to communicate with the radio, it relies on the +following modules: + +* sx125x_com : abstract interfacing to select USB or SPI interface +* sx125x_spi : implementation of the SPI interface ### 2.7. loragw_sx1250 -This module contains functions to handle the configuration of SX1250 radios. +This module contains functions to handle the configuration of SX1250 radios. In +order to communicate with the radio, it relies on the following modules: + +* sx1250_com : abstract interfacing to select USB or SPI interface +* sx1250_spi : implementation of the SPI interface +* sx1250_usb : implementation of the USB interface ### 2.8. loragw_sx1302 @@ -200,12 +254,76 @@ into account the LoRa demodulation processing time. ### 2.11. loragw_stts751 This module contains a very basic driver for the STmicroelectronics ST751 -temeprature sensor which is on the CoreCell reference design. +temperature sensor which is on the CoreCell reference design. -### 2.12. loragw_i2c +### 2.12. loragw_ad5338r + +This module contains a very basic driver for the Analog Devices AD5338R DAC used +on the Semtech CN490 Full Duplex reference design to set the PA fixed gain. + +### 2.13. loragw_i2c This module provides basic function to communicate with I2C devices on the board. -It is used in this project for accessing the temperature sensor. +It is used in this project for accessing the temperature sensor, the AD5338R DAC... + +### 2.14. loragw_sx1261 + +This module contains functions to handle the configuration of SX1261 radio for +Listen-Before-Talk or Spectral Scan functionnalities. In order to communicate +with the radio, it relies on the following modules: + +* sx1261_com : abstract interfacing to select USB or SPI interface +* sx1261_spi : implementation of the SPI interface +* sx1261_usb : implementation of the USB interface + +This module will also load the sx1261 firmware patch RAM, necessary to support +Listen-Before-Talk and spectral scan features, from the sx1261_pram.var file. + +### 2.15. loragw_lbt + +This module contains functions to start and stop the Listen-Before-Talk feature +when it is enabled. Those functions are called by the lgw_send() function to +ensure that the concentrator is allowed to transmit. + +Listen-Before-Talk (LBT) and Spectral Scan features need an additional sx1261 +radio to be configured. + +The Listen-Before-Talk feature works as follows: + +* the HAL configures the sx1261 for scanning the channel on which it needs to +transmit. +* the SX1261 will scan the channel and set a GPIO to high or low depending if +the channel is busy or not (according to scanning parameters) +* the sx1302 AGC firmware will check the status of this GPIO before actually +starting the transmit, to ensure it is allowed. The AGC fw sets its status +register to inform if the transmit could be done or not. +* the HAL waits for the transmit to be initiated and checks if it was allowed or +not. +* the HAL stops the scanning, and return the tramsit status to the caller. + +### 2.16. loragw_mcu + +This module contains the functions to setup the communication interface with the +STM32 MCU, and to communicate with the sx1302 and the radios when the host and +the concentrator are connected through USB llink. + +The MCU acts as a simple USB <-> SPI bridge. This means that the HAL running on +the host is the same, for both SPI or USB gateways. + +But, as the USB communication link brings a 1ms latency for each transfer, the +MCU provides a mean to group register write requests in one single USB transfer. +It is necessary when a particular configuration has to be done in a time +critical task. + +For this, 2 new functions has been added: +* lgw_com_set_write_mode, to indicate if the following calls to lgw_com_w(b) +need to be grouped on a single USB transfer (BULK mode) or not (SINGLE mode). +* lgw_com_flush, to actually perform the USB transfer of all grouped commands +if BULK mode was selected. + +Both functions will do nothing in case of SPI. + +The same mechanism can be used to configure the sx1261 radio. ## 3. Software build process @@ -270,20 +388,7 @@ The library will not work if there is a mismatch between the hardware version and the library version. You can use the test program test_loragw_reg to check if the hardware registers match their software declaration. -### 4.2. SPI communication - -loragw_spi contains 4 SPI functions (read, write, burst read, burst write) that -are platform-dependant. -The functions must be rewritten depending on the SPI bridge you use: - -* SPI master matched to the Linux SPI device driver (provided) -* SPI over USB using FTDI components (not provided) -* native SPI using a microcontroller peripheral (not provided) - -You can use the test program test_loragw_spi to check with a logic analyser -that the SPI communication is working - -### 4.3. GPS receiver (or other GNSS system) +### 4.2. GPS receiver (or other GNSS system) To use the GPS module of the library, the host must be connected to a GPS receiver via a serial link (or an equivalent receiver using a different @@ -304,6 +409,12 @@ on to allow internal concentrator timestamps to be converted to absolute GPS tim If the GPS receiver sends a GGA NMEA sentence, the gateway 3D position will also be available. +### 4.3. Additionnal SX1261 radio + +In order to perform Listen-Before-Talk and/or Spectral Scan, an additional SX1261 +radio is required. Its internal firmware also needs to be patched (patch RAM) to +support those particular features. + ## 5. Usage ### 5.1. Setting the software environment diff --git a/libloragw/src/agc_fw_sx1250.var b/libloragw/src/agc_fw_sx1250.var index 04ca5ecb..68048708 100644 --- a/libloragw/src/agc_fw_sx1250.var +++ b/libloragw/src/agc_fw_sx1250.var @@ -1,514 +1,515 @@ -static uint8_t agc_firmware_sx1250[8192] = { -0x8A, 0x51, 0xF0, 0x6F, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0x64, 0xC0, 0x80, 0x81, 0x84, 0x0A, 0x04, 0xC6, 0x03, 0x59, 0x00, 0xF4, 0x04, 0xC6, -0x12, 0x28, 0xE5, 0x40, 0xCA, 0x00, 0x61, 0x08, 0xCB, 0x40, 0x62, 0x08, 0xCC, 0x00, 0x63, 0x48, -0xCD, 0x40, 0x4A, 0x70, 0xDC, 0x40, 0x64, 0x08, 0xDD, 0x80, 0x04, 0xF0, 0x32, 0x2F, 0x1D, 0xB0, -0xCA, 0x00, 0x08, 0xF0, 0xCB, 0x40, 0x8B, 0xB0, 0xCC, 0x00, 0x4A, 0x70, 0xCD, 0x81, 0xCE, 0x81, -0xCF, 0xC1, 0xD0, 0x01, 0xD1, 0x41, 0xDC, 0x40, 0x08, 0xF0, 0xDD, 0xC1, 0x6B, 0x67, 0x8A, 0x51, -0x4D, 0x48, 0x83, 0x96, 0xBA, 0x40, 0x83, 0x52, 0x4C, 0x08, 0x83, 0x96, 0xB9, 0x40, 0x83, 0x52, -0x4B, 0x48, 0x83, 0x96, 0xB8, 0x00, 0x83, 0x52, 0x4A, 0x08, 0x83, 0x96, 0xB7, 0x80, 0x1D, 0xB0, -0x83, 0x52, 0xCA, 0x00, 0x08, 0xF0, 0xCB, 0x40, 0x8B, 0xB0, 0xCC, 0x00, 0x4A, 0x70, 0xCD, 0x81, -0xCE, 0x81, 0xCF, 0xC1, 0xD0, 0x01, 0xD1, 0x41, 0xDC, 0x40, 0x08, 0xF0, 0xDD, 0xC1, 0xDD, 0x0A, -0x6B, 0x67, 0x4D, 0x48, 0x83, 0x96, 0xBE, 0x80, 0x83, 0x52, 0x4C, 0x08, 0x83, 0x96, 0xBD, 0x80, -0x83, 0x52, 0x4B, 0x48, 0x83, 0x96, 0xBC, 0x40, 0x83, 0x52, 0x4A, 0x08, 0x83, 0x96, 0xBB, 0x80, -0x08, 0x40, 0xE3, 0x40, 0xC1, 0x70, 0xCA, 0x00, 0x4A, 0x70, 0xDC, 0x40, 0x63, 0x48, 0xDD, 0x80, -0x01, 0xF0, 0x32, 0xE7, 0x8A, 0x51, 0x07, 0x70, 0xE2, 0x00, 0x3A, 0xB0, 0xE1, 0x00, 0xE1, 0xCB, -0x77, 0x28, 0xE2, 0xCB, 0x77, 0x28, 0x00, 0x00, 0x0D, 0x70, 0x83, 0x52, 0x03, 0x53, 0xCA, 0x00, -0x05, 0x30, 0xCB, 0x40, 0x84, 0x30, 0xCC, 0x00, 0x4A, 0x70, 0xCD, 0x81, 0xDC, 0x40, 0x63, 0x48, -0xDD, 0x80, 0x04, 0xF0, 0x32, 0xE7, 0x8A, 0x51, 0x0D, 0x70, 0xCA, 0x00, 0x05, 0x30, 0xCB, 0x40, -0x85, 0x70, 0xCC, 0x00, 0x4A, 0x70, 0xCD, 0x81, 0xDC, 0x40, 0x63, 0x48, 0xDD, 0x80, 0x04, 0xF0, -0x32, 0xE7, 0x8A, 0x51, 0x0D, 0x70, 0xCA, 0x00, 0x05, 0x30, 0xCB, 0x40, 0x82, 0x30, 0xCC, 0x00, -0x4A, 0x70, 0xCD, 0x81, 0xDC, 0x40, 0x63, 0x48, 0xDD, 0x80, 0x04, 0xF0, 0x32, 0xE7, 0x8A, 0x51, -0x0D, 0x70, 0xCA, 0x00, 0x05, 0x30, 0xCB, 0x40, 0x80, 0xF0, 0xCC, 0x00, 0x4A, 0x70, 0xCD, 0x81, -0xDC, 0x40, 0x63, 0x48, 0xDD, 0x80, 0x04, 0xF0, 0x32, 0xE7, 0x8A, 0x51, 0x0D, 0x70, 0xCA, 0x00, -0x05, 0x30, 0xCB, 0x40, 0x83, 0x70, 0xCC, 0x00, 0x4A, 0x70, 0xCD, 0x81, 0xDC, 0x40, 0x63, 0x48, -0xDD, 0x80, 0x04, 0xF0, 0x32, 0xE7, 0x8A, 0x51, 0x63, 0x83, 0x03, 0x9D, 0xC9, 0xA8, 0x9C, 0x91, -0xCA, 0xA8, 0x1C, 0x51, 0x05, 0x30, 0xE1, 0x00, 0xE1, 0xCB, 0xCC, 0xA8, 0x0D, 0x70, 0x83, 0x52, -0x03, 0x53, 0xCA, 0x00, 0x05, 0x30, 0xCB, 0x40, 0x87, 0xB0, 0xCC, 0x00, 0x0B, 0x70, 0xCD, 0x40, -0x4A, 0x70, 0xDC, 0x40, 0x63, 0x48, 0xDD, 0x80, 0x04, 0xF0, 0x32, 0xE7, 0x8A, 0x51, 0x00, 0xB0, -0xE3, 0x88, 0x03, 0x9D, 0xE8, 0xA8, 0x2A, 0xE7, 0x8A, 0x51, 0xC9, 0x00, 0xF3, 0x30, 0xEC, 0xE8, -0x2A, 0xE7, 0x8A, 0x51, 0xC9, 0x00, 0x3F, 0x30, 0xC9, 0x85, 0x49, 0x08, 0xDC, 0x40, 0x00, 0xB0, -0x22, 0xA7, 0x8A, 0x51, 0x63, 0x98, 0xFB, 0x68, 0x00, 0xB0, 0x2A, 0xE7, 0x8A, 0x51, 0xC9, 0x00, -0xFE, 0xF9, 0x01, 0x38, 0x01, 0x29, 0x00, 0xB0, 0x2A, 0xE7, 0x8A, 0x51, 0xC9, 0x00, 0xEF, 0xF9, -0x10, 0x38, 0xC9, 0x00, 0xDC, 0x40, 0x00, 0xB0, 0x22, 0xA7, 0x8A, 0x51, 0x00, 0xB0, 0x2A, 0xE7, -0x8A, 0x51, 0xC9, 0x00, 0xC9, 0x50, 0x49, 0x08, 0xDC, 0x40, 0x00, 0xB0, 0x22, 0xA7, 0x8A, 0x51, -0x00, 0xB0, 0x2A, 0xE7, 0x8A, 0x51, 0xC9, 0x00, 0xC9, 0x92, 0x49, 0x08, 0xDC, 0x40, 0x00, 0xB0, -0x22, 0xA7, 0x8A, 0x51, 0x86, 0x70, 0xCA, 0x00, 0xE3, 0x88, 0x03, 0x9D, 0x2E, 0xE9, 0x83, 0x96, -0x3A, 0x48, 0x83, 0x52, 0xCB, 0x40, 0x83, 0x96, 0x39, 0x48, 0x83, 0x52, 0xCC, 0x00, 0x83, 0x96, -0x38, 0x08, 0x83, 0x52, 0xCD, 0x40, 0x83, 0x96, 0x37, 0x88, 0x3C, 0xE9, 0x83, 0x96, 0x3E, 0x88, -0x83, 0x52, 0xCB, 0x40, 0x83, 0x96, 0x3D, 0x88, 0x83, 0x52, 0xCC, 0x00, 0x83, 0x96, 0x3C, 0x48, -0x83, 0x52, 0xCD, 0x40, 0x83, 0x96, 0x3B, 0x88, 0x83, 0x52, 0xCE, 0x40, 0x4A, 0x70, 0xDC, 0x40, -0x63, 0x48, 0xDD, 0x80, 0x05, 0x30, 0x32, 0xE7, 0x8A, 0x51, 0x0D, 0x70, 0xCA, 0x00, 0x08, 0xF0, -0xCB, 0x40, 0x8F, 0xF0, 0xCC, 0x00, 0x4A, 0x70, 0xCD, 0x81, 0xCE, 0x81, 0xCF, 0xC1, 0xDC, 0x40, -0x63, 0x48, 0xDD, 0x80, 0x06, 0x30, 0x32, 0xE7, 0x8A, 0x51, 0x82, 0x30, 0xCA, 0x00, 0xFF, 0xB0, -0xCB, 0x40, 0xCC, 0x00, 0xCD, 0x40, 0x4A, 0x70, 0xDC, 0x40, 0x63, 0x48, 0xDD, 0x80, 0x04, 0xF0, -0x32, 0xE7, 0x8A, 0x51, 0xC7, 0xF0, 0xE1, 0x00, 0x00, 0x00, 0xE1, 0xCB, 0x64, 0xA9, 0x68, 0xA9, -0x00, 0x00, 0x08, 0x40, 0xEC, 0x40, 0xC1, 0x70, 0xCA, 0x00, 0x4A, 0x70, 0xDC, 0x40, 0x6C, 0x48, -0xDD, 0x80, 0x01, 0xF0, 0x32, 0xE7, 0x8A, 0x51, 0x9F, 0x30, 0xE1, 0x00, 0xE1, 0xCB, 0x76, 0x29, -0x79, 0x29, 0x95, 0xB0, 0x83, 0x52, 0x03, 0x53, 0xCA, 0x00, 0x42, 0xC8, 0xCB, 0x40, 0x41, 0xC8, -0xCC, 0x00, 0x40, 0x88, 0xCD, 0x40, 0x4A, 0x70, 0xCE, 0x81, 0xCE, 0xCA, 0xDC, 0x40, 0x6C, 0x48, -0xDD, 0x80, 0x05, 0x30, 0x32, 0xE7, 0x8A, 0x51, 0xEC, 0x88, 0x03, 0x9D, 0x91, 0xA9, 0x25, 0x70, -0x92, 0xA9, 0x2A, 0x70, 0x2A, 0xE7, 0x8A, 0x51, 0xEB, 0x80, 0xEA, 0x40, 0x3F, 0x30, 0xEA, 0xC5, -0x8E, 0xB0, 0xCA, 0x00, 0x6A, 0x48, 0xCB, 0x40, 0x02, 0xF0, 0xCC, 0x00, 0x4A, 0x70, 0xDC, 0x40, -0x6C, 0x48, 0xDD, 0x80, 0x03, 0x30, 0x32, 0xE7, 0x8A, 0x51, 0x6B, 0x88, 0xE1, 0x00, 0x06, 0x30, -0x03, 0xD0, 0xE1, 0x8C, 0xFF, 0x7E, 0x03, 0x9D, 0xA8, 0xA9, 0x61, 0x08, 0xEA, 0x40, 0x03, 0x30, -0xEA, 0xC5, 0x00, 0xB0, 0xEC, 0x88, 0x03, 0x9D, 0xC1, 0xA9, 0x2A, 0xE7, 0x8A, 0x51, 0xC9, 0x00, -0x6A, 0x48, 0xE1, 0x00, 0x03, 0xD0, 0xE1, 0xCD, 0x03, 0xD0, 0xE1, 0xCD, 0x49, 0x08, 0xF3, 0xB9, -0xCE, 0x29, 0x2A, 0xE7, 0x8A, 0x51, 0xC9, 0x00, 0x6A, 0x48, 0xE1, 0x00, 0x06, 0x30, 0x03, 0xD0, -0xE1, 0xCD, 0xFF, 0x7E, 0x03, 0x9D, 0xC7, 0x29, 0x49, 0x08, 0x3F, 0xB9, 0x61, 0x04, 0xC9, 0x00, -0xDC, 0x40, 0x00, 0xB0, 0x22, 0xA7, 0x8A, 0x51, 0xEC, 0x88, 0x03, 0x9D, 0xE1, 0xE9, 0x27, 0xB0, -0x2A, 0xE7, 0x8A, 0x51, 0xEA, 0x40, 0x28, 0x30, 0x2A, 0xE7, 0x8A, 0x51, 0xEB, 0x80, 0x29, 0x70, -0xEA, 0x29, 0x2C, 0x70, 0x2A, 0xE7, 0x8A, 0x51, 0xEA, 0x40, 0x2D, 0xB0, 0x2A, 0xE7, 0x8A, 0x51, -0xEB, 0x80, 0x2E, 0xB0, 0x2A, 0xE7, 0x8A, 0x51, 0xE3, 0x40, 0x86, 0x70, 0xCA, 0x00, 0x03, 0xD0, -0x6A, 0x8C, 0xCB, 0x40, 0x6B, 0x88, 0xE1, 0x00, 0x03, 0xD0, 0xE1, 0x8C, 0x6A, 0x48, 0xE2, 0x00, -0x06, 0x30, 0x03, 0xD0, 0xE2, 0xCD, 0xFF, 0x7E, 0x03, 0xD0, 0x03, 0x9D, 0xFA, 0x69, 0x62, 0x8D, -0x61, 0x04, 0xCC, 0x00, 0x63, 0x48, 0xE1, 0x00, 0x03, 0xD0, 0xE1, 0x8C, 0x6B, 0x88, 0xE2, 0x00, -0x06, 0x30, 0x03, 0xD0, 0xE2, 0xCD, 0xFF, 0x7E, 0x03, 0xD0, 0x03, 0x9D, 0x0A, 0x6A, 0x62, 0x8D, -0x61, 0x04, 0xCD, 0x40, 0x63, 0x48, 0xE1, 0x00, 0x06, 0x30, 0x03, 0xD0, 0xE1, 0xCD, 0xFF, 0x7E, -0x03, 0xD0, 0x03, 0x9D, 0x16, 0xAA, 0x61, 0x8D, 0xCE, 0x40, 0x4A, 0x70, 0xDC, 0x40, 0x6C, 0x48, -0xDD, 0x80, 0x05, 0x30, 0x32, 0xE7, 0x8A, 0x51, 0xE6, 0x81, 0x02, 0xF0, 0x66, 0x42, 0x03, 0x18, -0x45, 0xAA, 0xC7, 0xF0, 0xE1, 0x00, 0x00, 0x00, 0xE1, 0xCB, 0x2B, 0xEA, 0x2F, 0x2A, 0x00, 0x00, -0x1D, 0xB0, 0x83, 0x52, 0x03, 0x53, 0xCA, 0x00, 0x08, 0xF0, 0xCB, 0x40, 0xC8, 0x70, 0xCC, 0x00, -0x4A, 0x70, 0xCD, 0x81, 0xCE, 0x81, 0xDC, 0x40, 0x6C, 0x48, 0xDD, 0x80, 0x05, 0x30, 0x6B, 0x67, -0x8A, 0x51, 0xCA, 0xDA, 0x45, 0xAA, 0xE6, 0xCA, 0x25, 0xAA, 0x4A, 0x08, 0x20, 0x79, 0x66, 0x44, -0xDC, 0x40, 0x1C, 0x70, 0x22, 0xA7, 0x8A, 0x51, 0x1D, 0xB0, 0xCA, 0x00, 0x08, 0xF0, 0xCB, 0x40, -0xDD, 0x30, 0xCC, 0x00, 0x4A, 0x70, 0xCD, 0x81, 0xCE, 0x81, 0xDC, 0x40, 0x6C, 0x48, 0xDD, 0x80, -0x05, 0x30, 0x6B, 0x67, 0x8A, 0x51, 0x4A, 0x08, 0xDC, 0x40, 0x1B, 0xB0, 0x22, 0xA7, 0x8A, 0x51, -0x4A, 0x08, 0xE9, 0x40, 0x07, 0x70, 0xE9, 0xC5, 0x04, 0xF0, 0x69, 0x42, 0x03, 0x5C, 0x6A, 0xEA, -0x04, 0xF0, 0xE9, 0x40, 0xEC, 0x88, 0x03, 0x9D, 0x74, 0xEA, 0x69, 0x48, 0xDC, 0x40, 0x1D, 0xB0, -0x22, 0xA7, 0x8A, 0x51, 0x26, 0x70, 0x7B, 0x6A, 0x69, 0xCE, 0xF0, 0x39, 0xDC, 0x40, 0x1D, 0xB0, -0x22, 0xA7, 0x8A, 0x51, 0x2B, 0xB0, 0x2A, 0xE7, 0x8A, 0x51, 0xEA, 0x40, 0xEA, 0x9F, 0x39, 0x2B, -0x3D, 0xF0, 0x2A, 0xE7, 0x8A, 0x51, 0xEB, 0x80, 0x3E, 0xF0, 0x2A, 0xE7, 0x8A, 0x51, 0xEA, 0x40, -0x06, 0x30, 0xDC, 0x40, 0x69, 0x48, 0xC6, 0x27, 0x01, 0xBE, 0x84, 0x80, 0x8A, 0x95, 0x00, 0x60, -0x8A, 0x51, 0xE8, 0x00, 0x06, 0x30, 0xDC, 0x40, 0x69, 0x48, 0xC6, 0x27, 0x02, 0xBE, 0x84, 0x80, -0x8A, 0x95, 0x00, 0x60, 0x8A, 0x51, 0xE7, 0x80, 0x6B, 0x88, 0x68, 0x02, 0x03, 0x5C, 0xA8, 0xAA, -0x6B, 0x88, 0x68, 0x46, 0x03, 0x9D, 0xCD, 0x2A, 0x6A, 0x48, 0x67, 0x82, 0x03, 0x18, 0xCD, 0x2A, -0x01, 0xF0, 0x9E, 0x40, 0x1D, 0xB0, 0xCA, 0x00, 0x08, 0xF0, 0xCB, 0x40, 0xC1, 0x70, 0xCC, 0x00, -0x4A, 0x70, 0xCD, 0x81, 0xCE, 0x81, 0xDC, 0x40, 0x6C, 0x48, 0xDD, 0x80, 0x05, 0x30, 0x6B, 0x67, -0x8A, 0x51, 0x4A, 0x08, 0xEB, 0x80, 0x1F, 0x79, 0xE0, 0xB8, 0xEB, 0x80, 0x0D, 0x70, 0xCA, 0x00, -0x08, 0xF0, 0xCB, 0x40, 0xC1, 0x70, 0xCC, 0x00, 0x6B, 0x88, 0xCD, 0x40, 0x4A, 0x70, 0xDC, 0x40, -0x6C, 0x48, 0xDD, 0x80, 0x04, 0xF0, 0x32, 0xE7, 0x8A, 0x51, 0x06, 0x30, 0xDC, 0x40, 0x69, 0x48, -0xC6, 0x27, 0x03, 0xFE, 0x84, 0x80, 0x8A, 0x95, 0x00, 0x60, 0x8A, 0x51, 0xE8, 0x00, 0x06, 0x30, -0xDC, 0x40, 0x69, 0x48, 0xC6, 0x27, 0x04, 0xBE, 0x84, 0x80, 0x8A, 0x95, 0x00, 0x60, 0x8A, 0x51, -0xE7, 0x80, 0x6B, 0x88, 0x68, 0x02, 0x03, 0x5C, 0xED, 0x6A, 0x6B, 0x88, 0x68, 0x46, 0x03, 0x9D, -0xFF, 0x2B, 0x6A, 0x48, 0x67, 0x82, 0x03, 0x18, 0xFF, 0x2B, 0x06, 0x30, 0xDC, 0x40, 0x69, 0x48, -0xC6, 0x27, 0x05, 0xFE, 0x84, 0x80, 0x8A, 0x95, 0x00, 0x60, 0x8A, 0x51, 0xE8, 0x00, 0x06, 0x30, -0xDC, 0x40, 0x69, 0x48, 0xC6, 0x27, 0x06, 0xFE, 0x84, 0x80, 0x8A, 0x95, 0x00, 0x60, 0x8A, 0x51, -0xE7, 0x80, 0x6B, 0x88, 0x68, 0x02, 0x03, 0x5C, 0x0D, 0xEB, 0x6B, 0x88, 0x68, 0x46, 0x03, 0x9D, -0x23, 0xEB, 0x6A, 0x48, 0x67, 0x82, 0x03, 0x18, 0x23, 0xEB, 0x03, 0x30, 0x9E, 0x40, 0x1D, 0xB0, -0xCA, 0x00, 0x08, 0xF0, 0xCB, 0x40, 0xC2, 0x70, 0xCC, 0x00, 0x4A, 0x70, 0xCD, 0x81, 0xCE, 0x81, -0xDC, 0x40, 0x6C, 0x48, 0xDD, 0x80, 0x05, 0x30, 0x6B, 0x67, 0x8A, 0x51, 0x4A, 0x08, 0xEB, 0x80, -0xE1, 0x39, 0x0E, 0xB8, 0xEF, 0xEB, 0x02, 0xF0, 0x9E, 0x40, 0x1D, 0xB0, 0xCA, 0x00, 0x08, 0xF0, -0xCB, 0x40, 0xC2, 0x70, 0xCC, 0x00, 0x4A, 0x70, 0xCD, 0x81, 0xCE, 0x81, 0xDC, 0x40, 0x6C, 0x48, -0xDD, 0x80, 0x05, 0x30, 0x6B, 0x67, 0x8A, 0x51, 0x4A, 0x08, 0xEB, 0x80, 0xE1, 0x39, 0x06, 0x78, -0xEF, 0xEB, 0x07, 0x70, 0xEA, 0xC5, 0x05, 0x30, 0x6A, 0x42, 0x03, 0x18, 0x49, 0xEB, 0x69, 0x48, -0x3C, 0x7E, 0x84, 0x80, 0x8A, 0x95, 0x00, 0x60, 0x8A, 0x51, 0xE4, 0x00, 0x69, 0x48, 0x2D, 0x7E, -0x64, 0xEB, 0x6A, 0x48, 0x05, 0xBA, 0x03, 0x9D, 0x57, 0x6B, 0x69, 0x48, 0x41, 0xFE, 0x84, 0x80, -0x8A, 0x95, 0x00, 0x60, 0x8A, 0x51, 0xE4, 0x00, 0x69, 0x48, 0x32, 0x3E, 0x64, 0xEB, 0x06, 0x30, -0x6A, 0x42, 0x03, 0x5C, 0x69, 0x2B, 0x69, 0x48, 0x46, 0x3E, 0x84, 0x80, 0x8A, 0x95, 0x00, 0x60, -0x8A, 0x51, 0xE4, 0x00, 0x69, 0x48, 0x37, 0xBE, 0x84, 0x80, 0x8A, 0x95, 0x00, 0x60, 0x8A, 0x51, -0xE5, 0x40, 0x0D, 0x70, 0xCA, 0x00, 0x08, 0xF0, 0xCB, 0x40, 0x89, 0x70, 0xCC, 0x00, 0x06, 0x30, -0x6A, 0x42, 0x03, 0x5C, 0x75, 0x6B, 0x09, 0x30, 0x76, 0x6B, 0x0D, 0x70, 0xCD, 0x40, 0x4A, 0x70, -0xDC, 0x40, 0x6C, 0x48, 0xDD, 0x80, 0x04, 0xF0, 0x32, 0xE7, 0x8A, 0x51, 0x06, 0x30, 0x6A, 0x42, -0x03, 0x5C, 0xA5, 0x2B, 0x1D, 0xB0, 0xCA, 0x00, 0x08, 0xF0, 0xCB, 0x40, 0xC2, 0x70, 0xCC, 0x00, -0x4A, 0x70, 0xCD, 0x81, 0xCE, 0x81, 0xDC, 0x40, 0x6C, 0x48, 0xDD, 0x80, 0x05, 0x30, 0x6B, 0x67, -0x8A, 0x51, 0x4A, 0x08, 0xEB, 0x80, 0x9F, 0xB9, 0x20, 0x38, 0xEB, 0x80, 0x0D, 0x70, 0xCA, 0x00, -0x08, 0xF0, 0xCB, 0x40, 0xC2, 0x70, 0xCC, 0x00, 0x6B, 0x88, 0xCD, 0x40, 0x4A, 0x70, 0xDC, 0x40, -0x6C, 0x48, 0xDD, 0x80, 0x04, 0xF0, 0x32, 0xE7, 0x8A, 0x51, 0x64, 0x08, 0x03, 0x59, 0xD4, 0x2B, -0x1D, 0xB0, 0xCA, 0x00, 0x08, 0xF0, 0xCB, 0x40, 0xC1, 0x70, 0xCC, 0x00, 0x4A, 0x70, 0xCD, 0x81, -0xCE, 0x81, 0xDC, 0x40, 0x6C, 0x48, 0xDD, 0x80, 0x05, 0x30, 0x6B, 0x67, 0x8A, 0x51, 0x4A, 0x08, -0xEB, 0x80, 0x64, 0x08, 0xE1, 0x00, 0x05, 0x30, 0x03, 0xD0, 0xE1, 0xCD, 0xFF, 0x7E, 0x03, 0x9D, -0xBC, 0x6B, 0x6B, 0x88, 0x1F, 0x79, 0x61, 0x04, 0xEB, 0x80, 0x0D, 0x70, 0xCA, 0x00, 0x08, 0xF0, -0xCB, 0x40, 0xC1, 0x70, 0xCC, 0x00, 0x6B, 0x88, 0xCD, 0x40, 0x4A, 0x70, 0xDC, 0x40, 0x6C, 0x48, -0xDD, 0x80, 0x04, 0xF0, 0x32, 0xE7, 0x8A, 0x51, 0x65, 0x48, 0x03, 0x59, 0xFF, 0x2B, 0x1D, 0xB0, -0xCA, 0x00, 0x08, 0xF0, 0xCB, 0x40, 0xC2, 0x70, 0xCC, 0x00, 0x4A, 0x70, 0xCD, 0x81, 0xCE, 0x81, -0xDC, 0x40, 0x6C, 0x48, 0xDD, 0x80, 0x05, 0x30, 0x6B, 0x67, 0x8A, 0x51, 0x4A, 0x08, 0xEB, 0x80, -0x65, 0x48, 0xE1, 0x00, 0x03, 0xD0, 0xE1, 0xCD, 0x6B, 0x88, 0xE1, 0x39, 0x61, 0x04, 0xEB, 0x80, -0x0D, 0x70, 0xCA, 0x00, 0x08, 0xF0, 0xCB, 0x40, 0xC2, 0x70, 0xCC, 0x00, 0x6B, 0x88, 0xCD, 0x40, -0x4A, 0x70, 0xDC, 0x40, 0x6C, 0x48, 0xDD, 0x80, 0x04, 0xF0, 0x32, 0xE7, 0x8A, 0x51, 0x6C, 0xCB, -0x07, 0xAC, 0x00, 0xB0, 0x2A, 0xE7, 0x8A, 0x51, 0xC9, 0x00, 0x49, 0x52, 0x0C, 0x6C, 0x00, 0xB0, -0x2A, 0xE7, 0x8A, 0x51, 0xC9, 0x00, 0x49, 0x10, 0x49, 0x08, 0xDC, 0x40, 0x00, 0xB0, 0x22, 0xA7, -0x8A, 0x51, 0x05, 0x30, 0xE1, 0x00, 0xE1, 0xCB, 0x13, 0xAC, 0x00, 0xB0, 0x2A, 0xE7, 0x8A, 0x51, -0xC9, 0x00, 0xFD, 0xF9, 0x02, 0x38, 0xC9, 0x00, 0xDC, 0x40, 0x00, 0xB0, 0x22, 0xA7, 0x8A, 0x51, -0x00, 0xB0, 0x2A, 0xE7, 0x8A, 0x51, 0xC9, 0x00, 0xDF, 0xF9, 0x20, 0x38, 0xC9, 0x00, 0x49, 0x08, -0xDC, 0x40, 0x00, 0xB0, 0x22, 0xA7, 0x8A, 0x51, 0x15, 0x70, 0xE2, 0x00, 0xC6, 0xB0, 0xE1, 0x00, -0xE1, 0xCB, 0x30, 0x6C, 0xE2, 0xCB, 0x30, 0x6C, 0x00, 0x00, 0x0D, 0x70, 0x83, 0x52, 0x03, 0x53, -0xCA, 0x00, 0x05, 0x30, 0xCB, 0x40, 0x84, 0x30, 0xCC, 0x00, 0x4A, 0x70, 0xCD, 0x81, 0xDC, 0x40, -0x6C, 0x48, 0xDD, 0x80, 0x04, 0xF0, 0x32, 0xE7, 0x8A, 0x51, 0x0D, 0x70, 0xCA, 0x00, 0x05, 0x30, -0xCB, 0x40, 0x85, 0x70, 0xCC, 0x00, 0x4A, 0x70, 0xCD, 0x81, 0xDC, 0x40, 0x6C, 0x48, 0xDD, 0x80, -0x04, 0xF0, 0x32, 0xE7, 0x8A, 0x51, 0x0D, 0x70, 0xCA, 0x00, 0x05, 0x30, 0xCB, 0x40, 0x82, 0x30, -0xCC, 0x00, 0x3F, 0x30, 0xCD, 0x40, 0x4A, 0x70, 0xDC, 0x40, 0x6C, 0x48, 0xDD, 0x80, 0x04, 0xF0, -0x32, 0xE7, 0x8A, 0x51, 0x0D, 0x70, 0xCA, 0x00, 0x05, 0x30, 0xCB, 0x40, 0x80, 0xF0, 0xCC, 0x00, -0x3E, 0xF0, 0xCD, 0x40, 0x4A, 0x70, 0xDC, 0x40, 0x6C, 0x48, 0xDD, 0x80, 0x04, 0xF0, 0x32, 0xE7, -0x8A, 0x51, 0x0D, 0x70, 0xCA, 0x00, 0x05, 0x30, 0xCB, 0x40, 0x83, 0x70, 0xCC, 0x00, 0x3E, 0xF0, -0xCD, 0x40, 0x4A, 0x70, 0xDC, 0x40, 0x6C, 0x48, 0xDD, 0x80, 0x04, 0xF0, 0x32, 0xE7, 0x8A, 0x51, -0x6C, 0x83, 0x03, 0x9D, 0x85, 0xAC, 0x9C, 0xD5, 0x86, 0xAC, 0x1C, 0x95, 0x05, 0x30, 0xE1, 0x00, -0xE1, 0xCB, 0x88, 0x6C, 0x0D, 0x70, 0x83, 0x52, 0x03, 0x53, 0xCA, 0x00, 0x05, 0x30, 0xCB, 0x40, -0x87, 0xB0, 0xCC, 0x00, 0x09, 0x30, 0xCD, 0x40, 0x4A, 0x70, 0xDC, 0x40, 0x6C, 0x48, 0xDD, 0x80, -0x04, 0xF0, 0x32, 0xE7, 0x8A, 0x51, 0xD1, 0xB0, 0xCA, 0x00, 0x4A, 0x70, 0xDC, 0x40, 0x6C, 0x48, -0xDD, 0x80, 0x01, 0xF0, 0x32, 0xE7, 0x8A, 0x51, 0x04, 0xF0, 0xE2, 0x00, 0x1C, 0x70, 0xE1, 0x00, -0xE1, 0xCB, 0xA8, 0xAC, 0xE2, 0xCB, 0xA8, 0xAC, 0x00, 0x00, 0x08, 0x40, 0x55, 0xB0, 0x83, 0x52, -0x9B, 0x40, 0xC8, 0x01, 0x02, 0xF0, 0x48, 0xC2, 0x03, 0x18, 0x72, 0x2D, 0x48, 0xC8, 0x01, 0xBE, -0x9B, 0x40, 0x48, 0xC8, 0xB2, 0x7E, 0x84, 0x80, 0x83, 0x93, 0x80, 0x81, 0x48, 0xC8, 0xB2, 0x7E, -0x84, 0x80, 0x80, 0x88, 0x03, 0x9D, 0xEA, 0x2C, 0x3C, 0xB0, 0x2A, 0xE7, 0x8A, 0x51, 0xC9, 0x00, -0x48, 0x18, 0xD6, 0x2C, 0x48, 0xC8, 0xB2, 0x7E, 0x84, 0x80, 0x49, 0x08, 0xDE, 0x80, 0x07, 0x70, -0x03, 0xD0, 0xDE, 0x0C, 0xFF, 0x7E, 0x03, 0x9D, 0xD0, 0xAC, 0xE1, 0xEC, 0x48, 0xC8, 0xB2, 0x7E, -0x84, 0x80, 0x49, 0x08, 0xDE, 0x80, 0x05, 0x30, 0x03, 0xD0, 0xDE, 0x0C, 0xFF, 0x7E, 0x03, 0x9D, -0xDC, 0x2C, 0x5E, 0x88, 0x83, 0x93, 0x80, 0x40, 0x48, 0xC8, 0xB2, 0x7E, 0x84, 0x80, 0x01, 0xF0, -0x80, 0xC5, 0xBE, 0x6C, 0x3E, 0xF0, 0x2A, 0xE7, 0x8A, 0x51, 0xC9, 0x00, 0x49, 0x08, 0x03, 0x59, -0x0A, 0xAD, 0x10, 0xF0, 0x49, 0x02, 0x03, 0x18, 0x0A, 0xAD, 0x48, 0xC8, 0xAE, 0xBE, 0x84, 0x80, -0x49, 0x08, 0x83, 0x93, 0x80, 0x40, 0x48, 0xC8, 0xAE, 0xBE, 0x84, 0x80, 0x00, 0x48, 0xDC, 0x40, -0x1B, 0xB0, 0x22, 0xA7, 0x8A, 0x51, 0x48, 0xC8, 0xAC, 0x7E, 0x84, 0x80, 0x83, 0x93, 0x80, 0x81, -0x80, 0xCA, 0x29, 0xED, 0x49, 0x08, 0xFF, 0x3A, 0x03, 0x9D, 0x16, 0xED, 0x48, 0xC8, 0xAE, 0xBE, -0x84, 0x80, 0x04, 0xF0, 0x83, 0x93, 0x80, 0x40, 0xFF, 0xB0, 0x20, 0x6D, 0x48, 0xC8, 0xAE, 0xBE, -0x84, 0x80, 0x04, 0xF0, 0x83, 0x93, 0x80, 0x40, 0x48, 0xC8, 0xAE, 0xBE, 0x84, 0x80, 0x00, 0x48, -0xDC, 0x40, 0x1B, 0xB0, 0x22, 0xA7, 0x8A, 0x51, 0x48, 0xC8, 0xAC, 0x7E, 0x84, 0x80, 0x83, 0x93, -0x80, 0x81, 0x3F, 0x30, 0x2A, 0xE7, 0x8A, 0x51, 0xC9, 0x00, 0x49, 0x08, 0x03, 0x59, 0x49, 0xED, -0x0E, 0x70, 0x49, 0x02, 0x03, 0x18, 0x49, 0xED, 0x48, 0xC8, 0xA8, 0x3E, 0x84, 0x80, 0x49, 0x08, -0x83, 0x93, 0x80, 0x40, 0x48, 0xC8, 0xA8, 0x3E, 0x84, 0x80, 0x00, 0x48, 0xDC, 0x40, 0x1C, 0x70, -0x22, 0xA7, 0x8A, 0x51, 0x48, 0xC8, 0x23, 0x3E, 0x84, 0x80, 0x83, 0x93, 0x80, 0x81, 0x80, 0xCA, -0x68, 0xED, 0x49, 0x08, 0xFF, 0x3A, 0x03, 0x9D, 0x55, 0x2D, 0x48, 0xC8, 0xA8, 0x3E, 0x84, 0x80, -0x0D, 0x70, 0x83, 0x93, 0x80, 0x40, 0xFF, 0xB0, 0x5F, 0xAD, 0x48, 0xC8, 0xA8, 0x3E, 0x84, 0x80, -0x0D, 0x70, 0x83, 0x93, 0x80, 0x40, 0x48, 0xC8, 0xA8, 0x3E, 0x84, 0x80, 0x00, 0x48, 0xDC, 0x40, -0x1C, 0x70, 0x22, 0xA7, 0x8A, 0x51, 0x48, 0xC8, 0x23, 0x3E, 0x84, 0x80, 0x83, 0x93, 0x80, 0x81, -0x48, 0xC8, 0xB0, 0x3E, 0x84, 0x80, 0x00, 0x48, 0xDC, 0x40, 0x1A, 0x70, 0x22, 0xA7, 0x8A, 0x51, -0xC8, 0x4A, 0xB2, 0xEC, 0x03, 0x30, 0x9B, 0x40, 0x3C, 0xB0, 0x2A, 0xE7, 0x8A, 0x51, 0xC9, 0x00, -0x03, 0xBA, 0x03, 0x9D, 0x74, 0x2D, 0x3E, 0xF0, 0x2A, 0xE7, 0x8A, 0x51, 0xAA, 0x00, 0x3F, 0x30, -0x2A, 0xE7, 0x8A, 0x51, 0xAC, 0x00, 0x2A, 0x02, 0x03, 0x5C, 0x9C, 0x2D, 0x0E, 0x70, 0x2A, 0x02, -0x03, 0x18, 0x9C, 0x2D, 0x2C, 0x08, 0x03, 0x59, 0x9C, 0x2D, 0x2A, 0x08, 0xDC, 0x40, 0x1B, 0xB0, -0x22, 0xA7, 0x8A, 0x51, 0x2C, 0x08, 0xDC, 0x40, 0x1C, 0x70, 0x22, 0xA7, 0x8A, 0x51, 0x2A, 0x08, -0xA9, 0x00, 0x2C, 0x08, 0xAB, 0x40, 0xAA, 0x2D, 0x0D, 0x70, 0xDC, 0x40, 0x1B, 0xB0, 0x22, 0xA7, -0x8A, 0x51, 0x1C, 0x70, 0xDC, 0x81, 0xDC, 0xCA, 0x22, 0xA7, 0x8A, 0x51, 0x0D, 0x70, 0xA9, 0x00, -0xAB, 0x81, 0xAB, 0xCA, 0x04, 0xF0, 0x9B, 0x40, 0x3C, 0xB0, 0x2A, 0xE7, 0x8A, 0x51, 0xC9, 0x00, -0x04, 0x7A, 0x03, 0x9D, 0xAC, 0x2D, 0x3E, 0xF0, 0x2A, 0xE7, 0x8A, 0x51, 0xAE, 0x40, 0x3F, 0x30, -0x2A, 0xE7, 0x8A, 0x51, 0xB0, 0xC0, 0x2E, 0x42, 0x03, 0x5C, 0xD0, 0xED, 0x02, 0xF0, 0x30, 0xC2, -0x03, 0x5C, 0xD0, 0xED, 0x2E, 0x48, 0xDC, 0x40, 0x1B, 0xB0, 0x22, 0xA7, 0x8A, 0x51, 0x30, 0xC8, -0xDC, 0x40, 0x1C, 0x70, 0x22, 0xA7, 0x8A, 0x51, 0x2E, 0x48, 0xAD, 0x40, 0x30, 0xC8, 0xDD, 0xAD, -0x0C, 0x30, 0xDC, 0x40, 0x1B, 0xB0, 0x22, 0xA7, 0x8A, 0x51, 0x03, 0x30, 0xDC, 0x40, 0x1C, 0x70, -0x22, 0xA7, 0x8A, 0x51, 0x0C, 0x30, 0xAD, 0x40, 0x03, 0x30, 0xAF, 0x80, 0x05, 0x30, 0x9B, 0x40, -0x3C, 0xB0, 0x2A, 0xE7, 0x8A, 0x51, 0xC9, 0x00, 0x05, 0xBA, 0x03, 0x9D, 0xE0, 0xED, 0x3E, 0xF0, -0x2A, 0xE7, 0x8A, 0x51, 0xB7, 0x80, 0x3F, 0x30, 0x2A, 0xE7, 0x8A, 0x51, 0xB9, 0x40, 0x37, 0x82, -0x03, 0x5C, 0x07, 0xEE, 0x10, 0xF0, 0x37, 0x82, 0x03, 0x18, 0x07, 0xEE, 0x39, 0x48, 0x03, 0x59, -0x07, 0xEE, 0x37, 0x88, 0xDC, 0x40, 0x1B, 0xB0, 0x22, 0xA7, 0x8A, 0x51, 0x39, 0x48, 0xDC, 0x40, -0x1C, 0x70, 0x22, 0xA7, 0x8A, 0x51, 0x37, 0x88, 0xB6, 0x40, 0x39, 0x48, 0x14, 0xAE, 0x0F, 0xB0, -0xDC, 0x40, 0x1B, 0xB0, 0x22, 0xA7, 0x8A, 0x51, 0x04, 0xF0, 0xDC, 0x40, 0x1C, 0x70, 0x22, 0xA7, -0x8A, 0x51, 0x0F, 0xB0, 0xB6, 0x40, 0x04, 0xF0, 0xB8, 0x00, 0x06, 0x30, 0x9B, 0x40, 0x3C, 0xB0, -0x2A, 0xE7, 0x8A, 0x51, 0xC9, 0x00, 0x06, 0xBA, 0x03, 0x9D, 0x17, 0x2E, 0x3D, 0xF0, 0x2A, 0xE7, -0x8A, 0x51, 0xBD, 0x80, 0x3E, 0xF0, 0x2A, 0xE7, 0x8A, 0x51, 0xBB, 0x80, 0x3F, 0x30, 0x2A, 0xE7, -0x8A, 0x51, 0xBF, 0xC0, 0x3B, 0x88, 0x3D, 0x82, 0x03, 0x5C, 0x4A, 0xEE, 0x3F, 0xC8, 0x3B, 0x82, -0x03, 0x5C, 0x4A, 0xEE, 0x3F, 0xC8, 0x03, 0x59, 0x4A, 0xEE, 0x3D, 0x88, 0xDC, 0x40, 0x1A, 0x70, -0x22, 0xA7, 0x8A, 0x51, 0x3B, 0x88, 0xDC, 0x40, 0x1B, 0xB0, 0x22, 0xA7, 0x8A, 0x51, 0x3F, 0xC8, -0xDC, 0x40, 0x1C, 0x70, 0x22, 0xA7, 0x8A, 0x51, 0x3D, 0x88, 0xBC, 0x40, 0x3B, 0x88, 0xBA, 0x40, -0x3F, 0xC8, 0x5E, 0x6E, 0x5A, 0xB0, 0xDC, 0x40, 0x1A, 0x70, 0x22, 0xA7, 0x8A, 0x51, 0x50, 0x30, -0xDC, 0x40, 0x1B, 0xB0, 0x22, 0xA7, 0x8A, 0x51, 0x28, 0x30, 0xDC, 0x40, 0x1C, 0x70, 0x22, 0xA7, -0x8A, 0x51, 0x5A, 0xB0, 0xBC, 0x40, 0x50, 0x30, 0xBA, 0x40, 0x28, 0x30, 0xBE, 0x80, 0x07, 0x70, -0x9B, 0x40, 0x3C, 0xB0, 0x2A, 0xE7, 0x8A, 0x51, 0xC9, 0x00, 0x07, 0xFA, 0x03, 0x9D, 0x61, 0xEE, -0x3E, 0xF0, 0x2A, 0xE7, 0x8A, 0x51, 0xB1, 0x00, 0x3F, 0x30, 0x2A, 0xE7, 0x8A, 0x51, 0xB3, 0x40, -0x31, 0x02, 0x03, 0x5C, 0x8A, 0xEE, 0x10, 0xF0, 0x31, 0x02, 0x03, 0x18, 0x8A, 0xEE, 0x33, 0x48, -0x03, 0x59, 0x8A, 0xEE, 0x31, 0x08, 0xDC, 0x40, 0x1B, 0xB0, 0x22, 0xA7, 0x8A, 0x51, 0x33, 0x48, -0xDC, 0x40, 0x1C, 0x70, 0x22, 0xA7, 0x8A, 0x51, 0x31, 0x08, 0x83, 0x96, 0xB4, 0x00, 0x83, 0x52, -0x33, 0x48, 0x99, 0x2E, 0x0E, 0x70, 0xDC, 0x40, 0x1B, 0xB0, 0x22, 0xA7, 0x8A, 0x51, 0x04, 0xF0, -0xDC, 0x40, 0x1C, 0x70, 0x22, 0xA7, 0x8A, 0x51, 0x0E, 0x70, 0x83, 0x96, 0xB4, 0x00, 0x04, 0xF0, -0x83, 0x52, 0xB2, 0x00, 0x08, 0xF0, 0x9B, 0x40, 0x3C, 0xB0, 0x2A, 0xE7, 0x8A, 0x51, 0xC9, 0x00, -0x08, 0x7A, 0x03, 0x9D, 0x9C, 0x2E, 0x3E, 0xF0, 0x2A, 0xE7, 0x8A, 0x51, 0xB4, 0x00, 0x3F, 0x30, -0x2A, 0xE7, 0x8A, 0x51, 0xB5, 0x40, 0x34, 0x08, 0x35, 0x42, 0x03, 0x18, 0xC4, 0xEE, 0x02, 0xF0, -0x35, 0x42, 0x03, 0x5C, 0xC4, 0xEE, 0x34, 0x08, 0xDC, 0x40, 0x1B, 0xB0, 0x22, 0xA7, 0x8A, 0x51, -0x35, 0x48, 0xDC, 0x40, 0x1C, 0x70, 0x22, 0xA7, 0x8A, 0x51, 0x34, 0x08, 0x83, 0x96, 0xB5, 0x40, -0x83, 0x52, 0x35, 0x48, 0x83, 0x96, 0xD2, 0x2E, 0x84, 0x30, 0xDC, 0x40, 0x1B, 0xB0, 0x22, 0xA7, -0x8A, 0x51, 0x34, 0x70, 0xDC, 0x40, 0x1C, 0x70, 0x22, 0xA7, 0x8A, 0x51, 0x84, 0x30, 0x83, 0x96, -0xB5, 0x40, 0x34, 0x70, 0xB6, 0x40, 0x09, 0x30, 0x83, 0x52, 0x9B, 0x40, 0x3C, 0xB0, 0x2A, 0xE7, -0x8A, 0x51, 0xC9, 0x00, 0x09, 0xBA, 0x03, 0x9D, 0xD6, 0x6E, 0x3D, 0xF0, 0x2A, 0xE7, 0x8A, 0x51, -0xC2, 0xC0, 0x3E, 0xF0, 0x2A, 0xE7, 0x8A, 0x51, 0xC1, 0xC0, 0x3F, 0x30, 0x2A, 0xE7, 0x8A, 0x51, -0xC0, 0x80, 0x07, 0x70, 0x42, 0xC2, 0x03, 0x18, 0x05, 0xEF, 0x08, 0xF0, 0x41, 0xC2, 0x03, 0x18, -0x05, 0xEF, 0x02, 0xF0, 0x40, 0x82, 0x03, 0x18, 0x05, 0xEF, 0x42, 0xC8, 0xDC, 0x40, 0x1A, 0x70, -0x22, 0xA7, 0x8A, 0x51, 0x41, 0xC8, 0xDC, 0x40, 0x1B, 0xB0, 0x22, 0xA7, 0x8A, 0x51, 0x40, 0x88, -0xDC, 0x40, 0x1C, 0x70, 0x22, 0xA7, 0x8A, 0x51, 0x18, 0xEF, 0x1A, 0x70, 0xDC, 0x81, 0xDC, 0xCA, -0x22, 0xA7, 0x8A, 0x51, 0x02, 0xF0, 0xDC, 0x40, 0x1B, 0xB0, 0x22, 0xA7, 0x8A, 0x51, 0x1C, 0x70, -0xDC, 0x81, 0x22, 0xA7, 0x8A, 0x51, 0x02, 0xF0, 0xC2, 0x01, 0xC2, 0x4A, 0xC1, 0xC0, 0xC0, 0xC1, -0x0A, 0x30, 0x9B, 0x40, 0x3C, 0xB0, 0x2A, 0xE7, 0x8A, 0x51, 0xC9, 0x00, 0x0A, 0xBA, 0x03, 0x59, -0x08, 0x40, 0x1A, 0x2F, 0xDD, 0x80, 0x5C, 0x48, 0x96, 0x00, 0x5D, 0x88, 0x95, 0x00, 0x17, 0x94, -0x17, 0x50, 0x08, 0x40, 0x83, 0x52, 0x03, 0x53, 0xDC, 0x40, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, -0x0D, 0x08, 0x08, 0x40, 0xDF, 0xC0, 0x5D, 0xD8, 0x50, 0xEF, 0xE0, 0x01, 0x5F, 0xC8, 0x60, 0xC2, -0x03, 0x18, 0x45, 0x2F, 0x60, 0xC8, 0x5C, 0xC7, 0xDE, 0x80, 0x84, 0x80, 0x83, 0x93, 0x00, 0x48, -0x99, 0x00, 0x18, 0x14, 0x18, 0xD0, 0xE0, 0x4A, 0x36, 0x6F, 0x18, 0x55, 0x18, 0x11, 0x83, 0x52, -0x03, 0x53, 0x11, 0x5C, 0x08, 0x40, 0x05, 0x30, 0xDE, 0x80, 0xDE, 0x4B, 0x4D, 0x6F, 0x47, 0x6F, -0xE0, 0x01, 0x5F, 0xC8, 0x60, 0xC2, 0x03, 0x18, 0x60, 0xEF, 0x60, 0xC8, 0x5C, 0xC7, 0xDE, 0x80, -0x84, 0x80, 0x83, 0x93, 0x00, 0x48, 0x9A, 0x00, 0x98, 0x95, 0x98, 0x51, 0xE0, 0x4A, 0x51, 0x2F, -0x98, 0x96, 0x98, 0x52, 0x83, 0x52, 0x03, 0x53, 0x91, 0x9C, 0x08, 0x40, 0x05, 0x30, 0xDE, 0x80, -0xDE, 0x4B, 0x68, 0x2F, 0x62, 0x2F, 0xDF, 0xC0, 0x5D, 0xD8, 0x9A, 0x6F, 0xE0, 0x01, 0x5F, 0xC8, -0x60, 0xC2, 0x03, 0x18, 0x7E, 0xEF, 0x60, 0xC8, 0x5C, 0xC7, 0xDE, 0x80, 0x84, 0x80, 0x83, 0x93, -0x00, 0x48, 0x99, 0x00, 0x18, 0x14, 0x18, 0xD0, 0xE0, 0x4A, 0x6F, 0xEF, 0x18, 0x55, 0x18, 0x11, -0x83, 0x52, 0x03, 0x53, 0x11, 0x5C, 0x8A, 0x2F, 0x2A, 0x70, 0xDE, 0x80, 0xDE, 0x4B, 0x86, 0x2F, -0x00, 0x00, 0x80, 0xAF, 0xE0, 0x01, 0x5F, 0xC8, 0x60, 0xC2, 0x03, 0x18, 0x08, 0x40, 0x60, 0xC8, -0x5C, 0xC7, 0xDE, 0x80, 0x84, 0x80, 0x0F, 0x48, 0x83, 0x93, 0x80, 0x40, 0x98, 0x54, 0x98, 0x10, -0xE0, 0x4A, 0x8B, 0x6F, 0xE0, 0x01, 0x5F, 0xC8, 0x60, 0xC2, 0x03, 0x18, 0xAA, 0x6F, 0x60, 0xC8, -0x5C, 0xC7, 0xDE, 0x80, 0x84, 0x80, 0x83, 0x93, 0x00, 0x48, 0x9A, 0x00, 0x98, 0x95, 0x98, 0x51, -0xE0, 0x4A, 0x9B, 0xAF, 0x98, 0x96, 0x98, 0x52, 0x83, 0x52, 0x03, 0x53, 0x91, 0x9C, 0xB6, 0xAF, -0x2A, 0x70, 0xDE, 0x80, 0xDE, 0x4B, 0xB2, 0x6F, 0x00, 0x00, 0xAC, 0x6F, 0xE0, 0x01, 0x5F, 0xC8, -0x60, 0xC2, 0x03, 0x18, 0x08, 0x40, 0x60, 0xC8, 0x5C, 0xC7, 0xDE, 0x80, 0x84, 0x80, 0x10, 0x88, -0x83, 0x93, 0x80, 0x40, 0x18, 0x56, 0x18, 0x12, 0xE0, 0x4A, 0xB7, 0xEF, 0xDE, 0x80, 0xDD, 0xC1, -0x5C, 0x48, 0x5E, 0xD8, 0xDD, 0x47, 0x03, 0xD0, 0xDC, 0x0D, 0x5C, 0x48, 0xDE, 0x18, 0xDD, 0x47, -0x03, 0xD0, 0xDC, 0x0D, 0x5C, 0x48, 0x5E, 0x19, 0xDD, 0x47, 0x03, 0xD0, 0xDC, 0x0D, 0x5C, 0x48, -0xDE, 0x59, 0xDD, 0x47, 0x03, 0xD0, 0xDC, 0x0D, 0x5C, 0x48, 0x5E, 0x1A, 0xDD, 0x47, 0x03, 0xD0, -0xDC, 0x0D, 0x5C, 0x48, 0xDE, 0x5A, 0xDD, 0x47, 0x03, 0xD0, 0xDC, 0x0D, 0x5C, 0x48, 0x5E, 0x5B, -0xDD, 0x47, 0x03, 0xD0, 0xDC, 0x0D, 0x5C, 0x48, 0xDE, 0x9B, 0xDD, 0x47, 0x5D, 0x88, 0x08, 0x40, -0xEF, 0x01, 0x83, 0x93, 0x21, 0x30, 0x84, 0x80, 0x5C, 0xB0, 0x8A, 0x51, 0x11, 0xE0, 0x8A, 0x51, -0xA0, 0x30, 0x84, 0x80, 0xC2, 0x70, 0x8A, 0x51, 0x11, 0xE0, 0x83, 0x01, 0x8A, 0x95, 0x5F, 0xAB, -0x08, 0xF0, 0x8A, 0xC0, 0x04, 0x88, 0x84, 0x0A, 0x82, 0x47, 0x00, 0xF4, 0x01, 0x34, 0x48, 0x74, -0x02, 0x34, 0x3D, 0x34, 0x03, 0x74, 0x33, 0xF4, 0x00, 0xF4, 0xA4, 0xB4, 0x00, 0xF4, 0xF6, 0x74, -0x03, 0x74, 0x33, 0xF4, 0x00, 0xF4, 0x52, 0xB4, 0x00, 0xF4, 0x52, 0xB4, 0x00, 0xF4, 0xCD, 0x34, -0x00, 0xF4, 0x29, 0xB4, 0x00, 0xF4, 0x29, 0xB4, 0x00, 0xF4, 0x66, 0xF4, 0x00, 0xF4, 0x15, 0xB4, -0x00, 0xF4, 0x15, 0xB4, 0x00, 0xF4, 0x29, 0xB4, 0x7F, 0xB4, 0x34, 0xB4, 0x2F, 0x34, 0x2A, 0xB4, -0x26, 0xB4, 0x21, 0x74, 0x1A, 0xB4, 0x16, 0xB4, 0x0F, 0xF4, 0x09, 0x74, 0x04, 0x34, 0x02, 0x34, -0x01, 0x34, 0x00, 0xF4, 0x00, 0xF4, 0x00, 0xF4, 0x00, 0xF4, 0x03, 0x74, 0x03, 0x74, 0x00, 0xF4, -0x00, 0xF4, 0x03, 0x74, 0x03, 0x74, 0x07, 0xB4, 0x01, 0x34, 0x01, 0x34, 0x01, 0x34, 0x07, 0xB4, -0x07, 0xB4, 0x00, 0xF4, 0x00, 0xF4, 0x07, 0xB4, 0x07, 0xB4, 0x07, 0xB4, 0x00, 0xF4, 0x07, 0xB4, -0x07, 0xB4, 0x07, 0xB4, 0x07, 0xB4, 0x07, 0xB4, 0x07, 0xB4, 0x07, 0xB4, 0x07, 0xB4, 0x07, 0xB4, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xC8, 0x01, 0x02, 0xF0, 0x48, 0xC2, -0x03, 0x18, 0x3D, 0x6B, 0x48, 0xC8, 0xB0, 0x3E, 0x84, 0x80, 0x83, 0x93, 0x80, 0x81, 0x48, 0xC8, -0xAE, 0xBE, 0x84, 0x80, 0x80, 0x81, 0x48, 0xC8, 0xA8, 0x3E, 0x84, 0x80, 0x80, 0x81, 0x48, 0xC8, -0xB2, 0x7E, 0x84, 0x80, 0x80, 0x81, 0x48, 0xC8, 0x23, 0x3E, 0x84, 0x80, 0x80, 0x81, 0x48, 0xC8, -0xAC, 0x7E, 0x84, 0x80, 0x80, 0x81, 0xC8, 0x4A, 0x1E, 0x2B, 0x0D, 0x70, 0xAB, 0x81, 0xAB, 0xCA, -0xA9, 0x00, 0x03, 0x30, 0xAF, 0x80, 0x0C, 0x30, 0xAD, 0x40, 0x04, 0xF0, 0xB8, 0x00, 0x0F, 0xB0, -0xB6, 0x40, 0x28, 0x30, 0xBE, 0x80, 0x50, 0x30, 0xBA, 0x40, 0x5A, 0xB0, 0xBC, 0x40, 0x04, 0xF0, -0xB2, 0x00, 0x0E, 0x70, 0x83, 0x96, 0xB4, 0x00, 0x34, 0x70, 0xB6, 0x40, 0x84, 0x30, 0xB5, 0x40, -0x83, 0x52, 0x02, 0xF0, 0xC2, 0x01, 0xC2, 0x4A, 0xC1, 0xC0, 0xC0, 0xC1, 0x08, 0x40, 0x9B, 0x81, -0x01, 0xF0, 0xA0, 0x80, 0x95, 0x41, 0x96, 0x41, 0x97, 0x81, 0x98, 0x01, 0x99, 0x41, 0x9A, 0x41, -0x9B, 0x81, 0x9C, 0x41, 0x9E, 0x81, 0x55, 0xB0, 0x9E, 0x40, 0x9B, 0x81, 0x1C, 0x70, 0xDC, 0x81, -0xDC, 0xCA, 0x8A, 0x51, 0x22, 0xA7, 0x8A, 0x95, 0x8A, 0x95, 0x1D, 0xE3, 0x8A, 0x95, 0x3D, 0xF0, -0x8A, 0x51, 0x2A, 0xE7, 0x8A, 0x95, 0xC9, 0x00, 0xF7, 0xFA, 0x03, 0x9D, 0x82, 0xAB, 0x83, 0x96, -0xBF, 0x01, 0x85, 0xEB, 0x83, 0x96, 0xBF, 0x01, 0xBF, 0x4A, 0x3F, 0xC8, 0x03, 0x59, 0x8C, 0xEB, -0x8A, 0x51, 0xAE, 0xE4, 0x8A, 0x95, 0x8F, 0x6B, 0x01, 0xF0, 0x83, 0x52, 0x9B, 0x40, 0x8A, 0x51, -0x27, 0x60, 0x8A, 0x95, 0x00, 0xB0, 0x8A, 0x51, 0x2A, 0xE7, 0x8A, 0x95, 0xC9, 0x00, 0xFE, 0xF9, -0x01, 0x38, 0xC9, 0x00, 0xDC, 0x40, 0x00, 0xB0, 0x8A, 0x51, 0x22, 0xA7, 0x8A, 0x95, 0x00, 0xB0, -0x8A, 0x51, 0x2A, 0xE7, 0x8A, 0x95, 0xC9, 0x00, 0xEF, 0xF9, 0x10, 0x38, 0xC9, 0x00, 0x49, 0x08, -0xDC, 0x40, 0x00, 0xB0, 0x8A, 0x51, 0x22, 0xA7, 0x8A, 0x95, 0x1C, 0x54, 0x9C, 0x94, 0x9C, 0xD6, -0x35, 0xB0, 0xED, 0x80, 0xED, 0x4B, 0xB2, 0x2B, 0x83, 0x52, 0x03, 0x53, 0x9C, 0x92, 0x9C, 0x17, -0x35, 0xB0, 0xED, 0x80, 0xED, 0x4B, 0xBA, 0x6B, 0x83, 0x52, 0x03, 0x53, 0x9C, 0xD3, 0xC8, 0x01, -0x02, 0xF0, 0x48, 0xC2, 0x03, 0x18, 0x8D, 0xEC, 0x08, 0xF0, 0xE1, 0x00, 0xAC, 0xB0, 0xE2, 0x00, -0x03, 0x30, 0xE3, 0x40, 0x48, 0xC8, 0xE4, 0x00, 0x0D, 0x70, 0x8A, 0x51, 0x19, 0x20, 0x8A, 0x95, -0x48, 0xC8, 0x23, 0x3E, 0x84, 0x80, 0x83, 0x93, 0x00, 0xCB, 0xE0, 0xEB, 0x48, 0xC8, 0xA8, 0x3E, -0x84, 0x80, 0x00, 0x48, 0xED, 0x80, 0x48, 0xC8, 0x21, 0xFE, 0x84, 0x80, 0x6D, 0x88, 0xE4, 0x2B, -0x48, 0xC8, 0x21, 0xFE, 0x84, 0x80, 0x29, 0x08, 0x80, 0x40, 0x48, 0xC8, 0x21, 0xFE, 0x84, 0x80, -0x00, 0x48, 0x1F, 0xBE, 0x84, 0x80, 0x8A, 0x95, 0x00, 0x60, 0x8A, 0x95, 0xED, 0x80, 0x48, 0xC8, -0x27, 0x7E, 0x84, 0x80, 0x6D, 0x88, 0x80, 0x40, 0x08, 0xF0, 0xE1, 0x00, 0xB6, 0xF0, 0xE2, 0x00, -0x48, 0xC8, 0x21, 0xFE, 0x84, 0x80, 0x00, 0x48, 0x20, 0x38, 0xE3, 0x40, 0x48, 0xC8, 0xE4, 0x00, -0x0D, 0x70, 0x8A, 0x51, 0x19, 0x20, 0x8A, 0x95, 0x48, 0xC8, 0xAC, 0x7E, 0x84, 0x80, 0x83, 0x93, -0x00, 0xCB, 0x14, 0x6C, 0x48, 0xC8, 0xAE, 0xBE, 0x84, 0x80, 0x00, 0x48, 0xED, 0x80, 0x48, 0xC8, -0x5A, 0x7E, 0x84, 0x80, 0x6D, 0x88, 0x18, 0x6C, 0x48, 0xC8, 0x5A, 0x7E, 0x84, 0x80, 0x38, 0x08, -0x80, 0x40, 0x06, 0x30, 0xDC, 0x40, 0x48, 0xC8, 0x5A, 0x7E, 0x84, 0x80, 0x38, 0x08, 0x00, 0x42, -0x8A, 0x51, 0xC6, 0x27, 0x8A, 0x95, 0xED, 0x80, 0x48, 0xC8, 0xAA, 0x7E, 0x84, 0x80, 0x6D, 0x88, -0x83, 0x93, 0x80, 0x40, 0x48, 0xC8, 0xAA, 0x7E, 0x84, 0x80, 0x00, 0x48, 0xED, 0x80, 0x48, 0xC8, -0x27, 0x7E, 0x84, 0x80, 0x00, 0x48, 0x6D, 0x07, 0xEE, 0x80, 0x48, 0xC8, 0x25, 0x3E, 0x84, 0x80, -0x6E, 0x88, 0x80, 0x40, 0x48, 0xC8, 0x25, 0x3E, 0x84, 0x80, 0x7F, 0x70, 0x00, 0x42, 0x03, 0x5C, -0x46, 0xAC, 0x48, 0xC8, 0x25, 0x3E, 0x84, 0x80, 0x7F, 0x70, 0x80, 0x40, 0x48, 0x18, 0x4F, 0x2C, -0x48, 0xC8, 0x25, 0x3E, 0x84, 0x80, 0x00, 0x48, 0xDC, 0x40, 0x03, 0x30, 0x55, 0xEC, 0x48, 0xC8, -0x25, 0x3E, 0x84, 0x80, 0x00, 0x48, 0xDC, 0x40, 0x04, 0xF0, 0x8A, 0x51, 0x22, 0xA7, 0x8A, 0x95, -0x48, 0x18, 0x71, 0xEC, 0x48, 0xC8, 0x5A, 0x7E, 0x84, 0x80, 0x83, 0x93, 0x00, 0x48, 0xED, 0x80, -0x01, 0xF0, 0x03, 0xD0, 0xED, 0x4D, 0xFF, 0x7E, 0x03, 0xD0, 0x03, 0x9D, 0x62, 0xAC, 0x6D, 0x0D, -0x02, 0xBE, 0xC9, 0x00, 0xDC, 0x40, 0x01, 0xF0, 0x8A, 0x51, 0x22, 0xA7, 0x8A, 0x95, 0x01, 0xF0, -0x87, 0xEC, 0x48, 0xC8, 0x5A, 0x7E, 0x84, 0x80, 0x83, 0x93, 0x00, 0x48, 0xED, 0x80, 0x01, 0xF0, -0x03, 0xD0, 0xED, 0x4D, 0xFF, 0x7E, 0x03, 0xD0, 0x03, 0x9D, 0x79, 0x2C, 0x6D, 0x0D, 0x02, 0xBE, -0xC9, 0x00, 0xDC, 0x40, 0x02, 0xF0, 0x8A, 0x51, 0x22, 0xA7, 0x8A, 0x95, 0x02, 0xF0, 0xDC, 0x81, -0x8A, 0x51, 0x22, 0xA7, 0x8A, 0x95, 0xC8, 0x4A, 0xC0, 0xAB, 0xC4, 0x01, 0x08, 0xF0, 0x44, 0xC2, -0x03, 0x18, 0xB0, 0xAC, 0x44, 0xC8, 0xDC, 0x40, 0x05, 0x30, 0x8A, 0x51, 0x22, 0xA7, 0x8A, 0x95, -0x44, 0xC8, 0xA0, 0xFE, 0x84, 0x80, 0x32, 0x08, 0x83, 0x93, 0x80, 0x40, 0x44, 0xC8, 0xA0, 0xFE, -0x84, 0x80, 0x03, 0x14, 0x00, 0xCD, 0xC9, 0x00, 0xDC, 0x40, 0x06, 0x30, 0x8A, 0x51, 0x22, 0xA7, -0x8A, 0x95, 0x06, 0x30, 0xDC, 0x81, 0x8A, 0x51, 0x22, 0xA7, 0x8A, 0x95, 0xC4, 0x4A, 0x8E, 0xEC, -0x34, 0x70, 0x9E, 0x40, 0x83, 0x52, 0x12, 0x18, 0x1C, 0x9C, 0xBC, 0x2C, 0x1C, 0x10, 0x9B, 0xD4, -0x00, 0xB0, 0x8A, 0x51, 0x6A, 0xA1, 0x8A, 0x95, 0x83, 0x52, 0x03, 0x53, 0x12, 0x5C, 0x1C, 0x58, -0xC9, 0xEC, 0x00, 0xB0, 0x8A, 0x51, 0x69, 0x60, 0x8A, 0x95, 0x83, 0x52, 0x03, 0x53, 0x1C, 0x54, -0x9B, 0x90, 0x92, 0x58, 0x9C, 0xDC, 0xD2, 0xEC, 0x9C, 0x50, 0x9B, 0xD4, 0x01, 0xF0, 0x8A, 0x51, -0x6A, 0xA1, 0x8A, 0x95, 0x83, 0x52, 0x03, 0x53, 0x92, 0x9C, 0x9C, 0x98, 0xDF, 0xAC, 0x01, 0xF0, -0x8A, 0x51, 0x69, 0x60, 0x8A, 0x95, 0x83, 0x52, 0x03, 0x53, 0x9C, 0x94, 0x9B, 0x90, 0xC4, 0x01, -0x08, 0xF0, 0x44, 0xC2, 0x03, 0x18, 0x2E, 0x2D, 0x44, 0xC8, 0xDC, 0x40, 0x05, 0x30, 0x8A, 0x51, -0x22, 0xA7, 0x8A, 0x95, 0x24, 0x30, 0x8A, 0x51, 0x2A, 0xE7, 0x8A, 0x95, 0xC5, 0x00, 0x83, 0x96, -0x35, 0x42, 0x03, 0x18, 0x04, 0x6D, 0x83, 0x52, 0x44, 0xC8, 0xA0, 0xFE, 0x84, 0x80, 0x83, 0x96, -0x34, 0x08, 0x83, 0x93, 0x00, 0x42, 0x03, 0x18, 0x04, 0x6D, 0x83, 0x52, 0x44, 0xC8, 0xA0, 0xFE, -0x84, 0x80, 0x00, 0x48, 0x01, 0xBE, 0x16, 0xED, 0x36, 0x48, 0x83, 0x52, 0x45, 0x02, 0x03, 0x18, -0x2C, 0xED, 0x44, 0xC8, 0xA0, 0xFE, 0x84, 0x80, 0x83, 0x93, 0x00, 0x48, 0x32, 0x02, 0x03, 0x18, -0x2C, 0xED, 0x44, 0xC8, 0xA0, 0xFE, 0x84, 0x80, 0x00, 0x48, 0xFF, 0x7E, 0xED, 0x80, 0x44, 0xC8, -0xA0, 0xFE, 0x84, 0x80, 0x6D, 0x88, 0x80, 0x40, 0x44, 0xC8, 0xA0, 0xFE, 0x84, 0x80, 0x03, 0x14, -0x00, 0xCD, 0xC9, 0x00, 0xDC, 0x40, 0x06, 0x30, 0x8A, 0x51, 0x22, 0xA7, 0x8A, 0x95, 0x06, 0x30, -0xDC, 0x81, 0x8A, 0x51, 0x22, 0xA7, 0x8A, 0x95, 0xC4, 0x4A, 0xE0, 0xAC, 0x00, 0xB0, 0x14, 0x59, -0x80, 0xF0, 0x94, 0x99, 0x40, 0x38, 0x14, 0x18, 0x20, 0x38, 0x94, 0x58, 0x10, 0x38, 0x21, 0xC4, -0x9E, 0x40, 0x83, 0x96, 0xC0, 0xC1, 0x83, 0x52, 0x21, 0x30, 0xC8, 0x01, 0x8A, 0x51, 0x2A, 0xE7, -0x8A, 0x95, 0xC6, 0x00, 0x20, 0xF0, 0x8A, 0x51, 0x2A, 0xE7, 0x8A, 0x95, 0xC3, 0x00, 0x94, 0x9C, -0x9B, 0x6D, 0x48, 0xC8, 0x23, 0x3E, 0x84, 0x80, 0x83, 0x93, 0x80, 0x88, 0x03, 0x9D, 0x90, 0xAD, -0x48, 0xC8, 0x21, 0xFE, 0x84, 0x80, 0x00, 0x48, 0x2B, 0x42, 0x03, 0x18, 0x90, 0xAD, 0x48, 0xC8, -0x21, 0xFE, 0x84, 0x80, 0x00, 0x48, 0xFF, 0x7E, 0xED, 0x80, 0x48, 0xC8, 0x21, 0xFE, 0x84, 0x80, -0x6D, 0x88, 0x80, 0x40, 0x08, 0xF0, 0xE1, 0x00, 0xB6, 0xF0, 0xE2, 0x00, 0x48, 0xC8, 0x21, 0xFE, -0x84, 0x80, 0x00, 0x48, 0x20, 0x38, 0xE3, 0x40, 0x48, 0xC8, 0xE4, 0x00, 0x0D, 0x70, 0x8A, 0x51, -0x19, 0x20, 0x8A, 0x95, 0x01, 0xF0, 0xDC, 0x81, 0xDC, 0xCA, 0x8A, 0x51, 0x22, 0xA7, 0x8A, 0x95, -0x01, 0xF0, 0xDC, 0x81, 0x8A, 0x51, 0x22, 0xA7, 0x8A, 0x95, 0x48, 0xC8, 0x21, 0xFE, 0x84, 0x80, -0x83, 0x93, 0x00, 0x48, 0x1F, 0xBE, 0x84, 0x80, 0x8A, 0x95, 0x00, 0x60, 0x8A, 0x95, 0xED, 0x80, -0x48, 0xC8, 0x27, 0x7E, 0x84, 0x80, 0x6D, 0x88, 0x80, 0x40, 0x83, 0x96, 0xC0, 0xC1, 0xC0, 0x0A, -0x83, 0x52, 0x9C, 0x92, 0x1C, 0x96, 0x05, 0x30, 0xED, 0x80, 0xED, 0x4B, 0x95, 0x2D, 0x83, 0x52, -0x03, 0x53, 0x1C, 0x52, 0x71, 0x2E, 0x48, 0xC8, 0xAC, 0x7E, 0x84, 0x80, 0x83, 0x93, 0x80, 0x88, -0x03, 0x9D, 0x15, 0xEE, 0x48, 0xC8, 0x21, 0xFE, 0x84, 0x80, 0x00, 0x48, 0x29, 0x46, 0x03, 0x9D, -0xAB, 0x6D, 0x3A, 0x48, 0xAC, 0x2D, 0x3C, 0x48, 0xC7, 0x40, 0x46, 0x08, 0x47, 0x42, 0x03, 0x18, -0xD0, 0xED, 0x48, 0xC8, 0x5A, 0x7E, 0x84, 0x80, 0x36, 0x48, 0x00, 0x42, 0x03, 0x18, 0xD0, 0xED, -0x48, 0xC8, 0x5A, 0x7E, 0x84, 0x80, 0x00, 0x48, 0x01, 0xBE, 0xED, 0x80, 0x48, 0xC8, 0x5A, 0x7E, -0x84, 0x80, 0x6D, 0x88, 0x80, 0x40, 0x48, 0xC8, 0x5A, 0x7E, 0x84, 0x80, 0x00, 0x48, 0xED, 0x80, -0x01, 0xF0, 0x03, 0xD0, 0xED, 0x4D, 0xFF, 0x7E, 0x03, 0xD0, 0x03, 0x9D, 0xCA, 0x2D, 0xF2, 0x6D, -0x3E, 0x88, 0x46, 0x02, 0x48, 0xC8, 0x03, 0x18, 0x16, 0xEE, 0x5A, 0x7E, 0x84, 0x80, 0x00, 0x48, -0x38, 0x02, 0x48, 0xC8, 0x03, 0x18, 0x16, 0xEE, 0x5A, 0x7E, 0x84, 0x80, 0x00, 0x48, 0xFF, 0x7E, -0xED, 0x80, 0x48, 0xC8, 0x5A, 0x7E, 0x84, 0x80, 0x6D, 0x88, 0x80, 0x40, 0x48, 0xC8, 0x5A, 0x7E, -0x84, 0x80, 0x00, 0x48, 0xED, 0x80, 0x01, 0xF0, 0x03, 0xD0, 0xED, 0x4D, 0xFF, 0x7E, 0x03, 0xD0, -0x03, 0x9D, 0xED, 0xAD, 0x6D, 0x0D, 0x02, 0xBE, 0xC9, 0x00, 0xDC, 0x40, 0x01, 0xF0, 0x8A, 0x51, -0x22, 0xA7, 0x8A, 0x95, 0x01, 0xF0, 0xDC, 0x81, 0x8A, 0x51, 0x22, 0xA7, 0x8A, 0x95, 0x83, 0x96, -0x06, 0x30, 0xC0, 0xC1, 0xC0, 0x0A, 0x83, 0x52, 0xDC, 0x40, 0x48, 0xC8, 0x5A, 0x7E, 0x84, 0x80, -0x38, 0x08, 0x83, 0x93, 0x00, 0x42, 0x8A, 0x51, 0xC6, 0x27, 0x8A, 0x95, 0xED, 0x80, 0x48, 0xC8, -0xAA, 0x7E, 0x84, 0x80, 0x6D, 0x88, 0x83, 0x93, 0x80, 0x40, 0x48, 0xC8, 0x23, 0x3E, 0x84, 0x80, -0x80, 0x88, 0x03, 0x9D, 0x71, 0x2E, 0x43, 0x08, 0x2D, 0x42, 0x03, 0x18, 0x2C, 0xEE, 0x48, 0xC8, -0x21, 0xFE, 0x84, 0x80, 0x00, 0x48, 0x2B, 0x42, 0x03, 0x18, 0x2C, 0xEE, 0x48, 0xC8, 0x21, 0xFE, -0x84, 0x80, 0x00, 0x48, 0xFF, 0x7E, 0x3C, 0x2E, 0x2F, 0x88, 0x43, 0x02, 0x03, 0x18, 0x71, 0x2E, -0x48, 0xC8, 0x21, 0xFE, 0x84, 0x80, 0x29, 0x08, 0x00, 0x42, 0x03, 0x18, 0x71, 0x2E, 0x48, 0xC8, -0x21, 0xFE, 0x84, 0x80, 0x00, 0x48, 0x01, 0xBE, 0xED, 0x80, 0x48, 0xC8, 0x21, 0xFE, 0x84, 0x80, -0x6D, 0x88, 0x80, 0x40, 0x08, 0xF0, 0xE1, 0x00, 0xB6, 0xF0, 0xE2, 0x00, 0x48, 0xC8, 0x21, 0xFE, -0x84, 0x80, 0x00, 0x48, 0x20, 0x38, 0xE3, 0x40, 0x48, 0xC8, 0xE4, 0x00, 0x0D, 0x70, 0x8A, 0x51, -0x19, 0x20, 0x8A, 0x95, 0x01, 0xF0, 0xDC, 0x81, 0xDC, 0xCA, 0x8A, 0x51, 0x22, 0xA7, 0x8A, 0x95, -0x01, 0xF0, 0xDC, 0x81, 0x8A, 0x51, 0x22, 0xA7, 0x8A, 0x95, 0x83, 0x96, 0xC0, 0xC1, 0xC0, 0x0A, -0x83, 0x52, 0x48, 0xC8, 0x21, 0xFE, 0x84, 0x80, 0x83, 0x93, 0x00, 0x48, 0x1F, 0xBE, 0x84, 0x80, -0x8A, 0x95, 0x00, 0x60, 0x8A, 0x95, 0xED, 0x80, 0x48, 0xC8, 0x27, 0x7E, 0x84, 0x80, 0x6D, 0x88, -0x80, 0x40, 0x83, 0x96, 0x40, 0x0B, 0x9B, 0x6E, 0x83, 0x52, 0x48, 0xC8, 0xAA, 0x7E, 0x84, 0x80, -0x83, 0x93, 0x00, 0x48, 0xED, 0x80, 0x48, 0xC8, 0x27, 0x7E, 0x84, 0x80, 0x00, 0x48, 0x6D, 0x07, -0xEE, 0x80, 0x48, 0xC8, 0x25, 0x3E, 0x84, 0x80, 0x6E, 0x88, 0x80, 0x40, 0x48, 0xC8, 0x25, 0x3E, -0x84, 0x80, 0x7F, 0x70, 0x00, 0x42, 0x48, 0xC8, 0x03, 0x5C, 0x93, 0x2E, 0x25, 0x3E, 0x84, 0x80, -0x7F, 0x70, 0x80, 0x40, 0x48, 0xC8, 0x25, 0x3E, 0x84, 0x80, 0x00, 0x48, 0xDC, 0x40, 0x03, 0x30, -0x8A, 0x51, 0x22, 0xA7, 0x8A, 0x95, 0x83, 0x52, 0x23, 0x70, 0xC8, 0x01, 0xC8, 0x4A, 0x8A, 0x51, -0x2A, 0xE7, 0x8A, 0x95, 0xC6, 0x00, 0x22, 0x30, 0x8A, 0x51, 0x2A, 0xE7, 0x8A, 0x95, 0xC3, 0x00, -0x83, 0x96, 0xC1, 0x01, 0x83, 0x52, 0x94, 0xDD, 0xFF, 0x2E, 0x48, 0xC8, 0x23, 0x3E, 0x84, 0x80, -0x83, 0x93, 0x80, 0x88, 0x03, 0x9D, 0xF4, 0x6E, 0x48, 0xC8, 0x21, 0xFE, 0x84, 0x80, 0x00, 0x48, -0x2B, 0x42, 0x03, 0x18, 0xF4, 0x6E, 0x48, 0xC8, 0x21, 0xFE, 0x84, 0x80, 0x00, 0x48, 0xFF, 0x7E, -0xED, 0x80, 0x48, 0xC8, 0x21, 0xFE, 0x84, 0x80, 0x6D, 0x88, 0x80, 0x40, 0x08, 0xF0, 0xE1, 0x00, -0xB6, 0xF0, 0xE2, 0x00, 0x48, 0xC8, 0x21, 0xFE, 0x84, 0x80, 0x00, 0x48, 0x20, 0x38, 0xE3, 0x40, -0x48, 0xC8, 0xE4, 0x00, 0x0D, 0x70, 0x8A, 0x51, 0x19, 0x20, 0x8A, 0x95, 0x02, 0xF0, 0xDC, 0x81, -0xDC, 0xCA, 0x8A, 0x51, 0x22, 0xA7, 0x8A, 0x95, 0x02, 0xF0, 0xDC, 0x81, 0x8A, 0x51, 0x22, 0xA7, -0x8A, 0x95, 0x48, 0xC8, 0x21, 0xFE, 0x84, 0x80, 0x83, 0x93, 0x00, 0x48, 0x1F, 0xBE, 0x84, 0x80, -0x8A, 0x95, 0x00, 0x60, 0x8A, 0x95, 0xED, 0x80, 0x48, 0xC8, 0x27, 0x7E, 0x84, 0x80, 0x6D, 0x88, -0x80, 0x40, 0x83, 0x96, 0xC1, 0x01, 0xC1, 0x4A, 0x83, 0x52, 0x9C, 0xD3, 0x1C, 0xD7, 0x05, 0x30, -0xED, 0x80, 0xED, 0x4B, 0xF9, 0xAE, 0x83, 0x52, 0x03, 0x53, 0x1C, 0x93, 0xD5, 0xAF, 0x48, 0xC8, -0xAC, 0x7E, 0x84, 0x80, 0x83, 0x93, 0x80, 0x88, 0x03, 0x9D, 0x79, 0xAF, 0x48, 0xC8, 0x21, 0xFE, -0x84, 0x80, 0x00, 0x48, 0x29, 0x46, 0x03, 0x9D, 0x0F, 0x6F, 0x3A, 0x48, 0x10, 0xAF, 0x3C, 0x48, -0xC7, 0x40, 0x46, 0x08, 0x47, 0x42, 0x03, 0x18, 0x34, 0x2F, 0x48, 0xC8, 0x5A, 0x7E, 0x84, 0x80, -0x36, 0x48, 0x00, 0x42, 0x03, 0x18, 0x34, 0x2F, 0x48, 0xC8, 0x5A, 0x7E, 0x84, 0x80, 0x00, 0x48, -0x01, 0xBE, 0xED, 0x80, 0x48, 0xC8, 0x5A, 0x7E, 0x84, 0x80, 0x6D, 0x88, 0x80, 0x40, 0x48, 0xC8, -0x5A, 0x7E, 0x84, 0x80, 0x00, 0x48, 0xED, 0x80, 0x01, 0xF0, 0x03, 0xD0, 0xED, 0x4D, 0xFF, 0x7E, -0x03, 0xD0, 0x03, 0x9D, 0x2E, 0x6F, 0x56, 0x6F, 0x3E, 0x88, 0x46, 0x02, 0x48, 0xC8, 0x03, 0x18, -0x7A, 0xAF, 0x5A, 0x7E, 0x84, 0x80, 0x00, 0x48, 0x38, 0x02, 0x48, 0xC8, 0x03, 0x18, 0x7A, 0xAF, -0x5A, 0x7E, 0x84, 0x80, 0x00, 0x48, 0xFF, 0x7E, 0xED, 0x80, 0x48, 0xC8, 0x5A, 0x7E, 0x84, 0x80, -0x6D, 0x88, 0x80, 0x40, 0x48, 0xC8, 0x5A, 0x7E, 0x84, 0x80, 0x00, 0x48, 0xED, 0x80, 0x01, 0xF0, -0x03, 0xD0, 0xED, 0x4D, 0xFF, 0x7E, 0x03, 0xD0, 0x03, 0x9D, 0x51, 0x2F, 0x6D, 0x0D, 0x02, 0xBE, -0xC9, 0x00, 0xDC, 0x40, 0x02, 0xF0, 0x8A, 0x51, 0x22, 0xA7, 0x8A, 0x95, 0x02, 0xF0, 0xDC, 0x81, -0x8A, 0x51, 0x22, 0xA7, 0x8A, 0x95, 0x83, 0x96, 0x06, 0x30, 0xC1, 0x01, 0xC1, 0x4A, 0x83, 0x52, -0xDC, 0x40, 0x48, 0xC8, 0x5A, 0x7E, 0x84, 0x80, 0x38, 0x08, 0x83, 0x93, 0x00, 0x42, 0x8A, 0x51, -0xC6, 0x27, 0x8A, 0x95, 0xED, 0x80, 0x48, 0xC8, 0xAA, 0x7E, 0x84, 0x80, 0x6D, 0x88, 0x83, 0x93, -0x80, 0x40, 0x48, 0xC8, 0x23, 0x3E, 0x84, 0x80, 0x80, 0x88, 0x03, 0x9D, 0xD5, 0xAF, 0x43, 0x08, -0x2D, 0x42, 0x03, 0x18, 0x90, 0xEF, 0x48, 0xC8, 0x21, 0xFE, 0x84, 0x80, 0x00, 0x48, 0x2B, 0x42, -0x03, 0x18, 0x90, 0xEF, 0x48, 0xC8, 0x21, 0xFE, 0x84, 0x80, 0x00, 0x48, 0xFF, 0x7E, 0xA0, 0xEF, -0x2F, 0x88, 0x43, 0x02, 0x03, 0x18, 0xD5, 0xAF, 0x48, 0xC8, 0x21, 0xFE, 0x84, 0x80, 0x29, 0x08, -0x00, 0x42, 0x03, 0x18, 0xD5, 0xAF, 0x48, 0xC8, 0x21, 0xFE, 0x84, 0x80, 0x00, 0x48, 0x01, 0xBE, -0xED, 0x80, 0x48, 0xC8, 0x21, 0xFE, 0x84, 0x80, 0x6D, 0x88, 0x80, 0x40, 0x08, 0xF0, 0xE1, 0x00, -0xB6, 0xF0, 0xE2, 0x00, 0x48, 0xC8, 0x21, 0xFE, 0x84, 0x80, 0x00, 0x48, 0x20, 0x38, 0xE3, 0x40, -0x48, 0xC8, 0xE4, 0x00, 0x0D, 0x70, 0x8A, 0x51, 0x19, 0x20, 0x8A, 0x95, 0x02, 0xF0, 0xDC, 0x81, -0xDC, 0xCA, 0x8A, 0x51, 0x22, 0xA7, 0x8A, 0x95, 0x02, 0xF0, 0xDC, 0x81, 0x8A, 0x51, 0x22, 0xA7, -0x8A, 0x95, 0x83, 0x96, 0xC1, 0x01, 0xC1, 0x4A, 0x83, 0x52, 0x48, 0xC8, 0x21, 0xFE, 0x84, 0x80, -0x83, 0x93, 0x00, 0x48, 0x1F, 0xBE, 0x84, 0x80, 0x8A, 0x95, 0x00, 0x60, 0x8A, 0x95, 0xED, 0x80, -0x48, 0xC8, 0x27, 0x7E, 0x84, 0x80, 0x6D, 0x88, 0x80, 0x40, 0x83, 0x96, 0x41, 0x4B, 0xB2, 0xEC, -0x83, 0x52, 0x48, 0xC8, 0xAA, 0x7E, 0x84, 0x80, 0x83, 0x93, 0x00, 0x48, 0xED, 0x80, 0x48, 0xC8, -0x27, 0x7E, 0x84, 0x80, 0x00, 0x48, 0x6D, 0x07, 0xEE, 0x80, 0x48, 0xC8, 0x25, 0x3E, 0x84, 0x80, -0x6E, 0x88, 0x80, 0x40, 0x48, 0xC8, 0x25, 0x3E, 0x84, 0x80, 0x7F, 0x70, 0x00, 0x42, 0x48, 0xC8, -0x03, 0x5C, 0xF7, 0x2F, 0x25, 0x3E, 0x84, 0x80, 0x7F, 0x70, 0x80, 0x40, 0x48, 0xC8, 0x25, 0x3E, -0x84, 0x80, 0x00, 0x48, 0xDC, 0x40, 0x04, 0xF0, 0x8A, 0x51, 0x22, 0xA7, 0x8A, 0x95, 0xB2, 0xEC -}; \ No newline at end of file +static uint8_t agc_firmware_sx1250[8192] = { +0x8A, 0x51, 0xF0, 0x6F, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xE3, 0x40, +0xC8, 0xC0, 0x5F, 0xC8, 0xC9, 0x00, 0x60, 0xC8, 0xCA, 0x00, 0x61, 0x08, 0xCB, 0x40, 0x48, 0x30, +0xDA, 0x40, 0x62, 0x08, 0xDB, 0x80, 0x04, 0xF0, 0x5C, 0x6F, 0xE1, 0x00, 0xC1, 0x70, 0xC8, 0xC0, +0x48, 0x30, 0xDA, 0x40, 0x61, 0x08, 0xDB, 0x80, 0x01, 0xF0, 0x5C, 0x27, 0x8A, 0x51, 0x07, 0x70, +0xE0, 0xC0, 0x3A, 0xB0, 0xDF, 0xC0, 0xDF, 0x8B, 0x23, 0x68, 0xE0, 0x8B, 0x23, 0x68, 0x00, 0x00, +0x0D, 0x70, 0x83, 0x52, 0x03, 0x53, 0xC8, 0xC0, 0x05, 0x30, 0xC9, 0x00, 0x84, 0x30, 0xCA, 0x00, +0x48, 0x30, 0xCB, 0x81, 0xDA, 0x40, 0x61, 0x08, 0xDB, 0x80, 0x04, 0xF0, 0x5C, 0x27, 0x8A, 0x51, +0x0D, 0x70, 0xC8, 0xC0, 0x05, 0x30, 0xC9, 0x00, 0x85, 0x70, 0xCA, 0x00, 0x48, 0x30, 0xCB, 0x81, +0xDA, 0x40, 0x61, 0x08, 0xDB, 0x80, 0x04, 0xF0, 0x5C, 0x27, 0x8A, 0x51, 0x0D, 0x70, 0xC8, 0xC0, +0x05, 0x30, 0xC9, 0x00, 0x82, 0x30, 0xCA, 0x00, 0x48, 0x30, 0xCB, 0x81, 0xDA, 0x40, 0x61, 0x08, +0xDB, 0x80, 0x04, 0xF0, 0x5C, 0x27, 0x8A, 0x51, 0x0D, 0x70, 0xC8, 0xC0, 0x05, 0x30, 0xC9, 0x00, +0x83, 0x70, 0xCA, 0x00, 0x48, 0x30, 0xCB, 0x81, 0xDA, 0x40, 0x61, 0x08, 0xDB, 0x80, 0x04, 0xF0, +0x5C, 0x27, 0x8A, 0x51, 0x61, 0x8B, 0x66, 0xA8, 0x9C, 0x91, 0x67, 0xE8, 0x1C, 0x51, 0x0D, 0x70, +0xC8, 0xC0, 0x05, 0x30, 0xC9, 0x00, 0x80, 0xF0, 0xCA, 0x00, 0x48, 0x30, 0xCB, 0x81, 0xDA, 0x40, +0x61, 0x08, 0xDB, 0x80, 0x04, 0xF0, 0x5C, 0x27, 0x8A, 0x51, 0x05, 0x30, 0xDF, 0xC0, 0xDF, 0x8B, +0x77, 0x28, 0x0D, 0x70, 0x83, 0x52, 0x03, 0x53, 0xC8, 0xC0, 0x05, 0x30, 0xC9, 0x00, 0x87, 0xB0, +0xCA, 0x00, 0x0B, 0x70, 0xCB, 0x40, 0x48, 0x30, 0xDA, 0x40, 0x61, 0x08, 0xDB, 0x80, 0x04, 0xF0, +0x5C, 0x27, 0x8A, 0x51, 0x00, 0xB0, 0xE1, 0x48, 0x03, 0x9D, 0xA9, 0xA8, 0x54, 0xE7, 0x8A, 0x51, +0xC5, 0x00, 0xF3, 0x30, 0xC5, 0x85, 0x45, 0x08, 0xDA, 0x40, 0x00, 0xB0, 0x4C, 0xE7, 0x8A, 0x51, +0x00, 0xB0, 0x54, 0xE7, 0x8A, 0x51, 0xC5, 0x00, 0xFE, 0xF9, 0x01, 0x38, 0xC5, 0x00, 0xDA, 0x40, +0x00, 0xB0, 0x4C, 0xE7, 0x8A, 0x51, 0x00, 0xB0, 0x54, 0xE7, 0x8A, 0x51, 0xC5, 0x00, 0xC5, 0x50, +0xC3, 0xA8, 0x54, 0xE7, 0x8A, 0x51, 0xC5, 0x00, 0x3F, 0x30, 0xC5, 0x85, 0x45, 0x08, 0xDA, 0x40, +0x00, 0xB0, 0x4C, 0xE7, 0x8A, 0x51, 0x00, 0xB0, 0x54, 0xE7, 0x8A, 0x51, 0xC5, 0x00, 0xEF, 0xF9, +0x10, 0x38, 0xC5, 0x00, 0xDA, 0x40, 0x00, 0xB0, 0x4C, 0xE7, 0x8A, 0x51, 0x00, 0xB0, 0x54, 0xE7, +0x8A, 0x51, 0xC5, 0x00, 0xC5, 0x92, 0x45, 0x08, 0xDA, 0x40, 0x00, 0xB0, 0x4C, 0xE7, 0x8A, 0x51, +0x86, 0x70, 0xC8, 0xC0, 0xE1, 0x48, 0x03, 0x9D, 0xDC, 0xE8, 0x83, 0x96, 0x3E, 0x88, 0x83, 0x52, +0xC9, 0x00, 0x83, 0x96, 0x3D, 0x88, 0x83, 0x52, 0xCA, 0x00, 0x83, 0x96, 0x3C, 0x48, 0x83, 0x52, +0xCB, 0x40, 0x83, 0x96, 0x3B, 0x88, 0xEA, 0xE8, 0x83, 0x96, 0x42, 0xC8, 0x83, 0x52, 0xC9, 0x00, +0x83, 0x96, 0x41, 0xC8, 0x83, 0x52, 0xCA, 0x00, 0x83, 0x96, 0x40, 0x88, 0x83, 0x52, 0xCB, 0x40, +0x83, 0x96, 0x3F, 0xC8, 0x83, 0x52, 0xCC, 0x00, 0x48, 0x30, 0xDA, 0x40, 0x61, 0x08, 0xDB, 0x80, +0x05, 0x30, 0x5C, 0x27, 0x8A, 0x51, 0x0D, 0x70, 0xC8, 0xC0, 0x08, 0xF0, 0xC9, 0x00, 0x8F, 0xF0, +0xCA, 0x00, 0x48, 0x30, 0xCB, 0x81, 0xCC, 0x41, 0xCD, 0x81, 0xDA, 0x40, 0x61, 0x08, 0xDB, 0x80, +0x06, 0x30, 0x5C, 0x27, 0x8A, 0x51, 0x82, 0x30, 0xC8, 0xC0, 0xFF, 0xB0, 0xC9, 0x00, 0xCA, 0x00, +0xCB, 0x40, 0x48, 0x30, 0xDA, 0x40, 0x61, 0x08, 0xDB, 0x80, 0x04, 0xF0, 0x5C, 0x27, 0x8A, 0x51, +0x82, 0x30, 0xC8, 0xC0, 0xFF, 0xB0, 0xC9, 0x00, 0xCA, 0x00, 0xCB, 0x40, 0x48, 0x30, 0xDA, 0x40, +0x61, 0x08, 0xDB, 0x80, 0x04, 0xF0, 0x5C, 0x27, 0x8A, 0x51, 0xC7, 0xF0, 0xDF, 0xC0, 0x00, 0x00, +0xDF, 0x8B, 0x1F, 0x29, 0x23, 0xA9, 0x00, 0x00, 0x08, 0x40, 0xEC, 0x40, 0xC1, 0x70, 0xC8, 0xC0, +0x48, 0x30, 0xDA, 0x40, 0x6C, 0x48, 0xDB, 0x80, 0x01, 0xF0, 0x5C, 0x27, 0x8A, 0x51, 0x9F, 0x30, +0xE0, 0xC0, 0xE0, 0x8B, 0x31, 0xA9, 0x34, 0xA9, 0x95, 0xB0, 0x83, 0x52, 0x03, 0x53, 0xC8, 0xC0, +0x3F, 0xC8, 0xC9, 0x00, 0x3E, 0x88, 0xCA, 0x00, 0x3C, 0x48, 0xCB, 0x40, 0x48, 0x30, 0xCC, 0x41, +0xCC, 0x8A, 0xDA, 0x40, 0x6C, 0x48, 0xDB, 0x80, 0x05, 0x30, 0x5C, 0x27, 0x8A, 0x51, 0x1D, 0xB0, +0xC8, 0xC0, 0x08, 0xF0, 0xC9, 0x00, 0xE2, 0xB0, 0xCA, 0x00, 0x48, 0x30, 0xCB, 0x81, 0xCC, 0x41, +0xDA, 0x40, 0x6C, 0x48, 0xDB, 0x80, 0x05, 0x30, 0x95, 0x27, 0x8A, 0x51, 0x48, 0xC8, 0xCD, 0x40, +0x13, 0x70, 0xCD, 0xC5, 0x0D, 0x70, 0xC8, 0xC0, 0x08, 0xF0, 0xC9, 0x00, 0xE2, 0xB0, 0xCA, 0x00, +0x4D, 0x48, 0xCB, 0x40, 0x48, 0x30, 0xDA, 0x40, 0x6C, 0x48, 0xDB, 0x80, 0x04, 0xF0, 0x5C, 0x27, +0x8A, 0x51, 0xEC, 0x88, 0x03, 0x9D, 0x6E, 0x29, 0x25, 0x70, 0x6F, 0x69, 0x2A, 0x70, 0x54, 0xE7, +0x8A, 0x51, 0xEB, 0x80, 0xEA, 0x40, 0x3F, 0x30, 0xEA, 0xC5, 0x8E, 0xB0, 0xC8, 0xC0, 0x6A, 0x48, +0xC9, 0x00, 0x48, 0x30, 0xCA, 0x41, 0xDA, 0x40, 0x6C, 0x48, 0xDB, 0x80, 0x03, 0x30, 0x5C, 0x27, +0x8A, 0x51, 0x6B, 0x88, 0xE0, 0xC0, 0x06, 0x30, 0x03, 0xD0, 0xE0, 0x4C, 0xFF, 0x7E, 0x03, 0x9D, +0x84, 0x69, 0x60, 0xC8, 0xEA, 0x40, 0x03, 0x30, 0xEA, 0xC5, 0x00, 0xB0, 0xEC, 0x88, 0x03, 0x9D, +0x9D, 0x29, 0x54, 0xE7, 0x8A, 0x51, 0xC5, 0x00, 0x6A, 0x48, 0xE0, 0xC0, 0x03, 0xD0, 0xE0, 0x8D, +0x03, 0xD0, 0xE0, 0x8D, 0x45, 0x08, 0xF3, 0xB9, 0xAA, 0xE9, 0x54, 0xE7, 0x8A, 0x51, 0xC5, 0x00, +0x6A, 0x48, 0xE0, 0xC0, 0x06, 0x30, 0x03, 0xD0, 0xE0, 0x8D, 0xFF, 0x7E, 0x03, 0x9D, 0xA3, 0xE9, +0x45, 0x08, 0x3F, 0xB9, 0x60, 0xC4, 0xC5, 0x00, 0xDA, 0x40, 0x00, 0xB0, 0x4C, 0xE7, 0x8A, 0x51, +0xEC, 0x88, 0x03, 0x9D, 0xBD, 0x69, 0x27, 0xB0, 0x54, 0xE7, 0x8A, 0x51, 0xEA, 0x40, 0x28, 0x30, +0x54, 0xE7, 0x8A, 0x51, 0xEB, 0x80, 0x29, 0x70, 0xC6, 0xE9, 0x2C, 0x70, 0x54, 0xE7, 0x8A, 0x51, +0xEA, 0x40, 0x2D, 0xB0, 0x54, 0xE7, 0x8A, 0x51, 0xEB, 0x80, 0x2E, 0xB0, 0x54, 0xE7, 0x8A, 0x51, +0xE3, 0x40, 0x86, 0x70, 0xC8, 0xC0, 0x03, 0xD0, 0x6A, 0x8C, 0xC9, 0x00, 0x6B, 0x88, 0xE0, 0xC0, +0x03, 0xD0, 0xE0, 0x4C, 0x6A, 0x48, 0xE1, 0x00, 0x06, 0x30, 0x03, 0xD0, 0xE1, 0xCD, 0xFF, 0x7E, +0x03, 0xD0, 0x03, 0x9D, 0xD6, 0x29, 0x61, 0x8D, 0x60, 0xC4, 0xCA, 0x00, 0x63, 0x48, 0xE0, 0xC0, +0x03, 0xD0, 0xE0, 0x4C, 0x6B, 0x88, 0xE1, 0x00, 0x06, 0x30, 0x03, 0xD0, 0xE1, 0xCD, 0xFF, 0x7E, +0x03, 0xD0, 0x03, 0x9D, 0xE6, 0x29, 0x61, 0x8D, 0x60, 0xC4, 0xCB, 0x40, 0x63, 0x48, 0xE0, 0xC0, +0x06, 0x30, 0x03, 0xD0, 0xE0, 0x8D, 0xFF, 0x7E, 0x03, 0xD0, 0x03, 0x9D, 0xF2, 0x29, 0x60, 0x4D, +0xCC, 0x00, 0x48, 0x30, 0xDA, 0x40, 0x6C, 0x48, 0xDB, 0x80, 0x05, 0x30, 0x5C, 0x27, 0x8A, 0x51, +0xE7, 0xC1, 0x02, 0xF0, 0x67, 0x82, 0x03, 0x18, 0x21, 0x6A, 0xC7, 0xF0, 0xE0, 0xC0, 0x00, 0x00, +0xE0, 0x8B, 0x07, 0xAA, 0x0B, 0xAA, 0x00, 0x00, 0x1D, 0xB0, 0x83, 0x52, 0x03, 0x53, 0xC8, 0xC0, +0x08, 0xF0, 0xC9, 0x00, 0xC8, 0x70, 0xCA, 0x00, 0x48, 0x30, 0xCB, 0x81, 0xCC, 0x41, 0xDA, 0x40, +0x6C, 0x48, 0xDB, 0x80, 0x05, 0x30, 0x95, 0x27, 0x8A, 0x51, 0xC8, 0x9A, 0x21, 0x6A, 0xE7, 0x0A, +0x01, 0x2A, 0x48, 0xC8, 0x20, 0x79, 0x67, 0x84, 0xDA, 0x40, 0x1C, 0x70, 0x4C, 0xE7, 0x8A, 0x51, +0x1D, 0xB0, 0xC8, 0xC0, 0x08, 0xF0, 0xC9, 0x00, 0xDD, 0x30, 0xCA, 0x00, 0x48, 0x30, 0xCB, 0x81, +0xCC, 0x41, 0xDA, 0x40, 0x6C, 0x48, 0xDB, 0x80, 0x05, 0x30, 0x95, 0x27, 0x8A, 0x51, 0x48, 0xC8, +0xDA, 0x40, 0x1B, 0xB0, 0x4C, 0xE7, 0x8A, 0x51, 0x48, 0xC8, 0xE9, 0x40, 0x07, 0x70, 0xE9, 0xC5, +0x04, 0xF0, 0x69, 0x42, 0x03, 0x5C, 0x46, 0xAA, 0x04, 0xF0, 0xE9, 0x40, 0xEC, 0x88, 0x03, 0x9D, +0x50, 0x6A, 0x69, 0x48, 0xDA, 0x40, 0x1D, 0xB0, 0x4C, 0xE7, 0x8A, 0x51, 0x26, 0x70, 0x57, 0x2A, +0x69, 0xCE, 0xF0, 0x39, 0xDA, 0x40, 0x1D, 0xB0, 0x4C, 0xE7, 0x8A, 0x51, 0x2B, 0xB0, 0x54, 0xE7, +0x8A, 0x51, 0xEA, 0x40, 0xEA, 0x9F, 0x19, 0xEB, 0x3D, 0xF0, 0x54, 0xE7, 0x8A, 0x51, 0xEB, 0x80, +0x3E, 0xF0, 0x54, 0xE7, 0xEA, 0x40, 0x06, 0x30, 0xDA, 0x40, 0x69, 0x48, 0x8A, 0x95, 0x81, 0x22, +0x8A, 0x51, 0x01, 0xBE, 0x84, 0x80, 0x8A, 0x95, 0x00, 0x60, 0x8A, 0x51, 0xE8, 0x00, 0x06, 0x30, +0xDA, 0x40, 0x69, 0x48, 0x8A, 0x95, 0x81, 0x22, 0x8A, 0x51, 0x02, 0xBE, 0x84, 0x80, 0x8A, 0x95, +0x00, 0x60, 0x8A, 0x51, 0xE6, 0x40, 0x6B, 0x88, 0x68, 0x02, 0x03, 0x5C, 0x87, 0xEA, 0x6B, 0x88, +0x68, 0x46, 0x03, 0x9D, 0xA9, 0xEA, 0x6A, 0x48, 0x66, 0x42, 0x03, 0x18, 0xA9, 0xEA, 0x1D, 0xB0, +0xC8, 0xC0, 0x08, 0xF0, 0xC9, 0x00, 0xC1, 0x70, 0xCA, 0x00, 0x48, 0x30, 0xCB, 0x81, 0xCC, 0x41, +0xDA, 0x40, 0x6C, 0x48, 0xDB, 0x80, 0x05, 0x30, 0x95, 0x27, 0x8A, 0x51, 0x48, 0xC8, 0xEB, 0x80, +0x1F, 0x79, 0xE0, 0xB8, 0xEB, 0x80, 0x0D, 0x70, 0xC8, 0xC0, 0x08, 0xF0, 0xC9, 0x00, 0xC1, 0x70, +0xCA, 0x00, 0x6B, 0x88, 0xCB, 0x40, 0x48, 0x30, 0xDA, 0x40, 0x6C, 0x48, 0xDB, 0x80, 0x04, 0xF0, +0x5C, 0x27, 0x06, 0x30, 0xDA, 0x40, 0x69, 0x48, 0x8A, 0x95, 0x81, 0x22, 0x8A, 0x51, 0x03, 0xFE, +0x84, 0x80, 0x8A, 0x95, 0x00, 0x60, 0x8A, 0x51, 0xE8, 0x00, 0x06, 0x30, 0xDA, 0x40, 0x69, 0x48, +0x8A, 0x95, 0x81, 0x22, 0x8A, 0x51, 0x04, 0xBE, 0x84, 0x80, 0x8A, 0x95, 0x00, 0x60, 0x8A, 0x51, +0xE6, 0x40, 0x6B, 0x88, 0x68, 0x02, 0x03, 0x5C, 0xCD, 0x2A, 0x6B, 0x88, 0x68, 0x46, 0x03, 0x9D, +0xDF, 0xEB, 0x6A, 0x48, 0x66, 0x42, 0x03, 0x18, 0xDF, 0xEB, 0x06, 0x30, 0xDA, 0x40, 0x69, 0x48, +0x8A, 0x95, 0x81, 0x22, 0x8A, 0x51, 0x05, 0xFE, 0x84, 0x80, 0x8A, 0x95, 0x00, 0x60, 0x8A, 0x51, +0xE8, 0x00, 0x06, 0x30, 0xDA, 0x40, 0x69, 0x48, 0x8A, 0x95, 0x81, 0x22, 0x8A, 0x51, 0x06, 0xFE, +0x84, 0x80, 0x8A, 0x95, 0x00, 0x60, 0x8A, 0x51, 0xE6, 0x40, 0x6B, 0x88, 0x68, 0x02, 0x03, 0x5C, +0xF1, 0x2A, 0x6B, 0x88, 0x68, 0x46, 0x03, 0x9D, 0x05, 0xAB, 0x6A, 0x48, 0x66, 0x42, 0x03, 0x18, +0x05, 0xAB, 0x1D, 0xB0, 0xC8, 0xC0, 0x08, 0xF0, 0xC9, 0x00, 0xC2, 0x70, 0xCA, 0x00, 0x48, 0x30, +0xCB, 0x81, 0xCC, 0x41, 0xDA, 0x40, 0x6C, 0x48, 0xDB, 0x80, 0x05, 0x30, 0x95, 0x27, 0x8A, 0x51, +0x48, 0xC8, 0xEB, 0x80, 0xE1, 0x39, 0x0E, 0xB8, 0xCF, 0xAB, 0x1D, 0xB0, 0xC8, 0xC0, 0x08, 0xF0, +0xC9, 0x00, 0xC2, 0x70, 0xCA, 0x00, 0x48, 0x30, 0xCB, 0x81, 0xCC, 0x41, 0xDA, 0x40, 0x6C, 0x48, +0xDB, 0x80, 0x05, 0x30, 0x95, 0x27, 0x8A, 0x51, 0x48, 0xC8, 0xEB, 0x80, 0xE1, 0x39, 0x06, 0x78, +0xCF, 0xAB, 0x07, 0x70, 0xEA, 0xC5, 0x05, 0x30, 0x6A, 0x42, 0x03, 0x18, 0x29, 0xEB, 0x69, 0x48, +0x3C, 0x7E, 0x84, 0x80, 0x8A, 0x95, 0x00, 0x60, 0x8A, 0x51, 0xE4, 0x00, 0x69, 0x48, 0x2D, 0x7E, +0x44, 0xAB, 0x6A, 0x48, 0x05, 0xBA, 0x03, 0x9D, 0x37, 0x6B, 0x69, 0x48, 0x41, 0xFE, 0x84, 0x80, +0x8A, 0x95, 0x00, 0x60, 0x8A, 0x51, 0xE4, 0x00, 0x69, 0x48, 0x32, 0x3E, 0x44, 0xAB, 0x06, 0x30, +0x6A, 0x42, 0x03, 0x5C, 0x49, 0xEB, 0x69, 0x48, 0x46, 0x3E, 0x84, 0x80, 0x8A, 0x95, 0x00, 0x60, +0x8A, 0x51, 0xE4, 0x00, 0x69, 0x48, 0x37, 0xBE, 0x84, 0x80, 0x8A, 0x95, 0x00, 0x60, 0x8A, 0x51, +0xE5, 0x40, 0x0D, 0x70, 0xC8, 0xC0, 0x08, 0xF0, 0xC9, 0x00, 0x89, 0x70, 0xCA, 0x00, 0x06, 0x30, +0x6A, 0x42, 0x03, 0x5C, 0x55, 0x2B, 0x09, 0x30, 0x56, 0x2B, 0x0D, 0x70, 0xCB, 0x40, 0x48, 0x30, +0xDA, 0x40, 0x6C, 0x48, 0xDB, 0x80, 0x04, 0xF0, 0x5C, 0x27, 0x8A, 0x51, 0x06, 0x30, 0x6A, 0x42, +0x03, 0x5C, 0x85, 0xEB, 0x1D, 0xB0, 0xC8, 0xC0, 0x08, 0xF0, 0xC9, 0x00, 0xC2, 0x70, 0xCA, 0x00, +0x48, 0x30, 0xCB, 0x81, 0xCC, 0x41, 0xDA, 0x40, 0x6C, 0x48, 0xDB, 0x80, 0x05, 0x30, 0x95, 0x27, +0x8A, 0x51, 0x48, 0xC8, 0xEB, 0x80, 0x9F, 0xB9, 0x20, 0x38, 0xEB, 0x80, 0x0D, 0x70, 0xC8, 0xC0, +0x08, 0xF0, 0xC9, 0x00, 0xC2, 0x70, 0xCA, 0x00, 0x6B, 0x88, 0xCB, 0x40, 0x48, 0x30, 0xDA, 0x40, +0x6C, 0x48, 0xDB, 0x80, 0x04, 0xF0, 0x5C, 0x27, 0x8A, 0x51, 0x64, 0x08, 0x03, 0x59, 0xB4, 0x2B, +0x1D, 0xB0, 0xC8, 0xC0, 0x08, 0xF0, 0xC9, 0x00, 0xC1, 0x70, 0xCA, 0x00, 0x48, 0x30, 0xCB, 0x81, +0xCC, 0x41, 0xDA, 0x40, 0x6C, 0x48, 0xDB, 0x80, 0x05, 0x30, 0x95, 0x27, 0x8A, 0x51, 0x48, 0xC8, +0xEB, 0x80, 0x64, 0x08, 0xE0, 0xC0, 0x05, 0x30, 0x03, 0xD0, 0xE0, 0x8D, 0xFF, 0x7E, 0x03, 0x9D, +0x9C, 0x2B, 0x6B, 0x88, 0x1F, 0x79, 0x60, 0xC4, 0xEB, 0x80, 0x0D, 0x70, 0xC8, 0xC0, 0x08, 0xF0, +0xC9, 0x00, 0xC1, 0x70, 0xCA, 0x00, 0x6B, 0x88, 0xCB, 0x40, 0x48, 0x30, 0xDA, 0x40, 0x6C, 0x48, +0xDB, 0x80, 0x04, 0xF0, 0x5C, 0x27, 0x8A, 0x51, 0x65, 0x48, 0x03, 0x59, 0xDF, 0xEB, 0x1D, 0xB0, +0xC8, 0xC0, 0x08, 0xF0, 0xC9, 0x00, 0xC2, 0x70, 0xCA, 0x00, 0x48, 0x30, 0xCB, 0x81, 0xCC, 0x41, +0xDA, 0x40, 0x6C, 0x48, 0xDB, 0x80, 0x05, 0x30, 0x95, 0x27, 0x8A, 0x51, 0x48, 0xC8, 0xEB, 0x80, +0x65, 0x48, 0xE0, 0xC0, 0x03, 0xD0, 0xE0, 0x8D, 0x6B, 0x88, 0xE1, 0x39, 0x60, 0xC4, 0xEB, 0x80, +0x0D, 0x70, 0xC8, 0xC0, 0x08, 0xF0, 0xC9, 0x00, 0xC2, 0x70, 0xCA, 0x00, 0x6B, 0x88, 0xCB, 0x40, +0x48, 0x30, 0xDA, 0x40, 0x6C, 0x48, 0xDB, 0x80, 0x04, 0xF0, 0x5C, 0x27, 0x8A, 0x51, 0x5F, 0xC8, +0x03, 0x9D, 0x9D, 0x5B, 0xE4, 0x2B, 0xA4, 0xAC, 0xE2, 0x41, 0xE2, 0x8A, 0x6C, 0xCB, 0xEE, 0xAB, +0x00, 0xB0, 0x54, 0xE7, 0x8A, 0x51, 0xC5, 0x00, 0x45, 0x52, 0xF3, 0xAB, 0x00, 0xB0, 0x54, 0xE7, +0x8A, 0x51, 0xC5, 0x00, 0x45, 0x10, 0x45, 0x08, 0xDA, 0x40, 0x00, 0xB0, 0x4C, 0xE7, 0x8A, 0x51, +0x05, 0x30, 0xE0, 0xC0, 0xE0, 0x8B, 0xFA, 0xAB, 0x00, 0xB0, 0x54, 0xE7, 0x8A, 0x51, 0xC5, 0x00, +0xFD, 0xF9, 0x02, 0x38, 0xC5, 0x00, 0xDA, 0x40, 0x00, 0xB0, 0x4C, 0xE7, 0x8A, 0x51, 0x00, 0xB0, +0x54, 0xE7, 0x8A, 0x51, 0xC5, 0x00, 0xDF, 0xF9, 0x20, 0x38, 0xC5, 0x00, 0xDA, 0x40, 0x00, 0xB0, +0x4C, 0xE7, 0x8A, 0x51, 0xE7, 0xC1, 0x40, 0x88, 0x67, 0x82, 0x03, 0x18, 0x23, 0xAC, 0xE4, 0xB0, +0xE0, 0xC0, 0x1A, 0xAC, 0x1B, 0xEC, 0xE0, 0x8B, 0x19, 0xAC, 0x1E, 0xEC, 0x00, 0x00, 0x83, 0x52, +0x03, 0x53, 0xE7, 0x0A, 0x13, 0xAC, 0x0D, 0x70, 0xC8, 0xC0, 0x05, 0x30, 0xC9, 0x00, 0x84, 0x30, +0xCA, 0x00, 0x48, 0x30, 0xCB, 0x81, 0xDA, 0x40, 0x6C, 0x48, 0xDB, 0x80, 0x04, 0xF0, 0x5C, 0x27, +0x8A, 0x51, 0x0D, 0x70, 0xC8, 0xC0, 0x05, 0x30, 0xC9, 0x00, 0x85, 0x70, 0xCA, 0x00, 0x48, 0x30, +0xCB, 0x81, 0xDA, 0x40, 0x6C, 0x48, 0xDB, 0x80, 0x04, 0xF0, 0x5C, 0x27, 0x8A, 0x51, 0x0D, 0x70, +0xC8, 0xC0, 0x05, 0x30, 0xC9, 0x00, 0x82, 0x30, 0xCA, 0x00, 0x3F, 0x30, 0xCB, 0x40, 0x48, 0x30, +0xDA, 0x40, 0x6C, 0x48, 0xDB, 0x80, 0x04, 0xF0, 0x5C, 0x27, 0x8A, 0x51, 0x0D, 0x70, 0xC8, 0xC0, +0x05, 0x30, 0xC9, 0x00, 0x80, 0xF0, 0xCA, 0x00, 0x3E, 0xF0, 0xCB, 0x40, 0x48, 0x30, 0xDA, 0x40, +0x6C, 0x48, 0xDB, 0x80, 0x04, 0xF0, 0x5C, 0x27, 0x8A, 0x51, 0x0D, 0x70, 0xC8, 0xC0, 0x05, 0x30, +0xC9, 0x00, 0x83, 0x70, 0xCA, 0x00, 0x3E, 0xF0, 0xCB, 0x40, 0x48, 0x30, 0xDA, 0x40, 0x6C, 0x48, +0xDB, 0x80, 0x04, 0xF0, 0x5C, 0x27, 0x8A, 0x51, 0x6C, 0x83, 0x03, 0x9D, 0x71, 0xEC, 0x9C, 0xD5, +0x72, 0xEC, 0x1C, 0x95, 0x05, 0x30, 0xE0, 0xC0, 0xE0, 0x8B, 0x74, 0xEC, 0x0D, 0x70, 0x83, 0x52, +0x03, 0x53, 0xC8, 0xC0, 0x05, 0x30, 0xC9, 0x00, 0x87, 0xB0, 0xCA, 0x00, 0x09, 0x30, 0xCB, 0x40, +0x48, 0x30, 0xDA, 0x40, 0x6C, 0x48, 0xDB, 0x80, 0x04, 0xF0, 0x5C, 0x27, 0x8A, 0x51, 0xE4, 0xB0, +0xE0, 0xC0, 0x8A, 0xAC, 0x8B, 0xEC, 0xE0, 0x8B, 0x89, 0xAC, 0x8E, 0xEC, 0x00, 0x00, 0xD1, 0xB0, +0x83, 0x52, 0x03, 0x53, 0xC8, 0xC0, 0x48, 0x30, 0xDA, 0x40, 0x6C, 0x48, 0xDB, 0x80, 0x01, 0xF0, +0x5C, 0x27, 0x8A, 0x51, 0x04, 0xF0, 0xE1, 0x00, 0x1C, 0x70, 0xE0, 0xC0, 0xE0, 0x8B, 0x9E, 0x2C, +0xE1, 0xCB, 0x9E, 0x2C, 0x00, 0x00, 0xA5, 0xEC, 0xE2, 0x41, 0x83, 0x52, 0x03, 0x53, 0x62, 0x08, +0x08, 0x40, 0x55, 0xB0, 0x83, 0x52, 0x9B, 0x40, 0xC4, 0x01, 0x02, 0xF0, 0x44, 0xC2, 0x03, 0x18, +0x6D, 0x6D, 0x44, 0xC8, 0x01, 0xBE, 0x9B, 0x40, 0x44, 0xC8, 0xB4, 0x7E, 0x84, 0x80, 0x83, 0x93, +0x80, 0x81, 0x44, 0xC8, 0xB4, 0x7E, 0x84, 0x80, 0x80, 0x88, 0x03, 0x9D, 0xE5, 0x2C, 0x3C, 0xB0, +0x54, 0xE7, 0x8A, 0x51, 0xC5, 0x00, 0x44, 0x18, 0xD1, 0xEC, 0x44, 0xC8, 0xB4, 0x7E, 0x84, 0x80, +0x45, 0x08, 0xDC, 0x40, 0x07, 0x70, 0x03, 0xD0, 0xDC, 0xCC, 0xFF, 0x7E, 0x03, 0x9D, 0xCB, 0x2C, +0xDC, 0x2C, 0x44, 0xC8, 0xB4, 0x7E, 0x84, 0x80, 0x45, 0x08, 0xDC, 0x40, 0x05, 0x30, 0x03, 0xD0, +0xDC, 0xCC, 0xFF, 0x7E, 0x03, 0x9D, 0xD7, 0x6C, 0x5C, 0x48, 0x83, 0x93, 0x80, 0x40, 0x44, 0xC8, +0xB4, 0x7E, 0x84, 0x80, 0x01, 0xF0, 0x80, 0xC5, 0xB9, 0x2C, 0x3E, 0xF0, 0x54, 0xE7, 0x8A, 0x51, +0xC5, 0x00, 0x45, 0x08, 0x03, 0x59, 0x05, 0xAD, 0x10, 0xF0, 0x45, 0x02, 0x03, 0x18, 0x05, 0xAD, +0x44, 0xC8, 0xB0, 0x3E, 0x84, 0x80, 0x45, 0x08, 0x83, 0x93, 0x80, 0x40, 0x44, 0xC8, 0xB0, 0x3E, +0x84, 0x80, 0x00, 0x48, 0xDA, 0x40, 0x1B, 0xB0, 0x4C, 0xE7, 0x8A, 0x51, 0x44, 0xC8, 0xAE, 0xBE, +0x84, 0x80, 0x83, 0x93, 0x80, 0x81, 0x80, 0xCA, 0x24, 0xAD, 0x45, 0x08, 0xFF, 0x3A, 0x03, 0x9D, +0x11, 0xAD, 0x44, 0xC8, 0xB0, 0x3E, 0x84, 0x80, 0x04, 0xF0, 0x83, 0x93, 0x80, 0x40, 0xFF, 0xB0, +0x1B, 0x2D, 0x44, 0xC8, 0xB0, 0x3E, 0x84, 0x80, 0x04, 0xF0, 0x83, 0x93, 0x80, 0x40, 0x44, 0xC8, +0xB0, 0x3E, 0x84, 0x80, 0x00, 0x48, 0xDA, 0x40, 0x1B, 0xB0, 0x4C, 0xE7, 0x8A, 0x51, 0x44, 0xC8, +0xAE, 0xBE, 0x84, 0x80, 0x83, 0x93, 0x80, 0x81, 0x3F, 0x30, 0x54, 0xE7, 0x8A, 0x51, 0xC5, 0x00, +0x45, 0x08, 0x03, 0x59, 0x44, 0xAD, 0x0E, 0x70, 0x45, 0x02, 0x03, 0x18, 0x44, 0xAD, 0x44, 0xC8, +0xAA, 0x7E, 0x84, 0x80, 0x45, 0x08, 0x83, 0x93, 0x80, 0x40, 0x44, 0xC8, 0xAA, 0x7E, 0x84, 0x80, +0x00, 0x48, 0xDA, 0x40, 0x1C, 0x70, 0x4C, 0xE7, 0x8A, 0x51, 0x44, 0xC8, 0xA8, 0x3E, 0x84, 0x80, +0x83, 0x93, 0x80, 0x81, 0x80, 0xCA, 0x63, 0x2D, 0x45, 0x08, 0xFF, 0x3A, 0x03, 0x9D, 0x50, 0xAD, +0x44, 0xC8, 0xAA, 0x7E, 0x84, 0x80, 0x0D, 0x70, 0x83, 0x93, 0x80, 0x40, 0xFF, 0xB0, 0x5A, 0x2D, +0x44, 0xC8, 0xAA, 0x7E, 0x84, 0x80, 0x0D, 0x70, 0x83, 0x93, 0x80, 0x40, 0x44, 0xC8, 0xAA, 0x7E, +0x84, 0x80, 0x00, 0x48, 0xDA, 0x40, 0x1C, 0x70, 0x4C, 0xE7, 0x8A, 0x51, 0x44, 0xC8, 0xA8, 0x3E, +0x84, 0x80, 0x83, 0x93, 0x80, 0x81, 0x44, 0xC8, 0xB2, 0x7E, 0x84, 0x80, 0x00, 0x48, 0xDA, 0x40, +0x1A, 0x70, 0x4C, 0xE7, 0x8A, 0x51, 0xC4, 0x4A, 0xAD, 0x2C, 0x03, 0x30, 0x9B, 0x40, 0x3C, 0xB0, +0x54, 0xE7, 0x8A, 0x51, 0xC5, 0x00, 0x03, 0xBA, 0x03, 0x9D, 0x6F, 0xAD, 0x3E, 0xF0, 0x54, 0xE7, +0x8A, 0x51, 0xA8, 0xC0, 0x3F, 0x30, 0x54, 0xE7, 0x8A, 0x51, 0xAA, 0x00, 0x28, 0xC2, 0x03, 0x5C, +0x97, 0x6D, 0x0E, 0x70, 0x28, 0xC2, 0x03, 0x18, 0x97, 0x6D, 0x2A, 0x08, 0x03, 0x59, 0x97, 0x6D, +0x28, 0xC8, 0xDA, 0x40, 0x1B, 0xB0, 0x4C, 0xE7, 0x8A, 0x51, 0x2A, 0x08, 0xDA, 0x40, 0x1C, 0x70, +0x4C, 0xE7, 0x8A, 0x51, 0x28, 0xC8, 0xA7, 0x40, 0x2A, 0x08, 0xA9, 0x00, 0xA5, 0x2D, 0x0D, 0x70, +0xDA, 0x40, 0x1B, 0xB0, 0x4C, 0xE7, 0x8A, 0x51, 0x1C, 0x70, 0xDA, 0x81, 0xDA, 0xCA, 0x4C, 0xE7, +0x8A, 0x51, 0x0D, 0x70, 0xA7, 0x40, 0xA9, 0x41, 0xA9, 0x8A, 0x04, 0xF0, 0x9B, 0x40, 0x3C, 0xB0, +0x54, 0xE7, 0x8A, 0x51, 0xC5, 0x00, 0x04, 0x7A, 0x03, 0x9D, 0xA7, 0x6D, 0x3E, 0xF0, 0x54, 0xE7, +0x8A, 0x51, 0xAC, 0x00, 0x3F, 0x30, 0x54, 0xE7, 0x8A, 0x51, 0xAE, 0x40, 0x2C, 0x02, 0x03, 0x5C, +0xCB, 0x6D, 0x02, 0xF0, 0x2E, 0x42, 0x03, 0x5C, 0xCB, 0x6D, 0x2C, 0x08, 0xDA, 0x40, 0x1B, 0xB0, +0x4C, 0xE7, 0x8A, 0x51, 0x2E, 0x48, 0xDA, 0x40, 0x1C, 0x70, 0x4C, 0xE7, 0x8A, 0x51, 0x2C, 0x08, +0xAB, 0x40, 0x2E, 0x48, 0xD8, 0x2D, 0x0C, 0x30, 0xDA, 0x40, 0x1B, 0xB0, 0x4C, 0xE7, 0x8A, 0x51, +0x03, 0x30, 0xDA, 0x40, 0x1C, 0x70, 0x4C, 0xE7, 0x8A, 0x51, 0x0C, 0x30, 0xAB, 0x40, 0x03, 0x30, +0xAD, 0x40, 0x05, 0x30, 0x9B, 0x40, 0x3C, 0xB0, 0x54, 0xE7, 0x8A, 0x51, 0xC5, 0x00, 0x05, 0xBA, +0x03, 0x9D, 0xDB, 0xAD, 0x3E, 0xF0, 0x54, 0xE7, 0x8A, 0x51, 0xB4, 0x00, 0x3F, 0x30, 0x54, 0xE7, +0x8A, 0x51, 0xB6, 0x40, 0x34, 0x02, 0x03, 0x5C, 0x02, 0x6E, 0x10, 0xF0, 0x34, 0x02, 0x03, 0x18, +0x02, 0x6E, 0x36, 0x48, 0x03, 0x59, 0x02, 0x6E, 0x34, 0x08, 0xDA, 0x40, 0x1B, 0xB0, 0x4C, 0xE7, +0x8A, 0x51, 0x36, 0x48, 0xDA, 0x40, 0x1C, 0x70, 0x4C, 0xE7, 0x8A, 0x51, 0x34, 0x08, 0xB3, 0x40, +0x36, 0x48, 0x0F, 0x2E, 0x0F, 0xB0, 0xDA, 0x40, 0x1B, 0xB0, 0x4C, 0xE7, 0x8A, 0x51, 0x04, 0xF0, +0xDA, 0x40, 0x1C, 0x70, 0x4C, 0xE7, 0x8A, 0x51, 0x0F, 0xB0, 0xB3, 0x40, 0x04, 0xF0, 0xB5, 0x40, +0x06, 0x30, 0x9B, 0x40, 0x3C, 0xB0, 0x54, 0xE7, 0x8A, 0x51, 0xC5, 0x00, 0x06, 0xBA, 0x03, 0x9D, +0x12, 0xAE, 0x3D, 0xF0, 0x54, 0xE7, 0x8A, 0x51, 0xB9, 0x40, 0x3E, 0xF0, 0x54, 0xE7, 0x8A, 0x51, +0xB8, 0x00, 0x3F, 0x30, 0x54, 0xE7, 0x8A, 0x51, 0xBB, 0x80, 0x38, 0x08, 0x39, 0x42, 0x03, 0x5C, +0x47, 0x2E, 0x3B, 0x88, 0x38, 0x02, 0x03, 0x5C, 0x47, 0x2E, 0x3B, 0x88, 0x03, 0x59, 0x47, 0x2E, +0x39, 0x48, 0xDA, 0x40, 0x1A, 0x70, 0x4C, 0xE7, 0x8A, 0x51, 0x38, 0x08, 0xDA, 0x40, 0x1B, 0xB0, +0x4C, 0xE7, 0x8A, 0x51, 0x3B, 0x88, 0xDA, 0x40, 0x1C, 0x70, 0x4C, 0xE7, 0x8A, 0x51, 0x39, 0x48, +0x83, 0x96, 0xBA, 0x40, 0x83, 0x52, 0x38, 0x08, 0xB7, 0x80, 0x3B, 0x88, 0x5D, 0x6E, 0x5A, 0xB0, +0xDA, 0x40, 0x1A, 0x70, 0x4C, 0xE7, 0x8A, 0x51, 0x50, 0x30, 0xDA, 0x40, 0x1B, 0xB0, 0x4C, 0xE7, +0x8A, 0x51, 0x28, 0x30, 0xDA, 0x40, 0x1C, 0x70, 0x4C, 0xE7, 0x8A, 0x51, 0x5A, 0xB0, 0x83, 0x96, +0xBA, 0x40, 0x50, 0x30, 0x83, 0x52, 0xB7, 0x80, 0x28, 0x30, 0xBA, 0x40, 0x07, 0x70, 0x9B, 0x40, +0x3C, 0xB0, 0x54, 0xE7, 0x8A, 0x51, 0xC5, 0x00, 0x07, 0xFA, 0x03, 0x9D, 0x60, 0xAE, 0x3E, 0xF0, +0x54, 0xE7, 0x8A, 0x51, 0xAF, 0x80, 0x3F, 0x30, 0x54, 0xE7, 0x8A, 0x51, 0xB0, 0xC0, 0x2F, 0x82, +0x03, 0x5C, 0x8A, 0xEE, 0x10, 0xF0, 0x2F, 0x82, 0x03, 0x18, 0x8A, 0xEE, 0x30, 0xC8, 0x03, 0x59, +0x8A, 0xEE, 0x2F, 0x88, 0xDA, 0x40, 0x1B, 0xB0, 0x4C, 0xE7, 0x8A, 0x51, 0x30, 0xC8, 0xDA, 0x40, +0x1C, 0x70, 0x4C, 0xE7, 0x8A, 0x51, 0x2F, 0x88, 0x83, 0x96, 0xB6, 0x40, 0x83, 0x52, 0x30, 0xC8, +0x83, 0x96, 0x98, 0xEE, 0x0E, 0x70, 0xDA, 0x40, 0x1B, 0xB0, 0x4C, 0xE7, 0x8A, 0x51, 0x04, 0xF0, +0xDA, 0x40, 0x1C, 0x70, 0x4C, 0xE7, 0x8A, 0x51, 0x0E, 0x70, 0x83, 0x96, 0xB6, 0x40, 0x04, 0xF0, +0xB7, 0x80, 0x08, 0xF0, 0x83, 0x52, 0x9B, 0x40, 0x3C, 0xB0, 0x54, 0xE7, 0x8A, 0x51, 0xC5, 0x00, +0x08, 0x7A, 0x03, 0x9D, 0x9C, 0x2E, 0x3E, 0xF0, 0x54, 0xE7, 0x8A, 0x51, 0xB1, 0x00, 0x3F, 0x30, +0x54, 0xE7, 0x8A, 0x51, 0xB2, 0x00, 0x31, 0x08, 0x32, 0x02, 0x03, 0x18, 0xC4, 0xEE, 0x02, 0xF0, +0x32, 0x02, 0x03, 0x5C, 0xC4, 0xEE, 0x31, 0x08, 0xDA, 0x40, 0x1B, 0xB0, 0x4C, 0xE7, 0x8A, 0x51, +0x32, 0x08, 0xDA, 0x40, 0x1C, 0x70, 0x4C, 0xE7, 0x8A, 0x51, 0x31, 0x08, 0x83, 0x96, 0xB8, 0x00, +0x83, 0x52, 0x32, 0x08, 0x83, 0x96, 0xD2, 0x2E, 0x84, 0x30, 0xDA, 0x40, 0x1B, 0xB0, 0x4C, 0xE7, +0x8A, 0x51, 0x34, 0x70, 0xDA, 0x40, 0x1C, 0x70, 0x4C, 0xE7, 0x8A, 0x51, 0x84, 0x30, 0x83, 0x96, +0xB8, 0x00, 0x34, 0x70, 0xB9, 0x40, 0x09, 0x30, 0x83, 0x52, 0x9B, 0x40, 0x3C, 0xB0, 0x54, 0xE7, +0x8A, 0x51, 0xC5, 0x00, 0x09, 0xBA, 0x03, 0x9D, 0xD6, 0x6E, 0x3D, 0xF0, 0x54, 0xE7, 0x8A, 0x51, +0xBF, 0xC0, 0x3E, 0xF0, 0x54, 0xE7, 0x8A, 0x51, 0xBE, 0x80, 0x3F, 0x30, 0x54, 0xE7, 0x8A, 0x51, +0xBC, 0x40, 0x07, 0x70, 0x3F, 0xC2, 0x03, 0x18, 0x05, 0xEF, 0x08, 0xF0, 0x3E, 0x82, 0x03, 0x18, +0x05, 0xEF, 0x02, 0xF0, 0x3C, 0x42, 0x03, 0x18, 0x05, 0xEF, 0x3F, 0xC8, 0xDA, 0x40, 0x1A, 0x70, +0x4C, 0xE7, 0x8A, 0x51, 0x3E, 0x88, 0xDA, 0x40, 0x1B, 0xB0, 0x4C, 0xE7, 0x8A, 0x51, 0x3C, 0x48, +0xDA, 0x40, 0x1C, 0x70, 0x4C, 0xE7, 0x8A, 0x51, 0x18, 0xEF, 0x1A, 0x70, 0xDA, 0x81, 0xDA, 0xCA, +0x4C, 0xE7, 0x8A, 0x51, 0x02, 0xF0, 0xDA, 0x40, 0x1B, 0xB0, 0x4C, 0xE7, 0x8A, 0x51, 0x1C, 0x70, +0xDA, 0x81, 0x4C, 0xE7, 0x8A, 0x51, 0x02, 0xF0, 0xBF, 0x01, 0xBF, 0x4A, 0xBE, 0x80, 0xBC, 0x81, +0x0A, 0x30, 0x9B, 0x40, 0x3C, 0xB0, 0x54, 0xE7, 0x8A, 0x51, 0xC5, 0x00, 0x0A, 0xBA, 0x03, 0x9D, +0x1A, 0x2F, 0x3F, 0x30, 0x54, 0xE7, 0x8A, 0x51, 0xC0, 0x80, 0xDA, 0x40, 0x1C, 0x70, 0x4C, 0xE7, +0x8A, 0x51, 0x0B, 0x70, 0x9B, 0x40, 0x3C, 0xB0, 0x54, 0xE7, 0x8A, 0x51, 0xC5, 0x00, 0x0B, 0xFA, +0x03, 0x9D, 0x2B, 0x6F, 0x3F, 0x30, 0x54, 0xE7, 0x8A, 0x51, 0xBD, 0x80, 0x3D, 0x88, 0x03, 0x59, +0x3D, 0xAF, 0x3D, 0x0B, 0x3C, 0x6F, 0x3D, 0xAF, 0xBD, 0xC1, 0x3D, 0x88, 0xDA, 0x40, 0x1C, 0x70, +0x4C, 0xE7, 0x8A, 0x51, 0x0F, 0xB0, 0x9B, 0x40, 0x3C, 0xB0, 0x54, 0xE7, 0x8A, 0x51, 0xC5, 0x00, +0x0F, 0x3A, 0x03, 0x59, 0x08, 0x40, 0x44, 0xEF, 0xDB, 0x80, 0x5A, 0x48, 0x96, 0x00, 0x5B, 0x88, +0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x08, 0x40, 0x83, 0x52, 0x03, 0x53, 0xDA, 0x40, 0x95, 0x00, +0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0x08, 0x40, 0xDD, 0x80, 0x5B, 0xD8, 0x7A, 0xAF, 0xDE, 0xC1, +0x5D, 0x88, 0x5E, 0x82, 0x03, 0x18, 0x6F, 0xEF, 0x5E, 0x88, 0x5A, 0xC7, 0xDC, 0x40, 0x84, 0x80, +0x83, 0x93, 0x00, 0x48, 0x99, 0x00, 0x18, 0x14, 0x18, 0xD0, 0xDE, 0x0A, 0x60, 0xEF, 0x18, 0x55, +0x18, 0x11, 0x83, 0x52, 0x03, 0x53, 0x11, 0x5C, 0x08, 0x40, 0x05, 0x30, 0xDC, 0x40, 0xDC, 0x0B, +0x77, 0xEF, 0x71, 0x6F, 0xDE, 0xC1, 0x5D, 0x88, 0x5E, 0x82, 0x03, 0x18, 0x8A, 0x2F, 0x5E, 0x88, +0x5A, 0xC7, 0xDC, 0x40, 0x84, 0x80, 0x83, 0x93, 0x00, 0x48, 0x9A, 0x00, 0x98, 0x95, 0x98, 0x51, +0xDE, 0x0A, 0x7B, 0xEF, 0x98, 0x96, 0x98, 0x52, 0x83, 0x52, 0x03, 0x53, 0x91, 0x9C, 0x08, 0x40, +0x05, 0x30, 0xDC, 0x40, 0xDC, 0x0B, 0x92, 0x2F, 0x8C, 0x2F, 0xDD, 0x80, 0x5B, 0xD8, 0xC4, 0x2F, +0xDE, 0xC1, 0x5D, 0x88, 0x5E, 0x82, 0x03, 0x18, 0xA8, 0x2F, 0x5E, 0x88, 0x5A, 0xC7, 0xDC, 0x40, +0x84, 0x80, 0x83, 0x93, 0x00, 0x48, 0x99, 0x00, 0x18, 0x14, 0x18, 0xD0, 0xDE, 0x0A, 0x99, 0x6F, +0x18, 0x55, 0x18, 0x11, 0x83, 0x52, 0x03, 0x53, 0x11, 0x5C, 0xB4, 0x6F, 0x2A, 0x70, 0xDC, 0x40, +0xDC, 0x0B, 0xB0, 0x2F, 0x00, 0x00, 0xAA, 0x6F, 0xDE, 0xC1, 0x5D, 0x88, 0x5E, 0x82, 0x03, 0x18, +0x08, 0x40, 0x5E, 0x88, 0x5A, 0xC7, 0xDC, 0x40, 0x84, 0x80, 0x0F, 0x48, 0x83, 0x93, 0x80, 0x40, +0x98, 0x54, 0x98, 0x10, 0xDE, 0x0A, 0xB5, 0xAF, 0xDE, 0xC1, 0x5D, 0x88, 0x5E, 0x82, 0x03, 0x18, +0xD4, 0x6F, 0x5E, 0x88, 0x5A, 0xC7, 0xDC, 0x40, 0x84, 0x80, 0x83, 0x93, 0x00, 0x48, 0x9A, 0x00, +0x98, 0x95, 0x98, 0x51, 0xDE, 0x0A, 0xC5, 0x6F, 0x98, 0x96, 0x98, 0x52, 0x83, 0x52, 0x03, 0x53, +0x91, 0x9C, 0xE0, 0x2F, 0x2A, 0x70, 0xDC, 0x40, 0xDC, 0x0B, 0xDC, 0xAF, 0x00, 0x00, 0xD6, 0xAF, +0xDE, 0xC1, 0x5D, 0x88, 0x5E, 0x82, 0x03, 0x18, 0x08, 0x40, 0x5E, 0x88, 0x5A, 0xC7, 0xDC, 0x40, +0x84, 0x80, 0x10, 0x88, 0x83, 0x93, 0x80, 0x40, 0x18, 0x56, 0x18, 0x12, 0xDE, 0x0A, 0xE1, 0x6F, +0xEF, 0x01, 0x83, 0x93, 0x21, 0x30, 0x84, 0x80, 0x5A, 0xB0, 0x8A, 0x95, 0x79, 0xE2, 0x8A, 0x51, +0xA0, 0x30, 0x84, 0x80, 0xC8, 0x70, 0x8A, 0x95, 0x79, 0xE2, 0x83, 0x01, 0x8A, 0x95, 0x34, 0xEB, +0x08, 0xF0, 0x8A, 0xC0, 0x04, 0x88, 0x84, 0x0A, 0x82, 0x47, 0x00, 0xF4, 0x01, 0x34, 0x48, 0x74, +0x02, 0x34, 0x3D, 0x34, 0x03, 0x74, 0x33, 0xF4, 0x00, 0xF4, 0xA4, 0xB4, 0x00, 0xF4, 0xF6, 0x74, +0x03, 0x74, 0x33, 0xF4, 0x00, 0xF4, 0x52, 0xB4, 0x00, 0xF4, 0x52, 0xB4, 0x00, 0xF4, 0xCD, 0x34, +0x00, 0xF4, 0x29, 0xB4, 0x00, 0xF4, 0x29, 0xB4, 0x00, 0xF4, 0x66, 0xF4, 0x00, 0xF4, 0x15, 0xB4, +0x00, 0xF4, 0x15, 0xB4, 0x00, 0xF4, 0x29, 0xB4, 0x7F, 0xB4, 0x34, 0xB4, 0x2F, 0x34, 0x2A, 0xB4, +0x26, 0xB4, 0x21, 0x74, 0x1A, 0xB4, 0x16, 0xB4, 0x0F, 0xF4, 0x09, 0x74, 0x04, 0x34, 0x02, 0x34, +0x01, 0x34, 0x00, 0xF4, 0x00, 0xF4, 0x00, 0xF4, 0x00, 0xF4, 0x03, 0x74, 0x03, 0x74, 0x00, 0xF4, +0x00, 0xF4, 0x03, 0x74, 0x03, 0x74, 0x07, 0xB4, 0x01, 0x34, 0x01, 0x34, 0x01, 0x34, 0x07, 0xB4, +0x07, 0xB4, 0x00, 0xF4, 0x00, 0xF4, 0x07, 0xB4, 0x07, 0xB4, 0x07, 0xB4, 0x00, 0xF4, 0x07, 0xB4, +0x07, 0xB4, 0x07, 0xB4, 0x07, 0xB4, 0x07, 0xB4, 0x07, 0xB4, 0x07, 0xB4, 0x07, 0xB4, 0x07, 0xB4, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0x64, 0xC0, 0x80, 0x81, 0x84, 0x0A, 0x04, 0xC6, 0x03, 0x59, 0x00, 0xF4, 0x04, 0xC6, +0x7A, 0x2A, 0xDC, 0x40, 0xDB, 0xC1, 0x5A, 0x48, 0x5C, 0x98, 0xDB, 0x47, 0x03, 0xD0, 0xDA, 0x0D, +0x5A, 0x48, 0xDC, 0xD8, 0xDB, 0x47, 0x03, 0xD0, 0xDA, 0x0D, 0x5A, 0x48, 0x5C, 0xD9, 0xDB, 0x47, +0x03, 0xD0, 0xDA, 0x0D, 0x5A, 0x48, 0xDC, 0x19, 0xDB, 0x47, 0x03, 0xD0, 0xDA, 0x0D, 0x5A, 0x48, +0x5C, 0xDA, 0xDB, 0x47, 0x03, 0xD0, 0xDA, 0x0D, 0x5A, 0x48, 0xDC, 0x1A, 0xDB, 0x47, 0x03, 0xD0, +0xDA, 0x0D, 0x5A, 0x48, 0x5C, 0x1B, 0xDB, 0x47, 0x03, 0xD0, 0xDA, 0x0D, 0x5A, 0x48, 0xDC, 0x5B, +0xDB, 0x47, 0x5B, 0x88, 0x08, 0x40, 0x1D, 0xB0, 0xC8, 0xC0, 0x08, 0xF0, 0xC9, 0x00, 0x8B, 0xB0, +0xCA, 0x00, 0x48, 0x30, 0xCB, 0x81, 0xCC, 0x41, 0xCD, 0x81, 0xCE, 0x81, 0xCF, 0xC1, 0xDA, 0x40, +0x08, 0xF0, 0xDB, 0xC1, 0x8A, 0x51, 0x95, 0x27, 0x8A, 0x95, 0x4B, 0x48, 0x83, 0x96, 0xBE, 0x80, +0x83, 0x52, 0x4A, 0x08, 0x83, 0x96, 0xBD, 0x80, 0x83, 0x52, 0x49, 0x08, 0x83, 0x96, 0xBC, 0x40, +0x83, 0x52, 0x48, 0xC8, 0x83, 0x96, 0xBB, 0x80, 0x1D, 0xB0, 0x83, 0x52, 0xC8, 0xC0, 0x08, 0xF0, +0xC9, 0x00, 0x8B, 0xB0, 0xCA, 0x00, 0x48, 0x30, 0xCB, 0x81, 0xCC, 0x41, 0xCD, 0x81, 0xCE, 0x81, +0xCF, 0xC1, 0xDA, 0x40, 0x08, 0xF0, 0xDB, 0xC1, 0xDB, 0x0A, 0x8A, 0x51, 0x95, 0x27, 0x4B, 0x48, +0x83, 0x96, 0xC2, 0xC0, 0x83, 0x52, 0x4A, 0x08, 0x83, 0x96, 0xC1, 0xC0, 0x83, 0x52, 0x49, 0x08, +0x83, 0x96, 0xC0, 0x80, 0x83, 0x52, 0x48, 0xC8, 0x83, 0x96, 0xBF, 0xC0, 0x08, 0x40, 0xC4, 0x01, +0x02, 0xF0, 0x44, 0xC2, 0x03, 0x18, 0x0F, 0x2B, 0x44, 0xC8, 0xB2, 0x7E, 0x84, 0x80, 0x83, 0x93, +0x80, 0x81, 0x44, 0xC8, 0xB0, 0x3E, 0x84, 0x80, 0x80, 0x81, 0x44, 0xC8, 0xAA, 0x7E, 0x84, 0x80, +0x80, 0x81, 0x44, 0xC8, 0xB4, 0x7E, 0x84, 0x80, 0x80, 0x81, 0x44, 0xC8, 0xA8, 0x3E, 0x84, 0x80, +0x80, 0x81, 0x44, 0xC8, 0xAE, 0xBE, 0x84, 0x80, 0x80, 0x81, 0xC4, 0x4A, 0xF0, 0xEA, 0x0D, 0x70, +0xA9, 0x41, 0xA9, 0x8A, 0xA7, 0x40, 0x03, 0x30, 0xAD, 0x40, 0x0C, 0x30, 0xAB, 0x40, 0x04, 0xF0, +0xB5, 0x40, 0x0F, 0xB0, 0xB3, 0x40, 0x28, 0x30, 0xBA, 0x40, 0x50, 0x30, 0xB7, 0x80, 0x5A, 0xB0, +0x83, 0x96, 0xBA, 0x40, 0x04, 0xF0, 0xB7, 0x80, 0x0E, 0x70, 0xB6, 0x40, 0x34, 0x70, 0xB9, 0x40, +0x84, 0x30, 0xB8, 0x00, 0x83, 0x52, 0x02, 0xF0, 0xBF, 0x01, 0xBF, 0x4A, 0xBE, 0x80, 0x0A, 0x30, +0xBC, 0x81, 0xC0, 0x80, 0xBD, 0xC1, 0x08, 0x40, 0x9B, 0x81, 0x0A, 0x30, 0xA0, 0x80, 0x95, 0x41, +0x96, 0x41, 0x97, 0x81, 0x98, 0x01, 0x99, 0x41, 0x9A, 0x41, 0x9B, 0x81, 0x9C, 0x41, 0x9E, 0x81, +0x9B, 0x81, 0xDA, 0x40, 0x1C, 0x70, 0x8A, 0x51, 0x4C, 0xE7, 0x8A, 0x95, 0x8A, 0x95, 0xEF, 0x62, +0x8A, 0x95, 0x3D, 0xF0, 0x8A, 0x51, 0x54, 0xE7, 0x8A, 0x95, 0xC5, 0x00, 0xF7, 0xFA, 0x03, 0x9D, +0x54, 0xEB, 0x83, 0x96, 0xC3, 0x41, 0x57, 0x6B, 0x83, 0x96, 0xC3, 0x41, 0xC3, 0x8A, 0x43, 0x08, +0x03, 0x59, 0x5E, 0x6B, 0x8A, 0x51, 0xA9, 0xA4, 0x8A, 0x95, 0x61, 0xEB, 0x01, 0xF0, 0x83, 0x52, +0x9B, 0x40, 0x8A, 0x95, 0xAB, 0xE2, 0x8A, 0x95, 0x00, 0xB0, 0x8A, 0x51, 0x54, 0xE7, 0x8A, 0x95, +0xC5, 0x00, 0xFE, 0xF9, 0x01, 0x38, 0xC5, 0x00, 0xDA, 0x40, 0x00, 0xB0, 0x8A, 0x51, 0x4C, 0xE7, +0x8A, 0x95, 0x00, 0xB0, 0x8A, 0x51, 0x54, 0xE7, 0x8A, 0x95, 0xC5, 0x00, 0xEF, 0xF9, 0x10, 0x38, +0xC5, 0x00, 0x45, 0x08, 0xDA, 0x40, 0x00, 0xB0, 0x8A, 0x51, 0x4C, 0xE7, 0x8A, 0x95, 0x1C, 0x54, +0x9C, 0x94, 0x9C, 0xD6, 0x35, 0xB0, 0xED, 0x80, 0xED, 0x4B, 0x84, 0xAB, 0x83, 0x52, 0x03, 0x53, +0x9C, 0x92, 0x9C, 0x17, 0x35, 0xB0, 0xED, 0x80, 0xED, 0x4B, 0x8C, 0xEB, 0x83, 0x52, 0x03, 0x53, +0x9C, 0xD3, 0xC4, 0x01, 0x02, 0xF0, 0x44, 0xC2, 0x03, 0x18, 0x5F, 0x6C, 0x08, 0xF0, 0xDF, 0xC0, +0xAC, 0xB0, 0xE0, 0xC0, 0x97, 0xF0, 0xE1, 0x00, 0x44, 0xC8, 0xE2, 0x00, 0x0D, 0x70, 0x8A, 0x51, +0x07, 0x20, 0x8A, 0x95, 0x44, 0xC8, 0xA8, 0x3E, 0x84, 0x80, 0x83, 0x93, 0x00, 0xCB, 0xB2, 0x2B, +0x44, 0xC8, 0xAA, 0x7E, 0x84, 0x80, 0x00, 0x48, 0xED, 0x80, 0x44, 0xC8, 0x21, 0xFE, 0x84, 0x80, +0x6D, 0x88, 0xB6, 0x6B, 0x44, 0xC8, 0x21, 0xFE, 0x84, 0x80, 0x27, 0x48, 0x80, 0x40, 0x44, 0xC8, +0x21, 0xFE, 0x84, 0x80, 0x00, 0x48, 0x1F, 0xBE, 0x84, 0x80, 0x8A, 0x95, 0x00, 0x60, 0x8A, 0x95, +0xED, 0x80, 0x44, 0xC8, 0x25, 0x3E, 0x84, 0x80, 0x6D, 0x88, 0x80, 0x40, 0x08, 0xF0, 0xDF, 0xC0, +0xB6, 0xF0, 0xE0, 0xC0, 0x44, 0xC8, 0x21, 0xFE, 0x84, 0x80, 0x00, 0x48, 0x20, 0x38, 0xE1, 0x00, +0x44, 0xC8, 0xE2, 0x00, 0x0D, 0x70, 0x8A, 0x51, 0x07, 0x20, 0x8A, 0x95, 0x44, 0xC8, 0xAE, 0xBE, +0x84, 0x80, 0x83, 0x93, 0x00, 0xCB, 0xE6, 0x6B, 0x44, 0xC8, 0xB0, 0x3E, 0x84, 0x80, 0x00, 0x48, +0xED, 0x80, 0x44, 0xC8, 0x58, 0x3E, 0x84, 0x80, 0x6D, 0x88, 0xEA, 0x6B, 0x44, 0xC8, 0x58, 0x3E, +0x84, 0x80, 0x35, 0x48, 0x80, 0x40, 0x06, 0x30, 0xDA, 0x40, 0x44, 0xC8, 0x58, 0x3E, 0x84, 0x80, +0x35, 0x48, 0x00, 0x42, 0x8A, 0x95, 0x81, 0x22, 0x8A, 0x95, 0xED, 0x80, 0x44, 0xC8, 0xAC, 0x7E, +0x84, 0x80, 0x6D, 0x88, 0x83, 0x93, 0x80, 0x40, 0x44, 0xC8, 0xAC, 0x7E, 0x84, 0x80, 0x00, 0x48, +0xED, 0x80, 0x44, 0xC8, 0x25, 0x3E, 0x84, 0x80, 0x00, 0x48, 0x6D, 0x07, 0xEE, 0x80, 0x44, 0xC8, +0x23, 0x3E, 0x84, 0x80, 0x6E, 0x88, 0x80, 0x40, 0x44, 0xC8, 0x23, 0x3E, 0x84, 0x80, 0x7F, 0x70, +0x00, 0x42, 0x03, 0x5C, 0x18, 0x6C, 0x44, 0xC8, 0x23, 0x3E, 0x84, 0x80, 0x7F, 0x70, 0x80, 0x40, +0x44, 0x18, 0x21, 0x6C, 0x44, 0xC8, 0x23, 0x3E, 0x84, 0x80, 0x00, 0x48, 0xDA, 0x40, 0x03, 0x30, +0x27, 0xEC, 0x44, 0xC8, 0x23, 0x3E, 0x84, 0x80, 0x00, 0x48, 0xDA, 0x40, 0x04, 0xF0, 0x8A, 0x51, +0x4C, 0xE7, 0x8A, 0x95, 0x44, 0x18, 0x43, 0xAC, 0x44, 0xC8, 0x58, 0x3E, 0x84, 0x80, 0x83, 0x93, +0x00, 0x48, 0xED, 0x80, 0x01, 0xF0, 0x03, 0xD0, 0xED, 0x4D, 0xFF, 0x7E, 0x03, 0xD0, 0x03, 0x9D, +0x34, 0xAC, 0x6D, 0x0D, 0x02, 0xBE, 0xC5, 0x00, 0xDA, 0x40, 0x01, 0xF0, 0x8A, 0x51, 0x4C, 0xE7, +0x8A, 0x95, 0x01, 0xF0, 0x59, 0xEC, 0x44, 0xC8, 0x58, 0x3E, 0x84, 0x80, 0x83, 0x93, 0x00, 0x48, +0xED, 0x80, 0x01, 0xF0, 0x03, 0xD0, 0xED, 0x4D, 0xFF, 0x7E, 0x03, 0xD0, 0x03, 0x9D, 0x4B, 0xEC, +0x6D, 0x0D, 0x02, 0xBE, 0xC5, 0x00, 0xDA, 0x40, 0x02, 0xF0, 0x8A, 0x51, 0x4C, 0xE7, 0x8A, 0x95, +0x02, 0xF0, 0xDA, 0x81, 0x8A, 0x51, 0x4C, 0xE7, 0x8A, 0x95, 0xC4, 0x4A, 0x92, 0xEB, 0xC2, 0x01, +0x08, 0xF0, 0x42, 0xC2, 0x03, 0x18, 0x84, 0x6C, 0x42, 0xC8, 0xDA, 0x40, 0x05, 0x30, 0x8A, 0x51, +0x4C, 0xE7, 0x8A, 0x95, 0x42, 0xC8, 0xA0, 0xFE, 0x84, 0x80, 0x83, 0x96, 0x37, 0x88, 0x83, 0x93, +0x80, 0x40, 0x83, 0x52, 0x42, 0xC8, 0xA0, 0xFE, 0x84, 0x80, 0x03, 0x14, 0x00, 0xCD, 0xC5, 0x00, +0xDA, 0x40, 0x06, 0x30, 0x8A, 0x51, 0x4C, 0xE7, 0x8A, 0x95, 0x06, 0x30, 0xDA, 0x81, 0x8A, 0x51, +0x4C, 0xE7, 0x8A, 0x95, 0xC2, 0x4A, 0x60, 0x6C, 0x9B, 0x81, 0x3F, 0x30, 0x8A, 0x51, 0x54, 0xE7, +0x8A, 0x95, 0xC5, 0x00, 0xFF, 0x3A, 0x03, 0x59, 0x9B, 0x81, 0x12, 0x18, 0x1C, 0x9C, 0xA5, 0xEC, +0x1C, 0x10, 0x3D, 0x88, 0xDF, 0xC0, 0x00, 0xB0, 0x8A, 0x51, 0x25, 0x61, 0x8A, 0x95, 0xC6, 0x00, +0x46, 0x8B, 0x9F, 0x6C, 0x1B, 0x48, 0xBF, 0xF9, 0x01, 0x38, 0x9B, 0x40, 0xA5, 0xEC, 0x41, 0x30, +0x9B, 0x84, 0x00, 0xB0, 0x8A, 0x51, 0x15, 0x20, 0x8A, 0x95, 0x83, 0x52, 0x03, 0x53, 0x12, 0x5C, +0x1C, 0x58, 0xB4, 0xEC, 0x46, 0x8B, 0xB1, 0xEC, 0xC6, 0x41, 0x00, 0xB0, 0x8A, 0x51, 0x15, 0x20, +0x8A, 0x95, 0x83, 0x52, 0x03, 0x53, 0x1C, 0x54, 0x92, 0x58, 0x9C, 0xDC, 0xCC, 0xEC, 0x9C, 0x50, +0x3D, 0x88, 0xDF, 0xC0, 0x01, 0xF0, 0x8A, 0x51, 0x25, 0x61, 0x8A, 0x95, 0xC7, 0x40, 0x47, 0xCB, +0xC6, 0xEC, 0x1B, 0x48, 0x7F, 0xF9, 0x02, 0x38, 0x9B, 0x40, 0xCC, 0xEC, 0x82, 0x30, 0x9B, 0x84, +0x01, 0xF0, 0x8A, 0x51, 0x15, 0x20, 0x8A, 0x95, 0x83, 0x52, 0x03, 0x53, 0x92, 0x9C, 0x9C, 0x98, +0xDB, 0x6C, 0x47, 0xCB, 0xD8, 0xEC, 0xC7, 0x81, 0x01, 0xF0, 0x8A, 0x51, 0x15, 0x20, 0x8A, 0x95, +0x83, 0x52, 0x03, 0x53, 0x9C, 0x94, 0xC2, 0x01, 0x08, 0xF0, 0x42, 0xC2, 0x03, 0x18, 0x2D, 0x2D, +0x42, 0xC8, 0xDA, 0x40, 0x05, 0x30, 0x8A, 0x51, 0x4C, 0xE7, 0x8A, 0x95, 0x24, 0x30, 0x8A, 0x51, +0x54, 0xE7, 0x8A, 0x95, 0x83, 0x96, 0xC4, 0xC0, 0x38, 0x02, 0x03, 0x18, 0x00, 0x2D, 0x83, 0x52, +0x42, 0xC8, 0xA0, 0xFE, 0x84, 0x80, 0x83, 0x96, 0x36, 0x48, 0x83, 0x93, 0x00, 0x42, 0x03, 0x18, +0x00, 0x2D, 0x83, 0x52, 0x42, 0xC8, 0xA0, 0xFE, 0x84, 0x80, 0x00, 0x48, 0x01, 0xBE, 0x14, 0xAD, +0x39, 0x48, 0x44, 0xC2, 0x03, 0x18, 0x2A, 0xED, 0x83, 0x52, 0x42, 0xC8, 0xA0, 0xFE, 0x84, 0x80, +0x83, 0x93, 0x00, 0x48, 0x83, 0x96, 0x37, 0x82, 0x03, 0x18, 0x2A, 0xED, 0x83, 0x52, 0x42, 0xC8, +0xA0, 0xFE, 0x84, 0x80, 0x00, 0x48, 0xFF, 0x7E, 0xED, 0x80, 0x42, 0xC8, 0xA0, 0xFE, 0x84, 0x80, +0x6D, 0x88, 0x80, 0x40, 0x42, 0xC8, 0xA0, 0xFE, 0x84, 0x80, 0x03, 0x14, 0x00, 0xCD, 0xC5, 0x00, +0xDA, 0x40, 0x06, 0x30, 0x8A, 0x51, 0x4C, 0xE7, 0x8A, 0x95, 0x06, 0x30, 0xDA, 0x81, 0x8A, 0x51, +0x4C, 0xE7, 0x8A, 0x95, 0x83, 0x52, 0xC2, 0x4A, 0xDC, 0x2C, 0x83, 0x96, 0xC6, 0x41, 0x83, 0x52, +0x21, 0x30, 0xC4, 0x01, 0x8A, 0x51, 0x54, 0xE7, 0x8A, 0x95, 0xC3, 0x00, 0x20, 0xF0, 0x8A, 0x51, +0x54, 0xE7, 0x8A, 0x95, 0xC1, 0xC0, 0x94, 0x9C, 0x8F, 0x6D, 0x44, 0xC8, 0xA8, 0x3E, 0x84, 0x80, +0x83, 0x93, 0x80, 0x88, 0x03, 0x9D, 0x84, 0xAD, 0x44, 0xC8, 0x21, 0xFE, 0x84, 0x80, 0x00, 0x48, +0x29, 0x02, 0x03, 0x18, 0x84, 0xAD, 0x44, 0xC8, 0x21, 0xFE, 0x84, 0x80, 0x00, 0x48, 0xFF, 0x7E, +0xED, 0x80, 0x44, 0xC8, 0x21, 0xFE, 0x84, 0x80, 0x6D, 0x88, 0x80, 0x40, 0x08, 0xF0, 0xDF, 0xC0, +0xB6, 0xF0, 0xE0, 0xC0, 0x44, 0xC8, 0x21, 0xFE, 0x84, 0x80, 0x00, 0x48, 0x20, 0x38, 0xE1, 0x00, +0x44, 0xC8, 0xE2, 0x00, 0x0D, 0x70, 0x8A, 0x51, 0x07, 0x20, 0x8A, 0x95, 0x01, 0xF0, 0xDA, 0x81, +0xDA, 0xCA, 0x8A, 0x51, 0x4C, 0xE7, 0x8A, 0x95, 0x01, 0xF0, 0xDA, 0x81, 0x8A, 0x51, 0x4C, 0xE7, +0x8A, 0x95, 0x44, 0xC8, 0x21, 0xFE, 0x84, 0x80, 0x83, 0x93, 0x00, 0x48, 0x1F, 0xBE, 0x84, 0x80, +0x8A, 0x95, 0x00, 0x60, 0x8A, 0x95, 0xED, 0x80, 0x44, 0xC8, 0x25, 0x3E, 0x84, 0x80, 0x6D, 0x88, +0x80, 0x40, 0x83, 0x96, 0xC6, 0x41, 0xC6, 0x8A, 0x83, 0x52, 0x9C, 0x92, 0x1C, 0x96, 0x05, 0x30, +0xED, 0x80, 0xED, 0x4B, 0x89, 0xED, 0x83, 0x52, 0x03, 0x53, 0x1C, 0x52, 0x6B, 0x6E, 0x44, 0xC8, +0xAE, 0xBE, 0x84, 0x80, 0x83, 0x93, 0x80, 0x88, 0x03, 0x9D, 0x0F, 0x2E, 0x44, 0xC8, 0x21, 0xFE, +0x84, 0x80, 0x00, 0x48, 0x27, 0x86, 0x03, 0x9D, 0xA0, 0xAD, 0x37, 0x88, 0x83, 0x96, 0xA2, 0xED, +0x83, 0x96, 0x3A, 0x48, 0xC5, 0x00, 0x83, 0x52, 0x43, 0x08, 0x83, 0x96, 0x45, 0x02, 0x03, 0x18, +0xC9, 0x2D, 0x83, 0x52, 0x44, 0xC8, 0x58, 0x3E, 0x84, 0x80, 0x33, 0x48, 0x00, 0x42, 0x03, 0x18, +0xC9, 0x2D, 0x44, 0xC8, 0x58, 0x3E, 0x84, 0x80, 0x00, 0x48, 0x01, 0xBE, 0xED, 0x80, 0x44, 0xC8, +0x58, 0x3E, 0x84, 0x80, 0x6D, 0x88, 0x80, 0x40, 0x44, 0xC8, 0x58, 0x3E, 0x84, 0x80, 0x00, 0x48, +0xED, 0x80, 0x01, 0xF0, 0x03, 0xD0, 0xED, 0x4D, 0xFF, 0x7E, 0x03, 0xD0, 0x03, 0x9D, 0xC3, 0x2D, +0xEC, 0x6D, 0x83, 0x52, 0x3A, 0x48, 0x43, 0x02, 0x44, 0xC8, 0x03, 0x18, 0x10, 0x6E, 0x58, 0x3E, +0x84, 0x80, 0x00, 0x48, 0x35, 0x42, 0x44, 0xC8, 0x03, 0x18, 0x10, 0x6E, 0x58, 0x3E, 0x84, 0x80, +0x00, 0x48, 0xFF, 0x7E, 0xED, 0x80, 0x44, 0xC8, 0x58, 0x3E, 0x84, 0x80, 0x6D, 0x88, 0x80, 0x40, +0x44, 0xC8, 0x58, 0x3E, 0x84, 0x80, 0x00, 0x48, 0xED, 0x80, 0x01, 0xF0, 0x03, 0xD0, 0xED, 0x4D, +0xFF, 0x7E, 0x03, 0xD0, 0x03, 0x9D, 0xE7, 0xAD, 0x6D, 0x0D, 0x02, 0xBE, 0xC5, 0x00, 0xDA, 0x40, +0x01, 0xF0, 0x8A, 0x51, 0x4C, 0xE7, 0x8A, 0x95, 0x01, 0xF0, 0xDA, 0x81, 0x8A, 0x51, 0x4C, 0xE7, +0x8A, 0x95, 0x83, 0x96, 0x06, 0x30, 0xC6, 0x41, 0xC6, 0x8A, 0x83, 0x52, 0xDA, 0x40, 0x44, 0xC8, +0x58, 0x3E, 0x84, 0x80, 0x35, 0x48, 0x83, 0x93, 0x00, 0x42, 0x8A, 0x95, 0x81, 0x22, 0x8A, 0x95, +0xED, 0x80, 0x44, 0xC8, 0xAC, 0x7E, 0x84, 0x80, 0x6D, 0x88, 0x83, 0x93, 0x80, 0x40, 0x44, 0xC8, +0xA8, 0x3E, 0x84, 0x80, 0x80, 0x88, 0x03, 0x9D, 0x6B, 0x6E, 0x41, 0xC8, 0x2B, 0x42, 0x03, 0x18, +0x26, 0xEE, 0x44, 0xC8, 0x21, 0xFE, 0x84, 0x80, 0x00, 0x48, 0x29, 0x02, 0x03, 0x18, 0x26, 0xEE, +0x44, 0xC8, 0x21, 0xFE, 0x84, 0x80, 0x00, 0x48, 0xFF, 0x7E, 0x36, 0x2E, 0x2D, 0x48, 0x41, 0xC2, +0x03, 0x18, 0x6B, 0x6E, 0x44, 0xC8, 0x21, 0xFE, 0x84, 0x80, 0x27, 0x48, 0x00, 0x42, 0x03, 0x18, +0x6B, 0x6E, 0x44, 0xC8, 0x21, 0xFE, 0x84, 0x80, 0x00, 0x48, 0x01, 0xBE, 0xED, 0x80, 0x44, 0xC8, +0x21, 0xFE, 0x84, 0x80, 0x6D, 0x88, 0x80, 0x40, 0x08, 0xF0, 0xDF, 0xC0, 0xB6, 0xF0, 0xE0, 0xC0, +0x44, 0xC8, 0x21, 0xFE, 0x84, 0x80, 0x00, 0x48, 0x20, 0x38, 0xE1, 0x00, 0x44, 0xC8, 0xE2, 0x00, +0x0D, 0x70, 0x8A, 0x51, 0x07, 0x20, 0x8A, 0x95, 0x01, 0xF0, 0xDA, 0x81, 0xDA, 0xCA, 0x8A, 0x51, +0x4C, 0xE7, 0x8A, 0x95, 0x01, 0xF0, 0xDA, 0x81, 0x8A, 0x51, 0x4C, 0xE7, 0x8A, 0x95, 0x83, 0x96, +0xC6, 0x41, 0xC6, 0x8A, 0x83, 0x52, 0x44, 0xC8, 0x21, 0xFE, 0x84, 0x80, 0x83, 0x93, 0x00, 0x48, +0x1F, 0xBE, 0x84, 0x80, 0x8A, 0x95, 0x00, 0x60, 0x8A, 0x95, 0xED, 0x80, 0x44, 0xC8, 0x25, 0x3E, +0x84, 0x80, 0x6D, 0x88, 0x80, 0x40, 0x83, 0x96, 0x46, 0x8B, 0x95, 0x2E, 0x83, 0x52, 0x44, 0xC8, +0xAC, 0x7E, 0x84, 0x80, 0x83, 0x93, 0x00, 0x48, 0xED, 0x80, 0x44, 0xC8, 0x25, 0x3E, 0x84, 0x80, +0x00, 0x48, 0x6D, 0x07, 0xEE, 0x80, 0x44, 0xC8, 0x23, 0x3E, 0x84, 0x80, 0x6E, 0x88, 0x80, 0x40, +0x44, 0xC8, 0x23, 0x3E, 0x84, 0x80, 0x7F, 0x70, 0x00, 0x42, 0x44, 0xC8, 0x03, 0x5C, 0x8D, 0x2E, +0x23, 0x3E, 0x84, 0x80, 0x7F, 0x70, 0x80, 0x40, 0x44, 0xC8, 0x23, 0x3E, 0x84, 0x80, 0x00, 0x48, +0xDA, 0x40, 0x03, 0x30, 0x8A, 0x51, 0x4C, 0xE7, 0x8A, 0x95, 0x83, 0x52, 0x23, 0x70, 0xC4, 0x01, +0xC4, 0x4A, 0x8A, 0x51, 0x54, 0xE7, 0x8A, 0x95, 0xC3, 0x00, 0x22, 0x30, 0x8A, 0x51, 0x54, 0xE7, +0x8A, 0x95, 0xC1, 0xC0, 0x83, 0x96, 0xC7, 0x81, 0x83, 0x52, 0x94, 0xDD, 0xF9, 0xAE, 0x44, 0xC8, +0xA8, 0x3E, 0x84, 0x80, 0x83, 0x93, 0x80, 0x88, 0x03, 0x9D, 0xEE, 0xAE, 0x44, 0xC8, 0x21, 0xFE, +0x84, 0x80, 0x00, 0x48, 0x29, 0x02, 0x03, 0x18, 0xEE, 0xAE, 0x44, 0xC8, 0x21, 0xFE, 0x84, 0x80, +0x00, 0x48, 0xFF, 0x7E, 0xED, 0x80, 0x44, 0xC8, 0x21, 0xFE, 0x84, 0x80, 0x6D, 0x88, 0x80, 0x40, +0x08, 0xF0, 0xDF, 0xC0, 0xB6, 0xF0, 0xE0, 0xC0, 0x44, 0xC8, 0x21, 0xFE, 0x84, 0x80, 0x00, 0x48, +0x20, 0x38, 0xE1, 0x00, 0x44, 0xC8, 0xE2, 0x00, 0x0D, 0x70, 0x8A, 0x51, 0x07, 0x20, 0x8A, 0x95, +0x02, 0xF0, 0xDA, 0x81, 0xDA, 0xCA, 0x8A, 0x51, 0x4C, 0xE7, 0x8A, 0x95, 0x02, 0xF0, 0xDA, 0x81, +0x8A, 0x51, 0x4C, 0xE7, 0x8A, 0x95, 0x44, 0xC8, 0x21, 0xFE, 0x84, 0x80, 0x83, 0x93, 0x00, 0x48, +0x1F, 0xBE, 0x84, 0x80, 0x8A, 0x95, 0x00, 0x60, 0x8A, 0x95, 0xED, 0x80, 0x44, 0xC8, 0x25, 0x3E, +0x84, 0x80, 0x6D, 0x88, 0x80, 0x40, 0x83, 0x96, 0xC7, 0x81, 0xC7, 0xCA, 0x83, 0x52, 0x9C, 0xD3, +0x1C, 0xD7, 0x05, 0x30, 0xED, 0x80, 0xED, 0x4B, 0xF3, 0xAE, 0x83, 0x52, 0x03, 0x53, 0x1C, 0x93, +0xD5, 0xAF, 0x44, 0xC8, 0xAE, 0xBE, 0x84, 0x80, 0x83, 0x93, 0x80, 0x88, 0x03, 0x9D, 0x79, 0xAF, +0x44, 0xC8, 0x21, 0xFE, 0x84, 0x80, 0x00, 0x48, 0x27, 0x86, 0x03, 0x9D, 0x0A, 0xEF, 0x37, 0x88, +0x83, 0x96, 0x0C, 0xEF, 0x83, 0x96, 0x3A, 0x48, 0xC5, 0x00, 0x83, 0x52, 0x43, 0x08, 0x83, 0x96, +0x45, 0x02, 0x03, 0x18, 0x33, 0x6F, 0x83, 0x52, 0x44, 0xC8, 0x58, 0x3E, 0x84, 0x80, 0x33, 0x48, +0x00, 0x42, 0x03, 0x18, 0x33, 0x6F, 0x44, 0xC8, 0x58, 0x3E, 0x84, 0x80, 0x00, 0x48, 0x01, 0xBE, +0xED, 0x80, 0x44, 0xC8, 0x58, 0x3E, 0x84, 0x80, 0x6D, 0x88, 0x80, 0x40, 0x44, 0xC8, 0x58, 0x3E, +0x84, 0x80, 0x00, 0x48, 0xED, 0x80, 0x01, 0xF0, 0x03, 0xD0, 0xED, 0x4D, 0xFF, 0x7E, 0x03, 0xD0, +0x03, 0x9D, 0x2D, 0x6F, 0x56, 0x6F, 0x83, 0x52, 0x3A, 0x48, 0x43, 0x02, 0x44, 0xC8, 0x03, 0x18, +0x7A, 0xAF, 0x58, 0x3E, 0x84, 0x80, 0x00, 0x48, 0x35, 0x42, 0x44, 0xC8, 0x03, 0x18, 0x7A, 0xAF, +0x58, 0x3E, 0x84, 0x80, 0x00, 0x48, 0xFF, 0x7E, 0xED, 0x80, 0x44, 0xC8, 0x58, 0x3E, 0x84, 0x80, +0x6D, 0x88, 0x80, 0x40, 0x44, 0xC8, 0x58, 0x3E, 0x84, 0x80, 0x00, 0x48, 0xED, 0x80, 0x01, 0xF0, +0x03, 0xD0, 0xED, 0x4D, 0xFF, 0x7E, 0x03, 0xD0, 0x03, 0x9D, 0x51, 0x2F, 0x6D, 0x0D, 0x02, 0xBE, +0xC5, 0x00, 0xDA, 0x40, 0x02, 0xF0, 0x8A, 0x51, 0x4C, 0xE7, 0x8A, 0x95, 0x02, 0xF0, 0xDA, 0x81, +0x8A, 0x51, 0x4C, 0xE7, 0x8A, 0x95, 0x83, 0x96, 0x06, 0x30, 0xC7, 0x81, 0xC7, 0xCA, 0x83, 0x52, +0xDA, 0x40, 0x44, 0xC8, 0x58, 0x3E, 0x84, 0x80, 0x35, 0x48, 0x83, 0x93, 0x00, 0x42, 0x8A, 0x95, +0x81, 0x22, 0x8A, 0x95, 0xED, 0x80, 0x44, 0xC8, 0xAC, 0x7E, 0x84, 0x80, 0x6D, 0x88, 0x83, 0x93, +0x80, 0x40, 0x44, 0xC8, 0xA8, 0x3E, 0x84, 0x80, 0x80, 0x88, 0x03, 0x9D, 0xD5, 0xAF, 0x41, 0xC8, +0x2B, 0x42, 0x03, 0x18, 0x90, 0xEF, 0x44, 0xC8, 0x21, 0xFE, 0x84, 0x80, 0x00, 0x48, 0x29, 0x02, +0x03, 0x18, 0x90, 0xEF, 0x44, 0xC8, 0x21, 0xFE, 0x84, 0x80, 0x00, 0x48, 0xFF, 0x7E, 0xA0, 0xEF, +0x2D, 0x48, 0x41, 0xC2, 0x03, 0x18, 0xD5, 0xAF, 0x44, 0xC8, 0x21, 0xFE, 0x84, 0x80, 0x27, 0x48, +0x00, 0x42, 0x03, 0x18, 0xD5, 0xAF, 0x44, 0xC8, 0x21, 0xFE, 0x84, 0x80, 0x00, 0x48, 0x01, 0xBE, +0xED, 0x80, 0x44, 0xC8, 0x21, 0xFE, 0x84, 0x80, 0x6D, 0x88, 0x80, 0x40, 0x08, 0xF0, 0xDF, 0xC0, +0xB6, 0xF0, 0xE0, 0xC0, 0x44, 0xC8, 0x21, 0xFE, 0x84, 0x80, 0x00, 0x48, 0x20, 0x38, 0xE1, 0x00, +0x44, 0xC8, 0xE2, 0x00, 0x0D, 0x70, 0x8A, 0x51, 0x07, 0x20, 0x8A, 0x95, 0x02, 0xF0, 0xDA, 0x81, +0xDA, 0xCA, 0x8A, 0x51, 0x4C, 0xE7, 0x8A, 0x95, 0x02, 0xF0, 0xDA, 0x81, 0x8A, 0x51, 0x4C, 0xE7, +0x8A, 0x95, 0x83, 0x96, 0xC7, 0x81, 0xC7, 0xCA, 0x83, 0x52, 0x44, 0xC8, 0x21, 0xFE, 0x84, 0x80, +0x83, 0x93, 0x00, 0x48, 0x1F, 0xBE, 0x84, 0x80, 0x8A, 0x95, 0x00, 0x60, 0x8A, 0x95, 0xED, 0x80, +0x44, 0xC8, 0x25, 0x3E, 0x84, 0x80, 0x6D, 0x88, 0x80, 0x40, 0x83, 0x96, 0x47, 0xCB, 0x85, 0xAC, +0x83, 0x52, 0x44, 0xC8, 0xAC, 0x7E, 0x84, 0x80, 0x83, 0x93, 0x00, 0x48, 0xED, 0x80, 0x44, 0xC8, +0x25, 0x3E, 0x84, 0x80, 0x00, 0x48, 0x6D, 0x07, 0xEE, 0x80, 0x44, 0xC8, 0x23, 0x3E, 0x84, 0x80, +0x6E, 0x88, 0x80, 0x40, 0x44, 0xC8, 0x23, 0x3E, 0x84, 0x80, 0x7F, 0x70, 0x00, 0x42, 0x44, 0xC8, +0x03, 0x5C, 0xF7, 0x2F, 0x23, 0x3E, 0x84, 0x80, 0x7F, 0x70, 0x80, 0x40, 0x44, 0xC8, 0x23, 0x3E, +0x84, 0x80, 0x00, 0x48, 0xDA, 0x40, 0x04, 0xF0, 0x8A, 0x51, 0x4C, 0xE7, 0x8A, 0x95, 0x85, 0xAC + }; + \ No newline at end of file diff --git a/libloragw/src/agc_fw_sx1257.var b/libloragw/src/agc_fw_sx1257.var index 73a0b0b8..83be9d4b 100644 --- a/libloragw/src/agc_fw_sx1257.var +++ b/libloragw/src/agc_fw_sx1257.var @@ -48,215 +48,213 @@ static uint8_t agc_firmware_sx125x[8192] = { 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0x64, 0xC0, 0x80, 0x81, 0x84, 0x0A, -0x04, 0xC6, 0x03, 0x59, 0x00, 0xF4, 0x04, 0xC6, 0xB6, 0x29, 0xDF, 0xC0, 0xDE, 0xC1, 0x5D, 0x88, -0x5F, 0x18, 0xDE, 0x47, 0x03, 0xD0, 0xDD, 0x4D, 0x5D, 0x88, 0xDF, 0x58, 0xDE, 0x47, 0x03, 0xD0, -0xDD, 0x4D, 0x5D, 0x88, 0x5F, 0x59, 0xDE, 0x47, 0x03, 0xD0, 0xDD, 0x4D, 0x5D, 0x88, 0xDF, 0x99, -0xDE, 0x47, 0x03, 0xD0, 0xDD, 0x4D, 0x5D, 0x88, 0x5F, 0x5A, 0xDE, 0x47, 0x03, 0xD0, 0xDD, 0x4D, -0x5D, 0x88, 0xDF, 0x9A, 0xDE, 0x47, 0x03, 0xD0, 0xDD, 0x4D, 0x5D, 0x88, 0x5F, 0x9B, 0xDE, 0x47, -0x03, 0xD0, 0xDD, 0x4D, 0x5D, 0x88, 0xDF, 0xDB, 0xDE, 0x47, 0x5E, 0x88, 0x08, 0x40, 0xD7, 0xC1, -0x02, 0xF0, 0x57, 0x82, 0x03, 0x18, 0x07, 0xAA, 0x57, 0x88, 0x2D, 0x7E, 0x84, 0x80, 0x83, 0x93, -0x80, 0x81, 0x57, 0x88, 0x2B, 0x7E, 0x84, 0x80, 0x80, 0x81, 0x57, 0x88, 0x25, 0x3E, 0x84, 0x80, -0x80, 0x81, 0x57, 0x88, 0x33, 0x7E, 0x84, 0x80, 0x80, 0x81, 0x57, 0x88, 0xA8, 0x3E, 0x84, 0x80, -0x80, 0x81, 0x57, 0x88, 0x29, 0x3E, 0x84, 0x80, 0x80, 0x81, 0xD7, 0x0A, 0xE8, 0xE9, 0x09, 0x30, -0xB7, 0xC1, 0xB5, 0x40, 0x10, 0xF0, 0xBB, 0x80, 0x23, 0x70, 0xB9, 0x40, 0x07, 0x70, 0xC7, 0x40, -0x0B, 0x70, 0xC5, 0x00, 0x2D, 0xB0, 0xCD, 0x40, 0x64, 0x70, 0xC9, 0x00, 0x73, 0xF0, 0xCB, 0x40, -0x04, 0xF0, 0xBF, 0xC0, 0x0E, 0x70, 0xBD, 0x80, 0x34, 0x70, 0xC3, 0x00, 0x84, 0x30, 0xC1, 0xC0, -0x08, 0x40, 0x01, 0xF0, 0xA0, 0x80, 0x95, 0x41, 0x96, 0x41, 0x97, 0x81, 0x98, 0x01, 0x99, 0x41, -0x9A, 0x41, 0x9B, 0x81, 0x9C, 0x41, 0x9E, 0x81, 0x00, 0xB0, 0xDD, 0x80, 0x5D, 0x88, 0x95, 0x00, -0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xEF, 0xC0, 0x6F, 0xC8, 0xFE, 0xF9, 0x01, 0x38, 0xEF, 0xC0, -0x6F, 0xC8, 0xDD, 0x80, 0x00, 0xB0, 0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, -0x17, 0x94, 0x17, 0x50, 0x00, 0xB0, 0xDD, 0x80, 0x5D, 0x88, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, -0x0D, 0x08, 0xEF, 0xC0, 0x6F, 0xC8, 0xEF, 0xF9, 0x10, 0x38, 0xEF, 0xC0, 0x6F, 0xC8, 0xDD, 0x80, -0x00, 0xB0, 0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, -0x1C, 0x54, 0x9C, 0x94, 0x83, 0x96, 0xAC, 0x41, 0xAD, 0x81, 0x83, 0x52, 0xB3, 0x81, 0xB4, 0x41, -0x9B, 0x81, 0x1C, 0x70, 0xDD, 0xC1, 0xDD, 0x0A, 0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, -0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x8A, 0x51, 0xE7, 0x21, 0x8A, 0x51, 0x3D, 0xF0, 0xDD, 0x80, -0x5D, 0x88, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xEF, 0xC0, 0x6F, 0xC8, 0xF7, 0xFA, -0x03, 0x9D, 0x7C, 0x2A, 0xCF, 0xC1, 0x7E, 0x6A, 0xCF, 0xC1, 0xCF, 0x0A, 0x4F, 0x88, 0x03, 0x59, -0x85, 0xAA, 0x8A, 0x95, 0xB1, 0xA4, 0x8A, 0x51, 0x87, 0xEA, 0x01, 0xF0, 0x9B, 0x40, 0xD7, 0xC1, -0x02, 0xF0, 0x57, 0x82, 0x03, 0x18, 0x94, 0xEB, 0x57, 0x88, 0xA8, 0x3E, 0x84, 0x80, 0x83, 0x93, -0x00, 0xCB, 0x9C, 0xEA, 0x57, 0x88, 0x25, 0x3E, 0x84, 0x80, 0x00, 0x48, 0xEB, 0x80, 0x57, 0x88, -0x23, 0x3E, 0x84, 0x80, 0x6B, 0x88, 0xA0, 0x6A, 0x57, 0x88, 0x23, 0x3E, 0x84, 0x80, 0x37, 0x88, -0x80, 0x40, 0x57, 0x88, 0x23, 0x3E, 0x84, 0x80, 0x00, 0x48, 0x01, 0xBE, 0x84, 0x80, 0x8A, 0x51, -0x02, 0xA0, 0x8A, 0x51, 0x0F, 0x39, 0xEB, 0x80, 0x03, 0xD0, 0xEB, 0x4D, 0x57, 0x88, 0x23, 0x3E, -0x84, 0x80, 0x00, 0x48, 0x15, 0x3E, 0x84, 0x80, 0x8A, 0x51, 0x02, 0xA0, 0x8A, 0x51, 0x07, 0xF9, -0xEC, 0x40, 0x04, 0xF0, 0x03, 0xD0, 0xEC, 0x0D, 0xFF, 0x7E, 0x03, 0xD0, 0x03, 0x9D, 0xBB, 0x6A, -0x6C, 0xCD, 0x6B, 0x84, 0x01, 0x38, 0xED, 0x80, 0x57, 0x88, 0x21, 0xFE, 0x84, 0x80, 0x6D, 0x88, -0x80, 0x40, 0x8C, 0x70, 0x83, 0x96, 0xB5, 0x40, 0x83, 0x52, 0x57, 0x88, 0x21, 0xFE, 0x84, 0x80, -0x00, 0x48, 0x83, 0x96, 0xB6, 0x40, 0xB5, 0xF0, 0x83, 0x52, 0xDD, 0x80, 0x57, 0x88, 0xDE, 0x80, -0x02, 0xF0, 0x8A, 0x95, 0x15, 0x64, 0x8A, 0x51, 0x57, 0x88, 0x23, 0x3E, 0x84, 0x80, 0x83, 0x93, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0x64, 0xC0, 0x80, 0x81, 0x84, 0x0A, 0x04, 0xC6, 0x03, 0x59, +0x00, 0xF4, 0x04, 0xC6, 0x8C, 0xA9, 0xDE, 0x80, 0xDD, 0xC1, 0x5C, 0x48, 0x5E, 0xD8, 0xDD, 0x47, +0x03, 0xD0, 0xDC, 0x0D, 0x5C, 0x48, 0xDE, 0x18, 0xDD, 0x47, 0x03, 0xD0, 0xDC, 0x0D, 0x5C, 0x48, +0x5E, 0x19, 0xDD, 0x47, 0x03, 0xD0, 0xDC, 0x0D, 0x5C, 0x48, 0xDE, 0x59, 0xDD, 0x47, 0x03, 0xD0, +0xDC, 0x0D, 0x5C, 0x48, 0x5E, 0x1A, 0xDD, 0x47, 0x03, 0xD0, 0xDC, 0x0D, 0x5C, 0x48, 0xDE, 0x5A, +0xDD, 0x47, 0x03, 0xD0, 0xDC, 0x0D, 0x5C, 0x48, 0x5E, 0x5B, 0xDD, 0x47, 0x03, 0xD0, 0xDC, 0x0D, +0x5C, 0x48, 0xDE, 0x9B, 0xDD, 0x47, 0x5D, 0x88, 0x08, 0x40, 0xD7, 0xC1, 0x02, 0xF0, 0x57, 0x82, +0x03, 0x18, 0xDD, 0x69, 0x57, 0x88, 0x2B, 0x7E, 0x84, 0x80, 0x83, 0x93, 0x80, 0x81, 0x57, 0x88, +0x29, 0x3E, 0x84, 0x80, 0x80, 0x81, 0x57, 0x88, 0x25, 0x3E, 0x84, 0x80, 0x80, 0x81, 0x57, 0x88, +0x31, 0x3E, 0x84, 0x80, 0x80, 0x81, 0x57, 0x88, 0xA8, 0x3E, 0x84, 0x80, 0x80, 0x81, 0x57, 0x88, +0xAC, 0x7E, 0x84, 0x80, 0x80, 0x81, 0xD7, 0x0A, 0xBE, 0x69, 0x09, 0x30, 0xB5, 0x81, 0xB3, 0x40, +0x10, 0xF0, 0xB9, 0x40, 0x23, 0x70, 0xB7, 0x80, 0x07, 0x70, 0xC5, 0x00, 0x0B, 0x70, 0xC3, 0x00, +0x2D, 0xB0, 0xCB, 0x40, 0x64, 0x70, 0xC7, 0x40, 0x73, 0xF0, 0xC9, 0x00, 0x04, 0xF0, 0xBD, 0x80, +0x0E, 0x70, 0xBB, 0x80, 0x34, 0x70, 0xC1, 0xC0, 0x84, 0x30, 0xBF, 0xC0, 0x0A, 0x30, 0xCF, 0x80, +0xCD, 0x81, 0x08, 0x40, 0x06, 0x30, 0xA0, 0x80, 0x95, 0x41, 0x96, 0x41, 0x97, 0x81, 0x98, 0x01, +0x99, 0x41, 0x9A, 0x41, 0x9B, 0x81, 0x9C, 0x41, 0x9E, 0x81, 0x00, 0xB0, 0xDC, 0x40, 0x5C, 0x48, +0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xEF, 0xC0, 0x6F, 0xC8, 0xFE, 0xF9, 0x01, 0x38, +0xEF, 0xC0, 0x6F, 0xC8, 0xDC, 0x40, 0x00, 0xB0, 0xDD, 0x80, 0x5C, 0x48, 0x96, 0x00, 0x5D, 0x88, +0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x00, 0xB0, 0xDC, 0x40, 0x5C, 0x48, 0x95, 0x00, 0x97, 0xD4, +0x97, 0x90, 0x0D, 0x08, 0xEF, 0xC0, 0x6F, 0xC8, 0xEF, 0xF9, 0x10, 0x38, 0xEF, 0xC0, 0x6F, 0xC8, +0xDC, 0x40, 0x00, 0xB0, 0xDD, 0x80, 0x5C, 0x48, 0x96, 0x00, 0x5D, 0x88, 0x95, 0x00, 0x17, 0x94, +0x17, 0x50, 0x1C, 0x54, 0x9C, 0x94, 0x83, 0x96, 0xAE, 0x81, 0xAF, 0xC1, 0x83, 0x52, 0xB1, 0x41, +0xB2, 0x41, 0x9B, 0x81, 0x06, 0x30, 0xDC, 0x40, 0x1C, 0x70, 0xDD, 0x80, 0x5C, 0x48, 0x96, 0x00, +0x5D, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x8A, 0x51, 0xBD, 0x21, 0x8A, 0x51, 0x3D, 0xF0, +0xDC, 0x40, 0x5C, 0x48, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xEF, 0xC0, 0x6F, 0xC8, +0xF7, 0xFA, 0x03, 0x9D, 0x55, 0xEA, 0xCE, 0x81, 0x57, 0x2A, 0xCE, 0x81, 0xCE, 0xCA, 0x4E, 0x48, +0x03, 0x59, 0x5E, 0x2A, 0x8A, 0x95, 0x61, 0x64, 0x8A, 0x51, 0x60, 0x6A, 0x01, 0xF0, 0x9B, 0x40, +0xD7, 0xC1, 0x02, 0xF0, 0x57, 0x82, 0x03, 0x18, 0x6C, 0x2B, 0x57, 0x88, 0xA8, 0x3E, 0x84, 0x80, +0x83, 0x93, 0x00, 0xCB, 0x75, 0x2A, 0x57, 0x88, 0x25, 0x3E, 0x84, 0x80, 0x00, 0x48, 0xEB, 0x80, +0x57, 0x88, 0x23, 0x3E, 0x84, 0x80, 0x6B, 0x88, 0x79, 0x2A, 0x57, 0x88, 0x23, 0x3E, 0x84, 0x80, +0x35, 0x48, 0x80, 0x40, 0x57, 0x88, 0x23, 0x3E, 0x84, 0x80, 0x00, 0x48, 0x01, 0xBE, 0x84, 0x80, +0x8A, 0x51, 0x02, 0xA0, 0x8A, 0x51, 0x0F, 0x39, 0xEB, 0x80, 0x03, 0xD0, 0xEB, 0x4D, 0x57, 0x88, +0x23, 0x3E, 0x84, 0x80, 0x00, 0x48, 0x15, 0x3E, 0x84, 0x80, 0x8A, 0x51, 0x02, 0xA0, 0x8A, 0x51, +0x07, 0xF9, 0xEC, 0x40, 0x04, 0xF0, 0x03, 0xD0, 0xEC, 0x0D, 0xFF, 0x7E, 0x03, 0xD0, 0x03, 0x9D, +0x94, 0xAA, 0x6C, 0xCD, 0x6B, 0x84, 0xED, 0x80, 0x57, 0x88, 0x21, 0xFE, 0x84, 0x80, 0x6D, 0x88, +0x80, 0x40, 0x8C, 0x70, 0x83, 0x96, 0xB8, 0x00, 0x83, 0x52, 0x57, 0x88, 0x21, 0xFE, 0x84, 0x80, +0x00, 0x48, 0x83, 0x96, 0xB9, 0x40, 0xB8, 0xB0, 0x83, 0x52, 0xDC, 0x40, 0x57, 0x88, 0xDD, 0x80, +0x02, 0xF0, 0x8A, 0x95, 0xC5, 0xE3, 0x8A, 0x51, 0x57, 0x88, 0x23, 0x3E, 0x84, 0x80, 0x83, 0x93, 0x00, 0x48, 0x0B, 0x3E, 0x84, 0x80, 0x8A, 0x51, 0x02, 0xA0, 0x8A, 0x51, 0xEB, 0x80, 0x57, 0x88, -0x31, 0x3E, 0x84, 0x80, 0x6B, 0x88, 0x80, 0x40, 0x57, 0x88, 0x29, 0x3E, 0x84, 0x80, 0x00, 0xCB, -0xFB, 0xAA, 0x57, 0x88, 0x2B, 0x7E, 0x84, 0x80, 0x00, 0x48, 0xEB, 0x80, 0x57, 0x88, 0x59, 0x7E, -0x84, 0x80, 0x6B, 0x88, 0xFF, 0xEA, 0x57, 0x88, 0x59, 0x7E, 0x84, 0x80, 0x47, 0x48, 0x80, 0x40, -0x06, 0x30, 0xDD, 0x80, 0x57, 0x88, 0x59, 0x7E, 0x84, 0x80, 0x47, 0x48, 0x00, 0x42, 0x8A, 0x51, -0xBD, 0x21, 0x8A, 0x51, 0xEB, 0x80, 0x57, 0x88, 0x27, 0x7E, 0x84, 0x80, 0x6B, 0x88, 0x80, 0x40, -0x57, 0x88, 0x27, 0x7E, 0x84, 0x80, 0x00, 0x48, 0xEB, 0x80, 0x57, 0x88, 0x31, 0x3E, 0x84, 0x80, -0x00, 0x48, 0x6B, 0x07, 0xEC, 0x40, 0x57, 0x88, 0x2F, 0xBE, 0x84, 0x80, 0x6C, 0x48, 0x80, 0x40, -0x57, 0x88, 0x2F, 0xBE, 0x84, 0x80, 0x7F, 0x70, 0x00, 0x42, 0x03, 0x5C, 0x2C, 0xEB, 0x57, 0x88, -0x2F, 0xBE, 0x84, 0x80, 0x7F, 0x70, 0x80, 0x40, 0x57, 0xD8, 0x62, 0xEB, 0x57, 0x88, 0x2F, 0xBE, -0x84, 0x80, 0x00, 0x48, 0xDD, 0x80, 0x03, 0x30, 0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, -0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x57, 0xD8, 0x70, 0xEB, 0x57, 0x88, 0x59, 0x7E, 0x84, 0x80, +0x2F, 0xBE, 0x84, 0x80, 0x6B, 0x88, 0x80, 0x40, 0x57, 0x88, 0xAC, 0x7E, 0x84, 0x80, 0x00, 0xCB, +0xD3, 0x2A, 0x57, 0x88, 0x29, 0x3E, 0x84, 0x80, 0x00, 0x48, 0xEB, 0x80, 0x57, 0x88, 0x58, 0x3E, +0x84, 0x80, 0x6B, 0x88, 0xD7, 0x6A, 0x57, 0x88, 0x58, 0x3E, 0x84, 0x80, 0x45, 0x08, 0x80, 0x40, +0x06, 0x30, 0xDC, 0x40, 0x57, 0x88, 0x58, 0x3E, 0x84, 0x80, 0x45, 0x08, 0x00, 0x42, 0x8A, 0x51, +0x93, 0xA1, 0x8A, 0x51, 0xEB, 0x80, 0x57, 0x88, 0x27, 0x7E, 0x84, 0x80, 0x6B, 0x88, 0x80, 0x40, +0x57, 0x88, 0x27, 0x7E, 0x84, 0x80, 0x00, 0x48, 0xEB, 0x80, 0x57, 0x88, 0x2F, 0xBE, 0x84, 0x80, +0x00, 0x48, 0x6B, 0x07, 0xEC, 0x40, 0x57, 0x88, 0x2D, 0x7E, 0x84, 0x80, 0x6C, 0x48, 0x80, 0x40, +0x57, 0x88, 0x2D, 0x7E, 0x84, 0x80, 0x7F, 0x70, 0x00, 0x42, 0x03, 0x5C, 0x04, 0x6B, 0x57, 0x88, +0x2D, 0x7E, 0x84, 0x80, 0x7F, 0x70, 0x80, 0x40, 0x57, 0xD8, 0x3A, 0x2B, 0x57, 0x88, 0x2D, 0x7E, +0x84, 0x80, 0x00, 0x48, 0xDC, 0x40, 0x03, 0x30, 0xDD, 0x80, 0x5C, 0x48, 0x96, 0x00, 0x5D, 0x88, +0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x57, 0xD8, 0x48, 0xAB, 0x57, 0x88, 0x58, 0x3E, 0x84, 0x80, 0x83, 0x93, 0x00, 0x48, 0xEB, 0x80, 0x01, 0xF0, 0x03, 0xD0, 0xEB, 0x4D, 0xFF, 0x7E, 0x03, 0xD0, -0x03, 0x9D, 0x45, 0xEB, 0x6B, 0x0D, 0x02, 0xBE, 0xEF, 0xC0, 0x6F, 0xC8, 0xDD, 0x80, 0x01, 0xF0, -0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x01, 0xF0, -0xDD, 0xC1, 0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, -0xD7, 0x0A, 0x88, 0x6A, 0x57, 0x88, 0x2F, 0xBE, 0x84, 0x80, 0x00, 0x48, 0xDD, 0x80, 0x04, 0xF0, -0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x3B, 0x6B, -0x57, 0x88, 0x59, 0x7E, 0x84, 0x80, 0x83, 0x93, 0x00, 0x48, 0xEB, 0x80, 0x01, 0xF0, 0x03, 0xD0, -0xEB, 0x4D, 0xFF, 0x7E, 0x03, 0xD0, 0x03, 0x9D, 0x78, 0x2B, 0x6B, 0x0D, 0x02, 0xBE, 0xEF, 0xC0, -0x6F, 0xC8, 0xDD, 0x80, 0x02, 0xF0, 0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, -0x17, 0x94, 0x17, 0x50, 0x02, 0xF0, 0xDD, 0xC1, 0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, -0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x60, 0xAB, 0xD1, 0x41, 0x51, 0x08, 0xDD, 0x80, 0x05, 0x30, -0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x51, 0x08, -0xA0, 0xFE, 0x84, 0x80, 0x3F, 0xC8, 0x83, 0x93, 0x80, 0x40, 0x51, 0x08, 0xA0, 0xFE, 0x84, 0x80, -0x03, 0x14, 0x00, 0xCD, 0xEF, 0xC0, 0x6F, 0xC8, 0xDD, 0x80, 0x06, 0x30, 0xDE, 0x80, 0x5D, 0x88, -0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x06, 0x30, 0xDD, 0xC1, 0xDE, 0x80, -0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x08, 0xF0, 0xD1, 0x8A, -0x51, 0x02, 0x03, 0x5C, 0x95, 0x2B, 0x12, 0x18, 0x1C, 0x9C, 0xCC, 0x2B, 0x1C, 0x10, 0x9B, 0xD4, -0x00, 0xB0, 0x8A, 0x95, 0x0F, 0xA2, 0x8A, 0x51, 0x12, 0x5C, 0x1C, 0x58, 0xD5, 0x6B, 0x00, 0xB0, -0x8A, 0x95, 0x75, 0xE1, 0x8A, 0x51, 0x1C, 0x54, 0x9B, 0x90, 0x92, 0x58, 0x9C, 0xDC, 0xDE, 0xAB, -0x9C, 0x50, 0x9B, 0xD4, 0x01, 0xF0, 0x8A, 0x95, 0x0F, 0xA2, 0x8A, 0x51, 0x92, 0x9C, 0x9C, 0x98, -0xE7, 0xAB, 0x01, 0xF0, 0x8A, 0x95, 0x75, 0xE1, 0x8A, 0x51, 0x9C, 0x94, 0x9B, 0x90, 0xD1, 0x41, -0x51, 0x08, 0xDD, 0x80, 0x05, 0x30, 0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, -0x17, 0x94, 0x17, 0x50, 0x24, 0x30, 0xDD, 0x80, 0x5D, 0x88, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, -0x0D, 0x08, 0xD2, 0x00, 0x52, 0x08, 0x41, 0xC2, 0x03, 0x18, 0x4D, 0xEC, 0x51, 0x08, 0xA0, 0xFE, -0x84, 0x80, 0x3D, 0x88, 0x83, 0x93, 0x00, 0x42, 0x03, 0x18, 0x4D, 0xEC, 0x51, 0x08, 0xA0, 0xFE, -0x84, 0x80, 0x00, 0x48, 0x01, 0xBE, 0xEB, 0x80, 0x51, 0x08, 0xA0, 0xFE, 0x84, 0x80, 0x6B, 0x88, -0x80, 0x40, 0x51, 0x08, 0xA0, 0xFE, 0x84, 0x80, 0x03, 0x14, 0x00, 0xCD, 0xEF, 0xC0, 0x6F, 0xC8, -0xDD, 0x80, 0x06, 0x30, 0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, -0x17, 0x50, 0x06, 0x30, 0xDD, 0xC1, 0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, -0x17, 0x94, 0x17, 0x50, 0x08, 0xF0, 0xD1, 0x8A, 0x51, 0x02, 0x03, 0x5C, 0xE8, 0x2B, 0x59, 0xCE, -0xF0, 0x39, 0x23, 0x04, 0x9E, 0x40, 0x21, 0x30, 0xD7, 0xC1, 0xDD, 0x80, 0x5D, 0x88, 0x95, 0x00, -0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xD3, 0x40, 0x20, 0xF0, 0xDD, 0x80, 0x5D, 0x88, 0x95, 0x00, -0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xD0, 0xC0, 0xD5, 0x81, 0x57, 0x88, 0x29, 0x3E, 0x84, 0x80, -0x83, 0x93, 0x80, 0x88, 0x03, 0x59, 0x7E, 0x6C, 0x0D, 0xED, 0x43, 0x08, 0x52, 0x02, 0x03, 0x18, -0x2A, 0xAC, 0x51, 0x08, 0xA0, 0xFE, 0x84, 0x80, 0x83, 0x93, 0x00, 0x48, 0x3F, 0xC2, 0x03, 0x18, -0x2A, 0xAC, 0x51, 0x08, 0xA0, 0xFE, 0x84, 0x80, 0x00, 0x48, 0xFF, 0x7E, 0xEB, 0x80, 0x51, 0x08, -0xA0, 0xFE, 0x84, 0x80, 0x6B, 0x88, 0x80, 0x40, 0x51, 0x08, 0xA0, 0xFE, 0x84, 0x80, 0x03, 0x14, -0x00, 0xCD, 0xEF, 0xC0, 0x6F, 0xC8, 0xDD, 0x80, 0x06, 0x30, 0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, -0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x06, 0x30, 0xDD, 0xC1, 0xDE, 0x80, 0x5D, 0x88, -0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x2A, 0xAC, 0x57, 0x88, 0x23, 0x3E, -0x84, 0x80, 0x00, 0x48, 0x37, 0xC6, 0x03, 0x9D, 0x87, 0xEC, 0x49, 0x08, 0x88, 0x6C, 0x4B, 0x48, -0xD4, 0x00, 0x53, 0x48, 0x54, 0x02, 0x03, 0x18, 0xC2, 0xAC, 0x57, 0x88, 0x59, 0x7E, 0x84, 0x80, -0x45, 0x08, 0x00, 0x42, 0x03, 0x18, 0xC2, 0xAC, 0x57, 0x88, 0x59, 0x7E, 0x84, 0x80, 0x00, 0x48, -0x01, 0xBE, 0xEB, 0x80, 0x57, 0x88, 0x59, 0x7E, 0x84, 0x80, 0x6B, 0x88, 0x80, 0x40, 0x57, 0x88, -0x59, 0x7E, 0x84, 0x80, 0x00, 0x48, 0xEB, 0x80, 0x01, 0xF0, 0x03, 0xD0, 0xEB, 0x4D, 0xFF, 0x7E, -0x03, 0xD0, 0x03, 0x9D, 0xA6, 0xEC, 0x6B, 0x0D, 0x02, 0xBE, 0xEF, 0xC0, 0x6F, 0xC8, 0xDD, 0x80, -0x01, 0xF0, 0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, -0x01, 0xF0, 0xDD, 0xC1, 0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, -0x17, 0x50, 0xFA, 0x6C, 0x4D, 0x48, 0x53, 0x42, 0x57, 0x88, 0x03, 0x18, 0x0E, 0xED, 0x59, 0x7E, -0x84, 0x80, 0x00, 0x48, 0x47, 0x42, 0x57, 0x88, 0x03, 0x18, 0x0E, 0xED, 0x59, 0x7E, 0x84, 0x80, -0x00, 0x48, 0xFF, 0x7E, 0xEB, 0x80, 0x57, 0x88, 0x59, 0x7E, 0x84, 0x80, 0x6B, 0x88, 0x80, 0x40, -0x57, 0x88, 0x59, 0x7E, 0x84, 0x80, 0x00, 0x48, 0xEB, 0x80, 0x01, 0xF0, 0x03, 0xD0, 0xEB, 0x4D, -0xFF, 0x7E, 0x03, 0xD0, 0x03, 0x9D, 0xDF, 0xAC, 0x6B, 0x0D, 0x02, 0xBE, 0xEF, 0xC0, 0x6F, 0xC8, -0xDD, 0x80, 0x01, 0xF0, 0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, -0x17, 0x50, 0x01, 0xF0, 0xDD, 0xC1, 0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, -0x17, 0x94, 0x17, 0x50, 0x06, 0x30, 0xD5, 0x81, 0xD5, 0xCA, 0xDD, 0x80, 0x57, 0x88, 0x59, 0x7E, -0x84, 0x80, 0x47, 0x48, 0x83, 0x93, 0x00, 0x42, 0x8A, 0x51, 0xBD, 0x21, 0x8A, 0x51, 0xEB, 0x80, -0x57, 0x88, 0x27, 0x7E, 0x84, 0x80, 0x6B, 0x88, 0x80, 0x40, 0x57, 0x88, 0xA8, 0x3E, 0x84, 0x80, -0x80, 0x88, 0x03, 0x9D, 0xEE, 0xAD, 0x50, 0xC8, 0x39, 0x42, 0x03, 0x18, 0x78, 0x2D, 0x57, 0x88, -0x23, 0x3E, 0x84, 0x80, 0x35, 0x48, 0x00, 0x42, 0x03, 0x18, 0x78, 0x2D, 0x57, 0x88, 0x23, 0x3E, -0x84, 0x80, 0x00, 0x48, 0x01, 0xBE, 0xEB, 0x80, 0x57, 0x88, 0x23, 0x3E, 0x84, 0x80, 0x6B, 0x88, +0x03, 0x9D, 0x1D, 0x2B, 0x6B, 0x0D, 0x02, 0xBE, 0xEF, 0xC0, 0x6F, 0xC8, 0xDC, 0x40, 0x01, 0xF0, +0xDD, 0x80, 0x5C, 0x48, 0x96, 0x00, 0x5D, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x01, 0xF0, +0xDC, 0x81, 0xDD, 0x80, 0x5C, 0x48, 0x96, 0x00, 0x5D, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, +0xD7, 0x0A, 0x61, 0xAA, 0x57, 0x88, 0x2D, 0x7E, 0x84, 0x80, 0x00, 0x48, 0xDC, 0x40, 0x04, 0xF0, +0xDD, 0x80, 0x5C, 0x48, 0x96, 0x00, 0x5D, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x13, 0xEB, +0x57, 0x88, 0x58, 0x3E, 0x84, 0x80, 0x83, 0x93, 0x00, 0x48, 0xEB, 0x80, 0x01, 0xF0, 0x03, 0xD0, +0xEB, 0x4D, 0xFF, 0x7E, 0x03, 0xD0, 0x03, 0x9D, 0x50, 0xAB, 0x6B, 0x0D, 0x02, 0xBE, 0xEF, 0xC0, +0x6F, 0xC8, 0xDC, 0x40, 0x02, 0xF0, 0xDD, 0x80, 0x5C, 0x48, 0x96, 0x00, 0x5D, 0x88, 0x95, 0x00, +0x17, 0x94, 0x17, 0x50, 0x02, 0xF0, 0xDC, 0x81, 0xDD, 0x80, 0x5C, 0x48, 0x96, 0x00, 0x5D, 0x88, +0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x38, 0xEB, 0xD1, 0x41, 0x51, 0x08, 0xDC, 0x40, 0x05, 0x30, +0xDD, 0x80, 0x5C, 0x48, 0x96, 0x00, 0x5D, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x51, 0x08, +0xA0, 0xFE, 0x84, 0x80, 0x3D, 0x88, 0x83, 0x93, 0x80, 0x40, 0x51, 0x08, 0xA0, 0xFE, 0x84, 0x80, +0x03, 0x14, 0x00, 0xCD, 0xEF, 0xC0, 0x6F, 0xC8, 0xDC, 0x40, 0x06, 0x30, 0xDD, 0x80, 0x5C, 0x48, +0x96, 0x00, 0x5D, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x06, 0x30, 0xDC, 0x81, 0xDD, 0x80, +0x5C, 0x48, 0x96, 0x00, 0x5D, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x08, 0xF0, 0xD1, 0x8A, +0x51, 0x02, 0x03, 0x5C, 0x6D, 0x6B, 0x9B, 0x81, 0x6E, 0x90, 0x3F, 0x30, 0xDC, 0x40, 0x5C, 0x48, +0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xEF, 0xC0, 0x6F, 0xC8, 0xFF, 0x3A, 0x03, 0x59, +0x9B, 0x81, 0x12, 0x18, 0x1C, 0x9C, 0xBF, 0xEB, 0x1C, 0x10, 0x4D, 0x48, 0x03, 0x9D, 0x9D, 0x5B, +0xB2, 0x2B, 0xBC, 0x6B, 0xEE, 0x14, 0x1B, 0x48, 0xBF, 0xF9, 0x01, 0x38, 0x9B, 0x40, 0x00, 0xB0, +0x8A, 0x95, 0xB1, 0xA1, 0x8A, 0x51, 0xBF, 0xEB, 0xEE, 0xD0, 0x41, 0x30, 0x9B, 0x84, 0x12, 0x5C, +0x1C, 0x58, 0xCA, 0x2B, 0xEE, 0x5C, 0xC9, 0x2B, 0xEE, 0xD0, 0x00, 0xB0, 0x8A, 0x95, 0x17, 0xA1, +0x8A, 0x51, 0x1C, 0x54, 0x92, 0x58, 0x9C, 0xDC, 0xE0, 0xEB, 0x9C, 0x50, 0x4D, 0x48, 0x03, 0x9D, +0x9D, 0x5B, 0xD3, 0x6B, 0xDD, 0xAB, 0x6E, 0x15, 0x1B, 0x48, 0x7F, 0xF9, 0x02, 0x38, 0x9B, 0x40, +0x01, 0xF0, 0x8A, 0x95, 0xB1, 0xA1, 0x8A, 0x51, 0xE0, 0xEB, 0x6E, 0xD1, 0x82, 0x30, 0x9B, 0x84, +0x92, 0x9C, 0x9C, 0x98, 0xEB, 0xAB, 0x6E, 0x5D, 0xEA, 0x6B, 0x6E, 0xD1, 0x01, 0xF0, 0x8A, 0x95, +0x17, 0xA1, 0x8A, 0x51, 0x9C, 0x94, 0xD1, 0x41, 0x51, 0x08, 0xDC, 0x40, 0x05, 0x30, 0xDD, 0x80, +0x5C, 0x48, 0x96, 0x00, 0x5D, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x24, 0x30, 0xDC, 0x40, +0x5C, 0x48, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xD2, 0x00, 0x52, 0x08, 0x3F, 0xC2, +0x03, 0x18, 0x51, 0xAC, 0x51, 0x08, 0xA0, 0xFE, 0x84, 0x80, 0x3B, 0x88, 0x83, 0x93, 0x00, 0x42, +0x03, 0x18, 0x51, 0xAC, 0x51, 0x08, 0xA0, 0xFE, 0x84, 0x80, 0x00, 0x48, 0x01, 0xBE, 0xEB, 0x80, +0x51, 0x08, 0xA0, 0xFE, 0x84, 0x80, 0x6B, 0x88, 0x80, 0x40, 0x51, 0x08, 0xA0, 0xFE, 0x84, 0x80, +0x03, 0x14, 0x00, 0xCD, 0xEF, 0xC0, 0x6F, 0xC8, 0xDC, 0x40, 0x06, 0x30, 0xDD, 0x80, 0x5C, 0x48, +0x96, 0x00, 0x5D, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x06, 0x30, 0xDC, 0x81, 0xDD, 0x80, +0x5C, 0x48, 0x96, 0x00, 0x5D, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x08, 0xF0, 0xD1, 0x8A, +0x51, 0x02, 0x03, 0x5C, 0xEC, 0x6B, 0x58, 0x8E, 0xF0, 0x39, 0x23, 0x04, 0x9E, 0x40, 0x21, 0x30, +0xD7, 0xC1, 0xDC, 0x40, 0x5C, 0x48, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xD3, 0x40, +0x20, 0xF0, 0xDC, 0x40, 0x5C, 0x48, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xD0, 0xC0, +0xD5, 0x81, 0x57, 0x88, 0xAC, 0x7E, 0x84, 0x80, 0x83, 0x93, 0x80, 0x88, 0x03, 0x59, 0x82, 0x6C, +0x11, 0xAD, 0x41, 0xC8, 0x52, 0x02, 0x03, 0x18, 0x2E, 0xEC, 0x51, 0x08, 0xA0, 0xFE, 0x84, 0x80, +0x83, 0x93, 0x00, 0x48, 0x3D, 0x82, 0x03, 0x18, 0x2E, 0xEC, 0x51, 0x08, 0xA0, 0xFE, 0x84, 0x80, +0x00, 0x48, 0xFF, 0x7E, 0xEB, 0x80, 0x51, 0x08, 0xA0, 0xFE, 0x84, 0x80, 0x6B, 0x88, 0x80, 0x40, +0x51, 0x08, 0xA0, 0xFE, 0x84, 0x80, 0x03, 0x14, 0x00, 0xCD, 0xEF, 0xC0, 0x6F, 0xC8, 0xDC, 0x40, +0x06, 0x30, 0xDD, 0x80, 0x5C, 0x48, 0x96, 0x00, 0x5D, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, +0x06, 0x30, 0xDC, 0x81, 0xDD, 0x80, 0x5C, 0x48, 0x96, 0x00, 0x5D, 0x88, 0x95, 0x00, 0x17, 0x94, +0x17, 0x50, 0x2E, 0xEC, 0x57, 0x88, 0x23, 0x3E, 0x84, 0x80, 0x00, 0x48, 0x35, 0x86, 0x03, 0x9D, +0x8B, 0xEC, 0x47, 0x48, 0x8C, 0xAC, 0x49, 0x08, 0xD4, 0x00, 0x53, 0x48, 0x54, 0x02, 0x03, 0x18, +0xC6, 0xEC, 0x57, 0x88, 0x58, 0x3E, 0x84, 0x80, 0x43, 0x08, 0x00, 0x42, 0x03, 0x18, 0xC6, 0xEC, +0x57, 0x88, 0x58, 0x3E, 0x84, 0x80, 0x00, 0x48, 0x01, 0xBE, 0xEB, 0x80, 0x57, 0x88, 0x58, 0x3E, +0x84, 0x80, 0x6B, 0x88, 0x80, 0x40, 0x57, 0x88, 0x58, 0x3E, 0x84, 0x80, 0x00, 0x48, 0xEB, 0x80, +0x01, 0xF0, 0x03, 0xD0, 0xEB, 0x4D, 0xFF, 0x7E, 0x03, 0xD0, 0x03, 0x9D, 0xAA, 0xEC, 0x6B, 0x0D, +0x02, 0xBE, 0xEF, 0xC0, 0x6F, 0xC8, 0xDC, 0x40, 0x01, 0xF0, 0xDD, 0x80, 0x5C, 0x48, 0x96, 0x00, +0x5D, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x01, 0xF0, 0xDC, 0x81, 0xDD, 0x80, 0x5C, 0x48, +0x96, 0x00, 0x5D, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0xFE, 0xAC, 0x4B, 0x48, 0x53, 0x42, +0x57, 0x88, 0x03, 0x18, 0x12, 0xAD, 0x58, 0x3E, 0x84, 0x80, 0x00, 0x48, 0x45, 0x02, 0x57, 0x88, +0x03, 0x18, 0x12, 0xAD, 0x58, 0x3E, 0x84, 0x80, 0x00, 0x48, 0xFF, 0x7E, 0xEB, 0x80, 0x57, 0x88, +0x58, 0x3E, 0x84, 0x80, 0x6B, 0x88, 0x80, 0x40, 0x57, 0x88, 0x58, 0x3E, 0x84, 0x80, 0x00, 0x48, +0xEB, 0x80, 0x01, 0xF0, 0x03, 0xD0, 0xEB, 0x4D, 0xFF, 0x7E, 0x03, 0xD0, 0x03, 0x9D, 0xE3, 0x2C, +0x6B, 0x0D, 0x02, 0xBE, 0xEF, 0xC0, 0x6F, 0xC8, 0xDC, 0x40, 0x01, 0xF0, 0xDD, 0x80, 0x5C, 0x48, +0x96, 0x00, 0x5D, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x01, 0xF0, 0xDC, 0x81, 0xDD, 0x80, +0x5C, 0x48, 0x96, 0x00, 0x5D, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x06, 0x30, 0xD5, 0x81, +0xD5, 0xCA, 0xDC, 0x40, 0x57, 0x88, 0x58, 0x3E, 0x84, 0x80, 0x45, 0x08, 0x83, 0x93, 0x00, 0x42, +0x8A, 0x51, 0x93, 0xA1, 0x8A, 0x51, 0xEB, 0x80, 0x57, 0x88, 0x27, 0x7E, 0x84, 0x80, 0x6B, 0x88, +0x80, 0x40, 0x57, 0x88, 0xA8, 0x3E, 0x84, 0x80, 0x80, 0x88, 0x03, 0x9D, 0xF0, 0x2D, 0x50, 0xC8, +0x37, 0x82, 0x03, 0x18, 0x7B, 0xAD, 0x57, 0x88, 0x23, 0x3E, 0x84, 0x80, 0x33, 0x48, 0x00, 0x42, +0x03, 0x18, 0x7B, 0xAD, 0x57, 0x88, 0x23, 0x3E, 0x84, 0x80, 0x00, 0x48, 0x01, 0xBE, 0xEB, 0x80, +0x57, 0x88, 0x23, 0x3E, 0x84, 0x80, 0x6B, 0x88, 0x80, 0x40, 0x57, 0x88, 0x23, 0x3E, 0x84, 0x80, +0x00, 0x48, 0x01, 0xBE, 0x84, 0x80, 0x8A, 0x51, 0x02, 0xA0, 0x8A, 0x51, 0x0F, 0x39, 0xEB, 0x80, +0x03, 0xD0, 0xEB, 0x4D, 0x57, 0x88, 0x23, 0x3E, 0x84, 0x80, 0x00, 0x48, 0x15, 0x3E, 0x84, 0x80, +0x8A, 0x51, 0x02, 0xA0, 0x8A, 0x51, 0x07, 0xF9, 0xEC, 0x40, 0x04, 0xF0, 0x03, 0xD0, 0xEC, 0x0D, +0xFF, 0x7E, 0x03, 0xD0, 0x03, 0x9D, 0x47, 0x2D, 0x6C, 0xCD, 0x6B, 0x84, 0xED, 0x80, 0x57, 0x88, +0x21, 0xFE, 0x84, 0x80, 0x6D, 0x88, 0x80, 0x40, 0x8C, 0x70, 0x83, 0x96, 0xB8, 0x00, 0x83, 0x52, +0x57, 0x88, 0x21, 0xFE, 0x84, 0x80, 0x00, 0x48, 0x83, 0x96, 0xB9, 0x40, 0xB8, 0xB0, 0x83, 0x52, +0xDC, 0x40, 0x57, 0x88, 0xDD, 0x80, 0x02, 0xF0, 0x8A, 0x95, 0xC5, 0xE3, 0x8A, 0x51, 0x01, 0xF0, +0xDC, 0x81, 0xDC, 0xCA, 0xDD, 0x80, 0x5C, 0x48, 0x96, 0x00, 0x5D, 0x88, 0x95, 0x00, 0x17, 0x94, +0x17, 0x50, 0x01, 0xF0, 0xDC, 0x81, 0xDD, 0x80, 0x5C, 0x48, 0x96, 0x00, 0x5D, 0x88, 0x95, 0x00, +0x17, 0x94, 0x17, 0x50, 0xDE, 0xAD, 0x39, 0x48, 0x50, 0xC2, 0x03, 0x18, 0xF0, 0x2D, 0x57, 0x88, +0x23, 0x3E, 0x84, 0x80, 0x00, 0x48, 0x35, 0x42, 0x03, 0x18, 0xF0, 0x2D, 0x57, 0x88, 0x23, 0x3E, +0x84, 0x80, 0x00, 0x48, 0xFF, 0x7E, 0xEB, 0x80, 0x57, 0x88, 0x23, 0x3E, 0x84, 0x80, 0x6B, 0x88, 0x80, 0x40, 0x57, 0x88, 0x23, 0x3E, 0x84, 0x80, 0x00, 0x48, 0x01, 0xBE, 0x84, 0x80, 0x8A, 0x51, 0x02, 0xA0, 0x8A, 0x51, 0x0F, 0x39, 0xEB, 0x80, 0x03, 0xD0, 0xEB, 0x4D, 0x57, 0x88, 0x23, 0x3E, 0x84, 0x80, 0x00, 0x48, 0x15, 0x3E, 0x84, 0x80, 0x8A, 0x51, 0x02, 0xA0, 0x8A, 0x51, 0x07, 0xF9, -0xEC, 0x40, 0x04, 0xF0, 0x03, 0xD0, 0xEC, 0x0D, 0xFF, 0x7E, 0x03, 0xD0, 0x03, 0x9D, 0x43, 0xED, -0x6C, 0xCD, 0x6B, 0x84, 0x01, 0x38, 0xED, 0x80, 0x57, 0x88, 0x21, 0xFE, 0x84, 0x80, 0x6D, 0x88, -0x80, 0x40, 0x8C, 0x70, 0x83, 0x96, 0xB5, 0x40, 0x83, 0x52, 0x57, 0x88, 0x21, 0xFE, 0x84, 0x80, -0x00, 0x48, 0x83, 0x96, 0xB6, 0x40, 0xB5, 0xF0, 0x83, 0x52, 0xDD, 0x80, 0x57, 0x88, 0xDE, 0x80, -0x02, 0xF0, 0x8A, 0x95, 0x15, 0x64, 0x8A, 0x51, 0x01, 0xF0, 0xDD, 0xC1, 0xDD, 0x0A, 0xDE, 0x80, -0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x01, 0xF0, 0xDD, 0xC1, -0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0xDC, 0x6D, -0x3B, 0x88, 0x50, 0xC2, 0x03, 0x18, 0xEE, 0xAD, 0x57, 0x88, 0x23, 0x3E, 0x84, 0x80, 0x00, 0x48, -0x37, 0x82, 0x03, 0x18, 0xEE, 0xAD, 0x57, 0x88, 0x23, 0x3E, 0x84, 0x80, 0x00, 0x48, 0xFF, 0x7E, -0xEB, 0x80, 0x57, 0x88, 0x23, 0x3E, 0x84, 0x80, 0x6B, 0x88, 0x80, 0x40, 0x57, 0x88, 0x23, 0x3E, -0x84, 0x80, 0x00, 0x48, 0x01, 0xBE, 0x84, 0x80, 0x8A, 0x51, 0x02, 0xA0, 0x8A, 0x51, 0x0F, 0x39, -0xEB, 0x80, 0x03, 0xD0, 0xEB, 0x4D, 0x57, 0x88, 0x23, 0x3E, 0x84, 0x80, 0x00, 0x48, 0x15, 0x3E, -0x84, 0x80, 0x8A, 0x51, 0x02, 0xA0, 0x8A, 0x51, 0x07, 0xF9, 0xEC, 0x40, 0x04, 0xF0, 0x03, 0xD0, -0xEC, 0x0D, 0xFF, 0x7E, 0x03, 0xD0, 0x03, 0x9D, 0xA8, 0xED, 0x6C, 0xCD, 0x6B, 0x84, 0x01, 0x38, -0xED, 0x80, 0x57, 0x88, 0x21, 0xFE, 0x84, 0x80, 0x6D, 0x88, 0x80, 0x40, 0x8C, 0x70, 0x83, 0x96, -0xB5, 0x40, 0x83, 0x52, 0x57, 0x88, 0x21, 0xFE, 0x84, 0x80, 0x00, 0x48, 0x83, 0x96, 0xB6, 0x40, -0xB5, 0xF0, 0x83, 0x52, 0xDD, 0x80, 0x57, 0x88, 0xDE, 0x80, 0x02, 0xF0, 0x8A, 0x95, 0x15, 0x64, -0x8A, 0x51, 0x01, 0xF0, 0xDD, 0xC1, 0xDD, 0x0A, 0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, -0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x01, 0xF0, 0xDD, 0xC1, 0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, -0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0xD5, 0x81, 0xD5, 0xCA, 0x57, 0x88, 0x23, 0x3E, -0x84, 0x80, 0x83, 0x93, 0x00, 0x48, 0x0B, 0x3E, 0x84, 0x80, 0x8A, 0x51, 0x02, 0xA0, 0x8A, 0x51, -0xEB, 0x80, 0x57, 0x88, 0x31, 0x3E, 0x84, 0x80, 0x6B, 0x88, 0x80, 0x40, 0x55, 0xCB, 0x19, 0xEE, -0x57, 0x88, 0x27, 0x7E, 0x84, 0x80, 0x00, 0x48, 0xEB, 0x80, 0x57, 0x88, 0x31, 0x3E, 0x84, 0x80, -0x00, 0x48, 0x6B, 0x07, 0xEC, 0x40, 0x57, 0x88, 0x2F, 0xBE, 0x84, 0x80, 0x6C, 0x48, 0x80, 0x40, -0x57, 0x88, 0x2F, 0xBE, 0x84, 0x80, 0x7F, 0x70, 0x00, 0x42, 0x57, 0x88, 0x03, 0x5C, 0x0D, 0xEE, -0x2F, 0xBE, 0x84, 0x80, 0x7F, 0x70, 0x80, 0x40, 0x57, 0x88, 0x2F, 0xBE, 0x84, 0x80, 0x00, 0x48, -0xDD, 0x80, 0x03, 0x30, 0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, -0x17, 0x50, 0x23, 0x70, 0xD7, 0xC1, 0xD7, 0x0A, 0xDD, 0x80, 0x5D, 0x88, 0x95, 0x00, 0x97, 0xD4, -0x97, 0x90, 0x0D, 0x08, 0xD3, 0x40, 0x22, 0x30, 0xDD, 0x80, 0x5D, 0x88, 0x95, 0x00, 0x97, 0xD4, -0x97, 0x90, 0x0D, 0x08, 0xD0, 0xC0, 0xD6, 0x81, 0x57, 0x88, 0x29, 0x3E, 0x84, 0x80, 0x83, 0x93, -0x80, 0x88, 0x03, 0x9D, 0xC2, 0xEE, 0x57, 0x88, 0x23, 0x3E, 0x84, 0x80, 0x00, 0x48, 0x37, 0xC6, -0x03, 0x9D, 0x3C, 0x2E, 0x49, 0x08, 0x3D, 0x6E, 0x4B, 0x48, 0xD4, 0x00, 0x53, 0x48, 0x54, 0x02, -0x03, 0x18, 0x77, 0xAE, 0x57, 0x88, 0x59, 0x7E, 0x84, 0x80, 0x45, 0x08, 0x00, 0x42, 0x03, 0x18, -0x77, 0xAE, 0x57, 0x88, 0x59, 0x7E, 0x84, 0x80, 0x00, 0x48, 0x01, 0xBE, 0xEB, 0x80, 0x57, 0x88, -0x59, 0x7E, 0x84, 0x80, 0x6B, 0x88, 0x80, 0x40, 0x57, 0x88, 0x59, 0x7E, 0x84, 0x80, 0x00, 0x48, -0xEB, 0x80, 0x01, 0xF0, 0x03, 0xD0, 0xEB, 0x4D, 0xFF, 0x7E, 0x03, 0xD0, 0x03, 0x9D, 0x5B, 0x6E, -0x6B, 0x0D, 0x02, 0xBE, 0xEF, 0xC0, 0x6F, 0xC8, 0xDD, 0x80, 0x02, 0xF0, 0xDE, 0x80, 0x5D, 0x88, -0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x02, 0xF0, 0xDD, 0xC1, 0xDE, 0x80, -0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0xAF, 0xAE, 0x4D, 0x48, -0x53, 0x42, 0x57, 0x88, 0x03, 0x18, 0xC3, 0x2E, 0x59, 0x7E, 0x84, 0x80, 0x00, 0x48, 0x47, 0x42, -0x57, 0x88, 0x03, 0x18, 0xC3, 0x2E, 0x59, 0x7E, 0x84, 0x80, 0x00, 0x48, 0xFF, 0x7E, 0xEB, 0x80, -0x57, 0x88, 0x59, 0x7E, 0x84, 0x80, 0x6B, 0x88, 0x80, 0x40, 0x57, 0x88, 0x59, 0x7E, 0x84, 0x80, -0x00, 0x48, 0xEB, 0x80, 0x01, 0xF0, 0x03, 0xD0, 0xEB, 0x4D, 0xFF, 0x7E, 0x03, 0xD0, 0x03, 0x9D, -0x94, 0xEE, 0x6B, 0x0D, 0x02, 0xBE, 0xEF, 0xC0, 0x6F, 0xC8, 0xDD, 0x80, 0x02, 0xF0, 0xDE, 0x80, -0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x02, 0xF0, 0xDD, 0xC1, -0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x06, 0x30, -0xD6, 0x81, 0xD6, 0xCA, 0xDD, 0x80, 0x57, 0x88, 0x59, 0x7E, 0x84, 0x80, 0x47, 0x48, 0x83, 0x93, -0x00, 0x42, 0x8A, 0x51, 0xBD, 0x21, 0x8A, 0x51, 0xEB, 0x80, 0x57, 0x88, 0x27, 0x7E, 0x84, 0x80, -0x6B, 0x88, 0x80, 0x40, 0x57, 0x88, 0xA8, 0x3E, 0x84, 0x80, 0x80, 0x88, 0x03, 0x9D, 0xA3, 0x6F, -0x50, 0xC8, 0x39, 0x42, 0x03, 0x18, 0x2D, 0x6F, 0x57, 0x88, 0x23, 0x3E, 0x84, 0x80, 0x35, 0x48, -0x00, 0x42, 0x03, 0x18, 0x2D, 0x6F, 0x57, 0x88, 0x23, 0x3E, 0x84, 0x80, 0x00, 0x48, 0x01, 0xBE, -0xEB, 0x80, 0x57, 0x88, 0x23, 0x3E, 0x84, 0x80, 0x6B, 0x88, 0x80, 0x40, 0x57, 0x88, 0x23, 0x3E, -0x84, 0x80, 0x00, 0x48, 0x01, 0xBE, 0x84, 0x80, 0x8A, 0x51, 0x02, 0xA0, 0x8A, 0x51, 0x0F, 0x39, -0xEB, 0x80, 0x03, 0xD0, 0xEB, 0x4D, 0x57, 0x88, 0x23, 0x3E, 0x84, 0x80, 0x00, 0x48, 0x15, 0x3E, -0x84, 0x80, 0x8A, 0x51, 0x02, 0xA0, 0x8A, 0x51, 0x07, 0xF9, 0xEC, 0x40, 0x04, 0xF0, 0x03, 0xD0, -0xEC, 0x0D, 0xFF, 0x7E, 0x03, 0xD0, 0x03, 0x9D, 0xF8, 0x6E, 0x6C, 0xCD, 0x6B, 0x84, 0x01, 0x38, -0xED, 0x80, 0x57, 0x88, 0x21, 0xFE, 0x84, 0x80, 0x6D, 0x88, 0x80, 0x40, 0x8C, 0x70, 0x83, 0x96, -0xB5, 0x40, 0x83, 0x52, 0x57, 0x88, 0x21, 0xFE, 0x84, 0x80, 0x00, 0x48, 0x83, 0x96, 0xB6, 0x40, -0xB5, 0xF0, 0x83, 0x52, 0xDD, 0x80, 0x57, 0x88, 0xDE, 0x80, 0x02, 0xF0, 0x8A, 0x95, 0x15, 0x64, -0x8A, 0x51, 0x02, 0xF0, 0xDD, 0xC1, 0xDD, 0x0A, 0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, -0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x02, 0xF0, 0xDD, 0xC1, 0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, -0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x91, 0x2F, 0x3B, 0x88, 0x50, 0xC2, 0x03, 0x18, -0xA3, 0x6F, 0x57, 0x88, 0x23, 0x3E, 0x84, 0x80, 0x00, 0x48, 0x37, 0x82, 0x03, 0x18, 0xA3, 0x6F, -0x57, 0x88, 0x23, 0x3E, 0x84, 0x80, 0x00, 0x48, 0xFF, 0x7E, 0xEB, 0x80, 0x57, 0x88, 0x23, 0x3E, -0x84, 0x80, 0x6B, 0x88, 0x80, 0x40, 0x57, 0x88, 0x23, 0x3E, 0x84, 0x80, 0x00, 0x48, 0x01, 0xBE, -0x84, 0x80, 0x8A, 0x51, 0x02, 0xA0, 0x8A, 0x51, 0x0F, 0x39, 0xEB, 0x80, 0x03, 0xD0, 0xEB, 0x4D, -0x57, 0x88, 0x23, 0x3E, 0x84, 0x80, 0x00, 0x48, 0x15, 0x3E, 0x84, 0x80, 0x8A, 0x51, 0x02, 0xA0, -0x8A, 0x51, 0x07, 0xF9, 0xEC, 0x40, 0x04, 0xF0, 0x03, 0xD0, 0xEC, 0x0D, 0xFF, 0x7E, 0x03, 0xD0, -0x03, 0x9D, 0x5D, 0xAF, 0x6C, 0xCD, 0x6B, 0x84, 0x01, 0x38, 0xED, 0x80, 0x57, 0x88, 0x21, 0xFE, -0x84, 0x80, 0x6D, 0x88, 0x80, 0x40, 0x8C, 0x70, 0x83, 0x96, 0xB5, 0x40, 0x83, 0x52, 0x57, 0x88, -0x21, 0xFE, 0x84, 0x80, 0x00, 0x48, 0x83, 0x96, 0xB6, 0x40, 0xB5, 0xF0, 0x83, 0x52, 0xDD, 0x80, -0x57, 0x88, 0xDE, 0x80, 0x02, 0xF0, 0x8A, 0x95, 0x15, 0x64, 0x8A, 0x51, 0x02, 0xF0, 0xDD, 0xC1, -0xDD, 0x0A, 0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, -0x02, 0xF0, 0xDD, 0xC1, 0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, +0xEC, 0x40, 0x04, 0xF0, 0x03, 0xD0, 0xEC, 0x0D, 0xFF, 0x7E, 0x03, 0xD0, 0x03, 0x9D, 0xAB, 0x6D, +0x6C, 0xCD, 0x6B, 0x84, 0xED, 0x80, 0x57, 0x88, 0x21, 0xFE, 0x84, 0x80, 0x6D, 0x88, 0x80, 0x40, +0x8C, 0x70, 0x83, 0x96, 0xB8, 0x00, 0x83, 0x52, 0x57, 0x88, 0x21, 0xFE, 0x84, 0x80, 0x00, 0x48, +0x83, 0x96, 0xB9, 0x40, 0xB8, 0xB0, 0x83, 0x52, 0xDC, 0x40, 0x57, 0x88, 0xDD, 0x80, 0x02, 0xF0, +0x8A, 0x95, 0xC5, 0xE3, 0x8A, 0x51, 0x01, 0xF0, 0xDC, 0x81, 0xDC, 0xCA, 0xDD, 0x80, 0x5C, 0x48, +0x96, 0x00, 0x5D, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x01, 0xF0, 0xDC, 0x81, 0xDD, 0x80, +0x5C, 0x48, 0x96, 0x00, 0x5D, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0xD5, 0x81, 0xD5, 0xCA, +0x57, 0x88, 0x23, 0x3E, 0x84, 0x80, 0x83, 0x93, 0x00, 0x48, 0x0B, 0x3E, 0x84, 0x80, 0x8A, 0x51, +0x02, 0xA0, 0x8A, 0x51, 0xEB, 0x80, 0x57, 0x88, 0x2F, 0xBE, 0x84, 0x80, 0x6B, 0x88, 0x80, 0x40, +0x55, 0xCB, 0x1B, 0x2E, 0x57, 0x88, 0x27, 0x7E, 0x84, 0x80, 0x00, 0x48, 0xEB, 0x80, 0x57, 0x88, +0x2F, 0xBE, 0x84, 0x80, 0x00, 0x48, 0x6B, 0x07, 0xEC, 0x40, 0x57, 0x88, 0x2D, 0x7E, 0x84, 0x80, +0x6C, 0x48, 0x80, 0x40, 0x57, 0x88, 0x2D, 0x7E, 0x84, 0x80, 0x7F, 0x70, 0x00, 0x42, 0x57, 0x88, +0x03, 0x5C, 0x0F, 0x2E, 0x2D, 0x7E, 0x84, 0x80, 0x7F, 0x70, 0x80, 0x40, 0x57, 0x88, 0x2D, 0x7E, +0x84, 0x80, 0x00, 0x48, 0xDC, 0x40, 0x03, 0x30, 0xDD, 0x80, 0x5C, 0x48, 0x96, 0x00, 0x5D, 0x88, +0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x23, 0x70, 0xD7, 0xC1, 0xD7, 0x0A, 0xDC, 0x40, 0x5C, 0x48, +0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xD3, 0x40, 0x22, 0x30, 0xDC, 0x40, 0x5C, 0x48, +0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xD0, 0xC0, 0xD6, 0x81, 0x57, 0x88, 0xAC, 0x7E, +0x84, 0x80, 0x83, 0x93, 0x80, 0x88, 0x03, 0x9D, 0xC4, 0xEE, 0x57, 0x88, 0x23, 0x3E, 0x84, 0x80, +0x00, 0x48, 0x35, 0x86, 0x03, 0x9D, 0x3E, 0x6E, 0x47, 0x48, 0x3F, 0xAE, 0x49, 0x08, 0xD4, 0x00, +0x53, 0x48, 0x54, 0x02, 0x03, 0x18, 0x79, 0x6E, 0x57, 0x88, 0x58, 0x3E, 0x84, 0x80, 0x43, 0x08, +0x00, 0x42, 0x03, 0x18, 0x79, 0x6E, 0x57, 0x88, 0x58, 0x3E, 0x84, 0x80, 0x00, 0x48, 0x01, 0xBE, +0xEB, 0x80, 0x57, 0x88, 0x58, 0x3E, 0x84, 0x80, 0x6B, 0x88, 0x80, 0x40, 0x57, 0x88, 0x58, 0x3E, +0x84, 0x80, 0x00, 0x48, 0xEB, 0x80, 0x01, 0xF0, 0x03, 0xD0, 0xEB, 0x4D, 0xFF, 0x7E, 0x03, 0xD0, +0x03, 0x9D, 0x5D, 0x6E, 0x6B, 0x0D, 0x02, 0xBE, 0xEF, 0xC0, 0x6F, 0xC8, 0xDC, 0x40, 0x02, 0xF0, +0xDD, 0x80, 0x5C, 0x48, 0x96, 0x00, 0x5D, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x02, 0xF0, +0xDC, 0x81, 0xDD, 0x80, 0x5C, 0x48, 0x96, 0x00, 0x5D, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, +0xB1, 0x2E, 0x4B, 0x48, 0x53, 0x42, 0x57, 0x88, 0x03, 0x18, 0xC5, 0x2E, 0x58, 0x3E, 0x84, 0x80, +0x00, 0x48, 0x45, 0x02, 0x57, 0x88, 0x03, 0x18, 0xC5, 0x2E, 0x58, 0x3E, 0x84, 0x80, 0x00, 0x48, +0xFF, 0x7E, 0xEB, 0x80, 0x57, 0x88, 0x58, 0x3E, 0x84, 0x80, 0x6B, 0x88, 0x80, 0x40, 0x57, 0x88, +0x58, 0x3E, 0x84, 0x80, 0x00, 0x48, 0xEB, 0x80, 0x01, 0xF0, 0x03, 0xD0, 0xEB, 0x4D, 0xFF, 0x7E, +0x03, 0xD0, 0x03, 0x9D, 0x96, 0x2E, 0x6B, 0x0D, 0x02, 0xBE, 0xEF, 0xC0, 0x6F, 0xC8, 0xDC, 0x40, +0x02, 0xF0, 0xDD, 0x80, 0x5C, 0x48, 0x96, 0x00, 0x5D, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, +0x02, 0xF0, 0xDC, 0x81, 0xDD, 0x80, 0x5C, 0x48, 0x96, 0x00, 0x5D, 0x88, 0x95, 0x00, 0x17, 0x94, +0x17, 0x50, 0x06, 0x30, 0xD6, 0x81, 0xD6, 0xCA, 0xDC, 0x40, 0x57, 0x88, 0x58, 0x3E, 0x84, 0x80, +0x45, 0x08, 0x83, 0x93, 0x00, 0x42, 0x8A, 0x51, 0x93, 0xA1, 0x8A, 0x51, 0xEB, 0x80, 0x57, 0x88, +0x27, 0x7E, 0x84, 0x80, 0x6B, 0x88, 0x80, 0x40, 0x57, 0x88, 0xA8, 0x3E, 0x84, 0x80, 0x80, 0x88, +0x03, 0x9D, 0xA3, 0x6F, 0x50, 0xC8, 0x37, 0x82, 0x03, 0x18, 0x2E, 0x6F, 0x57, 0x88, 0x23, 0x3E, +0x84, 0x80, 0x33, 0x48, 0x00, 0x42, 0x03, 0x18, 0x2E, 0x6F, 0x57, 0x88, 0x23, 0x3E, 0x84, 0x80, +0x00, 0x48, 0x01, 0xBE, 0xEB, 0x80, 0x57, 0x88, 0x23, 0x3E, 0x84, 0x80, 0x6B, 0x88, 0x80, 0x40, +0x57, 0x88, 0x23, 0x3E, 0x84, 0x80, 0x00, 0x48, 0x01, 0xBE, 0x84, 0x80, 0x8A, 0x51, 0x02, 0xA0, +0x8A, 0x51, 0x0F, 0x39, 0xEB, 0x80, 0x03, 0xD0, 0xEB, 0x4D, 0x57, 0x88, 0x23, 0x3E, 0x84, 0x80, +0x00, 0x48, 0x15, 0x3E, 0x84, 0x80, 0x8A, 0x51, 0x02, 0xA0, 0x8A, 0x51, 0x07, 0xF9, 0xEC, 0x40, +0x04, 0xF0, 0x03, 0xD0, 0xEC, 0x0D, 0xFF, 0x7E, 0x03, 0xD0, 0x03, 0x9D, 0xFA, 0xAE, 0x6C, 0xCD, +0x6B, 0x84, 0xED, 0x80, 0x57, 0x88, 0x21, 0xFE, 0x84, 0x80, 0x6D, 0x88, 0x80, 0x40, 0x8C, 0x70, +0x83, 0x96, 0xB8, 0x00, 0x83, 0x52, 0x57, 0x88, 0x21, 0xFE, 0x84, 0x80, 0x00, 0x48, 0x83, 0x96, +0xB9, 0x40, 0xB8, 0xB0, 0x83, 0x52, 0xDC, 0x40, 0x57, 0x88, 0xDD, 0x80, 0x02, 0xF0, 0x8A, 0x95, +0xC5, 0xE3, 0x8A, 0x51, 0x02, 0xF0, 0xDC, 0x81, 0xDC, 0xCA, 0xDD, 0x80, 0x5C, 0x48, 0x96, 0x00, +0x5D, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x02, 0xF0, 0xDC, 0x81, 0xDD, 0x80, 0x5C, 0x48, +0x96, 0x00, 0x5D, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x91, 0x2F, 0x39, 0x48, 0x50, 0xC2, +0x03, 0x18, 0xA3, 0x6F, 0x57, 0x88, 0x23, 0x3E, 0x84, 0x80, 0x00, 0x48, 0x35, 0x42, 0x03, 0x18, +0xA3, 0x6F, 0x57, 0x88, 0x23, 0x3E, 0x84, 0x80, 0x00, 0x48, 0xFF, 0x7E, 0xEB, 0x80, 0x57, 0x88, +0x23, 0x3E, 0x84, 0x80, 0x6B, 0x88, 0x80, 0x40, 0x57, 0x88, 0x23, 0x3E, 0x84, 0x80, 0x00, 0x48, +0x01, 0xBE, 0x84, 0x80, 0x8A, 0x51, 0x02, 0xA0, 0x8A, 0x51, 0x0F, 0x39, 0xEB, 0x80, 0x03, 0xD0, +0xEB, 0x4D, 0x57, 0x88, 0x23, 0x3E, 0x84, 0x80, 0x00, 0x48, 0x15, 0x3E, 0x84, 0x80, 0x8A, 0x51, +0x02, 0xA0, 0x8A, 0x51, 0x07, 0xF9, 0xEC, 0x40, 0x04, 0xF0, 0x03, 0xD0, 0xEC, 0x0D, 0xFF, 0x7E, +0x03, 0xD0, 0x03, 0x9D, 0x5E, 0xAF, 0x6C, 0xCD, 0x6B, 0x84, 0xED, 0x80, 0x57, 0x88, 0x21, 0xFE, +0x84, 0x80, 0x6D, 0x88, 0x80, 0x40, 0x8C, 0x70, 0x83, 0x96, 0xB8, 0x00, 0x83, 0x52, 0x57, 0x88, +0x21, 0xFE, 0x84, 0x80, 0x00, 0x48, 0x83, 0x96, 0xB9, 0x40, 0xB8, 0xB0, 0x83, 0x52, 0xDC, 0x40, +0x57, 0x88, 0xDD, 0x80, 0x02, 0xF0, 0x8A, 0x95, 0xC5, 0xE3, 0x8A, 0x51, 0x02, 0xF0, 0xDC, 0x81, +0xDC, 0xCA, 0xDD, 0x80, 0x5C, 0x48, 0x96, 0x00, 0x5D, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, +0x02, 0xF0, 0xDC, 0x81, 0xDD, 0x80, 0x5C, 0x48, 0x96, 0x00, 0x5D, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0xD6, 0x81, 0xD6, 0xCA, 0x57, 0x88, 0x23, 0x3E, 0x84, 0x80, 0x83, 0x93, 0x00, 0x48, -0x0B, 0x3E, 0x84, 0x80, 0x8A, 0x51, 0x02, 0xA0, 0x8A, 0x51, 0xEB, 0x80, 0x57, 0x88, 0x31, 0x3E, -0x84, 0x80, 0x6B, 0x88, 0x80, 0x40, 0x56, 0xCB, 0xC3, 0x2B, 0x57, 0x88, 0x27, 0x7E, 0x84, 0x80, -0x00, 0x48, 0xEB, 0x80, 0x57, 0x88, 0x31, 0x3E, 0x84, 0x80, 0x00, 0x48, 0x6B, 0x07, 0xEC, 0x40, -0x57, 0x88, 0x2F, 0xBE, 0x84, 0x80, 0x6C, 0x48, 0x80, 0x40, 0x57, 0x88, 0x2F, 0xBE, 0x84, 0x80, -0x7F, 0x70, 0x00, 0x42, 0x57, 0x88, 0x03, 0x5C, 0xC2, 0x2F, 0x2F, 0xBE, 0x84, 0x80, 0x7F, 0x70, -0x80, 0x40, 0x57, 0x88, 0x2F, 0xBE, 0x84, 0x80, 0x00, 0x48, 0xDD, 0x80, 0x04, 0xF0, 0xDE, 0x80, -0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0xC3, 0x2B, 0xEE, 0xC1, -0x83, 0x93, 0x23, 0x70, 0x84, 0x80, 0x5D, 0xF0, 0x8A, 0x51, 0xB5, 0xE1, 0x8A, 0x51, 0xA0, 0x30, -0x84, 0x80, 0xD5, 0xF0, 0x8A, 0x51, 0xB5, 0xE1, 0x83, 0x96, 0x38, 0x70, 0xD5, 0x40, 0x3A, 0xB0, -0xD6, 0x40, 0x3C, 0xB0, 0xD7, 0x80, 0x78, 0xB0, 0xD8, 0x00, 0x7A, 0xF0, 0xD9, 0x40, 0x7C, 0xF0, -0xDA, 0x40, 0x7D, 0x30, 0xDB, 0x80, 0x7F, 0x70, 0xDC, 0x40, 0xB9, 0xF0, 0xDD, 0x80, 0xBA, 0xF0, -0xDE, 0x80, 0xBB, 0x30, 0xDF, 0xC0, 0xFA, 0x30, 0xE0, 0xC0, 0xFB, 0x70, 0xE1, 0x00, 0xFC, 0x30, -0xE2, 0x00, 0xFD, 0x70, 0xE3, 0x40, 0xFF, 0xB0, 0xE4, 0x00, 0x83, 0x01, 0x8A, 0x51, 0x21, 0x6A, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0x0B, 0x3E, 0x84, 0x80, 0x8A, 0x51, 0x02, 0xA0, 0x8A, 0x51, 0xEB, 0x80, 0x57, 0x88, 0x2F, 0xBE, +0x84, 0x80, 0x6B, 0x88, 0x80, 0x40, 0x56, 0xCB, 0x9D, 0x6B, 0x57, 0x88, 0x27, 0x7E, 0x84, 0x80, +0x00, 0x48, 0xEB, 0x80, 0x57, 0x88, 0x2F, 0xBE, 0x84, 0x80, 0x00, 0x48, 0x6B, 0x07, 0xEC, 0x40, +0x57, 0x88, 0x2D, 0x7E, 0x84, 0x80, 0x6C, 0x48, 0x80, 0x40, 0x57, 0x88, 0x2D, 0x7E, 0x84, 0x80, +0x7F, 0x70, 0x00, 0x42, 0x57, 0x88, 0x03, 0x5C, 0xC2, 0x2F, 0x2D, 0x7E, 0x84, 0x80, 0x7F, 0x70, +0x80, 0x40, 0x57, 0x88, 0x2D, 0x7E, 0x84, 0x80, 0x00, 0x48, 0xDC, 0x40, 0x04, 0xF0, 0xDD, 0x80, +0x5C, 0x48, 0x96, 0x00, 0x5D, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x9D, 0x6B, 0xEE, 0xC1, +0x83, 0x93, 0x23, 0x70, 0x84, 0x80, 0x5C, 0xB0, 0x8A, 0x51, 0x8B, 0xA1, 0x8A, 0x51, 0xA0, 0x30, +0x84, 0x80, 0xD8, 0xB0, 0x8A, 0x51, 0x8B, 0xA1, 0x83, 0x96, 0x38, 0x70, 0xD8, 0x00, 0x3A, 0xB0, +0xD9, 0x40, 0x3C, 0xB0, 0xDA, 0x40, 0x78, 0xB0, 0xDB, 0x80, 0x7A, 0xF0, 0xDC, 0x40, 0x7C, 0xF0, +0xDD, 0x80, 0x7D, 0x30, 0xDE, 0x80, 0x7F, 0x70, 0xDF, 0xC0, 0xB9, 0xF0, 0xE0, 0xC0, 0xBA, 0xF0, +0xE1, 0x00, 0xBB, 0x30, 0xE2, 0x00, 0xFA, 0x30, 0xE3, 0x40, 0xFB, 0x70, 0xE4, 0x00, 0xFC, 0x30, +0xE5, 0x40, 0xFD, 0x70, 0xE6, 0x40, 0xFF, 0xB0, 0xE7, 0x80, 0x83, 0x01, 0x8A, 0x51, 0xFA, 0x69, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, @@ -291,225 +289,227 @@ static uint8_t agc_firmware_sx125x[8192] = { 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xE4, 0x00, 0x64, 0x9C, 0xDD, 0x69, -0x00, 0xB0, 0xDD, 0x80, 0x5D, 0x88, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xEF, 0xC0, -0x3F, 0x30, 0xEF, 0x45, 0x6F, 0xC8, 0xDD, 0x80, 0x00, 0xB0, 0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, -0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0xE4, 0xB0, 0xE2, 0x00, 0x8F, 0x29, 0x90, 0x69, -0xE2, 0xCB, 0x8E, 0xE9, 0x93, 0xE9, 0x00, 0x00, 0x83, 0x52, 0x03, 0x53, 0x64, 0x9C, 0xF2, 0x29, -0x00, 0xB0, 0xDD, 0x80, 0x5D, 0x88, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xEF, 0xC0, -0x6F, 0xC8, 0xEF, 0xF9, 0x10, 0x38, 0xEF, 0xC0, 0x6F, 0xC8, 0xDD, 0x80, 0x00, 0xB0, 0xDE, 0x80, -0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x80, 0xF0, 0x83, 0x96, -0xB5, 0x40, 0x03, 0x30, 0xB6, 0x40, 0xB5, 0xF0, 0x83, 0x52, 0xDD, 0x80, 0x64, 0x08, 0xDE, 0x80, -0x02, 0xF0, 0x15, 0x64, 0x8A, 0x95, 0x04, 0xF0, 0xE3, 0x40, 0x1C, 0x70, 0xE2, 0x00, 0xE2, 0xCB, -0xBF, 0xA9, 0xE3, 0x0B, 0xBF, 0xA9, 0x00, 0x00, 0x91, 0x70, 0x83, 0x96, 0x03, 0x53, 0xB5, 0x40, -0x83, 0x52, 0x64, 0x08, 0xAE, 0xBE, 0x84, 0x80, 0x83, 0x93, 0x00, 0x48, 0x83, 0x96, 0xB6, 0x40, -0xB5, 0xF0, 0x83, 0x52, 0xDD, 0x80, 0x64, 0x08, 0xDE, 0x80, 0x02, 0xF0, 0x15, 0x64, 0x8A, 0x95, -0x83, 0x96, 0xAE, 0x1C, 0xAF, 0x18, 0x09, 0x6A, 0x0C, 0x6A, 0x00, 0xB0, 0xDD, 0x80, 0x5D, 0x88, -0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xEF, 0xC0, 0xF3, 0x30, 0xEF, 0x45, 0x6F, 0xC8, -0xDD, 0x80, 0x00, 0xB0, 0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, -0x17, 0x50, 0x8C, 0xA9, 0x00, 0xB0, 0xDD, 0x80, 0x5D, 0x88, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, -0x0D, 0x08, 0xEF, 0xC0, 0x6F, 0xC8, 0xFE, 0xF9, 0x01, 0x38, 0xEF, 0xC0, 0x6F, 0xC8, 0xDD, 0x80, -0x00, 0xB0, 0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, -0xAE, 0x29, 0x83, 0x52, 0x1B, 0x92, 0x08, 0x40, 0x83, 0x52, 0x1B, 0xD6, 0x08, 0x40, 0xEA, 0x40, -0x6A, 0x98, 0x1A, 0xAA, 0x25, 0x70, 0xDD, 0x80, 0x5D, 0x88, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, -0x0D, 0x08, 0x21, 0x6A, 0x2A, 0x70, 0xDD, 0x80, 0x5D, 0x88, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, -0x0D, 0x08, 0xE6, 0x40, 0xE9, 0x40, 0x3F, 0x30, 0xE9, 0xC5, 0x88, 0x30, 0x83, 0x96, 0xB5, 0x40, -0x83, 0x52, 0x69, 0x48, 0x83, 0x96, 0xB6, 0x40, 0xB5, 0xF0, 0x83, 0x52, 0xDD, 0x80, 0x6A, 0x48, -0xDE, 0x80, 0x02, 0xF0, 0x15, 0x64, 0x8A, 0x95, 0x66, 0x48, 0xE3, 0x40, 0x06, 0x30, 0x03, 0xD0, -0xE3, 0xCC, 0xFF, 0x7E, 0x03, 0x9D, 0x37, 0x2A, 0x63, 0x48, 0xE9, 0x40, 0x03, 0x30, 0xE9, 0xC5, -0x6A, 0x98, 0x68, 0xAA, 0x00, 0xB0, 0xDD, 0x80, 0x5D, 0x88, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, -0x0D, 0x08, 0xEF, 0xC0, 0x69, 0x48, 0xE3, 0x40, 0x03, 0xD0, 0xE3, 0x0D, 0x03, 0xD0, 0xE3, 0x0D, -0x6F, 0xC8, 0xF3, 0xB9, 0x63, 0x44, 0xEF, 0xC0, 0x6F, 0xC8, 0xDD, 0x80, 0x00, 0xB0, 0xDE, 0x80, -0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x6A, 0x98, 0x87, 0xEA, -0x26, 0x70, 0xDD, 0x80, 0x5D, 0x88, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0x8E, 0xEA, -0x00, 0xB0, 0xDD, 0x80, 0x5D, 0x88, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xEF, 0xC0, -0x69, 0x48, 0xE3, 0x40, 0x06, 0x30, 0x03, 0xD0, 0xE3, 0x0D, 0xFF, 0x7E, 0x03, 0x9D, 0x73, 0x2A, -0x6F, 0xC8, 0x3F, 0xB9, 0x63, 0x44, 0xEF, 0xC0, 0x6F, 0xC8, 0xDD, 0x80, 0x00, 0xB0, 0xDE, 0x80, -0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x5E, 0x2A, 0x2B, 0xB0, -0xDD, 0x80, 0x5D, 0x88, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xE9, 0x40, 0x07, 0x70, -0xE9, 0xC5, 0x06, 0x30, 0x69, 0x42, 0x03, 0x5C, 0xA7, 0x2A, 0x8A, 0x70, 0x83, 0x96, 0xB5, 0x40, -0x6A, 0xB0, 0xB6, 0x40, 0xB5, 0xF0, 0x83, 0x52, 0xDD, 0x80, 0x6A, 0x48, 0xDE, 0x80, 0x02, 0xF0, -0x15, 0x64, 0x8A, 0x95, 0x8B, 0xB0, 0x83, 0x96, 0xB5, 0x40, 0x04, 0xF0, 0xC0, 0x6A, 0x69, 0x48, -0x05, 0xBA, 0x8A, 0x70, 0x03, 0x9D, 0xB1, 0xEA, 0x83, 0x96, 0xB5, 0x40, 0x20, 0xF0, 0xB6, 0x40, -0xB4, 0xEA, 0x83, 0x96, 0xB5, 0x40, 0xB6, 0x81, 0xB5, 0xF0, 0x83, 0x52, 0xDD, 0x80, 0x6A, 0x48, -0xDE, 0x80, 0x02, 0xF0, 0x15, 0x64, 0x8A, 0x95, 0x8B, 0xB0, 0x83, 0x96, 0xB5, 0x40, 0x05, 0x30, -0xB6, 0x40, 0xB5, 0xF0, 0x83, 0x52, 0xDD, 0x80, 0x6A, 0x48, 0xDE, 0x80, 0x02, 0xF0, 0x15, 0x64, -0x8A, 0x95, 0x6A, 0x98, 0xE3, 0x2A, 0x27, 0xB0, 0xDD, 0x80, 0x5D, 0x88, 0x95, 0x00, 0x97, 0xD4, -0x97, 0x90, 0x0D, 0x08, 0xE5, 0x40, 0x28, 0x30, 0xDD, 0x80, 0x5D, 0x88, 0x95, 0x00, 0x97, 0xD4, -0x97, 0x90, 0x0D, 0x08, 0xE9, 0x40, 0x29, 0x70, 0xDD, 0x80, 0x5D, 0x88, 0x95, 0x00, 0x97, 0xD4, -0x97, 0x90, 0x0D, 0x08, 0xFA, 0x6A, 0x2C, 0x70, 0xDD, 0x80, 0x5D, 0x88, 0x95, 0x00, 0x97, 0xD4, -0x97, 0x90, 0x0D, 0x08, 0xE5, 0x40, 0x2D, 0xB0, 0xDD, 0x80, 0x5D, 0x88, 0x95, 0x00, 0x97, 0xD4, -0x97, 0x90, 0x0D, 0x08, 0xE9, 0x40, 0x2E, 0xB0, 0xDD, 0x80, 0x5D, 0x88, 0x95, 0x00, 0x97, 0xD4, -0x97, 0x90, 0x0D, 0x08, 0xE7, 0x80, 0x69, 0x48, 0xE3, 0x40, 0x07, 0x70, 0x03, 0xD0, 0xE3, 0xCC, -0xFF, 0x7E, 0x03, 0xD0, 0x03, 0x9D, 0xFF, 0xEA, 0x65, 0xCD, 0x63, 0x44, 0xE8, 0x00, 0x84, 0x30, -0x83, 0x96, 0xB5, 0x40, 0x83, 0x52, 0x68, 0x08, 0x83, 0x96, 0xB6, 0x40, 0xB5, 0xF0, 0x83, 0x52, -0xDD, 0x80, 0x6A, 0x48, 0xDE, 0x80, 0x02, 0xF0, 0x15, 0x64, 0x8A, 0x95, 0x67, 0x88, 0xE3, 0x40, -0x07, 0x70, 0x03, 0xD0, 0xE3, 0xCC, 0xFF, 0x7E, 0x03, 0xD0, 0x03, 0x9D, 0x1A, 0xEB, 0x69, 0xCD, -0x63, 0x44, 0xE8, 0x00, 0x85, 0x70, 0x83, 0x96, 0xB5, 0x40, 0x83, 0x52, 0x68, 0x08, 0x83, 0x96, -0xB6, 0x40, 0xB5, 0xF0, 0x83, 0x52, 0xDD, 0x80, 0x6A, 0x48, 0xDE, 0x80, 0x02, 0xF0, 0x15, 0x64, -0x8A, 0x95, 0x03, 0xD0, 0x67, 0x0D, 0xE8, 0x00, 0x86, 0x70, 0x83, 0x96, 0xB5, 0x40, 0x83, 0x52, -0x68, 0x08, 0x83, 0x96, 0xB6, 0x40, 0xB5, 0xF0, 0x83, 0x52, 0xDD, 0x80, 0x6A, 0x48, 0xDE, 0x80, -0x02, 0xF0, 0x15, 0x64, 0x8A, 0x95, 0x6A, 0x48, 0x2D, 0x7E, 0x84, 0x80, 0x83, 0x93, 0x80, 0x88, -0x03, 0x59, 0x4F, 0x6B, 0x6A, 0x48, 0x5B, 0xBE, 0x84, 0x80, 0x03, 0x30, 0x53, 0x2B, 0x6A, 0x48, -0x5B, 0xBE, 0x84, 0x80, 0x01, 0xF0, 0x80, 0x40, 0x80, 0xF0, 0x83, 0x96, 0xB5, 0x40, 0x83, 0x52, -0x6A, 0x48, 0x5B, 0xBE, 0x84, 0x80, 0x00, 0x48, 0x04, 0x38, 0x83, 0x96, 0xB6, 0x40, 0xB5, 0xF0, -0x83, 0x52, 0xDD, 0x80, 0x6A, 0x48, 0xDE, 0x80, 0x02, 0xF0, 0x15, 0x64, 0x8A, 0x95, 0x03, 0x30, -0xE4, 0x00, 0x7D, 0x30, 0xE3, 0x40, 0xE3, 0x0B, 0x6B, 0x6B, 0xE4, 0xCB, 0x6B, 0x6B, 0x83, 0x52, -0x03, 0x53, 0x6A, 0xDC, 0x87, 0x2B, 0x00, 0xB0, 0xDD, 0x80, 0x5D, 0x88, 0x95, 0x00, 0x97, 0xD4, -0x97, 0x90, 0x0D, 0x08, 0xEF, 0xC0, 0x6F, 0x12, 0x6F, 0xC8, 0xDD, 0x80, 0x00, 0xB0, 0xDE, 0x80, -0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x9A, 0x2B, 0x00, 0xB0, -0xDD, 0x80, 0x5D, 0x88, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xEF, 0xC0, 0x6F, 0xD0, -0x6F, 0xC8, 0xDD, 0x80, 0x00, 0xB0, 0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, -0x17, 0x94, 0x17, 0x50, 0x05, 0x30, 0xE3, 0x40, 0xE3, 0x0B, 0x9C, 0x2B, 0x83, 0x52, 0x03, 0x53, -0x6A, 0xDC, 0xFC, 0xAB, 0x00, 0xB0, 0xDD, 0x80, 0x5D, 0x88, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, -0x0D, 0x08, 0xEF, 0xC0, 0x6F, 0xC8, 0xDF, 0xF9, 0x20, 0x38, 0xEF, 0xC0, 0x6F, 0xC8, 0xDD, 0x80, -0x00, 0xB0, 0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, -0x15, 0x70, 0xE4, 0x00, 0xC6, 0xB0, 0xE3, 0x40, 0xE3, 0x0B, 0xBC, 0x6B, 0xE4, 0xCB, 0xBC, 0x6B, -0x00, 0x00, 0x80, 0xF0, 0x83, 0x96, 0x03, 0x53, 0xB5, 0x40, 0x83, 0x52, 0x6A, 0x48, 0x5B, 0xBE, -0x84, 0x80, 0x83, 0x93, 0x00, 0x48, 0x0C, 0x78, 0x83, 0x96, 0xB6, 0x40, 0xB5, 0xF0, 0x83, 0x52, -0xDD, 0x80, 0x6A, 0x48, 0xDE, 0x80, 0x02, 0xF0, 0x15, 0x64, 0x8A, 0x95, 0x04, 0xF0, 0xE4, 0x00, -0x1C, 0x70, 0xE3, 0x40, 0xE3, 0x0B, 0xDA, 0x6B, 0xE4, 0xCB, 0xDA, 0x6B, 0x00, 0x00, 0x11, 0x30, -0x83, 0x96, 0x03, 0x53, 0xB5, 0x40, 0x55, 0xB0, 0xB6, 0x40, 0xB5, 0xF0, 0x83, 0x52, 0xDD, 0x80, -0x6A, 0x48, 0xDE, 0x80, 0x02, 0xF0, 0x4E, 0xA4, 0x8A, 0x95, 0x6A, 0x48, 0xAE, 0xBE, 0x84, 0x80, -0x83, 0x96, 0x35, 0x48, 0x83, 0x93, 0x80, 0x40, 0x83, 0x52, 0x6A, 0x48, 0xAE, 0xBE, 0x84, 0x80, -0x00, 0xDC, 0x13, 0xAC, 0x9B, 0xD2, 0x08, 0x40, 0x00, 0xB0, 0xDD, 0x80, 0x5D, 0x88, 0x95, 0x00, -0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xEF, 0xC0, 0x6F, 0xC8, 0xFD, 0xF9, 0x02, 0x38, 0xEF, 0xC0, -0x6F, 0xC8, 0xDD, 0x80, 0x00, 0xB0, 0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, -0x17, 0x94, 0x17, 0x50, 0xB8, 0x2B, 0x9B, 0x16, 0x08, 0x40, 0xE0, 0xC0, 0x5E, 0xD8, 0x33, 0xEC, -0xE1, 0x41, 0x60, 0xC8, 0x61, 0x02, 0x03, 0x18, 0x28, 0x6C, 0x61, 0x08, 0x5D, 0x07, 0xDF, 0xC0, -0x84, 0x80, 0x83, 0x93, 0x00, 0x48, 0x99, 0x00, 0x18, 0x14, 0x18, 0xD0, 0xE1, 0x8A, 0x19, 0xAC, -0x18, 0x55, 0x18, 0x11, 0x83, 0x52, 0x03, 0x53, 0x11, 0x5C, 0x08, 0x40, 0x05, 0x30, 0xDF, 0xC0, -0xDF, 0x8B, 0x30, 0x6C, 0x2A, 0xAC, 0xE1, 0x41, 0x60, 0xC8, 0x61, 0x02, 0x03, 0x18, 0x43, 0xAC, -0x61, 0x08, 0x5D, 0x07, 0xDF, 0xC0, 0x84, 0x80, 0x83, 0x93, 0x00, 0x48, 0x9A, 0x00, 0x98, 0x95, -0x98, 0x51, 0xE1, 0x8A, 0x34, 0xAC, 0x98, 0x96, 0x98, 0x52, 0x83, 0x52, 0x03, 0x53, 0x91, 0x9C, -0x08, 0x40, 0x05, 0x30, 0xDF, 0xC0, 0xDF, 0x8B, 0x4B, 0xEC, 0x45, 0xAC, 0xE0, 0xC0, 0x10, 0xF0, -0x60, 0xC2, 0x03, 0x5C, 0x55, 0xEC, 0x10, 0xF0, 0x56, 0xEC, 0x60, 0xC8, 0xE1, 0x00, 0x5E, 0xD8, -0x85, 0xAC, 0xE2, 0x41, 0x61, 0x08, 0x62, 0x02, 0x03, 0x18, 0x69, 0xEC, 0x62, 0x08, 0x5D, 0x07, -0xDF, 0xC0, 0x84, 0x80, 0x83, 0x93, 0x00, 0x48, 0x99, 0x00, 0x18, 0x14, 0x18, 0xD0, 0xE2, 0x8A, -0x5A, 0xEC, 0x18, 0x55, 0x18, 0x11, 0x83, 0x52, 0x03, 0x53, 0x11, 0x5C, 0x75, 0x2C, 0x2A, 0x70, -0xDF, 0xC0, 0xDF, 0x8B, 0x71, 0xEC, 0x00, 0x00, 0x6B, 0x2C, 0xE2, 0x41, 0x61, 0x08, 0x62, 0x02, -0x03, 0x18, 0x08, 0x40, 0x62, 0x08, 0x5D, 0x07, 0xDF, 0xC0, 0x84, 0x80, 0x0F, 0x48, 0x83, 0x93, -0x80, 0x40, 0x98, 0x54, 0x98, 0x10, 0xE2, 0x8A, 0x76, 0x2C, 0xE2, 0x41, 0x61, 0x08, 0x62, 0x02, -0x03, 0x18, 0x95, 0xEC, 0x62, 0x08, 0x5D, 0x07, 0xDF, 0xC0, 0x84, 0x80, 0x83, 0x93, 0x00, 0x48, -0x9A, 0x00, 0x98, 0x95, 0x98, 0x51, 0xE2, 0x8A, 0x86, 0xAC, 0x98, 0x96, 0x98, 0x52, 0x83, 0x52, -0x03, 0x53, 0x91, 0x9C, 0xA1, 0xAC, 0x2A, 0x70, 0xDF, 0xC0, 0xDF, 0x8B, 0x9D, 0x2C, 0x00, 0x00, -0x97, 0x2C, 0xE2, 0x41, 0x61, 0x08, 0x62, 0x02, 0x03, 0x18, 0x08, 0x40, 0x62, 0x08, 0x5D, 0x07, -0xDF, 0xC0, 0x84, 0x80, 0x10, 0x88, 0x83, 0x93, 0x80, 0x40, 0x18, 0x56, 0x18, 0x12, 0xE2, 0x8A, -0xA2, 0xAC, 0x55, 0xB0, 0x9B, 0x40, 0xD7, 0xC1, 0x02, 0xF0, 0x57, 0x82, 0x03, 0x18, 0xC9, 0x2D, -0x57, 0x88, 0x01, 0xBE, 0x9B, 0x40, 0x57, 0x88, 0x33, 0x7E, 0x84, 0x80, 0x83, 0x93, 0x80, 0x81, -0x57, 0x88, 0x33, 0x7E, 0x84, 0x80, 0x80, 0x88, 0x03, 0x9D, 0xEC, 0x2C, 0x3C, 0xB0, 0xDD, 0x80, -0x5D, 0x88, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xEF, 0xC0, 0x57, 0xD8, 0xDC, 0x2C, -0x57, 0x88, 0x33, 0x7E, 0x84, 0x80, 0x6F, 0xC8, 0xDF, 0xC0, 0x07, 0x70, 0x03, 0xD0, 0xDF, 0x4C, -0xFF, 0x7E, 0x03, 0x9D, 0xD6, 0x2C, 0xE7, 0x6C, 0x57, 0x88, 0x33, 0x7E, 0x84, 0x80, 0x6F, 0xC8, -0xDF, 0xC0, 0x05, 0x30, 0x03, 0xD0, 0xDF, 0x4C, 0xFF, 0x7E, 0x03, 0x9D, 0xE2, 0xEC, 0x5F, 0xC8, -0x01, 0x79, 0x83, 0x93, 0x80, 0x40, 0xC0, 0x6C, 0x3D, 0xF0, 0xDD, 0x80, 0x5D, 0x88, 0x95, 0x00, -0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xEF, 0xC0, 0x6F, 0xC8, 0x03, 0x59, 0xF9, 0x6C, 0x6F, 0x4B, -0x18, 0xAD, 0x57, 0x88, 0x2D, 0x7E, 0x84, 0x80, 0x6F, 0xC8, 0x83, 0x93, 0x80, 0x40, 0x57, 0x88, -0x2D, 0x7E, 0x84, 0x80, 0x00, 0x48, 0xDD, 0x80, 0x1A, 0x70, 0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, -0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x3E, 0xF0, 0xDD, 0x80, 0x5D, 0x88, 0x95, 0x00, -0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xEF, 0xC0, 0x6F, 0xC8, 0x03, 0x9D, 0x2B, 0x2D, 0x49, 0xED, -0x57, 0x88, 0x2D, 0x7E, 0x84, 0x80, 0x83, 0x93, 0x80, 0x81, 0x57, 0x88, 0x2D, 0x7E, 0x84, 0x80, -0x00, 0x48, 0xDD, 0x80, 0x1A, 0x70, 0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, -0x17, 0x94, 0x17, 0x50, 0x0C, 0xAD, 0x10, 0xF0, 0x6F, 0xC2, 0x03, 0x18, 0x49, 0xED, 0x57, 0x88, -0x2B, 0x7E, 0x84, 0x80, 0x6F, 0xC8, 0x83, 0x93, 0x80, 0x40, 0x57, 0x88, 0x2B, 0x7E, 0x84, 0x80, -0x00, 0x48, 0xDD, 0x80, 0x1B, 0xB0, 0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, -0x17, 0x94, 0x17, 0x50, 0x57, 0x88, 0x29, 0x3E, 0x84, 0x80, 0x83, 0x93, 0x80, 0x81, 0x80, 0xCA, -0x76, 0x6D, 0x6F, 0xC8, 0xFF, 0x3A, 0x03, 0x9D, 0x5E, 0x6D, 0x57, 0x88, 0x2B, 0x7E, 0x84, 0x80, -0x07, 0x70, 0x83, 0x93, 0x80, 0x40, 0xFF, 0xB0, 0xDD, 0x80, 0x1B, 0xB0, 0xDE, 0x80, 0x5D, 0x88, -0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x71, 0x2D, 0x57, 0x88, 0x2B, 0x7E, -0x84, 0x80, 0x07, 0x70, 0x83, 0x93, 0x80, 0x40, 0x57, 0x88, 0x2B, 0x7E, 0x84, 0x80, 0x00, 0x48, -0xDD, 0x80, 0x1B, 0xB0, 0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, -0x17, 0x50, 0x57, 0x88, 0x29, 0x3E, 0x84, 0x80, 0x83, 0x93, 0x80, 0x81, 0x3F, 0x30, 0xDD, 0x80, -0x5D, 0x88, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xEF, 0xC0, 0x0A, 0x30, 0x6F, 0xC2, -0x03, 0x18, 0x9C, 0x2D, 0x57, 0x88, 0x25, 0x3E, 0x84, 0x80, 0x6F, 0xC8, 0x83, 0x93, 0x80, 0x40, -0x57, 0x88, 0x25, 0x3E, 0x84, 0x80, 0x00, 0x48, 0xDD, 0x80, 0x1C, 0x70, 0xDE, 0x80, 0x5D, 0x88, -0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x57, 0x88, 0xA8, 0x3E, 0x84, 0x80, -0x83, 0x93, 0x80, 0x81, 0x80, 0xCA, 0xC7, 0x6D, 0x6F, 0xC8, 0xFF, 0x3A, 0x03, 0x9D, 0xB0, 0xED, -0x57, 0x88, 0x25, 0x3E, 0x84, 0x80, 0xFF, 0xB0, 0x83, 0x93, 0x80, 0x81, 0xDD, 0x80, 0x1C, 0x70, -0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0xC2, 0xED, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xE3, 0x40, +0x63, 0xDC, 0x7F, 0xA9, 0x00, 0xB0, 0xDC, 0x40, 0x5C, 0x48, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, +0x0D, 0x08, 0xEF, 0xC0, 0x3F, 0x30, 0xEF, 0x45, 0x6F, 0xC8, 0xDC, 0x40, 0x00, 0xB0, 0xDD, 0x80, +0x5C, 0x48, 0x96, 0x00, 0x5D, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0xE4, 0xB0, 0xE1, 0x00, +0x31, 0xA9, 0x32, 0xA9, 0xE1, 0xCB, 0x30, 0x69, 0x35, 0xE9, 0x00, 0x00, 0x83, 0x52, 0x03, 0x53, +0x63, 0xDC, 0x94, 0xA9, 0x00, 0xB0, 0xDC, 0x40, 0x5C, 0x48, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, +0x0D, 0x08, 0xEF, 0xC0, 0x6F, 0xC8, 0xEF, 0xF9, 0x10, 0x38, 0xEF, 0xC0, 0x6F, 0xC8, 0xDC, 0x40, +0x00, 0xB0, 0xDD, 0x80, 0x5C, 0x48, 0x96, 0x00, 0x5D, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, +0x80, 0xF0, 0x83, 0x96, 0xB8, 0x00, 0x03, 0x30, 0xB9, 0x40, 0xB8, 0xB0, 0x83, 0x52, 0xDC, 0x40, +0x63, 0x48, 0xDD, 0x80, 0x02, 0xF0, 0xC5, 0xE3, 0x8A, 0x95, 0x04, 0xF0, 0xE2, 0x00, 0x1C, 0x70, +0xE1, 0x00, 0xE1, 0xCB, 0x61, 0xA9, 0xE2, 0xCB, 0x61, 0xA9, 0x00, 0x00, 0x91, 0x70, 0x83, 0x96, +0x03, 0x53, 0xB8, 0x00, 0x83, 0x52, 0x63, 0x48, 0xB0, 0x3E, 0x84, 0x80, 0x83, 0x93, 0x00, 0x48, +0x83, 0x96, 0xB9, 0x40, 0xB8, 0xB0, 0x83, 0x52, 0xDC, 0x40, 0x63, 0x48, 0xDD, 0x80, 0x02, 0xF0, +0xC5, 0xE3, 0x8A, 0x95, 0x83, 0x96, 0xB0, 0x9C, 0xB1, 0x98, 0xAB, 0x29, 0xAE, 0x29, 0x00, 0xB0, +0xDC, 0x40, 0x5C, 0x48, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xEF, 0xC0, 0xF3, 0x30, +0xEF, 0x45, 0x6F, 0xC8, 0xDC, 0x40, 0x00, 0xB0, 0xDD, 0x80, 0x5C, 0x48, 0x96, 0x00, 0x5D, 0x88, +0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x2E, 0xE9, 0x00, 0xB0, 0xDC, 0x40, 0x5C, 0x48, 0x95, 0x00, +0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xEF, 0xC0, 0x6F, 0xC8, 0xFE, 0xF9, 0x01, 0x38, 0xEF, 0xC0, +0x6F, 0xC8, 0xDC, 0x40, 0x00, 0xB0, 0xDD, 0x80, 0x5C, 0x48, 0x96, 0x00, 0x5D, 0x88, 0x95, 0x00, +0x17, 0x94, 0x17, 0x50, 0x50, 0x69, 0x83, 0x52, 0x1B, 0x92, 0x08, 0x40, 0x83, 0x52, 0x1B, 0xD6, +0x08, 0x40, 0xEA, 0x40, 0x6A, 0x98, 0xBC, 0x29, 0x25, 0x70, 0xDC, 0x40, 0x5C, 0x48, 0x95, 0x00, +0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xC3, 0xE9, 0x2A, 0x70, 0xDC, 0x40, 0x5C, 0x48, 0x95, 0x00, +0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xE6, 0x40, 0xE9, 0x40, 0x3F, 0x30, 0xE9, 0xC5, 0x88, 0x30, +0x83, 0x96, 0xB8, 0x00, 0x83, 0x52, 0x69, 0x48, 0x83, 0x96, 0xB9, 0x40, 0xB8, 0xB0, 0x83, 0x52, +0xDC, 0x40, 0x6A, 0x48, 0xDD, 0x80, 0x02, 0xF0, 0xC5, 0xE3, 0x8A, 0x95, 0x66, 0x48, 0xE2, 0x00, +0x06, 0x30, 0x03, 0xD0, 0xE2, 0x8C, 0xFF, 0x7E, 0x03, 0x9D, 0xD9, 0x29, 0x62, 0x08, 0xE9, 0x40, +0x03, 0x30, 0xE9, 0xC5, 0x6A, 0x98, 0x0A, 0x6A, 0x00, 0xB0, 0xDC, 0x40, 0x5C, 0x48, 0x95, 0x00, +0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xEF, 0xC0, 0x69, 0x48, 0xE2, 0x00, 0x03, 0xD0, 0xE2, 0xCD, +0x03, 0xD0, 0xE2, 0xCD, 0x6F, 0xC8, 0xF3, 0xB9, 0x62, 0x04, 0xEF, 0xC0, 0x6F, 0xC8, 0xDC, 0x40, +0x00, 0xB0, 0xDD, 0x80, 0x5C, 0x48, 0x96, 0x00, 0x5D, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, +0x6A, 0x98, 0x29, 0xAA, 0x26, 0x70, 0xDC, 0x40, 0x5C, 0x48, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, +0x0D, 0x08, 0x30, 0x6A, 0x00, 0xB0, 0xDC, 0x40, 0x5C, 0x48, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, +0x0D, 0x08, 0xEF, 0xC0, 0x69, 0x48, 0xE2, 0x00, 0x06, 0x30, 0x03, 0xD0, 0xE2, 0xCD, 0xFF, 0x7E, +0x03, 0x9D, 0x15, 0xAA, 0x6F, 0xC8, 0x3F, 0xB9, 0x62, 0x04, 0xEF, 0xC0, 0x6F, 0xC8, 0xDC, 0x40, +0x00, 0xB0, 0xDD, 0x80, 0x5C, 0x48, 0x96, 0x00, 0x5D, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, +0x00, 0xEA, 0x2B, 0xB0, 0xDC, 0x40, 0x5C, 0x48, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, +0xE9, 0x40, 0x07, 0x70, 0xE9, 0xC5, 0x06, 0x30, 0x69, 0x42, 0x03, 0x5C, 0x49, 0xAA, 0x8A, 0x70, +0x83, 0x96, 0xB8, 0x00, 0x6A, 0xB0, 0xB9, 0x40, 0xB8, 0xB0, 0x83, 0x52, 0xDC, 0x40, 0x6A, 0x48, +0xDD, 0x80, 0x02, 0xF0, 0xC5, 0xE3, 0x8A, 0x95, 0x8B, 0xB0, 0x83, 0x96, 0xB8, 0x00, 0x04, 0xF0, +0x62, 0xAA, 0x69, 0x48, 0x05, 0xBA, 0x8A, 0x70, 0x03, 0x9D, 0x53, 0xEA, 0x83, 0x96, 0xB8, 0x00, +0x20, 0xF0, 0xB9, 0x40, 0x56, 0xEA, 0x83, 0x96, 0xB8, 0x00, 0xB9, 0x81, 0xB8, 0xB0, 0x83, 0x52, +0xDC, 0x40, 0x6A, 0x48, 0xDD, 0x80, 0x02, 0xF0, 0xC5, 0xE3, 0x8A, 0x95, 0x8B, 0xB0, 0x83, 0x96, +0xB8, 0x00, 0x05, 0x30, 0xB9, 0x40, 0xB8, 0xB0, 0x83, 0x52, 0xDC, 0x40, 0x6A, 0x48, 0xDD, 0x80, +0x02, 0xF0, 0xC5, 0xE3, 0x8A, 0x95, 0x6A, 0x98, 0x85, 0xAA, 0x27, 0xB0, 0xDC, 0x40, 0x5C, 0x48, +0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xE4, 0x00, 0x28, 0x30, 0xDC, 0x40, 0x5C, 0x48, +0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xE9, 0x40, 0x29, 0x70, 0xDC, 0x40, 0x5C, 0x48, +0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0x9C, 0xEA, 0x2C, 0x70, 0xDC, 0x40, 0x5C, 0x48, +0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xE4, 0x00, 0x2D, 0xB0, 0xDC, 0x40, 0x5C, 0x48, +0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xE9, 0x40, 0x2E, 0xB0, 0xDC, 0x40, 0x5C, 0x48, +0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xE7, 0x80, 0x69, 0x48, 0xE2, 0x00, 0x07, 0x70, +0x03, 0xD0, 0xE2, 0x8C, 0xFF, 0x7E, 0x03, 0xD0, 0x03, 0x9D, 0xA1, 0xAA, 0x64, 0x8D, 0x62, 0x04, +0xE8, 0x00, 0x84, 0x30, 0x83, 0x96, 0xB8, 0x00, 0x83, 0x52, 0x68, 0x08, 0x83, 0x96, 0xB9, 0x40, +0xB8, 0xB0, 0x83, 0x52, 0xDC, 0x40, 0x6A, 0x48, 0xDD, 0x80, 0x02, 0xF0, 0xC5, 0xE3, 0x8A, 0x95, +0x67, 0x88, 0xE2, 0x00, 0x07, 0x70, 0x03, 0xD0, 0xE2, 0x8C, 0xFF, 0x7E, 0x03, 0xD0, 0x03, 0x9D, +0xBC, 0x2A, 0x69, 0xCD, 0x62, 0x04, 0xE8, 0x00, 0x85, 0x70, 0x83, 0x96, 0xB8, 0x00, 0x83, 0x52, +0x68, 0x08, 0x83, 0x96, 0xB9, 0x40, 0xB8, 0xB0, 0x83, 0x52, 0xDC, 0x40, 0x6A, 0x48, 0xDD, 0x80, +0x02, 0xF0, 0xC5, 0xE3, 0x8A, 0x95, 0x03, 0xD0, 0x67, 0x0D, 0xE8, 0x00, 0x86, 0x70, 0x83, 0x96, +0xB8, 0x00, 0x83, 0x52, 0x68, 0x08, 0x83, 0x96, 0xB9, 0x40, 0xB8, 0xB0, 0x83, 0x52, 0xDC, 0x40, +0x6A, 0x48, 0xDD, 0x80, 0x02, 0xF0, 0xC5, 0xE3, 0x8A, 0x95, 0x6A, 0x48, 0x2B, 0x7E, 0x84, 0x80, +0x83, 0x93, 0x80, 0x88, 0x03, 0x59, 0xF1, 0x2A, 0x6A, 0x48, 0x5A, 0x7E, 0x84, 0x80, 0x03, 0x30, +0xF5, 0x6A, 0x6A, 0x48, 0x5A, 0x7E, 0x84, 0x80, 0x01, 0xF0, 0x80, 0x40, 0x80, 0xF0, 0x83, 0x96, +0xB8, 0x00, 0x83, 0x52, 0x6A, 0x48, 0x5A, 0x7E, 0x84, 0x80, 0x00, 0x48, 0x04, 0x38, 0x83, 0x96, +0xB9, 0x40, 0xB8, 0xB0, 0x83, 0x52, 0xDC, 0x40, 0x6A, 0x48, 0xDD, 0x80, 0x02, 0xF0, 0xC5, 0xE3, +0x8A, 0x95, 0x03, 0x30, 0xE3, 0x40, 0x7D, 0x30, 0xE2, 0x00, 0xE2, 0xCB, 0x0D, 0xEB, 0xE3, 0x0B, +0x0D, 0xEB, 0x83, 0x52, 0x03, 0x53, 0x6A, 0x48, 0x2B, 0x7E, 0x84, 0x80, 0x83, 0x93, 0x00, 0x48, +0x03, 0x9D, 0x43, 0xEB, 0x6A, 0xDC, 0x30, 0xAB, 0x00, 0xB0, 0xDC, 0x40, 0x5C, 0x48, 0x95, 0x00, +0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xEF, 0xC0, 0x6F, 0x12, 0x6F, 0xC8, 0xDC, 0x40, 0x00, 0xB0, +0xDD, 0x80, 0x5C, 0x48, 0x96, 0x00, 0x5D, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x43, 0xEB, +0x00, 0xB0, 0xDC, 0x40, 0x5C, 0x48, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xEF, 0xC0, +0x6F, 0xD0, 0x6F, 0xC8, 0xDC, 0x40, 0x00, 0xB0, 0xDD, 0x80, 0x5C, 0x48, 0x96, 0x00, 0x5D, 0x88, +0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x05, 0x30, 0xE2, 0x00, 0xE2, 0xCB, 0x45, 0xEB, 0x83, 0x52, +0x03, 0x53, 0x6A, 0xDC, 0x63, 0x2B, 0x00, 0xB0, 0xDC, 0x40, 0x5C, 0x48, 0x95, 0x00, 0x97, 0xD4, +0x97, 0x90, 0x0D, 0x08, 0xEF, 0xC0, 0x6F, 0xC8, 0xDF, 0xF9, 0x20, 0x38, 0xEF, 0xC0, 0x6F, 0xC8, +0xDC, 0x40, 0x00, 0xB0, 0xDD, 0x80, 0x5C, 0x48, 0x96, 0x00, 0x5D, 0x88, 0x95, 0x00, 0x17, 0x94, +0x17, 0x50, 0xE5, 0x81, 0x85, 0xEB, 0x00, 0xB0, 0xDC, 0x40, 0x5C, 0x48, 0x95, 0x00, 0x97, 0xD4, +0x97, 0x90, 0x0D, 0x08, 0xEF, 0xC0, 0x6F, 0xC8, 0xFD, 0xF9, 0x02, 0x38, 0xEF, 0xC0, 0x6F, 0xC8, +0xDC, 0x40, 0x00, 0xB0, 0xDD, 0x80, 0x5C, 0x48, 0x96, 0x00, 0x5D, 0x88, 0x95, 0x00, 0x17, 0x94, +0x17, 0x50, 0x61, 0xEB, 0xE4, 0xB0, 0xE2, 0x00, 0x7D, 0xAB, 0x7E, 0xAB, 0xE2, 0xCB, 0x7C, 0x6B, +0x81, 0xAB, 0x00, 0x00, 0x83, 0x52, 0x03, 0x53, 0xE5, 0xCA, 0x4F, 0x88, 0x65, 0x42, 0x03, 0x5C, +0x7A, 0x6B, 0x80, 0xF0, 0x83, 0x96, 0xB8, 0x00, 0x83, 0x52, 0x6A, 0x48, 0x5A, 0x7E, 0x84, 0x80, +0x83, 0x93, 0x00, 0x48, 0x0C, 0x78, 0x83, 0x96, 0xB9, 0x40, 0xB8, 0xB0, 0x83, 0x52, 0xDC, 0x40, +0x6A, 0x48, 0xDD, 0x80, 0x02, 0xF0, 0xC5, 0xE3, 0x8A, 0x95, 0x04, 0xF0, 0xE3, 0x40, 0x1C, 0x70, +0xE2, 0x00, 0xE2, 0xCB, 0xA1, 0xEB, 0xE3, 0x0B, 0xA1, 0xEB, 0x00, 0x00, 0x11, 0x30, 0x83, 0x96, +0x03, 0x53, 0xB8, 0x00, 0x55, 0xB0, 0xB9, 0x40, 0xB8, 0xB0, 0x83, 0x52, 0xDC, 0x40, 0x6A, 0x48, +0xDD, 0x80, 0x02, 0xF0, 0xFE, 0xA3, 0x8A, 0x95, 0x6A, 0x48, 0xB0, 0x3E, 0x84, 0x80, 0x83, 0x96, +0x38, 0x08, 0x83, 0x93, 0x80, 0x40, 0x83, 0x52, 0x6A, 0x48, 0xB0, 0x3E, 0x84, 0x80, 0x00, 0xDC, +0xC3, 0x2B, 0x9B, 0xD2, 0x08, 0x40, 0x9B, 0x16, 0x08, 0x40, 0xDF, 0xC0, 0x5D, 0xD8, 0xE3, 0x6B, +0xE0, 0x01, 0x5F, 0xC8, 0x60, 0xC2, 0x03, 0x18, 0xD8, 0x2B, 0x60, 0xC8, 0x5C, 0xC7, 0xDE, 0x80, +0x84, 0x80, 0x83, 0x93, 0x00, 0x48, 0x99, 0x00, 0x18, 0x14, 0x18, 0xD0, 0xE0, 0x4A, 0xC9, 0x2B, +0x18, 0x55, 0x18, 0x11, 0x83, 0x52, 0x03, 0x53, 0x11, 0x5C, 0x08, 0x40, 0x05, 0x30, 0xDE, 0x80, +0xDE, 0x4B, 0xE0, 0xEB, 0xDA, 0x6B, 0xE0, 0x01, 0x5F, 0xC8, 0x60, 0xC2, 0x03, 0x18, 0xF3, 0xAB, +0x60, 0xC8, 0x5C, 0xC7, 0xDE, 0x80, 0x84, 0x80, 0x83, 0x93, 0x00, 0x48, 0x9A, 0x00, 0x98, 0x95, +0x98, 0x51, 0xE0, 0x4A, 0xE4, 0x2B, 0x98, 0x96, 0x98, 0x52, 0x83, 0x52, 0x03, 0x53, 0x91, 0x9C, +0x08, 0x40, 0x05, 0x30, 0xDE, 0x80, 0xDE, 0x4B, 0xFB, 0xEB, 0xF5, 0xAB, 0xDF, 0xC0, 0x10, 0xF0, +0x5F, 0xC2, 0x03, 0x5C, 0x05, 0x6C, 0x10, 0xF0, 0x06, 0x6C, 0x5F, 0xC8, 0xE0, 0xC0, 0x5D, 0xD8, +0x35, 0xEC, 0xE1, 0x41, 0x60, 0xC8, 0x61, 0x02, 0x03, 0x18, 0x19, 0xAC, 0x61, 0x08, 0x5C, 0xC7, +0xDE, 0x80, 0x84, 0x80, 0x83, 0x93, 0x00, 0x48, 0x99, 0x00, 0x18, 0x14, 0x18, 0xD0, 0xE1, 0x8A, +0x0A, 0x6C, 0x18, 0x55, 0x18, 0x11, 0x83, 0x52, 0x03, 0x53, 0x11, 0x5C, 0x25, 0xAC, 0x2A, 0x70, +0xDE, 0x80, 0xDE, 0x4B, 0x21, 0x6C, 0x00, 0x00, 0x1B, 0xEC, 0xE1, 0x41, 0x60, 0xC8, 0x61, 0x02, +0x03, 0x18, 0x08, 0x40, 0x61, 0x08, 0x5C, 0xC7, 0xDE, 0x80, 0x84, 0x80, 0x0F, 0x48, 0x83, 0x93, +0x80, 0x40, 0x98, 0x54, 0x98, 0x10, 0xE1, 0x8A, 0x26, 0xAC, 0xE1, 0x41, 0x60, 0xC8, 0x61, 0x02, +0x03, 0x18, 0x45, 0xAC, 0x61, 0x08, 0x5C, 0xC7, 0xDE, 0x80, 0x84, 0x80, 0x83, 0x93, 0x00, 0x48, +0x9A, 0x00, 0x98, 0x95, 0x98, 0x51, 0xE1, 0x8A, 0x36, 0xEC, 0x98, 0x96, 0x98, 0x52, 0x83, 0x52, +0x03, 0x53, 0x91, 0x9C, 0x51, 0xAC, 0x2A, 0x70, 0xDE, 0x80, 0xDE, 0x4B, 0x4D, 0xEC, 0x00, 0x00, +0x47, 0xEC, 0xE1, 0x41, 0x60, 0xC8, 0x61, 0x02, 0x03, 0x18, 0x08, 0x40, 0x61, 0x08, 0x5C, 0xC7, +0xDE, 0x80, 0x84, 0x80, 0x10, 0x88, 0x83, 0x93, 0x80, 0x40, 0x18, 0x56, 0x18, 0x12, 0xE1, 0x8A, +0x52, 0xAC, 0x55, 0xB0, 0x9B, 0x40, 0xD7, 0xC1, 0x02, 0xF0, 0x57, 0x82, 0x03, 0x18, 0x79, 0x6D, +0x57, 0x88, 0x01, 0xBE, 0x9B, 0x40, 0x57, 0x88, 0x31, 0x3E, 0x84, 0x80, 0x83, 0x93, 0x80, 0x81, +0x57, 0x88, 0x31, 0x3E, 0x84, 0x80, 0x80, 0x88, 0x03, 0x9D, 0x9C, 0xEC, 0x3C, 0xB0, 0xDC, 0x40, +0x5C, 0x48, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xEF, 0xC0, 0x57, 0xD8, 0x8C, 0xAC, +0x57, 0x88, 0x31, 0x3E, 0x84, 0x80, 0x6F, 0xC8, 0xDE, 0x80, 0x07, 0x70, 0x03, 0xD0, 0xDE, 0x0C, +0xFF, 0x7E, 0x03, 0x9D, 0x86, 0xAC, 0x97, 0x2C, 0x57, 0x88, 0x31, 0x3E, 0x84, 0x80, 0x6F, 0xC8, +0xDE, 0x80, 0x05, 0x30, 0x03, 0xD0, 0xDE, 0x0C, 0xFF, 0x7E, 0x03, 0x9D, 0x92, 0xAC, 0x5E, 0x88, +0x01, 0x79, 0x83, 0x93, 0x80, 0x40, 0x70, 0xAC, 0x3D, 0xF0, 0xDC, 0x40, 0x5C, 0x48, 0x95, 0x00, +0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xEF, 0xC0, 0x6F, 0xC8, 0x03, 0x59, 0xA9, 0xEC, 0x6F, 0x4B, +0xC8, 0xAC, 0x57, 0x88, 0x2B, 0x7E, 0x84, 0x80, 0x6F, 0xC8, 0x83, 0x93, 0x80, 0x40, 0x57, 0x88, +0x2B, 0x7E, 0x84, 0x80, 0x00, 0x48, 0xDC, 0x40, 0x1A, 0x70, 0xDD, 0x80, 0x5C, 0x48, 0x96, 0x00, +0x5D, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x3E, 0xF0, 0xDC, 0x40, 0x5C, 0x48, 0x95, 0x00, +0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xEF, 0xC0, 0x6F, 0xC8, 0x03, 0x9D, 0xDB, 0x6C, 0xF9, 0x6C, +0x57, 0x88, 0x2B, 0x7E, 0x84, 0x80, 0x83, 0x93, 0x80, 0x81, 0x57, 0x88, 0x2B, 0x7E, 0x84, 0x80, +0x00, 0x48, 0xDC, 0x40, 0x1A, 0x70, 0xDD, 0x80, 0x5C, 0x48, 0x96, 0x00, 0x5D, 0x88, 0x95, 0x00, +0x17, 0x94, 0x17, 0x50, 0xBC, 0x2C, 0x10, 0xF0, 0x6F, 0xC2, 0x03, 0x18, 0xF9, 0x6C, 0x57, 0x88, +0x29, 0x3E, 0x84, 0x80, 0x6F, 0xC8, 0x83, 0x93, 0x80, 0x40, 0x57, 0x88, 0x29, 0x3E, 0x84, 0x80, +0x00, 0x48, 0xDC, 0x40, 0x1B, 0xB0, 0xDD, 0x80, 0x5C, 0x48, 0x96, 0x00, 0x5D, 0x88, 0x95, 0x00, +0x17, 0x94, 0x17, 0x50, 0x57, 0x88, 0xAC, 0x7E, 0x84, 0x80, 0x83, 0x93, 0x80, 0x81, 0x80, 0xCA, +0x26, 0xED, 0x6F, 0xC8, 0xFF, 0x3A, 0x03, 0x9D, 0x0E, 0xED, 0x57, 0x88, 0x29, 0x3E, 0x84, 0x80, +0x07, 0x70, 0x83, 0x93, 0x80, 0x40, 0xFF, 0xB0, 0xDC, 0x40, 0x1B, 0xB0, 0xDD, 0x80, 0x5C, 0x48, +0x96, 0x00, 0x5D, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x21, 0xAD, 0x57, 0x88, 0x29, 0x3E, +0x84, 0x80, 0x07, 0x70, 0x83, 0x93, 0x80, 0x40, 0x57, 0x88, 0x29, 0x3E, 0x84, 0x80, 0x00, 0x48, +0xDC, 0x40, 0x1B, 0xB0, 0xDD, 0x80, 0x5C, 0x48, 0x96, 0x00, 0x5D, 0x88, 0x95, 0x00, 0x17, 0x94, +0x17, 0x50, 0x57, 0x88, 0xAC, 0x7E, 0x84, 0x80, 0x83, 0x93, 0x80, 0x81, 0x3F, 0x30, 0xDC, 0x40, +0x5C, 0x48, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xEF, 0xC0, 0x0A, 0x30, 0x6F, 0xC2, +0x03, 0x18, 0x4C, 0xED, 0x57, 0x88, 0x25, 0x3E, 0x84, 0x80, 0x6F, 0xC8, 0x83, 0x93, 0x80, 0x40, +0x57, 0x88, 0x25, 0x3E, 0x84, 0x80, 0x00, 0x48, 0xDC, 0x40, 0x1C, 0x70, 0xDD, 0x80, 0x5C, 0x48, +0x96, 0x00, 0x5D, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x57, 0x88, 0xA8, 0x3E, 0x84, 0x80, +0x83, 0x93, 0x80, 0x81, 0x80, 0xCA, 0x77, 0xAD, 0x6F, 0xC8, 0xFF, 0x3A, 0x03, 0x9D, 0x60, 0xAD, +0x57, 0x88, 0x25, 0x3E, 0x84, 0x80, 0xFF, 0xB0, 0x83, 0x93, 0x80, 0x81, 0xDC, 0x40, 0x1C, 0x70, +0xDD, 0x80, 0x5C, 0x48, 0x96, 0x00, 0x5D, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x72, 0x2D, 0x57, 0x88, 0x25, 0x3E, 0x84, 0x80, 0x83, 0x93, 0x80, 0x81, 0x57, 0x88, 0x25, 0x3E, 0x84, 0x80, -0x00, 0x48, 0xDD, 0x80, 0x1C, 0x70, 0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, +0x00, 0x48, 0xDC, 0x40, 0x1C, 0x70, 0xDD, 0x80, 0x5C, 0x48, 0x96, 0x00, 0x5D, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x57, 0x88, 0xA8, 0x3E, 0x84, 0x80, 0x83, 0x93, 0x80, 0x81, 0xD7, 0x0A, -0xB4, 0xEC, 0x03, 0x30, 0x9B, 0x40, 0x3C, 0xB0, 0xDD, 0x80, 0x5D, 0x88, 0x95, 0x00, 0x97, 0xD4, -0x97, 0x90, 0x0D, 0x08, 0xEF, 0xC0, 0x6F, 0xC8, 0x03, 0xBA, 0x03, 0x9D, 0xCB, 0x6D, 0x3E, 0xF0, -0xDD, 0x80, 0x5D, 0x88, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xB6, 0x40, 0x3F, 0x30, -0xDD, 0x80, 0x5D, 0x88, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xB8, 0x00, 0x36, 0x42, -0x03, 0x5C, 0x07, 0xEE, 0x0A, 0x30, 0x36, 0x42, 0x03, 0x18, 0x07, 0xEE, 0x36, 0x48, 0xDD, 0x80, -0x1B, 0xB0, 0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, -0x38, 0x08, 0xDD, 0x80, 0x1C, 0x70, 0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, -0x17, 0x94, 0x17, 0x50, 0x36, 0x48, 0xB5, 0x40, 0x38, 0x08, 0xB7, 0x80, 0x1D, 0x2E, 0x09, 0x30, -0xDD, 0x80, 0x1B, 0xB0, 0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, -0x17, 0x50, 0x1C, 0x70, 0xDD, 0xC1, 0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, -0x17, 0x94, 0x17, 0x50, 0x09, 0x30, 0xB5, 0x40, 0xB7, 0xC1, 0x04, 0xF0, 0x9B, 0x40, 0x3C, 0xB0, -0xDD, 0x80, 0x5D, 0x88, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xEF, 0xC0, 0x6F, 0xC8, -0x04, 0x7A, 0x03, 0x9D, 0x1F, 0x6E, 0x3E, 0xF0, 0xDD, 0x80, 0x5D, 0x88, 0x95, 0x00, 0x97, 0xD4, -0x97, 0x90, 0x0D, 0x08, 0xBA, 0x40, 0x3F, 0x30, 0xDD, 0x80, 0x5D, 0x88, 0x95, 0x00, 0x97, 0xD4, -0x97, 0x90, 0x0D, 0x08, 0xBC, 0x40, 0x3A, 0x42, 0x03, 0x5C, 0x5A, 0x2E, 0x02, 0xF0, 0x3C, 0x42, -0x03, 0x5C, 0x5A, 0x2E, 0x3A, 0x48, 0xDD, 0x80, 0x1B, 0xB0, 0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, -0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x3C, 0x48, 0xDD, 0x80, 0x1C, 0x70, 0xDE, 0x80, -0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x3A, 0x48, 0xB9, 0x40, -0x3C, 0x48, 0x71, 0x2E, 0x23, 0x70, 0xDD, 0x80, 0x1B, 0xB0, 0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, -0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x10, 0xF0, 0xDD, 0x80, 0x1C, 0x70, 0xDE, 0x80, -0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x23, 0x70, 0xB9, 0x40, -0x10, 0xF0, 0xBB, 0x80, 0x05, 0x30, 0x9B, 0x40, 0x3C, 0xB0, 0xDD, 0x80, 0x5D, 0x88, 0x95, 0x00, -0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xEF, 0xC0, 0x6F, 0xC8, 0x05, 0xBA, 0x03, 0x9D, 0x74, 0x2E, -0x3E, 0xF0, 0xDD, 0x80, 0x5D, 0x88, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xC6, 0x00, -0x3F, 0x30, 0xDD, 0x80, 0x5D, 0x88, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xC8, 0xC0, -0x46, 0x02, 0x03, 0x5C, 0xB2, 0x2E, 0x10, 0xF0, 0x46, 0x02, 0x03, 0x18, 0xB2, 0x2E, 0x48, 0xC8, -0x03, 0x59, 0xB2, 0x2E, 0x46, 0x08, 0xDD, 0x80, 0x1B, 0xB0, 0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, -0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x48, 0xC8, 0xDD, 0x80, 0x1C, 0x70, 0xDE, 0x80, -0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x46, 0x08, 0xC5, 0x00, -0x48, 0xC8, 0xC9, 0x2E, 0x0B, 0x70, 0xDD, 0x80, 0x1B, 0xB0, 0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, -0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x07, 0x70, 0xDD, 0x80, 0x1C, 0x70, 0xDE, 0x80, -0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x0B, 0x70, 0xC5, 0x00, -0x07, 0x70, 0xC7, 0x40, 0x06, 0x30, 0x9B, 0x40, 0x3C, 0xB0, 0xDD, 0x80, 0x5D, 0x88, 0x95, 0x00, -0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xEF, 0xC0, 0x6F, 0xC8, 0x06, 0xBA, 0x03, 0x9D, 0xCC, 0x2E, -0x3D, 0xF0, 0xDD, 0x80, 0x5D, 0x88, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xCC, 0x00, -0x3E, 0xF0, 0xDD, 0x80, 0x5D, 0x88, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xCA, 0x00, -0x3F, 0x30, 0xDD, 0x80, 0x5D, 0x88, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xCE, 0x40, -0x4A, 0x08, 0x4C, 0x02, 0x03, 0x5C, 0x1F, 0xAF, 0x4E, 0x48, 0x4A, 0x02, 0x03, 0x5C, 0x1F, 0xAF, -0x4E, 0x48, 0x03, 0x59, 0x1F, 0xAF, 0x4C, 0x08, 0xDD, 0x80, 0x1A, 0x70, 0xDE, 0x80, 0x5D, 0x88, -0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x4A, 0x08, 0xDD, 0x80, 0x1B, 0xB0, -0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x4E, 0x48, -0xDD, 0x80, 0x1C, 0x70, 0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, -0x17, 0x50, 0x4C, 0x08, 0xCB, 0x40, 0x4A, 0x08, 0xC9, 0x00, 0x4E, 0x48, 0x42, 0xEF, 0x73, 0xF0, -0xDD, 0x80, 0x1A, 0x70, 0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, -0x17, 0x50, 0x64, 0x70, 0xDD, 0x80, 0x1B, 0xB0, 0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, -0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x2D, 0xB0, 0xDD, 0x80, 0x1C, 0x70, 0xDE, 0x80, 0x5D, 0x88, -0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x73, 0xF0, 0xCB, 0x40, 0x64, 0x70, -0xC9, 0x00, 0x2D, 0xB0, 0xCD, 0x40, 0x07, 0x70, 0x9B, 0x40, 0x3C, 0xB0, 0xDD, 0x80, 0x5D, 0x88, +0x64, 0xAC, 0x03, 0x30, 0x9B, 0x40, 0x3C, 0xB0, 0xDC, 0x40, 0x5C, 0x48, 0x95, 0x00, 0x97, 0xD4, +0x97, 0x90, 0x0D, 0x08, 0xEF, 0xC0, 0x6F, 0xC8, 0x03, 0xBA, 0x03, 0x9D, 0x7B, 0xAD, 0x3E, 0xF0, +0xDC, 0x40, 0x5C, 0x48, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xB4, 0x00, 0x3F, 0x30, +0xDC, 0x40, 0x5C, 0x48, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xB6, 0x40, 0x34, 0x02, +0x03, 0x5C, 0xB7, 0xAD, 0x0A, 0x30, 0x34, 0x02, 0x03, 0x18, 0xB7, 0xAD, 0x34, 0x08, 0xDC, 0x40, +0x1B, 0xB0, 0xDD, 0x80, 0x5C, 0x48, 0x96, 0x00, 0x5D, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, +0x36, 0x48, 0xDC, 0x40, 0x1C, 0x70, 0xDD, 0x80, 0x5C, 0x48, 0x96, 0x00, 0x5D, 0x88, 0x95, 0x00, +0x17, 0x94, 0x17, 0x50, 0x34, 0x08, 0xB3, 0x40, 0x36, 0x48, 0xB5, 0x40, 0xCD, 0x6D, 0x09, 0x30, +0xDC, 0x40, 0x1B, 0xB0, 0xDD, 0x80, 0x5C, 0x48, 0x96, 0x00, 0x5D, 0x88, 0x95, 0x00, 0x17, 0x94, +0x17, 0x50, 0x1C, 0x70, 0xDC, 0x81, 0xDD, 0x80, 0x5C, 0x48, 0x96, 0x00, 0x5D, 0x88, 0x95, 0x00, +0x17, 0x94, 0x17, 0x50, 0x09, 0x30, 0xB3, 0x40, 0xB5, 0x81, 0x04, 0xF0, 0x9B, 0x40, 0x3C, 0xB0, +0xDC, 0x40, 0x5C, 0x48, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xEF, 0xC0, 0x6F, 0xC8, +0x04, 0x7A, 0x03, 0x9D, 0xCF, 0xAD, 0x3E, 0xF0, 0xDC, 0x40, 0x5C, 0x48, 0x95, 0x00, 0x97, 0xD4, +0x97, 0x90, 0x0D, 0x08, 0xB8, 0x00, 0x3F, 0x30, 0xDC, 0x40, 0x5C, 0x48, 0x95, 0x00, 0x97, 0xD4, +0x97, 0x90, 0x0D, 0x08, 0xBA, 0x40, 0x38, 0x02, 0x03, 0x5C, 0x0A, 0xAE, 0x02, 0xF0, 0x3A, 0x42, +0x03, 0x5C, 0x0A, 0xAE, 0x38, 0x08, 0xDC, 0x40, 0x1B, 0xB0, 0xDD, 0x80, 0x5C, 0x48, 0x96, 0x00, +0x5D, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x3A, 0x48, 0xDC, 0x40, 0x1C, 0x70, 0xDD, 0x80, +0x5C, 0x48, 0x96, 0x00, 0x5D, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x38, 0x08, 0xB7, 0x80, +0x3A, 0x48, 0x21, 0xAE, 0x23, 0x70, 0xDC, 0x40, 0x1B, 0xB0, 0xDD, 0x80, 0x5C, 0x48, 0x96, 0x00, +0x5D, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x10, 0xF0, 0xDC, 0x40, 0x1C, 0x70, 0xDD, 0x80, +0x5C, 0x48, 0x96, 0x00, 0x5D, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x23, 0x70, 0xB7, 0x80, +0x10, 0xF0, 0xB9, 0x40, 0x05, 0x30, 0x9B, 0x40, 0x3C, 0xB0, 0xDC, 0x40, 0x5C, 0x48, 0x95, 0x00, +0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xEF, 0xC0, 0x6F, 0xC8, 0x05, 0xBA, 0x03, 0x9D, 0x24, 0xAE, +0x3E, 0xF0, 0xDC, 0x40, 0x5C, 0x48, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xC4, 0xC0, +0x3F, 0x30, 0xDC, 0x40, 0x5C, 0x48, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xC6, 0x00, +0x44, 0xC2, 0x03, 0x5C, 0x62, 0xEE, 0x10, 0xF0, 0x44, 0xC2, 0x03, 0x18, 0x62, 0xEE, 0x46, 0x08, +0x03, 0x59, 0x62, 0xEE, 0x44, 0xC8, 0xDC, 0x40, 0x1B, 0xB0, 0xDD, 0x80, 0x5C, 0x48, 0x96, 0x00, +0x5D, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x46, 0x08, 0xDC, 0x40, 0x1C, 0x70, 0xDD, 0x80, +0x5C, 0x48, 0x96, 0x00, 0x5D, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x44, 0xC8, 0xC3, 0x00, +0x46, 0x08, 0x79, 0x6E, 0x0B, 0x70, 0xDC, 0x40, 0x1B, 0xB0, 0xDD, 0x80, 0x5C, 0x48, 0x96, 0x00, +0x5D, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x07, 0x70, 0xDC, 0x40, 0x1C, 0x70, 0xDD, 0x80, +0x5C, 0x48, 0x96, 0x00, 0x5D, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x0B, 0x70, 0xC3, 0x00, +0x07, 0x70, 0xC5, 0x00, 0x06, 0x30, 0x9B, 0x40, 0x3C, 0xB0, 0xDC, 0x40, 0x5C, 0x48, 0x95, 0x00, +0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xEF, 0xC0, 0x6F, 0xC8, 0x06, 0xBA, 0x03, 0x9D, 0x7C, 0x6E, +0x3D, 0xF0, 0xDC, 0x40, 0x5C, 0x48, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xCA, 0x00, +0x3E, 0xF0, 0xDC, 0x40, 0x5C, 0x48, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xC8, 0xC0, +0x3F, 0x30, 0xDC, 0x40, 0x5C, 0x48, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xCC, 0x00, +0x48, 0xC8, 0x4A, 0x02, 0x03, 0x5C, 0xCF, 0xAE, 0x4C, 0x08, 0x48, 0xC2, 0x03, 0x5C, 0xCF, 0xAE, +0x4C, 0x08, 0x03, 0x59, 0xCF, 0xAE, 0x4A, 0x08, 0xDC, 0x40, 0x1A, 0x70, 0xDD, 0x80, 0x5C, 0x48, +0x96, 0x00, 0x5D, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x48, 0xC8, 0xDC, 0x40, 0x1B, 0xB0, +0xDD, 0x80, 0x5C, 0x48, 0x96, 0x00, 0x5D, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x4C, 0x08, +0xDC, 0x40, 0x1C, 0x70, 0xDD, 0x80, 0x5C, 0x48, 0x96, 0x00, 0x5D, 0x88, 0x95, 0x00, 0x17, 0x94, +0x17, 0x50, 0x4A, 0x08, 0xC9, 0x00, 0x48, 0xC8, 0xC7, 0x40, 0x4C, 0x08, 0xF2, 0x6E, 0x73, 0xF0, +0xDC, 0x40, 0x1A, 0x70, 0xDD, 0x80, 0x5C, 0x48, 0x96, 0x00, 0x5D, 0x88, 0x95, 0x00, 0x17, 0x94, +0x17, 0x50, 0x64, 0x70, 0xDC, 0x40, 0x1B, 0xB0, 0xDD, 0x80, 0x5C, 0x48, 0x96, 0x00, 0x5D, 0x88, +0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x2D, 0xB0, 0xDC, 0x40, 0x1C, 0x70, 0xDD, 0x80, 0x5C, 0x48, +0x96, 0x00, 0x5D, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x73, 0xF0, 0xC9, 0x00, 0x64, 0x70, +0xC7, 0x40, 0x2D, 0xB0, 0xCB, 0x40, 0x07, 0x70, 0x9B, 0x40, 0x3C, 0xB0, 0xDC, 0x40, 0x5C, 0x48, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xEF, 0xC0, 0x6F, 0xC8, 0x07, 0xFA, 0x03, 0x9D, -0x45, 0x2F, 0x3E, 0xF0, 0xDD, 0x80, 0x5D, 0x88, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, -0xBE, 0x80, 0x3F, 0x30, 0xDD, 0x80, 0x5D, 0x88, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, -0xC0, 0x80, 0x3E, 0x82, 0x03, 0x5C, 0x83, 0x2F, 0x10, 0xF0, 0x3E, 0x82, 0x03, 0x18, 0x83, 0x2F, -0x40, 0x88, 0x03, 0x59, 0x83, 0x2F, 0x3E, 0x88, 0xDD, 0x80, 0x1B, 0xB0, 0xDE, 0x80, 0x5D, 0x88, -0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x40, 0x88, 0xDD, 0x80, 0x1C, 0x70, -0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x3E, 0x88, -0xBD, 0x80, 0x40, 0x88, 0x9A, 0x6F, 0x0E, 0x70, 0xDD, 0x80, 0x1B, 0xB0, 0xDE, 0x80, 0x5D, 0x88, -0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x04, 0xF0, 0xDD, 0x80, 0x1C, 0x70, -0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x0E, 0x70, -0xBD, 0x80, 0x04, 0xF0, 0xBF, 0xC0, 0x08, 0xF0, 0x9B, 0x40, 0x3C, 0xB0, 0xDD, 0x80, 0x5D, 0x88, +0xF5, 0xAE, 0x3E, 0xF0, 0xDC, 0x40, 0x5C, 0x48, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, +0xBC, 0x40, 0x3F, 0x30, 0xDC, 0x40, 0x5C, 0x48, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, +0xBE, 0x80, 0x3C, 0x42, 0x03, 0x5C, 0x33, 0x6F, 0x10, 0xF0, 0x3C, 0x42, 0x03, 0x18, 0x33, 0x6F, +0x3E, 0x88, 0x03, 0x59, 0x33, 0x6F, 0x3C, 0x48, 0xDC, 0x40, 0x1B, 0xB0, 0xDD, 0x80, 0x5C, 0x48, +0x96, 0x00, 0x5D, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x3E, 0x88, 0xDC, 0x40, 0x1C, 0x70, +0xDD, 0x80, 0x5C, 0x48, 0x96, 0x00, 0x5D, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x3C, 0x48, +0xBB, 0x80, 0x3E, 0x88, 0x4A, 0x2F, 0x0E, 0x70, 0xDC, 0x40, 0x1B, 0xB0, 0xDD, 0x80, 0x5C, 0x48, +0x96, 0x00, 0x5D, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x04, 0xF0, 0xDC, 0x40, 0x1C, 0x70, +0xDD, 0x80, 0x5C, 0x48, 0x96, 0x00, 0x5D, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x0E, 0x70, +0xBB, 0x80, 0x04, 0xF0, 0xBD, 0x80, 0x08, 0xF0, 0x9B, 0x40, 0x3C, 0xB0, 0xDC, 0x40, 0x5C, 0x48, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xEF, 0xC0, 0x6F, 0xC8, 0x08, 0x7A, 0x03, 0x9D, -0x9D, 0xAF, 0x3E, 0xF0, 0xDD, 0x80, 0x5D, 0x88, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, -0xC2, 0xC0, 0x3F, 0x30, 0xDD, 0x80, 0x5D, 0x88, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, -0xC4, 0xC0, 0x42, 0xC8, 0x44, 0xC2, 0x03, 0x18, 0xD9, 0xAF, 0x02, 0xF0, 0x44, 0xC2, 0x03, 0x5C, -0xD9, 0xAF, 0x42, 0xC8, 0xDD, 0x80, 0x1B, 0xB0, 0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, -0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x44, 0xC8, 0xDD, 0x80, 0x1C, 0x70, 0xDE, 0x80, 0x5D, 0x88, -0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x42, 0xC8, 0xC1, 0xC0, 0x44, 0xC8, -0xF0, 0x6F, 0x84, 0x30, 0xDD, 0x80, 0x1B, 0xB0, 0xDE, 0x80, 0x5D, 0x88, 0x96, 0x00, 0x5E, 0x88, -0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x34, 0x70, 0xDD, 0x80, 0x1C, 0x70, 0xDE, 0x80, 0x5D, 0x88, -0x96, 0x00, 0x5E, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x84, 0x30, 0xC1, 0xC0, 0x34, 0x70, -0xC3, 0x00, 0x09, 0x30, 0x9B, 0x40, 0x3C, 0xB0, 0xDD, 0x80, 0x5D, 0x88, 0x95, 0x00, 0x97, 0xD4, -0x97, 0x90, 0x0D, 0x08, 0xEF, 0xC0, 0x6F, 0xC8, 0x09, 0xBA, 0x03, 0x59, 0x08, 0x40, 0xF3, 0xEF +0x4D, 0x6F, 0x3E, 0xF0, 0xDC, 0x40, 0x5C, 0x48, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, +0xC0, 0x80, 0x3F, 0x30, 0xDC, 0x40, 0x5C, 0x48, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, +0xC2, 0xC0, 0x40, 0x88, 0x42, 0xC2, 0x03, 0x18, 0x89, 0x2F, 0x02, 0xF0, 0x42, 0xC2, 0x03, 0x5C, +0x89, 0x2F, 0x40, 0x88, 0xDC, 0x40, 0x1B, 0xB0, 0xDD, 0x80, 0x5C, 0x48, 0x96, 0x00, 0x5D, 0x88, +0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x42, 0xC8, 0xDC, 0x40, 0x1C, 0x70, 0xDD, 0x80, 0x5C, 0x48, +0x96, 0x00, 0x5D, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x40, 0x88, 0xBF, 0xC0, 0x42, 0xC8, +0xA0, 0xEF, 0x84, 0x30, 0xDC, 0x40, 0x1B, 0xB0, 0xDD, 0x80, 0x5C, 0x48, 0x96, 0x00, 0x5D, 0x88, +0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x34, 0x70, 0xDC, 0x40, 0x1C, 0x70, 0xDD, 0x80, 0x5C, 0x48, +0x96, 0x00, 0x5D, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x84, 0x30, 0xBF, 0xC0, 0x34, 0x70, +0xC1, 0xC0, 0x09, 0x30, 0x9B, 0x40, 0x3C, 0xB0, 0xDC, 0x40, 0x5C, 0x48, 0x95, 0x00, 0x97, 0xD4, +0x97, 0x90, 0x0D, 0x08, 0xEF, 0xC0, 0x6F, 0xC8, 0x0A, 0xBA, 0x03, 0x9D, 0xA3, 0x6F, 0x3F, 0x30, +0xDC, 0x40, 0x5C, 0x48, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xCF, 0x80, 0xDC, 0x40, +0x1C, 0x70, 0xDD, 0x80, 0x5C, 0x48, 0x96, 0x00, 0x5D, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, +0x0B, 0x70, 0x9B, 0x40, 0x3C, 0xB0, 0xDC, 0x40, 0x5C, 0x48, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, +0x0D, 0x08, 0xEF, 0xC0, 0x6F, 0xC8, 0x0B, 0xFA, 0x03, 0x9D, 0xC2, 0x2F, 0x3F, 0x30, 0xDC, 0x40, +0x5C, 0x48, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xCD, 0x40, 0x4D, 0x48, 0x03, 0x59, +0xDB, 0xEF, 0x4D, 0xCB, 0xF4, 0xAF, 0x4D, 0x48, 0xDC, 0x40, 0x1C, 0x70, 0xDD, 0x80, 0x5C, 0x48, +0x96, 0x00, 0x5D, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x0F, 0xB0, 0x9B, 0x40, 0x3C, 0xB0, +0xDC, 0x40, 0x5C, 0x48, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xEF, 0xC0, 0x6F, 0xC8, +0x0F, 0x3A, 0x03, 0x59, 0x08, 0x40, 0xE7, 0xEF, 0xCD, 0x81, 0x4D, 0x48, 0xDC, 0x40, 0x1C, 0x70, +0xDD, 0x80, 0x5C, 0x48, 0x96, 0x00, 0x5D, 0x88, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0xE5, 0xAF }; \ No newline at end of file diff --git a/libloragw/src/arb_fw.var b/libloragw/src/arb_fw.var index 325df3c6..5d115ce9 100644 --- a/libloragw/src/arb_fw.var +++ b/libloragw/src/arb_fw.var @@ -125,80 +125,80 @@ static uint8_t arb_firmware[8192] = { 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, -0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0x64, 0xC0, 0x80, 0x81, 0x84, 0x0A, 0x04, 0xC6, -0x03, 0x59, 0x00, 0xF4, 0x04, 0xC6, 0xF5, 0xAB, 0x95, 0x41, 0x96, 0x41, 0x97, 0x81, 0x98, 0x01, -0x99, 0x41, 0x9A, 0x41, 0x9B, 0x81, 0x9C, 0x41, 0x9E, 0x81, 0xDC, 0x81, 0x10, 0xF0, 0x5C, 0x42, -0x03, 0x18, 0x25, 0xAC, 0x5C, 0x48, 0x22, 0xFE, 0x84, 0x80, 0x80, 0x81, 0x5C, 0x48, 0xB0, 0x3E, -0x84, 0x80, 0x80, 0x81, 0x5C, 0x48, 0xD0, 0x3E, 0x84, 0x80, 0x80, 0x81, 0x5C, 0x48, 0xA0, 0xFE, -0x84, 0x80, 0x80, 0x81, 0x5C, 0x48, 0xC0, 0xFE, 0x84, 0x80, 0x80, 0x81, 0x5C, 0x48, 0xE0, 0x3E, -0x84, 0x80, 0xFF, 0xB0, 0x80, 0x40, 0xDC, 0xCA, 0x06, 0x6C, 0xC6, 0x41, 0xC7, 0x81, 0xC9, 0x41, -0xC4, 0x01, 0xC8, 0x01, 0x6C, 0x50, 0xEC, 0x90, 0xA1, 0x01, 0xDC, 0x81, 0x40, 0xF0, 0x5C, 0x42, -0x03, 0x18, 0x3A, 0xEC, 0x5C, 0x48, 0xA0, 0xFE, 0x84, 0x80, 0xFF, 0xB0, 0x83, 0xD7, 0x80, 0x40, -0xDC, 0xCA, 0x2E, 0xEC, 0xC5, 0x41, 0x08, 0xF0, 0x45, 0x02, 0x03, 0x18, 0x08, 0x40, 0x45, 0x08, -0x3A, 0x7E, 0x84, 0x80, 0x83, 0x93, 0x80, 0x81, 0x45, 0x08, 0x32, 0x3E, 0x84, 0x80, 0x80, 0x81, -0xC5, 0x8A, 0x3B, 0x2C, 0x01, 0xF0, 0xA0, 0x80, 0x8A, 0x51, 0xFC, 0x63, 0x8A, 0x51, 0x03, 0x30, -0x9E, 0x40, 0x02, 0xF0, 0x9E, 0x40, 0x01, 0xF0, 0x9E, 0x40, 0x9E, 0x81, 0x9B, 0x81, 0xE1, 0x41, -0xE1, 0x8A, 0xE2, 0x00, 0x61, 0x08, 0x96, 0x00, 0x62, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, -0x2A, 0x70, 0x83, 0x52, 0x03, 0x53, 0xE1, 0x00, 0x61, 0x08, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, -0x0D, 0x08, 0xDF, 0xC0, 0x5F, 0xC8, 0xF7, 0xFA, 0x03, 0x9D, 0x70, 0xAC, 0xEB, 0xC1, 0x72, 0xEC, -0xEB, 0xC1, 0xEB, 0x0A, 0x6B, 0x88, 0x03, 0x59, 0xE2, 0xEC, 0x01, 0xF0, 0x9B, 0x40, 0x29, 0x70, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, +0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0x64, 0xC0, +0x80, 0x81, 0x84, 0x0A, 0x04, 0xC6, 0x03, 0x59, 0x00, 0xF4, 0x04, 0xC6, 0x08, 0x2C, 0x95, 0x41, +0x96, 0x41, 0x97, 0x81, 0x98, 0x01, 0x99, 0x41, 0x9A, 0x41, 0x9B, 0x81, 0x9C, 0x41, 0x9E, 0x81, +0xDC, 0x81, 0x10, 0xF0, 0x5C, 0x42, 0x03, 0x18, 0x38, 0xAC, 0x5C, 0x48, 0x22, 0xFE, 0x84, 0x80, +0x80, 0x81, 0x5C, 0x48, 0xB0, 0x3E, 0x84, 0x80, 0x80, 0x81, 0x5C, 0x48, 0xD0, 0x3E, 0x84, 0x80, +0x80, 0x81, 0x5C, 0x48, 0xA0, 0xFE, 0x84, 0x80, 0x80, 0x81, 0x5C, 0x48, 0xC0, 0xFE, 0x84, 0x80, +0x80, 0x81, 0x5C, 0x48, 0xE0, 0x3E, 0x84, 0x80, 0xFF, 0xB0, 0x80, 0x40, 0xDC, 0xCA, 0x19, 0xAC, +0xC6, 0x41, 0xC7, 0x81, 0xC9, 0x41, 0xC4, 0x01, 0xC8, 0x01, 0x6F, 0xD0, 0xEF, 0x10, 0xA1, 0x01, +0xDC, 0x81, 0x40, 0xF0, 0x5C, 0x42, 0x03, 0x18, 0x4D, 0xEC, 0x5C, 0x48, 0xA0, 0xFE, 0x84, 0x80, +0xFF, 0xB0, 0x83, 0xD7, 0x80, 0x40, 0xDC, 0xCA, 0x41, 0x6C, 0xC5, 0x41, 0x08, 0xF0, 0x45, 0x02, +0x03, 0x18, 0x08, 0x40, 0x45, 0x08, 0x3A, 0x7E, 0x84, 0x80, 0x83, 0x93, 0x80, 0x81, 0x45, 0x08, +0x32, 0x3E, 0x84, 0x80, 0x80, 0x81, 0xC5, 0x8A, 0x4E, 0xEC, 0x02, 0xF0, 0xA0, 0x80, 0x8A, 0x51, +0x0F, 0xA4, 0x8A, 0x51, 0x03, 0x30, 0x9E, 0x40, 0x02, 0xF0, 0x9E, 0x40, 0x01, 0xF0, 0x9E, 0x40, +0x9E, 0x81, 0x9B, 0x81, 0x02, 0xF0, 0xE1, 0x00, 0x01, 0xF0, 0xE2, 0x00, 0x61, 0x08, 0x96, 0x00, +0x62, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x2A, 0x70, 0x83, 0x52, 0x03, 0x53, 0xE1, 0x00, +0x61, 0x08, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xDF, 0xC0, 0x5F, 0xC8, 0xF7, 0xFA, +0x03, 0x9D, 0x84, 0x6C, 0xEE, 0xC1, 0x86, 0xAC, 0xEE, 0xC1, 0xEE, 0x0A, 0x6E, 0x88, 0x03, 0x59, +0xCC, 0xEC, 0x01, 0xF0, 0x9B, 0x40, 0x29, 0x70, 0x83, 0x52, 0x03, 0x53, 0xE1, 0x00, 0x61, 0x08, +0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xDF, 0xC0, 0x5F, 0x4B, 0x8B, 0xEC, 0x28, 0x30, +0x83, 0x52, 0x03, 0x53, 0xE1, 0x00, 0x61, 0x08, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, +0xCA, 0x00, 0x0D, 0x70, 0x4A, 0x02, 0x03, 0x18, 0xAC, 0xEC, 0x05, 0x30, 0x4A, 0x02, 0xD5, 0x81, +0x03, 0x5C, 0xAD, 0x2C, 0xD5, 0xCA, 0xAD, 0x2C, 0xD5, 0x81, 0x2A, 0x70, 0x83, 0x52, 0x03, 0x53, +0xE1, 0x00, 0x61, 0x08, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xD6, 0x40, 0x10, 0xF0, +0x56, 0x42, 0x03, 0x5C, 0xBD, 0x6C, 0x10, 0xF0, 0xD6, 0x40, 0xF0, 0xB0, 0xD6, 0x0E, 0xD6, 0xC5, +0x2B, 0xB0, 0x83, 0x52, 0x03, 0x53, 0xE1, 0x00, 0x61, 0x08, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, +0x0D, 0x08, 0xE0, 0xC0, 0x9B, 0x81, 0xD5, 0x2C, 0x07, 0x70, 0xD5, 0x81, 0xD5, 0xCA, 0xCA, 0x00, +0x30, 0x30, 0xD6, 0x40, 0xE0, 0x01, 0x01, 0xF0, 0x9B, 0x40, 0x0E, 0x58, 0xD5, 0x2C, 0x25, 0x70, 0x83, 0x52, 0x03, 0x53, 0xE1, 0x00, 0x61, 0x08, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, -0xDF, 0xC0, 0x5F, 0x4B, 0x77, 0x6C, 0x28, 0x30, 0x83, 0x52, 0x03, 0x53, 0xE1, 0x00, 0x61, 0x08, -0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xCA, 0x00, 0x0D, 0x70, 0x4A, 0x02, 0x03, 0x18, -0xD6, 0x2C, 0x05, 0x30, 0x4A, 0x02, 0xD5, 0x81, 0x03, 0x5C, 0xD7, 0x6C, 0xD5, 0xCA, 0x4A, 0x08, -0xE1, 0x00, 0x01, 0xF0, 0xE2, 0x00, 0x61, 0x08, 0x96, 0x00, 0x62, 0x08, 0x95, 0x00, 0x17, 0x94, -0x17, 0x50, 0x2A, 0x70, 0x83, 0x52, 0x03, 0x53, 0xE1, 0x00, 0x61, 0x08, 0x95, 0x00, 0x97, 0xD4, -0x97, 0x90, 0x0D, 0x08, 0xD6, 0x40, 0x10, 0xF0, 0x56, 0x42, 0x03, 0x5C, 0xB1, 0xEC, 0x10, 0xF0, -0xD6, 0x40, 0xF0, 0xB0, 0xD6, 0x0E, 0xD6, 0xC5, 0x56, 0x48, 0xE1, 0x00, 0x03, 0x30, 0xE2, 0x00, -0x61, 0x08, 0x96, 0x00, 0x62, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x2B, 0xB0, 0x83, 0x52, -0x03, 0x53, 0xE1, 0x00, 0x61, 0x08, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xE0, 0xC0, -0x01, 0xF0, 0xE0, 0x45, 0x60, 0xC8, 0xE1, 0x00, 0x04, 0xF0, 0xE2, 0x00, 0x61, 0x08, 0x96, 0x00, -0x62, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x9B, 0x81, 0xEB, 0x6C, 0xD5, 0x81, 0xFF, 0xB0, -0xE1, 0x00, 0x01, 0xF0, 0xE2, 0x00, 0x61, 0x08, 0x96, 0x00, 0x62, 0x08, 0x95, 0x00, 0x17, 0x94, -0x17, 0x50, 0xA1, 0xAC, 0x07, 0x70, 0xD5, 0x81, 0xD5, 0xCA, 0xCA, 0x00, 0x30, 0x30, 0xD6, 0x40, -0x01, 0xF0, 0xE0, 0x01, 0x9B, 0x40, 0x0E, 0x58, 0xEB, 0x6C, 0x25, 0x70, 0x83, 0x52, 0x03, 0x53, -0xE1, 0x00, 0x61, 0x08, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xD7, 0x80, 0x07, 0x70, -0xD7, 0x05, 0x57, 0x88, 0xC5, 0x00, 0x08, 0xF0, 0x45, 0x02, 0x03, 0x18, 0xEB, 0x6C, 0x45, 0x08, -0x8A, 0x51, 0x07, 0xA5, 0x8A, 0x51, 0x0E, 0x58, 0xEB, 0x6C, 0xC5, 0x8A, 0xFB, 0xAC, 0xEA, 0x40, -0xE1, 0x00, 0x00, 0xB0, 0xE2, 0x00, 0x61, 0x08, 0x96, 0x00, 0x62, 0x08, 0x95, 0x00, 0x17, 0x94, -0x17, 0x50, 0x8E, 0xDC, 0x08, 0x40, 0x18, 0x14, 0x05, 0x30, 0xE8, 0x00, 0xE8, 0xCB, 0x16, 0xED, -0x21, 0x30, 0x83, 0x52, 0x03, 0x53, 0xE1, 0x00, 0x61, 0x08, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, -0x0D, 0x08, 0xC6, 0x00, 0x22, 0x30, 0x83, 0x52, 0x03, 0x53, 0xE1, 0x00, 0x61, 0x08, 0x95, 0x00, -0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xC7, 0x40, 0x24, 0x30, 0x83, 0x52, 0x03, 0x53, 0xE1, 0x00, -0x61, 0x08, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xC9, 0x00, 0x20, 0xF0, 0x83, 0x52, -0x03, 0x53, 0xE1, 0x00, 0x61, 0x08, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xC4, 0xC0, -0x23, 0x70, 0x83, 0x52, 0x03, 0x53, 0xE1, 0x00, 0x61, 0x08, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, -0x0D, 0x08, 0xC8, 0xC0, 0x18, 0xD0, 0x55, 0xCB, 0x56, 0x2D, 0x48, 0xC8, 0xE8, 0x00, 0xE9, 0x81, -0xE8, 0x1B, 0xE9, 0xC3, 0x4A, 0x46, 0x69, 0x44, 0x03, 0x59, 0x60, 0xAD, 0x0A, 0x30, 0x49, 0x02, -0x03, 0x5C, 0x8D, 0x2D, 0x44, 0xC8, 0x80, 0x7A, 0x78, 0x7E, 0x03, 0x5C, 0x81, 0xAD, 0x8D, 0x2D, -0x44, 0xC8, 0x80, 0x7A, 0x78, 0x7E, 0x03, 0x5C, 0xC4, 0xDB, 0x56, 0x2D, 0x44, 0xC8, 0x3A, 0x7E, -0x84, 0x80, 0x83, 0x93, 0x00, 0x48, 0x01, 0xBE, 0xE8, 0x00, 0x44, 0xC8, 0x3A, 0x7E, 0x84, 0x80, -0x68, 0x08, 0x80, 0x40, 0x44, 0xC8, 0x3A, 0x7E, 0x84, 0x80, 0x00, 0x48, 0xE1, 0x00, 0x44, 0xC8, -0x01, 0xBE, 0xE2, 0x00, 0x61, 0x08, 0x96, 0x00, 0x62, 0x08, 0x95, 0x00, 0x17, 0x94, 0x17, 0x50, -0x56, 0x2D, 0xC4, 0xDB, 0x8D, 0x2D, 0x48, 0xC8, 0x80, 0x7A, 0x73, 0xBE, 0x03, 0x18, 0x8D, 0x2D, -0x48, 0xC8, 0x80, 0x7A, 0x7B, 0xFE, 0x03, 0x18, 0x8F, 0x6D, 0x6C, 0xD5, 0x30, 0xAE, 0x6C, 0x91, -0x48, 0xC8, 0x11, 0xFE, 0x84, 0x80, 0x8A, 0x51, 0x02, 0xA0, 0x8A, 0x51, 0xC7, 0xC5, 0x48, 0xC8, -0xE8, 0x00, 0x03, 0xD0, 0xE8, 0xCD, 0x03, 0xD0, 0xE8, 0xCD, 0x03, 0xD0, 0xE8, 0xCD, 0x44, 0xC8, -0x68, 0x87, 0xD8, 0x7E, 0xDE, 0x80, 0xA0, 0xFE, 0x84, 0x80, 0x83, 0xD7, 0x00, 0x48, 0xDC, 0x40, -0x3E, 0xF0, 0x83, 0x52, 0x03, 0x53, 0xE1, 0x00, 0x61, 0x08, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, -0x0D, 0x08, 0xC2, 0xC0, 0x3F, 0x30, 0x83, 0x52, 0x03, 0x53, 0xE1, 0x00, 0x61, 0x08, 0x95, 0x00, -0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xC3, 0x00, 0x6C, 0x91, 0xDC, 0x81, 0x08, 0xF0, 0x5C, 0x42, -0x03, 0x18, 0xF5, 0xAD, 0x5C, 0x48, 0xE0, 0x3E, 0x84, 0x80, 0x83, 0x93, 0x00, 0x48, 0x5E, 0xC6, -0x03, 0x9D, 0xF1, 0x6D, 0x5C, 0x48, 0x01, 0xBE, 0x84, 0x80, 0x8A, 0x51, 0x02, 0xA0, 0x8A, 0x51, -0x42, 0x05, 0x03, 0x59, 0xF1, 0x6D, 0x46, 0x08, 0xE1, 0x00, 0x5C, 0x48, 0xB0, 0x3E, 0x84, 0x80, -0x00, 0x48, 0xE2, 0x00, 0x5C, 0x48, 0x22, 0xFE, 0x84, 0x80, 0x00, 0x48, 0xE3, 0x40, 0x56, 0x48, -0xE4, 0x00, 0x48, 0xC8, 0xE5, 0x40, 0x47, 0x48, 0x52, 0xE7, 0x8A, 0x51, 0xE8, 0x00, 0x68, 0x4C, -0x03, 0x5C, 0xEE, 0xAD, 0x83, 0x52, 0x03, 0x53, 0x6C, 0xD5, 0xF1, 0x6D, 0x83, 0x52, 0x03, 0x53, -0x6C, 0x91, 0x6C, 0xD9, 0xF5, 0xAD, 0xDC, 0xCA, 0xBE, 0xAD, 0x6C, 0xD9, 0x30, 0xAE, 0x08, 0xF0, -0xDC, 0x40, 0x10, 0xF0, 0x5C, 0x42, 0x03, 0x18, 0x30, 0xAE, 0x5C, 0x48, 0xE0, 0x3E, 0x84, 0x80, -0x83, 0x93, 0x00, 0x48, 0x5E, 0xC6, 0x03, 0x9D, 0x2C, 0xEE, 0x5C, 0x48, 0x01, 0xBE, 0x84, 0x80, -0x8A, 0x51, 0x02, 0xA0, 0x8A, 0x51, 0x43, 0x45, 0x03, 0x59, 0x2C, 0xEE, 0x46, 0x08, 0xE1, 0x00, -0x5C, 0x48, 0xB0, 0x3E, 0x84, 0x80, 0x00, 0x48, 0xE2, 0x00, 0x5C, 0x48, 0x22, 0xFE, 0x84, 0x80, -0x00, 0x48, 0xE3, 0x40, 0x56, 0x48, 0xE4, 0x00, 0x48, 0xC8, 0xE5, 0x40, 0x47, 0x48, 0x52, 0xE7, -0x8A, 0x51, 0xE8, 0x00, 0x68, 0x4C, 0x03, 0x5C, 0x29, 0xEE, 0x83, 0x52, 0x03, 0x53, 0x6C, 0xD5, -0x2C, 0xEE, 0x83, 0x52, 0x03, 0x53, 0x6C, 0x91, 0x6C, 0xD9, 0x30, 0xAE, 0xDC, 0xCA, 0xF9, 0xAD, -0x6C, 0xD9, 0x08, 0x40, 0x48, 0xC8, 0x80, 0x7A, 0x77, 0xFE, 0x03, 0x5C, 0x3B, 0x6E, 0x60, 0xC8, -0x01, 0xBE, 0xDD, 0x80, 0x3D, 0x6E, 0xDD, 0xC1, 0xDD, 0x0A, 0xDB, 0xC1, 0x5D, 0x88, 0x5B, 0x82, +0xD7, 0x80, 0x07, 0x70, 0xD7, 0x05, 0x57, 0x88, 0xC5, 0x00, 0x08, 0xF0, 0x45, 0x02, 0x03, 0x18, +0xD5, 0x2C, 0x45, 0x08, 0x8A, 0x51, 0xF1, 0xE4, 0x8A, 0x51, 0x0E, 0x58, 0xD5, 0x2C, 0xC5, 0x8A, +0xE5, 0x2C, 0xED, 0x80, 0xE1, 0x00, 0x00, 0xB0, 0xE2, 0x00, 0x61, 0x08, 0x96, 0x00, 0x62, 0x08, +0x95, 0x00, 0x17, 0x94, 0x17, 0x50, 0x8E, 0xDC, 0x08, 0x40, 0x18, 0x14, 0x05, 0x30, 0xE8, 0x00, +0xE8, 0xCB, 0x00, 0x2D, 0x21, 0x30, 0x83, 0x52, 0x03, 0x53, 0xE1, 0x00, 0x61, 0x08, 0x95, 0x00, +0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xC6, 0x00, 0x22, 0x30, 0x83, 0x52, 0x03, 0x53, 0xE1, 0x00, +0x61, 0x08, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xC7, 0x40, 0x24, 0x30, 0x83, 0x52, +0x03, 0x53, 0xE1, 0x00, 0x61, 0x08, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xC9, 0x00, +0x20, 0xF0, 0x83, 0x52, 0x03, 0x53, 0xE1, 0x00, 0x61, 0x08, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, +0x0D, 0x08, 0xC4, 0xC0, 0x23, 0x70, 0x83, 0x52, 0x03, 0x53, 0xE1, 0x00, 0x61, 0x08, 0x95, 0x00, +0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xC8, 0xC0, 0x18, 0xD0, 0x55, 0xCB, 0x40, 0x6D, 0x48, 0xC8, +0xE8, 0x00, 0xE9, 0x81, 0xE8, 0x1B, 0xE9, 0xC3, 0x4A, 0x46, 0x69, 0x44, 0x03, 0x59, 0x4A, 0xED, +0x0A, 0x30, 0x49, 0x02, 0x03, 0x5C, 0x77, 0xAD, 0x44, 0xC8, 0x80, 0x7A, 0x78, 0x7E, 0x03, 0x5C, +0x6B, 0x6D, 0x77, 0xAD, 0x44, 0xC8, 0x80, 0x7A, 0x78, 0x7E, 0x03, 0x5C, 0xC4, 0xDB, 0x40, 0x6D, +0x44, 0xC8, 0x3A, 0x7E, 0x84, 0x80, 0x83, 0x93, 0x00, 0x48, 0x01, 0xBE, 0xE8, 0x00, 0x44, 0xC8, +0x3A, 0x7E, 0x84, 0x80, 0x68, 0x08, 0x80, 0x40, 0x44, 0xC8, 0x3A, 0x7E, 0x84, 0x80, 0x00, 0x48, +0xE1, 0x00, 0x44, 0xC8, 0x01, 0xBE, 0xE2, 0x00, 0x61, 0x08, 0x96, 0x00, 0x62, 0x08, 0x95, 0x00, +0x17, 0x94, 0x17, 0x50, 0x40, 0x6D, 0xC4, 0xDB, 0x77, 0xAD, 0x48, 0xC8, 0x80, 0x7A, 0x73, 0xBE, +0x03, 0x18, 0x77, 0xAD, 0x48, 0xC8, 0x80, 0x7A, 0x7B, 0xFE, 0x03, 0x18, 0x79, 0x6D, 0x6F, 0x55, +0x1A, 0xEE, 0x6F, 0x11, 0x48, 0xC8, 0x11, 0xFE, 0x84, 0x80, 0x8A, 0x51, 0x02, 0xA0, 0x8A, 0x51, +0xC7, 0xC5, 0x48, 0xC8, 0xE8, 0x00, 0x03, 0xD0, 0xE8, 0xCD, 0x03, 0xD0, 0xE8, 0xCD, 0x03, 0xD0, +0xE8, 0xCD, 0x44, 0xC8, 0x68, 0x87, 0xD8, 0x7E, 0xDE, 0x80, 0xA0, 0xFE, 0x84, 0x80, 0x83, 0xD7, +0x00, 0x48, 0xDC, 0x40, 0x3E, 0xF0, 0x83, 0x52, 0x03, 0x53, 0xE1, 0x00, 0x61, 0x08, 0x95, 0x00, +0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xC2, 0xC0, 0x3F, 0x30, 0x83, 0x52, 0x03, 0x53, 0xE1, 0x00, +0x61, 0x08, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xC3, 0x00, 0x6F, 0x11, 0xDC, 0x81, +0x08, 0xF0, 0x5C, 0x42, 0x03, 0x18, 0xDF, 0xED, 0x5C, 0x48, 0xE0, 0x3E, 0x84, 0x80, 0x83, 0x93, +0x00, 0x48, 0x5E, 0xC6, 0x03, 0x9D, 0xDB, 0xAD, 0x5C, 0x48, 0x01, 0xBE, 0x84, 0x80, 0x8A, 0x51, +0x02, 0xA0, 0x8A, 0x51, 0x42, 0x05, 0x03, 0x59, 0xDB, 0xAD, 0x46, 0x08, 0xE1, 0x00, 0x5C, 0x48, +0xB0, 0x3E, 0x84, 0x80, 0x00, 0x48, 0xE2, 0x00, 0x5C, 0x48, 0x22, 0xFE, 0x84, 0x80, 0x00, 0x48, +0xE3, 0x40, 0x56, 0x48, 0xE4, 0x00, 0x48, 0xC8, 0xE5, 0x40, 0x47, 0x48, 0x52, 0xE7, 0x8A, 0x51, +0xE8, 0x00, 0x68, 0x4C, 0x03, 0x5C, 0xD8, 0x2D, 0x83, 0x52, 0x03, 0x53, 0x6F, 0x55, 0xDB, 0xAD, +0x83, 0x52, 0x03, 0x53, 0x6F, 0x11, 0x6F, 0x59, 0xDF, 0xED, 0xDC, 0xCA, 0xA8, 0xED, 0x6F, 0x59, +0x1A, 0xEE, 0x08, 0xF0, 0xDC, 0x40, 0x10, 0xF0, 0x5C, 0x42, 0x03, 0x18, 0x1A, 0xEE, 0x5C, 0x48, +0xE0, 0x3E, 0x84, 0x80, 0x83, 0x93, 0x00, 0x48, 0x5E, 0xC6, 0x03, 0x9D, 0x16, 0xEE, 0x5C, 0x48, +0x01, 0xBE, 0x84, 0x80, 0x8A, 0x51, 0x02, 0xA0, 0x8A, 0x51, 0x43, 0x45, 0x03, 0x59, 0x16, 0xEE, +0x46, 0x08, 0xE1, 0x00, 0x5C, 0x48, 0xB0, 0x3E, 0x84, 0x80, 0x00, 0x48, 0xE2, 0x00, 0x5C, 0x48, +0x22, 0xFE, 0x84, 0x80, 0x00, 0x48, 0xE3, 0x40, 0x56, 0x48, 0xE4, 0x00, 0x48, 0xC8, 0xE5, 0x40, +0x47, 0x48, 0x52, 0xE7, 0x8A, 0x51, 0xE8, 0x00, 0x68, 0x4C, 0x03, 0x5C, 0x13, 0xEE, 0x83, 0x52, +0x03, 0x53, 0x6F, 0x55, 0x16, 0xEE, 0x83, 0x52, 0x03, 0x53, 0x6F, 0x11, 0x6F, 0x59, 0x1A, 0xEE, +0xDC, 0xCA, 0xE3, 0x6D, 0x6F, 0x59, 0x08, 0x40, 0x48, 0xC8, 0xFB, 0x3E, 0xE8, 0x00, 0x01, 0xF0, +0xE8, 0x8A, 0xE9, 0x40, 0x00, 0xB0, 0xEA, 0x40, 0x28, 0xAE, 0x03, 0xD0, 0xE9, 0x0D, 0xEA, 0x0D, +0xE8, 0xCB, 0x25, 0xEE, 0x60, 0xC8, 0x69, 0x85, 0xEB, 0x80, 0xEC, 0x81, 0xE0, 0xDB, 0xEC, 0xC3, +0x83, 0x52, 0x03, 0x53, 0x6A, 0x48, 0xEC, 0xC5, 0x6C, 0x48, 0x6B, 0x84, 0x03, 0x9D, 0x3B, 0x6E, +0xDD, 0xC1, 0xDD, 0x0A, 0x3D, 0x6E, 0x02, 0xF0, 0xDD, 0x80, 0xDB, 0xC1, 0x5D, 0x88, 0x5B, 0x82, 0x03, 0x18, 0x08, 0x40, 0x11, 0x30, 0xDC, 0x40, 0x3C, 0xB0, 0x83, 0x52, 0x03, 0x53, 0xE1, 0x00, 0x61, 0x08, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xD8, 0x00, 0x3D, 0xF0, 0x83, 0x52, 0x03, 0x53, 0xE1, 0x00, 0x61, 0x08, 0x95, 0x00, 0x97, 0xD4, 0x97, 0x90, 0x0D, 0x08, 0xD9, 0x40, @@ -252,9 +252,9 @@ static uint8_t arb_firmware[8192] = { 0x51, 0x02, 0x03, 0x18, 0x89, 0x2F, 0xDA, 0x81, 0xDA, 0xCA, 0x92, 0x2F, 0xCF, 0xC1, 0x63, 0x48, 0x61, 0x02, 0x03, 0x5C, 0xDE, 0xEF, 0x63, 0x48, 0x61, 0x02, 0xE0, 0x2F, 0x61, 0x08, 0x63, 0x42, 0xCB, 0x40, 0xCF, 0xC8, 0x03, 0x9D, 0x00, 0xF4, 0x64, 0x08, 0x4B, 0x42, 0x03, 0x5C, 0x01, 0x34, -0x00, 0xF4, 0xEC, 0x81, 0x83, 0x93, 0x22, 0x30, 0x84, 0x80, 0x61, 0x70, 0x8A, 0x51, 0xF4, 0x23, -0x8A, 0x51, 0xA0, 0x30, 0x84, 0x80, 0xF0, 0xB0, 0x8A, 0x51, 0xF4, 0x23, 0x8A, 0x51, 0x83, 0xD7, -0xA0, 0x30, 0x84, 0x80, 0xE0, 0x70, 0x8A, 0x51, 0xF4, 0x23, 0x83, 0x01, 0x8A, 0x51, 0x4A, 0xAC, +0x00, 0xF4, 0xEF, 0x01, 0x83, 0x93, 0x22, 0x30, 0x84, 0x80, 0x61, 0x70, 0x8A, 0x51, 0x07, 0x64, +0x8A, 0x51, 0xA0, 0x30, 0x84, 0x80, 0xF0, 0xB0, 0x8A, 0x51, 0x07, 0x64, 0x8A, 0x51, 0x83, 0xD7, +0xA0, 0x30, 0x84, 0x80, 0xE0, 0x70, 0x8A, 0x51, 0x07, 0x64, 0x83, 0x01, 0x8A, 0x51, 0x5D, 0x2C, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, diff --git a/libloragw/src/loragw_ad5338r.c b/libloragw/src/loragw_ad5338r.c new file mode 100644 index 00000000..ea085130 --- /dev/null +++ b/libloragw/src/loragw_ad5338r.c @@ -0,0 +1,93 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2020 Semtech + +Description: + Basic driver for Analog AD5338R DAC. + +License: Revised BSD License, see LICENSE.TXT file include in the project +*/ + +#include /* C99 types */ +#include /* bool type */ +#include /* printf fprintf */ + +#include "loragw_i2c.h" +#include "loragw_ad5338r.h" + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE MACROS ------------------------------------------------------- */ + +#if DEBUG_I2C == 1 + #define DEBUG_MSG(str) fprintf(stdout, str) + #define DEBUG_PRINTF(fmt, args...) fprintf(stdout,"%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_I2C_ERROR;} +#else + #define DEBUG_MSG(str) + #define DEBUG_PRINTF(fmt, args...) + #define CHECK_NULL(a) if(a==NULL){return LGW_I2C_ERROR;} +#endif + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC FUNCTIONS DEFINITION ------------------------------------------ */ + +int ad5338r_configure(int i2c_fd, uint8_t i2c_addr) { + int err; + uint8_t cmd_soft_reset[AD5338R_CMD_SIZE] = {0x69, 0x00, 0x00}; + uint8_t cmd_power_up_dn[AD5338R_CMD_SIZE] = {0x40, 0x00, 0x00}; + uint8_t cmd_internal_ref[AD5338R_CMD_SIZE] = {0x70, 0x00, 0x00}; + + /* Check Input Params */ + if (i2c_fd <= 0) { + printf("ERROR: invalid I2C file descriptor\n"); + return LGW_I2C_ERROR; + } + + DEBUG_PRINTF("INFO: configuring AD5338R DAC on 0x%02X...\n", i2c_addr); + + /* Sofwtare reset of DAC A & B */ + /* TODO: LSB data bytes seems not acknoledged by AD5338R, leading to an error if sent. + Only send MSB data byte */ + err = i2c_linuxdev_write(i2c_fd, i2c_addr, cmd_soft_reset[0], cmd_soft_reset[1]); + if (err != 0) { + printf("ERROR: AD5338R software reset failed\n"); + return LGW_I2C_ERROR; + } + + /* Normal operation */ + err = ad5338r_write(i2c_fd, i2c_addr, cmd_power_up_dn); + if (err != 0) { + printf("ERROR: AD5338R failed to set to normal operation\n"); + return LGW_I2C_ERROR; + } + + /* Internal reference ON */ + err = ad5338r_write(i2c_fd, i2c_addr, cmd_internal_ref); + if (err != 0) { + printf("ERROR: AD5338R failed to set internal reference ON\n"); + return LGW_I2C_ERROR; + } + + printf("INFO: AD5338R is configured\n"); + + return LGW_I2C_SUCCESS; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int ad5338r_write(int i2c_fd, uint8_t i2c_addr, uint8_t buf[static AD5338R_CMD_SIZE]) { + int err; + + /* Write AD5338R command buffer */ + err = i2c_linuxdev_write_buffer(i2c_fd, i2c_addr, buf, AD5338R_CMD_SIZE); + if (err != 0) { + printf("ERROR: failed to write AD5338R command\n"); + return LGW_I2C_ERROR; + } + + return LGW_I2C_SUCCESS; +} diff --git a/libloragw/src/loragw_aux.c b/libloragw/src/loragw_aux.c index 76dab0d4..44221232 100644 --- a/libloragw/src/loragw_aux.c +++ b/libloragw/src/loragw_aux.c @@ -25,13 +25,17 @@ License: Revised BSD License, see LICENSE.TXT file include in the project #include /* printf fprintf */ #include /* clock_nanosleep */ +#include /* pow, ceil */ + +#include "loragw_aux.h" +#include "loragw_hal.h" /* -------------------------------------------------------------------------- */ /* --- 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(stdout, str) + #define DEBUG_PRINTF(fmt, args...) fprintf(stdout,"%s:%d: "fmt, __FUNCTION__, __LINE__, args) #else #define DEBUG_MSG(str) #define DEBUG_PRINTF(fmt, args...) @@ -40,13 +44,38 @@ License: Revised BSD License, see LICENSE.TXT file include in the project /* -------------------------------------------------------------------------- */ /* --- PUBLIC FUNCTIONS DEFINITION ------------------------------------------ */ -/* This implementation is POSIX-pecific and require a fix to be compatible with C99 */ -void wait_ms(unsigned long a) { +void wait_us(unsigned long delay_us) { + struct timespec dly; + struct timespec rem; + + dly.tv_sec = delay_us / 1000000; + dly.tv_nsec = (delay_us % 1000000) * 1000; + + DEBUG_PRINTF("NOTE dly: %ld sec %ld ns\n", dly.tv_sec, dly.tv_nsec); + + while ((dly.tv_sec > 0) || (dly.tv_nsec > 1000)) { + /* + rem is set ONLY if clock_nanosleep is interrupted (eg. by a signal). + Must be zeroed each time or will get into an infinite loop after an IT. + */ + rem.tv_sec = 0; + rem.tv_nsec = 0; + clock_nanosleep(CLOCK_MONOTONIC, 0, &dly, &rem); + DEBUG_PRINTF("NOTE remain: %ld sec %ld ns\n", rem.tv_sec, rem.tv_nsec); + dly = rem; + } + + return; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +void wait_ms(unsigned long delay_ms) { struct timespec dly; struct timespec rem; - dly.tv_sec = a / 1000; - dly.tv_nsec = ((long)a % 1000) * 1000000; + dly.tv_sec = delay_ms / 1000; + dly.tv_nsec = ((long)delay_ms % 1000) * 1000000; DEBUG_PRINTF("NOTE dly: %ld sec %ld ns\n", dly.tv_sec, dly.tv_nsec); @@ -57,4 +86,133 @@ void wait_ms(unsigned long a) { return; } +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +uint32_t lora_packet_time_on_air(const uint8_t bw, const uint8_t sf, const uint8_t cr, const uint16_t n_symbol_preamble, + const bool no_header, const bool no_crc, const uint8_t size, + double * out_nb_symbols, uint32_t * out_nb_symbols_payload, uint16_t * out_t_symbol_us) { + uint8_t H, DE, n_bit_crc; + uint8_t bw_pow; + uint16_t t_symbol_us; + double n_symbol; + uint32_t toa_us, n_symbol_payload; + + /* Check input parameters */ + if (IS_LORA_DR(sf) == false) { + printf("ERROR: wrong datarate - %s\n", __FUNCTION__); + return 0; + } + if (IS_LORA_BW(bw) == false) { + printf("ERROR: wrong bandwidth - %s\n", __FUNCTION__); + return 0; + } + if (IS_LORA_CR(cr) == false) { + printf("ERROR: wrong coding rate - %s\n", __FUNCTION__); + return 0; + } + + /* Get bandwidth 125KHz divider*/ + switch (bw) { + case BW_125KHZ: + bw_pow = 1; + break; + case BW_250KHZ: + bw_pow = 2; + break; + case BW_500KHZ: + bw_pow = 4; + break; + default: + printf("ERROR: unsupported bandwith 0x%02X (%s)\n", bw, __FUNCTION__); + return 0; + } + + /* Duration of 1 symbol */ + t_symbol_us = (1 << sf) * 8 / bw_pow; /* 2^SF / BW , in microseconds */ + + /* Packet parameters */ + H = (no_header == false) ? 1 : 0; /* header is always enabled, except for beacons */ + DE = (sf >= 11) ? 1 : 0; /* Low datarate optimization enabled for SF11 and SF12 */ + n_bit_crc = (no_crc == false) ? 16 : 0; + + /* Number of symbols in the payload */ + n_symbol_payload = ceil( MAX( (double)( 8 * size + n_bit_crc - 4*sf + ((sf >= 7) ? 8 : 0) + 20*H ), 0.0) / + (double)( 4 * (sf - 2*DE)) ) + * ( cr + 4 ); /* Explicitely cast to double to keep precision of the division */ + + /* number of symbols in packet */ + n_symbol = (double)n_symbol_preamble + ((sf >= 7) ? 4.25 : 6.25) + 8.0 + (double)n_symbol_payload; + + /* Duration of packet in microseconds */ + toa_us = (uint32_t)( (double)n_symbol * (double)t_symbol_us ); + + DEBUG_PRINTF("INFO: LoRa packet ToA: %u us (n_symbol:%f, t_symbol_us:%u)\n", toa_us, n_symbol, t_symbol_us); + + /* Return details if required */ + if (out_nb_symbols != NULL) { + *out_nb_symbols = n_symbol; + } + if (out_nb_symbols_payload != NULL) { + *out_nb_symbols_payload = n_symbol_payload; + } + if (out_t_symbol_us != NULL) { + *out_t_symbol_us = t_symbol_us; + } + + return toa_us; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-parameter" +void _meas_time_start(struct timeval *tm) +{ +#if (DEBUG_PERF > 0) && (DEBUG_PERF <= 5) + gettimeofday(tm, NULL); +#endif +} + +void _meas_time_stop(int debug_level, struct timeval start_time, const char *str) +{ +#if (DEBUG_PERF > 0) && (DEBUG_PERF <= 5) + struct timeval tm; + double time_ms; + char *indent[] = { "", " ..", " ....", " ......", " ........" }; + + gettimeofday(&tm, NULL); + + time_ms = (tm.tv_sec - start_time.tv_sec) * 1000.0 + (tm.tv_usec - start_time.tv_usec) / 1000.0; + if ((debug_level > 0) && (debug_level <= DEBUG_PERF)) { + printf("PERF:%s %s %f ms\n", indent[debug_level - 1], str, time_ms); + } +#endif +} +#pragma GCC diagnostic pop + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +void timeout_start(struct timeval * start) { + gettimeofday(start, NULL); +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int timeout_check(struct timeval start, uint32_t timeout_ms) { + struct timeval tm; + struct timeval diff; + uint32_t ms; + + gettimeofday(&tm, NULL); + + TIMER_SUB(&tm, &start, &diff); + + ms = diff.tv_sec * 1000 + diff.tv_usec / 1000; + if (ms >= timeout_ms) { + return -1; + } else { + return 0; + } +} + /* --- EOF ------------------------------------------------------------------ */ diff --git a/libloragw/src/loragw_cal.c b/libloragw/src/loragw_cal.c index bb94b7d8..c0775258 100644 --- a/libloragw/src/loragw_cal.c +++ b/libloragw/src/loragw_cal.c @@ -18,15 +18,7 @@ License: Revised BSD License, see LICENSE.TXT file include in the project #include /* C99 types */ #include /* printf fprintf */ -#include /* malloc free */ -#include /* lseek, close */ -#include /* open */ -#include /* memset */ -#include -#include - -#include -#include +#include /* log10 */ #include "loragw_reg.h" #include "loragw_aux.h" @@ -45,8 +37,8 @@ License: Revised BSD License, see LICENSE.TXT file include in the project #define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) #if DEBUG_CAL == 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(stdout, str) + #define DEBUG_PRINTF(fmt, args...) fprintf(stdout,"%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) @@ -62,7 +54,7 @@ License: Revised BSD License, see LICENSE.TXT file include in the project #define CAL_TX_CORR_DURATION 0 /* 0:1ms, 1:2ms, 2:4ms, 3:8ms */ /* -------------------------------------------------------------------------- */ -/* --- INTERNAL SHARED VARIABLES -------------------------------------------- */ +/* --- PRIVATE VARIABLES -------------------------------------------- */ /* Record Rx IQ mismatch corrections from calibration */ static int8_t rf_rx_image_amp[LGW_RF_CHAIN_NB] = {0, 0}; @@ -293,39 +285,39 @@ int sx125x_cal_rx_image(uint8_t rf_chain, uint32_t freq_hz, bool use_loopback, u DEBUG_PRINTF("ERROR: UNEXPECTED VALUE %d FOR RADIO TYPE\n", radio_type); return LGW_HAL_ERROR; } - lgw_sx125x_reg_w(SX125x_REG_FRF_RX_MSB, 0xFF & rx_freq_int, rx); - lgw_sx125x_reg_w(SX125x_REG_FRF_RX_MID, 0xFF & (rx_freq_frac >> 8), rx); - lgw_sx125x_reg_w(SX125x_REG_FRF_RX_LSB, 0xFF & rx_freq_frac, rx); - lgw_sx125x_reg_w(SX125x_REG_FRF_TX_MSB, 0xFF & tx_freq_int, tx); - lgw_sx125x_reg_w(SX125x_REG_FRF_TX_MID, 0xFF & (tx_freq_frac >> 8), tx); - lgw_sx125x_reg_w(SX125x_REG_FRF_TX_LSB, 0xFF & tx_freq_frac, tx); + sx125x_reg_w(SX125x_REG_FRF_RX_MSB, 0xFF & rx_freq_int, rx); + sx125x_reg_w(SX125x_REG_FRF_RX_MID, 0xFF & (rx_freq_frac >> 8), rx); + sx125x_reg_w(SX125x_REG_FRF_RX_LSB, 0xFF & rx_freq_frac, rx); + sx125x_reg_w(SX125x_REG_FRF_TX_MSB, 0xFF & tx_freq_int, tx); + sx125x_reg_w(SX125x_REG_FRF_TX_MID, 0xFF & (tx_freq_frac >> 8), tx); + sx125x_reg_w(SX125x_REG_FRF_TX_LSB, 0xFF & tx_freq_frac, tx); /* Radio settings for calibration */ //sx125x_reg_w(SX125x_RX_ANA_GAIN__LNA_ZIN, 1, rx); /* Default: 1 */ //sx125x_reg_w(SX125x_RX_ANA_GAIN__BB_GAIN, 15, rx); /* Default: 15 */ //sx125x_reg_w(SX125x_RX_ANA_GAIN__LNA_GAIN, 1, rx); /* Default: 1 */ - lgw_sx125x_reg_w(SX125x_REG_RX_BW__BB_BW, 0, rx); - lgw_sx125x_reg_w(SX125x_REG_RX_BW__ADC_TRIM, 6, rx); + sx125x_reg_w(SX125x_REG_RX_BW__BB_BW, 0, rx); + sx125x_reg_w(SX125x_REG_RX_BW__ADC_TRIM, 6, rx); //sx125x_reg_w(SX125x_RX_BW__ADC_BW, 7, rx); /* Default: 7 */ - lgw_sx125x_reg_w(SX125x_REG_RX_PLL_BW__PLL_BW, 0, rx); - lgw_sx125x_reg_w(SX125x_REG_TX_BW__PLL_BW, 0, tx); + sx125x_reg_w(SX125x_REG_RX_PLL_BW__PLL_BW, 0, rx); + sx125x_reg_w(SX125x_REG_TX_BW__PLL_BW, 0, tx); //sx125x_reg_w(SX125x_TX_BW__ANA_BW, 0, tx); /* Default: 0 */ - lgw_sx125x_reg_w(SX125x_REG_TX_DAC_BW, 5, tx); + sx125x_reg_w(SX125x_REG_TX_DAC_BW, 5, tx); //sx125x_reg_w(SX125x_CLK_SELECT__DAC_CLK_SELECT, 0, tx); /* Use internal clock, in case no Tx connection from SX1302, Default: 0 */ if (use_loopback == true) { - lgw_sx125x_reg_w(SX125x_REG_TX_GAIN__DAC_GAIN, 3, tx); - lgw_sx125x_reg_w(SX125x_REG_TX_GAIN__MIX_GAIN, 10, tx); //8 - lgw_sx125x_reg_w(SX125x_REG_CLK_SELECT__RF_LOOPBACK_EN, 1, tx); - lgw_sx125x_reg_w(SX125x_REG_MODE, 15, tx); + sx125x_reg_w(SX125x_REG_TX_GAIN__DAC_GAIN, 3, tx); + sx125x_reg_w(SX125x_REG_TX_GAIN__MIX_GAIN, 10, tx); //8 + sx125x_reg_w(SX125x_REG_CLK_SELECT__RF_LOOPBACK_EN, 1, tx); + sx125x_reg_w(SX125x_REG_MODE, 15, tx); } else { - lgw_sx125x_reg_w(SX125x_REG_TX_GAIN__DAC_GAIN, 3, tx); - lgw_sx125x_reg_w(SX125x_REG_TX_GAIN__MIX_GAIN, 15, tx); - lgw_sx125x_reg_w(SX125x_REG_MODE, 3, rx); - lgw_sx125x_reg_w(SX125x_REG_MODE, 13, tx); + sx125x_reg_w(SX125x_REG_TX_GAIN__DAC_GAIN, 3, tx); + sx125x_reg_w(SX125x_REG_TX_GAIN__MIX_GAIN, 15, tx); + sx125x_reg_w(SX125x_REG_MODE, 3, rx); + sx125x_reg_w(SX125x_REG_MODE, 13, tx); } wait_ms(10); - lgw_sx125x_reg_r(SX125x_REG_MODE_STATUS__RX_PLL_LOCKED, &rx_pll_locked, rx); - lgw_sx125x_reg_r(SX125x_REG_MODE_STATUS__TX_PLL_LOCKED, &tx_pll_locked, tx); + sx125x_reg_r(SX125x_REG_MODE_STATUS__RX_PLL_LOCKED, &rx_pll_locked, rx); + sx125x_reg_r(SX125x_REG_MODE_STATUS__TX_PLL_LOCKED, &tx_pll_locked, tx); if ((rx_pll_locked == 0) || (tx_pll_locked == 0)) { DEBUG_MSG("ERROR: PLL failed to lock\n"); return LGW_HAL_ERROR; @@ -441,32 +433,32 @@ int sx125x_cal_tx_dc_offset(uint8_t rf_chain, uint32_t freq_hz, uint8_t dac_gain DEBUG_PRINTF("ERROR: UNEXPECTED VALUE %d FOR RADIO TYPE\n", radio_type); return LGW_HAL_ERROR; } - lgw_sx125x_reg_w(SX125x_REG_FRF_RX_MSB, 0xFF & rx_freq_int, rf_chain); - lgw_sx125x_reg_w(SX125x_REG_FRF_RX_MID, 0xFF & (rx_freq_frac >> 8), rf_chain); - lgw_sx125x_reg_w(SX125x_REG_FRF_RX_LSB, 0xFF & rx_freq_frac, rf_chain); - lgw_sx125x_reg_w(SX125x_REG_FRF_TX_MSB, 0xFF & tx_freq_int, rf_chain); - lgw_sx125x_reg_w(SX125x_REG_FRF_TX_MID, 0xFF & (tx_freq_frac >> 8), rf_chain); - lgw_sx125x_reg_w(SX125x_REG_FRF_TX_LSB, 0xFF & tx_freq_frac, rf_chain); + sx125x_reg_w(SX125x_REG_FRF_RX_MSB, 0xFF & rx_freq_int, rf_chain); + sx125x_reg_w(SX125x_REG_FRF_RX_MID, 0xFF & (rx_freq_frac >> 8), rf_chain); + sx125x_reg_w(SX125x_REG_FRF_RX_LSB, 0xFF & rx_freq_frac, rf_chain); + sx125x_reg_w(SX125x_REG_FRF_TX_MSB, 0xFF & tx_freq_int, rf_chain); + sx125x_reg_w(SX125x_REG_FRF_TX_MID, 0xFF & (tx_freq_frac >> 8), rf_chain); + sx125x_reg_w(SX125x_REG_FRF_TX_LSB, 0xFF & tx_freq_frac, rf_chain); /* Radio settings for calibration */ - //lgw_sx125x_reg_w(SX125x_RX_ANA_GAIN__LNA_ZIN, 1, rf_chain); /* Default: 1 */ - //lgw_sx125x_reg_w(SX125x_RX_ANA_GAIN__BB_GAIN, 15, rf_chain); /* Default: 15 */ - //lgw_sx125x_reg_w(SX125x_RX_ANA_GAIN__LNA_GAIN, 1, rf_chain); /* Default: 1 */ - lgw_sx125x_reg_w(SX125x_REG_RX_BW__BB_BW, 0, rf_chain); - lgw_sx125x_reg_w(SX125x_REG_RX_BW__ADC_TRIM, 6, rf_chain); - //lgw_sx125x_reg_w(SX125x_RX_BW__ADC_BW, 7, rf_chain); /* Default: 7 */ - lgw_sx125x_reg_w(SX125x_REG_RX_PLL_BW__PLL_BW, 0, rf_chain); - lgw_sx125x_reg_w(SX125x_REG_TX_BW__PLL_BW, 0, rf_chain); - //lgw_sx125x_reg_w(SX125x_TX_BW__ANA_BW, 0, rf_chain); /* Default: 0 */ - lgw_sx125x_reg_w(SX125x_REG_TX_DAC_BW, 5, rf_chain); - lgw_sx125x_reg_w(SX125x_REG_CLK_SELECT__DAC_CLK_SELECT, 1, rf_chain); /* Use external clock from SX1302 */ - lgw_sx125x_reg_w(SX125x_REG_TX_GAIN__DAC_GAIN, dac_gain, rf_chain); - lgw_sx125x_reg_w(SX125x_REG_TX_GAIN__MIX_GAIN, mix_gain, rf_chain); - lgw_sx125x_reg_w(SX125x_REG_CLK_SELECT__RF_LOOPBACK_EN, 1, rf_chain); - lgw_sx125x_reg_w(SX125x_REG_MODE, 15, rf_chain); + //sx125x_reg_w(SX125x_RX_ANA_GAIN__LNA_ZIN, 1, rf_chain); /* Default: 1 */ + //sx125x_reg_w(SX125x_RX_ANA_GAIN__BB_GAIN, 15, rf_chain); /* Default: 15 */ + //sx125x_reg_w(SX125x_RX_ANA_GAIN__LNA_GAIN, 1, rf_chain); /* Default: 1 */ + sx125x_reg_w(SX125x_REG_RX_BW__BB_BW, 0, rf_chain); + sx125x_reg_w(SX125x_REG_RX_BW__ADC_TRIM, 6, rf_chain); + //sx125x_reg_w(SX125x_RX_BW__ADC_BW, 7, rf_chain); /* Default: 7 */ + sx125x_reg_w(SX125x_REG_RX_PLL_BW__PLL_BW, 0, rf_chain); + sx125x_reg_w(SX125x_REG_TX_BW__PLL_BW, 0, rf_chain); + //sx125x_reg_w(SX125x_TX_BW__ANA_BW, 0, rf_chain); /* Default: 0 */ + sx125x_reg_w(SX125x_REG_TX_DAC_BW, 5, rf_chain); + sx125x_reg_w(SX125x_REG_CLK_SELECT__DAC_CLK_SELECT, 1, rf_chain); /* Use external clock from SX1302 */ + sx125x_reg_w(SX125x_REG_TX_GAIN__DAC_GAIN, dac_gain, rf_chain); + sx125x_reg_w(SX125x_REG_TX_GAIN__MIX_GAIN, mix_gain, rf_chain); + sx125x_reg_w(SX125x_REG_CLK_SELECT__RF_LOOPBACK_EN, 1, rf_chain); + sx125x_reg_w(SX125x_REG_MODE, 15, rf_chain); wait_ms(1); - lgw_sx125x_reg_r(SX125x_REG_MODE_STATUS__RX_PLL_LOCKED, &rx_pll_locked, rf_chain); - lgw_sx125x_reg_r(SX125x_REG_MODE_STATUS__TX_PLL_LOCKED, &tx_pll_locked, rf_chain); + sx125x_reg_r(SX125x_REG_MODE_STATUS__RX_PLL_LOCKED, &rx_pll_locked, rf_chain); + sx125x_reg_r(SX125x_REG_MODE_STATUS__TX_PLL_LOCKED, &tx_pll_locked, rf_chain); if ((rx_pll_locked == 0) || (tx_pll_locked == 0)) { DEBUG_MSG("ERROR: PLL failed to lock\n"); return LGW_HAL_ERROR; @@ -833,6 +825,7 @@ void agc_cal_tx_dc_offset(uint8_t rf_chain, signed char freq, char amp_hal, char do { lgw_reg_r(SX1302_REG_RADIO_FE_SIG_ANA_CFG_VALID, &val); wait_ms(1); + /* TODO: addtimeout */ } while (val == 0); lgw_reg_r(SX1302_REG_RADIO_FE_SIG_ANA_ABS_LSB_CORR_ABS_OUT, &abs_lsb); diff --git a/libloragw/src/loragw_com.c b/libloragw/src/loragw_com.c new file mode 100644 index 00000000..d6a0894b --- /dev/null +++ b/libloragw/src/loragw_com.c @@ -0,0 +1,383 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2020 Semtech + +Description: + Functions to abstract the communication interface used to communicate with + the concentrator. + Single-byte read/write and burst read/write. + +License: Revised BSD License, see LICENSE.TXT file include in the project +*/ + + +/* -------------------------------------------------------------------------- */ +/* --- DEPENDANCIES --------------------------------------------------------- */ + +#include /* C99 types */ +#include /* printf fprintf */ + +#include "loragw_com.h" +#include "loragw_usb.h" +#include "loragw_spi.h" +#include "loragw_aux.h" + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE MACROS ------------------------------------------------------- */ + +#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) +#if DEBUG_COM == 1 + #define DEBUG_MSG(str) fprintf(stdout, str) + #define DEBUG_PRINTF(fmt, args...) fprintf(stdout,"%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_COM_ERROR;} +#else + #define DEBUG_MSG(str) + #define DEBUG_PRINTF(fmt, args...) + #define CHECK_NULL(a) if(a==NULL){return LGW_COM_ERROR;} +#endif + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE CONSTANTS ---------------------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE VARIABLES ---------------------------------------------------- */ + +/** +@brief The current communication type in use (SPI, USB) +*/ +static lgw_com_type_t _lgw_com_type = LGW_COM_UNKNOWN; + +/** +@brief A generic pointer to the COM device (file descriptor) +*/ +static void* _lgw_com_target = NULL; + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC FUNCTIONS DEFINITION ------------------------------------------ */ + +int lgw_com_open(lgw_com_type_t com_type, const char * com_path) { + int com_stat; + + /* Check input parameters */ + CHECK_NULL(com_path); + if ((com_type != LGW_COM_SPI) && (com_type != LGW_COM_USB)) { + DEBUG_MSG("ERROR: COMMUNICATION INTERFACE TYPE IS NOT SUPPORTED\n"); + return LGW_COM_ERROR; + } + + if (_lgw_com_target != NULL) { + DEBUG_MSG("WARNING: CONCENTRATOR WAS ALREADY CONNECTED\n"); + lgw_com_close(); + } + + /* set current com type */ + _lgw_com_type = com_type; + + switch (com_type) { + case LGW_COM_SPI: + printf("Opening SPI communication interface\n"); + com_stat = lgw_spi_open(com_path, &_lgw_com_target); + break; + case LGW_COM_USB: + printf("Opening USB communication interface\n"); + com_stat = lgw_usb_open(com_path, &_lgw_com_target); + break; + default: + com_stat = LGW_COM_ERROR; + break; + } + + return com_stat; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +/* SPI release */ +int lgw_com_close(void) { + int com_stat; + + if (_lgw_com_target == NULL) { + printf("ERROR: concentrator is not connected\n"); + return -1; + } + + switch (_lgw_com_type) { + case LGW_COM_SPI: + printf("Closing SPI communication interface\n"); + com_stat = lgw_spi_close(_lgw_com_target); + break; + case LGW_COM_USB: + printf("Closing USB communication interface\n"); + com_stat = lgw_usb_close(_lgw_com_target); + break; + default: + printf("ERROR(%s:%d): wrong communication type (SHOULD NOT HAPPEN)\n", __FUNCTION__, __LINE__); + com_stat = LGW_COM_ERROR; + break; + } + + _lgw_com_target = NULL; + + return com_stat; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +/* Simple write */ +int lgw_com_w(uint8_t spi_mux_target, uint16_t address, uint8_t data) { + int com_stat; + /* performances variables */ + struct timeval tm; + + /* Record function start time */ + _meas_time_start(&tm); + + /* Check input parameters */ + CHECK_NULL(_lgw_com_target); + + switch (_lgw_com_type) { + case LGW_COM_SPI: + com_stat = lgw_spi_w(_lgw_com_target, spi_mux_target, address, data); + break; + case LGW_COM_USB: + com_stat = lgw_usb_w(_lgw_com_target, spi_mux_target, address, data); + break; + default: + printf("ERROR(%s:%d): wrong communication type (SHOULD NOT HAPPEN)\n", __FUNCTION__, __LINE__); + com_stat = LGW_COM_ERROR; + break; + } + + /* Compute time spent in this function */ + _meas_time_stop(5, tm, __FUNCTION__); + + return com_stat; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +/* Simple read */ +int lgw_com_r(uint8_t spi_mux_target, uint16_t address, uint8_t *data) { + int com_stat; + /* performances variables */ + struct timeval tm; + + /* Record function start time */ + _meas_time_start(&tm); + + /* Check input parameters */ + CHECK_NULL(_lgw_com_target); + CHECK_NULL(data); + + switch (_lgw_com_type) { + case LGW_COM_SPI: + com_stat = lgw_spi_r(_lgw_com_target, spi_mux_target, address, data); + break; + case LGW_COM_USB: + com_stat = lgw_usb_r(_lgw_com_target, spi_mux_target, address, data); + break; + default: + printf("ERROR(%s:%d): wrong communication type (SHOULD NOT HAPPEN)\n", __FUNCTION__, __LINE__); + com_stat = LGW_COM_ERROR; + break; + } + + /* Compute time spent in this function */ + _meas_time_stop(5, tm, __FUNCTION__); + + return com_stat; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int lgw_com_rmw(uint8_t spi_mux_target, uint16_t address, uint8_t offs, uint8_t leng, uint8_t data) { + int com_stat; + /* performances variables */ + struct timeval tm; + + /* Record function start time */ + _meas_time_start(&tm); + + /* Check input parameters */ + CHECK_NULL(_lgw_com_target); + + switch (_lgw_com_type) { + case LGW_COM_SPI: + com_stat = lgw_spi_rmw(_lgw_com_target, spi_mux_target, address, offs, leng, data); + break; + case LGW_COM_USB: + com_stat = lgw_usb_rmw(_lgw_com_target, address, offs, leng, data); + break; + default: + printf("ERROR(%s:%d): wrong communication type (SHOULD NOT HAPPEN)\n", __FUNCTION__, __LINE__); + com_stat = LGW_COM_ERROR; + break; + } + + /* Compute time spent in this function */ + _meas_time_stop(5, tm, __FUNCTION__); + + return com_stat; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +/* Burst (multiple-byte) write */ +int lgw_com_wb(uint8_t spi_mux_target, uint16_t address, const uint8_t *data, uint16_t size) { + int com_stat; + /* performances variables */ + struct timeval tm; + + /* Record function start time */ + _meas_time_start(&tm); + + /* Check input parameters */ + CHECK_NULL(_lgw_com_target); + CHECK_NULL(data); + + switch (_lgw_com_type) { + case LGW_COM_SPI: + com_stat = lgw_spi_wb(_lgw_com_target, spi_mux_target, address, data, size); + break; + case LGW_COM_USB: + com_stat = lgw_usb_wb(_lgw_com_target, spi_mux_target, address, data, size); + break; + default: + printf("ERROR(%s:%d): wrong communication type (SHOULD NOT HAPPEN)\n", __FUNCTION__, __LINE__); + com_stat = LGW_COM_ERROR; + break; + } + + /* Compute time spent in this function */ + _meas_time_stop(5, tm, __FUNCTION__); + + return com_stat; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +/* Burst (multiple-byte) read */ +int lgw_com_rb(uint8_t spi_mux_target, uint16_t address, uint8_t *data, uint16_t size) { + int com_stat; + /* performances variables */ + struct timeval tm; + + /* Record function start time */ + _meas_time_start(&tm); + + /* Check input parameters */ + CHECK_NULL(_lgw_com_target); + CHECK_NULL(data); + + switch (_lgw_com_type) { + case LGW_COM_SPI: + com_stat = lgw_spi_rb(_lgw_com_target, spi_mux_target, address, data, size); + break; + case LGW_COM_USB: + com_stat = lgw_usb_rb(_lgw_com_target, spi_mux_target, address, data, size); + break; + default: + printf("ERROR(%s:%d): wrong communication type (SHOULD NOT HAPPEN)\n", __FUNCTION__, __LINE__); + com_stat = LGW_COM_ERROR; + break; + } + + /* Compute time spent in this function */ + _meas_time_stop(5, tm, __FUNCTION__); + + return com_stat; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int lgw_com_set_write_mode(lgw_com_write_mode_t write_mode) { + int com_stat = LGW_COM_SUCCESS; + + switch (_lgw_com_type) { + case LGW_COM_SPI: + /* Do nothing: only single mode is supported on SPI */ + break; + case LGW_COM_USB: + com_stat = lgw_usb_set_write_mode(write_mode); + break; + default: + printf("ERROR(%s:%d): wrong communication type (SHOULD NOT HAPPEN)\n", __FUNCTION__, __LINE__); + com_stat = LGW_COM_ERROR; + break; + } + + return com_stat; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int lgw_com_flush(void) { + int com_stat = LGW_COM_SUCCESS; + + switch (_lgw_com_type) { + case LGW_COM_SPI: + /* Do nothing: only single mode is supported on SPI */ + break; + case LGW_COM_USB: + com_stat = lgw_usb_flush(_lgw_com_target); + break; + default: + printf("ERROR(%s:%d): wrong communication type (SHOULD NOT HAPPEN)\n", __FUNCTION__, __LINE__); + com_stat = LGW_COM_ERROR; + break; + } + + return com_stat; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +uint16_t lgw_com_chunk_size(void) { + switch (_lgw_com_type) { + case LGW_COM_SPI: + return lgw_spi_chunk_size(); + case LGW_COM_USB: + return lgw_usb_chunk_size(); + break; + default: + printf("ERROR(%s:%d): wrong communication type (SHOULD NOT HAPPEN)\n", __FUNCTION__, __LINE__); + return 0; + } +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int lgw_com_get_temperature(float * temperature) { + /* Check input parameters */ + CHECK_NULL(_lgw_com_target); + CHECK_NULL(temperature); + + switch (_lgw_com_type) { + case LGW_COM_SPI: + printf("ERROR(%s:%d): not supported for SPI com\n", __FUNCTION__, __LINE__); + return -1; + case LGW_COM_USB: + return lgw_usb_get_temperature(_lgw_com_target, temperature); + default: + printf("ERROR(%s:%d): wrong communication type (SHOULD NOT HAPPEN)\n", __FUNCTION__, __LINE__); + return LGW_COM_ERROR; + } +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +void* lgw_com_target(void) { + return _lgw_com_target; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +lgw_com_type_t lgw_com_type(void) { + return _lgw_com_type; +} + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/libloragw/src/loragw_debug.c b/libloragw/src/loragw_debug.c index 2415d804..9a81a085 100644 --- a/libloragw/src/loragw_debug.c +++ b/libloragw/src/loragw_debug.c @@ -60,24 +60,6 @@ void dbg_init_random(void) { /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ -void dbg_init_gpio(void) { - /* Select GPIO_6 to be controlled by HOST */ - lgw_reg_w(SX1302_REG_GPIO_GPIO_SEL_6_SELECTION, 0); - /* Configure it as an OUTPUT */ - lgw_reg_w(SX1302_REG_GPIO_GPIO_DIR_L_DIRECTION, 0xFF); -} - -/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ - -void dbg_toggle_gpio(void) { - /* Set GPIO_6 to high */ - lgw_reg_w(SX1302_REG_GPIO_GPIO_OUT_L_OUT_VALUE, 64); - /* Set GPIO_6 to low */ - lgw_reg_w(SX1302_REG_GPIO_GPIO_OUT_L_OUT_VALUE, 0); -} - -/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ - void dbg_log_buffer_to_file(FILE * file, uint8_t * buffer, uint16_t size) { int i; char stat_timestamp[24]; diff --git a/libloragw/src/loragw_gps.c b/libloragw/src/loragw_gps.c index 847dafee..ca9602d7 100644 --- a/libloragw/src/loragw_gps.c +++ b/libloragw/src/loragw_gps.c @@ -23,15 +23,13 @@ License: Revised BSD License, see LICENSE.TXT file include in the project #include /* bool type */ #include /* printf fprintf */ #include /* memcpy */ -#include +#include /* strerrno */ #include /* struct timespec */ #include /* open */ #include /* tcflush */ #include /* modf */ -#include - #include "loragw_gps.h" /* -------------------------------------------------------------------------- */ diff --git a/libloragw/src/loragw_hal.c b/libloragw/src/loragw_hal.c index 3b00c09c..2f2a2228 100644 --- a/libloragw/src/loragw_hal.c +++ b/libloragw/src/loragw_hal.c @@ -23,25 +23,29 @@ License: Revised BSD License, see LICENSE.TXT file include in the project #define _XOPEN_SOURCE 500 #endif +#define _GNU_SOURCE /* needed for qsort_r to be defined */ +#include /* qsort_r */ + #include /* C99 types */ #include /* bool type */ #include /* printf fprintf */ #include /* memcpy */ -#include /* pow, cell */ -#include #include /* symlink, unlink */ -#include #include #include "loragw_reg.h" #include "loragw_hal.h" #include "loragw_aux.h" -#include "loragw_spi.h" +#include "loragw_com.h" #include "loragw_i2c.h" +#include "loragw_lbt.h" #include "loragw_sx1250.h" #include "loragw_sx125x.h" +#include "loragw_sx1261.h" #include "loragw_sx1302.h" +#include "loragw_sx1302_timestamp.h" #include "loragw_stts751.h" +#include "loragw_ad5338r.h" #include "loragw_debug.h" /* -------------------------------------------------------------------------- */ @@ -54,9 +58,9 @@ License: Revised BSD License, see LICENSE.TXT file include in the project #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;acount_us - p2->count_us) <= 24) && + (p1->if_chain == p2->if_chain) && + (p1->datarate == p2->datarate) && + (p1->size == p2->size) && + (memcmp(p1->payload, p2->payload, p1->size) == 0)) { + + return true; + } + } + + return false; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +static int remove_pkt(struct lgw_pkt_rx_s * p, uint8_t * nb_pkt, uint8_t pkt_index) { + /* Check input parameters */ + CHECK_NULL(p); + CHECK_NULL(nb_pkt); + if (pkt_index > ((*nb_pkt) - 1)) { + printf("ERROR: failed to remove packet index %u\n", pkt_index); + return -1; + } + + /* Remove pkt from array, by replacing it with last packet of array */ + if (pkt_index == ((*nb_pkt) - 1)) { + /* If we remove last element, just decrement nb packet counter */ + /* Do nothing */ + } else { + /* Copy last packet onto the packet to be removed */ + memcpy(p + pkt_index, p + (*nb_pkt) - 1, sizeof(struct lgw_pkt_rx_s)); + } + + *nb_pkt -= 1; + + return 0; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int compare_pkt_tmst(const void *a, const void *b, void *arg) +{ + struct lgw_pkt_rx_s *p = (struct lgw_pkt_rx_s *)a; + struct lgw_pkt_rx_s *q = (struct lgw_pkt_rx_s *)b; + int *counter = (int *)arg; + int p_count, q_count; + + p_count = p->count_us; + q_count = q->count_us; + + if (p_count > q_count) { + *counter = *counter + 1; + } + + return (p_count - q_count); +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +static int merge_packets(struct lgw_pkt_rx_s * p, uint8_t * nb_pkt) { + uint8_t cpt; + int j, k, pkt_dup_idx, x; +#if DEBUG_HAL == 1 + int pkt_idx; +#endif + bool dup_restart = false; + int counter_qsort_swap = 0; + + /* Check input parameters */ + CHECK_NULL(p); + CHECK_NULL(nb_pkt); + + /* Init number of packets in array before merge */ + cpt = *nb_pkt; + + /* --------------------------------------------- */ + /* ---------- For Debug only - START ----------- */ + if (cpt > 0) { + DEBUG_MSG("<----- Searching for DUPLICATEs ------\n"); + } + for (j = 0; j < cpt; j++) { + DEBUG_PRINTF(" %d: tmst=%u SF=%u CRC_status=%d freq=%u chan=%u", j, p[j].count_us, p[j].datarate, p[j].status, p[j].freq_hz, p[j].if_chain); + if (p[j].ftime_received == true) { + DEBUG_PRINTF(" ftime=%u\n", p[j].ftime); + } else { + DEBUG_MSG (" ftime=NONE\n"); + } + } + /* ---------- For Debug only - END ------------- */ + /* --------------------------------------------- */ + + /* Remove duplicates */ + j = 0; + while (j < cpt) { + for (k = (j+1); k < cpt; k++) { + /* Searching for duplicated packets: + -- count_us should be equal or can have up to 24µs of difference (3 samples) + -- channel should be same + -- datarate should be same + -- payload should be same + */ + if (is_same_pkt( &p[j], &p[k])) { + /* We keep the packet which has CRC checked */ + if ((p[j].status == STAT_CRC_OK) && (p[k].status == STAT_CRC_BAD)) { + pkt_dup_idx = k; +#if DEBUG_HAL == 1 + pkt_idx = j; +#endif + } else if ((p[j].status == STAT_CRC_BAD) && (p[k].status == STAT_CRC_OK)) { + pkt_dup_idx = j; +#if DEBUG_HAL == 1 + pkt_idx = k; +#endif + } else { + /* we keep the packet which has a fine timestamp */ + if (p[j].ftime_received == true) { + pkt_dup_idx = k; +#if DEBUG_HAL == 1 + pkt_idx = j; +#endif + } else { + pkt_dup_idx = j; +#if DEBUG_HAL == 1 + pkt_idx = k; +#endif + } + /* sanity check */ + if (((p[j].ftime_received == true) && (p[k].ftime_received == true)) || + ((p[j].ftime_received == false) && (p[k].ftime_received == false))) { + DEBUG_MSG("WARNING: both duplicates have fine timestamps, or none has ? TBC\n"); + } + } + /* pkt_dup_idx contains the index to be deleted */ + DEBUG_PRINTF("duplicate found %d:%d, deleting %d\n", pkt_idx, pkt_dup_idx, pkt_dup_idx); + /* Remove duplicated packet from packet array */ + x = remove_pkt(p, &cpt, pkt_dup_idx); + if (x != 0) { + printf("ERROR: failed to remove packet from array (%d)\n", x); + } + dup_restart = true; + break; + } + } + if (dup_restart == true) { + /* Duplicate found, restart searching for duplicate from first element */ + j = 0; + dup_restart = false; +#if 0 + printf( "restarting search for duplicate\n" ); /* Too verbose */ +#endif + } else { + /* No duplicate found, continue... */ + j += 1; +#if 0 + printf( "no duplicate found\n" ); /* Too verbose */ +#endif + } + } + + /* Sort the packet array by ascending counter_us value */ + qsort_r(p, cpt, sizeof(p[0]), compare_pkt_tmst, &counter_qsort_swap); + DEBUG_PRINTF("%d elements swapped during sorting...\n", counter_qsort_swap); + + /* --------------------------------------------- */ + /* ---------- For Debug only - START ----------- */ + if (cpt > 0) { + DEBUG_MSG("--\n"); + } + for (j = 0; j < cpt; j++) { + DEBUG_PRINTF(" %d: tmst=%u SF=%d CRC_status=%d freq=%u chan=%u", j, p[j].count_us, p[j].datarate, p[j].status, p[j].freq_hz, p[j].if_chain); + if (p[j].ftime_received == true) { + DEBUG_PRINTF(" ftime=%u\n", p[j].ftime); + } else { + DEBUG_MSG (" ftime=NONE\n"); + } + } + if (cpt > 0) { + DEBUG_MSG( " ------------------------------------>\n\n" ); + } + /* ---------- For Debug only - END ------------- */ + /* --------------------------------------------- */ + + /* Update number of packets contained in packet array */ + *nb_pkt = cpt; + + return 0; +} + /* -------------------------------------------------------------------------- */ /* --- PUBLIC FUNCTIONS DEFINITION ------------------------------------------ */ @@ -230,17 +455,25 @@ int lgw_board_setconf(struct lgw_conf_board_s * conf) { return LGW_HAL_ERROR; } + /* Check input parameters */ + if ((conf->com_type != LGW_COM_SPI) && (conf->com_type != LGW_COM_USB)) { + DEBUG_MSG("ERROR: WRONG COM TYPE\n"); + return LGW_HAL_ERROR; + } + /* set internal config according to parameters */ CONTEXT_LWAN_PUBLIC = conf->lorawan_public; CONTEXT_BOARD.clksrc = conf->clksrc; CONTEXT_BOARD.full_duplex = conf->full_duplex; - strncpy(CONTEXT_SPI, conf->spidev_path, sizeof CONTEXT_SPI); - CONTEXT_SPI[sizeof CONTEXT_SPI - 1] = '\0'; /* ensure string termination */ + CONTEXT_COM_TYPE = conf->com_type; + strncpy(CONTEXT_COM_PATH, conf->com_path, sizeof CONTEXT_COM_PATH); + CONTEXT_COM_PATH[sizeof CONTEXT_COM_PATH - 1] = '\0'; /* ensure string termination */ - DEBUG_PRINTF("Note: board configuration: spidev_path: %s, lorawan_public:%d, clksrc:%d, full_duplex:%d\n", CONTEXT_SPI, - CONTEXT_LWAN_PUBLIC, - CONTEXT_BOARD.clksrc, - CONTEXT_BOARD.full_duplex); + DEBUG_PRINTF("Note: board configuration: com_type: %s, com_path: %s, lorawan_public:%d, clksrc:%d, full_duplex:%d\n", (CONTEXT_COM_TYPE == LGW_COM_SPI) ? "SPI" : "USB", + CONTEXT_COM_PATH, + CONTEXT_LWAN_PUBLIC, + CONTEXT_BOARD.clksrc, + CONTEXT_BOARD.full_duplex); return LGW_HAL_SUCCESS; } @@ -474,6 +707,16 @@ int lgw_rxif_setconf(uint8_t if_chain, struct lgw_conf_rxif_s * conf) { /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ +int lgw_demod_setconf(struct lgw_conf_demod_s * conf) { + CHECK_NULL(conf); + + CONTEXT_DEMOD.multisf_datarate = conf->multisf_datarate; + + return LGW_HAL_SUCCESS; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + int lgw_txgain_setconf(uint8_t rf_chain, struct lgw_tx_gain_lut_s * conf) { int i; @@ -506,7 +749,7 @@ int lgw_txgain_setconf(uint8_t rf_chain, struct lgw_tx_gain_lut_s * conf) { return LGW_HAL_ERROR; } if (conf->lut[i].pwr_idx > 22) { - DEBUG_MSG("ERROR: TX gain LUT: SX1250 power iundex must not exceed 22\n"); + DEBUG_MSG("ERROR: TX gain LUT: SX1250 power index must not exceed 22\n"); return LGW_HAL_ERROR; } @@ -529,12 +772,43 @@ int lgw_txgain_setconf(uint8_t rf_chain, struct lgw_tx_gain_lut_s * conf) { /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ -int lgw_timestamp_setconf(struct lgw_conf_timestamp_s * conf) { +int lgw_ftime_setconf(struct lgw_conf_ftime_s * conf) { CHECK_NULL(conf); - CONTEXT_TIMESTAMP.enable_precision_ts = conf->enable_precision_ts; - CONTEXT_TIMESTAMP.max_ts_metrics = conf->max_ts_metrics; - CONTEXT_TIMESTAMP.nb_symbols = conf->nb_symbols; + CONTEXT_FINE_TIMESTAMP.enable = conf->enable; + CONTEXT_FINE_TIMESTAMP.mode = conf->mode; + + return LGW_HAL_SUCCESS; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int lgw_sx1261_setconf(struct lgw_conf_sx1261_s * conf) { + int i; + + CHECK_NULL(conf); + + /* Set the SX1261 global conf */ + CONTEXT_SX1261.enable = conf->enable; + strncpy(CONTEXT_SX1261.spi_path, conf->spi_path, sizeof CONTEXT_SX1261.spi_path); + CONTEXT_SX1261.spi_path[sizeof CONTEXT_SX1261.spi_path - 1] = '\0'; /* ensure string termination */ + CONTEXT_SX1261.rssi_offset = conf->rssi_offset; + + /* Set the LBT conf */ + CONTEXT_SX1261.lbt_conf.enable = conf->lbt_conf.enable; + CONTEXT_SX1261.lbt_conf.rssi_target = conf->lbt_conf.rssi_target; + CONTEXT_SX1261.lbt_conf.nb_channel = conf->lbt_conf.nb_channel; + for (i = 0; i < CONTEXT_SX1261.lbt_conf.nb_channel; i++) { + if (conf->lbt_conf.channels[i].bandwidth != BW_125KHZ && conf->lbt_conf.channels[i].bandwidth != BW_250KHZ) { + printf("ERROR: bandwidth not supported for LBT channel %d\n", i); + return LGW_HAL_ERROR; + } + if (conf->lbt_conf.channels[i].scan_time_us != LGW_LBT_SCAN_TIME_128_US && conf->lbt_conf.channels[i].scan_time_us != LGW_LBT_SCAN_TIME_5000_US) { + printf("ERROR: scan_time_us not supported for LBT channel %d\n", i); + return LGW_HAL_ERROR; + } + CONTEXT_SX1261.lbt_conf.channels[i] = conf->lbt_conf.channels[i]; + } return LGW_HAL_SUCCESS; } @@ -571,18 +845,26 @@ int lgw_debug_setconf(struct lgw_conf_debug_s * conf) { int lgw_start(void) { int i, err; - int reg_stat; + + DEBUG_PRINTF(" --- %s\n", "IN"); if (CONTEXT_STARTED == true) { DEBUG_MSG("Note: LoRa concentrator already started, restarting it now\n"); } - reg_stat = lgw_connect(CONTEXT_SPI); - if (reg_stat == LGW_REG_ERROR) { + err = lgw_connect(CONTEXT_COM_TYPE, CONTEXT_COM_PATH); + if (err == LGW_REG_ERROR) { DEBUG_MSG("ERROR: FAIL TO CONNECT BOARD\n"); return LGW_HAL_ERROR; } + /* Set all GPIOs to 0 */ + err = sx1302_set_gpio(0x00); + if (err != LGW_REG_SUCCESS) { + printf("ERROR: failed to set all GPIOs to 0\n"); + return LGW_HAL_ERROR; + } + /* Calibrate radios */ err = sx1302_radio_calibrate(&CONTEXT_RF_CHAIN[0], CONTEXT_BOARD.clksrc, &CONTEXT_TX_GAIN_LUT[0]); if (err != LGW_REG_SUCCESS) { @@ -593,95 +875,185 @@ int lgw_start(void) { /* Setup radios for RX */ for (i = 0; i < LGW_RF_CHAIN_NB; i++) { if (CONTEXT_RF_CHAIN[i].enable == true) { - sx1302_radio_reset(i, CONTEXT_RF_CHAIN[i].type); + /* Reset the radio */ + err = sx1302_radio_reset(i, CONTEXT_RF_CHAIN[i].type); + if (err != LGW_REG_SUCCESS) { + printf("ERROR: failed to reset radio %d\n", i); + return LGW_HAL_ERROR; + } + + /* Setup the radio */ switch (CONTEXT_RF_CHAIN[i].type) { case LGW_RADIO_TYPE_SX1250: - sx1250_setup(i, CONTEXT_RF_CHAIN[i].freq_hz, CONTEXT_RF_CHAIN[i].single_input_mode); + err = sx1250_setup(i, CONTEXT_RF_CHAIN[i].freq_hz, CONTEXT_RF_CHAIN[i].single_input_mode); break; case LGW_RADIO_TYPE_SX1255: case LGW_RADIO_TYPE_SX1257: - sx125x_setup(i, CONTEXT_BOARD.clksrc, true, CONTEXT_RF_CHAIN[i].type, CONTEXT_RF_CHAIN[i].freq_hz); + err = sx125x_setup(i, CONTEXT_BOARD.clksrc, true, CONTEXT_RF_CHAIN[i].type, CONTEXT_RF_CHAIN[i].freq_hz); break; default: DEBUG_PRINTF("ERROR: RADIO TYPE NOT SUPPORTED (RF_CHAIN %d)\n", i); return LGW_HAL_ERROR; } - sx1302_radio_set_mode(i, CONTEXT_RF_CHAIN[i].type); + if (err != LGW_REG_SUCCESS) { + printf("ERROR: failed to setup radio %d\n", i); + return LGW_HAL_ERROR; + } + + /* Set radio mode */ + err = sx1302_radio_set_mode(i, CONTEXT_RF_CHAIN[i].type); + if (err != LGW_REG_SUCCESS) { + printf("ERROR: failed to set mode for radio %d\n", i); + return LGW_HAL_ERROR; + } } } /* Select the radio which provides the clock to the sx1302 */ - sx1302_radio_clock_select(CONTEXT_BOARD.clksrc); + err = sx1302_radio_clock_select(CONTEXT_BOARD.clksrc); + if (err != LGW_REG_SUCCESS) { + printf("ERROR: failed to get clock from radio %u\n", CONTEXT_BOARD.clksrc); + return LGW_HAL_ERROR; + } /* Release host control on radio (will be controlled by AGC) */ - sx1302_radio_host_ctrl(false); + err = sx1302_radio_host_ctrl(false); + if (err != LGW_REG_SUCCESS) { + printf("ERROR: failed to release control over radios\n"); + return LGW_HAL_ERROR; + } /* Basic initialization of the sx1302 */ - sx1302_init(&CONTEXT_TIMESTAMP); + err = sx1302_init(&CONTEXT_FINE_TIMESTAMP); + if (err != LGW_REG_SUCCESS) { + printf("ERROR: failed to initialize SX1302\n"); + return LGW_HAL_ERROR; + } /* Configure PA/LNA LUTs */ - sx1302_pa_lna_lut_configure(); + err = sx1302_pa_lna_lut_configure(&CONTEXT_BOARD); + if (err != LGW_REG_SUCCESS) { + printf("ERROR: failed to configure SX1302 PA/LNA LUT\n"); + return LGW_HAL_ERROR; + } /* Configure Radio FE */ - sx1302_radio_fe_configure(); + err = sx1302_radio_fe_configure(); + if (err != LGW_REG_SUCCESS) { + printf("ERROR: failed to configure SX1302 radio frontend\n"); + return LGW_HAL_ERROR; + } /* Configure the Channelizer */ - sx1302_channelizer_configure(CONTEXT_IF_CHAIN, false); + err = sx1302_channelizer_configure(CONTEXT_IF_CHAIN, false); + if (err != LGW_REG_SUCCESS) { + printf("ERROR: failed to configure SX1302 channelizer\n"); + return LGW_HAL_ERROR; + } - /* configure LoRa 'multi' demodulators */ - sx1302_lora_correlator_configure(); - sx1302_lora_modem_configure(CONTEXT_RF_CHAIN[0].freq_hz); /* TODO: freq_hz used to confiogure freq to time drift, based on RF0 center freq only */ + /* configure LoRa 'multi-sf' modems */ + err = sx1302_lora_correlator_configure(CONTEXT_IF_CHAIN, &(CONTEXT_DEMOD)); + if (err != LGW_REG_SUCCESS) { + printf("ERROR: failed to configure SX1302 LoRa modem correlators\n"); + return LGW_HAL_ERROR; + } + err = sx1302_lora_modem_configure(CONTEXT_RF_CHAIN[0].freq_hz); + if (err != LGW_REG_SUCCESS) { + printf("ERROR: failed to configure SX1302 LoRa modems\n"); + return LGW_HAL_ERROR; + } - /* configure LoRa 'stand-alone' modem */ + /* configure LoRa 'single-sf' modem */ if (CONTEXT_IF_CHAIN[8].enable == true) { - sx1302_lora_service_correlator_configure(&(CONTEXT_LORA_SERVICE)); - sx1302_lora_service_modem_configure(&(CONTEXT_LORA_SERVICE), CONTEXT_RF_CHAIN[0].freq_hz); /* TODO: freq_hz used to confiogure freq to time drift, based on RF0 center freq only */ + err = sx1302_lora_service_correlator_configure(&(CONTEXT_LORA_SERVICE)); + if (err != LGW_REG_SUCCESS) { + printf("ERROR: failed to configure SX1302 LoRa Service modem correlators\n"); + return LGW_HAL_ERROR; + } + err = sx1302_lora_service_modem_configure(&(CONTEXT_LORA_SERVICE), CONTEXT_RF_CHAIN[0].freq_hz); + if (err != LGW_REG_SUCCESS) { + printf("ERROR: failed to configure SX1302 LoRa Service modem\n"); + return LGW_HAL_ERROR; + } } /* configure FSK modem */ if (CONTEXT_IF_CHAIN[9].enable == true) { - sx1302_fsk_configure(&(CONTEXT_FSK)); + err = sx1302_fsk_configure(&(CONTEXT_FSK)); + if (err != LGW_REG_SUCCESS) { + printf("ERROR: failed to configure SX1302 FSK modem\n"); + return LGW_HAL_ERROR; + } } /* configure syncword */ - sx1302_lora_syncword(CONTEXT_LWAN_PUBLIC, CONTEXT_LORA_SERVICE.datarate); + err = sx1302_lora_syncword(CONTEXT_LWAN_PUBLIC, CONTEXT_LORA_SERVICE.datarate); + if (err != LGW_REG_SUCCESS) { + printf("ERROR: failed to configure SX1302 LoRa syncword\n"); + return LGW_HAL_ERROR; + } /* enable demodulators - to be done before starting AGC/ARB */ - sx1302_modem_enable(); + err = sx1302_modem_enable(); + if (err != LGW_REG_SUCCESS) { + printf("ERROR: failed to enable SX1302 modems\n"); + return LGW_HAL_ERROR; + } - /* Load firmware */ + /* Load AGC firmware */ switch (CONTEXT_RF_CHAIN[CONTEXT_BOARD.clksrc].type) { case LGW_RADIO_TYPE_SX1250: DEBUG_MSG("Loading AGC fw for sx1250\n"); - if (sx1302_agc_load_firmware(agc_firmware_sx1250) != LGW_HAL_SUCCESS) { + err = sx1302_agc_load_firmware(agc_firmware_sx1250); + if (err != LGW_REG_SUCCESS) { + printf("ERROR: failed to load AGC firmware for sx1250\n"); return LGW_HAL_ERROR; } break; + case LGW_RADIO_TYPE_SX1255: case LGW_RADIO_TYPE_SX1257: DEBUG_MSG("Loading AGC fw for sx125x\n"); - if (sx1302_agc_load_firmware(agc_firmware_sx125x) != LGW_HAL_SUCCESS) { + err = sx1302_agc_load_firmware(agc_firmware_sx125x); + if (err != LGW_REG_SUCCESS) { + printf("ERROR: failed to load AGC firmware for sx125x\n"); return LGW_HAL_ERROR; } break; default: break; } - if (sx1302_agc_start(FW_VERSION_AGC, CONTEXT_RF_CHAIN[CONTEXT_BOARD.clksrc].type, SX1302_AGC_RADIO_GAIN_AUTO, SX1302_AGC_RADIO_GAIN_AUTO, (CONTEXT_BOARD.full_duplex == true) ? 1 : 0) != LGW_HAL_SUCCESS) { + err = sx1302_agc_start(FW_VERSION_AGC, CONTEXT_RF_CHAIN[CONTEXT_BOARD.clksrc].type, SX1302_AGC_RADIO_GAIN_AUTO, SX1302_AGC_RADIO_GAIN_AUTO, CONTEXT_BOARD.full_duplex, CONTEXT_SX1261.lbt_conf.enable); + if (err != LGW_REG_SUCCESS) { + printf("ERROR: failed to start AGC firmware\n"); return LGW_HAL_ERROR; } + + /* Load ARB firmware */ DEBUG_MSG("Loading ARB fw\n"); - if (sx1302_arb_load_firmware(arb_firmware) != LGW_HAL_SUCCESS) { + err = sx1302_arb_load_firmware(arb_firmware); + if (err != LGW_REG_SUCCESS) { + printf("ERROR: failed to load ARB firmware\n"); return LGW_HAL_ERROR; } - if (sx1302_arb_start(FW_VERSION_ARB) != LGW_HAL_SUCCESS) { + err = sx1302_arb_start(FW_VERSION_ARB, &CONTEXT_FINE_TIMESTAMP); + if (err != LGW_REG_SUCCESS) { + printf("ERROR: failed to start ARB firmware\n"); return LGW_HAL_ERROR; } /* static TX configuration */ - sx1302_tx_configure(CONTEXT_RF_CHAIN[CONTEXT_BOARD.clksrc].type); + err = sx1302_tx_configure(CONTEXT_RF_CHAIN[CONTEXT_BOARD.clksrc].type); + if (err != LGW_REG_SUCCESS) { + printf("ERROR: failed to configure SX1302 TX path\n"); + return LGW_HAL_ERROR; + } /* enable GPS */ - sx1302_gps_enable(true); + err = sx1302_gps_enable(true); + if (err != LGW_REG_SUCCESS) { + printf("ERROR: failed to enable GPS on sx1302\n"); + return LGW_HAL_ERROR; + } /* For debug logging */ #if HAL_DEBUG_FILE_LOG @@ -714,42 +1086,120 @@ int lgw_start(void) { /* Configure the pseudo-random generator (For Debug) */ dbg_init_random(); -#if 0 - /* Configure a GPIO to be toggled for debug purpose */ - dbg_init_gpio(); -#endif + if (CONTEXT_COM_TYPE == LGW_COM_SPI) { + /* Find the temperature sensor on the known supported ports */ + for (i = 0; i < (int)(sizeof I2C_PORT_TEMP_SENSOR); i++) { + ts_addr = I2C_PORT_TEMP_SENSOR[i]; + err = i2c_linuxdev_open(I2C_DEVICE, ts_addr, &ts_fd); + if (err != LGW_I2C_SUCCESS) { + printf("ERROR: failed to open I2C for temperature sensor on port 0x%02X\n", ts_addr); + return LGW_HAL_ERROR; + } - /* Try to configure temperature sensor STTS751-0DP3F */ - ts_addr = I2C_PORT_TEMP_SENSOR_0; - i2c_linuxdev_open(I2C_DEVICE, ts_addr, &ts_fd); - err = stts751_configure(ts_fd, ts_addr); - if (err != LGW_I2C_SUCCESS) { - i2c_linuxdev_close(ts_fd); - ts_fd = -1; - /* Not found, try to configure temperature sensor STTS751-1DP3F */ - ts_addr = I2C_PORT_TEMP_SENSOR_1; - i2c_linuxdev_open(I2C_DEVICE, ts_addr, &ts_fd); - err = stts751_configure(ts_fd, ts_addr); - if (err != LGW_I2C_SUCCESS) { - printf("ERROR: failed to configure the temperature sensor\n"); + err = stts751_configure(ts_fd, ts_addr); + if (err != LGW_I2C_SUCCESS) { + printf("INFO: no temeprature sensor found on port 0x%02X\n", ts_addr); + i2c_linuxdev_close(ts_fd); + ts_fd = -1; + } else { + printf("INFO: found temperature sensor on port 0x%02X\n", ts_addr); + break; + } + } + if (i == sizeof I2C_PORT_TEMP_SENSOR) { + printf("ERROR: no temeprature sensor found.\n"); return LGW_HAL_ERROR; } + + /* Configure ADC AD338R for full duplex (CN490 reference design) */ + if (CONTEXT_BOARD.full_duplex == true) { + err = i2c_linuxdev_open(I2C_DEVICE, I2C_PORT_DAC_AD5338R, &ad_fd); + if (err != LGW_I2C_SUCCESS) { + printf("ERROR: failed to open I2C for ad5338r\n"); + return LGW_HAL_ERROR; + } + + err = ad5338r_configure(ad_fd, I2C_PORT_DAC_AD5338R); + if (err != LGW_I2C_SUCCESS) { + printf("ERROR: failed to configure ad5338r\n"); + i2c_linuxdev_close(ad_fd); + ad_fd = -1; + return LGW_HAL_ERROR; + } + + /* Turn off the PA: set DAC output to 0V */ + uint8_t volt_val[AD5338R_CMD_SIZE] = { 0x39, (uint8_t)VOLTAGE2HEX_H(0), (uint8_t)VOLTAGE2HEX_L(0) }; + err = ad5338r_write(ad_fd, I2C_PORT_DAC_AD5338R, volt_val); + if (err != LGW_I2C_SUCCESS) { + printf("ERROR: AD5338R: failed to set DAC output to 0V\n"); + return LGW_HAL_ERROR; + } + printf("INFO: AD5338R: Set DAC output to 0x%02X 0x%02X\n", (uint8_t)VOLTAGE2HEX_H(0), (uint8_t)VOLTAGE2HEX_L(0)); + } + } + + /* Connect to the external sx1261 for LBT or Spectral Scan */ + if (CONTEXT_SX1261.enable == true) { + err = sx1261_connect(CONTEXT_COM_TYPE, (CONTEXT_COM_TYPE == LGW_COM_SPI) ? CONTEXT_SX1261.spi_path : NULL); + if (err != LGW_REG_SUCCESS) { + printf("ERROR: failed to connect to the sx1261 radio (LBT/Spectral Scan)\n"); + return LGW_HAL_ERROR; + } + + err = sx1261_load_pram(); + if (err != LGW_REG_SUCCESS) { + printf("ERROR: failed to patch sx1261 radio for LBT/Spectral Scan\n"); + return LGW_HAL_ERROR; + } + + err = sx1261_calibrate(CONTEXT_RF_CHAIN[0].freq_hz); + if (err != LGW_REG_SUCCESS) { + printf("ERROR: failed to calibrate sx1261 radio\n"); + return LGW_HAL_ERROR; + } + + err = sx1261_setup(); + if (err != LGW_REG_SUCCESS) { + printf("ERROR: failed to setup sx1261 radio\n"); + return LGW_HAL_ERROR; + } + } + + /* Set CONFIG_DONE GPIO to 1 (turn on the corresponding LED) */ + err = sx1302_set_gpio(0x01); + if (err != LGW_REG_SUCCESS) { + printf("ERROR: failed to set CONFIG_DONE GPIO\n"); + return LGW_HAL_ERROR; } /* set hal state */ CONTEXT_STARTED = true; + DEBUG_PRINTF(" --- %s\n", "OUT"); + return LGW_HAL_SUCCESS; } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ int lgw_stop(void) { - int i, err; + int i, x, err = LGW_HAL_SUCCESS; + + DEBUG_PRINTF(" --- %s\n", "IN"); + + if (CONTEXT_STARTED == false) { + DEBUG_MSG("Note: LoRa concentrator was not started...\n"); + return LGW_HAL_SUCCESS; + } - DEBUG_MSG("INFO: aborting TX\n"); + /* Abort current TX if needed */ for (i = 0; i < LGW_RF_CHAIN_NB; i++) { - lgw_abort_tx(i); + DEBUG_PRINTF("INFO: aborting TX on chain %u\n", i); + x = lgw_abort_tx(i); + if (x != LGW_HAL_SUCCESS) { + printf("WARNING: failed to get abort TX on chain %u\n", i); + err = LGW_HAL_ERROR; + } } /* Close log file */ @@ -759,33 +1209,52 @@ int lgw_stop(void) { } DEBUG_MSG("INFO: Disconnecting\n"); - lgw_disconnect(); + x = lgw_disconnect(); + if (x != LGW_HAL_SUCCESS) { + printf("ERROR: failed to disconnect concentrator\n"); + err = LGW_HAL_ERROR; + } - DEBUG_MSG("INFO: Closing I2C\n"); - err = i2c_linuxdev_close(ts_fd); - if (err != 0) { - printf("ERROR: failed to close I2C device (err=%i)\n", err); + if (CONTEXT_COM_TYPE == LGW_COM_SPI) { + DEBUG_MSG("INFO: Closing I2C for temperature sensor\n"); + x = i2c_linuxdev_close(ts_fd); + if (x != 0) { + printf("ERROR: failed to close I2C temperature sensor device (err=%i)\n", x); + err = LGW_HAL_ERROR; + } + + if (CONTEXT_BOARD.full_duplex == true) { + DEBUG_MSG("INFO: Closing I2C for AD5338R\n"); + x = i2c_linuxdev_close(ad_fd); + if (x != 0) { + printf("ERROR: failed to close I2C AD5338R device (err=%i)\n", x); + err = LGW_HAL_ERROR; + } + } } CONTEXT_STARTED = false; - return LGW_HAL_SUCCESS; + + DEBUG_PRINTF(" --- %s\n", "OUT"); + + return err; } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ int lgw_receive(uint8_t max_pkt, struct lgw_pkt_rx_s *pkt_data) { int res; - uint8_t nb_pkt_fetched = 0; - uint16_t nb_pkt_found = 0; - uint16_t nb_pkt_left = 0; - float current_temperature, rssi_temperature_offset; + uint8_t nb_pkt_fetched = 0; + uint8_t nb_pkt_found = 0; + uint8_t nb_pkt_left = 0; + float current_temperature = 0.0, rssi_temperature_offset = 0.0; + /* performances variables */ + struct timeval tm; - /* Check that AGC/ARB firmwares are not corrupted, and update internal counter */ - /* WARNING: this needs to be called regularly by the upper layer */ - res = sx1302_update(); - if (res != LGW_REG_SUCCESS) { - return LGW_HAL_ERROR; - } + DEBUG_PRINTF(" --- %s\n", "IN"); + + /* Record function start time */ + _meas_time_start(&tm); /* Get packets from SX1302, if any */ res = sx1302_fetch(&nb_pkt_fetched); @@ -793,7 +1262,17 @@ int lgw_receive(uint8_t max_pkt, struct lgw_pkt_rx_s *pkt_data) { printf("ERROR: failed to fetch packets from SX1302\n"); return LGW_HAL_ERROR; } + + /* Update internal counter */ + /* WARNING: this needs to be called regularly by the upper layer */ + res = sx1302_update(); + if (res != LGW_REG_SUCCESS) { + return LGW_HAL_ERROR; + } + + /* Exit now if no packet fetched */ if (nb_pkt_fetched == 0) { + _meas_time_stop(1, tm, __FUNCTION__); return 0; } if (nb_pkt_fetched > max_pkt) { @@ -802,7 +1281,7 @@ int lgw_receive(uint8_t max_pkt, struct lgw_pkt_rx_s *pkt_data) { } /* Apply RSSI temperature compensation */ - res = stts751_get_temperature(ts_fd, ts_addr, ¤t_temperature); + res = lgw_get_temperature(¤t_temperature); if (res != LGW_I2C_SUCCESS) { printf("ERROR: failed to get current temperature\n"); return LGW_HAL_ERROR; @@ -812,8 +1291,11 @@ int lgw_receive(uint8_t max_pkt, struct lgw_pkt_rx_s *pkt_data) { for (nb_pkt_found = 0; nb_pkt_found < ((nb_pkt_fetched <= max_pkt) ? nb_pkt_fetched : max_pkt); nb_pkt_found++) { /* Get packet and move to next one */ res = sx1302_parse(&lgw_context, &pkt_data[nb_pkt_found]); - if (res != LGW_REG_SUCCESS) { - printf("ERROR: failed to parse fetched packet %d, aborting...\n", nb_pkt_found); + if (res == LGW_REG_WARNING) { + printf("WARNING: parsing error on packet %d, discarding fetched packets\n", nb_pkt_found); + return LGW_HAL_SUCCESS; + } else if (res == LGW_REG_ERROR) { + printf("ERROR: fatal parsing error on packet %d, aborting...\n", nb_pkt_found); return LGW_HAL_ERROR; } @@ -829,15 +1311,39 @@ int lgw_receive(uint8_t max_pkt, struct lgw_pkt_rx_s *pkt_data) { DEBUG_PRINTF("INFO: nb pkt found:%u left:%u\n", nb_pkt_found, nb_pkt_left); + /* Remove duplicated packets generated by double demod when precision timestamp is enabled */ + if ((nb_pkt_found > 0) && (CONTEXT_FINE_TIMESTAMP.enable == true)) { + res = merge_packets(pkt_data, &nb_pkt_found); + if (res != 0) { + printf("WARNING: failed to remove duplicated packets\n"); + } + + DEBUG_PRINTF("INFO: nb pkt found:%u (after de-duplicating)\n", nb_pkt_found); + } + + _meas_time_stop(1, tm, __FUNCTION__); + + DEBUG_PRINTF(" --- %s\n", "OUT"); + return nb_pkt_found; } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ int lgw_send(struct lgw_pkt_tx_s * pkt_data) { + int err; + bool lbt_tx_allowed; + /* performances variables */ + struct timeval tm; + + DEBUG_PRINTF(" --- %s\n", "IN"); + + /* Record function start time */ + _meas_time_start(&tm); + /* check if the concentrator is running */ if (CONTEXT_STARTED == false) { - DEBUG_MSG("ERROR: CONCENTRATOR IS NOT RUNNING, START IT BEFORE SENDING\n"); + printf("ERROR: CONCENTRATOR IS NOT RUNNING, START IT BEFORE SENDING\n"); return LGW_HAL_ERROR; } @@ -845,66 +1351,139 @@ int lgw_send(struct lgw_pkt_tx_s * pkt_data) { /* check input range (segfault prevention) */ if (pkt_data->rf_chain >= LGW_RF_CHAIN_NB) { - DEBUG_MSG("ERROR: INVALID RF_CHAIN TO SEND PACKETS\n"); + printf("ERROR: INVALID RF_CHAIN TO SEND PACKETS\n"); return LGW_HAL_ERROR; } /* check input variables */ if (CONTEXT_RF_CHAIN[pkt_data->rf_chain].tx_enable == false) { - DEBUG_MSG("ERROR: SELECTED RF_CHAIN IS DISABLED FOR TX ON SELECTED BOARD\n"); + printf("ERROR: SELECTED RF_CHAIN IS DISABLED FOR TX ON SELECTED BOARD\n"); return LGW_HAL_ERROR; } if (CONTEXT_RF_CHAIN[pkt_data->rf_chain].enable == false) { - DEBUG_MSG("ERROR: SELECTED RF_CHAIN IS DISABLED\n"); + printf("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"); + printf("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"); + printf("ERROR: BANDWIDTH NOT SUPPORTED BY LORA TX\n"); return LGW_HAL_ERROR; } if (!IS_LORA_DR(pkt_data->datarate)) { - DEBUG_MSG("ERROR: DATARATE NOT SUPPORTED BY LORA TX\n"); + printf("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"); + printf("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"); + printf("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"); + printf("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"); + printf("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"); + printf("ERROR: PAYLOAD LENGTH TOO BIG FOR FSK TX\n"); return LGW_HAL_ERROR; } } else if (pkt_data->modulation == MOD_CW) { /* do nothing */ } else { - DEBUG_MSG("ERROR: INVALID TX MODULATION\n"); + printf("ERROR: INVALID TX MODULATION\n"); + return LGW_HAL_ERROR; + } + + /* Set PA gain with AD5338R when using full duplex CN490 ref design */ + if (CONTEXT_BOARD.full_duplex == true) { + uint8_t volt_val[AD5338R_CMD_SIZE] = {0x39, VOLTAGE2HEX_H(2.51), VOLTAGE2HEX_L(2.51)}; /* set to 2.51V */ + err = ad5338r_write(ad_fd, I2C_PORT_DAC_AD5338R, volt_val); + if (err != LGW_I2C_SUCCESS) { + printf("ERROR: failed to set voltage by ad5338r\n"); + return LGW_HAL_ERROR; + } + printf("INFO: AD5338R: Set DAC output to 0x%02X 0x%02X\n", (uint8_t)VOLTAGE2HEX_H(2.51), (uint8_t)VOLTAGE2HEX_L(2.51)); + } + + /* Start Listen-Before-Talk */ + if (CONTEXT_SX1261.lbt_conf.enable == true) { + err = lgw_lbt_start(&CONTEXT_SX1261, pkt_data); + if (err != 0) { + printf("ERROR: failed to start LBT\n"); + return LGW_HAL_ERROR; + } + } + + /* Send the TX request to the concentrator */ + err = sx1302_send(CONTEXT_RF_CHAIN[pkt_data->rf_chain].type, &CONTEXT_TX_GAIN_LUT[pkt_data->rf_chain], CONTEXT_LWAN_PUBLIC, &CONTEXT_FSK, pkt_data); + if (err != LGW_REG_SUCCESS) { + printf("ERROR: %s: Failed to send packet\n", __FUNCTION__); + + if (CONTEXT_SX1261.lbt_conf.enable == true) { + err = lgw_lbt_stop(); + if (err != 0) { + printf("ERROR: %s: Failed to stop LBT\n", __FUNCTION__); + } + } + return LGW_HAL_ERROR; } - return sx1302_send(CONTEXT_RF_CHAIN[pkt_data->rf_chain].type, &CONTEXT_TX_GAIN_LUT[pkt_data->rf_chain], CONTEXT_LWAN_PUBLIC, &CONTEXT_FSK, pkt_data); + _meas_time_stop(1, tm, __FUNCTION__); + + /* Stop Listen-Before-Talk */ + if (CONTEXT_SX1261.lbt_conf.enable == true) { + err = lgw_lbt_tx_status(pkt_data->rf_chain, &lbt_tx_allowed); + if (err != 0) { + printf("ERROR: %s: Failed to get LBT TX status, TX aborted\n", __FUNCTION__); + err = sx1302_tx_abort(pkt_data->rf_chain); + if (err != 0) { + printf("ERROR: %s: Failed to abort TX\n", __FUNCTION__); + } + err = lgw_lbt_stop(); + if (err != 0) { + printf("ERROR: %s: Failed to stop LBT\n", __FUNCTION__); + } + return LGW_HAL_ERROR; + } + if (lbt_tx_allowed == true) { + printf("LBT: packet is allowed to be transmitted\n"); + } else { + printf("LBT: (ERROR) packet is NOT allowed to be transmitted\n"); + } + + err = lgw_lbt_stop(); + if (err != 0) { + printf("ERROR: %s: Failed to stop LBT\n", __FUNCTION__); + return LGW_HAL_ERROR; + } + } + + DEBUG_PRINTF(" --- %s\n", "OUT"); + + if (CONTEXT_SX1261.lbt_conf.enable == true && lbt_tx_allowed == false) { + return LGW_LBT_NOT_ALLOWED; + } else { + return LGW_HAL_SUCCESS; + } } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ int lgw_status(uint8_t rf_chain, uint8_t select, uint8_t *code) { + DEBUG_PRINTF(" --- %s\n", "IN"); + /* check input variables */ CHECK_NULL(code); if (rf_chain >= LGW_RF_CHAIN_NB) { @@ -930,6 +1509,8 @@ int lgw_status(uint8_t rf_chain, uint8_t select, uint8_t *code) { return LGW_HAL_ERROR; } + DEBUG_PRINTF(" --- %s\n", "OUT"); + //DEBUG_PRINTF("INFO: STATUS %u\n", *code); return LGW_HAL_SUCCESS; } @@ -937,6 +1518,10 @@ int lgw_status(uint8_t rf_chain, uint8_t select, uint8_t *code) { /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ int lgw_abort_tx(uint8_t rf_chain) { + int err; + + DEBUG_PRINTF(" --- %s\n", "IN"); + /* check input variables */ if (rf_chain >= LGW_RF_CHAIN_NB) { DEBUG_MSG("ERROR: NOT A VALID RF_CHAIN NUMBER\n"); @@ -944,50 +1529,81 @@ int lgw_abort_tx(uint8_t rf_chain) { } /* Abort current TX */ - return sx1302_tx_abort(rf_chain); + err = sx1302_tx_abort(rf_chain); + + DEBUG_PRINTF(" --- %s\n", "OUT"); + + return err; } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ int lgw_get_trigcnt(uint32_t* trig_cnt_us) { + DEBUG_PRINTF(" --- %s\n", "IN"); + CHECK_NULL(trig_cnt_us); *trig_cnt_us = sx1302_timestamp_counter(true); + DEBUG_PRINTF(" --- %s\n", "OUT"); + return LGW_HAL_SUCCESS; } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ int lgw_get_instcnt(uint32_t* inst_cnt_us) { + DEBUG_PRINTF(" --- %s\n", "IN"); + CHECK_NULL(inst_cnt_us); *inst_cnt_us = sx1302_timestamp_counter(false); + DEBUG_PRINTF(" --- %s\n", "OUT"); + return LGW_HAL_SUCCESS; } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ int lgw_get_eui(uint64_t* eui) { + DEBUG_PRINTF(" --- %s\n", "IN"); + CHECK_NULL(eui); if (sx1302_get_eui(eui) != LGW_REG_SUCCESS) { return LGW_HAL_ERROR; } + + DEBUG_PRINTF(" --- %s\n", "OUT"); + return LGW_HAL_SUCCESS; } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ int lgw_get_temperature(float* temperature) { + int err = LGW_HAL_ERROR; + + DEBUG_PRINTF(" --- %s\n", "IN"); + CHECK_NULL(temperature); - if (stts751_get_temperature(ts_fd, ts_addr, temperature) != LGW_I2C_SUCCESS) { - return LGW_HAL_ERROR; + switch (CONTEXT_COM_TYPE) { + case LGW_COM_SPI: + err = stts751_get_temperature(ts_fd, ts_addr, temperature); + break; + case LGW_COM_USB: + err = lgw_com_get_temperature(temperature); + break; + default: + printf("ERROR(%s:%d): wrong communication type (SHOULD NOT HAPPEN)\n", __FUNCTION__, __LINE__); + break; } - return LGW_HAL_SUCCESS; + DEBUG_PRINTF(" --- %s\n", "OUT"); + + return err; } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ @@ -998,58 +1614,21 @@ const char* lgw_version_info() { /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ -uint32_t lgw_time_on_air(struct lgw_pkt_tx_s *packet) { - int32_t val; - uint8_t SF, H, DE; - uint16_t BW; - uint32_t payloadSymbNb, Tpacket; - double Tsym, Tpreamble, Tpayload, Tfsk; +uint32_t lgw_time_on_air(const struct lgw_pkt_tx_s *packet) { + double t_fsk; + uint32_t toa_ms, toa_us; + + DEBUG_PRINTF(" --- %s\n", "IN"); if (packet == NULL) { - DEBUG_MSG("ERROR: Failed to compute time on air, wrong parameter\n"); + printf("ERROR: Failed to compute time on air, wrong parameter\n"); return 0; } if (packet->modulation == MOD_LORA) { - /* Get bandwidth */ - val = lgw_bw_getval(packet->bandwidth); - if (val != -1) { - BW = (uint16_t)(val / 1E3); - } else { - DEBUG_PRINTF("ERROR: Cannot compute time on air for this packet, unsupported bandwidth (0x%02X)\n", packet->bandwidth); - return 0; - } - - /* Get datarate */ - val = lgw_sf_getval(packet->datarate); - if (val != -1) { - SF = (uint8_t)val; - /* TODO: update formula for SF5/SF6 */ - if (SF < 7) { - DEBUG_MSG("WARNING: clipping time on air computing to SF7 for SF5/SF6\n"); - SF = 7; - } - } else { - DEBUG_PRINTF("ERROR: Cannot compute time on air for this packet, unsupported datarate (0x%02X)\n", packet->datarate); - return 0; - } - - /* Duration of 1 symbol */ - Tsym = pow(2, SF) / BW; - - /* Duration of preamble */ - Tpreamble = ((double)(packet->preamble) + 4.25) * Tsym; - - /* Duration of payload */ - H = (packet->no_header==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; - - /* Duration of packet */ - Tpacket = Tpreamble + Tpayload; + toa_us = lora_packet_time_on_air(packet->bandwidth, packet->datarate, packet->coderate, packet->preamble, packet->no_header, packet->no_crc, packet->size, NULL, NULL, NULL); + toa_ms = (uint32_t)( (double)toa_us / 1000.0 + 0.5 ); + DEBUG_PRINTF("INFO: LoRa packet ToA: %u ms\n", toa_ms); } else if (packet->modulation == MOD_FSK) { /* PREAMBLE + SYNC_WORD + PKT_LEN + PKT_PAYLOAD + CRC PREAMBLE: default 5 bytes @@ -1058,16 +1637,61 @@ uint32_t lgw_time_on_air(struct lgw_pkt_tx_s *packet) { PKT_PAYLOAD: x bytes CRC: 0 or 2 bytes */ - Tfsk = (8 * (double)(packet->preamble + CONTEXT_FSK.sync_word_size + 1 + packet->size + ((packet->no_crc == true) ? 0 : 2)) / (double)packet->datarate) * 1E3; + t_fsk = (8 * (double)(packet->preamble + CONTEXT_FSK.sync_word_size + 1 + packet->size + ((packet->no_crc == true) ? 0 : 2)) / (double)packet->datarate) * 1E3; /* Duration of packet */ - Tpacket = (uint32_t)Tfsk + 1; /* add margin for rounding */ + toa_ms = (uint32_t)t_fsk + 1; /* add margin for rounding */ } else { - Tpacket = 0; - DEBUG_PRINTF("ERROR: Cannot compute time on air for this packet, unsupported modulation (0x%02X)\n", packet->modulation); + toa_ms = 0; + printf("ERROR: Cannot compute time on air for this packet, unsupported modulation (0x%02X)\n", packet->modulation); } - return Tpacket; + DEBUG_PRINTF(" --- %s\n", "OUT"); + + return toa_ms; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int lgw_spectral_scan_start(uint32_t freq_hz, uint16_t nb_scan) { + int err; + + if (CONTEXT_SX1261.enable != true) { + printf("ERROR: sx1261 is not enabled, no spectral scan\n"); + return LGW_HAL_ERROR; + } + + err = sx1261_set_rx_params(freq_hz, BW_125KHZ); + if (err != LGW_REG_SUCCESS) { + printf("ERROR: Failed to set RX params for Spectral Scan\n"); + return LGW_HAL_ERROR; + } + + err = sx1261_spectral_scan_start(nb_scan); + if (err != LGW_REG_SUCCESS) { + printf("ERROR: start spectral scan failed\n"); + return LGW_HAL_ERROR; + } + + return LGW_HAL_SUCCESS; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int lgw_spectral_scan_get_status(lgw_spectral_scan_status_t * status) { + return sx1261_spectral_scan_status(status); +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int lgw_spectral_scan_get_results(int16_t levels_dbm[static LGW_SPECTRAL_SCAN_RESULT_SIZE], uint16_t results[static LGW_SPECTRAL_SCAN_RESULT_SIZE]) { + return sx1261_spectral_scan_get_results(CONTEXT_SX1261.rssi_offset, levels_dbm, results); +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int lgw_spectral_scan_abort() { + return sx1261_spectral_scan_abort(); } /* --- EOF ------------------------------------------------------------------ */ diff --git a/libloragw/src/loragw_i2c.c b/libloragw/src/loragw_i2c.c index cb2c860c..87e8b3ae 100644 --- a/libloragw/src/loragw_i2c.c +++ b/libloragw/src/loragw_i2c.c @@ -18,10 +18,8 @@ License: Revised BSD License, see LICENSE.TXT file include in the project #include /* C99 types */ #include /* printf fprintf */ -#include /* malloc free */ #include /* lseek, close */ #include /* open */ -#include /* memset */ #include /* errno */ #include @@ -36,13 +34,13 @@ License: Revised BSD License, see LICENSE.TXT file include in the project #define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) #if DEBUG_I2C == 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;} + #define DEBUG_MSG(str) fprintf(stdout, str) + #define DEBUG_PRINTF(fmt, args...) fprintf(stdout,"%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_I2C_ERROR;} #else #define DEBUG_MSG(str) #define DEBUG_PRINTF(fmt, args...) - #define CHECK_NULL(a) if(a==NULL){return LGW_SPI_ERROR;} + #define CHECK_NULL(a) if(a==NULL){return LGW_I2C_ERROR;} #endif /* -------------------------------------------------------------------------- */ @@ -141,6 +139,31 @@ int i2c_linuxdev_write(int i2c_fd, uint8_t device_addr, uint8_t reg_addr, uint8_ /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ +int i2c_linuxdev_write_buffer(int i2c_fd, uint8_t device_addr, uint8_t *buffer, uint8_t size) { + struct i2c_rdwr_ioctl_data packets; + struct i2c_msg messages[1]; + + /* Check input parameters */ + CHECK_NULL(buffer); + + messages[0].addr = device_addr; + messages[0].flags = 0; + messages[0].len = size; + messages[0].buf = buffer; + + packets.msgs = messages; + packets.nmsgs = 1; + + if (ioctl(i2c_fd, I2C_RDWR, &packets) < 0) { + DEBUG_PRINTF("ERROR: Write buffer to I2C Device failed (%d, 0x%02x) - %s\n", i2c_fd, device_addr, strerror(errno)); + return LGW_I2C_ERROR; + } + + return LGW_I2C_SUCCESS; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + int i2c_linuxdev_close(int i2c_fd) { int i; diff --git a/libloragw/src/loragw_lbt.c b/libloragw/src/loragw_lbt.c new file mode 100644 index 00000000..e298c42b --- /dev/null +++ b/libloragw/src/loragw_lbt.c @@ -0,0 +1,235 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2020 Semtech + +Description: + LoRa concentrator Listen-Before-Talk functions + +License: Revised BSD License, see LICENSE.TXT file include in the project +*/ + + +/* -------------------------------------------------------------------------- */ +/* --- DEPENDANCIES --------------------------------------------------------- */ + +/* fix an issue between POSIX and C99 */ +#if __STDC_VERSION__ >= 199901L + #define _XOPEN_SOURCE 600 +#else + #define _XOPEN_SOURCE 500 +#endif + +#include /* printf */ +#include /* llabs */ + +#include "loragw_aux.h" +#include "loragw_lbt.h" +#include "loragw_sx1261.h" +#include "loragw_sx1302.h" +#include "loragw_hal.h" + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE MACROS ------------------------------------------------------- */ + +#if DEBUG_LBT == 1 + #define DEBUG_MSG(str) fprintf(stdout, str) + #define DEBUG_PRINTF(fmt, args...) fprintf(stdout,"%s:%d: "fmt, __FUNCTION__, __LINE__, args) +#else + #define DEBUG_MSG(str) + #define DEBUG_PRINTF(fmt, args...) +#endif + +/* -------------------------------------------------------------------------- */ +/* --- 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 */ +static 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 */ + return ((diff <= 10000) ? true : false); +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +static int is_lbt_channel(const struct lgw_conf_lbt_s * lbt_context, uint32_t freq_hz, uint8_t bandwidth) { + int i; + int lbt_channel_match = -1; + + for (i = 0; i < lbt_context->nb_channel; i++) { + if ((is_equal_freq(freq_hz, lbt_context->channels[i].freq_hz) == true) && (bandwidth == lbt_context->channels[i].bandwidth)) { + DEBUG_PRINTF("LBT: select channel %d (freq:%u Hz, bw:0x%02X)\n", i, lbt_context->channels[i].freq_hz, lbt_context->channels[i].bandwidth); + lbt_channel_match = i; + break; + } + } + + /* Return the index of the LBT channel which matched */ + return lbt_channel_match; +} + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC FUNCTIONS DEFINITION ------------------------------------------ */ + +int lgw_lbt_start(const struct lgw_conf_sx1261_s * sx1261_context, const struct lgw_pkt_tx_s * pkt) { + int err; + int lbt_channel_selected; + uint32_t toa_ms; + /* performances variables */ + struct timeval tm; + + /* Record function start time */ + _meas_time_start(&tm); + + /* Check if we have a LBT channel for this transmit frequency */ + lbt_channel_selected = is_lbt_channel(&(sx1261_context->lbt_conf), pkt->freq_hz, pkt->bandwidth); + if (lbt_channel_selected == -1) { + printf("ERROR: Cannot start LBT - wrong channel\n"); + return -1; + } + + /* Check if the packet Time On Air exceeds the maximum allowed transmit time on this channel */ + /* Channel sensing is checked 1.5ms before the packet departure time, so need to take this into account */ + if (sx1261_context->lbt_conf.channels[lbt_channel_selected].transmit_time_ms * 1000 <= 1500) { + printf("ERROR: Cannot start LBT - channel transmit_time_ms must be > 1.5ms\n"); + return -1; + } + toa_ms = lgw_time_on_air(pkt); + if ((toa_ms * 1000) > (uint32_t)(sx1261_context->lbt_conf.channels[lbt_channel_selected].transmit_time_ms * 1000 - 1500)) { + printf("ERROR: Cannot start LBT - packet time on air exceeds allowed transmit time (toa:%ums, max:%ums)\n", toa_ms, sx1261_context->lbt_conf.channels[lbt_channel_selected].transmit_time_ms); + return -1; + } + + /* Set LBT scan frequency */ + err = sx1261_set_rx_params(pkt->freq_hz, pkt->bandwidth); + if (err != 0) { + printf("ERROR: Cannot start LBT - unable to set sx1261 RX parameters\n"); + return -1; + } + + /* Start LBT */ + err = sx1261_lbt_start(sx1261_context->lbt_conf.channels[lbt_channel_selected].scan_time_us, sx1261_context->lbt_conf.rssi_target + sx1261_context->rssi_offset); + if (err != 0) { + printf("ERROR: Cannot start LBT - sx1261 LBT start\n"); + return -1; + } + + _meas_time_stop(3, tm, __FUNCTION__); + + return 0; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int lgw_lbt_tx_status(uint8_t rf_chain, bool * tx_ok) { + int err; + uint8_t status; + bool tx_timeout = false; + struct timeval tm_start; + /* performances variables */ + struct timeval tm; + + /* Record function start time */ + _meas_time_start(&tm); + + /* Wait for transmit to be initiated */ + /* Bit 0 in status: TX has been initiated on Radio A */ + /* Bit 1 in status: TX has been initiated on Radio B */ + timeout_start(&tm_start); + do { + /* handle timeout */ + if (timeout_check(tm_start, 500) != 0) { + printf("ERROR: %s: TIMEOUT on TX start, not started\n", __FUNCTION__); + tx_timeout = true; + /* we'll still perform the AGC clear status and return an error to upper layer */ + break; + } + + /* get tx status */ + err = sx1302_agc_status(&status); + if (err != 0) { + printf("ERROR: %s: failed to get AGC status\n", __FUNCTION__); + return -1; + } + wait_ms(1); + } while ((status & (1 << rf_chain)) == 0x00); + + if (tx_timeout == false) { + /* Check if the packet has been transmitted or blocked by LBT */ + /* Bit 6 in status: Radio A is not allowed to transmit */ + /* Bit 7 in status: Radio B is not allowed to transmit */ + if (TAKE_N_BITS_FROM(status, ((rf_chain == 0) ? 6 : 7), 1) == 0) { + *tx_ok = true; + } else { + *tx_ok = false; + } + } + + /* Clear AGC transmit status */ + sx1302_agc_mailbox_write(0, 0xFF); + + /* Wait for transmit status to be cleared */ + timeout_start(&tm_start); + do { + /* handle timeout */ + if (timeout_check(tm_start, 500) != 0) { + printf("ERROR: %s: TIMEOUT on TX start (AGC clear status)\n", __FUNCTION__); + tx_timeout = true; + break; + } + + /* get tx status */ + err = sx1302_agc_status(&status); + if (err != 0) { + printf("ERROR: %s: failed to get AGC status\n", __FUNCTION__); + return -1; + } + wait_ms(1); + } while (status != 0x00); + + /* Acknoledge */ + sx1302_agc_mailbox_write(0, 0x00); + + _meas_time_stop(3, tm, __FUNCTION__); + + if (tx_timeout == true) { + return -1; + } else { + return 0; + } +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int lgw_lbt_stop(void) { + int err; + + /* performances variables */ + struct timeval tm; + + /* Record function start time */ + _meas_time_start(&tm); + + err = sx1261_lbt_stop(); + if (err != 0) { + printf("ERROR: Cannot stop LBT - failed\n"); + return -1; + } + + _meas_time_stop(3, tm, __FUNCTION__); + + return 0; +} + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/libloragw/src/loragw_mcu.c b/libloragw/src/loragw_mcu.c new file mode 100644 index 00000000..658e2a35 --- /dev/null +++ b/libloragw/src/loragw_mcu.c @@ -0,0 +1,702 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2020 Semtech + +Description: + Host specific functions to address the LoRa concentrator registers through + a USB interface. + Single-byte read/write and burst read/write. + +License: Revised BSD License, see LICENSE.TXT file include in the project +*/ + + +/* -------------------------------------------------------------------------- */ +/* --- DEPENDANCIES --------------------------------------------------------- */ + +#include /* C99 types */ +#include /* bool type */ +#include /* printf fprintf */ +#include /* rand */ +#include /* lseek, close */ +#include /* memset */ +#include /* Error number definitions */ +#include /* POSIX terminal control definitions */ + +#include "loragw_mcu.h" +#include "loragw_aux.h" + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE MACROS ------------------------------------------------------- */ + +#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) +#if DEBUG_MCU == 1 + #define DEBUG_MSG(str) fprintf(stdout, str) + #define DEBUG_PRINTF(fmt, args...) fprintf(stdout, fmt, args) + #define CHECK_NULL(a) if(a==NULL){fprintf(stderr,"%s:%d: ERROR: NULL POINTER AS ARGUMENT\n", __FUNCTION__, __LINE__);return -1;} +#else + #define DEBUG_MSG(str) + #define DEBUG_PRINTF(fmt, args...) + #define CHECK_NULL(a) if(a==NULL){return -1;} +#endif + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE CONSTANTS ---------------------------------------------------- */ + +#if DEBUG_MCU == 1 +#define DEBUG_VERBOSE 0 +#endif + +#define HEADER_CMD_SIZE 4 + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE TYPES -------------------------------------------------------- */ + +typedef struct spi_req_bulk_s { + uint16_t size; + uint8_t nb_req; + uint8_t buffer[LGW_USB_BURST_CHUNK]; +} spi_req_bulk_t; + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE VARIABLES --------------------------------------------------- */ + +static uint8_t buf_hdr[HEADER_CMD_SIZE]; + +static spi_req_bulk_t spi_bulk_buffer = { + .size = 0, + .nb_req = 0, + .buffer = { 0 } +}; + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE FUNCTIONS DEFINITION ----------------------------------------- */ + +int spi_req_bulk_insert(spi_req_bulk_t * bulk_buffer, uint8_t * req, uint16_t req_size) { + /* Check input parameters */ + CHECK_NULL(bulk_buffer); + CHECK_NULL(req); + + if (bulk_buffer->nb_req == 255) { + printf("ERROR: cannot insert a new SPI request in bulk buffer - too many requests\n"); + return -1; + } + + if ((bulk_buffer->size + req_size) > LGW_USB_BURST_CHUNK) { + printf("ERROR: cannot insert a new SPI request in bulk buffer - buffer full\n"); + return -1; + } + + /* Add a new request entry in storage buffer */ + memcpy(bulk_buffer->buffer + bulk_buffer->size, req, req_size); + + bulk_buffer->nb_req += 1; + bulk_buffer->size += req_size; + + return 0; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +uint32_t bytes_be_to_uint32_le(const uint8_t * bytes) { + uint32_t val = 0; + + if (bytes != NULL) { + /* Big endian to Little Endian */ + val = (uint32_t)(bytes[0] << 24); + val |= (uint32_t)(bytes[1] << 16); + val |= (uint32_t)(bytes[2] << 8); + val |= (uint32_t)(bytes[3] << 0); + } + + return val; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int32_t bytes_be_to_int32_le(const uint8_t * bytes) { + int32_t val = 0; + + if (bytes != NULL) { + /* Big endian to Little Endian */ + val = (int32_t)(bytes[0] << 24); + val |= (int32_t)(bytes[1] << 16); + val |= (int32_t)(bytes[2] << 8); + val |= (int32_t)(bytes[3] << 0); + } + + return val; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +const char * cmd_get_str(const uint8_t cmd) { + switch (cmd) { + case ORDER_ID__REQ_PING: + return "REQ_PING"; + case ORDER_ID__REQ_GET_STATUS: + return "REQ_GET_STATUS"; + case ORDER_ID__REQ_BOOTLOADER_MODE: + return "REQ_BOOTLOADER_MODE"; + case ORDER_ID__REQ_RESET: + return "REQ_RESET"; + case ORDER_ID__REQ_WRITE_GPIO: + return "REQ_WRITE_GPIO"; + case ORDER_ID__REQ_SPI: + return "REQ_SPI"; + case ORDER_ID__REQ_MULTIPLE_SPI: + return "REQ_MULTIPLE_SPI"; + default: + return "UNKNOWN"; + } +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +uint8_t cmd_get_id(const uint8_t * bytes) { + return bytes[0]; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +uint16_t cmd_get_size(const uint8_t * bytes) { + return (uint16_t)(bytes[1] << 8) | bytes[2]; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +uint8_t cmd_get_type(const uint8_t * bytes) { + return bytes[3]; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +const char * spi_status_get_str(const uint8_t status) { + switch (status) { + case SPI_STATUS_OK: + return "SPI_STATUS_OK"; + case SPI_STATUS_FAIL: + return "SPI_STATUS_FAIL"; + case SPI_STATUS_WRONG_PARAM: + return "SPI_STATUS_WRONG_PARAM"; + case SPI_STATUS_TIMEOUT: + return "SPI_STATUS_TIMEOUT"; + default: + return "SPI_STATUS_UNKNOWN"; + } +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int write_req(int fd, order_id_t cmd, const uint8_t * payload, uint16_t payload_size ) { + uint8_t buf_w[HEADER_CMD_SIZE]; + int n; + /* performances variables */ + struct timeval tm; + + /* Record function start time */ + _meas_time_start(&tm); + + /* Check input params */ + if (payload_size > MAX_SIZE_COMMAND) { + printf("ERROR: payload size exceeds maximum transfer size (req:%u, max:%d)\n", payload_size, MAX_SIZE_COMMAND); + return -1; + } + + /* Write command header */ + buf_w[0] = rand() % 255; + buf_w[1] = (uint8_t)(payload_size >> 8); /* MSB */ + buf_w[2] = (uint8_t)(payload_size >> 0); /* LSB */ + buf_w[3] = cmd; + n = write(fd, buf_w, HEADER_CMD_SIZE); + if (n < 0) { + printf("ERROR: failed to write command header to com port\n"); + return -1; + } + + /* Write command payload */ + if (payload_size > 0) { + if (payload == NULL) { + printf("ERROR: invalid payload\n"); + return -1; + } + n = write(fd, payload, payload_size); + if (n < 0) { + printf("ERROR: failed to write command payload to com port\n"); + return -1; + } + } + + DEBUG_PRINTF("\nINFO: write_req 0x%02X (%s) done, id:0x%02X\n", cmd, cmd_get_str(cmd), buf_w[0]); + +#if DEBUG_VERBOSE + int i; + for (i = 0; i < 4; i++) { + printf("%02X ", buf_w[i]); + } + for (i = 0; i < payload_size; i++) { + printf("%02X ", payload[i]); + } + printf("\n"); +#endif + + /* Compute time spent in this function */ + _meas_time_stop(5, tm, __FUNCTION__); + + return 0; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int read_ack(int fd, uint8_t * hdr, uint8_t * buf, size_t buf_size) { +#if DEBUG_VERBOSE + int i; +#endif + int n; + size_t size; + int nb_read = 0; + /* performances variables */ + struct timeval tm; + + /* Record function start time */ + _meas_time_start(&tm); + + /* Read message header first, handle EINTR as it is a blocking call */ + do { + n = read(fd, &hdr[0], (size_t)HEADER_CMD_SIZE); + } while (n == -1 && errno == EINTR); + + if (n == -1) { + perror("ERROR: Unable to read /dev/ttyACMx - "); + return -1; + } else { + DEBUG_PRINTF("INFO: read %d bytes for header from gateway\n", n); + } + + /* Compute time spent in this function */ + _meas_time_stop(5, tm, "read_ack(hdr)"); + +#if DEBUG_VERBOSE + printf("read_ack(hdr):"); + /* debug print */ + for (i = 0; i < (int)(HEADER_CMD_SIZE); i++) { + printf("%02X ", hdr[i]); + } + printf("\n"); +#endif + + /* Record function start time */ + _meas_time_start(&tm); + + /* Check if the command id is valid */ + if ((cmd_get_type(hdr) < 0x40) || (cmd_get_type(hdr) > 0x46)) { + printf("ERROR: received wrong ACK type (0x%02X)\n", cmd_get_type(hdr)); + return -1; + } + + /* Get remaining payload size (metadata + pkt payload) */ + size = (size_t)cmd_get_size(hdr); + if (size > buf_size) { + printf("ERROR: not enough memory to store all data (%zd)\n", size); + return -1; + } + + /* Read payload if any */ + if (size > 0) { + do { + /* handle EINTR as it is a blocking call */ + do { + n = read(fd, &buf[nb_read], size - nb_read); + } while (n == -1 && errno == EINTR); + + if (n == -1) { + perror("ERROR: Unable to read /dev/ttyACMx - "); + return -1; + } else { + DEBUG_PRINTF("INFO: read %d bytes from gateway\n", n); + nb_read += n; + } + } while (nb_read < (int)size); /* we want to read only the expected payload, not more */ + +#if DEBUG_VERBOSE + /* debug print */ + printf("read_ack(pld):"); + for (i = 0; i < (int)size; i++) { + printf("%02X ", buf[i]); + } + printf("\n"); +#endif + } + + /* Compute time spent in this function */ + _meas_time_stop(5, tm, "read_ack(payload)"); + + return nb_read; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int decode_ack_ping(const uint8_t * hdr, const uint8_t * payload, s_ping_info * info) { + /* sanity checks */ + if ((hdr == NULL) || (payload == NULL) || (info == NULL)) { + printf("ERROR: invalid parameter\n"); + return -1; + } + + if (cmd_get_type(hdr) != ORDER_ID__ACK_PING) { + printf("ERROR: wrong ACK type for PING (expected:0x%02X, got 0x%02X)\n", ORDER_ID__ACK_PING, cmd_get_type(hdr)); + return -1; + } + + /* payload info */ + info->unique_id_high = bytes_be_to_uint32_le(&payload[ACK_PING__UNIQUE_ID_0]); + info->unique_id_mid = bytes_be_to_uint32_le(&payload[ACK_PING__UNIQUE_ID_4]); + info->unique_id_low = bytes_be_to_uint32_le(&payload[ACK_PING__UNIQUE_ID_8]); + + memcpy(info->version, &payload[ACK_PING__VERSION_0], (sizeof info->version) - 1); + info->version[(sizeof info->version) - 1] = '\0'; /* terminate string */ + +#if DEBUG_VERBOSE + DEBUG_MSG ("## ACK_PING\n"); + DEBUG_PRINTF(" id: 0x%02X\n", cmd_get_id(hdr)); + DEBUG_PRINTF(" size: %u\n", cmd_get_size(hdr)); + DEBUG_PRINTF(" unique_id: 0x%08X%08X%08X\n", info->unique_id_high, info->unique_id_mid, info->unique_id_low); + DEBUG_PRINTF(" FW version: %s\n", info->version); +#endif + + return 0; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int decode_ack_bootloader_mode(const uint8_t * hdr) { + /* sanity checks */ + if (hdr == NULL) { + printf("ERROR: invalid parameter\n"); + return -1; + } + + if (cmd_get_type(hdr) != ORDER_ID__ACK_BOOTLOADER_MODE) { + printf("ERROR: wrong ACK type for ACK_BOOTLOADER_MODE (expected:0x%02X, got 0x%02X)\n", ORDER_ID__ACK_BOOTLOADER_MODE, cmd_get_type(hdr)); + return -1; + } + +#if DEBUG_VERBOSE + DEBUG_MSG ("## ACK_BOOTLOADER_MODE\n"); + DEBUG_PRINTF(" id: 0x%02X\n", cmd_get_id(hdr)); + DEBUG_PRINTF(" size: %u\n", cmd_get_size(hdr)); +#endif + + return 0; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int decode_ack_get_status(const uint8_t * hdr, const uint8_t * payload, s_status * status) { + int16_t temperature_sensor; + + /* sanity checks */ + if ((payload == NULL) || (status == NULL)) { + printf("ERROR: invalid parameter\n"); + return -1; + } + + if (cmd_get_type(hdr) != ORDER_ID__ACK_GET_STATUS) { + printf("ERROR: wrong ACK type for GET_STATUS (expected:0x%02X, got 0x%02X)\n", ORDER_ID__ACK_GET_STATUS, cmd_get_type(hdr)); + return -1; + } + + /* payload info */ + status->system_time_ms = bytes_be_to_uint32_le(&payload[ACK_GET_STATUS__SYSTEM_TIME_31_24]); + + temperature_sensor = (int16_t)(payload[ACK_GET_STATUS__TEMPERATURE_15_8] << 8) | + (int16_t)(payload[ACK_GET_STATUS__TEMPERATURE_7_0] << 0); + status->temperature = (float)temperature_sensor / 100.0; + + +#if DEBUG_VERBOSE + DEBUG_MSG ("## ACK_GET_STATUS\n"); + DEBUG_PRINTF(" id: 0x%02X\n", cmd_get_id(hdr)); + DEBUG_PRINTF(" size: %u\n", cmd_get_size(hdr)); + DEBUG_PRINTF(" sys_time: %u\n", status->system_time_ms); + DEBUG_PRINTF(" temperature: %.1f\n", status->temperature); +#endif + + return 0; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int decode_ack_gpio_access(const uint8_t * hdr, const uint8_t * payload, uint8_t * write_status) { + if ((hdr == NULL) || (payload == NULL) || (write_status == NULL)) { + printf("ERROR: invalid parameter\n"); + return -1; + } + + if (cmd_get_type(hdr) != ORDER_ID__ACK_WRITE_GPIO) { + printf("ERROR: wrong ACK type for WRITE_GPIO (expected:0x%02X, got 0x%02X)\n", ORDER_ID__ACK_WRITE_GPIO, cmd_get_type(hdr)); + return -1; + } + + /* payload info */ + *write_status = payload[ACK_GPIO_WRITE__STATUS]; + +#if DEBUG_VERBOSE + DEBUG_MSG ("## ACK_WRITE_GPIO\n"); + DEBUG_PRINTF(" id: 0x%02X\n", cmd_get_id(hdr)); + DEBUG_PRINTF(" size: %u\n", cmd_get_size(hdr)); + DEBUG_PRINTF(" status: %u\n", *write_status); +#endif + + return 0; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int decode_ack_spi_access(const uint8_t * hdr, const uint8_t * payload) { + /* sanity checks */ + if ((hdr == NULL) || (payload == NULL)) { + printf("ERROR: invalid parameter\n"); + return -1; + } + + if (cmd_get_type(hdr) != ORDER_ID__ACK_SPI) { + printf("ERROR: wrong ACK type for ACK_SPI (expected:0x%02X, got 0x%02X)\n", ORDER_ID__ACK_SPI, cmd_get_type(hdr)); + return -1; + } + +#if DEBUG_VERBOSE + DEBUG_MSG ("## ACK_SPI_ACCESS\n"); + DEBUG_PRINTF(" id: 0x%02X\n", cmd_get_id(hdr)); + DEBUG_PRINTF(" size: %u\n", cmd_get_size(hdr)); +#endif + + return 0; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int decode_ack_spi_bulk(const uint8_t * hdr, const uint8_t * payload) { + uint8_t req_id, req_type, req_status; + uint16_t frame_size; + int i; + + /* sanity checks */ + if ((hdr == NULL) || (payload == NULL)) { + printf("ERROR: invalid parameter\n"); + return -1; + } + + if (cmd_get_type(hdr) != ORDER_ID__ACK_MULTIPLE_SPI) { + printf("ERROR: wrong ACK type for ACK_MULTIPLE_SPI (expected:0x%02X, got 0x%02X)\n", ORDER_ID__ACK_MULTIPLE_SPI, cmd_get_type(hdr)); + return -1; + } + +#if DEBUG_VERBOSE + DEBUG_MSG ("## ACK_SPI_BULK\n"); + DEBUG_PRINTF(" id: 0x%02X\n", cmd_get_id(hdr)); + DEBUG_PRINTF(" size: %u\n", cmd_get_size(hdr)); +#endif + + i = 0; + while (i < cmd_get_size(hdr)) { + /* parse the request */ + req_id = payload[i + 0]; + req_type = payload[i + 1]; + if (req_type != MCU_SPI_REQ_TYPE_READ_WRITE && req_type != MCU_SPI_REQ_TYPE_READ_MODIFY_WRITE) { + printf("ERROR: %s: wrong type for SPI request %u (0x%02X)\n", __FUNCTION__, req_id, req_type); + return -1; + } + req_status = payload[i + 2]; + if (req_status != 0) { + /* Exit if any of the requests failed */ + printf("ERROR: %s: SPI request %u failed with %u - %s\n", __FUNCTION__, req_id, req_status, spi_status_get_str(req_status)); + return -1; + } +#if DEBUG_VERBOSE + DEBUG_PRINTF(" ----- REQ_SPI %u -----\n", req_id); + DEBUG_PRINTF(" type %s\n", (req_type == MCU_SPI_REQ_TYPE_READ_WRITE) ? "read/write" : "read-modify-write"); + DEBUG_PRINTF(" status %u\n", req_status); +#endif + /* Move to the next REQ */ + if (req_type == MCU_SPI_REQ_TYPE_READ_WRITE) { + frame_size = (uint16_t)(payload[i + 3] << 8) | (uint16_t)(payload[i + 4]); +#if DEBUG_VERBOSE + int j; + DEBUG_PRINTF(" RAW SPI frame (sz:%u): ", frame_size); + for (j = 0; j < frame_size; j++) { + DEBUG_PRINTF(" %02X", payload[i + 5 + j]); + } + DEBUG_MSG("\n"); +#endif + i += (5 + frame_size); /* REQ ACK metadata + SPI raw frame */ + } else { +#if DEBUG_VERBOSE + DEBUG_PRINTF(" read value 0x%02X\n", payload[i + 3]); + DEBUG_PRINTF(" modified value 0x%02X\n", payload[i + 4]); +#endif + i += 5; + } + } + + return 0; +} + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC FUNCTIONS DEFINITION ------------------------------------------ */ + +int mcu_ping(int fd, s_ping_info * info) { + uint8_t buf_ack[ACK_PING_SIZE]; + + CHECK_NULL(info); + + if (write_req(fd, ORDER_ID__REQ_PING, NULL, 0) != 0) { + printf("ERROR: failed to write PING request\n"); + return -1; + } + + if (read_ack(fd, buf_hdr, buf_ack, sizeof buf_ack) < 0) { + printf("ERROR: failed to read PING ack\n"); + return -1; + } + + if (decode_ack_ping(buf_hdr, buf_ack, info) != 0) { + printf("ERROR: invalid PING ack\n"); + return -1; + } + + return 0; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int mcu_boot(int fd) { + if (write_req(fd, ORDER_ID__REQ_BOOTLOADER_MODE, NULL, 0) != 0) { + printf("ERROR: failed to write BOOTLOADER_MODE request\n"); + return -1; + } + + if (read_ack(fd, buf_hdr, NULL, 0) < 0) { + printf("ERROR: failed to read BOOTLOADER_MODE ack\n"); + return -1; + } + + if (decode_ack_bootloader_mode(buf_hdr) != 0) { + printf("ERROR: invalid BOOTLOADER_MODE ack\n"); + return -1; + } + + return 0; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int mcu_get_status(int fd, s_status * status) { + uint8_t buf_ack[ACK_GET_STATUS_SIZE]; + + CHECK_NULL(status); + + if (write_req(fd, ORDER_ID__REQ_GET_STATUS, NULL, 0) != 0) { + printf("ERROR: failed to write GET_STATUS request\n"); + return -1; + } + + if (read_ack(fd, buf_hdr, buf_ack, sizeof buf_ack) < 0) { + printf("ERROR: failed to read GET_STATUS ack\n"); + return -1; + } + + if (decode_ack_get_status(buf_hdr, buf_ack, status) != 0) { + printf("ERROR: invalid GET_STATUS ack\n"); + return -1; + } + + return 0; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int mcu_gpio_write(int fd, uint8_t gpio_port, uint8_t gpio_id, uint8_t gpio_value) { + uint8_t status; + uint8_t buf_req[REQ_WRITE_GPIO_SIZE]; + uint8_t buf_ack[ACK_GPIO_WRITE_SIZE]; + + buf_req[REQ_WRITE_GPIO__PORT] = gpio_port; + buf_req[REQ_WRITE_GPIO__PIN] = gpio_id; + buf_req[REQ_WRITE_GPIO__STATE] = gpio_value; + if (write_req(fd, ORDER_ID__REQ_WRITE_GPIO, buf_req, REQ_WRITE_GPIO_SIZE) != 0) { + printf("ERROR: failed to write REQ_WRITE_GPIO request\n"); + return -1; + } + + if (read_ack(fd, buf_hdr, buf_ack, sizeof buf_ack) < 0) { + printf("ERROR: failed to read PING ack\n"); + return -1; + } + + if (decode_ack_gpio_access(buf_hdr, buf_ack, &status) != 0) { + printf("ERROR: invalid REQ_WRITE_GPIO ack\n"); + return -1; + } + + if (status != 0) { + printf("ERROR: Failed to write GPIO (port:%u id:%u value:%u)\n", gpio_port, gpio_id, gpio_value); + return -1; + } + + return 0; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int mcu_spi_write(int fd, uint8_t * in_out_buf, size_t buf_size) { + /* Check input parameters */ + CHECK_NULL(in_out_buf); + + if (write_req(fd, ORDER_ID__REQ_MULTIPLE_SPI, in_out_buf, buf_size) != 0) { + printf("ERROR: failed to write REQ_MULTIPLE_SPI request\n"); + return -1; + } + + if (read_ack(fd, buf_hdr, in_out_buf, buf_size) < 0) { + printf("ERROR: failed to read REQ_MULTIPLE_SPI ack\n"); + return -1; + } + + if (decode_ack_spi_bulk(buf_hdr, in_out_buf) != 0) { + printf("ERROR: invalid REQ_MULTIPLE_SPI ack\n"); + return -1; + } + + return 0; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int mcu_spi_store(uint8_t * in_out_buf, size_t buf_size) { + CHECK_NULL(in_out_buf); + + return spi_req_bulk_insert(&spi_bulk_buffer, in_out_buf, buf_size); +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int mcu_spi_flush(int fd) { + /* Write pending SPI requests to MCU */ + if (mcu_spi_write(fd, spi_bulk_buffer.buffer, spi_bulk_buffer.size) != 0) { + printf("ERROR: %s: failed to write SPI requests to MCU\n", __FUNCTION__); + return -1; + } + + /* Reset bulk storage buffer */ + spi_bulk_buffer.nb_req = 0; + spi_bulk_buffer.size = 0; + + return 0; +} + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/libloragw/src/loragw_reg.c b/libloragw/src/loragw_reg.c index 3117f5c9..6a7d0dfe 100644 --- a/libloragw/src/loragw_reg.c +++ b/libloragw/src/loragw_reg.c @@ -23,7 +23,6 @@ License: Revised BSD License, see LICENSE.TXT file include in the project #include /* bool type */ #include /* printf fprintf */ -#include "loragw_spi.h" #include "loragw_reg.h" /* -------------------------------------------------------------------------- */ @@ -31,8 +30,8 @@ License: Revised BSD License, see LICENSE.TXT file include in the project #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 DEBUG_MSG(str) fprintf(stdout, str) + #define DEBUG_PRINTF(fmt, args...) fprintf(stdout,"%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) @@ -1111,60 +1110,39 @@ const struct lgw_reg_s loregs[LGW_TOTALREGS+1] = { /* -------------------------------------------------------------------------- */ /* --- PRIVATE VARIABLES ---------------------------------------------------- */ -/* -------------------------------------------------------------------------- */ -/* --- INTERNAL SHARED VARIABLES -------------------------------------------- */ - -void *lgw_spi_target = NULL; /*! generic pointer to the SPI device */ - /* -------------------------------------------------------------------------- */ /* --- PRIVATE FUNCTIONS ---------------------------------------------------- */ -int reg_w_align32(void *spi_target, 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"; +int reg_w(uint8_t spi_mux_target, struct lgw_reg_s r, int32_t reg_value) { + int com_stat = LGW_REG_SUCCESS; if ((r.leng == 8) && (r.offs == 0)) { /* direct write */ - spi_stat += lgw_spi_w(spi_target, spi_mux_target, r.addr, (uint8_t)reg_value); + com_stat = lgw_com_w(spi_mux_target, r.addr, (uint8_t)reg_value); + DEBUG_PRINTF("==> DIRECT WRITE @ 0x%04X\n", r.addr); } 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_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_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_target, r.addr, buf, size_byte); /* write the register in one burst */ + /* read-modify-write */ + com_stat = lgw_com_rmw(spi_mux_target, r.addr, r.offs, r.leng, (uint8_t)reg_value); + DEBUG_PRINTF("==> READ MODIFY WRITE @ 0x%04X (offs:%u leng:%u)\n", r.addr, r.offs, r.leng); } 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; + return com_stat; } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ -int reg_r_align32(void *spi_target, uint8_t spi_mux_target, struct lgw_reg_s r, int32_t *reg_value) { - int spi_stat = LGW_SPI_SUCCESS; +int reg_r(uint8_t spi_mux_target, struct lgw_reg_s r, int32_t *reg_value) { + int com_stat = LGW_REG_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_target, r.addr, &bufu[0]); + com_stat = lgw_com_r(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) */ @@ -1173,64 +1151,43 @@ int reg_r_align32(void *spi_target, uint8_t spi_mux_target, struct lgw_reg_s r, 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_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; + return com_stat; } /* -------------------------------------------------------------------------- */ /* --- PUBLIC FUNCTIONS DEFINITION ------------------------------------------ */ /* Concentrator connect */ -int lgw_connect(const char * spidev_path) { - int spi_stat = LGW_SPI_SUCCESS; +int lgw_connect(const lgw_com_type_t com_type, const char * com_path) { + int com_stat = LGW_COM_SUCCESS; uint8_t u = 0; - /* check SPI link status */ - if (spidev_path == NULL) { - DEBUG_MSG("ERROR: SPIDEV PATH IS NOT SET\n"); + /* check COM link status */ + if (com_path == NULL) { + DEBUG_MSG("ERROR: COM PATH IS NOT SET\n"); return LGW_REG_ERROR; } - if (lgw_spi_target != NULL) { - DEBUG_MSG("WARNING: concentrator was already connected\n"); - lgw_spi_close(lgw_spi_target); - } - /* open the SPI link */ - spi_stat = lgw_spi_open(spidev_path, &lgw_spi_target); - if (spi_stat != LGW_SPI_SUCCESS) { + /* open the COM link */ + com_stat = lgw_com_open(com_type, com_path); + if (com_stat != LGW_COM_SUCCESS) { DEBUG_MSG("ERROR CONNECTING CONCENTRATOR\n"); return LGW_REG_ERROR; } /* check SX1302 version */ - spi_stat = lgw_spi_r(lgw_spi_target, LGW_SPI_MUX_TARGET_SX1302, loregs[SX1302_REG_COMMON_VERSION_VERSION].addr, &u); - if (spi_stat != LGW_SPI_SUCCESS) { + com_stat = lgw_com_r(LGW_SPI_MUX_TARGET_SX1302, loregs[SX1302_REG_COMMON_VERSION_VERSION].addr, &u); + if (com_stat != LGW_COM_SUCCESS) { DEBUG_MSG("ERROR READING CHIP VERSION REGISTER\n"); return LGW_REG_ERROR; } - if (u != loregs[SX1302_REG_COMMON_VERSION_VERSION].dflt) { - DEBUG_PRINTF("ERROR: NOT EXPECTED CHIP VERSION (v%u)\n", u); - return LGW_REG_ERROR; - } - DEBUG_PRINTF("Note: chip version is 0x%02X (v%u.%u)\n", u, (u >> 4) & 0x0F, u & 0x0F) ; + printf("Note: chip version is 0x%02X (v%u.%u)\n", u, (u >> 4) & 0x0F, u & 0x0F) ; DEBUG_MSG("Note: success connecting the concentrator\n"); return LGW_REG_SUCCESS; @@ -1240,13 +1197,14 @@ int lgw_connect(const char * spidev_path) { /* Concentrator disconnect */ int lgw_disconnect(void) { - if (lgw_spi_target != NULL) { - lgw_spi_close(lgw_spi_target); - lgw_spi_target = NULL; + int com_stat; + + com_stat = lgw_com_close(); + if (com_stat == LGW_COM_SUCCESS) { DEBUG_MSG("Note: success disconnecting the concentrator\n"); return LGW_REG_SUCCESS; } else { - DEBUG_MSG("WARNING: concentrator was already disconnected\n"); + DEBUG_MSG("ERROR: Failed to disconnect the concentrator\n"); return LGW_REG_ERROR; } } @@ -1255,7 +1213,7 @@ int lgw_disconnect(void) { /* Write to a register addressed by name */ int lgw_reg_w(uint16_t register_id, int32_t reg_value) { - int spi_stat = LGW_SPI_SUCCESS; + int com_stat = LGW_COM_SUCCESS; struct lgw_reg_s r; /* check input parameters */ @@ -1264,12 +1222,6 @@ int lgw_reg_w(uint16_t register_id, int32_t reg_value) { return LGW_REG_ERROR; } - /* check if SPI is initialised */ - if (lgw_spi_target == NULL) { - DEBUG_MSG("ERROR: CONCENTRATOR UNCONNECTED\n"); - return LGW_REG_ERROR; - } - /* get register struct from the struct array */ r = loregs[register_id]; @@ -1279,10 +1231,10 @@ int lgw_reg_w(uint16_t register_id, int32_t reg_value) { return LGW_REG_ERROR; } - spi_stat += reg_w_align32(lgw_spi_target, LGW_SPI_MUX_TARGET_SX1302, r, reg_value); + com_stat = reg_w(LGW_SPI_MUX_TARGET_SX1302, r, reg_value); - if (spi_stat != LGW_SPI_SUCCESS) { - DEBUG_MSG("ERROR: SPI ERROR DURING REGISTER WRITE\n"); + if (com_stat != LGW_COM_SUCCESS) { + DEBUG_MSG("ERROR: COM ERROR DURING REGISTER WRITE\n"); return LGW_REG_ERROR; } else { return LGW_REG_SUCCESS; @@ -1293,7 +1245,7 @@ int lgw_reg_w(uint16_t register_id, int32_t reg_value) { /* 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; + int com_stat = LGW_COM_SUCCESS; struct lgw_reg_s r; /* check input parameters */ @@ -1303,19 +1255,13 @@ int lgw_reg_r(uint16_t register_id, int32_t *reg_value) { return LGW_REG_ERROR; } - /* check if SPI is initialised */ - if (lgw_spi_target == NULL) { - DEBUG_MSG("ERROR: CONCENTRATOR UNCONNECTED\n"); - return LGW_REG_ERROR; - } - /* get register struct from the struct array */ r = loregs[register_id]; - spi_stat += reg_r_align32(lgw_spi_target, LGW_SPI_MUX_TARGET_SX1302, r, reg_value); + com_stat = reg_r(LGW_SPI_MUX_TARGET_SX1302, r, reg_value); - if (spi_stat != LGW_SPI_SUCCESS) { - DEBUG_MSG("ERROR: SPI ERROR DURING REGISTER WRITE\n"); + if (com_stat != LGW_COM_SUCCESS) { + DEBUG_MSG("ERROR: COM ERROR DURING REGISTER WRITE\n"); return LGW_REG_ERROR; } else { return LGW_REG_SUCCESS; @@ -1326,7 +1272,7 @@ int lgw_reg_r(uint16_t register_id, int32_t *reg_value) { /* 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; + int com_stat = LGW_COM_SUCCESS; struct lgw_reg_s r; /* check input parameters */ @@ -1340,12 +1286,6 @@ int lgw_reg_wb(uint16_t register_id, uint8_t *data, uint16_t size) { return LGW_REG_ERROR; } - /* check if SPI is initialised */ - if (lgw_spi_target == NULL) { - DEBUG_MSG("ERROR: CONCENTRATOR UNCONNECTED\n"); - return LGW_REG_ERROR; - } - /* get register struct from the struct array */ r = loregs[register_id]; @@ -1356,10 +1296,10 @@ int lgw_reg_wb(uint16_t register_id, uint8_t *data, uint16_t size) { } /* do the burst write */ - spi_stat += lgw_spi_wb(lgw_spi_target, LGW_SPI_MUX_TARGET_SX1302, r.addr, data, size); + com_stat = lgw_com_wb(LGW_SPI_MUX_TARGET_SX1302, r.addr, data, size); - if (spi_stat != LGW_SPI_SUCCESS) { - DEBUG_MSG("ERROR: SPI ERROR DURING REGISTER BURST WRITE\n"); + if (com_stat != LGW_COM_SUCCESS) { + DEBUG_MSG("ERROR: COM ERROR DURING REGISTER BURST WRITE\n"); return LGW_REG_ERROR; } else { return LGW_REG_SUCCESS; @@ -1370,7 +1310,7 @@ int lgw_reg_wb(uint16_t register_id, uint8_t *data, uint16_t size) { /* 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; + int com_stat = LGW_COM_SUCCESS; struct lgw_reg_s r; /* check input parameters */ @@ -1384,20 +1324,14 @@ int lgw_reg_rb(uint16_t register_id, uint8_t *data, uint16_t size) { return LGW_REG_ERROR; } - /* check if SPI is initialised */ - if (lgw_spi_target == NULL) { - DEBUG_MSG("ERROR: CONCENTRATOR UNCONNECTED\n"); - return LGW_REG_ERROR; - } - /* get register struct from the struct array */ r = loregs[register_id]; /* do the burst read */ - spi_stat += lgw_spi_rb(lgw_spi_target, LGW_SPI_MUX_TARGET_SX1302, r.addr, data, size); + com_stat = lgw_com_rb(LGW_SPI_MUX_TARGET_SX1302, r.addr, data, size); - if (spi_stat != LGW_SPI_SUCCESS) { - DEBUG_MSG("ERROR: SPI ERROR DURING REGISTER BURST READ\n"); + if (com_stat != LGW_COM_SUCCESS) { + DEBUG_MSG("ERROR: COM ERROR DURING REGISTER BURST READ\n"); return LGW_REG_ERROR; } else { return LGW_REG_SUCCESS; @@ -1407,12 +1341,12 @@ int lgw_reg_rb(uint16_t register_id, uint8_t *data, uint16_t size) { /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ int lgw_mem_wb(uint16_t mem_addr, const uint8_t *data, uint16_t size) { - int spi_stat = LGW_SPI_SUCCESS; + int com_stat = LGW_COM_SUCCESS; int chunk_cnt = 0; uint16_t addr = mem_addr; uint16_t sz_todo = size; uint16_t chunk_size; - const uint16_t CHUNK_SIZE_MAX = 1024; + const uint16_t CHUNK_SIZE_MAX = lgw_com_chunk_size(); /* check input parameters */ CHECK_NULL(data); @@ -1421,19 +1355,13 @@ int lgw_mem_wb(uint16_t mem_addr, const uint8_t *data, uint16_t size) { return LGW_REG_ERROR; } - /* check if SPI is initialised */ - if (lgw_spi_target == NULL) { - DEBUG_MSG("ERROR: CONCENTRATOR UNCONNECTED\n"); - return LGW_REG_ERROR; - } - /* write memory by chunks */ while (sz_todo > 0) { /* full or partial chunk ? */ chunk_size = (sz_todo > CHUNK_SIZE_MAX) ? CHUNK_SIZE_MAX : sz_todo; /* do the burst write */ - spi_stat += lgw_spi_wb(lgw_spi_target, LGW_SPI_MUX_TARGET_SX1302, addr, &data[chunk_cnt * CHUNK_SIZE_MAX], chunk_size); + com_stat = lgw_com_wb(LGW_SPI_MUX_TARGET_SX1302, addr, &data[chunk_cnt * CHUNK_SIZE_MAX], chunk_size); /* prepare for next write */ addr += chunk_size; @@ -1441,8 +1369,8 @@ int lgw_mem_wb(uint16_t mem_addr, const uint8_t *data, uint16_t size) { chunk_cnt += 1; } - if (spi_stat != LGW_SPI_SUCCESS) { - DEBUG_MSG("ERROR: SPI ERROR DURING REGISTER BURST WRITE\n"); + if (com_stat != LGW_COM_SUCCESS) { + DEBUG_MSG("ERROR: COM ERROR DURING REGISTER BURST WRITE\n"); return LGW_REG_ERROR; } else { return LGW_REG_SUCCESS; @@ -1452,12 +1380,12 @@ int lgw_mem_wb(uint16_t mem_addr, const uint8_t *data, uint16_t size) { /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ int lgw_mem_rb(uint16_t mem_addr, uint8_t *data, uint16_t size, bool fifo_mode) { - int spi_stat = LGW_SPI_SUCCESS; + int com_stat = LGW_COM_SUCCESS; int chunk_cnt = 0; uint16_t addr = mem_addr; uint16_t sz_todo = size; uint16_t chunk_size; - const uint16_t CHUNK_SIZE_MAX = 1024; + const uint16_t CHUNK_SIZE_MAX = lgw_com_chunk_size(); /* check input parameters */ CHECK_NULL(data); @@ -1466,19 +1394,13 @@ int lgw_mem_rb(uint16_t mem_addr, uint8_t *data, uint16_t size, bool fifo_mode) return LGW_REG_ERROR; } - /* check if SPI is initialised */ - if (lgw_spi_target == NULL) { - DEBUG_MSG("ERROR: CONCENTRATOR UNCONNECTED\n"); - return LGW_REG_ERROR; - } - /* read memory by chunks */ while (sz_todo > 0) { /* full or partial chunk ? */ chunk_size = (sz_todo > CHUNK_SIZE_MAX) ? CHUNK_SIZE_MAX : sz_todo; /* do the burst read */ - spi_stat += lgw_spi_rb(lgw_spi_target, LGW_SPI_MUX_TARGET_SX1302, addr, &data[chunk_cnt * CHUNK_SIZE_MAX], chunk_size); + com_stat = lgw_com_rb(LGW_SPI_MUX_TARGET_SX1302, addr, &data[chunk_cnt * CHUNK_SIZE_MAX], chunk_size); /* do not increment the address when the target memory is in FIFO mode (auto-increment) */ if (fifo_mode == false) { @@ -1490,8 +1412,8 @@ int lgw_mem_rb(uint16_t mem_addr, uint8_t *data, uint16_t size, bool fifo_mode) chunk_cnt += 1; } - if (spi_stat != LGW_SPI_SUCCESS) { - DEBUG_MSG("ERROR: SPI ERROR DURING REGISTER BURST READ\n"); + if (com_stat != LGW_COM_SUCCESS) { + DEBUG_MSG("ERROR: COM ERROR DURING REGISTER BURST READ\n"); return LGW_REG_ERROR; } else { return LGW_REG_SUCCESS; diff --git a/libloragw/src/loragw_spi.c b/libloragw/src/loragw_spi.c index 307c50a5..94231b7b 100644 --- a/libloragw/src/loragw_spi.c +++ b/libloragw/src/loragw_spi.c @@ -36,9 +36,9 @@ License: Revised BSD License, see LICENSE.TXT file include in the project /* --- 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) +#if DEBUG_COM == 1 + #define DEBUG_MSG(str) fprintf(stdout, str) + #define DEBUG_PRINTF(fmt, args...) fprintf(stdout,"%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) @@ -52,18 +52,21 @@ License: Revised BSD License, see LICENSE.TXT file include in the project #define READ_ACCESS 0x00 #define WRITE_ACCESS 0x80 +#define LGW_BURST_CHUNK 1024 + /* -------------------------------------------------------------------------- */ /* --- PUBLIC FUNCTIONS DEFINITION ------------------------------------------ */ /* SPI initialization and configuration */ -int lgw_spi_open(const char * spidev_path, void **spi_target_ptr) { +int lgw_spi_open(const char * com_path, void **com_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) */ + CHECK_NULL(com_path); + CHECK_NULL(com_target_ptr); /* allocate memory for the device descriptor */ spi_device = malloc(sizeof(int)); @@ -73,9 +76,9 @@ int lgw_spi_open(const char * spidev_path, void **spi_target_ptr) { } /* open SPI device */ - dev = open(spidev_path, O_RDWR); + dev = open(com_path, O_RDWR); if (dev < 0) { - DEBUG_PRINTF("ERROR: failed to open SPI device %s\n", spidev_path); + DEBUG_PRINTF("ERROR: failed to open SPI device %s\n", com_path); return LGW_SPI_ERROR; } @@ -123,7 +126,7 @@ int lgw_spi_open(const char * spidev_path, void **spi_target_ptr) { } *spi_device = dev; - *spi_target_ptr = (void *)spi_device; + *com_target_ptr = (void *)spi_device; DEBUG_MSG("Note: SPI port opened and configured ok\n"); return LGW_SPI_SUCCESS; } @@ -131,17 +134,17 @@ int lgw_spi_open(const char * spidev_path, void **spi_target_ptr) { /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ /* SPI release */ -int lgw_spi_close(void *spi_target) { +int lgw_spi_close(void *com_target) { int spi_device; int a; /* check input variables */ - CHECK_NULL(spi_target); + CHECK_NULL(com_target); /* close file & deallocate file descriptor */ - spi_device = *(int *)spi_target; /* must check that spi_target is not null beforehand */ + spi_device = *(int *)com_target; /* must check that spi_target is not null beforehand */ a = close(spi_device); - free(spi_target); + free(com_target); /* determine return code */ if (a < 0) { @@ -156,7 +159,7 @@ int lgw_spi_close(void *spi_target) { /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ /* Simple write */ -int lgw_spi_w(void *spi_target, uint8_t spi_mux_target, uint16_t address, uint8_t data) { +int lgw_spi_w(void *com_target, uint8_t spi_mux_target, uint16_t address, uint8_t data) { int spi_device; uint8_t out_buf[4]; uint8_t command_size; @@ -164,9 +167,9 @@ int lgw_spi_w(void *spi_target, uint8_t spi_mux_target, uint16_t address, uint8_ int a; /* check input variables */ - CHECK_NULL(spi_target); + CHECK_NULL(com_target); - spi_device = *(int *)spi_target; /* must check that spi_target is not null beforehand */ + spi_device = *(int *)com_target; /* must check that spi_target is not null beforehand */ /* prepare frame to be sent */ out_buf[0] = spi_mux_target; @@ -197,7 +200,7 @@ int lgw_spi_w(void *spi_target, uint8_t spi_mux_target, uint16_t address, uint8_ /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ /* Simple read */ -int lgw_spi_r(void *spi_target, uint8_t spi_mux_target, uint16_t address, uint8_t *data) { +int lgw_spi_r(void *com_target, uint8_t spi_mux_target, uint16_t address, uint8_t *data) { int spi_device; uint8_t out_buf[5]; uint8_t command_size; @@ -206,10 +209,10 @@ int lgw_spi_r(void *spi_target, uint8_t spi_mux_target, uint16_t address, uint8_ int a; /* check input variables */ - CHECK_NULL(spi_target); + CHECK_NULL(com_target); CHECK_NULL(data); - spi_device = *(int *)spi_target; /* must check that spi_target is not null beforehand */ + spi_device = *(int *)com_target; /* must check that com_target is not null beforehand */ /* prepare frame to be sent */ out_buf[0] = spi_mux_target; @@ -240,8 +243,29 @@ int lgw_spi_r(void *spi_target, uint8_t spi_mux_target, uint16_t address, uint8_ /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ +/* Single Byte Read-Modify-Write */ +int lgw_spi_rmw(void *com_target, uint8_t spi_mux_target, uint16_t address, uint8_t offs, uint8_t leng, uint8_t data) { + int spi_stat = LGW_SPI_SUCCESS; + uint8_t buf[4] = "\x00\x00\x00\x00"; + + /* Read */ + spi_stat += lgw_spi_r(com_target, spi_mux_target, address, &buf[0]); + + /* Modify */ + buf[1] = ((1 << leng) - 1) << offs; /* bit mask */ + buf[2] = ((uint8_t)data) << offs; /* new data offsetted */ + buf[3] = (~buf[1] & buf[0]) | (buf[1] & buf[2]); /* mixing old & new data */ + + /* Write */ + spi_stat += lgw_spi_w(com_target, spi_mux_target, address, buf[3]); + + return spi_stat; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + /* Burst (multiple-byte) write */ -int lgw_spi_wb(void *spi_target, uint8_t spi_mux_target, uint16_t address, const uint8_t *data, uint16_t size) { +int lgw_spi_wb(void *com_target, uint8_t spi_mux_target, uint16_t address, const uint8_t *data, uint16_t size) { int spi_device; uint8_t command[3]; uint8_t command_size; @@ -251,14 +275,14 @@ int lgw_spi_wb(void *spi_target, uint8_t spi_mux_target, uint16_t address, const int i; /* check input parameters */ - CHECK_NULL(spi_target); + CHECK_NULL(com_target); 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 */ + spi_device = *(int *)com_target; /* must check that com_target is not null beforehand */ /* prepare command byte */ command[0] = spi_mux_target; @@ -296,7 +320,7 @@ int lgw_spi_wb(void *spi_target, uint8_t spi_mux_target, uint16_t address, const /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ /* Burst (multiple-byte) read */ -int lgw_spi_rb(void *spi_target, uint8_t spi_mux_target, uint16_t address, uint8_t *data, uint16_t size) { +int lgw_spi_rb(void *com_target, uint8_t spi_mux_target, uint16_t address, uint8_t *data, uint16_t size) { int spi_device; uint8_t command[4]; uint8_t command_size; @@ -306,14 +330,14 @@ int lgw_spi_rb(void *spi_target, uint8_t spi_mux_target, uint16_t address, uint8 int i; /* check input parameters */ - CHECK_NULL(spi_target); + CHECK_NULL(com_target); 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 */ + spi_device = *(int *)com_target; /* must check that com_target is not null beforehand */ /* prepare command byte */ command[0] = spi_mux_target; @@ -349,4 +373,10 @@ int lgw_spi_rb(void *spi_target, uint8_t spi_mux_target, uint16_t address, uint8 } } +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +uint16_t lgw_spi_chunk_size(void) { + return (uint16_t)LGW_BURST_CHUNK; +} + /* --- EOF ------------------------------------------------------------------ */ diff --git a/libloragw/src/loragw_stts751.c b/libloragw/src/loragw_stts751.c index c417dc20..7766961c 100644 --- a/libloragw/src/loragw_stts751.c +++ b/libloragw/src/loragw_stts751.c @@ -28,8 +28,8 @@ License: Revised BSD License, see LICENSE.TXT file include in the project #define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) #if DEBUG_I2C == 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(stdout, str) + #define DEBUG_PRINTF(fmt, args...) fprintf(stdout,"%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) @@ -71,9 +71,6 @@ License: Revised BSD License, see LICENSE.TXT file include in the project /* -------------------------------------------------------------------------- */ /* --- PRIVATE VARIABLES ---------------------------------------------------- */ -/* -------------------------------------------------------------------------- */ -/* --- INTERNAL SHARED VARIABLES -------------------------------------------- */ - /* -------------------------------------------------------------------------- */ /* --- PRIVATE FUNCTIONS ---------------------------------------------------- */ diff --git a/libloragw/src/loragw_sx1250.c b/libloragw/src/loragw_sx1250.c index 24246f4c..3bc808a6 100644 --- a/libloragw/src/loragw_sx1250.c +++ b/libloragw/src/loragw_sx1250.c @@ -18,146 +18,83 @@ License: Revised BSD License, see LICENSE.TXT file include in the project #include /* C99 types */ #include /* printf fprintf */ -#include /* malloc free */ -#include /* lseek, close */ -#include /* open */ -#include /* memset */ -#include -#include - -#include "loragw_spi.h" -#include "loragw_reg.h" -#include "loragw_aux.h" #include "loragw_sx1250.h" +#include "loragw_com.h" +#include "loragw_aux.h" +#include "loragw_reg.h" +#include "loragw_hal.h" + +#include "sx1250_com.h" /* -------------------------------------------------------------------------- */ /* --- PRIVATE MACROS ------------------------------------------------------- */ #define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) #if DEBUG_RAD == 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;} + #define DEBUG_MSG(str) fprintf(stdout, str) + #define DEBUG_PRINTF(fmt, args...) fprintf(stdout,"%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_SPI_ERROR;} + #define CHECK_NULL(a) if(a==NULL){return LGW_REG_ERROR;} #endif -#define SX1250_FREQ_TO_REG(f) (uint32_t)((uint64_t)f * (1 << 25) / 32000000U) - /* -------------------------------------------------------------------------- */ /* --- PRIVATE CONSTANTS ---------------------------------------------------- */ -#define WAIT_BUSY_SX1250_MS 1 - -/* -------------------------------------------------------------------------- */ -/* --- INTERNAL SHARED VARIABLES -------------------------------------------- */ - -extern void *lgw_spi_target; /*! generic pointer to the SPI device */ - /* -------------------------------------------------------------------------- */ /* --- PUBLIC FUNCTIONS DEFINITION ------------------------------------------ */ -int sx1250_write_command(uint8_t rf_chain, sx1250_op_code_t op_code, uint8_t *data, uint16_t size) { - int spi_device; - int cmd_size = 2; /* header + op_code */ - uint8_t out_buf[cmd_size + size]; - uint8_t command_size; - struct spi_ioc_transfer k; - int a, i; - - /* wait BUSY */ - wait_ms(WAIT_BUSY_SX1250_MS); +int sx1250_reg_w(sx1250_op_code_t op_code, uint8_t *data, uint16_t size, uint8_t rf_chain) { + int com_stat; - /* check input variables */ - CHECK_NULL(lgw_spi_target); + /* checking input parameters */ + if (rf_chain >= LGW_RF_CHAIN_NB) { + DEBUG_MSG("ERROR: INVALID RF_CHAIN\n"); + return LGW_REG_ERROR; + } - spi_device = *(int *)lgw_spi_target; /* must check that spi_target is not null beforehand */ + com_stat = sx1250_com_w(lgw_com_type(), lgw_com_target(), ((rf_chain == 0) ? LGW_SPI_MUX_TARGET_RADIOA : LGW_SPI_MUX_TARGET_RADIOB), op_code, data, size); - /* prepare frame to be sent */ - out_buf[0] = (rf_chain == 0) ? LGW_SPI_MUX_TARGET_RADIOA : LGW_SPI_MUX_TARGET_RADIOB; - out_buf[1] = (uint8_t)op_code; - for(i = 0; i < (int)size; i++) { - out_buf[cmd_size + i] = data[i]; - } - command_size = cmd_size + size; - - /* I/O transaction */ - memset(&k, 0, sizeof(k)); /* clear k */ - k.tx_buf = (unsigned long) out_buf; - k.len = command_size; - k.speed_hz = SPI_SPEED; - k.cs_change = 0; - 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; + if (com_stat != LGW_COM_SUCCESS) { + DEBUG_MSG("ERROR: COM ERROR DURING RADIO REGISTER WRITE\n"); + return LGW_REG_ERROR; } else { - DEBUG_MSG("Note: SPI write success\n"); - return LGW_SPI_SUCCESS; + return LGW_REG_SUCCESS; } } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ -int sx1250_read_command(uint8_t rf_chain, sx1250_op_code_t op_code, uint8_t *data, uint16_t size) { - int spi_device; - int cmd_size = 2; /* header + op_code + NOP */ - uint8_t out_buf[cmd_size + size]; - uint8_t command_size; - uint8_t in_buf[ARRAY_SIZE(out_buf)]; - struct spi_ioc_transfer k; - int a, i; - - /* wait BUSY */ - wait_ms(WAIT_BUSY_SX1250_MS); - - /* check input variables */ - CHECK_NULL(lgw_spi_target); - CHECK_NULL(data); - - spi_device = *(int *)lgw_spi_target; /* must check that spi_target is not null beforehand */ - - /* prepare frame to be sent */ - out_buf[0] = (rf_chain == 0) ? LGW_SPI_MUX_TARGET_RADIOA : LGW_SPI_MUX_TARGET_RADIOB; - out_buf[1] = (uint8_t)op_code; - for(i = 0; i < (int)size; i++) { - out_buf[cmd_size + i] = data[i]; +int sx1250_reg_r(sx1250_op_code_t op_code, uint8_t *data, uint16_t size, uint8_t rf_chain) { + int com_stat; + + /* checking input parameters */ + if (rf_chain >= LGW_RF_CHAIN_NB) { + DEBUG_MSG("ERROR: INVALID RF_CHAIN\n"); + return LGW_REG_ERROR; } - command_size = cmd_size + size; - - /* 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 = command_size; - k.cs_change = 0; - 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; + + com_stat = sx1250_com_r(lgw_com_type(), lgw_com_target(), ((rf_chain == 0) ? LGW_SPI_MUX_TARGET_RADIOA : LGW_SPI_MUX_TARGET_RADIOB), op_code, data, size); + + if (com_stat != LGW_COM_SUCCESS) { + DEBUG_MSG("ERROR: COM ERROR DURING RADIO REGISTER READ\n"); + return LGW_REG_ERROR; } else { - DEBUG_MSG("Note: SPI read success\n"); - //*data = in_buf[command_size - 1]; - memcpy(data, in_buf + cmd_size, size); - return LGW_SPI_SUCCESS; + return LGW_REG_SUCCESS; } } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ int sx1250_calibrate(uint8_t rf_chain, uint32_t freq_hz) { + int err = LGW_REG_SUCCESS; uint8_t buff[16]; buff[0] = 0x00; - sx1250_read_command(rf_chain, GET_STATUS, buff, 1); + err |= sx1250_reg_r(GET_STATUS, buff, 1, rf_chain); /* Run calibration */ if ((freq_hz > 430E6) && (freq_hz < 440E6)) { @@ -177,9 +114,9 @@ int sx1250_calibrate(uint8_t rf_chain, uint32_t freq_hz) { buff[1] = 0xE9; } else { printf("ERROR: failed to calibrate sx1250 radio, frequency range not supported (%u)\n", freq_hz); - return -1; + return LGW_REG_ERROR; } - sx1250_write_command(rf_chain, CALIBRATE_IMAGE, buff, 2); + err |= sx1250_reg_w(CALIBRATE_IMAGE, buff, 2, rf_chain); /* Wait for calibration to complete */ wait_ms(10); @@ -187,13 +124,13 @@ int sx1250_calibrate(uint8_t rf_chain, uint32_t freq_hz) { buff[0] = 0x00; buff[1] = 0x00; buff[2] = 0x00; - sx1250_read_command(rf_chain, GET_DEVICE_ERRORS, buff, 3); + err |= sx1250_reg_r(GET_DEVICE_ERRORS, buff, 3, rf_chain); if (TAKE_N_BITS_FROM(buff[2], 4, 1) != 0) { printf("ERROR: sx1250 Image Calibration Error\n"); - return -1; + return LGW_REG_ERROR; } - return 0; + return err; } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ @@ -201,79 +138,80 @@ int sx1250_calibrate(uint8_t rf_chain, uint32_t freq_hz) { int sx1250_setup(uint8_t rf_chain, uint32_t freq_hz, bool single_input_mode) { int32_t freq_reg; uint8_t buff[16]; + int err = LGW_REG_SUCCESS; /* Set Radio in Standby for calibrations */ buff[0] = (uint8_t)STDBY_RC; - sx1250_write_command(rf_chain, SET_STANDBY, buff, 1); + err |= sx1250_reg_w(SET_STANDBY, buff, 1, rf_chain); wait_ms(10); /* Get status to check Standby mode has been properly set */ buff[0] = 0x00; - sx1250_read_command(rf_chain, GET_STATUS, buff, 1); + err |= sx1250_reg_r(GET_STATUS, buff, 1, rf_chain); if ((uint8_t)(TAKE_N_BITS_FROM(buff[0], 4, 3)) != 0x02) { printf("ERROR: Failed to set SX1250_%u in STANDBY_RC mode\n", rf_chain); - return -1; + return LGW_REG_ERROR; } /* Run all calibrations (TCXO) */ buff[0] = 0x7F; - sx1250_write_command(rf_chain, CALIBRATE, buff, 1); + err |= sx1250_reg_w(CALIBRATE, buff, 1, rf_chain); wait_ms(10); /* Set Radio in Standby with XOSC ON */ buff[0] = (uint8_t)STDBY_XOSC; - sx1250_write_command(rf_chain, SET_STANDBY, buff, 1); + err |= sx1250_reg_w(SET_STANDBY, buff, 1, rf_chain); wait_ms(10); /* Get status to check Standby mode has been properly set */ buff[0] = 0x00; - sx1250_read_command(rf_chain, GET_STATUS, buff, 1); + err |= sx1250_reg_r(GET_STATUS, buff, 1, rf_chain); if ((uint8_t)(TAKE_N_BITS_FROM(buff[0], 4, 3)) != 0x03) { printf("ERROR: Failed to set SX1250_%u in STANDBY_XOSC mode\n", rf_chain); - return -1; + return LGW_REG_ERROR; } /* Set Bitrate to maximum (to lower TX to FS switch time) */ buff[0] = 0x06; buff[1] = 0xA1; buff[2] = 0x01; - sx1250_write_command(rf_chain, WRITE_REGISTER, buff, 3); + err |= sx1250_reg_w(WRITE_REGISTER, buff, 3, rf_chain); buff[0] = 0x06; buff[1] = 0xA2; buff[2] = 0x00; - sx1250_write_command(rf_chain, WRITE_REGISTER, buff, 3); + err |= sx1250_reg_w(WRITE_REGISTER, buff, 3, rf_chain); buff[0] = 0x06; buff[1] = 0xA3; buff[2] = 0x00; - sx1250_write_command(rf_chain, WRITE_REGISTER, buff, 3); + err |= sx1250_reg_w(WRITE_REGISTER, buff, 3, rf_chain); /* Configure DIO for Rx */ buff[0] = 0x05; buff[1] = 0x82; buff[2] = 0x00; - sx1250_write_command(rf_chain, WRITE_REGISTER, buff, 3); /* Drive strength to min */ + err |= sx1250_reg_w(WRITE_REGISTER, buff, 3, rf_chain); /* Drive strength to min */ buff[0] = 0x05; buff[1] = 0x83; buff[2] = 0x00; - sx1250_write_command(rf_chain, WRITE_REGISTER, buff, 3); /* Input enable, all disabled */ + err |= sx1250_reg_w(WRITE_REGISTER, buff, 3, rf_chain); /* Input enable, all disabled */ buff[0] = 0x05; buff[1] = 0x84; buff[2] = 0x00; - sx1250_write_command(rf_chain, WRITE_REGISTER, buff, 3); /* No pull up */ + err |= sx1250_reg_w(WRITE_REGISTER, buff, 3, rf_chain); /* No pull up */ buff[0] = 0x05; buff[1] = 0x85; buff[2] = 0x00; - sx1250_write_command(rf_chain, WRITE_REGISTER, buff, 3); /* No pull down */ + err |= sx1250_reg_w(WRITE_REGISTER, buff, 3, rf_chain); /* No pull down */ buff[0] = 0x05; buff[1] = 0x80; buff[2] = 0x00; - sx1250_write_command(rf_chain, WRITE_REGISTER, buff, 3); /* Output enable, all enabled */ + err |= sx1250_reg_w(WRITE_REGISTER, buff, 3, rf_chain); /* Output enable, all enabled */ /* Set fix gain (??) */ buff[0] = 0x08; buff[1] = 0xB6; buff[2] = 0x2A; - sx1250_write_command(rf_chain, WRITE_REGISTER, buff, 3); + err |= sx1250_reg_w(WRITE_REGISTER, buff, 3, rf_chain); /* Set frequency */ freq_reg = SX1250_FREQ_TO_REG(freq_hz); @@ -281,7 +219,7 @@ int sx1250_setup(uint8_t rf_chain, uint32_t freq_hz, bool single_input_mode) { buff[1] = (uint8_t)(freq_reg >> 16); buff[2] = (uint8_t)(freq_reg >> 8); buff[3] = (uint8_t)(freq_reg >> 0); - sx1250_write_command(rf_chain, SET_RF_FREQUENCY, buff, 4); + err |= sx1250_reg_w(SET_RF_FREQUENCY, buff, 4, rf_chain); /* Set frequency offset to 0 */ buff[0] = 0x08; @@ -289,13 +227,13 @@ int sx1250_setup(uint8_t rf_chain, uint32_t freq_hz, bool single_input_mode) { buff[2] = 0x00; buff[3] = 0x00; buff[4] = 0x00; - sx1250_write_command(rf_chain, WRITE_REGISTER, buff, 5); + err |= sx1250_reg_w(WRITE_REGISTER, buff, 5, rf_chain); /* Set Radio in Rx mode, necessary to give a clock to SX1302 */ buff[0] = 0xFF; buff[1] = 0xFF; buff[2] = 0xFF; - sx1250_write_command(rf_chain, SET_RX, buff, 3); /* Rx Continuous */ + err |= sx1250_reg_w(SET_RX, buff, 3, rf_chain); /* Rx Continuous */ /* Select single input or differential input mode */ if (single_input_mode == true) { @@ -303,15 +241,21 @@ int sx1250_setup(uint8_t rf_chain, uint32_t freq_hz, bool single_input_mode) { buff[0] = 0x08; buff[1] = 0xE2; buff[2] = 0x0D; - sx1250_write_command(rf_chain, WRITE_REGISTER, buff, 3); + err |= sx1250_reg_w(WRITE_REGISTER, buff, 3, rf_chain); } buff[0] = 0x05; buff[1] = 0x87; buff[2] = 0x0B; - sx1250_write_command(rf_chain, WRITE_REGISTER, buff, 3); /* FPGA_MODE_RX */ + err |= sx1250_reg_w(WRITE_REGISTER, buff, 3, rf_chain); /* FPGA_MODE_RX */ + + /* Check if something went wrong */ + if (err != LGW_REG_SUCCESS) { + printf("ERROR: failed to setup SX1250_%u radio\n", rf_chain); + return LGW_REG_ERROR; + } - return 0; + return LGW_REG_SUCCESS; } /* --- EOF ------------------------------------------------------------------ */ diff --git a/libloragw/src/loragw_sx125x.c b/libloragw/src/loragw_sx125x.c index 43deebb4..271d6682 100644 --- a/libloragw/src/loragw_sx125x.c +++ b/libloragw/src/loragw_sx125x.c @@ -18,12 +18,10 @@ License: Revised BSD License, see LICENSE.TXT file include in the project #include /* C99 types */ #include /* bool type */ #include /* printf fprintf */ -#include /* memset */ -#include -#include +#include "sx125x_com.h" #include "loragw_sx125x.h" -#include "loragw_spi.h" +#include "loragw_com.h" #include "loragw_aux.h" #include "loragw_reg.h" #include "loragw_hal.h" @@ -33,8 +31,8 @@ License: Revised BSD License, see LICENSE.TXT file include in the project #define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) #if DEBUG_RAD == 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(stdout, str) + #define DEBUG_PRINTF(fmt, args...) fprintf(stdout,"%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) @@ -50,9 +48,6 @@ License: Revised BSD License, see LICENSE.TXT file include in the project #define PLL_LOCK_MAX_ATTEMPTS 5 -#define READ_ACCESS 0x00 -#define WRITE_ACCESS 0x80 - static const struct radio_reg_s sx125x_regs[RADIO_TOTALREGS] = { {0,0,8}, /* MODE */ {0,3,1}, /* MODE__PA_DRIVER_EN */ @@ -110,95 +105,15 @@ static const struct radio_reg_s sx125x_regs[RADIO_TOTALREGS] = { /* -------------------------------------------------------------------------- */ /* --- PRIVATE VARIABLES ---------------------------------------------------- */ -extern void *lgw_spi_target; /*! generic pointer to the SPI device */ - /* -------------------------------------------------------------------------- */ /* --- PRIVATE FUNCTIONS ---------------------------------------------------- */ -/* Simple read */ -int sx125x_reg_r(void *spi_target, uint8_t spi_mux_target, uint8_t address, uint8_t *data) { - int spi_device; - uint8_t out_buf[3]; - uint8_t command_size; - uint8_t in_buf[ARRAY_SIZE(out_buf)]; - struct spi_ioc_transfer k; - int a; - - /* check input variables */ - CHECK_NULL(spi_target); - 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_target; - out_buf[1] = READ_ACCESS | (address & 0x7F); - out_buf[2] = 0x00; - command_size = 3; - - /* 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 = command_size; - k.cs_change = 0; - 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[command_size - 1]; - return LGW_SPI_SUCCESS; - } -} - -/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ - -int sx125x_reg_w(void *spi_target, uint8_t spi_mux_target, uint8_t address, uint8_t data) { - int spi_device; - uint8_t out_buf[3]; - uint8_t command_size; - struct spi_ioc_transfer k; - int a; - - /* check input variables */ - CHECK_NULL(spi_target); - - spi_device = *(int *)spi_target; /* must check that spi_target is not null beforehand */ - - /* prepare frame to be sent */ - out_buf[0] = spi_mux_target; - out_buf[1] = WRITE_ACCESS | (address & 0x7F); - out_buf[2] = data; - command_size = 3; - - /* I/O transaction */ - memset(&k, 0, sizeof(k)); /* clear k */ - k.tx_buf = (unsigned long) out_buf; - k.len = command_size; - k.speed_hz = SPI_SPEED; - k.cs_change = 0; - 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; - } -} - -/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC FUNCTIONS DEFINITION ------------------------------------------ */ -int lgw_sx125x_reg_w(radio_reg_t idx, uint8_t data, uint8_t rf_chain) { +int sx125x_reg_w(radio_reg_t idx, uint8_t data, uint8_t rf_chain) { - int spi_stat; + int com_stat; struct radio_reg_s reg; uint8_t mask; uint8_t r; @@ -219,24 +134,24 @@ int lgw_sx125x_reg_w(radio_reg_t idx, uint8_t data, uint8_t rf_chain) { if ((reg.leng == 8) && (reg.offs == 0)){ /* direct write */ - spi_stat = sx125x_reg_w(lgw_spi_target, ((rf_chain == 0) ? LGW_SPI_MUX_TARGET_RADIOA : LGW_SPI_MUX_TARGET_RADIOB), reg.addr, data); + com_stat = sx125x_com_w(lgw_com_type(), lgw_com_target(), ((rf_chain == 0) ? LGW_SPI_MUX_TARGET_RADIOA : LGW_SPI_MUX_TARGET_RADIOB), reg.addr, data); } else { /* read-modify-write */ - spi_stat = sx125x_reg_r(lgw_spi_target, ((rf_chain == 0) ? LGW_SPI_MUX_TARGET_RADIOA : LGW_SPI_MUX_TARGET_RADIOB), reg.addr, &r); + com_stat = sx125x_com_r(lgw_com_type(), lgw_com_target(), ((rf_chain == 0) ? LGW_SPI_MUX_TARGET_RADIOA : LGW_SPI_MUX_TARGET_RADIOB), reg.addr, &r); mask = ((1 << reg.leng) - 1) << reg.offs; w = (r & ~mask) | ((data << reg.offs) & mask); - spi_stat |= sx125x_reg_w(lgw_spi_target, ((rf_chain == 0) ? LGW_SPI_MUX_TARGET_RADIOA : LGW_SPI_MUX_TARGET_RADIOB), reg.addr, w); + com_stat |= sx125x_com_w(lgw_com_type(), lgw_com_target(), ((rf_chain == 0) ? LGW_SPI_MUX_TARGET_RADIOA : LGW_SPI_MUX_TARGET_RADIOB), reg.addr, w); } /* Check that we can read what we have written */ - lgw_sx125x_reg_r(idx, &val_check, rf_chain); + sx125x_reg_r(idx, &val_check, rf_chain); if (val_check != data) { printf("ERROR: sx125x register %d write failed (w:%u r:%u)!!\n", idx, data, val_check); - spi_stat = LGW_SPI_ERROR; + com_stat = LGW_COM_ERROR; } - if (spi_stat != LGW_SPI_SUCCESS) { - DEBUG_MSG("ERROR: SPI ERROR DURING RADIO REGISTER WRITE\n"); + if (com_stat != LGW_COM_SUCCESS) { + DEBUG_MSG("ERROR: COM ERROR DURING RADIO REGISTER WRITE\n"); return LGW_REG_ERROR; } else { return LGW_REG_SUCCESS; @@ -245,9 +160,9 @@ int lgw_sx125x_reg_w(radio_reg_t idx, uint8_t data, uint8_t rf_chain) { /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ -int lgw_sx125x_reg_r(radio_reg_t idx, uint8_t *data, uint8_t rf_chain) { +int sx125x_reg_r(radio_reg_t idx, uint8_t *data, uint8_t rf_chain) { - int spi_stat; + int com_stat; struct radio_reg_s reg; uint8_t mask; uint8_t r; @@ -264,23 +179,19 @@ int lgw_sx125x_reg_r(radio_reg_t idx, uint8_t *data, uint8_t rf_chain) { reg = sx125x_regs[idx]; - spi_stat = sx125x_reg_r(lgw_spi_target, ((rf_chain == 0) ? LGW_SPI_MUX_TARGET_RADIOA : LGW_SPI_MUX_TARGET_RADIOB), reg.addr, &r); + com_stat = sx125x_com_r(lgw_com_type(), lgw_com_target(), ((rf_chain == 0) ? LGW_SPI_MUX_TARGET_RADIOA : LGW_SPI_MUX_TARGET_RADIOB), reg.addr, &r); mask = ((1 << reg.leng) - 1) << reg.offs; *data = (r & mask) >> reg.offs; - if (spi_stat != LGW_SPI_SUCCESS) { - DEBUG_MSG("ERROR: SPI ERROR DURING RADIO REGISTER READ\n"); + if (com_stat != LGW_COM_SUCCESS) { + DEBUG_MSG("ERROR: COM ERROR DURING RADIO REGISTER READ\n"); return LGW_REG_ERROR; } else { return LGW_REG_SUCCESS; } } -/* -------------------------------------------------------------------------- */ -/* --- PRIVATE FUNCTIONS DEFINITION ----------------------------------------- */ - -/* -------------------------------------------------------------------------- */ -/* --- PUBLIC FUNCTIONS DEFINITION ------------------------------------------ */ +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ int sx125x_setup(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; @@ -294,26 +205,26 @@ int sx125x_setup(uint8_t rf_chain, uint8_t rf_clkout, bool rf_enable, uint8_t rf } /* Get version to identify SX1255/57 silicon revision */ - lgw_sx125x_reg_r(SX125x_REG_VERSION, &val, rf_chain); + sx125x_reg_r(SX125x_REG_VERSION, &val, rf_chain); DEBUG_PRINTF("Note: SX125x #%d version register returned 0x%02x\n", rf_chain, val); /* General radio setup */ if (rf_clkout == rf_chain) { - lgw_sx125x_reg_w(SX125x_REG_CLK_SELECT, SX125x_TX_DAC_CLK_SEL + 2, rf_chain); + sx125x_reg_w(SX125x_REG_CLK_SELECT, SX125x_TX_DAC_CLK_SEL + 2, rf_chain); DEBUG_PRINTF("Note: SX125x #%d clock output enabled\n", rf_chain); } else { - lgw_sx125x_reg_w(SX125x_REG_CLK_SELECT, SX125x_TX_DAC_CLK_SEL, rf_chain); + sx125x_reg_w(SX125x_REG_CLK_SELECT, SX125x_TX_DAC_CLK_SEL, rf_chain); DEBUG_PRINTF("Note: SX125x #%d clock output disabled\n", rf_chain); } switch (rf_radio_type) { case LGW_RADIO_TYPE_SX1255: - lgw_sx125x_reg_w(SX125x_REG_SX1255_XOSC_TEST__GM_STARTUP, SX125x_XOSC_GM_STARTUP, rf_chain); - lgw_sx125x_reg_w(SX125x_REG_SX1255_XOSC_TEST__DISABLE, SX125x_XOSC_DISABLE, rf_chain); + sx125x_reg_w(SX125x_REG_SX1255_XOSC_TEST__GM_STARTUP, SX125x_XOSC_GM_STARTUP, rf_chain); + sx125x_reg_w(SX125x_REG_SX1255_XOSC_TEST__DISABLE, SX125x_XOSC_DISABLE, rf_chain); break; case LGW_RADIO_TYPE_SX1257: - lgw_sx125x_reg_w(SX125x_REG_SX1257_XOSC_TEST__GM_STARTUP, SX125x_XOSC_GM_STARTUP, rf_chain); - lgw_sx125x_reg_w(SX125x_REG_SX1257_XOSC_TEST__DISABLE, SX125x_XOSC_DISABLE, rf_chain); + sx125x_reg_w(SX125x_REG_SX1257_XOSC_TEST__GM_STARTUP, SX125x_XOSC_GM_STARTUP, rf_chain); + sx125x_reg_w(SX125x_REG_SX1257_XOSC_TEST__DISABLE, SX125x_XOSC_DISABLE, rf_chain); break; default: DEBUG_PRINTF("ERROR: UNEXPECTED VALUE %d FOR RADIO TYPE\n", rf_radio_type); @@ -322,25 +233,25 @@ int sx125x_setup(uint8_t rf_chain, uint8_t rf_clkout, bool rf_enable, uint8_t rf if (rf_enable == true) { /* Tx gain and trim */ - lgw_sx125x_reg_w(SX125x_REG_TX_GAIN__MIX_GAIN, SX125x_TX_MIX_GAIN, rf_chain); - lgw_sx125x_reg_w(SX125x_REG_TX_GAIN__DAC_GAIN, SX125x_TX_DAC_GAIN, rf_chain); + sx125x_reg_w(SX125x_REG_TX_GAIN__MIX_GAIN, SX125x_TX_MIX_GAIN, rf_chain); + sx125x_reg_w(SX125x_REG_TX_GAIN__DAC_GAIN, SX125x_TX_DAC_GAIN, rf_chain); - lgw_sx125x_reg_w(SX125x_REG_TX_BW__ANA_BW, SX125x_TX_ANA_BW, rf_chain); - lgw_sx125x_reg_w(SX125x_REG_TX_BW__PLL_BW, SX125x_TX_PLL_BW, rf_chain); + sx125x_reg_w(SX125x_REG_TX_BW__ANA_BW, SX125x_TX_ANA_BW, rf_chain); + sx125x_reg_w(SX125x_REG_TX_BW__PLL_BW, SX125x_TX_PLL_BW, rf_chain); - lgw_sx125x_reg_w(SX125x_REG_TX_DAC_BW, SX125x_TX_DAC_BW, rf_chain); + sx125x_reg_w(SX125x_REG_TX_DAC_BW, SX125x_TX_DAC_BW, rf_chain); /* Rx gain and trim */ - lgw_sx125x_reg_w(SX125x_REG_RX_ANA_GAIN__LNA_ZIN, SX125x_LNA_ZIN, rf_chain); - lgw_sx125x_reg_w(SX125x_REG_RX_ANA_GAIN__BB_GAIN, SX125x_RX_BB_GAIN, rf_chain); - lgw_sx125x_reg_w(SX125x_REG_RX_ANA_GAIN__LNA_GAIN, SX125x_RX_LNA_GAIN, rf_chain); + sx125x_reg_w(SX125x_REG_RX_ANA_GAIN__LNA_ZIN, SX125x_LNA_ZIN, rf_chain); + sx125x_reg_w(SX125x_REG_RX_ANA_GAIN__BB_GAIN, SX125x_RX_BB_GAIN, rf_chain); + sx125x_reg_w(SX125x_REG_RX_ANA_GAIN__LNA_GAIN, SX125x_RX_LNA_GAIN, rf_chain); - lgw_sx125x_reg_w(SX125x_REG_RX_BW__BB_BW, SX125x_RX_BB_BW, rf_chain); - lgw_sx125x_reg_w(SX125x_REG_RX_BW__ADC_TRIM, SX125x_RX_ADC_TRIM, rf_chain); - lgw_sx125x_reg_w(SX125x_REG_RX_BW__ADC_BW, SX125x_RX_ADC_BW, rf_chain); + sx125x_reg_w(SX125x_REG_RX_BW__BB_BW, SX125x_RX_BB_BW, rf_chain); + sx125x_reg_w(SX125x_REG_RX_BW__ADC_TRIM, SX125x_RX_ADC_TRIM, rf_chain); + sx125x_reg_w(SX125x_REG_RX_BW__ADC_BW, SX125x_RX_ADC_BW, rf_chain); - lgw_sx125x_reg_w(SX125x_REG_RX_PLL_BW__ADC_TEMP_EN, SX125x_ADC_TEMP, rf_chain); - lgw_sx125x_reg_w(SX125x_REG_RX_PLL_BW__PLL_BW, SX125x_RX_PLL_BW, rf_chain); + sx125x_reg_w(SX125x_REG_RX_PLL_BW__ADC_TEMP_EN, SX125x_ADC_TEMP, rf_chain); + sx125x_reg_w(SX125x_REG_RX_PLL_BW__PLL_BW, SX125x_RX_PLL_BW, rf_chain); /* set RX PLL frequency */ switch (rf_radio_type) { @@ -357,9 +268,9 @@ int sx125x_setup(uint8_t rf_chain, uint8_t rf_clkout, bool rf_enable, uint8_t rf break; } - lgw_sx125x_reg_w(SX125x_REG_FRF_RX_MSB, 0xFF & part_int, rf_chain); - lgw_sx125x_reg_w(SX125x_REG_FRF_RX_MID, 0xFF & (part_frac >> 8), rf_chain); - lgw_sx125x_reg_w(SX125x_REG_FRF_RX_LSB, 0xFF & part_frac, rf_chain); + sx125x_reg_w(SX125x_REG_FRF_RX_MSB, 0xFF & part_int, rf_chain); + sx125x_reg_w(SX125x_REG_FRF_RX_MID, 0xFF & (part_frac >> 8), rf_chain); + sx125x_reg_w(SX125x_REG_FRF_RX_LSB, 0xFF & part_frac, rf_chain); /* start and PLL lock */ do { @@ -367,12 +278,12 @@ int sx125x_setup(uint8_t rf_chain, uint8_t rf_clkout, bool rf_enable, uint8_t rf DEBUG_MSG("ERROR: FAIL TO LOCK PLL\n"); return -1; } - lgw_sx125x_reg_w(SX125x_REG_MODE, 1, rf_chain); - lgw_sx125x_reg_w(SX125x_REG_MODE, 3, rf_chain); + sx125x_reg_w(SX125x_REG_MODE, 1, rf_chain); + sx125x_reg_w(SX125x_REG_MODE, 3, rf_chain); ++cpt_attempts; DEBUG_PRINTF("Note: SX125x #%d PLL start (attempt %d)\n", rf_chain, cpt_attempts); wait_ms(1); - lgw_sx125x_reg_r(SX125x_REG_MODE_STATUS, &val, rf_chain); + sx125x_reg_r(SX125x_REG_MODE_STATUS, &val, rf_chain); } while ((val & 0x02) == 0); } else { DEBUG_PRINTF("Note: SX125x #%d kept in standby mode\n", rf_chain); diff --git a/libloragw/src/loragw_sx1261.c b/libloragw/src/loragw_sx1261.c new file mode 100644 index 00000000..779a1896 --- /dev/null +++ b/libloragw/src/loragw_sx1261.c @@ -0,0 +1,687 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2019 Semtech + +Description: + Functions used to handle LoRa concentrator SX1261 radio used to handle LBT + and Spectral Scan. + +License: Revised BSD License, see LICENSE.TXT file include in the project +*/ + + +/* -------------------------------------------------------------------------- */ +/* --- DEPENDANCIES --------------------------------------------------------- */ + +#include /* C99 types */ +#include /* printf fprintf */ +#include /* strncmp */ + +#include "loragw_sx1261.h" +#include "loragw_spi.h" +#include "loragw_com.h" +#include "loragw_aux.h" +#include "loragw_reg.h" +#include "loragw_hal.h" + +#include "sx1261_com.h" + +#include "sx1261_pram.var" + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE MACROS ------------------------------------------------------- */ + +#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) +#if DEBUG_LBT == 1 + #define DEBUG_MSG(str) fprintf(stdout, str) + #define DEBUG_PRINTF(fmt, args...) fprintf(stdout,"%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 CHECK_ERR(a) if(a==-1){return LGW_REG_ERROR;} + +#define DEBUG_SX1261_GET_STATUS 0 + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE CONSTANTS ---------------------------------------------------- */ + +#define SX1261_PRAM_VERSION_FULL_SIZE 16 /* 15 bytes + terminating char */ + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE VARIABLES ---------------------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE FUNCTIONS ---------------------------------------------------- */ + +int sx1261_pram_get_version(char * version_str) { + uint8_t buff[3 + SX1261_PRAM_VERSION_FULL_SIZE] = { 0 }; + int x; + + /* Check input parameter */ + CHECK_NULL(version_str); + + /* Get version string (15 bytes) at address 0x320 */ + buff[0] = 0x03; + buff[1] = 0x20; + buff[2] = 0x00; /* status */ + x = sx1261_reg_r(SX1261_READ_REGISTER, buff, 18); + if (x != LGW_REG_SUCCESS) { + printf("ERROR: failed to read SX1261 PRAM version\n"); + return x; + } + + /* Return full PRAM version string */ + buff[18] = '\0'; + strncpy(version_str, (char*)(buff + 3), 16); /* 15 bytes + terminating char */ + version_str[16] = '\0'; + + return LGW_REG_SUCCESS; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int sx1261_get_status(uint8_t * status) { + uint8_t buff[1]; + + buff[0] = 0x00; + sx1261_reg_r(SX1261_GET_STATUS, buff, 1); + + *status = buff[0] & 0x7E; /* ignore bit 0 & 7 */ + + DEBUG_PRINTF("SX1261: %s: get_status: 0x%02X (0x%02X)\n", __FUNCTION__, *status, buff[0]); + + return LGW_REG_SUCCESS; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int sx1261_check_status(uint8_t expected_status) { + int err; + uint8_t status; + + err = sx1261_get_status(&status); + if (err != LGW_REG_SUCCESS) { + printf("ERROR: %s: failed to get status\n", __FUNCTION__); + return LGW_REG_ERROR; + } + + if (status != expected_status) { + printf("ERROR: %s: SX1261 status is not as expected: got:0x%02X expected:0x%02X\n", __FUNCTION__, status, expected_status); + return LGW_REG_ERROR; + } + + return LGW_REG_SUCCESS; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +const char * get_scan_status_str(const lgw_spectral_scan_status_t status) { + switch (status) { + case LGW_SPECTRAL_SCAN_STATUS_NONE: + return "LGW_SPECTRAL_SCAN_STATUS_NONE"; + case LGW_SPECTRAL_SCAN_STATUS_ON_GOING: + return "LGW_SPECTRAL_SCAN_STATUS_ON_GOING"; + case LGW_SPECTRAL_SCAN_STATUS_ABORTED: + return "LGW_SPECTRAL_SCAN_STATUS_ABORTED"; + case LGW_SPECTRAL_SCAN_STATUS_COMPLETED: + return "LGW_SPECTRAL_SCAN_STATUS_COMPLETED"; + default: + return "LGW_SPECTRAL_SCAN_STATUS_UNKNOWN"; + } +} + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC FUNCTIONS DEFINITION ------------------------------------------ */ + +int sx1261_connect(lgw_com_type_t com_type, const char *com_path) { + if (com_type == LGW_COM_SPI && com_path == NULL) { + printf("ERROR: %s: unspecified COM path to connect to sx1261 radio\n", __FUNCTION__); + return LGW_REG_ERROR; + } + return sx1261_com_open(com_type, com_path); +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int sx1261_disconnect(void) { + return sx1261_com_close(); +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int sx1261_reg_w(sx1261_op_code_t op_code, uint8_t *data, uint16_t size) { + int com_stat; + + /* checking input parameters */ + CHECK_NULL(data); + + com_stat = sx1261_com_w(op_code, data, size); + if (com_stat != LGW_COM_SUCCESS) { + printf("ERROR: COM ERROR DURING SX1261 RADIO REGISTER WRITE\n"); + return LGW_REG_ERROR; + } else { + return LGW_REG_SUCCESS; + } +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int sx1261_reg_r(sx1261_op_code_t op_code, uint8_t *data, uint16_t size) { + int com_stat; + + /* checking input parameters */ + CHECK_NULL(data); + + com_stat = sx1261_com_r(op_code, data, size); + if (com_stat != LGW_COM_SUCCESS) { + printf("ERROR: COM ERROR DURING SX1261 RADIO REGISTER READ\n"); + return LGW_REG_ERROR; + } else { + return LGW_REG_SUCCESS; + } +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int sx1261_load_pram(void) { + int i, err; + uint8_t buff[32]; + char pram_version[SX1261_PRAM_VERSION_FULL_SIZE]; + uint32_t val, addr; + + /* Set Radio in Standby mode */ + buff[0] = (uint8_t)SX1261_STDBY_RC; + sx1261_reg_w(SX1261_SET_STANDBY, buff, 1); + + /* Check status */ + err = sx1261_check_status(SX1261_STATUS_MODE_STBY_RC | SX1261_STATUS_READY); + if (err != LGW_REG_SUCCESS) { + printf("ERROR: %s: SX1261 status error\n", __FUNCTION__); + return -1; + } + + err = sx1261_pram_get_version(pram_version); + if (err != LGW_REG_SUCCESS) { + printf("ERROR: %s: SX1261 failed to get pram version\n", __FUNCTION__); + return -1; + } + printf("SX1261: PRAM version: %s\n", pram_version); + + /* Enable patch update */ + buff[0] = 0x06; + buff[1] = 0x10; + buff[2] = 0x10; + err = sx1261_reg_w( SX1261_WRITE_REGISTER, buff, 3); + CHECK_ERR(err); + + /* Load patch */ + for (i = 0; i < (int)PRAM_COUNT; i++) { + val = pram[i]; + addr = 0x8000 + 4*i; + + buff[0] = (addr >> 8) & 0xFF; + buff[1] = (addr >> 0) & 0xFF; + buff[2] = (val >> 24) & 0xFF; + buff[3] = (val >> 16) & 0xFF; + buff[4] = (val >> 8) & 0xFF; + buff[5] = (val >> 0) & 0xFF; + err = sx1261_reg_w(SX1261_WRITE_REGISTER, buff, 6); + CHECK_ERR(err); + } + + /* Disable patch update */ + buff[0] = 0x06; + buff[1] = 0x10; + buff[2] = 0x00; + err = sx1261_reg_w( SX1261_WRITE_REGISTER, buff, 3); + CHECK_ERR(err); + + /* Update pram */ + buff[0] = 0; + err = sx1261_reg_w(0xd9, buff, 0); + CHECK_ERR(err); + + err = sx1261_pram_get_version(pram_version); + if (err != LGW_REG_SUCCESS) { + printf("ERROR: %s: SX1261 failed to get pram version\n", __FUNCTION__); + return -1; + } + printf("SX1261: PRAM version: %s\n", pram_version); + + /* Check PRAM version (only last 4 bytes) */ + if (strncmp(pram_version + 11, sx1261_pram_version_string, 4) != 0) { + printf("ERROR: SX1261 PRAM version mismatch (got:%s expected:%s)\n", pram_version + 11, sx1261_pram_version_string); + return -1; + } + + return 0; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int sx1261_calibrate(uint32_t freq_hz) { + int err = LGW_REG_SUCCESS; + uint8_t buff[16]; + + buff[0] = 0x00; + err = sx1261_reg_r(SX1261_GET_STATUS, buff, 1); + CHECK_ERR(err); + + /* Run calibration */ + if ((freq_hz > 430E6) && (freq_hz < 440E6)) { + buff[0] = 0x6B; + buff[1] = 0x6F; + } else if ((freq_hz > 470E6) && (freq_hz < 510E6)) { + buff[0] = 0x75; + buff[1] = 0x81; + } else if ((freq_hz > 779E6) && (freq_hz < 787E6)) { + buff[0] = 0xC1; + buff[1] = 0xC5; + } else if ((freq_hz > 863E6) && (freq_hz < 870E6)) { + buff[0] = 0xD7; + buff[1] = 0xDB; + } else if ((freq_hz > 902E6) && (freq_hz < 928E6)) { + buff[0] = 0xE1; + buff[1] = 0xE9; + } else { + printf("ERROR: failed to calibrate sx1261 radio, frequency range not supported (%u)\n", freq_hz); + return LGW_REG_ERROR; + } + err = sx1261_reg_w(SX1261_CALIBRATE_IMAGE, buff, 2); + CHECK_ERR(err); + + /* Wait for calibration to complete */ + wait_ms(10); + + buff[0] = 0x00; + buff[1] = 0x00; + buff[2] = 0x00; + err = sx1261_reg_r(SX1261_GET_DEVICE_ERRORS, buff, 3); + CHECK_ERR(err); + if (TAKE_N_BITS_FROM(buff[2], 4, 1) != 0) { + printf("ERROR: sx1261 Image Calibration Error\n"); + return LGW_REG_ERROR; + } + + return err; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int sx1261_setup(void) { + int err; + uint8_t buff[32]; + + /* Set Radio in Standby mode */ + buff[0] = (uint8_t)SX1261_STDBY_RC; + err = sx1261_reg_w(SX1261_SET_STANDBY, buff, 1); + CHECK_ERR(err); + + /* Check radio status */ + err = sx1261_check_status(SX1261_STATUS_MODE_STBY_RC | SX1261_STATUS_READY); + CHECK_ERR(err); + + /* Set Buffer Base address */ + buff[0] = 0x80; + buff[1] = 0x80; + err = sx1261_reg_w(SX1261_SET_BUFFER_BASE_ADDRESS, buff, 2); + CHECK_ERR(err); + + /* sensi adjust */ + buff[0] = 0x08; + buff[1] = 0xAC; + buff[2] = 0xCB; + err = sx1261_reg_w(SX1261_WRITE_REGISTER, buff, 3); + CHECK_ERR(err); + + DEBUG_MSG("SX1261: setup for LBT / Spectral Scan done\n"); + + return LGW_REG_SUCCESS; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int sx1261_set_rx_params(uint32_t freq_hz, uint8_t bandwidth) { + int err; + uint8_t buff[16]; + int32_t freq_reg; + uint8_t fsk_bw_reg; + /* performances variables */ + struct timeval tm; + + /* Record function start time */ + _meas_time_start(&tm); + + /* Set SPI write bulk mode to optimize speed on USB */ + err = sx1261_com_set_write_mode(LGW_COM_WRITE_MODE_BULK); + CHECK_ERR(err); + + /* Disable any on-going spectral scan to free the sx1261 radio for LBT */ + err = sx1261_spectral_scan_abort(); + CHECK_ERR(err); + + /* Set FS */ + err = sx1261_reg_w(SX1261_SET_FS, buff, 0); + CHECK_ERR(err); + +#if DEBUG_SX1261_GET_STATUS /* need to disable spi bulk mode if enable this check */ + /* Check radio status */ + err = sx1261_check_status(SX1261_STATUS_MODE_FS | SX1261_STATUS_READY); + CHECK_ERR(err); +#endif + + /* Set frequency */ + freq_reg = SX1261_FREQ_TO_REG(freq_hz); + buff[0] = (uint8_t)(freq_reg >> 24); + buff[1] = (uint8_t)(freq_reg >> 16); + buff[2] = (uint8_t)(freq_reg >> 8); + buff[3] = (uint8_t)(freq_reg >> 0); + err = sx1261_reg_w(SX1261_SET_RF_FREQUENCY, buff, 4); + CHECK_ERR(err); + + /* Configure RSSI averaging window */ + buff[0] = 0x08; + buff[1] = 0x9B; + buff[2] = 0x05 << 2; + err = sx1261_reg_w(SX1261_WRITE_REGISTER, buff, 3); + CHECK_ERR(err); + + /* Set PacketType */ + buff[0] = 0x00; /* FSK */ + err = sx1261_reg_w(SX1261_SET_PACKET_TYPE, buff, 1); + CHECK_ERR(err); + + /* Set GFSK bandwidth */ + switch (bandwidth) { + case BW_125KHZ: + fsk_bw_reg = 0x0A; /* RX_BW_234300 Hz */ + break; + case BW_250KHZ: + fsk_bw_reg = 0x09; /* RX_BW_467000 Hz */ + break; + default: + printf("ERROR: %s: Cannot configure sx1261 for bandwidth %u\n", __FUNCTION__, bandwidth); + return LGW_REG_ERROR; + } + + /* Set modulation params for FSK */ + buff[0] = 0; // BR + buff[1] = 0x14; // BR + buff[2] = 0x00; // BR + buff[3] = 0x00; // Gaussian BT disabled + buff[4] = fsk_bw_reg; + buff[5] = 0x02; // FDEV + buff[6] = 0xE9; // FDEV + buff[7] = 0x0F; // FDEV + err = sx1261_reg_w(SX1261_SET_MODULATION_PARAMS, buff, 8); + CHECK_ERR(err); + + /* Set packet params for FSK */ + buff[0] = 0x00; /* Preamble length MSB */ + buff[1] = 0x20; /* Preamble length LSB 32 bits*/ + buff[2] = 0x05; /* Preamble detector lenght 16 bits */ + buff[3] = 0x20; /* SyncWordLength 32 bits*/ + buff[4] = 0x00; /* AddrComp disabled */ + buff[5] = 0x01; /* PacketType variable size */ + buff[6] = 0xff; /* PayloadLength 255 bytes */ + buff[7] = 0x00; /* CRCType 1 Byte */ + buff[8] = 0x00; /* Whitening disabled*/ + err = sx1261_reg_w(SX1261_SET_PACKET_PARAMS, buff, 9); + CHECK_ERR(err); + + /* Set Radio in Rx continuous mode */ + buff[0] = 0xFF; + buff[1] = 0xFF; + buff[2] = 0xFF; + err = sx1261_reg_w(SX1261_SET_RX, buff, 3); + CHECK_ERR(err); + + /* Flush write (USB BULK mode) */ + err = sx1261_com_flush(); + if (err != 0) { + printf("ERROR: %s: Failed to flush sx1261 SPI\n", __FUNCTION__); + return -1; + } + + /* Setting back to SINGLE BULK write mode */ + err = sx1261_com_set_write_mode(LGW_COM_WRITE_MODE_SINGLE); + CHECK_ERR(err); + +#if DEBUG_SX1261_GET_STATUS + /* Check radio status */ + err = sx1261_check_status(SX1261_STATUS_MODE_RX | SX1261_STATUS_READY); + CHECK_ERR(err); +#endif + + DEBUG_PRINTF("SX1261: RX params set to %u Hz (bw:0x%02X)\n", freq_hz, bandwidth); + + _meas_time_stop(4, tm, __FUNCTION__); + + return LGW_REG_SUCCESS; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int sx1261_lbt_start(lgw_lbt_scan_time_t scan_time_us, int8_t threshold_dbm) { + int err; + uint8_t buff[16]; + uint16_t nb_scan; + uint8_t threshold_reg = -2 * threshold_dbm; + /* performances variables */ + struct timeval tm; + + /* Record function start time */ + _meas_time_start(&tm); + + switch (scan_time_us) { + case LGW_LBT_SCAN_TIME_128_US: + nb_scan = 24; + break; + case LGW_LBT_SCAN_TIME_5000_US: + nb_scan = 715; + break; + default: + printf("ERROR: wrong scan_time_us value\n"); + return -1; + } + +#if DEBUG_SX1261_GET_STATUS + /* Check radio status */ + err = sx1261_check_status(SX1261_STATUS_MODE_RX | SX1261_STATUS_READY); + CHECK_ERR(err); +#endif + + /* Configure LBT scan */ + buff[0] = 11; // intervall_rssi_read (10 => 7.68 usec,11 => 8.2 usec, 12 => 8.68 usec) + buff[1] = (nb_scan >> 8) & 0xFF; + buff[2] = (nb_scan >> 0) & 0xFF; + buff[3] = threshold_reg; + buff[4] = 1; // gpioId + err = sx1261_reg_w(0x9a, buff, 5); + CHECK_ERR(err); + + /* Wait for Scan Time before TX trigger request */ + wait_us((uint16_t)scan_time_us); + + DEBUG_PRINTF("SX1261: LBT started: scan time = %uus, threshold = %ddBm\n", (uint16_t)scan_time_us, threshold_dbm); + + _meas_time_stop(4, tm, __FUNCTION__); + + return LGW_REG_SUCCESS; + +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int sx1261_lbt_stop(void) { + int err; + uint8_t buff[16]; + /* performances variables */ + struct timeval tm; + + /* Record function start time */ + _meas_time_start(&tm); + + /* Disable LBT */ + buff[0] = 0x08; + buff[1] = 0x9B; + buff[2] = 0x00; + err = sx1261_reg_w(SX1261_WRITE_REGISTER, buff, 3); + CHECK_ERR(err); + + /* Set FS */ + err = sx1261_reg_w(SX1261_SET_FS, buff, 0); + CHECK_ERR(err); + + DEBUG_MSG("SX1261: LBT stopped\n"); + + _meas_time_stop(4, tm, __FUNCTION__); + + return LGW_REG_SUCCESS; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int sx1261_spectral_scan_start(uint16_t nb_scan) { + int err; + uint8_t buff[4]; /* 66 bytes for spectral scan results + 2 bytes register address + 1 dummy byte for reading */ + /* performances variables */ + struct timeval tm; + + /* Record function start time */ + _meas_time_start(&tm); + + /* Start spectral scan */ + buff[0] = (nb_scan >> 8) & 0xFF; /* nb_scan MSB */ + buff[1] = (nb_scan >> 0) & 0xFF; /* nb_scan LSB */ + buff[2] = 11; /* interval between scans - 8.2 us */ + err = sx1261_reg_w(0x9b, buff, 9); + CHECK_ERR(err); + + DEBUG_MSG("INFO: Spectral Scan started...\n"); + + _meas_time_stop(4, tm, __FUNCTION__); + + return LGW_REG_SUCCESS; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int sx1261_spectral_scan_get_results(int8_t rssi_offset, int16_t * levels_dbm, uint16_t * results) { + int err, i; + uint8_t buff[69]; /* 66 bytes for spectral scan results + 2 bytes register address + 1 dummy byte for reading */ + /* performances variables */ + struct timeval tm; + + /* Record function start time */ + _meas_time_start(&tm); + + /* Check input parameters */ + CHECK_NULL(levels_dbm); + CHECK_NULL(results); + + /* Get the results (66 bytes) */ + buff[0] = 0x04; + buff[1] = 0x01; + buff[2] = 0x00; /* dummy */ + for (i = 3; i < (66 + 3) ; i++) { + buff[i] = 0x00; + } + err = sx1261_reg_r(SX1261_READ_REGISTER, buff, 66 + 3); + CHECK_ERR(err); + + /* Copy the results in the given buffers */ + /* The number of points measured ABOVE each threshold */ + for (i = 0; i < 32; i++) { + levels_dbm[i] = -i*4 + rssi_offset; + results[i] = (uint16_t)((buff[3 + i*2] << 8) | buff[3 + i*2 + 1]); + } + /* The number of points measured BELOW the lower threshold */ + levels_dbm[32] = -31*4 + rssi_offset; + results[32] = (uint16_t)((buff[3 + 32*2] << 8) + buff[3 + 32*2 + 1]); + + _meas_time_stop(4, tm, __FUNCTION__); + + return LGW_REG_SUCCESS; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int sx1261_spectral_scan_status(lgw_spectral_scan_status_t * status) { + int err; + uint8_t buff[16]; + /* performances variables */ + struct timeval tm; + + CHECK_NULL(status); + + /* Record function start time */ + _meas_time_start(&tm); + + /* Get status */ + buff[0] = 0x07; + buff[1] = 0xCD; + buff[2] = 0x00; /* dummy */ + buff[3] = 0x00; /* read value holder */ + err = sx1261_reg_r(SX1261_READ_REGISTER, buff, 4); + CHECK_ERR(err); + + switch (buff[3]) { + case 0x00: + *status = LGW_SPECTRAL_SCAN_STATUS_NONE; + break; + case 0x0F: + *status = LGW_SPECTRAL_SCAN_STATUS_ON_GOING; + break; + case 0xF0: + *status = LGW_SPECTRAL_SCAN_STATUS_ABORTED; + break; + case 0xFF: + *status = LGW_SPECTRAL_SCAN_STATUS_COMPLETED; + break; + default: + *status = LGW_SPECTRAL_SCAN_STATUS_UNKNOWN; + break; + } + + DEBUG_PRINTF("INFO: %s: %s\n", __FUNCTION__, get_scan_status_str(*status)); + + _meas_time_stop(4, tm, __FUNCTION__); + + return LGW_REG_SUCCESS; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int sx1261_spectral_scan_abort(void) { + int err; + uint8_t buff[16]; + /* performances variables */ + struct timeval tm; + + /* Record function start time */ + _meas_time_start(&tm); + + /* Disable LBT */ + buff[0] = 0x08; + buff[1] = 0x9B; + buff[2] = 0x00; + err = sx1261_reg_w(SX1261_WRITE_REGISTER, buff, 3); + CHECK_ERR(err); + + DEBUG_MSG("SX1261: spectral scan aborted\n"); + + _meas_time_stop(4, tm, __FUNCTION__); + + return LGW_REG_SUCCESS; +} + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/libloragw/src/loragw_sx1302.c b/libloragw/src/loragw_sx1302.c index 251fb360..9a3c7ee7 100644 --- a/libloragw/src/loragw_sx1302.c +++ b/libloragw/src/loragw_sx1302.c @@ -18,17 +18,11 @@ License: Revised BSD License, see LICENSE.TXT file include in the project #include /* C99 types */ #include /* printf fprintf */ -#include /* malloc free */ -#include /* lseek, close */ -#include /* open */ -#include /* memset */ +#include /* memcmp */ #include /* pow, cell */ #include #include -#include -#include - #include "loragw_reg.h" #include "loragw_aux.h" #include "loragw_hal.h" @@ -45,14 +39,15 @@ License: Revised BSD License, see LICENSE.TXT file include in the project #define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) #if DEBUG_SX1302 == 1 - #define DEBUG_MSG(str) fprintf(stderr, str) - #define DEBUG_PRINTF(fmt, args...) fprintf(stderr, fmt, 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(stdout, str) + #define DEBUG_PRINTF(fmt, args...) fprintf(stdout, fmt, 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 CHECK_NULL(a) if(a==NULL){return LGW_REG_ERROR;} #endif +#define CHECK_ERR(a) if(a==-1){return LGW_REG_ERROR;} #define IF_HZ_TO_REG(f) ((f << 5) / 15625) @@ -77,9 +72,10 @@ License: Revised BSD License, see LICENSE.TXT file include in the project #define FW_VERSION_CAL 1 /* Expected version of calibration firmware */ -#define RSSI_FSK_POLY_0 86 /* polynomiam coefficients to linearize FSK RSSI */ -#define RSSI_FSK_POLY_1 1 -#define RSSI_FSK_POLY_2 0 +#define RSSI_FSK_POLY_0 90.636423 /* polynomiam coefficients to linearize FSK RSSI */ +#define RSSI_FSK_POLY_1 0.420835 +#define RSSI_FSK_POLY_2 0.007129 +#define RSSI_FSK_POLY_3 -0.000026 #define FREQ_OFFSET_LSB_125KHZ 0.11920929f /* 125000 * 32 / 2^6 / 2^19 */ #define FREQ_OFFSET_LSB_250KHZ 0.238418579f /* 250000 * 32 / 2^6 / 2^19 */ @@ -106,6 +102,19 @@ const uint8_t ifmod_config[LGW_IF_CHAIN_NB] = LGW_IFMODEM_CONFIG; #define MIN_FSK_PREAMBLE 3 #define STD_FSK_PREAMBLE 5 +#define GPIO_CFG_REGISTER 0x00 +#define GPIO_CFG_AGC 0x01 +#define GPIO_CFG_ARB 0x02 +#define GPIO_CFG_SPI_EXP_1 0x03 +#define GPIO_CFG_CSN_SPI_EXP 0x04 +#define GPIO_CFG_SPI_EXP_2 0x05 +#define GPIO_CFG_UART 0x06 +#define GPIO_CFG_SX1255_IQ 0x07 +#define GPIO_CFG_SX1261_IQ 0x08 +#define GPIO_CFG_STATUS 0x09 +#define GPIO_CFG_MBIST 0x0A +#define GPIO_CFG_OTP 0x0B + /* -------------------------------------------------------------------------- */ /* --- PRIVATE VARIABLES ---------------------------------------------------- */ @@ -201,18 +210,75 @@ void lora_crc16(const char data, int *crc) { (*crc) = next; } +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int sx1302_config_gpio(void) { + int err; + + err = lgw_reg_w(SX1302_REG_GPIO_GPIO_SEL_0_SELECTION, GPIO_CFG_REGISTER); /* GPIO_0 => CONFIG_DONE */ + CHECK_ERR(err); + err = lgw_reg_w(SX1302_REG_GPIO_GPIO_SEL_1_SELECTION, GPIO_CFG_REGISTER); /* GPIO_1 => UNUSED */ + CHECK_ERR(err); + err = lgw_reg_w(SX1302_REG_GPIO_GPIO_SEL_2_SELECTION, GPIO_CFG_STATUS); /* GPIO_2 => Tx ON */ + CHECK_ERR(err); + err = lgw_reg_w(SX1302_REG_GPIO_GPIO_SEL_3_SELECTION, GPIO_CFG_REGISTER); /* GPIO_3 => UNUSED */ + CHECK_ERR(err); + err = lgw_reg_w(SX1302_REG_GPIO_GPIO_SEL_4_SELECTION, GPIO_CFG_STATUS); /* GPIO_4 => RX ON (PKT_RECEIVE_TOGGLE_OUT) */ + CHECK_ERR(err); + err = lgw_reg_w(SX1302_REG_GPIO_GPIO_SEL_5_SELECTION, GPIO_CFG_REGISTER); /* GPIO_5 => UNUSED */ + CHECK_ERR(err); + err = lgw_reg_w(SX1302_REG_GPIO_GPIO_SEL_6_SELECTION, GPIO_CFG_REGISTER); /* GPIO_6 => UNUSED */ + CHECK_ERR(err); + err = lgw_reg_w(SX1302_REG_GPIO_GPIO_SEL_7_SELECTION, GPIO_CFG_AGC); /* GPIO_7 => USED FOR LBT (MUST BE INPUT) */ + CHECK_ERR(err); + err = lgw_reg_w(SX1302_REG_GPIO_GPIO_DIR_L_DIRECTION, 0x7F); /* GPIO output direction (0 for input and 1 for output) */ + CHECK_ERR(err); + + return LGW_REG_SUCCESS; +} + /* -------------------------------------------------------------------------- */ /* --- PUBLIC FUNCTIONS DEFINITION ------------------------------------------ */ -void sx1302_init(struct lgw_conf_timestamp_s *conf_ts) { - timestamp_counter_new(&counter_us); +int sx1302_init(const struct lgw_conf_ftime_s * ftime_context) { + sx1302_model_id_t model_id; + int x; - if (conf_ts != NULL) { - timestamp_counter_mode(conf_ts->enable_precision_ts, conf_ts->max_ts_metrics, conf_ts->nb_symbols); - } + /* Check input parameters */ + CHECK_NULL(ftime_context); + + /* Initialize internal counter */ + timestamp_counter_new(&counter_us); /* Initialize RX buffer */ rx_buffer_new(&rx_buffer); + + /* Configure timestamping mode */ + if (ftime_context->enable == true) { + x = sx1302_get_model_id(&model_id); + if (x != LGW_REG_SUCCESS) { + printf("ERROR: failed to get Chip Model ID\n"); + return LGW_REG_ERROR; + } + + if (model_id != CHIP_MODEL_ID_SX1303) { + printf("ERROR: Fine Timestamping is not supported on this Chip Model ID 0x%02X\n", model_id); + return LGW_REG_ERROR; + } + } + x = timestamp_counter_mode(ftime_context->enable); + if (x != LGW_REG_SUCCESS) { + printf("ERROR: failed to configure timestamp counter mode\n"); + return LGW_REG_ERROR; + } + + x = sx1302_config_gpio(); + if (x != LGW_REG_SUCCESS) { + printf("ERROR: failed to configure sx1302 GPIOs\n"); + return LGW_REG_ERROR; + } + + return LGW_REG_SUCCESS; } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ @@ -240,7 +306,37 @@ int sx1302_get_eui(uint64_t * eui) { /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ +int sx1302_get_model_id(sx1302_model_id_t * model_id) { + int err; + int32_t val; + + /* Select ChipModelID */ + err = lgw_reg_w(SX1302_REG_OTP_BYTE_ADDR_ADDR, 0xD0); + if (err != LGW_REG_SUCCESS) { + return LGW_REG_ERROR; + } + + /* Read Modem ID */ + err = lgw_reg_r(SX1302_REG_OTP_RD_DATA_RD_DATA, &val); + if (err != LGW_REG_SUCCESS) { + return LGW_REG_ERROR; + } + *model_id = (sx1302_model_id_t)val; + + return LGW_REG_SUCCESS; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + int sx1302_update(void) { + uint32_t inst, pps; + /* performances variables */ + struct timeval tm; + + /* Record function start time */ + _meas_time_start(&tm); + +#if 0 /* Disabled because it brings latency on USB, for low value. TODO: do this less frequently ? */ int32_t val; /* Check MCUs parity errors */ @@ -254,10 +350,12 @@ int sx1302_update(void) { printf("ERROR: Parity error check failed on ARB firmware\n"); return LGW_REG_ERROR; } +#endif /* Update internal timestamp counter wrapping status */ - timestamp_counter_get(&counter_us, false); /* maintain inst counter */ - timestamp_counter_get(&counter_us, true); /* maintain pps counter */ + timestamp_counter_get(&counter_us, &inst, &pps); + + _meas_time_stop(2, tm, __FUNCTION__); return LGW_REG_SUCCESS; } @@ -265,6 +363,8 @@ int sx1302_update(void) { /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ int sx1302_radio_clock_select(uint8_t rf_chain) { + int err = LGW_REG_SUCCESS; + /* Check input parameters */ if (rf_chain >= LGW_RF_CHAIN_NB) { @@ -276,23 +376,29 @@ int sx1302_radio_clock_select(uint8_t rf_chain) { switch (rf_chain) { case 0: DEBUG_MSG("Select Radio A clock\n"); - lgw_reg_w(SX1302_REG_CLK_CTRL_CLK_SEL_CLK_RADIO_A_SEL, 0x01); - lgw_reg_w(SX1302_REG_CLK_CTRL_CLK_SEL_CLK_RADIO_B_SEL, 0x00); + err |= lgw_reg_w(SX1302_REG_CLK_CTRL_CLK_SEL_CLK_RADIO_A_SEL, 0x01); + err |= lgw_reg_w(SX1302_REG_CLK_CTRL_CLK_SEL_CLK_RADIO_B_SEL, 0x00); break; case 1: DEBUG_MSG("Select Radio B clock\n"); - lgw_reg_w(SX1302_REG_CLK_CTRL_CLK_SEL_CLK_RADIO_A_SEL, 0x00); - lgw_reg_w(SX1302_REG_CLK_CTRL_CLK_SEL_CLK_RADIO_B_SEL, 0x01); + err |= lgw_reg_w(SX1302_REG_CLK_CTRL_CLK_SEL_CLK_RADIO_A_SEL, 0x00); + err |= lgw_reg_w(SX1302_REG_CLK_CTRL_CLK_SEL_CLK_RADIO_B_SEL, 0x01); break; default: return LGW_REG_ERROR; } /* Enable clock dividers */ - lgw_reg_w(SX1302_REG_CLK_CTRL_CLK_SEL_CLKDIV_EN, 0x01); + err |= lgw_reg_w(SX1302_REG_CLK_CTRL_CLK_SEL_CLKDIV_EN, 0x01); /* Set the RIF clock to the 32MHz clock of the radio */ - lgw_reg_w(SX1302_REG_COMMON_CTRL0_CLK32_RIF_CTRL, 0x01); + err |= lgw_reg_w(SX1302_REG_COMMON_CTRL0_CLK32_RIF_CTRL, 0x01); + + /* Check if something went wrong */ + if (err != LGW_REG_SUCCESS) { + printf("ERROR: failed to select radio clock for radio_%u\n", rf_chain); + return LGW_REG_ERROR; + } return LGW_REG_SUCCESS; } @@ -302,6 +408,7 @@ int sx1302_radio_clock_select(uint8_t rf_chain) { int sx1302_radio_reset(uint8_t rf_chain, lgw_radio_type_t type) { uint16_t reg_radio_en; uint16_t reg_radio_rst; + int err = LGW_REG_SUCCESS; /* Check input parameters */ if (rf_chain >= LGW_RF_CHAIN_NB) @@ -315,17 +422,17 @@ int sx1302_radio_reset(uint8_t rf_chain, lgw_radio_type_t type) { } /* Switch to SPI clock before reseting the radio */ - lgw_reg_w(SX1302_REG_COMMON_CTRL0_CLK32_RIF_CTRL, 0x00); + err |= lgw_reg_w(SX1302_REG_COMMON_CTRL0_CLK32_RIF_CTRL, 0x00); /* Enable the radio */ reg_radio_en = REG_SELECT(rf_chain, SX1302_REG_AGC_MCU_RF_EN_A_RADIO_EN, SX1302_REG_AGC_MCU_RF_EN_B_RADIO_EN); - lgw_reg_w(reg_radio_en, 0x01); + err |= lgw_reg_w(reg_radio_en, 0x01); /* Select the proper reset sequence depending on the radio type */ reg_radio_rst = REG_SELECT(rf_chain, SX1302_REG_AGC_MCU_RF_EN_A_RADIO_RST, SX1302_REG_AGC_MCU_RF_EN_B_RADIO_RST); - lgw_reg_w(reg_radio_rst, 0x01); + err |= lgw_reg_w(reg_radio_rst, 0x01); wait_ms(500); - lgw_reg_w(reg_radio_rst, 0x00); + err |= lgw_reg_w(reg_radio_rst, 0x00); wait_ms(10); switch (type) { case LGW_RADIO_TYPE_SX1255: @@ -334,7 +441,7 @@ int sx1302_radio_reset(uint8_t rf_chain, lgw_radio_type_t type) { DEBUG_PRINTF("INFO: reset sx125x (RADIO_%s) done\n", REG_SELECT(rf_chain, "A", "B")); break; case LGW_RADIO_TYPE_SX1250: - lgw_reg_w(reg_radio_rst, 0x01); + err |= lgw_reg_w(reg_radio_rst, 0x01); wait_ms(10); /* wait for auto calibration to complete */ DEBUG_PRINTF("INFO: reset sx1250 (RADIO_%s) done\n", REG_SELECT(rf_chain, "A", "B")); break; @@ -342,6 +449,12 @@ int sx1302_radio_reset(uint8_t rf_chain, lgw_radio_type_t type) { return LGW_REG_ERROR; } + /* Check if something went wrong */ + if (err != LGW_REG_SUCCESS) { + printf("ERROR: failed to reset the radios\n"); + return LGW_REG_ERROR; + } + return LGW_REG_SUCCESS; } @@ -349,6 +462,7 @@ int sx1302_radio_reset(uint8_t rf_chain, lgw_radio_type_t type) { int sx1302_radio_set_mode(uint8_t rf_chain, lgw_radio_type_t type) { uint16_t reg; + int err; /* Check input parameters */ if (rf_chain >= LGW_RF_CHAIN_NB) { @@ -366,13 +480,17 @@ int sx1302_radio_set_mode(uint8_t rf_chain, lgw_radio_type_t type) { switch (type) { case LGW_RADIO_TYPE_SX1250: DEBUG_PRINTF("Setting rf_chain_%u in sx1250 mode\n", rf_chain); - lgw_reg_w(reg, 0x01); + err = lgw_reg_w(reg, 0x01); break; default: DEBUG_PRINTF("Setting rf_chain_%u in sx125x mode\n", rf_chain); - lgw_reg_w(reg, 0x00); + err = lgw_reg_w(reg, 0x00); break; } + if (err != LGW_REG_SUCCESS) { + printf("ERROR: failed to set mode for radio %u\n", rf_chain); + return LGW_REG_ERROR; + } return LGW_REG_SUCCESS; } @@ -380,39 +498,53 @@ int sx1302_radio_set_mode(uint8_t rf_chain, lgw_radio_type_t type) { /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ int sx1302_radio_host_ctrl(bool host_ctrl) { - lgw_reg_w(SX1302_REG_COMMON_CTRL0_HOST_RADIO_CTRL, (host_ctrl == false) ? 0x00 : 0x01); - - return LGW_REG_SUCCESS; + return lgw_reg_w(SX1302_REG_COMMON_CTRL0_HOST_RADIO_CTRL, (host_ctrl == false) ? 0x00 : 0x01); } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ int sx1302_radio_calibrate(struct lgw_conf_rxrf_s * context_rf_chain, uint8_t clksrc, struct lgw_tx_gain_lut_s * txgain_lut) { int i; + int err = LGW_REG_SUCCESS; /* -- Reset radios */ for (i = 0; i < LGW_RF_CHAIN_NB; i++) { if (context_rf_chain[i].enable == true) { - sx1302_radio_reset(i, context_rf_chain[i].type); - sx1302_radio_set_mode(i, context_rf_chain[i].type); + err = sx1302_radio_reset(i, context_rf_chain[i].type); + if (err != LGW_REG_SUCCESS) { + printf("ERROR: failed to reset radio %d\n", i); + return LGW_REG_ERROR; + } + + err = sx1302_radio_set_mode(i, context_rf_chain[i].type); + if (err != LGW_REG_SUCCESS) { + printf("ERROR: failed to set radio %d mode\n", i); + return LGW_REG_ERROR; + } } } /* -- Select the radio which provides the clock to the sx1302 */ - sx1302_radio_clock_select(clksrc); + err = sx1302_radio_clock_select(clksrc); + if (err != LGW_REG_SUCCESS) { + printf("ERROR: failed to get select clock from radio %u\n", clksrc); + return LGW_REG_ERROR; + } /* -- Ensure PA/LNA are disabled */ - lgw_reg_w(SX1302_REG_AGC_MCU_CTRL_FORCE_HOST_FE_CTRL, 1); - lgw_reg_w(SX1302_REG_AGC_MCU_RF_EN_A_PA_EN, 0); - lgw_reg_w(SX1302_REG_AGC_MCU_RF_EN_A_LNA_EN, 0); + err |= lgw_reg_w(SX1302_REG_AGC_MCU_CTRL_FORCE_HOST_FE_CTRL, 1); + err |= lgw_reg_w(SX1302_REG_AGC_MCU_RF_EN_A_PA_EN, 0); + err |= lgw_reg_w(SX1302_REG_AGC_MCU_RF_EN_A_LNA_EN, 0); /* -- Start calibration */ if ((context_rf_chain[clksrc].type == LGW_RADIO_TYPE_SX1257) || (context_rf_chain[clksrc].type == LGW_RADIO_TYPE_SX1255)) { DEBUG_MSG("Loading CAL fw for sx125x\n"); - if (sx1302_agc_load_firmware(cal_firmware_sx125x) != LGW_HAL_SUCCESS) { + err = sx1302_agc_load_firmware(cal_firmware_sx125x); + if (err != LGW_REG_SUCCESS) { printf("ERROR: Failed to load calibration fw\n"); return LGW_REG_ERROR; } - if (sx1302_cal_start(FW_VERSION_CAL, context_rf_chain, txgain_lut) != LGW_HAL_SUCCESS) { + err = sx1302_cal_start(FW_VERSION_CAL, context_rf_chain, txgain_lut); + if (err != LGW_REG_SUCCESS) { printf("ERROR: radio calibration failed\n"); sx1302_radio_reset(0, context_rf_chain[0].type); sx1302_radio_reset(1, context_rf_chain[1].type); @@ -422,7 +554,8 @@ int sx1302_radio_calibrate(struct lgw_conf_rxrf_s * context_rf_chain, uint8_t cl DEBUG_MSG("Calibrating sx1250 radios\n"); for (i = 0; i < LGW_RF_CHAIN_NB; i++) { if (context_rf_chain[i].enable == true) { - if (sx1250_calibrate(i, context_rf_chain[i].freq_hz)) { + err = sx1250_calibrate(i, context_rf_chain[i].freq_hz); + if (err != LGW_REG_SUCCESS) { printf("ERROR: radio calibration failed\n"); return LGW_REG_ERROR; } @@ -430,41 +563,53 @@ int sx1302_radio_calibrate(struct lgw_conf_rxrf_s * context_rf_chain, uint8_t cl } } /* -- Release control over FE */ - lgw_reg_w(SX1302_REG_AGC_MCU_CTRL_FORCE_HOST_FE_CTRL, 0); + err |= lgw_reg_w(SX1302_REG_AGC_MCU_CTRL_FORCE_HOST_FE_CTRL, 0); - return LGW_REG_SUCCESS; + return err; } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ -int sx1302_pa_lna_lut_configure(void) { - lgw_reg_w(SX1302_REG_AGC_MCU_LUT_TABLE_A_PA_LUT, 0x04); /* Enable PA: RADIO_CTRL[2] is high when PA_EN=1 & LNA_EN=0 */ - lgw_reg_w(SX1302_REG_AGC_MCU_LUT_TABLE_B_PA_LUT, 0x04); /* Enable PA: RADIO_CTRL[8] is high when PA_EN=1 & LNA_EN=0 */ - lgw_reg_w(SX1302_REG_AGC_MCU_LUT_TABLE_A_LNA_LUT, 0x02); /* Enable LNA: RADIO_CTRL[1] is high when PA_EN=0 & LNA_EN=1 */ - lgw_reg_w(SX1302_REG_AGC_MCU_LUT_TABLE_B_LNA_LUT, 0x02); /* Enable LNA: RADIO_CTRL[7] is high when PA_EN=0 & LNA_EN=1 */ +int sx1302_pa_lna_lut_configure(struct lgw_conf_board_s * context_board) { + int err = LGW_REG_SUCCESS; - return LGW_REG_SUCCESS; + /* Configure LUT Table A */ + if (context_board->full_duplex == true) { + err |= lgw_reg_w(SX1302_REG_AGC_MCU_LUT_TABLE_A_PA_LUT, 0x0C); /* Enable PA: RADIO_CTRL[2] is high when PA_EN=1 */ + err |= lgw_reg_w(SX1302_REG_AGC_MCU_LUT_TABLE_A_LNA_LUT, 0x0F); /* Enable LNA: RADIO_CTRL[1] is always high */ + } else { + err |= lgw_reg_w(SX1302_REG_AGC_MCU_LUT_TABLE_A_PA_LUT, 0x04); /* Enable PA: RADIO_CTRL[2] is high when PA_EN=1 */ + err |= lgw_reg_w(SX1302_REG_AGC_MCU_LUT_TABLE_A_LNA_LUT, 0x02); /* Enable LNA: RADIO_CTRL[1] is high when PA_EN=0 & LNA_EN=1 */ + } + + /* Configure LUT Table B */ + err |= lgw_reg_w(SX1302_REG_AGC_MCU_LUT_TABLE_B_PA_LUT, 0x04); /* Enable PA: RADIO_CTRL[8] is high when PA_EN=1 & LNA_EN=0 */ + err |= lgw_reg_w(SX1302_REG_AGC_MCU_LUT_TABLE_B_LNA_LUT, 0x02); /* Enable LNA: RADIO_CTRL[7] is high when PA_EN=0 & LNA_EN=1 */ + + return err; } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ int sx1302_radio_fe_configure(void) { - lgw_reg_w(SX1302_REG_RADIO_FE_RSSI_BB_FILTER_ALPHA_RADIO_A_RSSI_BB_FILTER_ALPHA, 0x03); - lgw_reg_w(SX1302_REG_RADIO_FE_RSSI_DEC_FILTER_ALPHA_RADIO_A_RSSI_DEC_FILTER_ALPHA, 0x07); - lgw_reg_w(SX1302_REG_RADIO_FE_RSSI_BB_FILTER_ALPHA_RADIO_B_RSSI_BB_FILTER_ALPHA, 0x03); - lgw_reg_w(SX1302_REG_RADIO_FE_RSSI_DEC_FILTER_ALPHA_RADIO_B_RSSI_DEC_FILTER_ALPHA, 0x07); + int err = LGW_REG_SUCCESS; - lgw_reg_w(SX1302_REG_RADIO_FE_RSSI_DB_DEF_RADIO_A_RSSI_DB_DEFAULT_VALUE, 23); - lgw_reg_w(SX1302_REG_RADIO_FE_RSSI_DEC_DEF_RADIO_A_RSSI_DEC_DEFAULT_VALUE, 66); - lgw_reg_w(SX1302_REG_RADIO_FE_RSSI_DB_DEF_RADIO_B_RSSI_DB_DEFAULT_VALUE, 23); - lgw_reg_w(SX1302_REG_RADIO_FE_RSSI_DEC_DEF_RADIO_B_RSSI_DEC_DEFAULT_VALUE, 66); + err |= lgw_reg_w(SX1302_REG_RADIO_FE_RSSI_BB_FILTER_ALPHA_RADIO_A_RSSI_BB_FILTER_ALPHA, 0x03); + err |= lgw_reg_w(SX1302_REG_RADIO_FE_RSSI_DEC_FILTER_ALPHA_RADIO_A_RSSI_DEC_FILTER_ALPHA, 0x07); + err |= lgw_reg_w(SX1302_REG_RADIO_FE_RSSI_BB_FILTER_ALPHA_RADIO_B_RSSI_BB_FILTER_ALPHA, 0x03); + err |= lgw_reg_w(SX1302_REG_RADIO_FE_RSSI_DEC_FILTER_ALPHA_RADIO_B_RSSI_DEC_FILTER_ALPHA, 0x07); - lgw_reg_w(SX1302_REG_RADIO_FE_CTRL0_RADIO_A_DC_NOTCH_EN, 1); - lgw_reg_w(SX1302_REG_RADIO_FE_CTRL0_RADIO_A_HOST_FILTER_GAIN, 0x0b); - lgw_reg_w(SX1302_REG_RADIO_FE_CTRL0_RADIO_B_DC_NOTCH_EN, 1); - lgw_reg_w(SX1302_REG_RADIO_FE_CTRL0_RADIO_B_HOST_FILTER_GAIN, 0x0b); + err |= lgw_reg_w(SX1302_REG_RADIO_FE_RSSI_DB_DEF_RADIO_A_RSSI_DB_DEFAULT_VALUE, 23); + err |= lgw_reg_w(SX1302_REG_RADIO_FE_RSSI_DEC_DEF_RADIO_A_RSSI_DEC_DEFAULT_VALUE, 66); + err |= lgw_reg_w(SX1302_REG_RADIO_FE_RSSI_DB_DEF_RADIO_B_RSSI_DB_DEFAULT_VALUE, 23); + err |= lgw_reg_w(SX1302_REG_RADIO_FE_RSSI_DEC_DEF_RADIO_B_RSSI_DEC_DEFAULT_VALUE, 66); - return LGW_REG_SUCCESS; + err |= lgw_reg_w(SX1302_REG_RADIO_FE_CTRL0_RADIO_A_DC_NOTCH_EN, 1); + err |= lgw_reg_w(SX1302_REG_RADIO_FE_CTRL0_RADIO_A_HOST_FILTER_GAIN, 0x0b); + err |= lgw_reg_w(SX1302_REG_RADIO_FE_CTRL0_RADIO_B_DC_NOTCH_EN, 1); + err |= lgw_reg_w(SX1302_REG_RADIO_FE_CTRL0_RADIO_B_HOST_FILTER_GAIN, 0x0b); + + return err; } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ @@ -479,92 +624,90 @@ int sx1302_channelizer_configure(struct lgw_conf_rxif_s * if_cfg, bool fix_gain) int32_t if_freq; uint8_t channels_mask = 0x00; int i; + int err = LGW_REG_SUCCESS; /* Check input parameters */ - if (if_cfg == NULL) { - printf("ERROR: Failed to configure LoRa channelizer\n"); - return LGW_REG_ERROR; - } + CHECK_NULL(if_cfg); /* Select which radio is connected to each multi-SF channel */ for (i = 0; i < LGW_MULTI_NB; i++) { channels_mask |= (if_cfg[i].rf_chain << i); } DEBUG_PRINTF("LoRa multi-SF radio select: 0x%02X\n", channels_mask); - lgw_reg_w(SX1302_REG_RX_TOP_RADIO_SELECT_RADIO_SELECT, channels_mask); + err |= lgw_reg_w(SX1302_REG_RX_TOP_RADIO_SELECT_RADIO_SELECT, channels_mask); /* Select which radio is connected to the LoRa service channel */ DEBUG_PRINTF("LoRa service radio select: 0x%02X\n", if_cfg[8].rf_chain); - lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_LORA_SERVICE_RADIO_SEL_RADIO_SELECT, if_cfg[8].rf_chain); + err |= lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_LORA_SERVICE_RADIO_SEL_RADIO_SELECT, if_cfg[8].rf_chain); /* Select which radio is connected to the FSK channel */ DEBUG_PRINTF("FSK radio select %u\n", if_cfg[9].rf_chain); - lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_CFG_3_RADIO_SELECT, if_cfg[9].rf_chain); + err |= lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_CFG_3_RADIO_SELECT, if_cfg[9].rf_chain); /* Configure multi-SF channels IF frequencies */ if_freq = IF_HZ_TO_REG(if_cfg[0].freq_hz); - lgw_reg_w(SX1302_REG_RX_TOP_FREQ_0_MSB_IF_FREQ_0, (if_freq >> 8) & 0x0000001F); - lgw_reg_w(SX1302_REG_RX_TOP_FREQ_0_LSB_IF_FREQ_0, (if_freq >> 0) & 0x000000FF); + err |= lgw_reg_w(SX1302_REG_RX_TOP_FREQ_0_MSB_IF_FREQ_0, (if_freq >> 8) & 0x0000001F); + err |= lgw_reg_w(SX1302_REG_RX_TOP_FREQ_0_LSB_IF_FREQ_0, (if_freq >> 0) & 0x000000FF); if_freq = IF_HZ_TO_REG(if_cfg[1].freq_hz); - lgw_reg_w(SX1302_REG_RX_TOP_FREQ_1_MSB_IF_FREQ_1, (if_freq >> 8) & 0x0000001F); - lgw_reg_w(SX1302_REG_RX_TOP_FREQ_1_LSB_IF_FREQ_1, (if_freq >> 0) & 0x000000FF); + err |= lgw_reg_w(SX1302_REG_RX_TOP_FREQ_1_MSB_IF_FREQ_1, (if_freq >> 8) & 0x0000001F); + err |= lgw_reg_w(SX1302_REG_RX_TOP_FREQ_1_LSB_IF_FREQ_1, (if_freq >> 0) & 0x000000FF); if_freq = IF_HZ_TO_REG(if_cfg[2].freq_hz); - lgw_reg_w(SX1302_REG_RX_TOP_FREQ_2_MSB_IF_FREQ_2, (if_freq >> 8) & 0x0000001F); - lgw_reg_w(SX1302_REG_RX_TOP_FREQ_2_LSB_IF_FREQ_2, (if_freq >> 0) & 0x000000FF); + err |= lgw_reg_w(SX1302_REG_RX_TOP_FREQ_2_MSB_IF_FREQ_2, (if_freq >> 8) & 0x0000001F); + err |= lgw_reg_w(SX1302_REG_RX_TOP_FREQ_2_LSB_IF_FREQ_2, (if_freq >> 0) & 0x000000FF); if_freq = IF_HZ_TO_REG(if_cfg[3].freq_hz); - lgw_reg_w(SX1302_REG_RX_TOP_FREQ_3_MSB_IF_FREQ_3, (if_freq >> 8) & 0x0000001F); - lgw_reg_w(SX1302_REG_RX_TOP_FREQ_3_LSB_IF_FREQ_3, (if_freq >> 0) & 0x000000FF); + err |= lgw_reg_w(SX1302_REG_RX_TOP_FREQ_3_MSB_IF_FREQ_3, (if_freq >> 8) & 0x0000001F); + err |= lgw_reg_w(SX1302_REG_RX_TOP_FREQ_3_LSB_IF_FREQ_3, (if_freq >> 0) & 0x000000FF); if_freq = IF_HZ_TO_REG(if_cfg[4].freq_hz); - lgw_reg_w(SX1302_REG_RX_TOP_FREQ_4_MSB_IF_FREQ_4, (if_freq >> 8) & 0x0000001F); - lgw_reg_w(SX1302_REG_RX_TOP_FREQ_4_LSB_IF_FREQ_4, (if_freq >> 0) & 0x000000FF); + err |= lgw_reg_w(SX1302_REG_RX_TOP_FREQ_4_MSB_IF_FREQ_4, (if_freq >> 8) & 0x0000001F); + err |= lgw_reg_w(SX1302_REG_RX_TOP_FREQ_4_LSB_IF_FREQ_4, (if_freq >> 0) & 0x000000FF); if_freq = IF_HZ_TO_REG(if_cfg[5].freq_hz); - lgw_reg_w(SX1302_REG_RX_TOP_FREQ_5_MSB_IF_FREQ_5, (if_freq >> 8) & 0x0000001F); - lgw_reg_w(SX1302_REG_RX_TOP_FREQ_5_LSB_IF_FREQ_5, (if_freq >> 0) & 0x000000FF); + err |= lgw_reg_w(SX1302_REG_RX_TOP_FREQ_5_MSB_IF_FREQ_5, (if_freq >> 8) & 0x0000001F); + err |= lgw_reg_w(SX1302_REG_RX_TOP_FREQ_5_LSB_IF_FREQ_5, (if_freq >> 0) & 0x000000FF); if_freq = IF_HZ_TO_REG(if_cfg[6].freq_hz); - lgw_reg_w(SX1302_REG_RX_TOP_FREQ_6_MSB_IF_FREQ_6, (if_freq >> 8) & 0x0000001F); - lgw_reg_w(SX1302_REG_RX_TOP_FREQ_6_LSB_IF_FREQ_6, (if_freq >> 0) & 0x000000FF); + err |= lgw_reg_w(SX1302_REG_RX_TOP_FREQ_6_MSB_IF_FREQ_6, (if_freq >> 8) & 0x0000001F); + err |= lgw_reg_w(SX1302_REG_RX_TOP_FREQ_6_LSB_IF_FREQ_6, (if_freq >> 0) & 0x000000FF); if_freq = IF_HZ_TO_REG(if_cfg[7].freq_hz); - lgw_reg_w(SX1302_REG_RX_TOP_FREQ_7_MSB_IF_FREQ_7, (if_freq >> 8) & 0x0000001F); - lgw_reg_w(SX1302_REG_RX_TOP_FREQ_7_LSB_IF_FREQ_7, (if_freq >> 0) & 0x000000FF); + err |= lgw_reg_w(SX1302_REG_RX_TOP_FREQ_7_MSB_IF_FREQ_7, (if_freq >> 8) & 0x0000001F); + err |= lgw_reg_w(SX1302_REG_RX_TOP_FREQ_7_LSB_IF_FREQ_7, (if_freq >> 0) & 0x000000FF); /* Configure LoRa service channel IF frequency */ if_freq = IF_HZ_TO_REG(if_cfg[8].freq_hz); - lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_LORA_SERVICE_FREQ_MSB_IF_FREQ_0, (if_freq >> 8) & 0x0000001F); - lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_LORA_SERVICE_FREQ_LSB_IF_FREQ_0, (if_freq >> 0) & 0x000000FF); + err |= lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_LORA_SERVICE_FREQ_MSB_IF_FREQ_0, (if_freq >> 8) & 0x0000001F); + err |= lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_LORA_SERVICE_FREQ_LSB_IF_FREQ_0, (if_freq >> 0) & 0x000000FF); /* Configure FSK channel IF frequency */ if_freq = IF_HZ_TO_REG(if_cfg[9].freq_hz); - lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_FREQ_MSB_IF_FREQ_0, (if_freq >> 8) & 0x0000001F); - lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_FREQ_LSB_IF_FREQ_0, (if_freq >> 0) & 0x000000FF); + err |= lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_FREQ_MSB_IF_FREQ_0, (if_freq >> 8) & 0x0000001F); + err |= lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_FREQ_LSB_IF_FREQ_0, (if_freq >> 0) & 0x000000FF); /* Set the low pass filtering corner frequency for RSSI indicator */ - lgw_reg_w(SX1302_REG_RX_TOP_RSSI_CONTROL_RSSI_FILTER_ALPHA, 0x05); + err |= lgw_reg_w(SX1302_REG_RX_TOP_RSSI_CONTROL_RSSI_FILTER_ALPHA, 0x05); /* Set the channelizer RSSI reset value */ - lgw_reg_w(SX1302_REG_RX_TOP_RSSI_DEF_VALUE_CHAN_RSSI_DEF_VALUE, 85); + err |= lgw_reg_w(SX1302_REG_RX_TOP_RSSI_DEF_VALUE_CHAN_RSSI_DEF_VALUE, 85); /* Force channelizer in fix gain, or let it be controlled by AGC */ if (fix_gain == true) { - lgw_reg_w(SX1302_REG_RX_TOP_CHANN_DAGC_CFG5_CHAN_DAGC_MODE, 0x00); - lgw_reg_w(SX1302_REG_RX_TOP_GAIN_CONTROL_CHAN_GAIN, 5); + err |= lgw_reg_w(SX1302_REG_RX_TOP_CHANN_DAGC_CFG5_CHAN_DAGC_MODE, 0x00); + err |= lgw_reg_w(SX1302_REG_RX_TOP_GAIN_CONTROL_CHAN_GAIN, 5); } else { /* Allow the AGC to control gains */ - lgw_reg_w(SX1302_REG_RX_TOP_CHANN_DAGC_CFG5_CHAN_DAGC_MODE, 0x01); + err |= lgw_reg_w(SX1302_REG_RX_TOP_CHANN_DAGC_CFG5_CHAN_DAGC_MODE, 0x01); /* Disable the internal DAGC */ - lgw_reg_w(SX1302_REG_RX_TOP_CHANN_DAGC_CFG1_CHAN_DAGC_THRESHOLD_HIGH, 255 ); - lgw_reg_w(SX1302_REG_RX_TOP_CHANN_DAGC_CFG2_CHAN_DAGC_THRESHOLD_LOW, 0 ); - lgw_reg_w(SX1302_REG_RX_TOP_CHANN_DAGC_CFG3_CHAN_DAGC_MAX_ATTEN, 15 ); - lgw_reg_w(SX1302_REG_RX_TOP_CHANN_DAGC_CFG3_CHAN_DAGC_MIN_ATTEN, 0 ); + err |= lgw_reg_w(SX1302_REG_RX_TOP_CHANN_DAGC_CFG1_CHAN_DAGC_THRESHOLD_HIGH, 255 ); + err |= lgw_reg_w(SX1302_REG_RX_TOP_CHANN_DAGC_CFG2_CHAN_DAGC_THRESHOLD_LOW, 0 ); + err |= lgw_reg_w(SX1302_REG_RX_TOP_CHANN_DAGC_CFG3_CHAN_DAGC_MAX_ATTEN, 15 ); + err |= lgw_reg_w(SX1302_REG_RX_TOP_CHANN_DAGC_CFG3_CHAN_DAGC_MIN_ATTEN, 0 ); } - return LGW_REG_SUCCESS; + return err; } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ @@ -572,151 +715,167 @@ int sx1302_channelizer_configure(struct lgw_conf_rxif_s * if_cfg, bool fix_gain) int sx1302_fsk_configure(struct lgw_conf_rxif_s * cfg) { uint64_t fsk_sync_word_reg; uint32_t fsk_br_reg; + int err = LGW_REG_SUCCESS; DEBUG_PRINTF("FSK: syncword:0x%" PRIx64 ", syncword_size:%u\n", cfg->sync_word, cfg->sync_word_size); - lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_CFG_1_PSIZE, cfg->sync_word_size - 1); + err |= lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_CFG_1_PSIZE, cfg->sync_word_size - 1); fsk_sync_word_reg = cfg->sync_word << (8 * (8 - cfg->sync_word_size)); - lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_REF_PATTERN_BYTE0_FSK_REF_PATTERN, (uint8_t)(fsk_sync_word_reg >> 0)); - lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_REF_PATTERN_BYTE1_FSK_REF_PATTERN, (uint8_t)(fsk_sync_word_reg >> 8)); - lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_REF_PATTERN_BYTE2_FSK_REF_PATTERN, (uint8_t)(fsk_sync_word_reg >> 16)); - lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_REF_PATTERN_BYTE3_FSK_REF_PATTERN, (uint8_t)(fsk_sync_word_reg >> 24)); - lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_REF_PATTERN_BYTE4_FSK_REF_PATTERN, (uint8_t)(fsk_sync_word_reg >> 32)); - lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_REF_PATTERN_BYTE5_FSK_REF_PATTERN, (uint8_t)(fsk_sync_word_reg >> 40)); - lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_REF_PATTERN_BYTE6_FSK_REF_PATTERN, (uint8_t)(fsk_sync_word_reg >> 48)); - lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_REF_PATTERN_BYTE7_FSK_REF_PATTERN, (uint8_t)(fsk_sync_word_reg >> 56)); + err |= lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_REF_PATTERN_BYTE0_FSK_REF_PATTERN, (uint8_t)(fsk_sync_word_reg >> 0)); + err |= lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_REF_PATTERN_BYTE1_FSK_REF_PATTERN, (uint8_t)(fsk_sync_word_reg >> 8)); + err |= lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_REF_PATTERN_BYTE2_FSK_REF_PATTERN, (uint8_t)(fsk_sync_word_reg >> 16)); + err |= lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_REF_PATTERN_BYTE3_FSK_REF_PATTERN, (uint8_t)(fsk_sync_word_reg >> 24)); + err |= lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_REF_PATTERN_BYTE4_FSK_REF_PATTERN, (uint8_t)(fsk_sync_word_reg >> 32)); + err |= lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_REF_PATTERN_BYTE5_FSK_REF_PATTERN, (uint8_t)(fsk_sync_word_reg >> 40)); + err |= lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_REF_PATTERN_BYTE6_FSK_REF_PATTERN, (uint8_t)(fsk_sync_word_reg >> 48)); + err |= lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_REF_PATTERN_BYTE7_FSK_REF_PATTERN, (uint8_t)(fsk_sync_word_reg >> 56)); fsk_br_reg = 32000000 / cfg->datarate; - lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BIT_RATE_MSB_BIT_RATE, (uint8_t)(fsk_br_reg >> 8)); - lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BIT_RATE_LSB_BIT_RATE, (uint8_t)(fsk_br_reg >> 0)); - lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_CFG_1_CH_BW_EXPO, 0x03); /* 125KHz */ - - lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_CFG_3_RX_INVERT, 0); - lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_CFG_3_MODEM_INVERT_IQ, 0); - - lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_CFG_4_RSSI_LENGTH, 4); - lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_CFG_0_PKT_MODE, 1); /* variable length */ - lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_CFG_0_CRC_EN, 1); - lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_CFG_0_DCFREE_ENC, 2); - lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_CFG_0_CRC_IBM, 0); - lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_CFG_4_ERROR_OSR_TOL, 10); - lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_PKT_LENGTH_PKT_LENGTH, 255); - lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_NODE_ADRS_NODE_ADRS, 0); - lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_BROADCAST_BROADCAST, 0); - lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_CFG_3_AUTO_AFC, 1); /* ?? */ - lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_TIMEOUT_MSB_TIMEOUT, 0); - lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_TIMEOUT_LSB_TIMEOUT, 128); - - return LGW_REG_SUCCESS; + err |= lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BIT_RATE_MSB_BIT_RATE, (uint8_t)(fsk_br_reg >> 8)); + err |= lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_BIT_RATE_LSB_BIT_RATE, (uint8_t)(fsk_br_reg >> 0)); + err |= lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_CFG_1_CH_BW_EXPO, 0x03); /* 125KHz */ + + err |= lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_CFG_3_RX_INVERT, 0); + err |= lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_CFG_3_MODEM_INVERT_IQ, 0); + + err |= lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_CFG_4_RSSI_LENGTH, 4); + err |= lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_CFG_0_PKT_MODE, 1); /* variable length */ + err |= lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_CFG_0_CRC_EN, 1); + err |= lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_CFG_0_DCFREE_ENC, 2); + err |= lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_CFG_0_CRC_IBM, 0); + err |= lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_CFG_4_ERROR_OSR_TOL, 10); + err |= lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_PKT_LENGTH_PKT_LENGTH, 255); + err |= lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_NODE_ADRS_NODE_ADRS, 0); + err |= lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_BROADCAST_BROADCAST, 0); + err |= lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_CFG_3_AUTO_AFC, 1); /* ?? */ + err |= lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_TIMEOUT_MSB_TIMEOUT, 0); + err |= lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FSK_TIMEOUT_LSB_TIMEOUT, 128); + + return err; } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ -int sx1302_lora_correlator_configure() { - lgw_reg_w(SX1302_REG_RX_TOP_SF5_CFG2_ACC_PNR, 52); - lgw_reg_w(SX1302_REG_RX_TOP_SF5_CFG4_MSP_PNR, 24); - lgw_reg_w(SX1302_REG_RX_TOP_SF5_CFG6_MSP_PEAK_NB, 7); - lgw_reg_w(SX1302_REG_RX_TOP_SF5_CFG7_MSP2_PEAK_NB, 5); - - lgw_reg_w(SX1302_REG_RX_TOP_SF6_CFG2_ACC_PNR, 52); - lgw_reg_w(SX1302_REG_RX_TOP_SF6_CFG4_MSP_PNR, 24); - lgw_reg_w(SX1302_REG_RX_TOP_SF6_CFG6_MSP_PEAK_NB, 7); - lgw_reg_w(SX1302_REG_RX_TOP_SF6_CFG7_MSP2_PEAK_NB, 5); - - lgw_reg_w(SX1302_REG_RX_TOP_SF7_CFG2_ACC_PNR, 52); - lgw_reg_w(SX1302_REG_RX_TOP_SF7_CFG4_MSP_PNR, 24); - lgw_reg_w(SX1302_REG_RX_TOP_SF7_CFG6_MSP_PEAK_NB, 7); - lgw_reg_w(SX1302_REG_RX_TOP_SF7_CFG7_MSP2_PEAK_NB, 5); - - lgw_reg_w(SX1302_REG_RX_TOP_SF8_CFG2_ACC_PNR, 52); - lgw_reg_w(SX1302_REG_RX_TOP_SF8_CFG4_MSP_PNR, 24); - lgw_reg_w(SX1302_REG_RX_TOP_SF8_CFG6_MSP_PEAK_NB, 7); - lgw_reg_w(SX1302_REG_RX_TOP_SF8_CFG7_MSP2_PEAK_NB, 5); - - lgw_reg_w(SX1302_REG_RX_TOP_SF9_CFG2_ACC_PNR, 52); - lgw_reg_w(SX1302_REG_RX_TOP_SF9_CFG4_MSP_PNR, 24); - lgw_reg_w(SX1302_REG_RX_TOP_SF9_CFG6_MSP_PEAK_NB, 7); - lgw_reg_w(SX1302_REG_RX_TOP_SF9_CFG7_MSP2_PEAK_NB, 5); - - lgw_reg_w(SX1302_REG_RX_TOP_SF10_CFG2_ACC_PNR, 52); - lgw_reg_w(SX1302_REG_RX_TOP_SF10_CFG4_MSP_PNR, 24); - lgw_reg_w(SX1302_REG_RX_TOP_SF10_CFG6_MSP_PEAK_NB, 7); - lgw_reg_w(SX1302_REG_RX_TOP_SF10_CFG7_MSP2_PEAK_NB, 5); - - lgw_reg_w(SX1302_REG_RX_TOP_SF11_CFG2_ACC_PNR, 52); - lgw_reg_w(SX1302_REG_RX_TOP_SF11_CFG4_MSP_PNR, 24); - lgw_reg_w(SX1302_REG_RX_TOP_SF11_CFG6_MSP_PEAK_NB, 7); - lgw_reg_w(SX1302_REG_RX_TOP_SF11_CFG7_MSP2_PEAK_NB, 5); - - lgw_reg_w(SX1302_REG_RX_TOP_SF12_CFG2_ACC_PNR, 52); - lgw_reg_w(SX1302_REG_RX_TOP_SF12_CFG4_MSP_PNR, 24); - lgw_reg_w(SX1302_REG_RX_TOP_SF12_CFG6_MSP_PEAK_NB, 7); - lgw_reg_w(SX1302_REG_RX_TOP_SF12_CFG7_MSP2_PEAK_NB, 5); - - lgw_reg_w(SX1302_REG_RX_TOP_CORR_CLOCK_ENABLE_CLK_EN, 0xFF); - - lgw_reg_w(SX1302_REG_RX_TOP_CORRELATOR_ENABLE_ONLY_FIRST_DET_EDGE_ENABLE_ONLY_FIRST_DET_EDGE, 0xFF); - lgw_reg_w(SX1302_REG_RX_TOP_CORRELATOR_ENABLE_ACC_CLEAR_ENABLE_CORR_ACC_CLEAR, 0xFF); - lgw_reg_w(SX1302_REG_RX_TOP_CORRELATOR_SF_EN_CORR_SF_EN, 0xFF); /* 12 11 10 9 8 7 6 5 */ - lgw_reg_w(SX1302_REG_RX_TOP_CORRELATOR_EN_CORR_EN, 0xFF); /* 1 correlator per channel */ +int sx1302_lora_correlator_configure(struct lgw_conf_rxif_s * if_cfg, struct lgw_conf_demod_s * demod_cfg) { + int i, err = LGW_REG_SUCCESS; + uint8_t channels_mask = 0x00; + + /* Check input parameters */ + CHECK_NULL(if_cfg); + + err |= lgw_reg_w(SX1302_REG_RX_TOP_SF5_CFG2_ACC_PNR, 52); + err |= lgw_reg_w(SX1302_REG_RX_TOP_SF5_CFG4_MSP_PNR, 24); + err |= lgw_reg_w(SX1302_REG_RX_TOP_SF5_CFG6_MSP_PEAK_NB, 7); + err |= lgw_reg_w(SX1302_REG_RX_TOP_SF5_CFG7_MSP2_PEAK_NB, 5); + + err |= lgw_reg_w(SX1302_REG_RX_TOP_SF6_CFG2_ACC_PNR, 52); + err |= lgw_reg_w(SX1302_REG_RX_TOP_SF6_CFG4_MSP_PNR, 24); + err |= lgw_reg_w(SX1302_REG_RX_TOP_SF6_CFG6_MSP_PEAK_NB, 7); + err |= lgw_reg_w(SX1302_REG_RX_TOP_SF6_CFG7_MSP2_PEAK_NB, 5); + + err |= lgw_reg_w(SX1302_REG_RX_TOP_SF7_CFG2_ACC_PNR, 52); + err |= lgw_reg_w(SX1302_REG_RX_TOP_SF7_CFG4_MSP_PNR, 24); + err |= lgw_reg_w(SX1302_REG_RX_TOP_SF7_CFG6_MSP_PEAK_NB, 7); + err |= lgw_reg_w(SX1302_REG_RX_TOP_SF7_CFG7_MSP2_PEAK_NB, 5); + + err |= lgw_reg_w(SX1302_REG_RX_TOP_SF8_CFG2_ACC_PNR, 52); + err |= lgw_reg_w(SX1302_REG_RX_TOP_SF8_CFG4_MSP_PNR, 24); + err |= lgw_reg_w(SX1302_REG_RX_TOP_SF8_CFG6_MSP_PEAK_NB, 7); + err |= lgw_reg_w(SX1302_REG_RX_TOP_SF8_CFG7_MSP2_PEAK_NB, 5); + + err |= lgw_reg_w(SX1302_REG_RX_TOP_SF9_CFG2_ACC_PNR, 52); + err |= lgw_reg_w(SX1302_REG_RX_TOP_SF9_CFG4_MSP_PNR, 24); + err |= lgw_reg_w(SX1302_REG_RX_TOP_SF9_CFG6_MSP_PEAK_NB, 7); + err |= lgw_reg_w(SX1302_REG_RX_TOP_SF9_CFG7_MSP2_PEAK_NB, 5); + + err |= lgw_reg_w(SX1302_REG_RX_TOP_SF10_CFG2_ACC_PNR, 52); + err |= lgw_reg_w(SX1302_REG_RX_TOP_SF10_CFG4_MSP_PNR, 24); + err |= lgw_reg_w(SX1302_REG_RX_TOP_SF10_CFG6_MSP_PEAK_NB, 7); + err |= lgw_reg_w(SX1302_REG_RX_TOP_SF10_CFG7_MSP2_PEAK_NB, 5); + + err |= lgw_reg_w(SX1302_REG_RX_TOP_SF11_CFG2_ACC_PNR, 52); + err |= lgw_reg_w(SX1302_REG_RX_TOP_SF11_CFG4_MSP_PNR, 24); + err |= lgw_reg_w(SX1302_REG_RX_TOP_SF11_CFG6_MSP_PEAK_NB, 7); + err |= lgw_reg_w(SX1302_REG_RX_TOP_SF11_CFG7_MSP2_PEAK_NB, 5); + + err |= lgw_reg_w(SX1302_REG_RX_TOP_SF12_CFG2_ACC_PNR, 52); + err |= lgw_reg_w(SX1302_REG_RX_TOP_SF12_CFG4_MSP_PNR, 24); + err |= lgw_reg_w(SX1302_REG_RX_TOP_SF12_CFG6_MSP_PEAK_NB, 7); + err |= lgw_reg_w(SX1302_REG_RX_TOP_SF12_CFG7_MSP2_PEAK_NB, 5); + + err |= lgw_reg_w(SX1302_REG_RX_TOP_CORRELATOR_ENABLE_ONLY_FIRST_DET_EDGE_ENABLE_ONLY_FIRST_DET_EDGE, 0xFF); + err |= lgw_reg_w(SX1302_REG_RX_TOP_CORRELATOR_ENABLE_ACC_CLEAR_ENABLE_CORR_ACC_CLEAR, 0xFF); + + /* Enabled selected spreading factors */ + err |= lgw_reg_w(SX1302_REG_RX_TOP_CORRELATOR_SF_EN_CORR_SF_EN, demod_cfg->multisf_datarate); + DEBUG_PRINTF("INFO: LoRa multi-SF correlator SF enable mask: 0x%02X\n", demod_cfg->multisf_datarate); + + /* Enable correlator if channel is enabled (1 correlator per channel) */ + for (i = 0; i < LGW_MULTI_NB; i++) { + channels_mask |= (if_cfg[i].enable << i); + } + DEBUG_PRINTF("INFO: LoRa multi-SF channel enable mask: 0x%02X\n", channels_mask); + err |= lgw_reg_w(SX1302_REG_RX_TOP_CORR_CLOCK_ENABLE_CLK_EN, channels_mask); + err |= lgw_reg_w(SX1302_REG_RX_TOP_CORRELATOR_EN_CORR_EN, channels_mask); /* For debug: get packets with sync_error and header_error in FIFO */ #if 0 - lgw_reg_w(SX1302_REG_RX_TOP_RX_BUFFER_STORE_SYNC_FAIL_META, 0x01); - lgw_reg_w(SX1302_REG_RX_TOP_RX_BUFFER_STORE_HEADER_ERR_META, 0x01); + err |= lgw_reg_w(SX1302_REG_RX_TOP_RX_BUFFER_STORE_SYNC_FAIL_META, 0x01); + err |= lgw_reg_w(SX1302_REG_RX_TOP_RX_BUFFER_STORE_HEADER_ERR_META, 0x01); #endif - return LGW_REG_SUCCESS; + return err; } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ int sx1302_lora_service_correlator_configure(struct lgw_conf_rxif_s * cfg) { + int err = LGW_REG_SUCCESS; /* Common config for all SF */ - lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_DETECT_MSP2_MSP_PEAK_NB, 7); - lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_DETECT_MSP2_MSP2_PEAK_NB, 5); - lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_DETECT_ACC1_USE_GAIN_SYMB, 1); + err |= lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_DETECT_MSP2_MSP_PEAK_NB, 7); + err |= lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_DETECT_MSP2_MSP2_PEAK_NB, 5); + err |= lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_DETECT_ACC1_USE_GAIN_SYMB, 1); switch (cfg->datarate) { case 5: - lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_TXRX_CFG2_FINE_SYNCH_EN, 1); - lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_DETECT_ACC1_ACC_PNR, 52); + err |= lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_TXRX_CFG2_FINE_SYNCH_EN, 1); + err |= lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_DETECT_ACC1_ACC_PNR, 52); break; case 6: - lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_TXRX_CFG2_FINE_SYNCH_EN, 1); - lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_DETECT_ACC1_ACC_PNR, 52); + err |= lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_TXRX_CFG2_FINE_SYNCH_EN, 1); + err |= lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_DETECT_ACC1_ACC_PNR, 52); break; case 7: - lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_TXRX_CFG2_FINE_SYNCH_EN, 0); - lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_DETECT_ACC1_ACC_PNR, 52); + err |= lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_TXRX_CFG2_FINE_SYNCH_EN, 0); + err |= lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_DETECT_ACC1_ACC_PNR, 52); break; case 8: - lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_TXRX_CFG2_FINE_SYNCH_EN, 0); - lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_DETECT_ACC1_ACC_PNR, 52); + err |= lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_TXRX_CFG2_FINE_SYNCH_EN, 0); + err |= lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_DETECT_ACC1_ACC_PNR, 52); break; case 9: - lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_TXRX_CFG2_FINE_SYNCH_EN, 0); - lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_DETECT_ACC1_ACC_PNR, 52); + err |= lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_TXRX_CFG2_FINE_SYNCH_EN, 0); + err |= lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_DETECT_ACC1_ACC_PNR, 52); break; case 10: - lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_TXRX_CFG2_FINE_SYNCH_EN, 0); - lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_DETECT_ACC1_ACC_PNR, 52); + err |= lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_TXRX_CFG2_FINE_SYNCH_EN, 0); + err |= lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_DETECT_ACC1_ACC_PNR, 52); break; case 11: - lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_TXRX_CFG2_FINE_SYNCH_EN, 0); - lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_DETECT_ACC1_ACC_PNR, 52); + err |= lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_TXRX_CFG2_FINE_SYNCH_EN, 0); + err |= lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_DETECT_ACC1_ACC_PNR, 52); break; case 12: - lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_TXRX_CFG2_FINE_SYNCH_EN, 0); - lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_DETECT_ACC1_ACC_PNR, 52); + err |= lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_TXRX_CFG2_FINE_SYNCH_EN, 0); + err |= lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_DETECT_ACC1_ACC_PNR, 52); break; default: printf("ERROR: Failed to configure LoRa service modem correlators\n"); return LGW_REG_ERROR; } - return LGW_REG_SUCCESS; + return err; } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ @@ -724,55 +883,92 @@ int sx1302_lora_service_correlator_configure(struct lgw_conf_rxif_s * cfg) { int sx1302_lora_modem_configure(uint32_t radio_freq_hz) { uint16_t mantissa = 0; uint8_t exponent = 0; + int err = LGW_REG_SUCCESS; - /* TODO: test if channel is enabled */ - - lgw_reg_w(SX1302_REG_RX_TOP_DC_NOTCH_CFG1_ENABLE, 0x00); - lgw_reg_w(SX1302_REG_RX_TOP_RX_DFE_AGC1_FORCE_DEFAULT_FIR, 0x01); - lgw_reg_w(SX1302_REG_RX_TOP_DAGC_CFG_GAIN_DROP_COMP, 0x01); - lgw_reg_w(SX1302_REG_RX_TOP_DAGC_CFG_TARGET_LVL, 0x01); + err |= lgw_reg_w(SX1302_REG_RX_TOP_DC_NOTCH_CFG1_ENABLE, 0x00); + err |= lgw_reg_w(SX1302_REG_RX_TOP_RX_DFE_AGC1_FORCE_DEFAULT_FIR, 0x01); + err |= lgw_reg_w(SX1302_REG_RX_TOP_DAGC_CFG_GAIN_DROP_COMP, 0x01); + err |= lgw_reg_w(SX1302_REG_RX_TOP_DAGC_CFG_TARGET_LVL, 0x01); /* Enable full modems */ DEBUG_MSG("Configuring 8 full-SF modems\n"); - lgw_reg_w(SX1302_REG_OTP_MODEM_EN_0_MODEM_EN, 0xFF); + err |= lgw_reg_w(SX1302_REG_OTP_MODEM_EN_0_MODEM_EN, 0xFF); /* Enable limited modems */ DEBUG_MSG("Configuring 8 limited-SF modems\n"); - lgw_reg_w(SX1302_REG_OTP_MODEM_EN_1_MODEM_EN, 0xFF); + err |= lgw_reg_w(SX1302_REG_OTP_MODEM_EN_1_MODEM_EN, 0xFF); /* Configure coarse sync between correlators and modems */ - lgw_reg_w(SX1302_REG_RX_TOP_MODEM_SYNC_DELTA_MSB_MODEM_SYNC_DELTA, 0); - lgw_reg_w(SX1302_REG_RX_TOP_MODEM_SYNC_DELTA_LSB_MODEM_SYNC_DELTA, 126); + err |= lgw_reg_w(SX1302_REG_RX_TOP_MODEM_SYNC_DELTA_MSB_MODEM_SYNC_DELTA, 0); + err |= lgw_reg_w(SX1302_REG_RX_TOP_MODEM_SYNC_DELTA_LSB_MODEM_SYNC_DELTA, 126); /* Configure fine sync offset for each channel */ - lgw_reg_w(SX1302_REG_ARB_MCU_CHANNEL_SYNC_OFFSET_01_CHANNEL_0_OFFSET, 1); - lgw_reg_w(SX1302_REG_ARB_MCU_CHANNEL_SYNC_OFFSET_01_CHANNEL_1_OFFSET, 5); - lgw_reg_w(SX1302_REG_ARB_MCU_CHANNEL_SYNC_OFFSET_23_CHANNEL_2_OFFSET, 9); - lgw_reg_w(SX1302_REG_ARB_MCU_CHANNEL_SYNC_OFFSET_23_CHANNEL_3_OFFSET, 13); - lgw_reg_w(SX1302_REG_ARB_MCU_CHANNEL_SYNC_OFFSET_45_CHANNEL_4_OFFSET, 1); - lgw_reg_w(SX1302_REG_ARB_MCU_CHANNEL_SYNC_OFFSET_45_CHANNEL_5_OFFSET, 5); - lgw_reg_w(SX1302_REG_ARB_MCU_CHANNEL_SYNC_OFFSET_67_CHANNEL_6_OFFSET, 9); - lgw_reg_w(SX1302_REG_ARB_MCU_CHANNEL_SYNC_OFFSET_67_CHANNEL_7_OFFSET, 13); + err |= lgw_reg_w(SX1302_REG_ARB_MCU_CHANNEL_SYNC_OFFSET_01_CHANNEL_0_OFFSET, 1); + err |= lgw_reg_w(SX1302_REG_ARB_MCU_CHANNEL_SYNC_OFFSET_01_CHANNEL_1_OFFSET, 5); + err |= lgw_reg_w(SX1302_REG_ARB_MCU_CHANNEL_SYNC_OFFSET_23_CHANNEL_2_OFFSET, 9); + err |= lgw_reg_w(SX1302_REG_ARB_MCU_CHANNEL_SYNC_OFFSET_23_CHANNEL_3_OFFSET, 13); + err |= lgw_reg_w(SX1302_REG_ARB_MCU_CHANNEL_SYNC_OFFSET_45_CHANNEL_4_OFFSET, 1); + err |= lgw_reg_w(SX1302_REG_ARB_MCU_CHANNEL_SYNC_OFFSET_45_CHANNEL_5_OFFSET, 5); + err |= lgw_reg_w(SX1302_REG_ARB_MCU_CHANNEL_SYNC_OFFSET_67_CHANNEL_6_OFFSET, 9); + err |= lgw_reg_w(SX1302_REG_ARB_MCU_CHANNEL_SYNC_OFFSET_67_CHANNEL_7_OFFSET, 13); /* Configure PPM offset */ - lgw_reg_w(SX1302_REG_RX_TOP_MODEM_PPM_OFFSET1_PPM_OFFSET_SF5, 0x00); - lgw_reg_w(SX1302_REG_RX_TOP_MODEM_PPM_OFFSET1_PPM_OFFSET_SF6, 0x00); - lgw_reg_w(SX1302_REG_RX_TOP_MODEM_PPM_OFFSET1_PPM_OFFSET_SF7, 0x00); - lgw_reg_w(SX1302_REG_RX_TOP_MODEM_PPM_OFFSET1_PPM_OFFSET_SF8, 0x00); - lgw_reg_w(SX1302_REG_RX_TOP_MODEM_PPM_OFFSET2_PPM_OFFSET_SF9, 0x00); - lgw_reg_w(SX1302_REG_RX_TOP_MODEM_PPM_OFFSET2_PPM_OFFSET_SF10, 0x00); - lgw_reg_w(SX1302_REG_RX_TOP_MODEM_PPM_OFFSET2_PPM_OFFSET_SF11, 0x01); - lgw_reg_w(SX1302_REG_RX_TOP_MODEM_PPM_OFFSET2_PPM_OFFSET_SF12, 0x01); + err |= lgw_reg_w(SX1302_REG_RX_TOP_MODEM_PPM_OFFSET1_PPM_OFFSET_SF5, 0x00); + err |= lgw_reg_w(SX1302_REG_RX_TOP_MODEM_PPM_OFFSET1_PPM_OFFSET_SF6, 0x00); + err |= lgw_reg_w(SX1302_REG_RX_TOP_MODEM_PPM_OFFSET1_PPM_OFFSET_SF7, 0x00); + err |= lgw_reg_w(SX1302_REG_RX_TOP_MODEM_PPM_OFFSET1_PPM_OFFSET_SF8, 0x00); + err |= lgw_reg_w(SX1302_REG_RX_TOP_MODEM_PPM_OFFSET2_PPM_OFFSET_SF9, 0x00); + err |= lgw_reg_w(SX1302_REG_RX_TOP_MODEM_PPM_OFFSET2_PPM_OFFSET_SF10, 0x00); + err |= lgw_reg_w(SX1302_REG_RX_TOP_MODEM_PPM_OFFSET2_PPM_OFFSET_SF11, 0x01); + err |= lgw_reg_w(SX1302_REG_RX_TOP_MODEM_PPM_OFFSET2_PPM_OFFSET_SF12, 0x01); /* Improve SF5 and SF6 performances */ - lgw_reg_w(SX1302_REG_RX_TOP_FINE_TIMING_A_1_GAIN_P_AUTO, 3); // Default is 1 - lgw_reg_w(SX1302_REG_RX_TOP_FINE_TIMING_A_1_GAIN_P_PAYLOAD, 3); // Default is 2 + err |= lgw_reg_w(SX1302_REG_RX_TOP_FINE_TIMING_A_1_GAIN_P_AUTO, 3); // Default is 1 + err |= lgw_reg_w(SX1302_REG_RX_TOP_FINE_TIMING_A_1_GAIN_P_PAYLOAD, 3); // Default is 2 /* Improve SF11/SF12 performances */ - lgw_reg_w(SX1302_REG_RX_TOP_FINE_TIMING_A_5_GAIN_I_EN_SF11, 1); - lgw_reg_w(SX1302_REG_RX_TOP_FINE_TIMING_A_5_GAIN_I_EN_SF12, 1); - lgw_reg_w(SX1302_REG_RX_TOP_FINE_TIMING_B_5_GAIN_I_EN_SF11, 1); - lgw_reg_w(SX1302_REG_RX_TOP_FINE_TIMING_B_5_GAIN_I_EN_SF12, 1); + err |= lgw_reg_w(SX1302_REG_RX_TOP_FINE_TIMING_A_5_GAIN_I_EN_SF11, 1); + err |= lgw_reg_w(SX1302_REG_RX_TOP_FINE_TIMING_A_5_GAIN_I_EN_SF12, 1); + + /* Set threshold for 1bin correction (CAN-314) */ + err |= lgw_reg_w(SX1302_REG_RX_TOP_FREQ_TRACK4_FREQ_SYNCH_THR, 15); + + /* Configure modems for best tracking (best demodulation) */ + err |= lgw_reg_w(SX1302_REG_RX_TOP_FREQ_TRACK_A_0_FREQ_TRACK_EN_SF5, RX_FREQ_TRACK_AUTO); + err |= lgw_reg_w(SX1302_REG_RX_TOP_FREQ_TRACK_A_0_FREQ_TRACK_EN_SF6, RX_FREQ_TRACK_AUTO); + err |= lgw_reg_w(SX1302_REG_RX_TOP_FREQ_TRACK_A_0_FREQ_TRACK_EN_SF7, RX_FREQ_TRACK_AUTO); + err |= lgw_reg_w(SX1302_REG_RX_TOP_FREQ_TRACK_A_0_FREQ_TRACK_EN_SF8, RX_FREQ_TRACK_AUTO); + err |= lgw_reg_w(SX1302_REG_RX_TOP_FREQ_TRACK_A_1_FREQ_TRACK_EN_SF9, RX_FREQ_TRACK_AUTO); + err |= lgw_reg_w(SX1302_REG_RX_TOP_FREQ_TRACK_A_1_FREQ_TRACK_EN_SF10, RX_FREQ_TRACK_AUTO); + err |= lgw_reg_w(SX1302_REG_RX_TOP_FREQ_TRACK_A_1_FREQ_TRACK_EN_SF11, RX_FREQ_TRACK_AUTO); + err |= lgw_reg_w(SX1302_REG_RX_TOP_FREQ_TRACK_A_1_FREQ_TRACK_EN_SF12, RX_FREQ_TRACK_AUTO); + + /* Configure modems for best timestamping (only valid when double demodulation is enabled) */ + err |= lgw_reg_w(SX1302_REG_RX_TOP_FREQ_TRACK_B_0_FREQ_TRACK_EN_SF5, RX_FREQ_TRACK_OFF); + err |= lgw_reg_w(SX1302_REG_RX_TOP_FREQ_TRACK_B_0_FREQ_TRACK_EN_SF6, RX_FREQ_TRACK_OFF); + err |= lgw_reg_w(SX1302_REG_RX_TOP_FREQ_TRACK_B_0_FREQ_TRACK_EN_SF7, RX_FREQ_TRACK_OFF); + err |= lgw_reg_w(SX1302_REG_RX_TOP_FREQ_TRACK_B_0_FREQ_TRACK_EN_SF8, RX_FREQ_TRACK_OFF); + err |= lgw_reg_w(SX1302_REG_RX_TOP_FREQ_TRACK_B_1_FREQ_TRACK_EN_SF9, RX_FREQ_TRACK_OFF); + err |= lgw_reg_w(SX1302_REG_RX_TOP_FREQ_TRACK_B_1_FREQ_TRACK_EN_SF10, RX_FREQ_TRACK_OFF); + err |= lgw_reg_w(SX1302_REG_RX_TOP_FREQ_TRACK_B_1_FREQ_TRACK_EN_SF11, RX_FREQ_TRACK_OFF); + err |= lgw_reg_w(SX1302_REG_RX_TOP_FREQ_TRACK_B_1_FREQ_TRACK_EN_SF12, RX_FREQ_TRACK_OFF); + /* -- */ + err |= lgw_reg_w(SX1302_REG_RX_TOP_FINE_TIMING_B_5_GAIN_I_EN_SF11, 0); + err |= lgw_reg_w(SX1302_REG_RX_TOP_FINE_TIMING_B_5_GAIN_I_EN_SF12, 0); + err |= lgw_reg_w(SX1302_REG_RX_TOP_FINE_TIMING_B_0_ROUNDING, 1); + err |= lgw_reg_w(SX1302_REG_RX_TOP_FINE_TIMING_B_0_MODE, RX_FINE_TIMING_MODE_LINEAR); + /* -- */ + err |= lgw_reg_w(SX1302_REG_RX_TOP_FINE_TIMING_B_1_GAIN_P_AUTO, 0); + err |= lgw_reg_w(SX1302_REG_RX_TOP_FINE_TIMING_B_1_GAIN_P_PREAMB, 6); + err |= lgw_reg_w(SX1302_REG_RX_TOP_FINE_TIMING_B_1_GAIN_P_PAYLOAD, 2); + /* -- */ + err |= lgw_reg_w(SX1302_REG_RX_TOP_FINE_TIMING_B_2_GAIN_I_AUTO, 0); + err |= lgw_reg_w(SX1302_REG_RX_TOP_FINE_TIMING_B_2_GAIN_I_PREAMB, 1); + err |= lgw_reg_w(SX1302_REG_RX_TOP_FINE_TIMING_B_2_GAIN_I_PAYLOAD, 0); + + /* Set preamble size to 10 (to handle 12 for SF5/SF6 and 8 for SF7->SF12) */ + err |= lgw_reg_w(SX1302_REG_RX_TOP_TXRX_CFG7_PREAMBLE_SYMB_NB, 0); /* MSB */ + err |= lgw_reg_w(SX1302_REG_RX_TOP_TXRX_CFG6_PREAMBLE_SYMB_NB, 10); /* LSB */ /* Freq2TimeDrift computation */ if (calculate_freq_to_time_drift(radio_freq_hz, BW_125KHZ, &mantissa, &exponent) != 0) { @@ -780,15 +976,17 @@ int sx1302_lora_modem_configure(uint32_t radio_freq_hz) { return LGW_REG_ERROR; } DEBUG_PRINTF("Freq2TimeDrift MultiSF: Mantissa = %d (0x%02X, 0x%02X), Exponent = %d (0x%02X)\n", mantissa, (mantissa >> 8) & 0x00FF, (mantissa) & 0x00FF, exponent, exponent); - lgw_reg_w(SX1302_REG_RX_TOP_FREQ_TO_TIME0_FREQ_TO_TIME_DRIFT_MANT, (mantissa >> 8) & 0x00FF); - lgw_reg_w(SX1302_REG_RX_TOP_FREQ_TO_TIME1_FREQ_TO_TIME_DRIFT_MANT, (mantissa) & 0x00FF); - lgw_reg_w(SX1302_REG_RX_TOP_FREQ_TO_TIME2_FREQ_TO_TIME_DRIFT_EXP, exponent); + err |= lgw_reg_w(SX1302_REG_RX_TOP_FREQ_TO_TIME0_FREQ_TO_TIME_DRIFT_MANT, (mantissa >> 8) & 0x00FF); + err |= lgw_reg_w(SX1302_REG_RX_TOP_FREQ_TO_TIME1_FREQ_TO_TIME_DRIFT_MANT, (mantissa) & 0x00FF); + err |= lgw_reg_w(SX1302_REG_RX_TOP_FREQ_TO_TIME2_FREQ_TO_TIME_DRIFT_EXP, exponent); /* Time drift compensation */ - lgw_reg_w(SX1302_REG_RX_TOP_FREQ_TO_TIME3_FREQ_TO_TIME_INVERT_TIME_SYMB, 1); + err |= lgw_reg_w(SX1302_REG_RX_TOP_FREQ_TO_TIME3_FREQ_TO_TIME_INVERT_TIME_SYMB, 1); + /* DFT peak mode : set to AUTO, check timestamp_counter_correction() if changed */ + err |= lgw_reg_w(SX1302_REG_RX_TOP_RX_CFG0_DFT_PEAK_EN, RX_DFT_PEAK_MODE_AUTO); - return LGW_REG_SUCCESS; + return err; } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ @@ -796,40 +994,42 @@ int sx1302_lora_modem_configure(uint32_t radio_freq_hz) { int sx1302_lora_service_modem_configure(struct lgw_conf_rxif_s * cfg, uint32_t radio_freq_hz) { uint16_t mantissa = 0; uint8_t exponent = 0; + uint8_t preamble_nb_symb; + int err = LGW_REG_SUCCESS; - lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_DC_NOTCH_CFG1_ENABLE, 0x00); - lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_RX_DFE_AGC1_FORCE_DEFAULT_FIR, 0x01); - lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_DAGC_CFG_GAIN_DROP_COMP, 0x01); - lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_DAGC_CFG_TARGET_LVL, 0x01); + err |= lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_DC_NOTCH_CFG1_ENABLE, 0x00); + err |= lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_RX_DFE_AGC1_FORCE_DEFAULT_FIR, 0x01); + err |= lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_DAGC_CFG_GAIN_DROP_COMP, 0x01); + err |= lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_DAGC_CFG_TARGET_LVL, 0x01); - lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FINE_TIMING1_GAIN_P_AUTO, 0x03); - lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FINE_TIMING2_GAIN_I_PAYLOAD, 0x03); + err |= lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FINE_TIMING1_GAIN_P_AUTO, 0x03); + err |= lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FINE_TIMING2_GAIN_I_PAYLOAD, 0x03); switch (cfg->datarate) { case DR_LORA_SF5: case DR_LORA_SF6: - lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FINE_TIMING1_GAIN_P_PREAMB, 0x04); // Default value - lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FINE_TIMING2_GAIN_I_EN, 0x00); // Default value + err |= lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FINE_TIMING1_GAIN_P_PREAMB, 0x04); // Default value + err |= lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FINE_TIMING2_GAIN_I_EN, 0x00); // Default value break; case DR_LORA_SF7: case DR_LORA_SF8: case DR_LORA_SF9: case DR_LORA_SF10: - lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FINE_TIMING1_GAIN_P_PREAMB, 0x06); - lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FINE_TIMING2_GAIN_I_EN, 0x00); + err |= lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FINE_TIMING1_GAIN_P_PREAMB, 0x06); + err |= lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FINE_TIMING2_GAIN_I_EN, 0x00); break; case DR_LORA_SF11: case DR_LORA_SF12: - lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FINE_TIMING1_GAIN_P_PREAMB, 0x07); + err |= lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FINE_TIMING1_GAIN_P_PREAMB, 0x07); switch (cfg->bandwidth) { case BW_125KHZ: - lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FINE_TIMING2_GAIN_I_EN, 0x01); + err |= lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FINE_TIMING2_GAIN_I_EN, 0x01); break; case BW_250KHZ: - lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FINE_TIMING2_GAIN_I_EN, 0x02); + err |= lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FINE_TIMING2_GAIN_I_EN, 0x02); break; case BW_500KHZ: - lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FINE_TIMING2_GAIN_I_EN, 0x03); + err |= lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FINE_TIMING2_GAIN_I_EN, 0x03); break; default: printf("ERROR: unsupported bandwidth %u for LoRa Service modem\n", cfg->bandwidth); @@ -841,109 +1041,127 @@ int sx1302_lora_service_modem_configure(struct lgw_conf_rxif_s * cfg, uint32_t r break; } - lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_TXRX_CFG2_IMPLICIT_HEADER, (cfg->implicit_hdr == true) ? 1 : 0); - lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_TXRX_CFG2_CRC_EN, (cfg->implicit_crc_en == true) ? 1 : 0); - lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_TXRX_CFG1_CODING_RATE, cfg->implicit_coderate); - lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_TXRX_CFG3_PAYLOAD_LENGTH, cfg->implicit_payload_length); + err |= lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_TXRX_CFG2_IMPLICIT_HEADER, (cfg->implicit_hdr == true) ? 1 : 0); + err |= lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_TXRX_CFG2_CRC_EN, (cfg->implicit_crc_en == true) ? 1 : 0); + err |= lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_TXRX_CFG1_CODING_RATE, cfg->implicit_coderate); + err |= lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_TXRX_CFG3_PAYLOAD_LENGTH, cfg->implicit_payload_length); - lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_TXRX_CFG0_MODEM_SF, cfg->datarate); - lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_TXRX_CFG0_MODEM_BW, cfg->bandwidth); - lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_TXRX_CFG1_PPM_OFFSET, SET_PPM_ON(cfg->bandwidth, cfg->datarate)); + err |= lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_TXRX_CFG0_MODEM_SF, cfg->datarate); + err |= lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_TXRX_CFG0_MODEM_BW, cfg->bandwidth); + err |= lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_TXRX_CFG1_PPM_OFFSET, SET_PPM_ON(cfg->bandwidth, cfg->datarate)); - //SX1302_REG_RX_TOP_LORA_SERVICE_FSK_TXRX_CFG6_PREAMBLE_SYMB_NB - //SX1302_REG_RX_TOP_LORA_SERVICE_FSK_TXRX_CFG7_PREAMBLE_SYMB_NB + /* Set preamble size to 8 for SF7->SF12 and to 12 for SF5->SF6 (aligned with end-device drivers) */ + if ((cfg->datarate == DR_LORA_SF5) || (cfg->datarate == DR_LORA_SF6)) { + preamble_nb_symb = 12; + } else { + preamble_nb_symb = 8; + } + printf("INFO: LoRa Service modem: configuring preamble size to %u symbols\n", preamble_nb_symb); + err |= lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_TXRX_CFG7_PREAMBLE_SYMB_NB, (preamble_nb_symb >> 8) & 0xFF); /* MSB */ + err |= lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_TXRX_CFG6_PREAMBLE_SYMB_NB, (preamble_nb_symb >> 0) & 0xFF); /* LSB */ /* Freq2TimeDrift computation */ if (calculate_freq_to_time_drift(radio_freq_hz, cfg->bandwidth, &mantissa, &exponent) != 0) { printf("ERROR: failed to calculate frequency to time drift for LoRa service modem\n"); return LGW_REG_ERROR; } - lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FREQ_TO_TIME0_FREQ_TO_TIME_DRIFT_MANT, (mantissa >> 8) & 0x00FF); - lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FREQ_TO_TIME1_FREQ_TO_TIME_DRIFT_MANT, (mantissa) & 0x00FF); - lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FREQ_TO_TIME2_FREQ_TO_TIME_DRIFT_EXP, exponent); + err |= lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FREQ_TO_TIME0_FREQ_TO_TIME_DRIFT_MANT, (mantissa >> 8) & 0x00FF); + err |= lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FREQ_TO_TIME1_FREQ_TO_TIME_DRIFT_MANT, (mantissa) & 0x00FF); + err |= lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FREQ_TO_TIME2_FREQ_TO_TIME_DRIFT_EXP, exponent); DEBUG_PRINTF("Freq2TimeDrift SingleSF: Mantissa = %d (0x%02X, 0x%02X), Exponent = %d (0x%02X)\n", mantissa, (mantissa >> 8) & 0x00FF, (mantissa) & 0x00FF, exponent, exponent); /* Time drift compensation */ - lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FREQ_TO_TIME3_FREQ_TO_TIME_INVERT_TIME_SYMB, 1); + err |= lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FREQ_TO_TIME3_FREQ_TO_TIME_INVERT_TIME_SYMB, 1); - lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_RX_DFE_AGC2_DAGC_IN_COMP, 1); + err |= lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_RX_DFE_AGC2_DAGC_IN_COMP, 1); - lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_TXRX_CFG1_MODEM_EN, 1); - lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_TXRX_CFG2_CADRXTX, 1); + err |= lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_TXRX_CFG1_MODEM_EN, 1); + err |= lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_TXRX_CFG2_CADRXTX, 1); - lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_TXRX_CFG2_MODEM_START, 1); + err |= lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_TXRX_CFG2_MODEM_START, 1); - return LGW_REG_SUCCESS; + /* DFT peak mode : set to AUTO, check timestamp_counter_correction() if changed */ + err |= lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_RX_CFG0_DFT_PEAK_EN, RX_DFT_PEAK_MODE_AUTO); + + return err; } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ int sx1302_modem_enable(void) { + int err = LGW_REG_SUCCESS; + /* Enable LoRa multi-SF modems */ - lgw_reg_w(SX1302_REG_COMMON_GEN_CONCENTRATOR_MODEM_ENABLE, 0x01); + err |= lgw_reg_w(SX1302_REG_COMMON_GEN_CONCENTRATOR_MODEM_ENABLE, 0x01); /* Enable LoRa service modem */ - lgw_reg_w(SX1302_REG_COMMON_GEN_MBWSSF_MODEM_ENABLE, 0x01); + err |= lgw_reg_w(SX1302_REG_COMMON_GEN_MBWSSF_MODEM_ENABLE, 0x01); /* Enable FSK modem */ - lgw_reg_w(SX1302_REG_COMMON_GEN_FSK_MODEM_ENABLE, 0x01); + err |= lgw_reg_w(SX1302_REG_COMMON_GEN_FSK_MODEM_ENABLE, 0x01); /* Enable RX */ - lgw_reg_w(SX1302_REG_COMMON_GEN_GLOBAL_EN, 0x01); + err |= lgw_reg_w(SX1302_REG_COMMON_GEN_GLOBAL_EN, 0x01); - return LGW_REG_SUCCESS; + return err; } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ int sx1302_lora_syncword(bool public, uint8_t lora_service_sf) { + int err = LGW_REG_SUCCESS; + /* Multi-SF modem configuration */ DEBUG_MSG("INFO: configuring LoRa (Multi-SF) SF5->SF6 with syncword PRIVATE (0x12)\n"); - lgw_reg_w(SX1302_REG_RX_TOP_FRAME_SYNCH0_SF5_PEAK1_POS_SF5, 2); - lgw_reg_w(SX1302_REG_RX_TOP_FRAME_SYNCH1_SF5_PEAK2_POS_SF5, 4); - lgw_reg_w(SX1302_REG_RX_TOP_FRAME_SYNCH0_SF6_PEAK1_POS_SF6, 2); - lgw_reg_w(SX1302_REG_RX_TOP_FRAME_SYNCH1_SF6_PEAK2_POS_SF6, 4); + err |= lgw_reg_w(SX1302_REG_RX_TOP_FRAME_SYNCH0_SF5_PEAK1_POS_SF5, 2); + err |= lgw_reg_w(SX1302_REG_RX_TOP_FRAME_SYNCH1_SF5_PEAK2_POS_SF5, 4); + err |= lgw_reg_w(SX1302_REG_RX_TOP_FRAME_SYNCH0_SF6_PEAK1_POS_SF6, 2); + err |= lgw_reg_w(SX1302_REG_RX_TOP_FRAME_SYNCH1_SF6_PEAK2_POS_SF6, 4); if (public == true) { DEBUG_MSG("INFO: configuring LoRa (Multi-SF) SF7->SF12 with syncword PUBLIC (0x34)\n"); - lgw_reg_w(SX1302_REG_RX_TOP_FRAME_SYNCH0_SF7TO12_PEAK1_POS_SF7TO12, 6); - lgw_reg_w(SX1302_REG_RX_TOP_FRAME_SYNCH1_SF7TO12_PEAK2_POS_SF7TO12, 8); + err |= lgw_reg_w(SX1302_REG_RX_TOP_FRAME_SYNCH0_SF7TO12_PEAK1_POS_SF7TO12, 6); + err |= lgw_reg_w(SX1302_REG_RX_TOP_FRAME_SYNCH1_SF7TO12_PEAK2_POS_SF7TO12, 8); } else { DEBUG_MSG("INFO: configuring LoRa (Multi-SF) SF7->SF12 with syncword PRIVATE (0x12)\n"); - lgw_reg_w(SX1302_REG_RX_TOP_FRAME_SYNCH0_SF7TO12_PEAK1_POS_SF7TO12, 2); - lgw_reg_w(SX1302_REG_RX_TOP_FRAME_SYNCH1_SF7TO12_PEAK2_POS_SF7TO12, 4); + err |= lgw_reg_w(SX1302_REG_RX_TOP_FRAME_SYNCH0_SF7TO12_PEAK1_POS_SF7TO12, 2); + err |= lgw_reg_w(SX1302_REG_RX_TOP_FRAME_SYNCH1_SF7TO12_PEAK2_POS_SF7TO12, 4); } /* LoRa Service modem configuration */ if ((public == false) || (lora_service_sf == DR_LORA_SF5) || (lora_service_sf == DR_LORA_SF6)) { DEBUG_PRINTF("INFO: configuring LoRa (Service) SF%u with syncword PRIVATE (0x12)\n", lora_service_sf); - lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FRAME_SYNCH0_PEAK1_POS, 2); - lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FRAME_SYNCH1_PEAK2_POS, 4); + err |= lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FRAME_SYNCH0_PEAK1_POS, 2); + err |= lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FRAME_SYNCH1_PEAK2_POS, 4); } else { DEBUG_PRINTF("INFO: configuring LoRa (Service) SF%u with syncword PUBLIC (0x34)\n", lora_service_sf); - lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FRAME_SYNCH0_PEAK1_POS, 6); - lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FRAME_SYNCH1_PEAK2_POS, 8); + err |= lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FRAME_SYNCH0_PEAK1_POS, 6); + err |= lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_FRAME_SYNCH1_PEAK2_POS, 8); } - return LGW_REG_SUCCESS; + return err; } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ uint32_t sx1302_timestamp_counter(bool pps) { - return timestamp_counter_get(&counter_us, pps); + uint32_t inst_cnt, pps_cnt; + timestamp_counter_get(&counter_us, &inst_cnt, &pps_cnt); + return ((pps == true) ? pps_cnt : inst_cnt); } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ int sx1302_gps_enable(bool enable) { + int err = LGW_REG_SUCCESS; + if (enable == true) { - lgw_reg_w(SX1302_REG_TIMESTAMP_GPS_CTRL_GPS_EN, 1); - lgw_reg_w(SX1302_REG_TIMESTAMP_GPS_CTRL_GPS_POL, 1); /* invert polarity for PPS */ + err |= lgw_reg_w(SX1302_REG_TIMESTAMP_GPS_CTRL_GPS_EN, 1); + err |= lgw_reg_w(SX1302_REG_TIMESTAMP_GPS_CTRL_GPS_POL, 1); /* invert polarity for PPS */ } else { - lgw_reg_w(SX1302_REG_TIMESTAMP_GPS_CTRL_GPS_EN, 0); + err |= lgw_reg_w(SX1302_REG_TIMESTAMP_GPS_CTRL_GPS_EN, 0); } - return LGW_HAL_SUCCESS; + return err; } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ @@ -951,57 +1169,52 @@ int sx1302_gps_enable(bool enable) { int sx1302_agc_load_firmware(const uint8_t *firmware) { int32_t val; uint8_t fw_check[MCU_FW_SIZE]; - int32_t gpio_sel = MCU_AGC; - - /* Configure GPIO to let AGC MCU access board LEDs */ - lgw_reg_w(SX1302_REG_GPIO_GPIO_SEL_0_SELECTION, gpio_sel); - lgw_reg_w(SX1302_REG_GPIO_GPIO_SEL_1_SELECTION, gpio_sel); - lgw_reg_w(SX1302_REG_GPIO_GPIO_SEL_2_SELECTION, gpio_sel); - lgw_reg_w(SX1302_REG_GPIO_GPIO_SEL_3_SELECTION, gpio_sel); - lgw_reg_w(SX1302_REG_GPIO_GPIO_SEL_4_SELECTION, gpio_sel); - lgw_reg_w(SX1302_REG_GPIO_GPIO_SEL_5_SELECTION, gpio_sel); - lgw_reg_w(SX1302_REG_GPIO_GPIO_SEL_6_SELECTION, gpio_sel); - lgw_reg_w(SX1302_REG_GPIO_GPIO_SEL_7_SELECTION, gpio_sel); - lgw_reg_w(SX1302_REG_GPIO_GPIO_DIR_L_DIRECTION, 0xFF); /* GPIO output direction */ + int err = LGW_REG_SUCCESS; /* Take control over AGC MCU */ - lgw_reg_w(SX1302_REG_AGC_MCU_CTRL_MCU_CLEAR, 0x01); - lgw_reg_w(SX1302_REG_AGC_MCU_CTRL_HOST_PROG, 0x01); - lgw_reg_w(SX1302_REG_COMMON_PAGE_PAGE, 0x00); + err |= lgw_reg_w(SX1302_REG_AGC_MCU_CTRL_MCU_CLEAR, 0x01); + err |= lgw_reg_w(SX1302_REG_AGC_MCU_CTRL_HOST_PROG, 0x01); + err |= lgw_reg_w(SX1302_REG_COMMON_PAGE_PAGE, 0x00); /* Write AGC fw in AGC MEM */ - lgw_mem_wb(AGC_MEM_ADDR, firmware, MCU_FW_SIZE); + err |= lgw_mem_wb(AGC_MEM_ADDR, firmware, MCU_FW_SIZE); /* Read back and check */ - lgw_mem_rb(AGC_MEM_ADDR, fw_check, MCU_FW_SIZE, false); + err |= lgw_mem_rb(AGC_MEM_ADDR, fw_check, MCU_FW_SIZE, false); if (memcmp(firmware, fw_check, sizeof fw_check) != 0) { - printf ("ERROR: Failed to load fw\n"); - return -1; + printf("ERROR: AGC fw read/write check failed\n"); + return LGW_REG_ERROR; } /* Release control over AGC MCU */ - lgw_reg_w(SX1302_REG_AGC_MCU_CTRL_HOST_PROG, 0x00); - lgw_reg_w(SX1302_REG_AGC_MCU_CTRL_MCU_CLEAR, 0x00); + err |= lgw_reg_w(SX1302_REG_AGC_MCU_CTRL_HOST_PROG, 0x00); + err |= lgw_reg_w(SX1302_REG_AGC_MCU_CTRL_MCU_CLEAR, 0x00); - lgw_reg_r(SX1302_REG_AGC_MCU_CTRL_PARITY_ERROR, &val); - DEBUG_PRINTF("AGC fw loaded (parity error:0x%02X)\n", val); + err |= lgw_reg_r(SX1302_REG_AGC_MCU_CTRL_PARITY_ERROR, &val); + if (val != 0) { + printf("ERROR: Failed to load AGC fw: parity error check failed\n"); + return LGW_REG_ERROR; + } + DEBUG_MSG("AGC fw loaded\n"); - return LGW_HAL_SUCCESS; + return err; } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ int sx1302_agc_status(uint8_t* status) { int32_t val; + int err = LGW_REG_SUCCESS; - if (lgw_reg_r(SX1302_REG_AGC_MCU_MCU_AGC_STATUS_MCU_AGC_STATUS, &val) != LGW_REG_SUCCESS) { + err = lgw_reg_r(SX1302_REG_AGC_MCU_MCU_AGC_STATUS_MCU_AGC_STATUS, &val); + if (err != LGW_REG_SUCCESS) { printf("ERROR: Failed to get AGC status\n"); - return LGW_HAL_ERROR; + return LGW_REG_ERROR; } *status = (uint8_t)val; - return LGW_HAL_SUCCESS; + return err; } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ @@ -1010,12 +1223,13 @@ int sx1302_agc_wait_status(uint8_t status) { uint8_t val; do { - if (sx1302_agc_status(&val) != LGW_HAL_SUCCESS) { - return LGW_HAL_ERROR; + if (sx1302_agc_status(&val) != LGW_REG_SUCCESS) { + return LGW_REG_ERROR; } + /* TODO: add timeout */ } while (val != status); - return LGW_HAL_SUCCESS; + return LGW_REG_SUCCESS; } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ @@ -1027,18 +1241,18 @@ int sx1302_agc_mailbox_read(uint8_t mailbox, uint8_t* value) { /* Check parameters */ if (mailbox > 3) { printf("ERROR: invalid AGC mailbox ID\n"); - return LGW_HAL_ERROR; + return LGW_REG_ERROR; } reg = SX1302_REG_AGC_MCU_MCU_MAIL_BOX_RD_DATA_BYTE0_MCU_MAIL_BOX_RD_DATA - mailbox; if (lgw_reg_r(reg, &val) != LGW_REG_SUCCESS) { printf("ERROR: failed to read AGC mailbox\n"); - return LGW_HAL_ERROR; + return LGW_REG_ERROR; } *value = (uint8_t)val; - return LGW_HAL_SUCCESS; + return LGW_REG_SUCCESS; } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ @@ -1049,23 +1263,25 @@ int sx1302_agc_mailbox_write(uint8_t mailbox, uint8_t value) { /* Check parameters */ if (mailbox > 3) { printf("ERROR: invalid AGC mailbox ID\n"); - return LGW_HAL_ERROR; + return LGW_REG_ERROR; } reg = SX1302_REG_AGC_MCU_MCU_MAIL_BOX_WR_DATA_BYTE0_MCU_MAIL_BOX_WR_DATA - mailbox; if (lgw_reg_w(reg, (int32_t)value) != LGW_REG_SUCCESS) { printf("ERROR: failed to write AGC mailbox\n"); - return LGW_HAL_ERROR; + return LGW_REG_ERROR; } - return LGW_HAL_SUCCESS; + return LGW_REG_SUCCESS; } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ -int sx1302_agc_start(uint8_t version, lgw_radio_type_t radio_type, uint8_t ana_gain, uint8_t dec_gain, uint8_t fdd_mode) { +int sx1302_agc_start(uint8_t version, lgw_radio_type_t radio_type, uint8_t ana_gain, uint8_t dec_gain, bool full_duplex, bool lbt_enable) { uint8_t val; struct agc_gain_params_s agc_params; + uint8_t pa_start_delay; + uint8_t fdd_mode = ((full_duplex == true) ? 1 : 0); /* Check parameters */ if ((radio_type != LGW_RADIO_TYPE_SX1255) && (radio_type != LGW_RADIO_TYPE_SX1257) && (radio_type != LGW_RADIO_TYPE_SX1250)) { @@ -1079,10 +1295,12 @@ int sx1302_agc_start(uint8_t version, lgw_radio_type_t radio_type, uint8_t ana_g sx1302_agc_mailbox_read(0, &val); if (val != version) { printf("ERROR: wrong AGC fw version (%d)\n", val); - return LGW_HAL_ERROR; + return LGW_REG_ERROR; } DEBUG_PRINTF("AGC FW VERSION: %d\n", val); + /* -----------------------------------------------------------------------*/ + /* Configure Radio A gains */ sx1302_agc_mailbox_write(0, ana_gain); /* 0:auto agc*/ sx1302_agc_mailbox_write(1, dec_gain); @@ -1101,25 +1319,27 @@ int sx1302_agc_start(uint8_t version, lgw_radio_type_t radio_type, uint8_t ana_g sx1302_agc_mailbox_read(0, &val); if (val != ana_gain) { printf("ERROR: Analog gain of Radio A has not been set properly\n"); - return LGW_HAL_ERROR; + return LGW_REG_ERROR; } /* Check dec_gain setting */ sx1302_agc_mailbox_read(1, &val); if (val != dec_gain) { printf("ERROR: Decimator gain of Radio A has not been set properly\n"); - return LGW_HAL_ERROR; + return LGW_REG_ERROR; } /* Check FDD mode setting */ sx1302_agc_mailbox_read(2, &val); if (val != fdd_mode) { printf("ERROR: FDD mode of Radio A has not been set properly\n"); - return LGW_HAL_ERROR; + return LGW_REG_ERROR; } DEBUG_MSG("AGC: Radio A config done\n"); + /* -----------------------------------------------------------------------*/ + /* Configure Radio B gains */ sx1302_agc_mailbox_write(0, ana_gain); /* 0:auto agc*/ sx1302_agc_mailbox_write(1, dec_gain); @@ -1137,25 +1357,27 @@ int sx1302_agc_start(uint8_t version, lgw_radio_type_t radio_type, uint8_t ana_g sx1302_agc_mailbox_read(0, &val); if (val != ana_gain) { printf("ERROR: Analog gain of Radio B has not been set properly\n"); - return LGW_HAL_ERROR; + return LGW_REG_ERROR; } /* Check dec_gain setting */ sx1302_agc_mailbox_read(1, &val); if (val != dec_gain) { printf("ERROR: Decimator gain of Radio B has not been set properly\n"); - return LGW_HAL_ERROR; + return LGW_REG_ERROR; } /* Check FDD mode setting */ sx1302_agc_mailbox_read(2, &val); if (val != fdd_mode) { printf("ERROR: FDD mode of Radio B has not been set properly\n"); - return LGW_HAL_ERROR; + return LGW_REG_ERROR; } DEBUG_MSG("AGC: Radio B config done\n"); + /* -----------------------------------------------------------------------*/ + /* Configure AGC gains */ agc_params = (radio_type == LGW_RADIO_TYPE_SX1250) ? agc_params_sx1250 : agc_params_sx125x; @@ -1173,16 +1395,18 @@ int sx1302_agc_start(uint8_t version, lgw_radio_type_t radio_type, uint8_t ana_g sx1302_agc_mailbox_read(0, &val); if (val != agc_params.ana_min) { printf("ERROR: wrong ana_min (w:%u r:%u)\n", agc_params.ana_min, val); - return LGW_HAL_ERROR; + return LGW_REG_ERROR; } sx1302_agc_mailbox_read(1, &val); if (val != agc_params.ana_max) { printf("ERROR: ana_max (w:%u r:%u)\n", agc_params.ana_max, val); - return LGW_HAL_ERROR; + return LGW_REG_ERROR; } DEBUG_MSG("AGC: config of analog gain min/max done\n"); + /* -----------------------------------------------------------------------*/ + /* Configure analog thresholds */ sx1302_agc_mailbox_write(0, agc_params.ana_thresh_l); sx1302_agc_mailbox_write(1, agc_params.ana_thresh_h); @@ -1197,16 +1421,18 @@ int sx1302_agc_start(uint8_t version, lgw_radio_type_t radio_type, uint8_t ana_g sx1302_agc_mailbox_read(0, &val); if (val != agc_params.ana_thresh_l) { printf("ERROR: wrong ana_thresh_l (w:%u r:%u)\n", agc_params.ana_thresh_l, val); - return LGW_HAL_ERROR; + return LGW_REG_ERROR; } sx1302_agc_mailbox_read(1, &val); if (val != agc_params.ana_thresh_h) { printf("ERROR: wrong ana_thresh_h (w:%u r:%u)\n", agc_params.ana_thresh_h, val); - return LGW_HAL_ERROR; + return LGW_REG_ERROR; } DEBUG_MSG("AGC: config of analog threshold done\n"); + /* -----------------------------------------------------------------------*/ + /* Configure decimator attenuation min/max */ sx1302_agc_mailbox_write(0, agc_params.dec_attn_min); sx1302_agc_mailbox_write(1, agc_params.dec_attn_max); @@ -1221,16 +1447,18 @@ int sx1302_agc_start(uint8_t version, lgw_radio_type_t radio_type, uint8_t ana_g sx1302_agc_mailbox_read(0, &val); if (val != agc_params.dec_attn_min) { printf("ERROR: wrong dec_attn_min (w:%u r:%u)\n", agc_params.dec_attn_min, val); - return LGW_HAL_ERROR; + return LGW_REG_ERROR; } sx1302_agc_mailbox_read(1, &val); if (val != agc_params.dec_attn_max) { printf("ERROR: wrong dec_attn_max (w:%u r:%u)\n", agc_params.dec_attn_max, val); - return LGW_HAL_ERROR; + return LGW_REG_ERROR; } DEBUG_MSG("AGC: config of decimator atten min/max done\n"); + /* -----------------------------------------------------------------------*/ + /* Configure decimator attenuation thresholds */ sx1302_agc_mailbox_write(0, agc_params.dec_thresh_l); sx1302_agc_mailbox_write(1, agc_params.dec_thresh_h1); @@ -1246,21 +1474,23 @@ int sx1302_agc_start(uint8_t version, lgw_radio_type_t radio_type, uint8_t ana_g sx1302_agc_mailbox_read(0, &val); if (val != agc_params.dec_thresh_l) { printf("ERROR: wrong dec_thresh_l (w:%u r:%u)\n", agc_params.dec_thresh_l, val); - return LGW_HAL_ERROR; + return LGW_REG_ERROR; } sx1302_agc_mailbox_read(1, &val); if (val != agc_params.dec_thresh_h1) { printf("ERROR: wrong dec_thresh_h1 (w:%u r:%u)\n", agc_params.dec_thresh_h1, val); - return LGW_HAL_ERROR; + return LGW_REG_ERROR; } sx1302_agc_mailbox_read(2, &val); if (val != agc_params.dec_thresh_h2) { printf("ERROR: wrong dec_thresh_h2 (w:%u r:%u)\n", agc_params.dec_thresh_h2, val); - return LGW_HAL_ERROR; + return LGW_REG_ERROR; } DEBUG_MSG("AGC: config of decimator threshold done\n"); + /* -----------------------------------------------------------------------*/ + /* Configure channel attenuation min/max */ sx1302_agc_mailbox_write(0, agc_params.chan_attn_min); sx1302_agc_mailbox_write(1, agc_params.chan_attn_max); @@ -1275,16 +1505,18 @@ int sx1302_agc_start(uint8_t version, lgw_radio_type_t radio_type, uint8_t ana_g sx1302_agc_mailbox_read(0, &val); if (val != agc_params.chan_attn_min) { printf("ERROR: wrong chan_attn_min (w:%u r:%u)\n", agc_params.chan_attn_min, val); - return LGW_HAL_ERROR; + return LGW_REG_ERROR; } sx1302_agc_mailbox_read(1, &val); if (val != agc_params.chan_attn_max) { printf("ERROR: wrong chan_attn_max (w:%u r:%u)\n", agc_params.chan_attn_max, val); - return LGW_HAL_ERROR; + return LGW_REG_ERROR; } DEBUG_MSG("AGC: config of channel atten min/max done\n"); + /* -----------------------------------------------------------------------*/ + /* Configure channel attenuation threshold */ sx1302_agc_mailbox_write(0, agc_params.chan_thresh_l); sx1302_agc_mailbox_write(1, agc_params.chan_thresh_h); @@ -1299,18 +1531,20 @@ int sx1302_agc_start(uint8_t version, lgw_radio_type_t radio_type, uint8_t ana_g sx1302_agc_mailbox_read(0, &val); if (val != agc_params.chan_thresh_l) { printf("ERROR: wrong chan_thresh_l (w:%u r:%u)\n", agc_params.chan_thresh_l, val); - return LGW_HAL_ERROR; + return LGW_REG_ERROR; } sx1302_agc_mailbox_read(1, &val); if (val != agc_params.chan_thresh_h) { printf("ERROR: wrong chan_thresh_h (w:%u r:%u)\n", agc_params.chan_thresh_h, val); - return LGW_HAL_ERROR; + return LGW_REG_ERROR; } DEBUG_MSG("AGC: config of channel atten threshold done\n"); + /* -----------------------------------------------------------------------*/ + + /* Configure sx1250 SetPAConfig */ if (radio_type == LGW_RADIO_TYPE_SX1250) { - /* Configure sx1250 SetPAConfig */ sx1302_agc_mailbox_write(0, agc_params.deviceSel); sx1302_agc_mailbox_write(1, agc_params.hpMax); sx1302_agc_mailbox_write(2, agc_params.paDutyCycle); @@ -1325,88 +1559,124 @@ int sx1302_agc_start(uint8_t version, lgw_radio_type_t radio_type, uint8_t ana_g sx1302_agc_mailbox_read(0, &val); if (val != agc_params.deviceSel) { printf("ERROR: wrong deviceSel (w:%u r:%u)\n", agc_params.deviceSel, val); - return LGW_HAL_ERROR; + return LGW_REG_ERROR; } sx1302_agc_mailbox_read(1, &val); if (val != agc_params.hpMax) { printf("ERROR: wrong hpMax (w:%u r:%u)\n", agc_params.hpMax, val); - return LGW_HAL_ERROR; + return LGW_REG_ERROR; } sx1302_agc_mailbox_read(2, &val); if (val != agc_params.paDutyCycle) { printf("ERROR: wrong paDutyCycle (w:%u r:%u)\n", agc_params.paDutyCycle, val); - return LGW_HAL_ERROR; + return LGW_REG_ERROR; } DEBUG_MSG("AGC: config of sx1250 PA optimal settings done\n"); + } - /* notify AGC that it can resume */ - sx1302_agc_mailbox_write(3, 0x0A); - } else { - /* notify AGC that it can resume */ - sx1302_agc_mailbox_write(3, 0x09); + /* -----------------------------------------------------------------------*/ + + /* Set PA start delay */ + pa_start_delay = 8; + sx1302_agc_mailbox_write(0, pa_start_delay); /* 1 LSB = 100 µs*/ + + /* notify AGC that params have been set to mailbox */ + sx1302_agc_mailbox_write(3, 0x0A); + + /* Wait for AGC to acknoledge it has received params */ + sx1302_agc_wait_status(0x0B); + + /* Check params */ + sx1302_agc_mailbox_read(0, &val); + if (val != pa_start_delay) { + printf("ERROR: wrong PA start delay (w:%u r:%u)\n", pa_start_delay, val); + return LGW_REG_ERROR; } + DEBUG_MSG("AGC: config of PA start delay done\n"); + + /* -----------------------------------------------------------------------*/ + + /* Enable LBT if required */ + sx1302_agc_mailbox_write(0, (lbt_enable == true) ? 1 : 0); + + /* notify AGC that params have been set to mailbox */ + sx1302_agc_mailbox_write(3, 0x0B); + + /* Wait for AGC to acknoledge it has received params */ + sx1302_agc_wait_status(0x0F); + + /* Check params */ + sx1302_agc_mailbox_read(0, &val); + if ((bool)val != lbt_enable) { + printf("ERROR: wrong LBT configuration (w:%u r:%u)\n", lbt_enable, val); + return LGW_REG_ERROR; + } + + DEBUG_PRINTF("AGC: LBT is %s\n", (lbt_enable == true) ? "enabled" : "disabled"); + + /* -----------------------------------------------------------------------*/ + + /* notify AGC that configuration is finished */ + sx1302_agc_mailbox_write(3, 0x0F); + DEBUG_MSG("AGC: started\n"); - return LGW_HAL_SUCCESS; + return LGW_REG_SUCCESS; } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ int sx1302_arb_load_firmware(const uint8_t *firmware) { uint8_t fw_check[MCU_FW_SIZE]; - int32_t gpio_sel = MCU_ARB; int32_t val; - - lgw_reg_w(SX1302_REG_GPIO_GPIO_SEL_0_SELECTION, gpio_sel); - lgw_reg_w(SX1302_REG_GPIO_GPIO_SEL_1_SELECTION, gpio_sel); - lgw_reg_w(SX1302_REG_GPIO_GPIO_SEL_2_SELECTION, gpio_sel); - lgw_reg_w(SX1302_REG_GPIO_GPIO_SEL_3_SELECTION, gpio_sel); - lgw_reg_w(SX1302_REG_GPIO_GPIO_SEL_4_SELECTION, gpio_sel); - lgw_reg_w(SX1302_REG_GPIO_GPIO_SEL_5_SELECTION, gpio_sel); - lgw_reg_w(SX1302_REG_GPIO_GPIO_SEL_6_SELECTION, gpio_sel); - lgw_reg_w(SX1302_REG_GPIO_GPIO_SEL_7_SELECTION, gpio_sel); - lgw_reg_w(SX1302_REG_GPIO_GPIO_DIR_L_DIRECTION, 0xFF); /* GPIO output direction */ + int err = LGW_REG_SUCCESS; /* Take control over ARB MCU */ - lgw_reg_w(SX1302_REG_ARB_MCU_CTRL_MCU_CLEAR, 0x01); - lgw_reg_w(SX1302_REG_ARB_MCU_CTRL_HOST_PROG, 0x01); - lgw_reg_w(SX1302_REG_COMMON_PAGE_PAGE, 0x00); + err |= lgw_reg_w(SX1302_REG_ARB_MCU_CTRL_MCU_CLEAR, 0x01); + err |= lgw_reg_w(SX1302_REG_ARB_MCU_CTRL_HOST_PROG, 0x01); + err |= lgw_reg_w(SX1302_REG_COMMON_PAGE_PAGE, 0x00); /* Write ARB fw in ARB MEM */ - lgw_mem_wb(ARB_MEM_ADDR, &firmware[0], MCU_FW_SIZE); + err |= lgw_mem_wb(ARB_MEM_ADDR, &firmware[0], MCU_FW_SIZE); /* Read back and check */ - lgw_mem_rb(ARB_MEM_ADDR, fw_check, MCU_FW_SIZE, false); + err |= lgw_mem_rb(ARB_MEM_ADDR, fw_check, MCU_FW_SIZE, false); if (memcmp(firmware, fw_check, sizeof fw_check) != 0) { - printf ("ERROR: Failed to load fw\n"); - return -1; + printf("ERROR: ARB fw read/write check failed\n"); + return LGW_REG_ERROR; } /* Release control over ARB MCU */ - lgw_reg_w(SX1302_REG_ARB_MCU_CTRL_HOST_PROG, 0x00); - lgw_reg_w(SX1302_REG_ARB_MCU_CTRL_MCU_CLEAR, 0x00); + err |= lgw_reg_w(SX1302_REG_ARB_MCU_CTRL_HOST_PROG, 0x00); + err |= lgw_reg_w(SX1302_REG_ARB_MCU_CTRL_MCU_CLEAR, 0x00); - lgw_reg_r(SX1302_REG_ARB_MCU_CTRL_PARITY_ERROR, &val); - DEBUG_PRINTF("ARB fw loaded (parity error:0x%02X)\n", val); + err |= lgw_reg_r(SX1302_REG_ARB_MCU_CTRL_PARITY_ERROR, &val); + if (val != 0) { + printf("ERROR: Failed to load ARB fw: parity error check failed\n"); + return LGW_REG_ERROR; + } + DEBUG_MSG("ARB fw loaded\n"); - return 0; + return err; } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ int sx1302_arb_status(uint8_t* status) { int32_t val; + int err = LGW_REG_SUCCESS; - if (lgw_reg_r(SX1302_REG_ARB_MCU_MCU_ARB_STATUS_MCU_ARB_STATUS, &val) != LGW_REG_SUCCESS) { - printf("ERROR: Failed to get AGC status\n"); - return LGW_HAL_ERROR; + err = lgw_reg_r(SX1302_REG_ARB_MCU_MCU_ARB_STATUS_MCU_ARB_STATUS, &val); + if (err != LGW_REG_SUCCESS) { + printf("ERROR: Failed to get ARB status\n"); + return LGW_REG_ERROR; } *status = (uint8_t)val; - return LGW_HAL_SUCCESS; + return err; } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ @@ -1415,12 +1685,13 @@ int sx1302_arb_wait_status(uint8_t status) { uint8_t val; do { - if (sx1302_arb_status(&val) != LGW_HAL_SUCCESS) { - return LGW_HAL_ERROR; + if (sx1302_arb_status(&val) != LGW_REG_SUCCESS) { + return LGW_REG_ERROR; } + /* TODO: add timeout */ } while (val != status); - return LGW_HAL_SUCCESS; + return LGW_REG_SUCCESS; } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ @@ -1432,18 +1703,18 @@ int sx1302_arb_debug_read(uint8_t reg_id, uint8_t* value) { /* Check parameters */ if (reg_id > 15) { printf("ERROR: invalid ARB debug register ID\n"); - return LGW_HAL_ERROR; + return LGW_REG_ERROR; } reg = SX1302_REG_ARB_MCU_ARB_DEBUG_STS_0_ARB_DEBUG_STS_0 + reg_id; if (lgw_reg_r(reg, &val) != LGW_REG_SUCCESS) { printf("ERROR: failed to read ARB debug register\n"); - return LGW_HAL_ERROR; + return LGW_REG_ERROR; } *value = (uint8_t)val; - return LGW_HAL_SUCCESS; + return LGW_REG_SUCCESS; } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ @@ -1454,16 +1725,16 @@ int sx1302_arb_debug_write(uint8_t reg_id, uint8_t value) { /* Check parameters */ if (reg_id > 3) { printf("ERROR: invalid ARB debug register ID\n"); - return LGW_HAL_ERROR; + return LGW_REG_ERROR; } reg = SX1302_REG_ARB_MCU_ARB_DEBUG_CFG_0_ARB_DEBUG_CFG_0 + reg_id; if (lgw_reg_w(reg, (int32_t)value) != LGW_REG_SUCCESS) { printf("ERROR: failed to write ARB debug register ID\n"); - return LGW_HAL_ERROR; + return LGW_REG_ERROR; } - return LGW_HAL_SUCCESS; + return LGW_REG_SUCCESS; } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ @@ -1537,7 +1808,7 @@ void sx1302_arb_print_debug_stats(void) { /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ -int sx1302_arb_start(uint8_t version) { +int sx1302_arb_start(uint8_t version, const struct lgw_conf_ftime_s * ftime_context) { uint8_t val; /* Wait for ARB fw to be started, and VERSION available in debug registers */ @@ -1547,15 +1818,29 @@ int sx1302_arb_start(uint8_t version) { sx1302_arb_debug_read(0, &val); if (val != version) { printf("ERROR: wrong ARB fw version (%d)\n", val); - return LGW_HAL_ERROR; + return LGW_REG_ERROR; } DEBUG_PRINTF("ARB FW VERSION: %d\n", val); /* Enable/disable ARB detect/modem alloc stats for the specified SF */ sx1302_arb_set_debug_stats(true, DR_LORA_SF7); - /* 0:Disable 1:Enable double demod for different timing set (best_timestamp / best_demodulation) - Only available for SF9 -> SF12 */ - sx1302_arb_debug_write(3, 0); + /* Enable/Disable double demod for different timing set (best timestamp / best demodulation) - 1 bit per SF (LSB=SF5, MSB=SF12) => 0:Disable 1:Enable */ + if (ftime_context->enable == false) { + printf("ARB: dual demodulation disabled for all SF\n"); + sx1302_arb_debug_write(3, 0x00); /* double demod disabled for all SF */ + } else { + if (ftime_context->mode == LGW_FTIME_MODE_ALL_SF) { + printf("ARB: dual demodulation enabled for all SF\n"); + sx1302_arb_debug_write(3, 0xFF); /* double demod enabled for all SF */ + } else if (ftime_context->mode == LGW_FTIME_MODE_HIGH_CAPACITY) { + printf("ARB: dual demodulation enabled for SF5 -> SF10\n"); + sx1302_arb_debug_write(3, 0x3F); /* double demod enabled for SF10 <- SF5 */ + } else { + printf("ERROR: fine timestamp mode is not supported (%d)\n", ftime_context->mode); + return LGW_REG_ERROR; + } + } /* Set double detect packet filtering threshold [0..3] */ sx1302_arb_debug_write(2, 3); @@ -1575,6 +1860,10 @@ int sx1302_arb_start(uint8_t version) { int sx1302_fetch(uint8_t * nb_pkt) { int err; + struct timeval tm; + + /* Record function start time */ + _meas_time_start(&tm); /* Fetch packets from sx1302 if no more left in RX buffer */ if (rx_buffer.buffer_pkt_nb == 0) { @@ -1598,30 +1887,43 @@ int sx1302_fetch(uint8_t * nb_pkt) { /* Return the number of packet fetched */ *nb_pkt = rx_buffer.buffer_pkt_nb; + _meas_time_stop(2, tm, __FUNCTION__); + return LGW_REG_SUCCESS; } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ int sx1302_parse(lgw_context_t * context, struct lgw_pkt_rx_s * p) { - int i, err; + int err; int ifmod; /* type of if_chain/modem a packet was received by */ uint16_t payload_crc16_calc; uint8_t cr; - uint32_t timestamp_correction; /* correction to account for processing delay */ + int32_t timestamp_correction; rx_packet_t pkt; + struct timeval tm; + + /* Record function start time */ + _meas_time_start(&tm); /* Check input params */ CHECK_NULL(context); CHECK_NULL(p); - /* FOR DEBUG: Print statistics of number of detects and modem allocations from ARB for configured SF (see sx1302_arb_start()) */ +#if 0 + /* For DEBUG: WARNING: it is quite time consuming in USB mode, due to SPI over USB latency + Print statistics of number of detects and modem allocations from ARB for configured SF (see sx1302_arb_start()) + */ sx1302_arb_print_debug_stats(); +#endif /* get packet from RX buffer */ err = rx_buffer_pop(&rx_buffer, &pkt); - if (err != LGW_REG_SUCCESS) { - return LGW_REG_ERROR; + if (err == LGW_REG_WARNING) { + rx_buffer_del(&rx_buffer); /* clear the buffer */ + return err; + } else if (err == LGW_REG_ERROR) { + return err; } /* copy payload to result struct */ @@ -1653,7 +1955,7 @@ int sx1302_parse(lgw_context_t * context, struct lgw_pkt_rx_s * p) { p->modulation = MOD_LORA; /* Get CRC status */ - if (pkt.crc_en || (context->lora_service_cfg.implicit_crc_en == true)) { + if (pkt.crc_en || ((ifmod == IF_LORA_STD) && (context->lora_service_cfg.implicit_crc_en == true))) { /* CRC enabled */ if (pkt.payload_crc_error) { p->status = STAT_CRC_BAD; @@ -1680,7 +1982,8 @@ int sx1302_parse(lgw_context_t * context, struct lgw_pkt_rx_s * p) { p->status = STAT_NO_CRC; } -#if 1 +#if 0 + int i; /* FOR DEBUG: Check data integrity for known devices (debug context) */ if (p->status == STAT_CRC_OK || p->status == STAT_NO_CRC) { /* We compare the received payload with predefined ones to ensure that the payload content is what we expect. @@ -1748,13 +2051,13 @@ int sx1302_parse(lgw_context_t * context, struct lgw_pkt_rx_s * p) { /* Get frequency offset in Hz depending on bandwidth */ switch (p->bandwidth) { case BW_125KHZ: - p->freq_offset = (int32_t)((float)(pkt.frequency_offset_error) * FREQ_OFFSET_LSB_125KHZ ); + p->freq_offset = (int32_t)((float)(pkt.frequency_offset_error) * FREQ_OFFSET_LSB_125KHZ); break; case BW_250KHZ: - p->freq_offset = (int32_t)((float)(pkt.frequency_offset_error) * FREQ_OFFSET_LSB_250KHZ ); + p->freq_offset = (int32_t)((float)(pkt.frequency_offset_error) * FREQ_OFFSET_LSB_250KHZ); break; case BW_500KHZ: - p->freq_offset = (int32_t)((float)(pkt.frequency_offset_error) * FREQ_OFFSET_LSB_500KHZ ); + p->freq_offset = (int32_t)((float)(pkt.frequency_offset_error) * FREQ_OFFSET_LSB_500KHZ); break; default: p->freq_offset = 0; @@ -1762,8 +2065,18 @@ int sx1302_parse(lgw_context_t * context, struct lgw_pkt_rx_s * p) { break; } - /* Get timestamp correction to be applied */ - timestamp_correction = timestamp_counter_correction(ifmod, p->bandwidth, p->datarate, p->coderate, pkt.crc_en, pkt.rxbytenb_modem); + /* Get timestamp correction to be applied to count_us */ + timestamp_correction = timestamp_counter_correction(context, p->bandwidth, p->datarate, p->coderate, pkt.crc_en, pkt.rxbytenb_modem, RX_DFT_PEAK_MODE_AUTO); + + /* Compute fine timestmap for packets coming from the modem optimized for fine timestamping, if CRC is OK */ + p->ftime_received = false; + p->ftime = 0; + if ((pkt.num_ts_metrics_stored > 0) && (pkt.timing_set == true) && (p->status == STAT_CRC_OK)) { + err = precise_timestamp_calculate(pkt.num_ts_metrics_stored, &pkt.timestamp_avg[0], pkt.timestamp_cnt, pkt.rx_rate_sf, context->if_chain_cfg[p->if_chain].freq_hz, &(p->ftime)); + if (err == 0) { + p->ftime_received = true; + } + } } else if (ifmod == IF_FSK_STD) { DEBUG_PRINTF("Note: FSK packet (modem %u chan %u)\n", pkt.modem_id, p->if_chain); p->modulation = MOD_FSK; @@ -1791,7 +2104,7 @@ int sx1302_parse(lgw_context_t * context, struct lgw_pkt_rx_s * p) { timestamp_correction = ((uint32_t)680000 / context->fsk_cfg.datarate) - 20; /* RSSI correction */ - p->rssic = RSSI_FSK_POLY_0 + RSSI_FSK_POLY_1 * p->rssic + RSSI_FSK_POLY_2 * pow(p->rssic, 2); + p->rssic = RSSI_FSK_POLY_0 + RSSI_FSK_POLY_1 * p->rssic + RSSI_FSK_POLY_2 * pow(p->rssic, 2) + RSSI_FSK_POLY_3 * pow(p->rssic, 3); /* Undefined for FSK */ p->coderate = CR_UNDEFINED; @@ -1812,21 +2125,43 @@ int sx1302_parse(lgw_context_t * context, struct lgw_pkt_rx_s * p) { timestamp_correction = 0; } - /* Update counter reference / wrap status before expanding */ - timestamp_counter_get(&counter_us, false); - /* Scale 32 MHz packet timestamp to 1 MHz (microseconds) */ p->count_us = pkt.timestamp_cnt / 32; - /* Expand 27-bits counter to 32-bits counter, based on current wrapping status */ + /* Expand 27-bits counter to 32-bits counter, based on current wrapping status (updated after fetch) */ p->count_us = timestamp_pkt_expand(&counter_us, p->count_us); + +#if 0 // debug code to check for failed submicros/micros handling + { + static uint32_t last_valid = 0; + static uint32_t last_us32 = 0; + static uint32_t last_pkt_num; + int32_t diff = p->count_us - last_us32; + uint32_t pkt_num = (p->payload[4] << 24) | (p->payload[5] << 16) | (p->payload[6] << 8) | (p->payload[7] << 0); + + printf("XXXXXXXXXXXXXXXX inst - ref=%u wrap=%u\n", counter_us.inst.counter_us_27bits_ref, counter_us.inst.counter_us_27bits_wrap); + printf("XXXXXXXXXXXXXXXX pps - ref=%u wrap=%u\n", counter_us.pps.counter_us_27bits_ref, counter_us.pps.counter_us_27bits_wrap); + printf("XXXXXXXXXXXXXXXX pkt=%u (%u) last=%u diff=%d\n", p->count_us, pkt.timestamp_cnt / 32, last_us32, diff); + printf("XXXXXXXXXXXXXXXX pkt num=%u\n", pkt_num); + if (last_valid && (diff > 30000000) && (pkt_num == (last_pkt_num + 1))) { + printf("XXXXXXXXXXXXXXXX ERROR jump ahead count_us\n"); + exit(1); + } + last_us32 = p->count_us; + last_pkt_num = pkt_num; + last_valid = 1; + } +#endif + /* Packet timestamp corrected */ - p->count_us = p->count_us - timestamp_correction; + p->count_us = p->count_us + timestamp_correction; /* Packet CRC status */ p->crc = pkt.rx_crc16_value; + _meas_time_stop(2, tm, __FUNCTION__); + return LGW_REG_SUCCESS; } @@ -1846,17 +2181,23 @@ uint16_t sx1302_lora_payload_crc(const uint8_t * data, uint8_t size) { /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ -int sx1302_tx_set_start_delay(uint8_t rf_chain, lgw_radio_type_t radio_type, uint8_t modulation, uint8_t bandwidth, uint16_t * delay) { +int sx1302_tx_set_start_delay(uint8_t rf_chain, lgw_radio_type_t radio_type, uint8_t modulation, uint8_t bandwidth, uint8_t chirp_lowpass, uint16_t * delay) { + int err; uint16_t tx_start_delay = TX_START_DELAY_DEFAULT * 32; uint16_t radio_bw_delay = 0; uint16_t filter_delay = 0; uint16_t modem_delay = 0; int32_t bw_hz = lgw_bw_getval(bandwidth); - int32_t val; - uint8_t chirp_low_pass = 0; + uint8_t buff[2]; /* for 16 bits register write operation */ CHECK_NULL(delay); + /* tx start delay only necessary for beaconing (LoRa) */ + if (modulation != MOD_LORA) { + *delay = 0; + return LGW_REG_SUCCESS; + } + /* Adjust with radio type and bandwidth */ switch (radio_type) { case LGW_RADIO_TYPE_SX1250: @@ -1891,25 +2232,18 @@ int sx1302_tx_set_start_delay(uint8_t rf_chain, lgw_radio_type_t radio_type, uin } /* Adjust with modulation */ - if (modulation == MOD_LORA) { - lgw_reg_r(SX1302_REG_TX_TOP_TX_CFG0_0_CHIRP_LOWPASS(0), &val); - chirp_low_pass = (uint8_t)val; - filter_delay = ((1 << chirp_low_pass) - 1) * 1e6 / bw_hz; - modem_delay = 8 * (32e6 / (32 * bw_hz)); /* if bw=125k then modem freq=4MHz */ - } else { - /* TODO */ - filter_delay = 0; - modem_delay = 0; - } + filter_delay = ((1 << chirp_lowpass) - 1) * 1e6 / bw_hz; + modem_delay = 8 * (32e6 / (32 * bw_hz)); /* if bw=125k then modem freq=4MHz */ /* Compute total delay */ tx_start_delay -= (radio_bw_delay + filter_delay + modem_delay); DEBUG_PRINTF("INFO: tx_start_delay=%u (%u, radio_bw_delay=%u, filter_delay=%u, modem_delay=%u)\n", (uint16_t)tx_start_delay, TX_START_DELAY_DEFAULT*32, radio_bw_delay, filter_delay, modem_delay); - /* Configure the SX1302 with the calculated delay */ - lgw_reg_w(SX1302_REG_TX_TOP_TX_START_DELAY_MSB_TX_START_DELAY(rf_chain), (uint8_t)(tx_start_delay >> 8)); - lgw_reg_w(SX1302_REG_TX_TOP_TX_START_DELAY_LSB_TX_START_DELAY(rf_chain), (uint8_t)(tx_start_delay >> 0)); + buff[0] = (uint8_t)(tx_start_delay >> 8); + buff[1] = (uint8_t)(tx_start_delay >> 0); + err = lgw_reg_wb(SX1302_REG_TX_TOP_TX_START_DELAY_MSB_TX_START_DELAY(rf_chain), buff, 2); + CHECK_ERR(err); /* return tx_start_delay */ *delay = tx_start_delay; @@ -1973,7 +2307,8 @@ uint8_t sx1302_rx_status(uint8_t rf_chain) { int sx1302_tx_abort(uint8_t rf_chain) { int err; - uint8_t tx_status; + uint8_t tx_status = TX_STATUS_UNKNOWN; + struct timeval tm_start; err = lgw_reg_w(SX1302_REG_TX_TOP_TX_TRIG_TX_TRIG_IMMEDIATE(rf_chain), 0x00); err |= lgw_reg_w(SX1302_REG_TX_TOP_TX_TRIG_TX_TRIG_DELAYED(rf_chain), 0x00); @@ -1983,9 +2318,18 @@ int sx1302_tx_abort(uint8_t rf_chain) { return err; } + timeout_start(&tm_start); do { + /* handle timeout */ + if (timeout_check(tm_start, 1000) != 0) { + printf("ERROR: %s: TIMEOUT on TX abort\n", __FUNCTION__); + return LGW_REG_ERROR; + } + + /* get tx status */ + tx_status = sx1302_tx_status(rf_chain); wait_ms(1); - } while ((tx_status = sx1302_tx_status(rf_chain)) != TX_FREE && tx_status != TX_STATUS_UNKNOWN); + } while (tx_status != TX_FREE); return LGW_REG_SUCCESS; } @@ -1993,21 +2337,24 @@ int sx1302_tx_abort(uint8_t rf_chain) { /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ int sx1302_tx_configure(lgw_radio_type_t radio_type) { + int err = LGW_REG_SUCCESS; + /* Select the TX destination interface */ switch (radio_type) { case LGW_RADIO_TYPE_SX1250: /* Let AGC control PLL DIV (sx1250 only) */ - lgw_reg_w(SX1302_REG_TX_TOP_A_TX_RFFE_IF_CTRL2_PLL_DIV_CTRL_AGC, 1); - lgw_reg_w(SX1302_REG_TX_TOP_B_TX_RFFE_IF_CTRL2_PLL_DIV_CTRL_AGC, 1); + err |= lgw_reg_w(SX1302_REG_TX_TOP_A_TX_RFFE_IF_CTRL2_PLL_DIV_CTRL_AGC, 1); + err |= lgw_reg_w(SX1302_REG_TX_TOP_B_TX_RFFE_IF_CTRL2_PLL_DIV_CTRL_AGC, 1); /* SX126x Tx RFFE */ - lgw_reg_w(SX1302_REG_TX_TOP_A_TX_RFFE_IF_CTRL_TX_IF_DST, 0x01); - lgw_reg_w(SX1302_REG_TX_TOP_B_TX_RFFE_IF_CTRL_TX_IF_DST, 0x01); + err |= lgw_reg_w(SX1302_REG_TX_TOP_A_TX_RFFE_IF_CTRL_TX_IF_DST, 0x01); + err |= lgw_reg_w(SX1302_REG_TX_TOP_B_TX_RFFE_IF_CTRL_TX_IF_DST, 0x01); break; + case LGW_RADIO_TYPE_SX1255: case LGW_RADIO_TYPE_SX1257: /* SX1255/57 Tx RFFE */ - lgw_reg_w(SX1302_REG_TX_TOP_A_TX_RFFE_IF_CTRL_TX_IF_DST, 0x00); - lgw_reg_w(SX1302_REG_TX_TOP_B_TX_RFFE_IF_CTRL_TX_IF_DST, 0x00); + err |= lgw_reg_w(SX1302_REG_TX_TOP_A_TX_RFFE_IF_CTRL_TX_IF_DST, 0x00); + err |= lgw_reg_w(SX1302_REG_TX_TOP_B_TX_RFFE_IF_CTRL_TX_IF_DST, 0x00); break; default: DEBUG_MSG("ERROR: radio type not supported\n"); @@ -2015,19 +2362,20 @@ int sx1302_tx_configure(lgw_radio_type_t radio_type) { } /* Configure the TX mode of operation */ - lgw_reg_w(SX1302_REG_TX_TOP_A_TX_RFFE_IF_CTRL_TX_MODE, 0x01); /* Modulation */ - lgw_reg_w(SX1302_REG_TX_TOP_B_TX_RFFE_IF_CTRL_TX_MODE, 0x01); /* Modulation */ + err |= lgw_reg_w(SX1302_REG_TX_TOP_A_TX_RFFE_IF_CTRL_TX_MODE, 0x01); /* Modulation */ + err |= lgw_reg_w(SX1302_REG_TX_TOP_B_TX_RFFE_IF_CTRL_TX_MODE, 0x01); /* Modulation */ /* Configure the output data clock edge */ - lgw_reg_w(SX1302_REG_TX_TOP_A_TX_RFFE_IF_CTRL_TX_CLK_EDGE, 0x00); /* Data on rising edge */ - lgw_reg_w(SX1302_REG_TX_TOP_B_TX_RFFE_IF_CTRL_TX_CLK_EDGE, 0x00); /* Data on rising edge */ + err |= lgw_reg_w(SX1302_REG_TX_TOP_A_TX_RFFE_IF_CTRL_TX_CLK_EDGE, 0x00); /* Data on rising edge */ + err |= lgw_reg_w(SX1302_REG_TX_TOP_B_TX_RFFE_IF_CTRL_TX_CLK_EDGE, 0x00); /* Data on rising edge */ - return LGW_REG_SUCCESS; + return err; } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ int sx1302_send(lgw_radio_type_t radio_type, struct lgw_tx_gain_lut_s * tx_lut, bool lwan_public, struct lgw_conf_rxif_s * context_fsk, struct lgw_pkt_tx_s * pkt_data) { + int err; uint32_t freq_reg, fdev_reg; uint32_t freq_dev; uint32_t fsk_br_reg; @@ -2039,24 +2387,41 @@ int sx1302_send(lgw_radio_type_t radio_type, struct lgw_tx_gain_lut_s * tx_lut, uint8_t mod_bw; uint8_t pa_en; uint16_t tx_start_delay; + uint8_t chirp_lowpass = 0; + uint8_t buff[2]; /* for 16-bits register write operation */ + /* performances variables */ + struct timeval tm; + + /* Record function start time */ + _meas_time_start(&tm); - /* CHeck input parameters */ + /* Check input parameters */ CHECK_NULL(tx_lut); CHECK_NULL(pkt_data); + /* Setting BULK write mode (to speed up configuration on USB) */ + err = lgw_com_set_write_mode(LGW_COM_WRITE_MODE_BULK); + CHECK_ERR(err); + /* Select the proper modem */ switch (pkt_data->modulation) { case MOD_CW: - lgw_reg_w(SX1302_REG_TX_TOP_GEN_CFG_0_MODULATION_TYPE(pkt_data->rf_chain), 0x00); - lgw_reg_w(SX1302_REG_TX_TOP_TX_RFFE_IF_CTRL_TX_IF_SRC(pkt_data->rf_chain), 0x00); + err = lgw_reg_w(SX1302_REG_TX_TOP_GEN_CFG_0_MODULATION_TYPE(pkt_data->rf_chain), 0x00); + CHECK_ERR(err); + err = lgw_reg_w(SX1302_REG_TX_TOP_TX_RFFE_IF_CTRL_TX_IF_SRC(pkt_data->rf_chain), 0x00); + CHECK_ERR(err); break; case MOD_LORA: - lgw_reg_w(SX1302_REG_TX_TOP_GEN_CFG_0_MODULATION_TYPE(pkt_data->rf_chain), 0x00); - lgw_reg_w(SX1302_REG_TX_TOP_TX_RFFE_IF_CTRL_TX_IF_SRC(pkt_data->rf_chain), 0x01); + err = lgw_reg_w(SX1302_REG_TX_TOP_GEN_CFG_0_MODULATION_TYPE(pkt_data->rf_chain), 0x00); + CHECK_ERR(err); + err = lgw_reg_w(SX1302_REG_TX_TOP_TX_RFFE_IF_CTRL_TX_IF_SRC(pkt_data->rf_chain), 0x01); + CHECK_ERR(err); break; case MOD_FSK: - lgw_reg_w(SX1302_REG_TX_TOP_GEN_CFG_0_MODULATION_TYPE(pkt_data->rf_chain), 0x01); - lgw_reg_w(SX1302_REG_TX_TOP_TX_RFFE_IF_CTRL_TX_IF_SRC(pkt_data->rf_chain), 0x02); + err = lgw_reg_w(SX1302_REG_TX_TOP_GEN_CFG_0_MODULATION_TYPE(pkt_data->rf_chain), 0x01); + CHECK_ERR(err); + err = lgw_reg_w(SX1302_REG_TX_TOP_TX_RFFE_IF_CTRL_TX_IF_SRC(pkt_data->rf_chain), 0x02); + CHECK_ERR(err); break; default: DEBUG_MSG("ERROR: modulation type not supported\n"); @@ -2072,8 +2437,10 @@ int sx1302_send(lgw_radio_type_t radio_type, struct lgw_tx_gain_lut_s * tx_lut, DEBUG_PRINTF("INFO: selecting TX Gain LUT index %u\n", pow_index); /* loading calibrated Tx DC offsets */ - lgw_reg_w(SX1302_REG_TX_TOP_TX_RFFE_IF_I_OFFSET_I_OFFSET(pkt_data->rf_chain), tx_lut->lut[pow_index].offset_i); - lgw_reg_w(SX1302_REG_TX_TOP_TX_RFFE_IF_Q_OFFSET_Q_OFFSET(pkt_data->rf_chain), tx_lut->lut[pow_index].offset_q); + err = lgw_reg_w(SX1302_REG_TX_TOP_TX_RFFE_IF_I_OFFSET_I_OFFSET(pkt_data->rf_chain), tx_lut->lut[pow_index].offset_i); + CHECK_ERR(err); + err = lgw_reg_w(SX1302_REG_TX_TOP_TX_RFFE_IF_Q_OFFSET_Q_OFFSET(pkt_data->rf_chain), tx_lut->lut[pow_index].offset_q); + CHECK_ERR(err); DEBUG_PRINTF("INFO: Applying IQ offset (i:%d, q:%d)\n", tx_lut->lut[pow_index].offset_i, tx_lut->lut[pow_index].offset_q); @@ -2083,23 +2450,33 @@ int sx1302_send(lgw_radio_type_t radio_type, struct lgw_tx_gain_lut_s * tx_lut, pa_en = (tx_lut->lut[pow_index].pa_gain > 0) ? 1 : 0; /* only 1 bit used to control the external PA */ power = (pa_en << 6) | tx_lut->lut[pow_index].pwr_idx; break; + case LGW_RADIO_TYPE_SX1255: case LGW_RADIO_TYPE_SX1257: power = (tx_lut->lut[pow_index].pa_gain << 6) | (tx_lut->lut[pow_index].dac_gain << 4) | tx_lut->lut[pow_index].mix_gain; break; default: DEBUG_MSG("ERROR: radio type not supported\n"); - return LGW_HAL_ERROR; + return LGW_REG_ERROR; } - lgw_reg_w(SX1302_REG_TX_TOP_AGC_TX_PWR_AGC_TX_PWR(pkt_data->rf_chain), power); + err = lgw_reg_w(SX1302_REG_TX_TOP_AGC_TX_PWR_AGC_TX_PWR(pkt_data->rf_chain), power); + CHECK_ERR(err); /* Set digital gain */ - lgw_reg_w(SX1302_REG_TX_TOP_TX_RFFE_IF_IQ_GAIN_IQ_GAIN(pkt_data->rf_chain), tx_lut->lut[pow_index].dig_gain); + err = lgw_reg_w(SX1302_REG_TX_TOP_TX_RFFE_IF_IQ_GAIN_IQ_GAIN(pkt_data->rf_chain), tx_lut->lut[pow_index].dig_gain); + CHECK_ERR(err); /* Set Tx frequency */ - freq_reg = SX1302_FREQ_TO_REG(pkt_data->freq_hz); /* TODO: AGC fw to be updated for sx1255 */ - lgw_reg_w(SX1302_REG_TX_TOP_TX_RFFE_IF_FREQ_RF_H_FREQ_RF(pkt_data->rf_chain), (freq_reg >> 16) & 0xFF); - lgw_reg_w(SX1302_REG_TX_TOP_TX_RFFE_IF_FREQ_RF_M_FREQ_RF(pkt_data->rf_chain), (freq_reg >> 8) & 0xFF); - lgw_reg_w(SX1302_REG_TX_TOP_TX_RFFE_IF_FREQ_RF_L_FREQ_RF(pkt_data->rf_chain), (freq_reg >> 0) & 0xFF); + if (radio_type == LGW_RADIO_TYPE_SX1255) { + freq_reg = SX1302_FREQ_TO_REG(pkt_data->freq_hz * 2); + } else { + freq_reg = SX1302_FREQ_TO_REG(pkt_data->freq_hz); + } + err = lgw_reg_w(SX1302_REG_TX_TOP_TX_RFFE_IF_FREQ_RF_H_FREQ_RF(pkt_data->rf_chain), (freq_reg >> 16) & 0xFF); + CHECK_ERR(err); + err = lgw_reg_w(SX1302_REG_TX_TOP_TX_RFFE_IF_FREQ_RF_M_FREQ_RF(pkt_data->rf_chain), (freq_reg >> 8) & 0xFF); + CHECK_ERR(err); + err = lgw_reg_w(SX1302_REG_TX_TOP_TX_RFFE_IF_FREQ_RF_L_FREQ_RF(pkt_data->rf_chain), (freq_reg >> 0) & 0xFF); + CHECK_ERR(err); /* Set AGC bandwidth and modulation type*/ switch (pkt_data->modulation) { @@ -2114,35 +2491,45 @@ int sx1302_send(lgw_radio_type_t radio_type, struct lgw_tx_gain_lut_s * tx_lut, printf("ERROR: Modulation not supported\n"); return LGW_REG_ERROR; } - lgw_reg_w(SX1302_REG_TX_TOP_AGC_TX_BW_AGC_TX_BW(pkt_data->rf_chain), mod_bw); + err = lgw_reg_w(SX1302_REG_TX_TOP_AGC_TX_BW_AGC_TX_BW(pkt_data->rf_chain), mod_bw); + CHECK_ERR(err); /* Configure modem */ switch (pkt_data->modulation) { case MOD_CW: /* Set frequency deviation */ - freq_dev = ceil(fabs((float)pkt_data->freq_offset/10))*10e3; + freq_dev = ceil(fabs( (float)pkt_data->freq_offset / 10) ) * 10e3; printf("CW: f_dev %d Hz\n", (int)(freq_dev)); fdev_reg = SX1302_FREQ_TO_REG(freq_dev); - lgw_reg_w(SX1302_REG_TX_TOP_TX_RFFE_IF_FREQ_DEV_H_FREQ_DEV(pkt_data->rf_chain), (fdev_reg >> 8) & 0xFF); - lgw_reg_w(SX1302_REG_TX_TOP_TX_RFFE_IF_FREQ_DEV_L_FREQ_DEV(pkt_data->rf_chain), (fdev_reg >> 0) & 0xFF); + err = lgw_reg_w(SX1302_REG_TX_TOP_TX_RFFE_IF_FREQ_DEV_H_FREQ_DEV(pkt_data->rf_chain), (fdev_reg >> 8) & 0xFF); + CHECK_ERR(err); + err = lgw_reg_w(SX1302_REG_TX_TOP_TX_RFFE_IF_FREQ_DEV_L_FREQ_DEV(pkt_data->rf_chain), (fdev_reg >> 0) & 0xFF); + CHECK_ERR(err); /* Send frequency deviation to AGC fw for radio config */ fdev_reg = SX1250_FREQ_TO_REG(freq_dev); - lgw_reg_w(SX1302_REG_AGC_MCU_MCU_MAIL_BOX_WR_DATA_BYTE2_MCU_MAIL_BOX_WR_DATA, (fdev_reg >> 16) & 0xFF); /* Needed by AGC to configure the sx1250 */ - lgw_reg_w(SX1302_REG_AGC_MCU_MCU_MAIL_BOX_WR_DATA_BYTE1_MCU_MAIL_BOX_WR_DATA, (fdev_reg >> 8) & 0xFF); /* Needed by AGC to configure the sx1250 */ - lgw_reg_w(SX1302_REG_AGC_MCU_MCU_MAIL_BOX_WR_DATA_BYTE0_MCU_MAIL_BOX_WR_DATA, (fdev_reg >> 0) & 0xFF); /* Needed by AGC to configure the sx1250 */ + err = lgw_reg_w(SX1302_REG_AGC_MCU_MCU_MAIL_BOX_WR_DATA_BYTE2_MCU_MAIL_BOX_WR_DATA, (fdev_reg >> 16) & 0xFF); /* Needed by AGC to configure the sx1250 */ + CHECK_ERR(err); + err = lgw_reg_w(SX1302_REG_AGC_MCU_MCU_MAIL_BOX_WR_DATA_BYTE1_MCU_MAIL_BOX_WR_DATA, (fdev_reg >> 8) & 0xFF); /* Needed by AGC to configure the sx1250 */ + CHECK_ERR(err); + err = lgw_reg_w(SX1302_REG_AGC_MCU_MCU_MAIL_BOX_WR_DATA_BYTE0_MCU_MAIL_BOX_WR_DATA, (fdev_reg >> 0) & 0xFF); /* Needed by AGC to configure the sx1250 */ + CHECK_ERR(err); /* Set the frequency offset (ratio of the frequency deviation)*/ printf("CW: IF test mod freq %d\n", (int)(((float)pkt_data->freq_offset*1e3*64/(float)freq_dev))); - lgw_reg_w(SX1302_REG_TX_TOP_TX_RFFE_IF_TEST_MOD_FREQ(pkt_data->rf_chain), (int)(((float)pkt_data->freq_offset*1e3*64/(float)freq_dev))); + err = lgw_reg_w(SX1302_REG_TX_TOP_TX_RFFE_IF_TEST_MOD_FREQ(pkt_data->rf_chain), (int)(((float)pkt_data->freq_offset*1e3*64/(float)freq_dev))); + CHECK_ERR(err); break; case MOD_LORA: /* Set bandwidth */ freq_dev = lgw_bw_getval(pkt_data->bandwidth) / 2; fdev_reg = SX1302_FREQ_TO_REG(freq_dev); - lgw_reg_w(SX1302_REG_TX_TOP_TX_RFFE_IF_FREQ_DEV_H_FREQ_DEV(pkt_data->rf_chain), (fdev_reg >> 8) & 0xFF); - lgw_reg_w(SX1302_REG_TX_TOP_TX_RFFE_IF_FREQ_DEV_L_FREQ_DEV(pkt_data->rf_chain), (fdev_reg >> 0) & 0xFF); - lgw_reg_w(SX1302_REG_TX_TOP_TXRX_CFG0_0_MODEM_BW(pkt_data->rf_chain), pkt_data->bandwidth); + err = lgw_reg_w(SX1302_REG_TX_TOP_TX_RFFE_IF_FREQ_DEV_H_FREQ_DEV(pkt_data->rf_chain), (fdev_reg >> 8) & 0xFF); + CHECK_ERR(err); + err = lgw_reg_w(SX1302_REG_TX_TOP_TX_RFFE_IF_FREQ_DEV_L_FREQ_DEV(pkt_data->rf_chain), (fdev_reg >> 0) & 0xFF); + CHECK_ERR(err); + err = lgw_reg_w(SX1302_REG_TX_TOP_TXRX_CFG0_0_MODEM_BW(pkt_data->rf_chain), pkt_data->bandwidth); + CHECK_ERR(err); /* Preamble length */ if (pkt_data->preamble == 0) { /* if not explicit, use recommended LoRa preamble size */ @@ -2151,62 +2538,83 @@ int sx1302_send(lgw_radio_type_t radio_type, struct lgw_tx_gain_lut_s * tx_lut, pkt_data->preamble = MIN_LORA_PREAMBLE; DEBUG_MSG("Note: preamble length adjusted to respect minimum LoRa preamble size\n"); } - lgw_reg_w(SX1302_REG_TX_TOP_TXRX_CFG1_3_PREAMBLE_SYMB_NB(pkt_data->rf_chain), (pkt_data->preamble >> 8) & 0xFF); /* MSB */ - lgw_reg_w(SX1302_REG_TX_TOP_TXRX_CFG1_2_PREAMBLE_SYMB_NB(pkt_data->rf_chain), (pkt_data->preamble >> 0) & 0xFF); /* LSB */ + err = lgw_reg_w(SX1302_REG_TX_TOP_TXRX_CFG1_3_PREAMBLE_SYMB_NB(pkt_data->rf_chain), (pkt_data->preamble >> 8) & 0xFF); /* MSB */ + CHECK_ERR(err); + err = lgw_reg_w(SX1302_REG_TX_TOP_TXRX_CFG1_2_PREAMBLE_SYMB_NB(pkt_data->rf_chain), (pkt_data->preamble >> 0) & 0xFF); /* LSB */ + CHECK_ERR(err); /* LoRa datarate */ - lgw_reg_w(SX1302_REG_TX_TOP_TXRX_CFG0_0_MODEM_SF(pkt_data->rf_chain), pkt_data->datarate); - if (pkt_data->datarate < 10) { - lgw_reg_w(SX1302_REG_TX_TOP_TX_CFG0_0_CHIRP_LOWPASS(pkt_data->rf_chain), 6); /* less filtering for low SF : TBC */ - } else { - lgw_reg_w(SX1302_REG_TX_TOP_TX_CFG0_0_CHIRP_LOWPASS(pkt_data->rf_chain), 7); - } + err = lgw_reg_w(SX1302_REG_TX_TOP_TXRX_CFG0_0_MODEM_SF(pkt_data->rf_chain), pkt_data->datarate); + CHECK_ERR(err); + + /* Chirp filtering */ + chirp_lowpass = (pkt_data->datarate < 10) ? 6 : 7; + err = lgw_reg_w(SX1302_REG_TX_TOP_TX_CFG0_0_CHIRP_LOWPASS(pkt_data->rf_chain), (int32_t)chirp_lowpass); + CHECK_ERR(err); /* Coding Rate */ - lgw_reg_w(SX1302_REG_TX_TOP_TXRX_CFG0_1_CODING_RATE(pkt_data->rf_chain), pkt_data->coderate); + err = lgw_reg_w(SX1302_REG_TX_TOP_TXRX_CFG0_1_CODING_RATE(pkt_data->rf_chain), pkt_data->coderate); + CHECK_ERR(err); /* Start LoRa modem */ - lgw_reg_w(SX1302_REG_TX_TOP_TXRX_CFG0_2_MODEM_EN(pkt_data->rf_chain), 1); - lgw_reg_w(SX1302_REG_TX_TOP_TXRX_CFG0_2_CADRXTX(pkt_data->rf_chain), 2); - lgw_reg_w(SX1302_REG_TX_TOP_TXRX_CFG1_1_MODEM_START(pkt_data->rf_chain), 1); - lgw_reg_w(SX1302_REG_TX_TOP_TX_CFG0_0_CONTINUOUS(pkt_data->rf_chain), 0); + err = lgw_reg_w(SX1302_REG_TX_TOP_TXRX_CFG0_2_MODEM_EN(pkt_data->rf_chain), 1); + CHECK_ERR(err); + err = lgw_reg_w(SX1302_REG_TX_TOP_TXRX_CFG0_2_CADRXTX(pkt_data->rf_chain), 2); + CHECK_ERR(err); + err = lgw_reg_w(SX1302_REG_TX_TOP_TXRX_CFG1_1_MODEM_START(pkt_data->rf_chain), 1); + CHECK_ERR(err); + err = lgw_reg_w(SX1302_REG_TX_TOP_TX_CFG0_0_CONTINUOUS(pkt_data->rf_chain), 0); + CHECK_ERR(err); /* Modulation options */ - lgw_reg_w(SX1302_REG_TX_TOP_TX_CFG0_0_CHIRP_INVERT(pkt_data->rf_chain), (pkt_data->invert_pol) ? 1 : 0); - lgw_reg_w(SX1302_REG_TX_TOP_TXRX_CFG0_2_IMPLICIT_HEADER(pkt_data->rf_chain), (pkt_data->no_header) ? 1 : 0); - lgw_reg_w(SX1302_REG_TX_TOP_TXRX_CFG0_2_CRC_EN(pkt_data->rf_chain), (pkt_data->no_crc) ? 0 : 1); + err = lgw_reg_w(SX1302_REG_TX_TOP_TX_CFG0_0_CHIRP_INVERT(pkt_data->rf_chain), (pkt_data->invert_pol) ? 1 : 0); + CHECK_ERR(err); + err = lgw_reg_w(SX1302_REG_TX_TOP_TXRX_CFG0_2_IMPLICIT_HEADER(pkt_data->rf_chain), (pkt_data->no_header) ? 1 : 0); + CHECK_ERR(err); + err = lgw_reg_w(SX1302_REG_TX_TOP_TXRX_CFG0_2_CRC_EN(pkt_data->rf_chain), (pkt_data->no_crc) ? 0 : 1); + CHECK_ERR(err); /* Syncword */ if ((lwan_public == false) || (pkt_data->datarate == DR_LORA_SF5) || (pkt_data->datarate == DR_LORA_SF6)) { DEBUG_MSG("Setting LoRa syncword 0x12\n"); - lgw_reg_w(SX1302_REG_TX_TOP_FRAME_SYNCH_0_PEAK1_POS(pkt_data->rf_chain), 2); - lgw_reg_w(SX1302_REG_TX_TOP_FRAME_SYNCH_1_PEAK2_POS(pkt_data->rf_chain), 4); + err = lgw_reg_w(SX1302_REG_TX_TOP_FRAME_SYNCH_0_PEAK1_POS(pkt_data->rf_chain), 2); + CHECK_ERR(err); + err = lgw_reg_w(SX1302_REG_TX_TOP_FRAME_SYNCH_1_PEAK2_POS(pkt_data->rf_chain), 4); + CHECK_ERR(err); } else { DEBUG_MSG("Setting LoRa syncword 0x34\n"); - lgw_reg_w(SX1302_REG_TX_TOP_FRAME_SYNCH_0_PEAK1_POS(pkt_data->rf_chain), 6); - lgw_reg_w(SX1302_REG_TX_TOP_FRAME_SYNCH_1_PEAK2_POS(pkt_data->rf_chain), 8); + err = lgw_reg_w(SX1302_REG_TX_TOP_FRAME_SYNCH_0_PEAK1_POS(pkt_data->rf_chain), 6); + CHECK_ERR(err); + err = lgw_reg_w(SX1302_REG_TX_TOP_FRAME_SYNCH_1_PEAK2_POS(pkt_data->rf_chain), 8); + CHECK_ERR(err); } /* Set Fine Sync for SF5/SF6 */ if ((pkt_data->datarate == DR_LORA_SF5) || (pkt_data->datarate == DR_LORA_SF6)) { DEBUG_MSG("Enable Fine Sync\n"); - lgw_reg_w(SX1302_REG_TX_TOP_TXRX_CFG0_2_FINE_SYNCH_EN(pkt_data->rf_chain), 1); + err = lgw_reg_w(SX1302_REG_TX_TOP_TXRX_CFG0_2_FINE_SYNCH_EN(pkt_data->rf_chain), 1); + CHECK_ERR(err); } else { DEBUG_MSG("Disable Fine Sync\n"); - lgw_reg_w(SX1302_REG_TX_TOP_TXRX_CFG0_2_FINE_SYNCH_EN(pkt_data->rf_chain), 0); + err = lgw_reg_w(SX1302_REG_TX_TOP_TXRX_CFG0_2_FINE_SYNCH_EN(pkt_data->rf_chain), 0); + CHECK_ERR(err); } /* Set Payload length */ - lgw_reg_w(SX1302_REG_TX_TOP_TXRX_CFG0_3_PAYLOAD_LENGTH(pkt_data->rf_chain), pkt_data->size); + err = lgw_reg_w(SX1302_REG_TX_TOP_TXRX_CFG0_3_PAYLOAD_LENGTH(pkt_data->rf_chain), pkt_data->size); + CHECK_ERR(err); /* Set PPM offset (low datarate optimization) */ - lgw_reg_w(SX1302_REG_TX_TOP_TXRX_CFG0_1_PPM_OFFSET_HDR_CTRL(pkt_data->rf_chain), 0); + err = lgw_reg_w(SX1302_REG_TX_TOP_TXRX_CFG0_1_PPM_OFFSET_HDR_CTRL(pkt_data->rf_chain), 0); + CHECK_ERR(err); if (SET_PPM_ON(pkt_data->bandwidth, pkt_data->datarate)) { DEBUG_MSG("Low datarate optimization ENABLED\n"); - lgw_reg_w(SX1302_REG_TX_TOP_TXRX_CFG0_1_PPM_OFFSET(pkt_data->rf_chain), 1); + err = lgw_reg_w(SX1302_REG_TX_TOP_TXRX_CFG0_1_PPM_OFFSET(pkt_data->rf_chain), 1); + CHECK_ERR(err); } else { DEBUG_MSG("Low datarate optimization DISABLED\n"); - lgw_reg_w(SX1302_REG_TX_TOP_TXRX_CFG0_1_PPM_OFFSET(pkt_data->rf_chain), 0); + err = lgw_reg_w(SX1302_REG_TX_TOP_TXRX_CFG0_1_PPM_OFFSET(pkt_data->rf_chain), 0); + CHECK_ERR(err); } break; case MOD_FSK: @@ -2215,41 +2623,65 @@ int sx1302_send(lgw_radio_type_t radio_type, struct lgw_tx_gain_lut_s * tx_lut, /* Set frequency deviation */ freq_dev = pkt_data->f_dev * 1e3; fdev_reg = SX1302_FREQ_TO_REG(freq_dev); - lgw_reg_w(SX1302_REG_TX_TOP_TX_RFFE_IF_FREQ_DEV_H_FREQ_DEV(pkt_data->rf_chain), (fdev_reg >> 8) & 0xFF); - lgw_reg_w(SX1302_REG_TX_TOP_TX_RFFE_IF_FREQ_DEV_L_FREQ_DEV(pkt_data->rf_chain), (fdev_reg >> 0) & 0xFF); + err = lgw_reg_w(SX1302_REG_TX_TOP_TX_RFFE_IF_FREQ_DEV_H_FREQ_DEV(pkt_data->rf_chain), (fdev_reg >> 8) & 0xFF); + CHECK_ERR(err); + err = lgw_reg_w(SX1302_REG_TX_TOP_TX_RFFE_IF_FREQ_DEV_L_FREQ_DEV(pkt_data->rf_chain), (fdev_reg >> 0) & 0xFF); + CHECK_ERR(err); /* Send frequency deviation to AGC fw for radio config */ fdev_reg = SX1250_FREQ_TO_REG(freq_dev); - lgw_reg_w(SX1302_REG_AGC_MCU_MCU_MAIL_BOX_WR_DATA_BYTE2_MCU_MAIL_BOX_WR_DATA, (fdev_reg >> 16) & 0xFF); /* Needed by AGC to configure the sx1250 */ - lgw_reg_w(SX1302_REG_AGC_MCU_MCU_MAIL_BOX_WR_DATA_BYTE1_MCU_MAIL_BOX_WR_DATA, (fdev_reg >> 8) & 0xFF); /* Needed by AGC to configure the sx1250 */ - lgw_reg_w(SX1302_REG_AGC_MCU_MCU_MAIL_BOX_WR_DATA_BYTE0_MCU_MAIL_BOX_WR_DATA, (fdev_reg >> 0) & 0xFF); /* Needed by AGC to configure the sx1250 */ + err = lgw_reg_w(SX1302_REG_AGC_MCU_MCU_MAIL_BOX_WR_DATA_BYTE2_MCU_MAIL_BOX_WR_DATA, (fdev_reg >> 16) & 0xFF); /* Needed by AGC to configure the sx1250 */ + CHECK_ERR(err); + err = lgw_reg_w(SX1302_REG_AGC_MCU_MCU_MAIL_BOX_WR_DATA_BYTE1_MCU_MAIL_BOX_WR_DATA, (fdev_reg >> 8) & 0xFF); /* Needed by AGC to configure the sx1250 */ + CHECK_ERR(err); + err = lgw_reg_w(SX1302_REG_AGC_MCU_MCU_MAIL_BOX_WR_DATA_BYTE0_MCU_MAIL_BOX_WR_DATA, (fdev_reg >> 0) & 0xFF); /* Needed by AGC to configure the sx1250 */ + CHECK_ERR(err); /* Modulation parameters */ - lgw_reg_w(SX1302_REG_TX_TOP_FSK_CFG_0_PKT_MODE(pkt_data->rf_chain), 1); /* Variable length */ - lgw_reg_w(SX1302_REG_TX_TOP_FSK_CFG_0_CRC_EN(pkt_data->rf_chain), (pkt_data->no_crc) ? 0 : 1); - lgw_reg_w(SX1302_REG_TX_TOP_FSK_CFG_0_CRC_IBM(pkt_data->rf_chain), 0); /* CCITT CRC */ - lgw_reg_w(SX1302_REG_TX_TOP_FSK_CFG_0_DCFREE_ENC(pkt_data->rf_chain), 2); /* Whitening Encoding */ - lgw_reg_w(SX1302_REG_TX_TOP_FSK_MOD_FSK_GAUSSIAN_EN(pkt_data->rf_chain), 1); - lgw_reg_w(SX1302_REG_TX_TOP_FSK_MOD_FSK_GAUSSIAN_SELECT_BT(pkt_data->rf_chain), 2); - lgw_reg_w(SX1302_REG_TX_TOP_FSK_MOD_FSK_REF_PATTERN_EN(pkt_data->rf_chain), 1); - lgw_reg_w(SX1302_REG_TX_TOP_FSK_MOD_FSK_REF_PATTERN_SIZE(pkt_data->rf_chain), context_fsk->sync_word_size - 1); + err = lgw_reg_w(SX1302_REG_TX_TOP_FSK_CFG_0_PKT_MODE(pkt_data->rf_chain), 1); /* Variable length */ + CHECK_ERR(err); + err = lgw_reg_w(SX1302_REG_TX_TOP_FSK_CFG_0_CRC_EN(pkt_data->rf_chain), (pkt_data->no_crc) ? 0 : 1); + CHECK_ERR(err); + err = lgw_reg_w(SX1302_REG_TX_TOP_FSK_CFG_0_CRC_IBM(pkt_data->rf_chain), 0); /* CCITT CRC */ + CHECK_ERR(err); + err = lgw_reg_w(SX1302_REG_TX_TOP_FSK_CFG_0_DCFREE_ENC(pkt_data->rf_chain), 2); /* Whitening Encoding */ + CHECK_ERR(err); + err = lgw_reg_w(SX1302_REG_TX_TOP_FSK_MOD_FSK_GAUSSIAN_EN(pkt_data->rf_chain), 1); + CHECK_ERR(err); + err = lgw_reg_w(SX1302_REG_TX_TOP_FSK_MOD_FSK_GAUSSIAN_SELECT_BT(pkt_data->rf_chain), 2); + CHECK_ERR(err); + err = lgw_reg_w(SX1302_REG_TX_TOP_FSK_MOD_FSK_REF_PATTERN_EN(pkt_data->rf_chain), 1); + CHECK_ERR(err); + err = lgw_reg_w(SX1302_REG_TX_TOP_FSK_MOD_FSK_REF_PATTERN_SIZE(pkt_data->rf_chain), context_fsk->sync_word_size - 1); + CHECK_ERR(err); /* Syncword */ fsk_sync_word_reg = context_fsk->sync_word << (8 * (8 - context_fsk->sync_word_size)); - lgw_reg_w(SX1302_REG_TX_TOP_FSK_REF_PATTERN_BYTE0_FSK_REF_PATTERN(pkt_data->rf_chain), (uint8_t)(fsk_sync_word_reg >> 0)); - lgw_reg_w(SX1302_REG_TX_TOP_FSK_REF_PATTERN_BYTE1_FSK_REF_PATTERN(pkt_data->rf_chain), (uint8_t)(fsk_sync_word_reg >> 8)); - lgw_reg_w(SX1302_REG_TX_TOP_FSK_REF_PATTERN_BYTE2_FSK_REF_PATTERN(pkt_data->rf_chain), (uint8_t)(fsk_sync_word_reg >> 16)); - lgw_reg_w(SX1302_REG_TX_TOP_FSK_REF_PATTERN_BYTE3_FSK_REF_PATTERN(pkt_data->rf_chain), (uint8_t)(fsk_sync_word_reg >> 24)); - lgw_reg_w(SX1302_REG_TX_TOP_FSK_REF_PATTERN_BYTE4_FSK_REF_PATTERN(pkt_data->rf_chain), (uint8_t)(fsk_sync_word_reg >> 32)); - lgw_reg_w(SX1302_REG_TX_TOP_FSK_REF_PATTERN_BYTE5_FSK_REF_PATTERN(pkt_data->rf_chain), (uint8_t)(fsk_sync_word_reg >> 40)); - lgw_reg_w(SX1302_REG_TX_TOP_FSK_REF_PATTERN_BYTE6_FSK_REF_PATTERN(pkt_data->rf_chain), (uint8_t)(fsk_sync_word_reg >> 48)); - lgw_reg_w(SX1302_REG_TX_TOP_FSK_REF_PATTERN_BYTE7_FSK_REF_PATTERN(pkt_data->rf_chain), (uint8_t)(fsk_sync_word_reg >> 56)); - lgw_reg_w(SX1302_REG_TX_TOP_FSK_MOD_FSK_PREAMBLE_SEQ(pkt_data->rf_chain), 0); + err = lgw_reg_w(SX1302_REG_TX_TOP_FSK_REF_PATTERN_BYTE0_FSK_REF_PATTERN(pkt_data->rf_chain), (uint8_t)(fsk_sync_word_reg >> 0)); + CHECK_ERR(err); + err = lgw_reg_w(SX1302_REG_TX_TOP_FSK_REF_PATTERN_BYTE1_FSK_REF_PATTERN(pkt_data->rf_chain), (uint8_t)(fsk_sync_word_reg >> 8)); + CHECK_ERR(err); + err = lgw_reg_w(SX1302_REG_TX_TOP_FSK_REF_PATTERN_BYTE2_FSK_REF_PATTERN(pkt_data->rf_chain), (uint8_t)(fsk_sync_word_reg >> 16)); + CHECK_ERR(err); + err = lgw_reg_w(SX1302_REG_TX_TOP_FSK_REF_PATTERN_BYTE3_FSK_REF_PATTERN(pkt_data->rf_chain), (uint8_t)(fsk_sync_word_reg >> 24)); + CHECK_ERR(err); + err = lgw_reg_w(SX1302_REG_TX_TOP_FSK_REF_PATTERN_BYTE4_FSK_REF_PATTERN(pkt_data->rf_chain), (uint8_t)(fsk_sync_word_reg >> 32)); + CHECK_ERR(err); + err = lgw_reg_w(SX1302_REG_TX_TOP_FSK_REF_PATTERN_BYTE5_FSK_REF_PATTERN(pkt_data->rf_chain), (uint8_t)(fsk_sync_word_reg >> 40)); + CHECK_ERR(err); + err = lgw_reg_w(SX1302_REG_TX_TOP_FSK_REF_PATTERN_BYTE6_FSK_REF_PATTERN(pkt_data->rf_chain), (uint8_t)(fsk_sync_word_reg >> 48)); + CHECK_ERR(err); + err = lgw_reg_w(SX1302_REG_TX_TOP_FSK_REF_PATTERN_BYTE7_FSK_REF_PATTERN(pkt_data->rf_chain), (uint8_t)(fsk_sync_word_reg >> 56)); + CHECK_ERR(err); + err = lgw_reg_w(SX1302_REG_TX_TOP_FSK_MOD_FSK_PREAMBLE_SEQ(pkt_data->rf_chain), 0); + CHECK_ERR(err); /* Set datarate */ fsk_br_reg = 32000000 / pkt_data->datarate; - lgw_reg_w(SX1302_REG_TX_TOP_FSK_BIT_RATE_MSB_BIT_RATE(pkt_data->rf_chain), fsk_br_reg >> 8); - lgw_reg_w(SX1302_REG_TX_TOP_FSK_BIT_RATE_LSB_BIT_RATE(pkt_data->rf_chain), fsk_br_reg >> 0); + buff[0] = (uint8_t)(fsk_br_reg >> 8); + buff[1] = (uint8_t)(fsk_br_reg >> 0); + err = lgw_reg_wb(SX1302_REG_TX_TOP_FSK_BIT_RATE_MSB_BIT_RATE(pkt_data->rf_chain), buff, 2); + CHECK_ERR(err); /* Preamble length */ if (pkt_data->preamble == 0) { /* if not explicit, use LoRaWAN preamble size */ @@ -2258,11 +2690,14 @@ int sx1302_send(lgw_radio_type_t radio_type, struct lgw_tx_gain_lut_s * tx_lut, pkt_data->preamble = MIN_FSK_PREAMBLE; DEBUG_MSG("Note: preamble length adjusted to respect minimum FSK preamble size\n"); } - lgw_reg_w(SX1302_REG_TX_TOP_FSK_PREAMBLE_SIZE_MSB_PREAMBLE_SIZE(pkt_data->rf_chain), (pkt_data->preamble >> 8) & 0xFF); /* MSB */ - lgw_reg_w(SX1302_REG_TX_TOP_FSK_PREAMBLE_SIZE_LSB_PREAMBLE_SIZE(pkt_data->rf_chain), (pkt_data->preamble >> 0) & 0xFF); /* LSB */ + buff[0] = (uint8_t)(pkt_data->preamble >> 8); + buff[1] = (uint8_t)(pkt_data->preamble >> 8); + err = lgw_reg_wb(SX1302_REG_TX_TOP_FSK_PREAMBLE_SIZE_MSB_PREAMBLE_SIZE(pkt_data->rf_chain), buff, 2); + CHECK_ERR(err); /* Set Payload length */ - lgw_reg_w(SX1302_REG_TX_TOP_FSK_PKT_LEN_PKT_LENGTH(pkt_data->rf_chain), pkt_data->size); + err = lgw_reg_w(SX1302_REG_TX_TOP_FSK_PKT_LEN_PKT_LENGTH(pkt_data->rf_chain), pkt_data->size); + CHECK_ERR(err); break; default: printf("ERROR: Modulation not supported\n"); @@ -2270,48 +2705,103 @@ int sx1302_send(lgw_radio_type_t radio_type, struct lgw_tx_gain_lut_s * tx_lut, } /* Set TX start delay */ - sx1302_tx_set_start_delay(pkt_data->rf_chain, radio_type, pkt_data->modulation, pkt_data->bandwidth, &tx_start_delay); + err = sx1302_tx_set_start_delay(pkt_data->rf_chain, radio_type, pkt_data->modulation, pkt_data->bandwidth, chirp_lowpass, &tx_start_delay); + CHECK_ERR(err); /* Write payload in transmit buffer */ - lgw_reg_w(SX1302_REG_TX_TOP_TX_CTRL_WRITE_BUFFER(pkt_data->rf_chain), 0x01); + err = lgw_reg_w(SX1302_REG_TX_TOP_TX_CTRL_WRITE_BUFFER(pkt_data->rf_chain), 0x01); + CHECK_ERR(err); mem_addr = REG_SELECT(pkt_data->rf_chain, 0x5300, 0x5500); if (pkt_data->modulation == MOD_FSK) { - lgw_mem_wb(mem_addr, (uint8_t *)(&(pkt_data->size)), 1); /* insert payload size in the packet for FSK variable mode (1 byte) */ - lgw_mem_wb(mem_addr+1, &(pkt_data->payload[0]), pkt_data->size); + err = lgw_mem_wb(mem_addr, (uint8_t *)(&(pkt_data->size)), 1); /* insert payload size in the packet for FSK variable mode (1 byte) */ + CHECK_ERR(err); + err = lgw_mem_wb(mem_addr+1, &(pkt_data->payload[0]), pkt_data->size); + CHECK_ERR(err); } else { - lgw_mem_wb(mem_addr, &(pkt_data->payload[0]), pkt_data->size); + err = lgw_mem_wb(mem_addr, &(pkt_data->payload[0]), pkt_data->size); + CHECK_ERR(err); } - lgw_reg_w(SX1302_REG_TX_TOP_TX_CTRL_WRITE_BUFFER(pkt_data->rf_chain), 0x00); + err = lgw_reg_w(SX1302_REG_TX_TOP_TX_CTRL_WRITE_BUFFER(pkt_data->rf_chain), 0x00); + CHECK_ERR(err); /* Trigger transmit */ DEBUG_PRINTF("Start Tx: Freq:%u %s%u size:%u preamb:%u\n", pkt_data->freq_hz, (pkt_data->modulation == MOD_LORA) ? "SF" : "DR:", pkt_data->datarate, pkt_data->size, pkt_data->preamble); switch (pkt_data->tx_mode) { case IMMEDIATE: - lgw_reg_w(SX1302_REG_TX_TOP_TX_TRIG_TX_TRIG_IMMEDIATE(pkt_data->rf_chain), 0x00); /* reset state machine */ - lgw_reg_w(SX1302_REG_TX_TOP_TX_TRIG_TX_TRIG_IMMEDIATE(pkt_data->rf_chain), 0x01); + err = lgw_reg_w(SX1302_REG_TX_TOP_TX_TRIG_TX_TRIG_IMMEDIATE(pkt_data->rf_chain), 0x00); /* reset state machine */ + CHECK_ERR(err); + err = lgw_reg_w(SX1302_REG_TX_TOP_TX_TRIG_TX_TRIG_IMMEDIATE(pkt_data->rf_chain), 0x01); + CHECK_ERR(err); break; case TIMESTAMPED: count_us = pkt_data->count_us * 32 - tx_start_delay; DEBUG_PRINTF("--> programming trig delay at %u (%u)\n", pkt_data->count_us - (tx_start_delay / 32), count_us); - lgw_reg_w(SX1302_REG_TX_TOP_TIMER_TRIG_BYTE0_TIMER_DELAYED_TRIG(pkt_data->rf_chain), (uint8_t)((count_us >> 0) & 0x000000FF)); - lgw_reg_w(SX1302_REG_TX_TOP_TIMER_TRIG_BYTE1_TIMER_DELAYED_TRIG(pkt_data->rf_chain), (uint8_t)((count_us >> 8) & 0x000000FF)); - lgw_reg_w(SX1302_REG_TX_TOP_TIMER_TRIG_BYTE2_TIMER_DELAYED_TRIG(pkt_data->rf_chain), (uint8_t)((count_us >> 16) & 0x000000FF)); - lgw_reg_w(SX1302_REG_TX_TOP_TIMER_TRIG_BYTE3_TIMER_DELAYED_TRIG(pkt_data->rf_chain), (uint8_t)((count_us >> 24) & 0x000000FF)); - - lgw_reg_w(SX1302_REG_TX_TOP_TX_TRIG_TX_TRIG_DELAYED(pkt_data->rf_chain), 0x00); /* reset state machine */ - lgw_reg_w(SX1302_REG_TX_TOP_TX_TRIG_TX_TRIG_DELAYED(pkt_data->rf_chain), 0x01); + err = lgw_reg_w(SX1302_REG_TX_TOP_TIMER_TRIG_BYTE0_TIMER_DELAYED_TRIG(pkt_data->rf_chain), (uint8_t)((count_us >> 0) & 0x000000FF)); + CHECK_ERR(err); + err = lgw_reg_w(SX1302_REG_TX_TOP_TIMER_TRIG_BYTE1_TIMER_DELAYED_TRIG(pkt_data->rf_chain), (uint8_t)((count_us >> 8) & 0x000000FF)); + CHECK_ERR(err); + err = lgw_reg_w(SX1302_REG_TX_TOP_TIMER_TRIG_BYTE2_TIMER_DELAYED_TRIG(pkt_data->rf_chain), (uint8_t)((count_us >> 16) & 0x000000FF)); + CHECK_ERR(err); + err = lgw_reg_w(SX1302_REG_TX_TOP_TIMER_TRIG_BYTE3_TIMER_DELAYED_TRIG(pkt_data->rf_chain), (uint8_t)((count_us >> 24) & 0x000000FF)); + CHECK_ERR(err); + + err = lgw_reg_w(SX1302_REG_TX_TOP_TX_TRIG_TX_TRIG_DELAYED(pkt_data->rf_chain), 0x00); /* reset state machine */ + CHECK_ERR(err); + err = lgw_reg_w(SX1302_REG_TX_TOP_TX_TRIG_TX_TRIG_DELAYED(pkt_data->rf_chain), 0x01); + CHECK_ERR(err); break; case ON_GPS: - lgw_reg_w(SX1302_REG_TX_TOP_TX_TRIG_TX_TRIG_GPS(pkt_data->rf_chain), 0x00); /* reset state machine */ - lgw_reg_w(SX1302_REG_TX_TOP_TX_TRIG_TX_TRIG_GPS(pkt_data->rf_chain), 0x01); + err = lgw_reg_w(SX1302_REG_TX_TOP_TX_TRIG_TX_TRIG_GPS(pkt_data->rf_chain), 0x00); /* reset state machine */ + CHECK_ERR(err); + err = lgw_reg_w(SX1302_REG_TX_TOP_TX_TRIG_TX_TRIG_GPS(pkt_data->rf_chain), 0x01); + CHECK_ERR(err); break; default: printf("ERROR: TX mode not supported\n"); return LGW_REG_ERROR; } + /* Flush write (USB BULK mode) */ + err = lgw_com_flush(); + CHECK_ERR(err); + + /* Setting back to SINGLE BULK write mode */ + err = lgw_com_set_write_mode(LGW_COM_WRITE_MODE_SINGLE); + CHECK_ERR(err); + + /* Compute time spent in this function */ + _meas_time_stop(2, tm, __FUNCTION__); + return LGW_REG_SUCCESS; } +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int sx1302_set_gpio(uint8_t gpio_reg_val) { + int err; + + err = lgw_reg_w(SX1302_REG_GPIO_GPIO_OUT_L_OUT_VALUE, gpio_reg_val); /* set all GPIOs at once, 1 bit per GPIO */ + CHECK_ERR(err); + + return LGW_REG_SUCCESS; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +double sx1302_dc_notch_delay(double if_freq_khz) { + double delay; + + if ((if_freq_khz < -75.0) || (if_freq_khz > 75.0)) { + delay = 0.0; + } else { + delay = 1.7e-6 * pow(if_freq_khz, 4) + 2.4e-6 * pow(if_freq_khz, 3) - 0.0101 * pow(if_freq_khz, 2) - 0.01275 * if_freq_khz + 10.2922; + } + + printf("SX1302: DC notch filter delay : %f\n", delay); + + /* Number of 32MHz clock cycles */ + return delay; +} + /* --- EOF ------------------------------------------------------------------ */ diff --git a/libloragw/src/loragw_sx1302_rx.c b/libloragw/src/loragw_sx1302_rx.c index 945d549e..5f151298 100644 --- a/libloragw/src/loragw_sx1302_rx.c +++ b/libloragw/src/loragw_sx1302_rx.c @@ -31,8 +31,8 @@ License: Revised BSD License, see LICENSE.TXT file include in the project #define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) #if DEBUG_SX1302 == 1 - #define DEBUG_MSG(str) fprintf(stderr, str) - #define DEBUG_PRINTF(fmt, args...) fprintf(stderr, fmt, args) + #define DEBUG_MSG(str) fprintf(stdout, str) + #define DEBUG_PRINTF(fmt, args...) fprintf(stdout, fmt, 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) @@ -91,9 +91,6 @@ License: Revised BSD License, see LICENSE.TXT file include in the project /* -------------------------------------------------------------------------- */ /* --- PRIVATE FUNCTIONS DECLARATION ---------------------------------------- */ -/* -------------------------------------------------------------------------- */ -/* --- INTERNAL SHARED VARIABLES -------------------------------------------- */ - /* -------------------------------------------------------------------------- */ /* --- PRIVATE FUNCTIONS DEFINITION ----------------------------------------- */ @@ -132,23 +129,23 @@ int rx_buffer_del(rx_buffer_t * self) { int rx_buffer_fetch(rx_buffer_t * self) { int i, res; uint8_t buff[2]; - int32_t msb; + uint8_t payload_len; + uint16_t next_pkt_idx; + int idx; + uint16_t nb_bytes_1, nb_bytes_2; /* Check input params */ CHECK_NULL(self); /* Check if there is data in the FIFO */ lgw_reg_rb(SX1302_REG_RX_TOP_RX_BUFFER_NB_BYTES_MSB_RX_BUFFER_NB_BYTES, buff, sizeof buff); - /* Workaround concentrator chip issue: - - read MSB again - - if MSB changed, read the full size gain - */ - lgw_reg_r(SX1302_REG_RX_TOP_RX_BUFFER_NB_BYTES_MSB_RX_BUFFER_NB_BYTES, &msb); - if (buff[0] != (uint8_t)msb) { - lgw_reg_rb(SX1302_REG_RX_TOP_RX_BUFFER_NB_BYTES_MSB_RX_BUFFER_NB_BYTES, buff, sizeof buff); - } + nb_bytes_1 = (buff[0] << 8) | (buff[1] << 0); + + /* Workaround for multi-byte read issue: read again and ensure new read is not lower than the previous one */ + lgw_reg_rb(SX1302_REG_RX_TOP_RX_BUFFER_NB_BYTES_MSB_RX_BUFFER_NB_BYTES, buff, sizeof buff); + nb_bytes_2 = (buff[0] << 8) | (buff[1] << 0); - self->buffer_size = (buff[0] << 8) | (buff[1] << 0); + self->buffer_size = (nb_bytes_2 > nb_bytes_1) ? nb_bytes_2 : nb_bytes_1; /* Fetch bytes from fifo if any */ if (self->buffer_size > 0) { @@ -162,36 +159,60 @@ int rx_buffer_fetch(rx_buffer_t * self) { return LGW_REG_ERROR; } - /* print debug info : TODO to be removed */ + /* print debug info */ DEBUG_MSG("RX_BUFFER: "); for (i = 0; i < self->buffer_size; i++) { DEBUG_PRINTF("%02X ", self->buffer[i]); } DEBUG_MSG("\n"); - } + /* Sanity check: is there at least 1 complete packet in the buffer */ + if (self->buffer_size < (SX1302_PKT_HEAD_METADATA + SX1302_PKT_TAIL_METADATA)) { + printf("WARNING: not enough data to have a complete packet, discard rx_buffer\n"); + return rx_buffer_del(self); + } - /* Parse buffer to get number of packet fetched */ - uint8_t payload_len; - uint16_t next_pkt_idx; - int idx = 0; - while (idx < self->buffer_size) { - if ((self->buffer[idx] != SX1302_PKT_SYNCWORD_BYTE_0) || (self->buffer[idx + 1] != SX1302_PKT_SYNCWORD_BYTE_1)) { - printf("ERROR: syncword not found in rx_buffer\n"); - return LGW_REG_ERROR; + /* Sanity check: is there a syncword at 0 ? If not, move to the first syncword found */ + idx = 0; + while (idx <= (self->buffer_size - 2)) { + if ((self->buffer[idx] == SX1302_PKT_SYNCWORD_BYTE_0) && (self->buffer[idx + 1] == SX1302_PKT_SYNCWORD_BYTE_1)) { + DEBUG_PRINTF("INFO: syncword found at idx %d\n", idx); + break; + } else { + printf("INFO: syncword not found at idx %d\n", idx); + idx += 1; + } + } + if (idx > self->buffer_size - 2) { + printf("WARNING: no syncword found, discard rx_buffer\n"); + return rx_buffer_del(self); + } + if (idx != 0) { + printf("INFO: re-sync rx_buffer at idx %d\n", idx); + memmove((void *)(self->buffer), (void *)(self->buffer + idx), self->buffer_size - idx); + self->buffer_size -= idx; + } + + /* Rewind and parse buffer to get the number of packet fetched */ + idx = 0; + while (idx < self->buffer_size) { + if ((self->buffer[idx] != SX1302_PKT_SYNCWORD_BYTE_0) || (self->buffer[idx + 1] != SX1302_PKT_SYNCWORD_BYTE_1)) { + printf("WARNING: syncword not found at idx %d, discard the rx_buffer\n", idx); + return rx_buffer_del(self); + } + /* One packet found in the buffer */ + self->buffer_pkt_nb += 1; + + /* Compute the number of bytes for this packet */ + payload_len = SX1302_PKT_PAYLOAD_LENGTH(self->buffer, idx); + next_pkt_idx = SX1302_PKT_HEAD_METADATA + + payload_len + + SX1302_PKT_TAIL_METADATA + + (2 * SX1302_PKT_NUM_TS_METRICS(self->buffer, idx + payload_len)); + + /* Move to next packet */ + idx += (int)next_pkt_idx; } - /* One packet found in the buffer */ - self->buffer_pkt_nb += 1; - - /* Compute the number of bytes for thsi packet */ - payload_len = SX1302_PKT_PAYLOAD_LENGTH(self->buffer, idx); - next_pkt_idx = SX1302_PKT_HEAD_METADATA + - payload_len + - SX1302_PKT_TAIL_METADATA + - (2 * SX1302_PKT_NUM_TS_METRICS(self->buffer, idx + payload_len)); - - /* Move to next packet */ - idx += (int)next_pkt_idx; } /* Initialize the current buffer index to iterate on */ @@ -220,10 +241,7 @@ int rx_buffer_pop(rx_buffer_t * self, rx_packet_t * pkt) { /* Get pkt sync words */ if ((self->buffer[self->buffer_index] != SX1302_PKT_SYNCWORD_BYTE_0) || (self->buffer[self->buffer_index + 1] != SX1302_PKT_SYNCWORD_BYTE_1)) { - printf("INFO: searching syncword...\n"); - self->buffer_index += 1; return LGW_REG_ERROR; - /* TODO: while loop until syncword found ?? */ } DEBUG_PRINTF("INFO: pkt syncword found at index %u\n", self->buffer_index); @@ -239,7 +257,7 @@ int rx_buffer_pop(rx_buffer_t * self, rx_packet_t * pkt) { /* Check if we have a complete packet in the rx buffer fetched */ if((self->buffer_index + pkt_num_bytes) > self->buffer_size) { printf("WARNING: aborting truncated message (size=%u)\n", self->buffer_size); - return LGW_REG_ERROR; + return LGW_REG_WARNING; } /* Get the checksum as received in the RX buffer */ @@ -254,7 +272,7 @@ int rx_buffer_pop(rx_buffer_t * self, rx_packet_t * pkt) { /* Check if the checksum is correct */ if (checksum_rcv != checksum_calc) { printf("WARNING: checksum failed (got:0x%02X calc:0x%02X)\n", checksum_rcv, checksum_calc); - return LGW_REG_ERROR; + return LGW_REG_WARNING; } else { DEBUG_PRINTF("Packet checksum OK (0x%02X)\n", checksum_rcv); } @@ -286,6 +304,12 @@ int rx_buffer_pop(rx_buffer_t * self, rx_packet_t * pkt) { pkt->timestamp_cnt |= (uint32_t)((SX1302_PKT_TIMESTAMP_23_16(self->buffer, self->buffer_index + pkt->rxbytenb_modem) << 16) & 0x00FF0000); pkt->timestamp_cnt |= (uint32_t)((SX1302_PKT_TIMESTAMP_31_24(self->buffer, self->buffer_index + pkt->rxbytenb_modem) << 24) & 0xFF000000); + /* TS metrics: it is expected the nb_symbols parameter is set to 0 here */ + for (i = 0; i < (pkt->num_ts_metrics_stored * 2); i++) { + pkt->timestamp_avg[i] = (int8_t)SX1302_PKT_NUM_TS_METRICS(self->buffer, self->buffer_index + pkt->rxbytenb_modem + 1 + i); + pkt->timestamp_stddev[i] = 0; /* no stddev when nb_symbols == 0 */ + } + DEBUG_MSG ("-----------------\n"); DEBUG_PRINTF(" modem: %u\n", pkt->modem_id); DEBUG_PRINTF(" chan: %u\n", pkt->rx_channel_in); @@ -298,6 +322,14 @@ int rx_buffer_pop(rx_buffer_t * self, rx_packet_t * pkt) { DEBUG_PRINTF(" codr: %u\n", pkt->coding_rate); DEBUG_PRINTF(" datr: %u\n", pkt->rx_rate_sf); DEBUG_PRINTF(" num_ts: %u\n", pkt->num_ts_metrics_stored); + if (pkt->num_ts_metrics_stored > 0) { + DEBUG_MSG(" ts_avg: "); + for (i = 0; i < (pkt->num_ts_metrics_stored * 2); i++) { + DEBUG_PRINTF("%d ", pkt->timestamp_avg[i]); + } + DEBUG_MSG("\n"); + DEBUG_MSG(" ts_stdev: NONE (nb_symbols=0)\n"); + } DEBUG_MSG ("-----------------\n"); /* Sanity checks: check the range of few metadata */ diff --git a/libloragw/src/loragw_sx1302_timestamp.c b/libloragw/src/loragw_sx1302_timestamp.c index 77c3d4d1..dc49c370 100644 --- a/libloragw/src/loragw_sx1302_timestamp.c +++ b/libloragw/src/loragw_sx1302_timestamp.c @@ -22,21 +22,23 @@ License: Revised BSD License, see LICENSE.TXT file include in the project /* --- DEPENDANCIES --------------------------------------------------------- */ #include /* C99 types */ +#include /* boolean type */ #include /* printf fprintf */ #include /* memset */ +#include /* PRIx64, PRIu64... */ +#include #include "loragw_sx1302_timestamp.h" #include "loragw_reg.h" -#include "loragw_hal.h" -#include "loragw_sx1302.h" +#include "loragw_aux.h" /* -------------------------------------------------------------------------- */ /* --- PRIVATE MACROS ------------------------------------------------------- */ #define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) -#if DEBUG_SX1302 == 1 - #define DEBUG_MSG(str) fprintf(stderr, str) - #define DEBUG_PRINTF(fmt, args...) fprintf(stderr, fmt, args) +#if DEBUG_FTIME == 1 + #define DEBUG_MSG(str) fprintf(stdout, str) + #define DEBUG_PRINTF(fmt, args...) fprintf(stdout, fmt, 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) @@ -47,21 +49,241 @@ License: Revised BSD License, see LICENSE.TXT file include in the project /* -------------------------------------------------------------------------- */ /* --- PRIVATE TYPES -------------------------------------------------------- */ +#define MAX_TIMESTAMP_PPS_HISTORY 16 +struct timestamp_pps_history_s { + uint32_t history[MAX_TIMESTAMP_PPS_HISTORY]; + uint8_t idx; /* next slot to be written */ + uint8_t size; /* current size */ +}; + /* -------------------------------------------------------------------------- */ /* --- PRIVATE CONSTANTS ---------------------------------------------------- */ +#define PRECISION_TIMESTAMP_TS_METRICS_MAX 32 /* reduce number of metrics to better match GW v2 fine timestamp (max is 255) */ +#define PRECISION_TIMESTAMP_NB_SYMBOLS 0 + /* -------------------------------------------------------------------------- */ /* --- PRIVATE VARIABLES ---------------------------------------------------- */ +/* history of the last PPS timestamps */ +static struct timestamp_pps_history_s timestamp_pps_history = { + .history = { 0 }, + .idx = 0, + .size = 0 +}; + /* -------------------------------------------------------------------------- */ /* --- PRIVATE FUNCTIONS DECLARATION ---------------------------------------- */ -/* -------------------------------------------------------------------------- */ -/* --- INTERNAL SHARED VARIABLES -------------------------------------------- */ +/** +@brief TODO +@param TODO +@return The correction to be applied to the packet timestamp, in microseconds +*/ +int32_t legacy_timestamp_correction(uint8_t bandwidth, uint8_t datarate, uint8_t coderate, bool no_crc, uint8_t payload_length, sx1302_rx_dft_peak_mode_t dft_peak_mode); + +/** +@brief TODO +@param TODO +@return The correction to be applied to the packet timestamp, in microseconds +*/ +int32_t precision_timestamp_correction(uint8_t bandwidth, uint8_t datarate, uint8_t coderate, bool crc_en, uint8_t payload_length); + + +/** +@brief TODO +@param TODO +@return The correction to be applied to the packet timestamp, in microseconds +*/ +void timestamp_pps_history_save(uint32_t timestamp_pps_reg); /* -------------------------------------------------------------------------- */ /* --- PRIVATE FUNCTIONS DEFINITION ----------------------------------------- */ +int32_t legacy_timestamp_correction(uint8_t bandwidth, uint8_t sf, uint8_t cr, bool crc_en, uint8_t payload_length, sx1302_rx_dft_peak_mode_t dft_peak_mode) { + uint64_t clk_period, filtering_delay, demap_delay, fft_delay_state3, fft_delay, decode_delay, total_delay; + uint32_t nb_nibble, nb_nibble_in_hdr, nb_nibble_in_last_block; + uint8_t nb_iter, bw_pow, dft_peak_en = (dft_peak_mode == RX_DFT_PEAK_MODE_DISABLED) ? 0 : 1; + uint8_t ppm = SET_PPM_ON(bandwidth, sf) ? 1 : 0; + int32_t timestamp_correction; + bool payload_fits_in_header = false; + uint8_t cr_local = cr; + + switch (bandwidth) + { + case BW_125KHZ: + bw_pow = 1; + break; + case BW_250KHZ: + bw_pow = 2; + break; + case BW_500KHZ: + bw_pow = 4; + break; + default: + printf("ERROR: UNEXPECTED VALUE %d IN SWITCH STATEMENT - %s\n", bandwidth, __FUNCTION__); + return 0; + } + + /* Prepare variables for delay computing */ + clk_period = 250E3 / bw_pow; + + nb_nibble = (payload_length + 2 * crc_en) * 2 + 5; + + if ((sf == 5) || (sf == 6)) { + nb_nibble_in_hdr = sf; + } else { + nb_nibble_in_hdr = sf - 2; + } + + nb_nibble_in_last_block = nb_nibble - nb_nibble_in_hdr - (sf - 2 * ppm) * ((nb_nibble - nb_nibble_in_hdr) / (sf - 2 * ppm)); + if (nb_nibble_in_last_block == 0) { + nb_nibble_in_last_block = sf - 2 * ppm; + } + + nb_iter = (sf + 1) / 2; /* intended to be truncated */ + + /* Update some variables if payload fits entirely in the header */ + if (((int)(2 * (payload_length + 2 * crc_en) - (sf - 7)) <= 0) || ((payload_length == 0) && (crc_en == false))) { + /* Payload fits entirely in first 8 symbols (header): + - not possible for SF5/SF6, unless payload length is 0 and no CRC + */ + payload_fits_in_header = true; + + /* overwrite some variables accordingly */ + dft_peak_en = 0; + + cr_local = 4; /* header coding rate is 4 */ + + if (sf > 6) { + nb_nibble_in_last_block = sf - 2; + } else { + nb_nibble_in_last_block = sf; + } + } + + /* Filtering delay : I/Q 32Mhz -> 4Mhz */ + filtering_delay = 16000E3 / bw_pow + 2031250; + + /* demap delay */ + if (payload_fits_in_header == true) { + demap_delay = clk_period + (1 << sf) * clk_period * 3 / 4 + 3 * clk_period + (sf - 2) * clk_period; + } else { + demap_delay = clk_period + (1 << sf) * clk_period * (1 - ppm / 4) + 3 * clk_period + (sf - 2 * ppm) * clk_period; + } + + /* FFT delays */ + fft_delay_state3 = clk_period * (((1 << sf) - 6) + 2 * ((1 << sf) * (nb_iter - 1) + 6)) + 4 * clk_period; + + if (dft_peak_en) { + fft_delay = (5 - 2 * ppm) * ((1 << sf) * clk_period + 7 * clk_period) + 2 * clk_period; + } else { + fft_delay = (1 << sf) * 2 * clk_period + 3 * clk_period; + } + + /* Decode delay */ + decode_delay = 5 * clk_period + (9 * clk_period + clk_period * cr_local) * nb_nibble_in_last_block + 3 * clk_period; + + /* Cumulated delays */ + total_delay = (filtering_delay + fft_delay_state3 + fft_delay + demap_delay + decode_delay + 500E3) / 1E6; + + if (total_delay > INT32_MAX) { + printf("ERROR: overflow error for timestamp correction (SHOULD NOT HAPPEN)\n"); + printf("=> filtering_delay %" PRIu64 "\n", filtering_delay); + printf("=> fft_delay_state3 %" PRIu64 "\n", fft_delay_state3); + printf("=> fft_delay %" PRIu64 "\n", fft_delay); + printf("=> demap_delay %" PRIu64 "\n", demap_delay); + printf("=> decode_delay %" PRIu64 "\n", decode_delay); + printf("=> total_delay %" PRIu64 "\n", total_delay); + assert(0); + } + + timestamp_correction = -((int32_t)total_delay); /* compensate all decoding processing delays */ + + DEBUG_PRINTF("FTIME OFF : filtering_delay %llu \n", filtering_delay); + DEBUG_PRINTF("FTIME OFF : fft_delay_state3 %llu \n", fft_delay_state3); + DEBUG_PRINTF("FTIME OFF : fft_delay %llu \n", fft_delay); + DEBUG_PRINTF("FTIME OFF : demap_delay %llu \n", demap_delay); + DEBUG_PRINTF("FTIME OFF : decode_delay %llu \n", decode_delay); + DEBUG_PRINTF("FTIME OFF : timestamp correction %d \n", timestamp_correction); + + return timestamp_correction; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int32_t precision_timestamp_correction(uint8_t bandwidth, uint8_t datarate, uint8_t coderate, bool crc_en, uint8_t payload_length) { + uint32_t nb_symbols_payload; + uint16_t t_symbol_us; + int32_t timestamp_correction; + uint8_t bw_pow; + uint32_t filtering_delay; + + switch (bandwidth) + { + case BW_125KHZ: + bw_pow = 1; + break; + case BW_250KHZ: + bw_pow = 2; + break; + case BW_500KHZ: + bw_pow = 4; + break; + default: + printf("ERROR: UNEXPECTED VALUE %d IN SWITCH STATEMENT - %s\n", bandwidth, __FUNCTION__); + return 0; + } + + filtering_delay = 16000000 / bw_pow + 2031250; + + /* NOTE: no need of the preamble size, only the payload duration is needed */ + /* WARNING: implicit header not supported */ + if (lora_packet_time_on_air(bandwidth, datarate, coderate, 0, false, !crc_en, payload_length, NULL, &nb_symbols_payload, &t_symbol_us) == 0) { + printf("ERROR: failed to compute packet time on air - %s\n", __FUNCTION__); + return 0; + } + + timestamp_correction = 0; + timestamp_correction += (nb_symbols_payload * t_symbol_us); /* shift from end of header to end of packet */ + timestamp_correction -= (filtering_delay + 500E3) / 1E6; /* compensate the filtering delay */ + + DEBUG_PRINTF("FTIME ON : timestamp correction %d \n", timestamp_correction); + + return timestamp_correction; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +void timestamp_pps_history_save(uint32_t timestamp_pps_reg) { + /* Store it only if different from the previous one */ + if ((timestamp_pps_reg != timestamp_pps_history.history[timestamp_pps_history.idx] || (timestamp_pps_history.size == 0))) { + /* Select next index */ + if (timestamp_pps_history.size > 0) { + timestamp_pps_history.idx += 1; + } + if (timestamp_pps_history.idx == MAX_TIMESTAMP_PPS_HISTORY) { + timestamp_pps_history.idx = 0; + } + + /* Set PPS counter value */ + timestamp_pps_history.history[timestamp_pps_history.idx] = timestamp_pps_reg; + + /* Add one entry to the history */ + if (timestamp_pps_history.size < MAX_TIMESTAMP_PPS_HISTORY) { + timestamp_pps_history.size += 1; + } + +#if 0 + printf("---- timestamp PPS history (idx:%u size:%u) ----\n", timestamp_pps_history.idx, timestamp_pps_history.size); + for (int i = 0; i < timestamp_pps_history.size; i++) { + printf(" %u\n", timestamp_pps_history.history[i]); + } + printf("--------------------------------\n"); +#endif + } +} + /* -------------------------------------------------------------------------- */ /* --- PUBLIC FUNCTIONS DEFINITION ------------------------------------------ */ @@ -77,67 +299,79 @@ void timestamp_counter_delete(timestamp_counter_t * self) { /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ -void timestamp_counter_update(timestamp_counter_t * self, bool pps, uint32_t cnt) { - struct timestamp_info_s* tinfo = (pps == true) ? &self->pps : &self->inst; +void timestamp_counter_update(timestamp_counter_t * self, uint32_t pps, uint32_t inst) { + //struct timestamp_info_s* tinfo = (pps == true) ? &self->pps : &self->inst; /* Check if counter has wrapped, and update wrap status if necessary */ - if (cnt < tinfo->counter_us_27bits_ref) { - tinfo->counter_us_27bits_wrap += 1; - tinfo->counter_us_27bits_wrap %= 32; + if (pps < self->pps.counter_us_27bits_ref) { + self->pps.counter_us_27bits_wrap += 1; + self->pps.counter_us_27bits_wrap %= 32; + } + if (inst < self->inst.counter_us_27bits_ref) { + self->inst.counter_us_27bits_wrap += 1; + self->inst.counter_us_27bits_wrap %= 32; } /* Update counter reference */ - tinfo->counter_us_27bits_ref = cnt; + self->pps.counter_us_27bits_ref = pps; + self->inst.counter_us_27bits_ref = inst; } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ -uint32_t timestamp_counter_get(timestamp_counter_t * self, bool pps) { +int timestamp_counter_get(timestamp_counter_t * self, uint32_t * inst, uint32_t * pps) { int x; - uint8_t buff[4]; - uint32_t counter_us_raw_27bits_now; - int32_t msb; - - /* Get the 32MHz timestamp counter - 4 bytes */ - x = lgw_reg_rb((pps == true) ? SX1302_REG_TIMESTAMP_TIMESTAMP_PPS_MSB2_TIMESTAMP_PPS : - SX1302_REG_TIMESTAMP_TIMESTAMP_MSB2_TIMESTAMP, - &buff[0], 4); + uint8_t buff[8]; + uint8_t buff_wa[8]; + uint32_t counter_inst_us_raw_27bits_now; + uint32_t counter_pps_us_raw_27bits_now; + + /* Get the freerun and pps 32MHz timestamp counters - 8 bytes + 0 -> 3 : PPS counter + 4 -> 7 : Freerun counter (inst) + */ + x = lgw_reg_rb(SX1302_REG_TIMESTAMP_TIMESTAMP_PPS_MSB2_TIMESTAMP_PPS, &buff[0], 8); if (x != LGW_REG_SUCCESS) { printf("ERROR: Failed to get timestamp counter value\n"); - return 0; + return -1; } /* Workaround concentrator chip issue: - read MSB again - - if MSB changed, read the full counter gain + - if MSB changed, read the full counter again */ - x = lgw_reg_r((pps == true) ? SX1302_REG_TIMESTAMP_TIMESTAMP_PPS_MSB2_TIMESTAMP_PPS : - SX1302_REG_TIMESTAMP_TIMESTAMP_MSB2_TIMESTAMP, - &msb); + x = lgw_reg_rb(SX1302_REG_TIMESTAMP_TIMESTAMP_PPS_MSB2_TIMESTAMP_PPS, &buff_wa[0], 8); if (x != LGW_REG_SUCCESS) { printf("ERROR: Failed to get timestamp counter MSB value\n"); - return 0; + return -1; } - if (buff[0] != (uint8_t)msb) { - x = lgw_reg_rb((pps == true) ? SX1302_REG_TIMESTAMP_TIMESTAMP_PPS_MSB2_TIMESTAMP_PPS : - SX1302_REG_TIMESTAMP_TIMESTAMP_MSB2_TIMESTAMP, - &buff[0], 4); + if ((buff[0] != buff_wa[0]) || (buff[4] != buff_wa[4])) { + x = lgw_reg_rb(SX1302_REG_TIMESTAMP_TIMESTAMP_PPS_MSB2_TIMESTAMP_PPS, &buff_wa[0], 8); if (x != LGW_REG_SUCCESS) { - printf("ERROR: Failed to get timestamp counter value\n"); - return 0; + printf("ERROR: Failed to get timestamp counter MSB value\n"); + return -1; } + memcpy(buff, buff_wa, 8); /* use the new read value */ } - counter_us_raw_27bits_now = (buff[0]<<24) | (buff[1]<<16) | (buff[2]<<8) | buff[3]; + counter_pps_us_raw_27bits_now = (buff[0]<<24) | (buff[1]<<16) | (buff[2]<<8) | buff[3]; + counter_inst_us_raw_27bits_now = (buff[4]<<24) | (buff[5]<<16) | (buff[6]<<8) | buff[7]; + + /* Store PPS counter to history, for fine timestamp calculation */ + timestamp_pps_history_save(counter_pps_us_raw_27bits_now); /* Scale to 1MHz */ - counter_us_raw_27bits_now /= 32; + counter_pps_us_raw_27bits_now /= 32; + counter_inst_us_raw_27bits_now /= 32; /* Update counter wrapping status */ - timestamp_counter_update(self, pps, counter_us_raw_27bits_now); + timestamp_counter_update(self, counter_pps_us_raw_27bits_now, counter_inst_us_raw_27bits_now); /* Convert 27-bits counter to 32-bits counter */ - return timestamp_counter_expand(self, pps, counter_us_raw_27bits_now); + *inst = timestamp_counter_expand(self, false, counter_inst_us_raw_27bits_now); + *pps = timestamp_counter_expand(self, true, counter_pps_us_raw_27bits_now); + + return 0; } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ @@ -191,121 +425,207 @@ uint32_t timestamp_pkt_expand(timestamp_counter_t * self, uint32_t pkt_cnt_us) { /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ -int timestamp_counter_mode(bool enable_precision_ts, uint8_t max_ts_metrics, uint8_t nb_symbols) { - if (enable_precision_ts == false) { - DEBUG_MSG("INFO: using legacy timestamp\n"); +int timestamp_counter_mode(bool ftime_enable) { + int x = LGW_REG_SUCCESS; + + if (ftime_enable == false) { + printf("INFO: using legacy timestamp\n"); /* Latch end-of-packet timestamp (sx1301 compatibility) */ - lgw_reg_w(SX1302_REG_RX_TOP_RX_BUFFER_LEGACY_TIMESTAMP, 0x01); + x |= lgw_reg_w(SX1302_REG_RX_TOP_RX_BUFFER_LEGACY_TIMESTAMP, 0x01); } else { - DEBUG_PRINTF("INFO: using precision timestamp (max_ts_metrics:%u nb_symbols:%u)\n", max_ts_metrics, nb_symbols); + printf("INFO: using precision timestamp (max_ts_metrics:%u nb_symbols:%u)\n", PRECISION_TIMESTAMP_TS_METRICS_MAX, PRECISION_TIMESTAMP_NB_SYMBOLS); + /* Latch end-of-preamble timestamp */ - lgw_reg_w(SX1302_REG_RX_TOP_RX_BUFFER_LEGACY_TIMESTAMP, 0x00); - lgw_reg_w(SX1302_REG_RX_TOP_RX_BUFFER_TIMESTAMP_CFG_MAX_TS_METRICS, max_ts_metrics); + x |= lgw_reg_w(SX1302_REG_RX_TOP_RX_BUFFER_LEGACY_TIMESTAMP, 0x00); + x |= lgw_reg_w(SX1302_REG_RX_TOP_RX_BUFFER_TIMESTAMP_CFG_MAX_TS_METRICS, (int32_t)PRECISION_TIMESTAMP_TS_METRICS_MAX); /* LoRa multi-SF modems */ - lgw_reg_w(SX1302_REG_RX_TOP_TIMESTAMP_ENABLE, 0x01); - lgw_reg_w(SX1302_REG_RX_TOP_TIMESTAMP_NB_SYMB, nb_symbols); + x |= lgw_reg_w(SX1302_REG_RX_TOP_TIMESTAMP_ENABLE, 0x01); + x |= lgw_reg_w(SX1302_REG_RX_TOP_TIMESTAMP_NB_SYMB, (int32_t)PRECISION_TIMESTAMP_NB_SYMBOLS); + } - /* LoRa service modem */ - lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_TIMESTAMP_ENABLE, 0x01); - lgw_reg_w(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_TIMESTAMP_NB_SYMB, nb_symbols); + return x; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int32_t timestamp_counter_correction(lgw_context_t * context, uint8_t bandwidth, uint8_t datarate, uint8_t coderate, bool crc_en, uint8_t payload_length, sx1302_rx_dft_peak_mode_t dft_peak_mode) { + /* Check input parameters */ + CHECK_NULL(context); + if (IS_LORA_DR(datarate) == false) { + printf("ERROR: wrong datarate (%u) - %s\n", datarate, __FUNCTION__); + return 0; + } + if (IS_LORA_BW(bandwidth) == false) { + printf("ERROR: wrong bandwidth (%u) - %s\n", bandwidth, __FUNCTION__); + return 0; + } + if (IS_LORA_CR(coderate) == false) { + printf("ERROR: wrong coding rate (%u) - %s\n", coderate, __FUNCTION__); + return 0; } - return LGW_REG_SUCCESS; + /* Calculate the correction to be applied */ + if (context->ftime_cfg.enable == false) { + return legacy_timestamp_correction(bandwidth, datarate, coderate, crc_en, payload_length, dft_peak_mode); + } else { + return precision_timestamp_correction(bandwidth, datarate, coderate, crc_en, payload_length); + } } /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ -uint32_t timestamp_counter_correction(int ifmod, uint8_t bandwidth, uint8_t datarate, uint8_t coderate, uint32_t crc_en, uint16_t payload_length) { - int32_t val; - uint32_t sf = (uint32_t)datarate, cr = (uint32_t)coderate, bw_pow; - uint32_t clk_period; - uint32_t nb_nibble, nb_nibble_in_hdr, nb_nibble_in_last_block; - uint32_t dft_peak_en, nb_iter; - uint32_t demap_delay, decode_delay, fft_delay_state3, fft_delay, delay_x; - uint32_t timestamp_correction; - uint32_t ppm = SET_PPM_ON(bandwidth, datarate) ? 1 : 0; +int precise_timestamp_calculate(uint8_t ts_metrics_nb, const int8_t * ts_metrics, uint32_t timestamp_cnt, uint8_t sf, int32_t if_freq_hz, uint32_t * result_ftime) { + int i, x, timestamp_pps_idx, timestamp_pps_idx_next, timestamp_pps_idx_prev; + int32_t ftime_sum; + int32_t ftime[256]; + float ftime_mean; + uint32_t timestamp_cnt_end_of_preamble; + uint32_t timestamp_pps = 0; + uint32_t timestamp_pps_reg = 0; + uint32_t offset_preamble_hdr; + uint8_t buff[4]; + uint32_t diff_pps; + double pkt_ftime; + uint8_t ts_metrics_nb_clipped; + double xtal_correct; + + /* Check input parameters */ + CHECK_NULL(ts_metrics); + CHECK_NULL(result_ftime); + + /* Check if we can calculate a ftime */ + if (timestamp_pps_history.size < MAX_TIMESTAMP_PPS_HISTORY) { + printf("INFO: Cannot compute ftime yet, PPS history is too short\n"); + return -1; + } - switch (bandwidth) - { - case BW_125KHZ: - bw_pow = 1; + /* Coarse timestamp correction to match with GW v2 (end of header -> end of preamble) */ + offset_preamble_hdr = 256 * (1 << sf) * (8 + 4 + (((sf == 5) || (sf == 6)) ? 2 : 0)) + + 256 * ((1 << sf) / 4 - 1); /* 32e6 / 125e3 = 256 */ + + timestamp_cnt_end_of_preamble = timestamp_cnt - offset_preamble_hdr + 2138; /* 2138 is the number of 32MHz clock cycle offset b/w GW_V2 and SX1303 decimation/filtering group delay */ + + /* Shift the packet coarse timestamp which is used to get ref PPS counter */ + timestamp_cnt = timestamp_cnt_end_of_preamble; + + /* Clip the number of metrics depending on Spreading Factor, reduce fine timestamp variation versus packet duration */ + switch (sf) { + case 12: + ts_metrics_nb_clipped = MIN(4, ts_metrics_nb); break; - case BW_250KHZ: - bw_pow = 2; + case 11: + ts_metrics_nb_clipped = MIN(8, ts_metrics_nb); break; - case BW_500KHZ: - bw_pow = 4; + case 10: + ts_metrics_nb_clipped = MIN(16, ts_metrics_nb); break; default: - DEBUG_PRINTF("ERROR: UNEXPECTED VALUE %d IN SWITCH STATEMENT\n", bandwidth); - return 0; + ts_metrics_nb_clipped = MIN(32, ts_metrics_nb); + break; } - clk_period = 250000 / bw_pow; - delay_x = 16000000 / bw_pow + 2031250; - nb_nibble = (payload_length + 2 * crc_en) * 2 + 5; - - if ((sf == 5) || (sf == 6)) { - nb_nibble_in_hdr = sf; - } else { - nb_nibble_in_hdr = sf - 2; +#if 0 + printf("%s\n", __FUNCTION__); + printf("ts_metrics_nb_clipped*2: %u\n", ts_metrics_nb_clipped * 2); + for (i = 0; i < (2 * ts_metrics_nb_clipped); i++) { + printf("%d ", ts_metrics[i]); } + printf("\n"); +#endif - nb_nibble_in_last_block = nb_nibble - nb_nibble_in_hdr - (sf - 2 * ppm) * ((nb_nibble - nb_nibble_in_hdr) / (sf - 2 * ppm)); - if (nb_nibble_in_last_block == 0) { - nb_nibble_in_last_block = sf - 2 * ppm; + /* Compute the ftime cumulative sum */ + ftime[0] = (int32_t)ts_metrics[0]; + ftime_sum = ftime[0]; + for (i = 1; i < (2 * ts_metrics_nb_clipped); i++) { + ftime[i] = ftime[i-1] + ts_metrics[i]; + ftime_sum += ftime[i]; } - nb_iter = ((sf + 1) >> 1); + /* Compute the mean of the cumulative sum */ + ftime_mean = (float)ftime_sum / (float)(2 * ts_metrics_nb_clipped); - /* timestamp correction code, variable delay */ - if (ifmod == IF_LORA_STD) { - lgw_reg_r(SX1302_REG_RX_TOP_LORA_SERVICE_FSK_RX_CFG0_DFT_PEAK_EN, &val); - } else { - lgw_reg_r(SX1302_REG_RX_TOP_RX_CFG0_DFT_PEAK_EN, &val); + /* Find the last timestamp_pps before packet to use as reference for ftime */ + x = lgw_reg_rb(SX1302_REG_TIMESTAMP_TIMESTAMP_PPS_MSB2_TIMESTAMP_PPS , &buff[0], 4); + if (x != LGW_REG_SUCCESS) { + printf("ERROR: Failed to get timestamp counter value\n"); + return 0; } - if (val != 0) { - /* TODO: should we differentiate the mode (FULL/TRACK) ? */ - dft_peak_en = 1; + timestamp_pps_reg = (uint32_t)((buff[0] << 24) & 0xFF000000); + timestamp_pps_reg |= (uint32_t)((buff[1] << 16) & 0x00FF0000); + timestamp_pps_reg |= (uint32_t)((buff[2] << 8) & 0x0000FF00); + timestamp_pps_reg |= (uint32_t)((buff[3] << 0) & 0x000000FF); + + /* Ensure that the timestamp PPS history is up-to-date */ + timestamp_pps_history_save(timestamp_pps_reg); + + /* Check if timestamp_pps_reg we just read is the reference to be used to compute ftime or not */ + if ((timestamp_cnt - timestamp_pps_reg) > 32e6) { + /* The timestamp_pps_reg we just read is after the packet timestamp, we need to rewind */ + for (timestamp_pps_idx = 0; timestamp_pps_idx < timestamp_pps_history.size; timestamp_pps_idx++) { + /* search the pps counter in history */ + if ((timestamp_cnt - timestamp_pps_history.history[timestamp_pps_idx]) < 32e6) { + timestamp_pps = timestamp_pps_history.history[timestamp_pps_idx]; + DEBUG_PRINTF("==> timestamp_pps found at history[%d] => %u\n", timestamp_pps_idx, timestamp_pps); + break; + } + } + if (timestamp_pps_idx == timestamp_pps_history.size) { + printf("ERROR: failed to find the reference timestamp_pps, cannot compute ftime\n"); + return -1; + } + + /* Calculate the Xtal error between the reference PPS we just found and the next one */ + timestamp_pps_idx_next = (timestamp_pps_idx == (MAX_TIMESTAMP_PPS_HISTORY - 1)) ? 0 : timestamp_pps_idx + 1; + diff_pps = timestamp_pps_history.history[timestamp_pps_idx_next] - timestamp_pps_history.history[timestamp_pps_idx]; + xtal_correct = (double)32e6 / (double)(diff_pps); } else { - dft_peak_en = 0; + /* The timestamp_pps_reg we just read is the reference we use to calculate the fine timestamp */ + timestamp_pps = timestamp_pps_reg; + DEBUG_PRINTF("==> timestamp_pps => %u\n", timestamp_pps); + + /* Calculate the Xtal error between the reference PPS we just found and the previous one */ + timestamp_pps_idx = timestamp_pps_history.idx; + timestamp_pps_idx_prev = (timestamp_pps_idx == 0) ? (MAX_TIMESTAMP_PPS_HISTORY - 1) : (timestamp_pps_idx - 1); + diff_pps = timestamp_pps_history.history[timestamp_pps_idx] - timestamp_pps_history.history[timestamp_pps_idx_prev]; + xtal_correct = (double)32e6 / (double)(diff_pps); } + /* Sanity Check on xtal_correct */ + if ((xtal_correct > 1.2) || (xtal_correct < 0.8)) { + printf("ERROR: xtal_error is invalid (%.15lf)\n", xtal_correct); + return -1; + } - if ((sf >= 5) && (sf <= 12) && (bw_pow > 0)) { - if ((2 * (payload_length + 2 * crc_en) - (sf - 7)) <= 0) { /* payload fits entirely in first 8 symbols (header) */ - if (sf > 6) { - nb_nibble_in_last_block = sf - 2; - } else { - nb_nibble_in_last_block = sf; // can't be acheived - } - dft_peak_en = 0; - cr = 4; /* header coding rate is 4 */ - demap_delay = clk_period + (1 << sf) * clk_period * 3 / 4 + 3 * clk_period + (sf - 2) * clk_period; - } else { - demap_delay = clk_period + (1 << sf) * clk_period * (1 - ppm / 4) + 3 * clk_period + (sf - 2 * ppm) * clk_period; - } + /* Coarse timestamp based on PPS reference */ + diff_pps = timestamp_cnt - timestamp_pps; - fft_delay_state3 = clk_period * (((1 << sf) - 6) + 2 * ((1 << sf) * (nb_iter - 1) + 6)) + 4 * clk_period; + DEBUG_PRINTF("timestamp_cnt : %u\n", timestamp_cnt); + DEBUG_PRINTF("timestamp_pps : %u\n", timestamp_pps); + DEBUG_PRINTF("diff_pps : %d\n", diff_pps); - if (dft_peak_en) { - fft_delay = (5 - 2 * ppm) * ((1 << sf) * clk_period + 7 * clk_period) + 2 * clk_period; - } else { - fft_delay = (1 << sf) * 2 * clk_period + 3 * clk_period; - } + /* Compute the fine timestamp */ + pkt_ftime = (double)diff_pps + (double)ftime_mean; + DEBUG_PRINTF("pkt_ftime = %f\n", pkt_ftime); - decode_delay = 5 * clk_period + (9 * clk_period + clk_period * cr) * nb_nibble_in_last_block + 3 * clk_period; - timestamp_correction = (uint32_t)(delay_x + fft_delay_state3 + fft_delay + demap_delay + decode_delay + 0.5e6) / 1e6; - //printf("INFO: timestamp_correction = %u us (delay_x %u, fft_delay_state3=%u, fft_delay=%u, demap_delay=%u, decode_delay = %u)\n", timestamp_correction, delay_x, fft_delay_state3, fft_delay, demap_delay, decode_delay); - } - else - { - timestamp_correction = 0; - DEBUG_MSG("WARNING: invalid packet, no timestamp correction\n"); + /* Add the DC notch filtering delay if necessary */ + pkt_ftime += sx1302_dc_notch_delay((double)if_freq_hz / 1E3); + + /* Convert fine timestamp from 32 Mhz clock to nanoseconds */ + pkt_ftime *= 31.25; + + /* Apply current XTAL error correction */ + pkt_ftime *= xtal_correct; + + *result_ftime = (uint32_t)pkt_ftime; + if (*result_ftime > 1E9) { + printf("ERROR: fine timestamp is out of range (%u)\n", *result_ftime); + return -1; } - return timestamp_correction; + DEBUG_PRINTF("==> ftime = %u ns since last PPS (%.15lf)\n", *result_ftime, pkt_ftime); + + return 0; } /* --- EOF ------------------------------------------------------------------ */ diff --git a/libloragw/src/loragw_usb.c b/libloragw/src/loragw_usb.c new file mode 100644 index 00000000..4fe7e9ab --- /dev/null +++ b/libloragw/src/loragw_usb.c @@ -0,0 +1,505 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2020 Semtech + +Description: + Host specific functions to address the LoRa concentrator registers through + a USB interface. + Single-byte read/write and burst read/write. + +License: Revised BSD License, see LICENSE.TXT file include in the project +*/ + + +/* -------------------------------------------------------------------------- */ +/* --- DEPENDANCIES --------------------------------------------------------- */ + +#include /* C99 types */ +#include /* bool type */ +#include /* printf fprintf */ +#include /* malloc free */ +#include /* lseek, close */ +#include /* open */ +#include /* strncmp */ +#include /* Error number definitions */ +#include /* POSIX terminal control definitions */ + +#include "loragw_com.h" +#include "loragw_usb.h" +#include "loragw_mcu.h" +#include "loragw_aux.h" + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE MACROS ------------------------------------------------------- */ + +#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) +#if DEBUG_COM == 1 + #define DEBUG_MSG(str) fprintf(stdout, str) + #define DEBUG_PRINTF(fmt, args...) fprintf(stdout, fmt, args) + #define CHECK_NULL(a) if(a==NULL){fprintf(stderr,"%s:%d: ERROR: NULL POINTER AS ARGUMENT\n", __FUNCTION__, __LINE__);return LGW_USB_ERROR;} +#else + #define DEBUG_MSG(str) + #define DEBUG_PRINTF(fmt, args...) + #define CHECK_NULL(a) if(a==NULL){return LGW_USB_ERROR;} +#endif + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE CONSTANTS ---------------------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE TYPES -------------------------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE VARIABLES --------------------------------------------------- */ + +static lgw_com_write_mode_t _lgw_write_mode = LGW_COM_WRITE_MODE_SINGLE; +static uint8_t _lgw_spi_req_nb = 0; + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE FUNCTIONS DEFINITION ----------------------------------------- */ + +int set_interface_attribs_linux(int fd, int speed) { + struct termios tty; + + memset(&tty, 0, sizeof tty); + + /* Get current attributes */ + if (tcgetattr(fd, &tty) != 0) { + DEBUG_PRINTF("ERROR: tcgetattr failed with %d - %s", errno, strerror(errno)); + return LGW_USB_ERROR; + } + + cfsetospeed(&tty, speed); + cfsetispeed(&tty, speed); + + /* Control Modes */ + tty.c_cflag = (tty.c_cflag & ~CSIZE) | CS8; /* set 8-bit characters */ + tty.c_cflag |= CLOCAL; /* local connection, no modem control */ + tty.c_cflag |= CREAD; /* enable receiving characters */ + tty.c_cflag &= ~PARENB; /* no parity */ + tty.c_cflag &= ~CSTOPB; /* one stop bit */ + /* Input Modes */ + tty.c_iflag &= ~IGNBRK; + tty.c_iflag &= ~(IXON | IXOFF | IXANY | ICRNL); + /* Output Modes */ + tty.c_oflag &= ~IGNBRK; + tty.c_oflag &= ~(IXON | IXOFF | IXANY | ICRNL); + /* Local Modes */ + tty.c_lflag = 0; + /* Settings for non-canonical mode */ + tty.c_cc[VMIN] = 0; /* non-blocking mode */ + tty.c_cc[VTIME] = 0; /* wait for (n * 0.1) seconds before returning */ + + /* Set attributes */ + if (tcsetattr(fd, TCSANOW, &tty) != 0) { + DEBUG_PRINTF("ERROR: tcsetattr failed with %d - %s", errno, strerror(errno)); + return LGW_USB_ERROR; + } + + return LGW_USB_SUCCESS; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +/* configure serial interface to be read blocking or not*/ +int set_blocking_linux(int fd, bool blocking) { + struct termios tty; + + memset(&tty, 0, sizeof tty); + + /* Get current attributes */ + if (tcgetattr(fd, &tty) != 0) { + DEBUG_PRINTF("ERROR: tcgetattr failed with %d - %s", errno, strerror(errno)); + return LGW_USB_ERROR; + } + + tty.c_cc[VMIN] = (blocking == true) ? 1 : 0; /* set blocking or non-blocking mode */ + tty.c_cc[VTIME] = 1; /* wait for (n * 0.1) seconds before returning */ + + /* Set attributes */ + if (tcsetattr(fd, TCSANOW, &tty) != 0) { + DEBUG_PRINTF("ERROR: tcsetattr failed with %d - %s", errno, strerror(errno)); + return LGW_USB_ERROR; + } + + return LGW_USB_SUCCESS; +} + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC FUNCTIONS DEFINITION ------------------------------------------ */ + +int lgw_usb_open(const char * com_path, void **com_target_ptr) { + int *usb_device = NULL; + char portname[50]; + int x; + int fd; + s_ping_info gw_info; + s_status mcu_status; + uint8_t data; + ssize_t n; + + /*check input variables*/ + CHECK_NULL(com_target_ptr); + + usb_device = malloc(sizeof(int)); + if (usb_device == NULL) { + DEBUG_MSG("ERROR : MALLOC FAIL\n"); + return LGW_USB_ERROR; + } + + /* open tty port */ + sprintf(portname, "%s", com_path); + fd = open(portname, O_RDWR | O_NOCTTY | O_SYNC); + if (fd < 0) { + printf("ERROR: failed to open COM port %s - %s\n", portname, strerror(errno)); + } else { + printf("INFO: Configuring TTY\n"); + x = set_interface_attribs_linux(fd, B115200); + if (x != 0) { + printf("ERROR: failed to configure COM port %s\n", portname); + free(usb_device); + return LGW_USB_ERROR; + } + + /* flush tty port before setting it as blocking */ + printf("INFO: Flushing TTY\n"); + do { + n = read(fd, &data, 1); + if (n > 0) { + printf("NOTE: flushing serial port (0x%2X)\n", data); + } + } while (n > 0); + + /* set tty port blocking */ + printf("INFO: Setting TTY in blocking mode\n"); + x = set_blocking_linux(fd, true); + if (x != 0) { + printf("ERROR: failed to configure COM port %s\n", portname); + free(usb_device); + return LGW_USB_ERROR; + } + + *usb_device = fd; + *com_target_ptr = (void*)usb_device; + + /* Initialize pseudo-random generator for MCU request ID */ + srand(0); + + /* Check MCU version (ignore first char of the received version (release/debug) */ + printf("INFO: Connect to MCU\n"); + if (mcu_ping(fd, &gw_info) != 0) { + printf("ERROR: failed to ping the concentrator MCU\n"); + return LGW_USB_ERROR; + } + if (strncmp(gw_info.version + 1, mcu_version_string, sizeof mcu_version_string) != 0) { + printf("WARNING: MCU version mismatch (expected:%s, got:%s)\n", mcu_version_string, gw_info.version); + } + printf("INFO: Concentrator MCU version is %s\n", gw_info.version); + + /* Get MCU status */ + if (mcu_get_status(fd, &mcu_status) != 0) { + printf("ERROR: failed to get status from the concentrator MCU\n"); + return LGW_USB_ERROR; + } + printf("INFO: MCU status: sys_time:%u temperature:%.1foC\n", mcu_status.system_time_ms, mcu_status.temperature); + + /* Reset SX1302 */ + x = mcu_gpio_write(fd, 0, 1, 1); /* set PA1 : POWER_EN */ + x |= mcu_gpio_write(fd, 0, 2, 1); /* set PA2 : SX1302_RESET active */ + x |= mcu_gpio_write(fd, 0, 2, 0); /* unset PA2 : SX1302_RESET inactive */ + /* Reset SX1261 (LBT / Spectral Scan) */ + x |= mcu_gpio_write(fd, 0, 8, 0); /* set PA8 : SX1261_NRESET active */ + x |= mcu_gpio_write(fd, 0, 8, 1); /* unset PA8 : SX1261_NRESET inactive */ + if (x != 0) { + printf("ERROR: failed to reset SX1302\n"); + free(usb_device); + return LGW_USB_ERROR; + } + + return LGW_USB_SUCCESS; + } + + free(usb_device); + return LGW_USB_ERROR; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +/* SPI release */ +int lgw_usb_close(void *com_target) { + int usb_device; + int x, err = LGW_USB_SUCCESS; + + /* check input variables */ + CHECK_NULL(com_target); + + usb_device = *(int *)com_target; + + /* Reset SX1302 before closing */ + x = mcu_gpio_write(usb_device, 0, 1, 1); /* set PA1 : POWER_EN */ + x |= mcu_gpio_write(usb_device, 0, 2, 1); /* set PA2 : SX1302_RESET active */ + x |= mcu_gpio_write(usb_device, 0, 2, 0); /* unset PA2 : SX1302_RESET inactive */ + /* Reset SX1261 (LBT / Spectral Scan) */ + x |= mcu_gpio_write(usb_device, 0, 8, 0); /* set PA8 : SX1261_NRESET active */ + x |= mcu_gpio_write(usb_device, 0, 8, 1); /* unset PA8 : SX1261_NRESET inactive */ + if (x != 0) { + printf("ERROR: failed to reset SX1302\n"); + err = LGW_USB_ERROR; + } + + /* close file & deallocate file descriptor */ + x = close(usb_device); + free(com_target); + if (x != 0) { + printf("ERROR: failed to close USB file\n"); + err = LGW_USB_ERROR; + } + + /* determine return code */ + if (err != 0) { + printf("ERROR: USB PORT FAILED TO CLOSE\n"); + return LGW_USB_ERROR; + } else { + DEBUG_MSG("Note: USB port closed\n"); + return LGW_USB_SUCCESS; + } +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +/* Simple write */ +int lgw_usb_w(void *com_target, uint8_t spi_mux_target, uint16_t address, uint8_t data) { + return lgw_usb_wb(com_target, spi_mux_target, address, &data, 1); +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +/* Simple read */ +int lgw_usb_r(void *com_target, uint8_t spi_mux_target, uint16_t address, uint8_t *data) { + return lgw_usb_rb(com_target, spi_mux_target, address, data, 1); +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +/* Single Byte Read-Modify-Write */ +int lgw_usb_rmw(void *com_target, uint16_t address, uint8_t offs, uint8_t leng, uint8_t data) { + int usb_device; + uint8_t command_size = 6; + uint8_t in_out_buf[command_size]; + int a = 0; + + /* check input variables */ + CHECK_NULL(com_target); + + usb_device = *(int *)com_target; + + DEBUG_PRINTF("==> RMW register @ 0x%04X, offs:%u leng:%u value:0x%02X\n", address, offs, leng, data); + + /* prepare frame to be sent */ + in_out_buf[0] = _lgw_spi_req_nb; /* Req ID */ + in_out_buf[1] = MCU_SPI_REQ_TYPE_READ_MODIFY_WRITE; /* Req type */ + in_out_buf[2] = (uint8_t)(address >> 8); /* Register address MSB */ + in_out_buf[3] = (uint8_t)(address >> 0); /* Register address LSB */ + in_out_buf[4] = ((1 << leng) - 1) << offs; /* Register bitmask */ + in_out_buf[5] = data << offs; + + if (_lgw_write_mode == LGW_COM_WRITE_MODE_BULK) { + a = mcu_spi_store(in_out_buf, command_size); + _lgw_spi_req_nb += 1; + } else { + a = mcu_spi_write(usb_device, in_out_buf, command_size); + } + + /* determine return code */ + if (a != 0) { + DEBUG_MSG("ERROR: USB WRITE FAILURE\n"); + return -1; + } else { + DEBUG_MSG("Note: USB write success\n"); + return 0; + } +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +/* Burst (multiple-byte) write */ +int lgw_usb_wb(void *com_target, uint8_t spi_mux_target, uint16_t address, const uint8_t *data, uint16_t size) { + int usb_device; + uint16_t command_size = size + 8; /* 5 bytes: REQ metadata (MCU), 3 bytes: SPI header (SX1302) */ + uint8_t in_out_buf[command_size]; + int i; + int a = 0; + + /* check input parameters */ + CHECK_NULL(com_target); + CHECK_NULL(data); + + usb_device = *(int *)com_target; + + /* prepare command */ + /* Request metadata */ + in_out_buf[0] = _lgw_spi_req_nb; /* Req ID */ + in_out_buf[1] = MCU_SPI_REQ_TYPE_READ_WRITE; /* Req type */ + in_out_buf[2] = MCU_SPI_TARGET_SX1302; /* MCU -> SX1302 */ + in_out_buf[3] = (uint8_t)((size + 3) >> 8); /* payload size + spi_mux_target + address */ + in_out_buf[4] = (uint8_t)((size + 3) >> 0); /* payload size + spi_mux_target + address */ + /* RAW SPI frame */ + in_out_buf[5] = spi_mux_target; /* SX1302 -> RADIO_A or RADIO_B */ + in_out_buf[6] = 0x80 | ((address >> 8) & 0x7F); + in_out_buf[7] = ((address >> 0) & 0xFF); + for (i = 0; i < size; i++) { + in_out_buf[i + 8] = data[i]; + } + + if (_lgw_write_mode == LGW_COM_WRITE_MODE_BULK) { + a = mcu_spi_store(in_out_buf, command_size); + _lgw_spi_req_nb += 1; + } else { + a = mcu_spi_write(usb_device, in_out_buf, command_size); + } + + /* determine return code */ + if (a != 0) { + DEBUG_MSG("ERROR: USB WRITE BURST FAILURE\n"); + return -1; + } else { + DEBUG_MSG("Note: USB write burst success\n"); + return 0; + } +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +/* Burst (multiple-byte) read */ +int lgw_usb_rb(void *com_target, uint8_t spi_mux_target, uint16_t address, uint8_t *data, uint16_t size) { + int usb_device; + uint16_t command_size = size + 9; /* 5 bytes: REQ metadata (MCU), 3 bytes: SPI header (SX1302), 1 byte: dummy*/ + uint8_t in_out_buf[command_size]; + int i; + int a = 0; + + /* check input parameters */ + CHECK_NULL(com_target); + CHECK_NULL(data); + + usb_device = *(int *)com_target; + + /* prepare command */ + /* Request metadata */ + in_out_buf[0] = 0; /* Req ID */ + in_out_buf[1] = MCU_SPI_REQ_TYPE_READ_WRITE; /* Req type */ + in_out_buf[2] = MCU_SPI_TARGET_SX1302; /* MCU -> SX1302 */ + in_out_buf[3] = (uint8_t)((size + 4) >> 8); /* payload size + spi_mux_target + address + dummy byte */ + in_out_buf[4] = (uint8_t)((size + 4) >> 0); /* payload size + spi_mux_target + address + dummy byte */ + /* RAW SPI frame */ + in_out_buf[5] = spi_mux_target; /* SX1302 -> RADIO_A or RADIO_B */ + in_out_buf[6] = 0x00 | ((address >> 8) & 0x7F); + in_out_buf[7] = ((address >> 0) & 0xFF); + in_out_buf[8] = 0x00; /* dummy byte */ + for (i = 0; i < size; i++) { + in_out_buf[i + 9] = data[i]; + } + + if (_lgw_write_mode == LGW_COM_WRITE_MODE_BULK) { + /* makes no sense to read in bulk mode, as we can't get the result */ + printf("ERROR: USB READ BURST FAILURE - bulk mode is enabled\n"); + return -1; + } else { + a = mcu_spi_write(usb_device, in_out_buf, command_size); + } + + /* determine return code */ + if (a != 0) { + DEBUG_MSG("ERROR: USB READ BURST FAILURE\n"); + return -1; + } else { + DEBUG_MSG("Note: USB read burst success\n"); + memcpy(data, in_out_buf + 9, size); /* remove the first bytes, keep only the payload */ + return 0; + } +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int lgw_usb_set_write_mode(lgw_com_write_mode_t write_mode) { + if (write_mode >= LGW_COM_WRITE_MODE_UNKNOWN) { + printf("ERROR: wrong write mode\n"); + return -1; + } + + DEBUG_PRINTF("INFO: setting USB write mode to %s\n", (write_mode == LGW_COM_WRITE_MODE_SINGLE) ? "SINGLE" : "BULK"); + + _lgw_write_mode = write_mode; + + return 0; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int lgw_usb_flush(void *com_target) { + int usb_device; + int a = 0; + + /* Check input parameters */ + CHECK_NULL(com_target); + if (_lgw_write_mode != LGW_COM_WRITE_MODE_BULK) { + printf("ERROR: %s: cannot flush in single write mode\n", __FUNCTION__); + return -1; + } + + /* Restore single mode after flushing */ + _lgw_write_mode = LGW_COM_WRITE_MODE_SINGLE; + + if (_lgw_spi_req_nb == 0) { + printf("INFO: no SPI request to flush\n"); + return 0; + } + + usb_device = *(int *)com_target; + + DEBUG_MSG("INFO: flushing USB write buffer\n"); + a = mcu_spi_flush(usb_device); + if (a != 0) { + printf("ERROR: Failed to flush USB write buffer\n"); + } + + /* reset the pending request number */ + _lgw_spi_req_nb = 0; + + return a; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +uint16_t lgw_usb_chunk_size(void) { + return (uint16_t)LGW_USB_BURST_CHUNK; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int lgw_usb_get_temperature(void *com_target, float * temperature) { + int usb_device; + s_status mcu_status; + + /* check input parameters */ + CHECK_NULL(com_target); + CHECK_NULL(temperature); + + usb_device = *(int *)com_target; + + if (mcu_get_status(usb_device, &mcu_status) != 0) { + printf("ERROR: failed to get status from the concentrator MCU\n"); + return -1; + } + DEBUG_PRINTF("INFO: temperature:%.1foC\n", mcu_status.temperature); + + *temperature = mcu_status.temperature; + + return 0; +} + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/libloragw/src/sx1250_com.c b/libloragw/src/sx1250_com.c new file mode 100644 index 00000000..082982af --- /dev/null +++ b/libloragw/src/sx1250_com.c @@ -0,0 +1,94 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2019 Semtech + +Description: + Functions used to handle LoRa concentrator SX1250 radios. + +License: Revised BSD License, see LICENSE.TXT file include in the project +*/ + + +/* -------------------------------------------------------------------------- */ +/* --- DEPENDANCIES --------------------------------------------------------- */ + +#include /* C99 types */ +#include /* printf fprintf */ + +#include "sx1250_com.h" +#include "sx1250_spi.h" +#include "sx1250_usb.h" + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE MACROS ------------------------------------------------------- */ + +#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) +#if DEBUG_RAD == 1 + #define DEBUG_MSG(str) fprintf(stdout, str) + #define DEBUG_PRINTF(fmt, args...) fprintf(stdout,"%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 -1;} +#else + #define DEBUG_MSG(str) + #define DEBUG_PRINTF(fmt, args...) + #define CHECK_NULL(a) if(a==NULL){return -1;} +#endif + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE CONSTANTS ---------------------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC FUNCTIONS DEFINITION ------------------------------------------ */ + +int sx1250_com_w(lgw_com_type_t com_type, void *com_target, uint8_t spi_mux_target, sx1250_op_code_t op_code, uint8_t *data, uint16_t size) { + int com_stat; + + /* Check input parameters */ + CHECK_NULL(com_target); + CHECK_NULL(data); + + switch (com_type) { + case LGW_COM_SPI: + com_stat = sx1250_spi_w(com_target, spi_mux_target, op_code, data, size); + break; + case LGW_COM_USB: + com_stat = sx1250_usb_w(com_target, spi_mux_target, op_code, data, size); + break; + default: + printf("ERROR: wrong communication type (SHOULD NOT HAPPEN)\n"); + com_stat = LGW_COM_ERROR; + break; + } + + return com_stat; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int sx1250_com_r(lgw_com_type_t com_type, void *com_target, uint8_t spi_mux_target, sx1250_op_code_t op_code, uint8_t *data, uint16_t size) { + int com_stat; + + /* Check input parameters */ + CHECK_NULL(com_target); + CHECK_NULL(data); + + switch (com_type) { + case LGW_COM_SPI: + com_stat = sx1250_spi_r(com_target, spi_mux_target, op_code, data, size); + break; + case LGW_COM_USB: + com_stat = sx1250_usb_r(com_target, spi_mux_target, op_code, data, size); + break; + default: + printf("ERROR: wrong communication type (SHOULD NOT HAPPEN)\n"); + com_stat = LGW_COM_ERROR; + break; + } + + return com_stat; +} + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/libloragw/src/sx1250_spi.c b/libloragw/src/sx1250_spi.c new file mode 100644 index 00000000..93237042 --- /dev/null +++ b/libloragw/src/sx1250_spi.c @@ -0,0 +1,146 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2019 Semtech + +Description: + Functions used to handle LoRa concentrator SX1250 radios. + +License: Revised BSD License, see LICENSE.TXT file include in the project +*/ + + +/* -------------------------------------------------------------------------- */ +/* --- DEPENDANCIES --------------------------------------------------------- */ + +#include /* C99 types */ +#include /* printf fprintf */ +#include /* lseek, close */ +#include /* open */ +#include /* memset */ + +#include +#include + +#include "loragw_spi.h" +#include "loragw_aux.h" +#include "sx1250_spi.h" + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE MACROS ------------------------------------------------------- */ + +#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) +#if DEBUG_RAD == 1 + #define DEBUG_MSG(str) fprintf(stdout, str) + #define DEBUG_PRINTF(fmt, args...) fprintf(stdout,"%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 WAIT_BUSY_SX1250_MS 1 + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC FUNCTIONS DEFINITION ------------------------------------------ */ + +int sx1250_spi_w(void *com_target, uint8_t spi_mux_target, sx1250_op_code_t op_code, uint8_t *data, uint16_t size) { + int com_device; + int cmd_size = 2; /* header + op_code */ + uint8_t out_buf[cmd_size + size]; + uint8_t command_size; + struct spi_ioc_transfer k; + int a, i; + + /* wait BUSY */ + wait_ms(WAIT_BUSY_SX1250_MS); + + /* check input variables */ + CHECK_NULL(com_target); + CHECK_NULL(data); + + com_device = *(int *)com_target; + + /* prepare frame to be sent */ + out_buf[0] = spi_mux_target; + out_buf[1] = (uint8_t)op_code; + for(i = 0; i < (int)size; i++) { + out_buf[cmd_size + i] = data[i]; + } + command_size = cmd_size + size; + + /* I/O transaction */ + memset(&k, 0, sizeof(k)); /* clear k */ + k.tx_buf = (unsigned long) out_buf; + k.len = command_size; + k.speed_hz = SPI_SPEED; + k.cs_change = 0; + k.bits_per_word = 8; + a = ioctl(com_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; + } +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int sx1250_spi_r(void *com_target, uint8_t spi_mux_target, sx1250_op_code_t op_code, uint8_t *data, uint16_t size) { + int com_device; + int cmd_size = 2; /* header + op_code + NOP */ + uint8_t out_buf[cmd_size + size]; + uint8_t command_size; + uint8_t in_buf[ARRAY_SIZE(out_buf)]; + struct spi_ioc_transfer k; + int a, i; + + /* wait BUSY */ + wait_ms(WAIT_BUSY_SX1250_MS); + + /* check input variables */ + CHECK_NULL(com_target); + CHECK_NULL(data); + + com_device = *(int *)com_target; + + /* prepare frame to be sent */ + out_buf[0] = spi_mux_target; + out_buf[1] = (uint8_t)op_code; + for(i = 0; i < (int)size; i++) { + out_buf[cmd_size + i] = data[i]; + } + command_size = cmd_size + size; + + /* 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 = command_size; + k.cs_change = 0; + a = ioctl(com_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[command_size - 1]; + memcpy(data, in_buf + cmd_size, size); + return LGW_SPI_SUCCESS; + } +} + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/libloragw/src/sx1250_usb.c b/libloragw/src/sx1250_usb.c new file mode 100644 index 00000000..dd442900 --- /dev/null +++ b/libloragw/src/sx1250_usb.c @@ -0,0 +1,134 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2019 Semtech + +Description: + Functions used to handle LoRa concentrator SX1250 radios. + +License: Revised BSD License, see LICENSE.TXT file include in the project +*/ + + +/* -------------------------------------------------------------------------- */ +/* --- DEPENDANCIES --------------------------------------------------------- */ + +#include /* C99 types */ +#include /* printf fprintf */ +#include /* memcmp */ + +#include "loragw_aux.h" +#include "loragw_mcu.h" +#include "sx1250_usb.h" + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE MACROS ------------------------------------------------------- */ + +#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) +#if DEBUG_RAD == 1 + #define DEBUG_MSG(str) fprintf(stdout, str) + #define DEBUG_PRINTF(fmt, args...) fprintf(stdout,"%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 -1;} +#else + #define DEBUG_MSG(str) + #define DEBUG_PRINTF(fmt, args...) + #define CHECK_NULL(a) if(a==NULL){return -1;} +#endif + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE CONSTANTS ---------------------------------------------------- */ + +#define WAIT_BUSY_SX1250_MS 1 + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC FUNCTIONS DEFINITION ------------------------------------------ */ + +int sx1250_usb_w(void *com_target, uint8_t spi_mux_target, sx1250_op_code_t op_code, uint8_t *data, uint16_t size) { + int usb_device; + uint8_t command_size = size + 7; /* 5 bytes: REQ metadata, 2 bytes: RAW SPI frame */ + uint8_t in_out_buf[command_size]; + int a; + int i; + + /* check input variables */ + CHECK_NULL(com_target); + CHECK_NULL(data); + + usb_device = *(int *)com_target; + + /* wait BUSY */ + wait_ms(WAIT_BUSY_SX1250_MS); + + /* prepare command */ + /* Request metadata */ + in_out_buf[0] = 0; /* Req ID */ + in_out_buf[1] = MCU_SPI_REQ_TYPE_READ_WRITE; /* Req type */ + in_out_buf[2] = MCU_SPI_TARGET_SX1302; /* MCU -> SX1302 */ + in_out_buf[3] = (uint8_t)((size + 2) >> 8); /* payload size + spi_mux_target + op_code */ + in_out_buf[4] = (uint8_t)((size + 2) >> 0); /* payload size + spi_mux_target + op_code */ + /* RAW SPI frame */ + in_out_buf[5] = spi_mux_target; /* SX1302 -> RADIO_A or RADIO_B */ + in_out_buf[6] = (uint8_t)op_code; + for (i = 0; i < size; i++) { + in_out_buf[i + 7] = data[i]; + } + a = mcu_spi_write(usb_device, in_out_buf, command_size); + + /* determine return code */ + if (a != 0) { + DEBUG_MSG("ERROR: USB SX1250 WRITE FAILURE\n"); + return -1; + } else { + DEBUG_MSG("Note: USB SX1250 write success\n"); + return 0; + } +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int sx1250_usb_r(void *com_target, uint8_t spi_mux_target, sx1250_op_code_t op_code, uint8_t *data, uint16_t size) { + int usb_device; + uint8_t command_size = size + 7; /* 5 bytes: REQ metadata, 2 bytes: RAW SPI frame */ + uint8_t in_out_buf[command_size]; + int a; + int i; + + /* check input variables */ + CHECK_NULL(com_target); + CHECK_NULL(data); + + usb_device = *(int *)com_target; + + /* wait BUSY */ + wait_ms(WAIT_BUSY_SX1250_MS); + + /* prepare command */ + /* Request metadata */ + in_out_buf[0] = 0; /* Req ID */ + in_out_buf[1] = MCU_SPI_REQ_TYPE_READ_WRITE; /* Req type */ + in_out_buf[2] = MCU_SPI_TARGET_SX1302; /* MCU -> SX1302 */ + in_out_buf[3] = (uint8_t)((size + 2) >> 8); /* payload size + spi_mux_target + op_code */ + in_out_buf[4] = (uint8_t)((size + 2) >> 0); /* payload size + spi_mux_target + op_code */ + /* RAW SPI frame */ + in_out_buf[5] = spi_mux_target; /* SX1302 -> RADIO_A or RADIO_B */ + in_out_buf[6] = (uint8_t)op_code; + for (i = 0; i < size; i++) { + in_out_buf[i + 7] = data[i]; + } + a = mcu_spi_write(usb_device, in_out_buf, command_size); + + /* determine return code */ + if (a != 0) { + DEBUG_MSG("ERROR: USB SX1250 READ FAILURE\n"); + return -1; + } else { + DEBUG_MSG("Note: USB SX1250 read success\n"); + memcpy(data, in_out_buf + 7, size); /* remove the first bytes, keep only the payload */ + return 0; + } +} + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/libloragw/src/sx125x_com.c b/libloragw/src/sx125x_com.c new file mode 100644 index 00000000..4a20f811 --- /dev/null +++ b/libloragw/src/sx125x_com.c @@ -0,0 +1,98 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2019 Semtech + +Description: + Functions used to handle LoRa concentrator SX1255/SX1257 radios. + +License: Revised BSD License, see LICENSE.TXT file include in the project +*/ + +/* -------------------------------------------------------------------------- */ +/* --- DEPENDANCIES --------------------------------------------------------- */ + +#include /* C99 types */ +#include /* printf fprintf */ + +#include "sx125x_com.h" +#include "sx125x_spi.h" + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE MACROS ------------------------------------------------------- */ + +#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) +#if DEBUG_RAD == 1 + #define DEBUG_MSG(str) fprintf(stdout, str) + #define DEBUG_PRINTF(fmt, args...) fprintf(stdout,"%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 -1;} +#else + #define DEBUG_MSG(str) + #define DEBUG_PRINTF(fmt, args...) + #define CHECK_NULL(a) if(a==NULL){return -1;} +#endif + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE TYPES -------------------------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE CONSTANTS ---------------------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE VARIABLES ---------------------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE FUNCTIONS ---------------------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC FUNCTIONS DEFINITION ------------------------------------------ */ + +int sx125x_com_r(lgw_com_type_t com_type, void *com_target, uint8_t spi_mux_target, uint8_t address, uint8_t *data) { + int com_stat; + + /* Check input parameters */ + CHECK_NULL(com_target); + CHECK_NULL(data); + + switch (com_type) { + case LGW_COM_SPI: + com_stat = sx125x_spi_r(com_target, spi_mux_target, address, data); + break; + case LGW_COM_USB: + printf("ERROR: USB COM type is not supported for sx125x\n"); + return -1; + default: + printf("ERROR: wrong communication type (SHOULD NOT HAPPEN)\n"); + return -1; + } + + return com_stat; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int sx125x_com_w(lgw_com_type_t com_type, void *com_target, uint8_t spi_mux_target, uint8_t address, uint8_t data) { + int com_stat; + + /* Check input parameters */ + CHECK_NULL(com_target); + + switch (com_type) { + case LGW_COM_SPI: + com_stat = sx125x_spi_w(com_target, spi_mux_target, address, data); + break; + case LGW_COM_USB: + printf("ERROR: USB COM type is not supported for sx125x\n"); + return -1; + default: + printf("ERROR: wrong communication type (SHOULD NOT HAPPEN)\n"); + return -1; + } + + return com_stat; +} + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/libloragw/src/sx125x_spi.c b/libloragw/src/sx125x_spi.c new file mode 100644 index 00000000..dfa361ce --- /dev/null +++ b/libloragw/src/sx125x_spi.c @@ -0,0 +1,140 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2019 Semtech + +Description: + Functions used to handle LoRa concentrator SX1255/SX1257 radios. + +License: Revised BSD License, see LICENSE.TXT file include in the project +*/ + +/* -------------------------------------------------------------------------- */ +/* --- DEPENDANCIES --------------------------------------------------------- */ + +#include /* C99 types */ +#include /* bool type */ +#include /* printf fprintf */ +#include /* memset */ + +#include +#include + +#include "sx125x_spi.h" +#include "loragw_spi.h" + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE MACROS ------------------------------------------------------- */ + +#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) +#if DEBUG_RAD == 1 + #define DEBUG_MSG(str) fprintf(stdout, str) + #define DEBUG_PRINTF(fmt, args...) fprintf(stdout,"%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 TYPES -------------------------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE CONSTANTS ---------------------------------------------------- */ + +#define READ_ACCESS 0x00 +#define WRITE_ACCESS 0x80 + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE VARIABLES ---------------------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE FUNCTIONS ---------------------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC FUNCTIONS DEFINITION ------------------------------------------ */ + +/* Simple read */ +int sx125x_spi_r(void *com_target, uint8_t spi_mux_target, uint8_t address, uint8_t *data) { + int com_device; + uint8_t out_buf[3]; + uint8_t command_size; + uint8_t in_buf[ARRAY_SIZE(out_buf)]; + struct spi_ioc_transfer k; + int a; + + /* check input variables */ + CHECK_NULL(com_target); + CHECK_NULL(data); + + com_device = *(int *)com_target; + + /* prepare frame to be sent */ + out_buf[0] = spi_mux_target; + out_buf[1] = READ_ACCESS | (address & 0x7F); + out_buf[2] = 0x00; + command_size = 3; + + /* 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 = command_size; + k.cs_change = 0; + a = ioctl(com_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[command_size - 1]; + return LGW_SPI_SUCCESS; + } +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int sx125x_spi_w(void *spi_target, uint8_t spi_mux_target, uint8_t address, uint8_t data) { + int spi_device; + uint8_t out_buf[3]; + uint8_t command_size; + struct spi_ioc_transfer k; + int a; + + /* check input variables */ + CHECK_NULL(spi_target); + + spi_device = *(int *)spi_target; /* must check that spi_target is not null beforehand */ + + /* prepare frame to be sent */ + out_buf[0] = spi_mux_target; + out_buf[1] = WRITE_ACCESS | (address & 0x7F); + out_buf[2] = data; + command_size = 3; + + /* I/O transaction */ + memset(&k, 0, sizeof(k)); /* clear k */ + k.tx_buf = (unsigned long) out_buf; + k.len = command_size; + k.speed_hz = SPI_SPEED; + k.cs_change = 0; + 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; + } +} + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/libloragw/src/sx1261_com.c b/libloragw/src/sx1261_com.c new file mode 100644 index 00000000..f9de7620 --- /dev/null +++ b/libloragw/src/sx1261_com.c @@ -0,0 +1,211 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2019 Semtech + +Description: + Functions used to handle the sx1261 radio used for LBT/Spectral Scan. + +License: Revised BSD License, see LICENSE.TXT file include in the project +*/ + + +/* -------------------------------------------------------------------------- */ +/* --- DEPENDANCIES --------------------------------------------------------- */ + +#include /* C99 types */ +#include /* printf fprintf */ + +#include "loragw_com.h" +#include "loragw_spi.h" +#include "sx1261_com.h" +#include "sx1261_spi.h" +#include "sx1261_usb.h" + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE MACROS ------------------------------------------------------- */ + +#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) +#if DEBUG_LBT == 1 + #define DEBUG_MSG(str) fprintf(stdout, str) + #define DEBUG_PRINTF(fmt, args...) fprintf(stdout,"%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 -1;} +#else + #define DEBUG_MSG(str) + #define DEBUG_PRINTF(fmt, args...) + #define CHECK_NULL(a) if(a==NULL){return -1;} +#endif + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE CONSTANTS ---------------------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE VARIABLES ---------------------------------------------------- */ + +/** +@brief The current communication type in use (SPI, USB) +*/ +static lgw_com_type_t _sx1261_com_type = LGW_COM_UNKNOWN; + +/** +@brief A generic pointer to the COM device (file descriptor) +*/ +static void* _sx1261_com_target = NULL; + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC FUNCTIONS DEFINITION ------------------------------------------ */ + +/** + * +*/ +int sx1261_com_open(lgw_com_type_t com_type, const char *com_path) { + int spi_stat = LGW_COM_SUCCESS; + + _sx1261_com_type = com_type; + + switch(com_type) { + case LGW_COM_SPI: + /* open the SPI link */ + spi_stat = lgw_spi_open(com_path, &_sx1261_com_target); + if (spi_stat != LGW_SPI_SUCCESS) { + printf("ERROR: %s: Failed to connect to sx1261 radio on %s\n", __FUNCTION__, com_path); + return LGW_COM_ERROR; + } + DEBUG_PRINTF("SX1261: connected with SPI %s\n", com_path); + break; + case LGW_COM_USB: + /* the USB link has already been opened (lgw_connect) */ + _sx1261_com_target = lgw_com_target(); + DEBUG_MSG("SX1261: connected with USB\n"); + break; + default: + printf("ERROR: %s: wrong COM type\n", __FUNCTION__); + return LGW_COM_ERROR; + } + + return LGW_COM_SUCCESS; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int sx1261_com_close(void) { + int spi_stat = LGW_COM_SUCCESS; + + switch(_sx1261_com_type) { + case LGW_COM_SPI: + /* Close the SPI link */ + spi_stat = lgw_spi_close(_sx1261_com_target); + if (spi_stat != LGW_SPI_SUCCESS) { + printf("ERROR: %s: Failed to disconnect SX1261 radio\n", __FUNCTION__); + return LGW_COM_ERROR; + } + break; + case LGW_COM_USB: + break; + default: + printf("ERROR: %s: sx1261 not connected\n", __FUNCTION__); + return LGW_COM_ERROR; + } + + _sx1261_com_type = LGW_COM_UNKNOWN; + _sx1261_com_target = NULL; + + return LGW_COM_SUCCESS; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int sx1261_com_w(sx1261_op_code_t op_code, uint8_t *data, uint16_t size) { + int com_stat; + + /* Check input parameters */ + CHECK_NULL(_sx1261_com_target); + CHECK_NULL(data); + + switch (_sx1261_com_type) { + case LGW_COM_SPI: + com_stat = sx1261_spi_w(_sx1261_com_target, op_code, data, size); + break; + case LGW_COM_USB: + com_stat = sx1261_usb_w(_sx1261_com_target, op_code, data, size); + break; + default: + printf("ERROR: wrong communication type (SHOULD NOT HAPPEN)\n"); + com_stat = LGW_COM_ERROR; + break; + } + + return com_stat; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int sx1261_com_r(sx1261_op_code_t op_code, uint8_t *data, uint16_t size) { + int com_stat; + + /* Check input parameters */ + CHECK_NULL(_sx1261_com_target); + CHECK_NULL(data); + + switch (_sx1261_com_type) { + case LGW_COM_SPI: + com_stat = sx1261_spi_r(_sx1261_com_target, op_code, data, size); + break; + case LGW_COM_USB: + com_stat = sx1261_usb_r(_sx1261_com_target, op_code, data, size); + break; + default: + printf("ERROR: wrong communication type (SHOULD NOT HAPPEN)\n"); + com_stat = LGW_COM_ERROR; + break; + } + + return com_stat; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int sx1261_com_set_write_mode(lgw_com_write_mode_t write_mode) { + int com_stat = LGW_COM_SUCCESS; + + switch (_sx1261_com_type) { + case LGW_COM_SPI: + /* Do nothing: only single mode is supported on SPI */ + break; + case LGW_COM_USB: + com_stat = sx1261_usb_set_write_mode(write_mode); + break; + default: + printf("ERROR(%s:%d): wrong communication type (SHOULD NOT HAPPEN)\n", __FUNCTION__, __LINE__); + com_stat = LGW_COM_ERROR; + break; + } + + return com_stat; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int sx1261_com_flush(void) { + int com_stat = LGW_COM_SUCCESS; + + switch (_sx1261_com_type) { + case LGW_COM_SPI: + /* Do nothing: only single mode is supported on SPI */ + break; + case LGW_COM_USB: + com_stat = sx1261_usb_flush(_sx1261_com_target); + break; + default: + printf("ERROR(%s:%d): wrong communication type (SHOULD NOT HAPPEN)\n", __FUNCTION__, __LINE__); + com_stat = LGW_COM_ERROR; + break; + } + + return com_stat; +} + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/libloragw/src/sx1261_pram.var b/libloragw/src/sx1261_pram.var new file mode 100644 index 00000000..9958c324 --- /dev/null +++ b/libloragw/src/sx1261_pram.var @@ -0,0 +1,390 @@ +const uint32_t pram[] = { +0x337fe1, +0x337fdb, +0x337fd5, +0x337fcf, +0x3a7fc8, +0x3f3fff, +0x378ff, +0x379ff, +0x3a7fb7, +0x16a901, +0x16a801, +0x23ffff, +0x378ff, +0x379ff, +0x3a7faf, +0x16a901, +0x16a801, +0x23ffff, +0x378ff, +0x379ff, +0x3a7f34, +0x16a901, +0x16a801, +0x23ffff, +0x378ff, +0x379ff, +0x3a7e80, +0x16a901, +0x16a801, +0x23ffff, +0x378ff, +0x379ff, +0x3a7fc3, +0x16a901, +0x16a801, +0x337fc9, +0x378ff, +0x379ff, +0x3a7fc0, +0x16a901, +0x16a801, +0x337fc9, +0x378ff, +0x379ff, +0x3a7fbd, +0x16a901, +0x16a801, +0x337fc9, +0x378ff, +0x379ff, +0x3a7fba, +0x16a901, +0x16a801, +0x337fc9, +0x23ffff, +0xea1fc, +0xea0df, +0xeafc9, +0x2cf0e, +0x23ffff, +0xeacff, +0xeabff, +0x23ffff, +0xeacff, +0xeabff, +0x23ffff, +0xeacff, +0xeabff, +0x23ffff, +0xeacff, +0xeabff, +0x23ffff, +0x378ff, +0x379ff, +0x3a7fc8, +0xeacfd, +0xeabff, +0x16a901, +0x16a801, +0x23ffff, +0x374ff, +0x375ff, +0x378ff, +0x379ff, +0x16affe, +0xea5ff, +0xea465, +0x1dbb04, +0xe1bfd, +0x307fa3, +0x1caf00, +0x327fa0, +0xeacf7, +0xeabff, +0x337f3a, +0x1cad04, +0xeacfe, +0xeabff, +0xdbfdd, +0x307f97, +0xdafcc, +0xdefbb, +0xdbfdd, +0x347f9b, +0xeecef, +0xcaffc, +0x4ade8, +0xcbdcd, +0x1bde8, +0x4abe8, +0xebbbf, +0x1bbe8, +0x4abe8, +0xebbdf, +0x1bbe8, +0x1ca800, +0xea9ff, +0x4abe3, +0xe2b01, +0x1bbe3, +0x4abee, +0xe2b01, +0x1bbee, +0x1ca202, +0x1ca301, +0x2f300, +0x2f201, +0xea064, +0xea1f7, +0x18ab00, +0x367f58, +0xea041, +0xea1f7, +0x18ab00, +0x1c1b03, +0x317f3f, +0x1ea201, +0x1ea300, +0xcb23f, +0x327f4a, +0x1cab04, +0xeaefe, +0xdbfbb, +0x307f6c, +0xdafee, +0xdbfbb, +0x347f6f, +0x4abee, +0xcbbeb, +0x1bbee, +0xeacff, +0xeabff, +0xc1b9f, +0x327f64, +0xc1c8f, +0x317f5c, +0x3fffff, +0xd1fcc, +0xd5fbb, +0xc1b9f, +0x327f5d, +0xc1c8f, +0x357f63, +0xea064, +0xea1f7, +0x18ab00, +0x327f7c, +0x1cad04, +0xeacfe, +0xdbfdd, +0x307f51, +0xdafcc, +0xdbfdd, +0x347f54, +0xd8fcb, +0x4acee, +0xc2bcb, +0x1bbee, +0xeacfd, +0xeabff, +0x337f3a, +0x1cad04, +0xeacfe, +0xdbfdd, +0x307f43, +0xdafcc, +0xdbfdd, +0x347f46, +0xd8fcb, +0x4acee, +0xc2bcb, +0x337f6a, +0xcb23f, +0x367f75, +0xdbf22, +0xdff33, +0x337f75, +0x16af02, +0x16a901, +0x16a801, +0x16a501, +0x16a401, +0x23ffff, +0x374ff, +0x375ff, +0x378ff, +0x379ff, +0x16afe0, +0xea5ff, +0xea465, +0x1ca802, +0xea9ff, +0xeafff, +0x2ff00, +0xeaff7, +0x2ff01, +0xeafef, +0x2ff02, +0xeafe7, +0x2ff03, +0xeafdf, +0x2ff04, +0xeafd7, +0x2ff05, +0xeafcf, +0x2ff06, +0xeafc7, +0x2ff07, +0xeafbf, +0x2ff08, +0xeafb7, +0x2ff09, +0xeafaf, +0x2ff0a, +0xeafa7, +0x2ff0b, +0xeaf9f, +0x2ff0c, +0xeaf97, +0x2ff0d, +0xeaf8f, +0x2ff0e, +0xeaf87, +0x2ff0f, +0xeaf7f, +0x2ff10, +0xeaf77, +0x2ff11, +0xeaf6f, +0x2ff12, +0xeaf67, +0x2ff13, +0xeaf5f, +0x2ff14, +0xeaf57, +0x2ff15, +0xeaf4f, +0x2ff16, +0xeaf47, +0x2ff17, +0xeaf3f, +0x2ff18, +0xeaf37, +0x2ff19, +0xeaf2f, +0x2ff1a, +0xeaf27, +0x2ff1b, +0xeaf1f, +0x2ff1c, +0xeaf17, +0x2ff1d, +0xeaf0f, +0x2ff1e, +0xeaf07, +0x2ff1f, +0x4abee, +0xe2b01, +0x1bbee, +0xeacff, +0xeabff, +0xcafc0, +0xec0ff, +0xcafb1, +0xed1fb, +0xeafff, +0x2cf00, +0xd1fcc, +0xd5fbb, +0xe1bff, +0x327edb, +0xe1c00, +0x347ee6, +0xea2ff, +0xea3ff, +0xea032, +0xea1f8, +0xeaff0, +0x2cf00, +0xea064, +0xea1f7, +0x18ad00, +0x1c1300, +0x327ece, +0x1c1201, +0x317e9b, +0xe1dff, +0x367e9b, +0xea041, +0xea1f7, +0x18ab00, +0xeebdf, +0xcafbc, +0xeabff, +0xccccc, +0xcdbbb, +0xcafc0, +0xec0ff, +0xcafb1, +0xed1fb, +0x18ab01, +0xeacff, +0x18ae02, +0xeadff, +0xccecc, +0xcddbb, +0xd1fcc, +0xd5fbb, +0xcafbe, +0xeadff, +0x2ce01, +0x2cc02, +0xd1f22, +0xd5f33, +0xea064, +0xea1f7, +0x18ad00, +0xeacff, +0xeabff, +0xc1b9f, +0x327ea9, +0xc1c8f, +0x317ea1, +0x3fffff, +0xd1fcc, +0xd5fbb, +0xc1b9f, +0x327ea2, +0xc1c8f, +0x357ea8, +0x1c1300, +0x327e9e, +0x1c1201, +0x317e9b, +0xe1dff, +0x327ecb, +0xe1dff, +0x367e90, +0xea032, +0xea1f8, +0xeaf00, +0x2cf00, +0xea1fb, +0xea0ff, +0xeaf00, +0x2cf00, +0x337e88, +0xea032, +0xea1f8, +0xeaf0f, +0x2cf00, +0xea1fb, +0xea0ff, +0xeaf0f, +0x2cf00, +0xeacfd, +0xeabff, +0x16af20, +0x16a901, +0x16a801, +0x16a501, +0x16a401, +0x23ffff, +0xeacf7, +0xeabff, +0x23ffff +}; + +#define PRAM_COUNT 386 \ No newline at end of file diff --git a/libloragw/src/sx1261_spi.c b/libloragw/src/sx1261_spi.c new file mode 100644 index 00000000..d1ddb570 --- /dev/null +++ b/libloragw/src/sx1261_spi.c @@ -0,0 +1,144 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2019 Semtech + +Description: + Functions used to handle LoRa concentrator SX1250 radios. + +License: Revised BSD License, see LICENSE.TXT file include in the project +*/ + + +/* -------------------------------------------------------------------------- */ +/* --- DEPENDANCIES --------------------------------------------------------- */ + +#include /* C99 types */ +#include /* printf fprintf */ +#include /* lseek, close */ +#include /* open */ +#include /* memset */ + +#include +#include + +#include "loragw_spi.h" +#include "loragw_aux.h" +#include "sx1261_spi.h" + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE MACROS ------------------------------------------------------- */ + +#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) +#if DEBUG_LBT == 1 + #define DEBUG_MSG(str) fprintf(stdout, str) + #define DEBUG_PRINTF(fmt, args...) fprintf(stdout,"%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 WAIT_BUSY_SX1250_MS 1 + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC FUNCTIONS DEFINITION ------------------------------------------ */ + +int sx1261_spi_w(void *com_target, sx1261_op_code_t op_code, uint8_t *data, uint16_t size) { + int com_device; + int cmd_size = 1; /* op_code */ + uint8_t out_buf[cmd_size + size]; + uint8_t command_size; + struct spi_ioc_transfer k; + int a, i; + + /* wait BUSY */ + wait_ms(WAIT_BUSY_SX1250_MS); + + /* check input variables */ + CHECK_NULL(com_target); + CHECK_NULL(data); + + com_device = *(int *)com_target; + + /* prepare frame to be sent */ + out_buf[0] = (uint8_t)op_code; + for(i = 0; i < (int)size; i++) { + out_buf[cmd_size + i] = data[i]; + } + command_size = cmd_size + size; + + /* I/O transaction */ + memset(&k, 0, sizeof(k)); /* clear k */ + k.tx_buf = (unsigned long) out_buf; + k.len = command_size; + k.speed_hz = SPI_SPEED; + k.cs_change = 0; + k.bits_per_word = 8; + a = ioctl(com_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; + } +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int sx1261_spi_r(void *com_target, sx1261_op_code_t op_code, uint8_t *data, uint16_t size) { + int com_device; + int cmd_size = 1; /* op_code */ + uint8_t out_buf[cmd_size + size]; + uint8_t command_size; + uint8_t in_buf[ARRAY_SIZE(out_buf)]; + struct spi_ioc_transfer k; + int a, i; + + /* wait BUSY */ + wait_ms(WAIT_BUSY_SX1250_MS); + + /* check input variables */ + CHECK_NULL(com_target); + CHECK_NULL(data); + + com_device = *(int *)com_target; + + /* prepare frame to be sent */ + out_buf[0] = (uint8_t)op_code; + for(i = 0; i < (int)size; i++) { + out_buf[cmd_size + i] = data[i]; + } + command_size = cmd_size + size; + + /* 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 = command_size; + k.cs_change = 0; + a = ioctl(com_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[command_size - 1]; + memcpy(data, in_buf + cmd_size, size); + return LGW_SPI_SUCCESS; + } +} + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/libloragw/src/sx1261_usb.c b/libloragw/src/sx1261_usb.c new file mode 100644 index 00000000..c82252a9 --- /dev/null +++ b/libloragw/src/sx1261_usb.c @@ -0,0 +1,192 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2019 Semtech + +Description: + Functions used to handle LoRa concentrator SX1250 radios. + +License: Revised BSD License, see LICENSE.TXT file include in the project +*/ + + +/* -------------------------------------------------------------------------- */ +/* --- DEPENDANCIES --------------------------------------------------------- */ + +#include /* C99 types */ +#include /* printf fprintf */ +#include + +#include "loragw_aux.h" +#include "loragw_mcu.h" +#include "sx1261_usb.h" + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE MACROS ------------------------------------------------------- */ + +#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) +#if DEBUG_LBT == 1 + #define DEBUG_MSG(str) fprintf(stdout, str) + #define DEBUG_PRINTF(fmt, args...) fprintf(stdout,"%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 -1;} +#else + #define DEBUG_MSG(str) + #define DEBUG_PRINTF(fmt, args...) + #define CHECK_NULL(a) if(a==NULL){return -1;} +#endif + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE CONSTANTS ---------------------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE VARIABLES ---------------------------------------------------- */ + +static lgw_com_write_mode_t _sx1261_write_mode = LGW_COM_WRITE_MODE_SINGLE; +static uint8_t _sx1261_spi_req_nb = 0; + +/* -------------------------------------------------------------------------- */ +/* --- PUBLIC FUNCTIONS DEFINITION ------------------------------------------ */ + +int sx1261_usb_w(void *com_target, sx1261_op_code_t op_code, uint8_t *data, uint16_t size) { + int usb_device; + uint8_t command_size = size + 6; /* 5 bytes: REQ metadata, 1 byte: op_code */ + uint8_t in_out_buf[command_size]; + int a; + int i; + + /* check input variables */ + CHECK_NULL(com_target); + CHECK_NULL(data); + + usb_device = *(int *)com_target; + + /* prepare command */ + /* Request metadata */ + in_out_buf[0] = _sx1261_spi_req_nb; /* Req ID */ + in_out_buf[1] = MCU_SPI_REQ_TYPE_READ_WRITE; /* Req type */ + in_out_buf[2] = MCU_SPI_TARGET_SX1261; /* MCU -> SX1302 */ + in_out_buf[3] = (uint8_t)((size + 1) >> 8); /* payload size + op_code */ + in_out_buf[4] = (uint8_t)((size + 1) >> 0); /* payload size + op_code */ + /* RAW SPI frame */ + in_out_buf[5] = (uint8_t)op_code; + for (i = 0; i < size; i++) { + in_out_buf[i + 6] = data[i]; + } + + if (_sx1261_write_mode == LGW_COM_WRITE_MODE_BULK) { + a = mcu_spi_store(in_out_buf, command_size); + _sx1261_spi_req_nb += 1; + } else { + a = mcu_spi_write(usb_device, in_out_buf, command_size); + } + + /* determine return code */ + if (a != 0) { + DEBUG_MSG("ERROR: USB SX1261 WRITE FAILURE\n"); + return -1; + } else { + DEBUG_MSG("Note: USB SX1261 write success\n"); + return 0; + } +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int sx1261_usb_r(void *com_target, sx1261_op_code_t op_code, uint8_t *data, uint16_t size) { + int usb_device; + uint8_t command_size = size + 6; /* 5 bytes: REQ metadata, 1 byte: op_code */ + uint8_t in_out_buf[command_size]; + int a; + int i; + + /* check input variables */ + CHECK_NULL(com_target); + CHECK_NULL(data); + + usb_device = *(int *)com_target; + + /* prepare command */ + /* Request metadata */ + in_out_buf[0] = _sx1261_spi_req_nb; /* Req ID */ + in_out_buf[1] = MCU_SPI_REQ_TYPE_READ_WRITE; /* Req type */ + in_out_buf[2] = MCU_SPI_TARGET_SX1261; /* MCU -> SX1302 */ + in_out_buf[3] = (uint8_t)((size + 1) >> 8); /* payload size + op_code */ + in_out_buf[4] = (uint8_t)((size + 1) >> 0); /* payload size + op_code */ + /* RAW SPI frame */ + in_out_buf[5] = (uint8_t)op_code; + for (i = 0; i < size; i++) { + in_out_buf[i + 6] = data[i]; + } + if (_sx1261_write_mode == LGW_COM_WRITE_MODE_BULK) { + /* makes no sense to read in bulk mode, as we can't get the result */ + printf("ERROR: USB READ BURST FAILURE - bulk mode is enabled\n"); + return -1; + } else { + a = mcu_spi_write(usb_device, in_out_buf, command_size); + } + + /* determine return code */ + if (a != 0) { + DEBUG_MSG("ERROR: USB SX1261 WRITE FAILURE\n"); + return -1; + } else { + DEBUG_MSG("Note: USB SX1261 write success\n"); + memcpy(data, in_out_buf + 6, size); /* remove the first bytes, keep only the payload */ + return 0; + } +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int sx1261_usb_set_write_mode(lgw_com_write_mode_t write_mode) { + if (write_mode >= LGW_COM_WRITE_MODE_UNKNOWN) { + printf("ERROR: %s: wrong write mode\n", __FUNCTION__); + return -1; + } + + DEBUG_PRINTF("INFO: setting SX1261 USB write mode to %s\n", (write_mode == LGW_COM_WRITE_MODE_SINGLE) ? "SINGLE" : "BULK"); + + _sx1261_write_mode = write_mode; + + return 0; +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +int sx1261_usb_flush(void *com_target) { + int usb_device; + int a = 0; + + /* Check input parameters */ + CHECK_NULL(com_target); + if (_sx1261_write_mode != LGW_COM_WRITE_MODE_BULK) { + printf("ERROR: %s: cannot flush in single write mode\n", __FUNCTION__); + return -1; + } + + /* Restore single mode after flushing */ + _sx1261_write_mode = LGW_COM_WRITE_MODE_SINGLE; + + if (_sx1261_spi_req_nb == 0) { + printf("INFO: no SX1261 SPI request to flush\n"); + return 0; + } + + usb_device = *(int *)com_target; + + DEBUG_MSG("INFO: flushing SX1261 USB write buffer\n"); + a = mcu_spi_flush(usb_device); + if (a != 0) { + printf("ERROR: Failed to flush sx1261 USB write buffer\n"); + } + + /* reset the pending request number */ + _sx1261_spi_req_nb = 0; + + return a; +} + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/libloragw/tst/test_loragw_cal.c b/libloragw/tst/test_loragw_cal_sx125x.c similarity index 79% rename from libloragw/tst/test_loragw_cal.c rename to libloragw/tst/test_loragw_cal_sx125x.c index d1e643d4..cf98f602 100644 --- a/libloragw/tst/test_loragw_cal.c +++ b/libloragw/tst/test_loragw_cal_sx125x.c @@ -7,7 +7,7 @@ (C)2019 Semtech Description: - Minimum test program for HAL calibration + Minimum test program for HAL calibration for sx1255/sx1257 radios License: Revised BSD License, see LICENSE.TXT file include in the project */ @@ -35,6 +35,7 @@ License: Revised BSD License, see LICENSE.TXT file include in the project #include "loragw_hal.h" #include "loragw_reg.h" +#include "loragw_com.h" #include "loragw_sx1302.h" #include "loragw_sx125x.h" #include "loragw_aux.h" @@ -45,13 +46,14 @@ License: Revised BSD License, see LICENSE.TXT file include in the project #define RAND_RANGE(min, max) (rand() % (max + 1 - min) + min) -#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(stdout, str) +#define DEBUG_PRINTF(fmt, args...) fprintf(stdout,"%s:%d: "fmt, __FUNCTION__, __LINE__, args) /* -------------------------------------------------------------------------- */ /* --- PRIVATE CONSTANTS ---------------------------------------------------- */ -#define LINUXDEV_PATH_DEFAULT "/dev/spidev0.0" +#define COM_TYPE_DEFAULT LGW_COM_SPI +#define COM_PATH_DEFAULT "/dev/spidev0.0" #define DEFAULT_CLK_SRC 0 #define DEFAULT_FREQ_HZ 868500000U @@ -98,13 +100,12 @@ void usage(void) { //printf("Library version information: %s\n", lgw_version_info()); printf("Available options:\n"); printf(" -h print this help\n"); - printf(" -d use Linux SPI device driver\n"); - printf(" => default path: " LINUXDEV_PATH_DEFAULT "\n"); + printf(" -u Set COM type as USB (default is SPI)\n"); + printf(" -d [path] Path to the COM interface\n"); + printf(" => default path: " COM_PATH_DEFAULT "\n"); printf(" -k Concentrator clock source (Radio A or Radio B) [0..1]\n"); printf(" -c RF chain to be used for TX (Radio A or Radio B) [0..1]\n"); - printf(" -r Radio type (1255, 1257, 1250)\n"); - printf(" -j Set radio in single input mode (SX1250 only)\n"); - printf(" -f Radio TX frequency in MHz\n"); + printf(" -r Radio type (1255, 1257)\n"); printf( "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" ); printf(" --pa PA gain [0..3]\n"); printf(" --dig sx1302 digital gain [0..3]\n"); @@ -149,32 +150,32 @@ int setup_tx_dc_offset(uint8_t rf_chain, uint32_t freq_hz, uint8_t dac_gain, uin DEBUG_PRINTF("ERROR: UNEXPECTED VALUE %d FOR RADIO TYPE\n", radio_type); return LGW_HAL_ERROR; } - lgw_sx125x_reg_w(SX125x_REG_FRF_RX_MSB, 0xFF & rx_freq_int, rf_chain); - lgw_sx125x_reg_w(SX125x_REG_FRF_RX_MID, 0xFF & (rx_freq_frac >> 8), rf_chain); - lgw_sx125x_reg_w(SX125x_REG_FRF_RX_LSB, 0xFF & rx_freq_frac, rf_chain); - lgw_sx125x_reg_w(SX125x_REG_FRF_TX_MSB, 0xFF & tx_freq_int, rf_chain); - lgw_sx125x_reg_w(SX125x_REG_FRF_TX_MID, 0xFF & (tx_freq_frac >> 8), rf_chain); - lgw_sx125x_reg_w(SX125x_REG_FRF_TX_LSB, 0xFF & tx_freq_frac, rf_chain); + sx125x_reg_w(SX125x_REG_FRF_RX_MSB, 0xFF & rx_freq_int, rf_chain); + sx125x_reg_w(SX125x_REG_FRF_RX_MID, 0xFF & (rx_freq_frac >> 8), rf_chain); + sx125x_reg_w(SX125x_REG_FRF_RX_LSB, 0xFF & rx_freq_frac, rf_chain); + sx125x_reg_w(SX125x_REG_FRF_TX_MSB, 0xFF & tx_freq_int, rf_chain); + sx125x_reg_w(SX125x_REG_FRF_TX_MID, 0xFF & (tx_freq_frac >> 8), rf_chain); + sx125x_reg_w(SX125x_REG_FRF_TX_LSB, 0xFF & tx_freq_frac, rf_chain); /* Radio settings for calibration */ - //lgw_sx125x_reg_w(SX125x_RX_ANA_GAIN__LNA_ZIN, 1, rf_chain); /* Default: 1 */ - //lgw_sx125x_reg_w(SX125x_RX_ANA_GAIN__BB_GAIN, 15, rf_chain); /* Default: 15 */ - //lgw_sx125x_reg_w(SX125x_RX_ANA_GAIN__LNA_GAIN, 1, rf_chain); /* Default: 1 */ - lgw_sx125x_reg_w(SX125x_REG_RX_BW__BB_BW, 0, rf_chain); - lgw_sx125x_reg_w(SX125x_REG_RX_BW__ADC_TRIM, 6, rf_chain); - //lgw_sx125x_reg_w(SX125x_RX_BW__ADC_BW, 7, rf_chain); /* Default: 7 */ - lgw_sx125x_reg_w(SX125x_REG_RX_PLL_BW__PLL_BW, 0, rf_chain); - lgw_sx125x_reg_w(SX125x_REG_TX_BW__PLL_BW, 0, rf_chain); - //lgw_sx125x_reg_w(SX125x_TX_BW__ANA_BW, 0, rf_chain); /* Default: 0 */ - lgw_sx125x_reg_w(SX125x_REG_TX_DAC_BW, 5, rf_chain); - lgw_sx125x_reg_w(SX125x_REG_CLK_SELECT__DAC_CLK_SELECT, 1, rf_chain); /* Use external clock from SX1302 */ - lgw_sx125x_reg_w(SX125x_REG_TX_GAIN__DAC_GAIN, dac_gain, rf_chain); - lgw_sx125x_reg_w(SX125x_REG_TX_GAIN__MIX_GAIN, mix_gain, rf_chain); - lgw_sx125x_reg_w(SX125x_REG_CLK_SELECT__RF_LOOPBACK_EN, 1, rf_chain); - lgw_sx125x_reg_w(SX125x_REG_MODE, 15, rf_chain); + //sx125x_reg_w(SX125x_RX_ANA_GAIN__LNA_ZIN, 1, rf_chain); /* Default: 1 */ + //sx125x_reg_w(SX125x_RX_ANA_GAIN__BB_GAIN, 15, rf_chain); /* Default: 15 */ + //sx125x_reg_w(SX125x_RX_ANA_GAIN__LNA_GAIN, 1, rf_chain); /* Default: 1 */ + sx125x_reg_w(SX125x_REG_RX_BW__BB_BW, 0, rf_chain); + sx125x_reg_w(SX125x_REG_RX_BW__ADC_TRIM, 6, rf_chain); + //sx125x_reg_w(SX125x_RX_BW__ADC_BW, 7, rf_chain); /* Default: 7 */ + sx125x_reg_w(SX125x_REG_RX_PLL_BW__PLL_BW, 0, rf_chain); + sx125x_reg_w(SX125x_REG_TX_BW__PLL_BW, 0, rf_chain); + //sx125x_reg_w(SX125x_TX_BW__ANA_BW, 0, rf_chain); /* Default: 0 */ + sx125x_reg_w(SX125x_REG_TX_DAC_BW, 5, rf_chain); + sx125x_reg_w(SX125x_REG_CLK_SELECT__DAC_CLK_SELECT, 1, rf_chain); /* Use external clock from SX1302 */ + sx125x_reg_w(SX125x_REG_TX_GAIN__DAC_GAIN, dac_gain, rf_chain); + sx125x_reg_w(SX125x_REG_TX_GAIN__MIX_GAIN, mix_gain, rf_chain); + sx125x_reg_w(SX125x_REG_CLK_SELECT__RF_LOOPBACK_EN, 1, rf_chain); + sx125x_reg_w(SX125x_REG_MODE, 15, rf_chain); wait_ms(1); - lgw_sx125x_reg_r(SX125x_REG_MODE_STATUS__RX_PLL_LOCKED, &rx_pll_locked, rf_chain); - lgw_sx125x_reg_r(SX125x_REG_MODE_STATUS__TX_PLL_LOCKED, &tx_pll_locked, rf_chain); + sx125x_reg_r(SX125x_REG_MODE_STATUS__RX_PLL_LOCKED, &rx_pll_locked, rf_chain); + sx125x_reg_r(SX125x_REG_MODE_STATUS__TX_PLL_LOCKED, &tx_pll_locked, rf_chain); if ((rx_pll_locked == 0) || (tx_pll_locked == 0)) { DEBUG_MSG("ERROR: PLL failed to lock\n"); return LGW_HAL_ERROR; @@ -460,22 +461,17 @@ int test_capture_ram(uint8_t rf_chain) { int main(int argc, char **argv) { int i, x; - uint32_t ft = DEFAULT_FREQ_HZ; - double arg_d = 0.0; unsigned int arg_u; uint8_t clocksource = 0; uint8_t rf_chain = 0; - lgw_radio_type_t radio_type = LGW_RADIO_TYPE_NONE; - bool single_input_mode = false; - - struct lgw_conf_board_s boardconf; - struct lgw_conf_rxrf_s rfconf; + lgw_radio_type_t radio_type = LGW_RADIO_TYPE_SX1257; static struct sigaction sigact; /* SIGQUIT&SIGINT&SIGTERM signal handling */ /* SPI interfaces */ - const char spidev_path_default[] = LINUXDEV_PATH_DEFAULT; - const char * spidev_path = spidev_path_default; + const char com_path_default[] = COM_PATH_DEFAULT; + const char * com_path = com_path_default; + lgw_com_type_t com_type = COM_TYPE_DEFAULT; /* Initialize TX gain LUT */ txlut.size = 1; @@ -492,22 +488,26 @@ int main(int argc, char **argv) }; /* parse command line options */ - while ((i = getopt_long (argc, argv, "hjf:k:r:c:d:", long_options, &option_index)) != -1) { + while ((i = getopt_long (argc, argv, "hk:r:c:d:u", long_options, &option_index)) != -1) { switch (i) { case 'h': usage(); return -1; break; + case 'u': + com_type = LGW_COM_USB; + break; + case 'd': if (optarg != NULL) { - spidev_path = optarg; + com_path = optarg; } break; case 'r': /* Radio type */ i = sscanf(optarg, "%u", &arg_u); - if ((i != 1) || ((arg_u != 1255) && (arg_u != 1257) && (arg_u != 1250))) { + if ((i != 1) || ((arg_u != 1255) && (arg_u != 1257))) { printf("ERROR: argument parsing of -r argument. Use -h to print help\n"); return EXIT_FAILURE; } else { @@ -518,8 +518,8 @@ int main(int argc, char **argv) case 1257: radio_type = LGW_RADIO_TYPE_SX1257; break; - default: /* 1250 */ - radio_type = LGW_RADIO_TYPE_SX1250; + default: + /* should not happen */ break; } } @@ -545,20 +545,6 @@ int main(int argc, char **argv) } break; - case 'j': - single_input_mode = true; - break; - - case 'f': /* Radio TX frequency in MHz */ - i = sscanf(optarg, "%lf", &arg_d); - if (i != 1) { - printf("ERROR: argument parsing of -f argument. Use -h to print help\n"); - return EXIT_FAILURE; - } else { - ft = (uint32_t)((arg_d*1e6) + 0.5); /* .5 Hz offset to get rounding instead of truncating */ - } - break; - case 0: if (strcmp(long_options[option_index].name, "dac") == 0) { i = sscanf(optarg, "%u", &arg_u); @@ -599,50 +585,17 @@ int main(int argc, char **argv) sigaction( SIGINT, &sigact, NULL ); sigaction( SIGTERM, &sigact, NULL ); - /* Board reset */ - if (system("./reset_lgw.sh start") != 0) { - printf("ERROR: failed to reset SX1302, check your reset_lgw.sh script\n"); + /* USB is currently not supported for sx1255/sx1257 radios */ + if (com_type == LGW_COM_USB) { + printf("ERROR: USB interface is currently not supported for sx1255/sx1257 radios\n"); exit(EXIT_FAILURE); } - /* Configure the gateway */ - memset(&boardconf, 0, sizeof boardconf); - boardconf.lorawan_public = true; - boardconf.clksrc = clocksource; - boardconf.full_duplex = false; - strncpy(boardconf.spidev_path, spidev_path, sizeof boardconf.spidev_path); - boardconf.spidev_path[sizeof boardconf.spidev_path - 1] = '\0'; /* ensure string termination */ - if (lgw_board_setconf(&boardconf) != LGW_HAL_SUCCESS) { - printf("ERROR: failed to configure board\n"); - return EXIT_FAILURE; - } - - memset(&rfconf, 0, sizeof rfconf); - rfconf.enable = ((rf_chain == 0) ? true : false); - rfconf.freq_hz = ft; - rfconf.type = radio_type; - rfconf.tx_enable = true; - rfconf.single_input_mode = single_input_mode; - if (lgw_rxrf_setconf(0, &rfconf) != LGW_HAL_SUCCESS) { - printf("ERROR: failed to configure rxrf 0\n"); - return EXIT_FAILURE; - } - - memset(&rfconf, 0, sizeof rfconf); - rfconf.enable = ((rf_chain == 1) ? true : false); - rfconf.freq_hz = ft; - rfconf.type = radio_type; - rfconf.tx_enable = true; - rfconf.single_input_mode = single_input_mode; - if (lgw_rxrf_setconf(1, &rfconf) != LGW_HAL_SUCCESS) { - printf("ERROR: failed to configure rxrf 1\n"); - return EXIT_FAILURE; - } - - if (txlut.size > 0) { - if (lgw_txgain_setconf(rf_chain, &txlut) != LGW_HAL_SUCCESS) { - printf("ERROR: failed to configure txgain lut\n"); - return EXIT_FAILURE; + if (com_type == LGW_COM_SPI) { + /* Board reset */ + if (system("./reset_lgw.sh start") != 0) { + printf("ERROR: failed to reset SX1302, check your reset_lgw.sh script\n"); + exit(EXIT_FAILURE); } } @@ -650,15 +603,15 @@ int main(int argc, char **argv) fp = fopen("log.txt", "w+"); /* connect the gateway */ - x = lgw_connect(spidev_path); + x = lgw_connect(com_type, com_path); if (x != 0) { printf("ERROR: failed to connect the gateway\n"); return EXIT_FAILURE; } - sx1302_radio_reset(rf_chain, LGW_RADIO_TYPE_SX1257); + sx1302_radio_reset(rf_chain, radio_type); sx1302_radio_clock_select(clocksource); - sx1302_radio_set_mode(rf_chain, LGW_RADIO_TYPE_SX1257); + sx1302_radio_set_mode(rf_chain, radio_type); printf("Loading CAL fw for sx125x\n"); if (sx1302_agc_load_firmware(cal_firmware_sx125x) != LGW_HAL_SUCCESS) { @@ -680,8 +633,8 @@ int main(int argc, char **argv) //test_capture_ram(rf_chain); - sx1302_radio_reset(0, LGW_RADIO_TYPE_SX1257); - sx1302_radio_reset(1, LGW_RADIO_TYPE_SX1257); + sx1302_radio_reset(0, radio_type); + sx1302_radio_reset(1, radio_type); /* disconnect the gateway */ x = lgw_disconnect(); @@ -693,10 +646,12 @@ int main(int argc, char **argv) /* Close log file */ fclose(fp); - /* Board reset */ - if (system("./reset_lgw.sh stop") != 0) { - printf("ERROR: failed to reset SX1302, check your reset_lgw.sh script\n"); - exit(EXIT_FAILURE); + if (com_type == LGW_COM_SPI) { + /* Board reset */ + if (system("./reset_lgw.sh stop") != 0) { + printf("ERROR: failed to reset SX1302, check your reset_lgw.sh script\n"); + exit(EXIT_FAILURE); + } } printf("=========== Test End ===========\n"); diff --git a/libloragw/tst/test_loragw_capture_ram.c b/libloragw/tst/test_loragw_capture_ram.c index db120449..93528a43 100644 --- a/libloragw/tst/test_loragw_capture_ram.c +++ b/libloragw/tst/test_loragw_capture_ram.c @@ -23,14 +23,13 @@ License: Revised BSD License, see LICENSE.TXT file include in the project #define _XOPEN_SOURCE 500 #endif - -// #include #include /* printf */ #include #include /* sigaction */ #include /* getopt_long */ #include "loragw_hal.h" +#include "loragw_com.h" #include "loragw_reg.h" #include "loragw_aux.h" #include "loragw_sx1250.h" @@ -40,11 +39,11 @@ License: Revised BSD License, see LICENSE.TXT file include in the project /* -------------------------------------------------------------------------- */ /* --- PRIVATE MACROS ------------------------------------------------------- */ -#define DEBUG_MSG(str) fprintf(stderr, str) +#define DEBUG_MSG(str) fprintf(stdout, str) -#define LINUXDEV_PATH_DEFAULT "/dev/spidev0.0" +#define COM_TYPE_DEFAULT LGW_COM_SPI +#define COM_PATH_DEFAULT "/dev/spidev0.0" -#define FULL_INIT 0 #define CAPTURE_RAM_SIZE 0x4000 /* -------------------------------------------------------------------------- */ @@ -56,21 +55,6 @@ static int quit_sig = 0; /* 1 -> application terminates without shutting down th uint32_t sampling_frequency[] = {4e6, 4e6, 4e6, 4e6, 4e6, 4e6, 4e6, 0, 0, 1e6, 125e3, 125e3, 125e3, 125e3, 125e3, 125e3, 125e3, 125e3, 8e6, 125e3, 125e3, 125e3, 0, 32e6, 32e6, 0, 32e6, 32e6, 0, 32e6, 32e6, 32e6}; -#if FULL_INIT -#include "src/text_agc_sx1250_27_Nov_1.var" -#include "src/text_agc_sx1257_19_Nov_1.var" -#include "src/text_arb_sx1302_13_Nov_3.var" - -#define FW_VERSION_CAL 0 /* Expected version of calibration firmware */ /* TODO */ -#define FW_VERSION_AGC 1 /* Expected version of AGC firmware */ -#define FW_VERSION_ARB 1 /* Expected version of arbiter firmware */ - -static bool rf_enable[LGW_RF_CHAIN_NB]; -static uint32_t rf_rx_freq[LGW_RF_CHAIN_NB]; /* absolute, in Hz */ -static lgw_radio_type_t rf_radio_type[LGW_RF_CHAIN_NB]; -static uint8_t rf_clkout = 0; -#endif - /* -------------------------------------------------------------------------- */ /* --- PRIVATE FUNCTIONS ---------------------------------------------------- */ @@ -79,8 +63,8 @@ void usage(void) { printf("Available options:\n"); printf(" -h print this help\n"); - printf(" -d use Linux SPI device driver\n"); - printf(" => default path: " LINUXDEV_PATH_DEFAULT "\n"); + printf(" -d [path] Path to the SPI interface (USB is not supported)\n"); + printf(" => default path: " COM_PATH_DEFAULT "\n"); printf(" -s Capture source [0..31]\n"); } @@ -105,16 +89,14 @@ int main(int argc, char **argv) uint8_t capture_source = 0; uint16_t period_value = 0; int16_t real = 0, imag = 0; -#if FULL_INIT - uint32_t val1, val2; -#endif uint8_t capture_ram_buffer[CAPTURE_RAM_SIZE]; static struct sigaction sigact; /* SIGQUIT&SIGINT&SIGTERM signal handling */ /* SPI interfaces */ - const char spidev_path_default[] = LINUXDEV_PATH_DEFAULT; - const char * spidev_path = spidev_path_default; + const char com_path_default[] = COM_PATH_DEFAULT; + const char * com_path = com_path_default; + lgw_com_type_t com_type = COM_TYPE_DEFAULT; /* Parameter parsing */ int option_index = 0; @@ -132,7 +114,7 @@ int main(int argc, char **argv) case 'd': if (optarg != NULL) { - spidev_path = optarg; + com_path = optarg; } break; @@ -161,105 +143,17 @@ int main(int argc, char **argv) sigaction( SIGINT, &sigact, NULL ); sigaction( SIGTERM, &sigact, NULL ); -#if FULL_INIT - /* Board reset */ - if (system("./reset_lgw.sh start") != 0) { - printf("ERROR: failed to reset SX1302, check your reset_lgw.sh script\n"); - exit(EXIT_FAILURE); - } -#endif - /* Initialize memory for capture */ for (i = 0; i < CAPTURE_RAM_SIZE; i++) { capture_ram_buffer[i] = i%256; } - reg_stat = lgw_connect(spidev_path); + reg_stat = lgw_connect(com_type, com_path); if (reg_stat == LGW_REG_ERROR) { DEBUG_MSG("ERROR: FAIL TO CONNECT BOARD\n"); return LGW_HAL_ERROR; } - /* Manual init */ -#if FULL_INIT - rf_radio_type[0] = LGW_RADIO_TYPE_SX1250; - rf_radio_type[1] = LGW_RADIO_TYPE_SX1257; - rf_enable[0] = false; - rf_enable[1] = true; - rf_clkout = 1; - rf_rx_freq[1] = 863700000; - - /* setup radios */ - for (i=0; i < 2; i++) - { - if (rf_enable[i] == true) { - sx1302_radio_reset(i, rf_radio_type[i]); - switch (radio_type) { - case LGW_RADIO_TYPE_SX1250: - sx1250_setup(i, rf_rx_freq[i], false); - break; - case LGW_RADIO_TYPE_SX1255: - case LGW_RADIO_TYPE_SX1257: - sx125x_setup(i, rf_clkout, true, rf_radio_type[i], rf_rx_freq[i]); - break; - default: - DEBUG_MSG("ERROR: RADIO TYPE NOT SUPPORTED\n"); - return LGW_HAL_ERROR; - } - sx1302_radio_set_mode(i, radio_type); - } - } - - /* Select the radio which provides the clock to the sx1302 */ - sx1302_radio_clock_select(rf_clkout); - - /* Check that the SX1302 timestamp counter is running */ - lgw_get_instcnt(&val1); - lgw_get_instcnt(&val2); - if (val1 == val2) { - printf("ERROR: SX1302 timestamp counter is not running (val:%u)\n", (uint32_t)val1); - return -1; - } - - /* Configure Radio FE */ - sx1302_radio_fe_configure(); - - /* give radio control to AGC MCU */ - lgw_reg_w(SX1302_REG_COMMON_CTRL0_HOST_RADIO_CTRL, 0x00); - - /* Load firmware */ - switch (rf_radio_type[rf_clkout]) { - case LGW_RADIO_TYPE_SX1250: - printf("Loading AGC fw for sx1250\n"); - if (sx1302_agc_load_firmware(agc_firmware_sx1250) != LGW_HAL_SUCCESS) { - return LGW_HAL_ERROR; - } - if (sx1302_agc_start(FW_VERSION_AGC, SX1302_RADIO_TYPE_SX1250, SX1302_AGC_RADIO_GAIN_AUTO, SX1302_AGC_RADIO_GAIN_AUTO, 0) != LGW_HAL_SUCCESS) { - return LGW_HAL_ERROR; - } - break; - case LGW_RADIO_TYPE_SX1257: - printf("Loading AGC fw for sx125x\n"); - if (sx1302_agc_load_firmware(agc_firmware_sx125x) != LGW_HAL_SUCCESS) { - return LGW_HAL_ERROR; - } - if (sx1302_agc_start(FW_VERSION_AGC, SX1302_RADIO_TYPE_SX125X, SX1302_AGC_RADIO_GAIN_AUTO, SX1302_AGC_RADIO_GAIN_AUTO, 0) != LGW_HAL_SUCCESS) { - // if (sx1302_agc_start(FW_VERSION_AGC, SX1302_RADIO_TYPE_SX125X, 1, 7, 0) != LGW_HAL_SUCCESS) { - return LGW_HAL_ERROR; - } - break; - default: - break; - } - printf("Loading ARB fw\n"); - if (sx1302_arb_load_firmware(arb_firmware) != LGW_HAL_SUCCESS) { - return LGW_HAL_ERROR; - } - if (sx1302_arb_start(FW_VERSION_ARB) != LGW_HAL_SUCCESS) { - return LGW_HAL_ERROR; - } -#endif - // lgw_reg_w(SX1302_REG_CAPTURE_RAM_CLOCK_GATE_OVERRIDE_CLK_OVERRIDE, 3); /* Configure the Capture Ram block */ @@ -270,12 +164,9 @@ int main(int argc, char **argv) lgw_reg_w(SX1302_REG_CAPTURE_RAM_CAPTURE_SOURCE_A_SOURCEMUX, capture_source); printf("Sampling frequency: %d\n", sampling_frequency[capture_source]); - if (sampling_frequency[capture_source] != 0) - { + if (sampling_frequency[capture_source] != 0) { period_value = (32e6/sampling_frequency[capture_source]) - 1; - } - else - { + } else { fprintf(stderr ,"ERROR: Sampling frequency is null\n"); return -1; } @@ -295,7 +186,7 @@ int main(int argc, char **argv) // lgw_reg_w(SX1302_REG_CAPTURE_RAM_CAPTURE_CFG_CAPTUREFORCETRIGGER, 1); /* Poll Status.CapComplete */ - do{ + do { lgw_reg_r(SX1302_REG_CAPTURE_RAM_STATUS_CAPCOMPLETE, &val); wait_ms(10); @@ -305,7 +196,6 @@ int main(int argc, char **argv) } while (val != 1); lgw_reg_w(SX1302_REG_CAPTURE_RAM_CAPTURE_CFG_CAPTURESTART, 0); - // lgw_reg_r(SX1302_REG_CAPTURE_RAM_LAST_RAM_ADDR_0_LASTRAMADDR, &val); // fprintf(stdout, "SX1302_REG_CAPTURE_RAM_LAST_RAM_ADDR_0_LASTRAMADDR value: %02x\n", val); // lgw_reg_r(SX1302_REG_CAPTURE_RAM_LAST_RAM_ADDR_1_LASTRAMADDR, &val); @@ -316,54 +206,34 @@ int main(int argc, char **argv) lgw_reg_w(SX1302_REG_COMMON_PAGE_PAGE, 0); printf("Data:\n"); - for (i = 0; i < CAPTURE_RAM_SIZE; i += 4) - { - if (((capture_source >= 2) && (capture_source <= 3)) || (capture_source == 9)) - { + for (i = 0; i < CAPTURE_RAM_SIZE; i += 4) { + if (((capture_source >= 2) && (capture_source <= 3)) || (capture_source == 9)) { real = (int16_t)((((uint16_t)(capture_ram_buffer[i+3]) << 8) & 0xFF00) + ((uint16_t)capture_ram_buffer[i+2] & 0x00FF)); imag = (int16_t)((((uint16_t)(capture_ram_buffer[i+1]) << 8) & 0xFF00) + ((uint16_t)capture_ram_buffer[i+0] & 0x00FF)); - real >>= 4; // 12 bits I - imag >>= 4; // 12 bits Q - } - else if ((capture_source >= 4) && (capture_source <= 6)) - { - real = (int16_t)((((uint16_t)(capture_ram_buffer[i+3]) << 8) & 0xFF00) + ((uint16_t)capture_ram_buffer[i+2] & 0x00FF)); // 16 bits I - imag = (int16_t)((((uint16_t)(capture_ram_buffer[i+1]) << 8) & 0xFF00) + ((uint16_t)capture_ram_buffer[i+0] & 0x00FF)); // 16 bits Q - } - else if ((capture_source >= 10) && (capture_source <= 17)) - { - real = (int8_t)(capture_ram_buffer[i+3]); // 8 bits I - imag = (int8_t)(capture_ram_buffer[i+1]); // 8 bits Q - } - else - { + real >>= 4; // 12 bits I + imag >>= 4; // 12 bits Q + } else if ((capture_source >= 4) && (capture_source <= 6)) { + real = (int16_t)((((uint16_t)(capture_ram_buffer[i+3]) << 8) & 0xFF00) + ((uint16_t)capture_ram_buffer[i+2] & 0x00FF)); // 16 bits I + imag = (int16_t)((((uint16_t)(capture_ram_buffer[i+1]) << 8) & 0xFF00) + ((uint16_t)capture_ram_buffer[i+0] & 0x00FF)); // 16 bits Q + } else if ((capture_source >= 10) && (capture_source <= 17)) { + real = (int8_t)(capture_ram_buffer[i+3]); // 8 bits I + imag = (int8_t)(capture_ram_buffer[i+1]); // 8 bits Q + } else { real = 0; imag = 0; } - if (((capture_source >= 2) && (capture_source <= 6)) || ((capture_source >= 9) && (capture_source <= 17))) - { + if (((capture_source >= 2) && (capture_source <= 6)) || ((capture_source >= 9) && (capture_source <= 17))) { fprintf(stdout, "%d", real); - if (imag >= 0) - { + if (imag >= 0) { fprintf(stdout, "+"); } fprintf(stdout, "%di\n", imag); - } - else - { + } else { printf("%02X ", capture_ram_buffer[i]); } } printf("End of Data\n"); -#if FULL_INIT - /* Board reset */ - if (system("./reset_lgw.sh stop") != 0) { - printf("ERROR: failed to reset SX1302, check your reset_lgw.sh script\n"); - exit(EXIT_FAILURE); - } -#endif - return 0; } diff --git a/libloragw/tst/test_loragw_com.c b/libloragw/tst/test_loragw_com.c new file mode 100644 index 00000000..5fdef120 --- /dev/null +++ b/libloragw/tst/test_loragw_com.c @@ -0,0 +1,427 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2019 Semtech + +Description: + Minimum test program for the loragw_com module + +License: Revised BSD License, see LICENSE.TXT file include in the project +*/ + + +/* -------------------------------------------------------------------------- */ +/* --- DEPENDANCIES --------------------------------------------------------- */ + +/* Fix an issue between POSIX and C99 */ +#if __STDC_VERSION__ >= 199901L + #define _XOPEN_SOURCE 600 +#else + #define _XOPEN_SOURCE 500 +#endif + +#include +#include +#include +#include +#include /* sigaction */ +#include /* getopt, access */ +#include +#include + +#include "loragw_com.h" +#include "loragw_aux.h" +#include "loragw_hal.h" + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE MACROS ------------------------------------------------------- */ + +#define RAND_RANGE(min, max) (rand() % (max + 1 - min) + min) + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE CONSTANTS ---------------------------------------------------- */ + +#define BUFF_SIZE_SPI 1024 +#define BUFF_SIZE_USB 4096 + +#define SX1302_AGC_MCU_MEM 0x0000 +#define SX1302_REG_COMMON 0x5600 +#define SX1302_REG_AGC_MCU 0x5780 + +#define COM_TYPE_DEFAULT LGW_COM_SPI +#define COM_PATH_DEFAULT "/dev/spidev0.0" + +/* -------------------------------------------------------------------------- */ +/* --- GLOBAL VARIABLES ----------------------------------------------------- */ + +/* Signal handling variables */ +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 */ + +/* Buffers */ +static uint8_t * test_buff = NULL; +static uint8_t * read_buff = NULL; + +/* -------------------------------------------------------------------------- */ +/* --- SUBFUNCTIONS DECLARATION --------------------------------------------- */ + +static void sig_handler(int sigio); +static void usage(void); +static void exit_failure(void); + +/* -------------------------------------------------------------------------- */ +/* --- MAIN FUNCTION -------------------------------------------------------- */ + +int main(int argc, char ** argv) +{ + static struct sigaction sigact; /* SIGQUIT&SIGINT&SIGTERM signal handling */ + + uint16_t max_buff_size; + uint8_t data = 0; + int cycle_number = 0; + int i, x; + uint16_t size; + + /* COM interfaces */ + const char com_path_default[] = COM_PATH_DEFAULT; + const char * com_path = com_path_default; + lgw_com_type_t com_type = COM_TYPE_DEFAULT; + + /* Parse command line options */ + while ((i = getopt(argc, argv, "hd:u")) != -1) { + switch (i) { + case 'h': + usage(); + return EXIT_SUCCESS; + break; + + case 'd': + if (optarg != NULL) { + com_path = optarg; + } + break; + + case 'u': + com_type = LGW_COM_USB; + break; + + default: + printf("ERROR: argument parsing options, 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 ); + + /* Board reset */ + if (com_type == LGW_COM_SPI) { + if (system("./reset_lgw.sh start") != 0) { + printf("ERROR: failed to reset SX1302, check your reset_lgw.sh script\n"); + exit(EXIT_FAILURE); + } + } + + printf("Beginning of test for loragw_com.c\n"); + x = lgw_com_open(com_type, com_path); + if (x != 0) { + printf("ERROR: failed to open COM device %s\n", com_path); + exit(EXIT_FAILURE); + } + + /* normal R/W test */ + /* TODO */ + + /* burst R/W test, small bursts << LGW_BURST_CHUNK */ + /* TODO */ + + /* burst R/W test, large bursts >> LGW_BURST_CHUNK */ + /* TODO */ + + x = lgw_com_r(LGW_SPI_MUX_TARGET_SX1302, SX1302_REG_COMMON + 6, &data); + if (x != 0) { + printf("ERROR (%d): failed to read register\n", __LINE__); + exit_failure(); + } + printf("SX1302 version: 0x%02X\n", data); + + x = lgw_com_r(LGW_SPI_MUX_TARGET_SX1302, SX1302_REG_AGC_MCU + 0, &data); + if (x != 0) { + printf("ERROR (%d): failed to read register\n", __LINE__); + exit_failure(); + } + x = lgw_com_w(LGW_SPI_MUX_TARGET_SX1302, SX1302_REG_AGC_MCU + 0, 0x06); /* mcu_clear, host_prog */ + if (x != 0) { + printf("ERROR (%d): failed to write register\n", __LINE__); + exit_failure(); + } + + srand(time(NULL)); + + /* Allocate buffers according to com type capabilities */ + max_buff_size = (com_type == LGW_COM_SPI) ? BUFF_SIZE_SPI : BUFF_SIZE_USB; + test_buff = (uint8_t*)malloc(max_buff_size * sizeof(uint8_t)); + if (test_buff == NULL) { + printf("ERROR: failed to allocate memory for test_buff - %s\n", strerror(errno)); + exit_failure(); + } + read_buff = (uint8_t*)malloc(max_buff_size * sizeof(uint8_t)); + if (read_buff == NULL) { + printf("ERROR: failed to allocate memory for read_buff - %s\n", strerror(errno)); + exit_failure(); + } + + /* databuffer R/W stress test */ + while ((quit_sig != 1) && (exit_sig != 1)) { + /************************************************* + * + * WRITE BURST TEST + * + * ***********************************************/ + + size = rand() % max_buff_size; + for (i = 0; i < size; ++i) { + test_buff[i] = rand() & 0xFF; + } + printf("Cycle %i> ", cycle_number); + + /* Write burst with random data */ + x = lgw_com_wb(LGW_SPI_MUX_TARGET_SX1302, SX1302_AGC_MCU_MEM, test_buff, size); + if (x != 0) { + printf("ERROR (%d): failed to write burst\n", __LINE__); + exit_failure(); + } + + /* Read back */ + x = lgw_com_rb(LGW_SPI_MUX_TARGET_SX1302, SX1302_AGC_MCU_MEM, read_buff, size); + if (x != 0) { + printf("ERROR (%d): failed to read burst\n", __LINE__); + exit_failure(); + } + + /* Compare read / write buffers */ + for (i=0; ((i ", cycle_number); + + test_buff[0] = rand() & 0xFF; + + /* Write single byte */ + x = lgw_com_w(LGW_SPI_MUX_TARGET_SX1302, SX1302_AGC_MCU_MEM, test_buff[0]); + if (x != 0) { + printf("ERROR (%d): failed to write burst\n", __LINE__); + exit_failure(); + } + + /* Read back */ + x = lgw_com_r(LGW_SPI_MUX_TARGET_SX1302, SX1302_AGC_MCU_MEM, &read_buff[0]); + if (x != 0) { + printf("ERROR (%d): failed to read burst\n", __LINE__); + exit_failure(); + } + + /* Compare read / write bytes */ + if (test_buff[0] != read_buff[0]) { + printf("error during the byte comparison\n"); + + /* Print what has been written */ + printf("Written value: %02X\n", test_buff[0]); + + /* Print what has been read back */ + printf("Read values: %02X\n", read_buff[0]); + + /* exit */ + exit_failure(); + } else { + printf("did a 1-byte R/W on a data buffer with no error\n"); + ++cycle_number; + } + + /************************************************* + * + * WRITE WITH BULK (USB only mode) + * + * ***********************************************/ + x = lgw_com_set_write_mode(LGW_COM_WRITE_MODE_BULK); + if (x != 0) { + printf("ERROR (%d): failed to set bulk write mode\n", __LINE__); + exit_failure(); + } + + uint16_t num_req = RAND_RANGE(1, 254); /* keep one req for remaining bytes */ + size = RAND_RANGE(num_req, max_buff_size / 2); /* TODO: test proper limit */ + for (i = 0; i < size; i++) { + test_buff[i] = rand() & 0xFF; + } + uint16_t size_per_req = size / num_req; + uint16_t size_remaining = size - (num_req * size_per_req); + printf("Cycle %i> ", cycle_number); + + uint16_t size_written = 0; + for (i = 0; i < num_req; i++) { + x = lgw_com_wb(LGW_SPI_MUX_TARGET_SX1302, SX1302_AGC_MCU_MEM + size_written, test_buff + size_written, size_per_req); + if (x != 0) { + printf("ERROR (%d): failed to write burst\n", __LINE__); + exit_failure(); + } + size_written += (size_per_req); + } + if (size_remaining > 0) { + x = lgw_com_wb(LGW_SPI_MUX_TARGET_SX1302, SX1302_AGC_MCU_MEM + size_written, test_buff + size_written, size_remaining); + if (x != 0) { + printf("ERROR (%d): failed to write burst\n", __LINE__); + exit_failure(); + } + } + + /* Send data to MCU (UBS mode only) */ + x = lgw_com_flush(); + if (x != 0) { + printf("ERROR (%d): failed to flush write\n", __LINE__); + exit_failure(); + } + + /* Read back */ + x = lgw_com_rb(LGW_SPI_MUX_TARGET_SX1302, SX1302_AGC_MCU_MEM, read_buff, size); + if (x != 0) { + printf("ERROR (%d): failed to read burst\n", __LINE__); + exit_failure(); + } + + /* Compare read / write buffers */ + for (i=0; ((i COM path to be used to connect the concentrator\n"); + printf(" => default path (SPI): " COM_PATH_DEFAULT "\n"); +} + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/libloragw/tst/test_loragw_spi_sx1250.c b/libloragw/tst/test_loragw_com_sx1250.c similarity index 72% rename from libloragw/tst/test_loragw_spi_sx1250.c rename to libloragw/tst/test_loragw_com_sx1250.c index ca91162e..461a5b01 100644 --- a/libloragw/tst/test_loragw_spi_sx1250.c +++ b/libloragw/tst/test_loragw_com_sx1250.c @@ -7,7 +7,7 @@ (C)2019 Semtech Description: - Minimum test program for the sx1250 module + Minimum test program for the sx1250_com module License: Revised BSD License, see LICENSE.TXT file include in the project */ @@ -30,8 +30,8 @@ License: Revised BSD License, see LICENSE.TXT file include in the project #include /* sigaction */ #include /* getopt, access */ -#include "loragw_spi.h" #include "loragw_aux.h" +#include "loragw_com.h" #include "loragw_reg.h" #include "loragw_hal.h" #include "loragw_sx1250.h" @@ -45,7 +45,8 @@ License: Revised BSD License, see LICENSE.TXT file include in the project #define BUFF_SIZE 16 -#define LINUXDEV_PATH_DEFAULT "/dev/spidev0.0" +#define COM_TYPE_DEFAULT LGW_COM_SPI +#define COM_PATH_DEFAULT "/dev/spidev0.0" /* -------------------------------------------------------------------------- */ /* --- GLOBAL VARIABLES ----------------------------------------------------- */ @@ -59,6 +60,7 @@ static int quit_sig = 0; /* 1 -> application terminates without shutting down th static void sig_handler(int sigio); static void usage(void); +static void exit_failure(); /* -------------------------------------------------------------------------- */ /* --- MAIN FUNCTION -------------------------------------------------------- */ @@ -73,12 +75,13 @@ int main(int argc, char ** argv) int cycle_number = 0; int i, x; - /* SPI interfaces */ - const char spidev_path_default[] = LINUXDEV_PATH_DEFAULT; - const char * spidev_path = spidev_path_default; + /* COM interfaces */ + const char com_path_default[] = COM_PATH_DEFAULT; + const char * com_path = com_path_default; + lgw_com_type_t com_type = COM_TYPE_DEFAULT; /* Parse command line options */ - while ((i = getopt(argc, argv, "hd:")) != -1) { + while ((i = getopt(argc, argv, "hd:u")) != -1) { switch (i) { case 'h': usage(); @@ -87,10 +90,14 @@ int main(int argc, char ** argv) case 'd': if (optarg != NULL) { - spidev_path = optarg; + com_path = optarg; } break; + case 'u': + com_type = LGW_COM_USB; + break; + default: printf("ERROR: argument parsing options, use -h option for help\n"); usage(); @@ -106,15 +113,18 @@ int main(int argc, char ** argv) sigaction( SIGINT, &sigact, NULL ); sigaction( SIGTERM, &sigact, NULL ); + /* Board reset */ - if (system("./reset_lgw.sh start") != 0) { - printf("ERROR: failed to reset SX1302, check your reset_lgw.sh script\n"); - exit(EXIT_FAILURE); + if (com_type == LGW_COM_SPI) { + if (system("./reset_lgw.sh start") != 0) { + printf("ERROR: failed to reset SX1302, check your reset_lgw.sh script\n"); + exit(EXIT_FAILURE); + } } - x = lgw_connect(spidev_path); + x = lgw_connect(com_type, com_path); if (x != LGW_REG_SUCCESS) { - printf("ERROR: Failed to connect to the concentrator using SPI %s\n", spidev_path); + printf("ERROR: Failed to connect to the concentrator using COM %s\n", com_path); return EXIT_FAILURE; } @@ -137,14 +147,30 @@ int main(int argc, char ** argv) /* Set Radio in Standby mode */ test_buff[0] = (uint8_t)STDBY_XOSC; - sx1250_write_command(0, SET_STANDBY, test_buff, 1); - sx1250_write_command(1, SET_STANDBY, test_buff, 1); + x = sx1250_reg_w(SET_STANDBY, test_buff, 1, 0); + if (x != LGW_REG_SUCCESS) { + printf("ERROR(%d): Failed to configure sx1250_0\n", __LINE__); + exit_failure(); + } + x = sx1250_reg_w(SET_STANDBY, test_buff, 1, 1); + if (x != LGW_REG_SUCCESS) { + printf("ERROR(%d): Failed to configure sx1250_1\n", __LINE__); + exit_failure(); + } wait_ms(10); test_buff[0] = 0x00; - sx1250_read_command(0, GET_STATUS, test_buff, 1); + x = sx1250_reg_r(GET_STATUS, test_buff, 1, 0); + if (x != LGW_REG_SUCCESS) { + printf("ERROR(%d): Failed to get sx1250_0 status\n", __LINE__); + exit_failure(); + } printf("Radio0: get_status: 0x%02X\n", test_buff[0]); - sx1250_read_command(1, GET_STATUS, test_buff, 1); + x = sx1250_reg_r(GET_STATUS, test_buff, 1, 1); + if (x != LGW_REG_SUCCESS) { + printf("ERROR(%d): Failed to get sx1250_1 status\n", __LINE__); + exit_failure(); + } printf("Radio1: get_status: 0x%02X\n", test_buff[0]); /* databuffer R/W stress test */ @@ -154,7 +180,7 @@ int main(int argc, char ** argv) test_buff[2] = rand() & 0xFF; test_buff[3] = rand() & 0xFF; test_val = (test_buff[0] << 24) | (test_buff[1] << 16) | (test_buff[2] << 8) | (test_buff[3] << 0); - sx1250_write_command(0, SET_RF_FREQUENCY, test_buff, 4); + sx1250_reg_w(SET_RF_FREQUENCY, test_buff, 4, 0); read_buff[0] = 0x08; read_buff[1] = 0x8B; @@ -163,7 +189,7 @@ int main(int argc, char ** argv) read_buff[4] = 0x00; read_buff[5] = 0x00; read_buff[6] = 0x00; - sx1250_read_command(0, READ_REGISTER, read_buff, 7); + sx1250_reg_r(READ_REGISTER, read_buff, 7, 0); read_val = (read_buff[3] << 24) | (read_buff[4] << 16) | (read_buff[5] << 8) | (read_buff[6] << 0); printf("Cycle %i > ", cycle_number); @@ -182,10 +208,12 @@ int main(int argc, char ** argv) lgw_disconnect(); printf("End of test for loragw_spi_sx1250.c\n"); - /* Board reset */ - if (system("./reset_lgw.sh stop") != 0) { - printf("ERROR: failed to reset SX1302, check your reset_lgw.sh script\n"); - exit(EXIT_FAILURE); + if (com_type == LGW_COM_SPI) { + /* Board reset */ + if (system("./reset_lgw.sh stop") != 0) { + printf("ERROR: failed to reset SX1302, check your reset_lgw.sh script\n"); + exit(EXIT_FAILURE); + } } return 0; @@ -204,13 +232,24 @@ static void sig_handler(int sigio) { /* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ +static void exit_failure() { + lgw_disconnect(); + + printf("End of test for loragw_spi_sx1250.c\n"); + + exit(EXIT_FAILURE); +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + static void usage(void) { printf("~~~ Library version string~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"); printf(" %s\n", lgw_version_info()); printf("~~~ Available options ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"); printf(" -h print this help\n"); - printf(" -d use Linux SPI device driver\n"); - printf(" => default path: " LINUXDEV_PATH_DEFAULT "\n"); + printf(" -d path of the COM device used to access the concentrator\n"); + printf(" => default path: " COM_PATH_DEFAULT "\n"); + printf(" -u set COM type as USB (default is SPI)\n"); } /* --- EOF ------------------------------------------------------------------ */ diff --git a/libloragw/tst/test_loragw_spi.c b/libloragw/tst/test_loragw_com_sx1261.c similarity index 52% rename from libloragw/tst/test_loragw_spi.c rename to libloragw/tst/test_loragw_com_sx1261.c index 01118968..ca8b9711 100644 --- a/libloragw/tst/test_loragw_spi.c +++ b/libloragw/tst/test_loragw_com_sx1261.c @@ -7,7 +7,7 @@ (C)2019 Semtech Description: - Minimum test program for the loragw_spi module + Minimum test program for the sx1261_com module License: Revised BSD License, see LICENSE.TXT file include in the project */ @@ -29,11 +29,12 @@ License: Revised BSD License, see LICENSE.TXT file include in the project #include #include /* sigaction */ #include /* getopt, access */ -#include -#include "loragw_spi.h" #include "loragw_aux.h" #include "loragw_hal.h" +#include "loragw_reg.h" +#include "loragw_com.h" +#include "loragw_sx1261.h" /* -------------------------------------------------------------------------- */ /* --- PRIVATE MACROS ------------------------------------------------------- */ @@ -41,13 +42,11 @@ License: Revised BSD License, see LICENSE.TXT file include in the project /* -------------------------------------------------------------------------- */ /* --- PRIVATE CONSTANTS ---------------------------------------------------- */ -#define BUFF_SIZE 1024 +#define BUFF_SIZE 16 -#define SX1302_AGC_MCU_MEM 0x0000 -#define SX1302_REG_COMMON 0x5600 -#define SX1302_REG_AGC_MCU 0x5780 - -#define LINUXDEV_PATH_DEFAULT "/dev/spidev0.0" +#define COM_TYPE_DEFAULT LGW_COM_SPI +#define COM_PATH_DEFAULT "/dev/spidev0.0" +#define SX1261_PATH_DEFAULT "/dev/spidev0.1" /* -------------------------------------------------------------------------- */ /* --- GLOBAL VARIABLES ----------------------------------------------------- */ @@ -61,6 +60,7 @@ static int quit_sig = 0; /* 1 -> application terminates without shutting down th static void sig_handler(int sigio); static void usage(void); +static void exit_failure(); /* -------------------------------------------------------------------------- */ /* --- MAIN FUNCTION -------------------------------------------------------- */ @@ -69,20 +69,19 @@ int main(int argc, char ** argv) { static struct sigaction sigact; /* SIGQUIT&SIGINT&SIGTERM signal handling */ - uint8_t data = 0; uint8_t test_buff[BUFF_SIZE]; uint8_t read_buff[BUFF_SIZE]; + uint32_t test_val, read_val; int cycle_number = 0; - int i; - uint16_t size; + int i, x; - /* SPI interfaces */ - const char spidev_path_default[] = LINUXDEV_PATH_DEFAULT; - const char * spidev_path = spidev_path_default; - void *spi_target = NULL; + /* COM interfaces */ + const char com_path_default[] = COM_PATH_DEFAULT; + const char * com_path = com_path_default; + lgw_com_type_t com_type = COM_TYPE_DEFAULT; /* Parse command line options */ - while ((i = getopt(argc, argv, "hd:")) != -1) { + while ((i = getopt(argc, argv, "hd:u")) != -1) { switch (i) { case 'h': usage(); @@ -91,10 +90,14 @@ int main(int argc, char ** argv) case 'd': if (optarg != NULL) { - spidev_path = optarg; + com_path = optarg; } break; + case 'u': + com_type = LGW_COM_USB; + break; + default: printf("ERROR: argument parsing options, use -h option for help\n"); usage(); @@ -110,74 +113,91 @@ int main(int argc, char ** argv) sigaction( SIGINT, &sigact, NULL ); sigaction( SIGTERM, &sigact, NULL ); + /* Board reset */ - if (system("./reset_lgw.sh start") != 0) { - printf("ERROR: failed to reset SX1302, check your reset_lgw.sh script\n"); - exit(EXIT_FAILURE); + if (com_type == LGW_COM_SPI) { + if (system("./reset_lgw.sh start") != 0) { + printf("ERROR: failed to reset SX1302, check your reset_lgw.sh script\n"); + exit(EXIT_FAILURE); + } } - printf("Beginning of test for loragw_spi.c\n"); - i = lgw_spi_open(spidev_path, &spi_target); - if (i != 0) { - printf("ERROR: failed to open SPI device %s\n", spidev_path); - return -1; + /* Connect to the concentrator board */ + x = lgw_connect(com_type, com_path); + if (x != LGW_REG_SUCCESS) { + printf("ERROR: Failed to connect to the concentrator using COM %s\n", com_path); + return EXIT_FAILURE; } - /* normal R/W test */ - /* TODO */ - - /* burst R/W test, small bursts << LGW_BURST_CHUNK */ - /* TODO */ - - /* burst R/W test, large bursts >> LGW_BURST_CHUNK */ - /* TODO */ - - lgw_spi_r(spi_target, LGW_SPI_MUX_TARGET_SX1302, SX1302_REG_COMMON + 6, &data); - printf("SX1302 version: 0x%02X\n", data); + /* Connect to the sx1261 radio */ + x = sx1261_connect(com_type, SX1261_PATH_DEFAULT); + if (x != LGW_REG_SUCCESS) { + printf("ERROR: Failed to connect to the sx1261 using COM %s\n", com_path); + return EXIT_FAILURE; + } - lgw_spi_r(spi_target, LGW_SPI_MUX_TARGET_SX1302, SX1302_REG_AGC_MCU + 0, &data); - lgw_spi_w(spi_target, LGW_SPI_MUX_TARGET_SX1302, SX1302_REG_AGC_MCU + 0, 0x06); /* mcu_clear, host_prog */ + /* Set Radio in Standby mode */ + test_buff[0] = (uint8_t)SX1261_STDBY_RC; + x = sx1261_reg_w(SX1261_SET_STANDBY, test_buff, 1); + if (x != LGW_REG_SUCCESS) { + printf("ERROR(%d): Failed to configure sx1261\n", __LINE__); + exit_failure(); + } + wait_ms(10); - srand(time(NULL)); + test_buff[0] = 0x00; + x = sx1261_reg_r(SX1261_GET_STATUS, test_buff, 1); + if (x != LGW_REG_SUCCESS) { + printf("ERROR(%d): Failed to get sx1261 status\n", __LINE__); + exit_failure(); + } + printf("SX1261: get_status: 0x%02X\n", test_buff[0]); /* databuffer R/W stress test */ while ((quit_sig != 1) && (exit_sig != 1)) { - size = rand() % BUFF_SIZE; - for (i = 0; i < size; ++i) { - test_buff[i] = rand() & 0xFF; - } + test_buff[0] = rand() & 0x7F; + test_buff[1] = rand() & 0xFF; + test_buff[2] = rand() & 0xFF; + test_buff[3] = rand() & 0xFF; + test_val = (test_buff[0] << 24) | (test_buff[1] << 16) | (test_buff[2] << 8) | (test_buff[3] << 0); + sx1261_reg_w(SX1261_SET_RF_FREQUENCY, test_buff, 4); + + read_buff[0] = 0x08; + read_buff[1] = 0x8B; + read_buff[2] = 0x00; + read_buff[3] = 0x00; + read_buff[4] = 0x00; + read_buff[5] = 0x00; + read_buff[6] = 0x00; + sx1261_reg_r(SX1261_READ_REGISTER, read_buff, 7); + read_val = (read_buff[3] << 24) | (read_buff[4] << 16) | (read_buff[5] << 8) | (read_buff[6] << 0); + printf("Cycle %i > ", cycle_number); - lgw_spi_wb(spi_target, LGW_SPI_MUX_TARGET_SX1302, SX1302_AGC_MCU_MEM, test_buff, size); - lgw_spi_rb(spi_target, LGW_SPI_MUX_TARGET_SX1302, SX1302_AGC_MCU_MEM, read_buff, size); - for (i=0; ((i use Linux SPI device driver\n"); - printf(" => default path: " LINUXDEV_PATH_DEFAULT "\n"); + printf(" -d path to access the COM device\n"); + printf(" => default path: " COM_PATH_DEFAULT "\n"); + printf(" -u set COM type as USB (default is SPI)\n"); } /* --- EOF ------------------------------------------------------------------ */ diff --git a/libloragw/tst/test_loragw_counter.c b/libloragw/tst/test_loragw_counter.c index c5f6ba18..110a588d 100644 --- a/libloragw/tst/test_loragw_counter.c +++ b/libloragw/tst/test_loragw_counter.c @@ -38,7 +38,8 @@ License: Revised BSD License, see LICENSE.TXT file include in the project /* -------------------------------------------------------------------------- */ /* --- PRIVATE MACROS ------------------------------------------------------- */ -#define LINUXDEV_PATH_DEFAULT "/dev/spidev0.0" +#define COM_TYPE_DEFAULT LGW_COM_SPI +#define COM_PATH_DEFAULT "/dev/spidev0.0" #define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) #define RAND_RANGE(min, max) (rand() % (max + 1 - min) + min) @@ -67,11 +68,14 @@ static void sig_handler(int sigio) { void usage(void) { //printf("Library version information: %s\n", lgw_version_info()); - printf( "Available options:\n"); - printf( " -h print this help\n"); - printf( " -k Concentrator clock source (Radio A or Radio B) [0..1]\n"); - printf( " -r Radio type (1255, 1257, 1250)\n"); - printf( " -p Test PPS trig counter when set\n" ); + printf("Available options:\n"); + printf(" -h print this help\n"); + printf(" -u set COM type as USB (default is SPI)\n"); + printf(" -d COM path to be used to connect the concentrator\n"); + printf(" => default path (SPI): " COM_PATH_DEFAULT "\n"); + printf(" -k Concentrator clock source (Radio A or Radio B) [0..1]\n"); + printf(" -r Radio type (1255, 1257, 1250)\n"); + printf(" -p Test PPS trig counter when set\n" ); } /* -------------------------------------------------------------------------- */ @@ -80,8 +84,9 @@ void usage(void) { int main(int argc, char **argv) { /* SPI interfaces */ - const char spidev_path_default[] = LINUXDEV_PATH_DEFAULT; - const char * spidev_path = spidev_path_default; + const char com_path_default[] = COM_PATH_DEFAULT; + const char * com_path = com_path_default; + lgw_com_type_t com_type = COM_TYPE_DEFAULT; struct sigaction sigact; /* SIGQUIT&SIGINT&SIGTERM signal handling */ @@ -90,7 +95,7 @@ int main(int argc, char **argv) uint32_t fb = DEFAULT_FREQ_HZ; unsigned int arg_u; uint8_t clocksource = 0; - lgw_radio_type_t radio_type = LGW_RADIO_TYPE_NONE; + lgw_radio_type_t radio_type = LGW_RADIO_TYPE_SX1250; struct lgw_conf_board_s boardconf; struct lgw_conf_rxrf_s rfconf; @@ -114,12 +119,17 @@ int main(int argc, char **argv) const uint8_t channel_rfchain[9] = { 1, 1, 1, 0, 0, 0, 0, 0, 1 }; /* parse command line options */ - while ((i = getopt (argc, argv, "hk:r:p")) != -1) { + while ((i = getopt (argc, argv, "hk:r:pd:u")) != -1) { switch (i) { case 'h': usage(); return -1; break; + case 'd': + if (optarg != NULL) { + com_path = optarg; + } + break; case 'r': /* Radio type */ i = sscanf(optarg, "%u", &arg_u); if ((i != 1) || ((arg_u != 1255) && (arg_u != 1257) && (arg_u != 1250))) { @@ -151,6 +161,9 @@ int main(int argc, char **argv) case 'p': trig_cnt = true; break; + case 'u': + com_type = LGW_COM_USB; + break; default: printf("ERROR: argument parsing\n"); usage(); @@ -168,26 +181,29 @@ int main(int argc, char **argv) printf("===== sx1302 counter test =====\n"); - /* Board reset */ - if (system("./reset_lgw.sh start") != 0) { - printf("ERROR: failed to reset SX1302, check your reset_lgw.sh script\n"); - exit(EXIT_FAILURE); + if (com_type == LGW_COM_SPI) { + /* Board reset */ + if (system("./reset_lgw.sh start") != 0) { + printf("ERROR: failed to reset SX1302, check your reset_lgw.sh script\n"); + exit(EXIT_FAILURE); + } } /* Configure the gateway */ - memset( &boardconf, 0, sizeof boardconf); + memset(&boardconf, 0, sizeof boardconf); boardconf.lorawan_public = true; boardconf.clksrc = clocksource; boardconf.full_duplex = false; - strncpy(boardconf.spidev_path, spidev_path, sizeof boardconf.spidev_path); - boardconf.spidev_path[sizeof boardconf.spidev_path - 1] = '\0'; /* ensure string termination */ + boardconf.com_type = com_type; + strncpy(boardconf.com_path, com_path, sizeof boardconf.com_path); + boardconf.com_path[sizeof boardconf.com_path - 1] = '\0'; /* ensure string termination */ if (lgw_board_setconf(&boardconf) != LGW_HAL_SUCCESS) { printf("ERROR: failed to configure board\n"); return EXIT_FAILURE; } /* set configuration for RF chains */ - memset( &rfconf, 0, sizeof rfconf); + memset(&rfconf, 0, sizeof rfconf); rfconf.enable = true; rfconf.freq_hz = fa; rfconf.type = radio_type; @@ -198,7 +214,7 @@ int main(int argc, char **argv) return EXIT_FAILURE; } - memset( &rfconf, 0, sizeof rfconf); + memset(&rfconf, 0, sizeof rfconf); rfconf.enable = true; rfconf.freq_hz = fb; rfconf.type = radio_type; @@ -247,10 +263,12 @@ int main(int argc, char **argv) return EXIT_FAILURE; } - /* Board reset */ - if (system("./reset_lgw.sh stop") != 0) { - printf("ERROR: failed to reset SX1302, check your reset_lgw.sh script\n"); - exit(EXIT_FAILURE); + if (com_type == LGW_COM_SPI) { + /* Board reset */ + if (system("./reset_lgw.sh stop") != 0) { + printf("ERROR: failed to reset SX1302, check your reset_lgw.sh script\n"); + exit(EXIT_FAILURE); + } } printf("=========== Test End ===========\n"); diff --git a/libloragw/tst/test_loragw_gps.c b/libloragw/tst/test_loragw_gps.c index 5c8900dd..5ce63335 100644 --- a/libloragw/tst/test_loragw_gps.c +++ b/libloragw/tst/test_loragw_gps.c @@ -43,7 +43,8 @@ License: Revised BSD License, see LICENSE.TXT file include in the project /* -------------------------------------------------------------------------- */ /* --- PRIVATE CONSTANTS ---------------------------------------------------- */ -#define LINUXDEV_PATH_DEFAULT "/dev/spidev0.0" +#define COM_TYPE_DEFAULT LGW_COM_SPI +#define COM_PATH_DEFAULT "/dev/spidev0.0" /* -------------------------------------------------------------------------- */ /* --- PRIVATE VARIABLES ---------------------------------------------------- */ @@ -65,10 +66,13 @@ static void gps_process_coords(void); void usage(void) { //printf("Library version information: %s\n", lgw_version_info()); - printf( "Available options:\n"); - printf( " -h print this help\n"); - printf( " -k Concentrator clock source (Radio A or Radio B) [0..1]\n"); - printf( " -r Radio type (1255, 1257, 1250)\n"); + printf("Available options:\n"); + printf(" -h print this help\n"); + printf(" -u set COM type as USB (default is SPI)\n"); + printf(" -d COM path to be used to connect the concentrator\n"); + printf(" => default path (SPI): " COM_PATH_DEFAULT "\n"); + printf(" -k Concentrator clock source (Radio A or Radio B) [0..1]\n"); + printf(" -r Radio type (1255, 1257, 1250)\n"); } static void sig_handler(int sigio) { @@ -170,8 +174,9 @@ static void gps_process_coords(void) { int main(int argc, char **argv) { /* SPI interfaces */ - const char spidev_path_default[] = LINUXDEV_PATH_DEFAULT; - const char * spidev_path = spidev_path_default; + const char com_path_default[] = COM_PATH_DEFAULT; + const char * com_path = com_path_default; + lgw_com_type_t com_type = COM_TYPE_DEFAULT; struct sigaction sigact; /* SIGQUIT&SIGINT&SIGTERM signal handling */ @@ -180,7 +185,7 @@ int main(int argc, char **argv) /* concentrator variables */ uint8_t clocksource = 0; - lgw_radio_type_t radio_type = LGW_RADIO_TYPE_NONE; + lgw_radio_type_t radio_type = LGW_RADIO_TYPE_SX1250; struct lgw_conf_board_s boardconf; struct lgw_conf_rxrf_s rfconf; @@ -193,12 +198,20 @@ int main(int argc, char **argv) enum gps_msg latest_msg; /* keep track of latest NMEA/UBX message parsed */ /* parse command line options */ - while ((i = getopt (argc, argv, "hk:r:")) != -1) { + while ((i = getopt (argc, argv, "hk:r:d:u")) != -1) { switch (i) { case 'h': usage(); return -1; break; + case 'd': + if (optarg != NULL) { + com_path = optarg; + } + break; + case 'u': + com_type = LGW_COM_USB; + break; case 'r': /* Radio type */ i = sscanf(optarg, "%u", &arg_u); if ((i != 1) || ((arg_u != 1255) && (arg_u != 1257) && (arg_u != 1250))) { @@ -234,13 +247,6 @@ int main(int argc, char **argv) } } - /* Check arguments */ - if (radio_type == LGW_RADIO_TYPE_NONE) { - printf("ERROR: radio type must be specified\n"); - usage(); - exit(EXIT_FAILURE); - } - /* configure signal handling */ sigemptyset(&sigact.sa_mask); sigact.sa_flags = 0; @@ -253,10 +259,12 @@ int main(int argc, char **argv) printf("Beginning of test for loragw_gps.c\n"); printf("*** Library version information ***\n%s\n***\n", lgw_version_info()); - /* Board reset */ - if (system("./reset_lgw.sh start") != 0) { - printf("ERROR: failed to reset SX1302, check your reset_lgw.sh script\n"); - exit(EXIT_FAILURE); + if (com_type == LGW_COM_SPI) { + /* Board reset */ + if (system("./reset_lgw.sh start") != 0) { + printf("ERROR: failed to reset SX1302, check your reset_lgw.sh script\n"); + exit(EXIT_FAILURE); + } } /* Open and configure GPS */ @@ -272,8 +280,9 @@ int main(int argc, char **argv) boardconf.lorawan_public = true; boardconf.clksrc = clocksource; boardconf.full_duplex = false; - strncpy(boardconf.spidev_path, spidev_path, sizeof boardconf.spidev_path); - boardconf.spidev_path[sizeof boardconf.spidev_path - 1] = '\0'; /* ensure string termination */ + boardconf.com_type = com_type; + strncpy(boardconf.com_path, com_path, sizeof boardconf.com_path); + boardconf.com_path[sizeof boardconf.com_path - 1] = '\0'; /* ensure string termination */ if (lgw_board_setconf(&boardconf) != LGW_HAL_SUCCESS) { printf("ERROR: failed to configure board\n"); return EXIT_FAILURE; @@ -405,10 +414,12 @@ int main(int argc, char **argv) lgw_stop(); } - /* Board reset */ - if (system("./reset_lgw.sh stop") != 0) { - printf("ERROR: failed to reset SX1302, check your reset_lgw.sh script\n"); - exit(EXIT_FAILURE); + if (com_type == LGW_COM_SPI) { + /* Board reset */ + if (system("./reset_lgw.sh stop") != 0) { + printf("ERROR: failed to reset SX1302, check your reset_lgw.sh script\n"); + exit(EXIT_FAILURE); + } } printf("\nEnd of test for loragw_gps.c\n"); diff --git a/libloragw/tst/test_loragw_hal_rx.c b/libloragw/tst/test_loragw_hal_rx.c index edd2dd84..65c896d3 100644 --- a/libloragw/tst/test_loragw_hal_rx.c +++ b/libloragw/tst/test_loragw_hal_rx.c @@ -30,6 +30,7 @@ License: Revised BSD License, see LICENSE.TXT file include in the project #include #include #include +#include #include "loragw_hal.h" #include "loragw_reg.h" @@ -38,7 +39,8 @@ License: Revised BSD License, see LICENSE.TXT file include in the project /* -------------------------------------------------------------------------- */ /* --- PRIVATE MACROS ------------------------------------------------------- */ -#define LINUXDEV_PATH_DEFAULT "/dev/spidev0.0" +#define COM_TYPE_DEFAULT LGW_COM_SPI +#define COM_PATH_DEFAULT "/dev/spidev0.0" #define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) #define RAND_RANGE(min, max) (rand() % (max + 1 - min) + min) @@ -66,9 +68,12 @@ static void sig_handler(int sigio) { } void usage(void) { - //printf("Library version information: %s\n", lgw_version_info()); + printf("Library version information: %s\n", lgw_version_info()); printf("Available options:\n"); printf(" -h print this help\n"); + printf(" -u set COM type as USB (default is SPI)\n"); + printf(" -d COM path to be used to connect the concentrator\n"); + printf(" => default path: " COM_PATH_DEFAULT "\n"); printf(" -k Concentrator clock source (Radio A or Radio B) [0..1]\n"); printf(" -r Radio type (1255, 1257, 1250)\n"); printf(" -a Radio A RX frequency in MHz\n"); @@ -78,6 +83,8 @@ void usage(void) { printf(" -z Size of the RX packet array to be passed to lgw_receive()\n"); printf(" -m Channel frequency plan mode [0:LoRaWAN-like, 1:Same frequency for all channels (-400000Hz on RF0)]\n"); printf(" -j Set radio in single input mode (SX1250 only)\n"); + printf( "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" ); + printf(" --fdd Enable Full-Duplex mode (CN490 reference design)\n"); } /* -------------------------------------------------------------------------- */ @@ -86,8 +93,9 @@ void usage(void) { int main(int argc, char **argv) { /* SPI interfaces */ - const char spidev_path_default[] = LINUXDEV_PATH_DEFAULT; - const char * spidev_path = spidev_path_default; + const char com_path_default[] = COM_PATH_DEFAULT; + const char * com_path = com_path_default; + lgw_com_type_t com_type = COM_TYPE_DEFAULT; struct sigaction sigact; /* SIGQUIT&SIGINT&SIGTERM signal handling */ @@ -101,6 +109,7 @@ int main(int argc, char **argv) uint8_t max_rx_pkt = 16; bool single_input_mode = false; float rssi_offset = 0.0; + bool full_duplex = false; struct lgw_conf_board_s boardconf; struct lgw_conf_rxrf_s rfconf; @@ -118,9 +127,9 @@ int main(int argc, char **argv) -400000, -200000, 0, - -400000, - -200000, - -400000 /* lora service */ + 200000, + 400000, + -200000 /* lora service */ }; const int32_t channel_if_mode1[9] = { @@ -139,13 +148,28 @@ int main(int argc, char **argv) const uint8_t channel_rfchain_mode1[9] = { 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + /* Parameter parsing */ + int option_index = 0; + static struct option long_options[] = { + {"fdd", no_argument, 0, 0}, + {0, 0, 0, 0} + }; + /* parse command line options */ - while ((i = getopt (argc, argv, "hja:b:k:r:n:z:m:o:")) != -1) { + while ((i = getopt_long(argc, argv, "hja:b:k:r:n:z:m:o:d:u", long_options, &option_index)) != -1) { switch (i) { case 'h': usage(); return -1; break; + case 'd': /* COM path */ + if (optarg != NULL) { + com_path = optarg; + } + break; + case 'u': /* Configure USB connection type */ + com_type = LGW_COM_USB; + break; case 'r': /* Radio type */ i = sscanf(optarg, "%u", &arg_u); if ((i != 1) || ((arg_u != 1255) && (arg_u != 1257) && (arg_u != 1250))) { @@ -225,12 +249,20 @@ int main(int argc, char **argv) case 'o': /* RSSI offset in dB */ i = sscanf(optarg, "%lf", &arg_d); if (i != 1) { - printf("ERROR: argument parsing of -f argument. Use -h to print help\n"); + printf("ERROR: argument parsing of -o argument. Use -h to print help\n"); return EXIT_FAILURE; } else { rssi_offset = (float)arg_d; } break; + case 0: + if (strcmp(long_options[option_index].name, "fdd") == 0) { + full_duplex = true; + } else { + printf("ERROR: argument parsing options. Use -h to print help\n"); + return EXIT_FAILURE; + } + break; default: printf("ERROR: argument parsing\n"); usage(); @@ -252,9 +284,10 @@ int main(int argc, char **argv) memset( &boardconf, 0, sizeof boardconf); boardconf.lorawan_public = true; boardconf.clksrc = clocksource; - boardconf.full_duplex = false; - strncpy(boardconf.spidev_path, spidev_path, sizeof boardconf.spidev_path); - boardconf.spidev_path[sizeof boardconf.spidev_path - 1] = '\0'; /* ensure string termination */ + boardconf.full_duplex = full_duplex; + boardconf.com_type = com_type; + strncpy(boardconf.com_path, com_path, sizeof boardconf.com_path); + boardconf.com_path[sizeof boardconf.com_path - 1] = '\0'; /* ensure string termination */ if (lgw_board_setconf(&boardconf) != LGW_HAL_SUCCESS) { printf("ERROR: failed to configure board\n"); return EXIT_FAILURE; @@ -328,10 +361,12 @@ int main(int argc, char **argv) { cnt_loop += 1; - /* Board reset */ - if (system("./reset_lgw.sh start") != 0) { - printf("ERROR: failed to reset SX1302, check your reset_lgw.sh script\n"); - exit(EXIT_FAILURE); + if (com_type == LGW_COM_SPI) { + /* Board reset */ + if (system("./reset_lgw.sh start") != 0) { + printf("ERROR: failed to reset SX1302, check your reset_lgw.sh script\n"); + exit(EXIT_FAILURE); + } } /* connect, configure and start the LoRa concentrator */ @@ -386,10 +421,12 @@ int main(int argc, char **argv) return EXIT_FAILURE; } - /* Board reset */ - if (system("./reset_lgw.sh stop") != 0) { - printf("ERROR: failed to reset SX1302, check your reset_lgw.sh script\n"); - exit(EXIT_FAILURE); + if (com_type == LGW_COM_SPI) { + /* Board reset */ + if (system("./reset_lgw.sh stop") != 0) { + printf("ERROR: failed to reset SX1302, check your reset_lgw.sh script\n"); + exit(EXIT_FAILURE); + } } } diff --git a/libloragw/tst/test_loragw_hal_tx.c b/libloragw/tst/test_loragw_hal_tx.c index 8489c664..f9f2757e 100644 --- a/libloragw/tst/test_loragw_hal_tx.c +++ b/libloragw/tst/test_loragw_hal_tx.c @@ -44,7 +44,8 @@ License: Revised BSD License, see LICENSE.TXT file include in the project /* -------------------------------------------------------------------------- */ /* --- PRIVATE CONSTANTS ---------------------------------------------------- */ -#define LINUXDEV_PATH_DEFAULT "/dev/spidev0.0" +#define COM_TYPE_DEFAULT LGW_COM_SPI +#define COM_PATH_DEFAULT "/dev/spidev0.0" #define DEFAULT_CLK_SRC 0 #define DEFAULT_FREQ_HZ 868500000U @@ -64,6 +65,9 @@ void usage(void) { //printf("Library version information: %s\n", lgw_version_info()); printf("Available options:\n"); printf(" -h print this help\n"); + printf(" -u Set COM type as USB (default is SPI)\n"); + printf(" -d COM path to be used to connect the concentrator\n"); + printf(" => default path: " COM_PATH_DEFAULT "\n"); printf(" -k Concentrator clock source (Radio A or Radio B) [0..1]\n"); printf(" -c RF chain to be used for TX (Radio A or Radio B) [0..1]\n"); printf(" -r Radio type (1255, 1257, 1250)\n"); @@ -73,8 +77,6 @@ void usage(void) { printf(" -s LoRa datarate 0:random, [5..12]\n"); printf(" -b LoRa bandwidth in khz 0:random, [125, 250, 500]\n"); printf(" -l FSK/LoRa preamble length, [6..65535]\n"); - printf(" -d FSK frequency deviation in kHz [1:250]\n"); - printf(" -q FSK bitrate in kbps [0.5:250]\n"); printf(" -n Number of packets to be sent\n"); printf(" -z size of packets to be sent 0:random, [9..255]\n"); printf(" -t TX mode timestamped with delay in ms. If delay is 0, TX mode GPS trigger\n"); @@ -82,6 +84,9 @@ void usage(void) { printf(" -i Send LoRa packet using inverted modulation polarity\n"); printf(" -j Set radio in single input mode (SX1250 only)\n"); printf( "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" ); + printf(" --fdev FSK frequency deviation in kHz [1:250]\n"); + printf(" --br FSK bitrate in kbps [0.5:250]\n"); + printf( "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" ); printf(" --pa PA gain SX125x:[0..3], SX1250:[0,1]\n"); printf(" --dig sx1302 digital gain for sx125x [0..3]\n"); printf(" --dac sx125x DAC gain [0..3]\n"); @@ -91,6 +96,8 @@ void usage(void) { printf(" --nhdr Send LoRa packet with implicit header\n"); printf( "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" ); printf(" --loop Number of loops for HAL start/stop (HAL unitary test)\n"); + printf( "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" ); + printf(" --fdd Enable Full-Duplex mode (CN490 reference design)\n"); } /* handle signals */ @@ -133,6 +140,7 @@ int main(int argc, char **argv) bool invert_pol = false; bool no_header = false; bool single_input_mode = false; + bool full_duplex = false; struct lgw_conf_board_s boardconf; struct lgw_conf_rxrf_s rfconf; @@ -144,8 +152,9 @@ int main(int argc, char **argv) bool trig_delay = false; /* SPI interfaces */ - const char spidev_path_default[] = LINUXDEV_PATH_DEFAULT; - const char * spidev_path = spidev_path_default; + const char com_path_default[] = COM_PATH_DEFAULT; + const char * com_path = com_path_default; + lgw_com_type_t com_type = COM_TYPE_DEFAULT; static struct sigaction sigact; /* SIGQUIT&SIGINT&SIGTERM signal handling */ @@ -156,6 +165,8 @@ int main(int argc, char **argv) /* Parameter parsing */ int option_index = 0; static struct option long_options[] = { + {"fdev", required_argument, 0, 0}, + {"br", required_argument, 0, 0}, {"pa", required_argument, 0, 0}, {"dac", required_argument, 0, 0}, {"dig", required_argument, 0, 0}, @@ -163,16 +174,25 @@ int main(int argc, char **argv) {"pwid", required_argument, 0, 0}, {"loop", required_argument, 0, 0}, {"nhdr", no_argument, 0, 0}, + {"fdd", no_argument, 0, 0}, {0, 0, 0, 0} }; /* parse command line options */ - while ((i = getopt_long (argc, argv, "hjif:s:b:n:z:p:k:r:c:l:t:m:o:q:d:", long_options, &option_index)) != -1) { + while ((i = getopt_long (argc, argv, "hjif:s:b:n:z:p:k:r:c:l:t:m:o:ud:", long_options, &option_index)) != -1) { switch (i) { case 'h': usage(); return -1; break; + case 'u': + com_type = LGW_COM_USB; + break; + case 'd': /* COM path */ + if (optarg != NULL) { + com_path = optarg; + } + break; case 'i': /* Send packet using inverted modulation polarity */ invert_pol = true; break; @@ -225,24 +245,6 @@ int main(int argc, char **argv) freq_offset = (int32_t)arg_i; } break; - case 'd': /* FSK frequency deviation */ - i = sscanf(optarg, "%u", &arg_u); - if ((i != 1) || (arg_u < 1) || (arg_u > 250)) { - printf("ERROR: invalid FSK frequency deviation\n"); - return EXIT_FAILURE; - } else { - fdev_khz = (uint8_t)arg_u; - } - break; - case 'q': /* FSK bitrate */ - i = sscanf(optarg, "%f", &xf); - if ((i != 1) || (xf < 0.5) || (xf > 250)) { - printf("ERROR: invalid FSK bitrate\n"); - return EXIT_FAILURE; - } else { - br_kbps = xf; - } - break; case 't': /* Trigger delay in ms */ i = sscanf(optarg, "%u", &arg_u); if (i != 1) { @@ -328,7 +330,23 @@ int main(int argc, char **argv) } break; case 0: - if (strcmp(long_options[option_index].name, "pa") == 0) { + if (strcmp(long_options[option_index].name, "fdev") == 0) { + i = sscanf(optarg, "%u", &arg_u); + if ((i != 1) || (arg_u < 1) || (arg_u > 250)) { + printf("ERROR: invalid FSK frequency deviation\n"); + return EXIT_FAILURE; + } else { + fdev_khz = (uint8_t)arg_u; + } + } else if (strcmp(long_options[option_index].name, "br") == 0) { + i = sscanf(optarg, "%f", &xf); + if ((i != 1) || (xf < 0.5) || (xf > 250)) { + printf("ERROR: invalid FSK bitrate\n"); + return EXIT_FAILURE; + } else { + br_kbps = xf; + } + } else if (strcmp(long_options[option_index].name, "pa") == 0) { i = sscanf(optarg, "%u", &arg_u); if ((i != 1) || (arg_u > 3)) { printf("ERROR: argument parsing of --pa argument. Use -h to print help\n"); @@ -385,6 +403,8 @@ int main(int argc, char **argv) } } else if (strcmp(long_options[option_index].name, "nhdr") == 0) { no_header = true; + } else if (strcmp(long_options[option_index].name, "fdd") == 0) { + full_duplex = true; } else { printf("ERROR: argument parsing options. Use -h to print help\n"); return EXIT_FAILURE; @@ -419,9 +439,10 @@ int main(int argc, char **argv) memset( &boardconf, 0, sizeof boardconf); boardconf.lorawan_public = true; boardconf.clksrc = clocksource; - boardconf.full_duplex = false; - strncpy(boardconf.spidev_path, spidev_path, sizeof boardconf.spidev_path); - boardconf.spidev_path[sizeof boardconf.spidev_path - 1] = '\0'; /* ensure string termination */ + boardconf.full_duplex = full_duplex; + boardconf.com_type = com_type; + strncpy(boardconf.com_path, com_path, sizeof boardconf.com_path); + boardconf.com_path[sizeof boardconf.com_path - 1] = '\0'; /* ensure string termination */ if (lgw_board_setconf(&boardconf) != LGW_HAL_SUCCESS) { printf("ERROR: failed to configure board\n"); return EXIT_FAILURE; @@ -457,10 +478,12 @@ int main(int argc, char **argv) } for (cnt_loop = 0; cnt_loop < nb_loop; cnt_loop++) { + if (com_type == LGW_COM_SPI) { /* Board reset */ - if (system("./reset_lgw.sh start") != 0) { - printf("ERROR: failed to reset SX1302, check your reset_lgw.sh script\n"); - exit(EXIT_FAILURE); + if (system("./reset_lgw.sh start") != 0) { + printf("ERROR: failed to reset SX1302, check your reset_lgw.sh script\n"); + exit(EXIT_FAILURE); + } } /* connect, configure and start the LoRa concentrator */ @@ -553,7 +576,7 @@ int main(int argc, char **argv) x = lgw_send(&pkt); if (x != 0) { printf("ERROR: failed to send packet\n"); - return EXIT_FAILURE; + break; } /* wait for packet to finish sending */ do { @@ -573,13 +596,14 @@ int main(int argc, char **argv) x = lgw_stop(); if (x != 0) { printf("ERROR: failed to stop the gateway\n"); - return EXIT_FAILURE; } - /* Board reset */ - if (system("./reset_lgw.sh stop") != 0) { - printf("ERROR: failed to reset SX1302, check your reset_lgw.sh script\n"); - exit(EXIT_FAILURE); + if (com_type == LGW_COM_SPI) { + /* Board reset */ + if (system("./reset_lgw.sh stop") != 0) { + printf("ERROR: failed to reset SX1302, check your reset_lgw.sh script\n"); + exit(EXIT_FAILURE); + } } } diff --git a/libloragw/tst/test_loragw_reg.c b/libloragw/tst/test_loragw_reg.c index b5e32b90..d9e40b09 100644 --- a/libloragw/tst/test_loragw_reg.c +++ b/libloragw/tst/test_loragw_reg.c @@ -29,6 +29,7 @@ License: Revised BSD License, see LICENSE.TXT file include in the project #include /* getopt, access */ #include +#include "loragw_com.h" #include "loragw_reg.h" #include "loragw_aux.h" #include "loragw_hal.h" @@ -36,7 +37,8 @@ License: Revised BSD License, see LICENSE.TXT file include in the project /* -------------------------------------------------------------------------- */ /* --- PRIVATE MACROS ------------------------------------------------------- */ -#define LINUXDEV_PATH_DEFAULT "/dev/spidev0.0" +#define COM_TYPE_DEFAULT LGW_COM_SPI +#define COM_PATH_DEFAULT "/dev/spidev0.0" /* -------------------------------------------------------------------------- */ /* --- PRIVATE CONSTANTS ---------------------------------------------------- */ @@ -62,20 +64,25 @@ int main(int argc, char ** argv) uint8_t reg_max; /* SPI interfaces */ - const char spidev_path_default[] = LINUXDEV_PATH_DEFAULT; - const char * spidev_path = spidev_path_default; + const char com_path_default[] = COM_PATH_DEFAULT; + const char * com_path = com_path_default; + lgw_com_type_t com_type = COM_TYPE_DEFAULT; /* Parse command line options */ - while ((i = getopt(argc, argv, "hd:")) != -1) { + while ((i = getopt(argc, argv, "hd:u")) != -1) { switch (i) { case 'h': usage(); return EXIT_SUCCESS; break; + case 'u': /* Configure USB connection type */ + com_type = LGW_COM_USB; + break; + case 'd': if (optarg != NULL) { - spidev_path = optarg; + com_path = optarg; } break; @@ -86,13 +93,15 @@ int main(int argc, char ** argv) } } - /* Board reset */ - if (system("./reset_lgw.sh start") != 0) { - printf("ERROR: failed to reset SX1302, check your reset_lgw.sh script\n"); - exit(EXIT_FAILURE); + if (com_type == LGW_COM_SPI) { + /* Board reset */ + if (system("./reset_lgw.sh start") != 0) { + printf("ERROR: failed to reset SX1302, check your reset_lgw.sh script\n"); + exit(EXIT_FAILURE); + } } - x = lgw_connect(spidev_path); + x = lgw_connect(com_type, com_path); if (x != LGW_REG_SUCCESS) { printf("ERROR: failed to connect\n"); return -1; @@ -179,10 +188,12 @@ int main(int argc, char ** argv) return -1; } - /* Board reset */ - if (system("./reset_lgw.sh stop") != 0) { - printf("ERROR: failed to reset SX1302, check your reset_lgw.sh script\n"); - exit(EXIT_FAILURE); + if (com_type == LGW_COM_SPI) { + /* Board reset */ + if (system("./reset_lgw.sh stop") != 0) { + printf("ERROR: failed to reset SX1302, check your reset_lgw.sh script\n"); + exit(EXIT_FAILURE); + } } return 0; @@ -195,9 +206,10 @@ static void usage(void) { printf("~~~ Library version string~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"); printf(" %s\n", lgw_version_info()); printf("~~~ Available options ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"); - printf(" -h print this help\n"); - printf(" -d use Linux SPI device driver\n"); - printf(" => default path: " LINUXDEV_PATH_DEFAULT "\n"); + printf(" -h print this help\n"); + printf(" -u set COM type as USB (default is SPI)\n"); + printf(" -d COM path to be used to connect the concentrator\n"); + printf(" => default path: " COM_PATH_DEFAULT "\n"); } diff --git a/libloragw/tst/test_loragw_sx1261_rssi.c b/libloragw/tst/test_loragw_sx1261_rssi.c new file mode 100644 index 00000000..4be6748b --- /dev/null +++ b/libloragw/tst/test_loragw_sx1261_rssi.c @@ -0,0 +1,361 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2020 Semtech + +Description: + Set the sx1261 radio of the Corecell in RX continuous mode, and measure RSSI + +License: Revised BSD License, see LICENSE.TXT file include in the project +*/ + + +/* -------------------------------------------------------------------------- */ +/* --- DEPENDANCIES --------------------------------------------------------- */ + +/* Fix an issue between POSIX and C99 */ +#if __STDC_VERSION__ >= 199901L + #define _XOPEN_SOURCE 600 +#else + #define _XOPEN_SOURCE 500 +#endif + +#include +#include +#include +#include +#include /* sigaction */ +#include /* getopt, access */ + +#include "loragw_aux.h" +#include "loragw_hal.h" +#include "loragw_reg.h" +#include "loragw_com.h" +#include "loragw_sx1261.h" + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE MACROS ------------------------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE CONSTANTS ---------------------------------------------------- */ + +#define BUFF_SIZE 16 + +#define COM_TYPE_DEFAULT LGW_COM_SPI +#define COM_PATH_DEFAULT "/dev/spidev0.0" +#define SX1261_PATH_DEFAULT "/dev/spidev0.1" + +#define DEFAULT_FREQ_HZ 868500000U + +/* -------------------------------------------------------------------------- */ +/* --- GLOBAL VARIABLES ----------------------------------------------------- */ + +/* Signal handling variables */ +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 */ + +/* -------------------------------------------------------------------------- */ +/* --- SUBFUNCTIONS DECLARATION --------------------------------------------- */ + +static void sig_handler(int sigio); +static void usage(void); +static void exit_failure(); + +/* -------------------------------------------------------------------------- */ +/* --- MAIN FUNCTION -------------------------------------------------------- */ + +int main(int argc, char ** argv) +{ + static struct sigaction sigact; /* SIGQUIT&SIGINT&SIGTERM signal handling */ + + int i, x; + double arg_d = 0.0; + unsigned int arg_u; + + uint8_t buff[BUFF_SIZE]; + uint32_t freq_hz = 0; + float rssi_inst; + uint32_t fa = DEFAULT_FREQ_HZ; + uint32_t fb = DEFAULT_FREQ_HZ; + uint8_t clocksource = 0; + lgw_radio_type_t radio_type = LGW_RADIO_TYPE_SX1250; + + /* COM interfaces */ + const char com_path_default[] = COM_PATH_DEFAULT; + const char * com_path = com_path_default; + lgw_com_type_t com_type = COM_TYPE_DEFAULT; + const char sx1261_path_default[] = SX1261_PATH_DEFAULT; + const char * sx1261_path = sx1261_path_default; + + struct lgw_conf_board_s boardconf; + struct lgw_conf_rxrf_s rfconf; + + /* Parse command line options */ + while ((i = getopt(argc, argv, "hd:uf:D:k:r:a:b:")) != -1) { + switch (i) { + case 'h': + usage(); + return EXIT_SUCCESS; + break; + + case 'd': + if (optarg != NULL) { + com_path = optarg; + } + break; + + case 'D': + if (optarg != NULL) { + sx1261_path = optarg; + } + break; + + case 'u': + com_type = LGW_COM_USB; + break; + + case 'r': /* Radio type */ + i = sscanf(optarg, "%u", &arg_u); + if ((i != 1) || ((arg_u != 1255) && (arg_u != 1257) && (arg_u != 1250))) { + printf("ERROR: argument parsing of -r argument. Use -h to print help\n"); + return EXIT_FAILURE; + } else { + switch (arg_u) { + case 1255: + radio_type = LGW_RADIO_TYPE_SX1255; + break; + case 1257: + radio_type = LGW_RADIO_TYPE_SX1257; + break; + default: /* 1250 */ + radio_type = LGW_RADIO_TYPE_SX1250; + break; + } + } + break; + + case 'k': /* Clock Source */ + i = sscanf(optarg, "%u", &arg_u); + if ((i != 1) || (arg_u > 1)) { + printf("ERROR: argument parsing of -k argument. Use -h to print help\n"); + return EXIT_FAILURE; + } else { + clocksource = (uint8_t)arg_u; + } + break; + + case 'a': /* Radio A RX frequency in MHz */ + i = sscanf(optarg, "%lf", &arg_d); + if (i != 1) { + printf("ERROR: argument parsing of -f argument. Use -h to print help\n"); + return EXIT_FAILURE; + } else { + fa = (uint32_t)((arg_d*1e6) + 0.5); /* .5 Hz offset to get rounding instead of truncating */ + } + break; + + case 'b': /* Radio B RX frequency in MHz */ + i = sscanf(optarg, "%lf", &arg_d); + if (i != 1) { + printf("ERROR: argument parsing of -f argument. Use -h to print help\n"); + return EXIT_FAILURE; + } else { + fb = (uint32_t)((arg_d*1e6) + 0.5); /* .5 Hz offset to get rounding instead of truncating */ + } + break; + + case 'f': /* SX1261 Radio RX frequency in MHz */ + i = sscanf(optarg, "%lf", &arg_d); + if (i != 1) { + printf("ERROR: argument parsing of -f argument. Use -h to print help\n"); + return EXIT_FAILURE; + } else { + freq_hz = (uint32_t)((arg_d*1e6) + 0.5); /* .5 Hz offset to get rounding instead of truncating */ + } + break; + + default: + printf("ERROR: argument parsing options, use -h option for help\n"); + usage(); + return EXIT_FAILURE; + } + } + + /* Check mandatory params */ + if (freq_hz == 0) { + printf("ERROR: frequency must me set\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 ); + + /* Board reset */ + if (com_type == LGW_COM_SPI) { + if (system("./reset_lgw.sh start") != 0) { + printf("ERROR: failed to reset SX1302, check your reset_lgw.sh script\n"); + exit(EXIT_FAILURE); + } + } + + /* Configure the gateway */ + memset( &boardconf, 0, sizeof boardconf); + boardconf.lorawan_public = true; + boardconf.clksrc = clocksource; + boardconf.full_duplex = false; + boardconf.com_type = com_type; + strncpy(boardconf.com_path, com_path, sizeof boardconf.com_path); + boardconf.com_path[sizeof boardconf.com_path - 1] = '\0'; /* ensure string termination */ + if (lgw_board_setconf(&boardconf) != LGW_HAL_SUCCESS) { + printf("ERROR: failed to configure board\n"); + return EXIT_FAILURE; + } + + /* set configuration for RF chains */ + memset( &rfconf, 0, sizeof rfconf); + rfconf.enable = true; /* must be enabled to proper RF matching */ + rfconf.freq_hz = fa; + rfconf.type = radio_type; + rfconf.rssi_offset = 0.0; + rfconf.tx_enable = false; + rfconf.single_input_mode = false; + if (lgw_rxrf_setconf(0, &rfconf) != LGW_HAL_SUCCESS) { + printf("ERROR: failed to configure rxrf 0\n"); + return EXIT_FAILURE; + } + + memset( &rfconf, 0, sizeof rfconf); + rfconf.enable = true; /* must be enabled to proper RF matching */ + rfconf.freq_hz = fb; + rfconf.type = radio_type; + rfconf.rssi_offset = 0.0; + rfconf.tx_enable = false; + rfconf.single_input_mode = false; + if (lgw_rxrf_setconf(1, &rfconf) != LGW_HAL_SUCCESS) { + printf("ERROR: failed to configure rxrf 1\n"); + return EXIT_FAILURE; + } + + /* Connect to the concentrator board */ + x = lgw_start(); + if (x != LGW_REG_SUCCESS) { + printf("ERROR: Failed to connect to the concentrator using COM %s\n", com_path); + return EXIT_FAILURE; + } + + /* Connect to the sx1261 radio */ + x = sx1261_connect(com_type, sx1261_path); + if (x != LGW_REG_SUCCESS) { + printf("ERROR: Failed to connect to the sx1261 using COM %s\n", com_path); + return EXIT_FAILURE; + } + + x = sx1261_calibrate(freq_hz); + if (x != LGW_REG_SUCCESS) { + printf("ERROR: Failed to calibrate the sx1261\n"); + exit_failure(); + } + + x = sx1261_setup(); + if (x != LGW_REG_SUCCESS) { + printf("ERROR: Failed to setup the sx1261\n"); + exit_failure(); + } + + x = sx1261_set_rx_params(freq_hz, BW_125KHZ); + if (x != LGW_REG_SUCCESS) { + printf("ERROR: Failed to set RX params\n"); + exit_failure(); + } + + /* databuffer R/W stress test */ + while ((quit_sig != 1) && (exit_sig != 1)) { + buff[0] = 0x00; + buff[1] = 0x00; + sx1261_reg_r(SX1261_GET_RSSI_INST, buff, 2); + + rssi_inst = -((float)buff[1] / 2); + + printf("\rSX1261 RSSI at %uHz: %f dBm", freq_hz, rssi_inst); + fflush(stdout); + + wait_ms(100); + } + printf("\n"); + + /* Disconnect from the sx1261 radio */ + x = sx1261_disconnect(); + if (x != LGW_REG_SUCCESS) { + printf("ERROR: Failed to disconnect from the SX1261 radio\n"); + } + + /* Disconnect from the concentrator board */ + x = lgw_stop(); + if (x != LGW_REG_SUCCESS) { + printf("ERROR: Failed to disconnect from the concentrator\n"); + } + + printf("Disconnected\n"); + + if (com_type == LGW_COM_SPI) { + /* Board reset */ + if (system("./reset_lgw.sh stop") != 0) { + printf("ERROR: failed to reset SX1302, check your reset_lgw.sh script\n"); + exit(EXIT_FAILURE); + } + } + + return 0; +} + +/* -------------------------------------------------------------------------- */ +/* --- SUBFUNCTIONS DEFINITION ---------------------------------------------- */ + +static void sig_handler(int sigio) { + if (sigio == SIGQUIT) { + quit_sig = 1; + } else if((sigio == SIGINT) || (sigio == SIGTERM)) { + exit_sig = 1; + } +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +static void exit_failure() { + sx1261_disconnect(); + lgw_disconnect(); + + printf("End of test for loragw_spi_sx1261.c\n"); + + exit(EXIT_FAILURE); +} + +/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + +static void usage(void) { + printf("~~~ Library version string~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"); + printf(" %s\n", lgw_version_info()); + printf("~~~ Available options ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"); + printf(" -h print this help\n"); + printf(" -u set COM type as USB (default is SPI)\n"); + printf(" -d path to access the main COM device\n"); + printf(" => default path: " COM_PATH_DEFAULT "\n"); + printf(" -D [path] Path to the SX1261 SPI interface (not used for USB)\n"); + printf(" => default path: " SX1261_PATH_DEFAULT "\n"); + printf(" -k Concentrator clock source (Radio A or Radio B) [0..1]\n"); + printf(" -r Radio type (1255, 1257, 1250)\n"); + printf(" -a Radio A RX frequency in MHz\n"); + printf(" -b Radio B RX frequency in MHz\n"); + printf(" -f SX1261 frequency for RSSI scanning, in MHz\n"); +} + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/libloragw/tst/test_loragw_toa.c b/libloragw/tst/test_loragw_toa.c new file mode 100644 index 00000000..84759f2e --- /dev/null +++ b/libloragw/tst/test_loragw_toa.c @@ -0,0 +1,206 @@ +/* + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2020 Semtech + +Description: + Utility to compute Time on Air of a LoRa packet + +License: Revised BSD License, see LICENSE.TXT file include in the project +*/ + + +/* -------------------------------------------------------------------------- */ +/* --- 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 */ +#include /* EXIT_FAILURE */ +#include /* getopt_long */ +#include /* strcmp */ + +#include "loragw_hal.h" + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE MACROS ------------------------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE CONSTANTS ---------------------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE VARIABLES ---------------------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE FUNCTIONS DECLARATION ---------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE FUNCTIONS DEFINITION ----------------------------------------- */ + +/* 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(" -s LoRa datarate [5..12]\n"); + printf(" -b LoRa bandwidth in khz [125, 250, 500]\n"); + printf(" -l LoRa preamble length, [6..65535]\n"); + printf(" -c LoRa coding rate [1=4/5 2=4/6 3=4/7 4=4/8]\n"); + printf(" -z Payload length [0..255]\n"); + printf(" -i Implicit header (no header)\n"); + printf(" -r CRC enabled\n"); +} + +/* -------------------------------------------------------------------------- */ +/* --- MAIN FUNCTION -------------------------------------------------------- */ + +int main(int argc, char **argv) { + int i; + unsigned int arg_u; + + struct lgw_pkt_tx_s pkt; + uint32_t toa_u; + + /* mandatory params to be set by user */ + bool sf = false; + bool bw = false; + bool preamb = false; + bool cr = false; + bool sz = false; + + /* Parameter parsing */ + int option_index = 0; + static struct option long_options[] = { + {0, 0, 0, 0} + }; + + memset(&pkt, 0, sizeof pkt); + pkt.no_crc = true; + pkt.modulation = MOD_LORA; + + /* parse command line options */ + while ((i = getopt_long (argc, argv, "hirs:b:z:l:c:", long_options, &option_index)) != -1) { + switch (i) { + case 'h': + usage(); + return -1; + break; + case 'i': + pkt.no_header = true; + break; + case 'r': + pkt.no_crc = false; + break; + case 'l': + preamb = true; /* param set */ + i = sscanf(optarg, "%u", &arg_u); + if ((i != 1) || (arg_u > 65535)) { + printf("ERROR: argument parsing of -l argument. Use -h to print help\n"); + return EXIT_FAILURE; + } else { + pkt.preamble = (uint16_t)arg_u; + } + break; + case 's': + sf = true; /* param set */ + i = sscanf(optarg, "%u", &arg_u); + if ((i != 1) || (arg_u < 5) || (arg_u > 12)) { + printf("ERROR: argument parsing of -s argument. Use -h to print help\n"); + return EXIT_FAILURE; + } else { + pkt.datarate = arg_u; + } + break; + case 'b': + bw = true; /* param set */ + i = sscanf(optarg, "%u", &arg_u); + if (i != 1) { + printf("ERROR: argument parsing of -b argument. Use -h to print help\n"); + return EXIT_FAILURE; + } else { + switch (arg_u) { + case 125: + pkt.bandwidth = BW_125KHZ; + break; + case 250: + pkt.bandwidth = BW_250KHZ; + break; + case 500: + pkt.bandwidth = BW_500KHZ; + break; + default: + printf("ERROR: argument parsing of -b argument. Use -h to print help\n"); + return EXIT_FAILURE; + } + } + break; + case 'c': + cr = true; /* param set */ + i = sscanf(optarg, "%u", &arg_u); + if (i != 1) { + printf("ERROR: argument parsing of -b argument. Use -h to print help\n"); + return EXIT_FAILURE; + } else { + switch (arg_u) { + case 1: + pkt.coderate = CR_LORA_4_5; + break; + case 2: + pkt.coderate = CR_LORA_4_6; + break; + case 3: + pkt.coderate = CR_LORA_4_7; + break; + case 4: + pkt.coderate = CR_LORA_4_8; + break; + default: + printf("ERROR: argument parsing of -b argument. Use -h to print help\n"); + return EXIT_FAILURE; + } + } + break; + case 'z': + sz = true; /* param set */ + i = sscanf(optarg, "%u", &arg_u); + if ((i != 1) || (arg_u > 255)) { + printf("ERROR: argument parsing of -z argument. Use -h to print help\n"); + return EXIT_FAILURE; + } else { + pkt.size = (uint8_t)arg_u; + } + break; + default: + printf("ERROR: argument parsing\n"); + usage(); + return EXIT_FAILURE; + } + } + + printf("### LoRa - Time On Air Calculator ###\n"); + + if (sf == false || + bw == false || + preamb == false || + cr == false || + sz == false) { + printf("ERROR: missing mandatory packet description parameter\n"); + usage(); + return EXIT_FAILURE; + } + + toa_u = lgw_time_on_air(&pkt); + printf("=> %u ms\n", toa_u); + + return 0; +} diff --git a/mcu_bin/dbg_000206_CoreCell_USB.bin b/mcu_bin/dbg_000206_CoreCell_USB.bin new file mode 100644 index 00000000..32fc9c5b Binary files /dev/null and b/mcu_bin/dbg_000206_CoreCell_USB.bin differ diff --git a/mcu_bin/rlz_000206_CoreCell_USB.bin b/mcu_bin/rlz_000206_CoreCell_USB.bin new file mode 100644 index 00000000..da4bd999 Binary files /dev/null and b/mcu_bin/rlz_000206_CoreCell_USB.bin differ diff --git a/packet_forwarder/Makefile b/packet_forwarder/Makefile index 83097507..2325a472 100644 --- a/packet_forwarder/Makefile +++ b/packet_forwarder/Makefile @@ -63,6 +63,7 @@ install_conf: @echo "---- Copying packet_forwarder conf files to $(TARGET_IP):$(TARGET_DIR)" @ssh $(TARGET_USR)@$(TARGET_IP) "mkdir -p $(TARGET_DIR)" @scp global_conf.json.sx1250.* $(TARGET_USR)@$(TARGET_IP):$(TARGET_DIR) + @scp global_conf.json.sx1255.* $(TARGET_USR)@$(TARGET_IP):$(TARGET_DIR) @scp global_conf.json.sx1257.* $(TARGET_USR)@$(TARGET_IP):$(TARGET_DIR) else @echo "ERROR: TARGET_USR is not configured in target.cfg" diff --git a/packet_forwarder/PROTOCOL.md b/packet_forwarder/PROTOCOL.md index ac2bb295..58d1f0d0 100644 --- a/packet_forwarder/PROTOCOL.md +++ b/packet_forwarder/PROTOCOL.md @@ -129,6 +129,7 @@ and associated metadata with the following fields: time | string | UTC time of pkt RX, us precision, ISO 8601 'compact' format tmms | number | GPS time of pkt RX, number of milliseconds since 06.Jan.1980 tmst | number | Internal timestamp of "RX finished" event (32b unsigned) + ftime| number | fine timestamp, number of nanoseconds since last PPS [0..999999999] (Optional) freq | number | RX central frequency in MHz (unsigned float, Hz precision) chan | number | Concentrator "IF" channel used for RX (unsigned integer) rfch | number | Concentrator "RF chain" used for RX (unsigned integer) @@ -150,17 +151,21 @@ Example (white-spaces, indentation and newlines added for readability): ``` json {"rxpk":[ { - "time":"2013-03-31T16:21:17.528002Z", "tmst":3512348611, + "time":"2020-10-01T09:30:52.592567Z", + "tmms":1285579871592, + "ftime":85224290, "chan":2, "rfch":0, "freq":866.349812, + "mid":3, "stat":1, "modu":"LORA", "datr":"SF7BW125", "codr":"4/6", "rssi":-35, "lsnr":5.1, + "foff":-3313, "size":32, "data":"-DS4CGaDCdG+48eJNM3Vai-zDpsR71Pn9CPA9uCON84" },{ @@ -367,6 +372,7 @@ following fields: size | number | RF packet payload size in bytes (unsigned integer) data | string | Base64 encoded RF packet payload, padding optional ncrc | bool | If true, disable the CRC of the physical layer (optional) + nhdr | bool | If true, disable the header of the physical layer (optional) Most fields are optional. If a field is omitted, default parameters will be used. @@ -454,6 +460,12 @@ Examples (white-spaces, indentation and newlines added for readability): ## 7. Revisions +### v1.6 ### +* Added "mid" field in "rxpk" for concentrator modem ID used to demodulate pkt +* Added "foff" field in "rxpk" for frequency offset measured +* Added "rssis" field in "rxpk" for signal RSSI measured. +* Added "ftime" field in "rxpk" for fine timestamping + ### v1.5 ### * Moved TX_POWER from "error" category to "warn" category in "txpk_ack" object diff --git a/packet_forwarder/global_conf.json.sx1250.AS923.USB b/packet_forwarder/global_conf.json.sx1250.AS923.USB new file mode 100644 index 00000000..fe360842 --- /dev/null +++ b/packet_forwarder/global_conf.json.sx1250.AS923.USB @@ -0,0 +1,122 @@ +{ + "SX130x_conf": { + "com_type": "USB", + "com_path": "/dev/ttyACM0", + "lorawan_public": true, + "clksrc": 0, + "antenna_gain": 0, /* antenna gain, in dBi */ + "full_duplex": false, + "fine_timestamp": { + "enable": false, + "mode": "all_sf" /* high_capacity or all_sf */ + }, + "sx1261_conf": { + "rssi_offset": 0, /* dB */ + "spectral_scan": { + "enable": false, + "freq_start": 922000000, + "nb_chan": 8, + "nb_scan": 2000, + "pace_s": 10 + }, + "lbt": { + "enable": true, + "rssi_target": -80, /* dBm */ + "channels":[ /* 16 channels maximum */ + { "freq_hz": 920600000, "bandwidth": 125000, "scan_time_us": 5000, "transmit_time_ms": 4000 }, + { "freq_hz": 920800000, "bandwidth": 125000, "scan_time_us": 5000, "transmit_time_ms": 4000 }, + { "freq_hz": 921000000, "bandwidth": 125000, "scan_time_us": 5000, "transmit_time_ms": 4000 }, + { "freq_hz": 921200000, "bandwidth": 125000, "scan_time_us": 5000, "transmit_time_ms": 4000 }, + { "freq_hz": 921400000, "bandwidth": 125000, "scan_time_us": 5000, "transmit_time_ms": 4000 }, + { "freq_hz": 921600000, "bandwidth": 125000, "scan_time_us": 5000, "transmit_time_ms": 4000 }, + { "freq_hz": 921800000, "bandwidth": 125000, "scan_time_us": 5000, "transmit_time_ms": 4000 }, + { "freq_hz": 923200000, "bandwidth": 125000, "scan_time_us": 5000, "transmit_time_ms": 4000 }, + { "freq_hz": 923400000, "bandwidth": 125000, "scan_time_us": 5000, "transmit_time_ms": 4000 } + ] + } + }, + "radio_0": { + "enable": true, + "type": "SX1250", + "freq": 922300000, + "rssi_offset": -215.4, + "rssi_tcomp": {"coeff_a": 0, "coeff_b": 0, "coeff_c": 20.41, "coeff_d": 2162.56, "coeff_e": 0}, + "tx_enable": true, + "tx_freq_min": 920000000, + "tx_freq_max": 924000000, + "tx_gain_lut":[ + {"rf_power": 0, "pa_gain": 0, "pwr_idx": 0}, + {"rf_power": 12, "pa_gain": 0, "pwr_idx": 15}, + {"rf_power": 13, "pa_gain": 0, "pwr_idx": 16}, + {"rf_power": 14, "pa_gain": 0, "pwr_idx": 17}, + {"rf_power": 15, "pa_gain": 0, "pwr_idx": 19}, + {"rf_power": 16, "pa_gain": 0, "pwr_idx": 20}, + {"rf_power": 17, "pa_gain": 0, "pwr_idx": 22}, + {"rf_power": 18, "pa_gain": 1, "pwr_idx": 1}, + {"rf_power": 19, "pa_gain": 1, "pwr_idx": 2}, + {"rf_power": 20, "pa_gain": 1, "pwr_idx": 3}, + {"rf_power": 21, "pa_gain": 1, "pwr_idx": 4}, + {"rf_power": 22, "pa_gain": 1, "pwr_idx": 5}, + {"rf_power": 23, "pa_gain": 1, "pwr_idx": 6}, + {"rf_power": 24, "pa_gain": 1, "pwr_idx": 9} + ] + }, + "radio_1": { + "enable": true, + "type": "SX1250", + "freq": 923100000, + "rssi_offset": -215.4, + "rssi_tcomp": {"coeff_a": 0, "coeff_b": 0, "coeff_c": 20.41, "coeff_d": 2162.56, "coeff_e": 0}, + "tx_enable": false + }, + "chan_multiSF_All": {"spreading_factor_enable": [ 5, 6, 7, 8, 9, 10, 11, 12 ]}, + "chan_multiSF_0": {"enable": true, "radio": 0, "if": -300000}, + "chan_multiSF_1": {"enable": true, "radio": 0, "if": -100000}, + "chan_multiSF_2": {"enable": true, "radio": 0, "if": 100000}, + "chan_multiSF_3": {"enable": true, "radio": 0, "if": 300000}, + "chan_multiSF_4": {"enable": true, "radio": 1, "if": -300000}, + "chan_multiSF_5": {"enable": true, "radio": 1, "if": -100000}, + "chan_multiSF_6": {"enable": true, "radio": 1, "if": 100000}, + "chan_multiSF_7": {"enable": true, "radio": 1, "if": 300000}, + "chan_Lora_std": {"enable": true, "radio": 0, "if": 300000, "bandwidth": 500000, "spread_factor": 8, + "implicit_hdr": false, "implicit_payload_length": 17, "implicit_crc_en": false, "implicit_coderate": 1}, + "chan_FSK": {"enable": false, "radio": 1, "if": 300000, "bandwidth": 125000, "datarate": 50000} + }, + + "gateway_conf": { + "gateway_ID": "AA555A0000000000", + /* change with default server address/ports */ + "server_address": "localhost", + "serv_port_up": 1730, + "serv_port_down": 1730, + /* adjust the following parameters for your network */ + "keepalive_interval": 10, + "stat_interval": 30, + "push_timeout_ms": 100, + /* forward only valid packets */ + "forward_crc_valid": true, + "forward_crc_error": false, + "forward_crc_disabled": false, + /* GPS configuration */ + "gps_tty_path": "/dev/ttyS0", + /* GPS reference coordinates */ + "ref_latitude": 0.0, + "ref_longitude": 0.0, + "ref_altitude": 0, + /* Beaconing parameters */ + "beacon_period": 0, /* disable class B beacon */ + "beacon_freq_hz": 923400000, + "beacon_datarate": 9, + "beacon_bw_hz": 125000, + "beacon_power": 14, + "beacon_infodesc": 0 + }, + + "debug_conf": { + "ref_payload":[ + {"id": "0xCAFE1234"}, + {"id": "0xCAFE2345"} + ], + "log_file": "loragw_hal.log" + } +} diff --git a/packet_forwarder/global_conf.json.sx1250.CN490 b/packet_forwarder/global_conf.json.sx1250.CN490 index eeca5be9..82684c1c 100644 --- a/packet_forwarder/global_conf.json.sx1250.CN490 +++ b/packet_forwarder/global_conf.json.sx1250.CN490 @@ -1,14 +1,14 @@ { "SX130x_conf": { - "spidev_path": "/dev/spidev0.0", + "com_type": "SPI", + "com_path": "/dev/spidev0.0", "lorawan_public": true, "clksrc": 0, "antenna_gain": 0, /* antenna gain, in dBi */ "full_duplex": false, - "precision_timestamp": { + "fine_timestamp": { "enable": false, - "max_ts_metrics": 255, - "nb_symbols": 1 + "mode": "all_sf" /* high_capacity or all_sf */ }, "radio_0": { "enable": true, @@ -28,15 +28,15 @@ {"rf_power": 6, "pa_gain": 1, "pwr_idx": 4}, {"rf_power": 10, "pa_gain": 1, "pwr_idx": 5}, {"rf_power": 11, "pa_gain": 1, "pwr_idx": 6}, - {"rf_power": 12, "pa_gain": 2, "pwr_idx": 7}, + {"rf_power": 12, "pa_gain": 1, "pwr_idx": 7}, {"rf_power": 13, "pa_gain": 1, "pwr_idx": 8}, - {"rf_power": 14, "pa_gain": 2, "pwr_idx": 9}, - {"rf_power": 16, "pa_gain": 2, "pwr_idx": 10}, - {"rf_power": 20, "pa_gain": 3, "pwr_idx": 11}, - {"rf_power": 23, "pa_gain": 3, "pwr_idx": 12}, - {"rf_power": 25, "pa_gain": 3, "pwr_idx": 13}, - {"rf_power": 26, "pa_gain": 3, "pwr_idx": 14}, - {"rf_power": 27, "pa_gain": 3, "pwr_idx": 15} + {"rf_power": 14, "pa_gain": 1, "pwr_idx": 9}, + {"rf_power": 16, "pa_gain": 1, "pwr_idx": 10}, + {"rf_power": 20, "pa_gain": 1, "pwr_idx": 11}, + {"rf_power": 23, "pa_gain": 1, "pwr_idx": 12}, + {"rf_power": 25, "pa_gain": 1, "pwr_idx": 13}, + {"rf_power": 26, "pa_gain": 1, "pwr_idx": 14}, + {"rf_power": 27, "pa_gain": 1, "pwr_idx": 15} ] }, "radio_1": { @@ -48,6 +48,7 @@ "rssi_tcomp": {"coeff_a": 0, "coeff_b": 0, "coeff_c": 20.41, "coeff_d": 2162.56, "coeff_e": 0}, "tx_enable": false }, + "chan_multiSF_All": {"spreading_factor_enable": [ 5, 6, 7, 8, 9, 10, 11, 12 ]}, "chan_multiSF_0": {"enable": true, "radio": 0, "if": -300000}, "chan_multiSF_1": {"enable": true, "radio": 0, "if": -100000}, "chan_multiSF_2": {"enable": true, "radio": 0, "if": 100000}, diff --git a/packet_forwarder/global_conf.json.sx1250.CN490.USB b/packet_forwarder/global_conf.json.sx1250.CN490.USB new file mode 100644 index 00000000..dacc873c --- /dev/null +++ b/packet_forwarder/global_conf.json.sx1250.CN490.USB @@ -0,0 +1,126 @@ +{ + "SX130x_conf": { + "com_type": "USB", + "com_path": "/dev/ttyACM0", + "lorawan_public": true, + "clksrc": 0, + "antenna_gain": 0, /* antenna gain, in dBi */ + "full_duplex": false, + "fine_timestamp": { + "enable": false, + "mode": "all_sf" /* high_capacity or all_sf */ + }, + "sx1261_conf": { + "rssi_offset": 0, /* dB */ + "spectral_scan": { + "enable": false, + "freq_start": 473100000, + "nb_chan": 8, + "nb_scan": 2000, + "pace_s": 10 + }, + "lbt": { + "enable": false, + "rssi_target": -70, /* dBm */ + "channels":[ /* 16 channels maximum */ + { "freq_hz": 500300000, "bandwidth": 125000, "scan_time_us": 128, "transmit_time_ms": 400 }, + { "freq_hz": 500500000, "bandwidth": 125000, "scan_time_us": 128, "transmit_time_ms": 400 }, + { "freq_hz": 500700000, "bandwidth": 125000, "scan_time_us": 128, "transmit_time_ms": 400 }, + { "freq_hz": 500900000, "bandwidth": 125000, "scan_time_us": 128, "transmit_time_ms": 400 }, + { "freq_hz": 501100000, "bandwidth": 125000, "scan_time_us": 128, "transmit_time_ms": 400 }, + { "freq_hz": 501300000, "bandwidth": 125000, "scan_time_us": 128, "transmit_time_ms": 400 }, + { "freq_hz": 501500000, "bandwidth": 125000, "scan_time_us": 128, "transmit_time_ms": 400 }, + { "freq_hz": 501700000, "bandwidth": 125000, "scan_time_us": 128, "transmit_time_ms": 400 }, + { "freq_hz": 505300000, "bandwidth": 125000, "scan_time_us": 5000, "transmit_time_ms": 4000 } + ] + } + }, + "radio_0": { + "enable": true, + "type": "SX1250", + "single_input_mode": true, + "freq": 473400000, + "rssi_offset": -218.0, + "rssi_tcomp": {"coeff_a": 0, "coeff_b": 0, "coeff_c": 20.41, "coeff_d": 2162.56, "coeff_e": 0}, + "tx_enable": true, + "tx_freq_min": 500000000, + "tx_freq_max": 510000000, + "tx_gain_lut":[ + {"rf_power": 3, "pa_gain": 0, "pwr_idx": 7}, + {"rf_power": 4, "pa_gain": 0, "pwr_idx": 8}, + {"rf_power": 5, "pa_gain": 0, "pwr_idx": 9}, + {"rf_power": 6, "pa_gain": 1, "pwr_idx": 10}, + {"rf_power": 7, "pa_gain": 1, "pwr_idx": 11}, + {"rf_power": 8, "pa_gain": 1, "pwr_idx": 12}, + {"rf_power": 9, "pa_gain": 1, "pwr_idx": 13}, + {"rf_power": 10, "pa_gain": 1, "pwr_idx": 14}, + {"rf_power": 11, "pa_gain": 1, "pwr_idx": 15}, + {"rf_power": 12, "pa_gain": 1, "pwr_idx": 16}, + {"rf_power": 13, "pa_gain": 1, "pwr_idx": 17}, + {"rf_power": 14, "pa_gain": 1, "pwr_idx": 18}, + {"rf_power": 15, "pa_gain": 1, "pwr_idx": 19}, + {"rf_power": 16, "pa_gain": 1, "pwr_idx": 20}, + {"rf_power": 17, "pa_gain": 1, "pwr_idx": 21}, + {"rf_power": 18, "pa_gain": 1, "pwr_idx": 22} + ] + }, + "radio_1": { + "enable": true, + "type": "SX1250", + "single_input_mode": true, + "freq": 474200000, + "rssi_offset": -218.0, + "rssi_tcomp": {"coeff_a": 0, "coeff_b": 0, "coeff_c": 20.41, "coeff_d": 2162.56, "coeff_e": 0}, + "tx_enable": false + }, + "chan_multiSF_All": {"spreading_factor_enable": [ 5, 6, 7, 8, 9, 10, 11, 12 ]}, + "chan_multiSF_0": {"enable": true, "radio": 0, "if": -300000}, + "chan_multiSF_1": {"enable": true, "radio": 0, "if": -100000}, + "chan_multiSF_2": {"enable": true, "radio": 0, "if": 100000}, + "chan_multiSF_3": {"enable": true, "radio": 0, "if": 300000}, + "chan_multiSF_4": {"enable": true, "radio": 1, "if": -300000}, + "chan_multiSF_5": {"enable": true, "radio": 1, "if": -100000}, + "chan_multiSF_6": {"enable": true, "radio": 1, "if": 100000}, + "chan_multiSF_7": {"enable": true, "radio": 1, "if": 300000}, + "chan_Lora_std": {"enable": true, "radio": 1, "if": -200000, "bandwidth": 250000, "spread_factor": 7, + "implicit_hdr": false, "implicit_payload_length": 17, "implicit_crc_en": false, "implicit_coderate": 1}, + "chan_FSK": {"enable": true, "radio": 1, "if": 300000, "bandwidth": 125000, "datarate": 50000} + }, + + "gateway_conf": { + "gateway_ID": "AA555A0000000000", + /* change with default server address/ports */ + "server_address": "localhost", + "serv_port_up": 1730, + "serv_port_down": 1730, + /* adjust the following parameters for your network */ + "keepalive_interval": 10, + "stat_interval": 30, + "push_timeout_ms": 100, + /* forward only valid packets */ + "forward_crc_valid": true, + "forward_crc_error": false, + "forward_crc_disabled": false, + /* GPS configuration */ + "gps_tty_path": "/dev/ttyS0", + /* GPS reference coordinates */ + "ref_latitude": 0.0, + "ref_longitude": 0.0, + "ref_altitude": 0, + /* Beaconing parameters */ + "beacon_period": 0, + "beacon_freq_hz": 500000000, + "beacon_datarate": 9, + "beacon_bw_hz": 125000, + "beacon_power": 14, + "beacon_infodesc": 0 + }, + + "debug_conf": { + "ref_payload":[ + {"id": "0xCAFE1234"}, + {"id": "0xCAFE2345"} + ], + "log_file": "loragw_hal.log" + } +} diff --git a/packet_forwarder/global_conf.json.sx1250.EU868 b/packet_forwarder/global_conf.json.sx1250.EU868 index d09bc21d..063c9312 100644 --- a/packet_forwarder/global_conf.json.sx1250.EU868 +++ b/packet_forwarder/global_conf.json.sx1250.EU868 @@ -1,14 +1,41 @@ { "SX130x_conf": { - "spidev_path": "/dev/spidev0.0", + "com_type": "SPI", + "com_path": "/dev/spidev0.0", "lorawan_public": true, "clksrc": 0, "antenna_gain": 0, /* antenna gain, in dBi */ "full_duplex": false, - "precision_timestamp": { + "fine_timestamp": { "enable": false, - "max_ts_metrics": 255, - "nb_symbols": 1 + "mode": "all_sf" /* high_capacity or all_sf */ + }, + "sx1261_conf": { + "spi_path": "/dev/spidev0.1", + "rssi_offset": 0, /* dB */ + "spectral_scan": { + "enable": false, + "freq_start": 867100000, + "nb_chan": 8, + "nb_scan": 2000, + "pace_s": 10 + }, + "lbt": { + "enable": false, + "rssi_target": -70, /* dBm */ + "channels":[ /* 16 channels maximum */ + { "freq_hz": 867100000, "bandwidth": 125000, "scan_time_us": 128, "transmit_time_ms": 400 }, + { "freq_hz": 867300000, "bandwidth": 125000, "scan_time_us": 128, "transmit_time_ms": 400 }, + { "freq_hz": 867500000, "bandwidth": 125000, "scan_time_us": 128, "transmit_time_ms": 400 }, + { "freq_hz": 867700000, "bandwidth": 125000, "scan_time_us": 128, "transmit_time_ms": 400 }, + { "freq_hz": 867900000, "bandwidth": 125000, "scan_time_us": 128, "transmit_time_ms": 400 }, + { "freq_hz": 868100000, "bandwidth": 125000, "scan_time_us": 128, "transmit_time_ms": 400 }, + { "freq_hz": 868300000, "bandwidth": 125000, "scan_time_us": 128, "transmit_time_ms": 400 }, + { "freq_hz": 868500000, "bandwidth": 125000, "scan_time_us": 128, "transmit_time_ms": 400 }, + { "freq_hz": 869525000, "bandwidth": 125000, "scan_time_us": 5000, "transmit_time_ms": 4000 }, + { "freq_hz": 868300000, "bandwidth": 250000, "scan_time_us": 128, "transmit_time_ms": 400 } + ] + } }, "radio_0": { "enable": true, @@ -46,6 +73,7 @@ "rssi_tcomp": {"coeff_a": 0, "coeff_b": 0, "coeff_c": 20.41, "coeff_d": 2162.56, "coeff_e": 0}, "tx_enable": false }, + "chan_multiSF_All": {"spreading_factor_enable": [ 5, 6, 7, 8, 9, 10, 11, 12 ]}, "chan_multiSF_0": {"enable": true, "radio": 1, "if": -400000}, "chan_multiSF_1": {"enable": true, "radio": 1, "if": -200000}, "chan_multiSF_2": {"enable": true, "radio": 1, "if": 0}, diff --git a/packet_forwarder/global_conf.json.sx1250.EU868.USB b/packet_forwarder/global_conf.json.sx1250.EU868.USB new file mode 100644 index 00000000..c654751b --- /dev/null +++ b/packet_forwarder/global_conf.json.sx1250.EU868.USB @@ -0,0 +1,125 @@ +{ + "SX130x_conf": { + "com_type": "USB", + "com_path": "/dev/ttyACM0", + "lorawan_public": true, + "clksrc": 0, + "antenna_gain": 0, /* antenna gain, in dBi */ + "full_duplex": false, + "fine_timestamp": { + "enable": false, + "mode": "all_sf" /* high_capacity or all_sf */ + }, + "sx1261_conf": { + "rssi_offset": 0, /* dB */ + "spectral_scan": { + "enable": false, + "freq_start": 867100000, + "nb_chan": 8, + "nb_scan": 2000, + "pace_s": 10 + }, + "lbt": { + "enable": false, + "rssi_target": -70, /* dBm */ + "channels":[ /* 16 channels maximum */ + {"freq_hz": 867100000, "bandwidth": 125000, "scan_time_us": 128, "transmit_time_ms": 400}, + {"freq_hz": 867300000, "bandwidth": 125000, "scan_time_us": 128, "transmit_time_ms": 400}, + {"freq_hz": 867500000, "bandwidth": 125000, "scan_time_us": 128, "transmit_time_ms": 400}, + {"freq_hz": 867700000, "bandwidth": 125000, "scan_time_us": 128, "transmit_time_ms": 400}, + {"freq_hz": 867900000, "bandwidth": 125000, "scan_time_us": 128, "transmit_time_ms": 400}, + {"freq_hz": 868100000, "bandwidth": 125000, "scan_time_us": 128, "transmit_time_ms": 400}, + {"freq_hz": 868300000, "bandwidth": 125000, "scan_time_us": 128, "transmit_time_ms": 400}, + {"freq_hz": 868500000, "bandwidth": 125000, "scan_time_us": 128, "transmit_time_ms": 400}, + {"freq_hz": 869525000, "bandwidth": 125000, "scan_time_us": 5000, "transmit_time_ms": 4000}, + {"freq_hz": 868300000, "bandwidth": 250000, "scan_time_us": 128, "transmit_time_ms": 400} + ] + } + }, + "radio_0": { + "enable": true, + "type": "SX1250", + "freq": 867500000, + "rssi_offset": -215.4, + "rssi_tcomp": {"coeff_a": 0, "coeff_b": 0, "coeff_c": 20.41, "coeff_d": 2162.56, "coeff_e": 0}, + "tx_enable": true, + "tx_freq_min": 863000000, + "tx_freq_max": 870000000, + "tx_gain_lut":[ + {"rf_power": 12, "pa_gain": 0, "pwr_idx": 15}, + {"rf_power": 13, "pa_gain": 0, "pwr_idx": 16}, + {"rf_power": 14, "pa_gain": 0, "pwr_idx": 17}, + {"rf_power": 15, "pa_gain": 0, "pwr_idx": 19}, + {"rf_power": 16, "pa_gain": 0, "pwr_idx": 20}, + {"rf_power": 17, "pa_gain": 0, "pwr_idx": 22}, + {"rf_power": 18, "pa_gain": 1, "pwr_idx": 1}, + {"rf_power": 19, "pa_gain": 1, "pwr_idx": 2}, + {"rf_power": 20, "pa_gain": 1, "pwr_idx": 3}, + {"rf_power": 21, "pa_gain": 1, "pwr_idx": 4}, + {"rf_power": 22, "pa_gain": 1, "pwr_idx": 5}, + {"rf_power": 23, "pa_gain": 1, "pwr_idx": 6}, + {"rf_power": 24, "pa_gain": 1, "pwr_idx": 7}, + {"rf_power": 25, "pa_gain": 1, "pwr_idx": 9}, + {"rf_power": 26, "pa_gain": 1, "pwr_idx": 11}, + {"rf_power": 27, "pa_gain": 1, "pwr_idx": 14} + ] + }, + "radio_1": { + "enable": true, + "type": "SX1250", + "freq": 868500000, + "rssi_offset": -215.4, + "rssi_tcomp": {"coeff_a": 0, "coeff_b": 0, "coeff_c": 20.41, "coeff_d": 2162.56, "coeff_e": 0}, + "tx_enable": false + }, + "chan_multiSF_All": {"spreading_factor_enable": [ 5, 6, 7, 8, 9, 10, 11, 12 ]}, + "chan_multiSF_0": {"enable": true, "radio": 1, "if": -400000}, + "chan_multiSF_1": {"enable": true, "radio": 1, "if": -200000}, + "chan_multiSF_2": {"enable": true, "radio": 1, "if": 0}, + "chan_multiSF_3": {"enable": true, "radio": 0, "if": -400000}, + "chan_multiSF_4": {"enable": true, "radio": 0, "if": -200000}, + "chan_multiSF_5": {"enable": true, "radio": 0, "if": 0}, + "chan_multiSF_6": {"enable": true, "radio": 0, "if": 200000}, + "chan_multiSF_7": {"enable": true, "radio": 0, "if": 400000}, + "chan_Lora_std": {"enable": true, "radio": 1, "if": -200000, "bandwidth": 250000, "spread_factor": 7, + "implicit_hdr": false, "implicit_payload_length": 17, "implicit_crc_en": false, "implicit_coderate": 1}, + "chan_FSK": {"enable": true, "radio": 1, "if": 300000, "bandwidth": 125000, "datarate": 50000} + }, + + "gateway_conf": { + "gateway_ID": "AA555A0000000000", + /* change with default server address/ports */ + "server_address": "localhost", + "serv_port_up": 1730, + "serv_port_down": 1730, + /* adjust the following parameters for your network */ + "keepalive_interval": 10, + "stat_interval": 30, + "push_timeout_ms": 100, + /* forward only valid packets */ + "forward_crc_valid": true, + "forward_crc_error": false, + "forward_crc_disabled": false, + /* GPS configuration */ + "gps_tty_path": "/dev/ttyS0", + /* GPS reference coordinates */ + "ref_latitude": 0.0, + "ref_longitude": 0.0, + "ref_altitude": 0, + /* Beaconing parameters */ + "beacon_period": 0, + "beacon_freq_hz": 869525000, + "beacon_datarate": 9, + "beacon_bw_hz": 125000, + "beacon_power": 14, + "beacon_infodesc": 0 + }, + + "debug_conf": { + "ref_payload":[ + {"id": "0xCAFE1234"}, + {"id": "0xCAFE2345"} + ], + "log_file": "loragw_hal.log" + } +} diff --git a/packet_forwarder/global_conf.json.sx1250.US915 b/packet_forwarder/global_conf.json.sx1250.US915 index 7bd850da..04434bf9 100644 --- a/packet_forwarder/global_conf.json.sx1250.US915 +++ b/packet_forwarder/global_conf.json.sx1250.US915 @@ -1,14 +1,28 @@ { "SX130x_conf": { - "spidev_path": "/dev/spidev0.0", + "com_type": "SPI", + "com_path": "/dev/spidev0.0", "lorawan_public": true, "clksrc": 0, "antenna_gain": 0, /* antenna gain, in dBi */ "full_duplex": false, - "precision_timestamp": { + "fine_timestamp": { "enable": false, - "max_ts_metrics": 255, - "nb_symbols": 1 + "mode": "all_sf" /* high_capacity or all_sf */ + }, + "sx1261_conf": { + "spi_path": "/dev/spidev0.1", + "rssi_offset": 0, /* dB */ + "spectral_scan": { + "enable": false, + "freq_start": 903900000, + "nb_chan": 8, + "nb_scan": 2000, + "pace_s": 10 + }, + "lbt": { + "enable": false /* LBT for 500 Khz channels is not supported */ + } }, "radio_0": { "enable": true, @@ -46,6 +60,7 @@ "rssi_tcomp": {"coeff_a": 0, "coeff_b": 0, "coeff_c": 20.41, "coeff_d": 2162.56, "coeff_e": 0}, "tx_enable": false }, + "chan_multiSF_All": {"spreading_factor_enable": [ 5, 6, 7, 8, 9, 10, 11, 12 ]}, "chan_multiSF_0": {"enable": true, "radio": 0, "if": -400000}, /* Freq : 903.9 MHz*/ "chan_multiSF_1": {"enable": true, "radio": 0, "if": -200000}, /* Freq : 904.1 MHz*/ "chan_multiSF_2": {"enable": true, "radio": 0, "if": 0}, /* Freq : 904.3 MHz*/ diff --git a/packet_forwarder/global_conf.json.sx1250.US915.USB b/packet_forwarder/global_conf.json.sx1250.US915.USB new file mode 100644 index 00000000..ec28fa3e --- /dev/null +++ b/packet_forwarder/global_conf.json.sx1250.US915.USB @@ -0,0 +1,112 @@ +{ + "SX130x_conf": { + "com_type": "USB", + "com_path": "/dev/ttyACM0", + "lorawan_public": true, + "clksrc": 0, + "antenna_gain": 0, /* antenna gain, in dBi */ + "full_duplex": false, + "fine_timestamp": { + "enable": false, + "mode": "all_sf" /* high_capacity or all_sf */ + }, + "sx1261_conf": { + "rssi_offset": 0, /* dB */ + "spectral_scan": { + "enable": false, + "freq_start": 903900000, + "nb_chan": 8, + "nb_scan": 2000, + "pace_s": 10 + }, + "lbt": { + "enable": false /* LBT for 500 Khz channels is not supported */ + } + }, + "radio_0": { + "enable": true, + "type": "SX1250", + "freq": 904300000, + "rssi_offset": -215.4, + "rssi_tcomp": {"coeff_a": 0, "coeff_b": 0, "coeff_c": 20.41, "coeff_d": 2162.56, "coeff_e": 0}, + "tx_enable": true, + "tx_freq_min": 923000000, + "tx_freq_max": 928000000, + "tx_gain_lut":[ + {"rf_power": 12, "pa_gain": 0, "pwr_idx": 15}, + {"rf_power": 13, "pa_gain": 0, "pwr_idx": 16}, + {"rf_power": 14, "pa_gain": 0, "pwr_idx": 17}, + {"rf_power": 15, "pa_gain": 0, "pwr_idx": 19}, + {"rf_power": 16, "pa_gain": 0, "pwr_idx": 20}, + {"rf_power": 17, "pa_gain": 0, "pwr_idx": 22}, + {"rf_power": 18, "pa_gain": 1, "pwr_idx": 1}, + {"rf_power": 19, "pa_gain": 1, "pwr_idx": 2}, + {"rf_power": 20, "pa_gain": 1, "pwr_idx": 3}, + {"rf_power": 21, "pa_gain": 1, "pwr_idx": 4}, + {"rf_power": 22, "pa_gain": 1, "pwr_idx": 5}, + {"rf_power": 23, "pa_gain": 1, "pwr_idx": 6}, + {"rf_power": 24, "pa_gain": 1, "pwr_idx": 7}, + {"rf_power": 25, "pa_gain": 1, "pwr_idx": 9}, + {"rf_power": 26, "pa_gain": 1, "pwr_idx": 11}, + {"rf_power": 27, "pa_gain": 1, "pwr_idx": 14} + ] + }, + "radio_1": { + "enable": true, + "type": "SX1250", + "freq": 905000000, + "rssi_offset": -215.4, + "rssi_tcomp": {"coeff_a": 0, "coeff_b": 0, "coeff_c": 20.41, "coeff_d": 2162.56, "coeff_e": 0}, + "tx_enable": false + }, + "chan_multiSF_All": {"spreading_factor_enable": [ 5, 6, 7, 8, 9, 10, 11, 12 ]}, + "chan_multiSF_0": {"enable": true, "radio": 0, "if": -400000}, /* Freq : 903.9 MHz*/ + "chan_multiSF_1": {"enable": true, "radio": 0, "if": -200000}, /* Freq : 904.1 MHz*/ + "chan_multiSF_2": {"enable": true, "radio": 0, "if": 0}, /* Freq : 904.3 MHz*/ + "chan_multiSF_3": {"enable": true, "radio": 0, "if": 200000}, /* Freq : 904.5 MHz*/ + "chan_multiSF_4": {"enable": true, "radio": 1, "if": -300000}, /* Freq : 904.7 MHz*/ + "chan_multiSF_5": {"enable": true, "radio": 1, "if": -100000}, /* Freq : 904.9 MHz*/ + "chan_multiSF_6": {"enable": true, "radio": 1, "if": 100000}, /* Freq : 905.1 MHz*/ + "chan_multiSF_7": {"enable": true, "radio": 1, "if": 300000}, /* Freq : 905.3 MHz*/ + "chan_Lora_std": {"enable": true, "radio": 0, "if": 300000, "bandwidth": 500000, "spread_factor": 8, /* Freq : 904.6 MHz*/ + "implicit_hdr": false, "implicit_payload_length": 17, "implicit_crc_en": false, "implicit_coderate": 1}, + "chan_FSK": {"enable": false, "radio": 1, "if": 300000, "bandwidth": 125000, "datarate": 50000} /* Freq : 868.8 MHz*/ + }, + + "gateway_conf": { + "gateway_ID": "AA555A0000000000", + /* change with default server address/ports */ + "server_address": "localhost", + "serv_port_up": 1730, + "serv_port_down": 1730, + /* adjust the following parameters for your network */ + "keepalive_interval": 10, + "stat_interval": 30, + "push_timeout_ms": 100, + /* forward only valid packets */ + "forward_crc_valid": true, + "forward_crc_error": false, + "forward_crc_disabled": false, + /* GPS configuration */ + "gps_tty_path": "/dev/ttyS0", + /* GPS reference coordinates */ + "ref_latitude": 0.0, + "ref_longitude": 0.0, + "ref_altitude": 0, + /* Beaconing parameters */ + "beacon_period": 0, /* disable class B beacon */ + "beacon_freq_hz": 869525000, + "beacon_datarate": 9, + "beacon_bw_hz": 125000, + "beacon_power": 14, + "beacon_infodesc": 0 + }, + + "debug_conf": { + "ref_payload":[ + {"id": "0xCAFE1234"}, + {"id": "0xCAFE2345"} + ], + "log_file": "loragw_hal.log" + } +} diff --git a/packet_forwarder/global_conf.json.sx1255.CN490.full-duplex b/packet_forwarder/global_conf.json.sx1255.CN490.full-duplex new file mode 100644 index 00000000..91cb5501 --- /dev/null +++ b/packet_forwarder/global_conf.json.sx1255.CN490.full-duplex @@ -0,0 +1,89 @@ +{ + "SX130x_conf": { + "com_type": "SPI", + "com_path": "/dev/spidev0.0", + "lorawan_public": true, + "clksrc": 0, + "antenna_gain": 0, /* antenna gain, in dBi */ + "full_duplex": true, + "radio_0": { + "enable": true, + "type": "SX1255", + "freq": 472000000, + "rssi_offset": -203.0, + "rssi_tcomp": {"coeff_a": 0.00, "coeff_b": 0.00, "coeff_c": -39.78, "coeff_d": 1349.50, "coeff_e": 0.00}, + "tx_enable": true, + "tx_freq_min": 500000000, + "tx_freq_max": 510000000, + "tx_gain_lut":[ + {"rf_power": 10, "pa_gain": 1, "dac_gain": 1, "mix_gain": 9, "dig_gain": 0}, + {"rf_power": 12, "pa_gain": 1, "dac_gain": 3, "mix_gain": 7, "dig_gain": 0}, + {"rf_power": 13, "pa_gain": 1, "dac_gain": 1, "mix_gain": 11, "dig_gain": 1}, + {"rf_power": 14, "pa_gain": 1, "dac_gain": 0, "mix_gain": 12, "dig_gain": 0}, + {"rf_power": 15, "pa_gain": 1, "dac_gain": 0, "mix_gain": 9, "dig_gain": 0}, + {"rf_power": 16, "pa_gain": 1, "dac_gain": 1, "mix_gain": 12, "dig_gain": 2}, + {"rf_power": 17, "pa_gain": 1, "dac_gain": 1, "mix_gain": 11, "dig_gain": 0}, + {"rf_power": 18, "pa_gain": 1, "dac_gain": 1, "mix_gain": 11, "dig_gain": 1}, + {"rf_power": 20, "pa_gain": 1, "dac_gain": 2, "mix_gain": 10, "dig_gain": 0}, + {"rf_power": 21, "pa_gain": 1, "dac_gain": 0, "mix_gain": 13, "dig_gain": 0} + ] + }, + "radio_1": { + "enable": true, + "type": "SX1255", + "freq": 473000000, + "rssi_offset": -205.5, + "rssi_tcomp": {"coeff_a": 0.00, "coeff_b": 0.00, "coeff_c": 9.97, "coeff_d": -2748.33, "coeff_e": 0.00}, + "tx_enable": false + }, + "chan_multiSF_All": {"spreading_factor_enable": [ 5, 6, 7, 8, 9, 10, 11, 12 ]}, + "chan_multiSF_0": {"enable": true, "radio": 0, "if": -300000}, + "chan_multiSF_1": {"enable": true, "radio": 0, "if": -100000}, + "chan_multiSF_2": {"enable": true, "radio": 0, "if": 100000}, + "chan_multiSF_3": {"enable": true, "radio": 0, "if": 300000}, + "chan_multiSF_4": {"enable": true, "radio": 1, "if": -300000}, + "chan_multiSF_5": {"enable": true, "radio": 1, "if": -100000}, + "chan_multiSF_6": {"enable": true, "radio": 1, "if": 100000}, + "chan_multiSF_7": {"enable": true, "radio": 1, "if": 300000}, + "chan_Lora_std": {"enable": true, "radio": 1, "if": -200000, "bandwidth": 250000, "spread_factor": 7, + "implicit_hdr": false, "implicit_payload_length": 17, "implicit_crc_en": false, "implicit_coderate": 1}, + "chan_FSK": {"enable": true, "radio": 1, "if": 300000, "bandwidth": 125000, "datarate": 50000} + }, + + "gateway_conf": { + "gateway_ID": "AA555A0000000000", + /* change with default server address/ports */ + "server_address": "localhost", + "serv_port_up": 1730, + "serv_port_down": 1730, + /* adjust the following parameters for your network */ + "keepalive_interval": 10, + "stat_interval": 30, + "push_timeout_ms": 100, + /* forward only valid packets */ + "forward_crc_valid": true, + "forward_crc_error": false, + "forward_crc_disabled": false, + /* GPS configuration */ + "gps_tty_path": "/dev/ttyS0", + /* GPS reference coordinates */ + "ref_latitude": 0.0, + "ref_longitude": 0.0, + "ref_altitude": 0, + /* Beaconing parameters */ + "beacon_period": 0, + "beacon_freq_hz": 869525000, + "beacon_datarate": 9, + "beacon_bw_hz": 125000, + "beacon_power": 14, + "beacon_infodesc": 0 + }, + + "debug_conf": { + "ref_payload":[ + {"id": "0xCAFE1234"}, + {"id": "0xCAFE2345"} + ], + "log_file": "loragw_hal.log" + } +} diff --git a/packet_forwarder/global_conf.json.sx1257.EU868 b/packet_forwarder/global_conf.json.sx1257.EU868 index 249547b5..70a43678 100644 --- a/packet_forwarder/global_conf.json.sx1257.EU868 +++ b/packet_forwarder/global_conf.json.sx1257.EU868 @@ -1,15 +1,11 @@ { "SX130x_conf": { - "spidev_path": "/dev/spidev0.0", + "com_type": "SPI", + "com_path": "/dev/spidev0.0", "lorawan_public": true, "clksrc": 0, "antenna_gain": 0, /* antenna gain, in dBi */ "full_duplex": false, - "precision_timestamp": { - "enable": false, - "max_ts_metrics": 255, - "nb_symbols": 1 - }, "radio_0": { "enable": true, "type": "SX1257", @@ -46,6 +42,7 @@ "rssi_tcomp": {"coeff_a": -0.006, "coeff_b": 0.789, "coeff_c": -14.992, "coeff_d": 1988.572, "coeff_e": 105236.996}, "tx_enable": false }, + "chan_multiSF_All": {"spreading_factor_enable": [ 5, 6, 7, 8, 9, 10, 11, 12 ]}, "chan_multiSF_0": {"enable": true, "radio": 1, "if": -400000}, "chan_multiSF_1": {"enable": true, "radio": 1, "if": -200000}, "chan_multiSF_2": {"enable": true, "radio": 1, "if": 0}, diff --git a/packet_forwarder/readme.md b/packet_forwarder/readme.md index e6de9eeb..45dc643e 100644 --- a/packet_forwarder/readme.md +++ b/packet_forwarder/readme.md @@ -197,8 +197,7 @@ different system constraints. concentrator TX buffer before its actual departure time. TX_MARGIN_DELAY: Packet collision check margin -6. License ------------ +### 6. License Copyright (C) 2019, SEMTECH S.A. All rights reserved. @@ -226,8 +225,7 @@ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -6. License for Parson library ------------------------------- +#### 7. License for Parson library Parson ( http://kgabis.github.com/parson/ ) Copyright (C) 2012 Krzysztof Gabis diff --git a/packet_forwarder/src/jitqueue.c b/packet_forwarder/src/jitqueue.c index e3889f77..22ec7013 100644 --- a/packet_forwarder/src/jitqueue.c +++ b/packet_forwarder/src/jitqueue.c @@ -32,10 +32,8 @@ License: Revised BSD License, see LICENSE.TXT file include in the project /* -------------------------------------------------------------------------- */ /* --- PRIVATE CONSTANTS & TYPES -------------------------------------------- */ #define TX_START_DELAY 1500 /* microseconds */ - /* TODO: get this value from HAL? */ #define TX_MARGIN_DELAY 1000 /* Packet overlap margin in microseconds */ - /* TODO: How much margin should we take? */ -#define TX_JIT_DELAY 30000 /* Pre-delay to program packet for TX in microseconds */ +#define TX_JIT_DELAY 40000 /* Pre-delay to program packet for TX in microseconds */ #define TX_MAX_ADVANCE_DELAY ((JIT_NUM_BEACON_IN_QUEUE + 1) * 128 * 1E6) /* Maximum advance delay accepted for a TX packet, compared to current time */ #define BEACON_GUARD 3000000 /* Interval where no ping slot can be placed, @@ -173,7 +171,7 @@ enum jit_error_e jit_enqueue(struct jit_queue_s *queue, uint32_t time_us, struct packet->tx_mode = TIMESTAMPED; /* Search for the ASAP timestamp to be given to the packet */ - asap_count_us = time_us + 1E6; /* TODO: Take 1 second margin, to be refined */ + asap_count_us = time_us + 2 * TX_JIT_DELAY; /* margin */ if (queue->num_pkt == 0) { /* If the jit queue is empty, we can insert this packet */ MSG_DEBUG(DEBUG_JIT, "DEBUG: insert IMMEDIATE downlink, first in JiT queue (count_us=%u)\n", asap_count_us); diff --git a/packet_forwarder/src/lora_pkt_fwd.c b/packet_forwarder/src/lora_pkt_fwd.c index 08508938..53661de7 100644 --- a/packet_forwarder/src/lora_pkt_fwd.c +++ b/packet_forwarder/src/lora_pkt_fwd.c @@ -62,6 +62,8 @@ License: Revised BSD License, see LICENSE.TXT file include in the project #define STRINGIFY(x) #x #define STR(x) STRINGIFY(x) +#define RAND_RANGE(min, max) (rand() % (max + 1 - min) + min) + /* -------------------------------------------------------------------------- */ /* --- PRIVATE CONSTANTS ---------------------------------------------------- */ @@ -82,10 +84,10 @@ License: Revised BSD License, see LICENSE.TXT file include in the project #define FETCH_SLEEP_MS 10 /* nb of ms waited when a fetch return no packets */ #define BEACON_POLL_MS 50 /* time in ms between polling of beacon TX status */ -#define PROTOCOL_VERSION 2 /* v1.3 */ +#define PROTOCOL_VERSION 2 /* v1.6 */ #define PROTOCOL_JSON_RXPK_FRAME_FORMAT 1 -#define XERR_INIT_AVG 128 /* nb of measurements the XTAL correction is averaged on as initial value */ +#define XERR_INIT_AVG 16 /* nb of measurements the XTAL correction is averaged on as initial value */ #define XERR_FILT_COEF 256 /* coefficient for low-pass XTAL error tracking */ #define PKT_PUSH_DATA 0 @@ -117,6 +119,18 @@ License: Revised BSD License, see LICENSE.TXT file include in the project #define DEFAULT_BEACON_POWER 14 #define DEFAULT_BEACON_INFODESC 0 +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE TYPES -------------------------------------------------------- */ + +/* spectral scan */ +typedef struct spectral_scan_s { + bool enable; /* enable spectral scan thread */ + uint32_t freq_hz_start; /* first channel frequency, in Hz */ + uint8_t nb_chan; /* number of channels to scan (200kHz between each channel) */ + uint16_t nb_scan; /* number of scan points for each frequency scan */ + uint32_t pace_s; /* number of seconds between 2 scans in the thread */ +} spectral_scan_t; + /* -------------------------------------------------------------------------- */ /* --- PRIVATE VARIABLES (GLOBAL) ------------------------------------------- */ @@ -234,6 +248,7 @@ static int8_t antenna_gain = 0; static struct lgw_tx_gain_lut_s txlut[LGW_RF_CHAIN_NB]; /* TX gain table */ static uint32_t tx_freq_min[LGW_RF_CHAIN_NB]; /* lowest frequency supported by TX chain */ static uint32_t tx_freq_max[LGW_RF_CHAIN_NB]; /* highest frequency supported by TX chain */ +static bool tx_enable[LGW_RF_CHAIN_NB] = {false}; /* Is TX enabled for a given RF chain ? */ static uint32_t nb_pkt_log[LGW_IF_CHAIN_NB][8]; /* [CH][SF] */ static uint32_t nb_pkt_received_lora = 0; @@ -242,6 +257,18 @@ static uint32_t nb_pkt_received_fsk = 0; static struct lgw_conf_debug_s debugconf; static uint32_t nb_pkt_received_ref[16]; +/* Interface type */ +static lgw_com_type_t com_type = LGW_COM_SPI; + +/* Spectral Scan */ +static spectral_scan_t spectral_scan_params = { + .enable = false, + .freq_hz_start = 0, + .nb_chan = 0, + .nb_scan = 0, + .pace_s = 10 +}; + /* -------------------------------------------------------------------------- */ /* --- PRIVATE FUNCTIONS DECLARATION ---------------------------------------- */ @@ -271,6 +298,7 @@ void thread_down(void); void thread_jit(void); void thread_gps(void); void thread_valid(void); +void thread_spectral_scan(void); /* -------------------------------------------------------------------------- */ /* --- PRIVATE FUNCTIONS DEFINITION ----------------------------------------- */ @@ -295,7 +323,7 @@ static void sig_handler(int sigio) { } static int parse_SX130x_configuration(const char * conf_file) { - int i, j; + int i, j, number; char param_name[32]; /* used to generate variable parameter names */ const char *str; /* used to store string value from JSON object */ const char conf_obj_name[] = "SX130x_conf"; @@ -304,14 +332,23 @@ static int parse_SX130x_configuration(const char * conf_file) { JSON_Object *conf_obj = NULL; JSON_Object *conf_txgain_obj; JSON_Object *conf_ts_obj; - JSON_Array *conf_txlut_array; + JSON_Object *conf_sx1261_obj = NULL; + JSON_Object *conf_scan_obj = NULL; + JSON_Object *conf_lbt_obj = NULL; + JSON_Object *conf_lbtchan_obj = NULL; + JSON_Array *conf_txlut_array = NULL; + JSON_Array *conf_lbtchan_array = NULL; + JSON_Array *conf_demod_array = NULL; struct lgw_conf_board_s boardconf; struct lgw_conf_rxrf_s rfconf; struct lgw_conf_rxif_s ifconf; - struct lgw_conf_timestamp_s tsconf; + struct lgw_conf_demod_s demodconf; + struct lgw_conf_ftime_s tsconf; + struct lgw_conf_sx1261_s sx1261conf; uint32_t sf, bw, fdev; bool sx1250_tx_lut; + size_t size; /* try to parse JSON */ root_val = json_parse_file_with_comments(conf_file); @@ -331,15 +368,27 @@ static int parse_SX130x_configuration(const char * conf_file) { /* set board configuration */ memset(&boardconf, 0, sizeof boardconf); /* initialize configuration structure */ - str = json_object_get_string(conf_obj, "spidev_path"); + str = json_object_get_string(conf_obj, "com_type"); + if (str == NULL) { + MSG("ERROR: com_type must be configured in %s\n", conf_file); + return -1; + } else if (!strncmp(str, "SPI", 3) || !strncmp(str, "spi", 3)) { + boardconf.com_type = LGW_COM_SPI; + } else if (!strncmp(str, "USB", 3) || !strncmp(str, "usb", 3)) { + boardconf.com_type = LGW_COM_USB; + } else { + MSG("ERROR: invalid com type: %s (should be SPI or USB)\n", str); + return -1; + } + com_type = boardconf.com_type; + str = json_object_get_string(conf_obj, "com_path"); if (str != NULL) { - strncpy(boardconf.spidev_path, str, sizeof boardconf.spidev_path); - boardconf.spidev_path[sizeof boardconf.spidev_path - 1] = '\0'; /* ensure string termination */ + strncpy(boardconf.com_path, str, sizeof boardconf.com_path); + boardconf.com_path[sizeof boardconf.com_path - 1] = '\0'; /* ensure string termination */ } else { - MSG("ERROR: spidev path must be configured in %s\n", conf_file); + MSG("ERROR: com_path must be configured in %s\n", conf_file); return -1; } - val = json_object_get_value(conf_obj, "lorawan_public"); /* fetch value (if possible) */ if (json_value_get_type(val) == JSONBoolean) { boardconf.lorawan_public = (bool)json_value_get_boolean(val); @@ -361,7 +410,7 @@ static int parse_SX130x_configuration(const char * conf_file) { MSG("WARNING: Data type for full_duplex seems wrong, please check\n"); boardconf.full_duplex = false; } - MSG("INFO: spidev_path %s, lorawan_public %d, clksrc %d, full_duplex %d\n", boardconf.spidev_path, boardconf.lorawan_public, boardconf.clksrc, boardconf.full_duplex); + MSG("INFO: com_type %s, com_path %s, lorawan_public %d, clksrc %d, full_duplex %d\n", (boardconf.com_type == LGW_COM_SPI) ? "SPI" : "USB", boardconf.com_path, boardconf.lorawan_public, boardconf.clksrc, boardconf.full_duplex); /* all parameters parsed, submitting configuration to the HAL */ if (lgw_board_setconf(&boardconf) != LGW_HAL_SUCCESS) { MSG("ERROR: Failed to configure board\n"); @@ -381,37 +430,35 @@ static int parse_SX130x_configuration(const char * conf_file) { MSG("INFO: antenna_gain %d dBi\n", antenna_gain); /* set timestamp configuration */ - conf_ts_obj = json_object_get_object(conf_obj, "precision_timestamp"); + conf_ts_obj = json_object_get_object(conf_obj, "fine_timestamp"); if (conf_ts_obj == NULL) { - MSG("INFO: %s does not contain a JSON object for precision timestamp\n", conf_file); + MSG("INFO: %s does not contain a JSON object for fine timestamp\n", conf_file); } else { val = json_object_get_value(conf_ts_obj, "enable"); /* fetch value (if possible) */ if (json_value_get_type(val) == JSONBoolean) { - tsconf.enable_precision_ts = (bool)json_value_get_boolean(val); + tsconf.enable = (bool)json_value_get_boolean(val); } else { - MSG("WARNING: Data type for precision_timestamp.enable seems wrong, please check\n"); - tsconf.enable_precision_ts = false; + MSG("WARNING: Data type for fine_timestamp.enable seems wrong, please check\n"); + tsconf.enable = false; } - if (tsconf.enable_precision_ts == true) { - val = json_object_get_value(conf_ts_obj, "max_ts_metrics"); /* fetch value (if possible) */ - if (json_value_get_type(val) == JSONNumber) { - tsconf.max_ts_metrics = (uint8_t)json_value_get_number(val); - } else { - MSG("WARNING: Data type for precision_timestamp.max_ts_metrics seems wrong, please check\n"); - tsconf.max_ts_metrics = 0xFF; - } - val = json_object_get_value(conf_ts_obj, "nb_symbols"); /* fetch value (if possible) */ - if (json_value_get_type(val) == JSONNumber) { - tsconf.nb_symbols = (uint8_t)json_value_get_number(val); + if (tsconf.enable == true) { + str = json_object_get_string(conf_ts_obj, "mode"); + if (str == NULL) { + MSG("ERROR: fine_timestamp.mode must be configured in %s\n", conf_file); + return -1; + } else if (!strncmp(str, "high_capacity", 13) || !strncmp(str, "HIGH_CAPACITY", 13)) { + tsconf.mode = LGW_FTIME_MODE_HIGH_CAPACITY; + } else if (!strncmp(str, "all_sf", 6) || !strncmp(str, "ALL_SF", 6)) { + tsconf.mode = LGW_FTIME_MODE_ALL_SF; } else { - MSG("WARNING: Data type for precision_timestamp.nb_symbols seems wrong, please check\n"); - tsconf.nb_symbols = 1; + MSG("ERROR: invalid fine timestamp mode: %s (should be high_capacity or all_sf)\n", str); + return -1; } - MSG("INFO: Configuring precision timestamp: max_ts_metrics:%u, nb_symbols:%u\n", tsconf.max_ts_metrics, tsconf.nb_symbols); + MSG("INFO: Configuring precision timestamp with %s mode\n", str); /* all parameters parsed, submitting configuration to the HAL */ - if (lgw_timestamp_setconf(&tsconf) != LGW_HAL_SUCCESS) { - MSG("ERROR: Failed to configure precision timestamp\n"); + if (lgw_ftime_setconf(&tsconf) != LGW_HAL_SUCCESS) { + MSG("ERROR: Failed to configure fine timestamp\n"); return -1; } } else { @@ -419,6 +466,190 @@ static int parse_SX130x_configuration(const char * conf_file) { } } + /* set SX1261 configuration */ + memset(&sx1261conf, 0, sizeof sx1261conf); /* initialize configuration structure */ + conf_sx1261_obj = json_object_get_object(conf_obj, "sx1261_conf"); /* fetch value (if possible) */ + if (conf_sx1261_obj == NULL) { + MSG("INFO: no configuration for SX1261\n"); + } else { + /* Global SX1261 configuration */ + str = json_object_get_string(conf_sx1261_obj, "spi_path"); + if (str != NULL) { + strncpy(sx1261conf.spi_path, str, sizeof sx1261conf.spi_path); + sx1261conf.spi_path[sizeof sx1261conf.spi_path - 1] = '\0'; /* ensure string termination */ + } else { + MSG("INFO: SX1261 spi_path is not configured in %s\n", conf_file); + } + val = json_object_get_value(conf_sx1261_obj, "rssi_offset"); /* fetch value (if possible) */ + if (json_value_get_type(val) == JSONNumber) { + sx1261conf.rssi_offset = (int8_t)json_value_get_number(val); + } else { + MSG("WARNING: Data type for sx1261_conf.rssi_offset seems wrong, please check\n"); + sx1261conf.rssi_offset = 0; + } + + /* Spectral Scan configuration */ + conf_scan_obj = json_object_get_object(conf_sx1261_obj, "spectral_scan"); /* fetch value (if possible) */ + if (conf_scan_obj == NULL) { + MSG("INFO: no configuration for Spectral Scan\n"); + } else { + val = json_object_get_value(conf_scan_obj, "enable"); /* fetch value (if possible) */ + if (json_value_get_type(val) == JSONBoolean) { + /* Enable background spectral scan thread in packet forwarder */ + spectral_scan_params.enable = (bool)json_value_get_boolean(val); + } else { + MSG("WARNING: Data type for spectral_scan.enable seems wrong, please check\n"); + } + if (spectral_scan_params.enable == true) { + /* Enable the sx1261 radio hardware configuration to allow spectral scan */ + sx1261conf.enable = true; + MSG("INFO: Spectral Scan with SX1261 is enabled\n"); + + /* Get Spectral Scan Parameters */ + val = json_object_get_value(conf_scan_obj, "freq_start"); /* fetch value (if possible) */ + if (json_value_get_type(val) == JSONNumber) { + spectral_scan_params.freq_hz_start = (uint32_t)json_value_get_number(val); + } else { + MSG("WARNING: Data type for spectral_scan.freq_start seems wrong, please check\n"); + } + val = json_object_get_value(conf_scan_obj, "nb_chan"); /* fetch value (if possible) */ + if (json_value_get_type(val) == JSONNumber) { + spectral_scan_params.nb_chan = (uint8_t)json_value_get_number(val); + } else { + MSG("WARNING: Data type for spectral_scan.nb_chan seems wrong, please check\n"); + } + val = json_object_get_value(conf_scan_obj, "nb_scan"); /* fetch value (if possible) */ + if (json_value_get_type(val) == JSONNumber) { + spectral_scan_params.nb_scan = (uint16_t)json_value_get_number(val); + } else { + MSG("WARNING: Data type for spectral_scan.nb_scan seems wrong, please check\n"); + } + val = json_object_get_value(conf_scan_obj, "pace_s"); /* fetch value (if possible) */ + if (json_value_get_type(val) == JSONNumber) { + spectral_scan_params.pace_s = (uint32_t)json_value_get_number(val); + } else { + MSG("WARNING: Data type for spectral_scan.pace_s seems wrong, please check\n"); + } + } + } + + /* LBT configuration */ + conf_lbt_obj = json_object_get_object(conf_sx1261_obj, "lbt"); /* fetch value (if possible) */ + if (conf_lbt_obj == NULL) { + MSG("INFO: no configuration for LBT\n"); + } else { + val = json_object_get_value(conf_lbt_obj, "enable"); /* fetch value (if possible) */ + if (json_value_get_type(val) == JSONBoolean) { + sx1261conf.lbt_conf.enable = (bool)json_value_get_boolean(val); + } else { + MSG("WARNING: Data type for lbt.enable seems wrong, please check\n"); + } + if (sx1261conf.lbt_conf.enable == true) { + /* Enable the sx1261 radio hardware configuration to allow spectral scan */ + sx1261conf.enable = true; + MSG("INFO: Listen-Before-Talk with SX1261 is enabled\n"); + + val = json_object_get_value(conf_lbt_obj, "rssi_target"); /* fetch value (if possible) */ + if (json_value_get_type(val) == JSONNumber) { + sx1261conf.lbt_conf.rssi_target = (int8_t)json_value_get_number(val); + } else { + MSG("WARNING: Data type for lbt.rssi_target seems wrong, please check\n"); + sx1261conf.lbt_conf.rssi_target = 0; + } + /* set LBT channels configuration */ + conf_lbtchan_array = json_object_get_array(conf_lbt_obj, "channels"); + if (conf_lbtchan_array != NULL) { + sx1261conf.lbt_conf.nb_channel = json_array_get_count(conf_lbtchan_array); + MSG("INFO: %u LBT channels configured\n", sx1261conf.lbt_conf.nb_channel); + } + for (i = 0; i < (int)sx1261conf.lbt_conf.nb_channel; i++) { + /* Sanity check */ + if (i >= LGW_LBT_CHANNEL_NB_MAX) { + MSG("ERROR: LBT channel %d not supported, skip it\n", i); + break; + } + /* Get LBT channel configuration object from array */ + conf_lbtchan_obj = json_array_get_object(conf_lbtchan_array, i); + + /* Channel frequency */ + val = json_object_dotget_value(conf_lbtchan_obj, "freq_hz"); /* fetch value (if possible) */ + if (val != NULL) { + if (json_value_get_type(val) == JSONNumber) { + sx1261conf.lbt_conf.channels[i].freq_hz = (uint32_t)json_value_get_number(val); + } else { + MSG("WARNING: Data type for lbt.channels[%d].freq_hz seems wrong, please check\n", i); + sx1261conf.lbt_conf.channels[i].freq_hz = 0; + } + } else { + MSG("ERROR: no frequency defined for LBT channel %d\n", i); + return -1; + } + + /* Channel bandiwdth */ + val = json_object_dotget_value(conf_lbtchan_obj, "bandwidth"); /* fetch value (if possible) */ + if (val != NULL) { + if (json_value_get_type(val) == JSONNumber) { + bw = (uint32_t)json_value_get_number(val); + switch(bw) { + case 500000: sx1261conf.lbt_conf.channels[i].bandwidth = BW_500KHZ; break; + case 250000: sx1261conf.lbt_conf.channels[i].bandwidth = BW_250KHZ; break; + case 125000: sx1261conf.lbt_conf.channels[i].bandwidth = BW_125KHZ; break; + default: sx1261conf.lbt_conf.channels[i].bandwidth = BW_UNDEFINED; + } + } else { + MSG("WARNING: Data type for lbt.channels[%d].freq_hz seems wrong, please check\n", i); + sx1261conf.lbt_conf.channels[i].bandwidth = BW_UNDEFINED; + } + } else { + MSG("ERROR: no bandiwdth defined for LBT channel %d\n", i); + return -1; + } + + /* Channel scan time */ + val = json_object_dotget_value(conf_lbtchan_obj, "scan_time_us"); /* fetch value (if possible) */ + if (val != NULL) { + if (json_value_get_type(val) == JSONNumber) { + if ((uint16_t)json_value_get_number(val) == 128) { + sx1261conf.lbt_conf.channels[i].scan_time_us = LGW_LBT_SCAN_TIME_128_US; + } else if ((uint16_t)json_value_get_number(val) == 5000) { + sx1261conf.lbt_conf.channels[i].scan_time_us = LGW_LBT_SCAN_TIME_5000_US; + } else { + MSG("ERROR: scan time not supported for LBT channel %d, must be 128 or 5000\n", i); + return -1; + } + } else { + MSG("WARNING: Data type for lbt.channels[%d].scan_time_us seems wrong, please check\n", i); + sx1261conf.lbt_conf.channels[i].scan_time_us = 0; + } + } else { + MSG("ERROR: no scan_time_us defined for LBT channel %d\n", i); + return -1; + } + + /* Channel transmit time */ + val = json_object_dotget_value(conf_lbtchan_obj, "transmit_time_ms"); /* fetch value (if possible) */ + if (val != NULL) { + if (json_value_get_type(val) == JSONNumber) { + sx1261conf.lbt_conf.channels[i].transmit_time_ms = (uint16_t)json_value_get_number(val); + } else { + MSG("WARNING: Data type for lbt.channels[%d].transmit_time_ms seems wrong, please check\n", i); + sx1261conf.lbt_conf.channels[i].transmit_time_ms = 0; + } + } else { + MSG("ERROR: no transmit_time_ms defined for LBT channel %d\n", i); + return -1; + } + } + } + } + + /* all parameters parsed, submitting configuration to the HAL */ + if (lgw_sx1261_setconf(&sx1261conf) != LGW_HAL_SUCCESS) { + MSG("ERROR: Failed to configure the SX1261 radio\n"); + return -1; + } + } + /* set configuration for RF chains */ for (i = 0; i < LGW_RF_CHAIN_NB; ++i) { memset(&rfconf, 0, sizeof rfconf); /* initialize configuration structure */ @@ -476,6 +707,7 @@ static int parse_SX130x_configuration(const char * conf_file) { val = json_object_dotget_value(conf_obj, param_name); if (json_value_get_type(val) == JSONBoolean) { rfconf.tx_enable = (bool)json_value_get_boolean(val); + tx_enable[i] = rfconf.tx_enable; /* update global context for later check */ if (rfconf.tx_enable == true) { /* tx is enabled on this rf chain, we need its frequency range */ snprintf(param_name, sizeof param_name, "radio_%i.tx_freq_min", i); @@ -591,6 +823,36 @@ static int parse_SX130x_configuration(const char * conf_file) { } } + /* set configuration for demodulators */ + memset(&demodconf, 0, sizeof demodconf); /* initialize configuration structure */ + val = json_object_get_value(conf_obj, "chan_multiSF_All"); /* fetch value (if possible) */ + if (json_value_get_type(val) != JSONObject) { + MSG("INFO: no configuration for LoRa multi-SF spreading factors enabling\n"); + } else { + conf_demod_array = json_object_dotget_array(conf_obj, "chan_multiSF_All.spreading_factor_enable"); + if ((conf_demod_array != NULL) && ((size = json_array_get_count(conf_demod_array)) <= LGW_MULTI_NB)) { + for (i = 0; i < (int)size; i++) { + number = json_array_get_number(conf_demod_array, i); + if (number < 5 || number > 12) { + MSG("WARNING: failed to parse chan_multiSF_All.spreading_factor_enable (wrong value at idx %d)\n", i); + demodconf.multisf_datarate = 0xFF; /* enable all SFs */ + break; + } else { + /* set corresponding bit in the bitmask SF5 is LSB -> SF12 is MSB */ + demodconf.multisf_datarate |= (1 << (number - 5)); + } + } + } else { + MSG("WARNING: failed to parse chan_multiSF_All.spreading_factor_enable\n"); + demodconf.multisf_datarate = 0xFF; /* enable all SFs */ + } + /* all parameters parsed, submitting configuration to the HAL */ + if (lgw_demod_setconf(&demodconf) != LGW_HAL_SUCCESS) { + MSG("ERROR: invalid configuration for demodulation parameters\n"); + return -1; + } + } + /* 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 */ @@ -1176,6 +1438,7 @@ int main(int argc, char ** argv) pthread_t thrid_gps; pthread_t thrid_valid; pthread_t thrid_jit; + pthread_t thrid_ss; /* network socket creation */ struct addrinfo hints; @@ -1347,7 +1610,7 @@ int main(int argc, char ** argv) /* look for server address w/ downstream port */ i = getaddrinfo(serv_addr, serv_port_down, &hints, &result); if (i != 0) { - MSG("ERROR: [down] getaddrinfo on address %s (port %s) returned %s\n", serv_addr, serv_port_up, gai_strerror(i)); + MSG("ERROR: [down] getaddrinfo on address %s (port %s) returned %s\n", serv_addr, serv_port_down, gai_strerror(i)); exit(EXIT_FAILURE); } @@ -1358,7 +1621,7 @@ int main(int argc, char ** argv) else break; /* success, get out of loop */ } if (q == NULL) { - MSG("ERROR: [down] failed to open socket to any of server %s addresses (port %s)\n", serv_addr, serv_port_up); + MSG("ERROR: [down] failed to open socket to any of server %s addresses (port %s)\n", serv_addr, serv_port_down); i = 1; for (q=result; q!=NULL; q=q->ai_next) { getnameinfo(q->ai_addr, q->ai_addrlen, host_name, sizeof host_name, port_name, sizeof port_name, NI_NUMERICHOST); @@ -1376,10 +1639,12 @@ int main(int argc, char ** argv) } freeaddrinfo(result); - /* Board reset */ - if (system("./reset_lgw.sh start") != 0) { - printf("ERROR: failed to reset SX1302, check your reset_lgw.sh script\n"); - exit(EXIT_FAILURE); + if (com_type == LGW_COM_SPI) { + /* Board reset */ + if (system("./reset_lgw.sh start") != 0) { + printf("ERROR: failed to reset SX1302, check your reset_lgw.sh script\n"); + exit(EXIT_FAILURE); + } } for (l = 0; l < LGW_IF_CHAIN_NB; l++) { @@ -1406,30 +1671,39 @@ int main(int argc, char ** argv) } /* spawn threads to manage upstream and downstream */ - i = pthread_create( &thrid_up, NULL, (void * (*)(void *))thread_up, NULL); + i = pthread_create(&thrid_up, NULL, (void * (*)(void *))thread_up, NULL); if (i != 0) { MSG("ERROR: [main] impossible to create upstream thread\n"); exit(EXIT_FAILURE); } - i = pthread_create( &thrid_down, NULL, (void * (*)(void *))thread_down, NULL); + i = pthread_create(&thrid_down, NULL, (void * (*)(void *))thread_down, NULL); if (i != 0) { MSG("ERROR: [main] impossible to create downstream thread\n"); exit(EXIT_FAILURE); } - i = pthread_create( &thrid_jit, NULL, (void * (*)(void *))thread_jit, NULL); + i = pthread_create(&thrid_jit, NULL, (void * (*)(void *))thread_jit, NULL); if (i != 0) { MSG("ERROR: [main] impossible to create JIT thread\n"); exit(EXIT_FAILURE); } + /* spawn thread for background spectral scan */ + if (spectral_scan_params.enable == true) { + i = pthread_create(&thrid_ss, NULL, (void * (*)(void *))thread_spectral_scan, NULL); + if (i != 0) { + MSG("ERROR: [main] impossible to create Spectral Scan thread\n"); + exit(EXIT_FAILURE); + } + } + /* spawn thread to manage GPS */ if (gps_enabled == true) { - i = pthread_create( &thrid_gps, NULL, (void * (*)(void *))thread_gps, NULL); + i = pthread_create(&thrid_gps, NULL, (void * (*)(void *))thread_gps, NULL); if (i != 0) { MSG("ERROR: [main] impossible to create GPS thread\n"); exit(EXIT_FAILURE); } - i = pthread_create( &thrid_valid, NULL, (void * (*)(void *))thread_valid, NULL); + i = pthread_create(&thrid_valid, NULL, (void * (*)(void *))thread_valid, NULL); if (i != 0) { MSG("ERROR: [main] impossible to create validation thread\n"); exit(EXIT_FAILURE); @@ -1597,7 +1871,9 @@ int main(int argc, char ** argv) } else { printf("# GPS sync is disabled\n"); } + pthread_mutex_lock(&mx_concent); i = lgw_get_temperature(&temperature); + pthread_mutex_unlock(&mx_concent); if (i != LGW_HAL_SUCCESS) { printf("### Concentrator temperature unknown ###\n"); } else { @@ -1616,13 +1892,28 @@ int main(int argc, char ** argv) pthread_mutex_unlock(&mx_stat_rep); } - /* wait for upstream thread to finish (1 fetch cycle max) */ - pthread_join(thrid_up, NULL); - pthread_cancel(thrid_down); /* don't wait for downstream thread */ - pthread_cancel(thrid_jit); /* don't wait for jit thread */ + /* wait for all threads with a COM with the concentrator board to finish (1 fetch cycle max) */ + i = pthread_join(thrid_up, NULL); + if (i != 0) { + printf("ERROR: failed to join upstream thread with %d - %s\n", i, strerror(errno)); + } + i = pthread_join(thrid_down, NULL); + if (i != 0) { + printf("ERROR: failed to join downstream thread with %d - %s\n", i, strerror(errno)); + } + i = pthread_join(thrid_jit, NULL); + if (i != 0) { + printf("ERROR: failed to join JIT thread with %d - %s\n", i, strerror(errno)); + } + if (spectral_scan_params.enable == true) { + i = pthread_join(thrid_ss, NULL); + if (i != 0) { + printf("ERROR: failed to join Spectral Scan thread with %d - %s\n", i, strerror(errno)); + } + } if (gps_enabled == true) { - pthread_cancel(thrid_gps); /* don't wait for GPS thread */ - pthread_cancel(thrid_valid); /* don't wait for validation thread */ + pthread_cancel(thrid_gps); /* don't wait for GPS thread, no access to concentrator board */ + pthread_cancel(thrid_valid); /* don't wait for validation thread, no access to concentrator board */ i = lgw_gps_disable(gps_tty_fd); if (i == LGW_HAL_SUCCESS) { @@ -1646,10 +1937,12 @@ int main(int argc, char ** argv) } } - /* Board reset */ - if (system("./reset_lgw.sh stop") != 0) { - printf("ERROR: failed to reset SX1302, check your reset_lgw.sh script\n"); - exit(EXIT_FAILURE); + if (com_type == LGW_COM_SPI) { + /* Board reset */ + if (system("./reset_lgw.sh stop") != 0) { + printf("ERROR: failed to reset SX1302, check your reset_lgw.sh script\n"); + exit(EXIT_FAILURE); + } } MSG("INFO: Exiting packet forwarder program\n"); @@ -1873,6 +2166,17 @@ void thread_up(void) { } } + /* Fine timestamp */ + if (p->ftime_received == true) { + j = snprintf((char *)(buff_up + buff_index), TX_BUFF_SIZE-buff_index, ",\"ftime\":%u", p->ftime); + if (j > 0) { + buff_index += j; + } else { + MSG("ERROR: [up] snprintf failed line %u\n", (__LINE__ - 4)); + exit(EXIT_FAILURE); + } + } + /* Packet concentrator channel, RF chain & RX frequency, 34-36 useful chars */ j = snprintf((char *)(buff_up + buff_index), TX_BUFF_SIZE-buff_index, ",\"chan\":%1u,\"rfch\":%1u,\"freq\":%.6lf,\"mid\":%2u", p->if_chain, p->rf_chain, ((double)p->freq_hz / 1e6), p->modem_id); if (j > 0) { @@ -2426,7 +2730,7 @@ void thread_down(void) { /* listen to packets and process them until a new PULL request must be sent */ recv_time = send_time; - while ((int)difftimespec(recv_time, send_time) < keepalive_time) { + while (((int)difftimespec(recv_time, send_time) < keepalive_time) && !exit_sig && !quit_sig) { /* try to receive a datagram */ msg_len = recv(sock_down, (void *)buff_down, (sizeof buff_down)-1, 0); @@ -2666,6 +2970,12 @@ void thread_down(void) { txpkt.no_crc = (bool)json_value_get_boolean(val); } + /* Parse "No header" flag (optional field) */ + val = json_object_get_value(txpk_obj,"nhdr"); + if (val != NULL) { + txpkt.no_header = (bool)json_value_get_boolean(val); + } + /* parse target frequency (mandatory) */ val = json_object_get_value(txpk_obj,"freq"); if (val == NULL) { @@ -2683,6 +2993,11 @@ void thread_down(void) { continue; } txpkt.rf_chain = (uint8_t)json_value_get_number(val); + if (tx_enable[txpkt.rf_chain] == false) { + MSG("WARNING: [down] TX is not enabled on RF chain %u, TX aborted\n", txpkt.rf_chain); + json_value_free(root_val); + continue; + } /* parse TX power (optional field) */ val = json_object_get_value(txpk_obj,"powe"); @@ -2984,9 +3299,15 @@ void thread_jit(void) { /* send packet to concentrator */ pthread_mutex_lock(&mx_concent); /* may have to wait for a fetch to finish */ + if (spectral_scan_params.enable == true) { + result = lgw_spectral_scan_abort(); + if (result != LGW_HAL_SUCCESS) { + MSG("WARNING: [jit%d] lgw_spectral_scan_abort failed\n", i); + } + } result = lgw_send(&pkt); pthread_mutex_unlock(&mx_concent); /* free concentrator ASAP */ - if (result == LGW_HAL_ERROR) { + if (result != LGW_HAL_SUCCESS) { pthread_mutex_lock(&mx_meas_dw); meas_nb_tx_fail += 1; pthread_mutex_unlock(&mx_meas_dw); @@ -3009,6 +3330,8 @@ void thread_jit(void) { } } } + + MSG("\nINFO: End of JIT thread\n"); } /* -------------------------------------------------------------------------- */ @@ -3238,9 +3561,126 @@ void thread_valid(void) { // fprintf(log_file,"%.18lf,\"track\"\n", xtal_correct); // DEBUG } } - // printf("Time ref: %s, XTAL correct: %s (%.15lf)\n", ref_valid_local?"valid":"invalid", xtal_correct_ok?"valid":"invalid", xtal_correct); // DEBUG + + //printf("Time ref: %s, XTAL correct: %s (%.15lf)\n", ref_valid_local?"valid":"invalid", xtal_correct_ok?"valid":"invalid", xtal_correct); // DEBUG } MSG("\nINFO: End of validation thread\n"); } +/* -------------------------------------------------------------------------- */ +/* --- THREAD 6: BACKGROUND SPECTRAL SCAN --------- */ + +void thread_spectral_scan(void) { + int i, x; + uint32_t freq_hz = spectral_scan_params.freq_hz_start; + uint32_t freq_hz_stop = spectral_scan_params.freq_hz_start + spectral_scan_params.nb_chan * 200E3; + int16_t levels[LGW_SPECTRAL_SCAN_RESULT_SIZE]; + uint16_t results[LGW_SPECTRAL_SCAN_RESULT_SIZE]; + struct timeval tm_start; + lgw_spectral_scan_status_t status; + uint8_t tx_status = TX_FREE; + bool spectral_scan_started; + bool exit_thread = false; + + /* main loop task */ + while (!exit_sig && !quit_sig) { + /* Pace the scan thread (1 sec min), and avoid waiting several seconds when exit */ + for (i = 0; i < (int)(spectral_scan_params.pace_s ? spectral_scan_params.pace_s : 1); i++) { + if (exit_sig || quit_sig) { + exit_thread = true; + break; + } + wait_ms(1000); + } + if (exit_thread == true) { + break; + } + + spectral_scan_started = false; + + /* Start spectral scan (if no downlink programmed) */ + pthread_mutex_lock(&mx_concent); + /* -- Check if there is a downlink programmed */ + for (i = 0; i < LGW_RF_CHAIN_NB; i++) { + if (tx_enable[i] == true) { + x = lgw_status((uint8_t)i, TX_STATUS, &tx_status); + if (x != LGW_HAL_SUCCESS) { + printf("ERROR: failed to get TX status on chain %d\n", i); + } else { + if (tx_status == TX_SCHEDULED || tx_status == TX_EMITTING) { + printf("INFO: skip spectral scan (downlink programmed on RF chain %d)\n", i); + break; /* exit for loop */ + } + } + } + } + if (tx_status != TX_SCHEDULED && tx_status != TX_EMITTING) { + x = lgw_spectral_scan_start(freq_hz, spectral_scan_params.nb_scan); + if (x != 0) { + printf("ERROR: spectral scan start failed\n"); + pthread_mutex_unlock(&mx_concent); + continue; /* main while loop */ + } + spectral_scan_started = true; + } + pthread_mutex_unlock(&mx_concent); + + if (spectral_scan_started == true) { + /* Wait for scan to be completed */ + status = LGW_SPECTRAL_SCAN_STATUS_UNKNOWN; + timeout_start(&tm_start); + do { + /* handle timeout */ + if (timeout_check(tm_start, 2000) != 0) { + printf("ERROR: %s: TIMEOUT on Spectral Scan\n", __FUNCTION__); + break; /* do while */ + } + + /* get spectral scan status */ + pthread_mutex_lock(&mx_concent); + x = lgw_spectral_scan_get_status(&status); + pthread_mutex_unlock(&mx_concent); + if (x != 0) { + printf("ERROR: spectral scan status failed\n"); + break; /* do while */ + } + + /* wait a bit before checking status again */ + wait_ms(10); + } while (status != LGW_SPECTRAL_SCAN_STATUS_COMPLETED && status != LGW_SPECTRAL_SCAN_STATUS_ABORTED); + + if (status == LGW_SPECTRAL_SCAN_STATUS_COMPLETED) { + /* Get spectral scan results */ + memset(levels, 0, sizeof levels); + memset(results, 0, sizeof results); + pthread_mutex_lock(&mx_concent); + x = lgw_spectral_scan_get_results(levels, results); + pthread_mutex_unlock(&mx_concent); + if (x != 0) { + printf("ERROR: spectral scan get results failed\n"); + continue; /* main while loop */ + } + + /* print results */ + printf("SPECTRAL SCAN - %u Hz: ", freq_hz); + for (i = 0; i < LGW_SPECTRAL_SCAN_RESULT_SIZE; i++) { + printf("%u ", results[i]); + } + printf("\n"); + + /* Next frequency to scan */ + freq_hz += 200000; /* 200kHz channels */ + if (freq_hz >= freq_hz_stop) { + freq_hz = spectral_scan_params.freq_hz_start; + } + } else if (status == LGW_SPECTRAL_SCAN_STATUS_ABORTED) { + printf("INFO: %s: spectral scan has been aborted\n", __FUNCTION__); + } else { + printf("ERROR: %s: spectral scan status us unexpected 0x%02X\n", __FUNCTION__, status); + } + } + } + printf("\nINFO: End of Spectral Scan thread\n"); +} + /* --- EOF ------------------------------------------------------------------ */ diff --git a/readme.md b/readme.md index f072df89..ac396a70 100644 --- a/readme.md +++ b/readme.md @@ -3,7 +3,7 @@ \____ \| ___ | (_ _) ___ |/ ___) _ \ _____) ) ____| | | || |_| ____( (___| | | | (______/|_____)_|_|_| \__)_____)\____)_| |_| - (C)2019 Semtech + (C)2020 Semtech SX1302 LoRa Gateway project =========================== @@ -37,7 +37,7 @@ link, and emits RF packets that are sent by the server. | +- -|- - - - - - - - - - - - -+ xxxxxxxxxxxx +--------+ |+--+-----------+ +------+| xx x x xxx | | - || | | || xx Internet xx | | + || | USB | || xx Internet xx | | || Concentrator |<----+ Host |<------xx or xx-------->| | || | SPI | || xx Intranet xx | Server | |+--------------+ +------+| xxxx x xxxx | | @@ -73,6 +73,21 @@ local CSV file. Please refer to the readme.md file located in the util_net_downlink directory for more details. +### 2.3. util_chip_id ### + +This utility configures the SX1302 to be able to retrieve its EUI. It can then +be used as a Gateway ID. + +### 2.4. util_boot ### + +On used for a USB gateway, this software switches the concentrator in DFU mode +in order to program its internal STM32 MCU. + +### 2.5. util_spectral_scan ### + +This software allows to scan the spectral band using the additional sx1261 radio +of the Semtech Corecell reference design. + ## 3. Helper scripts ### 3.1. tools/reset_lgw.sh @@ -152,14 +167,102 @@ set, do: `make clean all` -## 5. Third party libraries +## 5. USB + +This project provides support for both SPI or USB gateways. For USB interface, +the concentrator board has a STM32 MCU with which the linux host will +communicate to configure the sx1302 and the associated radios. The STM32 acts +as a USB <-> SPI bridge. + +The STM32 MCU has to be programmed with the binary provided in the `mcu_bin` +directory of this project. For more details about how to flash it, please refer +to the `util_boot/readme.md` instructions. + +Each test utility of the project can be used using the `-u -d /dev/ttyACMx' +command line option, or with the proper configuration in the packet forwarder +global_conf.json file. + +## 6. Third party libraries This project relies on several third-party open source libraries, that can be found in the `libtools` directory. * parson: a JSON parser (http://kgabis.github.com/parson/) * tinymt32: a pseudo-random generator (only used for debug/test) -## 6. Changelog +## 7. Changelog + +### v2.0.0 ### + +> #### New features + +* Added support for USB interface between the HOST and the concentrator, +for sx1250 based concentrator only. +* Added support for Listen-Before-Talk for AS923 region, using the additional +sx1261 radio from the Semtech Corecell reference design v3. +* Added support for Spectral Scan with additional sx1261 radio from the Semtech +Corecell reference design v3. +* Added support for SX1303 chip, for further Fine Timestamping support. +* Merged the master-fdd-cn490 branch to bring support for CN490 Full-Duplex +reference design. It is an integration of the releases v1.1.0, v1.1.1, v1.1.2 +described below. + +> #### Changes + +* HAL: Reworked the complete communication layer. A new loragw_com module has +been introduced to handle switching from a USB or a SPI communication interface, +aligned function prototypes for sx125x, sx1250 and sx1261 radios. For USB, a +mode has been added to group SPI write commands request to the STM32 MCU, in +order to optimize latency during time critical configuration phases. +* HAL: Added preliminary support for Fine Timestamping for TDOA localization. +* HAL: Updated AGC firmware to v6: add configurable delay for PA to start, add +Listen-Before-Talk support. +* HAL: Added new API function lgw_demod_setconf() to set global demodulator +settings. +* HAL: Added new API functions for Spectral Scan. +* Packet Forwarder: The type of interface is configurable in the +global_conf.json file: com_type can be "USB" or "SPI". +* Packet Forwarder: Changed the parameters to configure fine timestamping in the +global_conf.json. +* Packet Forwarder: Added sections to configure the spectral scan and +Listen-Before-Talk features. +* Packet Forwarder: Added a new thread for background spectral scan example, +to show how to use the spectral scan API provided by the HAL, without +interfering with the main tasks of the gateway (aka Receive uplinks and transmit +downlinks). +* Packet Forwarder: Added "nhdr" field parsing from "txpk" JSON downlink request +in order to be able to send beacon request from Network Server. +* Packet Forwarder: Added chan_multiSF_All in global_conf.json to choose which +spreading factors to enable for multi-sf demodulators. +* Packet Forwarder: Updated PROTOCOL.md to v1.6. +* Tools: added util_spectral_scan, a standalone spectral scanner utility. + +> #### Notes + +* This release has been validated on the Semtech Corecell reference design v3 +with USB interface. + +### v1.1.2 ### + +> Integrated in ***v2.0.0*** from ***master-fdd-cn490*** branch. + +* packet forwarder: updated global_conf.json.sx1255.CN490.full-duplex with RSSI +temperature compensation coefficients, and updated RSSI offset for radio 1. + +### v1.1.1 ### + +> Integrated in ***v2.0.0*** from ***master-fdd-cn490*** branch. + +* HAL: Updated SX1302 LNA/PA LUT configuration for Full Duplex CN490 reference +design. +* test_loragw_hal_rx/tx: added --fdd option to enable Full Duplex +* packet forwarder: updated global_conf.json.sx1255.CN490.full-duplex for CN490 +reference design. + +### v1.1.0 ### + +> Integrated in ***v2.0.0*** from ***master-fdd-cn490*** branch. + +* HAL: Added support for CN490 full duplex reference design. ### v1.0.5 ### @@ -210,7 +313,7 @@ calibration * HAL: Initial private release for TAP program -## 7. Legal notice +## 8. 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 diff --git a/tools/reset_lgw.sh b/tools/reset_lgw.sh index 0136d72e..b58f0e98 100755 --- a/tools/reset_lgw.sh +++ b/tools/reset_lgw.sh @@ -3,6 +3,7 @@ # This script is intended to be used on SX1302 CoreCell platform, it performs # the following actions: # - export/unpexort GPIO23 and GPIO18 used to reset the SX1302 chip and to enable the LDOs +# - export/unexport GPIO22 used to reset the optional SX1261 radio used for LBT/Spectral Scan # # Usage examples: # ./reset_lgw.sh stop @@ -11,8 +12,10 @@ # GPIO mapping has to be adapted with HW # -SX1302_RESET_PIN=23 -SX1302_POWER_EN_PIN=18 +SX1302_RESET_PIN=23 # SX1302 reset +SX1302_POWER_EN_PIN=18 # SX1302 power enable +SX1261_RESET_PIN=22 # SX1261 reset (LBT / Spectral Scan) +AD5338R_RESET_PIN=13 # AD5338R reset (full-duplex CN490 reference design) WAIT_GPIO() { sleep 0.1 @@ -21,22 +24,34 @@ WAIT_GPIO() { init() { # setup GPIOs echo "$SX1302_RESET_PIN" > /sys/class/gpio/export; WAIT_GPIO + echo "$SX1261_RESET_PIN" > /sys/class/gpio/export; WAIT_GPIO echo "$SX1302_POWER_EN_PIN" > /sys/class/gpio/export; WAIT_GPIO + echo "$AD5338R_RESET_PIN" > /sys/class/gpio/export; WAIT_GPIO # set GPIOs as output echo "out" > /sys/class/gpio/gpio$SX1302_RESET_PIN/direction; WAIT_GPIO + echo "out" > /sys/class/gpio/gpio$SX1261_RESET_PIN/direction; WAIT_GPIO echo "out" > /sys/class/gpio/gpio$SX1302_POWER_EN_PIN/direction; WAIT_GPIO + echo "out" > /sys/class/gpio/gpio$AD5338R_RESET_PIN/direction; WAIT_GPIO } reset() { echo "CoreCell reset through GPIO$SX1302_RESET_PIN..." + echo "SX1261 reset through GPIO$SX1302_RESET_PIN..." echo "CoreCell power enable through GPIO$SX1302_POWER_EN_PIN..." + echo "CoreCell ADC reset through GPIO$AD5338R_RESET_PIN..." # write output for SX1302 CoreCell power_enable and reset echo "1" > /sys/class/gpio/gpio$SX1302_POWER_EN_PIN/value; WAIT_GPIO echo "1" > /sys/class/gpio/gpio$SX1302_RESET_PIN/value; WAIT_GPIO echo "0" > /sys/class/gpio/gpio$SX1302_RESET_PIN/value; WAIT_GPIO + + echo "0" > /sys/class/gpio/gpio$SX1261_RESET_PIN/value; WAIT_GPIO + echo "1" > /sys/class/gpio/gpio$SX1261_RESET_PIN/value; WAIT_GPIO + + echo "0" > /sys/class/gpio/gpio$AD5338R_RESET_PIN/value; WAIT_GPIO + echo "1" > /sys/class/gpio/gpio$AD5338R_RESET_PIN/value; WAIT_GPIO } term() { @@ -45,10 +60,18 @@ term() { then echo "$SX1302_RESET_PIN" > /sys/class/gpio/unexport; WAIT_GPIO fi + if [ -d /sys/class/gpio/gpio$SX1261_RESET_PIN ] + then + echo "$SX1261_RESET_PIN" > /sys/class/gpio/unexport; WAIT_GPIO + fi if [ -d /sys/class/gpio/gpio$SX1302_POWER_EN_PIN ] then echo "$SX1302_POWER_EN_PIN" > /sys/class/gpio/unexport; WAIT_GPIO fi + if [ -d /sys/class/gpio/gpio$AD5338R_RESET_PIN ] + then + echo "$AD5338R_RESET_PIN" > /sys/class/gpio/unexport; WAIT_GPIO + fi } case "$1" in diff --git a/util_boot/Makefile b/util_boot/Makefile new file mode 100644 index 00000000..399db85d --- /dev/null +++ b/util_boot/Makefile @@ -0,0 +1,83 @@ +### get external defined data + +include ../target.cfg + +### User defined build options + +ARCH ?= +CROSS_COMPILE ?= +BUILD_MODE := release +OBJDIR = obj + +### ----- AVOID MODIFICATIONS BELLOW ------ AVOID MODIFICATIONS BELLOW ----- ### + +ifeq '$(BUILD_MODE)' 'alpha' + $(warning /\/\/\/ Building in 'alpha' mode \/\/\/\) + WARN_CFLAGS := + OPT_CFLAGS := -O0 + DEBUG_CFLAGS := -g + LDFLAGS := +else ifeq '$(BUILD_MODE)' 'debug' + $(warning /\/\/\/ Building in 'debug' mode \/\/\/\) + WARN_CFLAGS := -Wall -Wextra + OPT_CFLAGS := -O2 + DEBUG_CFLAGS := -g + LDFLAGS := +else ifeq '$(BUILD_MODE)' 'release' + $(warning /\/\/\/ Building in 'release' mode \/\/\/\) + WARN_CFLAGS := -Wall -Wextra + OPT_CFLAGS := -O2 -ffunction-sections -fdata-sections + DEBUG_CFLAGS := + LDFLAGS := -Wl,--gc-sections +else + $(error BUILD_MODE must be set to either 'alpha', 'debug' or 'release') +endif + +### Application-specific variables +APP_NAME := boot +APP_LIBS := -lloragw -lm -ltinymt32 -lrt + +### Environment constants +LIB_PATH := ../libloragw + +### Expand build options +CFLAGS := -std=c99 $(WARN_CFLAGS) $(OPT_CFLAGS) $(DEBUG_CFLAGS) +CC := $(CROSS_COMPILE)gcc +AR := $(CROSS_COMPILE)ar + +### General build targets +all: $(APP_NAME) + +clean: + rm -f obj/*.o + rm -f $(APP_NAME) + +install: +ifneq ($(strip $(TARGET_IP)),) + ifneq ($(strip $(TARGET_DIR)),) + ifneq ($(strip $(TARGET_USR)),) + @echo "---- Copying boot files to $(TARGET_IP):$(TARGET_DIR)" + @ssh $(TARGET_USR)@$(TARGET_IP) "mkdir -p $(TARGET_DIR)" + @scp boot $(TARGET_USR)@$(TARGET_IP):$(TARGET_DIR) + else + @echo "ERROR: TARGET_USR is not configured in target.cfg" + endif + else + @echo "ERROR: TARGET_DIR is not configured in target.cfg" + endif +else + @echo "ERROR: TARGET_IP is not configured in target.cfg" +endif + +$(OBJDIR): + mkdir -p $(OBJDIR) + +### Compile main program +$(OBJDIR)/$(APP_NAME).o: src/$(APP_NAME).c | $(OBJDIR) + $(CC) -c $< -o $@ $(CFLAGS) -Iinc -I../libloragw/inc + +### Link everything together +$(APP_NAME): $(OBJDIR)/$(APP_NAME).o $(LIB_PATH)/libloragw.a + $(CC) -L$(LIB_PATH) -L../libtools $^ -o $@ $(LDFLAGS) $(APP_LIBS) + +### EOF diff --git a/util_boot/readme.md b/util_boot/readme.md new file mode 100644 index 00000000..610f46a0 --- /dev/null +++ b/util_boot/readme.md @@ -0,0 +1,63 @@ + ______ _ + / _____) _ | | + ( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | + (______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2020 Semtech-Cycleo + +Switch LoRa concentrator MCU to DFU mode (USB gateway) +====================================================== + + +## 1. Introduction + +This software is used to switch the concentrator in DFU mode in order to program +its internal MCU. + +## 2. Usage + +```console +./boot -d /dev/ttyACMx +``` + +## 3. Program binary file into internal MCU flash memory + +Download the dfu-util tool from here: http://dfu-util.sourceforge.net + +```console +sudo dfu-util -a 0 -s 0x08000000:leave -t 0 -D ../mcu_bin/xxx_fwm_xxx.bin +``` + +This will automatically exit the DFU bootloader mode, the concentrator is now +ready to be used. + +## 4. License + +Copyright (c) 2020, SEMTECH S.A. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +* Neither the name of the Semtech corporation nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL SEMTECH S.A. BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*EOF* diff --git a/util_boot/src/boot.c b/util_boot/src/boot.c new file mode 100644 index 00000000..82c80b6f --- /dev/null +++ b/util_boot/src/boot.c @@ -0,0 +1,132 @@ +/* + ______ _ + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2020 Semtech + +Description: + Utility to switch the concentrator MCU in DFU boot mode (USB gateway) + +License: Revised BSD License, see LICENSE.TXT file include in the project +*/ + + +/* -------------------------------------------------------------------------- */ +/* --- DEPENDANCIES --------------------------------------------------------- */ + +/* fix an issue between POSIX and C99 */ +#if __STDC_VERSION__ >= 199901L + #define _XOPEN_SOURCE 600 +#else + #define _XOPEN_SOURCE 500 +#endif + +#include +#include +#include +#include /* PRIx64, PRIu64... */ +#include +#include +#include +#include /* sigaction */ +#include /* getopt_long */ + +#include "loragw_usb.h" +#include "loragw_mcu.h" +#include "loragw_hal.h" + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE MACROS ------------------------------------------------------- */ + +#define RAND_RANGE(min, max) (rand() % (max + 1 - min) + min) + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE CONSTANTS ---------------------------------------------------- */ + +#define TTY_PATH_DEFAULT "/dev/ttyACM0" + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE VARIABLES ---------------------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE FUNCTIONS ---------------------------------------------------- */ + +/* 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(" -d [path] TTY path to be used to access the concentrator\n"); +} + +/* -------------------------------------------------------------------------- */ +/* --- MAIN FUNCTION -------------------------------------------------------- */ + +int main(int argc, char **argv) +{ + int i, x; + int fd; + + /* USB interfaces */ + const char tty_path_default[] = TTY_PATH_DEFAULT; + const char * tty_path = tty_path_default; + void* com_target = NULL; + + /* Parameter parsing */ + int option_index = 0; + static struct option long_options[] = { + {0, 0, 0, 0} + }; + + /* parse command line options */ + while ((i = getopt_long (argc, argv, "hd:", long_options, &option_index)) != -1) { + switch (i) { + case 'h': + usage(); + return -1; + break; + + case 'd': + tty_path = optarg; + break; + + default: + printf("ERROR: argument parsing\n"); + usage(); + return -1; + } + } + + /* Open connexion with the MCU over USB */ + x = lgw_usb_open(tty_path, &com_target); + if (x != 0) { + printf("ERROR: failed to open USB on %s\n", tty_path); + return EXIT_FAILURE; + } + + /* Switch the MCU in Bootloader mode */ + fd = *(int *)com_target; + x = mcu_boot(fd); + if (x != 0) { + printf("ERROR: failed to switch MCU in BOOT mode\n"); + return EXIT_FAILURE; + } + + printf("INFO: The concentrator is in DFU mode, the MCU can be programmed\n"); + + /* close USB connexion (do not call lgw_usb_close() to avoid sending + write_gpio commands that the MCU won't be able to receive as already in + bootloader mode) */ + x = close(fd); + if (x != 0) { + printf("ERROR: failed to close USB\n"); + return EXIT_FAILURE; + } + + return 0; +} + +/* --- EOF ------------------------------------------------------------------ */ diff --git a/util_chip_id/Makefile b/util_chip_id/Makefile index baed4005..50b110c4 100644 --- a/util_chip_id/Makefile +++ b/util_chip_id/Makefile @@ -77,7 +77,7 @@ $(OBJDIR)/$(APP_NAME).o: src/$(APP_NAME).c | $(OBJDIR) $(CC) -c $< -o $@ $(CFLAGS) -Iinc -I../libloragw/inc ### Link everything together -$(APP_NAME): $(OBJDIR)/$(APP_NAME).o +$(APP_NAME): $(OBJDIR)/$(APP_NAME).o $(LIB_PATH)/libloragw.a $(CC) -L$(LIB_PATH) -L../libtools $^ -o $@ $(LDFLAGS) $(APP_LIBS) ### EOF diff --git a/util_chip_id/readme.md b/util_chip_id/readme.md index 879f788a..5135e725 100644 --- a/util_chip_id/readme.md +++ b/util_chip_id/readme.md @@ -4,7 +4,7 @@ \____ \| ___ | (_ _) ___ |/ ___) _ \ _____) ) ____| | | || |_| ____( (___| | | | (______/|_____)_|_|_| \__)_____)\____)_| |_| - (C)2019 Semtech + (C)2020 Semtech Utility to get SX1302 chip EUI ============================== @@ -24,10 +24,15 @@ will display a short help and version informations. ### 2.2. SPI options ### -`-d filename` +`-d spidev_path` use the Linux SPI device driver, but with an explicit path, for systems with several SPI device drivers, or uncommon numbering scheme. +### 2.3. USB options ### + +`-u -d tty_path` +use the TTY path associated with the gateway. + ## 3. Legal notice The information presented in this project documentation does not form part of diff --git a/util_chip_id/src/chip_id.c b/util_chip_id/src/chip_id.c index 76afe330..ae670b4c 100644 --- a/util_chip_id/src/chip_id.c +++ b/util_chip_id/src/chip_id.c @@ -46,7 +46,8 @@ License: Revised BSD License, see LICENSE.TXT file include in the project /* -------------------------------------------------------------------------- */ /* --- PRIVATE CONSTANTS ---------------------------------------------------- */ -#define LINUXDEV_PATH_DEFAULT "/dev/spidev0.0" +#define COM_TYPE_DEFAULT LGW_COM_SPI +#define COM_PATH_DEFAULT "/dev/spidev0.0" #define DEFAULT_CLK_SRC 0 #define DEFAULT_FREQ_HZ 868500000U @@ -61,8 +62,10 @@ License: Revised BSD License, see LICENSE.TXT file include in the project void usage(void) { printf("Library version information: %s\n", lgw_version_info()); printf("Available options:\n"); - printf(" -h print this help\n"); - printf(" -d [path] Path the spidev file (ex: /dev/spidev0.0)\n"); + printf(" -h Print this help\n"); + printf(" -u Set COM type as USB (default is SPI)\n"); + printf(" -d [path] Path to the COM interface\n"); + printf(" => default path: " COM_PATH_DEFAULT "\n"); printf(" -k Concentrator clock source (Radio A or Radio B) [0..1]\n"); printf(" -r Radio type (1255, 1257, 1250)\n"); } @@ -82,8 +85,9 @@ int main(int argc, char **argv) uint64_t eui; /* SPI interfaces */ - const char spidev_path_default[] = LINUXDEV_PATH_DEFAULT; - const char * spidev_path = spidev_path_default; + const char com_path_default[] = COM_PATH_DEFAULT; + const char * com_path = com_path_default; + lgw_com_type_t com_type = COM_TYPE_DEFAULT; /* Parameter parsing */ int option_index = 0; @@ -92,15 +96,19 @@ int main(int argc, char **argv) }; /* parse command line options */ - while ((i = getopt_long (argc, argv, "hd:k:r:", long_options, &option_index)) != -1) { + while ((i = getopt_long (argc, argv, "hud:k:r:", long_options, &option_index)) != -1) { switch (i) { case 'h': usage(); return -1; break; + case 'u': + com_type = LGW_COM_USB; + break; + case 'd': - spidev_path = optarg; + com_path = optarg; break; case 'r': /* Radio type */ @@ -140,10 +148,12 @@ int main(int argc, char **argv) } } - /* Board reset */ - if (system("./reset_lgw.sh start") != 0) { - printf("ERROR: failed to reset SX1302, check your reset_lgw.sh script\n"); - exit(EXIT_FAILURE); + if (com_type == LGW_COM_SPI) { + /* Board reset */ + if (system("./reset_lgw.sh start") != 0) { + printf("ERROR: failed to reset SX1302, check your reset_lgw.sh script\n"); + exit(EXIT_FAILURE); + } } /* Configure the gateway */ @@ -151,8 +161,9 @@ int main(int argc, char **argv) boardconf.lorawan_public = true; boardconf.clksrc = clocksource; boardconf.full_duplex = false; - strncpy(boardconf.spidev_path, spidev_path, sizeof boardconf.spidev_path); - boardconf.spidev_path[sizeof boardconf.spidev_path - 1] = '\0'; /* ensure string termination */ + boardconf.com_type = com_type; + strncpy(boardconf.com_path, com_path, sizeof boardconf.com_path); + boardconf.com_path[sizeof boardconf.com_path - 1] = '\0'; /* ensure string termination */ if (lgw_board_setconf(&boardconf) != LGW_HAL_SUCCESS) { printf("ERROR: failed to configure board\n"); return EXIT_FAILURE; @@ -201,10 +212,12 @@ int main(int argc, char **argv) return EXIT_FAILURE; } - /* Board reset */ - if (system("./reset_lgw.sh stop") != 0) { - printf("ERROR: failed to reset SX1302, check your reset_lgw.sh script\n"); - exit(EXIT_FAILURE); + if (com_type == LGW_COM_SPI) { + /* Board reset */ + if (system("./reset_lgw.sh stop") != 0) { + printf("ERROR: failed to reset SX1302, check your reset_lgw.sh script\n"); + exit(EXIT_FAILURE); + } } return 0; diff --git a/util_net_downlink/src/net_downlink.c b/util_net_downlink/src/net_downlink.c index 80ce0d32..0020a5b9 100644 --- a/util_net_downlink/src/net_downlink.c +++ b/util_net_downlink/src/net_downlink.c @@ -851,7 +851,7 @@ int main( int argc, char **argv ) { if( is_first == true ) { - fprintf(log_file, "tmst,chan,rfch,freq,mid,stat,modu,datr,bw,codr,rssic,rssis,lsnr,size,data\n"); + fprintf(log_file, "tmst,ftime,chan,rfch,freq,mid,stat,modu,datr,bw,codr,rssic,rssis,lsnr,size,data\n"); is_first = false; } log_csv( log_file, &databuf_up[12] ); @@ -931,6 +931,22 @@ static void log_csv(FILE * file, uint8_t * buf) } fprintf(file, "%u", (uint32_t)json_value_get_number( val ) ); + /* optional field */ + val = json_object_get_value( rxpk, "ftime" ); + if( val != NULL ) + { + + if( json_value_get_type( val ) != JSONNumber ) + { + printf( "ERROR: wrong type for tmst\n" ); + json_value_free( root_val ); + return; + } + fprintf(file, ",%u", (uint32_t)json_value_get_number( val ) ); + } else { + fprintf(file, "," ); + } + val = json_object_get_value( rxpk, "chan" ); if( json_value_get_type( val ) != JSONNumber ) { @@ -1220,6 +1236,7 @@ void prepare_downlink_json( const thread_params_t * params, uint8_t rf_chain, ui pream_sz = params->preamb_size[rf_chain]; json_object_set_number( obj, "prea", pream_sz ); json_object_set_boolean( obj, "ncrc", true ); + json_object_set_boolean( obj, "nhdr", false ); json_object_set_number( obj, "size", params->pl_size[rf_chain] ); /* Fill last bytes of payload with downlink counter (32 bits) */ diff --git a/util_spectral_scan/Makefile b/util_spectral_scan/Makefile new file mode 100644 index 00000000..93220096 --- /dev/null +++ b/util_spectral_scan/Makefile @@ -0,0 +1,83 @@ +### get external defined data + +include ../target.cfg + +### User defined build options + +ARCH ?= +CROSS_COMPILE ?= +BUILD_MODE := release +OBJDIR = obj + +### ----- AVOID MODIFICATIONS BELLOW ------ AVOID MODIFICATIONS BELLOW ----- ### + +ifeq '$(BUILD_MODE)' 'alpha' + $(warning /\/\/\/ Building in 'alpha' mode \/\/\/\) + WARN_CFLAGS := + OPT_CFLAGS := -O0 + DEBUG_CFLAGS := -g + LDFLAGS := +else ifeq '$(BUILD_MODE)' 'debug' + $(warning /\/\/\/ Building in 'debug' mode \/\/\/\) + WARN_CFLAGS := -Wall -Wextra + OPT_CFLAGS := -O2 + DEBUG_CFLAGS := -g + LDFLAGS := +else ifeq '$(BUILD_MODE)' 'release' + $(warning /\/\/\/ Building in 'release' mode \/\/\/\) + WARN_CFLAGS := -Wall -Wextra + OPT_CFLAGS := -O2 -ffunction-sections -fdata-sections + DEBUG_CFLAGS := + LDFLAGS := -Wl,--gc-sections +else + $(error BUILD_MODE must be set to either 'alpha', 'debug' or 'release') +endif + +### Application-specific variables +APP_NAME := spectral_scan +APP_LIBS := -lloragw -lm -ltinymt32 -lrt + +### Environment constants +LIB_PATH := ../libloragw + +### Expand build options +CFLAGS := -std=c99 $(WARN_CFLAGS) $(OPT_CFLAGS) $(DEBUG_CFLAGS) +CC := $(CROSS_COMPILE)gcc +AR := $(CROSS_COMPILE)ar + +### General build targets +all: $(APP_NAME) + +clean: + rm -f obj/*.o + rm -f $(APP_NAME) + +install: +ifneq ($(strip $(TARGET_IP)),) + ifneq ($(strip $(TARGET_DIR)),) + ifneq ($(strip $(TARGET_USR)),) + @echo "---- Copying spectral_scan files to $(TARGET_IP):$(TARGET_DIR)" + @ssh $(TARGET_USR)@$(TARGET_IP) "mkdir -p $(TARGET_DIR)" + @scp spectral_scan $(TARGET_USR)@$(TARGET_IP):$(TARGET_DIR) + else + @echo "ERROR: TARGET_USR is not configured in target.cfg" + endif + else + @echo "ERROR: TARGET_DIR is not configured in target.cfg" + endif +else + @echo "ERROR: TARGET_IP is not configured in target.cfg" +endif + +$(OBJDIR): + mkdir -p $(OBJDIR) + +### Compile main program +$(OBJDIR)/$(APP_NAME).o: src/$(APP_NAME).c | $(OBJDIR) + $(CC) -c $< -o $@ $(CFLAGS) -Iinc -I../libloragw/inc + +### Link everything together +$(APP_NAME): $(OBJDIR)/$(APP_NAME).o $(LIB_PATH)/libloragw.a + $(CC) -L$(LIB_PATH) -L../libtools $^ -o $@ $(LDFLAGS) $(APP_LIBS) + +### EOF diff --git a/util_spectral_scan/plot_rssi_histogram.py b/util_spectral_scan/plot_rssi_histogram.py new file mode 100644 index 00000000..6411e065 --- /dev/null +++ b/util_spectral_scan/plot_rssi_histogram.py @@ -0,0 +1,64 @@ +#!/usr/bin/python +# -*- encoding: utf-8 -*- + +# ______ _ +# / _____) _ | | +# ( (____ _____ ____ _| |_ _____ ____| |__ +# \____ \| ___ | (_ _) ___ |/ ___) _ \ +# _____) ) ____| | | || |_| ____( (___| | | | +# (______/|_____)_|_|_| \__)_____)\____)_| |_| +# (C)2020 Semtech +# +# Description: +# Spectral Scan CSV result file plot - v0.3.0 +# +# License: Revised BSD License, see LICENSE.TXT file include in the project + +#Library importation +import pylab as pl +import numpy as np +import csv +import sys + +#Read argument +if len(sys.argv) >= 2: + filename = sys.argv[1] +else: + print ("Usage: %s " %sys.argv[0]) + sys.exit() + +#Initiate array +rssi = [] +freq = [] + +#Process .csv file +with open(filename, 'r') as csvfile: + reader = csv.reader(csvfile, delimiter=',', quotechar='|') + for row in reader: + f=int(row[0])//1000 #frequency + freq.append(f) + rssi_line=[] + rssi_val=[] + for k in range(1,(len(row)-1)//2): + rssi_line.append(int(row[k*2])) + rssi_val.append(int(row[k*2-1])) + rssi.append(rssi_line) + +#Set x to frequency axis and y to signal level axis +A = np.array(rssi).T +A = np.flip(A,0) +rssi_val =np.flip(rssi_val,0) + +#Find max value to scale heatmap then plot. +maxx = max(max(rssi)) +print(maxx, A) +h=pl.imshow(A , cmap='afmhot', vmin=0, vmax=maxx, aspect='auto', origin='lower', interpolation='nearest') +pl.xticks(range(0,len(freq),10),freq[::10]) +pl.yticks(range(0,len(rssi[1]),2),rssi_val[0::2]) +pl.show() + + + + + + diff --git a/util_spectral_scan/readme.md b/util_spectral_scan/readme.md new file mode 100644 index 00000000..c47c2e31 --- /dev/null +++ b/util_spectral_scan/readme.md @@ -0,0 +1,85 @@ + ______ _ + / _____) _ | | + ( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | + (______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2020 Semtech + +Spectral Scan Utility +===================== + + +## 1. Introduction + +This software allows to scan the spectral band using the additional sx1261 radio +of the Semtech Corecell reference design. +It computes a RSSI histogram on several frequencies, that will help to detect +occupied bands and get interferer profiles. +It logs the histogram in a .csv file. + +## 2. Command line options + +### 2.1. General options ### + +`-h` +will display a short help and version informations. + +### 2.2. SPI options ### + +`-d spidev_path` +specifies the spi_dev path to be used to acces the sx1261 radio. + +### 2.3. USB options ### + +`-u -d tty_path` +specifies the tty path to be used to acces the MCU which will redirect commands +to the sx1261 radio. + +## 3. Usage + +This utility is autonomous and will perform a full initialization of the +concentrator. So it cannot be run in parallel of the packet forwarder. + +It will scan channels as specified on the command line, the first channel being +the given frequency (-f argument) and the other channels (number given with -n +argument) shifted by 200kHz from the previous one. + +It then generates a CSV file with the RSSI histogram for each channel. + +## 4. Plotting the results + +In order to have a visual representation of the spectral scan results, a python +script is provided here. rssi_histogram.csv is the file generated by the +spectral_scan utility. + +```bash +python3 plot_rssi_histogram.py rssi_histogram.csv +``` + +The python script uses `pylab` and `numpy` packages, so both have to be installed +prior to using the `plot_rssi_histogram.py` script. + +## 5. 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* \ No newline at end of file diff --git a/util_spectral_scan/src/spectral_scan.c b/util_spectral_scan/src/spectral_scan.c new file mode 100644 index 00000000..0c482482 --- /dev/null +++ b/util_spectral_scan/src/spectral_scan.c @@ -0,0 +1,367 @@ +/* + ______ _ + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2020 Semtech + +Description: + Spectral Scan Utility + +License: Revised BSD License, see LICENSE.TXT file include in the project +*/ + + +/* -------------------------------------------------------------------------- */ +/* --- DEPENDANCIES --------------------------------------------------------- */ + +/* fix an issue between POSIX and C99 */ +#if __STDC_VERSION__ >= 199901L + #define _XOPEN_SOURCE 600 +#else + #define _XOPEN_SOURCE 500 +#endif + +#include +#include +#include +#include /* PRIx64, PRIu64... */ +#include +#include +#include +#include /* sigaction */ +#include /* getopt_long */ + +#include "loragw_hal.h" +#include "loragw_aux.h" + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE MACROS ------------------------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE CONSTANTS ---------------------------------------------------- */ + +#define COM_TYPE_DEFAULT LGW_COM_SPI +#define COM_PATH_DEFAULT "/dev/spidev0.0" +#define SX1261_PATH_DEFAULT "/dev/spidev0.1" + +#define DEFAULT_CLK_SRC 0 +#define DEFAULT_RADIO_TYPE LGW_RADIO_TYPE_SX1250 +#define DEFAULT_FREQ_HZ 863100000U +#define DEFAULT_NB_CHAN 35 +#define DEFAULT_NB_SCAN 2000 +#define DEFAULT_RSSI_OFFSET -11 /* RSSI offset of SX1261 */ + +#define DEFAULT_LOG_NAME "rssi_histogram" + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE VARIABLES ---------------------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* --- PRIVATE FUNCTIONS ---------------------------------------------------- */ + +/* 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(" -u Set COM type as USB (default is SPI)\n"); + printf(" -d [path] Path to the main COM interface\n"); + printf(" => default path: " COM_PATH_DEFAULT "\n"); + printf(" -D [path] Path to the SX1261 SPI interface (not used for USB)\n"); + printf(" => default path: " SX1261_PATH_DEFAULT "\n"); + printf(" -f Scan start frequency, in MHz\n"); + printf(" -n Number of channels to scan\n"); + printf(" -s Number of scan points per frequency step [1..65535]\n"); + printf(" -o RSSI Offset of the sx1261 path, in dB [-127..128]\n"); + printf(" -l Log file name\n"); +} + +/* -------------------------------------------------------------------------- */ +/* --- MAIN FUNCTION -------------------------------------------------------- */ + +int main(int argc, char **argv) +{ + int i, j, x; + unsigned int arg_u; + double arg_d = 0.0; + int arg_i; + char arg_s[64]; + + struct lgw_conf_board_s boardconf; + struct lgw_conf_rxrf_s rfconf; + struct lgw_conf_sx1261_s sx1261conf; + + /* COM interface */ + const char com_path_default[] = COM_PATH_DEFAULT; + const char * com_path = com_path_default; + lgw_com_type_t com_type = COM_TYPE_DEFAULT; + const char sx1261_path_default[] = SX1261_PATH_DEFAULT; + const char * sx1261_path = sx1261_path_default; + + /* Spectral Scan */ + uint32_t freq_hz = DEFAULT_FREQ_HZ; + uint8_t nb_channels = DEFAULT_NB_CHAN; + uint16_t nb_scan = DEFAULT_NB_SCAN; + int8_t rssi_offset = DEFAULT_RSSI_OFFSET; + int16_t levels[LGW_SPECTRAL_SCAN_RESULT_SIZE]; + uint16_t results[LGW_SPECTRAL_SCAN_RESULT_SIZE]; + char log_file_name[64] = DEFAULT_LOG_NAME; + FILE * log_file = NULL; + + struct timeval tm_start; + lgw_spectral_scan_status_t status; + + /* Parameter parsing */ + int option_index = 0; + static struct option long_options[] = { + {0, 0, 0, 0} + }; + + /* parse command line options */ + while ((i = getopt_long (argc, argv, "hud:f:n:o:s:l:D:", long_options, &option_index)) != -1) { + switch (i) { + case 'h': + usage(); + return -1; + break; + + case 'u': + com_type = LGW_COM_USB; + break; + + case 'd': + com_path = optarg; + break; + + case 'D': + sx1261_path = optarg; + break; + + case 'f': /* Scan start frequency, in MHz */ + i = sscanf(optarg, "%lf", &arg_d); + if (i != 1) { + printf("ERROR: argument parsing of -f argument. Use -h to print help\n"); + return EXIT_FAILURE; + } else { + freq_hz = (uint32_t)((arg_d*1e6) + 0.5); /* .5 Hz offset to get rounding instead of truncating */ + } + break; + + case 'n': /* Number of channels to scan */ + i = sscanf(optarg, "%u", &arg_u); + if (i != 1) { + printf("ERROR: argument parsing of -n argument. Use -h to print help\n"); + return EXIT_FAILURE; + } else { + if (arg_u > 255) { + printf("ERROR: Number of channels must be < 255\n"); + return EXIT_FAILURE; + } + nb_channels = (uint8_t)arg_u; + } + break; + + case 's': + i = sscanf(optarg, "%u", &arg_u); + if (i != 1) { + printf("ERROR: argument parsing of -n argument. Use -h to print help\n"); + return EXIT_FAILURE; + } else { + if (arg_u > 65535) { + printf("ERROR: Number of scan must be < 65535\n"); + return EXIT_FAILURE; + } + nb_scan = (uint16_t)arg_u; + } + break; + + case 'o': /* SX1261 RSSI offset in dB */ + i = sscanf(optarg, "%d", &arg_i); + if (i != 1) { + printf("ERROR: argument parsing of -o argument. Use -h to print help\n"); + return EXIT_FAILURE; + } else { + if (arg_i < -127 || arg_i > 128) { + printf("ERROR: SX1261 RSSI value out of range\n"); + return EXIT_FAILURE; + } + rssi_offset = (int8_t)arg_i; + } + break; + + case 'l': /* -l Log file name */ + j = sscanf(optarg, "%63s", arg_s); + if (j != 1) { + printf("ERROR: argument parsing of -l argument. Use -h to print help\n"); + return EXIT_FAILURE; + } else { + sprintf(log_file_name, "%s", arg_s); + } + break; + + default: + printf("ERROR: argument parsing\n"); + usage(); + return -1; + } + } + + printf("==\n"); + printf("== Spectral Scan: freq_hz=%uHz, nb_channels=%u, nb_scan=%u, rssi_offset=%ddB\n", freq_hz, nb_channels, nb_scan, rssi_offset); + printf("==\n"); + + if (com_type == LGW_COM_SPI) { + /* Board reset */ + if (system("./reset_lgw.sh start") != 0) { + printf("ERROR: failed to reset SX1302, check your reset_lgw.sh script\n"); + exit(EXIT_FAILURE); + } + } + + /* Configure the gateway */ + memset(&boardconf, 0, sizeof boardconf); + boardconf.lorawan_public = true; + boardconf.clksrc = DEFAULT_CLK_SRC; + boardconf.full_duplex = false; + boardconf.com_type = com_type; + strncpy(boardconf.com_path, com_path, sizeof boardconf.com_path); + boardconf.com_path[sizeof boardconf.com_path - 1] = '\0'; /* ensure string termination */ + if (lgw_board_setconf(&boardconf) != LGW_HAL_SUCCESS) { + printf("ERROR: failed to configure board\n"); + return EXIT_FAILURE; + } + + memset(&rfconf, 0, sizeof rfconf); + rfconf.enable = true; + rfconf.freq_hz = 867500000; /* dummy */ + rfconf.type = DEFAULT_RADIO_TYPE; + rfconf.tx_enable = false; + rfconf.single_input_mode = false; + if (lgw_rxrf_setconf(0, &rfconf) != LGW_HAL_SUCCESS) { + printf("ERROR: failed to configure rxrf 0\n"); + return EXIT_FAILURE; + } + + memset(&rfconf, 0, sizeof rfconf); + rfconf.enable = true; + rfconf.freq_hz = 868500000; /* dummy */ + rfconf.type = DEFAULT_RADIO_TYPE; + rfconf.tx_enable = false; + rfconf.single_input_mode = false; + if (lgw_rxrf_setconf(1, &rfconf) != LGW_HAL_SUCCESS) { + printf("ERROR: failed to configure rxrf 1\n"); + return EXIT_FAILURE; + } + + /* Configure the sx1261 for spectral scan */ + memset(&sx1261conf, 0, sizeof sx1261conf); + sx1261conf.enable = true; + strncpy(sx1261conf.spi_path, sx1261_path, sizeof sx1261conf.spi_path); + sx1261conf.spi_path[sizeof sx1261conf.spi_path - 1] = '\0'; /* ensure string termination */ + sx1261conf.rssi_offset = rssi_offset; + sx1261conf.lbt_conf.enable = false; + if (lgw_sx1261_setconf(&sx1261conf) != LGW_HAL_SUCCESS) { + printf("ERROR: failed to configure sx1261\n"); + return EXIT_FAILURE; + } + + /* Start the gateway, initialize sx1261 radio for scanning */ + x = lgw_start(); + if (x != 0) { + printf("ERROR: failed to start the gateway\n"); + return EXIT_FAILURE; + } + + /* create log file */ + strcat(log_file_name,".csv"); + log_file = fopen(log_file_name, "w"); + if (log_file == NULL) { + printf("ERROR: impossible to create log file %s\n", log_file_name); + return EXIT_FAILURE; + } + + /* Launch Spectral Scan on each channels */ + for (j = 0; j < nb_channels; j++) { + x = lgw_spectral_scan_start(freq_hz, 2000); + if (x != 0) { + printf("ERROR: spectral scan start failed\n"); + continue; + } + + /* Wait for scan to be completed */ + timeout_start(&tm_start); + do { + /* handle timeout */ + if (timeout_check(tm_start, 2000) != 0) { + printf("ERROR: %s: TIMEOUT on Spectral Scan\n", __FUNCTION__); + continue; + } + + /* get spectral scan status */ + status = LGW_SPECTRAL_SCAN_STATUS_UNKNOWN; + x = lgw_spectral_scan_get_status(&status); + if (x != 0) { + printf("ERROR: spectral scan status failed\n"); + break; + } + + wait_ms(10); + } while (status != LGW_SPECTRAL_SCAN_STATUS_COMPLETED && status != LGW_SPECTRAL_SCAN_STATUS_ABORTED); + + if (status == LGW_SPECTRAL_SCAN_STATUS_COMPLETED) { + memset(levels, 0, sizeof levels); + memset(results, 0, sizeof results); + x = lgw_spectral_scan_get_results(levels, results); + if (x != 0) { + printf("ERROR: spectral scan get results failed\n"); + continue; + } + + /* log results */ + fprintf(log_file, "%u", freq_hz); + for (i = 0; i < LGW_SPECTRAL_SCAN_RESULT_SIZE; i++) { + fprintf(log_file, ",%d,%u", levels[i], results[i]); + } + fprintf(log_file, "\n"); + + /* print results */ + printf("%u: ", freq_hz); + for (i = 0; i < LGW_SPECTRAL_SCAN_RESULT_SIZE; i++) { + printf("%u ", results[i]); + } + printf("\n"); + + /* Next frequency to scan */ + freq_hz += 200000; /* 200kHz channels */ + } else if (status == LGW_SPECTRAL_SCAN_STATUS_ABORTED) { + printf("INFO: spectral scan has been aborted\n"); + } else { + printf("ERROR: spectral scan status us unexpected 0x%02X\n", status); + } + } + + /* close log file */ + fclose(log_file); + + /* Stop the gateway */ + x = lgw_stop(); + if (x != 0) { + printf("ERROR: failed to stop the gateway\n"); + return EXIT_FAILURE; + } + + if (com_type == LGW_COM_SPI) { + /* Board reset */ + if (system("./reset_lgw.sh stop") != 0) { + printf("ERROR: failed to reset SX1302, check your reset_lgw.sh script\n"); + exit(EXIT_FAILURE); + } + } + + return 0; +} + +/* --- EOF ------------------------------------------------------------------ */