This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

Status and approach for PA/LNA Support in BLE/Thread Concurrent Multiprotocol

I am migrating to the Fanstel BT840XE module with nRF52840 and a Skyworks PA/LNA.  I need some assistance in getting my PA/LNA working in a multiprotocol setup. 

I have successfully implemented and tested the Softdevice portion of PA/LNA control for BLE, and tested it on my custom hardware platform.  I confirmed operation using a BLE-only example from the SDK and confirmed the transmit power is indeed higher when the PA/LNA flag is set -- I think this demonstrates my code is correct and hardware is working.

My actual multiprotocol project implements the same BLE PA/LNA setup tested above (code snippets below).  I have *not yet* implemented any PA/LNA control for Thread.

1. At this point, I would expect my BLE advertisements would be send with higher power with the PA/LNA enabled, but they are not.  Is the multiprotocol usage somehow defeating the PA/LNA initialization I am doing?

2. What is the recommended approach to PA/LNA in a BLE/Thread multiprotocol concurrent project?

DETAILS:

My project uses concurrent BLE/Thread Multiprotocol with nRF5_SDK_for_Thread_and_Zigbee_3.1.0_c7c4730 and S140 6.1.1.

My custom board file includes the following section for PA/LNA and transmit power:

#define APP_PA_LNA
// This board has a Power Amplifier and LNA  "APP_PA_LNA" will trigger main to initialize it.
#ifdef APP_PA_LNA
#define APP_PA_PIN      17
#define APP_LNA_PIN     19
#define APP_CHL_PIN	8
#define APP_CPS_PIN	6
#endif //APP_PA_LNA

#define MAX_BLE_XMIT_PWR_DBM  0   //Fanstel BT840XE max transmit power from radio IC is +2dBm.  PA adds 21dBm!  Use with 0dBm gain antenna max for FCC/IC.
#define THREAD_POWER 0    // <i> Transmit Power (in dBm) used by Thread.

In main, after softdevice init, I configure the PA/LNA:

    ble_stack_init();

    // set up PA/LNA if present - NOTE: must be done after softdevice is enabled.
    #ifdef APP_PA_LNA	
	nrf_gpio_cfg_output(APP_CPS_PIN);
	nrf_gpio_cfg_output(APP_CHL_PIN);
	nrf_gpio_pin_set(APP_CHL_PIN);
	nrf_gpio_pin_clear(APP_CPS_PIN); //enable
	pa_lna_init(APP_PA_PIN,APP_LNA_PIN);
    #endif	

Here is my PA/LNA init code:

#include "fanstel_pa_lna.h"
#include "nrf_assert.h"
#include "nrf_log.h"
#include "ble.h"
#include "app_error.h"
#include "nrf_drv_gpiote.h"
#include "nrf_drv_ppi.h"

void pa_lna_init(uint32_t gpio_pa_pin, uint32_t gpio_lna_pin)
{
    ble_opt_t opt;
    uint32_t gpiote_ch = NULL;
    ret_code_t err_code;        

    memset(&opt, 0, sizeof(ble_opt_t));
    
    err_code = nrf_drv_gpiote_init();
    if(err_code != NRF_ERROR_INVALID_STATE)
        APP_ERROR_CHECK(err_code);
    
    err_code = nrf_drv_ppi_init();
    //if(err_code != MODULE_ALREADY_INITIALIZED)
        APP_ERROR_CHECK(err_code);
    
    nrf_ppi_channel_t ppi_set_ch;
    nrf_ppi_channel_t ppi_clr_ch;
    
    err_code = nrf_drv_ppi_channel_alloc(&ppi_set_ch);
    APP_ERROR_CHECK(err_code);
    
    err_code = nrf_drv_ppi_channel_alloc(&ppi_clr_ch);
    APP_ERROR_CHECK(err_code);

    nrf_drv_gpiote_out_config_t config = GPIOTE_CONFIG_OUT_TASK_TOGGLE(false);
    
    if((gpio_pa_pin == NULL) && (gpio_lna_pin == NULL))
    {
        err_code = NRF_ERROR_INVALID_PARAM;
        APP_ERROR_CHECK(err_code);
    }    

    if(gpio_pa_pin != NULL)
    {
        if(gpiote_ch == NULL)
        {
            err_code = nrf_drv_gpiote_out_init(gpio_pa_pin, &config);
            APP_ERROR_CHECK(err_code);
            
            gpiote_ch = nrf_drv_gpiote_out_task_addr_get(gpio_pa_pin); 
        }
        
        // PA config
        opt.common_opt.pa_lna.pa_cfg.active_high = 1;   // Set the pin to be active high
        opt.common_opt.pa_lna.pa_cfg.enable      = 1;   // Enable toggling
        opt.common_opt.pa_lna.pa_cfg.gpio_pin    = gpio_pa_pin; // The GPIO pin to toggle tx  
    }
    
    if(gpio_lna_pin != NULL)
    {
        if(gpiote_ch == NULL)
        {
            err_code = nrf_drv_gpiote_out_init(gpio_lna_pin, &config);
            APP_ERROR_CHECK(err_code);        
            
            gpiote_ch = nrf_drv_gpiote_out_task_addr_get(gpio_lna_pin); 
        }
        
        // LNA config
        opt.common_opt.pa_lna.lna_cfg.active_high  = 1; // Set the pin to be active high
        opt.common_opt.pa_lna.lna_cfg.enable       = 1; // Enable toggling
        opt.common_opt.pa_lna.lna_cfg.gpio_pin     = gpio_lna_pin;  // The GPIO pin to toggle rx
    }

    // Common PA/LNA config
    opt.common_opt.pa_lna.gpiote_ch_id  = (gpiote_ch - NRF_GPIOTE_BASE) >> 2;   // GPIOTE channel used for radio pin toggling
    opt.common_opt.pa_lna.ppi_ch_id_clr = ppi_clr_ch;   // PPI channel used for radio pin clearing
    opt.common_opt.pa_lna.ppi_ch_id_set = ppi_set_ch;   // PPI channel used for radio pin setting
    
    err_code = sd_ble_opt_set(BLE_COMMON_OPT_PA_LNA, &opt);
    APP_ERROR_CHECK(err_code);
        
    if (err_code == NRF_SUCCESS)
    {
        NRF_LOG_INFO("PA/LNA Successfully Enabled");
    }
    return;
}

When I start advertising, I set the radio transmit power using the power level defined in the board file:

#define BLE_GAP_ADV_POWER               MAX_BLE_XMIT_PWR_DBM                    /**< BLE Transmit power to use for advertising, in dBm. */

/**@brief Function for starting advertising.
 */
