LoRa Basics™ Modem User Manual v1.0 documentation

Porting

This chapter provides some basic information about porting LoRa Basics™ Modem to a new MCU.

Overview

LoRa Basics Modem runs on top of a hardware abstraction layer (HAL), meant to facilitate porting. In what follows, LBM_DIR refers to the directory containing LoRa Basics Modem, which is named “lora_basics_modem” in the software developer kit (SDK).

MCU Requirements

Currently, LoRa Basics Modem has only been tested on 32-bit microcontrollers. The following MCU features are required:

  • Software MCU reset.

  • A timer with 1ms resolution (timer accuracy compensation is possible by widening the reception windows).

  • SPI controller.

  • A random number generator.

  • Non-volatile storage for modem state storage.

  • A dedicated (non-shared) GPIO MCU interrupt for the transceiver is recommended.

Worst-case stack usage is currently unknown. A simple example performing a join and a few uplinks uses approximately 2kB of stack RAM.

RAM and Flash requirements for STM32L476 are listed below. These values were determined by modifying the apps/lorawan/makefile, and building, as follows:

APP_TRACE ?= no
MODEM_TRACE ?= no
DEBUG ?= 0
OPT ?= -O2

$ make REGION=EU_868,US_915,CN_470_RP_1_0,CN_470

 text    data     bss     dec     hex filename
  522       0       0     522     20a lr1110_bootloader.o
 1242       0       0    1242     4da lr1110_crypto_engine.o
   15       0       0      15       f lr1110_driver_version.o
 2559       0       0    2559     9ff lr1110_radio.o
  486       0       0     486     1e6 lr1110_regmem.o
 1540       0       0    1540     604 lr1110_system.o
 2140       0       0    2140     85c lr1110_wifi.o
 1670       0       0    1670     686 lr1110_gnss.o
 2414       0       0    2414     96e ral_lr1110.o
  236       0       0     236      ec ralf_lr1110.o
 2755       0       0    2755     ac3 radio_planner.o
   32       0       0      32      20 radio_planner_hal.o
  564       0       0     564     234 smtc_modem_api_lr1110_crypto_engine.o
  134       0       0     134      86 smtc_modem_api_lr1110_system.o
 1618       0    2780    4398    112e lorawan_api.o
  760       0       0     760     2f8 dm_downlink.o
 4727      17     394    5138    1412 modem_context.o
 4590       0    1826    6416    1910 smtc_modem.o
 2392       0     640    3032     bd8 smtc_modem_test.o
  756       0       0     756     2f4 fifo_ctrl.o
   52       0       0      52      34 modem_utilities.o
  106       0       0     106      6a smtc_modem_services_hal.o
  950       0       0     950     3b6 smtc_clock_sync.o
  912       0       0     912     390 lorawan_certification.o
 3244       3     635    3882     f2a modem_supervisor.o
   58       0       0      58      3a almanac_update.o
  230       0       0     230      e6 stream.o
 1882       0       0    1882     75a rose.o
  810       0       0     810     32a alc_sync.o
 1334       0       0    1334     536 file_upload.o
 1161       0      24    1185     4a1 lr1110_se.o
  936      16       0     952     3b8 smtc_modem_crypto.o
 2971       0       0    2971     b9b region_cn_470.o
 1601       0       0    1601     641 region_cn_470_rp_1_0.o
 1555       0       0    1555     613 region_eu_868.o
 2565       0       0    2565     a05 region_us_915.o
 7798       0       0    7798    1e76 lr1_stack_mac_layer.o
 2764       0       0    2764     acc lr1mac_core.o
  606       0       0     606     25e lr1mac_utilities.o
 5908       0       0    5908    1714 smtc_real.o
 1313       0       0    1313     521 smtc_duty_cycle.o
  836       0       0     836     344 smtc_lbt.o
 1980       8       0    1988     7c4 lr1mac_class_c.o
72724      44    6299   79067   134db (TOTALS)

System Design Considerations

When designing hardware, consider that LoRa Basics Modem is designed to use the transceiver’s DIO1 pad as an interrupt source.

