STM32 HAL库学习(二):RCC and RTC

1 RCC(Reset Clock Control)

1.1 系统时钟

Q1:什么是时钟?

  • 时钟是单片机运行的基础,时钟信号推动单片机内各个部分执行相应的指令。 时钟系统就是CPU的脉搏,决定CPU速率,像人的心跳一样,只有有了心跳,人才能做其他的事情,而单片机有了时钟,才能够运行执行指令,才能够做其他的处理 (点灯,串口,ADC),时钟的重要性不言而喻。

Q2:为什么 STM32 要有多个时钟源?

  • STM32本身十分复杂,外设非常多,但我们实际使用的时候只会用到有限的几个外设,使用任何外设都需要时钟才能启动,但并不是所有的外设都需要系统时钟那么高的频率,为了兼容不同速度的设备,有些高速,有些低速,如果都用高速时钟,势必造成浪费;并且同一个电路,时钟越快功耗越快,同时抗电磁干扰能力也就越弱,所以较为复杂的MCU都是采用多时钟源的方法来解决这些问题。

对于上述话可总结为以下两点原因:
1、STM32 外设多,不同的外设有不同的时钟要求;
2、功耗原因,速度越快,功耗越大,抗电磁干扰能力越弱。

Q3:记忆点

  • STM32时钟系统主要的目的就是给相对独立的外设模块提供时钟,也是为了降低整个芯片的耗能
  • 系统时钟,是处理器运行时间基准(每一条机器指令一个时钟周期)
  • STM32为了低功耗,他将所有的外设时钟都设置为disable(不使能),用到什么外设,只要打开对应外设的时钟就可以, 其他的没用到的可以还是disable(不使能),这样耗能就会减少。 这就是为什么不管你配置什么功能都需要先打开对应的时钟的原因
1.2 时钟树

RCC时钟树详解
下面这张图可以从左、右两部分来理解:

  • 左边是时钟源的选择,从而产生系统时钟
  • 右边是外设是系统时钟通过AHB预分频器****,给外设设置时钟频率

从左到右可以简单理解为
时钟源—>系统时钟源的选择—>各个外设时钟的设置

即,STM32将时钟信号(例如HSE)经过分频或倍频(PLL)后,得到系统时钟,系统时钟经过分频,产生外设所使用的时钟。
在这里插入图片描述

1.2.1 时钟源

STM32 有4个独立时钟源:HSI、HSE、LSI、LSE。

其中,LSI是作为IWDGCLK(独立看门狗)时钟源和RTC时钟源 而独立使用;

而HSI高速内部时钟 HSE高速外部时钟 LSI低速内部时钟 这三个经过分频或者倍频 作为系统时钟来使用;

PLL为锁相环倍频输出,其时钟输入源可选择为HSI/2、HSE或者HSE/2。倍频可选择为2~16倍,但是其输出频率最大不得超过72MHz通过倍频之后作为系统时钟的时钟源。
在这里插入图片描述
表中,H 高 L 低 I 内 E 外
使用 STM32CubeMX 配置时钟时,可以看到下图
STM32CubeMX使用教程
在这里插入图片描述

1.2.2 系统时钟

从图中可以看出,系统时钟SYSCLK可来源于三个时钟源:
①、HSI振荡器时钟
②、HSE振荡器时钟
③、PLL时钟
最大为72Mhz
在这里插入图片描述

1.2.3 外设时钟

右边部分为:系统时钟SYSCLK通过AHB分频器分频后送给各模块使用,AHB分频器可选择1、2、4、8、16、64、128、256、512分频。
其中AHB分频器输出的时钟送给5大模块使用:

  • 内核总线:送给AHB总线、内核、内存和DMA使用的HCLK时钟。
  • Tick定时器:通过8分频后送给Cortex的系统定时器时钟。
  • I2S总线:直接送给Cortex的空闲运行时钟FCLK。
  • APB1外设:送给APB1分频器。APB1分频器可选择1、2、4、8、16分频,其输出一路供APB1外设使用(PCLK1,最大频率36MHz),另一路送给通用定时器使用。该倍频器可选择1或者2倍频,时钟输出供定时器2-7使用。
  • APB2外设:送给APB2分频器。APB2分频器可选择1、2、4、8、16分频,其输出一路供APB2外设使用(PCLK2,最大频率72MHz),另一路送给高级定时器。该倍频器可选择1或者2倍频,时钟输出供定时器1和定时器8使用。

