文章目录
- 一、定时器——Timer
- (一)概念
- (二)分类
- (三)功能
- (四)结构
- 1.模块一——时基单元
- 2.模块二——输出比较模块
- 二、实验内容
- (一)标准库点亮LED灯
- 1.实验说明
- 2.设计思路
- 3.代码部分
- 4.实验现象
- 5.keil仿真
- (二)标准库点亮呼吸灯
- 1.实验说明
- 2.设计思路
- 3.代码部分
- 4.实验现象
- 5.kei仿真
- 三、总结
一、定时器——Timer
(一)概念
定时器,见名知意,是一种专门负责定时功能的片上外设。它的作用是设定一个时间,时间到后通过中断等方式通知STM32执行某些程序。因此,定时器的本质也可认为是计数器。
注意:定时器并不能直接获得现实世界的实时时间,而是当做一个计数器或者计时器来用。
(二)分类
STM32F103C8T6芯片中的定时器有三种八个。(TIM1到TIM8)
1.基本定时器:TIM6、TIM7。
2.通用定时器:TIM2、TIM3、TIM4、TIM5。
3.高级定时器:TIM1、TIM8。
其中高级定时器挂在总线APB2上,基本定时器和通用定时器挂在总线APB1上。
(三)功能
1.基本定时器:就是时基单元的功能。
2.通用定时器:在基本定时器的基础上增加了输入捕获功能和输出比较功能。
3.高级定时器:在通用定时器的基础上增加了从模式控制器和高级输出控制功能。
我们实验主要采用通用定时器,但我们要尽量了解高级定时器。
(四)结构
STM32中单个高级定时器的内部结构如下图所示
主要可分为四大模块:时基单元、输入捕获、输出比较(包括绿色框中的高级功能)和从模式控制器。(在这里只介绍常用的时基单元与输出比较模块)
1.模块一——时基单元
(1)预分频器
RCC传来的脉冲频率很高,一般为72MHz或者为36MHz,而计数器是16位的,因此而一个计数器最多只能计数2的16次方个信号,即65536个脉冲,所以我们理应对该脉冲信号进行分频,分频之后的频率=原频率/(PSC设置的值+1)。
(2)计数器与自动重装寄存器
这二者可以看成是捆绑在一起的,自动重装寄存器相当于是计数器的管理员,当自动重装寄存器的值设为n时,要经过n+1个脉冲,计数器的值才会向外输出1个脉冲。即计数器向外输出的脉冲个数=传入寄存器内部的脉冲个数/(ARR设置的值+1)。
(3)重复寄存器
仅存在高级定时器中,还是相当于一个分频器。
小节:其实这三者都能起到分频作用,而中间的计数器与自动重装寄存器除此之外还能计数而已。它是时基单元与CCR寄存器建立联系的桥梁。
2.模块二——输出比较模块
二、实验内容
(一)标准库点亮LED灯
1.实验说明
使用STM32F103的 Tim2~Tim5其一定时器的某一个通道pin(与GPIOx管脚复用,见下图),连接一个LED,用定时器计数方式,控制LED以2s的频率周期性地亮-灭。
2.设计思路
(1)中断的条件
设置为UVE(update event),设置为定时器中的事件更新触发中断。说人话就是,定时器中的计数器溢出一次就出发中断。显然是等周期触发中断,我设置为1ms触发一次中断。每触发一次中断就计时一次,可以用全局变量currentTick来记录中断次数,也可以等同为程序运行的时间。
(2)延时函数的设计
先定义一个我们想要的延时变量命名为ms,其单位也是ms。
再定义一个变量uint64_t expireTime = App_Timer_GetTick() + ms,作为刚开始的定值变量。
接下里在while循环里不断让App_Timer_GetTick()返回的动态变量currentTick去追逐静态的高值变量expireTime,差值恰好为我们想要的延时ms。
3.代码部分
代码中的注释已经很明确了,这里不再逐句赘述。
(1)main.c文件
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "app_timer.h"
int main()
{
App_Timer_Init();
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_1;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_Init(GPIOA,&GPIO_InitStruct);
while(1)
{
GPIO_WriteBit(GPIOA,GPIO_Pin_1,Bit_SET);
App_Timer_Delay(2000);
GPIO_WriteBit(GPIOA,GPIO_Pin_1,Bit_RESET);
App_Timer_Delay(2000);
}
}
2.app_timer.c文件
#include "app_timer.h"
#include "stdint.h"
static volatile uint64_t currentTick = 0;
void App_Timer_Init()
{
//对定时器3的复位
RCC_APB1PeriphResetCmd(RCC_APB1Periph_TIM3,ENABLE);
RCC_APB1PeriphResetCmd(RCC_APB1Periph_TIM3,DISABLE);
//1.使能定时器3的时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
//2.使能ARR寄存器的预加载特性
TIM_ARRPreloadConfig(TIM3,ENABLE);
//3.初始化实际单元的参数
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
TIM_TimeBaseInitStruct.TIM_Prescaler = 71; //就是PSC里面的值,先72分频为,72MHz/72 = 1MHz;————>1us
TIM_TimeBaseInitStruct.TIM_Period = 999; //1us——————>1ms
TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStruct.TIM_RepetitionCounter = 0; //就是RCR里面的值,重复计数器写0,相当于不再分频。
//TIM_TimeBaseInitStruct.TIM_ClockDivision ; //死区时间、输入滤波,暂时不设置
//TIM_TimeBaseInitStruct.TIM_RepetitionCounter; //定时器3没有重复计数器,不需要设置
TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStruct);
//这些参数还在影子寄存器里,手动产生中断
TIM_GenerateEvent(TIM3,TIM_EventSource_Update);
//4.使能Update中断
TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE); //重点!!!!中断触发的条件————>更新事件.
//而更新事件通常指的是定时器的计数器达到自动重载寄存器(ARR)的值时,即完成了一个计数周期,此时硬件会自动置位一个中断标志位(TIM_FLAG_Update)
//显然就是:1ms触发一次中断!
//5.配置NVIC模块
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_InitStruct.NVIC_IRQChannel = TIM3_IRQn;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0 ;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0 ;
NVIC_Init(&NVIC_InitStruct);
//6.使能定时器
TIM_Cmd(TIM3,ENABLE);
}
void TIM3_IRQHandler()
{
if(TIM_GetFlagStatus(TIM3,TIM_FLAG_Update)== SET)
//这个函数的本意是得到具体定时器的具体部位的标志位
//在这里是得到定时器3的更新标志位:当定时器计数器达到自动重载寄存器(ARR)的值并发生更新事件时,硬件会自动置。在这里是1ms
{
TIM_ClearFlag(TIM3,TIM_FLAG_Update); //中断函数的第一步,清除中断标志位
currentTick++; //currentTick计的是毫秒值。每经过1ms,currentTick就加1.
}
}
uint64_t App_Timer_GetTick() //获取当前时刻,单位ms
{
return currentTick;
}
void App_Timer_Delay(uint32_t ms) //毫秒级延时
{
uint64_t expireTime = App_Timer_GetTick() + ms;
while(App_Timer_GetTick() < expireTime);
}
//这个中断函数设计的很巧妙,expireTime是定值,刚进入while循环时,这个大的定值一直与currentTick这个动态值的差值刚好是传入的ms,当currentTick经过ms时间后,循环结束。
//这个动态小值追逐定值大值的过程就是花费的我们设置的延时时间。
4.实验现象
20240531——001
5.keil仿真
延时恰好为2s,设计正确。
(二)标准库点亮呼吸灯
1.实验说明
采用定时器PWM模式,让 LED 以呼吸灯方式渐亮渐灭,周期为1~2秒,自己调整占空比变化到一个满意效果;使用Keil虚拟示波器,观察 PWM输出波形。
2.设计思路
选择定时器的输出比较模式中的PWM模式,输出一个正弦电压到引脚即可。
3.代码部分
(1)main.c文件
#include "stm32f10x.h"
#include "stm32f10x_pal.h"
int main(void)
{
PAL_Init();
// #1. 将PA8初始化为复用推挽模式
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_8;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_10MHz;
GPIO_Init(GPIOA, &GPIO_InitStruct);
// #2. 开启定时器1的时钟
RCC_APB2PeriphResetCmd(RCC_APB2Periph_TIM1, ENABLE);
RCC_APB2PeriphResetCmd(RCC_APB2Periph_TIM1, DISABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1,ENABLE);
// #3. 初始化时基单元
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
TIM_TimeBaseInitStruct.TIM_Prescaler = 71;
TIM_TimeBaseInitStruct.TIM_Period = 999;
TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStruct.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM1, &TIM_TimeBaseInitStruct);
// #4. 初始化输出比较通道1
TIM_OCInitTypeDef TIM_OCInitStruct;
TIM_OCInitStruct.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStruct.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OCInitStruct.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStruct.TIM_Pulse = 0;
TIM_OC1Init(TIM1, &TIM_OCInitStruct);
// #5. 闭合MOE
TIM_CtrlPWMOutputs(TIM1, ENABLE);
// #6. 手动产生UEV
TIM_GenerateEvent(TIM1, TIM_EventSource_Update);
// #7. 使能定时器
TIM_Cmd(TIM1, ENABLE);
while(1)
{
float t = PAL_GetTick() * 0.001;
float duty = 0.5 + 0.5 * sin(0.6 *3.14 * t);
uint16_t ccr = duty * 999;
TIM_SetCompare1(TIM1, ccr);
}
}
4.实验现象
20240531——002
5.kei仿真
我不太明白为啥是频率快的方波,也能达到周期为1~2S的呼吸灯的效果。
三、总结
通过本次实验,学习了定时器的有关概念及其结构。我用标准库通过定时器中断的方式点亮了LED灯。在此过程中我感觉定时器的很多用途都得与中断联系在一起。总的来说,标准库点灯会遇到很多麻烦,和异常的现象,比如keil波形是快频率的方波,实验现象却是呼吸灯的效果。