QN902x的低功耗模式分析

这里写图片描述

系统最终进入何种状态,关键看懂这种表就OK了。
USR 和 BLE只要有一个是Active的, 系统就是Active
进入Deep Sleep的条件是:BLE Sleep, USR Deep Sleep
进入Sleep的条件:BLE Sleep,USR sleep
其他为IDLE

Main 函数里有这一个while(1)循环

    while(1)
    {
        ke_schedule(); // Run Scheduler,检查消息队列,执行消息投递

        // Checks for sleep have to be done with interrupt disabled
        GLOBAL_INT_DISABLE_WITHOUT_TUNER();

        // Check whether the chip can enters sleep mode
        //
        // Chip enter sleep condition:
        // +--------+--------+--------+--------+--------+
        // |    USR |        |        |        |        |
        // | BLE    | ACTIVE | IDLE   | SLEEP  | DEEP   |
        // +--------+--------+--------+--------+--------+
        // | ACTIVE | active | active | active | active |
        // | IDLE   | active | idle   | idle   | idle   |
        // | SLEEP  | active | idle   | sleep  | deep   |
        // +--------+--------+--------+--------+--------+

        // Obtain the status of the user program
        usr_sleep_st = usr_sleep();

        // If the user program can be sleep or deep sleep then check ble status
        if(usr_sleep_st != PM_ACTIVE)
        {
            // Obtain the status of ble sleep mode
            ble_sleep_st = ble_sleep(usr_sleep_st);

            // Check if the processor clock can be gated
            if(((ble_sleep_st == PM_IDLE) || (usr_sleep_st == PM_IDLE))
             && (ble_sleep_st != PM_ACTIVE))
            {
                enter_sleep(SLEEP_CPU_CLK_OFF,
                            WAKEUP_BY_ALL_IRQ_SOURCE,
                            NULL);
            }

            // Check if the processor can be power down
            else if((ble_sleep_st == PM_SLEEP) && (usr_sleep_st == PM_SLEEP))
            {
                enter_sleep(SLEEP_NORMAL,
                            (WAKEUP_BY_OSC_EN | WAKEUP_BY_GPIO),
                            sleep_cb);
            }

            // Check if the system can be deep sleep
            else if((ble_sleep_st == PM_SLEEP) && (usr_sleep_st == PM_DEEP_SLEEP))
            {
                enter_sleep(SLEEP_DEEP,
                            WAKEUP_BY_GPIO,
                            sleep_cb);
            }
        }

        // Checks for sleep have to be done with interrupt disabled
        GLOBAL_INT_RESTORE_WITHOUT_TUNER();
    }

ke_schedule(),用来Run Scheduler, 它检查消息队列,执行消息投递
GLOBAL_INT_DISABLE_WITHOUT_TUNER()的定义如下,它是和GLOBAL_INT_RESTORE_WITHOUT_TUNER配合一前一后一起使用的:

#define GLOBAL_INT_DISABLE_WITHOUT_TUNER()  \
do {                                        \
    uint32_t int_restore;                   \
    int_restore = NVIC->ISER[0];            \
    NVIC->ICER[0] = 0x1fffffff;

/** @brief Restore interrupts from the previous global disable.
 * @sa GLOBAL_INT_DISABLE
 */

#define GLOBAL_INT_RESTORE_WITHOUT_TUNER()  \
    NVIC->ISER[0] = int_restore;            \
} while(0)

刚开始时关闭所有中断,最后是恢复原来的中断使能状态。

usr_sleep_st = usr_sleep(); 是用来USR的status

看看这两个语句(这两句位于while(1)之前的初始化部分)

sleep_init();
wakeup_by_sleep_timer(__32K_TYPE);
/**
 ****************************************************************************************
 * @brief  Init sleep power down modules
 * @description
 *  This function is used to init MCU sleep mode.
 *****************************************************************************************
 */