另外,APB2分频器还有一路输出供ADC分频器使用,分频后送给ADC模块使用。ADC分频器可选择为2、4、6、8分频。

以F1系列为例,
APB1上面连接的是低速外设,包括电源接口、备份接口、CAN、USB、****I2C1、I2C2、USART2、USART3、UART4、UART5、SPI2、SP3等;

APB2上面连接的是高速外设,包括UART1、SPI1、Timer1、ADC1、ADC2、ADC3、所有的普通I/O口(PA-PE)、第二功能I/O(AFIO)口等。

1.3 常用结构体

结构体具体参数含义

1.3.1 RCC_OscInitTypeDef

RCC内部/外部振荡器(HSE, HSI, LSE, LSI)配置结构体定义

typedef struct
{
  uint32_t OscillatorType;           //选定将被配置的振荡器        
  uint32_t HSEState;               //HSE状态              
  uint32_t LSEState;               //LSE状态            
  uint32_t HSIState;               //HSI状态             
  uint32_t HSICalibrationValue;      //HSI校准调整值  
  uint32_t LSIState;               //LSI状态          
 
#if defined(RCC_HSI48_SUPPORT)
  uint32_t HSI48State;       //HSI状态,#if defined(RCC_HSI48_SUPPORT)            
#endif
 
  uint32_t MSIState;             //MSI状态              
  uint32_t MSICalibrationValue;   //MSI校准调整值  
  uint32_t MSIClockRange;       //MSI频率范围       
  RCC_PLLInitTypeDef PLL;       //PLL结构体参数         
} RCC_OscInitTypeDef;
1.3.2 RCC_ClkInitTypeDef

在stm32l0xx_hal_rcc.h中
在RCC系统,AHB和APB总线时钟配置结构体定义

typedef struct
{
  uint32_t ClockType;       //①,选定将被配置的时钟             
  uint32_t SYSCLKSource;    //②,用作系统时钟的时钟源选择         
  uint32_t AHBCLKDivider;   //③,AHB时钟(HCLK)分频器,该时钟由SYSCLK而来      
  uint32_t APB1CLKDivider;  //④,APB1时钟(PCLK1)分频器,该时钟由HCLK而来      
  uint32_t APB2CLKDivider;  //⑤,APB2时钟(PCLK2)分频器,该时钟由HCLK而来      
} RCC_ClkInitTypeDef;
1.3.3 RCC_PLLInitTypeDef

在stm32l0xx_hal_rcc.h中
RCC PLL配置结构体定义

typedef struct
{
  uint32_t PLLState;   //①,PLL状态      
  uint32_t PLLSource;  //②,PLL输入时钟源               
  uint32_t PLLMUL;     //③,PLL输入时钟倍增系数              
  uint32_t PLLDIV;     //④,PLL输入时钟分频系数                
} RCC_PLLInitTypeDef;

2 RTC(Real Time Clock)

实时时钟 (RTC) 是一个独立的 BCD 定时器/计数器,只能向上计数。 RTC 提供具有可编程闹钟中断功能的日历时钟 /日历。RTC 还包含具有中断功能的周期性可编程唤醒标志。

利用RTC可以实现产品的精确计时,比如平时用的笔记本电脑、电子日历等都有RTC模块,当主电源断电时,RTC的模块用电池来供电,继续进行计时。STM32中的RTC和定时器有点类似,有一个32位的计数器,可以计数2的32次方,大约可以计时136年。

假如某个时刻读取到计数器的数值为X = 60* 60* 24* 2,即两天时间的秒数,而假设又
知道计数器是在2011 年1 月1 日的0 时0 分0 秒置0 的,那么就可以根据计数器的这个相
对时间数值,计算得这个X 时刻是2011 年1 月3 日的0 时0 分0 秒了。

2.1 RTC时钟源

RTC时钟源分为三种:

(1)LSE,外部低速时钟
(2)HSE,外部高速时钟的128 分频
(3)LSI,内部低速时钟
使HSE 分频时钟或LSI 的话,在主电源VDD 掉电的情况下,这两个时钟来源都
会受到影响,因此没法保证RTC 正常工作。因此RTC 一般使用低速外部时钟LSE。

在主电源VDD 有效的情况下(待机),RTC 还可以配置闹钟事件使STM32 退出待机模式。

