STM32的RCC(Reset and Clock Control)时钟控制
stm32f103c8的时钟是72MHz, stm32f401ccu6的时钟是80M, 开发板板载两个晶振, 一个高速一个低速
时钟源
STM32时钟的走向, 从时钟源一步步分配给系统和外设, stm32系统一共有四个时钟源, 依次是
- 高速内部时钟(HSI): 以内部RC振荡器产生, 频率为8Mhz,但相较于外部时钟不稳定.
- 高速外部时钟(HSE): 以外部晶振作为时钟源, 晶振频率可取范围为4 - 16Mhz,一般采用8Mhz的晶振.
- 低速内部时钟(LSI): 从内部RC振荡器产生, 频率为40khz, 也是主要提供给实时时钟模块.
- 低速外部时钟(LSE): 以外部晶振作为时钟源, 主要是提供给实时时钟模块, 所以一般选用32.768khz,该频率下定时器方便取整.
时钟分倍频路径
以最常用的高速外部时钟为例, 从时钟源往下游的每一个环节:
- 最左端的OSC_OUT和OSC_IN 这两个引脚分别连接到外部晶振的两端
这个晶振一般为8Mhz - 第一个分频器PLLXTPRE
在这个分频器中, 可以选择设置二分频, 或者不分频. 这里选择不分频 - 开关PLLSRC
这个开关可以选择HSE或者HSI作为其时钟输出, 这里选择HSE - 锁相环/倍频器 PLL
可以设定2到16的倍频因子(PLLMUL), 经过PLL的时钟称为PLLCLK. 这里设置倍频因子为9, 产生的PLLCLK为72Mhz- USB预分频器, PLLCLK在输入到SW前流向这里, 这个PLLCLK也作为USB的时钟
- 开关SW
通过这个开关, 可以切换SYSCLK的时钟源, 有HSI,PLLCLK,HSE三个选择. 经过这个开关之后就是STM32的系统时钟(SYSCLK)了. 选择PLLCLK时钟则SYSCLK就为72Mhz - AHB预分频器, SYSCLK经过AHB预分频后输入到其他外设. 本例中AHB不分频, 直接输入到HCLK, FCLK或者SDIOCLK等时钟
- AHB总线、内核、内存和DMA使用的HCLK时钟
- 8分频后送给Cortex系统定时器时钟,即SysTick
- 自由运行时钟FCLK
- APB1分频器, PCLK1,最大频率36MHz, 供APB1外设使用. 另一路送给定时器Timer, 1倍频或2倍频
- APB2分频器, PCLK2,最大频率72MHz, 供APB2外设使用. GPIO外设是挂载在APB2总线上的
- ADC分频器。ADC分频器经过2、4、6、8分频后送给ADC1/2/3使用,ADC最大频率为14M
- 二分频, SDIO使用
与代码相关的时钟
- SYSCLK: 系统时钟, 是STM32大部分器件的时钟来源, 主要由AHB预分频器分配到各个部件
- HCLK: 由AHB预分频器直接输出得到, 它是高速总线AHB的时钟信号, 提供给存储器, DMA及Cortex内核, 是Cortex内核运行的时钟, CPU主频就是这个信号
- FCLK: 也是由AHB输出得到, 是内核的“自由运行时钟”. “自由”表现在它不来自时钟HCLK. 因此在HCLK停止时FCLK也可以继续运行. 也就是说, 即使CPU休眠了, 也能够采样到外部中断和跟踪休眠事件. 低功耗模式下使用
- PCLK1: 外设时钟, 由APB1分频得到, 最大可为36Mhz, 提供给APB1总线上的外设使用
- PCLK2: 外设时钟, 由APB2预分频输出得到, 最大为72Mhz, 提供给APB2总线上的外设
时钟源使能和配置
在配置好时钟系统之后, 如果要使用某些外设, 例如GPIO, ADC, 还要使能这些外设时钟. 如果在使用外设之前没有使能外设时钟, 这个外设不能正常运行.
STM32时钟系统的配置代码分为两部分, 一部分在 CMSIS 目录下的 system_stm32f10x.c 中的SystemInit()函数, 用于初始化. 另一部分在 FWLib 目录下的 stm32f10x_rcc.c 中, 用于操作时钟系统. 对于系统时钟来说, 默认情况下是在SystemInit函数的SetSysClock函数中判断的, SetSysClock 函数的定义如下
static void SetSysClock(void)
{
#ifdef SYSCLK_FREQ_HSE
SetSysClockToHSE();
#elif defined SYSCLK_FREQ_24MHz
SetSysClockTo24();
#elif defined SYSCLK_FREQ_36MHz
SetSysClockTo36();
#elif defined SYSCLK_FREQ_48MHz
SetSysClockTo48();
#elif defined SYSCLK_FREQ_56MHz
SetSysClockTo56();
#elif defined SYSCLK_FREQ_72MHz
SetSysClockTo72();
#endif
}
这段代码设置系统时钟的频率为72MHz,其宏定义为:
#define SYSCLK_FREQ_72MHz 72000000
如果想设置系统时钟频率为36MHz,只需注释掉上面的宏定义,并加入如下定义即可:
#define SYSCLK_FREQ_36MHz 36000000
设置完系统时钟后可以通过变量 SystemCoreClock 获得系统时钟的值,在 system_stm32f10x.c 中该变量的定义为:
#ifdef SYSCLK_FREQ_HSE
uint32_t SystemCoreClock = SYSCLK_FREQ_HSE;
#elif defined SYSCLK_FREQ_24MHz
uint32_t SystemCoreClock = SYSCLK_FREQ_24MHz;
#elif defined SYSCLK_FREQ_36MHz
uint32_t SystemCoreClock = SYSCLK_FREQ_36MHz;
#elif defined SYSCLK_FREQ_48MHz
uint32_t SystemCoreClock = SYSCLK_FREQ_48MHz;
#elif defined SYSCLK_FREQ_56MHz
uint32_t SystemCoreClock = SYSCLK_FREQ_56MHz;
#elif defined SYSCLK_FREQ_72MHz
uint32_t SystemCoreClock = SYSCLK_FREQ_72MHz;
#else /*!< HSI Selected as System Clock source */
uint32_t SystemCoreClock = HSI_VALUE;
#endif
当系统时钟频率设置为72MHz大小时:
- PLL时钟:72MHz
- SYSCLK时钟:72MHz
- AHB总线时钟(使用SYSCLK为时钟源):72MHz
- APB1总线时钟:36MHz
- APB2总线时钟:72MHz
时钟源使能中AHB, APB1, APB2的区别
这三个对应了各自总线上的外设时钟的开启和关闭, 在STM32F10x中
AHB总线 RCC_AHBPeriphClockCmd
void RCC_AHBPeriphClockCmd ( uint32_t RCC_AHBPeriph, FunctionalState NewState)
RCC_AHBPeriph 可用的参数(可以是组合)
- STM32_Connectivity_line_devices
RCC_AHBPeriph_DMA1
RCC_AHBPeriph_DMA2
RCC_AHBPeriph_SRAM
RCC_AHBPeriph_FLITF
RCC_AHBPeriph_CRC
RCC_AHBPeriph_OTG_FS
RCC_AHBPeriph_ETH_MAC
RCC_AHBPeriph_ETH_MAC_Tx
RCC_AHBPeriph_ETH_MAC_Rx
- other_STM32_devices
RCC_AHBPeriph_DMA1
RCC_AHBPeriph_DMA2
RCC_AHBPeriph_SRAM
RCC_AHBPeriph_FLITF
RCC_AHBPeriph_CRC
RCC_AHBPeriph_FSMC
RCC_AHBPeriph_SDIO
Note:
SRAM and FLITF clock can be disabled only during sleep mode.
APB1总线 RCC_APB1PeriphClockCmd
void RCC_APB1PeriphClockCmd ( uint32_t RCC_APB1Periph, FunctionalState NewState)
可用参数(可以是组合)
RCC_APB1Periph_TIM2,
RCC_APB1Periph_TIM3,
RCC_APB1Periph_TIM4,
RCC_APB1Periph_TIM5,
RCC_APB1Periph_TIM6,
RCC_APB1Periph_TIM7,
RCC_APB1Periph_TIM12,
RCC_APB1Periph_TIM13,
RCC_APB1Periph_TIM14
RCC_APB1Periph_SPI2,
RCC_APB1Periph_SPI3,
RCC_APB1Periph_USART2,
RCC_APB1Periph_USART3,
RCC_APB1Periph_USART4,
RCC_APB1Periph_USART5,
RCC_APB1Periph_I2C1,
RCC_APB1Periph_I2C2,
RCC_APB1Periph_USB,
RCC_APB1Periph_CAN1,
RCC_APB1Periph_BKP,
RCC_APB1Periph_PWR,
RCC_APB1Periph_DAC,
RCC_APB1Periph_CEC,
RCC_APB1Periph_WWDG,
- USART: 除了1都是
- I2C: 都是
- SPI: 除了1都是
APB2总线 RCC_APB2PeriphClockCmd
void RCC_APB2PeriphClockCmd ( uint32_t RCC_APB2Periph, FunctionalState NewState)
可用参数(可以是组合)
RCC_APB2Periph_AFIO,
RCC_APB2Periph_GPIOA,
RCC_APB2Periph_GPIOB,
RCC_APB2Periph_GPIOC,
RCC_APB2Periph_GPIOD,
RCC_APB2Periph_GPIOE,
RCC_APB2Periph_GPIOF,
RCC_APB2Periph_GPIOG,
RCC_APB2Periph_ADC1,
RCC_APB2Periph_ADC2,
RCC_APB2Periph_ADC3,
RCC_APB2Periph_SPI1,
RCC_APB2Periph_USART1,
RCC_APB2Periph_TIM1,
RCC_APB2Periph_TIM8,
RCC_APB2Periph_TIM9,
RCC_APB2Periph_TIM10,
RCC_APB2Periph_TIM11
RCC_APB2Periph_TIM15,
RCC_APB2Periph_TIM16,
RCC_APB2Periph_TIM17,
- 所有的GPIO都是
- 所有的ADC
系统时钟 SysTick系统定时器
STK(SysTick Timer)系统定时器
STK(SysTick Timer)系统定时器是CM3内核的外设, 内嵌在 NVIC. 系统定时器 是一个24bit的向下递减的计数器, 计数器每计数一次的时间为 1/SYSCLK, 一般我们设置系统时钟 SYSCLK 等于 72M. 当重装载数值寄存器的值递减到 0 的时候, 系统定时器就产生一次中断, 以此循环往复. 因为 SysTick 是属于 CM3 内核的外设, 所以所有基于 CM3 内核的单片机都具有这个 系统定时器, 使得软件在 CM3 单片机中可以很容易的移植. 系统定时器一般用于操作系统, 用于产生时基, 维持操作系统的心跳.
SysTick Register 系统定时寄存器
SysTick—系统定时器有4个寄存器, 在使用 SysTick 产生定时的时候, 只需要配置前三个寄存器, 最后一个校准寄存器不需要使用
寄存器名称 寄存器描述
CTRL SysTick 控制及状态寄存器
LOAD SysTick 重装载数值寄存器
VAL SysTick 当前数值寄存器
CALIB SysTick 校准数值寄存器
- STK_CSR控制寄存器, 寄存器低4位含义
- 第0位:ENABLE,Systick 使能位 (0:关闭Systick功能;1:开启Systick功能)
- 第1位:TICKINT,Systick 中断使能位(0:关闭Systick中断;1:开启Systick中断)
- 第2位:CLKSOURCE,Systick时钟源选择 (0:使用HCLK/8 作为Systick时钟;1:使用HCLK作为Systick时钟)
- 第3位:COUNTFLAG,Systick计数比较标志,如果在上次读取本寄存器后,SysTick 已经数到了0, 则该位为1, 如果读取该位, 该位将自动清零
- STK_LOAD 重载寄存器
- Systick是一个递减的定时器, 当定时器递减至0时, 重载寄存器中的值就会被重装载, 继续开始递减. STK_LOAD寄存器是个24位的寄存器, 最大计数0xFFFFFF
- STK_VAL当前值寄存器
也是个24位的寄存器, 读取时返回当前倒计数的值, 写它则使之清零, 同时还会清除在SysTick 控制及状态寄存器中的COUNTFLAG 标志
用SysTick产生1s的时间基准, 控制LED每隔1s闪一次
流程说明
- 调用内建函数 SysTick_Config(SystemCoreClock / 100000), 就是10us产生一次SysTick中断,
- 然后系统会去调用内建的中断处理函数 SysTick_Handler(void), 这个函数里对 TimingDelay--,
- 在 Dealy_us() 里用while循环阻塞进程, 直到 TimingDelay 减为0才跳出阻塞
所以将 TimingDelay 变量设为多少, 就会产生多少毫秒的延迟. 在调用 Delay_us() 函数时, 传入的数值就是要延迟的10us数
- 设置重载寄存器的值
uint32_t TimingDelay = 0;
/**
* @brief 启动系统滴答定时器 SysTick
* @param 无
* @retval 无
*/
void SysTick_Init(void)
{
/* SystemFrequency / 1000 1ms 中断一次
* SystemFrequency / 100000 10us 中断一次
* SystemFrequency / 1000000 1us 中断一次
*/
if (SysTick_Config(SystemCoreClock / 100000)) {
/* Capture error */
while (1);
}
}
/**
* @brief us 延时程序,10us 为一个单位
* @param
* @arg nTime: Delay_us( 1 ) 则实现的延时为 1 * 10us = 10us
* @retval 无
*/
void Delay_us(__IO uint32_t nTime)
{
TimingDelay = nTime;
while (TimingDelay != 0);
}
/**
* @brief 获取节拍程序
* @param 无
* @retval 无
* @attention 在 SysTick 中断函数 SysTick_Handler()调用
*/
void TimingDelay_Decrement(void)
{
if (TimingDelay != 0x00) {
TimingDelay--;
}
}
- 实现中断服务函数 SysTick_Handler
/**
* @brief This function handles SysTick Handler.
* @param None
* @retval None
*/
void SysTick_Handler(void)
{
TimingDelay_Decrement();
}
- 在main函数中实现LED延时亮灭
/* LED 端口初始化 */
LED_GPIO_Config();
/* 配置 SysTick 为 10us 中断一次,时间到后触发定时中断,
*进入 stm32fxx_it.c 文件的 SysTick_Handler 处理,通过数中断次数计时
*/
SysTick_Init();
while (1) {
LED1_ON;
Delay_us(100000); // 100000 * 10us = 1000ms
LED1_OFF;
LED2_ON;
Delay_us(100000); // 100000 * 10us = 1000ms
LED2_OFF;
LED3_ON;
Delay_us(100000); // 100000 * 10us = 1000ms
LED3_OFF;
}
外设时钟 TIM1 - TIM8
STM32中一共有11个定时器
- 2个高级控制定时器: TIM1, TIM8
- 能够产生3对PWM互补输出的高级定时器, 常用于三相电机的驱动, 时钟由APB2的输出产生.
- 4个普通定时器: TIM2 - TIM5
- 16位自动重装载计数器
- 向上计数模式: 从0开始计数, 计到 LOAD寄存器 TIMx_ARR 中的数值时, 清0, 依次循环
- 2个基本定时器: TIM6, TIM7
- 时钟由APB1输出产生
- 2个看门狗定时器
- 1个系统嘀嗒定时器
使用方法(TIM2时钟)
第一步:配置系统时钟
除此之外,还需将GPIO和TIM2外设时钟打开。
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
TIM2是挂在APB1上的, 调用RCC_APB1PeriphClockCmd函数, 而不是RCC_APB2PeriphClockCmd
第二步:配置中断
void NVIC_Configuration(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQChannel;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 4;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
第三步:配置GPIO的模式, 输入模式还是输出模式
void GPIO_Configuration(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOC, &GPIO_InitStructure);
}
第四步:定时器配置
void TIM2_Configuration(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
//重新将Timer设置为缺省值
TIM_DeInit(TIM2);
//采用内部时钟给TIM2提供时钟源
TIM_InternalClockConfig(TIM2);
//预分频系数为36000-1,这样计数器时钟为72MHz/36000 = 2kHz
TIM_TimeBaseStructure.TIM_Prescaler = 36000 - 1;
//设置周期数,每计2000个数就产生一次中断
TIM_TimeBaseStructure.TIM_Period = 2000;
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
TIM_ClearFlag(TIM2, TIM_FLAG_Update);
//禁止ARR预装载缓冲器
TIM_ARRPreloadConfig(TIM2, DISABLE); //预装载寄存器的内容被立即传送到影子寄存器
//开启TIM2的中断
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
}
该函数完成两个功能
- 设定预分频系数TIM_Prescaler = 36000 - 1
- 设定自动重装载值TIM_Period = 2000
注意:上述只是配置好了TIM2,但还没有开启TIM2。
下面是完整代码
#include “stm32f10x_lib.h”
void RCC_Configuration(void);
void NVIC_Configuration(void);
void GPIO_Configuration(void);
void TIM2_Configuration(void);
void Delay(vu32 nCount);
int main(void)
{
#ifdef DEBUG
debug();
#endif
RCC_Configuration();
NVIC_Configuration();
GPIO_Configuration();
TIM2_Configuration();
TIM_Cmd(TIM2, ENABLE); //开启定时器2
while (1)
{
}
}
void RCC_Configuration(void)
{
ErrorStatus HSEStartUpStatus;
RCC_DeInit();
RCC_HSEConfig(RCC_HSE_ON);
HSEStartUpStatus = RCC_WaitForHSEStartUp()
if (HSEStartUpStatus == SUCCESS)
{
FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable);
FLASH_SetLatency(FLASH_Latency_2);
RCC_HCLKConfig(RCC_SYSCLK_Div1);
RCC_PCLK2Config(RCC_HCLK_Div1);
RCC_PCLK1Config(RCC_HCLK_Div2);
RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9);
RCC_PLLCmd(ENABLE);
while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET) {}
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
while(RCC_GetSYSCLKSource() != 0x08) {}
}
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
}
void NVIC_Configuration(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
#ifdef VECT_TAB_RAM
NVIC_SetVectorTable(NVIC_VectTab_RAM, 0x0);
#else
NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0x0);
#endif
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQChannel;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 4;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
void GPIO_Configuration(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOC, &GPIO_InitStructure);
}
void TIM2_Configuration(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
//重新将Timer设置为缺省值
TIM_DeInit(TIM2);
//采用内部时钟给TIM2提供时钟源
TIM_InternalClockConfig(TIM2);
//预分频系数为36000-1,这样计数器时钟为72MHz/36000 = 2kHz
TIM_TimeBaseStructure.TIM_Prescaler = 36000 - 1;
TIM_TimeBaseStructure.TIM_Period = 2000;
//设置时钟分割
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
TIM_ClearFlag(TIM2, TIM_FLAG_Update);
//禁止ARR预装载缓冲器
TIM_ARRPreloadConfig(TIM2, DISABLE); //预装载寄存器的内容被立即传送到影子寄存器
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
}
使用方法 TIM2延时
这段放在timer.c里, 将接口放到头文件, 就可以在main中调用delay方法延时了.
void TIM2_Init(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
TIM_DeInit(TIM2);
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_TimeBaseStructure.TIM_Prescaler = (SystemCoreClock / 2000000) - 1; // 降频到2MHz
TIM_TimeBaseStructure.TIM_Period = 20 - 1; // (1/2M)*20 = 1/100K = 10 us
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseStructure);
TIM_ClearFlag(TIM2, TIM_FLAG_Update);
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
//TIM_Cmd(TIM2, ENABLE); // 开启TIM2
printf("## TIM2 Initialized ##
");
}
void TIM2_IRQHandler(void)
{
if(TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) {
if(TIM2_TimingDelay != 0x00) {
TIM2_TimingDelay--;
}
}
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}
void TIM2_Delay_10us(__IO uint32_t count)
{
TIM_Cmd(TIM2, ENABLE);
TIM2_TimingDelay = count;
while(TIM2_TimingDelay != 0);
TIM_Cmd(TIM2, DISABLE);
}
void TIM2_Delay_ms(__IO uint32_t count)
{
TIM_Cmd(TIM2, ENABLE);
TIM2_TimingDelay = count * 100;
while(TIM2_TimingDelay != 0);
TIM_Cmd(TIM2, DISABLE);
}