void sleep_init(void)
{
    // --------------------------------------------
    // sleep
    // --------------------------------------------

    //23 : PD_XTAL32
    //10 : PD_RCO
    // 7 : PD_MEM7
    // 6 : PD_MEM6
    // 5 : PD_MEM5
    // 4 : PD_MEM4
    // 3 : PD_MEM3
    // 2 : PD_MEM2
    // 1 : PD_MEM1
    // 0 : PL_VREG_D
    sleep_env.retention_modules |= QN_MEM_RETENTION;

    // power down all module in sleep except retention modules
    syscon_SetPGCR0WithMask(QN_SYSCON, 0xF7FFFCFE, (0xFFFFFC00 | QN_MEM_UNRETENTION));

    // power down all unretention memory all the time.
    // if you want to use the unretention memory in the active mode, remove the following snippet.
    syscon_SetPGCR1WithMask(QN_SYSCON, 
                            (SYSCON_MASK_DIS_MEM1
                            | SYSCON_MASK_DIS_MEM2
                            | SYSCON_MASK_DIS_MEM3
                            | SYSCON_MASK_DIS_MEM4
                            | SYSCON_MASK_DIS_MEM5
                            | SYSCON_MASK_DIS_MEM6
                            | SYSCON_MASK_DIS_MEM7),
                            QN_MEM_UNRETENTION);
}

syscon_SetPGCR0WithMask(QN_SYSCON, 0xF7FFFCFE, (0xFFFFFC00 | QN_MEM_UNRETENTION)); 这句话是用来控制在sleep mode下memory power是否需要打开,0表示 switch on,1表示switch off。

syscon_SetPGCR1WithMask(QN_SYSCON,
(SYSCON_MASK_DIS_MEM1
| SYSCON_MASK_DIS_MEM2
| SYSCON_MASK_DIS_MEM3
| SYSCON_MASK_DIS_MEM4
| SYSCON_MASK_DIS_MEM5
| SYSCON_MASK_DIS_MEM6
| SYSCON_MASK_DIS_MEM7),
QN_MEM_UNRETENTION);
是在正常模式下是否switch off。

/// Memory retention
#if (defined(CFG_MEM_RETENTION))
    #define QN_MEM_RETENTION        CFG_MEM_RETENTION
    #define QN_MEM_UNRETENTION      (~(CFG_MEM_RETENTION) & 0xfe)
#else
    #define QN_MEM_RETENTION        (MEM_BLOCK1 | MEM_BLOCK2 | MEM_BLOCK3 | MEM_BLOCK4 | MEM_BLOCK5 | MEM_BLOCK6 | MEM_BLOCK7)
    #define QN_MEM_UNRETENTION      0
#endif
#define CFG_MEM_RETENTION   (MEM_BLOCK1 | MEM_BLOCK2 | MEM_BLOCK6 | MEM_BLOCK7)
/**
 *
 * @param *SYSCON SYSCON Pointer
 * @param value The value to set to PGCR0 register
 *
 * @brief  outputs specified value to PGCR0 register
 *
 *  |      31 : SEL_PD
 *  |      30 : PD_OSC
 *  |      29 : PD_BG
 *  |      28 : PD_V2I
 *  |      27 : PD_BUCK
 *  |      26 : PD_VREG_A
 *  |      25 : PD_VREG_D
 *  |      24 : PD_XTAL
 *  |      23 : PD_XTAL32
 *  |      22 : PD_REF_PLL_B0
 *  |      22 : DIV_RST_SYNC_B1
 *  |      21 : PD_LO_VCO
 *  |      20 : PD_LO_PLL
 *  |      19 : PD_PA
 *  |      18 : PD_LNA
 *  |      17 : PD_LNA_PKDET
 *  |      16 : PD_MIXER
 *  |      15 : PD_PPF_PKDET
 *  |      14 : PD_PPF
 *  |      13 : PD_RX_PKDET
 *  |      12 : PD_RX_ADC
 *  |      11 : PD_SAR_ADC
 *  |      10 : PD_RCO
 *  |       9 : BOND_EN
 *  |       8 : RSVD
 *  |       7 : PD_MEM7
 *  |       6 : PD_MEM6
 *  |       5 : PD_MEM5
 *  |       4 : PD_MEM4
 *  |       3 : PD_MEM3
 *  |       2 : PD_MEM2
 *  |       1 : PD_MEM1
 *  |       0 : PL_VERG_D
 */
 __STATIC_INLINE void syscon_SetPGCR0(QN_SYSCON_TypeDef *SYSCON, uint32_t value)
 {
     __wr_reg((uint32_t)&SYSCON->PGCR0, value);
 }

 __STATIC_INLINE void syscon_SetPGCR0WithMask(QN_SYSCON_TypeDef *SYSCON, uint32_t mask, uint32_t value)
 {
     __wr_reg_with_msk((uint32_t)&SYSCON->PGCR0, mask, value);
 }