There are two principal interrupt sources that interact with LoRa Basics Modem: a timer interrupt, and a radio interrupt. The system interrupt priorities must be configured in a way that the timer and radio interrupts do not interrupt one another, or nest.

The current implementation of LoRa Basics Modem was designed to perform certain radio operations in interrupt context. For this reason, HAL API commands are provided to disable and enable these two interrupt sources. Therefore, when designing hardware to run LoRa Basics Modem, it is recommended that the MCU GPIO lines you select for the transceiver’s DIO1 interrupt request line do not share an MCU interrupt flag with other GPIO lines that are used for any timing-critical hardware. If this is done, it will still be possible to react quickly to these interrupt sources if necessary.

Because the timer and radio interrupt service routines may perform radio operations over the transceiver SPI bus, it is likely that interrupt handlers will reconfigure the MCU hardware SPI controller. If the MCU hardware SPI controller is used to communicate with other devices, keep in mind that timer or radio interrupts could occur, interfering with communication. In certain circumstances, it may be possible to coordinate the SPI communication between the various devices, but that is beyond the scope of this document.

The recommendation is that the radio should have exclusive use of its MCU hardware SPI controller device.

Note

Some of the limitations described above may be relaxed in a future LoRa Basics Modem release.

HAL Implementation

Porting LoRa Basics Modem to a new architecture requires implementing the API commands described by the prototypes in the following header file:

LBM_DIR/smtc_modem_hal/smtc_modem_hal.h

smtc_modem_hal_reset_mcu()

void smtc_modem_hal_reset_mcu(void);
Brief

LoRa Basics Modem may need to reset the MCU. For instance, on initial startup, or if the state stored in non-volatile memory is corrupt, a fresh configuration will be written to non-volatile memory, and the MCU will be reset.

smtc_modem_hal_reload_wdog()

void smtc_modem_hal_reload_wdog(void);
Brief

If your HAL implementation configures a watchdog timer, then you may also wish to implement this to reload the watchdog timer. Currently, the only code in LoRa Basics Modem that actually calls this HAL API command is the test code in smtc_modem_test.c.

smtc_modem_hal_get_time_in_s()

uint32_t smtc_modem_hal_get_time_in_s(void);
Brief

LoRa Basics Modem uses this command to help perform various LoRaWAN® activities that do not have significant time accuracy requirements, such as nb_trans retransmissions.

Returns

The current system uptime in seconds.

smtc_modem_hal_get_time_compensation_in_s()

int32_t smtc_modem_hal_get_time_compensation_in_s(void);
Brief

Suppose that, due to MCU clock inaccuracy, the principal time source used for smtc_modem_hal_get_time_in_s() significantly lags or leads the real time. If the MCU HAL developer is able to quantify this deviation and calculate an integer number of seconds that will additively correct the time source, it should be returned by this HAL API command. Otherwise, this command should return the value 0.

Returns

Additive correction of the time source. Returns zero, if unknown.

smtc_modem_hal_get_compensated_time_in_s()

uint32_t smtc_modem_hal_get_compensated_time_in_s(void);
Brief

This command should be implemented as follows:

uint32_t smtc_modem_hal_get_compensated_time_in_s()
{
    return smtc_modem_hal_get_time_compensation_in_s() + smtc_modem_hal_get_time_in_s();
}

Note

In the future, this HAL API command may be removed.

Note

The ALCSync service can be used to obtain accurate time from the network GPS clock. Currently, the ALCSync implementation is the only LoRa Basics Modem code that uses the compensated time, as determined above. This may seem unnecessary, since the purpose of ALCSync is to provide an accurate clock. However, if the time is accurately compensated by smtc_modem_hal_get_time_compensation_in_s() and smtc_modem_hal_get_compensated_time_in_s(), ALCSync will require less network activity to keep the clock perfectly synchronized.

Returns

Additive correction of the time source. Returns zero, if unknown.

smtc_modem_hal_get_time_in_ms()

