STM32输入捕获(IC)学习笔记(测周法和测频法的实现)
所以最终得到的结果就是程序运行到TIM_SetCounter(TIM3, 0)语言,这时的CNT的值就是我们需要的PWM的值,而且最奇怪的是你在中断的前段无论怎么延时都不能改变到达该语句使CNT里的值,笔者也改变过TIM4的中断时间比如0.1s,中断里的cnt的值也相应的缩小了10倍,改变PWM的输入波形结果都是获得了正确的波形。它与CNT的结果就差1.// TI1作为触发源。当然笔者依然观察到了
资料来自江协科技
分享几个学习过程中的错误
这篇最主要的是一种测频法的实现,而且是一种奇怪的实现方式,笔者到目前还没搞清楚它是怎么出现这种情况的。
1:调试过程中直接注释内部时钟中断的内容,但是没有在主函数中把中断初始化语句注释掉,导致程序出现异常,由于中断标志位一直没有清除,因此一直在访问中断。这个问题真的要特别注意,很容易忘记出错。
2:在输入捕获的时候,在动态显示占空比的时候发现它好像不显示偶数位的占空比,即是1%,3%,5%这样显示就是不显示偶数位的占空比一开始笔者觉得很奇怪,而且显示间隔好像也不是中断设置的刷新间隔,然后在IC分频系数选择二分频结果都能显示。
当然如果你用江协科技的源码是不会有这个问题的,它的源码
uint8_t IC_GetDuty(void)
{
return ((TIM_GetCapture2( TIM3)+1)*100/(TIM_GetCapture1( TIM3) +1));
}
CCR1和CCR2都加了1就没有这个问题。
一开始的时候没有去看CCR里面的值,主要是Debug不太熟练,最后还是看CCR里面的值解决这个问题。
在不分频的时候CCR2里的值0,10,19,30,39,50......,CCR1中的值是999
笔者之钱前的代码
uint8_t IC_GetDuty(void)
{
return (TIM_GetCapture2( TIM3)*100/(TIM_GetCapture1( TIM3) );
}
所以计算的结果就是:0% 1% 1.9% 3% 3.9% 5%,1.9%它是显示1%,3.9%它是显示3%,因此它的显示结果就变成之前笔者描述的,只显示奇数的百分比,不显示偶数的百分比。它在捕捉偶数位占空比的时候都少捕获了一个周期的信号。
主函数代码(测周法)
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "PWM.h"
#include "Timer.h"
#include "IC.h"
#include "OLED.h"
int main(void)
{
PWM_Init();
OLED_Init();
IC_Init();
Timer_Init();
/*显示静态字符串*/
OLED_ShowString(1, 1, "Freq:00000Hz"); //1行1列显示字符串Freq:00000Hz
OLED_ShowString(2, 1, "Duty:00%");
TIM2_SetCompare(50);//PWM设置占空比
TIM2_SetPrescaler(720-1); //频率设置 72MHZ/(pres+1)/(ARR+1)
while(1)
{
OLED_ShowNum(1, 6, IC_GetFreq(), 5); //不断刷新显示输入捕获测得的频率
OLED_ShowNum(2, 6, IC_GetDuty(), 2); //不断刷新显示输入捕获测得的占空比
}
}
/*占空比调整*/
void TIM2_IRQHandler(void)
{
static uint8_t Index = 1;//选择模式占空比自增或者自减
static uint16_t i = 0;//选择CRR的值
static uint16_t k = 0;//每500次调整一次占空比中断时间1ms
if( TIM_GetITStatus( TIM2, TIM_IT_Update) == SET)
{
k++;
if(k >= 500)
{
k = 0;
switch(Index)
{
case 1: TIM2_SetCompare(i);i++; if(i>=100) {Index = 2;} break;
case 2: TIM2_SetCompare(i);i--; if(i<= 0) {Index = 1;} break;
default: break;
}
}
}
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}
IC.C
#include "stm32f10x.h" // Device header
void IC_Init(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);//TIM2外设时钟使能
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE );//CPIOA外设时钟使能
//GPIO_StructInit(GPIO_InitTypeDef* GPIO_InitStruct);
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU ;
GPIO_InitStruct.GPIO_Pin =GPIO_Pin_6 ; //PA6通道
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz ;
GPIO_Init( GPIOA,&GPIO_InitStruct );
TIM_InternalClockConfig(TIM3); //选择内部时钟TIM3
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1 ;//不分频
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up ;
TIM_TimeBaseInitStructure.TIM_Period = 65535; //ARR
TIM_TimeBaseInitStructure.TIM_Prescaler = 72-1 ;//
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0x00; //高级计数器使用
TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStructure);//时基使能
TIM_ICInitTypeDef TIM_ICInitStruct;
TIM_ICInitStruct.TIM_Channel = TIM_Channel_1 ;
TIM_ICInitStruct.TIM_ICFilter = 0x0F ;
TIM_ICInitStruct.TIM_ICPolarity = TIM_ICPolarity_Rising ;
TIM_ICInitStruct.TIM_ICPrescaler = TIM_ICPSC_DIV4;
TIM_ICInitStruct.TIM_ICSelection = TIM_ICSelection_DirectTI ;
TIM_ICInit(TIM3, &TIM_ICInitStruct);
TIM_PWMIConfig( TIM3, &TIM_ICInitStruct);
TIM_SelectInputTrigger( TIM3, TIM_TS_TI1FP1);
TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Reset);
TIM_Cmd( TIM3, ENABLE);//使能TIM2
}
void TIM3_SetCompare(uint16_t i)
{
TIM_SetCompare1( TIM3, i);
}
uint32_t IC_GetFreq(void)
{
return 1000000 / (TIM_GetCapture1(TIM3) + 1); //测周法得到频率fx = fc / N,这里不执行+1的操作也可
}
uint8_t IC_GetDuty(void)
{
return ((TIM_GetCapture2( TIM3)+1)*100/(TIM_GetCapture1( TIM3) +1));
//return (TIM_GetCapture2( TIM3)*100/(TIM_GetCapture1( TIM3) +1));
}
测周法它采用的是输入捕获