/**
 *
 * @param *SYSCON SYSCON Pointer
 * @param value The value to set to PGCR1 register
 *
 * @brief  outputs specified value to PGCR1 register
 *
 *  |      31 : VDD_RCO_SET
 *  |      30 : DIS_OSC
 *  |      29 : DIS_BG
 *  |      28 : DIS_V2I
 *  |      27 : DIS_BUCK
 *  |      26 : DIS_VREG_A
 *  |      25 : DIS_VREG_D
 *  |      24 : DIS_XTAL
 *  |      23 : DIS_XTAL32
 *  |      22 : DIS_REF_PLL
 *  |      21 : DIS_LO_VCO
 *  |      20 : DIS_LO_PLL
 *  |      19 : DIS_PA
 *  |      18 : DIS_LNA
 *  |      17 : DIS_LNA_PKDET
 *  |      16 : DIS_MIXER
 *  |      15 : DIS_PPF_PKDET
 *  |      14 : DIS_PPF
 *  |      13 : DIS_RX_PKDET
 *  |      12 : DIS_RX_ADC
 *  |      11 : DIS_SAR_ADC
 *  |      10 : DIS_RCO
 *  |       9 : RSVD
 *  |       8 : RSVD
 *  |       7 : DIS_MEM7
 *  |       6 : DIS_MEM6
 *  |       5 : DIS_MEM5
 *  |       4 : DIS_MEM4
 *  |       3 : DIS_MEM3
 *  |       2 : DIS_MEM2
 *  |       1 : DIS_MEM1
 *  |       0 : DIS_SAR_BUF
 */
 __STATIC_INLINE void syscon_SetPGCR1(QN_SYSCON_TypeDef *SYSCON, uint32_t value)
 {
     __wr_reg((uint32_t)&SYSCON->PGCR1, value);
 }

 __STATIC_INLINE void syscon_SetPGCR1WithMask(QN_SYSCON_TypeDef *SYSCON, uint32_t mask, uint32_t value)
 {
     __wr_reg_with_msk((uint32_t)&SYSCON->PGCR1, mask, value);
 }
/// Memory block
enum MEM_BLOCK
{
    MEM_BLOCK0    = 0,          /*!< Memory Block1:  0K ~  8K */
    MEM_BLOCK1    = (1 << 1),   /*!< Memory Block1:  8K ~ 16K */
    MEM_BLOCK2    = (1 << 2),   /*!< Memory Block1: 16K ~ 24K */
    MEM_BLOCK3    = (1 << 3),   /*!< Memory Block1: 24K ~ 32K */
    MEM_BLOCK4    = (1 << 4),   /*!< Memory Block1: 32K ~ 40K */
    MEM_BLOCK5    = (1 << 5),   /*!< Memory Block1: 40K ~ 48K */
    MEM_BLOCK6    = (1 << 6),   /*!< Memory Block1: 48K ~ 56K */
    MEM_BLOCK7    = (1 << 7),   /*!< Memory Block1: 56K ~ 64K */
    MEM_ALL       = (0xFE)      /*!< Memory Block1: 56K ~ 64K */
};

64K SRAM被分为了8个bank:bank0-bank7
bank6 bank7 是给BLE Stack用的,Bank0-Bank5 是用户使用的。

下面看一下usr_sleep的具体代码实现:

#ifdef BLE_PRJ
/**
 ****************************************************************************************
 * @brief   Check application whether to enter sleep mode
 * @return  sleep allowed status
 ****************************************************************************************
 */