uint32_t smtc_modem_hal_get_time_in_ms(void);
Brief

Currently, this command is used to timestamp radio interrupts and to busy wait until it is time to open an RX window.

Returns

The system uptime, in milliseconds.

smtc_modem_hal_start_timer()

void smtc_modem_hal_start_timer(
    uint32_t milliseconds,
    void (*callback)(void* context),
    void* context
    );
Brief

This HAL API command should start a timer that will expire at the requested time. Upon expiration, it should call the provided callback, and provide the provided callback with context as its sole argument.

The current design of LoRa Basics Modem has only been tested in the case where the provided callback is executed in interrupt context, with interrupts disabled.

Note

This callback may communicate with the radio using the MCU SPI device.

Parameters

[in]

milliseconds

Number of milliseconds before callback execution

[in]

callback

Callback to execute

[in]

context

Argument that will be passed to callback

smtc_modem_hal_stop_timer()

void smtc_modem_hal_stop_timer(void);
Brief

This HAL API command should stop the timer that may have been started with smtc_modem_hal_start_timer(). It is possible that this command will be called when the timer is not running.

smtc_modem_hal_disable_modem_irq()

void smtc_modem_hal_disable_modem_irq(void);
Brief

This HAL API command must disable the two interrupt sources that execute LoRa Basics Modem code: the timer, and the transceiver DIO1 interrupt source. Please also refer to System Design Considerations.

smtc_modem_hal_enable_modem_irq()

void smtc_modem_hal_enable_modem_irq(void);
Brief

This HAL API command must enable the two interrupt sources that execute LoRa Basics Modem code: the timer, and the transceiver DIO1 interrupt source. Please also refer to System Design Considerations.

smtc_modem_hal_context_restore()

void smtc_modem_hal_context_restore(
    const modem_context_type_t ctx_type,
    uint8_t* buffer,
    uint32_t size
    );
Brief

This restores to RAM a data structure of type ctx_type that has previously been stored to non-volatile memory by calling smtc_modem_hal_context_store().

Parameters

[in]

ctx_type

Type of modem context that needs to be restored

[out]

buffer

Buffer pointer to write to

[in]

size

Buffer size to read in bytes

smtc_modem_hal_context_store()

void smtc_modem_hal_context_store(
    const modem_context_type_t ctx_type,
    const uint8_t* buffer,
    uint32_t size
    );
Brief

This stores a data structure of type ctx_type from RAM to non-volatile memory.

Parameters

[in]

ctx_type

Type of modem context that needs to be saved

[in]

buffer

Buffer pointer to write from

[in]

size

Buffer size to written in bytes

smtc_modem_hal_store_crashlog()

void smtc_modem_hal_store_crashlog(uint8_t crashlog[CRASH_LOG_SIZE]);
Brief

This stores the modem crash log to non-volatile memory. On most MCUs, RAM is preserved upon reset, so it may be possible to use RAM for this purpose.

Parameters

[in]

crashlog

Buffer pointer to write from

smtc_modem_hal_restore_crashlog()

void smtc_modem_hal_restore_crashlog(uint8_t crashlog[CRASH_LOG_SIZE]);
Brief

This retrieves the modem crash log from non-volatile memory. On most MCUs, RAM is preserved upon reset, so it may be possible to use RAM for this purpose.

Parameters

[out]

crashlog

Buffer pointer to write to

smtc_modem_hal_set_crashlog_status()

void smtc_modem_hal_set_crashlog_status(bool available);
Brief

This command is used to store the modem crash log status to non-volatile memory. On most MCUs, RAM is preserved upon reset, so it may be possible to use RAM for this purpose.

Parameters

[in]

available

True if a crash log is available, false otherwise

smtc_modem_hal_get_crashlog_status()

bool smtc_modem_hal_get_crashlog_status(void);
Brief

This command is used to get the modem crash log status from non-volatile memory. On most MCUs, RAM is preserved upon reset, so it may be possible to use RAM for this purpose.

Returns

The crash log status, as written using smtc_modem_hal_set_crashlog_status().

