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欧的电阻送来之前,先做如下小结。

  1. STM32 RTC可以唤醒Standby模式的设备,保证最低功耗
  2. STM32F103x High Density 设备的RTC电路设计问题导致不能使用常见的12.5pF晶振
  3. Errata描述需要在晶振两端并联16-25M欧的电阻
  4. 某宝售3毛钱的6pF的KDS晶振是假的,推荐使用EPSON MC-146 7pF贴片晶振
  5. 多数开发板使用6x2圆柱晶振,其焊盘也适用于MC-146,可直接替换
    未完待续