2.2 RTC相关代码
// 常见结构体
RTC_HandleTypeDef
RTC_AlarmTypeDef
RTC_TimeTypeDe
RTC_DateTypeDef
第一步,选择RTC的时钟源

本例选择内部低速时钟LSI,在系统时钟 RCC 配置中有该时钟源的配置。

void SystemClock_Config(void{ 
    //..............省略   
     PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_RTC;      
   PeriphClkInit.RTCClockSelection = RCC_RTCCLKSOURCE_LSI;                     
   if(HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit)= HAL_OK!)
   {   
       _Error_Handler(__ FILE __,__ LINE__); 
   } 
   //............省略 
}
第二步,RTC初始化和使能

进行RTC初始化并启动设置RTC的定时时间

void MX_RTC_Init(void{ 
    / **初始化RTC  
    * / 
  hrtc.Instance = RTC; 
  hrtc.Init.HourFormat = RTC_HOURFORMAT_24; // 24小时制,还可选择12小时制
  hrtc.Init.AsynchPrediv = 124; //异步预分频值
  hrtc.Init.SynchPrediv = 295; //同步预分频值,异步与同步的预分频设置下面进行详细讲解
  hrtc.Init.OutPut = RTC_OUTPUT_DISABLE; //输出关闭
  hrtc.Init.OutPutPolarity = RTC_OUTPUT_POLARITY_HIGH;// 输出极性为高,无效
  hrtc.Init.OutPutType = RTC_OUTPUT_TYPE_OPENDRAIN; //输出方式,开漏
  if(HAL_RTC_Init(&hrtc)!= HAL_OK)
  { 
    _Error_Handler(__ FILE __,__ LINE__); 
  } 
 
    / **初始化RTC并设置时间和日期  
    * / 
  if(HAL_RTCEx_BKUPRead(&hrtc,RTC_BKP_DR0)!= 0x32F2{ 
  sTime.Hours = 0x09; 
  sTime.Minutes = 0x1F; 
  sTime.Seconds = 0x0;
  sTime.DayLightSaving = RTC_DAYLIGHTSAVING_NONE; //不使用夏令时
  sTime.StoreOperation = RTC_STOREOPERATION_RESET; 
  if(HAL_RTC_SetTime(&hrtc,&sTime,RTC_FORMAT_BIN)!= HAL_OK)//注意传入参数有二进制格式和BCD格式可选
  { 
    _Error_Handler(__ FILE __,__ LINE__); 
  } 
 
  sDate.WeekDay = RTC_WEEKDAY_MONDAY; 
  sDate.Month = RTC_MONTH_NOVEMBER; 
  sDate.Date = 0x15; 
  sDate.Year = 0x11; 
 
  if(HAL_RTC_SetDate(&hrtc,&sDate,RTC_FORMAT_BIN)!= HAL_OK)
  { 
    _Error_Handler(__ FILE __,__ LINE__); 
  } 
 
    HAL_RTCEx_BKUPWrite(&hrtc,RTC_BKP_DR0,0x32F2; 
  }
  //设置RTC定时时间 alarm_sec 产生中断
  /**Enable the Alarm A
      */
    sAlarm.AlarmTime.Hours = 0x0;
    sAlarm.AlarmTime.Minutes = 0x0;
    sAlarm.AlarmTime.Seconds = alarm_sec;
    sAlarm.AlarmTime.SubSeconds = 00;
    sAlarm.AlarmTime.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;
    sAlarm.AlarmTime.StoreOperation = RTC_STOREOPERATION_RESET;
    sAlarm.AlarmMask = RTC_ALARMMASK_DATEWEEKDAY | RTC_ALARMMASK_HOURS | RTC_ALARMMASK_MINUTES;
    sAlarm.AlarmSubSecondMask = RTC_ALARMSUBSECONDMASK_ALL;
    sAlarm.AlarmDateWeekDaySel = RTC_ALARMDATEWEEKDAYSEL_DATE;
    sAlarm.AlarmDateWeekDay = 0x1;
    sAlarm.Alarm = RTC_ALARM_A;

    if (HAL_RTC_SetAlarm_IT(&hrtc, &sAlarm, RTC_FORMAT_BCD) != HAL_OK)
    {
        _Error_Handler(__FILE__, __LINE__);
    }     
}

RTC预分频值的设定关系到RTC多长时间记一次数。为了实现RTC的秒中断,需要对同步和异步预分频进行设置,如下图。
在这里插入图片描述
如上图,为了得到1HZ计数周期,37KHZ/(124+1)/(295+1)=1HZ , 故预分频设置为124和295,如使用32.768k,设置为127和255

RTC的时钟使能如下:

void HAL_RTC_MspInit(RTC_HandleTypeDef * hrtc)
{ 
       if(hrtc-> Instance == RTC)
      { 
        / * *RTC时钟使能* / 
        __HAL_RCC_RTC_ENABLE(); 
      / * USER CODE BEGIN RTC_MspInit 1 * / 
        HAL_NVIC_SetPriority(RTC_WKUP_IRQn,0,0;//设置RTC秒中断优先级
        HAL_NVIC_EnableIRQ(RTC_WKUP_IRQn);       //使能RTC秒中断
      / * USER CODE END RTC_MspInit 1 * / 
      } 
}
第三步,编写RTC中断服务函数

清除中断标志位,调用中断回调函数。

注意:后面两处代码与RTC初始化有冲突,所产生的中断不一样,这里是闹钟中断。

void HAL_RTC_AlarmIRQHandler(RTC_HandleTypeDef* hrtc)
{  
  /* Get the AlarmA interrupt source enable status */
  if(__HAL_RTC_ALARM_GET_IT_SOURCE(hrtc, RTC_IT_ALRA) != RESET)
  {
    /* Get the pending status of the AlarmA Interrupt */
    if(__HAL_RTC_ALARM_GET_FLAG(hrtc, RTC_FLAG_ALRAF) != RESET)
    {
      /* AlarmA callback */
      HAL_RTC_AlarmAEventCallback(hrtc);

      /* Clear the AlarmA interrupt pending bit */
      __HAL_RTC_ALARM_CLEAR_FLAG(hrtc, RTC_FLAG_ALRAF);
    }
  }
  
  /* Clear the EXTI's line Flag for RTC Alarm */
  __HAL_RTC_ALARM_EXTI_CLEAR_FLAG();
  
  /* Change RTC state */
  hrtc->State = HAL_RTC_STATE_READY; 
}
第四步,执行回调函数
void HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef *hrtc)
{

    gstr_sys_power_manager.wake_up_flag = WAKE_UP_FROM_RTC;
}
  • 3
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
好的,我来逐句注释这段函数: ```c void SystemClock_Config(void) { RCC_OscInitTypeDef RCC_OscInitStruct = {0}; RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; /** Configure the main internal regulator output voltage */ __HAL_RCC_PWR_CLK_ENABLE(); // 使能电源时钟 __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1); // 配置主内部稳压器的输出电压 /** Initializes the RCC Oscillators according to the specified parameters * in the RCC_OscInitTypeDef structure. */ RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; // RCC振荡器类型为HSE RCC_OscInitStruct.HSEState = RCC_HSE_ON; // 使能HSE时钟源 RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; // 使能PLL RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; // PLL时钟源为HSE RCC_OscInitStruct.PLL.PLLM = 8; // PLL输入时钟频率除以M (M=8) RCC_OscInitStruct.PLL.PLLN = 336; // PLL倍频系数N为336 RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2; // PLL输出时钟频率除以P (P=2) RCC_OscInitStruct.PLL.PLLQ = 4; // PLL输出时钟频率除以Q (Q=4) if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) // 初始化RCC振荡器 { Error_Handler(); // 如果初始化失败,则调用错误处理函数 } /** Initializes the CPU, AHB and APB buses clocks */ RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2; // 配置系统时钟和总线时钟类型 RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; // 系统时钟源为PLL RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; // AHB总线时钟为系统时钟 RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4; // APB1总线时钟为系统时钟的1/4 RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2; // APB2总线时钟为系统时钟的1/2 if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK) // 初始化CPU、AHB和APB总线时钟 { Error_Handler(); // 如果初始化失败,则调用错误处理函数 } } ``` 这段代码主要是对 STM32 微控制器的时钟进行配置,包括配置内部稳压器输出电压、配置 RCC 振荡器、配置 PLL 和配置 CPU、AHB 和 APB 总线时钟。其中,PLL 是锁相环,可以通过调节 PLLM、PLLN、PLLP、PLLQ 等参数来调节系统时钟频率。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

la_fe_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值