smtc_modem_hal_get_random_nb()

uint32_t smtc_modem_hal_get_random_nb(void);
Brief

Returns a uniformly distributed 32-bit unsigned random integer.

Returns

The random integer.

smtc_modem_hal_get_random_nb_in_range()

uint32_t smtc_modem_hal_get_random_nb_in_range(
    const uint32_t val_1,
    const uint32_t val_2
    );
Brief

Returns a uniformly distributed unsigned random integer from the closed interval [val_1, …, val_2] or [val_2, …, val_1].

This command may be implemented as follows:

uint32_t smtc_modem_hal_get_random_nb_in_range( const uint32_t val_1, const uint32_t val_2 )
{
    if( val_1 <= val_2 )
    {
        return ( uint32_t )( ( smtc_modem_hal_get_random_nb( ) % ( val_2 - val_1 + 1 ) ) + val_1 );
    }
    else
    {
        return ( uint32_t )( ( smtc_modem_hal_get_random_nb( ) % ( val_1 - val_2 + 1 ) ) + val_2 );
    }
}

Note

In the future, this HAL API command may be removed.

Returns

The random integer.

smtc_modem_hal_get_signed_random_nb_in_range()

int32_t smtc_modem_hal_get_signed_random_nb_in_range(
    const int32_t val_1,
    const int32_t val_2
    );
Brief

Returns a uniformly distributed signed random integer from the closed interval [val_1, …, val_2] or [val_2, …, val_1].

This command may be implemented as follows:

int32_t smtc_modem_hal_get_signed_random_nb_in_range( const int32_t val_1, const int32_t val_2 )
{
    uint32_t tmp_range = 0;  // ( val_1 <= val_2 ) ? ( val_2 - val_1 ) : ( val_1 - val_2 );

    if( val_1 <= val_2 )
    {
        tmp_range = ( val_2 - val_1 );
        return ( int32_t )( ( val_1 + smtc_modem_hal_get_random_nb_in_range( 0, tmp_range ) ) );
    }
    else
    {
        tmp_range = ( val_1 - val_2 );
        return ( int32_t )( ( val_2 + smtc_modem_hal_get_random_nb_in_range( 0, tmp_range ) ) );
    }
}

Note

In the future, this HAL API command may be removed.

Returns

The random integer.

smtc_modem_hal_irq_config_radio_irq()

void smtc_modem_hal_irq_config_radio_irq(
    void ( *callback )( void* context ),
    void* context
);
Brief

This command is called by the modem to define the callback that should be executed when a radio event occurs.

Parameters

[in]

callback

The callback that will be executed upon radio interrupt service request

[in]

context

The argument that will be provided to the above callback

smtc_modem_hal_irq_is_radio_irq_pending()

bool smtc_modem_hal_irq_is_radio_irq_pending(void);
Brief

This command must indicate whether an interrupt service request is pending inside the MCU hardware interrupt controller.

Returns

True, if an interrupt is pending; false, otherwise.

smtc_modem_hal_start_radio_tcxo()

bool smtc_modem_hal_start_radio_tcxo(void);
Brief

This command is called by the modem when the TCXO should be started. If no TCXO is used, implement an empty command.

smtc_modem_hal_stop_radio_tcxo()

bool smtc_modem_hal_stop_radio_tcxo(void);
Brief

This command is called by the modem when the TCXO should be stopped. If no TCXO is used, implement an empty command.

smtc_modem_hal_get_radio_tcxo_startup_delay_ms()

uint32_t smtc_modem_hal_get_radio_tcxo_startup_delay_ms(void);
Brief

Indicates to LoRa Basics Modem how much time the TCXO needs to get started.

Returns

The needed TCXO startup time, in milliseconds. Returns 0 if no TCXO is used.

smtc_modem_hal_get_battery_level()

uint8_t smtc_modem_hal_get_battery_level(void);
Brief

Indicates the current battery state.

Returns

A value between 0 (for 0%) and 255 (for 100%).

smtc_modem_hal_get_temperature()

int8_t smtc_modem_hal_get_temperature(void);
Brief

Indicates the current system temperature.

Returns

The temperature, in degrees Celsius.

smtc_modem_hal_get_voltage()

int8_t smtc_modem_hal_get_voltage(void);
Brief

Indicates the current battery voltage.

Returns

The battery voltage, in units of 20mV.

smtc_modem_hal_get_board_delay_ms()

int8_t smtc_modem_hal_get_board_delay_ms(void);
Brief

Used to return how much time passes between the moment the MCU calls ral_set_tx() or ral_set_rx(), and the moment when the radio receiver enters RX or TX state. This varies depending on the MCU clock speed and SPI bus speed.

Returns

The board delay, in milliseconds.

smtc_modem_hal_print_trace()

void smtc_modem_hal_print_trace(
    const char* fmt,
    ...
    );
Brief

This outputs a printf-style variable-length argument list to the logging subsystem.

Parameters

[in]

fmt

printf-style string

[in]

Arguments that accompany fmt

Alternatively, if you have some other command for logging that takes a variable number of arguments, you may simply wish to comment out the declaration of smtc_modem_hal_print_trace() in LBM_DIR/smtc_modem_hal/smtc_modem_hal.h, then edit LBM/smtc_modem_core/modem_config/smtc_modem_hal_dbg_trace.h as follows:

  • Add a proper #include directive pointing to your command declaration, such as ‘``#include <stdio.h>

  • Replace the two instances of smtc_modem_hal_print_trace by your logging command, such as printf.

Source Code Files

The following source files must be compiled for use cases:

LBM/smtc_modem_core/radio_drivers/lr1110_driver/src/lr1110_bootloader.c
LBM/smtc_modem_core/radio_drivers/lr1110_driver/src/lr1110_crypto_engine.c
LBM/smtc_modem_core/radio_drivers/lr1110_driver/src/lr1110_driver_version.c
LBM/smtc_modem_core/radio_drivers/lr1110_driver/src/lr1110_radio.c
LBM/smtc_modem_core/radio_drivers/lr1110_driver/src/lr1110_regmem.c
LBM/smtc_modem_core/radio_drivers/lr1110_driver/src/lr1110_system.c
LBM/smtc_modem_core/radio_drivers/lr1110_driver/src/lr1110_wifi.c
LBM/smtc_modem_core/radio_drivers/lr1110_driver/src/lr1110_gnss.c
LBM/smtc_modem_core/smtc_ral/src/ral_lr1110.c
LBM/smtc_modem_core/smtc_ralf/src/ralf_lr1110.c
LBM/smtc_modem_core/radio_planner/src/radio_planner.c
LBM/smtc_modem_core/radio_planner/src/radio_planner_hal.c
LBM/smtc_modem_core/modem_core/smtc_modem_api_lr1110_crypto_engine.c
LBM/smtc_modem_core/modem_core/smtc_modem_api_lr1110_system.c
LBM/smtc_modem_core/lorawan_api/lorawan_api.c
LBM/smtc_modem_core/device_management/dm_downlink.c
LBM/smtc_modem_core/device_management/modem_context.c
LBM/smtc_modem_core/modem_core/smtc_modem.c
LBM/smtc_modem_core/modem_core/smtc_modem_test.c
LBM/smtc_modem_core/modem_services/fifo_ctrl.c
LBM/smtc_modem_core/modem_services/modem_utilities.c
LBM/smtc_modem_core/modem_services/smtc_modem_services_hal.c
LBM/smtc_modem_core/modem_services/smtc_clock_sync.c
LBM/smtc_modem_core/modem_services/lorawan_certification.c
LBM/smtc_modem_core/modem_supervisor/modem_supervisor.c
LBM/smtc_modem_core/smtc_modem_services/src/almanac_update/almanac_update.c
LBM/smtc_modem_core/smtc_modem_services/src/stream/stream.c
LBM/smtc_modem_core/smtc_modem_services/src/stream/rose.c
LBM/smtc_modem_core/smtc_modem_services/src/alc_sync/alc_sync.c
LBM/smtc_modem_core/smtc_modem_services/src/file_upload/file_upload.c
LBM/smtc_modem_core/smtc_modem_crypto/smtc_modem_crypto.c
LBM/smtc_modem_core/lr1mac/src/smtc_real/src/region_cn_470.c
LBM/smtc_modem_core/lr1mac/src/smtc_real/src/region_cn_470_rp_1_0.c
LBM/smtc_modem_core/lr1mac/src/smtc_real/src/region_eu_868.c
LBM/smtc_modem_core/lr1mac/src/smtc_real/src/region_us_915.c
LBM/smtc_modem_core/lr1mac/src/lr1_stack_mac_layer.c
LBM/smtc_modem_core/lr1mac/src/lr1mac_core.c
LBM/smtc_modem_core/lr1mac/src/lr1mac_utilities.c
LBM/smtc_modem_core/lr1mac/src/smtc_real/src/smtc_real.c
LBM/smtc_modem_core/lr1mac/src/services/smtc_duty_cycle.c
LBM/smtc_modem_core/lr1mac/src/services/smtc_lbt.c
LBM/smtc_modem_core/lr1mac/src/lr1mac_class_c/lr1mac_class_c.c

