STM32F103C8-定时器入门(9)
在嵌入式开发中,定时器是最常用的外设之一,常用于精确延时、PWM输出、输入捕获(如测频率/脉宽)、定时中断等场景。
定时器分类:
基本定时器:无输入捕获/比较通道,仅支持定时中断和PWM(基础功能),主要做简单延时、低精度定时任务
通用定时器:支持16位自动重装载计数器,一般4个独立通道(可配置为输入捕获/输出比较/PWM),主要做精确延时、PWM输出、测频率/脉宽
高级定时器:支持互补PWM(用于电机控制)、死区控制等高级功能,主要场景是电机驱动、复杂波形生成。
定时器应用:
记住三个寄存器
PSC 心跳分频(计数分频)
ARR 周期上限(自动重装载值)
CCR 比较/捕获值
PSC 决定“心跳快慢”
ARR 决定“心跳次数”
CCR 决定“何时响铃”
例子1:TIM2定时中断实现精确延时(1秒闪烁LED)
#include "stm32f10x.h"
#include "stm32f10x_tim.h"
#include "stm32f10x_rcc.h"
#include "stm32f10x_gpio.h"
#include "stm32f10x_nvic.h"// LED引脚初始化(PC13)
void LED_Init(void) {GPIO_InitTypeDef GPIO_InitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE); // 使能GPIOC时钟GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; // 推挽输出GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOC, &GPIO_InitStructure);GPIO_ResetBits(GPIOC, GPIO_Pin_13); // 初始LED灭
}// TIM2定时中断初始化(1秒周期)
void TIM2_Init(void) {TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;NVIC_InitTypeDef NVIC_InitStructure;RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); // 使能TIM2时钟// 配置TIM2基础参数TIM_TimeBaseStructure.TIM_Period = 999; // ARR=999(计数到999后溢出)TIM_TimeBaseStructure.TIM_Prescaler = 71999; // PSC=71999(72MHz/(71999+1)=1kHz)TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; // 向上计数TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);// 配置NVIC中断(TIM2中断优先级)NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_Init(&NVIC_InitStructure);TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); // 使能更新中断(溢出中断)TIM_Cmd(TIM2, ENABLE); // 启动TIM2
}// TIM2中断服务函数(在stm32f10x_it.c中需声明,此处直接实现)
void TIM2_IRQHandler(void) {if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) { // 检查是否是更新中断TIM_ClearITPendingBit(TIM2, TIM_IT_Update); // 清除中断标志位GPIOC->ODR ^= GPIO_Pin_13; // 翻转PC13(LED状态取反)}
}int main(void) {LED_Init(); // 初始化LEDTIM2_Init(); // 初始化TIM2定时中断while (1) {// 主循环无需操作,LED由中断控制}
}
例子2:TIM2 PWM输出控制LED亮度(呼吸灯效果)
利用TIM2的通道1(CH1,默认对应PA0引脚),输出PWM信号调节外接LED的亮度(通过改变占空比实现呼吸灯效果)
TIM2参数计算:
目标PWM频率:1kHz(人眼无法察觉闪烁)。
定时器时钟:72MHz → 需分频为72000(即1kHz=72MHz/72000)。
预分频(PSC):72000 - 1 = 71999(72MHz/(71999+1)=1kHz)。
自动重装载值(ARR):决定PWM周期(1kHz下,ARR+1=周期计数,此处设为999→周期=1ms,频率1kHz)。
占空比:通过比较值(CCR1)控制,范围0~ARR(0%=0,100%=ARR)。
#include "stm32f10x.h"
#include "stm32f10x_tim.h"
#include "stm32f10x_rcc.h"
#include "stm32f10x_gpio.h"// TIM2 PWM初始化(PA0为TIM2_CH1)
void TIM2_PWM_Init(void) {TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;TIM_OCInitTypeDef TIM_OCInitStructure;GPIO_InitTypeDef GPIO_InitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // 使能GPIOA时钟RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); // 使能TIM2时钟// 配置PA0为复用推挽输出(TIM2_CH1功能)GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 复用推挽输出GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure);// 配置TIM2基础参数TIM_TimeBaseStructure.TIM_Period = 999; // ARR=999(周期=1ms,频率1kHz)TIM_TimeBaseStructure.TIM_Prescaler = 71999; // PSC=71999(72MHz→1kHz)TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);// 配置TIM2通道1为PWM模式1(当计数器<CRR1时输出高电平)TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;TIM_OCInitStructure.TIM_Pulse = 0; // 初始占空比0%(LED灭)TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; // 高电平有效TIM_OC1Init(TIM2, &TIM_OCInitStructure); // 初始化通道1TIM_OC1PreloadConfig(TIM2, TIM_OCPreload_Enable); // 使能ARR预装载(可选,更平滑)TIM_Cmd(TIM2, ENABLE); // 启动TIM2
}// 设置PWM占空比(0~100%)
void TIM2_SetPWM_Duty(uint8_t duty) {uint32_t pulse = (TIM2->ARR + 1) * duty / 100; // 计算比较值(CCR1)TIM_SetCompare1(TIM2, pulse); // 设置通道1比较值
}int main(void) {TIM2_PWM_Init(); // 初始化TIM2 PWMuint8_t duty = 0;uint8_t dir = 1; // 方向标志(1:增,0:减)while (1) {TIM2_SetPWM_Duty(duty); // 设置当前占空比delay_ms(20); // 延时20ms(调节呼吸速度)if (dir) {duty += 2; // 占空比+2%if (duty >= 100) dir = 0; // 达到100%后反向} else {duty -= 2; // 占空比-2%if (duty <= 0) dir = 1; // 达到0%后正向}}
}// 简单延时函数(非精确,仅用于演示)
void delay_ms(uint32_t ms) {uint32_t i, j;for (i = 0; i < ms; i++)for (j = 0; j < 8000; j++);
}
例子3:TIM2输入捕获测量外部脉冲频率
通过TIM2的通道1(CH1,默认PA0引脚)捕获外部输入的脉冲信号(如信号发生器输出的方波),计算其频率(单位:Hz)。
硬件连接:将外部脉冲信号(如3.3V方波)接PA0(TIM2_CH1),GND共地。
TIM2参数配置:
输入捕获模式:检测信号的上升沿,记录当前计数值(CNT),两次上升沿的时间差=计数差×计数周期。
定时器时钟:72MHz(APB1预分频为2时,TIM2时钟=72MHz)。
预分频(PSC):72-1=71(72MHz/72=1MHz,即1计数=1μs)。
自动重装载值(ARR):设为较大值(如0xFFFF),避免溢出干扰。
代码逻辑:
使用TIM2的输入捕获功能,捕获信号的上升沿,记录两次捕获的计数值差(ΔCNT)。
频率计算公式:频率 = TIM2时钟 / (ΔCNT × 计数周期)(若PSC=71→计数周期=1μs→频率=72MHz/ΔCNT)。
#include "stm32f10x.h"
#include "stm32f10x_tim.h"
#include "stm32f10x_rcc.h"
#include "stm32f10x_gpio.h"
#include <stdio.h>volatile uint32_t capture1 = 0; // 第一次捕获值
volatile uint32_t capture2 = 0; // 第二次捕获值
volatile uint8_t capture_flag = 0; // 捕获完成标志// TIM2输入捕获初始化(PA0为TIM2_CH1)
void TIM2_InputCapture_Init(void) {TIM_ICInitTypeDef TIM_ICInitStructure;TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;GPIO_InitTypeDef GPIO_InitStructure;NVIC_InitTypeDef NVIC_InitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // 使能GPIOA时钟RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); // 使能TIM2时钟// 配置PA0为浮空输入(TIM2_CH1信号输入)GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;GPIO_Init(GPIOA, &GPIO_InitStructure);// 配置TIM2基础参数TIM_TimeBaseStructure.TIM_Period = 0xFFFF; // ARR=最大值(避免溢出)TIM_TimeBaseStructure.TIM_Prescaler = 71; // PSC=71(72MHz→1MHz,1计数=1μs)TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);// 配置TIM2通道1为输入捕获模式(直接模式,检测上升沿)TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; // 捕获上升沿TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; // 不分频TIM_ICInitStructure.TIM_ICFilter = 0x0; // 无滤波TIM_ICInit(TIM2, &TIM_ICInitStructure);// 配置NVIC中断(输入捕获中断)NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_Init(&NVIC_InitStructure);TIM_ITConfig(TIM2, TIM_IT_CC1, ENABLE); // 使能通道1捕获中断TIM_Cmd(TIM2, ENABLE); // 启动TIM2
}// TIM2中断服务函数(检测输入捕获事件)
void TIM2_IRQHandler(void) {if (TIM_GetITStatus(TIM2, TIM_IT_CC1) != RESET) { // 检查是否是通道1捕获中断if (capture_flag == 0) {capture1 = TIM_GetCapture1(TIM2); // 第一次捕获计数值capture_flag = 1; // 标记已捕获第一次TIM_OC1PolarityConfig(TIM2, TIM_ICPolarity_Falling); // 改为捕获下降沿(可选,本例直接捕获两次上升沿)} else {capture2 = TIM_GetCapture1(TIM2); // 第二次捕获计数值capture_flag = 0; // 重置标志TIM_OC1PolarityConfig(TIM2, TIM_ICPolarity_Rising); // 恢复捕获上升沿}TIM_ClearITPendingBit(TIM2, TIM_IT_CC1); // 清除中断标志位}
}// 计算并返回输入信号的频率(Hz)
uint32_t Get_Input_Frequency(void) {if (capture_flag == 0 && capture2 != 0) { // 确保已捕获两次uint32_t delta_cnt = capture2 - capture1; // 计数差值uint32_t frequency = 1000000 / delta_cnt; // 频率=1MHz/ΔCNT(因为计数周期=1μs)capture1 = capture2 = 0; // 清空捕获值return frequency;}return 0; // 未完成捕获返回0
}int main(void) {TIM2_InputCapture_Init(); // 初始化TIM2输入捕获printf("TIM2输入捕获测试:连接外部脉冲信号到PA0,查看频率\r\n");while (1) {uint32_t freq = Get_Input_Frequency();if (freq > 0) {printf("检测到频率:%lu Hz\r\n", freq); // 打印频率(需串口支持)}// 实际项目中可通过串口或LCD显示频率值}
}
延伸应用:
定时中断延时:适合需要固定周期触发任务的场景(如LED闪烁、传感器定时采样)。
PWM输出:用于调节外设功率管(如LED亮度、电机转速)。
输入捕获:测量外部信号频率/脉宽(如红外遥控解码、转速检测)。