int usr_sleep(void)
{
    int32_t rt;

    rt = sleep_get_pm(); // 获取允许的低功耗状态,这个结合sleep_set_pm()函数看

    // If the BLE timer queue is not NULL or BLE event is exist, prevent entering into DEEPSLEEP mode
    if(rt == PM_DEEP_SLEEP && 
       (!ke_timer_empty() || !ble_evt_empty()))
    {
        rt = PM_SLEEP;
    }

    // Check Device status
    if((rt >= PM_SLEEP)
       && dev_get_bf())
    {
        // If any devices are still working, the chip cann't enter into SLEEP/DEEPSLEEP mode.
        rt = PM_IDLE;
    }

    if ((rt >= PM_SLEEP) && (!gpio_sleep_allowed()))
    {
        return PM_ACTIVE;
    }

#if ACMP_WAKEUP_EN == TRUE
    if ((rt >= PM_SLEEP) && (!acmp_sleep_allowed()))
    {
        return PM_ACTIVE;
    }
#endif


#if QN_DBG_PRINT
    int uart_tx_st = uart_check_tx_free(QN_DEBUG_UART);

    if((rt >= PM_SLEEP) && (uart_tx_st == UART_TX_BUF_BUSY))
    {
        rt = PM_IDLE;
    }
    else if(uart_tx_st == UART_LAST_BYTE_ONGOING)
    {
        return PM_ACTIVE;    // If CLOCK OFF & POWER DOWN is disabled, return immediately
    }
#endif

#if QN_EACI
    if ((rt >= PM_SLEEP) &&
        (  (eaci_env.tx_state!=EACI_STATE_TX_IDLE)              // Check EACI UART TX status
        || (eaci_env.rx_state!=EACI_STATE_RX_START)) )          // Check EACI UART RX status
    {
        rt = PM_IDLE;
    }

    int tx_st = 0;
    #if (defined(CFG_HCI_UART))
    tx_st = uart_check_tx_free(QN_HCI_PORT);
    if ((rt >= PM_SLEEP) && (tx_st == UART_TX_BUF_BUSY))
    {
        rt = PM_IDLE;
    }
    else if (tx_st == UART_LAST_BYTE_ONGOING)
    {
        return PM_ACTIVE;    // If CLOCK OFF & POWER DOWN is disabled, return immediately
    }
    #elif (defined(CFG_HCI_SPI))
    tx_st = spi_check_tx_free(QN_HCI_PORT);
    if ((rt >= PM_SLEEP) && (tx_st == SPI_TX_BUF_BUSY))
    {
        rt = PM_IDLE;
    }
    else if (tx_st == SPI_LAST_BYTE_ONGOING)
    {
        return PM_ACTIVE;    // If CLOCK OFF & POWER DOWN is disabled, return immediately
    }
    #endif

#endif

#if !(QN_32K_RCO)
    // wait for 32k xtal ready
    if(syscon_GetBLESR(QN_SYSCON) & SYSCON_MASK_CLK_XTAL32_RDY)
    {
        // disable schmitt trigger in 32.768KHz buffer
        syscon_SetIvrefX32WithMask(QN_SYSCON, SYSCON_MASK_X32SMT_EN, MASK_DISABLE);
        // Set 32.768KHz xtal to normal current
        syscon_SetIvrefX32WithMask(QN_SYSCON, SYSCON_MASK_X32ICTRL, 16);
    }
    else if(rt > PM_ACTIVE)
    {
        rt = PM_ACTIVE;
    }
#endif

    return rt;
}
#endif

下面实际测试下QN902x的功耗,使用QPPS工程,由于手上没有专门的功耗测试仪器,用万用表简单测试下。

修改下QPPS工程,将GAP_ADV_FAST_INTV1和GAP_ADV_FAST_INTV2的值做下改动,

#define GAP_ADV_FAST_INTV1                                  0x00A0 // 100ms

#define GAP_ADV_FAST_INTV2                                  0x00A0 // 100ms

快速广播间隔设置为100ms,测得deep sleep功耗为2.8uA,
这里写图片描述

广播的平均电流为172.6uA
这里写图片描述

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值