The following files must be compiled to enable LR1110 hardware cryptography:

LBM/smtc_modem_core/smtc_modem_crypto/lr1110_secure_element/lr1110_se.c

Otherwise, the following files must be compiled to enable software cryptography:

LBM/smtc_modem_core/smtc_modem_crypto/soft_secure_element/aes.c
LBM/smtc_modem_core/smtc_modem_crypto/soft_secure_element/cmac.c
LBM/smtc_modem_core/smtc_modem_crypto/soft_secure_element/soft_se.c

Include Directories

The following directories must be included for all use cases:

LBM/smtc_modem_core/radio_drivers/lr1110_driver/src
LBM/smtc_modem_core/smtc_ral/src
LBM/smtc_modem_core/smtc_ralf/src
LBM/smtc_modem_api
LBM/smtc_modem_core/modem_supervisor
LBM/smtc_modem_core/smtc_modem_crypto
LBM/smtc_modem_core/modem_config
LBM/smtc_modem_core/radio_planner/src
LBM/smtc_modem_core/lr1mac/src
LBM/smtc_modem_core/smtc_modem_services/headers
LBM/smtc_modem_core/modem_services
LBM/smtc_modem_core/smtc_modem_services/src/file_upload
LBM/smtc_modem_core/smtc_modem_services/src/stream
LBM/smtc_modem_core/lr1mac/src/smtc_real/src
LBM/smtc_modem_core/lr1mac/src/services
LBM/smtc_modem_core/device_management
LBM/smtc_modem_core/lorawan_api
LBM/smtc_modem_core/lr1mac/src/lr1mac_class_c
LBM/smtc_modem_core/modem_core
LBM/smtc_modem_core/modem_core/smtc_modem_services/headers
LBM/smtc_modem_core/smtc_modem_services/src
LBM/smtc_modem_core/lr1mac
LBM/smtc_modem_core/smtc_modem_services
LBM/smtc_modem_core/lr1mac/src/lr1mac_class_b
LBM
LBM/smtc_modem_hal
LBM/smtc_modem_core/smtc_modem_crypto/smtc_secure_element

The following directories must be included to enable LR1110 hardware cryptography:

LBM/smtc_modem_core/smtc_modem_crypto/lr1110_secure_element

Otherwise, the following directories must be included to enable software cryptography:

LBM/smtc_modem_core/smtc_modem_crypto/soft_secure_element

C Preprocessor Definitions

When compiling LoRa Basics Modem, it is necessary to define a number of C preprocessor definitions, described below.

Transceiver Selection

To select the transceiver, add the following definitions:

  • LR1110

  • LR1110_TRANSCEIVER

Region Selection

To select the region, add any combination of the following definitions:

  • REGION_EU_868

  • REGION_US_915

  • REGION_CN_470

  • REGION_CN_470_RP_1_0

