这星期老师让给项目中添加一个检测输入信号频率的功能,用于矿下煤气浓度检测,于是搞了几天做成了一个样例,由于电路板的限制,用的是TIM3和TIM4。
这个程序最多支持8路不通频率信号的测量,由于有实际要求,我把测量的频率设定在1000~200Hz之间,当然测更高的频率也行,只是我没有测试过2000Hz以上的信号。
频率检测的原理
怎么编程需要看懂下面这张定时器输入捕获结构图,我只进行了最低限度的解释,要了解更多推荐看看野火的《零死角玩转STM32F103指南者》高级定时器那一章。
使用 STM32 一定要找到参考手册、数据手册或者其他参考书,不然寸步难行。
这里先说一下怎么进行信号捕获
0.TIMx一直在计数
1.信号通过 TIMx_CH1/2/3/4 流入
2.经过滤波器和边沿检测
3.信号进入捕获通道
4.经过预分频器
5.捕获寄存器在发生捕获时存储 CNT 的值,产生 CCxI 中断
首先借助于定时器的输入捕获的4个通道的边沿捕获功能,检测到上升沿后出发 CI 中断。
以 TIM3 的 CH1 为例,它检测到第一个上升沿后,我们用变量 cnt_val 记录一下 CNT(计数器) 的值。
当 CH1 遇到第二次遇到上升沿时,TIM3->CCR1 寄存器记录下此时 CNT 的值,我们用一个变量 ccr_val 记录一下。
然后退出中断, ( ccr_val - cnt_val ) 的值就是一个周期内 TIM3 计数的次数(忽略计数器更新),用变量 c_num 记录。接着用 TIM3 的计时频率 / c_num 可以得出频率的值。
从上面的图中可以看出,假如捕获信号的一个周期内定时器发生了更新,那这次采集就算失败,因为更新后CNT重新计数了。
还有一种情况是信号源突然掉线,所以需要定时进行在线检测。
目前我没想到其它的bug。
GPIO初始化
我用到了两个计时器,这里我只拿一个TIM3举例子,下面用到了很多宏定义,是为了方便我开关某些功能。
#define USE_TIM3 1
#define USE_TIM3_CH1 1
#define USE_TIM3_CH2 1
#define USE_TIM3_CH3 1
#define USE_TIM3_CH4 1
#define TIM3_IT_CCx /**/TIM_IT_CC1|TIM_IT_CC2|TIM_IT_CC3|TIM_IT_CC4/*注意要与上面的通道向对应*/
#define TIM3_GPIO_CLK RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOB
static void TIM3_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
//GPIO_PinRemapConfig(GPIO_FullRemap_TIM3, ENABLE);//TIM3 端口重映射,我用不上
//GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable , ENABLE); //禁止JTAG功能,把PB3,PB4作为普通IO口使用,我也用不上
RCC_APB2PeriphClockCmd(TIM3_GPIO_CLK,ENABLE);//开启对应GPIO的时钟,TIM3_GPIO_CLK是宏定义
#if USE_TIM3_CH1
//配置CH1
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
GPIO_InitStructure.GPIO_Pin = TIM3_CH1_PIN;
GPIO_Init(TIM3_CH1_PORT,&GPIO_InitStructure);
#endif
#if USE_TIM3_CH2
//配置CH2
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_InitStructure.GPIO_Pin = TIM3_CH2_PIN;
GPIO_Init(TIM3_CH2_PORT,&GPIO_InitStructure);
#endif
#if USE_TIM3_CH3
//配置CH3
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_InitStructure.GPIO_Pin = TIM3_CH3_PIN;
GPIO_Init(TIM3_CH3_PORT,&GPIO_InitStructure);
#endif
#if USE_TIM3_CH4
//配置CH4
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_InitStructure.GPIO_Pin = TIM3_CH4_PIN;
GPIO_Init(TIM3_CH4_PORT,&GPIO_InitStructure);
#endif
}
定时器初始化
同样,只拿一个TIM3举例
static void GENERAL_TIM3_Config(void)
{
//配置引脚,就是上面的那个函数
TIM3_GPIO_Config();
//配置时基
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
//初始化TIM3时钟72MHz
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
TIM_TimeBaseInitStructure.TIM_Prescaler = 72-1; //分频 72M/72 = 1MHz
TIM_TimeBaseInitStructure.TIM_Period = 0xffff-1; //计时周期65535 计时器的一个周期是(1s/1M)*65535 = 0.0656s即65ms
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; //配置为1分频
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;//配置为向上计数模式
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; //重复计数器,TIM3没有