当使用STM32单片机时,定时器是一个非常重要的外设,可以用于实现多种计时和计数功能。在本文中,我们将介绍STM32单片机定时器的概述、使用方法和应用示例。
一、STM32单片机定时器的概述
STM32单片机的定时器模块包括基本定时器、通用定时器和高级定时器。基本定时器是一种简单的定时器,通用定时器是一种更加通用的定时器,高级定时器是一种功能更加强大的定时器。这些定时器可以用于测量时间、生成PWM信号、捕获外部脉冲等多种应用。
二、STM32单片机定时器的使用方法
在使用STM32单片机定时器之前,需要先进行相关的配置。以下是配置步骤:
-
选择定时器模块:根据实际需求选择基本定时器、通用定时器或高级定时器。
-
配置定时器时钟源:定时器需要使用时钟源进行计数,需要根据实际需求选择合适的时钟源。可以选择内部时钟、外部时钟或其他外部时钟源。
-
配置定时器的计数模式:定时器有多种计数模式,可以根据需要选择合适的计数模式。常见的计数模式有向上计数、向下计数和向上/向下计数。
-
配置定时器的计数周期:定时器的计数周期是定时器计数的最大值,需要根据实际需求进行配置。定时器计数到计数周期时会产生定时器中断,可以在中断处理函数中进行相应的操作。
-
配置定时器的工作模式:定时器有多种工作模式,可以根据需要选择合适的工作模式。常见的工作模式有定时器模式、计数器模式、PWM输出模式和输入捕获模式等。
-
启动定时器:配置完成后,需要启动定时器开始计数。可以通过设置定时器控制寄存器的使能位来启动定时器。
二、应用示例
以下是一个通过STM32单片机定时器实现LED闪烁的示例:
1.LED闪烁
/**
* 使用STM32F4的TIM2定时器实现GPIO的周期性翻转
*/
#include "stm32f10xx.h" // 包含STM32F10的头文件
void TIM2_IRQHandler(void) // TIM2中断处理函数
{
if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) // 如果是更新中断
{
GPIO_ToggleBits(GPIOD, GPIO_Pin_12); // 翻转GPIOD的Pin12引脚
TIM_ClearITPendingBit(TIM2, TIM_IT_Update); // 清除中断标志位
}
}
int main(void)
{
GPIO_InitTypeDef GPIO_InitStruct; // 定义GPIO初始化结构体
TIM_TimeBaseInitTypeDef TIM_InitStruct; // 定义TIM基本定时器初始化结构体
NVIC_InitTypeDef NVIC_InitStruct; // 定义NVIC初始化结构体
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE); // 使能GPIOD时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); // 使能TIM2时钟
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_12;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_Init(GPIOD, &GPIO_InitStruct); // 初始化GPIOD
TIM_InitStruct.TIM_Prescaler = 41999;
TIM_InitStruct.TIM_CounterMode = TIM_CounterMode_Up;
TIM_InitStruct.TIM_Period = 5000;
TIM_InitStruct.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInit(TIM2, &TIM_InitStruct); // 初始化TIM2定时器
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); // 使能更新中断
NVIC_InitStruct.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; // 使能中断
NVIC_Init(&NVIC_InitStruct); // 初始化NVIC
TIM_Cmd(TIM2, ENABLE); // 启动定时器
while (1) // 主循环
{
// do nothing
}
}
在本示例中,我们选择了通用定时器TIM2,并将其配置为向上计数模式。使用了内部时钟源,设置了计数周期为5000,预分频器为41999,从而设置了定时器的计数频率为2Khz。通过设置中断处理函数来实现LED闪烁效果,即每当定时器计数到5000时,就会产生定时器中断,中断处理函数中会翻转LED的状态。最后通过使能定时器和中断来启动定时器。
除了LED闪烁的应用场景外,STM32单片机定时器还可以用于其他应用场景,例如:
2.PWM信号生成
可以通过定时器的PWM输出模式生成PWM信号,用于控制电机、LED亮度等。
以下是一个通过STM32单片机定时器实现PWM输出的示例:
/**
* 使用STM32F10的TIM4通用定时器和PWM功能控制GPIOD的Pin12引脚产生PWM信号
*/
#include "stm32f10xx.h" // 包含STM32F10的头文件
int main(void)
{
GPIO_InitTypeDef GPIO_InitStruct; // 定义GPIO初始化结构体
TIM_TimeBaseInitTypeDef TIM_InitStruct; // 定义TIM基本定时器初始化结构体
TIM_OCInitTypeDef TIM_OC_InitStruct; // 定义TIM输出比较通道初始化结构体
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE); // 使能GPIOD时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE); // 使能TIM4时钟
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_12;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_Init(GPIOD, &GPIO_InitStruct); // 初始化GPIOD
GPIO_PinAFConfig(GPIOD, GPIO_PinSource12, GPIO_AF_TIM4); // 配置GPIOD的Pin12引脚复用为TIM4的功能
TIM_InitStruct.TIM_Prescaler = 41999;
TIM_InitStruct.TIM_CounterMode = TIM_CounterMode_Up;
TIM_InitStruct.TIM_Period = 999;
TIM_InitStruct.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInit(TIM4, &TIM_InitStruct); // 初始化TIM4定时器
TIM_OC_InitStruct.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OC_InitStruct.TIM_OutputState = TIM_OutputState_Enable;
TIM_OC_InitStruct.TIM_Pulse = 500;
TIM_OC_InitStruct.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OC1Init(TIM4, &TIM_OC_InitStruct); // 初始化TIM4的输出比较通道1
TIM_Cmd(TIM4, ENABLE); // 启动定时器
while (1) // 主循环
{
// do nothing
}
}
在本示例中,我们选择了通用定时器TIM4,并将其配置为向上计数模式。使用了内部时钟源,预分频器为41999,从而设置了定时器的计数频率为2Khz。定时器的计数周期为1000,即每1秒钟会产生一次计数器溢出中断。PWM输出的占空比为50%,即输出高电平500次、低电平500次,使得输出的信号频率为1Hz。最后通过使能定时器、输出比较通道和GPIO引脚来启动PWM输出
3.脉冲捕获
可以通过定时器的输入捕获模式捕获外部脉冲信号,用于测量脉冲的频率、占空比等。
以下是一个通过STM32单片机定时器实现脉冲捕获的示例:
#include "stm32f4xx.h"
uint32_t pulseWidth = 0; // 记录捕获脉冲的宽度
void TIM3_IRQHandler(void)
{
static uint32_t lastCapture = 0; // 记录上一次捕获的计数器值
uint32_t capture = TIM_GetCapture1(TIM3); // 获取当前捕获的计数器值
if (lastCapture != 0) // 如果不是第一次捕获
{
pulseWidth = capture - lastCapture; // 计算脉冲宽度
}
lastCapture = capture; // 更新上一次捕获的计数器值
TIM_ClearITPendingBit(TIM3, TIM_IT_CC1); // 清除中断标志位
}
int main(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
TIM_TimeBaseInitTypeDef TIM_InitStruct;
TIM_ICInitTypeDef TIM_ICInitStruct;
NVIC_InitTypeDef NVIC_InitStruct;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_Init(GPIOC, &GPIO_InitStruct);
GPIO_PinAFConfig(GPIOC, GPIO_PinSource6, GPIO_AF_TIM3);
TIM_InitStruct.TIM_Prescaler = 41999; // 设置预分频器,使计数器频率为2KHz
TIM_InitStruct.TIM_CounterMode = TIM_CounterMode_Up; // 设置计数模式为向上计数
TIM_InitStruct.TIM_Period = 0xFFFF; // 设置计数器的最大值,即计数周期为0xFFFF
TIM_InitStruct.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInit(TIM3, &TIM_InitStruct);
TIM_ICInitStruct.TIM_Channel = TIM_Channel_1; // 选择捕获通道1
TIM_ICInitStruct.TIM_ICPolarity = TIM_ICPolarity_Rising; // 选择上升沿触发捕获
TIM_ICInitStruct.TIM_ICSelection = TIM_ICSelection_DirectTI;
TIM_ICInitStruct.TIM_ICPrescaler = TIM_ICPSC_DIV1;
TIM_ICInitStruct.TIM_ICFilter = 0x0; // 不需要滤波
TIM_ICInit(TIM3, &TIM_ICInitStruct);
TIM_ITConfig(TIM3, TIM_IT_CC1, ENABLE); // 使能捕获/比较中断
NVIC_InitStruct.NVIC_IRQChannel = TIM3_IRQn; // 设置中断向量表中的中断号
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0; // 设置抢占优先级
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0; // 设置子优先级
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; // 使能中断
NVIC_Init(&NVIC_InitStruct); // 初始化NVIC
TIM_Cmd(TIM3, ENABLE); // 启动定时器
while (1)
{
}
}
在本示例中,我们选择了通用定时器TIM3,并将其配置为向上计数模式。使用了内部时钟源,设置了计数周期为0xFFFF,预分频器为41999,从而设置了定时器的计数频率为2Khz。通过设置输入捕获模式和输入捕获寄存器的值来捕获外部脉冲信号,从而实现测量脉冲的频率、占空比等功能。最后通过使能定时器和中断来启动定时器。中断处理函数中会计算捕获到的脉冲宽度,并将其保存在pulseWidth变量中。
本文章内示例中加入了大量的注释,一方面方便读者阅读、学习,另一方面也是培养我自己编程的好习惯。