1 输入捕获
1.1 输入捕获简介
IC(Input Capture)输入捕获
输入捕获模式下,当通道输入引脚出现指定电平跳变时(上升沿/下降沿),当前CNT的值将被锁存到CCR中(把CNT的值读出来,写入到CCR中),可用于测量PWM波形的频率、占空比、脉冲间隔、电平持续时间等参数
每个高级定时器和通用定时器都拥有4个输入捕获通道
可配置为PWMI模式(PWM的输入模式),同时测量频率和占空比
可配合主从触发模式,实现硬件全自动测量
对于同一个定时器,输入捕获和输出比较只能使用其中一个,不能同时使用(共用)
输出比较是根据CNT和CCT的大小关系来执行输出动作;
输入捕获是接收到输入信号,执行CNT锁存到CCR的动作;
1.2 频率测量
**左边频率大于右边频率。**STM32也只能测数字信号。
测频法:上升沿出现的次数/闸门时间。适合高频信号,测量结果更新慢
测周法:两个上升沿的持续时间。fc是标准频率/计次。适合低频信号,测量结果更新快
中界频率:测频法与测周法误差相等的频率点
当待测频率小于中界频率时,测周法误差更小;反之测频法误差更小。
本节使用的是测周法
1.3 输入捕获电路
最左边是4个通道的引脚,参考引脚定义表就可以知道这个引脚复用在哪个位置。接下来是三输入的异或门,接到了CH1、CH2、CH3,异或门的逻辑是当三个输入引脚的任何一个有电平翻转时,输出引脚就产生一次电平翻转,之后,输出通过数据选择器,到达输入捕获通道1。数据选择器如果选择上面的,就是3个引脚的异或值;如果选择下面一个,那异或门就没有用,4通道各用各的引脚。异或门是为了三相无刷电机服务的。
输入信号此时来到输入滤波器和边沿检测器,输入滤波器对信号进行滤波,避免高频的毛刺信号误触发;边沿检测器处可以选择高电平触发/低电平触发(类似中断),当出现指定电平时,边沿检测电路就会触发后续电路执行动作。有两套电路,分别输出TI1FP1(TI1 Filter Polarity 1,输出给通道1的后续电路)和TI1FP2(TI1 Filter Polarity 2,输出给 下面通道2的后续电路),下面同理。有一个交叉,即两个信号进来可以选择各走各的,也可以是让CH1引脚输入给通道2,让CH2引脚输入给通道1。**目的:(1)可以灵活切换后续捕获电路的输入;(2)可以把一个引脚的输入同时映射到两个捕获单元。**这也是PWMI模式的经典结构。
第一个通道使用上升沿触发,用来捕获周期;
第二个通道使用下降沿触发,用来捕获占空比;两个通道同时对一个引脚进行捕获,就可以同时测量频率和占空比,这就是PWMI模式。
TRC信号也是为了无刷电机的驱动。
经过预分频器之后的信号,就可以触发捕获电路进行工作了。每来一个触发信号,CNT的值,就会向CCR转运(写入/存储)一次,转运的同时,会发生一个捕获事件,这个事件会在状态寄存器置标志位,同时也可以产生中断,可以开启这个中断处理事情。CNT的计数器是由内部标准时钟驱动的,所以CNT的数值其实就可以记录两个上升沿之间的时间间隔,这个时间间隔就是周期,取个倒数就是频率,这就是测周法测量频率
上升沿用于触发输入捕获,CNT用于计数计时,每来一个上升沿,取一下CNT的值,自动存在CCR里,CCR捕获到的值就是计数值N,CNT的驱动时钟就是fc。每次捕获之后,需要把CNT清零(主从触发模式自动处理)。
1.4 输入捕获通道
引脚进来,还行先经过一个滤波器,滤波器的输入就是TI1,就是CH1的引脚,输出TI1F的就是滤波后的信号。fDTS是滤波器的采样时钟来源,下面的CCMR1寄存器的ICF位可以控制滤波器的参数。
这个滤波器的工作原理:以采样频率对输入信号进行采样,当连续N个值都是高电平时,输出才是高电平;连续N个值都是低电平时,输出才是低电平。采样频率越低,采样个数N越大,滤波效果就越好。
滤波之后信号通过边沿检测器,捕获上升沿/下降沿,用CCER寄存器里面的CC1P位就可以选择极性了,最终得到TI1FP1触发信号,通过数据选择器,进入通道1后续的捕获电路(还有TI1FP2触发信号;同样通道2有TI2FP1和TI2FP2信号,也就是前面的交叉)。CCMR寄存器的CC1S位可以对数据选择器进行选择,之后ICPS位可以配置这里的预分频器(不分频/2分频/4分频/8分频),最后CCER寄存器的CC1E位控制输出使能或者失能。如果使能了输出,输入端产生指定边沿信号经过层层电路,就可以让这里的CNT的值,转运到CCR里面来,硬件电路自动完成捕获之后的CNT清零工作。
这个从模式里面有电路完成CNT的自动清零
1.5 主从触发模式
主模式可以将定时器内部的信号映射到TRGO引脚,用于触发别的外设,所以这部分叫主模式。
从模式就是接收其他外设或者自身外设的一些信号,用于控制自身定时器的允运行,也就是被别的信号控制,所以这部分叫从模式。
触发源选择就是选择从模式的触发信号源(是从模式的一部分),选择一个指定的信号,得到TRGI,TRGI去触发从模式,从模式可以在列表中选择一项操作来自动执行。
TI1FP1信号自动触发CNT清零操作选择线路。
手册
主模式选择:
从模式触发源选择
从模式选择
1.6 输入捕获基本结构
只使用了一个通道,目前只能测频率。右上角是时基单元(配置好),启动定时器,CNT就会在预分频器之后的时钟驱动下不断自增(CNT就是测周法计数计时的参数),经过预分频之后的时钟频率就是驱动CNT的标准频率fc(fc = 72M / 预分频系数),
下面输入捕获通道1的GPIO口,输入一个左上角这样的方波信号,经过滤波器和边沿检测,选择TI1FP1为上升沿触发,之后输出选择直连的通道,分频器选择不分频;当TI1FP1出现上升沿后,CNT的当前计数值转运到CCR1里;同时触发源选择,选中TI1FP1为触发信号,从模式选择复位操作。有先后顺序,先转运CNT的值到CCR里去,再触发从模式给CNT清零。
信号出现一个上升沿,CCR1 = CNT,就是把CNT的值转运到CCR1里面去,这是输入捕获自动执行的,然后CNT = 0,清零计数器,这是从模式自动执行的;然后在一个周期之内,CNT在标准时钟的驱动下,不断自增,并且由于之前清零过了,所以CNT就是从上升沿开始,从0开始计数,一直自增,直到下一次上升沿来临,然后执行相同的操作。
第二次捕获的时候,CNT的值就是之前的计数值,这个计数值就自动放在CCR1里;然后下一个周期里,CNT从0开始自增,直到下一个上升沿,这是CCR1刷新为第二个周期即CNT计数值;不断重复。所以当这个电路工作时,CCR1的值始终保持为最新一个周期的计数值(就是测周法的N)
注意事项:
(1)CNT的值是有上限的(65535),如果信号频率太低,CNT可能会溢出;
(2)从模式的触发源选择,只有TI1FP1和TI2FP2,没有TI3和TI4
1.7 PWMI基本结构
PWMI模式使用了两个通道同时捕获一个引脚,可以同时测量周期和占空比。下面多了一个通道。首先TI1FP1配置上升沿触发,触发捕获和清零CNT。这是再来一个TI1FP2,将其配置为下降沿触发,通过交叉通道,器触发通道2的捕获单元,见左上角图。
最开始上升沿,CCR1捕获,同时清零CNT,之后CNT一直自增;然后在下降沿这个时刻,触发CCR2捕获,所以CCR2捕获的值就是高电平所计数的CNT的值(好巧妙),这样CCR1的值是整个周期的值,而CCR2的值是高电平的值,就可以求出占空比(CCR2/CCR1),这就是PWMI模式,使用两个通道来捕获频率和占空比。
手册
2 TIM输入捕获之测频率
2.1 接线图
目前测量信号的输入引脚是PA6,信号从PA6进来,待测的PWM信号也是STM32自己生成的,输出引脚是PA0。也就是STM32产生的信号从PA0输出,而STM32测频率的接口是PA6。
2.2 模块封装
PWM.c
cpp
#include "stm32f10x.h" // Device header
// PWM初始化函数
void PWM_Init(void)
{
// 1RCC开启时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
TIM_InternalClockConfig(TIM2);
// 2配置时基单元(预分频器、自动重装器、计数模式等)
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; // 时钟分频,影响不大
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; // 计数模式,向上计数
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; // 重复计数器的值
// 关键参数
TIM_TimeBaseInitStructure.TIM_Period = 100 - 1; // ARR自动重装器的值,固定ARR
TIM_TimeBaseInitStructure.TIM_Prescaler = 720 - 1; // PSC预分频器的值,调节PSC来实现调节频率
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);
// 3配置输出比较单元(CCR的值、输出比较模式、极性选择、输出使能)
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; // CCR的值w为50,先设置为0,后面变化
// 这个函数的选择按照GPIO口需求来,PA0口对应的是第一个输出比较通道
TIM_OC1Init(TIM2, &TIM_OCInitStructure);
// 频率1kHz,占空比50% ,分辨率为1%。 CCR = 50 ,
/**
PWM分辨率: Reso = 1 / (ARR + 1) = 1% ==> ARR= 100 - 1
PWM占空比: Duty = CCR / (ARR + 1) = 50% ==> CCR = 50
PWM频率: Freq = CK_PSC(72M) / (PSC + 1) / (ARR + 1) = 1K ==> PSC = 720 - 1
*/
// 4配置GPIO(初始化为复用推挽输出,参考引脚定义表)
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 复用推挽输出。定时器控制引脚,输出控制权转移给片上外设
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 5运行控制,启动计数器
TIM_Cmd(TIM2, ENABLE);
}
// 更改通道1的CCR值,改变占空比
void PWM_SetCompare1(uint16_t Compare)
{
TIM_SetCompare1(TIM2, Compare);
}
// 修改PSC,改变频率
void PWM_SetPrescaler(uint16_t prescaler)
{
// 立刻生效
TIM_PrescalerConfig(TIM2, prescaler, TIM_PSCReloadMode_Immediate);
}
输入捕获的初始化按照这个步骤
库函数
cpp
// TIM输入捕获的函数,4个通道公用一个函数(电路中公用模块)
void TIM_ICInit(TIM_TypeDef* TIMx, TIM_ICInitTypeDef* TIM_ICInitStruct);
// TIM输入捕获的函数,可以快速配置两个通道(PWMI模式)
void TIM_PWMIConfig(TIM_TypeDef* TIMx, TIM_ICInitTypeDef* TIM_ICInitStruct);
// 输入捕获结构体赋值一个初始值
void TIM_ICStructInit(TIM_ICInitTypeDef* TIM_ICInitStruct);
// 选择输入触发源TRGI(从模式的触发源选择)
void TIM_SelectInputTrigger(TIM_TypeDef* TIMx, uint16_t TIM_InputTriggerSource);
// 选择输出触发源TRGO(主模式输出的触发源)
void TIM_SelectOutputTrigger(TIM_TypeDef* TIMx, uint16_t TIM_TRGOSource);
// 选择从模式(从模式选择)
void TIM_SelectSlaveMode(TIM_TypeDef* TIMx, uint16_t TIM_SlaveMode);
// 分别单独配置通道1234的分频器
void TIM_SetIC1Prescaler(TIM_TypeDef* TIMx, uint16_t TIM_ICPSC);
void TIM_SetIC2Prescaler(TIM_TypeDef* TIMx, uint16_t TIM_ICPSC);
void TIM_SetIC3Prescaler(TIM_TypeDef* TIMx, uint16_t TIM_ICPSC);
void TIM_SetIC4Prescaler(TIM_TypeDef* TIMx, uint16_t TIM_ICPSC);
// 分别读取4个通道的CCR。输出比较模式下,CCR是只写的,使用set
// 输入捕获模式下CCR是只读的,使用get
uint16_t TIM_GetCapture1(TIM_TypeDef* TIMx);
uint16_t TIM_GetCapture2(TIM_TypeDef* TIMx);
uint16_t TIM_GetCapture3(TIM_TypeDef* TIMx);
uint16_t TIM_GetCapture4(TIM_TypeDef* TIMx);
选择TIM3的通道,选择引脚。使用TIM3的通道1引脚,即选择PA6
IC.c
cpp
#include "stm32f10x.h" // Device header
// 输入捕获初始化
void IC_Init(void)
{
// 1RCC开启时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
// 2GPIO初始化(输入模式,上拉/浮空)
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU; // 上拉输入
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStruct);
// 选择内部时钟
TIM_InternalClockConfig(TIM3);
// 3配置时基单元
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
TIM_TimeBaseInitStruct.TIM_Prescaler = 72 - 1; // PSC预分频器的值,72分频,通过PSC来调节
TIM_TimeBaseInitStruct.TIM_Period = 65536 - 1; // ARR自动重装器的值 两个合起来计数1秒,固定
TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up; // 向上计数
TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1; // 不分频
TIM_TimeBaseInitStruct.TIM_RepetitionCounter = 0; // 重复计数器的值
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStruct);
// 4配置输入捕获单元
TIM_ICInitTypeDef TIM_ICInitStructure;
TIM_ICInitStructure.TIM_Channel = TIM_Channel_1; // 选择通道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; // 选择触发信号从哪个引脚输入,直连通道/交叉通道
TIM_ICInit(TIM3, &TIM_ICInitStructure);
// 5选择从模式的触发源(TI1FP1)
TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1);
// 6选择触发之后的操作(从模式reset操作)
TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Reset);
// 7开启定时器
TIM_Cmd(TIM3, ENABLE);
}
// 获得频率函数
uint32_t IC_GetFreq(void)
{
// fx = fc / N
return 1000000 / (TIM_GetCapture1(TIM3) + 1);
}
2.3 主函数
cpp
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "PWM.h"
#include "IC.h"
int main()
{
OLED_Init(); // 初始化OLED
PWM_Init(); // PWM初始化
IC_Init(); // IC初始化
OLED_ShowString(1, 1, "Freq:00000Hz"); // 显示字符串
PWM_SetPrescaler(720 - 1); // Freq = 72M / (PSC + 1) / (ARR + 1 = 100) = 1000Hz
PWM_SetCompare1(50); // 设置CCR的值,占空比Duty = CCR / (ARR + 1 = 100) = 50%
// 上面这里输出信号是1000Hz的PWM波
while (1)
{
OLED_ShowNum(1, 6, IC_GetFreq(), 5);
}
}
现象:OLED上显示1000Kz
3 TIM输入捕获之PWMI模式测频率占空比
3.1 接线图
3.2 模块封装
按照这个配置
测周法
PWM.c不变
IC.c
cpp
#include "stm32f10x.h" // Device header
// 输入捕获初始化
void IC_Init(void)
{
// 1RCC开启时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
// 2GPIO初始化(输入模式,上拉/浮空)
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU; // 上拉输入
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStruct);
// 选择内部时钟
TIM_InternalClockConfig(TIM3);
// 3配置时基单元
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
TIM_TimeBaseInitStruct.TIM_Prescaler = 72 - 1; // PSC预分频器的值,72分频,通过PSC来调节
TIM_TimeBaseInitStruct.TIM_Period = 65536 - 1; // ARR自动重装器的值 两个合起来计数1秒,固定
TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up; // 向上计数
TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1; // 不分频
TIM_TimeBaseInitStruct.TIM_RepetitionCounter = 0; // 重复计数器的值
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStruct);
// 4配置输入捕获单元,修改成两个通道同时捕获同一个引脚的模式
TIM_ICInitTypeDef TIM_ICInitStructure;
TIM_ICInitStructure.TIM_Channel = TIM_Channel_1; // 选择通道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; // 选择触发信号从哪个引脚输入,直连通道/交叉通道
// 法2,直接把另一个通道置成相反的配置,通道2/交叉/下降沿
TIM_PWMIConfig(TIM3, &TIM_ICInitStructure); // 配置PWMI模式
// 法1
// TIM_ICInitStructure.TIM_Channel = TIM_Channel_2; // 选择通道1
// TIM_ICInitStructure.TIM_ICFilter = 0xF; // 输入捕获滤波器,数越大,滤波效果越好
// TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Falling; // 下降沿触发
// TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; // 不分频,触发信号分频器,不分频就是每次都有效
// TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_IndirectTI; // 选择触发信号从哪个引脚输入,交叉通道
// TIM_ICInit(TIM3, &TIM_ICInitStruct);
// 5选择从模式的触发源(TI1FP1)
TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1);
// 6选择触发之后的操作(从模式reset操作)
TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Reset);
// 7开启定时器
TIM_Cmd(TIM3, ENABLE);
}
// 获得频率函数
uint32_t IC_GetFreq(void)
{
// fx = fc / N
return 1000000 / (TIM_GetCapture1(TIM3) + 1);
}
// 获取占空比函数
uint32_t IC_GetDuty(void)
{
// CCR2 / CCR1
return (TIM_GetCapture2(TIM3) + 1) * 100 / (TIM_GetCapture1(TIM3) + 1);
}
3.3 主函数
cpp
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "PWM.h"
#include "IC.h"
int main()
{
OLED_Init(); // 初始化OLED
PWM_Init(); // PWM初始化
IC_Init(); // IC初始化
OLED_ShowString(1, 1, "Freq:00000Hz"); // 显示字符串
OLED_ShowString(2, 1, "Duty:00%"); // 显示字符串
PWM_SetPrescaler(720 - 1); // Freq = 72M / (PSC + 1) / (ARR + 1 = 100) = 1000Hz
PWM_SetCompare1(50); // 设置CCR的值,占空比Duty = CCR / (ARR + 1 = 100) = 50%
// 上面这里输出信号是1000Hz的PWM波
while (1)
{
OLED_ShowNum(1, 6, IC_GetFreq(), 5);
OLED_ShowNum(2, 6, IC_GetDuty(), 2);
}
}