STM32-RCC与时钟树
前面我们认识到了GPIO可以进行输入和输出,那么它为什么能够输入和输出呢?接下来我们就来介绍它到底怎么工作的。
时钟
不管是其他外设还是CPU它们的工作都是靠时钟来进行“催促”完成的。CPU不会主动去Flash中读取指令。在单片机中,几乎所有外设也都不能主动工作,都需要时钟来驱动它工作。
这是一个简化的图,假设所有设备都在收到高电平时才会工作一次。这个信号我们叫做时钟信号,发出信号的源头,我们叫做时钟源。
在工作时,我们难道要让所有的设备都要按照时钟信号工作吗,比如我没有用到串口,难道串口也要工作吗?当然不是,所以设计师就加了一个开关,来控制是否使用。
时钟频率

这个是时钟信号的形式,不同设备触发工作的时机不同,可能有的在上升沿触发,有的在下降沿触发。对于处理速度,我们使用时钟频率来进行衡量。
倍频与分频
比如时钟信号输出的频率为8MHz,可以通过倍频,改为16MHz,也可以通过分频,改为4MHz。


这里分别为倍频和分频。
这样我们就可以给不同外设,输出不同的频率了。

这里是我们的一个外部时钟器。这个叫做无源谐振器,还有一部分内部电路,通过配合输出稳定的频率。还有一种叫做有源振动器,是由MCU内部通过电容来实现。
时钟树

这个图就是时钟树,首先我们要认识到有四个信号源,分别为,HSI,HSE,LSI,LSE。I为内部,E为外部,H为高频率,L为低频率。
这是外部接的两个外部时钟源。然后还要有分频和倍频功能。
同时,我们还看到了图中的复用器,用来选择使用哪个时钟源。同时最右边的类似与门的东西,称为使能开关,我们前面说,不会全部打开,就是靠这个来进行配置的。
时钟树一共有如下三部分
二三部分,我们先不讲解,主要讲解第一部分。
简化后就是上面这样。首先树根部分选择出时钟源,然后树干经过选择器,和倍频器,或直接输出,选择出放入预分频器,进行分频到外设。这里给大家强调一点,最后外设接的总线的时钟频率一般≥外设设置的频率,相当于提供给外设较大的能力,但是外设用多少看外设自身配置。
这里介绍一下PCLK1就是36MHz,就是APB1分出去的频率,其余类似。
看上面代码中有一个CSS是干嘛的呢?它的作用是在外部时钟源出现故障后,可以转换时钟源为内部时钟源。
这个图是自己的理解,可能不够权威,但是可以帮助大家理解时钟源和外设之间的关系,时钟信号就是通过这些总线来和外设进行连接,提供给外设一个活力,让其可以正常工作。外设的开关,就是我说的与门,我们配置一个,然后剩余一个由时钟信号来进行控制从而促使其工作。
CPU操作片上外设的过程
通信的实质是CPU内部寄存器和片上外设寄存器之间的数据拷贝
当时钟给写使能,且确定要写后,把数据转移到寄存器当中。
RCC
RCC就是复位与时钟控制器,我们这里先只考虑时钟相关的功能。我们的时钟树,就是要靠RCC来进行配置。
其中最重要的三个部分就是配置时钟源,配置倍频分频,配置外设开关。
时钟源配置
倍频分频配置

外设配置

