目录
目录
1、定时器简介(TIM)
简介:
定时器可以对输入的时钟进行计数,并在计数值达到设定值时触发中断
16位计数器、预分频器、自动重装寄存器的时基单元,在72MHz计数时钟下可以实现最大59.65s的定时
不仅具备基本的定时中断功能,而且还包含内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等多种功能
根据复杂度和应用场景分为了高级定时器、通用定时器、基本定时器三种类型
定时器类型和功能
类型 | 编号 | 总线 | 功能 |
高级定时器 | TIM1、TIM8 | APB2 | 拥有通用定时器全部功能,并额外具有重复计数器、死区生成、互补输出、刹车输入等功能 |
通用定时器 | TIM2、TIM3 TIM4、TIM5 | APB1 | 拥有基本定时器全部功能,并额外具有内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等功能 |
基本定时器 | TIM6、TIM7 | APB1 | 拥有定时中断、主模式触发DAC的功能 |
在stm32f103c8t6中只有TIM1、TIM2、TIM3、TIM4
三种定时器基本框图
1、高级定时器
重复次数计数器,可以控制每隔几个计数周期才发生一次中断,且可以给无刷电机输出PWM方波。
2、通用定时器
除了向上计数外,通用计数还有向下计数,中央对齐模式。
3、基本定时器
分频器可以改变数值(0-不分频-72Mhz,1-2分频-36Mhz以此类推)
2、定时器基本结构
3、预分频器时序图
计数器计数频率:CK_CNT = CK_PSC / (PSC + 1)
计数器时序
计数器溢出频率:CK_CNT_OV = CK_CNT / (ARR + 1)
= CK_PSC / (PSC + 1) / (ARR + 1)
RCC时钟树
4、初始化定时器的基本步骤
1.打开RCC时钟2.选择时基单元的时钟源(内部时钟源)3.配置时基单元(结构体)4.配置输出中断控制,允许更新中断输出到NVIC5.配置NVIC,打开定时器中断通道,设置优先级6.运行控制,使能计数器。
基本函数
void TIM_DeInit(TIM_TypeDef* TIMx);
void TIM_TimeBaseInit(TIM_TypeDef* TIMx, TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);
void TIM_TimeBaseStructInit(TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);
void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState);
void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState);
void TIM_InternalClockConfig(TIM_TypeDef* TIMx);
void TIM_ITRxExternalClockConfig(TIM_TypeDef* TIMx, uint16_t TIM_InputTriggerSource);
选择TIx捕获通道的时钟(一:选择定时器 二:选择TIx具体的某个引脚 三,四:输入的级性和滤波器)
void TIM_TIxExternalClockConfig(TIM_TypeDef* TIx, uint16_t TIM_TIxExternalCLKSource,
uint16_t TIM_ICPolarity, uint16_t ICFilter);
void TIM_ETRClockMode1Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity,uint16_t ExtTRGFilter);
void TIM_ETRClockMode2Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity, uint16_t ExtTRGFilter);
void TIM_ETRConfig(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity,uint16_t ExtTRGFilter);
void TIM_PrescalerConfig(TIM_TypeDef* TIMx, uint16_t Prescaler, uint16_t TIM_PSCReloadMode);
void TIM_CounterModeConfig(TIM_TypeDef* TIMx, uint16_t TIM_CounterMode);
void TIM_ARRPreloadConfig(TIM_TypeDef* TIMx, FunctionalState NewState);
void TIM_SetCounter(TIM_TypeDef* TIMx, uint16_t Counter);
void TIM_SetAutoreload(TIM_TypeDef* TIMx, uint16_t Autoreload);
uint16_t TIM_GetCounter(TIM_TypeDef* TIMx);
uint16_t TIM_GetPrescaler(TIM_TypeDef* TIMx);
5、定时中断和时钟源选择代码部分(定时器功能1)
定时器配置代码部分
#include "stm32f10x.h" // Device header
void Timer_Init(void)
{
//1.开启RCC时钟,定时器二在总线1上,所以要用APB1
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
//2.选择时基单元的时钟源(内部时钟源)
TIM_InternalClockConfig(TIM2);
//3.配置时基单元(结构体)
TIM_TimeBaseInitTypeDef TIMInitStructure;
TIMInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;//时钟分频
TIMInitStructure.TIM_CounterMode = TIM_CounterMode_Up;//向上计数模式
TIMInitStructure.TIM_Period = 10000 - 1;
TIMInitStructure.TIM_Prescaler = 7200 - 1;//预分频值
TIMInitStructure.TIM_RepetitionCounter = 0;//重装值
TIM_TimeBaseInit(TIM2,&TIMInitStructure);
//4.配置输出中断控制,允许更新中断输出到NVIC
TIM_ClearFlag(TIM2, TIM_FLAG_Update);//手动清楚更新标志位,不会出现进程序就进入中断的情况
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
//5.配置NVIC,打开定时器中断通道,设置优先级
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//优先级设置
NVIC_InitTypeDef NVICInitStructure;
NVICInitStructure.NVIC_IRQChannelPreemptionPriority = 2;//通道抢占优先级
NVICInitStructure.NVIC_IRQChannelSubPriority = 1;//优先级
NVICInitStructure.NVIC_IRQChannelCmd = ENABLE;//
NVICInitStructure.NVIC_IRQChannel = TIM2_IRQn;//定时器
NVIC_Init(&NVICInitStructure);
//6.运行控制,使能计数器
TIM_Cmd(TIM2,ENABLE);
}
/*
void TIM2_IRQHandler(void)
{
if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
{
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}
}
*/
根据代码,定时器(TIM2)的中断触发时间间隔将会根据以下参数计算得出:
1. 预分频值(`TIM_Prescaler`):7200 - 1
2. 周期值(`TIM_Period`):10000 - 1首先,计算实际应用于计数器的时钟频率:
时钟频率 = (输入时钟频率) / (预分频值 + 1)
= (输入时钟频率) / (7200)然后,计算每个计数周期所需的时间:
计数周期时间 = 1 / (实际应用于计数器的时钟频率)最后,计算中断触发时间间隔:
中断触发时间间隔 = 计数周期时间 * 周期值
= (1 / 实际应用于计数器的时钟频率) * (周期值)对于STM32F103ZE(c8t6也是一样的时钟频率),它的内部时钟(主时钟)为72 MHz。
根据之前的计算公式,使用预分频值7200和周期值10000-1,可以计算出中断触发时间间隔:
实际应用于计数器的时钟频率 = 72 MHz / (7200) = 10 kHz
计数周期时间 = 1 / 实际应用于计数器的时钟频率 = 1 / 10 kHz = 0.1 ms
中断触发时间间隔 = 计数周期时间 * 周期值 = 0.1 ms * (10000 - 1) ≈ 1 s
因此,根据给定的配置,定时器(TIM2)将在每秒触发一次中断。请注意,这里的计算结果可能会受到其他因素的影响,例如系统时钟的准确性和其他中断或操作的干扰。
主函数(定时器中断——正点原子精英板)
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "LED.h"
#include "Key.h"
#include "Buzzer.h"
#include "sys.h"
#include "lcd.h"
#include "usart.h"
#include "Timer.h"
int16_t Num;
unsigned int KeyNum;
int main(void)
{
Timer_Init();
u8 lcd_id[12]; //存放LCD ID字符串
delay_init(); //延时函数初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC中断分组2:2位抢占优先级,2位响应优先级
uart_init(115200); //串口初始化为115200
LED_Init(); //LED端口初始化
LCD_Init();
POINT_COLOR=RED;
sprintf((char*)lcd_id,"LCD ID:%04X",lcddev.id);//将LCD ID打印到lcd_id数组。
LCD_Clear(WHITE);
POINT_COLOR=BLACK;
while(1)
{
LCD_ShowString(30,70,200,16,16,lcd_id); //显示LCD ID
LCD_ShowNum(90,100,Num,5,16);
}
}
void TIM2_IRQHandler(void)
{
if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
{
Num++;
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}
}
6、输出比较OC(Output Compare)
输出比较可以通过比较CNT与CCR寄存器值的关系,来对输出电平进行置1、置0或翻转的操作,用于输出一定频率和占空比的PWM波形
每个高级定时器和通用定时器都拥有4个输出比较通道
高级定时器的前3个通道额外拥有死区生成和互补输出的功能
PWM简介
PWM(Pulse Width Modulation)脉冲宽度调制 在具有惯性的系统中,可以通过对一系列脉冲的宽度进行调制,来等效地获得所需要的模拟参量,常应用于电机控速等领域 PWM参数:
频率 = 1 / TS 占空比 = TON / TS 分辨率 = 占空比变化步距
占空比越大,所模拟的输出就越大。
输出比较通道(通用)
输出比较通道(高级)
7、 输出比较模式汇总
模式 | 描述 |
冻结 | CNT=CCR时,REF保持为原状态 |
匹配时置有效电平 | CNT=CCR时,REF置有效电平 |
匹配时置无效电平 | CNT=CCR时,REF置无效电平 |
匹配时电平翻转 | CNT=CCR时,REF电平翻转 |
强制为无效电平 | CNT与CCR无效,REF强制为无效电平 |
强制为有效电平 | CNT与CCR无效,REF强制为有效电平 |
PWM模式1 | 向上计数:CNT<CCR时,REF置有效电平,CNT≥CCR时,REF置无效电平 向下计数:CNT>CCR时,REF置无效电平,CNT≤CCR时,REF置有效电平 |
PWM模式2 | 向上计数:CNT<CCR时,REF置无效电平,CNT≥CCR时,REF置有效电平 向下计数:CNT>CCR时,REF置有效电平,CNT≤CCR时,REF置无效电平 |
#define TIM_OCMode_Timing ((uint16_t)0x0000)
#define TIM_OCMode_Active ((uint16_t)0x0010)
#define TIM_OCMode_Inactive ((uint16_t)0x0020)
#define TIM_OCMode_Toggle ((uint16_t)0x0030)
#define TIM_OCMode_PWM1 ((uint16_t)0x0060)
#define TIM_OCMode_PWM2 ((uint16_t)0x0070)
8、PWM基本结构
主要用PWM模式1和2,一般使用向上计数
参数计算
PWM频率: Freq = CK_PSC / (PSC + 1) / (ARR + 1)
PWM占空比: Duty = CCR / (ARR + 1)
PWM分辨率: Reso = 1 / (ARR + 1)
配置PWM
1.配置TIM外设和GPIO外设的RCC时钟
2.配置时基单元
3.配置输出比较单元(结构体)
4.配置GPIO(复用推挽)
5.运行控制。
#include "stm32f10x.h" // Device header
void PWM_Init(void)
{
//1.配置TIM外设和GPIO外设的RCC时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
//4.配置GPIO(复用推挽)
GPIO_InitTypeDef GPIOInitStructure;
GPIOInitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIOInitStructure.GPIO_Pin = GPIO_Pin_0;
GPIOInitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIOInitStructure);
//重映射
// RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
// GPIO_PinRemapConfig(GPIO_FullRemap_TIM2, ENABLE);
// GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);
//2.配置时基单元
TIM_InternalClockConfig(TIM2);//内部时钟配置
//3.配置输出比较单元(结构体)
TIM_TimeBaseInitTypeDef TIMInitStructure;
TIMInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIMInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIMInitStructure.TIM_Period = 100 - 1; //ARR
TIMInitStructure.TIM_Prescaler = 720 - 1; //PSC
TIMInitStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM2,&TIMInitStructure);
TIM_OCInitTypeDef TIM_OCInitStructure;
TIM_OCStructInit(&TIM_OCInitStructure);
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;//输出比较配置PWM1
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;//高电平
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 0; //CCR
TIM_OC1Init(TIM2, &TIM_OCInitStructure);
//5.运行控制。
TIM_Cmd(TIM2,ENABLE);
}
void PWM_SetCompare1(uint16_t Compare)//设置占空比
{
TIM_SetCompare1(TIM2, Compare);
}
其中重映射不用就是A0口,使用重映射就变成了A15,如下图。
输出比较函数简要
输出比较单元配置函数(选择定时器,输出比较结构体)
void TIM_OC1Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
void TIM_OC2Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
void TIM_OC3Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
void TIM_OC4Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
·给输出比较结构体赋默认值
void TIM_OCStructInit(TIM_OCInitTypeDef* TIM_OCInitStruct);
·更改CCR寄存器值的函数,更改占空比
void TIM_SetCompare1(TIM_TypeDef* TIMx, uint16_t Compare1);
void TIM_SetCompare2(TIM_TypeDef* TIMx, uint16_t Compare2);
void TIM_SetCompare3(TIM_TypeDef* TIMx, uint16_t Compare3);
void TIM_SetCompare4(TIM_TypeDef* TIMx, uint16_t Compare4);
9、输入捕获(IC(Input Capture))
输入捕获模式下,当通道输入引脚出现指定电平跳变时,当前CNT的值将被锁存到CCR中,可用于测量PWM波形的频率、占空比、脉冲间隔、电平持续时间等参数
每个高级定时器和通用定时器都拥有4个输入捕获通道
可配置为PWMI模式,同时测量频率和占空比
可配合主从触发模式,实现硬件全自动测量
1.RCC开启时钟(GPIO和TIM)
2.配置GPIO,把GPIO配置为输入模式,上拉或者浮空
3.配置时基单元,让cnt计数器在内部时钟的驱动下自增运行
4.配置输入捕获单元(结构体)
5.选择从模式的触发源
6.选择触发之后执行的操作
7.开启定时器
#include "stm32f10x.h" // Device header
void IC_Init(void)
{
//1.RCC开启时钟(GPIO和TIM)
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
//2.配置GPIO,把GPIO配置为输入模式,上拉或者浮空
GPIO_InitTypeDef GPIOInitStructure;
GPIOInitStructure.GPIO_Mode = GPIO_Mode_IPU;//下拉输入
GPIOInitStructure.GPIO_Pin = GPIO_Pin_6;
GPIOInitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIOInitStructure);
//3.配置时基单元,让cnt计数器在内部时钟的驱动下自增运行
TIM_InternalClockConfig(TIM3);
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStructure.TIM_Period = 65536 - 1; //ARR
TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1; //PSC
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure);
//4.配置输入捕获单元(结构体)
TIM_ICInitTypeDef TIM_ICInitStruct;
TIM_ICInitStruct.TIM_Channel = TIM_Channel_1;
TIM_ICInitStruct.TIM_ICFilter = 0xF;
TIM_ICInitStruct.TIM_ICPolarity = TIM_ICPolarity_Rising;
TIM_ICInitStruct.TIM_ICPrescaler = TIM_ICPSC_DIV1;
TIM_ICInitStruct.TIM_ICSelection = TIM_ICSelection_DirectTI;
TIM_ICInit(TIM3,&TIM_ICInitStruct);
//5.选择从模式的触发源
TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1);
//6.选择触发之后执行的操作
TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Reset);
//7.开启定时器
TIM_Cmd(TIM3, ENABLE);
}
uint32_t IC_GetFreq(void)
{
return 1000000 / (TIM_GetCapture1(TIM3) + 1);
}
上述配置完成后我们就可以使用它进行测试了
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "LED.h"
#include "Key.h"
#include "Buzzer.h"
#include "sys.h"
#include "lcd.h"
#include "usart.h"
#include "PWM.h"
#include "IC.h"
uint8_t i;
unsigned int KeyNum;
int main(void)
{
PWM_Init();
IC_Init();
u8 lcd_id[12]; //存放LCD ID字符串
delay_init(); //延时函数初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC中断分组2:2位抢占优先级,2位响应优先级
uart_init(115200); //串口初始化为115200
//LED_Init(); //LED端口初始化
LCD_Init();
POINT_COLOR=RED;
sprintf((char*)lcd_id,"LCD ID:%04X",lcddev.id);//将LCD ID打印到lcd_id数组。
LCD_Clear(WHITE);
POINT_COLOR=BLACK;
PWM_SetPrescaler(720 - 1); //Freq = 72M / (PSC + 1) / 100
PWM_SetCompare1(50); //Duty = CCR / 100
LCD_ShowString(30,90,200,16,16,"Freq:");
while(1)
{
LCD_ShowString(30,70,200,16,16,lcd_id); //显示LCD ID
LCD_ShowNum(60,90,IC_GetFreq(),15,16);
}
}
10、编码器测速
简单来说就是在编码器的使用上加入定时器,让定时器给我们做一个简单的加加操作,因为TIM定时器可以在产生脉冲的的时候崔cnt自增,所以可以把编码器的每一次操作记录下来并且每次自增和自减。
#include "stm32f10x.h" // Device header
void Encoder_Init(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStructure.TIM_Period = 65536 - 1; //ARR
TIM_TimeBaseInitStructure.TIM_Prescaler = 1 - 1; //PSC
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure);
TIM_ICInitTypeDef TIM_ICInitStructure;
TIM_ICStructInit(&TIM_ICInitStructure);
TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;
TIM_ICInitStructure.TIM_ICFilter = 0xF;
TIM_ICInit(TIM3, &TIM_ICInitStructure);
TIM_ICInitStructure.TIM_Channel = TIM_Channel_2;
TIM_ICInitStructure.TIM_ICFilter = 0xF;
TIM_ICInit(TIM3, &TIM_ICInitStructure);
TIM_EncoderInterfaceConfig(TIM3, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);
TIM_Cmd(TIM3, ENABLE);
}
int16_t Encoder_Get(void)
{
int16_t Temp;
Temp = TIM_GetCounter(TIM3);
TIM_SetCounter(TIM3, 0);
return Temp;
}
然后再定时器函数中让编码器++就行了。
void TIM2_IRQHandler(void)
{
if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
{
Speed = Encoder_Get();
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}
}