嵌入式系统低功耗管理的目的在于满足用户对性能需求的前提下,尽可能降低系统能耗以延长设备待机时间。高性能与有限的电池能量在嵌入式系统中矛盾最为突出,硬件低功耗设计与软件低功耗管理的联合应用成为解决矛盾的有效手段。现在的各种 MCU 都或多或少的在低功耗方面提供了管理接口。比如对主控时钟频率的调整、工作电压的改变、总线频率的调整甚至关闭、外围设备工作时钟的关闭等。有了硬件上的支持,合理的软件设计就成为节能的关键,一般可以把低功耗管理分为三个类别:
随着物联网 (IoT) 的兴起,产品对功耗的需求越来越强烈。作为数据采集的传感器节点通常需要在电池供电时长期工作,而作为联网的 SOC 也需要有快速的响应功能和较低的功耗。
在产品开发的起始阶段,首先考虑是尽快完成产品的功能开发。在产品功能逐步完善之后,就需要加入电源管理 (Power Management,以下简称 PM) 功能。为了适应 IoT 的这种需求,RT-Thread 提供了电源管理组件。电源管理组件的理念是尽量透明,使得产品加入低功耗功能更加轻松。
RT-Thread 的 PM 组件采用分层设计思想,分离架构和芯片相关的部分,提取公共部分作为核心。在对上层提供通用的接口同时,也让底层驱动对组件的适配变得更加简单。
RT-Thread PM 组件主要特点如下所示:
低功耗的本质是系统空闲时 CPU 停止工作,中断或事件唤醒后继续工作。在 RTOS 中,通常包含一个 IDLE 任务,该任务的优先级最低且一直保持就绪状态,当高优先级任务未就绪时,OS 执行 IDLE 任务。一般地,未进行低功耗处理时,CPU 在 IDLE 任务中循环执行空指令。RT-Thread 的电源管理组件在 IDLE 任务中,通过对 CPU 、时钟和设备等进行管理,从而有效降低系统的功耗。
在上图所示,当高优先级任务运行结束或被挂起时,系统将进入 IDLE 任务中。在 IDLE 任务执行后,它将判断系统是否可以进入到休眠状态(以节省功耗)。如果可以进入休眠, 将根据芯片情况关闭部分硬件模块,OS Tick 也非常有可能进入暂停状态。此时电源管理框架会根据系统定时器情况,计算出下一个超时时间点,并设置低功耗定时器,让设备能够在这个时刻点唤醒,并进行后续的工作。当系统被(低功耗定时器中断或其他唤醒中断源)唤醒后,系统也需要知道睡眠时间长度是多少,并对OS Tick 进行补偿,让系统的OS tick值调整为一个正确的值。
RT-Thread PM 组件将系统划分为两种状态:运行状态(RUN)和休眠状态(Sleep)。运行状态控制 CPU 的频率,适用于变频场景;休眠状态根据 SOC 特性实现休眠 CPU,以降低功耗。两种状态分别使用不同的 API 接口,独立控制。
在 RT-Thrad PM 组件中,外设或应用通过投票机制对所需的功耗模式进行投票,当系统空闲时,根据投票数决策出合适的功耗模式,调用抽象接口,控制芯片进入低功耗状态,从而降低系统功耗。当未进行进行任何投票时,会以默认模式进入(通常为空闲模式)。
pm组件的控制块:
API接口:
void rt_pm_request(uint8_t sleep_mode);
sleep_mode 取以下枚举值:
enum
{
/* sleep modes */
PM_SLEEP_MODE_NONE = 0, /* 活跃状态 */
PM_SLEEP_MODE_IDLE, /* 空闲模式(默认) */
PM_SLEEP_MODE_LIGHT, /* 轻度睡眠模式 */
PM_SLEEP_MODE_DEEP, /* 深度睡眠模式 */
PM_SLEEP_MODE_STANDBY, /* 待机模式 */
PM_SLEEP_MODE_SHUTDOWN, /* 关断模式 */
PM_SLEEP_MODE_MAX,
};
调用该函数会将对应的模式计数加1,并锁住该模式。此时如果请求更低级别的功耗模式,将无法进入,只有释放(解锁)先前请求的模式后,系统才能进入更低的模式;向更高的功耗模式请求则不受此影响。该函数需要和 rt_pm_release 配合使用,用于对某一阶段或过程进行保护。下面是具体代码实现:
void rt_pm_request(rt_uint8_t mode)
{
rt_base_t level;
struct rt_pm *pm;
if (_pm_init_flag == 0)
return;
if (mode > (PM_SLEEP_MODE_MAX - 1))
return;
level = rt_hw_interrupt_disable();
pm = &_pm;
if (pm->modes[mode] < 255)
pm->modes[mode] ++;//将对应的模式计数加1
rt_hw_interrupt_enable(level);
}
void rt_pm_release(uint8_t sleep_mode);
调用该函数会将对应的模式计数减1,配合 rt_pm_request 使用,释放先前请求的模式。下面是具体代码实现:
void rt_pm_release(rt_uint8_t mode)
{
rt_ubase_t level;
struct rt_pm *pm;
if (_pm_init_flag == 0)
return;
if (mode > (PM_SLEEP_MODE_MAX - 1))
return;
level = rt_hw_interrupt_disable();
pm = &_pm;
if (pm->modes[mode] > 0)
pm->modes[mode] --;//将对应的模式计数减1
rt_hw_interrupt_enable(level);
}
特殊情况下,比如某个阶段并不允许系统进入更低的功耗模式,此时可以通过 rt_pm_request 和 rt_pm_release 对该过程进行保护。如 I2C 读取数据期间,不允许进入深度睡眠模式(可能会导致外设停止工作),因此可以做如下处理:
/* 请求轻度睡眠模式(I2C外设该模式下正常工作) */
rt_pm_request(PM_SLEEP_MODE_LIGHT);
/* 读取数据过程 */
/* 释放该模式 */
rt_pm_release(PM_SLEEP_MODE_LIGHT);
int rt_pm_run_enter(uint8_t run_mode);
run_mode 可以取以下枚举值:
enum
{
/* run modes*/
PM_RUN_MODE_HIGH_SPEED = 0, /* 高速 */
PM_RUN_MODE_NORMAL_SPEED, /* 正常(默认) */
PM_RUN_MODE_MEDIUM_SPEED, /* 中速 */
PM_RUN_MODE_LOW_SPEED, /* 低速 */
PM_RUN_MODE_MAX,
};
调用该函数改变 CPU 的运行频率,从而降低运行时的功耗。此函数只提供级别,具体的 CPU 频率应在移植阶段视实际情况而定。
下面是具体代码实现:
int rt_pm_run_enter(rt_uint8_t mode)
{
rt_base_t level;
struct rt_pm *pm;
if (_pm_init_flag == 0)
return -RT_EIO;
if (mode > PM_RUN_MODE_MAX)
return -RT_EINVAL;
level = rt_hw_interrupt_disable();
pm = &_pm;
if (mode < pm->run_mode)
{
/* change system runing mode */
pm->ops->run(pm, mode);
/* changer device frequency */
_pm_device_frequency_change(mode);
}
else
{
pm->flags |= RT_PM_FREQUENCY_PENDING;
}
pm->run_mode = mode;
rt_hw_interrupt_enable(level);
return RT_EOK;
}
void rt_pm_notify_set(void (*notify)(uint8_t event, uint8_t mode, void *data), void *data);
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;
}
void rt_pm_device_register(struct rt_device *device, const struct rt_device_pm_ops *ops)
与应用不同,某些外设可能在进入低功耗状态时执行特定操作,退出低功耗时采取措施恢复,此时可以通过注册PM设备来实现。通过注册 PM 设备,在进入低功耗状态之前,会触发注册设备的 suspend 回调,开发者可在回调里执行自己的操作;类似地,从低功耗状态退出时,也会触发 resume 回调。运行模式下的频率改变同样会触发设备的 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);
}
设置进入/退出休眠模式的回调通知和注册为设备的回调通知流程:
首先应用设置进出休眠状态的回调函数,然后调用 rt_pm_request 请求休眠模式,触发休眠操作;PM 组件在系统空闲时检查休眠模式计数,根据投票数给出推荐的模式;接着 PM 组件调用 notfiy 通知应用,告知即将进入休眠模式;然后对注册的 PM 设备执行挂起操作,返回 OK 后执行 SOC 实现的的休眠模式,系统进入休眠状态(如果使能时间补偿,休眠之前会先启动低功耗定时器)。此时 CPU 停止工作,等待事件或者中断唤醒。当系统被唤醒后,由于全局中断为关闭状态,系统继续从该处执行,获取睡眠时间补偿系统的心跳,依次唤醒设备,通知应用从休眠模式退出。如此一个周期执行完毕,退出,等待系统下次空闲。
模式的切换代码实现:当任务进入到空闲线程,最终是调用此函数进入低功耗和唤醒的
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);
}
}
RT-Thread 低功耗管理系统从设计上分离运行模式和休眠模式,独立管理,运行模式用于变频和变电压,休眠调用芯片的休眠特性。对于多数芯片和开发来说,可能并不需要考虑变频和变电压,仅需关注休眠模式。底层功能的实现已经有Sunwancn大神对STM32做了全系列的适配,以下是底层实现原理,用户也可以自行根据自身情况对底层进行裁剪或增强。(注意: 驱动可能有更新,移植请到gitee下载最新pm驱动。地址在pm-ports-stm32-new 分支:https://gitee.com/sunwancn/rt-thread/tree/pm-ports-stm32-new)
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);
};
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;
}
}
休眠的时间补偿需要实现三个接口,分别用于启动低功耗定时器、停止定时器、唤醒后获取休眠的 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 组件即可。
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);
}
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
过短,某些线程将不能完成任务,从而不能进入低功耗模式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;
}
STM32L4系列 是 ST 公司推出的一款超低功耗的 Crotex-M4 内核的 MCU,支持多个电源管理模式,其中最低功耗 Shutdown 模式下,待机电流仅 30 nA。ST 公司 把 L4系列 的电管管理分为很多种,但各个模式的并非功耗逐级递减的特点,下面是各个模式之间的状态转换图:
尽管 STM32L4系列 的低功耗模式很多,但本质上并不复杂,理解它的原理有助于我们移植驱动,同时更好的在产品中选择合适的模式。最终决定 STM32L4系列 系统功耗的主要是三个因素:稳压器(voltage regulator)、CPU 工作频率、芯片自身低功耗的处理,下面分别对三个因素进行阐述。
Vcore = 1.2V,用于运行模式、睡眠模式和停止模式0,MR 未 Vcore 域提供全功率 | |
用于低功耗运行模式、低功耗休眠模式、停止模式 1、停止模式2 | |
Standby 和 Shutdown 模式下,MR 和 LPR 都被关闭 |
配置 PM 组件:
配置内核选项:使用 PM 组件需要更大的 IDLE 线程的栈,这里使用了1024 字节
在空闲线程中会调用rt_system_power_manager接口来进入低功耗模式:
/**
* This function will enter corresponding power mode.
*/
void rt_system_power_manager(void)
{
rt_uint8_t mode;
if (_pm_init_flag == 0)
return;
/* CPU frequency scaling according to the runing mode settings */
_pm_frequency_scaling(&_pm);
/* Low Power Mode Processing */
mode = _pm_select_sleep_mode(&_pm);
_pm_change_sleep_mode(&_pm, mode);
}
保存后,可以看到pm.c已经被添加到了工程:
然后添加PM组件的设备驱动,驱动的最新地址:pm-ports-stm32-new 分支:https://gitee.com/sunwancn/rt-thread/tree/pm-ports-stm32-new注意: 目前所使用的驱动不是最新版本,移植请到gitee下载最新pm驱动。
从\rt-thread\bsp\stm32\libraries\HAL_Drivers,拷贝如下四个文件到工程的drivers文件夹下:
本项目选择的是使用RTC作为STOP后的时间补偿,所以需要打开rtc设备和所使用的宏:
注: 如果没有使用RTT的自身的RTC函数的话,前面2个宏可以不要。
应用示例:此程序主要实现开机后经过10秒后进入 STOP 模式,然后每经过5秒 SLEEP 模式和 STOP 模式互相切换,如此循环往复,同时经过一个循环后,切换 MCU 的运行频率,验证运行的稳定性。并且打开了回调和中断唤醒,在进入睡眠和唤醒后会分别熄灭和点亮LED灯,在睡眠时间可以通过外部中断唤醒:
/*
* Copyright (c) 2006-2018, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2018-11-06 SummerGift first version
*/
#include
#include
#include
#include
#define LOG_TAG "PM.test"
#define LOG_LVL LOG_LVL_DBG
#include
/* defined the LED0 pin: PB13 */
#define LED0_PIN GET_PIN(C, 6)
#define MCU_IRQ_WAKE_PIN GET_PIN(C, 9)
static RTC_HandleTypeDef hrtc;
static RTC_TimeTypeDef curtime = {0};
static RTC_TimeTypeDef alarmtime = {0};
static RTC_AlarmTypeDef alarm = {0};
static struct rt_semaphore wake_sem;
static rt_uint8_t sleep_mode = PM_SLEEP_MODE_DEEP; /* STOP 模式 */
static rt_uint8_t run_mode = PM_RUN_MODE_NORMAL_SPEED;
static rt_uint32_t get_interval(void);
static void get_rtc_time(RTC_TimeTypeDef *time);
static rt_uint8_t mode_loop(void);
static rt_uint8_t issleep = 0;
/* 中断回调函数 */
static void MCU_IRQ_WAKE(void *args)
{
if(issleep)
{
rt_kprintf("MCU_IRQ_WAKE!\n");
rt_sem_release(&wake_sem);
issleep = 0;
}
}
static void pm_botify(uint8_t event, uint8_t mode, void *data)
{
if(event == RT_PM_ENTER_SLEEP && mode == PM_SLEEP_MODE_DEEP)
{
rt_pin_write(LED0_PIN, PIN_HIGH);
issleep = 1;
}
else if (event == RT_PM_EXIT_SLEEP && mode == PM_SLEEP_MODE_DEEP )
{
rt_pin_write(LED0_PIN, PIN_LOW);
}
}
int pm_test(void)
{
hrtc.Instance = RTC;
rt_sem_init(&wake_sem, "wake_sem", 0, RT_IPC_FLAG_FIFO);
rt_pm_notify_set(pm_botify,0);
/* 按键0引脚为输入模式 */
rt_pin_mode(MCU_IRQ_WAKE_PIN, PIN_MODE_INPUT_PULLDOWN);
/* 绑定中断,上升沿模式,回调函数名为beep_on */
rt_pin_attach_irq(MCU_IRQ_WAKE_PIN, PIN_IRQ_MODE_RISING, MCU_IRQ_WAKE, RT_NULL);
/* 使能中断 */
rt_pin_irq_enable(MCU_IRQ_WAKE_PIN, PIN_IRQ_ENABLE);
rt_thread_mdelay(10000);
#ifdef RT_USING_PM
/* 申请低功耗模式 */
rt_pm_request(sleep_mode);
#endif
get_rtc_time(&curtime);
if (sleep_mode == PM_SLEEP_MODE_STANDBY)
{
/* 设置休眠,闹钟 20 秒后唤醒,简化版闹钟,只支持 1分钟内有效 */
alarmtime.Hours = curtime.Hours;
alarmtime.Minutes = curtime.Minutes;
alarmtime.SubSeconds = curtime.SubSeconds;
alarmtime.Seconds = curtime.Seconds + 20;
if (alarmtime.Seconds >= 60)
{
alarmtime.Seconds -= 60;
alarmtime.Minutes ++;
if (alarmtime.Minutes >= 60)
alarmtime.Minutes -= 60;
}
alarm.Alarm = RTC_ALARM_A;
alarm.AlarmTime = alarmtime;
alarm.AlarmTime.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;
alarm.AlarmTime.StoreOperation = RTC_STOREOPERATION_RESET;
alarm.AlarmMask = RTC_ALARMMASK_DATEWEEKDAY | RTC_ALARMMASK_HOURS;
alarm.AlarmSubSecondMask = RTC_ALARMSUBSECONDMASK_NONE;
alarm.AlarmDateWeekDaySel = RTC_ALARMDATEWEEKDAYSEL_DATE;
alarm.AlarmDateWeekDay = 0x1;
/* 开启闹钟 */
HAL_RTC_SetAlarm_IT(&hrtc, &alarm, RTC_FORMAT_BIN);
}
while (1)
{
/* 开始进入低功耗模式 */
rt_sem_take(&wake_sem, rt_tick_from_millisecond(5000));
/* 退出低功耗模式 */
rt_kprintf("Sleep %d ms\n", get_interval());
#ifdef RT_USING_PM
/* 申请正常模式 */
rt_pm_request(PM_SLEEP_MODE_NONE);
#endif
rt_thread_mdelay(5000);
rt_kprintf("Wakeup %d ms\n", get_interval());
/* 运行模式切换 */
rt_pm_run_enter(mode_loop());
#ifdef RT_USING_PM
rt_pm_release(PM_SLEEP_MODE_NONE);
#endif
}
return RT_EOK;
}
//MSH_CMD_EXPORT(pm_test, PM TEST);
static rt_uint32_t get_interval(void)
{
rt_uint32_t seconds;
rt_uint32_t last_seconds = curtime.Seconds;
rt_uint32_t last_subseconds = curtime.SubSeconds;
get_rtc_time(&curtime);
if (curtime.Seconds < last_seconds)
seconds = 60 + curtime.Seconds - last_seconds;
else
seconds = curtime.Seconds - last_seconds;
return (rt_uint32_t)(seconds * 1000 + ((int32_t)last_subseconds - (int32_t)curtime.SubSeconds) * 1000 \
/ (int32_t)(((RTC->PRER & RTC_PRER_PREDIV_S) >> RTC_PRER_PREDIV_S_Pos) + 1U));
}
static void get_rtc_time(RTC_TimeTypeDef *time)
{
rt_uint32_t st, datetmpreg;
HAL_RTC_GetTime(&hrtc, time, RTC_FORMAT_BIN);
datetmpreg = RTC->DR;
if (HAL_RCC_GetPCLK1Freq() < 32000U * 7U)
{
st = time->SubSeconds;
HAL_RTC_GetTime(&hrtc, time, RTC_FORMAT_BIN);
datetmpreg = RTC->DR;
if (st != time->SubSeconds)
{
HAL_RTC_GetTime(&hrtc, time, RTC_FORMAT_BIN);
datetmpreg = RTC->DR;
}
}
(void)datetmpreg;
}
rt_uint8_t mode_loop(void)
{
rt_uint8_t mode = 1;
run_mode++;
switch (run_mode)
{
case 0:
case 1:
case 2:
case 3:
mode = run_mode;
break;
case 4:
mode = 2;
break;
case 5:
mode = 1;
break;
case 6:
mode = run_mode = 0;
break;
}
return mode;
}
运行效果:
服务热线:0755-82701660
联系电话:13420905587
微信 :13420905587
邮箱:yimingke@jyfdzsz.com
地址:深圳市福田区华强北街道华航社区中航路18号新亚洲国利大厦1339-1341