当前位置: 首页 > news >正文

完整教程:IC(输入捕获)

完整教程:IC(输入捕获)

1. 为什么要用输入捕获?

你要测量一个外部信号的“时间特性”(频率、周期、脉宽、占空比),就需要知道某个电平跳变时,定时器计数器 CNT 的值是多少。

如果靠软件轮询 GPIO,反应太慢(CPU 扫描周期远大于微秒级的信号)。
定时器输入捕获:硬件自动在边沿时刻把 CNT 锁存到 CCR,不会漏、不靠软件延时,精度高。

所以,输入捕获相当于给你一个 硬件秒表,能精确记下“事件发生的时间点”。

2. 步骤与配置

main.c

#include "stm32f10x.h"                  // 设备头文件
#include "Delay.h"
#include "OLED.h"
#include "PWM.h"
#include "IC.h"
int main(void)
{
OLED_Init();        // 初始化 OLED 显示
PWM_Init();         // 初始化 PWM(TIM2_CH1 输出)
IC_Init();          // 初始化输入捕获(TIM3_CH1 输入)
// 显示标题
OLED_ShowString(1, 1, "Freq:00000Hz");
OLED_ShowString(2, 1, "Duty:00%");
// 设置 PWM 输出信号:1kHz 50%
PWM_SetPrescaler(720 - 1);          // PSC=719 → f=72M/720=100kHz,ARR=100 → f=1kHz
PWM_SetCompare1(50);                // CCR1=50 → 占空比=50/100=50%
while (1)
{
// 实时显示频率与占空比
OLED_ShowNum(1, 6, IC_GetFreq(), 5);  // 显示频率(5位数)
OLED_ShowNum(2, 6, IC_GetDuty(), 2);  // 显示占空比(2位数)
}
}

初始化 OLED、PWM(输出信号)、IC(输入捕获)。

OLED 显示模板。

PWM_SetPrescaler(719) → PWM 频率 = 72MHz / 720 / 100 = 1kHz。

PWM_SetCompare1(50) → 占空比 = 50/100 = 50%。

while 循环里动态显示 IC_GetFreq() 和 IC_GetDuty() 的计算结果。

PWM.c

#include "stm32f10x.h"                  // 设备头文件
// 初始化 PWM:TIM2_CH1 → PA0
void PWM_Init(void)
{
// 1. 开启 TIM2 和 GPIOA 时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
// 2. 配置 PA0 为 复用推挽输出(PWM 信号)
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;   // 复用推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;         // TIM2_CH1
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 3. 定时器时钟选择内部时钟
TIM_InternalClockConfig(TIM2);
// 4. 配置 TIM2 基本定时器参数
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; // 不分频
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; // 向上计数
TIM_TimeBaseInitStructure.TIM_Period = 100 - 1;   // ARR=99(计数范围:0~99)
TIM_TimeBaseInitStructure.TIM_Prescaler = 720 - 1;// PSC=719 → 定时器频率=72M/720=100kHz
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);
// 5. 配置输出比较通道为 PWM 模式
TIM_OCInitTypeDef TIM_OCInitStructure;
TIM_OCStructInit(&TIM_OCInitStructure);           // 先用默认值填充结构体
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; // PWM 模式 1
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; // 高电平有效
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; // 使能输出
TIM_OCInitStructure.TIM_Pulse = 0;                // 初始比较值=0
TIM_OC1Init(TIM2, &TIM_OCInitStructure);          // 配置 TIM2_CH1
// 6. 启动定时器
TIM_Cmd(TIM2, ENABLE);
}
// 设置占空比:CCR1
void PWM_SetCompare1(uint16_t Compare)
{
TIM_SetCompare1(TIM2, Compare);
}
// 设置分频器:PSC
void PWM_SetPrescaler(uint16_t Prescaler)
{
TIM_PrescalerConfig(TIM2, Prescaler, TIM_PSCReloadMode_Immediate);
}

TIM2_CH1(PA0)配置成 PWM 输出。

定时器时钟:72MHz → 经过 PSC=719 → 100kHz。

ARR=99 → 每 100 个计数溢出一次 → PWM 频率 = 100kHz/100 = 1kHz。

CCR1=50 → 高电平 50 个计数 → 占空比 = 50/100 = 50%。

IC.c

