網(wǎng)站建設授權書百度客服聯(lián)系方式
【極海APM32替代筆記】HAL庫低功耗STOP停止模式的串口喚醒(解決進入以后立馬喚醒、串口喚醒和回調(diào)無法一起使用、接收數(shù)據(jù)不全的問題)
【STM32筆記】低功耗模式配置及避坑匯總
前文:
blog.csdn.net/weixin_53403301/article/details/128216064
【STM32筆記】HAL庫低功耗模式配置(ADC喚醒無法使用、低功耗模式無法燒錄解決方案)
低功耗模式如圖所示
停止模式有三種 分別是0 1 2
其中 0 1可以由串口喚醒
2只能由LPUART喚醒
在手冊里可以查到
進入也很簡單:
/*!* @brief 進入低功耗模式 ** @param [in] mode_flag: 模式標志* 0/大于4 不進入任何模式,1 進入睡眠,2 進入停止,3 進入待機,4 關機* [in] WakeUpPinPolarity: 待機模式下WKUP喚醒引腳極性配置,其他模式無用** @return None*/
void Enter_Low_PWR(uint8_t mode_flag,uint32_t WakeUpPinPolarity)
{ __HAL_RCC_PWR_CLK_ENABLE();switch(mode_flag){case 0:{printf("[INFO] 不進入低功耗模式\n");break;}case 1:{printf("[INFO] 進入睡眠模式\n");delay_ms(10); //消抖__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU); HAL_PWR_EnterSLEEPMode(PWR_MAINREGULATOR_ON,PWR_SLEEPENTRY_WFI);break;}case 2:{printf("[INFO] 進入停止模式\n");delay_ms(10); //消抖__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON,PWR_SLEEPENTRY_WFI);break;}case 3:{printf("[INFO] 三秒后進入待機模式\n");delay_ms(3000);printf("[INFO] 進入待機模式\n");HAL_PWR_EnableWakeUpPin(WakeUpPinPolarity);delay_ms(10); //消抖__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);HAL_PWR_EnterSTANDBYMode();break;}case 4:{printf("[INFO] 三秒后進入關機模式\n");delay_ms(3000);printf("[INFO] 進入關機模式\n");HAL_PWR_EnableWakeUpPin(WakeUpPinPolarity);delay_ms(10); //消抖__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);HAL_PWREx_EnterSHUTDOWNMode();break;}default:{printf("[INFO] 不進入低功耗模式\n");break;}}
}
要進入停止2模式則需要在pwr_ex.c中配置
HAL_PWREx_EnterSTOP2Mode();函數(shù)
其中
HAL_PWR_EnterSTOPMode中的PWR_MAINREGULATOR_ON、PWR_LOWPOWERREGULATOR_ON分別是開啟穩(wěn)壓器和關閉穩(wěn)壓器 分別對應STOP 0和1
在停止模式中,進一步關閉了其它所有的時鐘,于是所有的外設都停止了工作,但由于其 1.8V 區(qū)域的部分電源沒有關閉,還保留了內(nèi)核的寄存器、內(nèi)存的信息,所以從停止模式喚醒,并重新開啟時鐘后,還可以從上次停止處繼續(xù)執(zhí)行代碼。停止模式可以由任意一個外部中斷(EXTI)喚醒,在停止模式中可以選擇電壓調(diào)節(jié)器為開模式或低功耗模式。
特性和說明:調(diào)壓器低功耗模式: 在停止模式下調(diào)壓器可工作在正常模式或低功耗模式,可進一步降低功耗。
進入方式: 內(nèi)核寄存器的 SLEEPDEEP=1,PWR_CR 寄存器中的 PDDS=0,然后調(diào)用 WFI 或 WFE 指令即可進入停止模式;PWR_CR 寄存器的 LPDS=0 時,調(diào)壓器工作在正常模式,LPDS=1 時工作在低功耗模式。
喚醒方式: 如果是使用 WFI 指令睡眠的,可使用任意 EXTI 線的中斷喚醒;如果是使用 WFE 指令睡眠的,可使用任意配置為事件模式的 EXTI 線事件喚醒。
停止時: 內(nèi)核停止,片上外設也停止。這個狀態(tài)會保留停止前的內(nèi)核寄存器、內(nèi)存的數(shù)據(jù)。
喚醒延遲: 基礎延遲為 HSI 振蕩器的啟動時間,若調(diào)壓器工作在低功耗模式,還需要加上調(diào)壓器從低功耗切換至正常模式下的時間。
喚醒后: 若由中斷喚醒,先進入中斷,退出中斷服務程序后,接著執(zhí)行 WFI 指令后的程序;若由事件喚醒,直接接著執(zhí)行 WFE 后的程序。喚醒后,STM32 會使用 HSI 作為系統(tǒng)時鐘。
只能由外部中斷喚醒 喚醒后需要重新使能時鐘(SystemClock_Config();)
建議將一條外部中斷線專門作為喚醒中斷,執(zhí)行中斷后進入回調(diào)進行時鐘使能
停止模式0和1由PWR_MAINREGULATOR_ON和PWR_LOWPOWERREGULATOR_ON兩個變量確定
停止模式0和1可以被串口 I2C等設備喚醒(具體看手冊)
停止模式2則在pwr_ex.c中進入
停止模式2 只能被特定器件(如LPUART等在內(nèi)部與EXTI有鏈接的器件)喚醒
若要配置UART喚醒 則需要:
/*!* @brief 配置串口在停止模式下的喚醒 ** @param [in] huart: UART_HandleTypeDef類型的器件* [in] EnableNotDisable: 使能或者關閉** @return None*/
void Ctrl_UART_StopMode_WakeUp(UART_HandleTypeDef *huart,bool EnableNotDisable)
{ if(EnableNotDisable){__HAL_RCC_WAKEUPSTOP_CLK_CONFIG(RCC_STOP_WAKEUPCLOCK_HSI); //保留喚醒用的HSI線 串口初始化時鐘也必須要配置為HSIUART_WakeUpTypeDef UART_WakeUpStruct={0};UART_WakeUpStruct.WakeUpEvent=UART_WAKEUP_ON_READDATA_NONEMPTY; //接收數(shù)據(jù)不為空時喚醒HAL_UARTEx_StopModeWakeUpSourceConfig(huart,UART_WakeUpStruct);__HAL_UART_ENABLE_IT(&huart2,UART_IT_WUF); //開啟喚醒中斷HAL_UARTEx_EnableStopMode(huart); //開啟模式}else{__HAL_UART_DISABLE_IT(&huart2,UART_IT_WUF); //關閉喚醒中斷HAL_UARTEx_DisableStopMode(huart); //關閉模式}
}
配置為接收數(shù)據(jù)就喚醒
若要使用 則UART必須為HSI或MSI時鐘 配置太麻煩 所以我建議直接在HAL里面配置
串口回調(diào)一般是:
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{if(huart==&huart2){ HAL_UART_Transmit(&huart2,&RxBuffer,1,0xFFFF);HAL_UART_Receive_IT(&huart2,&RxBuffer,1);}if(huart==&huart4){ HAL_UART_Transmit(&huart4,&RxBuffer,1,0xFFFF);HAL_UART_Receive_IT(&huart4,&RxBuffer,1);}
}
接收數(shù)據(jù)后發(fā)送數(shù)據(jù)
而喚醒回調(diào)則是:
void HAL_UARTEx_WakeupCallback(UART_HandleTypeDef *huart)
{if(huart==&huart2){ __HAL_RCC_PWR_CLK_ENABLE();SystemClock_Config(); Ctrl_UART_StopMode_WakeUp(huart,false);}
}
進入以后立馬喚醒
若是串口懸空 或硬件設計問題 串口數(shù)據(jù)不定 則可能進入以后立馬被喚醒
在外部硬件上加下拉 或者軟件配置下拉(或上拉)即可 不過下拉更省電
串口喚醒和回調(diào)無法一起使用的問題
在調(diào)試時 發(fā)現(xiàn)串口喚醒和回調(diào)無法一起使用 進入了回調(diào)以后就退出了 不會進入串口喚醒
其實就是
__HAL_RCC_WAKEUPSTOP_CLK_CONFIG(RCC_STOP_WAKEUPCLOCK_HSI); //保留喚醒用的HSI線 串口初始化時鐘也必須要配置為HSI
的問題
若不使用這個語句 雖然串口可以用 也能接收數(shù)據(jù)并返回 但是進不了喚醒回調(diào)
其實就是因為時序被改變了
進入低功耗以后 接收數(shù)據(jù)喚醒 則先進入接收回調(diào) 然后發(fā)一次數(shù)據(jù) 但不會進行喚醒 因為時序有問題 mcu認為接收的數(shù)據(jù)不正常
保留這個語句以后就好了
另外 每次進入低功耗前 都要先調(diào)用
Ctrl_UART_StopMode_WakeUp(huart,true);
語句 否則無法正常喚醒 也不能再次進入低功耗模式
為了避免出錯 每次喚醒以后都應該清空調(diào)喚醒中斷
STOP模式會關閉時鐘 所以建議是回調(diào)以后就初始化時鐘一次
Ctrl_UART_StopMode_WakeUp(&huart2,true);
Enter_Low_PWR(2,0);
LPUART的配置同理 完全一模一樣的語句
串口回調(diào)接收數(shù)不全的問題
在前文中 我們將喚醒后的時鐘初始化寫進了喚醒回調(diào)中
如果沒有特別要求 這樣寫確實挺方便的
RTC喚醒后的配置就可以這樣來寫
但是 在調(diào)試時發(fā)現(xiàn) 如果發(fā)送長字符 則可能導致接收數(shù)據(jù)不全的問題
我用的波特率是115200 在沒有進入低功耗時 發(fā)送連續(xù)幾段012345689 接收到數(shù)組內(nèi)是正常的
我的回調(diào)函數(shù)為:
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{if(huart==&huart2){
// HAL_UART_Transmit(&huart2,&(RxBuffer[RxFlag]),1,0xFFFF);RxFlag++;HAL_UART_Receive_IT(&huart2,&(RxBuffer[RxFlag]),1); }
}
但是 在進入低功耗后喚醒 buf里面只能接收到前兩個字符01 然后延后大約10個字符后繼續(xù)接收 總數(shù)少了約10個字符
經(jīng)過調(diào)試發(fā)現(xiàn) 其執(zhí)行的順序是先開啟兩次串口回調(diào) 接收到第一個字符 然后進入喚醒回調(diào) 在喚醒回調(diào)中 初始化時間較長 所以沒有觸發(fā)接收回調(diào)函數(shù) 導致丟失字符
調(diào)整后 喚醒回調(diào)改成:
void HAL_UARTEx_WakeupCallback(UART_HandleTypeDef *huart)
{}
同時把退出低功耗后的初始化配置放在退出低功耗以后(不采用中斷回調(diào)函數(shù)來完成)
printf("[INFO] 進入停止模式\n");
delay_ms(10); //消抖
Ctrl_UART_StopMode_WakeUp(&huart2,true);
Ctrl_RTC_WakeUp(5000,RTC_WAKEUPCLOCK_RTCCLK_DIV16,true);
PWR_Device_Init(false);
__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);
HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON,PWR_SLEEPENTRY_WFI);
__HAL_RCC_PWR_CLK_ENABLE();
SystemClock_Config();
Ctrl_UART_StopMode_WakeUp(&huart2,false);
Ctrl_RTC_WakeUp(0,0,false);
PWR_Device_Init(true);
這樣的話 就不會出現(xiàn)問題了
說到底 就是喚醒中斷優(yōu)先級高(且無法配置) 觸發(fā)了這個中斷 低級的串口接收中斷就無法觸發(fā)了
在實際應用中 也應保證中斷時間越短越好 以免干擾到其他中斷