打印
[其他ST产品]

STM32通用低功耗组件——PM

[复制链接]
楼主: 欢乐家园
手机看帖
扫描二维码
随时随地手机跟帖
21
欢乐家园|  楼主 | 2022-4-29 14:11 | 只看该作者 |只看大图 回帖奖励 |倒序浏览
设置进入/退出休眠模式的回调通知

void rt_pm_notify_set(void (*notify)(uint8_t event, uint8_t mode, void *data), void *data);

使用特权

评论回复
22
欢乐家园|  楼主 | 2022-4-29 14:11 | 只看该作者
event 为以下两个枚举值,分别标识进入/退出休眠模式。
enum
{
    RT_PM_ENTER_SLEEP = 0,    /* 进入休眠模式 */
    RT_PM_EXIT_SLEEP,         /* 退出休眠模式 */
};
在应用进入/退出休眠模式会触发回调通知。下面是具体代码实现:
void rt_pm_notify_set(void (*notify)(rt_uint8_t event, rt_uint8_t mode, void *data), void *data)
{
   _pm_notify.notify = notify;
   _pm_notify.data = data;
}

使用特权

评论回复
23
欢乐家园|  楼主 | 2022-4-29 14:12 | 只看该作者
注册PM设备
void rt_pm_device_register(struct rt_device *device, const struct rt_device_pm_ops *ops)

与应用不同,某些外设可能在进入低功耗状态时执行特定操作,退出低功耗时采取措施恢复,此时可以通过注册PM设备来实现。通过注册 PM 设备,在进入低功耗状态之前,会触发注册设备的 suspend 回调,开发者可在回调里执行自己的操作;类似地,从低功耗状态退出时,也会触发 resume 回调。

使用特权

评论回复
24
欢乐家园|  楼主 | 2022-4-29 14:13 | 只看该作者
运行模式下的频率改变同样会触发设备的 frequency_change 回调。下面是具体代码实现:
void rt_pm_device_register(struct rt_device *device, const struct rt_device_pm_ops *ops)
{
   rt_base_t level;
   struct rt_device_pm *device_pm;

   RT_DEBUG_NOT_IN_INTERRUPT;

   level = rt_hw_interrupt_disable();

   device_pm = (struct rt_device_pm *)RT_KERNEL_REALLOC(_pm.device_pm,
              (_pm.device_pm_number + 1) * sizeof(struct rt_device_pm));
   if (device_pm != RT_NULL)
  {
       _pm.device_pm = device_pm;
       _pm.device_pm[_pm.device_pm_number].device = device;
       _pm.device_pm[_pm.device_pm_number].ops    = ops;
       _pm.device_pm_number += 1;
  }

   rt_hw_interrupt_enable(level);
}

使用特权

评论回复
25
欢乐家园|  楼主 | 2022-4-29 14:14 | 只看该作者
设置进入/退出休眠模式的回调通知和注册为设备的回调通知流程:

首先应用设置进出休眠状态的回调函数,然后调用 rt_pm_request 请求休眠模式,触发休眠操作;PM 组件在系统空闲时检查休眠模式计数,根据投票数给出推荐的模式;接着 PM 组件调用 notfiy 通知应用,告知即将进入休眠模式;然后对注册的 PM 设备执行挂起操作,返回 OK 后执行 SOC 实现的的休眠模式,系统进入休眠状态(如果使能时间补偿,休眠之前会先启动低功耗定时器)。此时 CPU 停止工作,等待事件或者中断唤醒。当系统被唤醒后,由于全局中断为关闭状态,系统继续从该处执行,获取睡眠时间补偿系统的心跳,依次唤醒设备,通知应用从休眠模式退出。如此一个周期执行完毕,退出,等待系统下次空闲。

使用特权