#include "stm32f10x.h"                  // 设备头文件
// 初始化输入捕获:TIM3_CH1 → PA6
void IC_Init(void)
{
// 1. 开启 TIM3 和 GPIOA 时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
// 2. 配置 PA6 为上拉输入模式(TIM3_CH1)
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;     // 上拉输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;         // TIM3_CH1
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 3. 定时器使用内部时钟
TIM_InternalClockConfig(TIM3);
// 4. 配置 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=65535
TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1;   // PSC=71 → 计数频率=72M/72=1MHz(1us)
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure);
// 5. 配置输入捕获通道(PWM 输入模式)
TIM_ICInitTypeDef TIM_ICInitStructure;
TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;      // 选择 CH1
TIM_ICInitStructure.TIM_ICFilter = 0xF;               // 输入滤波
TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; // 上升沿捕获
TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; // 不分频
TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; // 直接映射 TI1
TIM_PWMIConfig(TIM3, &TIM_ICInitStructure);           // 配置为 PWM 输入模式
// 6. 配置触发源与从模式
TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1);          // 选择 TI1FP1 作为触发源
TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Reset);       // 触发时复位 CNT(保证 CCR1 是周期)
// 7. 启动定时器
TIM_Cmd(TIM3, ENABLE);
}
// 获取频率:Freq = 1,000,000 / 周期(us)
uint32_t IC_GetFreq(void)
{
return 1000000 / (TIM_GetCapture1(TIM3) + 1);
}
// 获取占空比:Duty = 高电平时间 / 周期 * 100%
uint32_t IC_GetDuty(void)
{
return (TIM_GetCapture2(TIM3) + 1) * 100 / (TIM_GetCapture1(TIM3) + 1);
}

用 TIM3_CH1 (PA6) 作为输入捕获引脚。

PSC=71 → 1MHz → CNT 每 1us 增加 1 → 捕获值直接等于“微秒数”。

TIM_PWMIConfig() 配置为 PWM 输入模式:

CCR1 捕获周期(上升沿 → 上升沿)。

CCR2 捕获高电平时间(上升沿 → 下降沿)。

SlaveMode_Reset → 每次上升沿自动复位 CNT,保证 CCR1 直接等于周期长度。

TIM_GetCapture1(TIM3) = 周期计数(单位:us)。

Freq = 1,000,000 / 周期(us)。

TIM_GetCapture2(TIM3) = 高电平计数(单位:us)。

Duty = 高电平 / 周期 × 100%。

例如:

CCR1 = 1000 → 周期 = 1000us = 1ms → 频率 = 1kHz。

CCR2 = 500 → 高电平时间 = 500us → 占空比 = 500/1000=50%。

整个运行过程(信号流)

TIM2_CH1(PA0) 输出一个 1kHz 50% PWM 信号。

把 PA0 用杜邦线接到 PA6 (TIM3_CH1)。

TIM3 在输入捕获模式下:

上升沿 → 捕获 CNT → 存入 CCR1(周期)。

下降沿 → 捕获 CNT → 存入 CCR2(高电平时间)。

软件调用 IC_GetFreq() / IC_GetDuty() → 算出频率和占空比。

OLED 实时显示。

3.频率测量

4.由此我再详细展开代码进行解释

Main()函数

OLED_ShowString(1, 1, "Freq:00000Hz");
OLED_ShowString(2, 1, "Duty:00%");

初始化显示文本框架:

第一行:频率

第二行:占空比

输入捕获初始化 IC_Init()

虽然 CH2 是 PA7,但不用手动配置,TIM_PWMIConfig() 会自动处理。

TIM_InternalClockConfig(TIM3);

设置 TIM3 使用内部时钟(72MHz)作为计数源。

若用外部时钟(如外部晶振或编码器信号),需额外配置。

TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStructure.TIM_Period = 65536 - 1;
TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1;
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure);

设定 TIM3 的基本计数参数:

Prescaler = 72-1:每 1us 计一次

ARR = 65535:最大计数周期 65.5ms(最小可测频率约 15Hz)

ClockDivision 是死区等控制,不影响输入捕获

CounterMode = Up:向上计数(从0到ARR)

TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;
TIM_ICInitStructure.TIM_ICFilter = 0xF;
TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;
TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;

配置通道1的输入捕获参数:

极性:上升沿触发

