STM32F103 High Density设备RTC挖坑记录(1)
概述
最近手头的项目涉及到了低功率的数据采集及传输,于是就要折腾STM32的低功耗模式了。STM32F103x的低功耗模式有如下三种,Sleep模式,Stop模式以及Standby模式,其特性如下。
Sleep模式:
- 低功耗,唤醒时间短
- 利用ARM Cortex-M3的 WFI(Wake From Interrupt)/WFE(Wake From Event)特性
- CPU主时钟关闭,SRAM内容及外设状态保留
- SysTick继续,NVIC仍响应外设中断
- 唤醒条件:WFI模式下CPU可由任意中断唤醒,WFE模式需要Wakeup Event
Stop模式:
- 更低功耗,唤醒时间稍长
- 由Cortex-M3的Deepsleep特性与外设结合
- 1.8V域时钟停止,PLL,HSE与HSI关闭,CPU电压调节器置于低功耗模式
- SRAM及外设状态保留
- 唤醒条件:预先设置的EXTI外部中断
Standby模式:
- 最低功耗,唤醒时间最长(几乎等于RESET)
- 1.8V域完全断电
- SRAM内容及外设状态丢失
- 备份域(RTC及备份寄存器等)正常工作
- 唤醒条件:NRST引脚复位(废话),IWDG复位,RTC Alarm(稍后详细说明)以及WK_UP引脚上升沿
由于我们的应用需要在很长一段时间内保持低功耗,故选择Standby模式以保证最长电池寿命。
这里采用RTC Alarm唤醒模式,通过设定一个RTC闹钟来在给定时刻唤醒。
初始化RTC
我们的系统使用外置8MHz晶振,7xPLL作为CPU主时钟,RTC则使用外置LSE(32.768kHz晶振)作为备份时钟源,主时钟检测开启。HAL驱动初始化代码如下
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct;
RCC_ClkInitTypeDef RCC_ClkInitStruct;
RCC_PeriphCLKInitTypeDef PeriphClkInit;
/**Initializes the CPU, AHB and APB busses clocks
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE|RCC_OSCILLATORTYPE_LSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
RCC_OscInitStruct.LSEState = RCC_LSE_ON;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
/**Initializes the CPU, AHB and APB busses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV2;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_1) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_RTC|RCC_PERIPHCLK_ADC;
PeriphClkInit.RTCClockSelection = RCC_RTCCLKSOURCE_LSE;
PeriphClkInit.AdcClockSelection = RCC_ADCPCLK2_DIV4;
if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
/**Enables the Clock Security System
*/
HAL_RCC_EnableCSS();
/**Configure the Systick interrupt time
*/
HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq()/1000);
/**Configure the Systick
*/
HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);
/* SysTick_IRQn interrupt configuration */
HAL_NVIC_SetPriority(SysTick_IRQn, 0, 0);
}
然后,尴尬的事情出现了……实际写入Flash后运行,会卡在上述初始化步骤中的
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_1) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
Oops……看起来时钟没有初始化成功……
排除了软件问题后,剩下的就是HSE和LSE本身了。RCC配置切换到HSI,问题依然出现,切换到LSI,居然可以正常初始化了……
GDB调试的结果,代码在执行到这里就处在循环等待Flag的状态了,最终会进入超时错误处理。
/* Wait till LSE is ready */
while(__HAL_RCC_GET_FLAG(RCC_FLAG_LSERDY) == RESET)
{
if((HAL_GetTick() - tickstart ) > RCC_LSE_TIMEOUT_VALUE)
{
return HAL_TIMEOUT;
}
}
我还能怎么办?我也很绝望啊……
搬出示波器的结果是,显然RTC的32.768kHz晶振没有起振……
更神奇的是,这个晶振起振带着蜜汁玄学,有时起,有时不起,外壳接地焊了又拆,拆了又焊,用手摸一摸,示波器高频笔搭上去等等,都有可能出现玄学的起振。
W.T.F?
根据Datasheet的指示,这个型号的设备不能使用12.5pF负载电容的晶振。是不是生产这块板子的华强北厂子用了这种晶振?
当即某宝下单,6pF负载电容晶振,3毛5一个……坐等到货。
………………
没等到货,晶振厂家的数据表示我们这款晶振是12.5pF的,你莫不是买了假货……
W.T.F? x2
到货换上,果然,玄学起振阴魂不散……
我的深水宝零假货记录啊……
翻遍国内外各大论坛,大家伙儿纷纷表示这MCU挑晶振,而且还不是一般的挑,用麻袋装来的32768不能用(这是原话)……那么,您这芯片这么跳,ST大爷您怎么想?
突然,在Errata里面找到了下面的东西……
W.T.F? x3
哦好的ST这锅您背好不送。
然而,第二批大(五)价(毛)钱买来的爱普生7pF晶振换上之后,稳如Poi……
好吧我真的买了假货。
在20M欧的电阻送来之前,先做如下小结。
- STM32 RTC可以唤醒Standby模式的设备,保证最低功耗
- STM32F103x High Density 设备的RTC电路设计问题导致不能使用常见的12.5pF晶振
- Errata描述需要在晶振两端并联16-25M欧的电阻
- 某宝售3毛钱的6pF的KDS晶振是假的,推荐使用EPSON MC-146 7pF贴片晶振
- 多数开发板使用6x2圆柱晶振,其焊盘也适用于MC-146,可直接替换
未完待续