完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
|
|
相关推荐
1个回答
|
|
这星期老师让给项目中添加一个检测输入信号频率的功能,用于矿下煤气浓度检测,于是搞了几天做成了一个样例,由于电路板的限制,用的是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没有 TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStructure); //配置TIM3 TIM_ClearFlag(TIM3, TIM_FLAG_Update); //清除中断标志位 } 配置输入捕获 这里需要注意 TIM_ICInitStructure.TIM_Channel 不能像配置GPIO一样多个值相或,如 GPIO_Pin_6|GPIO_Pin_7|GPIO_Pin_8,TIM_Channel只能一次配置一个,我踩过的坑希望其他人别去踩。 // 测量的起始边沿 #define GENERAL_TIM_STRAT_ICPolarity TIM_ICPolarity_Rising static void TIM3_IC_Config(void) { TIM_ICInitTypeDef TIM_ICInitStructure; #if USE_TIM3_CH1 TIM_ICInitStructure.TIM_Channel = TIM_Channel_1; //配置捕获通道,!!!!"这个参数不支持多参数相或"!!!!! TIM_ICInitStructure.TIM_ICFilter = 0x0f; //设置滤波,参考手册p215 TIM_ICInitStructure.TIM_ICPolarity = GENERAL_TIM_STRAT_ICPolarity;//选择IC触发边沿,上升沿还是下降沿,这里用的宏定义 TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; //预分频数1 TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;//直接输入 TIM_ICInit(TIM3, &TIM_ICInitStructure); //配置TIM3的输入捕获功能 #endif #if USE_TIM3_CH2 TIM_ICInitStructure.TIM_Channel = TIM_Channel_2; TIM_ICInitStructure.TIM_ICFilter = 0x0f; TIM_ICInitStructure.TIM_ICPolarity = GENERAL_TIM_STRAT_ICPolarity; TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; TIM_ICInit(TIM3, &TIM_ICInitStructure); #endif #if USE_TIM3_CH3 TIM_ICInitStructure.TIM_Channel = TIM_Channel_3; TIM_ICInitStructure.TIM_ICFilter = 0x0f; TIM_ICInitStructure.TIM_ICPolarity = GENERAL_TIM_STRAT_ICPolarity; TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; TIM_ICInit(TIM3, &TIM_ICInitStructure); #endif #if USE_TIM3_CH4 TIM_ICInitStructure.TIM_Channel = TIM_Channel_4; TIM_ICInitStructure.TIM_ICFilter = 0x0f; TIM_ICInitStructure.TIM_ICPolarity = GENERAL_TIM_STRAT_ICPolarity; TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; TIM_ICInit(TIM3, &TIM_ICInitStructure); #endif // 开启更新和捕获中断 TIM_ITConfig (TIM3, TIM_IT_Update | TIM3_IT_CCx, ENABLE ); TIM_ClearFlag(TIM3, TIM_FLAG_Update|TIM3_IT_CCx);//清中断标志 // 使能计数器 TIM_Cmd(TIM3, ENABLE); } 配置 NVIC 这里还是只贴出了TIM3的 static void GENERAL_TIM_NVIC_Config(void) { NVIC_InitTypeDef NVIC_InitStructure; #if USE_TIM3 // 设置中断组为 1 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1); // 设置中断来源 NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn ; // 设置主优先级为 1 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; // 设置抢占优先级为 3 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //中断通道使能 NVIC_Init(&NVIC_InitStructure); #endif } 自定义一个保存信息的结构体 typedef struct { uint8_t Time_OutFlag; //捕获超时标志 uint8_t Capture_FinishFlag; // 捕获结束标志位 uint8_t Capture_StartFlag; // 捕获开始标志位 uint16_t Capture_CNTValue; // 第一次捕获边沿时计数器的值 uint16_t Capture_CcrValue; // 捕获寄存器的值 }TIM_ICUserValueType; 中断处理函数 extern TIM_ICUserValueType TIM_ICUserValue[8]; void TIM3_IRQHandler(void) { if ( TIM_GetITStatus ( TIM3, TIM_IT_Update) != RESET ) {//假定超时 TIM_ICUserValue[0].Time_OutFlag = 1; TIM_ICUserValue[1].Time_OutFlag = 1; TIM_ICUserValue[2].Time_OutFlag = 1; TIM_ICUserValue[3].Time_OutFlag = 1; // 采集波形的一个周期内发生了寄存器溢出,放弃本次采集 TIM_ICUserValue[0].Capture_StartFlag = 0; TIM_ICUserValue[0].Capture_FinishFlag = 0; TIM_ICUserValue[1].Capture_StartFlag = 0; TIM_ICUserValue[1].Capture_FinishFlag = 0; TIM_ICUserValue[2].Capture_StartFlag = 0; TIM_ICUserValue[2].Capture_FinishFlag = 0; TIM_ICUserValue[3].Capture_StartFlag = 0; TIM_ICUserValue[3].Capture_FinishFlag = 0; //清除中断标志 TIM_ClearITPendingBit ( TIM3, TIM_FLAG_Update ); } #if USE_TIM3_CH1 // TIM3_CH1上升沿捕获中断 if ( TIM_GetITStatus (TIM3, TIM_IT_CC1 ) != RESET) { // 第一次捕获 if ( TIM_ICUserValue[0].Capture_StartFlag == 0 ) { //记录首次捕获时计数器的值 TIM_ICUserValue[0].Capture_CNTValue = TIM3->CNT; // 存捕获比较寄存器的值的变量的值清 0 TIM_ICUserValue[0].Capture_CcrValue = 0; // 开始捕获标准置 1 TIM_ICUserValue[0].Capture_StartFlag = 1; } // 下降沿捕获中断 else { // 第二次捕获 // 获取捕获比较寄存器的值减去上一次计数器的值,这个值就是捕获到的高电平的时间的值 TIM_ICUserValue[0].Capture_CcrValue = TIM_GetCapture1(TIM3)-TIM_ICUserValue[0].Capture_CNTValue; // 开始捕获标志清 0 TIM_ICUserValue[0].Capture_StartFlag = 0; // 捕获完成标志置 1 TIM_ICUserValue[0].Capture_FinishFlag = 1; //超时标志清除 TIM_ICUserValue[0].Time_OutFlag = 0; } TIM_ClearITPendingBit (TIM3,TIM_IT_CC1); } #endif #if USE_TIM3_CH2 // TIM3_CH2上升沿捕获中断 if ( TIM_GetITStatus (TIM3, TIM_IT_CC2 ) != RESET) { if ( TIM_ICUserValue[1].Capture_StartFlag == 0 ) { TIM_ICUserValue[1].Capture_CNTValue = TIM3->CNT; TIM_ICUserValue[1].Capture_CcrValue = 0; TIM_ICUserValue[1].Capture_StartFlag = 1; } else { TIM_ICUserValue[1].Capture_CcrValue = TIM_GetCapture2(TIM3)-TIM_ICUserValue[1].Capture_CNTValue; TIM_ICUserValue[1].Capture_StartFlag = 0; TIM_ICUserValue[1].Capture_FinishFlag = 1; TIM_ICUserValue[1].Time_OutFlag = 0; } TIM_ClearITPendingBit (TIM3,TIM_IT_CC2); } #endif #if USE_TIM3_CH3 // TIM3_CH3上升沿捕获中断 if ( TIM_GetITStatus (TIM3, TIM_IT_CC3 ) != RESET) { if ( TIM_ICUserValue[2].Capture_StartFlag == 0 ) { TIM_ICUserValue[2].Capture_CNTValue = TIM3->CNT; TIM_ICUserValue[2].Capture_CcrValue = 0; TIM_ICUserValue[2].Capture_StartFlag = 1; } else { TIM_ICUserValue[2].Capture_CcrValue = TIM3->CCR3-TIM_ICUserValue[2].Capture_CNTValue; TIM_ICUserValue[2].Capture_StartFlag = 0; TIM_ICUserValue[2].Capture_FinishFlag = 1; TIM_ICUserValue[2].Time_OutFlag = 0; } TIM_ClearITPendingBit (TIM3,TIM_IT_CC3); } #endif #if USE_TIM3_CH4 // TIM3_CH4上升沿捕获中断 if ( TIM_GetITStatus (TIM3, TIM_IT_CC4 ) != RESET) { if ( TIM_ICUserValue[3].Capture_StartFlag == 0 ) { TIM_ICUserValue[3].Capture_CNTValue = TIM3->CNT; TIM_ICUserValue[3].Capture_CcrValue = 0; TIM_ICUserValue[3].Capture_StartFlag = 1; } else { TIM_ICUserValue[3].Capture_CcrValue = TIM_GetCapture4(TIM3)-TIM_ICUserValue[3].Capture_CNTValue; TIM_ICUserValue[3].Capture_StartFlag = 0; TIM_ICUserValue[3].Capture_FinishFlag = 1; TIM_ICUserValue[3].Time_OutFlag = 0; } TIM_ClearITPendingBit (TIM3,TIM_IT_CC4); } #endif } 结果处理 这个函数你可以放到 main 函数里,我只是把它的结果用串口打印出来(串口部分请自行解决,网上的例子一大堆) void PWM_IC_Print(void) { double fre; // TIM 计数器的驱动时钟 uint32_t TIM_PscCLK = 72000000 / (71+1); int i = 0; for(i = 0;i<8;i++) { //超时判断 if(TIM_ICUserValue.Time_OutFlag == 1) continue; //判断是否捕获完成 if(TIM_ICUserValue.Capture_FinishFlag == 1) { // 计算一个周期的计数器的值 fre = (TIM_ICUserValue.Capture_CcrValue+1); //滤波器,我这里只留下了我需要的频段,具体值可以任意修改 //下面的值的计算方法是 (f(TIM3)/目标频率) if(fre>10000||fre<500) continue; // 打印频率,你想怎么处理,请自便 printf ( "rn %d号输入测得频率: %f hzrn",i,1/(fre/TIM_PscCLK)); //清除标志 TIM_ICUserValue.Capture_FinishFlag = 0; } } } 下面是实际的测试结果 我是用TIM2产生了一个200hz的方波,测了一下,还可以 然后连起来,PA6(TIM3 CH1) 接 PA2(待测信号),棕色那根是GND 打开串口调试助手 测试一下多路输入,1个200hz,2个1000hz |
|
|
|
只有小组成员才能发言,加入小组>>
3294 浏览 9 评论
2970 浏览 16 评论
3473 浏览 1 评论
9023 浏览 16 评论
4061 浏览 18 评论
1140浏览 3评论
589浏览 2评论
const uint16_t Tab[10]={0}; const uint16_t *p; p = Tab;//报错是怎么回事?
579浏览 2评论
用NUC131单片机UART3作为打印口,但printf没有输出东西是什么原因?
2313浏览 2评论
NUC980DK61YC启动随机性出现Err-DDR是为什么?
1876浏览 2评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-12-4 03:23 , Processed in 1.593857 second(s), Total 79, Slave 59 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号