奇怪的测频法
先上代码
main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "PWM.h"
//#include "Timer.h"
#include "IC.h"
#include "OLED.h"
#include "ICFMM.h"
#define TIM3_CNT (*(volatile uint32_t *)(TIM3_BASE + 0x24))
uint32_t frequency = 10;
static uint32_t current_count = 0;
int main(void)
{
GPIO_TIM3_Init();
TIM3_Counter_Init();
TIM4_Init();
PWM_Init();
OLED_Init();
/*显示静态字符串*/
OLED_ShowString(1, 1, "Freq:00000Hz"); //1行1列显示字符串Freq:00000Hz
OLED_ShowString(2, 1, "CNT3:00000Hz"); //1行1列显示字符串Freq:00000Hz
// OLED_ShowString(2, 1, "Duty:00%");
// TIM2_SetCompare(30);//PWM设置占空比
TIM2_SetPrescaler(360-1); //频率设置 72MHZ/(pres+1)/(ARR+1)
while(1)
{
OLED_ShowNum(1, 6,frequency , 5); //不断刷新显示输入捕获测得的频率
// OLED_ShowNum(2, 6, IC_GetDuty(), 2); //不断刷新显示输入捕获测得的频率
OLED_ShowNum(2, 6, current_count , 5);
}
}
void TIM4_IRQHandler(void)
{
static uint16_t k = 50000;
if (TIM_GetITStatus(TIM4, TIM_IT_Update) != RESET) {
// 读取TIM3的计数值
// Delay_ms(50);
//
// while(k--);
// k = 50000;
frequency = TIM_GetCounter(TIM3);
// while(k--);
// k = 50000;
// frequency = TIM_GetCapture1(TIM3);
// 重置TIM3计数器
TIM_SetCounter(TIM3, 0);
// while(k--);
// k = 50000;
// current_count = TIM3_CNT;
//TIM3->CNT = 0;
}
// 清除中断标志
TIM_ClearITPendingBit(TIM4, TIM_IT_Update);
}
外部时钟模式1的计数
ICFMM.c
#include "stm32f10x.h" // Device header
void GPIO_TIM3_Init(void) {
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; // 浮空输入
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
void TIM3_Counter_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
// TIM_ICInitTypeDef TIM_ICInitStructure;
// 使能时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
// 配置PA6为TIM3_CH1输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; // 浮空输入
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// TIM3基础配置
TIM_TimeBaseStructure.TIM_Period = 0xFFFF; // 计数器最大值65535
TIM_TimeBaseStructure.TIM_Prescaler = 0; // 无预分频
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
// // 配置外部时钟模式1(TI1上升沿触发)
// TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;
// TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;
// TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
// TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
// TIM_ICInitStructure.TIM_ICFilter = 0x0;
// TIM_ICInit(TIM3, &TIM_ICInitStructure);
// 配置从模式为外部时钟模式1
TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_External1);
TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1); // TI1作为触发源
// 启动TIM3
TIM_Cmd(TIM3, ENABLE);
}
void TIM4_Init(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
// TIM4配置为1秒中断(系统时钟72MHz)
TIM_TimeBaseStructure.TIM_Period = 10000 - 1; // 自动重装载值
TIM_TimeBaseStructure.TIM_Prescaler = 7200 - 1; // 预分频7200,得到10kHz时钟
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure);
// 使能TIM4更新中断
TIM_ITConfig(TIM4, TIM_IT_Update, ENABLE);
// 配置中断优先级
NVIC_InitStructure.NVIC_IRQChannel = TIM4_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
// 启动TIM2
TIM_Cmd(TIM4, ENABLE);
}
先说结论笔者试了几次能够正确的测得输入的PWM波的频率
程序其它模块来自江协科技的代码,我在主函数的中断里留了很多注释语句都是笔者调试用的,这个测频法能实现的核心就是 TIM_SetCounter(TIM3, 0);(用寄存器的方式也可以TIM3->CNT = 0;)这个清零操作,该怎么解释这种奇怪的现象呢?
比如我们输入1000HZ的波形,我们用了Tim4的定时中断1s,TIM3的1通道(PA6)是波形输入口,按照逻辑TIM4和TIM3的定时器应该同时使能同时计数,最后进入TIM4的中断,读取TIM3的cnt的值,因为是1s中断,cnt的值就是PA6输入PWM的频率。进入中断后读取CNT的值(TIM3),然后清零,最后看看误差是多少给个时间补偿。
但是结果并不需要这样,首先并没有同时使能两个定时器有些时间误差单这个关系不大,最主要的是你这么操作
void TIM4_IRQHandler(void)
{
static uint16_t k = 50000;
if (TIM_GetITStatus(TIM4, TIM_IT_Update) != RESET) {
这里添加延时50ms的语句
current_count = TIM3_CNT;
frequency = TIM_GetCounter(TIM3);
TIM_SetCounter(TIM3, 0);
//TIM3->CNT = 0;
}
// 清除中断标志
TIM_ClearITPendingBit(TIM4, TIM_IT_Update);
}
只要 frequency = TIM_GetCounter(TIM3);语句是在TIM_SetCounter(TIM3, 0);语句的上面并且中间没有插入延时语句, frequency 就能得到正确的值,无论前面是否有延时程序。
一开始笔者以为是因为进入TIM4中断后CNT(TIM3)会停止计数,然后并不是CNT并不会停止计数。
所以怎么看这个程序就很奇怪,当然应该是笔者没掌握工作逻辑。

