diff --git a/.travis.yml b/.travis.yml index 2dd1effa..d6d5f630 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,6 +12,22 @@ before_install: - mv arduino-1.8.5 $HOME/arduino_ide - ln -s $PWD $HOME/arduino_ide/libraries/Test_Library - export PATH="$HOME/arduino_ide:$PATH" + # + # functions to generate the board settings for SAMD, STM32L0, ... + # keep args for these aligned for any common options. $1 is always board name, $2 is region. + # + # Changes to the BSP may break this build, sorry! + # + - "function _samdopts { echo mcci:samd:${1:-mcci_catena_4450}:lorawan_region=${2:-us915} ; }" + - "function _stm32l0opts { echo mcci:stm32:Catena:pnum=${1:-CATENA_4551}:opt=${3:-osstd}:xserial=${4:-generic}:usb=${5:-none}:upload_method=${6:-STLink} ; }" + # + # Put one or more arguments into lmic_project_config.h as `#define $i\n` + - function _projcfg { for i in "$@" ; do printf '#define %s 1\n' "$i" ; done > $PWD/project_config/lmic_project_config.h ; } + # + # Handy macro to deal with expected failures. + - 'function _expect_failure { if [ $? -eq 0 ]; then echo "Suceeded, but should have failed!" ; echo project_config/lmic_project_config.h ; cat $PWD/project_config/lmic_project_config.h ; return 1 ; else echo "Failed, as expected"; return 0 ; fi ; }' + # + # modify the board manager preferences to point to our BSPs. - arduino --pref "boardsmanager.additional.urls=https://github.com/mcci-catena/arduino-boards/raw/master/BoardManagerFiles/package_mcci_index.json" --save-prefs install: @@ -19,7 +35,41 @@ install: # - arduino --install-boards mcci:stm32 script: - - arduino --verify --board mcci:samd:mcci_catena_4450 $PWD/examples/raw-feather/raw-feather.ino - - printf '#define COMPILE_REGRESSION_TEST 1\n#define CFG_sx1276_radio 1\n#define CFG_us915 1\n' > $PWD/project_config/lmic_project_config.h && arduino --verify --board mcci:samd:mcci_catena_4450 $PWD/examples/ttn-otaa-feather-us915/ttn-otaa-feather-us915.ino +# +# show the output of the config commands for reference. + - "echo $(_stm32l0opts) $(_stm32l0opts '' projcfg)" + - "echo $(_samdopts) $(_samdopts '' projcfg)" -# - arduino --pref "build.verbose=true" --verify --board mcci:stm32:Catena:pnum=CATENA_4551 $PWD/examples/raw-feather/raw-feather.ino +# +# test each of the regions. + - arduino --verify --board $(_samdopts '' us915) $PWD/examples/raw-feather/raw-feather.ino + - arduino --verify --board $(_samdopts '' eu868) $PWD/examples/raw-feather/raw-feather.ino +# V1.1.0 of the samd bsp doesn't support au921 correctly -- test with projcfg +# - arduino --verify --board $(_samdopts '' au921) $PWD/examples/raw-feather/raw-feather.ino + - arduino --verify --board $(_samdopts '' as923) $PWD/examples/raw-feather/raw-feather.ino + - arduino --verify --board $(_samdopts '' as923jp) $PWD/examples/raw-feather/raw-feather.ino + - arduino --verify --board $(_samdopts '' in866) $PWD/examples/raw-feather/raw-feather.ino + +# +# some tests using the projcfg file that should pass + - _projcfg COMPILE_REGRESSION_TEST CFG_us915 CFG_sx1276_radio && arduino --verify --board mcci:samd:mcci_catena_4450:lorawan_region=projcfg $PWD/examples/ttn-otaa-feather-us915/ttn-otaa-feather-us915.ino + - _projcfg COMPILE_REGRESSION_TEST CFG_us915 CFG_sx1272_radio && arduino --verify --board mcci:samd:mcci_catena_4450:lorawan_region=projcfg $PWD/examples/ttn-otaa-feather-us915/ttn-otaa-feather-us915.ino + - _projcfg CFG_au921 CFG_sx1276_radio && arduino --verify --board $(_samdopts '' projcfg) $PWD/examples/raw-feather/raw-feather.ino + +# +# some tests that should generate build failures. +# +# COMPILE_REGRESSION_TEST must be defined for ttn-otaa-feather-us915 + - _projcfg CFG_us915 CFG_sx1272_radio && { arduino --verify --board mcci:samd:mcci_catena_4450:lorawan_region=projcfg $PWD/examples/ttn-otaa-feather-us915/ttn-otaa-feather-us915.ino ; _expect_failure; } +# +# Only one radio may be defined + - _projcfg COMPILE_REGRESSION_TEST CFG_us915 CFG_sx1272_radio CFG_sx1276_radio && { arduino --verify --board mcci:samd:mcci_catena_4450:lorawan_region=projcfg $PWD/examples/ttn-otaa-feather-us915/ttn-otaa-feather-us915.ino ; _expect_failure; } + + +# +# *** TESTS FOR STM32L0 / Catena 4551 **** +# make sure you install the BSP above. +# +# - arduino --verify --board $(_stm32l0opts) $PWD/examples/raw-feather/raw-feather.ino + +### end of file ### \ No newline at end of file diff --git a/examples/raw-feather/raw-feather.ino b/examples/raw-feather/raw-feather.ino index 82b2f84c..d32054b3 100644 --- a/examples/raw-feather/raw-feather.ino +++ b/examples/raw-feather/raw-feather.ino @@ -5,33 +5,33 @@ Module: raw-feather.ino Function: - Slightly improved Raw test example, for Adafruit Feather M0 LoRa + Slightly improved Raw test example, for Adafruit Feather M0 LoRa Version: - V0.7.0 Tue Jan 23 2018 10:25:50 chwon Edit level 2 + V0.7.0 Tue Jan 23 2018 10:25:50 chwon Edit level 2 Copyright notice: - This file copyright (C) 2017, 2018 by + This file copyright (C) 2017, 2018 by - MCCI Corporation - 3520 Krums Corners Road - Ithaca, NY 14850 + MCCI Corporation + 3520 Krums Corners Road + Ithaca, NY 14850 - An unpublished work. All rights reserved. + An unpublished work. All rights reserved. - This file is proprietary information, and may not be disclosed or - copied without the prior permission of MCCI Corporation. + This file is proprietary information, and may not be disclosed or + copied without the prior permission of MCCI Corporation. Author: - Matthijs Kooijman 2015 - Terry Moore, MCCI Corporation April 2017 + Matthijs Kooijman 2015 + Terry Moore, MCCI Corporation April 2017 Revision history: 0.5.0 Sat Apr 1 2017 22:26:22 tmm - Module created. + Module created. 0.7.0 Tue Jan 23 2018 10:25:50 chwon - Add Catena 4551 platform support. + Add Catena 4551 platform support. */ @@ -53,6 +53,9 @@ Revision history: #include #include +#include +#include + // we formerly would check this configuration; but now there is a flag, // in the LMIC, LMIC.noRXIQinversion; // if we set that during init, we get the same effect. If @@ -72,34 +75,36 @@ Revision history: // https://docs.google.com/spreadsheets/d/1voGAtQAjC1qBmaVuP1ApNKs1ekgUjavHuVQIXyYSvNc #define TX_INTERVAL 2000 // milliseconds +#define RX_RSSI_INTERVAL 100 // milliseconds -#ifdef ARDUINO_ARCH_SAMD -// Pin mapping for Adafruit Feather M0 LoRa +// Pin mapping for Adafruit Feather M0 LoRa, etc. +#if defined(ARDUINO_SAMD_FEATHER_M0) const lmic_pinmap lmic_pins = { .nss = 8, .rxtx = LMIC_UNUSED_PIN, .rst = 4, .dio = {3, 6, LMIC_UNUSED_PIN}, + .rxtx_rx_active = 0, + .rssi_cal = 8, // LBT cal for the Adafruit Feather M0 LoRa, in dB + .spi_freq = 8000000, }; -#endif - -#ifdef ARDUINO_ARCH_STM32 -// Pin mapping for Catena 4551 Feather M0 LoRa +#elif defined(ARDUINO_CATENA_4551) const lmic_pinmap lmic_pins = { - .nss = D7, // chip select is D7 - .rxtx = D29, // RXTX is D29 - .rst = D8, // NRESET is D8 - - .dio = {D25, // DIO0 (IRQ) is D25 - D26, // DIO1 is D26 - D27, // DIO2 is D27 - }, - .rxtx_rx_active = 1, - .spi_freq = 8000000 /* 8MHz */ + .nss = 7, + .rxtx = 29, + .rst = 8, + .dio = { 25, // DIO0 (IRQ) is D25 + 26, // DIO1 is D26 + 27, // DIO2 is D27 + }, + .rxtx_rx_active = 1, + .rssi_cal = 10, + .spi_freq = 8000000 // 8MHz }; +#else +# error "Unknown target" #endif - // These callbacks are only used in over-the-air activation, so they are // left empty here (we cannot leave them out completely unless // DISABLE_JOIN is set in config.h, otherwise the linker will complain). @@ -112,18 +117,46 @@ void os_getDevKey (u1_t* buf) { } void onEvent (ev_t ev) { } +extern "C" { +void lmic_printf(const char *fmt, ...); +}; + +void lmic_printf(const char *fmt, ...) { + if (! Serial.dtr()) + return; + + char buf[256]; + va_list ap; + + va_start(ap, fmt); + (void) vsnprintf(buf, sizeof(buf) - 1, fmt, ap); + va_end(ap); + + // in case we overflowed: + buf[sizeof(buf) - 1] = '\0'; + if (Serial.dtr()) Serial.print(buf); +} + osjob_t txjob; osjob_t timeoutjob; static void tx_func (osjob_t* job); // Transmit the given string and call the given function afterwards void tx(const char *str, osjobcb_t func) { - os_radio(RADIO_RST); // Stop RX first - delay(1); // Wait a bit, without this os_radio below asserts, apparently because the state hasn't changed yet + // the radio is probably in RX mode; stop it. + os_radio(RADIO_RST); + // wait a bit so the radio can come out of RX mode + delay(1); + + // prepare data LMIC.dataLen = 0; while (*str) LMIC.frame[LMIC.dataLen++] = *str++; + + // set completion function. LMIC.osjob.func = func; + + // start the transmission os_radio(RADIO_TX); Serial.println("TX"); } @@ -227,49 +260,173 @@ void setup() { // guides the responder through all the channels, powers, ramps // the transmit power from min to max, and measures the RSSI and SNR. // Even more amazing would be a scheme where the controller could - // handle multiple nodes; in that case we'd have a - const static bool fDownlink = true; + // handle multiple nodes; in that case we'd have a way to do + // production test and qualification. However, using an RWC5020A + // is a much better use of development time. + + // set fDownlink true to use a downlink channel; false + // to use an uplink channel. Generally speaking, uplink + // is more interesting, because you can prove that gateways + // *should* be able to hear you. + const static bool fDownlink = false; + + // the downlink channel to be used. const static uint8_t kDownlinkChannel = 3; + + // the uplink channel to be used. const static uint8_t kUplinkChannel = 8 + 3; + + // this is automatically set to the proper bandwidth in kHz, + // based on the selected channel. uint32_t uBandwidth; if (! fDownlink) - { - if (kUplinkChannel < 64) - { - LMIC.freq = US915_125kHz_UPFBASE + - kUplinkChannel * US915_125kHz_UPFSTEP; - uBandwidth = 125; - } - else - { - LMIC.freq = US915_500kHz_UPFBASE + - (kUplinkChannel - 64) * US915_500kHz_UPFSTEP; - uBandwidth = 500; - } - } + { + if (kUplinkChannel < 64) + { + LMIC.freq = US915_125kHz_UPFBASE + + kUplinkChannel * US915_125kHz_UPFSTEP; + uBandwidth = 125; + } + else + { + LMIC.freq = US915_500kHz_UPFBASE + + (kUplinkChannel - 64) * US915_500kHz_UPFSTEP; + uBandwidth = 500; + } + } else - { - // downlink channel - LMIC.freq = US915_500kHz_DNFBASE + - kDownlinkChannel * US915_500kHz_DNFSTEP; - uBandwidth = 500; - } + { + // downlink channel + LMIC.freq = US915_500kHz_DNFBASE + + kDownlinkChannel * US915_500kHz_DNFSTEP; + uBandwidth = 500; + } // Use a suitable spreading factor if (uBandwidth < 500) - LMIC.datarate = DR_SF10; // DR0 + LMIC.datarate = US915_DR_SF7; // DR4 else - LMIC.datarate = DR_SF12CR; // DR8 + LMIC.datarate = US915_DR_SF12CR; // DR8 // default tx power for US: 21 dBm LMIC.txpow = 21; +#elif defined(CFG_au921) + // make it easier for test, by pull the parameters up to the top of the + // block. Ideally, we'd use the serial port to drive this; or have + // a voting protocol where one side is elected the controller and + // guides the responder through all the channels, powers, ramps + // the transmit power from min to max, and measures the RSSI and SNR. + // Even more amazing would be a scheme where the controller could + // handle multiple nodes; in that case we'd have a way to do + // production test and qualification. However, using an RWC5020A + // is a much better use of development time. + + // set fDownlink true to use a downlink channel; false + // to use an uplink channel. Generally speaking, uplink + // is more interesting, because you can prove that gateways + // *should* be able to hear you. + const static bool fDownlink = false; + + // the downlink channel to be used. + const static uint8_t kDownlinkChannel = 3; + + // the uplink channel to be used. + const static uint8_t kUplinkChannel = 8 + 3; + + // this is automatically set to the proper bandwidth in kHz, + // based on the selected channel. + uint32_t uBandwidth; + + if (! fDownlink) + { + if (kUplinkChannel < 64) + { + LMIC.freq = AU921_125kHz_UPFBASE + + kUplinkChannel * AU921_125kHz_UPFSTEP; + uBandwidth = 125; + } + else + { + LMIC.freq = AU921_500kHz_UPFBASE + + (kUplinkChannel - 64) * AU921_500kHz_UPFSTEP; + uBandwidth = 500; + } + } + else + { + // downlink channel + LMIC.freq = AU921_500kHz_DNFBASE + + kDownlinkChannel * AU921_500kHz_DNFSTEP; + uBandwidth = 500; + } + + // Use a suitable spreading factor + if (uBandwidth < 500) + LMIC.datarate = AU921_DR_SF7; // DR4 + else + LMIC.datarate = AU921_DR_SF12CR; // DR8 + + // default tx power for AU: 30 dBm + LMIC.txpow = 30; +#elif defined(CFG_as923) +// make it easier for test, by pull the parameters up to the top of the +// block. Ideally, we'd use the serial port to drive this; or have +// a voting protocol where one side is elected the controller and +// guides the responder through all the channels, powers, ramps +// the transmit power from min to max, and measures the RSSI and SNR. +// Even more amazing would be a scheme where the controller could +// handle multiple nodes; in that case we'd have a way to do +// production test and qualification. However, using an RWC5020A +// is a much better use of development time. + const static uint8_t kChannel = 0; + uint32_t uBandwidth; + + LMIC.freq = AS923_F1 + kChannel * 200000; + uBandwidth = 125; + + // Use a suitable spreading factor + if (uBandwidth == 125) + LMIC.datarate = AS923_DR_SF7; // DR7 + else + LMIC.datarate = AS923_DR_SF7B; // DR8 + + // default tx power for AS: 21 dBm + LMIC.txpow = 16; + + if (LMIC_COUNTRY_CODE == LMIC_COUNTRY_CODE_JP) + { + LMIC.lbt_ticks = us2osticks(AS923JP_LBT_US); + LMIC.lbt_dbmax = AS923JP_LBT_DB_MAX; + } +#elif defined(CFG_in866) +// make it easier for test, by pull the parameters up to the top of the +// block. Ideally, we'd use the serial port to drive this; or have +// a voting protocol where one side is elected the controller and +// guides the responder through all the channels, powers, ramps +// the transmit power from min to max, and measures the RSSI and SNR. +// Even more amazing would be a scheme where the controller could +// handle multiple nodes; in that case we'd have a way to do +// production test and qualification. However, using an RWC5020A +// is a much better use of development time. + const static uint8_t kChannel = 0; + uint32_t uBandwidth; + + LMIC.freq = IN866_F1 + kChannel * 200000; + uBandwidth = 125; + + LMIC.datarate = IN866_DR_SF7; // DR7 + // default tx power for IN: 30 dBm + LMIC.txpow = IN866_TX_EIRP_MAX_DBM; +#else +# error Unsupported LMIC regional configuration. #endif + // disable RX IQ inversion LMIC.noRXIQinversion = true; - // This sets CR 4/5, BW125 (except for EU DR_SF7B, which uses BW250) + // This sets CR 4/5, BW125 (except for EU/AS923 DR_SF7B, which uses BW250) LMIC.rps = updr2rps(LMIC.datarate); Serial.print("Frequency: "); Serial.print(LMIC.freq / 1000000); diff --git a/project_config/lmic_project_config.h b/project_config/lmic_project_config.h index eb608dac..7c19d39e 100644 --- a/project_config/lmic_project_config.h +++ b/project_config/lmic_project_config.h @@ -1,8 +1,9 @@ -// project-specific definitions for otaa sensor +// project-specific definitions //#define CFG_eu868 1 #define CFG_us915 1 //#define CFG_au921 1 //#define CFG_as923 1 +// #define LMIC_COUNTRY_CODE LMIC_COUNTRY_CODE_JP /* for as923-JP */ //#define CFG_in866 1 #define CFG_sx1276_radio 1 //#define LMIC_USE_INTERRUPTS diff --git a/src/hal/hal.cpp b/src/hal/hal.cpp index d9aede92..74a23c7a 100644 --- a/src/hal/hal.cpp +++ b/src/hal/hal.cpp @@ -61,6 +61,10 @@ void hal_pin_rst (u1_t val) { } } +s1_t hal_getRssiCal (void) { + return plmic_pins->rssi_cal; +} + #if !defined(LMIC_USE_INTERRUPTS) static void hal_interrupt_init() { pinMode(plmic_pins->dio[0], INPUT); diff --git a/src/hal/hal.h b/src/hal/hal.h index f40e19f2..c5ea1007 100644 --- a/src/hal/hal.h +++ b/src/hal/hal.h @@ -12,13 +12,18 @@ static const int NUM_DIO = 3; +// be careful of alignment below. struct lmic_pinmap { - u1_t nss; - u1_t rxtx; - u1_t rst; - u1_t dio[NUM_DIO]; - u1_t rxtx_rx_active; - u4_t spi_freq; + u1_t nss; // byte 0: pin for select + u1_t rxtx; // byte 1: pin for rx/tx control + u1_t rst; // byte 2: pin for reset + u1_t dio[NUM_DIO]; // bytes 3..5: pins for DIO0, DOI1, DIO2 + // true if we must set rxtx for rx_active, false for tx_active + u1_t rxtx_rx_active; // byte 6: polarity of rxtx active + s1_t rssi_cal; // byte 7: cal in dB -- added to RSSI + // measured prior to decision. + // Must include noise guardband! + u4_t spi_freq; // bytes 8..11: SPI freq in Hz. }; // Use this for any unused pins. diff --git a/src/lmic/config.h b/src/lmic/config.h index 3a86f036..17509078 100644 --- a/src/lmic/config.h +++ b/src/lmic/config.h @@ -3,31 +3,23 @@ // In the original LMIC code, these config values were defined on the // gcc commandline. Since Arduino does not allow easily modifying the -// compiler commandline, use this file to load project defaults and then fall back. -// Note that you should not edit this file, because then your changes will -// be applied to a globally-distributed file. Instead, create an -// lmic_project_config.h file. +// compiler commandline unless you modify the BSP, you have two choices: +// +// - edit {libraries}/arduino-lmic/project_config/lmic_project_config.h; +// - use a BSP like the MCCI Arduino BSPs, which get the configuration +// from the boards.txt file through a menu option. +// +// You definitely should not edit this file. +// set up preconditions, and load configuration if needed. #ifndef _LMIC_CONFIG_PRECONDITIONS_H_ # include "lmic_config_preconditions.h" #endif -// if you're editing this file directly (and not editing the project-config file -// referenced from the pre-conditions file), then uncomment exactly one of the -// following to select the operating bandplan - -//#define CFG_eu868 1 -//#define CFG_us915 1 -//#define CFG_cn783 1 // not yet -//#define CFG_eu433 1 // not yet -//#define CFG_au921 1 -//#define CFG_cn490 1 // not yet -//#define CFG_as923 1 -//#define CFG_kr921 1 // not yet -//#define CFG_in866 1 +// check post-conditions. +// make sure that we have exactly one target region defined. #if CFG_LMIC_REGION_MASK == 0 -# warning Target RF configuration not defined, assuming CFG_eu868 # define CFG_eu868 1 #elif (CFG_LMIC_REGION_MASK & (-CFG_LMIC_REGION_MASK)) != CFG_LMIC_REGION_MASK # error You can define at most one of CFG_... variables @@ -35,6 +27,17 @@ # error The selected CFG_... region is not supported yet. #endif +// make sure that LMIC_COUNTRY_CODE is defined. +#ifndef LMIC_COUNTRY_CODE +# define LMIC_COUNTRY_CODE LMIC_COUNTRY_CODE_NONE +#endif + +// if the country code is Japan, then the region must be AS923 +#if LMIC_COUNTRY_CODE == LMIC_COUNTRY_CODE_JP && CFG_region != LMIC_REGION_as923 +# error "If country code is JP, then region must be AS923" +#endif + +// check for internal consistency #if !(CFG_LMIC_EU_like || CFG_LMIC_US_like) # error "Internal error: Neither EU-like nor US-like!" #endif @@ -44,6 +47,9 @@ //#define CFG_sx1272_radio 1 // This is the SX1276/SX1277/SX1278/SX1279 radio, which is also used on // the HopeRF RFM95 boards. +//#define CFG_sx1276_radio 1 + +// ensure that a radio is defined. #if ! (defined(CFG_sx1272_radio) || defined(CFG_sx1276_radio)) # warning Target radio not defined, assuming CFG_sx1276_radio #define CFG_sx1276_radio 1 diff --git a/src/lmic/hal.h b/src/lmic/hal.h index b69e4fb4..562b4283 100644 --- a/src/lmic/hal.h +++ b/src/lmic/hal.h @@ -105,6 +105,11 @@ u1_t hal_checkTimer (u4_t targettime); */ void hal_failed (const char *file, u2_t line); +/* + * get the calibration value for radio_rssi + */ +s1_t hal_getRssiCal (void); + #ifdef __cplusplus } // extern "C" #endif diff --git a/src/lmic/lmic.c b/src/lmic/lmic.c index 10039b97..a42f4f08 100644 --- a/src/lmic/lmic.c +++ b/src/lmic/lmic.c @@ -615,15 +615,20 @@ scan_mac_cmds( case MCMD_DN2P_SET: { #if !defined(DISABLE_MCMD_DN2P_SET) dr_t dr = (dr_t)(opts[oidx+1] & 0x0F); + u1_t rx1DrOffset = (u1_t)((opts[oidx+1] & 0x70) >> 4); u4_t freq = LMICbandplan_convFreq(&opts[oidx+2]); LMIC.dn2Ans = 0x80; // answer pending if( validDR(dr) ) LMIC.dn2Ans |= MCMD_DN2P_ANS_DRACK; if( freq != 0 ) LMIC.dn2Ans |= MCMD_DN2P_ANS_CHACK; - if( LMIC.dn2Ans == (0x80|MCMD_DN2P_ANS_DRACK|MCMD_DN2P_ANS_CHACK) ) { + if (rx1DrOffset <= 3) + LMIC.dn2Ans |= MCMD_DN2P_ANS_RX1DrOffsetAck; + + if( LMIC.dn2Ans == (0x80|MCMD_DN2P_ANS_DRACK|MCMD_DN2P_ANS_CHACK| MCMD_DN2P_ANS_RX1DrOffsetAck) ) { LMIC.dn2Dr = dr; LMIC.dn2Freq = freq; + LMIC.rx1DrOffset = rx1DrOffset; DO_DEVDB(LMIC.dn2Dr,dn2Dr); DO_DEVDB(LMIC.dn2Freq,dn2Freq); } @@ -1223,14 +1228,6 @@ static void buildDataFrame (void) { LMIC.dutyCapAns = 0; } #endif // !DISABLE_MCMD_DCAP_REQ -#if !defined(DISABLE_MCMD_DN2P_SET) - if( LMIC.dn2Ans ) { - LMIC.frame[end+0] = MCMD_DN2P_ANS; - LMIC.frame[end+1] = LMIC.dn2Ans & ~MCMD_DN2P_ANS_RFU; - end += 2; - LMIC.dn2Ans = 0; - } -#endif // !DISABLE_MCMD_DN2P_SET if( LMIC.devsAns ) { // answer to device status LMIC.frame[end+0] = MCMD_DEVS_ANS; LMIC.frame[end+1] = os_getBattLevel(); @@ -1255,6 +1252,14 @@ static void buildDataFrame (void) { LMIC.adrAckReq = 0; LMIC.adrChanged = 0; } +#if !defined(DISABLE_MCMD_DN2P_SET) + if (LMIC.dn2Ans) { + LMIC.frame[end + 0] = MCMD_DN2P_ANS; + LMIC.frame[end + 1] = LMIC.dn2Ans & ~MCMD_DN2P_ANS_RFU; + end += 2; + LMIC.dn2Ans = 0; + } +#endif // !DISABLE_MCMD_DN2P_SET #if !defined(DISABLE_MCMD_PING_SET) && !defined(DISABLE_PING) if( LMIC.pingSetAns != 0 ) { LMIC.frame[end+0] = MCMD_PING_ANS; @@ -1866,6 +1871,7 @@ void LMIC_reset (void) { void LMIC_init (void) { LMIC.opmode = OP_SHUTDOWN; + LMICbandplan_init(); } diff --git a/src/lmic/lmic.h b/src/lmic/lmic.h index 2145e1f3..62e34406 100644 --- a/src/lmic/lmic.h +++ b/src/lmic/lmic.h @@ -207,6 +207,11 @@ struct lmic_t { // Radio settings TX/RX (also accessed by HAL) ostime_t txend; ostime_t rxtime; + + // LBT info + ostime_t lbt_ticks; // ticks to listen + s1_t lbt_dbmax; // max permissible dB on our channel (eg -80) + u4_t freq; s1_t rssi; s1_t snr; diff --git a/src/lmic/lmic_as923.c b/src/lmic/lmic_as923.c index 13b9d31a..f1e4b101 100644 --- a/src/lmic/lmic_as923.c +++ b/src/lmic/lmic_as923.c @@ -181,6 +181,25 @@ void LMICas923_initDefaultChannels(bit_t join) { LMIC.bands[BAND_CENTI].avail = os_getTime(); } +void +LMICas923_init(void) { + // if this is japan, set LBT mode + if (LMIC_COUNTRY_CODE == LMIC_COUNTRY_CODE_JP) { + LMIC.lbt_ticks = us2osticks(AS923JP_LBT_US); + LMIC.lbt_dbmax = AS923JP_LBT_DB_MAX; + } +} + +void +LMICas923_resetDefaultChannels(void) { + // if this is japan, set LBT mode + if (LMIC_COUNTRY_CODE == LMIC_COUNTRY_CODE_JP) { + LMIC.lbt_ticks = us2osticks(AS923JP_LBT_US); + LMIC.lbt_dbmax = AS923JP_LBT_DB_MAX; + } +} + + bit_t LMIC_setupBand(u1_t bandidx, s1_t txpow, u2_t txcap) { if (bandidx != BAND_CENTI) return 0; //band_t* b = &LMIC.bands[bandidx]; diff --git a/src/lmic/lmic_bandplan.h b/src/lmic/lmic_bandplan.h index 6aa2c743..0c3c5036 100644 --- a/src/lmic/lmic_bandplan.h +++ b/src/lmic/lmic_bandplan.h @@ -140,6 +140,9 @@ # error "LMICbandplan_nextJoinTime() not defined by bandplan" #endif +#if !defined(LMICbandplan_init) +# error "LMICbandplan_init() not defined by bandplan" +#endif // // Things common to lmic.c code // diff --git a/src/lmic/lmic_bandplan_as923.h b/src/lmic/lmic_bandplan_as923.h index 10dd2224..50017f29 100644 --- a/src/lmic/lmic_bandplan_as923.h +++ b/src/lmic/lmic_bandplan_as923.h @@ -52,6 +52,22 @@ LMICas923_isValidBeacon1(const uint8_t *d) { #undef LMICbandplan_isValidBeacon1 #define LMICbandplan_isValidBeacon1(pFrame) LMICas923_isValidBeacon1(pFrame) +// override default for LMICbandplan_resetDefaultChannels +void +LMICas923_resetDefaultChannels(void); + +#undef LMICbandplan_resetDefaultChannels +#define LMICbandplan_resetDefaultChannels() \ + LMICas923_resetDefaultChannels() + +// override default for LMICbandplan_init +void LMICas923_init(void); + +#undef LMICbandplan_init +#define LMICbandplan_init() \ + LMICas923_init() + + // override default for LMICbandplan_isFSK() #undef LMICbandplan_isFSK #define LMICbandplan_isFSK() (/* TX datarate */LMIC.rxsyms == AS923_DR_FSK) diff --git a/src/lmic/lmic_config_preconditions.h b/src/lmic/lmic_config_preconditions.h index cd925003..35e2011d 100644 --- a/src/lmic/lmic_config_preconditions.h +++ b/src/lmic/lmic_config_preconditions.h @@ -72,6 +72,20 @@ Revision history: #define LMIC_REGION_kr921 8 #define LMIC_REGION_in866 9 +// Some regions have country-specific overrides. For generality, we specify +// country codes using the LMIC_COUNTY_CODE_C() macro These values are chosen +// from the 2-letter domain suffixes standardized by ISO-3166-1 alpha2 (see +// https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2). They are therefore +// 16-bit constants. By convention, we use UPPER-CASE letters, thus +// LMIC_COUNTRY_CODE('J', 'P'), not ('j', 'p'). +#define LMIC_COUNTRY_CODE_C(c1, c2) ((c1) * 256 + (c2)) + +// this special code means "no country code defined" +#define LMIC_COUNTRY_CODE_NONE 0 + +// specific countries. Only the ones that are needed by the code are defined. +#define LMIC_COUNTRY_CODE_JP LMIC_COUNTRY_CODE_C('J', 'P') + // include the file that the user is really supposed to edit. But for really strange // ports, this can be suppressed #ifndef ARDUINO_LMIC_PROJECT_CONFIG_H_SUPPRESS diff --git a/src/lmic/lmic_eu_like.h b/src/lmic/lmic_eu_like.h index e6ea820e..f147790d 100644 --- a/src/lmic/lmic_eu_like.h +++ b/src/lmic/lmic_eu_like.h @@ -92,4 +92,7 @@ static inline ostime_t LMICeulike_nextJoinTime(ostime_t now) { } #define LMICbandplan_nextJoinTime(now) LMICeulike_nextJoinTime(now) +#define LMICbandplan_init() \ + do { /* nothing */ } while (0) + #endif // _lmic_eu_like_h_ diff --git a/src/lmic/lmic_us_like.h b/src/lmic/lmic_us_like.h index 7a35b208..66bc549a 100644 --- a/src/lmic/lmic_us_like.h +++ b/src/lmic/lmic_us_like.h @@ -94,5 +94,7 @@ static inline ostime_t LMICeulike_nextJoinTime(ostime_t now) { } #define LMICbandplan_nextJoinTime(now) LMICeulike_nextJoinTime(now) +#define LMICbandplan_init() \ + do { /* nothing */ } while (0) #endif // _lmic_us_like_h_ diff --git a/src/lmic/lorabase.h b/src/lmic/lorabase.h index 7368329f..3e57a9cd 100644 --- a/src/lmic/lorabase.h +++ b/src/lmic/lorabase.h @@ -483,7 +483,8 @@ enum { MCMD_LADR_ANS_CHACK = 0x01, // 0=unknown channel enabled }; enum { - MCMD_DN2P_ANS_RFU = 0xFC, // RFU bits + MCMD_DN2P_ANS_RFU = 0xF8, // RFU bits + MCMD_DN2P_ANS_RX1DrOffsetAck = 0x04, // 0=dr2 not allowed MCMD_DN2P_ANS_DRACK = 0x02, // 0=unknown data rate MCMD_DN2P_ANS_CHACK = 0x01, // 0=unknown channel enabled }; diff --git a/src/lmic/lorabase_as923.h b/src/lmic/lorabase_as923.h index aa5e62e3..2b95249c 100644 --- a/src/lmic/lorabase_as923.h +++ b/src/lmic/lorabase_as923.h @@ -74,4 +74,7 @@ enum { DR_PAGE_AS923 = 0x10 * (LMIC_REGION_as923 - 1) }; enum { AS923_LMIC_REGION_EIRP = 1 }; // region uses EIRP -#endif /* _lorabase_as923_h_ */ \ No newline at end of file +enum { AS923JP_LBT_US = 125 }; // microseconds of LBT time +enum { AS923JP_LBT_DB_MAX = -80 }; // maximum channel strength + +#endif /* _lorabase_as923_h_ */ diff --git a/src/lmic/oslmic.h b/src/lmic/oslmic.h index 9e190333..1cef61bf 100644 --- a/src/lmic/oslmic.h +++ b/src/lmic/oslmic.h @@ -73,6 +73,8 @@ typedef struct rxsched_t rxsched_t; typedef struct bcninfo_t bcninfo_t; typedef const u1_t* xref2cu1_t; typedef u1_t* xref2u1_t; +typedef s4_t ostime_t; + #define TYPEDEF_xref2rps_t typedef rps_t* xref2rps_t #define TYPEDEF_xref2rxsched_t typedef rxsched_t* xref2rxsched_t #define TYPEDEF_xref2chnldef_t typedef chnldef_t* xref2chnldef_t @@ -96,6 +98,15 @@ u1_t radio_rand1 (void); #define DEFINE_LMIC struct lmic_t LMIC #define DECLARE_LMIC extern struct lmic_t LMIC +typedef struct oslmic_radio_rssi_s oslmic_radio_rssi_t; + +struct oslmic_radio_rssi_s { + s2_t min_rssi; + s2_t max_rssi; + s2_t mean_rssi; + u2_t n_rssi; +}; + int radio_init (void); void radio_irq_handler (u1_t dio); void os_init (void); @@ -103,6 +114,7 @@ int os_init_ex (const void *pPinMap); void os_runloop (void); void os_runloop_once (void); u1_t radio_rssi (void); +void radio_monitor_rssi(ostime_t n, oslmic_radio_rssi_t *pRssi); //================================================================================ @@ -120,8 +132,6 @@ u1_t radio_rssi (void); #error Illegal OSTICKS_PER_SEC - must be in range [10000:64516]. One tick must be 15.5us .. 100us long. #endif -typedef s4_t ostime_t; - #if !HAS_ostick_conv #define us2osticks(us) ((ostime_t)( ((int64_t)(us) * OSTICKS_PER_SEC) / 1000000)) #define ms2osticks(ms) ((ostime_t)( ((int64_t)(ms) * OSTICKS_PER_SEC) / 1000)) diff --git a/src/lmic/radio.c b/src/lmic/radio.c index c3114e1b..673bbd76 100644 --- a/src/lmic/radio.c +++ b/src/lmic/radio.c @@ -195,7 +195,17 @@ #define RXLORA_RXMODE_RSSI_REG_MODEM_CONFIG2 0x74 #endif +//----------------------------------------- +// Parameters for RSSI monitoring +#define SX127X_FREQ_LF_MAX 525000000 // per datasheet 6.3 +// per datasheet 5.5.3: +#define SX127X_RSSI_ADJUST_LF -164 // add to rssi value to get dB (LF) +#define SX127X_RSSI_ADJUST_HF -157 // add to rssi value to get dB (HF) + +// per datasheet 2.5.2 (but note that we ought to ask Semtech to confirm, because +// datasheet is unclear). +#define SX127X_RX_POWER_UP us2osticks(500) // delay this long to let the receiver power up. // ---------------------------------------- // Constants for radio registers @@ -549,7 +559,58 @@ static void txlora () { // start transmitter (buf=LMIC.frame, len=LMIC.dataLen) static void starttx () { - ASSERT( (readReg(RegOpMode) & OPMODE_MASK) == OPMODE_SLEEP ); + u1_t const rOpMode = readReg(RegOpMode); + + // originally, this code ASSERT()ed, but asserts are both bad and + // blunt instruments. If we see that we're not in sleep mode, + // force sleep (because we might have to switch modes) + if ((rOpMode & OPMODE_MASK) != OPMODE_SLEEP) { +#if LMIC_DEBUG_LEVEL > 0 + LMIC_DEBUG_PRINTF("?%s: OPMODE != OPMODE_SLEEP: %#02x\n", __func__, rOpMode); +#endif + opmode(OPMODE_SLEEP); + hal_waitUntil(os_getTime() + ms2osticks(1)); + } + + if (LMIC.lbt_ticks > 0) { + oslmic_radio_rssi_t rssi; +#if LMIC_DEBUG_LEVEL > 1 + LMIC_DEBUG_PRINTF("%lu: scan RSSI for %u osticks\n", + os_getTime(), + LMIC.lbt_ticks + ); +#endif + radio_monitor_rssi(LMIC.lbt_ticks, &rssi); + + if (rssi.max_rssi >= LMIC.lbt_dbmax) { +#if LMIC_DEBUG_LEVEL > 0 + u1_t sf = getSf(LMIC.rps) + 6; // 1 == SF7 + u1_t bw = getBw(LMIC.rps); + u1_t cr = getCr(LMIC.rps); + LMIC_DEBUG_PRINTF("%lu: freq=%lu, SF=%d, BW=%d, CR=4/%d, interfering signal %d > %d dB\n", + os_getTime(), + LMIC.freq, + sf, + bw == BW125 ? 125 : (bw == BW250 ? 250 : 500), + cr == CR_4_5 ? 5 : (cr == CR_4_6 ? 6 : (cr == CR_4_7 ? 7 : 8)), + rssi.max_rssi, + LMIC.lbt_dbmax + ); +#endif + // complete the request by scheduling the job + os_setCallback(&LMIC.osjob, LMIC.osjob.func); + return; + } + +#if LMIC_DEBUG_LEVEL > 1 + LMIC_DEBUG_PRINTF("%lu: freq=%lu, interfering signal %d < %d dB\n", + os_getTime(), LMIC.freq, + rssi.max_rssi, + LMIC.lbt_dbmax + ); +#endif + } + if(getSf(LMIC.rps) == FSK) { // FSK modem txfsk(); } else { // LoRa modem @@ -791,6 +852,70 @@ u1_t radio_rssi () { return r; } +// monitor rssi for specified number of ostime_t ticks, and return statistics +// This puts the radio into RX continuous mode, waits long enough for the +// oscillators to start and the PLL to lock, and then measures for the specified +// period of time. The radio is then returned to idle. +// +// RSSI returned is expressed in units of dB, and is offset according to the +// current radio setting per section 5.5.5 of Semtech 1276 datasheet. +void radio_monitor_rssi(ostime_t nTicks, oslmic_radio_rssi_t *pRssi) { + uint8_t rssiMax, rssiMin; + uint16_t rssiSum; + uint16_t rssiN; + + int rssiAdjust; + ostime_t tBegin; + int notDone; + + rxlora(RXMODE_SCAN); + + // while we're waiting for the PLLs to spin up, determine which + // band we're in and choose the base RSSI. + if (LMIC.freq > SX127X_FREQ_LF_MAX) { + rssiAdjust = SX127X_RSSI_ADJUST_HF; + } else { + rssiAdjust = SX127X_RSSI_ADJUST_LF; + } + rssiAdjust += hal_getRssiCal(); + + // zero the results + rssiMax = 255; + rssiMin = 0; + rssiSum = 0; + rssiN = 0; + + // wait for PLLs + hal_waitUntil(os_getTime() + SX127X_RX_POWER_UP); + + // scan for the desired time. + tBegin = os_getTime(); + rssiMax = 0; + do { + ostime_t now; + + u1_t rssiNow = readReg(LORARegRssiValue); + + if (rssiMax < rssiNow) + rssiMax = rssiNow; + if (rssiNow < rssiMin) + rssiMin = rssiNow; + rssiSum += rssiNow; + ++rssiN; + now = os_getTime(); + notDone = now - (tBegin + nTicks) < 0; + } while (notDone); + + // put radio back to sleep + opmode(OPMODE_SLEEP); + + // compute the results + pRssi->max_rssi = (s2_t) (rssiMax + rssiAdjust); + pRssi->min_rssi = (s2_t) (rssiMin + rssiAdjust); + pRssi->mean_rssi = (s2_t) (rssiAdjust + ((rssiSum + (rssiN >> 1)) / rssiN)); + pRssi->n_rssi = rssiN; +} + static CONST_TABLE(u2_t, LORA_RXDONE_FIXUP)[] = { [FSK] = us2osticks(0), // ( 0 ticks) [SF7] = us2osticks(0), // ( 0 ticks)