江科大STM32TIM输入捕获模式PWMI模式测频率
【江科大STM32】TIM输入捕获模式&PWMI模式测频率
一、输入捕获测频率
接线图:
测信号的输入引脚为PA6,信号从PA6进来,待测的PWM信号也是STM32自己生成的,输出引脚是PA0,所以接线这里直接用一根线将PA0引到PA6就可以了。 如果有信号发生器的话,也可以设置成方波信号输出,高电平设置成3.3v,低电平0v,然后直接接到PA6,另一个就是共地。
PWM初始化模块:
目前这里我们要借用一下之前写过的PWM模块代码,以便生成待测信号,所以这里程序直接复制,之前的PWM驱动LED呼吸灯工程,在这个工程基础上写。然后在这个PWM模块代码进行改进,目前这个代码逻辑是,初始化TIM2的通道1,产生一个PWM的波形,输出引脚是PA0,然后通过Set_Compare函数,可以调节CCR1的寄存器的值,从而控制PWM的占空比,但是目前这个PWM频率是在初始化已经写好的了,是固定的,操作起来不太方便。所以我们要在最后再加一个函数,用来便捷地调节PWM频率。
那这里如何调节PWM频率?
通过公式,我们知道PWM频率=更新频率=72M/(PSC+1/(ARR+1),所以PSC和ARR都可以调节频率,但是占空比=CCR/(ARR+1),所以通过ARR调节频率,同时还会影响到占空比,而通过PSC调节频率,不会影响占空比,显然比较方便。所以这里我们直接固定ARR为100-1,通过调节PSC来改变PWM频率,这里的ARR为100-1,CCR的数值直接就是占空比。
这里实际使用也是有技巧的,一般可以根据分辨率的要求,先确定好ARR值,比如分辨率[1/(ARR+1)],1%就足够了。那ARR就是给100-1,这样PSC决定频率,CCR决定占空比。如果想要更高的分辨率,比如0.1%,那ARR就先固定1000-1,这样频率就是72M/预分频/1000,占空比就是CCR/1000,这样也好算。
然后ARR我们固定给100-1,初始化操作的PSC就先不管,后面再写一个函数,在初始化之后单独修改PSC。
void PWM_SetPrescaler(uint16_t Prescaler) //配置TIMx预调度器。
{
TIM_PrescalerConfig(TIM2,Prescaler,TIM_PSCReloadMode_Immediate);//Prescaler立即被加载
}
代码详解:
void TIM_PrescalerConfig(TIM_TypeDef* TIMx, uint16_t Prescaler, uint16_t TIM_PSCReloadMode)//配置TIMx预调度器
参数 | 说明 |
TIMx | 其中x为1 ~ 17,选择TIM外设 |
Prescaler | 指定预Prescaler寄存器值 |
TIM_PSCReloadMode | 指定TIM预分频器的重装模式。该参数可以是以下值之一:TIM_PSCReloadMode_Update:在更新事件中加载precaler;TIM_PSCReloadMode_Immediate:立即加载precaler |
TIM_PSCReloadMode的两个选择参数说白了还是影子寄存器的预装载问题,就是你写入的值是立刻生效还是在更新事件中生效。
- 立刻生效:可能在值发生改变时产生切断波形的现象,比如PWM一个周期刚过去一半,立刻生效了,那就立刻切断当前波形,开始新的一个周期。在频率变化时,这里会出现一个不完整的周期。
- 更新事件生效:就是会有一个缓存器,延迟参数的写入时间,等一个周期结束了,在更新事件时,再统一改变参数, 保证每个周期的完整。
在这里我们选择哪个参数都无所谓,我们要求不高,哪个都可以,那这里就选择立刻生效吧。
主函数这里调用之后就生成一个频率为 1KHz,占空比为50%的信号了。
int main(void)
{
OLED_Init();
PWM_Init();
PWM_SetPrescaler(720 - 1); //频率=72M/(PSC - 1)/(ARR - 1) 这里(ARR - 1)=100
PWM_SetCompare1(50); //Duty= CCR/(ARR - 1)
while(1)
{
}
}
输入捕获初始化:
这里因为是自己测自己,所以还要建一个输入捕获模块代码,测一下PA0口的频率和占空比。
步骤:
①RCC开启时钟(把要用的TIM外设和 外设时钟都打开)
②GPIO初始化,把GPIO配置为输入模式 ,一般选择上拉或者浮空输入模式
③配置时基单元,让CNT计数器在内部时钟驱动下自增运行
④配置输入捕获单元,包括滤波器、极性选择、直连通道还是交叉通道、分频器这些参数,用一个结构体就可以配置
⑤选择从模式的触发源触发源为TF1FP1,这里调用一个库函数给一个参数即可
⑥选择触发之后执行的操作
⑦开启定时器
代码:
这里的 时钟选择 ,要选择TIM3,也是APB1外设时钟,因为PWM模块还需要TIM2输出PWM,所以要开启TIM3时钟。GPIO初始化需要查看引脚定义表, TIM3通道1对应的是PA6引脚 ,如果选择其它定时器或者其它通道,那这个引脚就需要根据引脚定义表改变
这里的 TIM_Prescaler值 决定了测周法的标准频率Fc,72M/预分频就是就计数器自增的频率,就是计数器标准频率,这个需要根据你信号频率的分布范围来调整, 这里暂时先给72-1,这样标准频率就是72M/72=1MHz,这样方便计算。
void TIM_ICInit(TIM_TypeDef TIMx, TIM_ICInitTypeDef TIM_ICInitStruct)//根据指定初始化TIM外设TIM_ICInitStruct中的参数**
输入捕获初始化结构定义
参数 | 说明 |
TIM_Channel | 指定TIM通道(1~4通道) |
TIM_ICPolarity | 极性选择,指定输入信号的活动边缘(上升、下降、双边沿触发) |
TIM_ICSelection | 指定输入(直连或者交叉输入) |
TIM_ICPrescaler | 指定输入捕获预分频(1、2、4、8分频,1分频就是不分频) |
TIM_ICFilter | 指定输入捕获过滤器,取值范围为0x0 ~ 0xF之间的数字,数值越大,滤波效果越好 |
滤波器和分频器的区别:
虽然两个都是计次的东西,但是滤波器计次,并不会改变信号的原有频率,一般滤波器的采样频率都会远高于信号频率,所以它只会滤除高频噪声,使信号更加平滑,1KHz滤波之后仍然是1KHz,信号频率不会发生变化。而分频器就是对信号本身进行计次,会改变频率,1KHz,二分频之后就是500Hz,四分频之后就是250Hz。
对应步骤⑤ :
void TIM_SelectInputTrigger(TIM_TypeDef* TIMx, uint16_t TIM_InputTriggerSource)//选择输入触发器源
参数 | 说明 |
TIMx | 其中x可以是1、2、3、4、5、8、9、12或15来选择TIM外设。 |
TIM_InputTriggerSource | 输入触发器源。 |
TIM_InputTriggerSource参数选择:下面两图对应
对应步骤⑥
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参数选择:
最后,启动定时器后,CNT就会在内部时钟的驱动下不断自增,即使信号没有过来它也会自增。一直自增也没有关系,因为在有信号过来的时候,它就会在从模式下自动清零,不会影响测量。初始化完成后,整个电路就能实现全自动测量了。
void IC_Init(void)
{
/*开启时钟*/
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //开启TIM3的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA6引脚初始化为上拉输入
/*配置时钟源*/
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; //预分频器,即PSC的值
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; //重复计数器,高级定时器才会用到
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure); //将结构体变量交给TIM_TimeBaseInit,配置TIM3的时基单元
/*输入捕获初始化*/
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); //将结构体变量交给TIM_ICInit,配置TIM3的输入捕获通道
/*选择触发源及从模式*/
TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1); //触发源选择TI1FP1
TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Reset); //从模式选择复位
//即TI1产生上升沿时,会触发CNT归零
/*TIM使能*/
TIM_Cmd(TIM3, ENABLE); //使能TIM3,定时器开始运行
}
注意:
void TIM_OC1Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
void TIM_OC2Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
void TIM_OC3Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
void TIM_OC4Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
void TIM_ICInit(TIM_TypeDef* TIMx, TIM_ICInitTypeDef* TIM_ICInitStruct);
这里输出比较和输入捕获都有四个通道,OCInit四个通道,每个通道占用一个函数,而ICInit是四个通道共用一个函数的,在结构体会有额外一个参数选择哪个通道。
- void TIM_SetCompare1(TIM_TypeDef* TIMx, uint16_t Compare1);
- void TIM_SetCompare2(TIM_TypeDef* TIMx, uint16_t Compare2);
- void TIM_SetCompare3(TIM_TypeDef* TIMx, uint16_t Compare3);
- void TIM_SetCompare4(TIM_TypeDef* TIMx, uint16_t Compare4);
- uint16_t TIM_GetCapture1(TIM_TypeDef* TIMx);//获取TIMx输入捕获1值。
- uint16_t TIM_GetCapture2(TIM_TypeDef* TIMx);//获取TIMx输入捕获2值。
- uint16_t TIM_GetCapture3(TIM_TypeDef* TIMx);//获取TIMx输入捕获3值。
- uint16_t TIM_GetCapture4(TIM_TypeDef* TIMx); //获取TIMx输入捕获4值。
这8个函数都是读取各个通道的CCR值,是相对应的,输出比较模式下,CCR是只写的,用TIM_SetCompare写入;输入捕获模式下,CCR是只读的,用TIM_GetCapture读出。
返回值:各Capture Compare 寄存器值。
查看频率读取CCR(频率)
这里需要执行公式Fx=Fc/N,之前说Fc=72M/(PSC+1),PSC=72-1,所以72M/72=1MHz,然后除以N,N就是读取CCR的值,用uint16_t TIM_GetCapture1(TIM_TypeDef* TIMx)函数读取
uint32_t IC_GetFreq(void)
{
return 1000000 / (TIM_GetCapture1(TIM3) + 1); //测周法得到频率fx = fc / N,这里不执行+1的操作也可
}
主函数:
int main(void)
{
OLED_Init();
PWM_Init();
IC_Init();
OLED_ShowString(1,1,"Freq:00000Hz");
/*使用PWM模块提供输入捕获的测试信号:将待测信号传给PA0,PA0又通过导线输入到PA6,PA6是
TIM3的通道1,通道1通过输入捕获模块,测得频率*/
PWM_SetPrescaler(720 - 1); //频率=72M/(PSC - 1)/(ARR - 1) 这里(ARR - 1)=100
PWM_SetCompare1(50); //Duty= CCR/(ARR - 1)
while(1)
{
OLED_ShowNum(1,6,IC_GetFreq(),5);//不断刷新显示输入捕获测得的频率
}
}
二、PWMI模式测频率&占空比
这里输入捕获代码部分直接对上一个代码进行升级就可以了。配置成两个通道同时捕获一个引脚
这里输入捕获有两种配置方法:效果一样
1、直接复制多一份通道初始化
2、 使用ST公司封装好的函数
void TIM_PWMIConfig(TIM_TypeDef* TIMx, TIM_ICInitTypeDef* TIM_ICInitStruct)//根据指定配置TIM外设TIM_ICInitStruct中的参数来测量外部PWM信号
作用:这个函数你只要传入一个通道的参数,在函数里它会自动把剩下一个通道初始化成相反的配置,比如这里传入通道1,直连,上升沿,函数就会顺带配置通道2,交叉,下降;传入通道2,直连,上升沿,函数就会顺带配置通道1,交叉,下降沿。这个函数只能传入通道1和通道2,不能传通道3和通道4。
获取占空比函数:
根据上节课 PWMI 分析 ,高电平的计数值存在CCR2里,整个周期的计数值存在CCR1里,用CCR2/CCR1就可以得到占空比了。
/**
* 函 数:获取输入捕获的占空比
* 参 数:无
* 返 回 值:捕获得到的占空比
*/
uint32_t IC_GetDuty(void)
{
return (TIM_GetCapture2(TIM3) + 1) * 100 / (TIM_GetCapture1(TIM3) + 1); //占空比Duty = CCR2 / CCR1 * 100,这里不执行+1的操作也可
}
主函数:
int main(void)
{
/*模块初始化*/
OLED_Init(); //OLED初始化
PWM_Init(); //PWM初始化
IC_Init(); //输入捕获初始化
/*显示静态字符串*/
OLED_ShowString(1, 1, "Freq:00000Hz"); //1行1列显示字符串Freq:00000Hz
OLED_ShowString(2, 1, "Duty:00%"); //2行1列显示字符串Duty:00%
/*使用PWM模块提供输入捕获的测试信号*/
PWM_SetPrescaler(720 - 1); //PWM频率Freq = 72M / (PSC + 1) / 100
PWM_SetCompare1(50); //PWM占空比Duty = CCR / 100
while (1)
{
OLED_ShowNum(1, 6, IC_GetFreq(), 5); //不断刷新显示输入捕获测得的频率
OLED_ShowNum(2, 6, IC_GetDuty(), 2); //不断刷新显示输入捕获测得的占空比
}
}
测频率性能:
误差分析: