This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

CCS/CC2640R2F: simplePeripheral never enters Power_shutdown

Part Number: CC2640R2F
Other Parts Discussed in Thread: INA188

Tool/software: Code Composer Studio

Hello,

We have an application which requires long battery life and infrequent use of the device. I'm implementing BLE5SimplePeripheral. Out of the box the CC2640r2 never enters Power_shutdown, rather it returns to advertising mode. This consumes 2+ mA RMS. I have implemented a button that can be used to wake-up the CC2640r2 and code following the pinShutdown example. The tread is shown here:

//******************************************************************************
// Includes
//*****************************************************************************/
//#include <stdint.h>
//#include <stddef.h>
#include <xdc/std.h>
#include <xdc/runtime/Error.h>
//#include <ti/sysbios/BIOS.h>
#include <ti/sysbios/knl/Clock.h>
#include <ti/sysbios/knl/Task.h>
#include <ti/sysbios/knl/Semaphore.h>
#include <ti/sysbios/knl/Swi.h>
#include "Board.h"
#include <ti/drivers/GPIO.h>
//#include <ti/drivers/Power.h>
//#include <ti/drivers/power/PowerCC26XX.h>
#include <ti/drivers/pin/PINCC26XX.h>
#include <ti/devices/DeviceFamily.h>
#include DeviceFamily_constructPath(inc/hw_prcm.h)
#include DeviceFamily_constructPath(driverlib/sys_ctrl.h)


//******************************************************************************
// Globals
//*****************************************************************************/
/* Task and tast stack */
Task_Struct myTask;
#define THREADSTACKSIZE    512
uint8_t myTaskStack[THREADSTACKSIZE];

/* Semaphore used to gate for shutdown */
Semaphore_Struct shutdownSem;

/* Clock used for debounce logic */
Clock_Struct buttonClock;
Clock_Handle hButtonClock;

/* Pin driver handles */
PIN_Handle hPins;
PIN_Handle hButtons;

/* LED pin state */
PIN_State LedPinState;

/* Button pin state */
PIN_State buttonState;

/* Flag to store whether we woke up from shutdown or not */
bool isWakingFromShutdown;

/* PIN_Id for active button (in debounce period) */
PIN_Id activeButtonPinId;

/* Led pin table used when waking from reset. LED initially on*/
PIN_Config LedPinTable[] = {
    Board_BLED    | PIN_GPIO_OUTPUT_EN | PIN_GPIO_HIGH | PIN_PUSHPULL | PIN_DRVSTR_MAX, PIN_TERMINATE
};

/* Led pin table used when waking from shutdown. LED initially off */
PIN_Config LedPinTableSd[] = {
    Board_BLED    | PIN_GPIO_OUTPUT_EN | PIN_GPIO_LOW  | PIN_PUSHPULL | PIN_DRVSTR_MAX, PIN_TERMINATE
};

/* Wake-up Button pin table */
PIN_Config ButtonTableWakeUp[] = {
    Board_BTN1     | PIN_INPUT_EN | PIN_PULLUP | PINCC26XX_WAKEUP_NEGEDGE, PIN_TERMINATE
};

/* Shutdown Button pin table */
PIN_Config ButtonTableShutdown[] = {
    Board_BTN2   | PIN_INPUT_EN | PIN_PULLUP | PIN_IRQ_NEGEDGE, PIN_TERMINATE
};

/*!*****************************************************************************
 *  @brief      Button clock callback
 *
 *  Called when the debounce periode is over. Stopping the clock, toggling
 *  the device mode based on activeButtonPinId. Board_BTN1 will put the device
 *  in shutdown mode. Reenabling the interrupts and resetting the activeButtonPinId.
 *
 *  @param      arg  argument (PIN_Handle) connected to the callback
 *
 *  @return     none
 *
 ******************************************************************************/
static void buttonClockCb(UArg arg) {
    PIN_Handle buttonHandle = (PIN_State *) arg;

    /* Stop the button clock */
    Clock_stop(hButtonClock);

    /* Check that there is active button for debounce logic*/
    if (activeButtonPinId != PIN_TERMINATE) {
        /* Debounce logic, only toggle if the button is still pushed (low) */
        if (!PIN_getInputValue(activeButtonPinId)) {
            /* Toggle LED based on the button pressed */
            switch (activeButtonPinId) {
            case Board_PIN_BUTTON1:
                Semaphore_post(Semaphore_handle(&shutdownSem));
                break;
            default:
                /* Do nothing */
                break;
            }
        }
    }

    /* Re-enable interrupts to detect button release. */
    PIN_setConfig(buttonHandle, PIN_BM_IRQ, activeButtonPinId | PIN_IRQ_NEGEDGE);

    /* Set activeButtonPinId to none... */
    activeButtonPinId = PIN_TERMINATE;
}

