- 输出比较,引脚是输出端口,根据CNT和CCR的大小关系来执行输出动作
- 输入捕获,引脚是输入端口,接收到输入信号执行CNT锁存到CCR的动作
这里设计了两套滤波和边沿检测电路 第一套电路经过滤波和极性选择得到TI1FP1 输入给通道1的后续电路 第二套电路 经过另一个滤波和极性选择 得到TI1FP2 输入给通道2的后续电路 同理 下面TI2信号进来 也经过两套滤波和极性选择得到TI2FP1输入通道1和TI2FP2输入通道2 其中TI2FP1输给上面 TI2FP2输入给下面 在这里两个信号进来可以选择各走各的 也可以选择进行一个交叉 可以进行交叉连接 例如CH1引脚输入给通道2 CH2引脚输入给通道1 进行交叉连接的目的是两个:1.第一个目的可以灵活切换后续捕获电路的输入 比如你一会儿想以CH1作为输入 一会儿想以CH2作为输入 这样就可以通过这个数据选择器灵活的进行选择 2.第二个目的 也是它交叉的主要目的 就是可以把一个引脚的输入同时映射到两个捕获单元 这也是PWMI模式的经典结构
输入信号进行滤波和极性选择后就来到了预分频器 每个通道各有一个预分频器 可以选择对前面的信号进行分频 分频之后的触发信号 就可以触发捕获电路进行工作了 每来一个触发信号 CNT的值就会向CCR转运一次 转运的同时会发生一个捕获事件 这个事件会在状态寄存器置标志位 同时也可以产生中断 如果需要在捕获的瞬间 处理一些事情的话 就可以开启这个捕获中断 比如可以配置上升沿触发捕获 每来一个上升沿 CNT转运到CCR一次 又因为CNT计数器是由内部的标准时钟驱动的 所以CNT的数值可以用来记录两个上升沿之间的时间间隔(周期) 再取倒数就是测周法测量的频率了 在每次捕获后将CNT清零(可以用主从触发模式来自动完成CNT清零) 这样下次上升沿再捕获的时候 取出的CNT才是两个上升沿的时间间隔
主从触发模式 即主模式、从模式和触发源选择三个功能的简称 主模式可以将定时器内部的信号映射到TRGO引脚 用于触发其他外设的操作 从模式可以接收其他外设或自身外设的一些信号 用于触发自己的一些操作(定时器的运行) 触发源选择 即选择从模式的触发信号源功能 也可以认为它是从模式的一部分
分频器选择不分频 当TI1FP1出现上升沿之后 CNT的当前记录值转运到CCR1里 同时触发源选择选中TI1FP1为触发信号 从模式选择复位操作 这样TI1FP1的上升沿也会通过上面这一路去触发CNT清零 当然这里会有个先后顺序 肯定是得先转运CNT的值到CCR里去 再触发从模式给CNT清零 或者是非阻塞的同时转移:CNT的值转移到CCR 同时0转移到CNT里面去 总之是先捕获 再清零)
- CNT的计数值是有上限的 由于ARR最大为65535 故CNT最大也只能计65535个数 如果信号频率太低 CNT的计数值可能会溢出
- 从模式的触发源选择中有TI1FP1和TI2FP2 但是没有TI3和TI4的信号 所以如果要使用从模式自动清零CNT 就必须使用通道1或通道2作为输入 对于通道3和通道4 就只能开启捕获中断 在中断中手动清0了(这样程序会处于频繁中断的状态 比较占用软件资源)
首先TI1FP1配置上升沿触发 触发捕获和清零CNT 正常的捕获周期 再来一个TI1FP2 配置为下降沿触发 通过交叉通道去触发通道2的捕获单元(最开始上升沿CCR1捕获同时清零CNT 之后CNT一直加加 然后在下降沿时刻触发CCR2捕获 这时CCR2的值就是CNT从上升沿到下降沿的计数值也就是高电平期间的计数值 CCR2捕获并不触发CNT清零 所以CNT继续加加 直到下一次上升沿 CCR1捕获周期并CNT清零 这样执行之后CCR1就是一整个周期的计数值 CCR2就是高电平期间的计数值 用CCR2/CCR1就是占空比 以上就是PWMI模式使用两个通道来捕获频率和占空比的思路 另外也可以两个通道同时捕获第一个引脚的输入 这样通道二的前面这一部分就没有用到行 当然也可以配置两个通道同时捕获第二个引脚的输入 这样我们就是使用TI2FP1和TI2FP2这两个引脚了 这两个输入可以灵活切换
void TIM_PrescalerConfig(TIM_TypeDef* TIMx, uint16_t Prescaler, uint16_t TIM_PSCReloadMode);
功能:
单独写入PSC
参数:
TIMx:其中x为1 ~ 17,表示选择TIM外设
Prescaler:写入的PSC的值
TIM_PSCReloadMode:指定的重装模式 可以是:TIM_PSCReloadMode_Update:在更新事件中重装预分频器、TIM_PSCReloadMode_Immediate:预分频器立即重装(立刻生效 可能会在值改变时产生切断波形的现象 若在更新事件生效 会有一个缓冲器 延迟参数的写入时间 保证每个周期完整)
返回值:
无
void TIM_ICInit(TIM_TypeDef* TIMx, TIM_ICInitTypeDef* TIM_ICInitStruct);
功能:
根据TIM_ICInitStruct中指定的参数初始化TIM外设
参数:
TIMx:其中x除6、7外可选1 ~ 17,用于选择TIM外设
TIM_ICInitStruct:指向TIM_ICInitTypeDef结构体的指针,该结构体包含指定TIM外设的配置信息
返回值:
无
//注意:输入捕获和输出比较都有四个通道 OCInit有4个 每个通道占一个函数 而IcIni中4个通道共用一个函数 在结构体中有一个参数 可以用来选择具体配置哪个通道
//与第三个函数类似 但只能配置一个通道 用于第一个代码工程
void TIM_PWMIConfig(TIM_TypeDef* TIMx, TIM_ICInitTypeDef* TIM_ICInitStruct);
功能:
根据TIM_ICInitStruct中指定的参数配置TIM外设以测量外部PWM信号
参数:
TIMx:其中x可以是1、2、3、4、5、8、9、12或15,选择TIM外设
TIM_ICInitStruct:指向TIM_ICInitTypeDef结构体的指针,该结构体包含指定TIM外设的配置信息
返回值:
无
//与第二个函数类似 但这里可以快速配置两个通道 用于第二个代码工程
void TIM_SelectInputTrigger(TIM_TypeDef* TIMx, uint16_t TIM_InputTriggerSource);
功能:
选择输入触发源TRGI
参数:
TIMx:其中x可以是1、2、3、4、5、8、9、12或15,选择TIM外设
TIM_InputTriggerSource:触发器输入源
可以是以下几种:TIM_TS_ITR0:内部触发器0
TIM_TS_ITR1:内部触发器1
TIM_TS_ITR2:内部触发器2
TIM_TS_ITR3:内部触发
TIM_TS_TI1F_ED: TI1边缘检测器
TIM_TS_TI1FP1:滤波定时器输入1
TIM_TS_TI2FP2:滤波定时器输入2
TIM_TS_ETRF:外部触发输入
返回值:
无
//对应主从触发模式图的触发源选择部分
void TIM_SelectOutputTrigger(TIM_TypeDef* TIMx, uint16_t TIM_TRGOSource);
功能:
选择输出触发源TRGO
参数:
TIMx:其中x可以是1、2、3、4、5、8、9、12或15,选择TIM外设
TIM_InputTriggerSource:输入触发器源
该参数可以是以下值之一:
所有TIMx
TIM_TRGOSource_Reset:在TIM_EGR寄存器的UG位被用作触发输出(TRGO)
TIM_TRGOSource_Enable:计数器启用CEN被用作触发输出(TRGO)
TIM_TRGOSource_Update:选择更新事件作为触发输出(TRGO)
除TIM6和TIM7外的所有TIMx
TIM_TRGOSource_OC1:当要设置CC1IF标志时,一旦捕获或比较匹配发生(TRGO),触发器输出发送一个正脉冲
TIM_TRGOSource_OC1Ref: OC1REF信号被用作触发输出(TRGO)
TIM_TRGOSource_OC2Ref: OC2REF信号被用作触发输出(TRGO)
TIM_TRGOSource_OC3Ref: OC3REF信号被用作触发输出(TRGO)
TIM_TRGOSource_OC4Ref: OC4REF信号被用作触发输出(TRGO)
返回值:
无
//对应主从触发输出图的主模式输出触发源选择
void TIM_SelectSlaveMode(TIM_TypeDef* TIMx, uint16_t TIM_SlaveMode);
功能:
选择TIMx从模式
参数:
TIMx:其中x可以是1、2、3、4、5、8、9、12或15,选择TIM外设
TIM_SlaveMode:定时器从模式
该参数可以是以下值之一:
TIM_SlaveMode_Reset:所选触发信号(TRGI)的上升沿重新初始化计数器并触发寄存器的更新
TIM_SlaveMode_Gated:当触发信号(TRGI)高时使能计数器
TIM_SlaveMode_Trigger:计数器从触发TRGI的上升沿开始
TIM_SlaveMode_External1:所选触发器(TRGI)的上升沿时钟计数器
返回值:
无
//对应主从触发模式图的从模式选择部分
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);
功能:
分别单独配置通道1、2、3、4的分频器
参数:
TIMx:通道1:其中x除6、7外可选1 ~ 17,用于选择TIM外设
通道2:其中x可以是1、2、3、4、5、8、9、12或15来选择TIM外设
通道3:其中x可以是1、2、3、4、5或8来选择TIM外设
通道4:其中x可以是1、2、3、4、5或8来选择TIM外设
TIM_ICPSC:指定输入寄存预分频器的新值
返回值:
无
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);
功能:
分别读取4个通道的CCR值
参数:
TIMx:通道1:其中x除6、7外可选1 ~ 17,用于选择TIM外设
通道2:其中x可以是1、2、3、4、5、8、9、12或15来选择TIM外设
通道3:其中x可以是1、2、3、4、5或8来选择TIM外设
通道4:其中x可以是1、2、3、4、5或8来选择TIM外设
返回值:
无
8.8和8.7中的函数是相互对应的 读写的都是CCR寄存器 输出比较模式下 CCR是只写的 要用SetComper写入 输入捕获模式下 CCR是只读的 要用GetCapture读出
#include "stm32f10x.h" // Device header
void PWM_Init(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);//使能TIM2的RCC时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//将GPIO口配置复用推挽输出 前面ppt中有图:普通开漏/推挽输出 引脚控制权来自输出数据寄存器 如果让定时器控制引脚 需要使用复用开漏/输出模式 在这里输出数据寄存器会被断开 输出控制权转移给片上外设 在这里片上外设是TIM2的CH1通道 PWM波形才能通过引脚输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
TIM_InternalClockConfig(TIM2);//时钟选择为RCC内部时钟
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;//指定时钟分频 这里的选择的是1分频
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;//配置计数模式 这里选择上拉计数模式
TIM_TimeBaseInitStructure.TIM_Period = 100 - 1;//周期配置 是ARR自动重装值 固定ARR+1为100 则CCR的值就是占空比的值
TIM_TimeBaseInitStructure.TIM_Prescaler = 720 - 1;//配置预分频器PCS的值 通过PSC来调节频率
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;//这里是重复计数器 是高级定时器才需要用到 而我们这里是通用定时器 随意直接给其配置0
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);
TIM_OCInitTypeDef TIM_OCInitStructure;
TIM_OCStructInit(&TIM_OCInitStructure);//给下面的结构体赋初始值
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
// TIM_OCInitStructure.TIM_Pulse = 50;//CCR 这里配置占空比为50% 频率为1KHz 分辨率为1%的PWM波形 通过计算可得CCR、PCS、ARR的值
TIM_OCInitStructure.TIM_Pulse = 0;//占空比配置0 在下面封装一个占空比变化函数
TIM_OC1Init(TIM2,&TIM_OCInitStructure);//输出比较初始化 需要初始化哪个通道就选哪个 这里选的是PA0口 对应第一个输出比较通道
TIM_Cmd(TIM2,ENABLE);
}
void PWM_SetCompare1(uint16_t Compare)
{
TIM_SetCompare1(TIM2,Compare);//设置CCR1的值
}
void PWM_SetPrescal(uint16_t Prescaler)
{
TIM_PrescalerConfig(TIM2,Prescaler,TIM_PSCReloadMode_Immediate);//设置PSC的值
}
#ifndef __PWM_H
#define __PWM_H
void PWM_Init(void);
void PWM_SetCompare1(uint16_t Compare);
void PWM_SetPrescal(uint16_t Prescaler);
#endif
#include "stm32f10x.h" // Device header
void IC_Init(void)
{
/*开启时钟*/
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//将PA6引脚初始化为上拉输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
/*配置时钟源*/
TIM_InternalClockConfig(TIM3);//选择TIM3为内部时钟,若不调用此函数,TIM默认也为内部时钟
/*时基单元初始化*/
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的值 设置最大是防止计数溢出
TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1;//标准频率fc=72M/72=1M
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStructure);
/*PWMI模式初始化*/
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;//这里配置的是分频器 不分频就是每次触发都有效 二分频就是每隔1秒有效1次 本次代码需要每次触发都有效 所以选择不分频
TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;//配置的是图中黄色的数据选择器 可以选择直连或者交叉 本次选择直连即可
TIM_ICInit(TIM3,&TIM_ICInitStructure);
/*选择触发源及从模式*/
TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1);//触发源选择TI1FP1
TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Reset);//从模式选择复位 即TI1产生上升沿时,会触发CNT归零
/*TIM使能*/
TIM_Cmd(TIM3,ENABLE);//使能TIM3,定时器开始运行
}
/**
* 函 数:获取输入捕获的频率
* 参 数:无
* 返 回 值:捕获得到的频率
*/
uint32_t IC_GetFreq(void)
{
return 1000000 / (TIM_GetCapture1(TIM3) + 1);//测周法得到频率fx = fc / N(N就是CCR 直接调用读取CCR值函数即可),这里不执行+1的操作也可
}
#ifndef __IC_H
#define __IC_H
void IC_Init(void);
uint32_t IC_GetFreq(void);
#endif
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "PWM.h"
#include "IC.h"
uint8_t i;
int main(void)
{
OLED_Init();
PWM_Init();
IC_Init();//输入捕获初始化
OLED_ShowString(1, 1, "Freq:00000Hz");//1行1列显示字符串Freq:00000Hz
PWM_SetPrescal(720 - 1);//PWM频率Freq = 72M / (PSC + 1) / 100 pwm.c中设置ARR+1为100 这里通过PSC改变调节频率 这里频率为1000
PWM_SetCompare1(50);//PWM占空比Duty = CCR / 100 设置占空比为50%
while (1)
{
OLED_ShowNum(1, 6, IC_GetFreq(), 5);//不断刷新显示输入捕获测得的频率
}
}
现象:PWM模块将待测信号输出的PA0口 PA0通过导线输出到PA6 PA6是TIM3的通道1 通道1通过输入捕获模块 测量得到频率
#include "stm32f10x.h" // Device header
void IC_Init(void)
{
/*开启时钟*/
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//将PA6引脚初始化为上拉输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;//这里TIM2要用来输出PWM 所以输入捕获的定时器需要换一个 暂时换到TIM3 由引脚定义表可以看到TIM3的通道1对应PA6 通道2对应PA7 通道3对应PB0 通道4对应PB1 这里选择TIM3的通道1 所以选择PA6引脚 这里需要配置上拉输入模式
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
/*配置时钟源*/
TIM_InternalClockConfig(TIM3);//选择TIM3为内部时钟,若不调用此函数,TIM默认也为内部时钟
/*时基单元初始化*/
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的值 设置最大是防止计数溢出
TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1;//标准频率fc=72M/72=1M
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStructure);
/*PWMI模式初始化*/
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;//这里配置的是分频器 不分频就是每次触发都有效 二分频就是每隔1秒有效1次 本次代码需要每次触发都有效 所以选择不分频
TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;//配置的是图中黄色的数据选择器 可以选择直连或者交叉 本次选择直连即可
TIM_PWMIConfig(TIM3,&TIM_ICInitStructure);//配置通道二参数 结构体中的参数都与通道一相反
/*选择触发源及从模式*/
TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1);//触发源选择TI1FP1
TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Reset);//从模式选择复位 即TI1产生上升沿时,会触发CNT归零
/*TIM使能*/
TIM_Cmd(TIM3,ENABLE);//使能TIM3,定时器开始运行
}
/**
* 函 数:获取输入捕获的频率
* 参 数:无
* 返 回 值:捕获得到的频率
*/
uint32_t IC_GetFreq(void)
{
return 1000000 / (TIM_GetCapture1(TIM3) + 1);//测周法得到频率fx = fc / N(N就是CCR 直接调用读取CCR值函数即可),这里不执行+1的操作也可
}
/**
* 函 数:获取输入捕获的占空比
* 参 数:无
* 返 回 值:捕获得到的占空比
*/
uint32_t IC_GetDuty(void)
{
return (TIM_GetCapture2(TIM3) + 1) * 100 / (TIM_GetCapture1(TIM3) + 1); //占空比Duty = CCR2 / CCR1 * 100,这里不执行+1的操作也可
}
#ifndef __IC_H
#define __IC_H
void IC_Init(void);
uint32_t IC_GetFreq(void);
uint32_t IC_GetDuty(void);
#endif
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "PWM.h"
#include "IC.h"
uint8_t i;
int main(void)
{
OLED_Init();
PWM_Init();
IC_Init();//输入捕获初始化
OLED_ShowString(1, 1, "Freq:00000Hz");//1行1列显示字符串Freq:00000Hz
OLED_ShowString(2, 1, "Duty:00%");//2行1列显示字符串Duty:00%
PWM_SetPrescal(720 - 1);//PWM频率Freq = 72M / (PSC + 1) / 100 pwm.c中设置ARR+1为100 这里通过PSC改变调节频率 这里频率为1000
PWM_SetCompare1(50);//PWM占空比Duty = CCR / 100 设置占空比为50%
while (1)
{
OLED_ShowNum(1, 6, IC_GetFreq(), 5);//不断刷新显示输入捕获测得的频率
OLED_ShowNum(2, 6, IC_GetDuty(), 2); //不断刷新显示输入捕获测得的占空比
}
}
现象:通过调节main.c中的PWM频率和占空比 可以让OLED上捕获到的频率和占空比值随之改变
工程实现思路: