有项目要做低功耗,试了一下STM32G030的LP Run模式。
LP Run模式进入方法
手册上进LP Run模式的步骤:
STM32CubeMX里面直接把频率设到2M,这样不用在代码里改主频,直接就能进LP Run模式。
进LP Run就很简单了,直接调用库函数:
HAL_PWREx_EnableLowPowerRunMode();
寄存器PWR_SR2的值从0x0180变成了0x0380
所以可以加一个循环,等待模式切换成功:
while(!HAL_IS_BIT_SET(PWR->SR2, PWR_SR2_REGLPF));
关闭Flash
让代码在SRAM运行
如果说进行Low-Power Run是平平无奇,一帆风顺的话,关闭Flash就是天坑,一步一个坑。
先改sct文件,加一句*.o(RAMCODE):
; *************************************************************
; *** Scatter-Loading Description File generated by uVision ***
; *************************************************************LR_IROM1 0x08000000 0x00008000 { ; load region size_region
ER_IROM1 0x08000000 0x00008000 { ; load address = execution address
*.o (RESET, +First)
*(InRoot$$Sections)
.ANY (+RO)
.ANY (+XO)
}
RW_IRAM1 0x20000000 0x00002000 { ; RW data
*.o(RAMCODE)
.ANY (+RW +ZI)
}
}
函数前加__attribute__((section("RAMCODE")))修饰符就会被加载到SRAM区域,例如:
__attribute__((section("RAMCODE"))) void sensor_loop(void)
{
//...
}
编译之后,map文件:
关闭Flash
进入Low-Power Run模式的代码改成:
HAL_PWREx_EnableFlashPowerDown(PWR_FLASHPD_LPRUN);
HAL_PWREx_EnableLowPowerRunMode();
while(!HAL_IS_BIT_SET(PWR->SR2, PWR_SR2_REGLPF));
当你写代码时坑就来了,不能调用库函数,因为只要调用就会跳转到Flash,除非把调用的函数也加到RAMCODE段。所以用到哪些库函数,都要自己改写。直接读写寄存是没问题的,一般HAL的宏函数喜欢写成全大写,或者以两条下划线开头,比如下面这些函数就都没有问题:
__HAL_I2C_CLEAR_FLAG(hi2c, I2C_FLAG_STOPF);
I2C_RESET_CR2(hi2c);
__HAL_TIM_SetCompare(&htim1, TIM_CHANNEL_1, pluse);
所以为了安全起见,上面进入Low-Power Run模式的代码也改成了不使用HAL函数的方案:
PWR->CR1 |= PWR_FLASHPD_LPRUN; //0x40007000
PWR->CR1 |= PWR_FLASHPD_LPSLEEP;
PWR->CR1 |= PWR_FLASHPD_STOP;
SET_BIT(PWR->CR1, PWR_CR1_LPR); //0x40007000
while(!HAL_IS_BIT_SET(PWR->SR2, PWR_SR2_REGLPF)); //0x40007014
关闭中断
因为中断向量的代码在Flash段,所以只要中断,就会卡死。我的代码不用中断,我直接禁用:
__disable_irq();
禁用全局中断之后,要注意,系统滴答也没有了,要延时就用代码跑循环,HAL的库里在Low Power这一块就有这么干的,例如下面这个函数:
/**
* @brief Exit Low-power Run mode.
* @note Before HAL_PWREx_DisableLowPowerRunMode() completion, the function checks that
* REGLPF has been properly reset (otherwise, HAL_PWREx_DisableLowPowerRunMode
* returns HAL_TIMEOUT status). The system clock frequency can then be
* increased above 2 MHz.
* @retval HAL Status
*/
HAL_StatusTypeDef HAL_PWREx_DisableLowPowerRunMode(void)
{
uint32_t wait_loop_index = ((PWR_REGLPF_SETTING_DELAY_6_US * SystemCoreClock) / 1000000U) + 1U;
/* Clear LPR bit */
CLEAR_BIT(PWR->CR1, PWR_CR1_LPR);
/* Wait until REGLPF is reset */
while (HAL_IS_BIT_SET(PWR->SR2, PWR_SR2_REGLPF))
{
if (wait_loop_index != 0U)
{
wait_loop_index--;
}
else
{
return HAL_TIMEOUT;
}
}
return HAL_OK;
}
除法的问题
下一个问题就更麻烦了,因为Cortex-M0是没有除法器的,所以在做除法或取模时,实际上是调用__aeabi_uidiv函数,再次卡死。
例外是除以2的幂,或者除以10。不会调用函数。
我的代码是优化算法,把除法去掉了。如果实在去不掉,就自己在网上找除法的函数:
//gcc的除和模除算法,在这里实现为了避免memcode跳转到flash域
//num:被除数;den:除数;modwanted:是除法还是取模
__attribute__((section("RAMCODE"))) uint32_t __udivmodsi4(uint32_t num, uint32_t den, int modwanted)
{
uint32_t bit = 1;
uint32_t res = 0;
while (den < num && bit && !(den & (1L<<31)))
{
den <<=1;
bit <<=1;
}
while (bit)
{
if (num >= den)
{
num -= den;
res |= bit;
}
bit >>=1;
den >>=1;
}
if (modwanted) return num;
return res;
}
__attribute__((section("RAMCODE"))) uint16_t __udivmodhi4(uint16_t num, uint16_t den, int modwanted)
{
uint16_t bit = 1;
uint16_t res = 0;
while (den < num && bit && !(den & (1U<<15)))
{
den <<=1;
bit <<=1;
}
while (bit)
{
if (num >= den)
{
num -= den;
res |= bit;
}
bit >>=1;
den >>=1;
}
if (modwanted) return num;
return res;
}
常量的问题
如果是定义的一般的常量,编译器会直接当作宏来处理,这个没什么问题。
但是当算法中有查表的需求,写了一个数组,而且是只读的。那这个数组其实是在code段,也就是flash中,自然也会卡死。
而且在map中还找不到这个数组名,很迷。
所以解决的方法也很简单,就是把这个数组加长一点,然后在代码中写一下,编译器就会认为这个数组是RW,就会放在ram中。
这个问题应该有更优雅的解决办法,不过我没找到。
最终模式切换代码是这样的:
__attribute__((section("RAMCODE"))) void sensor_loop(void)
{
//...
//关闭全局中断
__disable_irq();
//关闭flash
//HAL_PWREx_EnableFlashPowerDown(PWR_FLASHPD_LPRUN);
PWR->CR1 |= PWR_FLASHPD_LPRUN; //0x40007000
PWR->CR1 |= PWR_FLASHPD_LPSLEEP;
PWR->CR1 |= PWR_FLASHPD_STOP;
//进入Low-Power Run
//HAL_PWREx_EnableLowPowerRunMode();
SET_BIT(PWR->CR1, PWR_CR1_LPR); //0x40007000
//等待电源切换完成
while(!HAL_IS_BIT_SET(PWR->SR2, PWR_SR2_REGLPF)); //0x40007014
while(1)
{
//...
}
//不会执行到这里
CLEAR_BIT(PWR->CR1, PWR_CR1_LPR); //退出Low-Power Run
while (HAL_IS_BIT_SET(PWR->SR2, PWR_SR2_REGLPF)); //等待电源切换完成
while (!HAL_IS_BIT_SET(PWR->SR2, PWR_SR2_FLASH_RDY)); //等待Flash就绪
__enable_irq(); //打开全局中断
}
疑问
主频不设置成2M以下,好像也是能把电源切成Low-Power,估计可能运行不太稳定吧。