滤波:0xF = 最大滤波,抗干扰

选择:TI1,直接连接引脚

预分频器不分频,每个沿都捕获

TIM_PWMIConfig(TIM3, &TIM_ICInitStructure);

关键函数!配置 TIM3 为PWM 输入模式:

自动配置:

CH1 捕获周期:两个上升沿之间

CH2 捕获高电平:上升沿 → 下降沿

TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1);

TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Reset);

设置 TI1FP1 为输入触发(上升沿)

使用 Reset 模式

每次上升沿,CNT 清零,从而方便测周期时间

频率与占空比计算

uint32_t IC_GetFreq(void)
{
return 1000000 / (TIM_GetCapture1(TIM3) + 1);
}

解释:

获取 CCR1:CH1 捕获的周期(单位:us)

使用公式:

Freq = 1秒 / 周期时间 = 1,000,000 / 周期(us)

+1 防止除以 0,安全设计。

uint32_t IC_GetDuty(void)
{
return (TIM_GetCapture2(TIM3) + 1) * 100 / (TIM_GetCapture1(TIM3) + 1);
}

解释:

占空比 = 高电平时间 / 周期 × 100%

CH2 捕获高电平时间,CH1 捕获周期

+1 防止除以 0,容错设计。

功能

方法

接口

测频率

CH1 捕获周期(上升-上升)

IC_GetFreq()

测占空比

CH2 捕获高电平(上升-下降)

IC_GetDuty()

显示

OLED 显示

OLED 接口

信号路径总览

输入的 PWM 波形 → 经 GPIO 进入 → 经过滤波器 → 边沿极性判断 → 分为 TI1FP1 和 TI1FP2 两路 → 连接到捕获单元 → 最终更新 CCR1 和 CCR2

从左到右模块解析

GPIO(PA6)

输入外部 PWM 信号,STM32 的 TIM3_CH1 通常连接到 GPIOA 的 PA6。

信号从这里进入定时器捕获逻辑。

滤波器

用于对输入信号进行消抖处理(抗干扰)。

滤波级别由 TIM_ICFilter 决定,在你代码中设置为 0xF,表示最高等级。

TIM_ICInitStructure.TIM_ICFilter = 0xF;

边沿检测 + 极性选择

决定检测哪个边沿触发捕获:

上升沿:TIM_ICPolarity_Rising

下降沿:TIM_ICPolarity_Falling

该配置决定 TI1FP1、TI1FP2 的行为。

在你设置的 PWM 输入模式中,TI1FP1 用于上升沿,TI1FP2 用于下降沿。

TI1FP1 / TI1FP2

这是捕获信号的逻辑输入通道,分别对应两个不同极性的触发事件:

TI1FP1(CH1 通道):用于产生周期捕获(上升 → 上升)

TI1FP2(CH2 通道):用于产生高电平捕获(上升 → 下降)

两个核心路径

路径1:TI1FP1 → 分频器 → CCR1

捕获的是 周期时间:

TI1FP1 接上升沿信号 → 触发 TIM3 在 CNT=当前值时锁存至 CCR1

然后 重置 CNT = 0(因为配置了 Slave Mode = Reset)

下一个上升沿又会触发捕获 → 得到完整周期

你用 CCR1 来算频率:

IC_GetFreq() = 1,000,000 / (CCR1 + 1);

路径2:TI1FP2 → 分频器 → CCR2

捕获的是 高电平持续时间:

TI1FP2 接下降沿信号 → 捕获当前 CNT → 锁存至 CCR2

它代表从上升沿开始后经过多少时间到下降沿

你用 CCR2 来算占空比:

IC_GetDuty() = (CCR2 + 1) * 100 / (CCR1 + 1);

模块

作用

PSC(预分频器)

将 72MHz 主时钟分频,比如 72 → 1MHz(即 1us 计一次)

CNT(计数器)

从0开始计数,在边沿触发时会被捕获到 CCRx

ARR(自动重装)

控制最大计数值,超过就溢出

Slave Mode:为什么要用 Reset

在你代码中:

TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1);             // 设置上升沿为触发源

TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Reset);          // 每次上升沿将 CNT 清零

解释:

让每一个周期都从 0 开始计时。

这样 CCR1、CCR2 都是“从上升沿开始算”的相对时间。

避免了你必须手动记录 CNT 起始值、当前值,简化计算。