如果你这两句之间插入延时程序比如50ms,PWM输入是1000HZ,那么现在frequency的值就是950.
当然如果放在TIM_SetCounter(TIM3, 0);后面,cnt的值就是就会使一个很小的的值,从零开始计数。
所以最终得到的结果就是程序运行到TIM_SetCounter(TIM3, 0)语言,这时的CNT的值就是我们需要的PWM的值,而且最奇怪的是你在中断的前段无论怎么延时都不能改变到达该语句使CNT里的值,笔者也改变过TIM4的中断时间比如0.1s,中断里的cnt的值也相应的缩小了10倍,改变PWM的输入波形结果都是获得了正确的波形。
// 配置从模式为外部时钟模式1
TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_External1);
TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1); // TI1作为触发源
因此这个工作模式就很奇怪了,
当然笔者依然观察到了一个现象就是第一次进入中断的时候CNT的值不是我们输入的PWM的波形的值,第二次往后都是正确的值。
第二个现象是CRR里的值会比CNT里的值小1,,但不是所有的都小1,


这个值来着测频法的数据,前提是把ICFMM.c里面注释掉的输入捕获的语句从新使用起来,不注释这份程序也可以用的,而且可以使用frequency = TIM_GetCapture1(TIM3);它与CNT的结果就差1.
至此本案结束,如果有小伙伴知道这个原因的,留个言交流一下。
更多推荐



所有评论(0)