Q1:什么是时钟?
Q2:为什么 STM32 要有多个时钟源?
对于上述话可总结为以下两点原因:
1、STM32 外设多,不同的外设有不同的时钟要求;
2、功耗原因,速度越快,功耗越大,抗电磁干扰能力越弱。
Q3:记忆点
RCC时钟树详解
下面这张图可以从左、右两部分来理解:
从左到右可以简单理解为
时钟源—>系统时钟源的选择—>各个外设时钟的设置
即,STM32将时钟信号(例如HSE)经过分频或倍频(PLL)后,得到系统时钟,系统时钟经过分频,产生外设所使用的时钟。
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使用教程
从图中可以看出,系统时钟SYSCLK可来源于三个时钟源:
①、HSI振荡器时钟
②、HSE振荡器时钟
③、PLL时钟
最大为72Mhz
右边部分为:系统时钟SYSCLK通过AHB分频器分频后送给各模块使用,AHB分频器可选择1、2、4、8、16、64、128、256、512分频。
其中AHB分频器输出的时钟送给5大模块使用:
另外,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)口等。
结构体具体参数含义
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;
在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;
在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;
实时时钟 (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 秒了。
RTC时钟源分为三种:
(1)LSE,外部低速时钟
(2)HSE,外部高速时钟的128 分频
(3)LSI,内部低速时钟
使HSE 分频时钟或LSI 的话,在主电源VDD 掉电的情况下,这两个时钟来源都
会受到影响,因此没法保证RTC 正常工作。因此RTC 一般使用低速外部时钟LSE。
在主电源VDD 有效的情况下(待机),RTC 还可以配置闹钟事件使STM32 退出待机模式。
// 常见结构体
RTC_HandleTypeDef
RTC_AlarmTypeDef
RTC_TimeTypeDe
RTC_DateTypeDef
本例选择内部低速时钟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的定时时间
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初始化有冲突,所产生的中断不一样,这里是闹钟中断。
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;
}