QN902x的低功耗模式分析

举报
TopSemic嵌入式 发表于 2021/10/18 23:30:07 2021/10/18
【摘要】 系统最终进入何种状态,关键看懂这种表就OK了。 USR 和 BLE只要有一个是Active的, 系统就是Active 进入Deep Sleep的条件是:BLE Sleep, USR Deep Sl...

这里写图片描述

系统最终进入何种状态,关键看懂这种表就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();
    }
  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57

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)
  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

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

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

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

sleep_init();
wakeup_by_sleep_timer(__32K_TYPE);
  
 
  • 1
  • 2
/**
 ****************************************************************************************
 * @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);
}
  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40

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
  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
#define CFG_MEM_RETENTION   (MEM_BLOCK1 | MEM_BLOCK2 | MEM_BLOCK6 | MEM_BLOCK7)
  
 
  • 1
/**
 *
 * @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);
 }
  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
/**
 *
 * @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);
 }
  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
/// 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 */
};
  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

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
  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105

下面实际测试下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
  
 
  • 1
  • 2
  • 3

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

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

文章来源: blog.csdn.net,作者:TopSemic嵌入式,版权归原作者所有,如需转载,请联系作者。

原文链接:blog.csdn.net/wangwenxue1989/article/details/51682400

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。