今日理解一下STM32F103 C8T6的时钟与时钟系统、滴答计时器、定时器计时中断的配置,文章提供原理,代码,测试工程下载。
目录
时钟树与时钟系统:
滴答计时器:
定时器计时中断:
测试结果:
测试工程下载:
时钟树与时钟系统:
该系统介绍在 STM32F10x-中文参考手册 P56页开始
微控制器的时钟系统包括以下几个主要的时钟源:
1. HSE(High-Speed External): 外部高速晶振,可接入外部晶振作为系统时钟源。
2. HSI(High-Speed Internal): 内部高速振荡器,提供内部时钟源。
3. PLL(Phase Locked Loop): 锁相环,可以通过将外部时钟源或内部时钟源倍频得到更高的系统时钟频率。
时钟系统的配置和选择可以通过对系统寄存器 RCC(Reset and Clock Control)的相应位进行配置。根据配置的不同,时钟系统可分为以下几个模式:
1. HSI模式:使用HSI作为系统时钟源。
2. HSE模式:使用HSE作为系统时钟源。
3. PLL模式:通过PLL倍频方式产生高频时钟。
1、单片机内部的RC振荡器是8Mhz
2、通过单片机引脚(OSC_IN OSC_OUT)接外部的晶振,这里就对外部的晶振有要求了,要求外部晶振输入频率范围是4Mhz~32Mhz
3、是通过单片机引脚接外部的低速32.768Khz晶振,这个是单独的给内部的实时时钟模块(RTC)使用
4、是内部的低速RC振荡器40K,可以给RTC用,也可以给IWDG看门狗模块用
5、是时钟信号从MCO这个引脚上输出,这个输出可以作为测试,看看内部的时钟配置是否正确,也可以用作和其他硬件进行时钟同步用如上5种不同类型的时钟,供给不同的需求,内置的RC振荡器受到温度影响会大一些;
这几个外部时钟晶振接口,根据需求使用;也可选择不用,空着,或者接其他电路也可以;
时钟信号进来,还要操作一些相关寄存器 分频/倍频后,才成为"系统时钟SYSCLK"、HSI时钟、HSE时钟等等之类的,应用于单片机各个模块(比如定时器、ADC、USART、APB perpherials、I2C… )
滴答计时器:
#include "SysTick.h"
static u8 fac_us=0; //us延时倍乘数
static u16 fac_ms=0; //ms延时倍乘数
//初始化延迟函数
//SYSTICK的时钟固定为AHB时钟的1/8
//SYSCLK:系统时钟频率
void SysTick_Init(u8 SYSCLK)
{
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);
fac_us=SYSCLK/8;
fac_ms=(u16)fac_us*1000;
}
//延时nus
//nus为要延时的us数.
void delay_us(u32 nus)
{
u32 temp;
SysTick->LOAD=nus*fac_us; //时间加载
SysTick->VAL=0x00; //清空计数器
SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ; //开始倒数
do
{
temp=SysTick->CTRL;
}while((temp&0x01)&&!(temp&(1<<16))); //等待时间到达
SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk; //关闭计数器
SysTick->VAL =0X00; //清空计数器
}
//延时nms
//注意nms的范围
//SysTick->LOAD为24位寄存器,所以,最大延时为:
//nms<=0xffffff*8*1000/SYSCLK
//SYSCLK单位为Hz,nms单位为ms
//对72M条件下,nms<=1864
void delay_ms(u16 nms)
{
u32 temp;
SysTick->LOAD=(u32)nms*fac_ms; //时间加载(SysTick->LOAD为24bit)
SysTick->VAL =0x00; //清空计数器
SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ; //开始倒数
do
{
temp=SysTick->CTRL;
}while((temp&0x01)&&!(temp&(1<<16))); //等待时间到达
SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk; //关闭计数器
SysTick->VAL =0X00; //清空计数器
}
定时器计时中断:
查表可知,TIM2/3/4是适合作通用定时器的:
此处我以初始化定时器4为通用定时器举例:
计数器溢出频率:
CK CNT_OV= CK CNT / (ARR+1)
= CK PSC / (PSC +1) / (ARR +1)
这里的计数器溢出频率单位是赫兹,计数器溢出频率的倒数就是定时器触发的时间周期,一般我们计算用的是下面一个等于号的式子,这里的符号表示如下:
CK_PSC 一般为72Mhz(72 00 000)
ARR 自动重装 对应变量TIM_Period 范围0~65535
PSC 分频 对应变量 TIM_Prescaler 范围0~65535
1. 定时器时钟分频(TIMx_PSC)
是用来将系统时钟(通常为主频)分频为定时器的时钟频率。例如,如果系统时钟为72MHz,定时器时钟分频设置为72-1,则定时器时钟频率为1MHz。定时器时钟分频越大,定时器的时钟频率越低。
2. 预分频(TIMx_ARR)
是用来设置定时器溢出时间(自动重装载寄存器值)的参数。当定时器计数器达到预分频值时,定时器将溢出,并产生中断或其他相关事件。预分频的值决定了定时器溢出时间的长度。例如,如果预分频值为1000,定时器时钟频率为1MHz,则定时器溢出时间为1ms。
以下为初始化定时器 2 作定时中断,周期为1ms :
每次进入定时中断都会通过串口1 打印一次进入中断的总次数T:
#include "TIMER_init.h"
//初始化定时器2用作计时中断定时器:
void Timer2_Init(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
TIM_InternalClockConfig(TIM2);//选择哪个中断就写哪个
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; //修改分频,对实际情况影响不大,可以不修改,这里是不分频(可选1~72)
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //向上对齐模式,同时还有向下对齐,中央对齐模式
TIM_TimeBaseInitStructure.TIM_Period = 10000 - 1; //计数器周期。该参数决定了计数器计数溢出前的最大值。
TIM_TimeBaseInitStructure.TIM_Prescaler = 7200 - 1; //分频器预分频系数。该参数决定了计数器时钟频率的变化程度。
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; //高级计数器需要,不需要用到的直接给0就好
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);
TIM_ClearFlag(TIM2, TIM_FLAG_Update); //用于解决一复位时就先进一次中断的情况
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; //抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //响应优先级
NVIC_Init(&NVIC_InitStructure);
TIM_Cmd(TIM2, ENABLE);
}
定时中断服务函数:
#include "TIMER_init.h"
uint16_t T;
void TIM2_IRQHandler(void)
{
if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
{
printf("T=%d",T);
T++;
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);//清出中断寄存器标志位,用于退出中断
}
}
测试结果:
测试工程下载:
https://download.csdn.net/download/qq_64257614/88202750?spm=1001.2014.3001.5503