static void advertising_start(void)
{
    ret_code_t err_code;

    // NORDIC: SET TX POWER FOR ADVERTISING
    err_code = sd_ble_gap_tx_power_set(BLE_GAP_TX_POWER_ROLE_ADV, m_advertising.adv_handle, BLE_GAP_ADV_POWER); 
    APP_ERROR_CHECK(err_code); 
    
    err_code = ble_advertising_start(&m_advertising, BLE_ADV_MODE_FAST);
    // We don't have a good way of knowing current adv state, so ignore the error
    if ((err_code != NRF_SUCCESS) )  //&& (err_code != NRF_ERROR_INVALID_STATE))
    {
        APP_ERROR_CHECK(err_code);
    }

    NRF_LOG_INFO("advertising is started");
}

  • I located the issue and appear to have the PA/LNA working in multiprotocol mode.  I had an error in my pa_lna_init_thread() function which was passing the address of the gpiote task instead of the channel.  My new function is pasted below for reference.

    1. I would still like Nordic to confirm that I am handling the PA/LNA correctly for a BLE/Thread project. 

    2. I know that your team has been working on the 802.15.4 radio driver quite a bit lately in the area of PA/LNA (FEM) handling.  I would like to know if I should be updating to the new driver now vs. waiting for the next SDK release.

    Thanks,
    Rob

    <Code deleted, it was incorrect anyway>

  • Hi Rob,

    I did a code review and suggest few changes

    void pa_lna_init_thread(uint32_t gpio_pa_pin, uint32_t gpio_lna_pin)
    {
        PlatformFemConfigParams cfg;
        uint32_t gpiote_ch_pa = NULL;
        uint32_t gpiote_ch_lna = NULL;
        ret_code_t err_code;  
    
        if((gpio_pa_pin == NULL) || (gpio_lna_pin == NULL))
        {
            // Note: if you do not return here, then last 5 lines of your code will be wrong
            // I suggest to change the return type of this function, return error with failure
            // and handle the error from the caller of this function.
            return;
        }
        
        memset(&cfg, 0, sizeof(cfg));
    
        // Get some PPI channels to use
        nrf_ppi_channel_t ppi_set_ch;
        nrf_ppi_channel_t ppi_clr_ch;
            
        err_code = nrf_drv_ppi_channel_alloc(&ppi_set_ch);
        APP_ERROR_CHECK(err_code);
    
        err_code = nrf_drv_ppi_channel_alloc(&ppi_clr_ch);
        APP_ERROR_CHECK(err_code);
    
        // NOTE: No need to compare gpiote_ch_pa with NULL, since it will always be NULL here
        gpiote_ch_pa = nrf_drv_gpiote_out_task_addr_get(gpio_pa_pin);
    
        // PA config
        cfg.mPaCfg.mEnable     = 1;
        cfg.mPaCfg.mActiveHigh = 1;
        cfg.mPaCfg.mGpioPin    = gpio_pa_pin;
    
        // NOTE: No need to compare gpiote_ch_lna with NULL, since it will always be NULL here
        gpiote_ch_lna = nrf_drv_gpiote_out_task_addr_get(gpio_lna_pin);
        
        // LNA config
        cfg.mLnaCfg.mEnable     = 1;
        cfg.mLnaCfg.mActiveHigh = 1;
        cfg.mLnaCfg.mGpioPin    = gpio_lna_pin;
    
    
        cfg.mPpiChIdClr    = ppi_clr_ch;
        cfg.mPpiChIdSet    = ppi_set_ch;    
    
        //cfg.mGpiotePaChId  = gpiote_ch_pa;
        cfg.mGpiotePaChId  = (gpiote_ch_pa - NRF_GPIOTE_BASE) >> 2;   // GPIOTE channel used for pa pin toggling
    
        //cfg.mGpioteLnaChId = gpiote_ch_lna;
        cfg.mGpiotePaChId  = (gpiote_ch_lna - NRF_GPIOTE_BASE) >> 2;   // GPIOTE channel used for pa pin toggling
        
        PlatformFemSetConfigParams(&cfg);
        return;
    }

  • Just wanted to reply to document and close out this issue.  Using the NRF5 SDK for Thread v.4.1.0, it is indeed possible to implement a multiprotocol project (BLE+Thread) where both are able to cooperatively use a Front-End Module (FEM or PA/LNA).  The correct approach (based on openthread stack as of Fall 2020) includes:

    1. Enable the FEM for Thread by modifying the openthread source and makefiles as described here and then re-building the Thread libraries used in your NRF5 project as described here.  A couple of important points:

    • Take note of the PPI channels and groups used to control the PA and LNA lines.
    • Consider omitting the CHL pin (if required by your FEM chip) and just controlling it in your application code (see below)
    • Consider lying to openthread about the pin for PDN (give it an unused pin or otherwise defeat this functionality).

    2. Enable and control the FEM for BLE as it it were a BLE-only project.  Some module vendors offer example code for this purpose.  Ignore my mis-guided application code above which attempted to initialize the FEM for Thread in addition to BLE.  A couple of important points:

    • If your FEM has powerdown or high/low gain pins (like CHL and CPS on the SKY66112-11), you may wish to set them in your init code.  Then, you can ignore these in the OpenThread step below.
    • FEM control (both BLE and Thread) uses the nrfx_ppi allocator module, which normally prevents conflicting use.  The problem is, the allocator isn't going to be aware of PPI resources being used in the pre-compiled openthread library.  You can prevent conflicts by editing the ppi resource masks NRFX_PPI_CHANNELS_USED/NRFX_PPI_GROUPS_USED.

    Good luck!

    Rob

Related