如果不想看的可以直接使用git把我的代码下载出来,里面工程挺全的,后期会慢慢的补注释之类的
码云地址:stm32学习笔记: stm32学习笔记源码
如果不会使用git快速下载可以选择直接下载压缩包或者去看看git的使用
git的使用(下载及上传_git如何下载文件_八月风贼冷的博客-CSDN博客
目录
一、理论部分
1、什么是定时器
2、定时器类型以及功能
3、定时中断图
4、通用定时器框图
二、代码部分
1、开启时钟
2、配置为使用内部的时钟源
3、配置时基单元
4、配置中断输出控制:TIM_ITConfig
5、配置NVIC
6、配置完成后一定记得开启计数器,启动定时器。
7、中断函数
三、外部时钟(这里我会使用按键的高低电平来模拟时钟)
本章内容为基本的定时和使用外部时钟源定时,部分框图和代码分析,不涉及全部的时钟树和定时器框图分析,代码使用通用定时器2来写(TIM2)。
实验现象、让一个数每一秒加1,
总共分为6个部分:
1、RCC的内部时钟打开 2、内部时钟模式的配置 3、配置时基单元 4、配置中断输出控制
5、NVIC配置 6、运行控制(计数器使能)
本次实验只涉及到了定时中断与外部时钟源选择,所以没有涉及到下面一大部分的输入模式。
根据框图的部分、首先开启时钟(基本所以配置代码都需要开启时钟,不然后面的配置代码没有时钟就是无效的,时钟是一段代码的心脏)查询一下TIM2挂在在哪个时钟下(数据手册和参考手册都有图)。第一张的是第一张总线结构,第二章是数据手册12页的性能线框图。
注意!!!!:虽然TIM2挂在在APB1下但是他的时钟频率并不是36Mhz,具体原因看第三张图所示。
这里有说明,如果APB1的分频系数=1,这里会倍频率1,否则会乘以2,因为APB1是二分频,所以这里为36x2=72Mhz。
开启时钟:在rcc.c中找到函数填入参数即可。
void TimerInit()
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
}
选择内部时钟源TIM2为时钟、这个不配置也没关系,时钟初始化之后使用的本来就是内部时钟。
void TimerInit()
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
TIM_InternalClockConfig(TIM2);
}
配置图中的时基单元:由ARR(自动重装器)、PSC(预分频器)、CNT(计数器组成)。这一部分可以直接配置结构体完成,写一下配置逻辑。
PSC:一个预分频器对输入的时钟进行分频:如PSC写入71则是对输入的72Mhz进行71+1为72的分频,分频因子都会加1(计数器计数频率:CK_CNT = CK_PSC / (PSC + 1))
CNT:计数值:可以计数,配合自动充装器完成计数以及归零
ARR:自动重载器:计时器溢出频率:CK_CNT_OV = CK_CNT / (ARR + 1) 当计数值达到预定的装在值,则归0,
假设想要一个一次一秒的计时:则psc设置为7200 此时:72000000/7200=10000
arr设置为100000 10000/10000=1 即1hz 设定不唯一取的是我觉得方便算的值。
这里不用自己往寄存器写入数据,调用结构体配置好就会帮你写入,找Tim.h里面的结构体。初始化时基单元。TIM_TimeBaseInitTypeDef自己声明一个结构体将成员全部点出来。代码如下:
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
TIM_TimeBaseInitStruct.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_TimeBaseInitStruct.TIM_CounterMode=TIM_CounterMode_Up;
TIM_TimeBaseInitStruct.TIM_Period=9999;
TIM_TimeBaseInitStruct.TIM_Prescaler=7199;
TIM_TimeBaseInitStruct.TIM_RepetitionCounter=0;
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStruct);
参数:Clockdividion:这个东西并不是定时器的时钟分频,而是滤波器的分频,滤波器的时钟可以由内部的时钟产生,也能通过内部时钟分频产生,滤波原理就是通过采集n个点来观测这一段的频率是不是都一致,如果不一致则不输出或者直接输出低电平,f的频率越低,采样点点数越多,滤波的效果就好。本次不使用这个东西参数随便选一个。
TIM_CounterMode:为计数模式:这里有几种模式可以选择,基本使用向上计数模式,后面的折中模式基本使用在三相无刷电机、以后可能会写这个东西。
TIM_Prescaler:分频:输入为72M,对他进行7200分频让他只有10k。
TIM_Period:周期:也就是ARR的值嘛,记到多少让他归0,这里手动输入9999,因为公式会+1,所以我们自己输入的时候要-1;(因为现在时钟是10k嘛,代表1ms内可以处理10k个数据,所以这里直接给了他10k数据,就是一秒完成了)
TIM_RepetitionCounter:重复计数器(高级定时器才有写了也没用)就是这个东西,他可以让你几个周期才产生一次中断(很牛逼)假设之前能定时1分钟,这个16位的寄存器能让这个值扩大55636倍,省时省力直接延时几万倍。
需要配置的参数为:tim,TIM_IT,NewState
其中第二个参数可以有以下选择,1、更新中断源 2、输入比较1中断源 3差不多后面写吧、
代码如下
TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);
这个在前面的外部中断学习笔记已经写过了,首先配置分组,并且整个工程只能有一个分组,然后初始化NVIC代码如下,其中参数为通道、使能、抢占优先级和优先级具体讲解看这一章。https://blog.csdn.net/qq_51426845/article/details/130034250?spm=1001.2014.3001.5501
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_InitStruct.NVIC_IRQChannel=TIM2_IRQn;
NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=0;
NVIC_InitStruct.NVIC_IRQChannelSubPriority=0;
NVIC_Init(&NVIC_InitStruct);
TIM_Cmd(TIM2,ENABLE);
在main.c下定义一个全局变量Timer 所以要调用只能用extern声明一下变量,也可以在这个文件下面直接写个函数返回值。
//Timer.c
extern u8 Timer;
void TIM2_IRQHandler(void)
{
if(TIM_GetITStatus(TIM2,TIM_IT_Update)==SET)
{
Timer++;
TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
}
}
u8 Timer=0;
/**
* @brief 主函数
* @param 无
* @retval 无
*/
int main(void)
{
/*初始化USART 配置模式为 115200 8-N-1,中断接收*/
USART_Config();
delay_init();
TimerInit();
/* 发送一个字符串 */
Usart_SendString( DEBUG_USARTx,"这是一个串口中断接收回显实验\n");
printf("欢迎使用野火STM32开发板\n\n\n\n");
while(1)
{
printf("%d\n",Timer);
delay_ms(1000);
}
}
这个是因为在TIM_TimeBaseInit函数里面写了一个操作。他在代码的最后直接更新了一个事件,所以代码会默认标志位已经有值了,这里我们需要手动清除一下,直接加在中断之前就行。
看一下效果,这里就已经是从0开始计数了。
ok写完了内部时钟,开始写外部时钟。
这一章学习的就是内外时钟的选择了。其他代码不变,我们得将刚刚选择得内部时钟改为外部,外部时钟ETR。找一下tim里面得函数。
这里加了一个我的按键gpio得初始化,以及屏蔽掉了内部时钟,打开了ETR的模式2,然后因为是手动模拟时钟,所以按一下就是1hz,这里我们就不分频了,重载值也就设置个3吧= =方便实验嘛。试了一下TIM_ETRClockMode1Config不管是模式1还是模式2好像都是有用的。
void TimerInit(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
//TIM_InternalClockConfig(TIM2);
TIM_ETRClockMode2Config(TIM2,TIM_ExtTRGPSC_OFF,TIM_ExtTRGPolarity_NonInverted,0x00);
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_IN_FLOATING;
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_0;
GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStruct);
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
TIM_TimeBaseInitStruct.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_TimeBaseInitStruct.TIM_CounterMode=TIM_CounterMode_Up;
TIM_TimeBaseInitStruct.TIM_Period=5;
TIM_TimeBaseInitStruct.TIM_Prescaler=2;
TIM_TimeBaseInitStruct.TIM_RepetitionCounter=0;
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStruct);
TIM_ClearFlag(TIM2,TIM_IT_Update);
TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_InitStruct.NVIC_IRQChannel=TIM2_IRQn;
NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=0;
NVIC_InitStruct.NVIC_IRQChannelSubPriority=0;
NVIC_Init(&NVIC_InitStruct);
TIM_Cmd(TIM2,ENABLE);
}
实验是成功了= =,之后会优化一下代码的,让他不变动的时候不打印,这个实验拿OLED屏幕就能很好的打印,拿串口反而麻烦。这是主函数代码。和结果图。
while(1)
{
printf("num=%d\n",Timer);
printf("count=%d\n",TIM_GetCounter(TIM2));
delay_ms(500);
}
}