目录
[2.GPIO 初始化](#2.GPIO 初始化)
一、引言
在嵌入式系统开发中,STM32 系列微控制器因其强大的性能和丰富的功能被广泛应用。其中,输入捕获功能是 STM32 定时器的一个重要特性,它可以用来测量外部信号的周期、频率和占空比等参数,在电机控制、速度测量、通信同步等众多领域都有着重要的应用。本文将详细介绍 STM32 的输入捕获功能,包括其原理、配置步骤以及程序示例。
二、输入捕获原理
STM32 的输入捕获,简单来说就是通过检测 TIMx_CHx 上的边沿信号,在边沿信号发生跳变(比如上升沿/下降沿/上升沿和下降沿均触发)的时候,将当前定时器计数器的值(TIMx_CNT)存放到对应的通道的捕获/比较寄存器(TIMx_CCRx)里面,完成一次捕获。同时还可以配置捕获时是否触发中断或 DMA 等操作。
例如,我们要测量一个高电平脉冲的宽度,首先设置定时器通道为上升沿捕获,在上升沿到来时,会捕获到当前的 TIMx_CNT 值,然后立即清零 TIMx_CNT,并设置通道为下降沿捕获,当下降沿到来时,又会发生捕获事件,得到此时的 TIMx_CNT 值。这样,前后两次 TIMx_CNT 的差值,就是高电平的脉宽。如果脉宽比较长,定时器可能会产生多次溢出,这就需要我们对定时器溢出进行处理,以保证测量结果的准确性。
三、寄存器介绍
- TIMx_ARR:自动重装载寄存器,用来设置定时器的计数周期。当定时器计数器的值达到 ARR 的值时,会重新从 0 开始计数。
- TIMx_PSC:预分频寄存器,用于对定时器的时钟源进行分频,以得到合适的计数频率。
- TIMx_CCMR1:捕获/比较模式寄存器 1,用于配置输入捕获的通道方向、触发极性、输入分频等参数。
- TIMx_CCER:捕获/比较使能寄存器,用来使能或禁止捕获/比较通道的输入捕获功能。
- TIMx_DIER:DMA/中断使能寄存器,用于开启或关闭捕获/比较通道的中断或 DMA 请求。
- TIMx_CR1:控制寄存器 1,用于控制定时器的启动、停止、计数模式等。
- TIMx_CCR1:捕获/比较寄存器 1,用来存储捕获发生时的 TIMx_CNT 值。
四、配置步骤
以下是以 STM32F4 为例,配置输入捕获的一般步骤:
1.开启时钟
- 开启定时器的时钟。例如,如果要使用 TIM5,需要调用 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5, ENABLE) 来使能 TIM5 的时钟。
- 开启对应 GPIO 引脚的时钟。例如,如果捕获信号连接到 PA0 引脚,需要调用 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE) 来使能 GPIOA 的时钟。
2.GPIO 初始化
- 将捕获信号对应的 GPIO 引脚配置为复用功能。例如,如果使用 TIM5 的通道 1 捕获信号,且信号连接到 PA0 引脚,需要将 PA0 引脚配置为复用功能,并设置复用功能为 AF2(具体的复用功能值根据芯片手册确定)。
- 根据需要配置 GPIO 引脚的上拉、下拉电阻等。
3.初始化定时器
设置 TIM_TimeBaseInitTypeDef 结构体的参数,包括自动重装载值 TIM_Period、预分频值 TIM_Prescaler、计数模式 TIM_CounterMode 等。例如,如果要设置定时器的计数周期为 1000,预分频值为 7199(使得定时器的计数频率为 10kHz),计数模式为向上计数,可以这样设置:
如果需要,可以设置定时器的重复计数器值 TIM_RepetitionCounter,该参数在高级定时器中使用较多。
4.配置输入捕获模式
设置 TIM_ICInitTypeDef 结构体的参数,包括捕获通道 TIM_Channel、触发极性 TIM_ICPolarity、输入选择 TIM_ICSelection、输入分频 TIM_ICPrescaler、输入滤波 TIM_ICFilter 等。例如,如果要配置 TIM5 的通道 1 为上升沿捕获,不分频,不滤波,可以这样设置:
cpp
IM_ICInitTypeDef TIM5_ICInitStructure;
TIM5_ICInitStructure.TIM_Channel = TIM_Channel_1;
TIM5_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;
TIM5_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
TIM5_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
TIM5_ICInitStructure.TIM_ICFilter = 0x00;
TIM_ICInit(TIM5, &TIM5_ICInitStructure);
5.使能捕获和更新中断
如果需要在捕获事件发生时产生中断,需要调用 TIM_ITConfig 函数开启捕获中断和更新中断。例如:
cpp
TIM_ITConfig(TIMx, TIM_IT_Update | TIM_IT_CC1, ENABLE);
6.设置中断分组并编写中断服务函数
设置中断分组,通常使用 NVIC_Init 函数来完成。中断分组的设置决定了中断的优先级。
在中断服务函数中,需要判断中断类型,并进行相应的数据处理。例如,如果是捕获中断,需要读取捕获寄存器的值;如果是更新中断,可能需要处理定时器溢出等情况。以下是一个简单的中断服务函数示例:
cpp
void TIMx_IRQHandler(void)
{
if (TIM_GetITStatus(TIMx, TIM_IT_Update)!= RESET)
{
// 处理更新中断
}
if (TIM_GetITStatus(TIMx, TIM_IT_CC1)!= RESET)
{
// 处理捕获中断
// 读取捕获寄存器的值,进行数据处理
uint16_t captureValue = TIM_GetCapture1(TIMx);
// 清除中断标志位
TIM_ClearITPendingBit(TIMx, TIM_IT_CC1 | TIM_IT_Update);
}
}
7.使能定时器
调用 TIM_Cmd 函数使能定时器,开始输入捕获。例如:
cpp
TIM_Cmd(TIMx, ENABLE);
五、程序示例
以下是一个完整的 STM32 输入捕获程序示例,用于测量 PA0 引脚上输入信号的高电平脉宽,并通过串口打印结果:
cpp
#include "stm32f4xx.h"
#include <stdio.h>
// 定义捕获状态和捕获值变量
volatile uint8_t TIM5CH1_CAPTURE_STA = 0;
volatile uint16_t TIM5CH1_CAPTURE_VAL = 0;
// 定时器 5 中断服务函数
void TIM5_IRQHandler(void)
{
if ((TIM5CH1_CAPTURE_STA & 0X80) == 0) // 还未成功捕获
{
if (TIM_GetITStatus(TIM5, TIM_IT_Update)!= RESET) // 检测是否发生更新中断
{
if (TIM5CH1_CAPTURE_STA & 0X40) // 已经捕获到高电平
{
if ((TIM5CH1_CAPTURE_STA & 0X3F) == 0X3F) // 定时器溢出
{
TIM5CH1_CAPTURE_STA |= 0X80; // 标记为成功捕获
TIM5CH1_CAPTURE_VAL = 0XFFFF;
}
else
{
TIM5CH1_CAPTURE_STA++; // 对溢出次数进行计数
}
}
}
if (TIM_GetITStatus(TIM5, TIM_IT_CC1)!= RESET) // 判断是否发生捕获事件
{
if (TIM5CH1_CAPTURE_STA & 0X40) // 如果已经捕获到上升沿
{
// 捕获到下降沿,计算脉宽
TIM5CH1_CAPTURE_VAL = TIM_GetCapture1(TIM5);
TIM5CH1_CAPTURE_STA |= 0X80; // 标记为成功捕获
}
else
{
// 捕获到上升沿,记录当前定时器值,并设置为下降沿捕获
TIM_SetCounter(TIM5, 0);
TIM5CH1_CAPTURE_STA = 0X40;
TIM_OC1PolarityConfig(TIM5, TIM_ICPolarity_Falling);
}
}
}
// 清除中断标志位
TIM_ClearITPendingBit(TIM5, TIM_IT_CC1 | TIM_IT_Update);
}
int main(void)
{
// 开启 TIM5 和 GPIOA 时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
// GPIOA 引脚初始化
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 连接 PA0 到 TIM5 的通道 1
GPIO_PinAFConfig(GPIOA, GPIO_PinSource0, GPIO_AF_TIM5);
// 定时器 5 初始化
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_TimeBaseStructure.TIM_Period = 0xFFFF;
TIM_TimeBaseStructure.TIM_Prescaler = 7199;
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM5, &TIM_TimeBaseStructure);
// 输入捕获配置
TIM_ICInitTypeDef TIM5_ICInitStructure;
TIM5_ICInitStructure.TIM_Channel = TIM_Channel_1;
TIM5_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;
TIM5_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
TIM5_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
TIM5_ICInitStructure.TIM_ICFilter = 0x00;
TIM_ICInit(TIM5, &TIM5_ICInitStructure);
// 使能捕获和更新中断
TIM_ITConfig(TIM5, TIM_IT_Update | TIM_IT_CC1, ENABLE);
// 设置中断分组
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = TIM5_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
// 使能定时器
TIM_Cmd(TIM5, ENABLE);
while (1)
{
if (TIM5CH1_CAPTURE_STA & 0X80)
{
// 计算高电平脉宽
uint32_t pulseWidth = (TIM5CH1_CAPTURE_STA & 0X3F) * 65536 + TIM5CH1_CAPTURE_VAL;
// 打印结果
printf("高电平脉宽:%d us\n", pulseWidth);
// 清除捕获状态
TIM5CH1_CAPTURE_STA = 0;
}
}
}
在上述程序中,首先初始化了定时器和 GPIO 引脚,配置了输入捕获模式和中断。在中断服务函数中,根据上升沿和下降沿的捕获情况,记录定时器的值并计算高电平脉宽。在主循环中,不断检查是否成功捕获到高电平脉宽,并打印结果。
六、总结
STM32 的输入捕获功能是一个非常强大的工具,可以帮助我们准确地测量外部信号的参数。通过合理地配置定时器和相关寄存器,以及编写中断服务函数,我们可以轻松地实现输入捕获功能。在实际应用中,我们需要根据具体的需求选择合适的定时器和捕获通道,并注意处理定时器溢出等情况,以保证测量结果的准确性。希望本文对大家理解和使用 STM32 的输入捕获功能有所帮助。