summaryrefslogtreecommitdiff
path: root/libloragw
diff options
context:
space:
mode:
authorHarsh Sharma <92harshsharma@gmail.com>2018-06-13 13:24:54 -0500
committerHarsh Sharma <92harshsharma@gmail.com>2018-06-13 13:24:54 -0500
commit7c383be1542368f2601015d9fc2a417197677677 (patch)
treebc06453f879cbadf65fd88123c506956403c5684 /libloragw
downloadlora_gateway_mtac_full-7c383be1542368f2601015d9fc2a417197677677.tar.gz
lora_gateway_mtac_full-7c383be1542368f2601015d9fc2a417197677677.tar.bz2
lora_gateway_mtac_full-7c383be1542368f2601015d9fc2a417197677677.zip
Initial Commit
Diffstat (limited to 'libloragw')
-rw-r--r--libloragw/99-libftdi.rules8
-rw-r--r--libloragw/Makefile91
-rw-r--r--libloragw/inc/loragw_aux.h48
-rw-r--r--libloragw/inc/loragw_fpga.h135
-rw-r--r--libloragw/inc/loragw_gps.h235
-rw-r--r--libloragw/inc/loragw_hal.h419
-rw-r--r--libloragw/inc/loragw_lbt.h70
-rw-r--r--libloragw/inc/loragw_radio.h73
-rw-r--r--libloragw/inc/loragw_reg.h461
-rw-r--r--libloragw/inc/loragw_spi.h105
-rw-r--r--libloragw/inc/loragw_sx125x.h49
-rw-r--r--libloragw/inc/loragw_sx1272_fsk.h113
-rw-r--r--libloragw/inc/loragw_sx1272_lora.h89
-rw-r--r--libloragw/inc/loragw_sx1276_fsk.h112
-rw-r--r--libloragw/inc/loragw_sx1276_lora.h94
-rw-r--r--libloragw/library.cfg12
-rw-r--r--libloragw/readme.md457
-rw-r--r--libloragw/src/agc_fw.var529
-rw-r--r--libloragw/src/arb_fw.var529
-rw-r--r--libloragw/src/cal_fw.var529
-rw-r--r--libloragw/src/loragw_aux.c61
-rw-r--r--libloragw/src/loragw_fpga.c352
-rw-r--r--libloragw/src/loragw_gps.c835
-rw-r--r--libloragw/src/loragw_hal.c1767
-rw-r--r--libloragw/src/loragw_lbt.c391
-rw-r--r--libloragw/src/loragw_radio.c562
-rw-r--r--libloragw/src/loragw_reg.c819
-rw-r--r--libloragw/src/loragw_spi.native.c385
-rw-r--r--libloragw/tst/test_loragw_cal.c756
-rw-r--r--libloragw/tst/test_loragw_gps.c288
-rw-r--r--libloragw/tst/test_loragw_hal.c416
-rw-r--r--libloragw/tst/test_loragw_reg.c134
-rw-r--r--libloragw/tst/test_loragw_spi.c85
33 files changed, 11009 insertions, 0 deletions
diff --git a/libloragw/99-libftdi.rules b/libloragw/99-libftdi.rules
new file mode 100644
index 0000000..8487413
--- /dev/null
+++ b/libloragw/99-libftdi.rules
@@ -0,0 +1,8 @@
+# FTDI Devices: FT232BM/L/Q, FT245BM/L/Q, FT232RL/Q, FT245RL/Q, VNC1L with VDPS Firmware
+SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6001", MODE="0664", GROUP="plugdev"
+
+# FTDI Devices: FT2232C/D/L, FT2232HL/Q
+SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6010", MODE="0664", GROUP="plugdev"
+
+# FTDI Devices: FT4232HL/Q
+SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6011", MODE="0664", GROUP="plugdev"
diff --git a/libloragw/Makefile b/libloragw/Makefile
new file mode 100644
index 0000000..53c33d9
--- /dev/null
+++ b/libloragw/Makefile
@@ -0,0 +1,91 @@
+### get external defined data
+
+LIBLORAGW_VERSION := `cat ../VERSION`
+include library.cfg
+
+### constant symbols
+
+ARCH ?=
+CROSS_COMPILE ?=
+CC := $(CROSS_COMPILE)gcc
+AR := $(CROSS_COMPILE)ar
+
+CFLAGS := -O2 -Wall -Wextra -std=c99 -Iinc -I.
+
+OBJDIR = obj
+INCLUDES = $(wildcard inc/*.h)
+
+### linking options
+
+LIBS := -lloragw -lrt -lm
+
+### general build targets
+
+all: libloragw.a test_loragw_spi test_loragw_reg test_loragw_hal test_loragw_gps test_loragw_cal
+
+clean:
+ rm -f libloragw.a
+ rm -f test_loragw_*
+ rm -f $(OBJDIR)/*.o
+ rm -f inc/config.h
+
+### transpose library.cfg into a C header file : config.h
+
+inc/config.h: ../VERSION library.cfg
+ @echo "*** Checking libloragw library configuration ***"
+ @rm -f $@
+ #File initialization
+ @echo "#ifndef _LORAGW_CONFIGURATION_H" >> $@
+ @echo "#define _LORAGW_CONFIGURATION_H" >> $@
+ # Release version
+ @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_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)" >> $@
+ # end of file
+ @echo "#endif" >> $@
+ @echo "*** Configuration seems ok ***"
+
+### library module target
+
+$(OBJDIR):
+ mkdir -p $(OBJDIR)
+
+$(OBJDIR)/%.o: src/%.c $(INCLUDES) inc/config.h | $(OBJDIR)
+ $(CC) -c $(CFLAGS) $< -o $@
+
+$(OBJDIR)/loragw_spi.o: src/loragw_spi.native.c $(INCLUDES) inc/config.h | $(OBJDIR)
+ $(CC) -c $(CFLAGS) $< -o $@
+
+$(OBJDIR)/loragw_hal.o: src/loragw_hal.c $(INCLUDES) src/arb_fw.var src/agc_fw.var src/cal_fw.var inc/config.h | $(OBJDIR)
+ $(CC) -c $(CFLAGS) $< -o $@
+
+### static library
+
+libloragw.a: $(OBJDIR)/loragw_hal.o $(OBJDIR)/loragw_gps.o $(OBJDIR)/loragw_reg.o $(OBJDIR)/loragw_spi.o $(OBJDIR)/loragw_aux.o $(OBJDIR)/loragw_radio.o $(OBJDIR)/loragw_fpga.o $(OBJDIR)/loragw_lbt.o
+ $(AR) rcs $@ $^
+
+### test programs
+
+test_loragw_spi: tst/test_loragw_spi.c libloragw.a
+ $(CC) $(CFLAGS) -L. $< -o $@ $(LIBS)
+
+test_loragw_reg: tst/test_loragw_reg.c libloragw.a
+ $(CC) $(CFLAGS) -L. $< -o $@ $(LIBS)
+
+test_loragw_hal: tst/test_loragw_hal.c libloragw.a
+ $(CC) $(CFLAGS) -L. $< -o $@ $(LIBS)
+
+test_loragw_gps: tst/test_loragw_gps.c libloragw.a
+ $(CC) $(CFLAGS) -L. $< -o $@ $(LIBS)
+
+test_loragw_cal: tst/test_loragw_cal.c libloragw.a src/cal_fw.var
+ $(CC) $(CFLAGS) -L. $< -o $@ $(LIBS)
+
+### EOF
diff --git a/libloragw/inc/loragw_aux.h b/libloragw/inc/loragw_aux.h
new file mode 100644
index 0000000..35b39ab
--- /dev/null
+++ b/libloragw/inc/loragw_aux.h
@@ -0,0 +1,48 @@
+/*
+ / _____) _ | |
+( (____ _____ ____ _| |_ _____ ____| |__
+ \____ \| ___ | (_ _) ___ |/ ___) _ \
+ _____) ) ____| | | || |_| ____( (___| | | |
+(______/|_____)_|_|_| \__)_____)\____)_| |_|
+ (C)2013 Semtech-Cycleo
+
+Description:
+ LoRa concentrator HAL common auxiliary functions
+
+License: Revised BSD License, see LICENSE.TXT file include in the project
+Maintainer: Sylvain Miermont
+*/
+
+
+#ifndef _LORAGW_AUX_H
+#define _LORAGW_AUX_H
+
+/* -------------------------------------------------------------------------- */
+/* --- DEPENDANCIES --------------------------------------------------------- */
+
+#include "config.h" /* library configuration options (dynamically generated) */
+
+/* -------------------------------------------------------------------------- */
+/* --- PUBLIC MACROS -------------------------------------------------------- */
+
+/**
+@brief Get a particular bit value from a byte
+@param b [in] Any byte from which we want a bit value
+@param p [in] Position of the bit in the byte [0..7]
+@param n [in] Number of bits we want to get
+@return The value corresponding the requested bits
+*/
+#define TAKE_N_BITS_FROM(b, p, n) (((b) >> (p)) & ((1 << (n)) - 1))
+
+/* -------------------------------------------------------------------------- */
+/* --- PUBLIC FUNCTIONS PROTOTYPES ------------------------------------------ */
+
+/**
+@brief Wait for a certain time (millisecond accuracy)
+@param t number of milliseconds to wait.
+*/
+void wait_ms(unsigned long t);
+
+#endif
+
+/* --- EOF ------------------------------------------------------------------ */
diff --git a/libloragw/inc/loragw_fpga.h b/libloragw/inc/loragw_fpga.h
new file mode 100644
index 0000000..f599f73
--- /dev/null
+++ b/libloragw/inc/loragw_fpga.h
@@ -0,0 +1,135 @@
+/*
+ / _____) _ | |
+( (____ _____ ____ _| |_ _____ ____| |__
+ \____ \| ___ | (_ _) ___ |/ ___) _ \
+ _____) ) ____| | | || |_| ____( (___| | | |
+(______/|_____)_|_|_| \__)_____)\____)_| |_|
+ (C)2013 Semtech-Cycleo
+
+Description:
+ Functions used to handle FPGA register access for LoRa concentrator.
+ Registers are addressed by name.
+ Multi-bytes registers are handled automatically.
+ Read-modify-write is handled automatically.
+
+License: Revised BSD License, see LICENSE.TXT file include in the project
+Maintainer: Michael Coracin
+*/
+
+#ifndef _LORAGW_FPGA_REG_H
+#define _LORAGW_FPGA_REG_H
+
+/* -------------------------------------------------------------------------- */
+/* --- DEPENDANCIES --------------------------------------------------------- */
+
+#include <stdint.h> /* C99 types */
+#include <stdbool.h> /* bool type */
+
+/* -------------------------------------------------------------------------- */
+/* --- PUBLIC CONSTANTS ----------------------------------------------------- */
+
+#define LGW_REG_SUCCESS 0
+#define LGW_REG_ERROR -1
+
+#define LGW_MIN_NOTCH_FREQ 126000U /* 126 KHz */
+#define LGW_MAX_NOTCH_FREQ 250000U /* 250 KHz */
+#define LGW_DEFAULT_NOTCH_FREQ 129000U /* 129 KHz */
+
+/*
+auto generated register mapping for C code
+this file contains autogenerated C struct used to access the FPGA registers
+this file is autogenerated from registers description
+*/
+
+#define LGW_FPGA_SOFT_RESET 0
+#define LGW_FPGA_FEATURE 1
+#define LGW_FPGA_LBT_INITIAL_FREQ 2
+#define LGW_FPGA_VERSION 3
+#define LGW_FPGA_STATUS 4
+#define LGW_FPGA_CTRL_FEATURE_START 5
+#define LGW_FPGA_CTRL_RADIO_RESET 6
+#define LGW_FPGA_CTRL_INPUT_SYNC_I 7
+#define LGW_FPGA_CTRL_INPUT_SYNC_Q 8
+#define LGW_FPGA_CTRL_OUTPUT_SYNC 9
+#define LGW_FPGA_CTRL_INVERT_IQ 10
+#define LGW_FPGA_CTRL_ACCESS_HISTO_MEM 11
+#define LGW_FPGA_CTRL_CLEAR_HISTO_MEM 12
+#define LGW_FPGA_HISTO_RAM_ADDR 13
+#define LGW_FPGA_HISTO_RAM_DATA 14
+#define LGW_FPGA_HISTO_NB_READ 15
+#define LGW_FPGA_LBT_TIMESTAMP_CH 16
+#define LGW_FPGA_LBT_TIMESTAMP_SELECT_CH 17
+#define LGW_FPGA_LBT_CH0_FREQ_OFFSET 18
+#define LGW_FPGA_LBT_CH1_FREQ_OFFSET 19
+#define LGW_FPGA_LBT_CH2_FREQ_OFFSET 20
+#define LGW_FPGA_LBT_CH3_FREQ_OFFSET 21
+#define LGW_FPGA_LBT_CH4_FREQ_OFFSET 22
+#define LGW_FPGA_LBT_CH5_FREQ_OFFSET 23
+#define LGW_FPGA_LBT_CH6_FREQ_OFFSET 24
+#define LGW_FPGA_LBT_CH7_FREQ_OFFSET 25
+#define LGW_FPGA_SCAN_FREQ_OFFSET 26
+#define LGW_FPGA_LBT_SCAN_TIME_CH0 27
+#define LGW_FPGA_LBT_SCAN_TIME_CH1 28
+#define LGW_FPGA_LBT_SCAN_TIME_CH2 29
+#define LGW_FPGA_LBT_SCAN_TIME_CH3 30
+#define LGW_FPGA_LBT_SCAN_TIME_CH4 31
+#define LGW_FPGA_LBT_SCAN_TIME_CH5 32
+#define LGW_FPGA_LBT_SCAN_TIME_CH6 33
+#define LGW_FPGA_LBT_SCAN_TIME_CH7 34
+#define LGW_FPGA_RSSI_TARGET 35
+#define LGW_FPGA_HISTO_SCAN_FREQ 36
+#define LGW_FPGA_NOTCH_FREQ_OFFSET 37
+#define LGW_FPGA_TOTALREGS 38
+
+/* -------------------------------------------------------------------------- */
+/* --- PUBLIC FUNCTIONS PROTOTYPES ------------------------------------------ */
+
+/**
+@brief LoRa concentrator TX notch filter delay
+@return delay in microseconds introduced by TX notch filter
+*/
+float lgw_fpga_get_tx_notch_delay(void);
+
+/**
+@brief LoRa concentrator FPGA configuration
+@param tx_notch_freq TX notch filter frequency, in Hertz
+@return status of register operation (LGW_REG_SUCCESS/LGW_REG_ERROR)
+*/
+int lgw_fpga_configure(uint32_t tx_notch_freq);
+
+/**
+@brief LoRa concentrator FPGA register write
+@param register_id register number in the data structure describing registers
+@param reg_value signed value to write to the register (for u32, use cast)
+@return status of register operation (LGW_REG_SUCCESS/LGW_REG_ERROR)
+*/
+int lgw_fpga_reg_w(uint16_t register_id, int32_t reg_value);
+
+/**
+@brief LoRa concentrator FPGA register read
+@param register_id register number in the data structure describing registers
+@param reg_value pointer to a variable where to write register read value
+@return status of register operation (LGW_REG_SUCCESS/LGW_REG_ERROR)
+*/
+int lgw_fpga_reg_r(uint16_t register_id, int32_t *reg_value);
+
+/**
+@brief LoRa concentrator FPGA register burst write
+@param register_id register number in the data structure describing registers
+@param data pointer to byte array that will be sent to the LoRa concentrator
+@param size size of the transfer, in byte(s)
+@return status of register operation (LGW_REG_SUCCESS/LGW_REG_ERROR)
+*/
+int lgw_fpga_reg_wb(uint16_t register_id, uint8_t *data, uint16_t size);
+
+/**
+@brief LoRa concentrator FPGA 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 size size of the transfer, in byte(s)
+@return status of register operation (LGW_REG_SUCCESS/LGW_REG_ERROR)
+*/
+int lgw_fpga_reg_rb(uint16_t register_id, uint8_t *data, uint16_t size);
+
+#endif
+/* --- EOF ------------------------------------------------------------------ */
diff --git a/libloragw/inc/loragw_gps.h b/libloragw/inc/loragw_gps.h
new file mode 100644
index 0000000..6dbd30b
--- /dev/null
+++ b/libloragw/inc/loragw_gps.h
@@ -0,0 +1,235 @@
+/*
+ / _____) _ | |
+( (____ _____ ____ _| |_ _____ ____| |__
+ \____ \| ___ | (_ _) ___ |/ ___) _ \
+ _____) ) ____| | | || |_| ____( (___| | | |
+(______/|_____)_|_|_| \__)_____)\____)_| |_|
+ (C)2013 Semtech-Cycleo
+
+Description:
+ Library of functions to manage a GNSS module (typically GPS) for accurate
+ timestamping of packets and synchronisation of gateways.
+ A limited set of module brands/models are supported.
+
+License: Revised BSD License, see LICENSE.TXT file include in the project
+Maintainer: Michael Coracin
+*/
+
+
+#ifndef _LORAGW_GPS_H
+#define _LORAGW_GPS_H
+
+/* -------------------------------------------------------------------------- */
+/* --- DEPENDANCIES --------------------------------------------------------- */
+
+#define _GNU_SOURCE
+#include <stdint.h> /* C99 types */
+#include <time.h> /* time library */
+#include <termios.h> /* speed_t */
+#include <unistd.h> /* ssize_t */
+
+#include "config.h" /* library configuration options (dynamically generated) */
+
+/* -------------------------------------------------------------------------- */
+/* --- PUBLIC TYPES --------------------------------------------------------- */
+
+/**
+@struct coord_s
+@brief Time solution required for timestamp to absolute time conversion
+*/
+struct tref {
+ time_t systime; /*!> system time when solution was calculated */
+ uint32_t count_us; /*!> reference concentrator internal timestamp */
+ struct timespec utc; /*!> reference UTC time (from GPS/NMEA) */
+ struct timespec gps; /*!> reference GPS time (since 01.Jan.1980) */
+ double xtal_err; /*!> raw clock error (eg. <1 'slow' XTAL) */
+};
+
+/**
+@struct coord_s
+@brief Geodesic coordinates
+*/
+struct coord_s {
+ double lat; /*!> latitude [-90,90] (North +, South -) */
+ double lon; /*!> longitude [-180,180] (East +, West -)*/
+ short alt; /*!> altitude in meters (WGS 84 geoid ref.) */
+};
+
+/**
+@enum gps_msg
+@brief Type of GPS (and other GNSS) sentences
+*/
+enum gps_msg {
+ UNKNOWN, /*!> neutral value */
+ IGNORED, /*!> frame was not parsed by the system */
+ INVALID, /*!> system try to parse frame but failed */
+ INCOMPLETE, /*!> frame parsed was missing bytes */
+ /* NMEA messages of interest */
+ NMEA_RMC, /*!> Recommended Minimum data (time + date) */
+ NMEA_GGA, /*!> Global positioning system fix data (pos + alt) */
+ NMEA_GNS, /*!> GNSS fix data (pos + alt, sat number) */
+ NMEA_ZDA, /*!> Time and Date */
+ /* NMEA message useful for time reference quality assessment */
+ NMEA_GBS, /*!> GNSS Satellite Fault Detection */
+ NMEA_GST, /*!> GNSS Pseudo Range Error Statistics */
+ NMEA_GSA, /*!> GNSS DOP and Active Satellites (sat number) */
+ NMEA_GSV, /*!> GNSS Satellites in View (sat SNR) */
+ /* Misc. NMEA messages */
+ NMEA_GLL, /*!> Latitude and longitude, with time fix and status */
+ NMEA_TXT, /*!> Text Transmission */
+ NMEA_VTG, /*!> Course over ground and Ground speed */
+ /* uBlox proprietary NMEA messages of interest */
+ UBX_NAV_TIMEGPS, /*!> GPS Time Solution */
+ UBX_NAV_TIMEUTC /*!> UTC Time Solution */
+};
+
+/* -------------------------------------------------------------------------- */
+/* --- PUBLIC CONSTANTS ----------------------------------------------------- */
+
+#define LGW_GPS_SUCCESS 0
+#define LGW_GPS_ERROR -1
+
+#define LGW_GPS_MIN_MSG_SIZE (8)
+#define LGW_GPS_UBX_SYNC_CHAR (0xB5)
+#define LGW_GPS_NMEA_SYNC_CHAR (0x24)
+
+/* -------------------------------------------------------------------------- */
+/* --- PUBLIC FUNCTIONS PROTOTYPES ------------------------------------------ */
+
+/**
+@brief Configure a GPS module
+
+@param tty_path path to the TTY connected to the GPS
+@param gps_familly parameter (eg. ubx6 for uBlox gen.6)
+@param target_brate target baudrate for communication (0 keeps default target baudrate)
+@param fd_ptr pointer to a variable to receive file descriptor on GPS tty
+@return success if the function was able to connect and configure a GPS module
+*/
+int lgw_gps_enable(char* tty_path, char* gps_familly, speed_t target_brate, int* fd_ptr);
+
+/**
+@brief Restore GPS serial configuration and close serial device
+
+@param fd file descriptor on GPS tty
+@return success if the function was able to complete
+*/
+int lgw_gps_disable(int fd);
+
+/**
+@brief Parse messages coming from the GPS system (or other GNSS)
+
+@param serial_buff pointer to the string to be parsed
+@param buff_size maximum string lengths for NMEA parsing (incl. null char)
+@return type of frame parsed
+
+The RAW NMEA sentences are parsed to a global set of variables shared with the
+lgw_gps_get function.
+If the lgw_parse_nmea and lgw_gps_get are used in different threads, a mutex
+lock must be acquired before calling either function.
+*/
+enum gps_msg lgw_parse_nmea(const char* serial_buff, int buff_size);
+
+/**
+@brief Parse Ublox proprietary messages coming from the GPS system
+
+@param serial_buff pointer to the string to be parsed
+@param buff_size maximum string lengths for UBX parsing (incl. null char)
+@param msg_size number of bytes parsed as UBX message if found
+@return type of frame parsed
+
+The RAW UBX sentences are parsed to a global set of variables shared with the
+lgw_gps_get function.
+If the lgw_parse_ubx and lgw_gps_get are used in different threads, a mutex
+lock must be acquired before calling either function.
+*/
+enum gps_msg lgw_parse_ubx(const char* serial_buff, size_t buff_size, size_t *msg_size);
+
+/**
+@brief Get the GPS solution (space & time) for the concentrator
+
+@param utc pointer to store UTC time, with ns precision (NULL to ignore)
+@param gps_time pointer to store GPS time, with ns precision (NULL to ignore)
+@param loc pointer to store coordinates (NULL to ignore)
+@param err pointer to store coordinates standard deviation (NULL to ignore)
+@return success if the chosen elements could be returned
+
+This function read the global variables generated by the NMEA/UBX parsing
+functions lgw_parse_nmea/lgw_parse_ubx. It returns time and location data in a
+format that is exploitable by other functions in that library sub-module.
+If the lgw_parse_nmea/lgw_parse_ubx and lgw_gps_get are used in different
+threads, a mutex lock must be acquired before calling either function.
+*/
+int lgw_gps_get(struct timespec *utc, struct timespec *gps_time, struct coord_s *loc, struct coord_s *err);
+
+/**
+@brief Get time and position information from the serial GPS last message received
+@param utc UTC time, with ns precision (leap seconds are ignored)
+@param gps_time timestamp of last time pulse from the GPS module converted to the UNIX epoch
+ (leap seconds are ignored)
+@param loc location information
+@param err location error estimate if supported
+@return success if timestamp was read and time reference could be refreshed
+
+Set systime to 0 in ref to trigger initial synchronization.
+*/
+int lgw_gps_sync(struct tref *ref, uint32_t count_us, struct timespec utc, struct timespec gps_time);
+
+/**
+@brief Convert concentrator timestamp counter value to UTC time
+
+@param ref time reference structure required for time conversion
+@param count_us internal timestamp counter of the LoRa concentrator
+@param utc pointer to store UTC time, with ns precision (leap seconds ignored)
+@return success if the function was able to convert timestamp to UTC
+
+This function is typically used when a packet is received to transform the
+internal counter-based timestamp in an absolute timestamp with an accuracy in
+the order of a couple microseconds (ns resolution).
+*/
+int lgw_cnt2utc(struct tref ref, uint32_t count_us, struct timespec* utc);
+
+/**
+@brief Convert UTC time to concentrator timestamp counter value
+
+@param ref time reference structure required for time conversion
+@param utc UTC time, with ns precision (leap seconds are ignored)
+@param count_us pointer to store internal timestamp counter of LoRa concentrator
+@return success if the function was able to convert UTC to timestamp
+
+This function is typically used when a packet must be sent at an accurate time
+(eg. to send a piggy-back response after receiving a packet from a node) to
+transform an absolute UTC time into a matching internal concentrator timestamp.
+*/
+int lgw_utc2cnt(struct tref ref,struct timespec utc, uint32_t* count_us);
+
+/**
+@brief Convert concentrator timestamp counter value to GPS time
+
+@param ref time reference structure required for time conversion
+@param count_us internal timestamp counter of the LoRa concentrator
+@param gps_time pointer to store GPS time, with ns precision (leap seconds ignored)
+@return success if the function was able to convert timestamp to GPS time
+
+This function is typically used when a packet is received to transform the
+internal counter-based timestamp in an absolute timestamp with an accuracy in
+the order of a millisecond.
+*/
+int lgw_cnt2gps(struct tref ref, uint32_t count_us, struct timespec* gps_time);
+
+/**
+@brief Convert GPS time to concentrator timestamp counter value
+
+@param ref time reference structure required for time conversion
+@param gps_time GPS time, with ns precision (leap seconds are ignored)
+@param count_us pointer to store internal timestamp counter of LoRa concentrator
+@return success if the function was able to convert GPS time to timestamp
+
+This function is typically used when a packet must be sent at an accurate time
+(eg. to send a piggy-back response after receiving a packet from a node) to
+transform an absolute GPS time into a matching internal concentrator timestamp.
+*/
+int lgw_gps2cnt(struct tref ref, struct timespec gps_time, uint32_t* count_us);
+
+#endif
+
+/* --- EOF ------------------------------------------------------------------ */
diff --git a/libloragw/inc/loragw_hal.h b/libloragw/inc/loragw_hal.h
new file mode 100644
index 0000000..d5f9ade
--- /dev/null
+++ b/libloragw/inc/loragw_hal.h
@@ -0,0 +1,419 @@
+/*
+ / _____) _ | |
+( (____ _____ ____ _| |_ _____ ____| |__
+ \____ \| ___ | (_ _) ___ |/ ___) _ \
+ _____) ) ____| | | || |_| ____( (___| | | |
+(______/|_____)_|_|_| \__)_____)\____)_| |_|
+ (C)2013 Semtech-Cycleo
+
+Description:
+ LoRa concentrator Hardware Abstraction Layer
+
+License: Revised BSD License, see LICENSE.TXT file include in the project
+Maintainer: Sylvain Miermont
+*/
+
+
+#ifndef _LORAGW_HAL_H
+#define _LORAGW_HAL_H
+
+/* -------------------------------------------------------------------------- */
+/* --- DEPENDANCIES --------------------------------------------------------- */
+
+#include <stdint.h> /* C99 types */
+#include <stdbool.h> /* bool type */
+
+#include "config.h" /* library configuration options (dynamically generated) */
+
+/* -------------------------------------------------------------------------- */
+/* --- PUBLIC MACROS -------------------------------------------------------- */
+
+#define IS_LORA_BW(bw) ((bw == BW_125KHZ) || (bw == BW_250KHZ) || (bw == BW_500KHZ))
+#define IS_LORA_STD_DR(dr) ((dr == DR_LORA_SF7) || (dr == DR_LORA_SF8) || (dr == DR_LORA_SF9) || (dr == DR_LORA_SF10) || (dr == DR_LORA_SF11) || (dr == DR_LORA_SF12))
+#define IS_LORA_MULTI_DR(dr) ((dr & ~DR_LORA_MULTI) == 0) /* ones outside of DR_LORA_MULTI bitmask -> not a combination of LoRa datarates */
+#define IS_LORA_CR(cr) ((cr == CR_LORA_4_5) || (cr == CR_LORA_4_6) || (cr == CR_LORA_4_7) || (cr == CR_LORA_4_8))
+
+#define IS_FSK_BW(bw) ((bw >= 1) && (bw <= 7))
+#define IS_FSK_DR(dr) ((dr >= DR_FSK_MIN) && (dr <= DR_FSK_MAX))
+
+#define IS_TX_MODE(mode) ((mode == IMMEDIATE) || (mode == TIMESTAMPED) || (mode == ON_GPS))
+
+/* -------------------------------------------------------------------------- */
+/* --- PUBLIC CONSTANTS ----------------------------------------------------- */
+
+/* return status code */
+#define LGW_HAL_SUCCESS 0
+#define LGW_HAL_ERROR -1
+#define LGW_LBT_ISSUE 1
+
+/* radio-specific parameters */
+#define LGW_XTAL_FREQU 32000000 /* frequency of the RF reference oscillator */
+#define LGW_RF_CHAIN_NB 2 /* number of RF chains */
+#define LGW_RF_RX_BANDWIDTH {1000000, 1000000} /* bandwidth of the radios */
+
+/* type of if_chain + modem */
+#define IF_UNDEFINED 0
+#define IF_LORA_STD 0x10 /* if + standard single-SF LoRa modem */
+#define IF_LORA_MULTI 0x11 /* if + LoRa receiver with multi-SF capability */
+#define IF_FSK_STD 0x20 /* if + standard FSK modem */
+
+/* concentrator chipset-specific parameters */
+/* to use array parameters, declare a local const and use 'if_chain' as index */
+#define LGW_IF_CHAIN_NB 10 /* number of IF+modem RX chains */
+#define LGW_PKT_FIFO_SIZE 16 /* depth of the RX packet FIFO */
+#define LGW_DATABUFF_SIZE 1024 /* size in bytes of the RX data buffer (contains payload & metadata) */
+#define LGW_REF_BW 125000 /* typical bandwidth of data channel */
+#define LGW_MULTI_NB 8 /* number of LoRa 'multi SF' chains */
+#define LGW_IFMODEM_CONFIG {\
+ IF_LORA_MULTI, \
+ IF_LORA_MULTI, \
+ IF_LORA_MULTI, \
+ IF_LORA_MULTI, \
+ IF_LORA_MULTI, \
+ IF_LORA_MULTI, \
+ IF_LORA_MULTI, \
+ IF_LORA_MULTI, \
+ IF_LORA_STD, \
+ IF_FSK_STD } /* configuration of available IF chains and modems on the hardware */
+
+/* values available for the 'modulation' parameters */
+/* NOTE: arbitrary values */
+#define MOD_UNDEFINED 0
+#define MOD_LORA 0x10
+#define MOD_FSK 0x20
+
+/* values available for the 'bandwidth' parameters (LoRa & FSK) */
+/* NOTE: directly encode FSK RX bandwidth, do not change */
+#define BW_UNDEFINED 0
+#define BW_500KHZ 0x01
+#define BW_250KHZ 0x02
+#define BW_125KHZ 0x03
+#define BW_62K5HZ 0x04
+#define BW_31K2HZ 0x05
+#define BW_15K6HZ 0x06
+#define BW_7K8HZ 0x07
+
+/* values available for the 'datarate' parameters */
+/* NOTE: LoRa values used directly to code SF bitmask in 'multi' modem, do not change */
+#define DR_UNDEFINED 0
+#define DR_LORA_SF7 0x02
+#define DR_LORA_SF8 0x04
+#define DR_LORA_SF9 0x08
+#define DR_LORA_SF10 0x10
+#define DR_LORA_SF11 0x20
+#define DR_LORA_SF12 0x40
+#define DR_LORA_MULTI 0x7E
+/* NOTE: for FSK directly use baudrate between 500 bauds and 250 kbauds */
+#define DR_FSK_MIN 500
+#define DR_FSK_MAX 250000
+
+/* values available for the 'coderate' parameters (LoRa only) */
+/* NOTE: arbitrary values */
+#define CR_UNDEFINED 0
+#define CR_LORA_4_5 0x01
+#define CR_LORA_4_6 0x02
+#define CR_LORA_4_7 0x03
+#define CR_LORA_4_8 0x04
+
+/* values available for the 'status' parameter */
+/* NOTE: values according to hardware specification */
+#define STAT_UNDEFINED 0x00
+#define STAT_NO_CRC 0x01
+#define STAT_CRC_BAD 0x11
+#define STAT_CRC_OK 0x10
+
+/* values available for the 'tx_mode' parameter */
+#define IMMEDIATE 0
+#define TIMESTAMPED 1
+#define ON_GPS 2
+//#define ON_EVENT 3
+//#define GPS_DELAYED 4
+//#define EVENT_DELAYED 5
+
+/* values available for 'select' in the status function */
+#define TX_STATUS 1
+#define RX_STATUS 2
+
+/* status code for TX_STATUS */
+/* NOTE: arbitrary values */
+#define TX_STATUS_UNKNOWN 0
+#define TX_OFF 1 /* TX modem disabled, it will ignore commands */
+#define TX_FREE 2 /* TX modem is free, ready to receive a command */
+#define TX_SCHEDULED 3 /* TX modem is loaded, ready to send the packet after an event and/or delay */
+#define TX_EMITTING 4 /* TX modem is emitting */
+
+/* status code for RX_STATUS */
+/* NOTE: arbitrary values */
+#define RX_STATUS_UNKNOWN 0
+#define RX_OFF 1 /* RX modem is disabled, it will ignore commands */
+#define RX_ON 2 /* RX modem is receiving */
+#define RX_SUSPENDED 3 /* RX is suspended while a TX is ongoing */
+
+/* Maximum size of Tx gain LUT */
+#define TX_GAIN_LUT_SIZE_MAX 16
+
+/* LBT constants */
+#define LBT_CHANNEL_FREQ_NB 8 /* Number of LBT channels */
+
+/* -------------------------------------------------------------------------- */
+/* --- PUBLIC TYPES --------------------------------------------------------- */
+
+/**
+@enum lgw_radio_type_e
+@brief Radio types that can be found on the LoRa Gateway
+*/
+enum lgw_radio_type_e {
+ LGW_RADIO_TYPE_NONE,
+ LGW_RADIO_TYPE_SX1255,
+ LGW_RADIO_TYPE_SX1257,
+ LGW_RADIO_TYPE_SX1272,
+ LGW_RADIO_TYPE_SX1276
+};
+
+/**
+@struct lgw_conf_board_s
+@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 */
+};
+
+/**
+@struct lgw_conf_lbt_chan_s
+@brief Configuration structure for LBT channels
+*/
+struct lgw_conf_lbt_chan_s {
+ uint32_t freq_hz;
+ uint16_t scan_time_us;
+};
+
+/**
+@struct lgw_conf_lbt_s
+@brief Configuration structure for LBT specificities
+*/
+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_lbt_chan_s channels[LBT_CHANNEL_FREQ_NB];
+ int8_t rssi_offset; /*!> RSSI offset to be applied to SX127x RSSI values */
+};
+
+/**
+@struct lgw_conf_rxrf_s
+@brief Configuration structure for a RF chain
+*/
+struct lgw_conf_rxrf_s {
+ bool enable; /*!> enable or disable that RF chain */
+ uint32_t freq_hz; /*!> center frequency of the radio in Hz */
+ float rssi_offset; /*!> Board-specific RSSI correction factor */
+ enum lgw_radio_type_e type; /*!> Radio type for that RF chain (SX1255, SX1257....) */
+ bool tx_enable; /*!> enable or disable TX on that RF chain */
+ uint32_t tx_notch_freq; /*!> TX notch filter frequency [126KHz..250KHz] */
+};
+
+/**
+@struct lgw_conf_rxif_s
+@brief Configuration structure for an IF chain
+*/
+struct lgw_conf_rxif_s {
+ bool enable; /*!> enable or disable that IF chain */
+ uint8_t rf_chain; /*!> to which RF chain is that IF chain associated */
+ int32_t freq_hz; /*!> center frequ of the IF chain, relative to RF chain frequency */
+ uint8_t bandwidth; /*!> RX bandwidth, 0 for default */
+ uint32_t datarate; /*!> RX datarate, 0 for default */
+ uint8_t sync_word_size; /*!> size of FSK sync word (number of bytes, 0 for default) */
+ uint64_t sync_word; /*!> FSK sync word (ALIGN RIGHT, eg. 0xC194C1) */
+};
+
+/**
+@struct lgw_pkt_rx_s
+@brief Structure containing the metadata of a packet that was received and a pointer to the payload
+*/
+struct lgw_pkt_rx_s {
+ uint32_t freq_hz; /*!> central frequency of the IF chain */
+ uint8_t if_chain; /*!> by which IF chain was packet received */
+ uint8_t status; /*!> status of the received packet */
+ uint32_t count_us; /*!> internal concentrator counter for timestamping, 1 microsecond resolution */
+ uint8_t rf_chain; /*!> through which RF chain the packet was received */
+ uint8_t modulation; /*!> modulation used by the packet */
+ uint8_t bandwidth; /*!> modulation bandwidth (LoRa only) */
+ uint32_t datarate; /*!> RX datarate of the packet (SF for LoRa) */
+ uint8_t coderate; /*!> error-correcting code of the packet (LoRa only) */
+ float rssi; /*!> average packet RSSI in dB */
+ float snr; /*!> average packet SNR, in dB (LoRa only) */
+ float snr_min; /*!> minimum packet SNR, in dB (LoRa only) */
+ float snr_max; /*!> maximum packet SNR, in dB (LoRa only) */
+ uint16_t crc; /*!> CRC that was received in the payload */
+ uint16_t size; /*!> payload size in bytes */
+ uint8_t payload[256]; /*!> buffer containing the payload */
+};
+
+/**
+@struct lgw_pkt_tx_s
+@brief Structure containing the configuration of a packet to send and a pointer to the payload
+*/
+struct lgw_pkt_tx_s {
+ uint32_t freq_hz; /*!> center frequency of TX */
+ uint8_t tx_mode; /*!> select on what event/time the TX is triggered */
+ uint32_t count_us; /*!> timestamp or delay in microseconds for TX trigger */
+ uint8_t rf_chain; /*!> through which RF chain will the packet be sent */
+ int8_t rf_power; /*!> TX power, in dBm */
+ uint8_t modulation; /*!> modulation to use for the packet */
+ uint8_t bandwidth; /*!> modulation bandwidth (LoRa only) */
+ uint32_t datarate; /*!> TX datarate (baudrate for FSK, SF for LoRa) */
+ uint8_t coderate; /*!> error-correcting code of the packet (LoRa only) */
+ bool invert_pol; /*!> invert signal polarity, for orthogonal downlinks (LoRa only) */
+ uint8_t f_dev; /*!> frequency deviation, in kHz (FSK only) */
+ uint16_t preamble; /*!> set the preamble length, 0 for default */
+ bool no_crc; /*!> if true, do not send a CRC in the packet */
+ bool no_header; /*!> if true, enable implicit header mode (LoRa), fixed length (FSK) */
+ uint16_t size; /*!> payload size in bytes */
+ uint8_t payload[256]; /*!> buffer containing the payload */
+};
+
+/**
+@struct lgw_tx_gain_s
+@brief Structure containing all gains of Tx chain
+*/
+struct lgw_tx_gain_s {
+ uint8_t dig_gain; /*!> 2 bits, control of the digital gain of SX1301 */
+ uint8_t pa_gain; /*!> 2 bits, control of the external PA (SX1301 I/O) */
+ uint8_t dac_gain; /*!> 2 bits, control of the radio DAC */
+ uint8_t mix_gain; /*!> 4 bits, control of the radio mixer */
+ int8_t rf_power; /*!> measured TX power at the board connector, in dBm */
+};
+
+/**
+@struct lgw_tx_gain_lut_s
+@brief Structure defining the Tx gain LUT
+*/
+struct lgw_tx_gain_lut_s {
+ struct lgw_tx_gain_s lut[TX_GAIN_LUT_SIZE_MAX]; /*!> Array of Tx gain struct */
+ uint8_t size; /*!> Number of LUT indexes */
+};
+
+/* -------------------------------------------------------------------------- */
+/* --- PUBLIC FUNCTIONS PROTOTYPES ------------------------------------------ */
+
+/**
+@brief Configure the gateway board
+@param conf structure containing the configuration parameters
+@return LGW_HAL_ERROR id the operation failed, LGW_HAL_SUCCESS else
+*/
+int lgw_board_setconf(struct lgw_conf_board_s conf);
+
+/**
+@brief Configure the gateway lbt function
+@param conf structure containing the configuration parameters
+@return LGW_HAL_ERROR id the operation failed, LGW_HAL_SUCCESS else
+*/
+int lgw_lbt_setconf(struct lgw_conf_lbt_s conf);
+
+/**
+@brief Configure an RF chain (must configure before start)
+@param rf_chain number of the RF chain to configure [0, LGW_RF_CHAIN_NB - 1]
+@param conf structure containing the configuration parameters
+@return LGW_HAL_ERROR id the operation failed, LGW_HAL_SUCCESS else
+*/
+int lgw_rxrf_setconf(uint8_t rf_chain, struct lgw_conf_rxrf_s conf);
+
+/**
+@brief Configure an IF chain + modem (must configure before start)
+@param if_chain number of the IF chain + modem to configure [0, LGW_IF_CHAIN_NB - 1]
+@param conf structure containing the configuration parameters
+@return LGW_HAL_ERROR id the operation failed, LGW_HAL_SUCCESS else
+*/
+int lgw_rxif_setconf(uint8_t if_chain, struct lgw_conf_rxif_s conf);
+
+/**
+@brief Configure the Tx gain LUT
+@param pointer to structure defining the LUT
+@return LGW_HAL_ERROR id the operation failed, LGW_HAL_SUCCESS else
+*/
+int lgw_txgain_setconf(struct lgw_tx_gain_lut_s *conf);
+
+/**
+@brief Connect to the LoRa concentrator, reset it and configure it according to previously set parameters
+@return LGW_HAL_ERROR id the operation failed, LGW_HAL_SUCCESS else
+*/
+int lgw_start(void);
+
+/**
+@brief Stop the LoRa concentrator and disconnect it
+@return LGW_HAL_ERROR id the operation failed, LGW_HAL_SUCCESS else
+*/
+int lgw_stop(void);
+
+/**
+@brief A non-blocking function that will fetch up to 'max_pkt' packets from the LoRa concentrator FIFO and data buffer
+@param max_pkt maximum number of packet that must be retrieved (equal to the size of the array of struct)
+@param pkt_data pointer to an array of struct that will receive the packet metadata and payload pointers
+@return LGW_HAL_ERROR id the operation failed, else the number of packets retrieved
+*/
+int lgw_receive(uint8_t max_pkt, struct lgw_pkt_rx_s *pkt_data);
+
+/**
+@brief Schedule a packet to be send immediately or after a delay depending on tx_mode
+@param pkt_data structure containing the data and metadata for the packet to send
+@return LGW_HAL_ERROR id the operation failed, LGW_HAL_SUCCESS else
+
+/!\ When sending a packet, there is a delay (approx 1.5ms) for the analog
+circuitry to start and be stable. This delay is adjusted by the HAL depending
+on the board version (lgw_i_tx_start_delay_us).
+
+In 'timestamp' mode, this is transparent: the modem is started
+lgw_i_tx_start_delay_us microseconds before the user-set timestamp value is
+reached, the preamble of the packet start right when the internal timestamp
+counter reach target value.
+
+In 'immediate' mode, the packet is emitted as soon as possible: transferring the
+packet (and its parameters) from the host to the concentrator takes some time,
+then there is the lgw_i_tx_start_delay_us, then the packet is emitted.
+
+In 'triggered' mode (aka PPS/GPS mode), the packet, typically a beacon, is
+emitted lgw_i_tx_start_delay_us microsenconds after a rising edge of the
+trigger signal. Because there is no way to anticipate the triggering event and
+start the analog circuitry beforehand, that delay must be taken into account in
+the protocol.
+*/
+int lgw_send(struct lgw_pkt_tx_s pkt_data);
+
+/**
+@brief Give the the status of different part of the LoRa concentrator
+@param select is used to select what status we want to know
+@param code is used to return the status code
+@return LGW_HAL_ERROR id the operation failed, LGW_HAL_SUCCESS else
+*/
+int lgw_status(uint8_t select, uint8_t *code);
+
+/**
+@brief Abort a currently scheduled or ongoing TX
+@return LGW_HAL_ERROR id the operation failed, LGW_HAL_SUCCESS else
+*/
+int lgw_abort_tx(void);
+
+/**
+@brief Return value of internal counter when latest event (eg GPS pulse) was captured
+@param trig_cnt_us pointer to receive timestamp value
+@return LGW_HAL_ERROR id the operation failed, LGW_HAL_SUCCESS else
+*/
+int lgw_get_trigcnt(uint32_t* trig_cnt_us);
+
+/**
+@brief Allow user to check the version/options of the library once compiled
+@return pointer on a human-readable null terminated string
+*/
+const char* lgw_version_info(void);
+
+/**
+@brief Return time on air of given packet, in milliseconds
+@param packet is a pointer to the packet structure
+@return the packet time on air in milliseconds
+*/
+uint32_t lgw_time_on_air(struct lgw_pkt_tx_s *packet);
+
+#endif
+
+/* --- EOF ------------------------------------------------------------------ */
diff --git a/libloragw/inc/loragw_lbt.h b/libloragw/inc/loragw_lbt.h
new file mode 100644
index 0000000..cb8aada
--- /dev/null
+++ b/libloragw/inc/loragw_lbt.h
@@ -0,0 +1,70 @@
+/*
+ / _____) _ | |
+( (____ _____ ____ _| |_ _____ ____| |__
+ \____ \| ___ | (_ _) ___ |/ ___) _ \
+ _____) ) ____| | | || |_| ____( (___| | | |
+(______/|_____)_|_|_| \__)_____)\____)_| |_|
+ (C)2013 Semtech-Cycleo
+
+Description:
+ Functions used to handle the Listen Before Talk feature
+
+License: Revised BSD License, see LICENSE.TXT file include in the project
+Maintainer: Michael Coracin
+*/
+
+#ifndef _LORAGW_LBT_H
+#define _LORAGW_LBT_H
+
+/* -------------------------------------------------------------------------- */
+/* --- DEPENDANCIES --------------------------------------------------------- */
+
+#include <stdint.h> /* C99 types */
+#include <stdbool.h> /* bool type */
+
+#include "loragw_hal.h"
+
+/* -------------------------------------------------------------------------- */
+/* --- PUBLIC CONSTANTS ----------------------------------------------------- */
+
+#define LGW_LBT_SUCCESS 0
+#define LGW_LBT_ERROR -1
+
+/* -------------------------------------------------------------------------- */
+/* --- PUBLIC FUNCTIONS PROTOTYPES ------------------------------------------ */
+
+/**
+@brief Set the configuration parameters for LBT feature
+@param conf structure containing the configuration parameters
+@return LGW_LBT_ERROR id the operation failed, LGW_LBT_SUCCESS else
+*/
+int lbt_setconf(struct lgw_conf_lbt_s * conf);
+
+/**
+@brief Configure the concentrator for LBT feature
+@return LGW_LBT_ERROR id the operation failed, LGW_LBT_SUCCESS else
+*/
+int lbt_setup(void);
+
+/**
+@brief Start the LBT FSM
+@return LGW_LBT_ERROR id the operation failed, LGW_LBT_SUCCESS else
+*/
+int lbt_start(void);
+
+/**
+@brief Configure the concentrator for LBT feature
+@param pkt_data pointer to downlink packet to be trabsmitted
+@param tx_allowed pointer to receive permission for transmission
+@return LGW_LBT_ERROR id the operation failed, LGW_LBT_SUCCESS else
+*/
+int lbt_is_channel_free(struct lgw_pkt_tx_s * pkt_data, uint16_t tx_start_delay, bool * tx_allowed);
+
+/**
+@brief Check if LBT is enabled
+@return true if enabled, false otherwise
+*/
+bool lbt_is_enabled(void);
+
+#endif
+/* --- EOF ------------------------------------------------------------------ */
diff --git a/libloragw/inc/loragw_radio.h b/libloragw/inc/loragw_radio.h
new file mode 100644
index 0000000..32d494b
--- /dev/null
+++ b/libloragw/inc/loragw_radio.h
@@ -0,0 +1,73 @@
+/*
+ / _____) _ | |
+( (____ _____ ____ _| |_ _____ ____| |__
+ \____ \| ___ | (_ _) ___ |/ ___) _ \
+ _____) ) ____| | | || |_| ____( (___| | | |
+(______/|_____)_|_|_| \__)_____)\____)_| |_|
+ (C)2013 Semtech-Cycleo
+
+Description:
+ Functions used to handle LoRa concentrator radios.
+
+License: Revised BSD License, see LICENSE.TXT file include in the project
+Maintainer: Michael Coracin
+*/
+
+#ifndef _LORAGW_RADIO_H
+#define _LORAGW_RADIO_H
+
+/* -------------------------------------------------------------------------- */
+/* --- DEPENDANCIES --------------------------------------------------------- */
+
+#include <stdint.h> /* C99 types */
+#include <stdbool.h> /* bool type */
+
+/* -------------------------------------------------------------------------- */
+/* --- PUBLIC CONSTANTS ----------------------------------------------------- */
+
+#define LGW_REG_SUCCESS 0
+#define LGW_REG_ERROR -1
+
+#define SX125x_32MHz_FRAC 15625 /* irreductible fraction for PLL register caculation */
+
+/* -------------------------------------------------------------------------- */
+/* --- PUBLIC CONSTANTS ----------------------------------------------------- */
+
+enum lgw_sx127x_rxbw_e {
+ LGW_SX127X_RXBW_2K6_HZ,
+ LGW_SX127X_RXBW_3K1_HZ,
+ LGW_SX127X_RXBW_3K9_HZ,
+ LGW_SX127X_RXBW_5K2_HZ,
+ LGW_SX127X_RXBW_6K3_HZ,
+ LGW_SX127X_RXBW_7K8_HZ,
+ LGW_SX127X_RXBW_10K4_HZ,
+ LGW_SX127X_RXBW_12K5_HZ,
+ LGW_SX127X_RXBW_15K6_HZ,
+ LGW_SX127X_RXBW_20K8_HZ,
+ LGW_SX127X_RXBW_25K_HZ,
+ LGW_SX127X_RXBW_31K3_HZ,
+ LGW_SX127X_RXBW_41K7_HZ,
+ LGW_SX127X_RXBW_50K_HZ,
+ LGW_SX127X_RXBW_62K5_HZ,
+ LGW_SX127X_RXBW_83K3_HZ,
+ LGW_SX127X_RXBW_100K_HZ,
+ LGW_SX127X_RXBW_125K_HZ,
+ LGW_SX127X_RXBW_166K7_HZ,
+ LGW_SX127X_RXBW_200K_HZ,
+ LGW_SX127X_RXBW_250K_HZ
+};
+
+/* -------------------------------------------------------------------------- */
+/* --- PUBLIC FUNCTIONS PROTOTYPES ------------------------------------------ */
+
+int lgw_setup_sx125x(uint8_t rf_chain, uint8_t rf_clkout, bool rf_enable, uint8_t rf_radio_type, uint32_t freq_hz);
+
+int lgw_setup_sx127x(uint32_t frequency, uint8_t modulation, enum lgw_sx127x_rxbw_e rxbw_khz, int8_t rssi_offset);
+
+int lgw_sx127x_reg_w(uint8_t address, uint8_t reg_value);
+
+int lgw_sx127x_reg_r(uint8_t address, uint8_t *reg_value);
+
+
+#endif
+/* --- EOF ------------------------------------------------------------------ */
diff --git a/libloragw/inc/loragw_reg.h b/libloragw/inc/loragw_reg.h
new file mode 100644
index 0000000..2a944a7
--- /dev/null
+++ b/libloragw/inc/loragw_reg.h
@@ -0,0 +1,461 @@
+/*
+ / _____) _ | |
+( (____ _____ ____ _| |_ _____ ____| |__
+ \____ \| ___ | (_ _) ___ |/ ___) _ \
+ _____) ) ____| | | || |_| ____( (___| | | |
+(______/|_____)_|_|_| \__)_____)\____)_| |_|
+ (C)2013 Semtech-Cycleo
+
+Description:
+ Functions used to handle a single LoRa concentrator.
+ Registers are addressed by name.
+ Multi-bytes registers are handled automatically.
+ Read-modify-write is handled automatically.
+
+License: Revised BSD License, see LICENSE.TXT file include in the project
+Maintainer: Sylvain Miermont
+*/
+
+
+#ifndef _LORAGW_REG_H
+#define _LORAGW_REG_H
+
+/* -------------------------------------------------------------------------- */
+/* --- DEPENDANCIES --------------------------------------------------------- */
+
+#include <stdint.h> /* C99 types */
+#include <stdbool.h> /* bool type */
+
+#include "config.h" /* library configuration options (dynamically generated) */
+
+/* -------------------------------------------------------------------------- */
+/* --- INTERNAL SHARED TYPES ------------------------------------------------ */
+
+struct lgw_reg_s {
+ int8_t page; /*!< page containing the register (-1 for all pages) */
+ uint8_t addr; /*!< base address of the register (7 bit) */
+ uint8_t offs; /*!< position of the register LSB (between 0 to 7) */
+ bool sign; /*!< 1 indicates the register is signed (2 complem.) */
+ uint8_t leng; /*!< number of bits in the register */
+ bool rdon; /*!< 1 indicates a read-only register */
+ int32_t dflt; /*!< register default value */
+};
+
+/* -------------------------------------------------------------------------- */
+/* --- INTERNAL SHARED FUNCTIONS -------------------------------------------- */
+
+int reg_w_align32(void *spi_target, uint8_t spi_mux_mode, uint8_t spi_mux_target, struct lgw_reg_s r, int32_t reg_value);
+int reg_r_align32(void *spi_target, uint8_t spi_mux_mode, uint8_t spi_mux_target, struct lgw_reg_s r, int32_t *reg_value);
+
+/* -------------------------------------------------------------------------- */
+/* --- PUBLIC CONSTANTS ----------------------------------------------------- */
+
+#define LGW_REG_SUCCESS 0
+#define LGW_REG_ERROR -1
+
+/*
+auto generated register mapping for C code : 11-Jul-2013 13:20:40
+this file contains autogenerated C struct used to access the LORA registers
+this file is autogenerated from registers description
+293 registers are defined
+*/
+
+#define LGW_PAGE_REG 0
+#define LGW_SOFT_RESET 1
+#define LGW_VERSION 2
+#define LGW_RX_DATA_BUF_ADDR 3
+#define LGW_RX_DATA_BUF_DATA 4
+#define LGW_TX_DATA_BUF_ADDR 5
+#define LGW_TX_DATA_BUF_DATA 6
+#define LGW_CAPTURE_RAM_ADDR 7
+#define LGW_CAPTURE_RAM_DATA 8
+#define LGW_MCU_PROM_ADDR 9
+#define LGW_MCU_PROM_DATA 10
+#define LGW_RX_PACKET_DATA_FIFO_NUM_STORED 11
+#define LGW_RX_PACKET_DATA_FIFO_ADDR_POINTER 12
+#define LGW_RX_PACKET_DATA_FIFO_STATUS 13
+#define LGW_RX_PACKET_DATA_FIFO_PAYLOAD_SIZE 14
+#define LGW_MBWSSF_MODEM_ENABLE 15
+#define LGW_CONCENTRATOR_MODEM_ENABLE 16
+#define LGW_FSK_MODEM_ENABLE 17
+#define LGW_GLOBAL_EN 18
+#define LGW_CLK32M_EN 19
+#define LGW_CLKHS_EN 20
+#define LGW_START_BIST0 21
+#define LGW_START_BIST1 22
+#define LGW_CLEAR_BIST0 23
+#define LGW_CLEAR_BIST1 24
+#define LGW_BIST0_FINISHED 25
+#define LGW_BIST1_FINISHED 26
+#define LGW_MCU_AGC_PROG_RAM_BIST_STATUS 27
+#define LGW_MCU_ARB_PROG_RAM_BIST_STATUS 28
+#define LGW_CAPTURE_RAM_BIST_STATUS 29
+#define LGW_CHAN_FIR_RAM0_BIST_STATUS 30
+#define LGW_CHAN_FIR_RAM1_BIST_STATUS 31
+#define LGW_CORR0_RAM_BIST_STATUS 32
+#define LGW_CORR1_RAM_BIST_STATUS 33
+#define LGW_CORR2_RAM_BIST_STATUS 34
+#define LGW_CORR3_RAM_BIST_STATUS 35
+#define LGW_CORR4_RAM_BIST_STATUS 36
+#define LGW_CORR5_RAM_BIST_STATUS 37
+#define LGW_CORR6_RAM_BIST_STATUS 38
+#define LGW_CORR7_RAM_BIST_STATUS 39
+#define LGW_MODEM0_RAM0_BIST_STATUS 40
+#define LGW_MODEM1_RAM0_BIST_STATUS 41
+#define LGW_MODEM2_RAM0_BIST_STATUS 42
+#define LGW_MODEM3_RAM0_BIST_STATUS 43
+#define LGW_MODEM4_RAM0_BIST_STATUS 44
+#define LGW_MODEM5_RAM0_BIST_STATUS 45
+#define LGW_MODEM6_RAM0_BIST_STATUS 46
+#define LGW_MODEM7_RAM0_BIST_STATUS 47
+#define LGW_MODEM0_RAM1_BIST_STATUS 48
+#define LGW_MODEM1_RAM1_BIST_STATUS 49
+#define LGW_MODEM2_RAM1_BIST_STATUS 50
+#define LGW_MODEM3_RAM1_BIST_STATUS 51
+#define LGW_MODEM4_RAM1_BIST_STATUS 52
+#define LGW_MODEM5_RAM1_BIST_STATUS 53
+#define LGW_MODEM6_RAM1_BIST_STATUS 54
+#define LGW_MODEM7_RAM1_BIST_STATUS 55
+#define LGW_MODEM0_RAM2_BIST_STATUS 56
+#define LGW_MODEM1_RAM2_BIST_STATUS 57
+#define LGW_MODEM2_RAM2_BIST_STATUS 58
+#define LGW_MODEM3_RAM2_BIST_STATUS 59
+#define LGW_MODEM4_RAM2_BIST_STATUS 60
+#define LGW_MODEM5_RAM2_BIST_STATUS 61
+#define LGW_MODEM6_RAM2_BIST_STATUS 62
+#define LGW_MODEM7_RAM2_BIST_STATUS 63
+#define LGW_MODEM_MBWSSF_RAM0_BIST_STATUS 64
+#define LGW_MODEM_MBWSSF_RAM1_BIST_STATUS 65
+#define LGW_MODEM_MBWSSF_RAM2_BIST_STATUS 66
+#define LGW_MCU_AGC_DATA_RAM_BIST0_STATUS 67
+#define LGW_MCU_AGC_DATA_RAM_BIST1_STATUS 68
+#define LGW_MCU_ARB_DATA_RAM_BIST0_STATUS 69
+#define LGW_MCU_ARB_DATA_RAM_BIST1_STATUS 70
+#define LGW_TX_TOP_RAM_BIST0_STATUS 71
+#define LGW_TX_TOP_RAM_BIST1_STATUS 72
+#define LGW_DATA_MNGT_RAM_BIST0_STATUS 73
+#define LGW_DATA_MNGT_RAM_BIST1_STATUS 74
+#define LGW_GPIO_SELECT_INPUT 75
+#define LGW_GPIO_SELECT_OUTPUT 76
+#define LGW_GPIO_MODE 77
+#define LGW_GPIO_PIN_REG_IN 78
+#define LGW_GPIO_PIN_REG_OUT 79
+#define LGW_MCU_AGC_STATUS 80
+#define LGW_MCU_ARB_STATUS 81
+#define LGW_CHIP_ID 82
+#define LGW_EMERGENCY_FORCE_HOST_CTRL 83
+#define LGW_RX_INVERT_IQ 84
+#define LGW_MODEM_INVERT_IQ 85
+#define LGW_MBWSSF_MODEM_INVERT_IQ 86
+#define LGW_RX_EDGE_SELECT 87
+#define LGW_MISC_RADIO_EN 88
+#define LGW_FSK_MODEM_INVERT_IQ 89
+#define LGW_FILTER_GAIN 90
+#define LGW_RADIO_SELECT 91
+#define LGW_IF_FREQ_0 92
+#define LGW_IF_FREQ_1 93
+#define LGW_IF_FREQ_2 94
+#define LGW_IF_FREQ_3 95
+#define LGW_IF_FREQ_4 96
+#define LGW_IF_FREQ_5 97
+#define LGW_IF_FREQ_6 98
+#define LGW_IF_FREQ_7 99
+#define LGW_IF_FREQ_8 100
+#define LGW_IF_FREQ_9 101
+#define LGW_CHANN_OVERRIDE_AGC_GAIN 102
+#define LGW_CHANN_AGC_GAIN 103
+#define LGW_CORR0_DETECT_EN 104
+#define LGW_CORR1_DETECT_EN 105
+#define LGW_CORR2_DETECT_EN 106
+#define LGW_CORR3_DETECT_EN 107
+#define LGW_CORR4_DETECT_EN 108
+#define LGW_CORR5_DETECT_EN 109
+#define LGW_CORR6_DETECT_EN 110
+#define LGW_CORR7_DETECT_EN 111
+#define LGW_CORR_SAME_PEAKS_OPTION_SF6 112
+#define LGW_CORR_SAME_PEAKS_OPTION_SF7 113
+#define LGW_CORR_SAME_PEAKS_OPTION_SF8 114
+#define LGW_CORR_SAME_PEAKS_OPTION_SF9 115
+#define LGW_CORR_SAME_PEAKS_OPTION_SF10 116
+#define LGW_CORR_SAME_PEAKS_OPTION_SF11 117
+#define LGW_CORR_SAME_PEAKS_OPTION_SF12 118
+#define LGW_CORR_SIG_NOISE_RATIO_SF6 119
+#define LGW_CORR_SIG_NOISE_RATIO_SF7 120
+#define LGW_CORR_SIG_NOISE_RATIO_SF8 121
+#define LGW_CORR_SIG_NOISE_RATIO_SF9 122
+#define LGW_CORR_SIG_NOISE_RATIO_SF10 123
+#define LGW_CORR_SIG_NOISE_RATIO_SF11 124
+#define LGW_CORR_SIG_NOISE_RATIO_SF12 125
+#define LGW_CORR_NUM_SAME_PEAK 126
+#define LGW_CORR_MAC_GAIN 127
+#define LGW_ADJUST_MODEM_START_OFFSET_RDX4 128
+#define LGW_ADJUST_MODEM_START_OFFSET_SF12_RDX4 129
+#define LGW_DBG_CORR_SELECT_SF 130
+#define LGW_DBG_CORR_SELECT_CHANNEL 131
+#define LGW_DBG_DETECT_CPT 132
+#define LGW_DBG_SYMB_CPT 133
+#define LGW_CHIRP_INVERT_RX 134
+#define LGW_DC_NOTCH_EN 135
+#define LGW_IMPLICIT_CRC_EN 136
+#define LGW_IMPLICIT_CODING_RATE 137
+#define LGW_IMPLICIT_PAYLOAD_LENGHT 138
+#define LGW_FREQ_TO_TIME_INVERT 139
+#define LGW_FREQ_TO_TIME_DRIFT 140
+#define LGW_PAYLOAD_FINE_TIMING_GAIN 141
+#define LGW_PREAMBLE_FINE_TIMING_GAIN 142
+#define LGW_TRACKING_INTEGRAL 143
+#define LGW_FRAME_SYNCH_PEAK1_POS 144
+#define LGW_FRAME_SYNCH_PEAK2_POS 145
+#define LGW_PREAMBLE_SYMB1_NB 146
+#define LGW_FRAME_SYNCH_GAIN 147
+#define LGW_SYNCH_DETECT_TH 148
+#define LGW_LLR_SCALE 149
+#define LGW_SNR_AVG_CST 150
+#define LGW_PPM_OFFSET 151
+#define LGW_MAX_PAYLOAD_LEN 152
+#define LGW_ONLY_CRC_EN 153
+#define LGW_ZERO_PAD 154
+#define LGW_DEC_GAIN_OFFSET 155
+#define LGW_CHAN_GAIN_OFFSET 156
+#define LGW_FORCE_HOST_RADIO_CTRL 157
+#define LGW_FORCE_HOST_FE_CTRL 158
+#define LGW_FORCE_DEC_FILTER_GAIN 159
+#define LGW_MCU_RST_0 160
+#define LGW_MCU_RST_1 161
+#define LGW_MCU_SELECT_MUX_0 162
+#define LGW_MCU_SELECT_MUX_1 163
+#define LGW_MCU_CORRUPTION_DETECTED_0 164
+#define LGW_MCU_CORRUPTION_DETECTED_1 165
+#define LGW_MCU_SELECT_EDGE_0 166
+#define LGW_MCU_SELECT_EDGE_1 167
+#define LGW_CHANN_SELECT_RSSI 168
+#define LGW_RSSI_BB_DEFAULT_VALUE 169
+#define LGW_RSSI_DEC_DEFAULT_VALUE 170
+#define LGW_RSSI_CHANN_DEFAULT_VALUE 171
+#define LGW_RSSI_BB_FILTER_ALPHA 172
+#define LGW_RSSI_DEC_FILTER_ALPHA 173
+#define LGW_RSSI_CHANN_FILTER_ALPHA 174
+#define LGW_IQ_MISMATCH_A_AMP_COEFF 175
+#define LGW_IQ_MISMATCH_A_PHI_COEFF 176
+#define LGW_IQ_MISMATCH_B_AMP_COEFF 177
+#define LGW_IQ_MISMATCH_B_SEL_I 178
+#define LGW_IQ_MISMATCH_B_PHI_COEFF 179
+#define LGW_TX_TRIG_IMMEDIATE 180
+#define LGW_TX_TRIG_DELAYED 181
+#define LGW_TX_TRIG_GPS 182
+#define LGW_TX_START_DELAY 183
+#define LGW_TX_FRAME_SYNCH_PEAK1_POS 184
+#define LGW_TX_FRAME_SYNCH_PEAK2_POS 185
+#define LGW_TX_RAMP_DURATION 186
+#define LGW_TX_OFFSET_I 187
+#define LGW_TX_OFFSET_Q 188
+#define LGW_TX_MODE 189
+#define LGW_TX_ZERO_PAD 190
+#define LGW_TX_EDGE_SELECT 191
+#define LGW_TX_EDGE_SELECT_TOP 192
+#define LGW_TX_GAIN 193
+#define LGW_TX_CHIRP_LOW_PASS 194
+#define LGW_TX_FCC_WIDEBAND 195
+#define LGW_TX_SWAP_IQ 196
+#define LGW_MBWSSF_IMPLICIT_HEADER 197
+#define LGW_MBWSSF_IMPLICIT_CRC_EN 198
+#define LGW_MBWSSF_IMPLICIT_CODING_RATE 199
+#define LGW_MBWSSF_IMPLICIT_PAYLOAD_LENGHT 200
+#define LGW_MBWSSF_AGC_FREEZE_ON_DETECT 201
+#define LGW_MBWSSF_FRAME_SYNCH_PEAK1_POS 202
+#define LGW_MBWSSF_FRAME_SYNCH_PEAK2_POS 203
+#define LGW_MBWSSF_PREAMBLE_SYMB1_NB 204
+#define LGW_MBWSSF_FRAME_SYNCH_GAIN 205
+#define LGW_MBWSSF_SYNCH_DETECT_TH 206
+#define LGW_MBWSSF_DETECT_MIN_SINGLE_PEAK 207
+#define LGW_MBWSSF_DETECT_TRIG_SAME_PEAK_NB 208
+#define LGW_MBWSSF_FREQ_TO_TIME_INVERT 209
+#define LGW_MBWSSF_FREQ_TO_TIME_DRIFT 210
+#define LGW_MBWSSF_PPM_CORRECTION 211
+#define LGW_MBWSSF_PAYLOAD_FINE_TIMING_GAIN 212
+#define LGW_MBWSSF_PREAMBLE_FINE_TIMING_GAIN 213
+#define LGW_MBWSSF_TRACKING_INTEGRAL 214
+#define LGW_MBWSSF_ZERO_PAD 215
+#define LGW_MBWSSF_MODEM_BW 216
+#define LGW_MBWSSF_RADIO_SELECT 217
+#define LGW_MBWSSF_RX_CHIRP_INVERT 218
+#define LGW_MBWSSF_LLR_SCALE 219
+#define LGW_MBWSSF_SNR_AVG_CST 220
+#define LGW_MBWSSF_PPM_OFFSET 221
+#define LGW_MBWSSF_RATE_SF 222
+#define LGW_MBWSSF_ONLY_CRC_EN 223
+#define LGW_MBWSSF_MAX_PAYLOAD_LEN 224
+#define LGW_TX_STATUS 225
+#define LGW_FSK_CH_BW_EXPO 226
+#define LGW_FSK_RSSI_LENGTH 227
+#define LGW_FSK_RX_INVERT 228
+#define LGW_FSK_PKT_MODE 229
+#define LGW_FSK_PSIZE 230
+#define LGW_FSK_CRC_EN 231
+#define LGW_FSK_DCFREE_ENC 232
+#define LGW_FSK_CRC_IBM 233
+#define LGW_FSK_ERROR_OSR_TOL 234
+#define LGW_FSK_RADIO_SELECT 235
+#define LGW_FSK_BR_RATIO 236
+#define LGW_FSK_REF_PATTERN_LSB 237
+#define LGW_FSK_REF_PATTERN_MSB 238
+#define LGW_FSK_PKT_LENGTH 239
+#define LGW_FSK_TX_GAUSSIAN_EN 240
+#define LGW_FSK_TX_GAUSSIAN_SELECT_BT 241
+#define LGW_FSK_TX_PATTERN_EN 242
+#define LGW_FSK_TX_PREAMBLE_SEQ 243
+#define LGW_FSK_TX_PSIZE 244
+#define LGW_FSK_NODE_ADRS 245
+#define LGW_FSK_BROADCAST 246
+#define LGW_FSK_AUTO_AFC_ON 247
+#define LGW_FSK_PATTERN_TIMEOUT_CFG 248
+#define LGW_SPI_RADIO_A__DATA 249
+#define LGW_SPI_RADIO_A__DATA_READBACK 250
+#define LGW_SPI_RADIO_A__ADDR 251
+#define LGW_SPI_RADIO_A__CS 252
+#define LGW_SPI_RADIO_B__DATA 253
+#define LGW_SPI_RADIO_B__DATA_READBACK 254
+#define LGW_SPI_RADIO_B__ADDR 255
+#define LGW_SPI_RADIO_B__CS 256
+#define LGW_RADIO_A_EN 257
+#define LGW_RADIO_B_EN 258
+#define LGW_RADIO_RST 259
+#define LGW_LNA_A_EN 260
+#define LGW_PA_A_EN 261
+#define LGW_LNA_B_EN 262
+#define LGW_PA_B_EN 263
+#define LGW_PA_GAIN 264
+#define LGW_LNA_A_CTRL_LUT 265
+#define LGW_PA_A_CTRL_LUT 266
+#define LGW_LNA_B_CTRL_LUT 267
+#define LGW_PA_B_CTRL_LUT 268
+#define LGW_CAPTURE_SOURCE 269
+#define LGW_CAPTURE_START 270
+#define LGW_CAPTURE_FORCE_TRIGGER 271
+#define LGW_CAPTURE_WRAP 272
+#define LGW_CAPTURE_PERIOD 273
+#define LGW_MODEM_STATUS 274
+#define LGW_VALID_HEADER_COUNTER_0 275
+#define LGW_VALID_PACKET_COUNTER_0 276
+#define LGW_VALID_HEADER_COUNTER_MBWSSF 277
+#define LGW_VALID_HEADER_COUNTER_FSK 278
+#define LGW_VALID_PACKET_COUNTER_MBWSSF 279
+#define LGW_VALID_PACKET_COUNTER_FSK 280
+#define LGW_CHANN_RSSI 281
+#define LGW_BB_RSSI 282
+#define LGW_DEC_RSSI 283
+#define LGW_DBG_MCU_DATA 284
+#define LGW_DBG_ARB_MCU_RAM_DATA 285
+#define LGW_DBG_AGC_MCU_RAM_DATA 286
+#define LGW_NEXT_PACKET_CNT 287
+#define LGW_ADDR_CAPTURE_COUNT 288
+#define LGW_TIMESTAMP 289
+#define LGW_DBG_CHANN0_GAIN 290
+#define LGW_DBG_CHANN1_GAIN 291
+#define LGW_DBG_CHANN2_GAIN 292
+#define LGW_DBG_CHANN3_GAIN 293
+#define LGW_DBG_CHANN4_GAIN 294
+#define LGW_DBG_CHANN5_GAIN 295
+#define LGW_DBG_CHANN6_GAIN 296
+#define LGW_DBG_CHANN7_GAIN 297
+#define LGW_DBG_DEC_FILT_GAIN 298
+#define LGW_SPI_DATA_FIFO_PTR 299
+#define LGW_PACKET_DATA_FIFO_PTR 300
+#define LGW_DBG_ARB_MCU_RAM_ADDR 301
+#define LGW_DBG_AGC_MCU_RAM_ADDR 302
+#define LGW_SPI_MASTER_CHIP_SELECT_POLARITY 303
+#define LGW_SPI_MASTER_CPOL 304
+#define LGW_SPI_MASTER_CPHA 305
+#define LGW_SIG_GEN_ANALYSER_MUX_SEL 306
+#define LGW_SIG_GEN_EN 307
+#define LGW_SIG_ANALYSER_EN 308
+#define LGW_SIG_ANALYSER_AVG_LEN 309
+#define LGW_SIG_ANALYSER_PRECISION 310
+#define LGW_SIG_ANALYSER_VALID_OUT 311
+#define LGW_SIG_GEN_FREQ 312
+#define LGW_SIG_ANALYSER_FREQ 313
+#define LGW_SIG_ANALYSER_I_OUT 314
+#define LGW_SIG_ANALYSER_Q_OUT 315
+#define LGW_GPS_EN 316
+#define LGW_GPS_POL 317
+#define LGW_SW_TEST_REG1 318
+#define LGW_SW_TEST_REG2 319
+#define LGW_SW_TEST_REG3 320
+#define LGW_DATA_MNGT_STATUS 321
+#define LGW_DATA_MNGT_CPT_FRAME_ALLOCATED 322
+#define LGW_DATA_MNGT_CPT_FRAME_FINISHED 323
+#define LGW_DATA_MNGT_CPT_FRAME_READEN 324
+#define LGW_TX_TRIG_ALL 325
+
+#define LGW_TOTALREGS 326
+
+/* -------------------------------------------------------------------------- */
+/* --- PUBLIC FUNCTIONS PROTOTYPES ------------------------------------------ */
+
+/**
+@brief Connect LoRa concentrator by opening SPI link
+@param spi_only indicates if we only want to create the SPI connexion to the
+concentrator, or if we also want to reset it and configure the FPGA (if present)
+@param tx_notch_filter TX notch filter frequency to be set in the FPGA (only
+used with SX1301AP2 reference design).
+@return status of register operation (LGW_REG_SUCCESS/LGW_REG_ERROR)
+*/
+int lgw_connect(bool spi_only, uint32_t tx_notch_freq);
+
+/**
+@brief Disconnect LoRa concentrator by closing SPI link
+@return status of register operation (LGW_REG_SUCCESS/LGW_REG_ERROR)
+*/
+int lgw_disconnect(void);
+
+/**
+@brief Use the soft-reset register to put the concentrator in initial state
+@return status of register operation (LGW_REG_SUCCESS/LGW_REG_ERROR)
+*/
+int lgw_soft_reset(void);
+
+/**
+@brief Check if the registers are ok, send diagnostics to stdio/stderr/file
+@param f file descriptor to to which the check result will be written
+@return status of register operation (LGW_REG_SUCCESS/LGW_REG_ERROR)
+*/
+int lgw_reg_check(FILE *f);
+
+/**
+@brief LoRa concentrator register write
+@param register_id register number in the data structure describing registers
+@param reg_value signed value to write to the register (for u32, use cast)
+@return status of register operation (LGW_REG_SUCCESS/LGW_REG_ERROR)
+*/
+int lgw_reg_w(uint16_t register_id, int32_t reg_value);
+
+/**
+@brief LoRa concentrator register read
+@param register_id register number in the data structure describing registers
+@param reg_value pointer to a variable where to write register read value
+@return status of register operation (LGW_REG_SUCCESS/LGW_REG_ERROR)
+*/
+int lgw_reg_r(uint16_t register_id, int32_t *reg_value);
+
+/**
+@brief LoRa concentrator register burst write
+@param register_id register number in the data structure describing registers
+@param data pointer to byte array that will be sent to the LoRa concentrator
+@param size size of the transfer, in byte(s)
+@return status of register operation (LGW_REG_SUCCESS/LGW_REG_ERROR)
+*/
+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 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);
+
+
+#endif
+
+/* --- EOF ------------------------------------------------------------------ */
diff --git a/libloragw/inc/loragw_spi.h b/libloragw/inc/loragw_spi.h
new file mode 100644
index 0000000..fef1f48
--- /dev/null
+++ b/libloragw/inc/loragw_spi.h
@@ -0,0 +1,105 @@
+/*
+ / _____) _ | |
+( (____ _____ ____ _| |_ _____ ____| |__
+ \____ \| ___ | (_ _) ___ |/ ___) _ \
+ _____) ) ____| | | || |_| ____( (___| | | |
+(______/|_____)_|_|_| \__)_____)\____)_| |_|
+ (C)2013 Semtech-Cycleo
+
+Description:
+ Host specific functions to address the LoRa concentrator registers through a
+ SPI interface.
+ Single-byte read/write and burst read/write.
+ Does not handle pagination.
+ Could be used with multiple SPI ports in parallel (explicit file descriptor)
+
+License: Revised BSD License, see LICENSE.TXT file include in the project
+Maintainer: Sylvain Miermont
+*/
+
+
+#ifndef _LORAGW_SPI_H
+#define _LORAGW_SPI_H
+
+/* -------------------------------------------------------------------------- */
+/* --- DEPENDANCIES --------------------------------------------------------- */
+
+#include <stdint.h> /* C99 types*/
+
+#include "config.h" /* library configuration options (dynamically generated) */
+
+/* -------------------------------------------------------------------------- */
+/* --- PUBLIC CONSTANTS ----------------------------------------------------- */
+
+#define LGW_SPI_SUCCESS 0
+#define LGW_SPI_ERROR -1
+#define LGW_BURST_CHUNK 1024
+
+#define LGW_SPI_MUX_MODE0 0x0 /* No FPGA */
+#define LGW_SPI_MUX_MODE1 0x1 /* FPGA, with spi mux header */
+
+#define LGW_SPI_MUX_TARGET_SX1301 0x0
+#define LGW_SPI_MUX_TARGET_FPGA 0x1
+#define LGW_SPI_MUX_TARGET_EEPROM 0x2
+#define LGW_SPI_MUX_TARGET_SX127X 0x3
+
+/* -------------------------------------------------------------------------- */
+/* --- PUBLIC FUNCTIONS PROTOTYPES ------------------------------------------ */
+
+/**
+@brief LoRa concentrator SPI setup (configure I/O and peripherals)
+@param spi_target_ptr pointer on a generic pointer to SPI target (implementation dependant)
+@return status of register operation (LGW_SPI_SUCCESS/LGW_SPI_ERROR)
+*/
+
+int lgw_spi_open(void **spi_target_ptr);
+
+/**
+@brief LoRa concentrator SPI close
+@param spi_target generic pointer to SPI target (implementation dependant)
+@return status of register operation (LGW_SPI_SUCCESS/LGW_SPI_ERROR)
+*/
+
+int lgw_spi_close(void *spi_target);
+
+/**
+@brief LoRa concentrator SPI single-byte write
+@param spi_target generic pointer to SPI target (implementation dependant)
+@param address 7-bit register address
+@param data data byte to write
+@return status of register operation (LGW_SPI_SUCCESS/LGW_SPI_ERROR)
+*/
+int lgw_spi_w(void *spi_target, uint8_t spi_mux_mode, uint8_t spi_mux_target, uint8_t address, uint8_t data);
+
+/**
+@brief LoRa concentrator SPI single-byte read
+@param spi_target generic pointer to SPI target (implementation dependant)
+@param address 7-bit register address
+@param data data byte to write
+@return status of register operation (LGW_SPI_SUCCESS/LGW_SPI_ERROR)
+*/
+int lgw_spi_r(void *spi_target, uint8_t spi_mux_mode, uint8_t spi_mux_target, uint8_t address, uint8_t *data);
+
+/**
+@brief LoRa concentrator SPI burst (multiple-byte) write
+@param spi_target generic pointer to SPI target (implementation dependant)
+@param address 7-bit register address
+@param data pointer to byte array that will be sent to the LoRa concentrator
+@param size size of the transfer, in byte(s)
+@return status of register operation (LGW_SPI_SUCCESS/LGW_SPI_ERROR)
+*/
+int lgw_spi_wb(void *spi_target, uint8_t spi_mux_mode, uint8_t spi_mux_target, uint8_t address, uint8_t *data, uint16_t size);
+
+/**
+@brief LoRa concentrator SPI burst (multiple-byte) read
+@param spi_target generic pointer to SPI target (implementation dependant)
+@param address 7-bit register address
+@param data pointer to byte array that will be written from the LoRa concentrator
+@param size size of the transfer, in byte(s)
+@return status of register operation (LGW_SPI_SUCCESS/LGW_SPI_ERROR)
+*/
+int lgw_spi_rb(void *spi_target, uint8_t spi_mux_mode, uint8_t spi_mux_target, uint8_t address, uint8_t *data, uint16_t size);
+
+#endif
+
+/* --- EOF ------------------------------------------------------------------ */
diff --git a/libloragw/inc/loragw_sx125x.h b/libloragw/inc/loragw_sx125x.h
new file mode 100644
index 0000000..120ee68
--- /dev/null
+++ b/libloragw/inc/loragw_sx125x.h
@@ -0,0 +1,49 @@
+/*
+ / _____) _ | |
+( (____ _____ ____ _| |_ _____ ____| |__
+ \____ \| ___ | (_ _) ___ |/ ___) _ \
+ _____) ) ____| | | || |_| ____( (___| | | |
+(______/|_____)_|_|_| \__)_____)\____)_| |_|
+ (C)2013 Semtech
+
+Description: SX125x radio registers and constant definitions
+
+License: Revised BSD License, see LICENSE.TXT file include in the project
+
+Maintainer: Michael Coracin
+*/
+#ifndef __SX125X_REGS_H__
+#define __SX125X_REGS_H__
+
+/*
+SX1257 frequency setting :
+F_register(24bit) = F_rf (Hz) / F_step(Hz)
+ = F_rf (Hz) * 2^19 / F_xtal(Hz)
+ = F_rf (Hz) * 2^19 / 32e6
+ = F_rf (Hz) * 256/15625
+
+SX1255 frequency setting :
+F_register(24bit) = F_rf (Hz) / F_step(Hz)
+ = F_rf (Hz) * 2^20 / F_xtal(Hz)
+ = F_rf (Hz) * 2^20 / 32e6
+ = F_rf (Hz) * 512/15625
+*/
+
+#define SX125x_TX_DAC_CLK_SEL 1 /* 0:int, 1:ext */
+#define SX125x_TX_DAC_GAIN 2 /* 3:0, 2:-3, 1:-6, 0:-9 dBFS (default 2) */
+#define SX125x_TX_MIX_GAIN 14 /* -38 + 2*TxMixGain dB (default 14) */
+#define SX125x_TX_PLL_BW 1 /* 0:75, 1:150, 2:225, 3:300 kHz (default 3) */
+#define SX125x_TX_ANA_BW 0 /* 17.5 / 2*(41-TxAnaBw) MHz (default 0) */
+#define SX125x_TX_DAC_BW 5 /* 24 + 8*TxDacBw Nb FIR taps (default 2) */
+#define SX125x_RX_LNA_GAIN 1 /* 1 to 6, 1 highest gain */
+#define SX125x_RX_BB_GAIN 12 /* 0 to 15 , 15 highest gain */
+#define SX125x_LNA_ZIN 1 /* 0:50, 1:200 Ohms (default 1) */
+#define SX125x_RX_ADC_BW 7 /* 0 to 7, 2:100<BW<200, 5:200<BW<400,7:400<BW kHz SSB (default 7) */
+#define SX125x_RX_ADC_TRIM 6 /* 0 to 7, 6 for 32MHz ref, 5 for 36MHz ref */
+#define SX125x_RX_BB_BW 0 /* 0:750, 1:500, 2:375; 3:250 kHz SSB (default 1, max 3) */
+#define SX125x_RX_PLL_BW 0 /* 0:75, 1:150, 2:225, 3:300 kHz (default 3, max 3) */
+#define SX125x_ADC_TEMP 0 /* ADC temperature measurement mode (default 0) */
+#define SX125x_XOSC_GM_STARTUP 13 /* (default 13) */
+#define SX125x_XOSC_DISABLE 2 /* Disable of Xtal Oscillator blocks bit0:regulator, bit1:core(gm), bit2:amplifier */
+
+#endif // __SX125X_REGS_H__
diff --git a/libloragw/inc/loragw_sx1272_fsk.h b/libloragw/inc/loragw_sx1272_fsk.h
new file mode 100644
index 0000000..c202bc4
--- /dev/null
+++ b/libloragw/inc/loragw_sx1272_fsk.h
@@ -0,0 +1,113 @@
+/*
+ / _____) _ | |
+( (____ _____ ____ _| |_ _____ ____| |__
+ \____ \| ___ | (_ _) ___ |/ ___) _ \
+ _____) ) ____| | | || |_| ____( (___| | | |
+(______/|_____)_|_|_| \__)_____)\____)_| |_|
+ (C)2013 Semtech
+
+Description: SX1272 FSK modem registers
+
+License: Revised BSD License, see LICENSE.TXT file include in the project
+
+Maintainer: Michael Coracin
+*/
+#ifndef _LORAGW_SX1272_REGS_FSK_H
+#define _LORAGW_SX1272_REGS_FSK_H
+
+/*!
+ * ============================================================================
+ * SX1272 Internal registers Address
+ * ============================================================================
+ */
+#define SX1272_REG_FIFO 0x00
+// Common settings
+#define SX1272_REG_OPMODE 0x01
+#define SX1272_REG_BITRATEMSB 0x02
+#define SX1272_REG_BITRATELSB 0x03
+#define SX1272_REG_FDEVMSB 0x04
+#define SX1272_REG_FDEVLSB 0x05
+#define SX1272_REG_FRFMSB 0x06
+#define SX1272_REG_FRFMID 0x07
+#define SX1272_REG_FRFLSB 0x08
+// Tx settings
+#define SX1272_REG_PACONFIG 0x09
+#define SX1272_REG_PARAMP 0x0A
+#define SX1272_REG_OCP 0x0B
+// Rx settings
+#define SX1272_REG_LNA 0x0C
+#define SX1272_REG_RXCONFIG 0x0D
+#define SX1272_REG_RSSICONFIG 0x0E
+#define SX1272_REG_RSSICOLLISION 0x0F
+#define SX1272_REG_RSSITHRESH 0x10
+#define SX1272_REG_RSSIVALUE 0x11
+#define SX1272_REG_RXBW 0x12
+#define SX1272_REG_AFCBW 0x13
+#define SX1272_REG_OOKPEAK 0x14
+#define SX1272_REG_OOKFIX 0x15
+#define SX1272_REG_OOKAVG 0x16
+#define SX1272_REG_RES17 0x17
+#define SX1272_REG_RES18 0x18
+#define SX1272_REG_RES19 0x19
+#define SX1272_REG_AFCFEI 0x1A
+#define SX1272_REG_AFCMSB 0x1B
+#define SX1272_REG_AFCLSB 0x1C
+#define SX1272_REG_FEIMSB 0x1D
+#define SX1272_REG_FEILSB 0x1E
+#define SX1272_REG_PREAMBLEDETECT 0x1F
+#define SX1272_REG_RXTIMEOUT1 0x20
+#define SX1272_REG_RXTIMEOUT2 0x21
+#define SX1272_REG_RXTIMEOUT3 0x22
+#define SX1272_REG_RXDELAY 0x23
+// Oscillator settings
+#define SX1272_REG_OSC 0x24
+// Packet handler settings
+#define SX1272_REG_PREAMBLEMSB 0x25
+#define SX1272_REG_PREAMBLELSB 0x26
+#define SX1272_REG_SYNCCONFIG 0x27
+#define SX1272_REG_SYNCVALUE1 0x28
+#define SX1272_REG_SYNCVALUE2 0x29
+#define SX1272_REG_SYNCVALUE3 0x2A
+#define SX1272_REG_SYNCVALUE4 0x2B
+#define SX1272_REG_SYNCVALUE5 0x2C
+#define SX1272_REG_SYNCVALUE6 0x2D
+#define SX1272_REG_SYNCVALUE7 0x2E
+#define SX1272_REG_SYNCVALUE8 0x2F
+#define SX1272_REG_PACKETCONFIG1 0x30
+#define SX1272_REG_PACKETCONFIG2 0x31
+#define SX1272_REG_PAYLOADLENGTH 0x32
+#define SX1272_REG_NODEADRS 0x33
+#define SX1272_REG_BROADCASTADRS 0x34
+#define SX1272_REG_FIFOTHRESH 0x35
+// SM settings
+#define SX1272_REG_SEQCONFIG1 0x36
+#define SX1272_REG_SEQCONFIG2 0x37
+#define SX1272_REG_TIMERRESOL 0x38
+#define SX1272_REG_TIMER1COEF 0x39
+#define SX1272_REG_TIMER2COEF 0x3A
+// Service settings
+#define SX1272_REG_IMAGECAL 0x3B
+#define SX1272_REG_TEMP 0x3C
+#define SX1272_REG_LOWBAT 0x3D
+// Status
+#define SX1272_REG_IRQFLAGS1 0x3E
+#define SX1272_REG_IRQFLAGS2 0x3F
+// I/O settings
+#define SX1272_REG_DIOMAPPING1 0x40
+#define SX1272_REG_DIOMAPPING2 0x41
+// Version
+#define SX1272_REG_VERSION 0x42
+// Additional settings
+#define SX1272_REG_AGCREF 0x43
+#define SX1272_REG_AGCTHRESH1 0x44
+#define SX1272_REG_AGCTHRESH2 0x45
+#define SX1272_REG_AGCTHRESH3 0x46
+#define SX1272_REG_PLLHOP 0x4B
+#define SX1272_REG_TCXO 0x58
+#define SX1272_REG_PADAC 0x5A
+#define SX1272_REG_PLL 0x5C
+#define SX1272_REG_PLLLOWPN 0x5E
+#define SX1272_REG_FORMERTEMP 0x6C
+#define SX1272_REG_BITRATEFRAC 0x70
+
+#endif // _LORAGW_SX1272_REGS_FSK_H
diff --git a/libloragw/inc/loragw_sx1272_lora.h b/libloragw/inc/loragw_sx1272_lora.h
new file mode 100644
index 0000000..7f7a7a5
--- /dev/null
+++ b/libloragw/inc/loragw_sx1272_lora.h
@@ -0,0 +1,89 @@
+/*
+ / _____) _ | |
+( (____ _____ ____ _| |_ _____ ____| |__
+ \____ \| ___ | (_ _) ___ |/ ___) _ \
+ _____) ) ____| | | || |_| ____( (___| | | |
+(______/|_____)_|_|_| \__)_____)\____)_| |_|
+ (C)2013 Semtech
+
+Description: SX1272 LoRa modem registers and bits definitions
+
+License: Revised BSD License, see LICENSE.TXT file include in the project
+
+Maintainer: Michael Coracin
+*/
+#ifndef _LORAGW_SX1272_REGS_LORA_H
+#define _LORAGW_SX1272_REGS_LORA_H
+
+/*!
+ * ============================================================================
+ * SX1272 Internal registers Address
+ * ============================================================================
+ */
+#define SX1272_REG_LR_FIFO 0x00
+// Common settings
+#define SX1272_REG_LR_OPMODE 0x01
+#define SX1272_REG_LR_FRFMSB 0x06
+#define SX1272_REG_LR_FRFMID 0x07
+#define SX1272_REG_LR_FRFLSB 0x08
+// Tx settings
+#define SX1272_REG_LR_PACONFIG 0x09
+#define SX1272_REG_LR_PARAMP 0x0A
+#define SX1272_REG_LR_OCP 0x0B
+// Rx settings
+#define SX1272_REG_LR_LNA 0x0C
+// LoRa registers
+#define SX1272_REG_LR_FIFOADDRPTR 0x0D
+#define SX1272_REG_LR_FIFOTXBASEADDR 0x0E
+#define SX1272_REG_LR_FIFORXBASEADDR 0x0F
+#define SX1272_REG_LR_FIFORXCURRENTADDR 0x10
+#define SX1272_REG_LR_IRQFLAGSMASK 0x11
+#define SX1272_REG_LR_IRQFLAGS 0x12
+#define SX1272_REG_LR_RXNBBYTES 0x13
+#define SX1272_REG_LR_RXHEADERCNTVALUEMSB 0x14
+#define SX1272_REG_LR_RXHEADERCNTVALUELSB 0x15
+#define SX1272_REG_LR_RXPACKETCNTVALUEMSB 0x16
+#define SX1272_REG_LR_RXPACKETCNTVALUELSB 0x17
+#define SX1272_REG_LR_MODEMSTAT 0x18
+#define SX1272_REG_LR_PKTSNRVALUE 0x19
+#define SX1272_REG_LR_PKTRSSIVALUE 0x1A
+#define SX1272_REG_LR_RSSIVALUE 0x1B
+#define SX1272_REG_LR_HOPCHANNEL 0x1C
+#define SX1272_REG_LR_MODEMCONFIG1 0x1D
+#define SX1272_REG_LR_MODEMCONFIG2 0x1E
+#define SX1272_REG_LR_SYMBTIMEOUTLSB 0x1F
+#define SX1272_REG_LR_PREAMBLEMSB 0x20
+#define SX1272_REG_LR_PREAMBLELSB 0x21
+#define SX1272_REG_LR_PAYLOADLENGTH 0x22
+#define SX1272_REG_LR_PAYLOADMAXLENGTH 0x23
+#define SX1272_REG_LR_HOPPERIOD 0x24
+#define SX1272_REG_LR_FIFORXBYTEADDR 0x25
+#define SX1272_REG_LR_FEIMSB 0x28
+#define SX1272_REG_LR_FEIMID 0x29
+#define SX1272_REG_LR_FEILSB 0x2A
+#define SX1272_REG_LR_RSSIWIDEBAND 0x2C
+#define SX1272_REG_LR_DETECTOPTIMIZE 0x31
+#define SX1272_REG_LR_INVERTIQ 0x33
+#define SX1272_REG_LR_DETECTIONTHRESHOLD 0x37
+#define SX1272_REG_LR_SYNCWORD 0x39
+#define SX1272_REG_LR_INVERTIQ2 0x3B
+
+// end of documented register in datasheet
+// I/O settings
+#define SX1272_REG_LR_DIOMAPPING1 0x40
+#define SX1272_REG_LR_DIOMAPPING2 0x41
+// Version
+#define SX1272_REG_LR_VERSION 0x42
+// Additional settings
+#define SX1272_REG_LR_AGCREF 0x43
+#define SX1272_REG_LR_AGCTHRESH1 0x44
+#define SX1272_REG_LR_AGCTHRESH2 0x45
+#define SX1272_REG_LR_AGCTHRESH3 0x46
+#define SX1272_REG_LR_PLLHOP 0x4B
+#define SX1272_REG_LR_TCXO 0x58
+#define SX1272_REG_LR_PADAC 0x5A
+#define SX1272_REG_LR_PLL 0x5C
+#define SX1272_REG_LR_PLLLOWPN 0x5E
+#define SX1272_REG_LR_FORMERTEMP 0x6C
+
+#endif // _LORAGW_SX1272_REGS_LORA_H
diff --git a/libloragw/inc/loragw_sx1276_fsk.h b/libloragw/inc/loragw_sx1276_fsk.h
new file mode 100644
index 0000000..944958a
--- /dev/null
+++ b/libloragw/inc/loragw_sx1276_fsk.h
@@ -0,0 +1,112 @@
+/*
+ / _____) _ | |
+( (____ _____ ____ _| |_ _____ ____| |__
+ \____ \| ___ | (_ _) ___ |/ ___) _ \
+ _____) ) ____| | | || |_| ____( (___| | | |
+(______/|_____)_|_|_| \__)_____)\____)_| |_|
+ (C)2013 Semtech
+
+Description: SX1276 FSK modem registers
+
+License: Revised BSD License, see LICENSE.TXT file include in the project
+
+Maintainer: Michael Coracin
+*/
+#ifndef _LORAGW_SX1276_REGS_FSK_H
+#define _LORAGW_SX1276_REGS_FSK_H
+
+/*!
+ * ============================================================================
+ * SX1276 Internal registers Address
+ * ============================================================================
+ */
+#define SX1276_REG_FIFO 0x00
+// Common settings
+#define SX1276_REG_OPMODE 0x01
+#define SX1276_REG_BITRATEMSB 0x02
+#define SX1276_REG_BITRATELSB 0x03
+#define SX1276_REG_FDEVMSB 0x04
+#define SX1276_REG_FDEVLSB 0x05
+#define SX1276_REG_FRFMSB 0x06
+#define SX1276_REG_FRFMID 0x07
+#define SX1276_REG_FRFLSB 0x08
+// Tx settings
+#define SX1276_REG_PACONFIG 0x09
+#define SX1276_REG_PARAMP 0x0A
+#define SX1276_REG_OCP 0x0B
+// Rx settings
+#define SX1276_REG_LNA 0x0C
+#define SX1276_REG_RXCONFIG 0x0D
+#define SX1276_REG_RSSICONFIG 0x0E
+#define SX1276_REG_RSSICOLLISION 0x0F
+#define SX1276_REG_RSSITHRESH 0x10
+#define SX1276_REG_RSSIVALUE 0x11
+#define SX1276_REG_RXBW 0x12
+#define SX1276_REG_AFCBW 0x13
+#define SX1276_REG_OOKPEAK 0x14
+#define SX1276_REG_OOKFIX 0x15
+#define SX1276_REG_OOKAVG 0x16
+#define SX1276_REG_RES17 0x17
+#define SX1276_REG_RES18 0x18
+#define SX1276_REG_RES19 0x19
+#define SX1276_REG_AFCFEI 0x1A
+#define SX1276_REG_AFCMSB 0x1B
+#define SX1276_REG_AFCLSB 0x1C
+#define SX1276_REG_FEIMSB 0x1D
+#define SX1276_REG_FEILSB 0x1E
+#define SX1276_REG_PREAMBLEDETECT 0x1F
+#define SX1276_REG_RXTIMEOUT1 0x20
+#define SX1276_REG_RXTIMEOUT2 0x21
+#define SX1276_REG_RXTIMEOUT3 0x22
+#define SX1276_REG_RXDELAY 0x23
+// Oscillator settings
+#define SX1276_REG_OSC 0x24
+// Packet handler settings
+#define SX1276_REG_PREAMBLEMSB 0x25
+#define SX1276_REG_PREAMBLELSB 0x26
+#define SX1276_REG_SYNCCONFIG 0x27
+#define SX1276_REG_SYNCVALUE1 0x28
+#define SX1276_REG_SYNCVALUE2 0x29
+#define SX1276_REG_SYNCVALUE3 0x2A
+#define SX1276_REG_SYNCVALUE4 0x2B
+#define SX1276_REG_SYNCVALUE5 0x2C
+#define SX1276_REG_SYNCVALUE6 0x2D
+#define SX1276_REG_SYNCVALUE7 0x2E
+#define SX1276_REG_SYNCVALUE8 0x2F
+#define SX1276_REG_PACKETCONFIG1 0x30
+#define SX1276_REG_PACKETCONFIG2 0x31
+#define SX1276_REG_PAYLOADLENGTH 0x32
+#define SX1276_REG_NODEADRS 0x33
+#define SX1276_REG_BROADCASTADRS 0x34
+#define SX1276_REG_FIFOTHRESH 0x35
+// SM settings
+#define SX1276_REG_SEQCONFIG1 0x36
+#define SX1276_REG_SEQCONFIG2 0x37
+#define SX1276_REG_TIMERRESOL 0x38
+#define SX1276_REG_TIMER1COEF 0x39
+#define SX1276_REG_TIMER2COEF 0x3A
+// Service settings
+#define SX1276_REG_IMAGECAL 0x3B
+#define SX1276_REG_TEMP 0x3C
+#define SX1276_REG_LOWBAT 0x3D
+// Status
+#define SX1276_REG_IRQFLAGS1 0x3E
+#define SX1276_REG_IRQFLAGS2 0x3F
+// I/O settings
+#define SX1276_REG_DIOMAPPING1 0x40
+#define SX1276_REG_DIOMAPPING2 0x41
+// Version
+#define SX1276_REG_VERSION 0x42
+// Additional settings
+#define SX1276_REG_PLLHOP 0x44
+#define SX1276_REG_TCXO 0x4B
+#define SX1276_REG_PADAC 0x4D
+#define SX1276_REG_FORMERTEMP 0x5B
+#define SX1276_REG_BITRATEFRAC 0x5D
+#define SX1276_REG_AGCREF 0x61
+#define SX1276_REG_AGCTHRESH1 0x62
+#define SX1276_REG_AGCTHRESH2 0x63
+#define SX1276_REG_AGCTHRESH3 0x64
+#define SX1276_REG_PLL 0x70
+
+#endif // __SX1276_REGS_FSK_H__
diff --git a/libloragw/inc/loragw_sx1276_lora.h b/libloragw/inc/loragw_sx1276_lora.h
new file mode 100644
index 0000000..d03e1ff
--- /dev/null
+++ b/libloragw/inc/loragw_sx1276_lora.h
@@ -0,0 +1,94 @@
+/*
+ / _____) _ | |
+( (____ _____ ____ _| |_ _____ ____| |__
+ \____ \| ___ | (_ _) ___ |/ ___) _ \
+ _____) ) ____| | | || |_| ____( (___| | | |
+(______/|_____)_|_|_| \__)_____)\____)_| |_|
+ (C)2013 Semtech
+
+Description: SX1276 LoRa modem registers
+
+License: Revised BSD License, see LICENSE.TXT file include in the project
+
+Maintainer: Michael Coracin
+*/
+#ifndef _LORAGW_SX1276_REGS_LORA_H
+#define _LORAGW_SX1276_REGS_LORA_H
+
+/*!
+ * ============================================================================
+ * SX1276 Internal registers Address
+ * ============================================================================
+ */
+#define SX1276_REG_LR_FIFO 0x00
+// Common settings
+#define SX1276_REG_LR_OPMODE 0x01
+#define SX1276_REG_LR_FRFMSB 0x06
+#define SX1276_REG_LR_FRFMID 0x07
+#define SX1276_REG_LR_FRFLSB 0x08
+// Tx settings
+#define SX1276_REG_LR_PACONFIG 0x09
+#define SX1276_REG_LR_PARAMP 0x0A
+#define SX1276_REG_LR_OCP 0x0B
+// Rx settings
+#define SX1276_REG_LR_LNA 0x0C
+// LoRa registers
+#define SX1276_REG_LR_FIFOADDRPTR 0x0D
+#define SX1276_REG_LR_FIFOTXBASEADDR 0x0E
+#define SX1276_REG_LR_FIFORXBASEADDR 0x0F
+#define SX1276_REG_LR_FIFORXCURRENTADDR 0x10
+#define SX1276_REG_LR_IRQFLAGSMASK 0x11
+#define SX1276_REG_LR_IRQFLAGS 0x12
+#define SX1276_REG_LR_RXNBBYTES 0x13
+#define SX1276_REG_LR_RXHEADERCNTVALUEMSB 0x14
+#define SX1276_REG_LR_RXHEADERCNTVALUELSB 0x15
+#define SX1276_REG_LR_RXPACKETCNTVALUEMSB 0x16
+#define SX1276_REG_LR_RXPACKETCNTVALUELSB 0x17
+#define SX1276_REG_LR_MODEMSTAT 0x18
+#define SX1276_REG_LR_PKTSNRVALUE 0x19
+#define SX1276_REG_LR_PKTRSSIVALUE 0x1A
+#define SX1276_REG_LR_RSSIVALUE 0x1B
+#define SX1276_REG_LR_HOPCHANNEL 0x1C
+#define SX1276_REG_LR_MODEMCONFIG1 0x1D
+#define SX1276_REG_LR_MODEMCONFIG2 0x1E
+#define SX1276_REG_LR_SYMBTIMEOUTLSB 0x1F
+#define SX1276_REG_LR_PREAMBLEMSB 0x20
+#define SX1276_REG_LR_PREAMBLELSB 0x21
+#define SX1276_REG_LR_PAYLOADLENGTH 0x22
+#define SX1276_REG_LR_PAYLOADMAXLENGTH 0x23
+#define SX1276_REG_LR_HOPPERIOD 0x24
+#define SX1276_REG_LR_FIFORXBYTEADDR 0x25
+#define SX1276_REG_LR_MODEMCONFIG3 0x26
+#define SX1276_REG_LR_FEIMSB 0x28
+#define SX1276_REG_LR_FEIMID 0x29
+#define SX1276_REG_LR_FEILSB 0x2A
+#define SX1276_REG_LR_RSSIWIDEBAND 0x2C
+#define SX1276_REG_LR_TEST2F 0x2F
+#define SX1276_REG_LR_TEST30 0x30
+#define SX1276_REG_LR_DETECTOPTIMIZE 0x31
+#define SX1276_REG_LR_INVERTIQ 0x33
+#define SX1276_REG_LR_TEST36 0x36
+#define SX1276_REG_LR_DETECTIONTHRESHOLD 0x37
+#define SX1276_REG_LR_SYNCWORD 0x39
+#define SX1276_REG_LR_TEST3A 0x3A
+#define SX1276_REG_LR_INVERTIQ2 0x3B
+
+// end of documented register in datasheet
+// I/O settings
+#define SX1276_REG_LR_DIOMAPPING1 0x40
+#define SX1276_REG_LR_DIOMAPPING2 0x41
+// Version
+#define SX1276_REG_LR_VERSION 0x42
+// Additional settings
+#define SX1276_REG_LR_PLLHOP 0x44
+#define SX1276_REG_LR_TCXO 0x4B
+#define SX1276_REG_LR_PADAC 0x4D
+#define SX1276_REG_LR_FORMERTEMP 0x5B
+#define SX1276_REG_LR_BITRATEFRAC 0x5D
+#define SX1276_REG_LR_AGCREF 0x61
+#define SX1276_REG_LR_AGCTHRESH1 0x62
+#define SX1276_REG_LR_AGCTHRESH2 0x63
+#define SX1276_REG_LR_AGCTHRESH3 0x64
+#define SX1276_REG_LR_PLL 0x70
+
+#endif // _LORAGW_SX1276_REGS_LORA_H
diff --git a/libloragw/library.cfg b/libloragw/library.cfg
new file mode 100644
index 0000000..f962ba2
--- /dev/null
+++ b/libloragw/library.cfg
@@ -0,0 +1,12 @@
+# That file will be included in the Makefile files that have hardware dependencies
+
+### Debug options ###
+# Set the DEBUG_* to 1 to activate debug mode in individual modules.
+# Warning: that makes the module *very verbose*, do not use for production
+
+DEBUG_AUX= 0
+DEBUG_SPI= 0
+DEBUG_REG= 0
+DEBUG_HAL= 0
+DEBUG_LBT= 0
+DEBUG_GPS= 0
diff --git a/libloragw/readme.md b/libloragw/readme.md
new file mode 100644
index 0000000..bdbf97f
--- /dev/null
+++ b/libloragw/readme.md
@@ -0,0 +1,457 @@
+ / _____) _ | |
+ ( (____ _____ ____ _| |_ _____ ____| |__
+ \____ \| ___ | (_ _) ___ |/ ___) _ \
+ _____) ) ____| | | || |_| ____( (___| | | |
+ (______/|_____)_|_|_| \__)_____)\____)_| |_|
+ (C)2013 Semtech-Cycleo
+
+LoRa concentrator HAL user manual
+============================
+
+1. Introduction
+---------------
+
+The LoRa concentrator Hardware Abstraction Layer is a C library that allow you
+to use a Semtech concentrator chip through a reduced number of high level C
+functions to configure the hardware, send and receive packets.
+
+The Semtech LoRa concentrator is a digital multi-channel multi-standard packet
+radio used to send and receive packets wirelessly using LoRa or FSK modulations.
+
+2. Components of the library
+----------------------------
+
+The library is composed of 6(8) modules:
+
+* loragw_hal
+* loragw_reg
+* loragw_spi
+* loragw_aux
+* loragw_gps
+* loragw_radio
+* loragw_fpga (only for SX1301AP2 ref design)
+* loragw_lbt (only for SX1301AP2 ref design)
+
+The library also contains basic test programs to demonstrate code use and check
+functionality.
+
+### 2.1. loragw_hal ###
+
+This is the main module and contains the high level functions to configure and
+use the LoRa concentrator:
+
+* lgw_board_setconf, to set the configuration of the concentrator
+* lgw_rxrf_setconf, to set the configuration of the radio channels
+* lgw_rxif_setconf, to set the configuration of the IF+modem channels
+* lgw_txgain_setconf, to set the configuration of the concentrator gain table
+* lgw_start, to apply the set configuration to the hardware and start it
+* lgw_stop, to stop the hardware
+* 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
+
+For an standard application, include only this module.
+The use of this module is detailed on the usage section.
+
+/!\ When sending a packet, there is a delay (approx 1.5ms) for the analog
+circuitry to start and be stable. This delay is adjusted by the HAL depending
+on the board version (lgw_i_tx_start_delay_us).
+
+In 'timestamp' mode, this is transparent: the modem is started
+lgw_i_tx_start_delay_us microseconds before the user-set timestamp value is
+reached, the preamble of the packet start right when the internal timestamp
+counter reach target value.
+
+In 'immediate' mode, the packet is emitted as soon as possible: transferring the
+packet (and its parameters) from the host to the concentrator takes some time,
+then there is the lgw_i_tx_start_delay_us, then the packet is emitted.
+
+In 'triggered' mode (aka PPS/GPS mode), the packet, typically a beacon, is
+emitted lgw_i_tx_start_delay_us microsenconds after a rising edge of the
+trigger signal. Because there is no way to anticipate the triggering event and
+start the analog circuitry beforehand, that delay must be taken into account in
+the protocol.
+
+### 2.2. loragw_reg ###
+
+This module is used to access to the LoRa concentrator registers by name instead
+of by address:
+
+* lgw_connect, to initialise and check the connection with the hardware
+* lgw_disconnect, to disconnect the hardware
+* lgw_soft_reset, to reset the whole hardware by resetting the register array
+* lgw_reg_check, to check all registers vs. their default value and output the
+result to a file
+* lgw_reg_r, read a named register
+* 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
+
+This module handles pagination, 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 burst length constraints.
+
+It make the code much easier to read and to debug.
+Moreover, if registers are relocated between different hardware revisions but
+keep the same function, the code written using register names can be reused "as
+is".
+
+If you need access to all the registers, include this module in your
+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 ###
+
+This module contains the functions to access the LoRa concentrator register
+array through the SPI interface:
+
+* 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
+
+Please *do not* include that module directly into your application.
+
+**/!\ Warning** Accessing the LoRa concentrator register array without the
+checks and safety provided by the functions in loragw_reg is not recommended.
+
+### 2.4. loragw_aux ###
+
+This module contains a single host-dependant function wait_ms to pause for a
+defined amount of milliseconds.
+
+The procedure to start and configure the LoRa concentrator hardware contained in
+the loragw_hal module requires to wait for several milliseconds at certain
+steps, typically to allow for supply voltages or clocks to stabilize after been
+switched on.
+
+An accuracy of 1 ms or less is ideal.
+If your system does not allow that level of accuracy, make sure that the actual
+delay is *longer* that the time specified when the function is called (ie.
+wait_ms(X) **MUST NOT** before X milliseconds under any circumstance).
+
+If the minimum delays are not guaranteed during the configuration and start
+procedure, the hardware might not work at nominal performance.
+Most likely, it will not work at all.
+
+### 2.5. loragw_gps ###
+
+This module contains functions to synchronize the concentrator internal
+counter with an absolute time reference, in our case a GPS satellite receiver.
+
+The internal concentrator counter is used to timestamp incoming packets and to
+triggers outgoing packets with a microsecond accuracy.
+In some cases, it might be useful to be able to transform that internal
+timestamp (that is independent for each concentrator running in a typical
+networked system) into an absolute GPS time.
+
+In a typical implementation a GPS specific thread will be called, doing the
+following things after opening the serial port:
+
+* blocking reads on the serial port (using system read() function)
+* parse UBX messages (using lgw_parse_ubx) to get actual native GPS time
+* parse NMEA sentences (using lgw_parse_nmea) to get location and UTC time
+Note: the RMC sentence gives UTC time, not native GPS time.
+
+And each time an NAV-TIMEGPS UBX message has been received:
+
+* get the concentrator timestamp (using lgw_get_trigcnt, mutex needed to
+ protect access to the concentrator)
+* get the GPS time contained in the UBX message (using lgw_gps_get)
+* call the lgw_gps_sync function (use mutex to protect the time reference that
+ should be a global shared variable).
+
+Then, in other threads, you can simply used that continuously adjusted time
+reference to convert internal timestamps to GPS time (using lgw_cnt2gps) or
+the other way around (using lgw_gps2cnt). Inernal concentrator timestamp can
+also be converted to/from UTC time using lgw_cnt2utc/lgw_utc2cnt functions.
+
+### 2.6. loragw_radio ###
+
+This module contains functions to handle the configuration of SX125x and
+SX127x radios.
+
+### 2.7. loragw_fpga ###
+
+This module contains the description of the FPGA registers, the functions to
+read/write those registers, and a function to configure the FPGA features.
+
+This module is only required for SX1301AP2 reference design.
+
+### 2.8. loragw_lbt ###
+
+This module contains functions to configure and use the "Listen-Before-Talk"
+feature (refered as LBT below). It depends on the loragw_fpga and loragw_radio
+modules.
+
+LBT feature is only available on SX1301AP2 reference design, which provides the
+FPGA and the SX127x radio required to accomplish the feature.
+
+The FPGA implements the following Finite State Machine (FSM) to scan the defined
+LBT channels (8 max), and also compute the RSSI histogram for spectral scan,
+using the SX127x radio.
+
+
+ +-------+
+ +------------------>+ idle +------------------+
+ | +-------+ v
+ | | +-----------+
+ | | | clean mem |
+ | v +-----------+
+ | +----------+ |
+ | | set freq |<---------------+
+ | +----------+
+ | |
+ | v
+ | +----------+
+ | | wait pll |
+ | | lock |
+ | +----------+
+ | | (SCAN_CHANNEL)
+ | v +-----------+
+ | +-----------+ | |
+ | | +----------+ v
+ | +-->| read RSSI | +------------+
+ | | | +<---------------+ calc histo |
+ | | +-----------+ SCANNING +------------+
+ | | | |
+ | SCANNING | | (LBT_CHANNEL) |
+ | | v |
+ | | +-------------+ |
+ | | | compare | |
+ | +--+ with | |
+ | | RSSI_TARGET | HISTO_DONE |
+ | +-------------+ |
+ | | |
+ | SCAN DONE | |
+ | v |
+ | +------------+ |
+ | | increase | |
+ +-----------------+ +<--------------------+
+ | freq |
+ +------------+
+
+
+
+In order to configure the LBT, the following parameters have to be set:
+- RSSI_TARGET: signal strength target used to detect if the channel is clear
+ or not.
+ RSSI_TARGET_dBm = -RSSI_TARGET/2
+- LBT_CHx_FREQ_OFFSET: with x=[0..7], offset from the predefined LBT start
+ frequency (863MHz or 915MHz depending on FPGA image),
+ in 100KHz unit.
+- LBT_SCAN_TIME_CHx: with x=[0..7], the channel scan time to be used for this
+ LBT channel: 128µs or 5000µs
+
+With this FSM, the FPGA keeps the last instant when each channel was free during
+more than LBT_SCAN_TIME_CHx µs.
+
+Then, the HAL, when receiving a downlink request, will first determine on which
+LBT channel this downlink is supposed to be sent and then checks if the channel
+is busy or if downlink is allowed.
+
+In order to determine if a downlink is allowed or not, the HAL does:
+- read the LBT_TIMESTAMP_CH of the channel on which downlink is requested. This
+ gives the last time when channel was free (LBT_TIME).
+- compute the time on air of the downlink packet to determine the end time of
+ the packet emission (PKT_END_TIME).
+- if ((PKT_END_TIME - LBT_TIME) < TX_MAX_TIME)
+ ALLOWED = TRUE
+ else
+ ALLOWED = FALSE
+ endif
+ where TX_MAX_TIME is the maximum time allowed to send a packet since the
+ last channel free time (this depends on the channel scan time ).
+
+
+3. Software build process
+--------------------------
+
+### 3.1. Details of the software ###
+
+The library is written following ANSI C conventions but using C99 explicit
+length data type for all data exchanges with hardware and for parameters.
+
+The loragw_aux module contains POSIX dependant functions for millisecond
+accuracy pause.
+For embedded platforms, the function could be rewritten using hardware timers.
+
+### 3.2. Building options ###
+
+All modules use a fprintf(stderr,...) function to display debug diagnostic
+messages if the DEBUG_xxx is set to 1 in library.cfg
+
+### 3.3. Building procedures ###
+
+For cross-compilation set the ARCH and CROSS_COMPILE variables in the Makefile,
+or in your shell environment, with the correct toolchain name and path.
+ex:
+export PATH=/home/foo/rpi-toolchain/tools/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian-x64/bin:$PATH
+export ARCH=arm
+export CROSS_COMPILE=arm-linux-gnueabihf-
+
+The Makefile in the libloragw directory will parse the library.cfg file and
+generate a config.h C header file containing #define options.
+Those options enables and disables sections of code in the loragw_xxx.h files
+and the *.c source files.
+
+The library.cfg is also used directly to select the proper set of dynamic
+libraries to be linked with.
+
+### 3.4. Export ###
+
+Once build, to use that library on another system, you need to export the
+following files :
+
+* libloragw/library.cfg -> root configuration file
+* libloragw/libloragw.a -> static library, to be linked with a program
+* libloragw/readme.md -> required for license compliance
+* libloragw/inc/config.h -> C configuration flags, derived from library.cfg
+* libloragw/inc/loragw_*.h -> take only the ones you need (eg. _hal and _gps)
+
+After statically linking the library to your application, only the license
+is required to be kept or copied inside your program documentation.
+
+4. Hardware dependencies
+------------------------
+
+### 4.1. Hardware revision ###
+
+The loragw_reg and loragw_hal are written for a specific version on the Semtech
+hardware (IP and/or silicon revision).
+
+This code has been written for:
+
+* Semtech SX1301 chip
+* Semtech SX1257 or SX1255 I/Q transceivers
+
+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) ###
+
+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
+satellite constellation).
+The serial link must appear as a "tty" device in the /dev/ directory, and the
+user launching the program must have the proper system rights to read and
+write on that device.
+Use `chmod a+rw` to allow all users to access that specific tty device, or use
+sudo to run all your programs (eg. `sudo ./test_loragw_gps`).
+
+In the current revision, the library only reads data from the serial port,
+expecting to receive NMEA frames that are generally sent by GPS receivers as
+soon as they are powered up, and UBX messages which are proprietary to u-blox
+modules.
+
+The GPS receiver **MUST** send UBX messages shortly after sending a PPS pulse
+on to allow internal concentrator timestamps to be converted to absolute GPS time.
+If the GPS receiver sends a GGA NMEA sentence, the gateway 3D position will
+also be available.
+
+5. Usage
+--------
+
+### 5.1. Setting the software environment ###
+
+For a typical application you need to:
+
+* include loragw_hal.h in your program source
+* link to the libloragw.a static library during compilation
+* link to the librt library due to loragw_aux dependencies (timing functions)
+
+For an application that will also access the concentrator configuration
+registers directly (eg. for advanced configuration) you also need to:
+
+* include loragw_reg.h in your program source
+
+### 5.2. Using the software API ###
+
+To use the HAL in your application, you must follow some basic rules:
+
+* configure the radios path and IF+modem path before starting the radio
+* the configuration is only transferred to hardware when you call the *start*
+ function
+* you cannot receive packets until one (or +) radio is enabled AND one (or +)
+ IF+modem part is enabled AND the concentrator is started
+* you cannot send packets until one (or +) radio is enabled AND the concentrator
+ is started
+* you must stop the concentrator before changing the configuration
+
+A typical application flow for using the HAL is the following:
+
+ <configure the radios and IF+modems>
+ <start the LoRa concentrator>
+ loop {
+ <fetch packets that were received by the concentrator>
+ <process, store and/or forward received packets>
+ <send packets through the concentrator>
+ }
+ <stop the concentrator>
+
+**/!\ Warning** The lgw_send function is non-blocking and returns while the
+LoRa concentrator is still sending the packet, or even before the packet has
+started to be transmitted if the packet is triggered on a future event.
+While a packet is emitted, no packet can be received (limitation intrinsic to
+most radio frequency systems).
+
+Your application *must* take into account the time it takes to send a packet or
+check the status (using lgw_status) before attempting to send another packet.
+
+Trying to send a packet while the previous packet has not finished being send
+will result in the previous packet not being sent or being sent only partially
+(resulting in a CRC error in the receiver).
+
+### 5.3. Debugging mode ###
+
+To debug your application, it might help to compile the loragw_hal function
+with the debug messages activated (set DEBUG_HAL=1 in library.cfg).
+It then send a lot of details, including detailed error messages to *stderr*.
+
+6. License
+-----------
+
+Copyright (c) 2013, 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/libloragw/src/agc_fw.var b/libloragw/src/agc_fw.var
new file mode 100644
index 0000000..5083a0e
--- /dev/null
+++ b/libloragw/src/agc_fw.var
@@ -0,0 +1,529 @@
+/*
+ / _____) _ | |
+( (____ _____ ____ _| |_ _____ ____| |__
+ \____ \| ___ | (_ _) ___ |/ ___) _ \
+ _____) ) ____| | | || |_| ____( (___| | | |
+(______/|_____)_|_|_| \__)_____)\____)_| |_|
+ (C)2013 Semtech-Cycleo
+
+Description:
+ AGC firmware
+
+License: Revised BSD License, see LICENSE.TXT file include in the project
+Maintainer: Matthieu Leurent
+*/
+
+static uint8_t agc_firmware[MCU_AGC_FW_BYTE] = {
+0x8A, 0x51, 0x11, 0x28, 0xFF, 0xBF, 0xFF, 0xBF, 0x80, 0x40, 0x03, 0x4E, 0x83, 0x52, 0x03, 0x53,
+0xAC, 0x00, 0x04, 0x88, 0xAD, 0x40, 0x0A, 0xC8, 0xAE, 0x40, 0x01, 0x88, 0xAF, 0x80, 0x8A, 0x51,
+0x13, 0x68, 0x8A, 0x51, 0x59, 0x2D, 0x8B, 0xDC, 0x1A, 0x68, 0xA0, 0xE0, 0x8A, 0x51, 0x27, 0x60,
+0x40, 0xF0, 0x9B, 0x40, 0x10, 0xF0, 0x8B, 0x00, 0x2F, 0x88, 0x81, 0x80, 0x2E, 0x48, 0x8A, 0xC0,
+0x2D, 0x48, 0x84, 0x80, 0x2C, 0x8E, 0x83, 0xC0, 0x80, 0x0E, 0x00, 0xCE, 0x09, 0x80, 0x95, 0x41,
+0x96, 0x41, 0x97, 0x81, 0x98, 0x01, 0x99, 0x41, 0x9A, 0x41, 0x9B, 0x81, 0x9C, 0x41, 0x9E, 0x81,
+0x19, 0x54, 0x19, 0x95, 0x18, 0x56, 0x8B, 0x41, 0xD4, 0x41, 0x02, 0xF0, 0x54, 0x02, 0x03, 0x18,
+0x59, 0xA8, 0x54, 0x08, 0x51, 0x3E, 0x84, 0x80, 0x83, 0x93, 0x80, 0x81, 0x54, 0x08, 0x5D, 0xBE,
+0x84, 0x80, 0x07, 0x70, 0x80, 0x40, 0x54, 0x08, 0x72, 0x60, 0x8A, 0x51, 0x54, 0x08, 0x5D, 0xBE,
+0x84, 0x80, 0x00, 0xCE, 0xF0, 0x39, 0x96, 0x00, 0x54, 0x9C, 0x52, 0x68, 0x83, 0x52, 0x03, 0x53,
+0x18, 0x14, 0x55, 0xA8, 0x83, 0x52, 0x03, 0x53, 0x18, 0xD0, 0x18, 0x55, 0x18, 0x11, 0xD4, 0x8A,
+0x35, 0xA8, 0xD3, 0x81, 0x08, 0xF0, 0x53, 0x42, 0x03, 0x18, 0x6F, 0x28, 0x53, 0x48, 0x55, 0x7E,
+0x84, 0x80, 0x04, 0xF0, 0x83, 0x93, 0x80, 0x40, 0x53, 0x48, 0x55, 0x7E, 0x84, 0x80, 0x00, 0x48,
+0x96, 0x00, 0x53, 0x48, 0x95, 0x00, 0x98, 0x54, 0x98, 0x10, 0xD3, 0xCA, 0x5A, 0xA8, 0x10, 0xF0,
+0x9B, 0x40, 0x08, 0x40, 0xAB, 0x40, 0x51, 0x3E, 0x84, 0x80, 0x00, 0x48, 0xAA, 0x00, 0x23, 0x3E,
+0x84, 0x80, 0x8A, 0x51, 0x1D, 0xE5, 0x8A, 0x51, 0xA7, 0x40, 0x05, 0x30, 0x03, 0xD0, 0xA7, 0x0D,
+0xFF, 0x7E, 0x03, 0x9D, 0x7E, 0x28, 0x2A, 0x08, 0x2D, 0x7E, 0x84, 0x80, 0x8A, 0x51, 0x1D, 0xE5,
+0x8A, 0x51, 0xA8, 0xC0, 0x28, 0x47, 0x27, 0x44, 0x01, 0x38, 0xA9, 0x00, 0x2B, 0x48, 0x22, 0xFE,
+0x84, 0x80, 0x29, 0x08, 0x80, 0x40, 0x2B, 0x48, 0x22, 0xFE, 0x84, 0x80, 0x00, 0x48, 0x83, 0x96,
+0xA0, 0x80, 0x83, 0x52, 0x2B, 0x48, 0x83, 0x96, 0xA1, 0xC0, 0x0C, 0x30, 0xD3, 0xE1, 0x08, 0x40,
+0x18, 0x12, 0x80, 0xF0, 0x9B, 0x40, 0x10, 0xF0, 0x9E, 0x40, 0x13, 0x1F, 0xAB, 0xE8, 0x83, 0x52,
+0x03, 0x53, 0x18, 0x14, 0xAE, 0xE8, 0x83, 0x52, 0x03, 0x53, 0x18, 0xD0, 0x92, 0x1F, 0xB4, 0xA8,
+0x83, 0x96, 0x03, 0x53, 0xA4, 0x54, 0xB7, 0x28, 0x83, 0x96, 0x03, 0x53, 0xA4, 0x10, 0x83, 0x52,
+0x12, 0xDF, 0xBE, 0x28, 0x83, 0x96, 0x03, 0x53, 0x24, 0x14, 0xC1, 0x68, 0x83, 0x96, 0x03, 0x53,
+0x24, 0xD0, 0x83, 0x52, 0x5F, 0xC8, 0xA7, 0x40, 0x06, 0x30, 0x03, 0xD0, 0xA7, 0x0D, 0xFF, 0x7E,
+0x03, 0x9D, 0xC5, 0xA8, 0x12, 0xC8, 0x3F, 0xB9, 0x27, 0x44, 0xA5, 0x00, 0x83, 0x96, 0x24, 0x5C,
+0xD3, 0xE8, 0x83, 0x52, 0x9E, 0x15, 0x83, 0x52, 0x13, 0x08, 0x0F, 0x39, 0x3F, 0xFE, 0x84, 0x80,
+0x83, 0x93, 0x00, 0x48, 0xA1, 0xC0, 0x21, 0xC8, 0xA9, 0x00, 0x3F, 0x30, 0xA9, 0x85, 0x29, 0x08,
+0x83, 0x96, 0xA0, 0x80, 0x00, 0xB0, 0x83, 0x52, 0x13, 0xDB, 0x01, 0xF0, 0x83, 0x96, 0xA1, 0xC0,
+0x08, 0xF0, 0xD3, 0xE1, 0x8A, 0x51, 0x21, 0xDF, 0xF1, 0xE8, 0x83, 0x52, 0x03, 0x53, 0x19, 0x96,
+0xF4, 0xE8, 0x83, 0x52, 0x03, 0x53, 0x19, 0x52, 0xA1, 0x1F, 0xFA, 0x28, 0x83, 0x52, 0x03, 0x53,
+0x99, 0xD6, 0xFD, 0x68, 0x83, 0x52, 0x03, 0x53, 0x99, 0x92, 0x83, 0x96, 0xA4, 0x9C, 0x0D, 0xA9,
+0x6C, 0xB0, 0xA0, 0x80, 0x00, 0xB0, 0x83, 0x52, 0x13, 0xDB, 0x01, 0xF0, 0x83, 0x96, 0xA1, 0xC0,
+0x0A, 0x30, 0xD3, 0xE1, 0x8A, 0x51, 0x04, 0xF0, 0x19, 0xA9, 0x60, 0x30, 0xA0, 0x80, 0x00, 0xB0,
+0x83, 0x52, 0x13, 0xDB, 0x01, 0xF0, 0x83, 0x96, 0xA1, 0xC0, 0x0A, 0x30, 0xD3, 0xE1, 0x8A, 0x51,
+0x05, 0x30, 0x83, 0x96, 0xA0, 0x80, 0x00, 0xB0, 0x83, 0x52, 0x13, 0xDB, 0x01, 0xF0, 0x83, 0x96,
+0xA1, 0xC0, 0x0B, 0x70, 0xD3, 0xE1, 0x8A, 0x51, 0x25, 0x08, 0x83, 0x96, 0xA0, 0x80, 0x00, 0xB0,
+0x83, 0x52, 0x13, 0xDB, 0x01, 0xF0, 0x83, 0x96, 0xA1, 0xC0, 0x04, 0xF0, 0xD3, 0xE1, 0x8A, 0x51,
+0x11, 0xC8, 0x83, 0x96, 0xA0, 0x80, 0x00, 0xB0, 0x83, 0x52, 0x13, 0xDB, 0x01, 0xF0, 0x83, 0x96,
+0xA1, 0xC0, 0x05, 0x30, 0xD3, 0xE1, 0x8A, 0x51, 0x10, 0x88, 0x83, 0x96, 0xA0, 0x80, 0x00, 0xB0,
+0x83, 0x52, 0x13, 0xDB, 0x01, 0xF0, 0x83, 0x96, 0xA1, 0xC0, 0x06, 0x30, 0xD3, 0xE1, 0x8A, 0x51,
+0x83, 0x96, 0x00, 0xB0, 0xA0, 0xC1, 0xA0, 0x0A, 0x83, 0x52, 0x13, 0xDB, 0x01, 0xF0, 0x83, 0x96,
+0xA1, 0xC0, 0x00, 0xB0, 0xD3, 0xE1, 0x8A, 0x51, 0x35, 0xB0, 0xA7, 0x40, 0xA7, 0x0B, 0x56, 0xE9,
+0x05, 0x30, 0x83, 0x96, 0x03, 0x53, 0xA0, 0x80, 0x00, 0xB0, 0x83, 0x52, 0x13, 0xDB, 0x01, 0xF0,
+0x83, 0x96, 0xA1, 0xC0, 0x00, 0xB0, 0xD3, 0xE1, 0x8A, 0x51, 0x03, 0x30, 0xA8, 0xC0, 0x7D, 0x30,
+0xA7, 0x40, 0xA7, 0x0B, 0x69, 0xE9, 0xA8, 0x8B, 0x69, 0xE9, 0x83, 0x52, 0x03, 0x53, 0x13, 0x1F,
+0x73, 0x29, 0x19, 0x51, 0x74, 0xE9, 0x19, 0x10, 0x05, 0x30, 0xA7, 0x40, 0xA7, 0x0B, 0x76, 0x29,
+0x83, 0x52, 0x03, 0x53, 0x13, 0x1F, 0x7E, 0x69, 0x99, 0xD5, 0x7F, 0xA9, 0x99, 0x94, 0x15, 0x70,
+0xA8, 0xC0, 0xC6, 0xB0, 0xA7, 0x40, 0xA7, 0x0B, 0x83, 0xA9, 0xA8, 0x8B, 0x83, 0xA9, 0x00, 0x00,
+0x0D, 0x70, 0x83, 0x96, 0x03, 0x53, 0xA0, 0x80, 0x00, 0xB0, 0x83, 0x52, 0x13, 0xDB, 0x01, 0xF0,
+0x83, 0x96, 0xA1, 0xC0, 0x00, 0xB0, 0xD3, 0xE1, 0x8A, 0x51, 0x93, 0x1B, 0x95, 0xE9, 0x99, 0x92,
+0x19, 0x52, 0x83, 0x96, 0x00, 0xB0, 0xA0, 0xC1, 0xA0, 0x0A, 0x83, 0x52, 0x13, 0xDB, 0x01, 0xF0,
+0x83, 0x96, 0xA1, 0xC0, 0x00, 0xB0, 0xD3, 0xE1, 0x8A, 0x51, 0x13, 0x1F, 0xA9, 0xE9, 0x99, 0x91,
+0xAA, 0xE9, 0x99, 0x50, 0xE4, 0xB0, 0xA7, 0x40, 0xAD, 0x29, 0xAE, 0x29, 0xA7, 0x0B, 0xAC, 0xE9,
+0xB1, 0xE9, 0x00, 0x00, 0x83, 0x52, 0x03, 0x53, 0x13, 0x1F, 0xB8, 0xE9, 0x19, 0x95, 0xB9, 0x29,
+0x19, 0x54, 0x03, 0x30, 0x83, 0x96, 0xA0, 0x80, 0x00, 0xB0, 0x83, 0x52, 0x13, 0xDB, 0x01, 0xF0,
+0x83, 0x96, 0xA1, 0xC0, 0x00, 0xB0, 0xD3, 0xE1, 0x8A, 0x51, 0x05, 0x30, 0xA8, 0xC0, 0x26, 0x70,
+0xA7, 0x40, 0xA7, 0x0B, 0xC9, 0xE9, 0xA8, 0x8B, 0xC9, 0xE9, 0x00, 0x00, 0x83, 0x52, 0x03, 0x53,
+0x18, 0x56, 0x9E, 0x81, 0x08, 0x40, 0x83, 0x52, 0xA6, 0x00, 0x83, 0x96, 0x21, 0xC8, 0x03, 0x59,
+0xE0, 0xA9, 0x20, 0x88, 0x83, 0x52, 0x88, 0x80, 0x26, 0x08, 0x80, 0x38, 0x86, 0xC0, 0x08, 0x40,
+0x20, 0x88, 0x83, 0x52, 0x88, 0x80, 0x26, 0x08, 0x80, 0x38, 0x85, 0xC0, 0x08, 0x40, 0x95, 0x41,
+0x96, 0x41, 0x97, 0x81, 0x98, 0x01, 0x99, 0x41, 0x9A, 0x41, 0x9B, 0x81, 0x9C, 0x41, 0x9E, 0x81,
+0x19, 0x54, 0x19, 0x95, 0x18, 0x56, 0x8B, 0x41, 0xD4, 0x41, 0x02, 0xF0, 0x54, 0x02, 0x03, 0x18,
+0x19, 0xAA, 0x54, 0x08, 0x51, 0x3E, 0x84, 0x80, 0x83, 0x93, 0x80, 0x81, 0x54, 0x08, 0x5D, 0xBE,
+0x84, 0x80, 0x07, 0x70, 0x80, 0x40, 0x54, 0x08, 0x32, 0x62, 0x8A, 0x51, 0x54, 0x08, 0x5D, 0xBE,
+0x84, 0x80, 0x00, 0xCE, 0xF0, 0x39, 0x96, 0x00, 0x54, 0x9C, 0x12, 0x6A, 0x83, 0x52, 0x03, 0x53,
+0x18, 0x14, 0x15, 0xAA, 0x83, 0x52, 0x03, 0x53, 0x18, 0xD0, 0x18, 0x55, 0x18, 0x11, 0xD4, 0x8A,
+0xF5, 0x69, 0xD3, 0x81, 0x08, 0xF0, 0x53, 0x42, 0x03, 0x18, 0x2F, 0x2A, 0x53, 0x48, 0x55, 0x7E,
+0x84, 0x80, 0x04, 0xF0, 0x83, 0x93, 0x80, 0x40, 0x53, 0x48, 0x55, 0x7E, 0x84, 0x80, 0x00, 0x48,
+0x96, 0x00, 0x53, 0x48, 0x95, 0x00, 0x98, 0x54, 0x98, 0x10, 0xD3, 0xCA, 0x1A, 0xAA, 0x10, 0xF0,
+0x9B, 0x40, 0x08, 0x40, 0xB5, 0x40, 0x51, 0x3E, 0x84, 0x80, 0x00, 0x48, 0xB4, 0x00, 0x23, 0x3E,
+0x84, 0x80, 0x8A, 0x51, 0x1D, 0xE5, 0x8A, 0x51, 0xB1, 0x00, 0x05, 0x30, 0x03, 0xD0, 0xB1, 0xCD,
+0xFF, 0x7E, 0x03, 0x9D, 0x3E, 0x2A, 0x34, 0x08, 0x2D, 0x7E, 0x84, 0x80, 0x8A, 0x51, 0x1D, 0xE5,
+0x8A, 0x51, 0xB2, 0x00, 0x32, 0x87, 0x31, 0x04, 0x01, 0x38, 0xB3, 0x40, 0x35, 0x48, 0x22, 0xFE,
+0x84, 0x80, 0x33, 0x48, 0x80, 0x40, 0x35, 0x48, 0x22, 0xFE, 0x84, 0x80, 0x00, 0x48, 0x83, 0x96,
+0xA2, 0xC0, 0x83, 0x52, 0x35, 0x48, 0x83, 0x96, 0xA3, 0x00, 0x0C, 0x30, 0x92, 0xEB, 0x18, 0x12,
+0x80, 0xF0, 0x9B, 0x40, 0x10, 0xF0, 0x9E, 0x40, 0x13, 0x1F, 0x6A, 0xEA, 0x83, 0x52, 0x03, 0x53,
+0x18, 0x14, 0x6D, 0x2A, 0x83, 0x52, 0x03, 0x53, 0x18, 0xD0, 0x92, 0x1F, 0x73, 0x2A, 0x83, 0x96,
+0x03, 0x53, 0xA4, 0x54, 0x76, 0x2A, 0x83, 0x96, 0x03, 0x53, 0xA4, 0x10, 0x83, 0x52, 0x12, 0xDF,
+0x7D, 0x6A, 0x83, 0x96, 0x03, 0x53, 0x24, 0x14, 0x80, 0x2A, 0x83, 0x96, 0x03, 0x53, 0x24, 0xD0,
+0x83, 0x52, 0x5F, 0xC8, 0xB1, 0x00, 0x06, 0x30, 0x03, 0xD0, 0xB1, 0xCD, 0xFF, 0x7E, 0x03, 0x9D,
+0x84, 0x6A, 0x12, 0xC8, 0x3F, 0xB9, 0x31, 0x04, 0xA5, 0x00, 0x83, 0x96, 0x24, 0x5C, 0x92, 0xAA,
+0x83, 0x52, 0x9E, 0x15, 0x83, 0x52, 0x13, 0x08, 0x0F, 0x39, 0x3F, 0xFE, 0x84, 0x80, 0x83, 0x93,
+0x00, 0x48, 0xA1, 0xC0, 0x21, 0xC8, 0xB3, 0x40, 0x3F, 0x30, 0xB3, 0xC5, 0x33, 0x48, 0x83, 0x96,
+0xA2, 0xC0, 0x00, 0xB0, 0x83, 0x52, 0x13, 0xDB, 0x01, 0xF0, 0x83, 0x96, 0xA3, 0x00, 0x08, 0xF0,
+0x92, 0xA3, 0x8A, 0x51, 0x21, 0xDF, 0xB0, 0xAA, 0x83, 0x52, 0x03, 0x53, 0x19, 0x96, 0xB3, 0x2A,
+0x83, 0x52, 0x03, 0x53, 0x19, 0x52, 0xA1, 0x1F, 0xB9, 0x2A, 0x83, 0x52, 0x03, 0x53, 0x99, 0xD6,
+0xBC, 0x2A, 0x83, 0x52, 0x03, 0x53, 0x99, 0x92, 0x83, 0x96, 0xA4, 0x9C, 0xCC, 0xEA, 0x6C, 0xB0,
+0xA2, 0xC0, 0x00, 0xB0, 0x83, 0x52, 0x13, 0xDB, 0x01, 0xF0, 0x83, 0x96, 0xA3, 0x00, 0x0A, 0x30,
+0x92, 0xA3, 0x8A, 0x51, 0x04, 0xF0, 0xD8, 0xEA, 0x60, 0x30, 0xA2, 0xC0, 0x00, 0xB0, 0x83, 0x52,
+0x13, 0xDB, 0x01, 0xF0, 0x83, 0x96, 0xA3, 0x00, 0x0A, 0x30, 0x92, 0xA3, 0x8A, 0x51, 0x05, 0x30,
+0x83, 0x96, 0xA2, 0xC0, 0x00, 0xB0, 0x83, 0x52, 0x13, 0xDB, 0x01, 0xF0, 0x83, 0x96, 0xA3, 0x00,
+0x0B, 0x70, 0x92, 0xA3, 0x8A, 0x51, 0x25, 0x08, 0x83, 0x96, 0xA2, 0xC0, 0x00, 0xB0, 0x83, 0x52,
+0x13, 0xDB, 0x01, 0xF0, 0x83, 0x96, 0xA3, 0x00, 0x04, 0xF0, 0x92, 0xA3, 0x8A, 0x51, 0x11, 0xC8,
+0x83, 0x96, 0xA2, 0xC0, 0x00, 0xB0, 0x83, 0x52, 0x13, 0xDB, 0x01, 0xF0, 0x83, 0x96, 0xA3, 0x00,
+0x05, 0x30, 0x92, 0xA3, 0x8A, 0x51, 0x10, 0x88, 0x83, 0x96, 0xA2, 0xC0, 0x00, 0xB0, 0x83, 0x52,
+0x13, 0xDB, 0x01, 0xF0, 0x83, 0x96, 0xA3, 0x00, 0x06, 0x30, 0x92, 0xA3, 0x8A, 0x51, 0x83, 0x96,
+0x00, 0xB0, 0xA2, 0x01, 0xA2, 0x4A, 0x83, 0x52, 0x13, 0xDB, 0x01, 0xF0, 0x83, 0x96, 0xA3, 0x00,
+0x00, 0xB0, 0x92, 0xA3, 0x8A, 0x51, 0x35, 0xB0, 0xB1, 0x00, 0xB1, 0xCB, 0x15, 0xEB, 0x05, 0x30,
+0x83, 0x96, 0x03, 0x53, 0xA2, 0xC0, 0x00, 0xB0, 0x83, 0x52, 0x13, 0xDB, 0x01, 0xF0, 0x83, 0x96,
+0xA3, 0x00, 0x00, 0xB0, 0x92, 0xA3, 0x8A, 0x51, 0x03, 0x30, 0xB2, 0x00, 0x7D, 0x30, 0xB1, 0x00,
+0xB1, 0xCB, 0x28, 0xAB, 0xB2, 0xCB, 0x28, 0xAB, 0x83, 0x52, 0x03, 0x53, 0x13, 0x1F, 0x32, 0xEB,
+0x19, 0x51, 0x33, 0x2B, 0x19, 0x10, 0x05, 0x30, 0xB1, 0x00, 0xB1, 0xCB, 0x35, 0x2B, 0x83, 0x52,
+0x03, 0x53, 0x13, 0x1F, 0x3D, 0x6B, 0x99, 0xD5, 0x3E, 0x6B, 0x99, 0x94, 0x15, 0x70, 0xB2, 0x00,
+0xC6, 0xB0, 0xB1, 0x00, 0xB1, 0xCB, 0x42, 0xAB, 0xB2, 0xCB, 0x42, 0xAB, 0x00, 0x00, 0x0D, 0x70,
+0x83, 0x96, 0x03, 0x53, 0xA2, 0xC0, 0x00, 0xB0, 0x83, 0x52, 0x13, 0xDB, 0x01, 0xF0, 0x83, 0x96,
+0xA3, 0x00, 0x00, 0xB0, 0x92, 0xA3, 0x8A, 0x51, 0x93, 0x1B, 0x54, 0xEB, 0x99, 0x92, 0x19, 0x52,
+0x83, 0x96, 0x00, 0xB0, 0xA2, 0x01, 0xA2, 0x4A, 0x83, 0x52, 0x13, 0xDB, 0x01, 0xF0, 0x83, 0x96,
+0xA3, 0x00, 0x00, 0xB0, 0x92, 0xA3, 0x8A, 0x51, 0x13, 0x1F, 0x68, 0xEB, 0x99, 0x91, 0x69, 0x2B,
+0x99, 0x50, 0xE4, 0xB0, 0xB1, 0x00, 0x6C, 0x2B, 0x6D, 0x6B, 0xB1, 0xCB, 0x6B, 0x6B, 0x70, 0xEB,
+0x00, 0x00, 0x83, 0x52, 0x03, 0x53, 0x13, 0x1F, 0x77, 0xAB, 0x19, 0x95, 0x78, 0x2B, 0x19, 0x54,
+0x03, 0x30, 0x83, 0x96, 0xA2, 0xC0, 0x00, 0xB0, 0x83, 0x52, 0x13, 0xDB, 0x01, 0xF0, 0x83, 0x96,
+0xA3, 0x00, 0x00, 0xB0, 0x92, 0xA3, 0x8A, 0x51, 0x05, 0x30, 0xB2, 0x00, 0x26, 0x70, 0xB1, 0x00,
+0xB1, 0xCB, 0x88, 0xAB, 0xB2, 0xCB, 0x88, 0xAB, 0x00, 0x00, 0x83, 0x52, 0x03, 0x53, 0x18, 0x56,
+0x9E, 0x81, 0x08, 0x40, 0x83, 0x52, 0xB0, 0xC0, 0x83, 0x96, 0x23, 0x08, 0x03, 0x59, 0x9F, 0xAB,
+0x22, 0xC8, 0x83, 0x52, 0x88, 0x80, 0x30, 0xC8, 0x80, 0x38, 0x86, 0xC0, 0x08, 0x40, 0x22, 0xC8,
+0x83, 0x52, 0x88, 0x80, 0x30, 0xC8, 0x80, 0x38, 0x85, 0xC0, 0x08, 0x40, 0x11, 0x30, 0xBE, 0x80,
+0x04, 0xF0, 0xA0, 0x80, 0x8A, 0x51, 0xE7, 0x21, 0x8A, 0x51, 0xBD, 0xC1, 0x10, 0xF0, 0x3D, 0x82,
+0x03, 0x18, 0xDD, 0xAB, 0x3E, 0x88, 0x10, 0x7A, 0x03, 0x59, 0xBB, 0xAB, 0x8A, 0x51, 0x8A, 0xA5,
+0x8A, 0x51, 0xBE, 0x80, 0xB2, 0x2B, 0x3D, 0x88, 0x20, 0x38, 0x9B, 0x40, 0x3E, 0x88, 0x10, 0x7A,
+0x03, 0x9D, 0xC7, 0x6B, 0x8A, 0x51, 0x8A, 0xA5, 0x8A, 0x51, 0xBE, 0x80, 0xBE, 0xAB, 0x3E, 0x88,
+0x11, 0xBA, 0x03, 0x9D, 0xCE, 0x6B, 0x30, 0x30, 0x9B, 0x40, 0xDD, 0xAB, 0x3D, 0x88, 0x3F, 0xFE,
+0x84, 0x80, 0x3E, 0x88, 0x83, 0x93, 0x80, 0x40, 0x3D, 0x88, 0x30, 0x78, 0x9B, 0x40, 0xBD, 0x0A,
+0xAE, 0x6B, 0x8A, 0x51, 0x8A, 0xA5, 0x8A, 0x51, 0xBE, 0x80, 0x3E, 0x88, 0x10, 0x7A, 0x03, 0x9D,
+0xD9, 0x6B, 0x20, 0xF0, 0x9B, 0x40, 0x3E, 0x88, 0x10, 0x7A, 0x03, 0x9D, 0xEC, 0x6B, 0x8A, 0x51,
+0x8A, 0xA5, 0x8A, 0x51, 0xBE, 0x80, 0xE3, 0x6B, 0x3E, 0x88, 0xDF, 0xC0, 0x5F, 0xC8, 0x30, 0x78,
+0x9B, 0x40, 0x3E, 0x88, 0x10, 0x7A, 0x03, 0x59, 0xFA, 0xAB, 0x8A, 0x51, 0x8A, 0xA5, 0x8A, 0x51,
+0xBE, 0x80, 0xF1, 0x6B, 0x20, 0xF0, 0x9B, 0x40, 0x3E, 0x88, 0x10, 0x7A, 0x03, 0x9D, 0x05, 0x6C,
+0x8A, 0x51, 0x8A, 0xA5, 0x8A, 0x51, 0xBE, 0x80, 0xFC, 0xAB, 0x3E, 0x88, 0xBC, 0x40, 0x30, 0x78,
+0x9B, 0x40, 0x3E, 0x88, 0x10, 0x7A, 0x03, 0x59, 0x12, 0x6C, 0x8A, 0x51, 0x8A, 0xA5, 0x8A, 0x51,
+0xBE, 0x80, 0x09, 0x6C, 0x40, 0xF0, 0x9B, 0x40, 0x93, 0x5F, 0x1B, 0xEC, 0x8A, 0x51, 0x5F, 0x22,
+0x8A, 0x51, 0x40, 0xF0, 0x9B, 0x40, 0xD3, 0x81, 0x08, 0xF0, 0x53, 0x42, 0x03, 0x18, 0x53, 0xEC,
+0x53, 0x48, 0x95, 0x00, 0x85, 0x70, 0x0D, 0x02, 0x03, 0x5C, 0x33, 0xEC, 0x53, 0x48, 0x55, 0x7E,
+0x84, 0x80, 0x0E, 0x70, 0x83, 0x93, 0x00, 0x42, 0x03, 0x18, 0x33, 0xEC, 0x53, 0x48, 0x55, 0x7E,
+0x84, 0x80, 0x00, 0x8A, 0x44, 0x6C, 0x34, 0x70, 0x0D, 0x02, 0x03, 0x18, 0x51, 0xAC, 0x53, 0x48,
+0x55, 0x7E, 0x84, 0x80, 0x05, 0x30, 0x83, 0x93, 0x00, 0x42, 0x03, 0x5C, 0x51, 0xAC, 0x53, 0x48,
+0x55, 0x7E, 0x84, 0x80, 0x00, 0x48, 0xFF, 0x7E, 0xB6, 0x40, 0x53, 0x48, 0x55, 0x7E, 0x84, 0x80,
+0x36, 0x48, 0x80, 0x40, 0x53, 0x48, 0x55, 0x7E, 0x84, 0x80, 0x00, 0x48, 0x96, 0x00, 0x98, 0x54,
+0x98, 0x10, 0xD3, 0xCA, 0x1C, 0xAC, 0xD4, 0x41, 0x02, 0xF0, 0x54, 0x02, 0x03, 0x18, 0xC7, 0x2C,
+0x54, 0x9C, 0x5E, 0x2C, 0x83, 0x52, 0x03, 0x53, 0x18, 0x14, 0x61, 0xAC, 0x83, 0x52, 0x03, 0x53,
+0x18, 0xD0, 0x54, 0x08, 0x51, 0x3E, 0x84, 0x80, 0x83, 0x93, 0x00, 0x48, 0x03, 0x9D, 0x6A, 0xEC,
+0x64, 0x70, 0x6B, 0x2C, 0x73, 0xF0, 0xB7, 0x80, 0x0E, 0x08, 0x37, 0x82, 0x03, 0x18, 0x7C, 0x2C,
+0x54, 0x08, 0x5D, 0xBE, 0x84, 0x80, 0x0B, 0x70, 0x00, 0x42, 0x03, 0x18, 0x7C, 0x2C, 0x54, 0x08,
+0x5D, 0xBE, 0x84, 0x80, 0x00, 0x8A, 0x8C, 0xAC, 0x2D, 0xB0, 0x0E, 0x02, 0x03, 0x18, 0x9A, 0xEC,
+0x54, 0x08, 0x5D, 0xBE, 0x84, 0x80, 0x08, 0xF0, 0x00, 0x42, 0x03, 0x5C, 0x9A, 0xEC, 0x54, 0x08,
+0x5D, 0xBE, 0x84, 0x80, 0x00, 0x48, 0xFF, 0x7E, 0xB6, 0x40, 0x54, 0x08, 0x5D, 0xBE, 0x84, 0x80,
+0x36, 0x48, 0x80, 0x40, 0x54, 0x08, 0x5D, 0xBE, 0x84, 0x80, 0x00, 0xCE, 0xF0, 0x39, 0x96, 0x00,
+0x18, 0x55, 0x18, 0x11, 0x24, 0x30, 0x0F, 0x42, 0x03, 0x5C, 0xAA, 0xEC, 0x54, 0x08, 0x51, 0x3E,
+0x84, 0x80, 0x09, 0x30, 0x00, 0x42, 0x03, 0x18, 0xAA, 0xEC, 0x54, 0x08, 0x51, 0x3E, 0x84, 0x80,
+0x00, 0x8A, 0xB9, 0x2C, 0x10, 0xF0, 0x0F, 0x42, 0x03, 0x18, 0xC5, 0xEC, 0x54, 0x08, 0x51, 0x3E,
+0x84, 0x80, 0x80, 0x88, 0x03, 0x59, 0xC5, 0xEC, 0x54, 0x08, 0x51, 0x3E, 0x84, 0x80, 0x00, 0x48,
+0xFF, 0x7E, 0xB6, 0x40, 0x54, 0x08, 0x51, 0x3E, 0x84, 0x80, 0x36, 0x48, 0x80, 0x40, 0x54, 0x08,
+0x8A, 0x51, 0x32, 0x62, 0x8A, 0x51, 0x98, 0x95, 0x98, 0x51, 0xD4, 0x8A, 0x54, 0xAC, 0x51, 0x08,
+0x19, 0x3E, 0x84, 0x80, 0x8A, 0x51, 0x1D, 0xE5, 0x8A, 0x51, 0xBA, 0x40, 0x06, 0x30, 0xE0, 0xC0,
+0x5D, 0x88, 0xF9, 0xFE, 0x8A, 0x51, 0x99, 0xE5, 0x8A, 0x51, 0xBB, 0x80, 0x3A, 0xC7, 0x97, 0x40,
+0x52, 0x08, 0x19, 0x3E, 0x84, 0x80, 0x8A, 0x51, 0x1D, 0xE5, 0x8A, 0x51, 0xBA, 0x40, 0x06, 0x30,
+0xE0, 0xC0, 0x5E, 0x88, 0xF9, 0xFE, 0x8A, 0x51, 0x99, 0xE5, 0x8A, 0x51, 0xBB, 0x80, 0x3A, 0xC7,
+0x9C, 0x00, 0x3C, 0x48, 0x95, 0x00, 0x0D, 0x08, 0xB6, 0x40, 0x03, 0xD0, 0xB6, 0xCC, 0x03, 0xD0,
+0xB6, 0xCC, 0x03, 0xD0, 0xB6, 0xCC, 0x36, 0x48, 0x01, 0xBE, 0x84, 0x80, 0x8A, 0x51, 0x1D, 0xE5,
+0x8A, 0x51, 0xB9, 0x40, 0x06, 0x30, 0xE0, 0xC0, 0x3C, 0x48, 0x55, 0x7E, 0x84, 0x80, 0x83, 0x93,
+0x00, 0x48, 0xFC, 0xFE, 0x8A, 0x51, 0x99, 0xE5, 0x8A, 0x51, 0xB8, 0x00, 0x01, 0xF0, 0xB6, 0x40,
+0x3C, 0x8A, 0x0C, 0xAD, 0x03, 0xD0, 0xB6, 0x0D, 0xFF, 0x7E, 0x03, 0x9D, 0x0A, 0xAD, 0x36, 0x48,
+0x14, 0x05, 0x03, 0x59, 0x16, 0xED, 0x38, 0x08, 0x1C, 0x87, 0x18, 0xAD, 0x38, 0x08, 0x17, 0xC7,
+0xB6, 0x40, 0x39, 0x48, 0x36, 0xC7, 0x9A, 0x00, 0x14, 0x6C, 0x05, 0x30, 0x8A, 0xC0, 0x04, 0x88,
+0x84, 0x0A, 0x82, 0x47, 0x00, 0xF4, 0x00, 0xF4, 0x00, 0xF4, 0x00, 0xF4, 0x00, 0xF4, 0x00, 0xF4,
+0x02, 0x34, 0x04, 0x34, 0x05, 0x74, 0x06, 0x74, 0x07, 0xB4, 0x08, 0x34, 0x09, 0x74, 0x0A, 0x74,
+0x0A, 0x74, 0x0B, 0xB4, 0x0B, 0xB4, 0x0C, 0x74, 0x0D, 0xB4, 0x0D, 0xB4, 0x0E, 0xB4, 0x0E, 0xB4,
+0x0E, 0xB4, 0x0F, 0xF4, 0x0F, 0xF4, 0x00, 0xF4, 0x06, 0x74, 0x0C, 0x74, 0x12, 0x74, 0x18, 0x74,
+0x1E, 0xF4, 0x24, 0x74, 0x2A, 0xB4, 0x30, 0x74, 0x36, 0xF4, 0x01, 0x34, 0x01, 0x34, 0x01, 0x34,
+0x02, 0x34, 0x03, 0x74, 0x04, 0x34, 0x05, 0x74, 0x05, 0x74, 0x06, 0x74, 0x06, 0x74, 0x0F, 0xF4,
+0x0C, 0x74, 0x09, 0x74, 0x09, 0x74, 0x09, 0x74, 0x0C, 0x74, 0x0F, 0xF4, 0x0C, 0x74, 0x0F, 0xF4,
+0x0C, 0x74, 0x83, 0x93, 0x51, 0x70, 0x84, 0x80, 0x60, 0x30, 0x8A, 0x51, 0xA7, 0x25, 0x8A, 0x51,
+0x83, 0x96, 0xA4, 0x01, 0x83, 0x52, 0x38, 0x70, 0xBF, 0xC0, 0x3A, 0xB0, 0xC0, 0x80, 0x3C, 0xB0,
+0xC1, 0xC0, 0x78, 0xB0, 0xC2, 0xC0, 0x7A, 0xF0, 0xC3, 0x00, 0x7C, 0xF0, 0xC4, 0xC0, 0x7D, 0x30,
+0xC5, 0x00, 0x7F, 0x70, 0xC6, 0x00, 0xB9, 0xF0, 0xC7, 0x40, 0xBA, 0xF0, 0xC8, 0xC0, 0xBB, 0x30,
+0xC9, 0x00, 0xFA, 0x30, 0xCA, 0x00, 0xFB, 0x70, 0xCB, 0x40, 0xFC, 0x30, 0xCC, 0x00, 0xFD, 0x70,
+0xCD, 0x40, 0xFF, 0xB0, 0xCE, 0x40, 0x00, 0xB0, 0xCF, 0x80, 0x01, 0xF0, 0xD0, 0xC0, 0x83, 0x01,
+0x8A, 0x51, 0xA6, 0x2B, 0x50, 0xC8, 0xB1, 0x00, 0x4F, 0x88, 0xB0, 0xC0, 0x31, 0x08, 0x30, 0x06,
+0x03, 0x59, 0x97, 0x6D, 0x14, 0xC8, 0xB0, 0xC0, 0x14, 0xC8, 0xB1, 0x00, 0x8E, 0x2D, 0x31, 0x08,
+0x08, 0x40, 0xB1, 0x00, 0xB0, 0x01, 0x60, 0xC8, 0x31, 0x58, 0xB0, 0x87, 0x03, 0xD0, 0xE0, 0x8D,
+0x03, 0xD0, 0xB1, 0x8C, 0xB1, 0x48, 0x03, 0x9D, 0x9B, 0x6D, 0x30, 0xC8, 0x08, 0x40, 0x64, 0xC0,
+0x80, 0x81, 0x84, 0x0A, 0x04, 0xC6, 0x03, 0x59, 0x00, 0xF4, 0x04, 0xC6, 0xA8, 0xED, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 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/arb_fw.var b/libloragw/src/arb_fw.var
new file mode 100644
index 0000000..7f06906
--- /dev/null
+++ b/libloragw/src/arb_fw.var
@@ -0,0 +1,529 @@
+/*
+ / _____) _ | |
+( (____ _____ ____ _| |_ _____ ____| |__
+ \____ \| ___ | (_ _) ___ |/ ___) _ \
+ _____) ) ____| | | || |_| ____( (___| | | |
+(______/|_____)_|_|_| \__)_____)\____)_| |_|
+ (C)2013 Semtech-Cycleo
+
+Description:
+ Arbiter firmware
+
+License: Revised BSD License, see LICENSE.TXT file include in the project
+Maintainer: Matthieu Leurent
+*/
+
+static uint8_t arb_firmware[MCU_ARB_FW_BYTE] = {
+0x8A, 0x51, 0xAE, 0x6E, 0x00, 0xB0, 0x8A, 0xC0, 0x04, 0x88, 0x84, 0x0A, 0x82, 0x47, 0x00, 0xF4,
+0x07, 0xB4, 0x06, 0x74, 0x05, 0x74, 0x04, 0x34, 0x03, 0x74, 0x02, 0x34, 0x01, 0x34, 0x00, 0xF4,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 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, 0xA7, 0x6E, 0xD9, 0x81, 0x83, 0x93,
+0x22, 0x30, 0x84, 0x80, 0x59, 0xB0, 0x8A, 0x51, 0xA6, 0xE6, 0x83, 0x01, 0x8A, 0x51, 0xB8, 0x2E,
+0x01, 0xF0, 0xA0, 0x80, 0x8A, 0x51, 0x01, 0x67, 0x8A, 0x51, 0x0D, 0x58, 0xD4, 0x2E, 0xD3, 0x81,
+0x59, 0x94, 0x53, 0x48, 0x8A, 0x51, 0xDB, 0x66, 0x8A, 0x51, 0x83, 0x52, 0x03, 0x53, 0xD9, 0x1C,
+0xCC, 0x2E, 0x8A, 0x51, 0x85, 0xE7, 0x8A, 0x51, 0x0D, 0x58, 0xBD, 0xAE, 0x08, 0xF0, 0xD3, 0xCA,
+0x53, 0x42, 0x03, 0x18, 0xBD, 0xAE, 0xC0, 0xAE, 0x59, 0xDC, 0xBD, 0xAE, 0x8A, 0x51, 0x32, 0xE7,
+0x8A, 0x51, 0x59, 0x50, 0xBD, 0xAE, 0xDB, 0x80, 0xD9, 0x90, 0x5B, 0x88, 0x96, 0x00, 0x15, 0x70,
+0xDA, 0x40, 0xDA, 0x0B, 0xE1, 0x2E, 0x83, 0x52, 0x03, 0x53, 0x8D, 0xDC, 0x08, 0x40, 0xD9, 0xD4,
+0x15, 0x54, 0x15, 0x70, 0xDA, 0x40, 0xDA, 0x0B, 0xEB, 0xAE, 0x83, 0x52, 0x03, 0x53, 0x0D, 0xDD,
+0xED, 0xAE, 0x10, 0x88, 0xD4, 0x00, 0x11, 0xC8, 0xD5, 0x40, 0x0F, 0x48, 0xD7, 0x80, 0x0E, 0x08,
+0xD2, 0x00, 0x12, 0xC8, 0xD6, 0x40, 0x15, 0x10, 0x15, 0x70, 0xDA, 0x40, 0xDA, 0x0B, 0xFE, 0xEE,
+0x08, 0x40, 0x95, 0x41, 0x96, 0x41, 0x97, 0x81, 0x98, 0x01, 0x99, 0x41, 0x9A, 0x41, 0x9B, 0x81,
+0x9C, 0x41, 0x9E, 0x81, 0xD8, 0x41, 0x08, 0xF0, 0x58, 0x02, 0x03, 0x18, 0x29, 0x2F, 0x58, 0x08,
+0x32, 0x3E, 0x84, 0x80, 0x80, 0x81, 0x58, 0x08, 0x3A, 0x7E, 0x84, 0x80, 0x80, 0x81, 0x58, 0x08,
+0x4A, 0x3E, 0x84, 0x80, 0x80, 0x81, 0x58, 0x08, 0x22, 0xFE, 0x84, 0x80, 0x80, 0x81, 0x58, 0x08,
+0x42, 0xFE, 0x84, 0x80, 0x80, 0x81, 0x58, 0x08, 0x2A, 0x3E, 0x84, 0x80, 0x80, 0x81, 0xD8, 0x8A,
+0x0B, 0x2F, 0xD4, 0x41, 0xD5, 0x81, 0xD7, 0xC1, 0xD2, 0x41, 0xD6, 0x81, 0x59, 0x50, 0xD9, 0x90,
+0xA1, 0x01, 0x08, 0x40, 0xD8, 0x41, 0x08, 0xF0, 0x58, 0x02, 0x03, 0x18, 0x08, 0x40, 0x58, 0x08,
+0x2A, 0x3E, 0x84, 0x80, 0x83, 0x93, 0x00, 0xCB, 0x83, 0x2F, 0x58, 0x08, 0x2A, 0x3E, 0x84, 0x80,
+0x80, 0x81, 0x58, 0x08, 0x42, 0xFE, 0x84, 0x80, 0x00, 0x48, 0x97, 0x40, 0x58, 0x08, 0x22, 0xFE,
+0x84, 0x80, 0x00, 0x48, 0x98, 0xC0, 0x58, 0x08, 0x32, 0x3E, 0x84, 0x80, 0x00, 0x48, 0x99, 0x00,
+0x58, 0x08, 0x3A, 0x7E, 0x84, 0x80, 0x00, 0x48, 0x9A, 0x00, 0x58, 0x08, 0x01, 0xBE, 0x84, 0x80,
+0x8A, 0x51, 0x02, 0xA0, 0x8A, 0x51, 0x9B, 0x40, 0x58, 0x08, 0x4A, 0x3E, 0x84, 0x80, 0x00, 0x48,
+0x9C, 0x00, 0x95, 0x94, 0x15, 0x70, 0xDA, 0x40, 0xDA, 0x0B, 0x64, 0x2F, 0x83, 0x52, 0x03, 0x53,
+0x95, 0x50, 0x15, 0x70, 0xDA, 0x40, 0xDA, 0x0B, 0x6B, 0xAF, 0x01, 0xF0, 0x83, 0x52, 0x03, 0x53,
+0xDA, 0x40, 0x58, 0x08, 0x01, 0xBE, 0x84, 0x80, 0x8A, 0x51, 0x02, 0xA0, 0x8A, 0x51, 0x01, 0xBE,
+0x7B, 0xEF, 0x03, 0xD0, 0xDA, 0x0D, 0xFF, 0x7E, 0x03, 0x9D, 0x79, 0xAF, 0x5A, 0x48, 0x13, 0x45,
+0x03, 0x59, 0x6D, 0xAF, 0xA1, 0x4A, 0xD8, 0x8A, 0x33, 0x6F, 0x59, 0x91, 0xD8, 0x41, 0x08, 0xF0,
+0x58, 0x02, 0x03, 0x18, 0xBD, 0xEF, 0x0A, 0x30, 0x57, 0x82, 0x03, 0x18, 0x91, 0x2F, 0x59, 0xD5,
+0xB9, 0xAF, 0x01, 0xF0, 0xDA, 0x40, 0x58, 0x08, 0x01, 0xBE, 0x84, 0x80, 0x8A, 0x51, 0x02, 0xA0,
+0x8A, 0x51, 0x01, 0xBE, 0x9D, 0xAF, 0x03, 0xD0, 0xDA, 0x0D, 0xFF, 0x7E, 0x03, 0x9D, 0x9B, 0xAF,
+0x5A, 0x48, 0x13, 0x45, 0x03, 0x9D, 0xAA, 0x6F, 0x58, 0x08, 0x2A, 0x3E, 0x84, 0x80, 0x83, 0x93,
+0x00, 0xCB, 0xB9, 0xAF, 0x58, 0x08, 0x42, 0xFE, 0x84, 0x80, 0x83, 0x93, 0x00, 0x48, 0x56, 0x86,
+0x03, 0x9D, 0xB9, 0xAF, 0x58, 0x08, 0x22, 0xFE, 0x84, 0x80, 0x00, 0x48, 0x52, 0x46, 0x03, 0x59,
+0x8F, 0xAF, 0x59, 0xD9, 0xBD, 0xEF, 0xD8, 0x8A, 0x87, 0x6F, 0x59, 0xD9, 0x08, 0x40, 0xD8, 0x41,
+0x08, 0xF0, 0x58, 0x02, 0x03, 0x18, 0x08, 0x40, 0x01, 0xF0, 0xDA, 0x40, 0x58, 0x08, 0x01, 0xBE,
+0x84, 0x80, 0x8A, 0x51, 0x02, 0xA0, 0x8A, 0x51, 0x01, 0xBE, 0xD0, 0x2F, 0x03, 0xD0, 0xDA, 0x0D,
+0xFF, 0x7E, 0x03, 0x9D, 0xCE, 0xAF, 0x5A, 0x48, 0x13, 0x45, 0x03, 0x9D, 0xFE, 0x2F, 0x58, 0x08,
+0x2A, 0x3E, 0x84, 0x80, 0x83, 0x93, 0x00, 0x48, 0x03, 0x9D, 0xFE, 0x2F, 0x58, 0x08, 0x32, 0x3E,
+0x84, 0x80, 0x54, 0x08, 0x80, 0x40, 0x58, 0x08, 0x3A, 0x7E, 0x84, 0x80, 0x55, 0x48, 0x80, 0x40,
+0x58, 0x08, 0x4A, 0x3E, 0x84, 0x80, 0x57, 0x88, 0x80, 0x40, 0x58, 0x08, 0x22, 0xFE, 0x84, 0x80,
+0x52, 0x08, 0x80, 0x40, 0x58, 0x08, 0x42, 0xFE, 0x84, 0x80, 0x56, 0x48, 0x80, 0x40, 0x58, 0x08,
+0x2A, 0x3E, 0x84, 0x80, 0x01, 0xF0, 0x80, 0x40, 0x59, 0xD5, 0x08, 0x40, 0xD8, 0x8A, 0xC0, 0xEF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 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/cal_fw.var b/libloragw/src/cal_fw.var
new file mode 100644
index 0000000..8c9389e
--- /dev/null
+++ b/libloragw/src/cal_fw.var
@@ -0,0 +1,529 @@
+/*
+ / _____) _ | |
+( (____ _____ ____ _| |_ _____ ____| |__
+ \____ \| ___ | (_ _) ___ |/ ___) _ \
+ _____) ) ____| | | || |_| ____( (___| | | |
+(______/|_____)_|_|_| \__)_____)\____)_| |_|
+ (C)2013 Semtech-Cycleo
+
+Description:
+ Calibration firmware
+
+License: Revised BSD License, see LICENSE.TXT file include in the project
+Maintainer: Matthieu Leurent
+*/
+
+static uint8_t cal_firmware[MCU_AGC_FW_BYTE] = {
+0x8A, 0x51, 0x6F, 0x28, 0x00, 0xB0, 0x8A, 0xC0, 0x04, 0x88, 0x84, 0x0A, 0x82, 0x47, 0x00, 0xF4,
+0x18, 0x74, 0x1C, 0xB4, 0x1E, 0xF4, 0x20, 0x34, 0x22, 0x74, 0x23, 0xB4, 0x24, 0x74, 0x25, 0xB4,
+0x26, 0xB4, 0x27, 0xF4, 0x28, 0x74, 0x28, 0x74, 0x29, 0xB4, 0x2A, 0xB4, 0x2A, 0xB4, 0x2B, 0xF4,
+0x2B, 0xF4, 0x2C, 0xB4, 0x2C, 0xB4, 0x2D, 0xF4, 0x2D, 0xF4, 0x2D, 0xF4, 0x2E, 0xF4, 0x2E, 0xF4,
+0x2E, 0xF4, 0x2F, 0x34, 0x2F, 0x34, 0x2F, 0x34, 0x30, 0x74, 0x30, 0x74, 0x00, 0xF4, 0x00, 0xF4,
+0x06, 0x74, 0x0A, 0x74, 0x0C, 0x74, 0x0E, 0xB4, 0x10, 0x34, 0x11, 0x74, 0x12, 0x74, 0x13, 0xB4,
+0x14, 0x74, 0x15, 0xB4, 0x16, 0xB4, 0x16, 0xB4, 0x17, 0xF4, 0x18, 0x74, 0x0F, 0xF4, 0x0C, 0x74,
+0x09, 0x74, 0x09, 0x74, 0x09, 0x74, 0x0C, 0x74, 0x0F, 0xF4, 0x0C, 0x74, 0x0F, 0xF4, 0x0C, 0x74,
+0x01, 0x34, 0x01, 0x34, 0x01, 0x34, 0x02, 0x34, 0x03, 0x74, 0x04, 0x34, 0x05, 0x74, 0x05, 0x74,
+0x06, 0x74, 0x06, 0x74, 0x02, 0x34, 0x02, 0x34, 0x03, 0x74, 0x04, 0x34, 0x05, 0x74, 0x06, 0x74,
+0x07, 0xB4, 0x40, 0x34, 0x20, 0x34, 0x10, 0x34, 0x08, 0x34, 0x04, 0x34, 0x02, 0x34, 0x01, 0x34,
+0x06, 0x74, 0x06, 0x74, 0x07, 0xB4, 0x07, 0xB4, 0x07, 0xB4, 0x10, 0x34, 0x08, 0x34, 0x04, 0x34,
+0x02, 0x34, 0x01, 0x34, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0x64, 0xC0,
+0x80, 0x81, 0x84, 0x0A, 0x04, 0xC6, 0x03, 0x59, 0x00, 0xF4, 0x04, 0xC6, 0x68, 0x68, 0xE6, 0x81,
+0xE7, 0xC1, 0x83, 0x93, 0x55, 0xB0, 0x84, 0x80, 0x66, 0xB0, 0x8A, 0x51, 0x67, 0xA0, 0x8A, 0x51,
+0xDA, 0xF0, 0x84, 0x80, 0xEE, 0x30, 0x8A, 0x51, 0x67, 0xA0, 0x8A, 0x51, 0x83, 0xD7, 0xA0, 0x30,
+0x84, 0x80, 0xAE, 0xF0, 0x8A, 0x51, 0x67, 0xA0, 0x83, 0x96, 0x01, 0xF0, 0xEE, 0x80, 0x83, 0x52,
+0x01, 0xF0, 0xE8, 0x00, 0x07, 0x70, 0xE9, 0x40, 0x06, 0x30, 0xEA, 0x40, 0x0F, 0xB0, 0xEB, 0x80,
+0x01, 0xF0, 0xEC, 0x40, 0x05, 0x30, 0xED, 0x80, 0x02, 0xF0, 0xEE, 0x80, 0x0E, 0x70, 0xEF, 0xC0,
+0x83, 0x01, 0x8A, 0x51, 0x4C, 0xAC, 0xA3, 0x41, 0x23, 0x08, 0xEA, 0xBE, 0x84, 0x80, 0x23, 0x08,
+0xA1, 0xC0, 0x00, 0xB0, 0x22, 0x21, 0x8A, 0x51, 0x83, 0x93, 0x80, 0x40, 0x23, 0x08, 0xE0, 0x3E,
+0x84, 0x80, 0x23, 0x08, 0xA1, 0xC0, 0x01, 0xF0, 0x22, 0x21, 0x8A, 0x51, 0x80, 0x40, 0x23, 0x08,
+0xDE, 0xFE, 0x84, 0x80, 0x23, 0x08, 0xA1, 0xC0, 0x02, 0xF0, 0x22, 0x21, 0x8A, 0x51, 0x80, 0x40,
+0x23, 0x08, 0xDA, 0xBE, 0x84, 0x80, 0x23, 0x08, 0xA1, 0xC0, 0x03, 0x30, 0x22, 0x21, 0x8A, 0x51,
+0x80, 0x40, 0x23, 0x08, 0xE8, 0x7E, 0x84, 0x80, 0x23, 0x08, 0xA1, 0xC0, 0x04, 0xF0, 0x22, 0x21,
+0x8A, 0x51, 0x80, 0x40, 0x23, 0x08, 0xE6, 0xBE, 0x84, 0x80, 0x23, 0x08, 0xA1, 0xC0, 0x05, 0x30,
+0x22, 0x21, 0x8A, 0x51, 0x80, 0x40, 0x23, 0x08, 0xE4, 0x7E, 0x84, 0x80, 0x23, 0x08, 0xA1, 0xC0,
+0x06, 0x30, 0x22, 0x21, 0x8A, 0x51, 0x80, 0x40, 0x23, 0x08, 0xAC, 0x7E, 0x84, 0x80, 0x23, 0x08,
+0xA1, 0xC0, 0x08, 0xF0, 0x22, 0x21, 0x8A, 0x51, 0x83, 0xD7, 0x80, 0x40, 0x23, 0x08, 0xA8, 0x3E,
+0x84, 0x80, 0x23, 0x08, 0xA1, 0xC0, 0x0A, 0x30, 0x22, 0x21, 0x8A, 0x51, 0x80, 0x40, 0x23, 0x08,
+0xAA, 0x7E, 0x84, 0x80, 0x23, 0x08, 0xA1, 0xC0, 0x0B, 0x70, 0x22, 0x21, 0x8A, 0x51, 0x80, 0x40,
+0x23, 0x08, 0xA2, 0x3E, 0x84, 0x80, 0x23, 0x08, 0xA1, 0xC0, 0x0C, 0x30, 0x22, 0x21, 0x8A, 0x51,
+0x80, 0x40, 0x23, 0x08, 0xA4, 0x3E, 0x84, 0x80, 0x23, 0x08, 0xA1, 0xC0, 0x0D, 0x70, 0x22, 0x21,
+0x8A, 0x51, 0x80, 0x40, 0x23, 0x08, 0xA6, 0x7E, 0x84, 0x80, 0x23, 0x08, 0xA1, 0xC0, 0x0E, 0x70,
+0x22, 0x21, 0x8A, 0x51, 0x80, 0x40, 0x23, 0x08, 0xA0, 0xFE, 0x84, 0x80, 0x23, 0x08, 0xA1, 0xC0,
+0x10, 0xF0, 0x22, 0x21, 0x8A, 0x51, 0x80, 0x40, 0x02, 0xF0, 0xA3, 0x8A, 0x23, 0x02, 0x03, 0x18,
+0x08, 0x40, 0x9C, 0xA8, 0xA2, 0xC0, 0x21, 0xC8, 0x03, 0x59, 0x2B, 0xE9, 0x22, 0xC8, 0x86, 0xC0,
+0x22, 0xC8, 0x86, 0xC0, 0x2F, 0x29, 0x22, 0xC8, 0x85, 0xC0, 0x22, 0xC8, 0x85, 0xC0, 0x08, 0x88,
+0x08, 0x40, 0xB4, 0x00, 0x34, 0x8E, 0xF0, 0x39, 0x0C, 0x78, 0xB8, 0x00, 0xBB, 0xC1, 0x38, 0x08,
+0xA1, 0xC0, 0x02, 0xF0, 0xA2, 0xC0, 0x54, 0x70, 0xA7, 0x23, 0x8A, 0x51, 0x38, 0x08, 0x02, 0xBE,
+0xA1, 0xC0, 0x02, 0xF0, 0xA2, 0xC0, 0x54, 0x70, 0xA7, 0x23, 0x8A, 0x51, 0x02, 0xF0, 0xA1, 0xC0,
+0x54, 0x70, 0x30, 0x24, 0x8A, 0x51, 0xE5, 0x40, 0xE5, 0x9F, 0x46, 0xA9, 0x02, 0xF0, 0xA1, 0xC0,
+0x57, 0xF0, 0x30, 0x24, 0x8A, 0x51, 0xB5, 0x40, 0x02, 0xF0, 0xA1, 0xC0, 0x58, 0x70, 0x30, 0x24,
+0x8A, 0x51, 0xB6, 0x40, 0x3B, 0x88, 0x25, 0x3E, 0x84, 0x80, 0x35, 0x48, 0x44, 0x24, 0x8A, 0x51,
+0xA4, 0xC0, 0x36, 0x48, 0x44, 0x24, 0x8A, 0x51, 0x24, 0x47, 0x83, 0x93, 0x80, 0x40, 0x0F, 0xB0,
+0xBB, 0x0A, 0x3B, 0x82, 0x03, 0x5C, 0x37, 0x29, 0xBA, 0x81, 0x25, 0x08, 0xB9, 0x40, 0xB7, 0xC1,
+0xBB, 0xC1, 0x3B, 0x88, 0x25, 0x3E, 0x84, 0x80, 0x39, 0x48, 0x00, 0x42, 0x03, 0x18, 0x7F, 0xA9,
+0x3B, 0x88, 0x25, 0x3E, 0x84, 0x80, 0x00, 0x48, 0xB9, 0x40, 0x3B, 0x88, 0xB7, 0x80, 0x0F, 0xB0,
+0xBB, 0x0A, 0x3B, 0x82, 0x03, 0x5C, 0x71, 0xE9, 0x37, 0x88, 0x25, 0x3E, 0x84, 0x80, 0xFF, 0xB0,
+0x80, 0x40, 0x08, 0xF0, 0xBA, 0xCA, 0x3A, 0x42, 0x03, 0x5C, 0x6D, 0x29, 0x39, 0x48, 0x08, 0x40,
+0xB2, 0x00, 0xC9, 0x41, 0xCA, 0x41, 0xA1, 0xC0, 0x02, 0xF0, 0xA2, 0xC0, 0x53, 0xB0, 0xA7, 0x23,
+0xC7, 0x81, 0x49, 0x08, 0xB3, 0x40, 0x4A, 0x08, 0xBC, 0x40, 0x47, 0x48, 0x4A, 0x3E, 0x84, 0x80,
+0x8A, 0x51, 0x02, 0xA0, 0x8A, 0x51, 0x49, 0x87, 0xB4, 0x00, 0x47, 0x48, 0x4A, 0x3E, 0x84, 0x80,
+0x8A, 0x51, 0x02, 0xA0, 0x8A, 0x51, 0x4A, 0x87, 0xBD, 0x80, 0x47, 0x48, 0x4A, 0x3E, 0x84, 0x80,
+0x8A, 0x51, 0x02, 0xA0, 0x8A, 0x51, 0x49, 0x87, 0xB5, 0x40, 0x47, 0x48, 0x4A, 0x3E, 0x84, 0x80,
+0x8A, 0x51, 0x02, 0xA0, 0x8A, 0x51, 0x4A, 0x02, 0xBE, 0x80, 0x47, 0x48, 0x4A, 0x3E, 0x84, 0x80,
+0x8A, 0x51, 0x02, 0xA0, 0x8A, 0x51, 0x49, 0x02, 0xB6, 0x40, 0x47, 0x48, 0x4A, 0x3E, 0x84, 0x80,
+0x8A, 0x51, 0x02, 0xA0, 0x8A, 0x51, 0x4A, 0x87, 0xBF, 0xC0, 0x47, 0x48, 0x4A, 0x3E, 0x84, 0x80,
+0x8A, 0x51, 0x02, 0xA0, 0x8A, 0x51, 0x49, 0x02, 0xB7, 0x80, 0x47, 0x48, 0x4A, 0x3E, 0x84, 0x80,
+0x8A, 0x51, 0x02, 0xA0, 0x8A, 0x51, 0x4A, 0x02, 0xC0, 0x80, 0xC8, 0x01, 0x48, 0xC8, 0x33, 0x7E,
+0x84, 0x80, 0x00, 0x48, 0xA1, 0xC0, 0x27, 0xB0, 0xA2, 0x01, 0xA2, 0x4A, 0xA7, 0x23, 0x8A, 0x51,
+0x48, 0xC8, 0x3C, 0x7E, 0x84, 0x80, 0x00, 0x48, 0xA1, 0xC0, 0x28, 0x30, 0xA2, 0x01, 0xA2, 0x4A,
+0xA7, 0x23, 0x47, 0x48, 0x43, 0x3E, 0x84, 0x80, 0x8A, 0x51, 0x02, 0xA0, 0x8A, 0x51, 0xD2, 0xE3,
+0x8A, 0x51, 0xC6, 0x00, 0x48, 0xC8, 0x03, 0x59, 0x01, 0x2A, 0x45, 0x08, 0x46, 0x02, 0x03, 0x18,
+0x0D, 0xAA, 0x46, 0x08, 0xC5, 0x00, 0x48, 0xC8, 0x33, 0x7E, 0x84, 0x80, 0x00, 0x48, 0xC9, 0x00,
+0x48, 0xC8, 0x3C, 0x7E, 0x84, 0x80, 0x00, 0x48, 0xCA, 0x00, 0x05, 0x30, 0xC8, 0x4A, 0x48, 0xC2,
+0x03, 0x5C, 0xDE, 0x69, 0x07, 0x70, 0xC7, 0xCA, 0x47, 0x42, 0x49, 0x08, 0xB3, 0x40, 0x4A, 0x08,
+0xBC, 0x40, 0x03, 0x5C, 0x9D, 0x29, 0x49, 0x4A, 0xB4, 0x00, 0x4A, 0x4A, 0xBD, 0x80, 0x49, 0x4A,
+0xB5, 0x40, 0x4A, 0x43, 0xBE, 0x80, 0x49, 0x43, 0xB6, 0x40, 0x4A, 0x4A, 0xBF, 0xC0, 0x49, 0x43,
+0xB7, 0x80, 0x4A, 0x43, 0xC0, 0x80, 0x49, 0x08, 0xB8, 0x00, 0x4A, 0x4A, 0xC1, 0xC0, 0x49, 0x08,
+0xB9, 0x40, 0x4A, 0x43, 0xC2, 0xC0, 0x49, 0x4A, 0xBA, 0x40, 0x4A, 0x08, 0xC3, 0x00, 0x49, 0x43,
+0xBB, 0x80, 0x4A, 0x08, 0xC4, 0xC0, 0xC8, 0x01, 0x48, 0xC8, 0x33, 0x7E, 0x84, 0x80, 0x00, 0x48,
+0xA1, 0xC0, 0x27, 0xB0, 0xA2, 0x01, 0xA2, 0x4A, 0xA7, 0x23, 0x8A, 0x51, 0x48, 0xC8, 0x3C, 0x7E,
+0x84, 0x80, 0x00, 0x48, 0xA1, 0xC0, 0x28, 0x30, 0xA2, 0x01, 0xA2, 0x4A, 0xA7, 0x23, 0x8A, 0x51,
+0x07, 0x70, 0xD2, 0xE3, 0x8A, 0x51, 0xC6, 0x00, 0x48, 0xC8, 0x03, 0x59, 0x5B, 0x2A, 0x45, 0x08,
+0x46, 0x02, 0x03, 0x18, 0x67, 0x2A, 0x46, 0x08, 0xC5, 0x00, 0x48, 0xC8, 0x33, 0x7E, 0x84, 0x80,
+0x00, 0x48, 0xC9, 0x00, 0x48, 0xC8, 0x3C, 0x7E, 0x84, 0x80, 0x00, 0x48, 0xCA, 0x00, 0x09, 0x30,
+0xC8, 0x4A, 0x48, 0xC2, 0x03, 0x5C, 0x3C, 0xEA, 0x49, 0x08, 0xA1, 0xC0, 0x27, 0xB0, 0xA2, 0x01,
+0xA2, 0x4A, 0xA7, 0x23, 0x8A, 0x51, 0x4A, 0x08, 0xA1, 0xC0, 0x28, 0x30, 0xA2, 0x01, 0xA2, 0x4A,
+0xA7, 0x6B, 0xB3, 0x40, 0xCB, 0x81, 0xCC, 0x41, 0x33, 0xCB, 0x82, 0x6A, 0x74, 0xB0, 0xC7, 0x40,
+0x75, 0xF0, 0x85, 0xAA, 0x72, 0xB0, 0xC7, 0x40, 0x73, 0xF0, 0xC8, 0xC0, 0x33, 0x48, 0xA1, 0xC0,
+0x02, 0xF0, 0xA2, 0xC0, 0x53, 0xB0, 0xA7, 0x23, 0xCA, 0x41, 0x4B, 0x48, 0xB5, 0x40, 0x4C, 0x08,
+0xBE, 0x80, 0x4A, 0x08, 0x56, 0x7E, 0x84, 0x80, 0x8A, 0x51, 0x02, 0xA0, 0x8A, 0x51, 0x4B, 0xC7,
+0xB6, 0x40, 0x4A, 0x08, 0x56, 0x7E, 0x84, 0x80, 0x8A, 0x51, 0x02, 0xA0, 0x8A, 0x51, 0x4C, 0x87,
+0xBF, 0xC0, 0x4A, 0x08, 0x56, 0x7E, 0x84, 0x80, 0x8A, 0x51, 0x02, 0xA0, 0x8A, 0x51, 0x4B, 0xC7,
+0xB7, 0x80, 0x4A, 0x08, 0x56, 0x7E, 0x84, 0x80, 0x8A, 0x51, 0x02, 0xA0, 0x8A, 0x51, 0x4C, 0x02,
+0xC0, 0x80, 0x4A, 0x08, 0x56, 0x7E, 0x84, 0x80, 0x8A, 0x51, 0x02, 0xA0, 0x8A, 0x51, 0x4B, 0x42,
+0xB8, 0x00, 0x4A, 0x08, 0x56, 0x7E, 0x84, 0x80, 0x8A, 0x51, 0x02, 0xA0, 0x8A, 0x51, 0x4C, 0x87,
+0xC1, 0xC0, 0x4A, 0x08, 0x56, 0x7E, 0x84, 0x80, 0x8A, 0x51, 0x02, 0xA0, 0x8A, 0x51, 0x4B, 0x42,
+0xB9, 0x40, 0x4A, 0x08, 0x56, 0x7E, 0x84, 0x80, 0x8A, 0x51, 0x02, 0xA0, 0x8A, 0x51, 0x4C, 0x02,
+0xC2, 0xC0, 0xCD, 0x81, 0x1F, 0xF0, 0xA1, 0xC0, 0xE0, 0x70, 0xA2, 0xC0, 0x4D, 0x48, 0x35, 0x7E,
+0x84, 0x80, 0x00, 0x48, 0xBB, 0x63, 0x8A, 0x51, 0xB2, 0x00, 0x4D, 0x48, 0x35, 0x7E, 0x84, 0x80,
+0x32, 0x08, 0x80, 0x40, 0x1F, 0xF0, 0xA1, 0xC0, 0xE0, 0x70, 0xA2, 0xC0, 0x4D, 0x48, 0x3E, 0xBE,
+0x84, 0x80, 0x00, 0x48, 0xBB, 0x63, 0x8A, 0x51, 0xB2, 0x00, 0x4D, 0x48, 0x3E, 0xBE, 0x84, 0x80,
+0x32, 0x08, 0x80, 0x40, 0x4D, 0x48, 0x35, 0x7E, 0x84, 0x80, 0x00, 0x48, 0xA1, 0xC0, 0xA2, 0x01,
+0x47, 0x48, 0xA7, 0x23, 0x8A, 0x51, 0x4D, 0x48, 0x3E, 0xBE, 0x84, 0x80, 0x00, 0x48, 0xA1, 0xC0,
+0xA2, 0x01, 0x48, 0xC8, 0xA7, 0x23, 0x4A, 0x08, 0x51, 0x3E, 0x84, 0x80, 0x8A, 0x51, 0x02, 0xA0,
+0x8A, 0x51, 0xD2, 0xE3, 0x8A, 0x51, 0xC9, 0x00, 0x4D, 0x48, 0x03, 0x59, 0x13, 0xEB, 0x34, 0x08,
+0x49, 0x02, 0x03, 0x18, 0x1F, 0x6B, 0x49, 0x08, 0xB4, 0x00, 0x4D, 0x48, 0x35, 0x7E, 0x84, 0x80,
+0x00, 0x48, 0xCB, 0x40, 0x4D, 0x48, 0x3E, 0xBE, 0x84, 0x80, 0x00, 0x48, 0xCC, 0x00, 0x05, 0x30,
+0xCD, 0xCA, 0x4D, 0x42, 0x03, 0x5C, 0xD2, 0xEA, 0x05, 0x30, 0xCA, 0x8A, 0x4A, 0x02, 0x4B, 0x48,
+0xB5, 0x40, 0x4C, 0x08, 0xBE, 0x80, 0x03, 0x5C, 0x91, 0xAA, 0x4B, 0x8A, 0xB6, 0x40, 0x4C, 0x4A,
+0xBF, 0xC0, 0x4B, 0x8A, 0xB7, 0x80, 0x4C, 0x43, 0xC0, 0x80, 0x4B, 0x83, 0xB8, 0x00, 0x4C, 0x4A,
+0xC1, 0xC0, 0x4B, 0x83, 0xB9, 0x40, 0x4C, 0x43, 0xC2, 0xC0, 0x4B, 0x48, 0xBA, 0x40, 0x4C, 0x4A,
+0xC3, 0x00, 0x4B, 0x48, 0xBB, 0x80, 0x4C, 0x43, 0xC4, 0xC0, 0x4B, 0x8A, 0xBC, 0x40, 0x4C, 0x08,
+0xC5, 0x00, 0x4B, 0x83, 0xBD, 0x80, 0x4C, 0x08, 0xC6, 0x00, 0xCD, 0x81, 0x1F, 0xF0, 0xA1, 0xC0,
+0xE0, 0x70, 0xA2, 0xC0, 0x4D, 0x48, 0x35, 0x7E, 0x84, 0x80, 0x00, 0x48, 0xBB, 0x63, 0x8A, 0x51,
+0xB2, 0x00, 0x4D, 0x48, 0x35, 0x7E, 0x84, 0x80, 0x32, 0x08, 0x80, 0x40, 0x1F, 0xF0, 0xA1, 0xC0,
+0xE0, 0x70, 0xA2, 0xC0, 0x4D, 0x48, 0x3E, 0xBE, 0x84, 0x80, 0x00, 0x48, 0xBB, 0x63, 0x8A, 0x51,
+0xB2, 0x00, 0x4D, 0x48, 0x3E, 0xBE, 0x84, 0x80, 0x32, 0x08, 0x80, 0x40, 0x4D, 0x48, 0x35, 0x7E,
+0x84, 0x80, 0x00, 0x48, 0xA1, 0xC0, 0xA2, 0x01, 0x47, 0x48, 0xA7, 0x23, 0x8A, 0x51, 0x4D, 0x48,
+0x3E, 0xBE, 0x84, 0x80, 0x00, 0x48, 0xA1, 0xC0, 0xA2, 0x01, 0x48, 0xC8, 0xA7, 0x23, 0x8A, 0x51,
+0x07, 0x70, 0xD2, 0xE3, 0x8A, 0x51, 0xC9, 0x00, 0x4D, 0x48, 0x03, 0x59, 0x8B, 0x2B, 0x34, 0x08,
+0x49, 0x02, 0x03, 0x18, 0x97, 0x6B, 0x49, 0x08, 0xB4, 0x00, 0x4D, 0x48, 0x35, 0x7E, 0x84, 0x80,
+0x00, 0x48, 0xCB, 0x40, 0x4D, 0x48, 0x3E, 0xBE, 0x84, 0x80, 0x00, 0x48, 0xCC, 0x00, 0x09, 0x30,
+0xCD, 0xCA, 0x4D, 0x42, 0x03, 0x5C, 0x4E, 0x2B, 0x4B, 0x48, 0xA1, 0xC0, 0xA2, 0x01, 0x47, 0x48,
+0xA7, 0x23, 0x8A, 0x51, 0x4C, 0x08, 0xA1, 0xC0, 0xA2, 0x01, 0x48, 0xC8, 0xA7, 0x6B, 0xA3, 0x00,
+0x22, 0x0A, 0x03, 0x59, 0xB5, 0x6B, 0x22, 0xC8, 0x63, 0x86, 0x03, 0x59, 0xB5, 0x6B, 0x22, 0xC8,
+0x88, 0x80, 0x80, 0xF0, 0x8C, 0xC0, 0x22, 0xC8, 0xE3, 0x40, 0x21, 0xC8, 0x88, 0x80, 0x23, 0x08,
+0x80, 0x38, 0x8C, 0xC0, 0x08, 0x40, 0xA4, 0xC0, 0x21, 0xC8, 0x80, 0x7A, 0xA3, 0x00, 0x24, 0xC8,
+0x80, 0x7A, 0xA3, 0x42, 0x03, 0x18, 0xC6, 0x2B, 0x21, 0xC8, 0x08, 0x40, 0x24, 0xC8, 0x80, 0x7A,
+0xA3, 0x00, 0x22, 0xC8, 0x80, 0x7A, 0xA3, 0x42, 0x03, 0x18, 0xD0, 0xEB, 0x22, 0xC8, 0x08, 0x40,
+0x24, 0xC8, 0x08, 0x40, 0xAA, 0x00, 0x2A, 0x8E, 0xF0, 0x39, 0x0C, 0x78, 0xAE, 0x40, 0xB1, 0x41,
+0x2E, 0x48, 0xA1, 0xC0, 0x02, 0xF0, 0xA2, 0xC0, 0x54, 0x70, 0xA7, 0x23, 0x8A, 0x51, 0x2E, 0x48,
+0x02, 0xBE, 0xA1, 0xC0, 0x02, 0xF0, 0xA2, 0xC0, 0x54, 0x70, 0xA7, 0x23, 0x8A, 0x51, 0x02, 0xF0,
+0xA1, 0xC0, 0x54, 0x70, 0x30, 0x24, 0x8A, 0x51, 0xE5, 0x40, 0xE5, 0x9F, 0xE7, 0xAB, 0x02, 0xF0,
+0xA1, 0xC0, 0x57, 0xF0, 0x30, 0x24, 0x8A, 0x51, 0xAB, 0x40, 0x02, 0xF0, 0xA1, 0xC0, 0x58, 0x70,
+0x30, 0x24, 0x8A, 0x51, 0xAC, 0x00, 0x31, 0x08, 0x25, 0x3E, 0x84, 0x80, 0x2B, 0x48, 0x44, 0x24,
+0x8A, 0x51, 0xA4, 0xC0, 0x2C, 0x08, 0x44, 0x24, 0x8A, 0x51, 0x24, 0x47, 0x80, 0x40, 0x05, 0x30,
+0xB1, 0x8A, 0x31, 0x02, 0x03, 0x5C, 0xD8, 0x2B, 0xB0, 0x01, 0x25, 0x08, 0xAF, 0x80, 0xAD, 0x81,
+0xB1, 0x41, 0x31, 0x08, 0x25, 0x3E, 0x84, 0x80, 0x2F, 0x88, 0x00, 0x42, 0x03, 0x18, 0x1F, 0x2C,
+0x31, 0x08, 0x25, 0x3E, 0x84, 0x80, 0x00, 0x48, 0xAF, 0x80, 0x31, 0x08, 0xAD, 0x40, 0x05, 0x30,
+0xB1, 0x8A, 0x31, 0x02, 0x03, 0x5C, 0x11, 0x6C, 0x2D, 0x48, 0x25, 0x3E, 0x84, 0x80, 0xFF, 0xB0,
+0x80, 0x40, 0x03, 0x30, 0xB0, 0x4A, 0x30, 0xC2, 0x03, 0x5C, 0x0D, 0xAC, 0x2F, 0x88, 0x08, 0x40,
+0xA2, 0xC0, 0x21, 0x0A, 0x03, 0x59, 0x3E, 0x2C, 0x21, 0xC8, 0x63, 0x86, 0x03, 0x59, 0x3E, 0x2C,
+0x21, 0xC8, 0x88, 0x80, 0x80, 0xF0, 0x8C, 0xC0, 0x21, 0xC8, 0xE3, 0x40, 0x22, 0xC8, 0x8C, 0xC0,
+0x22, 0xC8, 0x8C, 0xC0, 0x08, 0x88, 0x08, 0x40, 0xA1, 0xC0, 0xA1, 0x1F, 0x4A, 0xAC, 0x21, 0x03,
+0xFF, 0x3A, 0x08, 0x40, 0x21, 0xC8, 0x08, 0x40, 0x02, 0xF0, 0xA0, 0x80, 0x95, 0x41, 0x96, 0x41,
+0x97, 0x81, 0x98, 0x01, 0x99, 0x41, 0x9A, 0x41, 0x9B, 0x81, 0x9C, 0x41, 0x10, 0xF0, 0x9E, 0x40,
+0x8B, 0x41, 0x83, 0x96, 0xD0, 0x01, 0xD1, 0x41, 0x83, 0x52, 0xD4, 0x41, 0x54, 0x08, 0xA0, 0xFE,
+0x84, 0x80, 0x80, 0x81, 0x54, 0x08, 0xA8, 0x3E, 0x84, 0x80, 0x80, 0x81, 0x54, 0x08, 0xB0, 0x3E,
+0x84, 0x80, 0x80, 0x81, 0x54, 0x08, 0xB8, 0x7E, 0x84, 0x80, 0x80, 0x81, 0x54, 0x08, 0xC0, 0xFE,
+0x84, 0x80, 0x80, 0x81, 0x54, 0x08, 0xC8, 0x3E, 0x84, 0x80, 0x80, 0x81, 0x54, 0x08, 0xD2, 0x7E,
+0x84, 0x80, 0x08, 0xF0, 0x80, 0x81, 0xD4, 0x8A, 0x54, 0x02, 0x03, 0x5C, 0x5E, 0x2C, 0x00, 0xB0,
+0xA1, 0x01, 0xA1, 0x43, 0x8A, 0x51, 0x30, 0x24, 0x8A, 0x51, 0x03, 0xBA, 0x03, 0x9D, 0x7F, 0xAC,
+0x03, 0x30, 0xE3, 0x40, 0xC8, 0x70, 0xA1, 0xC0, 0x02, 0xF0, 0xA2, 0xC0, 0x56, 0xB0, 0x8A, 0x51,
+0xA7, 0x23, 0x8A, 0x51, 0x02, 0xF0, 0xA1, 0xC0, 0x56, 0xB0, 0x8A, 0x51, 0x30, 0x24, 0x8A, 0x51,
+0xE5, 0x40, 0xC8, 0xFA, 0x03, 0x59, 0x1B, 0x94, 0xC9, 0xB0, 0xA1, 0xC0, 0x04, 0xF0, 0xA2, 0x01,
+0x8A, 0x95, 0xF0, 0x27, 0x8A, 0x51, 0x04, 0xF0, 0xA1, 0x01, 0x8A, 0x51, 0x22, 0x21, 0x8A, 0x51,
+0xE5, 0x40, 0xC9, 0x3A, 0x03, 0x59, 0x9B, 0xD4, 0xCA, 0xB0, 0xA1, 0xC0, 0x04, 0xF0, 0xA2, 0x01,
+0xA2, 0x4A, 0x8A, 0x95, 0xF0, 0x27, 0x8A, 0x51, 0x04, 0xF0, 0xA1, 0x01, 0xA1, 0x4A, 0x8A, 0x51,
+0x22, 0x21, 0x8A, 0x51, 0xE5, 0x40, 0xCA, 0x3A, 0x03, 0x59, 0x1B, 0xD5, 0x14, 0xC8, 0xCE, 0x40,
+0x06, 0x30, 0x03, 0xD0, 0xCE, 0xCC, 0xFF, 0x7E, 0x03, 0x9D, 0xC1, 0xAC, 0x4E, 0x48, 0xD1, 0x00,
+0x14, 0x5C, 0xCE, 0x2C, 0x83, 0x52, 0x03, 0x53, 0x66, 0xD6, 0xD1, 0xEC, 0x83, 0x52, 0x03, 0x53,
+0x66, 0x92, 0x94, 0x9C, 0xD7, 0x6C, 0x83, 0x52, 0x03, 0x53, 0xE6, 0x16, 0xDA, 0x2C, 0x83, 0x52,
+0x03, 0x53, 0xE6, 0xD2, 0x14, 0x9D, 0xE0, 0xAC, 0x83, 0x52, 0x03, 0x53, 0xE6, 0x57, 0xE3, 0x2C,
+0x83, 0x52, 0x03, 0x53, 0xE6, 0x13, 0x94, 0xDD, 0xE9, 0x2C, 0x83, 0x52, 0x03, 0x53, 0x67, 0xD4,
+0xEC, 0x2C, 0x83, 0x52, 0x03, 0x53, 0x67, 0x90, 0x14, 0x9E, 0xF2, 0x2C, 0x83, 0x52, 0x03, 0x53,
+0x66, 0x94, 0xF5, 0x6C, 0x83, 0x52, 0x03, 0x53, 0x66, 0x50, 0x94, 0xDE, 0xFB, 0xAC, 0x83, 0x52,
+0x03, 0x53, 0x66, 0x17, 0xFE, 0xAC, 0x83, 0x52, 0x03, 0x53, 0x66, 0xD3, 0xD1, 0x48, 0x03, 0x9D,
+0x05, 0xAD, 0x83, 0x52, 0x03, 0x53, 0xE6, 0x15, 0x08, 0x6D, 0x83, 0x52, 0x03, 0x53, 0xE6, 0xD1,
+0x51, 0x8B, 0x0E, 0xED, 0x83, 0x52, 0x03, 0x53, 0xE6, 0xD4, 0x11, 0xAD, 0x83, 0x52, 0x03, 0x53,
+0xE6, 0x90, 0x51, 0x08, 0x02, 0x7A, 0x03, 0x9D, 0x19, 0xED, 0x83, 0x52, 0x03, 0x53, 0x66, 0xD5,
+0x1C, 0xED, 0x83, 0x52, 0x03, 0x53, 0x66, 0x91, 0x8A, 0x51, 0x9B, 0xA0, 0x8A, 0x51, 0xD4, 0x41,
+0x54, 0x08, 0xE0, 0x3E, 0x84, 0x80, 0x83, 0x93, 0x00, 0x48, 0xCE, 0x40, 0x54, 0x08, 0xE2, 0x7E,
+0x84, 0x80, 0x4E, 0x48, 0x80, 0x40, 0x54, 0x08, 0xDE, 0xFE, 0x84, 0x80, 0x00, 0x48, 0xCE, 0x40,
+0x54, 0x08, 0x55, 0x7E, 0x84, 0x80, 0x4E, 0x48, 0x80, 0x40, 0x54, 0x08, 0xDA, 0xBE, 0x84, 0x80,
+0x00, 0x48, 0xCE, 0x40, 0x54, 0x08, 0xDC, 0xBE, 0x84, 0x80, 0x4E, 0x48, 0x80, 0x40, 0x54, 0x08,
+0xE8, 0x7E, 0x84, 0x80, 0x00, 0x48, 0xCE, 0x40, 0x54, 0x08, 0x5B, 0xBE, 0x84, 0x80, 0x4E, 0x48,
+0x80, 0x40, 0x54, 0x08, 0xE6, 0xBE, 0x84, 0x80, 0x00, 0x48, 0xCE, 0x40, 0x54, 0x08, 0x59, 0x7E,
+0x84, 0x80, 0x4E, 0x48, 0x80, 0x40, 0x54, 0x08, 0xE4, 0x7E, 0x84, 0x80, 0x00, 0x48, 0xCE, 0x40,
+0x54, 0x08, 0x57, 0xBE, 0x84, 0x80, 0x4E, 0x48, 0x80, 0x40, 0x02, 0xF0, 0xD4, 0x8A, 0x54, 0x02,
+0x03, 0x5C, 0x20, 0x6D, 0x69, 0xB0, 0xA1, 0x01, 0xA2, 0x01, 0x8A, 0x51, 0xA7, 0x23, 0x8A, 0x51,
+0x19, 0x10, 0x19, 0x51, 0x99, 0x50, 0x99, 0x91, 0x21, 0x30, 0xA1, 0x01, 0xA1, 0x4A, 0xA2, 0x01,
+0xA2, 0x4A, 0x8A, 0x51, 0xA7, 0x23, 0x8A, 0x51, 0x21, 0x30, 0xA1, 0x01, 0xA2, 0x01, 0xA2, 0x4A,
+0x8A, 0x51, 0xA7, 0x23, 0x8A, 0x51, 0x07, 0x70, 0xA1, 0xC0, 0x02, 0xF0, 0xA2, 0xC0, 0x30, 0x30,
+0x8A, 0x51, 0xA7, 0x23, 0x8A, 0x51, 0xE6, 0xD8, 0xE8, 0x41, 0x27, 0xB0, 0xA1, 0x01, 0xA2, 0x01,
+0xA2, 0x4A, 0x8A, 0x51, 0xA7, 0x23, 0x8A, 0x51, 0x28, 0x30, 0xA1, 0x01, 0xA2, 0x01, 0xA2, 0x4A,
+0x8A, 0x51, 0xA7, 0x23, 0x8A, 0x51, 0x66, 0x1E, 0x3E, 0x6E, 0xE6, 0x5D, 0x66, 0xD9, 0x9A, 0x2D,
+0xE6, 0x1C, 0xBD, 0xAD, 0x18, 0x14, 0xE6, 0x1C, 0xB2, 0x2D, 0x03, 0x30, 0xA1, 0xC0, 0x02, 0xF0,
+0xA2, 0xC0, 0x2C, 0x70, 0x8A, 0x51, 0xA7, 0x23, 0x8A, 0x51, 0x99, 0xD5, 0x29, 0x70, 0xD0, 0xC0,
+0x96, 0xB0, 0xCF, 0x80, 0xA6, 0xB0, 0xCE, 0x40, 0xCE, 0x0B, 0xAC, 0x2D, 0xCF, 0x4B, 0xAC, 0x2D,
+0xD0, 0x8B, 0xAC, 0x2D, 0x03, 0x30, 0x83, 0x52, 0x03, 0x53, 0xEE, 0x80, 0x0F, 0xB0, 0xEF, 0xC0,
+0x00, 0xB0, 0x8A, 0x95, 0x02, 0x26, 0x8A, 0x51, 0xC7, 0x6D, 0x18, 0xD0, 0x02, 0xF0, 0xEE, 0x80,
+0x0E, 0x70, 0xEF, 0xC0, 0x01, 0xF0, 0xAA, 0x41, 0x8A, 0x95, 0x75, 0x25, 0x8A, 0x51, 0x6C, 0x48,
+0x83, 0x96, 0xD2, 0x00, 0x83, 0x52, 0x6B, 0x88, 0x83, 0x96, 0xD3, 0x40, 0x83, 0x52, 0x64, 0x08,
+0x83, 0x96, 0xD4, 0x00, 0x83, 0x52, 0x02, 0xF0, 0xA1, 0x01, 0xA2, 0xC0, 0x53, 0xB0, 0x8A, 0x51,
+0xA7, 0x23, 0x8A, 0x51, 0x14, 0x30, 0xA1, 0xC0, 0x02, 0xF0, 0xA2, 0xC0, 0x56, 0xB0, 0x8A, 0x51,
+0xA7, 0x23, 0x8A, 0x51, 0x02, 0xF0, 0x8A, 0x51, 0x31, 0x61, 0x8A, 0x51, 0xD2, 0x00, 0xEC, 0xF0,
+0xA1, 0xC0, 0x02, 0xF0, 0xA2, 0xC0, 0x56, 0xB0, 0x8A, 0x51, 0xA7, 0x23, 0x8A, 0x51, 0x00, 0xB0,
+0x8A, 0x51, 0x79, 0xE2, 0x8A, 0x51, 0x07, 0x70, 0x8A, 0x51, 0x31, 0x61, 0x8A, 0x51, 0xD3, 0x40,
+0x02, 0xF0, 0xA1, 0x01, 0xA2, 0xC0, 0x2F, 0xF0, 0x8A, 0x51, 0xA7, 0x23, 0x8A, 0x51, 0x24, 0x30,
+0xA1, 0xC0, 0x02, 0xF0, 0xA2, 0xC0, 0x2F, 0xF0, 0x8A, 0x51, 0xA7, 0x23, 0x8A, 0x51, 0x2A, 0x70,
+0xCF, 0x80, 0x8D, 0xB0, 0xCE, 0x40, 0xCE, 0x0B, 0x0B, 0xEE, 0xCF, 0x4B, 0x0B, 0xEE, 0x10, 0x6E,
+0x83, 0x52, 0x02, 0xF0, 0x03, 0x53, 0xA1, 0x01, 0xA2, 0xC0, 0x2F, 0xF0, 0x8A, 0x51, 0xA7, 0x23,
+0x8A, 0x51, 0xE6, 0x5D, 0x66, 0xD9, 0x1E, 0x2E, 0xE6, 0x1C, 0x25, 0xEE, 0x99, 0x91, 0x03, 0x30,
+0xA1, 0xC0, 0x00, 0xB0, 0xA2, 0x01, 0xA2, 0x4A, 0x29, 0xEE, 0x03, 0x30, 0xA1, 0xC0, 0x00, 0xB0,
+0xA2, 0x01, 0x8A, 0x95, 0xF0, 0x27, 0x8A, 0x51, 0x52, 0x08, 0x8A, 0x95, 0xEB, 0x24, 0x8A, 0x51,
+0xCE, 0x40, 0x53, 0x48, 0x8A, 0x95, 0xEB, 0x24, 0x8A, 0x51, 0x4E, 0x42, 0x1E, 0x7E, 0x83, 0x96,
+0xD0, 0xC0, 0x32, 0x70, 0x50, 0xC2, 0x83, 0x52, 0x03, 0x18, 0x9B, 0x15, 0x83, 0x52, 0xE6, 0x5E,
+0xEE, 0xAE, 0xE6, 0x5D, 0x66, 0xD9, 0x46, 0xEE, 0xE6, 0x1C, 0x69, 0x2E, 0x18, 0xD0, 0xE6, 0x1C,
+0x5E, 0x6E, 0x03, 0x30, 0xA1, 0xC0, 0x02, 0xF0, 0xA2, 0xC0, 0x2C, 0x70, 0x8A, 0x51, 0xA7, 0x23,
+0x8A, 0x51, 0x99, 0x94, 0x29, 0x70, 0xD0, 0xC0, 0x96, 0xB0, 0xCF, 0x80, 0xA6, 0xB0, 0xCE, 0x40,
+0xCE, 0x0B, 0x58, 0xEE, 0xCF, 0x4B, 0x58, 0xEE, 0xD0, 0x8B, 0x58, 0xEE, 0x03, 0x30, 0x83, 0x52,
+0x03, 0x53, 0xEE, 0x80, 0x0F, 0xB0, 0xEF, 0xC0, 0x01, 0xF0, 0x8A, 0x95, 0x02, 0x26, 0x8A, 0x51,
+0x74, 0x2E, 0x18, 0x14, 0x02, 0xF0, 0xEE, 0x80, 0x0E, 0x70, 0xEF, 0xC0, 0x01, 0xF0, 0xAA, 0x41,
+0xAA, 0x8A, 0x8A, 0x95, 0x75, 0x25, 0x8A, 0x51, 0x6C, 0x48, 0x83, 0x96, 0xD2, 0x00, 0x83, 0x52,
+0x6B, 0x88, 0x83, 0x96, 0xD3, 0x40, 0x83, 0x52, 0x64, 0x08, 0x83, 0x96, 0xD4, 0x00, 0x83, 0x52,
+0x02, 0xF0, 0xA1, 0x01, 0xA1, 0x4A, 0xA2, 0xC0, 0x53, 0xB0, 0x8A, 0x51, 0xA7, 0x23, 0x8A, 0x51,
+0x14, 0x30, 0xA1, 0xC0, 0x02, 0xF0, 0xA2, 0xC0, 0x56, 0xB0, 0x8A, 0x51, 0xA7, 0x23, 0x8A, 0x51,
+0x02, 0xF0, 0x8A, 0x51, 0x31, 0x61, 0x8A, 0x51, 0xD2, 0x00, 0xEC, 0xF0, 0xA1, 0xC0, 0x02, 0xF0,
+0xA2, 0xC0, 0x56, 0xB0, 0x8A, 0x51, 0xA7, 0x23, 0x8A, 0x51, 0x01, 0xF0, 0x8A, 0x51, 0x79, 0xE2,
+0x8A, 0x51, 0x07, 0x70, 0x8A, 0x51, 0x31, 0x61, 0x8A, 0x51, 0xD3, 0x40, 0x02, 0xF0, 0xA1, 0x01,
+0xA2, 0xC0, 0x2F, 0xF0, 0x8A, 0x51, 0xA7, 0x23, 0x8A, 0x51, 0x25, 0x70, 0xA1, 0xC0, 0x02, 0xF0,
+0xA2, 0xC0, 0x2F, 0xF0, 0x8A, 0x51, 0xA7, 0x23, 0x8A, 0x51, 0x2A, 0x70, 0xCF, 0x80, 0x8D, 0xB0,
+0xCE, 0x40, 0xCE, 0x0B, 0xB9, 0x6E, 0xCF, 0x4B, 0xB9, 0x6E, 0xBE, 0xAE, 0x83, 0x52, 0x02, 0xF0,
+0x03, 0x53, 0xA1, 0x01, 0xA2, 0xC0, 0x2F, 0xF0, 0x8A, 0x51, 0xA7, 0x23, 0x8A, 0x51, 0xE6, 0x5D,
+0x66, 0xD9, 0xCC, 0x2E, 0xE6, 0x1C, 0xD4, 0x2E, 0xE6, 0x1C, 0xCF, 0xAE, 0x99, 0x50, 0x03, 0x30,
+0xA1, 0xC0, 0x00, 0xB0, 0xA2, 0x01, 0xD9, 0x6E, 0x03, 0x30, 0xA1, 0xC0, 0x00, 0xB0, 0xA2, 0x01,
+0xA2, 0x4A, 0x8A, 0x95, 0xF0, 0x27, 0x8A, 0x51, 0x52, 0x08, 0x8A, 0x95, 0xEB, 0x24, 0x8A, 0x51,
+0xCE, 0x40, 0x53, 0x48, 0x8A, 0x95, 0xEB, 0x24, 0x8A, 0x51, 0x4E, 0x42, 0x1E, 0x7E, 0x83, 0x96,
+0xD1, 0x00, 0x32, 0x70, 0x51, 0x02, 0x83, 0x52, 0x03, 0x18, 0x1B, 0xD6, 0x83, 0x52, 0x66, 0xDC,
+0xF3, 0xAE, 0x03, 0x30, 0xF4, 0x6E, 0x02, 0xF0, 0xEE, 0x80, 0x01, 0xF0, 0xE1, 0x00, 0x14, 0x30,
+0xA1, 0xC0, 0x02, 0xF0, 0xA2, 0xC0, 0x56, 0xB0, 0x8A, 0x51, 0xA7, 0x23, 0x8A, 0x51, 0xE6, 0x9F,
+0x72, 0x6F, 0x18, 0xD0, 0x02, 0xF0, 0xA1, 0x01, 0xA2, 0xC0, 0x53, 0xB0, 0x8A, 0x51, 0xA7, 0x23,
+0x8A, 0x51, 0x9B, 0x16, 0xD4, 0x41, 0x27, 0xB0, 0xA1, 0x01, 0xA2, 0x01, 0xA2, 0x4A, 0x8A, 0x51,
+0xA7, 0x23, 0x8A, 0x51, 0x28, 0x30, 0xA1, 0x01, 0xA2, 0x01, 0xA2, 0x4A, 0x8A, 0x51, 0xA7, 0x23,
+0x8A, 0x51, 0x54, 0x08, 0x08, 0xBE, 0xEF, 0xC0, 0x00, 0xB0, 0xAA, 0x41, 0xD4, 0x48, 0x03, 0x59,
+0x01, 0xF0, 0x8A, 0x95, 0x75, 0x25, 0x8A, 0x51, 0x54, 0x08, 0xD2, 0x7E, 0x84, 0x80, 0x6B, 0x0E,
+0xF0, 0x39, 0x64, 0x04, 0x83, 0x93, 0x80, 0x40, 0x03, 0x30, 0x8A, 0x51, 0x31, 0x61, 0x8A, 0x51,
+0xD2, 0x00, 0x00, 0xB0, 0x8A, 0x51, 0x90, 0x21, 0x8A, 0x51, 0x07, 0x70, 0x8A, 0x51, 0x31, 0x61,
+0x8A, 0x51, 0xD3, 0x40, 0x52, 0x08, 0x8A, 0x95, 0xEB, 0x24, 0x8A, 0x51, 0xCE, 0x40, 0x53, 0x48,
+0x8A, 0x95, 0xEB, 0x24, 0x8A, 0x51, 0x4E, 0x42, 0x18, 0xFE, 0xCF, 0x80, 0x54, 0x08, 0xC0, 0xFE,
+0x84, 0x80, 0x4F, 0x88, 0x80, 0x40, 0x54, 0x08, 0xA0, 0xFE, 0x84, 0x80, 0x27, 0xB0, 0xA1, 0x01,
+0xA1, 0x4A, 0x8A, 0x51, 0x30, 0x24, 0x8A, 0x51, 0x80, 0x40, 0x54, 0x08, 0xA8, 0x3E, 0x84, 0x80,
+0x28, 0x30, 0xA1, 0x01, 0xA1, 0x4A, 0x8A, 0x51, 0x30, 0x24, 0x8A, 0x51, 0x80, 0x40, 0x54, 0x08,
+0xC0, 0xFE, 0x84, 0x80, 0x14, 0x30, 0x00, 0x42, 0x03, 0x5C, 0x9B, 0xD2, 0x08, 0xF0, 0xD4, 0x8A,
+0x54, 0x02, 0x03, 0x5C, 0x0B, 0x2F, 0x03, 0x30, 0xA1, 0xC0, 0x00, 0xB0, 0xA2, 0x01, 0x8A, 0x95,
+0xF0, 0x27, 0x8A, 0x51, 0x67, 0x1C, 0xE8, 0x6F, 0x18, 0x14, 0x02, 0xF0, 0xA1, 0x01, 0xA1, 0x4A,
+0xA2, 0xC0, 0x53, 0xB0, 0x8A, 0x51, 0xA7, 0x23, 0x8A, 0x51, 0x1B, 0x17, 0xD4, 0x41, 0x27, 0xB0,
+0xA1, 0x01, 0xA2, 0x01, 0xA2, 0x4A, 0x8A, 0x51, 0xA7, 0x23, 0x8A, 0x51, 0x28, 0x30, 0xA1, 0x01,
+0xA2, 0x01, 0xA2, 0x4A, 0x8A, 0x51, 0xA7, 0x23, 0x8A, 0x51, 0x54, 0x08, 0x08, 0xBE, 0xEF, 0xC0,
+0x00, 0xB0, 0xAA, 0x41, 0xAA, 0x8A, 0xD4, 0x48, 0x03, 0x59, 0x01, 0xF0, 0x8A, 0x95, 0x75, 0x25,
+0x8A, 0x51, 0x54, 0x08, 0xD2, 0x7E, 0x84, 0x80, 0x6B, 0x0E, 0xF0, 0x39, 0x64, 0x04, 0x83, 0x93,
+0x80, 0x40, 0x03, 0x30, 0x8A, 0x51, 0x31, 0x61, 0x8A, 0x51, 0xD2, 0x00, 0x01, 0xF0, 0x8A, 0x51,
+0x90, 0x21, 0x8A, 0x51, 0x07, 0x70, 0x8A, 0x51, 0x31, 0x61, 0x8A, 0x51, 0xD3, 0x40, 0x52, 0x08,
+0x8A, 0x95, 0xEB, 0x24, 0x8A, 0x51, 0xCE, 0x40, 0x53, 0x48, 0x8A, 0x95, 0xEB, 0x24, 0x8A, 0x51,
+0x4E, 0x42, 0x18, 0xFE, 0xCF, 0x80, 0x54, 0x08, 0xC8, 0x3E, 0x84, 0x80, 0x4F, 0x88, 0x80, 0x40,
+0x54, 0x08, 0xB0, 0x3E, 0x84, 0x80, 0x27, 0xB0, 0xA1, 0x01, 0xA1, 0x4A, 0x8A, 0x51, 0x30, 0x24,
+0x8A, 0x51, 0x80, 0x40, 0x54, 0x08, 0xB8, 0x7E, 0x84, 0x80, 0x28, 0x30, 0xA1, 0x01, 0xA1, 0x4A,
+0x8A, 0x51, 0x30, 0x24, 0x8A, 0x51, 0x80, 0x40, 0x54, 0x08, 0xC8, 0x3E, 0x84, 0x80, 0x14, 0x30,
+0x00, 0x42, 0x03, 0x5C, 0x1B, 0xD3, 0x08, 0xF0, 0xD4, 0x8A, 0x54, 0x02, 0x03, 0x5C, 0x7F, 0x2F,
+0x03, 0x30, 0xA1, 0xC0, 0x00, 0xB0, 0xA2, 0x01, 0xA2, 0x4A, 0x8A, 0x95, 0xF0, 0x27, 0x8A, 0x51,
+0x18, 0x56, 0x18, 0x12, 0x8A, 0x95, 0x5B, 0x67, 0x8A, 0x51, 0xE4, 0xB0, 0xCE, 0x40, 0xF0, 0x6F,
+0xF1, 0xAF, 0xCE, 0x0B, 0xEF, 0x2F, 0xF4, 0xAF, 0x00, 0x00, 0x0E, 0x70, 0x83, 0x52, 0x03, 0x53,
+0xA1, 0xC0, 0x69, 0xB0, 0xA2, 0x01, 0x8A, 0x51, 0xA7, 0x23, 0x8A, 0x51, 0x9B, 0x57, 0xFF, 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, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF,
+0xFF, 0xBF, 0xFF, 0xBF, 0xFF, 0xBF, 0xA2, 0xC0, 0x10, 0xF0, 0x22, 0xC2, 0x22, 0xC8, 0x03, 0x18,
+0xF6, 0x6C, 0x1F, 0xBE, 0x84, 0x80, 0x8A, 0x51, 0x02, 0xA0, 0x08, 0x40, 0xA1, 0xC0, 0x03, 0xD0,
+0xA1, 0x4C, 0x03, 0xD0, 0xA1, 0x4C, 0x03, 0xD0, 0xA1, 0x4C, 0x21, 0xC8, 0x01, 0xBE, 0x84, 0x80,
+0x8A, 0x51, 0x02, 0xA0, 0x08, 0x40, 0xA8, 0xC0, 0x28, 0x5C, 0x0A, 0xAD, 0x83, 0x52, 0x03, 0x53,
+0x18, 0x14, 0x0D, 0xED, 0x83, 0x52, 0x03, 0x53, 0x18, 0xD0, 0x07, 0x70, 0xE4, 0x00, 0x64, 0x8E,
+0xF0, 0x39, 0x96, 0x00, 0x18, 0x55, 0x18, 0x11, 0xA9, 0x41, 0x28, 0xC8, 0xA4, 0xC0, 0x00, 0xB0,
+0x56, 0xE5, 0x8A, 0x95, 0x98, 0x95, 0x98, 0x51, 0xA7, 0x81, 0x74, 0xB0, 0x0E, 0x02, 0x03, 0x5C,
+0x27, 0x2D, 0x0B, 0x70, 0x64, 0x02, 0x03, 0x18, 0x27, 0x2D, 0xE4, 0x8A, 0x31, 0xED, 0x2D, 0xB0,
+0x0E, 0x02, 0x03, 0x18, 0x36, 0x2D, 0x08, 0xF0, 0x64, 0x02, 0x03, 0x5C, 0x36, 0x2D, 0xFF, 0xB0,
+0xE4, 0xC7, 0x64, 0x8E, 0xF0, 0x39, 0x96, 0x00, 0x18, 0x55, 0x18, 0x11, 0x24, 0x30, 0x0F, 0x42,
+0x03, 0x5C, 0x40, 0x6D, 0x09, 0x30, 0x29, 0x02, 0x03, 0x18, 0x40, 0x6D, 0xA9, 0x8A, 0x49, 0xED,
+0x10, 0xF0, 0x0F, 0x42, 0x03, 0x18, 0x50, 0xAD, 0x29, 0x08, 0x03, 0x59, 0x50, 0xAD, 0xFF, 0xB0,
+0xA9, 0xC7, 0x28, 0xC8, 0xA4, 0xC0, 0x29, 0x08, 0x56, 0xE5, 0x8A, 0x95, 0x98, 0x95, 0x98, 0x51,
+0x14, 0x30, 0xA7, 0xCA, 0x27, 0x42, 0x03, 0x18, 0x08, 0x40, 0x1D, 0x2D, 0xA6, 0x00, 0x39, 0x7E,
+0x84, 0x80, 0x8A, 0x51, 0x02, 0xA0, 0x8A, 0x95, 0xEC, 0x40, 0x26, 0x08, 0x2F, 0xBE, 0x84, 0x80,
+0x8A, 0x51, 0x02, 0xA0, 0x8A, 0x95, 0xEB, 0x80, 0x6C, 0x48, 0xA5, 0x00, 0x05, 0x30, 0x03, 0xD0,
+0xA5, 0xCD, 0xFF, 0x7E, 0x03, 0xD0, 0x03, 0x9D, 0x68, 0xED, 0x6B, 0x0D, 0x25, 0x04, 0x68, 0x04,
+0xA1, 0xC0, 0x24, 0xC8, 0xA2, 0xC0, 0x0C, 0x30, 0xF0, 0x6F, 0xAD, 0x40, 0x01, 0xF0, 0x83, 0x96,
+0xED, 0x80, 0x83, 0x52, 0x2A, 0x08, 0xAA, 0xE6, 0x8A, 0x95, 0x2D, 0x48, 0x03, 0x59, 0xFE, 0xED,
+0x2A, 0x08, 0xE2, 0x7E, 0x84, 0x80, 0x00, 0x48, 0xAB, 0x40, 0x2A, 0x08, 0x5B, 0xBE, 0x84, 0x80,
+0x2B, 0x48, 0x80, 0x40, 0x66, 0x5F, 0x92, 0xED, 0x2A, 0x08, 0x55, 0x7E, 0x84, 0x80, 0x00, 0x48,
+0x28, 0xFE, 0x97, 0x6D, 0x2A, 0x08, 0x55, 0x7E, 0x84, 0x80, 0x00, 0x48, 0x14, 0xFE, 0xAB, 0x40,
+0x2A, 0x08, 0x59, 0x7E, 0x84, 0x80, 0x2B, 0x48, 0x80, 0x40, 0x2A, 0x08, 0x55, 0x7E, 0x84, 0x80,
+0x00, 0x48, 0xAB, 0x40, 0x2A, 0x08, 0x59, 0x7E, 0x84, 0x80, 0x00, 0x48, 0xAC, 0x00, 0x2B, 0x48,
+0x2C, 0x02, 0x2A, 0x08, 0x03, 0x18, 0xB6, 0x6D, 0x5B, 0xBE, 0x84, 0x80, 0x00, 0x8A, 0xAB, 0x40,
+0x2A, 0x08, 0x5B, 0xBE, 0x84, 0x80, 0x2B, 0x48, 0x80, 0x40, 0x2A, 0x08, 0xDC, 0xBE, 0x84, 0x80,
+0x00, 0x48, 0xAB, 0x40, 0x2A, 0x08, 0x57, 0xBE, 0x84, 0x80, 0x2B, 0x48, 0x80, 0x40, 0x2A, 0x08,
+0x5B, 0xBE, 0x84, 0x80, 0x00, 0x48, 0xA1, 0xC0, 0x2A, 0x08, 0xA2, 0xC0, 0x04, 0xF0, 0xF0, 0x27,
+0x8A, 0x95, 0x2A, 0x08, 0x59, 0x7E, 0x84, 0x80, 0x00, 0x48, 0xA1, 0xC0, 0x2A, 0x08, 0xA2, 0xC0,
+0x05, 0x30, 0xF0, 0x27, 0x8A, 0x95, 0x2A, 0x08, 0x57, 0xBE, 0x84, 0x80, 0x00, 0x48, 0xA1, 0xC0,
+0x2A, 0x08, 0xA2, 0xC0, 0x06, 0x30, 0xF0, 0x27, 0x8A, 0x95, 0xA1, 0x01, 0xA1, 0x4A, 0x2A, 0x08,
+0xA2, 0xC0, 0x00, 0xB0, 0xF0, 0x27, 0x8A, 0x95, 0xE4, 0xB0, 0xAB, 0x40, 0xE7, 0xAD, 0xE8, 0x2D,
+0xAB, 0x0B, 0xE6, 0x6D, 0xEB, 0xAD, 0x00, 0x00, 0x0F, 0xB0, 0x83, 0x52, 0x03, 0x53, 0xA1, 0xC0,
+0x2A, 0x08, 0xA2, 0xC0, 0x00, 0xB0, 0xF0, 0x27, 0x8A, 0x95, 0xD0, 0x70, 0xAC, 0x00, 0xC9, 0xB0,
+0xAB, 0x40, 0xAB, 0x0B, 0xF9, 0xAD, 0xAC, 0xCB, 0xF9, 0xAD, 0xFE, 0xED, 0x83, 0x52, 0x03, 0x53,
+0x2A, 0x08, 0x03, 0xAD, 0xAC, 0x00, 0x2C, 0x08, 0x03, 0x59, 0x0A, 0xAE, 0xAD, 0x81, 0xAD, 0xCA,
+0xAE, 0x81, 0x0D, 0xEE, 0xAD, 0x81, 0xAE, 0x81, 0xAE, 0xCA, 0x83, 0x96, 0xED, 0xC1, 0x0E, 0x70,
+0x83, 0x52, 0xEF, 0xC0, 0x02, 0xF0, 0xEE, 0x80, 0x2D, 0x48, 0xAA, 0xE6, 0x8A, 0x95, 0x2E, 0x48,
+0xAA, 0xE6, 0x8A, 0x95, 0x2D, 0x48, 0xE2, 0x7E, 0x84, 0x80, 0x00, 0x48, 0xAA, 0x00, 0x2E, 0x48,
+0x5B, 0xBE, 0x84, 0x80, 0x2A, 0x08, 0x80, 0x40, 0x66, 0x5F, 0x2C, 0xEE, 0x2D, 0x48, 0x55, 0x7E,
+0x84, 0x80, 0x00, 0x48, 0x28, 0xFE, 0x31, 0xEE, 0x2D, 0x48, 0x55, 0x7E, 0x84, 0x80, 0x00, 0x48,
+0x14, 0xFE, 0xAA, 0x00, 0x2E, 0x48, 0x59, 0x7E, 0x84, 0x80, 0x2A, 0x08, 0x80, 0x40, 0x2D, 0x48,
+0x55, 0x7E, 0x84, 0x80, 0x00, 0x48, 0xAA, 0x00, 0x2E, 0x48, 0x59, 0x7E, 0x84, 0x80, 0x00, 0x48,
+0xAB, 0x40, 0x2A, 0x08, 0x2B, 0x42, 0x03, 0x18, 0x4F, 0x6E, 0x2E, 0x48, 0x5B, 0xBE, 0x84, 0x80,
+0x00, 0x8A, 0xAA, 0x00, 0x2E, 0x48, 0x5B, 0xBE, 0x84, 0x80, 0x2A, 0x08, 0x80, 0x40, 0x2D, 0x48,
+0xDC, 0xBE, 0x84, 0x80, 0x00, 0x48, 0xAA, 0x00, 0x2E, 0x48, 0x57, 0xBE, 0x84, 0x80, 0x2A, 0x08,
+0x80, 0x40, 0x2E, 0x48, 0x5B, 0xBE, 0x84, 0x80, 0x00, 0x48, 0xA1, 0xC0, 0x2E, 0x48, 0xA2, 0xC0,
+0x04, 0xF0, 0xF0, 0x27, 0x8A, 0x95, 0x2E, 0x48, 0x59, 0x7E, 0x84, 0x80, 0x00, 0x48, 0xA1, 0xC0,
+0x2E, 0x48, 0xA2, 0xC0, 0x05, 0x30, 0xF0, 0x27, 0x8A, 0x95, 0x2E, 0x48, 0x57, 0xBE, 0x84, 0x80,
+0x00, 0x48, 0xA1, 0xC0, 0x2E, 0x48, 0xA2, 0xC0, 0x06, 0x30, 0xF0, 0x27, 0x8A, 0x95, 0xA1, 0x01,
+0xA1, 0x4A, 0x2D, 0x48, 0xA2, 0xC0, 0x00, 0xB0, 0xF0, 0x27, 0x8A, 0x95, 0xA1, 0x01, 0xA1, 0x4A,
+0x2E, 0x48, 0xA2, 0xC0, 0x00, 0xB0, 0xF0, 0x27, 0x8A, 0x95, 0xE4, 0xB0, 0xAA, 0x00, 0x88, 0xAE,
+0x89, 0xEE, 0xAA, 0xCB, 0x87, 0x2E, 0x8C, 0xEE, 0x00, 0x00, 0x03, 0x30, 0x83, 0x52, 0x03, 0x53,
+0xA1, 0xC0, 0x2D, 0x48, 0xA2, 0xC0, 0x00, 0xB0, 0xF0, 0x27, 0x8A, 0x95, 0x0D, 0x70, 0xA1, 0xC0,
+0x2E, 0x48, 0xA2, 0xC0, 0x00, 0xB0, 0xF0, 0x27, 0x8A, 0x95, 0xD0, 0x70, 0xAB, 0x40, 0xC9, 0xB0,
+0xAA, 0x00, 0xAA, 0xCB, 0xA1, 0xEE, 0xAB, 0x0B, 0xA1, 0xEE, 0xA6, 0x2E, 0x83, 0x52, 0x03, 0x53,
+0x2D, 0x48, 0x03, 0xAD, 0xA6, 0x00, 0xE2, 0x7E, 0x84, 0x80, 0x83, 0x93, 0x00, 0x48, 0xA1, 0xC0,
+0x26, 0x08, 0xA2, 0xC0, 0x01, 0xF0, 0xF0, 0x27, 0x8A, 0x95, 0x26, 0x08, 0x55, 0x7E, 0x84, 0x80,
+0x00, 0x48, 0xA1, 0xC0, 0x26, 0x08, 0xA2, 0xC0, 0x02, 0xF0, 0xF0, 0x27, 0x8A, 0x95, 0x26, 0x08,
+0xDC, 0xBE, 0x84, 0x80, 0x00, 0x48, 0xA1, 0xC0, 0x26, 0x08, 0xA2, 0xC0, 0x03, 0x30, 0xF0, 0x27,
+0x8A, 0x95, 0x26, 0x08, 0x5B, 0xBE, 0x84, 0x80, 0x00, 0x48, 0xA1, 0xC0, 0x26, 0x08, 0xA2, 0xC0,
+0x04, 0xF0, 0xF0, 0x27, 0x8A, 0x95, 0x26, 0x08, 0x59, 0x7E, 0x84, 0x80, 0x00, 0x48, 0xA1, 0xC0,
+0x26, 0x08, 0xA2, 0xC0, 0x05, 0x30, 0xF0, 0x27, 0x8A, 0x95, 0x26, 0x08, 0x57, 0xBE, 0x84, 0x80,
+0x00, 0x48, 0xA1, 0xC0, 0x26, 0x08, 0xA2, 0xC0, 0x06, 0x30, 0xF0, 0x27, 0x8A, 0x95, 0x6E, 0x0E,
+0xF0, 0x39, 0x6F, 0xC4, 0xA1, 0xC0, 0x26, 0x08, 0xA2, 0xC0, 0x08, 0xF0, 0xF0, 0x27, 0x8A, 0x95,
+0x62, 0x08, 0xA4, 0xC0, 0x04, 0xF0, 0x03, 0xD0, 0xA4, 0x8D, 0xFF, 0x7E, 0x03, 0xD0, 0x03, 0x9D,
+0xF4, 0x6E, 0x24, 0x4D, 0x60, 0xC4, 0xA1, 0xC0, 0x26, 0x08, 0xA2, 0xC0, 0x0A, 0x30, 0xF0, 0x27,
+0x8A, 0x95, 0x6D, 0x88, 0xA1, 0xC0, 0x26, 0x08, 0xA2, 0xC0, 0x0B, 0x70, 0xF0, 0x27, 0x8A, 0x95,
+0x6C, 0x48, 0xA4, 0xC0, 0x05, 0x30, 0x03, 0xD0, 0xA4, 0x8D, 0xFF, 0x7E, 0x03, 0xD0, 0x03, 0x9D,
+0x0C, 0xEF, 0x6B, 0x0D, 0x24, 0xC4, 0x68, 0x04, 0xA1, 0xC0, 0x26, 0x08, 0xA2, 0xC0, 0x0C, 0x30,
+0xF0, 0x27, 0x8A, 0x95, 0x69, 0x48, 0xA4, 0xC0, 0x05, 0x30, 0x03, 0xD0, 0xA4, 0x8D, 0xFF, 0x7E,
+0x03, 0x9D, 0x1D, 0x6F, 0x6A, 0x48, 0xA5, 0x00, 0x01, 0xF0, 0x03, 0xD0, 0xA5, 0xCD, 0xFF, 0x7E,
+0x03, 0xD0, 0x03, 0x9D, 0x26, 0x2F, 0x25, 0x8D, 0x24, 0xC4, 0x5E, 0x84, 0xA1, 0xC0, 0x26, 0x08,
+0xA2, 0xC0, 0x0D, 0x70, 0xF0, 0x27, 0x8A, 0x95, 0x03, 0xD0, 0x5F, 0x4D, 0x5D, 0x84, 0xA1, 0xC0,
+0x26, 0x08, 0xA2, 0xC0, 0x0E, 0x70, 0xF0, 0x27, 0x8A, 0x95, 0x83, 0x96, 0x6C, 0x48, 0x83, 0x52,
+0xA4, 0xC0, 0x03, 0xD0, 0xA4, 0x8D, 0x03, 0xD0, 0xA4, 0x8D, 0x03, 0xD0, 0xA4, 0x8D, 0x83, 0x96,
+0x6D, 0x88, 0x83, 0x52, 0xA5, 0x00, 0x03, 0xD0, 0xA5, 0xCD, 0x03, 0xD0, 0xA5, 0xCD, 0x83, 0x96,
+0x03, 0xD0, 0x6E, 0x0D, 0x83, 0x52, 0x25, 0x04, 0x24, 0xC4, 0x61, 0x04, 0xA1, 0xC0, 0x26, 0x08,
+0xA2, 0xC0, 0x10, 0xF0, 0xF0, 0x6F, 0xA4, 0x01, 0x24, 0xC8, 0xEA, 0xBE, 0x84, 0x80, 0x83, 0x93,
+0x00, 0x48, 0xA1, 0xC0, 0x24, 0xC8, 0xA2, 0xC0, 0x00, 0xB0, 0xF0, 0x27, 0x8A, 0x95, 0x24, 0xC8,
+0xE0, 0x3E, 0x84, 0x80, 0x00, 0x48, 0xA1, 0xC0, 0x24, 0xC8, 0xA2, 0xC0, 0x01, 0xF0, 0xF0, 0x27,
+0x8A, 0x95, 0x24, 0xC8, 0xDE, 0xFE, 0x84, 0x80, 0x00, 0x48, 0xA1, 0xC0, 0x24, 0xC8, 0xA2, 0xC0,
+0x02, 0xF0, 0xF0, 0x27, 0x8A, 0x95, 0x24, 0xC8, 0xDA, 0xBE, 0x84, 0x80, 0x00, 0x48, 0xA1, 0xC0,
+0x24, 0xC8, 0xA2, 0xC0, 0x03, 0x30, 0xF0, 0x27, 0x8A, 0x95, 0x24, 0xC8, 0xE8, 0x7E, 0x84, 0x80,
+0x00, 0x48, 0xA1, 0xC0, 0x24, 0xC8, 0xA2, 0xC0, 0x04, 0xF0, 0xF0, 0x27, 0x8A, 0x95, 0x24, 0xC8,
+0xE6, 0xBE, 0x84, 0x80, 0x00, 0x48, 0xA1, 0xC0, 0x24, 0xC8, 0xA2, 0xC0, 0x05, 0x30, 0xF0, 0x27,
+0x8A, 0x95, 0x24, 0xC8, 0xE4, 0x7E, 0x84, 0x80, 0x00, 0x48, 0xA1, 0xC0, 0x24, 0xC8, 0xA2, 0xC0,
+0x06, 0x30, 0xF0, 0x27, 0x8A, 0x95, 0x24, 0xC8, 0xAC, 0x7E, 0x84, 0x80, 0x83, 0xD7, 0x00, 0x48,
+0xA1, 0xC0, 0x24, 0xC8, 0xA2, 0xC0, 0x08, 0xF0, 0xF0, 0x27, 0x8A, 0x95, 0x24, 0xC8, 0xA8, 0x3E,
+0x84, 0x80, 0x00, 0x48, 0xA1, 0xC0, 0x24, 0xC8, 0xA2, 0xC0, 0x0A, 0x30, 0xF0, 0x27, 0x8A, 0x95,
+0x24, 0xC8, 0xAA, 0x7E, 0x84, 0x80, 0x00, 0x48, 0xA1, 0xC0, 0x24, 0xC8, 0xA2, 0xC0, 0x0B, 0x70,
+0xF0, 0x27, 0x8A, 0x95, 0x24, 0xC8, 0xA2, 0x3E, 0x84, 0x80, 0x00, 0x48, 0xA1, 0xC0, 0x24, 0xC8,
+0xA2, 0xC0, 0x0C, 0x30, 0xF0, 0x27, 0x8A, 0x95, 0x24, 0xC8, 0xA4, 0x3E, 0x84, 0x80, 0x00, 0x48,
+0xA1, 0xC0, 0x24, 0xC8, 0xA2, 0xC0, 0x0D, 0x70, 0xF0, 0x27, 0x8A, 0x95, 0x24, 0xC8, 0xA6, 0x7E,
+0x84, 0x80, 0x00, 0x48, 0xA1, 0xC0, 0x24, 0xC8, 0xA2, 0xC0, 0x0E, 0x70, 0xF0, 0x27, 0x8A, 0x95,
+0x24, 0xC8, 0xA0, 0xFE, 0x84, 0x80, 0x00, 0x48, 0xA1, 0xC0, 0x24, 0xC8, 0xA2, 0xC0, 0x10, 0xF0,
+0xF0, 0x27, 0x8A, 0x95, 0x02, 0xF0, 0xA4, 0x4A, 0x24, 0xC2, 0x03, 0x18, 0x08, 0x40, 0x5C, 0x6F,
+0xA3, 0x00, 0x22, 0xC8, 0x03, 0x59, 0xFA, 0xEF, 0x21, 0xC8, 0x88, 0x80, 0x23, 0x08, 0x80, 0x38,
+0x86, 0xC0, 0x08, 0x40, 0x21, 0xC8, 0x88, 0x80, 0x23, 0x08, 0x80, 0x38, 0x85, 0xC0, 0x08, 0x40
+};
diff --git a/libloragw/src/loragw_aux.c b/libloragw/src/loragw_aux.c
new file mode 100644
index 0000000..c921296
--- /dev/null
+++ b/libloragw/src/loragw_aux.c
@@ -0,0 +1,61 @@
+/*
+ / _____) _ | |
+( (____ _____ ____ _| |_ _____ ____| |__
+ \____ \| ___ | (_ _) ___ |/ ___) _ \
+ _____) ) ____| | | || |_| ____( (___| | | |
+(______/|_____)_|_|_| \__)_____)\____)_| |_|
+ (C)2013 Semtech-Cycleo
+
+Description:
+ LoRa concentrator HAL auxiliary functions
+
+License: Revised BSD License, see LICENSE.TXT file include in the project
+Maintainer: Sylvain Miermont
+*/
+
+
+/* -------------------------------------------------------------------------- */
+/* --- DEPENDANCIES --------------------------------------------------------- */
+
+/* fix an issue between POSIX and C99 */
+#if __STDC_VERSION__ >= 199901L
+ #define _XOPEN_SOURCE 600
+#else
+ #define _XOPEN_SOURCE 500
+#endif
+
+#include <stdio.h> /* printf fprintf */
+#include <time.h> /* clock_nanosleep */
+
+/* -------------------------------------------------------------------------- */
+/* --- PRIVATE MACROS ------------------------------------------------------- */
+
+#if DEBUG_AUX == 1
+ #define DEBUG_MSG(str) fprintf(stderr, str)
+ #define DEBUG_PRINTF(fmt, args...) fprintf(stderr,"%s:%d: "fmt, __FUNCTION__, __LINE__, args)
+#else
+ #define DEBUG_MSG(str)
+ #define DEBUG_PRINTF(fmt, args...)
+#endif
+
+/* -------------------------------------------------------------------------- */
+/* --- PUBLIC FUNCTIONS DEFINITION ------------------------------------------ */
+
+/* This implementation is POSIX-pecific and require a fix to be compatible with C99 */
+void wait_ms(unsigned long a) {
+ struct timespec dly;
+ struct timespec rem;
+
+ dly.tv_sec = a / 1000;
+ dly.tv_nsec = ((long)a % 1000) * 1000000;
+
+ DEBUG_PRINTF("NOTE dly: %ld sec %ld ns\n", dly.tv_sec, dly.tv_nsec);
+
+ if((dly.tv_sec > 0) || ((dly.tv_sec == 0) && (dly.tv_nsec > 100000))) {
+ clock_nanosleep(CLOCK_MONOTONIC, 0, &dly, &rem);
+ DEBUG_PRINTF("NOTE remain: %ld sec %ld ns\n", rem.tv_sec, rem.tv_nsec);
+ }
+ return;
+}
+
+/* --- EOF ------------------------------------------------------------------ */
diff --git a/libloragw/src/loragw_fpga.c b/libloragw/src/loragw_fpga.c
new file mode 100644
index 0000000..465f43e
--- /dev/null
+++ b/libloragw/src/loragw_fpga.c
@@ -0,0 +1,352 @@
+/*
+ / _____) _ | |
+( (____ _____ ____ _| |_ _____ ____| |__
+ \____ \| ___ | (_ _) ___ |/ ___) _ \
+ _____) ) ____| | | || |_| ____( (___| | | |
+(______/|_____)_|_|_| \__)_____)\____)_| |_|
+ (C)2013 Semtech-Cycleo
+
+Description:
+ Functions used to handle FPGA register access for LoRa concentrator.
+ Registers are addressed by name.
+ Multi-bytes registers are handled automatically.
+ Read-modify-write is handled automatically.
+
+License: Revised BSD License, see LICENSE.TXT file include in the project
+Maintainer: Michael Coracin
+*/
+
+/* -------------------------------------------------------------------------- */
+/* --- DEPENDANCIES --------------------------------------------------------- */
+
+#include <stdint.h> /* C99 types */
+#include <stdbool.h> /* bool type */
+#include <stdio.h> /* printf fprintf */
+
+#include "loragw_spi.h"
+#include "loragw_aux.h"
+#include "loragw_hal.h"
+#include "loragw_reg.h"
+#include "loragw_fpga.h"
+
+/* -------------------------------------------------------------------------- */
+/* --- PRIVATE MACROS ------------------------------------------------------- */
+
+#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
+#if DEBUG_REG == 1
+ #define DEBUG_MSG(str) fprintf(stderr, str)
+ #define DEBUG_PRINTF(fmt, args...) fprintf(stderr,"%s:%d: "fmt, __FUNCTION__, __LINE__, args)
+ #define CHECK_NULL(a) if(a==NULL){fprintf(stderr,"%s:%d: ERROR: NULL POINTER AS ARGUMENT\n", __FUNCTION__, __LINE__);return LGW_REG_ERROR;}
+#else
+ #define DEBUG_MSG(str)
+ #define DEBUG_PRINTF(fmt, args...)
+ #define CHECK_NULL(a) if(a==NULL){return LGW_REG_ERROR;}
+#endif
+
+/* -------------------------------------------------------------------------- */
+/* --- PRIVATE CONSTANTS ---------------------------------------------------- */
+
+/*
+auto generated register mapping for C code : 11-Jul-2013 13:20:40
+this file contains autogenerated C struct used to access the LoRa register from the Primer firmware
+this file is autogenerated from registers description
+293 registers are defined
+*/
+const struct lgw_reg_s fpga_regs[LGW_FPGA_TOTALREGS] = {
+ {-1,0,0,0,1,0,0}, /* SOFT_RESET */
+ {-1,0,1,0,4,1,0}, /* FPGA_FEATURE */
+ {-1,0,5,0,3,1,0}, /* LBT_INITIAL_FREQ */
+ {-1,1,0,0,8,1,0}, /* VERSION */
+ {-1,2,0,0,8,1,0}, /* FPGA_STATUS */
+ {-1,3,0,0,1,0,0}, /* FPGA_CTRL_FEATURE_START */
+ {-1,3,1,0,1,0,0}, /* FPGA_CTRL_RADIO_RESET */
+ {-1,3,2,0,1,0,0}, /* FPGA_CTRL_INPUT_SYNC_I */
+ {-1,3,3,0,1,0,0}, /* FPGA_CTRL_INPUT_SYNC_Q */
+ {-1,3,4,0,1,0,0}, /* FPGA_CTRL_OUTPUT_SYNC */
+ {-1,3,5,0,1,0,0}, /* FPGA_CTRL_INVERT_IQ */
+ {-1,3,6,0,1,0,0}, /* FPGA_CTRL_ACCESS_HISTO_MEM */
+ {-1,3,7,0,1,0,0}, /* FPGA_CTRL_CLEAR_HISTO_MEM */
+ {-1,4,0,0,8,0,0}, /* HISTO_RAM_ADDR */
+ {-1,5,0,0,8,1,0}, /* HISTO_RAM_DATA */
+ {-1,8,0,0,16,0,1000}, /* HISTO_NB_READ */
+ {-1,14,0,0,16,1,0}, /* LBT_TIMESTAMP_CH */
+ {-1,17,0,0,4,0,0}, /* LBT_TIMESTAMP_SELECT_CH */
+ {-1,18,0,0,8,0,0}, /* LBT_CH0_FREQ_OFFSET */
+ {-1,19,0,0,8,0,0}, /* LBT_CH1_FREQ_OFFSET */
+ {-1,20,0,0,8,0,0}, /* LBT_CH2_FREQ_OFFSET */
+ {-1,21,0,0,8,0,0}, /* LBT_CH3_FREQ_OFFSET */
+ {-1,22,0,0,8,0,0}, /* LBT_CH4_FREQ_OFFSET */
+ {-1,23,0,0,8,0,0}, /* LBT_CH5_FREQ_OFFSET */
+ {-1,24,0,0,8,0,0}, /* LBT_CH6_FREQ_OFFSET */
+ {-1,25,0,0,8,0,0}, /* LBT_CH7_FREQ_OFFSET */
+ {-1,26,0,0,8,0,0}, /* SCAN_FREQ_OFFSET */
+ {-1,28,0,0,1,0,0}, /* LBT_SCAN_TIME_CH0 */
+ {-1,28,1,0,1,0,0}, /* LBT_SCAN_TIME_CH1 */
+ {-1,28,2,0,1,0,0}, /* LBT_SCAN_TIME_CH2 */
+ {-1,28,3,0,1,0,0}, /* LBT_SCAN_TIME_CH3 */
+ {-1,28,4,0,1,0,0}, /* LBT_SCAN_TIME_CH4 */
+ {-1,28,5,0,1,0,0}, /* LBT_SCAN_TIME_CH5 */
+ {-1,28,6,0,1,0,0}, /* LBT_SCAN_TIME_CH6 */
+ {-1,28,7,0,1,0,0}, /* LBT_SCAN_TIME_CH7 */
+ {-1,30,0,0,8,0,160}, /* RSSI_TARGET */
+ {-1,31,0,0,24,0,0}, /* HISTO_SCAN_FREQ */
+ {-1,34,0,0,6,0,0} /* NOTCH_FREQ_OFFSET */
+};
+
+/* -------------------------------------------------------------------------- */
+/* --- INTERNAL SHARED VARIABLES -------------------------------------------- */
+
+extern void *lgw_spi_target; /*! generic pointer to the SPI device */
+extern uint8_t lgw_spi_mux_mode; /*! current SPI mux mode used */
+
+/* -------------------------------------------------------------------------- */
+/* --- PRIVATE VARIABLES ---------------------------------------------------- */
+static bool tx_notch_support = false;
+static uint8_t tx_notch_offset;
+
+/* -------------------------------------------------------------------------- */
+/* --- PRIVATE FUNCTIONS ---------------------------------------------------- */
+
+/* -------------------------------------------------------------------------- */
+/* --- PRIVATE FUNCTIONS DEFINITION ----------------------------------------- */
+
+/* -------------------------------------------------------------------------- */
+/* --- PUBLIC FUNCTIONS DEFINITION ------------------------------------------ */
+
+float lgw_fpga_get_tx_notch_delay(void) {
+ float tx_notch_delay;
+
+ if (tx_notch_support == false) {
+ return 0;
+ }
+
+ /* Notch filtering performed by FPGA adds a constant delay (group delay) that we need to compensate */
+ tx_notch_delay = (31.25 * ((64 + tx_notch_offset) / 2)) / 1E3; /* 32MHz => 31.25ns */
+
+ return tx_notch_delay;
+}
+
+/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
+
+int lgw_fpga_configure(uint32_t tx_notch_freq) {
+ int x;
+ int32_t val;
+ bool spectral_scan_support, lbt_support;
+
+ /* Check input parameters */
+ if ((tx_notch_freq < LGW_MIN_NOTCH_FREQ) || (tx_notch_freq > LGW_MAX_NOTCH_FREQ)) {
+ DEBUG_PRINTF("WARNING: FPGA TX notch frequency is out of range (%u - [%u..%u]), setting it to default (%u)\n", tx_notch_freq, LGW_MIN_NOTCH_FREQ, LGW_MAX_NOTCH_FREQ, LGW_DEFAULT_NOTCH_FREQ);
+ tx_notch_freq = LGW_DEFAULT_NOTCH_FREQ;
+ }
+
+ /* Get supported FPGA features */
+ printf("INFO: FPGA supported features:");
+ lgw_fpga_reg_r(LGW_FPGA_FEATURE, &val);
+ tx_notch_support = TAKE_N_BITS_FROM((uint8_t)val, 0, 1);
+ if (tx_notch_support == true) {
+ printf(" [TX filter] ");
+ }
+ spectral_scan_support = TAKE_N_BITS_FROM((uint8_t)val, 1, 1);
+ if (spectral_scan_support == true) {
+ printf(" [Spectral Scan] ");
+ }
+ lbt_support = TAKE_N_BITS_FROM((uint8_t)val, 2, 1);
+ if (lbt_support == true) {
+ printf(" [LBT] ");
+ }
+ printf("\n");
+
+ x = lgw_fpga_reg_w(LGW_FPGA_CTRL_INPUT_SYNC_I, 1);
+ x |= lgw_fpga_reg_w(LGW_FPGA_CTRL_INPUT_SYNC_Q, 1);
+ x |= lgw_fpga_reg_w(LGW_FPGA_CTRL_OUTPUT_SYNC, 0);
+ if (x != LGW_REG_SUCCESS) {
+ DEBUG_MSG("ERROR: Failed to configure FPGA TX synchro\n");
+ return LGW_REG_ERROR;
+ }
+
+ /* Required for Semtech AP2 reference design */
+ x = lgw_fpga_reg_w(LGW_FPGA_CTRL_INVERT_IQ, 1);
+ if (x != LGW_REG_SUCCESS) {
+ DEBUG_MSG("ERROR: Failed to configure FPGA polarity\n");
+ return LGW_REG_ERROR;
+ }
+
+ /* Configure TX notch filter */
+ if (tx_notch_support == true) {
+ tx_notch_offset = (32E6 / (2*tx_notch_freq)) - 64;
+ x = lgw_fpga_reg_w(LGW_FPGA_NOTCH_FREQ_OFFSET, (int32_t)tx_notch_offset);
+ if (x != LGW_REG_SUCCESS) {
+ DEBUG_MSG("ERROR: Failed to configure FPGA TX notch filter\n");
+ return LGW_REG_ERROR;
+ }
+
+ /* Readback to check that notch frequency is programmable */
+ x = lgw_fpga_reg_r(LGW_FPGA_NOTCH_FREQ_OFFSET, &val);
+ if (x != LGW_REG_SUCCESS) {
+ DEBUG_MSG("ERROR: Failed to read FPGA TX notch frequency\n");
+ return LGW_REG_ERROR;
+ }
+ if (val != tx_notch_offset) {
+ DEBUG_MSG("WARNING: TX notch filter frequency is not programmable (check your FPGA image)\n");
+ } else {
+ DEBUG_PRINTF("INFO: TX notch filter frequency set to %u (%i)\n", tx_notch_freq, tx_notch_offset);
+ }
+ }
+
+ return LGW_REG_SUCCESS;
+}
+
+/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
+
+/* Write to a register addressed by name */
+int lgw_fpga_reg_w(uint16_t register_id, int32_t reg_value) {
+ int spi_stat = LGW_SPI_SUCCESS;
+ struct lgw_reg_s r;
+
+ /* check input parameters */
+ if (register_id >= LGW_FPGA_TOTALREGS) {
+ DEBUG_MSG("ERROR: REGISTER NUMBER OUT OF DEFINED RANGE\n");
+ return LGW_REG_ERROR;
+ }
+
+ /* check if SPI is initialised */
+ if (lgw_spi_target == NULL) {
+ DEBUG_MSG("ERROR: CONCENTRATOR UNCONNECTED\n");
+ return LGW_REG_ERROR;
+ }
+
+ /* get register struct from the struct array */
+ r = fpga_regs[register_id];
+
+ /* reject write to read-only registers */
+ if (r.rdon == 1){
+ DEBUG_MSG("ERROR: TRYING TO WRITE A READ-ONLY REGISTER\n");
+ return LGW_REG_ERROR;
+ }
+
+ spi_stat += reg_w_align32(lgw_spi_target, LGW_SPI_MUX_MODE1, LGW_SPI_MUX_TARGET_FPGA, r, reg_value);
+
+ if (spi_stat != LGW_SPI_SUCCESS) {
+ DEBUG_MSG("ERROR: SPI ERROR DURING REGISTER WRITE\n");
+ return LGW_REG_ERROR;
+ } else {
+ return LGW_REG_SUCCESS;
+ }
+}
+
+/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
+
+/* Read to a register addressed by name */
+int lgw_fpga_reg_r(uint16_t register_id, int32_t *reg_value) {
+ int spi_stat = LGW_SPI_SUCCESS;
+ struct lgw_reg_s r;
+
+ /* check input parameters */
+ CHECK_NULL(reg_value);
+ if (register_id >= LGW_FPGA_TOTALREGS) {
+ DEBUG_MSG("ERROR: REGISTER NUMBER OUT OF DEFINED RANGE\n");
+ return LGW_REG_ERROR;
+ }
+
+ /* check if SPI is initialised */
+ if (lgw_spi_target == NULL) {
+ DEBUG_MSG("ERROR: CONCENTRATOR UNCONNECTED\n");
+ return LGW_REG_ERROR;
+ }
+
+ /* get register struct from the struct array */
+ r = fpga_regs[register_id];
+
+ spi_stat += reg_r_align32(lgw_spi_target, LGW_SPI_MUX_MODE1, LGW_SPI_MUX_TARGET_FPGA, r, reg_value);
+
+ if (spi_stat != LGW_SPI_SUCCESS) {
+ DEBUG_MSG("ERROR: SPI ERROR DURING REGISTER WRITE\n");
+ return LGW_REG_ERROR;
+ } else {
+ return LGW_REG_SUCCESS;
+ }
+}
+
+/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
+
+/* Point to a register by name and do a burst write */
+int lgw_fpga_reg_wb(uint16_t register_id, uint8_t *data, uint16_t size) {
+ int spi_stat = LGW_SPI_SUCCESS;
+ struct lgw_reg_s r;
+
+ /* check input parameters */
+ CHECK_NULL(data);
+ if (size == 0) {
+ DEBUG_MSG("ERROR: BURST OF NULL LENGTH\n");
+ return LGW_REG_ERROR;
+ }
+ if (register_id >= LGW_FPGA_TOTALREGS) {
+ DEBUG_MSG("ERROR: REGISTER NUMBER OUT OF DEFINED RANGE\n");
+ return LGW_REG_ERROR;
+ }
+
+ /* check if SPI is initialised */
+ if (lgw_spi_target == NULL) {
+ DEBUG_MSG("ERROR: CONCENTRATOR UNCONNECTED\n");
+ return LGW_REG_ERROR;
+ }
+
+ /* get register struct from the struct array */
+ r = fpga_regs[register_id];
+
+ /* reject write to read-only registers */
+ if (r.rdon == 1){
+ DEBUG_MSG("ERROR: TRYING TO BURST WRITE A READ-ONLY REGISTER\n");
+ return LGW_REG_ERROR;
+ }
+
+ /* do the burst write */
+ spi_stat += lgw_spi_wb(lgw_spi_target, LGW_SPI_MUX_MODE1, LGW_SPI_MUX_TARGET_FPGA, r.addr, data, size);
+
+ if (spi_stat != LGW_SPI_SUCCESS) {
+ DEBUG_MSG("ERROR: SPI ERROR DURING REGISTER BURST WRITE\n");
+ return LGW_REG_ERROR;
+ } else {
+ return LGW_REG_SUCCESS;
+ }
+}
+
+/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
+
+/* Point to a register by name and do a burst read */
+int lgw_fpga_reg_rb(uint16_t register_id, uint8_t *data, uint16_t size) {
+ int spi_stat = LGW_SPI_SUCCESS;
+ struct lgw_reg_s r;
+
+ /* check input parameters */
+ CHECK_NULL(data);
+ if (size == 0) {
+ DEBUG_MSG("ERROR: BURST OF NULL LENGTH\n");
+ return LGW_REG_ERROR;
+ }
+ if (register_id >= LGW_FPGA_TOTALREGS) {
+ DEBUG_MSG("ERROR: REGISTER NUMBER OUT OF DEFINED RANGE\n");
+ return LGW_REG_ERROR;
+ }
+
+ /* check if SPI is initialised */
+ if (lgw_spi_target == NULL) {
+ DEBUG_MSG("ERROR: CONCENTRATOR UNCONNECTED\n");
+ return LGW_REG_ERROR;
+ }
+
+ /* get register struct from the struct array */
+ r = fpga_regs[register_id];
+
+ /* do the burst read */
+ spi_stat += lgw_spi_rb(lgw_spi_target, LGW_SPI_MUX_MODE1, LGW_SPI_MUX_TARGET_FPGA, r.addr, data, size);
+
+ if (spi_stat != LGW_SPI_SUCCESS) {
+ DEBUG_MSG("ERROR: SPI ERROR DURING REGISTER BURST READ\n");
+ return LGW_REG_ERROR;
+ } else {
+ return LGW_REG_SUCCESS;
+ }
+}
+
+/* --- EOF ------------------------------------------------------------------ */
diff --git a/libloragw/src/loragw_gps.c b/libloragw/src/loragw_gps.c
new file mode 100644
index 0000000..c0e0ded
--- /dev/null
+++ b/libloragw/src/loragw_gps.c
@@ -0,0 +1,835 @@
+/*
+ / _____) _ | |
+( (____ _____ ____ _| |_ _____ ____| |__
+ \____ \| ___ | (_ _) ___ |/ ___) _ \
+ _____) ) ____| | | || |_| ____( (___| | | |
+(______/|_____)_|_|_| \__)_____)\____)_| |_|
+ (C)2013 Semtech-Cycleo
+
+Description:
+ Library of functions to manage a GNSS module (typically GPS) for accurate
+ timestamping of packets and synchronisation of gateways.
+ A limited set of module brands/models are supported.
+
+License: Revised BSD License, see LICENSE.TXT file include in the project
+Maintainer: Michael Coracin
+*/
+
+
+/* -------------------------------------------------------------------------- */
+/* --- DEPENDANCIES --------------------------------------------------------- */
+
+#define _GNU_SOURCE /* needed for qsort_r to be defined */
+#include <stdint.h> /* C99 types */
+#include <stdbool.h> /* bool type */
+#include <stdio.h> /* printf fprintf */
+#include <string.h> /* memcpy */
+
+#include <time.h> /* struct timespec */
+#include <fcntl.h> /* open */
+#include <termios.h> /* tcflush */
+#include <math.h> /* modf */
+
+#include <stdlib.h>
+
+#include "loragw_gps.h"
+
+/* -------------------------------------------------------------------------- */
+/* --- PRIVATE MACROS ------------------------------------------------------- */
+
+#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
+#if DEBUG_GPS == 1
+ #define DEBUG_MSG(args...) fprintf(stderr, args)
+ #define DEBUG_ARRAY(a,b,c) for(a=0;a<b;++a) fprintf(stderr,"%x.",c[a]);fprintf(stderr,"end\n")
+ #define CHECK_NULL(a) if(a==NULL){fprintf(stderr,"%s:%d: ERROR: NULL POINTER AS ARGUMENT\n", __FUNCTION__, __LINE__);return LGW_GPS_ERROR;}
+#else
+ #define DEBUG_MSG(args...)
+ #define DEBUG_ARRAY(a,b,c) for(a=0;a!=0;){}
+ #define CHECK_NULL(a) if(a==NULL){return LGW_GPS_ERROR;}
+#endif
+#define TRACE() fprintf(stderr, "@ %s %d\n", __FUNCTION__, __LINE__);
+
+/* -------------------------------------------------------------------------- */
+/* --- PRIVATE CONSTANTS ---------------------------------------------------- */
+
+#define TS_CPS 1E6 /* count-per-second of the timestamp counter */
+#define PLUS_10PPM 1.00001
+#define MINUS_10PPM 0.99999
+#define DEFAULT_BAUDRATE B9600
+
+#define UBX_MSG_NAVTIMEGPS_LEN 16
+
+/* -------------------------------------------------------------------------- */
+/* --- PRIVATE VARIABLES ---------------------------------------------------- */
+
+
+/* result of the NMEA parsing */
+static short gps_yea = 0; /* year (2 or 4 digits) */
+static short gps_mon = 0; /* month (1-12) */
+static short gps_day = 0; /* day of the month (1-31) */
+static short gps_hou = 0; /* hours (0-23) */
+static short gps_min = 0; /* minutes (0-59) */
+static short gps_sec = 0; /* seconds (0-60)(60 is for leap second) */
+static float gps_fra = 0.0; /* fractions of seconds (<1) */
+static bool gps_time_ok = false;
+static int16_t gps_week = 0; /* GPS week number of the navigation epoch */
+static uint32_t gps_iTOW = 0; /* GPS time of week in milliseconds */
+static int32_t gps_fTOW = 0; /* Fractional part of iTOW (+/-500000) in nanosec */
+
+static short gps_dla = 0; /* degrees of latitude */
+static double gps_mla = 0.0; /* minutes of latitude */
+static char gps_ola = 0; /* orientation (N-S) of latitude */
+static short gps_dlo = 0; /* degrees of longitude */
+static double gps_mlo = 0.0; /* minutes of longitude */
+static char gps_olo = 0; /* orientation (E-W) of longitude */
+static short gps_alt = 0; /* altitude */
+static bool gps_pos_ok = false;
+
+static char gps_mod = 'N'; /* GPS mode (N no fix, A autonomous, D differential) */
+static short gps_sat = 0; /* number of satellites used for fix */
+
+static struct termios ttyopt_restore;
+
+/* -------------------------------------------------------------------------- */
+/* --- PRIVATE FUNCTIONS DECLARATION ---------------------------------------- */
+
+static int nmea_checksum(const char *nmea_string, int buff_size, char *checksum);
+
+static char nibble_to_hexchar(uint8_t a);
+
+static bool validate_nmea_checksum(const char *serial_buff, int buff_size);
+
+static bool match_label(const char *s, char *label, int size, char wildcard);
+
+static int str_chop(char *s, int buff_size, char separator, int *idx_ary, int max_idx);
+
+/* -------------------------------------------------------------------------- */
+/* --- PRIVATE FUNCTIONS DEFINITION ----------------------------------------- */
+
+/*
+Calculate the checksum for a NMEA string
+Skip the first '$' if necessary and calculate checksum until '*' character is
+reached (or buff_size exceeded).
+Checksum must point to a 2-byte (or more) char array.
+Return position of the checksum in the string
+*/
+static int nmea_checksum(const char *nmea_string, int buff_size, char *checksum) {
+ int i = 0;
+ uint8_t check_num = 0;
+
+ /* check input parameters */
+ if ((nmea_string == NULL) || (checksum == NULL) || (buff_size <= 1)) {
+ DEBUG_MSG("Invalid parameters for nmea_checksum\n");
+ return -1;
+ }
+
+ /* skip the first '$' if necessary */
+ if (nmea_string[i] == '$') {
+ i += 1;
+ }
+
+ /* xor until '*' or max length is reached */
+ while (nmea_string[i] != '*') {
+ check_num ^= nmea_string[i];
+ i += 1;
+ if (i >= buff_size) {
+ DEBUG_MSG("Maximum length reached for nmea_checksum\n");
+ return -1;
+ }
+ }
+
+ /* Convert checksum value to 2 hexadecimal characters */
+ checksum[0] = nibble_to_hexchar(check_num / 16); /* upper nibble */
+ checksum[1] = nibble_to_hexchar(check_num % 16); /* lower nibble */
+
+ return i + 1;
+}
+
+/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
+
+static char nibble_to_hexchar(uint8_t a) {
+ if (a < 10) {
+ return '0' + a;
+ } else if (a < 16) {
+ return 'A' + (a-10);
+ } else {
+ return '?';
+ }
+}
+
+/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
+
+/*
+Calculate the checksum of a NMEA frame and compare it to the checksum that is
+present at the end of it.
+Return true if it matches
+*/
+static bool validate_nmea_checksum(const char *serial_buff, int buff_size) {
+ int checksum_index;
+ char checksum[2]; /* 2 characters to calculate NMEA checksum */
+
+ checksum_index = nmea_checksum(serial_buff, buff_size, checksum);
+
+ /* could we calculate a verification checksum ? */
+ if (checksum_index < 0) {
+ DEBUG_MSG("ERROR: IMPOSSIBLE TO PARSE NMEA SENTENCE\n");
+ return false;
+ }
+
+ /* check if there are enough char in the serial buffer to read checksum */
+ if (checksum_index >= (buff_size - 2)) {
+ DEBUG_MSG("ERROR: IMPOSSIBLE TO READ NMEA SENTENCE CHECKSUM\n");
+ return false;
+ }
+
+ /* check the checksum per se */
+ if ((serial_buff[checksum_index] == checksum[0]) && (serial_buff[checksum_index+1] == checksum[1])) {
+ return true;
+ } else {
+ DEBUG_MSG("ERROR: NMEA CHECKSUM %c%c DOESN'T MATCH VERIFICATION CHECKSUM %c%c\n", serial_buff[checksum_index], serial_buff[checksum_index+1], checksum[0], checksum[1]);
+ return false;
+ }
+}
+
+/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
+
+/*
+Return true if the "label" string (can contain wildcard characters) matches
+the begining of the "s" string
+*/
+static bool match_label(const char *s, char *label, int size, char wildcard) {
+ int i;
+
+ for (i=0; i < size; i++) {
+ if (label[i] == wildcard) continue;
+ if (label[i] != s[i]) return false;
+ }
+ return true;
+}
+
+/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
+
+/*
+Chop a string into smaller strings
+Replace every separator in the input character buffer by a null character so
+that all s[index] are valid strings.
+Populate an array of integer 'idx_ary' representing indexes of token in the
+string.
+buff_size and max_idx are there to prevent segfaults.
+Return the number of token found (number of idx_ary filled).
+*/
+int str_chop(char *s, int buff_size, char separator, int *idx_ary, int max_idx) {
+ int i = 0; /* index in the string */
+ int j = 0; /* index in the result array */
+
+ if ((s == NULL) || (buff_size < 0) || (separator == 0) || (idx_ary == NULL) || (max_idx < 0)) {
+ /* unsafe to do anything */
+ return -1;
+ }
+ if ((buff_size == 0) || (max_idx == 0)) {
+ /* nothing to do */
+ return 0;
+ }
+ s[buff_size - 1] = 0; /* add string terminator at the end of the buffer, just to be sure */
+ idx_ary[j] = 0;
+ j += 1;
+ /* loop until string terminator is reached */
+ while (s[i] != 0) {
+ if (s[i] == separator) {
+ s[i] = 0; /* replace separator by string terminator */
+ if (j >= max_idx) { /* no more room in the index array */
+ return j;
+ }
+ idx_ary[j] = i+1; /* next token start after replaced separator */
+ ++j;
+ }
+ ++i;
+ }
+ return j;
+}
+
+/* -------------------------------------------------------------------------- */
+/* --- PUBLIC FUNCTIONS DEFINITION ------------------------------------------ */
+
+int lgw_gps_enable(char *tty_path, char *gps_family, speed_t target_brate, int *fd_ptr) {
+ int i;
+ struct termios ttyopt; /* serial port options */
+ int gps_tty_dev; /* file descriptor to the serial port of the GNSS module */
+ uint8_t ubx_cmd_timegps[UBX_MSG_NAVTIMEGPS_LEN] = {
+ 0xB5, 0x62, /* UBX Sync Chars */
+ 0x06, 0x01, /* CFG-MSG Class/ID */
+ 0x08, 0x00, /* Payload length */
+ 0x01, 0x20, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, /* Enable NAV-TIMEGPS output on serial */
+ 0x32, 0x94 }; /* Checksum */
+ ssize_t num_written;
+
+ /* check input parameters */
+ CHECK_NULL(tty_path);
+ CHECK_NULL(fd_ptr);
+
+ /* open TTY device */
+ gps_tty_dev = open(tty_path, O_RDWR | O_NOCTTY);
+ if (gps_tty_dev <= 0) {
+ DEBUG_MSG("ERROR: TTY PORT FAIL TO OPEN, CHECK PATH AND ACCESS RIGHTS\n");
+ return LGW_GPS_ERROR;
+ }
+ *fd_ptr = gps_tty_dev;
+
+ /* manage the different GPS modules families */
+ if (gps_family == NULL) {
+ DEBUG_MSG("WARNING: this version of GPS module may not be supported\n");
+ } else if (strncmp(gps_family, "ubx7", 4) != 0) {
+ /* The current implementation relies on proprietary messages from U-Blox */
+ /* GPS modules (UBX, NAV-TIMEGPS...) and has only be tested with a u-blox 7. */
+ /* Those messages allow to get NATIVE GPS time (no leap seconds) required */
+ /* for class-B handling and GPS synchronization */
+ /* see lgw_parse_ubx() function for details */
+ DEBUG_MSG("WARNING: this version of GPS module may not be supported\n");
+ }
+
+ /* manage the target bitrate */
+ if (target_brate != 0) {
+ DEBUG_MSG("WARNING: target_brate parameter ignored for now\n"); // TODO
+ }
+
+ /* get actual serial port configuration */
+ i = tcgetattr(gps_tty_dev, &ttyopt);
+ if (i != 0) {
+ DEBUG_MSG("ERROR: IMPOSSIBLE TO GET TTY PORT CONFIGURATION\n");
+ return LGW_GPS_ERROR;
+ }
+
+ /* Save current serial port configuration for restoring later */
+ memcpy(&ttyopt_restore, &ttyopt, sizeof ttyopt);
+
+ /* update baudrates */
+ cfsetispeed(&ttyopt, DEFAULT_BAUDRATE);
+ cfsetospeed(&ttyopt, DEFAULT_BAUDRATE);
+
+ /* update terminal parameters */
+ /* The following configuration should allow to:
+ - Get ASCII NMEA messages
+ - Get UBX binary messages
+ - Send UBX binary commands
+ Note: as binary data have to be read/written, we need to disable
+ various character processing to avoid loosing data */
+ /* Control Modes */
+ ttyopt.c_cflag |= CLOCAL; /* local connection, no modem control */
+ ttyopt.c_cflag |= CREAD; /* enable receiving characters */
+ ttyopt.c_cflag |= CS8; /* 8 bit frames */
+ ttyopt.c_cflag &= ~PARENB; /* no parity */
+ ttyopt.c_cflag &= ~CSTOPB; /* one stop bit */
+ /* Input Modes */
+ ttyopt.c_iflag |= IGNPAR; /* ignore bytes with parity errors */
+ ttyopt.c_iflag &= ~ICRNL; /* do not map CR to NL on input*/
+ ttyopt.c_iflag &= ~IGNCR; /* do not ignore carriage return on input */
+ ttyopt.c_iflag &= ~IXON; /* disable Start/Stop output control */
+ ttyopt.c_iflag &= ~IXOFF; /* do not send Start/Stop characters */
+ /* Output Modes */
+ ttyopt.c_oflag = 0; /* disable everything on output as we only write binary */
+ /* Local Modes */
+ ttyopt.c_lflag &= ~ICANON; /* disable canonical input - cannot use with binary input */
+ ttyopt.c_lflag &= ~ISIG; /* disable check for INTR, QUIT, SUSP special characters */
+ ttyopt.c_lflag &= ~IEXTEN; /* disable any special control character */
+ ttyopt.c_lflag &= ~ECHO; /* do not echo back every character typed */
+ ttyopt.c_lflag &= ~ECHOE; /* does not erase the last character in current line */
+ ttyopt.c_lflag &= ~ECHOK; /* do not echo NL after KILL character */
+
+ /* settings for non-canonical mode
+ read will block for until the lesser of VMIN or requested chars have been received */
+ ttyopt.c_cc[VMIN] = LGW_GPS_MIN_MSG_SIZE;
+ ttyopt.c_cc[VTIME] = 0;
+
+ /* set new serial ports parameters */
+ i = tcsetattr(gps_tty_dev, TCSANOW, &ttyopt);
+ if (i != 0){
+ DEBUG_MSG("ERROR: IMPOSSIBLE TO UPDATE TTY PORT CONFIGURATION\n");
+ return LGW_GPS_ERROR;
+ }
+ tcflush(gps_tty_dev, TCIOFLUSH);
+
+ /* Send UBX CFG NAV-TIMEGPS message to tell GPS module to output native GPS time */
+ /* This is a binary message, serial port has to be properly configured to handle this */
+ num_written = write (gps_tty_dev, ubx_cmd_timegps, UBX_MSG_NAVTIMEGPS_LEN);
+ if (num_written != UBX_MSG_NAVTIMEGPS_LEN) {
+ DEBUG_MSG("ERROR: Failed to write on serial port (written=%d)\n", (int) num_written);
+ }
+
+ /* get timezone info */
+ tzset();
+
+ /* initialize global variables */
+ gps_time_ok = false;
+ gps_pos_ok = false;
+ gps_mod = 'N';
+
+ return LGW_GPS_SUCCESS;
+}
+
+/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
+
+int lgw_gps_disable(int fd) {
+ int i;
+
+ /* restore serial ports parameters */
+ i = tcsetattr(fd, TCSANOW, &ttyopt_restore);
+ if (i != 0){
+ DEBUG_MSG("ERROR: IMPOSSIBLE TO RESTORE TTY PORT CONFIGURATION\n");
+ return LGW_GPS_ERROR;
+ }
+ tcflush(fd, TCIOFLUSH);
+
+ i = close(fd);
+ if (i <= 0) {
+ DEBUG_MSG("ERROR: TTY PORT FAIL TO CLOSE\n");
+ return LGW_GPS_ERROR;
+ }
+
+ return LGW_GPS_SUCCESS;
+}
+
+/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
+
+enum gps_msg lgw_parse_ubx(const char *serial_buff, size_t buff_size, size_t *msg_size) {
+ bool valid = 0; /* iTOW, fTOW and week validity */
+ unsigned int payload_length;
+ uint8_t ck_a, ck_b;
+ uint8_t ck_a_rcv, ck_b_rcv;
+ unsigned int i;
+
+ *msg_size = 0; /* ensure msg_size alway receives a value */
+
+ /* check input parameters */
+ if (serial_buff == NULL) {
+ return IGNORED;
+ }
+ if (buff_size < 8) {
+ DEBUG_MSG("ERROR: TOO SHORT TO BE A VALID UBX MESSAGE\n");
+ return IGNORED;
+ }
+
+ /* display received serial data and checksum */
+ DEBUG_MSG("Note: parsing UBX frame> ");
+ for (i=0; i<buff_size; i++) {
+ DEBUG_MSG("%02x ", serial_buff[i]);
+ }
+ DEBUG_MSG("\n");
+
+ /* Check for UBX sync chars 0xB5 0x62 */
+ if ((serial_buff[0] == (char)0xB5) && (serial_buff[1] == (char)0x62)) {
+
+ /* Get payload length to compute message size */
+ payload_length = (uint8_t)serial_buff[4];
+ payload_length |= (uint8_t)serial_buff[5] << 8;
+ *msg_size = 6 + payload_length + 2; /* header + payload + checksum */
+
+ /* check for complete message in buffer */
+ if(*msg_size <= buff_size) {
+ /* Validate checksum of message */
+ ck_a_rcv = serial_buff[*msg_size-2]; /* received checksum */
+ ck_b_rcv = serial_buff[*msg_size-1]; /* received checksum */
+ /* Use 8-bit Fletcher Algorithm to compute checksum of actual payload */
+ ck_a = 0; ck_b = 0;
+ for (i=0; i<(4 + payload_length); i++) {
+ ck_a = ck_a + serial_buff[i+2];
+ ck_b = ck_b + ck_a;
+ }
+
+ /* Compare checksums and parse if OK */
+ if ((ck_a == ck_a_rcv) && (ck_b == ck_b_rcv)) {
+ /* Check for Class 0x01 (NAV) and ID 0x20 (NAV-TIMEGPS) */
+ if ((serial_buff[2] == 0x01) && (serial_buff[3] == 0x20)) {
+ /* Check validity of information */
+ valid = serial_buff[17] & 0x3; /* towValid, weekValid */
+ if (valid) {
+ /* Parse buffer to extract GPS time */
+ /* Warning: payload byte ordering is Little Endian */
+ gps_iTOW = (uint8_t)serial_buff[6];
+ gps_iTOW |= (uint8_t)serial_buff[7] << 8;
+ gps_iTOW |= (uint8_t)serial_buff[8] << 16;
+ gps_iTOW |= (uint8_t)serial_buff[9] << 24; /* GPS time of week, in ms */
+
+ gps_fTOW = (uint8_t)serial_buff[10];
+ gps_fTOW |= (uint8_t)serial_buff[11] << 8;
+ gps_fTOW |= (uint8_t)serial_buff[12] << 16;
+ gps_fTOW |= (uint8_t)serial_buff[13] << 24; /* Fractional part of iTOW, in ns */
+
+ gps_week = (uint8_t)serial_buff[14];
+ gps_week |= (uint8_t)serial_buff[15] << 8; /* GPS week number */
+
+ gps_time_ok = true;
+#if 0
+ /* For debug */
+ {
+ short ubx_gps_hou = 0; /* hours (0-23) */
+ short ubx_gps_min = 0; /* minutes (0-59) */
+ short ubx_gps_sec = 0; /* seconds (0-59) */
+
+ /* Format GPS time in hh:mm:ss based on iTOW */
+ ubx_gps_sec = (gps_iTOW / 1000) % 60;
+ ubx_gps_min = (gps_iTOW / 1000 / 60) % 60;
+ ubx_gps_hou = (gps_iTOW / 1000 / 60 / 60) % 24;
+ printf(" GPS time = %02d:%02d:%02d\n", ubx_gps_hou, ubx_gps_min, ubx_gps_sec);
+ }
+#endif
+ } else { /* valid */
+ gps_time_ok = false;
+ }
+
+ return UBX_NAV_TIMEGPS;
+ } else if ((serial_buff[2] == 0x05) && (serial_buff[3] == 0x00)) {
+ DEBUG_MSG("NOTE: UBX ACK-NAK received\n");
+ return IGNORED;
+ } else if ((serial_buff[2] == 0x05) && (serial_buff[3] == 0x01)) {
+ DEBUG_MSG("NOTE: UBX ACK-ACK received\n");
+ return IGNORED;
+ } else { /* not a supported message */
+ DEBUG_MSG("ERROR: UBX message is not supported (%02x %02x)\n", serial_buff[2], serial_buff[3]);
+ return IGNORED;
+ }
+ } else { /* checksum failed */
+ DEBUG_MSG("ERROR: UBX message is corrupted, checksum failed\n");
+ return INVALID;
+ }
+ } else { /* message contains less bytes than indicated by header */
+ DEBUG_MSG("ERROR: UBX message incomplete\n");
+ return INCOMPLETE;
+ }
+ } else { /* Not a UBX message */
+ /* Ignore messages which are not UBX ones for now */
+ return IGNORED;
+ }
+}
+
+/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
+
+enum gps_msg lgw_parse_nmea(const char *serial_buff, int buff_size) {
+ int i, j, k;
+ int str_index[30]; /* string index from the string chopping */
+ int nb_fields; /* number of strings detected by string chopping */
+ char parser_buf[256]; /* parsing modifies buffer so need a local copy */
+
+ /* check input parameters */
+ if (serial_buff == NULL) {
+ return UNKNOWN;
+ }
+
+ if(buff_size > (int)(sizeof(parser_buf) - 1)) {
+ DEBUG_MSG("Note: input string to big for parsing\n");
+ return INVALID;
+ }
+
+ /* look for some NMEA sentences in particular */
+ if (buff_size < 8) {
+ DEBUG_MSG("ERROR: TOO SHORT TO BE A VALID NMEA SENTENCE\n");
+ return UNKNOWN;
+ } else if (!validate_nmea_checksum(serial_buff, buff_size)) {
+ DEBUG_MSG("Warning: invalid NMEA sentence (bad checksum)\n");
+ return INVALID;
+ } else if (match_label(serial_buff, "$G?RMC", 6, '?')) {
+ /*
+ NMEA sentence format: $xxRMC,time,status,lat,NS,long,EW,spd,cog,date,mv,mvEW,posMode*cs<CR><LF>
+ Valid fix: $GPRMC,083559.34,A,4717.11437,N,00833.91522,E,0.004,77.52,091202,,,A*00
+ No fix: $GPRMC,,V,,,,,,,,,,N*00
+ */
+ memcpy(parser_buf, serial_buff, buff_size);
+ parser_buf[buff_size] = '\0';
+ nb_fields = str_chop(parser_buf, buff_size, ',', str_index, ARRAY_SIZE(str_index));
+ if (nb_fields != 13) {
+ DEBUG_MSG("Warning: invalid RMC sentence (number of fields)\n");
+ return IGNORED;
+ }
+ /* parse GPS status */
+ gps_mod = *(parser_buf + str_index[12]); /* get first character, no need to bother with sscanf */
+ if ((gps_mod != 'N') && (gps_mod != 'A') && (gps_mod != 'D')) {
+ gps_mod = 'N';
+ }
+ /* parse complete time */
+ i = sscanf(parser_buf + str_index[1], "%2hd%2hd%2hd%4f", &gps_hou, &gps_min, &gps_sec, &gps_fra);
+ j = sscanf(parser_buf + str_index[9], "%2hd%2hd%2hd", &gps_day, &gps_mon, &gps_yea);
+ if ((i == 4) && (j == 3)) {
+ if ((gps_mod == 'A') || (gps_mod == 'D')) {
+ gps_time_ok = true;
+ DEBUG_MSG("Note: Valid RMC sentence, GPS locked, date: 20%02d-%02d-%02dT%02d:%02d:%06.3fZ\n", gps_yea, gps_mon, gps_day, gps_hou, gps_min, gps_fra + (float)gps_sec);
+ } else {
+ gps_time_ok = false;
+ DEBUG_MSG("Note: Valid RMC sentence, no satellite fix, estimated date: 20%02d-%02d-%02dT%02d:%02d:%06.3fZ\n", gps_yea, gps_mon, gps_day, gps_hou, gps_min, gps_fra + (float)gps_sec);
+ }
+ } else {
+ /* could not get a valid hour AND date */
+ gps_time_ok = false;
+ DEBUG_MSG("Note: Valid RMC sentence, mode %c, no date\n", gps_mod);
+ }
+ return NMEA_RMC;
+ } else if (match_label(serial_buff, "$G?GGA", 6, '?')) {
+ /*
+ NMEA sentence format: $xxGGA,time,lat,NS,long,EW,quality,numSV,HDOP,alt,M,sep,M,diffAge,diffStation*cs<CR><LF>
+ Valid fix: $GPGGA,092725.00,4717.11399,N,00833.91590,E,1,08,1.01,499.6,M,48.0,M,,*5B
+ */
+ memcpy(parser_buf, serial_buff, buff_size);
+ parser_buf[buff_size] = '\0';
+ nb_fields = str_chop(parser_buf, buff_size, ',', str_index, ARRAY_SIZE(str_index));
+ if (nb_fields != 15) {
+ DEBUG_MSG("Warning: invalid GGA sentence (number of fields)\n");
+ return IGNORED;
+ }
+ /* parse number of satellites used for fix */
+ sscanf(parser_buf + str_index[7], "%hd", &gps_sat);
+ /* parse 3D coordinates */
+ i = sscanf(parser_buf + str_index[2], "%2hd%10lf", &gps_dla, &gps_mla);
+ gps_ola = *(parser_buf + str_index[3]);
+ j = sscanf(parser_buf + str_index[4], "%3hd%10lf", &gps_dlo, &gps_mlo);
+ gps_olo = *(parser_buf + str_index[5]);
+ k = sscanf(parser_buf + str_index[9], "%hd", &gps_alt);
+ if ((i == 2) && (j == 2) && (k == 1) && ((gps_ola=='N')||(gps_ola=='S')) && ((gps_olo=='E')||(gps_olo=='W'))) {
+ gps_pos_ok = true;
+ DEBUG_MSG("Note: Valid GGA sentence, %d sat, lat %02ddeg %06.3fmin %c, lon %03ddeg%06.3fmin %c, alt %d\n", gps_sat, gps_dla, gps_mla, gps_ola, gps_dlo, gps_mlo, gps_olo, gps_alt);
+ } else {
+ /* could not get a valid latitude, longitude AND altitude */
+ gps_pos_ok = false;
+ DEBUG_MSG("Note: Valid GGA sentence, %d sat, no coordinates\n", gps_sat);
+ }
+ return NMEA_GGA;
+ } else {
+ DEBUG_MSG("Note: ignored NMEA sentence\n"); /* quite verbose */
+ return IGNORED;
+ }
+}
+
+/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
+
+int lgw_gps_get(struct timespec *utc, struct timespec *gps_time, struct coord_s *loc, struct coord_s *err) {
+ struct tm x;
+ time_t y;
+ double intpart, fractpart;
+
+ if (utc != NULL) {
+ if (!gps_time_ok) {
+ DEBUG_MSG("ERROR: NO VALID TIME TO RETURN\n");
+ return LGW_GPS_ERROR;
+ }
+ memset(&x, 0, sizeof(x));
+ if (gps_yea < 100) { /* 2-digits year, 20xx */
+ x.tm_year = gps_yea + 100; /* 100 years offset to 1900 */
+ } else { /* 4-digits year, Gregorian calendar */
+ x.tm_year = gps_yea - 1900;
+ }
+ x.tm_mon = gps_mon - 1; /* tm_mon is [0,11], gps_mon is [1,12] */
+ x.tm_mday = gps_day;
+ x.tm_hour = gps_hou;
+ x.tm_min = gps_min;
+ x.tm_sec = gps_sec;
+ y = mktime(&x) - timezone; /* need to substract timezone bc mktime assumes time vector is local time */
+ if (y == (time_t)(-1)) {
+ DEBUG_MSG("ERROR: FAILED TO CONVERT BROKEN-DOWN TIME\n");
+ return LGW_GPS_ERROR;
+ }
+ utc->tv_sec = y;
+ utc->tv_nsec = (int32_t)(gps_fra * 1e9);
+ }
+ if (gps_time != NULL) {
+ if (!gps_time_ok) {
+ DEBUG_MSG("ERROR: NO VALID TIME TO RETURN\n");
+ return LGW_GPS_ERROR;
+ }
+ fractpart = modf(((double)gps_iTOW / 1E3) + ((double)gps_fTOW / 1E9), &intpart);
+ /* Number of seconds since beginning on current GPS week */
+ gps_time->tv_sec = (time_t)intpart;
+ /* Number of seconds since GPS epoch 06.Jan.1980 */
+ gps_time->tv_sec += (time_t)gps_week * 604800; /* day*hours*minutes*secondes: 7*24*60*60; */
+ /* Fractional part in nanoseconds */
+ gps_time->tv_nsec = (long)(fractpart * 1E9);
+ }
+ if (loc != NULL) {
+ if (!gps_pos_ok) {
+ DEBUG_MSG("ERROR: NO VALID POSITION TO RETURN\n");
+ return LGW_GPS_ERROR;
+ }
+ loc->lat = ((double)gps_dla + (gps_mla/60.0)) * ((gps_ola == 'N')?1.0:-1.0);
+ loc->lon = ((double)gps_dlo + (gps_mlo/60.0)) * ((gps_olo == 'E')?1.0:-1.0);
+ loc->alt = gps_alt;
+ }
+ if (err != NULL) {
+ DEBUG_MSG("Warning: localization error processing not implemented yet\n");
+ err->lat = 0.0;
+ err->lon = 0.0;
+ err->alt = 0;
+ }
+
+ return LGW_GPS_SUCCESS;
+}
+
+/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
+
+int lgw_gps_sync(struct tref *ref, uint32_t count_us, struct timespec utc, struct timespec gps_time) {
+ double cnt_diff; /* internal concentrator time difference (in seconds) */
+ double utc_diff; /* UTC time difference (in seconds) */
+ double slope; /* time slope between new reference and old reference (for sanity check) */
+
+ bool aber_n0; /* is the update value for synchronization aberrant or not ? */
+ static bool aber_min1 = false; /* keep track of whether value at sync N-1 was aberrant or not */
+ static bool aber_min2 = false; /* keep track of whether value at sync N-2 was aberrant or not */
+
+ CHECK_NULL(ref);
+
+ /* calculate the slope */
+
+ cnt_diff = (double)(count_us - ref->count_us) / (double)(TS_CPS); /* uncorrected by xtal_err */
+ utc_diff = (double)(utc.tv_sec - (ref->utc).tv_sec) + (1E-9 * (double)(utc.tv_nsec - (ref->utc).tv_nsec));
+
+ /* detect aberrant points by measuring if slope limits are exceeded */
+ if (utc_diff != 0) { // prevent divide by zero
+ slope = cnt_diff/utc_diff;
+ if ((slope > PLUS_10PPM) || (slope < MINUS_10PPM)) {
+ DEBUG_MSG("Warning: correction range exceeded\n");
+ aber_n0 = true;
+ } else {
+ aber_n0 = false;
+ }
+ } else {
+ DEBUG_MSG("Warning: aberrant UTC value for synchronization\n");
+ aber_n0 = true;
+ }
+
+ /* watch if the 3 latest sync point were aberrant or not */
+ if (aber_n0 == false) {
+ /* value no aberrant -> sync with smoothed slope */
+ ref->systime = time(NULL);
+ ref->count_us = count_us;
+ ref->utc.tv_sec = utc.tv_sec;
+ ref->utc.tv_nsec = utc.tv_nsec;
+ ref->gps.tv_sec = gps_time.tv_sec;
+ ref->gps.tv_nsec = gps_time.tv_nsec;
+ ref->xtal_err = slope;
+ aber_min2 = aber_min1;
+ aber_min1 = aber_n0;
+ return LGW_GPS_SUCCESS;
+ } else if (aber_n0 && aber_min1 && aber_min2) {
+ /* 3 successive aberrant values -> sync reset (keep xtal_err) */
+ ref->systime = time(NULL);
+ ref->count_us = count_us;
+ ref->utc.tv_sec = utc.tv_sec;
+ ref->utc.tv_nsec = utc.tv_nsec;
+ ref->gps.tv_sec = gps_time.tv_sec;
+ ref->gps.tv_nsec = gps_time.tv_nsec;
+ /* reset xtal_err only if the present value is out of range */
+ if ((ref->xtal_err > PLUS_10PPM) || (ref->xtal_err < MINUS_10PPM)) {
+ ref->xtal_err = 1.0;
+ }
+ DEBUG_MSG("Warning: 3 successive aberrant sync attempts, sync reset\n");
+ aber_min2 = aber_min1;
+ aber_min1 = aber_n0;
+ return LGW_GPS_SUCCESS;
+ } else {
+ /* only 1 or 2 successive aberrant values -> ignore and return an error */
+ aber_min2 = aber_min1;
+ aber_min1 = aber_n0;
+ return LGW_GPS_ERROR;
+ }
+
+ return LGW_GPS_SUCCESS;
+}
+
+/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
+
+int lgw_cnt2utc(struct tref ref, uint32_t count_us, struct timespec *utc) {
+ double delta_sec;
+ double intpart, fractpart;
+ long tmp;
+
+ CHECK_NULL(utc);
+ if ((ref.systime == 0) || (ref.xtal_err > PLUS_10PPM) || (ref.xtal_err < MINUS_10PPM)) {
+ DEBUG_MSG("ERROR: INVALID REFERENCE FOR CNT -> UTC CONVERSION\n");
+ return LGW_GPS_ERROR;
+ }
+
+ /* calculate delta in seconds between reference count_us and target count_us */
+ delta_sec = (double)(count_us - ref.count_us) / (TS_CPS * ref.xtal_err);
+
+ /* now add that delta to reference UTC time */
+ fractpart = modf (delta_sec , &intpart);
+ tmp = ref.utc.tv_nsec + (long)(fractpart * 1E9);
+ if (tmp < (long)1E9) { /* the nanosecond part doesn't overflow */
+ utc->tv_sec = ref.utc.tv_sec + (time_t)intpart;
+ utc->tv_nsec = tmp;
+ } else { /* must carry one second */
+ utc->tv_sec = ref.utc.tv_sec + (time_t)intpart + 1;
+ utc->tv_nsec = tmp - (long)1E9;
+ }
+
+ return LGW_GPS_SUCCESS;
+}
+
+/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
+
+int lgw_utc2cnt(struct tref ref, struct timespec utc, uint32_t *count_us) {
+ double delta_sec;
+
+ CHECK_NULL(count_us);
+ if ((ref.systime == 0) || (ref.xtal_err > PLUS_10PPM) || (ref.xtal_err < MINUS_10PPM)) {
+ DEBUG_MSG("ERROR: INVALID REFERENCE FOR UTC -> CNT CONVERSION\n");
+ return LGW_GPS_ERROR;
+ }
+
+ /* calculate delta in seconds between reference utc and target utc */
+ delta_sec = (double)(utc.tv_sec - ref.utc.tv_sec);
+ delta_sec += 1E-9 * (double)(utc.tv_nsec - ref.utc.tv_nsec);
+
+ /* now convert that to internal counter tics and add that to reference counter value */
+ *count_us = ref.count_us + (uint32_t)(delta_sec * TS_CPS * ref.xtal_err);
+
+ return LGW_GPS_SUCCESS;
+}
+
+/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
+
+int lgw_cnt2gps(struct tref ref, uint32_t count_us, struct timespec *gps_time) {
+ double delta_sec;
+ double intpart, fractpart;
+ long tmp;
+
+ CHECK_NULL(gps_time);
+ if ((ref.systime == 0) || (ref.xtal_err > PLUS_10PPM) || (ref.xtal_err < MINUS_10PPM)) {
+ DEBUG_MSG("ERROR: INVALID REFERENCE FOR CNT -> GPS CONVERSION\n");
+ return LGW_GPS_ERROR;
+ }
+
+ /* calculate delta in milliseconds between reference count_us and target count_us */
+ delta_sec = (double)(count_us - ref.count_us) / (TS_CPS * ref.xtal_err);
+
+ /* now add that delta to reference GPS time */
+ fractpart = modf (delta_sec , &intpart);
+ tmp = ref.gps.tv_nsec + (long)(fractpart * 1E9);
+ if (tmp < (long)1E9) { /* the nanosecond part doesn't overflow */
+ gps_time->tv_sec = ref.gps.tv_sec + (time_t)intpart;
+ gps_time->tv_nsec = tmp;
+ } else { /* must carry one second */
+ gps_time->tv_sec = ref.gps.tv_sec + (time_t)intpart + 1;
+ gps_time->tv_nsec = tmp - (long)1E9;
+ }
+
+ return LGW_GPS_SUCCESS;
+}
+
+/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
+
+int lgw_gps2cnt(struct tref ref, struct timespec gps_time, uint32_t *count_us) {
+ double delta_sec;
+
+ CHECK_NULL(count_us);
+ if ((ref.systime == 0) || (ref.xtal_err > PLUS_10PPM) || (ref.xtal_err < MINUS_10PPM)) {
+ DEBUG_MSG("ERROR: INVALID REFERENCE FOR GPS -> CNT CONVERSION\n");
+ return LGW_GPS_ERROR;
+ }
+
+ /* calculate delta in seconds between reference gps time and target gps time */
+ delta_sec = (double)(gps_time.tv_sec - ref.gps.tv_sec);
+ delta_sec += 1E-9 * (double)(gps_time.tv_nsec - ref.gps.tv_nsec);
+
+ /* now convert that to internal counter tics and add that to reference counter value */
+ *count_us = ref.count_us + (uint32_t)(delta_sec * TS_CPS * ref.xtal_err);
+
+ return LGW_GPS_SUCCESS;
+}
+
+/* --- EOF ------------------------------------------------------------------ */
diff --git a/libloragw/src/loragw_hal.c b/libloragw/src/loragw_hal.c
new file mode 100644
index 0000000..8103751
--- /dev/null
+++ b/libloragw/src/loragw_hal.c
@@ -0,0 +1,1767 @@
+/*
+ / _____) _ | |
+( (____ _____ ____ _| |_ _____ ____| |__
+ \____ \| ___ | (_ _) ___ |/ ___) _ \
+ _____) ) ____| | | || |_| ____( (___| | | |
+(______/|_____)_|_|_| \__)_____)\____)_| |_|
+ (C)2013 Semtech-Cycleo
+
+Description:
+ LoRa concentrator Hardware Abstraction Layer
+
+License: Revised BSD License, see LICENSE.TXT file include in the project
+Maintainer: Sylvain Miermont
+*/
+
+
+/* -------------------------------------------------------------------------- */
+/* --- DEPENDANCIES --------------------------------------------------------- */
+
+#include <stdint.h> /* C99 types */
+#include <stdbool.h> /* bool type */
+#include <stdio.h> /* printf fprintf */
+#include <string.h> /* memcpy */
+#include <math.h> /* pow, cell */
+
+#include "loragw_reg.h"
+#include "loragw_hal.h"
+#include "loragw_aux.h"
+#include "loragw_spi.h"
+#include "loragw_radio.h"
+#include "loragw_fpga.h"
+#include "loragw_lbt.h"
+
+/* -------------------------------------------------------------------------- */
+/* --- PRIVATE MACROS ------------------------------------------------------- */
+
+#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
+#if DEBUG_HAL == 1
+ #define DEBUG_MSG(str) fprintf(stderr, str)
+ #define DEBUG_PRINTF(fmt, args...) fprintf(stderr,"%s:%d: "fmt, __FUNCTION__, __LINE__, args)
+ #define DEBUG_ARRAY(a,b,c) for(a=0;a<b;++a) fprintf(stderr,"%x.",c[a]);fprintf(stderr,"end\n")
+ #define CHECK_NULL(a) if(a==NULL){fprintf(stderr,"%s:%d: ERROR: NULL POINTER AS ARGUMENT\n", __FUNCTION__, __LINE__);return LGW_HAL_ERROR;}
+#else
+ #define DEBUG_MSG(str)
+ #define DEBUG_PRINTF(fmt, args...)
+ #define DEBUG_ARRAY(a,b,c) for(a=0;a!=0;){}
+ #define CHECK_NULL(a) if(a==NULL){return LGW_HAL_ERROR;}
+#endif
+
+#define IF_HZ_TO_REG(f) (f << 5)/15625
+#define SET_PPM_ON(bw,dr) (((bw == BW_125KHZ) && ((dr == DR_LORA_SF11) || (dr == DR_LORA_SF12))) || ((bw == BW_250KHZ) && (dr == DR_LORA_SF12)))
+#define TRACE() fprintf(stderr, "@ %s %d\n", __FUNCTION__, __LINE__);
+
+/* -------------------------------------------------------------------------- */
+/* --- PRIVATE CONSTANTS & TYPES -------------------------------------------- */
+
+#define MCU_ARB 0
+#define MCU_AGC 1
+#define MCU_ARB_FW_BYTE 8192 /* size of the firmware IN BYTES (= twice the number of 14b words) */
+#define MCU_AGC_FW_BYTE 8192 /* size of the firmware IN BYTES (= twice the number of 14b words) */
+#define FW_VERSION_ADDR 0x20 /* Address of firmware version in data memory */
+#define FW_VERSION_CAL 2 /* Expected version of calibration firmware */
+#define FW_VERSION_AGC 4 /* Expected version of AGC firmware */
+#define FW_VERSION_ARB 1 /* Expected version of arbiter firmware */
+
+#define TX_METADATA_NB 16
+#define RX_METADATA_NB 16
+
+#define AGC_CMD_WAIT 16
+#define AGC_CMD_ABORT 17
+
+#define MIN_LORA_PREAMBLE 6
+#define STD_LORA_PREAMBLE 8
+#define MIN_FSK_PREAMBLE 3
+#define STD_FSK_PREAMBLE 5
+
+#define RSSI_MULTI_BIAS -35 /* difference between "multi" modem RSSI offset and "stand-alone" modem RSSI offset */
+#define RSSI_FSK_POLY_0 60 /* polynomiam coefficients to linearize FSK RSSI */
+#define RSSI_FSK_POLY_1 1.5351
+#define RSSI_FSK_POLY_2 0.003
+
+/* Useful bandwidth of SX125x radios to consider depending on channel bandwidth */
+/* Note: the below values come from lab measurements. For any question, please contact Semtech support */
+#define LGW_RF_RX_BANDWIDTH_125KHZ 925000 /* for 125KHz channels */
+#define LGW_RF_RX_BANDWIDTH_250KHZ 1000000 /* for 250KHz channels */
+#define LGW_RF_RX_BANDWIDTH_500KHZ 1100000 /* for 500KHz channels */
+
+#define TX_START_DELAY_DEFAULT 1497 /* Calibrated value for 500KHz BW and notch filter disabled */
+
+/* constant arrays defining hardware capability */
+const uint8_t ifmod_config[LGW_IF_CHAIN_NB] = LGW_IFMODEM_CONFIG;
+
+/* Version string, used to identify the library version/options once compiled */
+const char lgw_version_string[] = "Version: " LIBLORAGW_VERSION ";";
+
+/* -------------------------------------------------------------------------- */
+/* --- PRIVATE VARIABLES ---------------------------------------------------- */
+
+#include "arb_fw.var" /* external definition of the variable */
+#include "agc_fw.var" /* external definition of the variable */
+#include "cal_fw.var" /* external definition of the variable */
+
+/*
+The following static variables are the configuration set that the user can
+modify using rxrf_setconf, rxif_setconf and txgain_setconf functions.
+The functions _start and _send then use that set to configure the hardware.
+
+Parameters validity and coherency is verified by the _setconf functions and
+the _start and _send functions assume they are valid.
+*/
+
+static bool lgw_is_started;
+
+static bool rf_enable[LGW_RF_CHAIN_NB];
+static uint32_t rf_rx_freq[LGW_RF_CHAIN_NB]; /* absolute, in Hz */
+static float rf_rssi_offset[LGW_RF_CHAIN_NB];
+static bool rf_tx_enable[LGW_RF_CHAIN_NB];
+static uint32_t rf_tx_notch_freq[LGW_RF_CHAIN_NB];
+static enum lgw_radio_type_e rf_radio_type[LGW_RF_CHAIN_NB];
+
+static bool if_enable[LGW_IF_CHAIN_NB];
+static bool if_rf_chain[LGW_IF_CHAIN_NB]; /* for each IF, 0 -> radio A, 1 -> radio B */
+static int32_t if_freq[LGW_IF_CHAIN_NB]; /* relative to radio frequency, +/- in Hz */
+
+static uint8_t lora_multi_sfmask[LGW_MULTI_NB]; /* enables SF for LoRa 'multi' modems */
+
+static uint8_t lora_rx_bw; /* bandwidth setting for LoRa standalone modem */
+static uint8_t lora_rx_sf; /* spreading factor setting for LoRa standalone modem */
+static bool lora_rx_ppm_offset;
+
+static uint8_t fsk_rx_bw; /* bandwidth setting of FSK modem */
+static uint32_t fsk_rx_dr; /* FSK modem datarate in bauds */
+static uint8_t fsk_sync_word_size = 3; /* default number of bytes for FSK sync word */
+static uint64_t fsk_sync_word= 0xC194C1; /* default FSK sync word (ALIGNED RIGHT, MSbit first) */
+
+static bool lorawan_public = false;
+static uint8_t rf_clkout = 0;
+
+static struct lgw_tx_gain_lut_s txgain_lut = {
+ .size = 2,
+ .lut[0] = {
+ .dig_gain = 0,
+ .pa_gain = 2,
+ .dac_gain = 3,
+ .mix_gain = 10,
+ .rf_power = 14
+ },
+ .lut[1] = {
+ .dig_gain = 0,
+ .pa_gain = 3,
+ .dac_gain = 3,
+ .mix_gain = 14,
+ .rf_power = 27
+ }};
+
+/* TX I/Q imbalance coefficients for mixer gain = 8 to 15 */
+static int8_t cal_offset_a_i[8]; /* TX I offset for radio A */
+static int8_t cal_offset_a_q[8]; /* TX Q offset for radio A */
+static int8_t cal_offset_b_i[8]; /* TX I offset for radio B */
+static int8_t cal_offset_b_q[8]; /* TX Q offset for radio B */
+
+/* -------------------------------------------------------------------------- */
+/* --- PRIVATE FUNCTIONS DECLARATION ---------------------------------------- */
+
+int load_firmware(uint8_t target, uint8_t *firmware, uint16_t size);
+
+void lgw_constant_adjust(void);
+
+int32_t lgw_sf_getval(int x);
+int32_t lgw_bw_getval(int x);
+
+/* -------------------------------------------------------------------------- */
+/* --- PRIVATE FUNCTIONS DEFINITION ----------------------------------------- */
+
+/* size is the firmware size in bytes (not 14b words) */
+int load_firmware(uint8_t target, uint8_t *firmware, uint16_t size) {
+ int reg_rst;
+ int reg_sel;
+ uint8_t fw_check[8192];
+ int32_t dummy;
+
+ /* check parameters */
+ CHECK_NULL(firmware);
+ if (target == MCU_ARB) {
+ if (size != MCU_ARB_FW_BYTE) {
+ DEBUG_MSG("ERROR: NOT A VALID SIZE FOR MCU ARG FIRMWARE\n");
+ return -1;
+ }
+ reg_rst = LGW_MCU_RST_0;
+ reg_sel = LGW_MCU_SELECT_MUX_0;
+ }else if (target == MCU_AGC) {
+ if (size != MCU_AGC_FW_BYTE) {
+ DEBUG_MSG("ERROR: NOT A VALID SIZE FOR MCU AGC FIRMWARE\n");
+ return -1;
+ }
+ reg_rst = LGW_MCU_RST_1;
+ reg_sel = LGW_MCU_SELECT_MUX_1;
+ } else {
+ DEBUG_MSG("ERROR: NOT A VALID TARGET FOR LOADING FIRMWARE\n");
+ return -1;
+ }
+
+ /* reset the targeted MCU */
+ lgw_reg_w(reg_rst, 1);
+
+ /* set mux to access MCU program RAM and set address to 0 */
+ lgw_reg_w(reg_sel, 0);
+ lgw_reg_w(LGW_MCU_PROM_ADDR, 0);
+
+ /* write the program in one burst */
+ lgw_reg_wb(LGW_MCU_PROM_DATA, firmware, size);
+
+ /* Read back firmware code for check */
+ lgw_reg_r( LGW_MCU_PROM_DATA, &dummy ); /* bug workaround */
+ lgw_reg_rb( LGW_MCU_PROM_DATA, fw_check, size );
+ if (memcmp(firmware, fw_check, size) != 0) {
+ printf ("ERROR: Failed to load fw %d\n", (int)target);
+ return -1;
+ }
+
+ /* give back control of the MCU program ram to the MCU */
+ lgw_reg_w(reg_sel, 1);
+
+ return 0;
+}
+
+/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
+
+void lgw_constant_adjust(void) {
+
+ /* I/Q path setup */
+ // lgw_reg_w(LGW_RX_INVERT_IQ,0); /* default 0 */
+ // lgw_reg_w(LGW_MODEM_INVERT_IQ,1); /* default 1 */
+ // lgw_reg_w(LGW_CHIRP_INVERT_RX,1); /* default 1 */
+ // lgw_reg_w(LGW_RX_EDGE_SELECT,0); /* default 0 */
+ // lgw_reg_w(LGW_MBWSSF_MODEM_INVERT_IQ,0); /* default 0 */
+ // lgw_reg_w(LGW_DC_NOTCH_EN,1); /* default 1 */
+ lgw_reg_w(LGW_RSSI_BB_FILTER_ALPHA,6); /* default 7 */
+ lgw_reg_w(LGW_RSSI_DEC_FILTER_ALPHA,7); /* default 5 */
+ lgw_reg_w(LGW_RSSI_CHANN_FILTER_ALPHA,7); /* default 8 */
+ lgw_reg_w(LGW_RSSI_BB_DEFAULT_VALUE,23); /* default 32 */
+ lgw_reg_w(LGW_RSSI_CHANN_DEFAULT_VALUE,85); /* default 100 */
+ lgw_reg_w(LGW_RSSI_DEC_DEFAULT_VALUE,66); /* default 100 */
+ lgw_reg_w(LGW_DEC_GAIN_OFFSET,7); /* default 8 */
+ lgw_reg_w(LGW_CHAN_GAIN_OFFSET,6); /* default 7 */
+
+ /* Correlator setup */
+ // lgw_reg_w(LGW_CORR_DETECT_EN,126); /* default 126 */
+ // lgw_reg_w(LGW_CORR_NUM_SAME_PEAK,4); /* default 4 */
+ // lgw_reg_w(LGW_CORR_MAC_GAIN,5); /* default 5 */
+ // lgw_reg_w(LGW_CORR_SAME_PEAKS_OPTION_SF6,0); /* default 0 */
+ // lgw_reg_w(LGW_CORR_SAME_PEAKS_OPTION_SF7,1); /* default 1 */
+ // lgw_reg_w(LGW_CORR_SAME_PEAKS_OPTION_SF8,1); /* default 1 */
+ // lgw_reg_w(LGW_CORR_SAME_PEAKS_OPTION_SF9,1); /* default 1 */
+ // lgw_reg_w(LGW_CORR_SAME_PEAKS_OPTION_SF10,1); /* default 1 */
+ // lgw_reg_w(LGW_CORR_SAME_PEAKS_OPTION_SF11,1); /* default 1 */
+ // lgw_reg_w(LGW_CORR_SAME_PEAKS_OPTION_SF12,1); /* default 1 */
+ // lgw_reg_w(LGW_CORR_SIG_NOISE_RATIO_SF6,4); /* default 4 */
+ // lgw_reg_w(LGW_CORR_SIG_NOISE_RATIO_SF7,4); /* default 4 */
+ // lgw_reg_w(LGW_CORR_SIG_NOISE_RATIO_SF8,4); /* default 4 */
+ // lgw_reg_w(LGW_CORR_SIG_NOISE_RATIO_SF9,4); /* default 4 */
+ // lgw_reg_w(LGW_CORR_SIG_NOISE_RATIO_SF10,4); /* default 4 */
+ // lgw_reg_w(LGW_CORR_SIG_NOISE_RATIO_SF11,4); /* default 4 */
+ // lgw_reg_w(LGW_CORR_SIG_NOISE_RATIO_SF12,4); /* default 4 */
+
+ /* LoRa 'multi' demodulators setup */
+ // lgw_reg_w(LGW_PREAMBLE_SYMB1_NB,10); /* default 10 */
+ // lgw_reg_w(LGW_FREQ_TO_TIME_INVERT,29); /* default 29 */
+ // lgw_reg_w(LGW_FRAME_SYNCH_GAIN,1); /* default 1 */
+ // lgw_reg_w(LGW_SYNCH_DETECT_TH,1); /* default 1 */
+ // lgw_reg_w(LGW_ZERO_PAD,0); /* default 0 */
+ lgw_reg_w(LGW_SNR_AVG_CST,3); /* default 2 */
+ if (lorawan_public) { /* LoRa network */
+ lgw_reg_w(LGW_FRAME_SYNCH_PEAK1_POS,3); /* default 1 */
+ lgw_reg_w(LGW_FRAME_SYNCH_PEAK2_POS,4); /* default 2 */
+ } else { /* private network */
+ lgw_reg_w(LGW_FRAME_SYNCH_PEAK1_POS,1); /* default 1 */
+ lgw_reg_w(LGW_FRAME_SYNCH_PEAK2_POS,2); /* default 2 */
+ }
+
+ // lgw_reg_w(LGW_PREAMBLE_FINE_TIMING_GAIN,1); /* default 1 */
+ // lgw_reg_w(LGW_ONLY_CRC_EN,1); /* default 1 */
+ // lgw_reg_w(LGW_PAYLOAD_FINE_TIMING_GAIN,2); /* default 2 */
+ // lgw_reg_w(LGW_TRACKING_INTEGRAL,0); /* default 0 */
+ // lgw_reg_w(LGW_ADJUST_MODEM_START_OFFSET_RDX8,0); /* default 0 */
+ // lgw_reg_w(LGW_ADJUST_MODEM_START_OFFSET_SF12_RDX4,4092); /* default 4092 */
+ // lgw_reg_w(LGW_MAX_PAYLOAD_LEN,255); /* default 255 */
+
+ /* LoRa standalone 'MBWSSF' demodulator setup */
+ // lgw_reg_w(LGW_MBWSSF_PREAMBLE_SYMB1_NB,10); /* default 10 */
+ // lgw_reg_w(LGW_MBWSSF_FREQ_TO_TIME_INVERT,29); /* default 29 */
+ // lgw_reg_w(LGW_MBWSSF_FRAME_SYNCH_GAIN,1); /* default 1 */
+ // lgw_reg_w(LGW_MBWSSF_SYNCH_DETECT_TH,1); /* default 1 */
+ // lgw_reg_w(LGW_MBWSSF_ZERO_PAD,0); /* default 0 */
+ if (lorawan_public) { /* LoRa network */
+ lgw_reg_w(LGW_MBWSSF_FRAME_SYNCH_PEAK1_POS,3); /* default 1 */
+ lgw_reg_w(LGW_MBWSSF_FRAME_SYNCH_PEAK2_POS,4); /* default 2 */
+ } else {
+ lgw_reg_w(LGW_MBWSSF_FRAME_SYNCH_PEAK1_POS,1); /* default 1 */
+ lgw_reg_w(LGW_MBWSSF_FRAME_SYNCH_PEAK2_POS,2); /* default 2 */
+ }
+ // lgw_reg_w(LGW_MBWSSF_ONLY_CRC_EN,1); /* default 1 */
+ // lgw_reg_w(LGW_MBWSSF_PAYLOAD_FINE_TIMING_GAIN,2); /* default 2 */
+ // lgw_reg_w(LGW_MBWSSF_PREAMBLE_FINE_TIMING_GAIN,1); /* default 1 */
+ // lgw_reg_w(LGW_MBWSSF_TRACKING_INTEGRAL,0); /* default 0 */
+ // lgw_reg_w(LGW_MBWSSF_AGC_FREEZE_ON_DETECT,1); /* default 1 */
+
+ /* Improvement of reference clock frequency error tolerance */
+ lgw_reg_w(LGW_ADJUST_MODEM_START_OFFSET_RDX4, 1); /* default 0 */
+ lgw_reg_w(LGW_ADJUST_MODEM_START_OFFSET_SF12_RDX4, 4094); /* default 4092 */
+ lgw_reg_w(LGW_CORR_MAC_GAIN, 7); /* default 5 */
+
+ /* FSK datapath setup */
+ lgw_reg_w(LGW_FSK_RX_INVERT,1); /* default 0 */
+ lgw_reg_w(LGW_FSK_MODEM_INVERT_IQ,1); /* default 0 */
+
+ /* FSK demodulator setup */
+ lgw_reg_w(LGW_FSK_RSSI_LENGTH,4); /* default 0 */
+ lgw_reg_w(LGW_FSK_PKT_MODE,1); /* variable length, default 0 */
+ lgw_reg_w(LGW_FSK_CRC_EN,1); /* default 0 */
+ lgw_reg_w(LGW_FSK_DCFREE_ENC,2); /* default 0 */
+ // lgw_reg_w(LGW_FSK_CRC_IBM,0); /* default 0 */
+ lgw_reg_w(LGW_FSK_ERROR_OSR_TOL,10); /* default 0 */
+ lgw_reg_w(LGW_FSK_PKT_LENGTH,255); /* max packet length in variable length mode */
+ // lgw_reg_w(LGW_FSK_NODE_ADRS,0); /* default 0 */
+ // lgw_reg_w(LGW_FSK_BROADCAST,0); /* default 0 */
+ // lgw_reg_w(LGW_FSK_AUTO_AFC_ON,0); /* default 0 */
+ lgw_reg_w(LGW_FSK_PATTERN_TIMEOUT_CFG,128); /* sync timeout (allow 8 bytes preamble + 8 bytes sync word, default 0 */
+
+ /* TX general parameters */
+ lgw_reg_w(LGW_TX_START_DELAY, TX_START_DELAY_DEFAULT); /* default 0 */
+
+ /* TX LoRa */
+ // lgw_reg_w(LGW_TX_MODE,0); /* default 0 */
+ lgw_reg_w(LGW_TX_SWAP_IQ,1); /* "normal" polarity; default 0 */
+ if (lorawan_public) { /* LoRa network */
+ lgw_reg_w(LGW_TX_FRAME_SYNCH_PEAK1_POS,3); /* default 1 */
+ lgw_reg_w(LGW_TX_FRAME_SYNCH_PEAK2_POS,4); /* default 2 */
+ } else { /* Private network */
+ lgw_reg_w(LGW_TX_FRAME_SYNCH_PEAK1_POS,1); /* default 1 */
+ lgw_reg_w(LGW_TX_FRAME_SYNCH_PEAK2_POS,2); /* default 2 */
+ }
+
+ /* TX FSK */
+ // lgw_reg_w(LGW_FSK_TX_GAUSSIAN_EN,1); /* default 1 */
+ lgw_reg_w(LGW_FSK_TX_GAUSSIAN_SELECT_BT,2); /* Gaussian filter always on TX, default 0 */
+ // lgw_reg_w(LGW_FSK_TX_PATTERN_EN,1); /* default 1 */
+ // lgw_reg_w(LGW_FSK_TX_PREAMBLE_SEQ,0); /* default 0 */
+
+ return;
+}
+
+/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
+
+int32_t lgw_bw_getval(int x) {
+ switch (x) {
+ case BW_500KHZ: return 500000;
+ case BW_250KHZ: return 250000;
+ case BW_125KHZ: return 125000;
+ case BW_62K5HZ: return 62500;
+ case BW_31K2HZ: return 31200;
+ case BW_15K6HZ: return 15600;
+ case BW_7K8HZ : return 7800;
+ default: return -1;
+ }
+}
+
+/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
+
+int32_t lgw_sf_getval(int x) {
+ switch (x) {
+ case DR_LORA_SF7: return 7;
+ case DR_LORA_SF8: return 8;
+ case DR_LORA_SF9: return 9;
+ case DR_LORA_SF10: return 10;
+ case DR_LORA_SF11: return 11;
+ case DR_LORA_SF12: return 12;
+ default: return -1;
+ }
+}
+
+/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
+
+uint16_t lgw_get_tx_start_delay(bool tx_notch_enable, uint8_t bw) {
+ float notch_delay_us = 0.0;
+ float bw_delay_us = 0.0;
+ float tx_start_delay;
+
+ /* Notch filtering performed by FPGA adds a constant delay (group delay) that we need to compensate */
+ if (tx_notch_enable) {
+ notch_delay_us = lgw_fpga_get_tx_notch_delay();
+ }
+
+ /* Calibrated delay brought by SX1301 depending on signal bandwidth */
+ switch (bw) {
+ case BW_125KHZ:
+ bw_delay_us = 1.5;
+ break;
+ case BW_500KHZ:
+ /* Intended fall-through: it is the calibrated reference */
+ default:
+ break;
+ }
+
+ tx_start_delay = (float)TX_START_DELAY_DEFAULT - bw_delay_us - notch_delay_us;
+
+ printf("INFO: tx_start_delay=%u (%f) - (%u, bw_delay=%f, notch_delay=%f)\n", (uint16_t)tx_start_delay, tx_start_delay, TX_START_DELAY_DEFAULT, bw_delay_us, notch_delay_us);
+
+ return (uint16_t)tx_start_delay; /* keep truncating instead of rounding: better behaviour measured */
+}
+
+/* -------------------------------------------------------------------------- */
+/* --- PUBLIC FUNCTIONS DEFINITION ------------------------------------------ */
+
+int lgw_board_setconf(struct lgw_conf_board_s conf) {
+
+ /* check if the concentrator is running */
+ if (lgw_is_started == true) {
+ DEBUG_MSG("ERROR: CONCENTRATOR IS RUNNING, STOP IT BEFORE TOUCHING CONFIGURATION\n");
+ return LGW_HAL_ERROR;
+ }
+
+ /* set internal config according to parameters */
+ lorawan_public = conf.lorawan_public;
+ rf_clkout = conf.clksrc;
+
+ DEBUG_PRINTF("Note: board configuration; lorawan_public:%d, clksrc:%d\n", lorawan_public, rf_clkout);
+
+ return LGW_HAL_SUCCESS;
+}
+
+/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
+
+int lgw_lbt_setconf(struct lgw_conf_lbt_s conf) {
+ int x;
+
+ /* check if the concentrator is running */
+ if (lgw_is_started == true) {
+ DEBUG_MSG("ERROR: CONCENTRATOR IS RUNNING, STOP IT BEFORE TOUCHING CONFIGURATION\n");
+ return LGW_HAL_ERROR;
+ }
+
+ x = lbt_setconf(&conf);
+ if (x != LGW_LBT_SUCCESS) {
+ DEBUG_MSG("ERROR: Failed to configure concentrator for LBT\n");
+ return LGW_HAL_ERROR;
+ }
+
+ return LGW_HAL_SUCCESS;
+}
+
+/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
+
+int lgw_rxrf_setconf(uint8_t rf_chain, struct lgw_conf_rxrf_s conf) {
+
+ /* check if the concentrator is running */
+ if (lgw_is_started == true) {
+ DEBUG_MSG("ERROR: CONCENTRATOR IS RUNNING, STOP IT BEFORE TOUCHING CONFIGURATION\n");
+ return LGW_HAL_ERROR;
+ }
+
+ /* check input range (segfault prevention) */
+ if (rf_chain >= LGW_RF_CHAIN_NB) {
+ DEBUG_MSG("ERROR: NOT A VALID RF_CHAIN NUMBER\n");
+ return LGW_HAL_ERROR;
+ }
+
+ /* check if radio type is supported */
+ if ((conf.type != LGW_RADIO_TYPE_SX1255) && (conf.type != LGW_RADIO_TYPE_SX1257)) {
+ DEBUG_MSG("ERROR: NOT A VALID RADIO TYPE\n");
+ return LGW_HAL_ERROR;
+ }
+
+ /* check if TX notch filter frequency is supported */
+ if ((conf.tx_enable == true) && ((conf.tx_notch_freq < LGW_MIN_NOTCH_FREQ) || (conf.tx_notch_freq > LGW_MAX_NOTCH_FREQ))) {
+ DEBUG_PRINTF("WARNING: NOT A VALID TX NOTCH FILTER FREQUENCY [%u..%u]Hz\n", LGW_MIN_NOTCH_FREQ, LGW_MAX_NOTCH_FREQ);
+ conf.tx_notch_freq = 0;
+ }
+
+ /* set internal config according to parameters */
+ rf_enable[rf_chain] = conf.enable;
+ rf_rx_freq[rf_chain] = conf.freq_hz;
+ rf_rssi_offset[rf_chain] = conf.rssi_offset;
+ rf_radio_type[rf_chain] = conf.type;
+ rf_tx_enable[rf_chain] = conf.tx_enable;
+ rf_tx_notch_freq[rf_chain] = conf.tx_notch_freq;
+
+ DEBUG_PRINTF("Note: rf_chain %d configuration; en:%d freq:%d rssi_offset:%f radio_type:%d tx_enable:%d tx_notch_freq:%u\n", rf_chain, rf_enable[rf_chain], rf_rx_freq[rf_chain], rf_rssi_offset[rf_chain], rf_radio_type[rf_chain], rf_tx_enable[rf_chain], rf_tx_notch_freq[rf_chain]);
+
+ return LGW_HAL_SUCCESS;
+}
+
+/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
+
+int lgw_rxif_setconf(uint8_t if_chain, struct lgw_conf_rxif_s conf) {
+ int32_t bw_hz;
+ uint32_t rf_rx_bandwidth;
+
+ /* check if the concentrator is running */
+ if (lgw_is_started == true) {
+ DEBUG_MSG("ERROR: CONCENTRATOR IS RUNNING, STOP IT BEFORE TOUCHING CONFIGURATION\n");
+ return LGW_HAL_ERROR;
+ }
+
+ /* check input range (segfault prevention) */
+ if (if_chain >= LGW_IF_CHAIN_NB) {
+ DEBUG_PRINTF("ERROR: %d NOT A VALID IF_CHAIN NUMBER\n", if_chain);
+ return LGW_HAL_ERROR;
+ }
+
+ /* if chain is disabled, don't care about most parameters */
+ if (conf.enable == false) {
+ if_enable[if_chain] = false;
+ if_freq[if_chain] = 0;
+ DEBUG_PRINTF("Note: if_chain %d disabled\n", if_chain);
+ return LGW_HAL_SUCCESS;
+ }
+
+ /* check 'general' parameters */
+ if (ifmod_config[if_chain] == IF_UNDEFINED) {
+ DEBUG_PRINTF("ERROR: IF CHAIN %d NOT CONFIGURABLE\n", if_chain);
+ }
+ if (conf.rf_chain >= LGW_RF_CHAIN_NB) {
+ DEBUG_MSG("ERROR: INVALID RF_CHAIN TO ASSOCIATE WITH A LORA_STD IF CHAIN\n");
+ return LGW_HAL_ERROR;
+ }
+ /* check if IF frequency is optimal based on channel and radio bandwidths */
+ switch (conf.bandwidth) {
+ case BW_250KHZ:
+ rf_rx_bandwidth = LGW_RF_RX_BANDWIDTH_250KHZ; /* radio bandwidth */
+ break;
+ case BW_500KHZ:
+ rf_rx_bandwidth = LGW_RF_RX_BANDWIDTH_500KHZ; /* radio bandwidth */
+ break;
+ default:
+ /* For 125KHz and below */
+ rf_rx_bandwidth = LGW_RF_RX_BANDWIDTH_125KHZ; /* radio bandwidth */
+ break;
+ }
+ bw_hz = lgw_bw_getval(conf.bandwidth); /* channel bandwidth */
+ if ((conf.freq_hz + ((bw_hz==-1)?LGW_REF_BW:bw_hz)/2) > ((int32_t)rf_rx_bandwidth/2)) {
+ DEBUG_PRINTF("ERROR: IF FREQUENCY %d TOO HIGH\n", conf.freq_hz);
+ return LGW_HAL_ERROR;
+ } else if ((conf.freq_hz - ((bw_hz==-1)?LGW_REF_BW:bw_hz)/2) < -((int32_t)rf_rx_bandwidth/2)) {
+ DEBUG_PRINTF("ERROR: IF FREQUENCY %d TOO LOW\n", conf.freq_hz);
+ return LGW_HAL_ERROR;
+ }
+
+ /* check parameters according to the type of IF chain + modem,
+ fill default if necessary, and commit configuration if everything is OK */
+ switch (ifmod_config[if_chain]) {
+ case IF_LORA_STD:
+ /* fill default parameters if needed */
+ if (conf.bandwidth == BW_UNDEFINED) {
+ conf.bandwidth = BW_250KHZ;
+ }
+ if (conf.datarate == DR_UNDEFINED) {
+ conf.datarate = DR_LORA_SF9;
+ }
+ /* check BW & DR */
+ if (!IS_LORA_BW(conf.bandwidth)) {
+ DEBUG_MSG("ERROR: BANDWIDTH NOT SUPPORTED BY LORA_STD IF CHAIN\n");
+ return LGW_HAL_ERROR;
+ }
+ if (!IS_LORA_STD_DR(conf.datarate)) {
+ DEBUG_MSG("ERROR: DATARATE NOT SUPPORTED BY LORA_STD IF CHAIN\n");
+ return LGW_HAL_ERROR;
+ }
+ /* set internal configuration */
+ if_enable[if_chain] = conf.enable;
+ if_rf_chain[if_chain] = conf.rf_chain;
+ if_freq[if_chain] = conf.freq_hz;
+ lora_rx_bw = conf.bandwidth;
+ lora_rx_sf = (uint8_t)(DR_LORA_MULTI & conf.datarate); /* filter SF out of the 7-12 range */
+ if (SET_PPM_ON(conf.bandwidth, conf.datarate)) {
+ lora_rx_ppm_offset = true;
+ } else {
+ lora_rx_ppm_offset = false;
+ }
+
+ DEBUG_PRINTF("Note: LoRa 'std' if_chain %d configuration; en:%d freq:%d bw:%d dr:%d\n", if_chain, if_enable[if_chain], if_freq[if_chain], lora_rx_bw, lora_rx_sf);
+ break;
+
+ case IF_LORA_MULTI:
+ /* fill default parameters if needed */
+ if (conf.bandwidth == BW_UNDEFINED) {
+ conf.bandwidth = BW_125KHZ;
+ }
+ if (conf.datarate == DR_UNDEFINED) {
+ conf.datarate = DR_LORA_MULTI;
+ }
+ /* check BW & DR */
+ if (conf.bandwidth != BW_125KHZ) {
+ DEBUG_MSG("ERROR: BANDWIDTH NOT SUPPORTED BY LORA_MULTI IF CHAIN\n");
+ return LGW_HAL_ERROR;
+ }
+ if (!IS_LORA_MULTI_DR(conf.datarate)) {
+ DEBUG_MSG("ERROR: DATARATE(S) NOT SUPPORTED BY LORA_MULTI IF CHAIN\n");
+ return LGW_HAL_ERROR;
+ }
+ /* set internal configuration */
+ if_enable[if_chain] = conf.enable;
+ if_rf_chain[if_chain] = conf.rf_chain;
+ if_freq[if_chain] = conf.freq_hz;
+ lora_multi_sfmask[if_chain] = (uint8_t)(DR_LORA_MULTI & conf.datarate); /* filter SF out of the 7-12 range */
+
+ DEBUG_PRINTF("Note: LoRa 'multi' if_chain %d configuration; en:%d freq:%d SF_mask:0x%02x\n", if_chain, if_enable[if_chain], if_freq[if_chain], lora_multi_sfmask[if_chain]);
+ break;
+
+ case IF_FSK_STD:
+ /* fill default parameters if needed */
+ if (conf.bandwidth == BW_UNDEFINED) {
+ conf.bandwidth = BW_250KHZ;
+ }
+ if (conf.datarate == DR_UNDEFINED) {
+ conf.datarate = 64000; /* default datarate */
+ }
+ /* check BW & DR */
+ if(!IS_FSK_BW(conf.bandwidth)) {
+ DEBUG_MSG("ERROR: BANDWIDTH NOT SUPPORTED BY FSK IF CHAIN\n");
+ return LGW_HAL_ERROR;
+ }
+ if(!IS_FSK_DR(conf.datarate)) {
+ DEBUG_MSG("ERROR: DATARATE NOT SUPPORTED BY FSK IF CHAIN\n");
+ return LGW_HAL_ERROR;
+ }
+ /* set internal configuration */
+ if_enable[if_chain] = conf.enable;
+ if_rf_chain[if_chain] = conf.rf_chain;
+ if_freq[if_chain] = conf.freq_hz;
+ fsk_rx_bw = conf.bandwidth;
+ fsk_rx_dr = conf.datarate;
+ if (conf.sync_word > 0) {
+ fsk_sync_word_size = conf.sync_word_size;
+ fsk_sync_word = conf.sync_word;
+ }
+ DEBUG_PRINTF("Note: FSK if_chain %d configuration; en:%d freq:%d bw:%d dr:%d (%d real dr) sync:0x%0*llX\n", if_chain, if_enable[if_chain], if_freq[if_chain], fsk_rx_bw, fsk_rx_dr, LGW_XTAL_FREQU/(LGW_XTAL_FREQU/fsk_rx_dr), 2*fsk_sync_word_size, fsk_sync_word);
+ break;
+
+ default:
+ DEBUG_PRINTF("ERROR: IF CHAIN %d TYPE NOT SUPPORTED\n", if_chain);
+ return LGW_HAL_ERROR;
+ }
+
+ return LGW_HAL_SUCCESS;
+}
+
+/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
+
+int lgw_txgain_setconf(struct lgw_tx_gain_lut_s *conf) {
+ int i;
+
+ /* Check LUT size */
+ if ((conf->size < 1) || (conf->size > TX_GAIN_LUT_SIZE_MAX)) {
+ DEBUG_PRINTF("ERROR: TX gain LUT must have at least one entry and maximum %d entries\n", TX_GAIN_LUT_SIZE_MAX);
+ return LGW_HAL_ERROR;
+ }
+
+ txgain_lut.size = conf->size;
+
+ for (i = 0; i < txgain_lut.size; i++) {
+ /* Check gain range */
+ if (conf->lut[i].dig_gain > 3) {
+ DEBUG_MSG("ERROR: TX gain LUT: SX1301 digital gain must be between 0 and 3\n");
+ return LGW_HAL_ERROR;
+ }
+ if (conf->lut[i].dac_gain != 3) {
+ DEBUG_MSG("ERROR: TX gain LUT: SX1257 DAC gains != 3 are not supported\n");
+ return LGW_HAL_ERROR;
+ }
+ if (conf->lut[i].mix_gain > 15) {
+ DEBUG_MSG("ERROR: TX gain LUT: SX1257 mixer gain must not exceed 15\n");
+ return LGW_HAL_ERROR;
+ } else if (conf->lut[i].mix_gain < 8) {
+ DEBUG_MSG("ERROR: TX gain LUT: SX1257 mixer gains < 8 are not supported\n");
+ return LGW_HAL_ERROR;
+ }
+ if (conf->lut[i].pa_gain > 3) {
+ DEBUG_MSG("ERROR: TX gain LUT: External PA gain must not exceed 3\n");
+ return LGW_HAL_ERROR;
+ }
+
+ /* Set internal LUT */
+ txgain_lut.lut[i].dig_gain = conf->lut[i].dig_gain;
+ txgain_lut.lut[i].dac_gain = conf->lut[i].dac_gain;
+ txgain_lut.lut[i].mix_gain = conf->lut[i].mix_gain;
+ txgain_lut.lut[i].pa_gain = conf->lut[i].pa_gain;
+ txgain_lut.lut[i].rf_power = conf->lut[i].rf_power;
+ }
+
+ return LGW_HAL_SUCCESS;
+}
+
+/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
+
+int lgw_start(void) {
+ int i, err;
+ int reg_stat;
+ unsigned x;
+ uint8_t radio_select;
+ int32_t read_val;
+ uint8_t load_val;
+ uint8_t fw_version;
+ uint8_t cal_cmd;
+ uint16_t cal_time;
+ uint8_t cal_status;
+
+ uint64_t fsk_sync_word_reg;
+
+ if (lgw_is_started == true) {
+ DEBUG_MSG("Note: LoRa concentrator already started, restarting it now\n");
+ }
+
+ reg_stat = lgw_connect(false, rf_tx_notch_freq[rf_tx_enable[1]?1:0]);
+ if (reg_stat == LGW_REG_ERROR) {
+ DEBUG_MSG("ERROR: FAIL TO CONNECT BOARD\n");
+ return LGW_HAL_ERROR;
+ }
+
+ /* reset the registers (also shuts the radios down) */
+ lgw_soft_reset();
+
+ /* gate clocks */
+ lgw_reg_w(LGW_GLOBAL_EN, 0);
+ lgw_reg_w(LGW_CLK32M_EN, 0);
+
+ /* switch on and reset the radios (also starts the 32 MHz XTAL) */
+ lgw_reg_w(LGW_RADIO_A_EN,1);
+ lgw_reg_w(LGW_RADIO_B_EN,1);
+ wait_ms(500); /* TODO: optimize */
+ lgw_reg_w(LGW_RADIO_RST,1);
+ wait_ms(5);
+ lgw_reg_w(LGW_RADIO_RST,0);
+
+ /* setup the radios */
+ err = lgw_setup_sx125x(0, rf_clkout, rf_enable[0], rf_radio_type[0], rf_rx_freq[0]);
+ if (err != 0) {
+ DEBUG_MSG("ERROR: Failed to setup sx125x radio for RF chain 0\n");
+ return LGW_HAL_ERROR;
+ }
+ err = lgw_setup_sx125x(1, rf_clkout, rf_enable[1], rf_radio_type[1], rf_rx_freq[1]);
+ if (err != 0) {
+ DEBUG_MSG("ERROR: Failed to setup sx125x radio for RF chain 0\n");
+ return LGW_HAL_ERROR;
+ }
+
+ /* gives AGC control of GPIOs to enable Tx external digital filter */
+ lgw_reg_w(LGW_GPIO_MODE,31); /* Set all GPIOs as output */
+ lgw_reg_w(LGW_GPIO_SELECT_OUTPUT,2);
+
+ /* Configure LBT */
+ if (lbt_is_enabled() == true) {
+ lgw_reg_w(LGW_CLK32M_EN, 1);
+ i = lbt_setup();
+ if (i != LGW_LBT_SUCCESS) {
+ DEBUG_MSG("ERROR: lbt_setup() did not return SUCCESS\n");
+ return LGW_HAL_ERROR;
+ }
+
+ /* Start SX1301 counter and LBT FSM at the same time to be in sync */
+ lgw_reg_w(LGW_CLK32M_EN, 0);
+ i = lbt_start();
+ if (i != LGW_LBT_SUCCESS) {
+ DEBUG_MSG("ERROR: lbt_start() did not return SUCCESS\n");
+ return LGW_HAL_ERROR;
+ }
+ }
+
+ /* Enable clocks */
+ lgw_reg_w(LGW_GLOBAL_EN, 1);
+ lgw_reg_w(LGW_CLK32M_EN, 1);
+
+ /* GPIOs table :
+ DGPIO0 -> N/A
+ DGPIO1 -> N/A
+ DGPIO2 -> N/A
+ DGPIO3 -> TX digital filter ON
+ DGPIO4 -> TX ON
+ */
+
+ /* select calibration command */
+ cal_cmd = 0;
+ cal_cmd |= rf_enable[0] ? 0x01 : 0x00; /* Bit 0: Calibrate Rx IQ mismatch compensation on radio A */
+ cal_cmd |= rf_enable[1] ? 0x02 : 0x00; /* Bit 1: Calibrate Rx IQ mismatch compensation on radio B */
+ cal_cmd |= (rf_enable[0] && rf_tx_enable[0]) ? 0x04 : 0x00; /* Bit 2: Calibrate Tx DC offset on radio A */
+ cal_cmd |= (rf_enable[1] && rf_tx_enable[1]) ? 0x08 : 0x00; /* Bit 3: Calibrate Tx DC offset on radio B */
+ cal_cmd |= 0x10; /* Bit 4: 0: calibrate with DAC gain=2, 1: with DAC gain=3 (use 3) */
+
+ switch (rf_radio_type[0]) { /* we assume that there is only one radio type on the board */
+ case LGW_RADIO_TYPE_SX1255:
+ cal_cmd |= 0x20; /* Bit 5: 0: SX1257, 1: SX1255 */
+ break;
+ case LGW_RADIO_TYPE_SX1257:
+ cal_cmd |= 0x00; /* Bit 5: 0: SX1257, 1: SX1255 */
+ break;
+ default:
+ DEBUG_PRINTF("ERROR: UNEXPECTED VALUE %d FOR RADIO TYPE\n", rf_radio_type[0]);
+ break;
+ }
+
+ cal_cmd |= 0x00; /* Bit 6-7: Board type 0: ref, 1: FPGA, 3: board X */
+ cal_time = 2300; /* measured between 2.1 and 2.2 sec, because 1 TX only */
+
+ /* Load the calibration firmware */
+ load_firmware(MCU_AGC, cal_firmware, MCU_AGC_FW_BYTE);
+ lgw_reg_w(LGW_FORCE_HOST_RADIO_CTRL, 0); /* gives to AGC MCU the control of the radios */
+ lgw_reg_w(LGW_RADIO_SELECT, cal_cmd); /* send calibration configuration word */
+ lgw_reg_w(LGW_MCU_RST_1, 0);
+
+ /* Check firmware version */
+ lgw_reg_w(LGW_DBG_AGC_MCU_RAM_ADDR, FW_VERSION_ADDR);
+ lgw_reg_r(LGW_DBG_AGC_MCU_RAM_DATA, &read_val);
+ fw_version = (uint8_t)read_val;
+ if (fw_version != FW_VERSION_CAL) {
+ printf("ERROR: Version of calibration firmware not expected, actual:%d expected:%d\n", fw_version, FW_VERSION_CAL);
+ return -1;
+ }
+
+ lgw_reg_w(LGW_PAGE_REG, 3); /* Calibration will start on this condition as soon as MCU can talk to concentrator registers */
+ lgw_reg_w(LGW_EMERGENCY_FORCE_HOST_CTRL, 0); /* Give control of concentrator registers to MCU */
+
+ /* Wait for calibration to end */
+ DEBUG_PRINTF("Note: calibration started (time: %u ms)\n", cal_time);
+ wait_ms(cal_time); /* Wait for end of calibration */
+ lgw_reg_w(LGW_EMERGENCY_FORCE_HOST_CTRL, 1); /* Take back control */
+
+ /* Get calibration status */
+ lgw_reg_r(LGW_MCU_AGC_STATUS, &read_val);
+ cal_status = (uint8_t)read_val;
+ /*
+ bit 7: calibration finished
+ bit 0: could access SX1301 registers
+ bit 1: could access radio A registers
+ bit 2: could access radio B registers
+ bit 3: radio A RX image rejection successful
+ bit 4: radio B RX image rejection successful
+ bit 5: radio A TX DC Offset correction successful
+ bit 6: radio B TX DC Offset correction successful
+ */
+ if ((cal_status & 0x81) != 0x81) {
+ DEBUG_PRINTF("ERROR: CALIBRATION FAILURE (STATUS = %u)\n", cal_status);
+ return LGW_HAL_ERROR;
+ } else {
+ DEBUG_PRINTF("Note: calibration finished (status = %u)\n", cal_status);
+ }
+ if (rf_enable[0] && ((cal_status & 0x02) == 0)) {
+ DEBUG_MSG("WARNING: calibration could not access radio A\n");
+ }
+ if (rf_enable[1] && ((cal_status & 0x04) == 0)) {
+ DEBUG_MSG("WARNING: calibration could not access radio B\n");
+ }
+ if (rf_enable[0] && ((cal_status & 0x08) == 0)) {
+ DEBUG_MSG("WARNING: problem in calibration of radio A for image rejection\n");
+ }
+ if (rf_enable[1] && ((cal_status & 0x10) == 0)) {
+ DEBUG_MSG("WARNING: problem in calibration of radio B for image rejection\n");
+ }
+ if (rf_enable[0] && rf_tx_enable[0] && ((cal_status & 0x20) == 0)) {
+ DEBUG_MSG("WARNING: problem in calibration of radio A for TX DC offset\n");
+ }
+ if (rf_enable[1] && rf_tx_enable[1] && ((cal_status & 0x40) == 0)) {
+ DEBUG_MSG("WARNING: problem in calibration of radio B for TX DC offset\n");
+ }
+
+ /* Get TX DC offset values */
+ for(i=0; i<=7; ++i) {
+ lgw_reg_w(LGW_DBG_AGC_MCU_RAM_ADDR, 0xA0+i);
+ lgw_reg_r(LGW_DBG_AGC_MCU_RAM_DATA, &read_val);
+ cal_offset_a_i[i] = (int8_t)read_val;
+ lgw_reg_w(LGW_DBG_AGC_MCU_RAM_ADDR, 0xA8+i);
+ lgw_reg_r(LGW_DBG_AGC_MCU_RAM_DATA, &read_val);
+ cal_offset_a_q[i] = (int8_t)read_val;
+ lgw_reg_w(LGW_DBG_AGC_MCU_RAM_ADDR, 0xB0+i);
+ lgw_reg_r(LGW_DBG_AGC_MCU_RAM_DATA, &read_val);
+ cal_offset_b_i[i] = (int8_t)read_val;
+ lgw_reg_w(LGW_DBG_AGC_MCU_RAM_ADDR, 0xB8+i);
+ lgw_reg_r(LGW_DBG_AGC_MCU_RAM_DATA, &read_val);
+ cal_offset_b_q[i] = (int8_t)read_val;
+ }
+
+ /* load adjusted parameters */
+ lgw_constant_adjust();
+
+ /* Sanity check for RX frequency */
+ if (rf_rx_freq[0] == 0) {
+ DEBUG_MSG("ERROR: wrong configuration, rf_rx_freq[0] is not set\n");
+ return LGW_HAL_ERROR;
+ }
+
+ /* Freq-to-time-drift calculation */
+ x = 4096000000 / (rf_rx_freq[0] >> 1); /* dividend: (4*2048*1000000) >> 1, rescaled to avoid 32b overflow */
+ x = ( x > 63 ) ? 63 : x; /* saturation */
+ lgw_reg_w(LGW_FREQ_TO_TIME_DRIFT, x); /* default 9 */
+
+ x = 4096000000 / (rf_rx_freq[0] >> 3); /* dividend: (16*2048*1000000) >> 3, rescaled to avoid 32b overflow */
+ x = ( x > 63 ) ? 63 : x; /* saturation */
+ lgw_reg_w(LGW_MBWSSF_FREQ_TO_TIME_DRIFT, x); /* default 36 */
+
+ /* configure LoRa 'multi' demodulators aka. LoRa 'sensor' channels (IF0-3) */
+ radio_select = 0; /* IF mapping to radio A/B (per bit, 0=A, 1=B) */
+ for(i=0; i<LGW_MULTI_NB; ++i) {
+ radio_select += (if_rf_chain[i] == 1 ? 1 << i : 0); /* transform bool array into binary word */
+ }
+ /*
+ lgw_reg_w(LGW_RADIO_SELECT, radio_select);
+
+ LGW_RADIO_SELECT is used for communication with the firmware, "radio_select"
+ will be loaded in LGW_RADIO_SELECT at the end of start procedure.
+ */
+
+ lgw_reg_w(LGW_IF_FREQ_0, IF_HZ_TO_REG(if_freq[0])); /* default -384 */
+ lgw_reg_w(LGW_IF_FREQ_1, IF_HZ_TO_REG(if_freq[1])); /* default -128 */
+ lgw_reg_w(LGW_IF_FREQ_2, IF_HZ_TO_REG(if_freq[2])); /* default 128 */
+ lgw_reg_w(LGW_IF_FREQ_3, IF_HZ_TO_REG(if_freq[3])); /* default 384 */
+ lgw_reg_w(LGW_IF_FREQ_4, IF_HZ_TO_REG(if_freq[4])); /* default -384 */
+ lgw_reg_w(LGW_IF_FREQ_5, IF_HZ_TO_REG(if_freq[5])); /* default -128 */
+ lgw_reg_w(LGW_IF_FREQ_6, IF_HZ_TO_REG(if_freq[6])); /* default 128 */
+ lgw_reg_w(LGW_IF_FREQ_7, IF_HZ_TO_REG(if_freq[7])); /* default 384 */
+
+ lgw_reg_w(LGW_CORR0_DETECT_EN, (if_enable[0] == true) ? lora_multi_sfmask[0] : 0); /* default 0 */
+ lgw_reg_w(LGW_CORR1_DETECT_EN, (if_enable[1] == true) ? lora_multi_sfmask[1] : 0); /* default 0 */
+ lgw_reg_w(LGW_CORR2_DETECT_EN, (if_enable[2] == true) ? lora_multi_sfmask[2] : 0); /* default 0 */
+ lgw_reg_w(LGW_CORR3_DETECT_EN, (if_enable[3] == true) ? lora_multi_sfmask[3] : 0); /* default 0 */
+ lgw_reg_w(LGW_CORR4_DETECT_EN, (if_enable[4] == true) ? lora_multi_sfmask[4] : 0); /* default 0 */
+ lgw_reg_w(LGW_CORR5_DETECT_EN, (if_enable[5] == true) ? lora_multi_sfmask[5] : 0); /* default 0 */
+ lgw_reg_w(LGW_CORR6_DETECT_EN, (if_enable[6] == true) ? lora_multi_sfmask[6] : 0); /* default 0 */
+ lgw_reg_w(LGW_CORR7_DETECT_EN, (if_enable[7] == true) ? lora_multi_sfmask[7] : 0); /* default 0 */
+
+ lgw_reg_w(LGW_PPM_OFFSET, 0x60); /* as the threshold is 16ms, use 0x60 to enable ppm_offset for SF12 and SF11 @125kHz*/
+
+ lgw_reg_w(LGW_CONCENTRATOR_MODEM_ENABLE, 1); /* default 0 */
+
+ /* configure LoRa 'stand-alone' modem (IF8) */
+ lgw_reg_w(LGW_IF_FREQ_8, IF_HZ_TO_REG(if_freq[8])); /* MBWSSF modem (default 0) */
+ if (if_enable[8] == true) {
+ lgw_reg_w(LGW_MBWSSF_RADIO_SELECT, if_rf_chain[8]);
+ switch(lora_rx_bw) {
+ case BW_125KHZ: lgw_reg_w(LGW_MBWSSF_MODEM_BW, 0); break;
+ case BW_250KHZ: lgw_reg_w(LGW_MBWSSF_MODEM_BW, 1); break;
+ case BW_500KHZ: lgw_reg_w(LGW_MBWSSF_MODEM_BW, 2); break;
+ default:
+ DEBUG_PRINTF("ERROR: UNEXPECTED VALUE %d IN SWITCH STATEMENT\n", lora_rx_bw);
+ return LGW_HAL_ERROR;
+ }
+ switch(lora_rx_sf) {
+ case DR_LORA_SF7: lgw_reg_w(LGW_MBWSSF_RATE_SF, 7); break;
+ case DR_LORA_SF8: lgw_reg_w(LGW_MBWSSF_RATE_SF, 8); break;
+ case DR_LORA_SF9: lgw_reg_w(LGW_MBWSSF_RATE_SF, 9); break;
+ case DR_LORA_SF10: lgw_reg_w(LGW_MBWSSF_RATE_SF, 10); break;
+ case DR_LORA_SF11: lgw_reg_w(LGW_MBWSSF_RATE_SF, 11); break;
+ case DR_LORA_SF12: lgw_reg_w(LGW_MBWSSF_RATE_SF, 12); break;
+ default:
+ DEBUG_PRINTF("ERROR: UNEXPECTED VALUE %d IN SWITCH STATEMENT\n", lora_rx_sf);
+ return LGW_HAL_ERROR;
+ }
+ lgw_reg_w(LGW_MBWSSF_PPM_OFFSET, lora_rx_ppm_offset); /* default 0 */
+ lgw_reg_w(LGW_MBWSSF_MODEM_ENABLE, 1); /* default 0 */
+ } else {
+ lgw_reg_w(LGW_MBWSSF_MODEM_ENABLE, 0);
+ }
+
+ /* configure FSK modem (IF9) */
+ lgw_reg_w(LGW_IF_FREQ_9, IF_HZ_TO_REG(if_freq[9])); /* FSK modem, default 0 */
+ lgw_reg_w(LGW_FSK_PSIZE, fsk_sync_word_size-1);
+ lgw_reg_w(LGW_FSK_TX_PSIZE, fsk_sync_word_size-1);
+ fsk_sync_word_reg = fsk_sync_word << (8 * (8 - fsk_sync_word_size));
+ lgw_reg_w(LGW_FSK_REF_PATTERN_LSB, (uint32_t)(0xFFFFFFFF & fsk_sync_word_reg));
+ lgw_reg_w(LGW_FSK_REF_PATTERN_MSB, (uint32_t)(0xFFFFFFFF & (fsk_sync_word_reg >> 32)));
+ if (if_enable[9] == true) {
+ lgw_reg_w(LGW_FSK_RADIO_SELECT, if_rf_chain[9]);
+ lgw_reg_w(LGW_FSK_BR_RATIO, LGW_XTAL_FREQU/fsk_rx_dr); /* setting the dividing ratio for datarate */
+ lgw_reg_w(LGW_FSK_CH_BW_EXPO, fsk_rx_bw);
+ lgw_reg_w(LGW_FSK_MODEM_ENABLE, 1); /* default 0 */
+ } else {
+ lgw_reg_w(LGW_FSK_MODEM_ENABLE, 0);
+ }
+
+ /* Load firmware */
+ load_firmware(MCU_ARB, arb_firmware, MCU_ARB_FW_BYTE);
+ load_firmware(MCU_AGC, agc_firmware, MCU_AGC_FW_BYTE);
+
+ /* gives the AGC MCU control over radio, RF front-end and filter gain */
+ lgw_reg_w(LGW_FORCE_HOST_RADIO_CTRL, 0);
+ lgw_reg_w(LGW_FORCE_HOST_FE_CTRL, 0);
+ lgw_reg_w(LGW_FORCE_DEC_FILTER_GAIN, 0);
+
+ /* Get MCUs out of reset */
+ lgw_reg_w(LGW_RADIO_SELECT, 0); /* MUST not be = to 1 or 2 at firmware init */
+ lgw_reg_w(LGW_MCU_RST_0, 0);
+ lgw_reg_w(LGW_MCU_RST_1, 0);
+
+ /* Check firmware version */
+ lgw_reg_w(LGW_DBG_AGC_MCU_RAM_ADDR, FW_VERSION_ADDR);
+ lgw_reg_r(LGW_DBG_AGC_MCU_RAM_DATA, &read_val);
+ fw_version = (uint8_t)read_val;
+ if (fw_version != FW_VERSION_AGC) {
+ DEBUG_PRINTF("ERROR: Version of AGC firmware not expected, actual:%d expected:%d\n", fw_version, FW_VERSION_AGC);
+ return LGW_HAL_ERROR;
+ }
+ lgw_reg_w(LGW_DBG_ARB_MCU_RAM_ADDR, FW_VERSION_ADDR);
+ lgw_reg_r(LGW_DBG_ARB_MCU_RAM_DATA, &read_val);
+ fw_version = (uint8_t)read_val;
+ if (fw_version != FW_VERSION_ARB) {
+ DEBUG_PRINTF("ERROR: Version of arbiter firmware not expected, actual:%d expected:%d\n", fw_version, FW_VERSION_ARB);
+ return LGW_HAL_ERROR;
+ }
+
+ DEBUG_MSG("Info: Initialising AGC firmware...\n");
+ wait_ms(1);
+
+ lgw_reg_r(LGW_MCU_AGC_STATUS, &read_val);
+ if (read_val != 0x10) {
+ DEBUG_PRINTF("ERROR: AGC FIRMWARE INITIALIZATION FAILURE, STATUS 0x%02X\n", (uint8_t)read_val);
+ return LGW_HAL_ERROR;
+ }
+
+ /* Update Tx gain LUT and start AGC */
+ for (i = 0; i < txgain_lut.size; ++i) {
+ lgw_reg_w(LGW_RADIO_SELECT, AGC_CMD_WAIT); /* start a transaction */
+ wait_ms(1);
+ load_val = txgain_lut.lut[i].mix_gain + (16 * txgain_lut.lut[i].dac_gain) + (64 * txgain_lut.lut[i].pa_gain);
+ lgw_reg_w(LGW_RADIO_SELECT, load_val);
+ wait_ms(1);
+ lgw_reg_r(LGW_MCU_AGC_STATUS, &read_val);
+ if (read_val != (0x30 + i)) {
+ DEBUG_PRINTF("ERROR: AGC FIRMWARE INITIALIZATION FAILURE, STATUS 0x%02X\n", (uint8_t)read_val);
+ return LGW_HAL_ERROR;
+ }
+ }
+ /* As the AGC fw is waiting for 16 entries, we need to abort the transaction if we get less entries */
+ if (txgain_lut.size < TX_GAIN_LUT_SIZE_MAX) {
+ lgw_reg_w(LGW_RADIO_SELECT, AGC_CMD_WAIT);
+ wait_ms(1);
+ load_val = AGC_CMD_ABORT;
+ lgw_reg_w(LGW_RADIO_SELECT, load_val);
+ wait_ms(1);
+ lgw_reg_r(LGW_MCU_AGC_STATUS, &read_val);
+ if (read_val != 0x30) {
+ DEBUG_PRINTF("ERROR: AGC FIRMWARE INITIALIZATION FAILURE, STATUS 0x%02X\n", (uint8_t)read_val);
+ return LGW_HAL_ERROR;
+ }
+ }
+
+ /* Load Tx freq MSBs (always 3 if f > 768 for SX1257 or f > 384 for SX1255 */
+ lgw_reg_w(LGW_RADIO_SELECT, AGC_CMD_WAIT);
+ wait_ms(1);
+ lgw_reg_w(LGW_RADIO_SELECT, 3);
+ wait_ms(1);
+ lgw_reg_r(LGW_MCU_AGC_STATUS, &read_val);
+ if (read_val != 0x33) {
+ DEBUG_PRINTF("ERROR: AGC FIRMWARE INITIALIZATION FAILURE, STATUS 0x%02X\n", (uint8_t)read_val);
+ return LGW_HAL_ERROR;
+ }
+
+ /* Load chan_select firmware option */
+ lgw_reg_w(LGW_RADIO_SELECT, AGC_CMD_WAIT);
+ wait_ms(1);
+ lgw_reg_w(LGW_RADIO_SELECT, 0);
+ wait_ms(1);
+ lgw_reg_r(LGW_MCU_AGC_STATUS, &read_val);
+ if (read_val != 0x30) {
+ DEBUG_PRINTF("ERROR: AGC FIRMWARE INITIALIZATION FAILURE, STATUS 0x%02X\n", (uint8_t)read_val);
+ return LGW_HAL_ERROR;
+ }
+
+ /* End AGC firmware init and check status */
+ lgw_reg_w(LGW_RADIO_SELECT, AGC_CMD_WAIT);
+ wait_ms(1);
+ lgw_reg_w(LGW_RADIO_SELECT, radio_select); /* Load intended value of RADIO_SELECT */
+ wait_ms(1);
+ DEBUG_MSG("Info: putting back original RADIO_SELECT value\n");
+ lgw_reg_r(LGW_MCU_AGC_STATUS, &read_val);
+ if (read_val != 0x40) {
+ DEBUG_PRINTF("ERROR: AGC FIRMWARE INITIALIZATION FAILURE, STATUS 0x%02X\n", (uint8_t)read_val);
+ return LGW_HAL_ERROR;
+ }
+
+ /* enable GPS event capture */
+ lgw_reg_w(LGW_GPS_EN, 1);
+
+ /* */
+ if (lbt_is_enabled() == true) {
+ printf("INFO: Configuring LBT, this may take few seconds, please wait...\n");
+ wait_ms(8400);
+ }
+
+ lgw_is_started = true;
+ return LGW_HAL_SUCCESS;
+}
+
+/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
+
+int lgw_stop(void) {
+ lgw_soft_reset();
+ lgw_disconnect();
+
+ lgw_is_started = false;
+ return LGW_HAL_SUCCESS;
+}
+
+/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
+
+int lgw_receive(uint8_t max_pkt, struct lgw_pkt_rx_s *pkt_data) {
+ int nb_pkt_fetch; /* loop variable and return value */
+ struct lgw_pkt_rx_s *p; /* pointer to the current structure in the struct array */
+ uint8_t buff[255+RX_METADATA_NB]; /* buffer to store the result of SPI read bursts */
+ unsigned sz; /* size of the payload, uses to address metadata */
+ int ifmod; /* type of if_chain/modem a packet was received by */
+ int stat_fifo; /* the packet status as indicated in the FIFO */
+ uint32_t raw_timestamp; /* timestamp when internal 'RX finished' was triggered */
+ uint32_t delay_x, delay_y, delay_z; /* temporary variable for timestamp offset calculation */
+ uint32_t timestamp_correction; /* correction to account for processing delay */
+ uint32_t sf, cr, bw_pow, crc_en, ppm; /* used to calculate timestamp correction */
+
+ /* check if the concentrator is running */
+ if (lgw_is_started == false) {
+ DEBUG_MSG("ERROR: CONCENTRATOR IS NOT RUNNING, START IT BEFORE RECEIVING\n");
+ return LGW_HAL_ERROR;
+ }
+
+ /* check input variables */
+ if ((max_pkt <= 0) || (max_pkt > LGW_PKT_FIFO_SIZE)) {
+ DEBUG_PRINTF("ERROR: %d = INVALID MAX NUMBER OF PACKETS TO FETCH\n", max_pkt);
+ return LGW_HAL_ERROR;
+ }
+ CHECK_NULL(pkt_data);
+
+ /* Initialize buffer */
+ memset (buff, 0, sizeof buff);
+
+ /* iterate max_pkt times at most */
+ for (nb_pkt_fetch = 0; nb_pkt_fetch < max_pkt; ++nb_pkt_fetch) {
+
+ /* point to the proper struct in the struct array */
+ p = &pkt_data[nb_pkt_fetch];
+
+ /* fetch all the RX FIFO data */
+ lgw_reg_rb(LGW_RX_PACKET_DATA_FIFO_NUM_STORED, buff, 5);
+ /* 0: number of packets available in RX data buffer */
+ /* 1,2: start address of the current packet in RX data buffer */
+ /* 3: CRC status of the current packet */
+ /* 4: size of the current packet payload in byte */
+
+ /* how many packets are in the RX buffer ? Break if zero */
+ if (buff[0] == 0) {
+ break; /* no more packets to fetch, exit out of FOR loop */
+ }
+
+ /* sanity check */
+ if (buff[0] > LGW_PKT_FIFO_SIZE) {
+ DEBUG_PRINTF("WARNING: %u = INVALID NUMBER OF PACKETS TO FETCH, ABORTING\n", buff[0]);
+ break;
+ }
+
+ DEBUG_PRINTF("FIFO content: %x %x %x %x %x\n", buff[0], buff[1], buff[2], buff[3], buff[4]);
+
+ p->size = buff[4];
+ sz = p->size;
+ stat_fifo = buff[3]; /* will be used later, need to save it before overwriting buff */
+
+ /* get payload + metadata */
+ lgw_reg_rb(LGW_RX_DATA_BUF_DATA, buff, sz+RX_METADATA_NB);
+
+ /* copy payload to result struct */
+ memcpy((void *)p->payload, (void *)buff, sz);
+
+ /* process metadata */
+ p->if_chain = buff[sz+0];
+ if (p->if_chain >= LGW_IF_CHAIN_NB) {
+ DEBUG_PRINTF("WARNING: %u NOT A VALID IF_CHAIN NUMBER, ABORTING\n", p->if_chain);
+ break;
+ }
+ ifmod = ifmod_config[p->if_chain];
+ DEBUG_PRINTF("[%d %d]\n", p->if_chain, ifmod);
+
+ p->rf_chain = (uint8_t)if_rf_chain[p->if_chain];
+ p->freq_hz = (uint32_t)((int32_t)rf_rx_freq[p->rf_chain] + if_freq[p->if_chain]);
+ p->rssi = (float)buff[sz+5] + rf_rssi_offset[p->rf_chain];
+
+ if ((ifmod == IF_LORA_MULTI) || (ifmod == IF_LORA_STD)) {
+ DEBUG_MSG("Note: LoRa packet\n");
+ switch(stat_fifo & 0x07) {
+ case 5:
+ p->status = STAT_CRC_OK;
+ crc_en = 1;
+ break;
+ case 7:
+ p->status = STAT_CRC_BAD;
+ crc_en = 1;
+ break;
+ case 1:
+ p->status = STAT_NO_CRC;
+ crc_en = 0;
+ break;
+ default:
+ p->status = STAT_UNDEFINED;
+ crc_en = 0;
+ }
+ p->modulation = MOD_LORA;
+ p->snr = ((float)((int8_t)buff[sz+2]))/4;
+ p->snr_min = ((float)((int8_t)buff[sz+3]))/4;
+ p->snr_max = ((float)((int8_t)buff[sz+4]))/4;
+ if (ifmod == IF_LORA_MULTI) {
+ p->bandwidth = BW_125KHZ; /* fixed in hardware */
+ } else {
+ p->bandwidth = lora_rx_bw; /* get the parameter from the config variable */
+ }
+ sf = (buff[sz+1] >> 4) & 0x0F;
+ switch (sf) {
+ case 7: p->datarate = DR_LORA_SF7; break;
+ case 8: p->datarate = DR_LORA_SF8; break;
+ case 9: p->datarate = DR_LORA_SF9; break;
+ case 10: p->datarate = DR_LORA_SF10; break;
+ case 11: p->datarate = DR_LORA_SF11; break;
+ case 12: p->datarate = DR_LORA_SF12; break;
+ default: p->datarate = DR_UNDEFINED;
+ }
+ cr = (buff[sz+1] >> 1) & 0x07;
+ switch (cr) {
+ case 1: p->coderate = CR_LORA_4_5; break;
+ case 2: p->coderate = CR_LORA_4_6; break;
+ case 3: p->coderate = CR_LORA_4_7; break;
+ case 4: p->coderate = CR_LORA_4_8; break;
+ default: p->coderate = CR_UNDEFINED;
+ }
+
+ /* determine if 'PPM mode' is on, needed for timestamp correction */
+ if (SET_PPM_ON(p->bandwidth,p->datarate)) {
+ ppm = 1;
+ } else {
+ ppm = 0;
+ }
+
+ /* timestamp correction code, base delay */
+ if (ifmod == IF_LORA_STD) { /* if packet was received on the stand-alone LoRa modem */
+ switch (lora_rx_bw) {
+ case BW_125KHZ:
+ delay_x = 64;
+ bw_pow = 1;
+ break;
+ case BW_250KHZ:
+ delay_x = 32;
+ bw_pow = 2;
+ break;
+ case BW_500KHZ:
+ delay_x = 16;
+ bw_pow = 4;
+ break;
+ default:
+ DEBUG_PRINTF("ERROR: UNEXPECTED VALUE %d IN SWITCH STATEMENT\n", p->bandwidth);
+ delay_x = 0;
+ bw_pow = 0;
+ }
+ } else { /* packet was received on one of the sensor channels = 125kHz */
+ delay_x = 114;
+ bw_pow = 1;
+ }
+
+ /* timestamp correction code, variable delay */
+ if ((sf >= 6) && (sf <= 12) && (bw_pow > 0)) {
+ if ((2*(sz + 2*crc_en) - (sf-7)) <= 0) { /* payload fits entirely in first 8 symbols */
+ delay_y = ( ((1<<(sf-1)) * (sf+1)) + (3 * (1<<(sf-4))) ) / bw_pow;
+ delay_z = 32 * (2*(sz+2*crc_en) + 5) / bw_pow;
+ } else {
+ delay_y = ( ((1<<(sf-1)) * (sf+1)) + ((4 - ppm) * (1<<(sf-4))) ) / bw_pow;
+ delay_z = (16 + 4*cr) * (((2*(sz+2*crc_en)-sf+6) % (sf - 2*ppm)) + 1) / bw_pow;
+ }
+ timestamp_correction = delay_x + delay_y + delay_z;
+ } else {
+ timestamp_correction = 0;
+ DEBUG_MSG("WARNING: invalid packet, no timestamp correction\n");
+ }
+
+ /* RSSI correction */
+ if (ifmod == IF_LORA_MULTI) {
+ p->rssi -= RSSI_MULTI_BIAS;
+ }
+
+ } else if (ifmod == IF_FSK_STD) {
+ DEBUG_MSG("Note: FSK packet\n");
+ switch(stat_fifo & 0x07) {
+ case 5:
+ p->status = STAT_CRC_OK;
+ break;
+ case 7:
+ p->status = STAT_CRC_BAD;
+ break;
+ case 1:
+ p->status = STAT_NO_CRC;
+ break;
+ default:
+ p->status = STAT_UNDEFINED;
+ break;
+ }
+ p->modulation = MOD_FSK;
+ p->snr = -128.0;
+ p->snr_min = -128.0;
+ p->snr_max = -128.0;
+ p->bandwidth = fsk_rx_bw;
+ p->datarate = fsk_rx_dr;
+ p->coderate = CR_UNDEFINED;
+ timestamp_correction = ((uint32_t)680000 / fsk_rx_dr) - 20;
+
+ /* RSSI correction */
+ p->rssi = RSSI_FSK_POLY_0 + RSSI_FSK_POLY_1 * p->rssi + RSSI_FSK_POLY_2 * pow(p->rssi, 2);
+ } else {
+ DEBUG_MSG("ERROR: UNEXPECTED PACKET ORIGIN\n");
+ p->status = STAT_UNDEFINED;
+ p->modulation = MOD_UNDEFINED;
+ p->rssi = -128.0;
+ p->snr = -128.0;
+ p->snr_min = -128.0;
+ p->snr_max = -128.0;
+ p->bandwidth = BW_UNDEFINED;
+ p->datarate = DR_UNDEFINED;
+ p->coderate = CR_UNDEFINED;
+ timestamp_correction = 0;
+ }
+
+ raw_timestamp = (uint32_t)buff[sz+6] + ((uint32_t)buff[sz+7] << 8) + ((uint32_t)buff[sz+8] << 16) + ((uint32_t)buff[sz+9] << 24);
+ p->count_us = raw_timestamp - timestamp_correction;
+ p->crc = (uint16_t)buff[sz+10] + ((uint16_t)buff[sz+11] << 8);
+
+ /* advance packet FIFO */
+ lgw_reg_w(LGW_RX_PACKET_DATA_FIFO_NUM_STORED, 0);
+ }
+
+ return nb_pkt_fetch;
+}
+
+/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
+
+int lgw_send(struct lgw_pkt_tx_s pkt_data) {
+ int i, x;
+ uint8_t buff[256+TX_METADATA_NB]; /* buffer to prepare the packet to send + metadata before SPI write burst */
+ uint32_t part_int = 0; /* integer part for PLL register value calculation */
+ uint32_t part_frac = 0; /* fractional part for PLL register value calculation */
+ uint16_t fsk_dr_div; /* divider to configure for target datarate */
+ int transfer_size = 0; /* data to transfer from host to TX databuffer */
+ int payload_offset = 0; /* start of the payload content in the databuffer */
+ uint8_t pow_index = 0; /* 4-bit value to set the firmware TX power */
+ uint8_t target_mix_gain = 0; /* used to select the proper I/Q offset correction */
+ uint32_t count_trig = 0; /* timestamp value in trigger mode corrected for TX start delay */
+ bool tx_allowed = false;
+ uint16_t tx_start_delay;
+ bool tx_notch_enable = false;
+
+ /* check if the concentrator is running */
+ if (lgw_is_started == false) {
+ DEBUG_MSG("ERROR: CONCENTRATOR IS NOT RUNNING, START IT BEFORE SENDING\n");
+ return LGW_HAL_ERROR;
+ }
+
+ /* check input range (segfault prevention) */
+ if (pkt_data.rf_chain >= LGW_RF_CHAIN_NB) {
+ DEBUG_MSG("ERROR: INVALID RF_CHAIN TO SEND PACKETS\n");
+ return LGW_HAL_ERROR;
+ }
+
+ /* check input variables */
+ if (rf_tx_enable[pkt_data.rf_chain] == false) {
+ DEBUG_MSG("ERROR: SELECTED RF_CHAIN IS DISABLED FOR TX ON SELECTED BOARD\n");
+ return LGW_HAL_ERROR;
+ }
+ if (rf_enable[pkt_data.rf_chain] == false) {
+ DEBUG_MSG("ERROR: SELECTED RF_CHAIN IS DISABLED\n");
+ return LGW_HAL_ERROR;
+ }
+ if (!IS_TX_MODE(pkt_data.tx_mode)) {
+ DEBUG_MSG("ERROR: TX_MODE NOT SUPPORTED\n");
+ return LGW_HAL_ERROR;
+ }
+ if (pkt_data.modulation == MOD_LORA) {
+ if (!IS_LORA_BW(pkt_data.bandwidth)) {
+ DEBUG_MSG("ERROR: BANDWIDTH NOT SUPPORTED BY LORA TX\n");
+ return LGW_HAL_ERROR;
+ }
+ if (!IS_LORA_STD_DR(pkt_data.datarate)) {
+ DEBUG_MSG("ERROR: DATARATE NOT SUPPORTED BY LORA TX\n");
+ return LGW_HAL_ERROR;
+ }
+ if (!IS_LORA_CR(pkt_data.coderate)) {
+ DEBUG_MSG("ERROR: CODERATE NOT SUPPORTED BY LORA TX\n");
+ return LGW_HAL_ERROR;
+ }
+ if (pkt_data.size > 255) {
+ DEBUG_MSG("ERROR: PAYLOAD LENGTH TOO BIG FOR LORA TX\n");
+ return LGW_HAL_ERROR;
+ }
+ } else if (pkt_data.modulation == MOD_FSK) {
+ if((pkt_data.f_dev < 1) || (pkt_data.f_dev > 200)) {
+ DEBUG_MSG("ERROR: TX FREQUENCY DEVIATION OUT OF ACCEPTABLE RANGE\n");
+ return LGW_HAL_ERROR;
+ }
+ if(!IS_FSK_DR(pkt_data.datarate)) {
+ DEBUG_MSG("ERROR: DATARATE NOT SUPPORTED BY FSK IF CHAIN\n");
+ return LGW_HAL_ERROR;
+ }
+ if (pkt_data.size > 255) {
+ DEBUG_MSG("ERROR: PAYLOAD LENGTH TOO BIG FOR FSK TX\n");
+ return LGW_HAL_ERROR;
+ }
+ } else {
+ DEBUG_MSG("ERROR: INVALID TX MODULATION\n");
+ return LGW_HAL_ERROR;
+ }
+
+ /* Enable notch filter for LoRa 125kHz */
+ if ((pkt_data.modulation == MOD_LORA) && (pkt_data.bandwidth == BW_125KHZ)) {
+ tx_notch_enable = true;
+ }
+
+ /* Get the TX start delay to be applied for this TX */
+ tx_start_delay = lgw_get_tx_start_delay(tx_notch_enable, pkt_data.bandwidth);
+
+ /* interpretation of TX power */
+ for (pow_index = txgain_lut.size-1; pow_index > 0; pow_index--) {
+ if (txgain_lut.lut[pow_index].rf_power <= pkt_data.rf_power) {
+ break;
+ }
+ }
+
+ /* loading TX imbalance correction */
+ target_mix_gain = txgain_lut.lut[pow_index].mix_gain;
+ if (pkt_data.rf_chain == 0) { /* use radio A calibration table */
+ lgw_reg_w(LGW_TX_OFFSET_I, cal_offset_a_i[target_mix_gain - 8]);
+ lgw_reg_w(LGW_TX_OFFSET_Q, cal_offset_a_q[target_mix_gain - 8]);
+ } else { /* use radio B calibration table */
+ lgw_reg_w(LGW_TX_OFFSET_I, cal_offset_b_i[target_mix_gain - 8]);
+ lgw_reg_w(LGW_TX_OFFSET_Q, cal_offset_b_q[target_mix_gain - 8]);
+ }
+
+ /* Set digital gain from LUT */
+ lgw_reg_w(LGW_TX_GAIN, txgain_lut.lut[pow_index].dig_gain);
+
+ /* fixed metadata, useful payload and misc metadata compositing */
+ transfer_size = TX_METADATA_NB + pkt_data.size; /* */
+ payload_offset = TX_METADATA_NB; /* start the payload just after the metadata */
+
+ /* metadata 0 to 2, TX PLL frequency */
+ switch (rf_radio_type[0]) { /* we assume that there is only one radio type on the board */
+ case LGW_RADIO_TYPE_SX1255:
+ part_int = pkt_data.freq_hz / (SX125x_32MHz_FRAC << 7); /* integer part, gives the MSB */
+ part_frac = ((pkt_data.freq_hz % (SX125x_32MHz_FRAC << 7)) << 9) / SX125x_32MHz_FRAC; /* fractional part, gives middle part and LSB */
+ break;
+ case LGW_RADIO_TYPE_SX1257:
+ part_int = pkt_data.freq_hz / (SX125x_32MHz_FRAC << 8); /* integer part, gives the MSB */
+ part_frac = ((pkt_data.freq_hz % (SX125x_32MHz_FRAC << 8)) << 8) / SX125x_32MHz_FRAC; /* fractional part, gives middle part and LSB */
+ break;
+ default:
+ DEBUG_PRINTF("ERROR: UNEXPECTED VALUE %d FOR RADIO TYPE\n", rf_radio_type[0]);
+ break;
+ }
+
+ buff[0] = 0xFF & part_int; /* Most Significant Byte */
+ buff[1] = 0xFF & (part_frac >> 8); /* middle byte */
+ buff[2] = 0xFF & part_frac; /* Least Significant Byte */
+
+ /* metadata 3 to 6, timestamp trigger value */
+ /* TX state machine must be triggered at (T0 - lgw_i_tx_start_delay_us) for packet to start being emitted at T0 */
+ if (pkt_data.tx_mode == TIMESTAMPED)
+ {
+ count_trig = pkt_data.count_us - (uint32_t)tx_start_delay;
+ buff[3] = 0xFF & (count_trig >> 24);
+ buff[4] = 0xFF & (count_trig >> 16);
+ buff[5] = 0xFF & (count_trig >> 8);
+ buff[6] = 0xFF & count_trig;
+ }
+
+ /* parameters depending on modulation */
+ if (pkt_data.modulation == MOD_LORA) {
+ /* metadata 7, modulation type, radio chain selection and TX power */
+ buff[7] = (0x20 & (pkt_data.rf_chain << 5)) | (0x0F & pow_index); /* bit 4 is 0 -> LoRa modulation */
+
+ buff[8] = 0; /* metadata 8, not used */
+
+ /* metadata 9, CRC, LoRa CR & SF */
+ switch (pkt_data.datarate) {
+ case DR_LORA_SF7: buff[9] = 7; break;
+ case DR_LORA_SF8: buff[9] = 8; break;
+ case DR_LORA_SF9: buff[9] = 9; break;
+ case DR_LORA_SF10: buff[9] = 10; break;
+ case DR_LORA_SF11: buff[9] = 11; break;
+ case DR_LORA_SF12: buff[9] = 12; break;
+ default: DEBUG_PRINTF("ERROR: UNEXPECTED VALUE %d IN SWITCH STATEMENT\n", pkt_data.datarate);
+ }
+ switch (pkt_data.coderate) {
+ case CR_LORA_4_5: buff[9] |= 1 << 4; break;
+ case CR_LORA_4_6: buff[9] |= 2 << 4; break;
+ case CR_LORA_4_7: buff[9] |= 3 << 4; break;
+ case CR_LORA_4_8: buff[9] |= 4 << 4; break;
+ default: DEBUG_PRINTF("ERROR: UNEXPECTED VALUE %d IN SWITCH STATEMENT\n", pkt_data.coderate);
+ }
+ if (pkt_data.no_crc == false) {
+ buff[9] |= 0x80; /* set 'CRC enable' bit */
+ } else {
+ DEBUG_MSG("Info: packet will be sent without CRC\n");
+ }
+
+ /* metadata 10, payload size */
+ buff[10] = pkt_data.size;
+
+ /* metadata 11, implicit header, modulation bandwidth, PPM offset & polarity */
+ switch (pkt_data.bandwidth) {
+ case BW_125KHZ: buff[11] = 0; break;
+ case BW_250KHZ: buff[11] = 1; break;
+ case BW_500KHZ: buff[11] = 2; break;
+ default: DEBUG_PRINTF("ERROR: UNEXPECTED VALUE %d IN SWITCH STATEMENT\n", pkt_data.bandwidth);
+ }
+ if (pkt_data.no_header == true) {
+ buff[11] |= 0x04; /* set 'implicit header' bit */
+ }
+ if (SET_PPM_ON(pkt_data.bandwidth,pkt_data.datarate)) {
+ buff[11] |= 0x08; /* set 'PPM offset' bit at 1 */
+ }
+ if (pkt_data.invert_pol == true) {
+ buff[11] |= 0x10; /* set 'TX polarity' bit at 1 */
+ }
+
+ /* metadata 12 & 13, LoRa preamble size */
+ if (pkt_data.preamble == 0) { /* if not explicit, use recommended LoRa preamble size */
+ pkt_data.preamble = STD_LORA_PREAMBLE;
+ } else if (pkt_data.preamble < MIN_LORA_PREAMBLE) { /* enforce minimum preamble size */
+ pkt_data.preamble = MIN_LORA_PREAMBLE;
+ DEBUG_MSG("Note: preamble length adjusted to respect minimum LoRa preamble size\n");
+ }
+ buff[12] = 0xFF & (pkt_data.preamble >> 8);
+ buff[13] = 0xFF & pkt_data.preamble;
+
+ /* metadata 14 & 15, not used */
+ buff[14] = 0;
+ buff[15] = 0;
+
+ /* MSB of RF frequency is now used in AGC firmware to implement large/narrow filtering in SX1257/55 */
+ buff[0] &= 0x3F; /* Unset 2 MSBs of frequency code */
+ if (pkt_data.bandwidth == BW_500KHZ) {
+ buff[0] |= 0x80; /* Set MSB bit to enlarge analog filter for 500kHz BW */
+ }
+
+ /* Set MSB-1 bit to enable digital filter if required */
+ if (tx_notch_enable == true) {
+ DEBUG_MSG("INFO: Enabling TX notch filter\n");
+ buff[0] |= 0x40;
+ }
+ } else if (pkt_data.modulation == MOD_FSK) {
+ /* metadata 7, modulation type, radio chain selection and TX power */
+ buff[7] = (0x20 & (pkt_data.rf_chain << 5)) | 0x10 | (0x0F & pow_index); /* bit 4 is 1 -> FSK modulation */
+
+ buff[8] = 0; /* metadata 8, not used */
+
+ /* metadata 9, frequency deviation */
+ buff[9] = pkt_data.f_dev;
+
+ /* metadata 10, payload size */
+ buff[10] = pkt_data.size;
+ /* TODO: how to handle 255 bytes packets ?!? */
+
+ /* metadata 11, packet mode, CRC, encoding */
+ buff[11] = 0x01 | (pkt_data.no_crc?0:0x02) | (0x02 << 2); /* always in variable length packet mode, whitening, and CCITT CRC if CRC is not disabled */
+
+ /* metadata 12 & 13, FSK preamble size */
+ if (pkt_data.preamble == 0) { /* if not explicit, use LoRa MAC preamble size */
+ pkt_data.preamble = STD_FSK_PREAMBLE;
+ } else if (pkt_data.preamble < MIN_FSK_PREAMBLE) { /* enforce minimum preamble size */
+ pkt_data.preamble = MIN_FSK_PREAMBLE;
+ DEBUG_MSG("Note: preamble length adjusted to respect minimum FSK preamble size\n");
+ }
+ buff[12] = 0xFF & (pkt_data.preamble >> 8);
+ buff[13] = 0xFF & pkt_data.preamble;
+
+ /* metadata 14 & 15, FSK baudrate */
+ fsk_dr_div = (uint16_t)((uint32_t)LGW_XTAL_FREQU / pkt_data.datarate); /* Ok for datarate between 500bps and 250kbps */
+ buff[14] = 0xFF & (fsk_dr_div >> 8);
+ buff[15] = 0xFF & fsk_dr_div;
+
+ /* insert payload size in the packet for variable mode */
+ buff[16] = pkt_data.size;
+ ++transfer_size; /* one more byte to transfer to the TX modem */
+ ++payload_offset; /* start the payload with one more byte of offset */
+
+ /* MSB of RF frequency is now used in AGC firmware to implement large/narrow filtering in SX1257/55 */
+ buff[0] &= 0x7F; /* Always use narrow band for FSK (force MSB to 0) */
+
+ } else {
+ DEBUG_MSG("ERROR: INVALID TX MODULATION..\n");
+ return LGW_HAL_ERROR;
+ }
+
+ /* Configure TX start delay based on TX notch filter */
+ lgw_reg_w(LGW_TX_START_DELAY, tx_start_delay);
+
+ /* copy payload from user struct to buffer containing metadata */
+ memcpy((void *)(buff + payload_offset), (void *)(pkt_data.payload), pkt_data.size);
+
+ /* reset TX command flags */
+ lgw_abort_tx();
+
+ /* put metadata + payload in the TX data buffer */
+ lgw_reg_w(LGW_TX_DATA_BUF_ADDR, 0);
+ lgw_reg_wb(LGW_TX_DATA_BUF_DATA, buff, transfer_size);
+ DEBUG_ARRAY(i, transfer_size, buff);
+
+ x = lbt_is_channel_free(&pkt_data, tx_start_delay, &tx_allowed);
+ if (x != LGW_LBT_SUCCESS) {
+ DEBUG_MSG("ERROR: Failed to check channel availability for TX\n");
+ return LGW_HAL_ERROR;
+ }
+ if (tx_allowed == true) {
+ switch(pkt_data.tx_mode) {
+ case IMMEDIATE:
+ lgw_reg_w(LGW_TX_TRIG_IMMEDIATE, 1);
+ break;
+
+ case TIMESTAMPED:
+ lgw_reg_w(LGW_TX_TRIG_DELAYED, 1);
+ break;
+
+ case ON_GPS:
+ lgw_reg_w(LGW_TX_TRIG_GPS, 1);
+ break;
+
+ default:
+ DEBUG_PRINTF("ERROR: UNEXPECTED VALUE %d IN SWITCH STATEMENT\n", pkt_data.tx_mode);
+ return LGW_HAL_ERROR;
+ }
+ } else {
+ DEBUG_MSG("ERROR: Cannot send packet, channel is busy (LBT)\n");
+ return LGW_LBT_ISSUE;
+ }
+
+ return LGW_HAL_SUCCESS;
+}
+
+/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
+
+int lgw_status(uint8_t select, uint8_t *code) {
+ int32_t read_value;
+
+ /* check input variables */
+ CHECK_NULL(code);
+
+ if (select == TX_STATUS) {
+ lgw_reg_r(LGW_TX_STATUS, &read_value);
+ if (lgw_is_started == false) {
+ *code = TX_OFF;
+ } else if ((read_value & 0x10) == 0) { /* bit 4 @1: TX programmed */
+ *code = TX_FREE;
+ } else if ((read_value & 0x60) != 0) { /* bit 5 or 6 @1: TX sequence */
+ *code = TX_EMITTING;
+ } else {
+ *code = TX_SCHEDULED;
+ }
+ return LGW_HAL_SUCCESS;
+
+ } else if (select == RX_STATUS) {
+ *code = RX_STATUS_UNKNOWN; /* todo */
+ return LGW_HAL_SUCCESS;
+
+ } else {
+ DEBUG_MSG("ERROR: SELECTION INVALID, NO STATUS TO RETURN\n");
+ return LGW_HAL_ERROR;
+ }
+
+}
+
+/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
+
+int lgw_abort_tx(void) {
+ int i;
+
+ i = lgw_reg_w(LGW_TX_TRIG_ALL, 0);
+
+ if (i == LGW_REG_SUCCESS) return LGW_HAL_SUCCESS;
+ else return LGW_HAL_ERROR;
+}
+
+/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
+
+int lgw_get_trigcnt(uint32_t* trig_cnt_us) {
+ int i;
+ int32_t val;
+
+ i = lgw_reg_r(LGW_TIMESTAMP, &val);
+ if (i == LGW_REG_SUCCESS) {
+ *trig_cnt_us = (uint32_t)val;
+ return LGW_HAL_SUCCESS;
+ } else {
+ return LGW_HAL_ERROR;
+ }
+}
+
+/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
+
+const char* lgw_version_info() {
+ return lgw_version_string;
+}
+
+/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
+
+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;
+
+ if (packet == NULL) {
+ DEBUG_MSG("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;
+ } 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;
+ } else if (packet->modulation == MOD_FSK) {
+ /* PREAMBLE + SYNC_WORD + PKT_LEN + PKT_PAYLOAD + CRC
+ PREAMBLE: default 5 bytes
+ SYNC_WORD: default 3 bytes
+ PKT_LEN: 1 byte (variable length mode)
+ PKT_PAYLOAD: x bytes
+ CRC: 0 or 2 bytes
+ */
+ Tfsk = (8 * (double)(packet->preamble + 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 */
+ } else {
+ Tpacket = 0;
+ DEBUG_PRINTF("ERROR: Cannot compute time on air for this packet, unsupported modulation (0x%02X)\n", packet->modulation);
+ }
+
+ return Tpacket;
+}
+
+/* --- EOF ------------------------------------------------------------------ */
diff --git a/libloragw/src/loragw_lbt.c b/libloragw/src/loragw_lbt.c
new file mode 100644
index 0000000..9c43521
--- /dev/null
+++ b/libloragw/src/loragw_lbt.c
@@ -0,0 +1,391 @@
+/*
+ / _____) _ | |
+( (____ _____ ____ _| |_ _____ ____| |__
+ \____ \| ___ | (_ _) ___ |/ ___) _ \
+ _____) ) ____| | | || |_| ____( (___| | | |
+(______/|_____)_|_|_| \__)_____)\____)_| |_|
+ (C)2013 Semtech-Cycleo
+
+Description:
+ Functions used to handle the Listen Before Talk feature
+
+License: Revised BSD License, see LICENSE.TXT file include in the project
+Maintainer: Michael Coracin
+*/
+
+/* -------------------------------------------------------------------------- */
+/* --- DEPENDANCIES --------------------------------------------------------- */
+
+#include <stdint.h> /* C99 types */
+#include <stdbool.h> /* bool type */
+#include <stdio.h> /* printf fprintf */
+#include <stdlib.h> /* abs, labs, llabs */
+#include <string.h> /* memset */
+
+#include "loragw_radio.h"
+#include "loragw_aux.h"
+#include "loragw_lbt.h"
+#include "loragw_fpga.h"
+
+/* -------------------------------------------------------------------------- */
+/* --- PRIVATE MACROS ------------------------------------------------------- */
+
+#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
+#if DEBUG_LBT == 1
+ #define DEBUG_MSG(str) fprintf(stderr, str)
+ #define DEBUG_PRINTF(fmt, args...) fprintf(stderr,"%s:%d: "fmt, __FUNCTION__, __LINE__, args)
+ #define CHECK_NULL(a) if(a==NULL){fprintf(stderr,"%s:%d: ERROR: NULL POINTER AS ARGUMENT\n", __FUNCTION__, __LINE__);return LGW_REG_ERROR;}
+#else
+ #define DEBUG_MSG(str)
+ #define DEBUG_PRINTF(fmt, args...)
+ #define CHECK_NULL(a) if(a==NULL){return LGW_REG_ERROR;}
+#endif
+
+#define LBT_TIMESTAMP_MASK 0x007FF000 /* 11-bits timestamp */
+
+/* -------------------------------------------------------------------------- */
+/* --- PRIVATE TYPES -------------------------------------------------------- */
+
+/* -------------------------------------------------------------------------- */
+/* --- PRIVATE CONSTANTS ---------------------------------------------------- */
+
+/* -------------------------------------------------------------------------- */
+/* --- INTERNAL SHARED VARIABLES -------------------------------------------- */
+
+extern void *lgw_spi_target; /*! generic pointer to the SPI device */
+extern uint8_t lgw_spi_mux_mode; /*! current SPI mux mode used */
+extern uint16_t lgw_i_tx_start_delay_us;
+
+/* -------------------------------------------------------------------------- */
+/* --- PRIVATE VARIABLES ---------------------------------------------------- */
+
+static bool lbt_enable;
+static uint8_t lbt_nb_active_channel;
+static int8_t lbt_rssi_target_dBm;
+static int8_t lbt_rssi_offset_dB;
+static uint32_t lbt_start_freq;
+static struct lgw_conf_lbt_chan_s lbt_channel_cfg[LBT_CHANNEL_FREQ_NB];
+
+/* -------------------------------------------------------------------------- */
+/* --- PRIVATE FUNCTIONS ---------------------------------------------------- */
+
+bool is_equal_freq(uint32_t a, uint32_t b);
+
+/* -------------------------------------------------------------------------- */
+/* --- PUBLIC FUNCTIONS DEFINITION ------------------------------------------ */
+
+int lbt_setconf(struct lgw_conf_lbt_s * conf) {
+ int i;
+
+ /* Check input parameters */
+ if (conf == NULL) {
+ return LGW_LBT_ERROR;
+ }
+ if ((conf->nb_channel < 1) || (conf->nb_channel > LBT_CHANNEL_FREQ_NB)) {
+ DEBUG_PRINTF("ERROR: Number of defined LBT channels is out of range (%u)\n", conf->nb_channel);
+ return LGW_LBT_ERROR;
+ }
+
+ /* Initialize LBT channels configuration */
+ memset(lbt_channel_cfg, 0, sizeof lbt_channel_cfg);
+
+ /* Set internal LBT config according to parameters */
+ lbt_enable = conf->enable;
+ lbt_nb_active_channel = conf->nb_channel;
+ lbt_rssi_target_dBm = conf->rssi_target;
+ lbt_rssi_offset_dB = conf->rssi_offset;
+
+ for (i=0; i<lbt_nb_active_channel; i++) {
+ lbt_channel_cfg[i].freq_hz = conf->channels[i].freq_hz;
+ lbt_channel_cfg[i].scan_time_us = conf->channels[i].scan_time_us;
+ }
+
+ return LGW_LBT_SUCCESS;
+}
+
+/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
+
+int lbt_setup(void) {
+ int x, i;
+ int32_t val;
+ uint32_t freq_offset;
+
+ /* Check if LBT feature is supported by FPGA */
+ x = lgw_fpga_reg_r(LGW_FPGA_FEATURE, &val);
+ if (x != LGW_REG_SUCCESS) {
+ DEBUG_MSG("ERROR: Failed to read FPGA Features register\n");
+ return LGW_LBT_ERROR;
+ }
+ if (TAKE_N_BITS_FROM((uint8_t)val, 2, 1) != 1) {
+ DEBUG_MSG("ERROR: No support for LBT in FPGA\n");
+ return LGW_LBT_ERROR;
+ }
+
+ /* Get FPGA lowest frequency for LBT channels */
+ x = lgw_fpga_reg_r(LGW_FPGA_LBT_INITIAL_FREQ, &val);
+ if (x != LGW_REG_SUCCESS) {
+ DEBUG_MSG("ERROR: Failed to read LBT initial frequency from FPGA\n");
+ return LGW_LBT_ERROR;
+ }
+ switch(val) {
+ case 0:
+ lbt_start_freq = 915000000;
+ break;
+ case 1:
+ lbt_start_freq = 863000000;
+ break;
+ default:
+ DEBUG_PRINTF("ERROR: LBT start frequency %d is not supported\n", val);
+ return LGW_LBT_ERROR;
+ }
+
+ /* Configure SX127x for FSK */
+ x = lgw_setup_sx127x(lbt_start_freq, MOD_FSK, LGW_SX127X_RXBW_100K_HZ, lbt_rssi_offset_dB); /* 200KHz LBT channels */
+ if (x != LGW_REG_SUCCESS) {
+ DEBUG_MSG("ERROR: Failed to configure SX127x for LBT\n");
+ return LGW_LBT_ERROR;
+ }
+
+ /* Configure FPGA for LBT */
+ val = -2*lbt_rssi_target_dBm; /* Convert RSSI target in dBm to FPGA register format */
+ x = lgw_fpga_reg_w(LGW_FPGA_RSSI_TARGET, val);
+ if (x != LGW_REG_SUCCESS) {
+ DEBUG_MSG("ERROR: Failed to configure FPGA for LBT\n");
+ return LGW_LBT_ERROR;
+ }
+ /* Set default values for non-active LBT channels */
+ for (i=lbt_nb_active_channel; i<LBT_CHANNEL_FREQ_NB; i++) {
+ lbt_channel_cfg[i].freq_hz = lbt_start_freq;
+ lbt_channel_cfg[i].scan_time_us = 128; /* fastest scan for non-active channels */
+ }
+ /* Configure FPGA for both active and non-active LBT channels */
+ for (i=0; i<LBT_CHANNEL_FREQ_NB; i++) {
+ /* Check input parameters */
+ if (lbt_channel_cfg[i].freq_hz < lbt_start_freq) {
+ DEBUG_PRINTF("ERROR: LBT channel frequency is out of range (%u)\n", lbt_channel_cfg[i].freq_hz);
+ return LGW_LBT_ERROR;
+ }
+ if ((lbt_channel_cfg[i].scan_time_us != 128) && (lbt_channel_cfg[i].scan_time_us != 5000)) {
+ DEBUG_PRINTF("ERROR: LBT channel scan time is not supported (%u)\n", lbt_channel_cfg[i].scan_time_us);
+ return LGW_LBT_ERROR;
+ }
+ /* Configure */
+ freq_offset = (lbt_channel_cfg[i].freq_hz - lbt_start_freq) / 100E3; /* 100kHz unit */
+ x = lgw_fpga_reg_w(LGW_FPGA_LBT_CH0_FREQ_OFFSET+i, (int32_t)freq_offset);
+ if (x != LGW_REG_SUCCESS) {
+ DEBUG_PRINTF("ERROR: Failed to configure FPGA for LBT channel %d (freq offset)\n", i);
+ return LGW_LBT_ERROR;
+ }
+ if (lbt_channel_cfg[i].scan_time_us == 5000) { /* configured to 128 by default */
+ x = lgw_fpga_reg_w(LGW_FPGA_LBT_SCAN_TIME_CH0+i, 1);
+ if (x != LGW_REG_SUCCESS) {
+ DEBUG_PRINTF("ERROR: Failed to configure FPGA for LBT channel %d (freq offset)\n", i);
+ return LGW_LBT_ERROR;
+ }
+ }
+ }
+
+ DEBUG_MSG("Note: LBT configuration:\n");
+ DEBUG_PRINTF("\tlbt_enable: %d\n", lbt_enable );
+ DEBUG_PRINTF("\tlbt_nb_active_channel: %d\n", lbt_nb_active_channel );
+ DEBUG_PRINTF("\tlbt_start_freq: %d\n", lbt_start_freq);
+ DEBUG_PRINTF("\tlbt_rssi_target: %d\n", lbt_rssi_target_dBm );
+ for (i=0; i<LBT_CHANNEL_FREQ_NB; i++) {
+ DEBUG_PRINTF("\tlbt_channel_cfg[%d].freq_hz: %u\n", i, lbt_channel_cfg[i].freq_hz );
+ DEBUG_PRINTF("\tlbt_channel_cfg[%d].scan_time_us: %u\n", i, lbt_channel_cfg[i].scan_time_us );
+ }
+
+ return LGW_LBT_SUCCESS;
+
+}
+
+/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
+
+int lbt_start(void) {
+ int x;
+
+ x = lgw_fpga_reg_w(LGW_FPGA_CTRL_FEATURE_START, 1);
+ if (x != LGW_REG_SUCCESS) {
+ DEBUG_MSG("ERROR: Failed to start LBT FSM\n");
+ return LGW_LBT_ERROR;
+ }
+
+ return LGW_LBT_SUCCESS;
+}
+
+/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
+
+int lbt_is_channel_free(struct lgw_pkt_tx_s * pkt_data, uint16_t tx_start_delay, bool * tx_allowed) {
+ int i;
+ int32_t val;
+ uint32_t tx_start_time = 0;
+ uint32_t tx_end_time = 0;
+ uint32_t delta_time = 0;
+ uint32_t sx1301_time = 0;
+ uint32_t lbt_time = 0;
+ uint32_t lbt_time1 = 0;
+ uint32_t lbt_time2 = 0;
+ uint32_t tx_max_time = 0;
+ int lbt_channel_decod_1 = -1;
+ int lbt_channel_decod_2 = -1;
+ uint32_t packet_duration = 0;
+
+ /* Check input parameters */
+ if ((pkt_data == NULL) || (tx_allowed == NULL)) {
+ return LGW_LBT_ERROR;
+ }
+
+ /* Check if TX is allowed */
+ if (lbt_enable == true) {
+ /* TX allowed for LoRa only */
+ if (pkt_data->modulation != MOD_LORA) {
+ *tx_allowed = false;
+ DEBUG_PRINTF("INFO: TX is not allowed for this modulation (%x)\n", pkt_data->modulation);
+ return LGW_LBT_SUCCESS;
+ }
+
+ /* Get SX1301 time at last PPS */
+ lgw_get_trigcnt(&sx1301_time);
+
+ DEBUG_MSG("################################\n");
+ switch(pkt_data->tx_mode) {
+ case TIMESTAMPED:
+ DEBUG_MSG("tx_mode = TIMESTAMPED\n");
+ tx_start_time = pkt_data->count_us & LBT_TIMESTAMP_MASK;
+ break;
+ case ON_GPS:
+ DEBUG_MSG("tx_mode = ON_GPS\n");
+ tx_start_time = (sx1301_time + (uint32_t)tx_start_delay + 1000000) & LBT_TIMESTAMP_MASK;
+ break;
+ case IMMEDIATE:
+ DEBUG_MSG("ERROR: tx_mode IMMEDIATE is not supported when LBT is enabled\n");
+ /* FALLTHROUGH */
+ default:
+ return LGW_LBT_ERROR;
+ }
+
+ /* Select LBT Channel corresponding to required TX frequency */
+ lbt_channel_decod_1 = -1;
+ lbt_channel_decod_2 = -1;
+ if (pkt_data->bandwidth == BW_125KHZ) {
+ for (i=0; i<lbt_nb_active_channel; i++) {
+ if (is_equal_freq(pkt_data->freq_hz, lbt_channel_cfg[i].freq_hz) == true) {
+ DEBUG_PRINTF("LBT: select channel %d (%u Hz)\n", i, lbt_channel_cfg[i].freq_hz);
+ lbt_channel_decod_1 = i;
+ lbt_channel_decod_2 = i;
+ if (lbt_channel_cfg[i].scan_time_us == 5000) {
+ tx_max_time = 4000000; /* 4 seconds */
+ } else { /* scan_time_us = 128 */
+ tx_max_time = 400000; /* 400 milliseconds */
+ }
+ break;
+ }
+ }
+ } else if (pkt_data->bandwidth == BW_250KHZ) {
+ /* In case of 250KHz, the TX freq has to be in between 2 consecutive channels of 200KHz BW.
+ The TX can only be over 2 channels, not more */
+ for (i=0; i<(lbt_nb_active_channel-1); i++) {
+ if ((is_equal_freq(pkt_data->freq_hz, (lbt_channel_cfg[i].freq_hz+lbt_channel_cfg[i+1].freq_hz)/2) == true) && ((lbt_channel_cfg[i+1].freq_hz-lbt_channel_cfg[i].freq_hz)==200E3)) {
+ DEBUG_PRINTF("LBT: select channels %d,%d (%u Hz)\n", i, i+1, (lbt_channel_cfg[i].freq_hz+lbt_channel_cfg[i+1].freq_hz)/2);
+ lbt_channel_decod_1 = i;
+ lbt_channel_decod_2 = i+1;
+ if (lbt_channel_cfg[i].scan_time_us == 5000) {
+ tx_max_time = 4000000; /* 4 seconds */
+ } else { /* scan_time_us = 128 */
+ tx_max_time = 200000; /* 200 milliseconds */
+ }
+ break;
+ }
+ }
+ } else {
+ /* Nothing to do for now */
+ }
+
+ /* Get last time when selected channel was free */
+ if ((lbt_channel_decod_1 >= 0) && (lbt_channel_decod_2 >= 0)) {
+ lgw_fpga_reg_w(LGW_FPGA_LBT_TIMESTAMP_SELECT_CH, (int32_t)lbt_channel_decod_1);
+ lgw_fpga_reg_r(LGW_FPGA_LBT_TIMESTAMP_CH, &val);
+ lbt_time = lbt_time1 = (uint32_t)(val & 0x0000FFFF) * 256; /* 16bits (1LSB = 256µs) */
+
+ if (lbt_channel_decod_1 != lbt_channel_decod_2 ) {
+ lgw_fpga_reg_w(LGW_FPGA_LBT_TIMESTAMP_SELECT_CH, (int32_t)lbt_channel_decod_2);
+ lgw_fpga_reg_r(LGW_FPGA_LBT_TIMESTAMP_CH, &val);
+ lbt_time2 = (uint32_t)(val & 0x0000FFFF) * 256; /* 16bits (1LSB = 256µs) */
+
+ if (lbt_time2 < lbt_time1) {
+ lbt_time = lbt_time2;
+ }
+ }
+ } else {
+ lbt_time = 0;
+ }
+
+ packet_duration = lgw_time_on_air(pkt_data) * 1000UL;
+ tx_end_time = (tx_start_time + packet_duration) & LBT_TIMESTAMP_MASK;
+ if (lbt_time < tx_end_time) {
+ delta_time = tx_end_time - lbt_time;
+ } else {
+ /* It means LBT counter has wrapped */
+ printf("LBT: lbt counter has wrapped\n");
+ delta_time = (LBT_TIMESTAMP_MASK - lbt_time) + tx_end_time;
+ }
+
+ DEBUG_PRINTF("sx1301_time = %u\n", sx1301_time & LBT_TIMESTAMP_MASK);
+ DEBUG_PRINTF("tx_freq = %u\n", pkt_data->freq_hz);
+ DEBUG_MSG("------------------------------------------------\n");
+ DEBUG_PRINTF("packet_duration = %u\n", packet_duration);
+ DEBUG_PRINTF("tx_start_time = %u\n", tx_start_time);
+ DEBUG_PRINTF("lbt_time1 = %u\n", lbt_time1);
+ DEBUG_PRINTF("lbt_time2 = %u\n", lbt_time2);
+ DEBUG_PRINTF("lbt_time = %u\n", lbt_time);
+ DEBUG_PRINTF("delta_time = %u\n", delta_time);
+ DEBUG_MSG("------------------------------------------------\n");
+
+ /* send data if allowed */
+ /* lbt_time: last time when channel was free */
+ /* tx_max_time: maximum time allowed to send packet since last free time */
+ /* 2048: some margin */
+ if ((delta_time < (tx_max_time - 2048)) && (lbt_time != 0)) {
+ *tx_allowed = true;
+ } else {
+ DEBUG_MSG("ERROR: TX request rejected (LBT)\n");
+ *tx_allowed = false;
+ }
+ } else {
+ /* Always allow if LBT is disabled */
+ *tx_allowed = true;
+ }
+
+ return LGW_LBT_SUCCESS;
+}
+
+/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
+
+bool lbt_is_enabled(void) {
+ return lbt_enable;
+}
+
+/* -------------------------------------------------------------------------- */
+/* --- PRIVATE FUNCTIONS DEFINITION ----------------------------------------- */
+
+/* As given frequencies have been converted from float to integer, some aliasing
+issues can appear, so we can't simply check for equality, but have to take some
+margin */
+bool is_equal_freq(uint32_t a, uint32_t b) {
+ int64_t diff;
+ int64_t a64 = (int64_t)a;
+ int64_t b64 = (int64_t)b;
+
+ /* Calculate the difference */
+ diff = llabs(a64 - b64);
+
+ /* Check for acceptable diff range */
+ if( diff <= 10000 )
+ {
+ return true;
+ }
+
+ return false;
+}
+
+/* --- EOF ------------------------------------------------------------------ */
diff --git a/libloragw/src/loragw_radio.c b/libloragw/src/loragw_radio.c
new file mode 100644
index 0000000..b3046a9
--- /dev/null
+++ b/libloragw/src/loragw_radio.c
@@ -0,0 +1,562 @@
+/*
+ / _____) _ | |
+( (____ _____ ____ _| |_ _____ ____| |__
+ \____ \| ___ | (_ _) ___ |/ ___) _ \
+ _____) ) ____| | | || |_| ____( (___| | | |
+(______/|_____)_|_|_| \__)_____)\____)_| |_|
+ (C)2013 Semtech-Cycleo
+
+Description:
+ Functions used to handle LoRa concentrator radios.
+
+License: Revised BSD License, see LICENSE.TXT file include in the project
+Maintainer: Michael Coracin
+*/
+
+/* -------------------------------------------------------------------------- */
+/* --- DEPENDANCIES --------------------------------------------------------- */
+
+#include <stdint.h> /* C99 types */
+#include <stdbool.h> /* bool type */
+#include <stdio.h> /* printf fprintf */
+
+#include "loragw_sx125x.h"
+#include "loragw_sx1272_fsk.h"
+#include "loragw_sx1272_lora.h"
+#include "loragw_sx1276_fsk.h"
+#include "loragw_sx1276_lora.h"
+#include "loragw_spi.h"
+#include "loragw_aux.h"
+#include "loragw_reg.h"
+#include "loragw_hal.h"
+#include "loragw_radio.h"
+#include "loragw_fpga.h"
+
+/* -------------------------------------------------------------------------- */
+/* --- PRIVATE MACROS ------------------------------------------------------- */
+
+#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
+#if DEBUG_REG == 1
+ #define DEBUG_MSG(str) fprintf(stderr, str)
+ #define DEBUG_PRINTF(fmt, args...) fprintf(stderr,"%s:%d: "fmt, __FUNCTION__, __LINE__, args)
+ #define CHECK_NULL(a) if(a==NULL){fprintf(stderr,"%s:%d: ERROR: NULL POINTER AS ARGUMENT\n", __FUNCTION__, __LINE__);return LGW_REG_ERROR;}
+#else
+ #define DEBUG_MSG(str)
+ #define DEBUG_PRINTF(fmt, args...)
+ #define CHECK_NULL(a) if(a==NULL){return LGW_REG_ERROR;}
+#endif
+
+/* -------------------------------------------------------------------------- */
+/* --- PRIVATE TYPES -------------------------------------------------------- */
+
+/**
+@struct lgw_radio_FSK_bandwidth_s
+@brief Associate a bandwidth in kHz with its corresponding register values
+*/
+struct lgw_sx127x_FSK_bandwidth_s {
+ uint32_t RxBwKHz;
+ uint8_t RxBwMant;
+ uint8_t RxBwExp;
+};
+
+/**
+@struct lgw_radio_type_version_s
+@brief Associate a radio type with its corresponding expected version value
+ read in the radio version register.
+*/
+struct lgw_radio_type_version_s {
+ enum lgw_radio_type_e type;
+ uint8_t reg_version;
+};
+
+/* -------------------------------------------------------------------------- */
+/* --- PRIVATE CONSTANTS ---------------------------------------------------- */
+
+#define PLL_LOCK_MAX_ATTEMPTS 5
+
+const struct lgw_sx127x_FSK_bandwidth_s sx127x_FskBandwidths[] =
+{
+ { 2600 , 2, 7 }, /* LGW_SX127X_RXBW_2K6_HZ */
+ { 3100 , 1, 7 }, /* LGW_SX127X_RXBW_3K1_HZ */
+ { 3900 , 0, 7 }, /* ... */
+ { 5200 , 2, 6 },
+ { 6300 , 1, 6 },
+ { 7800 , 0, 6 },
+ { 10400 , 2, 5 },
+ { 12500 , 1, 5 },
+ { 15600 , 0, 5 },
+ { 20800 , 2, 4 },
+ { 25000 , 1, 4 }, /* ... */
+ { 31300 , 0, 4 },
+ { 41700 , 2, 3 },
+ { 50000 , 1, 3 },
+ { 62500 , 0, 3 },
+ { 83333 , 2, 2 },
+ { 100000, 1, 2 },
+ { 125000, 0, 2 },
+ { 166700, 2, 1 },
+ { 200000, 1, 1 }, /* ... */
+ { 250000, 0, 1 } /* LGW_SX127X_RXBW_250K_HZ */
+};
+
+/* -------------------------------------------------------------------------- */
+/* --- PRIVATE VARIABLES ---------------------------------------------------- */
+
+extern void *lgw_spi_target; /*! generic pointer to the SPI device */
+
+/* -------------------------------------------------------------------------- */
+/* --- PRIVATE FUNCTIONS ---------------------------------------------------- */
+
+void sx125x_write(uint8_t channel, uint8_t addr, uint8_t data);
+uint8_t sx125x_read(uint8_t channel, uint8_t addr);
+
+int setup_sx1272_FSK(uint32_t frequency, enum lgw_sx127x_rxbw_e rxbw_khz, int8_t rssi_offset);
+int setup_sx1276_FSK(uint32_t frequency, enum lgw_sx127x_rxbw_e rxbw_khz, int8_t rssi_offset);
+
+int reset_sx127x(enum lgw_radio_type_e radio_type);
+
+/* -------------------------------------------------------------------------- */
+/* --- PRIVATE FUNCTIONS DEFINITION ----------------------------------------- */
+
+void sx125x_write(uint8_t channel, uint8_t addr, uint8_t data) {
+ int reg_add, reg_dat, reg_cs;
+
+ /* checking input parameters */
+ if (channel >= LGW_RF_CHAIN_NB) {
+ DEBUG_MSG("ERROR: INVALID RF_CHAIN\n");
+ return;
+ }
+ if (addr >= 0x7F) {
+ DEBUG_MSG("ERROR: ADDRESS OUT OF RANGE\n");
+ return;
+ }
+
+ /* selecting the target radio */
+ switch (channel) {
+ case 0:
+ reg_add = LGW_SPI_RADIO_A__ADDR;
+ reg_dat = LGW_SPI_RADIO_A__DATA;
+ reg_cs = LGW_SPI_RADIO_A__CS;
+ break;
+
+ case 1:
+ reg_add = LGW_SPI_RADIO_B__ADDR;
+ reg_dat = LGW_SPI_RADIO_B__DATA;
+ reg_cs = LGW_SPI_RADIO_B__CS;
+ break;
+
+ default:
+ DEBUG_PRINTF("ERROR: UNEXPECTED VALUE %d IN SWITCH STATEMENT\n", channel);
+ return;
+ }
+
+ /* SPI master data write procedure */
+ lgw_reg_w(reg_cs, 0);
+ lgw_reg_w(reg_add, 0x80 | addr); /* MSB at 1 for write operation */
+ lgw_reg_w(reg_dat, data);
+ lgw_reg_w(reg_cs, 1);
+ lgw_reg_w(reg_cs, 0);
+
+ return;
+}
+
+/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
+
+uint8_t sx125x_read(uint8_t channel, uint8_t addr) {
+ int reg_add, reg_dat, reg_cs, reg_rb;
+ int32_t read_value;
+
+ /* checking input parameters */
+ if (channel >= LGW_RF_CHAIN_NB) {
+ DEBUG_MSG("ERROR: INVALID RF_CHAIN\n");
+ return 0;
+ }
+ if (addr >= 0x7F) {
+ DEBUG_MSG("ERROR: ADDRESS OUT OF RANGE\n");
+ return 0;
+ }
+
+ /* selecting the target radio */
+ switch (channel) {
+ case 0:
+ reg_add = LGW_SPI_RADIO_A__ADDR;
+ reg_dat = LGW_SPI_RADIO_A__DATA;
+ reg_cs = LGW_SPI_RADIO_A__CS;
+ reg_rb = LGW_SPI_RADIO_A__DATA_READBACK;
+ break;
+
+ case 1:
+ reg_add = LGW_SPI_RADIO_B__ADDR;
+ reg_dat = LGW_SPI_RADIO_B__DATA;
+ reg_cs = LGW_SPI_RADIO_B__CS;
+ reg_rb = LGW_SPI_RADIO_B__DATA_READBACK;
+ break;
+
+ default:
+ DEBUG_PRINTF("ERROR: UNEXPECTED VALUE %d IN SWITCH STATEMENT\n", channel);
+ return 0;
+ }
+
+ /* SPI master data read procedure */
+ lgw_reg_w(reg_cs, 0);
+ lgw_reg_w(reg_add, addr); /* MSB at 0 for read operation */
+ lgw_reg_w(reg_dat, 0);
+ lgw_reg_w(reg_cs, 1);
+ lgw_reg_w(reg_cs, 0);
+ lgw_reg_r(reg_rb, &read_value);
+
+ return (uint8_t)read_value;
+}
+
+/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
+
+int setup_sx1272_FSK(uint32_t frequency, enum lgw_sx127x_rxbw_e rxbw_khz, int8_t rssi_offset) {
+ uint64_t freq_reg;
+ uint8_t ModulationShaping = 0;
+ uint8_t PllHop = 1;
+ uint8_t LnaGain = 1;
+ uint8_t LnaBoost = 3;
+ uint8_t AdcBwAuto = 0;
+ uint8_t AdcBw = 7;
+ uint8_t AdcLowPwr = 0;
+ uint8_t AdcTrim = 6;
+ uint8_t AdcTest = 0;
+ uint8_t RxBwExp = sx127x_FskBandwidths[rxbw_khz].RxBwExp;
+ uint8_t RxBwMant = sx127x_FskBandwidths[rxbw_khz].RxBwMant;
+ uint8_t RssiSmoothing = 5;
+ uint8_t RssiOffsetReg;
+ uint8_t reg_val;
+ int x;
+
+ /* Set in FSK mode */
+ x = lgw_sx127x_reg_w(SX1272_REG_OPMODE, 0);
+ wait_ms(100);
+ x |= lgw_sx127x_reg_w(SX1272_REG_OPMODE, 0 | (ModulationShaping << 3)); /* Sleep mode, no FSK shaping */
+ wait_ms(100);
+ x |= lgw_sx127x_reg_w(SX1272_REG_OPMODE, 1 | (ModulationShaping << 3)); /* Standby mode, no FSK shaping */
+ wait_ms(100);
+
+ /* Set RF carrier frequency */
+ x |= lgw_sx127x_reg_w(SX1272_REG_PLLHOP, PllHop << 7);
+ freq_reg = ((uint64_t)frequency << 19) / (uint64_t)32000000;
+ x |= lgw_sx127x_reg_w(SX1272_REG_FRFMSB, (freq_reg >> 16) & 0xFF);
+ x |= lgw_sx127x_reg_w(SX1272_REG_FRFMID, (freq_reg >> 8) & 0xFF);
+ x |= lgw_sx127x_reg_w(SX1272_REG_FRFLSB, (freq_reg >> 0) & 0xFF);
+
+ /* Config */
+ x |= lgw_sx127x_reg_w(SX1272_REG_LNA, LnaBoost | (LnaGain << 5)); /* Improved sensitivity, highest gain */
+ x |= lgw_sx127x_reg_w(0x68, AdcBw | (AdcBwAuto << 3));
+ x |= lgw_sx127x_reg_w(0x69, AdcTest | (AdcTrim << 4) | (AdcLowPwr << 7));
+
+ /* set BR and FDEV for 200 kHz bandwidth*/
+ x |= lgw_sx127x_reg_w(SX1272_REG_BITRATEMSB, 125);
+ x |= lgw_sx127x_reg_w(SX1272_REG_BITRATELSB, 0);
+ x |= lgw_sx127x_reg_w(SX1272_REG_FDEVMSB, 2);
+ x |= lgw_sx127x_reg_w(SX1272_REG_FDEVLSB, 225);
+
+ /* Config continues... */
+ x |= lgw_sx127x_reg_w(SX1272_REG_RXCONFIG, 0); /* Disable AGC */
+ RssiOffsetReg = (rssi_offset >= 0) ? (uint8_t)rssi_offset : (uint8_t)(~(-rssi_offset)+1); /* 2's complement */
+ x |= lgw_sx127x_reg_w(SX1272_REG_RSSICONFIG, RssiSmoothing | (RssiOffsetReg << 3)); /* Set RSSI smoothing to 64 samples, RSSI offset to given value */
+ x |= lgw_sx127x_reg_w(SX1272_REG_RXBW, RxBwExp | (RxBwMant << 3));
+ x |= lgw_sx127x_reg_w(SX1272_REG_RXDELAY, 2);
+ x |= lgw_sx127x_reg_w(SX1272_REG_PLL, 0x10); /* PLL BW set to 75 KHz */
+ x |= lgw_sx127x_reg_w(0x47, 1); /* optimize PLL start-up time */
+
+ if (x != LGW_REG_SUCCESS) {
+ DEBUG_MSG("ERROR: Failed to configure SX1272\n");
+ return x;
+ }
+
+ /* set Rx continuous mode */
+ x = lgw_sx127x_reg_w(SX1272_REG_OPMODE, 5 | (ModulationShaping << 3)); /* Receiver Mode, no FSK shaping */
+ wait_ms(500);
+ x |= lgw_sx127x_reg_r(SX1272_REG_IRQFLAGS1, &reg_val);
+ /* Check if RxReady and ModeReady */
+ if ((TAKE_N_BITS_FROM(reg_val, 6, 1) == 0) || (TAKE_N_BITS_FROM(reg_val, 7, 1) == 0) || (x != LGW_REG_SUCCESS)) {
+ DEBUG_MSG("ERROR: SX1272 failed to enter RX continuous mode\n");
+ return LGW_REG_ERROR;
+ }
+ wait_ms(500);
+
+ DEBUG_PRINTF("INFO: Successfully configured SX1272 for FSK modulation (rxbw=%d)\n", rxbw_khz);
+
+ return LGW_REG_SUCCESS;
+}
+
+/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
+
+int setup_sx1276_FSK(uint32_t frequency, enum lgw_sx127x_rxbw_e rxbw_khz, int8_t rssi_offset) {
+ uint64_t freq_reg;
+ uint8_t ModulationShaping = 0;
+ uint8_t PllHop = 1;
+ uint8_t LnaGain = 1;
+ uint8_t LnaBoost = 3;
+ uint8_t AdcBwAuto = 0;
+ uint8_t AdcBw = 7;
+ uint8_t AdcLowPwr = 0;
+ uint8_t AdcTrim = 6;
+ uint8_t AdcTest = 0;
+ uint8_t RxBwExp = sx127x_FskBandwidths[rxbw_khz].RxBwExp;
+ uint8_t RxBwMant = sx127x_FskBandwidths[rxbw_khz].RxBwMant;
+ uint8_t RssiSmoothing = 5;
+ uint8_t RssiOffsetReg;
+ uint8_t reg_val;
+ int x;
+
+ /* Set in FSK mode */
+ x = lgw_sx127x_reg_w(SX1276_REG_OPMODE, 0);
+ wait_ms(100);
+ x |= lgw_sx127x_reg_w(SX1276_REG_OPMODE, 0 | (ModulationShaping << 3)); /* Sleep mode, no FSK shaping */
+ wait_ms(100);
+ x |= lgw_sx127x_reg_w(SX1276_REG_OPMODE, 1 | (ModulationShaping << 3)); /* Standby mode, no FSK shaping */
+ wait_ms(100);
+
+ /* Set RF carrier frequency */
+ x |= lgw_sx127x_reg_w(SX1276_REG_PLLHOP, PllHop << 7);
+ freq_reg = ((uint64_t)frequency << 19) / (uint64_t)32000000;
+ x |= lgw_sx127x_reg_w(SX1276_REG_FRFMSB, (freq_reg >> 16) & 0xFF);
+ x |= lgw_sx127x_reg_w(SX1276_REG_FRFMID, (freq_reg >> 8) & 0xFF);
+ x |= lgw_sx127x_reg_w(SX1276_REG_FRFLSB, (freq_reg >> 0) & 0xFF);
+
+ /* Config */
+ x |= lgw_sx127x_reg_w(SX1276_REG_LNA, LnaBoost | (LnaGain << 5)); /* Improved sensitivity, highest gain */
+ x |= lgw_sx127x_reg_w(0x57, AdcBw | (AdcBwAuto << 3));
+ x |= lgw_sx127x_reg_w(0x58, AdcTest | (AdcTrim << 4) | (AdcLowPwr << 7));
+
+ /* set BR and FDEV for 200 kHz bandwidth*/
+ x |= lgw_sx127x_reg_w(SX1276_REG_BITRATEMSB, 125);
+ x |= lgw_sx127x_reg_w(SX1276_REG_BITRATELSB, 0);
+ x |= lgw_sx127x_reg_w(SX1276_REG_FDEVMSB, 2);
+ x |= lgw_sx127x_reg_w(SX1276_REG_FDEVLSB, 225);
+
+ /* Config continues... */
+ x |= lgw_sx127x_reg_w(SX1276_REG_RXCONFIG, 0); /* Disable AGC */
+ RssiOffsetReg = (rssi_offset >= 0) ? (uint8_t)rssi_offset : (uint8_t)(~(-rssi_offset)+1); /* 2's complement */
+ x |= lgw_sx127x_reg_w(SX1276_REG_RSSICONFIG, RssiSmoothing | (RssiOffsetReg << 3)); /* Set RSSI smoothing to 64 samples, RSSI offset 3dB */
+ x |= lgw_sx127x_reg_w(SX1276_REG_RXBW, RxBwExp | (RxBwMant << 3));
+ x |= lgw_sx127x_reg_w(SX1276_REG_RXDELAY, 2);
+ x |= lgw_sx127x_reg_w(SX1276_REG_PLL, 0x10); /* PLL BW set to 75 KHz */
+ x |= lgw_sx127x_reg_w(0x43, 1); /* optimize PLL start-up time */
+
+ if (x != LGW_REG_SUCCESS) {
+ DEBUG_MSG("ERROR: Failed to configure SX1276\n");
+ return x;
+ }
+
+ /* set Rx continuous mode */
+ x = lgw_sx127x_reg_w(SX1276_REG_OPMODE, 5 | (ModulationShaping << 3)); /* Receiver Mode, no FSK shaping */
+ wait_ms(500);
+ x |= lgw_sx127x_reg_r(SX1276_REG_IRQFLAGS1, &reg_val);
+ /* Check if RxReady and ModeReady */
+ if ((TAKE_N_BITS_FROM(reg_val, 6, 1) == 0) || (TAKE_N_BITS_FROM(reg_val, 7, 1) == 0) || (x != LGW_REG_SUCCESS)) {
+ DEBUG_MSG("ERROR: SX1276 failed to enter RX continuous mode\n");
+ return LGW_REG_ERROR;
+ }
+ wait_ms(500);
+
+ DEBUG_PRINTF("INFO: Successfully configured SX1276 for FSK modulation (rxbw=%d)\n", rxbw_khz);
+
+ return LGW_REG_SUCCESS;
+}
+
+/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
+
+int reset_sx127x(enum lgw_radio_type_e radio_type) {
+ int x;
+
+ switch(radio_type) {
+ case LGW_RADIO_TYPE_SX1276:
+ x = lgw_fpga_reg_w(LGW_FPGA_CTRL_RADIO_RESET, 0);
+ x |= lgw_fpga_reg_w(LGW_FPGA_CTRL_RADIO_RESET, 1);
+ if (x != LGW_SPI_SUCCESS) {
+ DEBUG_MSG("ERROR: Failed to reset sx127x\n");
+ return x;
+ }
+ break;
+ case LGW_RADIO_TYPE_SX1272:
+ x = lgw_fpga_reg_w(LGW_FPGA_CTRL_RADIO_RESET, 1);
+ x |= lgw_fpga_reg_w(LGW_FPGA_CTRL_RADIO_RESET, 0);
+ if (x != LGW_SPI_SUCCESS) {
+ DEBUG_MSG("ERROR: Failed to reset sx127x\n");
+ return x;
+ }
+ break;
+ default:
+ DEBUG_PRINTF("ERROR: Failed to reset sx127x, not supported (%d)\n", radio_type);
+ return LGW_REG_ERROR;
+ }
+
+ return LGW_REG_SUCCESS;
+}
+
+/* -------------------------------------------------------------------------- */
+/* --- PUBLIC FUNCTIONS DEFINITION ------------------------------------------ */
+
+int lgw_setup_sx125x(uint8_t rf_chain, uint8_t rf_clkout, bool rf_enable, uint8_t rf_radio_type, uint32_t freq_hz) {
+ uint32_t part_int = 0;
+ uint32_t part_frac = 0;
+ int cpt_attempts = 0;
+
+ if (rf_chain >= LGW_RF_CHAIN_NB) {
+ DEBUG_MSG("ERROR: INVALID RF_CHAIN\n");
+ return -1;
+ }
+
+ /* Get version to identify SX1255/57 silicon revision */
+ DEBUG_PRINTF("Note: SX125x #%d version register returned 0x%02x\n", rf_chain, sx125x_read(rf_chain, 0x07));
+
+ /* General radio setup */
+ if (rf_clkout == rf_chain) {
+ sx125x_write(rf_chain, 0x10, SX125x_TX_DAC_CLK_SEL + 2);
+ DEBUG_PRINTF("Note: SX125x #%d clock output enabled\n", rf_chain);
+ } else {
+ sx125x_write(rf_chain, 0x10, SX125x_TX_DAC_CLK_SEL);
+ DEBUG_PRINTF("Note: SX125x #%d clock output disabled\n", rf_chain);
+ }
+
+ switch (rf_radio_type) {
+ case LGW_RADIO_TYPE_SX1255:
+ sx125x_write(rf_chain, 0x28, SX125x_XOSC_GM_STARTUP + SX125x_XOSC_DISABLE*16);
+ break;
+ case LGW_RADIO_TYPE_SX1257:
+ sx125x_write(rf_chain, 0x26, SX125x_XOSC_GM_STARTUP + SX125x_XOSC_DISABLE*16);
+ break;
+ default:
+ DEBUG_PRINTF("ERROR: UNEXPECTED VALUE %d FOR RADIO TYPE\n", rf_radio_type);
+ break;
+ }
+
+ if (rf_enable == true) {
+ /* Tx gain and trim */
+ sx125x_write(rf_chain, 0x08, SX125x_TX_MIX_GAIN + SX125x_TX_DAC_GAIN*16);
+ sx125x_write(rf_chain, 0x0A, SX125x_TX_ANA_BW + SX125x_TX_PLL_BW*32);
+ sx125x_write(rf_chain, 0x0B, SX125x_TX_DAC_BW);
+
+ /* Rx gain and trim */
+ sx125x_write(rf_chain, 0x0C, SX125x_LNA_ZIN + SX125x_RX_BB_GAIN*2 + SX125x_RX_LNA_GAIN*32);
+ sx125x_write(rf_chain, 0x0D, SX125x_RX_BB_BW + SX125x_RX_ADC_TRIM*4 + SX125x_RX_ADC_BW*32);
+ sx125x_write(rf_chain, 0x0E, SX125x_ADC_TEMP + SX125x_RX_PLL_BW*2);
+
+ /* set RX PLL frequency */
+ switch (rf_radio_type) {
+ case LGW_RADIO_TYPE_SX1255:
+ part_int = freq_hz / (SX125x_32MHz_FRAC << 7); /* integer part, gives the MSB */
+ part_frac = ((freq_hz % (SX125x_32MHz_FRAC << 7)) << 9) / SX125x_32MHz_FRAC; /* fractional part, gives middle part and LSB */
+ break;
+ case LGW_RADIO_TYPE_SX1257:
+ part_int = freq_hz / (SX125x_32MHz_FRAC << 8); /* integer part, gives the MSB */
+ part_frac = ((freq_hz % (SX125x_32MHz_FRAC << 8)) << 8) / SX125x_32MHz_FRAC; /* fractional part, gives middle part and LSB */
+ break;
+ default:
+ DEBUG_PRINTF("ERROR: UNEXPECTED VALUE %d FOR RADIO TYPE\n", rf_radio_type);
+ break;
+ }
+
+ sx125x_write(rf_chain, 0x01,0xFF & part_int); /* Most Significant Byte */
+ sx125x_write(rf_chain, 0x02,0xFF & (part_frac >> 8)); /* middle byte */
+ sx125x_write(rf_chain, 0x03,0xFF & part_frac); /* Least Significant Byte */
+
+ /* start and PLL lock */
+ do {
+ if (cpt_attempts >= PLL_LOCK_MAX_ATTEMPTS) {
+ DEBUG_MSG("ERROR: FAIL TO LOCK PLL\n");
+ return -1;
+ }
+ sx125x_write(rf_chain, 0x00, 1); /* enable Xtal oscillator */
+ sx125x_write(rf_chain, 0x00, 3); /* Enable RX (PLL+FE) */
+ ++cpt_attempts;
+ DEBUG_PRINTF("Note: SX125x #%d PLL start (attempt %d)\n", rf_chain, cpt_attempts);
+ wait_ms(1);
+ } while((sx125x_read(rf_chain, 0x11) & 0x02) == 0);
+ } else {
+ DEBUG_PRINTF("Note: SX125x #%d kept in standby mode\n", rf_chain);
+ }
+
+ return 0;
+}
+
+/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
+
+int lgw_sx127x_reg_w(uint8_t address, uint8_t reg_value) {
+ return lgw_spi_w(lgw_spi_target, LGW_SPI_MUX_MODE1, LGW_SPI_MUX_TARGET_SX127X, address, reg_value);
+}
+
+/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
+
+int lgw_sx127x_reg_r(uint8_t address, uint8_t *reg_value) {
+ return lgw_spi_r(lgw_spi_target, LGW_SPI_MUX_MODE1, LGW_SPI_MUX_TARGET_SX127X, address, reg_value);
+}
+
+/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
+
+int lgw_setup_sx127x(uint32_t frequency, uint8_t modulation, enum lgw_sx127x_rxbw_e rxbw_khz, int8_t rssi_offset) {
+ int x, i;
+ uint8_t version;
+ enum lgw_radio_type_e radio_type = LGW_RADIO_TYPE_NONE;
+ struct lgw_radio_type_version_s supported_radio_type[2] = {
+ {LGW_RADIO_TYPE_SX1272, 0x22},
+ {LGW_RADIO_TYPE_SX1276, 0x12}
+ };
+
+ /* Check parameters */
+ if (modulation != MOD_FSK) {
+ DEBUG_PRINTF("ERROR: modulation not supported for SX127x (%u)\n", modulation);
+ return LGW_REG_ERROR;
+ }
+ if (rxbw_khz > LGW_SX127X_RXBW_250K_HZ) {
+ DEBUG_PRINTF("ERROR: RX bandwidth not supported for SX127x (%u)\n", rxbw_khz);
+ return LGW_REG_ERROR;
+ }
+
+ /* Probing radio type */
+ for (i = 0; i < (int)(sizeof supported_radio_type); i++) {
+ /* Reset the radio */
+ x = reset_sx127x(supported_radio_type[i].type);
+ if (x != LGW_SPI_SUCCESS) {
+ DEBUG_MSG("ERROR: Failed to reset sx127x\n");
+ return x;
+ }
+ /* Read version register */
+ x = lgw_sx127x_reg_r(0x42, &version);
+ if (x != LGW_SPI_SUCCESS) {
+ DEBUG_MSG("ERROR: Failed to read sx127x version register\n");
+ return x;
+ }
+ /* Check if we got the expected version */
+ if (version != supported_radio_type[i].reg_version) {
+ DEBUG_PRINTF("INFO: sx127x version register - read:0x%02x, expected:0x%02x\n", version, supported_radio_type[i].reg_version);
+ continue;
+ } else {
+ DEBUG_PRINTF("INFO: sx127x radio has been found (type:%d, version:0x%02x)\n", supported_radio_type[i].type, version);
+ radio_type = supported_radio_type[i].type;
+ break;
+ }
+ }
+ if (radio_type == LGW_RADIO_TYPE_NONE) {
+ DEBUG_MSG("ERROR: sx127x radio has not been found\n");
+ return LGW_REG_ERROR;
+ }
+
+ /* Setup the radio */
+ switch (modulation) {
+ case MOD_FSK:
+ if (radio_type == LGW_RADIO_TYPE_SX1272) {
+ x = setup_sx1272_FSK(frequency, rxbw_khz, rssi_offset);
+ } else {
+ x = setup_sx1276_FSK(frequency, rxbw_khz, rssi_offset);
+ }
+ break;
+ default:
+ /* Should not happen */
+ break;
+ }
+ if (x != LGW_REG_SUCCESS) {
+ DEBUG_MSG("ERROR: failed to setup SX127x\n");
+ return x;
+ }
+
+ return LGW_REG_SUCCESS;
+}
+
+/* --- EOF ------------------------------------------------------------------ */
diff --git a/libloragw/src/loragw_reg.c b/libloragw/src/loragw_reg.c
new file mode 100644
index 0000000..14e23cd
--- /dev/null
+++ b/libloragw/src/loragw_reg.c
@@ -0,0 +1,819 @@
+/*
+ / _____) _ | |
+( (____ _____ ____ _| |_ _____ ____| |__
+ \____ \| ___ | (_ _) ___ |/ ___) _ \
+ _____) ) ____| | | || |_| ____( (___| | | |
+(______/|_____)_|_|_| \__)_____)\____)_| |_|
+ (C)2013 Semtech-Cycleo
+
+Description:
+ Functions used to handle a single LoRa concentrator.
+ Registers are addressed by name.
+ Multi-bytes registers are handled automatically.
+ Read-modify-write is handled automatically.
+
+License: Revised BSD License, see LICENSE.TXT file include in the project
+Maintainer: Sylvain Miermont
+*/
+
+
+/* -------------------------------------------------------------------------- */
+/* --- DEPENDANCIES --------------------------------------------------------- */
+
+#include <stdint.h> /* C99 types */
+#include <stdbool.h> /* bool type */
+#include <stdio.h> /* printf fprintf */
+
+#include "loragw_spi.h"
+#include "loragw_reg.h"
+#include "loragw_fpga.h"
+
+/* -------------------------------------------------------------------------- */
+/* --- PRIVATE MACROS ------------------------------------------------------- */
+
+#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
+#if DEBUG_REG == 1
+ #define DEBUG_MSG(str) fprintf(stderr, str)
+ #define DEBUG_PRINTF(fmt, args...) fprintf(stderr,"%s:%d: "fmt, __FUNCTION__, __LINE__, args)
+ #define CHECK_NULL(a) if(a==NULL){fprintf(stderr,"%s:%d: ERROR: NULL POINTER AS ARGUMENT\n", __FUNCTION__, __LINE__);return LGW_REG_ERROR;}
+#else
+ #define DEBUG_MSG(str)
+ #define DEBUG_PRINTF(fmt, args...)
+ #define CHECK_NULL(a) if(a==NULL){return LGW_REG_ERROR;}
+#endif
+
+/* -------------------------------------------------------------------------- */
+/* --- PRIVATE CONSTANTS ---------------------------------------------------- */
+
+#define PAGE_ADDR 0x00
+#define PAGE_MASK 0x03
+
+const uint8_t FPGA_VERSION[] = { 31, 33 }; /* several versions could be supported */
+
+/*
+auto generated register mapping for C code : 11-Jul-2013 13:20:40
+this file contains autogenerated C struct used to access the LoRa register from the Primer firmware
+this file is autogenerated from registers description
+293 registers are defined
+*/
+const struct lgw_reg_s loregs[LGW_TOTALREGS] = {
+ {-1,0,0,0,2,0,0}, /* PAGE_REG */
+ {-1,0,7,0,1,0,0}, /* SOFT_RESET */
+ {-1,1,0,0,8,1,103}, /* VERSION */
+ {-1,2,0,0,16,0,0}, /* RX_DATA_BUF_ADDR */
+ {-1,4,0,0,8,0,0}, /* RX_DATA_BUF_DATA */
+ {-1,5,0,0,8,0,0}, /* TX_DATA_BUF_ADDR */
+ {-1,6,0,0,8,0,0}, /* TX_DATA_BUF_DATA */
+ {-1,7,0,0,8,0,0}, /* CAPTURE_RAM_ADDR */
+ {-1,8,0,0,8,1,0}, /* CAPTURE_RAM_DATA */
+ {-1,9,0,0,8,0,0}, /* MCU_PROM_ADDR */
+ {-1,10,0,0,8,0,0}, /* MCU_PROM_DATA */
+ {-1,11,0,0,8,0,0}, /* RX_PACKET_DATA_FIFO_NUM_STORED */
+ {-1,12,0,0,16,1,0}, /* RX_PACKET_DATA_FIFO_ADDR_POINTER */
+ {-1,14,0,0,8,1,0}, /* RX_PACKET_DATA_FIFO_STATUS */
+ {-1,15,0,0,8,1,0}, /* RX_PACKET_DATA_FIFO_PAYLOAD_SIZE */
+ {-1,16,0,0,1,0,0}, /* MBWSSF_MODEM_ENABLE */
+ {-1,16,1,0,1,0,0}, /* CONCENTRATOR_MODEM_ENABLE */
+ {-1,16,2,0,1,0,0}, /* FSK_MODEM_ENABLE */
+ {-1,16,3,0,1,0,0}, /* GLOBAL_EN */
+ {-1,17,0,0,1,0,1}, /* CLK32M_EN */
+ {-1,17,1,0,1,0,1}, /* CLKHS_EN */
+ {-1,18,0,0,1,0,0}, /* START_BIST0 */
+ {-1,18,1,0,1,0,0}, /* START_BIST1 */
+ {-1,18,2,0,1,0,0}, /* CLEAR_BIST0 */
+ {-1,18,3,0,1,0,0}, /* CLEAR_BIST1 */
+ {-1,19,0,0,1,1,0}, /* BIST0_FINISHED */
+ {-1,19,1,0,1,1,0}, /* BIST1_FINISHED */
+ {-1,20,0,0,1,1,0}, /* MCU_AGC_PROG_RAM_BIST_STATUS */
+ {-1,20,1,0,1,1,0}, /* MCU_ARB_PROG_RAM_BIST_STATUS */
+ {-1,20,2,0,1,1,0}, /* CAPTURE_RAM_BIST_STATUS */
+ {-1,20,3,0,1,1,0}, /* CHAN_FIR_RAM0_BIST_STATUS */
+ {-1,20,4,0,1,1,0}, /* CHAN_FIR_RAM1_BIST_STATUS */
+ {-1,21,0,0,1,1,0}, /* CORR0_RAM_BIST_STATUS */
+ {-1,21,1,0,1,1,0}, /* CORR1_RAM_BIST_STATUS */
+ {-1,21,2,0,1,1,0}, /* CORR2_RAM_BIST_STATUS */
+ {-1,21,3,0,1,1,0}, /* CORR3_RAM_BIST_STATUS */
+ {-1,21,4,0,1,1,0}, /* CORR4_RAM_BIST_STATUS */
+ {-1,21,5,0,1,1,0}, /* CORR5_RAM_BIST_STATUS */
+ {-1,21,6,0,1,1,0}, /* CORR6_RAM_BIST_STATUS */
+ {-1,21,7,0,1,1,0}, /* CORR7_RAM_BIST_STATUS */
+ {-1,22,0,0,1,1,0}, /* MODEM0_RAM0_BIST_STATUS */
+ {-1,22,1,0,1,1,0}, /* MODEM1_RAM0_BIST_STATUS */
+ {-1,22,2,0,1,1,0}, /* MODEM2_RAM0_BIST_STATUS */
+ {-1,22,3,0,1,1,0}, /* MODEM3_RAM0_BIST_STATUS */
+ {-1,22,4,0,1,1,0}, /* MODEM4_RAM0_BIST_STATUS */
+ {-1,22,5,0,1,1,0}, /* MODEM5_RAM0_BIST_STATUS */
+ {-1,22,6,0,1,1,0}, /* MODEM6_RAM0_BIST_STATUS */
+ {-1,22,7,0,1,1,0}, /* MODEM7_RAM0_BIST_STATUS */
+ {-1,23,0,0,1,1,0}, /* MODEM0_RAM1_BIST_STATUS */
+ {-1,23,1,0,1,1,0}, /* MODEM1_RAM1_BIST_STATUS */
+ {-1,23,2,0,1,1,0}, /* MODEM2_RAM1_BIST_STATUS */
+ {-1,23,3,0,1,1,0}, /* MODEM3_RAM1_BIST_STATUS */
+ {-1,23,4,0,1,1,0}, /* MODEM4_RAM1_BIST_STATUS */
+ {-1,23,5,0,1,1,0}, /* MODEM5_RAM1_BIST_STATUS */
+ {-1,23,6,0,1,1,0}, /* MODEM6_RAM1_BIST_STATUS */
+ {-1,23,7,0,1,1,0}, /* MODEM7_RAM1_BIST_STATUS */
+ {-1,24,0,0,1,1,0}, /* MODEM0_RAM2_BIST_STATUS */
+ {-1,24,1,0,1,1,0}, /* MODEM1_RAM2_BIST_STATUS */
+ {-1,24,2,0,1,1,0}, /* MODEM2_RAM2_BIST_STATUS */
+ {-1,24,3,0,1,1,0}, /* MODEM3_RAM2_BIST_STATUS */
+ {-1,24,4,0,1,1,0}, /* MODEM4_RAM2_BIST_STATUS */
+ {-1,24,5,0,1,1,0}, /* MODEM5_RAM2_BIST_STATUS */
+ {-1,24,6,0,1,1,0}, /* MODEM6_RAM2_BIST_STATUS */
+ {-1,24,7,0,1,1,0}, /* MODEM7_RAM2_BIST_STATUS */
+ {-1,25,0,0,1,1,0}, /* MODEM_MBWSSF_RAM0_BIST_STATUS */
+ {-1,25,1,0,1,1,0}, /* MODEM_MBWSSF_RAM1_BIST_STATUS */
+ {-1,25,2,0,1,1,0}, /* MODEM_MBWSSF_RAM2_BIST_STATUS */
+ {-1,26,0,0,1,1,0}, /* MCU_AGC_DATA_RAM_BIST0_STATUS */
+ {-1,26,1,0,1,1,0}, /* MCU_AGC_DATA_RAM_BIST1_STATUS */
+ {-1,26,2,0,1,1,0}, /* MCU_ARB_DATA_RAM_BIST0_STATUS */
+ {-1,26,3,0,1,1,0}, /* MCU_ARB_DATA_RAM_BIST1_STATUS */
+ {-1,26,4,0,1,1,0}, /* TX_TOP_RAM_BIST0_STATUS */
+ {-1,26,5,0,1,1,0}, /* TX_TOP_RAM_BIST1_STATUS */
+ {-1,26,6,0,1,1,0}, /* DATA_MNGT_RAM_BIST0_STATUS */
+ {-1,26,7,0,1,1,0}, /* DATA_MNGT_RAM_BIST1_STATUS */
+ {-1,27,0,0,4,0,0}, /* GPIO_SELECT_INPUT */
+ {-1,28,0,0,4,0,0}, /* GPIO_SELECT_OUTPUT */
+ {-1,29,0,0,5,0,0}, /* GPIO_MODE */
+ {-1,30,0,0,5,1,0}, /* GPIO_PIN_REG_IN */
+ {-1,31,0,0,5,0,0}, /* GPIO_PIN_REG_OUT */
+ {-1,32,0,0,8,1,0}, /* MCU_AGC_STATUS */
+ {-1,125,0,0,8,1,0}, /* MCU_ARB_STATUS */
+ {-1,126,0,0,8,1,1}, /* CHIP_ID */
+ {-1,127,0,0,1,0,1}, /* EMERGENCY_FORCE_HOST_CTRL */
+ {0,33,0,0,1,0,0}, /* RX_INVERT_IQ */
+ {0,33,1,0,1,0,1}, /* MODEM_INVERT_IQ */
+ {0,33,2,0,1,0,0}, /* MBWSSF_MODEM_INVERT_IQ */
+ {0,33,3,0,1,0,0}, /* RX_EDGE_SELECT */
+ {0,33,4,0,1,0,0}, /* MISC_RADIO_EN */
+ {0,33,5,0,1,0,0}, /* FSK_MODEM_INVERT_IQ */
+ {0,34,0,0,4,0,7}, /* FILTER_GAIN */
+ {0,35,0,0,8,0,240}, /* RADIO_SELECT */
+ {0,36,0,1,13,0,-384}, /* IF_FREQ_0 */
+ {0,38,0,1,13,0,-128}, /* IF_FREQ_1 */
+ {0,40,0,1,13,0,128}, /* IF_FREQ_2 */
+ {0,42,0,1,13,0,384}, /* IF_FREQ_3 */
+ {0,44,0,1,13,0,-384}, /* IF_FREQ_4 */
+ {0,46,0,1,13,0,-128}, /* IF_FREQ_5 */
+ {0,48,0,1,13,0,128}, /* IF_FREQ_6 */
+ {0,50,0,1,13,0,384}, /* IF_FREQ_7 */
+ {0,52,0,1,13,0,0}, /* IF_FREQ_8 */
+ {0,54,0,1,13,0,0}, /* IF_FREQ_9 */
+ {0,64,0,0,1,0,0}, /* CHANN_OVERRIDE_AGC_GAIN */
+ {0,64,1,0,4,0,7}, /* CHANN_AGC_GAIN */
+ {0,65,0,0,7,0,0}, /* CORR0_DETECT_EN */
+ {0,66,0,0,7,0,0}, /* CORR1_DETECT_EN */
+ {0,67,0,0,7,0,0}, /* CORR2_DETECT_EN */
+ {0,68,0,0,7,0,0}, /* CORR3_DETECT_EN */
+ {0,69,0,0,7,0,0}, /* CORR4_DETECT_EN */
+ {0,70,0,0,7,0,0}, /* CORR5_DETECT_EN */
+ {0,71,0,0,7,0,0}, /* CORR6_DETECT_EN */
+ {0,72,0,0,7,0,0}, /* CORR7_DETECT_EN */
+ {0,73,0,0,1,0,0}, /* CORR_SAME_PEAKS_OPTION_SF6 */
+ {0,73,1,0,1,0,1}, /* CORR_SAME_PEAKS_OPTION_SF7 */
+ {0,73,2,0,1,0,1}, /* CORR_SAME_PEAKS_OPTION_SF8 */
+ {0,73,3,0,1,0,1}, /* CORR_SAME_PEAKS_OPTION_SF9 */
+ {0,73,4,0,1,0,1}, /* CORR_SAME_PEAKS_OPTION_SF10 */
+ {0,73,5,0,1,0,1}, /* CORR_SAME_PEAKS_OPTION_SF11 */
+ {0,73,6,0,1,0,1}, /* CORR_SAME_PEAKS_OPTION_SF12 */
+ {0,74,0,0,4,0,4}, /* CORR_SIG_NOISE_RATIO_SF6 */
+ {0,74,4,0,4,0,4}, /* CORR_SIG_NOISE_RATIO_SF7 */
+ {0,75,0,0,4,0,4}, /* CORR_SIG_NOISE_RATIO_SF8 */
+ {0,75,4,0,4,0,4}, /* CORR_SIG_NOISE_RATIO_SF9 */
+ {0,76,0,0,4,0,4}, /* CORR_SIG_NOISE_RATIO_SF10 */
+ {0,76,4,0,4,0,4}, /* CORR_SIG_NOISE_RATIO_SF11 */
+ {0,77,0,0,4,0,4}, /* CORR_SIG_NOISE_RATIO_SF12 */
+ {0,78,0,0,4,0,4}, /* CORR_NUM_SAME_PEAK */
+ {0,78,4,0,3,0,5}, /* CORR_MAC_GAIN */
+ {0,81,0,0,12,0,0}, /* ADJUST_MODEM_START_OFFSET_RDX4 */
+ {0,83,0,0,12,0,4092}, /* ADJUST_MODEM_START_OFFSET_SF12_RDX4 */
+ {0,85,0,0,8,0,7}, /* DBG_CORR_SELECT_SF */
+ {0,86,0,0,8,0,0}, /* DBG_CORR_SELECT_CHANNEL */
+ {0,87,0,0,8,1,0}, /* DBG_DETECT_CPT */
+ {0,88,0,0,8,1,0}, /* DBG_SYMB_CPT */
+ {0,89,0,0,1,0,1}, /* CHIRP_INVERT_RX */
+ {0,89,1,0,1,0,1}, /* DC_NOTCH_EN */
+ {0,90,0,0,1,0,0}, /* IMPLICIT_CRC_EN */
+ {0,90,1,0,3,0,0}, /* IMPLICIT_CODING_RATE */
+ {0,91,0,0,8,0,0}, /* IMPLICIT_PAYLOAD_LENGHT */
+ {0,92,0,0,8,0,29}, /* FREQ_TO_TIME_INVERT */
+ {0,93,0,0,6,0,9}, /* FREQ_TO_TIME_DRIFT */
+ {0,94,0,0,2,0,2}, /* PAYLOAD_FINE_TIMING_GAIN */
+ {0,94,2,0,2,0,1}, /* PREAMBLE_FINE_TIMING_GAIN */
+ {0,94,4,0,2,0,0}, /* TRACKING_INTEGRAL */
+ {0,95,0,0,4,0,1}, /* FRAME_SYNCH_PEAK1_POS */
+ {0,95,4,0,4,0,2}, /* FRAME_SYNCH_PEAK2_POS */
+ {0,96,0,0,16,0,10}, /* PREAMBLE_SYMB1_NB */
+ {0,98,0,0,1,0,1}, /* FRAME_SYNCH_GAIN */
+ {0,98,1,0,1,0,1}, /* SYNCH_DETECT_TH */
+ {0,99,0,0,4,0,8}, /* LLR_SCALE */
+ {0,99,4,0,2,0,2}, /* SNR_AVG_CST */
+ {0,100,0,0,7,0,0}, /* PPM_OFFSET */
+ {0,101,0,0,8,0,255}, /* MAX_PAYLOAD_LEN */
+ {0,102,0,0,1,0,1}, /* ONLY_CRC_EN */
+ {0,103,0,0,8,0,0}, /* ZERO_PAD */
+ {0,104,0,0,4,0,8}, /* DEC_GAIN_OFFSET */
+ {0,104,4,0,4,0,7}, /* CHAN_GAIN_OFFSET */
+ {0,105,1,0,1,0,1}, /* FORCE_HOST_RADIO_CTRL */
+ {0,105,2,0,1,0,1}, /* FORCE_HOST_FE_CTRL */
+ {0,105,3,0,1,0,1}, /* FORCE_DEC_FILTER_GAIN */
+ {0,106,0,0,1,0,1}, /* MCU_RST_0 */
+ {0,106,1,0,1,0,1}, /* MCU_RST_1 */
+ {0,106,2,0,1,0,0}, /* MCU_SELECT_MUX_0 */
+ {0,106,3,0,1,0,0}, /* MCU_SELECT_MUX_1 */
+ {0,106,4,0,1,1,0}, /* MCU_CORRUPTION_DETECTED_0 */
+ {0,106,5,0,1,1,0}, /* MCU_CORRUPTION_DETECTED_1 */
+ {0,106,6,0,1,0,0}, /* MCU_SELECT_EDGE_0 */
+ {0,106,7,0,1,0,0}, /* MCU_SELECT_EDGE_1 */
+ {0,107,0,0,8,0,1}, /* CHANN_SELECT_RSSI */
+ {0,108,0,0,8,0,32}, /* RSSI_BB_DEFAULT_VALUE */
+ {0,109,0,0,8,0,100}, /* RSSI_DEC_DEFAULT_VALUE */
+ {0,110,0,0,8,0,100}, /* RSSI_CHANN_DEFAULT_VALUE */
+ {0,111,0,0,5,0,7}, /* RSSI_BB_FILTER_ALPHA */
+ {0,112,0,0,5,0,5}, /* RSSI_DEC_FILTER_ALPHA */
+ {0,113,0,0,5,0,8}, /* RSSI_CHANN_FILTER_ALPHA */
+ {0,114,0,0,6,0,0}, /* IQ_MISMATCH_A_AMP_COEFF */
+ {0,115,0,0,6,0,0}, /* IQ_MISMATCH_A_PHI_COEFF */
+ {0,116,0,0,6,0,0}, /* IQ_MISMATCH_B_AMP_COEFF */
+ {0,116,6,0,1,0,0}, /* IQ_MISMATCH_B_SEL_I */
+ {0,117,0,0,6,0,0}, /* IQ_MISMATCH_B_PHI_COEFF */
+ {1,33,0,0,1,0,0}, /* TX_TRIG_IMMEDIATE */
+ {1,33,1,0,1,0,0}, /* TX_TRIG_DELAYED */
+ {1,33,2,0,1,0,0}, /* TX_TRIG_GPS */
+ {1,34,0,0,16,0,0}, /* TX_START_DELAY */
+ {1,36,0,0,4,0,1}, /* TX_FRAME_SYNCH_PEAK1_POS */
+ {1,36,4,0,4,0,2}, /* TX_FRAME_SYNCH_PEAK2_POS */
+ {1,37,0,0,3,0,0}, /* TX_RAMP_DURATION */
+ {1,39,0,1,8,0,0}, /* TX_OFFSET_I */
+ {1,40,0,1,8,0,0}, /* TX_OFFSET_Q */
+ {1,41,0,0,1,0,0}, /* TX_MODE */
+ {1,41,1,0,4,0,0}, /* TX_ZERO_PAD */
+ {1,41,5,0,1,0,0}, /* TX_EDGE_SELECT */
+ {1,41,6,0,1,0,0}, /* TX_EDGE_SELECT_TOP */
+ {1,42,0,0,2,0,0}, /* TX_GAIN */
+ {1,42,2,0,3,0,5}, /* TX_CHIRP_LOW_PASS */
+ {1,42,5,0,2,0,0}, /* TX_FCC_WIDEBAND */
+ {1,42,7,0,1,0,1}, /* TX_SWAP_IQ */
+ {1,43,0,0,1,0,0}, /* MBWSSF_IMPLICIT_HEADER */
+ {1,43,1,0,1,0,0}, /* MBWSSF_IMPLICIT_CRC_EN */
+ {1,43,2,0,3,0,0}, /* MBWSSF_IMPLICIT_CODING_RATE */
+ {1,44,0,0,8,0,0}, /* MBWSSF_IMPLICIT_PAYLOAD_LENGHT */
+ {1,45,0,0,1,0,1}, /* MBWSSF_AGC_FREEZE_ON_DETECT */
+ {1,46,0,0,4,0,1}, /* MBWSSF_FRAME_SYNCH_PEAK1_POS */
+ {1,46,4,0,4,0,2}, /* MBWSSF_FRAME_SYNCH_PEAK2_POS */
+ {1,47,0,0,16,0,10}, /* MBWSSF_PREAMBLE_SYMB1_NB */
+ {1,49,0,0,1,0,1}, /* MBWSSF_FRAME_SYNCH_GAIN */
+ {1,49,1,0,1,0,1}, /* MBWSSF_SYNCH_DETECT_TH */
+ {1,50,0,0,8,0,10}, /* MBWSSF_DETECT_MIN_SINGLE_PEAK */
+ {1,51,0,0,3,0,3}, /* MBWSSF_DETECT_TRIG_SAME_PEAK_NB */
+ {1,52,0,0,8,0,29}, /* MBWSSF_FREQ_TO_TIME_INVERT */
+ {1,53,0,0,6,0,36}, /* MBWSSF_FREQ_TO_TIME_DRIFT */
+ {1,54,0,0,12,0,0}, /* MBWSSF_PPM_CORRECTION */
+ {1,56,0,0,2,0,2}, /* MBWSSF_PAYLOAD_FINE_TIMING_GAIN */
+ {1,56,2,0,2,0,1}, /* MBWSSF_PREAMBLE_FINE_TIMING_GAIN */
+ {1,56,4,0,2,0,0}, /* MBWSSF_TRACKING_INTEGRAL */
+ {1,57,0,0,8,0,0}, /* MBWSSF_ZERO_PAD */
+ {1,58,0,0,2,0,0}, /* MBWSSF_MODEM_BW */
+ {1,58,2,0,1,0,0}, /* MBWSSF_RADIO_SELECT */
+ {1,58,3,0,1,0,1}, /* MBWSSF_RX_CHIRP_INVERT */
+ {1,59,0,0,4,0,8}, /* MBWSSF_LLR_SCALE */
+ {1,59,4,0,2,0,3}, /* MBWSSF_SNR_AVG_CST */
+ {1,59,6,0,1,0,0}, /* MBWSSF_PPM_OFFSET */
+ {1,60,0,0,4,0,7}, /* MBWSSF_RATE_SF */
+ {1,60,4,0,1,0,1}, /* MBWSSF_ONLY_CRC_EN */
+ {1,61,0,0,8,0,255}, /* MBWSSF_MAX_PAYLOAD_LEN */
+ {1,62,0,0,8,1,128}, /* TX_STATUS */
+ {1,63,0,0,3,0,0}, /* FSK_CH_BW_EXPO */
+ {1,63,3,0,3,0,0}, /* FSK_RSSI_LENGTH */
+ {1,63,6,0,1,0,0}, /* FSK_RX_INVERT */
+ {1,63,7,0,1,0,0}, /* FSK_PKT_MODE */
+ {1,64,0,0,3,0,0}, /* FSK_PSIZE */
+ {1,64,3,0,1,0,0}, /* FSK_CRC_EN */
+ {1,64,4,0,2,0,0}, /* FSK_DCFREE_ENC */
+ {1,64,6,0,1,0,0}, /* FSK_CRC_IBM */
+ {1,65,0,0,5,0,0}, /* FSK_ERROR_OSR_TOL */
+ {1,65,7,0,1,0,0}, /* FSK_RADIO_SELECT */
+ {1,66,0,0,16,0,0}, /* FSK_BR_RATIO */
+ {1,68,0,0,32,0,0}, /* FSK_REF_PATTERN_LSB */
+ {1,72,0,0,32,0,0}, /* FSK_REF_PATTERN_MSB */
+ {1,76,0,0,8,0,0}, /* FSK_PKT_LENGTH */
+ {1,77,0,0,1,0,1}, /* FSK_TX_GAUSSIAN_EN */
+ {1,77,1,0,2,0,0}, /* FSK_TX_GAUSSIAN_SELECT_BT */
+ {1,77,3,0,1,0,1}, /* FSK_TX_PATTERN_EN */
+ {1,77,4,0,1,0,0}, /* FSK_TX_PREAMBLE_SEQ */
+ {1,77,5,0,3,0,0}, /* FSK_TX_PSIZE */
+ {1,80,0,0,8,0,0}, /* FSK_NODE_ADRS */
+ {1,81,0,0,8,0,0}, /* FSK_BROADCAST */
+ {1,82,0,0,1,0,1}, /* FSK_AUTO_AFC_ON */
+ {1,83,0,0,10,0,0}, /* FSK_PATTERN_TIMEOUT_CFG */
+ {2,33,0,0,8,0,0}, /* SPI_RADIO_A__DATA */
+ {2,34,0,0,8,1,0}, /* SPI_RADIO_A__DATA_READBACK */
+ {2,35,0,0,8,0,0}, /* SPI_RADIO_A__ADDR */
+ {2,37,0,0,1,0,0}, /* SPI_RADIO_A__CS */
+ {2,38,0,0,8,0,0}, /* SPI_RADIO_B__DATA */
+ {2,39,0,0,8,1,0}, /* SPI_RADIO_B__DATA_READBACK */
+ {2,40,0,0,8,0,0}, /* SPI_RADIO_B__ADDR */
+ {2,42,0,0,1,0,0}, /* SPI_RADIO_B__CS */
+ {2,43,0,0,1,0,0}, /* RADIO_A_EN */
+ {2,43,1,0,1,0,0}, /* RADIO_B_EN */
+ {2,43,2,0,1,0,1}, /* RADIO_RST */
+ {2,43,3,0,1,0,0}, /* LNA_A_EN */
+ {2,43,4,0,1,0,0}, /* PA_A_EN */
+ {2,43,5,0,1,0,0}, /* LNA_B_EN */
+ {2,43,6,0,1,0,0}, /* PA_B_EN */
+ {2,44,0,0,2,0,0}, /* PA_GAIN */
+ {2,45,0,0,4,0,2}, /* LNA_A_CTRL_LUT */
+ {2,45,4,0,4,0,4}, /* PA_A_CTRL_LUT */
+ {2,46,0,0,4,0,2}, /* LNA_B_CTRL_LUT */
+ {2,46,4,0,4,0,4}, /* PA_B_CTRL_LUT */
+ {2,47,0,0,5,0,0}, /* CAPTURE_SOURCE */
+ {2,47,5,0,1,0,0}, /* CAPTURE_START */
+ {2,47,6,0,1,0,0}, /* CAPTURE_FORCE_TRIGGER */
+ {2,47,7,0,1,0,0}, /* CAPTURE_WRAP */
+ {2,48,0,0,16,0,0}, /* CAPTURE_PERIOD */
+ {2,51,0,0,8,1,0}, /* MODEM_STATUS */
+ {2,52,0,0,8,1,0}, /* VALID_HEADER_COUNTER_0 */
+ {2,54,0,0,8,1,0}, /* VALID_PACKET_COUNTER_0 */
+ {2,56,0,0,8,1,0}, /* VALID_HEADER_COUNTER_MBWSSF */
+ {2,57,0,0,8,1,0}, /* VALID_HEADER_COUNTER_FSK */
+ {2,58,0,0,8,1,0}, /* VALID_PACKET_COUNTER_MBWSSF */
+ {2,59,0,0,8,1,0}, /* VALID_PACKET_COUNTER_FSK */
+ {2,60,0,0,8,1,0}, /* CHANN_RSSI */
+ {2,61,0,0,8,1,0}, /* BB_RSSI */
+ {2,62,0,0,8,1,0}, /* DEC_RSSI */
+ {2,63,0,0,8,1,0}, /* DBG_MCU_DATA */
+ {2,64,0,0,8,1,0}, /* DBG_ARB_MCU_RAM_DATA */
+ {2,65,0,0,8,1,0}, /* DBG_AGC_MCU_RAM_DATA */
+ {2,66,0,0,16,1,0}, /* NEXT_PACKET_CNT */
+ {2,68,0,0,16,1,0}, /* ADDR_CAPTURE_COUNT */
+ {2,70,0,0,32,1,0}, /* TIMESTAMP */
+ {2,74,0,0,4,1,0}, /* DBG_CHANN0_GAIN */
+ {2,74,4,0,4,1,0}, /* DBG_CHANN1_GAIN */
+ {2,75,0,0,4,1,0}, /* DBG_CHANN2_GAIN */
+ {2,75,4,0,4,1,0}, /* DBG_CHANN3_GAIN */
+ {2,76,0,0,4,1,0}, /* DBG_CHANN4_GAIN */
+ {2,76,4,0,4,1,0}, /* DBG_CHANN5_GAIN */
+ {2,77,0,0,4,1,0}, /* DBG_CHANN6_GAIN */
+ {2,77,4,0,4,1,0}, /* DBG_CHANN7_GAIN */
+ {2,78,0,0,4,1,0}, /* DBG_DEC_FILT_GAIN */
+ {2,79,0,0,3,1,0}, /* SPI_DATA_FIFO_PTR */
+ {2,79,3,0,3,1,0}, /* PACKET_DATA_FIFO_PTR */
+ {2,80,0,0,8,0,0}, /* DBG_ARB_MCU_RAM_ADDR */
+ {2,81,0,0,8,0,0}, /* DBG_AGC_MCU_RAM_ADDR */
+ {2,82,0,0,1,0,0}, /* SPI_MASTER_CHIP_SELECT_POLARITY */
+ {2,82,1,0,1,0,0}, /* SPI_MASTER_CPOL */
+ {2,82,2,0,1,0,0}, /* SPI_MASTER_CPHA */
+ {2,83,0,0,1,0,0}, /* SIG_GEN_ANALYSER_MUX_SEL */
+ {2,84,0,0,1,0,0}, /* SIG_GEN_EN */
+ {2,84,1,0,1,0,0}, /* SIG_ANALYSER_EN */
+ {2,84,2,0,2,0,0}, /* SIG_ANALYSER_AVG_LEN */
+ {2,84,4,0,3,0,0}, /* SIG_ANALYSER_PRECISION */
+ {2,84,7,0,1,1,0}, /* SIG_ANALYSER_VALID_OUT */
+ {2,85,0,0,8,0,0}, /* SIG_GEN_FREQ */
+ {2,86,0,0,8,0,0}, /* SIG_ANALYSER_FREQ */
+ {2,87,0,0,8,1,0}, /* SIG_ANALYSER_I_OUT */
+ {2,88,0,0,8,1,0}, /* SIG_ANALYSER_Q_OUT */
+ {2,89,0,0,1,0,0}, /* GPS_EN */
+ {2,89,1,0,1,0,1}, /* GPS_POL */
+ {2,90,0,1,8,0,0}, /* SW_TEST_REG1 */
+ {2,91,2,1,6,0,0}, /* SW_TEST_REG2 */
+ {2,92,0,1,16,0,0}, /* SW_TEST_REG3 */
+ {2,94,0,0,4,1,0}, /* DATA_MNGT_STATUS */
+ {2,95,0,0,5,1,0}, /* DATA_MNGT_CPT_FRAME_ALLOCATED */
+ {2,96,0,0,5,1,0}, /* DATA_MNGT_CPT_FRAME_FINISHED */
+ {2,97,0,0,5,1,0}, /* DATA_MNGT_CPT_FRAME_READEN */
+ {1,33,0,0,8,0,0} /* TX_TRIG_ALL (alias) */
+};
+
+/* -------------------------------------------------------------------------- */
+/* --- PRIVATE VARIABLES ---------------------------------------------------- */
+
+static int lgw_regpage = -1; /*! keep the value of the register page selected */
+
+/* -------------------------------------------------------------------------- */
+/* --- INTERNAL SHARED VARIABLES -------------------------------------------- */
+
+void *lgw_spi_target = NULL; /*! generic pointer to the SPI device */
+uint8_t lgw_spi_mux_mode = 0; /*! current SPI mux mode used */
+
+/* -------------------------------------------------------------------------- */
+/* --- PRIVATE FUNCTIONS ---------------------------------------------------- */
+
+int page_switch(uint8_t target) {
+ lgw_regpage = PAGE_MASK & target;
+ lgw_spi_w(lgw_spi_target, lgw_spi_mux_mode, LGW_SPI_MUX_TARGET_SX1301, PAGE_ADDR, (uint8_t)lgw_regpage);
+ return LGW_REG_SUCCESS;
+}
+
+/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
+
+bool check_fpga_version(uint8_t version) {
+ int i;
+
+ for (i = 0; i < (int)(sizeof FPGA_VERSION); i++) {
+ if (FPGA_VERSION[i] == version ) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
+
+int reg_w_align32(void *spi_target, uint8_t spi_mux_mode, uint8_t spi_mux_target, struct lgw_reg_s r, int32_t reg_value) {
+ int spi_stat = LGW_REG_SUCCESS;
+ int i, size_byte;
+ uint8_t buf[4] = "\x00\x00\x00\x00";
+
+ if ((r.leng == 8) && (r.offs == 0)) {
+ /* direct write */
+ spi_stat += lgw_spi_w(spi_target, spi_mux_mode, spi_mux_target, r.addr, (uint8_t)reg_value);
+ } else if ((r.offs + r.leng) <= 8) {
+ /* single-byte read-modify-write, offs:[0-7], leng:[1-7] */
+ spi_stat += lgw_spi_r(spi_target, spi_mux_mode, spi_mux_target, r.addr, &buf[0]);
+ buf[1] = ((1 << r.leng) - 1) << r.offs; /* bit mask */
+ buf[2] = ((uint8_t)reg_value) << r.offs; /* new data offsetted */
+ buf[3] = (~buf[1] & buf[0]) | (buf[1] & buf[2]); /* mixing old & new data */
+ spi_stat += lgw_spi_w(spi_target, spi_mux_mode, spi_mux_target, r.addr, buf[3]);
+ } else if ((r.offs == 0) && (r.leng > 0) && (r.leng <= 32)) {
+ /* multi-byte direct write routine */
+ size_byte = (r.leng + 7) / 8; /* add a byte if it's not an exact multiple of 8 */
+ for (i=0; i<size_byte; ++i) {
+ /* big endian register file for a file on N bytes
+ Least significant byte is stored in buf[0], most one in buf[N-1] */
+ buf[i] = (uint8_t)(0x000000FF & reg_value);
+ reg_value = (reg_value >> 8);
+ }
+ spi_stat += lgw_spi_wb(spi_target, spi_mux_mode, spi_mux_target, r.addr, buf, size_byte); /* write the register in one burst */
+ } else {
+ /* register spanning multiple memory bytes but with an offset */
+ DEBUG_MSG("ERROR: REGISTER SIZE AND OFFSET ARE NOT SUPPORTED\n");
+ return LGW_REG_ERROR;
+ }
+
+ return spi_stat;
+}
+
+/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
+
+int reg_r_align32(void *spi_target, uint8_t spi_mux_mode, uint8_t spi_mux_target, struct lgw_reg_s r, int32_t *reg_value) {
+ int spi_stat = LGW_SPI_SUCCESS;
+ uint8_t bufu[4] = "\x00\x00\x00\x00";
+ int8_t *bufs = (int8_t *)bufu;
+ int i, size_byte;
+ uint32_t u = 0;
+
+ if ((r.offs + r.leng) <= 8) {
+ /* read one byte, then shift and mask bits to get reg value with sign extension if needed */
+ spi_stat += lgw_spi_r(spi_target, spi_mux_mode, spi_mux_target, r.addr, &bufu[0]);
+ bufu[1] = bufu[0] << (8 - r.leng - r.offs); /* left-align the data */
+ if (r.sign == true) {
+ bufs[2] = bufs[1] >> (8 - r.leng); /* right align the data with sign extension (ARITHMETIC right shift) */
+ *reg_value = (int32_t)bufs[2]; /* signed pointer -> 32b sign extension */
+ } else {
+ bufu[2] = bufu[1] >> (8 - r.leng); /* right align the data, no sign extension */
+ *reg_value = (int32_t)bufu[2]; /* unsigned pointer -> no sign extension */
+ }
+ } else if ((r.offs == 0) && (r.leng > 0) && (r.leng <= 32)) {
+ size_byte = (r.leng + 7) / 8; /* add a byte if it's not an exact multiple of 8 */
+ spi_stat += lgw_spi_rb(spi_target, spi_mux_mode, spi_mux_target, r.addr, bufu, size_byte);
+ u = 0;
+ for (i=(size_byte-1); i>=0; --i) {
+ u = (uint32_t)bufu[i] + (u << 8); /* transform a 4-byte array into a 32 bit word */
+ }
+ if (r.sign == true) {
+ u = u << (32 - r.leng); /* left-align the data */
+ *reg_value = (int32_t)u >> (32 - r.leng); /* right-align the data with sign extension (ARITHMETIC right shift) */
+ } else {
+ *reg_value = (int32_t)u; /* unsigned value -> return 'as is' */
+ }
+ } else {
+ /* register spanning multiple memory bytes but with an offset */
+ DEBUG_MSG("ERROR: REGISTER SIZE AND OFFSET ARE NOT SUPPORTED\n");
+ return LGW_REG_ERROR;
+ }
+
+ return spi_stat;
+}
+
+/* -------------------------------------------------------------------------- */
+/* --- PUBLIC FUNCTIONS DEFINITION ------------------------------------------ */
+
+/* Concentrator connect */
+int lgw_connect(bool spi_only, uint32_t tx_notch_freq) {
+ int spi_stat = LGW_SPI_SUCCESS;
+ uint8_t u = 0;
+ int x;
+
+ /* check SPI link status */
+ 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(&lgw_spi_target);
+ if (spi_stat != LGW_SPI_SUCCESS) {
+ DEBUG_MSG("ERROR CONNECTING CONCENTRATOR\n");
+ return LGW_REG_ERROR;
+ }
+
+ if (spi_only == false ) {
+ /* Detect if the gateway has an FPGA with SPI mux header support */
+ /* First, we assume there is an FPGA, and try to read its version */
+ spi_stat = lgw_spi_r(lgw_spi_target, LGW_SPI_MUX_MODE1, LGW_SPI_MUX_TARGET_FPGA, loregs[LGW_VERSION].addr, &u);
+ if (spi_stat != LGW_SPI_SUCCESS) {
+ DEBUG_MSG("ERROR READING VERSION REGISTER\n");
+ return LGW_REG_ERROR;
+ }
+ if (check_fpga_version(u) != true) {
+ /* We failed to read expected FPGA version, so let's assume there is no FPGA */
+ DEBUG_PRINTF("INFO: no FPGA detected or version not supported (v%u)\n", u);
+ lgw_spi_mux_mode = LGW_SPI_MUX_MODE0;
+ } else {
+ DEBUG_PRINTF("INFO: detected FPGA with SPI mux header (v%u)\n", u);
+ lgw_spi_mux_mode = LGW_SPI_MUX_MODE1;
+ /* FPGA Soft Reset */
+ lgw_spi_w(lgw_spi_target, lgw_spi_mux_mode, LGW_SPI_MUX_TARGET_FPGA, 0, 1);
+ lgw_spi_w(lgw_spi_target, lgw_spi_mux_mode, LGW_SPI_MUX_TARGET_FPGA, 0, 0);
+ /* FPGA configure */
+ x = lgw_fpga_configure(tx_notch_freq);
+ if (x != LGW_REG_SUCCESS) {
+ DEBUG_MSG("ERROR CONFIGURING FPGA\n");
+ return LGW_REG_ERROR;
+ }
+ }
+
+ /* check SX1301 version */
+ spi_stat = lgw_spi_r(lgw_spi_target, lgw_spi_mux_mode, LGW_SPI_MUX_TARGET_SX1301, loregs[LGW_VERSION].addr, &u);
+ if (spi_stat != LGW_SPI_SUCCESS) {
+ DEBUG_MSG("ERROR READING CHIP VERSION REGISTER\n");
+ return LGW_REG_ERROR;
+ }
+ if (u != loregs[LGW_VERSION].dflt) {
+ DEBUG_PRINTF("ERROR: NOT EXPECTED CHIP VERSION (v%u)\n", u);
+ return LGW_REG_ERROR;
+ }
+
+ /* write 0 to the page/reset register */
+ spi_stat = lgw_spi_w(lgw_spi_target, lgw_spi_mux_mode, LGW_SPI_MUX_TARGET_SX1301, loregs[LGW_PAGE_REG].addr, 0);
+ if (spi_stat != LGW_SPI_SUCCESS) {
+ DEBUG_MSG("ERROR WRITING PAGE REGISTER\n");
+ return LGW_REG_ERROR;
+ } else {
+ lgw_regpage = 0;
+ }
+ }
+
+ DEBUG_MSG("Note: success connecting the concentrator\n");
+ return LGW_REG_SUCCESS;
+}
+
+/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
+
+/* Concentrator disconnect */
+int lgw_disconnect(void) {
+ if (lgw_spi_target != NULL) {
+ lgw_spi_close(lgw_spi_target);
+ lgw_spi_target = NULL;
+ DEBUG_MSG("Note: success disconnecting the concentrator\n");
+ return LGW_REG_SUCCESS;
+ } else {
+ DEBUG_MSG("WARNING: concentrator was already disconnected\n");
+ return LGW_REG_ERROR;
+ }
+}
+
+/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
+
+/* soft-reset function */
+int lgw_soft_reset(void) {
+ /* check if SPI is initialised */
+ if ((lgw_spi_target == NULL) || (lgw_regpage < 0)) {
+ DEBUG_MSG("ERROR: CONCENTRATOR UNCONNECTED\n");
+ return LGW_REG_ERROR;
+ }
+ lgw_spi_w(lgw_spi_target, lgw_spi_mux_mode, LGW_SPI_MUX_TARGET_SX1301, 0, 0x80); /* 1 -> SOFT_RESET bit */
+ lgw_regpage = 0; /* reset the paging static variable */
+ return LGW_REG_SUCCESS;
+}
+
+/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
+
+/* register verification */
+int lgw_reg_check(FILE *f) {
+ struct lgw_reg_s r;
+ int32_t read_value;
+ char ok_msg[] = "+++MATCH+++";
+ char notok_msg[] = "###MISMATCH###";
+ char *ptr;
+ int i;
+
+ /* check if SPI is initialised */
+ if ((lgw_spi_target == NULL) || (lgw_regpage < 0)) {
+ DEBUG_MSG("ERROR: CONCENTRATOR UNCONNECTED\n");
+ fprintf(f, "ERROR: CONCENTRATOR UNCONNECTED\n");
+ return LGW_REG_ERROR;
+ }
+
+ fprintf(f, "Start of register verification\n");
+ for (i=0; i<LGW_TOTALREGS; ++i) {
+ r = loregs[i];
+ lgw_reg_r(i, &read_value);
+ ptr = (read_value == r.dflt) ? ok_msg : notok_msg;
+ if (r.sign == true)
+ fprintf(f, "%s reg number %d read: %d (%x) default: %d (%x)\n", ptr, i, read_value, read_value, r.dflt, r.dflt);
+ else
+ fprintf(f, "%s reg number %d read: %u (%x) default: %u (%x)\n", ptr, i, read_value, read_value, r.dflt, r.dflt);
+ }
+ fprintf(f, "End of register verification\n");
+
+ return LGW_REG_SUCCESS;
+}
+
+/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
+
+/* 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;
+ struct lgw_reg_s r;
+
+ /* check input parameters */
+ if (register_id >= LGW_TOTALREGS) {
+ DEBUG_MSG("ERROR: REGISTER NUMBER OUT OF DEFINED RANGE\n");
+ return LGW_REG_ERROR;
+ }
+
+ /* check if SPI is initialised */
+ if ((lgw_spi_target == NULL) || (lgw_regpage < 0)) {
+ DEBUG_MSG("ERROR: CONCENTRATOR UNCONNECTED\n");
+ return LGW_REG_ERROR;
+ }
+
+ /* intercept direct access to PAGE_REG & SOFT_RESET */
+ if (register_id == LGW_PAGE_REG) {
+ page_switch(reg_value);
+ return LGW_REG_SUCCESS;
+ } else if (register_id == LGW_SOFT_RESET) {
+ /* only reset if lsb is 1 */
+ if ((reg_value & 0x01) != 0)
+ lgw_soft_reset();
+ return LGW_REG_SUCCESS;
+ }
+
+ /* get register struct from the struct array */
+ r = loregs[register_id];
+
+ /* reject write to read-only registers */
+ if (r.rdon == 1){
+ DEBUG_MSG("ERROR: TRYING TO WRITE A READ-ONLY REGISTER\n");
+ return LGW_REG_ERROR;
+ }
+
+ /* select proper register page if needed */
+ if ((r.page != -1) && (r.page != lgw_regpage)) {
+ spi_stat += page_switch(r.page);
+ }
+
+ spi_stat += reg_w_align32(lgw_spi_target, lgw_spi_mux_mode, LGW_SPI_MUX_TARGET_SX1301, r, reg_value);
+
+ if (spi_stat != LGW_SPI_SUCCESS) {
+ DEBUG_MSG("ERROR: SPI ERROR DURING REGISTER WRITE\n");
+ return LGW_REG_ERROR;
+ } else {
+ return LGW_REG_SUCCESS;
+ }
+}
+
+/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
+
+/* Read to a register addressed by name */
+int lgw_reg_r(uint16_t register_id, int32_t *reg_value) {
+ int spi_stat = LGW_SPI_SUCCESS;
+ struct lgw_reg_s r;
+
+ /* check input parameters */
+ CHECK_NULL(reg_value);
+ if (register_id >= LGW_TOTALREGS) {
+ DEBUG_MSG("ERROR: REGISTER NUMBER OUT OF DEFINED RANGE\n");
+ return LGW_REG_ERROR;
+ }
+
+ /* check if SPI is initialised */
+ if ((lgw_spi_target == NULL) || (lgw_regpage < 0)) {
+ DEBUG_MSG("ERROR: CONCENTRATOR UNCONNECTED\n");
+ return LGW_REG_ERROR;
+ }
+
+ /* get register struct from the struct array */
+ r = loregs[register_id];
+
+ /* select proper register page if needed */
+ if ((r.page != -1) && (r.page != lgw_regpage)) {
+ spi_stat += page_switch(r.page);
+ }
+
+ spi_stat += reg_r_align32(lgw_spi_target, lgw_spi_mux_mode, LGW_SPI_MUX_TARGET_SX1301, r, reg_value);
+
+ if (spi_stat != LGW_SPI_SUCCESS) {
+ DEBUG_MSG("ERROR: SPI ERROR DURING REGISTER WRITE\n");
+ return LGW_REG_ERROR;
+ } else {
+ return LGW_REG_SUCCESS;
+ }
+}
+
+/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
+
+/* Point to a register by name and do a burst write */
+int lgw_reg_wb(uint16_t register_id, uint8_t *data, uint16_t size) {
+ int spi_stat = LGW_SPI_SUCCESS;
+ struct lgw_reg_s r;
+
+ /* check input parameters */
+ CHECK_NULL(data);
+ if (size == 0) {
+ DEBUG_MSG("ERROR: BURST OF NULL LENGTH\n");
+ return LGW_REG_ERROR;
+ }
+ if (register_id >= LGW_TOTALREGS) {
+ DEBUG_MSG("ERROR: REGISTER NUMBER OUT OF DEFINED RANGE\n");
+ return LGW_REG_ERROR;
+ }
+
+ /* check if SPI is initialised */
+ if ((lgw_spi_target == NULL) || (lgw_regpage < 0)) {
+ DEBUG_MSG("ERROR: CONCENTRATOR UNCONNECTED\n");
+ return LGW_REG_ERROR;
+ }
+
+ /* get register struct from the struct array */
+ r = loregs[register_id];
+
+ /* reject write to read-only registers */
+ if (r.rdon == 1){
+ DEBUG_MSG("ERROR: TRYING TO BURST WRITE A READ-ONLY REGISTER\n");
+ return LGW_REG_ERROR;
+ }
+
+ /* select proper register page if needed */
+ if ((r.page != -1) && (r.page != lgw_regpage)) {
+ spi_stat += page_switch(r.page);
+ }
+
+ /* do the burst write */
+ spi_stat += lgw_spi_wb(lgw_spi_target, lgw_spi_mux_mode, LGW_SPI_MUX_TARGET_SX1301, r.addr, data, size);
+
+ if (spi_stat != LGW_SPI_SUCCESS) {
+ DEBUG_MSG("ERROR: SPI ERROR DURING REGISTER BURST WRITE\n");
+ return LGW_REG_ERROR;
+ } else {
+ return LGW_REG_SUCCESS;
+ }
+}
+
+/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
+
+/* Point to a register by name and do a burst read */
+int lgw_reg_rb(uint16_t register_id, uint8_t *data, uint16_t size) {
+ int spi_stat = LGW_SPI_SUCCESS;
+ struct lgw_reg_s r;
+
+ /* check input parameters */
+ CHECK_NULL(data);
+ if (size == 0) {
+ DEBUG_MSG("ERROR: BURST OF NULL LENGTH\n");
+ return LGW_REG_ERROR;
+ }
+ if (register_id >= LGW_TOTALREGS) {
+ DEBUG_MSG("ERROR: REGISTER NUMBER OUT OF DEFINED RANGE\n");
+ return LGW_REG_ERROR;
+ }
+
+ /* check if SPI is initialised */
+ if ((lgw_spi_target == NULL) || (lgw_regpage < 0)) {
+ DEBUG_MSG("ERROR: CONCENTRATOR UNCONNECTED\n");
+ return LGW_REG_ERROR;
+ }
+
+ /* get register struct from the struct array */
+ r = loregs[register_id];
+
+ /* select proper register page if needed */
+ if ((r.page != -1) && (r.page != lgw_regpage)) {
+ spi_stat += page_switch(r.page);
+ }
+
+ /* do the burst read */
+ spi_stat += lgw_spi_rb(lgw_spi_target, lgw_spi_mux_mode, LGW_SPI_MUX_TARGET_SX1301, r.addr, data, size);
+
+ if (spi_stat != LGW_SPI_SUCCESS) {
+ DEBUG_MSG("ERROR: SPI ERROR DURING REGISTER BURST READ\n");
+ return LGW_REG_ERROR;
+ } else {
+ return LGW_REG_SUCCESS;
+ }
+}
+
+/* --- EOF ------------------------------------------------------------------ */
diff --git a/libloragw/src/loragw_spi.native.c b/libloragw/src/loragw_spi.native.c
new file mode 100644
index 0000000..c01ed1c
--- /dev/null
+++ b/libloragw/src/loragw_spi.native.c
@@ -0,0 +1,385 @@
+/*
+ / _____) _ | |
+( (____ _____ ____ _| |_ _____ ____| |__
+ \____ \| ___ | (_ _) ___ |/ ___) _ \
+ _____) ) ____| | | || |_| ____( (___| | | |
+(______/|_____)_|_|_| \__)_____)\____)_| |_|
+ (C)2013 Semtech-Cycleo
+
+Description:
+ Host specific functions to address the LoRa concentrator registers through
+ a SPI interface.
+ Single-byte read/write and burst read/write.
+ Does not handle pagination.
+ Could be used with multiple SPI ports in parallel (explicit file descriptor)
+
+License: Revised BSD License, see LICENSE.TXT file include in the project
+Maintainer: Sylvain Miermont
+*/
+
+
+/* -------------------------------------------------------------------------- */
+/* --- DEPENDANCIES --------------------------------------------------------- */
+
+#include <stdint.h> /* C99 types */
+#include <stdio.h> /* printf fprintf */
+#include <stdlib.h> /* malloc free */
+#include <unistd.h> /* lseek, close */
+#include <fcntl.h> /* open */
+#include <string.h> /* memset */
+
+#include <sys/ioctl.h>
+#include <linux/spi/spidev.h>
+
+#include "loragw_spi.h"
+#include "loragw_hal.h"
+
+/* -------------------------------------------------------------------------- */
+/* --- PRIVATE MACROS ------------------------------------------------------- */
+
+#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
+#if DEBUG_SPI == 1
+ #define DEBUG_MSG(str) fprintf(stderr, str)
+ #define DEBUG_PRINTF(fmt, args...) fprintf(stderr,"%s:%d: "fmt, __FUNCTION__, __LINE__, args)
+ #define CHECK_NULL(a) if(a==NULL){fprintf(stderr,"%s:%d: ERROR: NULL POINTER AS ARGUMENT\n", __FUNCTION__, __LINE__);return LGW_SPI_ERROR;}
+#else
+ #define DEBUG_MSG(str)
+ #define DEBUG_PRINTF(fmt, args...)
+ #define CHECK_NULL(a) if(a==NULL){return LGW_SPI_ERROR;}
+#endif
+
+/* -------------------------------------------------------------------------- */
+/* --- PRIVATE CONSTANTS ---------------------------------------------------- */
+
+#define READ_ACCESS 0x00
+#define WRITE_ACCESS 0x80
+#define SPI_SPEED 8000000
+#define SPI_DEV_PATH "/dev/spidev0.0"
+//#define SPI_DEV_PATH "/dev/spidev32766.0"
+
+/* -------------------------------------------------------------------------- */
+/* --- PUBLIC FUNCTIONS DEFINITION ------------------------------------------ */
+
+/* SPI initialization and configuration */
+int lgw_spi_open(void **spi_target_ptr) {
+ int *spi_device = NULL;
+ int dev;
+ int a=0, b=0;
+ int i;
+
+ /* check input variables */
+ CHECK_NULL(spi_target_ptr); /* cannot be null, must point on a void pointer (*spi_target_ptr can be null) */
+
+ /* allocate memory for the device descriptor */
+ spi_device = malloc(sizeof(int));
+ if (spi_device == NULL) {
+ DEBUG_MSG("ERROR: MALLOC FAIL\n");
+ return LGW_SPI_ERROR;
+ }
+
+ /* open SPI device */
+ dev = open(SPI_DEV_PATH, O_RDWR);
+ if (dev < 0) {
+ DEBUG_PRINTF("ERROR: failed to open SPI device %s\n", SPI_DEV_PATH);
+ return LGW_SPI_ERROR;
+ }
+
+ /* setting SPI mode to 'mode 0' */
+ i = SPI_MODE_0;
+ a = ioctl(dev, SPI_IOC_WR_MODE, &i);
+ b = ioctl(dev, SPI_IOC_RD_MODE, &i);
+ if ((a < 0) || (b < 0)) {
+ DEBUG_MSG("ERROR: SPI PORT FAIL TO SET IN MODE 0\n");
+ close(dev);
+ free(spi_device);
+ return LGW_SPI_ERROR;
+ }
+
+ /* setting SPI max clk (in Hz) */
+ i = SPI_SPEED;
+ a = ioctl(dev, SPI_IOC_WR_MAX_SPEED_HZ, &i);
+ b = ioctl(dev, SPI_IOC_RD_MAX_SPEED_HZ, &i);
+ if ((a < 0) || (b < 0)) {
+ DEBUG_MSG("ERROR: SPI PORT FAIL TO SET MAX SPEED\n");
+ close(dev);
+ free(spi_device);
+ return LGW_SPI_ERROR;
+ }
+
+ /* setting SPI to MSB first */
+ i = 0;
+ a = ioctl(dev, SPI_IOC_WR_LSB_FIRST, &i);
+ b = ioctl(dev, SPI_IOC_RD_LSB_FIRST, &i);
+ if ((a < 0) || (b < 0)) {
+ DEBUG_MSG("ERROR: SPI PORT FAIL TO SET MSB FIRST\n");
+ close(dev);
+ free(spi_device);
+ return LGW_SPI_ERROR;
+ }
+
+ /* setting SPI to 8 bits per word */
+ i = 0;
+ a = ioctl(dev, SPI_IOC_WR_BITS_PER_WORD, &i);
+ b = ioctl(dev, SPI_IOC_RD_BITS_PER_WORD, &i);
+ if ((a < 0) || (b < 0)) {
+ DEBUG_MSG("ERROR: SPI PORT FAIL TO SET 8 BITS-PER-WORD\n");
+ close(dev);
+ return LGW_SPI_ERROR;
+ }
+
+ *spi_device = dev;
+ *spi_target_ptr = (void *)spi_device;
+ DEBUG_MSG("Note: SPI port opened and configured ok\n");
+ return LGW_SPI_SUCCESS;
+}
+
+/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
+
+/* SPI release */
+int lgw_spi_close(void *spi_target) {
+ int spi_device;
+ int a;
+
+ /* check input variables */
+ CHECK_NULL(spi_target);
+
+ /* close file & deallocate file descriptor */
+ spi_device = *(int *)spi_target; /* must check that spi_target is not null beforehand */
+ a = close(spi_device);
+ free(spi_target);
+
+ /* determine return code */
+ if (a < 0) {
+ DEBUG_MSG("ERROR: SPI PORT FAILED TO CLOSE\n");
+ return LGW_SPI_ERROR;
+ } else {
+ DEBUG_MSG("Note: SPI port closed\n");
+ return LGW_SPI_SUCCESS;
+ }
+}
+
+/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
+
+/* Simple write */
+int lgw_spi_w(void *spi_target, uint8_t spi_mux_mode, 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);
+ if ((address & 0x80) != 0) {
+ DEBUG_MSG("WARNING: SPI address > 127\n");
+ }
+
+ spi_device = *(int *)spi_target; /* must check that spi_target is not null beforehand */
+
+ /* prepare frame to be sent */
+ if (spi_mux_mode == LGW_SPI_MUX_MODE1) {
+ out_buf[0] = spi_mux_target;
+ out_buf[1] = WRITE_ACCESS | (address & 0x7F);
+ out_buf[2] = data;
+ command_size = 3;
+ } else {
+ out_buf[0] = WRITE_ACCESS | (address & 0x7F);
+ out_buf[1] = data;
+ command_size = 2;
+ }
+
+ /* 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;
+ }
+}
+
+/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
+
+/* Simple read */
+int lgw_spi_r(void *spi_target, uint8_t spi_mux_mode, 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);
+ if ((address & 0x80) != 0) {
+ DEBUG_MSG("WARNING: SPI address > 127\n");
+ }
+ CHECK_NULL(data);
+
+ spi_device = *(int *)spi_target; /* must check that spi_target is not null beforehand */
+
+ /* prepare frame to be sent */
+ if (spi_mux_mode == LGW_SPI_MUX_MODE1) {
+ out_buf[0] = spi_mux_target;
+ out_buf[1] = READ_ACCESS | (address & 0x7F);
+ out_buf[2] = 0x00;
+ command_size = 3;
+ } else {
+ out_buf[0] = READ_ACCESS | (address & 0x7F);
+ out_buf[1] = 0x00;
+ command_size = 2;
+ }
+
+ /* 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;
+ }
+}
+
+/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
+
+/* Burst (multiple-byte) write */
+int lgw_spi_wb(void *spi_target, uint8_t spi_mux_mode, uint8_t spi_mux_target, uint8_t address, uint8_t *data, uint16_t size) {
+ int spi_device;
+ uint8_t command[2];
+ uint8_t command_size;
+ struct spi_ioc_transfer k[2];
+ int size_to_do, chunk_size, offset;
+ int byte_transfered = 0;
+ int i;
+
+ /* check input parameters */
+ CHECK_NULL(spi_target);
+ if ((address & 0x80) != 0) {
+ DEBUG_MSG("WARNING: SPI address > 127\n");
+ }
+ CHECK_NULL(data);
+ if (size == 0) {
+ DEBUG_MSG("ERROR: BURST OF NULL LENGTH\n");
+ return LGW_SPI_ERROR;
+ }
+
+ spi_device = *(int *)spi_target; /* must check that spi_target is not null beforehand */
+
+ /* prepare command byte */
+ if (spi_mux_mode == LGW_SPI_MUX_MODE1) {
+ command[0] = spi_mux_target;
+ command[1] = WRITE_ACCESS | (address & 0x7F);
+ command_size = 2;
+ } else {
+ command[0] = WRITE_ACCESS | (address & 0x7F);
+ command_size = 1;
+ }
+ size_to_do = size;
+
+ /* I/O transaction */
+ memset(&k, 0, sizeof(k)); /* clear k */
+ k[0].tx_buf = (unsigned long) &command[0];
+ k[0].len = command_size;
+ k[0].cs_change = 0;
+ k[1].cs_change = 0;
+ for (i=0; size_to_do > 0; ++i) {
+ chunk_size = (size_to_do < LGW_BURST_CHUNK) ? size_to_do : LGW_BURST_CHUNK;
+ offset = i * LGW_BURST_CHUNK;
+ k[1].tx_buf = (unsigned long)(data + offset);
+ k[1].len = chunk_size;
+ byte_transfered += (ioctl(spi_device, SPI_IOC_MESSAGE(2), &k) - k[0].len );
+ DEBUG_PRINTF("BURST WRITE: to trans %d # chunk %d # transferred %d \n", size_to_do, chunk_size, byte_transfered);
+ size_to_do -= chunk_size; /* subtract the quantity of data already transferred */
+ }
+
+ /* determine return code */
+ if (byte_transfered != size) {
+ DEBUG_MSG("ERROR: SPI BURST WRITE FAILURE\n");
+ return LGW_SPI_ERROR;
+ } else {
+ DEBUG_MSG("Note: SPI burst write success\n");
+ return LGW_SPI_SUCCESS;
+ }
+}
+
+/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
+
+/* Burst (multiple-byte) read */
+int lgw_spi_rb(void *spi_target, uint8_t spi_mux_mode, uint8_t spi_mux_target, uint8_t address, uint8_t *data, uint16_t size) {
+ int spi_device;
+ uint8_t command[2];
+ uint8_t command_size;
+ struct spi_ioc_transfer k[2];
+ int size_to_do, chunk_size, offset;
+ int byte_transfered = 0;
+ int i;
+
+ /* check input parameters */
+ CHECK_NULL(spi_target);
+ if ((address & 0x80) != 0) {
+ DEBUG_MSG("WARNING: SPI address > 127\n");
+ }
+ CHECK_NULL(data);
+ if (size == 0) {
+ DEBUG_MSG("ERROR: BURST OF NULL LENGTH\n");
+ return LGW_SPI_ERROR;
+ }
+
+ spi_device = *(int *)spi_target; /* must check that spi_target is not null beforehand */
+
+ /* prepare command byte */
+ if (spi_mux_mode == LGW_SPI_MUX_MODE1) {
+ command[0] = spi_mux_target;
+ command[1] = READ_ACCESS | (address & 0x7F);
+ command_size = 2;
+ } else {
+ command[0] = READ_ACCESS | (address & 0x7F);
+ command_size = 1;
+ }
+ size_to_do = size;
+
+ /* I/O transaction */
+ memset(&k, 0, sizeof(k)); /* clear k */
+ k[0].tx_buf = (unsigned long) &command[0];
+ k[0].len = command_size;
+ k[0].cs_change = 0;
+ k[1].cs_change = 0;
+ for (i=0; size_to_do > 0; ++i) {
+ chunk_size = (size_to_do < LGW_BURST_CHUNK) ? size_to_do : LGW_BURST_CHUNK;
+ offset = i * LGW_BURST_CHUNK;
+ k[1].rx_buf = (unsigned long)(data + offset);
+ k[1].len = chunk_size;
+ byte_transfered += (ioctl(spi_device, SPI_IOC_MESSAGE(2), &k) - k[0].len );
+ DEBUG_PRINTF("BURST READ: to trans %d # chunk %d # transferred %d \n", size_to_do, chunk_size, byte_transfered);
+ size_to_do -= chunk_size; /* subtract the quantity of data already transferred */
+ }
+
+ /* determine return code */
+ if (byte_transfered != size) {
+ DEBUG_MSG("ERROR: SPI BURST READ FAILURE\n");
+ return LGW_SPI_ERROR;
+ } else {
+ DEBUG_MSG("Note: SPI burst read success\n");
+ return LGW_SPI_SUCCESS;
+ }
+}
+
+/* --- EOF ------------------------------------------------------------------ */
diff --git a/libloragw/tst/test_loragw_cal.c b/libloragw/tst/test_loragw_cal.c
new file mode 100644
index 0000000..533d189
--- /dev/null
+++ b/libloragw/tst/test_loragw_cal.c
@@ -0,0 +1,756 @@
+/*
+ / _____) _ | |
+( (____ _____ ____ _| |_ _____ ____| |__
+ \____ \| ___ | (_ _) ___ |/ ___) _ \
+ _____) ) ____| | | || |_| ____( (___| | | |
+(______/|_____)_|_|_| \__)_____)\____)_| |_|
+ (C)2013 Semtech-Cycleo
+
+Description:
+ Minimum test program for the loragw_hal 'library'
+
+License: Revised BSD License, see LICENSE.TXT file include in the project
+Maintainer: Sylvain Miermont
+*/
+
+
+/* -------------------------------------------------------------------------- */
+/* --- DEPENDANCIES --------------------------------------------------------- */
+
+/* fix an issue between POSIX and C99 */
+#if __STDC_VERSION__ >= 199901L
+ #define _XOPEN_SOURCE 600
+#else
+ #define _XOPEN_SOURCE 500
+#endif
+
+#include <stdint.h> /* C99 types */
+#include <stdbool.h> /* bool type */
+#include <stdio.h> /* printf */
+#include <string.h> /* memset */
+#include <signal.h> /* sigaction */
+#include <math.h> /* cos */
+#include <unistd.h> /* getopt access */
+
+#include "loragw_hal.h"
+#include "loragw_reg.h"
+#include "loragw_aux.h"
+#include "loragw_radio.h"
+
+/* -------------------------------------------------------------------------- */
+/* --- PRIVATE MACROS ------------------------------------------------------- */
+
+#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
+
+/* -------------------------------------------------------------------------- */
+/* --- PRIVATE CONSTANTS ---------------------------------------------------- */
+
+#define DEFAULT_TX_NOTCH_FREQ 129E3
+#define DEFAULT_RSSI_OFFSET 0.0
+#define NB_CAL_MAX 100
+#define MCU_AGC 1
+#define MCU_AGC_FW_BYTE 8192 /* size of the firmware IN BYTES (= twice the number of 14b words) */
+#define FW_VERSION_ADDR 0x20
+#define FW_VERSION_CAL 2
+#define RAM_SIZE 4096
+#define FREQ_SIG_NORM 0.078125
+
+/* -------------------------------------------------------------------------- */
+/* --- PRIVATE VARIABLES ---------------------------------------------------- */
+
+#include "../src/cal_fw.var" /* external definition of the variable */
+
+/* -------------------------------------------------------------------------- */
+/* --- PRIVATE TYPES --------------------------------------------------------- */
+
+struct cal_res_s {
+ int8_t amp_a;
+ int8_t phi_a;
+ int8_t amp_b;
+ int8_t phi_b;
+ int8_t offset_i_a [8];
+ int8_t offset_q_a [8];
+ int8_t offset_i_b [8];
+ int8_t offset_q_b [8];
+ uint8_t img_rej_a;
+ uint8_t img_rej_b;
+ uint8_t offset_rej_a [8];
+ uint8_t offset_rej_b [8];
+ uint8_t debug [8];
+};
+
+/* -------------------------------------------------------------------------- */
+/* --- PRIVATE FUNCTIONS DECLARATION ---------------------------------------- */
+
+int load_firmware(uint8_t target, uint8_t *firmware, uint16_t size); /* defined in loragw_hal.c */
+
+uint8_t sx125x_cal(uint8_t cal_cmd, struct cal_res_s *cal_res);
+
+int read_capture(int16_t *i, int16_t *q, int nb_samp);
+
+uint8_t get_img_rej(int16_t *sig_i, int16_t *sig_q, int nb_samp, double f_sig_norm);
+
+void usage (void);
+
+/* -------------------------------------------------------------------------- */
+/* --- 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( " -a <float> Radio A frequency in MHz\n");
+ printf( " -b <float> Radio B frequency in MHz\n");
+ printf( " -r <int> Radio type (SX1255:1255, SX1257:1257)\n");
+ printf( " -n <uint> Number of calibration iterations\n");
+ printf( " -k <int> Concentrator clock source (0:radio_A, 1:radio_B(default))\n");
+ printf( " -t <int> Radio to run TX calibration on (0:None(default), 1:radio_A, 2:radio_B, 3:both)\n");
+}
+
+/* -------------------------------------------------------------------------- */
+/* --- MAIN FUNCTION -------------------------------------------------------- */
+
+int main(int argc, char **argv)
+{
+ int i, j, x;
+ int32_t read_val;
+ struct lgw_conf_board_s boardconf;
+ struct lgw_conf_rxrf_s rfconf;
+ uint8_t fw_version;
+ uint8_t cal_cmd;
+ uint8_t cal_status;
+ struct cal_res_s cal_res [NB_CAL_MAX];
+ struct cal_res_s cal_res_max;
+ struct cal_res_s cal_res_min;
+ int16_t sig_i [RAM_SIZE];
+ int16_t sig_q [RAM_SIZE];
+ uint8_t img_rej_a [NB_CAL_MAX];
+ uint8_t img_rej_b [NB_CAL_MAX];
+ uint8_t img_rej_a_max;
+ uint8_t img_rej_a_min;
+ uint8_t img_rej_b_max;
+ uint8_t img_rej_b_min;
+ //FILE *file;
+
+ /* command line options */
+ int xi = 0;
+ double xd = 0.0;
+ uint32_t fa = 0, fb = 0;
+ enum lgw_radio_type_e radio_type = LGW_RADIO_TYPE_NONE;
+ uint8_t clocksource = 1; /* Radio B is source by default */
+ uint8_t tx_enable = 0;
+ int nb_cal = 5;
+
+ /* parse command line options */
+ while ((i = getopt (argc, argv, "ha:b:r:n:k:t:")) != -1) {
+ switch (i) {
+ case 'h':
+ usage();
+ return -1;
+ break;
+ case 'a': /* <float> Radio A frequency in MHz */
+ sscanf(optarg, "%lf", &xd);
+ fa = (uint32_t)((xd*1e6) + 0.5); /* .5 Hz offset to get rounding instead of truncating */
+ break;
+ case 'b': /* <float> Radio B frequency in MHz */
+ sscanf(optarg, "%lf", &xd);
+ fb = (uint32_t)((xd*1e6) + 0.5); /* .5 Hz offset to get rounding instead of truncating */
+ break;
+ case 'r': /* <int> Radio type (1255, 1257) */
+ sscanf(optarg, "%i", &xi);
+ switch (xi) {
+ case 1255:
+ radio_type = LGW_RADIO_TYPE_SX1255;
+ break;
+ case 1257:
+ radio_type = LGW_RADIO_TYPE_SX1257;
+ break;
+ default:
+ printf("ERROR: invalid radio type\n");
+ usage();
+ return -1;
+ }
+ break;
+ case 'n': /* <uint> Number of calibration iterations */
+ i = sscanf(optarg, "%i", &xi);
+ if ((i != 1) || (xi > NB_CAL_MAX)) {
+ printf("ERROR: invalid number of calibration iterations (MAX %d)\n",NB_CAL_MAX);
+ usage();
+ return -1;
+ } else {
+ nb_cal = xi;
+ }
+ break;
+ case 'k': /* <int> Concentrator clock source (Radio A or Radio B) */
+ sscanf(optarg, "%i", &xi);
+ clocksource = (uint8_t)xi;
+ break;
+ case 't': /* <int> Radio to run TX calibration on */
+ sscanf(optarg, "%i", &xi);
+ tx_enable = (uint8_t)xi;
+ break;
+ default:
+ printf("ERROR: argument parsing\n");
+ usage();
+ return -1;
+ }
+ }
+
+ /* check input parameters */
+ if ((fa == 0) || (fb == 0)) {
+ printf("ERROR: missing frequency input parameter:\n");
+ printf(" Radio A RX: %u\n", fa);
+ printf(" Radio B RX: %u\n", fb);
+ usage();
+ return -1;
+ }
+
+ if (radio_type == LGW_RADIO_TYPE_NONE) {
+ printf("ERROR: missing radio type parameter:\n");
+ usage();
+ return -1;
+ }
+
+ /* starting the concentrator */
+ /* board config */
+ memset(&boardconf, 0, sizeof(boardconf));
+
+ boardconf.lorawan_public = true;
+ boardconf.clksrc = clocksource;
+ lgw_board_setconf(boardconf);
+
+ /* RF config */
+ memset(&rfconf, 0, sizeof(rfconf));
+
+ rfconf.enable = true;
+ rfconf.freq_hz = fa;
+ rfconf.rssi_offset = DEFAULT_RSSI_OFFSET;
+ rfconf.type = radio_type;
+ rfconf.tx_enable = false; /* ignored */
+ lgw_rxrf_setconf(0, rfconf);
+
+ rfconf.freq_hz = fb;
+ rfconf.tx_enable = false; /* ignored */
+ lgw_rxrf_setconf(1, rfconf);
+
+ /* Calibration command */
+ cal_cmd = 0;
+ //cal_cmd |= 0x01; /* Bit 0: Calibrate Rx IQ mismatch compensation on radio A */
+ //cal_cmd |= 0x02; /* Bit 1: Calibrate Rx IQ mismatch compensation on radio B */
+ //cal_cmd |= 0x04; /* Bit 2: Calibrate Tx DC offset on radio A */
+ //cal_cmd |= 0x08; /* Bit 3: Calibrate Tx DC offset on radio B */
+ cal_cmd |= 0x10; /* Bit 4: 0: calibrate with DAC gain=2, 1: with DAC gain=3 (use 3) */
+
+ switch (radio_type) {
+ case LGW_RADIO_TYPE_SX1255:
+ cal_cmd |= 0x20; /* Bit 5: 0: SX1257, 1: SX1255 */
+ break;
+ case LGW_RADIO_TYPE_SX1257:
+ cal_cmd |= 0x00; /* Bit 5: 0: SX1257, 1: SX1255 */
+ break;
+ default:
+ break;
+ }
+
+ cal_cmd |= 0x00; /* Bit 6-7: Board type 0: ref, 1: FPGA, 3: board X */
+
+ /* Recap parameters*/
+ printf("Library version information: %s\n", lgw_version_info());
+ printf("Radio type: %d\n",radio_type);
+ printf("Radio A frequency: %f MHz\n",fa/1e6);
+ printf("Radio B frequency: %f MHz\n",fb/1e6);
+ printf("Number of calibration iterations: %d\n",nb_cal);
+ printf("Calibration command: brd: %d, chip: %d, dac: %d\n\n", cal_cmd >> 6, 1257-2*((cal_cmd & 0x20) >> 5), 2+((cal_cmd & 0x10) >> 4));
+
+ x = lgw_connect(false, DEFAULT_TX_NOTCH_FREQ);
+ if (x == -1) {
+ printf("ERROR: FAIL TO CONNECT BOARD\n");
+ return -1;
+ }
+
+ /* reset the registers (also shuts the radios down) */
+ lgw_soft_reset();
+
+ /* ungate clocks (gated by default) */
+ lgw_reg_w(LGW_GLOBAL_EN, 1);
+
+ /* switch on and reset the radios (also starts the 32 MHz XTAL) */
+ lgw_reg_w(LGW_RADIO_A_EN,1);
+ lgw_reg_w(LGW_RADIO_B_EN,1);
+ wait_ms(500); /* TODO: optimize */
+ lgw_reg_w(LGW_RADIO_RST,1);
+ wait_ms(5);
+ lgw_reg_w(LGW_RADIO_RST,0);
+
+ /* setup the radios */
+ lgw_setup_sx125x(0, clocksource, true, radio_type, fa);
+ lgw_setup_sx125x(1, clocksource, false, radio_type, fb);
+
+ /* Set GPIO 4 high for calibration */
+ lgw_reg_w(LGW_GPIO_MODE,31); /* Set all GPIOs as output */
+ lgw_reg_w(LGW_GPIO_SELECT_OUTPUT,2); /* AGC MCU drives GPIOs */
+
+ /* Load the calibration firmware */
+ load_firmware(MCU_AGC, cal_firmware, MCU_AGC_FW_BYTE);
+ lgw_reg_w(LGW_MCU_RST_1,0);
+ lgw_reg_w(LGW_DBG_AGC_MCU_RAM_ADDR, FW_VERSION_ADDR);
+ lgw_reg_r(LGW_DBG_AGC_MCU_RAM_DATA, &read_val);
+ fw_version = (uint8_t)read_val;
+ if (fw_version != FW_VERSION_CAL) {
+ printf("ERROR: Version of calibration firmware not expected, actual:%d expected:%d\n", fw_version, FW_VERSION_CAL);
+ return -1;
+ }
+
+ /* Run Rx A IQ mismatch calibration only */
+ for (i=0; i<nb_cal; i++) {
+ cal_status = sx125x_cal(cal_cmd | 0x01, &cal_res[i]);
+ x = read_capture(sig_i, sig_q, RAM_SIZE);
+ /*
+ file = fopen("toto.txt","w");
+ for (j=0; j<RAM_SIZE; j++) {
+ fprintf(file, "%d %d\n", sig_i[j], sig_q[j]);
+ }
+ fclose(file);
+ */
+ img_rej_a[i] = get_img_rej(sig_i, sig_q, RAM_SIZE, FREQ_SIG_NORM);
+
+ printf("Rx A IQ mismatch: Amp: %3d Phi: %3d Rej: %2d dB Status: %3d | Debug: Rej: %2d dB Lna: %1d BB: %2d Dec: %2d\n", cal_res[i].amp_a, cal_res[i].phi_a, cal_res[i].img_rej_a, cal_status, img_rej_a[i], cal_res[i].debug[0], cal_res[i].debug[1], cal_res[i].debug[2]);
+ }
+
+ /* Run Rx B IQ mismatch calibation only */
+ printf("\n");
+ for (i=0; i<nb_cal; i++) {
+ cal_status = sx125x_cal(cal_cmd | 0x02, &cal_res[i]);
+ x = read_capture(sig_i, sig_q, RAM_SIZE);
+ img_rej_b[i] = get_img_rej(sig_i, sig_q, RAM_SIZE, FREQ_SIG_NORM);
+
+ printf("Rx B IQ mismatch: Amp: %3d Phi: %3d Rej: %2d dB Status: %3d | Debug: Rej: %2d dB Lna: %1d BB: %2d Dec: %2d\n", cal_res[i].amp_b, cal_res[i].phi_b, cal_res[i].img_rej_b, cal_status, img_rej_b[i], cal_res[i].debug[0], cal_res[i].debug[1], cal_res[i].debug[2]);
+ }
+
+ /* Run Tx A DC offset calibation only */
+ printf("\n");
+ if ((tx_enable == 1) || (tx_enable == 3)) {
+ for (i=0; i<nb_cal; i++) {
+ cal_status = sx125x_cal(cal_cmd | 0x04, &cal_res[i]);
+
+ printf("Tx A DC offset I :");
+ for (j=0; j<8; j++) {
+ printf(" %3d", cal_res[i].offset_i_a[j]);
+ }
+ printf("\n");
+ printf("Tx A DC offset Q :");
+ for (j=0; j<8; j++) {
+ printf(" %3d", cal_res[i].offset_q_a[j]);
+ }
+ printf("\n");
+ printf("Tx A DC rejection:");
+ for (j=0; j<8; j++) {
+ printf(" %3d", cal_res[i].offset_rej_a[j]);
+ }
+ printf("\n");
+ printf("Tx A DC debug BB :");
+ for (j=0; j<8; j++) {
+ printf(" %3d", (cal_res[i].debug[j] & 0xF0) >> 4);
+ }
+ printf("\n");
+ printf("Tx A DC debug Dec:");
+ for (j=0; j<8; j++) {
+ printf(" %3d", cal_res[i].debug[j] & 0x0F);
+ }
+ printf("\n");
+ printf("Tx A DC Status : %3d\n", cal_status);
+ }
+ } else {
+ printf("Tx A calibration bypassed\n");
+ }
+
+ /* Run Tx B DC offset calibation only */
+ printf("\n");
+ if ((tx_enable == 2) || (tx_enable == 3)) {
+ for (i=0; i<nb_cal; i++) {
+ cal_status = sx125x_cal(cal_cmd | 0x08, &cal_res[i]);
+
+ printf("Tx B DC offset I :");
+ for (j=0; j<8; j++) {
+ printf(" %3d", cal_res[i].offset_i_b[j]);
+ }
+ printf("\n");
+ printf("Tx B DC offset Q :");
+ for (j=0; j<8; j++) {
+ printf(" %3d", cal_res[i].offset_q_b[j]);
+ }
+ printf("\n");
+ printf("Tx B DC rejection:");
+ for (j=0; j<8; j++) {
+ printf(" %3d", cal_res[i].offset_rej_b[j]);
+ }
+ printf("\n");
+ printf("Tx B DC debug BB :");
+ for (j=0; j<8; j++) {
+ printf(" %3d", (cal_res[i].debug[j] & 0xF0) >> 4);
+ }
+ printf("\n");
+ printf("Tx B DC debug Dec:");
+ for (j=0; j<8; j++) {
+ printf(" %3d", cal_res[i].debug[j] & 0x0F);
+ }
+ printf("\n");
+ printf("Tx B DC Status : %3d\n", cal_status);
+ }
+ } else {
+ printf("Tx B calibration bypassed\n");
+ }
+
+ /* Compute statistics */
+ cal_res_max.amp_a = -128;
+ cal_res_max.phi_a = -128;
+ cal_res_max.amp_b = -128;
+ cal_res_max.phi_b = -128;
+ cal_res_max.img_rej_a = 0;
+ cal_res_max.img_rej_b = 0;
+ for (j=0; j<8; j++) {
+ cal_res_max.offset_i_a[j] = -128;
+ cal_res_max.offset_q_a[j] = -128;
+ cal_res_max.offset_i_b[j] = -128;
+ cal_res_max.offset_q_b[j] = -128;
+ cal_res_max.offset_rej_a[j] = 0;
+ cal_res_max.offset_rej_b[j] = 0;
+ }
+
+ cal_res_min.amp_a = 127;
+ cal_res_min.phi_a = 127;
+ cal_res_min.amp_b = 127;
+ cal_res_min.phi_b = 127;
+ cal_res_min.img_rej_a = 255;
+ cal_res_min.img_rej_b = 255;
+ for (j=0; j<8; j++) {
+ cal_res_min.offset_i_a[j] = 127;
+ cal_res_min.offset_q_a[j] = 127;
+ cal_res_min.offset_i_b[j] = 127;
+ cal_res_min.offset_q_b[j] = 127;
+ cal_res_min.offset_rej_a[j] = 255;
+ cal_res_min.offset_rej_b[j] = 255;
+ }
+
+ img_rej_a_max = 0;
+ img_rej_a_min = 255;
+ img_rej_b_max = 0;
+ img_rej_b_min = 255;
+
+ for (i=0; i<nb_cal; i++) {
+ if (cal_res[i].amp_a > cal_res_max.amp_a) {
+ cal_res_max.amp_a = cal_res[i].amp_a;
+ }
+ if (cal_res[i].phi_a > cal_res_max.phi_a) {
+ cal_res_max.phi_a = cal_res[i].phi_a;
+ }
+ if (cal_res[i].amp_b > cal_res_max.amp_b) {
+ cal_res_max.amp_b = cal_res[i].amp_b;
+ }
+ if (cal_res[i].phi_b > cal_res_max.phi_b) {
+ cal_res_max.phi_b = cal_res[i].phi_b;
+ }
+ if (cal_res[i].phi_b > cal_res_max.phi_b) {
+ cal_res_max.phi_b = cal_res[i].phi_b;
+ }
+ if (cal_res[i].img_rej_a > cal_res_max.img_rej_a) {
+ cal_res_max.img_rej_a = cal_res[i].img_rej_a;
+ }
+ if (cal_res[i].img_rej_b > cal_res_max.img_rej_b) {
+ cal_res_max.img_rej_b = cal_res[i].img_rej_b;
+ }
+ for (j=0; j<8; j++) {
+ if (cal_res[i].offset_i_a[j] > cal_res_max.offset_i_a[j]) {
+ cal_res_max.offset_i_a[j] = cal_res[i].offset_i_a[j];
+ }
+ if (cal_res[i].offset_q_a[j] > cal_res_max.offset_q_a[j]) {
+ cal_res_max.offset_q_a[j] = cal_res[i].offset_q_a[j];
+ }
+ if (cal_res[i].offset_i_b[j] > cal_res_max.offset_i_b[j]) {
+ cal_res_max.offset_i_b[j] = cal_res[i].offset_i_b[j];
+ }
+ if (cal_res[i].offset_q_b[j] > cal_res_max.offset_q_b[j]) {
+ cal_res_max.offset_q_b[j] = cal_res[i].offset_q_b[j];
+ }
+ if (cal_res[i].offset_rej_a[j] > cal_res_max.offset_rej_a[j]) {
+ cal_res_max.offset_rej_a[j] = cal_res[i].offset_rej_a[j];
+ }
+ if (cal_res[i].offset_rej_b[j] > cal_res_max.offset_rej_b[j]) {
+ cal_res_max.offset_rej_b[j] = cal_res[i].offset_rej_b[j];
+ }
+ }
+
+ if (cal_res[i].amp_a < cal_res_min.amp_a) {
+ cal_res_min.amp_a = cal_res[i].amp_a;
+ }
+ if (cal_res[i].phi_a < cal_res_min.phi_a) {
+ cal_res_min.phi_a = cal_res[i].phi_a;
+ }
+ if (cal_res[i].amp_b < cal_res_min.amp_b) {
+ cal_res_min.amp_b = cal_res[i].amp_b;
+ }
+ if (cal_res[i].phi_b < cal_res_min.phi_b) {
+ cal_res_min.phi_b = cal_res[i].phi_b;
+ }
+ if (cal_res[i].phi_b < cal_res_min.phi_b) {
+ cal_res_min.phi_b = cal_res[i].phi_b;
+ }
+ if (cal_res[i].img_rej_a < cal_res_min.img_rej_a) {
+ cal_res_min.img_rej_a = cal_res[i].img_rej_a;
+ }
+ if (cal_res[i].img_rej_b < cal_res_min.img_rej_b) {
+ cal_res_min.img_rej_b = cal_res[i].img_rej_b;
+ }
+ for (j=0; j<8; j++) {
+ if (cal_res[i].offset_i_a[j] < cal_res_min.offset_i_a[j]) {
+ cal_res_min.offset_i_a[j] = cal_res[i].offset_i_a[j];
+ }
+ if (cal_res[i].offset_q_a[j] < cal_res_min.offset_q_a[j]) {
+ cal_res_min.offset_q_a[j] = cal_res[i].offset_q_a[j];
+ }
+ if (cal_res[i].offset_i_b[j] < cal_res_min.offset_i_b[j]) {
+ cal_res_min.offset_i_b[j] = cal_res[i].offset_i_b[j];
+ }
+ if (cal_res[i].offset_q_b[j] < cal_res_min.offset_q_b[j]) {
+ cal_res_min.offset_q_b[j] = cal_res[i].offset_q_b[j];
+ }
+ if (cal_res[i].offset_rej_a[j] < cal_res_min.offset_rej_a[j]) {
+ cal_res_min.offset_rej_a[j] = cal_res[i].offset_rej_a[j];
+ }
+ if (cal_res[i].offset_rej_b[j] < cal_res_min.offset_rej_b[j]) {
+ cal_res_min.offset_rej_b[j] = cal_res[i].offset_rej_b[j];
+ }
+ }
+
+ if (img_rej_a[i] > img_rej_a_max) {
+ img_rej_a_max = img_rej_a[i];
+ }
+ if (img_rej_a[i] < img_rej_a_min) {
+ img_rej_a_min = img_rej_a[i];
+ }
+ if (img_rej_b[i] > img_rej_b_max) {
+ img_rej_b_max = img_rej_b[i];
+ }
+ if (img_rej_b[i] < img_rej_b_min) {
+ img_rej_b_min = img_rej_b[i];
+ }
+ }
+
+ /* Print statistics */
+ printf("\n");
+ printf("Rx A IQ mismatch calibration statistics on %3d iterations (min, max):\n", nb_cal);
+ printf("Amp: %3d %3d Phi: %3d %3d Rej: %2d %2d dB (capt.: %2d %2d dB)\n", cal_res_min.amp_a, cal_res_max.amp_a, cal_res_min.phi_a, cal_res_max.phi_a, cal_res_min.img_rej_a, cal_res_max.img_rej_a, img_rej_a_min, img_rej_a_max);
+
+ printf("\n");
+ printf("Rx B IQ mismatch calibration statistics on %3d iterations (min, max):\n", nb_cal);
+ printf("Amp: %3d %3d Phi: %3d %3d Rej: %2d %2d dB (capt.: %2d %2d dB)\n", cal_res_min.amp_b, cal_res_max.amp_b, cal_res_min.phi_b, cal_res_max.phi_b, cal_res_min.img_rej_b, cal_res_max.img_rej_b, img_rej_b_min, img_rej_b_max);
+
+ if ((tx_enable == 1) || (tx_enable == 3)) {
+ printf("\n");
+ printf("Tx A DC offset calibration statistics on %3d iterations (min, max):\n", nb_cal);
+ for (j=0; j<8; j++) {
+ printf(" Mix gain %2d: I: %3d %3d Q: %3d %3d Rej: %2d %2d dB\n", 8+j, cal_res_min.offset_i_a[j], cal_res_max.offset_i_a[j], cal_res_min.offset_q_a[j], cal_res_max.offset_q_a[j], cal_res_min.offset_rej_a[j], cal_res_max.offset_rej_a[j]);
+ }
+ }
+
+ if ((tx_enable == 2) || (tx_enable == 3)) {
+ printf("\n");
+ printf("Tx B DC offset calibration statistics on %3d iterations (min, max):\n", nb_cal);
+ for (j=0; j<8; j++) {
+ printf(" Mix gain %2d: I: %3d %3d Q: %3d %3d Rej: %2d %2d dB\n", 8+j, cal_res_min.offset_i_b[j], cal_res_max.offset_i_b[j], cal_res_min.offset_q_b[j], cal_res_max.offset_q_b[j], cal_res_min.offset_rej_b[j], cal_res_max.offset_rej_b[j]);
+ }
+ }
+
+ lgw_stop();
+
+ printf("\nEnd of radio calibration test\n");
+
+ return 0;
+}
+
+/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
+
+uint8_t sx125x_cal(uint8_t cal_cmd, struct cal_res_s *cal_res) {
+
+ int i;
+ int32_t read_val;
+ uint8_t cal_status;
+
+ lgw_reg_w(LGW_FORCE_HOST_RADIO_CTRL,0); /* gives to AGC MCU the control of the radios */
+ lgw_reg_w(LGW_RADIO_SELECT,cal_cmd); /* send calibration configuration word */
+ lgw_reg_w(LGW_MCU_RST_1,1);
+ lgw_reg_w(LGW_MCU_RST_1,0);
+ lgw_reg_w(LGW_PAGE_REG,3); /* Calibration will start on this condition as soon as MCU can talk to concentrator registers */
+ lgw_reg_w(LGW_EMERGENCY_FORCE_HOST_CTRL,0); /* Give control of concentrator registers to MCU */
+
+ wait_ms(2000); /* Wait for end of calibration */
+
+ lgw_reg_w(LGW_EMERGENCY_FORCE_HOST_CTRL,1); /* Take back control */
+
+ /* Get calibration status */
+ lgw_reg_r(LGW_MCU_AGC_STATUS, &read_val);
+ cal_status = (uint8_t)read_val;
+
+ /* Check calibration flags
+ bit 0: could access SX1301 registers
+ bit 1: could access radio A registers
+ bit 2: could access radio B registers
+ bit 3: radio A RX image rejection successful
+ bit 4: radio B RX image rejection successful
+ bit 5: radio A TX imbalance correction successful
+ bit 6: radio B TX imbalance correction successful
+ bit 7: calibration finished */
+
+ if ((cal_status & 0x01) == 0) {
+ printf("WARNING: calibration could not access SX1301 registers\n");
+ }
+ if ((cal_status & 0x02) == 0) {
+ printf("WARNING: calibration could not access radio A\n");
+ }
+ if ((cal_status & 0x04) == 0) {
+ printf("WARNING: calibration could not access radio B\n");
+ }
+ if ((cal_cmd & 0x01) && ((cal_status & 0x08) == 0)) {
+ printf("WARNING: problem in calibration of radio A for image rejection\n");
+ }
+ if ((cal_cmd & 0x02) && ((cal_status & 0x10) == 0)) {
+ printf("WARNING: problem in calibration of radio B for image rejection\n");
+ }
+ if ((cal_cmd & 0x04) && ((cal_status & 0x20) == 0)) {
+ printf("WARNING: problem in calibration of radio A for TX imbalance\n");
+ }
+ if ((cal_cmd & 0x08) && ((cal_status & 0x40) == 0)) {
+ printf("WARNING: problem in calibration of radio B for TX imbalance\n");
+ }
+ if ((cal_status & 0x80) == 0) {
+ printf("WARNING: Calibration not finished\n");
+ }
+
+ /* Get calibration results */
+ if (cal_cmd & 0x01) {
+ lgw_reg_r(LGW_IQ_MISMATCH_A_AMP_COEFF, &read_val);
+ (*cal_res).amp_a = (int8_t)((read_val > 31) ? read_val - 64 : read_val);
+ lgw_reg_r(LGW_IQ_MISMATCH_A_PHI_COEFF, &read_val);
+ (*cal_res).phi_a = (int8_t)((read_val > 31) ? read_val - 64 : read_val);
+ lgw_reg_w(LGW_DBG_AGC_MCU_RAM_ADDR, 0xD0);
+ lgw_reg_r(LGW_DBG_AGC_MCU_RAM_DATA, &read_val);
+ (*cal_res).img_rej_a = (uint8_t)read_val;
+ lgw_reg_w(LGW_DBG_AGC_MCU_RAM_ADDR, 0xD2);
+ lgw_reg_r(LGW_DBG_AGC_MCU_RAM_DATA, &read_val);
+ (*cal_res).debug[0] = (uint8_t)read_val;
+ lgw_reg_w(LGW_DBG_AGC_MCU_RAM_ADDR, 0xD3);
+ lgw_reg_r(LGW_DBG_AGC_MCU_RAM_DATA, &read_val);
+ (*cal_res).debug[1] = (uint8_t)read_val;
+ lgw_reg_w(LGW_DBG_AGC_MCU_RAM_ADDR, 0xD4);
+ lgw_reg_r(LGW_DBG_AGC_MCU_RAM_DATA, &read_val);
+ (*cal_res).debug[2] = (uint8_t)read_val;
+ }
+ if (cal_cmd & 0x02) {
+ lgw_reg_r(LGW_IQ_MISMATCH_B_AMP_COEFF, &read_val);
+ (*cal_res).amp_b = (int8_t)((read_val > 31) ? read_val - 64 : read_val);
+ lgw_reg_r(LGW_IQ_MISMATCH_B_PHI_COEFF, &read_val);
+ (*cal_res).phi_b = (int8_t)((read_val > 31) ? read_val - 64 : read_val);
+ lgw_reg_w(LGW_DBG_AGC_MCU_RAM_ADDR, 0xD1);
+ lgw_reg_r(LGW_DBG_AGC_MCU_RAM_DATA, &read_val);
+ (*cal_res).img_rej_b = (uint8_t)read_val;
+ lgw_reg_w(LGW_DBG_AGC_MCU_RAM_ADDR, 0xD2);
+ lgw_reg_r(LGW_DBG_AGC_MCU_RAM_DATA, &read_val);
+ (*cal_res).debug[0] = (uint8_t)read_val;
+ lgw_reg_w(LGW_DBG_AGC_MCU_RAM_ADDR, 0xD3);
+ lgw_reg_r(LGW_DBG_AGC_MCU_RAM_DATA, &read_val);
+ (*cal_res).debug[1] = (uint8_t)read_val;
+ lgw_reg_w(LGW_DBG_AGC_MCU_RAM_ADDR, 0xD4);
+ lgw_reg_r(LGW_DBG_AGC_MCU_RAM_DATA, &read_val);
+ (*cal_res).debug[2] = (uint8_t)read_val;
+ }
+ if (cal_cmd & 0x04) {
+ for (i=0; i<=7; ++i) {
+ lgw_reg_w(LGW_DBG_AGC_MCU_RAM_ADDR, 0xA0+i);
+ lgw_reg_r(LGW_DBG_AGC_MCU_RAM_DATA, &read_val);
+ (*cal_res).offset_i_a[i] = (int8_t)read_val;
+ lgw_reg_w(LGW_DBG_AGC_MCU_RAM_ADDR, 0xA8+i);
+ lgw_reg_r(LGW_DBG_AGC_MCU_RAM_DATA, &read_val);
+ (*cal_res).offset_q_a[i] = (int8_t)read_val;
+ lgw_reg_w(LGW_DBG_AGC_MCU_RAM_ADDR, 0xC0+i);
+ lgw_reg_r(LGW_DBG_AGC_MCU_RAM_DATA, &read_val);
+ (*cal_res).offset_rej_a[i] = (uint8_t)read_val;
+ lgw_reg_w(LGW_DBG_AGC_MCU_RAM_ADDR, 0xD2+i);
+ lgw_reg_r(LGW_DBG_AGC_MCU_RAM_DATA, &read_val);
+ (*cal_res).debug[i] = (uint8_t)read_val;
+ }
+ }
+ if (cal_cmd & 0x08) {
+ for (i=0; i<=7; ++i) {
+ lgw_reg_w(LGW_DBG_AGC_MCU_RAM_ADDR, 0xB0+i);
+ lgw_reg_r(LGW_DBG_AGC_MCU_RAM_DATA, &read_val);
+ (*cal_res).offset_i_b[i] = (int8_t)read_val;
+ lgw_reg_w(LGW_DBG_AGC_MCU_RAM_ADDR, 0xB8+i);
+ lgw_reg_r(LGW_DBG_AGC_MCU_RAM_DATA, &read_val);
+ (*cal_res).offset_q_b[i] = (int8_t)read_val;
+ lgw_reg_w(LGW_DBG_AGC_MCU_RAM_ADDR, 0xC8+i);
+ lgw_reg_r(LGW_DBG_AGC_MCU_RAM_DATA, &read_val);
+ (*cal_res).offset_rej_b[i] = (uint8_t)read_val;
+ lgw_reg_w(LGW_DBG_AGC_MCU_RAM_ADDR, 0xD2+i);
+ lgw_reg_r(LGW_DBG_AGC_MCU_RAM_DATA, &read_val);
+ (*cal_res).debug[i] = (uint8_t)read_val;
+ }
+ }
+
+ return cal_status;
+}
+
+/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
+
+int read_capture(int16_t *sig_i, int16_t *sig_q, int nb_samp) {
+
+ uint8_t read_burst[4];
+ uint16_t data_i_c2;
+ uint16_t data_q_c2;
+ int i;
+
+ lgw_reg_w(LGW_CAPTURE_RAM_ADDR, 0);
+ for (i=0 ; i<nb_samp ; i++) {
+ lgw_reg_rb(LGW_CAPTURE_RAM_DATA, read_burst, 4);
+ data_i_c2 = ((uint16_t)read_burst[3] << 4) + ((uint16_t)read_burst[2] >> 4);
+ data_q_c2 = ((uint16_t)read_burst[1] << 4) + ((uint16_t)read_burst[0] >> 4);
+ sig_i[i] = (int16_t)((data_i_c2 > 2047) ? data_i_c2 - 4096 : data_i_c2);
+ sig_q[i] = (int16_t)((data_q_c2 > 2047) ? data_q_c2 - 4096 : data_q_c2);
+ }
+
+ return 0;
+}
+
+/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
+
+uint8_t get_img_rej(int16_t *sig_i, int16_t *sig_q, int nb_samp, double f_sig_norm) {
+
+ int i;
+ double phase;
+ double corr_sig_i, corr_sig_q, corr_sig_abs;
+ double corr_img_i, corr_img_q, corr_img_abs;
+ double img_rej;
+
+ corr_sig_i = 0;
+ corr_sig_q = 0;
+ corr_img_i = 0;
+ corr_img_q = 0;
+
+ for (i=0 ; i<nb_samp ; i++) {
+ phase = 6.28318530717959*i*f_sig_norm;
+ corr_sig_i += (double)sig_i[i]*cos( phase) - (double)sig_q[i]*sin( phase);
+ corr_sig_q += (double)sig_q[i]*cos( phase) + (double)sig_i[i]*sin( phase);
+ corr_img_i += (double)sig_i[i]*cos(-phase) - (double)sig_q[i]*sin(-phase);
+ corr_img_q += (double)sig_q[i]*cos(-phase) + (double)sig_i[i]*sin(-phase);
+ }
+
+ corr_sig_abs = sqrt( corr_sig_i*corr_sig_i + corr_sig_q*corr_sig_q );
+ corr_img_abs = sqrt( corr_img_i*corr_img_i + corr_img_q*corr_img_q );
+
+ img_rej = 20*log10(corr_sig_abs/corr_img_abs);
+
+ return (uint8_t)img_rej;
+}
+
+
+/* --- EOF ------------------------------------------------------------------ */
diff --git a/libloragw/tst/test_loragw_gps.c b/libloragw/tst/test_loragw_gps.c
new file mode 100644
index 0000000..a4164a3
--- /dev/null
+++ b/libloragw/tst/test_loragw_gps.c
@@ -0,0 +1,288 @@
+/*
+ / _____) _ | |
+( (____ _____ ____ _| |_ _____ ____| |__
+ \____ \| ___ | (_ _) ___ |/ ___) _ \
+ _____) ) ____| | | || |_| ____( (___| | | |
+(______/|_____)_|_|_| \__)_____)\____)_| |_|
+ (C)2013 Semtech-Cycleo
+
+Description:
+ Minimum test program for the loragw_gps 'library'
+
+License: Revised BSD License, see LICENSE.TXT file include in the project
+Maintainer: Michael Coracin
+*/
+
+
+/* -------------------------------------------------------------------------- */
+/* --- DEPENDANCIES --------------------------------------------------------- */
+
+/* fix an issue between POSIX and C99 */
+#if __STDC_VERSION__ >= 199901L
+ #define _XOPEN_SOURCE 600
+#else
+ #define _XOPEN_SOURCE 500
+#endif
+
+#include <stdint.h> /* C99 types */
+#include <stdbool.h> /* bool type */
+#include <stdio.h> /* printf */
+#include <string.h> /* memset */
+#include <signal.h> /* sigaction */
+#include <stdlib.h> /* exit */
+#include <unistd.h> /* read */
+
+#include "loragw_hal.h"
+#include "loragw_gps.h"
+#include "loragw_aux.h"
+
+/* -------------------------------------------------------------------------- */
+/* --- PRIVATE 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 */
+
+struct tref ppm_ref;
+
+/* -------------------------------------------------------------------------- */
+/* --- PRIVATE FUNCTIONS DECLARATION ---------------------------------------- */
+
+static void sig_handler(int sigio);
+static void gps_process_sync(void);
+static void gps_process_coords(void);
+
+/* -------------------------------------------------------------------------- */
+/* --- PRIVATE FUNCTIONS DEFINITION ----------------------------------------- */
+
+static void sig_handler(int sigio) {
+ if (sigio == SIGQUIT) {
+ quit_sig = 1;;
+ } else if ((sigio == SIGINT) || (sigio == SIGTERM)) {
+ exit_sig = 1;
+ }
+}
+
+static void gps_process_sync(void) {
+ /* variables for PPM pulse GPS synchronization */
+ uint32_t ppm_tstamp;
+ struct timespec ppm_gps;
+ struct timespec ppm_utc;
+
+ /* variables for timestamp <-> GPS time conversions */
+ uint32_t x, z;
+ struct timespec y;
+
+ /* get GPS time for synchronization */
+ int i = lgw_gps_get(&ppm_utc, &ppm_gps, NULL, NULL);
+ if (i != LGW_GPS_SUCCESS) {
+ printf(" No valid reference GPS time available, synchronization impossible.\n");
+ return;
+ }
+
+ /* get timestamp for synchronization */
+ i = lgw_get_trigcnt(&ppm_tstamp);
+ if (i != LGW_HAL_SUCCESS) {
+ printf(" Failed to read timestamp, synchronization impossible.\n");
+ return;
+ }
+
+ /* try to update synchronize time reference with the new GPS & timestamp */
+ i = lgw_gps_sync(&ppm_ref, ppm_tstamp, ppm_utc, ppm_gps);
+ if (i != LGW_GPS_SUCCESS) {
+ printf(" Synchronization error.\n");
+ return;
+ }
+
+ /* display result */
+ printf(" * Synchronization successful *\n");
+ printf(" UTC reference time: %lld.%09ld\n", (long long)ppm_ref.utc.tv_sec, ppm_ref.utc.tv_nsec);
+ printf(" GPS reference time: %lld.%09ld\n", (long long)ppm_ref.gps.tv_sec, ppm_ref.gps.tv_nsec);
+ printf(" Internal counter reference value: %u\n", ppm_ref.count_us);
+ printf(" Clock error: %.9f\n", ppm_ref.xtal_err);
+
+ x = ppm_tstamp + 500000;
+ printf(" * Test of timestamp counter <-> GPS value conversion *\n");
+ printf(" Test value: %u\n", x);
+ lgw_cnt2gps(ppm_ref, x, &y);
+ printf(" Conversion to GPS: %lld.%09ld\n", (long long)y.tv_sec, y.tv_nsec);
+ lgw_gps2cnt(ppm_ref, y, &z);
+ printf(" Converted back: %u ==> %dµs\n", z, (int32_t)(z-x));
+ printf(" * Test of timestamp counter <-> UTC value conversion *\n");
+ printf(" Test value: %u\n", x);
+ lgw_cnt2utc(ppm_ref, x, &y);
+ printf(" Conversion to UTC: %lld.%09ld\n", (long long)y.tv_sec, y.tv_nsec);
+ lgw_utc2cnt(ppm_ref, y, &z);
+ printf(" Converted back: %u ==> %dµs\n", z, (int32_t)(z-x));
+}
+
+static void gps_process_coords(void) {
+ /* position variable */
+ struct coord_s coord;
+ struct coord_s gpserr;
+ int i = lgw_gps_get(NULL, NULL, &coord, &gpserr);
+
+ /* update gateway coordinates */
+ if (i == LGW_GPS_SUCCESS) {
+ printf("# GPS coordinates: latitude %.5f, longitude %.5f, altitude %i m\n", coord.lat, coord.lon, coord.alt);
+ printf("# GPS err: latitude %.5f, longitude %.5f, altitude %i m\n", gpserr.lat, gpserr.lon, gpserr.alt);
+ }
+}
+
+/* -------------------------------------------------------------------------- */
+/* --- MAIN FUNCTION -------------------------------------------------------- */
+
+int main()
+{
+ struct sigaction sigact; /* SIGQUIT&SIGINT&SIGTERM signal handling */
+
+ int i;
+
+ /* concentrator variables */
+ struct lgw_conf_board_s boardconf;
+ struct lgw_conf_rxrf_s rfconf;
+
+ /* serial variables */
+ char serial_buff[128]; /* buffer to receive GPS data */
+ size_t wr_idx = 0; /* pointer to end of chars in buffer */
+ int gps_tty_dev; /* file descriptor to the serial port of the GNSS module */
+
+ /* NMEA/UBX variables */
+ enum gps_msg latest_msg; /* keep track of latest NMEA/UBX message parsed */
+
+ /* configure signal handling */
+ sigemptyset(&sigact.sa_mask);
+ sigact.sa_flags = 0;
+ sigact.sa_handler = sig_handler;
+ sigaction(SIGQUIT, &sigact, NULL);
+ sigaction(SIGINT, &sigact, NULL);
+ sigaction(SIGTERM, &sigact, NULL);
+
+ /* Intro message and library information */
+ printf("Beginning of test for loragw_gps.c\n");
+ printf("*** Library version information ***\n%s\n***\n", lgw_version_info());
+
+ /* Open and configure GPS */
+ i = lgw_gps_enable("/dev/ttyAMA0", "ubx7", 0, &gps_tty_dev);
+ if (i != LGW_GPS_SUCCESS) {
+ printf("ERROR: IMPOSSIBLE TO ENABLE GPS\n");
+ exit(EXIT_FAILURE);
+ }
+
+ /* start concentrator (default conf for IoT SK) */
+ /* board config */
+ memset(&boardconf, 0, sizeof(boardconf));
+ boardconf.lorawan_public = true;
+ boardconf.clksrc = 1;
+ lgw_board_setconf(boardconf);
+
+ /* RF config */
+ memset(&rfconf, 0, sizeof(rfconf));
+ rfconf.enable = true;
+ rfconf.freq_hz = 868000000;
+ rfconf.rssi_offset = 0.0;
+ rfconf.type = LGW_RADIO_TYPE_SX1257;
+ rfconf.tx_enable = true;
+ lgw_rxrf_setconf(0, rfconf);
+
+ lgw_start();
+
+ /* initialize some variables before loop */
+ memset(serial_buff, 0, sizeof serial_buff);
+ memset(&ppm_ref, 0, sizeof ppm_ref);
+
+ /* loop until user action */
+ while ((quit_sig != 1) && (exit_sig != 1)) {
+ size_t rd_idx = 0;
+ size_t frame_end_idx = 0;
+
+ /* blocking non-canonical read on serial port */
+ ssize_t nb_char = read(gps_tty_dev, serial_buff + wr_idx, LGW_GPS_MIN_MSG_SIZE);
+ if (nb_char <= 0) {
+ printf("WARNING: [gps] read() returned value %d\n", nb_char);
+ continue;
+ }
+ wr_idx += (size_t)nb_char;
+
+ /*******************************************
+ * Scan buffer for UBX/NMEA sync chars and *
+ * attempt to decode frame if one is found *
+ *******************************************/
+ while (rd_idx < wr_idx) {
+ size_t frame_size = 0;
+
+ /* Scan buffer for UBX sync char */
+ if (serial_buff[rd_idx] == LGW_GPS_UBX_SYNC_CHAR) {
+
+ /***********************
+ * Found UBX sync char *
+ ***********************/
+ latest_msg = lgw_parse_ubx(&serial_buff[rd_idx], (wr_idx - rd_idx), &frame_size);
+
+ if (frame_size > 0) {
+ if (latest_msg == INCOMPLETE) {
+ /* UBX header found but frame appears to be missing bytes */
+ frame_size = 0;
+ } else if (latest_msg == INVALID) {
+ /* message header received but message appears to be corrupted */
+ printf("WARNING: [gps] could not get a valid message from GPS (no time)\n");
+ frame_size = 0;
+ } else if (latest_msg == UBX_NAV_TIMEGPS) {
+ printf("\n~~ UBX NAV-TIMEGPS sentence, triggering synchronization attempt ~~\n");
+ gps_process_sync();
+ }
+ }
+ } else if(serial_buff[rd_idx] == LGW_GPS_NMEA_SYNC_CHAR) {
+ /************************
+ * Found NMEA sync char *
+ ************************/
+ /* scan for NMEA end marker (LF = 0x0a) */
+ char* nmea_end_ptr = memchr(&serial_buff[rd_idx],(int)0x0a, (wr_idx - rd_idx));
+
+ if (nmea_end_ptr) {
+ /* found end marker */
+ frame_size = nmea_end_ptr - &serial_buff[rd_idx] + 1;
+ latest_msg = lgw_parse_nmea(&serial_buff[rd_idx], frame_size);
+
+ if(latest_msg == INVALID || latest_msg == UNKNOWN) {
+ /* checksum failed */
+ frame_size = 0;
+ } else if (latest_msg == NMEA_RMC) { /* Get location from RMC frames */
+ gps_process_coords();
+ }
+ }
+ }
+
+ if (frame_size > 0) {
+ /* At this point message is a checksum verified frame
+ we're processed or ignored. Remove frame from buffer */
+ rd_idx += frame_size;
+ frame_end_idx = rd_idx;
+ } else {
+ rd_idx++;
+ }
+ } /* ...for(rd_idx = 0... */
+
+ if (frame_end_idx) {
+ /* Frames have been processed. Remove bytes to end of last processed frame */
+ memcpy(serial_buff,&serial_buff[frame_end_idx],wr_idx - frame_end_idx);
+ wr_idx -= frame_end_idx;
+ } /* ...for(rd_idx = 0... */
+
+ /* Prevent buffer overflow */
+ if ((sizeof(serial_buff) - wr_idx) < LGW_GPS_MIN_MSG_SIZE) {
+ memcpy(serial_buff,&serial_buff[LGW_GPS_MIN_MSG_SIZE],wr_idx - LGW_GPS_MIN_MSG_SIZE);
+ wr_idx -= LGW_GPS_MIN_MSG_SIZE;
+ }
+ }
+
+ /* clean up before leaving */
+ if (exit_sig == 1) {
+ lgw_gps_disable(gps_tty_dev);
+ lgw_stop();
+ }
+
+ printf("\nEnd of test for loragw_gps.c\n");
+ exit(EXIT_SUCCESS);
+}
+
+/* --- EOF ------------------------------------------------------------------ */
diff --git a/libloragw/tst/test_loragw_hal.c b/libloragw/tst/test_loragw_hal.c
new file mode 100644
index 0000000..e2fee5e
--- /dev/null
+++ b/libloragw/tst/test_loragw_hal.c
@@ -0,0 +1,416 @@
+/*
+ / _____) _ | |
+( (____ _____ ____ _| |_ _____ ____| |__
+ \____ \| ___ | (_ _) ___ |/ ___) _ \
+ _____) ) ____| | | || |_| ____( (___| | | |
+(______/|_____)_|_|_| \__)_____)\____)_| |_|
+ (C)2013 Semtech-Cycleo
+
+Description:
+ Minimum test program for the loragw_hal 'library'
+
+License: Revised BSD License, see LICENSE.TXT file include in the project
+Maintainer: Sylvain Miermont
+*/
+
+
+/* -------------------------------------------------------------------------- */
+/* --- DEPENDANCIES --------------------------------------------------------- */
+
+/* fix an issue between POSIX and C99 */
+#if __STDC_VERSION__ >= 199901L
+ #define _XOPEN_SOURCE 600
+#else
+ #define _XOPEN_SOURCE 500
+#endif
+
+#include <stdint.h> /* C99 types */
+#include <stdbool.h> /* bool type */
+#include <stdio.h> /* printf */
+#include <string.h> /* memset */
+#include <signal.h> /* sigaction */
+#include <unistd.h> /* getopt access */
+
+#include "loragw_hal.h"
+#include "loragw_reg.h"
+#include "loragw_aux.h"
+
+/* -------------------------------------------------------------------------- */
+/* --- PRIVATE MACROS ------------------------------------------------------- */
+
+#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
+
+/* -------------------------------------------------------------------------- */
+/* --- PRIVATE CONSTANTS ---------------------------------------------------- */
+
+#define DEFAULT_RSSI_OFFSET 0.0
+#define DEFAULT_NOTCH_FREQ 129000U
+
+/* -------------------------------------------------------------------------- */
+/* --- PRIVATE 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 */
+
+/* -------------------------------------------------------------------------- */
+/* --- PRIVATE FUNCTIONS DECLARATION ---------------------------------------- */
+
+static void sig_handler(int sigio);
+
+/* -------------------------------------------------------------------------- */
+/* --- PRIVATE FUNCTIONS DEFINITION ----------------------------------------- */
+
+static void sig_handler(int sigio) {
+ if (sigio == SIGQUIT) {
+ quit_sig = 1;;
+ } else if ((sigio == SIGINT) || (sigio == SIGTERM)) {
+ exit_sig = 1;
+ }
+}
+
+/* describe command line options */
+void usage(void) {
+ printf("Library version information: %s\n", lgw_version_info());
+ printf( "Available options:\n");
+ printf( " -h print this help\n");
+ printf( " -a <float> Radio A RX frequency in MHz\n");
+ printf( " -b <float> Radio B RX frequency in MHz\n");
+ printf( " -t <float> Radio TX frequency in MHz\n");
+ printf( " -r <int> Radio type (SX1255:1255, SX1257:1257)\n");
+ printf( " -k <int> Concentrator clock source (0: radio_A, 1: radio_B(default))\n");
+}
+
+/* -------------------------------------------------------------------------- */
+/* --- MAIN FUNCTION -------------------------------------------------------- */
+
+int main(int argc, char **argv)
+{
+ struct sigaction sigact; /* SIGQUIT&SIGINT&SIGTERM signal handling */
+
+ struct lgw_conf_board_s boardconf;
+ struct lgw_conf_rxrf_s rfconf;
+ struct lgw_conf_rxif_s ifconf;
+
+ struct lgw_pkt_rx_s rxpkt[4]; /* array containing up to 4 inbound packets metadata */
+ struct lgw_pkt_tx_s txpkt; /* configuration and metadata for an outbound packet */
+ struct lgw_pkt_rx_s *p; /* pointer on a RX packet */
+
+ int i, j;
+ int nb_pkt;
+ uint32_t fa = 0, fb = 0, ft = 0;
+ enum lgw_radio_type_e radio_type = LGW_RADIO_TYPE_NONE;
+ uint8_t clocksource = 1; /* Radio B is source by default */
+
+ uint32_t tx_cnt = 0;
+ unsigned long loop_cnt = 0;
+ uint8_t status_var = 0;
+ double xd = 0.0;
+ int xi = 0;
+
+ /* parse command line options */
+ while ((i = getopt (argc, argv, "ha:b:t:r:k:")) != -1) {
+ switch (i) {
+ case 'h':
+ usage();
+ return -1;
+ break;
+ case 'a': /* <float> Radio A RX frequency in MHz */
+ sscanf(optarg, "%lf", &xd);
+ fa = (uint32_t)((xd*1e6) + 0.5); /* .5 Hz offset to get rounding instead of truncating */
+ break;
+ case 'b': /* <float> Radio B RX frequency in MHz */
+ sscanf(optarg, "%lf", &xd);
+ fb = (uint32_t)((xd*1e6) + 0.5); /* .5 Hz offset to get rounding instead of truncating */
+ break;
+ case 't': /* <float> Radio TX frequency in MHz */
+ sscanf(optarg, "%lf", &xd);
+ ft = (uint32_t)((xd*1e6) + 0.5); /* .5 Hz offset to get rounding instead of truncating */
+ break;
+ case 'r': /* <int> Radio type (1255, 1257) */
+ sscanf(optarg, "%i", &xi);
+ switch (xi) {
+ case 1255:
+ radio_type = LGW_RADIO_TYPE_SX1255;
+ break;
+ case 1257:
+ radio_type = LGW_RADIO_TYPE_SX1257;
+ break;
+ default:
+ printf("ERROR: invalid radio type\n");
+ usage();
+ return -1;
+ }
+ break;
+ case 'k': /* <int> Concentrator clock source (Radio A or Radio B) */
+ sscanf(optarg, "%i", &xi);
+ clocksource = (uint8_t)xi;
+ break;
+ default:
+ printf("ERROR: argument parsing\n");
+ usage();
+ return -1;
+ }
+ }
+
+ /* check input parameters */
+ if ((fa == 0) || (fb == 0) || (ft == 0)) {
+ printf("ERROR: missing frequency input parameter:\n");
+ printf(" Radio A RX: %u\n", fa);
+ printf(" Radio B RX: %u\n", fb);
+ printf(" Radio TX: %u\n", ft);
+ usage();
+ return -1;
+ }
+
+ if (radio_type == LGW_RADIO_TYPE_NONE) {
+ printf("ERROR: missing radio type parameter:\n");
+ usage();
+ return -1;
+ }
+
+ /* configure signal handling */
+ sigemptyset(&sigact.sa_mask);
+ sigact.sa_flags = 0;
+ sigact.sa_handler = sig_handler;
+ sigaction(SIGQUIT, &sigact, NULL);
+ sigaction(SIGINT, &sigact, NULL);
+ sigaction(SIGTERM, &sigact, NULL);
+
+ /* beginning of LoRa concentrator-specific code */
+ printf("Beginning of test for loragw_hal.c\n");
+
+ printf("*** Library version information ***\n%s\n\n", lgw_version_info());
+
+ /* set configuration for board */
+ memset(&boardconf, 0, sizeof(boardconf));
+
+ boardconf.lorawan_public = true;
+ boardconf.clksrc = clocksource;
+ lgw_board_setconf(boardconf);
+
+ /* set configuration for RF chains */
+ memset(&rfconf, 0, sizeof(rfconf));
+
+ rfconf.enable = true;
+ rfconf.freq_hz = fa;
+ rfconf.rssi_offset = DEFAULT_RSSI_OFFSET;
+ rfconf.type = radio_type;
+ rfconf.tx_enable = true;
+ rfconf.tx_notch_freq = DEFAULT_NOTCH_FREQ;
+ lgw_rxrf_setconf(0, rfconf); /* radio A, f0 */
+
+ rfconf.enable = true;
+ rfconf.freq_hz = fb;
+ rfconf.rssi_offset = DEFAULT_RSSI_OFFSET;
+ rfconf.type = radio_type;
+ rfconf.tx_enable = false;
+ lgw_rxrf_setconf(1, rfconf); /* radio B, f1 */
+
+ /* set configuration for LoRa multi-SF channels (bandwidth cannot be set) */
+ memset(&ifconf, 0, sizeof(ifconf));
+
+ ifconf.enable = true;
+ ifconf.rf_chain = 1;
+ ifconf.freq_hz = -400000;
+ ifconf.datarate = DR_LORA_MULTI;
+ lgw_rxif_setconf(0, ifconf); /* chain 0: LoRa 125kHz, all SF, on f1 - 0.4 MHz */
+
+ ifconf.enable = true;
+ ifconf.rf_chain = 1;
+ ifconf.freq_hz = -200000;
+ ifconf.datarate = DR_LORA_MULTI;
+ lgw_rxif_setconf(1, ifconf); /* chain 1: LoRa 125kHz, all SF, on f1 - 0.2 MHz */
+
+ ifconf.enable = true;
+ ifconf.rf_chain = 1;
+ ifconf.freq_hz = 0;
+ ifconf.datarate = DR_LORA_MULTI;
+ lgw_rxif_setconf(2, ifconf); /* chain 2: LoRa 125kHz, all SF, on f1 - 0.0 MHz */
+
+ ifconf.enable = true;
+ ifconf.rf_chain = 0;
+ ifconf.freq_hz = -400000;
+ ifconf.datarate = DR_LORA_MULTI;
+ lgw_rxif_setconf(3, ifconf); /* chain 3: LoRa 125kHz, all SF, on f0 - 0.4 MHz */
+
+ ifconf.enable = true;
+ ifconf.rf_chain = 0;
+ ifconf.freq_hz = -200000;
+ ifconf.datarate = DR_LORA_MULTI;
+ lgw_rxif_setconf(4, ifconf); /* chain 4: LoRa 125kHz, all SF, on f0 - 0.2 MHz */
+
+ ifconf.enable = true;
+ ifconf.rf_chain = 0;
+ ifconf.freq_hz = 0;
+ ifconf.datarate = DR_LORA_MULTI;
+ lgw_rxif_setconf(5, ifconf); /* chain 5: LoRa 125kHz, all SF, on f0 + 0.0 MHz */
+
+ ifconf.enable = true;
+ ifconf.rf_chain = 0;
+ ifconf.freq_hz = 200000;
+ ifconf.datarate = DR_LORA_MULTI;
+ lgw_rxif_setconf(6, ifconf); /* chain 6: LoRa 125kHz, all SF, on f0 + 0.2 MHz */
+
+ ifconf.enable = true;
+ ifconf.rf_chain = 0;
+ ifconf.freq_hz = 400000;
+ ifconf.datarate = DR_LORA_MULTI;
+ lgw_rxif_setconf(7, ifconf); /* chain 7: LoRa 125kHz, all SF, on f0 + 0.4 MHz */
+
+ /* set configuration for LoRa 'stand alone' channel */
+ memset(&ifconf, 0, sizeof(ifconf));
+ ifconf.enable = true;
+ ifconf.rf_chain = 0;
+ ifconf.freq_hz = 0;
+ ifconf.bandwidth = BW_250KHZ;
+ ifconf.datarate = DR_LORA_SF10;
+ lgw_rxif_setconf(8, ifconf); /* chain 8: LoRa 250kHz, SF10, on f0 MHz */
+
+ /* set configuration for FSK channel */
+ memset(&ifconf, 0, sizeof(ifconf));
+ ifconf.enable = true;
+ ifconf.rf_chain = 1;
+ ifconf.freq_hz = 0;
+ ifconf.bandwidth = BW_250KHZ;
+ ifconf.datarate = 64000;
+ lgw_rxif_setconf(9, ifconf); /* chain 9: FSK 64kbps, on f1 MHz */
+
+ /* set configuration for TX packet */
+ memset(&txpkt, 0, sizeof(txpkt));
+ txpkt.freq_hz = ft;
+ txpkt.tx_mode = IMMEDIATE;
+ txpkt.rf_power = 10;
+ txpkt.modulation = MOD_LORA;
+ txpkt.bandwidth = BW_125KHZ;
+ txpkt.datarate = DR_LORA_SF9;
+ txpkt.coderate = CR_LORA_4_5;
+ strcpy((char *)txpkt.payload, "TX.TEST.LORA.GW.????" );
+ txpkt.size = 20;
+ txpkt.preamble = 6;
+ txpkt.rf_chain = 0;
+/*
+ memset(&txpkt, 0, sizeof(txpkt));
+ txpkt.freq_hz = F_TX;
+ txpkt.tx_mode = IMMEDIATE;
+ txpkt.rf_power = 10;
+ txpkt.modulation = MOD_FSK;
+ txpkt.f_dev = 50;
+ txpkt.datarate = 64000;
+ strcpy((char *)txpkt.payload, "TX.TEST.LORA.GW.????" );
+ txpkt.size = 20;
+ txpkt.preamble = 4;
+ txpkt.rf_chain = 0;
+*/
+
+ /* connect, configure and start the LoRa concentrator */
+ i = lgw_start();
+ if (i == LGW_HAL_SUCCESS) {
+ printf("*** Concentrator started ***\n");
+ } else {
+ printf("*** Impossible to start concentrator ***\n");
+ return -1;
+ }
+
+ /* once configured, dump content of registers to a file, for reference */
+ // FILE * reg_dump = NULL;
+ // reg_dump = fopen("reg_dump.log", "w");
+ // if (reg_dump != NULL) {
+ // lgw_reg_check(reg_dump);
+ // fclose(reg_dump);
+ // }
+
+ while ((quit_sig != 1) && (exit_sig != 1)) {
+ loop_cnt++;
+
+ /* fetch N packets */
+ nb_pkt = lgw_receive(ARRAY_SIZE(rxpkt), rxpkt);
+
+ if (nb_pkt == 0) {
+ wait_ms(300);
+ } else {
+ /* display received packets */
+ for(i=0; i < nb_pkt; ++i) {
+ p = &rxpkt[i];
+ printf("---\nRcv pkt #%d >>", i+1);
+ if (p->status == STAT_CRC_OK) {
+ printf(" if_chain:%2d", p->if_chain);
+ printf(" tstamp:%010u", p->count_us);
+ printf(" size:%3u", p->size);
+ switch (p-> modulation) {
+ case MOD_LORA: printf(" LoRa"); break;
+ case MOD_FSK: printf(" FSK"); break;
+ default: printf(" modulation?");
+ }
+ switch (p->datarate) {
+ case DR_LORA_SF7: printf(" SF7"); break;
+ case DR_LORA_SF8: printf(" SF8"); break;
+ case DR_LORA_SF9: printf(" SF9"); break;
+ case DR_LORA_SF10: printf(" SF10"); break;
+ case DR_LORA_SF11: printf(" SF11"); break;
+ case DR_LORA_SF12: printf(" SF12"); break;
+ default: printf(" datarate?");
+ }
+ switch (p->coderate) {
+ case CR_LORA_4_5: printf(" CR1(4/5)"); break;
+ case CR_LORA_4_6: printf(" CR2(2/3)"); break;
+ case CR_LORA_4_7: printf(" CR3(4/7)"); break;
+ case CR_LORA_4_8: printf(" CR4(1/2)"); break;
+ default: printf(" coderate?");
+ }
+ printf("\n");
+ printf(" RSSI:%+6.1f SNR:%+5.1f (min:%+5.1f, max:%+5.1f) payload:\n", p->rssi, p->snr, p->snr_min, p->snr_max);
+
+ for (j = 0; j < p->size; ++j) {
+ printf(" %02X", p->payload[j]);
+ }
+ printf(" #\n");
+ } else if (p->status == STAT_CRC_BAD) {
+ printf(" if_chain:%2d", p->if_chain);
+ printf(" tstamp:%010u", p->count_us);
+ printf(" size:%3u\n", p->size);
+ printf(" CRC error, damaged packet\n\n");
+ } else if (p->status == STAT_NO_CRC){
+ printf(" if_chain:%2d", p->if_chain);
+ printf(" tstamp:%010u", p->count_us);
+ printf(" size:%3u\n", p->size);
+ printf(" no CRC\n\n");
+ } else {
+ printf(" if_chain:%2d", p->if_chain);
+ printf(" tstamp:%010u", p->count_us);
+ printf(" size:%3u\n", p->size);
+ printf(" invalid status ?!?\n\n");
+ }
+ }
+ }
+
+ /* send a packet every X loop */
+ if (loop_cnt%16 == 0) {
+ /* 32b counter in the payload, big endian */
+ txpkt.payload[16] = 0xff & (tx_cnt >> 24);
+ txpkt.payload[17] = 0xff & (tx_cnt >> 16);
+ txpkt.payload[18] = 0xff & (tx_cnt >> 8);
+ txpkt.payload[19] = 0xff & tx_cnt;
+ i = lgw_send(txpkt); /* non-blocking scheduling of TX packet */
+ j = 0;
+ printf("+++\nSending packet #%d, rf path %d, return %d\nstatus -> ", tx_cnt, txpkt.rf_chain, i);
+ do {
+ ++j;
+ wait_ms(100);
+ lgw_status(TX_STATUS, &status_var); /* get TX status */
+ printf("%d:", status_var);
+ } while ((status_var != TX_FREE) && (j < 100));
+ ++tx_cnt;
+ printf("\nTX finished\n");
+ }
+ }
+
+ if (exit_sig == 1) {
+ /* clean up before leaving */
+ lgw_stop();
+ }
+
+ printf("\nEnd of test for loragw_hal.c\n");
+ return 0;
+}
+
+/* --- EOF ------------------------------------------------------------------ */
diff --git a/libloragw/tst/test_loragw_reg.c b/libloragw/tst/test_loragw_reg.c
new file mode 100644
index 0000000..37a6f5a
--- /dev/null
+++ b/libloragw/tst/test_loragw_reg.c
@@ -0,0 +1,134 @@
+/*
+ / _____) _ | |
+( (____ _____ ____ _| |_ _____ ____| |__
+ \____ \| ___ | (_ _) ___ |/ ___) _ \
+ _____) ) ____| | | || |_| ____( (___| | | |
+(______/|_____)_|_|_| \__)_____)\____)_| |_|
+ (C)2013 Semtech-Cycleo
+
+Description:
+ Minimum test program for the loragw_spi 'library'
+
+License: Revised BSD License, see LICENSE.TXT file include in the project
+Maintainer: Sylvain Miermont
+*/
+
+
+/* -------------------------------------------------------------------------- */
+/* --- DEPENDANCIES --------------------------------------------------------- */
+
+#include <stdint.h>
+#include <stdio.h>
+
+#include "loragw_reg.h"
+
+/* -------------------------------------------------------------------------- */
+/* --- MAIN FUNCTION -------------------------------------------------------- */
+
+#define BURST_TEST_LENGTH 8192
+
+int main()
+{
+ int32_t read_value, test_value;
+ uint16_t lfsr;
+ uint8_t burst_buffout[BURST_TEST_LENGTH];
+ uint8_t burst_buffin[BURST_TEST_LENGTH];
+ int i;
+
+ printf("Beginning of test for loragw_reg.c\n");
+
+ lgw_connect(false, 129E3);
+ /* 2 SPI transactions:
+ -> 0x80 0x00 <- 0x00 0x00 forcing page 0
+ -> 0x01 0x00 <- 0x00 0x64 checking version
+ */
+
+ /* --- READ TEST --- */
+
+ lgw_reg_w(LGW_SOFT_RESET, 1);
+ lgw_reg_check(stdout);
+
+ /* --- READ/WRITE COHERENCY TEST --- */
+
+ /* 8b unsigned */
+ test_value = 197; /* 11000101b */
+ lgw_reg_w(LGW_IMPLICIT_PAYLOAD_LENGHT, test_value);
+ lgw_reg_r(LGW_IMPLICIT_PAYLOAD_LENGHT, &read_value);
+ printf("IMPLICIT_PAYLOAD_LENGHT = %d (should be %d)\n", read_value, test_value);
+
+ /* 8b signed */
+ /* NO SUCH REG AVAILABLE */
+ // /* RADIO_SELECT is normally unsigned, modify it manually in loragw_reg.c */
+ // test_value = -59; /* 11000101b */
+ // lgw_reg_w(LGW_RADIO_SELECT, test_value);
+ // lgw_reg_r(LGW_RADIO_SELECT, &read_value);
+ // printf("RADIO_SELECT = %d (should be %d)\n", read_value, test_value);
+
+ /* less than 8b, with offset, unsigned */
+ test_value = 11; /* 1011b */
+ lgw_reg_w(LGW_FRAME_SYNCH_PEAK2_POS, test_value);
+ lgw_reg_r(LGW_FRAME_SYNCH_PEAK2_POS, &read_value);
+ printf("FRAME_SYNCH_PEAK2_POS = %d (should be %d)\n", read_value, test_value);
+
+ /* less than 8b, with offset, signed */
+ /* NO SUCH REG AVAILABLE */
+ // /* MBWSSF_FRAME_SYNCH_PEAK2_POS is normally unsigned, modify it manually in loragw_reg.c */
+ // test_value = -5; /* 1011b */
+ // lgw_reg_w(LGW_MBWSSF_FRAME_SYNCH_PEAK2_POS, test_value);
+ // lgw_reg_r(LGW_MBWSSF_FRAME_SYNCH_PEAK2_POS, &read_value);
+ // printf("MBWSSF_FRAME_SYNCH_PEAK2_POS = %d (should be %d)\n", read_value, test_value);
+
+ /* 16b unsigned */
+ test_value = 49253; /* 11000000 01100101b */
+ lgw_reg_w(LGW_PREAMBLE_SYMB1_NB, test_value);
+ lgw_reg_r(LGW_PREAMBLE_SYMB1_NB, &read_value);
+ printf("PREAMBLE_SYMB1_NB = %d (should be %d)\n", read_value, test_value);
+
+ /* 16b signed */
+ /* NO SUCH REG AVAILABLE */
+ // /* CAPTURE_PERIOD is normally unsigned, modify it manually in loragw_reg.c */
+ // test_value = -16283; /* 11000000 01100101b */
+ // lgw_reg_w(LGW_CAPTURE_PERIOD, test_value);
+ // lgw_reg_r(LGW_CAPTURE_PERIOD, &read_value);
+ // printf("CAPTURE_PERIOD = %d (should be %d)\n", read_value, test_value);
+
+ /* between 8b and 16b, unsigned */
+ test_value = 3173; /* 1100 01100101b */
+ lgw_reg_w(LGW_ADJUST_MODEM_START_OFFSET_SF12_RDX4, test_value);
+ lgw_reg_r(LGW_ADJUST_MODEM_START_OFFSET_SF12_RDX4, &read_value);
+ printf("ADJUST_MODEM_START_OFFSET_SF12_RDX4 = %d (should be %d)\n", read_value, test_value);
+
+ /* between 8b and 16b, signed */
+ test_value = -1947; /* 11000 01100101b */
+ lgw_reg_w(LGW_IF_FREQ_1, test_value);
+ lgw_reg_r(LGW_IF_FREQ_1, &read_value);
+ printf("IF_FREQ_1 = %d (should be %d)\n", read_value, test_value);
+
+ /* --- BURST WRITE AND READ TEST --- */
+
+ /* initialize data for SPI test */
+ lfsr = 0xFFFF;
+ for(i=0; i<BURST_TEST_LENGTH; ++i) {
+ burst_buffout[i] = (uint8_t)(lfsr ^ (lfsr >> 4));
+ /* printf("%05d # 0x%04x 0x%02x\n", i, lfsr, burst_buffout[i]); */
+ lfsr = (lfsr & 1) ? ((lfsr >> 1) ^ 0x8679) : (lfsr >> 1);
+ }
+
+ lgw_reg_wb(LGW_TX_DATA_BUF_DATA, burst_buffout, 256);
+ lgw_reg_rb(LGW_RX_DATA_BUF_DATA, burst_buffin, 256);
+
+ /* impossible to check in software,
+ RX_DATA_BUF_DATA is read-only,
+ TX_DATA_BUF_DATA is write only,
+ use a logic analyser */
+
+ /* --- END OF TEST --- */
+
+ lgw_disconnect();
+ /* no SPI transaction */
+
+ printf("End of test for loragw_reg.c\n");
+ return 0;
+}
+
+/* --- EOF ------------------------------------------------------------------ */
diff --git a/libloragw/tst/test_loragw_spi.c b/libloragw/tst/test_loragw_spi.c
new file mode 100644
index 0000000..872a075
--- /dev/null
+++ b/libloragw/tst/test_loragw_spi.c
@@ -0,0 +1,85 @@
+/*
+ / _____) _ | |
+( (____ _____ ____ _| |_ _____ ____| |__
+ \____ \| ___ | (_ _) ___ |/ ___) _ \
+ _____) ) ____| | | || |_| ____( (___| | | |
+(______/|_____)_|_|_| \__)_____)\____)_| |_|
+ (C)2013 Semtech-Cycleo
+
+Description:
+ Minimum test program for the loragw_spi 'library'
+ Use logic analyser to check the results.
+
+License: Revised BSD License, see LICENSE.TXT file include in the project
+Maintainer: Sylvain Miermont
+*/
+
+
+/* -------------------------------------------------------------------------- */
+/* --- DEPENDANCIES --------------------------------------------------------- */
+
+#include <stdint.h>
+#include <stdio.h>
+
+#include "loragw_spi.h"
+
+/* -------------------------------------------------------------------------- */
+/* --- PRIVATE MACROS ------------------------------------------------------- */
+
+#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
+
+/* -------------------------------------------------------------------------- */
+/* --- PRIVATE CONSTANTS ---------------------------------------------------- */
+
+#define BURST_TEST_SIZE 2500 /* >> LGW_BURST_CHUNK */
+#define TIMING_REPEAT 1 /* repeat transactions multiple times for timing characterisation */
+
+/* -------------------------------------------------------------------------- */
+/* --- MAIN FUNCTION -------------------------------------------------------- */
+
+int main()
+{
+ int i;
+ void *spi_target = NULL;
+ uint8_t data = 0;
+ uint8_t dataout[BURST_TEST_SIZE];
+ uint8_t datain[BURST_TEST_SIZE];
+ uint8_t spi_mux_mode = LGW_SPI_MUX_MODE0;
+
+ for (i = 0; i < BURST_TEST_SIZE; ++i) {
+ dataout[i] = 0x30 + (i % 10); /* ASCCI code for 0 -> 9 */
+ datain[i] = 0x23; /* garbage data, to be overwritten by received data */
+ }
+
+ printf("Beginning of test for loragw_spi.c\n");
+ lgw_spi_open(&spi_target);
+
+ /* normal R/W test */
+ for (i = 0; i < TIMING_REPEAT; ++i)
+ lgw_spi_w(spi_target, spi_mux_mode, LGW_SPI_MUX_TARGET_SX1301, 0xAA, 0x96);
+ for (i = 0; i < TIMING_REPEAT; ++i)
+ lgw_spi_r(spi_target, spi_mux_mode, LGW_SPI_MUX_TARGET_SX1301, 0x55, &data);
+
+ /* burst R/W test, small bursts << LGW_BURST_CHUNK */
+ for (i = 0; i < TIMING_REPEAT; ++i)
+ lgw_spi_wb(spi_target, spi_mux_mode, LGW_SPI_MUX_TARGET_SX1301, 0x55, dataout, 16);
+ for (i = 0; i < TIMING_REPEAT; ++i)
+ lgw_spi_rb(spi_target, spi_mux_mode, LGW_SPI_MUX_TARGET_SX1301, 0x55, datain, 16);
+
+ /* burst R/W test, large bursts >> LGW_BURST_CHUNK */
+ for (i = 0; i < TIMING_REPEAT; ++i)
+ lgw_spi_wb(spi_target, spi_mux_mode, LGW_SPI_MUX_TARGET_SX1301, 0x5A, dataout, ARRAY_SIZE(dataout));
+ for (i = 0; i < TIMING_REPEAT; ++i)
+ lgw_spi_rb(spi_target, spi_mux_mode, LGW_SPI_MUX_TARGET_SX1301, 0x5A, datain, ARRAY_SIZE(datain));
+
+ /* last read (blocking), just to be sure no to quit before the FTDI buffer is flushed */
+ lgw_spi_r(spi_target, spi_mux_mode, LGW_SPI_MUX_TARGET_SX1301, 0x55, &data);
+ printf("data received (simple read): %d\n",data);
+
+ lgw_spi_close(spi_target);
+ printf("End of test for loragw_spi.c\n");
+
+ return 0;
+}
+
+/* --- EOF ------------------------------------------------------------------ */