/** * @file sx126x_lr_fhss.c * * @brief SX126x LR-FHSS driver implementation * * The Clear BSD License * Copyright Semtech Corporation 2021. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted (subject to the limitations in the disclaimer * below) 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. * * NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY * THIS LICENSE. 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 CORPORATION 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. */ /* * ----------------------------------------------------------------------------- * --- DEPENDENCIES ------------------------------------------------------------ */ #include "lr_fhss_mac.h" #include "sx126x_lr_fhss.h" #include "sx126x_hal.h" /* * ----------------------------------------------------------------------------- * --- PRIVATE MACROS----------------------------------------------------------- */ /* * ----------------------------------------------------------------------------- * --- PRIVATE CONSTANTS ------------------------------------------------------- */ /* \cond */ #define SX126X_SET_MODULATION_PARAMS ( 0x8B ) #define SX126X_SET_PKT_PARAMS ( 0x8C ) #define SX126X_LR_FHSS_DISABLE_HOPPING ( 0 ) #define SX126X_LR_FHSS_ENABLE_HOPPING ( 1 ) #define SX126X_LR_FHSS_HOP_TABLE_SIZE ( 16 ) #define SX126X_LR_FHSS_HOP_ENTRY_SIZE ( 6 ) #define SX126X_LR_FHSS_GRID_3906_HZ_PLL_STEPS ( 4096 ) #define SX126X_LR_FHSS_GRID_25391_HZ_PLL_STEPS ( 26624 ) #define SX126X_LR_FHSS_GRID_INDEX_TO_PLL_STEPS ( 512 ) /* \endcond */ /* * ----------------------------------------------------------------------------- * --- PRIVATE TYPES ----------------------------------------------------------- */ /* * ----------------------------------------------------------------------------- * --- PRIVATE VARIABLES ------------------------------------------------------- */ /* * ----------------------------------------------------------------------------- * --- PRIVATE FUNCTION DECLARATIONS ------------------------------------------- */ /** * @brief Configure the radio to perform frequency hopping * * @param [in] context Chip implementation context * @param [in] nb_bytes Total number of bytes * @param [in] nb_hops Total number of hops * * @returns Operation status */ sx126x_status_t sx126x_lr_fhss_write_hop_config( const void* context, const uint8_t nb_bytes, const uint8_t nb_hops ); /** * @brief Write a hop frequency/duration pair to the radio hop table * * @param [in] context Chip implementation context * @param [in] index Index to chip hop table * @param [in] nb_symbols Hop duration in symbols * @param [in] freq_in_pll_steps Hop frequency, in PLL steps * * @returns Operation status */ sx126x_status_t sx126x_lr_fhss_write_hop( const void* context, const uint8_t index, const uint16_t nb_symbols, const uint32_t freq_in_pll_steps ); /** * @brief Get Frequency, in PLL steps, of the next hop * * @param [in] params sx126x LR-FHSS parameter structure * @param [out] state sx126x LR-FHSS state structure that will be initialized by this function * * @returns Frequency, in PLL steps, of the next hop */ uint32_t sx126x_lr_fhss_get_next_freq_in_pll_steps( const sx126x_lr_fhss_params_t* params, sx126x_lr_fhss_state_t* state ); /** * @brief Get grid frequency, in PLL steps * * @param [in] params sx126x LR-FHSS parameter structure * * @returns Grid frequency, in PLL steps */ static inline unsigned int sx126x_lr_fhss_get_grid_in_pll_steps( const sx126x_lr_fhss_params_t* params ); /* * ----------------------------------------------------------------------------- * --- PUBLIC FUNCTIONS DEFINITION --------------------------------------------- */ sx126x_status_t sx126x_lr_fhss_init( const void* context, const sx126x_lr_fhss_params_t* params ) { const uint8_t pkt_params_buf[] = { SX126X_SET_PKT_PARAMS, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; const uint8_t mod_params_buf[] = { SX126X_SET_MODULATION_PARAMS, 32, 0, 0, SX126X_GFSK_PULSE_SHAPE_BT_1, 0, 0, 0, 0, }; sx126x_status_t status = sx126x_set_pkt_type( context, SX126X_PKT_TYPE_LR_FHSS ); if( status != SX126X_STATUS_OK ) { return status; } status = ( sx126x_status_t ) sx126x_hal_write( context, pkt_params_buf, sizeof( pkt_params_buf ), 0, 0 ); if( status != SX126X_STATUS_OK ) { return status; } status = ( sx126x_status_t ) sx126x_hal_write( context, mod_params_buf, sizeof( mod_params_buf ), 0, 0 ); if( status != SX126X_STATUS_OK ) { return status; } status = sx126x_set_buffer_base_address( context, 0x00, 0x00 ); return status; } uint16_t sx126x_lr_fhss_get_bit_delay_in_us( const sx126x_lr_fhss_params_t* params, uint16_t payload_length ) { lr_fhss_digest_t digest; lr_fhss_process_parameters( ¶ms->lr_fhss_params, payload_length, &digest ); const uint8_t nb_padding_bits = 1 + ( ( 32768 - digest.nb_bits ) & 0x07 ); return 1550 + nb_padding_bits * 2048; } sx126x_status_t sx126x_lr_fhss_process_parameters( const sx126x_lr_fhss_params_t* params, uint16_t hop_sequence_id, uint16_t payload_length, sx126x_lr_fhss_state_t* state ) { lr_fhss_process_parameters( ¶ms->lr_fhss_params, payload_length, &state->digest ); if( state->digest.nb_bytes > LR_FHSS_MAX_PHY_PAYLOAD_BYTES ) { return SX126X_STATUS_UNKNOWN_VALUE; } if( params->lr_fhss_params.grid == LR_FHSS_V1_GRID_25391_HZ ) { if( params->device_offset > 25 || params->device_offset < -26 ) { return SX126X_STATUS_UNKNOWN_VALUE; } if( params->lr_fhss_params.bw < LR_FHSS_V1_BW_722656_HZ ) { return SX126X_STATUS_UNKNOWN_VALUE; } } if( params->lr_fhss_params.grid == LR_FHSS_V1_GRID_3906_HZ ) { if( params->device_offset > 3 || params->device_offset < -4 ) { return SX126X_STATUS_UNKNOWN_VALUE; } } // Initialize hop index and params state->current_hop = 0; lr_fhss_status_t status = lr_fhss_get_hop_params( ¶ms->lr_fhss_params, &state->hop_params, &state->lfsr_state, hop_sequence_id ); if( status != LR_FHSS_STATUS_OK ) { return ( sx126x_status_t ) status; } // Skip the hop frequencies inside the set [0, 4 - header_count): if( params->lr_fhss_params.enable_hopping != 0 ) { for( int i = 0; i < 4 - params->lr_fhss_params.header_count; ++i ) { lr_fhss_get_next_state( &state->lfsr_state, &state->hop_params ); } } state->next_freq_in_pll_steps = sx126x_lr_fhss_get_next_freq_in_pll_steps( params, state ); return SX126X_STATUS_OK; } sx126x_status_t sx126x_lr_fhss_write_hop_sequence_head( const void* context, const sx126x_lr_fhss_params_t* params, sx126x_lr_fhss_state_t* state ) { sx126x_status_t status = sx126x_lr_fhss_write_hop_config( context, state->digest.nb_bytes, state->digest.nb_hops ); if( status != SX126X_STATUS_OK ) { return status; } const uint16_t pulse_shape_compensation = 1; if( params->lr_fhss_params.enable_hopping == 0 ) { // (LR_FHSS_HEADER_BITS + pulse_shape_compensation) symbols on first sync_word, LR_FHSS_HEADER_BITS on next // sync_words, LR_FHSS_BLOCK_BITS on payload const uint16_t nb_symbols = state->digest.nb_bits + pulse_shape_compensation; status = sx126x_lr_fhss_write_hop( context, state->current_hop, nb_symbols, state->next_freq_in_pll_steps ); if( status != SX126X_STATUS_OK ) { return status; } state->current_hop++; state->digest.nb_bits = 0; } else { // fill at most SX126X_LR_FHSS_HOP_TABLE_SIZE hops of the hardware hop table uint8_t truncated_hops = state->digest.nb_hops; if( truncated_hops > SX126X_LR_FHSS_HOP_TABLE_SIZE ) { truncated_hops = SX126X_LR_FHSS_HOP_TABLE_SIZE; } while( state->current_hop < truncated_hops ) { uint16_t nb_symbols; // (LR_FHSS_HEADER_BITS + pulse_shape_compensation) symbols on first sync_word, LR_FHSS_HEADER_BITS on // next sync_words, LR_FHSS_BLOCK_BITS on payload if( state->current_hop >= params->lr_fhss_params.header_count ) { if( state->digest.nb_bits > LR_FHSS_BLOCK_BITS ) { nb_symbols = LR_FHSS_BLOCK_BITS; } else { nb_symbols = state->digest.nb_bits; } } else if( state->current_hop > 0 ) { nb_symbols = LR_FHSS_HEADER_BITS; } else { nb_symbols = LR_FHSS_HEADER_BITS + pulse_shape_compensation; } status = sx126x_lr_fhss_write_hop( context, state->current_hop, nb_symbols, state->next_freq_in_pll_steps ); if( status != SX126X_STATUS_OK ) { return status; } state->current_hop++; state->digest.nb_bits -= nb_symbols; state->next_freq_in_pll_steps = sx126x_lr_fhss_get_next_freq_in_pll_steps( params, state ); } } return status; } sx126x_status_t sx126x_lr_fhss_write_payload( const void* context, const sx126x_lr_fhss_state_t* state, const uint8_t* payload ) { return sx126x_write_buffer( context, 0x00, payload, state->digest.nb_bytes ); } sx126x_status_t sx126x_lr_fhss_build_frame( const void* context, const sx126x_lr_fhss_params_t* params, sx126x_lr_fhss_state_t* state, uint16_t hop_sequence_id, const uint8_t* payload, uint16_t payload_length, uint32_t* first_frequency_in_pll_steps ) { sx126x_status_t status = sx126x_lr_fhss_process_parameters( params, hop_sequence_id, payload_length, state ); if( status != SX126X_STATUS_OK ) { return status; } if( first_frequency_in_pll_steps ) { *first_frequency_in_pll_steps = state->next_freq_in_pll_steps; } uint8_t tx_buffer[LR_FHSS_MAX_PHY_PAYLOAD_BYTES]; lr_fhss_build_frame( ¶ms->lr_fhss_params, state->hop_params.hop_sequence_id, payload, payload_length, tx_buffer ); status = sx126x_lr_fhss_write_payload( context, state, tx_buffer ); if( status != SX126X_STATUS_OK ) { return status; } status = sx126x_lr_fhss_write_hop_sequence_head( context, params, state ); return status; } sx126x_status_t sx126x_lr_fhss_handle_hop( const void* context, const sx126x_lr_fhss_params_t* params, sx126x_lr_fhss_state_t* state ) { if( state->current_hop < state->digest.nb_hops ) { uint16_t nb_bits; if( state->digest.nb_bits > LR_FHSS_BLOCK_BITS ) { nb_bits = LR_FHSS_BLOCK_BITS; } else { nb_bits = state->digest.nb_bits; } sx126x_status_t status = sx126x_lr_fhss_write_hop( context, state->current_hop % SX126X_LR_FHSS_HOP_TABLE_SIZE, LR_FHSS_BLOCK_BITS, state->next_freq_in_pll_steps ); if( status != SX126X_STATUS_OK ) { return status; } state->current_hop++; state->digest.nb_bits -= nb_bits; state->next_freq_in_pll_steps = sx126x_lr_fhss_get_next_freq_in_pll_steps( params, state ); } return SX126X_STATUS_OK; } sx126x_status_t sx126x_lr_fhss_handle_tx_done( const void* context, const sx126x_lr_fhss_params_t* params, sx126x_lr_fhss_state_t* state ) { const uint8_t ctrl = SX126X_LR_FHSS_DISABLE_HOPPING; return sx126x_write_register( context, SX126X_LR_FHSS_REG_CTRL, &ctrl, 1 ); } /* * ----------------------------------------------------------------------------- * --- PRIVATE FUNCTIONS DEFINITION -------------------------------------------- */ sx126x_status_t sx126x_lr_fhss_write_hop_config( const void* context, const uint8_t nb_bytes, const uint8_t nb_hops ) { uint8_t data[] = { SX126X_LR_FHSS_ENABLE_HOPPING, nb_bytes, nb_hops }; return sx126x_write_register( context, SX126X_LR_FHSS_REG_CTRL, data, 3 ); } sx126x_status_t sx126x_lr_fhss_write_hop( const void* context, const uint8_t index, const uint16_t nb_symbols, const uint32_t freq_in_pll_steps ) { if( index >= SX126X_LR_FHSS_HOP_TABLE_SIZE ) { return SX126X_STATUS_ERROR; } uint8_t data[SX126X_LR_FHSS_HOP_ENTRY_SIZE] = { ( uint8_t ) ( nb_symbols >> 8 ), ( uint8_t ) nb_symbols, ( uint8_t ) ( freq_in_pll_steps >> 24 ), ( uint8_t ) ( freq_in_pll_steps >> 16 ), ( uint8_t ) ( freq_in_pll_steps >> 8 ), ( uint8_t ) freq_in_pll_steps, }; return sx126x_write_register( context, SX126X_LR_FHSS_REG_NUM_SYMBOLS_0 + ( SX126X_LR_FHSS_HOP_ENTRY_SIZE * index ), data, SX126X_LR_FHSS_HOP_ENTRY_SIZE ); } uint32_t sx126x_lr_fhss_get_next_freq_in_pll_steps( const sx126x_lr_fhss_params_t* params, sx126x_lr_fhss_state_t* state ) { #ifdef HOP_AT_CENTER_FREQ const int16_t freq_table = 0; uint32_t grid_offset = 0; #else const int16_t freq_table = lr_fhss_get_next_freq_in_grid( &state->lfsr_state, &state->hop_params, ¶ms->lr_fhss_params ); uint32_t nb_channel_in_grid = params->lr_fhss_params.grid ? 8 : 52; uint32_t grid_offset = ( 1 + ( state->hop_params.n_grid % 2 ) ) * ( nb_channel_in_grid / 2 ); #endif unsigned int grid_in_pll_steps = sx126x_lr_fhss_get_grid_in_pll_steps( params ); uint32_t freq = params->center_freq_in_pll_steps - freq_table * grid_in_pll_steps - ( params->device_offset + grid_offset ) * SX126X_LR_FHSS_GRID_INDEX_TO_PLL_STEPS; #ifndef HOP_AT_CENTER_FREQ // Perform frequency correction for every other sync header if( params->lr_fhss_params.enable_hopping && ( state->current_hop < params->lr_fhss_params.header_count ) ) { if( ( ( ( params->lr_fhss_params.header_count - state->current_hop ) % 2 ) == 0 ) ) { // OFFSET_SYNCWORD = 488.28125 / 2, and FREQ_STEP_SX1261_2 = 0.95367431640625, so // OFFSET_SYNCWORD / FREQ_STEP_SX1261_2 = 256 freq = freq + 256; } } #endif return freq; } static inline unsigned int sx126x_lr_fhss_get_grid_in_pll_steps( const sx126x_lr_fhss_params_t* params ) { return ( params->lr_fhss_params.grid == LR_FHSS_V1_GRID_3906_HZ ) ? SX126X_LR_FHSS_GRID_3906_HZ_PLL_STEPS : SX126X_LR_FHSS_GRID_25391_HZ_PLL_STEPS; } /* --- EOF ------------------------------------------------------------------ */