评论回复
26
欢乐家园|  楼主 | 2022-4-29 14:16 | 只看该作者
模式的切换代码实现:当任务进入到空闲线程,最终是调用此函数进入低功耗和唤醒的
static void _pm_change_sleep_mode(struct rt_pm *pm, rt_uint8_t mode)
{
    rt_tick_t timeout_tick, delta_tick;
    rt_base_t level;
    int ret = RT_EOK;

    if (mode == PM_SLEEP_MODE_NONE)
    {
        pm->sleep_mode = mode;
        pm->ops->sleep(pm, PM_SLEEP_MODE_NONE);
    }
    else
    {
        level = rt_pm_enter_critical(mode);

        /* Notify app will enter sleep mode */
        if (_pm_notify.notify)
            _pm_notify.notify(RT_PM_ENTER_SLEEP, mode, _pm_notify.data);

        /* Suspend all peripheral device */
        ret = _pm_device_suspend(mode);
        if (ret != RT_EOK)
        {
            _pm_device_resume(mode);
            if (_pm_notify.notify)
                _pm_notify.notify(RT_PM_EXIT_SLEEP, mode, _pm_notify.data);
            rt_pm_exit_critical(level, mode);

            return;
        }

        /* Tickless*/
        if (pm->timer_mask & (0x01 << mode))
        {
            timeout_tick = rt_timer_next_timeout_tick();
            if (timeout_tick == RT_TICK_MAX)
            {
                if (pm->ops->timer_start)
                {
                    pm->ops->timer_start(pm, RT_TICK_MAX);
                }
            }
            else
            {
                timeout_tick = timeout_tick - rt_tick_get();
                if (timeout_tick < RT_PM_TICKLESS_THRESH)
                {
                    mode = PM_SLEEP_MODE_IDLE;
                }
                else
                {
                    pm->ops->timer_start(pm, timeout_tick);
                }
            }
        }

        /* enter lower power state */
        pm->ops->sleep(pm, mode);

        /* wake up from lower power state*/
        if (pm->timer_mask & (0x01 << mode))
        {
            delta_tick = pm->ops->timer_get_tick(pm);
            pm->ops->timer_stop(pm);
            if (delta_tick)
            {
                rt_tick_set(rt_tick_get() + delta_tick);
                rt_timer_check();
            }
        }

        /* resume all device */
        _pm_device_resume(pm->sleep_mode);

        if (_pm_notify.notify)
            _pm_notify.notify(RT_PM_EXIT_SLEEP, mode, _pm_notify.data);

        rt_pm_exit_critical(level, mode);
    }
}

使用特权

评论回复
27
欢乐家园|  楼主 | 2022-4-29 14:17 | 只看该作者
移植的实现原理
RT-Thread 低功耗管理系统从设计上分离运行模式和休眠模式,独立管理,运行模式用于变频和变电压,休眠调用芯片的休眠特性。对于多数芯片和开发来说,可能并不需要考虑变频和变电压,仅需关注休眠模式。底层功能的实现已经有Sunwancn大神对STM32做了全系列的适配,以下是底层实现原理,用户也可以自行根据自身情况对底层进行裁剪或增强。(注意: 驱动可能有更新,移植请到gitee下载最新pm驱动。地址在pm-ports-stm32-new 分支:https://gitee.com/sunwancn/rt-thread/tree/pm-ports-stm32-new)

使用特权

评论回复
28
欢乐家园|  楼主 | 2022-4-29 14:18 | 只看该作者
PM 组件的底层功能都是通过struct rt_pm_ops结构体里的函数完成:

/**
* low power mode operations
*/
struct rt_pm_ops
{
   void (*sleep)(struct rt_pm *pm, uint8_t mode);
   void (*run)(struct rt_pm *pm, uint8_t mode);
   void (*timer_start)(struct rt_pm *pm, rt_uint32_t timeout);
   void (*timer_stop)(struct rt_pm *pm);
   rt_tick_t (*timer_get_tick)(struct rt_pm *pm);
};
移植休眠模式移植休眠模式仅需关注 sleep 接口,下面是具体的实现:
void stm32_sleep(struct rt_pm *pm, rt_uint8_t mode)
{
    switch (mode)
    {
    case PM_SLEEP_MODE_NONE:
        break;

    case PM_SLEEP_MODE_IDLE:
        if (pm->run_mode == PM_RUN_MODE_LOW_SPEED)
        {
            /* Enter LP SLEEP Mode, Enable low-power regulator */
            HAL_PWR_EnterSLEEPMode(PWR_LOWPOWERREGULATOR_ON, PWR_SLEEPENTRY_WFI);
        }
        else
        {
            /* Enter SLEEP Mode, Main regulator is ON */
            HAL_PWR_EnterSLEEPMode(PWR_MAINREGULATOR_ON, PWR_SLEEPENTRY_WFI);
        }
        break;

    case PM_SLEEP_MODE_LIGHT:
        if (pm->run_mode == PM_RUN_MODE_LOW_SPEED)
        {
            __HAL_FLASH_SLEEP_POWERDOWN_ENABLE();
            /* Enter LP SLEEP Mode, Enable low-power regulator */
            HAL_PWR_EnterSLEEPMode(PWR_LOWPOWERREGULATOR_ON, PWR_SLEEPENTRY_WFI);
            __HAL_FLASH_SLEEP_POWERDOWN_DISABLE();
        }
        else
        {
            /* Enter SLEEP Mode, Main regulator is ON */
            HAL_PWR_EnterSLEEPMode(PWR_MAINREGULATOR_ON, PWR_SLEEPENTRY_WFI);
        }
        break;

    case PM_SLEEP_MODE_DEEP:
        /* Disable SysTick interrupt */
        CLEAR_BIT(SysTick->CTRL, (rt_uint32_t)SysTick_CTRL_TICKINT_Msk);

        if (pm->run_mode == PM_RUN_MODE_LOW_SPEED)
        {
            /* Clear LPR bit to back the normal run mode */
            CLEAR_BIT(PWR->CR1, PWR_CR1_LPR);
            /* Enter STOP 2 mode  */
            HAL_PWREx_EnterSTOP2Mode(PWR_STOPENTRY_WFI);
            /* Set Regulator parameter to lowpower run mode */
            SET_BIT(PWR->CR1, PWR_CR1_LPR);
        }
        else
        {
            /* Enter STOP 2 mode  */
            HAL_PWREx_EnterSTOP2Mode(PWR_STOPENTRY_WFI);
        }

        /* Enable SysTick interrupt */
        SET_BIT(SysTick->CTRL, (rt_uint32_t)SysTick_CTRL_TICKINT_Msk);
        /* Re-configure the system clock */
        systemclock_reconfig(pm->run_mode);
        break;

    case PM_SLEEP_MODE_STANDBY:
        __HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);
        /* Enter STANDBY mode */
        HAL_PWR_EnterSTANDBYMode();
        break;

    case PM_SLEEP_MODE_SHUTDOWN:
        __HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);
        /* Enter SHUTDOWNN mode */
        HAL_PWREx_EnterSHUTDOWNMode();
        break;

    default:
        RT_ASSERT(0);
        break;
    }
}

