Cortex M0/M0+相對於Cortex M3/M4性能稍弱, 但是優勢在於低價格和低功耗, 這使得M0特別適合性能要求不高且電池供電的便攜類應用, 比如遙控器, 墨水屏, 電子寵物, 電子煙等. 根據 PY32F0 各型號的數據手冊, 對比其最低功耗狀態(STOP模式)下的電流, 全系列可以大... ...
目錄
- 普冉PY32系列(一) PY32F0系列32位Cortex M0+ MCU簡介
- 普冉PY32系列(二) Ubuntu GCC Toolchain和VSCode開發環境
- 普冉PY32系列(三) PY32F002A資源實測 - 這個型號不簡單
- 普冉PY32系列(四) PY32F002A/003/030的時鐘設置
- 普冉PY32系列(五) 使用JLink RTT代替串口輸出日誌
- 普冉PY32系列(六) 通過I2C介面驅動PCF8574擴展的1602LCD
- 普冉PY32系列(七) SOP8,SOP10,SOP16封裝的PY32F002A/PY32F003管腳復用
- 普冉PY32系列(八) GPIO模擬和硬體SPI方式驅動無線收發晶元XN297LBW
- 普冉PY32系列(九) GPIO模擬和硬體SPI方式驅動無線收發晶元XL2400
- 普冉PY32系列(十) 基於PY32F002A的6+1通道遙控小車I - 綜述篇
- 普冉PY32系列(十一) 基於PY32F002A的6+1通道遙控小車II - 控制篇
- 普冉PY32系列(十二) 基於PY32F002A的6+1通道遙控小車III - 驅動篇
- 普冉PY32系列(十三) SPI驅動WS2812全彩LED
- 普冉PY32系列(十四) 從XL2400遷移到XL2400P
- 普冉PY32系列(十五) PY32F0系列的低功耗模式
聲明
任何在廠家數據手冊之外的資源都是無保證的, 本文內容僅對當前測試中使用的樣品有效, 請勿以此作為選型參考, 一切以廠家手冊為準. 因為使用本文數據產生的任何問題本人概不負責.
PY32F0系列的低功耗
Cortex M0/M0+相對於Cortex M3/M4性能稍弱, 但是優勢在於低價格和低功耗, 這使得M0特別適合性能要求不高且電池供電的便攜類應用, 比如遙控器, 墨水屏, 電子寵物, 電子煙等. 根據 PY32F0 各個型號的數據手冊, 對比其最低功耗狀態(STOP模式)下的電流, 全系列可以大致分為三檔
- PY32F04x PY32F07x: 最低 10.5 uA
- PY32F030 PY32F003 PY32F002A: 最低 4.5 uA
- PY32F002B: 最低 1.5 uA
可以看出待機功耗和片上外設的豐富程度基本上是成正比的.
- PY32F04X外設豐富功耗也大, 面向的是替代M3的場景, 低功耗可能不是最重要的特性
- PY32F030 系列, PY32F002A是一個特例, 具體原因大家也都知道的
- PY32F002B 資源最少, 但是功耗非常低, 待機電流1.5uA. 實際測試電流能做到1uA以下(看本文末尾的說明)
電池供電的便攜設備, 待機功耗基本上要控制在十個uA以內. 例如一個用主板電池CR2032供電的設備要求一年的電池使用壽命. CR2032電量為200mAH, 假定工作電流20mA, 待機5uA, 工作時間占比0.1%(比如每隔十秒採集上報一次數據, 上報耗時10毫秒), 電池壽命就差不多是一年. 對於這種場景使用 PY32F030 系列比較勉強, 而使用 PY32F002B 則功耗還有富餘.
這裡具體說明 PY32F030(適用於PY32F003和PY32F002A) 和 PY32F002B 這兩類型號的低功耗設置.
測試方法
要測量的目標為 10uA 以下的電流, 可以用萬用表的微安檔, 但是MCU啟動狀態和正常工作狀態電流差異巨大, 從十幾個mA到幾個uA, 為方便測量, 可以在萬用表的正負極並聯一個開關, 啟動時開關閉合, 電流走開關, 當工作穩定後開關打開, 由微安表讀出電流.
因為測量微小電流很容易受電路其它元件干擾, 為避免因為各種電流泄漏造成的測試結果不准確:
- 不要用普通的開發板(除非是專門設計用於測試低功耗場景的), 用簡單的分線板最可靠
- 不要用低管腳數的封裝, 因為存在管腳復用的情況, 當復用的管腳沒有正確配置時, 在內部管腳之間也會產生電流泄漏
PY32F030 系列(PY32F002A, PY32F003, PY32F030)
這個系列屬於 PY32F0 中的通用型號, 片上資源可以滿足大部分場景的需求. 待機電流雖然沒那麼低(4.5uA), 但是面對普通電池應用也是綽綽有餘, 一節五號電池可以輕鬆工作半年以上. PY32F030 系列低功耗狀態支持兩種模式 SLEEP 和 DEEP SLEEP(STOP).
- 正常運行模式下, 使用LSI可以顯著降低功耗, 啟用Flash睡眠後功耗電流可以控制到 100uA 以內
- SLEEP 模式下只是關閉了CPU時鐘, 外設還能工作, 時鐘頻率高的時候切換到SLEEP後節能效果明顯, 時鐘頻率越低則越無區別, 根據時鐘源為HSI還是LSI, 電流大小區間為 0.1 mA 到 2.x mA
- STOP 模式大部分外設停止, 時鐘 HSI, HSE 和 PLL 停止. LPTIM 基於 LSI 工作, 當切換到低壓調節器後, 電流大小在 4.5 uA 到 6 uA 區間
工作於內置低速時鐘LSI時的功耗控制
下麵的代碼用於演示如何從內部高速時鐘切換到內部低速時鐘, 並一步步降低功耗
/**
* 啟用LSI並將其設為系統時鐘
*/
static void APP_RCC_LSI_Config(void)
{
LL_RCC_LSI_Enable();
while(LL_RCC_LSI_IsReady() != 1);
LL_RCC_SetAHBPrescaler(LL_RCC_SYSCLK_DIV_1);
/* 設置 LSI 為系統時鐘源 */
LL_RCC_SetSysClkSource(LL_RCC_SYS_CLKSOURCE_LSI);
while(LL_RCC_GetSysClkSource() != LL_RCC_SYS_CLKSOURCE_STATUS_LSI);
LL_FLASH_SetLatency(LL_FLASH_LATENCY_0);
LL_RCC_SetAPB1Prescaler(LL_RCC_APB1_DIV_1);
LL_SetSystemCoreClock(LSI_VALUE);
/* 重設 SysTick 時鐘計數周期, 如果沒有這步, LL_mDelay()延遲就會不正常 */
LL_Init1msTick(32768);
}
int main(void)
{
// 設置 HSI 24MHz 作為系統時鐘
BSP_RCC_HSI_24MConfig();
// 系統運行於 HSI, 測得電流約 1.3 mA
LL_mDelay(3000);
// 系統時鐘切換到內部低速時鐘 LSI
APP_RCC_LSI_Config();
// 系統運行於 LSI, 但是 HSI 未關閉, 電流約 360 uA
LL_mDelay(3000);
// 關閉 HSI
LL_RCC_HSI_Disable();
// 電流降至約 180 uA
LL_mDelay(3000);
// 開啟 flash sleep
SET_BIT(FLASH->STCR, FLASH_STCR_SLEEP_EN);
// 電流降至約 100 uA
while (1);
}
測量的時候, 可以觀察到上電後, 每隔三秒電流會降一檔, 切換時鐘源到 LSI 後, 從 1.3mA 降到 360uA, 關閉 HSI 後, 降到 180uA, 啟用 Flash Sleep 後, 降到 100 uA 以內.
進入 SLEEP 模式
進入SLEEP模式的代碼很簡單, 啟用PWR時鐘並調用LL_LPM_EnableSleep
就啟用了SLEEP, 然後等待事件或中斷喚醒
// 使能低功耗控制模塊(PWR)時鐘
LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_PWR);
// 設置低功耗狀態為 Sleep, 清除SLEEPDEEP狀態位, CLEAR_BIT(SCB->SCR, ((uint32_t)SCB_SCR_SLEEPDEEP_Msk))
LL_LPM_EnableSleep();
/*
* 等待事件喚醒
* 如果是等待中斷, 將下麵的代碼換成 __WFI();
*/
__SEV();
__WFE();
__WFE();
進入 STOP 模式
啟用低功耗STOP模式, 並等待事件喚醒.
註意這裡面的LL_PWR_SetRegulVoltageScaling
方法, 如果 STOP 模式下測得的電流一直在 6 uA 以上, 很可能是電壓沒有調整為 1.0V
// 使能低功耗控制模塊(PWR)時鐘
LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_PWR);
/*
* 設置低功耗STOP電壓為1.0V, 預設電壓為1.2V, 會增大電流,
* 對於 PY32F030 系列, 1.0V和1.2V對應的電流為 4.5uA~4.8uA 和 6uA ~ 7uA
*/
LL_PWR_SetRegulVoltageScaling(LL_PWR_REGU_VOLTAGE_SCALE2);
/*
* 設置電壓調節器從工作狀態轉換為低功耗狀態, SET_BIT(PWR->CR1, PWR_CR1_LPR)
* 在開啟 STOP 模式前, 必須調用這個方法
*/
LL_PWR_EnableLowPowerRunMode();
/*
* 設置低功耗狀態的模式為Deep sleep, 即STOP模式,
* 對應寄存器命令為 SET_BIT(SCB->SCR, ((uint32_t)SCB_SCR_SLEEPDEEP_Msk));
*/
LL_LPM_EnableDeepSleep();
/*
* 等待事件喚醒
* 如果是等待中斷, 將下麵的代碼換成 __WFI();
*/
__SEV();
__WFE();
__WFE();
/*
* 退出 STOP 模式時, 設置低功耗狀態為 Sleep, 清除SLEEPDEEP狀態位,
* 對應寄存器命令為 CLEAR_BIT(SCB->SCR, ((uint32_t)SCB_SCR_SLEEPDEEP_Msk))
*/
LL_LPM_EnableSleep();
事件喚醒和中斷喚醒 - 按鍵喚醒
下麵配置的外部中斷, 用於事件喚醒或中斷喚醒 SLEEP/STOP 模式
static void APP_EXTIConfig(void)
{
LL_GPIO_InitTypeDef GPIO_InitStruct;
LL_EXTI_InitTypeDef EXTI_InitStruct;
// GPIOA時鐘使能
LL_IOP_GRP1_EnableClock (LL_IOP_GRP1_PERIPH_GPIOA);
// 選擇PA06引腳
GPIO_InitStruct.Pin = LL_GPIO_PIN_6;
// 選擇輸入模式
GPIO_InitStruct.Mode = LL_GPIO_MODE_INPUT;
// 選擇上拉
GPIO_InitStruct.Pull = LL_GPIO_PULL_UP;
// GPIOA初始化
LL_GPIO_Init(GPIOA, &GPIO_InitStruct);
// 選擇EXTI6做外部中斷輸入
LL_EXTI_SetEXTISource(LL_EXTI_CONFIG_PORTA,LL_EXTI_CONFIG_LINE6);
// 選擇EXTI6
EXTI_InitStruct.Line = LL_EXTI_LINE_6;
// 使能
EXTI_InitStruct.LineCommand = ENABLE;
/*
* 選擇中斷模式
* 事件喚醒使用 EXTI_InitStruct.Mode = LL_EXTI_MODE_EVENT;
* 中斷喚醒使用 EXTI_InitStruct.Mode = LL_EXTI_MODE_IT;
*/
EXTI_InitStruct.Mode = LL_EXTI_MODE_EVENT;
// 選擇下降沿觸發
EXTI_InitStruct.Trigger = LL_EXTI_TRIGGER_FALLING;
// 外部中斷初始化
LL_EXTI_Init(&EXTI_InitStruct);
// 設置中斷優先順序
NVIC_SetPriority(EXTI4_15_IRQn,1);
// 使能中斷
NVIC_EnableIRQ(EXTI4_15_IRQn);
}
如果配置為中斷喚醒, 那麼還需要加上下麵的中斷回調函數清理中斷位
void EXTI4_15_IRQHandler(void)
{
if(LL_EXTI_ReadFlag(LL_EXTI_LINE_6) == LL_EXTI_LINE_6)
{
LL_EXTI_ClearFlag(LL_EXTI_LINE_6);
}
}
LPTIM 喚醒 - 自動間隔喚醒
PY32F030 系列的 LPTIM 只有LL_LPTIM_OPERATING_MODE_ONESHOT
這一種模式, 不能連續載入. 如果需要保持定期喚醒, 需要在主迴圈中, 再次開始 LPTIM 計數 LL_LPTIM_StartCounter
.
配置 LPTIM 的代碼
// 開啟 LPTIM1時鐘
LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_LPTIM1);
// 開啟內部低速時鐘 LSI
LL_RCC_LSI_Enable();
while(LL_RCC_LSI_IsReady() == 0);
// 配置LSI為LPTIM時鐘源 Freq = 32.768 kHz
LL_RCC_SetLPTIMClockSource(LL_RCC_LPTIM1_CLKSOURCE_LSI);
// LPTIM預分頻器128分頻
LL_LPTIM_SetPrescaler(LPTIM1,LL_LPTIM_PRESCALER_DIV128);
// LPTIM計數周期結束更新ARR
LL_LPTIM_SetUpdateMode(LPTIM1,LL_LPTIM_UPDATE_MODE_ENDOFPERIOD);
// 使能NVIC請求
NVIC_SetPriority(LPTIM1_IRQn,0);
NVIC_EnableIRQ(LPTIM1_IRQn);
// 使能ARR中斷
LL_LPTIM_EnableIT_ARRM(LPTIM1);
// 使能LPTIM
LL_LPTIM_Enable(LPTIM1);
// 配置重裝載值 51
LL_LPTIM_SetAutoReload(LPTIM1,51);
配合中斷回調函數
void LPTIM1_IRQHandler(void)
{
if(LL_LPTIM_IsActiveFlag_ARRM(LPTIM) == 1)
{
// 清理中斷標誌位
LL_LPTIM_ClearFLAG_ARRM(LPTIM);
// 自定義的中斷處理方法
APP_LPTIMCallback();
}
}
在使用時, 在每一個迴圈中先進入低功耗狀態, 開啟LPTIM, 然後進入STOP 等待中斷, MCU會阻塞在__WFI()
方法. 當 LPTIM 計數結束後會喚醒 MCU 繼續往下執行.
// 使能低功耗狀態
LL_PWR_EnableLowPowerRunMode();
// 重啟 LPTIM
LL_LPTIM_Disable(LPTIM1);
LL_LPTIM_Enable(LPTIM1);
// 等待
APP_uDelay(65);
// 開啟LPTIM單次模式
LL_LPTIM_StartCounter(LPTIM1,LL_LPTIM_OPERATING_MODE_ONESHOT);
// 使能STOP模式並等待中斷喚醒
LL_LPM_EnableDeepSleep();
__WFI();
PY32F002B
PY32F002B 片上資源相比 PY32F030 系列縮水了不少, 存儲只有 24K flash / 3K RAM, 只有兩個定時器, 還只有一個定時器帶4個IO輸出, 但是勝在低功耗, STOP 模式電流只有 1.5 uA, 可以勝任很多低功耗的需求.
進入 SLEEP 模式
- PY32F002B 的 SLEEP 模式電流在 1 mA 以下, 整體比正常運行模式低 20% 左右
- PY32F002B 的 STOP 模式, 當切換到低壓調節器後, 電流大小陡然降到 1.5 uA - 1.7 uA 區間
啟用 SLEEP 模式的代碼和 PY32F030 完全一樣
// Enable PWR clock
LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_PWR);
// Enter Sleep mode
LL_LPM_EnableSleep();
/*
* 等待事件喚醒
* 如果是等待中斷, 將下麵的代碼換成 __WFI();
*/
__SEV();
__WFE();
__WFE();
進入 STOP 模式
PY32F002B 開啟 STOP 模式的過程和 PY32F030 系列有區別
- 下麵的
LL_PWR_SetLprMode
等價於F030中的LL_PWR_EnableLowPowerRunMode
方法, 都是切換到低電壓調節器 - 使用
LL_PWR_SetStopModeSramVoltCtrl
設置SRAM保持電壓 - 啟用 Deep Sleep (STOP) 模式
- 等待事件或中斷喚醒
// 使能低功耗控制模塊(PWR)時鐘
LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_PWR);
// STOP 模式啟用低電壓調節器
LL_PWR_SetLprMode(LL_PWR_LPR_MODE_LPR);
// SRAM保持電壓與數字LDO輸出對齊
LL_PWR_SetStopModeSramVoltCtrl(LL_PWR_SRAM_RETENTION_VOLT_CTRL_LDO);
// Enter DeepSleep mode
LL_LPM_EnableDeepSleep();
/*
* 等待事件喚醒
* 如果是等待中斷, 將下麵的代碼換成 __WFI();
*/
__SEV();
__WFE();
__WFE();
LL_LPM_EnableSleep();
事件喚醒和中斷喚醒 - 按鍵喚醒
PY32F002B 的按鍵喚醒和 PY32F030 系列是一樣的, 略.
LPTIM 喚醒 - 定時器自動喚醒
配置 LPTIM, PY32F002B 的 LPTIM 和 PY32F030 系列相比, 增加了一個連續模式 LL_LPTIM_OPERATING_MODE_CONTINUOUS
.
- 如果是單次模式
LL_LPTIM_OPERATING_MODE_ONESHOT
, 下次進入STOP模式後, 要重啟啟動 LPTIM 才能喚醒 - 如果是連續模式
LL_LPTIM_OPERATING_MODE_CONTINUOUS
, 下次進入STOP模式後, 不需要再設置 LPTIM, 會自動喚醒
// Enable LPTIM clock
LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_LPTIM1);
// Enabel LSI
LL_RCC_LSI_Enable();
while(LL_RCC_LSI_IsReady() == 0);
// Select LSI as LTPIM clock source
LL_RCC_SetLPTIMClockSource(LL_RCC_LPTIM1_CLKSOURCE_LSI);
// prescaler: 128
LPTIM_InitStruct.Prescaler = LL_LPTIM_PRESCALER_DIV128;
// Registers are updated after each APB bus write access
LPTIM_InitStruct.UpdateMode = LL_LPTIM_UPDATE_MODE_IMMEDIATE;
// Init LPTIM
if (LL_LPTIM_Init(LPTIM, &LPTIM_InitStruct) != SUCCESS)
{
APP_ErrorHandler();
}
// Enable LPTIM1 interrupt
NVIC_SetPriority(LPTIM1_IRQn, 0);
NVIC_EnableIRQ(LPTIM1_IRQn);
// Enable LPTIM autoreload match interrupt
LL_LPTIM_EnableIT_ARRM(LPTIM);
// Enable LPTIM
LL_LPTIM_Enable(LPTIM);
// Set autoreload value
LL_LPTIM_SetAutoReload(LPTIM, 51);
/*
* LPTIM starts in single mode
* 如果是連續模式, 則用 LL_LPTIM_StartCounter(LPTIM, LL_LPTIM_OPERATING_MODE_CONTINUOUS);
*/
LL_LPTIM_StartCounter(LPTIM, LL_LPTIM_OPERATING_MODE_ONESHOT);
回調
void LPTIM1_IRQHandler(void)
{
APP_LptimIRQCallback();
}
void APP_LptimIRQCallback(void)
{
if((LL_LPTIM_IsActiveFlag_ARRM(LPTIM) == 1) && (LL_LPTIM_IsEnabledIT_ARRM(LPTIM) == 1))
{
/* Clear autoreload match flag */
LL_LPTIM_ClearFLAG_ARRM(LPTIM);
}
}
文末的彩蛋: PY32F002B 的隱藏資源
1. 開啟 48MHz 運行時鐘
PY32F002B 手冊上的最高運行時鐘是 24MHz, 但是在 py32f002bx5.h 中增加一行 #define RCC_HSI48M_SUPPORT
, 就能開啟 PY32F002B 的 48MHz 時鐘支持. 對手裡的幾片 PY32F002B 測試, 以及對一些渠道廠商的合封晶元的測試, 開啟 48MHz 沒有問題, 按 48MHz 的時鐘基準設置定時器和PWM, 反過來也能驗證是真實的 48MHz 頻率.
2. 開啟 DEEP STOP 模式
PY32F002B 手冊上沒有列出 DEEP STOP 模式, 但是在 py32f002bx5.h 中增加如下幾行
#define PWR_DEEPSTOP_SUPPORT /*!< PWR feature available only on specific devices: Deep stop feature */
#define PWR_CR1_SRAM_RETV_DLP_Pos (18U)
#define PWR_CR1_SRAM_RETV_DLP_Msk (0x1UL << PWR_CR1_SRAM_RETV_DLP_Pos) /*!< 0x00040000 */
#define PWR_CR1_SRAM_RETV_DLP PWR_CR1_SRAM_RETV_DLP_Msk /*!< SRAM retention voltage control in DeepStop mode */
使用以下的代碼就能使 MCU 進入 DEEP STOP 模式
static void APP_EnterDeepStop(void)
{
/* Enable PWR clock */
LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_PWR);
/* STOP mode with deep low power regulator ON */
LL_PWR_SetLprMode(LL_PWR_LPR_MODE_DLPR);
/* SRAM retention voltage aligned with digital LDO output */
LL_PWR_SetStopModeSramVoltCtrl(LL_PWR_SRAM_RETENTION_VOLT_CTRL_LDO);
/* Enter DeepSleep mode */
LL_LPM_EnableDeepSleep();
/* Request Wait For Event */
__SEV();
__WFE();
__WFE();
LL_LPM_EnableSleep();
}
PY32F002B 在 DEEP STOP 模式下, 待機電流能降到約 0.6uA.
3. DEEP STOP 模式通過 LPTIM 喚醒
經過實際測試, PY32F002B 的 DEEP STOP 模式可以通過 LPTIM 喚醒
- 當 LPTIM 時鐘源使用 LSE 時, 定時準確, 精度取決於32K晶振的精度, 喚醒的間隔時間最長可以設置到256秒. 但是耗電稍高, 因為開啟 LSE 需要額外的 0.6uA 左右的電流, 待機電流 1.2uA 左右
- 當 LPTIM 時鐘源使用 LSI 時, 耗電最省, 待機只需要 0.6uA, 但是定時不准確. 當設置為4秒時, 實際間隔稍大於4秒, 當設置為5秒時, 實際間隔為7秒左右, 設置為6秒時, 實際間隔為26秒, 在7秒以上則很可能無法喚醒. 猜測在 DEEP STOP 模式下內部低速時鐘是依靠內部存留(電容?)電量運行, 會隨著內部電量消耗電壓降低而逐漸變慢.
相關代碼
以上的相關常式和代碼可以在下麵的鏈接中找到
- https://github.com/IOsetting/py32f0-template/tree/main/Examples/PY32F002B/LL/PWR
PY32F002B 的 STOP 和 DEEP STOP 模式 - https://github.com/IOsetting/py32f0-template/tree/main/Examples/PY32F0xx/LL/RCC/LSI_CurrentTest
PY32F002A/PY32F003/PY32F030 的 LSI 電流測試 - https://github.com/IOsetting/py32f0-template/tree/main/Examples/PY32F0xx/LL/LPTIM/LPTIM1_Wakeup
PY32F002A/PY32F003/PY32F030 的 STOP 模式和 LPTIM 喚醒