/*!*****************************************************************************
 *  @brief      Button callback
 *
 *  Initiates the debounce period by disabling interrupts, setting a timeout
 *  for the button clock callback and starting the button clock.
 *  Sets the activeButtonPinId.
 *
 *  @param      handle PIN_Handle connected to the callback
 *
 *  @param      pinId  PIN_Id of the DIO triggering the callback
 *
 *  @return     none
 ******************************************************************************/
static void buttonCb(PIN_Handle handle, PIN_Id pinId) {
    /* Set current pinId to active */
    activeButtonPinId = pinId;

    /* Disable interrupts during debounce */
    PIN_setConfig(handle, PIN_BM_IRQ, activeButtonPinId | PIN_IRQ_DIS);

    /* Set timeout 50 ms from now and re-start clock */
    Clock_setTimeout(hButtonClock, (50 * (1000 / Clock_tickPeriod)));
    Clock_start(hButtonClock);
}

/*!*****************************************************************************
 *  @brief      wakeUpTread used to construct power up thread
 *
 *  @param      none
 *
 *  @return     none
 ******************************************************************************/
void wakeUpThread(UArg a0, UArg a1) {

    uint32_t rSrc = SysCtrlResetSourceGet();

    Board_initGeneral();

    /* Do pin init before starting BIOS */
    /* If coming from shutdown, use special gpio table.*/
    if (rSrc == RSTSRC_WAKEUP_FROM_SHUTDOWN) {
        isWakingFromShutdown = true;
        hPins = PIN_open(&LedPinState, LedPinTableSd);
    } else {
        isWakingFromShutdown = false;
        hPins = PIN_open(&LedPinState, LedPinTable);
    }

    /* Setup button pins with ISR */
    hButtons = PIN_open(&buttonState, ButtonTableShutdown);
    PIN_registerIntCb(hButtons, buttonCb);

    /* Construct clock for debounce */
    Clock_Params clockParams;
    Clock_Params_init(&clockParams);
    clockParams.arg = (UArg)hButtons;
    Clock_construct(&buttonClock, buttonClockCb, 0, &clockParams);
    hButtonClock = Clock_handle(&buttonClock);

    /* If we are waking up from shutdown, toggle BLED several times. */
    if (isWakingFromShutdown) {
       uint32_t sleepUs = 500000;
       Task_sleep(sleepUs / Clock_tickPeriod);
       PIN_setOutputValue(hPins, Board_BLED, 0);
       Task_sleep(sleepUs / Clock_tickPeriod);
       PIN_setOutputValue(hPins, Board_BLED, 1);
       Task_sleep(sleepUs / Clock_tickPeriod);
       PIN_setOutputValue(hPins, Board_BLED, 0);
       Task_sleep(sleepUs / Clock_tickPeriod);
       PIN_setOutputValue(hPins, Board_BLED,1);
       Task_sleep(sleepUs / Clock_tickPeriod);
       PIN_setOutputValue(hPins, Board_BLED, 0);
       Task_sleep(sleepUs / Clock_tickPeriod);
    }

    /* Turn on LED0 to indicate active */
    PIN_setOutputValue(hPins, Board_BLED, 1);

    /* Pend on semaphore before going to shutdown */
    //Semaphore_pend(Semaphore_handle(&shutdownSem), BIOS_WAIT_FOREVER);

    /* Turn off BLED */
    PIN_setOutputValue(hPins, Board_BLED, 0);

    /* Configure DIO for wake up from shutdown */
    PINCC26XX_setWakeup(ButtonTableWakeUp);

    /* Go to shutdown */
    // (0, 0);

    /* Should never get here, since shutdown will reset. */
    while (1);
}

/* wakeUpThread_create. */
void wakeUpThread_create(void)  {
    Task_Params taskParams;

    //Configure task
    Task_Params_init(&taskParams);
    taskParams.stack = myTaskStack;
    taskParams.stackSize = sizeof(myTaskStack);
    taskParams.priority = 1;

    Task_construct(&myTask, wakeUpThread, &taskParams, NULL);
}

I declare the extern in main here:

and call wakeUpThread_create in main here:

Finally my questions is where in simplePeripheral or main should I call Power_shutdown(0, 0) if the device fails to connect (or becomes disconnected) for some period of time, say 90 seconds?

Thanks in advance,