使用特权

评论回复
29
欢乐家园|  楼主 | 2022-4-29 14:39 | 只看该作者
移植时间补偿接口某些情况下,我们可能需要系统在空闲时进入 Stop 模式,以达到更低的降功耗效果。根据手册可知,Stop 2 模式会关闭系统时钟,当前的 OS Tick 基于内核的 Systick 定时器。那么在系统时钟停止后,OS Tick 也会停止,对于某些依赖 OS Tick 的应用,在进入 Stop 2 模式,又被中断唤醒后,就会出现问题,因此需要在系统唤醒后,对 OS Tick 进行补偿。Stop 2 模式下,绝大多数外设都停止工作,仅低功耗定时器 1(LP_TIM1)和RTC,选择 LSI 作为时钟源后,仍然能正常运行,所以可以选择 LP_TIM1 或者RTC 作为 Stop 2 模式的时间补偿定时器。

使用特权

评论回复
30
欢乐家园|  楼主 | 2022-4-29 14:41 | 只看该作者
休眠的时间补偿需要实现三个接口,分别用于启动低功耗定时器、停止定时器、唤醒后获取休眠的 Tick,下面是具体的实现:

static void stm32_pm_timer_start(struct rt_pm *pm, rt_uint32_t timeout)
{
   RT_ASSERT(pm != RT_NULL);
   RT_ASSERT(timeout > 0);

   if (timeout != RT_TICK_MAX)
  {
       /* Convert OS Tick to PM timer timeout value */
       timeout = stm32_pm_tick_from_os_tick(timeout);
       if (timeout > stm32_pmtim_get_tick_max())
      {
           timeout = stm32_pmtim_get_tick_max();
      }

       /* Enter PM_TIMER_MODE */
       stm32_pmtim_start(timeout);
  }
}
static void stm32_pm_timer_stop(struct rt_pm *pm)
{
    RT_ASSERT(pm != RT_NULL);

    /* Reset PM timer status */
    stm32_pmtim_stop();
}
static rt_tick_t stm32_pm_timer_get_tick(struct rt_pm *pm)
{
   rt_uint32_t timer_tick;

   RT_ASSERT(pm != RT_NULL);

   timer_tick = stm32_pmtim_get_current_tick();

   return stm32_os_tick_from_pm_tick(timer_tick);
}
休眠时间补偿的移植相对并不复杂,根据 Tick 配置低功耗定时器超时,唤醒后获取实际休眠时间并转换为OS Tick,告知 PM 组件即可。

使用特权

评论回复
31
欢乐家园|  楼主 | 2022-4-29 14:42 | 只看该作者
移植运行模式移植休眠模式仅需关注 run接口,下面是具体的实现:

