本文作者:Miler Shao
近日有个工程师网络留言反映:
最近在做一个项目关于输入捕捉的,使用的单片机为STM8S105,用到STM8的输入捕捉功能。利用库函数TIME1CH1 捕捉PE0的频率很准,但此程序基础上将PE0口的频率改为由 TIME2产生150HZPWM波形,同样使用TIME1CH1 捕捉,捕捉到的频率却不对。用示波器查看TIME2 产生的PWM很准的。怎么回事呢? 上面是该工程师的留言,咋看之下,红色语句貌似就是互相矛盾的表述。呵呵将就下,很多人表述个东西就是任性,让你猜。 顺便提醒下,如果在类似论坛或邮件咨询STMCU的技术问题时,最基本的一条要先把芯片型号说完整,不要说STM8,STM32,或者STM32F1,STM32F4等。对于STM8一定要说出包括数字在内的前10字符,对于STM32一定要说出包括数字在内的前11字符。这样人家才能看出该芯片的管脚数和FLASH容量大小,很多问题跟二者息息相关。再就是尽可能把问题描述清楚。
拉回来继续刚才的话题。他提供的信息除了上面一段话外,再就是系统时钟配置和定时器初始化代码以及输入捕捉测量的函数。他的意思应该是说在利用TIM1的CH1的捕捉功能对外来脉冲做频率测量时,当待测信号频率低到一定程度时就测不准,频率较高时则正常。
void TimeCapture(void) { TIM1_ICPolarity = TIM1_ICPOLARITY_FALLING TIM1_ICPrescaler = TIM1_ICPSC_DIV8 。。。【为了节省篇幅,此处省却部分初始化代码】 /* wait a capture on CC1 */ while((TIM1->SR1 & TIM1_FLAG_CC1) !=TIM1_FLAG_CC1); /* Get CCR1 value*/ ICValue1 = TIM1_GetCapture1(); TIM1_ClearFlag(TIM1_FLAG_CC1); /* wait a capture on cc1 */ while((TIM1->SR1 & TIM1_FLAG_CC1) !=TIM1_FLAG_CC1); /* Get CCR1 value*/ ICValue2 = TIM1_GetCapture1(); TIM1_ClearFlag(TIM1_FLAG_CC1); /* Compute clock frequency */ ClockFreq = (TIM1_ICPSC_DIV8* TIM1ClockFreq) / (ICValue2 - ICValue1); } 从代码来看,他使用的STM8S TIM1输入捕捉的例程,下降沿捕捉。结合他的描述和现象分析,代码配置应该没啥问题了。我看到他的初始化代码里使用16M HIS做系统时钟,供给TIM1的时钟没有分频,即16M。他对输入捕捉事件做了8分频,就是上面代码中的红色语句。 他做这个8分频,意味着每8个下降沿才捕捉1次。150Hz本来就够慢了的,还要每8个脉冲才捕捉一次,意味着相邻两次捕捉动作的间隔就更长。而这个例程代码并没有考虑到计数器溢出问题,或者说它是假设中间不会发生计数器溢出而设计的。
直觉和经验告诉我,很可能两次捕捉间隔过长导致中途有计数器溢出事件发生,如果这样自然测算不准了。于是提醒该他在做150Hz信号输入捕捉测量时中途可能发生计数器溢出,建议其提高待测信号频率验证。后来他反馈当待测信号频率提高到2.4K以上后就正确了【当然他这个数字是随意选定的】,意思是说频率高于一定数字后就OK了。 看来的确是因为相邻两次捕捉动作的间隔过长导致中途有计数器溢出事件。我们可以大致计算下,假设定时器时钟为16M,不发生溢出情况下能捕捉的最大间隔,即最慢频率应该是16M/65536=24.4Hz, 这是最理想的情况,如果刚好错过了第一个捕捉沿后就得等到下个周期的捕捉沿,要想不发生溢出,待测频率最少也得48Hz以上。 现在他的待测信号频率为150Hz时,加上做了捕捉事件8分频,实测信号频率相当于18.7Hz,远低于不发生溢出要求的最慢频率48Hz,不可避免的会发生溢出,导致测量错误。
在代码逻辑不变的情况下,我们可以考虑将定时器时钟适当做分频处理,当然不可无限制分频,得兼顾测量精度需求。至于那个捕捉预分频参数可以根据待测信号特征和测量需求调整。 总之,上面STM8S定时器捕捉参考例程有其使用前提或局限性的。例程是通过查询方式实现,每次开启定时器后必须立马进入捕捉,测量过程中还不可发生溢出,否则测量就出问题。如果有些应用场合中途溢出难免,那就得对代码做些调整。 其实,在STM32定时器应用时也可能碰到过类似问题。曾经有人反映用参考例程里的输入捕捉代码测量频率时发现待测信号低到一定程度时就出现测量不正确的问题。 下面是STM32F1系列固件库输入捕捉项目例程里的代码: STM32F10x_StdPeriph_Lib_V3.5.0\Project\STM32F10x_StdPeriph_Examples\TIM\InputCapture voidTIM3_IRQHandler(void) { if(TIM_GetITStatus(TIM3, TIM_IT_CC2) == SET) { /* Clear TIM3 Capture compare interruptpending bit */ TIM_ClearITPendingBit(TIM3, TIM_IT_CC2); if(CaptureNumber == 0) { /* Get the Input Capture value */ IC3ReadValue1 = TIM_GetCapture2(TIM3); CaptureNumber = 1; } else if(CaptureNumber == 1) { /* Get the Input Capture value */ IC3ReadValue2 = TIM_GetCapture2(TIM3); /* Capture computation */ if(IC3ReadValue2 > IC3ReadValue1) { Capture = (IC3ReadValue2 - IC3ReadValue1); //N=0,未发生溢出; } else { Capture = ((0xFFFF - IC3ReadValue1) +IC3ReadValue2); //N=1发生1次溢出; } /* Frequency computation */ TIM3Freq = (uint32_t) SystemCoreClock /Capture; CaptureNumber = 0; } } } 这个例程代码使用了捕捉中断,程序也是基于一个前提:待测信号时间长度不会长于65536个定时器计数脉冲,相比上面的STM8S的例程应该说有所改进,中途最多允许溢出一次。偶尔有些人对上面例程红色代码的第2条感到疑惑,那就是指中途发生过一次溢出的情形。比如第一次捕捉到的计数器的值是0Xeb3f, 中途发生过一次溢出后计数器重新开始计数,再第二次捕捉到的计算器的值完全比前一个捕捉值小。那有无可能中途只溢出一次,第2次捕捉到的数据比第一次还大呢?不可能,不信你可以把问题简化下。在纸上书写从0~9选择任一个数字开始的10个连续的数看看。如果测量时中途发生1次以上的溢出事件时,该例程就有问题了。
也就是说,STM32参考代码里该例程也是使用前提的,尽管说它能满足大部分的应用,作为代码还是其局限性,当然代码有局限性是绝对的,没局限性的代码犹如没缺陷的人一样不复存在。[就此打住,别扯太远了。] STM32定时器的工作频率高,很多情况下完全可以先做时钟预分频再做测量操作。如果个别应用情形下,计数器会不可避免地可能溢出多次怎么办呢?其实只要搞清了原理,代码怎么写完全你自己掌握。在两次捕捉之间,我们可以开启溢出中断,并对溢出中断次数计数,假设中间发生了N次溢出中断,始、止两个捕捉数据value1和value2。除第一个溢出不做满量程计算外,其它N-1 个溢出中断为满量程计数。 整个计数脉冲个数=(65536-value1)+(N-1)*65536+value2 上面等式中的N 可以是任意非负整数。不难看出当N=0和N=1时,整个计数脉冲个数跟上面ST官方参考例程里两处红色代码描述的是吻合一致的。 或许有细心的人看到,官方例程里用的是65535,我这里是65536。我认为这里应该是65536,不过具体到应用上,这1个脉冲误差完全可以忽略不计了。 上面提到的定时器都是假设其为16位的,所以计数器满量程计数个数为65536。其实STM32家族中有不少系列都有32位的定时器,比如STM32F0,STM32F3,STM32F4,STM32F7,STM32L4系列中都带32位定时器。 搞清了原理,具体的代码实现也就不难了,多了个溢出中断次数计数,两次捕捉间的计数脉冲个数统一用上面的等式即可。弄清了原理,就能自主地对参考性的东西做甄别和借鉴,然后自行调整相关软硬件设计达到满足应用需求之目的。 |