Patrick

  • Do you need to use power shut down(100nA)? Or will power standby (1uA)work for your power budget?

    The reason I asked if that if power standby works for you, then all you need is to stop advertising then it will enter standby right away.

    If shutdown is what you need, then you don't need a separate task for it.

    You can first kick off a software timer after disconnecting. Then when it times out, you post an event to trigger shutdown.

    The timer should be kick off in function SimplePeripheral_processGapMessage() :: GAP_LINK_TERMINATED_EVENT when the device disconnects.

    To shutdown the device when peripheral advertise too long, you can register a advertising enable callback and kick off your software timer there. But remember to disable the timer once the device connects.

    In out of box simple_peripheral, it already register an advertising enable callback,  therefore what you need to do is to

    static void SimplePeripheral_processAdvEvent(spGapAdvEventData_t *pEventData)
    {
      switch (pEventData->event)
      {
        case GAP_EVT_ADV_START_AFTER_ENABLE:
          Display_printf(dispHandle, SP_ROW_ADVSTATE, 0, "Adv Set %d Enabled",
                         *(uint8_t *)(pEventData->pBuf));
           // Start timer here
          break;

  • Hi Cristin,

    Great answer, thank you for your completeness!

    My application calls for the device to be active 3 or 4 times per day for 15 minutes to an hour for each occurrence. We're using a CR2032 battery to power this and expect the battery life to be >5 years. For this use case which power mode would you suggest standby or shutdown?

    From low power mode, the device should enter normal advertising/connect mode as a result of a BTN1 push. If after 90 seconds the device has not connected to the client, it should return to low power mode waiting for a BTN1 interrupt. If after being connected to the client it becomes disconnected, it should advertise for 90 seconds to try to reconnect. If it fails to reconnect it should return to low power mode pending an interrupt from BTN1. In light of this additional application information, how would you implement this?

    Thanks again for the help.

    Patrick

  • I would say standby mode is good enough.
  • Thanks YK. I kind of think so too.
  • In your use case, I believe standby is sufficient.

    So what I would recommend you to do is in the SimplePeripheral_processGapMessage:: case GAP_DEVICE_INIT_DONE_EVENT, to move the

    status = GapAdv_enable(advHandleLegacy, GAP_ADV_ENABLE_OPTIONS_USE_MAX , 0); to a event posted by button push.

    static void SimplePeripheral_handleKeys(uint8_t keys)
    {
      if (keys & KEY_LEFT)
      {
        // Check if the key is still pressed. Workaround for possible bouncing.
        if (PIN_getInputValue(Board_PIN_BUTTON0) == 0)
        {
          //start advertising here
        }
      }
    }

    Then start a software timer at the advertising enable callback, stop the advertising after timer expired(90s).

    The out of box software will start advertising when it disconnects, so you don't need to do anything more to take care of that part.

  • Thank you for the suggestion.  I implemented your suggestion as follows:

    in SimplePeripheral_processGapMessage:: case GAP_DEVICE_INIT_DONE_EVENT, I added SimplePeripheral_handleKeys(status) as shown

    and in SimplePeripheral_handleKeys I added a call to start advertising as shown below

    With these changes the 2460r2lp is not seen by the scanning device (iPhone running LightBlue) until the button is pressed - YEAH! However, the radio is still broadcasting even without the button press. I know this because I'm using a TI INA188 instrumentation amplifier in differential mode to measure the voltage drop across a precision 1 ohm resistor on the return side of the power supply. With a 100X gain on the INA188, my scope is measuring current - 100mV = 1mA.

    With the CC2640r2lp turned on but not visible to my iPhone, the current draw suggests it's advertising.

    This looks the same as when I'm advertising and visible on my iPhone.

    For completeness, following is the current draw when connected.

    So, my question is, do I need to do something else to actually prevent the radio from advertising. The change I've made prevents the broadcast signal from being parsed by the client, but the signal is still being transmitted thus consuming power in standby mode.

    Thanks,

    Patrick

  • In ble5_simple_peripheral example, it advertises in both 1M PHY and Coded PHY. When it advertises using coded phy, most phones won't see it due to it's not widely supported yet.

    Did you comment out all the code regarding Coded PHY advertisement before you run the analysis?

    For example, this is Coded PHY related advertising code. You need to walk through the software to comment out all. (It's not that much.)

            // Use long range params to create long range set #2
            GapAdv_params_t advParamLongRange = GAPADV_PARAMS_AE_LONG_RANGE_CONN;
    
            // Create Advertisement set #2 and assign handle
            status = GapAdv_create(&SimplePeripheral_advCallback, &advParamLongRange,
                                   &advHandleLongRange);
            SIMPLEPERIPHERAL_ASSERT(status == SUCCESS);
    
            // Load advertising data for set #2 that is statically allocated by the app
            status = GapAdv_loadByHandle(advHandleLongRange, GAP_ADV_DATA_TYPE_ADV,
                                         sizeof(advertData), advertData);
            SIMPLEPERIPHERAL_ASSERT(status == SUCCESS);
    
            // Load scan response data for set #2 that is statically allocated by the app
            status = GapAdv_loadByHandle(advHandleLongRange, GAP_ADV_DATA_TYPE_SCAN_RSP,
                                         sizeof(scanRspData), scanRspData);
            SIMPLEPERIPHERAL_ASSERT(status == SUCCESS);
    
            // Set event mask for set #2
            status = GapAdv_setEventMask(advHandleLongRange,
                                         GAP_ADV_EVT_MASK_START_AFTER_ENABLE |
                                         GAP_ADV_EVT_MASK_END_AFTER_DISABLE |
                                         GAP_ADV_EVT_MASK_SET_TERMINATED);
    
            // Enable long range advertising for set #2
            status = GapAdv_enable(advHandleLongRange, GAP_ADV_ENABLE_OPTIONS_USE_MAX , 0);
            SIMPLEPERIPHERAL_ASSERT(status == SUCCESS);

  • Hi Christin,

    After many days of experimentation, I've settled on the implementation shown below. Now, when I start the debugger and the code launches, the device is not advertising (as I intended). I press the button, and the device begins to advertise. It will stay in this state forever [status = GapAdv_enable(advHandleLegacy, GAP_ADV_ENABLE_OPTIONS_USE_MAX , 0);]. Using LightBlue, I connect to the device. When I disconnect, the device advertises for a period of time that I define then stops advertising (as I intended). At this point, I expect that the device is in the same state is was when I initiated the debug session. But the button no longer initiates advertising. Do I need to do something in SP_processGapMessage::GAP_LINK_TERMINATED_EVENT to light up the radio after the terminated connection?

    Thanks,

    Patrick

    static void SimplePeripheral_handleKeys(uint8_t keys)
    {
        bStatus_t status = SUCCESS;
    
      if (keys & KEY_LEFT)
      {
        // Check if the key is still pressed. Workaround for possible bouncing.
        if (PIN_getInputValue(Board_PIN_BUTTON0) == 1);
        {
          status = PIN_getInputValue(Board_PIN_BUTTON0);
          status = GapAdv_enable(advHandleLegacy, GAP_ADV_ENABLE_OPTIONS_USE_MAX , 0);
          SIMPLEPERIPHERAL_ASSERT(status == SUCCESS);
          tbm_buttonLeft();
        }

    Here is the GAP_LINK_TERMINATED_EVENT - note that it stops advertising after 5 seconds.

     case GAP_LINK_TERMINATED_EVENT:
        {
          gapTerminateLinkEvent_t *pPkt = (gapTerminateLinkEvent_t *)pMsg;
    
          // Display the amount of current connections
          uint8_t numActive = linkDB_NumActive();
          Display_printf(dispHandle, SP_ROW_STATUS_1, 0, "Device Disconnected!");
          Display_printf(dispHandle, SP_ROW_STATUS_2, 0, "Num Conns: %d",
                         (uint16_t)numActive);
    
          // Remove the connection from the list and disable RSSI if needed
          SimplePeripheral_removeConn(pPkt->connectionHandle);
    
          // If no active connections
          if (numActive == 0)
          {
            // Stop periodic clock
            Util_stopClock(&clkPeriodic);
    
            // Disable Connection Selection option
            tbm_setItemStatus(&spMenuMain, TBM_ITEM_NONE, SP_ITEM_SELECT_CONN);
          }
    
          // Start advertising since there is room for more connections
          GapAdv_enable(advHandleLegacy, GAP_ADV_ENABLE_OPTIONS_USE_DURATION , 500);
           GapAdv_enable(advHandleLongRange, GAP_ADV_ENABLE_OPTIONS_USE_DURATION , 500);
    
          // Clear remaining lines
          Display_clearLine(dispHandle, SP_ROW_CONNECTION);
    
          break;

  • Hi,

    There is a bug in GapAdv_enable(advHandleLongRange, GAP_ADV_ENABLE_OPTIONS_USE_DURATION , 500); function when the mode is set to USE DURATION.
    Can you try to use a software timer to disable the advertising instead?

    BTW, you mentioned that button can no longer activate the advertising, have you checked if you were able to hit the handlekeys function when you pressed button again?
  • Hi Christin,

    Yes, the button press loops back to hit the handlekeys function. I'll try the software timer.

    Thanks,

    Patrick

  • Hello Christin,

    This bug was the source of another problem that I encountered. After disconnecting and halting advertising after the prescribed period, I was unable to reconnect to the device without power cycling. I changed GapAdv_enable(advHandleLongRange, GAP_ADV_ENABLE_OPTIONS_USE DURATION, 500) to GapAdv_enable(advHandleLongRange, GAP_ADV_ENABLE_OPTIONS_USE DURATION, 0). With that change, the device enters standby mode after advertising has stopped, and can be awakened with a button push as intended - yeah.

    Thanks for you help,

    Patrick