void stm32_run(struct rt_pm *pm, rt_uint8_t mode)
{
    static rt_uint32_t last_mode;
    static char *run_str[] = PM_RUN_MODE_NAMES;
    struct rcc_conf_struct sconf = _rcc_conf[mode];

    if (mode == last_mode)
        return;

    if (stm32_run_freq[mode][0] != stm32_run_freq[last_mode][0])
    {
        if (_rcc_conf[last_mode].low_pow_run_en && !sconf.low_pow_run_en)
        {
            /* Disable the Low-power Run mode */
            HAL_PWREx_DisableLowPowerRunMode();
        }

        systemclock_msi_on(last_mode);

        if (mode < last_mode)
        {
            /* Frequency increase */
            HAL_PWREx_ControlVoltageScaling(sconf.volt_scale);

            _set_sysclock[mode]();
        }
        else
        {
            /* Frequency reduce */
            _set_sysclock[mode]();

            HAL_PWREx_ControlVoltageScaling(sconf.volt_scale);
        }

        if (sconf.volt_scale == PWR_REGULATOR_VOLTAGE_SCALE2 || _osc_conf.osc_type == RCC_OSCILLATORTYPE_MSI)
        {
            /* Configure the wake up from stop clock to MSI */
            __HAL_RCC_WAKEUPSTOP_CLK_CONFIG(RCC_STOP_WAKEUPCLOCK_MSI);
        }
        else
        {
            /* Configure the wake up from stop clock to HSI */
            __HAL_RCC_WAKEUPSTOP_CLK_CONFIG(RCC_STOP_WAKEUPCLOCK_HSI);
        }

        if (sconf.low_pow_run_en)
        {
            /* Enable the Low-power Run mode */
            HAL_PWREx_EnableLowPowerRunMode();
        }

        systemclock_msi_off(mode);

#if defined(RT_USING_SERIAL)
        /* Re-Configure the UARTs */
        uart_console_reconfig();
#endif
        /* Re-Configure the Systick time */
        HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq() / RT_TICK_PER_SECOND);
        /* Re-Configure the Systick */
        HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);
    }

    last_mode = mode;
    rt_kprintf('switch to %s mode, frequency = %d %sHz\n',
               run_str[mode], stm32_run_freq[mode][0], (stm32_run_freq[mode][1] == 1) ? 'M' : 'K');

    if ((stm32_run_freq[mode][0] / stm32_run_freq[mode][1]) > OSC_CONF_SYS_FREQ_MAX)
        rt_kprintf('warning: The frequency has over than %d MHz\n', OSC_CONF_SYS_FREQ_MAX);
}

使用特权

评论回复
32
欢乐家园|  楼主 | 2022-4-29 14:43 | 只看该作者
自定义运行级别时钟树配置函数PM 组件驱动在给定运行频率时,已经尽量自动最优化配置时钟树,但有时外设时钟还是没有达到自己想要的频率,这时可以自己配置时钟树,在 board.c 添加以下单个或所有函数,代码可参考
SystemClock_Config()
函数:

rt_uint16_t stm32_run_freq[PM_RUN_MODE_MAX][2] =
{
   /* The actual frequency is 1/divisor MHz, divisor = {1, 1000} */
   /* {sysclk frequency, divisor} */
  {/* 配置高频运行时的时钟 */, /* 分频系数 */},    /* High speed */
  {/* 配置普通运行时的时钟 */, /* 分频系数 */},    /* Normal speed */
  {/* 配置中低运行时的时钟 */, /* 分频系数 */},    /* Medium speed */
  {/* 配置低频运行时的时钟 */, /* 分频系数 */},    /* Low speed, MSI clock 2.0 MHz */
};
void stm32_systemclock_high(void)
{
   /* 添加代码,配置高频运行时的时钟树 */
}
void stm32_systemclock_normal(void)
{
   /* 添加代码,配置普通速度运行时的时钟树 */
}
void stm32_systemclock_medium(void)
{
   /* 添加代码,配置中低频运行时的时钟树 */
}
void stm32_systemclock_low(void)
{
   /* 添加代码,配置低频运行时的时钟树 */
}
当低速的频率小于2MHz时,要注意以下2点:
串口波特率如果设置过高,将不能正常通信
在时钟频率很低时,要适当减小
RT_TICK_PER_SECOND
值,不然由于
OS_tick
过短,某些线程将不能完成任务,从而不能进入低功耗模式
初始化PM组件注意:休眠模式的时间补偿需要在初始化阶段通过设置 timer_mask 的对应模式的 bit 控制开启。