时间点

事件

CNT 行为

捕获行为

第一个上升沿

CNT=0

CNT清零(Reset)

等待下一次

第二个上升沿

CNT=x1

CCR1 = x1(周期)

CNT清零

第二个下降沿

CNT=y1

CCR2 = y1(高电平)

CNT继续

第三个上升沿

CNT=x2

CCR1 = x2

CNT清零

主从触发模式

主模式(Master Mode)

左边黄色框:

定时器可以把一些事件输出为 TRGO 信号,传给其他定时器当触发源。

可选择的输出事件:

Reset(复位)

Enable(使能)

Update(更新事件,即溢出时)

OC1/OC2/OC3/OC4(比较匹配时)

OC1REF、OC2REF、OC3REF、OC4REF(比较输出参考信号)

 简单理解:主模式就是定时器“产生”一个触发信号,给别人用。

触发源选择(Trigger Selection)

中间黄色框:

定时器从外部接收 TRGI 触发信号,这些信号可能来自:

ITR0~3(内部触发,来自别的定时器的 TRGO)

TI1FP1(来自 CH1 的输入捕获信号)

TI2FP2(来自 CH2 的输入捕获信号)

TI1F_ED(来自 CH1 的边沿检测)

ETRF(外部触发引脚)

简单理解:这是定时器“接收”触发源的入口。

从模式(Slave Mode)

右边绿色框:

接收到 TRGI 后,定时器可以有不同的反应:

Closed:关闭

Encoder1/2/3:编码器接口模式

Reset:接收到触发信号时复位 CNT(输入捕获常用)

Gated:只有触发信号有效时,计数器才计数

Trigger:触发一次就启动一次

External1:外部时钟模式

简单理解:从模式就是定时器“对触发信号的响应方式”。

应用到输入捕获测频率

TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1);   // 选择 CH1 的输入作为触发源
TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Reset);// 触发时复位 CNT

TI1FP1 = PA6 的输入波形,每次上升沿进来时,触发一次。

Reset 模式 → CNT 被清零,同时把这个周期锁存到 CCR1。

CCR1 = 周期(两个上升沿之间的时间)。

CCR2 = 高电平时间(由 PWM 输入模式自动配置)。

这样你就能自动得到 周期(频率)+ 高电平宽度(占空比)。
无需自己写中断处理 CNT 溢出,硬件帮助完成了。

http://www.wxhsa.cn/company.asp?id=4767

相关文章:

  • HiMarket 正式开源,为企业落地开箱即用的 AI 开放平台
  • 如何统计DrawMeshInstancedIndirect绘制物体的Triangle数据
  • VK1S68C点钟LED驱动控制专用芯片高抗干扰数显驱动IC 可支持134的点阵LED显示面板
  • 基于MATLAB的海洋中尺度涡旋诊断
  • 从混乱到有序:Tita 项目一体化管理的全场景赋能
  • SpringBoot入门指南:让Java开发变得像搭积木一样简单 - 教程
  • 汇编语言[王爽]-13 int指令【中断实现loop、jmp】
  • Supabase云同步架构:Flutter应用的数据同步策略
  • 汇编语言[王爽]-12 内中断
  • 【SPIE出版】第五届先进制造技术与电子信息国际学术会议(AMTEI 2025)
  • 2025.9.15 考试总结
  • 汇编语言[王爽]-01 基础知识
  • 贪心外套计数
  • 汇编语言[王爽]-02 寄存器
  • 汇编语言[王爽]-03 寄存器(内存访问)
  • 汇编语言[王爽]-05 [BX]和loop指令
  • 完整教程:YOLO数据集格式转换工具v1.0-微智启软件工作室
  • 2.docker 安装
  • 树形DP2F
  • 搞定SPI开发:硬件设计精讲与CH390H示例应用
  • Qt-摄像头捕获画面
  • 我开发的软件和开源/免费软件
  • PostgreSQL中级认证,PG证书官网查询
  • LLaMA-Adapter - 详解
  • 查看安装软件版本的命令
  • ubuntu 20.04安装mysql 5.7
  • 企业微信逆向开发协议,ipad协议调用方式
  • OpenStack Nova Scheduler 计算节点选择机制
  • 记一种很新的 bitset
  • 基于yolo12进行深度学习的机动车车牌检测