Cryptography Operation

To enable hardware cryptography on the LR1110, it is necessary to add the following definition:

  • USE_LR1110_SE

If hardware cryptography is enabled on the LR1110, you can also select the pre-provisioned DevEUI, JoinEUI, and PIN, by adding:

  • USE_PRE_PROVISIONED_FEATURES

Logging

To disable logging, define MODEM_HAL_DBG_TRACE to be equal to 0.

To enable additional logging of radio-related operations, define MODEM_HAL_DBG_TRACE_RP to be equal to 1.

You may wish to use a high-speed UART to implement the trace, because logging can potentially interfere with modem communication.

Rx Window Debugging Tips

LoRaWAN requires accurate receive window timing. This section provides some tips indicating how to verify that the window timing is good.

Clock Error Compensation

If your crystal error is more than 500 ppm, add a call (before joining the network) to the modem smtc_modem_set_crystal_error() API command to specify the crystal error, in parts per thousand. Large crystal error values result in wider Rx windows.

For more information, see Application Note AN1200.24.

Getting Started With Rx Window Fine Tuning

Initially, define the HAL API command smtc_modem_hal_get_board_delay_ms() to return 0.

You may also wish to configure the MCU to provide the most accurate possible clock to smtc_modem_hal_get_time_in_ms() and smtc_modem_hal_start_timer(). Having an accurate MCU clock should facilitate debugging.

Enable proper logging, as described in Logging. Using 921600 baud is recommended, if your hardware permits. Please keep in mind that logging code may interfere with timing, depending on your MCU speed and UART baud rate.

Consider the case where LoRa Basics Modem is running without a functioning packet forwarder, or without an appropiate configuration on the network server. In this case, uplinks will not be responded to, and will result in an RxTimeout interrupt. Since LoRa Basics Modem knows what RxTimeout value was used, the time elapsed between the TxDone interrupt and the RxTimeout interrupt can be used to position the start of the Rx window. This is the purpose of the fine-tuning algorithm, which can be found in LBM/smtc_modem_core/lr1mac/src/lr1_stack_mac_layer.c.

On every reception failure, the fine-tuning algorithm generates log messages like this:

DR3 Fine tune correction (ms) = 1, error fine tune (ms) = 0, lr1_mac->rx_offset_ms = -18

If this algorithm is working properly, on every reception failure for a given data rate, the fine tune correction value for that data rate will be incremented or decremented, until it converges on a value that results in reliable reception. From this point on, error fine tune should stay close to zero. This is a hands-off approach that works in many cases.

Rx Window Debugging Configuration

Fine tuning convergence may be slow, or not occur. Debugging this type of problem, and determing what value to use for smtc_modem_hal_get_board_delay_ms(), is the purpose of the following sections of this chapter.

In order to know whether something is interfering with Rx window placement, it is important to know the desired length of the window, as requested by the MCU. This desired window length can then be compared to the actual window length, as measured by a logic analyzer.

With this in mind, temporarily deactivate the window fine-tuning feature by globally defining the preprocessor definition BSP_LR1MAC_DISABLE_FINE_TUNE.

Add IRQ Timing Log Information

The following change to command rp_radio_irq() in LBM/smtc_modem_core/radio_planner/src/radio_planner.c will make it possible to observe in the log the MCU time at which every radio IRQ arrives. Change the following line of code

SMTC_MODEM_HAL_RP_TRACE_PRINTF( " RP: INFO - Radio IRQ received for hook #%u\n", rp->radio_task_id );

to read

SMTC_MODEM_HAL_RP_TRACE_PRINTF( " RP: INFO - Radio IRQ received for hook #%u at time %u\n", rp->radio_task_id, now );

Add Ready and Trigger Timing Log Information

The variable start_time_ms contains the MCU time at which the SetRx command is meant to be sent to the MCU. Shortly after, this provokes the opening of the Rx window.

