diff --git a/UNITTESTS/features/lorawan/loraphy/unittest.cmake b/UNITTESTS/features/lorawan/loraphy/unittest.cmake index c1abde4e46a..b3c8a5854a4 100644 --- a/UNITTESTS/features/lorawan/loraphy/unittest.cmake +++ b/UNITTESTS/features/lorawan/loraphy/unittest.cmake @@ -37,5 +37,5 @@ set(unittest-test-sources ) -set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DMBED_CONF_LORA_UPLINK_PREAMBLE_LENGTH=8 -DMBED_CONF_LORA_DUTY_CYCLE_ON_JOIN=true") -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DMBED_CONF_LORA_UPLINK_PREAMBLE_LENGTH=8 -DMBED_CONF_LORA_DUTY_CYCLE_ON_JOIN=true") +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DMBED_CONF_LORA_UPLINK_PREAMBLE_LENGTH=8 -DMBED_CONF_LORA_DUTY_CYCLE_ON_JOIN=true -DMBED_CONF_LORA_WAKEUP_TIME=5") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DMBED_CONF_LORA_UPLINK_PREAMBLE_LENGTH=8 -DMBED_CONF_LORA_DUTY_CYCLE_ON_JOIN=true -DMBED_CONF_LORA_WAKEUP_TIME=5") diff --git a/UNITTESTS/stubs/LoRaPHY_stub.cpp b/UNITTESTS/stubs/LoRaPHY_stub.cpp index 56862af5dfc..01b7312b7c3 100644 --- a/UNITTESTS/stubs/LoRaPHY_stub.cpp +++ b/UNITTESTS/stubs/LoRaPHY_stub.cpp @@ -168,9 +168,10 @@ uint8_t LoRaPHY::verify_link_ADR_req(verify_adr_params_t *verify_params, return LoRaPHY_stub::uint8_value; } -void LoRaPHY::get_rx_window_params(double t_symb, uint8_t min_rx_symb, - uint32_t rx_error, uint32_t wakeup_time, - uint32_t *window_timeout, int32_t *window_offset) +void LoRaPHY::get_rx_window_params(float t_symbol, uint8_t min_rx_symbols, + float rx_error, float wakeup_time, + uint32_t *window_length, int32_t *window_offset, + uint8_t phy_dr) { } diff --git a/features/lorawan/LoRaWANStack.cpp b/features/lorawan/LoRaWANStack.cpp index 0f125c5448b..79f899ef2ad 100644 --- a/features/lorawan/LoRaWANStack.cpp +++ b/features/lorawan/LoRaWANStack.cpp @@ -614,6 +614,8 @@ void LoRaWANStack::post_process_tx_with_reception() _loramac.get_device_class() == CLASS_A ? "A" : "C"); _ctrl_flags &= ~TX_DONE_FLAG; _ctrl_flags |= RETRY_EXHAUSTED_FLAG; + _loramac.post_process_mcps_req(); + make_tx_metadata_available(); state_controller(DEVICE_STATE_STATUS_CHECK); } } diff --git a/features/lorawan/lorastack/mac/LoRaMac.cpp b/features/lorawan/lorastack/mac/LoRaMac.cpp index 05c58a97cf5..a971fb9cd4f 100644 --- a/features/lorawan/lorastack/mac/LoRaMac.cpp +++ b/features/lorawan/lorastack/mac/LoRaMac.cpp @@ -166,11 +166,12 @@ void LoRaMac::post_process_mcps_req() _params.is_node_ack_requested = false; _mcps_confirmation.ack_received = false; _mcps_indication.is_ack_recvd = false; - _params.ul_frame_counter++; - _params.adr_ack_counter++; } else { _mcps_confirmation.status = LORAMAC_EVENT_INFO_STATUS_ERROR; } + + _params.ul_frame_counter++; + _params.adr_ack_counter++; } else { //UNCONFIRMED or PROPRIETARY _params.ul_frame_counter++; diff --git a/features/lorawan/lorastack/phy/LoRaPHY.cpp b/features/lorawan/lorastack/phy/LoRaPHY.cpp index c995cd211be..29414ee7aa0 100644 --- a/features/lorawan/lorastack/phy/LoRaPHY.cpp +++ b/features/lorawan/lorastack/phy/LoRaPHY.cpp @@ -32,8 +32,9 @@ SPDX-License-Identifier: BSD-3-Clause #define BACKOFF_DC_1_HOUR 100 #define BACKOFF_DC_10_HOURS 1000 #define BACKOFF_DC_24_HOURS 10000 - -#define CHANNELS_IN_MASK 16 +#define MAX_PREAMBLE_LENGTH 8.0f +#define TICK_GRANULARITY_JITTER 1.0f +#define CHANNELS_IN_MASK 16 LoRaPHY::LoRaPHY() : _radio(NULL), @@ -388,23 +389,56 @@ uint8_t LoRaPHY::verify_link_ADR_req(verify_adr_params_t *verify_params, return status; } -double LoRaPHY::compute_symb_timeout_lora(uint8_t phy_dr, uint32_t bandwidth) +float LoRaPHY::compute_symb_timeout_lora(uint8_t phy_dr, uint32_t bandwidth) { - return ((double)(1 << phy_dr) / (double) bandwidth) * 1000; + // in milliseconds + return ((float)(1 << phy_dr) / (float) bandwidth * 1000); } -double LoRaPHY::compute_symb_timeout_fsk(uint8_t phy_dr) +float LoRaPHY::compute_symb_timeout_fsk(uint8_t phy_dr) { - return (8.0 / (double) phy_dr); // 1 symbol equals 1 byte + return (8.0f / (float) phy_dr); // 1 symbol equals 1 byte } -void LoRaPHY::get_rx_window_params(double t_symb, uint8_t min_rx_symb, - uint32_t rx_error, uint32_t wakeup_time, - uint32_t *window_timeout, int32_t *window_offset) + +void LoRaPHY::get_rx_window_params(float t_symb, uint8_t min_rx_symb, + float error_fudge, float wakeup_time, + uint32_t *window_length, int32_t *window_offset, + uint8_t phy_dr) { - // Computed number of symbols - *window_timeout = MAX((uint32_t) ceil(((2 * min_rx_symb - 8) * t_symb + 2 * rx_error) / t_symb), min_rx_symb); - *window_offset = (int32_t) ceil((4.0 * t_symb) - ((*window_timeout * t_symb) / 2.0) - wakeup_time); + float target_rx_window_offset; + float window_len_in_ms; + + if (phy_params.fsk_supported && phy_dr == phy_params.max_rx_datarate) { + min_rx_symb = MAX_PREAMBLE_LENGTH; + } + + // We wish to be as close as possible to the actual start of data, i.e., + // we are interested in the preamble symbols which are at the tail of the + // preamble sequence. + target_rx_window_offset = (MAX_PREAMBLE_LENGTH - min_rx_symb) * t_symb; //in ms + + // Actual window offset in ms in response to timing error fudge factor and + // radio wakeup/turned around time. + *window_offset = floor(target_rx_window_offset - error_fudge - wakeup_time); + + // possible wait for next symbol start if we start inside the preamble + float possible_wait_for_symb_start = MIN(t_symb, + ((2 * error_fudge) + wakeup_time + TICK_GRANULARITY_JITTER)); + + // how early we might start reception relative to transmit start (so negative if before transmit starts) + float earliest_possible_start_time = *window_offset - error_fudge - TICK_GRANULARITY_JITTER; + + // time in (ms) we may have to wait for the other side to start transmission + float possible_wait_for_transmit = -earliest_possible_start_time; + + // Minimum reception time plus extra time (in ms) we may have turned on before the + // other side started transmission + window_len_in_ms = (min_rx_symb * t_symb) + MAX(possible_wait_for_transmit, possible_wait_for_symb_start); + + // Setting the window_length in terms of 'symbols' for LoRa modulation or + // in terms of 'bytes' for FSK + *window_length = (uint32_t) ceil(window_len_in_ms / t_symb); } int8_t LoRaPHY::compute_tx_power(int8_t tx_power_idx, float max_eirp, @@ -791,7 +825,7 @@ void LoRaPHY::compute_rx_win_params(int8_t datarate, uint8_t min_rx_symbols, uint32_t rx_error, rx_config_params_t *rx_conf_params) { - double t_symbol = 0.0; + float t_symbol = 0.0; // Get the datarate, perform a boundary check rx_conf_params->datarate = MIN(datarate, phy_params.max_rx_datarate); @@ -811,9 +845,9 @@ void LoRaPHY::compute_rx_win_params(int8_t datarate, uint8_t min_rx_symbols, rx_conf_params->frequency = phy_params.channels.channel_list[rx_conf_params->channel].frequency; } - - get_rx_window_params(t_symbol, min_rx_symbols, rx_error, RADIO_WAKEUP_TIME, - &rx_conf_params->window_timeout, &rx_conf_params->window_offset); + get_rx_window_params(t_symbol, min_rx_symbols, (float) rx_error, MBED_CONF_LORA_WAKEUP_TIME, + &rx_conf_params->window_timeout, &rx_conf_params->window_offset, + rx_conf_params->datarate); } bool LoRaPHY::rx_config(rx_config_params_t *rx_conf) @@ -847,13 +881,13 @@ bool LoRaPHY::rx_config(rx_config_params_t *rx_conf) // Radio configuration if (dr == DR_7 && phy_params.fsk_supported) { modem = MODEM_FSK; - _radio->set_rx_config(modem, 50000, phy_dr * 1000, 0, 83333, 5, + _radio->set_rx_config(modem, 50000, phy_dr * 1000, 0, 83333, MAX_PREAMBLE_LENGTH, rx_conf->window_timeout, false, 0, true, 0, 0, false, rx_conf->is_rx_continuous); } else { modem = MODEM_LORA; _radio->set_rx_config(modem, rx_conf->bandwidth, phy_dr, 1, 0, - MBED_CONF_LORA_DOWNLINK_PREAMBLE_LENGTH, + MAX_PREAMBLE_LENGTH, rx_conf->window_timeout, false, 0, false, 0, 0, true, rx_conf->is_rx_continuous); } @@ -899,8 +933,8 @@ bool LoRaPHY::tx_config(tx_config_params_t *tx_conf, int8_t *tx_power, // High Speed FSK channel modem = MODEM_FSK; _radio->set_tx_config(modem, phy_tx_power, 25000, bandwidth, - phy_dr * 1000, 0, 5, false, true, 0, 0, false, - 3000); + phy_dr * 1000, 0, MBED_CONF_LORA_UPLINK_PREAMBLE_LENGTH, + false, true, 0, 0, false, 3000); } else { modem = MODEM_LORA; _radio->set_tx_config(modem, phy_tx_power, 0, bandwidth, phy_dr, 1, diff --git a/features/lorawan/lorastack/phy/LoRaPHY.h b/features/lorawan/lorastack/phy/LoRaPHY.h index 76bad8993c5..d6be36b1dc9 100644 --- a/features/lorawan/lorastack/phy/LoRaPHY.h +++ b/features/lorawan/lorastack/phy/LoRaPHY.h @@ -202,40 +202,63 @@ class LoRaPHY : private mbed::NonCopyable { /** Computing Receive Windows * - * For more details please consult the following document, chapter 3.1.2. - * http://www.semtech.com/images/datasheet/SX1272_settings_for_LoRaWAN_v2.0.pdf - * or - * http://www.semtech.com/images/datasheet/SX1276_settings_for_LoRaWAN_v2.0.pdf - * - * Downlink start: T = Tx + 1s (+/- 20 us) - * | - * TRxEarly | TRxLate - * | | | - * | | +---+---+---+---+---+---+---+---+ - * | | | Latest Rx window | - * | | +---+---+---+---+---+---+---+---+ - * | | | + * The algorithm tries to calculate the length of receive windows (i.e., + * the minimum time it should remain to acquire a lock on the Preamble + * for synchronization) and the error offset which compensates for the system + * timing errors. Basic idea behind the algorithm is to optimize for the + * reception of last 'min_rx_symbols' symbols out of transmitted Premable + * symbols. The algorithm compensates for the clock drifts, tick granularity + * and system wake up time (from sleep state) by opening the window early for + * the lower SFs. For higher SFs, the symbol time is large enough that we can + * afford to open late (hence the positive offset). + * The table below shows the calculated values for SF7 to SF12 with 125 kHz + * bandwidth. + * + * +----+-----+----------+---------+-------------------------+----------------------+-------------------------+ + * | SF | BW (kHz) | rx_error (ms) | wake_up (ms) | min_rx_symbols | window_timeout(symb) | window_offset(ms) | + * +----+-----+----------+---------+-------------------------+----------------------+-------------------------+ + * | 7 | 125 | 5 | 5 | 5 | 18 | -7 | + * | 8 | 125 | 5 | 5 | 5 | 10 | -4 | + * | 9 | 125 | 5 | 5 | 5 | 6 | 2 | + * | 10 | 125 | 5 | 5 | 5 | 6 | 14 | + * | 11 | 125 | 5 | 5 | 5 | 6 | 39 | + * | 12 | 125 | 5 | 5 | 5 | 6 | 88 | + * +----+-----+----------+---------+-------------------------+----------------------+-------------------------+ + * + * For example for SF7, the receive window will open at downlink start time + * plus the offset calculated and will remain open for the length window_timeout. + * + * Symbol time = 1.024 ms + * Downlink start: T = Tx + 1s (+/- 20 us) + * | + * | + * | + * | + * | + * +---+---+---+---+---+---+---+---+ + * | 8 Preamble Symbols | + * +---+---+---+---+---+---+---+---+ + * | RX Window start time = T +/- Offset + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * | | | | | | | | | | | | | | | | | | | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * + * Similarly for SF12: + * + * Symbol time = 32.768 ms + * Downlink start: T = Tx + 1s (+/- 20 us) + * | + * | + * | + * | + * | * +---+---+---+---+---+---+---+---+ - * | Earliest Rx window | + * | 8 Preamble Symbols | * +---+---+---+---+---+---+---+---+ - * | - * +---+---+---+---+---+---+---+---+ - *Downlink preamble 8 symbols | | | | | | | | | - * +---+---+---+---+---+---+---+---+ - * - * Worst case Rx window timings - * - * TRxLate = DEFAULT_MIN_RX_SYMBOLS * tSymbol - RADIO_WAKEUP_TIME - * TRxEarly = 8 - DEFAULT_MIN_RX_SYMBOLS * tSymbol - RxWindowTimeout - RADIO_WAKEUP_TIME - * - * TRxLate - TRxEarly = 2 * DEFAULT_SYSTEM_MAX_RX_ERROR - * - * RxOffset = ( TRxLate + TRxEarly ) / 2 - * - * RxWindowTimeout = ( 2 * DEFAULT_MIN_RX_SYMBOLS - 8 ) * tSymbol + 2 * DEFAULT_SYSTEM_MAX_RX_ERROR - * RxOffset = 4 * tSymbol - RxWindowTimeout / 2 - RADIO_WAKE_UP_TIME - * - * The minimum value of RxWindowTimeout must be 5 symbols which implies that the system always tolerates at least an error of 1.5 * tSymbol. + * | RX Window start time = T +/- Offset + * +---+---+---+---+---+---+ + * | | | | | | | + * +---+---+---+---+---+---+ */ /*! * Computes the RX window timeout and offset. @@ -597,9 +620,10 @@ class LoRaPHY : private mbed::NonCopyable { /** * Computes the RX window timeout and the RX window offset. */ - void get_rx_window_params(double t_symbol, uint8_t min_rx_symbols, - uint32_t rx_error, uint32_t wakeup_time, - uint32_t *window_timeout, int32_t *window_offset); + void get_rx_window_params(float t_symbol, uint8_t min_rx_symbols, + float rx_error, float wakeup_time, + uint32_t *window_length, int32_t *window_offset, + uint8_t phy_dr); /** * Computes the txPower, based on the max EIRP and the antenna gain. @@ -632,12 +656,12 @@ class LoRaPHY : private mbed::NonCopyable { /** * Computes the symbol time for LoRa modulation. */ - double compute_symb_timeout_lora(uint8_t phy_dr, uint32_t bandwidth); + float compute_symb_timeout_lora(uint8_t phy_dr, uint32_t bandwidth); /** * Computes the symbol time for FSK modulation. */ - double compute_symb_timeout_fsk(uint8_t phy_dr); + float compute_symb_timeout_fsk(uint8_t phy_dr); protected: LoRaRadio *_radio; diff --git a/features/lorawan/mbed_lib.json b/features/lorawan/mbed_lib.json index e4eefd00e51..30e130b3645 100644 --- a/features/lorawan/mbed_lib.json +++ b/features/lorawan/mbed_lib.json @@ -70,24 +70,28 @@ "value": true }, "max-sys-rx-error": { - "help": "Maximum timing error of the receiver in ms. The receiver will turn on in [-RxError : + RxError]", - "value": 10 + "help": "Max. timing error fudge. The receiver will turn on in [-RxError : + RxError]", + "value": 5 + }, + "wakeup-time": { + "help": "Time in (ms) the platform takes to wakeup from sleep/deep sleep state. This number is platform dependent", + "value": 5 }, "downlink-preamble-length": { - "help": "Number of preamble symbols need to be captured (out of 8) for successful demodulation", - "value": 5 + "help": "Number of whole preamble symbols needed to have a firm lock on the signal.", + "value": 5 }, "uplink-preamble-length": { - "help": "Number of preamble symbols to transmit. Must be <= 8", - "value": 8 + "help": "Number of preamble symbols to transmit. Default: 8", + "value": 8 }, "fsb-mask": { - "help": "FSB mask for upstream [Only for US915 & AU915] Check lorawan/FSB_Usage.txt for more details", - "value": "{0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x00FF}" + "help": "FSB mask for upstream [Only for US915 & AU915] Check lorawan/FSB_Usage.txt for more details", + "value": "{0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x00FF}" }, "fsb-mask-china": { - "help": "FSB mask for upstream [CN470 PHY] Check lorawan/FSB_Usage.txt for more details", - "value": "{0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF}" + "help": "FSB mask for upstream [CN470 PHY] Check lorawan/FSB_Usage.txt for more details", + "value": "{0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF}" } } } diff --git a/features/lorawan/system/lorawan_data_structures.h b/features/lorawan/system/lorawan_data_structures.h index e35d5427617..dafcb29ff18 100644 --- a/features/lorawan/system/lorawan_data_structures.h +++ b/features/lorawan/system/lorawan_data_structures.h @@ -44,9 +44,6 @@ typedef uint32_t lorawan_time_t; #endif -// Radio wake-up time from sleep - unit ms. -#define RADIO_WAKEUP_TIME 1 - /*! * Sets the length of the LoRaMAC footer field. * Mainly indicates the MIC field length. @@ -1259,8 +1256,8 @@ typedef struct { /*! * LoRaMac reception windows delay - * \remark normal frame: RxWindowXDelay = ReceiveDelayX - RADIO_WAKEUP_TIME - * join frame : RxWindowXDelay = JoinAcceptDelayX - RADIO_WAKEUP_TIME + * \remark normal frame: RxWindowXDelay = ReceiveDelayX - Offset + * join frame : RxWindowXDelay = JoinAcceptDelayX - Offset */ uint32_t rx_window1_delay; uint32_t rx_window2_delay;