案例与源码
代码
void CPU_Delay(volatile uint32_t delay)
{
while(delay--);
}
while (1)
{
HAL_GPIO_TogglePin(GPIOF,GPIO_PIN_8);
CPU_Delay(654545);
}
代码非常简单,我们主要需要配置不同的时钟频率来实现执行这段代码的时常,从而改变LED灯闪烁时常。
但是volatile是什么呢,我们发现在不加时,显示不出来我们想要的效果,这是因为内部做了优化,会在cpu中的寄存器中存放中间值,所以我们看不到效果,加上之后就保证了内存的可见性,不让其优化。
源码
__HAL_RCC_GPIOF_CLK_ENABLE();
这个代码我们前面讲解GPIO初始化的时候见过,不过我们当时没讲,现在我们来讲解一下,这个代码就是打开GPIOF针脚的意思。
#define __HAL_RCC_GPIOF_CLK_ENABLE() do { \
__IO uint32_t tmpreg; \
SET_BIT(RCC->APB2ENR, RCC_APB2ENR_IOPFEN);\
/* Delay after an RCC peripheral clock enabling */ \
tmpreg = READ_BIT(RCC->APB2ENR, RCC_APB2ENR_IOPFEN);\
UNUSED(tmpreg); \
} while(0U)
首先我们要知道和GPIO相同RCC还是一个结构体,里面存放着自己的寄存器。
#define SET_BIT(REG, BIT) ((REG) |= (BIT))
#define RCC_APB2ENR_IOPFEN_Pos (7U)
#define RCC_APB2ENR_IOPFEN_Msk (0x1UL << RCC_APB2ENR_IOPFEN_Pos) /*!< 0x00000080 */
#define RCC_APB2ENR_IOPFEN RCC_APB2ENR_IOPFEN_Msk
代码中,SET_BIT把APB2ENR的第七位设置为1

这个就代表把外部使能打开了,写入了寄存器,在下面的代码就相当于检查,我们不管。
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
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_MUL2;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
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;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV4;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK)
{
Error_Handler();
}
}
在开始初始化的时候,这个代码就是用来配置时钟源和倍频分频的。
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
我们挑出这一个来讲一下
上半部分主要配置时钟源
/* Set the new HSE configuration ---------------------------------------*/
__HAL_RCC_HSE_CONFIG(RCC_OscInitStruct->HSEState);
#define RCC_CR_HSEON_Pos (16U)
#define RCC_CR_HSEON_Msk (0x1UL << RCC_CR_HSEON_Pos) /*!< 0x00010000 */
#define RCC_CR_HSEON RCC_CR_HSEON_Msk
#define __HAL_RCC_HSE_CONFIG(__STATE__) \
do{ \
if ((__STATE__) == RCC_HSE_ON) \
{ \
SET_BIT(RCC->CR, RCC_CR_HSEON); \
} \
else if ((__STATE__) == RCC_HSE_OFF) \
{ \
CLEAR_BIT(RCC->CR, RCC_CR_HSEON); \
CLEAR_BIT(RCC->CR, RCC_CR_HSEBYP); \
} \
else if ((__STATE__) == RCC_HSE_BYPASS) \
{ \
SET_BIT(RCC->CR, RCC_CR_HSEBYP); \
SET_BIT(RCC->CR, RCC_CR_HSEON); \
} \
else \
{ \
CLEAR_BIT(RCC->CR, RCC_CR_HSEON); \
CLEAR_BIT(RCC->CR, RCC_CR_HSEBYP); \
} \
}while(0U)
相当于把第16位设置为1
接下来,下半部分,就是设置分频的
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV4;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
其实这里我们从语义上完全可以理解,后面同样把这些写入寄存器当中,就完成了对时钟树的配置。
/*-------------------------- PCLK1 Configuration ---------------------------*/
if (((RCC_ClkInitStruct->ClockType) & RCC_CLOCKTYPE_PCLK1) == RCC_CLOCKTYPE_PCLK1)
{
assert_param(IS_RCC_PCLK(RCC_ClkInitStruct->APB1CLKDivider));
MODIFY_REG(RCC->CFGR, RCC_CFGR_PPRE1, RCC_ClkInitStruct->APB1CLKDivider);
}
#define MODIFY_REG(REG, CLEARMASK, SETMASK) WRITE_REG((REG), (((READ_REG(REG)) & (~(CLEARMASK))) | (SETMASK)))
#define RCC_CFGR_PPRE1_DIV2 0x00000400U
#define RCC_CFGR_PPRE1_Pos (8U)
#define RCC_CFGR_PPRE1_Msk (0x7UL << RCC_CFGR_PPRE1_Pos) /*!< 0x00000700 */
#define RCC_CFGR_PPRE1 RCC_CFGR_PPRE1_Msk
同时在代码中,以PCLK1来举例,这里把100写入8-10位寄存器
更多推荐
所有评论(0)