使用特权

评论回复
33
欢乐家园|  楼主 | 2022-4-29 14:44 | 只看该作者
例如需要开启 Deep Sleep 模式下的时间补偿,在实现 timer 相关的 ops 接口后,初始化时设置相应的bit:
int drv_pm_hw_init(void)
{
    static const struct rt_pm_ops _ops =
    {
        stm32_sleep,
        stm32_run,
        stm32_pm_timer_start,
        stm32_pm_timer_stop,
        stm32_pm_timer_get_tick
    };

    rt_uint8_t timer_mask = 0;

    /* Enable Power Clock */
    __HAL_RCC_PWR_CLK_ENABLE();

    /* initialize timer mask */
    timer_mask = 1UL << PM_SLEEP_MODE_DEEP;

    /* initialize system pm module */
    rt_system_pm_init(&_ops, timer_mask, RT_NULL);

    return 0;
}

INIT_BOARD_EXPORT(drv_pm_hw_init);
void rt_system_pm_init(const struct rt_pm_ops *ops,
                      rt_uint8_t              timer_mask,
                      void                   *user_data)
{
   struct rt_device *device;
   struct rt_pm *pm;

   pm = &_pm;
   device = &(_pm.parent);

   device->type        = RT_Device_Class_PM;
   device->rx_indicate = RT_NULL;
   device->tx_complete = RT_NULL;

#ifdef RT_USING_DEVICE_OPS
   device->ops = &pm_ops;
#else
   device->init        = RT_NULL;
   device->open        = RT_NULL;
   device->close       = RT_NULL;
   device->read        = _rt_pm_device_read;
   device->write       = _rt_pm_device_write;
   device->control     = _rt_pm_device_control;
#endif
   device->user_data   = user_data;

   /* register PM device to the system */
   rt_device_register(device, 'pm', RT_DEVICE_FLAG_RDWR);

   rt_memset(pm->modes, 0, sizeof(pm->modes));
   pm->sleep_mode = _pm_default_sleep;
   pm->run_mode   = RT_PM_DEFAULT_RUN_MODE;
   pm->timer_mask = timer_mask;

   pm->ops = ops;

   pm->device_pm = RT_NULL;
   pm->device_pm_number = 0;

   _pm_init_flag = 1;
}

使用特权

评论回复
34
欢乐家园|  楼主 | 2022-4-29 14:45 | 只看该作者
STM32L4 移植 PM
STM32L4 的低功耗模式简介:
STM32L4系列 是 ST 公司推出的一款超低功耗的 Crotex-M4 内核的 MCU,支持多个电源管理模式,其中最低功耗 Shutdown 模式下,待机电流仅 30 nA。ST 公司 把 L4系列 的电管管理分为很多种,但各个模式的并非功耗逐级递减的特点,下面是各个模式之间的状态转换

使用特权

评论回复
35
欢乐家园|  楼主 | 2022-4-29 14:47 | 只看该作者

使用特权

评论回复
36
欢乐家园|  楼主 | 2022-4-29 14:48 | 只看该作者
尽管 STM32L4系列 的低功耗模式很多,但本质上并不复杂,理解它的原理有助于我们移植驱动,同时更好的在产品中选择合适的模式。最终决定 STM32L4系列 系统功耗的主要是三个因素:稳压器(voltage regulator)、CPU 工作频率、芯片自身低功耗的处理,下面分别对三个因素进行阐述。

使用特权

评论回复
37
欢乐家园|  楼主 | 2022-4-29 14:49 | 只看该作者
稳压器L4 使用两个嵌入式线性稳压器为所有数字电路、待机电路以及备份时钟域供电,分别是主稳压器(main regulator,下文简称 MR)和低功耗稳压器(low-power regulator,下文简称 LPR)。稳压器在复位后处于使能状态,根据应用模式,选择不同的稳压器对 Vcore 域供电。其中,MR 的输出电压可以由软件配置为不同的范围(Range 1 和 Rnage 2)。稳压器应用场合

使用特权

评论回复
38
欢乐家园|  楼主 | 2022-4-29 14:50 | 只看该作者

使用特权

评论回复
39
欢乐家园|  楼主 | 2022-4-29 14:51 | 只看该作者

使用特权

评论回复
40
欢乐家园|  楼主 | 2022-4-29 14:52 | 只看该作者
配置内核选项:使用 PM 组件需要更大的 IDLE 线程的栈,这里使用了1024 字节

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则