When functioning properly, the command lr1_stack_mac_rx_lora_launch_callback_for_rp() is expected to be executed a short time before start_time_ms. After preparing the radio for reception, a while loop inside this command will wait until the current time is equal to start_time_ms. At this point in time, called the trigger time, the command ral_set_rx() will be called. The point at which this while loop was entered is called the ready time.

The following change to command lr1_stack_mac_rx_lora_launch_callback_for_rp() in LBM/smtc_modem_core/lr1mac/src/lr1_stack_mac_layer.c will make it possible to observe in the log the MCU ready time and the MCU trigger time.

Change the following block of code

// Wait the exact time
while( ( int32_t )( rp->tasks[id].start_time_ms - rp_hal_timestamp_get( ) ) > 0 )
{
}
if( ral_set_rx( &( rp->radio->ral ), rp->radio_params[id].rx.timeout_in_ms ) != RAL_STATUS_OK )
{
    smtc_modem_hal_mcu_panic( );
}
rp_stats_set_rx_timestamp( &rp->stats, rp_hal_timestamp_get( ) );

to read

// Wait the exact time
uint32_t tcurrent_ms = rp_hal_timestamp_get( );
while( ( int32_t )( rp->tasks[id].start_time_ms - rp_hal_timestamp_get( ) ) > 0 )
{
}
if( ral_set_rx( &( rp->radio->ral ), rp->radio_params[id].rx.timeout_in_ms ) != RAL_STATUS_OK )
{
    smtc_modem_hal_mcu_panic( );
}
rp_stats_set_rx_timestamp( &rp->stats, rp_hal_timestamp_get( ) );
SMTC_MODEM_HAL_TRACE_PRINTF( "RX ready at %d, waited until %d\n", tcurrent_ms, rp->tasks[id].start_time_ms );

Perform a Debugging Session

Once you have the logging configured as above, connect a logic analyzer, and run the loRaWAN example.

First, confirm that the MCU ready time is less than the MCU trigger time. If not, this indicates that there is no margin for error because either lr1_stack_mac_rx_lora_launch_callback_for_rp() is being entered too late, or the radio preparations are taking too long. If you debug this with additional trace calls, be careful to do so in a way that does not interfere with the timing. Other possibilities for this include using LED diagnostics.

Referring back to the Add IRQ Timing Log Information section, observe the log to determine at what MCU time the TxDone interrupt was timestamped by the MCU. Define this value tm1.

Referring back to section Add Ready and Trigger Timing Log Information, observe the log to determine at what MCU time the SetTx call was initiated. Define this value tm2.

Define delta1 = tm2 - tm1.

Using a logic analyzer that is able to decode the radio SPI bus communication, search for the last transceiver command preceding the first transceiver post-reset radio interrupt service request. The command should be SetTx, which can be verified by looking at the transceiver user manual. Define ta1 to be the time, according to the logic analyzer, at which the DIO1 line rises. Shortly after this moment at which the DIO1 line rises, the MCU software should read and clear the IRQ status, causing the DIO1 line to fall.

Now, search for the last transceiver command preceding the second post-reset transceiver interrupt service request. This command should be SetRx, which can be verified by looking at the transceiver user manual. Define ta2 to be the time, according to the logic analyzer, at which the NSS line fell right before sending SetRx. This corresponds approximately to the trigger time.

Define delta2 = ta2 – ta1.

delta1 is the MCU time between the TxDone interrupt and the initiation of the SetRx command.

delta2 is the logic analyzer time between the TxDone interrupt and the initiation of the SetRx command.

delta1 and delta2 should be within 1ms of one another, after correcting for the MCU clock accuracy.

Recall that the board delay is the amount of time between the ready time and the moment the transceiver initiates reception. Consider the SetRx command on the logic analyzer, and observe the amount of time between the moment that NSS falls and the moment that NSS rises. This value, in milli-seconds, is reasonably close to the board delay. Edit the smtc_modem_hal_get_board_delay_ms() HAL command so that it returns this value.

You may now wish to re-activate the window fine tuning feature by undefining the preprocessor definition BSP_LR1MAC_DISABLE_FINE_TUNE.